Skip to content

Commit d1479cc

Browse files
feat: knowledge
1 parent f156285 commit d1479cc

26 files changed

Lines changed: 532 additions & 62 deletions

File tree

‎ui/AGENTS.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,9 @@ Keep these behaviors:
208208

209209
## Frontend Implementation Notes
210210

211+
- Newly implemented frontend features must not add permission checks or permission-based visibility
212+
or disabled states until the user explicitly requests them. Preserve existing permission logic
213+
outside the requested scope; page-specific guidance is maintained in `src/views/VIEW_README.md`.
211214
- Prefer existing project structure and naming.
212215
- Do not modify shared components under `src/components/` without an explicit user instruction to
213216
change those components. Follow the modification boundary in `src/components/COMPONENT_README.md`.

‎ui/src/api/API_README.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,14 @@ Action、Drawer 或 Dialog。复用方直接使用 `typeof XxxApi` 约束完整
112112

113113
### 知识库维护
114114

115+
`exportKnowledgeExcel`、`exportKnowledgeZip`、`exportKnowledge` 分别通过 GET 请求知识库
116+
`/<knowledgeId>/export`、`export_zip`、`export_knowledge`,复用 `getExportFile` 下载。
117+
三种结果分别为文档 Excel、包含图片的文档 ZIP 和可导入创建的知识库 ZIP;优先使用服务端文件名。
118+
119+
`postKnowledgeImport(file, folderId)` 将文件与 `folder_id` 组装为 FormData,POST 到
120+
`/workspace/<workspaceId>/knowledge/import_knowledge`,响应为 `{ knowledge_id, type }`。
121+
后端校验知识库导出包并创建资源;导入成功后的用户权限和列表刷新由调用页面负责。
122+
115123
`workspace/knowledge/knowledge.ts` 与 `workspace/shared.ts` 的 `getAllKnowledge(query)`
116124
分别查询工作空间及共享知识库的非分页列表,返回 `KnowledgeItem[]`,用于关联知识库选择等
117125
需要全量选项的场景。原有 `getKnowledgePage` 继续用于分页列表。
@@ -121,6 +129,10 @@ Action、Drawer 或 Dialog。复用方直接使用 `typeof XxxApi` 约束完整
121129
组装为 `{ id_list, folder_id }`,`putBatchDeleteKnowledge` 将 ID 数组组装为 `{ id_list }`,
122130
分别使用 PUT 请求 `batch_move` 和 `batch_delete`。页面和 Action 负责类型判断及 loading。
123131

132+
`putReEmbeddingKnowledge` 使用 PUT 请求 `/<knowledgeId>/embedding` 重新向量化。
133+
设置页更换向量模型时先确认、保存,再调用该接口;Web、飞书配置通过 `meta` 提交,保留未编辑的
134+
已有配置,文件数量与大小限制仍作为知识库顶层字段提交。
135+
124136
知识库创建通过 `postKnowledge`、`postWebKnowledge` 分别提交到 `/base`、`/web`;
125137
`postLarkKnowledge` 沿用飞书扩展接口 `/lark/save`,当前开源后端未包含该实现。
126138
基础创建字段由 `KnowledgeCreatePayload` 统一维护,Web、飞书请求扩展对应类型;工作流创建

‎ui/src/api/admin/core/request.ts‎

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -93,15 +93,6 @@ async function getResponseErrorMessage(error: unknown) {
9393
return responseData?.message
9494
}
9595

96-
async function getFetchErrorMessage(response: Response) {
97-
const responseText = await response.text()
98-
try {
99-
const responseData = JSON.parse(responseText) as Partial<ApiResponse<unknown>>
100-
return responseData.message || responseText
101-
} catch {
102-
return responseText
103-
}
104-
}
10596

10697
async function downloadExportResponse(response: AxiosResponse<Blob>, fileName: string, mimeType = 'application/octet-stream') {
10798
if (response.data.type.includes('application/json')) {

‎ui/src/api/admin/workspace/knowledge/knowledge.ts‎

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { del, get, post, put } from '../../core/request'
1+
import { del, get, getExportFile, post, put } from '../../core/request'
22
import type { ParamsPage, ResponsePage } from '../../core/types'
33
import type { Dict, KnowledgeDetail, KnowledgeItem, KnowledgeCreatePayload, WebKnowledgeCreatePayload, LarkKnowledgeCreatePayload } from '@/api/types'
44
import { getWorkspaceId } from '@/utils/resource-context'
@@ -52,6 +52,11 @@ const putLarkKnowledge = (knowledgeId: string, payload: Partial<KnowledgeItem>)
5252
return put<Partial<KnowledgeItem>, KnowledgeItem>(`${getPrefix()}/lark/${knowledgeId}`, payload)
5353
}
5454

55+
/** 对知识库中的文档重新向量化。 */
56+
const putReEmbeddingKnowledge = (knowledgeId: string) => {
57+
return put<undefined, boolean>(`${getPrefix()}/${knowledgeId}/embedding`)
58+
}
59+
5560
/** 批量删除工作空间知识库。 */
5661
const putBatchDeleteKnowledge = (knowledgeIds: string[]) => {
5762
return put<{ id_list: string[] }, boolean>(`${getPrefix()}/batch_delete`, { id_list: knowledgeIds })
@@ -62,12 +67,33 @@ const putBatchMoveKnowledge = (knowledgeIds: string[], folderId: string) => {
6267
return put<{ id_list: string[]; folder_id: string }, boolean>(`${getPrefix()}/batch_move`, { id_list: knowledgeIds, folder_id: folderId })
6368
}
6469

65-
/** 导入知识库文件。 */
66-
const importKnowledgeBundle = (payload: FormData) => {
67-
return post<FormData, unknown>(`${getPrefix()}/import_knowledge`, payload)
70+
/** 将知识库文档导出为 Excel。 */
71+
const exportKnowledgeExcel = (knowledgeId: string, knowledgeName: string) => {
72+
return getExportFile(`${knowledgeName}.xlsx`, `${getPrefix()}/${knowledgeId}/export`)
73+
}
74+
75+
/** 将知识库文档及图片导出为 ZIP。 */
76+
const exportKnowledgeZip = (knowledgeId: string, knowledgeName: string) => {
77+
return getExportFile(`${knowledgeName}.zip`, `${getPrefix()}/${knowledgeId}/export_zip`)
78+
}
79+
80+
/** 导出可用于导入创建的知识库压缩包。 */
81+
const exportKnowledge = (knowledgeId: string, knowledgeName: string) => {
82+
return getExportFile(`${knowledgeName}.zip`, `${getPrefix()}/${knowledgeId}/export_knowledge`)
83+
}
84+
85+
/** 导入知识库文件并在指定文件夹创建知识库。 */
86+
const postKnowledgeImport = (file: File, folderId: string) => {
87+
const payload = new FormData()
88+
payload.append('file', file)
89+
payload.append('folder_id', folderId)
90+
return post<FormData, { knowledge_id: string; type: KnowledgeItem['type'] }>(`${getPrefix()}/import_knowledge`, payload)
6891
}
6992

7093
export default {
94+
exportKnowledgeExcel,
95+
exportKnowledgeZip,
96+
exportKnowledge,
7197
postKnowledge,
7298
postWebKnowledge,
7399
postLarkKnowledge,
@@ -77,7 +103,8 @@ export default {
77103
getKnowledgePage,
78104
putKnowledge,
79105
putLarkKnowledge,
106+
putReEmbeddingKnowledge,
80107
putBatchDeleteKnowledge,
81108
putBatchMoveKnowledge,
82-
importKnowledgeBundle,
109+
postKnowledgeImport,
83110
}

‎ui/src/api/enums/knowledge.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/** 后端知识库类型。 */
2-
export const KNOWLEDGE_TYPE = { BASE: 0, WEB: 1, LARK: 2, YUQUE: 3, WORKFLOW: 4 } as const
2+
export const KNOWLEDGE_TYPE = { BASE: 0, WEB: 1, LARK: 2, WORKFLOW: 4 } as const
33

44
/** 知识库分段检索模式。 */
55
export const KNOWLEDGE_SEARCH_MODE = { EMBEDDING: 'embedding', KEYWORDS: 'keywords', BLEND: 'blend' } as const

‎ui/src/components/COMPONENT_README.md‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,12 +79,15 @@ Admin、Chat 入口调用 `configureMarkdownEditor()` 配置本地高亮、KaTeX
7979

8080
### MkCollapse
8181

82+
`trigger` 默认 `header`,点击标题区域切换;设置为 `indicator` 时,仅点击箭头按钮切换,
83+
标题文字和空白区域不触发。箭头按钮支持键盘操作。
84+
8285
`v-model:expanded` 可控制展开状态;未绑定时沿用 `defaultExpanded` 的内部状态。
8386

8487
标题折叠区,`title` 或 `label` 插槽提供标题,默认插槽提供内容。
85-
`defaultExpanded` 默认 `true`,仅设置初始状态;点击标题内部切换,当前不提供展开状态事件。
88+
`defaultExpanded` 默认 `true`,仅设置初始状态;展开状态通过 `v-model:expanded` 同步。
8689
`indicatorPosition` 默认 `before`,可设为 `after`;`triggerClass`、`triggerStyle` 调整触发区。
87-
内容使用 `v-if`,收起会卸载内部组件。
90+
内容使用 `v-show`,收起时保留内部组件及表单状态。
8891

8992
### MkEmpty
9093

@@ -523,6 +526,10 @@ Skills 场景传入 `[TOOL_TYPE.SKILL]`,并通过 `title` 指定标题。两
523526

524527
### SelectModel
525528

529+
`teleported` 默认为 `false`,画布节点内保留此默认值,使下拉浮层跟随画布。
530+
画布外的表单、Dialog 和 Drawer 显式传入 `teleported`(即 `true`),将下拉浮层挂载到 body,
531+
避免被滚动容器裁剪。动态表单 Model 字段可通过透传属性配置该值。
532+
526533
按供应商分组展示模型,单选 `v-model` 为字符串,`multiple` 模式为字符串数组,变化时触发 `change`。`options` 为
527534
`ModelItem[]`,`providerOptions` 为 `ModelProviderItem[]`,模型列表和供应商列表均由使用方查询。
528535
需要接口加载的模型选项统一使用 `getModelListWithShared`,共享标签读取 `source: 'shared'`。

‎ui/src/components/business/select-model/index.vue‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const props = withDefaults(
2929
canAdd?: boolean
3030
modelParams?: Record<string, unknown>
3131
disabled?: boolean
32+
teleported?: boolean
3233
multiple?: Multiple
3334
}>(),
3435
{
@@ -38,6 +39,7 @@ const props = withDefaults(
3839
canAdd: false,
3940
modelParams: () => ({}),
4041
disabled: false,
42+
teleported: false,
4143
},
4244
)
4345
@@ -129,7 +131,7 @@ function openModelParams() {
129131
filterable
130132
:loading="loading"
131133
:multiple="multiple"
132-
:teleported="false"
134+
:teleported="teleported"
133135
>
134136
<el-option-group v-for="group in modelOptionGroups" :key="group.provider" :label="group.name">
135137
<el-option

‎ui/src/components/global/mk-collapse/index.vue‎

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ const props = withDefaults(
99
defaultExpanded?: boolean
1010
indicatorPosition?: 'after' | 'before'
1111
title?: string
12+
trigger?: 'header' | 'indicator'
1213
triggerClass?: HTMLAttributes['class']
1314
triggerStyle?: HTMLAttributes['style']
1415
}>(),
15-
{ defaultExpanded: true, indicatorPosition: 'before' },
16+
{ defaultExpanded: true, indicatorPosition: 'before', trigger: 'header' },
1617
)
1718
1819
defineSlots<{ default?: () => unknown; label?: () => unknown }>()
@@ -30,26 +31,29 @@ const expanded = computed({
3031

3132
<template>
3233
<section>
33-
<div class="py-2" :class="triggerClass" :style="triggerStyle" @click="expanded = !expanded">
34-
<div class="flex w-full cursor-pointer items-center gap-2 text-left">
35-
<MkIcon
36-
v-if="indicatorPosition === 'before'"
37-
:icon="CaretBottom"
38-
:size="14"
39-
class="transition-transform text-N600!"
40-
:class="{ '-rotate-90': !expanded }"
41-
/>
34+
<div class="py-2" :class="triggerClass" :style="triggerStyle" @click="trigger === 'header' && (expanded = !expanded)">
35+
<div class="flex w-full items-center gap-2 text-left" :class="{ 'cursor-pointer': trigger === 'header' }">
36+
<!-- 展开或收起内容 -->
37+
<button
38+
type="button"
39+
class="flex-center shrink-0 cursor-pointer border-0 bg-transparent p-0"
40+
:class="{ 'order-last': indicatorPosition === 'after' }"
41+
:title="expanded ? '收起' : '展开'"
42+
:aria-expanded="expanded"
43+
@click.stop="expanded = !expanded"
44+
>
45+
<MkIcon
46+
v-if="indicatorPosition === 'before'"
47+
:icon="CaretBottom"
48+
:size="14"
49+
class="transition-transform text-N600!"
50+
:class="{ '-rotate-90': !expanded }"
51+
/>
52+
<MkIcon v-else name="icon_down_outlined" :size="16" class="transition-transform text-N600!" :class="{ '-rotate-180': expanded }" />
53+
</button>
4254
<slot name="label">
4355
<span>{{ title }}</span>
4456
</slot>
45-
46-
<MkIcon
47-
v-if="indicatorPosition === 'after'"
48-
name="icon_down_outlined"
49-
:size="16"
50-
class="transition-transform text-N600!"
51-
:class="{ '-rotate-180': expanded }"
52-
/>
5357
</div>
5458
</div>
5559
<el-collapse-transition>

‎ui/src/components/mk-dynamics-form/constructor/items/ModelConstructor.vue‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ defineExpose({ getData, render })
154154

155155
<el-form-item label="可选模型" prop="provider_list" :rules="[{ required: true, message: '请选择模型', type: 'array' }]">
156156
<SelectModel
157+
teleported
157158
multiple
158159
v-model="selectedIds"
159160
placeholder="请选择模型"
@@ -170,6 +171,6 @@ defineExpose({ getData, render })
170171
:rules="formValue.required ? [{ required: true, message: '请选择模型' }] : []"
171172
v-if="formValue.provider_list && formValue.provider_list.length > 0"
172173
>
173-
<SelectModel v-model="defaultModelId" placeholder="请选择模型" :options="selectedModelsOptions" :provider-options="providerOptions" />
174+
<SelectModel v-model="defaultModelId" placeholder="请选择模型" :options="selectedModelsOptions" :provider-options="providerOptions" teleported />
174175
</el-form-item>
175176
</template>

‎ui/src/constants/CONSTANT_README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ API 枚举值。例如角色类型和登录方式以 `src/api/enums` 为唯一
2121
只维护它们的展示文案。
2222

2323
知识库类型的接口值保持为 `KNOWLEDGE_TYPE` 中的数字。`constants/knowledge.ts` 提供
24-
`KNOWLEDGE_TYPE_MAP`,将数字转换为 `BASE`、`WEB`、`LARK`、`YUQUE`、`WORKFLOW` 字符串,
24+
`KNOWLEDGE_TYPE_MAP`,将数字转换为 `BASE`、`WEB`、`LARK`、`WORKFLOW` 字符串,
2525
供前端判断;`KNOWLEDGE_TYPE_LABELS` 按这些字符串提供展示文案。提交接口时仍使用数字枚举值。
2626
字符串标识统一由 `KNOWLEDGE_TYPE_KEY` 维护,映射、文案和业务判断均引用该常量,避免重复写裸字符串。
2727

0 commit comments

Comments
 (0)