init
This commit is contained in:
334
src/views/knowledge/aiChatRecord/index.vue
Normal file
334
src/views/knowledge/aiChatRecord/index.vue
Normal file
@@ -0,0 +1,334 @@
|
||||
<template>
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<el-row v-show="showSearch">
|
||||
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
|
||||
<el-form-item label="知识库名" prop="title">
|
||||
<el-select class="!w-[150px]" placeholder="请选择知识库" v-model="state.queryForm.datasetId">
|
||||
<el-option :key="index" :label="item.name" :value="item.id" v-for="(item, index) in datasetList">
|
||||
{{ item.name }}
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="问题" prop="questionText">
|
||||
<el-input placeholder="请输入问题" v-model="state.queryForm.questionText" />
|
||||
</el-form-item>
|
||||
<el-form-item label="标注" prop="standardFlag">
|
||||
<el-select class="!w-[150px]" v-model="state.queryForm.standardFlag" placeholder="是否标注">
|
||||
<el-option v-for="item in yes_no_type" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="调用结果" prop="llmFlag">
|
||||
<el-select class="!w-[150px]" v-model="state.queryForm.llmFlag" placeholder="请选择交互结果">
|
||||
<el-option v-for="item in llm_use_status" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button icon="search" type="primary" @click="getDataList"> 查询 </el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<div class="mb8" style="width: 100%">
|
||||
<el-button plain :disabled="multiple" icon="Delete" type="primary" v-auth="'knowledge_aiChatRecord_del'" @click="handleDelete(selectObjs)">
|
||||
删除
|
||||
</el-button>
|
||||
<right-toolbar
|
||||
v-model:showSearch="showSearch"
|
||||
:export="'knowledge_aiChatRecord_export'"
|
||||
@exportExcel="exportExcel"
|
||||
class="ml10 mr20"
|
||||
style="float: right"
|
||||
@queryTable="getDataList"
|
||||
></right-toolbar>
|
||||
</div>
|
||||
</el-row>
|
||||
|
||||
<splitpanes>
|
||||
<pane size="65">
|
||||
<el-table
|
||||
:data="state.dataList"
|
||||
v-loading="state.loading"
|
||||
border
|
||||
:cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle"
|
||||
@selection-change="selectionChangHandle"
|
||||
@row-click="rowClickHandle"
|
||||
@sort-change="sortChangeHandle"
|
||||
>
|
||||
<el-table-column type="selection" width="40" align="center" />
|
||||
<el-table-column type="index" label="#" width="50" />
|
||||
<el-table-column width="150" prop="datasetName" label="所属知识库" show-overflow-tooltip />
|
||||
<el-table-column width="100" prop="username" label="用户标识" show-overflow-tooltip />
|
||||
<el-table-column prop="questionText" label="问题" show-overflow-tooltip />
|
||||
<el-table-column width="100" prop="standardFlag" label="已标注" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<dict-tag :options="yes_no_type" :value="scope.row.standardFlag"></dict-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column width="100" prop="llmFlag" label="交互成功" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<dict-tag :options="llm_use_status" :value="scope.row.llmFlag"></dict-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination" />
|
||||
</pane>
|
||||
<pane size="35">
|
||||
<div class="flex items-center justify-center h-full pl-4 bg-slate-50/30">
|
||||
<div class="mx-auto w-full max-w-[580px] h-full">
|
||||
<div
|
||||
v-if="!selectRow.recordId"
|
||||
class="flex flex-col items-center justify-center h-full p-8 bg-white border shadow-lg rounded-2xl border-slate-200/60 shadow-slate-100/50 backdrop-blur-sm"
|
||||
>
|
||||
<div class="flex items-center justify-center w-16 h-16 mb-6 border bg-gradient-to-br from-indigo-50 to-blue-50 rounded-2xl border-indigo-100/50">
|
||||
<svg class="w-8 h-8 text-indigo-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<h3 class="mb-2 text-lg font-semibold text-slate-700">请选择记录</h3>
|
||||
<p class="text-sm text-slate-500">从左侧列表中选择一条对话记录查看详情</p>
|
||||
</div>
|
||||
</div>
|
||||
<template v-else>
|
||||
<div class="h-full overflow-hidden bg-white border shadow-lg rounded-2xl border-slate-200/60 shadow-slate-100/50 backdrop-blur-sm">
|
||||
<!-- 顶部标注切换区域 -->
|
||||
<div class="sticky top-0 z-20 flex items-center justify-between px-6 py-4 border-b bg-gradient-to-r from-blue-500/5 to-indigo-500/5 backdrop-blur-md border-blue-100/50" v-if="selectRow.llmFlag === '1'">
|
||||
<div class="flex items-center space-x-3">
|
||||
<div class="relative">
|
||||
<div class="w-3 h-3 rounded-full bg-gradient-to-r from-blue-500 to-indigo-500 animate-pulse"></div>
|
||||
<div class="absolute inset-0 w-3 h-3 rounded-full bg-gradient-to-r from-blue-500 to-indigo-500 animate-ping opacity-20"></div>
|
||||
</div>
|
||||
<span class="text-lg font-bold text-transparent bg-gradient-to-r from-blue-600 to-indigo-600 bg-clip-text">智能标注</span>
|
||||
</div>
|
||||
<div class="flex items-center px-4 py-2 space-x-4 border rounded-full bg-white/60 border-blue-100/50 backdrop-blur-sm">
|
||||
<label class="text-sm font-medium cursor-pointer select-none text-slate-700">标注为正确答案</label>
|
||||
<el-switch
|
||||
v-model="selectRow.standardFlag"
|
||||
@change="editHandle"
|
||||
:active-value="'1'"
|
||||
:inactive-value="'0'"
|
||||
class="scale-110 drop-shadow-sm"
|
||||
></el-switch>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<div class="h-full overflow-auto">
|
||||
<div class="p-2 space-y-8">
|
||||
<!-- 用户提问区域 -->
|
||||
<div class="relative group">
|
||||
<div v-if="selectRow.llmFlag === '2'" class="relative">
|
||||
<div class="p-6 transition-all duration-300 border shadow-sm bg-gradient-to-br from-white to-slate-50/30 rounded-2xl border-slate-200/50 hover:shadow-md hover:border-slate-300/50">
|
||||
<div class="leading-relaxed prose-sm prose max-w-none text-slate-700" v-html="matchResult" @click="handleChildClick" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="relative">
|
||||
<div class="p-6 transition-all duration-300 border shadow-sm bg-gradient-to-br from-blue-50/30 to-indigo-50/30 rounded-2xl border-blue-200/50 hover:shadow-md hover:border-blue-300/50 hover:bg-gradient-to-br hover:from-blue-50/50 hover:to-indigo-50/50">
|
||||
<div class="leading-relaxed prose-sm prose max-w-none text-slate-800 font-medium">
|
||||
{{ selectRow.questionText }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200">
|
||||
<div class="flex items-center px-2 py-1 text-xs font-medium text-blue-600 bg-blue-50/80 rounded-full border border-blue-200/50 backdrop-blur-sm">
|
||||
<svg class="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
问题
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 大模型答案区域 -->
|
||||
<div class="relative group">
|
||||
<div class="flex items-center mb-5 space-x-3">
|
||||
<div class="relative">
|
||||
<div class="flex items-center justify-center w-10 h-10 border shadow-sm bg-gradient-to-br from-blue-50 to-indigo-50 rounded-xl border-blue-100/50">
|
||||
<svg class="w-5 h-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="absolute w-3 h-3 border-2 border-white rounded-full shadow-sm -top-1 -right-1 bg-blue-400"></div>
|
||||
</div>
|
||||
<div class="flex items-center space-x-3">
|
||||
<div>
|
||||
<h3 class="text-lg font-bold text-slate-800">AI 智能回答</h3>
|
||||
</div>
|
||||
<div class="flex items-center px-3 py-1 border rounded-full bg-gradient-to-r from-blue-50 to-indigo-50 border-blue-100/50">
|
||||
<svg class="w-3 h-3 text-blue-500 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
<span class="text-xs font-medium text-blue-600">支持Markdown</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="overflow-hidden transition-all duration-300 border shadow-sm bg-gradient-to-br from-white to-slate-50/30 rounded-2xl border-slate-200/50 hover:shadow-md hover:border-slate-300/50">
|
||||
<ai-editor
|
||||
v-model="selectRow.answerText"
|
||||
output="text"
|
||||
:hide-menubar="true"
|
||||
:hide-toolbar="true"
|
||||
placeholder="AI回答将在此处显示,支持Markdown格式..."
|
||||
:minHeight="480"
|
||||
class="bg-transparent border-0"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部渐变遮罩 -->
|
||||
<div class="sticky bottom-0 h-6 pointer-events-none bg-gradient-to-t from-white via-white/80 to-transparent"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</pane>
|
||||
</splitpanes>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="systemAiChatRecord">
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table';
|
||||
import { fetchList, delObjs, putObj } from '/@/api/knowledge/aiChatRecord';
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message';
|
||||
import { useDict } from '/@/hooks/dict';
|
||||
import { fetchDataList } from '/@/api/knowledge/aiDataset';
|
||||
import { addObj, getObj, testObj } from '/@/api/admin/sensitive';
|
||||
import AiEditor from '/@/components/AiEditor/index.vue';
|
||||
|
||||
// 定义查询字典
|
||||
const { yes_no_type, llm_use_status } = useDict('yes_no_type', 'llm_use_status');
|
||||
|
||||
// 定义变量内容
|
||||
const matchResult = ref();
|
||||
|
||||
const selectRow = reactive({
|
||||
recordId: '',
|
||||
answerText: '',
|
||||
questionText: '',
|
||||
standardFlag: 0,
|
||||
qdrantId: '',
|
||||
llmFlag: '' as '0' | '1' | '2',
|
||||
});
|
||||
|
||||
// 搜索变量
|
||||
const queryRef = ref();
|
||||
const showSearch = ref(true);
|
||||
// 多选变量
|
||||
const selectObjs = ref([]) as any;
|
||||
const multiple = ref(true);
|
||||
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: {},
|
||||
pageList: fetchList,
|
||||
descs: ['create_time'],
|
||||
});
|
||||
|
||||
// table hook
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, downBlobFile, tableStyle } = useTable(state);
|
||||
|
||||
// 清空搜索条件
|
||||
const resetQuery = () => {
|
||||
// 清空搜索条件
|
||||
queryRef.value?.resetFields();
|
||||
// 清空多选
|
||||
selectObjs.value = [];
|
||||
getDataList();
|
||||
};
|
||||
|
||||
// 导出excel
|
||||
const exportExcel = () => {
|
||||
downBlobFile('/knowledge/aiChatRecord/export', Object.assign(state.queryForm, { ids: selectObjs }), 'aiChatRecord.xlsx');
|
||||
};
|
||||
|
||||
// 多选事件
|
||||
const selectionChangHandle = (objs: { recordId: string }[]) => {
|
||||
selectObjs.value = objs.map(({ recordId }) => recordId);
|
||||
multiple.value = !objs.length;
|
||||
};
|
||||
|
||||
const datasetList = ref<Array<{ id: string; name: string }>>([]);
|
||||
const getDatasetList = async () => {
|
||||
const { data } = await fetchDataList();
|
||||
datasetList.value = data;
|
||||
};
|
||||
|
||||
// 删除操作
|
||||
const handleDelete = async (ids: string[]) => {
|
||||
try {
|
||||
await useMessageBox().confirm('此操作将永久删除');
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await delObjs(ids);
|
||||
getDataList();
|
||||
useMessage().success('删除成功');
|
||||
} catch (err: any) {
|
||||
getDataList();
|
||||
useMessage().error(err.msg);
|
||||
}
|
||||
};
|
||||
|
||||
// 表格行点击
|
||||
const rowClickHandle = async (row: any) => {
|
||||
Object.assign(selectRow, row);
|
||||
|
||||
if (row.llmFlag === '2') {
|
||||
const { data } = await testObj({ sensitiveWord: row.questionText });
|
||||
// 要处理的字符串
|
||||
matchResult.value = row.questionText;
|
||||
// 遍历关键词数组,并进行替换
|
||||
data.forEach((word: string) => {
|
||||
let regex = new RegExp(word, 'g');
|
||||
matchResult.value = matchResult.value.replace(
|
||||
regex,
|
||||
`
|
||||
<div class="tooltip tooltip-open tooltip-bottom" data-tip="加入白名单">
|
||||
<a class="link link-error" @click="$emit('click-child')">${word}</a>
|
||||
</div>
|
||||
`
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const editHandle = async () => {
|
||||
putObj(selectRow)
|
||||
.then(() => {
|
||||
getDataList();
|
||||
useMessage().success('操作成功');
|
||||
getDataList();
|
||||
})
|
||||
.catch((err: any) => {
|
||||
useMessage().error(err.msg);
|
||||
});
|
||||
};
|
||||
|
||||
const handleChildClick = async (event: any) => {
|
||||
try {
|
||||
if (event.target.tagName.toLowerCase() === 'a' && event.target.classList.contains('link-error')) {
|
||||
const { data } = await getObj({ sensitiveWord: event.target.innerText, sensitiveType: '1' });
|
||||
if (data) {
|
||||
useMessage().error('数据已存在,请勿重新添加');
|
||||
return;
|
||||
}
|
||||
await addObj({ sensitiveWord: event.target.innerText, sensitiveType: '1' });
|
||||
useMessage().success('白名单添加成功');
|
||||
}
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getDatasetList();
|
||||
});
|
||||
</script>
|
||||
Reference in New Issue
Block a user