Merge remote-tracking branch 'origin/main'

This commit is contained in:
moon 2025-07-29 15:32:55 +08:00
commit fee5698dd9
3 changed files with 134 additions and 23 deletions

97
chat-client/.env Normal file
View File

@ -0,0 +1,97 @@
# Vab Admin 系列产品受国家计算机软件著作权保护(证书号:软著登字第 7051316 号)。
# 关于举报盗版侵权请发送举报材料至我司客服邮箱1204505056@qq.com一经查实官司所得收入20%归举报人所有80%归律师事务所所有。
# Vue Admin系列产品购买地址https://vue-admin-beautiful.com/authorization
# 1.购买者可将授权后的产品用于任意「符合国家法律法规」的应用平台,禁止用于黄赌毒等危害国家安全与稳定的网站。
# 2.购买主体购买后可用于开发商业项目,不限制域名和项目数量,购买主体不可将源码分享第三方,否则我们有权利收回产品授权及更新权限,并根据事态轻重追究相应法律责任。
# 3.购买者务必尊重知识产权,严格保证不恶意传播产品源码、不得直接对授权的产品本身进行二次转售或倒卖、开源、不得对授权的产品进行简单包装后声称为自己的产品等,无论有意或无意,我们有权利收回产品授权及更新权限,并根据事态轻重追究相应法律责任。
# 4.购买者不可将vip群文档及资料分享给第三方否则我们有权利收回产品授权及更新权限并根据事态轻重追究相应法律责任。
# 5.购买者购买项目不可以用来构建存在竞争性质的产品并直接对外销售否则我们有权利收回产品授权及更新权限,并根据事态轻重追究相应法律责任。
# 6.购买者购买项目中的源码(包含全部源码、及部分源码片段)不可以用于任何形式的开源项目,否则我们有权利收回产品授权及更新权限,并根据事态轻重追究相应法律责任。
# 7.用于公司的项目商用时购买需提供公司名称,用于证明购买过我们的项目来用于商业用途,防范法律风险,我们不会将【购买公司】信息泄漏到互联网或告知第三方。
# 8.用于个人学习需提供姓名、联系方式。
# 9.如用于外包项目购买者购买项目中的源码不可直接对外出售npm run build编译后的项目不受限制。
# 10.虚拟物品不支持退货退款。
# 11.最终解释权归vab系列著作权人所有。
VUE_GITHUB_USER_NAME=MichaelFindSpace
# 以下内容不建议修改建议将VUE_APP_SECRET_KEY配置到【.env.local】中
VUE_APP_SECRET_KEY=preview

View File

@ -3,7 +3,7 @@
<!-- 会话记录侧边栏 -->
<div class="chat-sidebar" :class="{ 'sidebar-collapsed': isSidebarCollapsed }">
<div class="sidebar-header">
<h3 v-show="!isSidebarCollapsed">{{ $t('vabI18n.chat.history') }}</h3>
<h3 v-show="!isSidebarCollapsed">{{ t('vabI18n.chat.history') }}</h3>
<el-button
type="primary"
class="new-chat-btn"
@ -13,7 +13,7 @@
<svg viewBox="0 0 24 24" class="new-chat-icon">
<path fill="currentColor" d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
</svg>
{{ $t('vabI18n.chat.newChat') }}
{{ t('vabI18n.chat.newChat') }}
</el-button>
</div>
<div class="chat-history" v-show="!isSidebarCollapsed">
@ -24,7 +24,7 @@
:class="{ 'active': currentChatIndex === index }"
@click="switchChat(index)"
>
<span class="chat-title">{{ chat.title || $t('vabI18n.chat.chat') + ` ${index + 1}` }}</span>
<span class="chat-title">{{ chat.title || t('vabI18n.chat.chat') + ` ${index + 1}` }}</span>
<span class="chat-time">{{ formatTime(chat.timestamp) }}</span>
</div>
</div>
@ -51,14 +51,14 @@
<svg v-if="msg.isUser" viewBox="0 0 24 24" class="avatar-icon">
<path fill="currentColor" d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/>
</svg>
<img v-else :src="getAssistantAvatar()" alt="{{ $t('vabI18n.chat.assistant') }}" class="avatar-icon" />
<img v-else :src="getAssistantAvatar()" :alt="$t('vabI18n.chat.assistant')" class="avatar-icon" />
</div>
<!-- 消息内容 -->
<div class="message-content-wrapper" :class="{ 'has-charts': !msg.isUser && msg.echarts && msg.echarts.length > 0 }">
<div class="message-content">
<div class="message-header">
<span class="message-sender">{{ msg.isUser ? $t('vabI18n.chat.you') : $t('vabI18n.chat.assistant') }}</span>
<span class="message-sender">{{ msg.isUser ? t('vabI18n.chat.you') : t('vabI18n.chat.assistant') }}</span>
</div>
<div class="message-body">
<!-- 编辑状态 -->
@ -70,7 +70,7 @@
type="textarea"
:autosize="{ minRows: 3, maxRows: 8 }"
class="edit-textarea"
placeholder="{{ $t('vabI18n.chat.placeholder') }}"
:placeholder="$t('vabI18n.chat.placeholder')"
@keydown.enter.ctrl="handleSaveEdit"
@keydown.escape="handleCancelEdit"
/>
@ -79,14 +79,14 @@
@click="handleCancelEdit"
class="edit-btn cancel-btn"
>
{{ $t('vabI18n.chat.cancel') }}
{{ t('vabI18n.chat.cancel') }}
</button>
<button
@click="handleSaveEdit"
:disabled="!editingMessageText.trim()"
class="edit-btn save-btn"
>
{{ $t('vabI18n.chat.send') }}
{{ t('vabI18n.chat.send') }}
</button>
</div>
</div>
@ -101,7 +101,7 @@
<svg viewBox="0 0 24 24" class="think-icon">
<path fill="currentColor" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/>
</svg>
{{ $t('vabI18n.chat.think') }}
{{ t('vabI18n.chat.think') }}
</div>
{{ parseThink(msg.text).think }}
</div>
@ -137,7 +137,7 @@
<svg viewBox="0 0 24 24" class="sources-icon">
<path fill="currentColor" d="M14,2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2M18,20H6V4H13V9H18V20Z"/>
</svg>
{{ $t('vabI18n.chat.sources') }}
{{ t('vabI18n.chat.sources') }}
</div>
<div class="sources-list">
<!-- PDF文件来源 -->
@ -324,7 +324,7 @@
</el-button>
</div>
<div class="input-footer">
<span class="footer-hint">{{ $t('vabI18n.chat.sendHint') }}</span>
<span class="footer-hint">{{ t('vabI18n.chat.sendHint') }}</span>
</div>
</div>
</div>
@ -332,7 +332,7 @@
<!-- 文件预览模态框 -->
<el-dialog
v-model="filePreviewVisible"
title="{{ $t('chat.previewTitle') }}"
:title="t('vabI18n.chat.previewTitle')"
width="80%"
class="file-preview-dialog"
:close-on-click-modal="false"
@ -343,7 +343,7 @@
v-if="previewFileUrl"
:src="previewFileUrl"
@rendered="() => { previewLoading = false }"
@error="() => { previewLoading = false; ElMessage.error($t('vabI18n.chat.previewTitleFail')) }"
@error="() => { previewLoading = false; ElMessage.error(t('vabI18n.chat.previewTitleFail')) }"
class="pdf-preview"
/>
</div>
@ -352,7 +352,7 @@
<!-- Excel表格预览模态框 -->
<el-dialog
v-model="excelPreviewVisible"
title="{$t('vabI18n.chat.excelPreviewTitle')}"
:title="$t('vabI18n.chat.excelPreviewTitle')"
width="90%"
class="excel-preview-dialog"
:close-on-click-modal="false"
@ -905,9 +905,18 @@ const parseThink = (text: string) => {
// - 使
const getFileTypeIcon = (fileType: string) => {
// 使
const icons = require.context('@/assets/img/filetype-icon', false, /\.png$/)
// console.log(icons)
// console.log(' keys:', icons.keys())
const getIconUrl = (iconName: string) => {
return new URL(require(`@/assets/img/filetype-icon/${iconName}.png`), import.meta.url).href
const fullName = `./${iconName}.png`
if (icons.keys().includes(fullName)) {
return icons(fullName)
} else {
// console.warn(':', fullName)
return ''
}
}
const iconMap: Record<string, string> = {

View File

@ -339,14 +339,19 @@ const renameForm = reactive({
})
const getFileTypeIcon = (fileType: string) => {
// 使
// const getIconUrl = (iconName: string) => {
// return new URL(require(`@/assets/img/filetype-icon/${iconName}.png`), import.meta.url).href
// }
const getIconUrl = (iconName: string) => {
return new URL(`/src/assets/img/filetype-icon/${iconName}.png`, import.meta.url).href
}
const icons = require.context('@/assets/img/filetype-icon', false, /\.png$/)
// console.log(icons)
// console.log(' keys:', icons.keys())
const getIconUrl = (iconName: string) => {
const fullName = `./${iconName}.png`
if (icons.keys().includes(fullName)) {
return icons(fullName)
} else {
// console.warn(':', fullName)
return ''
}
}
const iconMap: Record<string, string> = {
'pdf': getIconUrl('pdf'),