From 217c59a13a40f15f7574c0e3d264e1c1e95d3dab Mon Sep 17 00:00:00 2001 From: dwelle <5153846+dwelle@users.noreply.github.com> Date: Tue, 24 Mar 2026 16:14:08 +0100 Subject: [PATCH] feat(editor): highlight TTD assistant responses --- .../components/TTDDialog/Chat/Chat.scss | 71 ++++++++++++++++- .../components/TTDDialog/Chat/ChatMessage.tsx | 63 ++++++++++++--- .../TTDDialog/Chat/TTDChatPanel.tsx | 4 +- .../components/TTDDialog/Chat/useChatAgent.ts | 1 + .../components/TTDDialog/TextToDiagram.tsx | 6 +- .../TTDDialog/hooks/useTextGeneration.ts | 1 + .../TTDDialog/mermaid-highlighting.test.ts | 40 ++++++++++ .../TTDDialog/mermaid-highlighting.ts | 79 +++++++++++++++++++ .../components/TTDDialog/mermaid-lang-lite.ts | 76 ++---------------- .../excalidraw/components/TTDDialog/types.ts | 3 + .../components/TTDDialog/useTTDChatStorage.ts | 13 ++- .../components/TTDDialog/utils/chat.test.ts | 24 ++++++ 12 files changed, 296 insertions(+), 85 deletions(-) create mode 100644 packages/excalidraw/components/TTDDialog/mermaid-highlighting.test.ts create mode 100644 packages/excalidraw/components/TTDDialog/mermaid-highlighting.ts diff --git a/packages/excalidraw/components/TTDDialog/Chat/Chat.scss b/packages/excalidraw/components/TTDDialog/Chat/Chat.scss index d205d19a5a..fbb710cb6e 100644 --- a/packages/excalidraw/components/TTDDialog/Chat/Chat.scss +++ b/packages/excalidraw/components/TTDDialog/Chat/Chat.scss @@ -3,7 +3,25 @@ $verticalBreakpoint: 861px; .excalidraw { + --ttd-mermaid-token-keyword: #0000ff; + --ttd-mermaid-token-string: #a31515; + --ttd-mermaid-token-comment: #008000; + --ttd-mermaid-token-number: #098658; + --ttd-mermaid-token-operator: #1e1e1e; + --ttd-mermaid-token-punctuation: #1e1e1e; + --ttd-mermaid-token-variable-name: #001080; + --ttd-mermaid-token-bracket: #af00db; + &.theme--dark { + --ttd-mermaid-token-keyword: #569cd6; + --ttd-mermaid-token-string: #ce9178; + --ttd-mermaid-token-comment: #6a9955; + --ttd-mermaid-token-number: #b5cea8; + --ttd-mermaid-token-operator: #d4d4d4; + --ttd-mermaid-token-punctuation: #d4d4d4; + --ttd-mermaid-token-variable-name: #9cdcfe; + --ttd-mermaid-token-bracket: #ffd700; + .chat-message { &--assistant { .chat-message__content { @@ -194,7 +212,7 @@ $verticalBreakpoint: 861px; align-items: flex-start; .chat-message__content { - background: var(--color-surface-low); + background: #f7f7f7; color: var(--color-on-surface); border-radius: var(--border-radius-md); min-width: 6rem; @@ -292,6 +310,51 @@ $verticalBreakpoint: 861px; word-wrap: break-word; } + &__text--error { + color: inherit; + } + + &__text--mermaid { + overflow-x: auto; + font-weight: 400; + } + + &__token { + white-space: inherit; + } + + &__token--keyword { + color: var(--ttd-mermaid-token-keyword); + } + + &__token--string { + color: var(--ttd-mermaid-token-string); + } + + &__token--comment { + color: var(--ttd-mermaid-token-comment); + } + + &__token--number { + color: var(--ttd-mermaid-token-number); + } + + &__token--operator { + color: var(--ttd-mermaid-token-operator); + } + + &__token--punctuation { + color: var(--ttd-mermaid-token-punctuation); + } + + &__token--variableName { + color: var(--ttd-mermaid-token-variable-name); + } + + &__token--bracket { + color: var(--ttd-mermaid-token-bracket); + } + &__cursor { display: inline-block; margin-left: 2px; @@ -332,11 +395,13 @@ $verticalBreakpoint: 861px; &__error { color: var(--color-danger); font-weight: 500; - white-space: pre-wrap; - word-wrap: break-word; display: flex; flex-direction: column; gap: 0.5rem; + + .chat-message__text--mermaid { + color: var(--color-on-surface); + } } &__error_message { diff --git a/packages/excalidraw/components/TTDDialog/Chat/ChatMessage.tsx b/packages/excalidraw/components/TTDDialog/Chat/ChatMessage.tsx index ad2cf574b0..762b4ee075 100644 --- a/packages/excalidraw/components/TTDDialog/Chat/ChatMessage.tsx +++ b/packages/excalidraw/components/TTDDialog/Chat/ChatMessage.tsx @@ -5,8 +5,49 @@ import { t } from "../../../i18n"; import { FilledButton } from "../../FilledButton"; import { TrashIcon, codeIcon, stackPushIcon, RetryIcon } from "../../icons"; +import { tokenizeMermaid } from "../mermaid-highlighting"; + import type { TChat, TTTDDialog } from "../types"; +const isMermaidMessage = (message: TChat.ChatMessage) => + message.contentFormat === "mermaid"; + +const renderMessageContent = ( + message: TChat.ChatMessage, + className: string, +) => { + const content = message.content ?? ""; + + console.log("@", message); + + if (!isMermaidMessage(message)) { + return ( +
+ {content} + {message.isGenerating && ( + + )} +
+ ); + } + + return ( +
+ {tokenizeMermaid(content).map((token, index) => ( + + {token.value} + + ))} + {message.isGenerating && } +
+ ); +}; + export const ChatMessage: React.FC<{ message: TChat.ChatMessage; onMermaidTabClick?: (message: TChat.ChatMessage) => void; @@ -122,7 +163,14 @@ export const ChatMessage: React.FC<{
{message.error ? ( <> -
{message.content}
+
+ {renderMessageContent( + message, + clsx("chat-message__text", { + "chat-message__text--error": !isMermaidMessage(message), + }), + )} +
{message.errorType !== "parse" && (
Error: {message.error || t("chat.errors.generationFailed")} @@ -132,7 +180,7 @@ export const ChatMessage: React.FC<{

{t("chat.errors.invalidDiagram")}

- {onMermaidTabClick && ( + {onMermaidTabClick && isMermaidMessage(message) && (
{message.type === "assistant" && !message.isGenerating && (
- {!message.error && onInsertMessage && ( + {!message.error && onInsertMessage && isMermaidMessage(message) && ( )} - {onMermaidTabClick && message.content && ( + {onMermaidTabClick && isMermaidMessage(message) && message.content && (