diff --git a/app/(admin)/settings/admin-settings-client.tsx b/app/(admin)/settings/admin-settings-client.tsx index ac6b03e4e..169063a20 100644 --- a/app/(admin)/settings/admin-settings-client.tsx +++ b/app/(admin)/settings/admin-settings-client.tsx @@ -12345,11 +12345,11 @@ export function AdminSettingsClient() {
开启并配置接口后,右侧对话框模式菜单显示 - Clawd;Token 仅保存在服务端,不会下发到前端。 + Claw;Token 仅保存在服务端,不会下发到前端。
& { function buildChatTitle(message: string) { const normalized = message.replace(/\s+/g, " ").trim(); - if (!normalized) return "Clawd 对话"; + if (!normalized) return "Claw 对话"; return normalized.length > 80 ? `${normalized.slice(0, 80)}...` : normalized; } @@ -144,11 +144,11 @@ export async function POST(request: Request) { const config = await readOpenClawConfig(); if (!config.enabled) { - return NextResponse.json({ error: "Clawd 未启用" }, { status: 403 }); + return NextResponse.json({ error: "Claw 未启用" }, { status: 403 }); } if (!config.baseUrl || !config.token) { return NextResponse.json( - { error: "Clawd 接口地址或 Token 未配置" }, + { error: "Claw 接口地址或 Token 未配置" }, { status: 500 } ); } diff --git a/components/canvas/CanvasChatPanel.tsx b/components/canvas/CanvasChatPanel.tsx index 924889e78..cc384e868 100644 --- a/components/canvas/CanvasChatPanel.tsx +++ b/components/canvas/CanvasChatPanel.tsx @@ -311,8 +311,10 @@ interface CanvasChatPanelProps { type CanvasComposerMode = "agent" | "image" | "video" | "detail" | "clawd"; +const CLAW_MODE_LABEL = "Claw"; + function getCanvasComposerModeSessionLabel(mode: CanvasComposerMode) { - if (mode === "clawd") return "Clawd"; + if (mode === "clawd") return CLAW_MODE_LABEL; if (mode === "video") return "视频"; if (mode === "detail") return "AI详情"; if (mode === "image") return "图像"; @@ -952,16 +954,125 @@ type CanvasTypewriterTextProps = { enabled: boolean; className?: string; renderMarkdown?: boolean; + preserveMarkdownTables?: boolean; onActiveChange?: (typingKey: string, active: boolean) => void; onComplete?: (typingKey: string) => void; }; +type MarkdownTableBlockRange = { + start: number; + end: number; +}; + +function isLikelyMarkdownTableRow(line: string) { + const trimmed = line.trim(); + if (!trimmed || !trimmed.includes("|")) return false; + + const cells = trimmed + .replace(/^\|/, "") + .replace(/\|$/, "") + .split("|"); + return cells.length >= 2; +} + +function isMarkdownTableSeparatorRow(line: string) { + if (!isLikelyMarkdownTableRow(line)) return false; + + const cells = line + .trim() + .replace(/^\|/, "") + .replace(/\|$/, "") + .split("|") + .map((cell) => cell.trim().replace(/\s+/g, "")); + + return cells.length >= 2 && cells.every((cell) => /^:?-{3,}:?$/.test(cell)); +} + +function getMarkdownTableBlockRanges(content: string): MarkdownTableBlockRange[] { + if (!content.includes("|")) return []; + + const rawLines = content.split("\n"); + const lines = rawLines.map((line, index) => { + const hasNewline = index < rawLines.length - 1; + const text = hasNewline ? `${line}\n` : line; + return { + line: line.replace(/\r$/, ""), + text, + }; + }); + + let offset = 0; + const indexedLines = lines.map((line) => { + const start = offset; + offset += line.text.length; + return { + ...line, + start, + end: offset, + }; + }); + + const ranges: MarkdownTableBlockRange[] = []; + for (let index = 0; index < indexedLines.length - 1; index += 1) { + const header = indexedLines[index]; + const separator = indexedLines[index + 1]; + if ( + !header || + !separator || + !isLikelyMarkdownTableRow(header.line) || + !isMarkdownTableSeparatorRow(separator.line) + ) { + continue; + } + + let endIndex = index + 2; + while ( + endIndex < indexedLines.length && + isLikelyMarkdownTableRow(indexedLines[endIndex]?.line || "") + ) { + endIndex += 1; + } + + ranges.push({ + start: header.start, + end: indexedLines[endIndex - 1]?.end || separator.end, + }); + index = endIndex - 1; + } + + return ranges; +} + +function advanceTypewriterCursor( + contentLength: number, + targetCursor: number, + tableRanges: MarkdownTableBlockRange[] +) { + const clampedCursor = Math.min(contentLength, targetCursor); + for (const range of tableRanges) { + if (clampedCursor >= range.start && clampedCursor < range.end) { + return Math.min(contentLength, range.end); + } + } + return clampedCursor; +} + +function CanvasTypewriterCursor() { + return ( +
) : null}
{[0, 1, 2].map((index) => ( @@ -1553,6 +1679,7 @@ export function CanvasChatPanel({ const [analysisCollapseState, setAnalysisCollapseState] = useState>({}); const [typedAssistantSections, setTypedAssistantSections] = useState>({}); const [activeTypingSections, setActiveTypingSections] = useState>({}); + const [clawdTypewriterMessageIds, setClawdTypewriterMessageIds] = useState>({}); const [insufficientPointsState, setInsufficientPointsState] = useState<{ balance: number; expectedCost: number; @@ -3633,7 +3760,7 @@ export function CanvasChatPanel({ ); const activeModeLabel = activeComposerMode === "clawd" - ? "Clawd" + ? CLAW_MODE_LABEL : activeComposerMode === "video" ? "视频" : activeComposerMode === "detail" @@ -3978,7 +4105,7 @@ export function CanvasChatPanel({ hiddenPromptRef.current = null; autoContextRef.current = null; if (mode === "clawd" && !isClawdModeEnabled) { - toast.error("Clawd 未开启"); + toast.error(`${CLAW_MODE_LABEL} 未开启`); return; } if (mode === "agent") { @@ -8991,7 +9118,7 @@ export function CanvasChatPanel({ }); const responseData = await response.json().catch(() => null); if (!response.ok) { - throw new Error(responseData?.error || "Clawd 请求失败"); + throw new Error(responseData?.error || `${CLAW_MODE_LABEL} 请求失败`); } const returnedMessages = Array.isArray(responseData?.messages) @@ -8999,6 +9126,19 @@ export function CanvasChatPanel({ : []; if (returnedMessages.length > 0) { const returnedIds = new Set(returnedMessages.map((message) => message.id)); + const returnedAssistantIds = returnedMessages + .filter((message) => message.role === "assistant") + .map((message) => message.id) + .filter(Boolean); + if (returnedAssistantIds.length > 0) { + setClawdTypewriterMessageIds((previousIds) => { + const nextIds = { ...previousIds }; + returnedAssistantIds.forEach((messageId) => { + nextIds[messageId] = true; + }); + return nextIds; + }); + } setMessages((previousMessages) => sortCanvasMessagesByCreatedAt([ ...previousMessages.filter( @@ -9014,11 +9154,11 @@ export function CanvasChatPanel({ } hasHydratedHistoryRef.current = true; } catch (error) { - console.error("[CanvasChat] Clawd request failed:", error); + console.error("[CanvasChat] Claw request failed:", error); setMessages((previousMessages) => previousMessages.filter((message) => message.id !== userMessageId) ); - toast.error(error instanceof Error ? error.message : "Clawd 请求失败"); + toast.error(error instanceof Error ? error.message : `${CLAW_MODE_LABEL} 请求失败`); setClawdTableFiles(tableFilesSnapshot); setAttachments(attachmentsSnapshot); const currentEditorText = (editableDiv?.textContent || "") @@ -9956,7 +10096,7 @@ export function CanvasChatPanel({ const hasEnoughAgentUploads = !hasAgentUploadSlots || attachments.length >= agentRequiredUploadCount; const composerPlaceholder = isWorkspacePresentation ? isClawdMode - ? "输入要交给 Clawd 的问题,可上传图片或表格" + ? `输入要交给 ${CLAW_MODE_LABEL} 的问题,可上传图片或表格` : isDetailGenerationMode ? "可选补充:风格、情绪氛围、禁用元素、期望生成张数(如:生成8张)" : isVideoWorkspace @@ -9967,7 +10107,7 @@ export function CanvasChatPanel({ ? "可选补充本次生图要求,例如:高级感、暖灰色、近景特写、生成3张。" : "可选补充本次生成要求,例如:更高级、更生活化、暖调灯光。" : isClawdMode - ? "输入要交给 Clawd 的问题,可上传图片或表格" + ? `输入要交给 ${CLAW_MODE_LABEL} 的问题,可上传图片或表格` : isDetailGenerationMode ? "可选补充:风格、情绪氛围、禁用元素、期望生成张数(如:生成8张)" : isVideoWorkspace @@ -10289,7 +10429,7 @@ export function CanvasChatPanel({ !shouldShowImageLoading; const workspaceEmptyTitle = isClawdMode - ? "开始 Clawd 对话" + ? `开始 ${CLAW_MODE_LABEL} 对话` : activeComposerMode === "agent" && !isPresetAgentSelection ? "开始 Auto Agent 对话" : hasAgentUploadSlots && attachments.length < agentRequiredUploadCount @@ -10961,6 +11101,10 @@ export function CanvasChatPanel({ isLoading && !hasAssistantMedia && index === renderableVisibleMessages.length - 1; + const shouldTypeClawAssistantMessage = + message.role === "assistant" && + !hasAssistantMedia && + Boolean(clawdTypewriterMessageIds[message.id]); const messageTimestamp = getMessageTimestamp(message); const prevMessageTimestamp = @@ -11137,16 +11281,18 @@ export function CanvasChatPanel({ const analysisTypingKey = `${message.id}-${i}-analysis-typing`; const plainTextTypingKey = `${message.id}-${i}-plain-typing`; const shouldAnimateAnalysis = - shouldTypeAssistantMessage && Boolean(analysisSection?.analysis) && - !hasCanvasRenderableMarkdown(analysisSection?.analysis || "") && - !typedAssistantSections[analysisTypingKey]; + !typedAssistantSections[analysisTypingKey] && + ((shouldTypeAssistantMessage && + !hasCanvasRenderableMarkdown(analysisSection?.analysis || "")) || + shouldTypeClawAssistantMessage); const shouldAnimatePlainText = - shouldTypeAssistantMessage && !analysisSection && Boolean(plainText) && - !hasCanvasRenderableMarkdown(plainText) && - !typedAssistantSections[plainTextTypingKey]; + !typedAssistantSections[plainTextTypingKey] && + ((shouldTypeAssistantMessage && + !hasCanvasRenderableMarkdown(plainText)) || + shouldTypeClawAssistantMessage); const shouldShowDoneSummary = !isDetailGenerationMode && imageUrls.length > 0 && @@ -11216,6 +11362,7 @@ export function CanvasChatPanel({ enabled={shouldAnimateAnalysis} className="break-words [overflow-wrap:anywhere]" renderMarkdown + preserveMarkdownTables={shouldTypeClawAssistantMessage} onActiveChange={handleTypewriterActiveChange} onComplete={handleTypewriterComplete} /> @@ -11232,6 +11379,7 @@ export function CanvasChatPanel({ enabled={shouldAnimatePlainText} className="break-words [overflow-wrap:anywhere]" renderMarkdown + preserveMarkdownTables={shouldTypeClawAssistantMessage} onActiveChange={handleTypewriterActiveChange} onComplete={handleTypewriterComplete} /> @@ -13023,7 +13171,7 @@ export function CanvasChatPanel({ className={getComposerModeMenuItemClass("clawd")} > - Clawd + {CLAW_MODE_LABEL} {activeComposerMode === "clawd" ? ( ) : null} diff --git a/lib/openclaw-chat/spreadsheet-markdown.ts b/lib/openclaw-chat/spreadsheet-markdown.ts index babae2791..03f902083 100644 --- a/lib/openclaw-chat/spreadsheet-markdown.ts +++ b/lib/openclaw-chat/spreadsheet-markdown.ts @@ -336,7 +336,7 @@ export async function spreadsheetFileToOpenClawMarkdown(file: File) { const extension = getSpreadsheetExtension(file.name); if (!SUPPORTED_EXTENSIONS.has(extension)) { - throw new Error("Clawd 目前支持 .xlsx、.csv、.tsv 表格文件"); + throw new Error("Claw 目前支持 .xlsx、.csv、.tsv 表格文件"); } if (extension === "csv") {