diff options
| author | soryu <soryu@soryu.co> | 2026-01-02 21:46:36 +0000 |
|---|---|---|
| committer | soryu <soryu@soryu.co> | 2026-01-02 21:46:36 +0000 |
| commit | e8ebf8f01101905bd9aec84aec94fd8854f8a030 (patch) | |
| tree | f079b9f0a8b814f9a3bda6ac7f779b0ddb4b431a | |
| parent | 062ae51396e88a8998bb30e78381275d77e7c90e (diff) | |
| download | soryu-e8ebf8f01101905bd9aec84aec94fd8854f8a030.tar.gz soryu-e8ebf8f01101905bd9aec84aec94fd8854f8a030.zip | |
Update display of LLM edit panel
| -rw-r--r-- | makima/frontend/src/components/SimpleMarkdown.tsx | 173 | ||||
| -rw-r--r-- | makima/frontend/src/components/files/CliInput.tsx | 3 | ||||
| -rw-r--r-- | makima/frontend/tsconfig.tsbuildinfo | 2 |
3 files changed, 176 insertions, 2 deletions
diff --git a/makima/frontend/src/components/SimpleMarkdown.tsx b/makima/frontend/src/components/SimpleMarkdown.tsx new file mode 100644 index 0000000..4f09644 --- /dev/null +++ b/makima/frontend/src/components/SimpleMarkdown.tsx @@ -0,0 +1,173 @@ +import { useMemo } from "react"; + +interface SimpleMarkdownProps { + content: string; + className?: string; +} + +/** + * A simplistic markdown renderer that handles: + * - Newlines (paragraphs) + * - Bold (**text**) + * - Inline code (`code`) + * - Code blocks (```code```) + * - Headers (# ## ###) + * - Lists (- item) + */ +export function SimpleMarkdown({ content, className = "" }: SimpleMarkdownProps) { + const rendered = useMemo(() => { + if (!content) return null; + + // Split by code blocks first to handle them separately + const parts = content.split(/(```[\s\S]*?```)/g); + + return parts.map((part, partIndex) => { + // Handle code blocks + if (part.startsWith("```") && part.endsWith("```")) { + const code = part.slice(3, -3).replace(/^\w+\n/, ""); // Remove language hint + return ( + <pre + key={partIndex} + className="bg-[#0a1525] border border-[rgba(117,170,252,0.2)] p-2 my-1 overflow-x-auto text-[#9bc3ff] text-[10px]" + > + <code>{code.trim()}</code> + </pre> + ); + } + + // Split by newlines and process each line + const lines = part.split("\n"); + + return lines.map((line, lineIndex) => { + const key = `${partIndex}-${lineIndex}`; + + // Skip empty lines but add spacing + if (!line.trim()) { + return <div key={key} className="h-1" />; + } + + // Headers + if (line.startsWith("### ")) { + return ( + <div key={key} className="font-bold text-white/90 mt-2 mb-1"> + {processInline(line.slice(4))} + </div> + ); + } + if (line.startsWith("## ")) { + return ( + <div key={key} className="font-bold text-white mt-2 mb-1"> + {processInline(line.slice(3))} + </div> + ); + } + if (line.startsWith("# ")) { + return ( + <div key={key} className="font-bold text-white text-sm mt-2 mb-1"> + {processInline(line.slice(2))} + </div> + ); + } + + // List items + if (line.match(/^[-*]\s/)) { + return ( + <div key={key} className="flex gap-1"> + <span className="text-[#555]">-</span> + <span>{processInline(line.slice(2))}</span> + </div> + ); + } + + // Numbered list items + if (line.match(/^\d+\.\s/)) { + const match = line.match(/^(\d+)\.\s(.*)$/); + if (match) { + return ( + <div key={key} className="flex gap-1"> + <span className="text-[#555]">{match[1]}.</span> + <span>{processInline(match[2])}</span> + </div> + ); + } + } + + // Regular paragraph + return <div key={key}>{processInline(line)}</div>; + }); + }); + }, [content]); + + return <div className={`space-y-0.5 ${className}`}>{rendered}</div>; +} + +/** + * Process inline markdown: bold, inline code + */ +function processInline(text: string): React.ReactNode { + if (!text) return null; + + // Split by inline code and bold patterns + const parts: React.ReactNode[] = []; + let remaining = text; + let keyIndex = 0; + + while (remaining.length > 0) { + // Check for inline code first + const codeMatch = remaining.match(/^(.*?)`([^`]+)`(.*)$/); + if (codeMatch) { + if (codeMatch[1]) { + parts.push(...processInlineBold(codeMatch[1], keyIndex++)); + } + parts.push( + <code + key={`code-${keyIndex++}`} + className="bg-[#0a1525] px-1 py-0.5 text-[#9bc3ff] text-[10px]" + > + {codeMatch[2]} + </code> + ); + remaining = codeMatch[3]; + continue; + } + + // No more inline code, process bold in remaining text + parts.push(...processInlineBold(remaining, keyIndex)); + break; + } + + return parts.length === 1 ? parts[0] : parts; +} + +/** + * Process bold text (**text**) + */ +function processInlineBold(text: string, startKey: number): React.ReactNode[] { + const parts: React.ReactNode[] = []; + let remaining = text; + let keyIndex = startKey; + + while (remaining.length > 0) { + const boldMatch = remaining.match(/^(.*?)\*\*([^*]+)\*\*(.*)$/); + if (boldMatch) { + if (boldMatch[1]) { + parts.push(<span key={`text-${keyIndex++}`}>{boldMatch[1]}</span>); + } + parts.push( + <strong key={`bold-${keyIndex++}`} className="text-white/90"> + {boldMatch[2]} + </strong> + ); + remaining = boldMatch[3]; + continue; + } + + // No more bold patterns + if (remaining) { + parts.push(<span key={`text-${keyIndex++}`}>{remaining}</span>); + } + break; + } + + return parts; +} diff --git a/makima/frontend/src/components/files/CliInput.tsx b/makima/frontend/src/components/files/CliInput.tsx index 1dcc884..0ac840a 100644 --- a/makima/frontend/src/components/files/CliInput.tsx +++ b/makima/frontend/src/components/files/CliInput.tsx @@ -1,5 +1,6 @@ import { useState, useCallback, useRef, useEffect } from "react"; import { chatWithFile, type BodyElement, type LlmModel } from "../../lib/api"; +import { SimpleMarkdown } from "../SimpleMarkdown"; interface CliInputProps { fileId: string; @@ -114,7 +115,7 @@ export function CliInput({ fileId, onUpdate }: CliInputProps) { )} {msg.type === "assistant" && ( <div className="pl-4 space-y-1"> - <div className="text-[#75aafc]">{msg.content}</div> + <SimpleMarkdown content={msg.content} className="text-[#75aafc]" /> {msg.toolCalls && msg.toolCalls.length > 0 && ( <div className="text-[#555] text-[10px] space-y-0.5"> {msg.toolCalls.map((tc, i) => ( diff --git a/makima/frontend/tsconfig.tsbuildinfo b/makima/frontend/tsconfig.tsbuildinfo index d7218f9..10234a8 100644 --- a/makima/frontend/tsconfig.tsbuildinfo +++ b/makima/frontend/tsconfig.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/main.tsx","./src/vite-env.d.ts","./src/components/gridoverlay.tsx","./src/components/logo.tsx","./src/components/masthead.tsx","./src/components/navstrip.tsx","./src/components/rewritelink.tsx","./src/components/charts/chartrenderer.tsx","./src/components/files/bodyrenderer.tsx","./src/components/files/cliinput.tsx","./src/components/files/conflictnotification.tsx","./src/components/files/filedetail.tsx","./src/components/files/filelist.tsx","./src/components/files/updatenotification.tsx","./src/components/files/versionhistorydropdown.tsx","./src/components/listen/controlpanel.tsx","./src/components/listen/speakerpanel.tsx","./src/components/listen/transcriptpanel.tsx","./src/hooks/usefilesubscription.ts","./src/hooks/usefiles.ts","./src/hooks/usemicrophone.ts","./src/hooks/usetextscramble.ts","./src/hooks/useversionhistory.ts","./src/hooks/usewebsocket.ts","./src/lib/api.ts","./src/routes/_index.tsx","./src/routes/files.tsx","./src/routes/listen.tsx","./src/types/messages.ts"],"version":"5.9.3"}
\ No newline at end of file +{"root":["./src/main.tsx","./src/vite-env.d.ts","./src/components/gridoverlay.tsx","./src/components/logo.tsx","./src/components/masthead.tsx","./src/components/navstrip.tsx","./src/components/rewritelink.tsx","./src/components/simplemarkdown.tsx","./src/components/charts/chartrenderer.tsx","./src/components/files/bodyrenderer.tsx","./src/components/files/cliinput.tsx","./src/components/files/conflictnotification.tsx","./src/components/files/filedetail.tsx","./src/components/files/filelist.tsx","./src/components/files/updatenotification.tsx","./src/components/files/versionhistorydropdown.tsx","./src/components/listen/controlpanel.tsx","./src/components/listen/speakerpanel.tsx","./src/components/listen/transcriptpanel.tsx","./src/hooks/usefilesubscription.ts","./src/hooks/usefiles.ts","./src/hooks/usemicrophone.ts","./src/hooks/usetextscramble.ts","./src/hooks/useversionhistory.ts","./src/hooks/usewebsocket.ts","./src/lib/api.ts","./src/routes/_index.tsx","./src/routes/files.tsx","./src/routes/listen.tsx","./src/types/messages.ts"],"version":"5.9.3"}
\ No newline at end of file |
