diff --git a/ui/AGENTS.md b/ui/AGENTS.md index daf56ea9f1e..30cb7e43ce4 100644 --- a/ui/AGENTS.md +++ b/ui/AGENTS.md @@ -102,7 +102,8 @@ ui/ │ │ ├── application/ # Application list and creation │ │ ├── application-detail/ # Application overview and settings │ │ ├── knowledge/ # Knowledge base list and cards -│ │ ├── knowledge-detail/ # Knowledge base and document details +│ │ ├── knowledge-detail/ # Knowledge base details, document lists, and settings +│ │ ├── document-detail/ # Document details and paragraph management │ │ ├── model/ # Model list, creation, and settings │ │ ├── tool/ # Tool list, forms, and tool store │ │ ├── trigger/ # Workspace trigger list diff --git a/ui/src/api/API_README.md b/ui/src/api/API_README.md index 456ac98d8e5..3dd107ddfe0 100644 --- a/ui/src/api/API_README.md +++ b/ui/src/api/API_README.md @@ -568,9 +568,14 @@ Workspace、System 资源管理及 System 共享资源的 `knowledge.ts` 提供 `getParagraphList` 查询完整分段列表,支持 `title`、`content`,服务端按 `position`、创建时间排序; `postParagraph` 提交 `title`、`content`、`is_active` 和可选的 `problem_list: [{ content }]`, 返回新增分段详情,默认插入首位并触发索引。 +新增载荷可传 `position`,在目标位置插入,用于当前分段上方添加。 `getParagraphDetail` 查询 `/`,返回 `ParagraphDetail`,包含 `problem_list`、 `hit_num` 和 `last_hit_time`。`putParagraph` 向同一路径提交标题和正文,解包后端三元素编辑响应的 首项为最新详情;不提交 `problem_list`,关联关系通过专属接口维护。 +编辑载荷使用 `ParagraphEditPayload`,允许仅传 `is_active` 更新启用状态,正文表单仍使用 +必填标题及正文的 `ParagraphUpdatePayload`。`deleteParagraph` DELETE `/` 删除单个分段。 +`putParagraphPosition` PUT `/adjust_position`,查询参数为 `paragraph_id`、`new_position`, +不传请求体,由服务端调整当前分段及受影响分段的位置。 `postParagraphProblem` 向 `//problem` 提交 `{ content }`,创建或复用问题并关联分段; `getParagraphProblems` GET 同一路径,返回当前分段的 `ParagraphProblem[]`,用于关联变更后刷新。 `putAssociateParagraphProblem` PUT 到 `/association`,通过查询参数传递 `paragraph_id`、`problem_id`, diff --git a/ui/src/api/admin/workspace/knowledge/paragraph.ts b/ui/src/api/admin/workspace/knowledge/paragraph.ts index bd194b5b606..7c46442cc63 100644 --- a/ui/src/api/admin/workspace/knowledge/paragraph.ts +++ b/ui/src/api/admin/workspace/knowledge/paragraph.ts @@ -1,12 +1,12 @@ -import { get, post, put } from '../../core/request' +import { del, get, post, put } from '../../core/request' import type { ParagraphCreatePayload, ParagraphDetail, + ParagraphEditPayload, ParagraphGeneratePayload, ParagraphItem, ParagraphProblem, ParagraphQuery, - ParagraphUpdatePayload, } from '@/api/types' import { getWorkspaceId } from '@/utils/resource-context' @@ -17,7 +17,7 @@ const getPrefix = (knowledgeId: string, documentId: string) => const getParagraphList = (knowledgeId: string, documentId: string, query?: ParagraphQuery) => get(getPrefix(knowledgeId, documentId), { ...query }) -/** 在文档首位新增分段,由服务端触发索引。 */ +/** 在指定位置新增分段,默认首位,由服务端触发索引。 */ const postParagraph = (knowledgeId: string, documentId: string, payload: ParagraphCreatePayload) => post(getPrefix(knowledgeId, documentId), payload) @@ -25,12 +25,11 @@ const postParagraph = (knowledgeId: string, documentId: string, payload: Paragra const getParagraphDetail = (knowledgeId: string, documentId: string, paragraphId: string) => get(`${getPrefix(knowledgeId, documentId)}/${paragraphId}`) -/** 保存分段标题和内容,提取后端编辑响应中的最新详情。 */ -const putParagraph = (knowledgeId: string, documentId: string, paragraphId: string, payload: ParagraphUpdatePayload) => - put( - `${getPrefix(knowledgeId, documentId)}/${paragraphId}`, - payload, - ).then(([paragraph]) => paragraph) +/** 更新分段正文或启用状态,提取后端编辑响应中的最新详情。 */ +const putParagraph = (knowledgeId: string, documentId: string, paragraphId: string, payload: ParagraphEditPayload) => + put(`${getPrefix(knowledgeId, documentId)}/${paragraphId}`, payload).then( + ([paragraph]) => paragraph, + ) /** 新增问题并关联到当前分段。 */ const postParagraphProblem = (knowledgeId: string, documentId: string, paragraphId: string, content: string) => @@ -62,6 +61,17 @@ const putGenerateParagraphQuestions = (knowledgeId: string, documentId: string, const putBatchDeleteParagraphs = (knowledgeId: string, documentId: string, paragraphIds: string[]) => put<{ id_list: string[] }, boolean>(`${getPrefix(knowledgeId, documentId)}/batch_delete`, { id_list: paragraphIds }) +/** 删除当前文档的单个分段及其关联数据。 */ +const deleteParagraph = (knowledgeId: string, documentId: string, paragraphId: string) => + del(`${getPrefix(knowledgeId, documentId)}/${paragraphId}`) + +/** 调整分段顺序,由服务端同步更新受影响的分段位置。 */ +const putParagraphPosition = (knowledgeId: string, documentId: string, paragraphId: string, position: number) => + put(`${getPrefix(knowledgeId, documentId)}/adjust_position`, undefined, { + paragraph_id: paragraphId, + new_position: position, + }) + export default { getParagraphList, postParagraph, @@ -74,4 +84,6 @@ export default { putMigrateParagraphs, putGenerateParagraphQuestions, putBatchDeleteParagraphs, + deleteParagraph, + putParagraphPosition, } diff --git a/ui/src/api/types/knowledge.ts b/ui/src/api/types/knowledge.ts index 0aa4c3c0840..5f7b0bbb0c0 100644 --- a/ui/src/api/types/knowledge.ts +++ b/ui/src/api/types/knowledge.ts @@ -123,7 +123,7 @@ export interface KnowledgeWorkflowCreatePayload extends KnowledgeCreatePayload { /** Web 文档分段、视觉处理和索引策略。 */ export interface DocumentStrategy { - split: { mode: 'smart' | 'advanced'; patterns: string[] | null; min_length: number; max_length: number; child_length: number; auto_clean: boolean } + split: { mode: 'smart' | 'advanced'; patterns: string[] | null; limit: number; child_length: number; auto_clean: boolean } visual: { enabled: boolean; strategy: 'model' | 'tool'; model_id: string | null; tool_id: string | null } index: { title_as_question: boolean } } diff --git a/ui/src/api/types/paragraph.ts b/ui/src/api/types/paragraph.ts index df0a6c46425..654c3b09f3a 100644 --- a/ui/src/api/types/paragraph.ts +++ b/ui/src/api/types/paragraph.ts @@ -29,6 +29,9 @@ export interface ParagraphUpdatePayload { content: string } +/** 保存正文或单独更新分段启用状态。 */ +export type ParagraphEditPayload = Partial> + export interface ParagraphQuery { title?: string content?: string @@ -38,6 +41,7 @@ export interface ParagraphCreatePayload { title: string content: string is_active: boolean + position?: number problem_list?: { content: string }[] } diff --git a/ui/src/components/global/markdown-editor/md-editor.scss b/ui/src/components/global/markdown-editor/md-editor.scss index 8bac5241bd4..ec6758ab11b 100644 --- a/ui/src/components/global/markdown-editor/md-editor.scss +++ b/ui/src/components/global/markdown-editor/md-editor.scss @@ -123,6 +123,29 @@ table { display: block; + max-width: 100%; + overflow-x: auto; + width: fit-content; + + td, + th { + max-width: calc(var(--spacing) * 100); + min-width: calc(var(--spacing) * 30); + overflow-wrap: break-word; + word-break: normal; + } + + th { + white-space: nowrap; + } + + tbody tr:hover { + background-color: var(--md-theme-table-tr-bg-color); + } + + tbody tr:nth-child(2n):hover { + background-color: var(--md-theme-table-stripe-color); + } } ul { diff --git a/ui/src/router/ROUTE_README.md b/ui/src/router/ROUTE_README.md index 48f051ec51e..d3e5b5ad346 100644 --- a/ui/src/router/ROUTE_README.md +++ b/ui/src/router/ROUTE_README.md @@ -411,7 +411,7 @@ Web 展示“设置 → 基础设置、文档处理策略、定时同步”; 普通 Workspace 文档分段页使用 `workspace-knowledge-paragraph`,地址为 `/admin/workspace/:workspaceId/knowledge/:knowledgeId/:type/document/:documentId`, 在 `knowledge.ts` 中与知识库详情容器同级注册,复用 Workspace AppLayout,保持知识库一级菜单激活。 -页面位于 `views/knowledge-detail/paragraph/index.vue`,使用自身的分段目录,不挂载 +页面位于 `views/document-detail/index.vue`,使用自身的分段目录,不挂载 `ResourceDetailLayout`。配置 `hidden` 与 `resourceDetailRoot`,使工作空间切换继续返回知识库列表; 返回按钮进入 `workspace-knowledge-document-list`,保留知识库类型和 query。 文档行点击进入分段页,选择列、名称编辑和操作按钮不触发跳转。 diff --git a/ui/src/router/admin/system/modules/knowledge-system-resource.ts b/ui/src/router/admin/system/modules/knowledge-system-resource.ts index 9ce1d847654..f9e2989c4f9 100644 --- a/ui/src/router/admin/system/modules/knowledge-system-resource.ts +++ b/ui/src/router/admin/system/modules/knowledge-system-resource.ts @@ -148,7 +148,7 @@ export const systemResourceKnowledgeRoutes: RouteRecordRaw[] = [ { path: '/system/resource/knowledge/:knowledgeId/:type/document/:documentId', name: 'system-resource-knowledge-paragraph', - component: () => import('@/views/knowledge-detail/paragraph/index.vue'), + component: () => import('@/views/document-detail/index.vue'), meta: { resourceScope: 'system-resource', scope: 'system', hideSidebar: true, title: '文档分段', hidden: true, resourceDetailRoot: true }, }, ] diff --git a/ui/src/router/admin/system/modules/knowledge-system-shared.ts b/ui/src/router/admin/system/modules/knowledge-system-shared.ts index 7613bc9c71c..10f1ed9aee3 100644 --- a/ui/src/router/admin/system/modules/knowledge-system-shared.ts +++ b/ui/src/router/admin/system/modules/knowledge-system-shared.ts @@ -148,7 +148,7 @@ export const systemSharedKnowledgeRoutes: RouteRecordRaw[] = [ { path: '/system/shared/knowledge/:knowledgeId/:type/document/:documentId', name: 'system-shared-knowledge-paragraph', - component: () => import('@/views/knowledge-detail/paragraph/index.vue'), + component: () => import('@/views/document-detail/index.vue'), meta: { resourceScope: 'system-shared', scope: 'system', hideSidebar: true, title: '文档分段', hidden: true, resourceDetailRoot: true }, }, ] diff --git a/ui/src/router/admin/workspace/modules/knowledge-shared.ts b/ui/src/router/admin/workspace/modules/knowledge-shared.ts index 229be4ec90b..de2422f4796 100644 --- a/ui/src/router/admin/workspace/modules/knowledge-shared.ts +++ b/ui/src/router/admin/workspace/modules/knowledge-shared.ts @@ -148,7 +148,7 @@ export const sharedKnowledgeRoutes: RouteRecordRaw[] = [ { path: 'shared/knowledge/:knowledgeId/:type/document/:documentId', name: 'workspace-shared-knowledge-paragraph', - component: () => import('@/views/knowledge-detail/paragraph/index.vue'), + component: () => import('@/views/document-detail/index.vue'), meta: { resourceScope: 'workspace-shared', activeMenu: 'workspace-knowledge', title: '文档分段', hidden: true, resourceDetailRoot: true }, }, ] diff --git a/ui/src/router/admin/workspace/modules/knowledge.ts b/ui/src/router/admin/workspace/modules/knowledge.ts index 4b747298a38..b64021e42df 100644 --- a/ui/src/router/admin/workspace/modules/knowledge.ts +++ b/ui/src/router/admin/workspace/modules/knowledge.ts @@ -160,7 +160,7 @@ export const knowledgeRoutes: RouteRecordRaw[] = [ { path: ':knowledgeId/:type/document/:documentId', name: 'workspace-knowledge-paragraph', - component: () => import('@/views/knowledge-detail/paragraph/index.vue'), + component: () => import('@/views/document-detail/index.vue'), meta: { title: '文档分段', hidden: true, resourceDetailRoot: true }, }, ], diff --git a/ui/src/styles/STYLE_README.md b/ui/src/styles/STYLE_README.md index 1413c70fc64..55ab0ac47bc 100644 --- a/ui/src/styles/STYLE_README.md +++ b/ui/src/styles/STYLE_README.md @@ -75,6 +75,15 @@ Admin 和 Chat 共用。组件不单独恢复焦点 `outline`,包括通过隐 这类规则会破坏 Element Plus 组件的默认间距。仅设置必要的基础规则,例如统一 `box-sizing`、清除 `body` 默认外边距和定义应用级布局。 +## Markdown 表格 + +`src/components/global/markdown-editor/md-editor.scss` 的预览 table 样式统一维护 +`.mk-markdown-editor .md-editor-preview table`,由同目录 `config.ts` 引入, +覆盖 `MdPreview` 与 `MdEditor` 的渲染预览。表格使用块级容器和 `overflow-x: auto`, +宽度限制在正文区域内,宽表格单独横向滚动,不让整段正文横向滚动。 +单元格最小宽度为 120px、最大宽度为 400px,正文正常换行并允许长链接折行;表头不换行, +避免多列时标题和正文被压缩成逐字纵向排列。滚动条沿用 Markdown 主题已有样式。 + ## 默认字号 项目正文和大部分界面文字默认使用 `14px / 22px`(字号/行高),统一由 `variables.scss` 中的 `--mk-font-size-base` 和 `--mk-line-height-base` 提供,`body` 直接使用这两个变量。 diff --git a/ui/src/views/VIEW_README.md b/ui/src/views/VIEW_README.md index d54618518bf..0a6d0ed88eb 100644 --- a/ui/src/views/VIEW_README.md +++ b/ui/src/views/VIEW_README.md @@ -488,7 +488,7 @@ Dialog。新增或重命名文件时,应同步更新所有导入和页面功 | `knowledge-detail/index.vue` | 知识库详情页面 | | `knowledge-detail/library/tags/index.vue` | 标签分组查询、创建、重命名、单值编辑、删除与关联文档管理 | | `knowledge-detail/workflow-transform/index.vue` | 非工作流知识库转换说明、确认与进入画布 | -| `knowledge-detail/paragraph/index.vue` | 普通 Workspace 文档分段查询、目录定位、批量操作、新增、编辑与详情 | +| `document-detail/index.vue` | 普通 Workspace 文档分段查询、目录定位、批量操作、新增、编辑与详情 | | | `knowledge-detail/setting/base/index.vue` | 知识库基本信息、来源配置与上传限制设置 | @@ -886,29 +886,30 @@ Workspace 普通资源、System 资源管理和 System 共享资源允许打开 `MkDropdown` 单选筛选全部、成功、失败、索引中、分词索引中、排队中和生成中;查询组合 `status` 与可选的 `task_type`,切换选项清除旧任务类型并回到第一页,保留名称和创建者条件。 -普通 Workspace 文档行点击进入 `knowledge-detail/paragraph/index.vue`,选择列、名称编辑及行操作 +文档详情页面及其专属组件统一放在 `views/document-detail/`,与 `knowledge/`、`knowledge-detail/` 平级。 +普通 Workspace 文档行点击进入 `document-detail/index.vue`,选择列、名称编辑及行操作 保留自身交互。分段页与知识库详情容器路由同级,复用 `MkViewLayout` 的顶部栏、侧栏和主区滚动; 顶部展示接口查询的文档名称、标题/内容搜索、批量选择和添加分段,侧栏按服务端位置排列全部 查询结果,点击定位正文,滚动同步高亮。正文使用 `MdPreview`;空文档展示空态。 批量选择支持点击整张卡片或复选框切换勾选、全选、取消、“迁移至”、“生成问题”和“删除”; -复选框阻止点击冒泡,避免重复切换。`paragraph/migrate/` 集中维护 +复选框阻止点击冒泡,避免重复切换。`document-detail/migrate/` 集中维护 `ButtonMigrateParagraph` 和 `ParagraphMigrateDialog`,沿用 chat-log 添加知识库的知识库/文档 选择样式,保留知识库和文档图标;迁移 API 由分段页传入完整 Paragraph API 对象。 打开时固定所选分段 ID,选择知识库后查询全部文档并排除当前文档,允许迁移至同知识库其他文档。 知识库和文档必选,成功关闭动画结束后通过 `success(paragraphIds)` 返回本次迁移的固定 ID, 页面从本地列表移除对应分段、清空勾选并更新目录定位,不重新查询分段接口,失败保留表单。 -`paragraph/components/ButtonGenerateQuestions.vue` 封装批量生成入口, +`document-detail/components/ButtonGenerateQuestions.vue` 封装批量生成入口, 接收页面传入的完整 Paragraph API,打开时固定知识库、文档和勾选分段。复用公共 `GenerateQuestionsDialog` 并传入 `showParagraphScope=false`,仅提交生成配置和所选分段 ID, 不传 `state_list`;请求成功关闭后重新查询当前筛选结果并清空选择,失败保留配置。 -批量删除按钮和请求逻辑直接维护在 `paragraph/index.vue`,不单独拆分组件。 +批量删除按钮和请求逻辑直接维护在 `document-detail/index.vue`,不单独拆分组件。 确认前固定所选分段与所属文档,确认后提交批量删除接口;成功从本地列表移除本次删除的分段、 清空勾选并更新目录定位,不重新查询分段接口。 取消或失败保留选择,确认及请求期间阻止重复操作。 -`paragraph/paragraph-dialog/` 维护两个独立弹窗,以本地组件组合左右布局: +`document-detail/paragraph-dialog/` 维护两个独立弹窗,以本地组件组合左右布局: - `CreateParagraphDialog.vue` 组合 `components/ParagraphForm.vue` 与 `components/ParagraphQuestions.vue`, - 管理添加请求,底部提供取消和添加。 + 管理添加请求,底部提供取消和添加;`open(position?)` 可指定插入位置,默认添加到文档首位。 - `EditParagraphDialog.vue` 组合 `ParagraphForm`、`ParagraphPreview` 与 `ParagraphQuestions`, 支持 `open(paragraph, initialMode?)`,初始模式默认为 `detail`,也可传入 `edit`。 详情点击编辑进入表单;取消编辑回到原详情,保存成功回到更新后的详情。 @@ -936,6 +937,13 @@ Workspace 普通资源、System 资源管理和 System 共享资源允许打开 避免覆盖并行的问题操作;取消编辑不撤销即时保存的问题关联。 新增成功关闭弹窗并通知页面清空搜索、重新查询服务端排序;编辑成功通知页面刷新当前筛选结果。 分段卡片在非批量模式点击打开详情弹窗,批量模式点击切换勾选;批量生成问题继续使用 `components/ButtonGenerateQuestions.vue`。 +非批量卡片右上角使用与文档导入预览相同的 sticky 悬浮操作区和白色阴影 Card, +提供启停、直接编辑及上方插入;更多菜单提供单段生成问题、迁移、移动位置与删除。 +`ButtonGenerateQuestions` 和 `ButtonMigrateParagraph` 的默认插槽暴露 `open`、`disabled`, +供单段菜单复用既有流程,默认仍渲染批量按钮;更多菜单使用 `persistent` 保留其中的业务弹窗。 +启停仅提交 `is_active`,成功后更新当前卡片;单段删除确认后调用独立 DELETE 接口并从本地移除。 +位置菜单包含置于顶部、上移、下移、置于底部,先查询完整文档分段顺序再调整位置, +不按搜索结果计算相邻位置;调整成功后刷新当前筛选结果并保留主区滚动位置。 Workspace 共享及 System 分段路由已注册并保持来源导航;各范围分段 API 接入暂缓。 System 资源管理与共享资源已注册共用知识库详情路由, diff --git a/ui/src/views/knowledge-detail/paragraph/components/ButtonGenerateQuestions.vue b/ui/src/views/document-detail/components/ButtonGenerateQuestions.vue similarity index 87% rename from ui/src/views/knowledge-detail/paragraph/components/ButtonGenerateQuestions.vue rename to ui/src/views/document-detail/components/ButtonGenerateQuestions.vue index 4b9da396bf4..d8db767b3d3 100644 --- a/ui/src/views/knowledge-detail/paragraph/components/ButtonGenerateQuestions.vue +++ b/ui/src/views/document-detail/components/ButtonGenerateQuestions.vue @@ -14,6 +14,7 @@ const props = defineProps<{ disabled?: boolean }>() const emit = defineEmits<{ refresh: [] }>() +defineSlots<{ default?(props: { open: () => unknown; disabled: boolean }): unknown }>() // 打开时固定本次生成问题的分段与所属文档。 const loading = ref(false) @@ -69,8 +70,10 @@ function handleClosed() { diff --git a/ui/src/views/knowledge-detail/paragraph/migrate/ButtonMigrateParagraph.vue b/ui/src/views/document-detail/migrate/ButtonMigrateParagraph.vue similarity index 73% rename from ui/src/views/knowledge-detail/paragraph/migrate/ButtonMigrateParagraph.vue rename to ui/src/views/document-detail/migrate/ButtonMigrateParagraph.vue index 68d97a30f11..3367213e22d 100644 --- a/ui/src/views/knowledge-detail/paragraph/migrate/ButtonMigrateParagraph.vue +++ b/ui/src/views/document-detail/migrate/ButtonMigrateParagraph.vue @@ -12,6 +12,7 @@ const props = defineProps<{ disabled?: boolean }>() const emit = defineEmits<{ success: [paragraphIds: string[]] }>() +defineSlots<{ default?(props: { open: () => unknown; disabled: boolean }): unknown }>() const dialogRef = useTemplateRef>('dialogRef') function handleOpenDialog() { @@ -21,8 +22,10 @@ function handleOpenDialog() {