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