新增复制按钮图标来复制 ChatGPT 回复内容。Golang 后端实现为每个用户订阅聊天角色功能

This commit is contained in:
RockYang 2023-04-19 10:37:55 +08:00
parent 3bf83cd48c
commit 50ff591dbb
8 changed files with 227 additions and 54 deletions

View File

@ -393,6 +393,17 @@ func (s *Server) GetChatHistoryHandle(c *gin.Context) {
}
session := s.ChatSession[sessionId]
user, err := GetUser(session.Username)
if err != nil {
c.JSON(http.StatusOK, types.BizVo{Code: types.Failed, Message: "Invalid args"})
return
}
if v, ok := user.ChatRoles[data.Role]; !ok || v != 1 {
c.JSON(http.StatusOK, types.BizVo{Code: types.Failed, Message: "No permission to access the history of role " + data.Role})
return
}
history, err := GetChatHistory(session.Username, data.Role)
if err != nil {
c.JSON(http.StatusOK, types.BizVo{Code: types.Failed, Message: "No history message"})

View File

@ -2,16 +2,18 @@ package server
import (
"encoding/json"
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"openai/types"
"openai/utils"
"strings"
)
func (s *Server) TestHandle(c *gin.Context) {
roles := types.GetDefaultChatRole()
for _, v := range roles {
PutChatRole(v)
_ = PutChatRole(v)
}
c.JSON(http.StatusOK, types.BizVo{Code: types.Success, Data: GetChatRoles()})
@ -81,10 +83,11 @@ func (s *Server) SetDebugHandle(c *gin.Context) {
// AddUserHandle 添加 Username
func (s *Server) AddUserHandle(c *gin.Context) {
var data struct {
Name string `json:"name"`
MaxCalls int `json:"max_calls"`
EnableHistory bool `json:"enable_history"`
Term int `json:"term"` // 有效期
Name string `json:"name"`
MaxCalls int `json:"max_calls"`
EnableHistory bool `json:"enable_history"`
Term int `json:"term"` // 有效期
ChatRoles []string `json:"chat_roles"` // 订阅角色
}
err := json.NewDecoder(c.Request.Body).Decode(&data)
if err != nil {
@ -105,12 +108,28 @@ func (s *Server) AddUserHandle(c *gin.Context) {
return
}
var chatRoles = make(map[string]int)
if len(data.ChatRoles) > 0 {
if data.ChatRoles[0] == "all" { // 所有的角色
roles := GetChatRoles()
for key := range roles {
chatRoles[key] = 1
}
} else {
for _, key := range data.ChatRoles {
chatRoles[key] = 1
}
}
}
user := types.User{
Name: data.Name,
MaxCalls: data.MaxCalls,
RemainingCalls: data.MaxCalls,
EnableHistory: data.EnableHistory,
Term: data.Term,
ChatRoles: chatRoles,
Status: true}
err = PutUser(user)
if err != nil {
@ -124,10 +143,11 @@ func (s *Server) AddUserHandle(c *gin.Context) {
// BatchAddUserHandle 批量生成 Username
func (s *Server) BatchAddUserHandle(c *gin.Context) {
var data struct {
Number int `json:"number"`
MaxCalls int `json:"max_calls"`
EnableHistory bool `json:"enable_history"`
Term int `json:"term"`
Number int `json:"number"`
MaxCalls int `json:"max_calls"`
EnableHistory bool `json:"enable_history"`
Term int `json:"term"`
ChatRoles []string `json:"chat_roles"`
}
err := json.NewDecoder(c.Request.Body).Decode(&data)
if err != nil || data.MaxCalls <= 0 {
@ -135,6 +155,21 @@ func (s *Server) BatchAddUserHandle(c *gin.Context) {
return
}
var chatRoles = make(map[string]int)
if len(data.ChatRoles) > 0 {
if data.ChatRoles[0] == "all" { // 所有的角色
roles := GetChatRoles()
for key := range roles {
chatRoles[key] = 1
}
} else {
for _, key := range data.ChatRoles {
chatRoles[key] = 1
}
}
}
var users = make([]types.User, 0)
for i := 0; i < data.Number; i++ {
name := utils.RandString(12)
@ -148,6 +183,7 @@ func (s *Server) BatchAddUserHandle(c *gin.Context) {
RemainingCalls: data.MaxCalls,
EnableHistory: data.EnableHistory,
Term: data.Term,
ChatRoles: chatRoles,
Status: true}
err = PutUser(user)
if err == nil {
@ -204,6 +240,24 @@ func (s *Server) SetUserHandle(c *gin.Context) {
if v, ok := data["api_key"]; ok {
user.ApiKey = v.(string)
}
if v, ok := data["chat_roles"]; ok {
if roles, ok := v.([]interface{}); ok {
chatRoles := make(map[string]int)
if roles[0] == "all" {
roles := GetChatRoles()
for key := range roles {
chatRoles[key] = 1
}
} else {
for _, key := range roles {
key := strings.TrimSpace(fmt.Sprintf("%v", key))
chatRoles[key] = 1
}
}
user.ChatRoles = chatRoles
}
}
err = PutUser(*user)
if err != nil {
@ -299,12 +353,24 @@ func (s *Server) ListApiKeysHandle(c *gin.Context) {
// GetChatRoleListHandle 获取聊天角色列表
func (s *Server) GetChatRoleListHandle(c *gin.Context) {
sessionId := c.GetHeader(types.TokenName)
session := s.ChatSession[sessionId]
user, err := GetUser(session.Username)
if err != nil {
c.JSON(http.StatusOK, types.BizVo{Code: types.Failed, Message: "Hacker Access!!!"})
return
}
var rolesOrder = []string{"gpt", "teacher", "translator", "english_trainer", "weekly_report", "girl_friend",
"kong_zi", "lu_xun", "steve_jobs", "elon_musk", "red_book", "dou_yin", "programmer",
"seller", "good_comment", "psychiatrist", "artist"}
var res = make([]interface{}, 0)
var roles = GetChatRoles()
for _, k := range rolesOrder {
// 确认当前用户是否订阅了当前角色
if v, ok := user.ChatRoles[k]; !ok || v != 1 {
continue
}
if v, ok := roles[k]; ok && v.Enable {
res = append(res, struct {
Key string `json:"key"`

View File

@ -14,15 +14,16 @@ type Config struct {
}
type User struct {
Name string `json:"name"`
MaxCalls int `json:"max_calls"` // 最多调用次数,如果为 0 则表示不限制
RemainingCalls int `json:"remaining_calls"` // 剩余调用次数
EnableHistory bool `json:"enable_history"` // 是否启用聊天记录
Status bool `json:"status"` // 当前状态
Term int `json:"term" default:"30"` // 会员有效期,单位:天
ActiveTime int64 `json:"active_time"` // 激活时间
ExpiredTime int64 `json:"expired_time"` // 到期时间
ApiKey string `json:"api_key"` // OpenAI API KEY
Name string `json:"name"`
MaxCalls int `json:"max_calls"` // 最多调用次数,如果为 0 则表示不限制
RemainingCalls int `json:"remaining_calls"` // 剩余调用次数
EnableHistory bool `json:"enable_history"` // 是否启用聊天记录
Status bool `json:"status"` // 当前状态
Term int `json:"term" default:"30"` // 会员有效期,单位:天
ActiveTime int64 `json:"active_time"` // 激活时间
ExpiredTime int64 `json:"expired_time"` // 到期时间
ApiKey string `json:"api_key"` // OpenAI API KEY
ChatRoles map[string]int `json:"chat_roles"` // 当前用户已订阅的聊天角色 map[role_key] => 0/1
}
// Chat configs struct

View File

@ -6,7 +6,24 @@
<div class="chat-item">
<div class="triangle"></div>
<div class="content reply-content" :data-clipboard-text="orgContent" v-html="content"></div>
<div class="content-box">
<div class="content" v-html="content"></div>
<div class="tool-box">
<el-tooltip
class="box-item"
effect="dark"
content="复制回答"
placement="top"
>
<el-button type="info" class="copy-reply" :data-clipboard-text="orgContent" plain>
<el-icon>
<DocumentCopy/>
</el-icon>
</el-button>
</el-tooltip>
</div>
</div>
</div>
</div>
</template>
@ -14,9 +31,11 @@
<script>
import {defineComponent} from "vue"
import {randString} from "@/utils/libs";
import {DocumentCopy} from "@element-plus/icons-vue";
export default defineComponent({
name: 'ChatReply',
components: {DocumentCopy},
props: {
content: {
type: String,
@ -70,28 +89,45 @@ export default defineComponent({
top: 13px;
}
.content {
min-height 20px;
word-break break-word;
padding: 8px 10px;
color var(--content-color)
background-color: #fff;
font-size: var(--content-font-size);
border-radius: 5px;
.content-box {
p:last-child {
margin-bottom: 0
display flex
flex-direction row
.content {
min-height 20px;
word-break break-word;
padding: 8px 10px;
color var(--content-color)
background-color: #fff;
font-size: var(--content-font-size);
border-radius: 5px;
p:last-child {
margin-bottom: 0
}
p:first-child {
margin-top 0
}
p > code {
color #cc0000
background-color #f1f1f1
}
}
p:first-child {
margin-top 0
}
.tool-box {
padding-left 10px;
font-size 16px;
p > code {
color #cc0000
background-color #f1f1f1
.el-button {
height 20px
padding 5px 2px;
}
}
}
}
}

View File

@ -34,7 +34,7 @@ const routes = [
},
{
name: 'free', path: '/free', component: ChatFree, meta: {
title: 'ChatGPT 免费版'
title: 'ChatGPT 通用免费版'
}
},
{

View File

@ -179,7 +179,7 @@ export default defineComponent({
return;
}
const clipboard = new Clipboard('.reply-content');
const clipboard = new Clipboard('.copy-reply');
clipboard.on('success', () => {
ElMessage.success('复制成功!');
})

View File

@ -3,19 +3,20 @@
<div class="sidebar" id="sidebar">
<nav>
<ul>
<li class="new-chat"><a>
<li class="new-chat" @click="newChat"><a>
<span class="icon"><el-icon><Plus/></el-icon></span>
<span class="text">新建会话</span>
<span class="btn" @click="toggleSidebar"><el-button size="small" type="info" circle><el-icon><CloseBold/></el-icon></el-button></span>
</a></li>
<li><a>
<li v-for="session in sessionList" :key="session.id"><a>
<span class="icon"><el-icon><ChatRound/></el-icon></span>
<span class="text">会话一</span>
</a></li>
<li class="active"><a>
<span class="icon"><el-icon><ChatRound/></el-icon></span>
<span class="text">会话二</span>
<span class="text">{{ session.title }}</span>
<span class="btn">
<el-icon title="编辑"><Edit/></el-icon>
<el-icon title="删除会话"><Delete/></el-icon>
</span>
</a></li>
</ul>
</nav>
</div>
@ -121,7 +122,16 @@
<script>
import {defineComponent, nextTick} from "vue"
import {ChatRound, CloseBold, Fold, Lock, Plus, RefreshRight, VideoPause} from "@element-plus/icons-vue";
import {
ChatRound,
CloseBold,
Delete, Edit,
Fold,
Lock,
Plus,
RefreshRight,
VideoPause
} from "@element-plus/icons-vue";
import {httpGet, httpPost} from "@/utils/http";
import hl from "highlight.js";
import ChatReply from "@/components/ChatReply.vue";
@ -134,7 +144,19 @@ import Clipboard from "clipboard";
// ChatGPT
export default defineComponent({
name: 'ChatFree',
components: {CloseBold, Lock, VideoPause, RefreshRight, ChatPrompt, ChatReply, ChatRound, Plus, Fold},
components: {
Edit,
Delete,
CloseBold,
Lock,
VideoPause,
RefreshRight,
ChatPrompt,
ChatReply,
ChatRound,
Plus,
Fold
},
data() {
return {
chatData: [],
@ -144,6 +166,10 @@ export default defineComponent({
showLoginDialog: false,
role: 'gpt',
replyIcon: 'images/avatar/gpt.png', //
sessionList: [{
id: randString(32),
title: '响应式页面布局代码'
}], //
showStopGenerate: false,
showReGenerate: false,
@ -164,7 +190,7 @@ export default defineComponent({
mounted() {
this.fetchChatHistory();
const clipboard = new Clipboard('.reply-content');
const clipboard = new Clipboard('.copy-reply');
clipboard.on('success', () => {
ElMessage.success('复制成功!');
})
@ -455,6 +481,11 @@ export default defineComponent({
toggleSidebar: function () {
document.getElementById("sidebar").classList.toggle('show');
},
//
newChat: function () {
}
}
})
</script>
@ -510,8 +541,26 @@ export default defineComponent({
overflow hidden
}
.btn {
//display none
position absolute
right 0;
top 2px;
.el-icon {
margin-left 5px;
color #9f9f9f
}
.el-icon:hover {
color #ffffff
}
}
}
}
li.active {
@ -521,11 +570,17 @@ export default defineComponent({
li.new-chat {
border: 1px solid #4A4B4D;
.btn {
display none
position absolute
right -2px;
top -2px;
a {
.btn {
display none
right -2px;
top -2px;
.el-icon {
margin-left 0;
color #ffffff
}
}
}
}
@ -678,6 +733,10 @@ export default defineComponent({
}
}
.row-center {
justify-content center
}
/* 移动端适配 */
@media (max-width: 768px) {
.chat-free-page {

View File

@ -439,8 +439,8 @@ export default defineComponent({
hl.highlightElement(block)
})
})
}).catch(() => {
// console.error(e.message)
}).catch((e) => {
console.error(e.message)
})
},