From dffdbf697b5b7d001a917ea71e267393f7aeeb53 Mon Sep 17 00:00:00 2001 From: GeekMaster Date: Tue, 6 May 2025 18:51:55 +0800 Subject: [PATCH 01/32] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E8=A1=A8=E6=95=B0=E6=8D=AE=E8=BF=81=E7=A7=BBbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/core/app_server.go | 14 +++++++++++ api/handler/admin/user_handler.go | 3 ++- api/handler/user_handler.go | 16 ++++++------ api/store/model/user.go | 42 +++++++++++++++---------------- api/store/mysql.go | 5 ++-- 5 files changed, 49 insertions(+), 31 deletions(-) diff --git a/api/core/app_server.go b/api/core/app_server.go index 14188562..3b97a3b4 100644 --- a/api/core/app_server.go +++ b/api/core/app_server.go @@ -99,6 +99,20 @@ func (s *AppServer) Run(db *gorm.DB) error { &model.UserLoginLog{}, &model.DallJob{}, ) + // 手动删除字段 + if db.Migrator().HasColumn(&model.Order{}, "deleted_at") { + db.Migrator().DropColumn(&model.Order{}, "deleted_at") + } + if db.Migrator().HasColumn(&model.ChatItem{}, "deleted_at") { + db.Migrator().DropColumn(&model.ChatItem{}, "deleted_at") + } + if db.Migrator().HasColumn(&model.ChatMessage{}, "deleted_at") { + db.Migrator().DropColumn(&model.ChatMessage{}, "deleted_at") + } + if db.Migrator().HasColumn(&model.User{}, "chat_config") { + db.Migrator().DropColumn(&model.User{}, "chat_config") + } + logger.Info("Database tables migrated successfully") // 统计安装信息 diff --git a/api/handler/admin/user_handler.go b/api/handler/admin/user_handler.go index 6f8a59fa..90ff4b20 100644 --- a/api/handler/admin/user_handler.go +++ b/api/handler/admin/user_handler.go @@ -178,6 +178,7 @@ func (h *UserHandler) Save(c *gin.Context) { Power: data.Power, Status: true, ChatRoles: utils.JsonEncode(data.ChatRoles), + ChatConfig: "{}", ChatModels: utils.JsonEncode(data.ChatModels), ExpiredTime: utils.Str2stamp(data.ExpiredTime), } @@ -353,4 +354,4 @@ func (h *UserHandler) GenLoginLink(c *gin.Context) { } resp.SUCCESS(c, tokenString) -} \ No newline at end of file +} diff --git a/api/handler/user_handler.go b/api/handler/user_handler.go index dee60047..60037952 100644 --- a/api/handler/user_handler.go +++ b/api/handler/user_handler.go @@ -137,13 +137,15 @@ func (h *UserHandler) Register(c *gin.Context) { salt := utils.RandString(8) user := model.User{ - Username: data.Username, - Password: utils.GenPassword(data.Password, salt), - Avatar: "/images/avatar/user.png", - Salt: salt, - Status: true, - ChatRoles: utils.JsonEncode([]string{"gpt"}), // 默认只订阅通用助手角色 - Power: h.App.SysConfig.InitPower, + Username: data.Username, + Password: utils.GenPassword(data.Password, salt), + Avatar: "/images/avatar/user.png", + Salt: salt, + Status: true, + ChatRoles: utils.JsonEncode([]string{"gpt"}), // 默认只订阅通用助手角色 + ChatConfig: "{}", + ChatModels: "{}", + Power: h.App.SysConfig.InitPower, } // check if the username is existing diff --git a/api/store/model/user.go b/api/store/model/user.go index 3a6fda61..a1866ffb 100644 --- a/api/store/model/user.go +++ b/api/store/model/user.go @@ -5,27 +5,27 @@ import ( ) type User struct { - Id uint `gorm:"column:id;primaryKey;autoIncrement" json:"id"` - Username string `gorm:"column:username;type:varchar(30);uniqueIndex;not null;comment:用户名" json:"username"` - Mobile string `gorm:"column:mobile;type:char(11);comment:手机号" json:"mobile"` - Email string `gorm:"column:email;type:varchar(50);comment:邮箱地址" json:"email"` - Nickname string `gorm:"column:nickname;type:varchar(30);not null;comment:昵称" json:"nickname"` - Password string `gorm:"column:password;type:char(64);not null;comment:密码" json:"password"` - Avatar string `gorm:"column:avatar;type:varchar(255);not null;comment:头像" json:"avatar"` - Salt string `gorm:"column:salt;type:char(12);not null;comment:密码盐" json:"salt"` - Power int `gorm:"column:power;type:int;not null;default:0;comment:剩余算力" json:"power"` - ExpiredTime int64 `gorm:"column:expired_time;type:int;not null;comment:用户过期时间" json:"expired_time"` - Status bool `gorm:"column:status;type:tinyint(1);not null;comment:当前状态" json:"status"` - ChatConfig string `gorm:"column:chat_config;type:text;not null;comment:聊天配置json" json:"chat_config"` - ChatRoles string `gorm:"column:chat_roles_json;type:text;not null;comment:聊天角色 json" json:"chat_roles_json"` - ChatModels string `gorm:"column:chat_models_json;type:text;not null;comment:AI模型 json" json:"chat_models_json"` - LastLoginAt int64 `gorm:"column:last_login_at;type:int;not null;comment:最后登录时间" json:"last_login_at"` - Vip bool `gorm:"column:vip;type:tinyint(1);not null;default:0;comment:是否会员" json:"vip"` - LastLoginIp string `gorm:"column:last_login_ip;type:char(16);not null;comment:最后登录 IP" json:"last_login_ip"` - OpenId string `gorm:"column:openid;type:varchar(100);comment:第三方登录账号ID" json:"openid"` - Platform string `gorm:"column:platform;type:varchar(30);comment:登录平台" json:"platform"` - CreatedAt time.Time `gorm:"column:created_at;type:datetime;not null" json:"created_at"` - UpdatedAt time.Time `gorm:"column:updated_at;type:datetime;not null" json:"updated_at"` + Id uint `gorm:"column:id;primaryKey;autoIncrement" json:"id"` + Username string `gorm:"column:username;type:varchar(30);uniqueIndex;not null;comment:用户名" json:"username"` + Mobile string `gorm:"column:mobile;type:char(11);comment:手机号" json:"mobile"` + Email string `gorm:"column:email;type:varchar(50);comment:邮箱地址" json:"email"` + Nickname string `gorm:"column:nickname;type:varchar(30);not null;comment:昵称" json:"nickname"` + Password string `gorm:"column:password;type:char(64);not null;comment:密码" json:"password"` + Avatar string `gorm:"column:avatar;type:varchar(255);not null;comment:头像" json:"avatar"` + Salt string `gorm:"column:salt;type:char(12);not null;comment:密码盐" json:"salt"` + Power int `gorm:"column:power;type:int;default:0;comment:剩余算力" json:"power"` + ExpiredTime int64 `gorm:"column:expired_time;type:int;not null;comment:用户过期时间" json:"expired_time"` + Status bool `gorm:"column:status;type:tinyint(1);not null;comment:当前状态" json:"status"` + ChatConfig string `gorm:"column:chat_config_json;type:text;default:null;comment:聊天配置json" json:"chat_config"` + ChatRoles string `gorm:"column:chat_roles_json;type:text;default:null;comment:聊天角色 json" json:"chat_roles"` + ChatModels string `gorm:"column:chat_models_json;type:text;default:null;comment:AI模型 json" json:"chat_models"` + LastLoginAt int64 `gorm:"column:last_login_at;type:int;not null;comment:最后登录时间" json:"last_login_at"` + Vip bool `gorm:"column:vip;type:tinyint(1);not null;default:0;comment:是否会员" json:"vip"` + LastLoginIp string `gorm:"column:last_login_ip;type:char(16);not null;comment:最后登录 IP" json:"last_login_ip"` + OpenId string `gorm:"column:openid;type:varchar(100);comment:第三方登录账号ID" json:"openid"` + Platform string `gorm:"column:platform;type:varchar(30);comment:登录平台" json:"platform"` + CreatedAt time.Time `gorm:"column:created_at;type:datetime;not null" json:"created_at"` + UpdatedAt time.Time `gorm:"column:updated_at;type:datetime;not null" json:"updated_at"` } func (m *User) TableName() string { diff --git a/api/store/mysql.go b/api/store/mysql.go index 70aba960..bc4707a5 100644 --- a/api/store/mysql.go +++ b/api/store/mysql.go @@ -9,11 +9,12 @@ package store import ( "geekai/core/types" + "time" + "gorm.io/driver/mysql" "gorm.io/gorm" "gorm.io/gorm/logger" "gorm.io/gorm/schema" - "time" ) func NewGormConfig() *gorm.Config { @@ -36,9 +37,9 @@ func NewMysql(config *gorm.Config, appConfig *types.AppConfig) (*gorm.DB, error) if err != nil { return nil, err } + sqlDB.SetMaxIdleConns(32) sqlDB.SetMaxOpenConns(512) sqlDB.SetConnMaxLifetime(time.Hour) - return db, nil } From fa74ae18eea1cff58bbc99f6a4dd1d452dd99a24 Mon Sep 17 00:00:00 2001 From: GeekMaster Date: Tue, 6 May 2025 18:54:18 +0800 Subject: [PATCH 02/32] =?UTF-8?q?=E8=AE=BE=E7=BD=AE=20gorm=20=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E7=AD=89=E7=BA=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/store/mysql.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/store/mysql.go b/api/store/mysql.go index bc4707a5..8a582740 100644 --- a/api/store/mysql.go +++ b/api/store/mysql.go @@ -19,7 +19,7 @@ import ( func NewGormConfig() *gorm.Config { return &gorm.Config{ - Logger: logger.Default.LogMode(logger.Silent), + Logger: logger.Default.LogMode(logger.Error), NamingStrategy: schema.NamingStrategy{ TablePrefix: "chatgpt_", // 设置表前缀 SingularTable: false, // 使用单数表名形式 From 643cf6085acaf8021b59f51462f8008aa080e746 Mon Sep 17 00:00:00 2001 From: GeekMaster Date: Tue, 6 May 2025 20:12:10 +0800 Subject: [PATCH 03/32] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=B8=80=E9=94=AE?= =?UTF-8?q?=E5=AE=89=E8=A3=85=E9=83=A8=E7=BD=B2=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build/geekai-install.sh | 196 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 196 insertions(+) create mode 100755 build/geekai-install.sh diff --git a/build/geekai-install.sh b/build/geekai-install.sh new file mode 100755 index 00000000..5c6fdcde --- /dev/null +++ b/build/geekai-install.sh @@ -0,0 +1,196 @@ +#!/usr/bin/env bash +# install-docker.sh — 自动识别 Linux 发行版并安装 Docker (Compose v2) +# 支持国内镜像自动/手动切换,内置错误处理与友好提示。 +# --------------------------------------------------------------- +set -Eeuo pipefail + +# ────────────────────────── 彩色输出 ────────────────────────── # +info() { printf "\e[32m[INFO]\e[0m %s\n" "$*"; } +warn() { printf "\e[33m[WARN]\e[0m %s\n" "$*" >&2; } +error() { printf "\e[31m[ERROR]\e[0m %s\n" "$*" >&2; exit 1; } +trap 'error "脚本失败,命令:\"${BASH_COMMAND}\", 退出码 $?"' ERR + +need_cmd() { command -v "$1" &>/dev/null; } +as_root() { ((EUID==0)) && "$@" || sudo "$@"; } + +# ─────────── 部署 GeekAI-Plus 函数 ─────────── # +deploy_geekai_plus(){ + local repo=https://gitee.com/blackfox/geekai-plus-open.git + local dir=${GEEKAI_DIR:-geekai-plus} + info "部署 GeekAI-Plus 到目录 \"$dir\"" + need_cmd git || error "未找到 git,请检查安装步骤。" + if [[ -d $dir ]]; then + warn "目录 $dir 已存在,跳过克隆。" + else + git clone --depth 1 "$repo" "$dir" + fi + pushd "$dir" >/dev/null + info "启动 docker compose…" + if docker compose up -d; then + info "GeekAI-Plus 部署完成!请访问 http://ip:8080。" + else + error "docker compose 启动失败。" + fi + popd >/dev/null +} + +# ─────────────────── 检测 Docker 是否已安装 ─────────────────── # +if need_cmd docker && (docker compose version &>/dev/null || need_cmd docker-compose); then + info "Docker 与 Compose 已安装,无需重复操作。" + deploy_geekai_plus + exit 0 +fi + +# ────────────────────────── 解析发行版 ───────────────────────── # +[[ -r /etc/os-release ]] || error "无法识别系统:缺少 /etc/os-release" +. /etc/os-release +OS_ID=${ID,,} +OS_VER=${VERSION_ID:-unknown} +ARCH=$(uname -m) + +info "检测到系统:$PRETTY_NAME ($OS_ID $OS_VER, $ARCH)" + +# ──────────────────── 镜像域名与自动回退逻辑 ──────────────────── # +# ❶ 用户可通过 DOCKER_MIRROR 指定: +# - aliyun → https://mirrors.aliyun.com/docker-ce +# - tuna → https://mirrors.tuna.tsinghua.edu.cn/docker-ce +# - official (默认) → https://download.docker.com +# +# ❷ 若未指定,则先探测官方域名能否连通;失败则自动切换到 aliyun。 +# +choose_mirror() { + local sel=${DOCKER_MIRROR:-auto} + + case "$sel" in + aliyun) MIRROR="https://mirrors.aliyun.com/docker-ce" ;; + tuna) MIRROR="https://mirrors.tuna.tsinghua.edu.cn/docker-ce" ;; + official) MIRROR="https://download.docker.com" ;; + auto) + MIRROR="https://download.docker.com" + info "检测官方源连通性…" + if ! curl -m 3 -sfL "${MIRROR}/linux/${OS_ID}/gpg" -o /dev/null; then + warn "官方源不可达,回退至阿里云镜像。" + MIRROR="https://mirrors.aliyun.com/docker-ce" + fi ;; + *) + error "未知镜像标识:$sel(可选 aliyun|tuna|official)" ;; + esac + info "使用镜像源:$MIRROR" +} +choose_mirror + +# ────────────────────────── 安装函数 ────────────────────────── # +install_docker_debian_like() { + info "使用 APT 安装 Docker" + as_root apt-get update -y + as_root apt-get install -y ca-certificates curl git gnupg lsb-release + + as_root install -m 0755 -d /etc/apt/keyrings + curl -fsSL "${MIRROR}/linux/${OS_ID}/gpg" \ + | as_root gpg --dearmor -o /etc/apt/keyrings/docker.gpg + chmod a+r /etc/apt/keyrings/docker.gpg + + echo \ + "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \ + ${MIRROR}/linux/${OS_ID} $(lsb_release -cs) stable" \ + | as_root tee /etc/apt/sources.list.d/docker.list >/dev/null + + as_root apt-get update -y + as_root apt-get install -y \ + docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin +} + +install_docker_centos_like() { + info "使用 YUM/DNF 安装 Docker" + local pkgcmd + if need_cmd dnf; then pkgcmd=dnf; else pkgcmd=yum; fi + + as_root $pkgcmd -y install ${pkgcmd}-plugins-core git + as_root $pkgcmd config-manager \ + --add-repo "${MIRROR}/linux/centos/docker-ce.repo" + as_root $pkgcmd -y install \ + docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin + as_root systemctl enable --now docker +} + +install_docker_fedora() { + info "使用 DNF 安装 Docker (Fedora)" + as_root dnf -y install dnf-plugins-core + as_root dnf config-manager --add-repo \ + "${MIRROR}/linux/fedora/docker-ce.repo" + as_root dnf -y install \ + docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin git + as_root systemctl enable --now docker +} + +install_docker_arch() { + info "使用 pacman 安装 Docker" + as_root pacman -Sy --noconfirm docker docker-compose git + as_root systemctl enable --now docker +} + +install_docker_opensuse() { + info "使用 zypper 安装 Docker" + as_root zypper -n in docker docker-compose git + as_root systemctl enable --now docker +} + +install_docker_alpine() { + info "使用 apk 安装 Docker" + as_root apk add --no-cache docker docker-cli-compose git + as_root rc-update add docker boot git + as_root service docker start +} + +install_docker_fallback() { + warn "发行版 \"$OS_ID\" 未做专门适配,执行官方一键脚本…" + curl -fsSL get.docker.com | as_root sh +} + +# ────────────────────────── 分发安装 ────────────────────────── # +case "$OS_ID" in + debian|ubuntu|linuxmint) install_docker_debian_like ;; + centos|rocky|almalinux|rhel) install_docker_centos_like ;; + fedora) install_docker_fedora ;; + arch|manjaro) install_docker_arch ;; + opensuse*|suse|sles) install_docker_opensuse ;; + alpine) install_docker_alpine ;; + *) install_docker_fallback ;; +esac + +# ──────────────────── 安装后检查 & docker 组 ─────────────────── # +need_cmd docker || error "Docker 安装后仍不可用,请检查日志。" +as_root usermod -aG docker "${SUDO_USER:-$USER}" || true + +# ──────────────────── (可选) 镜像加速器配置 ─────────────────── # +if [[ "${ENABLE_REGISTRYMIRROR:-1}" == "1" ]]; then + as_root mkdir -p /etc/docker + cat <<-JSON | as_root tee /etc/docker/daemon.json >/dev/null + { + "registry-mirrors": [ + "https://registry.docker-cn.com", "https://mirror.ccs.tencentyun.com","https://hub-mirror.c.163.com" + ] + } +JSON + as_root systemctl restart docker + info "已为 Docker 配置国内镜像加速器。" +fi + +# ────────────────────────── 最终信息 ────────────────────────── # +info "Docker 版本:$(docker --version | cut -d',' -f1)" +if docker compose version &>/dev/null; then + info "Compose 版本:$(docker compose version --short)" +elif need_cmd docker-compose; then + info "Compose 版本:$(docker-compose --version | awk '{print $3}')" +fi + +cat <<'EOF' +╭─────────────────────────────────────────────────────────╮ +│ 安装完成! │ +│ · 请重新登录或执行 `newgrp docker` 以使用 docker 免 sudo │ +│ · 如需跳过镜像加速,可执行:ENABLE_REGISTRYMIRROR=0 ... │ +╰─────────────────────────────────────────────────────────╯ +EOF + +deploy_geekai_plus + From 10e3e61b2cba1ba74176a7b45803db694471eebc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B8=85=E6=9F=AF?= <10691891+qing_ke@user.noreply.gitee.com> Date: Wed, 7 May 2025 04:17:18 +0800 Subject: [PATCH 04/32] =?UTF-8?q?1.=E7=AE=A1=E7=90=86=E5=90=8E=E5=8F=B0?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E8=AE=BE=E7=BD=AE=E9=BB=98=E8=AE=A4=E6=98=B5?= =?UTF-8?q?=E7=A7=B0=E3=80=822.Suno=E9=9F=B3=E4=B9=90=E5=88=9B=E4=BD=9C?= =?UTF-8?q?=E6=94=AF=E6=8C=814.5=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/core/types/config.go | 9 +++++---- api/handler/user_handler.go | 7 ++++++- web/src/views/Suno.vue | 3 ++- web/src/views/admin/SysConfig.vue | 4 ++++ 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/api/core/types/config.go b/api/core/types/config.go index 29169539..01b6bc02 100644 --- a/api/core/types/config.go +++ b/api/core/types/config.go @@ -162,10 +162,11 @@ type SystemConfig struct { SdNegPrompt string `json:"sd_neg_prompt"` // SD 默认反向提示词 MjMode string `json:"mj_mode"` // midjourney 默认的API模式,relax, fast, turbo - IndexNavs []int `json:"index_navs"` // 首页显示的导航菜单 - Copyright string `json:"copyright"` // 版权信息 - ICP string `json:"icp"` // ICP 备案号 - MarkMapText string `json:"mark_map_text"` // 思维导入的默认文本 + IndexNavs []int `json:"index_navs"` // 首页显示的导航菜单 + Copyright string `json:"copyright"` // 版权信息 + DefaultNickname string `json:"default_nickname"` // 默认昵称 + ICP string `json:"icp"` // ICP 备案号 + MarkMapText string `json:"mark_map_text"` // 思维导入的默认文本 EnabledVerify bool `json:"enabled_verify"` // 是否启用验证码 EmailWhiteList []string `json:"email_white_list"` // 邮箱白名单列表 diff --git a/api/handler/user_handler.go b/api/handler/user_handler.go index dee60047..2958e14c 100644 --- a/api/handler/user_handler.go +++ b/api/handler/user_handler.go @@ -170,10 +170,15 @@ func (h *UserHandler) Register(c *gin.Context) { if data.InviteCode != "" { user.Power += h.App.SysConfig.InvitePower } + if h.licenseService.GetLicense().Configs.DeCopy { user.Nickname = fmt.Sprintf("用户@%d", utils.RandomNumber(6)) } else { - user.Nickname = fmt.Sprintf("极客学长@%d", utils.RandomNumber(6)) + defaultNickname := h.App.SysConfig.DefaultNickname + if defaultNickname == "" { + defaultNickname = "极客学长" + } + user.Nickname = fmt.Sprintf("%s@%d", defaultNickname, utils.RandomNumber(6)) } tx := h.DB.Begin() diff --git a/web/src/views/Suno.vue b/web/src/views/Suno.vue index b2bd4f89..55d0a497 100644 --- a/web/src/views/Suno.vue +++ b/web/src/views/Suno.vue @@ -293,6 +293,7 @@ const models = ref([ { label: "v3.0", value: "chirp-v3-0" }, { label: "v3.5", value: "chirp-v3-5" }, { label: "v4.0", value: "chirp-v4" }, + { label: "v4.5", value: "chirp-auk" }, ]); const tags = ref([ { label: "女声", value: "female vocals" }, @@ -313,7 +314,7 @@ const tags = ref([ { label: "嘻哈", value: "hip hop" }, ]); const data = ref({ - model: "chirp-v3-0", + model: "chirp-auk", tags: "", lyrics: "", prompt: "", diff --git a/web/src/views/admin/SysConfig.vue b/web/src/views/admin/SysConfig.vue index 36802680..f3820f46 100644 --- a/web/src/views/admin/SysConfig.vue +++ b/web/src/views/admin/SysConfig.vue @@ -57,6 +57,10 @@ + + + + From 9edd6621b1f94701ff19811e20c1ebb33355e321 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B8=85=E6=9F=AF?= <10691891+qing_ke@user.noreply.gitee.com> Date: Wed, 7 May 2025 04:19:26 +0800 Subject: [PATCH 05/32] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d5c4a124..facb0b76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,8 @@ - Bug 修复:修复MJ画图页面已画出的图,点复制指令无效问题 - 功能优化:MJ画图的分辨率支持自定义,优先使用prompt中--ar参数 - Bug修复:修复MJ绘画U1-V1,拼写错误 - +- 功能新增:管理后台支持设置默认昵称 +- 功能优化:支持 Suno v4.5 模型支持 ## v4.2.2 From 26c18fcd5ab61e392131f2e81bc34b0f062741a8 Mon Sep 17 00:00:00 2001 From: GeekMaster Date: Wed, 7 May 2025 09:42:53 +0800 Subject: [PATCH 06/32] =?UTF-8?q?=E6=94=AF=E6=8C=81=20suno=204.5=20?= =?UTF-8?q?=E9=9F=B3=E4=B9=90=E6=A8=A1=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/service/suno/service.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/api/service/suno/service.go b/api/service/suno/service.go index 14e36e88..59e2aecc 100644 --- a/api/service/suno/service.go +++ b/api/service/suno/service.go @@ -94,6 +94,8 @@ func (s *Service) Run() { continue } + logger.Infof("任务提交成功: %+v", r) + // 更新任务信息 s.db.Model(&model.SunoJob{Id: task.Id}).UpdateColumns(map[string]interface{}{ "task_id": r.Data, @@ -127,6 +129,7 @@ func (s *Service) Create(task types.SunoTask) (RespVo, error) { "continue_clip_id": task.RefSongId, "continue_at": task.ExtendSecs, "make_instrumental": task.Instrumental, + "mv": task.Model, } // 灵感模式 if task.Type == 1 { @@ -134,7 +137,6 @@ func (s *Service) Create(task types.SunoTask) (RespVo, error) { } else { // 自定义模式 reqBody["prompt"] = task.Lyrics reqBody["tags"] = task.Tags - reqBody["mv"] = task.Model reqBody["title"] = task.Title } From c4fe6c825e51d58d0cf263459d6bebd28adbe168 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B8=85=E6=9F=AF?= <10691891+qing_ke@user.noreply.gitee.com> Date: Thu, 8 May 2025 03:19:51 +0800 Subject: [PATCH 07/32] =?UTF-8?q?=E7=94=A8=E6=88=B7=E6=B3=A8=E5=86=8C?= =?UTF-8?q?=E5=92=8C=E7=94=A8=E6=88=B7=E7=99=BB=E5=BD=95=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E5=8D=8F=E8=AE=AE=E5=92=8C=E9=9A=90=E7=A7=81?= =?UTF-8?q?=E6=94=BF=E7=AD=96=E5=8A=9F=E8=83=BD=EF=BC=8C=E9=9C=80=E8=A6=81?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E5=90=8C=E6=84=8F=E5=8D=8F=E8=AE=AE=E6=89=8D?= =?UTF-8?q?=E5=8F=AF=E6=B3=A8=E5=86=8C=E5=92=8C=E7=99=BB=E5=BD=95=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- database/update-v4.2.3.1.sql | 3 + database/update-v4.2.3.sql | 2 +- web/package-lock.json | 12 ++ web/package.json | 11 +- web/src/views/Login.vue | 180 +++++++++++++++++++++++++++++- web/src/views/Register.vue | 174 ++++++++++++++++++++++++++++- web/src/views/admin/SysConfig.vue | 55 +++++++++ 7 files changed, 429 insertions(+), 8 deletions(-) create mode 100644 database/update-v4.2.3.1.sql diff --git a/database/update-v4.2.3.1.sql b/database/update-v4.2.3.1.sql new file mode 100644 index 00000000..5029b160 --- /dev/null +++ b/database/update-v4.2.3.1.sql @@ -0,0 +1,3 @@ +INSERT INTO `chatgpt_configs` (`id`, `marker`, `config_json`) VALUES +(4, 'privacy', '{\"sd_neg_prompt\":\"\",\"mj_mode\":\"\",\"index_navs\":null,\"copyright\":\"\",\"default_nickname\":\"\",\"icp\":\"\",\"mark_map_text\":\"\",\"enabled_verify\":false,\"email_white_list\":null,\"translate_model_id\":0,\"max_file_size\":0,\"content\":\"# 隐私政策\\n\\n我们非常重视用户的隐私和个人信息保护。您在使用我们的产品与服务时,我们可能会收集和使用您的相关信息。我们希望通过本《隐私政策》向您说明我们在收集和使用您相关信息时对应的处理规则。\",\"updated\":true}'), +(5, 'agreement', '{\"sd_neg_prompt\":\"\",\"mj_mode\":\"\",\"index_navs\":null,\"copyright\":\"\",\"default_nickname\":\"\",\"icp\":\"\",\"mark_map_text\":\"\",\"enabled_verify\":false,\"email_white_list\":null,\"translate_model_id\":0,\"max_file_size\":0,\"content\":\"# 用户协议\\n\\n用户在使用本服务前应当阅读并同意本协议。本协议内容包括协议正文及所有本平台已经发布的或将来可能发布的各类规则。所有规则为本协议不可分割的组成部分,与协议正文具有同等法律效力。\",\"updated\":true}'); diff --git a/database/update-v4.2.3.sql b/database/update-v4.2.3.sql index 0cdf9f14..98ac1d2f 100644 --- a/database/update-v4.2.3.sql +++ b/database/update-v4.2.3.sql @@ -2,4 +2,4 @@ ALTER TABLE `chatgpt_chat_models` ADD `category` VARCHAR(1024) NOT NULL DEFAULT ALTER TABLE `chatgpt_chat_models` ADD `description` VARCHAR(1024) NOT NULL DEFAULT '' COMMENT '模型类型描述' AFTER `id`; ALTER TABLE `chatgpt_orders` DROP `deleted_at`; ALTER TABLE `chatgpt_chat_history` DROP `deleted_at`; -ALTER TABLE `chatgpt_chat_items` DROP `deleted_at`; +ALTER TABLE `chatgpt_chat_items` DROP `deleted_at`; \ No newline at end of file diff --git a/web/package-lock.json b/web/package-lock.json index 82dd37b7..8e7ac086 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -28,6 +28,7 @@ "markdown-it": "^13.0.1", "markdown-it-emoji": "^2.0.0", "markdown-it-mathjax3": "^4.3.2", + "marked": "^15.0.11", "markmap-common": "^0.16.0", "markmap-lib": "^0.16.1", "markmap-toolbar": "^0.17.0", @@ -8721,6 +8722,17 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/marked": { + "version": "15.0.11", + "resolved": "https://registry.npmmirror.com/marked/-/marked-15.0.11.tgz", + "integrity": "sha512-1BEXAU2euRCG3xwgLVT1y0xbJEld1XOrmRJpUwRCcy7rxhSCwMrmEu9LXoPhHSCJG41V7YcQ2mjKRr5BA3ITIA==", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/markmap-common": { "version": "0.16.0", "resolved": "https://registry.npmjs.org/markmap-common/-/markmap-common-0.16.0.tgz", diff --git a/web/package.json b/web/package.json index a7ea40e6..b6bdf84b 100644 --- a/web/package.json +++ b/web/package.json @@ -8,6 +8,11 @@ "lint": "vue-cli-service lint" }, "dependencies": { + "@better-scroll/core": "^2.5.1", + "@better-scroll/mouse-wheel": "^2.5.1", + "@better-scroll/observe-dom": "^2.5.1", + "@better-scroll/pull-up": "^2.5.1", + "@better-scroll/scroll-bar": "^2.5.1", "@element-plus/icons-vue": "^2.3.1", "animate.css": "^4.1.1", "axios": "^0.27.2", @@ -23,6 +28,7 @@ "markdown-it": "^13.0.1", "markdown-it-emoji": "^2.0.0", "markdown-it-mathjax3": "^4.3.2", + "marked": "^15.0.11", "markmap-common": "^0.16.0", "markmap-lib": "^0.16.1", "markmap-toolbar": "^0.17.0", @@ -32,11 +38,6 @@ "pinia": "^2.1.4", "qrcode": "^1.5.3", "qs": "^6.11.1", - "@better-scroll/core": "^2.5.1", - "@better-scroll/mouse-wheel": "^2.5.1", - "@better-scroll/observe-dom": "^2.5.1", - "@better-scroll/pull-up": "^2.5.1", - "@better-scroll/scroll-bar": "^2.5.1", "sortablejs": "^1.15.0", "three": "^0.128.0", "vant": "^4.5.0", diff --git a/web/src/views/Login.vue b/web/src/views/Login.vue index 07d2887e..7111f477 100644 --- a/web/src/views/Login.vue +++ b/web/src/views/Login.vue @@ -40,6 +40,16 @@ @keyup="handleKeyup" /> + +
+ + 我已阅读并同意 + 《用户协议》 + 和 + 《隐私政策》 + +
+
{ // 检查URL中是否存在token参数 @@ -110,6 +132,34 @@ onMounted(() => { title.value = 'Geek-AI' }) + // 获取用户协议 + httpGet('/api/config/get?key=agreement') + .then((res) => { + if (res.data && res.data.content) { + agreementContent.value = res.data.content + } else { + agreementContent.value = '# 用户协议\n\n用户在使用本服务前应当阅读并同意本协议。本协议内容包括协议正文及所有本平台已经发布的或将来可能发布的各类规则。所有规则为本协议不可分割的组成部分,与协议正文具有同等法律效力。' + } + }) + .catch((e) => { + console.warn(e) + agreementContent.value = '# 用户协议\n\n用户在使用本服务前应当阅读并同意本协议。本协议内容包括协议正文及所有本平台已经发布的或将来可能发布的各类规则。所有规则为本协议不可分割的组成部分,与协议正文具有同等法律效力。' + }) + + // 获取隐私政策 + httpGet('/api/config/get?key=privacy') + .then((res) => { + if (res.data && res.data.content) { + privacyContent.value = res.data.content + } else { + privacyContent.value = '# 隐私政策\n\n我们非常重视用户的隐私和个人信息保护。您在使用我们的产品与服务时,我们可能会收集和使用您的相关信息。我们希望通过本《隐私政策》向您说明我们在收集和使用您相关信息时对应的处理规则。' + } + }) + .catch((e) => { + console.warn(e) + privacyContent.value = '# 隐私政策\n\n我们非常重视用户的隐私和个人信息保护。您在使用我们的产品与服务时,我们可能会收集和使用您的相关信息。我们希望通过本《隐私政策》向您说明我们在收集和使用您相关信息时对应的处理规则。' + }) + getLicenseInfo() .then((res) => { licenseConfig.value = res.data @@ -141,6 +191,16 @@ const handleKeyup = (e) => { } const login = async function () { + if (!ruleForm.agreement) { + agreementError.value = true + isShaking.value = true + setTimeout(() => { + isShaking.value = false + }, 500) + showMessageError('请先阅读并同意用户协议') + return + } + await ruleFormRef.value.validate(async (valid) => { if (valid) { if (enableVerify.value) { @@ -170,8 +230,126 @@ const doLogin = (verifyData) => { showMessageError('登录失败,' + e.message) }) } + +const agreementError = ref(false) +const isShaking = ref(false) + +const handleAgreementChange = () => { + agreementError.value = !ruleForm.agreement + if (agreementError.value) { + isShaking.value = true + setTimeout(() => { + isShaking.value = false + }, 500) + } +} + +const openAgreement = () => { + // 使用弹窗显示用户协议内容,支持Markdown格式 + ElMessageBox.alert( + `
${md.render(agreementContent.value)}
`, + '用户协议', + { + confirmButtonText: '我已阅读', + dangerouslyUseHTMLString: true, + callback: () => {} + } + ) +} + +const openPrivacy = () => { + // 使用弹窗显示隐私政策内容,支持Markdown格式 + ElMessageBox.alert( + `
${md.render(privacyContent.value)}
`, + '隐私政策', + { + confirmButtonText: '我已阅读', + dangerouslyUseHTMLString: true, + callback: () => {} + } + ) +} + + diff --git a/web/src/views/Register.vue b/web/src/views/Register.vue index 864a8fdb..b6663206 100644 --- a/web/src/views/Register.vue +++ b/web/src/views/Register.vue @@ -62,6 +62,17 @@
+ +
+ + 我已阅读并同意 + 《用户协议》 + 和 + 《隐私政策》 + +
+
+ @@ -97,8 +108,9 @@ import AccountTop from "@/components/AccountTop.vue"; import AccountBg from "@/components/AccountBg.vue"; import { httpGet, httpPost } from "@/utils/http"; -import { ElMessage } from "element-plus"; +import { ElMessage, ElMessageBox } from "element-plus"; import { useRouter } from "vue-router"; +import MarkdownIt from "markdown-it"; import SendMsg from "@/components/SendMsg.vue"; import { arrayContains, isMobile } from "@/utils/libs"; @@ -119,6 +131,7 @@ const data = ref({ code: "", repass: "", invite_code: router.currentRoute.value.query["invite_code"], + agreement: false, }); const enableMobile = ref(false); @@ -130,6 +143,18 @@ const wxImg = ref("/images/wx.png"); const licenseConfig = ref({}); const enableVerify = ref(false); const captchaRef = ref(null); +const agreementError = ref(false); +const isShaking = ref(false); + +// 初始化markdown解析器 +const md = new MarkdownIt({ + html: true, + linkify: true, + typographer: true +}); + +const agreementContent = ref(''); +const privacyContent = ref(''); // 记录邀请码点击次数 if (data.value.invite_code) { @@ -168,6 +193,34 @@ getSystemInfo() ElMessage.error("获取系统配置失败:" + e.message); }); +// 获取用户协议 +httpGet('/api/config/get?key=agreement') + .then((res) => { + if (res.data && res.data.content) { + agreementContent.value = res.data.content; + } else { + agreementContent.value = '# 用户协议\n\n用户在使用本服务前应当阅读并同意本协议。本协议内容包括协议正文及所有本平台已经发布的或将来可能发布的各类规则。所有规则为本协议不可分割的组成部分,与协议正文具有同等法律效力。'; + } + }) + .catch((e) => { + console.warn(e); + agreementContent.value = '# 用户协议\n\n用户在使用本服务前应当阅读并同意本协议。本协议内容包括协议正文及所有本平台已经发布的或将来可能发布的各类规则。所有规则为本协议不可分割的组成部分,与协议正文具有同等法律效力。'; + }); + +// 获取隐私政策 +httpGet('/api/config/get?key=privacy') + .then((res) => { + if (res.data && res.data.content) { + privacyContent.value = res.data.content; + } else { + privacyContent.value = '# 隐私政策\n\n我们非常重视用户的隐私和个人信息保护。您在使用我们的产品与服务时,我们可能会收集和使用您的相关信息。我们希望通过本《隐私政策》向您说明我们在收集和使用您相关信息时对应的处理规则。'; + } + }) + .catch((e) => { + console.warn(e); + privacyContent.value = '# 隐私政策\n\n我们非常重视用户的隐私和个人信息保护。您在使用我们的产品与服务时,我们可能会收集和使用您的相关信息。我们希望通过本《隐私政策》向您说明我们在收集和使用您相关信息时对应的处理规则。'; + }); + getLicenseInfo() .then((res) => { licenseConfig.value = res.data; @@ -201,6 +254,16 @@ const submitRegister = () => { return showMessageError("请输入验证码"); } + if (!data.value.agreement) { + agreementError.value = true; + isShaking.value = true; + setTimeout(() => { + isShaking.value = false; + }, 500); + showMessageError("请先阅读并同意用户协议和隐私政策"); + return; + } + // 如果是用户名和密码登录,那么需要加载验证码 if (enableVerify.value && activeName.value === "username") { captchaRef.value.loadCaptcha(); @@ -228,6 +291,36 @@ const doSubmitRegister = (verifyData) => { showMessageError("注册失败," + e.message); }); }; + +const handleAgreementChange = () => { + agreementError.value = !data.value.agreement; +}; + +const openAgreement = () => { + // 使用弹窗显示用户协议内容,支持Markdown格式 + ElMessageBox.alert( + `
${md.render(agreementContent.value)}
`, + '用户协议', + { + confirmButtonText: '我已阅读', + dangerouslyUseHTMLString: true, + callback: () => {} + } + ); +}; + +const openPrivacy = () => { + // 使用弹窗显示隐私政策内容,支持Markdown格式 + ElMessageBox.alert( + `
${md.render(privacyContent.value)}
`, + '隐私政策', + { + confirmButtonText: '我已阅读', + dangerouslyUseHTMLString: true, + callback: () => {} + } + ); +}; + + diff --git a/web/src/views/admin/SysConfig.vue b/web/src/views/admin/SysConfig.vue index f3820f46..fddd2b99 100644 --- a/web/src/views/admin/SysConfig.vue +++ b/web/src/views/admin/SysConfig.vue @@ -311,6 +311,25 @@ + + + + +
+ 保存 +
+
+
+ + + + +
+ 保存 +
+
+
+ @@ -418,6 +437,8 @@ const loading = ref(true); const systemFormRef = ref(null); const models = ref([]); const notice = ref(""); +const agreement = ref(""); +const privacy = ref(""); const license = ref({ is_active: false }); const menus = ref([]); const mjModels = ref([ @@ -460,6 +481,24 @@ onMounted(() => { ElMessage.error("公告信息失败: " + e.message); }); + // 加载用户协议 + httpGet("/api/admin/config/get?key=agreement") + .then((res) => { + agreement.value = res.data["content"] || ''; + }) + .catch((e) => { + ElMessage.error("加载用户协议失败: " + e.message); + }); + + // 加载隐私政策 + httpGet("/api/admin/config/get?key=privacy") + .then((res) => { + privacy.value = res.data["content"] || ''; + }) + .catch((e) => { + ElMessage.error("加载隐私政策失败: " + e.message); + }); + httpGet("/api/admin/model/list") .then((res) => { models.value = res.data; @@ -517,6 +556,22 @@ const save = function (key) { .catch((e) => { ElMessage.error("操作失败:" + e.message); }); + } else if (key === "agreement") { + httpPost("/api/admin/config/update", { key: key, config: { content: agreement.value, updated: true } }) + .then(() => { + ElMessage.success("操作成功!"); + }) + .catch((e) => { + ElMessage.error("操作失败:" + e.message); + }); + } else if (key === "privacy") { + httpPost("/api/admin/config/update", { key: key, config: { content: privacy.value, updated: true } }) + .then(() => { + ElMessage.success("操作成功!"); + }) + .catch((e) => { + ElMessage.error("操作失败:" + e.message); + }); } }; From 615515094b3b6040348c48f27b49294101f3aadf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B8=85=E6=9F=AF?= <10691891+qing_ke@user.noreply.gitee.com> Date: Thu, 8 May 2025 03:20:51 +0800 Subject: [PATCH 08/32] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5ae036d..dc08d12c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## v4.2.4 - 功能新增:管理后台支持设置默认昵称 - 功能优化:支持 Suno v4.5 模型支持 +- 功能新增:用户注册和用户登录增加用户协议和隐私政策功能,需要用户同意协议才可注册和登录。 ## v4.2.3 From 19099aed6f6ad841261e6262b0a7e1420f149fd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B8=85=E6=9F=AF?= <10691891+qing_ke@user.noreply.gitee.com> Date: Thu, 8 May 2025 09:28:53 +0800 Subject: [PATCH 09/32] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=87=8D=E6=96=B0?= =?UTF-8?q?=E5=9B=9E=E7=AD=94=E5=8A=9F=E8=83=BD=EF=BC=8C=E6=92=A4=E5=9B=9E?= =?UTF-8?q?=E5=8D=83=E9=9D=A2=E7=9A=84=E9=97=AE=E7=AD=94=E5=86=85=E5=AE=B9?= =?UTF-8?q?=E4=B8=BA=E5=8F=AF=E7=BC=96=E8=BE=91=E5=86=85=E5=AE=B9=EF=BC=8C?= =?UTF-8?q?=E6=92=A4=E5=9B=9E=E7=9A=84=E5=86=85=E5=AE=B9=E4=B8=8D=E4=BC=9A?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=A2=9D=E5=A4=96=E7=9A=84=E4=B8=8A=E4=B8=8B?= =?UTF-8?q?=E6=96=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/components/ChatPrompt.vue | 48 ++++++++++- web/src/components/ChatReply.vue | 9 +-- web/src/views/ChatPlus.vue | 128 ++++++++++++++++++++++++------ 3 files changed, 152 insertions(+), 33 deletions(-) diff --git a/web/src/components/ChatPrompt.vue b/web/src/components/ChatPrompt.vue index a974dd84..a7aec543 100644 --- a/web/src/components/ChatPrompt.vue +++ b/web/src/components/ChatPrompt.vue @@ -29,7 +29,9 @@ -
+
+
+
{{ dateFormat(data.created_at) }}
-
+
+
+
{ processFiles() }) @@ -475,4 +482,39 @@ const isExternalImg = (link, files) => { } } + +.operations + display none + position absolute + right 5px + top 5px + +.text-box + &:hover + .operations + display flex + gap 5px + +.op-edit + cursor pointer + color #409eff + font-size 16px + + &:hover + color darken(#409eff, 10%) + +.position-relative { + position: relative; +} + +.action-buttons { + position: absolute; + top: 10px; + right: 10px; + display: none; +} + +.content:hover .action-buttons { + display: block; +} diff --git a/web/src/components/ChatReply.vue b/web/src/components/ChatReply.vue index efe2ee5f..4ed50237 100644 --- a/web/src/components/ChatReply.vue +++ b/web/src/components/ChatReply.vue @@ -26,7 +26,7 @@ - + @@ -92,7 +92,7 @@ - + @@ -233,9 +233,8 @@ const stopSynthesis = () => { } // 重新生成 -const reGenerate = (prompt) => { - console.log(prompt) - emits('regen', prompt) +const reGenerate = () => { + emits('regen') } diff --git a/web/src/views/ChatPlus.vue b/web/src/views/ChatPlus.vue index aaa907e6..39910071 100644 --- a/web/src/views/ChatPlus.vue +++ b/web/src/views/ChatPlus.vue @@ -264,7 +264,7 @@
- + = 2) { + // 从后往前找,如果最后一条是AI回复,再往前一条是用户消息 + if (chatData.value[chatData.value.length - 1].type === 'reply') { + // 删除AI回复 + chatData.value.pop(); + + // 如果此时最后一条是用户消息,也删除它 + if (chatData.value.length > 0 && chatData.value[chatData.value.length - 1].type === 'prompt') { + // 保存用户消息内容,填入输入框 + const userPrompt = chatData.value[chatData.value.length - 1].content; + // 删除用户消息 + chatData.value.pop(); + // 填入输入框 + prompt.value = userPrompt; + } + } + } + + // 将光标定位到输入框并聚焦 + nextTick(() => { + if (inputRef.value) { + inputRef.value.focus(); + // 触发输入事件以更新文本高度 + onInput({ keyCode: null }); + } + }); +} + +// 编辑用户消息 +const editUserPrompt = function (messageId) { + // 找到要编辑的消息及其索引 + let messageIndex = -1; + let messageContent = ''; + + for (let i = 0; i < chatData.value.length; i++) { + if (chatData.value[i].id === messageId) { + messageIndex = i; + messageContent = chatData.value[i].content; + break; + } + } + + if (messageIndex === -1) return; + + // 弹出编辑对话框 + ElMessageBox.prompt('', '编辑消息', { + confirmButtonText: '确定', + cancelButtonText: '取消', + inputValue: messageContent, + inputType: 'textarea', + customClass: 'edit-prompt-dialog', + roundButton: true + }).then(({ value }) => { + if (value.trim() === '') { + ElMessage.warning('消息内容不能为空'); + return; + } + + // 更新用户消息 + chatData.value[messageIndex].content = value; + + // 移除该消息之后的所有消息 + chatData.value = chatData.value.slice(0, messageIndex + 1); + + // 添加空回复消息 + const _role = getRoleById(roleId.value); + chatData.value.push({ + chat_id: chatId, + role_id: roleId.value, + type: 'reply', + id: randString(32), + icon: _role['icon'], + content: '', + }); + + disableInput(false); + + // 发送编辑后的消息 + store.socket.conn.send( + JSON.stringify({ + channel: 'chat', + type: 'text', + body: { + role_id: roleId.value, + model_id: modelID.value, + chat_id: chatId.value, + content: value, + tools: toolSelected.value, + stream: stream.value, + edit_message: true + }, + }) + ); + }).catch(() => { + // 取消编辑 + }); } const chatName = ref('') From 347b640614ec4476125f25d95ec993594beb6cd4 Mon Sep 17 00:00:00 2001 From: GeekMaster Date: Thu, 8 May 2025 09:29:31 +0800 Subject: [PATCH 10/32] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=8A=A5=E6=95=B0=E6=8D=AE=E5=BA=93=E5=AD=97?= =?UTF-8?q?=E6=AE=B5=E4=B8=8D=E5=AD=98=E5=9C=A8=E7=9A=84=20bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/handler/admin/chat_model_handler.go | 12 ++++++---- api/store/model/chat_model.go | 12 +++++----- api/store/mysql.go | 2 +- api/store/vo/chat_model.go | 4 ++-- web/src/views/admin/ChatModel.vue | 29 ++++++++++++------------- 5 files changed, 31 insertions(+), 28 deletions(-) diff --git a/api/handler/admin/chat_model_handler.go b/api/handler/admin/chat_model_handler.go index 5ad9f182..471e8f08 100644 --- a/api/handler/admin/chat_model_handler.go +++ b/api/handler/admin/chat_model_handler.go @@ -40,8 +40,8 @@ func (h *ChatModelHandler) Save(c *gin.Context) { Power int `json:"power"` MaxTokens int `json:"max_tokens"` // 最大响应长度 MaxContext int `json:"max_context"` // 最大上下文长度 - Description string `json:"description"` //模型描述 - Category string `json:"category"` //模型类别 + Desc string `json:"desc"` //模型描述 + Tag string `json:"tag"` //模型标签 Temperature float32 `json:"temperature"` // 模型温度 KeyId int `json:"key_id,omitempty"` CreatedAt int64 `json:"created_at"` @@ -66,8 +66,8 @@ func (h *ChatModelHandler) Save(c *gin.Context) { item.Power = data.Power item.MaxTokens = data.MaxTokens item.MaxContext = data.MaxContext - item.Description = data.Description - item.Category = data.Category + item.Desc = data.Desc + item.Tag = data.Tag item.Temperature = data.Temperature item.KeyId = uint(data.KeyId) item.Type = data.Type @@ -100,12 +100,16 @@ func (h *ChatModelHandler) List(c *gin.Context) { session := h.DB.Session(&gorm.Session{}) enable := h.GetBool(c, "enable") name := h.GetTrim(c, "name") + modelType := h.GetTrim(c, "type") if enable { session = session.Where("enabled", enable) } if name != "" { session = session.Where("name LIKE ?", name+"%") } + if modelType != "" { + session = session.Where("type", modelType) + } var items []model.ChatModel var cms = make([]vo.ChatModel, 0) res := session.Order("sort_num ASC").Find(&items) diff --git a/api/store/model/chat_model.go b/api/store/model/chat_model.go index b753c347..cf42e154 100644 --- a/api/store/model/chat_model.go +++ b/api/store/model/chat_model.go @@ -5,20 +5,20 @@ import ( ) type ChatModel struct { - Id uint `gorm:"column:id;primaryKey;autoIncrement" json:"id"` - Description string `gorm:"column:description;type:varchar(1024);not null;default:'';comment:模型类型描述" json:"description"` - Category string `gorm:"column:category;type:varchar(1024);not null;default:'';comment:模型类别" json:"category"` + Id uint `gorm:"column:id;primaryKey;autoIncrement" json:"id"` + Desc string `gorm:"column:desc;type:varchar(1024);not null;default:'';comment:模型类型描述" json:"desc"` + Tag string `gorm:"column:tag;type:varchar(1024);not null;default:'';comment:模型标签" json:"tag"` Type string `gorm:"column:type;type:varchar(10);not null;default:chat;comment:模型类型(chat,img)" json:"type"` Name string `gorm:"column:name;type:varchar(255);not null;comment:模型名称" json:"name"` Value string `gorm:"column:value;type:varchar(255);not null;comment:模型值" json:"value"` SortNum int `gorm:"column:sort_num;type:tinyint(1);not null;comment:排序数字" json:"sort_num"` - Enabled bool `gorm:"column:enabled;type:tinyint(1);not null;default:0;comment:是否启用模型" json:"enabled"` + Enabled bool `gorm:"column:enabled;type:tinyint(1);not null;default:0;comment:是否启用模型" json:"enabled"` Power int `gorm:"column:power;type:smallint;not null;comment:消耗算力点数" json:"power"` Temperature float32 `gorm:"column:temperature;type:float(3,1);not null;default:1.0;comment:模型创意度" json:"temperature"` MaxTokens int `gorm:"column:max_tokens;type:int;not null;default:1024;comment:最大响应长度" json:"max_tokens"` MaxContext int `gorm:"column:max_context;type:int;not null;default:4096;comment:最大上下文长度" json:"max_context"` - Open bool `gorm:"column:open;type:tinyint(1);not null;comment:是否开放模型" json:"open"` - KeyId uint `gorm:"column:key_id;type:int;not null;comment:绑定API KEY ID" json:"key_id"` + Open bool `gorm:"column:open;type:tinyint(1);not null;comment:是否开放模型" json:"open"` + KeyId uint `gorm:"column:key_id;type:int;not null;comment:绑定API KEY ID" json:"key_id"` Options string `gorm:"column:options;type:text;not null;comment:模型自定义选项" json:"options"` CreatedAt time.Time `gorm:"column:created_at;type:datetime" json:"created_at"` UpdatedAt time.Time `gorm:"column:updated_at;type:datetime" json:"updated_at"` diff --git a/api/store/mysql.go b/api/store/mysql.go index 8a582740..8233c695 100644 --- a/api/store/mysql.go +++ b/api/store/mysql.go @@ -19,7 +19,7 @@ import ( func NewGormConfig() *gorm.Config { return &gorm.Config{ - Logger: logger.Default.LogMode(logger.Error), + Logger: logger.Default.LogMode(logger.Warn), NamingStrategy: schema.NamingStrategy{ TablePrefix: "chatgpt_", // 设置表前缀 SingularTable: false, // 使用单数表名形式 diff --git a/api/store/vo/chat_model.go b/api/store/vo/chat_model.go index 427fa4e8..b4076e19 100644 --- a/api/store/vo/chat_model.go +++ b/api/store/vo/chat_model.go @@ -10,8 +10,8 @@ type ChatModel struct { Open bool `json:"open"` MaxTokens int `json:"max_tokens"` // 最大响应长度 MaxContext int `json:"max_context"` // 最大上下文长度 - Description string `json:"description"` // 模型描述 - Category string `json:"category"` //模型类别 + Desc string `json:"desc"` // 模型描述 + Tag string `json:"tag"` //模型标签 Temperature float32 `json:"temperature"` // 模型温度 KeyId uint `json:"key_id,omitempty"` KeyName string `json:"key_name"` diff --git a/web/src/views/admin/ChatModel.vue b/web/src/views/admin/ChatModel.vue index b7387336..8cd433da 100644 --- a/web/src/views/admin/ChatModel.vue +++ b/web/src/views/admin/ChatModel.vue @@ -3,6 +3,12 @@
+ + + {{ v.label }} + + + 搜索 新增
@@ -25,7 +31,7 @@ - + - - - @@ -79,7 +78,7 @@ - + {{ v.label }} @@ -92,8 +91,8 @@ - - + + @@ -137,8 +136,8 @@ /> - - + + @@ -237,7 +236,7 @@ const rules = reactive({ }) const loading = ref(true) const formRef = ref(null) -const type = ref([ +const modelTypes = ref([ { label: '聊天', value: 'chat' }, { label: '绘图', value: 'img' }, { label: '语音', value: 'tts' }, @@ -344,7 +343,7 @@ const add = function () { const edit = function (row) { title.value = '修改模型' showDialog.value = true - item.value = row + item.value = Object.assign({}, row) } const save = function () { From 303e9ed052449811ea6fb35cb2ba1392b9f7c98c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B8=85=E6=9F=AF?= <10691891+qing_ke@user.noreply.gitee.com> Date: Thu, 8 May 2025 09:29:50 +0800 Subject: [PATCH 11/32] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc08d12c..ff0f4eff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ - 功能新增:管理后台支持设置默认昵称 - 功能优化:支持 Suno v4.5 模型支持 - 功能新增:用户注册和用户登录增加用户协议和隐私政策功能,需要用户同意协议才可注册和登录。 +- 功能优化:修改重新回答功能,撤回千面的问答内容为可编辑内容,撤回的内容不会增加额外的上下文 ## v4.2.3 From ca2de54438046374f4517aef3d4d45883e0233ba Mon Sep 17 00:00:00 2001 From: GeekMaster Date: Thu, 8 May 2025 09:37:11 +0800 Subject: [PATCH 12/32] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=97=A0=E7=94=A8?= =?UTF-8?q?=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/core/app_server.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/api/core/app_server.go b/api/core/app_server.go index 3b97a3b4..2e81c245 100644 --- a/api/core/app_server.go +++ b/api/core/app_server.go @@ -112,6 +112,12 @@ func (s *AppServer) Run(db *gorm.DB) error { if db.Migrator().HasColumn(&model.User{}, "chat_config") { db.Migrator().DropColumn(&model.User{}, "chat_config") } + if db.Migrator().HasColumn(&model.ChatModel{}, "category") { + db.Migrator().DropColumn(&model.ChatModel{}, "category") + } + if db.Migrator().HasColumn(&model.ChatModel{}, "description") { + db.Migrator().DropColumn(&model.ChatModel{}, "description") + } logger.Info("Database tables migrated successfully") From 7c81d946a7735fb78df9666ea69adaf9008d76a3 Mon Sep 17 00:00:00 2001 From: GeekMaster Date: Thu, 8 May 2025 09:46:35 +0800 Subject: [PATCH 13/32] =?UTF-8?q?=E9=A6=96=E9=A1=B5=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E9=80=80=E5=87=BA=E7=99=BB=E5=BD=95=E6=8C=89=E9=92=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/views/Index.vue | 18 ++++++++++++++++++ web/src/views/Login.vue | 37 ++++++++++++++++++++++--------------- 2 files changed, 40 insertions(+), 15 deletions(-) diff --git a/web/src/views/Index.vue b/web/src/views/Index.vue index ef2a0340..cc0bb7c7 100644 --- a/web/src/views/Index.vue +++ b/web/src/views/Index.vue @@ -34,6 +34,15 @@ >登录/注册 + + + 退出登录 + +
@@ -195,6 +204,15 @@ const rainbowColor = (index) => { const hue = (index * 40) % 360 // 每个字符间隔40度,形成彩虹色 return `hsl(${hue}, 90%, 50%)` // 色调(hue),饱和度(70%),亮度(50%) } + +httpGet('/api/user/logout') + .then(() => { + removeUserToken() + router.push('/login') + }) + .catch(() => { + ElMessage.error('注销失败!') + }) diff --git a/web/src/components/FooterBar.vue b/web/src/components/FooterBar.vue index bb45c600..b6582291 100644 --- a/web/src/components/FooterBar.vue +++ b/web/src/components/FooterBar.vue @@ -17,45 +17,45 @@ diff --git a/web/src/components/TaskList.vue b/web/src/components/TaskList.vue index 0a4f79e1..e3383658 100644 --- a/web/src/components/TaskList.vue +++ b/web/src/components/TaskList.vue @@ -22,7 +22,7 @@ - diff --git a/web/src/views/ChatApps.vue b/web/src/views/ChatApps.vue index 272d3890..573bed5e 100644 --- a/web/src/views/ChatApps.vue +++ b/web/src/views/ChatApps.vue @@ -4,7 +4,12 @@
  • 全部分类
  • -
  • +
  • @@ -27,37 +32,25 @@
    {{ scope.item.hello_msg }}
- 使用 + 使用 - 移除 + 移除 - 添加 + 添加
- - - - - - - - - - - - - - - - - - - - -
@@ -69,112 +62,112 @@ - diff --git a/web/src/views/ChatPlus.vue b/web/src/views/ChatPlus.vue index 39910071..e04b1a51 100644 --- a/web/src/views/ChatPlus.vue +++ b/web/src/views/ChatPlus.vue @@ -264,7 +264,12 @@
- + = 2) { // 从后往前找,如果最后一条是AI回复,再往前一条是用户消息 if (chatData.value[chatData.value.length - 1].type === 'reply') { // 删除AI回复 - chatData.value.pop(); - + chatData.value.pop() + // 如果此时最后一条是用户消息,也删除它 - if (chatData.value.length > 0 && chatData.value[chatData.value.length - 1].type === 'prompt') { + if ( + chatData.value.length > 0 && + chatData.value[chatData.value.length - 1].type === 'prompt' + ) { // 保存用户消息内容,填入输入框 - const userPrompt = chatData.value[chatData.value.length - 1].content; + const userPrompt = chatData.value[chatData.value.length - 1].content // 删除用户消息 - chatData.value.pop(); + chatData.value.pop() // 填入输入框 - prompt.value = userPrompt; + prompt.value = userPrompt } } } - + // 将光标定位到输入框并聚焦 nextTick(() => { if (inputRef.value) { - inputRef.value.focus(); + inputRef.value.focus() // 触发输入事件以更新文本高度 - onInput({ keyCode: null }); + onInput({ keyCode: null }) } - }); + }) } // 编辑用户消息 const editUserPrompt = function (messageId) { // 找到要编辑的消息及其索引 - let messageIndex = -1; - let messageContent = ''; - + let messageIndex = -1 + let messageContent = '' + for (let i = 0; i < chatData.value.length; i++) { if (chatData.value[i].id === messageId) { - messageIndex = i; - messageContent = chatData.value[i].content; - break; + messageIndex = i + messageContent = chatData.value[i].content + break } } - - if (messageIndex === -1) return; - + + if (messageIndex === -1) return + // 弹出编辑对话框 ElMessageBox.prompt('', '编辑消息', { confirmButtonText: '确定', @@ -1246,51 +1254,53 @@ const editUserPrompt = function (messageId) { inputValue: messageContent, inputType: 'textarea', customClass: 'edit-prompt-dialog', - roundButton: true - }).then(({ value }) => { - if (value.trim() === '') { - ElMessage.warning('消息内容不能为空'); - return; - } - - // 更新用户消息 - chatData.value[messageIndex].content = value; - - // 移除该消息之后的所有消息 - chatData.value = chatData.value.slice(0, messageIndex + 1); - - // 添加空回复消息 - const _role = getRoleById(roleId.value); - chatData.value.push({ - chat_id: chatId, - role_id: roleId.value, - type: 'reply', - id: randString(32), - icon: _role['icon'], - content: '', - }); - - disableInput(false); - - // 发送编辑后的消息 - store.socket.conn.send( - JSON.stringify({ - channel: 'chat', - type: 'text', - body: { - role_id: roleId.value, - model_id: modelID.value, - chat_id: chatId.value, - content: value, - tools: toolSelected.value, - stream: stream.value, - edit_message: true - }, + roundButton: true, + }) + .then(({ value }) => { + if (value.trim() === '') { + ElMessage.warning('消息内容不能为空') + return + } + + // 更新用户消息 + chatData.value[messageIndex].content = value + + // 移除该消息之后的所有消息 + chatData.value = chatData.value.slice(0, messageIndex + 1) + + // 添加空回复消息 + const _role = getRoleById(roleId.value) + chatData.value.push({ + chat_id: chatId, + role_id: roleId.value, + type: 'reply', + id: randString(32), + icon: _role['icon'], + content: '', }) - ); - }).catch(() => { - // 取消编辑 - }); + + disableInput(false) + + // 发送编辑后的消息 + store.socket.conn.send( + JSON.stringify({ + channel: 'chat', + type: 'text', + body: { + role_id: roleId.value, + model_id: modelID.value, + chat_id: chatId.value, + content: value, + tools: toolSelected.value, + stream: stream.value, + edit_message: true, + }, + }) + ) + }) + .catch(() => { + // 取消编辑 + }) } const chatName = ref('') @@ -1374,11 +1384,11 @@ const realtimeChat = () => { diff --git a/web/src/views/Home.vue b/web/src/views/Home.vue index 277fd69b..35e575b2 100644 --- a/web/src/views/Home.vue +++ b/web/src/views/Home.vue @@ -297,6 +297,6 @@ const loginSuccess = () => { diff --git a/web/src/views/ImageMj.vue b/web/src/views/ImageMj.vue index 454e5832..905b983f 100644 --- a/web/src/views/ImageMj.vue +++ b/web/src/views/ImageMj.vue @@ -204,7 +204,10 @@
- 如需自定义比例,在绘画指令最后加一个空格然后加上指令(宽高比) --ar w:h 例如: 1 cat --ar 21:9 + 如需自定义比例,在绘画指令最后加一个空格然后加上指令(宽高比) --ar w:h + 例如: 1 cat --ar 21:9 +
@@ -330,8 +333,11 @@
- 如需自定义比例,在绘画指令最后加一个空格然后加上指令(宽高比) --ar w:h 例如: 1 cat --ar 21:9 -
+ 如需自定义比例,在绘画指令最后加一个空格然后加上指令(宽高比) --ar w:h + 例如: 1 cat --ar 21:9 + + @@ -545,7 +551,10 @@
- 如需自定义比例,在绘画指令最后加一个空格然后加上指令(宽高比) --ar w:h 例如: 1 cat --ar 21:9 + 如需自定义比例,在绘画指令最后加一个空格然后加上指令(宽高比) --ar w:h + 例如: 1 cat --ar 21:9 +
@@ -1213,8 +1222,8 @@ const generate = () => { return ElMessage.error('换脸操作需要上传两张图片') } - const regex = /(^|\s)--ar\s+(\d+:\d+)/; - const match = regex.exec(params.value.prompt); + const regex = /(^|\s)--ar\s+(\d+:\d+)/ + const match = regex.exec(params.value.prompt) if (match) { params.value.rate = match[2] } @@ -1349,6 +1358,6 @@ const generatePrompt = () => { diff --git a/web/src/views/ImageSd.vue b/web/src/views/ImageSd.vue index 38b4b9a2..1be8b9bf 100644 --- a/web/src/views/ImageSd.vue +++ b/web/src/views/ImageSd.vue @@ -471,21 +471,21 @@ diff --git a/web/src/views/ImagesWall.vue b/web/src/views/ImagesWall.vue index a639d199..0cbe70b1 100644 --- a/web/src/views/ImagesWall.vue +++ b/web/src/views/ImagesWall.vue @@ -11,11 +11,7 @@ -
+
- + - - + + @@ -129,11 +113,7 @@ class="pt-3 flex justify-center items-center border-t border-t-gray-600 border-opacity-50" >
- + - - + + @@ -203,11 +175,7 @@ class="pt-3 flex justify-center items-center border-t border-t-gray-600 border-opacity-50" >
- + diff --git a/web/src/views/Index.vue b/web/src/views/Index.vue index 6eecab88..249b5f77 100644 --- a/web/src/views/Index.vue +++ b/web/src/views/Index.vue @@ -84,6 +84,7 @@ import FooterBar from '@/components/FooterBar.vue' import ThemeChange from '@/components/ThemeChange.vue' import { checkSession, getLicenseInfo, getSystemInfo } from '@/store/cache' +import { removeUserToken } from '@/store/session' import { httpGet } from '@/utils/http' import { isMobile } from '@/utils/libs' import { ElMessage } from 'element-plus' @@ -211,12 +212,12 @@ const logout = function () { removeUserToken() router.push('/login') }) - .catch(() => { - ElMessage.error('注销失败!') + .catch((e) => { + ElMessage.error('注销失败:' + e.message) }) } diff --git a/web/src/views/Invitation.vue b/web/src/views/Invitation.vue index 18c20e61..b5d6f2f2 100644 --- a/web/src/views/Invitation.vue +++ b/web/src/views/Invitation.vue @@ -7,7 +7,8 @@
我们非常欢迎您把此应用分享给您身边的朋友,分享成功注册后您和被邀请人都将获得 {{ invitePower }} - 算力额度作为奖励。 你可以保存下面的二维码或者直接复制分享您的专属推广链接发送给微信好友。 + 算力额度作为奖励。 + 你可以保存下面的二维码或者直接复制分享您的专属推广链接发送给微信好友。
@@ -16,7 +17,9 @@
{{ inviteURL }} - 复制链接 + 复制链接
@@ -88,79 +91,79 @@ diff --git a/web/src/views/Login.vue b/web/src/views/Login.vue index 78041cab..3ccdfda6 100644 --- a/web/src/views/Login.vue +++ b/web/src/views/Login.vue @@ -145,7 +145,6 @@ onMounted(() => { } }) .catch((e) => { - console.warn(e) agreementContent.value = '用户在使用本服务前应当阅读并同意本协议。本协议内容包括协议正文及所有本平台已经发布的或将来可能发布的各类规则。所有规则为本协议不可分割的组成部分,与协议正文具有同等法律效力。' }) @@ -161,7 +160,6 @@ onMounted(() => { } }) .catch((e) => { - console.warn(e) privacyContent.value = '我们非常重视用户的隐私和个人信息保护。您在使用我们的产品与服务时,我们可能会收集和使用您的相关信息。我们希望通过本《隐私政策》向您说明我们在收集和使用您相关信息时对应的处理规则。' }) @@ -278,7 +276,7 @@ const openPrivacy = () => { diff --git a/web/src/views/MarkMap.vue b/web/src/views/MarkMap.vue index c5f61c21..7b7d0768 100644 --- a/web/src/views/MarkMap.vue +++ b/web/src/views/MarkMap.vue @@ -109,15 +109,15 @@ diff --git a/web/src/views/Member.vue b/web/src/views/Member.vue index 40a95b0b..03fd30e1 100644 --- a/web/src/views/Member.vue +++ b/web/src/views/Member.vue @@ -292,7 +292,7 @@ const payCallback = (success) => { } - diff --git a/web/src/views/PowerLog.vue b/web/src/views/PowerLog.vue index 9e6b728e..14244414 100644 --- a/web/src/views/PowerLog.vue +++ b/web/src/views/PowerLog.vue @@ -4,7 +4,12 @@
- + @@ -64,48 +75,48 @@ diff --git a/web/src/views/Suno.vue b/web/src/views/Suno.vue index 29f319bb..f3317eb3 100644 --- a/web/src/views/Suno.vue +++ b/web/src/views/Suno.vue @@ -5,27 +5,48 @@ - - + + 上传音乐 - +
- + 纯音乐
歌词 - +