feat: improve Claw shortcut mode and toolbar docking
This commit is contained in:
parent
045e36b242
commit
80f6827375
@ -1140,6 +1140,8 @@ const FLOATING_PANEL_INSET = 16;
|
|||||||
const DOCKED_PANEL_INSET = 0;
|
const DOCKED_PANEL_INSET = 0;
|
||||||
const FLOATING_UI_GAP = 16;
|
const FLOATING_UI_GAP = 16;
|
||||||
const FLOATING_BOTTOM_INSET = 20;
|
const FLOATING_BOTTOM_INSET = 20;
|
||||||
|
const FLOATING_TOOLBAR_ESTIMATED_WIDTH = 680;
|
||||||
|
const FLOATING_TOOLBAR_SIDE_DOCK_CLEARANCE = 48;
|
||||||
const WORKFLOW_AGENT_CONTEXT_START = "【视频画布同步上下文】";
|
const WORKFLOW_AGENT_CONTEXT_START = "【视频画布同步上下文】";
|
||||||
const WORKFLOW_AGENT_CONTEXT_END = "【/视频画布同步上下文】";
|
const WORKFLOW_AGENT_CONTEXT_END = "【/视频画布同步上下文】";
|
||||||
const AGENT_RESULT_MODE_LABELS: Record<AgentGenerationResultModeType, string> =
|
const AGENT_RESULT_MODE_LABELS: Record<AgentGenerationResultModeType, string> =
|
||||||
@ -1988,6 +1990,7 @@ export default function CanvasEditorLegacyPage({ params }: PageProps) {
|
|||||||
const [videoCanvasInsertMenu, setVideoCanvasInsertMenu] =
|
const [videoCanvasInsertMenu, setVideoCanvasInsertMenu] =
|
||||||
useState<VideoCanvasInsertMenuState | null>(null);
|
useState<VideoCanvasInsertMenuState | null>(null);
|
||||||
const videoCanvasInsertMenuRef = useRef<HTMLDivElement | null>(null);
|
const videoCanvasInsertMenuRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
const floatingToolbarContentRef = useRef<HTMLDivElement | null>(null);
|
||||||
const [isVideoCanvasFileListOpen, setIsVideoCanvasFileListOpen] =
|
const [isVideoCanvasFileListOpen, setIsVideoCanvasFileListOpen] =
|
||||||
useState(false);
|
useState(false);
|
||||||
const [isVideoCanvasMarkerPanelOpen, setIsVideoCanvasMarkerPanelOpen] =
|
const [isVideoCanvasMarkerPanelOpen, setIsVideoCanvasMarkerPanelOpen] =
|
||||||
@ -1995,6 +1998,8 @@ export default function CanvasEditorLegacyPage({ params }: PageProps) {
|
|||||||
// 面板模式: 'chat' 聊天面板, 'fileList' 文件列表
|
// 面板模式: 'chat' 聊天面板, 'fileList' 文件列表
|
||||||
const [panelMode, setPanelMode] = useState<"chat" | "fileList">("chat");
|
const [panelMode, setPanelMode] = useState<"chat" | "fileList">("chat");
|
||||||
const [viewportWidth, setViewportWidth] = useState(0);
|
const [viewportWidth, setViewportWidth] = useState(0);
|
||||||
|
const [horizontalFloatingToolbarWidth, setHorizontalFloatingToolbarWidth] =
|
||||||
|
useState(0);
|
||||||
const [workflowInputs, setWorkflowInputs] = useState<AgentWorkflowInputs>({
|
const [workflowInputs, setWorkflowInputs] = useState<AgentWorkflowInputs>({
|
||||||
primarySku: "",
|
primarySku: "",
|
||||||
secondarySku: "",
|
secondarySku: "",
|
||||||
@ -3003,6 +3008,23 @@ export default function CanvasEditorLegacyPage({ params }: PageProps) {
|
|||||||
const panelOcclusionRight = isFloatingSidebarVisible
|
const panelOcclusionRight = isFloatingSidebarVisible
|
||||||
? resolvedPanelWidth + DOCKED_PANEL_INSET
|
? resolvedPanelWidth + DOCKED_PANEL_INSET
|
||||||
: 0;
|
: 0;
|
||||||
|
const visibleCanvasWidth =
|
||||||
|
viewportWidth > 0 ? Math.max(viewportWidth - panelOcclusionRight, 0) : 0;
|
||||||
|
const horizontalFloatingToolbarWidthForDocking =
|
||||||
|
horizontalFloatingToolbarWidth || FLOATING_TOOLBAR_ESTIMATED_WIDTH;
|
||||||
|
const shouldDockFloatingToolbarToSide =
|
||||||
|
!isVideoMode &&
|
||||||
|
isFloatingSidebarVisible &&
|
||||||
|
visibleCanvasWidth > 0 &&
|
||||||
|
visibleCanvasWidth <
|
||||||
|
horizontalFloatingToolbarWidthForDocking +
|
||||||
|
FLOATING_TOOLBAR_SIDE_DOCK_CLEARANCE * 2;
|
||||||
|
const floatingToolbarTooltipSide = shouldDockFloatingToolbarToSide
|
||||||
|
? "right"
|
||||||
|
: "top";
|
||||||
|
const floatingToolbarDropdownSide = shouldDockFloatingToolbarToSide
|
||||||
|
? "right"
|
||||||
|
: "bottom";
|
||||||
const floatingToolbarOffset = isFloatingSidebarVisible
|
const floatingToolbarOffset = isFloatingSidebarVisible
|
||||||
? -(panelOcclusionRight / 2)
|
? -(panelOcclusionRight / 2)
|
||||||
: 0;
|
: 0;
|
||||||
@ -3487,6 +3509,43 @@ export default function CanvasEditorLegacyPage({ params }: PageProps) {
|
|||||||
);
|
);
|
||||||
}, [viewportWidth]);
|
}, [viewportWidth]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof window === "undefined") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const toolbarElement = floatingToolbarContentRef.current;
|
||||||
|
if (!toolbarElement) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const measureHorizontalToolbar = () => {
|
||||||
|
if (toolbarElement.dataset.canvasToolbarOrientation !== "horizontal") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextWidth = Math.ceil(toolbarElement.getBoundingClientRect().width);
|
||||||
|
if (!Number.isFinite(nextWidth) || nextWidth <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setHorizontalFloatingToolbarWidth((currentWidth) =>
|
||||||
|
Math.abs(currentWidth - nextWidth) > 1 ? nextWidth : currentWidth
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
measureHorizontalToolbar();
|
||||||
|
|
||||||
|
const resizeObserver = new ResizeObserver(measureHorizontalToolbar);
|
||||||
|
resizeObserver.observe(toolbarElement);
|
||||||
|
window.addEventListener("resize", measureHorizontalToolbar);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
resizeObserver.disconnect();
|
||||||
|
window.removeEventListener("resize", measureHorizontalToolbar);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
!isFloatingPanelWidthStorageReady ||
|
!isFloatingPanelWidthStorageReady ||
|
||||||
@ -10720,17 +10779,38 @@ export default function CanvasEditorLegacyPage({ params }: PageProps) {
|
|||||||
|
|
||||||
{/* 5. Floating Toolbar (Bottom) */}
|
{/* 5. Floating Toolbar (Bottom) */}
|
||||||
<div
|
<div
|
||||||
className="-translate-x-1/2 absolute left-1/2 z-20"
|
className={cn(
|
||||||
|
"absolute z-20",
|
||||||
|
shouldDockFloatingToolbarToSide
|
||||||
|
? "-translate-y-1/2"
|
||||||
|
: "-translate-x-1/2 left-1/2"
|
||||||
|
)}
|
||||||
data-canvas-floating-toolbar="true"
|
data-canvas-floating-toolbar="true"
|
||||||
style={{ bottom: `${FLOATING_BOTTOM_INSET}px` }}
|
style={
|
||||||
|
shouldDockFloatingToolbarToSide
|
||||||
|
? { left: `${FLOATING_UI_GAP}px`, top: "50%" }
|
||||||
|
: { bottom: `${FLOATING_BOTTOM_INSET}px` }
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
data-canvas-toolbar-orientation={
|
||||||
|
shouldDockFloatingToolbarToSide ? "vertical" : "horizontal"
|
||||||
|
}
|
||||||
|
ref={floatingToolbarContentRef}
|
||||||
className={cn(
|
className={cn(
|
||||||
isVideoMode
|
isVideoMode
|
||||||
? VIDEO_CANVAS_TOOLBAR_CLASS
|
? VIDEO_CANVAS_TOOLBAR_CLASS
|
||||||
: "flex items-center gap-1 rounded-[24px] border bg-white px-2.5 py-1.5 shadow-2xl dark:bg-zinc-950"
|
: "border bg-white shadow-2xl dark:bg-zinc-950",
|
||||||
|
!isVideoMode &&
|
||||||
|
(shouldDockFloatingToolbarToSide
|
||||||
|
? "flex flex-col items-center gap-1 rounded-[24px] px-1.5 py-2.5"
|
||||||
|
: "flex items-center gap-1 rounded-[24px] px-2.5 py-1.5")
|
||||||
)}
|
)}
|
||||||
style={{ marginLeft: `${floatingToolbarOffset}px` }}
|
style={
|
||||||
|
shouldDockFloatingToolbarToSide
|
||||||
|
? undefined
|
||||||
|
: { marginLeft: `${floatingToolbarOffset}px` }
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
@ -10759,7 +10839,7 @@ export default function CanvasEditorLegacyPage({ params }: PageProps) {
|
|||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="top">
|
<TooltipContent side={floatingToolbarTooltipSide}>
|
||||||
{getCanvasToolbarItem(canvasToolbarConfig, "selectTool").label}{" "}
|
{getCanvasToolbarItem(canvasToolbarConfig, "selectTool").label}{" "}
|
||||||
(V)
|
(V)
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
@ -10786,7 +10866,7 @@ export default function CanvasEditorLegacyPage({ params }: PageProps) {
|
|||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="top">
|
<TooltipContent side={floatingToolbarTooltipSide}>
|
||||||
{
|
{
|
||||||
getCanvasToolbarItem(
|
getCanvasToolbarItem(
|
||||||
canvasToolbarConfig,
|
canvasToolbarConfig,
|
||||||
@ -10795,7 +10875,10 @@ export default function CanvasEditorLegacyPage({ params }: PageProps) {
|
|||||||
}
|
}
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<DropdownMenuContent align="start" side="bottom">
|
<DropdownMenuContent
|
||||||
|
align="start"
|
||||||
|
side={floatingToolbarDropdownSide}
|
||||||
|
>
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onSelect={() => handleAddAgentImageSlotNode()}
|
onSelect={() => handleAddAgentImageSlotNode()}
|
||||||
>
|
>
|
||||||
@ -10843,7 +10926,7 @@ export default function CanvasEditorLegacyPage({ params }: PageProps) {
|
|||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="top">
|
<TooltipContent side={floatingToolbarTooltipSide}>
|
||||||
{getCanvasToolbarItem(canvasToolbarConfig, "panTool").label} (H)
|
{getCanvasToolbarItem(canvasToolbarConfig, "panTool").label} (H)
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@ -10872,7 +10955,7 @@ export default function CanvasEditorLegacyPage({ params }: PageProps) {
|
|||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="top">
|
<TooltipContent side={floatingToolbarTooltipSide}>
|
||||||
{
|
{
|
||||||
getCanvasToolbarItem(canvasToolbarConfig, "saveCanvas")
|
getCanvasToolbarItem(canvasToolbarConfig, "saveCanvas")
|
||||||
.label
|
.label
|
||||||
@ -10903,7 +10986,7 @@ export default function CanvasEditorLegacyPage({ params }: PageProps) {
|
|||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="top">
|
<TooltipContent side={floatingToolbarTooltipSide}>
|
||||||
{
|
{
|
||||||
getCanvasToolbarItem(canvasToolbarConfig, "cloudFiles")
|
getCanvasToolbarItem(canvasToolbarConfig, "cloudFiles")
|
||||||
.label
|
.label
|
||||||
@ -10939,7 +11022,7 @@ export default function CanvasEditorLegacyPage({ params }: PageProps) {
|
|||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="top">
|
<TooltipContent side={floatingToolbarTooltipSide}>
|
||||||
{
|
{
|
||||||
getCanvasToolbarItem(canvasToolbarConfig, "markerPanel")
|
getCanvasToolbarItem(canvasToolbarConfig, "markerPanel")
|
||||||
.label
|
.label
|
||||||
@ -10976,7 +11059,7 @@ export default function CanvasEditorLegacyPage({ params }: PageProps) {
|
|||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="top">
|
<TooltipContent side={floatingToolbarTooltipSide}>
|
||||||
{
|
{
|
||||||
getCanvasToolbarItem(
|
getCanvasToolbarItem(
|
||||||
canvasToolbarConfig,
|
canvasToolbarConfig,
|
||||||
@ -11023,7 +11106,7 @@ export default function CanvasEditorLegacyPage({ params }: PageProps) {
|
|||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="top">
|
<TooltipContent side={floatingToolbarTooltipSide}>
|
||||||
{getCanvasToolbarItem(canvasToolbarConfig, "styleMode").label}
|
{getCanvasToolbarItem(canvasToolbarConfig, "styleMode").label}
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@ -11052,7 +11135,7 @@ export default function CanvasEditorLegacyPage({ params }: PageProps) {
|
|||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="top">
|
<TooltipContent side={floatingToolbarTooltipSide}>
|
||||||
{getCanvasToolbarItem(canvasToolbarConfig, "stylePro").label}
|
{getCanvasToolbarItem(canvasToolbarConfig, "stylePro").label}
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@ -11081,7 +11164,7 @@ export default function CanvasEditorLegacyPage({ params }: PageProps) {
|
|||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="top">
|
<TooltipContent side={floatingToolbarTooltipSide}>
|
||||||
{
|
{
|
||||||
getCanvasToolbarItem(canvasToolbarConfig, "batchProcessing")
|
getCanvasToolbarItem(canvasToolbarConfig, "batchProcessing")
|
||||||
.label
|
.label
|
||||||
@ -11112,7 +11195,7 @@ export default function CanvasEditorLegacyPage({ params }: PageProps) {
|
|||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="top">
|
<TooltipContent side={floatingToolbarTooltipSide}>
|
||||||
{
|
{
|
||||||
getCanvasToolbarItem(
|
getCanvasToolbarItem(
|
||||||
canvasToolbarConfig,
|
canvasToolbarConfig,
|
||||||
@ -11144,7 +11227,7 @@ export default function CanvasEditorLegacyPage({ params }: PageProps) {
|
|||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="top">
|
<TooltipContent side={floatingToolbarTooltipSide}>
|
||||||
{
|
{
|
||||||
getCanvasToolbarItem(
|
getCanvasToolbarItem(
|
||||||
canvasToolbarConfig,
|
canvasToolbarConfig,
|
||||||
@ -11173,7 +11256,7 @@ export default function CanvasEditorLegacyPage({ params }: PageProps) {
|
|||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="top">
|
<TooltipContent side={floatingToolbarTooltipSide}>
|
||||||
{
|
{
|
||||||
getCanvasToolbarItem(canvasToolbarConfig, "addVideoInput")
|
getCanvasToolbarItem(canvasToolbarConfig, "addVideoInput")
|
||||||
.label
|
.label
|
||||||
@ -11207,9 +11290,9 @@ export default function CanvasEditorLegacyPage({ params }: PageProps) {
|
|||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent
|
<DropdownMenuContent
|
||||||
align="center"
|
align={shouldDockFloatingToolbarToSide ? "start" : "center"}
|
||||||
className="w-[420px] rounded-2xl border bg-background p-0 shadow-2xl"
|
className="w-[420px] rounded-2xl border bg-background p-0 shadow-2xl"
|
||||||
side="top"
|
side={shouldDockFloatingToolbarToSide ? "right" : "top"}
|
||||||
>
|
>
|
||||||
<div className="border-b px-4 py-3">
|
<div className="border-b px-4 py-3">
|
||||||
<div className="font-semibold text-foreground text-sm">
|
<div className="font-semibold text-foreground text-sm">
|
||||||
@ -11306,7 +11389,7 @@ export default function CanvasEditorLegacyPage({ params }: PageProps) {
|
|||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="top">
|
<TooltipContent side={floatingToolbarTooltipSide}>
|
||||||
{
|
{
|
||||||
resolveCanvasToolbarItemWithFallback(
|
resolveCanvasToolbarItemWithFallback(
|
||||||
canvasToolbarConfig,
|
canvasToolbarConfig,
|
||||||
@ -11353,7 +11436,7 @@ export default function CanvasEditorLegacyPage({ params }: PageProps) {
|
|||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="top">
|
<TooltipContent side={floatingToolbarTooltipSide}>
|
||||||
{selectedImageNode
|
{selectedImageNode
|
||||||
? resolveCanvasToolbarItemWithFallback(
|
? resolveCanvasToolbarItemWithFallback(
|
||||||
canvasToolbarConfig,
|
canvasToolbarConfig,
|
||||||
@ -11398,7 +11481,7 @@ export default function CanvasEditorLegacyPage({ params }: PageProps) {
|
|||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="top">
|
<TooltipContent side={floatingToolbarTooltipSide}>
|
||||||
{getCanvasToolbarItem(canvasToolbarConfig, "model3d").label}
|
{getCanvasToolbarItem(canvasToolbarConfig, "model3d").label}
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@ -11422,7 +11505,7 @@ export default function CanvasEditorLegacyPage({ params }: PageProps) {
|
|||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="top">
|
<TooltipContent side={floatingToolbarTooltipSide}>
|
||||||
{
|
{
|
||||||
getCanvasToolbarItem(
|
getCanvasToolbarItem(
|
||||||
canvasToolbarConfig,
|
canvasToolbarConfig,
|
||||||
@ -11457,7 +11540,7 @@ export default function CanvasEditorLegacyPage({ params }: PageProps) {
|
|||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="top">
|
<TooltipContent side={floatingToolbarTooltipSide}>
|
||||||
{
|
{
|
||||||
getCanvasToolbarItem(
|
getCanvasToolbarItem(
|
||||||
canvasToolbarConfig,
|
canvasToolbarConfig,
|
||||||
@ -11490,7 +11573,7 @@ export default function CanvasEditorLegacyPage({ params }: PageProps) {
|
|||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="top">
|
<TooltipContent side={floatingToolbarTooltipSide}>
|
||||||
{
|
{
|
||||||
getCanvasToolbarItem(
|
getCanvasToolbarItem(
|
||||||
canvasToolbarConfig,
|
canvasToolbarConfig,
|
||||||
|
|||||||
@ -362,6 +362,17 @@ function normalizeClawdComposerText(value: string) {
|
|||||||
.trim();
|
.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildClawdSkillTagPrompt(tag: Pick<CanvasSkillTag, "name" | "prompt"> | null) {
|
||||||
|
if (!tag) return "";
|
||||||
|
|
||||||
|
const tagName = String(tag.name || "").trim() || "快捷标签";
|
||||||
|
const tagPrompt = String(tag.prompt || "").trim();
|
||||||
|
return [
|
||||||
|
`【快捷标签:${tagName}】`,
|
||||||
|
tagPrompt || `用户选择了「${tagName}」快捷标签,请按该标签对应能力处理。`,
|
||||||
|
].join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
type ClawdComposerSubmissionSnapshot = {
|
type ClawdComposerSubmissionSnapshot = {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
canvasId?: string;
|
canvasId?: string;
|
||||||
@ -1591,6 +1602,8 @@ interface CanvasChatSession {
|
|||||||
title: string;
|
title: string;
|
||||||
createdAt: number;
|
createdAt: number;
|
||||||
updatedAt: number;
|
updatedAt: number;
|
||||||
|
clawdSkillTagId?: string | null;
|
||||||
|
clawdSkillTagPromptApplied?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CANVAS_CHAT_QUERY_PARAM = "chat";
|
const CANVAS_CHAT_QUERY_PARAM = "chat";
|
||||||
@ -2054,6 +2067,51 @@ export function CanvasChatPanel({
|
|||||||
[activeChatId, chatSessions.length, releaseSendGuard, replaceChatSessionQuery]
|
[activeChatId, chatSessions.length, releaseSendGuard, replaceChatSessionQuery]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const setSessionClawdSkillTag = useCallback(
|
||||||
|
(
|
||||||
|
chatSessionId: string,
|
||||||
|
tagId: string | null,
|
||||||
|
options?: { promptApplied?: boolean }
|
||||||
|
) => {
|
||||||
|
if (!chatSessionId) return;
|
||||||
|
const normalizedTagId = String(tagId || "").trim() || null;
|
||||||
|
setChatSessions((prevSessions) =>
|
||||||
|
prevSessions.map((session) =>
|
||||||
|
session.id === chatSessionId
|
||||||
|
? {
|
||||||
|
...session,
|
||||||
|
clawdSkillTagId: normalizedTagId,
|
||||||
|
clawdSkillTagPromptApplied:
|
||||||
|
normalizedTagId === null
|
||||||
|
? false
|
||||||
|
: options?.promptApplied === true,
|
||||||
|
updatedAt: Date.now(),
|
||||||
|
}
|
||||||
|
: session
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const markSessionClawdSkillTagPromptApplied = useCallback(
|
||||||
|
(chatSessionId: string, tagId: string) => {
|
||||||
|
if (!chatSessionId || !tagId) return;
|
||||||
|
setChatSessions((prevSessions) =>
|
||||||
|
prevSessions.map((session) =>
|
||||||
|
session.id === chatSessionId && session.clawdSkillTagId === tagId
|
||||||
|
? {
|
||||||
|
...session,
|
||||||
|
clawdSkillTagPromptApplied: true,
|
||||||
|
updatedAt: Date.now(),
|
||||||
|
}
|
||||||
|
: session
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
const handleTypewriterActiveChange = useCallback((typingKey: string, active: boolean) => {
|
const handleTypewriterActiveChange = useCallback((typingKey: string, active: boolean) => {
|
||||||
setActiveTypingSections((prev) => {
|
setActiveTypingSections((prev) => {
|
||||||
if (active) {
|
if (active) {
|
||||||
@ -2351,13 +2409,19 @@ export function CanvasChatPanel({
|
|||||||
const saved = localStorage.getItem(sessionStorageKey);
|
const saved = localStorage.getItem(sessionStorageKey);
|
||||||
const parsed = saved ? (JSON.parse(saved) as CanvasChatSession[]) : [];
|
const parsed = saved ? (JSON.parse(saved) as CanvasChatSession[]) : [];
|
||||||
|
|
||||||
const sanitized = parsed
|
const sanitized: CanvasChatSession[] = parsed
|
||||||
.filter((session) => session?.id)
|
.filter((session) => session?.id)
|
||||||
.map((session) => ({
|
.map((session): CanvasChatSession => ({
|
||||||
id: session.id,
|
id: session.id,
|
||||||
title: (session.title || "").trim() || "新对话",
|
title: (session.title || "").trim() || "新对话",
|
||||||
createdAt: Number(session.createdAt) || now,
|
createdAt: Number(session.createdAt) || now,
|
||||||
updatedAt: Number(session.updatedAt) || now,
|
updatedAt: Number(session.updatedAt) || now,
|
||||||
|
clawdSkillTagId:
|
||||||
|
typeof (session as CanvasChatSession).clawdSkillTagId === "string"
|
||||||
|
? (session as CanvasChatSession).clawdSkillTagId || null
|
||||||
|
: null,
|
||||||
|
clawdSkillTagPromptApplied:
|
||||||
|
(session as CanvasChatSession).clawdSkillTagPromptApplied === true,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let nextSessions = sanitized;
|
let nextSessions = sanitized;
|
||||||
@ -3584,6 +3648,17 @@ export function CanvasChatPanel({
|
|||||||
() => canvasSkillTags.find((tag) => tag.id === selectedSkillTagId) || null,
|
() => canvasSkillTags.find((tag) => tag.id === selectedSkillTagId) || null,
|
||||||
[canvasSkillTags, selectedSkillTagId]
|
[canvasSkillTags, selectedSkillTagId]
|
||||||
);
|
);
|
||||||
|
const activeChatSession = useMemo(
|
||||||
|
() => chatSessions.find((session) => session.id === activeChatId) || null,
|
||||||
|
[activeChatId, chatSessions]
|
||||||
|
);
|
||||||
|
const activeClawdSkillTag = useMemo(() => {
|
||||||
|
const tagId = String(activeChatSession?.clawdSkillTagId || "").trim();
|
||||||
|
if (!tagId) return null;
|
||||||
|
return canvasSkillTags.find((tag) => tag.id === tagId) || null;
|
||||||
|
}, [activeChatSession?.clawdSkillTagId, canvasSkillTags]);
|
||||||
|
const activeClawdSkillTagPromptApplied =
|
||||||
|
activeChatSession?.clawdSkillTagPromptApplied === true;
|
||||||
const selectedAgentPreset = useMemo(
|
const selectedAgentPreset = useMemo(
|
||||||
() => (agentPresetResponse?.success && agentPresetResponse.data ? agentPresetResponse.data : null),
|
() => (agentPresetResponse?.success && agentPresetResponse.data ? agentPresetResponse.data : null),
|
||||||
[agentPresetResponse]
|
[agentPresetResponse]
|
||||||
@ -3600,6 +3675,10 @@ export function CanvasChatPanel({
|
|||||||
() => getCanvasSkillIconComponent(selectedSkillTag?.iconName),
|
() => getCanvasSkillIconComponent(selectedSkillTag?.iconName),
|
||||||
[selectedSkillTag?.iconName]
|
[selectedSkillTag?.iconName]
|
||||||
);
|
);
|
||||||
|
const ActiveClawdSkillTagIcon = useMemo(
|
||||||
|
() => getCanvasSkillIconComponent(activeClawdSkillTag?.iconName),
|
||||||
|
[activeClawdSkillTag?.iconName]
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!selectedSkillTagId) return;
|
if (!selectedSkillTagId) return;
|
||||||
@ -4571,21 +4650,21 @@ export function CanvasChatPanel({
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const handleSelectGenerationMode = useCallback(
|
const handleSelectGenerationMode = useCallback(
|
||||||
(mode: CanvasComposerMode) => {
|
(mode: CanvasComposerMode): string | null => {
|
||||||
hiddenPromptRef.current = null;
|
hiddenPromptRef.current = null;
|
||||||
autoContextRef.current = null;
|
autoContextRef.current = null;
|
||||||
if (mode === "clawd" && !isClawdModeEnabled) {
|
if (mode === "clawd" && !isClawdModeEnabled) {
|
||||||
toast.error(`${CLAW_MODE_LABEL} 未开启`);
|
toast.error(`${CLAW_MODE_LABEL} 未开启`);
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
if (mode === "agent") {
|
if (mode === "agent") {
|
||||||
if (!effectiveAgentEnabled) {
|
if (!effectiveAgentEnabled) {
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (mode === "detail" && !canUseDetailGeneration) {
|
if (mode === "detail" && !canUseDetailGeneration) {
|
||||||
toast.error("AI详情生成功能未开启");
|
toast.error("AI详情生成功能未开启");
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const preferredImageModelId =
|
const preferredImageModelId =
|
||||||
@ -4600,7 +4679,7 @@ export function CanvasChatPanel({
|
|||||||
: null;
|
: null;
|
||||||
if (mode === "image" && !preferredImageModelId) {
|
if (mode === "image" && !preferredImageModelId) {
|
||||||
toast.error("当前没有可用的图像模型");
|
toast.error("当前没有可用的图像模型");
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const preferredVideoModelId =
|
const preferredVideoModelId =
|
||||||
@ -4610,21 +4689,22 @@ export function CanvasChatPanel({
|
|||||||
: null;
|
: null;
|
||||||
if (mode === "video" && !preferredVideoModelId) {
|
if (mode === "video" && !preferredVideoModelId) {
|
||||||
toast.error("当前没有可用的视频模型");
|
toast.error("当前没有可用的视频模型");
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isSwitchingMode = mode !== activeComposerMode;
|
const isSwitchingMode = mode !== activeComposerMode;
|
||||||
|
let targetChatSessionId = activeChatId;
|
||||||
if (
|
if (
|
||||||
isSwitchingMode &&
|
isSwitchingMode &&
|
||||||
(getSendGuard(activeChatId).inFlight || isClawdSubmitting || isDetailGenerating)
|
(getSendGuard(activeChatId).inFlight || isClawdSubmitting || isDetailGenerating)
|
||||||
) {
|
) {
|
||||||
toast.message("当前对话正在处理中,完成后再切换模式");
|
toast.message("当前对话正在处理中,完成后再切换模式");
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSwitchingMode && hasActiveChatContext()) {
|
if (isSwitchingMode && hasActiveChatContext()) {
|
||||||
const modeLabel = getCanvasComposerModeSessionLabel(mode);
|
const modeLabel = getCanvasComposerModeSessionLabel(mode);
|
||||||
startNewChatSession({
|
targetChatSessionId = startNewChatSession({
|
||||||
title: `${modeLabel} 对话`,
|
title: `${modeLabel} 对话`,
|
||||||
toastMessage: `已为 ${modeLabel} 新建聊天窗口`,
|
toastMessage: `已为 ${modeLabel} 新建聊天窗口`,
|
||||||
});
|
});
|
||||||
@ -4635,7 +4715,7 @@ export function CanvasChatPanel({
|
|||||||
|
|
||||||
if (mode === "clawd") {
|
if (mode === "clawd") {
|
||||||
setIsDetailGenerationMode(false);
|
setIsDetailGenerationMode(false);
|
||||||
return;
|
return targetChatSessionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mode === "agent") {
|
if (mode === "agent") {
|
||||||
@ -4648,7 +4728,7 @@ export function CanvasChatPanel({
|
|||||||
currentModelIdRef.current = preferredAgentModelId;
|
currentModelIdRef.current = preferredAgentModelId;
|
||||||
setSelectedModelId(preferredAgentModelId);
|
setSelectedModelId(preferredAgentModelId);
|
||||||
setIsDetailGenerationMode(false);
|
setIsDetailGenerationMode(false);
|
||||||
return;
|
return targetChatSessionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mode === "detail") {
|
if (mode === "detail") {
|
||||||
@ -4657,7 +4737,7 @@ export function CanvasChatPanel({
|
|||||||
setSelectedModelId(detailModelId);
|
setSelectedModelId(detailModelId);
|
||||||
setIsDetailGenerationMode(true);
|
setIsDetailGenerationMode(true);
|
||||||
setIsDetailWorkbenchCollapsed(true);
|
setIsDetailWorkbenchCollapsed(true);
|
||||||
return;
|
return targetChatSessionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mode === "image") {
|
if (mode === "image") {
|
||||||
@ -4665,13 +4745,14 @@ export function CanvasChatPanel({
|
|||||||
currentModelIdRef.current = nextImageModelId;
|
currentModelIdRef.current = nextImageModelId;
|
||||||
setSelectedModelId(nextImageModelId);
|
setSelectedModelId(nextImageModelId);
|
||||||
setIsDetailGenerationMode(false);
|
setIsDetailGenerationMode(false);
|
||||||
return;
|
return targetChatSessionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nextVideoModelId = preferredVideoModelId || "";
|
const nextVideoModelId = preferredVideoModelId || "";
|
||||||
currentModelIdRef.current = nextVideoModelId;
|
currentModelIdRef.current = nextVideoModelId;
|
||||||
setSelectedModelId(nextVideoModelId);
|
setSelectedModelId(nextVideoModelId);
|
||||||
setIsDetailGenerationMode(false);
|
setIsDetailGenerationMode(false);
|
||||||
|
return targetChatSessionId;
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
canUseDetailGeneration,
|
canUseDetailGeneration,
|
||||||
@ -4707,6 +4788,42 @@ export function CanvasChatPanel({
|
|||||||
isClawdModeEnabled,
|
isClawdModeEnabled,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const handleSelectCanvasSkillTag = useCallback(
|
||||||
|
(tag: CanvasSkillTag) => {
|
||||||
|
if (!isClawdModeEnabled) {
|
||||||
|
toast.error(`${CLAW_MODE_LABEL} 未开启`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getSendGuard(activeChatId).inFlight || isClawdSubmitting || isDetailGenerating) {
|
||||||
|
toast.message("当前对话正在处理中,完成后再切换模式");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetChatSessionId = handleSelectGenerationMode("clawd");
|
||||||
|
if (!targetChatSessionId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setSessionClawdSkillTag(targetChatSessionId, tag.id, {
|
||||||
|
promptApplied: false,
|
||||||
|
});
|
||||||
|
window.setTimeout(() => {
|
||||||
|
setSelectedSkillTagId(tag.id);
|
||||||
|
const editor = textareaRef.current as HTMLDivElement | null;
|
||||||
|
editor?.focus();
|
||||||
|
}, 0);
|
||||||
|
},
|
||||||
|
[
|
||||||
|
activeChatId,
|
||||||
|
getSendGuard,
|
||||||
|
handleSelectGenerationMode,
|
||||||
|
isClawdModeEnabled,
|
||||||
|
isClawdSubmitting,
|
||||||
|
isDetailGenerating,
|
||||||
|
setSessionClawdSkillTag,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!effectiveAgentEnabled || typeof window === "undefined") return;
|
if (!effectiveAgentEnabled || typeof window === "undefined") return;
|
||||||
const targetWindow = window as CanvasAgentPendingRequestWindow;
|
const targetWindow = window as CanvasAgentPendingRequestWindow;
|
||||||
@ -9535,26 +9652,51 @@ export function CanvasChatPanel({
|
|||||||
const textContent = normalizeClawdComposerText(input);
|
const textContent = normalizeClawdComposerText(input);
|
||||||
const tableFilesSnapshot = [...clawdTableFiles];
|
const tableFilesSnapshot = [...clawdTableFiles];
|
||||||
const hasTableFiles = tableFilesSnapshot.length > 0;
|
const hasTableFiles = tableFilesSnapshot.length > 0;
|
||||||
|
const clawdSkillTagForRequest = activeClawdSkillTag || selectedSkillTag;
|
||||||
|
const shouldIncludeClawdSkillTagPrompt = Boolean(
|
||||||
|
clawdSkillTagForRequest && !activeClawdSkillTagPromptApplied
|
||||||
|
);
|
||||||
|
const clawdSkillTagPrompt = shouldIncludeClawdSkillTagPrompt
|
||||||
|
? buildClawdSkillTagPrompt(clawdSkillTagForRequest)
|
||||||
|
: "";
|
||||||
|
const hasPendingClawdSkillTagPrompt = Boolean(clawdSkillTagPrompt);
|
||||||
const attachmentsSnapshot = [...attachments];
|
const attachmentsSnapshot = [...attachments];
|
||||||
const imageAttachmentsSnapshot = attachmentsSnapshot
|
const imageAttachmentsSnapshot = attachmentsSnapshot
|
||||||
.map((attachment) => sanitizeChatAttachment(attachment))
|
.map((attachment) => sanitizeChatAttachment(attachment))
|
||||||
.filter((attachment): attachment is PickedObjectAttachment => Boolean(attachment));
|
.filter((attachment): attachment is PickedObjectAttachment => Boolean(attachment));
|
||||||
const hasImageAttachments = imageAttachmentsSnapshot.length > 0;
|
const hasImageAttachments = imageAttachmentsSnapshot.length > 0;
|
||||||
|
|
||||||
if (!textContent && !hasTableFiles && !hasImageAttachments) {
|
if (
|
||||||
|
!textContent &&
|
||||||
|
!hasTableFiles &&
|
||||||
|
!hasImageAttachments &&
|
||||||
|
!hasPendingClawdSkillTagPrompt
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseRequestMessage =
|
const baseRequestMessage =
|
||||||
textContent || getClawdDefaultPrompt(hasTableFiles, hasImageAttachments);
|
textContent ||
|
||||||
|
(clawdSkillTagForRequest && hasPendingClawdSkillTagPrompt
|
||||||
|
? `请按「${clawdSkillTagForRequest.name}」快捷功能处理。`
|
||||||
|
: getClawdDefaultPrompt(hasTableFiles, hasImageAttachments));
|
||||||
const imageReferenceText = buildClawdImageReferenceText(
|
const imageReferenceText = buildClawdImageReferenceText(
|
||||||
imageAttachmentsSnapshot
|
imageAttachmentsSnapshot
|
||||||
);
|
);
|
||||||
const requestMessage = [baseRequestMessage, imageReferenceText]
|
const requestMessage = [
|
||||||
|
baseRequestMessage,
|
||||||
|
clawdSkillTagPrompt,
|
||||||
|
imageReferenceText,
|
||||||
|
]
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join("\n\n");
|
.join("\n\n");
|
||||||
|
const optimisticBaseMessage =
|
||||||
|
textContent ||
|
||||||
|
(clawdSkillTagForRequest && hasPendingClawdSkillTagPrompt
|
||||||
|
? `快捷功能:${clawdSkillTagForRequest.name}`
|
||||||
|
: baseRequestMessage);
|
||||||
const optimisticMessageText = [
|
const optimisticMessageText = [
|
||||||
buildClawdTableDisplayText(baseRequestMessage, tableFilesSnapshot),
|
buildClawdTableDisplayText(optimisticBaseMessage, tableFilesSnapshot),
|
||||||
buildClawdImageDisplayText(imageAttachmentsSnapshot),
|
buildClawdImageDisplayText(imageAttachmentsSnapshot),
|
||||||
]
|
]
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
@ -9563,6 +9705,8 @@ export function CanvasChatPanel({
|
|||||||
chatId: activeChatId,
|
chatId: activeChatId,
|
||||||
mode: "clawd",
|
mode: "clawd",
|
||||||
text: requestMessage,
|
text: requestMessage,
|
||||||
|
skillTagId: clawdSkillTagForRequest?.id || null,
|
||||||
|
skillTagPrompt: clawdSkillTagPrompt,
|
||||||
tableFiles: tableFilesSnapshot
|
tableFiles: tableFilesSnapshot
|
||||||
.map((file) => `${file.name}:${file.size}:${file.lastModified}`)
|
.map((file) => `${file.name}:${file.size}:${file.lastModified}`)
|
||||||
.sort(),
|
.sort(),
|
||||||
@ -9578,7 +9722,7 @@ export function CanvasChatPanel({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const nextProcessingHint = shouldShowClawdProcessingHint(
|
const nextProcessingHint = shouldShowClawdProcessingHint(
|
||||||
textContent,
|
[textContent, clawdSkillTagPrompt].filter(Boolean).join("\n\n"),
|
||||||
hasTableFiles,
|
hasTableFiles,
|
||||||
hasImageAttachments
|
hasImageAttachments
|
||||||
)
|
)
|
||||||
@ -9615,6 +9759,9 @@ export function CanvasChatPanel({
|
|||||||
startedAt: clawdSubmissionStartedAt,
|
startedAt: clawdSubmissionStartedAt,
|
||||||
submittedTexts: [
|
submittedTexts: [
|
||||||
textContent,
|
textContent,
|
||||||
|
clawdSkillTagForRequest?.name || "",
|
||||||
|
clawdSkillTagForRequest?.prompt || "",
|
||||||
|
clawdSkillTagPrompt,
|
||||||
baseRequestMessage,
|
baseRequestMessage,
|
||||||
requestMessage,
|
requestMessage,
|
||||||
optimisticMessageText,
|
optimisticMessageText,
|
||||||
@ -9637,6 +9784,7 @@ export function CanvasChatPanel({
|
|||||||
});
|
});
|
||||||
setAttachments([]);
|
setAttachments([]);
|
||||||
setClawdTableFiles([]);
|
setClawdTableFiles([]);
|
||||||
|
setSelectedSkillTagId(null);
|
||||||
clearSelection();
|
clearSelection();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -9666,6 +9814,13 @@ export function CanvasChatPanel({
|
|||||||
throw new Error(responseData?.error || `${CLAW_MODE_LABEL} 请求失败`);
|
throw new Error(responseData?.error || `${CLAW_MODE_LABEL} 请求失败`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (clawdSkillTagPrompt && clawdSkillTagForRequest) {
|
||||||
|
markSessionClawdSkillTagPromptApplied(
|
||||||
|
activeChatId,
|
||||||
|
clawdSkillTagForRequest.id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const returnedMessages = Array.isArray(responseData?.messages)
|
const returnedMessages = Array.isArray(responseData?.messages)
|
||||||
? (responseData.messages as ChatMessage[])
|
? (responseData.messages as ChatMessage[])
|
||||||
: [];
|
: [];
|
||||||
@ -9711,6 +9866,9 @@ export function CanvasChatPanel({
|
|||||||
toast.error(error instanceof Error ? error.message : `${CLAW_MODE_LABEL} 请求失败`);
|
toast.error(error instanceof Error ? error.message : `${CLAW_MODE_LABEL} 请求失败`);
|
||||||
setClawdTableFiles(tableFilesSnapshot);
|
setClawdTableFiles(tableFilesSnapshot);
|
||||||
setAttachments(attachmentsSnapshot);
|
setAttachments(attachmentsSnapshot);
|
||||||
|
if (clawdSkillTagForRequest && hasPendingClawdSkillTagPrompt) {
|
||||||
|
setSelectedSkillTagId(clawdSkillTagForRequest.id);
|
||||||
|
}
|
||||||
const editableDiv = textareaRef.current as HTMLDivElement | null;
|
const editableDiv = textareaRef.current as HTMLDivElement | null;
|
||||||
const currentEditorText = (editableDiv?.textContent || "")
|
const currentEditorText = (editableDiv?.textContent || "")
|
||||||
.replace(/\u200b/g, " ")
|
.replace(/\u200b/g, " ")
|
||||||
@ -10042,9 +10200,11 @@ export function CanvasChatPanel({
|
|||||||
chatScope,
|
chatScope,
|
||||||
rawChatId,
|
rawChatId,
|
||||||
input,
|
input,
|
||||||
attachments,
|
attachments,
|
||||||
clawdTableFiles,
|
clawdTableFiles,
|
||||||
hasAgentUploadSlots,
|
activeClawdSkillTag,
|
||||||
|
activeClawdSkillTagPromptApplied,
|
||||||
|
hasAgentUploadSlots,
|
||||||
hasWorkflowContext,
|
hasWorkflowContext,
|
||||||
selectedSkillTag,
|
selectedSkillTag,
|
||||||
selectedAgentPreset,
|
selectedAgentPreset,
|
||||||
@ -10081,8 +10241,9 @@ export function CanvasChatPanel({
|
|||||||
clearRestoredClawdComposerText,
|
clearRestoredClawdComposerText,
|
||||||
shouldBlockDuplicateSend,
|
shouldBlockDuplicateSend,
|
||||||
status,
|
status,
|
||||||
mutate,
|
mutate,
|
||||||
buildHistoryMessagesUrl,
|
markSessionClawdSkillTagPromptApplied,
|
||||||
|
buildHistoryMessagesUrl,
|
||||||
workflowContext,
|
workflowContext,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@ -10713,7 +10874,9 @@ export function CanvasChatPanel({
|
|||||||
const hasEnoughAgentUploads = !hasAgentUploadSlots || attachments.length >= agentRequiredUploadCount;
|
const hasEnoughAgentUploads = !hasAgentUploadSlots || attachments.length >= agentRequiredUploadCount;
|
||||||
const composerPlaceholder = isWorkspacePresentation
|
const composerPlaceholder = isWorkspacePresentation
|
||||||
? isClawdMode
|
? isClawdMode
|
||||||
? `输入要交给 ${CLAW_MODE_LABEL} 的问题,可上传图片或表格`
|
? activeClawdSkillTag
|
||||||
|
? `继续向 ${CLAW_MODE_LABEL} 提问,当前使用「${activeClawdSkillTag.name}」`
|
||||||
|
: `输入要交给 ${CLAW_MODE_LABEL} 的问题,可上传图片或表格`
|
||||||
: isDetailGenerationMode
|
: isDetailGenerationMode
|
||||||
? "可选补充:风格、情绪氛围、禁用元素、期望生成张数(如:生成8张)"
|
? "可选补充:风格、情绪氛围、禁用元素、期望生成张数(如:生成8张)"
|
||||||
: isVideoWorkspace
|
: isVideoWorkspace
|
||||||
@ -10724,7 +10887,9 @@ export function CanvasChatPanel({
|
|||||||
? "可选补充本次生图要求,例如:高级感、暖灰色、近景特写、生成3张。"
|
? "可选补充本次生图要求,例如:高级感、暖灰色、近景特写、生成3张。"
|
||||||
: "可选补充本次生成要求,例如:更高级、更生活化、暖调灯光。"
|
: "可选补充本次生成要求,例如:更高级、更生活化、暖调灯光。"
|
||||||
: isClawdMode
|
: isClawdMode
|
||||||
? `输入要交给 ${CLAW_MODE_LABEL} 的问题,可上传图片或表格`
|
? activeClawdSkillTag
|
||||||
|
? `继续向 ${CLAW_MODE_LABEL} 提问,当前使用「${activeClawdSkillTag.name}」`
|
||||||
|
: `输入要交给 ${CLAW_MODE_LABEL} 的问题,可上传图片或表格`
|
||||||
: isDetailGenerationMode
|
: isDetailGenerationMode
|
||||||
? "可选补充:风格、情绪氛围、禁用元素、期望生成张数(如:生成8张)"
|
? "可选补充:风格、情绪氛围、禁用元素、期望生成张数(如:生成8张)"
|
||||||
: isVideoWorkspace
|
: isVideoWorkspace
|
||||||
@ -10749,7 +10914,12 @@ export function CanvasChatPanel({
|
|||||||
? Math.min(100, Math.round((detailProgress.current / detailProgress.total) * 100))
|
? Math.min(100, Math.round((detailProgress.current / detailProgress.total) * 100))
|
||||||
: 0;
|
: 0;
|
||||||
const canSend = isClawdMode
|
const canSend = isClawdMode
|
||||||
? Boolean(input.trim() || attachments.length > 0 || clawdTableFiles.length > 0) && !isClawdSubmitting
|
? Boolean(
|
||||||
|
input.trim() ||
|
||||||
|
attachments.length > 0 ||
|
||||||
|
clawdTableFiles.length > 0 ||
|
||||||
|
(activeClawdSkillTag && !activeClawdSkillTagPromptApplied)
|
||||||
|
) && !isClawdSubmitting
|
||||||
: isDetailGenerationMode
|
: isDetailGenerationMode
|
||||||
? detailValidationIssues.length === 0 && !isDetailGenerating
|
? detailValidationIssues.length === 0 && !isDetailGenerating
|
||||||
: hasInputOrAttachments &&
|
: hasInputOrAttachments &&
|
||||||
@ -11332,6 +11502,42 @@ export function CanvasChatPanel({
|
|||||||
>
|
>
|
||||||
{displayCanvasName}
|
{displayCanvasName}
|
||||||
</h2>
|
</h2>
|
||||||
|
{isClawdMode && activeClawdSkillTag ? (
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"inline-flex max-w-[172px] shrink-0 items-center gap-1.5 rounded-full border px-2.5 py-1 text-[11px] font-medium leading-none",
|
||||||
|
isVideoWorkspace
|
||||||
|
? "border-border/60 bg-background/80 text-foreground/80"
|
||||||
|
: "border-violet-200 bg-violet-50 text-violet-700 dark:border-violet-400/30 dark:bg-violet-500/15 dark:text-violet-200"
|
||||||
|
)}
|
||||||
|
title={
|
||||||
|
activeClawdSkillTagPromptApplied
|
||||||
|
? `当前 Claw 对话正在使用「${activeClawdSkillTag.name}」快捷标签`
|
||||||
|
: `首次发送会带入「${activeClawdSkillTag.name}」快捷标签提示词`
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{activeClawdSkillTag.iconUrl ? (
|
||||||
|
<img
|
||||||
|
src={buildPreviewImageUrl(activeClawdSkillTag.iconUrl, 32)}
|
||||||
|
alt={activeClawdSkillTag.name}
|
||||||
|
className="h-3.5 w-3.5 shrink-0 rounded-sm object-cover"
|
||||||
|
loading="lazy"
|
||||||
|
decoding="async"
|
||||||
|
onError={(event) => {
|
||||||
|
if (event.currentTarget.src !== activeClawdSkillTag.iconUrl) {
|
||||||
|
event.currentTarget.src = activeClawdSkillTag.iconUrl || "";
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<ActiveClawdSkillTagIcon className="h-3.5 w-3.5 shrink-0" />
|
||||||
|
)}
|
||||||
|
<span className="truncate">{activeClawdSkillTag.name}</span>
|
||||||
|
<span className="shrink-0 text-[10px] opacity-70">
|
||||||
|
{activeClawdSkillTagPromptApplied ? "使用中" : "待带入"}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex shrink-0 items-center">
|
<div className="flex shrink-0 items-center">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
@ -11625,11 +11831,7 @@ export function CanvasChatPanel({
|
|||||||
<button
|
<button
|
||||||
key={tag.id}
|
key={tag.id}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => {
|
onClick={() => handleSelectCanvasSkillTag(tag)}
|
||||||
setSelectedSkillTagId(tag.id);
|
|
||||||
const editor = textareaRef.current as HTMLDivElement | null;
|
|
||||||
editor?.focus();
|
|
||||||
}}
|
|
||||||
className={cn(
|
className={cn(
|
||||||
"inline-flex h-[34px] max-w-full items-center justify-center gap-1.5 rounded-full border px-3.5 text-[13px] font-medium leading-none transition-colors",
|
"inline-flex h-[34px] max-w-full items-center justify-center gap-1.5 rounded-full border px-3.5 text-[13px] font-medium leading-none transition-colors",
|
||||||
isActive
|
isActive
|
||||||
@ -13080,7 +13282,10 @@ export function CanvasChatPanel({
|
|||||||
: "min-h-[132px]"
|
: "min-h-[132px]"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{!isWorkspacePresentation && !isDetailGenerationMode && selectedSkillTag && (
|
{!isWorkspacePresentation &&
|
||||||
|
!isDetailGenerationMode &&
|
||||||
|
!isClawdMode &&
|
||||||
|
selectedSkillTag ? (
|
||||||
<div className="mb-2 flex">
|
<div className="mb-2 flex">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@ -13103,7 +13308,7 @@ export function CanvasChatPanel({
|
|||||||
<X className="h-3.5 w-3.5 text-[#9ca3af] dark:text-zinc-400" />
|
<X className="h-3.5 w-3.5 text-[#9ca3af] dark:text-zinc-400" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
) : null}
|
||||||
{!isDetailGenerationMode && hasWorkflowContext && workflowContext && (
|
{!isDetailGenerationMode && hasWorkflowContext && workflowContext && (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user