Compare commits

...

13 Commits

Author SHA1 Message Date
RockYang
7ca1989d98 opt: adjust styles for ItemList component, cut string for chat role's hello message 2023-10-16 10:46:10 +08:00
RockYang
4595dcb7ed feat: finish adding chat role to user function 2023-10-16 06:56:42 +08:00
RockYang
a688d3feb5 opt: adjust ItemList component styles 2023-10-15 15:47:42 +08:00
RockYang
7d1d88a32f opt: optimize imgae loading for image wall page, loading thumb image replace source image 2023-10-13 23:07:41 +08:00
RockYang
d95c048edd feat: create chat app list page, build the layout of page 2023-10-13 18:05:40 +08:00
RockYang
df2fc9d77c feat: auto login when register successfully 2023-10-13 16:27:40 +08:00
RockYang
d7e815d2bb docs: update images for readme 2023-10-13 15:55:30 +08:00
RockYang
f58b0a65f0 Merge branch 'main' into image-wall 2023-10-13 15:39:34 +08:00
RockYang
b59ad521ca feat: image wall stable diffusion image list component is ready 2023-10-13 15:16:40 +08:00
RockYang
b47ff975b0 feat: optimize the midjourney image list styles 2023-10-13 11:14:39 +08:00
RockYang
d043a87b30 docs: update readme, database files 2023-10-13 09:59:05 +08:00
RockYang
4cae7525d9 docs: update readme file 2023-10-13 06:49:21 +08:00
RockYang
1ae79331e7 feat: image wall page is ready 2023-10-12 18:09:50 +08:00
53 changed files with 2959 additions and 693 deletions

View File

@@ -1,12 +1,12 @@
# ChatGPT-Plus
**ChatGPT-PLUS** 基于 AI 大语言模型 API 实现的 AI 助手全套开源解决方案,自带运营管理后台,开箱即用。集成了 OpenAI, Azure,
ChatGLM,讯飞星火,文心一言等多个平台的大语言模型。主要有如下特性:
ChatGLM,讯飞星火,文心一言等多个平台的大语言模型。集成了 MidJourney 和 Stable Diffusion AI绘画功能。主要有如下特性:
* 完整的开源系统,前端应用和后台管理系统皆可开箱即用。
* 聊天体验跟 ChatGPT 官方版本完全一致。
* 内置了各种预训练好的角色,比如小红书写手,英语翻译大师,苏格拉底,孔子,乔布斯,周报助手等。轻松满足你的各种聊天和应用需求。
* 支持 MidJourney AI 绘画集成,开箱即用。
* 支持 MidJourney / Stable Diffusion AI 绘画集成,开箱即用。
* 支持使用个人微信二维码作为充值收费的支付渠道,无需企业支付通道。(可定制开发其他支付通道支持)
* 集成插件 API 功能,可结合 GPT 开发各种强大的插件,已内置实现了微博热搜,今日头条,今日早报和 AI 绘画函数插件。
@@ -30,9 +30,9 @@ ChatGLM,讯飞星火,文心一言等多个平台的大语言模型。主要有
![ChatGPT function plugin](/docs/imgs/mj.jpg)
### 用户设置
### 绘图作品展
![ChatGPT user profle](/docs/imgs/user_profile.png)
![ChatGPT image_list](/docs/imgs/image-list.png)
### 登录页面
@@ -50,7 +50,8 @@ ChatGLM,讯飞星火,文心一言等多个平台的大语言模型。主要有
![Mobile chat list](/docs/imgs/mobile_chat_list.png)
![Mobile chat session](/docs/imgs/mobile_chat_session.png)
![Mobile chat setting](/docs/imgs/mobile_chat_setting.png)
![Mobile chat setting](/docs/imgs/mobile_user_profile.png)
![Mobile chat setting](/docs/imgs/mobile_pay.png)
### 7. 体验地址
@@ -101,12 +102,10 @@ ChatGPT 的服务。
* Github 地址https://github.com/yangjian102621/chatgpt-plus
* 码云地址https://gitee.com/blackfox/chatgpt-plus
## 客户端下载
目前已经支持 Win/Linux/Mac/Android 客户端下载地址为https://github.com/yangjian102621/chatgpt-plus/releases/tag/v3.1.2
## TODOLIST
* [x] 整合 Midjourney AI 绘画 API
@@ -134,7 +133,7 @@ cd docker/mysql
# 创建 mysql 容器
docker-compose up -d
# 导入数据库
docker exec -i chatgpt-plus-mysql sh -c 'exec mysql -uroot -p12345678' < ../../database/chatgpt_plus-v3.1.4.sql
docker exec -i chatgpt-plus-mysql sh -c 'exec mysql -uroot -p12345678' < ../../database/chatgpt_plus-v3.1.5.sql
```
如果你本地已经安装了 MySQL 服务,那么你只需手动导入数据库即可。
@@ -222,8 +221,11 @@ WeChatBot = false # 是否启动微信机器人
```
> 1. 如果你不知道如何获取 Discord 用户 Token 和 Bot Token
请查参考 [Midjourney如何集成到自己的平台](https://zhuanlan.zhihu.com/p/631079476)。
> 2. `Txt2ImgJsonPath` 的默认用的是使用最广泛的 [stable-diffusion-webui](https://github.com/AUTOMATIC1111/stable-diffusion-webui) 项目的 API如果你用的是其他版本比如秋叶的懒人包部署的那么请将对应的 text2img 的参数报文复制放在 `res/text2img.json` 文件中即可。
请查参考 [Midjourney如何集成到自己的平台](https://zhuanlan.zhihu.com/p/631079476)。
> 2. `Txt2ImgJsonPath`
的默认用的是使用最广泛的 [stable-diffusion-webui](https://github.com/AUTOMATIC1111/stable-diffusion-webui) 项目的
API如果你用的是其他版本比如秋叶的懒人包部署的那么请将对应的 text2img 的参数报文复制放在 `res/text2img.json`
文件中即可。
修改 nginx 配置文档 `docker/conf/nginx/conf.d/chatgpt-plus.conf`,把后端转发的地址改成当前主机的内网 IP 地址。
@@ -250,6 +252,42 @@ location /static/ {
### 3. 启动应用
先修改 `docker/docker-compose.yaml` 文件中的镜像地址,改成最新的版本:
```yaml
version: '3'
services:
# 后端 API 镜像
chatgpt-plus-api:
image: registry.cn-shenzhen.aliyuncs.com/geekmaster/chatgpt-plus-api:v3.1.5 #这里改成最新的 release 版本地址
container_name: chatgpt-plus-api
restart: always
environment:
- DEBUG=false
- LOG_LEVEL=info
- CONFIG_FILE=config.toml
ports:
- "5678:5678"
volumes:
- /usr/share/zoneinfo/Asia/Shanghai:/etc/localtime
- ./conf/config.toml:/var/www/app/config.toml
- ./logs:/var/www/app/logs
- ./static:/var/www/app/static
# 前端应用镜像
chatgpt-plus-web:
image: registry.cn-shenzhen.aliyuncs.com/geekmaster/chatgpt-plus-web:v3.1.5 #这里改成最新的 release 版本地址
container_name: chatgpt-plus-web
restart: always
ports:
- "8080:8080" # 这边是对外的端口,支持 808080和443
volumes:
- ./logs/nginx:/var/log/nginx
- ./conf/nginx/conf.d:/etc/nginx/conf.d
- ./conf/nginx/nginx.conf:/etc/nginx/nginx.conf
- ./ssl:/etc/nginx/ssl
```
```shell
cd docker
docker-compose up -d

View File

@@ -145,7 +145,10 @@ func authorizeMiddleware(s *AppServer, client *redis.Client) gin.HandlerFunc {
c.Request.URL.Path == "/api/mj/notify" ||
c.Request.URL.Path == "/api/chat/history" ||
c.Request.URL.Path == "/api/chat/detail" ||
c.Request.URL.Path == "/api/role/list" ||
c.Request.URL.Path == "/api/mj/jobs" ||
c.Request.URL.Path == "/api/mj/proxy" ||
c.Request.URL.Path == "/api/sd/jobs" ||
strings.HasPrefix(c.Request.URL.Path, "/api/sms/") ||
strings.HasPrefix(c.Request.URL.Path, "/api/captcha/") ||
strings.HasPrefix(c.Request.URL.Path, "/static/") ||

View File

@@ -2,6 +2,7 @@ package handler
import (
"chatplus/core"
"chatplus/core/types"
"chatplus/store/model"
"chatplus/store/vo"
"chatplus/utils"
@@ -24,6 +25,7 @@ func NewChatRoleHandler(app *core.AppServer, db *gorm.DB) *ChatRoleHandler {
// List get user list
func (h *ChatRoleHandler) List(c *gin.Context) {
all := h.GetBool(c, "all")
var roles []model.ChatRole
res := h.db.Where("enable", true).Order("sort_num ASC").Find(&roles)
if res.Error != nil {
@@ -31,13 +33,31 @@ func (h *ChatRoleHandler) List(c *gin.Context) {
return
}
user, err := utils.GetLoginUser(c, h.db)
if err != nil {
// 获取所有角色
if all {
// 转成 vo
var roleVos = make([]vo.ChatRole, 0)
for _, r := range roles {
var v vo.ChatRole
err := utils.CopyObject(r, &v)
if err == nil {
v.Id = r.Id
roleVos = append(roleVos, v)
}
}
resp.SUCCESS(c, roleVos)
return
}
userId := h.GetInt(c, "user_id", 0)
if userId == 0 {
resp.NotAuth(c)
return
}
var user model.User
h.db.First(&user, userId)
var roleKeys []string
err = utils.JsonDecode(user.ChatRoles, &roleKeys)
err := utils.JsonDecode(user.ChatRoles, &roleKeys)
if err != nil {
resp.ERROR(c, "角色解析失败!")
return
@@ -57,3 +77,29 @@ func (h *ChatRoleHandler) List(c *gin.Context) {
}
resp.SUCCESS(c, roleVos)
}
// UpdateRole 更新用户聊天角色
func (h *ChatRoleHandler) UpdateRole(c *gin.Context) {
user, err := utils.GetLoginUser(c, h.db)
if err != nil {
resp.NotAuth(c)
return
}
var data struct {
Keys []string `json:"keys"`
}
if err = c.ShouldBindJSON(&data); err != nil {
resp.ERROR(c, types.InvalidArgs)
return
}
res := h.db.Model(&model.User{}).Where("id = ?", user.Id).UpdateColumn("chat_roles_json", utils.JsonEncode(data.Keys))
if res.Error != nil {
logger.Error("添加应用失败:", err)
resp.ERROR(c, "更新数据库失败!")
return
}
resp.SUCCESS(c)
}

View File

@@ -315,14 +315,26 @@ func (h *MidJourneyHandler) Variation(c *gin.Context) {
// JobList 获取 MJ 任务列表
func (h *MidJourneyHandler) JobList(c *gin.Context) {
status := h.GetInt(c, "status", 0)
var items []model.MidJourneyJob
var res *gorm.DB
userId, _ := c.Get(types.LoginUserID)
userId := h.GetInt(c, "user_id", 0)
page := h.GetInt(c, "page", 0)
pageSize := h.GetInt(c, "page_size", 0)
session := h.db.Session(&gorm.Session{})
if status == 1 {
res = h.db.Where("user_id = ? AND progress = 100", userId).Order("id DESC").Find(&items)
session = session.Where("progress = ?", 100).Order("id DESC")
} else {
res = h.db.Where("user_id = ? AND progress < 100", userId).Order("id ASC").Find(&items)
session = session.Where("progress < ?", 100).Order("id ASC")
}
if userId > 0 {
session = session.Where("user_id = ?", userId)
}
if page > 0 && pageSize > 0 {
offset := (page - 1) * pageSize
session = session.Offset(offset).Limit(pageSize)
}
var items []model.MidJourneyJob
res := session.Find(&items)
if res.Error != nil {
resp.ERROR(c, types.NoData)
return

View File

@@ -8,7 +8,6 @@ import (
"chatplus/store/vo"
"chatplus/utils"
"chatplus/utils/resp"
"encoding/base64"
"fmt"
"github.com/gin-gonic/gin"
"github.com/go-redis/redis/v8"
@@ -160,17 +159,29 @@ func (h *SdJobHandler) Image(c *gin.Context) {
resp.SUCCESS(c)
}
// JobList 获取 MJ 任务列表
// JobList 获取 stable diffusion 任务列表
func (h *SdJobHandler) JobList(c *gin.Context) {
status := h.GetInt(c, "status", 0)
var items []model.SdJob
var res *gorm.DB
userId, _ := c.Get(types.LoginUserID)
userId := h.GetInt(c, "user_id", 0)
page := h.GetInt(c, "page", 0)
pageSize := h.GetInt(c, "page_size", 0)
session := h.db.Session(&gorm.Session{})
if status == 1 {
res = h.db.Where("user_id = ? AND progress = 100", userId).Order("id DESC").Find(&items)
session = session.Where("progress = ?", 100).Order("id DESC")
} else {
res = h.db.Where("user_id = ? AND progress < 100", userId).Order("id ASC").Find(&items)
session = session.Where("progress < ?", 100).Order("id ASC")
}
if userId > 0 {
session = session.Where("user_id = ?", userId)
}
if page > 0 && pageSize > 0 {
offset := (page - 1) * pageSize
session = session.Offset(offset).Limit(pageSize)
}
var items []model.SdJob
res := session.Find(&items)
if res.Error != nil {
resp.ERROR(c, types.NoData)
return
@@ -189,12 +200,6 @@ func (h *SdJobHandler) JobList(c *gin.Context) {
h.db.Delete(&item)
continue
}
if item.ImgURL != "" { // 正在运行中任务使用代理访问图片
image, err := utils.DownloadImage(item.ImgURL, h.App.Config.ProxyURL)
if err == nil {
job.ImgURL = "data:image/png;base64," + base64.StdEncoding.EncodeToString(image)
}
}
}
jobs = append(jobs, job)
}

View File

@@ -80,14 +80,6 @@ func (h *UserHandler) Register(c *gin.Context) {
return
}
// 默认订阅所有角色
var chatRoles []model.ChatRole
h.db.Find(&chatRoles)
var roleKeys = make([]string, 0)
for _, r := range chatRoles {
roleKeys = append(roleKeys, r.Key)
}
salt := utils.RandString(8)
user := model.User{
Password: utils.GenPassword(data.Password, salt),
@@ -95,7 +87,7 @@ func (h *UserHandler) Register(c *gin.Context) {
Salt: salt,
Status: true,
Mobile: data.Mobile,
ChatRoles: utils.JsonEncode(roleKeys),
ChatRoles: utils.JsonEncode([]string{"gpt"}), // 默认只订阅通用助手角色
ChatConfig: utils.JsonEncode(types.UserChatConfig{
ApiKeys: map[types.Platform]string{
types.OpenAI: "",
@@ -116,7 +108,24 @@ func (h *UserHandler) Register(c *gin.Context) {
if h.App.SysConfig.EnabledMsg {
_ = h.leveldb.Delete(key) // 注册成功,删除短信验证码
}
resp.SUCCESS(c, user)
// 自动登录创建 token
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"user_id": user.Id,
"expired": time.Now().Add(time.Second * time.Duration(h.App.Config.Session.MaxAge)).Unix(),
})
tokenString, err := token.SignedString([]byte(h.App.Config.Session.SecretKey))
if err != nil {
resp.ERROR(c, "Failed to generate token, "+err.Error())
return
}
// 保存到 redis
key = fmt.Sprintf("users/%d", user.Id)
if _, err := h.redis.Set(c, key, tokenString, 0).Result(); err != nil {
resp.ERROR(c, "error with save token: "+err.Error())
return
}
resp.SUCCESS(c, tokenString)
}
// Login 用户登录

View File

@@ -185,6 +185,7 @@ func main() {
fx.Invoke(func(s *core.AppServer, h *handler.ChatRoleHandler) {
group := s.Engine.Group("/api/role/")
group.GET("list", h.List)
group.POST("update", h.UpdateRole)
}),
fx.Invoke(func(s *core.AppServer, h *handler.UserHandler) {
group := s.Engine.Group("/api/user/")

View File

@@ -36,7 +36,7 @@ func CopyObject(src interface{}, dst interface{}) error {
pType := reflect.New(value.Type())
v2 := pType.Interface()
err := json.Unmarshal([]byte(v.String()), &v2)
if err == nil {
if err == nil && v2 != nil {
value.Set(reflect.ValueOf(v2).Elem())
}
// map, struct, slice to string

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@ version: '3'
services:
# 后端 API 程序
chatgpt-plus-api:
image: registry.cn-shenzhen.aliyuncs.com/geekmaster/chatgpt-plus-api:v3.1.4
image: registry.cn-shenzhen.aliyuncs.com/geekmaster/chatgpt-plus-api:v3.1.5
container_name: chatgpt-plus-api
restart: always
environment:
@@ -19,7 +19,7 @@ services:
# 前端应用
chatgpt-plus-web:
image: registry.cn-shenzhen.aliyuncs.com/geekmaster/chatgpt-plus-web:v3.1.4
image: registry.cn-shenzhen.aliyuncs.com/geekmaster/chatgpt-plus-web:v3.1.5
container_name: chatgpt-plus-web
restart: always
ports:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 322 KiB

After

Width:  |  Height:  |  Size: 201 KiB

BIN
docs/imgs/image-list.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

BIN
docs/imgs/mobile_pay.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 454 KiB

11
web/package-lock.json generated
View File

@@ -23,6 +23,7 @@
"pinia": "^2.1.4",
"qs": "^6.11.1",
"sortablejs": "^1.15.0",
"v3-waterfall": "^1.2.1",
"vant": "^4.5.0",
"vue": "^3.2.13",
"vue-router": "^4.0.15"
@@ -10459,6 +10460,11 @@
"uuid": "dist/bin/uuid"
}
},
"node_modules/v3-waterfall": {
"version": "1.2.1",
"resolved": "https://registry.npmmirror.com/v3-waterfall/-/v3-waterfall-1.2.1.tgz",
"integrity": "sha512-zjfT1FuHupsAahvS4mr3Yb8k2SHB8srW6st+/cBXwrsyhbCcj8Qhb1QtNUuEIx/tbpLQrMpxtJunZXkaKBfAEA=="
},
"node_modules/v8-compile-cache": {
"version": "2.3.0",
"resolved": "https://registry.npmmirror.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz",
@@ -19518,6 +19524,11 @@
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"dev": true
},
"v3-waterfall": {
"version": "1.2.1",
"resolved": "https://registry.npmmirror.com/v3-waterfall/-/v3-waterfall-1.2.1.tgz",
"integrity": "sha512-zjfT1FuHupsAahvS4mr3Yb8k2SHB8srW6st+/cBXwrsyhbCcj8Qhb1QtNUuEIx/tbpLQrMpxtJunZXkaKBfAEA=="
},
"v8-compile-cache": {
"version": "2.3.0",
"resolved": "https://registry.npmmirror.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz",

View File

@@ -17,14 +17,15 @@
"good-storage": "^1.1.1",
"highlight.js": "^11.7.0",
"json-bigint": "^1.0.0",
"lodash": "^4.17.21",
"markdown-it": "^13.0.1",
"md-editor-v3": "^2.2.1",
"pinia": "^2.1.4",
"qs": "^6.11.1",
"sortablejs": "^1.15.0",
"v3-waterfall": "^1.2.1",
"vant": "^4.5.0",
"vue": "^3.2.13",
"lodash": "^4.17.21",
"vue-router": "^4.0.15"
},
"devDependencies": {

View File

@@ -0,0 +1,56 @@
.page-apps {
background-color: #282c34;
height: 100vh;
}
.page-apps .title {
text-align: center;
background-color: #25272d;
font-size: 24px;
color: #fff;
padding: 10px;
border-bottom: 1px solid #3c3c3c;
}
.page-apps .inner {
display: flex;
color: #fff;
padding: 15px;
overflow-y: visible;
overflow-x: hidden;
}
.page-apps .inner .list-box .app-item {
border: 1px solid #666;
border-radius: 6px;
overflow: hidden;
transition: all 0.3s ease; /* 添加过渡效果 */
}
.page-apps .inner .list-box .app-item .el-image {
padding: 6px;
}
.page-apps .inner .list-box .app-item .el-image .el-image__inner {
border-radius: 10px;
}
.page-apps .inner .list-box .app-item .title {
display: flex;
padding: 10px;
}
.page-apps .inner .list-box .app-item .title .name {
width: 100%;
text-align: left;
font-size: 16px;
font-weight: bold;
color: #47fff1;
}
.page-apps .inner .list-box .app-item .title .opt {
position: relative;
top: -5px;
}
.page-apps .inner .list-box .app-item .hello-msg {
height: 60px;
padding: 10px;
font-size: 14px;
color: #999;
}
.page-apps .inner .list-box .app-item:hover {
box-shadow: 0 0 10px rgba(71,255,241,0.6); /* 添加阴影效果 */
transform: translateY(-10px); /* 向上移动10像素 */
}

View File

@@ -0,0 +1,69 @@
.page-apps {
background-color: #282c34;
height 100vh
.title {
text-align center
background-color #25272d
font-size 24px
color #ffffff
padding 10px
border-bottom 1px solid #3c3c3c
}
.inner {
display flex
color #ffffff
padding 15px;
overflow-y visible
overflow-x hidden
.list-box {
.app-item {
border 1px solid #666666
border-radius 6px
overflow hidden
transition: all 0.3s ease; /* */
.el-image {
padding 6px
.el-image__inner {
border-radius 10px
}
}
.title {
display flex
padding 10px
.name {
width 100%
text-align left
font-size 16px
font-weight bold
color #47fff1
}
.opt {
position: relative;
top -5px
}
}
.hello-msg {
height 60px
padding 10px
font-size 14px
color #999999
}
&:hover {
box-shadow: 0 0 10px rgba(71, 255, 241, 0.6); /* */
transform: translateY(-10px); /* 10 */
}
}
}
}
}

View File

@@ -63,7 +63,7 @@
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
width: 210px;
width: 190px;
}
#app .common-layout .el-aside .chat-list .content .chat-list-item .chat-title {
color: #c1c1c1;

View File

@@ -0,0 +1,13 @@
.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

@@ -0,0 +1,26 @@
.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

@@ -3,10 +3,6 @@
}
.page-mj .inner {
display: flex;
/* 修改滚动条的颜色 */
/* 修改滚动条轨道的背景颜色 */
/* 修改滚动条的滑块颜色 */
/* 修改滚动条的滑块的悬停颜色 */
}
.page-mj .inner .mj-box {
margin: 10px;
@@ -147,19 +143,6 @@
.page-mj .inner .el-form .el-slider {
width: 180px;
}
.page-mj .inner ::-webkit-scrollbar {
width: 10px; /* 滚动条宽度 */
}
.page-mj .inner ::-webkit-scrollbar-track {
background-color: #282c34;
}
.page-mj .inner ::-webkit-scrollbar-thumb {
background-color: #444;
border-radius: 10px;
}
.page-mj .inner ::-webkit-scrollbar-thumb:hover {
background-color: #666;
}
.page-mj .inner .task-list-box {
width: 100%;
padding: 10px;
@@ -193,6 +176,11 @@
.page-mj .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-mj .inner .task-list-box .finish-job-list .job-item .opt .opt-line {
margin: 6px 0;
@@ -202,11 +190,11 @@
flex-flow: row;
}
.page-mj .inner .task-list-box .finish-job-list .job-item .opt .opt-line ul li {
margin-right: 10px;
margin-right: 6px;
}
.page-mj .inner .task-list-box .finish-job-list .job-item .opt .opt-line ul li a {
padding: 3px 0;
width: 44px;
width: 40px;
text-align: center;
border-radius: 5px;
display: block;
@@ -221,10 +209,14 @@
font-size: 20px;
cursor: pointer;
}
.page-mj .inner .task-list-box .finish-job-list .job-item:hover {
box-shadow: 0 0 10px rgba(71,255,241,0.6); /* 添加阴影效果 */
transform: translateY(-10px); /* 向上移动10像素 */
}
.page-mj .inner .task-list-box .el-image {
width: 100%;
height: 100%;
max-height: 240px;
overflow: visible;
}
.page-mj .inner .task-list-box .el-image img {
height: 240px;
@@ -241,16 +233,17 @@
height: 100%;
min-height: 200px;
color: #fff;
height: 240px;
}
.page-mj .inner .task-list-box .el-image .image-slot .iconfont {
font-size: 50px;
margin-bottom: 10px;
}
.page-mj .inner .task-list-box .el-image.upscale {
max-height: 304px;
max-height: 310px;
}
.page-mj .inner .task-list-box .el-image.upscale img {
height: 304px;
height: 310px;
}
.page-mj .inner .task-list-box .el-image.upscale .el-image-viewer__wrapper img {
width: auto;

View File

@@ -182,160 +182,7 @@
}
}
/* */
::-webkit-scrollbar {
width: 10px; /* */
}
/* */
::-webkit-scrollbar-track {
background-color: #282C34;
}
/* */
::-webkit-scrollbar-thumb {
background-color: #444444;
border-radius 10px
}
/* */
::-webkit-scrollbar-thumb:hover {
background-color: #666666;
}
.task-list-box {
width 100%
padding 10px
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%
.opt {
.opt-line {
margin 6px 0
ul {
display flex
flex-flow row
li {
margin-right 10px
a {
padding 3px 0
width 44px
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
}
}
}
}
}
}
.el-image {
width 100%
height 100%
max-height 240px
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
.iconfont {
font-size 50px
margin-bottom 10px
}
}
}
.el-image.upscale {
max-height 304px
img {
height 304px
}
.el-image-viewer__wrapper {
img {
width auto
height auto
}
}
}
}
@import "task-list.styl"
}
}

View File

@@ -99,6 +99,11 @@
.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;
@@ -108,11 +113,11 @@
flex-flow: row;
}
.page-sd .inner .task-list-box .finish-job-list .job-item .opt .opt-line ul li {
margin-right: 10px;
margin-right: 6px;
}
.page-sd .inner .task-list-box .finish-job-list .job-item .opt .opt-line ul li a {
padding: 3px 0;
width: 44px;
width: 40px;
text-align: center;
border-radius: 5px;
display: block;
@@ -127,10 +132,14 @@
font-size: 20px;
cursor: pointer;
}
.page-sd .inner .task-list-box .finish-job-list .job-item: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%;
max-height: 240px;
overflow: visible;
}
.page-sd .inner .task-list-box .el-image img {
height: 240px;
@@ -147,16 +156,17 @@
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: 304px;
max-height: 310px;
}
.page-sd .inner .task-list-box .el-image.upscale img {
height: 304px;
height: 310px;
}
.page-sd .inner .task-list-box .el-image.upscale .el-image-viewer__wrapper img {
width: auto;
@@ -180,6 +190,15 @@
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;
@@ -231,16 +250,3 @@
position: relative;
top: 2px;
}
.custom-scroll ::-webkit-scrollbar {
width: 10px; /* 滚动条宽度 */
}
.custom-scroll ::-webkit-scrollbar-track {
background-color: #282c34;
}
.custom-scroll ::-webkit-scrollbar-thumb {
background-color: #444;
border-radius: 10px;
}
.custom-scroll ::-webkit-scrollbar-thumb:hover {
background-color: #666;
}

View File

@@ -87,222 +87,11 @@
}
}
.task-list-box {
width 100%
padding 10px
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%
.opt {
.opt-line {
margin 6px 0
ul {
display flex
flex-flow row
li {
margin-right 10px
a {
padding 3px 0
width 44px
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
}
}
}
}
}
}
.el-image {
width 100%
height 100%
max-height 240px
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
.iconfont {
font-size 50px
margin-bottom 10px
}
}
}
.el-image.upscale {
max-height 304px
img {
height 304px
}
.el-image-viewer__wrapper {
img {
width auto
height auto
}
}
}
}
@import "task-list.styl"
}
.el-overlay-dialog {
.el-dialog {
background-color #1a1b1e
@import "sd-task-dialog.styl"
.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
}
.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 20px 0 10px 0
.el-button {
width 100%
}
}
}
}
// end el-row
}
}
}
.mj-list-item-prompt {
.el-icon {
@@ -315,30 +104,3 @@
}
.custom-scroll {
/* */
::-webkit-scrollbar {
width: 10px; /* */
}
/* */
::-webkit-scrollbar-track {
background-color: #282C34;
}
/* */
::-webkit-scrollbar-thumb {
background-color: #444444;
border-radius 10px
}
/* */
::-webkit-scrollbar-thumb:hover {
background-color: #666666;
}
}

View File

@@ -0,0 +1,190 @@
.page-images-wall {
display: flex;
background-color: #282c34;
}
.page-images-wall .inner {
width: 100%;
color: #fff;
overflow: hidden;
}
.page-images-wall .inner .header {
display: flex;
padding: 0 40px;
}
.page-images-wall .inner .header h2 {
width: 300px;
}
.page-images-wall .inner .header .settings {
width: 100%;
display: flex;
justify-content: right;
}
.page-images-wall .inner .header .settings .el-radio-group {
font-size: 16px;
}
.page-images-wall .inner .header .settings .el-radio-group .el-radio {
color: #fff;
}
.page-images-wall .inner .waterfall {
position: relative;
margin: 0 auto;
overflow-y: auto;
overflow-x: hidden;
}
.page-images-wall .inner .waterfall .list-item .image {
overflow: hidden;
}
.page-images-wall .inner .waterfall .list-item .image .el-image {
transition: transform 0.3s;
cursor: pointer;
}
.page-images-wall .inner .waterfall .list-item .prompt {
display: none;
position: absolute;
width: 180px;
bottom: 0;
left: 0;
color: #fff;
padding: 10px 10px 20px 10px;
line-height: 1.2;
border-top-right-radius: 10px;
background-color: rgba(10,10,10,0.7);
}
.page-images-wall .inner .waterfall .list-item .prompt span {
word-break: break-all;
}
.page-images-wall .inner .waterfall .list-item .prompt .el-icon {
position: absolute;
bottom: 10px;
right: 10px;
cursor: pointer;
border: 1px solid #fff;
border-radius: 5px;
padding: 2px;
font-size: 12px;
}
.page-images-wall .inner .waterfall .list-item .prompt .el-icon:hover {
background-color: #999;
}
.page-images-wall .inner .waterfall .list-item:hover .prompt {
display: block;
animation: expandUp 0.3s ease-in-out forwards;
transform-origin: bottom center;
transform: scaleY(0); /* 初始状态元素高度为0 */
}
.page-images-wall .inner .waterfall .list-item:hover .image .el-image {
transform: scale(1.2); /* 放大图像到1.2倍大小 */
}
.page-images-wall .inner .footer {
display: flex;
padding: 20px;
align-items: center;
justify-content: center;
}
.page-images-wall .inner .footer .iconfont {
margin-left: 6px;
}
.page-images-wall .el-overlay-dialog .el-dialog {
background-color: #1a1b1e;
}
.page-images-wall .el-overlay-dialog .el-dialog .el-dialog__header .el-dialog__title {
color: #f5f5f5;
}
.page-images-wall .el-overlay-dialog .el-dialog .el-dialog__body {
padding: 0 0 0 15px !important;
display: flex;
height: 100%;
}
.page-images-wall .el-overlay-dialog .el-dialog .el-dialog__body .el-row {
width: 100%;
}
.page-images-wall .el-overlay-dialog .el-dialog .el-dialog__body .el-row .img-container {
display: flex;
justify-content: center;
}
.page-images-wall .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-images-wall .el-overlay-dialog .el-dialog .el-dialog__body .el-row .img-container .image-slot .el-icon {
font-size: 60px;
}
.page-images-wall .el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info {
background-color: #25262b;
padding: 1rem 1.5rem;
}
.page-images-wall .el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .info-line {
width: 100%;
}
.page-images-wall .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-images-wall .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-images-wall .el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .info-line .wrapper {
margin-top: 10px;
display: flex;
}
.page-images-wall .el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .info-line .wrapper label {
display: flex;
width: 100px;
color: #a5a5a5;
}
.page-images-wall .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-images-wall .el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .copy-params {
padding: 20px 0 10px 0;
}
.page-images-wall .el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .copy-params .el-button {
width: 100%;
}
@-moz-keyframes expandUp {
0% {
transform: scaleY(0);
}
100% {
transform: scaleY(1);
}
}
@-webkit-keyframes expandUp {
0% {
transform: scaleY(0);
}
100% {
transform: scaleY(1);
}
}
@-o-keyframes expandUp {
0% {
transform: scaleY(0);
}
100% {
transform: scaleY(1);
}
}
@keyframes expandUp {
0% {
transform: scaleY(0);
}
100% {
transform: scaleY(1);
}
}

View File

@@ -0,0 +1,124 @@
@keyframes expandUp {
0% {
transform: scaleY(0);
}
100% {
transform: scaleY(1);
}
}
.page-images-wall {
display: flex;
background-color: #282c34;
.inner {
width 100%
color #ffffff
overflow hidden
.header {
display flex
padding 0 40px
h2 {
width 300px
}
.settings {
width 100%
display flex
justify-content right
.el-radio-group {
font-size 16px
.el-radio {
color #ffffff
}
}
}
}
.waterfall {
position: relative;
margin: 0 auto;
overflow-y auto
overflow-x hidden
.list-item {
.image {
overflow hidden
.el-image {
transition: transform 0.3s;
cursor pointer
}
}
.prompt {
display none
position absolute
width 180px
bottom 0
left 0
color #ffffff
padding 10px 10px 20px 10px
line-height 1.2
border-top-right-radius 10px
background-color rgba(10, 10, 10, 0.7)
span {
word-break break-all
}
.el-icon {
position absolute
bottom 10px
right 10px
cursor pointer
border 1px solid #ffffff
border-radius 5px
padding 2px
font-size 12px;
&:hover {
background-color #999999
}
}
}
&:hover {
.prompt {
display block
animation: expandUp 0.3s ease-in-out forwards;
transform-origin: bottom center;
transform: scaleY(0); /* 0 */
}
.image {
.el-image {
transform: scale(1.2); /* 1.2倍大小 */
}
}
}
}
}
.footer {
display flex
padding 20px
align-items center
justify-content center
.iconfont {
margin-left 6px
}
}
}
@import "sd-task-dialog.styl"
}

View File

@@ -0,0 +1,63 @@
.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 .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: 20px 0 10px 0;
}
.el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .copy-params .el-button {
width: 100%;
}

View File

@@ -0,0 +1,96 @@
.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 20px 0 10px 0
.el-button {
width 100%
}
}
}
}
// end el-row
}
}
}

View File

@@ -0,0 +1,115 @@
.task-list-box {
width: 100%;
padding: 10px;
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%;
}
.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: 10px;
}
.task-list-box .finish-job-list .job-item .opt .opt-line ul li a {
padding: 3px 0;
width: 44px;
text-align: center;
border-radius: 5px;
display: block;
cursor: pointer;
background-color: #4e5058;
color: #fff;
}
.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 .el-image {
width: 100%;
height: 100%;
max-height: 240px;
}
.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;
}
.task-list-box .el-image .image-slot .iconfont {
font-size: 50px;
margin-bottom: 10px;
}
.task-list-box .el-image.upscale {
max-height: 312px;
}
.task-list-box .el-image.upscale img {
height: 312px;
}
.task-list-box .el-image.upscale .el-image-viewer__wrapper img {
width: auto;
height: auto;
}

View File

@@ -0,0 +1,140 @@
.task-list-box {
width 100%
padding 10px
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
}
}
}
}
&: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

@@ -1,8 +1,8 @@
@font-face {
font-family: "iconfont"; /* Project id 4125778 */
src: url('iconfont.woff2?t=1694420182193') format('woff2'),
url('iconfont.woff?t=1694420182193') format('woff'),
url('iconfont.ttf?t=1694420182193') format('truetype');
src: url('iconfont.woff2?t=1697164072791') format('woff2'),
url('iconfont.woff?t=1697164072791') format('woff'),
url('iconfont.ttf?t=1697164072791') format('truetype');
}
.iconfont {
@@ -13,6 +13,10 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-face:before {
content: "\e64b";
}
.icon-book:before {
content: "\e622";
}

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": "845789",
"name": "笑脸",
"font_class": "face",
"unicode": "e64b",
"unicode_decimal": 58955
},
{
"icon_id": "11836501",
"name": "知识库",

Binary file not shown.

View File

@@ -5,11 +5,11 @@
class="list-item"
v-for="(item, index) in items"
:key="index"
:style="{width:itemWidth + 'px', marginBottom: margin*2+'px'}"
:style="{width:itemWidth + 'px'}"
>
<div :style="{marginLeft: margin+'px', marginRight: margin+'px'}">
<div class="item-inner" :style="{padding: gap/2+'px'}">
<div class="item-wrapper">
<slot :item="item" :index="index"></slot>
<slot :item="item" :index="index" :width="itemWidth"></slot>
</div>
</div>
</div>
@@ -21,6 +21,7 @@
// 列表组件
import {onMounted, ref} from "vue";
// eslint-disable-next-line no-undef
const props = defineProps({
items: {
type: Array,
@@ -28,38 +29,25 @@ const props = defineProps({
},
gap: {
type: Number,
default: 10
default: 12
},
width: {
type: Number,
default: 240
},
height: {
type: Number,
default: 240
}
});
const container = ref(null)
const itemWidth = ref(props.width)
const margin = ref(props.gap)
onMounted(() => {
computeSize()
})
const computeSize = () => {
const w = container.value.offsetWidth - 10 // 减去滚动条的宽度
const w = container.value.offsetWidth - 8 // 减去滚动条的宽度
let cols = Math.floor(w / props.width)
itemWidth.value = Math.floor(w / cols) - 1
while (itemWidth.value < props.width && cols > 1) {
cols -= 1
itemWidth.value = Math.floor(w / cols) - 1
}
if (props.gap > 0) {
margin.value = props.gap / 2
}
itemWidth.value = Math.floor(w / cols)
}
window.onresize = () => {
@@ -76,15 +64,14 @@ window.onresize = () => {
flex-wrap wrap
.list-item {
div {
.item-inner {
display flex
height 100%
overflow hidden
.item-wrapper {
height 100%
width 100%
display flex
justify-content center
}
}
}

View File

@@ -0,0 +1,108 @@
<template>
<el-dialog
class="login-dialog"
v-model="showDialog"
:close-on-click-modal="true"
:show-close="true"
:before-close="close"
:width="400"
title="用户登录"
>
<div class="form">
<el-form label-width="65px">
<el-form-item>
<template #label>
<div class="label">
<el-icon>
<User/>
</el-icon>
<span>账号</span>
</div>
</template>
<template #default>
<el-input v-model="username" placeholder="手机号码"/>
</template>
</el-form-item>
<el-form-item>
<template #label>
<div class="label">
<el-icon>
<Lock/>
</el-icon>
<span>密码</span>
</div>
</template>
<template #default>
<el-input v-model="password" type="password" placeholder="密码"/>
</template>
</el-form-item>
<div class="login-btn">
<el-button type="primary" @click="submit" round>登录</el-button>
</div>
</el-form>
</div>
</el-dialog>
</template>
<script setup>
import {computed, ref} from "vue"
import {httpPost} from "@/utils/http";
import {ElMessage} from "element-plus";
import {setUserToken} from "@/store/session";
import {validateMobile} from "@/utils/validate";
import {Lock, User} from "@element-plus/icons-vue";
// eslint-disable-next-line no-undef
const props = defineProps({
show: Boolean,
});
const showDialog = computed(() => {
return props.show
})
const username = ref("")
const password = ref("")
// eslint-disable-next-line no-undef
const emits = defineEmits(['hide']);
const submit = function () {
if (!validateMobile(username.value)) {
return ElMessage.error('请输入合法的手机号');
}
if (password.value.trim() === '') {
return ElMessage.error('请输入密码');
}
httpPost('/api/user/login', {username: username.value.trim(), password: password.value.trim()}).then((res) => {
setUserToken(res.data)
ElMessage.success("登录成功!")
emits("hide")
}).catch((e) => {
ElMessage.error('登录失败,' + e.message)
})
}
const close = function () {
emits('hide', false);
}
</script>
<style lang="stylus">
.login-dialog {
border-radius 20px
.label {
.el-icon {
font-size 16px
margin-right 6px
}
}
.login-btn {
text-align center
padding-top 10px
.el-button {
width 50%
}
}
}
</style>

View File

@@ -88,7 +88,7 @@ const capabilities = ref([
value: ""
},
{
text: "国产大语言模型支持,GLM2 模型接入中",
text: "国产大语言模型支持,百度文心科大讯飞ChatGLM...",
value: ""
},
{

View File

@@ -33,6 +33,8 @@ import {
Uploader
} from "vant";
import router from "@/router";
import 'v3-waterfall/dist/style.css'
import V3waterfall from "v3-waterfall";
const app = createApp(App)
app.use(createPinia())
@@ -62,6 +64,7 @@ app.use(ShareSheet);
app.use(Switch);
app.use(Uploader);
app.use(Tag);
app.use(V3waterfall)
app.use(router).use(ElementPlus).mount('#app')

View File

@@ -22,7 +22,7 @@ const routes = [
},
{
name: 'image-sd',
path: '/sd',
path: '/sd/',
meta: {title: 'Stable Diffusion 绘画中心'},
component: () => import('@/views/ImageSd.vue'),
},
@@ -40,9 +40,9 @@ const routes = [
},
{
name: 'images',
path: '/images',
meta: {title: '绘画社区'},
component: () => import('@/views/Images.vue'),
path: '/images-wall',
meta: {title: '作品展示'},
component: () => import('@/views/ImagesWall.vue'),
},
{
name: 'user-invitation',

View File

@@ -128,3 +128,32 @@ export function copyObj(origin) {
export function disabledDate(time) {
return time.getTime() < Date.now()
}
// 字符串截取
export function substr(str, length) {
let result = ''
let count = 0
for (let i = 0; i < str.length; i++) {
const char = str.charAt(i)
const charCode = str.charCodeAt(i);
// 判断字符是否为中文字符
if (charCode >= 0x4e00 && charCode <= 0x9fff) {
// 中文字符算两个字符
count += 2
} else {
count++
}
if (count <= length) {
result += char
} else {
result += " ..."
break
}
}
return result
}

View File

@@ -1,41 +1,112 @@
<template>
<div class="page-apps" :style="{ height: winHeight + 'px' }">
<div class="inner">
<h1>应用中心</h1>
<h2>页面正在紧锣密鼓开发中敬请期待</h2>
<div class="page-apps custom-scroll">
<div class="title">
AI 助手应用中心
</div>
<div class="inner" :style="{height: listBoxHeight + 'px'}">
<ItemList :items="list" v-if="list.length > 0" :gap="20" :width="250">
<template #default="scope">
<div class="app-item" :style="{width: scope.width+'px'}">
<el-image :src="scope.item.icon" fit="cover" :style="{height: scope.width+'px'}"/>
<div class="title">
<span class="name">{{ scope.item.name }}</span>
<div class="opt">
<el-button v-if="hasRole(scope.item.key)" size="small" type="danger"
@click="updateRole(scope.item,'remove')">
<el-icon>
<Delete/>
</el-icon>
<span>移除应用</span>
</el-button>
<el-button v-else size="small"
style="--el-color-primary:#009999"
@click="updateRole(scope.item, 'add')">
<el-icon>
<Plus/>
</el-icon>
<span>添加应用</span>
</el-button>
</div>
</div>
<div class="hello-msg" ref="elements">{{ scope.item.intro }}</div>
</div>
</template>
</ItemList>
</div>
<login-dialog :show="showLoginDialog" @hide="showLoginDialog = false"/>
</div>
</template>
<script setup>
import {ref} from "vue"
import {nextTick, onMounted, ref} from "vue"
import {ElMessage} from "element-plus";
import {httpGet, httpPost} from "@/utils/http";
import ItemList from "@/components/ItemList.vue";
import {Delete, Plus} from "@element-plus/icons-vue";
import LoginDialog from "@/components/LoginDialog.vue";
import {checkSession} from "@/action/session";
import {arrayContains, removeArrayItem, substr} from "@/utils/libs";
const winHeight = ref(window.innerHeight)
const listBoxHeight = window.innerHeight - 97
const list = ref([])
const showLoginDialog = ref(false)
const roles = ref([])
const elements = ref(null)
onMounted(() => {
httpGet("/api/role/list?all=true").then((res) => {
const items = res.data
// 处理 hello message
for (let i = 0; i < items.length; i++) {
items[i].intro = substr(items[i].hello_msg, 80)
}
list.value = items
}).catch(e => {
ElMessage.error("获取应用失败:" + e.message)
})
checkSession().then(user => {
roles.value = user.chat_roles
}).catch(() => {
})
})
const updateRole = (row, opt) => {
checkSession().then(() => {
const title = ref("")
if (opt === "add") {
title.value = "添加应用"
const exists = arrayContains(roles.value, row.key)
if (exists) {
return
}
roles.value.push(row.key)
} else {
title.value = "移除应用"
const exists = arrayContains(roles.value, row.key)
if (!exists) {
return
}
roles.value = removeArrayItem(roles.value, row.key)
}
httpPost("/api/role/update", {keys: roles.value}).then(() => {
ElMessage.success(title.value + "成功!")
}).catch(e => {
ElMessage.error(title.value + "失败:" + e.message)
})
}).catch(() => {
showLoginDialog.value = true
})
}
const hasRole = (roleKey) => {
return arrayContains(roles.value, roleKey, (v1, v2) => v1 === v2)
}
</script>
<style lang="stylus" scoped>
.page-apps {
display: flex;
justify-content: center;
align-items center
background-color: #282c34;
.inner {
text-align center
h1 {
color: #202020;
font-size: 80px;
font-weight: bold;
letter-spacing: 0.1em;
text-shadow: -1px -1px 1px #111111, 2px 2px 1px #363636;
}
h2 {
color #ffffff;
font-weight: bold;
}
}
}
<style lang="stylus">
@import "@/assets/css/chat-app.styl"
@import "@/assets/css/custom-scroll.styl"
</style>

View File

@@ -553,7 +553,7 @@ const connect = function (chat_id, role_id) {
content: _role['hello_msg'],
orgContent: _role['hello_msg'],
})
ElMessage.success({message: "对话连接成功!", duration: 500})
ElMessage.success({message: "对话连接成功!", duration: 1000})
} else { // 加载聊天记录
loadChatHistory(chat_id);
}

View File

@@ -45,7 +45,7 @@ const navs = ref([
{path: "/mj", icon: "image", title: "MJ 绘画"},
{path: "/sd", icon: "palette", title: "SD 绘画"},
{path: "/apps", icon: "menu", title: "应用中心"},
{path: "/images", icon: "image-list", title: "绘画社区"},
{path: "/images-wall", icon: "image-list", title: "作品展示"},
{path: "/knowledge", icon: "book", title: "我的知识库"},
{path: "/member", icon: "vip-user", title: "会员计划"},
{path: "/invite", icon: "share", title: "推广计划"},
@@ -109,7 +109,7 @@ const changeNav = (item) => {
}
a:hover, a.active {
color #58D3FF
color #47fff1
}
}
}

View File

@@ -1,6 +1,6 @@
<template>
<div class="page-mj">
<div class="inner">
<div class="inner custom-scroll">
<div class="mj-box">
<h2>MidJourney 创作中心</h2>
@@ -226,66 +226,48 @@
<ItemList :items="runningJobs" v-if="runningJobs.length > 0">
<template #default="scope">
<div class="job-item">
<el-popover
placement="top-start"
:title="getTaskType(scope.item.type)"
:width="240"
trigger="hover"
>
<template #reference>
<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 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>
</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>
</template>
</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>
<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-popover>
</el-image>
</div>
</template>
</ItemList>
<el-empty :image-size="100" v-else/>
</div>
<h2>创作记录</h2>
<div class="finish-job-list">
<ItemList :items="finishedJobs" v-if="finishedJobs.length > 0">
<ItemList :items="finishedJobs" v-if="finishedJobs.length > 0" width="240" :gap="16">
<template #default="scope">
<div class="job-item">
<el-image
@@ -356,6 +338,8 @@
</div>
</template>
</ItemList>
<el-empty :image-size="100" v-else/>
</div> <!-- end finish job list-->
</div>
@@ -379,7 +363,6 @@ import {getSessionId, getUserToken} from "@/store/session";
const listBoxHeight = ref(window.innerHeight - 40)
const mjBoxHeight = ref(window.innerHeight - 150)
window.onresize = () => {
listBoxHeight.value = window.innerHeight - 40
mjBoxHeight.value = window.innerHeight - 150
@@ -476,14 +459,14 @@ onMounted(() => {
checkSession().then(user => {
imgCalls.value = user['img_calls']
// 获取运行中的任务
httpGet("/api/mj/jobs?status=0").then(res => {
httpGet(`/api/mj/jobs?status=0&user_id=${user['id']}`).then(res => {
runningJobs.value = res.data
}).catch(e => {
ElMessage.error("获取任务失败:" + e.message)
})
// 获取运行中的任务
httpGet("/api/mj/jobs?status=1").then(res => {
httpGet(`/api/mj/jobs?status=1&user_id=${user['id']}`).then(res => {
finishedJobs.value = res.data
previewImgList.value = []
for (let index in finishedJobs.value) {
@@ -501,7 +484,7 @@ onMounted(() => {
const clipboard = new Clipboard('.copy-prompt');
clipboard.on('success', () => {
ElMessage.success({message: "复制成功!", duration: 500});
ElMessage.success("复制成功!");
})
clipboard.on('error', () => {
@@ -601,4 +584,5 @@ const send = (url, index, item) => {
<style lang="stylus">
@import "@/assets/css/image-mj.styl"
@import "@/assets/css/custom-scroll.styl"
</style>

View File

@@ -289,58 +289,40 @@
<div class="task-list-inner" :style="{ height: listBoxHeight + 'px' }">
<h2>任务列表</h2>
<div class="running-job-list">
<ItemList :items="runningJobs" v-if="runningJobs.length > 0">
<ItemList :items="runningJobs" v-if="runningJobs.length > 0" width="240">
<template #default="scope">
<div class="job-item">
<el-popover
placement="top-start"
title="绘画提示词"
:width="240"
trigger="hover"
>
<template #reference>
<div v-if="scope.item.progress > 0" class="job-item-inner">
<el-image :src="scope.item['img_url']"
fit="cover"
loading="lazy">
<template #placeholder>
<div class="image-slot">
正在加载图片
</div>
</template>
<template #error>
<div class="image-slot">
<el-icon v-if="scope.item['img_url'] !== ''">
<Picture/>
</el-icon>
</div>
</template>
</el-image>
<div class="progress">
<el-progress type="circle" :percentage="scope.item.progress" :width="100" color="#47fff1"/>
<div v-if="scope.item.progress > 0" class="job-item-inner">
<el-image :src="scope.item['img_url']"
fit="cover"
loading="lazy">
<template #placeholder>
<div class="image-slot">
正在加载图片
</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>
</template>
</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>
<template #error>
<div class="image-slot">
<el-icon v-if="scope.item['img_url'] !== ''">
<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-popover>
</el-image>
</div>
</template>
</ItemList>
@@ -348,7 +330,7 @@
</div>
<h2>创作记录</h2>
<div class="finish-job-list">
<ItemList :items="finishedJobs" v-if="finishedJobs.length > 0">
<ItemList :items="finishedJobs" v-if="finishedJobs.length > 0" width="240" :gap="16">
<template #default="scope">
<div class="job-item" @click="showTask(scope.item)">
<el-image
@@ -372,6 +354,7 @@
</div>
</template>
</ItemList>
<el-empty :image-size="100" v-else/>
</div> <!-- end finish job list-->
</div>
@@ -539,6 +522,11 @@ const runningJobs = ref([])
const finishedJobs = ref([])
const previewImgList = ref([])
const router = useRouter()
// 检查是否有画同款的参数
const _params = router.currentRoute.value.params["copyParams"]
if (_params) {
params.value = JSON.parse(_params)
}
const socket = ref(null)
const imgCalls = ref(0)
@@ -614,14 +602,14 @@ onMounted(() => {
checkSession().then(user => {
imgCalls.value = user['img_calls']
// 获取运行中的任务
httpGet("/api/sd/jobs?status=0").then(res => {
httpGet(`/api/sd/jobs?status=0&user_id=${user['id']}`).then(res => {
runningJobs.value = res.data
}).catch(e => {
ElMessage.error("获取任务失败:" + e.message)
})
// 获取运行中的任务
httpGet("/api/sd/jobs?status=1").then(res => {
httpGet(`/api/sd/jobs?status=1&user_id=${user['id']}`).then(res => {
finishedJobs.value = res.data
previewImgList.value = []
for (let index in finishedJobs.value) {
@@ -639,7 +627,7 @@ onMounted(() => {
const clipboard = new Clipboard('.copy-prompt');
clipboard.on('success', () => {
ElMessage.success({message: "复制成功!", duration: 500});
ElMessage.success("复制成功!");
})
clipboard.on('error', () => {
@@ -681,4 +669,5 @@ const copyParams = (row) => {
<style lang="stylus">
@import "@/assets/css/image-sd.styl"
@import "@/assets/css/custom-scroll.styl"
</style>

View File

@@ -1,41 +0,0 @@
<template>
<div class="page-images" :style="{ height: winHeight + 'px' }">
<div class="inner">
<h1>绘画作品广场</h1>
<h2>页面正在紧锣密鼓开发中敬请期待</h2>
</div>
</div>
</template>
<script setup>
import {ref} from "vue"
const winHeight = ref(window.innerHeight)
</script>
<style lang="stylus" scoped>
.page-images {
display: flex;
justify-content: center;
align-items center
background-color: #282c34;
.inner {
text-align center
h1 {
color: #202020;
font-size: 80px;
font-weight: bold;
letter-spacing: 0.1em;
text-shadow: -1px -1px 1px #111111, 2px 2px 1px #363636;
}
h2 {
color #ffffff;
font-weight: bold;
}
}
}
</style>

View File

@@ -0,0 +1,317 @@
<template>
<div class="page-images-wall">
<div class="inner custom-scroll">
<div class="header">
<h2>AI 绘画作品墙</h2>
<div class="settings">
<el-radio-group v-model="imgType" @change="changeImgType">
<el-radio label="mj" size="large">MidJourney</el-radio>
<el-radio label="sd" size="large">Stable Diffusion</el-radio>
</el-radio-group>
</div>
</div>
<div class="waterfall" :style="{ height:listBoxHeight + 'px' }" id="waterfall-box">
<v3-waterfall id="waterfall" :list="list" srcKey="img_thumb"
:gap="12"
:bottomGap="-5"
:colWidth="colWidth"
:distanceToScroll="100"
:isLoading="loading"
:isOver="false"
@scrollReachBottom="getNext">
<template #default="slotProp">
<div class="list-item">
<div class="image" v-if="imgType === 'mj'">
<el-image :src="slotProp.item['img_thumb']"
:zoom-rate="1.2"
:preview-src-list="[slotProp.item['img_url']]"
:preview-teleported="true"
:initial-index="10"
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>
<div class="image" v-else>
<el-image :src="slotProp.item['img_thumb']" loading="lazy"
@click="showTask(slotProp.item)">
<template #placeholder>
<div class="image-slot">
正在加载图片
</div>
</template>
<template #error>
<div class="image-slot">
<el-icon>
<Picture/>
</el-icon>
</div>
</template>
</el-image>
</div>
<div class="prompt">
<span>{{ slotProp.item.prompt }}</span>
<el-icon class="copy-prompt" :data-clipboard-text="slotProp.item.prompt">
<DocumentCopy/>
</el-icon>
</div>
</div>
</template>
</v3-waterfall>
<div class="footer" v-if="isOver">
<span>没有更多数据了</span>
<i class="iconfont icon-face"></i>
</div>
</div>
</div>
<!-- 任务详情弹框 -->
<el-dialog v-model="showTaskDialog" title="绘画任务详情" :fullscreen="true">
<el-row :gutter="20">
<el-col :span="16">
<div class="img-container" :style="{maxHeight: fullImgHeight+'px'}">
<el-image :src="item['img_url']" fit="contain">
<template #placeholder>
<div class="image-slot">
正在加载图片
</div>
</template>
<template #error>
<div class="image-slot">
<el-icon>
<Picture/>
</el-icon>
</div>
</template>
</el-image>
</div>
</el-col>
<el-col :span="8">
<div class="task-info">
<div class="info-line">
<el-divider>
正向提示词
</el-divider>
<div class="prompt">
<span>{{ item.prompt }}</span>
<el-icon class="copy-prompt" :data-clipboard-text="item.prompt">
<DocumentCopy/>
</el-icon>
</div>
</div>
<div class="info-line">
<el-divider>
反向提示词
</el-divider>
<div class="prompt">
<span>{{ item.params.negative_prompt }}</span>
<el-icon class="copy-prompt" :data-clipboard-text="item.params.negative_prompt">
<DocumentCopy/>
</el-icon>
</div>
</div>
<div class="info-line">
<div class="wrapper">
<label>采样方法</label>
<div class="item-value">{{ item.params.sampler }}</div>
</div>
</div>
<div class="info-line">
<div class="wrapper">
<label>图片尺寸</label>
<div class="item-value">{{ item.params.width }} x {{ item.params.height }}</div>
</div>
</div>
<div class="info-line">
<div class="wrapper">
<label>迭代步数</label>
<div class="item-value">{{ item.params.steps }}</div>
</div>
</div>
<div class="info-line">
<div class="wrapper">
<label>引导系数</label>
<div class="item-value">{{ item.params.cfg_scale }}</div>
</div>
</div>
<div class="info-line">
<div class="wrapper">
<label>随机因子</label>
<div class="item-value">{{ item.params.seed }}</div>
</div>
</div>
<div v-if="item.params.hd_fix">
<el-divider>
高清修复
</el-divider>
<div class="info-line">
<div class="wrapper">
<label>重绘幅度</label>
<div class="item-value">{{ item.params.hd_redraw_rate }}</div>
</div>
</div>
<div class="info-line">
<div class="wrapper">
<label>放大算法</label>
<div class="item-value">{{ item.params.hd_scale_alg }}</div>
</div>
</div>
<div class="info-line">
<div class="wrapper">
<label>放大倍数</label>
<div class="item-value">{{ item.params.hd_scale }}</div>
</div>
</div>
<div class="info-line">
<div class="wrapper">
<label>迭代步数</label>
<div class="item-value">{{ item.params.hd_steps }}</div>
</div>
</div>
</div>
<div class="copy-params">
<el-button type="primary" round @click="copyParams(item)">画一张同款的</el-button>
</div>
</div>
</el-col>
</el-row>
</el-dialog>
</div>
</template>
<script setup>
import {nextTick, onMounted, ref} from "vue"
import {DocumentCopy, Picture} from "@element-plus/icons-vue";
import {httpGet} from "@/utils/http";
import {ElMessage} from "element-plus";
import Clipboard from "clipboard";
import {useRouter} from "vue-router";
const list = ref([])
const loading = ref(true)
const isOver = ref(false)
const imgType = ref("mj") // 图片类别
const listBoxHeight = window.innerHeight - 74
const colWidth = ref(240)
const fullImgHeight = ref(window.innerHeight - 60)
const showTaskDialog = ref(false)
const item = ref({})
// 计算瀑布流列宽度
const calcColWidth = () => {
const listBoxWidth = window.innerWidth - 60 - 80
const rows = Math.floor(listBoxWidth / colWidth.value)
colWidth.value = Math.floor((listBoxWidth - (rows - 1) * 12) / rows)
}
calcColWidth()
window.onresize = () => {
calcColWidth()
}
const page = ref(0)
const pageSize = ref(20)
// 获取下一页数据
const getNext = () => {
if (isOver.value) {
return
}
loading.value = true
page.value = page.value + 1
const url = imgType.value === "mj" ? "/api/mj/jobs" : "/api/sd/jobs"
// 获取运行中的任务
httpGet(`${url}?status=1&page=${page.value}&page_size=${pageSize.value}`).then(res => {
loading.value = false
if (res.data.length === 0) {
isOver.value = true
return
}
// 生成缩略图
const imageList = res.data
for (let i = 0; i < imageList.length; i++) {
imageList[i]["img_thumb"] = imageList[i]["img_url"] + "?imageView2/4/w/300/q/75"
}
if (list.value.length === 0) {
list.value = imageList
return
}
if (imageList.length < pageSize.value) {
isOver.value = true
}
list.value = list.value.concat(imageList)
}).catch(e => {
ElMessage.error("获取图片失败:" + e.message)
})
}
getNext()
onMounted(() => {
const clipboard = new Clipboard('.copy-prompt');
clipboard.on('success', () => {
ElMessage.success("复制成功!");
})
clipboard.on('error', () => {
ElMessage.error('复制失败!');
})
})
const changeImgType = () => {
document.getElementById('waterfall-box').scrollTo(0, 0)
page.value = 0
list.value = []
loading.value = true
isOver.value = false
nextTick(() => getNext())
}
const showTask = (row) => {
item.value = row
showTaskDialog.value = true
}
const router = useRouter()
const copyParams = (row) => {
router.push({name: "image-sd", params: {copyParams: JSON.stringify(row.params)}})
}
</script>
<style lang="stylus">
@import "@/assets/css/images-wall.styl"
@import "@/assets/css/custom-scroll.styl"
</style>

View File

@@ -109,6 +109,7 @@ import SendMsg from "@/components/SendMsg.vue";
import {validateMobile} from "@/utils/validate";
import {isMobile} from "@/utils/libs";
import SendMsgMobile from "@/components/SendMsg.vue";
import {setUserToken} from "@/store/session";
const router = useRouter();
const title = ref('ChatGPT-PLUS 用户注册');
@@ -144,8 +145,13 @@ const register = function () {
return ElMessage.error('请输入短信验证码');
}
formData.value.code = parseInt(formData.value.code)
httpPost('/api/user/register', formData.value).then(() => {
ElMessage.success({"message": "注册成功,即将跳转到登录页...", onClose: () => router.push("/login")})
httpPost('/api/user/register', formData.value).then((res) => {
setUserToken(res.data)
ElMessage.success({
"message": "注册成功,即将跳转到对话主界面...",
onClose: () => router.push("/chat"),
duration: 1000
})
}).catch((e) => {
ElMessage.error('注册失败,' + e.message)
})