Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions ui/src/api/API_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,9 @@ System 接口由资源管理服务提供;本地开源后端没有对应扩展
批量任务沿用 v2 的 `id_list` / `state_list`;生成问题使用 `document_id_list`,
添加标签使用 `document_ids` / `tag_ids`,迁移和批量导出直接提交文档 ID 数组。
向量化、生成问题默认排除成功分段,也可选择全部分段。任务类型使用 `DOCUMENT_TASK_TYPE`。
单项向量化使用 `putDocumentRefresh(knowledgeId, documentId, stateList)` 请求 `/<documentId>/refresh`;
单项取消使用 `putCancelTask(knowledgeId, documentId, taskType)` 请求 `/<documentId>/cancel_task`,提交 `{ type }`。
批量向量化与取消分别沿用 `batch_refresh` 和 `batch_cancel_task`,不以单元素批量请求代替单项接口。
文档设置的共享载荷为 `DocumentSettingPayload`,生成问题使用 `DocumentGeneratePayload`;
单项设置通过 `meta` 保留来源数据,批量设置将 `allow_download` 放在顶层。
飞书同步沿用 v2 扩展接口 `/knowledge/lark/<knowledgeId>/_batch`,需部署环境支持。
Expand Down
25 changes: 21 additions & 4 deletions ui/src/api/admin/workspace/knowledge/document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,28 +26,43 @@ const putQuickCreateDocuments = (knowledgeId: string, documents: DocumentQuickCr
const putDocumentActive = (knowledgeId: string, documentId: string, isActive: boolean) =>
put(`${getPrefix(knowledgeId)}/${documentId}`, { is_active: isActive })



/** 删除单个文档。 */
const deleteDocument = (knowledgeId: string, documentId: string) => del(`${getPrefix(knowledgeId)}/${documentId}`)

/** 批量删除文档。 */
const putBatchDeleteDocuments = (knowledgeId: string, documentIds: string[]) =>
put(`${getPrefix(knowledgeId)}/batch_delete`, { id_list: documentIds })

/** 单个文档向量化。 */
const putDocumentRefresh = (knowledgeId: string, documentId: string, stateList: DocumentTaskState[]) =>
put(`${getPrefix(knowledgeId)}/${documentId}/refresh`, { state_list: stateList })

/** 批量提交文档向量化。 */
/** 批量文档向量化。 */
const putBatchRefreshDocuments = (knowledgeId: string, documentIds: string[], stateList: DocumentTaskState[]) =>
put(`${getPrefix(knowledgeId)}/batch_refresh`, { id_list: documentIds, state_list: stateList })

/** 批量提交文档分词索引。 */
/** 单个文档分词索引。 */
const putDocumentTokenize: (knowledge_id: string, document_id: string, state_list: Array<string>, loading?: Ref<boolean>) => Promise<Result<any>> = (
knowledge_id,
document_id,
state_list,
loading,
) => {
return put(`${getPrefix(knowledge_id)}/${document_id}/tokenize`, { state_list }, undefined, loading)
}
/** 批量文档分词索引。 */
const putBatchTokenizeDocuments = (knowledgeId: string, documentIds: string[], stateList: DocumentTaskState[]) =>
put(`${getPrefix(knowledgeId)}/batch_tokenize`, { id_list: documentIds, state_list: stateList })

/** 单个取消文档任务。 */
const putCancelTask = (knowledgeId: string, documentId: string, taskType: DocumentTaskType) =>
put(`${getPrefix(knowledgeId)}/${documentId}/cancel_task`, { type: taskType })

/** 批量取消指定文档任务。 */
const putBatchCancelDocumentTask = (knowledgeId: string, documentIds: string[], taskType: DocumentTaskType) =>
put(`${getPrefix(knowledgeId)}/batch_cancel_task`, { id_list: documentIds, type: taskType })


/** 保存单个文档的召回及来源设置。 */
const putDocumentSetting = (knowledgeId: string, documentId: string, data: DocumentSettingPayload) =>
put(`${getPrefix(knowledgeId)}/${documentId}`, data)
Expand Down Expand Up @@ -103,8 +118,10 @@ export default {
putDocumentActive,
deleteDocument,
putBatchDeleteDocuments,
putDocumentRefresh,
putBatchRefreshDocuments,
putBatchTokenizeDocuments,
putCancelTask,
putBatchCancelDocumentTask,
putDocumentSetting,
putBatchDocumentSetting,
Expand Down
6 changes: 1 addition & 5 deletions ui/src/api/enums/state.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
/** 跨业务复用的任务状态,按需扩展。 */
/** 执行记录接口状态;文档任务阶段在所属 View 中维护。 */
export const STATE_TYPES = {
PENDING: 'PENDING',
STARTED: 'STARTED',
EMBEDDING: 'EMBEDDING',
GENERATE: 'GENERATE',
SYNC: 'SYNC',
TOKENIZE: 'TOKENIZE',
SUCCESS: 'SUCCESS',
FAILURE: 'FAILURE',
REVOKE: 'REVOKE',
Expand Down
2 changes: 1 addition & 1 deletion ui/src/assets/iconfont.js

Large diffs are not rendered by default.

27 changes: 17 additions & 10 deletions ui/src/components/COMPONENT_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,11 @@ Drawer 没有 `MkDialog` 的外壳挂载控制;需要按需挂载时,由业
菜单项通过 `icon` Prop 传 Element Plus 图标,或使用 `icon` 插槽;`selectable` 预留勾选位,
`selected` 控制选中。触发器遵循单根节点规则。

`hide-when-empty` 默认关闭。开启后检查 `dropdown` 插槽,展开标准 `MkDropdownMenu` 的默认插槽
和 Fragment,忽略注释、空白文本;没有有效 VNode 时不渲染下拉入口,不改变 `persistent`。
业务 Action 的组件 VNode 视为有效内容,不检查其内部的 `v-if`。此能力用于文档批量 More,
表格和卡片 More 保持原有的默认插槽判断。

### MkViewLayout

满高页面布局,`loading` 覆盖整个根节点。`title` 默认读取路由标题,传空字符串隐藏;
Expand Down Expand Up @@ -276,19 +281,21 @@ Element Plus 的 `v-infinite-scroll`。组件通过 `v-model` 管理已经加载

### MkStatusLabel

`active` 控制布尔状态,默认“已启用 / 已禁用”;用 `activeText`、`inactiveText` 修改文案。
多状态通过 `status` 直接传入 `STATE_LABELS` 的状态键,文案统一读取 `constants/state.ts`。
传入 `status` 时仅显示该状态配置的图标;未配置状态或没有图标时只显示文案,不回退到其他状态图标。
未传 `status` 时,`active` 为 true 显示成功图标,为 false 显示 `icon_ban_filled`。
组件直接使用公共枚举 `STATE_TYPES` 匹配图标;新增状态需补齐 `STATE_LABELS` 文案,图标可按需配置。
`STARTED`、`PENDING`、`REVOKE` 及 `EMBEDDING`、`GENERATE`、`SYNC`、`TOKENIZE`
统一显示内置加载图标,使用方直接传入 `status`,无需额外组合 `LoadingIcon`。
`status` 优先于 `active`,状态文案统一使用 `STATE_LABELS`,不提供 `text` 覆盖。
提供 `success`、`failure`、`loading`、`disabled` 四种图标类型,通过 `type + label` 展示业务状态。
组件不依赖 API 状态枚举或业务文案;业务方维护状态与 `{ type, label }` 的映射。
`StatusLabelType`、`StatusLabelOptions` 从 `global/mk-status-label/types.ts` 导入。
传入 `type` 时使用该类型和外部 `label`,优先于布尔模式,不回退到启用/禁用文案。

未传 `type` 时保留 `active` 便捷模式,默认“已启用 / 已禁用”;
`activeText`、`inactiveText` 可修改文案,`inactiveIcon` 可覆盖未激活图标(默认 `icon_ban_filled`)。
成功图标固定为 `icon_succeed_colorful`,失败图标为 `icon_close_colorful`,加载类型使用 `LoadingIcon`,
禁用图标使用灰色样式。`inactiveIcon` 仅影响布尔模式。
不再接收 `status`;未知业务状态由调用方显示原始文案,不伪装成成功或禁用状态。

```vue
<MkStatusLabel :active="enabled" />
<MkStatusLabel :status="row.state" />
<MkStatusLabel status="TRIGGER_ERROR" />
<MkStatusLabel type="loading" label="索引中" />
<MkStatusLabel v-bind="executionStatus" />
```

### MkTable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ defineExpose({ open, close })

<template>
<MkDialog v-model="visible" :title="scopeOnly ? '生效资源' : '配置权限'" :show-close="!loading" @closed="resetData">
<el-radio-group v-if="!scopeOnly" v-model="permission" class="vertical-radio-group" :disabled="loading">
<el-radio-group v-if="!scopeOnly" v-model="permission" class="mk-radio-group-vertical" :disabled="loading">
<el-radio v-for="option in options" :key="option.value" :value="option.value">
<p>{{ option.label }}</p>
<p v-if="option.description" class="mt-1 text-N500">{{ option.description }}</p>
Expand All @@ -55,7 +55,7 @@ defineExpose({ open, close })
<el-divider class="my-4!" />
<h6 class="mb-2">生效资源</h6>
</template>
<el-radio-group v-model="includeChildren" class="vertical-radio-group" :disabled="loading">
<el-radio-group v-model="includeChildren" class="mk-radio-group-vertical" :disabled="loading">
<el-radio :value="false">仅当前资源</el-radio>
<el-radio :value="true" :disabled="!canIncludeChildren">包含所有子文件夹</el-radio>
</el-radio-group>
Expand Down
33 changes: 29 additions & 4 deletions ui/src/components/global/mk-dropdown/index.vue
Original file line number Diff line number Diff line change
@@ -1,24 +1,43 @@
<script setup lang="ts">
import { ref } from 'vue'
import { Fragment, isVNode, ref } from 'vue'
import type { DropdownInstance } from 'element-plus'
import type { Options } from '@popperjs/core'
import { hasRenderableSlotContent } from '@/utils/vnode'
import MkDropdownMenu from './MkDropdownMenu.vue'

defineOptions({ name: 'MkDropdown', inheritAttrs: false })

withDefaults(defineProps<{ popperOptions?: Partial<Options>; persistent?: boolean }>(), {
withDefaults(defineProps<{ popperOptions?: Partial<Options>; persistent?: boolean; hideWhenEmpty?: boolean }>(), {
popperOptions: () => ({}),
persistent: false,
/* 非必要不开启,调试时可临时开启 */
hideWhenEmpty: false,
})

const dropdownRef = ref<DropdownInstance>()

defineSlots<{
const slots = defineSlots<{
/** 下拉触发器,必须只渲染一个有效根节点 */
default(): unknown
dropdown(): unknown
}>()

// 只展开标准菜单和 Fragment;业务 Action 仍按组件 VNode 判断。
function hasDropdownContent(children: unknown): boolean {
const childNodes = Array.isArray(children) ? children : [children]
return childNodes.some((child) => {
if (isVNode(child)) {
if (child.type === Fragment) return hasDropdownContent(child.children)
if (child.type === MkDropdownMenu) {
const menuSlots = child.children
const menuSlot = menuSlots && typeof menuSlots === 'object' && 'default' in menuSlots ? menuSlots.default : undefined
return hasDropdownContent(typeof menuSlot === 'function' ? menuSlot() : menuSlots)
}
}
return hasRenderableSlotContent(child)
})
}

function handleOpen() {
dropdownRef.value?.handleOpen()
}
Expand All @@ -31,7 +50,13 @@ defineExpose({ handleOpen, handleClose })
</script>

<template>
<el-dropdown class="mk-dropdown" ref="dropdownRef" :persistent="persistent" v-bind="$attrs">
<el-dropdown
v-if="!hideWhenEmpty || hasDropdownContent(slots.dropdown?.())"
class="mk-dropdown"
ref="dropdownRef"
:persistent="persistent"
v-bind="$attrs"
>
<slot />
<template #dropdown>
<slot name="dropdown" />
Expand Down
44 changes: 15 additions & 29 deletions ui/src/components/global/mk-status-label/index.vue
Original file line number Diff line number Diff line change
@@ -1,48 +1,34 @@
<script setup lang="ts">
import { computed } from 'vue'
import { STATE_LABELS } from '@/constants/state'
import { STATE_TYPES } from '@/api/enums'
import type { State } from '@/api/types'
import type { StatusLabelType } from './types'

defineOptions({ name: 'MkStatusLabel' })

const props = withDefaults(
defineProps<{
active?: boolean // 启用状态
type?: StatusLabelType
label?: string
active?: boolean
activeText?: string
inactiveText?: string
status?: State // 多种状态
inactiveIcon?: string
}>(),
{ activeText: '已启用', inactiveText: '已禁用' },
{ activeText: '已启用', inactiveText: '已禁用', inactiveIcon: 'icon_ban_filled' },
)

/* 状态文案与 STATE_LABELS 共用状态键,兼容原有启用/禁用展示。 */
const statusOptions: Partial<Record<State, { name?: string; loading?: boolean }>> = {
[STATE_TYPES.SUCCESS]: { name: 'icon_succeed_colorful' },
[STATE_TYPES.FAILURE]: { name: 'icon_close_colorful' },
[STATE_TYPES.STARTED]: { loading: true },
[STATE_TYPES.EMBEDDING]: { loading: true },
[STATE_TYPES.GENERATE]: { loading: true },
[STATE_TYPES.SYNC]: { loading: true },
[STATE_TYPES.TOKENIZE]: { loading: true },
[STATE_TYPES.REVOKE]: { loading: true },
[STATE_TYPES.PENDING]: { loading: true },
[STATE_TYPES.REVOKED]: { name: 'icon_close_colorful' },
}
const presentation = computed(() => {
if (props.status !== undefined) return statusOptions[props.status]
return props.active ? statusOptions[STATE_TYPES.SUCCESS] : undefined
const displayType = computed(() => props.type ?? (props.active ? 'success' : 'disabled'))
const displayLabel = computed(() => (props.type !== undefined ? props.label : props.active ? props.activeText : props.inactiveText))
const iconName = computed(() => {
if (displayType.value === 'success') return 'icon_succeed_colorful'
if (displayType.value === 'failure') return 'icon_close_colorful'
return props.type === undefined ? props.inactiveIcon : 'icon_ban_filled'
})
const label = computed(() =>
props.status !== undefined ? (STATE_LABELS[props.status] ?? props.status) : props.active ? props.activeText : props.inactiveText,
)
</script>

<template>
<span class="flex-align-center gap-2">
<LoadingIcon v-if="presentation?.loading" :size="16" />
<MkIcon v-else-if="presentation?.name" :name="presentation.name" />
<MkIcon v-else-if="status === undefined && !active" name="icon_ban_filled" class="text-N500!" />
<span>{{ label }}</span>
<LoadingIcon v-if="displayType === 'loading'" :size="16" />
<MkIcon v-else :name="iconName" :class="{ 'text-N500!': displayType === 'disabled' }" />
<span>{{ displayLabel }}</span>
</span>
</template>
6 changes: 6 additions & 0 deletions ui/src/components/global/mk-status-label/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export type StatusLabelType = 'success' | 'failure' | 'loading' | 'disabled'

export interface StatusLabelOptions {
type: StatusLabelType
label: string
}
10 changes: 5 additions & 5 deletions ui/src/constants/CONSTANT_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ const knowledgeTypeLabel = KNOWLEDGE_TYPE_LABELS[knowledgeType]
`trigger.ts` 的 `TRIGGER_SCHEDULE_OPTIONS` 统一提供触发器与长期记忆的周期级联选项,
包含周期文案、执行时间和间隔数值;使用方只读,配置回填与校验仍由各自组件维护。

`state.ts` 的 `STATE_LABELS` 统一维护公共枚举 `STATE_TYPES` 的展示文案。触发器等使用通用状态协议的业务
按需复用 `api/enums/state.ts` 中的状态值;新增状态时同步补齐文案和 `MkStatusLabel` 的图标映射。
`STATE_LABELS` 还提供 `EMBEDDING`(索引中)、`GENERATE`(生成中)、`SYNC`(同步中)、
`TOKENIZE`(分词索引中)的任务阶段文案,文档筛选直接复用对应文案。接口值继续使用
`DOCUMENT_TASK_STATE` 和 `DOCUMENT_TASK_TYPE` 映射,不将展示状态键直接提交为文档任务状态。
`state.ts` 的 `EXECUTION_STATUS_OPTIONS` 维护触发器、工具和知识库工作流执行记录共用的
中文文案与图标类型,仅包含 `PENDING`、`STARTED`、`SUCCESS`、`FAILURE`、`REVOKE`、
`REVOKED`、`TRIGGER_ERROR` 七种接口状态,列表筛选及执行详情复用该配置。新增状态只需在业务配置中补齐展示,
不修改 `MkStatusLabel`。文档任务使用 `views/knowledge-detail/document/status.ts` 的独立配置,
筛选查询仍映射 `DOCUMENT_TASK_STATE` 与 `DOCUMENT_TASK_TYPE`,不提交展示状态键。

`document.ts` 的 `DOCUMENT_HIT_HANDLING_LABELS` 维护文档命中处理方式的展示文案,
映射键引用 API 枚举 `DOCUMENT_HIT_HANDLING`。
Expand Down
23 changes: 10 additions & 13 deletions ui/src/constants/state.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
/** 任务状态的通用展示文案。 */
/** 触发器、工具和知识库工作流执行记录共用的状态展示配置。 */
import { STATE_TYPES } from '@/api/enums'
import type { State } from '@/api/types'
import type { StatusLabelOptions } from '@/components/global/mk-status-label/types'

export const STATE_LABELS: Record<State, string> = {
[STATE_TYPES.PENDING]: '排队中',
[STATE_TYPES.STARTED]: '执行中',
[STATE_TYPES.EMBEDDING]: '索引中',
[STATE_TYPES.GENERATE]: '生成中',
[STATE_TYPES.SYNC]: '同步中',
[STATE_TYPES.TOKENIZE]: '分词索引中',
[STATE_TYPES.SUCCESS]: '成功',
[STATE_TYPES.FAILURE]: '失败',
[STATE_TYPES.REVOKE]: '取消中',
[STATE_TYPES.REVOKED]: '已取消',
[STATE_TYPES.TRIGGER_ERROR]: '触发失败',
export const EXECUTION_STATUS_OPTIONS: Record<State, StatusLabelOptions> = {
[STATE_TYPES.PENDING]: { type: 'loading', label: '排队中' },
[STATE_TYPES.STARTED]: { type: 'loading', label: '执行中' },
[STATE_TYPES.SUCCESS]: { type: 'success', label: '成功' },
[STATE_TYPES.FAILURE]: { type: 'failure', label: '失败' },
[STATE_TYPES.REVOKE]: { type: 'loading', label: '取消中' },
[STATE_TYPES.REVOKED]: { type: 'failure', label: '已取消' },
[STATE_TYPES.TRIGGER_ERROR]: { type: 'failure', label: '触发失败' },
}
3 changes: 3 additions & 0 deletions ui/src/styles/STYLE_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,9 @@ CSS 自定义属性同样按变量名排序。嵌套选择器、伪类和媒体

## 常用交互工具类

纵向单选组在 `el-radio-group` 上添加 `mk-radio-group-vertical`,统一整行宽度、8px 间距及
多行选项的顶部对齐;普通单行和带描述的选项均可使用。

卡片内需要文字居左、单选圆点居右并垂直居中时,在 `el-radio` 上添加 `mk-card-radio`。
该类由 `app.scss` 统一设置整行宽度、自适应高度、文字换行和左右间距;卡片边框与选中状态
仍由外层卡片控制。
Expand Down
2 changes: 1 addition & 1 deletion ui/src/styles/app.scss
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ h6 {
}

// radio-group 垂直排列
.vertical-radio-group {
.mk-radio-group-vertical {
align-items: stretch;
display: flex;
flex-direction: column;
Expand Down
Loading
Loading