feat: surface granular image scope settings

This commit is contained in:
john
2026-07-20 09:29:42 +08:00
parent 427a31ba21
commit 1969071cf4
2 changed files with 185 additions and 1 deletions
+85 -1
View File
@@ -46,6 +46,10 @@ export function AssetGatewayPage() {
if (right.pluginId === 'asset-generate') return 1;
return left.pluginId.localeCompare(right.pluginId);
}) ?? [], [config]);
const imageGeneratePlugin = config?.plugins.find((plugin) => plugin.pluginId === 'asset-generate') ?? null;
const imageGenerateDraft = imageGeneratePlugin
? drafts[imageGeneratePlugin.pluginId] ?? draftFromPlugin(imageGeneratePlugin)
: null;
const load = useCallback(async () => {
setLoading(true);
@@ -129,8 +133,88 @@ export function AssetGatewayPage() {
<span>{config.enabled ? '已启用' : '已关闭'}</span>
</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>
<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}>
<input
type="checkbox"
checked={Boolean(imageGenerateDraft.purposes[purpose.id])}
onChange={(event) => updatePurpose(imageGeneratePlugin.pluginId, purpose.id, event.target.checked)}
/>
<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>
</div>
</section>
) : null}
<div className="model-center-section-title">
<h3></h3>
<p></p>
</div>
<div className="asset-plugin-grid asset-plugin-grid--balanced">
{orderedPlugins.map((plugin) => {
{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' };
+100
View File
@@ -1005,6 +1005,96 @@ body,
margin-bottom: 18px;
}
.image-generation-settings {
display: grid;
gap: 16px;
margin-bottom: 22px;
padding: 20px;
border: 1px solid color-mix(in srgb, #9374ff 62%, var(--color-border));
border-radius: var(--radius-lg);
background: linear-gradient(145deg, rgba(106, 76, 196, .16), var(--color-bg-surface) 48%);
box-shadow: 0 12px 34px rgba(0, 0, 0, .14);
}
.image-generation-settings-head,
.image-generation-settings-footer {
display: flex;
align-items: center;
justify-content: space-between;
gap: 18px;
}
.image-generation-settings-head h3 {
margin: 5px 0 6px;
font-size: 18px;
}
.image-generation-settings-head p,
.image-generation-settings-footer span {
margin: 0;
color: var(--color-text-muted);
font-size: 12px;
line-height: 1.5;
}
.image-generation-provider {
display: grid;
max-width: 360px;
gap: 6px;
color: var(--color-text-muted);
font-size: 11px;
}
.image-generation-scope-fieldset {
min-width: 0;
margin: 0;
padding: 14px;
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
}
.image-generation-scope-fieldset legend {
padding: 0 6px;
color: var(--color-text-muted);
font-size: 11px;
}
.image-generation-scope-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
}
.image-generation-scope-item {
display: flex;
align-items: flex-start;
gap: 10px;
padding: 12px;
border: 1px solid color-mix(in srgb, var(--color-border) 82%, transparent);
border-radius: var(--radius-sm);
background: rgba(255, 255, 255, .025);
}
.image-generation-scope-item input {
margin-top: 3px;
}
.image-generation-scope-item span {
display: grid;
gap: 4px;
}
.image-generation-scope-item strong {
color: var(--color-text);
font-size: 13px;
}
.image-generation-scope-item small {
color: var(--color-text-muted);
font-size: 11px;
line-height: 1.4;
}
.asset-plugin-card {
display: flex;
flex-direction: column;
@@ -1463,6 +1553,16 @@ body,
grid-template-columns: 1fr;
}
.image-generation-settings-head,
.image-generation-settings-footer {
align-items: flex-start;
flex-direction: column;
}
.image-generation-scope-grid {
grid-template-columns: 1fr;
}
.asset-plugin-footer {
align-items: flex-start;
flex-direction: column;