feat(canvas): default image composer to direct generation

This commit is contained in:
134 2026-10-22 18:58:52 +08:00
parent 76bf04c221
commit 34e6459fe8

View File

@ -6025,6 +6025,9 @@ export function CanvasChatPanel({
const activeComposerMode = unifiedImageAgent && (rawComposerMode === "image" || rawComposerMode === "video")
? "agent" : rawComposerMode;
const isImageAgentComposer = unifiedImageAgent && activeComposerMode === "agent";
const [imageAgentEnabled, setImageAgentEnabled] = useState(false);
const imageAgentEnabledRef = useRef(imageAgentEnabled);
imageAgentEnabledRef.current = imageAgentEnabled;
const isVideoToolbarDark = isVideoWorkspace || activeComposerMode === "video";
const isClawdMode = activeComposerMode === "clawd" && isClawdModeEnabled;
const activeChatSessionMode = getCanvasChatSessionMode(
@ -6652,6 +6655,32 @@ export function CanvasChatPanel({
() => videoOnlyModels.find((model) => model.selectable !== false)?.id || null,
[videoOnlyModels]
);
useEffect(() => {
if (!unifiedImageAgent || imageAgentEnabled) return;
const selectedModel = allModels.find((model) => model.id === selectedModelId);
if (isCanvasImageModel(selectedModel)) return;
const fallbackImageModelId = [
lastImageModelId,
firstImageModelId,
...visualModels.map((model) => model.id),
].find((modelId) => {
const model = allModels.find((item) => item.id === modelId);
return Boolean(modelId && modelId !== "auto" && isCanvasImageModel(model) && !isCanvasVideoModel(model));
});
if (!fallbackImageModelId) return;
currentModelIdRef.current = fallbackImageModelId;
setSelectedModelId(fallbackImageModelId);
}, [
allModels,
firstImageModelId,
imageAgentEnabled,
isCanvasImageModel,
isCanvasVideoModel,
lastImageModelId,
selectedModelId,
unifiedImageAgent,
visualModels,
]);
const activeModeLabel =
activeComposerMode === "clawd"
? CLAW_MODE_LABEL
@ -7940,7 +7969,13 @@ export function CanvasChatPanel({
contextPayload: capturedContextPayload,
lastMessage: request.messages.at(-1),
});
const effectiveComposerMode = activeComposerModeRef.current || activeComposerMode;
const activeRequestComposerMode = activeComposerModeRef.current || activeComposerMode;
const effectiveComposerMode =
unifiedImageAgent &&
activeRequestComposerMode === "agent" &&
!imageAgentEnabledRef.current
? "image"
: activeRequestComposerMode;
const effectiveAgentEntryMode = agentEntryModeRef.current;
const hiddenPrompt = hiddenPromptRef.current || undefined;
const autoContext = autoContextRef.current || undefined;
@ -9727,7 +9762,13 @@ export function CanvasChatPanel({
isValidChatHttpUrl(getCanvasImageNodeUrl(node)),
);
const effectiveComposerMode = activeComposerModeRef.current || activeComposerMode;
const activeRequestComposerMode = activeComposerModeRef.current || activeComposerMode;
const effectiveComposerMode =
unifiedImageAgent &&
activeRequestComposerMode === "agent" &&
!imageAgentEnabledRef.current
? "image"
: activeRequestComposerMode;
const shouldIsolateDirectModeContext = effectiveComposerMode !== "agent";
const shouldPreserveCanvasControlContext =
shouldBypassCanvasImagePointsPrecheckForAgentControl({
@ -9737,7 +9778,7 @@ export function CanvasChatPanel({
hasSkillTag: Boolean(selectedSkillTag),
hasWorkflowContext,
isDetailGenerationMode,
agentEnabled: effectiveAgentEnabled,
agentEnabled: effectiveAgentEnabled && imageAgentEnabled,
});
const selectedAgentSlotCountForContext = nodes.filter(
(node) => String(node.type) === "agent-slot" && selectedNodeIdSet.has(node.id)
@ -9751,7 +9792,7 @@ export function CanvasChatPanel({
hasSkillTag: Boolean(selectedSkillTag),
hasWorkflowContext,
isDetailGenerationMode,
agentEnabled: effectiveAgentEnabled,
agentEnabled: effectiveAgentEnabled && imageAgentEnabled,
});
const MAX_CONTEXT_IMAGES = 12;
const MAX_AGENT_CANDIDATE_NODES = 30;
@ -12259,16 +12300,16 @@ export function CanvasChatPanel({
]);
const ensureImagePointsBeforeSend = useCallback(() => {
if (isVideoModel || activeComposerMode === "agent") return true;
if (isVideoModel || (activeComposerMode === "agent" && imageAgentEnabled)) return true;
if (
shouldBypassCanvasImagePointsPrecheckForAgentControl({
text: input,
activeComposerMode,
activeComposerMode: isImageAgentComposer && !imageAgentEnabled ? "image" : activeComposerMode,
attachmentCount: attachments.length,
hasSkillTag: Boolean(selectedSkillTag),
hasWorkflowContext,
isDetailGenerationMode,
agentEnabled: effectiveAgentEnabled,
agentEnabled: effectiveAgentEnabled && imageAgentEnabled,
})
) {
return true;
@ -12314,6 +12355,7 @@ export function CanvasChatPanel({
attachments.length,
currentModel?.imageGenConfig,
effectiveAgentEnabled,
imageAgentEnabled,
hasWorkflowContext,
imageParams?.imageCount,
imageParams?.imageSize,
@ -12325,21 +12367,22 @@ export function CanvasChatPanel({
membershipSummary?.pointsSystemConfig?.enabled,
membershipSummary?.summary?.currentCredits,
selectedSkillTag,
isImageAgentComposer,
]);
const estimatedPointsCost = useMemo(() => {
if (membershipSummary?.isHighVip) return null;
if (membershipSummary?.pointsSystemConfig?.enabled !== true) return null;
if (activeComposerMode === "agent") return null;
if (activeComposerMode === "agent" && imageAgentEnabled) return null;
if (
shouldBypassCanvasImagePointsPrecheckForAgentControl({
text: input,
activeComposerMode,
activeComposerMode: isImageAgentComposer && !imageAgentEnabled ? "image" : activeComposerMode,
attachmentCount: attachments.length,
hasSkillTag: Boolean(selectedSkillTag),
hasWorkflowContext,
isDetailGenerationMode,
agentEnabled: effectiveAgentEnabled,
agentEnabled: effectiveAgentEnabled && imageAgentEnabled,
})
) {
return null;
@ -12382,6 +12425,7 @@ export function CanvasChatPanel({
attachments.length,
currentModel?.imageGenConfig,
effectiveAgentEnabled,
imageAgentEnabled,
hasWorkflowContext,
imageParams?.imageCount,
imageParams?.imageSize,
@ -12391,6 +12435,7 @@ export function CanvasChatPanel({
membershipSummary?.isHighVip,
membershipSummary?.pointsSystemConfig?.enabled,
selectedSkillTag,
isImageAgentComposer,
videoParams?.duration,
videoParams?.resolution,
]);
@ -12401,7 +12446,7 @@ export function CanvasChatPanel({
sensitiveInput?: OpenClawSensitiveInput
) => {
const supplement = typeof overrideInput === "string" ? overrideInput.trim() : input.trim();
if (isCanvasAgentRunning && !isClawdMode) {
if (isCanvasAgentRunning && !isClawdMode && imageAgentEnabled) {
if (supplement && await canvasAgentTask.control("steer", supplement)) {
setInput("");
const editor = textareaRef.current as HTMLDivElement | null;
@ -13048,7 +13093,13 @@ export function CanvasChatPanel({
if (!textContent && hasWorkflowContext && workflowContext?.summaryText) {
textContent = `请基于当前工作流设定生成方案:${workflowContext.summaryText}`;
}
const effectiveComposerMode = activeComposerModeRef.current || activeComposerMode;
const activeRequestComposerMode = activeComposerModeRef.current || activeComposerMode;
const effectiveComposerMode =
unifiedImageAgent &&
activeRequestComposerMode === "agent" &&
!imageAgentEnabledRef.current
? "image"
: activeRequestComposerMode;
const effectiveAgentEntryMode = agentEntryModeRef.current || agentEntryMode;
if (!textContent && hasAgentUploadSlots && selectedAgentPreset) {
textContent = `请按「${selectedAgentPreset.name}」风格化配置顺序处理产品图1、产品图2和模特图。`;
@ -13066,7 +13117,7 @@ export function CanvasChatPanel({
hasSkillTag: Boolean(selectedSkillTag),
hasWorkflowContext,
isDetailGenerationMode,
agentEnabled: effectiveAgentEnabled,
agentEnabled: effectiveAgentEnabled && imageAgentEnabled,
});
const shouldShowOptimisticImageGeneration =
!shouldTreatAsCanvasAgentControlIntent &&
@ -13247,6 +13298,8 @@ export function CanvasChatPanel({
canvasAgentTask.control,
activeChatId,
activeComposerMode,
unifiedImageAgent,
imageAgentEnabled,
agentEntryMode,
agentRequiredUploadCount,
canvasId,
@ -18775,7 +18828,10 @@ export function CanvasChatPanel({
>
{(effectiveAgentEnabled || unifiedImageAgent) ? (
<DropdownMenuItem
onClick={() => handleSelectGenerationMode("agent")}
onClick={() => {
setImageAgentEnabled(true);
handleSelectGenerationMode("agent");
}}
className={getComposerModeMenuItemClass("agent")}
>
<CanvasToolbarIcon
@ -18858,6 +18914,64 @@ export function CanvasChatPanel({
</DropdownMenuContent>
</DropdownMenu>
) : null}
{unifiedImageAgent && activeComposerMode === "agent" ? (
<Button
type="button"
variant="ghost"
size="icon"
className={cn(
"h-8 w-8 shrink-0 rounded-full transition-colors",
imageAgentEnabled
? "text-[#5b6472] hover:bg-[#f3f4f6] hover:text-[#20242a] dark:text-zinc-300 dark:hover:bg-white/10"
: "bg-[#eeeeef] text-[#20242a] hover:bg-[#e2e3e5] dark:bg-white/10 dark:text-zinc-100 dark:hover:bg-white/15"
)}
onClick={() => {
if (imageAgentEnabled) {
const selectedImageModel = allModels.find(
(model) => model.id === selectedModelId
);
const fallbackImageModelId = [
lastImageModelId,
firstImageModelId,
...visualModels.map((model) => model.id),
].find((modelId) => {
const model = allModels.find((item) => item.id === modelId);
return Boolean(
modelId &&
modelId !== "auto" &&
isCanvasImageModel(model) &&
!isCanvasVideoModel(model)
);
});
if (!isCanvasImageModel(selectedImageModel)) {
if (!fallbackImageModelId) {
toast.error("当前没有可用的生图模型");
return;
}
currentModelIdRef.current = fallbackImageModelId;
setSelectedModelId(fallbackImageModelId);
}
setImageAgentEnabled(false);
} else {
setImageAgentEnabled(true);
}
}}
disabled={isSubmitting}
title={
imageAgentEnabled
? "关闭 Agent,直接使用生图模型"
: "启用 Agent 任务处理"
}
aria-label={
imageAgentEnabled
? "关闭 Agent,直接使用生图模型"
: "启用 Agent 任务处理"
}
aria-pressed={imageAgentEnabled}
>
{imageAgentEnabled ? <X className="h-4 w-4" /> : <Bot className="h-4 w-4" />}
</Button>
) : null}
{isVideoModel ? (
<div className="min-w-0 flex-1 overflow-x-auto scrollbar-hide">