summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-01-02 21:46:36 +0000
committersoryu <soryu@soryu.co>2026-01-02 21:46:36 +0000
commite8ebf8f01101905bd9aec84aec94fd8854f8a030 (patch)
treef079b9f0a8b814f9a3bda6ac7f779b0ddb4b431a
parent062ae51396e88a8998bb30e78381275d77e7c90e (diff)
downloadsoryu-e8ebf8f01101905bd9aec84aec94fd8854f8a030.tar.gz
soryu-e8ebf8f01101905bd9aec84aec94fd8854f8a030.zip
Update display of LLM edit panel
-rw-r--r--makima/frontend/src/components/SimpleMarkdown.tsx173
-rw-r--r--makima/frontend/src/components/files/CliInput.tsx3
-rw-r--r--makima/frontend/tsconfig.tsbuildinfo2
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