mirror of
https://github.com/yangjian102621/geekai.git
synced 2025-11-12 04:03:42 +08:00
支持按次收费的 OpenAI 实时语音通话功能
This commit is contained in:
@@ -6,6 +6,6 @@ VUE_APP_ADMIN_USER=admin
|
||||
VUE_APP_ADMIN_PASS=admin123
|
||||
VUE_APP_KEY_PREFIX=GeekAI_DEV_
|
||||
VUE_APP_TITLE="Geek-AI 创作系统"
|
||||
VUE_APP_VERSION=v4.1.6
|
||||
VUE_APP_VERSION=v4.1.7
|
||||
VUE_APP_DOCS_URL=https://docs.geekai.me
|
||||
VUE_APP_GIT_URL=https://github.com/yangjian102621/geekai
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
VUE_APP_API_HOST=
|
||||
VUE_APP_WS_HOST=
|
||||
VUE_APP_KEY_PREFIX=GeekAI_
|
||||
VUE_APP_VERSION=v4.1.6
|
||||
VUE_APP_VERSION=v4.1.7
|
||||
VUE_APP_DOCS_URL=https://docs.geekai.me
|
||||
VUE_APP_GIT_URL=https://github.com/yangjian102621/geekai
|
||||
|
||||
@@ -1,14 +1,74 @@
|
||||
.el-form-item__content {
|
||||
.tip-input {
|
||||
display flex
|
||||
width 100%
|
||||
.form {
|
||||
.el-form-item__label {
|
||||
.label-title {
|
||||
display flex
|
||||
align-items center
|
||||
|
||||
.el-input,.el-select,.el-switch {
|
||||
margin-right 10px
|
||||
}
|
||||
|
||||
.info {
|
||||
margin-top 2px
|
||||
.el-icon {
|
||||
margin-left 5px
|
||||
cursor pointer
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-form-item__content {
|
||||
width 100%
|
||||
|
||||
.uploader-icon {
|
||||
font-size 24px
|
||||
position relative
|
||||
top 3px
|
||||
}
|
||||
|
||||
.tip-input-line {
|
||||
.tip {
|
||||
margin-top 10px
|
||||
color #c1c1c1
|
||||
font-size 12px;
|
||||
line-height 1.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-input {
|
||||
width 100%
|
||||
}
|
||||
|
||||
|
||||
.text {
|
||||
font-size 14px
|
||||
}
|
||||
|
||||
.active-info {
|
||||
line-height 1.5
|
||||
padding 10px 0 30px 0
|
||||
}
|
||||
|
||||
.el-descriptions {
|
||||
margin-bottom 20px
|
||||
|
||||
.el-icon {
|
||||
font-size 18px
|
||||
}
|
||||
|
||||
.selected {
|
||||
color #0bc15f
|
||||
}
|
||||
|
||||
.closed {
|
||||
color #da0d54
|
||||
}
|
||||
|
||||
.text {
|
||||
margin-left 10px
|
||||
font-size 12px
|
||||
color #999999
|
||||
position: relative;
|
||||
top -5px
|
||||
}
|
||||
}
|
||||
|
||||
.el-alert {
|
||||
margin-bottom 15px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,13 +345,14 @@
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
.tool-item-btn{
|
||||
|
||||
.iconfont{
|
||||
|
||||
.iconfont{
|
||||
font-size: 19px;
|
||||
|
||||
cursor pointer
|
||||
background-color: var(--chat-content-bg);
|
||||
padding: 5px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.add-new{
|
||||
|
||||
@@ -172,6 +172,58 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
.w-100 {
|
||||
width 100%
|
||||
}
|
||||
.mr-1 {
|
||||
margin-right 0.5rem
|
||||
}
|
||||
|
||||
.mr-2 {
|
||||
margin-right 1rem
|
||||
}
|
||||
|
||||
.ml-1 {
|
||||
margin-left 0.5rem
|
||||
}
|
||||
|
||||
.ml-2 {
|
||||
margin-left 1rem
|
||||
}
|
||||
|
||||
.d-flex {
|
||||
display flex !important
|
||||
}
|
||||
|
||||
.justify-center {
|
||||
justify-content center
|
||||
}
|
||||
.justify-between {
|
||||
justify-content space-between
|
||||
}
|
||||
|
||||
.justify-end {
|
||||
justify-content flex-end
|
||||
}
|
||||
|
||||
.align-center {
|
||||
align-items center
|
||||
}
|
||||
|
||||
|
||||
|
||||
.p-1 {
|
||||
padding 0.5rem
|
||||
}
|
||||
|
||||
.p-2 {
|
||||
padding 1rem
|
||||
}
|
||||
|
||||
.m-1 {
|
||||
margin 0.5rem
|
||||
}
|
||||
|
||||
.m-2 {
|
||||
margin 1rem
|
||||
}
|
||||
|
||||
@@ -6,23 +6,14 @@
|
||||
</div>
|
||||
|
||||
<div class="chat-item">
|
||||
<div
|
||||
class="content"
|
||||
v-html="md.render(processContent(data.content))"
|
||||
></div>
|
||||
<div class="content" v-html="md.render(processContent(data.content))"></div>
|
||||
<div class="bar" v-if="data.created_at">
|
||||
<span class="bar-item"
|
||||
><el-icon><Clock /></el-icon>
|
||||
{{ dateFormat(data.created_at) }}</span
|
||||
><el-icon><Clock /></el-icon> {{ dateFormat(data.created_at) }}</span
|
||||
>
|
||||
<span class="bar-item">tokens: {{ data.tokens }}</span>
|
||||
<span class="bar-item">
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="dark"
|
||||
content="复制回答"
|
||||
placement="bottom"
|
||||
>
|
||||
<el-tooltip class="box-item" effect="dark" content="复制回答" placement="bottom">
|
||||
<el-icon class="copy-reply" :data-clipboard-text="data.content">
|
||||
<DocumentCopy />
|
||||
</el-icon>
|
||||
@@ -30,23 +21,13 @@
|
||||
</span>
|
||||
<span v-if="!readOnly">
|
||||
<span class="bar-item" @click="reGenerate(data.prompt)">
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="dark"
|
||||
content="重新生成"
|
||||
placement="bottom"
|
||||
>
|
||||
<el-tooltip class="box-item" effect="dark" content="重新生成" placement="bottom">
|
||||
<el-icon><Refresh /></el-icon>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
|
||||
<span class="bar-item" @click="synthesis(data.content)">
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="dark"
|
||||
content="生成语音朗读"
|
||||
placement="bottom"
|
||||
>
|
||||
<el-tooltip class="box-item" effect="dark" content="生成语音朗读" placement="bottom">
|
||||
<i class="iconfont icon-speaker"></i>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
@@ -75,24 +56,15 @@
|
||||
</div>
|
||||
<div class="chat-item">
|
||||
<div class="content-wrapper">
|
||||
<div
|
||||
class="content"
|
||||
v-html="md.render(processContent(data.content))"
|
||||
></div>
|
||||
<div class="content" v-html="md.render(processContent(data.content))"></div>
|
||||
</div>
|
||||
<div class="bar" v-if="data.created_at">
|
||||
<span class="bar-item"
|
||||
><el-icon><Clock /></el-icon>
|
||||
{{ dateFormat(data.created_at) }}</span
|
||||
><el-icon><Clock /></el-icon> {{ dateFormat(data.created_at) }}</span
|
||||
>
|
||||
<!-- <span class="bar-item">tokens: {{ data.tokens }}</span>-->
|
||||
<span class="bar-item bg">
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="dark"
|
||||
content="复制回答"
|
||||
placement="bottom"
|
||||
>
|
||||
<el-tooltip class="box-item" effect="dark" content="复制回答" placement="bottom">
|
||||
<el-icon class="copy-reply" :data-clipboard-text="data.content">
|
||||
<DocumentCopy />
|
||||
</el-icon>
|
||||
@@ -100,23 +72,13 @@
|
||||
</span>
|
||||
<span v-if="!readOnly">
|
||||
<span class="bar-item bg" @click="reGenerate(data.prompt)">
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="dark"
|
||||
content="重新生成"
|
||||
placement="bottom"
|
||||
>
|
||||
<el-tooltip class="box-item" effect="dark" content="重新生成" placement="bottom">
|
||||
<el-icon><Refresh /></el-icon>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
|
||||
<span class="bar-item bg" @click="synthesis(data.content)">
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="dark"
|
||||
content="生成语音朗读"
|
||||
placement="bottom"
|
||||
>
|
||||
<el-tooltip class="box-item" effect="dark" content="生成语音朗读" placement="bottom">
|
||||
<i class="iconfont icon-speaker"></i>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
@@ -140,17 +102,17 @@ const props = defineProps({
|
||||
icon: "",
|
||||
content: "",
|
||||
created_at: "",
|
||||
tokens: 0
|
||||
}
|
||||
tokens: 0,
|
||||
},
|
||||
},
|
||||
readOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
default: false,
|
||||
},
|
||||
listStyle: {
|
||||
type: String,
|
||||
default: "list"
|
||||
}
|
||||
default: "list",
|
||||
},
|
||||
});
|
||||
|
||||
const mathjaxPlugin = require("markdown-it-mathjax3");
|
||||
@@ -160,8 +122,7 @@ const md = require("markdown-it")({
|
||||
linkify: true,
|
||||
typographer: true,
|
||||
highlight: function (str, lang) {
|
||||
const codeIndex =
|
||||
parseInt(Date.now()) + Math.floor(Math.random() * 10000000);
|
||||
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(
|
||||
@@ -180,7 +141,7 @@ const md = require("markdown-it")({
|
||||
const preCode = md.utils.escapeHtml(str);
|
||||
// 将代码包裹在 pre 中
|
||||
return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code>${copyBtn}</pre>`;
|
||||
}
|
||||
},
|
||||
});
|
||||
md.use(mathjaxPlugin);
|
||||
|
||||
@@ -257,7 +218,7 @@ const reGenerate = (prompt) => {
|
||||
code {
|
||||
color:var(--theme-text-color-primary);
|
||||
background-color var(--el-color-primary-light-3)
|
||||
padding 0 3px;
|
||||
padding 3px 5px;
|
||||
border-radius 5px;
|
||||
}
|
||||
}
|
||||
@@ -349,7 +310,6 @@ const reGenerate = (prompt) => {
|
||||
|
||||
.bar-item {
|
||||
background-color var( --little-btn-bg);
|
||||
color #888
|
||||
padding 3px 5px;
|
||||
margin-right 10px;
|
||||
border-radius 5px;
|
||||
@@ -434,7 +394,8 @@ const reGenerate = (prompt) => {
|
||||
code {
|
||||
color:var(--theme-text-color-primary);
|
||||
background-color var( --little-btn-bg)
|
||||
padding 0 3px;
|
||||
padding 3px 5px;
|
||||
font-weight 600;
|
||||
border-radius 5px;
|
||||
}
|
||||
}
|
||||
@@ -526,7 +487,6 @@ const reGenerate = (prompt) => {
|
||||
padding 10px 10px 10px 0;
|
||||
|
||||
.bar-item {
|
||||
color #888
|
||||
padding 3px 5px;
|
||||
margin-right 10px;
|
||||
border-radius 5px;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Util lib functions
|
||||
*/
|
||||
import {showConfirmDialog, showFailToast, showSuccessToast, showToast} from "vant";
|
||||
import {showConfirmDialog, showFailToast, showSuccessToast, showToast, showLoadingToast, closeToast} from "vant";
|
||||
import {isMobile} from "@/utils/libs";
|
||||
import {ElMessage} from "element-plus";
|
||||
|
||||
@@ -41,3 +41,11 @@ export function showMessageError(message) {
|
||||
ElMessage.error(message)
|
||||
}
|
||||
}
|
||||
|
||||
export function showLoading(message = '正在处理...') {
|
||||
showLoadingToast({ message: message, forbidClick: true, duration: 0 })
|
||||
}
|
||||
|
||||
export function closeLoading() {
|
||||
closeToast()
|
||||
}
|
||||
@@ -6,12 +6,12 @@
|
||||
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
import axios from 'axios'
|
||||
import {getAdminToken, getSessionId, getUserToken, removeAdminToken, removeUserToken} from "@/store/session";
|
||||
import {getAdminToken, getUserToken, removeAdminToken, removeUserToken} from "@/store/session";
|
||||
|
||||
axios.defaults.timeout = 180000
|
||||
axios.defaults.baseURL = process.env.VUE_APP_API_HOST
|
||||
axios.defaults.withCredentials = true;
|
||||
axios.defaults.headers.post['Content-Type'] = 'application/json'
|
||||
//axios.defaults.headers.post['Content-Type'] = 'application/json'
|
||||
|
||||
// HTTP拦截器
|
||||
axios.interceptors.request.use(
|
||||
@@ -81,4 +81,19 @@ export function httpDownload(url) {
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export function httpPostDownload(url, data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios({
|
||||
method: 'POST',
|
||||
url: url,
|
||||
data: data,
|
||||
responseType: 'blob' // 将响应类型设置为 `blob`
|
||||
}).then(response => {
|
||||
resolve(response)
|
||||
}).catch(err => {
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -3,21 +3,15 @@
|
||||
<el-container>
|
||||
<el-aside>
|
||||
<div class="media-page">
|
||||
<!-- <el-button @click="_newChat" color="#21aa93">
|
||||
<el-button @click="_newChat" type="primary" style="margin-bottom: 10px">
|
||||
<el-icon style="margin-right: 5px">
|
||||
<Plus/>
|
||||
<Plus />
|
||||
</el-icon>
|
||||
新建对话
|
||||
</el-button> -->
|
||||
</el-button>
|
||||
|
||||
<div class="search-box">
|
||||
<el-input
|
||||
v-model="chatName"
|
||||
placeholder="搜索会话"
|
||||
@keyup="searchChat($event)"
|
||||
style=""
|
||||
class="search-input"
|
||||
>
|
||||
<el-input v-model="chatName" placeholder="搜索会话" @keyup="searchChat($event)" style="" class="search-input">
|
||||
<template #prefix>
|
||||
<el-icon class="el-input__icon">
|
||||
<Search />
|
||||
@@ -28,14 +22,7 @@
|
||||
|
||||
<div class="content" :style="{ height: leftBoxHeight + 'px' }">
|
||||
<el-row v-for="chat in chatList" :key="chat.chat_id">
|
||||
<div
|
||||
:class="
|
||||
chat.chat_id === chatId
|
||||
? 'chat-list-item active'
|
||||
: 'chat-list-item'
|
||||
"
|
||||
@click="loadChat(chat)"
|
||||
>
|
||||
<div :class="chat.chat_id === chatId ? 'chat-list-item active' : 'chat-list-item'" @click="loadChat(chat)">
|
||||
<el-image :src="chat.icon" class="avatar" />
|
||||
<span class="chat-title-input" v-if="chat.edit">
|
||||
<el-input
|
||||
@@ -52,34 +39,23 @@
|
||||
|
||||
<span class="chat-opt">
|
||||
<el-dropdown trigger="click">
|
||||
<span
|
||||
class="el-dropdown-link"
|
||||
@click="stopPropagation($event)"
|
||||
>
|
||||
<span class="el-dropdown-link" @click="stopPropagation($event)">
|
||||
<el-icon><More /></el-icon>
|
||||
</span>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item
|
||||
:icon="Edit"
|
||||
@click="editChatTitle(chat)"
|
||||
>重命名</el-dropdown-item
|
||||
>
|
||||
<el-dropdown-item :icon="Edit" @click="editChatTitle(chat)">重命名</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
:icon="Delete"
|
||||
style="
|
||||
--el-text-color-regular: var(--el-color-danger);
|
||||
--el-dropdown-menuItem-hover-fill: #f8e1de;
|
||||
--el-dropdown-menuItem-hover-color: var(
|
||||
--el-color-danger
|
||||
);
|
||||
--el-dropdown-menuItem-hover-color: var(--el-color-danger);
|
||||
"
|
||||
@click="removeChat(chat)"
|
||||
>删除</el-dropdown-item
|
||||
>
|
||||
<el-dropdown-item :icon="Share" @click="shareChat(chat)"
|
||||
>分享</el-dropdown-item
|
||||
>
|
||||
<el-dropdown-item :icon="Share" @click="shareChat(chat)">分享</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
@@ -90,31 +66,14 @@
|
||||
</div>
|
||||
|
||||
<div class="tool-box">
|
||||
<el-button type="primary" size="small" @click="clearAllChats">
|
||||
<i class="iconfont icon-clear"></i> 清除所有对话
|
||||
</el-button>
|
||||
<el-button type="primary" size="small" @click="clearAllChats"> <i class="iconfont icon-clear"></i> 清除所有对话 </el-button>
|
||||
</div>
|
||||
</el-aside>
|
||||
<el-main
|
||||
v-loading="loading"
|
||||
element-loading-background="rgba(122, 122, 122, 0.3)"
|
||||
>
|
||||
<el-main v-loading="loading" element-loading-background="rgba(122, 122, 122, 0.3)">
|
||||
<div class="chat-container">
|
||||
<div class="chat-config">
|
||||
<el-select
|
||||
v-model="roleId"
|
||||
filterable
|
||||
placeholder="角色"
|
||||
@change="_newChat"
|
||||
class="role-select"
|
||||
style="width: 150px"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in roles"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
>
|
||||
<el-select v-model="roleId" filterable placeholder="角色" @change="_newChat" class="role-select" style="width: 150px">
|
||||
<el-option v-for="item in roles" :key="item.id" :label="item.name" :value="item.id">
|
||||
<div class="role-option">
|
||||
<el-image :src="item.icon"></el-image>
|
||||
<span>{{ item.name }}</span>
|
||||
@@ -122,43 +81,21 @@
|
||||
</el-option>
|
||||
</el-select>
|
||||
|
||||
<el-select
|
||||
v-model="modelID"
|
||||
filterable
|
||||
placeholder="模型"
|
||||
@change="_newChat"
|
||||
:disabled="disableModel"
|
||||
style="width: 150px"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in models"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
>
|
||||
<el-select v-model="modelID" filterable placeholder="模型" @change="_newChat" :disabled="disableModel" style="width: 150px">
|
||||
<el-option v-for="item in models" :key="item.id" :label="item.name" :value="item.id">
|
||||
<span>{{ item.name }}</span>
|
||||
<el-tag
|
||||
style="margin-left: 5px; position: relative; top: -2px"
|
||||
type="info"
|
||||
size="small"
|
||||
>{{ item.power }}算力
|
||||
</el-tag>
|
||||
<el-tag style="margin-left: 5px; position: relative; top: -2px" type="info" size="small">{{ item.power }}算力 </el-tag>
|
||||
</el-option>
|
||||
</el-select>
|
||||
<div class="flex-center">
|
||||
<el-dropdown :hide-on-click="false" trigger="click">
|
||||
<span class="setting"
|
||||
><i class="iconfont icon-plugin"></i
|
||||
></span>
|
||||
<span class="setting"><i class="iconfont icon-plugin"></i></span>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu class="tools-dropdown">
|
||||
<el-checkbox-group v-model="toolSelected">
|
||||
<el-dropdown-item v-for="item in tools" :key="item.id">
|
||||
<el-checkbox :value="item.id" :label="item.label" />
|
||||
<el-tooltip
|
||||
:content="item.description"
|
||||
placement="right"
|
||||
>
|
||||
<el-tooltip :content="item.description" placement="right">
|
||||
<el-icon><InfoFilled /></el-icon>
|
||||
</el-tooltip>
|
||||
</el-dropdown-item>
|
||||
@@ -175,27 +112,13 @@
|
||||
|
||||
<div>
|
||||
<div id="container" :style="{ height: mainWinHeight + 'px' }">
|
||||
<div
|
||||
class="chat-box"
|
||||
id="chat-box"
|
||||
:style="{ height: chatBoxHeight + 'px' }"
|
||||
>
|
||||
<div class="chat-box" id="chat-box" :style="{ height: chatBoxHeight + 'px' }">
|
||||
<div v-if="showHello">
|
||||
<welcome @send="autofillPrompt" />
|
||||
</div>
|
||||
<div v-for="item in chatData" :key="item.id" v-else>
|
||||
<chat-prompt
|
||||
v-if="item.type === 'prompt'"
|
||||
:data="item"
|
||||
:list-style="listStyle"
|
||||
/>
|
||||
<chat-reply
|
||||
v-else-if="item.type === 'reply'"
|
||||
:data="item"
|
||||
@regen="reGenerate"
|
||||
:read-only="false"
|
||||
:list-style="listStyle"
|
||||
/>
|
||||
<chat-prompt v-if="item.type === 'prompt'" :data="item" :list-style="listStyle" />
|
||||
<chat-reply v-else-if="item.type === 'reply'" :data="item" @regen="reGenerate" :read-only="false" :list-style="listStyle" />
|
||||
</div>
|
||||
|
||||
<back-top :right="30" :bottom="155" />
|
||||
@@ -248,66 +171,34 @@
|
||||
</textarea>
|
||||
</div>
|
||||
<div class="flex-between">
|
||||
<div @click="_newChat" class="flex-center add-new">
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="dark"
|
||||
content="新建会话"
|
||||
>
|
||||
<el-icon><CirclePlusFilled /></el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div class="flex little-btns">
|
||||
<span class="send-btn tool-item-btn">
|
||||
<!-- showStopGenerate -->
|
||||
<el-button
|
||||
type="info"
|
||||
v-if="showStopGenerate"
|
||||
@click="stopGenerate"
|
||||
plain
|
||||
>
|
||||
<el-icon>
|
||||
<VideoPause />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
<el-button
|
||||
@click="sendMessage"
|
||||
style="color: #754ff6"
|
||||
v-else
|
||||
>
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="dark"
|
||||
content="发送"
|
||||
>
|
||||
<el-icon><Promotion /></el-icon>
|
||||
</el-tooltip>
|
||||
</el-button>
|
||||
</span>
|
||||
<div class="flex-center little-btns">
|
||||
<span class="tool-item-btn" @click="realtimeChat">
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="dark"
|
||||
content="实时语音对话"
|
||||
>
|
||||
<el-tooltip class="box-item" effect="dark" :content="'实时语音对话,每次消耗' + config.advance_voice_power + '算力'">
|
||||
<i class="iconfont icon-mic-bold"></i>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
|
||||
<span class="tool-item-btn" v-if="isLogin">
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="dark"
|
||||
content="上传附件"
|
||||
>
|
||||
<file-select
|
||||
v-if="isLogin"
|
||||
:user-id="loginUser.id"
|
||||
@selected="insertFile"
|
||||
/>
|
||||
<el-tooltip class="box-item" effect="dark" content="上传附件">
|
||||
<file-select v-if="isLogin" :user-id="loginUser.id" @selected="insertFile" />
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex little-btns">
|
||||
<span class="send-btn tool-item-btn">
|
||||
<!-- showStopGenerate -->
|
||||
<el-button type="info" v-if="showStopGenerate" @click="stopGenerate" plain>
|
||||
<el-icon>
|
||||
<VideoPause />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
<el-button @click="sendMessage" style="color: #754ff6" v-else>
|
||||
<el-tooltip class="box-item" effect="dark" content="发送">
|
||||
<el-icon><Promotion /></el-icon>
|
||||
</el-tooltip>
|
||||
</el-button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -322,28 +213,21 @@
|
||||
</el-main>
|
||||
</el-container>
|
||||
|
||||
<el-dialog
|
||||
v-model="showNotice"
|
||||
:show-close="true"
|
||||
class="notice-dialog"
|
||||
title="网站公告"
|
||||
>
|
||||
<el-dialog v-model="showNotice" :show-close="true" class="notice-dialog" title="网站公告">
|
||||
<div class="notice">
|
||||
<div v-html="notice"></div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="notShow" type="primary"
|
||||
>我知道了,不再显示</el-button
|
||||
>
|
||||
<el-button @click="notShow" type="primary">我知道了,不再显示</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<ChatSetting :show="showChatSetting" @hide="showChatSetting = false" />
|
||||
|
||||
<el-dialog
|
||||
<!-- <el-dialog
|
||||
v-model="showConversationDialog"
|
||||
title="实时语音通话"
|
||||
:before-close="hangUp"
|
||||
@@ -353,6 +237,17 @@
|
||||
ref="conversationRef"
|
||||
:height="dialogHeight + 'px'"
|
||||
/>
|
||||
</el-dialog> -->
|
||||
|
||||
<el-dialog v-model="showConversationDialog" title="实时语音通话" :fullscreen="true">
|
||||
<div v-loading="!frameLoaded">
|
||||
<iframe
|
||||
style="width: 100%; height: calc(100vh - 100px); border: none"
|
||||
:src="voiceChatUrl"
|
||||
@load="frameLoaded = true"
|
||||
allow="microphone *;camera *;"
|
||||
></iframe>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
@@ -360,18 +255,7 @@
|
||||
import { nextTick, onMounted, onUnmounted, ref, watch } from "vue";
|
||||
import ChatPrompt from "@/components/ChatPrompt.vue";
|
||||
import ChatReply from "@/components/ChatReply.vue";
|
||||
import {
|
||||
Delete,
|
||||
Edit,
|
||||
InfoFilled,
|
||||
More,
|
||||
Plus,
|
||||
CirclePlusFilled,
|
||||
Promotion,
|
||||
Search,
|
||||
Share,
|
||||
VideoPause
|
||||
} from "@element-plus/icons-vue";
|
||||
import { Delete, Edit, InfoFilled, More, Plus, Promotion, Search, Share, VideoPause } from "@element-plus/icons-vue";
|
||||
import "highlight.js/styles/a11y-dark.css";
|
||||
import { isMobile, randString, removeArrayItem, UUID } from "@/utils/libs";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
@@ -385,8 +269,7 @@ import FileSelect from "@/components/FileSelect.vue";
|
||||
import FileList from "@/components/FileList.vue";
|
||||
import ChatSetting from "@/components/ChatSetting.vue";
|
||||
import BackTop from "@/components/BackTop.vue";
|
||||
import { showMessageError } from "@/utils/dialog";
|
||||
import RealtimeConversation from "@/components/RealtimeConversation.vue";
|
||||
import { closeLoading, showLoading, showMessageError } from "@/utils/dialog";
|
||||
|
||||
const title = ref("GeekAI-智能助手");
|
||||
const models = ref([]);
|
||||
@@ -415,6 +298,8 @@ const store = useSharedStore();
|
||||
const row = ref(1);
|
||||
const showChatSetting = ref(false);
|
||||
const listStyle = ref(store.chatListStyle);
|
||||
const config = ref({ advance_voice_power: 0 });
|
||||
const voiceChatUrl = ref("");
|
||||
watch(
|
||||
() => store.chatListStyle,
|
||||
(newValue) => {
|
||||
@@ -460,7 +345,8 @@ if (!chatId.value) {
|
||||
// 获取系统配置
|
||||
getSystemInfo()
|
||||
.then((res) => {
|
||||
title.value = res.data.title;
|
||||
config.value = res.data;
|
||||
title.value = config.value.title;
|
||||
})
|
||||
.catch((e) => {
|
||||
ElMessage.error("获取系统配置失败:" + e.message);
|
||||
@@ -470,7 +356,7 @@ const md = require("markdown-it")({
|
||||
breaks: true,
|
||||
html: true,
|
||||
linkify: true,
|
||||
typographer: true
|
||||
typographer: true,
|
||||
});
|
||||
// 获取系统公告
|
||||
httpGet("/api/config/get?key=notice")
|
||||
@@ -539,7 +425,7 @@ onMounted(() => {
|
||||
id: randString(32),
|
||||
icon: chatRole["icon"],
|
||||
prompt: prePrompt,
|
||||
content: data.body
|
||||
content: data.body,
|
||||
});
|
||||
isNewMsg.value = false;
|
||||
lineBuffer.value = data.body;
|
||||
@@ -561,16 +447,14 @@ onMounted(() => {
|
||||
httpPost("/api/chat/tokens", {
|
||||
text: "",
|
||||
model: getModelValue(modelID.value),
|
||||
chat_id: chatId.value
|
||||
chat_id: chatId.value,
|
||||
})
|
||||
.then((res) => {
|
||||
reply["created_at"] = new Date().getTime();
|
||||
reply["tokens"] = res.data;
|
||||
// 将聊天框的滚动条滑动到最底部
|
||||
nextTick(() => {
|
||||
document
|
||||
.getElementById("chat-box")
|
||||
.scrollTo(0, document.getElementById("chat-box").scrollHeight);
|
||||
document.getElementById("chat-box").scrollTo(0, document.getElementById("chat-box").scrollHeight);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
@@ -584,9 +468,7 @@ onMounted(() => {
|
||||
}
|
||||
// 将聊天框的滚动条滑动到最底部
|
||||
nextTick(() => {
|
||||
document
|
||||
.getElementById("chat-box")
|
||||
.scrollTo(0, document.getElementById("chat-box").scrollHeight);
|
||||
document.getElementById("chat-box").scrollTo(0, document.getElementById("chat-box").scrollHeight);
|
||||
localStorage.setItem("chat_id", chatId.value);
|
||||
});
|
||||
});
|
||||
@@ -687,8 +569,8 @@ const resizeElement = function () {
|
||||
// mainWinHeight.value = window.innerHeight;
|
||||
|
||||
// leftBoxHeight.value = window.innerHeight - 90 - 45 - 82;
|
||||
// leftBoxHeight.value = window.innerHeight - 90 - 82;
|
||||
leftBoxHeight.value = window.innerHeight - 90 - 50;
|
||||
leftBoxHeight.value = window.innerHeight - 90 - 90;
|
||||
// leftBoxHeight.value = window.innerHeight - 90 - 50;
|
||||
};
|
||||
|
||||
const _newChat = () => {
|
||||
@@ -714,10 +596,7 @@ const newChat = () => {
|
||||
disableModel.value = true;
|
||||
}
|
||||
// 已有新开的会话
|
||||
if (
|
||||
newChatItem.value !== null &&
|
||||
newChatItem.value["role_id"] === roles.value[0]["role_id"]
|
||||
) {
|
||||
if (newChatItem.value !== null && newChatItem.value["role_id"] === roles.value[0]["role_id"]) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -735,7 +614,7 @@ const newChat = () => {
|
||||
model_id: modelID.value,
|
||||
title: "",
|
||||
edit: false,
|
||||
removing: false
|
||||
removing: false,
|
||||
};
|
||||
showStopGenerate.value = false;
|
||||
loadChatHistory(chatId.value);
|
||||
@@ -796,7 +675,7 @@ const editConfirm = function (chat) {
|
||||
|
||||
httpPost("/api/chat/update", {
|
||||
chat_id: chat.chat_id,
|
||||
title: tmpChatTitle.value
|
||||
title: tmpChatTitle.value,
|
||||
})
|
||||
.then(() => {
|
||||
chat.title = tmpChatTitle.value;
|
||||
@@ -811,18 +690,14 @@ const removeChat = function (chat) {
|
||||
ElMessageBox.confirm(`该操作会删除"${chat.title}"`, "删除聊天", {
|
||||
confirmButtonText: "删除",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
type: "warning",
|
||||
})
|
||||
.then(() => {
|
||||
httpGet("/api/chat/remove?chat_id=" + chat.chat_id)
|
||||
.then(() => {
|
||||
chatList.value = removeArrayItem(
|
||||
chatList.value,
|
||||
chat,
|
||||
function (e1, e2) {
|
||||
return e1.id === e2.id;
|
||||
}
|
||||
);
|
||||
chatList.value = removeArrayItem(chatList.value, chat, function (e1, e2) {
|
||||
return e1.id === e2.id;
|
||||
});
|
||||
// 重置会话
|
||||
_newChat();
|
||||
})
|
||||
@@ -845,9 +720,7 @@ const enableInput = () => {
|
||||
|
||||
const onInput = (e) => {
|
||||
// 根据输入的内容自动计算输入框的行数
|
||||
const lineHeight = parseFloat(
|
||||
window.getComputedStyle(inputRef.value).lineHeight
|
||||
);
|
||||
const lineHeight = parseFloat(window.getComputedStyle(inputRef.value).lineHeight);
|
||||
textHeightRef.value.style.width = inputRef.value.clientWidth + "px"; // 设定宽度和 textarea 相同
|
||||
const lines = Math.floor(textHeightRef.value.clientHeight / lineHeight);
|
||||
inputRef.value.scrollTo(0, inputRef.value.scrollHeight);
|
||||
@@ -914,13 +787,11 @@ const sendMessage = function () {
|
||||
icon: loginUser.value.avatar,
|
||||
content: content,
|
||||
model: getModelValue(modelID.value),
|
||||
created_at: new Date().getTime() / 1000
|
||||
created_at: new Date().getTime() / 1000,
|
||||
});
|
||||
|
||||
nextTick(() => {
|
||||
document
|
||||
.getElementById("chat-box")
|
||||
.scrollTo(0, document.getElementById("chat-box").scrollHeight);
|
||||
document.getElementById("chat-box").scrollTo(0, document.getElementById("chat-box").scrollHeight);
|
||||
});
|
||||
|
||||
showHello.value = false;
|
||||
@@ -935,8 +806,8 @@ const sendMessage = function () {
|
||||
chat_id: chatId.value,
|
||||
content: content,
|
||||
tools: toolSelected.value,
|
||||
stream: stream.value
|
||||
}
|
||||
stream: stream.value,
|
||||
},
|
||||
})
|
||||
);
|
||||
tmpChatTitle.value = content;
|
||||
@@ -954,7 +825,7 @@ const clearAllChats = function () {
|
||||
dangerouslyUseHTMLString: true,
|
||||
showClose: true,
|
||||
closeOnClickModal: false,
|
||||
center: false
|
||||
center: false,
|
||||
})
|
||||
.then(() => {
|
||||
httpGet("/api/chat/clear")
|
||||
@@ -987,7 +858,7 @@ const loadChatHistory = function (chatId) {
|
||||
type: "reply",
|
||||
id: randString(32),
|
||||
icon: _role["icon"],
|
||||
content: _role["hello_msg"]
|
||||
content: _role["hello_msg"],
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -1000,9 +871,7 @@ const loadChatHistory = function (chatId) {
|
||||
}
|
||||
|
||||
nextTick(() => {
|
||||
document
|
||||
.getElementById("chat-box")
|
||||
.scrollTo(0, document.getElementById("chat-box").scrollHeight);
|
||||
document.getElementById("chat-box").scrollTo(0, document.getElementById("chat-box").scrollHeight);
|
||||
});
|
||||
})
|
||||
.catch((e) => {
|
||||
@@ -1027,7 +896,7 @@ const reGenerate = function (prompt) {
|
||||
type: "prompt",
|
||||
id: randString(32),
|
||||
icon: loginUser.value.avatar,
|
||||
content: text
|
||||
content: text,
|
||||
});
|
||||
store.socket.conn.send(
|
||||
JSON.stringify({
|
||||
@@ -1039,8 +908,8 @@ const reGenerate = function (prompt) {
|
||||
chat_id: chatId.value,
|
||||
content: text,
|
||||
tools: toolSelected.value,
|
||||
stream: stream.value
|
||||
}
|
||||
stream: stream.value,
|
||||
},
|
||||
})
|
||||
);
|
||||
};
|
||||
@@ -1055,11 +924,7 @@ const searchChat = function (e) {
|
||||
if (e.keyCode === 13) {
|
||||
const items = [];
|
||||
for (let i = 0; i < allChats.value.length; i++) {
|
||||
if (
|
||||
allChats.value[i].title
|
||||
.toLowerCase()
|
||||
.indexOf(chatName.value.toLowerCase()) !== -1
|
||||
) {
|
||||
if (allChats.value[i].title.toLowerCase().indexOf(chatName.value.toLowerCase()) !== -1) {
|
||||
items.push(allChats.value[i]);
|
||||
}
|
||||
}
|
||||
@@ -1073,12 +938,7 @@ const shareChat = (chat) => {
|
||||
return ElMessage.error("请先选中一个会话");
|
||||
}
|
||||
|
||||
const url =
|
||||
location.protocol +
|
||||
"//" +
|
||||
location.host +
|
||||
"/chat/export?chat_id=" +
|
||||
chat.chat_id;
|
||||
const url = location.protocol + "//" + location.host + "/chat/export?chat_id=" + chat.chat_id;
|
||||
window.open(url, "_blank");
|
||||
};
|
||||
|
||||
@@ -1102,31 +962,36 @@ const insertFile = (file) => {
|
||||
files.value.push(file);
|
||||
};
|
||||
const removeFile = (file) => {
|
||||
files.value = removeArrayItem(
|
||||
files.value,
|
||||
file,
|
||||
(v1, v2) => v1.url === v2.url
|
||||
);
|
||||
files.value = removeArrayItem(files.value, file, (v1, v2) => v1.url === v2.url);
|
||||
};
|
||||
|
||||
// 实时语音对话
|
||||
const showConversationDialog = ref(false);
|
||||
const conversationRef = ref(null);
|
||||
const dialogHeight = ref(window.innerHeight - 75);
|
||||
// const conversationRef = ref(null);
|
||||
// const dialogHeight = ref(window.innerHeight - 75);
|
||||
const frameLoaded = ref(false);
|
||||
const realtimeChat = () => {
|
||||
if (!isLogin.value) {
|
||||
store.setShowLoginDialog(true);
|
||||
return;
|
||||
}
|
||||
showConversationDialog.value = true;
|
||||
nextTick(() => {
|
||||
conversationRef.value.connect();
|
||||
});
|
||||
};
|
||||
const hangUp = () => {
|
||||
showConversationDialog.value = false;
|
||||
conversationRef.value.hangUp();
|
||||
showLoading("正在连接...");
|
||||
httpPost("/api/realtime/voice")
|
||||
.then((res) => {
|
||||
voiceChatUrl.value = res.data;
|
||||
showConversationDialog.value = true;
|
||||
closeLoading();
|
||||
})
|
||||
.catch((e) => {
|
||||
showMessageError("连接失败:" + e.message);
|
||||
closeLoading();
|
||||
});
|
||||
};
|
||||
|
||||
// const hangUp = () => {
|
||||
// showConversationDialog.value = false;
|
||||
// conversationRef.value.hangUp();
|
||||
// };
|
||||
</script>
|
||||
|
||||
<style scoped lang="stylus">
|
||||
|
||||
@@ -393,23 +393,23 @@
|
||||
<script setup>
|
||||
import nodata from "@/assets/img/no-data.png";
|
||||
|
||||
import { nextTick, onMounted, onUnmounted, ref, watch } from "vue";
|
||||
import { Delete, InfoFilled } from "@element-plus/icons-vue";
|
||||
import {nextTick, onMounted, onUnmounted, ref, watch} from "vue";
|
||||
import {Delete, InfoFilled} from "@element-plus/icons-vue";
|
||||
import BlackSelect from "@/components/ui/BlackSelect.vue";
|
||||
import BlackSwitch from "@/components/ui/BlackSwitch.vue";
|
||||
import BlackInput from "@/components/ui/BlackInput.vue";
|
||||
import MusicPlayer from "@/components/MusicPlayer.vue";
|
||||
import { compact } from "lodash";
|
||||
import { httpDownload, httpGet, httpPost } from "@/utils/http";
|
||||
import { showMessageError, showMessageOK } from "@/utils/dialog";
|
||||
import { checkSession, getClientId } from "@/store/cache";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { formatTime, replaceImg } from "@/utils/libs";
|
||||
import {compact} from "lodash";
|
||||
import {httpDownload, httpGet, httpPost} from "@/utils/http";
|
||||
import {showMessageError, showMessageOK} from "@/utils/dialog";
|
||||
import {checkSession, getClientId} from "@/store/cache";
|
||||
import {ElMessage, ElMessageBox} from "element-plus";
|
||||
import {formatTime, replaceImg} from "@/utils/libs";
|
||||
import Clipboard from "clipboard";
|
||||
import BlackDialog from "@/components/ui/BlackDialog.vue";
|
||||
import Compressor from "compressorjs";
|
||||
import Generating from "@/components/ui/Generating.vue";
|
||||
import { useSharedStore } from "@/store/sharedata";
|
||||
import {useSharedStore} from "@/store/sharedata";
|
||||
|
||||
// const winHeight = ref(window.innerHeight - 50);
|
||||
const winHeight = ref(window.innerHeight - 20);
|
||||
@@ -417,7 +417,8 @@ const winHeight = ref(window.innerHeight - 20);
|
||||
const custom = ref(false);
|
||||
const models = ref([
|
||||
{ label: "v3.0", value: "chirp-v3-0" },
|
||||
{ label: "v3.5", value: "chirp-v3-5" }
|
||||
{label: "v3.5", value: "chirp-v3-5"},
|
||||
{label: "v4.0", value: "chirp-v4"}
|
||||
]);
|
||||
const tags = ref([
|
||||
{ label: "女声", value: "female vocals" },
|
||||
|
||||
@@ -12,10 +12,14 @@
|
||||
</el-select>
|
||||
<el-button type="primary" :icon="Search" @click="fetchData">搜索</el-button>
|
||||
<el-button type="success" :icon="Plus" @click="add">添加兑换码</el-button>
|
||||
<el-button type="primary" @click="exportItems" :loading="exporting"><i class="iconfont icon-export mr-1"></i> 导出
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<el-row>
|
||||
<el-table :data="items" :row-key="row => row.id">
|
||||
<el-table :data="items" :row-key="row => row.id"
|
||||
@selection-change="handleSelectionChange" table-layout="auto">
|
||||
<el-table-column type="selection" width="38"></el-table-column>
|
||||
<el-table-column prop="name" label="名称"/>
|
||||
<el-table-column prop="code" label="兑换码">
|
||||
<template #default="scope">
|
||||
@@ -48,7 +52,8 @@
|
||||
|
||||
<el-table-column prop="enabled" label="启用状态">
|
||||
<template #default="scope">
|
||||
<el-switch v-model="scope.row['enabled']" @change="set('enabled',scope.row)" :disabled="scope.row['redeemed_at']>0"/>
|
||||
<el-switch v-model="scope.row['enabled']" @change="set('enabled',scope.row)"
|
||||
:disabled="scope.row['redeemed_at']>0"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
@@ -90,7 +95,7 @@
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="生成数量:" prop="num">
|
||||
<el-input v-model.number="item.num" />
|
||||
<el-input v-model.number="item.num"/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
@@ -107,17 +112,17 @@
|
||||
|
||||
<script setup>
|
||||
import {onMounted, onUnmounted, ref} from "vue";
|
||||
import {httpGet, httpPost} from "@/utils/http";
|
||||
import {httpGet, httpPost, httpPostDownload} from "@/utils/http";
|
||||
import {ElMessage} from "element-plus";
|
||||
import {dateFormat, removeArrayItem, substr} from "@/utils/libs";
|
||||
import {Delete, DocumentCopy, Plus, Search, UploadFilled} from "@element-plus/icons-vue";
|
||||
import {dateFormat, substr, UUID} from "@/utils/libs";
|
||||
import {DocumentCopy, Plus, Search} from "@element-plus/icons-vue";
|
||||
import {showMessageError} from "@/utils/dialog";
|
||||
import ClipboardJS from "clipboard";
|
||||
|
||||
// 变量定义
|
||||
const items = ref([])
|
||||
const loading = ref(true)
|
||||
const query = ref({code:"",status:-1})
|
||||
const query = ref({code: "", status: -1})
|
||||
const redeemStatus = ref([
|
||||
{value: -1, label: "全部"},
|
||||
{value: 0, label: "未核销"},
|
||||
@@ -126,6 +131,8 @@ const redeemStatus = ref([
|
||||
const showDialog = ref(false)
|
||||
const dialogLoading = ref(false)
|
||||
const item = ref({name: "", power: 0, num: 1})
|
||||
const itemIds = ref([])
|
||||
const exporting = ref(false)
|
||||
|
||||
const clipboard = ref(null)
|
||||
onMounted(() => {
|
||||
@@ -152,10 +159,10 @@ const add = () => {
|
||||
}
|
||||
|
||||
const save = () => {
|
||||
if (item.value.name ===""){
|
||||
if (item.value.name === "") {
|
||||
return showMessageError("请输入兑换码名称")
|
||||
}
|
||||
if (item.value.power === 0){
|
||||
if (item.value.power === 0) {
|
||||
return showMessageError("请输入算力额度")
|
||||
}
|
||||
if (item.value.num <= 0) {
|
||||
@@ -207,12 +214,39 @@ const remove = function (row) {
|
||||
ElMessage.error("删除失败:" + e.message)
|
||||
})
|
||||
}
|
||||
|
||||
const handleSelectionChange = (items) => {
|
||||
itemIds.value = items.map(item => item.id)
|
||||
}
|
||||
|
||||
const exportItems = () => {
|
||||
query.value.ids = itemIds.value
|
||||
exporting.value = true
|
||||
httpPostDownload("/api/admin/redeem/export", query.value).then(response => {
|
||||
const url = window.URL.createObjectURL(new Blob([response.data]));
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.setAttribute('download', UUID() + ".csv"); // 设置下载文件的名称
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
|
||||
// 移除 <a> 标签
|
||||
document.body.removeChild(link);
|
||||
window.URL.revokeObjectURL(url);
|
||||
exporting.value = false
|
||||
}).catch(() => {
|
||||
exporting.value = false
|
||||
showMessageError("下载失败")
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.list {
|
||||
.handle-box {
|
||||
margin-bottom 20px
|
||||
|
||||
.handle-input {
|
||||
max-width 150px;
|
||||
margin-right 10px;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="system-config" v-loading="loading">
|
||||
<div class="system-config form" v-loading="loading">
|
||||
|
||||
<el-tabs v-model="activeName" class="sys-tabs">
|
||||
<el-tab-pane label="系统配置" name="basic">
|
||||
@@ -33,8 +33,23 @@
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="首页背景图" prop="logo">
|
||||
<div class="tip-input">
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<div class="label-title">
|
||||
首页背景图
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
content="网站首页背景图片"
|
||||
raw-content
|
||||
placement="right"
|
||||
>
|
||||
<el-icon>
|
||||
<InfoFilled/>
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<div class="d-flex justify-between w-100">
|
||||
<el-input v-model="system['index_bg_url']" placeholder="网站首页背景图片">
|
||||
<template #append>
|
||||
<el-upload
|
||||
@@ -49,14 +64,28 @@
|
||||
</el-upload>
|
||||
</template>
|
||||
</el-input>
|
||||
<el-button type="primary" @click="system.index_bg_url = 'https://api.dujin.org/bing/1920.php'">使用动态背景</el-button>
|
||||
<el-button @click="system.index_bg_url = 'color'">使用纯色背景</el-button>
|
||||
<el-button class="ml-1" type="primary" @click="system.index_bg_url = 'https://api.dujin.org/bing/1920.php'">使用动态背景</el-button>
|
||||
<el-button class="ml-1" @click="system.index_bg_url = 'color'">使用纯色背景</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="首页导航菜单" prop="index_navs">
|
||||
<div class="tip-input">
|
||||
<el-select
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<div class="label-title">
|
||||
首页导航菜单
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
content="被选中的菜单将会在首页导航栏显示"
|
||||
raw-content
|
||||
placement="right"
|
||||
>
|
||||
<el-icon>
|
||||
<InfoFilled/>
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<el-select
|
||||
v-model="system['index_navs']"
|
||||
multiple
|
||||
:filterable="true"
|
||||
@@ -70,29 +99,16 @@
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
<div class="info">
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="dark"
|
||||
content="被选中的菜单将会在首页导航栏显示"
|
||||
placement="right"
|
||||
>
|
||||
<el-icon>
|
||||
<InfoFilled/>
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="版权信息" prop="copyright">
|
||||
<el-input v-model="system['copyright']" placeholder="更改此选项需要获取 License 授权"/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="开放注册" prop="enabled_register">
|
||||
<div class="tip-input">
|
||||
<el-switch v-model="system['enabled_register']"/>
|
||||
<div class="info">
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<div class="label-title">
|
||||
开放注册
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
content="关闭注册之后只能通过管理后台添加用户"
|
||||
@@ -104,13 +120,14 @@
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<el-switch v-model="system['enabled_register']"/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="启用验证码" prop="enabled_verify">
|
||||
<div class="tip-input">
|
||||
<el-switch v-model="system['enabled_verify']"/>
|
||||
<div class="info">
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<div class="label-title">
|
||||
启用验证码
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
content="启用验证码之后,注册登录都会加载行为验证码,增加安全性。此功能需要购买验证码服务才会生效。"
|
||||
@@ -122,7 +139,8 @@
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<el-switch v-model="system['enabled_verify']"/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="注册方式" prop="register_ways">
|
||||
@@ -153,36 +171,35 @@
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="默认翻译模型">
|
||||
<template #default>
|
||||
<div class="tip-input">
|
||||
<el-select
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<div class="label-title">
|
||||
默认翻译模型
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
content="选择一个默认模型来翻译提示词"
|
||||
raw-content
|
||||
placement="right"
|
||||
>
|
||||
<el-icon>
|
||||
<InfoFilled/>
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<el-select
|
||||
v-model.number="system['translate_model_id']"
|
||||
:filterable="true"
|
||||
placeholder="选择一个默认模型来翻译提示词"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in models"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
<div class="info">
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="dark"
|
||||
content="新用户注册默认开通的 AI 模型"
|
||||
placement="right"
|
||||
>
|
||||
<el-icon>
|
||||
<InfoFilled/>
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<el-option
|
||||
v-for="item in models"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="开启聊天上下文">
|
||||
@@ -193,15 +210,15 @@
|
||||
<el-input-number v-model="system['context_deep']" :min="0" :max="10"/>
|
||||
<div class="tip">会话上下文深度:在老会话中继续会话,默认加载多少条聊天记录作为上下文。如果设置为
|
||||
0
|
||||
则不加载聊天记录,仅仅使用当前角色的上下文。该配置参数最好设置需要为偶数,否则将无法兼容百度的 API。
|
||||
则不加载聊天记录,仅仅使用当前角色的上下文。该配置参数必须设置需要为偶数。
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="SD反向提示词" prop="sd_neg_prompt">
|
||||
<div class="tip-input">
|
||||
<el-input v-model="system['sd_neg_prompt']" placeholder=""/>
|
||||
<div class="info">
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<div class="label-title">
|
||||
SD反向提示词
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
content="Stable-Diffusion 绘画默认反向提示词"
|
||||
@@ -213,13 +230,14 @@
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<el-input type="textarea" :rows="2" v-model="system['sd_neg_prompt']" placeholder=""/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="会员充值说明" prop="order_pay_timeout">
|
||||
<div class="tip-input">
|
||||
<el-input v-model="system['vip_info_text']" placeholder=""/>
|
||||
<div class="info">
|
||||
<template #label>
|
||||
<div class="label-title">
|
||||
会员充值说明
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
content="会员充值页面的充值说明文字"
|
||||
@@ -231,7 +249,8 @@
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<el-input type="textarea" :rows="2" v-model="system['vip_info_text']" placeholder=""/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="MJ默认API模式" prop="mj_mode">
|
||||
@@ -255,17 +274,16 @@
|
||||
<el-input v-model.number="system['vip_month_power']" placeholder="VIP用户每月赠送算力"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="每日赠送算力" prop="daily_power">
|
||||
<div class="tip-input-line">
|
||||
<el-input v-model.number="system['daily_power']" placeholder="默认值0"/>
|
||||
<div class="tip">
|
||||
如果设置0表示不赠送,用户享受完免费算力额度之后就不能再发起对话了。如果设置为N,则系统每天将算力值小于N的用户自动补充到N。注意,此功能要配合XXL-JOB启用。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-text type="info">
|
||||
如果设置0表示不赠送,用户享受完免费算力额度之后就不能再发起对话了。如果设置为N,则系统每天将算力值小于N的用户自动补充到N。注意,此功能要配合XXL-JOB启用。
|
||||
</el-text>
|
||||
</el-form-item>
|
||||
<el-form-item label="MJ绘图算力" prop="mj_power">
|
||||
<div class="tip-input">
|
||||
<el-input v-model.number="system['mj_power']" placeholder=""/>
|
||||
<div class="info">
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<div class="label-title">
|
||||
MJ绘图算力
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
content="使用MidJourney画一张图消耗算力"
|
||||
@@ -277,12 +295,13 @@
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<el-input v-model.number="system['mj_power']" placeholder=""/>
|
||||
</el-form-item>
|
||||
<el-form-item label="MJ操作算力" prop="mj_action_power">
|
||||
<div class="tip-input">
|
||||
<el-input v-model.number="system['mj_action_power']" placeholder=""/>
|
||||
<div class="info">
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<div class="label-title">
|
||||
MJ操作算力
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
content="放大,变换,重绘操作一次消耗的算力"
|
||||
@@ -294,7 +313,8 @@
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<el-input v-model.number="system['mj_action_power']" placeholder=""/>
|
||||
</el-form-item>
|
||||
<el-form-item label="Stable-Diffusion算力" prop="sd_power">
|
||||
<el-input v-model.number="system['sd_power']" placeholder="使用Stable-Diffusion画一张图消耗算力"/>
|
||||
@@ -308,6 +328,24 @@
|
||||
<el-form-item label="Luma 算力" prop="luma_power">
|
||||
<el-input v-model.number="system['luma_power']" placeholder="使用 Luma 生成一段视频消耗算力"/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<div class="label-title">
|
||||
高级语音算力
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
content="使用一次 OpenAI 高级语音对话消耗的算力"
|
||||
raw-content
|
||||
placement="right"
|
||||
>
|
||||
<el-icon>
|
||||
<InfoFilled/>
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<el-input v-model.number="system['advance_voice_power']" placeholder=""/>
|
||||
</el-form-item>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
@@ -420,7 +458,7 @@ import {onMounted, reactive, ref} from "vue";
|
||||
import {httpGet, httpPost} from "@/utils/http";
|
||||
import Compressor from "compressorjs";
|
||||
import {ElMessage, ElMessageBox} from "element-plus";
|
||||
import {InfoFilled, UploadFilled,Select,CloseBold} from "@element-plus/icons-vue";
|
||||
import {CloseBold, InfoFilled, Select, UploadFilled} from "@element-plus/icons-vue";
|
||||
import MdEditor from "md-editor-v3";
|
||||
import 'md-editor-v3/lib/style.css';
|
||||
import Menu from "@/views/admin/Menu.vue";
|
||||
@@ -594,6 +632,7 @@ const fixData = () => {
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
@import "@/assets/css/admin/form.styl"
|
||||
@import "@/assets/css/main.styl"
|
||||
.system-config {
|
||||
display flex
|
||||
justify-content center
|
||||
@@ -603,76 +642,6 @@ const fixData = () => {
|
||||
background-color var(--el-bg-color)
|
||||
padding 10px 20px 40px 20px
|
||||
//border: 1px solid var(--el-border-color);
|
||||
|
||||
.container {
|
||||
.el-form {
|
||||
.el-form-item__content {
|
||||
|
||||
.tip-text {
|
||||
padding-left 10px;
|
||||
}
|
||||
|
||||
.el-icon {
|
||||
font-size 16px
|
||||
cursor pointer
|
||||
}
|
||||
|
||||
.uploader-icon {
|
||||
font-size 24px
|
||||
position relative
|
||||
top 3px
|
||||
}
|
||||
|
||||
.tip-input-line {
|
||||
.tip {
|
||||
margin-top 10px
|
||||
color #c1c1c1
|
||||
font-size 12px;
|
||||
line-height 1.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-input {
|
||||
width 100%
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.text {
|
||||
font-size 14px
|
||||
}
|
||||
|
||||
.active-info {
|
||||
line-height 1.5
|
||||
padding 10px 0 30px 0
|
||||
}
|
||||
.el-descriptions {
|
||||
margin-bottom 20px
|
||||
.el-icon {
|
||||
font-size 18px
|
||||
}
|
||||
.selected {
|
||||
color #0bc15f
|
||||
}
|
||||
|
||||
.closed {
|
||||
color #da0d54
|
||||
}
|
||||
.text {
|
||||
margin-left 10px
|
||||
font-size 12px
|
||||
color #999999
|
||||
position: relative;
|
||||
top -5px
|
||||
}
|
||||
}
|
||||
|
||||
.el-alert {
|
||||
margin-bottom 15px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user