Explorar o código

feat: 调整知识库功能

jiaxing.liao hai 1 semana
pai
achega
98ba43af0a

+ 2 - 2
apps/web/src/nodes/src/knowledge-retrieval/setter.vue

@@ -204,7 +204,7 @@ const mergeSelectedOptions = (options: SelectOption[], selectedValues: string[])
 const fetchKnowledgeBaseOptions = async () => {
 	knowledgeBaseLoading.value = true
 	try {
-		const res = await knowledge.postKnowledgeBasePageList({
+		const res = await knowledge.postAiKnowledgeBasePageList({
 			keyword: '',
 			type: '',
 			pageIndex: 1,
@@ -245,7 +245,7 @@ const fetchKnowledgeOptions = async (baseIdsInput?: string[]) => {
 	try {
 		const results = await Promise.all(
 			baseIds.map((knowledgeBaseId) =>
-				knowledge.postKnowledgePageList({
+				knowledge.postAiKnowledgePageList({
 					knowledge_base_id: knowledgeBaseId,
 					title: '',
 					file_type: '',

+ 1 - 1
apps/web/src/views/agent/components/EditModal.vue

@@ -1549,7 +1549,7 @@ function applyDetail(item: AgentItem) {
 async function loadKnowledgeBases() {
 	kbLoading.value = true
 	try {
-		const res = await knowledge.postKnowledgeBasePageList({
+		const res = await knowledge.postAiKnowledgeBasePageList({
 			keyword: '',
 			type: '',
 			pageIndex: 1,

+ 4 - 2
apps/web/src/views/agent/type.d.ts

@@ -121,7 +121,7 @@ export type ModelPageResponse = Awaited<ReturnType<typeof aiModel.postModelPageL
 export type ModelItem = NonNullable<ModelPageResponse['result']>['model'][number]
 
 export type KnowledgePageResponse = Awaited<
-	ReturnType<typeof knowledge.postKnowledgeBasePageList>
+	ReturnType<typeof knowledge.postAiKnowledgeBasePageList>
 >
 export type KnowledgeItem = NonNullable<KnowledgePageResponse['result']>['model'][number]
 
@@ -134,7 +134,9 @@ export interface AgentSelectOption {
 export type StorageProviderEnginesResponse = Awaited<
 	ReturnType<typeof storageProvider.postStorageProviderEngines>
 >
-export type StorageProviderEngineItem = NonNullable<StorageProviderEnginesResponse['result']>[number]
+export type StorageProviderEngineItem = NonNullable<
+	StorageProviderEnginesResponse['result']
+>[number]
 
 export type McpPageResponse = Awaited<ReturnType<typeof resource.postMcpPageList>>
 export type McpItem = NonNullable<McpPageResponse['result']>['model'][number]

+ 1 - 1
apps/web/src/views/chat/api/chat.api.ts

@@ -136,7 +136,7 @@ export async function getAgentInfo(id: string) {
 }
 
 export async function getKnowledgeBaseOptions(keyword = ''): Promise<ChatOptionItem[]> {
-	const res = await knowledge.postKnowledgeBasePageList({
+	const res = await knowledge.postAiKnowledgeBasePageList({
 		keyword,
 		type: '',
 		pageIndex: 1,

+ 2 - 2
apps/web/src/views/chat/components/AddKbModal.vue

@@ -51,7 +51,7 @@ const rules = {
 
 const getKbOptions = async (keyword: string = '') => {
 	try {
-		const res = await knowledge.postKnowledgeBasePageList({
+		const res = await knowledge.postAiKnowledgeBasePageList({
 			keyword,
 			type: 'document',
 			pageIndex: 1,
@@ -78,7 +78,7 @@ const handleSubmit = async () => {
 	if (vilid) {
 		try {
 			loading.value = true
-			const res = await knowledge.postKnowledgeCreateWithManual({
+			const res = await knowledge.postAiKnowledgeCreateWithManual({
 				...formData.value
 			})
 

+ 1 - 1
apps/web/src/views/chat/index.vue

@@ -1027,7 +1027,7 @@ const fetchKnowledgeOptions = async (baseIds: string[]) => {
 
 	const results = await Promise.all(
 		baseIds.map((knowledgeBaseId) =>
-			knowledge.postKnowledgePageList({
+			knowledge.postAiKnowledgePageList({
 				knowledge_base_id: knowledgeBaseId,
 				title: '',
 				file_type: '',

+ 7 - 7
apps/web/src/views/knowledge/DocumentManage.vue

@@ -315,7 +315,7 @@ async function fetchKnowledgeList() {
 	if (!props.currentBase.id) return
 	loading.value = true
 	try {
-		const res = await knowledge.postKnowledgePageList({
+		const res = await knowledge.postAiKnowledgePageList({
 			knowledge_base_id: props.currentBase.id,
 			title: keyword.value,
 			file_type: fileType.value,
@@ -369,7 +369,7 @@ async function openEditDrawer(row: KnowledgeItem) {
 	// 获取详情
 	try {
 		editDrawerLoading.value = true
-		const res = await knowledge.postKnowledgeInfo({
+		const res = await knowledge.postAiKnowledgeInfo({
 			id: row.id!
 		})
 		if (res.isSuccess) {
@@ -389,7 +389,7 @@ async function submitManualKnowledge() {
 
 	submitLoading.value = true
 	try {
-		await knowledge.postKnowledgeCreateWithManual({
+		await knowledge.postAiKnowledgeCreateWithManual({
 			knowledge_base_id: props.currentBase.id,
 			title: manualForm.title,
 			content: manualForm.content,
@@ -423,7 +423,7 @@ async function submitFileKnowledge() {
 
 	submitLoading.value = true
 	try {
-		const res = await knowledge.postKnowledgeCreateWithFile({
+		const res = await knowledge.postAiKnowledgeCreateWithFile({
 			knowledge_base_id: props.currentBase.id,
 			fileId,
 			metadata: buildMetadataPayload(),
@@ -449,7 +449,7 @@ async function submitEditKnowledge() {
 
 	submitLoading.value = true
 	try {
-		await knowledge.postKnowledgeUpdate({
+		await knowledge.postAiKnowledgeUpdate({
 			id: editForm.id,
 			title: editForm.title,
 			description: editForm.description
@@ -466,7 +466,7 @@ async function submitEditKnowledge() {
 
 async function reparseKnowledge(id?: string) {
 	if (!id) return
-	await knowledge.postKnowledgeReparse({ id })
+	await knowledge.postAiKnowledgeReparse({ id })
 	ElMessage.success('已触发重新解析')
 	await fetchKnowledgeList()
 }
@@ -480,7 +480,7 @@ async function removeKnowledge(id?: string) {
 		.catch(() => false)
 	if (!confirmed) return
 
-	await knowledge.postKnowledgeOpenApiDelete({ id })
+	await knowledge.postAiKnowledgeOpenApiDelete({ id })
 	ElMessage.success('知识已删除')
 	await fetchKnowledgeList()
 }

+ 26 - 8
apps/web/src/views/knowledge/KnowledgeBaseSidebar.vue

@@ -15,8 +15,13 @@
 		</div>
 
 		<div class="sidebar-toolbar">
-			<el-input v-model="keyword" clearable placeholder="搜索知识库名称" @keyup.enter="refreshKnowledgeBases"
-				@clear="refreshKnowledgeBases">
+			<el-input
+				v-model="keyword"
+				clearable
+				placeholder="搜索知识库名称"
+				@keyup.enter="refreshKnowledgeBases"
+				@clear="refreshKnowledgeBases"
+			>
 				<template #append>
 					<el-button @click="refreshKnowledgeBases">
 						<el-icon>
@@ -32,11 +37,24 @@
 			</el-radio-group>
 		</div>
 
-		<el-scrollbar ref="scrollbarRef" class="sidebar-body" v-loading="loading" @scroll="handleScroll">
+		<el-scrollbar
+			ref="scrollbarRef"
+			class="sidebar-body"
+			v-loading="loading"
+			@scroll="handleScroll"
+		>
 			<div v-if="knowledgeBases.length" class="base-list">
-				<div v-for="item in knowledgeBases" :key="item.id" class="base-card"
-					:class="{ 'base-card--active': item.id === currentBaseId }" role="button" tabindex="0"
-					@click="selectBase(item)" @keydown.enter="selectBase(item)" @keydown.space.prevent="selectBase(item)">
+				<div
+					v-for="item in knowledgeBases"
+					:key="item.id"
+					class="base-card"
+					:class="{ 'base-card--active': item.id === currentBaseId }"
+					role="button"
+					tabindex="0"
+					@click="selectBase(item)"
+					@keydown.enter="selectBase(item)"
+					@keydown.space.prevent="selectBase(item)"
+				>
 					<div class="base-card__top">
 						<div class="base-card__title">{{ item.name }}</div>
 						<el-tag :type="item.type === 'faq' ? 'warning' : 'success'" size="small">
@@ -123,7 +141,7 @@ async function fetchKnowledgeBases(reset = true) {
 
 	try {
 		const pageIndex = reset ? 1 : currentPage.value + 1
-		const res = await knowledge.postKnowledgeBasePageList({
+		const res = await knowledge.postAiKnowledgeBasePageList({
 			keyword: keyword.value,
 			type: typeFilter.value,
 			pageIndex,
@@ -189,7 +207,7 @@ async function removeKnowledgeBase(id: string) {
 		.catch(() => false)
 	if (!confirmed) return
 
-	await knowledge.postKnowledgeBaseOpenApiDelete({ id })
+	await knowledge.postAiKnowledgeBaseOpenApiDelete({ id })
 	ElMessage.success('知识库已删除')
 	await refreshKnowledgeBases()
 }

+ 253 - 13
apps/web/src/views/knowledge/QaManage.vue

@@ -332,11 +332,42 @@
 				</div>
 			</template>
 		</el-dialog>
+		<el-dialog
+			v-model="importTaskDialogVisible"
+			title="导入任务状态"
+			width="560px"
+			:close-on-click-modal="false"
+		>
+			<div class="import-task">
+				<div v-if="importTaskInfo" class="import-task__meta">
+					<div class="import-task__row">
+						<span class="import-task__label">任务编号</span>
+						<span class="import-task__value">{{ importTaskInfo.code || '-' }}</span>
+					</div>
+					<div class="import-task__row">
+						<span class="import-task__label">创建时间</span>
+						<span class="import-task__value">{{ importTaskInfo.creationTime || '-' }}</span>
+					</div>
+				</div>
+
+				<div class="import-task__status">
+					<el-tag :type="getImportTaskStatusTag(importTaskInfo?.status)">
+						{{ getImportTaskStatusText(importTaskInfo?.status) }}
+					</el-tag>
+					<span class="import-task__progress"> 进度:{{ importTaskInfo?.progress || '-' }} </span>
+				</div>
+			</div>
+			<template #footer>
+				<div class="drawer-footer">
+					<el-button @click="handleImportTaskDialogClose">关闭</el-button>
+				</div>
+			</template>
+		</el-dialog>
 	</div>
 </template>
 
 <script setup lang="ts">
-import { reactive, ref, watch } from 'vue'
+import { onBeforeUnmount, reactive, ref, watch } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { Plus, Search, Upload, Download } from '@element-plus/icons-vue'
 import { knowledge } from '@repo/api-service'
@@ -355,6 +386,16 @@ interface FaqDetail extends FaqItem {
 	tag_id?: string
 }
 
+interface AsyncTaskInfo {
+	code?: string
+	creationTime?: string
+	htmlStatusInfo?: string
+	id: string
+	progress?: string
+	status?: number
+	updateTime?: string
+}
+
 const props = defineProps<{ currentBaseId: string }>()
 
 const loading = ref(false)
@@ -374,6 +415,9 @@ const pagination = reactive({
 
 const importDialogVisible = ref(false)
 const importLoading = ref(false)
+const importTaskDialogVisible = ref(false)
+const importTaskLoading = ref(false)
+const importTaskInfo = ref<AsyncTaskInfo | null>(null)
 const importFormData = reactive<{
 	knowledge_base_id: string
 	fileId: string
@@ -385,6 +429,7 @@ const importFormData = reactive<{
 })
 
 const importFile = ref<WorkflowUploadFile>()
+let importTaskTimer: number | null = null
 
 type ArrayFieldKey = 'similar_questions' | 'negative_questions' | 'answers'
 
@@ -455,7 +500,7 @@ function applyFaqFormData(data: Partial<FaqDetail>) {
 
 async function fetchFaqDetail(id?: string) {
 	if (!id) return null
-	const res = await knowledge.postFaqInfo({ id })
+	const res = await knowledge.postAiFaqInfo({ id })
 	if (!res?.isSuccess || !res.result) return null
 	return (res.result || null) as FaqDetail | null
 }
@@ -464,7 +509,7 @@ async function fetchFaqList() {
 	if (!props.currentBaseId) return
 	loading.value = true
 	try {
-		const res = await knowledge.postFaqPageList({
+		const res = await knowledge.postAiFaqPageList({
 			knowledge_base_id: props.currentBaseId,
 			pageIndex: pagination.pageIndex,
 			pageSize: pagination.pageSize,
@@ -539,10 +584,10 @@ async function submitForm() {
 			is_enabled: form.is_enabled
 		}
 		if (form.id) {
-			await knowledge.postFaqUpdate({ id: form.id, ...payload })
+			await knowledge.postAiFaqUpdate({ id: form.id, ...payload })
 			ElMessage.success('问答已更新')
 		} else {
-			await knowledge.postFaqCreate({
+			await knowledge.postAiFaqCreate({
 				knowledge_base_id: props.currentBaseId,
 				...payload
 			})
@@ -567,8 +612,8 @@ async function toggleFaqStatus(row: FaqItem, value: string | number | boolean) {
 		ElMessage.error('问答详情加载失败')
 		return
 	}
-	await knowledge.postFaqUpdate({
-		id: detail.id,
+	await knowledge.postAiFaqUpdate({
+		id: row.id,
 		standard_question: detail.standard_question || '',
 		similar_questions: normalizeStringArray(detail.similar_questions),
 		negative_questions: normalizeStringArray(detail.negative_questions),
@@ -588,7 +633,7 @@ async function removeFaq(id?: string) {
 		.catch(() => false)
 	if (!confirmed) return
 
-	await knowledge.postFaqOpenApiDelete({ id })
+	await knowledge.postAiFaqOpenApiDelete({ id })
 	ElMessage.success('问答已删除')
 	await refreshFaqList()
 }
@@ -608,6 +653,113 @@ function openImportModal() {
 	importDialogVisible.value = true
 }
 
+function resetImportForm() {
+	importFile.value = undefined
+	importFormData.fileId = ''
+	importFormData.mode = 'append'
+}
+
+function clearImportTaskTimer() {
+	if (importTaskTimer) {
+		window.clearTimeout(importTaskTimer)
+		importTaskTimer = null
+	}
+}
+
+function getImportTaskStatusText(status?: number) {
+	switch (status) {
+		case 0:
+			return '已创建'
+		case 1:
+			return '运行中'
+		case 2:
+			return '成功'
+		case 3:
+			return '失败'
+		case 4:
+			return '挂起'
+		default:
+			return '处理中'
+	}
+}
+
+function getImportTaskStatusTag(status?: number) {
+	switch (status) {
+		case 2:
+			return 'success'
+		case 3:
+			return 'danger'
+		case 4:
+			return 'warning'
+		default:
+			return 'primary'
+	}
+}
+
+function extractAsyncTaskId(payload: any) {
+	if (!payload) return ''
+
+	return (
+		payload.id ||
+		payload.result?.id ||
+		payload.appAsynTaskId ||
+		payload.result?.appAsynTaskId ||
+		payload.taskId ||
+		payload.result?.taskId ||
+		''
+	)
+}
+
+async function fetchImportTaskInfo(taskId: string) {
+	const res = await knowledge.postBpmGetAsynTaskInfo({ id: taskId })
+	if (!res?.isSuccess || !res.result) {
+		throw new Error((res as any)?.error || '异步任务状态查询失败')
+	}
+
+	importTaskInfo.value = res.result as AsyncTaskInfo
+	return importTaskInfo.value
+}
+
+async function pollImportTask(taskId: string) {
+	importTaskLoading.value = true
+	try {
+		const taskInfo = await fetchImportTaskInfo(taskId)
+		const status = taskInfo?.status
+
+		if (status === 2) {
+			clearImportTaskTimer()
+			ElMessage.success('导入成功')
+			await refreshFaqList()
+			importTaskDialogVisible.value = false
+			importTaskInfo.value = null
+			return
+		}
+
+		if (status === 3) {
+			clearImportTaskTimer()
+			ElMessage.error('导入失败,请查看任务状态信息')
+			return
+		}
+
+		clearImportTaskTimer()
+		importTaskTimer = window.setTimeout(() => {
+			void pollImportTask(taskId)
+		}, 1000)
+	} catch (error) {
+		clearImportTaskTimer()
+		console.error('Fetch import task failed:', error)
+		ElMessage.error('异步任务状态查询失败,请稍后重试')
+	} finally {
+		importTaskLoading.value = false
+	}
+}
+
+function handleImportTaskDialogClose() {
+	clearImportTaskTimer()
+	importTaskDialogVisible.value = false
+	importTaskInfo.value = null
+}
+
 function downloadTemplate() {
 	window.open('/Content/Template/faq_example.xlsx', '_blank')
 }
@@ -630,17 +782,30 @@ async function submitImport() {
 			mode: importFormData.mode
 		}
 
-		const res = await knowledge.postFaqBatchImport(payload)
+		const res = await knowledge.postAiFaqBatchImport(payload)
 
 		if (!res.isSuccess) {
 			ElMessage.error(res.error)
 			return
 		}
-		ElMessage.success('导入成功')
+
+		const taskId = extractAsyncTaskId(res)
 		importDialogVisible.value = false
-		await refreshFaqList()
-		importFile.value = undefined
-		importFormData.mode = 'append'
+		resetImportForm()
+
+		if (!taskId) {
+			ElMessage.success('导入成功')
+			await refreshFaqList()
+			return
+		}
+
+		importTaskInfo.value = {
+			id: taskId,
+			status: 0,
+			progress: '0%'
+		}
+		importTaskDialogVisible.value = true
+		await pollImportTask(taskId)
 	} catch (error) {
 		console.error('Import failed:', error)
 		ElMessage.error('导入失败,请检查文件格式或联系管理员')
@@ -652,18 +817,26 @@ async function submitImport() {
 watch(
 	() => props.currentBaseId,
 	() => {
+		clearImportTaskTimer()
 		keyword.value = ''
 		faqList.value = []
 		pagination.pageIndex = 1
 		pagination.totalCount = 0
 		detailVisible.value = false
 		detailData.value = null
+		importTaskDialogVisible.value = false
+		importTaskInfo.value = null
+		resetImportForm()
 		if (props.currentBaseId) {
 			void fetchFaqList()
 		}
 	},
 	{ immediate: true }
 )
+
+onBeforeUnmount(() => {
+	clearImportTaskTimer()
+})
 </script>
 
 <style scoped lang="less">
@@ -853,4 +1026,71 @@ watch(
 	color: #909399;
 	line-height: 1.4;
 }
+
+.import-task {
+	display: flex;
+	flex-direction: column;
+	gap: 16px;
+	min-height: 180px;
+}
+
+.import-task__status {
+	display: flex;
+	align-items: center;
+	gap: 12px;
+}
+
+.import-task__progress {
+	font-size: 13px;
+	color: #6b7280;
+}
+
+.import-task__meta {
+	display: grid;
+	gap: 10px;
+	padding: 14px;
+	border-radius: 10px;
+	background: #f8fafc;
+}
+
+.import-task__row {
+	display: grid;
+	grid-template-columns: 72px minmax(0, 1fr);
+	gap: 10px;
+	font-size: 13px;
+}
+
+.import-task__label {
+	color: #6b7280;
+}
+
+.import-task__value {
+	color: #111827;
+	word-break: break-all;
+}
+
+.import-task__detail {
+	padding: 14px;
+	border: 1px solid #e5e7eb;
+	border-radius: 10px;
+	background: #fff;
+}
+
+.import-task__detail-title {
+	margin-bottom: 8px;
+	font-size: 13px;
+	font-weight: 600;
+	color: #111827;
+}
+
+.import-task__detail-body {
+	font-size: 13px;
+	line-height: 1.7;
+	color: #111827;
+	word-break: break-word;
+}
+
+.import-task__detail-body :deep(*) {
+	max-width: 100%;
+}
 </style>

+ 148 - 29
apps/web/src/views/knowledge/components/KnowledgeBaseEditModal.vue

@@ -185,7 +185,7 @@
 								启用后可上传音频文件并整段转写为文本(常见格式:mp3、wav、m4a、flac、ogg
 								等)。暂不支持视频上传。
 							</div>
-							<el-form-item label="启用音频语音识别">
+							<el-form-item label="音频语音识别">
 								<div class="switch-wrap">
 									<el-segmented
 										v-model="form.asr_enabled"
@@ -233,6 +233,28 @@
 					<el-tab-pane v-if="isDocumentType" label="分块设置" name="chunk">
 						<div class="tab-intro">配置文档分块参数,优化检索效果</div>
 						<div class="collapse-body">
+							<el-form-item label="分块策略">
+								<div class="slider-wrap">
+									<div class="field-tip mb-4px mt-0">
+										选择文档的分块方式。自动模式会分析每个文档的结构并选择最佳策略。
+									</div>
+									<el-segmented
+										v-model="form.chunk_strategy"
+										class="selection-segmented"
+										:options="chunkStrategyOptions"
+									/>
+									<!-- <el-select
+										v-model="form.chunk_strategy"
+										filterable
+										default-first-option
+										placeholder="请选择"
+										:options="chunkStrategyOptions"
+									/> -->
+									<div class="field-tip">
+										{{ chunkSrategyTip?.[form.chunk_strategy] }}
+									</div>
+								</div>
+							</el-form-item>
 							<el-form-item label="分块大小">
 								<div class="slider-wrap">
 									<el-slider
@@ -317,6 +339,54 @@
 									</div>
 								</el-form-item>
 							</div>
+
+							<el-collapse class="chunk-advanced-collapse mt-12px">
+								<el-collapse-item title="高级" name="advanced">
+									<div class="chunk-advanced-panel">
+										<el-form-item label="Token 上限">
+											<div class="slider-wrap">
+												<el-input-number
+													v-model="form.token_limit"
+													:min="0"
+													:max="8192"
+													:step="1"
+													style="width: 100%"
+												/>
+												<div class="field-tip">
+													每个分块的硬性 Token 上限(0-8192)。0 = 关闭(仅按字符数)。当嵌入模型
+													Token 上限较小时启用:MiniLM (256 tok) 用 200,BGE/Cohere (512 tok) 用
+													400。现代嵌入器(OpenAI、Voyage、Jina-v3)支持 &gt;2000 tokens,保持 0
+													即可。
+												</div>
+											</div>
+										</el-form-item>
+
+										<el-form-item label="语言提示">
+											<div class="switch-wrap">
+												<el-select
+													v-model="form.languages"
+													multiple
+													clearable
+													collapse-tags
+													collapse-tags-tooltip
+													placeholder="选择语言提示"
+												>
+													<el-option
+														v-for="item in languageOptions"
+														:key="item.value"
+														:label="item.label"
+														:value="item.value"
+													/>
+												</el-select>
+												<div class="field-tip">
+													限制启发式模式只识别选定的语言(DE/EN/ZH)。留空 =
+													自动检测。同质化语料库可显式设置以避免跨语言误匹配。
+												</div>
+											</div>
+										</el-form-item>
+									</div>
+								</el-collapse-item>
+							</el-collapse>
 						</div>
 					</el-tab-pane>
 
@@ -427,6 +497,11 @@ const separatorOptions = [
 ]
 const DEFAULT_SEPARATORS = separatorOptions.map((item) => item.value)
 const parserEngineOptions = ['builtin', 'markitdown', 'simple']
+const languageOptions = [
+	{ label: '中文', value: 'zh' },
+	{ label: '英语', value: 'en' },
+	{ label: '德语', value: 'de' }
+]
 const storageProviderOptions = ref<{ label: string; value: string }[]>([])
 const knowledgeTypeOptions = [
 	{ label: '文档', value: 'document' },
@@ -471,6 +546,7 @@ const createDefaultForm = (): KnowledgeBaseForm => ({
 	wiki_enabled: false,
 	question_generation_enabled: true,
 	question_count: 3,
+	chunk_strategy: 'auto',
 	chunk_size: 512,
 	chunk_overlap: 100,
 	separators: [...DEFAULT_SEPARATORS],
@@ -478,12 +554,42 @@ const createDefaultForm = (): KnowledgeBaseForm => ({
 	enable_parent_child: true,
 	parent_chunk_size: 4096,
 	child_chunk_size: 384,
+	token_limit: 0,
+	languages: [],
 	storage_provider: 'local',
 	wiki_extraction_granularity: 'standard',
 	wiki_max_pages_per_ingest: 0,
 	wiki_synthesis_model_id: ''
 })
 
+const chunkStrategyOptions = [
+	{
+		label: '自动',
+		value: 'auto'
+	},
+	{
+		label: '按标题切分',
+		value: 'heading'
+	},
+	{
+		label: '结构感知',
+		value: 'heuristic'
+	},
+	{
+		label: '按长度切分',
+		value: 'legacy'
+	}
+]
+
+const chunkSrategyTip = {
+	auto: '文档分析器根据内容结构自动在「按标题切分」「结构感知」「按长度切分」之间选择。',
+	heading:
+		'在 Markdown 标题(#、##、###)边界处切分,每块自动带上所在标题路径。适合结构清晰的 Markdown 文档。',
+	heuristic:
+		'识别分页符、编号章节、多语言章节标记(DE/EN/ZH)、全大写标题等结构信号进行切分。适合没有 Markdown 标题的 PDF / 扫描件。',
+	legacy: '忽略结构,仅按字符数和分隔符递归切分——原始行为。当上述策略对你的内容效果不佳时使用。'
+}
+
 const form = reactive<KnowledgeBaseForm>(createDefaultForm())
 
 const embeddingModels = computed(() => modelList.value.filter((item) => item.type === 'Embedding'))
@@ -509,7 +615,7 @@ async function fetchModels() {
 }
 
 async function fetchStorageProviders() {
-	const res = await storageProvider.postStorageProviderEngines({})
+	const res = await storageProvider.postAiStorageProviderEngines({})
 	if (res?.isSuccess) {
 		storageProviderOptions.value = (res.result || [])
 			.filter((item) => {
@@ -582,21 +688,6 @@ function buildModelLabel(item: KnowledgeModelOption) {
 	return title ? `${title} (${item.name})` : item.name
 }
 
-function formatSeparatorLabel(value: string) {
-	if (value === '\n\n') return '\\n\\n'
-	if (value === '\n') return '\\n'
-	if (value === ' ') return '[space]'
-	return value
-}
-
-function toggleSeparator(value: string) {
-	if (form.separators.includes(value)) {
-		form.separators = form.separators.filter((item) => item !== value)
-		return
-	}
-	form.separators = [...form.separators, value]
-}
-
 function handleKnowledgeBaseTypeChange(type: KnowledgeBaseForm['type']) {
 	activeTab.value = 'basic'
 	if (type === 'faq') {
@@ -625,6 +716,8 @@ function handleKnowledgeBaseTypeChange(type: KnowledgeBaseForm['type']) {
 	form.enable_parent_child = true
 	form.parent_chunk_size = 4096
 	form.child_chunk_size = 384
+	form.token_limit = 0
+	form.languages = []
 	form.storage_provider = 'local'
 	form.wiki_extraction_granularity = 'standard'
 	form.wiki_max_pages_per_ingest = 0
@@ -650,10 +743,22 @@ function openCreateDrawer() {
 async function openEditDrawer(id: string) {
 	editingId.value = id
 	resetForm()
-	const res = await knowledge.postKnowledgeBaseInfo({ id })
+	const res = await knowledge.postAiKnowledgeBaseInfo({ id })
 	if (!res?.isSuccess) return
 
 	const detail = res.result
+	const chunkingConfig = (detail.chunking_config || {}) as {
+		strategy?: KnowledgeBaseForm['chunk_strategy']
+		chunk_size?: number
+		chunk_overlap?: number
+		separators?: string[]
+		parser_engine_rules?: ParserEngineRule[]
+		enable_parent_child?: boolean
+		parent_chunk_size?: number
+		child_chunk_size?: number
+		tokenLimit?: number
+		languages?: string[]
+	}
 	Object.assign(form, {
 		name: detail.name,
 		description: detail.description || '',
@@ -673,15 +778,18 @@ async function openEditDrawer(id: string) {
 		wiki_enabled: detail.indexing_strategy?.wiki_enabled ?? false,
 		question_generation_enabled: detail.question_generation_config?.enabled ?? true,
 		question_count: detail.question_generation_config?.question_count ?? 3,
-		chunk_size: detail.chunking_config?.chunk_size ?? 512,
-		chunk_overlap: detail.chunking_config?.chunk_overlap ?? 100,
-		separators: detail.chunking_config?.separators?.length
-			? [...detail.chunking_config.separators]
+		chunk_strategy: chunkingConfig.strategy ?? 'auto',
+		chunk_size: chunkingConfig.chunk_size ?? 512,
+		chunk_overlap: chunkingConfig.chunk_overlap ?? 100,
+		separators: chunkingConfig.separators?.length
+			? [...chunkingConfig.separators]
 			: [...DEFAULT_SEPARATORS],
-		parser_engine_rules: cloneParserRules(detail.chunking_config?.parser_engine_rules),
-		enable_parent_child: detail.chunking_config?.enable_parent_child ?? true,
-		parent_chunk_size: detail.chunking_config?.parent_chunk_size ?? 4096,
-		child_chunk_size: detail.chunking_config?.child_chunk_size ?? 384,
+		parser_engine_rules: cloneParserRules(chunkingConfig.parser_engine_rules),
+		enable_parent_child: chunkingConfig.enable_parent_child ?? true,
+		parent_chunk_size: chunkingConfig.parent_chunk_size ?? 4096,
+		child_chunk_size: chunkingConfig.child_chunk_size ?? 384,
+		token_limit: chunkingConfig.tokenLimit ?? 0,
+		languages: chunkingConfig.languages?.length ? [...chunkingConfig.languages] : [],
 		storage_provider:
 			detail.storage_provider_config?.provider || detail.storage_config?.provider || 'local',
 		wiki_extraction_granularity: detail.wiki_config?.extraction_granularity || 'standard',
@@ -720,13 +828,16 @@ function buildCommonPayload() {
 				: form.summary_model_id
 		},
 		chunking_config: {
+			strategy: form.chunk_strategy,
 			chunk_size: form.chunk_size,
 			chunk_overlap: form.chunk_overlap,
 			separators: form.separators.length ? form.separators : [...DEFAULT_SEPARATORS],
 			parser_engine_rules: cloneParserRules(form.parser_engine_rules),
 			enable_parent_child: form.enable_parent_child,
 			parent_chunk_size: form.parent_chunk_size,
-			child_chunk_size: form.child_chunk_size
+			child_chunk_size: form.child_chunk_size,
+			tokenLimit: form.token_limit,
+			languages: [...form.languages]
 		},
 		vlm_config: {
 			model_id: vlmEnabled ? form.vlm_model_id : '',
@@ -785,13 +896,13 @@ async function submitForm() {
 	try {
 		const payload = buildPayload()
 		if (editingId.value) {
-			await knowledge.postKnowledgeBaseUpdate({
+			await knowledge.postAiKnowledgeBaseUpdate({
 				id: editingId.value,
 				...(payload as any)
 			} as any)
 			ElMessage.success('知识库已更新')
 		} else {
-			await knowledge.postKnowledgeBaseCreate(payload as any)
+			await knowledge.postAiKnowledgeBaseCreate(payload as any)
 			ElMessage.success('知识库已创建')
 		}
 		drawerVisible.value = false
@@ -979,6 +1090,14 @@ onMounted(async () => {
 		color: var(--agent-text-soft);
 	}
 
+	.chunk-advanced-collapse {
+		margin-left: 120px;
+	}
+
+	.chunk-advanced-panel {
+		padding-top: 12px;
+	}
+
 	.parser-rule-list {
 		display: grid;
 		gap: 12px;

+ 5 - 5
apps/web/src/views/knowledge/components/StorageModal.vue

@@ -460,7 +460,7 @@ function extractConfig(result: Record<string, any>) {
 async function loadEngines() {
 	engineLoading.value = true
 	try {
-		const res = await storageProvider.postStorageProviderEngines({})
+		const res = await storageProvider.postAiStorageProviderEngines({})
 		if (res?.isSuccess) {
 			engines.value = (res.result || [])
 				.filter((item): item is EngineItem =>
@@ -486,7 +486,7 @@ async function loadEngines() {
 async function handleInit() {
 	initLoading.value = true
 	try {
-		const res = await storageProvider.postStorageProviderInitStorageProvider({})
+		const res = await storageProvider.postAiStorageProviderInitStorageProvider({})
 		if (res?.isSuccess) {
 			ElMessage.success('初始化成功')
 			await loadEngines()
@@ -503,7 +503,7 @@ async function handleInit() {
 async function loadProviderConfig(provider: StorageProviderName) {
 	providerLoading.value = provider
 	try {
-		const res = await storageProvider.postStorageProviderConfig({ name: provider })
+		const res = await storageProvider.postAiStorageProviderConfig({ name: provider })
 		if (res?.isSuccess && res.result) {
 			const raw = res.result as Record<string, any>
 			assignConfig(provider, extractConfig(raw))
@@ -547,7 +547,7 @@ async function testProvider(provider: StorageProviderName) {
 		if (selectedProvider.value !== provider || !loadedProviders.value[provider]) {
 			await loadProviderConfig(provider)
 		}
-		const res = await storageProvider.postStorageProviderCheck(buildCheckPayload(provider) as any)
+		const res = await storageProvider.postAiStorageProviderCheck(buildCheckPayload(provider) as any)
 		if (res?.isSuccess) {
 			ElMessage.success(`${formatProviderLabel(provider)} 连通成功`)
 			return
@@ -579,7 +579,7 @@ async function saveConfig() {
 	}
 	saving.value = true
 	try {
-		const res = await storageProvider.postStorageProviderUpdate(buildUpdatePayload() as any)
+		const res = await storageProvider.postAiStorageProviderUpdate(buildUpdatePayload() as any)
 		if (res?.isSuccess) {
 			ElMessage.success('配置已保存')
 			await loadEngines()

+ 6 - 0
apps/web/src/views/knowledge/types.ts

@@ -13,9 +13,12 @@ export interface KnowledgeBaseConfig {
 		chunk_overlap: number
 		chunk_size: number
 		enable_parent_child: boolean
+		languages?: string[]
 		parent_chunk_size: number
 		parser_engine_rules: ParserEngineRule[]
 		separators: string[]
+		strategy?: 'auto' | 'heading' | 'heuristic' | 'legacy'
+		tokenLimit?: number
 	}
 	creationTime?: string
 	creatorUserId?: string
@@ -72,6 +75,7 @@ export interface KnowledgeBaseForm {
 	wiki_enabled: boolean
 	question_generation_enabled: boolean
 	question_count: number
+	chunk_strategy: 'auto' | 'heading' | 'heuristic' | 'legacy'
 	chunk_size: number
 	chunk_overlap: number
 	separators: string[]
@@ -79,6 +83,8 @@ export interface KnowledgeBaseForm {
 	enable_parent_child: boolean
 	parent_chunk_size: number
 	child_chunk_size: number
+	token_limit: number
+	languages: string[]
 	storage_provider: string
 	wiki_extraction_granularity: string
 	wiki_max_pages_per_ingest: number

+ 7 - 7
apps/web/src/views/resource/components/StorageManager.vue

@@ -524,7 +524,7 @@ function extractConfig(result: Record<string, any>) {
 async function loadEngines() {
 	engineLoading.value = true
 	try {
-		const res = await storageProvider.postStorageProviderEngines({})
+		const res = await storageProvider.postAiStorageProviderEngines({})
 		if (res?.isSuccess) {
 			engines.value = (res.result || [])
 				.filter((item): item is EngineItem =>
@@ -546,7 +546,7 @@ async function loadEngines() {
 
 async function loadDefaultProvider() {
 	try {
-		const res = await storageProvider.postStorageProviderDefaultProvider({})
+		const res = await storageProvider.postAiStorageProviderDefaultProvider({})
 		if (res?.isSuccess && res.result && providerNames.includes(res.result as StorageProviderName)) {
 			currentDefaultProvider.value = res.result as StorageProviderName
 			form.default_provider = res.result as StorageProviderName
@@ -561,7 +561,7 @@ async function loadDefaultProvider() {
 async function handleInit() {
 	initLoading.value = true
 	try {
-		const res = await storageProvider.postStorageProviderInitStorageProvider({})
+		const res = await storageProvider.postAiStorageProviderInitStorageProvider({})
 		if (res?.isSuccess) {
 			ElMessage.success('初始化成功')
 			await Promise.all([loadEngines(), loadDefaultProvider()])
@@ -578,7 +578,7 @@ async function handleInit() {
 async function loadProviderConfig(provider: StorageProviderName) {
 	providerLoading.value = provider
 	try {
-		const res = await storageProvider.postStorageProviderConfig({ name: provider })
+		const res = await storageProvider.postAiStorageProviderConfig({ name: provider })
 		if (res?.isSuccess && res.result) {
 			const raw = res.result as Record<string, any>
 			assignConfig(provider, extractConfig(raw))
@@ -626,7 +626,7 @@ async function testProvider(provider: StorageProviderName) {
 		if (selectedProvider.value !== provider || !loadedProviders.value[provider]) {
 			await loadProviderConfig(provider)
 		}
-		const res = await storageProvider.postStorageProviderCheck(buildCheckPayload(provider) as any)
+		const res = await storageProvider.postAiStorageProviderCheck(buildCheckPayload(provider) as any)
 		if (res?.isSuccess) {
 			ElMessage.success(`${formatProviderLabel(provider)} 连通成功`)
 			return
@@ -642,7 +642,7 @@ async function testProvider(provider: StorageProviderName) {
 async function updateDefaultProvider(provider: StorageProviderName) {
 	settingDefaultProvider.value = provider
 	try {
-		const res = await storageProvider.postStorageProviderUpdateDefaultProvider({ provider })
+		const res = await storageProvider.postAiStorageProviderUpdateDefaultProvider({ provider })
 		if (res?.isSuccess) {
 			currentDefaultProvider.value = provider
 			form.default_provider = provider
@@ -672,7 +672,7 @@ async function saveConfig() {
 	}
 	saving.value = true
 	try {
-		const res = await storageProvider.postStorageProviderUpdate(buildUpdatePayload() as any)
+		const res = await storageProvider.postAiStorageProviderUpdate(buildUpdatePayload() as any)
 		if (res?.isSuccess) {
 			ElMessage.success('配置已保存')
 			await Promise.all([loadEngines(), loadDefaultProvider()])

+ 192 - 0
packages/api-service/schema/knowledge.openapi.json

@@ -26,6 +26,198 @@
 		}
 	],
 	"paths": {
+		"/api/bpm/getAsynTaskInfo": {
+			"post": {
+				"summary": "查询异步任务状态",
+				"deprecated": false,
+				"description": "状态 0: 已创建  1: 运行中  2: 成功  3: 失败  4: 挂起",
+				"tags": ["knowledge"],
+				"parameters": [
+					{
+						"name": "Authorization",
+						"in": "header",
+						"description": "",
+						"example": "bpm_client_1519719271393923072",
+						"schema": {
+							"type": "string"
+						}
+					}
+				],
+				"requestBody": {
+					"content": {
+						"application/json": {
+							"schema": {
+								"type": "object",
+								"properties": {
+									"id": {
+										"type": "string"
+									}
+								},
+								"required": ["id"]
+							},
+							"example": {
+								"id": "a7d88070-5dd3-404e-ac02-429a883cf40c"
+							}
+						}
+					},
+					"required": true
+				},
+				"responses": {
+					"200": {
+						"description": "",
+						"content": {
+							"application/json": {
+								"schema": {
+									"type": "object",
+									"properties": {
+										"isSuccess": {
+											"type": "boolean"
+										},
+										"code": {
+											"type": "integer"
+										},
+										"result": {
+											"type": "object",
+											"properties": {
+												"appPageCode": {
+													"type": "string"
+												},
+												"argsInput": {
+													"type": "object",
+													"properties": {
+														"args": {
+															"type": "array",
+															"items": {
+																"type": "object",
+																"properties": {
+																	"name": {
+																		"type": "string"
+																	},
+																	"value": {
+																		"type": "string"
+																	}
+																}
+															}
+														}
+													},
+													"required": ["args"]
+												},
+												"code": {
+													"type": "string"
+												},
+												"creationTime": {
+													"type": "string"
+												},
+												"creatorUserId": {
+													"type": "string"
+												},
+												"downFileId": {
+													"type": "string"
+												},
+												"entityId": {
+													"type": "string"
+												},
+												"fileId": {
+													"type": "string"
+												},
+												"htmlStatusInfo": {
+													"type": "string"
+												},
+												"id": {
+													"type": "string"
+												},
+												"isDeleted": {
+													"type": "boolean"
+												},
+												"nodeId": {
+													"type": "string"
+												},
+												"params": {
+													"type": "string"
+												},
+												"progress": {
+													"type": "string"
+												},
+												"status": {
+													"type": "integer"
+												},
+												"taskId": {
+													"type": "string"
+												},
+												"type": {
+													"type": "integer"
+												},
+												"updateTime": {
+													"type": "string"
+												}
+											},
+											"required": [
+												"appPageCode",
+												"argsInput",
+												"code",
+												"creationTime",
+												"creatorUserId",
+												"downFileId",
+												"entityId",
+												"fileId",
+												"htmlStatusInfo",
+												"id",
+												"isDeleted",
+												"nodeId",
+												"params",
+												"progress",
+												"status",
+												"taskId",
+												"type",
+												"updateTime"
+											]
+										},
+										"isAuthorized": {
+											"type": "boolean"
+										}
+									},
+									"required": ["isSuccess", "code", "result", "isAuthorized"]
+								},
+								"example": {
+									"isSuccess": true,
+									"code": 1,
+									"result": {
+										"appPageCode": "",
+										"argsInput": {
+											"args": [
+												{
+													"name": "dto",
+													"value": "{\"fileId\":\"fe489262-4558-473c-aa8b-790a23f93a6c\",\"knowledge_base_id\":\"4eb56dcf-a376-4b1d-9475-88d0595d1343\",\"mode\":\"replace\",\"userId\":\"7F8A2BFE-402D-4499-9BB8-2EF7FFC7B993\"}"
+												}
+											]
+										},
+										"code": "2026060416453046069pq2bshgy",
+										"creationTime": "2026-06-04 16:45:30",
+										"creatorUserId": "7F8A2BFE-402D-4499-9BB8-2EF7FFC7B993",
+										"downFileId": "",
+										"entityId": "",
+										"fileId": "",
+										"htmlStatusInfo": "任务编号:2026060416453046069pq2bshgy\r\n当前状态:成功\r\n",
+										"id": "a7d88070-5dd3-404e-ac02-429a883cf40c",
+										"isDeleted": false,
+										"nodeId": "",
+										"params": "{\"args\":[{\"name\":\"dto\",\"value\":\"{\\\"fileId\\\":\\\"fe489262-4558-473c-aa8b-790a23f93a6c\\\",\\\"knowledge_base_id\\\":\\\"4eb56dcf-a376-4b1d-9475-88d0595d1343\\\",\\\"mode\\\":\\\"replace\\\",\\\"userId\\\":\\\"7F8A2BFE-402D-4499-9BB8-2EF7FFC7B993\\\"}\"}]}",
+										"progress": "",
+										"status": 2,
+										"taskId": "",
+										"type": 17,
+										"updateTime": "2026-06-04 16:45:30"
+									},
+									"isAuthorized": true
+								}
+							}
+						},
+						"headers": {}
+					}
+				},
+				"security": []
+			}
+		},
 		"/api/ai/knowledge/info": {
 			"post": {
 				"summary": "获取详情",

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 722 - 680
packages/api-service/servers/knowledge/api/knowledge.ts


+ 7 - 7
packages/api-service/servers/knowledge/api/storageProvider.ts

@@ -3,7 +3,7 @@
 import request from '@repo/api-client'
 
 /** 检查存储引擎连通性 POST /api/ai/storage-provider/check */
-export async function postStorageProviderCheck(
+export async function postAiStorageProviderCheck(
   body: {
     provider: string
     config: {
@@ -33,7 +33,7 @@ export async function postStorageProviderCheck(
 }
 
 /** 获取配置信息 POST /api/ai/storage-provider/config */
-export async function postStorageProviderConfig(
+export async function postAiStorageProviderConfig(
   body: {
     name: string
   },
@@ -64,7 +64,7 @@ export async function postStorageProviderConfig(
 }
 
 /** 获取默认存储厂商 POST /api/ai/storage-provider/default-provider */
-export async function postStorageProviderDefaultProvider(
+export async function postAiStorageProviderDefaultProvider(
   body: {},
   options?: { [key: string]: any }
 ) {
@@ -82,7 +82,7 @@ export async function postStorageProviderDefaultProvider(
 }
 
 /** 获取支持的引擎列表 POST /api/ai/storage-provider/engines */
-export async function postStorageProviderEngines(body: {}, options?: { [key: string]: any }) {
+export async function postAiStorageProviderEngines(body: {}, options?: { [key: string]: any }) {
   return request<{
     isSuccess: boolean
     code: number
@@ -99,7 +99,7 @@ export async function postStorageProviderEngines(body: {}, options?: { [key: str
 }
 
 /** 初始化存储厂商信息 POST /api/ai/storage-provider/initStorageProvider */
-export async function postStorageProviderInitStorageProvider(
+export async function postAiStorageProviderInitStorageProvider(
   body: {},
   options?: { [key: string]: any }
 ) {
@@ -117,7 +117,7 @@ export async function postStorageProviderInitStorageProvider(
 }
 
 /** 更新配置信息 POST /api/ai/storage-provider/update */
-export async function postStorageProviderUpdate(
+export async function postAiStorageProviderUpdate(
   body: {
     default_provider: string
     cos: {
@@ -187,7 +187,7 @@ export async function postStorageProviderUpdate(
 }
 
 /** 更新默认存储厂商 POST /api/ai/storage-provider/updateDefaultProvider */
-export async function postStorageProviderUpdateDefaultProvider(
+export async function postAiStorageProviderUpdateDefaultProvider(
   body: {
     provider: string
   },

+ 88 - 48
packages/api-service/servers/knowledge/api/wiki.ts

@@ -2,43 +2,8 @@
 /* eslint-disable */
 import request from '@repo/api-client'
 
-type WikiPageResult = {
-  aliases: string[]
-  chunk_refs: string[]
-  content: string
-  creationTime: string
-  creatorUserId: string
-  id: string
-  in_links: string[]
-  isDeleted: boolean
-  knowledge_base_id: string
-  out_links: string[]
-  page_metadata: Record<string, any>
-  page_type: string
-  slug: string
-  source_refs: string[]
-  status: string
-  summary: string
-  title: string
-  updateTime: string
-  version: number
-}
-
-type WikiIndexListItem = {
-  creationTime: string
-  id: string
-  knowledge_base_id: string
-  page_type: string
-  slug: string
-  status: string
-  summary?: string
-  content?: string
-  title: string
-  version: number
-}
-
 /** 获取图谱信息 POST /api/ai/wiki/graph */
-export async function postWikiGraph(
+export async function postAiWikiGraph(
   body: {
     knowledge_base_id: string
   },
@@ -83,7 +48,7 @@ export async function postWikiGraph(
 }
 
 /** 获取首页 POST /api/ai/wiki/index */
-export async function postWikiIndex(
+export async function postAiWikiIndex(
   body: {
     knowledge_base_id: string
   },
@@ -93,10 +58,25 @@ export async function postWikiIndex(
     isSuccess: boolean
     code: number
     result: {
-      concept_pages: WikiIndexListItem[]
-      entity_pages: WikiIndexListItem[]
-      index: WikiPageResult
-      summary_pages: WikiIndexListItem[]
+      aliases: string[]
+      chunk_refs: string[]
+      content: string
+      creationTime: string
+      creatorUserId: string
+      id: string
+      in_links: string[]
+      isDeleted: boolean
+      knowledge_base_id: string
+      out_links: string[]
+      page_metadata: Record<string, any>
+      page_type: string
+      slug: string
+      source_refs: string[]
+      status: string
+      summary: string
+      title: string
+      updateTime: string
+      version: number
     }
     isAuthorized: boolean
   }>('/api/ai/wiki/index', {
@@ -110,7 +90,7 @@ export async function postWikiIndex(
 }
 
 /** 获取日志信息 POST /api/ai/wiki/log */
-export async function postWikiLog(
+export async function postAiWikiLog(
   body: {
     knowledge_base_id: string
   },
@@ -119,7 +99,27 @@ export async function postWikiLog(
   return request<{
     isSuccess: boolean
     code: number
-    result: WikiPageResult
+    result: {
+      aliases: string[]
+      chunk_refs: string[]
+      content: string
+      creationTime: string
+      creatorUserId: string
+      id: string
+      in_links: string[]
+      isDeleted: boolean
+      knowledge_base_id: string
+      out_links: string[]
+      page_metadata: Record<string, any>
+      page_type: string
+      slug: string
+      source_refs: string[]
+      status: string
+      summary: string
+      title: string
+      updateTime: string
+      version: number
+    }
     isAuthorized: boolean
   }>('/api/ai/wiki/log', {
     method: 'POST',
@@ -132,7 +132,7 @@ export async function postWikiLog(
 }
 
 /** 获取页面信息 POST /api/ai/wiki/page */
-export async function postWikiPage(
+export async function postAiWikiPage(
   body: {
     knowledge_base_id: string
     slug: string
@@ -142,7 +142,27 @@ export async function postWikiPage(
   return request<{
     isSuccess: boolean
     code: number
-    result: WikiPageResult
+    result: {
+      aliases: string[]
+      chunk_refs: string[]
+      content: string
+      creationTime: string
+      creatorUserId: string
+      id: string
+      in_links: string[]
+      isDeleted: boolean
+      knowledge_base_id: string
+      out_links: string[]
+      page_metadata: Record<string, any>
+      page_type: string
+      slug: string
+      source_refs: string[]
+      status: string
+      summary: string
+      title: string
+      updateTime: string
+      version: number
+    }
     isAuthorized: boolean
   }>('/api/ai/wiki/page', {
     method: 'POST',
@@ -155,7 +175,7 @@ export async function postWikiPage(
 }
 
 /** 获取wiki页面列表 POST /api/ai/wiki/pageList */
-export async function postWikiPageList(
+export async function postAiWikiPageList(
   body: {
     knowledge_base_id: string
     pageIndex: number
@@ -174,7 +194,27 @@ export async function postWikiPageList(
       currentPage: number
       hasNextPage: boolean
       hasPreviousPage: boolean
-      model: WikiPageResult[]
+      model: {
+        aliases: string[]
+        chunk_refs: string[]
+        content: string
+        creationTime: string
+        creatorUserId: string
+        id: string
+        in_links: string[]
+        isDeleted: boolean
+        knowledge_base_id: string
+        out_links: string[]
+        page_metadata: Record<string, any>
+        page_type: string
+        slug: string
+        source_refs: string[]
+        status: string
+        summary: string
+        title: string
+        updateTime: string
+        version: number
+      }[]
       pageSize: number
       totalCount: number
       totalPages: number
@@ -191,7 +231,7 @@ export async function postWikiPageList(
 }
 
 /** 获取状态 POST /api/ai/wiki/stats */
-export async function postWikiStats(
+export async function postAiWikiStats(
   body: {
     knowledge_base_id: string
   },