21 changed files with 778 additions and 2034 deletions
-
2.env.development
-
1.env.production
-
1public/static/config.js
-
30src/api/ai/ai.js
-
74src/assets/iconfonts/light/iconfont.css
-
2src/assets/iconfonts/light/iconfont.js
-
119src/assets/iconfonts/light/iconfont.json
-
BINsrc/assets/iconfonts/light/iconfont.ttf
-
BINsrc/assets/iconfonts/light/iconfont.woff
-
BINsrc/assets/iconfonts/light/iconfont.woff2
-
159src/views/AIAssistant/AICataloging/Chat.vue
-
176src/views/AIAssistant/AICataloging/Chat2.vue
-
74src/views/AIAssistant/AICataloging/deepSeekChat.vue
-
672src/views/AIAssistant/AICataloging/index2.vue
-
394src/views/AIAssistant/AICataloging/running/index.vue
-
52src/views/AIAssistant/AICataloging/running/module/detail.vue
-
462src/views/AIAssistant/AIDigitalHuman/index.vue
-
404src/views/AIAssistant/AIDigitalHuman/index2.vue
-
159src/views/AIAssistant/AIDigitalHuman/message.vue
-
26src/views/components/category/PreviewForm.vue
-
5src/views/system/archivesClassify/module/tableList.vue
2
src/assets/iconfonts/light/iconfont.js
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -1,159 +0,0 @@ |
|||||
<template> |
|
||||
<div class="chat-container"> |
|
||||
<div class="chat-messages"> |
|
||||
<div v-for="(message, index) in messages" :key="index" :class="{ 'user-message': message.sender === 'user', 'bot-message': message.sender === 'bot' }"> |
|
||||
{{ message.content }} |
|
||||
</div> |
|
||||
<mavon-editor |
|
||||
ref="typingContainer" |
|
||||
class="md" |
|
||||
:value="editorContent" |
|
||||
:subfield="false" |
|
||||
:default-open="'preview'" |
|
||||
:toolbars-flag="false" |
|
||||
:editable="false" |
|
||||
:scroll-style="true" |
|
||||
:ishljs="true" |
|
||||
/> |
|
||||
</div> |
|
||||
<div class="chat-input"> |
|
||||
<input v-model="inputMessage" placeholder="输入你的问题..." @keyup.enter="sendMessage"> |
|
||||
<button @click="sendMessage">发送</button> |
|
||||
</div> |
|
||||
</div> |
|
||||
</template> |
|
||||
|
|
||||
<script> |
|
||||
export default { |
|
||||
data() { |
|
||||
return { |
|
||||
messages: [], |
|
||||
inputMessage: '', |
|
||||
currentBotMessage: '', |
|
||||
editorContent: '' |
|
||||
} |
|
||||
}, |
|
||||
methods: { |
|
||||
async sendMessage() { |
|
||||
if (this.inputMessage.trim() === '') return |
|
||||
|
|
||||
this.messages.push({ sender: 'user', content: this.inputMessage }) |
|
||||
this.inputMessage = '' |
|
||||
this.currentBotMessage = '' |
|
||||
|
|
||||
try { |
|
||||
const response = await fetch('http://192.168.99.86:11434/api/generate', { |
|
||||
method: 'POST', |
|
||||
headers: { |
|
||||
'Content-Type': 'application/json' |
|
||||
}, |
|
||||
body: JSON.stringify({ |
|
||||
model: 'deepseek-r1:14b', |
|
||||
// model: 'qwen:7b', |
|
||||
prompt: this.messages[this.messages.length - 1].content, |
|
||||
stream: true |
|
||||
}) |
|
||||
}) |
|
||||
|
|
||||
if (!response.ok) { |
|
||||
throw new Error(`HTTP error! status: ${response.status}`) |
|
||||
} |
|
||||
|
|
||||
const reader = response.body.getReader() |
|
||||
const decoder = new TextDecoder('utf-8') |
|
||||
let done = false |
|
||||
|
|
||||
while (!done) { |
|
||||
const { done: isDone, value } = await reader.read() |
|
||||
done = isDone |
|
||||
if (done) break |
|
||||
|
|
||||
const chunk = decoder.decode(value) |
|
||||
const lines = chunk.split('\n') |
|
||||
|
|
||||
lines.forEach(line => { |
|
||||
if (line.trim() !== '') { |
|
||||
try { |
|
||||
const data = JSON.parse(line) |
|
||||
if (data.response) { |
|
||||
this.currentBotMessage += data.response |
|
||||
if (this.messages.length === 0 || this.messages[this.messages.length - 1].sender === 'user') { |
|
||||
this.messages.push({ sender: 'bot', content: '' }) |
|
||||
} |
|
||||
this.messages[this.messages.length - 1].content = this.currentBotMessage |
|
||||
} |
|
||||
} catch (error) { |
|
||||
console.error('解析JSON数据出错:', error) |
|
||||
} |
|
||||
} |
|
||||
}) |
|
||||
} |
|
||||
|
|
||||
// 更新 mavon-editor 的内容 |
|
||||
const lastBotMessage = this.messages.find(msg => msg.sender === 'bot') |
|
||||
if (lastBotMessage) { |
|
||||
this.editorContent = lastBotMessage.content |
|
||||
} |
|
||||
} catch (error) { |
|
||||
console.error('请求出错:', error) |
|
||||
this.messages.push({ sender: 'bot', content: '请求出错,请稍后再试。' }) |
|
||||
this.editorContent = '请求出错,请稍后再试。' |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
</script> |
|
||||
|
|
||||
<style scoped> |
|
||||
.chat-container { |
|
||||
display: flex; |
|
||||
flex-direction: column; |
|
||||
height: 500px; |
|
||||
border: 1px solid #ccc; |
|
||||
border-radius: 5px; |
|
||||
padding: 10px; |
|
||||
} |
|
||||
|
|
||||
.chat-messages { |
|
||||
flex: 1; |
|
||||
overflow-y: auto; |
|
||||
margin-bottom: 10px; |
|
||||
} |
|
||||
|
|
||||
.user-message { |
|
||||
text-align: right; |
|
||||
background-color: #e0f7fa; |
|
||||
padding: 5px; |
|
||||
margin: 5px; |
|
||||
border-radius: 5px; |
|
||||
} |
|
||||
|
|
||||
.bot-message { |
|
||||
text-align: left; |
|
||||
background-color: #f1f8e9; |
|
||||
padding: 5px; |
|
||||
margin: 5px; |
|
||||
border-radius: 5px; |
|
||||
} |
|
||||
|
|
||||
.chat-input { |
|
||||
display: flex; |
|
||||
} |
|
||||
|
|
||||
.chat-input input { |
|
||||
flex: 1; |
|
||||
padding: 5px; |
|
||||
border: 1px solid #ccc; |
|
||||
border-radius: 5px; |
|
||||
margin-right: 5px; |
|
||||
} |
|
||||
|
|
||||
.chat-input button { |
|
||||
padding: 5px 10px; |
|
||||
border: none; |
|
||||
background-color: #2196f3; |
|
||||
color: white; |
|
||||
border-radius: 5px; |
|
||||
cursor: pointer; |
|
||||
} |
|
||||
</style> |
|
||||
@ -1,176 +0,0 @@ |
|||||
<template> |
|
||||
<div class="chat-container"> |
|
||||
<div class="chat-messages"> |
|
||||
<!-- 循环渲染消息 --> |
|
||||
<div v-for="(message, index) in messages" :key="index"> |
|
||||
<!-- 用户消息 --> |
|
||||
<div v-if="message.sender === 'user'" class="user-message"> |
|
||||
{{ message.content }} |
|
||||
</div> |
|
||||
<!-- 机器人消息 --> |
|
||||
<mavon-editor |
|
||||
v-else |
|
||||
ref="mavonEditorRef" |
|
||||
class="bot-message" |
|
||||
:value="message.content" |
|
||||
:subfield="false" |
|
||||
:default-open="'preview'" |
|
||||
:toolbars-flag="false" |
|
||||
:editable="false" |
|
||||
:scroll-style="true" |
|
||||
:ishljs="true" |
|
||||
/> |
|
||||
</div> |
|
||||
</div> |
|
||||
<div class="chat-input"> |
|
||||
<input v-model="inputMessage" placeholder="输入你的问题..." @keyup.enter="sendMessage"> |
|
||||
<button @click="sendMessage">发送</button> |
|
||||
</div> |
|
||||
</div> |
|
||||
</template> |
|
||||
|
|
||||
<script> |
|
||||
export default { |
|
||||
data() { |
|
||||
return { |
|
||||
messages: [], |
|
||||
inputMessage: '', |
|
||||
currentBotMessage: '' |
|
||||
} |
|
||||
}, |
|
||||
watch: { |
|
||||
messages: { |
|
||||
deep: true, |
|
||||
handler() { |
|
||||
this.$nextTick(() => { |
|
||||
const lastBotEditor = this.$refs.mavonEditorRef[this.$refs.mavonEditorRef.length - 1] |
|
||||
if (lastBotEditor) { |
|
||||
const editorContent = lastBotEditor.$el.querySelector('.editor-preview') |
|
||||
if (editorContent) { |
|
||||
editorContent.scrollTop = editorContent.scrollHeight |
|
||||
} |
|
||||
} |
|
||||
}) |
|
||||
} |
|
||||
} |
|
||||
}, |
|
||||
methods: { |
|
||||
async sendMessage() { |
|
||||
if (this.inputMessage.trim() === '') return |
|
||||
|
|
||||
this.messages.push({ sender: 'user', content: this.inputMessage }) |
|
||||
this.inputMessage = '' |
|
||||
this.currentBotMessage = '' |
|
||||
|
|
||||
try { |
|
||||
const response = await fetch('http://192.168.99.86:11434/api/generate', { |
|
||||
method: 'POST', |
|
||||
headers: { |
|
||||
'Content-Type': 'application/json' |
|
||||
}, |
|
||||
body: JSON.stringify({ |
|
||||
model: 'deepseek-r1:14b', |
|
||||
// model: 'qwen:7b', |
|
||||
prompt: this.messages[this.messages.length - 1].content, |
|
||||
stream: true |
|
||||
}) |
|
||||
}) |
|
||||
|
|
||||
if (!response.ok) { |
|
||||
throw new Error(`HTTP error! status: ${response.status}`) |
|
||||
} |
|
||||
|
|
||||
const reader = response.body.getReader() |
|
||||
const decoder = new TextDecoder('utf-8') |
|
||||
let done = false |
|
||||
|
|
||||
while (!done) { |
|
||||
const { done: isDone, value } = await reader.read() |
|
||||
done = isDone |
|
||||
if (done) break |
|
||||
|
|
||||
const chunk = decoder.decode(value) |
|
||||
const lines = chunk.split('\n') |
|
||||
|
|
||||
lines.forEach((line) => { |
|
||||
if (line.trim() !== '') { |
|
||||
try { |
|
||||
const data = JSON.parse(line) |
|
||||
if (data.response) { |
|
||||
this.currentBotMessage += data.response |
|
||||
if ( |
|
||||
this.messages.length === 0 || |
|
||||
this.messages[this.messages.length - 1].sender === 'user' |
|
||||
) { |
|
||||
this.messages.push({ sender: 'bot', content: '' }) |
|
||||
} |
|
||||
this.messages[this.messages.length - 1].content = this.currentBotMessage |
|
||||
} |
|
||||
} catch (error) { |
|
||||
console.error('解析JSON数据出错:', error) |
|
||||
} |
|
||||
} |
|
||||
}) |
|
||||
} |
|
||||
} catch (error) { |
|
||||
console.error('请求出错:', error) |
|
||||
this.messages.push({ sender: 'bot', content: '请求出错,请稍后再试。' }) |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
</script> |
|
||||
|
|
||||
<style scoped> |
|
||||
.chat-container { |
|
||||
display: flex; |
|
||||
flex-direction: column; |
|
||||
height: 500px; |
|
||||
border: 1px solid #ccc; |
|
||||
border-radius: 5px; |
|
||||
padding: 10px; |
|
||||
} |
|
||||
|
|
||||
.chat-messages { |
|
||||
flex: 1; |
|
||||
overflow-y: auto; |
|
||||
margin-bottom: 10px; |
|
||||
} |
|
||||
|
|
||||
.user-message { |
|
||||
text-align: right; |
|
||||
background-color: #e0f7fa; |
|
||||
padding: 5px; |
|
||||
margin: 5px; |
|
||||
border-radius: 5px; |
|
||||
} |
|
||||
|
|
||||
.bot-message { |
|
||||
text-align: left; |
|
||||
background-color: #f1f8e9; |
|
||||
padding: 5px; |
|
||||
margin: 5px; |
|
||||
border-radius: 5px; |
|
||||
} |
|
||||
|
|
||||
.chat-input { |
|
||||
display: flex; |
|
||||
} |
|
||||
|
|
||||
.chat-input input { |
|
||||
flex: 1; |
|
||||
padding: 5px; |
|
||||
border: 1px solid #ccc; |
|
||||
border-radius: 5px; |
|
||||
margin-right: 5px; |
|
||||
} |
|
||||
|
|
||||
.chat-input button { |
|
||||
padding: 5px 10px; |
|
||||
border: none; |
|
||||
background-color: #2196f3; |
|
||||
color: white; |
|
||||
border-radius: 5px; |
|
||||
cursor: pointer; |
|
||||
} |
|
||||
</style> |
|
||||
@ -1,74 +0,0 @@ |
|||||
<template> |
|
||||
<div> |
|
||||
<h1>DeepSeek问答</h1> |
|
||||
<input v-model="question" placeholder="请输入你的问题" @keyup.enter="sendQuestion"> |
|
||||
<button @click="sendQuestion">发送</button> |
|
||||
<div v-if="answer">{{ answer }}</div> |
|
||||
</div> |
|
||||
</template> |
|
||||
|
|
||||
<script> |
|
||||
import axios from 'axios' |
|
||||
|
|
||||
export default { |
|
||||
data() { |
|
||||
return { |
|
||||
question: '', |
|
||||
answer: '' |
|
||||
} |
|
||||
}, |
|
||||
methods: { |
|
||||
async sendQuestion() { |
|
||||
if (!this.question) return |
|
||||
|
|
||||
try { |
|
||||
// Ollama API端点,根据实际情况修改 |
|
||||
const apiUrl = 'http://192.168.99.86:11434/api/generate' |
|
||||
const response = await axios.post(apiUrl, { |
|
||||
model: 'deepseek-r1:14b', // 使用DeepSeek模型 |
|
||||
prompt: this.question |
|
||||
}) |
|
||||
console.log('response', response) |
|
||||
let fullAnswer = '' |
|
||||
const resultStr = response.data |
|
||||
// 假设对象之间用换行符分隔,根据实际情况调整 |
|
||||
const lines = resultStr.split('\n') |
|
||||
const resultArray = [] |
|
||||
lines.forEach(line => { |
|
||||
if (line.trim()) { |
|
||||
try { |
|
||||
const obj = JSON.parse(line) |
|
||||
resultArray.push(obj) |
|
||||
} catch (error) { |
|
||||
console.error('解析 JSON 时出错:', error) |
|
||||
} |
|
||||
} |
|
||||
}) |
|
||||
|
|
||||
resultArray.forEach(chunk => { |
|
||||
if (chunk.response) { |
|
||||
fullAnswer += chunk.response |
|
||||
} |
|
||||
}) |
|
||||
|
|
||||
this.answer = fullAnswer |
|
||||
} catch (error) { |
|
||||
console.error('请求出错:', error) |
|
||||
this.answer = '请求出错,请稍后再试。' |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
</script> |
|
||||
|
|
||||
<style scoped> |
|
||||
/* 可以添加一些样式 */ |
|
||||
input { |
|
||||
margin-right: 10px; |
|
||||
padding: 5px; |
|
||||
} |
|
||||
|
|
||||
button { |
|
||||
padding: 5px 10px; |
|
||||
} |
|
||||
</style> |
|
||||
@ -1,672 +0,0 @@ |
|||||
<template> |
|
||||
<div class="app-container category-container" style="height: calc(100vh - 140px);"> |
|
||||
<!-- 门类列表 --> |
|
||||
<div class="container-main"> |
|
||||
<div class="elect-cont-left"> |
|
||||
<div class="container-left"> |
|
||||
<span class="right-top-line" /> |
|
||||
<span class="left-bottom-line" /> |
|
||||
<!--门类树状结构--> |
|
||||
<div class="tree-scroll"> |
|
||||
<el-scrollbar style="height: calc(100vh - 230px);"> |
|
||||
<el-tree ref="categroyTree" v-loading="crud.loading" class="arc-tree arc-tree-01" :data="crud.data" :props="defaultProps" node-key="id" :expand-on-click-node="false" highlight-current @node-click="handleNodeClick"> |
|
||||
<span slot-scope="{ node, data }" class="custom-tree-node"> |
|
||||
<el-tooltip :content="node.label" placement="left" :enterable="false" effect="dark"> |
|
||||
<span v-if="data.isType === 0"> |
|
||||
{{ data.label }} |
|
||||
</span> |
|
||||
<span v-if="data.isType === 1" class="iconFolder tree-text"> |
|
||||
{{ data.label }} |
|
||||
</span> |
|
||||
<span v-if="data.isType === 2" class="iconArch tree-text"> |
|
||||
{{ data.label }} |
|
||||
</span> |
|
||||
<span v-if="data.isType === 3" class="iconFile tree-text"> |
|
||||
{{ data.label }} |
|
||||
</span> |
|
||||
</el-tooltip> |
|
||||
</span> |
|
||||
</el-tree> |
|
||||
</el-scrollbar> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
<!-- 门类管理tab --> |
|
||||
<div class="elect-cont-right"> |
|
||||
|
|
||||
<Chat /> |
|
||||
|
|
||||
<DeepSeekChat /> |
|
||||
|
|
||||
<div v-if="selectedCategory.isType === 2" class="container-right"> |
|
||||
<span class="right-top-line" /> |
|
||||
<span class="left-bottom-line" /> |
|
||||
<div style="display: flex; justify-content: flex-start;"> |
|
||||
<div class="upload-btn"> |
|
||||
<input id="upFile" type="file" name="upFile" multiple @change="changeAiFile($event)"> |
|
||||
<el-button :loading="aiLoading" size="small" type="primary"><i :class="['iconfont', aiLoading ? 'icon-huoqu' : 'icon-shangchuan']" />{{ aiLoading ? 'AI辅助著录识别中' : '选择文件' }}</el-button> |
|
||||
</div> |
|
||||
<!-- margin-left: 10px; line-height: 34px; height: 106px; overflow: hidden; overflow-y: scroll; --> |
|
||||
<div style="flex: 1; font-size: 12px; display: flex; line-height: 34px; "> |
|
||||
<div v-for="item in fileList" :key="item.name" class="file-list" style="margin-left: 10px;"> |
|
||||
<i class="iconfont icon-xiaowenjian" style="font-size: 14px;" /> |
|
||||
{{ item.name }} |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
<div class="ai-category-main"> |
|
||||
<div class="ai-des-left"> |
|
||||
<div class="des-title"> |
|
||||
<p>著录项</p> |
|
||||
</div> |
|
||||
<div class="ai-des-form"> |
|
||||
<PreviewForm |
|
||||
ref="previewForm" |
|
||||
:is-has-code="true" |
|
||||
:is-disabled="false" |
|
||||
:form-preview-data.sync="formPreviewData" |
|
||||
:selected-category="selectedCategory" |
|
||||
:is-des-form-type="isDesFormType" |
|
||||
:collect-level="collectLevel" |
|
||||
:category-menu="categoryMenu" |
|
||||
:is-ai-category="true" |
|
||||
/> |
|
||||
</div> |
|
||||
</div> |
|
||||
<div class="ai-des-right"> |
|
||||
<div class="des-title"> |
|
||||
<p>AI识别数据</p> |
|
||||
</div> |
|
||||
<div class="ai-des-json"> |
|
||||
<pre v-if="aiJsonData" ref="typingContainer" v-highlightjs="displayedText">{{ displayedText }}</pre> |
|
||||
</div> |
|
||||
<div v-if="typingFinished" style="text-align: right;"> |
|
||||
<el-button @click="saveAiData">保存</el-button> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
</template> |
|
||||
<script> |
|
||||
import crudCategory from '@/api/system/category/category' |
|
||||
import CRUD, { presenter, header } from '@crud/crud' |
|
||||
import PreviewForm from '@/views/components/category/PreviewForm' |
|
||||
import { FetchInitCategoryInputFieldByPid, FetchCategoryMenu } from '@/api/system/category/category' |
|
||||
import { FetchAIResultZhulu } from '@/api/collect/collect' |
|
||||
import { getCurrentTime } from '@/utils/index' |
|
||||
import { archivesUpload } from '@/utils/upload' |
|
||||
import { mapGetters } from 'vuex' |
|
||||
|
|
||||
import Chat from './Chat.vue' |
|
||||
import DeepSeekChat from './deepSeekChat.vue' |
|
||||
|
|
||||
export default { |
|
||||
name: 'AICataloging', |
|
||||
components: { PreviewForm, DeepSeekChat, Chat }, |
|
||||
cruds() { |
|
||||
return [ |
|
||||
CRUD({ |
|
||||
title: 'AI辅助著录', url: 'api/category/fondMenu', |
|
||||
crudMethod: { ...crudCategory }, |
|
||||
optShow: { |
|
||||
add: false, |
|
||||
edit: false, |
|
||||
del: false, |
|
||||
download: false, |
|
||||
group: false |
|
||||
} |
|
||||
}) |
|
||||
] |
|
||||
}, |
|
||||
mixins: [presenter(), header()], |
|
||||
provide() { |
|
||||
return { |
|
||||
parentsData: this |
|
||||
} |
|
||||
}, |
|
||||
data() { |
|
||||
return { |
|
||||
defaultProps: { |
|
||||
children: 'children', |
|
||||
label: 'label' |
|
||||
}, |
|
||||
selectedCategory: {}, |
|
||||
formPreviewData: [], |
|
||||
isDesFormType: 'arcives', |
|
||||
collectLevel: 3, |
|
||||
categoryMenu: [], |
|
||||
nowDate: '', // 当前时间 |
|
||||
aiLoading: false, |
|
||||
fileList: [], |
|
||||
aiJsonData: null, |
|
||||
displayedText: '', |
|
||||
typingInterval: null, |
|
||||
typingFinished: false, |
|
||||
currentLineIndex: 0 |
|
||||
} |
|
||||
}, |
|
||||
computed: { |
|
||||
...mapGetters([ |
|
||||
'baseApi' |
|
||||
]) |
|
||||
}, |
|
||||
mounted() { |
|
||||
this.getCategoryDataTree() |
|
||||
}, |
|
||||
methods: { |
|
||||
getCategoryDataTree() { |
|
||||
FetchCategoryMenu().then(res => { |
|
||||
this.categoryMenu = res |
|
||||
}) |
|
||||
}, |
|
||||
filterData(data) { |
|
||||
return data.filter(node => { |
|
||||
if (node.children && node.children.length > 0) { |
|
||||
node.children = this.filterData(node.children) // 递归处理子节点 |
|
||||
} |
|
||||
return node.isType !== 3 // 过滤掉isType为3的节点 |
|
||||
}) |
|
||||
}, |
|
||||
// 逆归实现 获取指定元素 |
|
||||
findNode(tree, func) { |
|
||||
for (const node of tree) { |
|
||||
if (func(node)) return node |
|
||||
if (node.children) { |
|
||||
const res = this.findNode(node.children, func) |
|
||||
if (res) return res |
|
||||
} |
|
||||
} |
|
||||
return null |
|
||||
}, |
|
||||
// 根据父级展开全部子级 |
|
||||
expandAllChildren(node, targetElement) { |
|
||||
node.expanded = true |
|
||||
// 递归展开当前节点的每个子节点 |
|
||||
if (node.childNodes && node.childNodes.length > 0) { |
|
||||
for (let i = 0; i < node.childNodes.length; i++) { |
|
||||
if (node.childNodes[i].data.id === targetElement.id) { |
|
||||
this.$refs.categroyTree.setCurrentKey(node.childNodes[i]) |
|
||||
} |
|
||||
this.expandAllChildren(node.childNodes[i], targetElement) |
|
||||
} |
|
||||
} |
|
||||
}, |
|
||||
// 转换函数,将原始数据转换为el-tree所需格式 |
|
||||
transformData(rawData) { |
|
||||
return rawData.map(item => { |
|
||||
return { |
|
||||
label: item.fondName, |
|
||||
isType: 0, |
|
||||
id: item.fondsId, |
|
||||
fondsNo: item.fondsNo, |
|
||||
children: item.categoryList.map(category => { |
|
||||
return { |
|
||||
label: category.cnName, |
|
||||
cnName: category.cnName, |
|
||||
id: category.id, |
|
||||
arrangeType: category.arrangeType, |
|
||||
isType: category.isType, |
|
||||
fondsId: item.fondsId, |
|
||||
fondName: item.fondName, |
|
||||
fondsNo: item.fondsNo, |
|
||||
children: this.transformChildren(category.children, item.fondsId, item.fondName, item.fondsNo) |
|
||||
} |
|
||||
}) |
|
||||
} |
|
||||
}) |
|
||||
}, |
|
||||
// 递归函数,用于处理数据的子节点 |
|
||||
transformChildren(children, fondsId, fondName, fondsNo) { |
|
||||
return children.map(child => { |
|
||||
return { |
|
||||
label: child.cnName, |
|
||||
cnName: child.cnName, |
|
||||
id: child.id, |
|
||||
isType: child.isType, |
|
||||
pid: child.pid, |
|
||||
code: child.code, |
|
||||
arrangeType: child.arrangeType, |
|
||||
fondsId: fondsId, |
|
||||
fondName: fondName, |
|
||||
fondsNo: fondsNo, |
|
||||
children: child.children.length ? this.transformChildren(child.children, fondsId, fondName, fondsNo) : [] |
|
||||
} |
|
||||
}) |
|
||||
}, |
|
||||
// 找顶级节点 |
|
||||
findTopLevelNode(data, fondsId) { |
|
||||
for (let i = 0; i < data.length; i++) { |
|
||||
if (data[i].id === fondsId) { |
|
||||
return data[i] |
|
||||
} |
|
||||
} |
|
||||
return null |
|
||||
}, |
|
||||
[CRUD.HOOK.afterRefresh]() { |
|
||||
this.crud.data = this.filterData(this.transformData(this.crud.data)) |
|
||||
this.$nextTick(() => { |
|
||||
let currentKey |
|
||||
if (localStorage.getItem('currentArchivesKey') !== null) { |
|
||||
currentKey = JSON.parse(localStorage.getItem('currentArchivesKey')) |
|
||||
// 删除门类节点后 |
|
||||
if (this.$refs.categroyTree.getCurrentKey(currentKey.id) == null) { |
|
||||
localStorage.removeItem('currentArchivesKey') |
|
||||
} |
|
||||
this.topLevelNode = this.findTopLevelNode(this.crud.data, currentKey.fondsId) |
|
||||
// 如果找到了顶级节点,则从该节点开始递归查找指定元素 |
|
||||
if (this.topLevelNode) { |
|
||||
if (currentKey) { |
|
||||
// 展开顶级节点的子节点 |
|
||||
if (currentKey.isType === 1) { |
|
||||
currentKey = this.findNode(this.crud.data[0].children, (node) => { |
|
||||
return node.isType !== 1 |
|
||||
}) |
|
||||
} |
|
||||
this.expandAllChildren(this.$refs.categroyTree.getNode(this.topLevelNode), currentKey) |
|
||||
} else { |
|
||||
this.defaultSetting(currentKey) |
|
||||
} |
|
||||
} else { |
|
||||
this.defaultSetting(currentKey) |
|
||||
} |
|
||||
} else { |
|
||||
this.defaultSetting(currentKey) |
|
||||
} |
|
||||
if (currentKey && currentKey.id) { |
|
||||
this.$nextTick(() => { |
|
||||
// 选中节点的门类详情 |
|
||||
this.handleNodeClick(currentKey) |
|
||||
}) |
|
||||
} |
|
||||
}) |
|
||||
}, |
|
||||
defaultSetting(currentKey) { |
|
||||
if (this.crud.data[0].isType === 0) { |
|
||||
currentKey = this.findNode(this.crud.data[0].children, (node) => { |
|
||||
return node.isType !== 1 |
|
||||
}) |
|
||||
this.expandAllChildren(this.$refs.categroyTree.getNode(this.crud.data[0]), currentKey) |
|
||||
} else { |
|
||||
currentKey = this.crud.data[0] |
|
||||
this.expandAllChildren(this.$refs.categroyTree.getNode(this.crud.data[0]), currentKey) |
|
||||
} |
|
||||
}, |
|
||||
// 选中门类后,设置门类详情数据 |
|
||||
handleNodeClick(val) { |
|
||||
if (val) { |
|
||||
localStorage.setItem('currentArchivesKey', JSON.stringify(val)) |
|
||||
this.selectedCategory = val |
|
||||
this.$nextTick(() => { |
|
||||
this.getFormInfo() |
|
||||
}) |
|
||||
} |
|
||||
}, |
|
||||
getFormInfo() { |
|
||||
// console.log('this.selectedCategory.arrangeType', this.selectedCategory.arrangeType) |
|
||||
// if (this.selectedCategory.arrangeType === 1) { |
|
||||
// this.collectLevel = 3 |
|
||||
// } else if (this.selectedCategory.arrangeType === 2) { |
|
||||
// this.collectLevel = 2 |
|
||||
// } else { |
|
||||
// this.collectLevel = 1 |
|
||||
// } |
|
||||
const params = { |
|
||||
'categoryId': this.selectedCategory.id, |
|
||||
'categoryLevel': this.collectLevel |
|
||||
} |
|
||||
FetchInitCategoryInputFieldByPid(params).then(data => { |
|
||||
this.formPreviewData = data |
|
||||
console.log('formPreviewData', this.formPreviewData) |
|
||||
this.isDesFormType = 'arcives' |
|
||||
this.$nextTick(() => { |
|
||||
this.$refs.previewForm.archivesType = 'add' |
|
||||
this.$refs.previewForm.FetchNoFormatField(this.selectedCategory.id) |
|
||||
}) |
|
||||
}) |
|
||||
}, |
|
||||
// 选择附件 |
|
||||
async changeAiFile(e) { |
|
||||
// 替换文件时清空 aiJsonData |
|
||||
this.aiJsonData = null |
|
||||
this.currentLineIndex = 0 |
|
||||
this.displayedText = '' |
|
||||
this.typingFinished = false |
|
||||
this.typingInterval = null |
|
||||
// if (this.typingInterval) { |
|
||||
// clearInterval(this.typingInterval); |
|
||||
// } |
|
||||
|
|
||||
const selectedFiles = Array.from(e.target.files) |
|
||||
const imageFiles = selectedFiles.filter(file => file.type.startsWith('image/')) |
|
||||
const nonImageFiles = selectedFiles.filter(file => !file.type.startsWith('image/')) |
|
||||
|
|
||||
// 不允许同时选择图片和非图片文件 |
|
||||
if (imageFiles.length > 0 && nonImageFiles.length > 0) { |
|
||||
this.$message.error('不能同时选择图片和其他类型文件,请重新选择') |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
const existingImageFiles = this.fileList.filter(item => item.formatType === 'image') |
|
||||
const existingNonImageFiles = this.fileList.filter(item => item.formatType !== 'image') |
|
||||
|
|
||||
if (imageFiles.length > 0) { |
|
||||
// if (existingImageFiles.length > 0) { |
|
||||
// if (existingImageFiles.length + imageFiles.length > 3) { |
|
||||
// // 若加入新图片会超过 3 张,清空已有图片 |
|
||||
// this.fileList = this.fileList.filter(item => item.formatType !== 'image') |
|
||||
// } |
|
||||
// } else if (existingNonImageFiles.length > 0) { |
|
||||
// // 若已有非图片文件,清空已有非图片文件 |
|
||||
// this.fileList = this.fileList.filter(item => item.formatType === 'image') |
|
||||
// } |
|
||||
|
|
||||
// // 检查图片文件数量 |
|
||||
// if (imageFiles.length > 3) { |
|
||||
// this.$message.error('图片文件最多只能选择 3 个,请重新选择') |
|
||||
// return |
|
||||
// } |
|
||||
if (existingNonImageFiles.length > 0) { |
|
||||
// 若已有非图片文件,清空已有非图片文件 |
|
||||
this.fileList = this.fileList.filter(item => item.formatType === 'image') |
|
||||
} |
|
||||
|
|
||||
for (const file of imageFiles) { |
|
||||
// 检查文件是否已存在 |
|
||||
if (this.fileList.some(item => item.name === file.name)) { |
|
||||
this.$message.warning(`文件 ${file.name} 已存在,请勿重复上传`) |
|
||||
continue |
|
||||
} |
|
||||
|
|
||||
const fileInfo = { |
|
||||
file: file, |
|
||||
size: file.size, |
|
||||
formatType: file.type.substring(0, file.type.indexOf('/')), |
|
||||
name: file.name, |
|
||||
postfix: file.name.substring( |
|
||||
file.name.lastIndexOf('.') + 1, |
|
||||
file.name.length |
|
||||
), |
|
||||
px: '' |
|
||||
} |
|
||||
|
|
||||
const fileBase64 = await this.getBase64(file) |
|
||||
const res = await this.getImgPx(fileBase64) |
|
||||
fileInfo.px = res.width + 'px*' + res.height + 'px' |
|
||||
|
|
||||
this.fileList.push(fileInfo) |
|
||||
} |
|
||||
this.FetchAiFileUplaod(this.fileList) |
|
||||
} else if (nonImageFiles.length > 0) { |
|
||||
if (existingNonImageFiles.length > 0) { |
|
||||
// 若已有非图片文件,直接替换 |
|
||||
this.fileList = this.fileList.filter(item => item.formatType === 'image') |
|
||||
} else if (existingImageFiles.length > 0) { |
|
||||
// 若已有图片文件,清空已有图片文件 |
|
||||
this.fileList = this.fileList.filter(item => item.formatType !== 'image') |
|
||||
} |
|
||||
|
|
||||
// 检查非图片文件数量 |
|
||||
if (nonImageFiles.length > 1) { |
|
||||
this.$message.error('非图片文件最多只能选择 1 个,请重新选择') |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
for (const file of nonImageFiles) { |
|
||||
// 检查文件是否已存在 |
|
||||
if (this.fileList.some(item => item.name === file.name)) { |
|
||||
this.$message.warning(`文件 ${file.name} 已存在,请勿重复上传`) |
|
||||
continue |
|
||||
} |
|
||||
|
|
||||
const fileInfo = { |
|
||||
file: file, |
|
||||
size: file.size, |
|
||||
formatType: file.type.substring(0, file.type.indexOf('/')), |
|
||||
name: file.name, |
|
||||
postfix: file.name.substring( |
|
||||
file.name.lastIndexOf('.') + 1, |
|
||||
file.name.length |
|
||||
), |
|
||||
px: '' |
|
||||
} |
|
||||
this.fileList.push(fileInfo) |
|
||||
} |
|
||||
this.FetchAiFileUplaod(this.fileList) |
|
||||
} |
|
||||
}, |
|
||||
FetchAiFileUplaod(files) { |
|
||||
this.aiLoading = true |
|
||||
this.nowDate = getCurrentTime() |
|
||||
const promiseArray = files.map(async(item, index) => { |
|
||||
const json = {} |
|
||||
json.file_name = item.name |
|
||||
json.file_size = item.size |
|
||||
json.file_type = item.postfix |
|
||||
json.file_path = '' |
|
||||
json.archive_id = this.arcId |
|
||||
json.file_dpi = item.px |
|
||||
json.file_thumbnail = '' |
|
||||
json.create_time = this.nowDate |
|
||||
json.id = null |
|
||||
json.last_modified = item.file.lastModified |
|
||||
return json |
|
||||
}) |
|
||||
console.log('promiseArray', promiseArray) |
|
||||
const fileDefault = files.map(item => item.file) |
|
||||
|
|
||||
Promise.all(promiseArray) |
|
||||
.then((arrayUpload) => { |
|
||||
archivesUpload(this.baseApi + '/api/collect/uploadAssistEnterFiles', |
|
||||
fileDefault, |
|
||||
this.selectedCategory.id, |
|
||||
this.arcId, |
|
||||
JSON.stringify(arrayUpload) |
|
||||
).then(res => { |
|
||||
if (res.data.data !== null) { |
|
||||
const params = { |
|
||||
'code': res.data.data.code, |
|
||||
'id': res.data.data.id |
|
||||
} |
|
||||
const fetchAiZhuluResult = () => { |
|
||||
// 检查是否应该继续请求 |
|
||||
if (!this.shouldContinueFetching) return |
|
||||
|
|
||||
FetchAIResultZhulu(params).then((res) => { |
|
||||
const data = JSON.parse(res) |
|
||||
console.log('data', data) |
|
||||
if (data.result !== null && data.status === 'success') { |
|
||||
this.$message({ message: '解析著录附件成功', type: 'success', offset: 8 }) |
|
||||
this.aiJsonData = data.result |
|
||||
this.startTypingEffect() |
|
||||
} else { |
|
||||
setTimeout(fetchAiZhuluResult, 3000) |
|
||||
} |
|
||||
}).catch(err => { |
|
||||
console.log(err) |
|
||||
}) |
|
||||
} |
|
||||
fetchAiZhuluResult() |
|
||||
} else { |
|
||||
this.$message({ message: '著录附件传输失败', type: 'error', offset: 8 }) |
|
||||
} |
|
||||
}) |
|
||||
}) |
|
||||
.catch((error) => { |
|
||||
console.error(error) |
|
||||
}) |
|
||||
}, |
|
||||
startTypingEffect() { |
|
||||
const lines = this.aiJsonData.split('\n') |
|
||||
this.currentLineIndex = 0 |
|
||||
this.displayedText = '' |
|
||||
this.typingFinished = false |
|
||||
|
|
||||
this.typingInterval = setInterval(() => { |
|
||||
if (this.currentLineIndex < lines.length) { |
|
||||
this.displayedText += lines[this.currentLineIndex] + '\n' |
|
||||
this.currentLineIndex++ |
|
||||
} else { |
|
||||
clearInterval(this.typingInterval) |
|
||||
|
|
||||
setTimeout(() => { |
|
||||
this.typingFinished = true |
|
||||
if (this.aiJsonData) { |
|
||||
// this.$refs.previewForm.archivesType = 'add' |
|
||||
this.$refs.previewForm.addOrUpdateForm = JSON.parse(this.aiJsonData) |
|
||||
this.aiLoading = false |
|
||||
} |
|
||||
}, 1000) |
|
||||
} |
|
||||
|
|
||||
// 滚动条始终保持在底部 |
|
||||
const container = this.$refs.typingContainer |
|
||||
if (container) { |
|
||||
container.scrollTop = container.scrollHeight |
|
||||
} |
|
||||
}, 200) |
|
||||
}, |
|
||||
saveAiData() { |
|
||||
this.$router.push({ path: '/collectReorganizi/collectionLibrary' }) |
|
||||
}, |
|
||||
// 将上传的图片转为base64 |
|
||||
getBase64(file) { |
|
||||
const reader = new FileReader() |
|
||||
reader.readAsDataURL(file) |
|
||||
return new Promise((resolve) => { |
|
||||
reader.onload = () => { |
|
||||
resolve(reader.result) |
|
||||
} |
|
||||
}) |
|
||||
}, |
|
||||
// 获取图片的分辨率 |
|
||||
getImgPx(img) { |
|
||||
const image = new Image() |
|
||||
image.src = img |
|
||||
return new Promise((resolve) => { |
|
||||
image.onload = () => { |
|
||||
const width = image.width |
|
||||
const height = image.height |
|
||||
resolve({ width, height }) |
|
||||
} |
|
||||
}) |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
</script> |
|
||||
|
|
||||
<style lang="scss" scoped> |
|
||||
@import "~@/assets/styles/mixin.scss"; |
|
||||
@import "~@/assets/styles/variables.scss"; |
|
||||
.category-container { |
|
||||
.elect-cont-left{ |
|
||||
width: 296px !important; |
|
||||
} |
|
||||
} |
|
||||
.openSidebar .category-container .elect-cont-right{ |
|
||||
width: calc(100vw - 614px) !important; |
|
||||
} |
|
||||
.hideSidebar .category-container .elect-cont-right { |
|
||||
width: calc(100vw - 412px) !important; |
|
||||
} |
|
||||
.tree-scroll{ |
|
||||
font-size: 14px; |
|
||||
} |
|
||||
.ai-category-main{ |
|
||||
display: flex; |
|
||||
justify-content: flex-start; |
|
||||
margin-top: 10px; |
|
||||
|
|
||||
.ai-des-left{ |
|
||||
width: 740px; |
|
||||
margin-right: 20px; |
|
||||
} |
|
||||
.ai-des-right{ |
|
||||
flex: 1; |
|
||||
} |
|
||||
|
|
||||
.ai-des-form{ |
|
||||
height: calc(100vh - 262px); |
|
||||
overflow-y: auto; |
|
||||
padding-right: 10px; |
|
||||
} |
|
||||
|
|
||||
.ai-des-json{ |
|
||||
height: calc(100vh - 300px); |
|
||||
margin-bottom: 10px; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
.prearch-upload{ |
|
||||
margin-right: 0 !important; |
|
||||
::v-deep .el-form-item__label{ |
|
||||
position: relative; |
|
||||
&::before{ |
|
||||
position: absolute; |
|
||||
top: -2px; |
|
||||
right: 70px; |
|
||||
content: "*"; |
|
||||
font-size: 10px; |
|
||||
color: #ff4949; |
|
||||
font-style: normal; |
|
||||
} |
|
||||
} |
|
||||
::v-deep .el-form-item__content{ |
|
||||
position: relative; |
|
||||
width: 540px !important; |
|
||||
display: flex; |
|
||||
justify-content: space-between; |
|
||||
.input-style{ |
|
||||
width: 500px; |
|
||||
height: 34px; |
|
||||
line-height: 34px; |
|
||||
padding: 0 20px; |
|
||||
border: 1px solid #e6e8ed; |
|
||||
border-radius: 3px; |
|
||||
// &.error-box{ |
|
||||
// border-color: #ed4a41; |
|
||||
// } |
|
||||
} |
|
||||
// .error-tip{ |
|
||||
// position: absolute; |
|
||||
// left: 0; |
|
||||
// bottom: -26px; |
|
||||
// font-size: 12px; |
|
||||
// color: #ff4949; |
|
||||
// } |
|
||||
.upload-btn{ |
|
||||
position: relative; |
|
||||
width:96px; |
|
||||
margin-right: 0 !important; |
|
||||
margin-left: 10px; |
|
||||
overflow: initial !important; |
|
||||
#upFile{ |
|
||||
position: absolute; |
|
||||
left: 0; |
|
||||
top: 0; |
|
||||
// opacity: 0; |
|
||||
width: 84px; |
|
||||
height: 34px; |
|
||||
} |
|
||||
.el-button{ |
|
||||
margin-top: -2px; |
|
||||
font-weight: bold; |
|
||||
border-color: #0348f3; |
|
||||
color: #0348f3; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
pre { |
|
||||
background-color: #f4f4f4; |
|
||||
padding: 10px; |
|
||||
border: 1px solid #ccc; |
|
||||
border-radius: 4px; |
|
||||
white-space: pre-wrap; |
|
||||
word-wrap: break-word; |
|
||||
height: calc(100vh - 300px); |
|
||||
overflow-y: auto; |
|
||||
} |
|
||||
</style> |
|
||||
@ -1,404 +0,0 @@ |
|||||
<template> |
|
||||
<div class="app-container category-container" style="position: relative; height: calc(100vh - 140px);"> |
|
||||
<!-- <div class="ai-wrapper"> |
|
||||
<iframe id="myIframe" style="position: absolute; top: 0; left: -34%; border: none; z-index: 9;" width="100%" height="100%" src="https://f.3dman.cn/art/sDpbYwwJ/fbx/CdYqVH95/index.html" /> |
|
||||
</div> --> |
|
||||
<div class="chat-wrapper"> |
|
||||
<div ref="chatContent" class="chat-content" /> |
|
||||
<div class="chat-send"> |
|
||||
<textarea v-model="message" cols="50" rows="7" placeholder="请输入你想咨询的问题" @input="updateSendButtonState" @keypress="handleKeyPress" /> |
|
||||
<div class="send-button-container"> |
|
||||
<div class="hot-word"> |
|
||||
<span v-for="(word, index) in hotWords" :key="index" @click="handleHotWordClick(word)">{{ word }}</span> |
|
||||
</div> |
|
||||
<span class="send-button" :class="{ 'send-disabled-button': isSendButtonDisabled }" :disabled="isSendButtonDisabled" @click="sendMessage">发送</span> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
</template> |
|
||||
<script> |
|
||||
export default { |
|
||||
name: 'AIDigitalHuman', |
|
||||
components: { }, |
|
||||
data() { |
|
||||
return { |
|
||||
message: '', |
|
||||
isSendButtonDisabled: true, |
|
||||
isBotReplying: false, |
|
||||
isBotTyping: false, |
|
||||
hotWords: ['什么是“人工智能”?', '什么是“AI大模型”?'] |
|
||||
} |
|
||||
}, |
|
||||
computed: { |
|
||||
}, |
|
||||
created() { |
|
||||
}, |
|
||||
mounted() { |
|
||||
this.sendWelcomeMessage() |
|
||||
this.updateSendButtonState() |
|
||||
}, |
|
||||
methods: { |
|
||||
sendWelcomeMessage() { |
|
||||
const welcomeMessage = '您好!请问有什么问题可以帮您解答吗?' |
|
||||
this.appendMessage(welcomeMessage, false, false, true) |
|
||||
}, |
|
||||
updateSendButtonState() { |
|
||||
this.isSendButtonDisabled = this.message.trim() === '' || this.isBotReplying || this.isBotTyping |
|
||||
}, |
|
||||
sendMessage() { |
|
||||
if (!this.isBotReplying && !this.isBotTyping) { |
|
||||
const message = this.message.trim() |
|
||||
if (message) { |
|
||||
this.appendMessage(message, true, false) |
|
||||
this.message = '' |
|
||||
this.isSendButtonDisabled = true |
|
||||
|
|
||||
this.appendMessage('', false, true) |
|
||||
this.isBotReplying = true |
|
||||
|
|
||||
// axios.post(`${this.config.apiUrl}/api/v1/workspace/dxhtsg/chat`, { |
|
||||
// message, |
|
||||
// mode: 'chat' |
|
||||
// }, { |
|
||||
// headers: { |
|
||||
// 'Authorization': this.config.apiKey |
|
||||
// } |
|
||||
// }) |
|
||||
// .then((res) => { |
|
||||
// const loadingElement = this.$refs.chatContent.querySelector('.loading') |
|
||||
// if (loadingElement) { |
|
||||
// loadingElement.parentNode.remove() |
|
||||
// } |
|
||||
// const botReply = res.data.textResponse.replace(/\【(\/)?SYS\】/gi, '飞天智能AI小助手') |
|
||||
// this.appendMessage('', false, false) |
|
||||
// this.isBotTyping = true |
|
||||
// let i = 0 |
|
||||
// const speed = 50 |
|
||||
|
|
||||
// const typeWriter = () => { |
|
||||
// if (!this.isBotTyping) { |
|
||||
// return |
|
||||
// } |
|
||||
// if (i < botReply.length) { |
|
||||
// const lastBotMessage = this.$refs.chatContent.querySelectorAll('.bot-message:last-child p')[0] |
|
||||
// lastBotMessage.innerHTML += botReply.charAt(i) |
|
||||
// i++ |
|
||||
// setTimeout(typeWriter, speed) |
|
||||
// this.$refs.chatContent.scrollTop = this.$refs.chatContent.scrollHeight |
|
||||
// } else { |
|
||||
// this.isBotTyping = false |
|
||||
// this.isBotReplying = false |
|
||||
// const stopButton = this.$refs.chatContent.querySelector('#stop-btn') |
|
||||
// if (stopButton) { |
|
||||
// stopButton.remove() |
|
||||
// } |
|
||||
// } |
|
||||
// } |
|
||||
// typeWriter() |
|
||||
// }) |
|
||||
// .catch((err) => { |
|
||||
// console.log(err) |
|
||||
// // 这里需要引入 layer 库来实现提示框功能 |
|
||||
// // layer.msg('因网络原因,您的问题暂时无法解答,请稍后再试!', { |
|
||||
// // offset: [window.innerHeight / 2 - 100, window.innerWidth / 2 + 100], |
|
||||
// // anim: 5 |
|
||||
// // }); |
|
||||
// const loadingElement = this.$refs.chatContent.querySelector('.loading') |
|
||||
// if (loadingElement) { |
|
||||
// loadingElement.parentNode.remove() |
|
||||
// } |
|
||||
// this.isBotTyping = false |
|
||||
// this.isBotReplying = false |
|
||||
// }) |
|
||||
} |
|
||||
} else if (this.isBotTyping) { |
|
||||
// layer.msg('请等待当前对话完成,稍后再试。', { |
|
||||
// offset: [window.innerHeight / 2 - 100, window.innerWidth / 2 + 100], |
|
||||
// anim: 6 |
|
||||
// }); |
|
||||
} else if (this.isBotReplying) { |
|
||||
// layer.msg('请等待当前对话完成,稍后再试。', { |
|
||||
// offset: [window.innerHeight / 2 - 100, window.innerWidth / 2 + 100], |
|
||||
// anim: 6 |
|
||||
// }); |
|
||||
} |
|
||||
}, |
|
||||
handleKeyPress(event) { |
|
||||
if (event.which === 13) { |
|
||||
event.preventDefault() |
|
||||
this.sendMessage() |
|
||||
} |
|
||||
}, |
|
||||
handleHotWordClick(word) { |
|
||||
this.message = word |
|
||||
this.sendMessage() |
|
||||
}, |
|
||||
appendMessage(content, isUserMessage, isLoading, isWelcomeMessage = false) { |
|
||||
const className = isUserMessage ? 'user-message' : 'bot-message' |
|
||||
let messageContent |
|
||||
|
|
||||
if (isLoading) { |
|
||||
messageContent = '<p class="loading"><img src="images/loading.webp" alt="" /></p>' |
|
||||
} else { |
|
||||
messageContent = isUserMessage |
|
||||
? `<p>${content}</p>` |
|
||||
: isWelcomeMessage |
|
||||
? `<p>${content}</p>` |
|
||||
: `<p>${content}<span id="stop-btn">停止输出</span></p>` |
|
||||
} |
|
||||
|
|
||||
const messageHtml = ` |
|
||||
<div class="chat-message ${className}"> |
|
||||
${messageContent} |
|
||||
</div>` |
|
||||
this.$refs.chatContent.insertAdjacentHTML('beforeend', messageHtml) |
|
||||
this.$refs.chatContent.scrollTop = this.$refs.chatContent.scrollHeight |
|
||||
|
|
||||
const stopButton = this.$refs.chatContent.querySelector('#stop-btn') |
|
||||
if (stopButton) { |
|
||||
stopButton.addEventListener('click', () => { |
|
||||
this.isBotTyping = false |
|
||||
this.isBotReplying = false |
|
||||
stopButton.removeAttribute('id') |
|
||||
stopButton.classList.add('remove-btn') |
|
||||
stopButton.innerHTML = '用户取消' |
|
||||
}) |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
</script> |
|
||||
|
|
||||
<style lang="scss" scoped> |
|
||||
/* ai数字人 start */ |
|
||||
.chat-wrapper { |
|
||||
position: absolute; |
|
||||
right: 0; |
|
||||
top: 0; |
|
||||
width: 1185px; |
|
||||
height: 778px; |
|
||||
padding: 24px; |
|
||||
background: url("~@/assets/images/list-bg.png") no-repeat left top; |
|
||||
background-size: 100% 100%; |
|
||||
z-index: 99; |
|
||||
font-family: 'PingFang'; |
|
||||
} |
|
||||
|
|
||||
.chat-content { |
|
||||
width: 100%; |
|
||||
height: calc(100% - 234px); |
|
||||
overflow: hidden; |
|
||||
overflow-y: scroll; |
|
||||
} |
|
||||
|
|
||||
.chat-message { |
|
||||
display: flex; |
|
||||
justify-content: flex-end; |
|
||||
font-size: 28px; |
|
||||
color: #fff; |
|
||||
margin-bottom: 16px; |
|
||||
} |
|
||||
|
|
||||
.chat-message p { |
|
||||
position: relative; |
|
||||
line-height: 30px; |
|
||||
padding: 10px 16px; |
|
||||
background: rgba(0,0,0,0.2); |
|
||||
border-radius: 28px 10px 28px 28px; |
|
||||
max-width: 90%; |
|
||||
} |
|
||||
|
|
||||
.chat-message p span{ |
|
||||
position: absolute; |
|
||||
left: 4px; |
|
||||
bottom: -40px; |
|
||||
width: 80px; |
|
||||
padding-left: 24px; |
|
||||
height: 32px; |
|
||||
text-align: center; |
|
||||
line-height: 32px; |
|
||||
border: 1px solid #4CA7FF; |
|
||||
color: #4CA7FF; |
|
||||
border-radius: 10px; |
|
||||
font-size: 16px; |
|
||||
cursor: pointer; |
|
||||
} |
|
||||
|
|
||||
.chat-message p span::before{ |
|
||||
content: ''; |
|
||||
position: absolute; |
|
||||
left: 4px; |
|
||||
top: 50%; |
|
||||
width: 24px; |
|
||||
height: 24px; |
|
||||
background: url("~@/assets/images/index-img7.png") no-repeat left top; |
|
||||
background-size: 100% 100%; |
|
||||
margin-top: -12px; |
|
||||
} |
|
||||
|
|
||||
.chat-message p span:hover{ |
|
||||
background-color: rgba(255,255,255,.2); |
|
||||
} |
|
||||
|
|
||||
.chat-message p span.remove-btn{ |
|
||||
padding-left: 0; |
|
||||
border: none; |
|
||||
color: #999; |
|
||||
} |
|
||||
|
|
||||
.chat-message p span.remove-btn::before{ |
|
||||
border: none; |
|
||||
color: #999; |
|
||||
background: none; |
|
||||
} |
|
||||
|
|
||||
.chat-message p span.remove-btn:hover{ |
|
||||
background-color: transparent; |
|
||||
cursor: default; |
|
||||
} |
|
||||
|
|
||||
.bot-message { |
|
||||
display: flex; |
|
||||
flex-direction: row-reverse; |
|
||||
justify-content: flex-end; |
|
||||
} |
|
||||
|
|
||||
.bot-message p { |
|
||||
line-height: 48px; |
|
||||
background-color: rgb(255,255,255,0.2); |
|
||||
border-radius: 8px 28px 28px 28px; |
|
||||
} |
|
||||
|
|
||||
.loading{ |
|
||||
position: relative; |
|
||||
display: flex; |
|
||||
justify-content: flex-start; |
|
||||
align-items: center; |
|
||||
height: 66px; |
|
||||
} |
|
||||
.loading img{ |
|
||||
display: block; |
|
||||
width: 67px; |
|
||||
margin: 0 10px; |
|
||||
} |
|
||||
|
|
||||
.chat-send { |
|
||||
width: 100%; |
|
||||
height: 210px; |
|
||||
margin-top: 24px; |
|
||||
background: linear-gradient( 90deg, #2C509B 0%, rgba(44,80,155,0.6) 100%); |
|
||||
border-radius: 16px; |
|
||||
} |
|
||||
|
|
||||
.chat-send textarea { |
|
||||
display: block; |
|
||||
width: calc(100% - 40px); |
|
||||
height: 120px; |
|
||||
font-size: 24px; |
|
||||
line-height: 36px; |
|
||||
border: none; |
|
||||
padding: 20px; |
|
||||
background-color: transparent; |
|
||||
color: #fff; |
|
||||
} |
|
||||
|
|
||||
.send-button-container { |
|
||||
display: flex; |
|
||||
justify-content: space-between; |
|
||||
align-items: center; |
|
||||
padding: 0 20px 0 0; |
|
||||
margin-top: 16px; |
|
||||
} |
|
||||
|
|
||||
.hot-word span{ |
|
||||
position: relative; |
|
||||
display: inline-block; |
|
||||
font-size: 20px; |
|
||||
color: #fff; |
|
||||
line-height: 50px; |
|
||||
margin: 0 10px; |
|
||||
padding: 6px 16px 6px 48px; |
|
||||
background: rgba(0,0,0,0.2); |
|
||||
border-radius: 28px 8px 28px 28px; |
|
||||
} |
|
||||
|
|
||||
.hot-word span::before{ |
|
||||
content: ""; |
|
||||
position: absolute; |
|
||||
left: 12px; |
|
||||
top: 50%; |
|
||||
width: 30px; |
|
||||
height: 30px; |
|
||||
background: url("~@/assets/images/wenhao.png") no-repeat left top; |
|
||||
background-size: 100% 100%; |
|
||||
transform: translateY(-50%); |
|
||||
} |
|
||||
|
|
||||
.hot-word span:hover{ |
|
||||
background-color: rgba(0,0,0,0.6); |
|
||||
cursor: pointer; |
|
||||
} |
|
||||
|
|
||||
.send-button { |
|
||||
position: relative; |
|
||||
display: inline-block; |
|
||||
width: 140px; |
|
||||
height: 58px; |
|
||||
line-height: 58px; |
|
||||
padding-left: 60px; |
|
||||
font-size: 26px; |
|
||||
text-align: left; |
|
||||
color: #fff; |
|
||||
background: linear-gradient(135deg, #06BFFF 0%, #2B74FF 100%); |
|
||||
border-radius: 58px; |
|
||||
cursor: pointer; |
|
||||
} |
|
||||
|
|
||||
.send-button::before { |
|
||||
content: ""; |
|
||||
position: absolute; |
|
||||
left: 16px; |
|
||||
top: 50%; |
|
||||
width: 36px; |
|
||||
height: 36px; |
|
||||
background: url("~@/assets/images/index-img8.png") no-repeat left top; |
|
||||
background-size: 36px 36px; |
|
||||
transform: translateY(-50%); |
|
||||
} |
|
||||
|
|
||||
.send-disabled-button { |
|
||||
background: rgba(255,255,255,.2); |
|
||||
cursor: not-allowed; |
|
||||
} |
|
||||
|
|
||||
.run-btn { |
|
||||
position: relative; |
|
||||
width: 120px; |
|
||||
height: 58px; |
|
||||
line-height: 58px; |
|
||||
padding-left: 60px; |
|
||||
font-size: 26px; |
|
||||
text-align: center; |
|
||||
color: #fff; |
|
||||
background: linear-gradient(135deg, #06BFFF 0%, #2B74FF 100%); |
|
||||
border-radius: 58px; |
|
||||
cursor: pointer; |
|
||||
margin: 40px 0 0 20px; |
|
||||
} |
|
||||
|
|
||||
.run-btn::before { |
|
||||
content: ""; |
|
||||
position: absolute; |
|
||||
left: 30px; |
|
||||
top: 50%; |
|
||||
width: 36px; |
|
||||
height: 36px; |
|
||||
background: url("~@/assets/images/run.png") no-repeat left top; |
|
||||
background-size: 36px 36px; |
|
||||
transform: translateY(-50%); |
|
||||
} |
|
||||
|
|
||||
/* ai数字人 end */ |
|
||||
|
|
||||
</style> |
|
||||
@ -1,159 +0,0 @@ |
|||||
<template> |
|
||||
<div :class="{ 'chat-message': true, 'user-message': isUserMessage, 'bot-message':!isUserMessage }"> |
|
||||
<p v-if="isLoading" class="loading"><img src="images/loading.webp" alt=""></p> |
|
||||
<p v-else> |
|
||||
{{ content }} |
|
||||
<span |
|
||||
v-if="!isUserMessage &&!isWelcomeMessage" |
|
||||
:class="{ 'stop-btn': isTyping, 'remove-btn':!isTyping && isStopped }" |
|
||||
@click="stopTyping($event)" |
|
||||
> |
|
||||
{{ isTyping? '停止输出' : (isStopped? '用户取消' : '') }} |
|
||||
</span> |
|
||||
</p> |
|
||||
</div> |
|
||||
</template> |
|
||||
|
|
||||
<script> |
|
||||
export default { |
|
||||
props: { |
|
||||
content: { |
|
||||
type: String, |
|
||||
default: '' |
|
||||
}, |
|
||||
isUserMessage: { |
|
||||
type: Boolean, |
|
||||
default: false |
|
||||
}, |
|
||||
isLoading: { |
|
||||
type: Boolean, |
|
||||
default: false |
|
||||
}, |
|
||||
isWelcomeMessage: { |
|
||||
type: Boolean, |
|
||||
default: false |
|
||||
}, |
|
||||
isTyping: { |
|
||||
type: Boolean, |
|
||||
default: false |
|
||||
} |
|
||||
}, |
|
||||
data() { |
|
||||
return { |
|
||||
isStopped: false |
|
||||
} |
|
||||
}, |
|
||||
methods: { |
|
||||
stopTyping(event) { |
|
||||
this.isTyping = false |
|
||||
this.isStopped = true |
|
||||
this.$emit('stop-typing', event) |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
</script> |
|
||||
|
|
||||
<style scoped> |
|
||||
/* 可以在这里添加组件特定的样式 */ |
|
||||
.chat-message { |
|
||||
display: flex; |
|
||||
justify-content: flex-end; |
|
||||
font-size: 22px; |
|
||||
color: #fff; |
|
||||
margin-bottom: 16px; |
|
||||
} |
|
||||
|
|
||||
.chat-message p { |
|
||||
position: relative; |
|
||||
line-height: 30px; |
|
||||
padding: 10px 16px; |
|
||||
background: rgba(0, 0, 0, 0.2); |
|
||||
border-radius: 28px 10px 28px 28px; |
|
||||
max-width: 90%; |
|
||||
} |
|
||||
|
|
||||
.chat-message p span.stop-btn { |
|
||||
position: absolute; |
|
||||
left: 4px; |
|
||||
bottom: -40px; |
|
||||
width: 100px; |
|
||||
padding-left: 24px; |
|
||||
height: 32px; |
|
||||
text-align: center; |
|
||||
line-height: 30px; |
|
||||
border: 1px solid #4CA7FF; |
|
||||
color: #4CA7FF; |
|
||||
border-radius: 10px; |
|
||||
font-size: 16px; |
|
||||
cursor: pointer; |
|
||||
} |
|
||||
|
|
||||
.chat-message p span.stop-btn::before { |
|
||||
content: ''; |
|
||||
position: absolute; |
|
||||
left: 4px; |
|
||||
top: 50%; |
|
||||
width: 24px; |
|
||||
height: 24px; |
|
||||
background: url("~@/assets/images/index-img7.png") no-repeat left top; |
|
||||
background-size: 100% 100%; |
|
||||
margin-top: -12px; |
|
||||
} |
|
||||
|
|
||||
.chat-message p span.stop-btn:hover { |
|
||||
background-color: rgba(255, 255, 255, 0.2); |
|
||||
} |
|
||||
|
|
||||
.chat-message p span.remove-btn { |
|
||||
position: absolute; |
|
||||
left: 4px; |
|
||||
bottom: -40px; |
|
||||
padding-left: 0; |
|
||||
border: none; |
|
||||
color: #999; |
|
||||
width: 100px; |
|
||||
padding-left: 24px; |
|
||||
height: 32px; |
|
||||
text-align: center; |
|
||||
line-height: 30px; |
|
||||
font-size: 16px; |
|
||||
cursor: pointer; |
|
||||
} |
|
||||
|
|
||||
.chat-message p span.remove-btn::before { |
|
||||
border: none; |
|
||||
color: #999; |
|
||||
background: none; |
|
||||
} |
|
||||
|
|
||||
.chat-message p span.remove-btn:hover { |
|
||||
background-color: transparent; |
|
||||
cursor: default; |
|
||||
} |
|
||||
|
|
||||
.bot-message { |
|
||||
display: flex; |
|
||||
flex-direction: row-reverse; |
|
||||
justify-content: flex-end; |
|
||||
} |
|
||||
|
|
||||
.bot-message p { |
|
||||
line-height: 48px; |
|
||||
background-color: rgba(255, 255, 255, 0.2); |
|
||||
border-radius: 8px 28px 28px 28px; |
|
||||
} |
|
||||
|
|
||||
.loading { |
|
||||
position: relative; |
|
||||
display: flex; |
|
||||
justify-content: flex-start; |
|
||||
align-items: center; |
|
||||
height: 66px; |
|
||||
} |
|
||||
|
|
||||
.loading img { |
|
||||
display: block; |
|
||||
width: 67px; |
|
||||
margin: 0 10px; |
|
||||
} |
|
||||
</style> |
|
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue