Open History to see the last commit
1import type {
2 EnvironmentId,
3 OrchestrationV2PendingBackgroundTask,
4 ThreadId,
5} from "@t3tools/contracts";
6import type { ThreadStructure } from "@t3tools/client-runtime/state/shell";
7import { useState } from "react";
8
9import { InlineButton } from "../ui/button";
10import {
11 Dialog,
12 DialogHeader,
13 DialogPanel,
14 DialogPopup,
15 DialogTitle,
16 DialogTrigger,
17} from "../ui/dialog";
18import { CommandOutputPanel, useCommandOutputStreaming } from "./CommandOutputPanel";
19import { ShellCommandBlock } from "./ShellCommandBlock";
20
21export function BackgroundWorkDetails(props: {
22 readonly environmentId: EnvironmentId;
23 readonly threadId: ThreadId;
24 readonly tasks: ReadonlyArray<OrchestrationV2PendingBackgroundTask>;
25 readonly turnItems: ThreadStructure["turnItems"];
26 readonly onOpenThread: (threadId: ThreadId) => void;
27}) {
28 const [open, setOpen] = useState(false);
29 const streamsOutput = useCommandOutputStreaming(props.environmentId);
30 return (
31 <Dialog open={open} onOpenChange={setOpen}>
32 <DialogTrigger render={<InlineButton />}>Details</DialogTrigger>
33 <DialogPopup>
34 <DialogHeader>
35 <DialogTitle>Background work</DialogTitle>
36 </DialogHeader>
37 <DialogPanel>
38 <div className="space-y-5">
39 {props.tasks.map((task) => {
40 const item = props.turnItems.findLast((candidate) =>
41 task.itemId === undefined
42 ? (candidate.nativeItemRef?.nativeId || candidate.id) === task.taskId
43 : candidate.id === task.itemId,
44 );
45 const command =
46 task.command ?? (item?.type === "command_execution" ? item.input : undefined);
47 const description = task.description?.trim();
48 const title =
49 description && description !== command?.trim()
50 ? description
51 : command
52 ? "Command"
53 : task.kind.replaceAll("_", " ");
54 const itemId =
55 task.itemId ?? (item?.type === "command_execution" ? item.id : undefined);
56 const childThreadId = task.kind === "subagent" ? task.childThreadId : undefined;
57 return (
58 <section key={task.taskId} className="space-y-2 text-sm">
59 <div className="flex items-baseline justify-between gap-3">
60 <h3 className="font-medium">{title}</h3>
61 <span className="shrink-0 text-xs text-muted-foreground">
62 {task.awaited ? "Waiting" : "Running"}
63 </span>
64 </div>
65 {command ? (
66 <div className="rounded-md border border-border bg-muted/20 p-3 font-mono text-xs whitespace-pre-wrap break-words">
67 <ShellCommandBlock command={command} />
68 </div>
69 ) : null}
70 {itemId && streamsOutput && item?.status === "running" ? (
71 <div className="font-mono text-xs whitespace-pre-wrap break-words">
72 <CommandOutputPanel
73 environmentId={props.environmentId}
74 threadId={task.sourceThreadId ?? item?.threadId ?? props.threadId}
75 itemId={itemId}
76 />
77 </div>
78 ) : null}
79 {childThreadId ? (
80 <InlineButton
81 onClick={() => {
82 setOpen(false);
83 props.onOpenThread(childThreadId);
84 }}
85 >
86 Open thread
87 </InlineButton>
88 ) : null}
89 </section>
90 );
91 })}
92 </div>
93 </DialogPanel>
94 </DialogPopup>
95 </Dialog>
96 );
97}