opt: adjust styles for ItemList component, cut string for chat role's hello message

This commit is contained in:
RockYang 2023-10-16 10:46:10 +08:00
parent d2a8d655c8
commit a80d01209c
14 changed files with 221 additions and 139 deletions

View File

@ -51,7 +51,7 @@ ChatGLM,讯飞星火,文心一言等多个平台的大语言模型。集成了
![Mobile chat list](/docs/imgs/mobile_chat_list.png) ![Mobile chat list](/docs/imgs/mobile_chat_list.png)
![Mobile chat session](/docs/imgs/mobile_chat_session.png) ![Mobile chat session](/docs/imgs/mobile_chat_session.png)
![Mobile chat setting](/docs/imgs/mobile_user_profile.png) ![Mobile chat setting](/docs/imgs/mobile_user_profile.png)
![Mobile chat setting](/docs/imgs/mobile_user_profile.png) ![Mobile chat setting](/docs/imgs/mobile_pay.png)
### 7. 体验地址 ### 7. 体验地址

View File

@ -78,8 +78,8 @@ func (h *ChatRoleHandler) List(c *gin.Context) {
resp.SUCCESS(c, roleVos) resp.SUCCESS(c, roleVos)
} }
// AddRole 为用户添加角色 // UpdateRole 更新用户聊天角色
func (h *ChatRoleHandler) AddRole(c *gin.Context) { func (h *ChatRoleHandler) UpdateRole(c *gin.Context) {
user, err := utils.GetLoginUser(c, h.db) user, err := utils.GetLoginUser(c, h.db)
if err != nil { if err != nil {
resp.NotAuth(c) resp.NotAuth(c)

View File

@ -8,7 +8,6 @@ import (
"chatplus/store/vo" "chatplus/store/vo"
"chatplus/utils" "chatplus/utils"
"chatplus/utils/resp" "chatplus/utils/resp"
"encoding/base64"
"fmt" "fmt"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/go-redis/redis/v8" "github.com/go-redis/redis/v8"
@ -160,7 +159,7 @@ func (h *SdJobHandler) Image(c *gin.Context) {
resp.SUCCESS(c) resp.SUCCESS(c)
} }
// JobList 获取 MJ 任务列表 // JobList 获取 stable diffusion 任务列表
func (h *SdJobHandler) JobList(c *gin.Context) { func (h *SdJobHandler) JobList(c *gin.Context) {
status := h.GetInt(c, "status", 0) status := h.GetInt(c, "status", 0)
userId := h.GetInt(c, "user_id", 0) userId := h.GetInt(c, "user_id", 0)
@ -201,12 +200,6 @@ func (h *SdJobHandler) JobList(c *gin.Context) {
h.db.Delete(&item) h.db.Delete(&item)
continue 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) jobs = append(jobs, job)
} }

View File

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

View File

@ -13,8 +13,44 @@
.page-apps .inner { .page-apps .inner {
display: flex; display: flex;
color: #fff; color: #fff;
padding: 20px; padding: 15px;
overflow-y: visible;
overflow-x: hidden;
} }
.page-apps .inner .left-menu { .page-apps .inner .list-box .app-item {
width: 160px; 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

@ -52,9 +52,6 @@
} }
.hello-msg { .hello-msg {
overflow: hidden;
white-space normal
text-overflow: ellipsis;
height 60px height 60px
padding 10px padding 10px
font-size 14px font-size 14px

View File

@ -176,6 +176,11 @@
.page-mj .inner .task-list-box .finish-job-list .job-item { .page-mj .inner .task-list-box .finish-job-list .job-item {
width: 100%; width: 100%;
height: 100%; height: 100%;
border: 1px solid #666;
padding: 6px;
overflow: hidden;
border-radius: 6px;
transition: all 0.3s ease; /* 添加过渡效果 */
} }
.page-mj .inner .task-list-box .finish-job-list .job-item .opt .opt-line { .page-mj .inner .task-list-box .finish-job-list .job-item .opt .opt-line {
margin: 6px 0; margin: 6px 0;
@ -185,11 +190,11 @@
flex-flow: row; 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 .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 { .page-mj .inner .task-list-box .finish-job-list .job-item .opt .opt-line ul li a {
padding: 3px 0; padding: 3px 0;
width: 44px; width: 40px;
text-align: center; text-align: center;
border-radius: 5px; border-radius: 5px;
display: block; display: block;
@ -204,10 +209,14 @@
font-size: 20px; font-size: 20px;
cursor: pointer; 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 { .page-mj .inner .task-list-box .el-image {
width: 100%; width: 100%;
height: 100%; height: 100%;
max-height: 240px; overflow: visible;
} }
.page-mj .inner .task-list-box .el-image img { .page-mj .inner .task-list-box .el-image img {
height: 240px; height: 240px;
@ -224,16 +233,17 @@
height: 100%; height: 100%;
min-height: 200px; min-height: 200px;
color: #fff; color: #fff;
height: 240px;
} }
.page-mj .inner .task-list-box .el-image .image-slot .iconfont { .page-mj .inner .task-list-box .el-image .image-slot .iconfont {
font-size: 50px; font-size: 50px;
margin-bottom: 10px; margin-bottom: 10px;
} }
.page-mj .inner .task-list-box .el-image.upscale { .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 { .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 { .page-mj .inner .task-list-box .el-image.upscale .el-image-viewer__wrapper img {
width: auto; width: auto;

View File

@ -99,6 +99,11 @@
.page-sd .inner .task-list-box .finish-job-list .job-item { .page-sd .inner .task-list-box .finish-job-list .job-item {
width: 100%; width: 100%;
height: 100%; height: 100%;
border: 1px solid #666;
padding: 6px;
overflow: hidden;
border-radius: 6px;
transition: all 0.3s ease; /* 添加过渡效果 */
} }
.page-sd .inner .task-list-box .finish-job-list .job-item .opt .opt-line { .page-sd .inner .task-list-box .finish-job-list .job-item .opt .opt-line {
margin: 6px 0; margin: 6px 0;
@ -108,11 +113,11 @@
flex-flow: row; 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 .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 { .page-sd .inner .task-list-box .finish-job-list .job-item .opt .opt-line ul li a {
padding: 3px 0; padding: 3px 0;
width: 44px; width: 40px;
text-align: center; text-align: center;
border-radius: 5px; border-radius: 5px;
display: block; display: block;
@ -127,10 +132,14 @@
font-size: 20px; font-size: 20px;
cursor: pointer; 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 { .page-sd .inner .task-list-box .el-image {
width: 100%; width: 100%;
height: 100%; height: 100%;
max-height: 240px; overflow: visible;
} }
.page-sd .inner .task-list-box .el-image img { .page-sd .inner .task-list-box .el-image img {
height: 240px; height: 240px;
@ -147,16 +156,17 @@
height: 100%; height: 100%;
min-height: 200px; min-height: 200px;
color: #fff; color: #fff;
height: 240px;
} }
.page-sd .inner .task-list-box .el-image .image-slot .iconfont { .page-sd .inner .task-list-box .el-image .image-slot .iconfont {
font-size: 50px; font-size: 50px;
margin-bottom: 10px; margin-bottom: 10px;
} }
.page-sd .inner .task-list-box .el-image.upscale { .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 { .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 { .page-sd .inner .task-list-box .el-image.upscale .el-image-viewer__wrapper img {
width: auto; width: auto;
@ -180,6 +190,15 @@
display: flex; display: flex;
justify-content: center; 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 { .page-sd .el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info {
background-color: #25262b; background-color: #25262b;
padding: 1rem 1.5rem; padding: 1rem 1.5rem;

View File

@ -102,6 +102,15 @@
display: flex; display: flex;
justify-content: center; 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 { .page-images-wall .el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info {
background-color: #25262b; background-color: #25262b;
padding: 1rem 1.5rem; padding: 1rem 1.5rem;

View File

@ -44,6 +44,7 @@
padding 6px padding 6px
overflow hidden overflow hidden
border-radius 6px border-radius 6px
transition: all 0.3s ease; /* */
.opt { .opt {
.opt-line { .opt-line {
@ -54,7 +55,7 @@
flex-flow row flex-flow row
li { li {
margin-right 10px margin-right 6px
a { a {
padding 3px 0 padding 3px 0
@ -80,6 +81,11 @@
} }
} }
&:hover {
box-shadow: 0 0 10px rgba(71, 255, 241, 0.6); /* */
transform: translateY(-10px); /* 10 */
}
} }
} }
@ -87,7 +93,7 @@
.el-image { .el-image {
width 100% width 100%
height 100% height 100%
max-height 240px overflow visible
img { img {
height 240px height 240px
@ -108,6 +114,7 @@
height 100% height 100%
min-height 200px min-height 200px
color #ffffff color #ffffff
height 240px
.iconfont { .iconfont {
font-size 50px font-size 50px

View File

@ -128,3 +128,32 @@ export function copyObj(origin) {
export function disabledDate(time) { export function disabledDate(time) {
return time.getTime() < Date.now() 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

@ -12,7 +12,8 @@
<span class="name">{{ scope.item.name }}</span> <span class="name">{{ scope.item.name }}</span>
<div class="opt"> <div class="opt">
<el-button v-if="hasRole(scope.item.key)" size="small" type="danger"> <el-button v-if="hasRole(scope.item.key)" size="small" type="danger"
@click="updateRole(scope.item,'remove')">
<el-icon> <el-icon>
<Delete/> <Delete/>
</el-icon> </el-icon>
@ -20,7 +21,7 @@
</el-button> </el-button>
<el-button v-else size="small" <el-button v-else size="small"
style="--el-color-primary:#009999" style="--el-color-primary:#009999"
@click="addRole(scope.item)"> @click="updateRole(scope.item, 'add')">
<el-icon> <el-icon>
<Plus/> <Plus/>
</el-icon> </el-icon>
@ -28,7 +29,7 @@
</el-button> </el-button>
</div> </div>
</div> </div>
<div class="hello-msg">{{ scope.item['hello_msg'] }}</div> <div class="hello-msg" ref="elements">{{ scope.item.intro }}</div>
</div> </div>
</template> </template>
</ItemList> </ItemList>
@ -39,22 +40,28 @@
</template> </template>
<script setup> <script setup>
import {onMounted, ref} from "vue" import {nextTick, onMounted, ref} from "vue"
import {ElMessage} from "element-plus"; import {ElMessage} from "element-plus";
import {httpGet, httpPost} from "@/utils/http"; import {httpGet, httpPost} from "@/utils/http";
import ItemList from "@/components/ItemList.vue"; import ItemList from "@/components/ItemList.vue";
import {Delete, Plus} from "@element-plus/icons-vue"; import {Delete, Plus} from "@element-plus/icons-vue";
import LoginDialog from "@/components/LoginDialog.vue"; import LoginDialog from "@/components/LoginDialog.vue";
import {checkSession} from "@/action/session"; import {checkSession} from "@/action/session";
import {arrayContains} from "@/utils/libs"; import {arrayContains, removeArrayItem, substr} from "@/utils/libs";
const listBoxHeight = window.innerHeight - 97 const listBoxHeight = window.innerHeight - 97
const list = ref([]) const list = ref([])
const showLoginDialog = ref(false) const showLoginDialog = ref(false)
const roles = ref([]) const roles = ref([])
const elements = ref(null)
onMounted(() => { onMounted(() => {
httpGet("/api/role/list?all=true").then((res) => { httpGet("/api/role/list?all=true").then((res) => {
list.value = res.data 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 => { }).catch(e => {
ElMessage.error("获取应用失败:" + e.message) ElMessage.error("获取应用失败:" + e.message)
}) })
@ -63,19 +70,31 @@ onMounted(() => {
roles.value = user.chat_roles roles.value = user.chat_roles
}).catch(() => { }).catch(() => {
}) })
}) })
const addRole = (row) => { const updateRole = (row, opt) => {
checkSession().then(() => { checkSession().then(() => {
const exists = arrayContains(roles.value, row.key, (v1, v2) => v1 === v2) const title = ref("")
if (opt === "add") {
title.value = "添加应用"
const exists = arrayContains(roles.value, row.key)
if (exists) { if (exists) {
return return
} }
roles.value.push(row.key) roles.value.push(row.key)
httpPost("/api/role/add", {keys: roles.value}).then(() => { } else {
ElMessage.success("添加应用成功!") 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 => { }).catch(e => {
ElMessage.error("添加应用失败:" + e.message) ElMessage.error(title.value + "失败:" + e.message)
}) })
}).catch(() => { }).catch(() => {
showLoginDialog.value = true showLoginDialog.value = true

View File

@ -226,13 +226,6 @@
<ItemList :items="runningJobs" v-if="runningJobs.length > 0"> <ItemList :items="runningJobs" v-if="runningJobs.length > 0">
<template #default="scope"> <template #default="scope">
<div class="job-item"> <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"> <div v-if="scope.item.progress > 0" class="job-item-inner">
<el-image :src="scope.item['img_url']" <el-image :src="scope.item['img_url']"
:zoom-rate="1.2" :zoom-rate="1.2"
@ -266,18 +259,6 @@
</div> </div>
</template> </template>
</el-image> </el-image>
</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>
</div> </div>
</template> </template>
</ItemList> </ItemList>
@ -286,7 +267,7 @@
<h2>创作记录</h2> <h2>创作记录</h2>
<div class="finish-job-list"> <div class="finish-job-list">
<ItemList :items="finishedJobs" v-if="finishedJobs.length > 0" width="240"> <ItemList :items="finishedJobs" v-if="finishedJobs.length > 0" width="240" :gap="16">
<template #default="scope"> <template #default="scope">
<div class="job-item"> <div class="job-item">
<el-image <el-image

View File

@ -292,13 +292,6 @@
<ItemList :items="runningJobs" v-if="runningJobs.length > 0" width="240"> <ItemList :items="runningJobs" v-if="runningJobs.length > 0" width="240">
<template #default="scope"> <template #default="scope">
<div class="job-item"> <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"> <div v-if="scope.item.progress > 0" class="job-item-inner">
<el-image :src="scope.item['img_url']" <el-image :src="scope.item['img_url']"
fit="cover" fit="cover"
@ -330,17 +323,6 @@
</div> </div>
</template> </template>
</el-image> </el-image>
</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>
</div> </div>
</template> </template>
</ItemList> </ItemList>
@ -348,7 +330,7 @@
</div> </div>
<h2>创作记录</h2> <h2>创作记录</h2>
<div class="finish-job-list"> <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"> <template #default="scope">
<div class="job-item" @click="showTask(scope.item)"> <div class="job-item" @click="showTask(scope.item)">
<el-image <el-image