From 555061b179b8ec034cb70f9a2dd6c823ced0f637 Mon Sep 17 00:00:00 2001
From: soryu <soryu@soryu.co>
Date: Tue, 23 Dec 2025 14:43:23 +0000
Subject: Add file body and initial tool call system

---
 .../frontend/src/components/files/BodyRenderer.tsx | 125 +++++++++++++++++++++
 1 file changed, 125 insertions(+)
 create mode 100644 makima/frontend/src/components/files/BodyRenderer.tsx

(limited to 'makima/frontend/src/components/files/BodyRenderer.tsx')

diff --git a/makima/frontend/src/components/files/BodyRenderer.tsx b/makima/frontend/src/components/files/BodyRenderer.tsx
new file mode 100644
index 0000000..9d008e2
--- /dev/null
+++ b/makima/frontend/src/components/files/BodyRenderer.tsx
@@ -0,0 +1,125 @@
+import type { BodyElement } from "../../lib/api";
+import { ChartRenderer } from "../charts/ChartRenderer";
+
+interface BodyRendererProps {
+  elements: BodyElement[];
+}
+
+export function BodyRenderer({ elements }: BodyRendererProps) {
+  if (elements.length === 0) {
+    return (
+      <div className="text-[#555] font-mono text-sm italic">
+        No content yet. Use the CLI below to add content.
+      </div>
+    );
+  }
+
+  return (
+    <div className="space-y-4">
+      {elements.map((element, index) => (
+        <BodyElementRenderer key={index} element={element} />
+      ))}
+    </div>
+  );
+}
+
+function BodyElementRenderer({ element }: { element: BodyElement }) {
+  switch (element.type) {
+    case "heading":
+      return <HeadingElement level={element.level} text={element.text} />;
+    case "paragraph":
+      return <ParagraphElement text={element.text} />;
+    case "chart":
+      return (
+        <ChartElement
+          chartType={element.chartType}
+          data={element.data}
+          title={element.title}
+          config={element.config}
+        />
+      );
+    case "image":
+      return (
+        <ImageElement
+          src={element.src}
+          alt={element.alt}
+          caption={element.caption}
+        />
+      );
+    default:
+      return null;
+  }
+}
+
+function HeadingElement({ level, text }: { level: number; text: string }) {
+  const className = "font-mono text-[#9bc3ff]";
+
+  switch (level) {
+    case 1:
+      return <h1 className={`${className} text-2xl font-bold`}>{text}</h1>;
+    case 2:
+      return <h2 className={`${className} text-xl font-bold`}>{text}</h2>;
+    case 3:
+      return <h3 className={`${className} text-lg font-semibold`}>{text}</h3>;
+    case 4:
+      return <h4 className={`${className} text-base font-semibold`}>{text}</h4>;
+    case 5:
+      return <h5 className={`${className} text-sm font-semibold`}>{text}</h5>;
+    case 6:
+      return <h6 className={`${className} text-xs font-semibold`}>{text}</h6>;
+    default:
+      return <h3 className={`${className} text-lg font-semibold`}>{text}</h3>;
+  }
+}
+
+function ParagraphElement({ text }: { text: string }) {
+  return <p className="font-mono text-sm text-white/80 leading-relaxed">{text}</p>;
+}
+
+function ChartElement({
+  chartType,
+  data,
+  title,
+  config,
+}: {
+  chartType: "line" | "bar" | "pie" | "area";
+  data: Record<string, unknown>[];
+  title?: string;
+  config?: Record<string, unknown>;
+}) {
+  return (
+    <div className="border border-[#333] p-4 bg-black/30">
+      <ChartRenderer
+        chartType={chartType}
+        data={data}
+        title={title}
+        config={config}
+      />
+    </div>
+  );
+}
+
+function ImageElement({
+  src,
+  alt,
+  caption,
+}: {
+  src: string;
+  alt?: string;
+  caption?: string;
+}) {
+  return (
+    <figure className="space-y-2">
+      <img
+        src={src}
+        alt={alt || ""}
+        className="max-w-full border border-[#333]"
+      />
+      {caption && (
+        <figcaption className="text-[#555] font-mono text-xs italic">
+          {caption}
+        </figcaption>
+      )}
+    </figure>
+  );
+}
-- 
cgit v1.2.3

