feat: complete image_make admin controls

This commit is contained in:
john
2026-07-21 17:01:35 +08:00
parent 1969071cf4
commit a49bb8c8b0
9 changed files with 845 additions and 96 deletions
+569 -96
View File
@@ -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<string, { icon: string; accent: string }> = {
'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<AssetGatewayConfig | null>(null);
const [keys, setKeys] = useState<LlmProviderKeyRow[]>([]);
const [drafts, setDrafts] = useState<Record<string, PluginDraft>>({});
const [loading, setLoading] = useState(true);
const [busy, setBusy] = useState<string | null>(null);
const [message, setMessage] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null);
const activeKeys = useMemo(() => keys.filter((key) => key.status === 'active'), [keys]);
const [imageMakeConfig, setImageMakeConfig] = useState<ImageMakeAdminConfig>(defaultImageMakeConfig);
const [imageMakeMeta, setImageMakeMeta] = useState<{ source?: string; updatedAt?: number | null; updatedBy?: string | null }>({});
const [imageMakeRuntime, setImageMakeRuntime] = useState<string>('');
const [visionSettings, setVisionSettings] = useState<LlmVisionSettings | null>(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<typeof updateImageMakeAdminConfig>[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 <div className="admin-page"><p className="muted"></p></div>;
if (!config) return <div className="admin-page"><p className="banner banner-error">{error ?? '无法加载配置'}</p></div>;
@@ -134,89 +252,451 @@ export function AssetGatewayPage() {
</label>
</section>
{imageGeneratePlugin && imageGenerateDraft ? (
<section className="image-generation-settings">
<div className="image-generation-settings-head">
<div>
<div className="asset-kicker">IMAGE GENERATION SCOPE</div>
<h3></h3>
<p>使 AI ComfyUI </p>
</div>
<label className="asset-mini-toggle" title="启用图片生成">
<input
type="checkbox"
checked={imageGenerateDraft.enabled}
onChange={(event) => updateDraft(imageGeneratePlugin.pluginId, { enabled: event.target.checked })}
/>
<span>{imageGenerateDraft.enabled ? '图片生成已开启' : '图片生成已关闭'}</span>
</label>
<>
<div className="model-center-section-title">
<h3></h3>
<p> Memind image_make AI </p>
</div>
<label className="image-generation-provider">
Provider
<select
value={imageGenerateDraft.provider}
disabled={!imageGenerateDraft.enabled}
onChange={(event) => {
const provider = event.target.value;
updateDraft(imageGeneratePlugin.pluginId, {
provider,
...(provider === 'image_make' ? { llmProviderKeyId: '', llmModel: '' } : {}),
});
}}
>
<option value=""> Provider</option>
{imageGeneratePlugin.providers.map((provider) => <option key={provider} value={provider}>{provider}</option>)}
</select>
</label>
<fieldset
className="image-generation-scope-fieldset"
disabled={!imageGenerateDraft.enabled || imageGenerateDraft.provider !== 'image_make'}
>
<legend></legend>
<div className="image-generation-scope-grid">
{imageGeneratePlugin.purposeCatalog?.map((purpose) => {
const descriptions: Record<string, string> = {
inline_image: '正文确有需要时单独生成;关闭可显著减少等待时间',
hero: '页面主视觉,最多调用一次 ComfyUI',
card_cover: '从页面头图裁剪,不额外调用 ComfyUI',
feed_cover: '从页面头图裁剪,不额外调用 ComfyUI',
};
return (
<label className="image-generation-scope-item" key={purpose.id}>
<div className="asset-plugin-grid asset-plugin-grid--balanced">
<section className="asset-plugin-card asset-plugin-card--violet">
<div className="asset-plugin-card-head">
<div className="asset-plugin-icon" aria-hidden="true"></div>
<div>
<h3>{imageGeneratePlugin.label}</h3>
<p>使 AI </p>
</div>
<label className="asset-mini-toggle" title="启用图片生成">
<input
type="checkbox"
checked={imageGenerateDraft.enabled}
onChange={(event) => updateDraft(imageGeneratePlugin.pluginId, { enabled: event.target.checked })}
/>
<span>{imageGenerateDraft.enabled ? '已开启' : '已关闭'}</span>
</label>
</div>
<div className="asset-plugin-fields">
<label>
Provider
<select
value={imageGenerateDraft.provider}
disabled={!imageGenerateDraft.enabled}
onChange={(event) => {
const provider = event.target.value;
updateDraft(imageGeneratePlugin.pluginId, {
provider,
...(provider === 'image_make' ? { llmProviderKeyId: '', llmModel: '' } : {}),
});
}}
>
<option value=""> Provider</option>
{imageGeneratePlugin.providers.map((provider) => (
<option key={provider} value={provider}>{provider}</option>
))}
</select>
</label>
<fieldset
className="asset-purpose-fieldset"
disabled={!imageGenerateDraft.enabled || imageGenerateDraft.provider !== 'image_make'}
>
<legend></legend>
<div className="asset-purpose-grid">
{imageGeneratePlugin.purposeCatalog?.map((purpose) => {
const descriptions: Record<string, string> = {
inline_image: '正文确有需要时单独生成',
hero: '页面主视觉,最多生成一次',
card_cover: '从页面头图派生',
feed_cover: '从页面头图派生',
};
return (
<label key={purpose.id}>
<input
type="checkbox"
checked={Boolean(imageGenerateDraft.purposes[purpose.id])}
onChange={(event) => updatePurpose(
imageGeneratePlugin.pluginId,
purpose.id,
event.target.checked,
)}
/>
<span>
{purpose.label}
<small>{descriptions[purpose.id] ?? purpose.presetId}</small>
</span>
</label>
);
})}
</div>
</fieldset>
</div>
<div className="asset-plugin-footer">
<span className={`asset-status ${imageGenerateDraft.enabled ? 'is-on' : ''}`}>
{imageGenerateDraft.enabled ? '待保存为启用' : '默认安全关闭'}
</span>
<button
type="button"
className="send-btn"
disabled={busy === imageGeneratePlugin.pluginId}
onClick={() => void savePlugin(imageGeneratePlugin)}
>
{busy === imageGeneratePlugin.pluginId ? '保存中…' : '保存图片生成配置'}
</button>
</div>
</section>
</div>
<div className="model-center-section-title">
<h3> Provider</h3>
<p>image_make DashScope Key Qwen VL </p>
</div>
<div className="asset-plugin-grid">
<section className={`asset-plugin-card asset-plugin-card--blue${imageMakeControlsEnabled ? '' : ' is-disabled'}`}>
<div className="asset-plugin-card-head">
<div className="asset-plugin-icon asset-plugin-icon--blue" aria-hidden="true"></div>
<div>
<h3></h3>
<p> Provider image_make </p>
</div>
</div>
<div className="asset-plugin-fields">
{!imageMakeControlsEnabled ? (
<div className="image-make-runtime-note">
Provider image_make
</div>
) : null}
<label>
Provider
<select
value={imageMakeConfig.defaultProvider}
disabled={!imageMakeControlsEnabled}
onChange={(event) => setImageMakeConfig((current) => ({
...current,
defaultProvider: event.target.value as ImageMakeAdminConfig['defaultProvider'],
}))}
>
<option value="aliyun_bailian"> Qwen-Image</option>
<option value="comfyui"> ComfyUI</option>
<option value="mock">Mock</option>
</select>
</label>
<label>
<input
type="number"
min={5}
max={1800}
value={imageMakeConfig.jobDefaultTimeoutSeconds}
disabled={!imageMakeControlsEnabled}
onChange={(event) => setImageMakeConfig((current) => ({
...current,
jobDefaultTimeoutSeconds: Number(event.target.value),
}))}
/>
</label>
<div className="image-make-runtime-note">
{imageMakeRuntime || '运行时状态未知'}
{' · '}
{imageMakeMeta.source === 'admin-db' ? '数据库' : '默认值'}
</div>
</div>
<div className="asset-plugin-footer">
<span className={`asset-status ${imageMakeControlsEnabled ? 'is-on' : ''}`}>
{imageMakeConfig.defaultProvider}
</span>
<button
type="button"
className="send-btn"
disabled={busy === 'image-make-config' || !imageMakeControlsEnabled}
onClick={() => void saveImageMakeConfig()}
>
{busy === 'image-make-config' ? '保存中…' : '保存生图模型配置'}
</button>
</div>
</section>
<section className={`asset-plugin-card asset-plugin-card--amber${imageMakeControlsEnabled ? '' : ' is-disabled'}`}>
<div className="asset-plugin-card-head">
<div className="asset-plugin-icon asset-plugin-icon--amber" aria-hidden="true"></div>
<div>
<h3> Qwen-Image</h3>
<p> DashScope Qwen VL DashScope Key</p>
</div>
<label className="asset-mini-toggle">
<input
type="checkbox"
checked={imageMakeConfig.providers.aliyun_bailian.enabled}
disabled={!imageMakeControlsEnabled}
onChange={(event) => setImageMakeConfig((current) => ({
...current,
providers: {
...current.providers,
aliyun_bailian: { ...current.providers.aliyun_bailian, enabled: event.target.checked },
},
}))}
/>
<span>{imageMakeConfig.providers.aliyun_bailian.enabled ? '已启用' : '未启用'}</span>
</label>
</div>
<div className="asset-plugin-fields">
<label>
<select
value={imageMakeConfig.providers.aliyun_bailian.model ?? 'qwen-image-plus'}
disabled={!imageMakeControlsEnabled}
onChange={(event) => setImageMakeConfig((current) => ({
...current,
providers: {
...current.providers,
aliyun_bailian: { ...current.providers.aliyun_bailian, model: event.target.value },
},
}))}
>
<option value="qwen-image-plus">qwen-image-plus</option>
<option value="qwen-image">qwen-image</option>
</select>
</label>
<label>
API Base
<input
value={imageMakeConfig.providers.aliyun_bailian.apiBase ?? ''}
placeholder="https://dashscope.aliyuncs.com/api/v1"
disabled={!imageMakeControlsEnabled}
onChange={(event) => setImageMakeConfig((current) => ({
...current,
providers: {
...current.providers,
aliyun_bailian: { ...current.providers.aliyun_bailian, apiBase: event.target.value },
},
}))}
/>
</label>
<div className="image-make-runtime-note">
DashScope API Key Qwen VL
{' '}
<Link to="/providers"></Link>
{' '}
Qwen Provider
{imageMakeConfig.providers.aliyun_bailian.apiKeyConfigured ? (
<>
{' '}
{imageMakeConfig.providers.aliyun_bailian.dashScopeKeyName
?? visionSettings?.keyName
?? 'DashScope'}
{' '}
(
{imageMakeConfig.providers.aliyun_bailian.apiKeyMasked
?? visionSettings?.apiKeyMasked
?? '已配置'}
)
</>
) : (
<> DashScope Key</>
)}
</div>
</div>
<div className="asset-plugin-footer">
<span className={`asset-status ${imageMakeConfig.providers.aliyun_bailian.enabled ? 'is-on' : ''}`}>
{imageMakeConfig.providers.aliyun_bailian.model ?? 'qwen-image-plus'}
</span>
<button
type="button"
className="send-btn"
disabled={busy === 'image-make-config' || !imageMakeControlsEnabled}
onClick={() => void saveImageMakeConfig()}
>
{busy === 'image-make-config' ? '保存中…' : '保存配置'}
</button>
</div>
</section>
<section className={`asset-plugin-card asset-plugin-card--green${imageMakeControlsEnabled ? '' : ' is-disabled'}`}>
<div className="asset-plugin-card-head">
<div className="asset-plugin-icon asset-plugin-icon--green" aria-hidden="true"></div>
<div>
<h3> ComfyUI</h3>
<p> SD / Flux 线</p>
</div>
<label className="asset-mini-toggle">
<input
type="checkbox"
checked={imageMakeConfig.providers.comfyui.enabled}
disabled={!imageMakeControlsEnabled}
onChange={(event) => setImageMakeConfig((current) => ({
...current,
providers: {
...current.providers,
comfyui: { ...current.providers.comfyui, enabled: event.target.checked },
},
}))}
/>
<span>{imageMakeConfig.providers.comfyui.enabled ? '已启用' : '未启用'}</span>
</label>
</div>
<div className="asset-plugin-fields">
<label>
ComfyUI
<input
value={imageMakeConfig.providers.comfyui.apiBase ?? ''}
disabled={!imageMakeControlsEnabled}
onChange={(event) => setImageMakeConfig((current) => ({
...current,
providers: {
...current.providers,
comfyui: { ...current.providers.comfyui, apiBase: event.target.value },
},
}))}
/>
</label>
<label>
Checkpoint
<input
value={imageMakeConfig.providers.comfyui.checkpoint ?? ''}
placeholder="v1-5-pruned-emaonly-fp16.safetensors"
disabled={!imageMakeControlsEnabled}
onChange={(event) => setImageMakeConfig((current) => ({
...current,
providers: {
...current.providers,
comfyui: { ...current.providers.comfyui, checkpoint: event.target.value },
},
}))}
/>
</label>
<label>
Workflow
<input
value={imageMakeConfig.providers.comfyui.workflowPath ?? ''}
disabled={!imageMakeControlsEnabled}
onChange={(event) => setImageMakeConfig((current) => ({
...current,
providers: {
...current.providers,
comfyui: { ...current.providers.comfyui, workflowPath: event.target.value },
},
}))}
/>
</label>
<label>
ID
<input
value={imageMakeConfig.providers.comfyui.outputNodeId ?? '9'}
disabled={!imageMakeControlsEnabled}
onChange={(event) => setImageMakeConfig((current) => ({
...current,
providers: {
...current.providers,
comfyui: { ...current.providers.comfyui, outputNodeId: event.target.value },
},
}))}
/>
</label>
<div className="image-generation-scope-grid">
<label>
Steps
<input
type="checkbox"
checked={Boolean(imageGenerateDraft.purposes[purpose.id])}
onChange={(event) => updatePurpose(imageGeneratePlugin.pluginId, purpose.id, event.target.checked)}
type="number"
min={1}
max={100}
value={imageMakeConfig.providers.comfyui.steps ?? 20}
disabled={!imageMakeControlsEnabled}
onChange={(event) => setImageMakeConfig((current) => ({
...current,
providers: {
...current.providers,
comfyui: { ...current.providers.comfyui, steps: Number(event.target.value) },
},
}))}
/>
<span>
<strong>{purpose.label}</strong>
<small>{descriptions[purpose.id] ?? purpose.presetId}</small>
</span>
</label>
);
})}
</div>
</fieldset>
<div className="image-generation-settings-footer">
<span> AI </span>
<button
type="button"
className="send-btn"
disabled={busy === imageGeneratePlugin.pluginId}
onClick={() => void savePlugin(imageGeneratePlugin)}
>
{busy === imageGeneratePlugin.pluginId ? '保存中…' : '保存图片生成配置'}
</button>
<label>
CFG
<input
type="number"
min={0}
max={30}
step={0.5}
value={imageMakeConfig.providers.comfyui.cfg ?? 7}
disabled={!imageMakeControlsEnabled}
onChange={(event) => setImageMakeConfig((current) => ({
...current,
providers: {
...current.providers,
comfyui: { ...current.providers.comfyui, cfg: Number(event.target.value) },
},
}))}
/>
</label>
<label>
Sampler
<input
value={imageMakeConfig.providers.comfyui.sampler ?? 'euler'}
disabled={!imageMakeControlsEnabled}
onChange={(event) => setImageMakeConfig((current) => ({
...current,
providers: {
...current.providers,
comfyui: { ...current.providers.comfyui, sampler: event.target.value },
},
}))}
/>
</label>
<label>
Scheduler
<input
value={imageMakeConfig.providers.comfyui.scheduler ?? 'normal'}
disabled={!imageMakeControlsEnabled}
onChange={(event) => setImageMakeConfig((current) => ({
...current,
providers: {
...current.providers,
comfyui: { ...current.providers.comfyui, scheduler: event.target.value },
},
}))}
/>
</label>
<label>
<input
type="number"
min={2}
max={1800}
value={imageMakeConfig.providers.comfyui.timeoutSeconds ?? 600}
disabled={!imageMakeControlsEnabled}
onChange={(event) => setImageMakeConfig((current) => ({
...current,
providers: {
...current.providers,
comfyui: { ...current.providers.comfyui, timeoutSeconds: Number(event.target.value) },
},
}))}
/>
</label>
</div>
</div>
<div className="asset-plugin-footer">
<span className={`asset-status ${imageMakeConfig.providers.comfyui.enabled ? 'is-on' : ''}`}>
{imageMakeConfig.providers.comfyui.checkpoint || '未配置 checkpoint'}
</span>
<button
type="button"
className="send-btn"
disabled={busy === 'image-make-config' || !imageMakeControlsEnabled}
onClick={() => void saveImageMakeConfig()}
>
{busy === 'image-make-config' ? '保存中…' : '保存配置'}
</button>
</div>
</section>
</div>
</section>
</>
) : null}
<div className="model-center-section-title">
<h3></h3>
<p></p>
<p> Provider</p>
</div>
<div className="asset-plugin-grid asset-plugin-grid--balanced">
{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 <section className={`asset-plugin-card asset-plugin-card--${visual.accent}`} key={plugin.pluginId}>
<div className="asset-plugin-card-head">
@@ -229,29 +709,22 @@ export function AssetGatewayPage() {
</div>
<div className="asset-plugin-fields">
<label> Provider<select value={draft.provider} onChange={(event) => {
const provider = event.target.value;
updateDraft(plugin.pluginId, {
provider,
...(provider === 'image_make' ? { llmProviderKeyId: '', llmModel: '' } : {}),
});
updateDraft(plugin.pluginId, { provider: event.target.value });
}} disabled={!draft.enabled}>
<option value=""> Provider</option>
{plugin.providers.map((provider) => <option key={provider} value={provider}>{provider}</option>)}
</select></label>
{plugin.pluginId === 'asset-generate' && draft.provider === 'image_make' ? (
{plugin.supportsLlm ? (
<div className="asset-image-make-note">
image_make
{' '}
<Link to="/providers"></Link>
{' '}
Qwen VL
</div>
) : plugin.supportsLlm ? <>
<label> LLM<select value={draft.llmProviderKeyId} onChange={(event) => updateDraft(plugin.pluginId, { llmProviderKeyId: event.target.value, llmModel: '' })} disabled={!draft.enabled}>
<option value="">使 LLM</option>
{activeKeys.map((key) => <option key={key.id} value={key.id}>{key.name}</option>)}
</select></label>
<label><select value={draft.llmModel} onChange={(event) => updateDraft(plugin.pluginId, { llmModel: event.target.value })} disabled={!draft.enabled || !selectedKey}>
<option value=""></option>
{selectedKey?.models.map((model) => <option key={model} value={model}>{model}</option>)}
</select></label>
</> : <div className="asset-no-llm"> · LLM</div>}
) : (
<div className="asset-no-llm"> · LLM</div>
)}
{plugin.pluginId === 'asset-generate' && plugin.purposeCatalog?.length ? (
<fieldset className="asset-purpose-fieldset" disabled={!draft.enabled || draft.provider !== 'image_make'}>
<legend>AI </legend>
+24
View File
@@ -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<ImageMakeAdminConfigResponse> {
return portalFetch('/admin-api/image-make/config');
}
export async function updateImageMakeAdminConfig(
payload: Partial<ImageMakeAdminConfig> & {
providers?: Partial<ImageMakeAdminConfig['providers']> & {
aliyun_bailian?: Partial<ImageMakeAdminConfig['providers']['aliyun_bailian']> & { apiKey?: string };
};
},
): Promise<ImageMakeAdminConfigResponse & { ok?: boolean; message?: string }> {
return portalFetch('/admin-api/image-make/config', {
method: 'PATCH',
body: JSON.stringify(payload),
});
}
export async function getImageMakeRuntimeStatus(): Promise<ImageMakeRuntimeResponse> {
return portalFetch('/admin-api/image-make/runtime');
}
export async function getMindSpaceAdminConfig(): Promise<MindSpaceAdminConfig> {
const result = await portalFetch<{ config: MindSpaceAdminConfig }>('/admin-api/mindspace/config');
return result.config;
+26
View File
@@ -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;
+47
View File
@@ -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 = {