diff --git a/components/canvas/CanvasChatPanel.tsx b/components/canvas/CanvasChatPanel.tsx index 04b5154fa..a03975983 100644 --- a/components/canvas/CanvasChatPanel.tsx +++ b/components/canvas/CanvasChatPanel.tsx @@ -2,7 +2,7 @@ import { useChat } from "@ai-sdk/react"; import { DefaultChatTransport } from "ai"; -import { useEffect, useRef, useState, useCallback, useMemo, type ClipboardEvent as ReactClipboardEvent } from "react"; +import { useEffect, useRef, useState, useCallback, useMemo, type ClipboardEvent as ReactClipboardEvent, type MouseEvent as ReactMouseEvent } from "react"; import { useSession } from "next-auth/react"; import { usePathname, useRouter, useSearchParams } from "next/navigation"; import { createPortal } from "react-dom"; @@ -1222,6 +1222,86 @@ function extractOpenClawActionsFromMarkdown(content: string) { }; } +function splitOpenClawMarkdownTableCells(line: string) { + return String(line || "") + .trim() + .replace(/^\|/, "") + .replace(/\|$/, "") + .split(/(? + cell + .replace(/\\\|/g, "|") + .replace(/\*\*(.*?)\*\*/g, "$1") + .replace(/`([^`]+)`/g, "$1") + .replace(/\s+/g, "") + .trim() + ); +} + +function isOpenClawRoutingDiagnosticTable(tableLines: string[]) { + if (tableLines.length < 3) return false; + + const headerCells = splitOpenClawMarkdownTableCells(tableLines[0] || ""); + const hasDiagnosticHeader = + headerCells.some((cell) => cell.includes("已尝试")) && + headerCells.some((cell) => cell.includes("结果")); + if (!hasDiagnosticHeader) return false; + + const bodyText = tableLines + .slice(2) + .map((line) => splitOpenClawMarkdownTableCells(line).join(" ")) + .join(" "); + + return ( + bodyText.includes("标准品类识别") || + bodyText.includes("市场分析准备") || + bodyText.includes("建议示例") + ); +} + +function stripOpenClawLowValueDiagnosticTables(content: string) { + const normalized = String(content || "").replace(/\r\n/g, "\n"); + if (!normalized.includes("|")) return normalized.trim(); + + const lines = normalized.split("\n"); + const keptLines: string[] = []; + let inCodeFence = false; + + for (let index = 0; index < lines.length; index += 1) { + const line = lines[index] || ""; + if (/^\s*```/.test(line)) { + inCodeFence = !inCodeFence; + keptLines.push(line); + continue; + } + + if ( + !inCodeFence && + index < lines.length - 1 && + isLikelyMarkdownTableRow(line) && + isMarkdownTableSeparatorRow(lines[index + 1] || "") + ) { + let endIndex = index + 2; + while ( + endIndex < lines.length && + isLikelyMarkdownTableRow(lines[endIndex] || "") + ) { + endIndex += 1; + } + + const tableLines = lines.slice(index, endIndex); + if (isOpenClawRoutingDiagnosticTable(tableLines)) { + index = endIndex - 1; + continue; + } + } + + keptLines.push(line); + } + + return keptLines.join("\n").replace(/\n{3,}/g, "\n\n").trim(); +} + type MarkdownTableBlockRange = { start: number; end: number; @@ -11568,6 +11648,35 @@ export function CanvasChatPanel({ (selectedSkillTag ? `请输入「${selectedSkillTag.name}」相关需求` : "请输入你的设计需求"); + const clawdEditablePresetPlaceholder = + isClawdMode && activeClawdSkillTag + ? activeClawdSkillTag.placeholder?.trim() || "" + : ""; + const handleClawdPresetPlaceholderDoubleClick = useCallback( + (event: ReactMouseEvent) => { + if (isSubmitting || !isClawdMode || !clawdEditablePresetPlaceholder) return; + + const editor = event.currentTarget; + if (input.trim() || getComposerPlainTextFromElement(editor)) return; + + event.preventDefault(); + event.stopPropagation(); + composerUserEditedAtRef.current = Date.now(); + setInput(clawdEditablePresetPlaceholder); + editor.textContent = clawdEditablePresetPlaceholder; + editor.focus(); + + const selection = window.getSelection(); + if (!selection) return; + + const range = document.createRange(); + range.selectNodeContents(editor); + range.collapse(false); + selection.removeAllRanges(); + selection.addRange(range); + }, + [clawdEditablePresetPlaceholder, input, isClawdMode, isSubmitting] + ); const detailProductCount = DETAIL_PRODUCT_SLOT_INDEXES.filter((index) => Boolean(detailSlots[index]?.url)).length; const detailModelReady = DETAIL_MODEL_SLOT_INDEX >= 0 ? Boolean(detailSlots[DETAIL_MODEL_SLOT_INDEX]?.url) : false; const detailBundleCount = DETAIL_BUNDLE_SLOT_INDEXES.filter((index) => Boolean(detailSlots[index]?.url)).length; @@ -12805,7 +12914,11 @@ export function CanvasChatPanel({ const openClawActionsResult = isClawAssistantMessage ? extractOpenClawActionsFromMarkdown(textWithoutImages) : { markdown: textWithoutImages, blocks: [] }; - const textWithoutActions = openClawActionsResult.markdown; + const textWithoutActions = isClawAssistantMessage + ? stripOpenClawLowValueDiagnosticTables( + openClawActionsResult.markdown + ) + : openClawActionsResult.markdown; const openClawActionBlocks = openClawActionsResult.blocks; const analysisSection = extractCanvasImageAnalysisSection(textWithoutActions); const plainText = normalizeCanvasAgentText(textWithoutActions); @@ -14457,6 +14570,7 @@ export function CanvasChatPanel({ : "text-[#1f2328] empty:before:text-[#a8abb2] dark:text-zinc-100 dark:empty:before:text-zinc-500" )} data-placeholder={composerPlaceholder} + onDoubleClick={handleClawdPresetPlaceholderDoubleClick} onInput={(e) => { // 从 contenteditable 中提取纯文本(排除附件标签文本) const target = e.target as HTMLDivElement; diff --git a/components/elements/response.tsx b/components/elements/response.tsx index a753ee2f7..c5f55bf87 100644 --- a/components/elements/response.tsx +++ b/components/elements/response.tsx @@ -571,10 +571,10 @@ function DownloadableMarkdownTable({ ); @@ -606,7 +609,10 @@ function MarkdownTableHeaderCell({ }: MarkdownTableHeaderCellProps) { return (
);