|
@@ -1,7 +1,6 @@
|
|
|
<script setup lang="ts">
|
|
|
import { computed, isReactive, nextTick, onMounted, onUnmounted, reactive, ref, watch } from 'vue'
|
|
|
import { Local } from '/@/utils/storage'
|
|
|
-import common from '/@/api/common/index'
|
|
|
import {
|
|
|
ArrowDown,
|
|
|
ChatDotRound,
|
|
@@ -9,10 +8,14 @@ import {
|
|
|
Close,
|
|
|
CopyDocument,
|
|
|
Delete,
|
|
|
+ Document,
|
|
|
Download,
|
|
|
Edit,
|
|
|
+ Files,
|
|
|
+ Headset,
|
|
|
Loading,
|
|
|
MoreFilled,
|
|
|
+ Picture,
|
|
|
Plus,
|
|
|
Promotion,
|
|
|
Search,
|
|
@@ -22,11 +25,7 @@ import {
|
|
|
StarFilled,
|
|
|
User,
|
|
|
VideoPause,
|
|
|
- Document,
|
|
|
- Picture,
|
|
|
VideoPlay,
|
|
|
- Headset,
|
|
|
- Files,
|
|
|
} from '@element-plus/icons-vue'
|
|
|
import { MarkdownPlugin } from '/@/components/markdown/type/markdown'
|
|
|
import EChartsPlugin from '/@/components/markdown/plugins/echarts'
|
|
@@ -41,6 +40,8 @@ import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
import StructDataPlugin from '/@/components/markdown/plugins/struct-data'
|
|
|
import { useFileDialog } from '@vueuse/core'
|
|
|
import download from 'downloadjs'
|
|
|
+import common from '/@/api/common'
|
|
|
+import { UploadFile } from '/@/api/common/type'
|
|
|
|
|
|
const plugins: Array<MarkdownPlugin<any>> = [EChartsPlugin(), ToolsLoadingPlugin(), TablePlugin(), StructDataPlugin()]
|
|
|
|
|
@@ -60,14 +61,29 @@ const {
|
|
|
})
|
|
|
|
|
|
// 附件管理:使用本地列表以支持单个移除
|
|
|
-const selectedFiles = ref<File[]>([])
|
|
|
+const selectedFiles = ref<UploadFile[]>([])
|
|
|
+
|
|
|
+const uploadProgress = ref<number>(-1)
|
|
|
+const { loading: loadingUpload, doLoading: doUpload } = useLoading(async (file: File[]) => {
|
|
|
+ uploadProgress.value = -1
|
|
|
+
|
|
|
+ return (await common.upload
|
|
|
+ .multi(file, (progress) => (uploadProgress.value = progress * 100))
|
|
|
+ .finally(() => (uploadProgress.value = -1))) as UploadFile[]
|
|
|
+})
|
|
|
|
|
|
watch(
|
|
|
attachments,
|
|
|
- (newFiles) => {
|
|
|
+ async (newFiles) => {
|
|
|
if (newFiles === null || newFiles.length === 0) return
|
|
|
|
|
|
- selectedFiles.value = [...selectedFiles.value, ...newFiles]
|
|
|
+ const data = await doUpload(Array.from(newFiles)).catch(() => undefined)
|
|
|
+ if (data === undefined) {
|
|
|
+ reset()
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ selectedFiles.value = [...selectedFiles.value, ...data]
|
|
|
reset()
|
|
|
},
|
|
|
{ immediate: true }
|
|
@@ -220,7 +236,7 @@ watch(selectPromptId, (newVal) => {
|
|
|
render_content: placeholder,
|
|
|
timestamp: Date.now(),
|
|
|
role: 'assistant',
|
|
|
- content: placeholder
|
|
|
+ content: placeholder,
|
|
|
})
|
|
|
// inputMessage.value = displayPromptList.value.find((i) => i.id === newVal)?.placeholder ?? ''
|
|
|
})
|
|
@@ -265,7 +281,7 @@ const promptDialogVisible = ref(false)
|
|
|
const chatInstance = ref<(() => void) | undefined>(undefined)
|
|
|
onUnmounted(() => chatInstance.value?.())
|
|
|
// 是否正在对话
|
|
|
-const isConversationActive = computed(() => chatInstance.value !== undefined || loadingUpload.value)
|
|
|
+const isConversationActive = computed(() => chatInstance.value !== undefined)
|
|
|
|
|
|
const { loading: loadingClearMessage, doLoading: clearMessage } = useLoading(async () => {
|
|
|
stopConversation()
|
|
@@ -283,10 +299,6 @@ const { loading: loadingClearMessage, doLoading: clearMessage } = useLoading(asy
|
|
|
}
|
|
|
messages.value = []
|
|
|
})
|
|
|
-
|
|
|
-const {loading:loadingUpload,doLoading: doUpload} = useLoading(async ()=> {
|
|
|
- return selectedFiles.value.length === 0 ? undefined : (await common.upload.multi(selectedFiles.value)) as Message['files']
|
|
|
-})
|
|
|
// 发送消息
|
|
|
const sendMessage = async () => {
|
|
|
if (!inputMessage.value.trim()) return
|
|
@@ -302,7 +314,7 @@ const sendMessage = async () => {
|
|
|
render_content: inputMessage.value,
|
|
|
content: inputMessage.value,
|
|
|
timestamp: Date.now(),
|
|
|
- files: await doUpload(),
|
|
|
+ files: selectedFiles.value,
|
|
|
})
|
|
|
selectedFiles.value = []
|
|
|
|
|
@@ -745,7 +757,15 @@ const getUserInfos = ref<{
|
|
|
}>(Local.get('userInfo') || {})
|
|
|
|
|
|
const canSendMessage = computed(() => {
|
|
|
- return !inputMessage.value.trim() || loadingModels.value || loadingEmbeddingModels.value || loadingPromptList.value || loadConversations.value || loadingMessage.value || loadingUpload.value
|
|
|
+ return (
|
|
|
+ !inputMessage.value.trim() ||
|
|
|
+ loadingModels.value ||
|
|
|
+ loadingEmbeddingModels.value ||
|
|
|
+ loadingPromptList.value ||
|
|
|
+ loadConversations.value ||
|
|
|
+ loadingMessage.value ||
|
|
|
+ loadingUpload.value
|
|
|
+ )
|
|
|
})
|
|
|
|
|
|
const router = useRouter()
|
|
@@ -930,7 +950,7 @@ const formatFileSize = (bytes: number): string => {
|
|
|
<div
|
|
|
v-for="conv in displayConversations"
|
|
|
:key="conv.session_id"
|
|
|
- @click="(editingConversationId !== conv.session_id && !multiDeleteConversationModel.visible) ? selectConversation(conv.session_id) : () => {}"
|
|
|
+ @click="editingConversationId !== conv.session_id && !multiDeleteConversationModel.visible ? selectConversation(conv.session_id) : () => {}"
|
|
|
:class="['conversation-item', { active: activeConversationId === conv.session_id, editing: editingConversationId === conv.session_id }]"
|
|
|
>
|
|
|
<!-- 非编辑状态 -->
|
|
@@ -1195,15 +1215,16 @@ const formatFileSize = (bytes: number): string => {
|
|
|
<div class="attachments-inline-scroll">
|
|
|
<div
|
|
|
v-for="(file, fIdx) in selectedFiles"
|
|
|
- :key="file.name + '_' + file.size + '_' + (file as any).lastModified"
|
|
|
+ :key="file.name + '_' + file.size"
|
|
|
class="attachment-card"
|
|
|
+ @click="openFile(file.full_path)"
|
|
|
title=""
|
|
|
>
|
|
|
<button class="control-btn attachment-item" :title="`${file.name}`">
|
|
|
<el-icon :size="10"><CopyDocument /></el-icon>
|
|
|
<span class="attachment-name">{{ file.name }}</span>
|
|
|
</button>
|
|
|
- <button class="remove-attachment-icon" @click="removeAttachment(fIdx)">
|
|
|
+ <button class="remove-attachment-icon" @click.stop="removeAttachment(fIdx)">
|
|
|
<el-icon><Close /></el-icon>
|
|
|
</button>
|
|
|
</div>
|
|
@@ -1213,8 +1234,11 @@ const formatFileSize = (bytes: number): string => {
|
|
|
</div>
|
|
|
</div>
|
|
|
</el-scrollbar>
|
|
|
- <button class="control-btn add-attachment-btn" @click="open">
|
|
|
- <el-icon :size="10"><Plus /></el-icon>
|
|
|
+ <button class="control-btn add-attachment-btn" @click="open" :disabled="loadingUpload">
|
|
|
+ <el-icon :size="10">
|
|
|
+ <Plus v-if="!loadingUpload" />
|
|
|
+ <div v-else>{{ uploadProgress.toFixed(2) }}</div>
|
|
|
+ </el-icon>
|
|
|
</button>
|
|
|
</div>
|
|
|
<!-- 输入框 -->
|
|
@@ -2044,7 +2068,6 @@ const formatFileSize = (bytes: number): string => {
|
|
|
}
|
|
|
|
|
|
.attachment-card {
|
|
|
-
|
|
|
position: relative;
|
|
|
display: inline-flex;
|
|
|
align-items: center;
|
|
@@ -2060,7 +2083,7 @@ const formatFileSize = (bytes: number): string => {
|
|
|
|
|
|
font-size: 10px !important;
|
|
|
|
|
|
- padding: 4px 8px!important;
|
|
|
+ padding: 4px 8px !important;
|
|
|
}
|
|
|
|
|
|
.attachment-name {
|