nandi/oripublic Fork 0
2434cc7fb724f02b34c0189dfd5fb30c1a8a68e3
Commits
Clone
git clone https://git.rickub.com/nandi/ori.git
git clone ssh://git@rickub.com/nandi/ori.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

forked from bots-garden/ori

ChatThread.tsx · 75 lines · 2.2 KBTypeScript Blame HistoryRaw
✨ Introduce new feature(s): ACP web client — Go backend (agent, bridge, httpserver, mockagent) + React SPA (Zed-like agent panel), tests, quality gate PASS 2434cc7 k33g 2d ago1/**
2 * ChatThread renders the conversation: user prompts, streamed agent answers
3 * (markdown) and inline errors, auto-scrolled to the latest entry.
4 *
5 * @example
6 * <ChatThread thread={[{ kind: "user", text: "hi" }]} turnActive={false} />
7 */
8
9import { useEffect, useRef } from "react";
10import type { ThreadItem } from "../reducer";
11import { Markdown } from "./Markdown";
12import { ToolCallCard } from "./ToolCallCard";
13
14interface ChatThreadProps {
15 thread: ThreadItem[];
16 turnActive: boolean;
17}
18
19export function ChatThread({ thread, turnActive }: ChatThreadProps) {
20 const endRef = useRef<HTMLDivElement>(null);
21
22 // biome-ignore lint/correctness/useExhaustiveDependencies: thread and turnActive intentionally retrigger the scroll-to-bottom on every conversation change.
23 useEffect(() => {
24 // Optional call: test DOMs (jsdom) do not implement scrollIntoView.
25 endRef.current?.scrollIntoView?.({ behavior: "smooth" });
26 }, [thread, turnActive]);
27
28 return (
29 <section className="thread" aria-label="conversation">
30 {thread.length === 0 && (
31 <p className="thread-empty">
32 Send a prompt to start working with the agent.
33 </p>
34 )}
35 {thread.map((item, index) => (
36 // biome-ignore lint/suspicious/noArrayIndexKey: the thread is append-only (never reordered or spliced), so the index is a stable identity.
37 <ThreadEntry key={index} item={item} />
38 ))}
39 {turnActive && (
40 <output className="thread-working">the agent is working</output>
41 )}
42 <div ref={endRef} />
43 </section>
44 );
45}
46
47function ThreadEntry({ item }: { item: ThreadItem }) {
48 switch (item.kind) {
49 case "user":
50 return <div className="message message-user">{item.text}</div>;
51 case "agent":
52 return (
53 <div className="message message-agent">
54 <Markdown text={item.text} />
55 </div>
56 );
57 case "thought":
58 return (
59 <details className="thought">
60 <summary className="thought-summary">💭 Thinking</summary>
61 <div className="thought-body">
62 <Markdown text={item.text} />
63 </div>
64 </details>
65 );
66 case "tool":
67 return <ToolCallCard call={item.call} />;
68 case "error":
69 return (
70 <div className="message message-error" role="alert">
71 {item.text}
72 </div>
73 );
74 }
75}