From a49bb8c8b09f9103bb0429c77f8f61da53f572c8 Mon Sep 17 00:00:00 2001 From: john Date: Tue, 21 Jul 2026 17:01:35 +0800 Subject: [PATCH] feat: complete image_make admin controls --- package.json | 2 + server/app.image-make.test.mjs | 120 +++++ server/app.mjs | 48 ++ server/bootstrap.mjs | 7 + server/index.mjs | 2 + src/admin/pages/AssetGatewayPage.tsx | 665 +++++++++++++++++++++++---- src/api/client.ts | 24 + src/index.css | 26 ++ src/types.ts | 47 ++ 9 files changed, 845 insertions(+), 96 deletions(-) create mode 100644 server/app.image-make.test.mjs diff --git a/package.json b/package.json index 49e38c7..011adaf 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,8 @@ "dev": "node scripts/dev.mjs", "dev:web": "vite", "dev:server": "node server/index.mjs", + "test": "node --test server/*.test.mjs", + "test:image-make-admin": "node --test server/app.image-make.test.mjs", "admin:init": "node scripts/init-admin.mjs", "release:prod": "bash scripts/release-prod.sh", "local_restart": "bash scripts/local_restart.sh", diff --git a/server/app.image-make.test.mjs b/server/app.image-make.test.mjs new file mode 100644 index 0000000..f988964 --- /dev/null +++ b/server/app.image-make.test.mjs @@ -0,0 +1,120 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { createAdminApp } from './app.mjs'; + +function createServices({ role = 'admin', imageMakeAdminConfigService } = {}) { + return { + ready: Promise.resolve(), + parseCookies: () => ({ adm_session: 'test-token' }), + USER_COOKIE: 'adm_session', + userLoginCookies: () => ['adm_session=test-token; Path=/; HttpOnly'], + clearUserSessionCookie: () => ['adm_session=; Path=/; Max-Age=0'], + resolveCookieDomainForRequest: () => null, + userAuth: { + getMe: async () => ({ id: 'admin-user-id', username: 'admin', role }), + }, + imageMakeAdminConfigService, + }; +} + +async function withServer(services, callback) { + const app = createAdminApp(services); + const server = await new Promise((resolve) => { + const instance = app.listen(0, '127.0.0.1', () => resolve(instance)); + }); + const address = server.address(); + const baseUrl = `http://127.0.0.1:${address.port}`; + try { + await callback(baseUrl); + } finally { + await new Promise((resolve, reject) => server.close((error) => (error ? reject(error) : resolve()))); + } +} + +const adminHeaders = { + cookie: 'adm_session=test-token', + 'content-type': 'application/json', +}; + +test('image_make admin routes read and update config with the admin identity', async () => { + let updateCall = null; + const service = { + getAdminConfig: async () => ({ + source: 'default', + config: { + defaultProvider: 'mock', + jobDefaultTimeoutSeconds: 120, + providers: { mock: { enabled: true }, aliyun_bailian: { enabled: false }, comfyui: { enabled: false } }, + }, + }), + updateAdminConfig: async (patch, context) => { + updateCall = { patch, context }; + return { ok: true, source: 'admin-db', config: patch }; + }, + getRuntimeConfig: async () => ({ + ok: true, + fingerprint: '1234567890abcdef', + defaultProvider: 'aliyun_bailian', + jobDefaultTimeoutSeconds: 120, + providers: { + mock: { enabled: true }, + aliyun_bailian: { + enabled: true, + model: 'qwen-image-plus', + apiBase: 'https://dashscope.aliyuncs.com/api/v1', + apiKey: 'must-not-leak', + }, + comfyui: { enabled: false }, + }, + }), + }; + + await withServer(createServices({ imageMakeAdminConfigService: service }), async (baseUrl) => { + const configResponse = await fetch(`${baseUrl}/admin-api/image-make/config`, { headers: adminHeaders }); + assert.equal(configResponse.status, 200); + assert.equal((await configResponse.json()).config.defaultProvider, 'mock'); + + const patch = { defaultProvider: 'mock', jobDefaultTimeoutSeconds: 180 }; + const updateResponse = await fetch(`${baseUrl}/admin-api/image-make/config`, { + method: 'PATCH', + headers: adminHeaders, + body: JSON.stringify(patch), + }); + assert.equal(updateResponse.status, 200); + assert.deepEqual(updateCall, { patch, context: { updatedBy: 'admin-user-id' } }); + + const runtimeResponse = await fetch(`${baseUrl}/admin-api/image-make/runtime`, { headers: adminHeaders }); + assert.equal(runtimeResponse.status, 200); + const runtime = await runtimeResponse.json(); + assert.equal(runtime.providers.aliyun_bailian.apiKeyConfigured, true); + assert.equal('apiKey' in runtime.providers.aliyun_bailian, false); + assert.equal(JSON.stringify(runtime).includes('must-not-leak'), false); + }); +}); + +test('image_make admin config validation failures are returned as 400', async () => { + const service = { + updateAdminConfig: async () => ({ ok: false, message: '默认 Provider 必须处于启用状态' }), + }; + await withServer(createServices({ imageMakeAdminConfigService: service }), async (baseUrl) => { + const response = await fetch(`${baseUrl}/admin-api/image-make/config`, { + method: 'PATCH', + headers: adminHeaders, + body: JSON.stringify({ defaultProvider: 'comfyui' }), + }); + assert.equal(response.status, 400); + assert.equal((await response.json()).message, '默认 Provider 必须处于启用状态'); + }); +}); + +test('image_make admin routes require an admin user and an enabled service', async () => { + await withServer(createServices({ role: 'user', imageMakeAdminConfigService: {} }), async (baseUrl) => { + const forbidden = await fetch(`${baseUrl}/admin-api/image-make/config`, { headers: adminHeaders }); + assert.equal(forbidden.status, 403); + }); + + await withServer(createServices({ imageMakeAdminConfigService: {} }), async (baseUrl) => { + const unavailable = await fetch(`${baseUrl}/admin-api/image-make/config`, { headers: adminHeaders }); + assert.equal(unavailable.status, 503); + }); +}); diff --git a/server/app.mjs b/server/app.mjs index 6ea46dc..615c25b 100644 --- a/server/app.mjs +++ b/server/app.mjs @@ -93,6 +93,7 @@ export function createAdminApp(services) { userAuth, llmProviderService, assetGatewayConfigService, + imageMakeAdminConfigService, pool, ready, wechatAdmin, @@ -369,6 +370,53 @@ export function createAdminApp(services) { res.json(result); }); + adminApi.get('/image-make/config', requireAdmin, async (_req, res) => { + if (!imageMakeAdminConfigService?.getAdminConfig) { + return res.status(503).json({ message: 'image_make 配置服务未启用' }); + } + res.json(await imageMakeAdminConfigService.getAdminConfig()); + }); + + const updateImageMakeConfig = async (req, res) => { + if (!imageMakeAdminConfigService?.updateAdminConfig) { + return res.status(503).json({ message: 'image_make 配置服务未启用' }); + } + const result = await imageMakeAdminConfigService.updateAdminConfig(req.body ?? {}, { + updatedBy: req.currentUser.id, + }); + if (result.ok === false) return res.status(400).json({ message: result.message }); + res.json(result); + }; + + adminApi.put('/image-make/config', requireAdmin, updateImageMakeConfig); + adminApi.patch('/image-make/config', requireAdmin, updateImageMakeConfig); + + adminApi.get('/image-make/runtime', requireAdmin, async (_req, res) => { + if (!imageMakeAdminConfigService?.getRuntimeConfig) { + return res.status(503).json({ message: 'image_make 配置服务未启用' }); + } + const runtime = await imageMakeAdminConfigService.getRuntimeConfig(); + if (!runtime.ok) return res.status(503).json({ message: runtime.message ?? 'image_make 运行时配置无效' }); + const { providers, ...rest } = runtime; + res.json({ + ...rest, + providers: { + mock: { enabled: providers.mock.enabled }, + aliyun_bailian: providers.aliyun_bailian.enabled + ? { + enabled: true, + model: providers.aliyun_bailian.model, + apiBase: providers.aliyun_bailian.apiBase, + apiKeyConfigured: Boolean(providers.aliyun_bailian.apiKey), + } + : { enabled: false }, + comfyui: providers.comfyui.enabled + ? { enabled: true, ...providers.comfyui } + : { enabled: false }, + }, + }); + }); + adminApi.get('/memory-v2/config', requireAdmin, async (_req, res) => { if (!memoryV2ConfigService) return res.status(503).json({ message: 'Memory V2 配置未启用' }); res.json(await memoryV2ConfigService.getAdminConfig()); diff --git a/server/bootstrap.mjs b/server/bootstrap.mjs index cb603c2..707c830 100644 --- a/server/bootstrap.mjs +++ b/server/bootstrap.mjs @@ -33,6 +33,7 @@ export async function bootstrapAdminServices() { const { createPersonalMemoryCandidateStore } = await importMemind('memory-v2-personal-store.mjs'); const { createSkillRuntimeAdminConfigService } = await importMemind('skill-runtime-admin-config.mjs'); const { createAssetGatewayConfigService } = await importMemind('asset-gateway.mjs'); + const { createImageMakeAdminConfigService } = await importMemind('image-make-admin-config.mjs'); const { ensureAssetGatewaySchema } = await importMemind('db.mjs'); const { createWechatScheduleLlmConfigService } = await importMemind('wechat-schedule-llm-config.mjs'); const { createAdminSystemTestService } = await importMemind('admin-system-tests.mjs'); @@ -93,6 +94,11 @@ export async function bootstrapAdminServices() { }); await ensureAssetGatewaySchema(pool); const assetGatewayConfigService = createAssetGatewayConfigService(pool, { llmProviderService }); + const imageMakeAdminConfigService = createImageMakeAdminConfigService(pool, { + env: process.env, + llmProviderService, + }); + await imageMakeAdminConfigService.ensureSchema(); const memoryV2ConfigService = createMemoryV2AdminConfigService(pool, { env: process.env, }); @@ -148,6 +154,7 @@ export async function bootstrapAdminServices() { userAuth, llmProviderService, assetGatewayConfigService, + imageMakeAdminConfigService, plazaOps, createOpsApi, wechatAdmin, diff --git a/server/index.mjs b/server/index.mjs index 9f314d2..7902ce6 100644 --- a/server/index.mjs +++ b/server/index.mjs @@ -99,6 +99,7 @@ ready userAuth, llmProviderService, assetGatewayConfigService, + imageMakeAdminConfigService, plazaOps, createOpsApi, wechatAdmin, @@ -125,6 +126,7 @@ ready userAuth, llmProviderService, assetGatewayConfigService, + imageMakeAdminConfigService, plazaOps, createOpsApi, wechatAdmin, diff --git a/src/admin/pages/AssetGatewayPage.tsx b/src/admin/pages/AssetGatewayPage.tsx index 401e862..cffb805 100644 --- a/src/admin/pages/AssetGatewayPage.tsx +++ b/src/admin/pages/AssetGatewayPage.tsx @@ -1,11 +1,15 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; +import { Link } from 'react-router-dom'; import { getAssetGatewayConfig, - listLlmProviderKeys, + getImageMakeAdminConfig, + getImageMakeRuntimeStatus, + getLlmVisionSettings, updateAssetGatewayConfig, updateAssetPluginConfig, + updateImageMakeAdminConfig, } from '../../api/client'; -import type { AssetGatewayConfig, AssetPluginConfig, LlmProviderKeyRow } from '../../types'; +import type { AssetGatewayConfig, AssetPluginConfig, ImageMakeAdminConfig, LlmVisionSettings } from '../../types'; type PluginDraft = { enabled: boolean; @@ -32,15 +36,44 @@ const pluginVisuals: Record = { 'asset-analyze': { icon: '◉', accent: 'green' }, }; +const defaultImageMakeConfig: ImageMakeAdminConfig = { + defaultProvider: 'mock', + jobDefaultTimeoutSeconds: 120, + providers: { + mock: { enabled: true }, + aliyun_bailian: { + enabled: false, + model: 'qwen-image-plus', + apiBase: 'https://dashscope.aliyuncs.com/api/v1', + apiKeyConfigured: false, + apiKeyMasked: '', + }, + comfyui: { + enabled: false, + apiBase: 'http://127.0.0.1:8188', + checkpoint: '', + workflowPath: './config/comfyui/workflows/core_txt2img_v1.json', + outputNodeId: '9', + timeoutSeconds: 600, + steps: 20, + cfg: 7, + sampler: 'euler', + scheduler: 'normal', + }, + }, +}; + export function AssetGatewayPage() { const [config, setConfig] = useState(null); - const [keys, setKeys] = useState([]); const [drafts, setDrafts] = useState>({}); const [loading, setLoading] = useState(true); const [busy, setBusy] = useState(null); const [message, setMessage] = useState(null); const [error, setError] = useState(null); - const activeKeys = useMemo(() => keys.filter((key) => key.status === 'active'), [keys]); + const [imageMakeConfig, setImageMakeConfig] = useState(defaultImageMakeConfig); + const [imageMakeMeta, setImageMakeMeta] = useState<{ source?: string; updatedAt?: number | null; updatedBy?: string | null }>({}); + const [imageMakeRuntime, setImageMakeRuntime] = useState(''); + const [visionSettings, setVisionSettings] = useState(null); const orderedPlugins = useMemo(() => config?.plugins.slice().sort((left, right) => { if (left.pluginId === 'asset-generate') return -1; if (right.pluginId === 'asset-generate') return 1; @@ -50,15 +83,44 @@ export function AssetGatewayPage() { const imageGenerateDraft = imageGeneratePlugin ? drafts[imageGeneratePlugin.pluginId] ?? draftFromPlugin(imageGeneratePlugin) : null; + const imageMakeControlsEnabled = Boolean( + imageGenerateDraft?.enabled && imageGenerateDraft.provider === 'image_make', + ); const load = useCallback(async () => { setLoading(true); setError(null); try { - const [nextConfig, nextKeys] = await Promise.all([getAssetGatewayConfig(), listLlmProviderKeys()]); + const [nextConfig, nextVisionSettings, nextImageMakeConfig, nextImageMakeRuntime] = await Promise.all([ + getAssetGatewayConfig(), + getLlmVisionSettings().catch(() => null), + getImageMakeAdminConfig(), + getImageMakeRuntimeStatus().catch(() => null), + ]); setConfig(nextConfig); - setKeys(nextKeys); + setVisionSettings(nextVisionSettings); setDrafts(Object.fromEntries(nextConfig.plugins.map((plugin) => [plugin.pluginId, draftFromPlugin(plugin)]))); + setImageMakeConfig({ + ...defaultImageMakeConfig, + ...nextImageMakeConfig.config, + providers: { + ...defaultImageMakeConfig.providers, + ...nextImageMakeConfig.config.providers, + mock: { ...defaultImageMakeConfig.providers.mock, ...nextImageMakeConfig.config.providers.mock }, + aliyun_bailian: { + ...defaultImageMakeConfig.providers.aliyun_bailian, + ...nextImageMakeConfig.config.providers.aliyun_bailian, + }, + comfyui: { + ...defaultImageMakeConfig.providers.comfyui, + ...nextImageMakeConfig.config.providers.comfyui, + }, + }, + }); + setImageMakeMeta(nextImageMakeConfig); + setImageMakeRuntime(nextImageMakeRuntime?.ok + ? `默认 Provider:${nextImageMakeRuntime.defaultProvider} · 指纹 ${nextImageMakeRuntime.fingerprint?.slice(0, 12) ?? '—'}` + : nextImageMakeRuntime?.message ?? '运行时未就绪'); } catch (err) { setError(err instanceof Error ? err.message : '加载资产能力配置失败'); } finally { @@ -100,8 +162,6 @@ export function AssetGatewayPage() { const result = await updateAssetPluginConfig(plugin.pluginId, { enabled: draft.enabled, provider: draft.provider || null, - llmProviderKeyId: draft.llmProviderKeyId || null, - llmModel: draft.llmModel || null, purposes: draft.purposes, }); setConfig(result.config); @@ -111,6 +171,64 @@ export function AssetGatewayPage() { finally { setBusy(null); } }; + const saveImageMakeConfig = async () => { + setBusy('image-make-config'); setMessage(null); setError(null); + try { + const payload: Parameters[0] = { + defaultProvider: imageMakeConfig.defaultProvider, + jobDefaultTimeoutSeconds: imageMakeConfig.jobDefaultTimeoutSeconds, + providers: { + mock: { enabled: imageMakeConfig.providers.mock.enabled }, + aliyun_bailian: { + enabled: imageMakeConfig.providers.aliyun_bailian.enabled, + model: imageMakeConfig.providers.aliyun_bailian.model, + apiBase: imageMakeConfig.providers.aliyun_bailian.apiBase, + }, + comfyui: { + enabled: imageMakeConfig.providers.comfyui.enabled, + apiBase: imageMakeConfig.providers.comfyui.apiBase, + checkpoint: imageMakeConfig.providers.comfyui.checkpoint, + workflowPath: imageMakeConfig.providers.comfyui.workflowPath, + outputNodeId: imageMakeConfig.providers.comfyui.outputNodeId, + timeoutSeconds: imageMakeConfig.providers.comfyui.timeoutSeconds, + steps: imageMakeConfig.providers.comfyui.steps, + cfg: imageMakeConfig.providers.comfyui.cfg, + sampler: imageMakeConfig.providers.comfyui.sampler, + scheduler: imageMakeConfig.providers.comfyui.scheduler, + }, + }, + }; + const result = await updateImageMakeAdminConfig(payload); + if (result.ok === false) throw new Error(result.message ?? '保存 image_make 配置失败'); + setImageMakeConfig({ + ...defaultImageMakeConfig, + ...result.config, + providers: { + ...defaultImageMakeConfig.providers, + ...result.config.providers, + mock: { ...defaultImageMakeConfig.providers.mock, ...result.config.providers.mock }, + aliyun_bailian: { + ...defaultImageMakeConfig.providers.aliyun_bailian, + ...result.config.providers.aliyun_bailian, + }, + comfyui: { + ...defaultImageMakeConfig.providers.comfyui, + ...result.config.providers.comfyui, + }, + }, + }); + setImageMakeMeta(result); + const nextVisionSettings = await getLlmVisionSettings().catch(() => null); + if (nextVisionSettings) setVisionSettings(nextVisionSettings); + const runtime = await getImageMakeRuntimeStatus().catch(() => null); + setImageMakeRuntime(runtime?.ok + ? `默认 Provider:${runtime.defaultProvider} · 指纹 ${runtime.fingerprint?.slice(0, 12) ?? '—'}` + : runtime?.message ?? '运行时未就绪'); + setMessage('image_make 生图模型配置已保存;服务会在数秒内自动拉取新配置。'); + } catch (err) { setError(err instanceof Error ? err.message : '保存 image_make 配置失败'); } + finally { setBusy(null); } + }; + if (loading) return

加载资产能力配置…

; if (!config) return

{error ?? '无法加载配置'}

; @@ -134,89 +252,451 @@ export function AssetGatewayPage() { {imageGeneratePlugin && imageGenerateDraft ? ( -
-
-
-
IMAGE GENERATION SCOPE
-

图片生成范围

-

勾选才会在对应位置使用 AI 图片;卡片和缩略图默认复用页面头图,不增加 ComfyUI 任务。

-
- + <> +
+

图片生成范围

+

控制 Memind 是否调用 image_make,以及各输出位置是否启用 AI 图片。

- -
- 按输出位置独立控制 -
- {imageGeneratePlugin.purposeCatalog?.map((purpose) => { - const descriptions: Record = { - inline_image: '正文确有需要时单独生成;关闭可显著减少等待时间', - hero: '页面主视觉,最多调用一次 ComfyUI', - card_cover: '从页面头图裁剪,不额外调用 ComfyUI', - feed_cover: '从页面头图裁剪,不额外调用 ComfyUI', - }; - return ( -
-
- 未勾选的 AI 图片项不执行;原有无图页面和基础缩略图继续可用。 - + + + + +
+
+
+ + {imageMakeConfig.providers.comfyui.checkpoint || '未配置 checkpoint'} + + +
+
- + ) : null}

其他资产能力

-

素材检索、图片处理和图片理解保持独立配置。

+

素材检索、图片处理和图片理解仅配置运行 Provider;大模型统一在「统一模型中心」管理。

{orderedPlugins.filter((plugin) => plugin.pluginId !== 'asset-generate').map((plugin) => { const draft = drafts[plugin.pluginId] ?? draftFromPlugin(plugin); - const selectedKey = activeKeys.find((key) => key.id === draft.llmProviderKeyId); const visual = pluginVisuals[plugin.pluginId] ?? { icon: '✦', accent: 'blue' }; return
@@ -229,29 +709,22 @@ export function AssetGatewayPage() {
- {plugin.pluginId === 'asset-generate' && draft.provider === 'image_make' ? ( + {plugin.supportsLlm ? (
- image_make 独立管理本地模型,不读取统一模型中心密钥。 + 大模型不在此单独配置;请在 + {' '} + 统一模型中心 + {' '} + 设置默认模型与图片任务模型(Qwen VL)。
- ) : plugin.supportsLlm ? <> - - - :
确定性处理 · 不调用 LLM
} + ) : ( +
确定性处理 · 不调用 LLM
+ )} {plugin.pluginId === 'asset-generate' && plugin.purposeCatalog?.length ? (
AI 图片输出项 diff --git a/src/api/client.ts b/src/api/client.ts index 04fe530..a58abe9 100644 --- a/src/api/client.ts +++ b/src/api/client.ts @@ -1,5 +1,8 @@ import type { AssetGatewayConfig, + ImageMakeAdminConfig, + ImageMakeAdminConfigResponse, + ImageMakeRuntimeResponse, AdminDashboardSummary, AdminServiceRestartAction, AdminServiceRestartResult, @@ -316,6 +319,27 @@ export async function updateAssetPluginConfig( }); } +export async function getImageMakeAdminConfig(): Promise { + return portalFetch('/admin-api/image-make/config'); +} + +export async function updateImageMakeAdminConfig( + payload: Partial & { + providers?: Partial & { + aliyun_bailian?: Partial & { apiKey?: string }; + }; + }, +): Promise { + return portalFetch('/admin-api/image-make/config', { + method: 'PATCH', + body: JSON.stringify(payload), + }); +} + +export async function getImageMakeRuntimeStatus(): Promise { + return portalFetch('/admin-api/image-make/runtime'); +} + export async function getMindSpaceAdminConfig(): Promise { const result = await portalFetch<{ config: MindSpaceAdminConfig }>('/admin-api/mindspace/config'); return result.config; diff --git a/src/index.css b/src/index.css index f38fc61..ff648ef 100644 --- a/src/index.css +++ b/src/index.css @@ -1116,6 +1116,32 @@ body, .asset-plugin-card--amber { border-top: 3px solid #e8af55; } .asset-plugin-card--green { border-top: 3px solid #55c79b; } +.asset-plugin-card.is-disabled { + opacity: 0.68; +} + +.asset-plugin-card.is-disabled .asset-plugin-fields, +.asset-plugin-card.is-disabled .asset-plugin-footer button { + pointer-events: none; +} + +.asset-plugin-card.is-disabled .asset-plugin-card-head .asset-mini-toggle { + pointer-events: auto; +} + +.asset-plugin-icon--blue { background: rgba(84, 168, 255, .17); color: #9fd0ff; } +.asset-plugin-icon--amber { background: rgba(232, 175, 85, .17); color: #f0cc84; } +.asset-plugin-icon--green { background: rgba(85, 199, 155, .17); color: #8fe0bc; } + +.image-make-runtime-note { + padding: 9px 10px; + border-radius: var(--radius-sm); + background: rgba(84, 168, 255, .1); + color: var(--color-text-muted); + font-size: 11px; + line-height: 1.5; +} + .asset-plugin-card-head { display: flex; gap: 10px; diff --git a/src/types.ts b/src/types.ts index 00c98d1..6033be9 100644 --- a/src/types.ts +++ b/src/types.ts @@ -120,6 +120,7 @@ export type LlmVisionSettings = { providerLabel: string | null; visionModel: string | null; availableModels: string[]; + apiKeyMasked?: string | null; }; export type LlmConnectionTestResult = { @@ -224,6 +225,52 @@ export type AssetGatewayConfig = { plugins: AssetPluginConfig[]; }; +export type ImageMakeProviderConfig = { + enabled: boolean; + model?: string; + apiBase?: string; + apiKeyConfigured?: boolean; + apiKeyMasked?: string; + dashScopeSource?: 'vision' | 'global' | null; + dashScopeKeyName?: string | null; + checkpoint?: string; + workflowPath?: string; + outputNodeId?: string; + timeoutSeconds?: number; + steps?: number; + cfg?: number; + sampler?: string; + scheduler?: string; +}; + +export type ImageMakeAdminConfig = { + defaultProvider: 'mock' | 'aliyun_bailian' | 'comfyui'; + jobDefaultTimeoutSeconds: number; + providers: { + mock: ImageMakeProviderConfig; + aliyun_bailian: ImageMakeProviderConfig; + comfyui: ImageMakeProviderConfig; + }; +}; + +export type ImageMakeAdminConfigResponse = { + config: ImageMakeAdminConfig; + source?: string; + updatedAt?: number | null; + updatedBy?: string | null; +}; + +export type ImageMakeRuntimeResponse = { + ok: boolean; + fingerprint?: string; + source?: string; + updatedAt?: number | null; + defaultProvider?: string; + jobDefaultTimeoutSeconds?: number; + providers?: ImageMakeAdminConfig['providers']; + message?: string; +}; + export type AdminServiceRestartAction = 'local_restart' | 'pro_restart'; export type AdminServiceRestartResult = {