fine-tune the new UI theme

This commit is contained in:
RockYang 2024-12-24 17:43:40 +08:00
parent e857f98e5c
commit b01b10014a
13 changed files with 325 additions and 448 deletions

View File

@ -3,10 +3,11 @@
## v4.1.8
- 功能优化:**UI 全新改版,支持主题切换**。 :rocket: :rocket: :rocket:
- Bug修复修复音 Luma API 更新导致任务响应解析失败的错误
- Bug 修复:修复音 Luma API 更新导致任务响应解析失败的错误
- 功能优化:支持 Suno v4.0 模型支持
- Bug修复修复 Suno 已完成任务删除失败的 错误
- Bug 修复:修复 Suno 已完成任务删除失败的 错误
- 功能新增:支持 OpenAI 实时语音通话功能,目前已经支持按次收费,支持管理员设置每次实时语音通话的算力消耗
- 功能新增:生成提示词需要消耗算力,支持管理员设置每次生成提示词的算力消耗,防止被白嫖
## v4.1.7

View File

@ -143,13 +143,14 @@ type SystemConfig struct {
OrderPayTimeout int `json:"order_pay_timeout,omitempty"` //订单支付超时时间
VipInfoText string `json:"vip_info_text,omitempty"` // 会员页面充值说明
MjPower int `json:"mj_power,omitempty"` // MJ 绘画消耗算力
MjActionPower int `json:"mj_action_power,omitempty"` // MJ 操作(放大,变换)消耗算力
SdPower int `json:"sd_power,omitempty"` // SD 绘画消耗算力
DallPower int `json:"dall_power,omitempty"` // DALL-E-3 绘图消耗算力
SunoPower int `json:"suno_power,omitempty"` // Suno 生成歌曲消耗算力
LumaPower int `json:"luma_power,omitempty"` // Luma 生成视频消耗算力
MjPower int `json:"mj_power,omitempty"` // MJ 绘画消耗算力
MjActionPower int `json:"mj_action_power,omitempty"` // MJ 操作(放大,变换)消耗算力
SdPower int `json:"sd_power,omitempty"` // SD 绘画消耗算力
DallPower int `json:"dall_power,omitempty"` // DALL-E-3 绘图消耗算力
SunoPower int `json:"suno_power,omitempty"` // Suno 生成歌曲消耗算力
LumaPower int `json:"luma_power,omitempty"` // Luma 生成视频消耗算力
AdvanceVoicePower int `json:"advance_voice_power,omitempty"` // 高级语音对话消耗算力
PromptPower int `json:"prompt_power,omitempty"` // 生成提示词消耗算力
WechatCardURL string `json:"wechat_card_url,omitempty"` // 微信客服地址

View File

@ -12,13 +12,13 @@ import (
"geekai/core"
"geekai/core/types"
"geekai/service"
"geekai/service/oss"
"geekai/service/suno"
"geekai/store/model"
"geekai/utils"
"geekai/utils/resp"
"strings"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"strings"
)
// 提示词生成 handler
@ -26,8 +26,6 @@ import (
type PromptHandler struct {
BaseHandler
sunoService *suno.Service
uploader *oss.UploaderManager
userService *service.UserService
}
@ -56,6 +54,15 @@ func (h *PromptHandler) Lyric(c *gin.Context) {
return
}
if h.App.SysConfig.PromptPower > 0 {
userId := h.GetLoginUserId(c)
h.userService.DecreasePower(int(userId), h.App.SysConfig.PromptPower, model.PowerLog{
Type: types.PowerConsume,
Model: h.getPromptModel(),
Remark: "生成歌词",
})
}
resp.SUCCESS(c, content)
}
@ -73,7 +80,14 @@ func (h *PromptHandler) Image(c *gin.Context) {
resp.ERROR(c, err.Error())
return
}
if h.App.SysConfig.PromptPower > 0 {
userId := h.GetLoginUserId(c)
h.userService.DecreasePower(int(userId), h.App.SysConfig.PromptPower, model.PowerLog{
Type: types.PowerConsume,
Model: h.getPromptModel(),
Remark: "生成绘画提示词",
})
}
resp.SUCCESS(c, strings.Trim(content, `"`))
}
@ -92,6 +106,15 @@ func (h *PromptHandler) Video(c *gin.Context) {
return
}
if h.App.SysConfig.PromptPower > 0 {
userId := h.GetLoginUserId(c)
h.userService.DecreasePower(int(userId), h.App.SysConfig.PromptPower, model.PowerLog{
Type: types.PowerConsume,
Model: h.getPromptModel(),
Remark: "生成视频脚本",
})
}
resp.SUCCESS(c, strings.Trim(content, `"`))
}
@ -121,3 +144,12 @@ func (h *PromptHandler) MetaPrompt(c *gin.Context) {
resp.SUCCESS(c, strings.Trim(content, `"`))
}
func (h *PromptHandler) getPromptModel() string {
if h.App.SysConfig.TranslateModelId > 0 {
var chatModel model.ChatModel
h.DB.Where("id", h.App.SysConfig.TranslateModelId).First(&chatModel)
return chatModel.Value
}
return "gpt-4o"
}

View File

@ -216,7 +216,7 @@
padding 0 20px
.prompt,.failed {
padding 6px 0
padding 0
font-size 16px
max-height 80px
line-height 28px

View File

@ -72,4 +72,8 @@
--el-table-row-hover-bg-color: rgba(16, 21, 43, .8);
--el-table-current-row-bg-color: rgba(16, 21, 43, .8);
}
//
--el-mask-color: rgba(255, 255, 255, 0.5);
--van-toast-background: rgba(255, 255, 255, 0.3);
}

View File

@ -43,6 +43,8 @@
//
--btn-bg: rgba(100, 100, 100, .1);
//
--el-mask-color: rgba(100, 100, 100, 0.2);
}

View File

@ -1,26 +1,18 @@
<template>
<div>
<div class="page-apps custom-scroll">
<div class="apps-type-nav">
<el-scrollbar>
<ul class="scrollbar-type-nav">
<li :class="{ active: typeId === '' }" @click="getAppList('')">
全部分类
</li>
<li
v-for="item in appTypes"
:key="item.id"
:class="{ active: typeId === item.id }"
@click="getAppList(item.id)"
>
<div class="image" v-if="item.icon">
<el-image :src="item.icon" fit="cover" />
</div>
{{ item.name }}
</li>
</ul>
</el-scrollbar>
<div class="flex h-20">
<ul class="scrollbar-type-nav">
<li :class="{ active: typeId === '' }" @click="getAppList('')">全部分类</li>
<li v-for="item in appTypes" :key="item.id" :class="{ active: typeId === item.id }" @click="getAppList(item.id)">
<div class="image" v-if="item.icon">
<el-image :src="item.icon" fit="cover" />
</div>
{{ item.name }}
</li>
</ul>
</div>
<div class="app-list-container" :style="{ height: listBoxHeight + 'px' }">
<ItemList :items="list" v-if="list.length > 0" :gap="15" :width="300">
<template #default="scope">
@ -35,31 +27,12 @@
<div class="info-text">{{ scope.item.hello_msg }}</div>
</div>
<div class="btn">
<el-button
size="small"
class="sm-btn-theme"
@click="useRole(scope.item)"
>使用</el-button
>
<el-tooltip
content="从工作区移除"
placement="top"
v-if="hasRole(scope.item.key)"
>
<el-button
size="small"
type="danger"
@click="updateRole(scope.item, 'remove')"
>移除</el-button
>
<el-button size="small" class="sm-btn-theme" @click="useRole(scope.item)">使用</el-button>
<el-tooltip content="从工作区移除" placement="top" v-if="hasRole(scope.item.key)">
<el-button size="small" type="danger" @click="updateRole(scope.item, 'remove')">移除</el-button>
</el-tooltip>
<el-tooltip content="添加到工作区" placement="top" v-else>
<el-button
size="small"
style="--el-color-primary: #009999"
@click="updateRole(scope.item, 'add')"
>添加</el-button
>
<el-button size="small" style="--el-color-primary: #009999" @click="updateRole(scope.item, 'add')">添加</el-button>
</el-tooltip>
</div>
</div>
@ -179,7 +152,7 @@ const updateRole = (row, opt) => {
.then(() => {
ElMessage.success({
message: title.value + "成功!",
duration: 1000
duration: 1000,
});
})
.catch((e) => {

View File

@ -56,7 +56,6 @@
ref="promptRef"
placeholder="请在此输入绘画提示词,您也可以点击下面的提示词助手生成绘画提示词"
v-loading="isGenerating"
style="--el-mask-color: rgba(100, 100, 100, 0.8)"
/>
</div>

View File

@ -178,7 +178,6 @@
type="textarea"
ref="promptRef"
v-loading="isGenerating"
style="--el-mask-color: rgba(100, 100, 100, 0.8)"
placeholder="请在此输入绘画提示词,您也可以点击下面的提示词助手生成绘画提示词"
/>
</div>
@ -272,7 +271,6 @@
type="textarea"
ref="promptRef"
v-loading="isGenerating"
style="--el-mask-color: rgba(100, 100, 100, 0.8)"
placeholder="请在此输入绘画提示词,系统会自动翻译中文提示词,高手请直接输入英文提示词"
/>
</div>

View File

@ -196,7 +196,6 @@
ref="promptRef"
placeholder="请在此输入绘画提示词,您也可以点击下面的提示词助手生成绘画提示词"
v-loading="isGenerating"
style="--el-mask-color: rgba(100, 100, 100, 0.8)"
/>
</div>

View File

@ -15,24 +15,11 @@
<div class="prompt-container">
<div class="input-container">
<div class="upload-icon" v-if="images.length < 2">
<el-upload
class="avatar-uploader"
:auto-upload="true"
:show-file-list="false"
:http-request="upload"
accept=".jpg,.png,.jpeg"
>
<el-upload class="avatar-uploader" :auto-upload="true" :show-file-list="false" :http-request="upload" accept=".jpg,.png,.jpeg">
<i class="iconfont icon-image"></i>
</el-upload>
</div>
<textarea
class="prompt-input"
:rows="row"
v-model="formData.prompt"
placeholder="请输入提示词或者上传图片"
autofocus
>
</textarea>
<textarea class="prompt-input" :rows="row" v-model="formData.prompt" placeholder="请输入提示词或者上传图片" autofocus> </textarea>
<div class="send-icon" @click="create">
<i class="iconfont icon-send"></i>
</div>
@ -40,13 +27,7 @@
<div class="params">
<div class="item-group">
<el-button
class="generate-btn"
size="small"
@click="generatePrompt"
color="#5865f2"
:disabled="isGenerating"
>
<el-button class="generate-btn" size="small" @click="generatePrompt" color="#5865f2">
<i class="iconfont icon-chuangzuo" style="margin-right: 5px"></i>
<span>生成AI视频提示词</span>
</el-button>
@ -63,12 +44,8 @@
</div>
</div>
<el-container
class="video-container"
v-loading="loading"
element-loading-background="rgba(100,100,100,0.3)"
>
<h2 class="h-title">你的作品</h2>
<el-container class="video-container" v-loading="loading" element-loading-background="rgba(100,100,100,0.3)">
<h2 class="h-title text-2xl mb-5 mt-2">你的作品</h2>
<div class="list-box" v-if="!noData">
<div v-for="item in list" :key="item.id">
@ -76,60 +53,30 @@
<div class="left">
<div class="container">
<div v-if="item.progress === 100">
<video
class="video"
:src="replaceImg(item.video_url)"
preload="auto"
loop="loop"
muted="muted"
>
您的浏览器不支持视频播放
</video>
<video class="video" :src="replaceImg(item.video_url)" preload="auto" loop="loop" muted="muted">您的浏览器不支持视频播放</video>
<button class="play" @click="play(item)">
<img src="/images/play.svg" alt="" />
</button>
</div>
<el-image
:src="item.cover_url"
fit="cover"
v-else-if="item.progress > 100"
/>
<el-image :src="item.cover_url" fit="cover" v-else-if="item.progress > 100" />
<generating message="正在生成视频" v-else />
</div>
</div>
<div class="center">
<div class="failed" v-if="item.progress === 101">
任务执行失败{{ item.err_msg }}任务提示词{{ item.prompt }}
</div>
<div class="failed" v-if="item.progress === 101">任务执行失败{{ item.err_msg }}任务提示词{{ item.prompt }}</div>
<div class="prompt" v-else>{{ item.prompt }}</div>
</div>
<div class="right" v-if="item.progress === 100">
<div class="tools">
<button class="btn btn-publish">
<span class="text">发布</span>
<black-switch
v-model:value="item.publish"
@change="publishJob(item)"
size="small"
/>
<black-switch v-model:value="item.publish" @change="publishJob(item)" size="small" />
</button>
<el-tooltip content="下载视频" placement="top">
<button
class="btn btn-icon"
@click="download(item)"
:disabled="item.downloading"
>
<i
class="iconfont icon-download"
v-if="!item.downloading"
></i>
<el-image
src="/images/loading.gif"
class="downloading"
fit="cover"
v-else
/>
<button class="btn btn-icon" @click="download(item)" :disabled="item.downloading">
<i class="iconfont icon-download" v-if="!item.downloading"></i>
<el-image src="/images/loading.gif" class="downloading" fit="cover" v-else />
</button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
@ -147,12 +94,7 @@
</div>
</div>
</div>
<el-empty
:image-size="100"
:image="nodata"
description="没有任何作品,赶紧去创作吧!"
v-else
/>
<el-empty :image-size="100" :image="nodata" description="没有任何作品,赶紧去创作吧!" v-else />
<div class="pagination">
<el-pagination
@ -168,22 +110,8 @@
/>
</div>
</el-container>
<black-dialog
v-model:show="showDialog"
title="预览视频"
hide-footer
@cancal="showDialog = false"
width="auto"
>
<video
style="width: 100%; max-height: 90vh"
:src="currentVideoUrl"
preload="auto"
:autoplay="true"
loop="loop"
muted="muted"
v-show="showDialog"
>
<black-dialog v-model:show="showDialog" title="预览视频" hide-footer @cancal="showDialog = false" width="auto">
<video style="width: 100%; max-height: 90vh" :src="currentVideoUrl" preload="auto" :autoplay="true" loop="loop" muted="muted" v-show="showDialog">
您的浏览器不支持视频播放
</video>
</black-dialog>
@ -197,7 +125,7 @@ import { onMounted, onUnmounted, reactive, ref } from "vue";
import { CircleCloseFilled } from "@element-plus/icons-vue";
import { httpDownload, httpPost, httpGet } from "@/utils/http";
import { checkSession, getClientId } from "@/store/cache";
import { showMessageError, showMessageOK } from "@/utils/dialog";
import { closeLoading, showLoading, showMessageError, showMessageOK } from "@/utils/dialog";
import { replaceImg } from "@/utils/libs";
import { ElMessage, ElMessageBox } from "element-plus";
import BlackSwitch from "@/components/ui/BlackSwitch.vue";
@ -216,7 +144,7 @@ const formData = reactive({
expand_prompt: false,
loop: false,
first_frame_img: "",
end_frame_img: ""
end_frame_img: "",
});
const store = useSharedStore();
@ -275,7 +203,7 @@ const removeJob = (item) => {
ElMessageBox.confirm("此操作将会删除任务相关文件,继续操作码?", "删除提示", {
confirmButtonText: "确认",
cancelButtonText: "取消",
type: "warning"
type: "warning",
})
.then(() => {
httpGet("/api/video/remove", { id: item.id })
@ -334,7 +262,7 @@ const fetchData = (_page) => {
httpGet("/api/video/list", {
page: page.value,
page_size: pageSize.value,
type: "luma"
type: "luma",
})
.then((res) => {
total.value = res.data.total;
@ -368,20 +296,19 @@ const create = () => {
});
};
const isGenerating = ref(false);
const generatePrompt = () => {
if (formData.prompt === "") {
return showMessageError("请输入原始提示词");
}
isGenerating.value = true;
httpPost("/api/prompt/image", { prompt: formData.prompt })
showLoading("正在生成视频脚本...");
httpPost("/api/prompt/video", { prompt: formData.prompt })
.then((res) => {
formData.prompt = res.data;
isGenerating.value = false;
closeLoading();
})
.catch((e) => {
showMessageError("生成提示词失败:" + e.message);
isGenerating.value = false;
closeLoading();
});
};
</script>

View File

@ -34,7 +34,7 @@
</template>
</el-popover>
</div>
<div class="item" v-loading="isGenerating" element-loading-text="正在生成歌词..." element-loading-background="rgba(122, 122, 122, 0.8)">
<div class="item" v-loading="isGenerating" element-loading-text="正在生成歌词...">
<black-input v-model:value="data.lyrics" type="textarea" :rows="10" :placeholder="promptPlaceholder" />
<button class="btn btn-lyric" @click="createLyric">生成歌词</button>
</div>

View File

@ -1,6 +1,5 @@
<template>
<div class="system-config form" v-loading="loading">
<el-tabs v-model="activeName" class="sys-tabs">
<el-tab-pane label="系统配置" name="basic">
<div class="container">
@ -8,25 +7,20 @@
<el-tabs type="border-card">
<el-tab-pane label="基础配置">
<el-form-item label="网站标题" prop="title">
<el-input v-model="system['title']"/>
<el-input v-model="system['title']" />
</el-form-item>
<el-form-item label="控制台标题" prop="admin_title">
<el-input v-model="system['admin_title']"/>
<el-input v-model="system['admin_title']" />
</el-form-item>
<el-form-item label="网站Slogan" prop="slogan">
<el-input v-model="system['slogan']"/>
<el-input v-model="system['slogan']" />
</el-form-item>
<el-form-item label="网站 LOGO" prop="logo">
<el-input v-model="system['logo']" placeholder="网站LOGO图片">
<template #append>
<el-upload
:auto-upload="true"
:show-file-list="false"
@click="beforeUpload('logo')"
:http-request="uploadImg"
>
<el-upload :auto-upload="true" :show-file-list="false" @click="beforeUpload('logo')" :http-request="uploadImg">
<el-icon class="uploader-icon">
<UploadFilled/>
<UploadFilled />
</el-icon>
</el-upload>
</template>
@ -37,14 +31,9 @@
<template #label>
<div class="label-title">
首页背景图
<el-tooltip
effect="dark"
content="网站首页背景图片"
raw-content
placement="right"
>
<el-tooltip effect="dark" content="网站首页背景图片" raw-content placement="right">
<el-icon>
<InfoFilled/>
<InfoFilled />
</el-icon>
</el-tooltip>
</div>
@ -52,14 +41,9 @@
<div class="d-flex justify-between w-100">
<el-input v-model="system['index_bg_url']" placeholder="网站首页背景图片">
<template #append>
<el-upload
:auto-upload="true"
:show-file-list="false"
@click="beforeUpload('index_bg_url')"
:http-request="uploadImg"
>
<el-upload :auto-upload="true" :show-file-list="false" @click="beforeUpload('index_bg_url')" :http-request="uploadImg">
<el-icon class="uploader-icon">
<UploadFilled/>
<UploadFilled />
</el-icon>
</el-upload>
</template>
@ -73,55 +57,34 @@
<template #label>
<div class="label-title">
首页导航菜单
<el-tooltip
effect="dark"
content="被选中的菜单将会在首页导航栏显示"
raw-content
placement="right"
>
<el-tooltip effect="dark" content="被选中的菜单将会在首页导航栏显示" raw-content placement="right">
<el-icon>
<InfoFilled/>
<InfoFilled />
</el-icon>
</el-tooltip>
</div>
</template>
<el-select
v-model="system['index_navs']"
multiple
:filterable="true"
placeholder="请选择菜单,多选"
style="width: 100%"
>
<el-option
v-for="item in menus"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
<el-select v-model="system['index_navs']" multiple :filterable="true" placeholder="请选择菜单,多选" style="width: 100%">
<el-option v-for="item in menus" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="版权信息" prop="copyright">
<el-input v-model="system['copyright']" placeholder="更改此选项需要获取 License 授权"/>
<el-input v-model="system['copyright']" placeholder="更改此选项需要获取 License 授权" />
</el-form-item>
<el-form-item>
<template #label>
<div class="label-title">
开放注册
<el-tooltip
effect="dark"
content="关闭注册之后只能通过管理后台添加用户"
raw-content
placement="right"
>
<el-tooltip effect="dark" content="关闭注册之后只能通过管理后台添加用户" raw-content placement="right">
<el-icon>
<InfoFilled/>
<InfoFilled />
</el-icon>
</el-tooltip>
</div>
</template>
<el-switch v-model="system['enabled_register']"/>
<el-switch v-model="system['enabled_register']" />
</el-form-item>
<el-form-item>
@ -129,18 +92,18 @@
<div class="label-title">
启用验证码
<el-tooltip
effect="dark"
content="启用验证码之后,注册登录都会加载行为验证码,增加安全性。此功能需要购买验证码服务才会生效。"
raw-content
placement="right"
effect="dark"
content="启用验证码之后,注册登录都会加载行为验证码,增加安全性。此功能需要购买验证码服务才会生效。"
raw-content
placement="right"
>
<el-icon>
<InfoFilled/>
<InfoFilled />
</el-icon>
</el-tooltip>
</div>
</template>
<el-switch v-model="system['enabled_verify']"/>
<el-switch v-model="system['enabled_verify']" />
</el-form-item>
<el-form-item label="注册方式" prop="register_ways">
@ -152,20 +115,15 @@
</el-form-item>
<el-form-item label="邮件域名白名单" prop="register_ways">
<items-input v-model:value="system['email_white_list']"/>
<items-input v-model:value="system['email_white_list']" />
</el-form-item>
<el-form-item label="微信客服二维码" prop="wechat_card_url">
<el-input v-model="system['wechat_card_url']" placeholder="微信客服二维码">
<template #append>
<el-upload
:auto-upload="true"
:show-file-list="false"
@click="beforeUpload('wechat_card_url')"
:http-request="uploadImg"
>
<el-upload :auto-upload="true" :show-file-list="false" @click="beforeUpload('wechat_card_url')" :http-request="uploadImg">
<el-icon class="uploader-icon">
<UploadFilled/>
<UploadFilled />
</el-icon>
</el-upload>
</template>
@ -175,41 +133,26 @@
<template #label>
<div class="label-title">
默认翻译模型
<el-tooltip
effect="dark"
content="选择一个默认模型来翻译提示词"
raw-content
placement="right"
>
<el-tooltip effect="dark" content="选择一个默认模型来翻译提示词" raw-content placement="right">
<el-icon>
<InfoFilled/>
<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 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>
</el-form-item>
<el-form-item label="开启聊天上下文">
<el-switch v-model="system['enable_context']"/>
<el-switch v-model="system['enable_context']" />
</el-form-item>
<el-form-item label="会话上下文深度">
<div class="tip-input-line">
<el-input-number v-model="system['context_deep']" :min="0" :max="10"/>
<div class="tip">会话上下文深度在老会话中继续会话默认加载多少条聊天记录作为上下文如果设置为
0
<el-input-number v-model="system['context_deep']" :min="0" :max="10" />
<div class="tip">
会话上下文深度在老会话中继续会话默认加载多少条聊天记录作为上下文如果设置为 0
则不加载聊天记录仅仅使用当前角色的上下文该配置参数必须设置需要为偶数
</div>
</div>
@ -219,62 +162,49 @@
<template #label>
<div class="label-title">
SD反向提示词
<el-tooltip
effect="dark"
content="Stable-Diffusion 绘画默认反向提示词"
raw-content
placement="right"
>
<el-tooltip effect="dark" content="Stable-Diffusion 绘画默认反向提示词" raw-content placement="right">
<el-icon>
<InfoFilled/>
<InfoFilled />
</el-icon>
</el-tooltip>
</div>
</template>
<el-input type="textarea" :rows="2" v-model="system['sd_neg_prompt']" placeholder=""/>
<el-input type="textarea" :rows="2" v-model="system['sd_neg_prompt']" placeholder="" />
</el-form-item>
<el-form-item label="会员充值说明" prop="order_pay_timeout">
<template #label>
<div class="label-title">
会员充值说明
<el-tooltip
effect="dark"
content="会员充值页面的充值说明文字"
raw-content
placement="right"
>
<el-tooltip effect="dark" content="会员充值页面的充值说明文字" raw-content placement="right">
<el-icon>
<InfoFilled/>
<InfoFilled />
</el-icon>
</el-tooltip>
</div>
</template>
<el-input type="textarea" :rows="2" v-model="system['vip_info_text']" placeholder=""/>
<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">
<el-select v-model="system['mj_mode']" placeholder="请选择模式">
<el-option v-for="item in mjModels" :value="item.value" :label="item.name" :key="item.value">{{
item.name
}}
</el-option>
<el-option v-for="item in mjModels" :value="item.value" :label="item.name" :key="item.value">{{ item.name }} </el-option>
</el-select>
</el-form-item>
</el-tab-pane>
<el-tab-pane label="算力配置">
<el-form-item label="注册赠送算力" prop="init_power">
<el-input v-model.number="system['init_power']" placeholder="新用户注册赠送算力"/>
<el-input v-model.number="system['init_power']" placeholder="新用户注册赠送算力" />
</el-form-item>
<el-form-item label="邀请赠送算力" prop="invite_power">
<el-input v-model.number="system['invite_power']" placeholder="邀请新用户注册赠送算力"/>
<el-input v-model.number="system['invite_power']" placeholder="邀请新用户注册赠送算力" />
</el-form-item>
<el-form-item label="VIP每月赠送算力" prop="vip_month_power">
<el-input v-model.number="system['vip_month_power']" placeholder="VIP用户每月赠送算力"/>
<el-input v-model.number="system['vip_month_power']" placeholder="VIP用户每月赠送算力" />
</el-form-item>
<el-form-item label="每日赠送算力" prop="daily_power">
<el-input v-model.number="system['daily_power']" placeholder="默认值0"/>
<el-input v-model.number="system['daily_power']" placeholder="默认值0" />
<el-text type="info">
如果设置0表示不赠送用户享受完免费算力额度之后就不能再发起对话了如果设置为N则系统每天将算力值小于N的用户自动补充到N注意此功能要配合XXL-JOB启用
@ -284,72 +214,70 @@
<template #label>
<div class="label-title">
MJ绘图算力
<el-tooltip
effect="dark"
content="使用MidJourney画一张图消耗算力"
raw-content
placement="right"
>
<el-tooltip effect="dark" content="使用MidJourney画一张图消耗算力" raw-content placement="right">
<el-icon>
<InfoFilled/>
<InfoFilled />
</el-icon>
</el-tooltip>
</div>
</template>
<el-input v-model.number="system['mj_power']" placeholder=""/>
<el-input v-model.number="system['mj_power']" placeholder="" />
</el-form-item>
<el-form-item>
<template #label>
<div class="label-title">
MJ操作算力
<el-tooltip
effect="dark"
content="放大,变换,重绘操作一次消耗的算力"
raw-content
placement="right"
>
<el-tooltip effect="dark" content="放大,变换,重绘操作一次消耗的算力" raw-content placement="right">
<el-icon>
<InfoFilled/>
<InfoFilled />
</el-icon>
</el-tooltip>
</div>
</template>
<el-input v-model.number="system['mj_action_power']" placeholder=""/>
<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画一张图消耗算力"/>
<el-input v-model.number="system['sd_power']" placeholder="使用Stable-Diffusion画一张图消耗算力" />
</el-form-item>
<el-form-item label="DALL-E-3算力" prop="dall_power">
<el-input v-model.number="system['dall_power']" placeholder="使用DALL-E-3画一张图消耗算力"/>
<el-input v-model.number="system['dall_power']" placeholder="使用DALL-E-3画一张图消耗算力" />
</el-form-item>
<el-form-item label="Suno 算力" prop="suno_power">
<el-input v-model.number="system['suno_power']" placeholder="使用 Suno 生成一首音乐消耗算力"/>
<el-input v-model.number="system['suno_power']" placeholder="使用 Suno 生成一首音乐消耗算力" />
</el-form-item>
<el-form-item label="Luma 算力" prop="luma_power">
<el-input v-model.number="system['luma_power']" placeholder="使用 Luma 生成一段视频消耗算力"/>
<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-tooltip effect="dark" content="使用一次 OpenAI 高级语音对话消耗的算力" raw-content placement="right">
<el-icon>
<InfoFilled/>
<InfoFilled />
</el-icon>
</el-tooltip>
</div>
</template>
<el-input v-model.number="system['advance_voice_power']" placeholder=""/>
<el-input v-model.number="system['advance_voice_power']" placeholder="" />
</el-form-item>
<el-form-item>
<template #label>
<div class="label-title">
提示词算力
<el-tooltip effect="dark" content="生成AI绘图提示词歌词视频描述消耗的算力" raw-content placement="right">
<el-icon>
<InfoFilled />
</el-icon>
</el-tooltip>
</div>
</template>
<el-input v-model.number="system['prompt_power']" placeholder="" />
</el-form-item>
</el-tab-pane>
</el-tabs>
<div style="padding: 10px;">
<div style="padding: 10px">
<el-form-item>
<el-button type="primary" @click="save('system')">保存</el-button>
</el-form-item>
@ -358,34 +286,28 @@
</div>
</el-tab-pane>
<el-tab-pane label="公告配置" name="notice">
<md-editor class="mgb20" v-model="notice" :theme="store.theme" @on-upload-img="onUploadImg"/>
<md-editor class="mgb20" v-model="notice" :theme="store.theme" @on-upload-img="onUploadImg" />
<el-form-item>
<div style="padding-top: 10px;margin-left: 150px;">
<div style="padding-top: 10px; margin-left: 150px">
<el-button type="primary" @click="save('notice')">保存</el-button>
</div>
</el-form-item>
</el-tab-pane>
<el-tab-pane label="思维导图" name="mark_map">
<md-editor class="mgb20" :theme="store.theme" v-model="system['mark_map_text']" @on-upload-img="onUploadImg"/>
<md-editor class="mgb20" :theme="store.theme" v-model="system['mark_map_text']" @on-upload-img="onUploadImg" />
<el-form-item>
<div style="padding-top: 10px;margin-left: 150px;">
<div style="padding-top: 10px; margin-left: 150px">
<el-button type="primary" @click="save('system')">保存</el-button>
</div>
</el-form-item>
</el-tab-pane>
<el-tab-pane label="菜单配置" name="menu">
<Menu/>
<Menu />
</el-tab-pane>
<el-tab-pane label="授权激活" name="license">
<div class="container">
<el-descriptions
v-if="license.is_active"
class="margin-top"
title="已授权信息"
:column="1"
border
>
<el-descriptions v-if="license.is_active" class="margin-top" title="已授权信息" :column="1" border>
<el-descriptions-item>
<template #label>
<div class="cell-item">License Key</div>
@ -428,7 +350,7 @@
<el-form :model="system" label-width="150px" label-position="right">
<el-form-item label="许可授权码" prop="license">
<el-input v-model="licenseKey"/>
<el-input v-model="licenseKey" />
</el-form-item>
<el-form-item>
@ -440,12 +362,14 @@
<el-tab-pane label="修复数据" name="fixData">
<div class="container">
<p class="text">有些版本升级的时候更新了数据库的结构比如字段名字改了需要把之前的字段的值转移到其他字段这些无法通过简单的
SQL 语句可以实现的需要手动写程序修正数据</p>
<p class="text">
有些版本升级的时候更新了数据库的结构比如字段名字改了需要把之前的字段的值转移到其他字段这些无法通过简单的 SQL
语句可以实现的需要手动写程序修正数据
</p>
<!-- <p class="text">当前版本 v4.1.4 需要修正用户数据增加了 mobile email 字段需要把之前用手机号或者邮箱注册的用户的 username 字段数据初始化到 mobile 或者 email 字段另外需要把订单的支付渠道从名字称修正为 key</p>-->
<!-- <p class="text">当前版本 v4.1.4 需要修正用户数据增加了 mobile email 字段需要把之前用手机号或者邮箱注册的用户的 username 字段数据初始化到 mobile 或者 email 字段另外需要把订单的支付渠道从名字称修正为 key</p>-->
<!-- <el-text type="danger">请注意在修复数据前请先备份好数据库以免数据丢失</el-text>-->
<!-- <el-text type="danger">请注意在修复数据前请先备份好数据库以免数据丢失</el-text>-->
<p><el-button type="primary" @click="fixData">立即修复</el-button></p>
</div>
@ -455,117 +379,133 @@
</template>
<script setup>
import {onMounted, reactive, ref} from "vue";
import {httpGet, httpPost} from "@/utils/http";
import { onMounted, reactive, ref } from "vue";
import { httpGet, httpPost } from "@/utils/http";
import Compressor from "compressorjs";
import {ElMessage, ElMessageBox} from "element-plus";
import {CloseBold, InfoFilled, Select, UploadFilled} from "@element-plus/icons-vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { CloseBold, InfoFilled, Select, UploadFilled } from "@element-plus/icons-vue";
import MdEditor from "md-editor-v3";
import 'md-editor-v3/lib/style.css';
import "md-editor-v3/lib/style.css";
import Menu from "@/views/admin/Menu.vue";
import {copyObj, dateFormat} from "@/utils/libs";
import { copyObj, dateFormat } from "@/utils/libs";
import ItemsInput from "@/components/ui/ItemsInput.vue";
import {useSharedStore} from "@/store/sharedata";
import { useSharedStore } from "@/store/sharedata";
const activeName = ref('basic')
const system = ref({models: []})
const configBak = ref({})
const loading = ref(true)
const systemFormRef = ref(null)
const models = ref([])
const notice = ref("")
const license = ref({is_active: false})
const menus = ref([])
const activeName = ref("basic");
const system = ref({ models: [] });
const configBak = ref({});
const loading = ref(true);
const systemFormRef = ref(null);
const models = ref([]);
const notice = ref("");
const license = ref({ is_active: false });
const menus = ref([]);
const mjModels = ref([
{name: "慢速Relax", value: "relax"},
{name: "快速Fast", value: "fast"},
{name: "急速Turbo", value: "turbo"},
])
const store = useSharedStore()
{ name: "慢速Relax", value: "relax" },
{ name: "快速Fast", value: "fast" },
{ name: "急速Turbo", value: "turbo" },
]);
const store = useSharedStore();
onMounted(() => {
//
httpGet('/api/admin/config/get?key=system').then(res => {
system.value = res.data
configBak.value = copyObj(system.value)
}).catch(e => {
ElMessage.error("加载系统配置失败: " + e.message)
})
httpGet("/api/admin/config/get?key=system")
.then((res) => {
system.value = res.data;
configBak.value = copyObj(system.value);
})
.catch((e) => {
ElMessage.error("加载系统配置失败: " + e.message);
});
//
httpGet('/api/admin/config/get?key=notice').then(res => {
notice.value = res.data['content']
}).catch(e => {
ElMessage.error("公告信息失败: " + e.message)
})
httpGet("/api/admin/config/get?key=notice")
.then((res) => {
notice.value = res.data["content"];
})
.catch((e) => {
ElMessage.error("公告信息失败: " + e.message);
});
httpGet('/api/admin/model/list').then(res => {
models.value = res.data
loading.value = false
}).catch(e => {
ElMessage.error("获取模型失败:" + e.message)
})
httpGet("/api/admin/model/list")
.then((res) => {
models.value = res.data;
loading.value = false;
})
.catch((e) => {
ElMessage.error("获取模型失败:" + e.message);
});
httpGet('/api/admin/menu/list').then(res => {
menus.value = res.data
}).catch(e => {
ElMessage.error("获取模型失败:" + e.message)
})
httpGet("/api/admin/menu/list")
.then((res) => {
menus.value = res.data;
})
.catch((e) => {
ElMessage.error("获取模型失败:" + e.message);
});
fetchLicense()
})
fetchLicense();
});
const fetchLicense = () => {
httpGet("/api/admin/config/license").then(res => {
license.value = res.data
}).catch(e => {
ElMessage.error("获取 License 失败:" + e.message)
})
}
httpGet("/api/admin/config/license")
.then((res) => {
license.value = res.data;
})
.catch((e) => {
ElMessage.error("获取 License 失败:" + e.message);
});
};
const rules = reactive({
title: [{required: true, message: '请输入网站标题', trigger: 'blur',}],
admin_title: [{required: true, message: '请输入控制台标题', trigger: 'blur',}],
init_chat_calls: [{required: true, message: '请输入赠送对话次数', trigger: 'blur'}],
user_img_calls: [{required: true, message: '请输入赠送绘图次数', trigger: 'blur'}],
})
title: [{ required: true, message: "请输入网站标题", trigger: "blur" }],
admin_title: [{ required: true, message: "请输入控制台标题", trigger: "blur" }],
init_chat_calls: [{ required: true, message: "请输入赠送对话次数", trigger: "blur" }],
user_img_calls: [{ required: true, message: "请输入赠送绘图次数", trigger: "blur" }],
});
const save = function (key) {
if (key === 'system') {
if (key === "system") {
systemFormRef.value.validate((valid) => {
if (valid) {
httpPost('/api/admin/config/update', {key: key, config: system.value, config_bak: configBak.value}).then(() => {
ElMessage.success("操作成功!")
}).catch(e => {
ElMessage.error("操作失败:" + e.message)
})
httpPost("/api/admin/config/update", { key: key, config: system.value, config_bak: configBak.value })
.then(() => {
ElMessage.success("操作成功!");
})
.catch((e) => {
ElMessage.error("操作失败:" + e.message);
});
}
})
} else if (key === 'notice') {
httpPost('/api/admin/config/update', {key: key, config: {content: notice.value, updated: true}}).then(() => {
ElMessage.success("操作成功!")
}).catch(e => {
ElMessage.error("操作失败:" + e.message)
})
});
} else if (key === "notice") {
httpPost("/api/admin/config/update", { key: key, config: { content: notice.value, updated: true } })
.then(() => {
ElMessage.success("操作成功!");
})
.catch((e) => {
ElMessage.error("操作失败:" + e.message);
});
}
}
};
//
const licenseKey = ref("")
const licenseKey = ref("");
const active = () => {
if (licenseKey.value === "") {
return ElMessage.error("请输入授权码")
return ElMessage.error("请输入授权码");
}
httpPost("/api/admin/config/active", {license: licenseKey.value}).then(res => {
ElMessage.success("授权成功,机器编码为:" + res.data)
fetchLicense()
}).catch(e => {
ElMessage.error(e.message)
})
}
httpPost("/api/admin/config/active", { license: licenseKey.value })
.then((res) => {
ElMessage.success("授权成功,机器编码为:" + res.data);
fetchLicense();
})
.catch((e) => {
ElMessage.error(e.message);
});
};
const configKey = ref("")
const configKey = ref("");
const beforeUpload = (key) => {
configKey.value = key
}
configKey.value = key;
};
//
const uploadImg = (file) => {
@ -574,17 +514,19 @@ const uploadImg = (file) => {
quality: 0.6,
success(result) {
const formData = new FormData();
formData.append('file', result, result.name);
formData.append("file", result, result.name);
//
httpPost('/api/admin/upload', formData).then((res) => {
system.value[configKey.value] = res.data.url
ElMessage.success('上传成功')
}).catch((e) => {
ElMessage.error('上传失败:' + e.message)
})
httpPost("/api/admin/upload", formData)
.then((res) => {
system.value[configKey.value] = res.data.url;
ElMessage.success("上传成功");
})
.catch((e) => {
ElMessage.error("上传失败:" + e.message);
});
},
error(e) {
ElMessage.error('上传失败:' + e.message)
ElMessage.error("上传失败:" + e.message);
},
});
};
@ -592,45 +534,44 @@ const uploadImg = (file) => {
//
const onUploadImg = (files, callback) => {
Promise.all(
files.map((file) => {
return new Promise((rev, rej) => {
const formData = new FormData();
formData.append('file', file, file.name);
//
httpPost('/api/admin/upload', formData).then((res) => rev(res)).catch((error) => rej(error));
});
})
).then(res => {
ElMessage.success({message: "上传成功", duration: 500})
callback(res.map((item) => item.data.url));
}).catch(e => {
ElMessage.error('图片上传失败:' + e.message)
})
files.map((file) => {
return new Promise((rev, rej) => {
const formData = new FormData();
formData.append("file", file, file.name);
//
httpPost("/api/admin/upload", formData)
.then((res) => rev(res))
.catch((error) => rej(error));
});
})
)
.then((res) => {
ElMessage.success({ message: "上传成功", duration: 500 });
callback(res.map((item) => item.data.url));
})
.catch((e) => {
ElMessage.error("图片上传失败:" + e.message);
});
};
const fixData = () => {
ElMessageBox.confirm(
'在修复数据前,请先备份好数据库,以免数据丢失!是否继续操作?',
'警告',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
).then(() => {
loading.value = true
httpGet("/api/admin/config/fixData").then(() => {
ElMessage.success("数据修复成功")
loading.value = false
}).catch(e => {
loading.value = false
ElMessage.error("数据修复失败:" + e.message)
})
})
}
ElMessageBox.confirm("在修复数据前,请先备份好数据库,以免数据丢失!是否继续操作?", "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(() => {
loading.value = true;
httpGet("/api/admin/config/fixData")
.then(() => {
ElMessage.success("数据修复成功");
loading.value = false;
})
.catch((e) => {
loading.value = false;
ElMessage.error("数据修复失败:" + e.message);
});
});
};
</script>
<style lang="stylus" scoped>