mirror of
https://github.com/yangjian102621/geekai.git
synced 2025-11-10 03:03:43 +08:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a56621ec3 | ||
|
|
a398e7a550 | ||
|
|
96816c12ca | ||
|
|
9984926f69 | ||
|
|
a2a6081027 | ||
|
|
5a10ed37a7 | ||
|
|
1a9dd9de0b | ||
|
|
0dae5bef71 | ||
|
|
b4413ed726 | ||
|
|
5e1fe88b8b | ||
|
|
91ed41b536 | ||
|
|
024c0032eb | ||
|
|
4a9f7e3bce | ||
|
|
cf4dcc34ec | ||
|
|
4d612c15af | ||
|
|
8aec87cc02 | ||
|
|
442e411cde | ||
|
|
acec0194de | ||
|
|
8557f5b94a | ||
|
|
efd4ab46f5 | ||
|
|
f0994ba457 |
@@ -1,4 +1,12 @@
|
|||||||
# 更新日志
|
# 更新日志
|
||||||
|
## v3.2.1
|
||||||
|
* 功能优化:切换角色和模型的时候自动创建新的对话
|
||||||
|
* Bug修复:修复文件上传失败No such file bug
|
||||||
|
* 功能新增:MidJourney 绘画页面新增提示词翻译功能,新增多个绘画参数
|
||||||
|
* Bug修复:[PC端对话在刷新后异常](https://github.com/yangjian102621/chatgpt-plus/issues/59)
|
||||||
|
* 功能新增:增加 arm64 架构打包脚本
|
||||||
|
* 功能新增:支持 dall-e3 绘图的 API 地址自定义配置
|
||||||
|
* 功能新增:新增虎皮椒支付功能接入,支持微信和支付宝通道
|
||||||
|
|
||||||
## v3.2.0
|
## v3.2.0
|
||||||
* 功能新增:新增邀请注册功能
|
* 功能新增:新增邀请注册功能
|
||||||
|
|||||||
19
README.md
19
README.md
@@ -13,6 +13,11 @@ ChatGLM,讯飞星火,文心一言等多个平台的大语言模型。集成了
|
|||||||
* 集成插件 API 功能,可结合大语言模型的 function 功能开发各种强大的插件,已内置实现了微博热搜,今日头条,今日早报和 AI
|
* 集成插件 API 功能,可结合大语言模型的 function 功能开发各种强大的插件,已内置实现了微博热搜,今日头条,今日早报和 AI
|
||||||
绘画函数插件。
|
绘画函数插件。
|
||||||
|
|
||||||
|
## 关于部署镜像申明
|
||||||
|
|
||||||
|
由于目前部署人数越来越多,本人的阿里云镜像仓库流量不够支撑大家使用了。所以从 v3.2.0 版本开始,一键部署脚本和部署镜像将只提供给 **[付费技术交流群]** 内用户使用。
|
||||||
|
代码依旧是全部开源的,大家可自行编译打包镜像。
|
||||||
|
|
||||||
## 功能截图
|
## 功能截图
|
||||||
|
|
||||||
### PC 端聊天界面
|
### PC 端聊天界面
|
||||||
@@ -90,22 +95,16 @@ ChatGLM,讯飞星火,文心一言等多个平台的大语言模型。集成了
|
|||||||
|
|
||||||
## 项目文档
|
## 项目文档
|
||||||
|
|
||||||
chatgpt-plus v3.2.0 一键部署脚本来了,真的只需运行一条命令,就可以完成部署:
|
详细的部署和开发文档请参考 [**ChatGPT-Plus 文档**](https://ai.r9it.com/docs/)。
|
||||||
```shell
|
|
||||||
bash -c "$(curl -fsSL https://img.r9it.com/tmp/install-v3.2.0-24e4849229.sh)"
|
|
||||||
```
|
|
||||||
目前只支持 Ubuntu 系统,推荐 Ubuntu 22.04 LTS
|
|
||||||
|
|
||||||
详细部署文档请参考 [ChatGPT-Plus 文档](https://ai.r9it.com/docs/)。
|
加微信进入微信讨论群可获取 **一键部署脚本(添加好友时请注明来自Github!!!)。**
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## 参与贡献
|
## 参与贡献
|
||||||
|
|
||||||
个人的力量始终有限,任何形式的贡献都是欢迎的,包括但不限于贡献代码,优化文档,提交 issue 和 PR 等。
|
个人的力量始终有限,任何形式的贡献都是欢迎的,包括但不限于贡献代码,优化文档,提交 issue 和 PR 等。
|
||||||
|
|
||||||
如果有兴趣的话,也可以加微信进入微信讨论群(**添加好友时请注明来自Github!!!**)。
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
#### 特此声明:由于个人时间有限,不接受在微信或者微信群给开发者提 Bug,有问题或者优化建议请提交 Issue 和 PR。非常感谢您的配合!
|
#### 特此声明:由于个人时间有限,不接受在微信或者微信群给开发者提 Bug,有问题或者优化建议请提交 Issue 和 PR。非常感谢您的配合!
|
||||||
|
|
||||||
### Commit 类型
|
### Commit 类型
|
||||||
|
|||||||
19
api/Makefile
19
api/Makefile
@@ -1,19 +1,14 @@
|
|||||||
SHELL=/usr/bin/env bash
|
SHELL=/usr/bin/env bash
|
||||||
NAME := chatgpt-plus
|
NAME := chatgpt-plus
|
||||||
all: window linux darwin
|
all: amd64 arm64
|
||||||
|
|
||||||
|
amd64:
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o bin/$(NAME)-linux main.go
|
||||||
|
.PHONY: amd64
|
||||||
|
|
||||||
window:
|
arm64:
|
||||||
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o bin/$(NAME)-amd64.exe main.go
|
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 GOARM=7 go build -o bin/$(NAME)-linux main.go
|
||||||
.PHONY: window
|
.PHONY: arm64
|
||||||
|
|
||||||
linux:
|
|
||||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o bin/$(NAME)-amd64-linux main.go
|
|
||||||
.PHONY: linux
|
|
||||||
|
|
||||||
darwin:
|
|
||||||
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o bin/$(NAME)-amd64-darwin main.go
|
|
||||||
.PHONY: darwin
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf bin/$(NAME)-*
|
rm -rf bin/$(NAME)-*
|
||||||
|
|||||||
@@ -156,6 +156,7 @@ func authorizeMiddleware(s *AppServer, client *redis.Client) gin.HandlerFunc {
|
|||||||
c.Request.URL.Path == "/api/mj/jobs" ||
|
c.Request.URL.Path == "/api/mj/jobs" ||
|
||||||
c.Request.URL.Path == "/api/invite/hits" ||
|
c.Request.URL.Path == "/api/invite/hits" ||
|
||||||
c.Request.URL.Path == "/api/sd/jobs" ||
|
c.Request.URL.Path == "/api/sd/jobs" ||
|
||||||
|
strings.HasPrefix(c.Request.URL.Path, "/test/") ||
|
||||||
strings.HasPrefix(c.Request.URL.Path, "/api/sms/") ||
|
strings.HasPrefix(c.Request.URL.Path, "/api/sms/") ||
|
||||||
strings.HasPrefix(c.Request.URL.Path, "/api/captcha/") ||
|
strings.HasPrefix(c.Request.URL.Path, "/api/captcha/") ||
|
||||||
strings.HasPrefix(c.Request.URL.Path, "/api/payment/") ||
|
strings.HasPrefix(c.Request.URL.Path, "/api/payment/") ||
|
||||||
@@ -229,35 +230,37 @@ func parameterHandlerMiddleware() gin.HandlerFunc {
|
|||||||
params[key][i] = strings.TrimSpace(value)
|
params[key][i] = strings.TrimSpace(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 更新参数
|
// update get parameters
|
||||||
c.Request.URL.RawQuery = params.Encode()
|
c.Request.URL.RawQuery = params.Encode()
|
||||||
|
// skip file upload requests
|
||||||
contentType := c.Request.Header.Get("Content-Type")
|
contentType := c.Request.Header.Get("Content-Type")
|
||||||
if strings.Contains(contentType, "multipart/form-data") {
|
if strings.Contains(contentType, "multipart/form-data") {
|
||||||
c.Next()
|
c.Next()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST JSON 参数处理
|
if strings.Contains(contentType, "application/json") {
|
||||||
bodyBytes, err := io.ReadAll(c.Request.Body)
|
// process POST JSON request body
|
||||||
if err != nil {
|
bodyBytes, err := io.ReadAll(c.Request.Body)
|
||||||
c.Next()
|
if err != nil {
|
||||||
return
|
c.Next()
|
||||||
}
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 还原请求体
|
// 还原请求体
|
||||||
c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
|
c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
|
||||||
// 将请求体解析为 JSON
|
// 将请求体解析为 JSON
|
||||||
var jsonData map[string]interface{}
|
var jsonData map[string]interface{}
|
||||||
if err := c.ShouldBindJSON(&jsonData); err != nil {
|
if err := c.ShouldBindJSON(&jsonData); err != nil {
|
||||||
c.Next()
|
c.Next()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 对 JSON 数据中的字符串值去除两端空格
|
// 对 JSON 数据中的字符串值去除两端空格
|
||||||
trimJSONStrings(jsonData)
|
trimJSONStrings(jsonData)
|
||||||
// 更新请求体
|
// 更新请求体
|
||||||
c.Request.Body = io.NopCloser(bytes.NewBufferString(utils.JsonEncode(jsonData)))
|
c.Request.Body = io.NopCloser(bytes.NewBufferString(utils.JsonEncode(jsonData)))
|
||||||
|
}
|
||||||
|
|
||||||
c.Next()
|
c.Next()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ func NewDefaultConfig() *types.AppConfig {
|
|||||||
BasePath: "./static/upload",
|
BasePath: "./static/upload",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
MjConfig: types.MidJourneyConfig{Enabled: false},
|
|
||||||
SdConfig: types.StableDiffusionConfig{Enabled: false, Txt2ImgJsonPath: "res/text2img.json"},
|
SdConfig: types.StableDiffusionConfig{Enabled: false, Txt2ImgJsonPath: "res/text2img.json"},
|
||||||
WeChatBot: false,
|
WeChatBot: false,
|
||||||
AlipayConfig: types.AlipayConfig{Enabled: false, SandBox: false},
|
AlipayConfig: types.AlipayConfig{Enabled: false, SandBox: false},
|
||||||
|
|||||||
@@ -18,12 +18,13 @@ type AppConfig struct {
|
|||||||
AesEncryptKey string
|
AesEncryptKey string
|
||||||
SmsConfig AliYunSmsConfig // AliYun send message service config
|
SmsConfig AliYunSmsConfig // AliYun send message service config
|
||||||
OSS OSSConfig // OSS config
|
OSS OSSConfig // OSS config
|
||||||
MjConfig MidJourneyConfig // mj 绘画配置
|
MjConfigs []MidJourneyConfig // mj 绘画配置池子
|
||||||
WeChatBot bool // 是否启用微信机器人
|
WeChatBot bool // 是否启用微信机器人
|
||||||
SdConfig StableDiffusionConfig // sd 绘画配置
|
SdConfig StableDiffusionConfig // sd 绘画配置
|
||||||
|
|
||||||
XXLConfig XXLConfig
|
XXLConfig XXLConfig
|
||||||
AlipayConfig AlipayConfig
|
AlipayConfig AlipayConfig
|
||||||
|
HuPiPayConfig HuPiPayConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChatPlusApiConfig struct {
|
type ChatPlusApiConfig struct {
|
||||||
@@ -40,10 +41,6 @@ type MidJourneyConfig struct {
|
|||||||
ChanelId string // Chanel ID
|
ChanelId string // Chanel ID
|
||||||
}
|
}
|
||||||
|
|
||||||
type WeChatConfig struct {
|
|
||||||
Enabled bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type StableDiffusionConfig struct {
|
type StableDiffusionConfig struct {
|
||||||
Enabled bool
|
Enabled bool
|
||||||
ApiURL string
|
ApiURL string
|
||||||
@@ -61,7 +58,7 @@ type AliYunSmsConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AlipayConfig struct {
|
type AlipayConfig struct {
|
||||||
Enabled bool // 是否启用该服务
|
Enabled bool // 是否启用该支付通道
|
||||||
SandBox bool // 是否沙盒环境
|
SandBox bool // 是否沙盒环境
|
||||||
AppId string // 应用 ID
|
AppId string // 应用 ID
|
||||||
UserId string // 支付宝用户 ID
|
UserId string // 支付宝用户 ID
|
||||||
@@ -72,6 +69,15 @@ type AlipayConfig struct {
|
|||||||
NotifyURL string // 异步通知回调
|
NotifyURL string // 异步通知回调
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type HuPiPayConfig struct { //虎皮椒第四方支付配置
|
||||||
|
Enabled bool // 是否启用该支付通道
|
||||||
|
Name string // 支付名称,如:wechat/alipay
|
||||||
|
AppId string // App ID
|
||||||
|
AppSecret string // app 密钥
|
||||||
|
NotifyURL string // 异步通知回调
|
||||||
|
PayURL string // 支付网关
|
||||||
|
}
|
||||||
|
|
||||||
type XXLConfig struct { // XXL 任务调度配置
|
type XXLConfig struct { // XXL 任务调度配置
|
||||||
Enabled bool
|
Enabled bool
|
||||||
ServerAddr string
|
ServerAddr string
|
||||||
@@ -106,9 +112,11 @@ type ChatConfig struct {
|
|||||||
Baidu ModelAPIConfig `json:"baidu"`
|
Baidu ModelAPIConfig `json:"baidu"`
|
||||||
XunFei ModelAPIConfig `json:"xun_fei"`
|
XunFei ModelAPIConfig `json:"xun_fei"`
|
||||||
|
|
||||||
EnableContext bool `json:"enable_context"` // 是否开启聊天上下文
|
EnableContext bool `json:"enable_context"` // 是否开启聊天上下文
|
||||||
EnableHistory bool `json:"enable_history"` // 是否允许保存聊天记录
|
EnableHistory bool `json:"enable_history"` // 是否允许保存聊天记录
|
||||||
ContextDeep int `json:"context_deep"` // 上下文深度
|
ContextDeep int `json:"context_deep"` // 上下文深度
|
||||||
|
DallApiURL string `json:"dall_api_url"` // dall-e3 绘图 API 地址
|
||||||
|
DallImgNum int `json:"dall_img_num"` // dall-e3 出图数量
|
||||||
}
|
}
|
||||||
|
|
||||||
type Platform string
|
type Platform string
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ type MiniOssConfig struct {
|
|||||||
AccessKey string
|
AccessKey string
|
||||||
AccessSecret string
|
AccessSecret string
|
||||||
Bucket string
|
Bucket string
|
||||||
|
SubDir string
|
||||||
UseSSL bool
|
UseSSL bool
|
||||||
Domain string
|
Domain string
|
||||||
}
|
}
|
||||||
@@ -21,6 +22,7 @@ type QiNiuOssConfig struct {
|
|||||||
AccessKey string
|
AccessKey string
|
||||||
AccessSecret string
|
AccessSecret string
|
||||||
Bucket string
|
Bucket string
|
||||||
|
SubDir string
|
||||||
Domain string
|
Domain string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,6 +31,7 @@ type AliYunOssConfig struct {
|
|||||||
AccessKey string
|
AccessKey string
|
||||||
AccessSecret string
|
AccessSecret string
|
||||||
Bucket string
|
Bucket string
|
||||||
|
SubDir string
|
||||||
Domain string
|
Domain string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,28 +11,15 @@ const (
|
|||||||
TaskImage = TaskType("image")
|
TaskImage = TaskType("image")
|
||||||
TaskUpscale = TaskType("upscale")
|
TaskUpscale = TaskType("upscale")
|
||||||
TaskVariation = TaskType("variation")
|
TaskVariation = TaskType("variation")
|
||||||
TaskTxt2Img = TaskType("text2img")
|
|
||||||
)
|
|
||||||
|
|
||||||
// TaskSrc 任务来源
|
|
||||||
type TaskSrc string
|
|
||||||
|
|
||||||
const (
|
|
||||||
TaskSrcChat = TaskSrc("chat") // 来自聊天页面
|
|
||||||
TaskSrcImg = TaskSrc("img") // 专业绘画页面
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// MjTask MidJourney 任务
|
// MjTask MidJourney 任务
|
||||||
type MjTask struct {
|
type MjTask struct {
|
||||||
Id int `json:"id"`
|
Id int `json:"id"`
|
||||||
SessionId string `json:"session_id"`
|
SessionId string `json:"session_id"`
|
||||||
Src TaskSrc `json:"src"`
|
|
||||||
Type TaskType `json:"type"`
|
Type TaskType `json:"type"`
|
||||||
UserId int `json:"user_id"`
|
UserId int `json:"user_id"`
|
||||||
Prompt string `json:"prompt,omitempty"`
|
Prompt string `json:"prompt,omitempty"`
|
||||||
ChatId string `json:"chat_id,omitempty"`
|
|
||||||
RoleId int `json:"role_id,omitempty"`
|
|
||||||
Icon string `json:"icon,omitempty"`
|
|
||||||
Index int `json:"index,omitempty"`
|
Index int `json:"index,omitempty"`
|
||||||
MessageId string `json:"message_id,omitempty"`
|
MessageId string `json:"message_id,omitempty"`
|
||||||
MessageHash string `json:"message_hash,omitempty"`
|
MessageHash string `json:"message_hash,omitempty"`
|
||||||
@@ -42,7 +29,6 @@ type MjTask struct {
|
|||||||
type SdTask struct {
|
type SdTask struct {
|
||||||
Id int `json:"id"` // job 数据库ID
|
Id int `json:"id"` // job 数据库ID
|
||||||
SessionId string `json:"session_id"`
|
SessionId string `json:"session_id"`
|
||||||
Src TaskSrc `json:"src"`
|
|
||||||
Type TaskType `json:"type"`
|
Type TaskType `json:"type"`
|
||||||
UserId int `json:"user_id"`
|
UserId int `json:"user_id"`
|
||||||
Prompt string `json:"prompt,omitempty"`
|
Prompt string `json:"prompt,omitempty"`
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ func (h *OrderHandler) List(c *gin.Context) {
|
|||||||
end := utils.Str2stamp(data.PayTime[1] + " 00:00:00")
|
end := utils.Str2stamp(data.PayTime[1] + " 00:00:00")
|
||||||
session = session.Where("pay_time >= ? AND pay_time <= ?", start, end)
|
session = session.Where("pay_time >= ? AND pay_time <= ?", start, end)
|
||||||
}
|
}
|
||||||
|
session = session.Where("status = ?", types.OrderPaidSuccess)
|
||||||
|
|
||||||
var total int64
|
var total int64
|
||||||
session.Model(&model.Order{}).Count(&total)
|
session.Model(&model.Order{}).Count(&total)
|
||||||
var items []model.Order
|
var items []model.Order
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -200,7 +201,7 @@ func (h *ChatHandler) sendAzureMessage(
|
|||||||
RoleId: role.Id,
|
RoleId: role.Id,
|
||||||
Type: types.PromptMsg,
|
Type: types.PromptMsg,
|
||||||
Icon: userVo.Avatar,
|
Icon: userVo.Avatar,
|
||||||
Content: prompt,
|
Content: template.HTMLEscapeString(prompt),
|
||||||
Tokens: promptToken,
|
Tokens: promptToken,
|
||||||
UseContext: useContext,
|
UseContext: useContext,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -156,7 +157,7 @@ func (h *ChatHandler) sendBaiduMessage(
|
|||||||
RoleId: role.Id,
|
RoleId: role.Id,
|
||||||
Type: types.PromptMsg,
|
Type: types.PromptMsg,
|
||||||
Icon: userVo.Avatar,
|
Icon: userVo.Avatar,
|
||||||
Content: prompt,
|
Content: template.HTMLEscapeString(prompt),
|
||||||
Tokens: promptToken,
|
Tokens: promptToken,
|
||||||
UseContext: true,
|
UseContext: true,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"chatplus/core/types"
|
"chatplus/core/types"
|
||||||
"chatplus/handler"
|
"chatplus/handler"
|
||||||
logger2 "chatplus/logger"
|
logger2 "chatplus/logger"
|
||||||
"chatplus/service/mj"
|
|
||||||
"chatplus/store/model"
|
"chatplus/store/model"
|
||||||
"chatplus/store/vo"
|
"chatplus/store/vo"
|
||||||
"chatplus/utils"
|
"chatplus/utils"
|
||||||
@@ -32,16 +31,14 @@ var logger = logger2.GetLogger()
|
|||||||
|
|
||||||
type ChatHandler struct {
|
type ChatHandler struct {
|
||||||
handler.BaseHandler
|
handler.BaseHandler
|
||||||
db *gorm.DB
|
db *gorm.DB
|
||||||
redis *redis.Client
|
redis *redis.Client
|
||||||
mjService *mj.Service
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewChatHandler(app *core.AppServer, db *gorm.DB, redis *redis.Client, service *mj.Service) *ChatHandler {
|
func NewChatHandler(app *core.AppServer, db *gorm.DB, redis *redis.Client) *ChatHandler {
|
||||||
h := ChatHandler{
|
h := ChatHandler{
|
||||||
db: db,
|
db: db,
|
||||||
redis: redis,
|
redis: redis,
|
||||||
mjService: service,
|
|
||||||
}
|
}
|
||||||
h.App = app
|
h.App = app
|
||||||
return &h
|
return &h
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/golang-jwt/jwt/v5"
|
"github.com/golang-jwt/jwt/v5"
|
||||||
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -135,7 +136,7 @@ func (h *ChatHandler) sendChatGLMMessage(
|
|||||||
RoleId: role.Id,
|
RoleId: role.Id,
|
||||||
Type: types.PromptMsg,
|
Type: types.PromptMsg,
|
||||||
Icon: userVo.Avatar,
|
Icon: userVo.Avatar,
|
||||||
Content: prompt,
|
Content: template.HTMLEscapeString(prompt),
|
||||||
Tokens: promptToken,
|
Tokens: promptToken,
|
||||||
UseContext: true,
|
UseContext: true,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -135,7 +136,6 @@ func (h *ChatHandler) sendOpenAiMessage(
|
|||||||
params["user_id"] = userVo.Id
|
params["user_id"] = userVo.Id
|
||||||
params["role_id"] = role.Id
|
params["role_id"] = role.Id
|
||||||
params["chat_id"] = session.ChatId
|
params["chat_id"] = session.ChatId
|
||||||
params["icon"] = "/images/avatar/mid_journey.png"
|
|
||||||
params["session_id"] = session.SessionId
|
params["session_id"] = session.SessionId
|
||||||
}
|
}
|
||||||
data, err := f.Invoke(params)
|
data, err := f.Invoke(params)
|
||||||
@@ -199,7 +199,7 @@ func (h *ChatHandler) sendOpenAiMessage(
|
|||||||
RoleId: role.Id,
|
RoleId: role.Id,
|
||||||
Type: types.PromptMsg,
|
Type: types.PromptMsg,
|
||||||
Icon: userVo.Avatar,
|
Icon: userVo.Avatar,
|
||||||
Content: prompt,
|
Content: template.HTMLEscapeString(prompt),
|
||||||
Tokens: promptToken,
|
Tokens: promptToken,
|
||||||
UseContext: useContext,
|
UseContext: useContext,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -198,7 +199,7 @@ func (h *ChatHandler) sendXunFeiMessage(
|
|||||||
RoleId: role.Id,
|
RoleId: role.Id,
|
||||||
Type: types.PromptMsg,
|
Type: types.PromptMsg,
|
||||||
Icon: userVo.Avatar,
|
Icon: userVo.Avatar,
|
||||||
Content: prompt,
|
Content: template.HTMLEscapeString(prompt),
|
||||||
Tokens: promptToken,
|
Tokens: promptToken,
|
||||||
UseContext: true,
|
UseContext: true,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package handler
|
|||||||
import (
|
import (
|
||||||
"chatplus/core"
|
"chatplus/core"
|
||||||
"chatplus/core/types"
|
"chatplus/core/types"
|
||||||
|
"chatplus/service"
|
||||||
"chatplus/service/mj"
|
"chatplus/service/mj"
|
||||||
"chatplus/store/model"
|
"chatplus/store/model"
|
||||||
"chatplus/store/vo"
|
"chatplus/store/vo"
|
||||||
@@ -11,50 +12,29 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/go-redis/redis/v8"
|
|
||||||
"github.com/gorilla/websocket"
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"net/http"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MidJourneyHandler struct {
|
type MidJourneyHandler struct {
|
||||||
BaseHandler
|
BaseHandler
|
||||||
redis *redis.Client
|
|
||||||
db *gorm.DB
|
db *gorm.DB
|
||||||
mjService *mj.Service
|
pool *mj.ServicePool
|
||||||
|
snowflake *service.Snowflake
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMidJourneyHandler(
|
func NewMidJourneyHandler(app *core.AppServer, db *gorm.DB, snowflake *service.Snowflake, pool *mj.ServicePool) *MidJourneyHandler {
|
||||||
app *core.AppServer,
|
|
||||||
client *redis.Client,
|
|
||||||
db *gorm.DB,
|
|
||||||
mjService *mj.Service) *MidJourneyHandler {
|
|
||||||
h := MidJourneyHandler{
|
h := MidJourneyHandler{
|
||||||
redis: client,
|
|
||||||
db: db,
|
db: db,
|
||||||
mjService: mjService,
|
snowflake: snowflake,
|
||||||
|
pool: pool,
|
||||||
}
|
}
|
||||||
h.App = app
|
h.App = app
|
||||||
return &h
|
return &h
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client WebSocket 客户端,用于通知任务状态变更
|
func (h *MidJourneyHandler) preCheck(c *gin.Context) bool {
|
||||||
func (h *MidJourneyHandler) Client(c *gin.Context) {
|
|
||||||
ws, err := (&websocket.Upgrader{CheckOrigin: func(r *http.Request) bool { return true }}).Upgrade(c.Writer, c.Request, nil)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
sessionId := c.Query("session_id")
|
|
||||||
client := types.NewWsClient(ws)
|
|
||||||
h.mjService.Clients.Put(sessionId, client)
|
|
||||||
logger.Infof("New websocket connected, IP: %s", c.ClientIP())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *MidJourneyHandler) checkLimits(c *gin.Context) bool {
|
|
||||||
user, err := utils.GetLoginUser(c, h.db)
|
user, err := utils.GetLoginUser(c, h.db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.NotAuth(c)
|
resp.NotAuth(c)
|
||||||
@@ -66,17 +46,17 @@ func (h *MidJourneyHandler) checkLimits(c *gin.Context) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !h.pool.HasAvailableService() {
|
||||||
|
resp.ERROR(c, "MidJourney 池子中没有没有可用的服务!")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Image 创建一个绘画任务
|
// Image 创建一个绘画任务
|
||||||
func (h *MidJourneyHandler) Image(c *gin.Context) {
|
func (h *MidJourneyHandler) Image(c *gin.Context) {
|
||||||
if !h.App.Config.MjConfig.Enabled {
|
|
||||||
resp.ERROR(c, "MidJourney service is disabled")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var data struct {
|
var data struct {
|
||||||
SessionId string `json:"session_id"`
|
SessionId string `json:"session_id"`
|
||||||
Prompt string `json:"prompt"`
|
Prompt string `json:"prompt"`
|
||||||
@@ -96,7 +76,7 @@ func (h *MidJourneyHandler) Image(c *gin.Context) {
|
|||||||
resp.ERROR(c, types.InvalidArgs)
|
resp.ERROR(c, types.InvalidArgs)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !h.checkLimits(c) {
|
if !h.preCheck(c) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,9 +117,16 @@ func (h *MidJourneyHandler) Image(c *gin.Context) {
|
|||||||
|
|
||||||
idValue, _ := c.Get(types.LoginUserID)
|
idValue, _ := c.Get(types.LoginUserID)
|
||||||
userId := utils.IntValue(utils.InterfaceToString(idValue), 0)
|
userId := utils.IntValue(utils.InterfaceToString(idValue), 0)
|
||||||
|
// generate task id
|
||||||
|
taskId, err := h.snowflake.Next(true)
|
||||||
|
if err != nil {
|
||||||
|
resp.ERROR(c, "error with generate task id: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
job := model.MidJourneyJob{
|
job := model.MidJourneyJob{
|
||||||
Type: types.TaskImage.String(),
|
Type: types.TaskImage.String(),
|
||||||
UserId: userId,
|
UserId: userId,
|
||||||
|
TaskId: taskId,
|
||||||
Progress: 0,
|
Progress: 0,
|
||||||
Prompt: prompt,
|
Prompt: prompt,
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
@@ -149,24 +136,13 @@ func (h *MidJourneyHandler) Image(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
h.mjService.PushTask(types.MjTask{
|
h.pool.PushTask(types.MjTask{
|
||||||
Id: int(job.Id),
|
Id: int(job.Id),
|
||||||
SessionId: data.SessionId,
|
SessionId: data.SessionId,
|
||||||
Src: types.TaskSrcImg,
|
|
||||||
Type: types.TaskImage,
|
Type: types.TaskImage,
|
||||||
Prompt: prompt,
|
Prompt: fmt.Sprintf("%s %s", taskId, prompt),
|
||||||
UserId: userId,
|
UserId: userId,
|
||||||
})
|
})
|
||||||
|
|
||||||
var jobVo vo.MidJourneyJob
|
|
||||||
err := utils.CopyObject(job, &jobVo)
|
|
||||||
if err == nil {
|
|
||||||
// 推送任务到前端
|
|
||||||
client := h.mjService.Clients.Get(data.SessionId)
|
|
||||||
if client != nil {
|
|
||||||
utils.ReplyChunkMessage(client, jobVo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resp.SUCCESS(c)
|
resp.SUCCESS(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,65 +166,23 @@ func (h *MidJourneyHandler) Upscale(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !h.checkLimits(c) {
|
if !h.preCheck(c) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
idValue, _ := c.Get(types.LoginUserID)
|
idValue, _ := c.Get(types.LoginUserID)
|
||||||
jobId := 0
|
jobId := 0
|
||||||
userId := utils.IntValue(utils.InterfaceToString(idValue), 0)
|
userId := utils.IntValue(utils.InterfaceToString(idValue), 0)
|
||||||
src := types.TaskSrc(data.Src)
|
h.pool.PushTask(types.MjTask{
|
||||||
if src == types.TaskSrcImg {
|
|
||||||
job := model.MidJourneyJob{
|
|
||||||
Type: types.TaskUpscale.String(),
|
|
||||||
UserId: userId,
|
|
||||||
Hash: data.MessageHash,
|
|
||||||
Progress: 0,
|
|
||||||
Prompt: data.Prompt,
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
}
|
|
||||||
if res := h.db.Create(&job); res.Error == nil {
|
|
||||||
jobId = int(job.Id)
|
|
||||||
} else {
|
|
||||||
resp.ERROR(c, "添加任务失败:"+res.Error.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var jobVo vo.MidJourneyJob
|
|
||||||
err := utils.CopyObject(job, &jobVo)
|
|
||||||
if err == nil {
|
|
||||||
// 推送任务到前端
|
|
||||||
client := h.mjService.Clients.Get(data.SessionId)
|
|
||||||
if client != nil {
|
|
||||||
utils.ReplyChunkMessage(client, jobVo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
h.mjService.PushTask(types.MjTask{
|
|
||||||
Id: jobId,
|
Id: jobId,
|
||||||
SessionId: data.SessionId,
|
SessionId: data.SessionId,
|
||||||
Src: src,
|
|
||||||
Type: types.TaskUpscale,
|
Type: types.TaskUpscale,
|
||||||
Prompt: data.Prompt,
|
Prompt: data.Prompt,
|
||||||
UserId: userId,
|
UserId: userId,
|
||||||
RoleId: data.RoleId,
|
|
||||||
Icon: data.Icon,
|
|
||||||
ChatId: data.ChatId,
|
|
||||||
Index: data.Index,
|
Index: data.Index,
|
||||||
MessageId: data.MessageId,
|
MessageId: data.MessageId,
|
||||||
MessageHash: data.MessageHash,
|
MessageHash: data.MessageHash,
|
||||||
})
|
})
|
||||||
|
|
||||||
if src == types.TaskSrcChat {
|
|
||||||
wsClient := h.App.ChatClients.Get(data.SessionId)
|
|
||||||
if wsClient != nil {
|
|
||||||
content := fmt.Sprintf("**%s** 已推送 upscale 任务到 MidJourney 机器人,请耐心等待任务执行...", data.Prompt)
|
|
||||||
utils.ReplyMessage(wsClient, content)
|
|
||||||
if h.mjService.ChatClients.Get(data.SessionId) == nil {
|
|
||||||
h.mjService.ChatClients.Put(data.SessionId, wsClient)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resp.SUCCESS(c)
|
resp.SUCCESS(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,67 +194,23 @@ func (h *MidJourneyHandler) Variation(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !h.checkLimits(c) {
|
if !h.preCheck(c) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
idValue, _ := c.Get(types.LoginUserID)
|
idValue, _ := c.Get(types.LoginUserID)
|
||||||
jobId := 0
|
jobId := 0
|
||||||
userId := utils.IntValue(utils.InterfaceToString(idValue), 0)
|
userId := utils.IntValue(utils.InterfaceToString(idValue), 0)
|
||||||
src := types.TaskSrc(data.Src)
|
h.pool.PushTask(types.MjTask{
|
||||||
if src == types.TaskSrcImg {
|
|
||||||
job := model.MidJourneyJob{
|
|
||||||
Type: types.TaskVariation.String(),
|
|
||||||
UserId: userId,
|
|
||||||
ImgURL: "",
|
|
||||||
Hash: data.MessageHash,
|
|
||||||
Progress: 0,
|
|
||||||
Prompt: data.Prompt,
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
}
|
|
||||||
if res := h.db.Create(&job); res.Error == nil {
|
|
||||||
jobId = int(job.Id)
|
|
||||||
} else {
|
|
||||||
resp.ERROR(c, "添加任务失败:"+res.Error.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var jobVo vo.MidJourneyJob
|
|
||||||
err := utils.CopyObject(job, &jobVo)
|
|
||||||
if err == nil {
|
|
||||||
// 推送任务到前端
|
|
||||||
client := h.mjService.Clients.Get(data.SessionId)
|
|
||||||
if client != nil {
|
|
||||||
utils.ReplyChunkMessage(client, jobVo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
h.mjService.PushTask(types.MjTask{
|
|
||||||
Id: jobId,
|
Id: jobId,
|
||||||
SessionId: data.SessionId,
|
SessionId: data.SessionId,
|
||||||
Src: src,
|
|
||||||
Type: types.TaskVariation,
|
Type: types.TaskVariation,
|
||||||
Prompt: data.Prompt,
|
Prompt: data.Prompt,
|
||||||
UserId: userId,
|
UserId: userId,
|
||||||
RoleId: data.RoleId,
|
|
||||||
Icon: data.Icon,
|
|
||||||
ChatId: data.ChatId,
|
|
||||||
Index: data.Index,
|
Index: data.Index,
|
||||||
MessageId: data.MessageId,
|
MessageId: data.MessageId,
|
||||||
MessageHash: data.MessageHash,
|
MessageHash: data.MessageHash,
|
||||||
})
|
})
|
||||||
|
|
||||||
if src == types.TaskSrcChat {
|
|
||||||
// 从聊天窗口发送的请求,记录客户端信息
|
|
||||||
wsClient := h.mjService.ChatClients.Get(data.SessionId)
|
|
||||||
if wsClient != nil {
|
|
||||||
content := fmt.Sprintf("**%s** 已推送 variation 任务到 MidJourney 机器人,请耐心等待任务执行...", data.Prompt)
|
|
||||||
utils.ReplyMessage(wsClient, content)
|
|
||||||
if h.mjService.Clients.Get(data.SessionId) == nil {
|
|
||||||
h.mjService.Clients.Put(data.SessionId, wsClient)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resp.SUCCESS(c)
|
resp.SUCCESS(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -359,19 +249,27 @@ func (h *MidJourneyHandler) JobList(c *gin.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if job.Progress == -1 {
|
||||||
|
h.db.Delete(&model.MidJourneyJob{Id: job.Id})
|
||||||
|
}
|
||||||
|
|
||||||
if item.Progress < 100 {
|
if item.Progress < 100 {
|
||||||
// 10 分钟还没完成的任务直接删除
|
// 10 分钟还没完成的任务直接删除
|
||||||
if time.Now().Sub(item.CreatedAt) > time.Minute*10 {
|
if time.Now().Sub(item.CreatedAt) > time.Minute*10 {
|
||||||
h.db.Delete(&item)
|
h.db.Delete(&item)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if item.ImgURL != "" { // 正在运行中任务使用代理访问图片
|
|
||||||
image, err := utils.DownloadImage(item.ImgURL, h.App.Config.ProxyURL)
|
// 正在运行中任务使用代理访问图片
|
||||||
|
if item.ImgURL == "" && item.OrgURL != "" {
|
||||||
|
image, err := utils.DownloadImage(item.OrgURL, h.App.Config.ProxyURL)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
job.ImgURL = "data:image/png;base64," + base64.StdEncoding.EncodeToString(image)
|
job.ImgURL = "data:image/png;base64," + base64.StdEncoding.EncodeToString(image)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
jobs = append(jobs, job)
|
jobs = append(jobs, job)
|
||||||
}
|
}
|
||||||
resp.SUCCESS(c, jobs)
|
resp.SUCCESS(c, jobs)
|
||||||
|
|||||||
@@ -21,31 +21,37 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
PayWayAlipay = "支付宝"
|
PayWayAlipay = "支付宝"
|
||||||
PayWayWechat = "微信支付"
|
PayWayXunHu = "虎皮椒"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PaymentHandler 支付服务回调 handler
|
// PaymentHandler 支付服务回调 handler
|
||||||
type PaymentHandler struct {
|
type PaymentHandler struct {
|
||||||
BaseHandler
|
BaseHandler
|
||||||
alipayService *payment.AlipayService
|
alipayService *payment.AlipayService
|
||||||
snowflake *service.Snowflake
|
huPiPayService *payment.HuPiPayService
|
||||||
db *gorm.DB
|
snowflake *service.Snowflake
|
||||||
fs embed.FS
|
db *gorm.DB
|
||||||
lock sync.Mutex
|
fs embed.FS
|
||||||
|
lock sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPaymentHandler(server *core.AppServer, alipayService *payment.AlipayService, snowflake *service.Snowflake, db *gorm.DB, fs embed.FS) *PaymentHandler {
|
func NewPaymentHandler(server *core.AppServer, alipayService *payment.AlipayService, huPiPayService *payment.HuPiPayService, snowflake *service.Snowflake, db *gorm.DB, fs embed.FS) *PaymentHandler {
|
||||||
h := PaymentHandler{lock: sync.Mutex{}}
|
h := PaymentHandler{
|
||||||
|
alipayService: alipayService,
|
||||||
|
huPiPayService: huPiPayService,
|
||||||
|
snowflake: snowflake,
|
||||||
|
fs: fs,
|
||||||
|
db: db,
|
||||||
|
lock: sync.Mutex{},
|
||||||
|
}
|
||||||
h.App = server
|
h.App = server
|
||||||
h.alipayService = alipayService
|
|
||||||
h.snowflake = snowflake
|
|
||||||
h.db = db
|
|
||||||
h.fs = fs
|
|
||||||
return &h
|
return &h
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *PaymentHandler) Alipay(c *gin.Context) {
|
func (h *PaymentHandler) DoPay(c *gin.Context) {
|
||||||
orderNo := h.GetTrim(c, "order_no")
|
orderNo := h.GetTrim(c, "order_no")
|
||||||
|
payWay := h.GetTrim(c, "pay_way")
|
||||||
|
|
||||||
if orderNo == "" {
|
if orderNo == "" {
|
||||||
resp.ERROR(c, types.InvalidArgs)
|
resp.ERROR(c, types.InvalidArgs)
|
||||||
return
|
return
|
||||||
@@ -60,21 +66,61 @@ func (h *PaymentHandler) Alipay(c *gin.Context) {
|
|||||||
|
|
||||||
// 更新扫码状态
|
// 更新扫码状态
|
||||||
h.db.Model(&order).UpdateColumn("status", types.OrderScanned)
|
h.db.Model(&order).UpdateColumn("status", types.OrderScanned)
|
||||||
// 生成支付链接
|
if payWay == "alipay" { // 支付宝
|
||||||
notifyURL := h.App.Config.AlipayConfig.NotifyURL
|
// 生成支付链接
|
||||||
returnURL := "" // 关闭同步回跳
|
notifyURL := h.App.Config.AlipayConfig.NotifyURL
|
||||||
amount := fmt.Sprintf("%.2f", order.Amount)
|
returnURL := "" // 关闭同步回跳
|
||||||
|
amount := fmt.Sprintf("%.2f", order.Amount)
|
||||||
|
|
||||||
uri, err := h.alipayService.PayUrlMobile(order.OrderNo, notifyURL, returnURL, amount, order.Subject)
|
uri, err := h.alipayService.PayUrlMobile(order.OrderNo, notifyURL, returnURL, amount, order.Subject)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.ERROR(c, "error with generate pay url: "+err.Error())
|
resp.ERROR(c, "error with generate pay url: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Redirect(302, uri)
|
||||||
return
|
return
|
||||||
}
|
} else if payWay == "hupi" { // 虎皮椒支付
|
||||||
|
params := map[string]string{
|
||||||
|
"version": "1.1",
|
||||||
|
"trade_order_id": orderNo,
|
||||||
|
"total_fee": fmt.Sprintf("%f", order.Amount),
|
||||||
|
"title": order.Subject,
|
||||||
|
"notify_url": h.App.Config.HuPiPayConfig.NotifyURL,
|
||||||
|
"return_url": "",
|
||||||
|
"wap_name": "极客学长",
|
||||||
|
"callback_url": "",
|
||||||
|
}
|
||||||
|
|
||||||
c.Redirect(302, uri)
|
res, err := h.huPiPayService.Pay(params)
|
||||||
|
if err != nil {
|
||||||
|
resp.ERROR(c, "error with generate pay url: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var r struct {
|
||||||
|
Openid int64 `json:"openid"`
|
||||||
|
UrlQrcode string `json:"url_qrcode"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
ErrCode int `json:"errcode"`
|
||||||
|
ErrMsg string `json:"errmsg"`
|
||||||
|
}
|
||||||
|
err = utils.JsonDecode(res, &r)
|
||||||
|
if err != nil {
|
||||||
|
resp.ERROR(c, "error with decode payment result: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.ErrCode != 0 {
|
||||||
|
resp.ERROR(c, "error with generate pay url: "+r.ErrMsg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Redirect(302, r.URL)
|
||||||
|
}
|
||||||
|
resp.ERROR(c, "Invalid operations")
|
||||||
}
|
}
|
||||||
|
|
||||||
// OrderQuery 清单状态查询
|
// OrderQuery 查询订单状态
|
||||||
func (h *PaymentHandler) OrderQuery(c *gin.Context) {
|
func (h *PaymentHandler) OrderQuery(c *gin.Context) {
|
||||||
var data struct {
|
var data struct {
|
||||||
OrderNo string `json:"order_no"`
|
OrderNo string `json:"order_no"`
|
||||||
@@ -111,16 +157,12 @@ func (h *PaymentHandler) OrderQuery(c *gin.Context) {
|
|||||||
resp.SUCCESS(c, gin.H{"status": order.Status})
|
resp.SUCCESS(c, gin.H{"status": order.Status})
|
||||||
}
|
}
|
||||||
|
|
||||||
// AlipayQrcode 生成支付宝支付 URL 二维码
|
// PayQrcode 生成支付 URL 二维码
|
||||||
func (h *PaymentHandler) AlipayQrcode(c *gin.Context) {
|
func (h *PaymentHandler) PayQrcode(c *gin.Context) {
|
||||||
if !h.App.SysConfig.EnabledAlipay || h.alipayService == nil {
|
|
||||||
resp.ERROR(c, "当前支付通道已经关闭,请联系管理员开通!")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var data struct {
|
var data struct {
|
||||||
ProductId uint `json:"product_id"`
|
PayWay string `json:"pay_way"` // 支付方式
|
||||||
UserId int `json:"user_id"`
|
ProductId uint `json:"product_id"`
|
||||||
|
UserId int `json:"user_id"`
|
||||||
}
|
}
|
||||||
if err := c.ShouldBindJSON(&data); err != nil {
|
if err := c.ShouldBindJSON(&data); err != nil {
|
||||||
resp.ERROR(c, types.InvalidArgs)
|
resp.ERROR(c, types.InvalidArgs)
|
||||||
@@ -134,7 +176,7 @@ func (h *PaymentHandler) AlipayQrcode(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
orderNo, err := h.snowflake.Next()
|
orderNo, err := h.snowflake.Next(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.ERROR(c, "error with generate trade no: "+err.Error())
|
resp.ERROR(c, "error with generate trade no: "+err.Error())
|
||||||
return
|
return
|
||||||
@@ -146,6 +188,10 @@ func (h *PaymentHandler) AlipayQrcode(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
payWay := PayWayAlipay
|
||||||
|
if data.PayWay == "hupi" {
|
||||||
|
payWay = PayWayXunHu
|
||||||
|
}
|
||||||
// 创建订单
|
// 创建订单
|
||||||
remark := types.OrderRemark{
|
remark := types.OrderRemark{
|
||||||
Days: product.Days,
|
Days: product.Days,
|
||||||
@@ -162,7 +208,7 @@ func (h *PaymentHandler) AlipayQrcode(c *gin.Context) {
|
|||||||
Subject: product.Name,
|
Subject: product.Name,
|
||||||
Amount: product.Price - product.Discount,
|
Amount: product.Price - product.Discount,
|
||||||
Status: types.OrderNotPaid,
|
Status: types.OrderNotPaid,
|
||||||
PayWay: PayWayAlipay,
|
PayWay: payWay,
|
||||||
Remark: utils.JsonEncode(remark),
|
Remark: utils.JsonEncode(remark),
|
||||||
}
|
}
|
||||||
res = h.db.Create(&order)
|
res = h.db.Create(&order)
|
||||||
@@ -171,19 +217,30 @@ func (h *PaymentHandler) AlipayQrcode(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成二维码图片
|
var logo string
|
||||||
file, err := h.fs.Open("res/img/alipay.jpg")
|
if data.PayWay == "alipay" {
|
||||||
|
logo = "res/img/alipay.jpg"
|
||||||
|
} else if data.PayWay == "hupi" {
|
||||||
|
if h.App.Config.HuPiPayConfig.Name == "wechat" {
|
||||||
|
logo = "res/img/wechat-pay.jpg"
|
||||||
|
} else {
|
||||||
|
logo = "res/img/alipay.jpg"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := h.fs.Open(logo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.ERROR(c, err.Error())
|
resp.ERROR(c, "error with open qrcode log file: "+err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
parse, err := url.Parse(h.App.Config.AlipayConfig.NotifyURL)
|
parse, err := url.Parse(h.App.Config.AlipayConfig.NotifyURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.ERROR(c, err.Error())
|
resp.ERROR(c, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
imageURL := fmt.Sprintf("%s://%s/api/payment/alipay?order_no=%s", parse.Scheme, parse.Host, orderNo)
|
imageURL := fmt.Sprintf("%s://%s/api/payment/doPay?order_no=%s&pay_way=%s", parse.Scheme, parse.Host, orderNo, data.PayWay)
|
||||||
imgData, err := utils.GenQrcode(imageURL, 400, file)
|
imgData, err := utils.GenQrcode(imageURL, 400, file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.ERROR(c, err.Error())
|
resp.ERROR(c, err.Error())
|
||||||
@@ -193,6 +250,7 @@ func (h *PaymentHandler) AlipayQrcode(c *gin.Context) {
|
|||||||
resp.SUCCESS(c, gin.H{"order_no": orderNo, "image": fmt.Sprintf("data:image/jpg;base64, %s", imgDataBase64), "url": imageURL})
|
resp.SUCCESS(c, gin.H{"order_no": orderNo, "image": fmt.Sprintf("data:image/jpg;base64, %s", imgDataBase64), "url": imageURL})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AlipayNotify 支付宝支付回调
|
||||||
func (h *PaymentHandler) AlipayNotify(c *gin.Context) {
|
func (h *PaymentHandler) AlipayNotify(c *gin.Context) {
|
||||||
err := c.Request.ParseForm()
|
err := c.Request.ParseForm()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -212,27 +270,46 @@ func (h *PaymentHandler) AlipayNotify(c *gin.Context) {
|
|||||||
h.lock.Lock()
|
h.lock.Lock()
|
||||||
defer h.lock.Unlock()
|
defer h.lock.Unlock()
|
||||||
|
|
||||||
var order model.Order
|
err = h.notify(r.OutTradeNo)
|
||||||
res := h.db.Where("order_no = ?", r.OutTradeNo).First(&order)
|
if err != nil {
|
||||||
if res.Error != nil {
|
|
||||||
logger.Error(res.Error)
|
|
||||||
c.String(http.StatusOK, "fail")
|
c.String(http.StatusOK, "fail")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.String(http.StatusOK, "success")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 异步通知回调公共逻辑
|
||||||
|
func (h *PaymentHandler) notify(orderNo string) error {
|
||||||
|
var order model.Order
|
||||||
|
res := h.db.Where("order_no = ?", orderNo).First(&order)
|
||||||
|
if res.Error != nil {
|
||||||
|
err := fmt.Errorf("error with fetch order: %v", res.Error)
|
||||||
|
logger.Error(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 已支付订单,直接返回
|
||||||
|
if order.Status == types.OrderPaidSuccess {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
var user model.User
|
var user model.User
|
||||||
res = h.db.First(&user, order.UserId)
|
res = h.db.First(&user, order.UserId)
|
||||||
if res.Error != nil {
|
if res.Error != nil {
|
||||||
logger.Error(res.Error)
|
err := fmt.Errorf("error with fetch user info: %v", res.Error)
|
||||||
c.String(http.StatusOK, "fail")
|
logger.Error(err)
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var remark types.OrderRemark
|
var remark types.OrderRemark
|
||||||
err = utils.JsonDecode(order.Remark, &remark)
|
err := utils.JsonDecode(order.Remark, &remark)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(res.Error)
|
err := fmt.Errorf("error with decode order remark: %v", err)
|
||||||
c.String(http.StatusOK, "fail")
|
logger.Error(err)
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. 点卡:days == 0, calls > 0
|
// 1. 点卡:days == 0, calls > 0
|
||||||
// 2. vip 套餐:days > 0, calls == 0
|
// 2. vip 套餐:days > 0, calls == 0
|
||||||
if remark.Days > 0 {
|
if remark.Days > 0 {
|
||||||
@@ -256,18 +333,57 @@ func (h *PaymentHandler) AlipayNotify(c *gin.Context) {
|
|||||||
// 更新用户信息
|
// 更新用户信息
|
||||||
res = h.db.Updates(&user)
|
res = h.db.Updates(&user)
|
||||||
if res.Error != nil {
|
if res.Error != nil {
|
||||||
logger.Error(res.Error)
|
err := fmt.Errorf("error with update user info: %v", res.Error)
|
||||||
c.String(http.StatusOK, "fail")
|
logger.Error(err)
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新订单状态
|
// 更新订单状态
|
||||||
order.PayTime = time.Now().Unix()
|
order.PayTime = time.Now().Unix()
|
||||||
order.Status = types.OrderPaidSuccess
|
order.Status = types.OrderPaidSuccess
|
||||||
h.db.Updates(&order)
|
res = h.db.Updates(&order)
|
||||||
|
if res.Error != nil {
|
||||||
|
err := fmt.Errorf("error with update order info: %v", res.Error)
|
||||||
|
logger.Error(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// 更新产品销量
|
// 更新产品销量
|
||||||
h.db.Model(&model.Product{}).Where("id = ?", order.ProductId).UpdateColumn("sales", gorm.Expr("sales + ?", 1))
|
h.db.Model(&model.Product{}).Where("id = ?", order.ProductId).UpdateColumn("sales", gorm.Expr("sales + ?", 1))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPayWays 获取支付方式
|
||||||
|
func (h *PaymentHandler) GetPayWays(c *gin.Context) {
|
||||||
|
data := gin.H{}
|
||||||
|
if h.App.Config.AlipayConfig.Enabled {
|
||||||
|
data["alipay"] = gin.H{"name": "alipay"}
|
||||||
|
}
|
||||||
|
if h.App.Config.HuPiPayConfig.Enabled {
|
||||||
|
data["hupi"] = gin.H{"name": h.App.Config.HuPiPayConfig.Name}
|
||||||
|
}
|
||||||
|
resp.SUCCESS(c, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HuPiPayNotify 虎皮椒支付异步回调
|
||||||
|
func (h *PaymentHandler) HuPiPayNotify(c *gin.Context) {
|
||||||
|
err := c.Request.ParseForm()
|
||||||
|
if err != nil {
|
||||||
|
c.String(http.StatusOK, "fail")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
orderNo := c.Request.Form.Get("trade_order_id")
|
||||||
|
logger.Infof("收到订单支付回调,订单 NO:%s", orderNo)
|
||||||
|
// TODO 是否要保存订单交易流水号
|
||||||
|
h.lock.Lock()
|
||||||
|
defer h.lock.Unlock()
|
||||||
|
|
||||||
|
err = h.notify(orderNo)
|
||||||
|
if err != nil {
|
||||||
|
c.String(http.StatusOK, "fail")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.String(http.StatusOK, "success")
|
c.String(http.StatusOK, "success")
|
||||||
}
|
}
|
||||||
|
|||||||
120
api/handler/prompt_handler.go
Normal file
120
api/handler/prompt_handler.go
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"chatplus/core"
|
||||||
|
"chatplus/core/types"
|
||||||
|
"chatplus/store/model"
|
||||||
|
"chatplus/utils/resp"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/imroc/req/v3"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
const rewritePromptTemplate = "Please rewrite the following text into AI painting prompt words, and please try to add detailed description of the picture, painting style, scene, rendering effect, picture light and other elements. Please output directly in English without any explanation, within 150 words. The text to be rewritten is: [%s]"
|
||||||
|
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]"
|
||||||
|
|
||||||
|
type PromptHandler struct {
|
||||||
|
BaseHandler
|
||||||
|
db *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPromptHandler(app *core.AppServer, db *gorm.DB) *PromptHandler {
|
||||||
|
h := &PromptHandler{db: db}
|
||||||
|
h.App = app
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
type apiRes struct {
|
||||||
|
Model string `json:"model"`
|
||||||
|
Choices []struct {
|
||||||
|
Index int `json:"index"`
|
||||||
|
Message struct {
|
||||||
|
Role string `json:"role"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
} `json:"message"`
|
||||||
|
FinishReason string `json:"finish_reason"`
|
||||||
|
} `json:"choices"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type apiErrRes struct {
|
||||||
|
Error struct {
|
||||||
|
Code interface{} `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Param interface{} `json:"param"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
} `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rewrite translate and rewrite prompt with ChatGPT
|
||||||
|
func (h *PromptHandler) Rewrite(c *gin.Context) {
|
||||||
|
var data struct {
|
||||||
|
Prompt string `json:"prompt"`
|
||||||
|
}
|
||||||
|
if err := c.ShouldBindJSON(&data); err != nil {
|
||||||
|
resp.ERROR(c, types.InvalidArgs)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
content, err := h.request(data.Prompt, rewritePromptTemplate)
|
||||||
|
if err != nil {
|
||||||
|
resp.ERROR(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.SUCCESS(c, content)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *PromptHandler) Translate(c *gin.Context) {
|
||||||
|
var data struct {
|
||||||
|
Prompt string `json:"prompt"`
|
||||||
|
}
|
||||||
|
if err := c.ShouldBindJSON(&data); err != nil {
|
||||||
|
resp.ERROR(c, types.InvalidArgs)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
content, err := h.request(data.Prompt, translatePromptTemplate)
|
||||||
|
if err != nil {
|
||||||
|
resp.ERROR(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
messages := make([]interface{}, 1)
|
||||||
|
messages[0] = types.Message{
|
||||||
|
Role: "user",
|
||||||
|
Content: fmt.Sprintf(promptTemplate, prompt),
|
||||||
|
}
|
||||||
|
|
||||||
|
var response apiRes
|
||||||
|
var errRes apiErrRes
|
||||||
|
r, err := req.C().SetProxyURL(h.App.Config.ProxyURL).R().SetHeader("Content-Type", "application/json").
|
||||||
|
SetHeader("Authorization", "Bearer "+apiKey.Value).
|
||||||
|
SetBody(types.ApiRequest{
|
||||||
|
Model: "gpt-3.5-turbo",
|
||||||
|
Temperature: 0.9,
|
||||||
|
MaxTokens: 1024,
|
||||||
|
Stream: false,
|
||||||
|
Messages: messages,
|
||||||
|
}).
|
||||||
|
SetErrorResult(&errRes).
|
||||||
|
SetSuccessResult(&response).Post(h.App.ChatConfig.OpenAI.ApiURL)
|
||||||
|
if err != nil || r.IsErrorState() {
|
||||||
|
return "", fmt.Errorf("error with http request: %v%v%s", err, r.Err, errRes.Error.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.Choices[0].Message.Content, nil
|
||||||
|
}
|
||||||
@@ -141,7 +141,6 @@ func (h *SdJobHandler) Image(c *gin.Context) {
|
|||||||
h.service.PushTask(types.SdTask{
|
h.service.PushTask(types.SdTask{
|
||||||
Id: int(job.Id),
|
Id: int(job.Id),
|
||||||
SessionId: data.SessionId,
|
SessionId: data.SessionId,
|
||||||
Src: types.TaskSrcImg,
|
|
||||||
Type: types.TaskImage,
|
Type: types.TaskImage,
|
||||||
Prompt: data.Prompt,
|
Prompt: data.Prompt,
|
||||||
Params: params,
|
Params: params,
|
||||||
|
|||||||
40
api/handler/test_handler.go
Normal file
40
api/handler/test_handler.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"chatplus/service"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TestHandler struct {
|
||||||
|
snowflake *service.Snowflake
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTestHandler(snowflake *service.Snowflake) *TestHandler {
|
||||||
|
return &TestHandler{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)
|
||||||
|
}
|
||||||
44
api/main.go
44
api/main.go
@@ -17,7 +17,6 @@ import (
|
|||||||
"chatplus/store"
|
"chatplus/store"
|
||||||
"context"
|
"context"
|
||||||
"embed"
|
"embed"
|
||||||
"github.com/go-redis/redis/v8"
|
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
@@ -26,6 +25,8 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-redis/redis/v8"
|
||||||
|
|
||||||
"github.com/lionsoul2014/ip2region/binding/golang/xdb"
|
"github.com/lionsoul2014/ip2region/binding/golang/xdb"
|
||||||
"go.uber.org/fx"
|
"go.uber.org/fx"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
@@ -162,24 +163,8 @@ func main() {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// MidJourney 机器人
|
// MidJourney service pool
|
||||||
fx.Provide(mj.NewBot),
|
fx.Provide(mj.NewServicePool),
|
||||||
fx.Provide(mj.NewClient),
|
|
||||||
fx.Invoke(func(config *types.AppConfig, bot *mj.Bot) {
|
|
||||||
if config.MjConfig.Enabled {
|
|
||||||
err := bot.Run()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("MidJourney 服务启动失败:", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
fx.Invoke(func(config *types.AppConfig, mjService *mj.Service) {
|
|
||||||
if config.MjConfig.Enabled {
|
|
||||||
go func() {
|
|
||||||
mjService.Run()
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
|
|
||||||
// Stable Diffusion 机器人
|
// Stable Diffusion 机器人
|
||||||
fx.Provide(sd.NewService),
|
fx.Provide(sd.NewService),
|
||||||
@@ -192,6 +177,7 @@ func main() {
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
fx.Provide(payment.NewAlipayService),
|
fx.Provide(payment.NewAlipayService),
|
||||||
|
fx.Provide(payment.NewHuPiPay),
|
||||||
fx.Provide(service.NewSnowflake),
|
fx.Provide(service.NewSnowflake),
|
||||||
fx.Provide(service.NewXXLJobExecutor),
|
fx.Provide(service.NewXXLJobExecutor),
|
||||||
fx.Invoke(func(exec *service.XXLJobExecutor, config *types.AppConfig) {
|
fx.Invoke(func(exec *service.XXLJobExecutor, config *types.AppConfig) {
|
||||||
@@ -254,13 +240,11 @@ func main() {
|
|||||||
group.POST("upscale", h.Upscale)
|
group.POST("upscale", h.Upscale)
|
||||||
group.POST("variation", h.Variation)
|
group.POST("variation", h.Variation)
|
||||||
group.GET("jobs", h.JobList)
|
group.GET("jobs", h.JobList)
|
||||||
group.Any("client", h.Client)
|
|
||||||
}),
|
}),
|
||||||
fx.Invoke(func(s *core.AppServer, h *handler.SdJobHandler) {
|
fx.Invoke(func(s *core.AppServer, h *handler.SdJobHandler) {
|
||||||
group := s.Engine.Group("/api/sd")
|
group := s.Engine.Group("/api/sd")
|
||||||
group.POST("image", h.Image)
|
group.POST("image", h.Image)
|
||||||
group.GET("jobs", h.JobList)
|
group.GET("jobs", h.JobList)
|
||||||
group.Any("client", h.Client)
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// 管理后台控制器
|
// 管理后台控制器
|
||||||
@@ -318,10 +302,12 @@ func main() {
|
|||||||
}),
|
}),
|
||||||
fx.Invoke(func(s *core.AppServer, h *handler.PaymentHandler) {
|
fx.Invoke(func(s *core.AppServer, h *handler.PaymentHandler) {
|
||||||
group := s.Engine.Group("/api/payment/")
|
group := s.Engine.Group("/api/payment/")
|
||||||
group.GET("alipay", h.Alipay)
|
group.GET("doPay", h.DoPay)
|
||||||
|
group.GET("payWays", h.GetPayWays)
|
||||||
group.POST("query", h.OrderQuery)
|
group.POST("query", h.OrderQuery)
|
||||||
group.POST("alipay/qrcode", h.AlipayQrcode)
|
group.POST("qrcode", h.PayQrcode)
|
||||||
group.POST("alipay/notify", h.AlipayNotify)
|
group.POST("alipay/notify", h.AlipayNotify)
|
||||||
|
group.POST("hupipay/notify", h.HuPiPayNotify)
|
||||||
}),
|
}),
|
||||||
fx.Invoke(func(s *core.AppServer, h *admin.ProductHandler) {
|
fx.Invoke(func(s *core.AppServer, h *admin.ProductHandler) {
|
||||||
group := s.Engine.Group("/api/admin/product/")
|
group := s.Engine.Group("/api/admin/product/")
|
||||||
@@ -353,6 +339,18 @@ func main() {
|
|||||||
group.GET("hits", h.Hits)
|
group.GET("hits", h.Hits)
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
fx.Provide(handler.NewPromptHandler),
|
||||||
|
fx.Invoke(func(s *core.AppServer, h *handler.PromptHandler) {
|
||||||
|
group := s.Engine.Group("/api/prompt/")
|
||||||
|
group.POST("rewrite", h.Rewrite)
|
||||||
|
group.POST("translate", h.Translate)
|
||||||
|
}),
|
||||||
|
|
||||||
|
fx.Provide(handler.NewTestHandler),
|
||||||
|
fx.Invoke(func(s *core.AppServer, h *handler.TestHandler) {
|
||||||
|
group := s.Engine.Group("/test/")
|
||||||
|
group.GET("pay", h.TestPay)
|
||||||
|
}),
|
||||||
fx.Invoke(func(s *core.AppServer, db *gorm.DB) {
|
fx.Invoke(func(s *core.AppServer, db *gorm.DB) {
|
||||||
err := s.Run(db)
|
err := s.Run(db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
BIN
api/res/img/wechat-pay.jpg
Normal file
BIN
api/res/img/wechat-pay.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.7 KiB |
@@ -1,20 +1,20 @@
|
|||||||
{
|
{
|
||||||
"data": [
|
"data": [
|
||||||
"task(m1wpaa4v60zedj8)",
|
"task(s95jqt5jr8yppcp)",
|
||||||
"a cute cat",
|
"A beautiful Chinese girl in a garden",
|
||||||
"",
|
"",
|
||||||
[],
|
[],
|
||||||
20,
|
30,
|
||||||
"DPM++ 2M Karras",
|
"Euler a",
|
||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
7,
|
7,
|
||||||
512,
|
512,
|
||||||
384,
|
512,
|
||||||
true,
|
true,
|
||||||
0.7,
|
0.7,
|
||||||
2,
|
2,
|
||||||
"ESRGAN_4x",
|
"Latent",
|
||||||
10,
|
10,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
@@ -55,13 +55,13 @@
|
|||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
0,
|
0,
|
||||||
false,
|
[
|
||||||
[],
|
],
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
""
|
""
|
||||||
],
|
],
|
||||||
"event_data": null,
|
"event_data": null,
|
||||||
"fn_index": 96,
|
"fn_index": 95,
|
||||||
"session_hash": "kmb0ojjfhdj"
|
"session_hash": "eqwumnt3rov"
|
||||||
}
|
}
|
||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"chatplus/store/model"
|
"chatplus/store/model"
|
||||||
"chatplus/utils"
|
"chatplus/utils"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/imroc/req/v3"
|
"github.com/imroc/req/v3"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
@@ -14,7 +15,6 @@ import (
|
|||||||
|
|
||||||
type FuncImage struct {
|
type FuncImage struct {
|
||||||
name string
|
name string
|
||||||
apiURL string
|
|
||||||
db *gorm.DB
|
db *gorm.DB
|
||||||
uploadManager *oss.UploaderManager
|
uploadManager *oss.UploaderManager
|
||||||
proxyURL string
|
proxyURL string
|
||||||
@@ -26,7 +26,6 @@ func NewImageFunc(db *gorm.DB, manager *oss.UploaderManager, config *types.AppCo
|
|||||||
name: "DALL-E3 绘画",
|
name: "DALL-E3 绘画",
|
||||||
uploadManager: manager,
|
uploadManager: manager,
|
||||||
proxyURL: config.ProxyURL,
|
proxyURL: config.ProxyURL,
|
||||||
apiURL: "https://api.openai.com/v1/images/generations",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,9 +56,34 @@ type ErrRes struct {
|
|||||||
func (f FuncImage) Invoke(params map[string]interface{}) (string, error) {
|
func (f FuncImage) Invoke(params map[string]interface{}) (string, error) {
|
||||||
logger.Infof("绘画参数:%+v", params)
|
logger.Infof("绘画参数:%+v", params)
|
||||||
prompt := utils.InterfaceToString(params["prompt"])
|
prompt := utils.InterfaceToString(params["prompt"])
|
||||||
// 获取绘图 API KEY
|
// get image generation API KEY
|
||||||
var apiKey model.ApiKey
|
var apiKey model.ApiKey
|
||||||
f.db.Where("platform = ? AND type = ?", types.OpenAI, "img").Order("last_used_at ASC").First(&apiKey)
|
tx := f.db.Where("platform = ? AND type = ?", types.OpenAI, "img").Order("last_used_at ASC").First(&apiKey)
|
||||||
|
if tx.Error != nil {
|
||||||
|
return "", fmt.Errorf("error with get generation API KEY: %v", tx.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// get image generation api URL
|
||||||
|
var conf model.Config
|
||||||
|
var chatConfig types.ChatConfig
|
||||||
|
tx = f.db.Where("marker", "chat").First(&conf)
|
||||||
|
if tx.Error != nil {
|
||||||
|
return "", fmt.Errorf("error with get chat configs: %v", tx.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := utils.JsonDecode(conf.Config, &chatConfig)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("error with decode chat config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
apiURL := chatConfig.DallApiURL
|
||||||
|
if utils.IsEmptyValue(apiURL) {
|
||||||
|
apiURL = "https://api.openai.com/v1/images/generations"
|
||||||
|
}
|
||||||
|
imgNum := chatConfig.DallImgNum
|
||||||
|
if imgNum <= 0 {
|
||||||
|
imgNum = 1
|
||||||
|
}
|
||||||
var res imgRes
|
var res imgRes
|
||||||
var errRes ErrRes
|
var errRes ErrRes
|
||||||
r, err := req.C().SetProxyURL(f.proxyURL).R().SetHeader("Content-Type", "application/json").
|
r, err := req.C().SetProxyURL(f.proxyURL).R().SetHeader("Content-Type", "application/json").
|
||||||
@@ -67,11 +91,11 @@ func (f FuncImage) Invoke(params map[string]interface{}) (string, error) {
|
|||||||
SetBody(imgReq{
|
SetBody(imgReq{
|
||||||
Model: "dall-e-3",
|
Model: "dall-e-3",
|
||||||
Prompt: prompt,
|
Prompt: prompt,
|
||||||
N: 1,
|
N: imgNum,
|
||||||
Size: "1024x1024",
|
Size: "1024x1024",
|
||||||
}).
|
}).
|
||||||
SetErrorResult(&errRes).
|
SetErrorResult(&errRes).
|
||||||
SetSuccessResult(&res).Post(f.apiURL)
|
SetSuccessResult(&res).Post(apiURL)
|
||||||
if err != nil || r.IsErrorState() {
|
if err != nil || r.IsErrorState() {
|
||||||
return "", fmt.Errorf("error with http request: %v%v%s", err, r.Err, errRes.Error.Message)
|
return "", fmt.Errorf("error with http request: %v%v%s", err, r.Err, errRes.Error.Message)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,17 +19,18 @@ var logger = logger2.GetLogger()
|
|||||||
type Bot struct {
|
type Bot struct {
|
||||||
config *types.MidJourneyConfig
|
config *types.MidJourneyConfig
|
||||||
bot *discordgo.Session
|
bot *discordgo.Session
|
||||||
|
name string
|
||||||
service *Service
|
service *Service
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBot(config *types.AppConfig, service *Service) (*Bot, error) {
|
func NewBot(name string, proxy string, config *types.MidJourneyConfig, service *Service) (*Bot, error) {
|
||||||
discord, err := discordgo.New("Bot " + config.MjConfig.BotToken)
|
discord, err := discordgo.New("Bot " + config.BotToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.ProxyURL != "" {
|
if proxy != "" {
|
||||||
proxy, _ := url.Parse(config.ProxyURL)
|
proxy, _ := url.Parse(proxy)
|
||||||
discord.Client = &http.Client{
|
discord.Client = &http.Client{
|
||||||
Transport: &http.Transport{
|
Transport: &http.Transport{
|
||||||
Proxy: http.ProxyURL(proxy),
|
Proxy: http.ProxyURL(proxy),
|
||||||
@@ -41,8 +42,9 @@ func NewBot(config *types.AppConfig, service *Service) (*Bot, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &Bot{
|
return &Bot{
|
||||||
config: &config.MjConfig,
|
config: config,
|
||||||
bot: discord,
|
bot: discord,
|
||||||
|
name: name,
|
||||||
service: service,
|
service: service,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@@ -52,13 +54,13 @@ func (b *Bot) Run() error {
|
|||||||
b.bot.AddHandler(b.messageCreate)
|
b.bot.AddHandler(b.messageCreate)
|
||||||
b.bot.AddHandler(b.messageUpdate)
|
b.bot.AddHandler(b.messageUpdate)
|
||||||
|
|
||||||
logger.Info("Starting MidJourney Bot...")
|
logger.Infof("Starting MidJourney %s", b.name)
|
||||||
err := b.bot.Open()
|
err := b.bot.Open()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Error opening Discord connection:", err)
|
logger.Errorf("Error opening Discord connection for %s, error: %v", b.name, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
logger.Info("Starting MidJourney Bot successfully!")
|
logger.Infof("Starting MidJourney %s successfully!", b.name)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,13 +14,14 @@ type Client struct {
|
|||||||
config *types.MidJourneyConfig
|
config *types.MidJourneyConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClient(config *types.AppConfig) *Client {
|
func NewClient(config *types.MidJourneyConfig, proxy string) *Client {
|
||||||
client := req.C().SetTimeout(10 * time.Second)
|
client := req.C().SetTimeout(10 * time.Second)
|
||||||
// set proxy URL
|
// set proxy URL
|
||||||
if config.ProxyURL != "" {
|
if proxy != "" {
|
||||||
client.SetProxyURL(config.ProxyURL)
|
client.SetProxyURL(proxy)
|
||||||
}
|
}
|
||||||
return &Client{client: client, config: &config.MjConfig}
|
logger.Info(proxy)
|
||||||
|
return &Client{client: client, config: config}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Imagine(prompt string) error {
|
func (c *Client) Imagine(prompt string) error {
|
||||||
|
|||||||
66
api/service/mj/pool.go
Normal file
66
api/service/mj/pool.go
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package mj
|
||||||
|
|
||||||
|
import (
|
||||||
|
"chatplus/core/types"
|
||||||
|
"chatplus/service/oss"
|
||||||
|
"chatplus/store"
|
||||||
|
"fmt"
|
||||||
|
"github.com/go-redis/redis/v8"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ServicePool Mj service pool
|
||||||
|
type ServicePool struct {
|
||||||
|
services []*Service
|
||||||
|
taskQueue *store.RedisQueue
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServicePool(db *gorm.DB, redisCli *redis.Client, manager *oss.UploaderManager, appConfig *types.AppConfig) *ServicePool {
|
||||||
|
services := make([]*Service, 0)
|
||||||
|
queue := store.NewRedisQueue("MidJourney_Task_Queue", redisCli)
|
||||||
|
// create mj client and service
|
||||||
|
for k, config := range appConfig.MjConfigs {
|
||||||
|
if config.Enabled == false {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// create mj client
|
||||||
|
client := NewClient(&config, appConfig.ProxyURL)
|
||||||
|
|
||||||
|
name := fmt.Sprintf("MjService-%d", k)
|
||||||
|
// create mj service
|
||||||
|
service := NewService(name, queue, 4, 600, db, client, manager, appConfig)
|
||||||
|
botName := fmt.Sprintf("MjBot-%d", k)
|
||||||
|
bot, err := NewBot(botName, appConfig.ProxyURL, &config, service)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
err = bot.Run()
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// run mj service
|
||||||
|
go func() {
|
||||||
|
service.Run()
|
||||||
|
}()
|
||||||
|
|
||||||
|
services = append(services, service)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ServicePool{
|
||||||
|
taskQueue: queue,
|
||||||
|
services: services,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PushTask push a new mj task in to task queue
|
||||||
|
func (p *ServicePool) PushTask(task types.MjTask) {
|
||||||
|
logger.Debugf("add a new MidJourney task to the task list: %+v", task)
|
||||||
|
p.taskQueue.RPush(task)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasAvailableService check if has available mj service in pool
|
||||||
|
func (p *ServicePool) HasAvailableService() bool {
|
||||||
|
return len(p.services) > 0
|
||||||
|
}
|
||||||
@@ -2,63 +2,64 @@ package mj
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"chatplus/core/types"
|
"chatplus/core/types"
|
||||||
|
"chatplus/service"
|
||||||
"chatplus/service/oss"
|
"chatplus/service/oss"
|
||||||
"chatplus/store"
|
"chatplus/store"
|
||||||
"chatplus/store/model"
|
"chatplus/store/model"
|
||||||
"chatplus/store/vo"
|
|
||||||
"chatplus/utils"
|
|
||||||
"context"
|
|
||||||
"encoding/base64"
|
|
||||||
"fmt"
|
|
||||||
"github.com/go-redis/redis/v8"
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MJ 绘画服务
|
// Service MJ 绘画服务
|
||||||
|
|
||||||
const RunningJobKey = "MidJourney_Running_Job"
|
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
client *Client // MJ 客户端
|
name string // service name
|
||||||
taskQueue *store.RedisQueue
|
client *Client // MJ client
|
||||||
redis *redis.Client
|
taskQueue *store.RedisQueue
|
||||||
db *gorm.DB
|
db *gorm.DB
|
||||||
uploadManager *oss.UploaderManager
|
uploadManager *oss.UploaderManager
|
||||||
Clients *types.LMap[string, *types.WsClient] // MJ 绘画页面 websocket 连接池,用户推送绘画消息
|
proxyURL string
|
||||||
ChatClients *types.LMap[string, *types.WsClient] // 聊天页面 websocket 连接池,用于推送绘画消息
|
maxHandleTaskNum int32 // max task number current service can handle
|
||||||
proxyURL string
|
handledTaskNum int32 // already handled task number
|
||||||
|
taskStartTimes map[int]time.Time // task start time, to check if the task is timeout
|
||||||
|
taskTimeout int64
|
||||||
|
snowflake *service.Snowflake
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService(redisCli *redis.Client, db *gorm.DB, client *Client, manager *oss.UploaderManager, config *types.AppConfig) *Service {
|
func NewService(name string, queue *store.RedisQueue, maxTaskNum int32, timeout int64, db *gorm.DB, client *Client, manager *oss.UploaderManager, config *types.AppConfig) *Service {
|
||||||
return &Service{
|
return &Service{
|
||||||
redis: redisCli,
|
name: name,
|
||||||
db: db,
|
db: db,
|
||||||
taskQueue: store.NewRedisQueue("MidJourney_Task_Queue", redisCli),
|
taskQueue: queue,
|
||||||
client: client,
|
client: client,
|
||||||
uploadManager: manager,
|
uploadManager: manager,
|
||||||
Clients: types.NewLMap[string, *types.WsClient](),
|
taskTimeout: timeout,
|
||||||
ChatClients: types.NewLMap[string, *types.WsClient](),
|
maxHandleTaskNum: maxTaskNum,
|
||||||
proxyURL: config.ProxyURL,
|
proxyURL: config.ProxyURL,
|
||||||
|
taskStartTimes: make(map[int]time.Time, 0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) Run() {
|
func (s *Service) Run() {
|
||||||
logger.Info("Starting MidJourney job consumer.")
|
logger.Infof("Starting MidJourney job consumer for %s", s.name)
|
||||||
ctx := context.Background()
|
|
||||||
for {
|
for {
|
||||||
_, err := s.redis.Get(ctx, RunningJobKey).Result()
|
s.checkTasks()
|
||||||
if err == nil { // 队列串行执行
|
if !s.canHandleTask() {
|
||||||
|
// current service is full, can not handle more task
|
||||||
|
// waiting for running task finish
|
||||||
time.Sleep(time.Second * 3)
|
time.Sleep(time.Second * 3)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
var task types.MjTask
|
var task types.MjTask
|
||||||
err = s.taskQueue.LPop(&task)
|
err := s.taskQueue.LPop(&task)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("taking task with error: %v", err)
|
logger.Errorf("taking task with error: %v", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
logger.Infof("Consuming Task: %+v", task)
|
|
||||||
|
logger.Infof("%s handle a new MidJourney task: %+v", s.name, task)
|
||||||
switch task.Type {
|
switch task.Type {
|
||||||
case types.TaskImage:
|
case types.TaskImage:
|
||||||
err = s.client.Imagine(task.Prompt)
|
err = s.client.Imagine(task.Prompt)
|
||||||
@@ -70,50 +71,43 @@ func (s *Service) Run() {
|
|||||||
case types.TaskVariation:
|
case types.TaskVariation:
|
||||||
err = s.client.Variation(task.Index, task.MessageId, task.MessageHash)
|
err = s.client.Variation(task.Index, task.MessageId, task.MessageHash)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("绘画任务执行失败:", err)
|
logger.Error("绘画任务执行失败:", err)
|
||||||
// 删除任务
|
// update the task progress
|
||||||
s.db.Delete(&model.MidJourneyJob{Id: uint(task.Id)})
|
s.db.Model(&model.MidJourneyJob{Id: uint(task.Id)}).UpdateColumn("progress", -1)
|
||||||
// 推送任务到前端
|
atomic.AddInt32(&s.handledTaskNum, -1)
|
||||||
client := s.Clients.Get(task.SessionId)
|
|
||||||
if client != nil {
|
|
||||||
utils.ReplyChunkMessage(client, vo.MidJourneyJob{
|
|
||||||
Type: task.Type.String(),
|
|
||||||
UserId: task.UserId,
|
|
||||||
MessageId: task.MessageId,
|
|
||||||
Progress: -1,
|
|
||||||
Prompt: task.Prompt,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新任务的执行状态
|
// lock the task until the execute timeout
|
||||||
s.db.Model(&model.MidJourneyJob{}).Where("id = ?", task.Id).UpdateColumn("started", true)
|
s.taskStartTimes[task.Id] = time.Now()
|
||||||
// 锁定任务执行通道,直到任务超时(5分钟)
|
atomic.AddInt32(&s.handledTaskNum, 1)
|
||||||
s.redis.Set(ctx, RunningJobKey, utils.JsonEncode(task), time.Minute*5)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) PushTask(task types.MjTask) {
|
// check if current service instance can handle more task
|
||||||
logger.Infof("add a new MidJourney Task: %+v", task)
|
func (s *Service) canHandleTask() bool {
|
||||||
s.taskQueue.RPush(task)
|
handledNum := atomic.LoadInt32(&s.handledTaskNum)
|
||||||
|
return handledNum < s.maxHandleTaskNum
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove the expired tasks
|
||||||
|
func (s *Service) checkTasks() {
|
||||||
|
for k, t := range s.taskStartTimes {
|
||||||
|
if time.Now().Unix()-t.Unix() > s.taskTimeout {
|
||||||
|
delete(s.taskStartTimes, k)
|
||||||
|
atomic.AddInt32(&s.handledTaskNum, -1)
|
||||||
|
// delete task from database
|
||||||
|
s.db.Delete(&model.MidJourneyJob{Id: uint(k)}, "progress < 100")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) Notify(data CBReq) {
|
func (s *Service) Notify(data CBReq) {
|
||||||
taskString, err := s.redis.Get(context.Background(), RunningJobKey).Result()
|
// extract the task ID
|
||||||
if err != nil { // 过期任务,丢弃
|
split := strings.Split(data.Prompt, " ")
|
||||||
logger.Warn("任务已过期:", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var task types.MjTask
|
|
||||||
err = utils.JsonDecode(taskString, &task)
|
|
||||||
if err != nil { // 非标准任务,丢弃
|
|
||||||
logger.Warn("任务解析失败:", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var job model.MidJourneyJob
|
var job model.MidJourneyJob
|
||||||
res := s.db.Where("message_id = ?", data.MessageId).First(&job)
|
res := s.db.Where("message_id = ?", data.MessageId).First(&job)
|
||||||
if res.Error == nil && data.Status == Finished {
|
if res.Error == nil && data.Status == Finished {
|
||||||
@@ -121,137 +115,39 @@ func (s *Service) Notify(data CBReq) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if task.Src == types.TaskSrcImg { // 绘画任务
|
res = s.db.Where("task_id = ?", split[0]).First(&job)
|
||||||
var job model.MidJourneyJob
|
if res.Error != nil {
|
||||||
res := s.db.Where("id = ?", task.Id).First(&job)
|
logger.Warn("非法任务:", res.Error)
|
||||||
if res.Error != nil {
|
return
|
||||||
logger.Warn("非法任务:", res.Error)
|
}
|
||||||
|
job.MessageId = data.MessageId
|
||||||
|
job.ReferenceId = data.ReferenceId
|
||||||
|
job.Progress = data.Progress
|
||||||
|
job.Prompt = data.Prompt
|
||||||
|
job.Hash = data.Image.Hash
|
||||||
|
job.OrgURL = data.Image.URL
|
||||||
|
|
||||||
|
// upload image
|
||||||
|
if data.Status == Finished {
|
||||||
|
imgURL, err := s.uploadManager.GetUploadHandler().PutImg(data.Image.URL, true)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("error with download img: ", err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
job.MessageId = data.MessageId
|
job.ImgURL = imgURL
|
||||||
job.ReferenceId = data.ReferenceId
|
}
|
||||||
job.Progress = data.Progress
|
|
||||||
job.Prompt = data.Prompt
|
res = s.db.Updates(&job)
|
||||||
job.Hash = data.Image.Hash
|
if res.Error != nil {
|
||||||
|
logger.Error("error with update job: ", res.Error)
|
||||||
// 任务完成,将最终的图片下载下来
|
return
|
||||||
if data.Progress == 100 {
|
|
||||||
imgURL, err := s.uploadManager.GetUploadHandler().PutImg(data.Image.URL, true)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("error with download img: ", err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
job.ImgURL = imgURL
|
|
||||||
} else {
|
|
||||||
// 临时图片直接保存,访问的时候使用代理进行转发
|
|
||||||
job.ImgURL = data.Image.URL
|
|
||||||
}
|
|
||||||
res = s.db.Updates(&job)
|
|
||||||
if res.Error != nil {
|
|
||||||
logger.Error("error with update job: ", res.Error)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var jobVo vo.MidJourneyJob
|
|
||||||
err := utils.CopyObject(job, &jobVo)
|
|
||||||
if err == nil {
|
|
||||||
if data.Progress < 100 {
|
|
||||||
image, err := utils.DownloadImage(jobVo.ImgURL, s.proxyURL)
|
|
||||||
if err == nil {
|
|
||||||
jobVo.ImgURL = "data:image/png;base64," + base64.StdEncoding.EncodeToString(image)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 推送任务到前端
|
|
||||||
client := s.Clients.Get(task.SessionId)
|
|
||||||
if client != nil {
|
|
||||||
utils.ReplyChunkMessage(client, jobVo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if task.Src == types.TaskSrcChat { // 聊天任务
|
|
||||||
wsClient := s.ChatClients.Get(task.SessionId)
|
|
||||||
if data.Status == Finished {
|
|
||||||
if wsClient != nil && data.ReferenceId != "" {
|
|
||||||
content := fmt.Sprintf("**%s** 任务执行成功,正在从 MidJourney 服务器下载图片,请稍后...", data.Prompt)
|
|
||||||
utils.ReplyMessage(wsClient, content)
|
|
||||||
}
|
|
||||||
// download image
|
|
||||||
imgURL, err := s.uploadManager.GetUploadHandler().PutImg(data.Image.URL, true)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("error with download image: ", err)
|
|
||||||
if wsClient != nil && data.ReferenceId != "" {
|
|
||||||
content := fmt.Sprintf("**%s** 图片下载失败:%s", data.Prompt, err.Error())
|
|
||||||
utils.ReplyMessage(wsClient, content)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
tx := s.db.Begin()
|
|
||||||
data.Image.URL = imgURL
|
|
||||||
message := model.HistoryMessage{
|
|
||||||
UserId: uint(task.UserId),
|
|
||||||
ChatId: task.ChatId,
|
|
||||||
RoleId: uint(task.RoleId),
|
|
||||||
Type: types.MjMsg,
|
|
||||||
Icon: task.Icon,
|
|
||||||
Content: utils.JsonEncode(data),
|
|
||||||
Tokens: 0,
|
|
||||||
UseContext: false,
|
|
||||||
}
|
|
||||||
res = tx.Create(&message)
|
|
||||||
if res.Error != nil {
|
|
||||||
logger.Error("error with update database: ", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// save the job
|
|
||||||
job.UserId = task.UserId
|
|
||||||
job.Type = task.Type.String()
|
|
||||||
job.MessageId = data.MessageId
|
|
||||||
job.ReferenceId = data.ReferenceId
|
|
||||||
job.Prompt = data.Prompt
|
|
||||||
job.ImgURL = imgURL
|
|
||||||
job.Progress = data.Progress
|
|
||||||
job.Hash = data.Image.Hash
|
|
||||||
job.CreatedAt = time.Now()
|
|
||||||
res = tx.Create(&job)
|
|
||||||
if res.Error != nil {
|
|
||||||
logger.Error("error with update database: ", err)
|
|
||||||
tx.Rollback()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
tx.Commit()
|
|
||||||
}
|
|
||||||
|
|
||||||
if wsClient == nil { // 客户端断线,则丢弃
|
|
||||||
logger.Errorf("Client is offline: %+v", data)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if data.Status == Finished {
|
|
||||||
utils.ReplyChunkMessage(wsClient, types.WsMessage{Type: types.WsMjImg, Content: data})
|
|
||||||
utils.ReplyChunkMessage(wsClient, types.WsMessage{Type: types.WsEnd})
|
|
||||||
// 本次绘画完毕,移除客户端
|
|
||||||
s.ChatClients.Delete(task.SessionId)
|
|
||||||
} else {
|
|
||||||
// 使用代理临时转发图片
|
|
||||||
if data.Image.URL != "" {
|
|
||||||
image, err := utils.DownloadImage(data.Image.URL, s.proxyURL)
|
|
||||||
if err == nil {
|
|
||||||
data.Image.URL = "data:image/png;base64," + base64.StdEncoding.EncodeToString(image)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
utils.ReplyChunkMessage(wsClient, types.WsMessage{Type: types.WsMjImg, Content: data})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新用户剩余绘图次数
|
|
||||||
// TODO: 放大图片是否需要消耗绘图次数?
|
|
||||||
if data.Status == Finished {
|
if data.Status == Finished {
|
||||||
s.db.Model(&model.User{}).Where("id = ?", task.UserId).UpdateColumn("img_calls", gorm.Expr("img_calls - ?", 1))
|
// update user's img calls
|
||||||
// 解除任务锁定
|
s.db.Model(&model.User{}).Where("id = ?", job.UserId).UpdateColumn("img_calls", gorm.Expr("img_calls - ?", 1))
|
||||||
s.redis.Del(context.Background(), RunningJobKey)
|
// release lock task
|
||||||
|
atomic.AddInt32(&s.handledTaskNum, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,10 @@ func NewAliYunOss(appConfig *types.AppConfig) (*AliYunOss, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if config.SubDir == "" {
|
||||||
|
config.SubDir = "gpt"
|
||||||
|
}
|
||||||
|
|
||||||
return &AliYunOss{
|
return &AliYunOss{
|
||||||
config: config,
|
config: config,
|
||||||
bucket: bucket,
|
bucket: bucket,
|
||||||
@@ -54,14 +58,14 @@ func (s AliYunOss) PutFile(ctx *gin.Context, name string) (string, error) {
|
|||||||
defer src.Close()
|
defer src.Close()
|
||||||
|
|
||||||
fileExt := filepath.Ext(file.Filename)
|
fileExt := filepath.Ext(file.Filename)
|
||||||
objectKey := fmt.Sprintf("%d%s", time.Now().UnixMicro(), fileExt)
|
objectKey := fmt.Sprintf("%s/%d%s", s.config.SubDir, time.Now().UnixMicro(), fileExt)
|
||||||
// 上传文件
|
// 上传文件
|
||||||
err = s.bucket.PutObject(objectKey, src)
|
err = s.bucket.PutObject(objectKey, src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Sprintf("https://%s.%s/%s", s.config.Bucket, s.config.Domain, objectKey), nil
|
return fmt.Sprintf("%s/%s", s.config.Domain, objectKey), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s AliYunOss) PutImg(imageURL string, useProxy bool) (string, error) {
|
func (s AliYunOss) PutImg(imageURL string, useProxy bool) (string, error) {
|
||||||
@@ -80,18 +84,19 @@ func (s AliYunOss) PutImg(imageURL string, useProxy bool) (string, error) {
|
|||||||
return "", fmt.Errorf("error with parse image URL: %v", err)
|
return "", fmt.Errorf("error with parse image URL: %v", err)
|
||||||
}
|
}
|
||||||
fileExt := filepath.Ext(parse.Path)
|
fileExt := filepath.Ext(parse.Path)
|
||||||
objectKey := fmt.Sprintf("%d%s", time.Now().UnixMicro(), fileExt)
|
objectKey := fmt.Sprintf("%s/%d%s", s.config.SubDir, time.Now().UnixMicro(), fileExt)
|
||||||
// 上传文件字节数据
|
// 上传文件字节数据
|
||||||
err = s.bucket.PutObject(objectKey, bytes.NewReader(imageData))
|
err = s.bucket.PutObject(objectKey, bytes.NewReader(imageData))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("https://%s.%s/%s", s.config.Bucket, s.config.Domain, objectKey), nil
|
return fmt.Sprintf("%s/%s", s.config.Domain, objectKey), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s AliYunOss) Delete(fileURL string) error {
|
func (s AliYunOss) Delete(fileURL string) error {
|
||||||
objectName := filepath.Base(fileURL)
|
objectName := filepath.Base(fileURL)
|
||||||
return s.bucket.DeleteObject(objectName)
|
key := fmt.Sprintf("%s/%s", s.config.SubDir, objectName)
|
||||||
|
return s.bucket.DeleteObject(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Uploader = AliYunOss{}
|
var _ Uploader = AliYunOss{}
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ func NewMiniOss(appConfig *types.AppConfig) (MiniOss, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return MiniOss{}, err
|
return MiniOss{}, err
|
||||||
}
|
}
|
||||||
|
if config.SubDir == "" {
|
||||||
|
config.SubDir = "gpt"
|
||||||
|
}
|
||||||
return MiniOss{config: config, client: minioClient, proxyURL: appConfig.ProxyURL}, nil
|
return MiniOss{config: config, client: minioClient, proxyURL: appConfig.ProxyURL}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,7 +51,7 @@ func (s MiniOss) PutImg(imageURL string, useProxy bool) (string, error) {
|
|||||||
return "", fmt.Errorf("error with parse image URL: %v", err)
|
return "", fmt.Errorf("error with parse image URL: %v", err)
|
||||||
}
|
}
|
||||||
fileExt := filepath.Ext(parse.Path)
|
fileExt := filepath.Ext(parse.Path)
|
||||||
filename := fmt.Sprintf("%d%s", time.Now().UnixMicro(), fileExt)
|
filename := fmt.Sprintf("%s/%d%s", s.config.SubDir, time.Now().UnixMicro(), fileExt)
|
||||||
info, err := s.client.PutObject(
|
info, err := s.client.PutObject(
|
||||||
context.Background(),
|
context.Background(),
|
||||||
s.config.Bucket,
|
s.config.Bucket,
|
||||||
@@ -75,7 +78,7 @@ func (s MiniOss) PutFile(ctx *gin.Context, name string) (string, error) {
|
|||||||
defer fileReader.Close()
|
defer fileReader.Close()
|
||||||
|
|
||||||
fileExt := filepath.Ext(file.Filename)
|
fileExt := filepath.Ext(file.Filename)
|
||||||
filename := fmt.Sprintf("%d%s", time.Now().UnixMicro(), fileExt)
|
filename := fmt.Sprintf("%s/%d%s", s.config.SubDir, time.Now().UnixMicro(), fileExt)
|
||||||
info, err := s.client.PutObject(ctx, s.config.Bucket, filename, fileReader, file.Size, minio.PutObjectOptions{
|
info, err := s.client.PutObject(ctx, s.config.Bucket, filename, fileReader, file.Size, minio.PutObjectOptions{
|
||||||
ContentType: file.Header.Get("Content-Type"),
|
ContentType: file.Header.Get("Content-Type"),
|
||||||
})
|
})
|
||||||
@@ -88,7 +91,8 @@ func (s MiniOss) PutFile(ctx *gin.Context, name string) (string, error) {
|
|||||||
|
|
||||||
func (s MiniOss) Delete(fileURL string) error {
|
func (s MiniOss) Delete(fileURL string) error {
|
||||||
objectName := filepath.Base(fileURL)
|
objectName := filepath.Base(fileURL)
|
||||||
return s.client.RemoveObject(context.Background(), s.config.Bucket, objectName, minio.RemoveObjectOptions{})
|
key := fmt.Sprintf("%s/%s", s.config.SubDir, objectName)
|
||||||
|
return s.client.RemoveObject(context.Background(), s.config.Bucket, key, minio.RemoveObjectOptions{})
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Uploader = MiniOss{}
|
var _ Uploader = MiniOss{}
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ type QinNiuOss struct {
|
|||||||
uploader *storage.FormUploader
|
uploader *storage.FormUploader
|
||||||
manager *storage.BucketManager
|
manager *storage.BucketManager
|
||||||
proxyURL string
|
proxyURL string
|
||||||
dir string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewQiNiuOss(appConfig *types.AppConfig) QinNiuOss {
|
func NewQiNiuOss(appConfig *types.AppConfig) QinNiuOss {
|
||||||
@@ -38,6 +37,9 @@ func NewQiNiuOss(appConfig *types.AppConfig) QinNiuOss {
|
|||||||
putPolicy := storage.PutPolicy{
|
putPolicy := storage.PutPolicy{
|
||||||
Scope: config.Bucket,
|
Scope: config.Bucket,
|
||||||
}
|
}
|
||||||
|
if config.SubDir == "" {
|
||||||
|
config.SubDir = "gpt"
|
||||||
|
}
|
||||||
return QinNiuOss{
|
return QinNiuOss{
|
||||||
config: config,
|
config: config,
|
||||||
mac: mac,
|
mac: mac,
|
||||||
@@ -45,7 +47,6 @@ func NewQiNiuOss(appConfig *types.AppConfig) QinNiuOss {
|
|||||||
uploader: formUploader,
|
uploader: formUploader,
|
||||||
manager: storage.NewBucketManager(mac, &storeConfig),
|
manager: storage.NewBucketManager(mac, &storeConfig),
|
||||||
proxyURL: appConfig.ProxyURL,
|
proxyURL: appConfig.ProxyURL,
|
||||||
dir: "chatgpt-plus",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +64,7 @@ func (s QinNiuOss) PutFile(ctx *gin.Context, name string) (string, error) {
|
|||||||
defer src.Close()
|
defer src.Close()
|
||||||
|
|
||||||
fileExt := filepath.Ext(file.Filename)
|
fileExt := filepath.Ext(file.Filename)
|
||||||
key := fmt.Sprintf("%s/%d%s", s.dir, time.Now().UnixMicro(), fileExt)
|
key := fmt.Sprintf("%s/%d%s", s.config.SubDir, time.Now().UnixMicro(), fileExt)
|
||||||
// 上传文件
|
// 上传文件
|
||||||
ret := storage.PutRet{}
|
ret := storage.PutRet{}
|
||||||
extra := storage.PutExtra{}
|
extra := storage.PutExtra{}
|
||||||
@@ -91,7 +92,7 @@ func (s QinNiuOss) PutImg(imageURL string, useProxy bool) (string, error) {
|
|||||||
return "", fmt.Errorf("error with parse image URL: %v", err)
|
return "", fmt.Errorf("error with parse image URL: %v", err)
|
||||||
}
|
}
|
||||||
fileExt := filepath.Ext(parse.Path)
|
fileExt := filepath.Ext(parse.Path)
|
||||||
key := fmt.Sprintf("%s/%d%s", s.dir, time.Now().UnixMicro(), fileExt)
|
key := fmt.Sprintf("%s/%d%s", s.config.SubDir, time.Now().UnixMicro(), fileExt)
|
||||||
ret := storage.PutRet{}
|
ret := storage.PutRet{}
|
||||||
extra := storage.PutExtra{}
|
extra := storage.PutExtra{}
|
||||||
// 上传文件字节数据
|
// 上传文件字节数据
|
||||||
@@ -104,7 +105,7 @@ func (s QinNiuOss) PutImg(imageURL string, useProxy bool) (string, error) {
|
|||||||
|
|
||||||
func (s QinNiuOss) Delete(fileURL string) error {
|
func (s QinNiuOss) Delete(fileURL string) error {
|
||||||
objectName := filepath.Base(fileURL)
|
objectName := filepath.Base(fileURL)
|
||||||
key := fmt.Sprintf("%s/%s", s.dir, objectName)
|
key := fmt.Sprintf("%s/%s", s.config.SubDir, objectName)
|
||||||
return s.manager.Delete(s.config.Bucket, key)
|
return s.manager.Delete(s.config.Bucket, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
72
api/service/payment/hupipay_serive.go
Normal file
72
api/service/payment/hupipay_serive.go
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
package payment
|
||||||
|
|
||||||
|
import (
|
||||||
|
"chatplus/core/types"
|
||||||
|
"crypto/md5"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HuPiPayService struct {
|
||||||
|
appId string
|
||||||
|
appSecret string
|
||||||
|
host string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHuPiPay(config *types.AppConfig) *HuPiPayService {
|
||||||
|
return &HuPiPayService{
|
||||||
|
appId: config.HuPiPayConfig.AppId,
|
||||||
|
appSecret: config.HuPiPayConfig.AppSecret,
|
||||||
|
host: config.HuPiPayConfig.PayURL,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pay 执行支付请求操作
|
||||||
|
func (s *HuPiPayService) Pay(params map[string]string) (string, error) {
|
||||||
|
data := url.Values{}
|
||||||
|
simple := strconv.FormatInt(time.Now().Unix(), 10)
|
||||||
|
params["appid"] = s.appId
|
||||||
|
params["time"] = simple
|
||||||
|
params["nonce_str"] = simple
|
||||||
|
for k, v := range params {
|
||||||
|
data.Add(k, v)
|
||||||
|
}
|
||||||
|
data.Add("hash", s.Sign(params))
|
||||||
|
resp, err := http.PostForm(s.host, data)
|
||||||
|
if err != nil {
|
||||||
|
return "error", err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
all, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "error", err
|
||||||
|
}
|
||||||
|
return string(all), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign 签名方法
|
||||||
|
func (s *HuPiPayService) Sign(params map[string]string) string {
|
||||||
|
var data string
|
||||||
|
keys := make([]string, 0, 0)
|
||||||
|
params["appid"] = s.appId
|
||||||
|
for key := range params {
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
//拼接
|
||||||
|
for _, k := range keys {
|
||||||
|
data = fmt.Sprintf("%s%s=%s&", data, k, params[k])
|
||||||
|
}
|
||||||
|
data = strings.Trim(data, "&")
|
||||||
|
data = fmt.Sprintf("%s%s", data, s.appSecret)
|
||||||
|
m := md5.New()
|
||||||
|
m.Write([]byte(data))
|
||||||
|
sign := fmt.Sprintf("%x", m.Sum(nil))
|
||||||
|
return sign
|
||||||
|
}
|
||||||
@@ -135,7 +135,6 @@ func (s *Service) runTask(taskInfo TaskInfo, client *req.Client) {
|
|||||||
"fn_index": taskInfo.FnIndex,
|
"fn_index": taskInfo.FnIndex,
|
||||||
"session_hash": taskInfo.SessionHash,
|
"session_hash": taskInfo.SessionHash,
|
||||||
}
|
}
|
||||||
logger.Debug(utils.JsonEncode(body))
|
|
||||||
var result = make(chan CBReq)
|
var result = make(chan CBReq)
|
||||||
go func() {
|
go func() {
|
||||||
var res struct {
|
var res struct {
|
||||||
@@ -231,7 +230,6 @@ func (s *Service) runTask(taskInfo TaskInfo, client *req.Client) {
|
|||||||
|
|
||||||
cbReq.ImageData = progressRes.LivePreview
|
cbReq.ImageData = progressRes.LivePreview
|
||||||
cbReq.Progress = int(progressRes.Progress * 100)
|
cbReq.Progress = int(progressRes.Progress * 100)
|
||||||
logger.Debug(cbReq)
|
|
||||||
s.callback(cbReq)
|
s.callback(cbReq)
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
}
|
}
|
||||||
@@ -287,8 +285,13 @@ func (s *Service) callback(data CBReq) {
|
|||||||
if data.Progress < 100 && data.ImageData != "" {
|
if data.Progress < 100 && data.ImageData != "" {
|
||||||
jobVo.ImgURL = data.ImageData
|
jobVo.ImgURL = data.ImageData
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.Infof("绘图进度:%d", data.Progress)
|
||||||
|
|
||||||
// 扣减绘图次数
|
// 扣减绘图次数
|
||||||
s.db.Model(&model.User{}).Where("id = ?", jobVo.UserId).UpdateColumn("img_calls", gorm.Expr("img_calls - ?", 1))
|
if data.Progress == 100 {
|
||||||
|
s.db.Model(&model.User{}).Where("id = ? AND img_calls > 0", jobVo.UserId).UpdateColumn("img_calls", gorm.Expr("img_calls - ?", 1))
|
||||||
|
}
|
||||||
// 推送任务到前端
|
// 推送任务到前端
|
||||||
if client != nil {
|
if client != nil {
|
||||||
utils.ReplyChunkMessage(client, jobVo)
|
utils.ReplyChunkMessage(client, jobVo)
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ func NewSnowflake() *Snowflake {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Next 生成一个新的唯一ID
|
// Next 生成一个新的唯一ID
|
||||||
func (s *Snowflake) Next() (string, error) {
|
func (s *Snowflake) Next(raw bool) (string, error) {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
@@ -43,6 +43,9 @@ func (s *Snowflake) Next() (string, error) {
|
|||||||
|
|
||||||
s.lastTimestamp = timestamp
|
s.lastTimestamp = timestamp
|
||||||
id := (timestamp << 22) | (int64(s.workerID) << 10) | int64(s.sequence)
|
id := (timestamp << 22) | (int64(s.workerID) << 10) | int64(s.sequence)
|
||||||
|
if raw {
|
||||||
|
return fmt.Sprintf("%d", id), nil
|
||||||
|
}
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
return fmt.Sprintf("%d%02d%02d%d", now.Year(), now.Month(), now.Day(), id), nil
|
return fmt.Sprintf("%d%02d%02d%d", now.Year(), now.Month(), now.Day(), id), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,13 +6,14 @@ type MidJourneyJob struct {
|
|||||||
Id uint `gorm:"primarykey;column:id"`
|
Id uint `gorm:"primarykey;column:id"`
|
||||||
Type string
|
Type string
|
||||||
UserId int
|
UserId int
|
||||||
|
TaskId string
|
||||||
MessageId string
|
MessageId string
|
||||||
ReferenceId string
|
ReferenceId string
|
||||||
ImgURL string
|
ImgURL string
|
||||||
|
OrgURL string // 原图地址
|
||||||
Hash string // message hash
|
Hash string // message hash
|
||||||
Progress int
|
Progress int
|
||||||
Prompt string
|
Prompt string
|
||||||
Started bool
|
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,12 +6,13 @@ type MidJourneyJob struct {
|
|||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
UserId int `json:"user_id"`
|
UserId int `json:"user_id"`
|
||||||
|
TaskId string `json:"task_id"`
|
||||||
MessageId string `json:"message_id"`
|
MessageId string `json:"message_id"`
|
||||||
ReferenceId string `json:"reference_id"`
|
ReferenceId string `json:"reference_id"`
|
||||||
ImgURL string `json:"img_url"`
|
ImgURL string `json:"img_url"`
|
||||||
|
OrgURL string `json:"org_url"`
|
||||||
Hash string `json:"hash"`
|
Hash string `json:"hash"`
|
||||||
Progress int `json:"progress"`
|
Progress int `json:"progress"`
|
||||||
Prompt string `json:"prompt"`
|
Prompt string `json:"prompt"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
Started bool `json:"started"`
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
version=$1
|
version=$1
|
||||||
# build go api
|
arch=${2:-amd64}
|
||||||
|
|
||||||
|
# build go api program
|
||||||
cd ../api
|
cd ../api
|
||||||
make clean linux
|
make clean $arch
|
||||||
|
|
||||||
# build web app
|
# build web app
|
||||||
cd ../web
|
cd ../web
|
||||||
@@ -12,15 +14,15 @@ npm run build
|
|||||||
cd ../build
|
cd ../build
|
||||||
|
|
||||||
# remove docker image if exists
|
# remove docker image if exists
|
||||||
docker rmi -f registry.cn-shenzhen.aliyuncs.com/geekmaster/chatgpt-plus-api:$version
|
docker rmi -f registry.cn-shenzhen.aliyuncs.com/geekmaster/chatgpt-plus-api:$version-$arch
|
||||||
# build docker image for chatgpt-plus-go
|
# build docker image for chatgpt-plus-go
|
||||||
docker build -t registry.cn-shenzhen.aliyuncs.com/geekmaster/chatgpt-plus-api:$version -f dockerfile-api-go ../
|
docker build -t registry.cn-shenzhen.aliyuncs.com/geekmaster/chatgpt-plus-api:$version-$arch -f dockerfile-api-go ../
|
||||||
|
|
||||||
# build docker image for chatgpt-plus-vue
|
# build docker image for chatgpt-plus-vue
|
||||||
docker rmi -f registry.cn-shenzhen.aliyuncs.com/geekmaster/chatgpt-plus-web:$version
|
docker rmi -f registry.cn-shenzhen.aliyuncs.com/geekmaster/chatgpt-plus-web:$version-$arch
|
||||||
docker build --platform linux/amd64 -t registry.cn-shenzhen.aliyuncs.com/geekmaster/chatgpt-plus-web:$version -f dockerfile-vue ../
|
docker build --platform linux/amd64 -t registry.cn-shenzhen.aliyuncs.com/geekmaster/chatgpt-plus-web:$version-$arch -f dockerfile-vue ../
|
||||||
|
|
||||||
if [ "$2" = "push" ];then
|
if [ "$3" = "push" ];then
|
||||||
docker push registry.cn-shenzhen.aliyuncs.com/geekmaster/chatgpt-plus-api:$version
|
docker push registry.cn-shenzhen.aliyuncs.com/geekmaster/chatgpt-plus-api:$version-$arch
|
||||||
docker push registry.cn-shenzhen.aliyuncs.com/geekmaster/chatgpt-plus-web:$version
|
docker push registry.cn-shenzhen.aliyuncs.com/geekmaster/chatgpt-plus-web:$version-$arch
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ FROM alpine:3.18.2
|
|||||||
MAINTAINER yangjian<yangjian102621@163.com>
|
MAINTAINER yangjian<yangjian102621@163.com>
|
||||||
|
|
||||||
WORKDIR /var/www/app
|
WORKDIR /var/www/app
|
||||||
COPY ./api/bin/chatgpt-plus-amd64-linux /var/www/app
|
COPY ./api/bin/chatgpt-plus-linux /var/www/app
|
||||||
|
|
||||||
EXPOSE 5678
|
EXPOSE 5678
|
||||||
|
|
||||||
# 容器启动时执行的命令
|
# 容器启动时执行的命令
|
||||||
CMD ["./chatgpt-plus-amd64-linux"]
|
CMD ["./chatgpt-plus-linux"]
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ WeChatBot = false
|
|||||||
Active = "local" # 默认使用本地文件存储引擎
|
Active = "local" # 默认使用本地文件存储引擎
|
||||||
[OSS.Local]
|
[OSS.Local]
|
||||||
BasePath = "./static/upload" # 本地文件上传根路径
|
BasePath = "./static/upload" # 本地文件上传根路径
|
||||||
BaseURL = "http://localhost:5678/static/upload" # 本地上传文件根 URL 如果是线上,则直接设置为 /static/upload 即可
|
BaseURL = "/static/upload" # 本地上传文件根 URL 如果是线上,则直接设置为 /static/upload 即可
|
||||||
[OSS.Minio]
|
[OSS.Minio]
|
||||||
Endpoint = "" # 如 172.22.11.200:9000
|
Endpoint = "" # 如 172.22.11.200:9000
|
||||||
AccessKey = "" # 自己去 Minio 控制台去创建一个 Access Key
|
AccessKey = "" # 自己去 Minio 控制台去创建一个 Access Key
|
||||||
@@ -82,4 +82,12 @@ WeChatBot = false
|
|||||||
PublicKey = "certs/alipay/appPublicCert.crt" # 应用公钥证书
|
PublicKey = "certs/alipay/appPublicCert.crt" # 应用公钥证书
|
||||||
AlipayPublicKey = "certs/alipay/alipayPublicCert.crt" # 支付宝公钥证书
|
AlipayPublicKey = "certs/alipay/alipayPublicCert.crt" # 支付宝公钥证书
|
||||||
RootCert = "certs/alipay/alipayRootCert.crt" # 支付宝根证书
|
RootCert = "certs/alipay/alipayRootCert.crt" # 支付宝根证书
|
||||||
NotifyURL = "http://r9it.com:6004/api/payment/alipay/notify" # 支付异步回调地址
|
NotifyURL = "http://ai.r9it.com/api/payment/alipay/notify" # 支付异步回调地址
|
||||||
|
|
||||||
|
[HuPiPayConfig] # 虎皮椒支付配置
|
||||||
|
Enabled = false
|
||||||
|
Name = "wechat"
|
||||||
|
AppId = ""
|
||||||
|
AppSecret = ""
|
||||||
|
PayURL = "https://api.xunhupay.com/payment/do.html"
|
||||||
|
NotifyURL = "http://ai.r9it.com/api/payment/hupipay/notify"
|
||||||
@@ -1,115 +1,186 @@
|
|||||||
.task-list-box {
|
.task-list-box {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
.task-list-box .task-list-inner .el-tabs {
|
||||||
|
--el-tabs-header-height: 55px;
|
||||||
|
}
|
||||||
|
.task-list-box .task-list-inner .el-tabs__item {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
.task-list-box .task-list-inner .title-tabs .el-tabs__item.is-active {
|
||||||
|
color: #47fff1;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
.task-list-box .task-list-inner .title-tabs .el-tabs__active-bar {
|
||||||
|
background-color: #47fff1;
|
||||||
|
}
|
||||||
|
.task-list-box .task-list-inner .title-tabs .el-tabs__content {
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
.task-list-box .task-list-inner .el-textarea {
|
||||||
|
--el-input-focus-border-color: #47fff1;
|
||||||
|
}
|
||||||
|
.task-list-box .task-list-inner .el-textarea__inner {
|
||||||
|
background: transparent;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.task-list-box .task-list-inner .el-input__wrapper {
|
||||||
|
background: transparent;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
.task-list-box .task-list-inner .text {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: #6b778c;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
.task-list-box .task-list-inner .param-line.pt {
|
||||||
|
padding-top: 5px;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
}
|
||||||
|
.task-list-box .task-list-inner .form-item-inner {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.task-list-box .task-list-inner .form-item-inner .el-icon {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
.task-list-box .task-list-inner .el-form-item__label {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.task-list-box .task-list-inner .img-uploader .el-upload {
|
||||||
|
border: 1px dashed var(--el-border-color);
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
width: 300px;
|
||||||
|
transition: var(--el-transition-duration-fast);
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.task-list-box .task-list-inner .img-uploader .el-upload:hover {
|
||||||
|
border-color: var(--el-color-primary);
|
||||||
|
}
|
||||||
|
.task-list-box .task-list-inner .img-uploader .el-upload .el-icon.uploader-icon {
|
||||||
|
font-size: 28px;
|
||||||
|
color: #8c939d;
|
||||||
|
width: 100%;
|
||||||
|
height: 120px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.task-list-box .task-list-inner .submit-btn {
|
||||||
|
display: flex;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
.task-list-box .task-list-inner .submit-btn .el-button {
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
.task-list-box .task-list-inner .submit-btn .text-info {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: right;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-list-box .running-job-list .job-item {
|
.task-list-box .running-job-list .job-item {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
background-color: #555;
|
background-color: #555;
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-list-box .running-job-list .job-item .job-item-inner {
|
.task-list-box .running-job-list .job-item .job-item-inner {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-list-box .running-job-list .job-item .job-item-inner .progress {
|
.task-list-box .running-job-list .job-item .job-item-inner .progress {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-list-box .running-job-list .job-item .job-item-inner .progress span {
|
.task-list-box .running-job-list .job-item .job-item-inner .progress span {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-list-box .finish-job-list .job-item {
|
.task-list-box .finish-job-list .job-item {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
border: 1px solid #666;
|
||||||
|
padding: 6px;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 6px;
|
||||||
|
transition: all 0.3s ease; /* 添加过渡效果 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-list-box .finish-job-list .job-item .opt .opt-line {
|
.task-list-box .finish-job-list .job-item .opt .opt-line {
|
||||||
margin: 6px 0;
|
margin: 6px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-list-box .finish-job-list .job-item .opt .opt-line ul {
|
.task-list-box .finish-job-list .job-item .opt .opt-line ul {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: row;
|
flex-flow: row;
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-list-box .finish-job-list .job-item .opt .opt-line ul li {
|
.task-list-box .finish-job-list .job-item .opt .opt-line ul li {
|
||||||
margin-right: 10px;
|
margin-right: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-list-box .finish-job-list .job-item .opt .opt-line ul li a {
|
.task-list-box .finish-job-list .job-item .opt .opt-line ul li a {
|
||||||
padding: 3px 0;
|
padding: 3px 0;
|
||||||
width: 44px;
|
width: 40px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
display: block;
|
display: block;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background-color: #4e5058;
|
background-color: #4e5058;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-list-box .finish-job-list .job-item .opt .opt-line ul li a:hover {
|
.task-list-box .finish-job-list .job-item .opt .opt-line ul li a:hover {
|
||||||
background-color: #6d6f78;
|
background-color: #6d6f78;
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-list-box .finish-job-list .job-item .opt .opt-line ul .show-prompt {
|
.task-list-box .finish-job-list .job-item .opt .opt-line ul .show-prompt {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.task-list-box .finish-job-list .animate:hover {
|
||||||
|
box-shadow: 0 0 10px rgba(71,255,241,0.6); /* 添加阴影效果 */
|
||||||
|
transform: translateY(-10px); /* 向上移动10像素 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-list-box .el-image {
|
.task-list-box .el-image {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
max-height: 240px;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-list-box .el-image img {
|
.task-list-box .el-image img {
|
||||||
height: 240px;
|
height: 240px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-list-box .el-image .el-image-viewer__wrapper img {
|
.task-list-box .el-image .el-image-viewer__wrapper img {
|
||||||
width: auto;
|
width: auto;
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-list-box .el-image .image-slot {
|
.task-list-box .el-image .image-slot {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: column;
|
flex-flow: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
min-height: 200px;
|
min-height: 200px;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
height: 240px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-list-box .el-image .image-slot .iconfont {
|
.task-list-box .el-image .image-slot .iconfont {
|
||||||
font-size: 50px;
|
font-size: 50px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-list-box .el-image.upscale {
|
.task-list-box .el-image.upscale {
|
||||||
max-height: 312px;
|
max-height: 310px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-list-box .el-image.upscale img {
|
.task-list-box .el-image.upscale img {
|
||||||
height: 312px;
|
height: 310px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-list-box .el-image.upscale .el-image-viewer__wrapper img {
|
.task-list-box .el-image.upscale .el-image-viewer__wrapper img {
|
||||||
width: auto;
|
width: auto;
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: "iconfont"; /* Project id 4125778 */
|
font-family: "iconfont"; /* Project id 4125778 */
|
||||||
src: url('iconfont.woff2?t=1697164072791') format('woff2'),
|
src: url('iconfont.woff2?t=1702024026523') format('woff2'),
|
||||||
url('iconfont.woff?t=1697164072791') format('woff'),
|
url('iconfont.woff?t=1702024026523') format('woff'),
|
||||||
url('iconfont.ttf?t=1697164072791') format('truetype');
|
url('iconfont.ttf?t=1702024026523') format('truetype');
|
||||||
}
|
}
|
||||||
|
|
||||||
.iconfont {
|
.iconfont {
|
||||||
@@ -13,6 +13,10 @@
|
|||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon-alipay:before {
|
||||||
|
content: "\e634";
|
||||||
|
}
|
||||||
|
|
||||||
.icon-face:before {
|
.icon-face:before {
|
||||||
content: "\e64b";
|
content: "\e64b";
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -5,6 +5,13 @@
|
|||||||
"css_prefix_text": "icon-",
|
"css_prefix_text": "icon-",
|
||||||
"description": "",
|
"description": "",
|
||||||
"glyphs": [
|
"glyphs": [
|
||||||
|
{
|
||||||
|
"icon_id": "1486848",
|
||||||
|
"name": "支付宝支付",
|
||||||
|
"font_class": "alipay",
|
||||||
|
"unicode": "e634",
|
||||||
|
"unicode_decimal": 58932
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"icon_id": "845789",
|
"icon_id": "845789",
|
||||||
"name": "笑脸",
|
"name": "笑脸",
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -105,12 +105,12 @@
|
|||||||
:value="item.id"
|
:value="item.id"
|
||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
<!-- <el-button type="primary" @click="newChat">-->
|
<el-button type="primary" @click="newChat">
|
||||||
<!-- <el-icon>-->
|
<el-icon>
|
||||||
<!-- <Plus/>-->
|
<Plus/>
|
||||||
<!-- </el-icon>-->
|
</el-icon>
|
||||||
<!-- 新建会话-->
|
新建对话
|
||||||
<!-- </el-button>-->
|
</el-button>
|
||||||
|
|
||||||
<el-button type="success" @click="exportChat" plain>
|
<el-button type="success" @click="exportChat" plain>
|
||||||
<i class="iconfont icon-export"></i>
|
<i class="iconfont icon-export"></i>
|
||||||
@@ -237,7 +237,7 @@ import {
|
|||||||
Check,
|
Check,
|
||||||
Close,
|
Close,
|
||||||
Delete,
|
Delete,
|
||||||
Edit,
|
Edit, Plus,
|
||||||
Promotion,
|
Promotion,
|
||||||
RefreshRight,
|
RefreshRight,
|
||||||
Search,
|
Search,
|
||||||
@@ -245,7 +245,7 @@ import {
|
|||||||
VideoPause
|
VideoPause
|
||||||
} from '@element-plus/icons-vue'
|
} from '@element-plus/icons-vue'
|
||||||
import 'highlight.js/styles/a11y-dark.css'
|
import 'highlight.js/styles/a11y-dark.css'
|
||||||
import {dateFormat, isMobile, randString, removeArrayItem, renderInputText, UUID} from "@/utils/libs";
|
import {dateFormat, isMobile, randString, removeArrayItem, UUID} from "@/utils/libs";
|
||||||
import {ElMessage, ElMessageBox} from "element-plus";
|
import {ElMessage, ElMessageBox} from "element-plus";
|
||||||
import hl from "highlight.js";
|
import hl from "highlight.js";
|
||||||
import {getSessionId, getUserToken, removeUserToken} from "@/store/session";
|
import {getSessionId, getUserToken, removeUserToken} from "@/store/session";
|
||||||
@@ -624,6 +624,7 @@ const connect = function (chat_id, role_id) {
|
|||||||
// 将聊天框的滚动条滑动到最底部
|
// 将聊天框的滚动条滑动到最底部
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
document.getElementById('chat-box').scrollTo(0, document.getElementById('chat-box').scrollHeight)
|
document.getElementById('chat-box').scrollTo(0, document.getElementById('chat-box').scrollHeight)
|
||||||
|
localStorage.setItem("chat_id", chat_id)
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -689,13 +690,12 @@ const sendMessage = function () {
|
|||||||
if (prompt.value.trim().length === 0 || canSend.value === false) {
|
if (prompt.value.trim().length === 0 || canSend.value === false) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 追加消息
|
// 追加消息
|
||||||
chatData.value.push({
|
chatData.value.push({
|
||||||
type: "prompt",
|
type: "prompt",
|
||||||
id: randString(32),
|
id: randString(32),
|
||||||
icon: loginUser.value.avatar,
|
icon: loginUser.value.avatar,
|
||||||
content: renderInputText(prompt.value),
|
content: md.render(prompt.value),
|
||||||
created_at: new Date().getTime(),
|
created_at: new Date().getTime(),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -760,10 +760,7 @@ const loadChatHistory = function (chatId) {
|
|||||||
}
|
}
|
||||||
showHello.value = false
|
showHello.value = false
|
||||||
for (let i = 0; i < data.length; i++) {
|
for (let i = 0; i < data.length; i++) {
|
||||||
if (data[i].type === "prompt") {
|
if (data[i].type === "mj") {
|
||||||
chatData.value.push(data[i]);
|
|
||||||
continue;
|
|
||||||
} else if (data[i].type === "mj") {
|
|
||||||
data[i].content = JSON.parse(data[i].content)
|
data[i].content = JSON.parse(data[i].content)
|
||||||
data[i].content.html = md.render(data[i].content?.content)
|
data[i].content.html = md.render(data[i].content?.content)
|
||||||
chatData.value.push(data[i]);
|
chatData.value.push(data[i]);
|
||||||
@@ -801,7 +798,7 @@ const reGenerate = function () {
|
|||||||
type: "prompt",
|
type: "prompt",
|
||||||
id: randString(32),
|
id: randString(32),
|
||||||
icon: loginUser.value.avatar,
|
icon: loginUser.value.avatar,
|
||||||
content: renderInputText(text)
|
content: md.render(text)
|
||||||
});
|
});
|
||||||
socket.value.send(text);
|
socket.value.send(text);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -233,29 +233,49 @@
|
|||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
|
|
||||||
<div class="param-line pt">
|
<div v-loading="loading" element-loading-background="rgba(122, 122, 122, 0.8)">
|
||||||
<div class="flex-row justify-between items-center">
|
<div class="param-line pt">
|
||||||
<div class="flex-row justify-start items-center">
|
<div class="flex-row justify-between items-center">
|
||||||
<span>提示词:</span>
|
<div class="flex-row justify-start items-center">
|
||||||
<el-tooltip effect="light" content="输入你想要的内容,用逗号分割" placement="right">
|
<span>提示词:</span>
|
||||||
<el-icon>
|
<el-tooltip effect="light" content="输入你想要的内容,用逗号分割" placement="right">
|
||||||
<InfoFilled/>
|
<el-icon>
|
||||||
</el-icon>
|
<InfoFilled/>
|
||||||
</el-tooltip>
|
</el-icon>
|
||||||
</div>
|
</el-tooltip>
|
||||||
<el-button type="success">
|
</div>
|
||||||
<el-icon style="margin-right: 6px;font-size: 18px;">
|
<div>
|
||||||
<Refresh/>
|
<el-button type="primary" @click="translatePrompt">
|
||||||
</el-icon>
|
<el-icon style="margin-right: 6px;font-size: 18px;">
|
||||||
翻译
|
<Refresh/>
|
||||||
</el-button>
|
</el-icon>
|
||||||
</div>
|
翻译
|
||||||
</div>
|
</el-button>
|
||||||
|
|
||||||
<div class="param-line pt">
|
<el-tooltip
|
||||||
<el-input v-model="params.prompt" :autosize="{ minRows: 4, maxRows: 6 }" type="textarea"
|
class="box-item"
|
||||||
ref="promptRef"
|
effect="light"
|
||||||
placeholder="这里输入你的英文咒语,例如:A chinese girl walking in the middle of a cobblestone street"/>
|
raw-content
|
||||||
|
content="使用 AI 翻译并重写提示词,<br/>增加更多细节,风格等描述"
|
||||||
|
placement="top-end"
|
||||||
|
>
|
||||||
|
<el-button type="success" @click="rewritePrompt">
|
||||||
|
<el-icon style="margin-right: 6px;font-size: 18px;">
|
||||||
|
<Refresh/>
|
||||||
|
</el-icon>
|
||||||
|
翻译并重写
|
||||||
|
</el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="param-line pt">
|
||||||
|
<el-input v-model="params.prompt" :autosize="{ minRows: 4, maxRows: 6 }" type="textarea"
|
||||||
|
ref="promptRef"
|
||||||
|
placeholder="这里输入你的英文咒语,例如:A chinese girl walking in the middle of a cobblestone street"/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="param-line pt">
|
<div class="param-line pt">
|
||||||
@@ -268,12 +288,12 @@
|
|||||||
</el-icon>
|
</el-icon>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<el-button type="success">
|
<!-- <el-button type="success">-->
|
||||||
<el-icon style="margin-right: 6px;font-size: 18px;">
|
<!-- <el-icon style="margin-right: 6px;font-size: 18px;">-->
|
||||||
<Refresh/>
|
<!-- <Refresh/>-->
|
||||||
</el-icon>
|
<!-- </el-icon>-->
|
||||||
翻译
|
<!-- 翻译-->
|
||||||
</el-button>
|
<!-- </el-button>-->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -286,7 +306,7 @@
|
|||||||
<div class="submit-btn">
|
<div class="submit-btn">
|
||||||
<el-button color="#47fff1" :dark="false" @click="generate" round>立即生成</el-button>
|
<el-button color="#47fff1" :dark="false" @click="generate" round>立即生成</el-button>
|
||||||
<div class="text-info">
|
<div class="text-info">
|
||||||
<el-tag type="success">可用额度:{{ imgCalls }}</el-tag>
|
<el-tag type="success">绘图可用额度:{{ imgCalls }}</el-tag>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-form>
|
</el-form>
|
||||||
@@ -336,7 +356,7 @@
|
|||||||
|
|
||||||
<h2>创作记录</h2>
|
<h2>创作记录</h2>
|
||||||
<div class="finish-job-list">
|
<div class="finish-job-list">
|
||||||
<ItemList :items="finishedJobs" v-if="finishedJobs.length > 0" width="240" :gap="16">
|
<ItemList :items="finishedJobs" v-if="finishedJobs.length > 0" :width="240" :gap="16">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<div class="job-item">
|
<div class="job-item">
|
||||||
<el-image
|
<el-image
|
||||||
@@ -430,8 +450,7 @@ import ItemList from "@/components/ItemList.vue";
|
|||||||
import Clipboard from "clipboard";
|
import Clipboard from "clipboard";
|
||||||
import {checkSession} from "@/action/session";
|
import {checkSession} from "@/action/session";
|
||||||
import {useRouter} from "vue-router";
|
import {useRouter} from "vue-router";
|
||||||
import {getSessionId, getUserToken} from "@/store/session";
|
import {getSessionId} from "@/store/session";
|
||||||
import {removeArrayItem} from "@/utils/libs";
|
|
||||||
|
|
||||||
const listBoxHeight = ref(window.innerHeight - 40)
|
const listBoxHeight = ref(window.innerHeight - 40)
|
||||||
const mjBoxHeight = ref(window.innerHeight - 150)
|
const mjBoxHeight = ref(window.innerHeight - 150)
|
||||||
@@ -500,93 +519,35 @@ const router = useRouter()
|
|||||||
|
|
||||||
const socket = ref(null)
|
const socket = ref(null)
|
||||||
const imgCalls = ref(0)
|
const imgCalls = ref(0)
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
const connect = () => {
|
const rewritePrompt = () => {
|
||||||
let host = process.env.VUE_APP_WS_HOST
|
loading.value = true
|
||||||
if (host === '') {
|
httpPost("/api/prompt/rewrite", {"prompt": params.value.prompt}).then(res => {
|
||||||
if (location.protocol === 'https:') {
|
params.value.prompt = res.data
|
||||||
host = 'wss://' + location.host;
|
loading.value = false
|
||||||
} else {
|
}).catch(e => {
|
||||||
host = 'ws://' + location.host;
|
ElMessage.error("翻译失败:" + e.message)
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
const _socket = new WebSocket(host + `/api/mj/client?session_id=${getSessionId()}&token=${getUserToken()}`);
|
|
||||||
_socket.addEventListener('open', () => {
|
|
||||||
socket.value = _socket;
|
|
||||||
});
|
|
||||||
|
|
||||||
_socket.addEventListener('message', event => {
|
const translatePrompt = () => {
|
||||||
if (event.data instanceof Blob) {
|
loading.value = true
|
||||||
const reader = new FileReader();
|
httpPost("/api/prompt/translate", {"prompt": params.value.prompt}).then(res => {
|
||||||
reader.readAsText(event.data, "UTF-8");
|
params.value.prompt = res.data
|
||||||
reader.onload = () => {
|
loading.value = false
|
||||||
const data = JSON.parse(String(reader.result));
|
}).catch(e => {
|
||||||
let isNew = true
|
ElMessage.error("翻译失败:" + e.message)
|
||||||
if (data.progress === 100) {
|
})
|
||||||
for (let i = 0; i < finishedJobs.value.length; i++) {
|
|
||||||
if (finishedJobs.value[i].id === data.id) {
|
|
||||||
isNew = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (let i = 0; i < runningJobs.value.length; i++) {
|
|
||||||
if (runningJobs.value[i].id === data.id) {
|
|
||||||
runningJobs.value.splice(i, 1)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isNew) {
|
|
||||||
finishedJobs.value.unshift(data)
|
|
||||||
}
|
|
||||||
} else if (data.progress === -1) { // 任务执行失败
|
|
||||||
ElNotification({
|
|
||||||
title: '任务执行失败',
|
|
||||||
message: "提示词:" + data['prompt'],
|
|
||||||
type: 'error',
|
|
||||||
})
|
|
||||||
runningJobs.value = removeArrayItem(runningJobs.value, data, (v1, v2) => v1.id === v2.id)
|
|
||||||
|
|
||||||
} else {
|
|
||||||
for (let i = 0; i < runningJobs.value.length; i++) {
|
|
||||||
if (runningJobs.value[i].id === data.id) {
|
|
||||||
isNew = false
|
|
||||||
runningJobs.value[i] = data
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isNew) {
|
|
||||||
runningJobs.value.push(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
_socket.addEventListener('close', () => {
|
|
||||||
ElMessage.error("Websocket 已经断开,正在重新连接服务器")
|
|
||||||
connect()
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
checkSession().then(user => {
|
checkSession().then(user => {
|
||||||
imgCalls.value = user['img_calls']
|
imgCalls.value = user['img_calls']
|
||||||
// 获取运行中的任务
|
|
||||||
httpGet(`/api/mj/jobs?status=0&user_id=${user['id']}`).then(res => {
|
|
||||||
runningJobs.value = res.data
|
|
||||||
}).catch(e => {
|
|
||||||
ElMessage.error("获取任务失败:" + e.message)
|
|
||||||
})
|
|
||||||
|
|
||||||
// 获取已完成的任务
|
fetchRunningJobs(user.id)
|
||||||
httpGet(`/api/mj/jobs?status=1&user_id=${user['id']}`).then(res => {
|
fetchFinishJobs(user.id)
|
||||||
finishedJobs.value = res.data
|
|
||||||
}).catch(e => {
|
|
||||||
ElMessage.error("获取任务失败:" + e.message)
|
|
||||||
})
|
|
||||||
|
|
||||||
// 连接 socket
|
|
||||||
connect();
|
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
router.push('/login')
|
router.push('/login')
|
||||||
});
|
});
|
||||||
@@ -601,6 +562,41 @@ onMounted(() => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 获取运行中的任务
|
||||||
|
const fetchRunningJobs = (userId) => {
|
||||||
|
httpGet(`/api/mj/jobs?status=0&user_id=${userId}`).then(res => {
|
||||||
|
const jobs = res.data
|
||||||
|
const _jobs = []
|
||||||
|
for (let i = 0; i < jobs.length; i++) {
|
||||||
|
if (jobs[i].progress === -1) {
|
||||||
|
ElNotification({
|
||||||
|
title: '任务执行失败',
|
||||||
|
message: "任务ID:" + jobs[i]['task_id'],
|
||||||
|
type: 'error',
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
_jobs.push(jobs[i])
|
||||||
|
}
|
||||||
|
runningJobs.value = _jobs
|
||||||
|
|
||||||
|
setTimeout(() => fetchRunningJobs(userId), 10000)
|
||||||
|
|
||||||
|
}).catch(e => {
|
||||||
|
ElMessage.error("获取任务失败:" + e.message)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchFinishJobs = (userId) => {
|
||||||
|
// 获取已完成的任务
|
||||||
|
httpGet(`/api/mj/jobs?status=1&user_id=${userId}`).then(res => {
|
||||||
|
finishedJobs.value = res.data
|
||||||
|
setTimeout(() => fetchFinishJobs(userId), 10000)
|
||||||
|
}).catch(e => {
|
||||||
|
ElMessage.error("获取任务失败:" + e.message)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 切换图片比例
|
// 切换图片比例
|
||||||
const changeRate = (item) => {
|
const changeRate = (item) => {
|
||||||
params.value.rate = item.value
|
params.value.rate = item.value
|
||||||
@@ -663,7 +659,6 @@ const variation = (index, item) => {
|
|||||||
const send = (url, index, item) => {
|
const send = (url, index, item) => {
|
||||||
httpPost(url, {
|
httpPost(url, {
|
||||||
index: index,
|
index: index,
|
||||||
src: "img",
|
|
||||||
message_id: item.message_id,
|
message_id: item.message_id,
|
||||||
message_hash: item.hash,
|
message_hash: item.hash,
|
||||||
session_id: getSessionId(),
|
session_id: getSessionId(),
|
||||||
|
|||||||
@@ -500,21 +500,21 @@ window.onresize = () => {
|
|||||||
}
|
}
|
||||||
const samplers = ["Euler a", "Euler", "DPM2 a Karras", "DPM++ 2S a Karras", "DPM++ 2M Karras", "DPM++ SDE Karras", "DPM2", "DPM2 a", "DPM++ 2S a", "DPM++ 2M", "DPM++ SDE", "DPM fast", "DPM adaptive",
|
const samplers = ["Euler a", "Euler", "DPM2 a Karras", "DPM++ 2S a Karras", "DPM++ 2M Karras", "DPM++ SDE Karras", "DPM2", "DPM2 a", "DPM++ 2S a", "DPM++ 2M", "DPM++ SDE", "DPM fast", "DPM adaptive",
|
||||||
"LMS Karras", "DPM2 Karras", "DDIM", "PLMS", "UniPC", "LMS", "Heun",]
|
"LMS Karras", "DPM2 Karras", "DDIM", "PLMS", "UniPC", "LMS", "Heun",]
|
||||||
const scaleAlg = ["ESRGAN_4x", "R-ESRGAN 4x+", "SwinIR_4x", "LDSR"]
|
const scaleAlg = ["Latent", "ESRGAN_4x", "R-ESRGAN 4x+", "SwinIR_4x", "LDSR"]
|
||||||
const params = ref({
|
const params = ref({
|
||||||
width: 1024,
|
width: 1024,
|
||||||
height: 1024,
|
height: 1024,
|
||||||
sampler: samplers[0],
|
sampler: samplers[0],
|
||||||
seed: -1,
|
seed: -1,
|
||||||
steps: 20,
|
steps: 30,
|
||||||
cfg_scale: 7,
|
cfg_scale: 7,
|
||||||
face_fix: false,
|
face_fix: false,
|
||||||
hd_fix: false,
|
hd_fix: false,
|
||||||
hd_redraw_rate: 0.3,
|
hd_redraw_rate: 0.7,
|
||||||
hd_scale: 2,
|
hd_scale: 2,
|
||||||
hd_scale_alg: scaleAlg[0],
|
hd_scale_alg: scaleAlg[0],
|
||||||
hd_steps: 0,
|
hd_steps: 10,
|
||||||
prompt: "A beautiful Chinese girl riding on a tiger",
|
prompt: "",
|
||||||
negative_prompt: "nsfw, paintings, cartoon, anime, sketches, low quality,easynegative,ng_deepnegative _v1 75t,(worst quality:2),(low quality:2),(normalquality:2),lowres,bad anatomy,bad hands,normal quality,((monochrome)),((grayscale)),((watermark))",
|
negative_prompt: "nsfw, paintings, cartoon, anime, sketches, low quality,easynegative,ng_deepnegative _v1 75t,(worst quality:2),(low quality:2),(normalquality:2),lowres,bad anatomy,bad hands,normal quality,((monochrome)),((grayscale)),((watermark))",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ const login = function () {
|
|||||||
|
|
||||||
httpPost('/api/user/login', {username: username.value.trim(), password: password.value.trim()}).then((res) => {
|
httpPost('/api/user/login', {username: username.value.trim(), password: password.value.trim()}).then((res) => {
|
||||||
setUserToken(res.data)
|
setUserToken(res.data)
|
||||||
if (prevRoute.path === '') {
|
if (prevRoute.path === '' || prevRoute.path === '/register') {
|
||||||
if (isMobile()) {
|
if (isMobile()) {
|
||||||
router.push('/mobile')
|
router.push('/mobile')
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
|
|
||||||
<ItemList :items="list" v-if="list.length > 0" :gap="30" :width="240">
|
<ItemList :items="list" v-if="list.length > 0" :gap="30" :width="240">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<div class="product-item" :style="{width: scope.width+'px'}" @click="orderPay(scope.item)">
|
<div class="product-item" :style="{width: scope.width+'px'}">
|
||||||
<div class="image-container">
|
<div class="image-container">
|
||||||
<el-image :src="vipImg" fit="cover"/>
|
<el-image :src="vipImg" fit="cover"/>
|
||||||
</div>
|
</div>
|
||||||
@@ -62,6 +62,16 @@
|
|||||||
<span class="expire" v-if="scope.item.days > 0">{{ scope.item.days }}天</span>
|
<span class="expire" v-if="scope.item.days > 0">{{ scope.item.days }}天</span>
|
||||||
<span class="expire" v-else>当月有效</span>
|
<span class="expire" v-else>当月有效</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="pay-way">
|
||||||
|
<el-button type="primary" @click="alipay(scope.item)" size="small" v-if="payWays['alipay']">
|
||||||
|
<i class="iconfont icon-alipay"></i> 支付宝
|
||||||
|
</el-button>
|
||||||
|
<el-button type="success" @click="huPiPay(scope.item)" size="small" v-if="payWays['hupi']">
|
||||||
|
<span v-if="payWays['hupi']['name'] === 'wechat'"><i class="iconfont icon-wechat-pay"></i> 微信</span>
|
||||||
|
<span v-else><i class="iconfont icon-alipay"></i> 支付宝</span>
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -113,7 +123,7 @@
|
|||||||
title="充值订单支付">
|
title="充值订单支付">
|
||||||
<div class="pay-container">
|
<div class="pay-container">
|
||||||
<div class="count-down">
|
<div class="count-down">
|
||||||
<count-down :second="orderTimeout" @timeout="orderPay" ref="countDown"/>
|
<count-down :second="orderTimeout" @timeout="refreshPayCode" ref="countDownRef"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pay-qrcode" v-loading="loading">
|
<div class="pay-qrcode" v-loading="loading">
|
||||||
@@ -130,9 +140,10 @@
|
|||||||
<el-icon>
|
<el-icon>
|
||||||
<InfoFilled/>
|
<InfoFilled/>
|
||||||
</el-icon>
|
</el-icon>
|
||||||
<span class="text">请打开手机支付宝扫码支付</span>
|
<span class="text">请打开手机{{ payName }}扫码支付</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -152,6 +163,7 @@ import RewardVerify from "@/components/RewardVerify.vue";
|
|||||||
import {useRouter} from "vue-router";
|
import {useRouter} from "vue-router";
|
||||||
import {removeUserToken} from "@/store/session";
|
import {removeUserToken} from "@/store/session";
|
||||||
import UserOrder from "@/components/UserOrder.vue";
|
import UserOrder from "@/components/UserOrder.vue";
|
||||||
|
import CountDown from "@/components/CountDown.vue";
|
||||||
|
|
||||||
const listBoxHeight = window.innerHeight - 97
|
const listBoxHeight = window.innerHeight - 97
|
||||||
const list = ref([])
|
const list = ref([])
|
||||||
@@ -171,11 +183,15 @@ const isLogin = ref(false)
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const curPayProduct = ref(null)
|
const curPayProduct = ref(null)
|
||||||
const activeOrderNo = ref("")
|
const activeOrderNo = ref("")
|
||||||
const countDown = ref(null)
|
const countDownRef = ref(null)
|
||||||
const orderTimeout = ref(1800)
|
const orderTimeout = ref(1800)
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
const orderPayInfoText = ref("")
|
const orderPayInfoText = ref("")
|
||||||
|
|
||||||
|
const payWays = ref({})
|
||||||
|
const payName = ref("支付宝")
|
||||||
|
const curPay = ref("alipay") // 当前支付方式
|
||||||
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
checkSession().then(_user => {
|
checkSession().then(_user => {
|
||||||
@@ -200,9 +216,48 @@ onMounted(() => {
|
|||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
ElMessage.error("获取系统配置失败:" + e.message)
|
ElMessage.error("获取系统配置失败:" + e.message)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
httpGet("/api/payment/payWays").then(res => {
|
||||||
|
payWays.value = res.data
|
||||||
|
}).catch(e => {
|
||||||
|
ElMessage.error("获取支付方式失败:" + e.message)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const orderPay = (row) => {
|
// refresh payment qrcode
|
||||||
|
const refreshPayCode = () => {
|
||||||
|
if (curPay.value === 'alipay') {
|
||||||
|
alipay()
|
||||||
|
} else if (curPay.value === 'hupi') {
|
||||||
|
huPiPay()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const genPayQrcode = () => {
|
||||||
|
loading.value = true
|
||||||
|
text.value = ""
|
||||||
|
httpPost("/api/payment/qrcode", {
|
||||||
|
pay_way: curPay.value,
|
||||||
|
product_id: curPayProduct.value.id,
|
||||||
|
user_id: user.value.id
|
||||||
|
}).then(res => {
|
||||||
|
showPayDialog.value = true
|
||||||
|
qrcode.value = res.data['image']
|
||||||
|
activeOrderNo.value = res.data['order_no']
|
||||||
|
queryOrder(activeOrderNo.value)
|
||||||
|
loading.value = false
|
||||||
|
// 重置计数器
|
||||||
|
if (countDownRef.value) {
|
||||||
|
countDownRef.value.resetTimer()
|
||||||
|
}
|
||||||
|
}).catch(e => {
|
||||||
|
ElMessage.error("生成支付订单失败:" + e.message)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const alipay = (row) => {
|
||||||
|
payName.value = "支付宝"
|
||||||
|
curPay.value = "alipay"
|
||||||
if (!user.value.id) {
|
if (!user.value.id) {
|
||||||
showLoginDialog.value = true
|
showLoginDialog.value = true
|
||||||
return
|
return
|
||||||
@@ -210,21 +265,22 @@ const orderPay = (row) => {
|
|||||||
if (row) {
|
if (row) {
|
||||||
curPayProduct.value = row
|
curPayProduct.value = row
|
||||||
}
|
}
|
||||||
loading.value = true
|
genPayQrcode()
|
||||||
text.value = ""
|
}
|
||||||
httpPost("/api/payment/alipay/qrcode", {product_id: curPayProduct.value.id, user_id: user.value.id}).then(res => {
|
|
||||||
showPayDialog.value = true
|
// 虎皮椒支付
|
||||||
qrcode.value = res.data['image']
|
const huPiPay = (row) => {
|
||||||
activeOrderNo.value = res.data['order_no']
|
payName.value = payWays.value["hupi"]["name"] === "wechat" ? '微信' : '支付宝'
|
||||||
queryOrder(activeOrderNo.value)
|
curPay.value = "hupi"
|
||||||
loading.value = false
|
if (!user.value.id) {
|
||||||
// 重置计数器
|
showLoginDialog.value = true
|
||||||
if (countDown.value) {
|
return
|
||||||
countDown.value.resetTimer()
|
}
|
||||||
}
|
if (row) {
|
||||||
}).catch(e => {
|
curPayProduct.value = row
|
||||||
ElMessage.error("生成支付订单失败:" + e.message)
|
}
|
||||||
})
|
genPayQrcode()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const queryOrder = (orderNo) => {
|
const queryOrder = (orderNo) => {
|
||||||
@@ -416,6 +472,16 @@ const logout = function () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.pay-way {
|
||||||
|
padding 10px 0
|
||||||
|
display flex
|
||||||
|
justify-content: space-between
|
||||||
|
|
||||||
|
.iconfont {
|
||||||
|
margin-right 5px
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
|
|||||||
@@ -127,14 +127,6 @@ import {isMobile} from "@/utils/libs";
|
|||||||
import {setUserToken} from "@/store/session";
|
import {setUserToken} from "@/store/session";
|
||||||
import {checkSession} from "@/action/session";
|
import {checkSession} from "@/action/session";
|
||||||
|
|
||||||
checkSession().then(() => {
|
|
||||||
if (isMobile()) {
|
|
||||||
router.push('/mobile')
|
|
||||||
} else {
|
|
||||||
router.push('/chat')
|
|
||||||
}
|
|
||||||
}).catch(() => {
|
|
||||||
})
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const title = ref('ChatGPT-PLUS 用户注册');
|
const title = ref('ChatGPT-PLUS 用户注册');
|
||||||
const formData = ref({
|
const formData = ref({
|
||||||
|
|||||||
@@ -249,6 +249,13 @@
|
|||||||
<el-input v-model.number="chat['xun_fei']['max_tokens']" placeholder="回复的最大字数,最大4096"/>
|
<el-input v-model.number="chat['xun_fei']['max_tokens']" placeholder="回复的最大字数,最大4096"/>
|
||||||
</el-form-item>
|
</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-input v-model.number="chat['dall_img_num']" placeholder="调用 DALL E3 API 传入的出图数量"/>
|
||||||
|
</el-form-item>
|
||||||
<el-form-item style="text-align: right">
|
<el-form-item style="text-align: right">
|
||||||
<el-button type="primary" @click="save('chat')">保存</el-button>
|
<el-button type="primary" @click="save('chat')">保存</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -274,6 +281,7 @@ const chat = ref({
|
|||||||
context_deep: 0,
|
context_deep: 0,
|
||||||
enable_context: true,
|
enable_context: true,
|
||||||
enable_history: true,
|
enable_history: true,
|
||||||
|
dall_api_url: "",
|
||||||
})
|
})
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
const systemFormRef = ref(null)
|
const systemFormRef = ref(null)
|
||||||
@@ -290,25 +298,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
// 加载聊天配置
|
// 加载聊天配置
|
||||||
httpGet('/api/admin/config/get?key=chat').then(res => {
|
httpGet('/api/admin/config/get?key=chat').then(res => {
|
||||||
// chat.value = res.data
|
chat.value = res.data
|
||||||
if (res.data.open_ai) {
|
|
||||||
chat.value.open_ai = res.data.open_ai
|
|
||||||
}
|
|
||||||
if (res.data.azure) {
|
|
||||||
chat.value.azure = res.data.azure
|
|
||||||
}
|
|
||||||
if (res.data.chat_gml) {
|
|
||||||
chat.value.chat_gml = res.data.chat_gml
|
|
||||||
}
|
|
||||||
if (res.data.baidu) {
|
|
||||||
chat.value.baidu = res.data.baidu
|
|
||||||
}
|
|
||||||
if (res.data.xun_fei) {
|
|
||||||
chat.value.xun_fei = res.data.xun_fei
|
|
||||||
}
|
|
||||||
chat.value.context_deep = res.data.context_deep
|
|
||||||
chat.value.enable_context = res.data.enable_context
|
|
||||||
chat.value.enable_history = res.data.enable_history
|
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
ElMessage.error("加载聊天配置失败: " + e.message)
|
ElMessage.error("加载聊天配置失败: " + e.message)
|
||||||
|
|||||||
Reference in New Issue
Block a user