Compare commits

...

21 Commits
v3.2.6 ... dev

Author SHA1 Message Date
RockYang
c83ac48bd2 feat: added delete file function 2024-02-19 16:43:03 +08:00
RockYang
3d159a833e fix: verifycation component touch event coordinates misplace in iphone browser 2024-02-19 14:04:50 +08:00
RockYang
4b09878bdd fix: fix bug for regenerate button did not work 2024-02-19 11:22:42 +08:00
RockYang
b0162e6a92 fix: Upscale and Variation task overrite each other 2024-02-16 18:08:29 +08:00
RockYang
8ab15e5dc4 feat: midjourney mobile page all function is ready 2024-02-16 15:55:04 +08:00
RockYang
d2ac807252 feat: mobile mj list page is ready 2024-02-15 18:11:22 +08:00
RockYang
0af01f6f1f feat: add mj image list component for mobile page. fixed bug for html tag escape 2024-02-15 11:39:04 +08:00
RockYang
013b319fab feat: add functions mj page for mobile 2024-01-31 07:24:35 +08:00
RockYang
2899ba5949 opt: add default extension for mj image 2024-01-30 21:46:17 +08:00
RockYang
a558b7e104 feat: mj for mobile page payout is ready 2024-01-30 18:34:01 +08:00
RockYang
7a833e2233 add docs and github link 2024-01-30 16:18:27 +08:00
RockYang
bf65746d00 opt: enable use cdn url for mj-plus 2024-01-28 21:56:25 +08:00
RockYang
f08a7862de feat: LaTeX parse is ready 2024-01-26 18:04:53 +08:00
RockYang
023a2c2f09 feat: add model field for chat_item and and chat_history data table 2024-01-26 16:54:00 +08:00
RockYang
1bcd0f4c1a feat: add err_msg field for mj and sd jobs 2024-01-26 14:50:36 +08:00
RockYang
a0f3bc8ccb feat: blend and swap face function for midjourney-plus is ready 2024-01-26 11:57:08 +08:00
RockYang
dea72738c1 feat: add blend and swapface task implements for midjourney 2024-01-25 18:50:24 +08:00
RockYang
a1d1fe7763 opt: refactor chat session page for mobile device 2024-01-25 14:07:10 +08:00
RockYang
a39ed9764c opt: optimize chat list page for mobile 2024-01-24 18:23:24 +08:00
RockYang
aaa5ba99aa feat: use vant replace element-plus as mobile UI framework 2024-01-24 17:34:30 +08:00
RockYang
2113508b6d feat: add websocket heartbeat message for mj page 2024-01-24 09:33:04 +08:00
107 changed files with 3004 additions and 4113 deletions

View File

@@ -1,4 +1,16 @@
# 更新日志
## v3.2.7
* 功能重构:采用 Vant 重构移动页面,新增 MidJourney 功能
* 功能优化:优化 PC 端 MidJourney 页面布局,新增融图和换脸功能
* Bug修复修复 issue [
管理界面操作用户存在的两个问题](https://github.com/yangjian102621/chatgpt-plus/issues/117#issuecomment-1909201532)
* 功能优化:在对话和聊天记录表中新增冗余字段 model存储对话模型
* Bug修复IPhone 手机验证码触摸事件坐标错位 [issue 144](https://github.com/yangjian102621/chatgpt-plus/issues/144)
* Bug修复重新生成按钮功能失效问题
* Bug修复对话输入HTML标签不显示的问题
* 功能优化gpt-4-all/gpts/midjourney-plus 支持第三方平台的 API KEY
* 功能新增:新增删除文件功能
## v3.2.6
* 功能优化:恢复关闭注册系统配置项,管理员可以在后台关闭用户注册,只允许内部添加账号
* 功能优化:兼用旧版本微信收款消息解析

View File

@@ -25,23 +25,16 @@ WeChatBot = false
AppId = ""
Token = ""
[SmsConfig] # 阿里云短信服务配置
AccessKey = ""
AccessSecret = ""
Product = "Dysmsapi"
Domain = "dysmsapi.aliyuncs.com"
Sign = ""
CodeTempId = ""
[Sms] # Sms 配置,用于发送短信
[SMS] # Sms 配置,用于发送短信
Active = "Ali" # 当前启用的短信服务,默认使用阿里云
[Sms.SmsBao]
[SMS.Bao]
Username = ""
Password = ""
Domain = "api.smsbao.com"
Sign = "【极客学长】"
CodeTemplate = "您的验证码是{code}。5分钟有效若非本人操作请忽略本短信。"
[Sms.Ali]
[SMS.Ali]
AccessKey = ""
AccessSecret = ""
Product = "Dysmsapi"
@@ -82,6 +75,7 @@ WeChatBot = false
[[MjPlusConfigs]]
Enabled = false
ApiURL = "https://api.chatgpt-plus.net" # 目前暂时不支持更改
CdnURL = "" # CND 加速的 URL如果有的话就设置
ApiKey = "sk-xxx"
NotifyURL = "https://ai.r9it.com/api/mj/notify" # 这里需要改成你的域名
@@ -113,9 +107,9 @@ WeChatBot = false
[HuPiPayConfig]
Enabled = false
Name = "wechat"
AppId = "201906161477"
AppSecret = "7f403199d510fb2c6f0b9f2311800e7c"
PayURL = "https://api.xunhupay.com/payment/do.html"
AppId = ""
AppSecret = ""
ApiURL = "https://api.xunhupay.com"
NotifyURL = "https://ai.r9it.com/api/payment/hupipay/notify"
[SmtpConfig] # 注意阿里云服务器禁用了25号端口所以如果需要使用邮件功能请别用阿里云服务器
@@ -130,5 +124,5 @@ WeChatBot = false
Name = "wechat" # 请不要改动
AppId = "" # 商户 ID
PrivateKey = "" # 秘钥
ApiURL = "https://payjs.cn/api/native"
ApiURL = "https://payjs.cn"
NotifyURL = "https://ai.r9it.com/api/payment/payjs/notify" # 异步回调地址,域名改成你自己的

View File

@@ -19,7 +19,6 @@ type AppConfig struct {
OSS OSSConfig // OSS config
MjConfigs []MidJourneyConfig // mj AI draw service pool
MjPlusConfigs []MidJourneyPlusConfig // MJ plus config
ImgCdnURL string // 图片反代加速地址
WeChatBot bool // 是否启用微信机器人
SdConfigs []StableDiffusionConfig // sd AI draw service pool
@@ -51,6 +50,7 @@ type MidJourneyConfig struct {
GuildId string // Server ID
ChanelId string // Chanel ID
UseCDN bool
ImgCdnURL string // 图片反代加速地址
DiscordAPI string
DiscordGateway string
}
@@ -63,8 +63,9 @@ type StableDiffusionConfig struct {
}
type MidJourneyPlusConfig struct {
Enabled bool // 如果启用了 MidJourney Plus将会自动禁用原生的MidJourney服务
ApiURL string
Enabled bool // 如果启用了 MidJourney Plus将会自动禁用原生的MidJourney服务
ApiURL string // api 地址
CdnURL string // CDN 加速地址
ApiKey string
NotifyURL string // 任务进度更新回调地址
}

View File

@@ -9,13 +9,17 @@ func (t TaskType) String() string {
const (
TaskImage = TaskType("image")
TaskBlend = TaskType("blend")
TaskSwapFace = TaskType("swapFace")
TaskUpscale = TaskType("upscale")
TaskVariation = TaskType("variation")
)
// MjTask MidJourney 任务
type MjTask struct {
Id int `json:"id"`
Id uint `json:"id"`
TaskId string `json:"task_id"`
ImgArr []string `json:"img_arr"`
ChannelId string `json:"channel_id"`
SessionId string `json:"session_id"`
Type TaskType `json:"type"`

View File

@@ -135,6 +135,7 @@ func (h *ChatHandler) sendAzureMessage(
Content: template.HTMLEscapeString(prompt),
Tokens: promptToken,
UseContext: true,
Model: req.Model,
}
historyUserMsg.CreatedAt = promptCreatedAt
historyUserMsg.UpdatedAt = promptCreatedAt
@@ -156,6 +157,7 @@ func (h *ChatHandler) sendAzureMessage(
Content: message.Content,
Tokens: totalTokens,
UseContext: true,
Model: req.Model,
}
historyReplyMsg.CreatedAt = replyCreatedAt
historyReplyMsg.UpdatedAt = replyCreatedAt
@@ -181,6 +183,7 @@ func (h *ChatHandler) sendAzureMessage(
} else {
chatItem.Title = prompt
}
chatItem.Model = req.Model
h.db.Create(&chatItem)
}
}

View File

@@ -160,6 +160,7 @@ func (h *ChatHandler) sendBaiduMessage(
Content: template.HTMLEscapeString(prompt),
Tokens: promptToken,
UseContext: true,
Model: req.Model,
}
historyUserMsg.CreatedAt = promptCreatedAt
historyUserMsg.UpdatedAt = promptCreatedAt
@@ -181,6 +182,7 @@ func (h *ChatHandler) sendBaiduMessage(
Content: message.Content,
Tokens: totalTokens,
UseContext: true,
Model: req.Model,
}
historyReplyMsg.CreatedAt = replyCreatedAt
historyReplyMsg.UpdatedAt = replyCreatedAt
@@ -205,6 +207,7 @@ func (h *ChatHandler) sendBaiduMessage(
} else {
chatItem.Title = prompt
}
chatItem.Model = req.Model
h.db.Create(&chatItem)
}
}

View File

@@ -97,7 +97,7 @@ func (h *ChatHandler) ChatHandle(c *gin.Context) {
// use old chat data override the chat model and role ID
var chat model.ChatItem
res = h.db.Where("chat_id=?", chatId).First(&chat)
res = h.db.Where("chat_id = ?", chatId).First(&chat)
if res.Error == nil {
chatModel.Id = chat.ModelId
roleId = int(chat.RoleId)
@@ -462,11 +462,7 @@ func (h *ChatHandler) doRequest(ctx context.Context, req types.ApiRequest, platf
req.Messages = nil
break
default:
if req.Model == "gpt-4-all" || strings.HasPrefix(req.Model, "gpt-4-gizmo-g-") {
apiURL = "https://gpt.bemore.lol/v1/chat/completions"
} else {
apiURL = apiKey.ApiURL
}
apiURL = apiKey.ApiURL
}
// 更新 API KEY 的最后使用时间
h.db.Model(apiKey).UpdateColumn("last_used_at", time.Now().Unix())

View File

@@ -139,6 +139,7 @@ func (h *ChatHandler) sendChatGLMMessage(
Content: template.HTMLEscapeString(prompt),
Tokens: promptToken,
UseContext: true,
Model: req.Model,
}
historyUserMsg.CreatedAt = promptCreatedAt
historyUserMsg.UpdatedAt = promptCreatedAt
@@ -160,6 +161,7 @@ func (h *ChatHandler) sendChatGLMMessage(
Content: message.Content,
Tokens: totalTokens,
UseContext: true,
Model: req.Model,
}
historyReplyMsg.CreatedAt = replyCreatedAt
historyReplyMsg.UpdatedAt = replyCreatedAt
@@ -184,6 +186,7 @@ func (h *ChatHandler) sendChatGLMMessage(
} else {
chatItem.Title = prompt
}
chatItem.Model = req.Model
h.db.Create(&chatItem)
}
}

View File

@@ -206,6 +206,7 @@ func (h *ChatHandler) sendOpenAiMessage(
Content: template.HTMLEscapeString(prompt),
Tokens: promptToken,
UseContext: useContext,
Model: req.Model,
}
historyUserMsg.CreatedAt = promptCreatedAt
historyUserMsg.UpdatedAt = promptCreatedAt
@@ -235,6 +236,7 @@ func (h *ChatHandler) sendOpenAiMessage(
Content: message.Content,
Tokens: totalTokens,
UseContext: useContext,
Model: req.Model,
}
historyReplyMsg.CreatedAt = replyCreatedAt
historyReplyMsg.UpdatedAt = replyCreatedAt
@@ -260,6 +262,7 @@ func (h *ChatHandler) sendOpenAiMessage(
} else {
chatItem.Title = prompt
}
chatItem.Model = req.Model
h.db.Create(&chatItem)
}
}

View File

@@ -160,6 +160,7 @@ func (h *ChatHandler) sendQWenMessage(
Content: template.HTMLEscapeString(prompt),
Tokens: promptToken,
UseContext: true,
Model: req.Model,
}
historyUserMsg.CreatedAt = promptCreatedAt
historyUserMsg.UpdatedAt = promptCreatedAt
@@ -181,6 +182,7 @@ func (h *ChatHandler) sendQWenMessage(
Content: message.Content,
Tokens: totalTokens,
UseContext: true,
Model: req.Model,
}
historyReplyMsg.CreatedAt = replyCreatedAt
historyReplyMsg.UpdatedAt = replyCreatedAt
@@ -205,6 +207,7 @@ func (h *ChatHandler) sendQWenMessage(
} else {
chatItem.Title = prompt
}
chatItem.Model = req.Model
h.db.Create(&chatItem)
}
}

View File

@@ -198,6 +198,7 @@ func (h *ChatHandler) sendXunFeiMessage(
Content: template.HTMLEscapeString(prompt),
Tokens: promptToken,
UseContext: true,
Model: req.Model,
}
historyUserMsg.CreatedAt = promptCreatedAt
historyUserMsg.UpdatedAt = promptCreatedAt
@@ -219,6 +220,7 @@ func (h *ChatHandler) sendXunFeiMessage(
Content: message.Content,
Tokens: totalTokens,
UseContext: true,
Model: req.Model,
}
historyReplyMsg.CreatedAt = replyCreatedAt
historyReplyMsg.UpdatedAt = replyCreatedAt
@@ -243,6 +245,7 @@ func (h *ChatHandler) sendXunFeiMessage(
} else {
chatItem.Title = prompt
}
chatItem.Model = req.Model
h.db.Create(&chatItem)
}
}

View File

@@ -86,19 +86,20 @@ func (h *MidJourneyHandler) Client(c *gin.Context) {
// Image 创建一个绘画任务
func (h *MidJourneyHandler) Image(c *gin.Context) {
var data struct {
SessionId string `json:"session_id"`
Prompt string `json:"prompt"`
NegPrompt string `json:"neg_prompt"`
Rate string `json:"rate"`
Model string `json:"model"`
Chaos int `json:"chaos"`
Raw bool `json:"raw"`
Seed int64 `json:"seed"`
Stylize int `json:"stylize"`
Img string `json:"img"`
Tile bool `json:"tile"`
Quality float32 `json:"quality"`
Weight float32 `json:"weight"`
SessionId string `json:"session_id"`
TaskType string `json:"task_type"`
Prompt string `json:"prompt"`
NegPrompt string `json:"neg_prompt"`
Rate string `json:"rate"`
Model string `json:"model"`
Chaos int `json:"chaos"`
Raw bool `json:"raw"`
Seed int64 `json:"seed"`
Stylize int `json:"stylize"`
ImgArr []string `json:"img_arr"`
Tile bool `json:"tile"`
Quality float32 `json:"quality"`
Weight float32 `json:"weight"`
}
if err := c.ShouldBindJSON(&data); err != nil {
resp.ERROR(c, types.InvalidArgs)
@@ -121,11 +122,8 @@ func (h *MidJourneyHandler) Image(c *gin.Context) {
if data.Chaos > 0 && !strings.Contains(prompt, "--c") && !strings.Contains(prompt, "--chaos") {
prompt += fmt.Sprintf(" --c %d", data.Chaos)
}
if data.Img != "" {
prompt = fmt.Sprintf("%s %s", data.Img, prompt)
if data.Weight > 0 {
prompt += fmt.Sprintf(" --iw %f", data.Weight)
}
if data.Weight > 0 {
prompt += fmt.Sprintf(" --iw %f", data.Weight)
}
if data.Raw {
prompt += " --style raw"
@@ -143,6 +141,11 @@ func (h *MidJourneyHandler) Image(c *gin.Context) {
prompt += fmt.Sprintf(" %s", data.Model)
}
// 处理融图和换脸的提示词
if data.TaskType == types.TaskSwapFace.String() || data.TaskType == types.TaskBlend.String() {
prompt = fmt.Sprintf("%s:%s", data.TaskType, strings.Join(data.ImgArr, ","))
}
idValue, _ := c.Get(types.LoginUserID)
userId := utils.IntValue(utils.InterfaceToString(idValue), 0)
// generate task id
@@ -152,28 +155,37 @@ func (h *MidJourneyHandler) Image(c *gin.Context) {
return
}
job := model.MidJourneyJob{
Type: types.TaskImage.String(),
Type: data.TaskType,
UserId: userId,
TaskId: taskId,
Progress: 0,
Prompt: prompt,
CreatedAt: time.Now(),
}
if data.TaskType == types.TaskBlend.String() {
data.Prompt = "融图:" + strings.Join(data.ImgArr, ",")
} else if data.TaskType == types.TaskSwapFace.String() {
data.Prompt = "换脸:" + strings.Join(data.ImgArr, ",")
}
if res := h.db.Create(&job); res.Error != nil || res.RowsAffected == 0 {
resp.ERROR(c, "添加任务失败:"+res.Error.Error())
return
}
h.pool.PushTask(types.MjTask{
Id: int(job.Id),
Id: job.Id,
TaskId: taskId,
SessionId: data.SessionId,
Type: types.TaskImage,
Prompt: fmt.Sprintf("%s %s", taskId, prompt),
Type: types.TaskType(data.TaskType),
Prompt: prompt,
UserId: userId,
ImgArr: data.ImgArr,
})
client := h.pool.Clients.Get(uint(job.UserId))
_ = client.Send([]byte("Task Updated"))
if client != nil {
_ = client.Send([]byte("Task Updated"))
}
// update user's img calls
h.db.Model(&model.User{}).Where("id = ?", job.UserId).UpdateColumn("img_calls", gorm.Expr("img_calls - ?", 1))
@@ -222,7 +234,7 @@ func (h *MidJourneyHandler) Upscale(c *gin.Context) {
}
h.pool.PushTask(types.MjTask{
Id: int(job.Id),
Id: job.Id,
SessionId: data.SessionId,
Type: types.TaskUpscale,
Prompt: data.Prompt,
@@ -234,7 +246,9 @@ func (h *MidJourneyHandler) Upscale(c *gin.Context) {
})
client := h.pool.Clients.Get(uint(job.UserId))
_ = client.Send([]byte("Task Updated"))
if client != nil {
_ = client.Send([]byte("Task Updated"))
}
resp.SUCCESS(c)
}
@@ -270,7 +284,7 @@ func (h *MidJourneyHandler) Variation(c *gin.Context) {
}
h.pool.PushTask(types.MjTask{
Id: int(job.Id),
Id: job.Id,
SessionId: data.SessionId,
Type: types.TaskVariation,
Prompt: data.Prompt,
@@ -282,7 +296,9 @@ func (h *MidJourneyHandler) Variation(c *gin.Context) {
})
client := h.pool.Clients.Get(uint(job.UserId))
_ = client.Send([]byte("Task Updated"))
if client != nil {
_ = client.Send([]byte("Task Updated"))
}
// update user's img calls
h.db.Model(&model.User{}).Where("id = ?", job.UserId).UpdateColumn("img_calls", gorm.Expr("img_calls - ?", 1))
@@ -329,19 +345,22 @@ func (h *MidJourneyHandler) JobList(c *gin.Context) {
continue
}
// 失败的任务直接删除
if job.Progress == -1 {
h.db.Delete(&model.MidJourneyJob{Id: job.Id})
jobs = append(jobs, job)
continue
}
if item.Progress < 100 && item.ImgURL == "" && item.OrgURL != "" {
// 正在运行中任务使用代理访问图片
if h.App.Config.ImgCdnURL != "" {
job.ImgURL = strings.ReplaceAll(job.OrgURL, "https://cdn.discordapp.com", h.App.Config.ImgCdnURL)
} else {
// discord 服务器图片需要使用代理转发图片数据流
if strings.HasPrefix(item.OrgURL, "https://cdn.discordapp.com") {
image, err := utils.DownloadImage(item.OrgURL, h.App.Config.ProxyURL)
if err == nil {
job.ImgURL = "data:image/png;base64," + base64.StdEncoding.EncodeToString(image)
}
} else {
job.ImgURL = job.OrgURL
}
}
@@ -376,7 +395,9 @@ func (h *MidJourneyHandler) Remove(c *gin.Context) {
}
client := h.pool.Clients.Get(data.UserId)
_ = client.Send([]byte("Task Updated"))
if client != nil {
_ = client.Send([]byte("Task Updated"))
}
resp.SUCCESS(c)
}

View File

@@ -76,6 +76,12 @@ func (h *PaymentHandler) DoPay(c *gin.Context) {
return
}
// fix: 这里先检查一下订单状态,如果已经支付了,就直接返回
if order.Status == types.OrderPaidSuccess {
resp.ERROR(c, "This order had been paid, please do not pay twice")
return
}
// 更新扫码状态
h.db.Model(&order).UpdateColumn("status", types.OrderScanned)
if payWay == "alipay" { // 支付宝

View File

@@ -11,6 +11,8 @@ import (
"chatplus/utils/resp"
"encoding/base64"
"fmt"
"github.com/gorilla/websocket"
"net/http"
"time"
"github.com/gin-gonic/gin"
@@ -36,6 +38,27 @@ func NewSdJobHandler(app *core.AppServer, db *gorm.DB, pool *sd.ServicePool, man
return &h
}
// Client WebSocket 客户端,用于通知任务状态变更
func (h *SdJobHandler) 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)
c.Abort()
return
}
userId := h.GetInt(c, "user_id", 0)
if userId == 0 {
logger.Info("Invalid user ID")
c.Abort()
return
}
client := types.NewWsClient(ws)
h.pool.Clients.Put(uint(userId), client)
logger.Infof("New websocket connected, IP: %s", c.RemoteIP())
}
func (h *SdJobHandler) checkLimits(c *gin.Context) bool {
user, err := utils.GetLoginUser(c, h.db)
if err != nil {

View File

@@ -35,6 +35,7 @@ func (h *UploadHandler) Upload(c *gin.Context) {
res := h.db.Create(&model.File{
UserId: userId,
Name: file.Name,
ObjKey: file.ObjKey,
URL: file.URL,
Ext: file.Ext,
Size: file.Size,
@@ -52,7 +53,7 @@ func (h *UploadHandler) List(c *gin.Context) {
userId := h.GetLoginUserId(c)
var items []model.File
var files = make([]vo.File, 0)
h.db.Debug().Where("user_id = ?", userId).Find(&items)
h.db.Where("user_id = ?", userId).Find(&items)
if len(items) > 0 {
for _, v := range items {
var file vo.File
@@ -68,3 +69,29 @@ func (h *UploadHandler) List(c *gin.Context) {
resp.SUCCESS(c, files)
}
// Remove remove files
func (h *UploadHandler) Remove(c *gin.Context) {
userId := h.GetLoginUserId(c)
id := h.GetInt(c, "id", 0)
var file model.File
tx := h.db.Where("user_id = ? AND id = ?", userId, id).First(&file)
if tx.Error != nil || file.Id == 0 {
resp.ERROR(c, "file not existed")
return
}
// remove database
tx = h.db.Model(&model.File{}).Delete("id = ?", id)
if tx.Error != nil || tx.RowsAffected == 0 {
resp.ERROR(c, "failed to update database")
return
}
// remove files
objectKey := file.ObjKey
if objectKey == "" {
objectKey = file.URL
}
_ = h.uploaderManager.GetUploadHandler().Delete(objectKey)
resp.SUCCESS(c)
}

View File

@@ -218,6 +218,7 @@ func main() {
fx.Invoke(func(s *core.AppServer, h *handler.UploadHandler) {
s.Engine.POST("/api/upload", h.Upload)
s.Engine.GET("/api/upload/list", h.List)
s.Engine.GET("/api/upload/remove", h.Remove)
}),
fx.Invoke(func(s *core.AppServer, h *handler.SmsHandler) {
group := s.Engine.Group("/api/sms/")

View File

@@ -2,6 +2,7 @@ package mj
import (
"chatplus/core/types"
"errors"
"fmt"
"time"
@@ -11,13 +12,12 @@ import (
// MidJourney client
type Client struct {
client *req.Client
Config types.MidJourneyConfig
imgCdnURL string
apiURL string
client *req.Client
Config types.MidJourneyConfig
apiURL string
}
func NewClient(config types.MidJourneyConfig, proxy string, imgCdnURL string) *Client {
func NewClient(config types.MidJourneyConfig, proxy string) *Client {
client := req.C().SetTimeout(10 * time.Second)
var apiURL string
// set proxy URL
@@ -30,10 +30,10 @@ func NewClient(config types.MidJourneyConfig, proxy string, imgCdnURL string) *C
}
}
return &Client{client: client, Config: config, apiURL: apiURL, imgCdnURL: imgCdnURL}
return &Client{client: client, Config: config, apiURL: apiURL}
}
func (c *Client) Imagine(prompt string) error {
func (c *Client) Imagine(task types.MjTask) error {
interactionsReq := &InteractionsRequest{
Type: 2,
ApplicationID: ApplicationID,
@@ -49,7 +49,7 @@ func (c *Client) Imagine(prompt string) error {
{
"type": 3,
"name": "prompt",
"value": prompt,
"value": fmt.Sprintf("%s %s", task.TaskId, task.Prompt),
},
},
"application_command": map[string]any{
@@ -88,20 +88,28 @@ func (c *Client) Imagine(prompt string) error {
return nil
}
func (c *Client) Blend(task types.MjTask) error {
return errors.New("function not implemented")
}
func (c *Client) SwapFace(task types.MjTask) error {
return errors.New("function not implemented")
}
// Upscale 放大指定的图片
func (c *Client) Upscale(index int, messageId string, hash string) error {
func (c *Client) Upscale(task types.MjTask) error {
flags := 0
interactionsReq := &InteractionsRequest{
Type: 3,
ApplicationID: ApplicationID,
GuildID: c.Config.GuildId,
ChannelID: c.Config.ChanelId,
MessageFlags: &flags,
MessageID: &messageId,
MessageFlags: flags,
MessageID: task.MessageId,
SessionID: SessionID,
Data: map[string]any{
"component_type": 2,
"custom_id": fmt.Sprintf("MJ::JOB::upsample::%d::%s", index, hash),
"custom_id": fmt.Sprintf("MJ::JOB::upsample::%d::%s", task.Index, task.MessageHash),
},
Nonce: fmt.Sprintf("%d", time.Now().UnixNano()),
}
@@ -120,19 +128,19 @@ func (c *Client) Upscale(index int, messageId string, hash string) error {
}
// Variation 以指定的图片的视角进行变换再创作,注意需要在对应的频道中关闭 Remix 变换,否则 Variation 指令将不会生效
func (c *Client) Variation(index int, messageId string, hash string) error {
func (c *Client) Variation(task types.MjTask) error {
flags := 0
interactionsReq := &InteractionsRequest{
Type: 3,
ApplicationID: ApplicationID,
GuildID: c.Config.GuildId,
ChannelID: c.Config.ChanelId,
MessageFlags: &flags,
MessageID: &messageId,
MessageFlags: flags,
MessageID: task.MessageId,
SessionID: SessionID,
Data: map[string]any{
"component_type": 2,
"custom_id": fmt.Sprintf("MJ::JOB::variation::%d::%s", index, hash),
"custom_id": fmt.Sprintf("MJ::JOB::variation::%d::%s", task.Index, task.MessageHash),
},
Nonce: fmt.Sprintf("%d", time.Now().UnixNano()),
}

View File

@@ -3,10 +3,14 @@ package plus
import (
"chatplus/core/types"
logger2 "chatplus/logger"
"chatplus/utils"
"encoding/base64"
"errors"
"fmt"
"io"
"github.com/gin-gonic/gin"
"github.com/imroc/req/v3"
)
@@ -15,16 +19,24 @@ var logger = logger2.GetLogger()
// Client MidJourney Plus Client
type Client struct {
Config types.MidJourneyPlusConfig
apiURL string
}
func NewClient(config types.MidJourneyPlusConfig) *Client {
return &Client{Config: config}
var apiURL string
if config.CdnURL != "" {
apiURL = config.CdnURL
} else {
apiURL = config.ApiURL
}
return &Client{Config: config, apiURL: apiURL}
}
type ImageReq struct {
BotType string `json:"botType"`
Prompt string `json:"prompt"`
Base64Array []interface{} `json:"base64Array,omitempty"`
BotType string `json:"botType"`
Prompt string `json:"prompt,omitempty"`
Dimensions string `json:"dimensions,omitempty"`
Base64Array []string `json:"base64Array,omitempty"`
AccountFilter struct {
InstanceId string `json:"instanceId"`
Modes []interface{} `json:"modes"`
@@ -49,12 +61,114 @@ type ErrRes struct {
} `json:"error"`
}
func (c *Client) Imagine(prompt string) (ImageRes, error) {
apiURL := fmt.Sprintf("%s/mj-fast/mj/submit/imagine", c.Config.ApiURL)
func (c *Client) Imagine(task types.MjTask) (ImageRes, error) {
apiURL := fmt.Sprintf("%s/mj-fast/mj/submit/imagine", c.apiURL)
body := ImageReq{
BotType: "MID_JOURNEY",
Prompt: prompt,
NotifyHook: c.Config.NotifyURL,
BotType: "MID_JOURNEY",
Prompt: task.Prompt,
NotifyHook: c.Config.NotifyURL,
Base64Array: make([]string, 0),
}
// 生成图片 Base64 编码
if len(task.ImgArr) > 0 {
imageData, err := utils.DownloadImage(task.ImgArr[0], "")
if err != nil {
logger.Error("error with download image: ", err)
} else {
body.Base64Array = append(body.Base64Array, "data:image/png;base64,"+base64.StdEncoding.EncodeToString(imageData))
}
}
var res ImageRes
var errRes ErrRes
r, err := req.C().R().
SetHeader("Authorization", "Bearer "+c.Config.ApiKey).
SetBody(body).
SetSuccessResult(&res).
SetErrorResult(&errRes).
Post(apiURL)
if err != nil {
return ImageRes{}, fmt.Errorf("请求 API 出错:%v", err)
}
if r.IsErrorState() {
errStr, _ := io.ReadAll(r.Body)
return ImageRes{}, fmt.Errorf("API 返回错误:%s%v", errRes.Error.Message, string(errStr))
}
return res, nil
}
// Blend 融图
func (c *Client) Blend(task types.MjTask) (ImageRes, error) {
apiURL := fmt.Sprintf("%s/mj-fast/mj/submit/blend", c.apiURL)
body := ImageReq{
BotType: "MID_JOURNEY",
Dimensions: "SQUARE",
NotifyHook: c.Config.NotifyURL,
Base64Array: make([]string, 0),
}
// 生成图片 Base64 编码
if len(task.ImgArr) > 0 {
for _, imgURL := range task.ImgArr {
imageData, err := utils.DownloadImage(imgURL, "")
if err != nil {
logger.Error("error with download image: ", err)
} else {
body.Base64Array = append(body.Base64Array, "data:image/png;base64,"+base64.StdEncoding.EncodeToString(imageData))
}
}
}
var res ImageRes
var errRes ErrRes
r, err := req.C().R().
SetHeader("Authorization", "Bearer "+c.Config.ApiKey).
SetBody(body).
SetSuccessResult(&res).
SetErrorResult(&errRes).
Post(apiURL)
if err != nil {
errStr, _ := io.ReadAll(r.Body)
return ImageRes{}, fmt.Errorf("请求 API 出错:%v%v", err, string(errStr))
}
if r.IsErrorState() {
return ImageRes{}, fmt.Errorf("API 返回错误:%s", errRes.Error.Message)
}
return res, nil
}
// SwapFace 换脸
func (c *Client) SwapFace(task types.MjTask) (ImageRes, error) {
apiURL := fmt.Sprintf("%s/mj-fast/mj/insight-face/swap", c.apiURL)
// 生成图片 Base64 编码
if len(task.ImgArr) != 2 {
return ImageRes{}, errors.New("参数错误必须上传2张图片")
}
var sourceBase64 string
var targetBase64 string
imageData, err := utils.DownloadImage(task.ImgArr[0], "")
if err != nil {
logger.Error("error with download image: ", err)
} else {
sourceBase64 = "data:image/png;base64," + base64.StdEncoding.EncodeToString(imageData)
}
imageData, err = utils.DownloadImage(task.ImgArr[1], "")
if err != nil {
logger.Error("error with download image: ", err)
} else {
targetBase64 = "data:image/png;base64," + base64.StdEncoding.EncodeToString(imageData)
}
body := gin.H{
"sourceBase64": sourceBase64,
"targetBase64": targetBase64,
"accountFilter": gin.H{
"instanceId": "",
},
"notifyHook": c.Config.NotifyURL,
"state": "",
}
var res ImageRes
var errRes ErrRes
@@ -77,13 +191,13 @@ func (c *Client) Imagine(prompt string) (ImageRes, error) {
}
// Upscale 放大指定的图片
func (c *Client) Upscale(index int, messageId string, hash string) (ImageRes, error) {
func (c *Client) Upscale(task types.MjTask) (ImageRes, error) {
body := map[string]string{
"customId": fmt.Sprintf("MJ::JOB::upsample::%d::%s", index, hash),
"taskId": messageId,
"customId": fmt.Sprintf("MJ::JOB::upsample::%d::%s", task.Index, task.MessageHash),
"taskId": task.MessageId,
"notifyHook": c.Config.NotifyURL,
}
apiURL := fmt.Sprintf("%s/mj/submit/action", c.Config.ApiURL)
apiURL := fmt.Sprintf("%s/mj/submit/action", c.apiURL)
var res ImageRes
var errRes ErrRes
r, err := req.C().R().
@@ -104,13 +218,13 @@ func (c *Client) Upscale(index int, messageId string, hash string) (ImageRes, er
}
// Variation 以指定的图片的视角进行变换再创作,注意需要在对应的频道中关闭 Remix 变换,否则 Variation 指令将不会生效
func (c *Client) Variation(index int, messageId string, hash string) (ImageRes, error) {
func (c *Client) Variation(task types.MjTask) (ImageRes, error) {
body := map[string]string{
"customId": fmt.Sprintf("MJ::JOB::variation::%d::%s", index, hash),
"taskId": messageId,
"customId": fmt.Sprintf("MJ::JOB::variation::%d::%s", task.Index, task.MessageHash),
"taskId": task.MessageId,
"notifyHook": c.Config.NotifyURL,
}
apiURL := fmt.Sprintf("%s/mj/submit/action", c.Config.ApiURL)
apiURL := fmt.Sprintf("%s/mj/submit/action", c.apiURL)
var res ImageRes
var errRes ErrRes
r, err := req.C().R().
@@ -156,7 +270,7 @@ type QueryRes struct {
}
func (c *Client) QueryTask(taskId string) (QueryRes, error) {
apiURL := fmt.Sprintf("%s/mj/task/%s/fetch", c.Config.ApiURL, taskId)
apiURL := fmt.Sprintf("%s/mj/task/%s/fetch", c.apiURL, taskId)
var res QueryRes
r, err := req.C().R().SetHeader("Authorization", "Bearer "+c.Config.ApiKey).
SetSuccessResult(&res).

View File

@@ -58,31 +58,42 @@ func (s *Service) Run() {
}
// if it's reference message, check if it's this channel's message
if task.ChannelId != "" && task.ChannelId != s.Name {
logger.Debugf("handle other service task, name: %s, channel_id: %s, drop it.", s.Name, task.ChannelId)
s.taskQueue.RPush(task)
time.Sleep(time.Second)
continue
}
//if task.ChannelId != "" && task.ChannelId != s.Name {
// logger.Debugf("handle other service task, name: %s, channel_id: %s, drop it.", s.Name, task.ChannelId)
// s.taskQueue.RPush(task)
// time.Sleep(time.Second)
// continue
//}
logger.Infof("%s handle a new MidJourney task: %+v", s.Name, task)
var res ImageRes
switch task.Type {
case types.TaskImage:
index := strings.Index(task.Prompt, " ")
res, err = s.Client.Imagine(task.Prompt[index+1:])
res, err = s.Client.Imagine(task)
break
case types.TaskUpscale:
res, err = s.Client.Upscale(task.Index, task.MessageId, task.MessageHash)
res, err = s.Client.Upscale(task)
break
case types.TaskVariation:
res, err = s.Client.Variation(task.Index, task.MessageId, task.MessageHash)
res, err = s.Client.Variation(task)
break
case types.TaskBlend:
res, err = s.Client.Blend(task)
break
case types.TaskSwapFace:
res, err = s.Client.SwapFace(task)
break
}
var job model.MidJourneyJob
s.db.Where("id = ?", task.Id).First(&job)
if err != nil || (res.Code != 1 && res.Code != 22) {
logger.Error("绘画任务执行失败:", err)
errMsg := fmt.Sprintf("%v,%s", err, res.Description)
logger.Error("绘画任务执行失败:", errMsg)
job.Progress = -1
job.ErrMsg = errMsg
// update the task progress
s.db.Model(&model.MidJourneyJob{Id: uint(task.Id)}).UpdateColumn("progress", -1)
s.db.Updates(&job)
// 任务失败,通知前端
s.notifyQueue.RPush(task.UserId)
// restore img_call quota
@@ -95,14 +106,12 @@ func (s *Service) Run() {
}
logger.Infof("任务提交成功:%+v", res)
// lock the task until the execute timeout
s.taskStartTimes[task.Id] = time.Now()
s.taskStartTimes[int(task.Id)] = time.Now()
atomic.AddInt32(&s.HandledTaskNum, 1)
// 更新任务 ID/频道
s.db.Model(&model.MidJourneyJob{}).Where("id = ?", task.Id).UpdateColumns(map[string]interface{}{
"task_id": res.Result,
"channel_id": s.Name,
})
job.TaskId = res.Result
job.ChannelId = s.Name
s.db.Updates(&job)
}
}
@@ -142,26 +151,54 @@ type CBReq struct {
} `json:"properties"`
}
func (s *Service) Notify(data CBReq, job model.MidJourneyJob) error {
job.Progress = utils.IntValue(strings.Replace(data.Progress, "%", "", 1), 0)
job.Prompt = data.Properties.FinalPrompt
if data.ImageUrl != "" {
job.OrgURL = data.ImageUrl
}
job.UseProxy = true
job.MessageId = data.Id
logger.Debugf("JOB: %+v", job)
res := s.db.Updates(&job)
if res.Error != nil {
return fmt.Errorf("error with update job: %v", res.Error)
func (s *Service) Notify(job model.MidJourneyJob) error {
task, err := s.Client.QueryTask(job.TaskId)
if err != nil {
return err
}
if data.Status == "SUCCESS" {
// 任务执行失败了
if task.FailReason != "" {
s.db.Model(&model.MidJourneyJob{Id: job.Id}).UpdateColumns(map[string]interface{}{
"progress": -1,
"err_msg": task.FailReason,
})
return fmt.Errorf("task failed: %v", task.FailReason)
}
if len(task.Buttons) > 0 {
job.Hash = GetImageHash(task.Buttons[0].CustomId)
}
oldProgress := job.Progress
job.Progress = utils.IntValue(strings.Replace(task.Progress, "%", "", 1), 0)
job.Prompt = task.PromptEn
if task.ImageUrl != "" {
if s.Client.Config.CdnURL != "" {
job.OrgURL = strings.Replace(task.ImageUrl, s.Client.Config.ApiURL, s.Client.Config.CdnURL, 1)
} else {
job.OrgURL = task.ImageUrl
}
}
job.MessageId = task.Id
tx := s.db.Updates(&job)
if tx.Error != nil {
return fmt.Errorf("error with update database: %v", tx.Error)
}
if task.Status == "SUCCESS" {
// release lock task
atomic.AddInt32(&s.HandledTaskNum, -1)
}
s.notifyQueue.RPush(job.UserId)
// 通知前端更新任务进度
if oldProgress != job.Progress {
s.notifyQueue.RPush(job.UserId)
}
return nil
}
func GetImageHash(action string) string {
split := strings.Split(action, "::")
if len(split) > 5 {
return split[4]
}
return split[len(split)-1]
}

View File

@@ -6,11 +6,9 @@ import (
"chatplus/service/oss"
"chatplus/store"
"chatplus/store/model"
"chatplus/utils"
"fmt"
"github.com/go-redis/redis/v8"
"strings"
"sync/atomic"
"time"
"gorm.io/gorm"
@@ -35,9 +33,6 @@ func NewServicePool(db *gorm.DB, redisCli *redis.Client, manager *oss.UploaderMa
if config.Enabled == false {
continue
}
if config.ApiURL != "https://gpt.bemore.lol" && config.ApiURL != "https://api.chat-plus.net" {
config.ApiURL = "https://api.chat-plus.net"
}
client := plus.NewClient(config)
name := fmt.Sprintf("mj-service-plus-%d", k)
servicePlus := plus.NewService(name, taskQueue, notifyQueue, 10, 600, db, client)
@@ -54,7 +49,7 @@ func NewServicePool(db *gorm.DB, redisCli *redis.Client, manager *oss.UploaderMa
continue
}
// create mj client
client := NewClient(config, appConfig.ProxyURL, appConfig.ImgCdnURL)
client := NewClient(config, appConfig.ProxyURL)
name := fmt.Sprintf("MjService-%d", k)
// create mj service
@@ -98,6 +93,9 @@ func (p *ServicePool) CheckTaskNotify() {
continue
}
client := p.Clients.Get(userId)
if client == nil {
continue
}
err = client.Send([]byte("Task Updated"))
if err != nil {
continue
@@ -120,30 +118,33 @@ func (p *ServicePool) DownloadImages() {
if v.OrgURL == "" {
continue
}
logger.Infof("try to download image: %s", v.OrgURL)
var imgURL string
var err error
if v.UseProxy {
if servicePlus := p.getServicePlus(v.ChannelId); servicePlus != nil {
task, _ := servicePlus.Client.QueryTask(v.TaskId)
if task.ImageUrl != "" {
imgURL, err = p.uploaderManager.GetUploadHandler().PutImg(task.ImageUrl, false)
}
if len(task.Buttons) > 0 {
v.Hash = getImageHash(task.Buttons[0].CustomId)
}
if servicePlus := p.getServicePlus(v.ChannelId); servicePlus != nil {
task, _ := servicePlus.Client.QueryTask(v.TaskId)
if len(task.Buttons) > 0 {
v.Hash = plus.GetImageHash(task.Buttons[0].CustomId)
}
imgURL, err = p.uploaderManager.GetUploadHandler().PutImg(v.OrgURL, false)
} else {
imgURL, err = p.uploaderManager.GetUploadHandler().PutImg(v.OrgURL, true)
}
if err != nil {
logger.Error("error with download image: ", err)
logger.Errorf("error with download image %s, %v", v.OrgURL, err)
continue
} else {
logger.Infof("download image %s successfully.", v.OrgURL)
}
v.ImgURL = imgURL
p.db.Updates(&v)
client := p.Clients.Get(uint(v.UserId))
if client == nil {
continue
}
err = client.Send([]byte("Task Updated"))
if err != nil {
continue
@@ -167,7 +168,7 @@ func (p *ServicePool) HasAvailableService() bool {
}
func (p *ServicePool) Notify(data plus.CBReq) error {
logger.Infof("收到任务回调:%+v", data)
logger.Debugf("收到任务回调:%+v", data)
var job model.MidJourneyJob
res := p.db.Where("task_id = ?", data.Id).First(&job)
if res.Error != nil {
@@ -179,7 +180,7 @@ func (p *ServicePool) Notify(data plus.CBReq) error {
return nil
}
if servicePlus := p.getServicePlus(job.ChannelId); servicePlus != nil {
return servicePlus.Notify(data, job)
return servicePlus.Notify(job)
}
return nil
@@ -190,7 +191,7 @@ func (p *ServicePool) SyncTaskProgress() {
go func() {
var items []model.MidJourneyJob
for {
res := p.db.Where("progress < ?", 100).Find(&items)
res := p.db.Where("progress >= ? AND progress < ?", 0, 100).Find(&items)
if res.Error != nil {
continue
}
@@ -211,32 +212,7 @@ func (p *ServicePool) SyncTaskProgress() {
}
if servicePlus := p.getServicePlus(v.ChannelId); servicePlus != nil {
task, err := servicePlus.Client.QueryTask(v.TaskId)
if err != nil {
continue
}
if len(task.Buttons) > 0 {
v.Hash = getImageHash(task.Buttons[0].CustomId)
}
oldProgress := v.Progress
v.Progress = utils.IntValue(strings.Replace(task.Progress, "%", "", 1), 0)
v.Prompt = task.PromptEn
if task.ImageUrl != "" {
v.OrgURL = task.ImageUrl
}
v.UseProxy = true
v.MessageId = task.Id
p.db.Updates(&v)
if task.Status == "SUCCESS" {
// release lock task
atomic.AddInt32(&servicePlus.HandledTaskNum, -1)
}
// 通知前端更新任务进度
if oldProgress != v.Progress {
p.notifyQueue.RPush(v.UserId)
}
_ = servicePlus.Notify(v)
}
}
@@ -255,11 +231,3 @@ func (p *ServicePool) getServicePlus(name string) *plus.Service {
}
return nil
}
func getImageHash(action string) string {
split := strings.Split(action, "::")
if len(split) > 5 {
return split[4]
}
return split[len(split)-1]
}

View File

@@ -65,20 +65,29 @@ func (s *Service) Run() {
logger.Infof("%s handle a new MidJourney task: %+v", s.name, task)
switch task.Type {
case types.TaskImage:
err = s.client.Imagine(task.Prompt)
err = s.client.Imagine(task)
break
case types.TaskUpscale:
err = s.client.Upscale(task.Index, task.MessageId, task.MessageHash)
err = s.client.Upscale(task)
break
case types.TaskVariation:
err = s.client.Variation(task.Index, task.MessageId, task.MessageHash)
err = s.client.Variation(task)
break
case types.TaskBlend:
err = s.client.Blend(task)
break
case types.TaskSwapFace:
err = s.client.SwapFace(task)
break
}
if err != nil {
logger.Error("绘画任务执行失败:", err)
logger.Error("绘画任务执行失败:", err.Error())
// update the task progress
s.db.Model(&model.MidJourneyJob{Id: uint(task.Id)}).UpdateColumn("progress", -1)
s.db.Model(&model.MidJourneyJob{Id: uint(task.Id)}).UpdateColumns(map[string]interface{}{
"progress": -1,
"err_msg": err.Error(),
})
s.notifyQueue.RPush(task.UserId)
// restore img_call quota
if task.Type.String() != types.TaskUpscale.String() {
@@ -88,7 +97,7 @@ func (s *Service) Run() {
}
// lock the task until the execute timeout
s.taskStartTimes[task.Id] = time.Now()
s.taskStartTimes[int(task.Id)] = time.Now()
atomic.AddInt32(&s.handledTaskNum, 1)
}
@@ -128,6 +137,12 @@ func (s *Service) Notify(data CBReq) {
} else {
tx = tx.Where("task_id = ?", split[0])
}
// fixed: 修复 U/V 操作任务混淆覆盖的 Bug
if strings.Contains(data.Prompt, "** - Image #") { // for upscale
tx = tx.Where("type = ?", types.TaskUpscale.String())
} else if strings.Contains(data.Prompt, "** - Variations (Strong)") { // for Variations
tx = tx.Where("type = ?", types.TaskVariation.String())
}
res = tx.First(&job)
if res.Error != nil {
logger.Warn("非法任务:", res.Error)
@@ -143,7 +158,7 @@ func (s *Service) Notify(data CBReq) {
job.OrgURL = data.Image.URL
if s.client.Config.UseCDN {
job.UseProxy = true
job.ImgURL = strings.ReplaceAll(data.Image.URL, "https://cdn.discordapp.com", s.client.imgCdnURL)
job.ImgURL = strings.ReplaceAll(data.Image.URL, "https://cdn.discordapp.com", s.client.Config.ImgCdnURL)
}
res = s.db.Updates(&job)

View File

@@ -8,8 +8,8 @@ const (
type InteractionsRequest struct {
Type int `json:"type"`
ApplicationID string `json:"application_id"`
MessageFlags *int `json:"message_flags,omitempty"`
MessageID *string `json:"message_id,omitempty"`
MessageFlags int `json:"message_flags,omitempty"`
MessageID string `json:"message_id,omitempty"`
GuildID string `json:"guild_id"`
ChannelID string `json:"channel_id"`
SessionID string `json:"session_id"`

View File

@@ -5,11 +5,13 @@ import (
"chatplus/core/types"
"chatplus/utils"
"fmt"
"github.com/aliyun/aliyun-oss-go-sdk/oss"
"github.com/gin-gonic/gin"
"net/url"
"path/filepath"
"strings"
"time"
"github.com/aliyun/aliyun-oss-go-sdk/oss"
"github.com/gin-gonic/gin"
)
type AliYunOss struct {
@@ -66,10 +68,11 @@ func (s AliYunOss) PutFile(ctx *gin.Context, name string) (File, error) {
}
return File{
Name: file.Filename,
URL: fmt.Sprintf("%s/%s", s.config.Domain, objectKey),
Ext: fileExt,
Size: file.Size,
Name: file.Filename,
ObjKey: objectKey,
URL: fmt.Sprintf("%s/%s", s.config.Domain, objectKey),
Ext: fileExt,
Size: file.Size,
}, nil
}
@@ -88,7 +91,7 @@ func (s AliYunOss) PutImg(imageURL string, useProxy bool) (string, error) {
if err != nil {
return "", fmt.Errorf("error with parse image URL: %v", err)
}
fileExt := filepath.Ext(parse.Path)
fileExt := utils.GetImgExt(parse.Path)
objectKey := fmt.Sprintf("%s/%d%s", s.config.SubDir, time.Now().UnixMicro(), fileExt)
// 上传文件字节数据
err = s.bucket.PutObject(objectKey, bytes.NewReader(imageData))
@@ -99,9 +102,14 @@ func (s AliYunOss) PutImg(imageURL string, useProxy bool) (string, error) {
}
func (s AliYunOss) Delete(fileURL string) error {
objectName := filepath.Base(fileURL)
key := fmt.Sprintf("%s/%s", s.config.SubDir, objectName)
return s.bucket.DeleteObject(key)
var objectKey string
if strings.HasPrefix(fileURL, "http") {
filename := filepath.Base(fileURL)
objectKey = fmt.Sprintf("%s/%s", s.config.SubDir, filename)
} else {
objectKey = fileURL
}
return s.bucket.DeleteObject(objectKey)
}
var _ Uploader = AliYunOss{}

View File

@@ -4,11 +4,12 @@ import (
"chatplus/core/types"
"chatplus/utils"
"fmt"
"github.com/gin-gonic/gin"
"net/url"
"os"
"path/filepath"
"strings"
"github.com/gin-gonic/gin"
)
type LocalStorage struct {
@@ -29,7 +30,7 @@ func (s LocalStorage) PutFile(ctx *gin.Context, name string) (File, error) {
return File{}, fmt.Errorf("error with get form: %v", err)
}
path, err := utils.GenUploadPath(s.config.BasePath, file.Filename)
path, err := utils.GenUploadPath(s.config.BasePath, file.Filename, false)
if err != nil {
return File{}, fmt.Errorf("error with generate filename: %s", err.Error())
}
@@ -41,10 +42,11 @@ func (s LocalStorage) PutFile(ctx *gin.Context, name string) (File, error) {
ext := filepath.Ext(file.Filename)
return File{
Name: file.Filename,
URL: utils.GenUploadUrl(s.config.BasePath, s.config.BaseURL, path),
Ext: ext,
Size: file.Size,
Name: file.Filename,
ObjKey: path,
URL: utils.GenUploadUrl(s.config.BasePath, s.config.BaseURL, path),
Ext: ext,
Size: file.Size,
}, nil
}
@@ -54,7 +56,7 @@ func (s LocalStorage) PutImg(imageURL string, useProxy bool) (string, error) {
return "", fmt.Errorf("error with parse image URL: %v", err)
}
filename := filepath.Base(parse.Path)
filePath, err := utils.GenUploadPath(s.config.BasePath, filename)
filePath, err := utils.GenUploadPath(s.config.BasePath, filename, true)
if err != nil {
return "", fmt.Errorf("error with generate image dir: %v", err)
}
@@ -72,6 +74,9 @@ func (s LocalStorage) PutImg(imageURL string, useProxy bool) (string, error) {
}
func (s LocalStorage) Delete(fileURL string) error {
if _, err := os.Stat(fileURL); err == nil {
return os.Remove(fileURL)
}
filePath := strings.Replace(fileURL, s.config.BaseURL, s.config.BasePath, 1)
return os.Remove(filePath)
}

View File

@@ -5,13 +5,14 @@ import (
"chatplus/utils"
"context"
"fmt"
"github.com/gin-gonic/gin"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
"net/url"
"path/filepath"
"strings"
"time"
"github.com/gin-gonic/gin"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
)
type MiniOss struct {
@@ -77,7 +78,7 @@ func (s MiniOss) PutFile(ctx *gin.Context, name string) (File, error) {
}
defer fileReader.Close()
fileExt := filepath.Ext(file.Filename)
fileExt := utils.GetImgExt(file.Filename)
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{
ContentType: file.Header.Get("Content-Type"),
@@ -87,17 +88,23 @@ func (s MiniOss) PutFile(ctx *gin.Context, name string) (File, error) {
}
return File{
Name: file.Filename,
URL: fmt.Sprintf("%s/%s/%s", s.config.Domain, s.config.Bucket, info.Key),
Ext: fileExt,
Size: file.Size,
Name: file.Filename,
ObjKey: info.Key,
URL: fmt.Sprintf("%s/%s/%s", s.config.Domain, s.config.Bucket, info.Key),
Ext: fileExt,
Size: file.Size,
}, nil
}
func (s MiniOss) Delete(fileURL string) error {
objectName := filepath.Base(fileURL)
key := fmt.Sprintf("%s/%s", s.config.SubDir, objectName)
return s.client.RemoveObject(context.Background(), s.config.Bucket, key, minio.RemoveObjectOptions{})
var objectKey string
if strings.HasPrefix(fileURL, "http") {
filename := filepath.Base(fileURL)
objectKey = fmt.Sprintf("%s/%s", s.config.SubDir, filename)
} else {
objectKey = fileURL
}
return s.client.RemoveObject(context.Background(), s.config.Bucket, objectKey, minio.RemoveObjectOptions{})
}
var _ Uploader = MiniOss{}

View File

@@ -6,12 +6,14 @@ import (
"chatplus/utils"
"context"
"fmt"
"net/url"
"path/filepath"
"strings"
"time"
"github.com/gin-gonic/gin"
"github.com/qiniu/go-sdk/v7/auth/qbox"
"github.com/qiniu/go-sdk/v7/storage"
"net/url"
"path/filepath"
"time"
)
type QinNiuOss struct {
@@ -74,10 +76,11 @@ func (s QinNiuOss) PutFile(ctx *gin.Context, name string) (File, error) {
}
return File{
Name: file.Filename,
URL: fmt.Sprintf("%s/%s", s.config.Domain, ret.Key),
Ext: fileExt,
Size: file.Size,
Name: file.Filename,
ObjKey: key,
URL: fmt.Sprintf("%s/%s", s.config.Domain, ret.Key),
Ext: fileExt,
Size: file.Size,
}, nil
}
@@ -97,7 +100,7 @@ func (s QinNiuOss) PutImg(imageURL string, useProxy bool) (string, error) {
if err != nil {
return "", fmt.Errorf("error with parse image URL: %v", err)
}
fileExt := filepath.Ext(parse.Path)
fileExt := utils.GetImgExt(parse.Path)
key := fmt.Sprintf("%s/%d%s", s.config.SubDir, time.Now().UnixMicro(), fileExt)
ret := storage.PutRet{}
extra := storage.PutExtra{}
@@ -110,9 +113,15 @@ func (s QinNiuOss) PutImg(imageURL string, useProxy bool) (string, error) {
}
func (s QinNiuOss) Delete(fileURL string) error {
objectName := filepath.Base(fileURL)
key := fmt.Sprintf("%s/%s", s.config.SubDir, objectName)
return s.manager.Delete(s.config.Bucket, key)
var objectKey string
if strings.HasPrefix(fileURL, "http") {
filename := filepath.Base(fileURL)
objectKey = fmt.Sprintf("%s/%s", s.config.SubDir, filename)
} else {
objectKey = fileURL
}
return s.manager.Delete(s.config.Bucket, objectKey)
}
var _ Uploader = QinNiuOss{}

View File

@@ -8,10 +8,11 @@ const QiNiu = "QINIU"
const AliYun = "ALIYUN"
type File struct {
Name string `json:"name"`
Size int64 `json:"size"`
URL string `json:"url"`
Ext string `json:"ext"`
Name string `json:"name"`
ObjKey string `json:"obj_key"`
Size int64 `json:"size"`
URL string `json:"url"`
Ext string `json:"ext"`
}
type Uploader interface {
PutFile(ctx *gin.Context, name string) (File, error)

View File

@@ -56,7 +56,7 @@ func (js *PayJS) Pay(param JPayReq) JPayReps {
}
p.Add("mchid", js.config.AppId)
p.Add("Sign", js.sign(p))
p.Add("sign", js.sign(p))
cli := http.Client{}
apiURL := fmt.Sprintf("%s/api/native", js.config.ApiURL)

View File

@@ -11,13 +11,16 @@ import (
)
type ServicePool struct {
services []*Service
taskQueue *store.RedisQueue
services []*Service
taskQueue *store.RedisQueue
notifyQueue *store.RedisQueue
Clients *types.LMap[uint, *types.WsClient] // UserId => Client
}
func NewServicePool(db *gorm.DB, redisCli *redis.Client, manager *oss.UploaderManager, appConfig *types.AppConfig) *ServicePool {
services := make([]*Service, 0)
queue := store.NewRedisQueue("StableDiffusion_Task_Queue", redisCli)
taskQueue := store.NewRedisQueue("StableDiffusion_Task_Queue", redisCli)
notifyQueue := store.NewRedisQueue("StableDiffusion_Queue", redisCli)
// create mj client and service
for k, config := range appConfig.SdConfigs {
if config.Enabled == false {
@@ -26,7 +29,7 @@ func NewServicePool(db *gorm.DB, redisCli *redis.Client, manager *oss.UploaderMa
// create sd service
name := fmt.Sprintf("StableDifffusion Service-%d", k)
service := NewService(name, 1, 300, config, queue, db, manager)
service := NewService(name, 1, 300, config, taskQueue, notifyQueue, db, manager)
// run sd service
go func() {
service.Run()
@@ -36,8 +39,10 @@ func NewServicePool(db *gorm.DB, redisCli *redis.Client, manager *oss.UploaderMa
}
return &ServicePool{
taskQueue: queue,
services: services,
taskQueue: taskQueue,
notifyQueue: notifyQueue,
services: services,
Clients: types.NewLMap[uint, *types.WsClient](),
}
}

View File

@@ -24,6 +24,7 @@ type Service struct {
httpClient *req.Client
config types.StableDiffusionConfig
taskQueue *store.RedisQueue
notifyQueue *store.RedisQueue
db *gorm.DB
uploadManager *oss.UploaderManager
name string // service name
@@ -33,12 +34,13 @@ type Service struct {
taskTimeout int64
}
func NewService(name string, maxTaskNum int32, timeout int64, config types.StableDiffusionConfig, queue *store.RedisQueue, db *gorm.DB, manager *oss.UploaderManager) *Service {
func NewService(name string, maxTaskNum int32, timeout int64, config types.StableDiffusionConfig, taskQueue *store.RedisQueue, notifyQueue *store.RedisQueue, db *gorm.DB, manager *oss.UploaderManager) *Service {
return &Service{
name: name,
config: config,
httpClient: req.C(),
taskQueue: queue,
taskQueue: taskQueue,
notifyQueue: notifyQueue,
db: db,
uploadManager: manager,
taskTimeout: timeout,
@@ -66,13 +68,18 @@ func (s *Service) Run() {
logger.Infof("%s handle a new Stable-Diffusion task: %+v", s.name, task)
err = s.Txt2Img(task)
if err != nil {
logger.Error("绘画任务执行失败:", err)
logger.Error("绘画任务执行失败:", err.Error())
// update the task progress
s.db.Model(&model.SdJob{Id: uint(task.Id)}).UpdateColumn("progress", -1)
s.db.Model(&model.SdJob{Id: uint(task.Id)}).UpdateColumns(map[string]interface{}{
"progress": -1,
"err_msg": err.Error(),
})
// restore img_call quota
s.db.Model(&model.User{}).Where("id = ?", task.UserId).UpdateColumn("img_calls", gorm.Expr("img_calls + ?", 1))
// release task num
atomic.AddInt32(&s.handledTaskNum, -1)
// 通知前端,任务失败
s.notifyQueue.RPush(task.UserId)
continue
}
@@ -296,7 +303,10 @@ func (s *Service) callback(data CBReq) {
} else { // 任务失败
logger.Error("任务执行失败:", data.Message)
// update the task progress
s.db.Model(&model.SdJob{Id: uint(data.JobId)}).UpdateColumn("progress", -1)
s.db.Model(&model.SdJob{Id: uint(data.JobId)}).UpdateColumns(map[string]interface{}{
"progress": -1,
"err_msg": data.Message,
})
// restore img_calls
s.db.Model(&model.User{}).Where("id = ? AND img_calls > 0", data.UserId).UpdateColumn("img_calls", gorm.Expr("img_calls + ?", 1))
}

View File

@@ -1 +0,0 @@
package wanx

View File

@@ -7,6 +7,7 @@ type HistoryMessage struct {
ChatId string // 会话 ID
UserId uint // 用户 ID
RoleId uint // 角色 ID
Model string // AI模型
Type string
Icon string
Tokens int

View File

@@ -7,7 +7,8 @@ type ChatItem struct {
ChatId string `gorm:"column:chat_id;unique"` // 会话 ID
UserId uint // 用户 ID
RoleId uint // 角色 ID
ModelId uint // 会话模型
ModelId uint // 模型 ID
Model string // 模型
Title string // 会话标题
DeletedAt gorm.DeletedAt
}

View File

@@ -6,6 +6,7 @@ type File struct {
Id uint `gorm:"primarykey;column:id"`
UserId uint
Name string
ObjKey string
URL string
Ext string
Size int64

View File

@@ -15,8 +15,9 @@ type MidJourneyJob struct {
Hash string // message hash
Progress int
Prompt string
UseProxy bool // 是否使用反代加载图片
Publish bool //是否发布图片到画廊
UseProxy bool // 是否使用反代加载图片
Publish bool //是否发布图片到画廊
ErrMsg string // 报错信息
CreatedAt time.Time
}

View File

@@ -11,7 +11,8 @@ type SdJob struct {
Progress int
Prompt string
Params string
Publish bool //是否发布图片到画廊
Publish bool //是否发布图片到画廊
ErrMsg string // 报错信息
CreatedAt time.Time
}

View File

@@ -11,7 +11,7 @@ import (
func NewGormConfig() *gorm.Config {
return &gorm.Config{
Logger: logger.Default.LogMode(logger.Warn),
Logger: logger.Default.LogMode(logger.Silent),
NamingStrategy: schema.NamingStrategy{
TablePrefix: "chatgpt_", // 设置表前缀
SingularTable: false, // 使用单数表名形式

View File

@@ -5,6 +5,7 @@ type HistoryMessage struct {
ChatId string `json:"chat_id"`
UserId uint `json:"user_id"`
RoleId uint `json:"role_id"`
Model string `json:"model"`
Type string `json:"type"`
Icon string `json:"icon"`
Tokens int `json:"tokens"`

View File

@@ -7,5 +7,6 @@ type ChatItem struct {
RoleId uint `json:"role_id"`
ChatId string `json:"chat_id"`
ModelId uint `json:"model_id"`
Model string `json:"model"`
Title string `json:"title"`
}

View File

@@ -1,9 +1,10 @@
package vo
type File struct {
Id uint
Id uint `json:"id"`
UserId uint `json:"user_id"`
Name string `json:"name"`
ObjKey string `json:"obj_key"`
URL string `json:"url"`
Ext string `json:"ext"`
Size int64 `json:"size"`

View File

@@ -17,5 +17,6 @@ type MidJourneyJob struct {
Prompt string `json:"prompt"`
UseProxy bool `json:"use_proxy"`
Publish bool `json:"publish"`
ErrMsg string `json:"err_msg"`
CreatedAt time.Time `json:"created_at"`
}

View File

@@ -15,5 +15,6 @@ type SdJob struct {
Progress int `json:"progress"`
Prompt string `json:"prompt"`
Publish bool `json:"publish"`
ErrMsg string `json:"err_msg"`
CreatedAt time.Time `json:"created_at"`
}

View File

@@ -2,11 +2,10 @@ package main
import (
"fmt"
"strings"
"net/url"
)
func main() {
str := "7151109597841850368 一个漂亮的中国女孩,手上拿着一桶爆米花,脸上带着迷人的微笑,电影效果"
index := strings.Index(str, " ")
fmt.Println(str[index+1:])
u, err := url.Parse("https://api.chat-plus.net/mj/image/1706368258238514?aaa=bbb")
fmt.Println(u.Path, u.RawQuery, err)
}

View File

@@ -48,12 +48,12 @@ func DownloadImage(imageURL string, proxy string) ([]byte, error) {
},
}
}
req, err := http.NewRequest("GET", imageURL, nil)
request, err := http.NewRequest("GET", imageURL, nil)
if err != nil {
return nil, err
}
resp, err := client.Do(req)
resp, err := client.Do(request)
if err != nil {
return nil, err
}

View File

@@ -12,7 +12,7 @@ import (
)
// GenUploadPath 生成上传文件路径
func GenUploadPath(basePath, filename string) (string, error) {
func GenUploadPath(basePath, filename string, isImg bool) (string, error) {
now := time.Now()
dir := fmt.Sprintf("%s/%d/%d", basePath, now.Year(), now.Month())
_, err := os.Stat(dir)
@@ -22,7 +22,12 @@ func GenUploadPath(basePath, filename string) (string, error) {
return "", fmt.Errorf("error with create upload dir%v", err)
}
}
fileExt := filepath.Ext(filename)
var fileExt string
if isImg {
fileExt = GetImgExt(filename)
} else {
fileExt = filepath.Ext(filename)
}
return fmt.Sprintf("%s/%d%s", dir, now.UnixMicro(), fileExt), nil
}
@@ -66,3 +71,11 @@ func DownloadFile(fileURL string, filepath string, proxy string) error {
return nil
}
func GetImgExt(filename string) string {
ext := filepath.Ext(filename)
if ext == "" {
return ".png"
}
return ext
}

View File

@@ -0,0 +1,15 @@
ALTER TABLE `chatgpt_mj_jobs` ADD `err_msg` VARCHAR(255) DEFAULT NULL COMMENT '错误信息' AFTER `publish`;
ALTER TABLE `chatgpt_sd_jobs` ADD `err_msg` VARCHAR(255) DEFAULT NULL COMMENT '错误信息' AFTER `publish`;
ALTER TABLE `chatgpt_chat_items` ADD `model` VARCHAR(30) NULL COMMENT '模型名称' AFTER `model_id`;
ALTER TABLE `chatgpt_chat_history` ADD `model` VARCHAR(30) NULL COMMENT '模型名称' AFTER `role_id`;
-- 初始化对话数据
UPDATE chatgpt_chat_items s SET model=(SELECT value FROM chatgpt_chat_models WHERE id = s.model_id);
-- 初始化聊天记录数据
UPDATE chatgpt_chat_history s SET model=(SELECT model FROM chatgpt_chat_items WHERE chat_id = s.chat_id);
-- 清理对话已删除的聊天记录(可选)
-- DELETE FROM `chatgpt_chat_history` WHERE model is NULL;
ALTER TABLE `chatgpt_files` ADD `obj_key` VARCHAR(100) NULL COMMENT '文件标识' AFTER `name`;

View File

@@ -26,23 +26,15 @@ WeChatBot = false
AppId = ""
Token = ""
[SmsConfig] # 阿里云短信服务配置
AccessKey = ""
AccessSecret = ""
Product = "Dysmsapi"
Domain = "dysmsapi.aliyuncs.com"
Sign = ""
CodeTempId = ""
[Sms] # Sms 配置,用于发送短信
[SMS] # Sms 配置,用于发送短信
Active = "Ali" # 当前启用的短信服务,默认使用阿里云
[Sms.SmsBao]
[SMS.Bao]
Username = ""
Password = ""
Domain = "api.smsbao.com"
Sign = "【极客学长】"
CodeTemplate = "您的验证码是{code}。5分钟有效若非本人操作请忽略本短信。"
[Sms.Ali]
[SMS.Ali]
AccessKey = ""
AccessSecret = ""
Product = "Dysmsapi"
@@ -83,6 +75,7 @@ WeChatBot = false
[[MjPlusConfigs]]
Enabled = false
ApiURL = "https://api.chatgpt-plus.net" # 目前暂时不支持更改
CdnURL = "" # CND 加速的 URL如果有的话就设置
ApiKey = "sk-xxx"
NotifyURL = "https://ai.r9it.com/api/mj/notify" # 这里需要改成你的域名
@@ -114,9 +107,9 @@ WeChatBot = false
[HuPiPayConfig]
Enabled = false
Name = "wechat"
AppId = "201906161477"
AppSecret = "7f403199d510fb2c6f0b9f2311800e7c"
PayURL = "https://api.xunhupay.com/payment/do.html"
AppId = ""
AppSecret = ""
ApiURL = "https://api.xunhupay.com"
NotifyURL = "https://ai.r9it.com/api/payment/hupipay/notify"
[SmtpConfig] # 注意阿里云服务器禁用了25号端口所以如果需要使用邮件功能请别用阿里云服务器
@@ -131,5 +124,5 @@ WeChatBot = false
Name = "wechat" # 请不要改动
AppId = "" # 商户 ID
PrivateKey = "" # 秘钥
ApiURL = "https://payjs.cn/api/native"
ApiURL = "https://payjs.cn"
NotifyURL = "https://ai.r9it.com/api/payment/payjs/notify" # 异步回调地址,域名改成你自己的

11
web/package-lock.json generated
View File

@@ -19,7 +19,6 @@
"json-bigint": "^1.0.0",
"lodash": "^4.17.21",
"markdown-it": "^13.0.1",
"markdown-it-emoji": "^3.0.0",
"md-editor-v3": "^2.2.1",
"pinia": "^2.1.4",
"qrcode": "^1.5.3",
@@ -7431,11 +7430,6 @@
"markdown-it": "bin/markdown-it.js"
}
},
"node_modules/markdown-it-emoji": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/markdown-it-emoji/-/markdown-it-emoji-3.0.0.tgz",
"integrity": "sha512-+rUD93bXHubA4arpEZO3q80so0qgoFJEKRkRbjKX8RTdca89v2kfyF+xR3i2sQTwql9tpPZPOQN5B+PunspXRg=="
},
"node_modules/markdown-it/node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
@@ -17367,11 +17361,6 @@
}
}
},
"markdown-it-emoji": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/markdown-it-emoji/-/markdown-it-emoji-3.0.0.tgz",
"integrity": "sha512-+rUD93bXHubA4arpEZO3q80so0qgoFJEKRkRbjKX8RTdca89v2kfyF+xR3i2sQTwql9tpPZPOQN5B+PunspXRg=="
},
"md-editor-v3": {
"version": "2.11.3",
"resolved": "https://registry.npmjs.org/md-editor-v3/-/md-editor-v3-2.11.3.tgz",

View File

@@ -19,6 +19,8 @@
"json-bigint": "^1.0.0",
"lodash": "^4.17.21",
"markdown-it": "^13.0.1",
"markdown-it-latex2img": "^0.0.6",
"markdown-it-mathjax": "^2.0.0",
"md-editor-v3": "^2.2.1",
"pinia": "^2.1.4",
"qrcode": "^1.5.3",

BIN
web/public/images/mic.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@@ -4,7 +4,7 @@
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no">
<link rel="icon" href="/favicon.ico" type="image/x-icon">
<title>ChatGPT-Plus</title>
</head>

View File

@@ -153,6 +153,12 @@
#app .common-layout .el-main .chat-head .iconfont {
margin-right: 5px;
}
#app .common-layout .el-main .chat-head .is-circle {
margin-left: 5px;
}
#app .common-layout .el-main .chat-head .is-circle .iconfont {
margin-right: 0;
}
#app .common-layout .el-main .right-box {
min-width: 0;
flex: 1;

View File

@@ -199,6 +199,13 @@ $borderColor = #4676d0;
.iconfont {
margin-right 5px;
}
.is-circle {
margin-left 5px
.iconfont {
margin-right 0
}
}
}
.right-box {
@@ -250,7 +257,6 @@ $borderColor = #4676d0;
margin-right 5px;
}
}
}
}

View File

@@ -201,9 +201,6 @@
.page-mj .inner .task-list-box .task-list-inner .title-tabs .el-tabs__active-bar {
background-color: #47fff1;
}
.page-mj .inner .task-list-box .task-list-inner .title-tabs .el-tabs__content {
padding: 10px 0;
}
.page-mj .inner .task-list-box .task-list-inner .el-textarea {
--el-input-focus-border-color: #47fff1;
}
@@ -234,26 +231,49 @@
.page-mj .inner .task-list-box .task-list-inner .el-form-item__label {
color: #fff;
}
.page-mj .inner .task-list-box .task-list-inner .img-uploader .el-upload {
.page-mj .inner .task-list-box .task-list-inner .img-inline {
display: flex;
}
.page-mj .inner .task-list-box .task-list-inner .img-inline .img-uploader .el-upload {
border: 1px dashed var(--el-border-color);
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
width: 300px;
width: 120px;
transition: var(--el-transition-duration-fast);
margin-bottom: 20px;
}
.page-mj .inner .task-list-box .task-list-inner .img-uploader .el-upload:hover {
.page-mj .inner .task-list-box .task-list-inner .img-inline .img-uploader .el-upload:hover {
border-color: var(--el-color-primary);
}
.page-mj .inner .task-list-box .task-list-inner .img-uploader .el-upload .el-icon.uploader-icon {
.page-mj .inner .task-list-box .task-list-inner .img-inline .img-uploader .el-upload .el-icon.uploader-icon {
font-size: 28px;
color: #8c939d;
width: 100%;
height: 120px;
text-align: center;
}
.page-mj .inner .task-list-box .task-list-inner .img-inline .img-list-box {
display: flex;
}
.page-mj .inner .task-list-box .task-list-inner .img-inline .img-list-box .img-item {
width: 120px;
position: relative;
margin-right: 10px;
}
.page-mj .inner .task-list-box .task-list-inner .img-inline .img-list-box .img-item .el-image {
width: 120px;
height: 120px;
border-radius: 5px;
}
.page-mj .inner .task-list-box .task-list-inner .img-inline .img-list-box .img-item .el-button {
position: absolute;
right: 5px;
top: 5px;
width: 20px;
height: 20px;
}
.page-mj .inner .task-list-box .task-list-inner .submit-btn {
display: flex;
margin: 20px 0;
@@ -267,17 +287,17 @@
justify-content: right;
align-items: center;
}
.page-mj .inner .task-list-box .running-job-list .job-item {
.page-mj .inner .task-list-box .task-list-inner .job-list-box .running-job-list .job-item {
width: 100%;
padding: 2px;
background-color: #555;
}
.page-mj .inner .task-list-box .running-job-list .job-item .job-item-inner {
.page-mj .inner .task-list-box .task-list-inner .job-list-box .running-job-list .job-item .job-item-inner {
position: relative;
height: 100%;
overflow: hidden;
}
.page-mj .inner .task-list-box .running-job-list .job-item .job-item-inner .progress {
.page-mj .inner .task-list-box .task-list-inner .job-list-box .running-job-list .job-item .job-item-inner .progress {
position: absolute;
width: 100%;
height: 100%;
@@ -287,11 +307,11 @@
justify-content: center;
align-items: center;
}
.page-mj .inner .task-list-box .running-job-list .job-item .job-item-inner .progress span {
.page-mj .inner .task-list-box .task-list-inner .job-list-box .running-job-list .job-item .job-item-inner .progress span {
font-size: 20px;
color: #fff;
}
.page-mj .inner .task-list-box .finish-job-list .job-item {
.page-mj .inner .task-list-box .task-list-inner .job-list-box .finish-job-list .job-item {
width: 100%;
height: 100%;
border: 1px solid #666;
@@ -301,17 +321,17 @@
transition: all 0.3s ease; /* 添加过渡效果 */
position: relative;
}
.page-mj .inner .task-list-box .finish-job-list .job-item .opt .opt-line {
.page-mj .inner .task-list-box .task-list-inner .job-list-box .finish-job-list .job-item .opt .opt-line {
margin: 6px 0;
}
.page-mj .inner .task-list-box .finish-job-list .job-item .opt .opt-line ul {
.page-mj .inner .task-list-box .task-list-inner .job-list-box .finish-job-list .job-item .opt .opt-line ul {
display: flex;
flex-flow: row;
}
.page-mj .inner .task-list-box .finish-job-list .job-item .opt .opt-line ul li {
.page-mj .inner .task-list-box .task-list-inner .job-list-box .finish-job-list .job-item .opt .opt-line ul li {
margin-right: 6px;
}
.page-mj .inner .task-list-box .finish-job-list .job-item .opt .opt-line ul li a {
.page-mj .inner .task-list-box .task-list-inner .job-list-box .finish-job-list .job-item .opt .opt-line ul li a {
padding: 3px 0;
width: 40px;
text-align: center;
@@ -321,59 +341,58 @@
background-color: #4e5058;
color: #fff;
}
.page-mj .inner .task-list-box .finish-job-list .job-item .opt .opt-line ul li a:hover {
.page-mj .inner .task-list-box .task-list-inner .job-list-box .finish-job-list .job-item .opt .opt-line ul li a:hover {
background-color: #6d6f78;
}
.page-mj .inner .task-list-box .finish-job-list .job-item .opt .opt-line ul .show-prompt {
.page-mj .inner .task-list-box .task-list-inner .job-list-box .finish-job-list .job-item .opt .opt-line ul .show-prompt {
font-size: 20px;
cursor: pointer;
}
.page-mj .inner .task-list-box .finish-job-list .job-item .remove {
.page-mj .inner .task-list-box .task-list-inner .job-list-box .finish-job-list .job-item .remove {
display: none;
position: absolute;
right: 10px;
top: 10px;
}
.page-mj .inner .task-list-box .finish-job-list .job-item:hover .remove {
.page-mj .inner .task-list-box .task-list-inner .job-list-box .finish-job-list .job-item:hover .remove {
display: block;
}
.page-mj .inner .task-list-box .finish-job-list .animate:hover {
.page-mj .inner .task-list-box .task-list-inner .job-list-box .finish-job-list .animate:hover {
box-shadow: 0 0 10px rgba(71,255,241,0.6); /* 添加阴影效果 */
transform: translateY(-10px); /* 向上移动10像素 */
}
.page-mj .inner .task-list-box .el-image {
.page-mj .inner .task-list-box .task-list-inner .job-list-box .el-image {
width: 100%;
height: 100%;
overflow: visible;
}
.page-mj .inner .task-list-box .el-image img {
.page-mj .inner .task-list-box .task-list-inner .job-list-box .el-image img {
height: 240px;
}
.page-mj .inner .task-list-box .el-image .el-image-viewer__wrapper img {
.page-mj .inner .task-list-box .task-list-inner .job-list-box .el-image .el-image-viewer__wrapper img {
width: auto;
height: auto;
}
.page-mj .inner .task-list-box .el-image .image-slot {
.page-mj .inner .task-list-box .task-list-inner .job-list-box .el-image .image-slot {
display: flex;
flex-flow: column;
justify-content: center;
align-items: center;
height: 100%;
min-height: 200px;
color: #fff;
height: 240px;
}
.page-mj .inner .task-list-box .el-image .image-slot .iconfont {
.page-mj .inner .task-list-box .task-list-inner .job-list-box .el-image .image-slot .iconfont {
font-size: 50px;
margin-bottom: 10px;
}
.page-mj .inner .task-list-box .el-image.upscale {
.page-mj .inner .task-list-box .task-list-inner .job-list-box .el-image.upscale {
max-height: 310px;
}
.page-mj .inner .task-list-box .el-image.upscale img {
.page-mj .inner .task-list-box .task-list-inner .job-list-box .el-image.upscale img {
height: 310px;
}
.page-mj .inner .task-list-box .el-image.upscale .el-image-viewer__wrapper img {
.page-mj .inner .task-list-box .task-list-inner .job-list-box .el-image.upscale .el-image-viewer__wrapper img {
width: auto;
height: auto;
}

View File

@@ -86,9 +86,6 @@
.page-sd .inner .task-list-box .task-list-inner .title-tabs .el-tabs__active-bar {
background-color: #47fff1;
}
.page-sd .inner .task-list-box .task-list-inner .title-tabs .el-tabs__content {
padding: 10px 0;
}
.page-sd .inner .task-list-box .task-list-inner .el-textarea {
--el-input-focus-border-color: #47fff1;
}
@@ -119,26 +116,49 @@
.page-sd .inner .task-list-box .task-list-inner .el-form-item__label {
color: #fff;
}
.page-sd .inner .task-list-box .task-list-inner .img-uploader .el-upload {
.page-sd .inner .task-list-box .task-list-inner .img-inline {
display: flex;
}
.page-sd .inner .task-list-box .task-list-inner .img-inline .img-uploader .el-upload {
border: 1px dashed var(--el-border-color);
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
width: 300px;
width: 120px;
transition: var(--el-transition-duration-fast);
margin-bottom: 20px;
}
.page-sd .inner .task-list-box .task-list-inner .img-uploader .el-upload:hover {
.page-sd .inner .task-list-box .task-list-inner .img-inline .img-uploader .el-upload:hover {
border-color: var(--el-color-primary);
}
.page-sd .inner .task-list-box .task-list-inner .img-uploader .el-upload .el-icon.uploader-icon {
.page-sd .inner .task-list-box .task-list-inner .img-inline .img-uploader .el-upload .el-icon.uploader-icon {
font-size: 28px;
color: #8c939d;
width: 100%;
height: 120px;
text-align: center;
}
.page-sd .inner .task-list-box .task-list-inner .img-inline .img-list-box {
display: flex;
}
.page-sd .inner .task-list-box .task-list-inner .img-inline .img-list-box .img-item {
width: 120px;
position: relative;
margin-right: 10px;
}
.page-sd .inner .task-list-box .task-list-inner .img-inline .img-list-box .img-item .el-image {
width: 120px;
height: 120px;
border-radius: 5px;
}
.page-sd .inner .task-list-box .task-list-inner .img-inline .img-list-box .img-item .el-button {
position: absolute;
right: 5px;
top: 5px;
width: 20px;
height: 20px;
}
.page-sd .inner .task-list-box .task-list-inner .submit-btn {
display: flex;
margin: 20px 0;
@@ -152,17 +172,17 @@
justify-content: right;
align-items: center;
}
.page-sd .inner .task-list-box .running-job-list .job-item {
.page-sd .inner .task-list-box .task-list-inner .job-list-box .running-job-list .job-item {
width: 100%;
padding: 2px;
background-color: #555;
}
.page-sd .inner .task-list-box .running-job-list .job-item .job-item-inner {
.page-sd .inner .task-list-box .task-list-inner .job-list-box .running-job-list .job-item .job-item-inner {
position: relative;
height: 100%;
overflow: hidden;
}
.page-sd .inner .task-list-box .running-job-list .job-item .job-item-inner .progress {
.page-sd .inner .task-list-box .task-list-inner .job-list-box .running-job-list .job-item .job-item-inner .progress {
position: absolute;
width: 100%;
height: 100%;
@@ -172,11 +192,11 @@
justify-content: center;
align-items: center;
}
.page-sd .inner .task-list-box .running-job-list .job-item .job-item-inner .progress span {
.page-sd .inner .task-list-box .task-list-inner .job-list-box .running-job-list .job-item .job-item-inner .progress span {
font-size: 20px;
color: #fff;
}
.page-sd .inner .task-list-box .finish-job-list .job-item {
.page-sd .inner .task-list-box .task-list-inner .job-list-box .finish-job-list .job-item {
width: 100%;
height: 100%;
border: 1px solid #666;
@@ -186,17 +206,17 @@
transition: all 0.3s ease; /* 添加过渡效果 */
position: relative;
}
.page-sd .inner .task-list-box .finish-job-list .job-item .opt .opt-line {
.page-sd .inner .task-list-box .task-list-inner .job-list-box .finish-job-list .job-item .opt .opt-line {
margin: 6px 0;
}
.page-sd .inner .task-list-box .finish-job-list .job-item .opt .opt-line ul {
.page-sd .inner .task-list-box .task-list-inner .job-list-box .finish-job-list .job-item .opt .opt-line ul {
display: flex;
flex-flow: row;
}
.page-sd .inner .task-list-box .finish-job-list .job-item .opt .opt-line ul li {
.page-sd .inner .task-list-box .task-list-inner .job-list-box .finish-job-list .job-item .opt .opt-line ul li {
margin-right: 6px;
}
.page-sd .inner .task-list-box .finish-job-list .job-item .opt .opt-line ul li a {
.page-sd .inner .task-list-box .task-list-inner .job-list-box .finish-job-list .job-item .opt .opt-line ul li a {
padding: 3px 0;
width: 40px;
text-align: center;
@@ -206,59 +226,58 @@
background-color: #4e5058;
color: #fff;
}
.page-sd .inner .task-list-box .finish-job-list .job-item .opt .opt-line ul li a:hover {
.page-sd .inner .task-list-box .task-list-inner .job-list-box .finish-job-list .job-item .opt .opt-line ul li a:hover {
background-color: #6d6f78;
}
.page-sd .inner .task-list-box .finish-job-list .job-item .opt .opt-line ul .show-prompt {
.page-sd .inner .task-list-box .task-list-inner .job-list-box .finish-job-list .job-item .opt .opt-line ul .show-prompt {
font-size: 20px;
cursor: pointer;
}
.page-sd .inner .task-list-box .finish-job-list .job-item .remove {
.page-sd .inner .task-list-box .task-list-inner .job-list-box .finish-job-list .job-item .remove {
display: none;
position: absolute;
right: 10px;
top: 10px;
}
.page-sd .inner .task-list-box .finish-job-list .job-item:hover .remove {
.page-sd .inner .task-list-box .task-list-inner .job-list-box .finish-job-list .job-item:hover .remove {
display: block;
}
.page-sd .inner .task-list-box .finish-job-list .animate:hover {
.page-sd .inner .task-list-box .task-list-inner .job-list-box .finish-job-list .animate:hover {
box-shadow: 0 0 10px rgba(71,255,241,0.6); /* 添加阴影效果 */
transform: translateY(-10px); /* 向上移动10像素 */
}
.page-sd .inner .task-list-box .el-image {
.page-sd .inner .task-list-box .task-list-inner .job-list-box .el-image {
width: 100%;
height: 100%;
overflow: visible;
}
.page-sd .inner .task-list-box .el-image img {
.page-sd .inner .task-list-box .task-list-inner .job-list-box .el-image img {
height: 240px;
}
.page-sd .inner .task-list-box .el-image .el-image-viewer__wrapper img {
.page-sd .inner .task-list-box .task-list-inner .job-list-box .el-image .el-image-viewer__wrapper img {
width: auto;
height: auto;
}
.page-sd .inner .task-list-box .el-image .image-slot {
.page-sd .inner .task-list-box .task-list-inner .job-list-box .el-image .image-slot {
display: flex;
flex-flow: column;
justify-content: center;
align-items: center;
height: 100%;
min-height: 200px;
color: #fff;
height: 240px;
}
.page-sd .inner .task-list-box .el-image .image-slot .iconfont {
.page-sd .inner .task-list-box .task-list-inner .job-list-box .el-image .image-slot .iconfont {
font-size: 50px;
margin-bottom: 10px;
}
.page-sd .inner .task-list-box .el-image.upscale {
.page-sd .inner .task-list-box .task-list-inner .job-list-box .el-image.upscale {
max-height: 310px;
}
.page-sd .inner .task-list-box .el-image.upscale img {
.page-sd .inner .task-list-box .task-list-inner .job-list-box .el-image.upscale img {
height: 310px;
}
.page-sd .inner .task-list-box .el-image.upscale .el-image-viewer__wrapper img {
.page-sd .inner .task-list-box .task-list-inner .job-list-box .el-image.upscale .el-image-viewer__wrapper img {
width: auto;
height: auto;
}

View File

@@ -1,17 +0,0 @@
.title {
color: #fff;
text-align: center;
font-size: 16px;
font-weight: bold;
}
.app-background {
background-color: #282828;
height: 100vh;
}
.mobile-setting .content {
padding-top: 60px;
}
.mobile-setting .content .van-field__label {
width: 100px;
text-align: right;
}

View File

@@ -1,20 +0,0 @@
.title {
color #fff
text-align center
font-size 16px
font-weight bold
}
.app-background {
background-color #282828
height 100vh
}
.mobile-setting {
.content {
padding-top 60px
.van-field__label {
width 100px
text-align right
}
}
}

View File

@@ -1,47 +1,29 @@
.app-background {
background-color: #1c1c1c;
height: 100vh;
}
.mobile-chat-list {
background-color: #1c1c1c;
position: absolute;
top: 40px;
width: 100%;
}
.content .van-cell__value .chat-list-item {
.mobile-chat-list .content .van-cell__value .chat-list-item {
display: flex;
font-size: 16px;
color: #fff;
background-color: #1c1c1c;
font-size: 14px;
}
.content .van-cell__value .chat-list-item .van-image {
.mobile-chat-list .content .van-cell__value .chat-list-item .van-image {
min-width: 32px;
width: 32px;
height: 32px;
}
.content .van-cell__value .chat-list-item .van-ellipsis {
.mobile-chat-list .content .van-cell__value .chat-list-item .van-ellipsis {
margin-top: 5px;
margin-left: 10px;
}
.van-picker-column .picker-option {
.mobile-chat-list .van-nav-bar .van-nav-bar__right .van-icon {
font-size: 20px;
}
.van-popup .picker-option {
display: flex;
width: 100%;
padding: 0 10px;
overflow: hidden;
height: 20px;
text-overflow: ellipsis;
}
.van-picker-column .picker-option .van-image {
.van-popup .picker-option .van-image {
width: 20px;
height: 20px;
margin-right: 5px;
}
.van-nav-bar .van-nav-bar__right .van-icon {
font-size: 20px;
}
.popup {
background-color: #1c1c1c;
}
.dialog {
background-color: #1c1c1c;
}
.field {
background-color: #1c1c1c;
}

View File

@@ -1,58 +1,47 @@
$fontSize = 16px;
.app-background {
background-color: #1c1c1c;
height: 100vh;
}
.mobile-chat-list {
background-color: #1c1c1c;
position: absolute;
top: 40px;
width: 100%;
}
.content {
.van-cell__value {
.chat-list-item {
display flex
font-size $fontSize
color: #fff;
background-color: #1c1c1c;
.van-image {
min-width 32px
width 32px
height 32px
}
.van-ellipsis {
margin-top 5px;
margin-left 10px;
}
}
}
}
.van-picker-column {
.picker-option {
display flex
width 100%
padding 0 10px
.van-image {
width 20px;
height 20px;
margin-right 5px
}
}
}
.van-nav-bar {
.van-nav-bar__right {
.van-icon {
font-size 20px;
}
}
}
.popup {
background-color: #1c1c1c;
}
.dialog {
background-color: #1c1c1c;
}
.field {
background-color: #1c1c1c;
}
.mobile-chat-list {
.content {
.van-cell__value {
.chat-list-item {
display flex
font-size 14px
.van-image {
min-width 32px
width 32px
height 32px
}
.van-ellipsis {
margin-top 5px;
margin-left 10px;
}
}
}
}
.van-nav-bar {
.van-nav-bar__right {
.van-icon {
font-size 20px;
}
}
}
}
.van-popup {
.picker-option {
display flex
width 100%
padding 0 10px
overflow hidden
height 20px
text-overflow ellipsis
.van-image {
width 20px;
height 20px;
margin-right 5px
}
}
}

View File

@@ -1,45 +1,46 @@
.app-background {
background-color: #1c1c1c;
height: 100vh;
.mobile-chat .message-list-box {
padding-top: 50px;
padding-bottom: 10px;
overflow-x: auto;
background: #f5f5f5;
}
body,
.mobile-chat,
.van-sticky,
.van-nav-bar,
.van-list,
.message-list-box,
.van-cell,
.chat-box,
.van-cell-group,
.van-field {
background-color: #1c1c1c !important;
color: #fff !important;
.mobile-chat .message-list-box .van-cell {
background: none;
font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
}
.chat-box-wrapper {
position: fixed;
bottom: 0;
width: 100%;
background-color: #1c1c1c;
.mobile-chat .chat-box-wrapper .van-sticky .van-cell-group--inset {
margin: 0;
}
.icon-box .van-icon,
.mobile-chat .van-nav-bar__title .van-dropdown-menu__title,
.mobile-chat .van-nav-bar__title .van-cell__title,
.mobile-chat .van-nav-bar__right .van-icon {
color: #fff !important;
.mobile-chat .chat-box-wrapper .van-sticky .van-cell-group--inset .van-cell {
padding: 10px;
}
.mobile-chat .chat-box-wrapper .van-sticky .van-cell-group--inset .van-cell .icon-box .van-icon {
font-size: 24px;
margin-left: 10px;
}
.mobile-chat .chat-box-wrapper .van-sticky .van-cell-group--inset .van-cell .button-voice {
padding: 0 2px;
height: 30px;
}
.mobile-chat .chat-box-wrapper .van-sticky .van-cell-group--inset .van-cell .button-voice .el-icon {
font-size: 24px;
}
.mobile-chat .van-nav-bar__title .van-dropdown-menu__title {
margin-right: 15px;
margin-right: 10px;
}
.mobile-chat .van-nav-bar__title .van-cell__title {
text-align: left;
}
.mobile-chat .chat-list-wrapper {
position: fixed;
top: 50px;
bottom: 60px;
width: 100vw;
overflow-y: scroll;
}
.mobile-chat .van-nav-bar__right .van-icon {
font-size: 20px;
}
.van-overlay .mic-wrapper {
display: flex;
height: 100vh;
justify-content: center;
align-items: center;
flex-flow: column;
}
.van-theme-dark .mobile-chat .message-list-box {
background: #232425;
}

View File

@@ -1,51 +1,75 @@
.app-background {
background-color #1c1c1c
height 100vh
}
body,
.mobile-chat,
.van-sticky,
.van-nav-bar,
.van-list,
.message-list-box
.van-cell,
.chat-box,
.van-cell-group,
.van-field {
background-color #1c1c1c !important
color #fff !important
}
.chat-box-wrapper {
position fixed
bottom 0
width 100%
background-color #1c1c1c
}
.icon-box .van-icon,
.mobile-chat .van-nav-bar__title .van-dropdown-menu__title,
.mobile-chat .van-nav-bar__title .van-cell__title,
.mobile-chat .van-nav-bar__right .van-icon {
color #fff !important
}
.mobile-chat {
.van-nav-bar__title {
.van-dropdown-menu__title {
margin-right 15px
}
.van-cell__title {
text-align left
}
}
.chat-list-wrapper {
position fixed
top 50px
bottom 60px
width 100vw
overflow-y scroll
}
.van-nav-bar__right {
.van-icon {
font-size 20px
}
}
}
.mobile-chat {
.message-list-box {
padding-top 50px
padding-bottom 10px
overflow-x auto
background #F5F5F5;
.van-cell {
background none
font-family: 'Microsoft YaHei', '', Arial, sans-serif;
}
}
.chat-box-wrapper {
.van-sticky {
.van-cell-group--inset {
margin 0
.van-cell {
padding 10px
.icon-box {
.van-icon {
font-size 24px
margin-left 10px
}
}
.button-voice {
padding 0 2px
.el-icon {
font-size 24px
}
height 30px
}
}
}
}
}
.van-nav-bar__title {
.van-dropdown-menu__title {
margin-right 10px
}
.van-cell__title {
text-align left
}
}
.van-nav-bar__right {
.van-icon {
font-size 20px
}
}
}
.van-overlay {
.mic-wrapper {
display flex
height 100vh
justify-content center
align-items center
flex-flow column
}
}
.van-theme-dark {
.mobile-chat {
.message-list-box {
background #232425;
}
}
}

View File

@@ -1,13 +0,0 @@
.custom-scroll ::-webkit-scrollbar {
width: 8px; /* 滚动条宽度 */
}
.custom-scroll ::-webkit-scrollbar-track {
background-color: #282c34;
}
.custom-scroll ::-webkit-scrollbar-thumb {
background-color: #444;
border-radius: 8px;
}
.custom-scroll ::-webkit-scrollbar-thumb:hover {
background-color: #666;
}

View File

@@ -1,26 +0,0 @@
.custom-scroll {
/* */
::-webkit-scrollbar {
width: 8px; /* */
}
/* */
::-webkit-scrollbar-track {
background-color: #282C34;
}
/* */
::-webkit-scrollbar-thumb {
background-color: #444444;
border-radius 8px
}
/* */
::-webkit-scrollbar-thumb:hover {
background-color: #666666;
}
}

View File

@@ -1,45 +0,0 @@
.my-tabbar {
background-color: #171717;
box-shadow: -3px 4px 16px #000;
box-sizing: border-box;
}
.banner {
display: flex;
justify-content: center;
align-items: center;
background-color: #171717;
border-bottom-right-radius: 10px;
box-shadow: 0 -4px 13px #000;
height: 40px;
width: 100%;
position: fixed;
z-index: 2 /* Add z-index */;
}
.banner-title {
color: #2cc995;
font-size: 20px;
font-weight: bold;
text-shadow: 0 -4px 13px #000;
}
.mobile-home {
background-color: #171717;
}
.mobile-home .van-tabbar before {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 50px;
background-color: #171717;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
opacity: 0;
transition: opacity 0.5s;
z-index: -1;
}
.mobile-home .van-tabbar.activeTab before {
opacity: 1;
}
.router-view {
margin-bottom: 50px;
}

View File

@@ -1,45 +0,0 @@
.my-tabbar {
background-color #171717
box-shadow -3px 4px 16px #000
box-sizing border-box
}
.banner {
display flex
justify-content center
align-items center
background-color #171717
border-bottom-right-radius 10px
box-shadow 0 -4px 13px #000
height 40px
width 100%
position fixed
z-index 2 /* Add z-index */
}
.banner-title {
color #2CC995
font-size 20px
font-weight bold
text-shadow 0 -4px 13px #000
}
.mobile-home {
background-color #171717
}
.mobile-home .van-tabbar before {
position absolute
bottom 0
left 0
right 0
height 50px
background-color #171717
border-top-left-radius 10px
border-top-right-radius 10px
opacity 0
transition opacity 0.5s
z-index -1
}
.mobile-home .van-tabbar.activeTab before {
opacity 1
}
.router-view {
margin-bottom 50px
}

View File

@@ -1,252 +0,0 @@
.page-sd {
background-color: #282828;
}
.page-sd .inner {
display: flex;
}
.page-sd .inner .sd-box {
margin: 10px;
background-color: #282828;
min-width: 92%;
max-width: 92%;
padding: 0px;
border-radius: 0px;
color: #fff;
font-size: 14px;
}
.page-sd .inner .sd-box h2 {
font-weight: bold;
font-size: 20px;
text-align: center;
color: #fff;
}
.page-sd .inner .sd-box ::-webkit-scrollbar {
width: 0;
height: 0;
background-color: transparent;
}
.page-sd .inner .sd-box .sd-params {
margin-top: 10px;
overflow: auto;
}
.page-sd .inner .sd-box .sd-params .param-line {
padding: 0 10px;
}
.page-sd .inner .sd-box .sd-params .param-line .el-icon {
position: relative;
top: 3px;
}
.page-sd .inner .sd-box .sd-params .param-line .el-input__suffix-inner .el-icon {
top: 0;
}
.page-sd .inner .sd-box .sd-params .param-line .grid-content,
.page-sd .inner .sd-box .sd-params .param-line .form-item-inner {
display: flex;
}
.page-sd .inner .sd-box .sd-params .param-line .grid-content .el-icon,
.page-sd .inner .sd-box .sd-params .param-line .form-item-inner .el-icon {
margin-left: 10px;
margin-top: 2px;
}
.page-sd .inner .sd-box .sd-params .param-line.pt {
padding-top: 5px;
padding-bottom: 5px;
}
.page-sd .inner .sd-box .submit-btn {
padding: 10px 15px 0 15px;
text-align: center;
}
.page-sd .inner .sd-box .submit-btn .el-button {
width: 100%;
}
.page-sd .inner .sd-box .submit-btn .el-button span {
color: #2d3a4b;
}
.page-sd .inner .el-form .el-form-item__label {
color: #fff;
}
.page-sd .inner .task-list-box {
width: 100%;
padding: 10px;
color: #fff;
overflow-x: hidden;
}
.page-sd .inner .task-list-box .running-job-list .job-item {
width: 100%;
padding: 2px;
background-color: #555;
}
.page-sd .inner .task-list-box .running-job-list .job-item .job-item-inner {
position: relative;
height: 100%;
overflow: hidden;
}
.page-sd .inner .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;
}
.page-sd .inner .task-list-box .running-job-list .job-item .job-item-inner .progress span {
font-size: 20px;
color: #fff;
}
.page-sd .inner .task-list-box .finish-job-list .job-item {
width: 100%;
height: 100%;
border: 1px solid #666;
padding: 6px;
overflow: hidden;
border-radius: 6px;
transition: all 0.3s ease; /* 添加过渡效果 */
}
.page-sd .inner .task-list-box .finish-job-list .job-item .opt .opt-line {
margin: 6px 0;
}
.page-sd .inner .task-list-box .finish-job-list .job-item .opt .opt-line ul {
display: flex;
flex-flow: row;
}
.page-sd .inner .task-list-box .finish-job-list .job-item .opt .opt-line ul li {
margin-right: 6px;
}
.page-sd .inner .task-list-box .finish-job-list .job-item .opt .opt-line ul li a {
padding: 3px 0;
width: 40px;
text-align: center;
border-radius: 5px;
display: block;
cursor: pointer;
background-color: #4e5058;
color: #fff;
}
.page-sd .inner .task-list-box .finish-job-list .job-item .opt .opt-line ul li a:hover {
background-color: #6d6f78;
}
.page-sd .inner .task-list-box .finish-job-list .job-item .opt .opt-line ul .show-prompt {
font-size: 20px;
cursor: pointer;
}
.page-sd .inner .task-list-box .finish-job-list .animate:hover {
box-shadow: 0 0 10px rgba(71,255,241,0.6); /* 添加阴影效果 */
transform: translateY(-10px); /* 向上移动10像素 */
}
.page-sd .inner .task-list-box .el-image {
width: 100%;
height: 100%;
overflow: visible;
}
.page-sd .inner .task-list-box .el-image img {
height: 100%;
}
.page-sd .inner .task-list-box .el-image .el-image-viewer__wrapper img {
width: auto;
height: auto;
}
.page-sd .inner .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;
height: 240px;
}
.page-sd .inner .task-list-box .el-image .image-slot .iconfont {
font-size: 50px;
margin-bottom: 10px;
}
.page-sd .inner .task-list-box .el-image.upscale {
max-height: 100%;
}
.page-sd .inner .task-list-box .el-image.upscale img {
height: 100%;
}
.page-sd .inner .task-list-box .el-image.upscale .el-image-viewer__wrapper img {
width: auto;
height: auto;
}
.page-sd .el-overlay-dialog .el-dialog {
background-color: #1a1b1e;
}
.page-sd .el-overlay-dialog .el-dialog .el-dialog__header .el-dialog__title {
color: #f5f5f5;
}
.page-sd .el-overlay-dialog .el-dialog .el-dialog__body {
padding: 0 0 0 15px !important;
display: flex;
height: 100%;
}
.page-sd .el-overlay-dialog .el-dialog .el-dialog__body .el-row {
width: 100%;
}
.page-sd .el-overlay-dialog .el-dialog .el-dialog__body .el-row .img-container {
display: flex;
justify-content: center;
}
.page-sd .el-overlay-dialog .el-dialog .el-dialog__body .el-row .img-container .image-slot {
display: flex;
height: 100vh;
align-items: center;
justify-content: center;
}
.page-sd .el-overlay-dialog .el-dialog .el-dialog__body .el-row .img-container .image-slot .el-icon {
font-size: 60px;
}
.page-sd .el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info {
background-color: #25262b;
padding: 1rem 1.5rem;
}
.page-sd .el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .info-line {
width: 100%;
}
.page-sd .el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .info-line .prompt {
background-color: #35363b;
padding: 10px;
color: #999;
overflow: auto;
max-height: 100%;
min-height: 100%;
position: relative;
}
.page-sd .el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .info-line .prompt .el-icon {
position: absolute;
right: 10px;
bottom: 10px;
cursor: pointer;
}
.page-sd .el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .info-line .wrapper {
margin-top: 10px;
display: flex;
}
.page-sd .el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .info-line .wrapper label {
display: flex;
width:100%;
color: #a5a5a5;
}
.page-sd .el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .info-line .wrapper .item-value {
display: flex;
width: 100%;
background-color: #35363b;
padding: 2px 5px;
border-radius: 5px;
color: #f5f5f5;
}
.page-sd .el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .copy-params {
padding: 20px 0 10px 0;
}
.page-sd .el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .copy-params .el-button {
width: 100%;
}
.page-sd .mj-list-item-prompt .el-icon {
margin-left: 10px;
cursor: pointer;
position: relative;
top: 2px;
}

View File

@@ -1,106 +0,0 @@
.page-sd {
background-color: #282828;
.inner {
display: flex;
align-items: center;
justify-content: center;
.sd-box {
background-color #282828
min-width 92%
max-width 92%
padding 0px
border-radius 0px
color #ffffff;
font-size 14px
h2 {
font-weight: bold;
font-size 20px
text-align center
color #fff
}
//
::-webkit-scrollbar {
width: 0;
height: 0;
background-color: transparent;
}
.sd-params {
margin-top 10px
overflow auto
.param-line {
padding 0 10px
.el-icon {
position relative
top 3px
}
.el-input__suffix-inner {
.el-icon {
top 0
}
}
.grid-content
.form-item-inner {
display flex
.el-icon {
margin-left 10px
margin-top 2px
}
}
}
.param-line.pt {
padding-top 5px
padding-bottom 5px
}
}
.submit-btn {
padding 10px 15px 0 15px
text-align center
.el-button {
width 100%
span {
color #2D3A4B
}
}
}
}
.el-form {
.el-form-item__label {
color #ffffff
}
}
@import "mobile/task-mobile-list.styl"
}
@import "sd-task-mobile-dialog.styl"
.mj-list-item-prompt {
.el-icon {
margin-left 10px
cursor pointer
position relative
top 2px
}
}
}

View File

@@ -0,0 +1,138 @@
.mobile-mj .content .text-line {
padding: 6px;
font-size: 14px;
}
.mobile-mj .content .text-line .van-row .van-col .rate {
display: flex;
justify-content: center;
background-color: #f5f5f5;
padding: 5px 10px;
margin: 5px 0;
border-radius: 5px;
flex-flow: column;
}
.mobile-mj .content .text-line .van-row .van-col .rate .icon {
text-align: center;
}
.mobile-mj .content .text-line .van-row .van-col .rate .icon .van-image {
max-width: 20px;
}
.mobile-mj .content .text-line .van-row .van-col .rate .text {
text-align: center;
color: #555;
}
.mobile-mj .content .text-line .van-row .van-col .model {
display: flex;
justify-content: center;
background-color: #f5f5f5;
padding: 6px;
margin: 5px 0;
border-radius: 5px;
flex-flow: column;
}
.mobile-mj .content .text-line .van-row .van-col .model .icon {
text-align: center;
}
.mobile-mj .content .text-line .van-row .van-col .model .icon .van-image {
width: 100%;
height: 50px;
}
.mobile-mj .content .text-line .van-row .van-col .model .text {
text-align: center;
color: #555;
}
.mobile-mj .content .text-line .van-row .van-col .active {
background-color: #e5e5e5;
}
.mobile-mj .content .text-line .van-button {
position: relative;
}
.mobile-mj .content .text-line .van-button .van-tag {
position: absolute;
right: 20px;
}
.mobile-mj .content .running-job-list .van-grid .van-grid-item .van-grid-item__content {
padding: 0;
position: relative;
}
.mobile-mj .content .running-job-list .van-grid .van-grid-item .van-grid-item__content .van-image,
.mobile-mj .content .running-job-list .van-grid .van-grid-item .van-grid-item__content .task-in-queue {
min-height: 100px;
}
.mobile-mj .content .running-job-list .van-grid .van-grid-item .van-grid-item__content .progress {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
background: rgba(50,50,50,0.5);
position: absolute;
left: 0;
top: 0;
}
.mobile-mj .content .running-job-list .van-grid .van-grid-item .van-grid-item__content .progress .van-circle__text {
color: #fff;
}
.mobile-mj .content .running-job-list .van-grid .van-grid-item .van-grid-item__content .task-in-queue {
display: flex;
flex-flow: column;
justify-content: center;
color: #c1c1c1;
}
.mobile-mj .content .running-job-list .van-grid .van-grid-item .van-grid-item__content .task-in-queue .icon {
text-align: center;
}
.mobile-mj .content .running-job-list .van-grid .van-grid-item .van-grid-item__content .task-in-queue .icon .iconfont {
font-size: 24px;
}
.mobile-mj .content .running-job-list .van-grid .van-grid-item .van-grid-item__content .task-in-queue .text {
font-size: 14px;
margin-top: 5px;
}
.mobile-mj .content .finish-job-list .van-grid .van-grid-item .van-grid-item__content {
padding: 0;
}
.mobile-mj .content .finish-job-list .van-grid .van-grid-item .van-grid-item__content .job-item {
overflow: hidden;
border-radius: 6px;
position: relative;
height: 100%;
width: 100%;
}
.mobile-mj .content .finish-job-list .van-grid .van-grid-item .van-grid-item__content .job-item .opt .opt-btn {
padding: 3px 10px;
text-align: center;
border-radius: 5px;
margin: 3px 0;
display: block;
cursor: pointer;
background-color: #4e5058;
color: #fff;
}
.mobile-mj .content .finish-job-list .van-grid .van-grid-item .van-grid-item__content .job-item .el-image {
width: 100%;
height: 200px;
}
.mobile-mj .content .finish-job-list .van-grid .van-grid-item .van-grid-item__content .job-item .el-image .image-slot {
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.mobile-mj .content .finish-job-list .van-grid .van-grid-item .van-grid-item__content .job-item .el-image .image-slot .iconfont {
margin-right: 5px;
}
.mobile-mj .content .finish-job-list .van-grid .van-grid-item .van-grid-item__content .job-item .upscale {
height: 260px;
width: 100%;
}
.mobile-mj .content .finish-job-list .van-grid .van-grid-item .van-grid-item__content .job-item .remove {
position: absolute;
right: 5px;
top: 5px;
}
.mobile-mj .content .finish-job-list .van-grid .van-grid-item .van-grid-item__content .job-item .remove .el-button {
margin-left: 5px;
height: auto;
padding: 5px;
}

View File

@@ -0,0 +1,193 @@
.mobile-mj {
.content {
.text-line {
padding 6px
font-size 14px
.van-row {
.van-col {
.rate {
display: flex;
justify-content center
background-color #f5f5f5
padding 5px 10px
margin 5px 0
border-radius 5px
flex-flow column
.icon {
text-align center
.van-image {
max-width 20px
}
}
.text {
text-align center
color #555555
}
}
.model {
display: flex;
justify-content center
background-color #f5f5f5
padding 6px
margin 5px 0
border-radius 5px
flex-flow column
.icon {
text-align center
.van-image {
width 100%
height 50px
}
}
.text {
text-align center
color #555555
}
}
.active {
background-color #e5e5e5
}
}
}
.van-button {
position relative
.van-tag {
position absolute
right 20px
}
}
}
.running-job-list {
.van-grid {
.van-grid-item {
.van-grid-item__content {
padding 0
position relative
.van-image, .task-in-queue {
min-height 100px
}
.progress {
display flex
justify-content center
align-items center
width 100%
height 100%
background rgba(50, 50, 50, 0.5)
position absolute
left 0
top 0
.van-circle__text {
color #ffffff
}
}
// end progress
.task-in-queue {
display flex
flex-flow column
justify-content center
color #c1c1c1
.icon {
text-align center
.iconfont {
font-size 24px
}
}
.text {
font-size 14px
margin-top 5px
}
}
}
}
}
}
//end running jobs
.finish-job-list {
.van-grid {
.van-grid-item {
.van-grid-item__content {
padding 0
.job-item {
overflow hidden
border-radius 6px
position relative
height 100%
width 100%
.opt {
.opt-btn {
padding 3px 10px
text-align center
border-radius 5px
margin 3px 0
display block
cursor pointer
background-color #4E5058
color #ffffff
}
}
.el-image {
width 100%
height 200px
.image-slot {
height 100%
display flex
justify-content center
align-items center
.iconfont {
margin-right 5px
}
}
}
.upscale {
height 260px
width 100%
}
.remove {
position absolute
right 5px
top 5px
.el-button {
margin-left 5px
height auto
padding 5px
}
}
}
}
}
}
}
}
}

View File

@@ -1,252 +0,0 @@
.page-sd {
background-color: #282828;
}
.page-sd .inner {
display: flex;
align-items: center;
justify-content: center;
}
.page-sd .inner .sd-box {
background-color: #282828;
min-width: 92%;
max-width: 92%;
padding: 0px;
border-radius: 0px;
color: #fff;
font-size: 14px;
}
.page-sd .inner .sd-box h2 {
font-weight: bold;
font-size: 16px;
text-align: center;
color: #fff;
}
.page-sd .inner .sd-box ::-webkit-scrollbar {
width: 0;
height: 0;
background-color: transparent;
}
.page-sd .inner .sd-box .sd-params {
margin-top: 10px;
overflow: auto;
}
.page-sd .inner .sd-box .sd-params .param-line {
padding: 0 10px;
}
.page-sd .inner .sd-box .sd-params .param-line .el-icon {
position: relative;
top: 3px;
}
.page-sd .inner .sd-box .sd-params .param-line .el-input__suffix-inner .el-icon {
top: 0;
}
.page-sd .inner .sd-box .sd-params .param-line .grid-content,
.page-sd .inner .sd-box .sd-params .param-line .form-item-inner {
display: flex;
}
.page-sd .inner .sd-box .sd-params .param-line .grid-content .el-icon,
.page-sd .inner .sd-box .sd-params .param-line .form-item-inner .el-icon {
margin-left: 10px;
margin-top: 2px;
}
.page-sd .inner .sd-box .sd-params .param-line.pt {
padding-top: 5px;
padding-bottom: 5px;
}
.page-sd .inner .sd-box .submit-btn {
padding: 10px 15px 0 15px;
text-align: center;
}
.page-sd .inner .sd-box .submit-btn .el-button {
width: 100%;
}
.page-sd .inner .sd-box .submit-btn .el-button span {
color: #2d3a4b;
}
.page-sd .inner .el-form .el-form-item__label {
color: #fff;
}
.page-sd .inner .task-list-box {
width: 100%;
padding: 5px;
color: #fff;
overflow-x: hidden;
}
.page-sd .inner .task-list-box .running-job-list .job-item {
width: 100%;
padding: 2px;
background-color: #555;
}
.page-sd .inner .task-list-box .running-job-list .job-item .job-item-inner {
position: relative;
height: 100%;
overflow: hidden;
}
.page-sd .inner .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;
}
.page-sd .inner .task-list-box .running-job-list .job-item .job-item-inner .progress span {
font-size: 20px;
color: #fff;
}
.page-sd .inner .task-list-box .finish-job-list .job-item {
width: 100%;
height: 100%;
border: 1px solid #666;
padding: 6px;
overflow: hidden;
border-radius: 6px;
transition: all 0.3s ease; /* 添加过渡效果 */
}
.page-sd .inner .task-list-box .finish-job-list .job-item .opt .opt-line {
margin: 6px 0;
}
.page-sd .inner .task-list-box .finish-job-list .job-item .opt .opt-line ul {
display: flex;
flex-flow: row;
}
.page-sd .inner .task-list-box .finish-job-list .job-item .opt .opt-line ul li {
margin-right: 6px;
}
.page-sd .inner .task-list-box .finish-job-list .job-item .opt .opt-line ul li a {
padding: 3px 0;
width: 40px;
text-align: center;
border-radius: 5px;
display: block;
cursor: pointer;
background-color: #4e5058;
color: #fff;
}
.page-sd .inner .task-list-box .finish-job-list .job-item .opt .opt-line ul li a:hover {
background-color: #6d6f78;
}
.page-sd .inner .task-list-box .finish-job-list .job-item .opt .opt-line ul .show-prompt {
font-size: 20px;
cursor: pointer;
}
.page-sd .inner .task-list-box .finish-job-list .animate:hover {
box-shadow: 0 0 10px rgba(71,255,241,0.6); /* 添加阴影效果 */
transform: translateY(-10px); /* 向上移动10像素 */
}
.page-sd .inner .task-list-box .el-image {
width: 100%;
height: 100%;
overflow: visible;
}
.page-sd .inner .task-list-box .el-image img {
height: 240px;
}
.page-sd .inner .task-list-box .el-image .el-image-viewer__wrapper img {
width: auto;
height: auto;
}
.page-sd .inner .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;
height: 240px;
}
.page-sd .inner .task-list-box .el-image .image-slot .iconfont {
font-size: 50px;
margin-bottom: 10px;
}
.page-sd .inner .task-list-box .el-image.upscale {
max-height: 310px;
}
.page-sd .inner .task-list-box .el-image.upscale img {
height: 310px;
}
.page-sd .inner .task-list-box .el-image.upscale .el-image-viewer__wrapper img {
width: auto;
height: auto;
}
.page-sd .el-overlay-dialog .el-dialog {
background-color: #1a1b1e;
}
.page-sd .el-overlay-dialog .el-dialog .el-dialog__header .el-dialog__title {
color: #f5f5f5;
}
.page-sd .el-overlay-dialog .el-dialog .el-dialog__body {
padding: 0 0 0 15px !important;
display: flex;
height: 100%;
}
.page-sd .el-overlay-dialog .el-dialog .el-dialog__body .el-row {
width: 100%;
}
.page-sd .el-overlay-dialog .el-dialog .el-dialog__body .el-row .img-container {
display: flex;
justify-content: center;
}
.page-sd .el-overlay-dialog .el-dialog .el-dialog__body .el-row .img-container .image-slot {
display: flex;
height: 100vh;
align-items: center;
justify-content: center;
}
.page-sd .el-overlay-dialog .el-dialog .el-dialog__body .el-row .img-container .image-slot .el-icon {
font-size: 60px;
}
.page-sd .el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info {
background-color: #25262b;
padding: 1rem 1.5rem;
}
.page-sd .el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .info-line {
width: 100%;
}
.page-sd .el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .info-line .prompt {
background-color: #35363b;
padding: 10px;
color: #999;
overflow: auto;
max-height: 100px;
min-height: 50px;
position: relative;
}
.page-sd .el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .info-line .prompt .el-icon {
position: absolute;
right: 10px;
bottom: 10px;
cursor: pointer;
}
.page-sd .el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .info-line .wrapper {
margin-top: 10px;
display: flex;
}
.page-sd .el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .info-line .wrapper label {
display: flex;
width: 100px;
color: #a5a5a5;
}
.page-sd .el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .info-line .wrapper .item-value {
display: flex;
width: 100%;
background-color: #35363b;
padding: 2px 5px;
border-radius: 5px;
color: #f5f5f5;
}
.page-sd .el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .copy-params {
padding: 30px 0 10px 0;
}
.page-sd .el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .copy-params .el-button {
width: 100%;
}
.page-sd .mj-list-item-prompt .el-icon {
margin-left: 10px;
cursor: pointer;
position: relative;
top: 2px;
}

View File

@@ -1,106 +0,0 @@
.page-sd {
background-color: #282828;
.inner {
display: flex;
align-items: center;
justify-content: center;
.sd-box {
background-color #282828
min-width 92%
max-width 92%
padding 0px
border-radius 0px
color #ffffff;
font-size 14px
h2 {
font-weight: bold;
font-size 16px
text-align center
color #fff
}
//
::-webkit-scrollbar {
width: 0;
height: 0;
background-color: transparent;
}
.sd-params {
margin-top 10px
overflow auto
.param-line {
padding 0 10px
.el-icon {
position relative
top 3px
}
.el-input__suffix-inner {
.el-icon {
top 0
}
}
.grid-content
.form-item-inner {
display flex
.el-icon {
margin-left 10px
margin-top 2px
}
}
}
.param-line.pt {
padding-top 5px
padding-bottom 5px
}
}
.submit-btn {
padding 10px 15px 0 15px
text-align center
.el-button {
width 100%
span {
color #2D3A4B
}
}
}
}
.el-form {
.el-form-item__label {
color #ffffff
}
}
@import "./task-list.styl"
}
@import "./sd-task-dialog.styl"
.mj-list-item-prompt {
.el-icon {
margin-left 10px
cursor pointer
position relative
top 2px
}
}
}

View File

@@ -1,93 +0,0 @@
.page-invitation {
display: flex;
justify-content: center;
background-color: #1c1c1c;
height: 100vh;
overflow-x: hidden;
overflow-y: visible;
}
.page-invitation .inner {
max-width: 100%;
width: 100%;
color: #fff;
}
.page-invitation .inner .center {
display: flex;
justify-content: center;
align-items: center;
}
.page-invitation .inner .title {
color: #fff;
text-align: center;
font-size: 16px;
font-weight: bold;
}
.page-invitation .inner .share-box .info {
line-height: 1.5;
border: 1px solid #444;
border-radius: 10px;
padding: 10px;
margin: 10px;
background-color: #1c1c1c;
}
.page-invitation .inner .share-box .info strong {
color: #f56c6c;
}
.page-invitation .inner .share-box .invite-qrcode {
text-align: center;
margin: 15px;
}
.page-invitation .inner .share-box .invite-url {
margin: 10px;
padding: 8px;
display: flex;
justify-content: space-between;
border: 1px solid #444;
border-radius: 10px;
background-color: #1c1c1c;
}
.page-invitation .inner .share-box .invite-url span {
position: relative;
font-size: 14px;
font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
top: 0px;
}
.page-invitation .inner .invite-stats {
padding: 10px 10px;
}
.page-invitation .inner .invite-stats .item-box {
border-radius: 10px;
padding: 10 0px;
}
.page-invitation .inner .invite-stats .item-box .el-col {
height: 80px;
display: flex;
align-items: center;
justify-content: center;
}
.page-invitation .inner .invite-stats .item-box .el-col .iconfont {
font-size: 30px;
}
.page-invitation .inner .invite-stats .item-box .el-col .item-info {
font-size: 14px;
}
.page-invitation .inner .invite-stats .item-box .el-col .item-info .text,
.page-invitation .inner .invite-stats .item-box .el-col .item-info .num {
padding: 3px 0;
text-align: center;
}
.page-invitation .inner .invite-stats .item-box .el-col .item-info .num {
font-size: 14px;
}
.page-invitation .inner .invite-stats .yellow {
background-color: #fec;
color: #d68f00;
}
.page-invitation .inner .invite-stats .blue {
background-color: #d6e4ff;
color: #1062fe;
}
.page-invitation .inner .invite-stats .green {
background-color: #e7f8eb;
color: #2d9f46;
}

View File

@@ -1,94 +0,0 @@
.page-invitation {
display flex
justify-content center
background-color #1c1c1c
height 100vh
overflow-x hidden
overflow-y visible
.inner {
max-width 100%
width 100%
color #fff
.center {
display flex
justify-content center
align-items center
}
.title {
color #fff
text-align center
font-size 16px
font-weight bold
}
.share-box {
.info {
line-height 1.5
border 1px solid #444444
border-radius 10px
padding 10px
margin 10px
background-color #1c1c1c
strong {
color #f56c6c
}
}
.invite-qrcode {
text-align center
margin 15px
}
.invite-url {
margin 10px
padding 8px
display flex
justify-content space-between
border 1px solid #444444
border-radius 10px
background-color #1c1c1c
span {
position relative
font-size 14px
font-family 'Microsoft YaHei', '', Arial, sans-serif
top 0px
}
}
}
.invite-stats {
padding 10px 10px
.item-box {
border-radius 10px
padding 10 0px
.el-col {
height 80px
display flex
align-items center
justify-content center
.iconfont {
font-size 30px
}
.item-info {
font-size 14px
.text, .num {
padding 3px 0
text-align center
}
.num {
font-size 14px
}
}
}
}
.yellow {
background-color #ffeecc
color #D68F00
}
.blue {
background-color #D6E4FF
color #1062FE
}
.green {
background-color #E7F8EB
color #2D9F46
}
}
}
}

View File

@@ -1,142 +0,0 @@
.app-background {
background-color: #1c1c1c;
height: 100vh;
}
.member {
background-color: #1c1c1c;
height: 100vh;
}
.member .el-dialog .el-dialog__body {
padding-top: 5px;
}
.member .el-dialog .el-dialog__body .pay-container .count-down {
display: flex;
justify-content: center;
}
.member .el-dialog .el-dialog__body .pay-container .pay-qrcode {
display: flex;
justify-content: center;
}
.member .el-dialog .el-dialog__body .pay-container .pay-qrcode .el-image {
width: 280px;
height: 280px;
}
.member .el-dialog .el-dialog__body .pay-container .tip {
display: flex;
justify-content: center;
}
.member .el-dialog .el-dialog__body .pay-container .tip .el-icon {
font-size: 20px;
}
.member .el-dialog .el-dialog__body .pay-container .tip .text {
font-size: 16px;
margin-left: 10px;
}
.member .el-dialog .el-dialog__body .pay-container .tip.success {
color: #07c160;
}
.member .title {
text-align: center;
background-color: #1c1c1c;
font-size: 16px;
color: #fff;
padding: 5px;
font-weight: bold;
}
.member .inner {
color: #fff;
overflow-x: hidden;
overflow-y: visible;
}
.member .inner .user-profile {
padding: 0 20px 0 20px;
background-color: #1c1c1c;
color: #fff;
border-radius: 10px;
width: 100%;
height: 91vh;
}
.member .inner .user-profile .el-form-item__label {
color: #fff;
justify-content: start;
}
.member .inner .user-profile .user-opt .el-col {
padding: 8px;
}
.member .inner .user-profile .user-opt .el-col .el-button {
width: 100%;
}
.member .inner .product-box .info {
padding: 0 20px 0 20px;
}
.member .inner .product-box .info .el-alert__description {
font-size: 14px !important;
color: #1c1c1c;
margin: 0;
}
.member .inner .product-box .list-box {
padding: 0 20px 0 5px;
}
.member .inner .product-box .list-box .product-item {
border: 1px solid #5f5f5f;
border-radius: 6px;
overflow: hidden;
cursor: pointer;
transition: all 0.3s ease /* 添加过渡效果 */;
}
.member .inner .product-box .list-box .product-item .image-container {
display: flex;
justify-content: center;
}
.member .inner .product-box .list-box .product-item .image-container .el-image {
width: 50px;
}
.member .inner .product-box .list-box .product-item .image-container .el-image .el-image__inner {
border-radius: 10px;
}
.member .inner .product-box .list-box .product-item .product-title {
display: flex;
padding: 5px;
}
.member .inner .product-box .list-box .product-item .product-title .name {
width: 100%;
text-align: center;
font-size: 16px;
font-weight: bold;
color: #2cc995;
}
.member .inner .product-box .list-box .product-item .product-info {
padding: 10px 20px;
font-size: 14px;
color: #999;
}
.member .inner .product-box .list-box .product-item .product-info .info-line {
display: flex;
width: 100%;
padding: 2px 0;
}
.member .inner .product-box .list-box .product-item .product-info .info-line .label {
display: flex;
width: 100%;
}
.member .inner .product-box .list-box .product-item .product-info .info-line .price,
.member .inner .product-box .list-box .product-item .product-info .info-line .expire {
display: flex;
width: 100%;
justify-content: right;
}
.member .inner .product-box .list-box .product-item .product-info .info-line .price {
color: #f56c6c;
}
.member .inner .product-box .list-box .product-item .product-info .info-line .expire {
color: #409eff;
}
.member .inner .product-box .list-box .product-item .product-info .pay-way {
padding: 5px 0;
display: flex;
justify-content: space-between;
}
.member .inner .product-box .list-box .product-item hover {
box-shadow: 0 0 10px rgba(71,255,241,0.6) /* 添加阴影效果 */;
transform: translateY(-10px) /* 向上移动10像素 */;
}

View File

@@ -1,149 +0,0 @@
.app-background {
background-color #1c1c1c
height 100vh
}
.member {
background-color #1c1c1c
height 100vh
.el-dialog {
.el-dialog__body {
padding-top 5px
.pay-container {
.count-down {
display flex
justify-content center
}
.pay-qrcode {
display flex
justify-content center
.el-image {
width 280px
height 280px
}
}
.tip {
display flex
justify-content center
.el-icon {
font-size 20px
}
.text {
font-size 16px
margin-left 10px
}
}
.tip.success {
color #07c160
}
}
}
}
.title {
text-align center
background-color #1c1c1c
font-size 16px
color #ffffff
padding 5px
font-weight bold
}
.inner {
color #ffffff
overflow-x hidden
overflow-y visible
.user-profile {
padding 0 20px 0 20px
background-color #1c1c1c
color #ffffff
border-radius 10px
width 100%
height 91vh
.el-form-item__label {
color #ffffff
justify-content start
}
.user-opt {
.el-col {
padding 8px
.el-button {
width 100%
}
}
}
}
.product-box {
.info {
.el-alert__description {
font-size 14px !important
color #1c1c1c
margin 0
}
padding 0 20px 0 20px
}
.list-box {
padding 0 20px 0 5px
.product-item {
border 1px solid #5f5f5f
border-radius 6px
overflow hidden
cursor pointer
transition all 0.3s ease /* */
.image-container {
display flex
justify-content center
.el-image {
width 50px
.el-image__inner {
border-radius 10px
}
}
}
.product-title {
display flex
padding 5px
.name {
width 100%
text-align center
font-size 16px
font-weight bold
color #2cc995
}
}
.product-info {
padding 10px 20px
font-size 14px
color #999999
.info-line {
display flex
width 100%
padding 2px 0
.label {
display flex
width 100%
}
.price, .expire {
display flex
width 100%
justify-content right
}
.price {
color #f56c6c
}
.expire {
color #409eff
}
}
.pay-way {
padding 5px 0
display flex
justify-content space-between
}
}
& hover {
box-shadow 0 0 10px rgba(71, 255, 241, 0.6) /* */
transform translateY(-10px) /* 10 */
}
}
}
}
}
}

View File

@@ -1,72 +0,0 @@
.el-overlay-dialog .el-dialog {
background-color: #1a1b1e;
}
.el-overlay-dialog .el-dialog .el-dialog__header .el-dialog__title {
color: #f5f5f5;
}
.el-overlay-dialog .el-dialog .el-dialog__body {
padding: 0 0 0 15px !important;
display: flex;
height: 100%;
}
.el-overlay-dialog .el-dialog .el-dialog__body .el-row {
width: 100%;
}
.el-overlay-dialog .el-dialog .el-dialog__body .el-row .img-container {
display: flex;
justify-content: center;
}
.el-overlay-dialog .el-dialog .el-dialog__body .el-row .img-container .image-slot {
display: flex;
height: 100vh;
align-items: center;
justify-content: center;
}
.el-overlay-dialog .el-dialog .el-dialog__body .el-row .img-container .image-slot .el-icon {
font-size: 60px;
}
.el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info {
background-color: #25262b;
padding: 1rem 1.5rem;
}
.el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .info-line {
width: 100%;
}
.el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .info-line .prompt {
background-color: #35363b;
padding: 10px;
color: #999;
overflow: auto;
max-height: 100px;
min-height: 50px;
position: relative;
}
.el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .info-line .prompt .el-icon {
position: absolute;
right: 10px;
bottom: 10px;
cursor: pointer;
}
.el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .info-line .wrapper {
margin-top: 10px;
display: flex;
}
.el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .info-line .wrapper label {
display: flex;
width: 100px;
color: #a5a5a5;
}
.el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .info-line .wrapper .item-value {
display: flex;
width: 100%;
background-color: #35363b;
padding: 2px 5px;
border-radius: 5px;
color: #f5f5f5;
}
.el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .copy-params {
padding: 30px 0 10px 0;
}
.el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .copy-params .el-button {
width: 100%;
}

View File

@@ -1,96 +0,0 @@
.el-overlay-dialog {
.el-dialog {
background-color #1a1b1e
.el-dialog__header {
.el-dialog__title {
color #F5F5F5
}
}
.el-dialog__body {
padding 0 0 0 15px !important
display flex
height 100%
.el-row {
width 100%
.img-container {
display flex
justify-content center
.image-slot {
display flex
height 100vh
align-items center
justify-content center
.el-icon {
font-size 60px
}
}
}
.task-info {
background-color #25262b
padding 1rem 1.5rem
.info-line {
width 100%
.prompt {
background-color #35363b
padding 10px
color #999999
overflow auto
max-height 100px
min-height 50px
position relative
.el-icon {
position absolute
right 10px
bottom 10px
cursor pointer
}
}
.wrapper {
margin-top 10px
display flex
label {
display flex
width 100px
color #a5a5a5
}
.item-value {
display flex
width 100%
background-color #35363b
padding 2px 5px
border-radius 5px
color #F5F5F5
}
}
}
.copy-params {
padding 30px 0 10px 0
.el-button {
width 100%
}
}
}
}
// end el-row
}
}
}

View File

@@ -1,106 +0,0 @@
.task-list-box {
width: 100%;
padding: 5px;
color: #fff;
overflow-x: hidden;
}
.task-list-box .running-job-list .job-item {
width: 100%;
padding: 2px;
background-color: #555;
}
.task-list-box .running-job-list .job-item .job-item-inner {
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;
}
.task-list-box .running-job-list .job-item .job-item-inner .progress span {
font-size: 20px;
color: #fff;
}
.task-list-box .finish-job-list .job-item {
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;
}
.task-list-box .finish-job-list .job-item .opt .opt-line ul {
display: flex;
flex-flow: row;
}
.task-list-box .finish-job-list .job-item .opt .opt-line ul li {
margin-right: 6px;
}
.task-list-box .finish-job-list .job-item .opt .opt-line ul li a {
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;
}
.task-list-box .finish-job-list .job-item .opt .opt-line ul .show-prompt {
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%;
overflow: visible;
}
.task-list-box .el-image img {
height: 240px;
}
.task-list-box .el-image .el-image-viewer__wrapper img {
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;
height: 240px;
}
.task-list-box .el-image .image-slot .iconfont {
font-size: 50px;
margin-bottom: 10px;
}
.task-list-box .el-image.upscale {
max-height: 310px;
}
.task-list-box .el-image.upscale img {
height: 310px;
}
.task-list-box .el-image.upscale .el-image-viewer__wrapper img {
width: auto;
height: auto;
}

View File

@@ -1,142 +0,0 @@
.task-list-box {
width 100%
padding 5px
color #ffffff
overflow-x hidden
.running-job-list {
.job-item {
//border: 1px solid #454545;
width: 100%;
padding 2px
background-color #555555
.job-item-inner {
position relative
height 100%
overflow hidden
.progress {
position absolute
width 100%
height 100%
top 0
left 0
display flex
justify-content center
align-items center
span {
font-size 20px
color #ffffff
}
}
}
}
}
.finish-job-list {
.job-item {
width 100%
height 100%
border 1px solid #666666
padding 6px
overflow hidden
border-radius 6px
transition: all 0.3s ease; /* */
.opt {
.opt-line {
margin 6px 0
ul {
display flex
flex-flow row
li {
margin-right 6px
a {
padding 3px 0
width 40px
text-align center
border-radius 5px
display block
cursor pointer
background-color #4E5058
color #ffffff
&:hover {
background-color #6D6F78
}
}
}
.show-prompt {
font-size 20px
cursor pointer
}
}
}
}
}
.animate {
&:hover {
box-shadow: 0 0 10px rgba(71, 255, 241, 0.6); /* */
transform: translateY(-10px); /* 10 */
}
}
}
.el-image {
width 100%
height 100%
overflow visible
img {
height 240px
}
.el-image-viewer__wrapper {
img {
width auto
height auto
}
}
.image-slot {
display flex
flex-flow column
justify-content center
align-items center
height 100%
min-height 200px
color #ffffff
height 240px
.iconfont {
font-size 50px
margin-bottom 10px
}
}
}
.el-image.upscale {
max-height 310px
img {
height 310px
}
.el-image-viewer__wrapper {
img {
width auto
height auto
}
}
}
}

View File

@@ -23,10 +23,6 @@
background-color: #47FFF1;
}
.title-tabs .el-tabs__content {
padding: 10px 0;
}
.el-textarea {
--el-input-focus-border-color: #47FFF1;
}
@@ -65,31 +61,63 @@
color #ffffff
}
.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;
//
&:hover {
border-color: var(--el-color-primary);
.img-inline {
display flex
.img-uploader {
.el-upload {
border: 1px dashed var(--el-border-color);
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
width 120px;
transition: var(--el-transition-duration-fast);
margin-bottom: 20px;
&:hover {
border-color: var(--el-color-primary);
}
.el-icon.uploader-icon {
font-size: 28px
color: #8c939d
width 100%
height: 120px
text-align: center
}
}
}
.el-icon.uploader-icon {
font-size: 28px
color: #8c939d
width 100%
height: 120px
text-align: center
.img-list-box {
display flex
.img-item {
width 120px
position relative
margin-right 10px
.el-image {
width 120px
height 120px
border-radius 5px
}
.el-button {
position absolute
right 5px
top 5px
width 20px
height 20px
}
}
}
}
//
.submit-btn {
display flex
margin: 20px 0
@@ -105,154 +133,159 @@
align-items center
}
}
}
.running-job-list {
.job-item {
//border: 1px solid #454545;
width: 100%;
padding 2px
background-color #555555
.job-item-inner {
position relative
height 100%
overflow hidden
//
.progress {
position absolute
width 100%
height 100%
top 0
left 0
display flex
justify-content center
align-items center
.job-list-box {
.running-job-list {
.job-item {
//border: 1px solid #454545;
width: 100%;
padding 2px
background-color #555555
span {
font-size 20px
color #ffffff
.job-item-inner {
position relative
height 100%
overflow hidden
.progress {
position absolute
width 100%
height 100%
top 0
left 0
display flex
justify-content center
align-items center
span {
font-size 20px
color #ffffff
}
}
}
}
}
}
}
.finish-job-list {
.job-item {
width 100%
height 100%
border 1px solid #666666
padding 6px
overflow hidden
border-radius 6px
transition: all 0.3s ease; /* */
position relative
.finish-job-list {
.job-item {
width 100%
height 100%
border 1px solid #666666
padding 6px
overflow hidden
border-radius 6px
transition: all 0.3s ease; /* */
position relative
.opt {
.opt-line {
margin 6px 0
.opt {
.opt-line {
margin 6px 0
ul {
display flex
flex-flow row
ul {
display flex
flex-flow row
li {
margin-right 6px
li {
margin-right 6px
a {
padding 3px 0
width 40px
text-align center
border-radius 5px
display block
cursor pointer
background-color #4E5058
color #ffffff
a {
padding 3px 0
width 40px
text-align center
border-radius 5px
display block
cursor pointer
background-color #4E5058
color #ffffff
&:hover {
background-color #6D6F78
&:hover {
background-color #6D6F78
}
}
}
.show-prompt {
font-size 20px
cursor pointer
}
}
}
}
.show-prompt {
font-size 20px
cursor pointer
.remove {
display none
position absolute
right 10px
top 10px
}
&:hover {
.remove {
display block
}
}
}
.animate {
&:hover {
box-shadow: 0 0 10px rgba(71, 255, 241, 0.6); /* */
transform: translateY(-10px); /* 10 */
}
}
}
.el-image {
width 100%
height 100%
overflow visible
img {
height 240px
}
.el-image-viewer__wrapper {
img {
width auto
height auto
}
}
.image-slot {
display flex
flex-flow column
justify-content center
align-items center
min-height 200px
color #ffffff
height 240px
.iconfont {
font-size 50px
margin-bottom 10px
}
}
}
.remove {
display none
position absolute
right 10px
top 10px
}
.el-image.upscale {
max-height 310px
&:hover{
.remove {
display block
img {
height 310px
}
}
}
.animate {
&:hover {
box-shadow: 0 0 10px rgba(71, 255, 241, 0.6); /* */
transform: translateY(-10px); /* 10 */
}
}
}
.el-image {
width 100%
height 100%
overflow visible
img {
height 240px
}
.el-image-viewer__wrapper {
img {
width auto
height auto
}
}
.image-slot {
display flex
flex-flow column
justify-content center
align-items center
height 100%
min-height 200px
color #ffffff
height 240px
.iconfont {
font-size 50px
margin-bottom 10px
}
}
}
.el-image.upscale {
max-height 310px
img {
height 310px
}
.el-image-viewer__wrapper {
img {
width auto
height auto
.el-image-viewer__wrapper {
img {
width auto
height auto
}
}
}
}
}

View File

@@ -1,8 +1,8 @@
@font-face {
font-family: "iconfont"; /* Project id 4125778 */
src: url('iconfont.woff2?t=1705615887594') format('woff2'),
url('iconfont.woff?t=1705615887594') format('woff'),
url('iconfont.ttf?t=1705615887594') format('truetype');
src: url('iconfont.woff2?t=1708054962140') format('woff2'),
url('iconfont.woff?t=1708054962140') format('woff'),
url('iconfont.ttf?t=1708054962140') format('truetype');
}
.iconfont {
@@ -13,6 +13,10 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-prompt:before {
content: "\e6ce";
}
.icon-share-bold:before {
content: "\e626";
}

File diff suppressed because one or more lines are too long

View File

@@ -5,6 +5,13 @@
"css_prefix_text": "icon-",
"description": "",
"glyphs": [
{
"icon_id": "8017627",
"name": "prompt",
"font_class": "prompt",
"unicode": "e6ce",
"unicode_decimal": 59086
},
{
"icon_id": "1132455",
"name": "share-bold",

Binary file not shown.

View File

@@ -122,10 +122,13 @@ export default {
// ===============================================
let mouseX = (navigator.vendor === 'Netscape') ? e.pageX : e.x + document.body.offsetTop
let mouseY = (navigator.vendor === 'Netscape') ? e.pageY : e.y + document.body.offsetTop
if (this.calcPosType === 'screen') {
mouseX = (navigator.vendor === 'Netscape') ? e.clientX : e.x
mouseY = (navigator.vendor === 'Netscape') ? e.clientY : e.y
// 兼容移动触摸事件
if (e.touches && e.touches.length > 0) {
mouseX = e.touches[0].clientX
mouseY = e.touches[0].clientY
} else {
mouseX = e.clientX
mouseY = e.clientY
}
// 计算点击的相对位置

View File

@@ -39,9 +39,13 @@
effect="dark"
:content="file.name"
placement="top">
<el-image :src="file.url" fit="fill" v-if="isImage(file.ext)" @click="insertURL(file.url)"/>
<el-image :src="getFileIcon(file.ext)" fit="fill" v-else @click="insertURL(file.url)"/>
<el-image :src="file.url" fit="cover" v-if="isImage(file.ext)" @click="insertURL(file.url)"/>
<el-image :src="getFileIcon(file.ext)" fit="cover" v-else @click="insertURL(file.url)"/>
</el-tooltip>
<div class="opt">
<el-button type="danger" size="small" :icon="Delete" @click="removeFile(file)" circle/>
</div>
</div>
</el-col>
</el-row>
@@ -54,8 +58,8 @@
import {ref} from "vue";
import {ElMessage} from "element-plus";
import {httpGet, httpPost} from "@/utils/http";
import {PictureFilled, Plus} from "@element-plus/icons-vue";
import {isImage} from "@/utils/libs";
import {Delete, PictureFilled, Plus} from "@element-plus/icons-vue";
import {isImage, removeArrayItem} from "@/utils/libs";
const props = defineProps({
userId: String,
@@ -103,6 +107,17 @@ const afterRead = (file) => {
})
};
const removeFile = (file) => {
httpGet('/api/upload/remove?id=' + file.id).then(() => {
fileList.value = removeArrayItem(fileList.value, file, (v1, v2) => {
return v1.id === v2.id
})
ElMessage.success("文件删除成功!")
}).catch((e) => {
ElMessage.error('文件删除失败:' + e.message)
})
}
const insertURL = (url) => {
show.value = false
emits('selected', url)
@@ -129,6 +144,7 @@ const insertURL = (url) => {
.grid-content {
margin-bottom 10px
position relative
.avatar-uploader {
width 100%
@@ -145,6 +161,7 @@ const insertURL = (url) => {
}
.el-image {
width 100%
height 80px
border 1px solid #ffffff
border-radius 6px
@@ -160,6 +177,19 @@ const insertURL = (url) => {
color #20a0ff
font-size 40px
}
.opt {
display none
position absolute
top 5px
right 5px
}
&:hover {
.opt {
display block
}
}
}
}

View File

@@ -77,7 +77,7 @@ onMounted(() => {
padding: 5px 10px;
background-color: #98E165;
color #444444
font-size: 16px
font-size: 14px
border-radius: 5px
line-height 1.5
}

View File

@@ -7,7 +7,7 @@
<div class="chat-item">
<div class="triangle"></div>
<div class="content-box">
<div ref="contentRef" :data-clipboard-text="orgContent" class="content" v-html="content"></div>
<div :data-clipboard-text="orgContent" class="content content-mobile" v-html="content"></div>
</div>
</div>
@@ -34,17 +34,6 @@ const props = defineProps({
default: '/images/gpt-icon.png',
}
});
const contentRef = ref(null)
onMounted(() => {
const clipboard = new Clipboard(contentRef.value);
clipboard.on('success', () => {
showNotify({type: 'success', message: '复制成功', duration: 1000})
})
clipboard.on('error', () => {
showNotify({type: 'danger', message: '复制失败', duration: 2000})
})
})
</script>
<style lang="stylus">
@@ -95,7 +84,7 @@ onMounted(() => {
padding: 5px 10px;
color #444444
background-color: #ffffff;
font-size: 16px
font-size: 14px
border-radius: 5px;
p:last-child {
@@ -118,6 +107,77 @@ onMounted(() => {
max-width 100%
}
}
.code-container {
position relative
.hljs {
border-radius 10px
line-height 1.5
}
.copy-code-mobile {
position: absolute;
right 10px
top 10px
cursor pointer
font-size 12px
color #c1c1c1
&:hover {
color #20a0ff
}
}
}
.lang-name {
display none
position absolute;
right 10px
bottom 50px
padding 2px 6px 4px 6px
background-color #444444
border-radius 10px
color #00e0e0
}
// 设置表格边框
table {
width 100%
margin-bottom 1rem
color #212529
border-collapse collapse;
border 1px solid #dee2e6;
background-color #ffffff
thead {
th {
border 1px solid #dee2e6
vertical-align: bottom
border-bottom: 2px solid #dee2e6
padding 10px
}
}
td {
border 1px solid #dee2e6
padding 10px
}
}
// 代码快
blockquote {
margin 0
background-color: #ebfffe;
padding: 0.8rem 1.5rem;
border-left: 0.5rem solid;
border-color: #026863;
color: #2c3e50;
}
}
}

View File

@@ -5,24 +5,38 @@ import 'vant/lib/index.css';
import App from './App.vue'
import {createPinia} from "pinia";
import {
Badge,
Button,
Cell,
CellGroup,
Circle,
Col,
Collapse,
CollapseItem,
ConfigProvider,
Dialog,
DropdownItem,
DropdownMenu,
Empty,
Field,
Form,
Grid,
GridItem,
Icon,
Image,
ImagePreview,
Lazyload,
List,
Loading,
NavBar,
Notify,
Overlay,
Picker,
Popup,
Row,
Search,
ShareSheet,
Slider,
Sticky,
SwipeCell,
Switch,
@@ -65,6 +79,20 @@ app.use(Switch);
app.use(Uploader);
app.use(Tag);
app.use(V3waterfall)
app.use(Overlay)
app.use(Col)
app.use(Row)
app.use(Slider)
app.use(Badge)
app.use(Collapse);
app.use(CollapseItem);
app.use(Grid);
app.use(GridItem);
app.use(Empty);
app.use(Circle);
app.use(Loading);
app.use(Lazyload);
app.use(ImagePreview);
app.use(router).use(ElementPlus).mount('#app')

View File

@@ -159,32 +159,23 @@ const routes = [
]
},
{
path: '/mobile/chat/session',
name: 'mobile-chat-session',
component: () => import('@/views/mobile/ChatSession.vue'),
},
{
name: 'mobile',
path: '/mobile',
meta: {title: 'ChatPuls-智能助手V3'},
meta: {title: 'ChatPlus-智能助手V3'},
component: () => import('@/views/mobile/Home.vue'),
redirect: '/mobile/chat/list',
redirect: '/mobile/chat',
children: [
{
path: '/mobile/chat/list',
name: 'mobile-chat-list',
path: '/mobile/chat',
name: 'mobile-chat',
component: () => import('@/views/mobile/ChatList.vue'),
},
{
path: '/mobile/imageSd',
name: 'mobile-imageSd',
component: () => import('@/views/mobile/ImageSd.vue'),
},
{
path: '/mobile/apps',
name: 'mobile-apps',
component: () => import('@/views/mobile/Apps.vue'),
path: '/mobile/mj',
name: 'mobile-mj',
component: () => import('@/views/mobile/ImageMj.vue'),
},
{
path: '/mobile/profile',
@@ -192,12 +183,23 @@ const routes = [
component: () => import('@/views/mobile/Profile.vue'),
},
{
path: '/mobile/invitation',
name: 'mobile-invitation',
component: () => import('@/views/mobile/Invitation.vue'),
path: '/mobile/apps',
name: 'mobile-app',
component: () => import('@/views/mobile/ChatApps.vue'),
},
]
},
{
path: '/mobile/chat/session',
name: 'mobile-chat-session',
component: () => import('@/views/mobile/ChatSession.vue'),
},
{
path: '/mobile/chat/export',
name: 'mobile-chat-export',
component: () => import('@/views/mobile/ChatExport.vue'),
},
{
name: 'test',
path: '/test',

View File

@@ -166,3 +166,40 @@ export function isImage(url) {
return expr.test(url);
}
export function processContent(content) {
//process img url
const linkRegex = /(https?:\/\/\S+)/g;
const links = content.match(linkRegex);
if (links) {
for (let link of links) {
if (isImage(link)) {
const index = content.indexOf(link)
if (content.substring(index - 1, 2) !== "]") {
content = content.replace(link, "\n![](" + link + ")\n")
}
}
}
}
// 处理引用块
if (content.indexOf("\n") === -1) {
return content
}
const texts = content.split("\n")
const lines = []
for (let txt of texts) {
lines.push(txt)
if (txt.startsWith(">")) {
lines.push("\n")
}
}
return lines.join("\n")
}
export function escapeHTML(html) {
return html.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
}

View File

@@ -14,7 +14,6 @@
:icon="item.icon"
:created-at="dateFormat(item['created_at'])"
:tokens="item['tokens']"
:model="model"
:content="item.content"/>
<chat-reply v-else-if="item.type==='reply'"
:icon="item.icon"

View File

@@ -117,12 +117,27 @@
<span>导出会话</span>
</el-button>
<el-button type="warning" @click="showFeedbackDialog = true">
<el-icon>
<Promotion/>
</el-icon>
<span>意见反馈</span>
</el-button>
<el-tooltip class="box-item"
effect="dark"
content="部署文档"
placement="bottom">
<a href="https://ai.r9it.com/docs/install/" target="_blank">
<el-button type="primary" circle>
<i class="iconfont icon-book"></i>
</el-button>
</a>
</el-tooltip>
<el-tooltip class="box-item"
effect="dark"
content="项目源码"
placement="bottom">
<a href="https://github.com/yangjian102621/chatgpt-plus" target="_blank">
<el-button type="success" circle>
<i class="iconfont icon-github"></i>
</el-button>
</a>
</el-tooltip>
</div>
</div>
@@ -204,28 +219,6 @@
</el-main>
</el-container>
<el-dialog
v-model="showFeedbackDialog"
:show-close="true"
width="340px"
title="意见反馈"
>
<el-alert type="info" :closable="false">
<div style="font-size: 14px">
如果您对本项目有任何改进意见您可以通过 Github
<el-link style="color: #f56c6c; font-weight: bold;"
href="https://github.com/yangjian102621/chatgpt-plus/issues">
提交改进意见
</el-link>
或者通过扫描下面的微信二维码加入 AI 技术交流群
</div>
</el-alert>
<div style="text-align: center;padding-top: 10px;">
<el-image :src="wechatCardURL"/>
</div>
</el-dialog>
<el-dialog
v-model="showNotice"
:show-close="true"
@@ -256,7 +249,7 @@ import {
ArrowDown,
Check,
Close,
Delete,
Delete, Document,
Edit,
Plus,
Promotion,
@@ -266,7 +259,16 @@ import {
VideoPause
} from '@element-plus/icons-vue'
import 'highlight.js/styles/a11y-dark.css'
import {dateFormat, isImage, isMobile, randString, removeArrayItem, UUID} from "@/utils/libs";
import {
dateFormat,
escapeHTML,
isImage,
isMobile,
processContent,
randString,
removeArrayItem,
UUID
} from "@/utils/libs";
import {ElMessage, ElMessageBox} from "element-plus";
import hl from "highlight.js";
import {getSessionId, getUserToken, removeUserToken} from "@/store/session";
@@ -299,11 +301,9 @@ const showConfigDialog = ref(false);
const isLogin = ref(false)
const showHello = ref(true)
const textInput = ref(null)
const showFeedbackDialog = ref(false)
const showNotice = ref(false)
const notice = ref("")
const noticeKey = ref("SYSTEM_NOTICE")
const wechatCardURL = ref("/images/wx.png")
if (isMobile()) {
router.replace("/mobile")
@@ -353,7 +353,6 @@ onMounted(() => {
// 获取系统配置
httpGet("/api/admin/config/get?key=system").then(res => {
title.value = res.data.title
wechatCardURL.value = res.data['wechat_card_url']
}).catch(e => {
ElMessage.error("获取系统配置失败:" + e.message)
})
@@ -509,6 +508,8 @@ const removeChat = function (event, chat) {
curOpt.value = 'remove';
}
const latexPlugin = require('markdown-it-latex2img')
const mathjaxPlugin = require('markdown-it-mathjax')
const md = require('markdown-it')({
breaks: true,
html: true,
@@ -533,6 +534,8 @@ const md = require('markdown-it')({
return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code>${copyBtn}</pre>`
}
});
md.use(latexPlugin)
md.use(mathjaxPlugin)
// 创建 socket 连接
const prompt = ref('');
@@ -567,6 +570,19 @@ const connect = function (chat_id, role_id) {
host = 'ws://' + location.host;
}
}
// 心跳函数
const sendHeartbeat = () => {
clearTimeout(heartbeatHandle.value)
new Promise((resolve, reject) => {
if (socket.value !== null) {
socket.value.send(JSON.stringify({type: "heartbeat", content: "ping"}))
}
resolve("success")
}).then(() => {
heartbeatHandle.value = setTimeout(() => sendHeartbeat(), 5000)
});
}
const _socket = new WebSocket(host + `/api/chat/new?session_id=${_sessionId}&role_id=${role_id}&chat_id=${chat_id}&model_id=${modelID.value}&token=${getUserToken()}`);
_socket.addEventListener('open', () => {
chatData.value = []; // 初始化聊天数据
@@ -589,15 +605,8 @@ const connect = function (chat_id, role_id) {
} else { // 加载聊天记录
loadChatHistory(chat_id);
}
// 发送心跳消息
clearInterval(heartbeatHandle.value)
heartbeatHandle.value = setInterval(() => {
if (socket.value !== null) {
socket.value.send(JSON.stringify({type: "heartbeat", content: "ping"}))
}
}, 5000);
sendHeartbeat()
});
_socket.addEventListener('message', event => {
@@ -717,7 +726,7 @@ const sendMessage = function () {
type: "prompt",
id: randString(32),
icon: loginUser.value.avatar,
content: md.render(processContent(prompt.value)),
content: md.render(escapeHTML(processContent(prompt.value))),
created_at: new Date().getTime(),
});
@@ -797,37 +806,6 @@ const loadChatHistory = function (chatId) {
})
}
const processContent = (content) => {
//process img url
const linkRegex = /(https?:\/\/\S+)/g;
const links = content.match(linkRegex);
if (links) {
for (let link of links) {
if (isImage(link)) {
const index = content.indexOf(link)
if (content.substring(index - 1, 2) !== "]") {
content = content.replace(link, "\n![](" + link + ")\n")
}
}
}
}
// 处理引用块
if (content.indexOf("\n") === -1) {
return content
}
const texts = content.split("\n")
const lines = []
for (let txt of texts) {
lines.push(txt)
if (txt.startsWith(">")) {
lines.push("\n")
}
}
return lines.join("\n")
}
const stopGenerate = function () {
showStopGenerate.value = false;
httpGet("/api/chat/stop?session_id=" + getSessionId()).then(() => {
@@ -846,7 +824,7 @@ const reGenerate = function () {
icon: loginUser.value.avatar,
content: md.render(text)
});
socket.value.send(text);
socket.value.send(JSON.stringify({type: "chat", content: previousText.value}));
}
const chatName = ref('')

View File

@@ -165,160 +165,241 @@
</div>
<div class="task-list-box">
<div class="task-list-inner" :style="{ height: listBoxHeight + 'px' }">
<h2>AI绘画</h2>
<el-form>
<el-tabs v-model="activeName" class="title-tabs">
<el-tab-pane label="生图(可选)" name="图生图">
<div class="text">图生图以某张图片为底稿参考来创作绘画生成类似风格或类型图像支持 PNG JPG 格式图片
</div>
<div class="param-line pt">
<el-form-item label="">
<template #default>
<div class="form-item-inner flex-row items-center">
<el-input v-model="params.img" size="small" placeholder="请输入图片地址或者上传图片"
style="width: 300px;"/>
<el-icon @click="params.img = ''" title="清空图片">
<DeleteFilled/>
</el-icon>
<el-tooltip effect="light"
content="垫图:以某张图片为底稿参考来创作绘画 <br/> 支持 PNG 和 JPG 格式图片"
raw-content placement="right">
<el-icon>
<InfoFilled/>
</el-icon>
</el-tooltip>
</div>
</template>
</el-form-item>
</div>
<div class="param-line">
<el-upload class="img-uploader" :auto-upload="true" :show-file-list="false"
:http-request="afterRead" style="--el-color-primary:#47fff1">
<el-image v-if="params.img !== ''" :src="params.img" fit="cover"/>
<el-icon v-else class="uploader-icon">
<Plus/>
</el-icon>
</el-upload>
</div>
<div class="param-line" style="padding-top: 10px">
<el-form-item label="图像权重:">
<template #default>
<div class="form-item-inner">
<el-slider v-model.number="params.weight" :max="1" :step="0.01"
style="width: 180px;--el-slider-main-bg-color:#47fff1"/>
<el-tooltip effect="light"
content="使用图像权重参数--iw来调整图像 URL 与文本的重要性 <br/>权重较高时意味着图像提示将对完成的作业产生更大的影响"
raw-content placement="right">
<el-icon style="margin-top: 9px">
<InfoFilled/>
</el-icon>
</el-tooltip>
</div>
</template>
</el-form-item>
</div>
</el-tab-pane>
<el-tab-pane label="图生文(可选)" name="图生文">
<div class="text">图生文功能正在紧锣密鼓开发中敬请期待...</div>
<!-- <div class="param-line pt">-->
<!-- <el-empty image-size="100px" description="功能建设中"/>-->
<!-- </div>-->
</el-tab-pane>
<el-tab-pane label="融图(可选)" name="融图">
<div class="text">融图功能正在紧锣密鼓开发中敬请期待...</div>
</el-tab-pane>
</el-tabs>
<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 class="extra-params">
<el-form>
<el-tabs v-model="activeName" class="title-tabs" @tabChange="tabChange">
<el-tab-pane label="生图(可选)" name="image">
<div class="text">图生图以某张图片为底稿参考来创作绘画生成类似风格或类型图像支持 PNG JPG 格式图片
</div>
<div>
<el-button type="primary" @click="translatePrompt">
<el-icon style="margin-right: 6px;font-size: 18px;">
<Refresh/>
</el-icon>
翻译
</el-button>
<el-tooltip
class="box-item"
effect="light"
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 class="param-line pt">
<el-form-item label="">
<template #default>
<div class="form-item-inner flex-row items-center">
<el-input v-model="params.img" size="small" placeholder="请输入图片地址或者上传图片"
style="width: 300px;"/>
<el-icon @click="params.img = ''" title="清空图片">
<DeleteFilled/>
</el-icon>
<el-tooltip effect="light"
content="垫图:以某张图片为底稿参考来创作绘画 <br/> 支持 PNG 和 JPG 格式图片"
raw-content placement="right">
<el-icon>
<InfoFilled/>
</el-icon>
</el-tooltip>
</div>
</template>
</el-form-item>
</div>
<div class="param-line">
<div class="img-inline">
<div class="img-list-box">
<div class="img-item" v-for="imgURL in imgList">
<el-image :src="imgURL" fit="cover"/>
<el-button type="danger" :icon="Delete" @click="removeUploadImage(imgURL)" circle/>
</div>
</div>
<el-upload class="img-uploader" :auto-upload="true" :show-file-list="false"
:http-request="uploadImg" style="--el-color-primary:#47fff1">
<el-icon class="uploader-icon">
<Plus/>
</el-icon>
</el-upload>
</div>
</div>
<div class="param-line" style="padding-top: 10px">
<el-form-item label="图像权重:">
<template #default>
<div class="form-item-inner">
<el-slider v-model.number="params.weight" :max="1" :step="0.01"
style="width: 180px;--el-slider-main-bg-color:#47fff1"/>
<el-tooltip effect="light"
content="使用图像权重参数--iw来调整图像 URL 与文本的重要性 <br/>权重较高时意味着图像提示将对完成的作业产生更大的影响"
raw-content placement="right">
<el-icon style="margin-top: 9px">
<InfoFilled/>
</el-icon>
</el-tooltip>
</div>
</template>
</el-form-item>
</div>
<div class="prompt-box">
<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>
<div>
<el-button type="primary" @click="translatePrompt(false)" :disabled="loading">
<el-icon style="margin-right: 6px;font-size: 18px;">
<Refresh/>
</el-icon>
翻译
</el-button>
<el-tooltip
class="box-item"
effect="light"
raw-content
content="使用 AI 翻译并重写提示词,<br/>增加更多细节,风格等描述"
placement="top-end"
>
<el-button type="success" @click="rewritePrompt" :disabled="loading">
<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 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="primary" @click="translatePrompt(true)" :disabled="loading">
<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.neg_prompt" :autosize="{ minRows: 4, maxRows: 6 }" type="textarea"
ref="promptRef"
placeholder="这里输入你不希望出现在图片上的内容,元素"/>
</div>
</div>
</el-tab-pane>
<el-tab-pane label="融图(可选)" name="blend">
<div class="text">请上传两张以上的图片最多不超过五张超过五张图片请使用文生图功能</div>
<div class="img-inline">
<div class="img-list-box">
<div class="img-item" v-for="imgURL in imgList">
<el-image :src="imgURL" fit="cover"/>
<el-button type="danger" :icon="Delete" @click="removeUploadImage(imgURL)" circle/>
</div>
</div>
<el-upload class="img-uploader" :auto-upload="true" :show-file-list="false"
:http-request="uploadImg" style="--el-color-primary:#47fff1">
<el-icon class="uploader-icon">
<Plus/>
</el-icon>
</el-upload>
</div>
</el-tab-pane>
<el-tab-pane label="换脸(可选)" name="swapFace">
<div class="text">请上传两张有脸部的图片用右边图片的脸替换左边图片的脸</div>
<div class="img-inline">
<div class="img-list-box">
<div class="img-item" v-for="imgURL in imgList">
<el-image :src="imgURL" fit="cover"/>
<el-button type="danger" :icon="Delete" @click="removeUploadImage(imgURL)" circle/>
</div>
</div>
<el-upload class="img-uploader" :auto-upload="true" :show-file-list="false"
:http-request="uploadImg" style="--el-color-primary:#47fff1">
<el-icon class="uploader-icon">
<Plus/>
</el-icon>
</el-upload>
</div>
</el-tab-pane>
</el-tabs>
<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>
</div>
</div>
</el-form>
</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 class="job-list-box">
<h2>任务列表</h2>
<div class="running-job-list">
<ItemList :items="runningJobs" v-if="runningJobs.length > 0">
<template #default="scope">
<div class="job-item">
<div v-if="scope.item.progress > 0" class="job-item-inner">
<el-image :src="scope.item['img_url']" :zoom-rate="1.2"
:preview-src-list="[scope.item['img_url']]" fit="cover" :initial-index="0"
loading="lazy">
<template #placeholder>
<div class="image-slot">
正在加载图片
</div>
</template>
<template #error>
<div class="image-slot">
<el-icon>
<Picture/>
</el-icon>
</div>
</template>
</el-image>
<div class="progress">
<el-progress type="circle" :percentage="scope.item.progress" :width="100"
color="#47fff1"/>
</div>
</div>
<el-image fit="cover" v-else>
<template #error>
<div class="image-slot">
<i class="iconfont icon-quick-start"></i>
<span>任务正在排队中</span>
</div>
</template>
</el-image>
</div>
</template>
</ItemList>
<el-empty :image-size="100" v-else/>
</div>
<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">-->
<!-- <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.neg_prompt" :autosize="{ minRows: 4, maxRows: 6 }" type="textarea"
ref="promptRef"
placeholder="这里输入你不希望出现在图片上的内容,元素"/>
</div>
<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>
</div>
</div>
</el-form>
<h2>任务列表</h2>
<div class="running-job-list">
<ItemList :items="runningJobs" v-if="runningJobs.length > 0">
<template #default="scope">
<div class="job-item">
<div v-if="scope.item.progress > 0" class="job-item-inner">
<el-image :src="scope.item['img_url']" :zoom-rate="1.2"
:preview-src-list="[scope.item['img_url']]" fit="cover" :initial-index="0"
loading="lazy">
<h2>创作记录</h2>
<div class="finish-job-list">
<ItemList :items="finishedJobs" v-if="finishedJobs.length > 0" :width="240" :gap="16">
<template #default="scope">
<div class="job-item">
<el-image
:src="scope.item['thumb_url']"
:class="scope.item['can_opt'] ? '' : 'upscale'" :zoom-rate="1.2"
:preview-src-list="[scope.item['img_url']]" fit="cover" :initial-index="scope.index"
loading="lazy" v-if="scope.item.progress > 0">
<template #placeholder>
<div class="image-slot">
正在加载图片
@@ -326,7 +407,11 @@
</template>
<template #error>
<div class="image-slot">
<div class="image-slot" v-if="scope.item['img_url'] === ''">
<i class="iconfont icon-loading"></i>
<span>正在下载图片</span>
</div>
<div class="image-slot" v-else>
<el-icon>
<Picture/>
</el-icon>
@@ -334,109 +419,63 @@
</template>
</el-image>
<div class="progress">
<el-progress type="circle" :percentage="scope.item.progress" :width="100"
color="#47fff1"/>
</div>
</div>
<el-image fit="cover" v-else>
<template #error>
<div class="image-slot">
<i class="iconfont icon-quick-start"></i>
<span>任务正在排队中</span>
</div>
</template>
</el-image>
</div>
</template>
</ItemList>
<el-empty :image-size="100" v-else/>
</div>
<div class="opt" v-if="scope.item['can_opt']">
<div class="opt-line">
<ul>
<li><a @click="upscale(1, scope.item)">U1</a></li>
<li><a @click="upscale(2, scope.item)">U2</a></li>
<li><a @click="upscale(3, scope.item)">U3</a></li>
<li><a @click="upscale(4, scope.item)">U4</a></li>
<li class="show-prompt">
<h2>创作记录</h2>
<div class="finish-job-list">
<ItemList :items="finishedJobs" v-if="finishedJobs.length > 0" :width="240" :gap="16">
<template #default="scope">
<div class="job-item">
<el-image
:src="scope.item['thumb_url']"
:class="scope.item.type === 'upscale' ? 'upscale' : ''" :zoom-rate="1.2"
:preview-src-list="[scope.item['img_url']]" fit="cover" :initial-index="scope.index"
loading="lazy" v-if="scope.item.progress > 0">
<template #placeholder>
<div class="image-slot">
正在加载图片
</div>
</template>
<template #error>
<div class="image-slot" v-if="scope.item['img_url'] === ''">
<i class="iconfont icon-loading"></i>
<span>正在下载图片</span>
</div>
<div class="image-slot" v-else>
<el-icon>
<Picture/>
</el-icon>
</div>
</template>
</el-image>
<div class="opt" v-if="scope.item.type !== 'upscale'">
<div class="opt-line">
<ul>
<li><a @click="upscale(1, scope.item)">U1</a></li>
<li><a @click="upscale(2, scope.item)">U2</a></li>
<li><a @click="upscale(3, scope.item)">U3</a></li>
<li><a @click="upscale(4, scope.item)">U4</a></li>
<li class="show-prompt">
<el-popover placement="left" title="提示词" :width="240" trigger="hover">
<template #reference>
<el-icon>
<ChromeFilled/>
</el-icon>
</template>
<template #default>
<div class="mj-list-item-prompt">
<span>{{ scope.item.prompt }}</span>
<el-icon class="copy-prompt"
:data-clipboard-text="scope.item.prompt">
<DocumentCopy/>
<el-popover placement="left" title="提示词" :width="240" trigger="hover">
<template #reference>
<el-icon>
<ChromeFilled/>
</el-icon>
</div>
</template>
</el-popover>
</li>
</ul>
</template>
<template #default>
<div class="mj-list-item-prompt">
<span>{{ scope.item.prompt }}</span>
<el-icon class="copy-prompt"
:data-clipboard-text="scope.item.prompt">
<DocumentCopy/>
</el-icon>
</div>
</template>
</el-popover>
</li>
</ul>
</div>
<div class="opt-line">
<ul>
<li><a @click="variation(1, scope.item)">V1</a></li>
<li><a @click="variation(2, scope.item)">V2</a></li>
<li><a @click="variation(3, scope.item)">V3</a></li>
<li><a @click="variation(4, scope.item)">V4</a></li>
</ul>
</div>
</div>
<div class="opt-line">
<ul>
<li><a @click="variation(1, scope.item)">V1</a></li>
<li><a @click="variation(2, scope.item)">V2</a></li>
<li><a @click="variation(3, scope.item)">V3</a></li>
<li><a @click="variation(4, scope.item)">V4</a></li>
</ul>
<div class="remove">
<el-button type="danger" :icon="Delete" @click="removeImage(scope.item)" circle/>
<el-button type="warning" v-if="scope.item.publish" @click="publishImage(scope.item, false)"
circle>
<i class="iconfont icon-cancel-share"></i>
</el-button>
<el-button type="success" v-else @click="publishImage(scope.item, true)" circle>
<i class="iconfont icon-share-bold"></i>
</el-button>
</div>
</div>
</template>
</ItemList>
<div class="remove">
<el-button type="danger" :icon="Delete" @click="removeImage(scope.item)" circle/>
<el-button type="warning" v-if="scope.item.publish" @click="publishImage(scope.item, false)" circle>
<i class="iconfont icon-cancel-share"></i>
</el-button>
<el-button type="success" v-else @click="publishImage(scope.item, true)" circle>
<i class="iconfont icon-share-bold"></i>
</el-button>
</div>
</div>
</template>
</ItemList>
<el-empty :image-size="100" v-else/>
</div> <!-- end finish job list-->
<el-empty :image-size="100" v-else/>
</div> <!-- end finish job list-->
</div>
</div>
</div><!-- end task list box -->
@@ -465,6 +504,7 @@ import Clipboard from "clipboard";
import {checkSession} from "@/action/session";
import {useRouter} from "vue-router";
import {getSessionId} from "@/store/session";
import {isMobile, removeArrayItem} from "@/utils/libs";
const listBoxHeight = ref(window.innerHeight - 40)
const mjBoxHeight = ref(window.innerHeight - 150)
@@ -516,13 +556,14 @@ const options = [
]
const params = ref({
task_type: "image",
rate: rates[0].value,
model: models[0].value,
chaos: 0,
stylize: 100,
stylize: 0,
seed: 0,
img_arr: [],
raw: false,
img: "",
weight: 0.25,
prompt: "",
neg_prompt: "",
@@ -530,7 +571,9 @@ const params = ref({
quality: 0
})
const activeName = ref('图生图')
const imgList = ref([])
const activeName = ref('image')
const runningJobs = ref([])
const finishedJobs = ref([])
@@ -541,6 +584,10 @@ const imgCalls = ref(0)
const loading = ref(false)
const userId = ref(0)
if (isMobile()) {
router.replace("/mobile/mj")
}
const rewritePrompt = () => {
loading.value = true
httpPost("/api/prompt/rewrite", {"prompt": params.value.prompt}).then(res => {
@@ -552,10 +599,18 @@ const rewritePrompt = () => {
})
}
const translatePrompt = () => {
const translatePrompt = (negative) => {
loading.value = true
httpPost("/api/prompt/translate", {"prompt": params.value.prompt}).then(res => {
params.value.prompt = res.data
let prompt = params.value.prompt
if (negative) {
prompt = params.value.neg_prompt
}
httpPost("/api/prompt/translate", {"prompt": prompt}).then(res => {
if (negative) {
params.value.neg_prompt = res.data
} else {
params.value.prompt = res.data
}
loading.value = false
}).catch(e => {
loading.value = false
@@ -563,6 +618,7 @@ const translatePrompt = () => {
})
}
const heartbeatHandle = ref(null)
const connect = () => {
let host = process.env.VUE_APP_WS_HOST
if (host === '') {
@@ -572,9 +628,26 @@ const connect = () => {
host = 'ws://' + location.host;
}
}
// 心跳函数
const sendHeartbeat = () => {
clearTimeout(heartbeatHandle.value)
new Promise((resolve, reject) => {
if (socket.value !== null) {
socket.value.send(JSON.stringify({type: "heartbeat", content: "ping"}))
}
resolve("success")
}).then(() => {
heartbeatHandle.value = setTimeout(() => sendHeartbeat(), 5000)
});
}
const _socket = new WebSocket(host + `/api/mj/client?user_id=${userId.value}`);
_socket.addEventListener('open', () => {
socket.value = _socket;
// 发送心跳消息
sendHeartbeat()
});
_socket.addEventListener('message', event => {
@@ -621,7 +694,8 @@ const fetchRunningJobs = (userId) => {
if (jobs[i].progress === -1) {
ElNotification({
title: '任务执行失败',
message: "任务ID" + jobs[i]['task_id'],
dangerouslyUseHTMLString: true,
message: `任务ID${jobs[i]['task_id']}<br />原因:${jobs[i]['err_msg']}`,
type: 'error',
})
imgCalls.value += 1
@@ -643,12 +717,16 @@ const fetchFinishJobs = (userId) => {
if (jobs[i]['use_proxy']) {
jobs[i]['thumb_url'] = jobs[i]['img_url'] + '?x-oss-process=image/quality,q_60&format=webp'
} else {
if (jobs[i].type === 'upscale') {
if (jobs[i].type === 'upscale' || jobs[i].type === 'swapFace') {
jobs[i]['thumb_url'] = jobs[i]['img_url'] + '?imageView2/1/w/480/h/600/q/75'
} else {
jobs[i]['thumb_url'] = jobs[i]['img_url'] + '?imageView2/1/w/480/h/480/q/75'
}
}
if (jobs[i].type === 'image' || jobs[i].type === 'variation') {
jobs[i]['can_opt'] = true
}
}
finishedJobs.value = jobs
}).catch(e => {
@@ -666,7 +744,7 @@ const changeModel = (item) => {
}
// 图片上传
const afterRead = (file) => {
const uploadImg = (file) => {
// 压缩图片并上传
new Compressor(file.file, {
quality: 0.6,
@@ -675,7 +753,7 @@ const afterRead = (file) => {
formData.append('file', result, result.name);
// 执行上传操作
httpPost('/api/upload', formData).then((res) => {
params.value.img = res.data.url
imgList.value.push(res.data.url)
ElMessage.success('上传成功')
}).catch((e) => {
ElMessage.error('上传失败:' + e.message)
@@ -686,17 +764,22 @@ const afterRead = (file) => {
},
});
};
// 创建绘图任务
const promptRef = ref(null)
const generate = () => {
if (params.value.prompt === '') {
if (params.value.prompt === '' && params.value.task_type === "image") {
promptRef.value.focus()
return ElMessage.error("请输入绘画提示词!")
}
if (params.value.model.indexOf("niji") !== -1 && params.value.raw) {
return ElMessage.error("动漫模型不允许启用原始模式")
}
if (imgList.value.length !== 2 && params.value.task_type === "swapFace") {
return ElMessage.error("换脸操作需要上传两张图片")
}
params.value.session_id = getSessionId()
params.value.img_arr = imgList.value
httpPost("/api/mj/image", params.value).then(() => {
ElMessage.success("绘画任务推送成功,请耐心等待任务执行...")
imgCalls.value -= 1
@@ -707,7 +790,6 @@ const generate = () => {
// 图片放大任务
const upscale = (index, item) => {
console.log(item)
send('/api/mj/upscale', index, item)
}
@@ -765,6 +847,16 @@ const publishImage = (item, action) => {
})
}
// 切换菜单
const tabChange = (tab) => {
params.value.task_type = tab
}
// 删除已上传图片
const removeUploadImage = (url) => {
imgList.value = removeArrayItem(imgList.value, url)
}
</script>
<style lang="stylus">

View File

@@ -600,7 +600,8 @@ onMounted(() => {
if (jobs[i].progress === -1) {
ElNotification({
title: '任务执行失败',
message: "任务ID" + jobs[i]['task_id'],
dangerouslyUseHTMLString: true,
message: `任务ID${jobs[i]['task_id']}<br />原因:${jobs[i]['err_msg']}`,
type: 'error',
})
imgCalls.value += 1

View File

@@ -309,7 +309,7 @@ const handleSelectionChange = function (rows) {
const resetPass = (row) => {
showResetPassDialog.value = true
pass.value.id = row.id
pass.value.username = row.mobile
pass.value.username = row.username
}
const doResetPass = () => {

View File

@@ -1,15 +0,0 @@
<template>
<div class="app-background">
<div class="mobile-setting container">
<div class="title" style="padding-top: 50px">应用广场</div>
<div class="title" style="padding-top: 200px">视频生成器即将开启敬请期待</div>
</div>
</div>
</template>
<script setup>
</script>
<style lang="stylus">
@import "@/assets/css/mobile/apps.styl"
</style>

View File

@@ -3,39 +3,8 @@
<van-nav-bar :title="title"/>
<div class="content">
<van-form @submit="save" v-model="form">
<van-cell-group inset>
<van-field
v-model="form.chat_config.api_keys.OpenAI"
label="OpenAI KEY"
placeholder="OpenAI API KEY"
/>
<van-field
v-model="form.chat_config.api_keys.Azure"
label="Azure KEY"
placeholder="Azure API KEY"
/>
<van-field
v-model="form.chat_config.api_keys.ChatGLM"
label="ChatGLM KEY"
placeholder="ChatGLM API KEY"
/>
</van-cell-group>
<div style="margin: 16px;">
<van-button round block type="primary" native-type="submit">
提交
</van-button>
</div>
</van-form>
<van-empty description="功能正在开发中"/>
</div>
<van-popup v-model:show="showPicker" round position="bottom">
<van-picker
:columns="models"
@cancel="showPicker = false"
@confirm="selectModel"
/>
</van-popup>
</div>
</template>
@@ -45,7 +14,7 @@ import {httpGet, httpPost} from "@/utils/http";
import {showFailToast, showSuccessToast} from "vant";
import {ElMessage} from "element-plus";
const title = ref('聊天设置')
const title = ref('图片创作广场')
const form = ref({
chat_config: {
api_keys: {OpenAI: "", Azure: "", ChatGLM: ""}

Some files were not shown because too many files have changed in this diff Show More