<!--知识库文档列表-->
<template>
<div class="p-2">
<BasicModal
wrapClassName="airag-knowledge-doc"
destroyOnClose
@register="registerModal"
:canFullscreen="false"
defaultFullscreen
:title="title"
:footer="null"
>
<a-layout style="height: 100%">
<a-layout-sider :style="siderStyle">
<a-menu v-model:selectedKeys="selectedKeys" mode="vertical" style="border: none" :items="menuItems" @click="handleMenuClick" />
</a-layout-sider>
<a-layout-content :style="contentStyle">
<div v-if="selectedKey === 'document'">
<div class="search-header" style="text-align: left;">
<a-space align="center" wrap>
<a-input
class="search-input"
v-model:value="searchText"
placeholder="请输入文档名称,回车搜索"
@pressEnter="searchTextEnter"
/>
<template v-if="selectedRows.length > 0">
<div>
<span>已进入多选模式,当前选中</span>
<a style="margin: 0 4px;"> {{ selectedRows.length }} </a>
<span>条文档</span>
</div>
<div>
<a @click="onClearSelected">清空选择</a>
<a-divider type="vertical"/>
<a @click="onDeleteBatch">批量删除</a>
</div>
</template>
</a-space>
</div>
<a-row :span="24" class="knowledge-row">
<a-col :xxl="4" :xl="6" :lg="6" :md="6" :sm="12" :xs="24">
<a-card class="add-knowledge-card" :bodyStyle="cardBodyStyle">
<span style="line-height: 18px;font-weight: 500;color:#676f83;font-size: 12px">
创建文档
<a-tooltip title="知识库文档">
<a style="color: unset" href="https://help.jeecg.com/aigc/guide/knowledge#4-%E7%9F%A5%E8%AF%86%E5%BA%93%E6%96%87%E6%A1%A3" target="_blank">
<Icon style="position:relative;top:1px" icon="ant-design:question-circle-outlined" size="14"></Icon>
</a>
</a-tooltip>
</span>
<div class="add-knowledge-doc" @click="handleCreateText">
<Icon icon="ant-design:form-outlined" size="13"></Icon><span>手动录入</span>
</div>
<div class="add-knowledge-doc" @click="handleCreateUpload">
<Icon icon="ant-design:cloud-upload-outlined" size="13"></Icon><span>文件上传</span>
</div>
<div class="add-knowledge-doc">
<a-upload
accept=".zip"
name="file"
:data="{ knowId: knowledgeId }"
:showUploadList="false"
:headers="headers"
:beforeUpload="beforeUpload"
:action="uploadUrl"
@change="handleUploadChange"
style="width: 100%;"
>
<div style="display: flex;width: 100%">
<Icon style="margin-left: 0;color:#676f83" icon="ant-design:project-outlined" size="13"></Icon>
<span style="color:#676f83;font-size: 12px">文档库上传</span>
</div>
</a-upload>
</div>
<a-dropdown placement="bottomRight" :trigger="['click']">
<div class="ant-dropdown-link pointer operation" @click.prevent.stop>
<Icon icon="ant-design:ellipsis-outlined" size="16"></Icon>
</div>
<template #overlay>
<a-menu>
<a-menu-item key="delete" @click="onDeleteAll">
<Icon icon="ant-design:delete-outlined" size="16"></Icon>
清空文档
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</a-card>
</a-col>
<a-col :xxl="4" :xl="6" :lg="6" :md="6" :sm="12" :xs="24" v-for="item in knowledgeDocDataList">
<a-card :class="['knowledge-card','pointer', {
checked: item.__checked,
}]" @click="handleEdit(item)">
<div class="knowledge-checkbox">
<a-checkbox v-model:checked="item.__checked" @click.stop=""/>
</div>
<div class="knowledge-header">
<div class="header-text flex">
<Icon v-if="item.type==='text'" icon="ant-design:file-text-outlined" size="32" color="#00a7d0"></Icon>
<Icon v-if="item.type==='file' && getFileSuffix(item.metadata) === 'pdf'" icon="ant-design:file-pdf-outlined" size="32" color="rgb(211, 47, 47)"></Icon>
<Icon v-if="item.type==='file' && getFileSuffix(item.metadata) === 'docx'" icon="ant-design:file-word-outlined" size="32" color="rgb(68, 138, 255)"></Icon>
<Icon v-if="item.type==='file' && getFileSuffix(item.metadata) === 'pptx'" icon="ant-design:file-ppt-outlined" size="32" color="rgb(245, 124, 0)"></Icon>
<Icon v-if="item.type==='file' && getFileSuffix(item.metadata) === 'xlsx'" icon="ant-design:file-excel-outlined" size="32" color="rgb(98, 187, 55)"></Icon>
<Icon v-if="item.type==='file' && getFileSuffix(item.metadata) === 'txt'" icon="ant-design:file-text-outlined" size="32" color="#00a7d0"></Icon>
<Icon v-if="item.type==='file' && getFileSuffix(item.metadata) === 'md'" icon="ant-design:file-markdown-outlined" size="32" color="#292929"></Icon>
<Icon v-if="item.type==='file' && getFileSuffix(item.metadata) === ''" icon="ant-design:file-unknown-outlined" size="32" color="#f5f5dc"></Icon>
<span class="ellipsis header-title">{{ item.title }}</span>
</div>
</div>
<div class="card-description">
<span>{{ item.content }}</span>
</div>
<div class="flex" style="justify-content: space-between">
<div class="card-text">
状态:
<div v-if="item.status==='complete'" class="card-text-status">
<Icon icon="ant-design:check-circle-outlined" size="16" color="#56D1A7"></Icon>
<span class="ml-2">已完成</span>
</div>
<div v-else-if="item.status==='building'" class="card-text-status">
<a-spin v-if="item.loading" :spinning="item.loading" :indicator="indicator"></a-spin>
<span class="ml-2">构建中</span>
</div>
<div v-else-if="item.status==='draft'" class="card-text-status">
<img src="../icon/draft.png" style="width: 16px;height: 16px" />
<span class="ml-2">草稿</span>
</div>
<a-tooltip v-else-if="item.status==='failed'" :title="getDocFailedReason(item)">
<div class="card-text-status">
<Icon icon="ant-design:close-circle-outlined" size="16" color="#FF4D4F"></Icon>
<span class="ml-2">失败</span>
</div>
</a-tooltip>
</div>
<a-dropdown placement="bottomRight" :trigger="['click']">
<div class="ant-dropdown-link pointer operation" @click.prevent.stop>
<Icon icon="ant-design:ellipsis-outlined" size="16"></Icon>
</div>
<template #overlay>
<a-menu>
<a-menu-item key="vectorization" @click="handleVectorization(item.id)">
<Icon icon="ant-design:retweet-outlined" size="16"></Icon>
向量化
</a-menu-item>
<a-menu-item key="edit" @click="handleEdit(item)">
<Icon icon="ant-design:edit-outlined" size="16"></Icon>
编辑
</a-menu-item>
<a-menu-item key="delete" @click="handleDelete(item.id)">
<Icon icon="ant-design:delete-outlined" size="16"></Icon>
删除
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</div>
</a-card>
</a-col>
</a-row>
<Pagination
v-if="knowledgeDocDataList.length > 0"
:current="pageNo"
:page-size="pageSize"
:page-size-options="pageSizeOptions"
:total="total"
:showQuickJumper="true"
:showSizeChanger="true"
@change="handlePageChange"
class="list-footer"
size="small"
:show-total="() => `共${total}条` "
/>
</div>
<div v-if="selectedKey === 'hitTest'" style="padding: 16px">
<a-spin :spinning="spinning">
<div class="hit-test">
<h4>命中测试</h4>
<span>针对用户提问调试段落匹配情况,保障回答效果。</span>
</div>
<div class="content">
<div class="content-title">
<Avatar v-if="hitShowSearchText" :size="35" :src="avatar" />
<span>{{ hitShowSearchText }}</span>
</div>
<div class="content-card">
<a-row :span="24" class="knowledge-row" v-if="hitTextList.length>0">
<a-col :xxl="6" :xl="6" :lg="6" :md="6" :sm="12" :xs="24" v-for="item in hitTextList">
<a-card class="hit-card pointer" style="border-color: #ffffff" @click="hitTextDescClick(item)">
<div class="card-title">
<div style="display: flex;">
<Icon icon="ant-design:appstore-outlined" size="14"></Icon>
<span style="margin-left: 4px">Chunk-{{item.chunk}}</span>
<span style="margin-left: 10px">{{ item.content.length }} 字符</span>
</div>
<a-tag class="card-title-tag" color="#a9c8ff">
<span>{{ getTagTxt(item.score) }}</span>
</a-tag>
</div>
<div class="card-description">
{{ item.content }}
</div>
<div class="card-footer">
{{item.docName}}
</div>
</a-card>
</a-col>
</a-row>
<div v-else-if="notHit">
<a-empty :image-style="{ margin: '0 auto', height: '160px', verticalAlign: 'middle', borderStyle: 'none' }">
<template #description>
<div style="margin-top: 26px; font-size: 20px; color: #000; text-align: center !important">
没有命中的分段
</div>
</template>
</a-empty>
</div>
</div>
</div>
<div class="param">
<span style="font-weight: bold; font-size: 16px">参数配置</span>
<ul>
<li>
<span>条数:</span>
<a-input-number :min="1" v-model:value="topNumber"></a-input-number>
</li>
<li>
<span>Score阈值:</span>
<a-input-number :min="0" :step="0.01" :max="1" v-model:value="similarity"></a-input-number>
</li>
</ul>
</div>
<div class="hit-test-footer">
<a-input v-model:value="hitText" size="large" placeholder="请输入" style="width: 100%" @pressEnter="hitTestClick">
<template #suffix>
<Icon icon="ant-design:send-outlined" style="transform: rotate(-33deg); cursor: pointer" size="22" @click="hitTestClick"></Icon>
</template>
</a-input>
</div>
</a-spin>
</div>
</a-layout-content>
</a-layout>
<Loading tip="上传中,请稍后" :loading="uploadLoading"></Loading>
</BasicModal>
<!-- 手工录入文本 -->
<AiragKnowledgeDocTextModal @register="docTextRegister" @success="handleSuccess"></AiragKnowledgeDocTextModal>
<!-- 文本明细 -->
<AiTextDescModal @register="docTextDescRegister"></AiTextDescModal>
</div>
</template>
<script lang="tsx">
import { onBeforeMount, computed, ref, unref, h } from 'vue';
import BasicModal from '@/components/Modal/src/BasicModal.vue';
import { useModal, useModalInner } from '@/components/Modal';
import { knowledgeDocList, knowledgeDeleteBatchDoc, knowledgeDeleteAllDoc, knowledgeRebuildDoc, knowledgeEmbeddingHitTest } from '../AiKnowledgeBase.api';
import { doDeleteAllDoc } from '../AiKnowledgeBase.api.util';
import { ActionItem, BasicTable, TableAction } from '@/components/Table';
import { useListPage } from '@/hooks/system/useListPage';
import AiragKnowledgeDocTextModal from './AiragKnowledgeDocTextModal.vue';
import AiTextDescModal from './AiTextDescModal.vue';
import { useMessage } from '@/hooks/web/useMessage';
import { LoadingOutlined } from '@ant-design/icons-vue';
import {Avatar, message, Modal, Pagination} from 'ant-design-vue';
import { useUserStore } from '@/store/modules/user';
import { getFileAccessHttpUrl, getHeaders } from '@/utils/common/compUtils';
import defaultImg from '/@/assets/images/header.jpg';
import Icon from "@/components/Icon";
import { useGlobSetting } from '/@/hooks/setting';
import Loading from '@/components/Loading/src/Loading.vue';
export default {
name: 'AiragKnowledgeDocListModal',
components: {
Icon,
Pagination,
Avatar,
LoadingOutlined,
TableAction,
BasicTable,
BasicModal,
AiragKnowledgeDocTextModal,
AiTextDescModal,
Loading,
},
emits: ['success', 'register'],
setup(props, { emit }) {
//标题
const title = ref<string>('知识库详情');
//保存或修改
const knowledgeId = ref<string>('');
//菜单初始化选中的值
const selectedKeys = ref(['document']);
//菜单点击选中的key,用于显示和隐藏table
const selectedKey = ref<string>('document');
//定向搜索的文本
const hitText = ref<string>('');
//定向显示的文本
const hitShowSearchText = ref<string>('');
//加载效果
const spinning = ref<boolean>(false);
//最小分数 0-1
const similarity = ref<number>(0.65);
//条数
const topNumber = ref<number>(5);
//定向返回结果集
const hitTextList = ref<any>([]);
//用户头像
const avatar = ref<any>('');
const userStore = useUserStore();
//文档列表
const knowledgeDocDataList = ref<any>([]);
//当前页数
const pageNo = ref<number>(1);
//每页条数
const pageSize = ref<number>(10);
//总条数
const total = ref<number>(0);
//可选择的页数
const pageSizeOptions = ref<any>(['10', '20', '30']);
//查询参数
const searchText = ref<string>('');
//是否没有命中
const notHit = ref<boolean>(false);
//定时任务刷新列表
const timer = ref<any>(null);
//token
const headers = getHeaders();
const globSetting = useGlobSetting();
//上传路径
const uploadUrl = ref<string>(globSetting.domainUrl+"/airag/knowledge/doc/import/zip");
//上传加载
const uploadLoading = ref<boolean>(false);
//菜单项
const menuItems = ref<any>([
{
key: 'document',
icon: '',
label: '文档',
title: '文档',
},
{
key: 'hitTest',
icon: '',
label: '命中测试',
title: '命中测试',
},
]);
// 当前选中的行
const selectedRows = computed(() => knowledgeDocDataList.value.filter(item => item.__checked))
//注册modal
const [docTextRegister, { openModal: docTextOpenModal }] = useModal();
const [docTextDescRegister, { openModal: docTextDescOpenModal }] = useModal();
//注册modal
const [registerModal, { closeModal, setModalProps }] = useModalInner(async (data) => {
knowledgeId.value = data.id;
selectedKeys.value = ['document'];
selectedKey.value = 'document';
spinning.value = false;
notHit.value = false;
await reload();
setModalProps({ confirmLoading: false });
});
const contentStyle = {
textAlign: 'center',
height: '100%',
width: '80%',
background: '#ffffff',
};
const siderStyle = {
textAlign: 'center',
width: '20%',
background: '#ffffff',
borderRight: '1px solid #cecece',
};
/**
* 加载指示符
*/
const indicator = h(LoadingOutlined, {
style: {
fontSize: '16px',
marginRight: '2px'
},
spin: true,
});
const { createMessage, createConfirmSync } = useMessage();
/**
* 手工录入文本
*/
function handleCreateText() {
docTextOpenModal(true, { knowledgeId: knowledgeId.value, type: "text" });
}
/**
* 文件上传
*/
function handleCreateUpload() {
console.log("11111111111")
docTextOpenModal(true, { knowledgeId: knowledgeId.value, type: "file" });
}
/**
* web网络地址
*/
function handleCreateWeb() {
createMessage.warning('功能正在完善中....');
}
/**
* 编辑
*/
function handleEdit(record) {
// 判断如果有选中的行,则说明是批量操作模式
if (selectedRows.value.length > 0) {
record.__checked = !record.__checked;
return
}
if (record.type === 'text' || record.type === 'file') {
docTextOpenModal(true, {
record,
isUpdate: true,
});
}
}
/**
* 删除
* @param id
*/
function handleDelete(id) {
Modal.confirm({
title: '提示',
content: '确认要删除该文档吗?',
okText: '确认',
cancelText: '取消',
onOk: () => {
if(knowledgeDocDataList.value.length == 1 && pageNo.value > 1) {
pageNo.value = pageNo.value - 1;
}
knowledgeDeleteBatchDoc({ ids: id }, reload);
}
})
}
function getDocFailedReason(doc) {
let metadata = doc?.metadata;
if (!metadata) {
return '构建失败,原因未知';
}
try {
metadata = JSON.parse(metadata);
return metadata?.failedReason || '构建失败,原因未知';
} catch (e) {
console.log('getDocFailedReason', e);
return '构建失败,原因未知';
}
}
/**
* 向量化
*
* @param id
*/
async function handleVectorization(id) {
await knowledgeRebuildDoc({ docIds: id }, handleSuccess);
}
/**
* 文档新增和编辑成功回调
*/
function handleSuccess() {
clearInterval(timer.value);
timer.value = null;
reload();
triggeringTimer();
}
/**
* 触发定时任务
*/
function triggeringTimer() {
timer.value = setInterval(() => {
reload();
},5000)
}
/**
* 菜单点击事件
* @param value
*/
function handleMenuClick(value) {
if (value.key === 'document') {
setTimeout(() => {
pageNo.value = 1;
pageSize.value = 10;
searchText.value = "";
reload();
});
} else {
hitTextList.value = [];
hitShowSearchText.value = '';
hitText.value = '';
avatar.value = '';
similarity.value = 0.65;
topNumber.value = 5;
}
selectedKey.value = value.key;
}
/**
* 命中测试
*/
function hitTestClick() {
if (hitText.value) {
spinning.value = true;
knowledgeEmbeddingHitTest({
queryText: hitText.value,
knowId: knowledgeId.value,
topNumber: topNumber.value,
similarity: similarity.value,
}).then((res) => {
if (res.success) {
if (res.result) {
hitTextList.value = res.result;
} else {
hitTextList.value = [];
}
}
hitShowSearchText.value = hitText.value;
avatar.value = userStore.getUserInfo.avatar ? getFileAccessHttpUrl(userStore.getUserInfo.avatar) : defaultImg;
hitText.value = '';
notHit.value = hitTextList.value.length == 0;
spinning.value = false;
}).catch(()=>{
spinning.value = false;
});
}
}
/**
* 获取文本
* @param value
*/
function getTagTxt(value) {
return 'score' + ' ' + value.toFixed(2);
}
/**
* 命中测试卡点击事件
* @param values
*/
function hitTextDescClick(values) {
docTextDescOpenModal(true, { ...values });
}
/**
* 加载表格
*/
async function reload() {
let params = {
pageNo: pageNo.value,
pageSize: pageSize.value,
knowledgeId: knowledgeId.value,
title: '*' + searchText.value + '*',
column: 'createTime',
order: 'desc'
};
await knowledgeDocList(params).then((res) => {
if (res.success) {
//update-begin---author:wangshuai---date:2025-03-21---for:【QQYUN-11636】向量化功能改成异步---
if(res.result.records){
let clearTimer = true;
for (const item of res.result.records) {
if(item.status && item.status === 'building' ){
clearTimer = false;
item.loading = true;
}else{
item.loading = false;
}
}
if(clearTimer){
clearInterval(timer.value);
}
}
//update-end---author:wangshuai---date:2025-03-21---for:【QQYUN-11636】向量化功能改成异步---
knowledgeDocDataList.value = res.result.records.map((item)=>{
item.__checked = false;
return item;
});
total.value = res.result.total;
} else {
knowledgeDocDataList.value = [];
total.value = 0;
}
});
}
/**
* 分页改变事件
* @param page
* @param current
*/
function handlePageChange(page, current) {
pageNo.value = page;
pageSize.value = current;
reload();
}
/**
* 获取文件后缀
*/
function getFileSuffix(metadata) {
if(metadata){
let filePath = JSON.parse(metadata).filePath;
const index = filePath.lastIndexOf('.');
return index > 0 ? filePath.substring(index + 1).toLowerCase() : '';
}
return '';
}
/**
* 上传前事件
*/
function beforeUpload(file) {
let fileType = file.type;
if (fileType !== 'application/zip' && fileType !== 'application/x-zip-compressed') {
createMessage.warning('请上传zip文件');
return false;
}
uploadLoading.value = true;
return true;
}
/**
* 文件上传回调事件
* @param info
*/
function handleUploadChange(info) {
let { file } = info;
if (file.status === 'error' || (file.response && file.response.code == 500)) {
createMessage.error(file.response?.message ||`${file.name} 上传失败,请查看服务端日志`);
uploadLoading.value = false;
return;
}
if (file.status === 'done') {
if(!file.response.success){
createMessage.warning(file.response.message);
uploadLoading.value = false;
return;
}
uploadLoading.value = false;
createMessage.success(file.response.message);
handleSuccess();
}
}
function onClearSelected() {
knowledgeDocDataList.value.forEach(item => {
item.__checked = false;
});
}
// 清空文档
async function onDeleteAll() {
pageNo.value = 1;
doDeleteAllDoc(knowledgeId.value, reload);
}
// 批量删除
async function onDeleteBatch() {
const flag = await createConfirmSync({ title: '批量删除', content: `确定要删除这 ${selectedRows.value.length} 条数据吗?` });
if (!flag) {
return;
}
const ids = selectedRows.value.map(item => item.id)
let number = knowledgeDocDataList.value.length - ids.length;
if(number == 0 && pageNo.value > 1) {
pageNo.value = pageNo.value - 1;
}
knowledgeDeleteBatchDoc({ ids }, reload);
}
/**
* 回车搜索
*/
function searchTextEnter(){
pageNo.value = 1;
reload();
}
onBeforeMount(()=>{
clearInterval(timer.value);
timer.value = null;
})
return {
registerModal,
title,
docTextRegister,
handleCreateText,
beforeUpload,
handleCreateUpload,
handleSuccess,
contentStyle,
siderStyle,
selectedKeys,
menuItems,
handleMenuClick,
selectedKey,
hitTestClick,
hitText,
spinning,
similarity,
topNumber,
hitShowSearchText,
avatar,
hitTextList,
getTagTxt,
docTextDescRegister,
hitTextDescClick,
knowledgeDocDataList,
handleEdit,
handleDelete,
getDocFailedReason,
handleVectorization,
pageNo,
pageSize,
pageSizeOptions,
total,
handlePageChange,
searchText,
reload,
cardBodyStyle:{ textAlign: 'left', width: '100%' },
getFileSuffix,
notHit,
indicator,
headers,
uploadUrl,
handleUploadChange,
knowledgeId,
uploadLoading,
selectedRows,
onClearSelected,
onDeleteAll,
onDeleteBatch,
searchTextEnter,
};
},
};
</script>
<style scoped lang="less">
.pointer {
cursor: pointer;
}
.hit-test {
box-sizing: border-box;
flex-wrap: wrap;
text-align: left;
display: flex;
margin-bottom: 10px;
h4 {
font-weight: bold;
font-size: 16px;
align-self: center;
margin-bottom: 0;
color: #1f2329;
}
span {
margin-left: 10px;
color: #8f959e;
font-weight: 400;
align-self: center;
margin-top: 2px;
}
}
.hit-test-footer {
margin-top: 10px;
display: flex;
}
.param {
text-align: left;
margin-top: 10px;
ul {
margin-top: 10px;
display: flex;
li {
align-items: center;
margin-right: 10px;
display: flex;
}
}
border-bottom: 1px solid #cec6c6;
}
.content {
height: calc(100vh - 300px);
padding: 8px;
border-radius: 10px;
background-color: #f9fbfd;
overflow-y: auto;
.content-title {
font-size: 16px;
font-weight: 600;
text-align: left;
margin-left: 10px;
display: flex;
span {
margin-left: 4px;
font-size: 20px;
align-self: center;
}
}
.content-card {
margin-top: 20px;
margin-left: 10px;
.hit-card {
height: 160px;
margin-bottom: 10px;
margin-right: 10px;
border-radius: 10px;
background: #fcfcfd;
border: 1px solid #f0f0f0;
box-shadow: 0 2px 4px #e6e6e6;
transition: all 0.3s ease;
.card-title {
justify-content: space-between;
color: #887a8b;
font-size: 14px;
display: flex;
}
}
}
}
.hit-card:hover {
box-shadow: 0 6px 12px #d0d3d8 !important;
}
.pointer {
cursor: pointer;
}
.card-description {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 4;
height: 6em;
overflow: hidden;
text-overflow: ellipsis;
line-height: 1.5;
margin-top: 16px;
text-align: left;
font-size: 12px;
color: #676F83;
}
.card-title-tag {
color: #477dee;
}
.knowledge-row {
padding: 16px;
overflow-y: auto;
}
.add-knowledge-card {
border-radius: 10px;
margin-bottom: 20px;
display: inline-flex;
font-size: 16px;
height: 166px;
width: calc(100% - 20px);
background: #fcfcfd;
border: 1px solid #f0f0f0;
box-shadow: 0 2px 4px #e6e6e6;
transition: all 0.3s ease;
.add-knowledge-card-icon {
padding: 8px;
margin-right: 12px;
}
}
.knowledge-card {
border-radius: 10px;
margin-right: 20px;
margin-bottom: 20px;
height: 166px;
background: #fcfcfd;
border: 1px solid #f0f0f0;
box-shadow: 0 2px 4px #e6e6e6;
transition: all 0.3s ease;
.knowledge-checkbox {
position: absolute;
top: 8px;
right: 8px;
z-index: 1;
align-items: center;
justify-content: center;
display: none;
}
&:hover, &.checked {
.knowledge-checkbox {
display: flex;
}
}
&.checked {
border: 1px solid @primary-color;
}
.knowledge-header {
position: relative;
font-size: 14px;
height: 20px;
text-align: left;
.header-img {
width: 40px;
height: 40px;
margin-right: 12px;
}
.header-title{
font-weight: bold;
color: #354052;
margin-left: 4px;
align-self: center;
}
.header-text {
overflow: hidden;
position: relative;
display: flex;
font-size: 16px;
}
}
}
.add-knowledge-card,.knowledge-card{
transition: box-shadow 0.3s ease;
}
.add-knowledge-card:hover,.knowledge-card:hover{
box-shadow: 0 6px 12px #d0d3d8;
}
.ellipsis {
text-overflow: ellipsis;
overflow: hidden;
text-wrap: nowrap;
width: calc(100% - 30px);
}
:deep(.ant-card .ant-card-body) {
padding: 16px;
}
.card-text{
font-size: 12px;
display: flex;
margin-top: 10px;
align-items: center;
}
.search-header {
margin-top: 10px;
margin-left: 26px;
.search-input {
width: 240px;
display: block;
}
}
.operation{
border: none;
margin-top: 10px;
align-items: end;
display: none !important;
bottom: 8px;
right: 4px;
position: absolute;
}
.add-knowledge-card:hover, .knowledge-card:hover{
.operation{
display: block !important;
}
}
.add-knowledge-doc{
margin-top: 6px;
color:#6F6F83;
font-size: 13px;
width: 100%;
cursor: pointer;
display: flex;
span{
margin-left: 4px;
line-height: 28px;
}
}
.add-knowledge-doc:hover{
background: #c8ceda33;
}
.operation{
background-color: unset;
border: none;
margin-right: 2px;
}
.operation:hover{
color: #000000;
background-color: #e9ecf2;
border: none;
}
.ant-dropdown-link{
font-size: 14px;
height: 24px;
padding: 0 7px;
border-radius: 4px;
align-content: center;
text-align: center;
}
.card-footer{
margin-top: 4px;
font-weight: 400;
color: #1f2329;
text-align: left;
font-size: 12px;
}
.card-text-status{
display: flex;
align-items: center;
}
.ml-2{
margin-left: 2px;
}
.add-knowledge-doc {
:deep(.ant-upload) {
width: 100%;
}
}
</style>
<style lang="less">
.airag-knowledge-doc .scroll-container {
padding: 0 !important;
}
</style>