|
|
@@ -500,7 +500,7 @@
|
|
|
placeholder="给AI智能助手发消息(按Enter进行发送)"
|
|
|
class="message-input"
|
|
|
v-model="messageText"
|
|
|
- @keyup.enter.exact="handleSendMessage"
|
|
|
+ @keydown.enter="handleMessageInputEnterKey"
|
|
|
@input="handleInput"
|
|
|
:disabled="isSending || hasTypingMessage"
|
|
|
maxlength="2000"
|
|
|
@@ -673,7 +673,7 @@
|
|
|
<input
|
|
|
ref="fileInput"
|
|
|
type="file"
|
|
|
- accept=".doc,.docx,.xls,.xlsx,.ppt,.pptx,.pdf,image/*,text/plain"
|
|
|
+ accept=".docx,.pptx,.pdf,text/plain"
|
|
|
style="display: none"
|
|
|
@change="handleFileSelect"
|
|
|
/>
|
|
|
@@ -722,7 +722,6 @@ import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
|
|
|
import '@wangeditor/editor/dist/css/style.css'
|
|
|
import Sidebar from '@/components/Sidebar.vue'
|
|
|
import ExamWorkshop from '@/views/ExamWorkshop.vue'
|
|
|
-import * as mammoth from 'mammoth'
|
|
|
|
|
|
// 导入Element Plus组件
|
|
|
import { ElMessage } from 'element-plus'
|
|
|
@@ -745,12 +744,15 @@ import {
|
|
|
buildDocumentGenerationUserMessage,
|
|
|
shouldAttachDocumentToRequest
|
|
|
} from '@/utils/aiWritingRequest.js'
|
|
|
+import { buildUploadedDocumentPayload } from '@/utils/attachmentContext.js'
|
|
|
+import { getAttachmentCardIcon } from '@/utils/attachmentFile.js'
|
|
|
import { prepareAIWritingEditorHtml } from '@/utils/aiWritingContent.js'
|
|
|
import { getGeneratedDocumentCardTime } from '@/utils/generatedDocumentCard.js'
|
|
|
import {
|
|
|
AI_WRITING_SIDEBAR_SIZE,
|
|
|
calculateResizableSidebarWidth
|
|
|
} from '@/utils/resizableSidebar.js'
|
|
|
+import { handleChatInputEnterKey } from '@/utils/chatInputKeydown.js'
|
|
|
import { getToken } from '@/utils/auth.js'
|
|
|
import { renderMarkdown } from '@/utils/markdown'
|
|
|
import 'katex/dist/katex.min.css'
|
|
|
@@ -1380,7 +1382,7 @@ const currentWebSearchData = ref({
|
|
|
// 文件处理配置
|
|
|
const fileConfig = reactive({
|
|
|
maxSize: 20 * 1024 * 1024, // 20MB
|
|
|
- allowedTypes: ['.docx'] // 只允许.docx格式的Word文档
|
|
|
+ allowedTypes: ['.docx', '.pdf', '.pptx', '.txt']
|
|
|
})
|
|
|
|
|
|
// 文件预览相关
|
|
|
@@ -2272,6 +2274,15 @@ const handleSendMessage = async () => {
|
|
|
scrollToBottom()
|
|
|
}
|
|
|
|
|
|
+const handleMessageInputEnterKey = (event) => {
|
|
|
+ handleChatInputEnterKey(event, {
|
|
|
+ submit: handleSendMessage,
|
|
|
+ updateValue: (value) => {
|
|
|
+ messageText.value = value
|
|
|
+ }
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
// 处理非流式请求 (AI写作 和 安全培训)
|
|
|
|
|
|
const handleAIWritingStream = async (data) => {
|
|
|
@@ -3654,12 +3665,15 @@ const handleStopGeneration = async () => {
|
|
|
const handleReportGeneratorSubmit = async (data) => {
|
|
|
isSending.value = true
|
|
|
currentQuestion.value = data.question
|
|
|
+ const attachedFile = selectedFile.value
|
|
|
+ const uploadedDocument = buildUploadedDocumentPayload(attachedFile)
|
|
|
|
|
|
// 添加用户消息
|
|
|
chatMessages.value.push({
|
|
|
id: Date.now(),
|
|
|
type: 'user',
|
|
|
content: data.question,
|
|
|
+ file: attachedFile || null,
|
|
|
timestamp: new Date().toISOString()
|
|
|
})
|
|
|
|
|
|
@@ -3710,7 +3724,8 @@ const handleReportGeneratorSubmit = async (data) => {
|
|
|
n_results: 10,
|
|
|
ai_conversation_id: ai_conversation_id.value,
|
|
|
is_network_search_enabled: isNetworkSearchEnabled.value,
|
|
|
- enable_online_model: isOnlineModel.value
|
|
|
+ enable_online_model: isOnlineModel.value,
|
|
|
+ uploaded_documents: uploadedDocument ? [uploadedDocument] : []
|
|
|
}
|
|
|
|
|
|
console.log('📤 发起 SSE POST 请求:', {
|
|
|
@@ -4336,38 +4351,19 @@ const triggerFileUpload = () => {
|
|
|
|
|
|
|
|
|
|
|
|
-// 读取Word文件内容
|
|
|
-const readWordFile = async (file) => {
|
|
|
- try {
|
|
|
- if (file.size === 0) throw new Error('Word文件为空')
|
|
|
- if (!mammoth) throw new Error('Word文档解析库未正确加载')
|
|
|
-
|
|
|
- const fileExtension = file.name.toLowerCase().split('.').pop()
|
|
|
- const arrayBuffer = await file.arrayBuffer()
|
|
|
-
|
|
|
- // 检查文件格式
|
|
|
- if (fileExtension === 'docx') {
|
|
|
- const uint8Array = new Uint8Array(arrayBuffer.slice(0, 2))
|
|
|
- if (uint8Array[0] !== 0x50 || uint8Array[1] !== 0x4B) {
|
|
|
- throw new Error('文件不是有效的.docx格式,可能已损坏')
|
|
|
- }
|
|
|
- } else if (fileExtension === 'doc') {
|
|
|
- throw new Error('检测到.doc格式文件。请将文件另存为.docx格式后重新上传。')
|
|
|
- }
|
|
|
-
|
|
|
- const result = await mammoth.extractRawText({ arrayBuffer })
|
|
|
- return result.value
|
|
|
- } catch (error) {
|
|
|
- console.error('Word文件读取失败:', error)
|
|
|
-
|
|
|
- if (error.message.includes('Can\'t find end of central directory')) {
|
|
|
- throw new Error('文件格式错误:这不是一个有效的Word文档,或者文件已损坏。')
|
|
|
- } else if (error.message.includes('Invalid file format')) {
|
|
|
- throw new Error('Word文件格式无效或已损坏')
|
|
|
- } else {
|
|
|
- throw error
|
|
|
- }
|
|
|
+const parseAttachmentFile = async (file) => {
|
|
|
+ const formData = new FormData()
|
|
|
+ formData.append('file', file)
|
|
|
+ const response = await apis.parseAttachment(formData)
|
|
|
+ const parsed = response?.data || {}
|
|
|
+ const text = parsed.text || ''
|
|
|
+ if (!text.trim()) {
|
|
|
+ const warning = Array.isArray(parsed.warnings) && parsed.warnings.length > 0
|
|
|
+ ? parsed.warnings[0]
|
|
|
+ : '未提取到可用文本'
|
|
|
+ throw new Error(warning)
|
|
|
}
|
|
|
+ return parsed
|
|
|
}
|
|
|
|
|
|
// 处理文件选择
|
|
|
@@ -4377,25 +4373,26 @@ const handleFileSelect = async (event) => {
|
|
|
|
|
|
try {
|
|
|
const fileExtension = validateFile(file)
|
|
|
- if (!mammoth) {
|
|
|
- throw new Error('Word文档解析库未正确加载,请刷新页面重试')
|
|
|
- }
|
|
|
|
|
|
isUploadingFile.value = true
|
|
|
- const extractedContent = await readWordFile(file)
|
|
|
+ const parsed = await parseAttachmentFile(file)
|
|
|
+ const extractedContent = parsed.text || ''
|
|
|
|
|
|
selectedFile.value = {
|
|
|
file,
|
|
|
- name: file.name,
|
|
|
+ name: parsed.file_name || file.name,
|
|
|
size: file.size,
|
|
|
- type: fileExtension,
|
|
|
+ type: parsed.file_type ? `.${parsed.file_type}` : fileExtension,
|
|
|
icon: getFileIcon(fileExtension),
|
|
|
- content: extractedContent
|
|
|
+ content: extractedContent,
|
|
|
+ attachmentId: parsed.attachment_id || '',
|
|
|
+ charCount: parsed.char_count || extractedContent.length,
|
|
|
+ warnings: parsed.warnings || []
|
|
|
}
|
|
|
ElMessage.success(`文件读取成功,共提取 ${extractedContent.length} 个字符`)
|
|
|
} catch (error) {
|
|
|
console.error('文件读取失败:', error)
|
|
|
- ElMessage.error(error.message || '文件读取失败,请重试')
|
|
|
+ ElMessage.error(error.message || error.msg || '文件读取失败,请重试')
|
|
|
} finally {
|
|
|
isUploadingFile.value = false
|
|
|
event.target.value = ''
|
|
|
@@ -4761,10 +4758,10 @@ const validateFile = (file) => {
|
|
|
const fileExtension = '.' + file.name.split('.').pop().toLowerCase()
|
|
|
|
|
|
// 支持的扩展名列表(对应于 input 的 accept 属性)
|
|
|
- const supportedExtensions = ['.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx', '.pdf', '.png', '.jpg', '.jpeg', '.gif', '.txt']
|
|
|
+ const supportedExtensions = fileConfig.allowedTypes
|
|
|
|
|
|
if (!supportedExtensions.includes(fileExtension)) {
|
|
|
- throw new Error(`不支持的文件格式: ${fileExtension}。支持的格式: 图片、文档、表格、PPT、PDF等。`)
|
|
|
+ throw new Error(`不支持的文件格式: ${fileExtension}。支持的格式: Word(.docx)、PPT(.pptx)、PDF、TXT。`)
|
|
|
}
|
|
|
|
|
|
return fileExtension
|
|
|
@@ -4772,21 +4769,7 @@ const validateFile = (file) => {
|
|
|
|
|
|
// 获取文件图标
|
|
|
const getFileIcon = (fileType) => {
|
|
|
- const type = fileType.toLowerCase()
|
|
|
- if (['.doc', '.docx'].includes(type)) {
|
|
|
- return wordDocIcon
|
|
|
- } else if (['.xls', '.xlsx'].includes(type)) {
|
|
|
- return new URL('../assets/Chat/excel.png', import.meta.url).href
|
|
|
- } else if (['.ppt', '.pptx'].includes(type)) {
|
|
|
- return new URL('../assets/Chat/ppt.png', import.meta.url).href
|
|
|
- } else if (type === '.pdf') {
|
|
|
- return new URL('../assets/Chat/pdf.png', import.meta.url).href
|
|
|
- } else if (['.png', '.jpg', '.jpeg', '.gif'].includes(type)) {
|
|
|
- return new URL('../assets/Chat/image.png', import.meta.url).href
|
|
|
- } else if (type === '.txt') {
|
|
|
- return new URL('../assets/Chat/txt.png', import.meta.url).href
|
|
|
- }
|
|
|
- return '📎'
|
|
|
+ return getAttachmentCardIcon(fileType, wordDocIcon)
|
|
|
}
|
|
|
|
|
|
// 滚动到底部
|
|
|
@@ -6139,6 +6122,8 @@ onActivated(async () => {
|
|
|
|
|
|
.message-text {
|
|
|
margin-top: 0;
|
|
|
+ white-space: pre-wrap;
|
|
|
+ overflow-wrap: anywhere;
|
|
|
}
|
|
|
}
|
|
|
|