From c5cef80ea4bdc1090d1f1ad59aec87a3479a3967 Mon Sep 17 00:00:00 2001 From: Stephen Zhou Date: Thu, 9 Jul 2026 11:52:42 +0800 Subject: [PATCH] chore: generate fastopenapi console contracts (#38580) --- .../generated/api/console/init/orpc.gen.ts | 45 ++++++++++++++ .../generated/api/console/init/types.gen.ts | 43 +++++++++++++ .../generated/api/console/init/zod.gen.ts | 36 +++++++++++ .../generated/api/console/orpc.gen.ts | 4 ++ .../generated/api/console/ping/orpc.gen.ts | 27 +++++++++ .../generated/api/console/ping/types.gen.ts | 22 +++++++ .../generated/api/console/ping/zod.gen.ts | 15 +++++ .../generated/api/console/router.gen.ts | 8 +++ .../generated/api/console/setup/orpc.gen.ts | 60 +++++++++++++++++++ .../generated/api/console/setup/types.gen.ts | 47 +++++++++++++++ .../generated/api/console/setup/zod.gen.ts | 40 +++++++++++++ .../generated/api/console/version/orpc.gen.ts | 29 +++++++++ .../api/console/version/types.gen.ts | 33 ++++++++++ .../generated/api/console/version/zod.gen.ts | 31 ++++++++++ packages/contracts/openapi-ts.api.config.ts | 35 ++++++++++- packages/contracts/package.json | 4 +- web/__tests__/utils/mock-app-context-state.ts | 13 ++-- .../__tests__/index.spec.tsx | 9 ++- .../account-about/__tests__/index.spec.tsx | 8 ++- .../components/header/account-about/index.tsx | 4 +- .../__tests__/use-reference-setting.spec.ts | 10 +++- .../__tests__/console-bootstrap.spec.tsx | 28 +++++++++ web/context/app-context-defaults.ts | 9 ++- web/context/app-context-normalizers.ts | 8 ++- web/context/app-context-state.ts | 10 +++- web/context/app-context-types.ts | 7 +++ web/models/common.ts | 10 ---- web/service/lang-genius-version.ts | 13 ---- 28 files changed, 561 insertions(+), 47 deletions(-) create mode 100644 packages/contracts/generated/api/console/init/orpc.gen.ts create mode 100644 packages/contracts/generated/api/console/init/types.gen.ts create mode 100644 packages/contracts/generated/api/console/init/zod.gen.ts create mode 100644 packages/contracts/generated/api/console/ping/orpc.gen.ts create mode 100644 packages/contracts/generated/api/console/ping/types.gen.ts create mode 100644 packages/contracts/generated/api/console/ping/zod.gen.ts create mode 100644 packages/contracts/generated/api/console/setup/orpc.gen.ts create mode 100644 packages/contracts/generated/api/console/setup/types.gen.ts create mode 100644 packages/contracts/generated/api/console/setup/zod.gen.ts create mode 100644 packages/contracts/generated/api/console/version/orpc.gen.ts create mode 100644 packages/contracts/generated/api/console/version/types.gen.ts create mode 100644 packages/contracts/generated/api/console/version/zod.gen.ts create mode 100644 web/context/app-context-types.ts delete mode 100644 web/service/lang-genius-version.ts diff --git a/packages/contracts/generated/api/console/init/orpc.gen.ts b/packages/contracts/generated/api/console/init/orpc.gen.ts new file mode 100644 index 00000000000..1b92ca143ef --- /dev/null +++ b/packages/contracts/generated/api/console/init/orpc.gen.ts @@ -0,0 +1,45 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { oc } from '@orpc/contract' +import * as z from 'zod' + +import { zGetInitResponse, zPostInitBody, zPostInitResponse } from './zod.gen' + +/** + * Get initialization validation status. + */ +export const get = oc + .route({ + inputStructure: 'detailed', + method: 'GET', + operationId: 'getInit', + path: '/init', + summary: 'Get initialization validation status.', + tags: ['console'], + }) + .output(zGetInitResponse) + +/** + * Validate initialization password. + */ +export const post = oc + .route({ + inputStructure: 'detailed', + method: 'POST', + operationId: 'postInit', + path: '/init', + successStatus: 201, + summary: 'Validate initialization password.', + tags: ['console'], + }) + .input(z.object({ body: zPostInitBody })) + .output(zPostInitResponse) + +export const init = { + get, + post, +} + +export const contract = { + init, +} diff --git a/packages/contracts/generated/api/console/init/types.gen.ts b/packages/contracts/generated/api/console/init/types.gen.ts new file mode 100644 index 00000000000..51f443f39b1 --- /dev/null +++ b/packages/contracts/generated/api/console/init/types.gen.ts @@ -0,0 +1,43 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type ClientOptions = { + baseUrl: `${string}://${string}/console/api` | (string & {}) +} + +export type InitStatusResponse = { + status: 'finished' | 'not_started' +} + +export type InitValidatePayload = { + password: string +} + +export type InitValidateResponse = { + result: string +} + +export type GetInitData = { + body?: never + path?: never + query?: never + url: '/init' +} + +export type GetInitResponses = { + 200: InitStatusResponse +} + +export type GetInitResponse = GetInitResponses[keyof GetInitResponses] + +export type PostInitData = { + body: InitValidatePayload + path?: never + query?: never + url: '/init' +} + +export type PostInitResponses = { + 201: InitValidateResponse +} + +export type PostInitResponse = PostInitResponses[keyof PostInitResponses] diff --git a/packages/contracts/generated/api/console/init/zod.gen.ts b/packages/contracts/generated/api/console/init/zod.gen.ts new file mode 100644 index 00000000000..32e3cdc8865 --- /dev/null +++ b/packages/contracts/generated/api/console/init/zod.gen.ts @@ -0,0 +1,36 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import * as z from 'zod' + +/** + * InitStatusResponse + */ +export const zInitStatusResponse = z.object({ + status: z.enum(['finished', 'not_started']), +}) + +/** + * InitValidatePayload + */ +export const zInitValidatePayload = z.object({ + password: z.string().max(30), +}) + +/** + * InitValidateResponse + */ +export const zInitValidateResponse = z.object({ + result: z.string(), +}) + +/** + * OK + */ +export const zGetInitResponse = zInitStatusResponse + +export const zPostInitBody = zInitValidatePayload + +/** + * Created + */ +export const zPostInitResponse = zInitValidateResponse diff --git a/packages/contracts/generated/api/console/orpc.gen.ts b/packages/contracts/generated/api/console/orpc.gen.ts index 035163b9fb7..1102f11e301 100644 --- a/packages/contracts/generated/api/console/orpc.gen.ts +++ b/packages/contracts/generated/api/console/orpc.gen.ts @@ -35,6 +35,7 @@ export const contractLoaders = { import('./forgot-password/orpc.gen').then(({ forgotPassword }) => ({ forgotPassword })), form: () => import('./form/orpc.gen').then(({ form }) => ({ form })), info: () => import('./info/orpc.gen').then(({ info }) => ({ info })), + init: () => import('./init/orpc.gen').then(({ init }) => ({ init })), installedApps: () => import('./installed-apps/orpc.gen').then(({ installedApps }) => ({ installedApps })), instructionGenerate: () => @@ -47,6 +48,7 @@ export const contractLoaders = { import('./notification/orpc.gen').then(({ notification }) => ({ notification })), notion: () => import('./notion/orpc.gen').then(({ notion }) => ({ notion })), oauth: () => import('./oauth/orpc.gen').then(({ oauth }) => ({ oauth })), + ping: () => import('./ping/orpc.gen').then(({ ping }) => ({ ping })), rag: () => import('./rag/orpc.gen').then(({ rag }) => ({ rag })), refreshToken: () => import('./refresh-token/orpc.gen').then(({ refreshToken }) => ({ refreshToken })), @@ -61,6 +63,7 @@ export const contractLoaders = { import('./rule-structured-output-generate/orpc.gen').then( ({ ruleStructuredOutputGenerate }) => ({ ruleStructuredOutputGenerate }), ), + setup: () => import('./setup/orpc.gen').then(({ setup }) => ({ setup })), snippets: () => import('./snippets/orpc.gen').then(({ snippets }) => ({ snippets })), spec: () => import('./spec/orpc.gen').then(({ spec }) => ({ spec })), systemFeatures: () => @@ -70,6 +73,7 @@ export const contractLoaders = { test: () => import('./test/orpc.gen').then(({ test }) => ({ test })), trialApps: () => import('./trial-apps/orpc.gen').then(({ trialApps }) => ({ trialApps })), trialModels: () => import('./trial-models/orpc.gen').then(({ trialModels }) => ({ trialModels })), + version: () => import('./version/orpc.gen').then(({ version }) => ({ version })), website: () => import('./website/orpc.gen').then(({ website }) => ({ website })), workflow: () => import('./workflow/orpc.gen').then(({ workflow }) => ({ workflow })), workflowGenerate: () => diff --git a/packages/contracts/generated/api/console/ping/orpc.gen.ts b/packages/contracts/generated/api/console/ping/orpc.gen.ts new file mode 100644 index 00000000000..9db8ecec5fb --- /dev/null +++ b/packages/contracts/generated/api/console/ping/orpc.gen.ts @@ -0,0 +1,27 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { oc } from '@orpc/contract' + +import { zGetPingResponse } from './zod.gen' + +/** + * Health check endpoint for connection testing. + */ +export const get = oc + .route({ + inputStructure: 'detailed', + method: 'GET', + operationId: 'getPing', + path: '/ping', + summary: 'Health check endpoint for connection testing.', + tags: ['console'], + }) + .output(zGetPingResponse) + +export const ping = { + get, +} + +export const contract = { + ping, +} diff --git a/packages/contracts/generated/api/console/ping/types.gen.ts b/packages/contracts/generated/api/console/ping/types.gen.ts new file mode 100644 index 00000000000..492989c8f6a --- /dev/null +++ b/packages/contracts/generated/api/console/ping/types.gen.ts @@ -0,0 +1,22 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type ClientOptions = { + baseUrl: `${string}://${string}/console/api` | (string & {}) +} + +export type PingResponse = { + result: string +} + +export type GetPingData = { + body?: never + path?: never + query?: never + url: '/ping' +} + +export type GetPingResponses = { + 200: PingResponse +} + +export type GetPingResponse = GetPingResponses[keyof GetPingResponses] diff --git a/packages/contracts/generated/api/console/ping/zod.gen.ts b/packages/contracts/generated/api/console/ping/zod.gen.ts new file mode 100644 index 00000000000..06fd5371d16 --- /dev/null +++ b/packages/contracts/generated/api/console/ping/zod.gen.ts @@ -0,0 +1,15 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import * as z from 'zod' + +/** + * PingResponse + */ +export const zPingResponse = z.object({ + result: z.string(), +}) + +/** + * OK + */ +export const zGetPingResponse = zPingResponse diff --git a/packages/contracts/generated/api/console/router.gen.ts b/packages/contracts/generated/api/console/router.gen.ts index f9d3d4c5a76..4342f9121ca 100644 --- a/packages/contracts/generated/api/console/router.gen.ts +++ b/packages/contracts/generated/api/console/router.gen.ts @@ -24,6 +24,7 @@ import { files } from './files/orpc.gen' import { forgotPassword } from './forgot-password/orpc.gen' import { form } from './form/orpc.gen' import { info } from './info/orpc.gen' +import { init } from './init/orpc.gen' import { installedApps } from './installed-apps/orpc.gen' import { instructionGenerate } from './instruction-generate/orpc.gen' import { login } from './login/orpc.gen' @@ -31,6 +32,7 @@ import { logout } from './logout/orpc.gen' import { notification } from './notification/orpc.gen' import { notion } from './notion/orpc.gen' import { oauth } from './oauth/orpc.gen' +import { ping } from './ping/orpc.gen' import { rag } from './rag/orpc.gen' import { refreshToken } from './refresh-token/orpc.gen' import { remoteFiles } from './remote-files/orpc.gen' @@ -38,6 +40,7 @@ import { resetPassword } from './reset-password/orpc.gen' import { ruleCodeGenerate } from './rule-code-generate/orpc.gen' import { ruleGenerate } from './rule-generate/orpc.gen' import { ruleStructuredOutputGenerate } from './rule-structured-output-generate/orpc.gen' +import { setup } from './setup/orpc.gen' import { snippets } from './snippets/orpc.gen' import { spec } from './spec/orpc.gen' import { systemFeatures } from './system-features/orpc.gen' @@ -46,6 +49,7 @@ import { tags } from './tags/orpc.gen' import { test } from './test/orpc.gen' import { trialApps } from './trial-apps/orpc.gen' import { trialModels } from './trial-models/orpc.gen' +import { version } from './version/orpc.gen' import { website } from './website/orpc.gen' import { workflowGenerate } from './workflow-generate/orpc.gen' import { workflow } from './workflow/orpc.gen' @@ -75,6 +79,7 @@ const communityContract = { forgotPassword, form, info, + init, installedApps, instructionGenerate, login, @@ -82,6 +87,7 @@ const communityContract = { notification, notion, oauth, + ping, rag, refreshToken, remoteFiles, @@ -89,6 +95,7 @@ const communityContract = { ruleCodeGenerate, ruleGenerate, ruleStructuredOutputGenerate, + setup, snippets, spec, systemFeatures, @@ -97,6 +104,7 @@ const communityContract = { test, trialApps, trialModels, + version, website, workflow, workflowGenerate, diff --git a/packages/contracts/generated/api/console/setup/orpc.gen.ts b/packages/contracts/generated/api/console/setup/orpc.gen.ts new file mode 100644 index 00000000000..cb98a380264 --- /dev/null +++ b/packages/contracts/generated/api/console/setup/orpc.gen.ts @@ -0,0 +1,60 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { oc } from '@orpc/contract' +import * as z from 'zod' + +import { zGetSetupResponse, zPostSetupBody, zPostSetupResponse } from './zod.gen' + +/** + * Get system setup status. + * + * NOTE: This endpoint is unauthenticated by design. + * + * During first-time bootstrap there is no admin account yet, so frontend initialization must be + * able to query setup progress before any login flow exists. + * + * Only bootstrap-safe status information should be returned by this endpoint. + * + */ +export const get = oc + .route({ + inputStructure: 'detailed', + method: 'GET', + operationId: 'getSetup', + path: '/setup', + summary: + 'Get system setup status.\n\n NOTE: This endpoint is unauthenticated by design.\n\n During first-time bootstrap there is no admin account yet, so frontend initialization must be\n able to query setup progress before any login flow exists.\n\n Only bootstrap-safe status information should be returned by this endpoint.\n ', + tags: ['console'], + }) + .output(zGetSetupResponse) + +/** + * Initialize system setup with admin account. + * + * NOTE: This endpoint is unauthenticated by design for first-time bootstrap. + * Access is restricted by deployment mode (`SELF_HOSTED`), one-time setup guards, + * and init-password validation rather than user session authentication. + * + */ +export const post = oc + .route({ + inputStructure: 'detailed', + method: 'POST', + operationId: 'postSetup', + path: '/setup', + successStatus: 201, + summary: + 'Initialize system setup with admin account.\n\n NOTE: This endpoint is unauthenticated by design for first-time bootstrap.\n Access is restricted by deployment mode (`SELF_HOSTED`), one-time setup guards,\n and init-password validation rather than user session authentication.\n ', + tags: ['console'], + }) + .input(z.object({ body: zPostSetupBody })) + .output(zPostSetupResponse) + +export const setup = { + get, + post, +} + +export const contract = { + setup, +} diff --git a/packages/contracts/generated/api/console/setup/types.gen.ts b/packages/contracts/generated/api/console/setup/types.gen.ts new file mode 100644 index 00000000000..4472dfedf98 --- /dev/null +++ b/packages/contracts/generated/api/console/setup/types.gen.ts @@ -0,0 +1,47 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type ClientOptions = { + baseUrl: `${string}://${string}/console/api` | (string & {}) +} + +export type SetupStatusResponse = { + setup_at?: string | null + step: 'finished' | 'not_started' +} + +export type SetupRequestPayload = { + email: string + language?: string | null + name: string + password: string +} + +export type SetupResponse = { + result: string +} + +export type GetSetupData = { + body?: never + path?: never + query?: never + url: '/setup' +} + +export type GetSetupResponses = { + 200: SetupStatusResponse +} + +export type GetSetupResponse = GetSetupResponses[keyof GetSetupResponses] + +export type PostSetupData = { + body: SetupRequestPayload + path?: never + query?: never + url: '/setup' +} + +export type PostSetupResponses = { + 201: SetupResponse +} + +export type PostSetupResponse = PostSetupResponses[keyof PostSetupResponses] diff --git a/packages/contracts/generated/api/console/setup/zod.gen.ts b/packages/contracts/generated/api/console/setup/zod.gen.ts new file mode 100644 index 00000000000..d00757c7571 --- /dev/null +++ b/packages/contracts/generated/api/console/setup/zod.gen.ts @@ -0,0 +1,40 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import * as z from 'zod' + +/** + * SetupStatusResponse + */ +export const zSetupStatusResponse = z.object({ + setup_at: z.string().nullish(), + step: z.enum(['finished', 'not_started']), +}) + +/** + * SetupRequestPayload + */ +export const zSetupRequestPayload = z.object({ + email: z.string(), + language: z.string().nullish(), + name: z.string().max(30), + password: z.string(), +}) + +/** + * SetupResponse + */ +export const zSetupResponse = z.object({ + result: z.string(), +}) + +/** + * OK + */ +export const zGetSetupResponse = zSetupStatusResponse + +export const zPostSetupBody = zSetupRequestPayload + +/** + * Created + */ +export const zPostSetupResponse = zSetupResponse diff --git a/packages/contracts/generated/api/console/version/orpc.gen.ts b/packages/contracts/generated/api/console/version/orpc.gen.ts new file mode 100644 index 00000000000..c93eb911e91 --- /dev/null +++ b/packages/contracts/generated/api/console/version/orpc.gen.ts @@ -0,0 +1,29 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { oc } from '@orpc/contract' +import * as z from 'zod' + +import { zGetVersionQuery, zGetVersionResponse } from './zod.gen' + +/** + * Check for application version updates. + */ +export const get = oc + .route({ + inputStructure: 'detailed', + method: 'GET', + operationId: 'getVersion', + path: '/version', + summary: 'Check for application version updates.', + tags: ['console'], + }) + .input(z.object({ query: zGetVersionQuery })) + .output(zGetVersionResponse) + +export const version = { + get, +} + +export const contract = { + version, +} diff --git a/packages/contracts/generated/api/console/version/types.gen.ts b/packages/contracts/generated/api/console/version/types.gen.ts new file mode 100644 index 00000000000..b11ccd8a5fb --- /dev/null +++ b/packages/contracts/generated/api/console/version/types.gen.ts @@ -0,0 +1,33 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type ClientOptions = { + baseUrl: `${string}://${string}/console/api` | (string & {}) +} + +export type VersionResponse = { + can_auto_update: boolean + features: VersionFeatures + release_date: string + release_notes: string + version: string +} + +export type VersionFeatures = { + can_replace_logo: boolean + model_load_balancing_enabled: boolean +} + +export type GetVersionData = { + body?: never + path?: never + query: { + current_version: string + } + url: '/version' +} + +export type GetVersionResponses = { + 200: VersionResponse +} + +export type GetVersionResponse = GetVersionResponses[keyof GetVersionResponses] diff --git a/packages/contracts/generated/api/console/version/zod.gen.ts b/packages/contracts/generated/api/console/version/zod.gen.ts new file mode 100644 index 00000000000..c73aa358f21 --- /dev/null +++ b/packages/contracts/generated/api/console/version/zod.gen.ts @@ -0,0 +1,31 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import * as z from 'zod' + +/** + * VersionFeatures + */ +export const zVersionFeatures = z.object({ + can_replace_logo: z.boolean(), + model_load_balancing_enabled: z.boolean(), +}) + +/** + * VersionResponse + */ +export const zVersionResponse = z.object({ + can_auto_update: z.boolean(), + features: zVersionFeatures, + release_date: z.string(), + release_notes: z.string(), + version: z.string(), +}) + +export const zGetVersionQuery = z.object({ + current_version: z.string(), +}) + +/** + * OK + */ +export const zGetVersionResponse = zVersionResponse diff --git a/packages/contracts/openapi-ts.api.config.ts b/packages/contracts/openapi-ts.api.config.ts index 0fb424bcfb5..423848d5815 100644 --- a/packages/contracts/openapi-ts.api.config.ts +++ b/packages/contracts/openapi-ts.api.config.ts @@ -62,6 +62,8 @@ const apiOpenApiDir = path.resolve(currentDir, 'openapi') const operationMethods = new Set(['delete', 'get', 'patch', 'post', 'put']) const pydanticDecimalStringPattern = '^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$' const codegenSafeDecimalStringPattern = '^(?![-+.]*$)[+-]?0*\\d*\\.?\\d*$' +const fastOpenApiConsoleSpecFilename = 'fastopenapi-console-openapi.json' +const fastOpenApiConsolePathPrefix = '/console/api' const apiSpecs: ApiSpec[] = [ { filename: 'console-openapi.json', name: 'console' }, @@ -276,6 +278,33 @@ const normalizeApiSwagger = (document: SwaggerDocument) => { return document } +const mergeFastOpenApiConsoleSwagger = (document: SwaggerDocument) => { + const fastOpenApiDocument = readApiSwagger(fastOpenApiConsoleSpecFilename) + const targetPaths = document.paths ??= {} + + for (const [routePath, pathItem] of Object.entries(fastOpenApiDocument.paths ?? {})) { + const contractPath = routePath.startsWith(fastOpenApiConsolePathPrefix) + ? routePath.slice(fastOpenApiConsolePathPrefix.length) || '/' + : routePath + + if (targetPaths[contractPath]) + throw new Error(`Duplicate console API path after FastOpenAPI merge: ${contractPath}`) + + targetPaths[contractPath] = pathItem + } + + const targetSchemas = getDocumentSchemas(document) + const sourceSchemas = getDocumentSchemas(fastOpenApiDocument) + for (const [schemaName, schema] of Object.entries(sourceSchemas)) { + if (targetSchemas[schemaName]) + throw new Error(`Duplicate console API schema after FastOpenAPI merge: ${schemaName}`) + + targetSchemas[schemaName] = schema + } + + return document +} + const topLevelPathSegment = (routePath: string) => { return routePath.split('/').filter(Boolean)[0] ?? 'root' } @@ -435,7 +464,11 @@ const splitConsoleDocument = (document: SwaggerDocument) => { } const createApiJobs = (spec: ApiSpec): ApiJob[] => { - const document = normalizeApiSwagger(readApiSwagger(spec.filename)) + const document = normalizeApiSwagger( + spec.name === 'console' + ? mergeFastOpenApiConsoleSwagger(readApiSwagger(spec.filename)) + : readApiSwagger(spec.filename), + ) if (spec.name === 'console') return splitConsoleDocument(document) diff --git a/packages/contracts/package.json b/packages/contracts/package.json index 4846d84937f..3eb36db6230 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -18,9 +18,7 @@ } }, "scripts": { - "gen-api-contract": "pnpm gen-api-openapi && pnpm gen-api-contract-from-openapi", - "gen-api-contract-from-openapi": "node -e \"fs.rmSync('generated/api', { recursive: true, force: true })\" && openapi-ts -f openapi-ts.api.config.ts && vp fmt generated/api && eslint --fix generated/api", - "gen-api-openapi": "uv run --project ../../api ../../api/dev/generate_swagger_specs.py --output-dir openapi", + "gen-api-contract": "uv run --project ../../api ../../api/dev/generate_swagger_specs.py --output-dir openapi && uv run --project ../../api ../../api/dev/generate_fastopenapi_specs.py --output-dir openapi && node -e \"fs.rmSync('generated/api', { recursive: true, force: true })\" && openapi-ts -f openapi-ts.api.config.ts && vp fmt generated/api && eslint --fix generated/api", "gen-enterprise-contract": "openapi-ts -f openapi-ts.enterprise.config.ts", "type-check": "tsc" }, diff --git a/web/__tests__/utils/mock-app-context-state.ts b/web/__tests__/utils/mock-app-context-state.ts index 10fcd01c7bd..dcd524b233c 100644 --- a/web/__tests__/utils/mock-app-context-state.ts +++ b/web/__tests__/utils/mock-app-context-state.ts @@ -1,4 +1,5 @@ -import type { ICurrentWorkspace, LangGeniusVersionResponse } from '@/models/common' +import type { LangGeniusVersionInfo } from '@/context/app-context-types' +import type { ICurrentWorkspace } from '@/models/common' const APP_CONTEXT_STATE_ATOM_KIND = Symbol('app-context-state-atom-kind') @@ -22,7 +23,7 @@ export type AppContextStateMockState = { isLoadingCurrentWorkspace?: boolean isLoadingWorkspacePermissionKeys?: boolean workspacePermissionKeys?: string[] - langGeniusVersionInfo?: Partial + langGeniusVersionInfo?: Partial refreshUserProfile?: () => void refreshCurrentWorkspace?: () => void mutateUserProfile?: () => void @@ -85,8 +86,12 @@ const defaultLangGeniusVersionInfo = { version: '', release_date: '', release_notes: '', + features: { + can_replace_logo: false, + model_load_balancing_enabled: false, + }, can_auto_update: false, -} satisfies LangGeniusVersionResponse +} satisfies LangGeniusVersionInfo let appContextStateMockRegistry: AppContextStateMockRegistry | undefined @@ -110,7 +115,7 @@ const getCurrentWorkspace = (state: AppContextStateMockState): ICurrentWorkspace ...state.currentWorkspace, }) -const getLangGeniusVersionInfo = (state: AppContextStateMockState): LangGeniusVersionResponse => ({ +const getLangGeniusVersionInfo = (state: AppContextStateMockState): LangGeniusVersionInfo => ({ ...defaultLangGeniusVersionInfo, ...state.langGeniusVersionInfo, }) diff --git a/web/app/components/billing/apps-full-in-dialog/__tests__/index.spec.tsx b/web/app/components/billing/apps-full-in-dialog/__tests__/index.spec.tsx index 7adf905b358..b58c2b201e0 100644 --- a/web/app/components/billing/apps-full-in-dialog/__tests__/index.spec.tsx +++ b/web/app/components/billing/apps-full-in-dialog/__tests__/index.spec.tsx @@ -2,8 +2,9 @@ import type { GetAccountProfileResponse } from '@dify/contracts/api/console/acco import type { Mock } from 'vitest' import type { AppContextStateMockState } from '@/__tests__/utils/mock-app-context-state' import type { UsagePlanInfo } from '@/app/components/billing/type' +import type { LangGeniusVersionInfo } from '@/context/app-context-types' import type { ProviderContextState } from '@/context/provider-context' -import type { ICurrentWorkspace, LangGeniusVersionResponse } from '@/models/common' +import type { ICurrentWorkspace } from '@/models/common' import { render, screen } from '@testing-library/react' import { Plan } from '@/app/components/billing/type' import { mailToSupport } from '@/app/components/header/utils/util' @@ -96,13 +97,17 @@ const buildAppContext = (overrides: Partial = {}): App trial_credits_used: 0, next_credit_reset_date: 0, } - const langGeniusVersionInfo: LangGeniusVersionResponse = { + const langGeniusVersionInfo: LangGeniusVersionInfo = { current_env: '', current_version: '1.0.0', latest_version: '', release_date: '', release_notes: '', version: '', + features: { + can_replace_logo: false, + model_load_balancing_enabled: false, + }, can_auto_update: false, } const base: AppContextStateMockState = { diff --git a/web/app/components/header/account-about/__tests__/index.spec.tsx b/web/app/components/header/account-about/__tests__/index.spec.tsx index cc9508e50f2..76bd3f9bf88 100644 --- a/web/app/components/header/account-about/__tests__/index.spec.tsx +++ b/web/app/components/header/account-about/__tests__/index.spec.tsx @@ -1,4 +1,4 @@ -import type { LangGeniusVersionResponse } from '@/models/common' +import type { LangGeniusVersionInfo } from '@/context/app-context-types' import { fireEvent, screen } from '@testing-library/react' import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' import AccountAbout from '../index' @@ -13,12 +13,16 @@ vi.mock('@/config', async (importOriginal) => { }) describe('AccountAbout', () => { - const mockVersionInfo: LangGeniusVersionResponse = { + const mockVersionInfo: LangGeniusVersionInfo = { current_version: '0.6.0', latest_version: '0.6.0', release_notes: 'https://github.com/langgenius/dify/releases/tag/0.6.0', version: '0.6.0', release_date: '2024-01-01', + features: { + can_replace_logo: false, + model_load_balancing_enabled: false, + }, can_auto_update: false, current_env: 'production', } diff --git a/web/app/components/header/account-about/index.tsx b/web/app/components/header/account-about/index.tsx index 0a610d0f8e4..dc36393c23a 100644 --- a/web/app/components/header/account-about/index.tsx +++ b/web/app/components/header/account-about/index.tsx @@ -1,5 +1,5 @@ 'use client' -import type { LangGeniusVersionResponse } from '@/models/common' +import type { LangGeniusVersionInfo } from '@/context/app-context-types' import { Button } from '@langgenius/dify-ui/button' import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog' import { RiCloseLine } from '@remixicon/react' @@ -13,7 +13,7 @@ import { systemFeaturesQueryOptions } from '@/features/system-features/client' import Link from '@/next/link' type IAccountSettingProps = { - langGeniusVersionInfo: LangGeniusVersionResponse + langGeniusVersionInfo: LangGeniusVersionInfo onCancel: () => void } diff --git a/web/app/components/plugins/plugin-page/__tests__/use-reference-setting.spec.ts b/web/app/components/plugins/plugin-page/__tests__/use-reference-setting.spec.ts index 699738e5127..29f9f31c313 100644 --- a/web/app/components/plugins/plugin-page/__tests__/use-reference-setting.spec.ts +++ b/web/app/components/plugins/plugin-page/__tests__/use-reference-setting.spec.ts @@ -1,6 +1,6 @@ // Import mocks for assertions import type { AppContextStateMockState } from '@/__tests__/utils/mock-app-context-state' -import type { LangGeniusVersionResponse } from '@/models/common' +import type { LangGeniusVersionInfo } from '@/context/app-context-types' import { toast } from '@langgenius/dify-ui/toast' import { waitFor } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' @@ -10,18 +10,22 @@ import { useInvalidateReferenceSettings, useMutationPluginPermissionSettings, us import { PermissionType, PluginCategoryEnum } from '../../types' import useReferenceSetting, { useCanInstallPluginFromMarketplace } from '../use-reference-setting' -const defaultLangGeniusVersionInfo: LangGeniusVersionResponse = { +const defaultLangGeniusVersionInfo: LangGeniusVersionInfo = { current_env: '', current_version: '1.0.0', latest_version: '', release_date: '', release_notes: '', version: '', + features: { + can_replace_logo: false, + model_load_balancing_enabled: false, + }, can_auto_update: false, } type MockAppContextState = Omit & { - langGeniusVersionInfo?: Partial + langGeniusVersionInfo?: Partial } let mockAppContextState: AppContextStateMockState = {} diff --git a/web/context/__tests__/console-bootstrap.spec.tsx b/web/context/__tests__/console-bootstrap.spec.tsx index 9bf7b787d68..2b26a7e9b14 100644 --- a/web/context/__tests__/console-bootstrap.spec.tsx +++ b/web/context/__tests__/console-bootstrap.spec.tsx @@ -85,11 +85,19 @@ const mockLangGeniusVersionState = vi.hoisted(() => ({ version: '1.0.1', release_date: '', release_notes: '', + features: { + can_replace_logo: false, + model_load_balancing_enabled: false, + }, can_auto_update: false, } as { version: string release_date: string release_notes: string + features: { + can_replace_logo: boolean + model_load_balancing_enabled: boolean + } can_auto_update: boolean } | undefined, })) @@ -142,6 +150,22 @@ vi.mock('@/service/client', () => ({ }, }, }, + version: { + get: { + queryOptions: (options: { + enabled?: boolean + input?: { + query: { + current_version: string + } + } + }) => ({ + queryKey: ['version', options.input?.query.current_version], + queryFn: async () => mockLangGeniusVersionState.data, + ...options, + }), + }, + }, }, })) @@ -311,6 +335,10 @@ describe('Console bootstrap', () => { version: '1.0.1', release_date: '', release_notes: '', + features: { + can_replace_logo: false, + model_load_balancing_enabled: false, + }, can_auto_update: false, } mockGetRequest.mockImplementation((url: string) => { diff --git a/web/context/app-context-defaults.ts b/web/context/app-context-defaults.ts index b50f4061399..f41d0116d6b 100644 --- a/web/context/app-context-defaults.ts +++ b/web/context/app-context-defaults.ts @@ -1,12 +1,17 @@ -import type { ICurrentWorkspace, LangGeniusVersionResponse } from '@/models/common' +import type { LangGeniusVersionInfo } from './app-context-types' +import type { ICurrentWorkspace } from '@/models/common' -export const initialLangGeniusVersionInfo: LangGeniusVersionResponse = { +export const initialLangGeniusVersionInfo: LangGeniusVersionInfo = { current_env: '', current_version: '', latest_version: '', release_date: '', release_notes: '', version: '', + features: { + can_replace_logo: false, + model_load_balancing_enabled: false, + }, can_auto_update: false, } diff --git a/web/context/app-context-normalizers.ts b/web/context/app-context-normalizers.ts index d0fc0faca84..24afe00f81e 100644 --- a/web/context/app-context-normalizers.ts +++ b/web/context/app-context-normalizers.ts @@ -1,5 +1,7 @@ +import type { GetVersionResponse } from '@dify/contracts/api/console/version/types.gen' import type { PostWorkspacesCurrentResponse } from '@dify/contracts/api/console/workspaces/types.gen' -import type { ICurrentWorkspace, LangGeniusVersionResponse } from '@/models/common' +import type { LangGeniusVersionInfo } from './app-context-types' +import type { ICurrentWorkspace } from '@/models/common' import { initialLangGeniusVersionInfo, initialWorkspaceInfo } from './app-context-defaults' const workspaceRoles = new Set(['owner', 'admin', 'editor', 'dataset_operator', 'normal']) @@ -64,8 +66,8 @@ export function getLangGeniusVersionInfo({ versionData, }: { meta: ProfileMeta - versionData?: Omit -}): LangGeniusVersionResponse { + versionData?: GetVersionResponse +}): LangGeniusVersionInfo { if (!meta.currentVersion || !versionData) return initialLangGeniusVersionInfo diff --git a/web/context/app-context-state.ts b/web/context/app-context-state.ts index e5fc2c706e5..13b412182ba 100644 --- a/web/context/app-context-state.ts +++ b/web/context/app-context-state.ts @@ -6,7 +6,6 @@ import { userProfileQueryOptions } from '@/features/account-profile/client' import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { workspacePermissionKeysQueryOptions } from '@/service/access-control/use-permission-keys' import { consoleQuery } from '@/service/client' -import { langGeniusVersionQueryOptions } from '@/service/lang-genius-version' import { atomWithResolvedSuspenseQuery } from '@/utils/query-atoms' import { initialLangGeniusVersionInfo, @@ -108,7 +107,14 @@ const versionQueryAtom = atomWithQuery((get) => { const systemFeaturesQuery = get(systemFeaturesQueryAtom) const enabled = Boolean(meta.currentVersion && !systemFeaturesQuery.data.branding.enabled) - return langGeniusVersionQueryOptions(meta.currentVersion, enabled) + return consoleQuery.version.get.queryOptions({ + input: { + query: { + current_version: meta.currentVersion ?? '', + }, + }, + enabled, + }) }) export const langGeniusVersionInfoAtom = atom((get) => { diff --git a/web/context/app-context-types.ts b/web/context/app-context-types.ts new file mode 100644 index 00000000000..8ce6e635c76 --- /dev/null +++ b/web/context/app-context-types.ts @@ -0,0 +1,7 @@ +import type { GetVersionResponse } from '@dify/contracts/api/console/version/types.gen' + +export type LangGeniusVersionInfo = GetVersionResponse & { + current_version: string + latest_version: string + current_env: string +} diff --git a/web/models/common.ts b/web/models/common.ts index 951e8680c16..359178c521e 100644 --- a/web/models/common.ts +++ b/web/models/common.ts @@ -16,16 +16,6 @@ export type InitValidateStatusResponse = { status: 'finished' | 'not_started' } -export type LangGeniusVersionResponse = { - current_version: string - latest_version: string - version: string - release_date: string - release_notes: string - can_auto_update: boolean - current_env: string -} - export type Member = Pick & { avatar: string last_login_at?: string diff --git a/web/service/lang-genius-version.ts b/web/service/lang-genius-version.ts deleted file mode 100644 index 6799fcd1ad4..00000000000 --- a/web/service/lang-genius-version.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { LangGeniusVersionResponse } from '@/models/common' -import { queryOptions } from '@tanstack/react-query' -// eslint-disable-next-line no-restricted-imports -import { get } from './base' -import { commonQueryKeys } from './use-common' - -export const langGeniusVersionQueryOptions = (currentVersion?: string | null, enabled?: boolean) => { - return queryOptions({ - queryKey: commonQueryKeys.langGeniusVersion(currentVersion || undefined), - queryFn: () => get('/version', { params: { current_version: currentVersion } }), - enabled: !!currentVersion && (enabled ?? true), - }) -}