feat: add copy code btn in chat page, fixed bug for code wrap in model of ChatGLM and XunFei

This commit is contained in:
RockYang 2023-11-22 17:00:43 +08:00
parent 5fee3a9288
commit 251fe626f2
10 changed files with 129 additions and 60 deletions

View File

@ -132,19 +132,20 @@ type ModelAPIConfig struct {
}
type SystemConfig struct {
Title string `json:"title"`
AdminTitle string `json:"admin_title"`
Models []string `json:"models"`
UserInitCalls int `json:"user_init_calls"` // 新用户注册默认总送多少次调用
InitImgCalls int `json:"init_img_calls"`
VipMonthCalls int `json:"vip_month_calls"` // 会员每个赠送的调用次数
EnabledRegister bool `json:"enabled_register"`
EnabledMsg bool `json:"enabled_msg"` // 启用短信验证码服务
EnabledDraw bool `json:"enabled_draw"` // 启动 AI 绘画功能
RewardImg string `json:"reward_img"` // 众筹收款二维码地址
EnabledFunction bool `json:"enabled_function"` // 启用 API 函数功能
EnabledReward bool `json:"enabled_reward"` // 启用众筹功能
EnabledAlipay bool `json:"enabled_alipay"` // 是否启用支付宝支付通道
OrderPayTimeout int `json:"order_pay_timeout"` //订单支付超时时间
DefaultModels []string `json:"default_models"` // 默认开通的 AI 模型
Title string `json:"title"`
AdminTitle string `json:"admin_title"`
Models []string `json:"models"`
UserInitCalls int `json:"user_init_calls"` // 新用户注册默认总送多少次调用
InitImgCalls int `json:"init_img_calls"`
VipMonthCalls int `json:"vip_month_calls"` // 会员每个赠送的调用次数
EnabledRegister bool `json:"enabled_register"`
EnabledMsg bool `json:"enabled_msg"` // 启用短信验证码服务
EnabledDraw bool `json:"enabled_draw"` // 启动 AI 绘画功能
RewardImg string `json:"reward_img"` // 众筹收款二维码地址
EnabledFunction bool `json:"enabled_function"` // 启用 API 函数功能
EnabledReward bool `json:"enabled_reward"` // 启用众筹功能
EnabledAlipay bool `json:"enabled_alipay"` // 是否启用支付宝支付通道
OrderPayTimeout int `json:"order_pay_timeout"` //订单支付超时时间
DefaultModels []string `json:"default_models"` // 默认开通的 AI 模型
OrderPayInfoText string `json:"order_pay_info_text"` // 订单支付页面说明文字
}

View File

@ -2,6 +2,7 @@ package admin
import (
"chatplus/core"
"chatplus/core/types"
"chatplus/handler"
"chatplus/store/model"
"chatplus/utils/resp"
@ -22,10 +23,10 @@ func NewDashboardHandler(app *core.AppServer, db *gorm.DB) *DashboardHandler {
}
type statsVo struct {
Users int64 `json:"users"`
Chats int64 `json:"chats"`
Tokens int64 `json:"tokens"`
Rewards float64 `json:"rewards"`
Users int64 `json:"users"`
Chats int64 `json:"chats"`
Tokens int `json:"tokens"`
Income float64 `json:"income"`
}
func (h *DashboardHandler) Stats(c *gin.Context) {
@ -47,17 +48,24 @@ func (h *DashboardHandler) Stats(c *gin.Context) {
}
// tokens took stats
var tokenCount int64
res = h.db.Model(&model.HistoryMessage{}).Select("sum(tokens) as total").Where("created_at > ?", zeroTime).Scan(&tokenCount)
if res.Error == nil {
stats.Tokens = tokenCount
var historyMessages []model.HistoryMessage
res = h.db.Where("created_at > ?", zeroTime).Find(&historyMessages)
for _, item := range historyMessages {
stats.Tokens += item.Tokens
}
// reward revenue
var amount float64
res = h.db.Model(&model.Reward{}).Select("sum(amount) as total").Where("created_at > ?", zeroTime).Scan(&amount)
if res.Error == nil {
stats.Rewards = amount
// 众筹收入
var rewards []model.Reward
res = h.db.Where("created_at > ?", zeroTime).Find(&rewards)
for _, item := range rewards {
stats.Income += item.Amount
}
// 订单收入
var orders []model.Order
res = h.db.Where("status = ?", types.OrderPaidSuccess).Where("created_at > ?", zeroTime).Find(&orders)
for _, item := range orders {
stats.Income += item.Amount
}
resp.SUCCESS(c, stats)
}

View File

@ -84,6 +84,11 @@ func (h *ChatHandler) sendBaiduMessage(
content = line[5:]
}
// 处理代码换行
if len(content) == 0 {
content = "\n"
}
var resp baiduResp
err := utils.JsonDecode(content, &resp)
if err != nil {

View File

@ -71,6 +71,10 @@ func (h *ChatHandler) sendChatGLMMessage(
if strings.HasPrefix(line, "data:") {
content = line[5:]
}
// 处理代码换行
if len(content) == 0 {
content = "\n"
}
switch event {
case "add":
if len(contents) == 0 {

View File

@ -138,6 +138,10 @@ func (h *ChatHandler) sendXunFeiMessage(
}
content = result.Payload.Choices.Text[0].Content
// 处理代码换行
if len(content) == 0 {
content = "\n"
}
contents = append(contents, content)
// 第一个结果
if result.Payload.Choices.Status == 0 {

View File

@ -31,11 +31,11 @@
<script>
import {defineComponent} from "vue"
import {Clock, DocumentCopy} from "@element-plus/icons-vue";
import {Clock, DocumentCopy, Position} from "@element-plus/icons-vue";
export default defineComponent({
name: 'ChatReply',
components: {Clock, DocumentCopy},
components: {Position, Clock, DocumentCopy},
props: {
content: {
type: String,
@ -126,6 +126,39 @@ export default defineComponent({
margin-top 0
}
.code-container {
position relative
.hljs {
border-radius 10px
line-height 1.5
}
.copy-code-btn {
position: absolute;
right 10px
top 10px
cursor pointer
font-size 12px
color #c1c1c1
&:hover {
color #20a0ff
}
}
}
.lang-name {
position absolute;
right 10px
bottom 50px
padding 2px 6px 4px 6px
background-color #444444
border-radius 10px
color #00e0e0
}
//
table {

View File

@ -117,7 +117,7 @@
<span>导出会话</span>
</el-button>
<el-button type="warning" @click="showFeekbackDialog = true">
<el-button type="warning" @click="showFeedbackDialog = true">
<el-icon>
<Promotion/>
</el-icon>
@ -202,15 +202,16 @@
</el-container>
<el-dialog
v-model="showFeekbackDialog"
v-model="showFeedbackDialog"
:show-close="true"
width="300px"
width="340px"
title="意见反馈"
>
<el-alert type="info" :closable="false">
<div style="font-size: 14px">
如果您对本项目有任何改进意见您可以通过 Github
<el-link style="color: #f56c6c; font-weight: bold;" href="https://github.com/yangjian102621/chatgpt-plus">
<el-link style="color: #f56c6c; font-weight: bold;"
href="https://github.com/yangjian102621/chatgpt-plus/issues">
提交改进意见
</el-link>
或者通过扫描下面的微信二维码加入 AI 技术交流群
@ -277,7 +278,7 @@ const showConfigDialog = ref(false);
const isLogin = ref(false)
const showHello = ref(true)
const textInput = ref(null)
const showFeekbackDialog = ref(false)
const showFeedbackDialog = ref(false)
if (isMobile()) {
router.replace("/mobile")
@ -333,7 +334,7 @@ onMounted(() => {
router.push('/login')
});
const clipboard = new Clipboard('.copy-reply');
const clipboard = new Clipboard('.copy-reply, .copy-code-btn');
clipboard.on('success', () => {
ElMessage.success('复制成功!');
})
@ -469,6 +470,28 @@ const removeChat = function (event, chat) {
curOpt.value = 'remove';
}
const md = require('markdown-it')({
breaks: true,
highlight: function (str, lang) {
const codeIndex = parseInt(Date.now()) + Math.floor(Math.random() * 10000000)
//
const copyBtn = `<span class="copy-code-btn" data-clipboard-action="copy" data-clipboard-target="#copy-target-${codeIndex}">复制</span>
<textarea style="position: absolute;top: -9999px;left: -9999px;z-index: -9999;" id="copy-target-${codeIndex}">${str.replace(/<\/textarea>/g, '&lt;/textarea>')}</textarea>`
if (lang && hl.getLanguage(lang)) {
const langHtml = `<span class="lang-name">${lang}</span>`
//
const preCode = hl.highlight(lang, str, true).value
// pre
return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code>${copyBtn} ${langHtml}</pre>`
}
//
const preCode = md.utils.escapeHtml(str)
// pre
return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code>${copyBtn}</pre>`
}
});
// socket
const prompt = ref('');
const showStopGenerate = ref(false); //
@ -542,7 +565,6 @@ const connect = function (chat_id, role_id) {
} else if (data.type === "mj") {
disableInput(true)
const content = data.content;
const md = require('markdown-it')({breaks: true});
content.html = md.render(content.content)
let key = content.key
// fixed bug: Upscale Variation
@ -596,19 +618,9 @@ const connect = function (chat_id, role_id) {
} else {
lineBuffer.value += data.content;
const md = require('markdown-it')({breaks: true});
const reply = chatData.value[chatData.value.length - 1]
reply['orgContent'] = lineBuffer.value;
reply['content'] = md.render(lineBuffer.value);
nextTick(() => {
hl.configure({ignoreUnescapedHTML: true})
const lines = document.querySelectorAll('.chat-line');
const blocks = lines[lines.length - 1].querySelectorAll('pre code');
blocks.forEach((block) => {
hl.highlightElement(block)
})
})
}
//
nextTick(() => {
@ -748,9 +760,6 @@ const loadChatHistory = function (chatId) {
return
}
showHello.value = false
const md = require('markdown-it')({breaks: true});
// md.use(require('markdown-it-copy')); //
for (let i = 0; i < data.length; i++) {
if (data[i].type === "prompt") {
chatData.value.push(data[i]);
@ -768,11 +777,6 @@ const loadChatHistory = function (chatId) {
}
nextTick(() => {
hl.configure({ignoreUnescapedHTML: true})
const blocks = document.querySelector("#chat-box").querySelectorAll('pre code');
blocks.forEach((block) => {
hl.highlightElement(block)
})
document.getElementById('chat-box').scrollTo(0, document.getElementById('chat-box').scrollHeight)
})
loading.value = false

View File

@ -33,10 +33,9 @@
<el-col :span="17">
<div class="product-box">
<div class="info">
<div class="info" v-if="orderPayInfoText !== ''">
<el-alert type="info" show-icon :closable="false" effect="dark">
<strong>说明:</strong> 成为本站会员后每月有500次对话额度50 AI 绘画额度限制下月1号解除若在期间超过次数后可单独购买点卡
当月充值的点卡有效期可以延期到下个月底
<strong>说明:</strong> {{ orderPayInfoText }}
</el-alert>
</div>
@ -176,6 +175,7 @@ const activeOrderNo = ref("")
const countDown = ref(null)
const orderTimeout = ref(1800)
const loading = ref(true)
const orderPayInfoText = ref("")
onMounted(() => {
@ -194,6 +194,7 @@ onMounted(() => {
httpGet("/api/admin/config/get?key=system").then(res => {
rewardImg.value = res.data['reward_img']
enableReward.value = res.data['enabled_reward']
orderPayInfoText.value = res.data['order_pay_info_text']
if (res.data['order_pay_timeout'] > 0) {
orderTimeout.value = res.data['order_pay_timeout']
}

View File

@ -47,7 +47,7 @@
<i class="iconfont icon-reward"></i>
</el-icon>
<div class="grid-cont-right">
<div class="grid-num">{{ stats.rewards }}</div>
<div class="grid-num">{{ stats.income }}</div>
<div>今日入账</div>
</div>
</div>
@ -70,7 +70,7 @@ httpGet('/api/admin/dashboard/stats').then((res) => {
stats.value.users = res.data.users
stats.value.chats = res.data.chats
stats.value.tokens = res.data.tokens
stats.value.rewards = res.data.rewards
stats.value.income = res.data.income
loading.value = false
}).catch((e) => {
ElMessage.error("获取统计数据失败:" + e.message)

View File

@ -120,6 +120,15 @@
</div>
</div>
</el-form-item>
<el-form-item label="会员充值说明" prop="order_pay_info_text">
<el-input
v-model="system['order_pay_info_text']"
:autosize="{ minRows: 3, maxRows: 10 }"
type="textarea"
placeholder="请输入会员充值说明文字,比如介绍会员计划"
/>
</el-form-item>
<el-form-item label="默认AI模型" prop="default_models">
<template #default>
<div class="tip-input">