Browse Source

AI辅助著录/AI数字人/档案分类bug

master
xuhuajiao 4 months ago
parent
commit
9fe20244bb
  1. 2
      .env.development
  2. 1
      .env.production
  3. 1
      public/static/config.js
  4. 30
      src/api/ai/ai.js
  5. 74
      src/assets/iconfonts/light/iconfont.css
  6. 2
      src/assets/iconfonts/light/iconfont.js
  7. 119
      src/assets/iconfonts/light/iconfont.json
  8. BIN
      src/assets/iconfonts/light/iconfont.ttf
  9. BIN
      src/assets/iconfonts/light/iconfont.woff
  10. BIN
      src/assets/iconfonts/light/iconfont.woff2
  11. 159
      src/views/AIAssistant/AICataloging/Chat.vue
  12. 176
      src/views/AIAssistant/AICataloging/Chat2.vue
  13. 74
      src/views/AIAssistant/AICataloging/deepSeekChat.vue
  14. 672
      src/views/AIAssistant/AICataloging/index2.vue
  15. 394
      src/views/AIAssistant/AICataloging/running/index.vue
  16. 52
      src/views/AIAssistant/AICataloging/running/module/detail.vue
  17. 462
      src/views/AIAssistant/AIDigitalHuman/index.vue
  18. 404
      src/views/AIAssistant/AIDigitalHuman/index2.vue
  19. 159
      src/views/AIAssistant/AIDigitalHuman/message.vue
  20. 26
      src/views/components/category/PreviewForm.vue
  21. 5
      src/views/system/archivesClassify/module/tableList.vue

2
.env.development

@ -4,7 +4,7 @@ ENV = 'development'
# 许镇-本地服地址
VUE_APP_BASE_API = 'http://192.168.99.72:11100'
VUE_APP_WS_API = 'ws://192.168.99.72:11100'
VUE_APP_AIDEEPSEEK_API = 'http://192.168.99.86:11434'
# VUE_APP_BASE_API = 'http://192.168.99.107:11100'

1
.env.production

@ -3,6 +3,7 @@ ENV = 'production'
# 如果使用 Nginx 代理后端接口,那么此处需要改为 '/',文件查看 Docker 部署篇,Nginx 配置
# 接口地址,注意协议,如果你没有配置 ssl,需要将 https 改为 http
VUE_APP_BASE_API = 'http://192.168.99.107:11100'
VUE_APP_AIDEEPSEEK_API = 'http://192.168.99.86:11434'
# VUE_APP_BASE_API = 'http://27.19.215.77:11100'
# VUE_APP_BASE_API = 'http://27.16.212.58:11100'
# VUE_APP_BASE_API = 'http://192.168.99.71:11110'

1
public/static/config.js

@ -3,6 +3,7 @@ window.g = {
// ApiUrl: 'http://27.16.212.58:11100', // 配置服务器地址,
// ApiUrl: 'http://192.168.99.71:11110',
ApiUrl: 'http://192.168.99.107:11100',
AIDeepSeekUrl:'http://192.168.99.86:11434',
// ParentPage: { // 后续看需求配置
// CrossDomainProxyUrl: '/Home/CrossDomainProxy',
// BtnsApi: '/api/services/app/Authorization/GetBtns',

30
src/api/ai/ai.js

@ -8,4 +8,32 @@ export function FetchDoHandleEnterAnalysis(params) {
method: 'get'
})
}
export default { FetchDoHandleEnterAnalysis }
// 根据辅助著录获取上传附件
export function FetchInitAssistEnterTemp(params) {
return request({
url: 'api/ai/initAssistEnterTemp' + '?' + qs.stringify(params, { indices: false }),
method: 'get'
})
}
// 删除AI辅助著录
export function FetchDelAssistEnter(data) {
return request({
url: 'api/ai/delAssistEnter',
method: 'post',
data
})
}
// AI辅助著录生成档案
export function FetchHandleEnterAnalysis(data) {
return request({
url: 'api/ai/handleEnterAnalysis',
method: 'post',
data
})
}
export default { FetchDoHandleEnterAnalysis, FetchInitAssistEnterTemp, FetchDelAssistEnter, FetchHandleEnterAnalysis }

74
src/assets/iconfonts/light/iconfont.css

@ -1,8 +1,8 @@
@font-face {
font-family: "iconfont"; /* Project id 3966148 */
src: url('iconfont.woff2?t=1716796171009') format('woff2'),
url('iconfont.woff?t=1716796171009') format('woff'),
url('iconfont.ttf?t=1716796171009') format('truetype');
src: url('iconfont.woff2?t=1741163523495') format('woff2'),
url('iconfont.woff?t=1741163523495') format('woff'),
url('iconfont.ttf?t=1741163523495') format('truetype');
}
.iconfont {
@ -13,6 +13,70 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-shendusikao:before {
content: "\e876";
}
.icon-zhongduanjiankong:before {
content: "\e691";
}
.icon-lianjieduankai:before {
content: "\e600";
}
.icon-stop:before {
content: "\e714";
}
.icon-zhuangtai1:before {
content: "\e690";
}
.icon-zhuangtai2:before {
content: "\e6bc";
}
.icon-biaoqian:before {
content: "\e693";
}
.icon-_biaoqian-:before {
content: "\e68f";
}
.icon-shuju:before {
content: "\e68e";
}
.icon-duolouceng:before {
content: "\e87c";
}
.icon-shujia:before {
content: "\e68c";
}
.icon-hangzhengquyuguanli:before {
content: "\e68d";
}
.icon-quyu1:before {
content: "\e68b";
}
.icon-gongsi:before {
content: "\e689";
}
.icon-louceng:before {
content: "\e68a";
}
.icon-wodeshujia:before {
content: "\e688";
}
.icon-ceshi:before {
content: "\e687";
}
@ -489,6 +553,10 @@
content: "\e613";
}
.icon-weizhi-copy:before {
content: "\e7d5";
}
.icon-xuanzhong:before {
content: "\e614";
}

2
src/assets/iconfonts/light/iconfont.js
File diff suppressed because it is too large
View File

119
src/assets/iconfonts/light/iconfont.json

@ -5,6 +5,118 @@
"css_prefix_text": "icon-",
"description": "",
"glyphs": [
{
"icon_id": "43375318",
"name": "深度思考",
"font_class": "shendusikao",
"unicode": "e876",
"unicode_decimal": 59510
},
{
"icon_id": "18610501",
"name": "终端监控",
"font_class": "zhongduanjiankong",
"unicode": "e691",
"unicode_decimal": 59025
},
{
"icon_id": "10596948",
"name": "链接断开",
"font_class": "lianjieduankai",
"unicode": "e600",
"unicode_decimal": 58880
},
{
"icon_id": "10014347",
"name": "终止",
"font_class": "stop",
"unicode": "e714",
"unicode_decimal": 59156
},
{
"icon_id": "13446589",
"name": "状态",
"font_class": "zhuangtai1",
"unicode": "e690",
"unicode_decimal": 59024
},
{
"icon_id": "18916458",
"name": "状态",
"font_class": "zhuangtai2",
"unicode": "e6bc",
"unicode_decimal": 59068
},
{
"icon_id": "4487893",
"name": "标签",
"font_class": "biaoqian",
"unicode": "e693",
"unicode_decimal": 59027
},
{
"icon_id": "6796688",
"name": "2_标签-06",
"font_class": "_biaoqian-",
"unicode": "e68f",
"unicode_decimal": 59023
},
{
"icon_id": "15392667",
"name": "数据",
"font_class": "shuju",
"unicode": "e68e",
"unicode_decimal": 59022
},
{
"icon_id": "10767710",
"name": "多楼层",
"font_class": "duolouceng",
"unicode": "e87c",
"unicode_decimal": 59516
},
{
"icon_id": "16328040",
"name": "书架",
"font_class": "shujia",
"unicode": "e68c",
"unicode_decimal": 59020
},
{
"icon_id": "23855671",
"name": "行政区域管理",
"font_class": "hangzhengquyuguanli",
"unicode": "e68d",
"unicode_decimal": 59021
},
{
"icon_id": "24272021",
"name": "区域",
"font_class": "quyu1",
"unicode": "e68b",
"unicode_decimal": 59019
},
{
"icon_id": "6901606",
"name": "公司",
"font_class": "gongsi",
"unicode": "e689",
"unicode_decimal": 59017
},
{
"icon_id": "7032903",
"name": "楼层",
"font_class": "louceng",
"unicode": "e68a",
"unicode_decimal": 59018
},
{
"icon_id": "15085894",
"name": "我的书架",
"font_class": "wodeshujia",
"unicode": "e688",
"unicode_decimal": 59016
},
{
"icon_id": "18585490",
"name": "数据测试名称",
@ -838,6 +950,13 @@
"unicode": "e613",
"unicode_decimal": 58899
},
{
"icon_id": "40682301",
"name": "位置-copy",
"font_class": "weizhi-copy",
"unicode": "e7d5",
"unicode_decimal": 59349
},
{
"icon_id": "34690923",
"name": "选中",

BIN
src/assets/iconfonts/light/iconfont.ttf

BIN
src/assets/iconfonts/light/iconfont.woff

BIN
src/assets/iconfonts/light/iconfont.woff2

159
src/views/AIAssistant/AICataloging/Chat.vue

@ -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>

176
src/views/AIAssistant/AICataloging/Chat2.vue

@ -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>

74
src/views/AIAssistant/AICataloging/deepSeekChat.vue

@ -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>

672
src/views/AIAssistant/AICataloging/index2.vue

@ -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 // isType3
})
},
//
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>

394
src/views/AIAssistant/AICataloging/running/index.vue

@ -22,10 +22,19 @@
<div style="cursor: pointer;" @click="handleTipContent">
<i style="font-size:14px; color:#ED4A41; margin-right: 10px;" class="el-icon-warning" />
</div>
<el-button size="mini" @click="uploadVisible=true">
<i class="iconfont icon-shangchuan" />
创建解析任务
</el-button>
<crudOperation :permission="permission">
<template v-slot:middle>
<el-button size="mini" @click="uploadVisible=true">
<i class="iconfont icon-shangchuan" />
创建解析任务
</el-button>
<el-button slot="reference" size="mini" :loading="crud.delAllLoading" :disabled="!crud.selections.length" @click="handleDel(crud.selections)">
<i class="iconfont icon-shanchu" />
删除
</el-button>
</template>
</crudOperation>
</div>
</div>
<!--表格渲染-->
@ -40,6 +49,7 @@
@cell-dblclick="tableDoubleClick"
@selection-change="crud.selectionChangeHandler"
>
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="任务编号" prop="id" />
<el-table-column label="文件数量" prop="fileNum" />
<el-table-column label="创建人" prop="create_by" />
@ -58,7 +68,7 @@
<el-table-column label="状态" prop="status" align="center" width="140">
<template slot-scope="scope">
<div v-if="!isHistroy">
<span v-if="!scope.row.isAnalysis" class="row-state row-lending state-active">解析中</span>
<span v-if="!scope.row.isAnalysis" class="row-state row-warehousing state-active">解析中</span>
<span v-else class="row-state row-binding state-active">已解析</span>
</div>
<div v-else>
@ -104,7 +114,7 @@
</div>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="handleClose">取消</el-button>
<el-button :loading="aiLoading" type="primary" @click="handleUploadConfirm">保存</el-button>
<el-button :loading="aiFileLoading" type="primary" @click="handleUploadConfirm">保存</el-button>
</div>
</div>
</el-dialog>
@ -168,6 +178,7 @@
</div>
</el-dialog>
<!-- 档案新增表单获取 -->
<el-dialog class="preview-dialog ai-preview-dialog" :modal-append-to-body="false" :close-on-click-modal="false" append-to-body :before-close="handleClose" :visible="formVisible" :title="formTitle">
<span class="dialog-right-top" />
<span class="dialog-left-bottom" />
@ -202,21 +213,20 @@
<script>
import CRUD, { presenter, header, crud } from '@crud/crud'
import crudOperation from '@crud/CRUD.operation'
import pagination from '@crud/Pagination'
import Detail from './module/detail'
import PreviewForm from '@/views/components/category/PreviewForm'
import { aiUpload } from '@/utils/upload'
import { getCurrentTime } from '@/utils/index'
import { mapGetters } from 'vuex'
// import { FetchAIResultZhulu } from '@/api/collect/collect'
import { FetchInitCategoryInputFieldByPid, FetchCategoryMenu } from '@/api/system/category/category'
import { getfondMenu } from '@/api/category/category'
import { FetchDoHandleEnterAnalysis } from '@/api/ai/ai'
import { FetchDoHandleEnterAnalysis, FetchDelAssistEnter, FetchHandleEnterAnalysis } from '@/api/ai/ai'
export default {
name: 'Running',
components: { pagination, Detail, PreviewForm },
components: { crudOperation, pagination, Detail, PreviewForm },
cruds() {
return CRUD({ title: 'AI著录', url: 'api/ai/initAssistEnter', crudMethod: {},
optShow: {
@ -229,7 +239,6 @@ export default {
}})
},
mixins: [presenter(), header(), crud()],
props: {
isHistroy: {
type: Boolean,
@ -243,6 +252,7 @@ export default {
},
data() {
return {
permission: {},
selectStatus: null,
stateOptions: [
{
@ -251,70 +261,61 @@ export default {
},
{
value: 0,
label: '解析'
label: '解析'
}
],
aiLoading: false,
permission: {},
aiFileLoading: false,
uploadVisible: false,
nowDate: '',
fileList: [],
aiJsonData: null,
displayedText: '',
typingInterval: null,
typingFinished: false,
currentLineIndex: 0,
shouldContinueFetching: true,
tipContentVisible: false,
categoryVisible: false,
tipContentVisible: false, //
categoryVisible: false, //
fondsMenu: [], //
defaultProps: {
children: 'children',
label: 'label'
},
currentAnId: {},
categoryMenu: [],
fondsMenu: [],
aiAddArchiveCategory: {},
formVisible: false,
aiAddArchiveCategory: {}, //
categoryMenu: [], // form
formVisible: false, //
formTitle: '新增档案',
formPreviewData: [],
isDesFormType: 'arcives',
arcId: null,
isTitleType: 2,
aiResultCaLoading: true,
messages: []
collectLevel: 3,
aiResultCaLoading: true, // ailoading
isDialogClosed: false, //
reader: null //
}
},
computed: {
...mapGetters([
'baseApi'
]),
collectLevel() {
if (this.isTitleType === 2) {
return 1
} else if (this.isTitleType === 3) {
if (this.aiAddArchiveCategory.arrangeType === 1) {
return 3
} else {
return 2
}
} else if (this.isTitleType === 4) {
return 3
} else if (this.isTitleType === 6) {
return 4
}
return null
}
])
// collectLevel() {
// if (this.isTitleType === 2) {
// return 1
// } else if (this.isTitleType === 3) {
// if (this.aiAddArchiveCategory.arrangeType === 1) {
// return 3
// } else {
// return 2
// }
// } else if (this.isTitleType === 4) {
// return 3
// } else if (this.isTitleType === 6) {
// return 4
// }
// return null
// }
},
mounted() {
this.getCategoryDataTree()
},
methods: {
getCategoryDataTree() {
FetchCategoryMenu().then(res => {
this.categoryMenu = res
})
},
[CRUD.HOOK.beforeRefresh]() {
if (this.isHistroy) {
this.crud.query.isHandle = 1
@ -331,15 +332,11 @@ export default {
// table -
tableDoubleClick(row) {
this.$refs.aiCatalogingFile.detailVisible = true
this.$refs.aiCatalogingFile.getFileList()
this.$refs.aiCatalogingFile.getFileList(row)
},
//
async changeAiFile(e) {
// aiJsonData
this.aiJsonData = null
this.currentLineIndex = 0
this.displayedText = ''
this.typingFinished = false
this.typingInterval = null
const selectedFiles = Array.from(e.target.files)
const imageFiles = selectedFiles.filter(file => file.type.startsWith('image/'))
@ -452,11 +449,9 @@ export default {
}
}
},
handleUploadConfirm() {
this.FetchAiFileUplaod(this.fileList)
},
//
FetchAiFileUplaod(files) {
this.aiLoading = true
this.aiFileLoading = true
this.nowDate = getCurrentTime()
const promiseArray = files.map(async(item, index) => {
const json = {}
@ -474,10 +469,7 @@ export default {
})
console.log('promiseArray', promiseArray)
const fileDefault = files.map(item => item.file)
console.log('fileDefault', fileDefault)
// this.selectedCategory.id,
// this.arcId,
Promise.all(promiseArray)
.then((arrayUpload) => {
console.log('arrayUpload', arrayUpload)
@ -485,34 +477,9 @@ export default {
fileDefault,
JSON.stringify(arrayUpload)
).then(res => {
console.log('eee', res)
if (res.data.data !== null) {
console.log(res.data.data)
this.$message({ message: '著录附件传输成功', type: 'success', offset: 8 })
this.crud.toQuery()
// 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 })
}
@ -523,35 +490,9 @@ export default {
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)
//
handleUploadConfirm() {
this.FetchAiFileUplaod(this.fileList)
},
// X
deleteFile(file) {
@ -589,26 +530,19 @@ export default {
}
})
},
handleClose() {
this.uploadVisible = false
this.fileList = []
this.aiLoading = false
this.categoryVisible = false
this.aiAddArchiveCategory = {}
this.tipContentVisible = false
this.formVisible = false
},
//
onClickAddArchive(row) {
console.log('row', row)
this.categoryVisible = true
this.currentAnId = row
this.getfondMenu()
},
//
getfondMenu() {
getfondMenu().then(res => {
this.fondsMenu = this.filterData(this.transformData(res))
})
},
//
filterData(data) {
return data.filter(node => {
if (node.children && node.children.length > 0) {
@ -617,51 +551,6 @@ export default {
return node.isType !== 3 // isType3
})
},
handleNodeClick(val) {
this.aiAddArchiveCategory = val
console.log('val', val)
if (this.aiAddArchiveCategory.arrangeType === 3) {
this.isTitleType = 2
} else if (this.aiAddArchiveCategory.arrangeType === 2) {
this.isTitleType = 3
} else if (this.aiAddArchiveCategory.arrangeType === 1) {
this.isTitleType = 3
}
},
handleCategoryForm() {
this.formTitle = '新增档案 - ' + this.aiAddArchiveCategory.cnName
this.formVisible = true
const params = {
'categoryId': this.aiAddArchiveCategory.id,
'categoryLevel': this.collectLevel
}
FetchInitCategoryInputFieldByPid(params).then(data => {
this.formPreviewData = data
this.isDesFormType = 'arcives'
this.$nextTick(() => {
this.$refs.previewForm.archivesType = 'add'
this.$refs.previewForm.FetchNoFormatField(this.aiAddArchiveCategory.id)
})
this.getDoHandleEnterAnalysis()
})
},
getDoHandleEnterAnalysis() {
const params = {
'categoryId': this.aiAddArchiveCategory.id,
'anId': this.currentAnId.id
}
FetchDoHandleEnterAnalysis(params).then(data => {
console.log(data)
const inputMessage = data.query + '需要提取得内容部分是' + data.context
this.sendMessage(inputMessage)
})
},
handlerArchivesSubmit() {
},
handleTipContent() {
this.tipContentVisible = true
},
transformData(rawData) {
return rawData.map(item => {
return {
@ -703,10 +592,61 @@ export default {
}
})
},
//
handleNodeClick(val) {
this.aiAddArchiveCategory = val
console.log('val', val)
if (this.aiAddArchiveCategory.arrangeType === 3) {
this.isTitleType = 2
} else if (this.aiAddArchiveCategory.arrangeType === 2) {
this.isTitleType = 3
} else if (this.aiAddArchiveCategory.arrangeType === 1) {
this.isTitleType = 3
}
},
// form
getCategoryDataTree() {
FetchCategoryMenu().then(res => {
this.categoryMenu = res
})
},
//
handleCategoryForm() {
this.formTitle = '新增档案 - ' + this.aiAddArchiveCategory.cnName
this.formVisible = true
const params = {
'categoryId': this.aiAddArchiveCategory.id,
'categoryLevel': this.collectLevel
}
FetchInitCategoryInputFieldByPid(params).then(data => {
this.formPreviewData = data
this.isDesFormType = 'arcives'
this.$nextTick(() => {
this.$refs.previewForm.archivesType = 'add'
this.$refs.previewForm.FetchNoFormatField(this.aiAddArchiveCategory.id)
})
this.getDoHandleEnterAnalysis()
})
},
//
getDoHandleEnterAnalysis() {
const params = {
'categoryId': this.aiAddArchiveCategory.id,
'anId': this.currentAnId.id
}
FetchDoHandleEnterAnalysis(params).then(data => {
console.log(data)
const inputMessage = data.query + '需要提取得内容部分是' + data.context
this.sendMessage(inputMessage)
})
},
// deepseek
async sendMessage(inputMessage) {
const linkSrc = process.env.NODE_ENV === 'production' ? window.g.AIDeepSeekUrl : process.env.VUE_APP_AIDEEPSEEK_API
this.displayedText = ''
this.isDialogClosed = false //
try {
const response = await fetch('http://192.168.99.86:11434/api/generate', {
const response = await fetch(linkSrc + '/api/generate', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
@ -723,13 +663,13 @@ export default {
throw new Error(`HTTP error! status: ${response.status}`)
}
const reader = response.body.getReader()
this.reader = response.body.getReader() //
const decoder = new TextDecoder('utf-8')
let done = false
while (!done) {
while (!done && !this.isDialogClosed) { //
this.aiResultCaLoading = false
const { done: isDone, value } = await reader.read()
const { done: isDone, value } = await this.reader.read()
done = isDone
if (done) break
@ -740,7 +680,7 @@ export default {
if (line.trim() !== '') {
try {
const data = JSON.parse(line)
if (data.response) {
if (data.response && !this.isDialogClosed) { //
this.displayedText += data.response
}
} catch (error) {
@ -761,28 +701,89 @@ export default {
this.aiResultCaLoading = false
} finally {
this.aiResultCaLoading = false
console.log('this.displayedText.', this.displayedText)
// JSON
const startIndex = this.displayedText.indexOf('{')
const endIndex = this.displayedText.lastIndexOf('}')
if (startIndex !== -1 && endIndex !== -1 && endIndex > startIndex) {
const jsonStr = this.displayedText.slice(startIndex, endIndex + 1)
console.log('jsonStr', jsonStr)
const jsonData = JSON.parse(jsonStr)
// //
// const filteredJsonData = Object.fromEntries(
// Object.entries(jsonData).filter(([_, value]) => {
// // nullundefined
// return value !== '' && value !== null && value !== undefined
// })
// )
console.log('提取并过滤后的 JSON 数据:', jsonData)
jsonData.fonds_no = this.aiAddArchiveCategory.fondsNo
jsonData.archival_category_code = this.aiAddArchiveCategory.code
console.log('jsonData', jsonData)
this.$refs.previewForm.addOrUpdateForm = jsonData
if (!this.isDialogClosed) {
console.log('this.displayedText.', this.displayedText)
// JSON
const startIndex = this.displayedText.indexOf('{')
const endIndex = this.displayedText.lastIndexOf('}')
if (startIndex !== -1 && endIndex !== -1 && endIndex > startIndex) {
const jsonStr = this.displayedText.slice(startIndex, endIndex + 1)
console.log('jsonStr', jsonStr)
const jsonData = JSON.parse(jsonStr)
console.log('提取并过滤后的 JSON 数据:', jsonData)
jsonData.fonds_no = this.aiAddArchiveCategory.fondsNo
jsonData.archival_category_code = this.aiAddArchiveCategory.code
jsonData.is_entity = 1
console.log('jsonData', jsonData)
this.$refs.previewForm.addOrUpdateForm = jsonData
}
}
this.reader = null //
}
},
//
handlerArchivesSubmit() {
console.log(this.$refs.previewForm.addOrUpdateForm)
// this.$refs.previewForm.submitForm('addOrUpdateForm', this.selectedCategory.id, this.quickPaperArcId)
this.$refs.previewForm.$refs.addOrUpdateForm.validate((valid) => {
if (valid) {
const params = {
'categoryId': this.aiAddArchiveCategory.id,
'assistEnterId': this.currentAnId.id,
'jsonString': JSON.stringify(this.$refs.previewForm.addOrUpdateForm)
}
FetchHandleEnterAnalysis(params).then(data => {
console.log('data', data)
this.$message({ message: data, type: 'success', offset: 8 })
this.crud.toQuery()
})
} else {
console.log('error submit!!')
return false
}
})
},
//
handleTipContent() {
this.tipContentVisible = true
},
handleClose() {
this.uploadVisible = false
this.fileList = []
this.aiFileLoading = false
this.categoryVisible = false
this.aiAddArchiveCategory = {}
this.tipContentVisible = false
this.formVisible = false
this.displayedText = '' // displayedText
this.isDialogClosed = true // true
if (this.reader) {
this.reader.cancel() //
}
},
//
handleDel(data) {
this.$confirm('此操作将删除所选数据' + '<span>你是否还要继续?</span>', '提示', {
confirmButtonText: '继续',
cancelButtonText: '取消',
type: 'warning',
dangerouslyUseHTMLString: true
}).then(() => {
const params = data.map(item => {
return item.id
})
FetchDelAssistEnter(params).then((res) => {
if (res.code !== 500) {
this.$message({ message: '删除成功', type: 'success', offset: 8 })
this.crud.refresh()
} else {
this.$message({ message: '删除失败', type: 'error', offset: 8 })
}
}).catch(err => {
console.log(err)
})
}).catch(() => {
})
}
}
}
@ -809,5 +810,4 @@ pre {
overflow: hidden;
overflow-y: auto;
}
</style>

52
src/views/AIAssistant/AICataloging/running/module/detail.vue

@ -7,42 +7,35 @@
style="min-width: 100%"
height="calc(100vh - 382px)"
>
<el-table-column type="selection" width="55" align="center" />
<el-table-column type="index" label="序号" width="55" align="center" />
<el-table-column prop="file_name" label="文件名称" show-overflow-tooltip min-width="140" />
<el-table-column prop="file_type" label="格式" min-width="60" align="center" />
<el-table-column prop="file_size" label="大小" min-width="85" align="center">
<el-table-column prop="fileName" label="文件名称" show-overflow-tooltip min-width="140" />
<el-table-column prop="fileType" label="格式" min-width="60" align="center" />
<el-table-column prop="fileSize" label="大小" min-width="85" align="center">
<template slot-scope="scope">
{{ getFileSize(scope.row.file_size) }}
{{ getFileSize(scope.row.fileSize) }}
</template>
</el-table-column>
<!-- <el-table-column prop="file_dpi" label="分辨率" min-width="120" align="center">
<template slot-scope="scope">
<div v-if="!scope.row.file_dpi || scope.row.file_dpi === 'null'"> - </div>
<div v-else> {{ scope.row.file_dpi }} </div>
</template>
</el-table-column> -->
<el-table-column prop="file_thumbnail" label="缩略图" min-width="60" align="center">
<el-table-column prop="fileThumbnail" label="缩略图" min-width="60" align="center">
<template slot-scope="scope">
<div v-if="scope.row.file_type === 'jpg' || scope.row.file_type === 'jpeg' || scope.row.file_type === 'png' || scope.row.file_type === 'bmp'|| scope.row.file_type === 'gif'">
<div v-if="scope.row.fileType === 'jpg' || scope.row.fileType === 'jpeg' || scope.row.fileType === 'png' || scope.row.fileType === 'bmp'|| scope.row.fileType === 'gif'">
<i class="fileIcon icon-image" />
</div>
<div v-else-if="scope.row.file_type === 'xlsx' || scope.row.file_type === 'xls'">
<div v-else-if="scope.row.fileType === 'xlsx' || scope.row.fileType === 'xls'">
<i class="fileIcon icon-excel" />
</div>
<div v-else-if="scope.row.file_type === 'docx' || scope.row.file_type === 'doc'">
<div v-else-if="scope.row.fileType === 'docx' || scope.row.fileType === 'doc'">
<i class="fileIcon icon-word" />
</div>
<div v-else-if="scope.row.file_type === 'pdf'">
<div v-else-if="scope.row.fileType === 'pdf'">
<i class="fileIcon icon-pdf" />
</div>
<div v-else-if="scope.row.file_type === 'ppt' || scope.row.file_type === 'pptx'">
<div v-else-if="scope.row.fileType === 'ppt' || scope.row.fileType === 'pptx'">
<i class="fileIcon icon-ppt" />
</div>
<div v-else-if="scope.row.file_type === 'zip' || scope.row.file_type === 'rar'">
<div v-else-if="scope.row.fileType === 'zip' || scope.row.fileType === 'rar'">
<i class="fileIcon icon-zip" />
</div>
<div v-else-if="scope.row.file_type === 'txt'">
<div v-else-if="scope.row.fileType === 'txt'">
<i class="fileIcon icon-txt" />
</div>
<div v-else>
@ -50,16 +43,17 @@
</div>
</template>
</el-table-column>
<el-table-column prop="create_time" label="创建时间" min-width="130" align="center" />
<el-table-column prop="createTime" label="创建时间" min-width="130" align="center" />
</el-table>
<div slot="footer" class="dialog-footer">
<!-- <div slot="footer" class="dialog-footer">
<el-button type="primary" @click="detailVisible=false">确定</el-button>
</div>
</div> -->
</div>
</el-dialog>
</template>
<script>
import { FetchInitAssistEnterTemp } from '@/api/ai/ai'
export default {
name: 'Detail',
components: {
@ -82,8 +76,20 @@ export default {
},
methods: {
getFileList() {
getFileList(row) {
console.log('附件列表')
const params = {
'anId': row.id
}
FetchInitAssistEnterTemp(params).then(data => {
console.log(data)
this.tableData = data
})
},
getFileSize(fileSize) {
const fileSizeInKB = (fileSize / 1024).toFixed(2) + 'kB'
const fileSizeInB = fileSize + 'B'
return (fileSize / 1024) <= 0.01 ? fileSizeInB : fileSizeInKB
}
}
}

462
src/views/AIAssistant/AIDigitalHuman/index.vue

@ -1,28 +1,43 @@
<template>
<div class="app-container category-container" style="position: relative; height: calc(100vh - 140px);">
<iframe id="myIframe" style="position: absolute; top: 0; left: -34%; border: none; z-index: 9;" width="100%" height="100%" :src="frameUrl" />
<!-- http://27.16.218.195:17001/art/sDpbYwwJ/fbx/CdYqVH95/index.html -->
<!-- https://f.3dman.cn/art/sDpbYwwJ/fbx/CdYqVH95/index.html -->
<div class="chat-wrapper">
<div ref="chatContent" class="chat-content">
<Message
v-for="(message, index) in chatMessages"
:key="index"
:content="message.content"
:is-user-message="message.isUserMessage"
:is-loading="message.isLoading"
:is-welcome-message="message.isWelcomeMessage"
:is-typing="message.isTyping"
@stop-typing="handleStopTyping(index)"
/>
<div v-for="(message, index) in messages" :key="index" :class="{'chat-message': true, 'user-message': message.sender === 'user', 'bot-message': message.sender === 'bot' }">
<p>
{{ message.content }}
<span
v-if="message.sender === 'bot'"
:class="{ 'stop-btn': message.isTyping, 'remove-btn':!message.isTyping && message.isInterrupted }"
@click="stopTyping(message, $event)"
>
{{ message.isTyping? '停止输出' : (message.isInterrupted? '用户取消' : '') }}
</span>
</p>
<mavon-editor
v-if="message.sender === 'bot' && message.isReplyFinished &&!message.isInterrupted"
ref="typingContainer"
class="md"
:value="message.editorContent"
:subfield="false"
:default-open="'preview'"
:toolbars-flag="false"
:editable="false"
:scroll-style="true"
:ishljs="true"
/>
</div>
</div>
<div class="chat-send">
<textarea v-model="message" cols="50" rows="7" placeholder="请输入你想咨询的问题" @input="updateSendButtonState" @keypress="handleKeyPress" />
<el-input v-model="inputMessage" type="textarea" placeholder="请输入你想咨询的问题" @input="updateSendButtonState" @keyup.enter="sendMessage" />
<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 style="display: flex; justify-content: flex-end; align-items: center;">
<div :class="isModelDeepseek ? 'model-select' : 'model-select active'" @click="selectModel"><i class="iconfont icon-shendusikao" />深度思考</div>
<span class="send-button" :class="{ 'send-disabled-button': isSendButtonDisabled }" :disabled="isSendButtonDisabled" @click="sendMessage">发送</span>
</div>
</div>
</div>
</div>
@ -30,137 +45,170 @@
</template>
<script>
import Message from './message.vue'
export default {
name: 'AIDigitalHuman',
components: {
Message
},
data() {
return {
frameUrl: '',
message: '',
hotWords: ['什么是“AI辅助著录”', '什么是“AI编研”?'],
modelVal: 'deepseek-r1:14b',
messages: [],
inputMessage: '',
currentBotMessage: '',
isSendButtonDisabled: true,
isBotReplying: false,
chatMessages: [],
hotWords: ['什么是“AI辅助著录”', '什么是“AI编研”?'],
//
mockReplies: {
'什么是“AI辅助著录”': 'AI辅助著录是利用AI数据模型(例如:DeepSeek-R1等),对档案电子原文进行解析及分析操作,再结合当前档案门类的特有著录项目(例如:标题、年度、发文字号等),从电子原文中提取出对应的数据,并自动完成档案表单的著录。辅助著录结束后,管理员任可继续编辑档案信息。',
'什么是“AI编研”?': 'AI编研是利用AI数据模型(例如:DeepSeek-R1等),结合用户上传的文件以及需求,通过对文件的解析、理解,对用户需求的理解、分析,从而智能的生成用户所需要的文本型数据(用户需求,例如:“结合我上传的附件内容,帮我写一篇,AI如何赋能图书馆业务的文章,要求不少于5000字”)'
}
reader: null,
isModelDeepseek: true
}
},
computed: {
},
created() {
},
mounted() {
const linkSrc = process.env.NODE_ENV === 'production' ? window.g.ApiUrl : process.env.VUE_APP_BASE_API
console.log('linkSrc', linkSrc)
const lastColonIndex = linkSrc.lastIndexOf(':')
const firstSlashIndex = linkSrc.indexOf('/', lastColonIndex)
let newUrl
if (firstSlashIndex === -1) {
newUrl = linkSrc.slice(0, lastColonIndex)
} else {
newUrl = linkSrc.slice(0, lastColonIndex) + linkSrc.slice(firstSlashIndex)
}
console.log(newUrl)
this.frameUrl = newUrl + ':17001/art/sDpbYwwJ/fbx/CdYqVH95/index.html'
this.sendWelcomeMessage()
this.updateSendButtonState()
this.messages.push({
sender: 'bot',
content: '您好!请问有什么问题可以帮您解答吗?',
isTyping: false,
isReplyFinished: true,
isInterrupted: false
})
},
methods: {
sendWelcomeMessage() {
const welcomeMessage = '您好!请问有什么问题可以帮您解答吗?'
this.appendMessage(welcomeMessage, false, false, true, false)
selectModel() {
this.isModelDeepseek = !this.isModelDeepseek
if (this.isModelDeepseek) {
this.modelVal = 'deepseek-r1:14b'
} else {
this.modelVal = 'qwen:7b'
}
},
updateSendButtonState() {
this.isSendButtonDisabled = this.message.trim() === '' || this.isBotReplying
this.isSendButtonDisabled = this.inputMessage.trim() === '' || this.isBotReplying
},
sendMessage() {
handleHotWordClick(word) {
if (!this.isBotReplying) {
const message = this.message.trim()
if (message) {
this.appendMessage(message, true, false, false, false)
this.message = ''
this.isSendButtonDisabled = true
this.isBotReplying = true
//
setTimeout(() => {
const loadingIndex = this.chatMessages.findIndex(msg => msg.isLoading)
if (loadingIndex !== -1) {
this.chatMessages.splice(loadingIndex, 1)
}
const botReply = this.mockReplies[message] || '很抱歉,我暂时无法回答这个问题。'
const lastMessageIndex = this.chatMessages.length
this.appendMessage('', false, false, false, true)
let i = 0
const speed = 50
const typeWriter = () => {
const currentMessage = this.chatMessages[lastMessageIndex]
if (!currentMessage.isTyping) {
return
}
if (i < botReply.length) {
currentMessage.content += botReply.charAt(i)
i++
setTimeout(typeWriter, speed)
this.$nextTick(() => {
this.$refs.chatContent.scrollTop = this.$refs.chatContent.scrollHeight
})
} else {
currentMessage.isTyping = false
this.isBotReplying = false
this.updateSendButtonState()
}
}
typeWriter()
}, 1000)
}
}
},
handleKeyPress(event) {
if (event.which === 13) {
event.preventDefault()
this.inputMessage = word
this.sendMessage()
}
},
handleHotWordClick(word) {
this.message = word
this.sendMessage()
},
appendMessage(content, isUserMessage, isLoading, isWelcomeMessage, isTyping = false) {
this.chatMessages.push({
content,
isUserMessage,
isLoading,
isWelcomeMessage,
isTyping
})
this.$nextTick(() => {
this.$refs.chatContent.scrollTop = this.$refs.chatContent.scrollHeight
})
},
handleStopTyping(index) {
const message = this.chatMessages[index]
stopTyping(message, event) {
message.isTyping = false
message.isInterrupted = true
if (this.reader) {
this.reader.cancel()
}
message.isReplyFinished = false //
this.isBotReplying = false
message.editorContent = ''
this.currentBotMessage = ''
this.updateSendButtonState()
},
async sendMessage() {
if (this.inputMessage.trim() === '') return
const linkSrc = process.env.NODE_ENV === 'production' ? window.g.AIDeepSeekUrl : process.env.VUE_APP_AIDEEPSEEK_API
this.messages.push({ sender: 'user', content: this.inputMessage })
this.inputMessage = ''
this.currentBotMessage = ''
try {
this.isBotReplying = true
this.updateSendButtonState()
const response = await fetch(linkSrc + '/api/generate', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: this.modelVal,
prompt: this.messages[this.messages.length - 1].content,
stream: true
})
})
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
this.reader = response.body.getReader()
const decoder = new TextDecoder('utf-8')
let done = false
while (!done) {
const { done: isDone, value } = await this.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: '', isTyping: true, isReplyFinished: false, editorContent: '', isInterrupted: false })
}
this.messages[this.messages.length - 1].content = this.currentBotMessage
}
} catch (error) {
console.error('解析JSON数据出错:', error)
}
}
})
this.$refs.chatContent.scrollTop = this.$refs.chatContent.scrollHeight
}
// <think> </think>
const thinkStartIndex = this.currentBotMessage.indexOf('<think>')
const thinkEndIndex = this.currentBotMessage.indexOf('</think>')
let processedMessage = this.currentBotMessage
if (thinkStartIndex !== -1 && thinkEndIndex !== -1) {
processedMessage = processedMessage.slice(0, thinkStartIndex) + processedMessage.slice(thinkEndIndex + '</think>'.length)
}
// mavon-editor
const lastBotMessage = this.messages.findLast(msg => msg.sender === 'bot')
if (lastBotMessage) {
lastBotMessage.editorContent = processedMessage
lastBotMessage.isReplyFinished = true
}
// bot isTyping false
const lastBotMsgIndex = this.messages.findLastIndex(msg => msg.sender === 'bot')
if (lastBotMsgIndex !== -1) {
this.messages[lastBotMsgIndex].isTyping = false
}
} catch (error) {
console.error('请求出错:', error)
const lastBotMessage = this.messages.findLast(msg => msg.sender === 'bot')
if (lastBotMessage) {
lastBotMessage.content = '请求出错,请稍后再试。'
lastBotMessage.editorContent = '请求出错,请稍后再试。'
lastBotMessage.isReplyFinished = true
}
} finally {
this.reader = null
this.isBotReplying = false
this.updateSendButtonState()
}
}
}
}
</script>
<style lang="scss">
/* ai数字人 start */
.chat-wrapper {
position: absolute;
right: 0;
@ -189,34 +237,34 @@ export default {
border-radius: 16px;
}
.chat-send textarea {
.chat-send .el-textarea__inner {
display: block;
width: calc(100% - 40px);
height: 120px;
font-size: 24px;
line-height: 36px;
width: calc(100%);
height: 136px;
font-size: 16px;
line-height: 24px;
border: none;
padding: 20px;
background-color: transparent;
color: #fff;
background-color: transparent;
}
.send-button-container {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20px 0 0;
padding: 0 10px 0 0;
margin-top: 16px;
}
.hot-word span {
position: relative;
display: inline-block;
font-size: 20px;
font-size: 16px;
color: #fff;
line-height: 50px;
margin: 0 10px;
padding: 6px 16px 6px 48px;
line-height: 40px;
margin: 0 5px;
padding: 4px 14px 4px 42px;
background: rgba(0, 0, 0, 0.2);
border-radius: 28px 8px 28px 28px;
}
@ -226,8 +274,8 @@ export default {
position: absolute;
left: 12px;
top: 50%;
width: 30px;
height: 30px;
width: 24px;
height: 24px;
background: url("~@/assets/images/wenhao.png") no-repeat left top;
background-size: 100% 100%;
transform: translateY(-50%);
@ -241,15 +289,15 @@ export default {
.send-button {
position: relative;
display: inline-block;
width: 140px;
height: 58px;
line-height: 58px;
padding-left: 60px;
font-size: 26px;
width: 110px;
height: 46px;
line-height: 46px;
padding-left: 44px;
font-size: 20px;
text-align: left;
color: #fff;
background: linear-gradient(135deg, #06BFFF 0%, #2B74FF 100%);
border-radius: 58px;
border-radius: 50px;
cursor: pointer;
}
@ -258,10 +306,10 @@ export default {
position: absolute;
left: 16px;
top: 50%;
width: 36px;
height: 36px;
width: 26px;
height: 26px;
background: url("~@/assets/images/index-img8.png") no-repeat left top;
background-size: 36px 36px;
background-size: 100% 100%;
transform: translateY(-50%);
}
@ -270,32 +318,150 @@ export default {
cursor: not-allowed;
}
.run-btn {
.chat-message {
display: flex;
font-size: 16px;
color: #fff;
margin-bottom: 16px;
}
.chat-message.user-message{
justify-content: flex-end;
}
.chat-message.bot-message{
flex-wrap: wrap;
flex-direction: column;
justify-content: flex-start;
}
.chat-message p {
position: relative;
width: 120px;
height: 58px;
line-height: 58px;
padding-left: 60px;
font-size: 26px;
line-height: 24px;
padding: 10px;
background: rgba(0, 0, 0, 0.2);
border-radius: 28px 10px 28px 28px;
max-width: 90%;
width: fit-content; /* 让宽度根据内容自适应 */
word-wrap: break-word; /* 防止长单词溢出 */
}
.chat-message p span.stop-btn {
position: absolute;
left: 4px;
bottom: -40px;
width: 80px;
padding-left: 16px;
height: 26px;
text-align: center;
color: #fff;
background: linear-gradient(135deg, #06BFFF 0%, #2B74FF 100%);
border-radius: 58px;
line-height: 24px;
border: 1px solid #4CA7FF;
color: #4CA7FF;
border-radius: 6px;
font-size: 12px;
cursor: pointer;
margin: 40px 0 0 20px;
}
.run-btn::before {
content: "";
.chat-message p span.stop-btn::before {
content: '';
position: absolute;
left: 30px;
left: 4px;
top: 50%;
width: 36px;
height: 36px;
background: url("~@/assets/images/run.png") no-repeat left top;
background-size: 36px 36px;
transform: translateY(-50%);
width: 18px;
height: 18px;
background: url("~@/assets/images/index-img7.png") no-repeat left top;
background-size: 100% 100%;
margin-top: -9px;
}
.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: 0;
height: 24px;
text-align: center;
line-height: 22px;
font-size: 12px;
cursor: pointer;
}
.chat-message p span.remove-btn::before {
border: none;
color: #999;
background: none;
}
/* ai数字人 end */
.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: 26px;
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>
<style scoped lang="scss">
.model-select{
font-size: 14px;
padding: 0 12px;
height: 30px;
line-height: 26px;
border-radius: 20px;
background-color: rgba(77, 107, 254, 0.40);
border: 1px solid rgba(77, 107, 254, 0.40);
color: rgba(69,166,273,1);
margin-right: 10px;
cursor: default;
}
.model-select:hover{
border: 1px solid rgba(77, 107, 254, 0.2);
background-color: rgba(77, 107, 254, 0.2);
}
.model-select.active{
color: #fff;
border: 1px solid #fff;
background-color: transparent;
}
::v-deep .v-note-wrapper{
width: 100% !important;
margin-top: 20px;
border-radius: 12px;
min-height: auto !important;
}
::v-deep .v-note-wrapper .v-note-panel .v-note-show .v-show-content{
border-radius: 12px;
padding: 0 !important;
}
</style>

404
src/views/AIAssistant/AIDigitalHuman/index2.vue

@ -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>

159
src/views/AIAssistant/AIDigitalHuman/message.vue

@ -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>

26
src/views/components/category/PreviewForm.vue

@ -746,20 +746,18 @@ export default {
if (item.fieldName === 'fonds_no' && (this.isDesFormType === 'arcives' || this.isDesFormType === 'manageArcives')) {
this.$set(this.addOrUpdateForm, item.fieldName, this.selectedCategory.fondsNo)
}
this.$set(this.rules, item.fieldName, [
{
required: !!item.isRequired,
message: (item.isInputClass === 'text' ? '请输入' : '请选择') + item.fieldCnName,
trigger: item.isInputClass === 'text' ? 'blur' : 'change'
}
])
// this.$set(this.rules, 'fileOriginal', [
// {
// required: fas,
// message: '',
// trigger: 'blur'
// }
// ])
const rule = {
required: !!item.isRequired,
message: (item.isInputClass === 'text' ? '请输入' : '请选择') + item.fieldCnName,
trigger: item.isInputClass === 'text' ? 'blur' : 'change'
}
if (item.isColumnLength) {
rule.max = parseInt(item.isColumnLength) //
rule.message = `输入内容最多为${item.isColumnLength}个字符的${item.fieldCnName}`
}
this.$set(this.rules, item.fieldName, [rule])
})
},
//

5
src/views/system/archivesClassify/module/tableList.vue

@ -440,13 +440,14 @@ export default {
}
},
classifyNormalizer(node) {
if (node.childArchivesClass && !node.childArchivesClass.length) {
if ((node.childArchivesClass && !node.childArchivesClass.length) || node.childArchivesClass === null) {
delete node.childArchivesClass
}
return {
id: node.id,
label: node.name,
children: node.childArchivesClass
children: node.childArchivesClass,
isDisabled: node.name === this.form.name
}
}
}

Loading…
Cancel
Save