mirror of
https://gitee.com/blackfox/geekai.git
synced 2024-12-02 12:17:42 +08:00
feat: add copy code btn in chat page, fixed bug for code wrap in model of ChatGLM and XunFei
This commit is contained in:
parent
5fee3a9288
commit
251fe626f2
@ -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"` // 订单支付页面说明文字
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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, '</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
|
||||
|
@ -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']
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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">
|
||||
|
Loading…
Reference in New Issue
Block a user