feat: make mode stage a full workspace
This commit is contained in:
parent
b977242dc7
commit
7ce4d4cdc2
@ -957,78 +957,99 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
||||
</div>
|
||||
) : (
|
||||
<div className="absolute inset-0 flex pt-[72px]">
|
||||
<div className="min-w-0 flex-1 border-r bg-white dark:bg-zinc-950">
|
||||
<div className="flex h-full flex-col">
|
||||
<div className="border-b p-4">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<div className="flex items-center gap-2 text-sm font-semibold">
|
||||
<Wand2 className="h-4 w-4 text-amber-500" />
|
||||
生图模式
|
||||
</div>
|
||||
<div className="mt-1 text-xs text-muted-foreground">
|
||||
先在本页选择生图模式,再使用上方 SKU / SPU 取图,右侧会自动进入对应图片槽位。
|
||||
</div>
|
||||
<div className="mt-2 inline-flex items-center rounded-full bg-muted px-2.5 py-1 text-[11px] text-muted-foreground">
|
||||
第 2 步 / 2 步
|
||||
<div className="flex h-full w-full bg-[#f5f7fb] dark:bg-zinc-950">
|
||||
<div className="w-[520px] shrink-0 border-r bg-white dark:bg-zinc-950">
|
||||
<div className="flex h-full flex-col">
|
||||
<div className="border-b p-4">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<div className="flex items-center gap-2 text-sm font-semibold">
|
||||
<Wand2 className="h-4 w-4 text-amber-500" />
|
||||
生图模式
|
||||
</div>
|
||||
<div className="mt-1 text-xs text-muted-foreground">
|
||||
先在本页选择生图模式,再使用上方 SKU / SPU 取图,右侧全屏工作区会自动进入对应图片槽位并展示结果。
|
||||
</div>
|
||||
<div className="mt-2 inline-flex items-center rounded-full bg-muted px-2.5 py-1 text-[11px] text-muted-foreground">
|
||||
第 2 步 / 2 步
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setCurrentAgentStage("workflow")}
|
||||
>
|
||||
<ArrowLeft className="mr-1 h-4 w-4" />
|
||||
返回上一步
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setCurrentAgentStage("workflow")}
|
||||
>
|
||||
<ArrowLeft className="mr-1 h-4 w-4" />
|
||||
返回上一步
|
||||
</Button>
|
||||
{!workflowProgress.isComplete ? (
|
||||
<div className="mt-3 rounded-xl border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-800">
|
||||
第一步尚未完成,你也可以在当前直接继续生图模式选择。
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
{!workflowProgress.isComplete ? (
|
||||
<div className="mt-3 rounded-xl border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-800">
|
||||
第一步尚未完成,你也可以在当前直接继续生图模式选择。
|
||||
<div className="min-h-0 flex-1 overflow-y-auto">
|
||||
<div className="space-y-4 p-4">
|
||||
<AgentWorkflowMaterialSelectionPanel
|
||||
inputs={workflowInputs}
|
||||
onInputsChange={setWorkflowInputs}
|
||||
materials={workflowMaterials}
|
||||
onMaterialsChange={setWorkflowMaterials}
|
||||
title="SKU / SPU 获取图片"
|
||||
description="第二步直接在这里搜索主 SKU / SPU 与搭配 SKU 图片,选中的图片会自动进入右侧对应产品槽位。"
|
||||
disabled={!selectedAgentPresetId}
|
||||
disabledHint="请先选择一个生图模式,系统才能把你选中的图片自动送入对应生图槽位。"
|
||||
/>
|
||||
<AgentPresetPanel
|
||||
embedded
|
||||
layout="inline"
|
||||
selectedPresetId={selectedAgentPresetId}
|
||||
onSelectPreset={setSelectedAgentPresetId}
|
||||
title="生图模式"
|
||||
description="先选择生图模式,再在上方搜索 SKU / SPU 图片并自动进入对应槽位"
|
||||
searchPlaceholder="搜索生图模式"
|
||||
publicTitle="公共生图模式"
|
||||
privateTitle="我的生图模式"
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="min-h-0 flex-1 overflow-y-auto">
|
||||
<div className="space-y-4 p-4">
|
||||
<AgentWorkflowMaterialSelectionPanel
|
||||
inputs={workflowInputs}
|
||||
onInputsChange={setWorkflowInputs}
|
||||
materials={workflowMaterials}
|
||||
onMaterialsChange={setWorkflowMaterials}
|
||||
title="SKU / SPU 获取图片"
|
||||
description="第二步直接在这里搜索主 SKU / SPU 与搭配 SKU 图片,选中的图片会自动进入右侧对应产品槽位。"
|
||||
disabled={!selectedAgentPresetId}
|
||||
disabledHint="请先选择一个生图模式,系统才能把你选中的图片自动送入对应生图槽位。"
|
||||
/>
|
||||
<AgentPresetPanel
|
||||
embedded
|
||||
layout="inline"
|
||||
selectedPresetId={selectedAgentPresetId}
|
||||
onSelectPreset={setSelectedAgentPresetId}
|
||||
title="生图模式"
|
||||
description="先选择生图模式,再在上方搜索 SKU / SPU 图片并自动进入对应槽位"
|
||||
searchPlaceholder="搜索生图模式"
|
||||
publicTitle="公共生图模式"
|
||||
privateTitle="我的生图模式"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="shrink-0 border-l bg-white dark:bg-zinc-950"
|
||||
style={{ width: FLOATING_PANEL_WIDTH }}
|
||||
>
|
||||
<div className="h-full overflow-hidden">
|
||||
<CanvasChatPanel
|
||||
chatId={`canvas-${id}`}
|
||||
onClose={() => {}}
|
||||
selectedAgentPresetId={isAgentFeatureEnabled ? selectedAgentPresetId : null}
|
||||
chatScope="agent"
|
||||
workflowContext={workflowContext}
|
||||
/>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex h-full flex-col">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<div className="text-sm font-semibold">执行工作区</div>
|
||||
<div className="mt-1 text-xs text-muted-foreground">
|
||||
这里不再是侧边对话框,而是全屏生图工作台。生成结果、槽位操作和补充指令都在这里完成。
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{selectedAgentPresetId ? (
|
||||
<div className="inline-flex items-center rounded-full bg-white px-3 py-1 text-xs font-medium text-foreground shadow-sm dark:bg-zinc-900">
|
||||
当前模式已选
|
||||
</div>
|
||||
) : (
|
||||
<div className="inline-flex items-center rounded-full bg-white px-3 py-1 text-xs text-muted-foreground shadow-sm dark:bg-zinc-900">
|
||||
请选择左侧生图模式
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="min-h-0 flex-1 overflow-hidden rounded-tl-[24px] border-t border-l bg-white shadow-[inset_0_1px_0_rgba(255,255,255,0.8)] dark:border-white/10 dark:bg-zinc-950">
|
||||
<CanvasChatPanel
|
||||
chatId={`canvas-${id}`}
|
||||
onClose={undefined}
|
||||
selectedAgentPresetId={isAgentFeatureEnabled ? selectedAgentPresetId : null}
|
||||
chatScope="agent"
|
||||
workflowContext={workflowContext}
|
||||
presentation="workspace"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -114,6 +114,7 @@ interface CanvasChatPanelProps {
|
||||
selectedAgentPresetId?: string | null;
|
||||
chatScope?: "canvas" | "agent";
|
||||
workflowContext?: AgentWorkflowComposerContext | null;
|
||||
presentation?: "panel" | "workspace";
|
||||
}
|
||||
|
||||
type VideoStoryboardStreamData =
|
||||
@ -1686,6 +1687,7 @@ export function CanvasChatPanel({
|
||||
selectedAgentPresetId,
|
||||
chatScope = "canvas",
|
||||
workflowContext = null,
|
||||
presentation = "panel",
|
||||
}: CanvasChatPanelProps) {
|
||||
const { data: session } = useSession();
|
||||
const [input, setInput] = useState("");
|
||||
@ -7882,9 +7884,15 @@ export function CanvasChatPanel({
|
||||
const fallbackTitle = String(projectTitle || "").trim();
|
||||
return fallbackTitle || "未命名画布";
|
||||
}, [activeSessionTitle, projectTitle]);
|
||||
const isWorkspacePresentation = presentation === "workspace";
|
||||
|
||||
return (
|
||||
<div className="relative flex h-full min-h-0 flex-col bg-white dark:bg-zinc-950">
|
||||
<div
|
||||
className={cn(
|
||||
"relative flex h-full min-h-0 flex-col",
|
||||
isWorkspacePresentation ? "bg-transparent" : "bg-white dark:bg-zinc-950"
|
||||
)}
|
||||
>
|
||||
{/* 全局遮罩提示 - 当处于画布选图模式时显示 */}
|
||||
{detailSlotPickerIndex !== null && (
|
||||
<div className="absolute inset-0 z-50 flex flex-col items-center justify-center bg-white dark:bg-zinc-950 animate-in fade-in duration-200">
|
||||
@ -7912,111 +7920,112 @@ export function CanvasChatPanel({
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* Header - Minimal with Icons */}
|
||||
<div className="z-20 flex items-center justify-between gap-3 p-4 pb-2">
|
||||
<div className="min-w-0 flex flex-1 items-center gap-2 pr-2">
|
||||
<span className="inline-flex h-5 w-5 items-center justify-center rounded-md bg-[#eef2f7] text-[#6b7280]">
|
||||
<MessageCircle className="h-3.5 w-3.5" />
|
||||
</span>
|
||||
<h2
|
||||
className="min-w-0 flex-1 truncate text-[14px] font-semibold leading-tight tracking-tight text-[#111827]"
|
||||
title={displayCanvasName}
|
||||
>
|
||||
{displayCanvasName}
|
||||
</h2>
|
||||
</div>
|
||||
<div className="flex shrink-0 items-center">
|
||||
<div className="flex items-center gap-1">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-8 w-8 rounded-full hover:bg-muted"
|
||||
onClick={createNewChatSession}
|
||||
title="新建聊天窗口"
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
</Button>
|
||||
<div className="relative" ref={sessionMenuRef}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className={cn(
|
||||
"h-8 w-8 rounded-full hover:bg-muted",
|
||||
isSessionMenuOpen && "bg-muted"
|
||||
)}
|
||||
onClick={() => setIsSessionMenuOpen((open) => !open)}
|
||||
title="聊天窗口记录"
|
||||
{!isWorkspacePresentation ? (
|
||||
<div className="z-20 flex items-center justify-between gap-3 p-4 pb-2">
|
||||
<div className="min-w-0 flex flex-1 items-center gap-2 pr-2">
|
||||
<span className="inline-flex h-5 w-5 items-center justify-center rounded-md bg-[#eef2f7] text-[#6b7280]">
|
||||
<MessageCircle className="h-3.5 w-3.5" />
|
||||
</span>
|
||||
<h2
|
||||
className="min-w-0 flex-1 truncate text-[14px] font-semibold leading-tight tracking-tight text-[#111827]"
|
||||
title={displayCanvasName}
|
||||
>
|
||||
<History className="h-4 w-4" />
|
||||
</Button>
|
||||
{isSessionMenuOpen && (
|
||||
<div className="absolute right-0 top-10 w-64 rounded-xl border bg-white dark:bg-zinc-950 shadow-xl p-2">
|
||||
<div className="px-2 py-1 text-xs text-muted-foreground">聊天窗口记录</div>
|
||||
<div className="max-h-56 overflow-y-auto space-y-1">
|
||||
{chatSessions.length === 0 ? (
|
||||
<div className="px-2 py-2 text-xs text-muted-foreground">暂无记录</div>
|
||||
) : (
|
||||
chatSessions.map((session, index) => {
|
||||
const isActive = session.id === activeChatId;
|
||||
const timeLabel = new Date(session.updatedAt).toLocaleTimeString([], {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
});
|
||||
return (
|
||||
<button
|
||||
key={session.id}
|
||||
type="button"
|
||||
className={cn(
|
||||
"w-full text-left rounded-lg px-2.5 py-2 transition-colors",
|
||||
isActive
|
||||
? "bg-primary/10 text-primary"
|
||||
: "hover:bg-muted"
|
||||
)}
|
||||
onClick={() => switchToSession(session.id)}
|
||||
>
|
||||
<div className="text-sm font-medium truncate">
|
||||
{session.title || `对话 ${index + 1}`}
|
||||
</div>
|
||||
<div className="text-[11px] text-muted-foreground mt-0.5">
|
||||
最近更新 {timeLabel}
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{displayCanvasName}
|
||||
</h2>
|
||||
</div>
|
||||
<div className="flex shrink-0 items-center">
|
||||
<div className="flex items-center gap-1">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-8 w-8 rounded-full hover:bg-muted"
|
||||
onClick={createNewChatSession}
|
||||
title="新建聊天窗口"
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
</Button>
|
||||
<div className="relative" ref={sessionMenuRef}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className={cn(
|
||||
"h-8 w-8 rounded-full hover:bg-muted",
|
||||
isSessionMenuOpen && "bg-muted"
|
||||
)}
|
||||
onClick={() => setIsSessionMenuOpen((open) => !open)}
|
||||
title="聊天窗口记录"
|
||||
>
|
||||
<History className="h-4 w-4" />
|
||||
</Button>
|
||||
{isSessionMenuOpen && (
|
||||
<div className="absolute right-0 top-10 w-64 rounded-xl border bg-white dark:bg-zinc-950 shadow-xl p-2">
|
||||
<div className="px-2 py-1 text-xs text-muted-foreground">聊天窗口记录</div>
|
||||
<div className="max-h-56 overflow-y-auto space-y-1">
|
||||
{chatSessions.length === 0 ? (
|
||||
<div className="px-2 py-2 text-xs text-muted-foreground">暂无记录</div>
|
||||
) : (
|
||||
chatSessions.map((session, index) => {
|
||||
const isActive = session.id === activeChatId;
|
||||
const timeLabel = new Date(session.updatedAt).toLocaleTimeString([], {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
});
|
||||
return (
|
||||
<button
|
||||
key={session.id}
|
||||
type="button"
|
||||
className={cn(
|
||||
"w-full text-left rounded-lg px-2.5 py-2 transition-colors",
|
||||
isActive
|
||||
? "bg-primary/10 text-primary"
|
||||
: "hover:bg-muted"
|
||||
)}
|
||||
onClick={() => switchToSession(session.id)}
|
||||
>
|
||||
<div className="text-sm font-medium truncate">
|
||||
{session.title || `对话 ${index + 1}`}
|
||||
</div>
|
||||
<div className="text-[11px] text-muted-foreground mt-0.5">
|
||||
最近更新 {timeLabel}
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-8 w-8 rounded-full hover:bg-muted"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
console.log("[CanvasChatPanel] File list button clicked");
|
||||
if (onOpenFileList) {
|
||||
onOpenFileList();
|
||||
}
|
||||
}}
|
||||
title="已生成文件列表"
|
||||
>
|
||||
<LayoutGrid className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-8 w-8 rounded-full hover:bg-muted"
|
||||
onClick={onClose}
|
||||
disabled={!onClose}
|
||||
title="收起聊天面板"
|
||||
>
|
||||
<ArrowRightToLine className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-8 w-8 rounded-full hover:bg-muted"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
console.log('[CanvasChatPanel] File list button clicked');
|
||||
if (onOpenFileList) {
|
||||
onOpenFileList();
|
||||
}
|
||||
}}
|
||||
title="已生成文件列表"
|
||||
>
|
||||
<LayoutGrid className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-8 w-8 rounded-full hover:bg-muted"
|
||||
onClick={onClose}
|
||||
disabled={!onClose}
|
||||
title="收起聊天面板"
|
||||
>
|
||||
<ArrowRightToLine className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{/* 消息列表 */}
|
||||
{isDetailEmptyState ? (
|
||||
@ -8025,7 +8034,8 @@ export function CanvasChatPanel({
|
||||
<div
|
||||
key={`${activeChatId}-${messageViewportKey}`}
|
||||
className={cn(
|
||||
"flex-1 min-h-0 overflow-y-auto overflow-x-hidden p-4",
|
||||
"flex-1 min-h-0 overflow-y-auto overflow-x-hidden",
|
||||
isWorkspacePresentation ? "p-6" : "p-4",
|
||||
isDetailGenerationMode && renderableVisibleMessages.length === 0
|
||||
? "pt-10"
|
||||
: "pt-4"
|
||||
@ -8659,7 +8669,14 @@ export function CanvasChatPanel({
|
||||
) : null}
|
||||
|
||||
{/* 底部输入区域 - 混合输入(图片标签+文字可自由混合) */}
|
||||
<div className="p-4 bg-white dark:bg-zinc-950 border-t">
|
||||
<div
|
||||
className={cn(
|
||||
"border-t",
|
||||
isWorkspacePresentation
|
||||
? "bg-white/92 p-5 backdrop-blur-sm dark:bg-zinc-950/92"
|
||||
: "bg-white p-4 dark:bg-zinc-950"
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className="relative flex flex-col bg-card rounded-3xl border transition-all duration-200 focus-within:border-primary/50 focus-within:bg-background"
|
||||
data-canvas-chat-input-anchor="true"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user