From 8b2cf90aab752b9dff9cc6f484ce88992c4cc64c Mon Sep 17 00:00:00 2001 From: RockYang Date: Mon, 8 Sep 2025 08:43:32 +0800 Subject: [PATCH] allow user to upload attachment for mobile chat page --- api/handler/admin/moderation_handler.go | 11 +- api/handler/chat_handler.go | 1 + web/src/assets/css/mobile/chat-session.scss | 71 ++++++++++ web/src/components/FileList.vue | 5 +- web/src/components/mobile/ChatPrompt.vue | 65 +++++++-- web/src/components/mobile/ChatReply.vue | 68 ++++++++-- web/src/components/mobile/MobileFileList.vue | 68 ++++++++++ web/src/views/ChatPlus.vue | 4 +- web/src/views/mobile/ChatList.vue | 44 +----- web/src/views/mobile/ChatSession.vue | 134 +++++++++++-------- 10 files changed, 357 insertions(+), 114 deletions(-) create mode 100644 web/src/components/mobile/MobileFileList.vue diff --git a/api/handler/admin/moderation_handler.go b/api/handler/admin/moderation_handler.go index c1d4b29c..86317332 100644 --- a/api/handler/admin/moderation_handler.go +++ b/api/handler/admin/moderation_handler.go @@ -244,7 +244,16 @@ func (h *ModerationHandler) UpdateModeration(c *gin.Context) { return } - err := h.DB.Where("name", types.ConfigKeyModeration).FirstOrCreate(&model.Config{Name: types.ConfigKeyModeration, Value: utils.JsonEncode(data)}).Error + var config model.Config + err := h.DB.Where("name", types.ConfigKeyModeration).First(&config).Error + if err != nil { + config.Name = types.ConfigKeyModeration + config.Value = utils.JsonEncode(data) + err = h.DB.Create(&config).Error + } else { + config.Value = utils.JsonEncode(data) + err = h.DB.Updates(&config).Error + } if err != nil { resp.ERROR(c, err.Error()) return diff --git a/api/handler/chat_handler.go b/api/handler/chat_handler.go index 02bae8d2..c44bb172 100644 --- a/api/handler/chat_handler.go +++ b/api/handler/chat_handler.go @@ -345,6 +345,7 @@ func (h *ChatHandler) sendMessage(ctx context.Context, input ChatInput, c *gin.C continue } else { fileContents = append(fileContents, fmt.Sprintf("%s 文件内容:%s", file.Name, content)) + logger.Debugf("fileContents: %s", fileContents) } } } diff --git a/web/src/assets/css/mobile/chat-session.scss b/web/src/assets/css/mobile/chat-session.scss index bea42876..41773c0d 100644 --- a/web/src/assets/css/mobile/chat-session.scss +++ b/web/src/assets/css/mobile/chat-session.scss @@ -49,6 +49,77 @@ } } } + + // .file-preview-list { + // display: flex; + // flex-wrap: wrap; + // padding: 6px 10px 0 10px; + // gap: 8px; + + // .file-preview-item { + // position: relative; + // display: inline-flex; + // align-items: center; + // border: 1px solid #e8e8e8; + // background: var(--van-cell-background); + // border-radius: 8px; + // padding: 6px 26px 6px 6px; + // overflow: hidden; + + // .thumb { + // width: 56px; + // height: 56px; + // border-radius: 6px; + // overflow: hidden; + + // .img { + // width: 56px; + // height: 56px; + // } + + // .size { + // position: absolute; + // left: 6px; + // bottom: 6px; + // background: rgba(0, 0, 0, 0.5); + // color: #fff; + // font-size: 10px; + // padding: 1px 4px; + // border-radius: 3px; + // } + // } + + // .doc { + // display: inline-flex; + // align-items: center; + // gap: 6px; + // max-width: 220px; + // .icon { + // width: 24px; + // height: 24px; + // } + // .name { + // white-space: nowrap; + // text-overflow: ellipsis; + // overflow: hidden; + // max-width: 180px; + // } + // .size { + // color: #8c8c8c; + // font-size: 12px; + // margin-left: 6px; + // } + // } + + // .remove { + // position: absolute; + // right: 6px; + // top: 6px; + // font-size: 14px; + // color: #999; + // } + // } + // } } } diff --git a/web/src/components/FileList.vue b/web/src/components/FileList.vue index b0b739c0..0ecb5cee 100644 --- a/web/src/components/FileList.vue +++ b/web/src/components/FileList.vue @@ -81,6 +81,7 @@ const removeFile = (file) => { border: 1px solid #e3e3e3; padding: 6px; margin-right: 10px; + max-height: 54px; .icon { .el-image { @@ -91,10 +92,10 @@ const removeFile = (file) => { .body { margin-left: 5px; - font-size: 14px; + font-size: 12px; .title { - line-height: 24px; + // line-height: 20px; color: #0d0d0d; } diff --git a/web/src/components/mobile/ChatPrompt.vue b/web/src/components/mobile/ChatPrompt.vue index e1441c25..574b303a 100644 --- a/web/src/components/mobile/ChatPrompt.vue +++ b/web/src/components/mobile/ChatPrompt.vue @@ -1,12 +1,17 @@ @@ -15,6 +20,7 @@ import Clipboard from 'clipboard' import { showNotify } from 'vant' import { onMounted, ref } from 'vue' +import MobileFileList from '@/components/mobile/MobileFileList.vue' // eslint-disable-next-line no-unused-vars,no-undef const props = defineProps({ @@ -34,6 +40,7 @@ const contentRef = ref(null) const content = computed(() => { return props.content.text }) +const files = computed(() => props.content.files || []) onMounted(() => { const clipboard = new Clipboard(contentRef.value) clipboard.on('success', () => { @@ -47,9 +54,6 @@ onMounted(() => { diff --git a/web/src/views/ChatPlus.vue b/web/src/views/ChatPlus.vue index 4987bb61..5a8becbf 100644 --- a/web/src/views/ChatPlus.vue +++ b/web/src/views/ChatPlus.vue @@ -384,7 +384,7 @@ import FileSelect from '@/components/FileSelect.vue' import Welcome from '@/components/Welcome.vue' import { checkSession, getClientId, getSystemInfo } from '@/store/cache' import { useSharedStore } from '@/store/sharedata' -import { closeLoading, showLoading, showMessageError } from '@/utils/dialog' +import { closeLoading, showLoading, showMessageError, showMessageInfo } from '@/utils/dialog' import { httpGet, httpPost } from '@/utils/http' import { isMobile, randString, removeArrayItem, UUID } from '@/utils/libs' import { @@ -1162,7 +1162,7 @@ const stopGenerate = function () { isGenerating.value = false httpGet('/api/chat/stop?session_id=' + getClientId()) .then(() => { - console.log('会话已中断') + showMessageInfo('会话已中断') }) .catch((e) => { showMessageError('中断对话失败:' + e.message) diff --git a/web/src/views/mobile/ChatList.vue b/web/src/views/mobile/ChatList.vue index af796b56..6367c5f9 100644 --- a/web/src/views/mobile/ChatList.vue +++ b/web/src/views/mobile/ChatList.vue @@ -104,8 +104,12 @@ checkSession() .then((user) => { loginUser.value = user isLogin.value = true + }) + .finally(() => { + loading.value = false + finished.value = true // 加载角色列表 - httpGet(`/api/app/list/user`) + httpGet(`/api/app/list`) .then((res) => { if (res.data) { const items = res.data @@ -139,44 +143,6 @@ checkSession() showFailToast('加载模型失败: ' + e.message) }) }) - .catch(() => { - loading.value = false - finished.value = true - - // 加载角色列表 - httpGet('/api/app/list/user') - .then((res) => { - if (res.data) { - const items = res.data - for (let i = 0; i < items.length; i++) { - // console.log(items[i]) - roles.value.push({ - text: items[i].name, - value: items[i].id, - icon: items[i].icon, - helloMsg: items[i].hello_msg, - }) - } - } - }) - .catch(() => { - showFailToast('加载聊天角色失败') - }) - - // 加载模型 - httpGet('/api/model/list') - .then((res) => { - if (res.data) { - const items = res.data - for (let i = 0; i < items.length; i++) { - models.value.push({ text: items[i].name, value: items[i].id }) - } - } - }) - .catch((e) => { - showFailToast('加载模型失败: ' + e.message) - }) - }) const onLoad = () => { checkSession() diff --git a/web/src/views/mobile/ChatSession.vue b/web/src/views/mobile/ChatSession.vue index d2c7dbd5..e160d3bf 100644 --- a/web/src/views/mobile/ChatSession.vue +++ b/web/src/views/mobile/ChatSession.vue @@ -58,8 +58,23 @@ center clearable placeholder="输入你的问题" + type="textarea" + rows="1" + :autosize="{ maxHeight: 100, minHeight: 20 }" + show-word-limit @keyup.enter="sendMessage" > +