From 90225f07d941db460bc09991b840e99a8747b9c5 Mon Sep 17 00:00:00 2001 From: yyh Date: Thu, 26 Mar 2026 14:32:47 +0800 Subject: [PATCH] fix(web): align data contracts with backend schema --- .../components/app/app-publisher/index.tsx | 7 +- ...import-from-marketplace-template-modal.tsx | 13 +- web/app/components/apps/list.tsx | 22 +++- .../workflow/hooks/use-workflow-comment.ts | 5 +- .../__tests__/index.spec.tsx | 2 +- .../components/__tests__/right-panel.spec.tsx | 2 +- .../components/right-panel.tsx | 2 +- .../__tests__/use-context-gen-data.spec.ts | 2 +- .../hooks/use-context-gen-data.ts | 2 +- .../hooks/use-context-generate.ts | 39 +++--- .../context-generate-modal/index.tsx | 2 +- .../hooks/use-mixed-variable-extractor.ts | 15 ++- web/contract/console/app-asset.ts | 11 -- web/contract/console/apps.ts | 89 ++++++++++++- web/contract/console/generator.ts | 103 +++++++++++++++ web/contract/console/workflow-comment.ts | 39 +++--- web/contract/console/workflow.ts | 15 +++ web/contract/router.ts | 29 +++- web/models/app.ts | 4 +- web/service/apps.ts | 124 +++--------------- web/service/debug.ts | 90 +------------ web/service/marketplace-templates.ts | 12 -- web/service/use-app-asset.ts | 30 ++--- web/service/workflow-comment.ts | 15 ++- web/service/workflow.ts | 6 - web/types/app-asset.ts | 1 - 26 files changed, 364 insertions(+), 317 deletions(-) create mode 100644 web/contract/console/generator.ts diff --git a/web/app/components/app/app-publisher/index.tsx b/web/app/components/app/app-publisher/index.tsx index 72d6490fa7..0674a2a977 100644 --- a/web/app/components/app/app-publisher/index.tsx +++ b/web/app/components/app/app-publisher/index.tsx @@ -40,7 +40,8 @@ import { useAsyncWindowOpen } from '@/hooks/use-async-window-open' import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now' import { AccessMode } from '@/models/access-control' import { useAppWhiteListSubjects, useGetUserCanAccessApp } from '@/service/access-control' -import { fetchAppDetailDirect, publishToCreatorsPlatform } from '@/service/apps' +import { fetchAppDetailDirect } from '@/service/apps' +import { consoleClient } from '@/service/client' import { fetchInstalledAppList } from '@/service/explore' import { useInvalidateAppWorkflow } from '@/service/use-workflow' import { fetchPublishedWorkflow } from '@/service/workflow' @@ -286,7 +287,9 @@ const AppPublisher = ({ return setPublishingToMarketplace(true) try { - const result = await publishToCreatorsPlatform({ appID: appDetail.id }) + const result = await consoleClient.apps.publishToCreatorsPlatform({ + params: { appId: appDetail.id }, + }) window.open(result.redirect_url, '_blank') } catch (error: any) { diff --git a/web/app/components/apps/import-from-marketplace-template-modal.tsx b/web/app/components/apps/import-from-marketplace-template-modal.tsx index 727968f4e3..cf8b2fabd8 100644 --- a/web/app/components/apps/import-from-marketplace-template-modal.tsx +++ b/web/app/components/apps/import-from-marketplace-template-modal.tsx @@ -1,6 +1,7 @@ 'use client' import type { MarketplaceTemplate } from '@/service/marketplace-templates' +import { skipToken, useQuery } from '@tanstack/react-query' import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import AppIcon from '@/app/components/base/app-icon' @@ -8,10 +9,8 @@ import Button from '@/app/components/base/button' import { Dialog, DialogCloseButton, DialogContent, DialogTitle } from '@/app/components/base/ui/dialog' import { toast } from '@/app/components/base/ui/toast' import { MARKETPLACE_API_PREFIX, MARKETPLACE_URL_PREFIX } from '@/config' -import { - fetchMarketplaceTemplateDSL, - useMarketplaceTemplateDetail, -} from '@/service/marketplace-templates' +import { marketplaceQuery } from '@/service/client' +import { fetchMarketplaceTemplateDSL } from '@/service/marketplace-templates' type ImportFromMarketplaceTemplateModalProps = { templateId: string @@ -26,7 +25,11 @@ const ImportFromMarketplaceTemplateModal = ({ }: ImportFromMarketplaceTemplateModalProps) => { const { t } = useTranslation() - const { data, isLoading, isError } = useMarketplaceTemplateDetail(templateId) + const { data, isLoading, isError } = useQuery(marketplaceQuery.templateDetail.queryOptions({ + input: templateId + ? { params: { templateId } } + : skipToken, + })) const template = data?.data ?? null const [isImporting, setIsImporting] = useState(false) diff --git a/web/app/components/apps/list.tsx b/web/app/components/apps/list.tsx index 9295a3050f..e97af4f99e 100644 --- a/web/app/components/apps/list.tsx +++ b/web/app/components/apps/list.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' -import { useQuery } from '@tanstack/react-query' +import type { WorkflowOnlineUsersResponse } from '@/models/app' +import { skipToken, useQuery } from '@tanstack/react-query' import { useDebounceFn } from 'ahooks' import { parseAsStringLiteral, useQueryState } from 'nuqs' import { useCallback, useEffect, useMemo, useRef, useState } from 'react' @@ -18,7 +19,7 @@ import { useAppContext } from '@/context/app-context' import { useGlobalPublicStore } from '@/context/global-public-context' import { CheckModal } from '@/hooks/use-pay' import dynamic from '@/next/dynamic' -import { fetchWorkflowOnlineUsers } from '@/service/apps' +import { consoleQuery } from '@/service/client' import { useInfiniteAppList } from '@/service/use-apps' import { AppModeEnum, AppModes } from '@/types/app' import { cn } from '@/utils/classnames' @@ -131,11 +132,18 @@ const List: FC = ({ return Array.from(ids) }, [apps]) - const { data: onlineUsersByWorkflow = {}, refetch: refreshOnlineUsers } = useQuery({ - queryKey: ['apps', 'workflow-online-users', workflowIds], - queryFn: () => fetchWorkflowOnlineUsers({ workflowIds }), - enabled: workflowIds.length > 0, - }) + const { data: onlineUsersByWorkflow = {}, refetch: refreshOnlineUsers } = useQuery( + consoleQuery.apps.workflowOnlineUsers.queryOptions({ + input: workflowIds.length + ? { query: { workflow_ids: workflowIds.join(',') } } + : skipToken, + select: ({ data }) => data.reduce>((acc, item) => { + if (item.workflow_id) + acc[item.workflow_id] = item.users + return acc + }, {}), + }), + ) useEffect(() => { const timer = window.setInterval(() => { diff --git a/web/app/components/workflow/hooks/use-workflow-comment.ts b/web/app/components/workflow/hooks/use-workflow-comment.ts index 3398f0d7c9..1ffc12a946 100644 --- a/web/app/components/workflow/hooks/use-workflow-comment.ts +++ b/web/app/components/workflow/hooks/use-workflow-comment.ts @@ -136,10 +136,7 @@ export const useWorkflowComment = () => { mentioned_user_ids: mentionedUserIds, }) - const createdAt = Number(newComment.created_at) - const createdAtSeconds = Number.isNaN(createdAt) - ? Math.floor(Date.parse(newComment.created_at) / 1000) - : createdAt + const createdAtSeconds = newComment.created_at const createdByAccount = { id: userProfile?.id ?? '', name: userProfile?.name ?? '', diff --git a/web/app/components/workflow/nodes/tool/components/context-generate-modal/__tests__/index.spec.tsx b/web/app/components/workflow/nodes/tool/components/context-generate-modal/__tests__/index.spec.tsx index b8242e8eb4..ed62a0f963 100644 --- a/web/app/components/workflow/nodes/tool/components/context-generate-modal/__tests__/index.spec.tsx +++ b/web/app/components/workflow/nodes/tool/components/context-generate-modal/__tests__/index.spec.tsx @@ -1,5 +1,5 @@ import type { ContextGenerateModalHandle } from '../index' -import type { ContextGenerateResponse } from '@/service/debug' +import type { ContextGenerateResponse } from '@/contract/console/generator' import { act, fireEvent, render, screen } from '@testing-library/react' import * as React from 'react' import { CodeLanguage } from '@/app/components/workflow/nodes/code/types' diff --git a/web/app/components/workflow/nodes/tool/components/context-generate-modal/components/__tests__/right-panel.spec.tsx b/web/app/components/workflow/nodes/tool/components/context-generate-modal/components/__tests__/right-panel.spec.tsx index 053c09a898..6f0fd54455 100644 --- a/web/app/components/workflow/nodes/tool/components/context-generate-modal/components/__tests__/right-panel.spec.tsx +++ b/web/app/components/workflow/nodes/tool/components/context-generate-modal/components/__tests__/right-panel.spec.tsx @@ -1,4 +1,4 @@ -import type { ContextGenerateResponse } from '@/service/debug' +import type { ContextGenerateResponse } from '@/contract/console/generator' import { fireEvent, render, screen } from '@testing-library/react' import * as React from 'react' import { CodeLanguage } from '@/app/components/workflow/nodes/code/types' diff --git a/web/app/components/workflow/nodes/tool/components/context-generate-modal/components/right-panel.tsx b/web/app/components/workflow/nodes/tool/components/context-generate-modal/components/right-panel.tsx index 5d471254ea..001972ae3d 100644 --- a/web/app/components/workflow/nodes/tool/components/context-generate-modal/components/right-panel.tsx +++ b/web/app/components/workflow/nodes/tool/components/context-generate-modal/components/right-panel.tsx @@ -1,6 +1,6 @@ import type { PointerEvent, RefObject } from 'react' import type { VersionOption } from '../types' -import type { ContextGenerateResponse } from '@/service/debug' +import type { ContextGenerateResponse } from '@/contract/console/generator' import { RiArrowDownSLine, RiCheckLine, RiCloseLine, RiPlayLargeLine } from '@remixicon/react' import { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/workflow/nodes/tool/components/context-generate-modal/hooks/__tests__/use-context-gen-data.spec.ts b/web/app/components/workflow/nodes/tool/components/context-generate-modal/hooks/__tests__/use-context-gen-data.spec.ts index cd37581d06..6679404ea8 100644 --- a/web/app/components/workflow/nodes/tool/components/context-generate-modal/hooks/__tests__/use-context-gen-data.spec.ts +++ b/web/app/components/workflow/nodes/tool/components/context-generate-modal/hooks/__tests__/use-context-gen-data.spec.ts @@ -1,4 +1,4 @@ -import type { ContextGenerateResponse } from '@/service/debug' +import type { ContextGenerateResponse } from '@/contract/console/generator' import { act, renderHook, waitFor } from '@testing-library/react' import useContextGenData from '../use-context-gen-data' diff --git a/web/app/components/workflow/nodes/tool/components/context-generate-modal/hooks/use-context-gen-data.ts b/web/app/components/workflow/nodes/tool/components/context-generate-modal/hooks/use-context-gen-data.ts index 3daf10b55f..2d84f99b5d 100644 --- a/web/app/components/workflow/nodes/tool/components/context-generate-modal/hooks/use-context-gen-data.ts +++ b/web/app/components/workflow/nodes/tool/components/context-generate-modal/hooks/use-context-gen-data.ts @@ -1,4 +1,4 @@ -import type { ContextGenerateResponse } from '@/service/debug' +import type { ContextGenerateResponse } from '@/contract/console/generator' import { useSessionStorageState } from 'ahooks' import { useCallback } from 'react' import { CONTEXT_GEN_STORAGE_SUFFIX, getContextGenStorageKey } from '../utils/storage' diff --git a/web/app/components/workflow/nodes/tool/components/context-generate-modal/hooks/use-context-generate.ts b/web/app/components/workflow/nodes/tool/components/context-generate-modal/hooks/use-context-generate.ts index d70b7c302d..e37800519c 100644 --- a/web/app/components/workflow/nodes/tool/components/context-generate-modal/hooks/use-context-generate.ts +++ b/web/app/components/workflow/nodes/tool/components/context-generate-modal/hooks/use-context-generate.ts @@ -10,7 +10,7 @@ import type { ContextGenerateMessage, ContextGenerateParameterInfo, ContextGenerateResponse, -} from '@/service/debug' +} from '@/contract/console/generator' import type { CompletionParams, Model, ModelModeType } from '@/types/app' import { useBoolean, useSessionStorageState } from 'ahooks' import { useCallback, useMemo, useRef, useState } from 'react' @@ -24,7 +24,8 @@ import { useStore } from '@/app/components/workflow/store' import { STORAGE_KEYS } from '@/config/storage-keys' import { useGetLanguage } from '@/context/i18n' import { languages } from '@/i18n-config/language' -import { fetchContextGenerateSuggestedQuestions, generateContext } from '@/service/debug' +import { consoleClient } from '@/service/client' +import { fetchContextGenerateSuggestedQuestions } from '@/service/debug' import { AppModeEnum } from '@/types/app' import { storage } from '@/utils/storage' import { CONTEXT_GEN_STORAGE_SUFFIX, getContextGenStorageKey } from '../utils/storage' @@ -35,7 +36,7 @@ export type ContextGenerateChatMessage = ContextGenerateMessage & { durationMs?: number } -export const normalizeCodeLanguage = (value?: string) => { +export const normalizeCodeLanguage = (value?: string): CodeLanguage => { if (value === CodeLanguage.javascript) return CodeLanguage.javascript if (value === CodeLanguage.python3) @@ -43,6 +44,12 @@ export const normalizeCodeLanguage = (value?: string) => { return CodeLanguage.python3 } +export const normalizeContextGenerateLanguage = (value?: string): 'python3' | 'javascript' => { + if (value === CodeLanguage.javascript) + return CodeLanguage.javascript + return CodeLanguage.python3 +} + const createChatMessageId = () => { return `${Date.now()}-${Math.random().toString(36).slice(2, 8)}` } @@ -454,19 +461,21 @@ const useContextGenerate = ({ setGeneratingTrue() generateStartRef.current = Date.now() try { - const response = await generateContext({ - language: normalizeCodeLanguage(current?.code_language || codeNodeData?.code_language) as 'python3' | 'javascript', - prompt_messages: nextMessages.map(({ role, content, tool_call_id }) => ({ - role, - content, - tool_call_id, - })), - model_config: { - ...modelConfig, + const response = await consoleClient.generator.contextGenerate({ + body: { + language: normalizeContextGenerateLanguage(current?.code_language || codeNodeData?.code_language), + prompt_messages: nextMessages.map(({ role, content, tool_call_id }) => ({ + role, + content, + tool_call_id, + })), + model_config: { + ...modelConfig, + }, + available_vars: availableVarsPayload, + parameter_info: parameterInfo, + code_context: codeContext, }, - available_vars: availableVarsPayload, - parameter_info: parameterInfo, - code_context: codeContext, }) if (response.error) { diff --git a/web/app/components/workflow/nodes/tool/components/context-generate-modal/index.tsx b/web/app/components/workflow/nodes/tool/components/context-generate-modal/index.tsx index 72b52b5560..31929982c4 100644 --- a/web/app/components/workflow/nodes/tool/components/context-generate-modal/index.tsx +++ b/web/app/components/workflow/nodes/tool/components/context-generate-modal/index.tsx @@ -1,7 +1,7 @@ 'use client' import type { CodeNodeType, OutputVar } from '@/app/components/workflow/nodes/code/types' import type { Node, NodeOutPutVar } from '@/app/components/workflow/types' -import type { ContextGenerateResponse } from '@/service/debug' +import type { ContextGenerateResponse } from '@/contract/console/generator' import * as React from 'react' import { forwardRef, useCallback, useImperativeHandle, useMemo } from 'react' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/hooks/use-mixed-variable-extractor.ts b/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/hooks/use-mixed-variable-extractor.ts index c24dab7864..0c5c3ed247 100644 --- a/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/hooks/use-mixed-variable-extractor.ts +++ b/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/hooks/use-mixed-variable-extractor.ts @@ -15,7 +15,7 @@ import { NULL_STRATEGY } from '@/app/components/workflow/nodes/_base/constants' import { Type } from '@/app/components/workflow/nodes/llm/types' import { BlockEnum, EditionType, isPromptMessageContext, PromptRole, VarType } from '@/app/components/workflow/types' import { generateNewNode, getNodeCustomTypeByNodeDataType, mergeNodeDefaultData } from '@/app/components/workflow/utils' -import { fetchNestedNodeGraph } from '@/service/workflow' +import { consoleClient } from '@/service/client' import { FlowType } from '@/types/common' // Constants @@ -427,11 +427,14 @@ export function useMixedVariableExtractor({ return const parameterSchema = resolveNestedNodeParameterSchema(paramKey) try { - const response = await fetchNestedNodeGraph(configsMap.flowType, configsMap.flowId, { - parent_node_id: toolNodeId, - parameter_key: paramKey, - context_source: [payload.agentId, 'context'], - parameter_schema: parameterSchema, + const response = await consoleClient.workflowDraft.nestedNodeGraph({ + params: { appId: configsMap.flowId }, + body: { + parent_node_id: toolNodeId, + parameter_key: paramKey, + context_source: [payload.agentId, 'context'], + parameter_schema: parameterSchema, + }, }) const nestedNode = response?.graph?.nodes?.find(node => node.id === payload.extractorNodeId) const nestedNodeData = nestedNode?.data as Partial | undefined diff --git a/web/contract/console/app-asset.ts b/web/contract/console/app-asset.ts index 24779183ec..5d8cd5abee 100644 --- a/web/contract/console/app-asset.ts +++ b/web/contract/console/app-asset.ts @@ -2,7 +2,6 @@ import type { AppAssetDeleteResponse, AppAssetFileDownloadUrlResponse, AppAssetNode, - AppAssetPublishResponse, AppAssetTreeResponse, BatchUploadPayload, BatchUploadResponse, @@ -111,16 +110,6 @@ export const reorderNodeContract = base }>()) .output(type()) -export const publishContract = base - .route({ - path: '/apps/{appId}/assets/publish', - method: 'POST', - }) - .input(type<{ - params: { appId: string } - }>()) - .output(type()) - export const getFileUploadUrlContract = base .route({ path: '/apps/{appId}/assets/files/upload', diff --git a/web/contract/console/apps.ts b/web/contract/console/apps.ts index fc6740d8c2..b6ca97bd46 100644 --- a/web/contract/console/apps.ts +++ b/web/contract/console/apps.ts @@ -1,7 +1,28 @@ -import type { WorkflowOnlineUsersResponse } from '@/models/app' +import type { DSLImportResponse, WorkflowOnlineUsersResponse } from '@/models/app' import { type } from '@orpc/contract' import { base } from '../base' +export type AppExportBundleResponse = { + download_url: string + filename: string +} + +export type AppRuntimeUpgradeResponse = { + result: 'success' | 'no_draft' | 'already_sandboxed' + new_app_id?: string + converted_agents?: number + skipped_agents?: number +} + +export type PublishToCreatorsPlatformResponse = { + redirect_url: string +} + +export type ImportBundlePrepareResponse = { + import_id: string + upload_url: string +} + export const workflowOnlineUsersContract = base .route({ path: '/apps/workflows/online-users', @@ -25,3 +46,69 @@ export const appDeleteContract = base } }>()) .output(type()) + +export const appExportBundleContract = base + .route({ + path: '/apps/{appId}/export-bundle', + method: 'GET', + }) + .input(type<{ + params: { + appId: string + } + query?: { + include_secret?: boolean + workflow_id?: string + } + }>()) + .output(type()) + +export const publishToCreatorsPlatformContract = base + .route({ + path: '/apps/{appId}/publish-to-creators-platform', + method: 'POST', + }) + .input(type<{ + params: { + appId: string + } + }>()) + .output(type()) + +export const upgradeAppRuntimeContract = base + .route({ + path: '/apps/{appId}/upgrade-runtime', + method: 'POST', + }) + .input(type<{ + params: { + appId: string + } + }>()) + .output(type()) + +export const prepareImportBundleContract = base + .route({ + path: '/apps/imports-bundle/prepare', + method: 'POST', + }) + .output(type()) + +export const confirmImportBundleContract = base + .route({ + path: '/apps/imports-bundle/{importId}/confirm', + method: 'POST', + }) + .input(type<{ + params: { + importId: string + } + body?: { + name?: string + description?: string + icon_type?: string + icon?: string + icon_background?: string + } + }>()) + .output(type()) diff --git a/web/contract/console/generator.ts b/web/contract/console/generator.ts new file mode 100644 index 0000000000..973d1a80f7 --- /dev/null +++ b/web/contract/console/generator.ts @@ -0,0 +1,103 @@ +import type { StructuredOutput } from '@/app/components/workflow/nodes/llm/types' +import type { BlockEnum, ValueSelector, VarType } from '@/app/components/workflow/types' +import type { CompletionParams } from '@/types/app' +import { type } from '@orpc/contract' +import { base } from '../base' + +export type ContextGenerateMessage = { + role: 'user' | 'assistant' | 'system' | 'tool' + content: string + tool_call_id?: string +} + +export type ContextGenerateAvailableVar = { + value_selector: ValueSelector + type: VarType + description?: string + node_id?: string + node_title?: string + node_type?: BlockEnum + schema?: StructuredOutput['schema'] | Record | null +} + +export type ContextGenerateParameterInfo = { + name: string + type?: string + description?: string + required?: boolean + options?: string[] + min?: number + max?: number + default?: string | number | boolean | null + multiple?: boolean + label?: string +} + +export type ContextGenerateVariable = { + variable: string + value_selector: string[] +} + +export type ContextGenerateCodeContext = { + code: string + outputs?: Record + variables?: ContextGenerateVariable[] +} + +export type ContextGenerateRequest = { + language?: 'python3' | 'javascript' + prompt_messages: ContextGenerateMessage[] + model_config: { + provider: string + name: string + completion_params?: CompletionParams + } + available_vars: ContextGenerateAvailableVar[] + parameter_info: ContextGenerateParameterInfo + code_context?: ContextGenerateCodeContext | null +} + +export type ContextGenerateResponse = { + variables: ContextGenerateVariable[] + code_language: string + code: string + outputs: Record + message: string + error: string +} + +export type ContextGenerateSuggestedQuestionsRequest = { + language: string + model_config?: { + provider: string + name: string + completion_params?: CompletionParams + } + available_vars: ContextGenerateAvailableVar[] + parameter_info: ContextGenerateParameterInfo +} + +export type ContextGenerateSuggestedQuestionsResponse = { + questions: string[] + error: string +} + +export const contextGenerateContract = base + .route({ + path: '/context-generate', + method: 'POST', + }) + .input(type<{ + body: ContextGenerateRequest + }>()) + .output(type()) + +export const contextGenerateSuggestedQuestionsContract = base + .route({ + path: '/context-generate/suggested-questions', + method: 'POST', + }) + .input(type<{ + body: ContextGenerateSuggestedQuestionsRequest + }>()) + .output(type()) diff --git a/web/contract/console/workflow-comment.ts b/web/contract/console/workflow-comment.ts index cac1307b0a..44a5e372ab 100644 --- a/web/contract/console/workflow-comment.ts +++ b/web/contract/console/workflow-comment.ts @@ -1,4 +1,3 @@ -import type { CommonResponse } from '@/models/common' import { type } from '@orpc/contract' import { base } from '../base' @@ -6,7 +5,13 @@ export type UserProfile = { id: string name: string email: string + avatar?: string | null avatar_url?: string + last_login_at?: number | null + last_active_at?: number | null + created_at?: number | null + role?: string + status?: string } export type WorkflowCommentList = { @@ -60,12 +65,12 @@ export type WorkflowCommentDetail = { export type WorkflowCommentCreateRes = { id: string - created_at: string + created_at: number } export type WorkflowCommentUpdateRes = { id: string - updated_at: string + updated_at: number } export type WorkflowCommentResolveRes = { @@ -75,20 +80,14 @@ export type WorkflowCommentResolveRes = { resolved_at: number } -export type WorkflowCommentReply = { +export type WorkflowCommentReplyCreateRes = { id: string - comment_id: string - content: string - created_by: string - created_at: string - updated_at: string - mentioned_user_ids: string[] - author: { - id: string - name: string - email: string - avatar?: string - } + created_at: number +} + +export type WorkflowCommentReplyUpdateRes = { + id: string + updated_at: number } export type CreateCommentParams = { @@ -173,7 +172,7 @@ export const workflowCommentDeleteContract = base commentId: string } }>()) - .output(type()) + .output(type()) export const workflowCommentResolveContract = base .route({ @@ -200,7 +199,7 @@ export const workflowCommentReplyCreateContract = base } body: CreateReplyParams }>()) - .output(type()) + .output(type()) export const workflowCommentReplyUpdateContract = base .route({ @@ -215,7 +214,7 @@ export const workflowCommentReplyUpdateContract = base } body: CreateReplyParams }>()) - .output(type()) + .output(type()) export const workflowCommentReplyDeleteContract = base .route({ @@ -229,7 +228,7 @@ export const workflowCommentReplyDeleteContract = base replyId: string } }>()) - .output(type()) + .output(type()) export const workflowCommentMentionUsersContract = base .route({ diff --git a/web/contract/console/workflow.ts b/web/contract/console/workflow.ts index 8a0543e2ff..150656f4e7 100644 --- a/web/contract/console/workflow.ts +++ b/web/contract/console/workflow.ts @@ -9,6 +9,7 @@ import type { } from '@/app/components/base/features/types' import type { ConversationVariable, EnvironmentVariable } from '@/app/components/workflow/types' import type { CommonResponse } from '@/models/common' +import type { NestedNodeGraphPayload, NestedNodeGraphResponse } from '@/types/workflow' import { type } from '@orpc/contract' import { base } from '../base' @@ -97,5 +98,19 @@ export const workflowDraftNodeSkillsContract = base type: string provider: string tool_name: string + enabled: boolean }[] }>()) + +export const workflowDraftNestedNodeGraphContract = base + .route({ + path: '/apps/{appId}/workflows/draft/nested-node-graph', + method: 'POST', + }) + .input(type<{ + params: { + appId: string + } + body: NestedNodeGraphPayload + }>()) + .output(type()) diff --git a/web/contract/router.ts b/web/contract/router.ts index f7b46f01c1..488d687ce1 100644 --- a/web/contract/router.ts +++ b/web/contract/router.ts @@ -8,13 +8,20 @@ import { getFileDownloadUrlContract, getFileUploadUrlContract, moveNodeContract, - publishContract, renameNodeContract, reorderNodeContract, treeContract, updateFileContentContract, } from './console/app-asset' -import { appDeleteContract, workflowOnlineUsersContract } from './console/apps' +import { + appDeleteContract, + appExportBundleContract, + confirmImportBundleContract, + prepareImportBundleContract, + publishToCreatorsPlatformContract, + upgradeAppRuntimeContract, + workflowOnlineUsersContract, +} from './console/apps' import { bindPartnerStackContract, invoicesContract } from './console/billing' import { exploreAppDetailContract, @@ -27,6 +34,10 @@ import { exploreInstalledAppsContract, exploreInstalledAppUninstallContract, } from './console/explore' +import { + contextGenerateContract, + contextGenerateSuggestedQuestionsContract, +} from './console/generator' import { changePreferredProviderTypeContract, modelProvidersModelsContract } from './console/model-providers' import { notificationContract, notificationDismissContract } from './console/notification' import { pluginCheckInstalledContract, pluginLatestVersionsContract } from './console/plugins' @@ -61,6 +72,7 @@ import { import { trialAppDatasetsContract, trialAppInfoContract, trialAppParametersContract, trialAppWorkflowsContract } from './console/try-app' import { workflowDraftEnvironmentVariablesContract, + workflowDraftNestedNodeGraphContract, workflowDraftNodeSkillsContract, workflowDraftUpdateConversationVariablesContract, workflowDraftUpdateEnvironmentVariablesContract, @@ -83,9 +95,20 @@ export const consoleRouterContract = { avatar: accountAvatarContract, }, systemFeatures: systemFeaturesContract, + generator: { + contextGenerate: contextGenerateContract, + contextGenerateSuggestedQuestions: contextGenerateSuggestedQuestionsContract, + }, apps: { deleteApp: appDeleteContract, workflowOnlineUsers: workflowOnlineUsersContract, + exportBundle: appExportBundleContract, + publishToCreatorsPlatform: publishToCreatorsPlatformContract, + upgradeRuntime: upgradeAppRuntimeContract, + importsBundle: { + prepare: prepareImportBundleContract, + confirm: confirmImportBundleContract, + }, }, explore: { apps: exploreAppsContract, @@ -138,13 +161,13 @@ export const consoleRouterContract = { renameNode: renameNodeContract, moveNode: moveNodeContract, reorderNode: reorderNodeContract, - publish: publishContract, getFileUploadUrl: getFileUploadUrlContract, batchUpload: batchUploadContract, }, workflowDraft: { environmentVariables: workflowDraftEnvironmentVariablesContract, nodeSkills: workflowDraftNodeSkillsContract, + nestedNodeGraph: workflowDraftNestedNodeGraphContract, updateEnvironmentVariables: workflowDraftUpdateEnvironmentVariablesContract, updateConversationVariables: workflowDraftUpdateConversationVariablesContract, updateFeatures: workflowDraftUpdateFeaturesContract, diff --git a/web/models/app.ts b/web/models/app.ts index 706c32ed94..01558c85c4 100644 --- a/web/models/app.ts +++ b/web/models/app.ts @@ -44,7 +44,7 @@ export type DSLImportResponse = { current_dsl_version?: string imported_dsl_version?: string error: string - leaked_dependencies: Dependency[] + leaked_dependencies?: Dependency[] } export type AppTemplatesResponse = { @@ -122,7 +122,7 @@ export type WorkflowOnlineUser = { } export type WorkflowOnlineUsersResponse = { - data: Record | Array<{ + data: Array<{ workflow_id: string users: WorkflowOnlineUser[] }> diff --git a/web/service/apps.ts b/web/service/apps.ts index a848ee6b1c..aa969b84fe 100644 --- a/web/service/apps.ts +++ b/web/service/apps.ts @@ -1,5 +1,6 @@ import type { TracingProvider } from '@/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/type' -import type { ApiKeysListResponse, AppDailyConversationsResponse, AppDailyEndUsersResponse, AppDailyMessagesResponse, AppDetailResponse, AppListResponse, AppStatisticsResponse, AppTemplatesResponse, AppTokenCostsResponse, AppVoicesListResponse, CreateApiKeyResponse, DSLImportMode, DSLImportResponse, GenerationIntroductionResponse, TracingConfig, TracingStatus, UpdateAppModelConfigResponse, UpdateAppSiteCodeResponse, UpdateOpenAIKeyResponse, ValidateOpenAIKeyResponse, WebhookTriggerResponse, WorkflowDailyConversationsResponse, WorkflowOnlineUser } from '@/models/app' +import type { AppExportBundleResponse, AppRuntimeUpgradeResponse } from '@/contract/console/apps' +import type { ApiKeysListResponse, AppDailyConversationsResponse, AppDailyEndUsersResponse, AppDailyMessagesResponse, AppDetailResponse, AppListResponse, AppStatisticsResponse, AppTemplatesResponse, AppTokenCostsResponse, AppVoicesListResponse, CreateApiKeyResponse, DSLImportMode, DSLImportResponse, GenerationIntroductionResponse, TracingConfig, TracingStatus, UpdateAppModelConfigResponse, UpdateAppSiteCodeResponse, UpdateOpenAIKeyResponse, ValidateOpenAIKeyResponse, WebhookTriggerResponse, WorkflowDailyConversationsResponse } from '@/models/app' import type { CommonResponse } from '@/models/common' import type { AppIconType, AppModeEnum, ModelConfig } from '@/types/app' import { del, get, patch, post, put } from './base' @@ -9,33 +10,6 @@ export const fetchAppList = ({ url, params }: { url: string, params?: Record(url, { params }) } -export const fetchWorkflowOnlineUsers = async ({ workflowIds }: { workflowIds: string[] }): Promise> => { - if (!workflowIds.length) - return {} - - const params = { workflow_ids: workflowIds.join(',') } - const response = await consoleClient.apps.workflowOnlineUsers({ - query: params, - }) - - if (!response || !response.data) - return {} - - if (Array.isArray(response.data)) { - return response.data.reduce>((acc, item) => { - if (item?.workflow_id) - acc[item.workflow_id] = item.users || [] - return acc - }, {}) - } - - return Object.entries(response.data).reduce>((acc, [workflowId, users]) => { - if (workflowId) - acc[workflowId] = users || [] - return acc - }, {}) -} - export const fetchAppDetail = ({ url, id }: { url: string, id: string }): Promise => { return get(`${url}/${id}`) } @@ -111,8 +85,10 @@ export const copyApp = ({ return post(`apps/${appID}/copy`, { body: { name, icon_type, icon, icon_background, mode, description } }) } -export const upgradeAppRuntime = (appID: string): Promise<{ result: string, new_app_id?: string, converted_agents?: number, skipped_agents?: number }> => { - return post(`apps/${appID}/upgrade-runtime`) +export const upgradeAppRuntime = (appID: string): Promise => { + return consoleClient.apps.upgradeRuntime({ + params: { appId: appID }, + }) } export const exportAppConfig = ({ appID, include = false, workflowID }: { appID: string, include?: boolean, workflowID?: string }): Promise<{ data: string }> => { @@ -125,28 +101,14 @@ export const exportAppConfig = ({ appID, include = false, workflowID }: { appID: } export const exportAppBundle = async ({ appID, include = false, workflowID }: { appID: string, include?: boolean, workflowID?: string }): Promise => { - const { API_PREFIX, CSRF_COOKIE_NAME, CSRF_HEADER_NAME } = await import('@/config') - const Cookies = (await import('js-cookie')).default - const params = new URLSearchParams({ - include_secret: include.toString(), - }) - if (workflowID) - params.append('workflow_id', workflowID) - - const url = `${API_PREFIX}/apps/${appID}/export-bundle?${params.toString()}` - const response = await fetch(url, { - method: 'GET', - credentials: 'include', - headers: { - [CSRF_HEADER_NAME]: Cookies.get(CSRF_COOKIE_NAME()) || '', + const result: AppExportBundleResponse = await consoleClient.apps.exportBundle({ + params: { appId: appID }, + query: { + include_secret: include, + ...(workflowID ? { workflow_id: workflowID } : {}), }, }) - if (!response.ok) - throw new Error('Export bundle failed') - - const result: { download_url: string, filename: string } = await response.json() - const a = document.createElement('a') a.href = result.download_url a.download = result.filename @@ -161,49 +123,6 @@ export const importDSLConfirm = ({ import_id }: { import_id: string }): Promise< return post(`apps/imports/${import_id}/confirm`, { body: {} }) } -export type PublishToCreatorsPlatformResponse = { - redirect_url: string -} - -export const publishToCreatorsPlatform = ({ appID }: { appID: string }): Promise => { - return post(`apps/${appID}/publish-to-creators-platform`, { body: {} }) -} - -export type ImportBundlePrepareResponse = { - import_id: string - upload_url: string -} - -export const prepareImportBundle = (): Promise => { - return post('apps/imports-bundle/prepare', { body: {} }) -} - -export const confirmImportBundle = ({ - import_id, - name, - description, - icon_type, - icon, - icon_background, -}: { - import_id: string - name?: string - description?: string - icon_type?: string - icon?: string - icon_background?: string -}): Promise => { - return post(`apps/imports-bundle/${import_id}/confirm`, { - body: { - name, - description, - icon_type, - icon, - icon_background, - }, - }) -} - export const importAppBundle = async ({ file, name, @@ -219,10 +138,8 @@ export const importAppBundle = async ({ icon?: string icon_background?: string }): Promise => { - // Step 1: Prepare import and get upload URL - const { import_id, upload_url } = await prepareImportBundle() + const { import_id, upload_url } = await consoleClient.apps.importsBundle.prepare() - // Step 2: Upload file to presigned URL const uploadResponse = await fetch(upload_url, { method: 'PUT', body: file, @@ -231,14 +148,15 @@ export const importAppBundle = async ({ if (!uploadResponse.ok) throw new Error('Failed to upload bundle file') - // Step 3: Confirm import - return confirmImportBundle({ - import_id, - name, - description, - icon_type, - icon, - icon_background, + return consoleClient.apps.importsBundle.confirm({ + params: { importId: import_id }, + body: { + name, + description, + icon_type, + icon, + icon_background, + }, }) } diff --git a/web/service/debug.ts b/web/service/debug.ts index dbc6042e35..2d798556fc 100644 --- a/web/service/debug.ts +++ b/web/service/debug.ts @@ -1,10 +1,9 @@ import type { IOnCompleted, IOnData, IOnError, IOnFile, IOnMessageEnd, IOnMessageReplace, IOnThought } from './base' import type { FileEntity } from '@/app/components/base/file-uploader/types' import type { ModelParameterRule } from '@/app/components/header/account-setting/model-provider-page/declarations' -import type { StructuredOutput } from '@/app/components/workflow/nodes/llm/types' -import type { BlockEnum, ValueSelector, VarType } from '@/app/components/workflow/types' +import type { ContextGenerateSuggestedQuestionsRequest, ContextGenerateSuggestedQuestionsResponse } from '@/contract/console/generator' import type { ChatPromptConfig, CompletionPromptConfig } from '@/models/debug' -import type { AppModeEnum, CompletionParams, ModelModeType } from '@/types/app' +import type { AppModeEnum, ModelModeType } from '@/types/app' import { get, post, ssePost } from './base' export type BasicAppFirstRes = { @@ -28,85 +27,6 @@ export type CodeGenRes = { error?: string } -export type ContextGenerateMessage = { - role: 'user' | 'assistant' | 'system' | 'tool' - content: string - tool_call_id?: string -} - -// FIXME -export type ContextGenerateAvailableVar = { - value_selector: ValueSelector - type: VarType - description?: string - node_id?: string - node_title?: string - node_type?: BlockEnum - schema?: StructuredOutput['schema'] | Record | null -} - -export type ContextGenerateParameterInfo = { - name: string - type?: string - description?: string - required?: boolean - options?: string[] - min?: number - max?: number - default?: string | number | boolean | null - multiple?: boolean - label?: string -} - -export type ContextGenerateCodeContext = { - code: string - outputs?: Record - variables?: ContextGenerateVariable[] -} - -export type ContextGenerateRequest = { - language?: 'python3' | 'javascript' - prompt_messages: ContextGenerateMessage[] - model_config: { - provider: string - name: string - completion_params?: CompletionParams - } - available_vars: ContextGenerateAvailableVar[] - parameter_info: ContextGenerateParameterInfo - code_context?: ContextGenerateCodeContext | null -} - -export type ContextGenerateVariable = { - variable: string - value_selector: string[] -} - -export type ContextGenerateResponse = { - variables: ContextGenerateVariable[] - code_language: string - code: string - outputs: Record - message: string - error: string -} - -export type ContextGenerateSuggestedQuestionsRequest = { - language: string - model_config?: { - provider: string - name: string - completion_params?: CompletionParams - } - available_vars: ContextGenerateAvailableVar[] - parameter_info: ContextGenerateParameterInfo -} - -export type ContextGenerateSuggestedQuestionsResponse = { - questions: string[] - error: string -} - export type TextGenerationMessageFile = FileEntity & { belongs_to?: 'assistant' | 'user' | string } @@ -192,12 +112,6 @@ export const generateRule = (body: Record) => { }) } -export const generateContext = (body: ContextGenerateRequest) => { - return post('/context-generate', { - body, - }) -} - export const fetchContextGenerateSuggestedQuestions = ( body: ContextGenerateSuggestedQuestionsRequest, getAbortController?: (abortController: AbortController) => void, diff --git a/web/service/marketplace-templates.ts b/web/service/marketplace-templates.ts index f429255400..f0f462a933 100644 --- a/web/service/marketplace-templates.ts +++ b/web/service/marketplace-templates.ts @@ -1,6 +1,4 @@ -import { useQuery } from '@tanstack/react-query' import { MARKETPLACE_API_PREFIX } from '@/config' -import { marketplaceClient, marketplaceQuery } from '@/service/client' export type MarketplaceTemplate = { id: string @@ -24,16 +22,6 @@ export type MarketplaceTemplate = { updated_at: string } -export const useMarketplaceTemplateDetail = (templateId: string) => { - return useQuery({ - queryKey: marketplaceQuery.templateDetail.queryKey({ - input: { params: { templateId } }, - }), - queryFn: () => marketplaceClient.templateDetail({ params: { templateId } }), - enabled: !!templateId, - }) -} - export const fetchMarketplaceTemplateDSL = async ( templateId: string, ): Promise => { diff --git a/web/service/use-app-asset.ts b/web/service/use-app-asset.ts index 15c5d49b00..9acddb907e 100644 --- a/web/service/use-app-asset.ts +++ b/web/service/use-app-asset.ts @@ -221,23 +221,6 @@ export const useReorderAppAssetNode = () => { }) } -export const usePublishAppAssets = () => { - const queryClient = useQueryClient() - return useMutation({ - mutationKey: consoleQuery.appAsset.publish.mutationKey(), - mutationFn: (appId: string) => { - return consoleClient.appAsset.publish({ - params: { appId }, - }) - }, - onSuccess: (_, appId) => { - queryClient.invalidateQueries({ - queryKey: consoleQuery.appAsset.tree.key({ type: 'query', input: { params: { appId } } }), - }) - }, - }) -} - export const useUploadFileWithPresignedUrl = () => { const queryClient = useQueryClient() return useMutation({ @@ -299,9 +282,20 @@ export const useBatchUpload = () => { }): Promise => { const response = await consoleClient.appAsset.batchUpload({ params: { appId }, - body: { children: tree, parent_id: parentId }, + body: { children: tree }, }) + if (parentId) { + await Promise.all( + response.children.map(node => + consoleClient.appAsset.moveNode({ + params: { appId, nodeId: node.id }, + body: { parent_id: parentId }, + }), + ), + ) + } + const uploadTasks: Array<{ path: string, file: File, url: string }> = [] const extractUploads = (nodes: BatchUploadNodeOutput[], pathPrefix: string = '') => { diff --git a/web/service/workflow-comment.ts b/web/service/workflow-comment.ts index 28e7729f3b..972bf01896 100644 --- a/web/service/workflow-comment.ts +++ b/web/service/workflow-comment.ts @@ -5,11 +5,11 @@ import type { WorkflowCommentCreateRes, WorkflowCommentDetail, WorkflowCommentList, - WorkflowCommentReply, + WorkflowCommentReplyCreateRes, + WorkflowCommentReplyUpdateRes, WorkflowCommentResolveRes, WorkflowCommentUpdateRes, } from '@/contract/console/workflow-comment' -import type { CommonResponse } from '@/models/common' import { consoleClient } from './client' export type { @@ -22,7 +22,8 @@ export type { WorkflowCommentDetailMention, WorkflowCommentDetailReply, WorkflowCommentList, - WorkflowCommentReply, + WorkflowCommentReplyCreateRes, + WorkflowCommentReplyUpdateRes, WorkflowCommentResolveRes, WorkflowCommentUpdateRes, } from '@/contract/console/workflow-comment' @@ -54,7 +55,7 @@ export const updateWorkflowComment = async (appId: string, commentId: string, pa }) } -export const deleteWorkflowComment = async (appId: string, commentId: string): Promise => { +export const deleteWorkflowComment = async (appId: string, commentId: string): Promise => { return consoleClient.workflowComments.delete({ params: { appId, commentId }, }) @@ -66,14 +67,14 @@ export const resolveWorkflowComment = async (appId: string, commentId: string): }) } -export const createWorkflowCommentReply = async (appId: string, commentId: string, params: CreateReplyParams): Promise => { +export const createWorkflowCommentReply = async (appId: string, commentId: string, params: CreateReplyParams): Promise => { return consoleClient.workflowComments.replies.create({ params: { appId, commentId }, body: params, }) } -export const updateWorkflowCommentReply = async (appId: string, commentId: string, replyId: string, params: CreateReplyParams): Promise => { +export const updateWorkflowCommentReply = async (appId: string, commentId: string, replyId: string, params: CreateReplyParams): Promise => { return consoleClient.workflowComments.replies.update({ params: { appId, @@ -84,7 +85,7 @@ export const updateWorkflowCommentReply = async (appId: string, commentId: strin }) } -export const deleteWorkflowCommentReply = async (appId: string, commentId: string, replyId: string): Promise => { +export const deleteWorkflowCommentReply = async (appId: string, commentId: string, replyId: string): Promise => { return consoleClient.workflowComments.replies.delete({ params: { appId, diff --git a/web/service/workflow.ts b/web/service/workflow.ts index 256ded5c8a..74ee134347 100644 --- a/web/service/workflow.ts +++ b/web/service/workflow.ts @@ -6,8 +6,6 @@ import type { ConversationVariableResponse, FetchWorkflowDraftResponse, HumanInputFormData, - NestedNodeGraphPayload, - NestedNodeGraphResponse, NodesDefaultConfigsResponse, VarInInspect, } from '@/types/workflow' @@ -37,10 +35,6 @@ export const fetchNodesDefaultConfigs = (url: string) => { return get(url) } -export const fetchNestedNodeGraph = (flowType: FlowType, flowId: string, payload: NestedNodeGraphPayload) => { - return post(`${getFlowPrefix(flowType)}/${flowId}/workflows/draft/nested-node-graph`, { body: payload }, { silent: true }) -} - export const singleNodeRun = (flowType: FlowType, flowId: string, nodeId: string, params: object) => { return post(`${getFlowPrefix(flowType)}/${flowId}/workflows/draft/nodes/${nodeId}/run`, { body: params }) } diff --git a/web/types/app-asset.ts b/web/types/app-asset.ts index 28754a6c03..01def5c400 100644 --- a/web/types/app-asset.ts +++ b/web/types/app-asset.ts @@ -178,7 +178,6 @@ export type BatchUploadNodeOutput = { * Request payload for batch upload */ export type BatchUploadPayload = { - parent_id?: string | null children: BatchUploadNodeInput[] }