作者 dong

修改日志管理代码,首页修改指标说明提示修改,

设置-功能按钮,设置-样式修改,知识库跳转,设置-全部字段必填,设置-将智能助手配置放菜单管理下
... ... @@ -15,5 +15,9 @@ export const getLoginfo = (params) => defHttp.get({ url: Api.loginfo, params },
*/
export const getVisitInfo = (params) => defHttp.get({ url: Api.visitInfo, params }, { isTransformResponse: false });
export const getStatistics = (params?: { rangeType?: string | null; startTime?: string; endTime?: string }) =>
defHttp.get({ url: '/airaglog/airagLog/getStatistics', params });
export const getStatistics = () => defHttp.get({ url: '/airaglog/airagLog/getStatistics' });
export const getButtonStats = (params?: { rangeType?: string | null; startTime?: string; endTime?: string }) =>
defHttp.get({ url: '/airaglog/airagLog/getButtonStats', params });
export const getQuestionStatistics = () => defHttp.get({ url: '/airaglog/airagLog/getQuestionStatistics' });
... ...
... ... @@ -9,20 +9,18 @@
:class="[index + 1 < 4 && '!md:mr-4']"
>
<template #action>
<a-tooltip title="指标说明">
<a-tooltip>
<Icon :icon="item.icon" :size="20" />
</a-tooltip>
</template>
<div v-if="type === 'chart'">
<!-- <Trend term="周同比" :percentage="12" v-if="index === 0" />-->
<!-- <Trend term="日同比" :percentage="11" v-if="index === 0" :type="false" />-->
<div v-if="index === 0" class="p-2 px-4 flex justify-between">
<span>平均问答次数</span>
<span>{{ formatNumber(statistics.averageCount) }}</span>
</div>
<SingleLine v-if="index === 1" :option="option" :chartData="chartData" :seriesColor="seriesColor" height="50px" />
<SingleLine v-if="index === 1" :option="option" :chartData="dailyCountsData" :seriesColor="seriesColor" height="50px" />
<Bar v-if="index === 2" :option="option" :chartData="chartData" :seriesColor="seriesColor" height="50px" />
<Bar v-if="index === 2" :option="option" :chartData="dailyRejectedCountsData" :seriesColor="seriesColor" height="50px" />
<Trend
v-if="index === 3"
... ... @@ -32,11 +30,11 @@
/>
</div>
<div v-else>
<SingleLine :seriesColor="seriesColor" v-if="index === 0" :option="option" :chartData="chartData" height="50px" />
<SingleLine :seriesColor="seriesColor" v-if="index === 0" :option="option" height="50px" />
<SingleLine :seriesColor="seriesColor" v-if="index === 1" :option="option" :chartData="chartData" height="50px" />
<SingleLine :seriesColor="seriesColor" v-if="index === 1" :option="option" :chartData="dailyCountsData" height="50px" />
<Bar :seriesColor="seriesColor" v-if="index === 2" :option="option" :chartData="chartData" height="50px" />
<Bar :seriesColor="seriesColor" v-if="index === 2" :option="option" :chartData="dailyRejectedCountsData" height="50px" />
<Progress v-if="index === 3" :percent="78" :show-info="false" />
</div>
... ... @@ -87,34 +85,45 @@
default: 'chart',
},
});
const option = ref({ xAxis: { show: false, boundaryGap: false }, yAxis: { show: false, boundaryGap: false, max: 220 } });
const dailyCountsData = computed(() => {
if (!props.statistics.dailyCounts) return [];
return props.statistics.dailyCounts.map((item) => ({
name: formatDayLabel(item.date),
value: item.count,
}));
});
const chartData = ref([
{
name: '1月',
value: 50,
},
{
name: '2月',
value: 100,
},
{
name: '3月',
value: 150,
},
{
name: '4月',
value: 40,
const dailyRejectedCountsData = computed(() => {
if (!props.statistics.dailyRejectedCounts) return [];
return props.statistics.dailyRejectedCounts.map((item) => ({
name: formatDayLabel(item.date),
value: item.count,
}));
});
const formatDayLabel = (dateStr: string) => {
const date = new Date(dateStr);
console.log('格式化日期:', dateStr);
return `${date.getMonth() + 1}/${date.getDate()}`; // 格式: 月/日
};
const option = ref({
xAxis: {
show: true, // 显示X轴
type: 'category',
boundaryGap: false,
},
{
name: '5月',
value: 110,
yAxis: {
show: true, // 显示Y轴
boundaryGap: false,
min: 0, // 设置最小值
},
{
name: '6月',
value: 120,
grid: {
top: 5,
bottom: 5,
left: 5,
right: 5,
},
]);
});
const seriesColor = computed(() => {
return getThemeColor.value;
});
... ...
... ... @@ -14,18 +14,18 @@
<a-range-picker :style="{ width: '256px' }" @change="handleDateChange" :value="dateRange" />
</div>
</template>
<a-tab-pane loading="true" tab="每月问答次数" key="1">
<a-tab-pane loading="true" tab="按钮统计数据" key="1">
<a-row>
<a-col :xl="16" :lg="12" :md="12" :sm="24" :xs="24">
<Bar
:chartData="barData"
:chartData="barChartData"
:option="{ title: { text: '', textStyle: { fontWeight: 'lighter' } } }"
height="40vh"
:seriesColor="seriesColor"
/>
</a-col>
<a-col :xl="8" :lg="12" :md="12" :sm="24" :xs="24">
<RankList title="按钮统计榜" :list="buttonStats" />
<RankList title="按钮统计榜" :list="buttonStatsData" />
</a-col>
</a-row>
</a-tab-pane>
... ... @@ -49,35 +49,61 @@
</a-card>
</template>
<script lang="ts" setup>
import { computed, ref } from 'vue';
import { computed, onMounted, ref } from 'vue';
import Bar from '/@/components/chart/Bar.vue';
import RankList from '/@/components/chart/RankList.vue';
import { useRootSetting } from '/@/hooks/setting/useRootSetting';
import { getButtonStats } from '@/views/dashboard/Analysis/api';
// 删除props中的barData和buttonStats
defineProps({
loading: {
type: Boolean,
},
barData: {
type: Array as () => Array<{ name: string; value: number }>, // 直接定义类型
default: () => [],
},
buttonStats: {
// 添加按钮统计属性
type: Array as () => Array<{ name: string; total: number }>,
default: () => [],
},
});
const activeRange = ref('all');
const dateRange = ref<any>(null);
const buttonStatsData = ref<Array<{ name: string; total: number }>>([]); // 存储按钮统计数据
const loadingStats = ref(false); // 按钮数据加载状态
const emit = defineEmits(['range-change']);
// 获取按钮统计数据
const fetchButtonStats = async (params?: { rangeType?: string | null; startTime?: string; endTime?: string }) => {
try {
loadingStats.value = true;
const res = await getButtonStats(params);
buttonStatsData.value = res || [];
} catch (error) {
console.error('获取按钮统计数据失败', error);
buttonStatsData.value = [];
} finally {
loadingStats.value = false;
}
};
// 将按钮统计数据转换为柱状图格式
const barChartData = computed(() => {
return buttonStatsData.value.map((item) => ({
name: item.name,
value: item.total,
}));
});
// 初始化加载数据
onMounted(() => {
fetchButtonStats();
});
const changeRange = (rangeType: string) => {
activeRange.value = rangeType;
dateRange.value = null; // 清除日期选择
// 根据范围类型构建参数
const params = rangeType === 'all' ? null : { rangeType };
fetchButtonStats(params); // 获取对应范围的按钮数据
if (rangeType === 'all') {
emit('range-change', { rangeType: null });
} else {
... ... @@ -89,16 +115,23 @@
if (dateStrings[0] && dateStrings[1]) {
activeRange.value = 'custom';
dateRange.value = dates;
emit('range-change', {
// 构建自定义范围参数
const params = {
rangeType: 'custom',
startTime: dateStrings[0] + ' 00:00:00',
endTime: dateStrings[1] + ' 23:59:59',
});
};
fetchButtonStats(params); // 获取自定义范围的按钮数据
emit('range-change', params);
} else {
activeRange.value = 'all';
fetchButtonStats(); // 重置为全部数据
emit('range-change', { rangeType: null });
}
};
const { getThemeColor } = useRootSetting();
const seriesColor = computed(() => {
return getThemeColor.value;
... ...
<template>
<div class="p-4">
<ChartGroupCard class="enter-y" :loading="loading" type="chart" :statistics="statistics" />
<SaleTabCard class="!my-4 enter-y" :loading="loading" :barData="barData" :buttonStats="buttonStats" @range-change="handleRangeChange" />
<SaleTabCard class="!my-4 enter-y" :loading="loading" @range-change="handleRangeChange" />
<a-row>
<a-col :span="24">
<a-card :loading="loading" :bordered="false" title="最近一周访问量统计">
<a-card :loading="questionLoading" :bordered="false" title="问题输入量统计">
<div class="infoArea">
<HeadInfo title="今日IP" :iconColor="ipColor" :content="loginfo.todayIp" icon="environment" />
<HeadInfo title="今日访问" :iconColor="visitColor" :content="loginfo.todayVisitCount" icon="team" />
<HeadInfo title="总访问量" :iconColor="seriesColor" :content="loginfo.totalVisitCount" icon="rise" />
<HeadInfo title="今日" :iconColor="ipColor" :content="formatNumber(questionStats.todayCount)" icon="environment" />
<HeadInfo title="本周" :iconColor="ipColor" :content="formatNumber(questionStats.weekCount)" icon="environment" />
<HeadInfo title="本月" :iconColor="ipColor" :content="formatNumber(questionStats.monthCount)" icon="environment" />
<HeadInfo title="本年" :iconColor="ipColor" :content="formatNumber(questionStats.yearCount)" icon="environment" />
<HeadInfo title="总计" :iconColor="seriesColor" :content="formatNumber(questionStats.totalCount)" icon="rise" />
</div>
<!-- <LineMulti :chartData="lineMultiData" height="33vh" type="line" :option="{ legend: { top: 'bottom' } }" />-->
</a-card>
... ... @@ -17,14 +19,15 @@
</div>
</template>
<script lang="ts" setup>
import { computed, onMounted, ref, watch } from 'vue';
import { onMounted, ref, watch } from 'vue';
import ChartGroupCard from '../components/ChartGroupCard.vue';
import SaleTabCard from '../components/SaleTabCard.vue';
import HeadInfo from '/@/components/chart/HeadInfo.vue';
import { getLoginfo, getStatistics, getVisitInfo } from '../api';
import { getLoginfo, getQuestionStatistics, getStatistics, getVisitInfo } from '../api';
import { useRootSetting } from '/@/hooks/setting/useRootSetting';
const loading = ref(true);
const questionLoading = ref(true);
const { getThemeColor } = useRootSetting();
// 默认范围类型(全部)
... ... @@ -49,10 +52,40 @@
rejectedCount: 0,
totalCount: 0,
averageCount: 0,
buttonStats: [] as { name: string; total: number }[],
monthlyData: [] as { month: string; count: number }[],
dailyCounts: [] as { date: string; count: number }[],
dailyRejectedCounts: [] as { date: string; count: number }[],
});
const questionStats = ref({
todayCount: 0,
weekCount: 0,
monthCount: 0,
yearCount: 0,
totalCount: 0,
});
// 获取问题统计数据的方法
async function fetchQuestionStatistics() {
try {
questionLoading.value = true;
const res = await getQuestionStatistics();
console.log('问题统计数据:', res);
// 更新数据(根据实际接口返回结构调整)
questionStats.value = {
todayCount: res.todayCount ?? 0,
weekCount: res.weekCount ?? 0,
monthCount: res.monthCount ?? 0,
yearCount: res.yearCount ?? 0,
totalCount: res.totalCount ?? 0,
};
} catch (error) {
console.error('获取问题统计失败', error);
} finally {
questionLoading.value = false;
}
}
// 实时数据获取函数
async function fetchStatistics() {
try {
... ... @@ -72,8 +105,9 @@
rejectedCount: res.rejectedCount || res.data?.rejectedCount || 0,
totalCount: res.totalCount || res.data?.totalCount || 0,
averageCount: res.averageCount || 0,
buttonStats: res.buttonStats || [],
monthlyData: res.monthlyData || res.data?.monthlyData || generateMonthlyData(),
dailyCounts: res.dailyCounts || [],
dailyRejectedCounts: res.dailyRejectedCounts || [],
};
console.log('更新后的 statistics:', statistics.value);
... ... @@ -90,27 +124,13 @@
}));
}
// 转换按钮统计数据格式
const buttonStats = computed(() => {
return statistics.value.buttonStats.map((item) => ({
name: item.name,
total: item.total,
}));
});
// 在 setup 中添加转换函数
const barData = computed(() => {
if (!statistics.value.monthlyData) return [];
return statistics.value.monthlyData.map((item) => {
return {
name: `${item.month}`,
value: item.count,
};
});
});
const formatNumber = (num: number) => {
return num.toLocaleString();
};
onMounted(() => {
fetchStatistics();
fetchQuestionStatistics();
});
setTimeout(() => {
... ...
import { FormSchema } from '@/components/Form';
import { BasicColumn } from '@/components/Table';
/**
* 表单
... ...
... ... @@ -39,7 +39,7 @@
</a-card>
</a-col>
<a-col v-if="knowledgeList && knowledgeList.length > 0" :xxl="4" :xl="6" :lg="6" :md="6" :sm="12" :xs="24" v-for="item in knowledgeList">
<a-card class="knowledge-card pointer" @click="handleDocClick(item.id)">
<a-card class="knowledge-card pointer" @click="handleDocClick(item)">
<div class="knowledge-header">
<div class="flex">
<img class="header-img" src="./icon/knowledge.png" />
... ... @@ -113,6 +113,7 @@
import AiragKnowledgeDocListModal from './components/AiragKnowledgeDocListModal.vue';
import Icon from '@/components/Icon';
import { useMessage } from '@/hooks/web/useMessage';
import { useRouter } from 'vue-router';
export default {
name: 'KnowledgeBaseList',
... ... @@ -128,7 +129,7 @@
setup() {
//模型列表
const knowledgeList = ref([]);
const router = useRouter();
//注册modal
const [registerModal, { openModal }] = useModal();
const [docListRegister, { openModal: openDocModal }] = useModal();
... ... @@ -246,8 +247,15 @@
*
* @param id
*/
function handleDocClick(id) {
openDocModal(true, { id });
function handleDocClick(item) {
console.log('测试' + item);
router.push({
path: '/super/airag/test/EmbeddingsList',
query: {
knowledgeId: item.id,
knowledgeName: item.name,
},
});
}
/**
... ...
<template>
<div class="p-2">
<BasicModal destroyOnClose @register="registerModal" :canFullscreen="false" width="600px" :title="title" @ok="handleOk" @cancel="handleCancel">
<BasicForm @register="registerForm"></BasicForm>
<BasicForm @register="registerForm" />
</BasicModal>
</div>
</template>
... ...
... ... @@ -4,14 +4,13 @@
<div class="p-2">
<div class="header">
<a-tag color="#a9c8ff">
<span>{{hitTextDescData.source}}</span>
<span>{{ hitTextDescData.source }}</span>
</a-tag>
</div>
<div class="content">
<MarkdownViewer :value="hitTextDescData.content" />
</div>
</div>
</BasicModal>
</template>
... ... @@ -32,18 +31,18 @@
},
emits: ['success', 'register'],
setup(props, { emit }) {
let hitTextDescData = ref<any>({})
let hitTextDescData = ref<any>({});
//注册modal
const [registerModal, { closeModal, setModalProps }] = useModalInner(async (data) => {
hitTextDescData.value.source = 'score' + ' ' + data.score.toFixed(2);
hitTextDescData.value.content = data.content;
setModalProps({ header: '300px' })
setModalProps({ header: '300px' });
});
return {
registerModal,
hitTextDescData
hitTextDescData,
};
},
};
... ...
... ... @@ -16,29 +16,29 @@
</a-layout-sider>
<a-layout-content :style="contentStyle">
<div v-if="selectedKey === 'document'">
<a-input v-model:value="searchText" placeholder="请输入文档名称,回车搜索" class="search-title" @pressEnter="reload"/>
<a-input v-model:value="searchText" placeholder="请输入文档名称,回车搜索" class="search-title" @press-enter="reload" />
<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">创建文档</span>
<span style="line-height: 18px; font-weight: 500; color: #676f83; font-size: 12px">创建文档</span>
<div class="add-knowledge-doc" @click="handleCreateText">
<Icon icon="ant-design:form-outlined" size="13"></Icon><span>手动录入</span>
<Icon icon="ant-design:form-outlined" size="13" /><span>手动录入</span>
</div>
<div class="add-knowledge-doc" @click="handleCreateUpload">
<Icon icon="ant-design:cloud-upload-outlined" size="13"></Icon><span>文件上传</span>
<Icon icon="ant-design:cloud-upload-outlined" size="13" /><span>文件上传</span>
</div>
<div class="add-knowledge-doc" @click="handleCreateUploadLibrary">
<a-upload
accept=".zip"
name="file"
:data="{ knowId: knowledgeId }"
:showUploadList="false"
:headers="headers"
:beforeUpload="beforeUpload"
:action="uploadUrl"
@change="handleUploadChange"
accept=".zip"
name="file"
:data="{ knowId: knowledgeId }"
:showUploadList="false"
:headers="headers"
:beforeUpload="beforeUpload"
:action="uploadUrl"
@change="handleUploadChange"
>
<Icon style="margin-left: 0" icon="ant-design:project-outlined" size="13"></Icon>
<Icon style="margin-left: 0" icon="ant-design:project-outlined" size="13" />
<span>文档库上传</span>
</a-upload>
</div>
... ... @@ -48,14 +48,49 @@
<a-card class="knowledge-card pointer" @click="handleEdit(item)">
<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>
<Icon v-if="item.type === 'text'" icon="ant-design:file-text-outlined" size="32" color="#00a7d0" />
<Icon
v-if="item.type === 'file' && getFileSuffix(item.metadata) === 'pdf'"
icon="ant-design:file-pdf-outlined"
size="32"
color="rgb(211, 47, 47)"
/>
<Icon
v-if="item.type === 'file' && getFileSuffix(item.metadata) === 'docx'"
icon="ant-design:file-word-outlined"
size="32"
color="rgb(68, 138, 255)"
/>
<Icon
v-if="item.type === 'file' && getFileSuffix(item.metadata) === 'pptx'"
icon="ant-design:file-ppt-outlined"
size="32"
color="rgb(245, 124, 0)"
/>
<Icon
v-if="item.type === 'file' && getFileSuffix(item.metadata) === 'xlsx'"
icon="ant-design:file-excel-outlined"
size="32"
color="rgb(98, 187, 55)"
/>
<Icon
v-if="item.type === 'file' && getFileSuffix(item.metadata) === 'txt'"
icon="ant-design:file-text-outlined"
size="32"
color="#00a7d0"
/>
<Icon
v-if="item.type === 'file' && getFileSuffix(item.metadata) === 'md'"
icon="ant-design:file-markdown-outlined"
size="32"
color="#292929"
/>
<Icon
v-if="item.type === 'file' && getFileSuffix(item.metadata) === ''"
icon="ant-design:file-unknown-outlined"
size="32"
color="#f5f5dc"
/>
<span class="ellipsis header-title">{{ item.title }}</span>
</div>
</div>
... ... @@ -65,35 +100,35 @@
<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>
<div v-if="item.status === 'complete'" class="card-text-status">
<Icon icon="ant-design:check-circle-outlined" size="16" color="#56D1A7" />
<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>
<div v-else-if="item.status === 'building'" class="card-text-status">
<a-spin v-if="item.loading" :spinning="item.loading" :indicator="indicator" />
<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" />
<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>
</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>
<Icon icon="ant-design:ellipsis-outlined" size="16" />
</div>
<template #overlay>
<a-menu>
<a-menu-item key="vectorization" @click="handleVectorization(item.id)">
<Icon icon="ant-design:retweet-outlined" size="16"></Icon>
<Icon icon="ant-design:retweet-outlined" size="16" />
向量化
</a-menu-item>
<a-menu-item key="edit" @click="handleEdit(item)">
<Icon icon="ant-design:edit-outlined" size="16"></Icon>
<Icon icon="ant-design:edit-outlined" size="16" />
编辑
</a-menu-item>
<a-menu-item key="delete" @click="handleDelete(item.id)">
<Icon icon="ant-design:delete-outlined" size="16"></Icon>
<Icon icon="ant-design:delete-outlined" size="16" />
删除
</a-menu-item>
</a-menu>
... ... @@ -129,13 +164,13 @@
<span>{{ hitShowSearchText }}</span>
</div>
<div class="content-card">
<a-row :span="24" class="knowledge-row" v-if="hitTextList.length>0">
<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>
<div style="display: flex">
<Icon icon="ant-design:appstore-outlined" size="14" />
<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">
... ... @@ -146,7 +181,7 @@
{{ item.content }}
</div>
<div class="card-footer">
{{item.docName}}
{{ item.docName }}
</div>
</a-card>
</a-col>
... ... @@ -154,9 +189,7 @@
<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>
<div style="margin-top: 26px; font-size: 20px; color: #000; text-align: center !important"> 没有命中的分段 </div>
</template>
</a-empty>
</div>
... ... @@ -167,18 +200,18 @@
<ul>
<li>
<span>条数:</span>
<a-input-number :min="1" v-model:value="topNumber"></a-input-number>
<a-input-number :min="1" v-model:value="topNumber" />
</li>
<li>
<span>Score阈值:</span>
<a-input-number :min="0" :step="0.01" :max="1" v-model:value="similarity"></a-input-number>
<a-input-number :min="0" :step="0.01" :max="1" v-model:value="similarity" />
</li>
</ul>
</div>
<div class="hit-test-footer">
<a-input v-model:value="hitText" size="large" placeholder="请输入" style="width: 100%" @pressEnter="hitTestClick">
<a-input v-model:value="hitText" size="large" placeholder="请输入" style="width: 100%" @press-enter="hitTestClick">
<template #suffix>
<Icon icon="ant-design:send-outlined" style="transform: rotate(-33deg); cursor: pointer" size="22" @click="hitTestClick"></Icon>
<Icon icon="ant-design:send-outlined" style="transform: rotate(-33deg); cursor: pointer" size="22" @click="hitTestClick" />
</template>
</a-input>
</div>
... ... @@ -189,9 +222,9 @@
</BasicModal>
<!-- 手工录入文本 -->
<AiragKnowledgeDocTextModal @register="docTextRegister" @success="handleSuccess"></AiragKnowledgeDocTextModal>
<AiragKnowledgeDocTextModal @register="docTextRegister" @success="handleSuccess" />
<!-- 文本明细 -->
<AiTextDescModal @register="docTextDescRegister"></AiTextDescModal>
<AiTextDescModal @register="docTextDescRegister" />
</div>
</template>
... ... @@ -206,11 +239,11 @@
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 { 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 Icon from '@/components/Icon';
import { useGlobSetting } from '/@/hooks/setting';
export default {
... ... @@ -273,8 +306,8 @@
const headers = getHeaders();
const globSetting = useGlobSetting();
//上传路径
const uploadUrl = ref<string>(globSetting.domainUrl+"/airag/knowledge/doc/import/zip");
const uploadUrl = ref<string>(globSetting.domainUrl + '/airag/knowledge/doc/import/zip');
//菜单项
const menuItems = ref<any>([
{
... ... @@ -322,28 +355,28 @@
/**
* 加载指示符
*/
const indicator = h(LoadingOutlined, {
const indicator = h(LoadingOutlined, {
style: {
fontSize: '16px',
marginRight: '2px'
marginRight: '2px',
},
spin: true,
});
const { createMessage } = useMessage();
/**
* 手工录入文本
*/
function handleCreateText() {
docTextOpenModal(true, { knowledgeId: knowledgeId.value, type: "text" });
docTextOpenModal(true, { knowledgeId: knowledgeId.value, type: 'text' });
}
/**
* 文件上传
*/
function handleCreateUpload() {
docTextOpenModal(true, { knowledgeId: knowledgeId.value, type: "file" });
docTextOpenModal(true, { knowledgeId: knowledgeId.value, type: 'file' });
}
/**
... ... @@ -377,8 +410,8 @@
cancelText: '取消',
onOk: () => {
knowledgeDeleteBatchDoc({ ids: id }, reload);
}
})
},
});
}
/**
... ... @@ -394,7 +427,7 @@
* 文档新增和编辑成功回调
*/
function handleSuccess() {
if(!timer.value){
if (!timer.value) {
reload();
}
clearInterval(timer.value);
... ... @@ -408,7 +441,7 @@
function triggeringTimer() {
timer.value = setInterval(() => {
reload();
},5000)
}, 5000);
}
/**
... ... @@ -420,8 +453,8 @@
setTimeout(() => {
pageNo.value = 1;
pageSize.value = 10;
searchText.value = "";
searchText.value = '';
reload();
});
} else {
... ... @@ -446,22 +479,24 @@
knowId: knowledgeId.value,
topNumber: topNumber.value,
similarity: similarity.value,
}).then((res) => {
if (res.success) {
if (res.result) {
hitTextList.value = res.result;
} else {
hitTextList.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;
});
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;
});
}
}
... ... @@ -491,22 +526,22 @@
knowledgeId: knowledgeId.value,
title: '*' + searchText.value + '*',
column: 'createTime',
order: 'desc'
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){
if (res.result.records) {
let clearTimer = true;
for (const item of res.result.records) {
if(item.status && item.status === 'building' ){
if (item.status && item.status === 'building') {
clearTimer = false;
item.loading = true;
}else{
} else {
item.loading = false;
}
}
if(clearTimer){
if (clearTimer) {
clearInterval(timer.value);
}
}
... ... @@ -519,7 +554,7 @@
}
});
}
/**
* 分页改变事件
* @param page
... ... @@ -535,7 +570,7 @@
* 获取文件后缀
*/
function getFileSuffix(metadata) {
if(metadata){
if (metadata) {
let filePath = JSON.parse(metadata).filePath;
const index = filePath.lastIndexOf('.');
return index > 0 ? filePath.substring(index + 1).toLowerCase() : '';
... ... @@ -549,8 +584,8 @@
function beforeUpload(file) {
let fileType = file.type;
if (fileType !== 'application/x-zip-compressed') {
createMessage.warning('请上传zip文件');
return false;
createMessage.warning('请上传zip文件');
return false;
}
return true;
}
... ... @@ -562,10 +597,10 @@
function handleUploadChange(info) {
let { file } = info;
if (file.status === 'error') {
createMessage.error(file.response.message ||`${file.name} 上传失败.`);
createMessage.error(file.response.message || `${file.name} 上传失败.`);
}
if (file.status === 'done') {
if(!file.response.success){
if (!file.response.success) {
createMessage.warning(file.response.message);
return;
}
... ... @@ -574,11 +609,11 @@
}
}
onBeforeMount(()=>{
onBeforeMount(() => {
clearInterval(timer.value);
timer.value = null;
})
});
return {
registerModal,
title,
... ... @@ -615,7 +650,7 @@
handlePageChange,
searchText,
reload,
cardBodyStyle:{ textAlign: 'left', width: '100%' },
cardBodyStyle: { textAlign: 'left', width: '100%' },
getFileSuffix,
notHit,
indicator,
... ... @@ -703,7 +738,7 @@
border-radius: 10px;
background: #fcfcfd;
border: 1px solid #f0f0f0;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
.card-title {
justify-content: space-between;
... ... @@ -715,12 +750,12 @@
}
}
.hit-card:hover {
box-shadow: 0 6px 12px rgba(0,0,0,0.15) !important;
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15) !important;
}
.pointer {
cursor: pointer;
}
.card-description {
display: -webkit-box;
-webkit-box-orient: vertical;
... ... @@ -732,18 +767,18 @@
margin-top: 16px;
text-align: left;
font-size: 12px;
color: #676F83;
color: #676f83;
}
.card-title-tag {
color: #477dee;
}
.knowledge-row {
padding: 16px;
overflow-y: auto;
}
.add-knowledge-card {
border-radius: 10px;
margin-bottom: 20px;
... ... @@ -753,14 +788,14 @@
width: calc(100% - 20px);
background: #fcfcfd;
border: 1px solid #f0f0f0;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
.add-knowledge-card-icon {
padding: 8px;
margin-right: 12px;
}
}
.knowledge-card {
border-radius: 10px;
margin-right: 20px;
... ... @@ -768,7 +803,7 @@
height: 166px;
background: #fcfcfd;
border: 1px solid #f0f0f0;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
.knowledge-header {
position: relative;
... ... @@ -780,13 +815,13 @@
height: 40px;
margin-right: 12px;
}
.header-title{
.header-title {
font-weight: bold;
color: #354052;
margin-left: 4px;
align-self: center;
}
.header-text {
overflow: hidden;
position: relative;
... ... @@ -796,40 +831,42 @@
}
}
.add-knowledge-card,.knowledge-card{
.add-knowledge-card,
.knowledge-card {
transition: box-shadow 0.3s ease;
}
.add-knowledge-card:hover,.knowledge-card:hover{
box-shadow: 0 6px 12px rgba(0,0,0,0.15);
.add-knowledge-card:hover,
.knowledge-card:hover {
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
}
.ellipsis {
text-overflow: ellipsis;
overflow: hidden;
text-wrap: nowrap;
width: calc(100% - 30px);
}
:deep(.ant-card .ant-card-body) {
padding: 16px;
}
.card-text{
.card-text {
font-size: 12px;
display: flex;
margin-top: 10px;
align-items: center;
}
.search-title{
.search-title {
width: 200px;
margin-top: 10px;
display: block;
margin-left: 20px;
}
.operation{
.operation {
border: none;
margin-top: 10px;
align-items: end;
... ... @@ -838,39 +875,39 @@
right: 4px;
position: absolute;
}
.knowledge-card:hover{
.operation{
.knowledge-card:hover {
.operation {
display: block !important;
}
}
.add-knowledge-doc{
.add-knowledge-doc {
margin-top: 6px;
color:#6F6F83;
color: #6f6f83;
font-size: 13px;
width: 100%;
cursor: pointer;
display: flex;
span{
span {
margin-left: 4px;
line-height: 28px;
}
}
.add-knowledge-doc:hover{
.add-knowledge-doc:hover {
background: #c8ceda33;
}
.operation{
.operation {
background-color: unset;
border: none;
margin-right: 2px;
}
.operation:hover{
.operation:hover {
color: #000000;
background-color: rgba(0,0,0,0.05);
background-color: rgba(0, 0, 0, 0.05);
border: none;
}
.ant-dropdown-link{
.ant-dropdown-link {
font-size: 14px;
height: 24px;
padding: 0 7px;
... ... @@ -878,18 +915,18 @@
align-content: center;
text-align: center;
}
.card-footer{
.card-footer {
margin-top: 4px;
font-weight: 400;
color: #1f2329;
text-align: left;
font-size: 12px;
}
.card-text-status{
.card-text-status {
display: flex;
align-items: center;
}
.ml-2{
.ml-2 {
margin-left: 2px;
}
</style>
... ...
... ... @@ -36,7 +36,7 @@ export const columns: BasicColumn[] = [
dataIndex: 'buttonValues',
},
{
title: '按钮code',
title: '按钮标识',
align: 'center',
dataIndex: 'code',
},
... ... @@ -98,7 +98,7 @@ export const formSchema: FormSchema[] = [
// componentProps: {},
// },
{
label: '按钮code',
label: '按钮标识',
field: 'code',
component: 'Input',
required: true,
... ...
... ... @@ -59,12 +59,7 @@ export const columns: BasicColumn[] = [
},
},
{
title: '按钮名称',
align: 'center',
dataIndex: 'buttonName',
},
{
title: 'code',
title: '按钮标识',
align: 'center',
dataIndex: 'code',
},
... ...
<template>
<a-card title="智能助手配置" class="config-card">
<a-form :model="formState" layout="vertical">
<a-form :model="formState" layout="vertical" ref="formRef" :rules="rules">
<!-- 向量模型 -->
<a-form-item label="向量模型" class="form-item">
<a-form-item label="向量模型" class="form-item required" name="embeddingId">
<a-select
v-model:value="formState.embeddingId"
placeholder="请选择向量模型"
... ... @@ -13,7 +13,7 @@
</a-form-item>
<!-- 大语言模型 -->
<a-form-item label="大语言模型" class="form-item">
<a-form-item label="大语言模型" class="form-item required" name="llmId">
<a-select
v-model:value="formState.llmId"
placeholder="请选择大语言模型"
... ... @@ -24,7 +24,7 @@
</a-form-item>
<!-- 知识库 -->
<a-form-item label="知识库" class="form-item">
<a-form-item label="知识库" class="form-item required" name="knowledgeId">
<a-select
v-model:value="formState.knowledgeId"
placeholder="请选择知识库"
... ... @@ -35,7 +35,7 @@
</a-form-item>
<!-- 按钮(多选) -->
<a-form-item label="功能按钮" class="form-item">
<a-form-item label="功能按钮" class="form-item required" name="buttonIds">
<a-select
v-model:value="formState.buttonIds"
mode="multiple"
... ... @@ -50,11 +50,11 @@
</a-form-item>
<!-- 提示词 -->
<a-form-item label="提示词" class="form-item">
<a-form-item label="提示词" class="form-item required" name="prompt">
<a-textarea
v-model:value="formState.prompt"
placeholder="请输入提示词,例如:你是一个专业的AI助手..."
:rows="4"
:rows="9"
:maxlength="500"
show-count
/>
... ... @@ -80,7 +80,7 @@
<script lang="ts" setup>
import { reactive, ref, onMounted } from 'vue';
import { getConfigData, saveConfig } from './Chatsetting.api';
import { message } from 'ant-design-vue';
import { FormInstance, message } from 'ant-design-vue';
import { SaveOutlined, UndoOutlined } from '@ant-design/icons-vue';
// 表单数据结构
... ... @@ -93,6 +93,25 @@
prompt: '',
});
const formRef = ref<FormInstance>(); // 添加表单引用
const rules = reactive({
embeddingId: [{ required: true, message: '请选择向量模型', trigger: 'change' }],
llmId: [{ required: true, message: '请选择大语言模型', trigger: 'change' }],
knowledgeId: [{ required: true, message: '请选择知识库', trigger: 'change' }],
buttonIds: [
{
required: true,
message: '请至少选择一个按钮',
trigger: 'change',
type: 'array',
},
],
prompt: [
{ required: true, message: '请输入提示词', trigger: 'blur' },
{ max: 500, message: '提示词长度不能超过500个字符', trigger: 'blur' },
],
});
// 选项数据
const options = reactive({
embeddingOptions: [],
... ... @@ -114,7 +133,11 @@
// 填充表单
Object.keys(formState).forEach((key) => {
if (data.airagChatsettingConfig[key] !== undefined) {
formState[key] = data.airagChatsettingConfig[key];
if (key === 'buttonIds') {
formState[key] = data.airagChatsettingConfig[key] || [];
} else {
formState[key] = data.airagChatsettingConfig[key];
}
console.log('shuju' + formState[key]);
}
});
... ... @@ -134,6 +157,12 @@
// 保存配置
const saveConfigData = async () => {
try {
await formRef.value?.validateFields();
} catch (error) {
message.error('请填写所有必填字段');
return;
}
const params = {
...formState,
id: formState.id || '',
... ... @@ -163,7 +192,7 @@
// 重置表单
const resetForm = () => {
loadConfig();
formRef.value?.resetFields();
};
// 初始化加载
... ... @@ -174,7 +203,7 @@
<style lang="less" scoped>
.config-card {
max-width: 800px;
max-width: 1800px;
margin: 20px auto;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
... ...
<template>
<div>
<div class="knowledge-header" v-if="currentKnowledge">
<div class="header-container">
<h2 class="knowledge-title">{{ currentKnowledge.name }} - 文档列表</h2>
<a-button type="link" @click="goBack" class="back-button"> <Icon icon="ant-design:arrow-left-outlined" /> 返回知识库列表 </a-button>
</div>
</div>
<!--引用表格-->
<BasicTable @register="registerTable" :rowSelection="rowSelection">
<!--插槽:table标题-->
... ... @@ -24,7 +30,7 @@
</a-button>
</a-dropdown>
<!-- 高级查询 -->
<!-- <super-query :config="superQueryConfig" @search="handleSuperQuery" />-->
<!-- <super-query :config="superQueryConfig" @search="handleSuperQuery" />-->
</template>
<!--操作栏-->
<template #action="{ record }">
... ... @@ -49,10 +55,43 @@
import { useUserStore } from '/@/store/modules/user';
import JUploadButton from '@/components/Button/src/JUploadButton.vue';
import { columns as defaultColumns } from './Test.data';
import { useRoute, useRouter } from 'vue-router';
const queryParam = reactive<any>({});
const checkedKeys = ref<Array<string | number>>([]);
const userStore = useUserStore();
// 添加知识库名称映射
const route = useRoute();
const router = useRouter();
// 当前知识库信息
const currentKnowledge = ref<{ id: string; name: string } | null>(null);
// 在onMounted中添加知识库ID处理
onMounted(() => {
// 从路由参数获取知识库ID
const knowledgeId = route.query.knowledgeId as string;
const knowledgeName = route.query.knowledgeName as string;
console.log('ceshi' + knowledgeId);
if (knowledgeId && knowledgeName) {
currentKnowledge.value = {
id: knowledgeId,
name: knowledgeName,
};
// 设置查询参数
queryParam.knowledgeId = knowledgeId;
reload();
}
loadKnowledgeMap();
});
// 返回知识库列表
function goBack() {
router.push('/super/airag/aiknowledge/AiKnowledgeBaseList');
}
// 知识库名称映射
const knowledgeMap = ref<Record<string, string>>({});
// 加载知识库列表
... ... @@ -142,6 +181,9 @@
fixed: 'right',
},
beforeFetch: (params) => {
if (currentKnowledge.value?.id) {
params.knowledgeId = currentKnowledge.value.id;
}
// 处理知识库查询参数
if (params.knowledgeId) {
// 直接使用对象而不是JSON字符串
... ... @@ -269,4 +311,69 @@
:deep(.ant-input-number) {
width: 100%;
}
.knowledge-header {
margin-bottom: 16px;
padding: 16px;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.09);
.header-container {
display: flex;
justify-content: space-between;
align-items: center;
.knowledge-title {
margin: 0;
font-size: 18px;
font-weight: 600;
color: #1f2329;
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding-right: 16px;
}
.back-button {
display: flex;
align-items: center;
color: #1890ff;
font-weight: 500;
padding: 4px 8px;
transition: all 0.3s;
flex-shrink: 0;
&:hover {
color: #40a9ff;
background: rgba(24, 144, 255, 0.1);
border-radius: 4px;
}
i {
margin-right: 4px;
font-size: 14px;
}
}
}
}
@media (max-width: 768px) {
.knowledge-header {
.header-container {
flex-direction: column;
align-items: flex-start;
.knowledge-title {
margin-bottom: 12px;
padding-right: 0;
width: 100%;
}
.back-button {
align-self: flex-end;
}
}
}
}
</style>
... ...