mirror of
https://github.com/yangjian102621/geekai.git
synced 2025-11-11 03:33:48 +08:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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
|
||||
* 功能新增:新增邀请注册功能
|
||||
|
||||
19
README.md
19
README.md
@@ -13,6 +13,11 @@ ChatGLM,讯飞星火,文心一言等多个平台的大语言模型。集成了
|
||||
* 集成插件 API 功能,可结合大语言模型的 function 功能开发各种强大的插件,已内置实现了微博热搜,今日头条,今日早报和 AI
|
||||
绘画函数插件。
|
||||
|
||||
## 关于部署镜像申明
|
||||
|
||||
由于目前部署人数越来越多,本人的阿里云镜像仓库流量不够支撑大家使用了。所以从 v3.2.0 版本开始,一键部署脚本和部署镜像将只提供给 **[付费技术交流群]** 内用户使用。
|
||||
代码依旧是全部开源的,大家可自行编译打包镜像。
|
||||
|
||||
## 功能截图
|
||||
|
||||
### PC 端聊天界面
|
||||
@@ -90,22 +95,16 @@ ChatGLM,讯飞星火,文心一言等多个平台的大语言模型。集成了
|
||||
|
||||
## 项目文档
|
||||
|
||||
chatgpt-plus v3.2.0 一键部署脚本来了,真的只需运行一条命令,就可以完成部署:
|
||||
```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/)。
|
||||
|
||||
详细部署文档请参考 [ChatGPT-Plus 文档](https://ai.r9it.com/docs/)。
|
||||
加微信进入微信讨论群可获取 **一键部署脚本(添加好友时请注明来自Github!!!)。**
|
||||
|
||||

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

|
||||
|
||||
#### 特此声明:由于个人时间有限,不接受在微信或者微信群给开发者提 Bug,有问题或者优化建议请提交 Issue 和 PR。非常感谢您的配合!
|
||||
|
||||
### Commit 类型
|
||||
|
||||
19
api/Makefile
19
api/Makefile
@@ -1,19 +1,14 @@
|
||||
SHELL=/usr/bin/env bash
|
||||
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:
|
||||
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o bin/$(NAME)-amd64.exe main.go
|
||||
.PHONY: window
|
||||
|
||||
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
|
||||
arm64:
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 GOARM=7 go build -o bin/$(NAME)-linux main.go
|
||||
.PHONY: arm64
|
||||
|
||||
clean:
|
||||
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/invite/hits" ||
|
||||
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/captcha/") ||
|
||||
strings.HasPrefix(c.Request.URL.Path, "/api/payment/") ||
|
||||
@@ -229,35 +230,37 @@ func parameterHandlerMiddleware() gin.HandlerFunc {
|
||||
params[key][i] = strings.TrimSpace(value)
|
||||
}
|
||||
}
|
||||
// 更新参数
|
||||
// update get parameters
|
||||
c.Request.URL.RawQuery = params.Encode()
|
||||
|
||||
// skip file upload requests
|
||||
contentType := c.Request.Header.Get("Content-Type")
|
||||
if strings.Contains(contentType, "multipart/form-data") {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
// POST JSON 参数处理
|
||||
bodyBytes, err := io.ReadAll(c.Request.Body)
|
||||
if err != nil {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
if strings.Contains(contentType, "application/json") {
|
||||
// process POST JSON request body
|
||||
bodyBytes, err := io.ReadAll(c.Request.Body)
|
||||
if err != nil {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
// 还原请求体
|
||||
c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
|
||||
// 将请求体解析为 JSON
|
||||
var jsonData map[string]interface{}
|
||||
if err := c.ShouldBindJSON(&jsonData); err != nil {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
// 还原请求体
|
||||
c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
|
||||
// 将请求体解析为 JSON
|
||||
var jsonData map[string]interface{}
|
||||
if err := c.ShouldBindJSON(&jsonData); err != nil {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
// 对 JSON 数据中的字符串值去除两端空格
|
||||
trimJSONStrings(jsonData)
|
||||
// 更新请求体
|
||||
c.Request.Body = io.NopCloser(bytes.NewBufferString(utils.JsonEncode(jsonData)))
|
||||
// 对 JSON 数据中的字符串值去除两端空格
|
||||
trimJSONStrings(jsonData)
|
||||
// 更新请求体
|
||||
c.Request.Body = io.NopCloser(bytes.NewBufferString(utils.JsonEncode(jsonData)))
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
|
||||
@@ -22,8 +22,9 @@ type AppConfig struct {
|
||||
WeChatBot bool // 是否启用微信机器人
|
||||
SdConfig StableDiffusionConfig // sd 绘画配置
|
||||
|
||||
XXLConfig XXLConfig
|
||||
AlipayConfig AlipayConfig
|
||||
XXLConfig XXLConfig
|
||||
AlipayConfig AlipayConfig
|
||||
HuPiPayConfig HuPiPayConfig
|
||||
}
|
||||
|
||||
type ChatPlusApiConfig struct {
|
||||
@@ -40,10 +41,6 @@ type MidJourneyConfig struct {
|
||||
ChanelId string // Chanel ID
|
||||
}
|
||||
|
||||
type WeChatConfig struct {
|
||||
Enabled bool
|
||||
}
|
||||
|
||||
type StableDiffusionConfig struct {
|
||||
Enabled bool
|
||||
ApiURL string
|
||||
@@ -61,7 +58,7 @@ type AliYunSmsConfig struct {
|
||||
}
|
||||
|
||||
type AlipayConfig struct {
|
||||
Enabled bool // 是否启用该服务
|
||||
Enabled bool // 是否启用该支付通道
|
||||
SandBox bool // 是否沙盒环境
|
||||
AppId string // 应用 ID
|
||||
UserId string // 支付宝用户 ID
|
||||
@@ -72,6 +69,15 @@ type AlipayConfig struct {
|
||||
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 任务调度配置
|
||||
Enabled bool
|
||||
ServerAddr string
|
||||
@@ -106,9 +112,11 @@ type ChatConfig struct {
|
||||
Baidu ModelAPIConfig `json:"baidu"`
|
||||
XunFei ModelAPIConfig `json:"xun_fei"`
|
||||
|
||||
EnableContext bool `json:"enable_context"` // 是否开启聊天上下文
|
||||
EnableHistory bool `json:"enable_history"` // 是否允许保存聊天记录
|
||||
ContextDeep int `json:"context_deep"` // 上下文深度
|
||||
EnableContext bool `json:"enable_context"` // 是否开启聊天上下文
|
||||
EnableHistory bool `json:"enable_history"` // 是否允许保存聊天记录
|
||||
ContextDeep int `json:"context_deep"` // 上下文深度
|
||||
DallApiURL string `json:"dall_api_url"` // dall-e3 绘图 API 地址
|
||||
DallImgNum int `json:"dall_img_num"` // dall-e3 出图数量
|
||||
}
|
||||
|
||||
type Platform string
|
||||
|
||||
@@ -44,6 +44,8 @@ func (h *OrderHandler) List(c *gin.Context) {
|
||||
end := utils.Str2stamp(data.PayTime[1] + " 00:00:00")
|
||||
session = session.Where("pay_time >= ? AND pay_time <= ?", start, end)
|
||||
}
|
||||
session = session.Where("status = ?", types.OrderPaidSuccess)
|
||||
|
||||
var total int64
|
||||
session.Model(&model.Order{}).Count(&total)
|
||||
var items []model.Order
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"gorm.io/gorm"
|
||||
"html/template"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -200,7 +201,7 @@ func (h *ChatHandler) sendAzureMessage(
|
||||
RoleId: role.Id,
|
||||
Type: types.PromptMsg,
|
||||
Icon: userVo.Avatar,
|
||||
Content: prompt,
|
||||
Content: template.HTMLEscapeString(prompt),
|
||||
Tokens: promptToken,
|
||||
UseContext: useContext,
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
@@ -156,7 +157,7 @@ func (h *ChatHandler) sendBaiduMessage(
|
||||
RoleId: role.Id,
|
||||
Type: types.PromptMsg,
|
||||
Icon: userVo.Avatar,
|
||||
Content: prompt,
|
||||
Content: template.HTMLEscapeString(prompt),
|
||||
Tokens: promptToken,
|
||||
UseContext: true,
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"html/template"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -135,7 +136,7 @@ func (h *ChatHandler) sendChatGLMMessage(
|
||||
RoleId: role.Id,
|
||||
Type: types.PromptMsg,
|
||||
Icon: userVo.Avatar,
|
||||
Content: prompt,
|
||||
Content: template.HTMLEscapeString(prompt),
|
||||
Tokens: promptToken,
|
||||
UseContext: true,
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"gorm.io/gorm"
|
||||
"html/template"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -135,7 +136,6 @@ func (h *ChatHandler) sendOpenAiMessage(
|
||||
params["user_id"] = userVo.Id
|
||||
params["role_id"] = role.Id
|
||||
params["chat_id"] = session.ChatId
|
||||
params["icon"] = "/images/avatar/mid_journey.png"
|
||||
params["session_id"] = session.SessionId
|
||||
}
|
||||
data, err := f.Invoke(params)
|
||||
@@ -199,7 +199,7 @@ func (h *ChatHandler) sendOpenAiMessage(
|
||||
RoleId: role.Id,
|
||||
Type: types.PromptMsg,
|
||||
Icon: userVo.Avatar,
|
||||
Content: prompt,
|
||||
Content: template.HTMLEscapeString(prompt),
|
||||
Tokens: promptToken,
|
||||
UseContext: useContext,
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/gorilla/websocket"
|
||||
"html/template"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -198,7 +199,7 @@ func (h *ChatHandler) sendXunFeiMessage(
|
||||
RoleId: role.Id,
|
||||
Type: types.PromptMsg,
|
||||
Icon: userVo.Avatar,
|
||||
Content: prompt,
|
||||
Content: template.HTMLEscapeString(prompt),
|
||||
Tokens: promptToken,
|
||||
UseContext: true,
|
||||
}
|
||||
|
||||
@@ -21,31 +21,37 @@ import (
|
||||
|
||||
const (
|
||||
PayWayAlipay = "支付宝"
|
||||
PayWayWechat = "微信支付"
|
||||
PayWayXunHu = "虎皮椒"
|
||||
)
|
||||
|
||||
// PaymentHandler 支付服务回调 handler
|
||||
type PaymentHandler struct {
|
||||
BaseHandler
|
||||
alipayService *payment.AlipayService
|
||||
snowflake *service.Snowflake
|
||||
db *gorm.DB
|
||||
fs embed.FS
|
||||
lock sync.Mutex
|
||||
alipayService *payment.AlipayService
|
||||
huPiPayService *payment.HuPiPayService
|
||||
snowflake *service.Snowflake
|
||||
db *gorm.DB
|
||||
fs embed.FS
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
func NewPaymentHandler(server *core.AppServer, alipayService *payment.AlipayService, snowflake *service.Snowflake, db *gorm.DB, fs embed.FS) *PaymentHandler {
|
||||
h := PaymentHandler{lock: sync.Mutex{}}
|
||||
func NewPaymentHandler(server *core.AppServer, alipayService *payment.AlipayService, huPiPayService *payment.HuPiPayService, snowflake *service.Snowflake, db *gorm.DB, fs embed.FS) *PaymentHandler {
|
||||
h := PaymentHandler{
|
||||
alipayService: alipayService,
|
||||
huPiPayService: huPiPayService,
|
||||
snowflake: snowflake,
|
||||
fs: fs,
|
||||
db: db,
|
||||
lock: sync.Mutex{},
|
||||
}
|
||||
h.App = server
|
||||
h.alipayService = alipayService
|
||||
h.snowflake = snowflake
|
||||
h.db = db
|
||||
h.fs = fs
|
||||
return &h
|
||||
}
|
||||
|
||||
func (h *PaymentHandler) Alipay(c *gin.Context) {
|
||||
func (h *PaymentHandler) DoPay(c *gin.Context) {
|
||||
orderNo := h.GetTrim(c, "order_no")
|
||||
payWay := h.GetTrim(c, "pay_way")
|
||||
|
||||
if orderNo == "" {
|
||||
resp.ERROR(c, types.InvalidArgs)
|
||||
return
|
||||
@@ -60,21 +66,61 @@ func (h *PaymentHandler) Alipay(c *gin.Context) {
|
||||
|
||||
// 更新扫码状态
|
||||
h.db.Model(&order).UpdateColumn("status", types.OrderScanned)
|
||||
// 生成支付链接
|
||||
notifyURL := h.App.Config.AlipayConfig.NotifyURL
|
||||
returnURL := "" // 关闭同步回跳
|
||||
amount := fmt.Sprintf("%.2f", order.Amount)
|
||||
if payWay == "alipay" { // 支付宝
|
||||
// 生成支付链接
|
||||
notifyURL := h.App.Config.AlipayConfig.NotifyURL
|
||||
returnURL := "" // 关闭同步回跳
|
||||
amount := fmt.Sprintf("%.2f", order.Amount)
|
||||
|
||||
uri, err := h.alipayService.PayUrlMobile(order.OrderNo, notifyURL, returnURL, amount, order.Subject)
|
||||
if err != nil {
|
||||
resp.ERROR(c, "error with generate pay url: "+err.Error())
|
||||
uri, err := h.alipayService.PayUrlMobile(order.OrderNo, notifyURL, returnURL, amount, order.Subject)
|
||||
if err != nil {
|
||||
resp.ERROR(c, "error with generate pay url: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Redirect(302, uri)
|
||||
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) {
|
||||
var data struct {
|
||||
OrderNo string `json:"order_no"`
|
||||
@@ -111,16 +157,12 @@ func (h *PaymentHandler) OrderQuery(c *gin.Context) {
|
||||
resp.SUCCESS(c, gin.H{"status": order.Status})
|
||||
}
|
||||
|
||||
// AlipayQrcode 生成支付宝支付 URL 二维码
|
||||
func (h *PaymentHandler) AlipayQrcode(c *gin.Context) {
|
||||
if !h.App.SysConfig.EnabledAlipay || h.alipayService == nil {
|
||||
resp.ERROR(c, "当前支付通道已经关闭,请联系管理员开通!")
|
||||
return
|
||||
}
|
||||
|
||||
// PayQrcode 生成支付 URL 二维码
|
||||
func (h *PaymentHandler) PayQrcode(c *gin.Context) {
|
||||
var data struct {
|
||||
ProductId uint `json:"product_id"`
|
||||
UserId int `json:"user_id"`
|
||||
PayWay string `json:"pay_way"` // 支付方式
|
||||
ProductId uint `json:"product_id"`
|
||||
UserId int `json:"user_id"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&data); err != nil {
|
||||
resp.ERROR(c, types.InvalidArgs)
|
||||
@@ -146,6 +188,10 @@ func (h *PaymentHandler) AlipayQrcode(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
payWay := PayWayAlipay
|
||||
if data.PayWay == "hupi" {
|
||||
payWay = PayWayXunHu
|
||||
}
|
||||
// 创建订单
|
||||
remark := types.OrderRemark{
|
||||
Days: product.Days,
|
||||
@@ -162,7 +208,7 @@ func (h *PaymentHandler) AlipayQrcode(c *gin.Context) {
|
||||
Subject: product.Name,
|
||||
Amount: product.Price - product.Discount,
|
||||
Status: types.OrderNotPaid,
|
||||
PayWay: PayWayAlipay,
|
||||
PayWay: payWay,
|
||||
Remark: utils.JsonEncode(remark),
|
||||
}
|
||||
res = h.db.Create(&order)
|
||||
@@ -171,19 +217,30 @@ func (h *PaymentHandler) AlipayQrcode(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// 生成二维码图片
|
||||
file, err := h.fs.Open("res/img/alipay.jpg")
|
||||
var logo string
|
||||
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 {
|
||||
resp.ERROR(c, err.Error())
|
||||
resp.ERROR(c, "error with open qrcode log file: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
parse, err := url.Parse(h.App.Config.AlipayConfig.NotifyURL)
|
||||
if err != nil {
|
||||
resp.ERROR(c, err.Error())
|
||||
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)
|
||||
if err != nil {
|
||||
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})
|
||||
}
|
||||
|
||||
// AlipayNotify 支付宝支付回调
|
||||
func (h *PaymentHandler) AlipayNotify(c *gin.Context) {
|
||||
err := c.Request.ParseForm()
|
||||
if err != nil {
|
||||
@@ -212,27 +270,46 @@ func (h *PaymentHandler) AlipayNotify(c *gin.Context) {
|
||||
h.lock.Lock()
|
||||
defer h.lock.Unlock()
|
||||
|
||||
var order model.Order
|
||||
res := h.db.Where("order_no = ?", r.OutTradeNo).First(&order)
|
||||
if res.Error != nil {
|
||||
logger.Error(res.Error)
|
||||
err = h.notify(r.OutTradeNo)
|
||||
if err != nil {
|
||||
c.String(http.StatusOK, "fail")
|
||||
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
|
||||
res = h.db.First(&user, order.UserId)
|
||||
if res.Error != nil {
|
||||
logger.Error(res.Error)
|
||||
c.String(http.StatusOK, "fail")
|
||||
return
|
||||
err := fmt.Errorf("error with fetch user info: %v", res.Error)
|
||||
logger.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
var remark types.OrderRemark
|
||||
err = utils.JsonDecode(order.Remark, &remark)
|
||||
err := utils.JsonDecode(order.Remark, &remark)
|
||||
if err != nil {
|
||||
logger.Error(res.Error)
|
||||
c.String(http.StatusOK, "fail")
|
||||
return
|
||||
err := fmt.Errorf("error with decode order remark: %v", err)
|
||||
logger.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// 1. 点卡:days == 0, calls > 0
|
||||
// 2. vip 套餐:days > 0, calls == 0
|
||||
if remark.Days > 0 {
|
||||
@@ -256,18 +333,57 @@ func (h *PaymentHandler) AlipayNotify(c *gin.Context) {
|
||||
// 更新用户信息
|
||||
res = h.db.Updates(&user)
|
||||
if res.Error != nil {
|
||||
logger.Error(res.Error)
|
||||
c.String(http.StatusOK, "fail")
|
||||
return
|
||||
err := fmt.Errorf("error with update user info: %v", res.Error)
|
||||
logger.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// 更新订单状态
|
||||
order.PayTime = time.Now().Unix()
|
||||
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))
|
||||
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")
|
||||
}
|
||||
|
||||
91
api/handler/prompt_handler.go
Normal file
91
api/handler/prompt_handler.go
Normal file
@@ -0,0 +1,91 @@
|
||||
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 translatePromptTemplate = "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]"
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
// 获取 OpenAI 的 API KEY
|
||||
var apiKey model.ApiKey
|
||||
res := h.db.Where("platform = ?", types.OpenAI).First(&apiKey)
|
||||
if res.Error != nil {
|
||||
resp.ERROR(c, "找不到可用 OpenAI API KEY")
|
||||
return
|
||||
}
|
||||
|
||||
messages := make([]interface{}, 1)
|
||||
messages[0] = types.Message{
|
||||
Role: "user",
|
||||
Content: fmt.Sprintf(translatePromptTemplate, data.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() {
|
||||
resp.ERROR(c, fmt.Sprintf("error with http request: %v%v%s", err, r.Err, errRes.Error.Message))
|
||||
return
|
||||
}
|
||||
|
||||
resp.SUCCESS(c, response.Choices[0].Message.Content)
|
||||
}
|
||||
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)
|
||||
}
|
||||
21
api/main.go
21
api/main.go
@@ -17,7 +17,6 @@ import (
|
||||
"chatplus/store"
|
||||
"context"
|
||||
"embed"
|
||||
"github.com/go-redis/redis/v8"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
@@ -26,6 +25,8 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/go-redis/redis/v8"
|
||||
|
||||
"github.com/lionsoul2014/ip2region/binding/golang/xdb"
|
||||
"go.uber.org/fx"
|
||||
"gorm.io/gorm"
|
||||
@@ -192,6 +193,7 @@ func main() {
|
||||
}),
|
||||
|
||||
fx.Provide(payment.NewAlipayService),
|
||||
fx.Provide(payment.NewHuPiPay),
|
||||
fx.Provide(service.NewSnowflake),
|
||||
fx.Provide(service.NewXXLJobExecutor),
|
||||
fx.Invoke(func(exec *service.XXLJobExecutor, config *types.AppConfig) {
|
||||
@@ -318,10 +320,12 @@ func main() {
|
||||
}),
|
||||
fx.Invoke(func(s *core.AppServer, h *handler.PaymentHandler) {
|
||||
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("alipay/qrcode", h.AlipayQrcode)
|
||||
group.POST("qrcode", h.PayQrcode)
|
||||
group.POST("alipay/notify", h.AlipayNotify)
|
||||
group.POST("hupipay/notify", h.HuPiPayNotify)
|
||||
}),
|
||||
fx.Invoke(func(s *core.AppServer, h *admin.ProductHandler) {
|
||||
group := s.Engine.Group("/api/admin/product/")
|
||||
@@ -353,6 +357,17 @@ func main() {
|
||||
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("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) {
|
||||
err := s.Run(db)
|
||||
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": [
|
||||
"task(m1wpaa4v60zedj8)",
|
||||
"a cute cat",
|
||||
"task(s95jqt5jr8yppcp)",
|
||||
"A beautiful Chinese girl in a garden",
|
||||
"",
|
||||
[],
|
||||
20,
|
||||
"DPM++ 2M Karras",
|
||||
30,
|
||||
"Euler a",
|
||||
1,
|
||||
1,
|
||||
7,
|
||||
512,
|
||||
384,
|
||||
512,
|
||||
true,
|
||||
0.7,
|
||||
2,
|
||||
"ESRGAN_4x",
|
||||
"Latent",
|
||||
10,
|
||||
0,
|
||||
0,
|
||||
@@ -55,13 +55,13 @@
|
||||
false,
|
||||
false,
|
||||
0,
|
||||
false,
|
||||
[],
|
||||
[
|
||||
],
|
||||
"",
|
||||
"",
|
||||
""
|
||||
],
|
||||
"event_data": null,
|
||||
"fn_index": 96,
|
||||
"session_hash": "kmb0ojjfhdj"
|
||||
"fn_index": 95,
|
||||
"session_hash": "eqwumnt3rov"
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"chatplus/store/model"
|
||||
"chatplus/utils"
|
||||
"fmt"
|
||||
|
||||
"github.com/imroc/req/v3"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
@@ -14,7 +15,6 @@ import (
|
||||
|
||||
type FuncImage struct {
|
||||
name string
|
||||
apiURL string
|
||||
db *gorm.DB
|
||||
uploadManager *oss.UploaderManager
|
||||
proxyURL string
|
||||
@@ -26,7 +26,6 @@ func NewImageFunc(db *gorm.DB, manager *oss.UploaderManager, config *types.AppCo
|
||||
name: "DALL-E3 绘画",
|
||||
uploadManager: manager,
|
||||
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) {
|
||||
logger.Infof("绘画参数:%+v", params)
|
||||
prompt := utils.InterfaceToString(params["prompt"])
|
||||
// 获取绘图 API KEY
|
||||
// get image generation API KEY
|
||||
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 errRes ErrRes
|
||||
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{
|
||||
Model: "dall-e-3",
|
||||
Prompt: prompt,
|
||||
N: 1,
|
||||
N: imgNum,
|
||||
Size: "1024x1024",
|
||||
}).
|
||||
SetErrorResult(&errRes).
|
||||
SetSuccessResult(&res).Post(f.apiURL)
|
||||
SetSuccessResult(&res).Post(apiURL)
|
||||
if err != nil || r.IsErrorState() {
|
||||
return "", fmt.Errorf("error with http request: %v%v%s", err, r.Err, errRes.Error.Message)
|
||||
}
|
||||
|
||||
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,
|
||||
"session_hash": taskInfo.SessionHash,
|
||||
}
|
||||
logger.Debug(utils.JsonEncode(body))
|
||||
var result = make(chan CBReq)
|
||||
go func() {
|
||||
var res struct {
|
||||
@@ -231,7 +230,6 @@ func (s *Service) runTask(taskInfo TaskInfo, client *req.Client) {
|
||||
|
||||
cbReq.ImageData = progressRes.LivePreview
|
||||
cbReq.Progress = int(progressRes.Progress * 100)
|
||||
logger.Debug(cbReq)
|
||||
s.callback(cbReq)
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
@@ -287,8 +285,13 @@ func (s *Service) callback(data CBReq) {
|
||||
if data.Progress < 100 && 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 {
|
||||
utils.ReplyChunkMessage(client, jobVo)
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
#!/bin/bash
|
||||
|
||||
version=$1
|
||||
# build go api
|
||||
arch=${2:-amd64}
|
||||
|
||||
# build go api program
|
||||
cd ../api
|
||||
make clean linux
|
||||
make clean $arch
|
||||
|
||||
# build web app
|
||||
cd ../web
|
||||
@@ -12,15 +14,15 @@ npm run build
|
||||
cd ../build
|
||||
|
||||
# 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
|
||||
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
|
||||
docker rmi -f registry.cn-shenzhen.aliyuncs.com/geekmaster/chatgpt-plus-web:$version
|
||||
docker build --platform linux/amd64 -t registry.cn-shenzhen.aliyuncs.com/geekmaster/chatgpt-plus-web:$version -f dockerfile-vue ../
|
||||
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-$arch -f dockerfile-vue ../
|
||||
|
||||
if [ "$2" = "push" ];then
|
||||
docker push registry.cn-shenzhen.aliyuncs.com/geekmaster/chatgpt-plus-api:$version
|
||||
docker push registry.cn-shenzhen.aliyuncs.com/geekmaster/chatgpt-plus-web:$version
|
||||
if [ "$3" = "push" ];then
|
||||
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-$arch
|
||||
fi
|
||||
|
||||
@@ -4,9 +4,9 @@ FROM alpine:3.18.2
|
||||
MAINTAINER yangjian<yangjian102621@163.com>
|
||||
|
||||
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
|
||||
|
||||
# 容器启动时执行的命令
|
||||
CMD ["./chatgpt-plus-amd64-linux"]
|
||||
CMD ["./chatgpt-plus-linux"]
|
||||
|
||||
@@ -37,7 +37,7 @@ WeChatBot = false
|
||||
Active = "local" # 默认使用本地文件存储引擎
|
||||
[OSS.Local]
|
||||
BasePath = "./static/upload" # 本地文件上传根路径
|
||||
BaseURL = "http://localhost:5678/static/upload" # 本地上传文件根 URL 如果是线上,则直接设置为 /static/upload 即可
|
||||
BaseURL = "/static/upload" # 本地上传文件根 URL 如果是线上,则直接设置为 /static/upload 即可
|
||||
[OSS.Minio]
|
||||
Endpoint = "" # 如 172.22.11.200:9000
|
||||
AccessKey = "" # 自己去 Minio 控制台去创建一个 Access Key
|
||||
@@ -82,4 +82,12 @@ WeChatBot = false
|
||||
PublicKey = "certs/alipay/appPublicCert.crt" # 应用公钥证书
|
||||
AlipayPublicKey = "certs/alipay/alipayPublicCert.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 {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
color: #fff;
|
||||
overflow-x: hidden;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
color: #fff;
|
||||
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 {
|
||||
width: 100%;
|
||||
padding: 2px;
|
||||
background-color: #555;
|
||||
width: 100%;
|
||||
padding: 2px;
|
||||
background-color: #555;
|
||||
}
|
||||
|
||||
.task-list-box .running-job-list .job-item .job-item-inner {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.task-list-box .running-job-list .job-item .job-item-inner .progress {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.task-list-box .running-job-list .job-item .job-item-inner .progress span {
|
||||
font-size: 20px;
|
||||
color: #fff;
|
||||
font-size: 20px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.task-list-box .finish-job-list .job-item {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
width: 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 {
|
||||
margin: 6px 0;
|
||||
margin: 6px 0;
|
||||
}
|
||||
|
||||
.task-list-box .finish-job-list .job-item .opt .opt-line ul {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
}
|
||||
|
||||
.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 {
|
||||
padding: 3px 0;
|
||||
width: 44px;
|
||||
text-align: center;
|
||||
border-radius: 5px;
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
background-color: #4e5058;
|
||||
color: #fff;
|
||||
padding: 3px 0;
|
||||
width: 40px;
|
||||
text-align: center;
|
||||
border-radius: 5px;
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
background-color: #4e5058;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.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 {
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
font-size: 20px;
|
||||
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 {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-height: 240px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.task-list-box .el-image img {
|
||||
height: 240px;
|
||||
height: 240px;
|
||||
}
|
||||
|
||||
.task-list-box .el-image .el-image-viewer__wrapper img {
|
||||
width: auto;
|
||||
height: auto;
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.task-list-box .el-image .image-slot {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
min-height: 200px;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
min-height: 200px;
|
||||
color: #fff;
|
||||
height: 240px;
|
||||
}
|
||||
|
||||
.task-list-box .el-image .image-slot .iconfont {
|
||||
font-size: 50px;
|
||||
margin-bottom: 10px;
|
||||
font-size: 50px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.task-list-box .el-image.upscale {
|
||||
max-height: 312px;
|
||||
max-height: 310px;
|
||||
}
|
||||
|
||||
.task-list-box .el-image.upscale img {
|
||||
height: 312px;
|
||||
height: 310px;
|
||||
}
|
||||
|
||||
.task-list-box .el-image.upscale .el-image-viewer__wrapper img {
|
||||
width: auto;
|
||||
height: auto;
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 4125778 */
|
||||
src: url('iconfont.woff2?t=1697164072791') format('woff2'),
|
||||
url('iconfont.woff?t=1697164072791') format('woff'),
|
||||
url('iconfont.ttf?t=1697164072791') format('truetype');
|
||||
src: url('iconfont.woff2?t=1702024026523') format('woff2'),
|
||||
url('iconfont.woff?t=1702024026523') format('woff'),
|
||||
url('iconfont.ttf?t=1702024026523') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
@@ -13,6 +13,10 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-alipay:before {
|
||||
content: "\e634";
|
||||
}
|
||||
|
||||
.icon-face:before {
|
||||
content: "\e64b";
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -5,6 +5,13 @@
|
||||
"css_prefix_text": "icon-",
|
||||
"description": "",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "1486848",
|
||||
"name": "支付宝支付",
|
||||
"font_class": "alipay",
|
||||
"unicode": "e634",
|
||||
"unicode_decimal": 58932
|
||||
},
|
||||
{
|
||||
"icon_id": "845789",
|
||||
"name": "笑脸",
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -105,12 +105,12 @@
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
<!-- <el-button type="primary" @click="newChat">-->
|
||||
<!-- <el-icon>-->
|
||||
<!-- <Plus/>-->
|
||||
<!-- </el-icon>-->
|
||||
<!-- 新建会话-->
|
||||
<!-- </el-button>-->
|
||||
<el-button type="primary" @click="newChat">
|
||||
<el-icon>
|
||||
<Plus/>
|
||||
</el-icon>
|
||||
新建对话
|
||||
</el-button>
|
||||
|
||||
<el-button type="success" @click="exportChat" plain>
|
||||
<i class="iconfont icon-export"></i>
|
||||
@@ -237,7 +237,7 @@ import {
|
||||
Check,
|
||||
Close,
|
||||
Delete,
|
||||
Edit,
|
||||
Edit, Plus,
|
||||
Promotion,
|
||||
RefreshRight,
|
||||
Search,
|
||||
@@ -245,7 +245,7 @@ import {
|
||||
VideoPause
|
||||
} from '@element-plus/icons-vue'
|
||||
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 hl from "highlight.js";
|
||||
import {getSessionId, getUserToken, removeUserToken} from "@/store/session";
|
||||
@@ -624,6 +624,7 @@ const connect = function (chat_id, role_id) {
|
||||
// 将聊天框的滚动条滑动到最底部
|
||||
nextTick(() => {
|
||||
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) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 追加消息
|
||||
chatData.value.push({
|
||||
type: "prompt",
|
||||
id: randString(32),
|
||||
icon: loginUser.value.avatar,
|
||||
content: renderInputText(prompt.value),
|
||||
content: md.render(prompt.value),
|
||||
created_at: new Date().getTime(),
|
||||
});
|
||||
|
||||
@@ -760,10 +760,7 @@ const loadChatHistory = function (chatId) {
|
||||
}
|
||||
showHello.value = false
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
if (data[i].type === "prompt") {
|
||||
chatData.value.push(data[i]);
|
||||
continue;
|
||||
} else if (data[i].type === "mj") {
|
||||
if (data[i].type === "mj") {
|
||||
data[i].content = JSON.parse(data[i].content)
|
||||
data[i].content.html = md.render(data[i].content?.content)
|
||||
chatData.value.push(data[i]);
|
||||
@@ -801,7 +798,7 @@ const reGenerate = function () {
|
||||
type: "prompt",
|
||||
id: randString(32),
|
||||
icon: loginUser.value.avatar,
|
||||
content: renderInputText(text)
|
||||
content: md.render(text)
|
||||
});
|
||||
socket.value.send(text);
|
||||
}
|
||||
|
||||
@@ -233,29 +233,31 @@
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<div class="param-line pt">
|
||||
<div class="flex-row justify-between items-center">
|
||||
<div class="flex-row justify-start items-center">
|
||||
<span>提示词:</span>
|
||||
<el-tooltip effect="light" content="输入你想要的内容,用逗号分割" placement="right">
|
||||
<el-icon>
|
||||
<InfoFilled/>
|
||||
<div v-loading="loading" element-loading-background="rgba(122, 122, 122, 0.8)">
|
||||
<div class="param-line pt">
|
||||
<div class="flex-row justify-between items-center">
|
||||
<div class="flex-row justify-start items-center">
|
||||
<span>提示词:</span>
|
||||
<el-tooltip effect="light" content="输入你想要的内容,用逗号分割" placement="right">
|
||||
<el-icon>
|
||||
<InfoFilled/>
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<el-button type="success" @click="translatePrompt">
|
||||
<el-icon style="margin-right: 6px;font-size: 18px;">
|
||||
<Refresh/>
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
翻译
|
||||
</el-button>
|
||||
</div>
|
||||
<el-button type="success">
|
||||
<el-icon style="margin-right: 6px;font-size: 18px;">
|
||||
<Refresh/>
|
||||
</el-icon>
|
||||
翻译
|
||||
</el-button>
|
||||
</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 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 class="param-line pt">
|
||||
@@ -268,12 +270,12 @@
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<el-button type="success">
|
||||
<el-icon style="margin-right: 6px;font-size: 18px;">
|
||||
<Refresh/>
|
||||
</el-icon>
|
||||
翻译
|
||||
</el-button>
|
||||
<!-- <el-button type="success">-->
|
||||
<!-- <el-icon style="margin-right: 6px;font-size: 18px;">-->
|
||||
<!-- <Refresh/>-->
|
||||
<!-- </el-icon>-->
|
||||
<!-- 翻译-->
|
||||
<!-- </el-button>-->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -286,7 +288,7 @@
|
||||
<div class="submit-btn">
|
||||
<el-button color="#47fff1" :dark="false" @click="generate" round>立即生成</el-button>
|
||||
<div class="text-info">
|
||||
<el-tag type="success">可用额度:{{ imgCalls }}</el-tag>
|
||||
<el-tag type="success">绘图可用额度:{{ imgCalls }}</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
@@ -336,7 +338,7 @@
|
||||
|
||||
<h2>创作记录</h2>
|
||||
<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">
|
||||
<div class="job-item">
|
||||
<el-image
|
||||
@@ -500,6 +502,7 @@ const router = useRouter()
|
||||
|
||||
const socket = ref(null)
|
||||
const imgCalls = ref(0)
|
||||
const loading = ref(false)
|
||||
|
||||
const connect = () => {
|
||||
let host = process.env.VUE_APP_WS_HOST
|
||||
@@ -568,6 +571,16 @@ const connect = () => {
|
||||
});
|
||||
}
|
||||
|
||||
const translatePrompt = () => {
|
||||
loading.value = true
|
||||
httpPost("/api/prompt/translate", {"prompt": params.value.prompt}).then(res => {
|
||||
params.value.prompt = res.data
|
||||
loading.value = false
|
||||
}).then(e => {
|
||||
ElMessage.error("翻译失败:" + e.message)
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
checkSession().then(user => {
|
||||
imgCalls.value = user['img_calls']
|
||||
|
||||
@@ -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",
|
||||
"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({
|
||||
width: 1024,
|
||||
height: 1024,
|
||||
sampler: samplers[0],
|
||||
seed: -1,
|
||||
steps: 20,
|
||||
steps: 30,
|
||||
cfg_scale: 7,
|
||||
face_fix: false,
|
||||
hd_fix: false,
|
||||
hd_redraw_rate: 0.3,
|
||||
hd_redraw_rate: 0.7,
|
||||
hd_scale: 2,
|
||||
hd_scale_alg: scaleAlg[0],
|
||||
hd_steps: 0,
|
||||
prompt: "A beautiful Chinese girl riding on a tiger",
|
||||
hd_steps: 10,
|
||||
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))",
|
||||
})
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
|
||||
<ItemList :items="list" v-if="list.length > 0" :gap="30" :width="240">
|
||||
<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">
|
||||
<el-image :src="vipImg" fit="cover"/>
|
||||
</div>
|
||||
@@ -62,6 +62,16 @@
|
||||
<span class="expire" v-if="scope.item.days > 0">{{ scope.item.days }}天</span>
|
||||
<span class="expire" v-else>当月有效</span>
|
||||
</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>
|
||||
</template>
|
||||
@@ -113,7 +123,7 @@
|
||||
title="充值订单支付">
|
||||
<div class="pay-container">
|
||||
<div class="count-down">
|
||||
<count-down :second="orderTimeout" @timeout="orderPay" ref="countDown"/>
|
||||
<count-down :second="orderTimeout" @timeout="refreshPayCode" ref="countDownRef"/>
|
||||
</div>
|
||||
|
||||
<div class="pay-qrcode" v-loading="loading">
|
||||
@@ -130,9 +140,10 @@
|
||||
<el-icon>
|
||||
<InfoFilled/>
|
||||
</el-icon>
|
||||
<span class="text">请打开手机支付宝扫码支付</span>
|
||||
<span class="text">请打开手机{{ payName }}扫码支付</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
@@ -152,6 +163,7 @@ import RewardVerify from "@/components/RewardVerify.vue";
|
||||
import {useRouter} from "vue-router";
|
||||
import {removeUserToken} from "@/store/session";
|
||||
import UserOrder from "@/components/UserOrder.vue";
|
||||
import CountDown from "@/components/CountDown.vue";
|
||||
|
||||
const listBoxHeight = window.innerHeight - 97
|
||||
const list = ref([])
|
||||
@@ -171,11 +183,15 @@ const isLogin = ref(false)
|
||||
const router = useRouter()
|
||||
const curPayProduct = ref(null)
|
||||
const activeOrderNo = ref("")
|
||||
const countDown = ref(null)
|
||||
const countDownRef = ref(null)
|
||||
const orderTimeout = ref(1800)
|
||||
const loading = ref(true)
|
||||
const orderPayInfoText = ref("")
|
||||
|
||||
const payWays = ref({})
|
||||
const payName = ref("支付宝")
|
||||
const curPay = ref("alipay") // 当前支付方式
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
checkSession().then(_user => {
|
||||
@@ -200,9 +216,48 @@ onMounted(() => {
|
||||
}).catch(e => {
|
||||
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) {
|
||||
showLoginDialog.value = true
|
||||
return
|
||||
@@ -210,21 +265,22 @@ const orderPay = (row) => {
|
||||
if (row) {
|
||||
curPayProduct.value = row
|
||||
}
|
||||
loading.value = true
|
||||
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']
|
||||
activeOrderNo.value = res.data['order_no']
|
||||
queryOrder(activeOrderNo.value)
|
||||
loading.value = false
|
||||
// 重置计数器
|
||||
if (countDown.value) {
|
||||
countDown.value.resetTimer()
|
||||
}
|
||||
}).catch(e => {
|
||||
ElMessage.error("生成支付订单失败:" + e.message)
|
||||
})
|
||||
genPayQrcode()
|
||||
}
|
||||
|
||||
// 虎皮椒支付
|
||||
const huPiPay = (row) => {
|
||||
payName.value = payWays.value["hupi"]["name"] === "wechat" ? '微信' : '支付宝'
|
||||
curPay.value = "hupi"
|
||||
if (!user.value.id) {
|
||||
showLoginDialog.value = true
|
||||
return
|
||||
}
|
||||
if (row) {
|
||||
curPayProduct.value = row
|
||||
}
|
||||
genPayQrcode()
|
||||
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
@@ -249,6 +249,13 @@
|
||||
<el-input v-model.number="chat['xun_fei']['max_tokens']" placeholder="回复的最大字数,最大4096"/>
|
||||
</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-button type="primary" @click="save('chat')">保存</el-button>
|
||||
</el-form-item>
|
||||
@@ -274,6 +281,7 @@ const chat = ref({
|
||||
context_deep: 0,
|
||||
enable_context: true,
|
||||
enable_history: true,
|
||||
dall_api_url: "",
|
||||
})
|
||||
const loading = ref(true)
|
||||
const systemFormRef = ref(null)
|
||||
@@ -290,25 +298,7 @@ onMounted(() => {
|
||||
|
||||
// 加载聊天配置
|
||||
httpGet('/api/admin/config/get?key=chat').then(res => {
|
||||
// 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
|
||||
chat.value = res.data
|
||||
loading.value = false
|
||||
}).catch(e => {
|
||||
ElMessage.error("加载聊天配置失败: " + e.message)
|
||||
|
||||
Reference in New Issue
Block a user