Browse Source

ai辅助著录

master
xuhuajiao 4 months ago
parent
commit
589a0b1da0
  1. 11
      src/api/ai/ai.js
  2. 9
      src/api/category/category.js
  3. 16
      src/utils/upload.js
  4. 159
      src/views/AIAssistant/AICataloging/Chat.vue
  5. 176
      src/views/AIAssistant/AICataloging/Chat2.vue
  6. 74
      src/views/AIAssistant/AICataloging/deepSeekChat.vue
  7. 4
      src/views/AIAssistant/AICataloging/index.vue
  8. 10
      src/views/AIAssistant/AICataloging/index2.vue
  9. 570
      src/views/AIAssistant/AICataloging/running/index.vue

11
src/api/ai/ai.js

@ -0,0 +1,11 @@
import request from '@/utils/request'
import qs from 'qs'
// AI辅助著录预生成档案
export function FetchDoHandleEnterAnalysis(params) {
return request({
url: 'api/ai/doHandleEnterAnalysis' + '?' + qs.stringify(params, { indices: false }),
method: 'get'
})
}
export default { FetchDoHandleEnterAnalysis }

9
src/api/category/category.js

@ -1,5 +1,12 @@
import request from '@/utils/request'
export function getfondMenu() {
return request({
url: 'api/category/fondMenu',
method: 'get'
})
}
export function getCategoryTree() {
return request({
url: 'api/archives-type/menu',
@ -57,4 +64,4 @@ export function edit(data) {
})
}
export default { add, edit, del }
export default { add, edit, del, getfondMenu }

16
src/utils/upload.js

@ -120,3 +120,19 @@ export function onlineUpload(api, file, params) {
}
return axios.post(api, data, config)
}
// AI
export function aiUpload(api, file, fileJsonString) {
var data = new FormData()
// data.append('files', file) // 之前
for (const item in file) { // 现在
data.append('files', file[item])
}
data.append('fileJsonString', fileJsonString)
const config = {
headers: {
'Authorization': getToken()
}
}
return axios.post(api, data, config)
}

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

@ -0,0 +1,159 @@
<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

@ -0,0 +1,176 @@
<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

@ -0,0 +1,74 @@
<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>

4
src/views/AIAssistant/AICataloging/index.vue

@ -5,8 +5,8 @@
<span class="left-bottom-line" />
<span class="right-bottom-line" />
<ul class="tab-nav">
<li :class="{ 'active-tab-nav': activeIndex == 0 }" @click="changeActiveTab(0)">处理<i /></li>
<li :class="{ 'active-tab-nav': activeIndex == 1 }" @click="changeActiveTab(1)">完成<i /></li>
<li :class="{ 'active-tab-nav': activeIndex == 0 }" @click="changeActiveTab(0)">处理<i /></li>
<li :class="{ 'active-tab-nav': activeIndex == 1 }" @click="changeActiveTab(1)">处理<i /></li>
<!-- 最右侧装饰img -->
<span class="tab-right-img" />
</ul>

10
src/views/AIAssistant/AICataloging/index copy.vue → src/views/AIAssistant/AICataloging/index2.vue

@ -33,6 +33,11 @@
</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" />
@ -95,9 +100,12 @@ 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 },
components: { PreviewForm, DeepSeekChat, Chat },
cruds() {
return [
CRUD({

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

@ -1,16 +1,38 @@
<template>
<div>
<!--工具栏-->
<div class="head-container" style="text-align: right;">
<el-button v-if="!isHistroy" size="mini" @click="uploadVisible=true">
<i class="iconfont icon-shangchuan" />
AI著录文件上传
</el-button>
<div class="head-container" style="display: flex; justify-content: space-between;">
<div v-if="!isHistroy" class="head-search" style="margin-bottom: 0;">
<el-select
v-model="selectStatus"
style="margin-right: 10px; width: 110px;"
placeholder="请选择"
@change="crud.toQuery"
>
<el-option
v-for="item in stateOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-button class="filter-item filter-refresh" size="mini" type="warning" icon="el-icon-refresh-left" @click="resetQuery()">刷新</el-button>
</div>
<div v-if="!isHistroy" style="display: flex; align-items: center;">
<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>
</div>
</div>
<!--表格渲染-->
<el-table
ref="table"
v-loading="crud.loading"
class="archives-table"
:data="crud.data"
row-key="id"
@select="crud.selectChange"
@ -18,39 +40,54 @@
@cell-dblclick="tableDoubleClick"
@selection-change="crud.selectionChangeHandler"
>
<el-table-column label="文件id" prop="id" />
<!-- <el-table-column label="结束时间" prop="endTime" align="center" min-width="160">
<el-table-column label="任务编号" prop="id" />
<el-table-column label="文件数量" prop="fileNum" />
<el-table-column label="创建人" prop="create_by" />
<el-table-column label="创建时间" prop="update_time" align="center" width="160">
<template slot-scope="scope">
<div v-if="scope.row.update_time">{{ scope.row.update_time | parseTime }}</div>
<div v-else>-</div>
</template>
</el-table-column>
<el-table-column label="解析完成时间" prop="analysisTime" align="center" width="160">
<template slot-scope="scope">
<div v-if="scope.row.endTime">{{ scope.row.endTime | parseTime }}</div>
<div v-if="scope.row.analysisTime">{{ scope.row.analysisTime | parseTime }}</div>
<div v-else>-</div>
</template>
</el-table-column> -->
<el-table-column label="解析状态" prop="status" align="center" width="140">
</el-table-column>
<el-table-column label="状态" prop="status" align="center" width="140">
<template slot-scope="scope">
<div v-if="!isHistroy">
<span class="row-state soon-state">解析中</span>
<!-- <span class="row-state end-state">解析完成</span> -->
<span v-if="!scope.row.isAnalysis" class="row-state row-lending state-active">解析中</span>
<span v-else class="row-state row-binding state-active">解析</span>
</div>
<div v-else>
<span v-if="scope.row.endTime" class="row-state end-state">已处理</span>
<span v-if="scope.row.isHandle" class="row-state">已处理</span>
</div>
</template>
</el-table-column>
<el-table-column label="操作" prop="status" align="center" width="140">
<!-- slot-scope="scope" -->
<template>
<!-- v-if="scope.row.status === 1" -->
<el-button size="mini" class="check-btn" style="padding: 5px;">
<el-table-column v-if="!isHistroy" label="操作" prop="status" align="center" width="140">
<template slot-scope="scope">
<el-button v-if="scope.row.isAnalysis" size="mini" class="check-btn" style="padding: 5px;" @click="onClickAddArchive(scope.row)">
<i class="iconfont icon-tianjiawenjian" />
新增档案
</el-button>
</template>
</el-table-column>
<el-table-column v-if="isHistroy" label="处理完成时间" prop="handleTime" align="center" width="160">
<template slot-scope="scope">
<div v-if="scope.row.handleTime">{{ scope.row.handleTime | parseTime }}</div>
<div v-else>-</div>
</template>
</el-table-column>
</el-table>
<!--分页组件-->
<pagination v-if="crud.data.length!==0" />
<!-- 文件详情 -->
<Detail ref="aiCatalogingFile" :is-histroy="isHistroy" />
<!-- 文件上传 -->
<el-dialog class="fileUpload-dialog" title="文件上传" :close-on-click-modal="false" :modal-append-to-body="false" append-to-body :visible.sync="uploadVisible">
<div class="setting-dialog">
<div class="upload-container">
@ -66,12 +103,100 @@
</div>
</div>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="uploadVisible = false">取消</el-button>
<el-button type="text" @click="handleClose">取消</el-button>
<el-button :loading="aiLoading" type="primary" @click="handleUploadConfirm">保存</el-button>
</div>
</div>
</el-dialog>
<!-- 说明提示 -->
<el-dialog class="check-tip-dialog" title="说明" append-to-body :close-on-click-modal="false" :modal-append-to-body="false" :visible.sync="tipContentVisible" :before-close="handleClose">
<span class="dialog-right-top" />
<span class="dialog-left-bottom" />
<div class="setting-dialog">
<div class="dialog-delt">
<div style="font-size: 14px; color: #0C0E1E; line-height: 20px;">
应盘 =
<span style="color: #12C37A;">已盘</span> +
<span style="color: #12C37A;">已借</span> +
<span style="color: #FF8329;">错位</span> +
<span style="color: #ED4A41;">未盘</span> +
<span style="color: #ED4A41;">异常</span>
<span style="display: inline-block; padding-left: 20px;"><i style="font-style:normal; color: #8B43F0; padding: 0 2px;">多盘</i>为非本盘点单计数</span>
</div>
<ul class="checkTip">
<li><span style="font-weight:bold;">应盘</span><p>当前盘点单中涉及到的全部档案文件或档案盒的数量</p></li>
<li><span style="font-weight:bold;color: #2ECAAC; border-color: #B1EBDF; background-color: #E8F8F5;">已盘</span><p>正确的档案文件或档案盒在正确的位置被盘点到</p></li>
<li><span style="font-weight:bold;color: #12C37A; border-color: #B1EBDF; background-color: #E8F8F5;">已借</span><p>在当前盘点单中已经借出的档案文件按盒盘点时无已借数量</p></li>
<li><span style="font-weight:bold;color: #FF8329; border-color: #FEBD98; background-color: #FFF3E5;">错位</span><p>在当前盘点单中盘点到不属于当前位置的档案文件或档案盒</p></li>
<li><span style="font-weight:bold;color: #ED4A41; border-color: #FBB5B5; background-color: #FCE9E9;">未盘</span><p>在当前盘点单中未被盘点到的档案文件或档案盒默认状态</p></li>
<li><span style="font-weight:bold;color: #ED4A41; border-color: #FBB5B5; background-color: #FCE9E9;">异常</span><p>在按件盘点时已借档案被盘点到盘点时自动变更状态在按盒盘点时盒内档案数量异常盘点时手动变更状态</p></li>
<li><span style="font-weight:bold;color: #8B43F0; border-color: #CAA4FF; background-color: #F4EDFF;">多盘</span><p>不在当前盘点单中但被盘点到的档案文件或档案盒</p></li>
</ul>
</div>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click.native="tipContentVisible = false">确定</el-button>
</div>
</div>
</el-dialog>
<!-- 门类选择 -->
<el-dialog class="fileUpload-dialog" title="选择门类" :close-on-click-modal="false" :modal-append-to-body="false" append-to-body :visible.sync="categoryVisible">
<div class="setting-dialog">
<div class="tree-scroll">
<el-tree ref="categroyTree" class="arc-tree arc-tree-01" :data="fondsMenu" :props="defaultProps" node-key="id" :expand-on-click-node="false" :default-expand-all="true" highlight-current @node-click="handleNodeClick">
<span slot-scope="{ node, data }" class="custom-tree-node">
<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>
</span>
</el-tree>
</div>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="handleClose">取消</el-button>
<el-button :disabled="aiAddArchiveCategory.isType!==2" type="primary" @click="handleCategoryForm">下一步</el-button>
</div>
</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" />
<div class="setting-dialog">
<div style="display: flex; justify-content: flex-start;">
<PreviewForm
v-if="formPreviewData.length"
ref="previewForm"
:is-has-code="true"
:is-disabled="false"
:form-preview-data.sync="formPreviewData"
:selected-category="aiAddArchiveCategory"
:arc-id="arcId"
:is-des-form-type="isDesFormType"
:is-title-type="isTitleType"
:collect-level="collectLevel"
:category-menu="categoryMenu"
@close-dialog="handleClose"
/>
<div v-loading="aiResultCaLoading" style="flex: 1; margin-left: 10px; ">
<pre ref="typingContainer" v-highlightjs="displayedText">{{ displayedText }}</pre>
</div>
</div>
<div slot="footer" class="dialog-footer" style="margin-top: 85px !important;">
<el-button type="primary" @click="handlerArchivesSubmit">保存</el-button>
</div>
</div>
</el-dialog>
</div>
</template>
@ -79,17 +204,21 @@
import CRUD, { presenter, header, crud } from '@crud/crud'
import pagination from '@crud/Pagination'
import Detail from './module/detail'
import PreviewForm from '@/views/components/category/PreviewForm'
import { archivesUpload } from '@/utils/upload'
import { aiUpload } from '@/utils/upload'
import { getCurrentTime } from '@/utils/index'
import { mapGetters } from 'vuex'
import { FetchAIResultZhulu } from '@/api/collect/collect'
// 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'
export default {
name: 'Running',
components: { pagination, Detail },
components: { pagination, Detail, PreviewForm },
cruds() {
return CRUD({ title: '处理中', url: 'api/flowable/getFlowList', crudMethod: {},
return CRUD({ title: 'AI著录', url: 'api/ai/initAssistEnter', crudMethod: {},
optShow: {
add: false,
edit: false,
@ -107,8 +236,24 @@ export default {
default: false
}
},
provide() {
return {
parentsData: this
}
},
data() {
return {
selectStatus: null,
stateOptions: [
{
value: 1,
label: '已解析'
},
{
value: 0,
label: '未解析'
}
],
aiLoading: false,
permission: {},
uploadVisible: false,
@ -119,23 +264,69 @@ export default {
typingInterval: null,
typingFinished: false,
currentLineIndex: 0,
shouldContinueFetching: true
shouldContinueFetching: true,
tipContentVisible: false,
categoryVisible: false,
defaultProps: {
children: 'children',
label: 'label'
},
currentAnId: {},
categoryMenu: [],
fondsMenu: [],
aiAddArchiveCategory: {},
formVisible: false,
formTitle: '新增档案',
formPreviewData: [],
isDesFormType: 'arcives',
arcId: null,
isTitleType: 2,
aiResultCaLoading: true,
messages: []
}
},
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
}
},
mounted() {
this.getCategoryDataTree()
},
methods: {
getCategoryDataTree() {
FetchCategoryMenu().then(res => {
this.categoryMenu = res
})
},
[CRUD.HOOK.beforeRefresh]() {
if (this.isHistroy) {
this.crud.query.isEnd = true
this.crud.query.isHandle = 1
} else {
this.crud.query.isEnd = false
this.crud.query.isHandle = 0
}
this.crud.query.isAnalysis = this.selectStatus
},
resetQuery() {
this.selectStatus = null
this.crud.query = {}
this.crud.toQuery()
},
// table -
tableDoubleClick(row) {
@ -143,15 +334,12 @@ export default {
this.$refs.aiCatalogingFile.getFileList()
},
async changeAiFile(e) {
// aiJsonData
// 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/'))
@ -160,6 +348,7 @@ export default {
//
if (imageFiles.length > 0 && nonImageFiles.length > 0) {
this.$message.error('不能同时选择图片和其他类型文件,请重新选择')
e.target.value = '' //
return
}
@ -167,21 +356,25 @@ export default {
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
// }
const maxSize = 10 * 1024 * 1024 // 10M
let totalImageSize = 0
//
for (const existingFile of existingImageFiles) {
totalImageSize += existingFile.size
}
//
for (const file of imageFiles) {
totalImageSize += file.size
}
if (totalImageSize > maxSize) {
this.$message.error('所选图片文件总大小超过 10M,请重新选择')
e.target.value = '' //
return
}
if (existingNonImageFiles.length > 0) {
//
this.fileList = this.fileList.filter(item => item.formatType === 'image')
@ -212,8 +405,16 @@ export default {
this.fileList.push(fileInfo)
}
this.FetchAiFileUplaod(this.fileList)
} else if (nonImageFiles.length > 0) {
const maxSingleNonImageSize = 10 * 1024 * 1024 // 10M
for (const file of nonImageFiles) {
if (file.size > maxSingleNonImageSize) {
this.$message.error(`文件 ${file.name} 大小超过 10M,请重新选择`)
e.target.value = '' //
return
}
}
if (existingNonImageFiles.length > 0) {
//
this.fileList = this.fileList.filter(item => item.formatType === 'image')
@ -225,6 +426,7 @@ export default {
//
if (nonImageFiles.length > 1) {
this.$message.error('非图片文件最多只能选择 1 个,请重新选择')
e.target.value = '' //
return
}
@ -272,41 +474,49 @@ 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) => {
archivesUpload(this.baseApi + '/api/collect/uploadAssistEnterFiles',
console.log('arrayUpload', arrayUpload)
aiUpload(this.baseApi + '/api/ai/uploadAssistEnterFiles',
fileDefault,
JSON.stringify(arrayUpload)
).then(res => {
console.log('eee', 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()
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 })
}
this.handleClose()
})
})
.catch((error) => {
@ -348,7 +558,7 @@ export default {
const index = this.fileList.indexOf(file)
this.fileList.splice(index, 1)
if (this.fileList.length !== 0) {
// archivesUpload(this.baseApi + '/api/collect/uploadFiles', this.fileList, this.selectedCategory.id).then(res => {
// aiUpload(this.baseApi + '/api/collect/uploadFiles', this.fileList, this.selectedCategory.id).then(res => {
// if (res.data.code === 200) {
// this.filePath = res.data.data
// }
@ -378,10 +588,226 @@ export default {
resolve({ width, height })
}
})
},
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) {
node.children = this.filterData(node.children) //
}
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 {
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) : []
}
})
},
async sendMessage(inputMessage) {
this.displayedText = ''
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: inputMessage,
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) {
this.aiResultCaLoading = false
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.displayedText += data.response
}
} catch (error) {
console.error('解析JSON数据出错:', error)
}
}
})
//
const container = this.$refs.typingContainer
if (container) {
container.scrollTop = container.scrollHeight
}
}
} catch (error) {
console.error('请求出错:', error)
this.displayedText = '请求出错,请稍后再试。'
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
}
}
}
}
}
</script>
<style lang="scss" scoped>
.ai-preview-dialog{
::v-deep .el-dialog{
width: 1300px !important;
.preview-content{
width: 728px !important;
}
}
}
pre {
background-color: #f4f4f4;
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
white-space: pre-wrap;
word-wrap: break-word;
height: calc(100vh - 330px);
overflow: hidden;
overflow-y: auto;
}
</style>
Loading…
Cancel
Save