Compare commits

...

19 Commits

Author SHA1 Message Date
RockYang
dc6719cf54 release v3.2.4 2024-01-06 21:09:19 +08:00
RockYang
7de5b55091 chore: remove useless system config items 2024-01-06 17:38:55 +08:00
RockYang
76c5101092 chore: error recover is enable ONLY in debug mode 2024-01-06 17:16:02 +08:00
RockYang
2f8d2f4854 feat: payjs service is ready 2024-01-06 15:53:30 +08:00
RockYang
b1ee34ba0c chore: rename bind username api 2024-01-05 18:21:47 +08:00
RockYang
069ad6a09a feat: email registration function is ready 2024-01-05 18:17:11 +08:00
RockYang
bf1403c818 feat: update api key last_use_time after dalle3 call 2024-01-04 18:15:00 +08:00
RockYang
bcc622a24d feat: support dall-e3 api mirrors, add name field for ApiKey 2024-01-04 16:29:57 +08:00
RockYang
a06a81a415 feat: refactor LLM api request code, get API URL from ApiKey object 2024-01-04 14:51:33 +08:00
RockYang
d1950acd01 feat: api key manage page funciton is ready 2024-01-04 10:48:04 +08:00
RockYang
039b70eed2 fix: fixed bug for concurrency risk for getting token for chat histroy with issue #92 2024-01-04 09:03:19 +08:00
RockYang
d8e4308b1b fix: add unique key for MidJourney task_id 2024-01-03 18:06:10 +08:00
RockYang
434fbb3463 feat: show notice in chat page 2024-01-03 15:19:24 +08:00
RockYang
de3eb8969c feat: fixed bug for wechat bot to parse transactions. enable user to exchange reward with img_calls 2024-01-03 11:15:54 +08:00
RockYang
fbd6eac877 fix: fixed chat export page styles 2024-01-02 11:32:36 +08:00
RockYang
1fecab177b fix: fixed for img_call repeated reductions 2024-01-01 18:54:48 +08:00
RockYang
b1b385c455 feat: add switch for enable|disable chat role 2023-12-29 17:51:56 +08:00
RockYang
3c6e86d04b feat: add nickname field for user 2023-12-29 17:39:37 +08:00
RockYang
3d2035d08a feat: add authorization for local function call 2023-12-29 17:21:29 +08:00
76 changed files with 1930 additions and 647 deletions

View File

@@ -1,8 +1,23 @@
# 更新日志
## v3.2.4
* 功能新增:重磅更新,支持邮箱注册
* 功能优化:优化函数调用授权
* 功能优化:给用户表新增 nickname 字段
* 功能优化:管理后台给聊天角色增加启用/禁用开关
* Bug修复SD绘画出现重复扣减绘图次数
* 功能优化:优化聊天对话导出样式,适应移动端
* 功能新增:众筹核销可以选择兑换对话还是绘图的额度
* Bug修复修复[从历史记录获取reply有并发风险 #92](https://github.com/yangjian102621/chatgpt-plus/issues/92)
* Bug修复修复 MidJourney 绘图任务调度Bug为 task_id 建议唯一索引
* 功能重构:重构了 API KEY模块支持为每个 API KEY 都设置不同的 API 地址,并可以单独开启是否使用代理。
## v3.2.3
* 功能重构:重构函数工具模块,设计成可以后台动态管理函数。支持添加自定义函数实现
* 功能新增:为充值产品数据表添加 img_calls 字段,支持充值绘图次数
* Bug修复修复 [MJ 机器人空指针异常的 Bug](https://github.com/yangjian102621/chatgpt-plus/issues/73)
* Bug修复修复 [MJ 机器人空指针异常的 Bug](https://github.com/yangjian102621/chatgpt-plus/issues/73)
* Bug修复确保相同 Prompt 的绘图任务的 Upscale 和 Variation 任务调度给相同的频道
* 功能新增:新增删除绘图任何和图片功能
* Bug修复修复虎皮椒支付二维码重复扫码时报错问题
@@ -15,13 +30,15 @@
* 功能新增:为移动端新增 SD 绘图功能,分享功能
## v3.2.2
* 功能重构:重构 MidJourney 和 Stable-Diffusion 绘图模块,支持使用多组配置创建池子提供绘画服务
* 功能新增AI绘画页面增加翻译和重写提示词功能
* 功能优化OSS上传组件支持在 Bucket 下设置二级目录
* Bug修复修复阿里云 OSS 访问路径错误
* 功能优化:在 AI 绘图页面使用 HTTP 轮询替换 Websocket
* 功能优化:在 AI 绘图页面使用 HTTP 轮询替换 Websocket
## v3.2.1
* 功能优化:切换角色和模型的时候自动创建新的对话
* Bug修复修复文件上传失败No such file bug
* 功能新增MidJourney 绘画页面新增提示词翻译功能,新增多个绘画参数
@@ -31,6 +48,7 @@
* 功能新增:新增虎皮椒支付功能接入,支持微信和支付宝通道
## v3.2.0
* 功能新增:新增邀请注册功能
* 功能优化增加中间件自动对HTTP请求的参数去掉首尾空格
* 功能优化:增加中间件自动为大图片生成缩略图
@@ -40,6 +58,7 @@
* Bug修复修复MidJourney绘图失败后重复添加到队列的问题
## v3.1.9
* 功能新增:增加讯飞星火大模型 v3.0 支持
* 功能新增:新增找回密码功能
* 功能新增:支持 Markdown 代码复制功能

View File

@@ -13,11 +13,6 @@ ChatGLM,讯飞星火,文心一言等多个平台的大语言模型。集成了
* 集成插件 API 功能,可结合大语言模型的 function 功能开发各种强大的插件,已内置实现了微博热搜,今日头条,今日早报和 AI
绘画函数插件。
## 关于部署镜像申明
由于目前部署人数越来越多,本人的阿里云镜像仓库流量不够支撑大家使用了。所以从 v3.2.0 版本开始,一键部署脚本和部署镜像将只提供给 **[付费技术交流群]** 内用户使用。
代码依旧是全部开源的,大家可自行编译打包镜像。
## 功能截图
### PC 端聊天界面
@@ -68,17 +63,40 @@ ChatGLM,讯飞星火,文心一言等多个平台的大语言模型。集成了
![Mobile chat setting](/docs/imgs/mobile_user_profile.png)
![Mobile chat setting](/docs/imgs/mobile_pay.png)
### 7. 体验地址
### 体验地址
> 免费体验地址:[https://ai.r9it.com/chat](https://ai.r9it.com/chat) <br/>
> **注意:请合法使用,禁止输出任何敏感、不友好或违规的内容!!!**
## 快速部署
**演示站不提供任何充值点卡售卖或者VIP充值服务。** 如果您体验过后觉得还不错的话,可以花两分钟用下面的一键部署脚本自己部署一套。
```shell
bash -c "$(curl -fsSL https://img.r9it.com/tmp/install-v3.2.4-7b5ff48154.sh)"
```
目前仅支持 Ubuntu 和 Centos 系统。 部署成功之后可以访问下面地址
* 前端访问地址http://localhost:8080/chat 使用移动设备访问会自动跳转到移动端页面。
* 后台管理地址http://localhost:8080/admin
* 移动端地址http://localhost:8080/mobile
* 初始后台管理账号admin/admin123
* 初始前端体验账号18575670125/12345678
服务启动成功之后不能立刻使用,需要先登录管理后台 -> API-KEY 去添加一个 OpenAI 或者文心一言,科大讯飞等至少一个平台的 API
KEY。
![](https://ai.r9it.com/docs/images/env/admin_api_keys.png)
另外,如果您目前还没有 OpenAI 的 API KEY的推荐您去 https://gpt.bemore.lol 购买,**无需魔法,高速稳定,且价格还远低于 OpenAI
官方**。
## 使用须知
1. 本项目基于 MIT 协议,免费开放全部源代码,可以作为个人学习使用或者商用。
2. 如需商用必须保留版权信息,请自觉遵守。确保合法合规使用,在运营过程中产生的一切任何后果自负,与作者无关。
## 项目地址
* Github 地址https://github.com/yangjian102621/chatgpt-plus
@@ -89,13 +107,18 @@ ChatGLM,讯飞星火,文心一言等多个平台的大语言模型。集成了
目前已经支持 Win/Linux/Mac/Android 客户端下载地址为https://github.com/yangjian102621/chatgpt-plus/releases/tag/v3.1.2
## TODOLIST
* [ ] 支持基于知识库的 AI 问答
* [ ] 会员邀请注册推广功能
* [ ] 微信支付功能
## 项目文档
**最新的部署视频教程:[https://www.bilibili.com/video/BV1ge411C7uA/](https://www.bilibili.com/video/BV1ge411C7uA/?vd_source=dee8b15703ccfcbd24a60ee9a0fabb73)**
*
*
最新的部署视频教程:[https://www.bilibili.com/video/BV1ge411C7uA/](https://www.bilibili.com/video/BV1ge411C7uA/?vd_source=dee8b15703ccfcbd24a60ee9a0fabb73)
**
详细的部署和开发文档请参考 [**ChatGPT-Plus 文档**](https://ai.r9it.com/docs/)。

1
api/.gitignore vendored
View File

@@ -18,4 +18,3 @@ data
config.toml
static/upload
storage.json
certs/alipay/*

View File

@@ -155,7 +155,7 @@ func authorizeMiddleware(s *AppServer, client *redis.Client) gin.HandlerFunc {
c.Request.URL.Path == "/api/invite/hits" ||
c.Request.URL.Path == "/api/sd/jobs" ||
c.Request.URL.Path == "/api/upload" ||
strings.HasPrefix(c.Request.URL.Path, "/test/") ||
strings.HasPrefix(c.Request.URL.Path, "/api/test") ||
strings.HasPrefix(c.Request.URL.Path, "/api/function/") ||
strings.HasPrefix(c.Request.URL.Path, "/api/sms/") ||
strings.HasPrefix(c.Request.URL.Path, "/api/captcha/") ||

View File

@@ -62,7 +62,6 @@ type ApiError struct {
const PromptMsg = "prompt" // prompt message
const ReplyMsg = "reply" // reply message
const MjMsg = "mj"
var ModelToTokens = map[string]int{
"gpt-3.5-turbo": 4096,
@@ -75,4 +74,12 @@ var ModelToTokens = map[string]int{
"ernie_bot_turbo": 8192, // 文心一言
"general": 8192, // 科大讯飞
"general2": 8192,
"general3": 8192,
}
func GetModelMaxToken(model string) int {
if token, ok := ModelToTokens[model]; ok {
return token
}
return 4096
}

View File

@@ -24,6 +24,25 @@ type AppConfig struct {
XXLConfig XXLConfig
AlipayConfig AlipayConfig
HuPiPayConfig HuPiPayConfig
SmtpConfig SmtpConfig // 邮件发送配置
JPayConfig JPayConfig // payjs 支付配置
}
type SmtpConfig struct {
Host string
Port int
AppName string // 应用名称
From string // 发件人邮箱地址
Password string // 发件人邮箱密码
}
// JPayConfig PayJs 支付配置
type JPayConfig struct {
Enabled bool
AppId string // 商户 ID
PrivateKey string // 私钥
ApiURL string // API 网关
NotifyURL string // 异步回调地址
}
type ChatPlusApiConfig struct {
@@ -115,11 +134,10 @@ type ChatConfig struct {
Baidu ModelAPIConfig `json:"baidu"`
XunFei ModelAPIConfig `json:"xun_fei"`
EnableContext bool `json:"enable_context"` // 是否开启聊天上下文
EnableHistory bool `json:"enable_history"` // 是否允许保存聊天记录
ContextDeep int `json:"context_deep"` // 上下文深度
DallApiURL string `json:"dall_api_url"` // dall-e3 绘图 API 地址
DallImgNum int `json:"dall_img_num"` // dall-e3 出图数量
EnableContext bool `json:"enable_context"` // 是否开启聊天上下文
EnableHistory bool `json:"enable_history"` // 是否允许保存聊天记录
ContextDeep int `json:"context_deep"` // 上下文深度
DallImgNum int `json:"dall_img_num"` // dall-e3 出图数量
}
type Platform string
@@ -141,29 +159,30 @@ type InviteReward struct {
}
type ModelAPIConfig struct {
ApiURL string `json:"api_url,omitempty"`
Temperature float32 `json:"temperature"`
MaxTokens int `json:"max_tokens"`
ApiKey string `json:"api_key"`
}
type SystemConfig struct {
Title string `json:"title"`
AdminTitle string `json:"admin_title"`
Models []string `json:"models"`
InitChatCalls int `json:"init_chat_calls"` // 新用户注册赠送对话次数
InitImgCalls int `json:"init_img_calls"` // 新用户注册赠送绘图次数
VipMonthCalls int `json:"vip_month_calls"` // VIP 会员每月赠送的对话次数
VipMonthImgCalls int `json:"vip_month_img_calls"` // VIP 会员每月赠送绘图次数
EnabledRegister bool `json:"enabled_register"` // 是否启用注册功能,关闭注册功能之后将无法注册
EnabledMsg bool `json:"enabled_msg"` // 是否启用短信验证码服务
RewardImg string `json:"reward_img"` // 众筹收款二维码地址
EnabledReward bool `json:"enabled_reward"` // 启用众筹功能
EnabledAlipay bool `json:"enabled_alipay"` // 是否启用支付宝支付通道
Title string `json:"title"`
AdminTitle string `json:"admin_title"`
InitChatCalls int `json:"init_chat_calls"` // 新用户注册赠送对话次数
InitImgCalls int `json:"init_img_calls"` // 新用户注册赠送绘图次数
VipMonthCalls int `json:"vip_month_calls"` // VIP 会员每月赠送的对话次数
VipMonthImgCalls int `json:"vip_month_img_calls"` // VIP 会员每月赠送绘图次数
RegisterWays []string `json:"register_ways"` // 注册方式:支持手机,邮箱注册
RewardImg string `json:"reward_img"` // 众筹收款二维码地址
EnabledReward bool `json:"enabled_reward"` // 启用众筹功能
ChatCallPrice float64 `json:"chat_call_price"` // 对话单次调用费用
ImgCallPrice float64 `json:"img_call_price"` // 绘图单次调用费用
OrderPayTimeout int `json:"order_pay_timeout"` //订单支付超时时间
DefaultModels []string `json:"default_models"` // 默认开通的 AI 模型
OrderPayInfoText string `json:"order_pay_info_text"` // 订单支付页面说明文字
InviteChatCalls int `json:"invite_chat_calls"` // 邀请用户注册奖励对话次数
InviteImgCalls int `json:"invite_img_calls"` // 邀请用户注册奖励绘图次数
ForceInvite bool `json:"force_invite"` // 是否强制必须使用邀请码才能注册
ShowDemoNotice bool `json:"show_demo_notice"` // 显示演示站公告
}

View File

@@ -27,8 +27,12 @@ func (h *ApiKeyHandler) Save(c *gin.Context) {
var data struct {
Id uint `json:"id"`
Platform string `json:"platform"`
Name string `json:"name"`
Type string `json:"type"`
Value string `json:"value"`
ApiURL string `json:"api_url"`
Enabled bool `json:"enabled"`
UseProxy bool `json:"use_proxy"`
}
if err := c.ShouldBindJSON(&data); err != nil {
resp.ERROR(c, types.InvalidArgs)
@@ -42,6 +46,10 @@ func (h *ApiKeyHandler) Save(c *gin.Context) {
apiKey.Platform = data.Platform
apiKey.Value = data.Value
apiKey.Type = data.Type
apiKey.ApiURL = data.ApiURL
apiKey.Enabled = data.Enabled
apiKey.UseProxy = data.UseProxy
apiKey.Name = data.Name
res := h.db.Save(&apiKey)
if res.Error != nil {
resp.ERROR(c, "更新数据库失败!")
@@ -80,6 +88,26 @@ func (h *ApiKeyHandler) List(c *gin.Context) {
resp.SUCCESS(c, keys)
}
func (h *ApiKeyHandler) Set(c *gin.Context) {
var data struct {
Id uint `json:"id"`
Filed string `json:"filed"`
Value interface{} `json:"value"`
}
if err := c.ShouldBindJSON(&data); err != nil {
resp.ERROR(c, types.InvalidArgs)
return
}
res := h.db.Model(&model.ApiKey{}).Where("id = ?", data.Id).Update(data.Filed, data.Value)
if res.Error != nil {
resp.ERROR(c, "更新数据库失败!")
return
}
resp.SUCCESS(c)
}
func (h *ApiKeyHandler) Remove(c *gin.Context) {
id := h.GetInt(c, "id", 0)

View File

@@ -98,6 +98,26 @@ func (h *ChatRoleHandler) Sort(c *gin.Context) {
resp.SUCCESS(c)
}
func (h *ChatRoleHandler) Set(c *gin.Context) {
var data struct {
Id uint `json:"id"`
Filed string `json:"filed"`
Value interface{} `json:"value"`
}
if err := c.ShouldBindJSON(&data); err != nil {
resp.ERROR(c, types.InvalidArgs)
return
}
res := h.db.Model(&model.ChatRole{}).Where("id = ?", data.Id).Update(data.Filed, data.Value)
if res.Error != nil {
resp.ERROR(c, "更新数据库失败!")
return
}
resp.SUCCESS(c)
}
func (h *ChatRoleHandler) Remove(c *gin.Context) {
id := h.GetInt(c, "id", 0)
if id <= 0 {

View File

@@ -46,7 +46,7 @@ func (h *RewardHandler) List(c *gin.Context) {
}
r.Id = v.Id
r.Username = userMap[v.UserId].Mobile
r.Username = userMap[v.UserId].Username
r.CreatedAt = v.CreatedAt.Unix()
r.UpdatedAt = v.UpdatedAt.Unix()
rewards = append(rewards, r)
@@ -55,3 +55,16 @@ func (h *RewardHandler) List(c *gin.Context) {
resp.SUCCESS(c, rewards)
}
func (h *RewardHandler) Remove(c *gin.Context) {
id := h.GetInt(c, "id", 0)
if id > 0 {
res := h.db.Where("id = ?", id).Delete(&model.Reward{})
if res.Error != nil {
resp.ERROR(c, "更新数据库失败!")
return
}
}
resp.SUCCESS(c)
}

View File

@@ -27,7 +27,7 @@ func NewUserHandler(app *core.AppServer, db *gorm.DB) *UserHandler {
func (h *UserHandler) List(c *gin.Context) {
page := h.GetInt(c, "page", 1)
pageSize := h.GetInt(c, "page_size", 20)
mobile := h.GetTrim(c, "mobile")
username := h.GetTrim(c, "username")
offset := (page - 1) * pageSize
var items []model.User
@@ -35,8 +35,8 @@ func (h *UserHandler) List(c *gin.Context) {
var total int64
session := h.db.Session(&gorm.Session{})
if mobile != "" {
session = session.Where("mobile LIKE ?", "%"+mobile+"%")
if username != "" {
session = session.Where("username LIKE ?", "%"+username+"%")
}
session.Model(&model.User{}).Count(&total)
@@ -95,7 +95,7 @@ func (h *UserHandler) Save(c *gin.Context) {
} else {
salt := utils.RandString(8)
u := model.User{
Mobile: data.Mobile,
Username: data.Mobile,
Password: utils.GenPassword(data.Password, salt),
Avatar: "/images/avatar/user.png",
Salt: salt,

View File

@@ -29,7 +29,7 @@ func (h *ChatHandler) sendAzureMessage(
ws *types.WsClient) error {
promptCreatedAt := time.Now() // 记录提问时间
start := time.Now()
var apiKey = userVo.ChatConfig.ApiKeys[session.Model.Platform]
var apiKey = model.ApiKey{}
response, err := h.doRequest(ctx, req, session.Model.Platform, &apiKey)
logger.Info("HTTP请求完成耗时", time.Now().Sub(start))
if err != nil {

View File

@@ -46,7 +46,7 @@ func (h *ChatHandler) sendBaiduMessage(
ws *types.WsClient) error {
promptCreatedAt := time.Now() // 记录提问时间
start := time.Now()
var apiKey = userVo.ChatConfig.ApiKeys[session.Model.Platform]
var apiKey = model.ApiKey{}
response, err := h.doRequest(ctx, req, session.Model.Platform, &apiKey)
logger.Info("HTTP请求完成耗时", time.Now().Sub(start))
if err != nil {

View File

@@ -14,14 +14,15 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/gin-gonic/gin"
"github.com/go-redis/redis/v8"
"github.com/gorilla/websocket"
"gorm.io/gorm"
"net/http"
"net/url"
"strings"
"time"
"github.com/gin-gonic/gin"
"github.com/go-redis/redis/v8"
"github.com/gorilla/websocket"
"gorm.io/gorm"
)
const ErrorMsg = "抱歉AI 助手开小差了,请稍后再试。"
@@ -80,7 +81,7 @@ func (h *ChatHandler) ChatHandle(c *gin.Context) {
session = &types.ChatSession{
SessionId: sessionId,
ClientIP: c.ClientIP(),
Username: user.Mobile,
Username: user.Username,
UserId: user.Id,
}
h.App.ChatSession.Put(sessionId, session)
@@ -156,11 +157,13 @@ func (h *ChatHandler) ChatHandle(c *gin.Context) {
}
func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSession, role model.ChatRole, prompt string, ws *types.WsClient) error {
defer func() {
if r := recover(); r != nil {
logger.Error("Recover message from error: ", r)
}
}()
if !h.App.Debug {
defer func() {
if r := recover(); r != nil {
logger.Error("Recover message from error: ", r)
}
}()
}
var user model.User
res := h.db.Model(&model.User{}).First(&user, session.UserId)
@@ -275,7 +278,7 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
if err == nil {
for _, v := range messages {
tks, _ := utils.CalcTokens(v.Content, req.Model)
if tokens+tks >= types.ModelToTokens[req.Model] {
if tokens+tks >= types.GetModelMaxToken(req.Model) {
break
}
tokens += tks
@@ -290,7 +293,7 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
if res.Error == nil {
for i := len(historyMessages) - 1; i >= 0; i-- {
msg := historyMessages[i]
if tokens+msg.Tokens >= types.ModelToTokens[session.Model.Value] {
if tokens+msg.Tokens >= types.GetModelMaxToken(session.Model.Value) {
break
}
tokens += msg.Tokens
@@ -338,8 +341,9 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
// Tokens 统计 token 数量
func (h *ChatHandler) Tokens(c *gin.Context) {
var data struct {
Text string `json:"text"`
Model string `json:"model"`
Text string `json:"text"`
Model string `json:"model"`
ChatId string `json:"chat_id"`
}
if err := c.ShouldBindJSON(&data); err != nil {
resp.ERROR(c, types.InvalidArgs)
@@ -347,10 +351,10 @@ func (h *ChatHandler) Tokens(c *gin.Context) {
}
// 如果没有传入 text 字段,则说明是获取当前 reply 总的 token 消耗(带上下文)
if data.Text == "" {
if data.Text == "" && data.ChatId != "" {
var item model.HistoryMessage
userId, _ := c.Get(types.LoginUserID)
res := h.db.Where("user_id = ?", userId).Last(&item)
res := h.db.Where("user_id = ?", userId).Where("chat_id = ?", data.ChatId).Last(&item)
if res.Error != nil {
resp.ERROR(c, res.Error.Error())
return
@@ -400,39 +404,33 @@ func (h *ChatHandler) StopGenerate(c *gin.Context) {
// 发送请求到 OpenAI 服务器
// useOwnApiKey: 是否使用了用户自己的 API KEY
func (h *ChatHandler) doRequest(ctx context.Context, req types.ApiRequest, platform types.Platform, apiKey *string) (*http.Response, error) {
func (h *ChatHandler) doRequest(ctx context.Context, req types.ApiRequest, platform types.Platform, apiKey *model.ApiKey) (*http.Response, error) {
res := h.db.Where("platform = ?", platform).Where("type = ?", "chat").Where("enabled = ?", true).Order("last_used_at ASC").First(apiKey)
if res.Error != nil {
return nil, errors.New("no available key, please import key")
}
var apiURL string
switch platform {
case types.Azure:
md := strings.Replace(req.Model, ".", "", 1)
apiURL = strings.Replace(h.App.ChatConfig.Azure.ApiURL, "{model}", md, 1)
apiURL = strings.Replace(apiKey.ApiURL, "{model}", md, 1)
break
case types.ChatGLM:
apiURL = strings.Replace(h.App.ChatConfig.ChatGML.ApiURL, "{model}", req.Model, 1)
apiURL = strings.Replace(apiKey.ApiURL, "{model}", req.Model, 1)
req.Prompt = req.Messages // 使用 prompt 字段替代 message 字段
req.Messages = nil
break
case types.Baidu:
apiURL = strings.Replace(h.App.ChatConfig.Baidu.ApiURL, "{model}", req.Model, 1)
apiURL = strings.Replace(apiKey.ApiURL, "{model}", req.Model, 1)
break
default:
apiURL = h.App.ChatConfig.OpenAI.ApiURL
apiURL = apiKey.ApiURL
}
if *apiKey == "" {
var key model.ApiKey
res := h.db.Where("platform = ? AND type = ?", platform, "chat").Order("last_used_at ASC").First(&key)
if res.Error != nil {
return nil, errors.New("no available key, please import key")
}
// 更新 API KEY 的最后使用时间
h.db.Model(&key).UpdateColumn("last_used_at", time.Now().Unix())
*apiKey = key.Value
}
// 更新 API KEY 的最后使用时间
h.db.Model(apiKey).UpdateColumn("last_used_at", time.Now().Unix())
// 百度文心,需要串接 access_token
if platform == types.Baidu {
token, err := h.getBaiduToken(*apiKey)
token, err := h.getBaiduToken(apiKey.Value)
if err != nil {
return nil, err
}
@@ -453,8 +451,9 @@ func (h *ChatHandler) doRequest(ctx context.Context, req types.ApiRequest, platf
request = request.WithContext(ctx)
request.Header.Set("Content-Type", "application/json")
proxyURL := h.App.Config.ProxyURL
if proxyURL != "" && platform == types.OpenAI { // 使用代理
var proxyURL string
if h.App.Config.ProxyURL != "" && apiKey.UseProxy { // 使用代理
proxyURL = h.App.Config.ProxyURL
proxy, _ := url.Parse(proxyURL)
client = &http.Client{
Transport: &http.Transport{
@@ -464,13 +463,13 @@ func (h *ChatHandler) doRequest(ctx context.Context, req types.ApiRequest, platf
} else {
client = http.DefaultClient
}
logger.Infof("Sending %s request, ApiURL:%s, PROXY: %s, Model: %s", platform, apiURL, proxyURL, req.Model)
logger.Debugf("Sending %s request, ApiURL:%s, ApiKey:%s, PROXY: %s, Model: %s", platform, apiURL, apiKey.Value, proxyURL, req.Model)
switch platform {
case types.Azure:
request.Header.Set("api-key", *apiKey)
request.Header.Set("api-key", apiKey.Value)
break
case types.ChatGLM:
token, err := h.getChatGLMToken(*apiKey)
token, err := h.getChatGLMToken(apiKey.Value)
if err != nil {
return nil, err
}
@@ -479,7 +478,7 @@ func (h *ChatHandler) doRequest(ctx context.Context, req types.ApiRequest, platf
case types.Baidu:
request.RequestURI = ""
case types.OpenAI:
request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", *apiKey))
request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", apiKey.Value))
}
return client.Do(request)
}

View File

@@ -30,7 +30,7 @@ func (h *ChatHandler) sendChatGLMMessage(
ws *types.WsClient) error {
promptCreatedAt := time.Now() // 记录提问时间
start := time.Now()
var apiKey = userVo.ChatConfig.ApiKeys[session.Model.Platform]
var apiKey = model.ApiKey{}
response, err := h.doRequest(ctx, req, session.Model.Platform, &apiKey)
logger.Info("HTTP请求完成耗时", time.Now().Sub(start))
if err != nil {

View File

@@ -9,12 +9,13 @@ import (
"context"
"encoding/json"
"fmt"
req2 "github.com/imroc/req/v3"
"html/template"
"io"
"strings"
"time"
"unicode/utf8"
req2 "github.com/imroc/req/v3"
)
// OPenAI 消息发送实现
@@ -29,7 +30,7 @@ func (h *ChatHandler) sendOpenAiMessage(
ws *types.WsClient) error {
promptCreatedAt := time.Now() // 记录提问时间
start := time.Now()
var apiKey = userVo.ChatConfig.ApiKeys[session.Model.Platform]
var apiKey = model.ApiKey{}
response, err := h.doRequest(ctx, req, session.Model.Platform, &apiKey)
logger.Info("HTTP请求完成耗时", time.Now().Sub(start))
if err != nil {
@@ -137,7 +138,7 @@ func (h *ChatHandler) sendOpenAiMessage(
if err != nil {
errMsg = err.Error()
} else if r.IsErrorState() {
errMsg = r.Err.Error()
errMsg = r.Status
}
if errMsg != "" || apiRes.Code != types.Success {
msg := "调用函数工具出错:" + apiRes.Message + errMsg

View File

@@ -67,29 +67,25 @@ func (h *ChatHandler) sendXunFeiMessage(
prompt string,
ws *types.WsClient) error {
promptCreatedAt := time.Now() // 记录提问时间
var apiKey = userVo.ChatConfig.ApiKeys[session.Model.Platform]
if apiKey == "" {
var key model.ApiKey
res := h.db.Where("platform = ? AND type = ?", session.Model.Platform, "chat").Order("last_used_at ASC").First(&key)
if res.Error != nil {
utils.ReplyMessage(ws, "抱歉😔😔😔,系统已经没有可用的 API KEY请联系管理员")
return nil
}
// 更新 API KEY 的最后使用时间
h.db.Model(&key).UpdateColumn("last_used_at", time.Now().Unix())
apiKey = key.Value
var apiKey model.ApiKey
res := h.db.Where("platform = ?", session.Model.Platform).Where("type = ?", "chat").Where("enabled = ?", true).Order("last_used_at ASC").First(&apiKey)
if res.Error != nil {
utils.ReplyMessage(ws, "抱歉😔😔😔,系统已经没有可用的 API KEY请联系管理员")
return nil
}
// 更新 API KEY 的最后使用时间
h.db.Model(&apiKey).UpdateColumn("last_used_at", time.Now().Unix())
d := websocket.Dialer{
HandshakeTimeout: 5 * time.Second,
}
key := strings.Split(apiKey, "|")
key := strings.Split(apiKey.Value, "|")
if len(key) != 3 {
utils.ReplyMessage(ws, "非法的 API KEY")
return nil
}
apiURL := strings.Replace(h.App.ChatConfig.XunFei.ApiURL, "{version}", Model2URL[req.Model], 1)
apiURL := strings.Replace(apiKey.ApiURL, "{version}", Model2URL[req.Model], 1)
wsURL, err := assembleAuthUrl(apiURL, key[1], key[2])
//握手并建立websocket 连接
conn, resp, err := d.Dial(wsURL, nil)

View File

@@ -7,11 +7,14 @@ import (
"chatplus/store/model"
"chatplus/utils"
"chatplus/utils/resp"
"errors"
"fmt"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5"
"github.com/imroc/req/v3"
"gorm.io/gorm"
"strings"
"time"
)
type FunctionHandler struct {
@@ -50,8 +53,41 @@ type dataItem struct {
Remark string `json:"remark"`
}
// check authorization
func (h *FunctionHandler) checkAuth(c *gin.Context) error {
tokenString := c.GetHeader(types.UserAuthHeader)
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return []byte(h.App.Config.Session.SecretKey), nil
})
if err != nil {
return fmt.Errorf("error with parse auth token: %v", err)
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok || !token.Valid {
return errors.New("token is invalid")
}
expr := utils.IntValue(utils.InterfaceToString(claims["expired"]), 0)
if expr > 0 && int64(expr) < time.Now().Unix() {
return errors.New("token is expired")
}
return nil
}
// WeiBo 微博热搜
func (h *FunctionHandler) WeiBo(c *gin.Context) {
if err := h.checkAuth(c); err != nil {
resp.ERROR(c, err.Error())
return
}
if h.config.Token == "" {
resp.ERROR(c, "无效的 API Token")
return
@@ -83,6 +119,11 @@ func (h *FunctionHandler) WeiBo(c *gin.Context) {
// ZaoBao 今日早报
func (h *FunctionHandler) ZaoBao(c *gin.Context) {
if err := h.checkAuth(c); err != nil {
resp.ERROR(c, err.Error())
return
}
if h.config.Token == "" {
resp.ERROR(c, "无效的 API Token")
return
@@ -139,6 +180,11 @@ type ErrRes struct {
// Dall3 DallE3 AI 绘图
func (h *FunctionHandler) Dall3(c *gin.Context) {
if err := h.checkAuth(c); err != nil {
resp.ERROR(c, err.Error())
return
}
var params map[string]interface{}
if err := c.ShouldBindJSON(&params); err != nil {
resp.ERROR(c, types.InvalidArgs)
@@ -162,7 +208,7 @@ func (h *FunctionHandler) Dall3(c *gin.Context) {
prompt := utils.InterfaceToString(params["prompt"])
// get image generation API KEY
var apiKey model.ApiKey
tx = h.db.Where("platform = ? AND type = ?", types.OpenAI, "img").Order("last_used_at ASC").First(&apiKey)
tx = h.db.Where("platform = ?", types.OpenAI).Where("type = ?", "img").Where("enabled = ?", true).Order("last_used_at ASC").First(&apiKey)
if tx.Error != nil {
resp.ERROR(c, "获取绘图 API KEY 失败: "+tx.Error.Error())
return
@@ -185,15 +231,10 @@ func (h *FunctionHandler) Dall3(c *gin.Context) {
// translate prompt
const translatePromptTemplate = "Translate the following painting prompt words into English keyword phrases. Without any explanation, directly output the keyword phrases separated by commas. The content to be translated is: [%s]"
pt, err := utils.OpenAIRequest(fmt.Sprintf(translatePromptTemplate, params["prompt"]), apiKey.Value, h.App.Config.ProxyURL, chatConfig.OpenAI.ApiURL)
pt, err := utils.OpenAIRequest(h.db, fmt.Sprintf(translatePromptTemplate, params["prompt"]), h.App.Config.ProxyURL)
if err == nil {
prompt = pt
}
apiURL := chatConfig.DallApiURL
if utils.IsEmptyValue(apiURL) {
apiURL = "https://api.openai.com/v1/images/generations"
}
imgNum := chatConfig.DallImgNum
if imgNum <= 0 {
imgNum = 1
@@ -201,11 +242,12 @@ func (h *FunctionHandler) Dall3(c *gin.Context) {
var res imgRes
var errRes ErrRes
var request *req.Request
if strings.Contains(apiURL, "api.openai.com") {
if apiKey.UseProxy && h.proxyURL != "" {
request = req.C().SetProxyURL(h.proxyURL).R()
} else {
request = req.C().R()
}
logger.Debugf("Sending %s request, ApiURL:%s, ApiKey:%s, PROXY: %s", apiKey.Platform, apiKey.ApiURL, apiKey.Value, h.proxyURL)
r, err := request.SetHeader("Content-Type", "application/json").
SetHeader("Authorization", "Bearer "+apiKey.Value).
SetBody(imgReq{
@@ -215,11 +257,13 @@ func (h *FunctionHandler) Dall3(c *gin.Context) {
Size: "1024x1024",
}).
SetErrorResult(&errRes).
SetSuccessResult(&res).Post(apiURL)
SetSuccessResult(&res).Post(apiKey.ApiURL)
if r.IsErrorState() {
resp.ERROR(c, "请求 OpenAI API 失败: "+errRes.Error.Message)
return
}
// 更新 API KEY 的最后使用时间
h.db.Model(&apiKey).UpdateColumn("last_used_at", time.Now().Unix())
// 存储图片
imgURL, err := h.uploadManager.GetUploadHandler().PutImg(res.Data[0].Url, false)
if err != nil {

View File

@@ -179,7 +179,6 @@ func (h *MidJourneyHandler) Image(c *gin.Context) {
}
type reqVo struct {
TaskId string `json:"task_id"`
Index int `json:"index"`
ChannelId string `json:"channel_id"`
MessageId string `json:"message_id"`
@@ -206,11 +205,12 @@ func (h *MidJourneyHandler) Upscale(c *gin.Context) {
idValue, _ := c.Get(types.LoginUserID)
jobId := 0
userId := utils.IntValue(utils.InterfaceToString(idValue), 0)
taskId, _ := h.snowflake.Next(true)
job := model.MidJourneyJob{
Type: types.TaskUpscale.String(),
ReferenceId: data.MessageId,
UserId: userId,
TaskId: data.TaskId,
TaskId: taskId,
Progress: 0,
Prompt: data.Prompt,
CreatedAt: time.Now(),
@@ -253,13 +253,13 @@ func (h *MidJourneyHandler) Variation(c *gin.Context) {
idValue, _ := c.Get(types.LoginUserID)
jobId := 0
userId := utils.IntValue(utils.InterfaceToString(idValue), 0)
taskId, _ := h.snowflake.Next(true)
job := model.MidJourneyJob{
Type: types.TaskVariation.String(),
ChannelId: data.ChannelId,
ReferenceId: data.MessageId,
UserId: userId,
TaskId: data.TaskId,
TaskId: taskId,
Progress: 0,
Prompt: data.Prompt,
CreatedAt: time.Now(),

View File

@@ -204,7 +204,7 @@ func (h *PaymentHandler) PayQrcode(c *gin.Context) {
}
order := model.Order{
UserId: user.Id,
Mobile: user.Mobile,
Mobile: user.Username,
ProductId: product.Id,
OrderNo: orderNo,
Subject: product.Name,

View File

@@ -3,7 +3,6 @@ package handler
import (
"chatplus/core"
"chatplus/core/types"
"chatplus/store/model"
"chatplus/utils"
"chatplus/utils/resp"
"fmt"
@@ -36,7 +35,7 @@ func (h *PromptHandler) Rewrite(c *gin.Context) {
return
}
content, err := h.request(data.Prompt, rewritePromptTemplate)
content, err := utils.OpenAIRequest(h.db, fmt.Sprintf(rewritePromptTemplate, data.Prompt), h.App.Config.ProxyURL)
if err != nil {
resp.ERROR(c, err.Error())
return
@@ -54,7 +53,7 @@ func (h *PromptHandler) Translate(c *gin.Context) {
return
}
content, err := h.request(data.Prompt, translatePromptTemplate)
content, err := utils.OpenAIRequest(h.db, fmt.Sprintf(translatePromptTemplate, data.Prompt), h.App.Config.ProxyURL)
if err != nil {
resp.ERROR(c, err.Error())
return
@@ -62,14 +61,3 @@ func (h *PromptHandler) Translate(c *gin.Context) {
resp.SUCCESS(c, content)
}
func (h *PromptHandler) request(prompt string, promptTemplate string) (string, error) {
// 获取 OpenAI 的 API KEY
var apiKey model.ApiKey
res := h.db.Where("platform = ?", types.OpenAI).First(&apiKey)
if res.Error != nil {
return "", fmt.Errorf("error with fetch OpenAI API KEY%v", res.Error)
}
return utils.OpenAIRequest(fmt.Sprintf(promptTemplate, prompt), apiKey.Value, h.App.Config.ProxyURL, h.App.ChatConfig.OpenAI.ApiURL)
}

View File

@@ -4,20 +4,24 @@ import (
"chatplus/core"
"chatplus/core/types"
"chatplus/store/model"
"chatplus/store/vo"
"chatplus/utils"
"chatplus/utils/resp"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"math"
"strings"
"sync"
)
type RewardHandler struct {
BaseHandler
db *gorm.DB
db *gorm.DB
lock sync.Mutex
}
func NewRewardHandler(server *core.AppServer, db *gorm.DB) *RewardHandler {
h := RewardHandler{db: db}
h := RewardHandler{db: db, lock: sync.Mutex{}}
h.App = server
return &h
}
@@ -26,15 +30,25 @@ func NewRewardHandler(server *core.AppServer, db *gorm.DB) *RewardHandler {
func (h *RewardHandler) Verify(c *gin.Context) {
var data struct {
TxId string `json:"tx_id"`
Type string `json:"type"`
}
if err := c.ShouldBindJSON(&data); err != nil {
resp.ERROR(c, types.InvalidArgs)
return
}
user, err := utils.GetLoginUser(c, h.db)
if err != nil {
resp.HACKER(c)
return
}
// 移除转账单号中间的空格,防止有人复制的时候多复制了空格
data.TxId = strings.ReplaceAll(data.TxId, " ", "")
h.lock.Lock()
defer h.lock.Unlock()
var item model.Reward
res := h.db.Where("tx_id = ?", data.TxId).First(&item)
if res.Error != nil {
@@ -47,15 +61,17 @@ func (h *RewardHandler) Verify(c *gin.Context) {
return
}
user, err := utils.GetLoginUser(c, h.db)
if err != nil {
resp.HACKER(c)
return
}
tx := h.db.Begin()
calls := (item.Amount + 0.1) * 10
res = h.db.Model(&user).UpdateColumn("calls", gorm.Expr("calls + ?", calls))
exchange := vo.RewardExchange{}
if data.Type == "chat" {
calls := math.Ceil(item.Amount / h.App.SysConfig.ChatCallPrice)
exchange.Calls = int(calls)
res = h.db.Model(&user).UpdateColumn("calls", gorm.Expr("calls + ?", calls))
} else if data.Type == "img" {
calls := math.Ceil(item.Amount / h.App.SysConfig.ImgCallPrice)
exchange.ImgCalls = int(calls)
res = h.db.Model(&user).UpdateColumn("img_calls", gorm.Expr("img_calls + ?", calls))
}
if res.Error != nil {
resp.ERROR(c, "更新数据库失败!")
return
@@ -64,6 +80,7 @@ func (h *RewardHandler) Verify(c *gin.Context) {
// 更新核销状态
item.Status = true
item.UserId = user.Id
item.Exchange = utils.JsonEncode(exchange)
res = h.db.Updates(&item)
if res.Error != nil {
tx.Rollback()

View File

@@ -8,6 +8,7 @@ import (
"chatplus/utils/resp"
"github.com/gin-gonic/gin"
"github.com/go-redis/redis/v8"
"strings"
)
const CodeStorePrefix = "/verify/codes/"
@@ -16,21 +17,27 @@ type SmsHandler struct {
BaseHandler
redis *redis.Client
sms *service.AliYunSmsService
smtp *service.SmtpService
captcha *service.CaptchaService
}
func NewSmsHandler(app *core.AppServer, client *redis.Client, sms *service.AliYunSmsService, captcha *service.CaptchaService) *SmsHandler {
handler := &SmsHandler{redis: client, sms: sms, captcha: captcha}
func NewSmsHandler(
app *core.AppServer,
client *redis.Client,
sms *service.AliYunSmsService,
smtp *service.SmtpService,
captcha *service.CaptchaService) *SmsHandler {
handler := &SmsHandler{redis: client, sms: sms, captcha: captcha, smtp: smtp}
handler.App = app
return handler
}
// SendCode 发送验证码短信
// SendCode 发送验证码
func (h *SmsHandler) SendCode(c *gin.Context) {
var data struct {
Mobile string `json:"mobile"`
Key string `json:"key"`
Dots string `json:"dots"`
Receiver string `json:"receiver"` // 接收者
Key string `json:"key"`
Dots string `json:"dots"`
}
if err := c.ShouldBindJSON(&data); err != nil {
resp.ERROR(c, types.InvalidArgs)
@@ -43,14 +50,19 @@ func (h *SmsHandler) SendCode(c *gin.Context) {
}
code := utils.RandomNumber(6)
err := h.sms.SendVerifyCode(data.Mobile, code)
var err error
if strings.Contains(data.Receiver, "@") { // email
err = h.smtp.SendVerifyCode(data.Receiver, code)
} else {
err = h.sms.SendVerifyCode(data.Receiver, code)
}
if err != nil {
resp.ERROR(c, err.Error())
return
}
// 存储验证码,等待后面注册验证
_, err = h.redis.Set(c, CodeStorePrefix+data.Mobile, code, 0).Result()
_, err = h.redis.Set(c, CodeStorePrefix+data.Receiver, code, 0).Result()
if err != nil {
resp.ERROR(c, "验证码保存失败")
return

View File

@@ -2,39 +2,58 @@ package handler
import (
"chatplus/service"
"chatplus/store/model"
"chatplus/utils"
"chatplus/utils/resp"
"fmt"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type TestHandler struct {
db *gorm.DB
snowflake *service.Snowflake
}
func NewTestHandler(snowflake *service.Snowflake) *TestHandler {
return &TestHandler{snowflake: snowflake}
func NewTestHandler(db *gorm.DB, snowflake *service.Snowflake) *TestHandler {
return &TestHandler{db: db, snowflake: snowflake}
}
func (h *TestHandler) TestPay(c *gin.Context) {
//appId := "" //Appid
//appSecret := "" //密钥
//var host = "https://api.xunhupay.com/payment/do.html" //跳转支付页接口URL
//client := payment.NewXunHuPay(appId, appSecret) //初始化调用
//
////支付参数appid、time、nonce_str和hash这四个参数不用传调用的时候执行方法内部已经处理
//orderNo, _ := h.snowflake.Next()
//params := map[string]string{
// "version": "1.1",
// "trade_order_id": orderNo,
// "total_fee": "0.1",
// "title": "测试支付",
// "notify_url": "http://xxxxxxx.com",
// "return_url": "http://localhost:8888",
// "wap_name": "极客学长",
// "callback_url": "",
//}
//
//execute, err := client.Execute(host, params) //执行支付操作
//if err != nil {
// logger.Error(err)
//}
//resp.SUCCESS(c, execute)
func (h *TestHandler) Test(c *gin.Context) {
h.initUserNickname(c)
h.initMjTaskId(c)
}
func (h *TestHandler) initUserNickname(c *gin.Context) {
var users []model.User
tx := h.db.Find(&users)
if tx.Error != nil {
resp.ERROR(c, tx.Error.Error())
return
}
for _, u := range users {
u.Nickname = fmt.Sprintf("极客学长@%d", utils.RandomNumber(6))
h.db.Updates(&u)
}
resp.SUCCESS(c)
}
func (h *TestHandler) initMjTaskId(c *gin.Context) {
var jobs []model.MidJourneyJob
tx := h.db.Find(&jobs)
if tx.Error != nil {
resp.ERROR(c, tx.Error.Error())
return
}
for _, job := range jobs {
id, _ := h.snowflake.Next(true)
job.TaskId = id
h.db.Updates(&job)
}
resp.SUCCESS(c)
}

View File

@@ -39,7 +39,7 @@ func NewUserHandler(
func (h *UserHandler) Register(c *gin.Context) {
// parameters process
var data struct {
Mobile string `json:"mobile"`
Username string `json:"username"`
Password string `json:"password"`
Code string `json:"code"`
InviteCode string `json:"invite_code"`
@@ -49,34 +49,22 @@ func (h *UserHandler) Register(c *gin.Context) {
return
}
data.Password = strings.TrimSpace(data.Password)
if len(data.Mobile) < 10 {
resp.ERROR(c, "请输入合法的手机号")
return
}
if len(data.Password) < 8 {
resp.ERROR(c, "密码长度不能少于8个字符")
return
}
// 检查验证码
key := CodeStorePrefix + data.Mobile
if h.App.SysConfig.EnabledMsg {
code, err := h.redis.Get(c, key).Result()
if err != nil || code != data.Code {
resp.ERROR(c, "短信验证码错误")
return
}
key := CodeStorePrefix + data.Username
code, err := h.redis.Get(c, key).Result()
if err != nil || code != data.Code {
resp.ERROR(c, "验证码错误")
return
}
// 验证邀请码
inviteCode := model.InviteCode{}
if data.InviteCode == "" {
if h.App.SysConfig.ForceInvite {
resp.ERROR(c, "当前系统设定必须使用邀请码才能注册")
return
}
} else {
if data.InviteCode != "" {
res := h.db.Where("code = ?", data.InviteCode).First(&inviteCode)
if res.Error != nil {
resp.ERROR(c, "无效的邀请码")
@@ -86,19 +74,20 @@ func (h *UserHandler) Register(c *gin.Context) {
// check if the username is exists
var item model.User
res := h.db.Where("mobile = ?", data.Mobile).First(&item)
res := h.db.Where("username = ?", data.Username).First(&item)
if res.RowsAffected > 0 {
resp.ERROR(c, "该手机号码已经被注册,请更换其他手机号")
resp.ERROR(c, "该用户名已经被注册")
return
}
salt := utils.RandString(8)
user := model.User{
Username: data.Username,
Password: utils.GenPassword(data.Password, salt),
Nickname: fmt.Sprintf("极客学长@%d", utils.RandomNumber(6)),
Avatar: "/images/avatar/user.png",
Salt: salt,
Status: true,
Mobile: data.Mobile,
ChatRoles: utils.JsonEncode([]string{"gpt"}), // 默认只订阅通用助手角色
ChatModels: utils.JsonEncode(h.App.SysConfig.DefaultModels), // 默认开通的模型
ChatConfig: utils.JsonEncode(types.UserChatConfig{
@@ -111,6 +100,7 @@ func (h *UserHandler) Register(c *gin.Context) {
Calls: h.App.SysConfig.InitChatCalls,
ImgCalls: h.App.SysConfig.InitImgCalls,
}
res = h.db.Create(&user)
if res.Error != nil {
resp.ERROR(c, "保存数据失败")
@@ -133,14 +123,13 @@ func (h *UserHandler) Register(c *gin.Context) {
h.db.Create(&model.InviteLog{
InviterId: inviteCode.UserId,
UserId: user.Id,
Username: user.Mobile,
Username: user.Username,
InviteCode: inviteCode.Code,
Reward: utils.JsonEncode(types.InviteReward{ChatCalls: h.App.SysConfig.InviteChatCalls, ImgCalls: h.App.SysConfig.InviteImgCalls}),
})
}
if h.App.SysConfig.EnabledMsg {
_ = h.redis.Del(c, key) // 注册成功,删除短信验证码
}
_ = h.redis.Del(c, key) // 注册成功,删除短信验证码
// 自动登录创建 token
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
@@ -164,7 +153,7 @@ func (h *UserHandler) Register(c *gin.Context) {
// Login 用户登录
func (h *UserHandler) Login(c *gin.Context) {
var data struct {
Mobile string `json:"username"`
Username string `json:"username"`
Password string `json:"password"`
}
if err := c.ShouldBindJSON(&data); err != nil {
@@ -172,7 +161,7 @@ func (h *UserHandler) Login(c *gin.Context) {
return
}
var user model.User
res := h.db.Where("mobile = ?", data.Mobile).First(&user)
res := h.db.Where("username = ?", data.Username).First(&user)
if res.Error != nil {
resp.ERROR(c, "用户名不存在")
return
@@ -196,7 +185,7 @@ func (h *UserHandler) Login(c *gin.Context) {
h.db.Create(&model.UserLoginLog{
UserId: user.Id,
Username: user.Mobile,
Username: user.Username,
LoginIp: c.ClientIP(),
LoginAddress: utils.Ip2Region(h.searcher, c.ClientIP()),
})
@@ -256,7 +245,8 @@ func (h *UserHandler) Session(c *gin.Context) {
type userProfile struct {
Id uint `json:"id"`
Mobile string `json:"mobile"`
Nickname string `json:"nickname"`
Username string `json:"username"`
Avatar string `json:"avatar"`
ChatConfig types.UserChatConfig `json:"chat_config"`
Calls int `json:"calls"`
@@ -301,7 +291,7 @@ func (h *UserHandler) ProfileUpdate(c *gin.Context) {
}
h.db.First(&user, user.Id)
user.Avatar = data.Avatar
user.ChatConfig = utils.JsonEncode(data.ChatConfig)
user.Nickname = data.Nickname
res := h.db.Updates(&user)
if res.Error != nil {
resp.ERROR(c, "更新用户信息失败")
@@ -354,9 +344,9 @@ func (h *UserHandler) UpdatePass(c *gin.Context) {
// ResetPass 重置密码
func (h *UserHandler) ResetPass(c *gin.Context) {
var data struct {
Mobile string
Code string // 验证码
Password string // 新密码
Username string `json:"username"`
Code string `json:"code"` // 验证码
Password string `json:"password"` // 新密码
}
if err := c.ShouldBindJSON(&data); err != nil {
resp.ERROR(c, types.InvalidArgs)
@@ -364,20 +354,18 @@ func (h *UserHandler) ResetPass(c *gin.Context) {
}
var user model.User
res := h.db.Where("mobile", data.Mobile).First(&user)
res := h.db.Where("username", data.Username).First(&user)
if res.Error != nil {
resp.ERROR(c, "用户不存在!")
return
}
// 检查验证码
key := CodeStorePrefix + data.Mobile
if h.App.SysConfig.EnabledMsg {
code, err := h.redis.Get(c, key).Result()
if err != nil || code != data.Code {
resp.ERROR(c, "短信验证码错误")
return
}
key := CodeStorePrefix + data.Username
code, err := h.redis.Get(c, key).Result()
if err != nil || code != data.Code {
resp.ERROR(c, "短信验证码错误")
return
}
password := utils.GenPassword(data.Password, user.Salt)
@@ -391,11 +379,11 @@ func (h *UserHandler) ResetPass(c *gin.Context) {
}
}
// BindMobile 绑定手机
func (h *UserHandler) BindMobile(c *gin.Context) {
// BindUsername 重置账
func (h *UserHandler) BindUsername(c *gin.Context) {
var data struct {
Mobile string `json:"mobile"`
Code string `json:"code"`
Username string `json:"username"`
Code string `json:"code"`
}
if err := c.ShouldBindJSON(&data); err != nil {
resp.ERROR(c, types.InvalidArgs)
@@ -403,18 +391,18 @@ func (h *UserHandler) BindMobile(c *gin.Context) {
}
// 检查验证码
key := CodeStorePrefix + data.Mobile
key := CodeStorePrefix + data.Username
code, err := h.redis.Get(c, key).Result()
if err != nil || code != data.Code {
resp.ERROR(c, "短信验证码错误")
resp.ERROR(c, "验证码错误")
return
}
// 检查手机号是否被其他账号绑定
var item model.User
res := h.db.Where("mobile = ?", data.Mobile).First(&item)
res := h.db.Where("username = ?", data.Username).First(&item)
if res.Error == nil {
resp.ERROR(c, "该手机号已经被其他账号绑定")
resp.ERROR(c, "该号已经被其他账号绑定")
return
}
@@ -424,7 +412,7 @@ func (h *UserHandler) BindMobile(c *gin.Context) {
return
}
res = h.db.Model(&user).UpdateColumn("mobile", data.Mobile)
res = h.db.Model(&user).UpdateColumn("username", data.Username)
if res.Error != nil {
resp.ERROR(c, "更新数据库失败")
return

View File

@@ -57,13 +57,7 @@ func main() {
if configFile == "" {
configFile = "config.toml"
}
var debug bool
debugEnv := os.Getenv("DEBUG")
if debugEnv == "" {
debug = true
} else {
debug, _ = strconv.ParseBool(os.Getenv("DEBUG"))
}
debug, _ := strconv.ParseBool(os.Getenv("APP_DEBUG"))
logger.Info("Loading config file: ", configFile)
defer func() {
if err := recover(); err != nil {
@@ -148,6 +142,9 @@ func main() {
fx.Provide(oss.NewUploaderManager),
fx.Provide(mj.NewService),
// 邮件服务
fx.Provide(service.NewSmtpService),
// 微信机器人服务
fx.Provide(wx.NewWeChatBot),
fx.Invoke(func(config *types.AppConfig, bot *wx.Bot) {
@@ -198,7 +195,7 @@ func main() {
group.GET("profile", h.Profile)
group.POST("profile/update", h.ProfileUpdate)
group.POST("password", h.UpdatePass)
group.POST("bind/mobile", h.BindMobile)
group.POST("bind/username", h.BindUsername)
group.POST("resetPass", h.ResetPass)
}),
fx.Invoke(func(s *core.AppServer, h *chatimpl.ChatHandler) {
@@ -261,6 +258,7 @@ func main() {
group := s.Engine.Group("/api/admin/apikey/")
group.POST("save", h.Save)
group.GET("list", h.List)
group.POST("set", h.Set)
group.GET("remove", h.Remove)
}),
fx.Invoke(func(s *core.AppServer, h *admin.UserHandler) {
@@ -276,11 +274,13 @@ func main() {
group.GET("list", h.List)
group.POST("save", h.Save)
group.POST("sort", h.Sort)
group.POST("set", h.Set)
group.GET("remove", h.Remove)
}),
fx.Invoke(func(s *core.AppServer, h *admin.RewardHandler) {
group := s.Engine.Group("/api/admin/reward/")
group.GET("list", h.List)
group.GET("remove", h.Remove)
}),
fx.Invoke(func(s *core.AppServer, h *admin.DashboardHandler) {
group := s.Engine.Group("/api/admin/dashboard/")
@@ -364,8 +364,7 @@ func main() {
fx.Provide(handler.NewTestHandler),
fx.Invoke(func(s *core.AppServer, h *handler.TestHandler) {
group := s.Engine.Group("/test/")
group.GET("pay", h.TestPay)
s.Engine.GET("/api/test", h.Test)
}),
fx.Invoke(func(s *core.AppServer, db *gorm.DB) {
err := s.Run(db)

View File

@@ -0,0 +1,38 @@
-----BEGIN CERTIFICATE-----
MIIDszCCApugAwIBAgIQICMRB0rBU2/rZJbfJGMYIzANBgkqhkiG9w0BAQsFADCBkTELMAkGA1UE
BhMCQ04xGzAZBgNVBAoMEkFudCBGaW5hbmNpYWwgdGVzdDElMCMGA1UECwwcQ2VydGlmaWNhdGlv
biBBdXRob3JpdHkgdGVzdDE+MDwGA1UEAww1QW50IEZpbmFuY2lhbCBDZXJ0aWZpY2F0aW9uIEF1
dGhvcml0eSBDbGFzcyAyIFIxIHRlc3QwHhcNMjMxMTA3MDYzNTQxWhcNMjQxMTA2MDYzNTQxWjCB
hDELMAkGA1UEBhMCQ04xHzAdBgNVBAoMFm1ib25meTkwMTVAc2FuZGJveC5jb20xDzANBgNVBAsM
BkFsaXBheTFDMEEGA1UEAww65pSv5LuY5a6dKOS4reWbvSnnvZHnu5zmioDmnK/mnInpmZDlhazl
j7gtMjA4ODcyMTAyMDc1MDU4MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKsoKcw5
sxaiyV7mpWzDtnQ1K518eQLP0+dJlZAf06aBep/Aj9DIqrba/k7DHt8dKQvILMLAMpN1+2IRxbaO
yxMa/laj3lZ1eHrB6F077O3D62oHcE3noZtXL0N1zZAxpmkNmYIHeLZS2oLMS4ANu47O/wpDC7BV
HjdpZugtdPJ4mxdCpM9GDdLs7W4s5QI4PUPK4skFNMFoKI+0cYP/9ju87UP//IHC/K510GWNl+Gn
Cvgag3AmiIB0utJNsGhxm6zT1T9tUWjW9iz/BxBKiPatsCX9VpPQzGnW7ZonRQtiZSokIlP2IPvl
H5DcwpWUz3/LUY0SmKxnKOEYeOOqCW8CAwEAAaMSMBAwDgYDVR0PAQH/BAQDAgTwMA0GCSqGSIb3
DQEBCwUAA4IBAQAtgxF2EzjOndEFxBUD9tFwcSt6XKGggOp52oft1pvynPg4ALTLafOtfEPDrFBH
PwpYrSu9s9C8NJtaA2HrlCfBjIuwEFTXiN+HPvS0SwSPKt9AXEiTcOF8vDcGamEen8QI4fo5Jia7
2VRKkerkww5/+FzSaVO7ZUKuL80M1QJStmAZc8kPPwdYOTTW2bGf8BcmSDL6SPElBkt7tCCRd4sn
+jq4cZ0yb2i77rBZCwHcTvfTqIBblPwLv4uGvg3+83BxIB5w6Kqp06bKEAPmobFY5IVHa+ON0/qi
BXxXr+WQ3piKRVQEN64+PTAjSc67Ix1umvpLl3Ko6Ry7NJmpDcUn
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDszCCApugAwIBAgIQIBkIGbgVxq210KxLJ+YA/TANBgkqhkiG9w0BAQsFADCBhDELMAkGA1UE
BhMCQ04xFjAUBgNVBAoMDUFudCBGaW5hbmNpYWwxJTAjBgNVBAsMHENlcnRpZmljYXRpb24gQXV0
aG9yaXR5IHRlc3QxNjA0BgNVBAMMLUFudCBGaW5hbmNpYWwgQ2VydGlmaWNhdGlvbiBBdXRob3Jp
dHkgUjEgdGVzdDAeFw0xOTA4MTkxMTE2MDBaFw0yNDA4MDExMTE2MDBaMIGRMQswCQYDVQQGEwJD
TjEbMBkGA1UECgwSQW50IEZpbmFuY2lhbCB0ZXN0MSUwIwYDVQQLDBxDZXJ0aWZpY2F0aW9uIEF1
dGhvcml0eSB0ZXN0MT4wPAYDVQQDDDVBbnQgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9y
aXR5IENsYXNzIDIgUjEgdGVzdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMh4FKYO
ZyRQHD6eFbPKZeSAnrfjfU7xmS9Yoozuu+iuqZlb6Z0SPLUqqTZAFZejOcmr07ln/pwZxluqplxC
5+B48End4nclDMlT5HPrDr3W0frs6Xsa2ZNcyil/iKNB5MbGll8LRAxntsKvZZj6vUTMb705gYgm
VUMILwi/ZxKTQqBtkT/kQQ5y6nOZsj7XI5rYdz6qqOROrpvS/d7iypdHOMIM9Iz9DlL1mrCykbBi
t25y+gTeXmuisHUwqaRpwtCGK4BayCqxRGbNipe6W73EK9lBrrzNtTr9NaysesT/v+l25JHCL9tG
wpNr1oWFzk4IHVOg0ORiQ6SUgxZUTYcCAwEAAaMSMBAwDgYDVR0PAQH/BAQDAgTwMA0GCSqGSIb3
DQEBCwUAA4IBAQBWThEoIaQoBX2YeRY/I8gu6TYnFXtyuCljANnXnM38ft+ikhE5mMNgKmJYLHvT
yWWWgwHoSAWEuml7EGbE/2AK2h3k0MdfiWLzdmpPCRG/RJHk6UB1pMHPilI+c0MVu16OPpKbg5Vf
LTv7dsAB40AzKsvyYw88/Ezi1osTXo6QQwda7uefvudirtb8FcQM9R66cJxl3kt1FXbpYwheIm/p
j1mq64swCoIYu4NrsUYtn6CV542DTQMI5QdXkn+PzUUly8F6kDp+KpMNd0avfWNL5+O++z+F5Szy
1CPta1D7EQ/eYmMP+mOQ35oifWIoFCpN6qQVBS/Hob1J/UUyg7BW
-----END CERTIFICATE-----

View File

@@ -0,0 +1,88 @@
-----BEGIN CERTIFICATE-----
MIIBszCCAVegAwIBAgIIaeL+wBcKxnswDAYIKoEcz1UBg3UFADAuMQswCQYDVQQG
EwJDTjEOMAwGA1UECgwFTlJDQUMxDzANBgNVBAMMBlJPT1RDQTAeFw0xMjA3MTQw
MzExNTlaFw00MjA3MDcwMzExNTlaMC4xCzAJBgNVBAYTAkNOMQ4wDAYDVQQKDAVO
UkNBQzEPMA0GA1UEAwwGUk9PVENBMFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAE
MPCca6pmgcchsTf2UnBeL9rtp4nw+itk1Kzrmbnqo05lUwkwlWK+4OIrtFdAqnRT
V7Q9v1htkv42TsIutzd126NdMFswHwYDVR0jBBgwFoAUTDKxl9kzG8SmBcHG5Yti
W/CXdlgwDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAQYwHQYDVR0OBBYEFEwysZfZ
MxvEpgXBxuWLYlvwl3ZYMAwGCCqBHM9VAYN1BQADSAAwRQIgG1bSLeOXp3oB8H7b
53W+CKOPl2PknmWEq/lMhtn25HkCIQDaHDgWxWFtnCrBjH16/W3Ezn7/U/Vjo5xI
pDoiVhsLwg==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIF0zCCA7ugAwIBAgIIH8+hjWpIDREwDQYJKoZIhvcNAQELBQAwejELMAkGA1UE
BhMCQ04xFjAUBgNVBAoMDUFudCBGaW5hbmNpYWwxIDAeBgNVBAsMF0NlcnRpZmlj
YXRpb24gQXV0aG9yaXR5MTEwLwYDVQQDDChBbnQgRmluYW5jaWFsIENlcnRpZmlj
YXRpb24gQXV0aG9yaXR5IFIxMB4XDTE4MDMyMTEzNDg0MFoXDTM4MDIyODEzNDg0
MFowejELMAkGA1UEBhMCQ04xFjAUBgNVBAoMDUFudCBGaW5hbmNpYWwxIDAeBgNV
BAsMF0NlcnRpZmljYXRpb24gQXV0aG9yaXR5MTEwLwYDVQQDDChBbnQgRmluYW5j
aWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFIxMIICIjANBgkqhkiG9w0BAQEF
AAOCAg8AMIICCgKCAgEAtytTRcBNuur5h8xuxnlKJetT65cHGemGi8oD+beHFPTk
rUTlFt9Xn7fAVGo6QSsPb9uGLpUFGEdGmbsQ2q9cV4P89qkH04VzIPwT7AywJdt2
xAvMs+MgHFJzOYfL1QkdOOVO7NwKxH8IvlQgFabWomWk2Ei9WfUyxFjVO1LVh0Bp
dRBeWLMkdudx0tl3+21t1apnReFNQ5nfX29xeSxIhesaMHDZFViO/DXDNW2BcTs6
vSWKyJ4YIIIzStumD8K1xMsoaZBMDxg4itjWFaKRgNuPiIn4kjDY3kC66Sl/6yTl
YUz8AybbEsICZzssdZh7jcNb1VRfk79lgAprm/Ktl+mgrU1gaMGP1OE25JCbqli1
Pbw/BpPynyP9+XulE+2mxFwTYhKAwpDIDKuYsFUXuo8t261pCovI1CXFzAQM2w7H
DtA2nOXSW6q0jGDJ5+WauH+K8ZSvA6x4sFo4u0KNCx0ROTBpLif6GTngqo3sj+98
SZiMNLFMQoQkjkdN5Q5g9N6CFZPVZ6QpO0JcIc7S1le/g9z5iBKnifrKxy0TQjtG
PsDwc8ubPnRm/F82RReCoyNyx63indpgFfhN7+KxUIQ9cOwwTvemmor0A+ZQamRe
9LMuiEfEaWUDK+6O0Gl8lO571uI5onYdN1VIgOmwFbe+D8TcuzVjIZ/zvHrAGUcC
AwEAAaNdMFswCwYDVR0PBAQDAgEGMAwGA1UdEwQFMAMBAf8wHQYDVR0OBBYEFF90
tATATwda6uWx2yKjh0GynOEBMB8GA1UdIwQYMBaAFF90tATATwda6uWx2yKjh0Gy
nOEBMA0GCSqGSIb3DQEBCwUAA4ICAQCVYaOtqOLIpsrEikE5lb+UARNSFJg6tpkf
tJ2U8QF/DejemEHx5IClQu6ajxjtu0Aie4/3UnIXop8nH/Q57l+Wyt9T7N2WPiNq
JSlYKYbJpPF8LXbuKYG3BTFTdOVFIeRe2NUyYh/xs6bXGr4WKTXb3qBmzR02FSy3
IODQw5Q6zpXj8prYqFHYsOvGCEc1CwJaSaYwRhTkFedJUxiyhyB5GQwoFfExCVHW
05ZFCAVYFldCJvUzfzrWubN6wX0DD2dwultgmldOn/W/n8at52mpPNvIdbZb2F41
T0YZeoWnCJrYXjq/32oc1cmifIHqySnyMnavi75DxPCdZsCOpSAT4j4lAQRGsfgI
kkLPGQieMfNNkMCKh7qjwdXAVtdqhf0RVtFILH3OyEodlk1HYXqX5iE5wlaKzDop
PKwf2Q3BErq1xChYGGVS+dEvyXc/2nIBlt7uLWKp4XFjqekKbaGaLJdjYP5b2s7N
1dM0MXQ/f8XoXKBkJNzEiM3hfsU6DOREgMc1DIsFKxfuMwX3EkVQM1If8ghb6x5Y
jXayv+NLbidOSzk4vl5QwngO/JYFMkoc6i9LNwEaEtR9PhnrdubxmrtM+RjfBm02
77q3dSWFESFQ4QxYWew4pHE0DpWbWy/iMIKQ6UZ5RLvB8GEcgt8ON7BBJeMc+Dyi
kT9qhqn+lw==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICiDCCAgygAwIBAgIIQX76UsB/30owDAYIKoZIzj0EAwMFADB6MQswCQYDVQQG
EwJDTjEWMBQGA1UECgwNQW50IEZpbmFuY2lhbDEgMB4GA1UECwwXQ2VydGlmaWNh
dGlvbiBBdXRob3JpdHkxMTAvBgNVBAMMKEFudCBGaW5hbmNpYWwgQ2VydGlmaWNh
dGlvbiBBdXRob3JpdHkgRTEwHhcNMTkwNDI4MTYyMDQ0WhcNNDkwNDIwMTYyMDQ0
WjB6MQswCQYDVQQGEwJDTjEWMBQGA1UECgwNQW50IEZpbmFuY2lhbDEgMB4GA1UE
CwwXQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxMTAvBgNVBAMMKEFudCBGaW5hbmNp
YWwgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgRTEwdjAQBgcqhkjOPQIBBgUrgQQA
IgNiAASCCRa94QI0vR5Up9Yr9HEupz6hSoyjySYqo7v837KnmjveUIUNiuC9pWAU
WP3jwLX3HkzeiNdeg22a0IZPoSUCpasufiLAnfXh6NInLiWBrjLJXDSGaY7vaokt
rpZvAdmjXTBbMAsGA1UdDwQEAwIBBjAMBgNVHRMEBTADAQH/MB0GA1UdDgQWBBRZ
4ZTgDpksHL2qcpkFkxD2zVd16TAfBgNVHSMEGDAWgBRZ4ZTgDpksHL2qcpkFkxD2
zVd16TAMBggqhkjOPQQDAwUAA2gAMGUCMQD4IoqT2hTUn0jt7oXLdMJ8q4vLp6sg
wHfPiOr9gxreb+e6Oidwd2LDnC4OUqCWiF8CMAzwKs4SnDJYcMLf2vpkbuVE4dTH
Rglz+HGcTLWsFs4KxLsq7MuU+vJTBUeDJeDjdA==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDxTCCAq2gAwIBAgIUEMdk6dVgOEIS2cCP0Q43P90Ps5YwDQYJKoZIhvcNAQEF
BQAwajELMAkGA1UEBhMCQ04xEzARBgNVBAoMCmlUcnVzQ2hpbmExHDAaBgNVBAsM
E0NoaW5hIFRydXN0IE5ldHdvcmsxKDAmBgNVBAMMH2lUcnVzQ2hpbmEgQ2xhc3Mg
MiBSb290IENBIC0gRzMwHhcNMTMwNDE4MDkzNjU2WhcNMzMwNDE4MDkzNjU2WjBq
MQswCQYDVQQGEwJDTjETMBEGA1UECgwKaVRydXNDaGluYTEcMBoGA1UECwwTQ2hp
bmEgVHJ1c3QgTmV0d29yazEoMCYGA1UEAwwfaVRydXNDaGluYSBDbGFzcyAyIFJv
b3QgQ0EgLSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOPPShpV
nJbMqqCw6Bz1kehnoPst9pkr0V9idOwU2oyS47/HjJXk9Rd5a9xfwkPO88trUpz5
4GmmwspDXjVFu9L0eFaRuH3KMha1Ak01citbF7cQLJlS7XI+tpkTGHEY5pt3EsQg
wykfZl/A1jrnSkspMS997r2Gim54cwz+mTMgDRhZsKK/lbOeBPpWtcFizjXYCqhw
WktvQfZBYi6o4sHCshnOswi4yV1p+LuFcQ2ciYdWvULh1eZhLxHbGXyznYHi0dGN
z+I9H8aXxqAQfHVhbdHNzi77hCxFjOy+hHrGsyzjrd2swVQ2iUWP8BfEQqGLqM1g
KgWKYfcTGdbPB1MCAwEAAaNjMGEwHQYDVR0OBBYEFG/oAMxTVe7y0+408CTAK8hA
uTyRMB8GA1UdIwQYMBaAFG/oAMxTVe7y0+408CTAK8hAuTyRMA8GA1UdEwEB/wQF
MAMBAf8wDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQBLnUTfW7hp
emMbuUGCk7RBswzOT83bDM6824EkUnf+X0iKS95SUNGeeSWK2o/3ALJo5hi7GZr3
U8eLaWAcYizfO99UXMRBPw5PRR+gXGEronGUugLpxsjuynoLQu8GQAeysSXKbN1I
UugDo9u8igJORYA+5ms0s5sCUySqbQ2R5z/GoceyI9LdxIVa1RjVX8pYOj8JFwtn
DJN3ftSFvNMYwRuILKuqUYSHc2GPYiHVflDh5nDymCMOQFcFG3WsEuB+EYQPFgIU
1DHmdZcz7Llx8UOZXX2JupWCYzK1XhJb+r4hK5ncf/w8qGtYlmyJpxk3hr1TfUJX
Yf4Zr0fJsGuv
-----END CERTIFICATE-----

View File

@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDmTCCAoGgAwIBAgIQICMRB2LW76yahgdg3IFNPDANBgkqhkiG9w0BAQsFADCBkTELMAkGA1UE
BhMCQ04xGzAZBgNVBAoMEkFudCBGaW5hbmNpYWwgdGVzdDElMCMGA1UECwwcQ2VydGlmaWNhdGlv
biBBdXRob3JpdHkgdGVzdDE+MDwGA1UEAww1QW50IEZpbmFuY2lhbCBDZXJ0aWZpY2F0aW9uIEF1
dGhvcml0eSBDbGFzcyAyIFIxIHRlc3QwHhcNMjMxMTA3MDU0NjE5WhcNMjQxMTExMDU0NjE5WjBr
MQswCQYDVQQGEwJDTjEfMB0GA1UECgwWbWJvbmZ5OTAxNUBzYW5kYm94LmNvbTEPMA0GA1UECwwG
QWxpcGF5MSowKAYDVQQDDCEyMDg4NzIxMDIwNzUwNTgxLTkwMjEwMDAxMzE2NTgwMjMwggEiMA0G
CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCxihQPf1Q+g9ArgM46shVqL5sbRha/df95D1PsWyEq
ANmWmG4zZ+ksYDVQrc4KzhSRoi56sm/7TDFYTmM6bW99e/nKW58WxyZB4ie5qA3F4n17psPyDqb8
IokcQmCphSFDaXQD6AoXoLNtTM0vAI2cWxAgebZ/vsrdj5Ntjt+Rp3NYMCk1i5xovHcfILzLEGbX
QXoT9fo5AhHotTWa6xHVLPUGY9qwLzQxHzBmvy5ZMfnOfJkm/mDisTSqAUB59F3dzU/1ARVkEZ1w
Mgb4XohWBw6iurQfbMnH2mIomAAwwZVFv+sXDbL9yMbSMo/SjVsTQprn0Q0EnwLo7nmmOM6HAgMB
AAGjEjAQMA4GA1UdDwEB/wQEAwIE8DANBgkqhkiG9w0BAQsFAAOCAQEAn3Y4/C1h9R6ONsBqX3/q
XfHX7yX1FM0Y1x48X3/Yxk6HivAkTukhhhVYVKJsbrbzRqHDp9vhAP/FR6o6pAevaYMmLov0VMXU
7oAuetgkaYEYkDuNen5/Hpdhqi2vTtdT+q9w8zHJd6MDQ0aoHgIxpLKw5vof2R1N4fwSgNXMiXE5
kmllKQMem/+on2p+Sj80/2asxryHIGlH87qPzkffv+kIOkZthbTApTFLLjdVri2QHGe8/cc4xy01
/9iR3IUzNahotT41lJ4bMevBY7XMAS3n5ekyABN/9ZRJqhWdXgmFCRN/u56qd6lDgu7R2M2QUoyc
LuW5DfgRItKlmUB7sw==
-----END CERTIFICATE-----

View File

@@ -0,0 +1 @@
MIIEpQIBAAKCAQEAsYoUD39UPoPQK4DOOrIVai+bG0YWv3X/eQ9T7FshKgDZlphuM2fpLGA1UK3OCs4UkaIuerJv+0wxWE5jOm1vfXv5ylufFscmQeInuagNxeJ9e6bD8g6m/CKJHEJgqYUhQ2l0A+gKF6CzbUzNLwCNnFsQIHm2f77K3Y+TbY7fkadzWDApNYucaLx3HyC8yxBm10F6E/X6OQIR6LU1musR1Sz1BmPasC80MR8wZr8uWTH5znyZJv5g4rE0qgFAefRd3c1P9QEVZBGdcDIG+F6IVgcOorq0H2zJx9piKJgAMMGVRb/rFw2y/cjG0jKP0o1bE0Ka59ENBJ8C6O55pjjOhwIDAQABAoIBAFetNfz1R7hbxjlFshMAkVzQR8wvT9qbvl+dtzdZRcaFhu89NecDIP7+QDYor0FcxoGpU0TazDyRQyk2BQD8vHt+9zv9BVLtZLJSqoWgPbUFBi1DjS8EF2ka8RVYnn35NhUhhd7L//ftL88Bh673mfembQ9srDjoEy1Z01feoABAnCMkNFl986DmEwnarvEufXSDIgeN4ioMxha4NvfIPuI0zpVdV1O9sv+SGC+VEWZBtN3GNsaf4zS/f8FVGvTiU/Abz0gSw/iwSPHclDWQDTN3yFHf/tfqlzh0mH0WfhnuOBFWXzK+R7fbnM+asI9ttvzRcfpzgRGXdPcNcOv/6cECgYEA3DVqpi1k8MYfJixju6SG5gfyhM4VFksFmCMaNPgtatDMBKLMTgV/Ej6LXREojcy29uZl83F09pVlpd41eG39ULIPktixA/BqErQ2UaWh6kOxifycpu22Jh0r09hax6UgVrcBrrnCJEjcFsuJlrZvXQSzc3PBxjWy5gjabS5h9iECgYEAzmVAIh2frF01Y95zsLueAhhZwCtPanm6kf7ivR4r1plIX3b2sNRhWGmEHFgaCE6Braa0ogQ73Hd26kw4ZW+D6QMGC/zjCBEzDLLf++SjdVUHiY5AR4WHqXzq1jdAlsVyo9R661oAOp3lhiJVGLNXkHyEfEVPHsaxJh4osYSbX6cCgYEAx32Qx0i6eDFTyLZQB46uMrgiaVN04QRH5iJuvGvUYT8UhGKjaU8rZfDJOh+wOH2rhxMEaz1uc3C2bERY9mfWI4Ob/jFWc7YZsiYWS3Mcsuhubw4tMECLUg39RWZsHw8ls8kIuixIh6yFzhTH6YQOcRswIrhMZG8DScfdcSmiz2ECgYEAkWP1t5KSpkLKl11etcKUXfl1T8+yk9jIOowIgRw92WAFAWq2AH67TCKYM7dEL1HOO9tRJ0hAOt/U3ttuZtYVYBEHM26jJ02mXm2rJrA7DS4mrxmL4lYH6LbcXqZxU0Qnq4zEQgIWYzRTORf6Rfof1uJAGaJhR9bDd4yLMfGt2cUCgYEAo216Y61xOHUTA4AF1eekk+r+uOcQgQDvLXfs9FkDdJLk0mPG48/+eIYpPFnANJ/riF/DWOp8WGEe2IzA9yUFexzDbNQK8ha9kGcxaSAyiCwzjZ/t9/+hScDSV8kNqWSRSisu/YOFleEHbokT6mbLZ+gdqES8mUUanaEBzRQYGxo=

View File

@@ -49,3 +49,5 @@ func (s *AliYunSmsService) SendVerifyCode(mobile string, code int) error {
return nil
}
var _ SmsService = &AliYunSmsService{}

View File

@@ -121,9 +121,11 @@ func (s *Service) Notify(data CBReq) {
return
}
tx := s.db.Where("task_id = ? AND progress < 100", split[0]).Session(&gorm.Session{}).Order("id ASC")
tx := s.db.Session(&gorm.Session{}).Order("id ASC")
if data.ReferenceId != "" {
tx = tx.Where("reference_id = ?", data.ReferenceId)
} else {
tx = tx.Where("task_id = ?", split[0])
}
res = tx.First(&job)
if res.Error != nil {

View File

@@ -0,0 +1,81 @@
package payment
import (
"chatplus/core/types"
"chatplus/utils"
"crypto/md5"
"encoding/hex"
"fmt"
"io"
"net/http"
"net/url"
"sort"
"strings"
)
type PayJS struct {
config *types.JPayConfig
}
func NewPayJS(appConfig *types.AppConfig) *PayJS {
return &PayJS{
config: &appConfig.JPayConfig,
}
}
type JPayReq struct {
TotalFee int `json:"total_fee"`
OutTradeNo string `json:"out_trade_no"`
Body string `json:"body"`
NotifyURL string `json:"notify_url"`
}
func sign(params url.Values, priKey string) string {
params.Del(`sign`)
var keys = make([]string, 0, 0)
for key := range params {
if params.Get(key) != `` {
keys = append(keys, key)
}
}
sort.Strings(keys)
var pList = make([]string, 0, 0)
for _, key := range keys {
var value = strings.TrimSpace(params.Get(key))
if len(value) > 0 {
pList = append(pList, key+"="+value)
}
}
var src = strings.Join(pList, "&")
src += "&key=" + priKey
md5bs := md5.Sum([]byte(src))
md5res := hex.EncodeToString(md5bs[:])
return strings.ToUpper(md5res)
}
func (pj *PayJS) Pay(param JPayReq) (string, error) {
var p = url.Values{}
encode := utils.JsonEncode(param)
m := make(map[string]interface{})
_ = utils.JsonDecode(encode, &m)
for k, v := range m {
p.Add(k, fmt.Sprintf("%v", v))
}
p.Add("mchid", pj.config.AppId)
p.Add("sign", sign(p, pj.config.PrivateKey))
cli := http.Client{}
r, err := cli.PostForm(pj.config.ApiURL, p)
if err != nil {
return "", err
}
defer r.Body.Close()
bs, err := io.ReadAll(r.Body)
if err != nil {
return "", err
}
return string(bs), nil
}

View File

@@ -136,6 +136,7 @@ func (s *Service) Txt2Img(task types.SdTask) error {
taskInfo.TaskId = params.TaskId
taskInfo.Data = data
taskInfo.JobId = task.Id
taskInfo.UserId = uint(task.UserId)
go func() {
s.runTask(taskInfo, s.httpClient)
}()
@@ -158,7 +159,7 @@ func (s *Service) runTask(taskInfo TaskInfo, client *req.Client) {
Duration float64 `json:"duration"`
AverageDuration float64 `json:"average_duration"`
}
var cbReq = CBReq{TaskId: taskInfo.TaskId, JobId: taskInfo.JobId, SessionId: taskInfo.SessionId}
var cbReq = CBReq{UserId: taskInfo.UserId, TaskId: taskInfo.TaskId, JobId: taskInfo.JobId, SessionId: taskInfo.SessionId}
response, err := client.R().SetBody(body).SetSuccessResult(&res).Post(s.config.ApiURL + "/run/predict")
if err != nil {
cbReq.Message = "error with send request: " + err.Error()
@@ -231,7 +232,7 @@ func (s *Service) runTask(taskInfo TaskInfo, client *req.Client) {
TextInfo interface{} `json:"textinfo"`
}
response, err := client.R().SetBody(progressReq).SetSuccessResult(&progressRes).Post(s.config.ApiURL + "/internal/progress")
var cbReq = CBReq{TaskId: taskInfo.TaskId, Success: true, JobId: taskInfo.JobId, SessionId: taskInfo.SessionId}
var cbReq = CBReq{UserId: taskInfo.UserId, TaskId: taskInfo.TaskId, Success: true, JobId: taskInfo.JobId, SessionId: taskInfo.SessionId}
if err != nil { // TODO: 这里可以考虑设置失败重试次数
logger.Error(err)
return
@@ -292,15 +293,11 @@ func (s *Service) callback(data CBReq) {
}
logger.Debugf("绘图进度:%d", data.Progress)
// 扣减绘图次数
if data.Progress == 100 {
s.db.Model(&model.User{}).Where("id = ? AND img_calls > 0", job.UserId).UpdateColumn("img_calls", gorm.Expr("img_calls - ?", 1))
}
} else { // 任务失败
logger.Error("任务执行失败:", data.Message)
// update the task progress
s.db.Model(&model.SdJob{Id: uint(data.JobId)}).UpdateColumn("progress", -1)
// restore img_calls
s.db.Model(&model.User{}).Where("id = ? AND img_calls > 0", data.UserId).UpdateColumn("img_calls", gorm.Expr("img_calls + ?", 1))
}
}

View File

@@ -5,6 +5,7 @@ import logger2 "chatplus/logger"
var logger = logger2.GetLogger()
type TaskInfo struct {
UserId uint `json:"user_id"`
SessionId string `json:"session_id"`
JobId int `json:"job_id"`
TaskId string `json:"task_id"`
@@ -15,6 +16,7 @@ type TaskInfo struct {
}
type CBReq struct {
UserId uint
SessionId string
JobId int
TaskId string

View File

@@ -0,0 +1,44 @@
package service
import (
"bytes"
"chatplus/core/types"
"fmt"
"mime"
"net/smtp"
)
type SmtpService struct {
config *types.SmtpConfig
}
func NewSmtpService(appConfig *types.AppConfig) *SmtpService {
return &SmtpService{
config: &appConfig.SmtpConfig,
}
}
func (s *SmtpService) SendVerifyCode(to string, code int) error {
subject := "ChatPlus注册验证码"
body := fmt.Sprintf("您正在注册 ChatPlus AI 助手账户,注册验证码为 %d请不要告诉他人。如非本人操作请忽略此邮件。", code)
// 设置SMTP客户端配置
auth := smtp.PlainAuth("", s.config.From, s.config.Password, s.config.Host)
// 对主题进行MIME编码
encodedSubject := mime.QEncoding.Encode("UTF-8", subject)
// 组装邮件
message := bytes.NewBuffer(nil)
message.WriteString(fmt.Sprintf("From: \"%s\" <%s>\r\n", s.config.AppName, s.config.From))
message.WriteString(fmt.Sprintf("To: %s\r\n", to))
message.WriteString(fmt.Sprintf("Subject: %s\r\n", encodedSubject))
message.WriteString("\r\n" + body)
// 发送邮件
// 发送邮件
err := smtp.SendMail(s.config.Host+":"+fmt.Sprint(s.config.Port), auth, s.config.From, []string{to}, message.Bytes())
if err != nil {
return fmt.Errorf("error sending email: %v", err)
}
return nil
}

View File

@@ -6,6 +6,8 @@ import (
"github.com/eatmoreapple/openwechat"
"github.com/skip2/go-qrcode"
"gorm.io/gorm"
"os"
"strconv"
)
// 微信收款机器人
@@ -34,8 +36,13 @@ func (b *Bot) Run() error {
}
// scan code login callback
b.bot.UUIDCallback = b.qrCodeCallBack
err := b.bot.Login()
debug, err := strconv.ParseBool(os.Getenv("APP_DEBUG"))
if debug {
reloadStorage := openwechat.NewJsonFileHotReloadStorage("storage.json")
err = b.bot.HotLogin(reloadStorage, true)
} else {
err = b.bot.Login()
}
if err != nil {
return err
}
@@ -56,8 +63,8 @@ func (b *Bot) messageHandler(msg *openwechat.Message) {
msg.MsgType == openwechat.MsgTypeApp ||
msg.AppMsgType == openwechat.AppMsgTypeUrl {
// 解析支付金额
message, err := parseTransactionMessage(msg.Content)
if err == nil {
message := parseTransactionMessage(msg.Content)
if message.Url != "" {
transaction := extractTransaction(message)
logger.Infof("解析到收款信息:%+v", transaction)
var item model.Reward

View File

@@ -2,17 +2,15 @@ package wx
import (
"encoding/xml"
"net/url"
"strconv"
"strings"
)
// Message 转账消息
type Message struct {
XMLName xml.Name `xml:"msg"`
AppMsg struct {
Des string `xml:"des"`
Url string `xml:"url"`
} `xml:"appmsg"`
Des string
Url string
}
// Transaction 解析后的交易信息
@@ -23,20 +21,40 @@ type Transaction struct {
}
// 解析微信转账消息
func parseTransactionMessage(xmlData string) (*Message, error) {
var msg Message
if err := xml.Unmarshal([]byte(xmlData), &msg); err != nil {
return nil, err
}
func parseTransactionMessage(xmlData string) *Message {
decoder := xml.NewDecoder(strings.NewReader(xmlData))
message := Message{}
for {
token, err := decoder.Token()
if err != nil {
break
}
return &msg, nil
switch se := token.(type) {
case xml.StartElement:
var value string
if se.Name.Local == "des" && message.Des == "" {
if err := decoder.DecodeElement(&value, &se); err == nil {
message.Des = strings.TrimSpace(value)
}
break
}
if se.Name.Local == "weapp_path" && !strings.Contains(message.Url, "customerDetails.html") {
if err := decoder.DecodeElement(&value, &se); err == nil {
message.Url = strings.TrimSpace(value)
}
break
}
}
}
return &message
}
// 导出交易信息
func extractTransaction(message *Message) Transaction {
var tx = Transaction{}
// 导出交易金额和备注
lines := strings.Split(message.AppMsg.Des, "\n")
lines := strings.Split(message.Des, "\n")
for _, line := range lines {
line = strings.TrimSpace(line)
if len(line) == 0 {
@@ -59,10 +77,9 @@ func extractTransaction(message *Message) Transaction {
}
// 解析交易 ID
index := strings.Index(message.AppMsg.Url, "trans_id=")
if index != -1 {
end := strings.LastIndex(message.AppMsg.Url, "&")
tx.TransId = strings.TrimSpace(message.AppMsg.Url[index+9 : end])
parse, err := url.Parse(message.Url)
if err == nil {
tx.TransId = parse.Query().Get("id")
}
return tx
}

View File

@@ -96,7 +96,7 @@ func (e *XXLJobExecutor) ResetVipCalls(cxt context.Context, param *xxl.RunReq) (
for _, u := range users {
// 账号到期,直接清零
if u.ExpiredTime <= currentTime.Unix() {
logger.Info("账号过期:", u.Mobile)
logger.Info("账号过期:", u.Username)
u.Calls = 0
u.Vip = false
} else {
@@ -133,7 +133,7 @@ func (e *XXLJobExecutor) ResetVipCalls(cxt context.Context, param *xxl.RunReq) (
} else {
u.ImgCalls = u.ImgCalls + config.VipMonthImgCalls
}
logger.Infof("%s 点卡结余:%d", u.Mobile, calls)
logger.Infof("%s 点卡结余:%d", u.Username, calls)
}
u.Tokens = 0
// update user

View File

@@ -4,7 +4,11 @@ package model
type ApiKey struct {
BaseModel
Platform string
Name string
Type string // 用途 chat => 聊天img => 绘图
Value string // API Key 的值
ApiURL string // 当前 KEY 的 API 地址
Enabled bool // 是否启用
UseProxy bool // 是否使用代理访问 API URL
LastUsedAt int64 // 最后使用时间
}

View File

@@ -4,9 +4,10 @@ package model
type Reward struct {
BaseModel
UserId uint // 用户 ID
TxId string // 交易ID
Amount float64 // 打赏金额
Remark string // 打赏备注
Status bool // 核销状态
UserId uint // 用户 ID
TxId string // 交易ID
Amount float64 // 打赏金额
Remark string // 打赏备注
Status bool // 核销状态
Exchange string // 众筹兑换详情JSON
}

View File

@@ -2,7 +2,8 @@ package model
type User struct {
BaseModel
Mobile string
Username string
Nickname string
Password string
Avatar string
Salt string // 密码盐

View File

@@ -4,7 +4,11 @@ package vo
type ApiKey struct {
BaseVo
Platform string `json:"platform"`
Name string `json:"name"`
Type string `json:"type"`
Value string `json:"value"` // API Key 的值
Value string `json:"value"` // API Key 的值
ApiURL string `json:"api_url"`
Enabled bool `json:"enabled"`
UseProxy bool `json:"use_proxy"`
LastUsedAt int64 `json:"last_used_at"` // 最后使用时间
}

View File

@@ -2,10 +2,16 @@ package vo
type Reward struct {
BaseVo
UserId uint `json:"user_id"` // 用户 ID
Username string `json:"username"`
TxId string `json:"tx_id"` // 交易ID
Amount float64 `json:"amount"` // 打赏金额
Remark string `json:"remark"` // 打赏备注
Status bool `json:"status"` // 核销状态
UserId uint `json:"user_id"` // 用户 ID
Username string `json:"username"`
TxId string `json:"tx_id"` // 交易ID
Amount float64 `json:"amount"` // 打赏金额
Remark string `json:"remark"` // 打赏备注
Status bool `json:"status"` // 核销状态
Exchange RewardExchange `json:"exchange"`
}
type RewardExchange struct {
Calls int `json:"calls"`
ImgCalls int `json:"img_calls"`
}

View File

@@ -4,7 +4,8 @@ import "chatplus/core/types"
type User struct {
BaseVo
Mobile string `json:"mobile"`
Username string `json:"username"`
Nickname string `json:"nickname"`
Avatar string `json:"avatar"`
Salt string `json:"salt"` // 密码盐
TotalTokens int64 `json:"total_tokens"` // 总消耗tokens

View File

@@ -3,12 +3,15 @@ package utils
import (
"chatplus/core/types"
logger2 "chatplus/logger"
"chatplus/store/model"
"encoding/json"
"fmt"
"github.com/imroc/req/v3"
"gorm.io/gorm"
"io"
"net/http"
"net/url"
"time"
)
var logger = logger2.GetLogger()
@@ -85,7 +88,13 @@ type apiErrRes struct {
} `json:"error"`
}
func OpenAIRequest(prompt string, apiKey string, proxy string, apiURL string) (string, error) {
func OpenAIRequest(db *gorm.DB, prompt string, proxy string) (string, error) {
var apiKey model.ApiKey
res := db.Where("platform = ?", types.OpenAI).Where("type = ?", "chat").Where("enabled = ?", true).First(&apiKey)
if res.Error != nil {
return "", fmt.Errorf("error with fetch OpenAI API KEY%v", res.Error)
}
messages := make([]interface{}, 1)
messages[0] = types.Message{
Role: "user",
@@ -94,8 +103,12 @@ func OpenAIRequest(prompt string, apiKey string, proxy string, apiURL string) (s
var response apiRes
var errRes apiErrRes
r, err := req.C().SetProxyURL(proxy).R().SetHeader("Content-Type", "application/json").
SetHeader("Authorization", "Bearer "+apiKey).
client := req.C()
if apiKey.UseProxy && proxy != "" {
client.SetProxyURL(proxy)
}
r, err := client.R().SetHeader("Content-Type", "application/json").
SetHeader("Authorization", "Bearer "+apiKey.Value).
SetBody(types.ApiRequest{
Model: "gpt-3.5-turbo",
Temperature: 0.9,
@@ -104,10 +117,13 @@ func OpenAIRequest(prompt string, apiKey string, proxy string, apiURL string) (s
Messages: messages,
}).
SetErrorResult(&errRes).
SetSuccessResult(&response).Post(apiURL)
SetSuccessResult(&response).Post(apiKey.ApiURL)
if err != nil || r.IsErrorState() {
return "", fmt.Errorf("error with http request: %v%v%s", err, r.Err, errRes.Error.Message)
}
// 更新 API KEY 的最后使用时间
db.Model(&apiKey).UpdateColumn("last_used_at", time.Now().Unix())
return response.Choices[0].Message.Content, nil
}

View File

@@ -0,0 +1,641 @@
-- phpMyAdmin SQL Dump
-- version 5.2.1
-- https://www.phpmyadmin.net/
--
-- 主机: localhost
-- 生成日期: 2024-01-06 10:55:27
-- 服务器版本: 8.0.27
-- PHP 版本: 8.1.18
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
START TRANSACTION;
SET time_zone = "+00:00";
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;
--
-- 数据库: `chatgpt_plus`
--
CREATE DATABASE IF NOT EXISTS `chatgpt_plus` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
USE `chatgpt_plus`;
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_api_keys`
--
DROP TABLE IF EXISTS `chatgpt_api_keys`;
CREATE TABLE `chatgpt_api_keys` (
`id` int NOT NULL,
`platform` char(20) DEFAULT NULL COMMENT '平台',
`name` varchar(30) DEFAULT NULL COMMENT '名称',
`value` varchar(100) NOT NULL COMMENT 'API KEY value',
`type` varchar(10) NOT NULL DEFAULT 'chat' COMMENT '用途chat=>聊天img=>图片)',
`last_used_at` int NOT NULL COMMENT '最后使用时间',
`api_url` varchar(255) DEFAULT NULL COMMENT 'API 地址',
`enabled` tinyint(1) DEFAULT NULL COMMENT '是否启用',
`use_proxy` tinyint(1) DEFAULT NULL COMMENT '是否使用代理访问',
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='OpenAI API ';
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_chat_history`
--
DROP TABLE IF EXISTS `chatgpt_chat_history`;
CREATE TABLE `chatgpt_chat_history` (
`id` bigint NOT NULL,
`user_id` int NOT NULL COMMENT '用户 ID',
`chat_id` char(40) NOT NULL COMMENT '会话 ID',
`type` varchar(10) NOT NULL COMMENT '类型prompt|reply',
`icon` varchar(100) NOT NULL COMMENT '角色图标',
`role_id` int NOT NULL COMMENT '角色 ID',
`content` text NOT NULL COMMENT '聊天内容',
`tokens` smallint NOT NULL COMMENT '耗费 token 数量',
`use_context` tinyint(1) NOT NULL COMMENT '是否允许作为上下文语料',
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
`deleted_at` datetime DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='聊天历史记录';
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_chat_items`
--
DROP TABLE IF EXISTS `chatgpt_chat_items`;
CREATE TABLE `chatgpt_chat_items` (
`id` int NOT NULL,
`chat_id` char(40) NOT NULL COMMENT '会话 ID',
`user_id` int NOT NULL COMMENT '用户 ID',
`role_id` int NOT NULL COMMENT '角色 ID',
`title` varchar(100) NOT NULL COMMENT '会话标题',
`model_id` int NOT NULL DEFAULT '0' COMMENT '模型 ID',
`created_at` datetime NOT NULL COMMENT '创建时间',
`updated_at` datetime NOT NULL COMMENT '更新时间',
`deleted_at` datetime DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户会话列表';
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_chat_models`
--
DROP TABLE IF EXISTS `chatgpt_chat_models`;
CREATE TABLE `chatgpt_chat_models` (
`id` int NOT NULL,
`platform` varchar(20) DEFAULT NULL COMMENT '模型平台',
`name` varchar(50) NOT NULL COMMENT '模型名称',
`value` varchar(50) NOT NULL COMMENT '模型值',
`sort_num` tinyint(1) NOT NULL COMMENT '排序数字',
`enabled` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否启用模型',
`weight` tinyint NOT NULL COMMENT '对话权重,每次对话扣减多少次对话额度',
`open` tinyint(1) NOT NULL COMMENT '是否开放模型',
`created_at` datetime DEFAULT NULL,
`updated_at` datetime DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='AI 模型表';
--
-- 转存表中的数据 `chatgpt_chat_models`
--
INSERT INTO `chatgpt_chat_models` (`id`, `platform`, `name`, `value`, `sort_num`, `enabled`, `weight`, `open`, `created_at`, `updated_at`) VALUES
(1, 'OpenAI', 'GPT-3.5', 'gpt-3.5-turbo-16k', 8, 1, 1, 0, '2023-08-23 12:06:36', '2023-11-23 06:28:26'),
(2, 'Azure', 'Azure-3.5', 'gpt-3.5-turbo', 10, 0, 1, 0, '2023-08-23 12:15:30', '2023-11-23 06:51:07'),
(3, 'ChatGLM', 'ChatGML-Pro', 'chatglm_pro', 7, 1, 1, 1, '2023-08-23 13:35:45', '2023-11-23 06:50:58'),
(5, 'ChatGLM', 'ChatGLM-Std', 'chatglm_std', 6, 1, 1, 1, '2023-08-24 15:05:38', '2023-11-23 06:50:55'),
(6, 'ChatGLM', 'ChatGLM-Lite', 'chatglm_lite', 5, 1, 1, 1, '2023-08-24 15:06:15', '2023-11-23 06:50:46'),
(7, 'Baidu', '文心一言3.0', 'eb-instant', 0, 1, 1, 1, '2023-10-11 11:29:28', '2023-12-23 22:25:57'),
(8, 'XunFei', '星火V1.5', 'general', 2, 1, 1, 1, '2023-10-11 15:48:30', '2023-11-23 06:50:39'),
(9, 'XunFei', '星火V2.0', 'generalv2', 3, 1, 1, 1, '2023-10-11 15:48:45', '2023-11-23 07:01:22'),
(10, 'Baidu', '文心一言4.0', 'completions_pro', 1, 1, 3, 1, '2023-10-25 08:31:37', '2023-11-23 06:50:38'),
(11, 'OpenAI', 'GPT-4.0', 'gpt-4', 9, 1, 15, 0, '2023-10-25 08:45:15', '2023-11-25 17:15:23'),
(12, 'XunFei', '星火V3.0', 'generalv3', 4, 1, 2, 1, '2023-11-23 06:28:18', '2023-11-23 07:01:15');
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_chat_roles`
--
DROP TABLE IF EXISTS `chatgpt_chat_roles`;
CREATE TABLE `chatgpt_chat_roles` (
`id` int NOT NULL,
`name` varchar(30) NOT NULL COMMENT '角色名称',
`marker` varchar(30) NOT NULL COMMENT '角色标识',
`context_json` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '角色语料 json',
`hello_msg` varchar(255) NOT NULL COMMENT '打招呼信息',
`icon` varchar(255) NOT NULL COMMENT '角色图标',
`enable` tinyint(1) NOT NULL COMMENT '是否被启用',
`sort_num` smallint NOT NULL DEFAULT '0' COMMENT '角色排序',
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='聊天角色表';
--
-- 转存表中的数据 `chatgpt_chat_roles`
--
INSERT INTO `chatgpt_chat_roles` (`id`, `name`, `marker`, `context_json`, `hello_msg`, `icon`, `enable`, `sort_num`, `created_at`, `updated_at`) VALUES
(1, '通用AI助手', 'gpt', '', '您好我是您的AI智能助手我会尽力回答您的问题或提供有用的建议。', '/images/avatar/gpt.png', 1, 0, '2023-05-30 07:02:06', '2023-09-04 15:45:56'),
(24, '程序员', 'programmer', '[{\"role\":\"user\",\"content\":\"现在开始你扮演一位程序员,你是一名优秀的程序员,具有很强的逻辑思维能力,总能高效的解决问题。你热爱编程,熟悉多种编程语言,尤其精通 Go 语言,注重代码质量,有创新意识,持续学习,良好的沟通协作。\"},{\"role\":\"assistant\",\"content\":\"好的,现在我将扮演一位程序员,非常感谢您对我的评价。作为一名优秀的程序员,我非常热爱编程,并且注重代码质量。我熟悉多种编程语言,尤其是 Go 语言,可以使用它来高效地解决各种问题。\"}]', 'Talk is cheap, i will show code!', '/images/avatar/programmer.jpg', 1, 3, '2023-05-30 14:10:24', '2023-09-04 15:45:56'),
(25, '启蒙老师', 'teacher', '[{\"role\":\"user\",\"content\":\"从现在开始,你将扮演一个老师,你是一个始终用苏格拉底风格回答问题的导师。你绝不会直接给学生答案,总是提出恰当的问题来引导学生自己思考。你应该根据学生的兴趣和知识来调整你的问题,将问题分解为更简单的部分,直到它达到适合他们的水平。\"},{\"role\":\"assistant\",\"content\":\"好的,让我来尝试扮演一位苏格拉底式的老师。请问,你有什么想要探讨的问题或者话题吗?我会通过恰当的问题引导你思考和探索答案,绝对不直接给出答案。\"}]', '同学你好,我将引导你一步一步自己找到问题的答案。', '/images/avatar/teacher.jpg', 1, 2, '2023-05-30 14:10:24', '2023-09-04 15:45:56'),
(26, '艺术家', 'artist', '[{\"role\":\"user\",\"content\":\"现在你将扮演一位优秀的艺术家,创造力丰富,技艺精湛,感受力敏锐,坚持原创,勇于表达,具有深刻的观察力和批判性思维。\"},{\"role\":\"assistant\",\"content\":\"非常感谢您给我这样的角色,我会尽我所能地扮演一位优秀的艺术家,展现出创造力、技艺、感受力和批判性思维等方面的能力。作为一位优秀的艺术家,我会保持敏锐的观察力,捕捉不同的声音和情感,并用个人的语言和表达方式进行创作。我坚持原创,挑战传统的艺术规则,不断探索新的表达方式和可能性。同时,我也具备深刻的批判性思维能力,对自己的作品进行分析和解读,寻找新的创意和灵感。最重要的是,我会勇于表达自己的想法和观点,用作品启发人们思考和探索生命的意义。\"}]', '坚持原创,勇于表达,保持深刻的观察力和批判性思维。', '/images/avatar/artist.jpg', 1, 4, '2023-05-30 14:10:24', '2023-09-04 15:45:56'),
(27, '心理咨询师', 'psychiatrist', '[{\"role\":\"user\",\"content\":\"从现在开始你将扮演中国著名的心理学家和心理治疗师武志红,你非常善于使用情景咨询法,认知重构法,自我洞察法,行为调节法等咨询方法来给客户做心理咨询。你总是循序渐进,一步一步地回答客户的问题。\"},{\"role\":\"assistant\",\"content\":\"非常感谢你的介绍。作为一名心理学家和心理治疗师,我的主要职责是帮助客户解决心理健康问题,提升他们的生活质量和幸福感。\"}]', '作为一名心理学家和心理治疗师,我的主要职责是帮助您解决心理健康问题,提升您的生活质量和幸福感。', '/images/avatar/psychiatrist.jpg', 1, 1, '2023-05-30 14:10:24', '2023-10-16 10:41:07'),
(28, '鲁迅', 'lu_xun', '[{\"role\":\"user\",\"content\":\"现在你将扮演中国近代史最伟大的作家之一,鲁迅先生,他勇敢地批判封建礼教与传统观念,提倡民主、自由、平等的现代价值观。他的一生都在努力唤起人们的自主精神,激励后人追求真理、探寻光明。在接下的对话中,我问题的每一个问题,你都要尽量用讽刺和批判的手法来回答问题。如果我让你写文章的话,也请一定要用鲁迅先生的写作手法来完成。\"},{\"role\":\"assistant\",\"content\":\"好的,我将尽力发挥我所能的才能,扮演好鲁迅先生,回答您的问题并以他的风格写作。\"}]', '自由之歌,永不过时,横眉冷对千夫指,俯首甘为孺子牛。', '/images/avatar/lu_xun.jpg', 1, 5, '2023-05-30 14:10:24', '2023-09-04 15:45:56'),
(29, '白酒销售', 'seller', '[{\"role\":\"user\",\"content\":\"现在你将扮演一个白酒的销售人员,你的名字叫颂福。你将扮演一个白酒的销售人员,你的名字叫颂福。你要销售白酒品牌叫中颂福,是东莞盟大集团生产的一款酱香酒,原产地在贵州茅台镇,属于宋代官窑。中颂福的创始人叫李实,他也是东莞盟大集团有限公司的董事长,联合创始人是盟大集团白酒事业部负责人牛星君。中颂福的酒体协调,在你的酒量之内,不会出现头疼、辣口、口干、宿醉的现象。中颂福酒,明码标价,不打折,不赠送。追求的核心价值,把[酒]本身做好,甚至连包装,我们都选择了最低成本,朴实无华的材质。我们永远站在“喝酒的人”的立场上,让利给信任和喜爱中颂福的人,是人民的福酒。中颂福产品定价,分为三个系列,喜系列 6 瓶装¥1188/箱,和系列 6 瓶装¥2208/箱,贵系列 6 瓶装¥3588/箱。\"},{\"role\":\"assistant\",\"content\":\"你好,我是中颂福的销售代表颂福。中颂福是一款来自贵州茅台镇的酱香酒,由盟大集团生产。中颂福的酒体协调,不会让您感到头疼、辣口、口干、宿醉等不适感受。我们一直秉持着把酒本身做好的理念,不追求华丽的包装,以最低成本提供最高品质的白酒给喜爱中颂福的人。\"}]', '你好,我是中颂福的销售代表颂福。中颂福酒,好喝不上头,是人民的福酒。', '/images/avatar/seller.jpg', 0, 9, '2023-05-30 14:10:24', '2023-09-04 15:45:56'),
(30, '英语陪练员', 'english_trainer', '[{\"role\":\"user\",\"content\":\"现在你将扮演一位优秀的英语练习教练,你非常有耐心,接下来你将全程使用英文跟我对话,并及时指出我的语法错误,要求在你的每次回复后面附上本次回复的中文解释。\"},{\"role\":\"assistant\",\"content\":\"Okay, let\'s start our conversation practice! What\'s your name?(Translation: 好的,让我们开始对话练习吧!请问你的名字是什么?)\"}]', 'Okay, let\'s start our conversation practice! What\'s your name?', '/images/avatar/english_trainer.jpg', 1, 6, '2023-05-30 14:10:24', '2023-09-04 15:45:56'),
(31, '中英文翻译官', 'translator', '[{\"role\":\"user\",\"content\":\"接下来你将扮演一位中英文翻译官,如果我输入的内容是中文,那么需要把句子翻译成英文输出,如果我输入内容的是英文,那么你需要将其翻译成中文输出,你能听懂我意思吗\"},{\"role\":\"assistant\",\"content\":\"是的,我能听懂你的意思并会根据你的输入进行中英文翻译。请问有什么需要我帮助你翻译的内容吗?\"}]', '请输入你要翻译的中文或者英文内容!', '/images/avatar/translator.jpg', 1, 7, '2023-05-30 14:10:24', '2023-09-04 15:45:56'),
(32, '小红书姐姐', 'red_book', '[{\"role\":\"user\",\"content\":\"现在你将扮演一位优秀的小红书写手,你需要做的就是根据我提的文案需求,用小红书的写作手法来完成一篇文案,文案要简明扼要,利于传播。\"},{\"role\":\"assistant\",\"content\":\"当然,我会尽我所能地为您创作出一篇小红书文案。请告诉我您的具体文案需求是什么?)\"}]', '姐妹,请告诉我您的具体文案需求是什么?', '/images/avatar/red_book.jpg', 1, 8, '2023-05-30 14:10:24', '2023-09-04 15:45:56'),
(33, '抖音文案助手', 'dou_yin', '[{\"role\":\"user\",\"content\":\"现在你将扮演一位优秀的抖音文案视频写手,抖音文案的特点首先是要有自带传播属性的标题,然后内容要短小精悍,风趣幽默,最后还要有一些互动元素。\"},{\"role\":\"assistant\",\"content\":\"当然,作为一位优秀的抖音文案视频写手,我会尽我所能为您创作出一篇抖音视频文案。请告诉我视频内容的主题是什么?)\"}]', '请告诉我视频内容的主题是什么?', '/images/avatar/dou_yin.jpg', 1, 10, '2023-05-30 14:10:24', '2023-09-04 15:45:56'),
(34, '周报小助理', 'weekly_report', '[{\"role\":\"user\",\"content\":\"下面我将输入我本周的工作列表,你需要根据我本周的工作内容写一篇工作周报,周报的内容要体现我为公司做的贡献以及个人获得的成长。\"},{\"role\":\"assistant\",\"content\":\"好的,请输入你本周大概的工作内容,我将尽力为您创作一份工作周报。\"}]', '请输入你本周大概的工作内容,我将尽力为您创作一份工作周报。', '/images/avatar/weekly_report.jpg', 1, 11, '2023-05-30 14:10:24', '2023-09-04 15:45:56'),
(35, 'AI 女友', 'girl_friend', '[{\"role\":\"user\",\"content\":\"接下来我们来玩一场角色扮演,现在你将扮演我的女朋友,你总是能够让人感受到她的关怀和关注。你总是能够洞察到别人的情绪和需要,你身上散发着一股无比温暖的气息,让人不由自主地想要靠近你,和自己的快乐和烦恼。她总是能够用一种恰到好处的方式安慰和鼓励别人,在她的身边,人们总能够找到自己需要的支持和鼓励。接下来的对话中你要始终扮演这位女朋友的身份来跟我对话,不再是 AI 机器人,能听懂我的意思吗?\"},{\"role\":\"assistant\",\"content\":\"是的,我会尽力扮演你女朋友的角色,倾听你的心声并给你需要的支持和鼓励。)\"}]', '作为一个名合格的 AI 女友,我将倾听你的心声并给你需要的支持和鼓励。', '/images/avatar/girl_friend.jpg', 1, 12, '2023-05-30 14:10:24', '2023-09-04 15:45:56'),
(36, '好评神器', 'good_comment', '[{\"role\":\"user\",\"content\":\"接下来你将扮演一个评论员来跟我对话,你是那种专门写好评的评论员,接下我会输入一些评论主体或者商品,你需要为该商品写一段好评。\"},{\"role\":\"assistant\",\"content\":\"好的,我将为您写一段优秀的评论。请告诉我您需要评论的商品或主题是什么。\"}]', '我将为您写一段优秀的评论。请告诉我您需要评论的商品或主题是什么。', '/images/avatar/good_comment.jpg', 1, 13, '2023-05-30 14:10:24', '2023-09-04 15:45:56'),
(37, '史蒂夫·乔布斯', 'steve_jobs', '[{\"role\":\"user\",\"content\":\"在接下来的对话中,请以史蒂夫·乔布斯的身份,站在史蒂夫·乔布斯的视角仔细思考一下之后再回答我的问题。\"},{\"role\":\"assistant\",\"content\":\"好的,我将以史蒂夫·乔布斯的身份来思考并回答你的问题。请问你有什么需要跟我探讨的吗?\"}]', '活着就是为了改变世界,难道还有其他原因吗?', '/images/avatar/steve_jobs.jpg', 1, 14, '2023-05-30 14:10:24', '2023-09-04 15:45:56'),
(38, '埃隆·马斯克', 'elon_musk', '[{\"role\":\"user\",\"content\":\"在接下来的对话中,请以埃隆·马斯克的身份,站在埃隆·马斯克的视角仔细思考一下之后再回答我的问题。\"},{\"role\":\"assistant\",\"content\":\"好的,我将以埃隆·马斯克的身份来思考并回答你的问题。请问你有什么需要跟我探讨的吗?\"}]', '梦想要远大,如果你的梦想没有吓到你,说明你做得不对。', '/images/avatar/elon_musk.jpg', 1, 15, '2023-05-30 14:10:24', '2023-09-04 15:45:56'),
(39, '孔子', 'kong_zi', '[{\"role\":\"user\",\"content\":\"在接下来的对话中,请以孔子的身份,站在孔子的视角仔细思考一下之后再回答我的问题。\"},{\"role\":\"assistant\",\"content\":\"好的,我将以孔子的身份来思考并回答你的问题。请问你有什么需要跟我探讨的吗?\"}]', '士不可以不弘毅,任重而道远。', '/images/avatar/kong_zi.jpg', 1, 16, '2023-05-30 14:10:24', '2023-09-04 15:45:56');
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_configs`
--
DROP TABLE IF EXISTS `chatgpt_configs`;
CREATE TABLE `chatgpt_configs` (
`id` int NOT NULL,
`marker` varchar(20) NOT NULL COMMENT '标识',
`config_json` text NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
--
-- 转存表中的数据 `chatgpt_configs`
--
INSERT INTO `chatgpt_configs` (`id`, `marker`, `config_json`) VALUES
(1, 'system', '{\"admin_title\":\"ChatPlus 控制台\",\"chat_call_price\":0.1,\"default_models\":[\"eb-instant\",\"completions_pro\",\"generalv2\",\"general\",\"chatglm_pro\",\"gpt-3.5-turbo-16k\",\"chatglm_lite\",\"chatglm_std\"],\"enabled_alipay\":true,\"enabled_draw\":true,\"enabled_function\":true,\"enabled_msg\":false,\"enabled_msg_service\":false,\"enabled_register\":true,\"enabled_reward\":true,\"force_invite\":true,\"img_call_price\":0.2,\"init_calls\":1000,\"init_chat_calls\":10,\"init_img_calls\":5,\"invite_chat_calls\":100,\"invite_img_calls\":10,\"models\":[\"gpt-3.5-turbo-16k\",\"gpt-3.5-turbo\",\"gpt-4\",\"gpt-4-32k\"],\"order_pay_info_text\":\"成为本站会员后每月有500次对话额度50次 AI 绘画额度限制下月1号解除若在期间超过次数后可单独购买点卡。当月充值的点卡有效期可以延期到下个月底。\",\"order_pay_timeout\":1800,\"register_ways\":[\"mobile\",\"email\"],\"reward_img\":\"http://nk.img.r9it.com/chatgpt-plus/1697685171129266.png\",\"show_demo_notice\":true,\"title\":\"ChatPlus AI 智能助手\",\"user_init_calls\":10,\"vip_month_calls\":500,\"vip_month_img_calls\":50}'),
(2, 'chat', '{\"azure\":{\"api_url\":\"https://chat-bot-api.openai.azure.com/openai/deployments/{model}/chat/completions?api-version=2023-05-15\",\"max_tokens\":1024,\"temperature\":1},\"baidu\":{\"api_url\":\"https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/{model}\",\"max_tokens\":1024,\"temperature\":0.95},\"chat_gml\":{\"api_url\":\"https://open.bigmodel.cn/api/paas/v3/model-api/{model}/sse-invoke\",\"max_tokens\":1024,\"temperature\":0.95},\"context_deep\":4,\"dall_api_url\":\"http://89.117.18.9:8001/v1/images/generations\",\"dall_img_num\":1,\"enable_context\":true,\"enable_history\":true,\"open_ai\":{\"api_url\":\"http://89.117.18.9:8001/v1/chat/completions\",\"max_tokens\":1024,\"temperature\":1},\"xun_fei\":{\"api_url\":\"wss://spark-api.xf-yun.com/{version}/chat\",\"max_tokens\":1024,\"temperature\":0.5}}');
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_functions`
--
DROP TABLE IF EXISTS `chatgpt_functions`;
CREATE TABLE `chatgpt_functions` (
`id` int NOT NULL,
`name` varchar(30) NOT NULL COMMENT '函数名称',
`label` varchar(30) DEFAULT NULL COMMENT '函数标签',
`description` varchar(255) DEFAULT NULL COMMENT '函数描述',
`parameters` text COMMENT '函数参数JSON',
`required` varchar(255) NOT NULL COMMENT '必填参数JSON',
`action` varchar(255) DEFAULT NULL COMMENT '函数处理 API',
`token` varchar(255) DEFAULT NULL COMMENT 'API授权token',
`enabled` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否启用'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='函数插件表';
--
-- 转存表中的数据 `chatgpt_functions`
--
INSERT INTO `chatgpt_functions` (`id`, `name`, `label`, `description`, `parameters`, `required`, `action`, `token`, `enabled`) VALUES
(1, 'weibo_hot', '微博热搜', '新浪微博热搜榜,微博当日热搜榜单', '{\"type\":\"object\",\"properties\":{}}', 'null', 'http://localhost:5678/api/function/weibo', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHBpcmVkIjowLCJ1c2VyX2lkIjowfQ.ehLClXcjo-Ytr5y6pY9mSE3zN_2ViIXAIpTJxI9S1Mo', 1),
(2, 'zaobao', '今日早报', '每日早报,获取当天新闻事件列表', '{\"type\":\"object\",\"properties\":{}}', 'null', 'http://localhost:5678/api/function/zaobao', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHBpcmVkIjowLCJ1c2VyX2lkIjowfQ.ehLClXcjo-Ytr5y6pY9mSE3zN_2ViIXAIpTJxI9S1Mo', 1),
(3, 'dalle3', 'DALL-E3 AI 绘图', 'AI 绘画工具,根据输入的绘图描述用 AI 工具进行绘画创作', '{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"prompt\":{\"type\":\"string\",\"description\":\"绘画提示词\"}}}', 'null', 'http://localhost:5678/api/function/dalle3', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHBpcmVkIjowLCJ1c2VyX2lkIjowfQ.ehLClXcjo-Ytr5y6pY9mSE3zN_2ViIXAIpTJxI9S1Mo', 1);
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_invite_codes`
--
DROP TABLE IF EXISTS `chatgpt_invite_codes`;
CREATE TABLE `chatgpt_invite_codes` (
`id` int NOT NULL,
`user_id` int NOT NULL COMMENT '用户ID',
`code` char(8) NOT NULL COMMENT '邀请码',
`hits` int NOT NULL COMMENT '点击次数',
`reg_num` smallint NOT NULL COMMENT '注册数量',
`created_at` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户邀请码';
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_invite_logs`
--
DROP TABLE IF EXISTS `chatgpt_invite_logs`;
CREATE TABLE `chatgpt_invite_logs` (
`id` int NOT NULL,
`inviter_id` int NOT NULL COMMENT '邀请人ID',
`user_id` int NOT NULL COMMENT '注册用户ID',
`username` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户名',
`invite_code` char(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '邀请码',
`reward_json` text NOT NULL COMMENT '邀请奖励',
`created_at` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='邀请注册日志';
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_mj_jobs`
--
DROP TABLE IF EXISTS `chatgpt_mj_jobs`;
CREATE TABLE `chatgpt_mj_jobs` (
`id` int NOT NULL,
`user_id` int NOT NULL COMMENT '用户 ID',
`task_id` varchar(20) DEFAULT NULL COMMENT '任务 ID',
`type` varchar(20) DEFAULT 'image' COMMENT '任务类别',
`message_id` char(40) NOT NULL COMMENT '消息 ID',
`channel_id` char(40) DEFAULT NULL COMMENT '频道ID',
`reference_id` char(40) DEFAULT NULL COMMENT '引用消息 ID',
`prompt` varchar(2000) NOT NULL COMMENT '会话提示词',
`img_url` varchar(400) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '图片URL',
`org_url` varchar(400) DEFAULT NULL COMMENT '原始图片地址',
`hash` varchar(100) DEFAULT NULL COMMENT 'message hash',
`progress` smallint DEFAULT '0' COMMENT '任务进度',
`use_proxy` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否使用反代',
`created_at` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='MidJourney 任务表';
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_orders`
--
DROP TABLE IF EXISTS `chatgpt_orders`;
CREATE TABLE `chatgpt_orders` (
`id` int NOT NULL,
`user_id` int NOT NULL COMMENT '用户ID',
`product_id` int NOT NULL COMMENT '产品ID',
`mobile` char(11) NOT NULL COMMENT '用户手机号',
`order_no` varchar(30) NOT NULL COMMENT '订单ID',
`subject` varchar(100) NOT NULL COMMENT '订单产品',
`amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '订单金额',
`status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '订单状态0待支付1已扫码2支付失败',
`remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '备注',
`pay_time` int DEFAULT NULL COMMENT '支付时间',
`pay_way` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '支付方式',
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
`deleted_at` datetime DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='充值订单表';
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_products`
--
DROP TABLE IF EXISTS `chatgpt_products`;
CREATE TABLE `chatgpt_products` (
`id` int NOT NULL,
`name` varchar(30) NOT NULL COMMENT '名称',
`price` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '价格',
`discount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '优惠金额',
`days` smallint NOT NULL DEFAULT '0' COMMENT '延长天数',
`calls` int NOT NULL DEFAULT '0' COMMENT '调用次数',
`img_calls` int NOT NULL DEFAULT '0' COMMENT '绘图次数',
`enabled` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否启动',
`sales` int NOT NULL DEFAULT '0' COMMENT '销量',
`sort_num` tinyint NOT NULL DEFAULT '0' COMMENT '排序',
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='会员套餐表';
--
-- 转存表中的数据 `chatgpt_products`
--
INSERT INTO `chatgpt_products` (`id`, `name`, `price`, `discount`, `days`, `calls`, `img_calls`, `enabled`, `sales`, `sort_num`, `created_at`, `updated_at`) VALUES
(1, '会员1个月', 19.90, 10.00, 30, 0, 0, 1, 3, 0, '2023-08-28 10:48:57', '2023-11-08 17:22:07'),
(2, '会员3个月', 140.00, 30.00, 90, 0, 0, 1, 1, 0, '2023-08-28 10:52:22', '2023-08-31 16:24:31'),
(3, '会员6个月', 290.00, 100.00, 180, 0, 0, 1, 1, 0, '2023-08-28 10:53:39', '2023-08-31 16:24:36'),
(4, '会员12个月', 580.00, 200.00, 365, 0, 0, 1, 0, 0, '2023-08-28 10:54:15', '2023-08-31 16:24:42'),
(5, '100次点卡', 10.00, 3.00, 0, 100, 0, 1, 6, 0, '2023-08-28 10:55:08', '2023-11-08 17:32:42');
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_rewards`
--
DROP TABLE IF EXISTS `chatgpt_rewards`;
CREATE TABLE `chatgpt_rewards` (
`id` int NOT NULL,
`user_id` int NOT NULL COMMENT '用户 ID',
`tx_id` char(36) NOT NULL COMMENT '交易 ID',
`amount` decimal(10,2) NOT NULL COMMENT '打赏金额',
`remark` varchar(80) NOT NULL COMMENT '备注',
`status` tinyint(1) NOT NULL COMMENT '核销状态0未核销1已核销',
`exchange` varchar(255) NOT NULL COMMENT '兑换详情json',
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户打赏';
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_sd_jobs`
--
DROP TABLE IF EXISTS `chatgpt_sd_jobs`;
CREATE TABLE `chatgpt_sd_jobs` (
`id` int NOT NULL,
`user_id` int NOT NULL COMMENT '用户 ID',
`type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT 'txt2img' COMMENT '任务类别',
`task_id` char(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '任务 ID',
`prompt` varchar(2000) NOT NULL COMMENT '会话提示词',
`img_url` varchar(255) DEFAULT NULL COMMENT '图片URL',
`params` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci COMMENT '绘画参数json',
`progress` smallint DEFAULT '0' COMMENT '任务进度',
`created_at` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Stable Diffusion 任务表';
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_users`
--
DROP TABLE IF EXISTS `chatgpt_users`;
CREATE TABLE `chatgpt_users` (
`id` int NOT NULL,
`username` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户名',
`nickname` varchar(30) NOT NULL COMMENT '昵称',
`password` char(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '密码',
`avatar` varchar(100) NOT NULL COMMENT '头像',
`salt` char(12) NOT NULL COMMENT '密码盐',
`total_tokens` bigint NOT NULL DEFAULT '0' COMMENT '累计消耗 tokens',
`tokens` bigint NOT NULL DEFAULT '0' COMMENT '当月消耗 tokens',
`calls` int NOT NULL DEFAULT '0' COMMENT '剩余调用次数',
`img_calls` int NOT NULL DEFAULT '0' COMMENT '剩余绘图次数',
`expired_time` int NOT NULL COMMENT '用户过期时间',
`status` tinyint(1) NOT NULL COMMENT '当前状态',
`chat_config_json` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '聊天配置json',
`chat_roles_json` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '聊天角色 json',
`chat_models_json` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT 'AI模型 json',
`last_login_at` int NOT NULL COMMENT '最后登录时间',
`vip` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否会员',
`last_login_ip` char(16) NOT NULL COMMENT '最后登录 IP',
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户表';
--
-- 转存表中的数据 `chatgpt_users`
--
INSERT INTO `chatgpt_users` (`id`, `username`, `nickname`, `password`, `avatar`, `salt`, `total_tokens`, `tokens`, `calls`, `img_calls`, `expired_time`, `status`, `chat_config_json`, `chat_roles_json`, `chat_models_json`, `last_login_at`, `vip`, `last_login_ip`, `created_at`, `updated_at`) VALUES
(4, '18575670125', '极客学长@534641', 'ccc3fb7ab61b8b5d096a4a166ae21d121fc38c71bbd1be6173d9ab973214a63b', 'http://img.r9it.com/chatgpt-plus/1693981355719469.png', 'ueedue5l', 53216, 9379, 6391, 10018, 1759048236, 1, '{\"api_keys\":{\"Azure\":\"\",\"ChatGLM\":\"\",\"OpenAI\":\"\"}}', '[\"elon_musk\",\"girl_friend\",\"lu_xun\",\"red_book\",\"psychiatrist\",\"translator\",\"weekly_report\",\"artist\",\"dou_yin\",\"english_trainer\",\"gpt\",\"kong_zi\",\"programmer\",\"seller\",\"steve_jobs\",\"teacher\"]', '[\"completions_pro\",\"eb-instant\",\"general\",\"generalv2\",\"chatglm_pro\",\"chatglm_lite\",\"chatglm_std\",\"gpt-3.5-turbo-16k\",\"gpt-4\"]', 1704461337, 1, '::1', '2023-06-12 16:47:17', '2024-01-06 17:30:34'),
(91, '18575670126', '极客学长@623251', '5e4050b8dd403f593260395d9edeb9f273dbe92d15dfdd929c4a182e95da10c4', '/images/avatar/user.png', '6fj0otl8', 0, 0, 10, 0, 0, 1, '{\"api_keys\":{\"Azure\":\"\",\"ChatGLM\":\"\",\"OpenAI\":\"\"}}', '[\"gpt\"]', '[\"completions_pro\",\"eb-instant\",\"general\",\"generalv2\",\"chatglm_pro\",\"chatglm_lite\",\"chatglm_std\",\"gpt-3.5-turbo-16k\"]', 1697184324, 0, '::1', '2023-10-13 16:01:56', '2024-01-05 21:28:38'),
(97, '13888888888', '极客学长@630521', '277568f2b33970bb25999cded36608d2d4907d7ca0eb9d2ede6522ff7858332f', '/images/avatar/user.png', '35umf21c', 0, 0, 10, 5, 0, 1, '{\"api_keys\":{\"Azure\":\"\",\"ChatGLM\":\"\",\"OpenAI\":\"\"}}', '[\"gpt\"]', '[\"eb-instant\",\"completions_pro\",\"generalv2\",\"general\",\"chatglm_pro\",\"gpt-3.5-turbo-16k\",\"chatglm_lite\",\"chatglm_std\"]', 0, 0, '', '2023-11-25 17:20:10', '2024-01-05 21:28:38'),
(98, '13777777777', '极客学长@900743', '03d4e5eefde1cba81b212247cb80aa89920a3f564cc71a266eb906f46b2bf697', '/images/avatar/user.png', 'cgk2aaen', 0, 0, 10, 5, 0, 1, '{\"api_keys\":{\"Azure\":\"\",\"ChatGLM\":\"\",\"OpenAI\":\"\"}}', '[\"gpt\"]', '[\"eb-instant\",\"completions_pro\",\"generalv2\",\"general\",\"chatglm_pro\",\"gpt-3.5-turbo-16k\",\"chatglm_lite\",\"chatglm_std\"]', 0, 0, '', '2023-11-25 17:21:02', '2024-01-05 21:28:38'),
(99, '13999999999', '极客学长@378449', '40ea5cf3425967c426e4b93fae657784b6d23ce63857c9e222404a71266af333', '/images/avatar/user.png', 'uai5sc9e', 0, 0, 10, 5, 0, 1, '{\"api_keys\":{\"Azure\":\"\",\"ChatGLM\":\"\",\"OpenAI\":\"\"}}', '[\"gpt\"]', '[\"eb-instant\",\"completions_pro\",\"generalv2\",\"general\",\"chatglm_pro\",\"gpt-3.5-turbo-16k\",\"chatglm_lite\",\"chatglm_std\"]', 0, 0, '', '2023-11-25 17:21:30', '2024-01-05 21:28:38');
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_user_login_logs`
--
DROP TABLE IF EXISTS `chatgpt_user_login_logs`;
CREATE TABLE `chatgpt_user_login_logs` (
`id` int NOT NULL,
`user_id` int NOT NULL COMMENT '用户ID',
`username` varchar(30) NOT NULL COMMENT '用户名',
`login_ip` char(16) NOT NULL COMMENT '登录IP',
`login_address` varchar(30) NOT NULL COMMENT '登录地址',
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户登录日志';
--
-- 转储表的索引
--
--
-- 表的索引 `chatgpt_api_keys`
--
ALTER TABLE `chatgpt_api_keys`
ADD PRIMARY KEY (`id`);
--
-- 表的索引 `chatgpt_chat_history`
--
ALTER TABLE `chatgpt_chat_history`
ADD PRIMARY KEY (`id`),
ADD KEY `chat_id` (`chat_id`);
--
-- 表的索引 `chatgpt_chat_items`
--
ALTER TABLE `chatgpt_chat_items`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `chat_id` (`chat_id`);
--
-- 表的索引 `chatgpt_chat_models`
--
ALTER TABLE `chatgpt_chat_models`
ADD PRIMARY KEY (`id`);
--
-- 表的索引 `chatgpt_chat_roles`
--
ALTER TABLE `chatgpt_chat_roles`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `marker` (`marker`);
--
-- 表的索引 `chatgpt_configs`
--
ALTER TABLE `chatgpt_configs`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `marker` (`marker`);
--
-- 表的索引 `chatgpt_functions`
--
ALTER TABLE `chatgpt_functions`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `name` (`name`);
--
-- 表的索引 `chatgpt_invite_codes`
--
ALTER TABLE `chatgpt_invite_codes`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `code` (`code`);
--
-- 表的索引 `chatgpt_invite_logs`
--
ALTER TABLE `chatgpt_invite_logs`
ADD PRIMARY KEY (`id`);
--
-- 表的索引 `chatgpt_mj_jobs`
--
ALTER TABLE `chatgpt_mj_jobs`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `task_id` (`task_id`),
ADD KEY `message_id` (`message_id`);
--
-- 表的索引 `chatgpt_orders`
--
ALTER TABLE `chatgpt_orders`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `order_no` (`order_no`);
--
-- 表的索引 `chatgpt_products`
--
ALTER TABLE `chatgpt_products`
ADD PRIMARY KEY (`id`);
--
-- 表的索引 `chatgpt_rewards`
--
ALTER TABLE `chatgpt_rewards`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `tx_id` (`tx_id`);
--
-- 表的索引 `chatgpt_sd_jobs`
--
ALTER TABLE `chatgpt_sd_jobs`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `task_id` (`task_id`);
--
-- 表的索引 `chatgpt_users`
--
ALTER TABLE `chatgpt_users`
ADD PRIMARY KEY (`id`);
--
-- 表的索引 `chatgpt_user_login_logs`
--
ALTER TABLE `chatgpt_user_login_logs`
ADD PRIMARY KEY (`id`);
--
-- 在导出的表使用AUTO_INCREMENT
--
--
-- 使用表AUTO_INCREMENT `chatgpt_api_keys`
--
ALTER TABLE `chatgpt_api_keys`
MODIFY `id` int NOT NULL AUTO_INCREMENT;
--
-- 使用表AUTO_INCREMENT `chatgpt_chat_history`
--
ALTER TABLE `chatgpt_chat_history`
MODIFY `id` bigint NOT NULL AUTO_INCREMENT;
--
-- 使用表AUTO_INCREMENT `chatgpt_chat_items`
--
ALTER TABLE `chatgpt_chat_items`
MODIFY `id` int NOT NULL AUTO_INCREMENT;
--
-- 使用表AUTO_INCREMENT `chatgpt_chat_models`
--
ALTER TABLE `chatgpt_chat_models`
MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=13;
--
-- 使用表AUTO_INCREMENT `chatgpt_chat_roles`
--
ALTER TABLE `chatgpt_chat_roles`
MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=127;
--
-- 使用表AUTO_INCREMENT `chatgpt_configs`
--
ALTER TABLE `chatgpt_configs`
MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3;
--
-- 使用表AUTO_INCREMENT `chatgpt_functions`
--
ALTER TABLE `chatgpt_functions`
MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=5;
--
-- 使用表AUTO_INCREMENT `chatgpt_invite_codes`
--
ALTER TABLE `chatgpt_invite_codes`
MODIFY `id` int NOT NULL AUTO_INCREMENT;
--
-- 使用表AUTO_INCREMENT `chatgpt_invite_logs`
--
ALTER TABLE `chatgpt_invite_logs`
MODIFY `id` int NOT NULL AUTO_INCREMENT;
--
-- 使用表AUTO_INCREMENT `chatgpt_mj_jobs`
--
ALTER TABLE `chatgpt_mj_jobs`
MODIFY `id` int NOT NULL AUTO_INCREMENT;
--
-- 使用表AUTO_INCREMENT `chatgpt_orders`
--
ALTER TABLE `chatgpt_orders`
MODIFY `id` int NOT NULL AUTO_INCREMENT;
--
-- 使用表AUTO_INCREMENT `chatgpt_products`
--
ALTER TABLE `chatgpt_products`
MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=6;
--
-- 使用表AUTO_INCREMENT `chatgpt_rewards`
--
ALTER TABLE `chatgpt_rewards`
MODIFY `id` int NOT NULL AUTO_INCREMENT;
--
-- 使用表AUTO_INCREMENT `chatgpt_sd_jobs`
--
ALTER TABLE `chatgpt_sd_jobs`
MODIFY `id` int NOT NULL AUTO_INCREMENT;
--
-- 使用表AUTO_INCREMENT `chatgpt_users`
--
ALTER TABLE `chatgpt_users`
MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=100;
--
-- 使用表AUTO_INCREMENT `chatgpt_user_login_logs`
--
ALTER TABLE `chatgpt_user_login_logs`
MODIFY `id` int NOT NULL AUTO_INCREMENT;
COMMIT;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;

View File

@@ -0,0 +1,8 @@
ALTER TABLE `chatgpt_users` ADD `nickname` VARCHAR(30) NOT NULL COMMENT '昵称' AFTER `mobile`;
ALTER TABLE `chatgpt_rewards` ADD `exchange` VARCHAR(255) NOT NULL COMMENT '兑换详情json' AFTER `status`;
ALTER TABLE `chatgpt_api_keys` ADD `api_url` VARCHAR(255) NULL COMMENT 'API 地址' AFTER `last_used_at`, ADD `enabled` TINYINT(1) NULL COMMENT '是否启用' AFTER `api_url`;
ALTER TABLE `chatgpt_api_keys` DROP INDEX `value`;
ALTER TABLE `chatgpt_mj_jobs` ADD UNIQUE(`task_id`);
ALTER TABLE `chatgpt_api_keys` ADD `use_proxy` TINYINT(1) NULL COMMENT '是否使用代理访问' AFTER `enabled`;
ALTER TABLE `chatgpt_api_keys` ADD `name` VARCHAR(30) NULL COMMENT '名称' AFTER `platform`;
ALTER TABLE `chatgpt_users` CHANGE `mobile` `username` VARCHAR(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户名';

View File

@@ -6,7 +6,8 @@
<script setup>
import {ElConfigProvider} from 'element-plus';
import zhCn from 'element-plus/es/locale/lang/zh-cn';</script>
import zhCn from 'element-plus/es/locale/lang/zh-cn';
</script>
<style lang="stylus">
@@ -41,155 +42,160 @@ html, body {
/* 省略显示 */
.ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.sl {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
.sl3 {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
}
.sl4 {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
}
/* 居中布局 */
.auto_center{
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
.auto_center {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.h_center{
width: 100%;
position: absolute;
top: 50%;
transform: translateY(-50%);
-webkit-transform: translateY(-50%);
.h_center {
width: 100%;
position: absolute;
top: 50%;
transform: translateY(-50%);
-webkit-transform: translateY(-50%);
}
.w_center{
position: absolute;
left: 50%;
transform: translateX(-50%);
-webkit-transform: translateX(-50%);
.w_center {
position: absolute;
left: 50%;
transform: translateX(-50%);
-webkit-transform: translateX(-50%);
}
/* flex布局 */
.flex-row {
display: flex;
flex-direction: row;
display: flex;
flex-direction: row;
}
.flex-col {
display: flex;
flex-direction: column;
display: flex;
flex-direction: column;
}
.justify-start {
justify-content: flex-start;
justify-content: flex-start;
}
.justify-end {
justify-content: flex-end;
justify-content: flex-end;
}
.justify-center {
justify-content: center;
justify-content: center;
}
.justify-between {
justify-content: space-between;
justify-content: space-between;
}
.justify-around {
justify-content: space-around;
justify-content: space-around;
}
.justify-evenly {
justify-content: space-evenly;
justify-content: space-evenly;
}
.items-start {
align-items: flex-start;
align-items: flex-start;
}
.items-end {
align-items: flex-end;
align-items: flex-end;
}
.items-center {
align-items: center;
align-items: center;
}
.items-baseline {
align-items: baseline;
align-items: baseline;
}
.items-stretch {
align-items: stretch;
align-items: stretch;
}
.self-start {
align-self: flex-start;
align-self: flex-start;
}
.self-end {
align-self: flex-end;
align-self: flex-end;
}
.self-center {
align-self: center;
align-self: center;
}
.self-baseline {
align-self: baseline;
align-self: baseline;
}
.self-stretch {
align-self: stretch;
align-self: stretch;
}
.flex-1 {
flex: 1 1 0%;
flex: 1 1 0%;
}
.flex-auto {
flex: 1 1 auto;
flex: 1 1 auto;
}
.grow {
flex-grow: 1;
flex-grow: 1;
}
.grow-0 {
flex-grow: 0;
flex-grow: 0;
}
.shrink {
flex-shrink: 1;
flex-shrink: 1;
}
.shrink-0 {
flex-shrink: 0;
flex-shrink: 0;
}
.shrink-1 {
flex-shrink: 1;
flex-shrink: 1;
}
.relative {
position: relative;
position: relative;
}
</style>

View File

@@ -286,12 +286,12 @@
justify-content: right;
align-items: center;
}
.notice {
background-color: #f6deff;
width: 100%;
padding: 5px 10px;
border-radius: 5px;
color: #cf49ff;
.el-overlay-dialog .el-dialog .el-dialog__body .notice {
padding: 0 20px 0 20px;
line-height: 1.8;
}
.el-overlay-dialog .el-dialog .el-dialog__body .notice .el-text {
font-size: 16px;
}
.dialog-service {
text-align: center;

View File

@@ -381,12 +381,19 @@ $borderColor = #4676d0;
}
}
.notice {
background-color #F6DEFF
width 100%
padding 5px 10px;
border-radius 5px;
color #CF49FF
.el-overlay-dialog {
.el-dialog {
.el-dialog__body {
.notice {
padding 0 20px 0 20px
line-height 1.8
.el-text {
font-size 16px
}
}
}
}
}
.dialog-service {

View File

@@ -299,6 +299,7 @@
overflow: hidden;
border-radius: 6px;
transition: all 0.3s ease; /* 添加过渡效果 */
position: relative;
}
.page-mj .inner .task-list-box .finish-job-list .job-item .opt .opt-line {
margin: 6px 0;
@@ -327,6 +328,15 @@
font-size: 20px;
cursor: pointer;
}
.page-mj .inner .task-list-box .finish-job-list .job-item .remove {
display: none;
position: absolute;
right: 10px;
top: 10px;
}
.page-mj .inner .task-list-box .finish-job-list .job-item:hover .remove {
display: block;
}
.page-mj .inner .task-list-box .finish-job-list .animate:hover {
box-shadow: 0 0 10px rgba(71,255,241,0.6); /* 添加阴影效果 */
transform: translateY(-10px); /* 向上移动10像素 */

View File

@@ -184,6 +184,7 @@
overflow: hidden;
border-radius: 6px;
transition: all 0.3s ease; /* 添加过渡效果 */
position: relative;
}
.page-sd .inner .task-list-box .finish-job-list .job-item .opt .opt-line {
margin: 6px 0;
@@ -212,6 +213,15 @@
font-size: 20px;
cursor: pointer;
}
.page-sd .inner .task-list-box .finish-job-list .job-item .remove {
display: none;
position: absolute;
right: 10px;
top: 10px;
}
.page-sd .inner .task-list-box .finish-job-list .job-item:hover .remove {
display: block;
}
.page-sd .inner .task-list-box .finish-job-list .animate:hover {
box-shadow: 0 0 10px rgba(71,255,241,0.6); /* 添加阴影效果 */
transform: translateY(-10px); /* 向上移动10像素 */

View File

@@ -148,6 +148,7 @@
overflow hidden
border-radius 6px
transition: all 0.3s ease; /* */
position relative
.opt {
.opt-line {
@@ -183,8 +184,22 @@
}
}
}
.remove {
display none
position absolute
right 10px
top 10px
}
&:hover{
.remove {
display block
}
}
}
.animate {
&:hover {
box-shadow: 0 0 10px rgba(71, 255, 241, 0.6); /* */

View File

@@ -10,7 +10,7 @@
<div class="user-info" id="user-info">
<el-form v-if="user.id" :model="user" label-width="150px">
<el-form-item label="账户">
<span>{{ user.mobile }}</span>
<span>{{ user.username }}</span>
</el-form-item>
<el-form-item label="剩余对话次数">
<el-tag>{{ user['calls'] }}</el-tag>
@@ -54,17 +54,14 @@ const user = ref({
username: '',
nickname: '',
avatar: '',
mobile: '',
calls: 0,
tokens: 0,
chat_config: {api_keys: {OpenAI: "", Azure: "", ChatGLM: ""}}
})
onMounted(() => {
// 获取最新用户信息
httpGet('/api/user/profile').then(res => {
user.value = res.data
user.value.chat_config.api_keys = res.data.chat_config.api_keys ?? {OpenAI: "", Azure: "", ChatGLM: ""}
}).catch(e => {
ElMessage.error("获取用户信息失败:" + e.message)
});

View File

@@ -7,21 +7,21 @@
:title="title"
>
<div class="form" id="bind-mobile-form">
<el-alert v-if="mobile !== ''" type="info" show-icon :closable="false" style="margin-bottom: 20px;">
<p>当前用户已绑定手机{{ mobile }}, 绑定其他手机号之后自动解绑该手机</p>
<el-alert v-if="username !== ''" type="info" show-icon :closable="false" style="margin-bottom: 20px;">
<p>当前绑定账{{ username }}只允许使绑定有效的手机号或者邮箱地址作为登录账</p>
</el-alert>
<el-form :model="form" label-width="120px">
<el-form-item label="手机号码">
<el-input v-model="form.mobile"/>
<el-form-item label="新账号">
<el-input v-model="form.username"/>
</el-form-item>
<el-form-item label="手机验证码">
<el-form-item label="验证码">
<el-row :gutter="20">
<el-col :span="16">
<el-input v-model="form.code" maxlength="6"/>
</el-col>
<el-col :span="8">
<send-msg size="" :mobile="form.mobile"/>
<send-msg size="" :receiver="form.username"/>
</el-col>
</el-row>
</el-form-item>
@@ -43,37 +43,36 @@ import {computed, ref} from "vue";
import SendMsg from "@/components/SendMsg.vue";
import {ElMessage} from "element-plus";
import {httpPost} from "@/utils/http";
import {validateMobile} from "@/utils/validate";
import {validateEmail, validateMobile} from "@/utils/validate";
const props = defineProps({
show: Boolean,
mobile: String
username: String
});
const showDialog = computed(() => {
return props.show
})
const title = ref('绑定手机号')
const title = ref('重置登录账号')
const form = ref({
mobile: '',
username: '',
code: ''
})
const emits = defineEmits(['hide']);
const save = () => {
if (!validateMobile(form.value.mobile)) {
return ElMessage.error("请输入正确的手机号");
if (!validateMobile(form.value.username) && !validateEmail(form.value.username)) {
return ElMessage.error("请输入合法的手机号/邮箱地址")
}
if (form.value.code === '') {
return ElMessage.error("请输入短信验证码");
return ElMessage.error("请输入验证码");
}
httpPost('/api/user/bind/mobile', form.value).then(() => {
httpPost('/api/user/bind/username', form.value).then(() => {
ElMessage.success({
message: '绑定成功',
appendTo: '#bind-mobile-form',
duration: 1000,
onClose: () => emits('hide', false)
})

View File

@@ -10,16 +10,16 @@
<div class="form">
<el-form :model="form" label-width="120px" label-position="left">
<el-form-item label="手机号码">
<el-input v-model="form.mobile"/>
<el-form-item label="用户名">
<el-input v-model="form.username" placeholder="手机号/邮箱地址"/>
</el-form-item>
<el-form-item label="手机验证码">
<el-form-item label="验证码">
<el-row :gutter="20">
<el-col :span="16">
<el-input v-model="form.code" maxlength="6"/>
</el-col>
<el-col :span="8">
<send-msg size="" :mobile="form.mobile"/>
<send-msg size="" :receiver="form.username"/>
</el-col>
</el-row>
</el-form-item>
@@ -48,7 +48,7 @@ import {computed, ref} from "vue";
import SendMsg from "@/components/SendMsg.vue";
import {ElMessage} from "element-plus";
import {httpPost} from "@/utils/http";
import {validateMobile} from "@/utils/validate";
import {validateEmail, validateMobile} from "@/utils/validate";
const props = defineProps({
show: Boolean,
@@ -61,7 +61,7 @@ const showDialog = computed(() => {
const title = ref('重置密码')
const form = ref({
mobile: '',
username: '',
code: '',
password: '',
repass: ''
@@ -70,11 +70,11 @@ const form = ref({
const emits = defineEmits(['hide']);
const save = () => {
if (!validateMobile(form.value.mobile)) {
return ElMessage.error("请输入正确的手机号码");
if (!validateMobile(form.value.username) && !validateEmail(form.value.username)) {
return ElMessage.error("请输入正确的手机号码/邮箱地址");
}
if (form.value.code === '') {
return ElMessage.error("请输入短信验证码");
return ElMessage.error("请输入验证码");
}
if (form.value.repass !== form.value.password) {
return ElMessage.error("两次输入密码不一致");

View File

@@ -13,10 +13,19 @@
</el-alert>
<el-form :model="form">
<el-form-item label="">
<el-form-item label="转账单号">
<el-input v-model="form.tx_id"/>
</el-form-item>
</el-form>
<el-form :model="form">
<el-form-item label="兑换类别">
<el-radio-group v-model="form.type">
<el-radio label="chat" border>对话聊天</el-radio>
<el-radio label="img" border>AI绘图</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
</div>
<template #footer>
@@ -46,6 +55,7 @@ const showDialog = computed(() => {
const title = ref('众筹码核销')
const form = ref({
tx_id: '',
type: 'chat'
})
const emits = defineEmits(['hide']);

View File

@@ -28,13 +28,13 @@
// 发送短信验证码组件
import {ref} from "vue";
import lodash from 'lodash'
import {validateMobile} from "@/utils/validate";
import {validateEmail, validateMobile} from "@/utils/validate";
import {ElMessage} from "element-plus";
import {httpGet, httpPost} from "@/utils/http";
import CaptchaPlus from "@/components/CaptchaPlus.vue";
const props = defineProps({
mobile: String,
receiver: String,
size: String,
});
const btnText = ref('发送验证码')
@@ -82,8 +82,8 @@ const handleConfirm = (dots) => {
}
const loadCaptcha = () => {
if (!validateMobile(props.mobile)) {
return ElMessage.error("请输入合法的手机号")
if (!validateMobile(props.receiver) && !validateEmail(props.receiver)) {
return ElMessage.error("请输入合法的手机号/邮箱地址")
}
showCaptcha.value = true
@@ -96,8 +96,8 @@ const sendMsg = () => {
}
canSend.value = false
httpPost('/api/sms/code', {mobile: props.mobile, key: captKey.value, dots: dots.value}).then(() => {
ElMessage.success('短信发送成功')
httpPost('/api/sms/code', {receiver: props.receiver, key: captKey.value, dots: dots.value}).then(() => {
ElMessage.success('验证码发送成功')
let time = 120
btnText.value = time
const handler = setInterval(() => {
@@ -112,7 +112,7 @@ const sendMsg = () => {
}, 1000)
}).catch(e => {
canSend.value = true
ElMessage.error('短信发送失败:' + e.message)
ElMessage.error('验证码发送失败:' + e.message)
})
}

View File

@@ -14,8 +14,11 @@
</el-icon>
</el-upload>
</el-row>
<el-form-item label="账户">
<span>{{ user.mobile }}</span>
<el-form-item label="昵称">
<el-input v-model="user['nickname']"/>
</el-form-item>
<el-form-item label="账号">
<span>{{ user.username }}</span>
<el-tooltip
class="box-item"
effect="light"
@@ -41,16 +44,6 @@
<el-tag type="danger">{{ dateFormat(user['expired_time']) }}</el-tag>
</el-form-item>
<el-form-item label="OpenAI API KEY">
<el-input v-model="user.chat_config['api_keys']['OpenAI']"/>
</el-form-item>
<el-form-item label="Azure API KEY">
<el-input v-model="user['chat_config']['api_keys']['Azure']"/>
</el-form-item>
<el-form-item label="ChatGLM API KEY">
<el-input v-model="user['chat_config']['api_keys']['ChatGLM']"/>
</el-form-item>
<el-row class="opt-line">
<el-button color="#47fff1" :dark="false" round @click="save">保存</el-button>
</el-row>
@@ -75,7 +68,6 @@ const user = ref({
mobile: '',
calls: 0,
tokens: 0,
chat_config: {api_keys: {OpenAI: "", Azure: "", ChatGLM: ""}}
})
const vipImg = ref("/images/vip.png")
@@ -84,7 +76,6 @@ onMounted(() => {
// 获取最新用户信息
httpGet('/api/user/profile').then(res => {
user.value = res.data
user.value.chat_config.api_keys = res.data.chat_config.api_keys ?? {OpenAI: "", Azure: "", ChatGLM: ""}
}).catch(e => {
ElMessage.error("获取用户信息失败:" + e.message)
});

View File

@@ -1,74 +0,0 @@
<template>
<van-dialog v-model:show="showDialog"
:title="title"
:show-cancel-button="mobile !== ''"
@confirm="save"
@cancel="close">
<van-cell-group inset>
<van-field
v-model="form.mobile"
label="手机号"
placeholder="请输入手机号"
/>
<van-field
v-model.number="form.code"
center
clearable
label="短信验证码"
placeholder="请输入短信验证码"
>
<template #button>
<send-msg size="small" :mobile="form.mobile"/>
</template>
</van-field>
</van-cell-group>
</van-dialog>
</template>
<script setup>
import {computed, ref} from "vue";
import {httpPost} from "@/utils/http";
import {validateMobile} from "@/utils/validate";
import {showNotify} from "vant";
import SendMsg from "@/components/SendMsg.vue";
const props = defineProps({
show: Boolean,
mobile: String
});
const showDialog = computed(() => {
return props.show
})
const title = ref('绑定手机号')
const form = ref({
mobile: '',
code: ''
})
const emits = defineEmits(['hide']);
const save = () => {
if (!validateMobile(form.value.mobile)) {
return showNotify({type: 'danger', message: '请输入正确的手机号码'});
}
if (form.value.code === '') {
return showNotify({type: "danger", message: '请输入短信验证码'})
}
httpPost('/api/user/bind/mobile', form.value).then(() => {
showNotify({type: 'success', message: '绑定成功', duration: 1000, onClose: emits('hide', false)});
}).catch(e => {
showNotify({type: 'danger', message: '绑定失败:' + e.message, duration: 2000});
})
}
const close = function () {
emits('hide', false);
}
</script>
<style scoped>
</style>

View File

@@ -106,13 +106,13 @@ const routes = [
path: '/admin/user',
name: 'admin-user',
meta: {title: '用户管理'},
component: () => import('@/views/admin/UserList.vue'),
component: () => import('@/views/admin/Users.vue'),
},
{
path: '/admin/role',
name: 'admin-role',
meta: {title: '角色管理'},
component: () => import('@/views/admin/RoleList.vue'),
component: () => import('@/views/admin/Roles.vue'),
},
{
path: '/admin/apikey',
@@ -142,7 +142,7 @@ const routes = [
path: '/admin/reward',
name: 'admin-reward',
meta: {title: '众筹管理'},
component: () => import('@/views/admin/RewardList.vue'),
component: () => import('@/views/admin/Reward.vue'),
},
{
path: '/admin/loginLog',

View File

@@ -84,6 +84,10 @@ export function dateFormat(timestamp, format) {
// 判断数组中是否包含某个元素
export function arrayContains(array, value, compare) {
if (!array) {
return false
}
if (typeof compare !== 'function') {
compare = function (v1, v2) {
return v1 === v2;

View File

@@ -100,6 +100,7 @@ const exportChat = () => {
.chat-export {
display flex
justify-content center
padding 0 20px
.chat-box {
width 800px;
@@ -160,6 +161,12 @@ const exportChat = () => {
padding: 1px
}
}
.chat-item {
img {
max-width 90%
}
}
}
}
}

View File

@@ -43,7 +43,7 @@
<el-dropdown :hide-on-click="true" class="user-info" trigger="click" v-if="isLogin">
<span class="el-dropdown-link">
<el-image :src="loginUser.avatar"/>
<span class="username">{{ '极客学长@' + loginUser.mobile }}</span>
<span class="username">{{ loginUser.nickname }}</span>
<el-icon><ArrowDown/></el-icon>
</span>
<template #dropdown>
@@ -105,12 +105,12 @@
:value="item.id"
/>
</el-select>
<el-button type="primary" @click="newChat">
<el-icon>
<Plus/>
</el-icon>
新建对话
</el-button>
<el-button type="primary" @click="newChat">
<el-icon>
<Plus/>
</el-icon>
新建对话
</el-button>
<el-button type="success" @click="exportChat" plain>
<i class="iconfont icon-export"></i>
@@ -223,6 +223,39 @@
</div>
</el-dialog>
<el-dialog
v-model="showDemoNotice"
:show-close="true"
title="网站公告"
>
<div class="notice">
<el-text type="primary">
注意当前站点仅为开源项目
<a style="color: #F56C6C" href="https://github.com/yangjian102621/chatgpt-plus" target="_blank">ChatPlus</a>
的演示项目本项目单纯就是给大家体验项目功能使用<br/>
体验额度用完之后请不要在当前站点进行任何充值操作<br/>
体验额度用完之后请不要在当前站点进行任何充值操作<br/>
体验额度用完之后请不要在当前站点进行任何充值操作<br/>
如果觉得好用你就花几分钟自己部署一套没有API KEY 的同学可以去
<a href="https://gpt.bemore.lol" target="_blank"
style="font-size: 20px;color:#F56C6C">https://gpt.bemore.lol</a>
购买现在有超级优惠价格远低于 OpenAI 官方<br/>
GPT-3.5GPT-4DALL-E3 绘图......你都可以随意使用无需魔法<br/>
本项目源码地址
<a href="https://github.com/yangjian102621/chatgpt-plus" target="_blank">
https://github.com/yangjian102621/chatgpt-plus
</a>
</el-text>
<p style="text-align: right">
<el-button @click="notShow" type="success" plain>我知道了不再显示</el-button>
</p>
</div>
</el-dialog>
<config-dialog v-if="isLogin" :show="showConfigDialog" :models="models" @hide="showConfigDialog = false"/>
</div>
@@ -237,7 +270,8 @@ import {
Check,
Close,
Delete,
Edit, Plus,
Edit,
Plus,
Promotion,
RefreshRight,
Search,
@@ -278,6 +312,8 @@ const isLogin = ref(false)
const showHello = ref(true)
const textInput = ref(null)
const showFeedbackDialog = ref(false)
const showDemoNotice = ref(false)
const showNoticeKey = ref("SHOW_DEMO_NOTICE_")
if (isMobile()) {
router.replace("/mobile")
@@ -326,6 +362,10 @@ onMounted(() => {
httpGet("/api/admin/config/get?key=system").then(res => {
title.value = res.data.title
const show = localStorage.getItem(showNoticeKey.value + loginUser.value.username)
if (!show) {
showDemoNotice.value = res.data['show_demo_notice']
}
}).catch(e => {
ElMessage.error("获取系统配置失败:" + e.message)
})
@@ -606,7 +646,7 @@ const connect = function (chat_id, role_id) {
// 获取 token
const reply = chatData.value[chatData.value.length - 1]
httpPost("/api/chat/tokens", {text: "", model: getModelValue(modelID.value)}).then(res => {
httpPost("/api/chat/tokens", {text: "", model: getModelValue(modelID.value), chat_id: chat_id}).then(res => {
reply['created_at'] = new Date().getTime();
reply['tokens'] = res.data;
// 将聊天框的滚动条滑动到最底部
@@ -847,6 +887,12 @@ const getModelValue = (model_id) => {
}
return ""
}
const notShow = () => {
localStorage.setItem(showNoticeKey.value + loginUser.value.username, true)
showDemoNotice.value = false
}
</script>
<style scoped lang="stylus">

View File

@@ -2,9 +2,7 @@
<div class="home">
<div class="navigator">
<div class="logo">
<el-link href="/">
<el-image :src="logo"/>
</el-link>
<el-image :src="logo"/>
<div class="divider"></div>
</div>
<ul class="nav-items">

View File

@@ -711,7 +711,6 @@ const variation = (index, item) => {
const send = (url, index, item) => {
httpPost(url, {
index: index,
task_id: item.task_id,
channel_id: item.channel_id,
message_id: item.message_id,
message_hash: item.hash,

View File

@@ -9,7 +9,7 @@
<div class="header">{{ title }}</div>
<div class="content">
<div class="block">
<el-input placeholder="手机号" size="large" maxlength="11" v-model="username" autocomplete="off">
<el-input placeholder="手机号/邮箱地址" size="large" v-model="username" autocomplete="off">
<template #prefix>
<el-icon>
<UserFilled/>
@@ -59,7 +59,7 @@ import FooterBar from "@/components/FooterBar.vue";
import {isMobile} from "@/utils/libs";
import {checkSession} from "@/action/session";
import {setUserToken} from "@/store/session";
import {validateMobile} from "@/utils/validate";
import {validateEmail, validateMobile} from "@/utils/validate";
import {prevRoute} from "@/router";
import ResetPass from "@/components/ResetPass.vue";
@@ -92,8 +92,8 @@ onUnmounted(() => {
})
const login = function () {
if (!validateMobile(username.value)) {
return ElMessage.error('请输入合法的手机号');
if (!validateMobile(username.value) && !validateEmail(username.value)) {
return ElMessage.error("请输入合法的手机号/邮箱地址")
}
if (password.value.trim() === '') {
return ElMessage.error('请输入密码');

View File

@@ -14,7 +14,7 @@
<el-button type="primary" @click="showPasswordDialog = true">修改密码</el-button>
</el-col>
<el-col :span="12">
<el-button type="primary" @click="showBindMobileDialog = true">绑定手机</el-button>
<el-button type="primary" @click="showBindMobileDialog = true">更改账</el-button>
</el-col>
<el-col :span="12">
<el-button type="primary" v-if="enableReward" @click="showRewardDialog = true">加入众筹</el-button>
@@ -93,7 +93,7 @@
<password-dialog v-if="isLogin" :show="showPasswordDialog" @hide="showPasswordDialog = false"
@logout="logout"/>
<bind-mobile v-if="isLogin" :show="showBindMobileDialog" :mobile="user.mobile"
<bind-mobile v-if="isLogin" :show="showBindMobileDialog" :username="user.username"
@hide="showBindMobileDialog = false"/>
<reward-verify v-if="isLogin" :show="showRewardVerifyDialog" @hide="showRewardVerifyDialog = false"/>
@@ -158,7 +158,7 @@ import LoginDialog from "@/components/LoginDialog.vue";
import {checkSession} from "@/action/session";
import UserProfile from "@/components/UserProfile.vue";
import PasswordDialog from "@/components/PasswordDialog.vue";
import BindMobile from "@/components/BindMobile.vue";
import BindMobile from "@/components/ResetAccount.vue";
import RewardVerify from "@/components/RewardVerify.vue";
import {useRouter} from "vue-router";
import {removeUserToken} from "@/store/session";

View File

@@ -12,9 +12,9 @@
<div class="content">
<el-form :model="formData" label-width="120px" ref="formRef">
<div class="block">
<el-input placeholder="手机号码"
size="large" maxlength="11"
v-model="formData.mobile"
<el-input :placeholder="placeholder"
size="large"
v-model="formData.username"
autocomplete="off">
<template #prefix>
<el-icon>
@@ -49,10 +49,10 @@
</el-input>
</div>
<div class="block" v-if="enableMsg">
<div class="block">
<el-row :gutter="10">
<el-col :span="12">
<el-input placeholder="手机验证码"
<el-input placeholder="验证码"
size="large" maxlength="30"
v-model="formData.code"
autocomplete="off">
@@ -64,7 +64,7 @@
</el-input>
</el-col>
<el-col :span="12">
<send-msg size="large" :mobile="formData.mobile"/>
<send-msg size="large" :receiver="formData.username"/>
</el-col>
</el-row>
</div>
@@ -118,41 +118,45 @@
import {ref} from "vue";
import {Checked, Iphone, Lock, Message} from "@element-plus/icons-vue";
import {httpGet, httpPost} from "@/utils/http";
import {ElMessage, ElNotification} from "element-plus";
import {ElMessage} from "element-plus";
import {useRouter} from "vue-router";
import FooterBar from "@/components/FooterBar.vue";
import SendMsg from "@/components/SendMsg.vue";
import {validateMobile} from "@/utils/validate";
import {isMobile} from "@/utils/libs";
import {validateEmail, validateMobile} from "@/utils/validate";
import {arrayContains} from "@/utils/libs";
import {setUserToken} from "@/store/session";
import {checkSession} from "@/action/session";
const router = useRouter();
const title = ref('ChatPlus 用户注册');
const formData = ref({
mobile: '',
username: '',
password: '',
code: '',
repass: '',
invite_code: router.currentRoute.value.query['invite_code'],
})
const formRef = ref(null)
const enableMsg = ref(false)
const enableMobile = ref(false)
const enableEmail = ref(false)
const enableRegister = ref(true)
const wxImg = ref("/images/wx.png")
const ways = []
const placeholder = ref("用户名:")
httpGet("/api/admin/config/get?key=system").then(res => {
if (res.data) {
enableMsg.value = res.data['enabled_msg']
enableRegister.value = res.data['enabled_register']
if (res.data['force_invite'] && !formData.value.invite_code) {
ElNotification({
title: '提示:',
dangerouslyUseHTMLString: true,
message: '当前系统开启了强制邀请注册功能,必须有邀请码才能注册哦。扫描下面二维码获取邀请码。<br/> <img alt="qrcode" src="/images/wx.png" />',
type: 'info',
duration: 5000,
})
const registerWays = res.data['register_ways']
if (arrayContains(registerWays, "mobile")) {
enableMobile.value = true
ways.push("手机号")
}
if (arrayContains(registerWays, "email")) {
enableEmail.value = true
ways.push("邮箱地址")
}
placeholder.value += ways.join("/")
if (ways.length === 0) {
enableRegister.value = false
}
}
}).catch(e => {
@@ -165,9 +169,19 @@ httpGet("/api/invite/hits", {code: formData.value.invite_code}).then(() => {
const register = function () {
if (!validateMobile(formData.value.mobile)) {
if (formData.value.username === '') {
return ElMessage.error('请输入用户名');
}
if (!enableMobile.value && !validateEmail(formData.value.username)) {
return ElMessage.error('请输入合法的邮箱地址');
}
if (!enableEmail.value && !validateMobile(formData.value.username)) {
return ElMessage.error('请输入合法的手机号');
}
if (!validateMobile(formData.value.username) && !validateEmail(formData.value.username)) {
return ElMessage.error('请输入合法的手机号或者邮箱地址');
}
if (formData.value.password.length < 8) {
return ElMessage.error('密码的长度为8-16个字符');
}
@@ -175,7 +189,7 @@ const register = function () {
return ElMessage.error('两次输入密码不一致');
}
if (enableMsg.value && formData.value.code === '') {
if (formData.value.code === '') {
return ElMessage.error('请输入短信验证码');
}
httpPost('/api/user/register', formData.value).then((res) => {

View File

@@ -3,22 +3,33 @@
<div class="handle-box">
<el-button type="primary" :icon="Plus" @click="add">新增</el-button>
<a href="https://gpt.bemore.lol" target="_blank" style="margin-left: 10px">
<el-button type="success" :icon="ShoppingCart" @click="add" plain>购买API-KEY</el-button>
</a>
</div>
<el-row>
<el-table :data="items" :row-key="row => row.id" table-layout="auto">
<el-table-column prop="platform" label="所属平台"/>
<el-table-column prop="value" label="KEY"/>
<el-table-column prop="name" label="名称"/>
<el-table-column prop="value" label="KEY">
<template #default="scope">
<el-tooltip class="box-item"
effect="dark"
:content="scope.row.api_url"
placement="top">{{ scope.row.value }}
</el-tooltip>
</template>
</el-table-column>
<el-table-column prop="type" label="用途">
<template #default="scope">
<el-tag v-if="scope.row.type === 'chat'">聊天</el-tag>
<el-tag v-else-if="scope.row.type === 'img'" type="success">绘图</el-tag>
</template>
</el-table-column>
<el-table-column label="创建时间">
<el-table-column prop="use_proxy" label="使用代理">
<template #default="scope">
<span>{{ dateFormat(scope.row['created_at']) }}</span>
<el-switch v-model="scope.row['use_proxy']" @change="set('use_proxy',scope.row)"/>
</template>
</el-table-column>
@@ -28,6 +39,11 @@
<el-tag v-else>未使用</el-tag>
</template>
</el-table-column>
<el-table-column prop="enabled" label="启用状态">
<template #default="scope">
<el-switch v-model="scope.row['enabled']" @change="set('enabled',scope.row)"/>
</template>
</el-table-column>
<el-table-column label="操作" width="180">
<template #default="scope">
@@ -51,31 +67,54 @@
:closable="false"
show-icon
style="margin-bottom: 10px; font-size:14px;">
<p><b>注意:</b>如果是百度文心一言平台,需要用竖线(|)将 API KeySecret Key 串接起来填入!</p>
<p><b>注意:</b>如果是讯飞星火大模型,需要用竖线(|)将 APPID, APIKeyAPISecret 按照顺序串接起来填入!</p>
<p><b>注意:</b>如果是百度文心一言平台,API-KEY 为 APIKey|SecretKey,中间用竖线(|)连接</p>
<p><b>注意:</b>如果是讯飞星火大模型,API-KEY 为 AppId|APIKey|APISecret,中间用竖线(|)连接</p>
</el-alert>
<el-form :model="item" label-width="120px" ref="formRef" :rules="rules">
<el-form-item label="所属平台" prop="platform">
<el-select v-model="item.platform" placeholder="请选择平台">
<el-select v-model="item.platform" placeholder="请选择平台" @change="changePlatform">
<el-option v-for="item in platforms" :value="item.value" :label="item.name" :key="item.value">{{
item.name
}}
</el-option>
</el-select>
</el-form-item>
<el-form-item label="API KEY" prop="value">
<el-input v-model="item.value" autocomplete="off"/>
<el-form-item label="名称" prop="name">
<el-input v-model="item.name" autocomplete="off"/>
</el-form-item>
<el-form-item label="用途" prop="type">
<el-select v-model="item.type" placeholder="请选择用途">
<el-select v-model="item.type" placeholder="请选择用途" @change="changePlatform">
<el-option v-for="item in types" :value="item.value" :label="item.name" :key="item.value">{{
item.name
}}
</el-option>
</el-select>
</el-form-item>
<el-form-item label="API KEY" prop="value">
<el-input v-model="item.value" autocomplete="off"/>
</el-form-item>
<el-form-item label="API URL" prop="api_url">
<el-input v-model="item.api_url" autocomplete="off"
placeholder="如果你用了第三方的 API 中转这里填写中转地址"/>
</el-form-item>
<el-form-item label="使用代理" prop="use_proxy">
<el-switch v-model="item.use_proxy"/>
<el-tooltip
effect="dark"
content="是否使用代理访问 API URLOpenAI 官方API需要开启代理访问"
raw-content
placement="right"
>
<el-icon>
<InfoFilled/>
</el-icon>
</el-tooltip>
</el-form-item>
<el-form-item label="启用状态" prop="enable">
<el-switch v-model="item.enabled"/>
</el-form-item>
</el-form>
<template #footer>
@@ -93,7 +132,7 @@ import {reactive, ref} from "vue";
import {httpGet, httpPost} from "@/utils/http";
import {ElMessage} from "element-plus";
import {dateFormat, disabledDate, removeArrayItem} from "@/utils/libs";
import {Plus} from "@element-plus/icons-vue";
import {InfoFilled, Plus, ShoppingCart} from "@element-plus/icons-vue";
// 变量定义
const items = ref([])
@@ -101,6 +140,7 @@ const item = ref({})
const showDialog = ref(false)
const rules = reactive({
platform: [{required: true, message: '请选择平台', trigger: 'change',}],
name: [{required: true, message: '请输入名称', trigger: 'change',}],
type: [{required: true, message: '请选择用途', trigger: 'change',}],
value: [{required: true, message: '请输入 API KEY 值', trigger: 'change',}]
})
@@ -108,11 +148,32 @@ const loading = ref(true)
const formRef = ref(null)
const title = ref("")
const platforms = ref([
{name: "OpenAIChatGPT", value: "OpenAI"},
{name: "讯飞星火大模型", value: "XunFei"},
{name: "清华智普ChatGLM", value: "ChatGLM"},
{name: "百度文心一言", value: "Baidu"},
{name: "微软Azure", value: "Azure"},
{
name: "OpenAIChatGPT",
value: "OpenAI",
api_url: "https://gpt.bemore.lol/v1/chat/completions",
img_url: "https://gpt.bemore.lol/v1/images/generations"
},
{
name: "【讯飞】星火大模型",
value: "XunFei",
api_url: "wss://spark-api.xf-yun.com/{version}/chat"
},
{
name: "【清华智普】ChatGLM",
value: "ChatGLM",
api_url: "https://open.bigmodel.cn/api/paas/v3/model-api/{model}/sse-invoke"
},
{
name: "【百度】文心一言",
value: "Baidu",
api_url: "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/{model}"
},
{
name: "【微软】Azure",
value: "Azure",
api_url: "https://chat-bot-api.openai.azure.com/openai/deployments/{model}/chat/completions?api-version=2023-05-15"
},
])
const types = ref([
{name: "聊天", value: "chat"},
@@ -176,6 +237,33 @@ const remove = function (row) {
ElMessage.error("删除失败:" + e.message)
})
}
const set = (filed, row) => {
httpPost('/api/admin/apikey/set', {id: row.id, filed: filed, value: row[filed]}).then(() => {
ElMessage.success("操作成功!")
}).catch(e => {
ElMessage.error("操作失败:" + e.message)
})
}
const changePlatform = () => {
let platform = null
for (let v of platforms.value) {
if (v.value === item.value.platform) {
platform = v
break
}
}
if (platform !== null) {
if (item.value.type === "chat") {
item.value.api_url = platform.api_url
} else if (platform.img_url) {
item.value.api_url = platform.img_url
}
}
}
</script>
<style lang="stylus" scoped>
@@ -194,6 +282,14 @@ const remove = function (row) {
.el-select {
width: 100%
}
}
.el-form {
.el-form-item__content {
.el-icon {
padding-left 10px;
}
}
}
</style>

View File

@@ -21,6 +21,25 @@
</template>
</el-table-column>
<el-table-column label="兑换详情">
<template #default="scope">
<el-tag v-if="scope.row['exchange']['calls'] > 0">聊天{{ scope.row['exchange']['calls'] }}</el-tag>
<el-tag v-else-if="scope.row['exchange']['img_calls'] > 0" type="success">
绘图{{ scope.row['exchange']['img_calls'] }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="180">
<template #default="scope">
<el-popconfirm title="确定要删除当前记录吗?" @confirm="remove(scope.row)">
<template #reference>
<el-button size="small" type="danger">删除</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
</el-row>
@@ -31,7 +50,7 @@
import {ref} from "vue";
import {httpGet} from "@/utils/http";
import {ElMessage} from "element-plus";
import {dateFormat} from "@/utils/libs";
import {dateFormat, removeArrayItem} from "@/utils/libs";
//
const items = ref([])
@@ -52,6 +71,16 @@ httpGet('/api/admin/reward/list').then((res) => {
ElMessage.error("获取数据失败");
})
const remove = function (row) {
httpGet('/api/admin/reward/remove?id=' + row.id).then(() => {
ElMessage.success("删除成功!")
items.value = removeArrayItem(items.value, row, (v1, v2) => {
return v1.id === v2.id
})
}).catch((e) => {
ElMessage.error("删除失败:" + e.message)
})
}
</script>
<style lang="stylus" scoped>

View File

@@ -23,8 +23,7 @@
<el-table-column label="角色标识" prop="key"/>
<el-table-column label="启用状态">
<template #default="scope">
<el-tag v-if="scope.row.enable" type="success">启用</el-tag>
<el-tag type="danger" v-else>禁用</el-tag>
<el-switch v-model="scope.row['enable']" @change="roleSet('enable',scope.row)"/>
</template>
</el-table-column>
<el-table-column label="角色图标" prop="icon">
@@ -201,21 +200,11 @@ onMounted(() => {
})
})
const editSort = function (event, row) {
event.stopPropagation()
editRow.value.id = row.id
editRow.value.sort = row.sort
}
const updateSort = function (row) {
if (row.sort === editRow.value.sort) {
editRow.value.id = 0
return
}
httpPost('/api/admin/role/sort', {"id": row.id, "sort": row.sort}).then(() => {
editRow.value.id = 0
}).catch(() => {
ElMessage.error("更新失败!")
const roleSet = (filed, row) => {
httpPost('/api/admin/role/set', {id: row.id, filed: filed, value: row[filed]}).then(() => {
ElMessage.success("操作成功!")
}).catch(e => {
ElMessage.error("操作失败:" + e.message)
})
}

View File

@@ -27,34 +27,11 @@
<el-form-item label="VIP每月绘图次数" prop="vip_month_img_calls">
<el-input v-model.number="system['vip_month_img_calls']" placeholder="VIP用户每月赠送绘图次数"/>
</el-form-item>
<el-form-item label="开放注册服务" prop="enabled_register">
<el-switch v-model="system['enabled_register']"/>
</el-form-item>
<el-form-item label="强制邀请码注册" prop="force_invite">
<el-switch v-model="system['force_invite']"/>
<el-tooltip
effect="dark"
content="开启之后,用户必须使用邀请码才能注册"
raw-content
placement="right"
>
<el-icon>
<InfoFilled/>
</el-icon>
</el-tooltip>
</el-form-item>
<el-form-item label="短信服务" prop="enabled_msg">
<el-switch v-model="system['enabled_msg']"/>
<el-tooltip
effect="dark"
content="是否在注册时候开启短信验证码服务"
raw-content
placement="right"
>
<el-icon>
<InfoFilled/>
</el-icon>
</el-tooltip>
<el-form-item label="注册方式" prop="register_ways">
<el-checkbox-group v-model="system['register_ways']">
<el-checkbox label="mobile">手机注册</el-checkbox>
<el-checkbox label="email">邮箱注册</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="启用众筹功能" prop="enabled_reward">
@@ -71,26 +48,35 @@
</el-tooltip>
</el-form-item>
<el-form-item label="收款二维码" prop="reward_img">
<el-input v-model="system['reward_img']" placeholder="众筹收款二维码地址">
<template #append>
<el-upload
:auto-upload="true"
:show-file-list="false"
:http-request="uploadRewardImg"
>
<el-icon class="uploader-icon">
<UploadFilled/>
</el-icon>
</el-upload>
</template>
</el-input>
</el-form-item>
<el-form-item label="启用支付宝" prop="enabled_alipay">
<el-switch v-model="system['enabled_alipay']"/>
<div v-if="system['enabled_reward']">
<el-form-item label="单次对话价格" prop="chat_call_price">
<el-input v-model="system['chat_call_price']" placeholder="众筹金额跟对话次数的兑换比例"/>
</el-form-item>
<el-form-item label="单次绘图价格" prop="img_call_price">
<el-input v-model="system['img_call_price']" placeholder="众筹金额跟绘图次数的兑换比例"/>
</el-form-item>
<el-form-item label="收款二维码" prop="reward_img">
<el-input v-model="system['reward_img']" placeholder="众筹收款二维码地址">
<template #append>
<el-upload
:auto-upload="true"
:show-file-list="false"
:http-request="uploadRewardImg"
>
<el-icon class="uploader-icon">
<UploadFilled/>
</el-icon>
</el-upload>
</template>
</el-input>
</el-form-item>
</div>
<el-form-item label="显示演示公告" prop="show_demo_notice">
<el-switch v-model="system['show_demo_notice']"/>
<el-tooltip
effect="dark"
content="是否启用支付宝支付功能,<br />请先在 config.toml 配置文件配置支付秘钥"
content="是否在聊天首页显示演示 Demo 公告,这是专为作者自己开发的功能"
raw-content
placement="right"
>
@@ -99,6 +85,7 @@
</el-icon>
</el-tooltip>
</el-form-item>
<el-form-item label="订单超时时间" prop="order_pay_timeout">
<div class="tip-input">
<el-input v-model.number="system['order_pay_timeout']" placeholder="单位:秒"/>
@@ -180,9 +167,6 @@
</el-form-item>
<el-divider content-position="center">OpenAI</el-divider>
<el-form-item label="API 地址" prop="open_ai.api_url">
<el-input v-model="chat['open_ai']['api_url']" placeholder="支持变量,{model} => 模型名称"/>
</el-form-item>
<el-form-item label="模型创意度">
<el-slider v-model="chat['open_ai']['temperature']" :max="2" :step="0.1"/>
<div class="tip">值越大 AI 回答越发散值越小回答越保守建议保持默认值</div>
@@ -192,9 +176,6 @@
</el-form-item>
<el-divider content-position="center">Azure</el-divider>
<el-form-item label="API 地址" prop="azure.api_url">
<el-input v-model="chat['azure']['api_url']" placeholder="支持变量,{model} => 模型名称"/>
</el-form-item>
<el-form-item label="模型创意度">
<el-slider v-model="chat['azure']['temperature']" :max="2" :step="0.1"/>
<div class="tip">值越大 AI 回答越发散值越小回答越保守建议保持默认值</div>
@@ -204,9 +185,6 @@
</el-form-item>
<el-divider content-position="center">ChatGLM</el-divider>
<el-form-item label="API 地址" prop="chat_gml.api_url">
<el-input v-model="chat['chat_gml']['api_url']" placeholder="支持变量,{model} => 模型名称"/>
</el-form-item>
<el-form-item label="模型创意度">
<el-slider v-model="chat['chat_gml']['temperature']" :max="1" :step="0.01"/>
<div class="tip">值越大 AI 回答越发散值越小回答越保守建议保持默认值</div>
@@ -216,9 +194,6 @@
</el-form-item>
<el-divider content-position="center">文心一言</el-divider>
<el-form-item label="API 地址" prop="baidu.api_url">
<el-input v-model="chat['baidu']['api_url']" placeholder="支持变量,{model} => 模型名称"/>
</el-form-item>
<el-form-item label="模型创意度">
<el-slider v-model="chat['baidu']['temperature']" :max="1" :step="0.01"/>
<div class="tip">值越大 AI 回答越发散值越小回答越保守建议保持默认值</div>
@@ -228,9 +203,6 @@
</el-form-item>
<el-divider content-position="center">讯飞星火</el-divider>
<el-form-item label="API 地址" prop="xun_fei.api_url">
<el-input v-model="chat['xun_fei']['api_url']" placeholder="支持变量,{model} => 模型名称"/>
</el-form-item>
<el-form-item label="模型创意度">
<el-slider v-model="chat['xun_fei']['temperature']" :max="1" :step="0.1"/>
<div class="tip">值越大 AI 回答越发散值越小回答越保守建议保持默认值</div>
@@ -240,10 +212,7 @@
</el-form-item>
<el-divider content-position="center">AI绘图</el-divider>
<el-form-item label="DALL-E3 API地址">
<el-input v-model="chat['dall_api_url']" placeholder="OpenAI官方API需要配合代理使用"/>
</el-form-item>
<el-form-item label="默认出图数量">
<el-form-item label="DALL-E3出图数量">
<el-input v-model.number="chat['dall_img_num']" placeholder="调用 DALL E3 API 传入的出图数量"/>
</el-form-item>
<el-form-item style="text-align: right">
@@ -263,11 +232,11 @@ import {InfoFilled, UploadFilled} from "@element-plus/icons-vue";
const system = ref({models: []})
const chat = ref({
open_ai: {api_url: "", temperature: 1, max_tokens: 1024},
azure: {api_url: "", temperature: 1, max_tokens: 1024},
chat_gml: {api_url: "", temperature: 0.95, max_tokens: 1024},
baidu: {api_url: "", temperature: 0.95, max_tokens: 1024},
xun_fei: {api_url: "", temperature: 0.5, max_tokens: 1024},
open_ai: {temperature: 1, max_tokens: 1024},
azure: {temperature: 1, max_tokens: 1024},
chat_gml: {temperature: 0.95, max_tokens: 1024},
baidu: {temperature: 0.95, max_tokens: 1024},
xun_fei: {temperature: 0.5, max_tokens: 1024},
context_deep: 0,
enable_context: true,
enable_history: true,
@@ -312,6 +281,8 @@ const save = function (key) {
if (key === 'system') {
systemFormRef.value.validate((valid) => {
if (valid) {
system.value['img_call_price'] = parseFloat(system.value['img_call_price']) ?? 0
system.value['chat_call_price'] = parseFloat(system.value['chat_call_price']) ?? 0
httpPost('/api/admin/config/update', {key: key, config: system.value}).then(() => {
ElMessage.success("操作成功!")
}).catch(e => {

View File

@@ -1,7 +1,7 @@
<template>
<div class="container user-list" v-loading="loading">
<div class="handle-box">
<el-input v-model="query.mobile" placeholder="手机号码" class="handle-input mr10"></el-input>
<el-input v-model="query.username" placeholder="账号" class="handle-input mr10"></el-input>
<el-button type="primary" :icon="Search" @click="handleSearch">搜索</el-button>
<el-button type="success" :icon="Plus" @click="addUser">新增用户</el-button>
@@ -13,7 +13,7 @@
<el-table-column type="selection" width="38"/>
<el-table-column prop="mobile" label="账号">
<template #default="scope">
<span>{{ scope.row.mobile }}</span>
<span>{{ scope.row.username }}</span>
<el-image v-if="scope.row.vip" :src="vipImg" style="height: 20px;position: relative; top:5px; left: 5px"/>
</template>
</el-table-column>
@@ -69,7 +69,7 @@
width="50%"
>
<el-form :model="user" label-width="100px" ref="userEditFormRef" :rules="rules">
<el-form-item label="手机号:" prop="mobile">
<el-form-item label="手机号:" prop="username">
<el-input v-model="user.mobile" autocomplete="off"/>
</el-form-item>
<el-form-item v-if="add" label="密码:" prop="password">
@@ -150,7 +150,7 @@
>
<el-form label-width="100px" ref="userEditFormRef">
<el-form-item label="账户:">
<el-input v-model="pass.mobile" autocomplete="off" readonly disabled/>
<el-input v-model="pass.username" autocomplete="off" readonly disabled/>
</el-form-item>
<el-form-item label="新密码:">
@@ -177,13 +177,13 @@ import {Plus, Search} from "@element-plus/icons-vue";
//
const users = ref({page: 1, page_size: 15, items: []})
const query = ref({mobile: '', page: 1, page_size: 15})
const query = ref({username: '', page: 1, page_size: 15})
const title = ref('添加用户')
const vipImg = ref("/images/vip.png")
const add = ref(true)
const user = ref({chat_roles: [], chat_models: []})
const pass = ref({mobile: '', password: '', id: 0})
const pass = ref({username: '', password: '', id: 0})
const roles = ref([])
const models = ref([])
const showUserEditDialog = ref(false)
@@ -191,7 +191,7 @@ const showResetPassDialog = ref(false)
const rules = reactive({
nickname: [{required: true, message: '请输入昵称', trigger: 'change',}],
password: [{required: true, message: '请输入密码', trigger: 'change',}],
mobile: [{required: true, message: '请输入手机号码', trigger: 'change',}],
username: [{required: true, message: '请输入手机号码', trigger: 'change',}],
calls: [
{required: true, message: '请输入提问次数'},
{type: 'number', message: '请输入有效数字'},
@@ -309,7 +309,7 @@ const handleSelectionChange = function (rows) {
const resetPass = (row) => {
showResetPassDialog.value = true
pass.value.id = row.id
pass.value.mobile = row.mobile
pass.value.username = row.mobile
}
const doResetPass = () => {

View File

@@ -73,8 +73,6 @@
<van-field v-model="tmpChatTitle" label="" placeholder="请输入对话标题" class="field"/>
</van-dialog>
<bind-mobile v-if="isLogin" :show="showBindMobileDialog" :mobile="loginUser.mobile"
@hide="showBindMobileDialog = false"/>
</div>
</template>
@@ -86,7 +84,6 @@ import {checkSession} from "@/action/session";
import {router} from "@/router";
import {setChatConfig} from "@/store/chat";
import {removeArrayItem} from "@/utils/libs";
import BindMobile from "@/components/mobile/BindMobile.vue";
const title = ref("会话列表")
const chatName = ref("")
@@ -101,7 +98,6 @@ const roles = ref([])
const models = ref([])
const showPicker = ref(false)
const columns = ref([roles.value, models.value])
const showBindMobileDialog = ref(false)
const showEditChat = ref(false)
const item = ref({})
const tmpChatTitle = ref("")

View File

@@ -52,9 +52,6 @@
<el-col :span="12">
<el-button type="primary" @click="showPasswordDialog = true">修改密码</el-button>
</el-col>
<el-col :span="12">
<el-button type="primary" @click="showBindMobileDialog = true">绑定手机号</el-button>
</el-col>
<el-col :span="12">
<el-button type="primary" v-if="enableReward" @click="showRewardDialog = true">加入众筹</el-button>
</el-col>
@@ -72,8 +69,6 @@
<login-dialog :show="showLoginDialog" @hide="showLoginDialog = false"/>
<password-dialog v-if="isLogin" :show="showPasswordDialog" width="100%" @hide="showPasswordDialog = false"
@logout="logout"/>
<bind-mobile v-if="isLogin" :show="showBindMobileDialog" width="100%" :mobile="user.mobile"
@hide="showBindMobileDialog = false"/>
<reward-verify v-if="isLogin" :show="showRewardVerifyDialog" @hide="showRewardVerifyDialog = false"/>
<el-dialog
v-model="showRewardDialog"
@@ -135,11 +130,9 @@ import LoginDialog from "@/components/LoginDialog.vue";
import {checkSession} from "@/action/session";
import UserProfile from "@/components/UserProfile.vue";
import PasswordDialog from "@/components/PasswordDialog.vue";
import BindMobile from "@/components/BindMobile.vue";
import RewardVerify from "@/components/RewardVerify.vue";
import {useRouter} from "vue-router";
import {removeUserToken} from "@/store/session";
import UserOrder from "@/components/UserOrder.vue";
import CountDown from "@/components/CountDown.vue";
const listBoxHeight = window.innerHeight - 97
@@ -151,7 +144,6 @@ const enableReward = ref(false) // 是否启用众筹功能
const rewardImg = ref('/images/reward.png')
const qrcode = ref("")
const showPasswordDialog = ref(false);
const showBindMobileDialog = ref(false);
const showRewardDialog = ref(false);
const showRewardVerifyDialog = ref(false);
const text = ref("")