diff --git a/app/(admin)/settings/admin-settings-client.tsx b/app/(admin)/settings/admin-settings-client.tsx index cb4b45db9..fce8c581e 100644 --- a/app/(admin)/settings/admin-settings-client.tsx +++ b/app/(admin)/settings/admin-settings-client.tsx @@ -192,6 +192,8 @@ import { type CanvasToolbarConfig, type CanvasToolbarItemKey, DEFAULT_CANVAS_TOOLBAR_ITEMS, + MAX_SELECTED_IMAGE_INLINE_ACTION_COUNT, + MIN_SELECTED_IMAGE_INLINE_ACTION_COUNT, defaultCanvasToolbarConfig, getDefaultCanvasToolbarItem, normalizeCanvasToolbarConfig, @@ -3691,6 +3693,7 @@ export function AdminSettingsClient() { return { ...prev, canvasToolbarConfig: { + ...normalized, items: { ...normalized.items, [key]: { @@ -12468,6 +12471,54 @@ export function AdminSettingsClient() { 恢复全部默认 +
+
+ +
+ 控制画布中选中图片后,快捷弹窗主条直接显示几个功能按钮;超出的功能自动收进“更多”菜单。 +
+
+ { + const normalized = + normalizeCanvasToolbarConfig( + siteSettings.canvasToolbarConfig + ); + const parsed = Number(event.target.value); + const nextValue = Number.isFinite(parsed) + ? Math.min( + MAX_SELECTED_IMAGE_INLINE_ACTION_COUNT, + Math.max( + MIN_SELECTED_IMAGE_INLINE_ACTION_COUNT, + Math.round(parsed) + ) + ) + : normalized.selectedImageInlineActionCount; + setSiteSettings({ + ...siteSettings, + canvasToolbarConfig: { + ...normalized, + selectedImageInlineActionCount: nextValue, + }, + }); + }} + type="number" + value={ + normalizeCanvasToolbarConfig( + siteSettings.canvasToolbarConfig + ).selectedImageInlineActionCount + } + /> +
{Array.from( new Set( DEFAULT_CANVAS_TOOLBAR_ITEMS.map( diff --git a/app/api/site-settings/route.ts b/app/api/site-settings/route.ts index 7d9bbcb5b..9e50c0c46 100644 --- a/app/api/site-settings/route.ts +++ b/app/api/site-settings/route.ts @@ -1894,11 +1894,13 @@ const updateSchema = z.object({ .optional(), canvasToolbarConfig: z .object({ + selectedImageInlineActionCount: z.number().int().min(3).max(12).optional(), items: z .record( z.object({ label: z.string().trim().max(40).optional(), iconUrl: z.string().trim().max(2000).optional(), + enabled: z.boolean().optional(), }) ) .optional(), diff --git a/components/canvas/nodes/ImageNode.tsx b/components/canvas/nodes/ImageNode.tsx index 962825fd0..b8e23c9fa 100644 --- a/components/canvas/nodes/ImageNode.tsx +++ b/components/canvas/nodes/ImageNode.tsx @@ -170,6 +170,7 @@ import { getScaledNodeContentStyle, } from "@/lib/canvas/node-content-scale"; import { + getSelectedImageInlineActionCount, getCanvasToolbarItem, isCanvasToolbarItemEnabled, type CanvasToolbarConfig, @@ -1180,6 +1181,47 @@ export function ImageNode({ ); const imageLayerSplitEnabled = isLayerSplitEnabled && imageLayerSplitEnabledByToolbar; + const selectedImageInlineActionCount = + getSelectedImageInlineActionCount(canvasToolbarConfig); + const selectedImageToolbarActionCandidates = [ + { key: "pickObject", enabled: imagePickObjectEnabled }, + { key: "enhance", enabled: imageEnhanceEnabled }, + { key: "removeBackground", enabled: imageRemoveBackgroundEnabled }, + { key: "aiWashQuick", enabled: imageAiWashQuickEnabled }, + { key: "textEdit", enabled: imageTextEditEnabled }, + { key: "boxCutout", enabled: imageBoxCutoutEnabled }, + { key: "localEdit", enabled: imageLocalEditEnabled }, + { key: "removeWatermark", enabled: imageRemoveWatermarkEnabled }, + { key: "resize", enabled: imageResizeEnabled }, + { key: "multiAngle", enabled: imageMultiAngleEnabled }, + { key: "generate3dModel", enabled: imageGenerate3dModelEnabled }, + { key: "generate3dWorld", enabled: imageGenerate3dWorldEnabled }, + { key: "layerSplit", enabled: imageLayerSplitEnabled }, + { key: "extend", enabled: imageExtendEnabled }, + { key: "crop", enabled: imageCropEnabled }, + { key: "adjust", enabled: imageAdjustEnabled }, + { key: "flipRotate", enabled: imageFlipRotateEnabled }, + { key: "promptRegenerate", enabled: imagePromptRegenerateEnabled }, + { key: "note", enabled: imageNoteEnabled }, + ] as const; + type SelectedImageToolbarActionKey = + (typeof selectedImageToolbarActionCandidates)[number]["key"]; + const enabledSelectedImageToolbarActions = + selectedImageToolbarActionCandidates.filter((action) => action.enabled); + const selectedImageInlineActionKeys = new Set( + enabledSelectedImageToolbarActions + .slice(0, selectedImageInlineActionCount) + .map((action) => action.key) + ); + const selectedImageMoreActionKeys = new Set( + enabledSelectedImageToolbarActions + .slice(selectedImageInlineActionCount) + .map((action) => action.key) + ); + const isSelectedImageActionInline = (key: SelectedImageToolbarActionKey) => + selectedImageInlineActionKeys.has(key); + const isSelectedImageActionInMore = (key: SelectedImageToolbarActionKey) => + selectedImageMoreActionKeys.has(key); const hasHoverImageMorePrimaryActions = imageRemoveBackgroundEnabled || imageRemoveWatermarkEnabled || @@ -1201,29 +1243,12 @@ export function ImageNode({ imageNodeMoreEnabled && (hasHoverImageMorePrimaryActions || hasHoverImageMoreSecondaryActions); const hasSelectedImageMorePrimaryActions = - imageBoxCutoutEnabled || - imageLocalEditEnabled || - imageRemoveWatermarkEnabled || - imageResizeEnabled || - imageMultiAngleEnabled || - imageGenerate3dModelEnabled || - imageGenerate3dWorldEnabled || - imageLayerSplitEnabled || - imageExtendEnabled || - imageCropEnabled || - imageAdjustEnabled || - imageFlipRotateEnabled || - imagePromptRegenerateEnabled || - imageNoteEnabled; + selectedImageMoreActionKeys.size > 0; const hasSelectedImageMoreMenu = hasSelectedImageMorePrimaryActions || (imageNodeMoreEnabled && imageDeleteEnabled); const hasSelectedImagePrimaryToolbarActions = - imagePickObjectEnabled || - imageEnhanceEnabled || - imageRemoveBackgroundEnabled || - imageTextEditEnabled || - imageAiWashQuickEnabled; + selectedImageInlineActionKeys.size > 0; const hasSelectedImageSecondaryToolbarActions = hasSelectedImageMoreMenu || imageDownloadEnabled; const contentScale = useMemo( @@ -8648,7 +8673,7 @@ export function ImageNode({ typeof document !== "undefined" && createPortal(
event.stopPropagation()} onMouseDown={(event) => event.stopPropagation()} @@ -8659,26 +8684,24 @@ export function ImageNode({ width: aiWashQuickPanelPosition.width, maxHeight: aiWashQuickPanelPosition.maxHeight, overflowY: "auto", - scrollbarWidth: "thin", + scrollbarWidth: "none", }} > -
-
- +
+
+ -
-
- {aiWashQuickModeLabel} -
+
+ {aiWashQuickModeLabel}
{aiWashConfigLoading ? ( -
+
正在加载 AI洗图 配置...
) : null} -
-
-
-
- +
+
+
+
+ 目标
- + {getAiWashStatusLabel(aiWashQuickStatus)}
@@ -8712,7 +8735,7 @@ export function ImageNode({ }} value={selectedAiWashQuickPreset?.id || aiWashQuickPresetId} > - + @@ -8724,26 +8747,26 @@ export function ImageNode({ {selectedAiWashQuickPreset?.description ? ( -
+
{selectedAiWashQuickPreset.description}
) : null}
-
-
+
+
参数
{shouldShowAiWashQuickModelSelector ? ( -
-