From 070aaf7714a3c71e95d442d2370f1d790b78fefa Mon Sep 17 00:00:00 2001 From: xuhuajiao <13476289682@163.com> Date: Wed, 12 Mar 2025 17:33:21 +0800 Subject: [PATCH] =?UTF-8?q?AI=E6=99=BA=E8=83=BD=E7=BC=96=E7=A0=94/AI?= =?UTF-8?q?=E7=9F=A5=E8=AF=86=E5=9B=BE=E8=B0=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/ai/ai.js | 10 +- .../AIIntelligentCoding/aiForm copy.vue | 622 ++++++++---------- .../AIIntelligentCoding/aiForm.vue | 349 +++++++--- .../AIKeywords/module/keywordMark.vue | 5 +- .../AIAssistant/AIknowledgeGraph/index.vue | 136 ++-- .../customDefinedStatistics/index.vue | 4 +- src/views/components/echarts/graph.vue | 124 +++- 7 files changed, 731 insertions(+), 519 deletions(-) diff --git a/src/api/ai/ai.js b/src/api/ai/ai.js index 03f8910..5385366 100644 --- a/src/api/ai/ai.js +++ b/src/api/ai/ai.js @@ -36,4 +36,12 @@ export function FetchHandleEnterAnalysis(data) { }) } -export default { FetchDoHandleEnterAnalysis, FetchInitAssistEnterTemp, FetchDelAssistEnter, FetchHandleEnterAnalysis } +// 根据门类初始化知识图谱 +export function FetchInitShowByCategory(params) { + return request({ + url: 'api/aineo/initShowByCategory' + '?' + qs.stringify(params, { indices: false }), + method: 'get' + }) +} + +export default { FetchDoHandleEnterAnalysis, FetchInitAssistEnterTemp, FetchDelAssistEnter, FetchHandleEnterAnalysis, FetchInitShowByCategory } diff --git a/src/views/AIAssistant/AIIntelligentCoding/aiForm copy.vue b/src/views/AIAssistant/AIIntelligentCoding/aiForm copy.vue index aab016e..e90c928 100644 --- a/src/views/AIAssistant/AIIntelligentCoding/aiForm copy.vue +++ b/src/views/AIAssistant/AIIntelligentCoding/aiForm copy.vue @@ -1,370 +1,294 @@ - + - - + diff --git a/src/views/AIAssistant/AIIntelligentCoding/aiForm.vue b/src/views/AIAssistant/AIIntelligentCoding/aiForm.vue index a81c1f1..680d063 100644 --- a/src/views/AIAssistant/AIIntelligentCoding/aiForm.vue +++ b/src/views/AIAssistant/AIIntelligentCoding/aiForm.vue @@ -14,23 +14,43 @@ - 保存 + 保存
-
对话框
- +
+
+
+

{{ item.content }}

+ +
+
+
+ AI +

{{ item.content }}

+
+
+ + +
+
+
+
-
- Selected Image -

{{ fileName }}

- -
-
+
@@ -61,6 +81,7 @@ type="textarea" :autosize="{ minRows:1 , maxRows: 4 }" placeholder="请输入内容" + @keydown.enter.native.prevent="handleMessage($event)" />
@@ -68,7 +89,7 @@
- +
@@ -77,18 +98,15 @@
- -
-
@@ -98,13 +116,11 @@ import { form } from '@crud/crud' import { mapGetters } from 'vuex' import { FetchDictionaryTree } from '@/api/system/dict' -import editor from '@/components/quillEditor/index' -// import strJson from './str.json' const defaultForm = { id: null, title: null, researchType: null } export default { name: 'Form', - components: { editor }, + components: { }, mixins: [ form(function() { return Object.assign({}, defaultForm) @@ -112,16 +128,10 @@ export default { ], data() { return { - formType: 1, formTitle: '', formVisible: false, - selectedFiles: [], - textarea: '帮我写一篇AI赋能档案管理的报告', editorRef: 'test', editorContent: '', - mockResponse: '', - loading: false, - intervalId: null, options: [], rules: { @@ -133,7 +143,13 @@ export default { imageUrl: '', fileName: '', isImage: false, - fileType: '' + fileType: '', + message: [], + controller: null, + arequestData: { + model: 'deepseek-r1:14b', + messages: [] + } } }, computed: { @@ -154,87 +170,180 @@ export default { console.log(err) }) }, - handleFileChange(event) { + async handleFileChange(event) { const file = event.target.files[0] + const allowedExtensions = ['.xlsx', '.xls', '.docx', '.doc', '.pdf', '.ofd', '.pptx', '.txt'] + const maxSize = 10 * 1024 * 1024 + if (file) { - this.fileType = file.type - console.log('this.fileType ', this.fileType) + const fileExtension = '.' + file.name.split('.').pop().toLowerCase() if (file.type.indexOf('image') !== -1) { - this.isImage = true - const reader = new FileReader() - reader.onload = (e) => { - this.imageUrl = e.target.result - this.fileName = file.name - } - reader.readAsDataURL(file) - } else { - this.isImage = false - this.fileName = file.name - this.imageUrl = '' + this.$message.error('不允许上传图片文件!') + return } + if (!allowedExtensions.includes(fileExtension)) { + this.$message.error('仅允许上传以下格式的文件:.xlsx, .xls, .docx, .doc, .pdf, .ofd, .pptx, .txt') + return + } + if (file.size > maxSize) { + this.$message.error('文件大小不能超过 10MB!') + return + } + + this.fileType = file.type + this.isImage = false + this.fileName = file.name + this.imageUrl = '' + + // 这种情况前端就给个提示:您上传的文件不是纯文本格式,暂不支持解析 } }, - deleteFile() { - this.imageUrl = '' - this.fileName = '' - this.fileType = '' - this.isImage = false - }, - previewFiles() { - const files = this.$refs.file.files - this.selectedFiles = [] - for (let i = 0; i < files.length; i++) { - this.selectedFiles.push(files[i]) + async handleMessage(e) { + if (e) { // 处理键盘事件 + if (e.keyCode === 13) { + if (e.ctrlKey) { // Ctrl+Enter 发送消息 + this.sendMessage() + } else { // Enter 键换行 + this.inputValue += '\n' + this.scrollToBottom() + return + } + } + } else { + this.sendMessage() } }, - handleAIEditing() { - if (this.selectedFiles.length === 0) { - this.$message({ message: '请上传要编研的文件', type: 'error', offset: 8 }) + async sendMessage(e) { + if (!this.inputValue) { + this.$message.error('请输入问题') return } - if (this.textarea === '') { - this.$message({ message: '请输入提问要求', type: 'error', offset: 8 }) - return + // 如果已经有一个正在进行的流式请求,则中止它 + if (this.controller) { + this.controller.abort() } - this.loading = true - this.editorContent = '' - const lines = this.mockResponse.split('\n') - let index = 0 + setTimeout(() => { + this.scrollToBottom() + }, 50) - this.intervalId = setInterval(() => { - if (index < lines.length) { - this.editorContent += (index > 0 ? '\n' : '') + lines[index] - index++ - } else { - clearInterval(this.intervalId) - this.loading = false + this.message.push({ + role: 'user', + content: this.inputValue + }) + this.message.push({ + role: 'ai', + content: '', + markDownText: '', + showMarkdown: false + }) + try { + this.controller = new AbortController() + const signal = this.controller.signal + this.arequestData.messages.push({ role: 'user', content: this.inputValue }) + const linkSrc = process.env.NODE_ENV === 'production' ? window.g.AIDeepSeekUrl : process.env.VUE_APP_AIDEEPSEEK_API + const response = await fetch(linkSrc + '/api/chat', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(this.arequestData), + signal + }) + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`) } - // this.$nextTick(() => { - // const container = this.$refs.typingContainer.$el.querySelector('.markdown-body') - // if (container) { - // container.scrollTop = container.scrollHeight - // } - // }) - }, 200) // 每500毫秒添加一行 + const reader = response.body.getReader() + const decoder = new TextDecoder() + let result = '' + + let done = false + while (!done) { + const { done: isDone, value } = await reader.read() + done = isDone + if (done) break + result += decoder.decode(value, { stream: true }) + const jsonChunks = result.split('\n').filter(line => line.trim()) + for (const chunk of jsonChunks) { + try { + const data = JSON.parse(chunk) + this.message[this.message.length - 1].content += data.message.content + + setTimeout(() => { + this.scrollToBottom() + }, 50) + } catch (e) { + // 处理 JSON 解析错误 + } + } + + // 去除 之间的内容 + const cleanText = this.message[this.message.length - 1].content.replace(/[^]*?<\/think>/g, '') + console.log('cleanText', cleanText) + this.message[this.message.length - 1].markDownText = cleanText + + result = '' + } + } catch (error) { + if (error.name === 'AbortError') { + console.log('Stream aborted') + this.message[this.message.length - 1].content = 'Stream aborted' + this.message[this.message.length - 1].markDownText = 'Stream aborted' + } else { + console.error('Streaming error:', error) + this.message[this.message.length - 1].content = 'Stream error' + error + this.message[this.message.length - 1].markDownText = 'Stream error' + error + } + } finally { + this.arequestData.messages.push({ + role: 'assistant', + content: this.message[this.message.length - 1].content + }) + this.message[this.message.length - 1].showMarkdown = true + setTimeout(() => { + this.scrollToBottom() + }, 50) + this.inputValue = '' + } + }, + scrollToBottom() { + const talkContent = this.$refs.talkContent + if (talkContent) { + talkContent.scrollTop = talkContent.scrollHeight + } + }, + beforeDestroy() { + // 组件销毁时中止流式请求 + if (this.controller) { + this.controller.abort() + } + }, + onClickContent(talkContent) { + this.editorContent = '' + this.editorContent = talkContent + }, + deleteFile() { + this.imageUrl = '' + this.fileName = '' + this.fileType = '' + this.isImage = false }, handleCloseForm() { - this.selectedFiles = [] - this.textarea = '' + this.inputValue = '' this.editorContent = '' + this.message = [] this.formVisible = false - clearInterval(this.intervalId) - this.loading = false }, handleComfiredEditing() { - // this.$refs.form.validate(valid => { - // if (valid) { - // console.log('保存数据', this.form); - // } - // }); - this.handleCloseForm() + this.$refs.form.validate(valid => { + if (valid) { + console.log('保存数据', this.form) + this.handleCloseForm() + } + }) }, toggleCollapse() { this.isCollapsed = !this.isCollapsed @@ -246,7 +355,7 @@ export default { diff --git a/src/views/AIAssistant/AIKeywords/module/keywordMark.vue b/src/views/AIAssistant/AIKeywords/module/keywordMark.vue index be88722..3a32716 100644 --- a/src/views/AIAssistant/AIKeywords/module/keywordMark.vue +++ b/src/views/AIAssistant/AIKeywords/module/keywordMark.vue @@ -138,7 +138,7 @@ export default { context = resultJson.data.map(item => item.content).join('\n\n') console.log('多张图片,用swiper') } - const prompt = '帮我提取5-10个关键词,关键词包含时间名称、地点名称、人名、部门机构名称以及档案学专用名称,按词频降序排列,用json格式带{}形式输出,其中只有一个关键字keywords数组字段,然后所有的关键字都放到这个keywords数组字段中' + const prompt = '帮我提取5-10个关键词,关键词包含地点名称、人名、部门机构名称以及档案学专用名称,按词频降序排列,用json格式带{}形式输出,其中只有一个关键字keywords数组字段,然后所有的关键字都放到这个keywords数组字段中' this.sendMessage(prompt, context) }).catch(err => { console.log(err) @@ -201,9 +201,6 @@ export default { } } }) - - // ... 原有的代码 ... - // 滚动条始终保持在底部 const container = this.$refs.typingContainer if (container) { diff --git a/src/views/AIAssistant/AIknowledgeGraph/index.vue b/src/views/AIAssistant/AIknowledgeGraph/index.vue index 08367bf..e40df67 100644 --- a/src/views/AIAssistant/AIknowledgeGraph/index.vue +++ b/src/views/AIAssistant/AIknowledgeGraph/index.vue @@ -36,7 +36,7 @@ - +
@@ -44,6 +44,7 @@