Repository navigation
Expand file tree
/
Copy pathconversation.tsx
More file actions
93 lines (88 loc) · 2.72 KB
/
Copy pathconversation.tsx
File metadata and controls
93 lines (88 loc) · 2.72 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import { useEffect, useMemo } from "react";
import { Button, Timeline } from "@ace/ui";
import type { Listing } from "@ace/host/protocol";
import { Composer } from "./composer";
import { toEvents } from "./timeline";
import { useTranscript } from "./transcript";
import { Usage } from "./usage";
type Props = {
channel: Listing;
chat: number;
user: string;
draft?: string;
onDraftLoaded: () => void;
onSettings?: () => void;
/** Called once the chat loads and whenever its agent may have changed files since. */
onWork?: () => void;
};
/** One chat's timeline and composer over its transcript; the content of a Chat tab. */
export function Conversation(
{ channel, chat: id, user, draft, onDraftLoaded, onSettings, onWork }: Props,
) {
const transcript = useTranscript(channel.id, id);
const chat = transcript.info?.chats.find((value) => value.id === id);
const model = chat?.model || channel.model;
const events = useMemo(
() =>
toEvents(transcript.items, channel.id, transcript.busy, {
text: transcript.draft,
model: model?.modelId,
}),
[transcript.items, channel.id, transcript.busy, transcript.draft, model],
);
const results = useMemo(
() =>
transcript.items.reduce(
(sum, item) =>
item.kind === "reply" ? sum + item.tools.filter((tool) => tool.result).length : sum,
0,
),
[transcript.items],
);
useEffect(() => {
if (transcript.live) onWork?.();
}, [transcript.live, transcript.busy, results, onWork]);
return (
<>
<div className="relative min-h-0 flex-1">
{transcript.live
? (
<Timeline
className="h-full min-h-0 contain-paint scroll-fade [--fade:3rem]"
events={events}
toolbar={false}
working={transcript.busy || undefined}
currentUser={{ login: user }}
intro={{ name: channel.name, createdAt: Math.floor(channel.created / 1000) }}
/>
)
: (
<div
className="grid h-full place-content-center justify-items-center gap-3 px-6 text-center text-sm text-muted-foreground"
role={transcript.error ? "alert" : "status"}
>
<p>
{transcript.error
|| (channel.state === "dormant" ? "Starting channel…" : "Loading chat…")}
</p>
{transcript.error && <Button onClick={transcript.retry}>Try again</Button>}
</div>
)}
</div>
<div className="utils:max-width relative z-20 shrink-0 px-3 pb-3">
<Composer
channel={channel}
chat={id}
user={user}
current={model}
busy={transcript.busy}
ready={transcript.live && channel.state !== "archived"}
draft={draft}
onDraftLoaded={onDraftLoaded}
onSettings={onSettings}
accessory={chat?.usage && <Usage value={chat.usage} context={chat.context} />}
/>
</div>
</>
);
}