From 9e123efe5c377407e8bcf6e314038b08e6df7c47 Mon Sep 17 00:00:00 2001 From: wangdan-fit2cloud Date: Wed, 16 Sep 2026 17:57:24 +0800 Subject: [PATCH] feat: workflow --- ui/src/api/API_README.md | 44 ++- ui/src/api/admin/core/request.ts | 115 ++----- ui/src/api/admin/core/types.ts | 13 +- ui/src/api/admin/store.ts | 13 +- .../workspace/knowledge/workflow-version.ts | 14 + .../api/admin/workspace/knowledge/workflow.ts | 41 ++- .../admin/workspace/tool/workflow-version.ts | 14 + ui/src/api/admin/workspace/tool/workflow.ts | 23 +- ui/src/api/chat/core/request.ts | 85 +---- ui/src/api/chat/core/types.ts | 13 +- ui/src/api/types/application.ts | 17 +- ui/src/api/types/index.ts | 1 + ui/src/api/types/knowledge.ts | 6 + ui/src/api/types/tool.ts | 20 ++ ui/src/api/types/workflow-template.ts | 14 + ui/src/components/COMPONENT_README.md | 3 + ui/src/components/mk-dynamics-form/index.vue | 75 +++-- .../mk-dynamics-form/items/tree/Tree.vue | 35 +- ui/src/views/VIEW_README.md | 123 +++++-- .../components/ButtonTemplateStore.vue | 49 ++- .../TemplateStoreDetailDrawer.vue | 53 --- .../template-store/TemplateStoreDialog.vue | 131 -------- .../tool-store/component/ToolStoreCard.vue | 2 +- .../application/ApplicationWorkflowView.vue | 187 ++++++----- .../application/ButtonTemplateStore.vue | 54 +++ .../components/ApplicationPublishDialog.vue | 102 ------ .../EditPublishVersionDialog.vue | 38 ++- .../TemplateStoreDetailDrawer.vue | 58 ++++ .../template-store/TemplateStoreDialog.vue | 74 ++++ .../components/TemplateCard.vue | 28 +- .../knowledge/ButtonPublishHistory.vue | 75 +++++ .../knowledge/ButtonTemplateStore.vue | 54 +++ .../knowledge/KnowledgeWorkflowView.vue | 316 ++++++++++++++---- .../workflow/knowledge/debug/DebugDrawer.vue | 25 +- .../knowledge/debug/action/DataSource.vue | 9 +- ui/src/views/workflow/knowledge/navigation.ts | 10 + .../workflow/tool/ButtonPublishHistory.vue | 78 +++++ .../workflow/tool/ButtonTemplateStore.vue | 54 +++ .../views/workflow/tool/ToolWorkflowView.vue | 314 +++++++++++++---- .../views/workflow/tool/debug/DebugDrawer.vue | 124 +++++++ .../workflow/tool/debug/ResultDrawer.vue | 157 +++++++++ ui/src/views/workflow/tool/navigation.ts | 18 + ui/src/workflow-canvas/WORKFLOW_README.md | 6 +- .../workflow-canvas/component/NodeSearch.vue | 2 +- 44 files changed, 1831 insertions(+), 856 deletions(-) create mode 100644 ui/src/api/admin/workspace/knowledge/workflow-version.ts create mode 100644 ui/src/api/admin/workspace/tool/workflow-version.ts create mode 100644 ui/src/api/types/workflow-template.ts delete mode 100644 ui/src/views/application/template-store/TemplateStoreDetailDrawer.vue delete mode 100644 ui/src/views/application/template-store/TemplateStoreDialog.vue create mode 100644 ui/src/views/workflow/application/ButtonTemplateStore.vue delete mode 100644 ui/src/views/workflow/application/components/ApplicationPublishDialog.vue create mode 100644 ui/src/views/workflow/components/template-store/TemplateStoreDetailDrawer.vue create mode 100644 ui/src/views/workflow/components/template-store/TemplateStoreDialog.vue rename ui/src/views/{application => workflow/components}/template-store/components/TemplateCard.vue (63%) create mode 100644 ui/src/views/workflow/knowledge/ButtonPublishHistory.vue create mode 100644 ui/src/views/workflow/knowledge/ButtonTemplateStore.vue create mode 100644 ui/src/views/workflow/knowledge/navigation.ts create mode 100644 ui/src/views/workflow/tool/ButtonPublishHistory.vue create mode 100644 ui/src/views/workflow/tool/ButtonTemplateStore.vue create mode 100644 ui/src/views/workflow/tool/debug/DebugDrawer.vue create mode 100644 ui/src/views/workflow/tool/debug/ResultDrawer.vue create mode 100644 ui/src/views/workflow/tool/navigation.ts diff --git a/ui/src/api/API_README.md b/ui/src/api/API_README.md index ffb73c58b14..cdd18f4effe 100644 --- a/ui/src/api/API_README.md +++ b/ui/src/api/API_README.md @@ -11,7 +11,7 @@ src/api/ │ │ └── types.ts # 认证 API 与认证 Store 共用类型 │ ├── core/ # Admin 请求基础能力 │ │ ├── request.ts # Axios 实例、HTTP 方法与统一响应解包 -│ │ └── types.ts # Admin 请求协议和 loading 类型 +│ │ └── types.ts # Admin 请求协议类型 │ ├── system/ # 系统管理业务接口 │ │ ├── chat-user/ # 对话用户、用户组及认证接口 │ │ ├── settings/ # 登录认证、邮件与外观设置接口 @@ -61,7 +61,7 @@ src/api/ - 一类资源的增删改查放在同一个最终资源文件中。调用方直接导入该文件,不为业务目录创建聚合 入口,也不创建汇总所有业务接口的 `api.ts`。 - 工具基础信息和工具工作流使用后端不同资源接口:`workspace/tool/tool.ts` 维护工具增删改查, - `workspace/tool/workflow.ts` 维护工具工作流的加载、保存和发布。 + `workspace/tool/workflow.ts` 维护工具工作流的加载、保存、发布与调试。 工具工作流保存请求的 `default_model_setting` 与详情响应统一复用 `DefaultModelSettingPayload`。 - 每个业务接口函数必须添加简短的 JSDoc,说明接口的业务作用;注释应描述“获取什么”“保存什么” 或“对哪个资源执行什么操作”,不重复参数类型、请求方法等代码已经清楚表达的信息。 @@ -103,6 +103,12 @@ Action、Drawer 或 Dialog。复用方直接使用 `typeof XxxApi` 约束完整 共用类型 `WorkflowVersion` 和 `WorkflowVersionPayload` 位于 `types/workflow-version.ts`, 通过 `@/api/types` 导出。`ButtonApplicationPublishHistory` 内部调用智能体版本 API,公共发布历史 UI 组件只接收数据和事件,不接收 API 或推测其他工作流的接口地址。 +`workspace/tool/workflow-version.ts` 单独维护工具 `tool_version` 资源,提供 +`getWorkflowVersions(toolId)` 和 `putWorkflowVersion(toolId, versionId, payload)`, +复用上述版本类型,由 `views/workflow/tool/ButtonPublishHistory.vue` 调用。 +工具版本接口同样尚未支持 `description`,且未返回版本的默认模型配置。 +`workspace/knowledge/workflow-version.ts` 维护知识库 `knowledge_version` 资源,提供同名查询与编辑方法, +由知识库 `ButtonPublishHistory.vue` 调用;知识库版本也尚未支持更新说明和默认模型配置。 v3 编辑表单提交 `{ name, description }`,标题上限 64、更新说明上限 1000。 当前仓库后端的版本编辑序列化器只处理 `name`,列表和详情也未返回 `description`; @@ -110,9 +116,10 @@ v3 编辑表单提交 `{ name, description }`,标题上限 64、更新说明 ### 智能体模板中心 -`admin/tool-store.ts` 的 `getStoreApplicationList(query)` 查询智能体模板,直接返回 -`ApplicationStoreResponse`,不再返回 `unknown`。模板元数据使用 `ApplicationStoreTemplate`, -与响应类型一起维护在 `api/types/application.ts`,经 `@/api/types` 导入。 +`admin/store.ts` 的 `getStoreApplicationList(query)` 查询智能体模板,直接返回 +`ApplicationStoreResponse`,不再返回 `unknown`。公共模板元数据为 `api/types/workflow-template.ts` 的 `WorkflowStoreTemplate`, +智能体 `ApplicationStoreTemplate` 为其类型别名,响应结构仍由 `api/types/application.ts` 维护。 +类型统一经 `@/api/types` 导入;各资源业务入口整理响应字段,公共 UI 不调用接口。 创建或覆盖工作流时通过已有智能体 API 提交 `work_flow_template`,成功后的刷新与导航由 View 负责。 ### 模型选项查询 @@ -235,7 +242,8 @@ API 枚举与类型统一在 `src/api` 范围内管理,相关规则由本文 - token 和平台公开档案由 `stores/auth.ts` 管理,语言由 `stores/user.ts` 管理;Router、Axios 等业务代码通过 `stores/index.ts` 导出的 `useStore()` 按需访问 Store;401 响应统一清除 token 并跳转 Admin 登录页。 -- loading 不作为 API 函数参数,由调用接口的页面或 Store 管理。 +- loading 不作为业务 API 或 Admin、Chat 底层请求封装的参数;JSON 请求、文件上传和下载均遵循此规则。 + 由调用接口的页面、组件或 Store 在请求前开启 loading,并在 Promise 的 `finally` 中释放,确保成功和失败都恢复状态。 - 流式 POST 请求使用 `postStream` 返回原始 `Response`,由业务组件按具体协议解析数据块; 参数顺序为 `postStream(base, path, data?, config?)`,`config.signal` 用于取消请求。 鉴权、语言请求头和错误状态仍由请求基础设施统一处理。 @@ -300,3 +308,27 @@ API 对象和工作空间上下文,作为该抽屉的范围选择例外;用 `ApplicationDetail` 复用 `ApplicationFormPayload` 中的配置字段,并保留详情接口的 `model` 和可空描述。复制通过 `getApplicationDetail` 获取完整配置,将 `model` 映射为 `model_id`, 再调用 `postApplication` 创建副本;不使用卡片列表摘要作为复制数据。 + +### 工具工作流调试 + +`workspace/tool/workflow.ts` 的 `postToolWorkflowDebug(toolId, parameters)` 使用 Admin `postStream` +请求 `//debug`,返回原始 SSE Response;输入参数来自工具基础节点,`chat_record_id` 用于识别 +执行记录,表单续跑沿用该 ID 并传入 `position`。`getToolWorkflowRecord(toolId, recordId)` 查询 +`//tool_record/`,返回 `ToolWorkflowRecord` 的运行状态、输出及节点详情。 +工具调试读取服务端已保存工作流,画布页面在调试前保存未提交改动。 + +### 工具工作流模板中心 + +`admin/store.ts` 的 `getStoreToolWorkflowList(query)` 查询 `/workspace/store/tool_workflow_template`, +返回 `ToolWorkflowStoreResponse`,其中 `apps` 复用 `WorkflowStoreTemplate[]`。 +`workspace/tool/workflow.ts` 的 `putToolWorkflow` 支持两种互斥载荷:保存 `work_flow` 与默认模型设置, +或提交 `work_flow_template` 由服务端下载并覆盖当前工具工作流。模板覆盖后由 View 重新查询详情, +同步默认模型设置、保存时间和图快照;确认取消或请求失败不关闭模板中心。 + +### 知识库工作流模板与导出 + +`admin/store.ts` 的 `getStoreKnowledgeList(query)` 查询 `/workspace/store/knowledge_template`, +返回 `KnowledgeWorkflowStoreResponse`,其中 `apps` 使用公共 `WorkflowStoreTemplate[]`。 +`putKnowledgeWorkflow` 接受互斥的 `work_flow` 保存载荷或 `work_flow_template` 覆盖载荷,覆盖成功后由 View 重载详情。 +`exportKnowledgeWorkflow(knowledgeId, name)` 通过 GET `//workflow/export` 下载 `.kbwf` 文件, +只导出工作流,不调用包含文档的知识库包导出接口。 diff --git a/ui/src/api/admin/core/request.ts b/ui/src/api/admin/core/request.ts index 94bb14c02c6..e04e77d3a76 100644 --- a/ui/src/api/admin/core/request.ts +++ b/ui/src/api/admin/core/request.ts @@ -3,7 +3,7 @@ import axios, { AxiosHeaders, type AxiosRequestConfig, type AxiosResponse, type InternalAxiosRequestConfig } from 'axios' import router from '@/router/admin' import { useStore } from '@/stores' -import type { ApiResponse, LoadingTarget } from './types' +import type { ApiResponse } from './types' import type { Dict } from '@/api/types' import { MsgError } from '@/utils/message' import { ADMIN_API_BASE_PATH } from '@/api/constants' @@ -30,28 +30,6 @@ function setRequestHeaders(config: InternalAxiosRequestConfig) { return config } -function startLoading(loading?: LoadingTarget) { - if (!loading) { - return - } - if ('start' in loading) { - loading.start() - return - } - loading.value = true -} - -function finishLoading(loading?: LoadingTarget) { - if (!loading) { - return - } - if ('done' in loading) { - loading.done() - return - } - loading.value = false -} - function extractFilename(contentDisposition?: string) { if (!contentDisposition) { return undefined @@ -93,7 +71,6 @@ async function getResponseErrorMessage(error: unknown) { return responseData?.message } - async function downloadExportResponse(response: AxiosResponse, fileName: string, mimeType = 'application/octet-stream') { if (response.data.type.includes('application/json')) { const text = await response.data.text() @@ -165,81 +142,49 @@ request.interceptors.response.use( ) /** - * 统一解包标准 API 响应,并同步可选的 loading 状态。 + * 统一解包标准 API 响应。 */ -export async function promise(requestPromise: Promise>>, loading?: LoadingTarget) { - startLoading(loading) - try { - const response = await requestPromise - return response.data.data - } finally { - finishLoading(loading) - } +export async function promise(requestPromise: Promise>>) { + const response = await requestPromise + return response.data.data } /** 发送 GET 请求。 */ -export function get(url: string, params?: Dict, loading?: LoadingTarget, timeout?: number) { - return promise(request.get>(url, { params, timeout }), loading) +export function get(url: string, params?: Dict, timeout?: number) { + return promise(request.get>(url, { params, timeout })) } /** 发送 POST 请求。 */ -export function post(url: string, data?: TData, params?: Dict, loading?: LoadingTarget, timeout?: number) { - return promise(request.post>(url, data, { params, timeout }), loading) +export function post(url: string, data?: TData, params?: Dict, timeout?: number) { + return promise(request.post>(url, data, { params, timeout })) } /** 发送 GET 请求并将 Blob 响应下载为文件。 */ -export async function getExportFile(fileName: string, url: string, params?: Dict, loading?: LoadingTarget): Promise { - startLoading(loading) - try { - const response = await request.get(url, { params, responseType: 'blob', skipGlobalErrorMessage: true } as ExportRequestConfig) +export async function getExportFile(fileName: string, url: string, params?: Dict): Promise { + const response = await request.get(url, { params, responseType: 'blob', skipGlobalErrorMessage: true } as ExportRequestConfig) - return downloadExportResponse(response, fileName) - } finally { - finishLoading(loading) - } + return downloadExportResponse(response, fileName) } /** 发送 POST 请求并将 Blob 响应下载为 Excel 文件。 */ -export async function postExportExcel( - fileName: string, - url: string, - params?: Dict, - data?: TData, - loading?: LoadingTarget, -): Promise { - startLoading(loading) - try { - const response = await request.post(url, data, { params, responseType: 'blob', skipGlobalErrorMessage: true } as ExportRequestConfig) +export async function postExportExcel(fileName: string, url: string, params?: Dict, data?: TData): Promise { + const response = await request.post(url, data, { params, responseType: 'blob', skipGlobalErrorMessage: true } as ExportRequestConfig) - return downloadExportResponse(response, fileName, 'application/vnd.ms-excel') - } finally { - finishLoading(loading) - } + return downloadExportResponse(response, fileName, 'application/vnd.ms-excel') } /** 发送指定方法的 Blob 请求并触发浏览器下载。 */ -export async function downloadRequest( - url: string, - method: string, - data?: unknown, - params?: Dict, - loading?: LoadingTarget, -): Promise { - startLoading(loading) - try { - const response = await request.request({ - url, - method, - data, - params, - responseType: 'blob', - skipGlobalErrorMessage: true, - } as ExportRequestConfig) - - return downloadExportResponse(response, 'download') - } finally { - finishLoading(loading) - } +export async function downloadRequest(url: string, method: string, data?: unknown, params?: Dict): Promise { + const response = await request.request({ + url, + method, + data, + params, + responseType: 'blob', + skipGlobalErrorMessage: true, + } as ExportRequestConfig) + + return downloadExportResponse(response, 'download') } /** 发送 POST 请求并返回可逐块读取的原始响应。 */ @@ -260,13 +205,13 @@ export function postStream(base: string, path: string, data?: unknown): Promise< } /** 发送 PUT 请求。 */ -export function put(url: string, data?: TData, params?: Dict, loading?: LoadingTarget, timeout?: number) { - return promise(request.put>(url, data, { params, timeout }), loading) +export function put(url: string, data?: TData, params?: Dict, timeout?: number) { + return promise(request.put>(url, data, { params, timeout })) } /** 发送 DELETE 请求。 */ -export function del(url: string, params?: Dict, data?: TData, loading?: LoadingTarget, timeout?: number) { - return promise(request.delete>(url, { params, data, timeout }), loading) +export function del(url: string, params?: Dict, data?: TData, timeout?: number) { + return promise(request.delete>(url, { params, data, timeout })) } export default request diff --git a/ui/src/api/admin/core/types.ts b/ui/src/api/admin/core/types.ts index ee98ff8de73..d6993e86ecf 100644 --- a/ui/src/api/admin/core/types.ts +++ b/ui/src/api/admin/core/types.ts @@ -1,4 +1,4 @@ -/** Admin 请求基础设施内部使用的协议和 loading 类型。 */ +/** Admin 请求基础设施内部使用的协议类型。 */ export interface ApiResponse { code: number @@ -6,17 +6,6 @@ export interface ApiResponse { data: T } -interface LoadingRef { - value: boolean -} - -interface LoadingProgress { - start(): void - done(): void -} - -export type LoadingTarget = LoadingRef | LoadingProgress - export interface ResponsePage { total: number records: T[] diff --git a/ui/src/api/admin/store.ts b/ui/src/api/admin/store.ts index d5b1e6293a7..540f2aa9e57 100644 --- a/ui/src/api/admin/store.ts +++ b/ui/src/api/admin/store.ts @@ -1,5 +1,12 @@ import { get } from './core/request' -import type { ApplicationStoreResponse, Dict, ToolItem, ToolStoreResponse } from '@/api/types' +import type { + ApplicationStoreResponse, + KnowledgeWorkflowStoreResponse, + Dict, + ToolItem, + ToolStoreResponse, + ToolWorkflowStoreResponse, +} from '@/api/types' /** 获取系统内置工具。 */ const getInternalToolList = (query?: Dict) => { @@ -18,12 +25,12 @@ const getStoreApplicationList = (query?: Dict) => { /** 获取知识库模板商店列表。 */ const getStoreKnowledgeList = (query?: Dict) => { - return get('/workspace/store/knowledge_template', query) + return get('/workspace/store/knowledge_template', query) } /** 获取工作流工具模板商店列表。 */ const getStoreToolWorkflowList = (query?: Dict) => { - return get('/workspace/store/tool_workflow_template', query) + return get('/workspace/store/tool_workflow_template', query) } export default { getInternalToolList, getStoreApplicationList, getStoreKnowledgeList, getStoreToolList, getStoreToolWorkflowList } diff --git a/ui/src/api/admin/workspace/knowledge/workflow-version.ts b/ui/src/api/admin/workspace/knowledge/workflow-version.ts new file mode 100644 index 00000000000..1b2df561121 --- /dev/null +++ b/ui/src/api/admin/workspace/knowledge/workflow-version.ts @@ -0,0 +1,14 @@ +import { get, put } from '../../core/request' +import type { WorkflowVersion, WorkflowVersionPayload } from '@/api/types' +import { getWorkspaceId } from '@/utils/resource-context' + +const getPrefix = (knowledgeId: string) => `/workspace/${getWorkspaceId()}/knowledge/${knowledgeId}/knowledge_version` + +/** 获取知识库发布历史,按发布时间倒序返回完整版本快照。 */ +const getWorkflowVersions = (knowledgeId: string) => get(getPrefix(knowledgeId)) + +/** 修改知识库历史版本标题和更新说明,更新说明需要服务端支持 description 字段。 */ +const putWorkflowVersion = (knowledgeId: string, versionId: string, data: WorkflowVersionPayload) => + put(`${getPrefix(knowledgeId)}/${versionId}`, data) + +export default { getWorkflowVersions, putWorkflowVersion } diff --git a/ui/src/api/admin/workspace/knowledge/workflow.ts b/ui/src/api/admin/workspace/knowledge/workflow.ts index bf0544223c3..53526467831 100644 --- a/ui/src/api/admin/workspace/knowledge/workflow.ts +++ b/ui/src/api/admin/workspace/knowledge/workflow.ts @@ -1,6 +1,5 @@ import type LogicFlow from '@logicflow/core' -import { get, post, put } from '../../core/request' -import type { LoadingTarget } from '../../core/types' +import { get, post, put, getExportFile } from '../../core/request' import type { DefaultModelSettingPayload, Dict, @@ -10,13 +9,13 @@ import type { KnowledgeWorkflowAction, KnowledgeWorkflowDebugPayload, KnowledgeWorkflowDetail, + WorkflowStoreTemplate, } from '@/api/types' import { getWorkspaceId } from '@/utils/resource-context' -interface KnowledgeWorkflowPayload { - default_model_setting?: DefaultModelSettingPayload - work_flow: LogicFlow.GraphConfigData -} +type KnowledgeWorkflowPayload = + | { default_model_setting?: DefaultModelSettingPayload; work_flow: LogicFlow.GraphConfigData; work_flow_template?: never } + | { work_flow_template: WorkflowStoreTemplate; work_flow?: never } interface CreateKnowledgeWorkflowPayload extends KnowledgeCreatePayload { work_flow: LogicFlow.GraphConfigData @@ -44,41 +43,39 @@ const putKnowledgeWorkflowPublish = (knowledgeId: string) => { } /** 上传知识库调试文件,返回文件访问地址(末段为 file_id)。 */ -const postKnowledgeUploadFile = (knowledgeId: string, file: File, loading?: LoadingTarget) => { +const postKnowledgeUploadFile = (knowledgeId: string, file: File) => { const payload = new FormData() payload.append('file', file) payload.append('source_id', knowledgeId) payload.append('source_type', 'KNOWLEDGE') - return post('/oss/file', payload, undefined, loading) + return post('/oss/file', payload) } /** 获取数据源节点的动态表单配置。 */ -const getKnowledgeWorkflowFormList = ( - knowledgeId: string, - type: 'local' | 'tool', - id: string, - node: Dict, - loading?: LoadingTarget, -) => { - return post<{ node: Dict }, Dict[]>(`${getPrefix()}/${knowledgeId}/datasource/${type}/${id}/form_list`, { node }, undefined, loading) +const getKnowledgeWorkflowFormList = (knowledgeId: string, type: 'local' | 'tool', id: string, node: Dict) => { + return post<{ node: Dict }, Dict[]>(`${getPrefix()}/${knowledgeId}/datasource/${type}/${id}/form_list`, { node }) } /** 提交知识库工作流调试任务。 */ -const postKnowledgeWorkflowDebug = (knowledgeId: string, payload: KnowledgeWorkflowDebugPayload, loading?: LoadingTarget) => { - return post(`${getPrefix()}/${knowledgeId}/debug`, payload, undefined, loading) +const postKnowledgeWorkflowDebug = (knowledgeId: string, payload: KnowledgeWorkflowDebugPayload) => { + return post(`${getPrefix()}/${knowledgeId}/debug`, payload) } /** 轮询知识库工作流调试任务详情。 */ -const getKnowledgeWorkflowAction = (knowledgeId: string, actionId: string, loading?: LoadingTarget) => { - return get(`${getPrefix()}/${knowledgeId}/action/${actionId}`, undefined, loading) +const getKnowledgeWorkflowAction = (knowledgeId: string, actionId: string) => { + return get(`${getPrefix()}/${knowledgeId}/action/${actionId}`) } /** 取消知识库工作流调试任务。 */ -const postCancelKnowledgeWorkflowAction = (knowledgeId: string, actionId: string, loading?: LoadingTarget) => { - return post(`${getPrefix()}/${knowledgeId}/action/${actionId}/cancel`, undefined, undefined, loading) +const postCancelKnowledgeWorkflowAction = (knowledgeId: string, actionId: string) => { + return post(`${getPrefix()}/${knowledgeId}/action/${actionId}/cancel`) } +/** 导出知识库工作流文件,不包含知识库文档。 */ +const exportKnowledgeWorkflow = (knowledgeId: string, name: string) => getExportFile(`${name}.kbwf`, `${getPrefix()}/${knowledgeId}/workflow/export`) + export default { + exportKnowledgeWorkflow, postKnowledgeWorkflow, putKnowledgeWorkflow, putKnowledgeWorkflowPublish, diff --git a/ui/src/api/admin/workspace/tool/workflow-version.ts b/ui/src/api/admin/workspace/tool/workflow-version.ts new file mode 100644 index 00000000000..491fd72d10b --- /dev/null +++ b/ui/src/api/admin/workspace/tool/workflow-version.ts @@ -0,0 +1,14 @@ +import { get, put } from '../../core/request' +import type { WorkflowVersion, WorkflowVersionPayload } from '@/api/types' +import { getWorkspaceId } from '@/utils/resource-context' + +const getPrefix = (toolId: string) => `/workspace/${getWorkspaceId()}/tool/${toolId}/tool_version` + +/** 获取工具发布历史,按发布时间倒序返回完整版本快照。 */ +const getWorkflowVersions = (toolId: string) => get(getPrefix(toolId)) + +/** 修改工具历史版本标题和更新说明,更新说明需要服务端支持 description 字段。 */ +const putWorkflowVersion = (toolId: string, versionId: string, data: WorkflowVersionPayload) => + put(`${getPrefix(toolId)}/${versionId}`, data) + +export default { getWorkflowVersions, putWorkflowVersion } diff --git a/ui/src/api/admin/workspace/tool/workflow.ts b/ui/src/api/admin/workspace/tool/workflow.ts index 79ca5b1d83c..28fbc23b6fd 100644 --- a/ui/src/api/admin/workspace/tool/workflow.ts +++ b/ui/src/api/admin/workspace/tool/workflow.ts @@ -1,12 +1,12 @@ import type LogicFlow from '@logicflow/core' -import { get, put } from '../../core/request' -import type { DefaultModelSettingPayload, ToolWorkflowDetail } from '@/api/types' +import { get, put, postStream } from '../../core/request' +import type { DefaultModelSettingPayload, ToolWorkflowDetail, ToolWorkflowRecord, WorkflowStoreTemplate } from '@/api/types' +import { ADMIN_API_BASE_PATH } from '@/api/constants' import { getWorkspaceId } from '@/utils/resource-context' -interface ToolWorkflowPayload { - default_model_setting?: DefaultModelSettingPayload - work_flow: LogicFlow.GraphConfigData -} +type ToolWorkflowPayload = + | { default_model_setting?: DefaultModelSettingPayload; work_flow: LogicFlow.GraphConfigData; work_flow_template?: never } + | { work_flow_template: WorkflowStoreTemplate; work_flow?: never } const getPrefix = () => { const workspaceId = getWorkspaceId() @@ -18,7 +18,7 @@ const getToolWorkflow = (toolId: string) => { return get(`${getPrefix()}/${toolId}/workflow`) } -/** 保存工具工作流。 */ +/** 保存工具工作流,或使用商店模板覆盖当前工作流。 */ const putToolWorkflow = (toolId: string, payload: ToolWorkflowPayload) => { return put(`${getPrefix()}/${toolId}/workflow`, payload) } @@ -28,4 +28,11 @@ const putToolWorkflowPublish = (toolId: string) => { return put(`${getPrefix()}/${toolId}/publish`) } -export default { getToolWorkflow, putToolWorkflow, putToolWorkflowPublish } +/** 调试已保存的工具工作流,返回 SSE 响应。 */ +const postToolWorkflowDebug = (toolId: string, parameters: Record) => + postStream(ADMIN_API_BASE_PATH, `${getPrefix()}/${toolId}/debug`, parameters) + +/** 查询工具工作流调试的输出和节点执行记录。 */ +const getToolWorkflowRecord = (toolId: string, recordId: string) => get(`${getPrefix()}/${toolId}/tool_record/${recordId}`) + +export default { getToolWorkflow, putToolWorkflow, putToolWorkflowPublish, postToolWorkflowDebug, getToolWorkflowRecord } diff --git a/ui/src/api/chat/core/request.ts b/ui/src/api/chat/core/request.ts index 5f6a9229892..ff96cb9a62c 100644 --- a/ui/src/api/chat/core/request.ts +++ b/ui/src/api/chat/core/request.ts @@ -1,12 +1,8 @@ /** 提供 Chat API 的 Axios 实例与常用 HTTP 请求封装。 */ -import axios, { - AxiosHeaders, - type AxiosResponse, - type InternalAxiosRequestConfig, -} from 'axios' +import axios, { AxiosHeaders, type AxiosResponse, type InternalAxiosRequestConfig } from 'axios' import { useStore } from '@/stores' -import type { ApiResponse, LoadingTarget } from './types' +import type { ApiResponse } from './types' import type { Dict } from '@/api/types' import { MsgError } from '@/utils/message' import { CHAT_API_BASE_PATH } from '@/api/constants' @@ -29,28 +25,6 @@ function setRequestHeaders(config: InternalAxiosRequestConfig) { return config } -function startLoading(loading?: LoadingTarget) { - if (!loading) { - return - } - if ('start' in loading) { - loading.start() - return - } - loading.value = true -} - -function finishLoading(loading?: LoadingTarget) { - if (!loading) { - return - } - if ('done' in loading) { - loading.done() - return - } - loading.value = false -} - async function getResponseErrorMessage(error: unknown) { if (!axios.isAxiosError | string>(error)) { return undefined @@ -92,62 +66,31 @@ request.interceptors.response.use( ) /** - * 统一解包标准 API 响应,并同步可选的 loading 状态。 + * 统一解包标准 API 响应。 */ -export async function promise( - requestPromise: Promise>>, - loading?: LoadingTarget, -) { - startLoading(loading) - try { - const response = await requestPromise - return response.data.data - } finally { - finishLoading(loading) - } +export async function promise(requestPromise: Promise>>) { + const response = await requestPromise + return response.data.data } /** 发送 GET 请求。 */ -export function get( - url: string, - params?: Dict, - loading?: LoadingTarget, - timeout?: number, -) { - return promise(request.get>(url, { params, timeout }), loading) +export function get(url: string, params?: Dict, timeout?: number) { + return promise(request.get>(url, { params, timeout })) } /** 发送 POST 请求。 */ -export function post( - url: string, - data?: TData, - params?: Dict, - loading?: LoadingTarget, - timeout?: number, -) { - return promise(request.post>(url, data, { params, timeout }), loading) +export function post(url: string, data?: TData, params?: Dict, timeout?: number) { + return promise(request.post>(url, data, { params, timeout })) } /** 发送 PUT 请求。 */ -export function put( - url: string, - data?: TData, - params?: Dict, - loading?: LoadingTarget, - timeout?: number, -) { - return promise(request.put>(url, data, { params, timeout }), loading) +export function put(url: string, data?: TData, params?: Dict, timeout?: number) { + return promise(request.put>(url, data, { params, timeout })) } /** 发送 DELETE 请求。 */ -export function del( - url: string, - params?: Dict, - data?: TData, - loading?: LoadingTarget, - timeout?: number, -) { - return promise(request.delete>(url, { params, data, timeout }), loading) +export function del(url: string, params?: Dict, data?: TData, timeout?: number) { + return promise(request.delete>(url, { params, data, timeout })) } export default request diff --git a/ui/src/api/chat/core/types.ts b/ui/src/api/chat/core/types.ts index 4040dd2fc85..abba48f5017 100644 --- a/ui/src/api/chat/core/types.ts +++ b/ui/src/api/chat/core/types.ts @@ -1,4 +1,4 @@ -/** Chat 请求基础设施内部使用的协议和 loading 类型。 */ +/** Chat 请求基础设施内部使用的协议类型。 */ export interface ApiResponse { code: number @@ -6,17 +6,6 @@ export interface ApiResponse { data: T } -interface LoadingRef { - value: boolean -} - -interface LoadingProgress { - start(): void - done(): void -} - -export type LoadingTarget = LoadingRef | LoadingProgress - export interface ResponsePage { total: number records: T[] diff --git a/ui/src/api/types/application.ts b/ui/src/api/types/application.ts index c579ea78d21..81782dc72c8 100644 --- a/ui/src/api/types/application.ts +++ b/ui/src/api/types/application.ts @@ -3,6 +3,7 @@ import type LogicFlow from '@logicflow/core' import { APPLICATION_TYPE } from '@/api/enums' import type { DefaultModelSettingPayload } from './model' +import type { WorkflowStoreTemplate } from './workflow-template' export type ApplicationType = (typeof APPLICATION_TYPE)[keyof typeof APPLICATION_TYPE] @@ -85,20 +86,8 @@ export interface PromptGeneratePayload { prompt: string } -/** 智能体模板中心的模板及下载元数据。 */ -export interface ApplicationStoreTemplate { - id: string - name: string - desc?: string | null - description?: string | null - icon?: string - label?: string | null - downloads?: number - readMe?: string - downloadUrl?: string - downloadCallbackUrl?: string - [key: string]: unknown -} +/** 智能体模板复用工作流模板的展示与下载元数据。 */ +export type ApplicationStoreTemplate = WorkflowStoreTemplate export interface ApplicationStoreResponse { additionalProperties: { tags: { key: string; name: string }[] } diff --git a/ui/src/api/types/index.ts b/ui/src/api/types/index.ts index 84e8602cbdc..78483776e16 100644 --- a/ui/src/api/types/index.ts +++ b/ui/src/api/types/index.ts @@ -19,3 +19,4 @@ export type * from './knowledge' export type * from './trigger' export type * from './related-resources' export type * from './workflow-version' +export type * from './workflow-template' diff --git a/ui/src/api/types/knowledge.ts b/ui/src/api/types/knowledge.ts index bddbac94c9f..16f5463f005 100644 --- a/ui/src/api/types/knowledge.ts +++ b/ui/src/api/types/knowledge.ts @@ -100,3 +100,9 @@ export interface KnowledgeWorkflowTemplate { downloadUrl: string downloadCallbackUrl?: string } + +/** 知识库工作流模板商店响应。 */ +export interface KnowledgeWorkflowStoreResponse { + additionalProperties: { tags: { key: string; name: string }[] } + apps: import('./workflow-template').WorkflowStoreTemplate[] +} diff --git a/ui/src/api/types/tool.ts b/ui/src/api/types/tool.ts index 9edd5f07692..43d877f0133 100644 --- a/ui/src/api/types/tool.ts +++ b/ui/src/api/types/tool.ts @@ -3,6 +3,7 @@ import type LogicFlow from '@logicflow/core' import { TOOL_SCOPE, TOOL_TYPE } from '@/api/enums' import type { DynamicFormField } from './common' +import type { WorkflowStoreTemplate } from './workflow-template' import type { DefaultModelSettingPayload } from '@/api/types/model.ts' export type ToolScope = (typeof TOOL_SCOPE)[keyof typeof TOOL_SCOPE] @@ -160,3 +161,22 @@ export interface UpdateStoreToolPayload { label: string versions: ToolStoreVersion[] } + +/** 工具工作流调试完成后的执行记录。 */ +export interface ToolWorkflowRecord { + id: string + state: string + run_time?: number + meta: { + output?: unknown + details?: + | import('@/workflow-canvas/details/types').ExecutionNodeDetail[] + | Record + } +} + +/** 工作流工具模板商店的查询响应。 */ +export interface ToolWorkflowStoreResponse { + additionalProperties: { tags: ToolStoreTag[] } + apps: WorkflowStoreTemplate[] +} diff --git a/ui/src/api/types/workflow-template.ts b/ui/src/api/types/workflow-template.ts new file mode 100644 index 00000000000..849f79db924 --- /dev/null +++ b/ui/src/api/types/workflow-template.ts @@ -0,0 +1,14 @@ +/** 工作流模板中心的模板及下载元数据。 */ +export interface WorkflowStoreTemplate { + id: string + name: string + desc?: string | null + description?: string | null + icon?: string + label?: string | null + downloads?: number + readMe?: string + downloadUrl?: string + downloadCallbackUrl?: string + [key: string]: unknown +} diff --git a/ui/src/components/COMPONENT_README.md b/ui/src/components/COMPONENT_README.md index be18372d8f4..07dc91a7c9d 100644 --- a/ui/src/components/COMPONENT_README.md +++ b/ui/src/components/COMPONENT_README.md @@ -450,6 +450,9 @@ JSON 专用输入框,通过 `v-model` 接收并回传解析后的 JSON 值, ### MkDynamicsForm、MkDynamicsFormConstructor +表单配置加载、字段联动请求与 Tree 懒加载的 loading 由组件在请求前开启,并在 `finally` 中释放; +不向 API 请求方法传递 loading,动态请求脚本也不再通过 `extra.loading` 获取状态。 + `MkDynamicsForm` 根据字段配置渲染动态表单,统一维护字段值、默认值、显隐规则和表单校验; `MkDynamicsFormConstructor` 用于新增或编辑单个字段配置。该组件族位于 `components` 直属目录, 使用方必须从 `@/components/mk-dynamics-form` 手动导入,不安装为 Vue 插件,也不全局注册其内部 diff --git a/ui/src/components/mk-dynamics-form/index.vue b/ui/src/components/mk-dynamics-form/index.vue index eebf3bdd67f..93401ddba01 100644 --- a/ui/src/components/mk-dynamics-form/index.vue +++ b/ui/src/components/mk-dynamics-form/index.vue @@ -195,37 +195,47 @@ function triggerFieldRequest( target: Dict, requestLoading: Ref, ) { - const executeRequest = new Function('self', 'triggerSetting', 'request', 'extra', triggerSetting.request || 'return request.get(extra.renderTemplate(triggerSetting.url));') as ( + const executeRequest = new Function( + 'self', + 'triggerSetting', + 'request', + 'extra', + triggerSetting.request || 'return request.get(extra.renderTemplate(triggerSetting.url));', + ) as ( self: Dict, setting: DynamicFormTriggerSetting, requestHelpers: typeof request, extra: Dict, ) => Promise - const requestPromise = executeRequest(target, triggerSetting, request, { - loading: requestLoading, - renderTemplate: (url: string) => renderTemplate(url, { trigger_value: triggerValue, ...props.otherParams }), - }) - - if (!triggerSetting.change && !triggerSetting.change_field) { - return - } - - void requestPromise.then((response) => { - const applyResponse = new Function( - 'self', - 'triggerSetting', - 'response', - 'extra', - triggerSetting.change || - `self[triggerSetting.change_field]=[ - ...response.data.shared_model.map((model) => ({ ...model, type: 'share' })), - ...response.data.model.map((model) => ({ ...model, type: 'workspace' })) - ];`, - ) as (self: Dict, setting: DynamicFormTriggerSetting, response: DynamicFormValue, extra: Dict) => void - - applyResponse(target, triggerSetting, response, { formData: formValue.value, getDefault: getFormDefaultValue }) - }) + requestLoading.value = true + return Promise.resolve() + .then(() => + executeRequest(target, triggerSetting, request, { + renderTemplate: (url: string) => renderTemplate(url, { trigger_value: triggerValue, ...props.otherParams }), + }), + ) + .then((response) => { + if (!triggerSetting.change && !triggerSetting.change_field) { + return + } + const applyResponse = new Function( + 'self', + 'triggerSetting', + 'response', + 'extra', + triggerSetting.change || + `self[triggerSetting.change_field]=[ + ...response.data.shared_model.map((model) => ({ ...model, type: 'share' })), + ...response.data.model.map((model) => ({ ...model, type: 'workspace' })) + ];`, + ) as (self: Dict, setting: DynamicFormTriggerSetting, response: DynamicFormValue, extra: Dict) => void + + applyResponse(target, triggerSetting, response, { formData: formValue.value, getDefault: getFormDefaultValue }) + }) + .finally(() => { + requestLoading.value = false + }) } function initializeFieldDefault(field: FormField) { @@ -263,7 +273,10 @@ function getResponseFields(response: FormField[] | DynamicFormResponse { if (typeof source === 'string') { - return get(source, {}, loading) + loading.value = true + return get(source).finally(() => { + loading.value = false + }) } if (Array.isArray(source)) { return source @@ -298,7 +311,15 @@ defineExpose({ initDefaultData: initializeFieldDefault, render, ruleFormRef, val