summaryrefslogtreecommitdiff
path: root/makima/frontend/src/components/history/ConversationView.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'makima/frontend/src/components/history/ConversationView.tsx')
-rw-r--r--makima/frontend/src/components/history/ConversationView.tsx114
1 files changed, 114 insertions, 0 deletions
diff --git a/makima/frontend/src/components/history/ConversationView.tsx b/makima/frontend/src/components/history/ConversationView.tsx
new file mode 100644
index 0000000..e3d1110
--- /dev/null
+++ b/makima/frontend/src/components/history/ConversationView.tsx
@@ -0,0 +1,114 @@
+import type {
+ TaskConversationResponse,
+ SupervisorConversationResponse,
+} from "../../lib/api";
+import { ConversationMessage } from "./ConversationMessage";
+
+interface ConversationViewProps {
+ conversation: TaskConversationResponse | SupervisorConversationResponse;
+}
+
+// Type guard for task conversation
+function isTaskConversation(
+ conv: TaskConversationResponse | SupervisorConversationResponse
+): conv is TaskConversationResponse {
+ return "taskId" in conv;
+}
+
+// Type guard for supervisor conversation
+function isSupervisorConversation(
+ conv: TaskConversationResponse | SupervisorConversationResponse
+): conv is SupervisorConversationResponse {
+ return "supervisorTaskId" in conv;
+}
+
+export function ConversationView({ conversation }: ConversationViewProps) {
+ const messages = conversation.messages;
+
+ return (
+ <div className="flex flex-col h-full">
+ {/* Header info */}
+ <div className="shrink-0 p-3 border-b border-[rgba(117,170,252,0.1)] bg-[rgba(0,0,0,0.2)]">
+ <div className="flex items-center justify-between">
+ <div>
+ {isTaskConversation(conversation) ? (
+ <div className="font-mono text-xs text-[#9bc3ff]">
+ {conversation.taskName}
+ <span
+ className={`ml-2 text-[9px] uppercase px-1.5 py-0.5 border ${
+ conversation.status === "done"
+ ? "text-emerald-400 border-emerald-400/30"
+ : conversation.status === "running"
+ ? "text-green-400 border-green-400/30"
+ : conversation.status === "failed"
+ ? "text-red-400 border-red-400/30"
+ : "text-[#7788aa] border-[rgba(117,170,252,0.25)]"
+ }`}
+ >
+ {conversation.status}
+ </span>
+ </div>
+ ) : isSupervisorConversation(conversation) ? (
+ <div className="font-mono text-xs text-[#9bc3ff]">
+ Supervisor
+ <span className="ml-2 text-[9px] uppercase text-cyan-400 px-1.5 py-0.5 border border-cyan-400/30">
+ {conversation.phase}
+ </span>
+ </div>
+ ) : null}
+ </div>
+
+ <div className="flex items-center gap-4 font-mono text-[10px] text-[#556677]">
+ <span>{messages.length} messages</span>
+ {isTaskConversation(conversation) && conversation.totalTokens && (
+ <span>{conversation.totalTokens.toLocaleString()} tokens</span>
+ )}
+ {isTaskConversation(conversation) &&
+ conversation.totalCost !== null &&
+ conversation.totalCost > 0 && (
+ <span>${conversation.totalCost.toFixed(4)}</span>
+ )}
+ </div>
+ </div>
+
+ {/* Spawned tasks (supervisor only) */}
+ {isSupervisorConversation(conversation) && conversation.spawnedTasks.length > 0 && (
+ <div className="mt-2 flex flex-wrap gap-2">
+ <span className="font-mono text-[9px] text-[#7788aa] uppercase">Spawned:</span>
+ {conversation.spawnedTasks.map((task) => (
+ <span
+ key={task.taskId}
+ className={`font-mono text-[9px] px-1.5 py-0.5 border ${
+ task.status === "done"
+ ? "text-emerald-400 border-emerald-400/30"
+ : task.status === "running"
+ ? "text-green-400 border-green-400/30"
+ : task.status === "failed"
+ ? "text-red-400 border-red-400/30"
+ : "text-[#7788aa] border-[rgba(117,170,252,0.25)]"
+ }`}
+ >
+ {task.taskName}
+ </span>
+ ))}
+ </div>
+ )}
+ </div>
+
+ {/* Messages */}
+ <div className="flex-1 overflow-y-auto">
+ {messages.length === 0 ? (
+ <div className="flex items-center justify-center h-32">
+ <div className="font-mono text-[#7788aa] text-xs">No messages</div>
+ </div>
+ ) : (
+ <div className="divide-y divide-[rgba(117,170,252,0.05)]">
+ {messages.map((message, index) => (
+ <ConversationMessage key={message.id || index} message={message} />
+ ))}
+ </div>
+ )}
+ </div>
+ </div>
+ );
+}