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

---
 .../src/components/charts/ChartRenderer.tsx        | 181 +++++++++++++++++++++
 1 file changed, 181 insertions(+)
 create mode 100644 makima/frontend/src/components/charts/ChartRenderer.tsx

(limited to 'makima/frontend/src/components/charts/ChartRenderer.tsx')

diff --git a/makima/frontend/src/components/charts/ChartRenderer.tsx b/makima/frontend/src/components/charts/ChartRenderer.tsx
new file mode 100644
index 0000000..276b170
--- /dev/null
+++ b/makima/frontend/src/components/charts/ChartRenderer.tsx
@@ -0,0 +1,181 @@
+import { useMemo } from "react";
+import {
+  LineChart,
+  Line,
+  BarChart,
+  Bar,
+  PieChart,
+  Pie,
+  AreaChart,
+  Area,
+  XAxis,
+  YAxis,
+  CartesianGrid,
+  Tooltip,
+  Legend,
+  ResponsiveContainer,
+  Cell,
+} from "recharts";
+import type { ChartType } from "../../lib/api";
+
+interface ChartRendererProps {
+  chartType: ChartType;
+  data: Record<string, unknown>[];
+  title?: string;
+  config?: Record<string, unknown>;
+}
+
+// Default color palette
+const COLORS = [
+  "#9bc3ff",
+  "#ff9b9b",
+  "#9bffb3",
+  "#ffeb9b",
+  "#d49bff",
+  "#9bfff0",
+  "#ff9beb",
+  "#b3ff9b",
+];
+
+export function ChartRenderer({
+  chartType,
+  data,
+  title,
+  config,
+}: ChartRendererProps) {
+  // Extract data keys (excluding 'name' which is used for labels)
+  const dataKeys = useMemo(() => {
+    if (data.length === 0) return [];
+    const keys = Object.keys(data[0]).filter((key) => key !== "name");
+    return keys;
+  }, [data]);
+
+  // Get colors from config or use defaults
+  const colors = (config?.colors as string[]) || COLORS;
+
+  const renderChart = () => {
+    switch (chartType) {
+      case "line":
+        return (
+          <LineChart data={data}>
+            <CartesianGrid strokeDasharray="3 3" stroke="#333" />
+            <XAxis dataKey="name" stroke="#9bc3ff" fontSize={12} />
+            <YAxis stroke="#9bc3ff" fontSize={12} />
+            <Tooltip
+              contentStyle={{
+                backgroundColor: "#111",
+                border: "1px solid #9bc3ff",
+                borderRadius: "0",
+              }}
+            />
+            <Legend />
+            {dataKeys.map((key, i) => (
+              <Line
+                key={key}
+                type="monotone"
+                dataKey={key}
+                stroke={colors[i % colors.length]}
+                strokeWidth={2}
+                dot={{ fill: colors[i % colors.length] }}
+              />
+            ))}
+          </LineChart>
+        );
+
+      case "bar":
+        return (
+          <BarChart data={data}>
+            <CartesianGrid strokeDasharray="3 3" stroke="#333" />
+            <XAxis dataKey="name" stroke="#9bc3ff" fontSize={12} />
+            <YAxis stroke="#9bc3ff" fontSize={12} />
+            <Tooltip
+              contentStyle={{
+                backgroundColor: "#111",
+                border: "1px solid #9bc3ff",
+                borderRadius: "0",
+              }}
+            />
+            <Legend />
+            {dataKeys.map((key, i) => (
+              <Bar key={key} dataKey={key} fill={colors[i % colors.length]} />
+            ))}
+          </BarChart>
+        );
+
+      case "area":
+        return (
+          <AreaChart data={data}>
+            <CartesianGrid strokeDasharray="3 3" stroke="#333" />
+            <XAxis dataKey="name" stroke="#9bc3ff" fontSize={12} />
+            <YAxis stroke="#9bc3ff" fontSize={12} />
+            <Tooltip
+              contentStyle={{
+                backgroundColor: "#111",
+                border: "1px solid #9bc3ff",
+                borderRadius: "0",
+              }}
+            />
+            <Legend />
+            {dataKeys.map((key, i) => (
+              <Area
+                key={key}
+                type="monotone"
+                dataKey={key}
+                stroke={colors[i % colors.length]}
+                fill={colors[i % colors.length]}
+                fillOpacity={0.3}
+              />
+            ))}
+          </AreaChart>
+        );
+
+      case "pie":
+        // For pie charts, use the first data key as value
+        const valueKey = dataKeys[0] || "value";
+        return (
+          <PieChart>
+            <Pie
+              data={data}
+              dataKey={valueKey}
+              nameKey="name"
+              cx="50%"
+              cy="50%"
+              outerRadius={80}
+              label={({ name, percent }) =>
+                `${name}: ${((percent ?? 0) * 100).toFixed(0)}%`
+              }
+              labelLine={{ stroke: "#9bc3ff" }}
+            >
+              {data.map((_, i) => (
+                <Cell key={i} fill={colors[i % colors.length]} />
+              ))}
+            </Pie>
+            <Tooltip
+              contentStyle={{
+                backgroundColor: "#111",
+                border: "1px solid #9bc3ff",
+                borderRadius: "0",
+              }}
+            />
+            <Legend />
+          </PieChart>
+        );
+
+      default:
+        return <div className="text-red-400">Unknown chart type: {chartType}</div>;
+    }
+  };
+
+  return (
+    <div className="w-full">
+      {title && (
+        <h4 className="text-[#9bc3ff] font-mono text-sm mb-2">{title}</h4>
+      )}
+      <div className="h-64 w-full">
+        <ResponsiveContainer width="100%" height="100%">
+          {renderChart()}
+        </ResponsiveContainer>
+      </div>
+    </div>
+  );
+}
-- 
cgit v1.2.3

