diff --git a/ui/AGENTS.md b/ui/AGENTS.md index 7f818063aac..21deab175e0 100644 --- a/ui/AGENTS.md +++ b/ui/AGENTS.md @@ -141,9 +141,9 @@ Structural responsibilities: internally to avoid circular access through the aggregate entry. - Put functions shared by most pages or multiple business modules in `utils/`; detailed placement, file organization, and naming rules are maintained in `src/utils/UTILS_README.md`. - Treat application, knowledge, model, and tool as the four special resource domains that can span - Workspace, System resource management, and System shared resources. Their route scope, API + Workspace resources, Workspace shared resources, System resource management, and System shared resources. Their route scope, API selection, reusable cards, and context-specific Actions follow `ROUTE_README.md`, - `API_README.md`, and `VIEW_README.md`; do not extend this three-scope pattern to ordinary pages. + `API_README.md`, and `VIEW_README.md`; do not extend this resource-scope pattern to ordinary pages. - Import source assets through `src/assets/`; use `public/` only when a file must retain its original filename and URL. ## Entry Points diff --git a/ui/src/api/API_README.md b/ui/src/api/API_README.md index f52d1237deb..1a36ca80c47 100644 --- a/ui/src/api/API_README.md +++ b/ui/src/api/API_README.md @@ -23,6 +23,7 @@ src/api/ │ │ ├── conversation.ts # 调试对话、历史会话与语音接口 │ │ ├── application/ # 智能体接口 │ │ ├── knowledge/ # 知识库接口 +│ │ ├── shared/ # 工作空间可用的共享模型、工具、知识库查询 │ │ ├── model.ts # 模型接口 │ │ ├── trigger/ # 触发器查询及维护接口 │ │ ├── tool/ # 工具、工具工作流及工具商店接口 @@ -101,7 +102,7 @@ FormData;保存返回完整配置,页面以该返回值作为唯一数据来 多套 API 时,以 `request` 为前缀加业务名区分,例如 `requestModelApi`、`requestToolApi`。 直接导入的 API 保持 PascalCase 命名;请求返回的 Promise 不属于此命名规则。 -`application`、`knowledge`、`model`、`tool` 是需要同时考虑 Workspace、System 资源管理和 +`application`、`knowledge`、`model`、`tool` 是需要同时考虑 Workspace 普通资源、Workspace 共享资源、System 资源管理和 System 共享资源的四类特殊资源。其接口按真实后端边界分别维护在 `admin/workspace/` 与 `admin/system/` 下,不把不同范围的 URL 合并为页面侧 API Map,也不让卡片或 Action 根据路由拼接 System 接口地址。 @@ -153,7 +154,7 @@ v3 编辑表单提交 `{ name, description }`,标题上限 64、更新说明 `workspace/tool/tool.ts` 的 `getAllTool(query)` 查询支持 `folder_id` 筛选的工作空间工具 非分页列表,用于文件夹菜单和工具选择弹窗。`getToolListWithShared(query)` 请求 `tool/tool_list`, 将响应的 `tools` 与 `shared_tools` 合并为 `ToolItem[]`,用于包含已授权共享工具的选项查询; -按工具类型筛选时使用 `tool_type`。`workspace/shared.ts` 的 `getAllTool(query)` 仅查询共享工具。 +按工具类型筛选时使用 `tool_type`。`workspace/shared/tool.ts` 的 `getAllTool(query)` 仅查询共享工具。 ### System 共享工具 @@ -200,7 +201,7 @@ MCP 入口加载配置后打开只读及复制弹窗;分词索引入口仅调 `/workspace//knowledge/import_knowledge`,响应为 `{ knowledge_id, type }`。 后端校验知识库导出包并创建资源;导入成功后的用户权限和列表刷新由调用页面负责。 -`workspace/knowledge/knowledge.ts` 与 `workspace/shared.ts` 的 `getAllKnowledge(query)` +`workspace/knowledge/knowledge.ts` 与 `workspace/shared/knowledge/knowledge.ts` 的 `getAllKnowledge(query)` 分别查询工作空间及共享知识库的非分页列表,返回 `KnowledgeItem[]`,用于关联知识库选择等 需要全量选项的场景。原有 `getKnowledgePage` 继续用于分页列表。 @@ -492,3 +493,30 @@ System 接口由资源管理服务提供;本地开源后端没有对应扩展 版本和执行记录,前缀为 `/system/shared/tool`,调用只传资源 ID,不读取工作空间。 两种 System 工具 API 的 `getToolListWithShared` 查询各自 `/tool_list` 并合并 `tools`、`shared_tools`, 供画布的工具菜单及 MCP 选项使用。系统扩展接口需连接支持相应协议的部署进行联调。 + +### 知识库文档 + +`workspace/knowledge/document.ts` 仅维护文档分页查询,路径为 +`/workspace//knowledge//document//`。 +分页使用 `ParamsPage` / `ResponsePage`,支持 `name`、`create_user` 筛选; +文档页传 `resource_type: 'document'`,创建者选项复用 Workspace `common.ts` 的 `getAllUsers`。 +类型从 `@/api/types` 导入,命中处理枚举从 `@/api/enums` 导入。API 不接收 loading,不重复包装响应。 + +### 工作空间共享资源查询 + +`workspace/shared/` 按最终资源文件拆分:`model.ts` 提供 `getModelList`,`tool.ts` 提供 +`getToolPage`、`getAllTool`,`knowledge/knowledge.ts` 提供 `getKnowledgePage`、`getAllKnowledge`、`getKnowledgeDetail`。 +上述资源查询沿用 `/system/shared/workspace//`,工作空间 ID 在调用时读取。 +调用方直接导入对应资源文件,与普通工作空间 API 同时使用时分别命名为 +`SharedModelApi`、`SharedToolApi`、`SharedKnowledgeApi`;目录不提供聚合入口。 + +`workspace/shared/knowledge/document.ts` 提供 `getDocumentPage(knowledgeId, page, query)`, +请求 `/system/shared/workspace//knowledge//document//`。 +共享与普通知识库文档查询统一使用 `ParamsPage`、`ResponsePage` 和 `Dict`, +不传 loading,不保留 v2 的 `Result` 或下划线分页参数。两个文件各自默认导出 API 对象。 + +共享知识库 `getKnowledgeDetail(knowledgeId)` 请求知识库资源 `/`,返回 `KnowledgeDetail`。 + +Workspace 共享知识库详情由 `resourceScope: 'workspace-shared'` 选择上述完整共享 API 对象; +普通详情继续使用 `workspace/knowledge/`。共享详情和文档查询不使用 System 共享资源管理 API, +也不回退到普通 Workspace 知识库接口。文档创建者选项沿用共享列表的 System `common.ts` 查询。 diff --git a/ui/src/api/admin/workspace/knowledge/document.ts b/ui/src/api/admin/workspace/knowledge/document.ts new file mode 100644 index 00000000000..6429627572c --- /dev/null +++ b/ui/src/api/admin/workspace/knowledge/document.ts @@ -0,0 +1,12 @@ +import { get } from '../../core/request' +import type { ParamsPage, ResponsePage } from '../../core/types' +import type { Dict, DocumentItem } from '@/api/types' +import { getWorkspaceId } from '@/utils/resource-context' + +const getPrefix = (knowledgeId: string) => `/workspace/${getWorkspaceId()}/knowledge/${knowledgeId}/document` + +/** 获取文档分页列表,支持名称和创建者筛选。 */ +const getDocumentPage = (knowledgeId: string, page: ParamsPage, query?: Dict) => + get>(`${getPrefix(knowledgeId)}/${page.currentPage}/${page.pageSize}`, query) + +export default { getDocumentPage } diff --git a/ui/src/api/admin/workspace/shared.ts b/ui/src/api/admin/workspace/shared.ts deleted file mode 100644 index 46ef45e53eb..00000000000 --- a/ui/src/api/admin/workspace/shared.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { get } from '../core/request' -import type { ParamsPage, ResponsePage } from '../core/types' -import type { Dict, KnowledgeItem, ModelItem, ToolItem } from '@/api/types' -import { getWorkspaceId } from '@/utils/resource-context' - -const getPrefix = () => { - const workspaceId = getWorkspaceId() - return `/system/shared/workspace/${workspaceId}` -} - -/** 获取工作空间共享的模型列表。 */ -const getModelList = (query?: Dict) => { - return get(`${getPrefix()}/model`, query) -} - -/** 获取工作空间共享的分页工具列表。 */ -const getToolPage = (page: ParamsPage, query?: Dict) => { - return get>(`${getPrefix()}/tool/${page.currentPage}/${page.pageSize}`, query) -} -/** 获取工作空间共享的不分页所有工具列表。 */ -const getAllTool = (query?: Dict) => { - return get(`${getPrefix()}/tool`, query) -} - -/** 获取工作空间共享的知识库列表。 */ -const getKnowledgePage = (page: ParamsPage, query?: Dict) => { - return get>(`${getPrefix()}/knowledge/${page.currentPage}/${page.pageSize}`, query) -} - -/** 获取工作空间共享的不分页知识库列表。 */ -const getAllKnowledge = (query?: Dict) => { - return get(`${getPrefix()}/knowledge`, query) -} - -export default { getAllKnowledge, getKnowledgePage, getModelList, getToolPage, getAllTool } diff --git a/ui/src/api/admin/workspace/shared/knowledge/document.ts b/ui/src/api/admin/workspace/shared/knowledge/document.ts new file mode 100644 index 00000000000..8be7bbe4931 --- /dev/null +++ b/ui/src/api/admin/workspace/shared/knowledge/document.ts @@ -0,0 +1,12 @@ +import { get } from '../../../core/request' +import type { ParamsPage, ResponsePage } from '../../../core/types' +import type { Dict, DocumentItem } from '@/api/types' +import { getWorkspaceId } from '@/utils/resource-context' + +const getPrefix = (knowledgeId: string) => `/system/shared/workspace/${getWorkspaceId()}/knowledge/${knowledgeId}/document` + +/** 获取工作空间共享知识库的文档分页列表。 */ +const getDocumentPage = (knowledgeId: string, page: ParamsPage, query?: Dict) => + get>(`${getPrefix(knowledgeId)}/${page.currentPage}/${page.pageSize}`, query) + +export default { getDocumentPage } diff --git a/ui/src/api/admin/workspace/shared/knowledge/knowledge.ts b/ui/src/api/admin/workspace/shared/knowledge/knowledge.ts new file mode 100644 index 00000000000..c74ec55a775 --- /dev/null +++ b/ui/src/api/admin/workspace/shared/knowledge/knowledge.ts @@ -0,0 +1,18 @@ +import { get } from '../../../core/request' +import type { ParamsPage, ResponsePage } from '../../../core/types' +import type { Dict, KnowledgeDetail, KnowledgeItem } from '@/api/types' +import { getWorkspaceId } from '@/utils/resource-context' + +const getPrefix = () => `/system/shared/workspace/${getWorkspaceId()}/knowledge` + +/** 获取工作空间共享的知识库分页列表。 */ +const getKnowledgePage = (page: ParamsPage, query?: Dict) => + get>(`${getPrefix()}/${page.currentPage}/${page.pageSize}`, query) + +/** 获取工作空间共享的不分页知识库列表。 */ +const getAllKnowledge = (query?: Dict) => get(getPrefix(), query) + +/** 获取工作空间共享的知识库详情。 */ +const getKnowledgeDetail = (knowledgeId: string) => get(`${getPrefix()}/${knowledgeId}`) + +export default { getKnowledgePage, getAllKnowledge, getKnowledgeDetail } diff --git a/ui/src/api/admin/workspace/shared/model.ts b/ui/src/api/admin/workspace/shared/model.ts new file mode 100644 index 00000000000..7f487ba3cb6 --- /dev/null +++ b/ui/src/api/admin/workspace/shared/model.ts @@ -0,0 +1,12 @@ +import { get } from '../../core/request' +import type { Dict, ModelItem } from '@/api/types' +import { getWorkspaceId } from '@/utils/resource-context' + +const getPrefix = () => `/system/shared/workspace/${getWorkspaceId()}/model` + +/** 获取工作空间共享的模型列表。 */ +const getModelList = (query?: Dict) => { + return get(getPrefix(), query) +} + +export default { getModelList } diff --git a/ui/src/api/admin/workspace/shared/tool.ts b/ui/src/api/admin/workspace/shared/tool.ts new file mode 100644 index 00000000000..203391a3178 --- /dev/null +++ b/ui/src/api/admin/workspace/shared/tool.ts @@ -0,0 +1,18 @@ +import { get } from '../../core/request' +import type { ParamsPage, ResponsePage } from '../../core/types' +import type { Dict, ToolItem } from '@/api/types' +import { getWorkspaceId } from '@/utils/resource-context' + +const getPrefix = () => `/system/shared/workspace/${getWorkspaceId()}/tool` + +/** 获取工作空间共享的分页工具列表。 */ +const getToolPage = (page: ParamsPage, query?: Dict) => { + return get>(`${getPrefix()}/${page.currentPage}/${page.pageSize}`, query) +} + +/** 获取工作空间共享的不分页工具列表。 */ +const getAllTool = (query?: Dict) => { + return get(getPrefix(), query) +} + +export default { getToolPage, getAllTool } diff --git a/ui/src/api/enums/document.ts b/ui/src/api/enums/document.ts new file mode 100644 index 00000000000..69098b71f09 --- /dev/null +++ b/ui/src/api/enums/document.ts @@ -0,0 +1,2 @@ +/** 文档命中处理方式。 */ +export const DOCUMENT_HIT_HANDLING = { OPTIMIZATION: 'optimization', DIRECTLY_RETURN: 'directly_return' } as const diff --git a/ui/src/api/enums/index.ts b/ui/src/api/enums/index.ts index 33b67e055b8..0547f89edb8 100644 --- a/ui/src/api/enums/index.ts +++ b/ui/src/api/enums/index.ts @@ -10,3 +10,5 @@ export * from './knowledge' export * from './trigger' export * from './file' export * from './state' + +export * from './document' diff --git a/ui/src/api/types/document.ts b/ui/src/api/types/document.ts new file mode 100644 index 00000000000..ba5b10ef1c3 --- /dev/null +++ b/ui/src/api/types/document.ts @@ -0,0 +1,23 @@ +import type { DOCUMENT_HIT_HANDLING } from '@/api/enums' +import type { KnowledgeType } from './knowledge' +export type DocumentHitHandling = (typeof DOCUMENT_HIT_HANDLING)[keyof typeof DOCUMENT_HIT_HANDLING] +/** 文档分页列表数据。 */ +export interface DocumentItem { + id: string + knowledge_id: string + name: string + type: KnowledgeType + is_active: boolean + status: string + status_meta?: Record + paragraph_count: number + char_length: number + hit_num: number + hit_handling_method: DocumentHitHandling + directly_return_similarity: number + tags?: { id: string; key: string; value: string }[] + meta?: Record + nick_name?: string | null + create_time: string + update_time: string +} diff --git a/ui/src/api/types/index.ts b/ui/src/api/types/index.ts index f2193959be6..1784ea3dfad 100644 --- a/ui/src/api/types/index.ts +++ b/ui/src/api/types/index.ts @@ -25,3 +25,5 @@ export type * from './homepage' export type * from './file' export type * from './state' export type * from './portal' + +export type * from './document' diff --git a/ui/src/assets/iconfont.js b/ui/src/assets/iconfont.js index e86a56969e2..1f847b76358 100644 --- a/ui/src/assets/iconfont.js +++ b/ui/src/assets/iconfont.js @@ -1 +1 @@ -window._iconfont_svg_string_5200172='',(h=>{var a=(l=(l=document.getElementsByTagName("script"))[l.length-1]).getAttribute("data-injectcss"),l=l.getAttribute("data-disable-injectsvg");if(!l){var o,v,t,i,c,d=function(a,l){l.parentNode.insertBefore(a,l)};if(a&&!h.__iconfont__svg__cssinject__){h.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(a){console&&console.log(a)}}o=function(){var a,l=document.createElement("div");l.innerHTML=h._iconfont_svg_string_5200172,(l=l.getElementsByTagName("svg")[0])&&(l.setAttribute("aria-hidden","true"),l.style.position="absolute",l.style.width=0,l.style.height=0,l.style.overflow="hidden",l=l,(a=document.body).firstChild?d(l,a.firstChild):a.appendChild(l))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(o,0):(v=function(){document.removeEventListener("DOMContentLoaded",v,!1),o()},document.addEventListener("DOMContentLoaded",v,!1)):document.attachEvent&&(t=o,i=h.document,c=!1,m(),i.onreadystatechange=function(){"complete"==i.readyState&&(i.onreadystatechange=null,e())})}function e(){c||(c=!0,t())}function m(){try{i.documentElement.doScroll("left")}catch(a){return void setTimeout(m,50)}e()}})(window); \ No newline at end of file +window._iconfont_svg_string_5200172='',(h=>{var a=(l=(l=document.getElementsByTagName("script"))[l.length-1]).getAttribute("data-injectcss"),l=l.getAttribute("data-disable-injectsvg");if(!l){var o,v,t,i,c,d=function(a,l){l.parentNode.insertBefore(a,l)};if(a&&!h.__iconfont__svg__cssinject__){h.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(a){console&&console.log(a)}}o=function(){var a,l=document.createElement("div");l.innerHTML=h._iconfont_svg_string_5200172,(l=l.getElementsByTagName("svg")[0])&&(l.setAttribute("aria-hidden","true"),l.style.position="absolute",l.style.width=0,l.style.height=0,l.style.overflow="hidden",l=l,(a=document.body).firstChild?d(l,a.firstChild):a.appendChild(l))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(o,0):(v=function(){document.removeEventListener("DOMContentLoaded",v,!1),o()},document.addEventListener("DOMContentLoaded",v,!1)):document.attachEvent&&(t=o,i=h.document,c=!1,m(),i.onreadystatechange=function(){"complete"==i.readyState&&(i.onreadystatechange=null,e())})}function e(){c||(c=!0,t())}function m(){try{i.documentElement.doScroll("left")}catch(a){return void setTimeout(m,50)}e()}})(window); \ No newline at end of file diff --git a/ui/src/components/business/select-knowledge-dialog/index.vue b/ui/src/components/business/select-knowledge-dialog/index.vue index 6880af14a84..711f5fec6d2 100644 --- a/ui/src/components/business/select-knowledge-dialog/index.vue +++ b/ui/src/components/business/select-knowledge-dialog/index.vue @@ -2,7 +2,7 @@ import { computed, nextTick, ref, useTemplateRef } from 'vue' import { cloneDeep } from 'lodash' import KnowledgeApi from '@/api/admin/workspace/knowledge/knowledge' -import SharedApi from '@/api/admin/workspace/shared' +import SharedKnowledgeApi from '@/api/admin/workspace/shared/knowledge/knowledge' import type { FolderItem, KnowledgeItem } from '@/api/types' import { RESOURCE_TYPE } from '@/api/enums' import { FOLDER_ENTRIES, FOLDER_ENTRY_ID } from '@/constants/folder' @@ -44,7 +44,7 @@ function refreshKnowledge() { knowledgeOptions.value = [] appliedSearchKeyword.value = searchKeyword.value.trim() const shared = currentFolder.value.id === FOLDER_ENTRY_ID.SHARED - const requestApi = shared ? SharedApi : KnowledgeApi + const requestApi = shared ? SharedKnowledgeApi : KnowledgeApi return requestApi .getAllKnowledge({ ...(shared ? {} : { folder_id: currentFolder.value.id }), diff --git a/ui/src/components/business/select-tool-dialog/index.vue b/ui/src/components/business/select-tool-dialog/index.vue index 67fb9efde85..61fdb57ad31 100644 --- a/ui/src/components/business/select-tool-dialog/index.vue +++ b/ui/src/components/business/select-tool-dialog/index.vue @@ -2,7 +2,7 @@ import { computed, nextTick, ref, useTemplateRef } from 'vue' import { cloneDeep } from 'lodash' import ToolApi from '@/api/admin/workspace/tool/tool' -import SharedApi from '@/api/admin/workspace/shared' +import SharedToolApi from '@/api/admin/workspace/shared/tool' import type { FolderItem, ToolItem, ToolType } from '@/api/types' import { RESOURCE_TYPE, TOOL_TYPE } from '@/api/enums' import { FOLDER_ENTRIES, FOLDER_ENTRY_ID } from '@/constants/folder' @@ -42,7 +42,7 @@ function refreshTool() { toolOptions.value = [] appliedSearchKeyword.value = searchKeyword.value.trim() const shared = currentFolder.value.id === FOLDER_ENTRY_ID.SHARED - const requestApi = shared ? SharedApi : ToolApi + const requestApi = shared ? SharedToolApi : ToolApi return requestApi .getAllTool({ tool_type_list: props.toolTypes, diff --git a/ui/src/layout/ResourceDetailLayout.vue b/ui/src/layout/ResourceDetailLayout.vue index f0edb8a0820..983b38144cf 100644 --- a/ui/src/layout/ResourceDetailLayout.vue +++ b/ui/src/layout/ResourceDetailLayout.vue @@ -1,5 +1,13 @@ + +