@opengeni/react 5.1.0-canary.0 → 5.2.0-canary.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/dist/artifacts.d.ts +2 -0
- package/dist/artifacts.js +1391 -2
- package/dist/artifacts.js.map +1 -1
- package/dist/chunk-2TKI3WEG.js +73 -0
- package/dist/chunk-2TKI3WEG.js.map +1 -0
- package/dist/{chunk-M2HNWZYR.js → chunk-IHQII5WO.js} +69 -18
- package/dist/chunk-IHQII5WO.js.map +1 -0
- package/dist/{chunk-5PQJOCX5.js → chunk-KANFU34B.js} +6 -4
- package/dist/chunk-KANFU34B.js.map +1 -0
- package/dist/{chunk-WS6D5BHH.js → chunk-O4NQNMPG.js} +2393 -2123
- package/dist/chunk-O4NQNMPG.js.map +1 -0
- package/dist/{chunk-HUMIRRYI.js → chunk-PZ27MCZP.js} +54 -19
- package/dist/chunk-PZ27MCZP.js.map +1 -0
- package/dist/{chunk-BRFMGY4L.js → chunk-UGMKPUEM.js} +28 -3
- package/dist/chunk-UGMKPUEM.js.map +1 -0
- package/dist/{chunk-BV5A4OTE.js → chunk-YS24CFDI.js} +44 -10
- package/dist/chunk-YS24CFDI.js.map +1 -0
- package/dist/components/artifacts/inline-html-document.d.ts +2 -0
- package/dist/components/artifacts/published-html-artifact-frame.d.ts +2 -0
- package/dist/components/artifacts/visualization-assets.gen.d.ts +2 -0
- package/dist/components/markdown.d.ts +13 -1
- package/dist/components/session-chrome.d.ts +6 -1
- package/dist/{computer-viewer-7TEZC6Y2.js → computer-viewer-CTFCJVS7.js} +2 -2
- package/dist/connect.js +1 -1
- package/dist/hooks/use-file-attachments.d.ts +2 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +16 -5
- package/dist/index.js.map +1 -1
- package/dist/interaction.js +1 -1
- package/dist/{platform-activity-row-27LKAWZD.js → platform-activity-row-DUE2W74J.js} +6 -3
- package/dist/platform-activity-row-DUE2W74J.js.map +1 -0
- package/dist/session-ui.d.ts +2 -0
- package/dist/session-ui.js +7 -107
- package/dist/session-ui.js.map +1 -1
- package/dist/session.js +2 -2
- package/dist/sites-ui.d.ts +2 -1
- package/dist/sites.js +12 -4
- package/dist/sites.js.map +1 -1
- package/dist/timeline/activity-rail.d.ts +1 -0
- package/dist/timeline/genie-loading.d.ts +9 -1
- package/dist/timeline/index.d.ts +3 -1
- package/dist/timeline/knowledge-receipt.d.ts +17 -0
- package/dist/timeline/platform-activity-row.d.ts +1 -2
- package/dist/timeline/retained-image.d.ts +16 -0
- package/dist/timeline/rolling-activity.d.ts +9 -0
- package/dist/timeline/shared.d.ts +4 -1
- package/dist/timeline/turn-summary.d.ts +4 -1
- package/dist/timeline/types.d.ts +13 -2
- package/package.json +4 -4
- package/src/artifacts.ts +2 -0
- package/src/components/artifacts/inline-html-document.ts +24 -0
- package/src/components/artifacts/published-html-artifact-frame.tsx +32 -2
- package/src/components/artifacts/visualization-assets/base.css +1015 -0
- package/src/components/artifacts/visualization-assets/helpers.html +349 -0
- package/src/components/artifacts/visualization-assets.gen.ts +3 -0
- package/src/components/computer-viewer.tsx +11 -3
- package/src/components/copy-button.tsx +7 -2
- package/src/components/markdown.tsx +111 -34
- package/src/components/message-timeline.tsx +48 -3
- package/src/components/session-chrome.tsx +14 -2
- package/src/hooks/use-computer-frame-stream.ts +17 -5
- package/src/hooks/use-computer-session.ts +36 -1
- package/src/hooks/use-file-attachments.ts +4 -1
- package/src/hooks/use-goal.ts +3 -1
- package/src/index.ts +12 -1
- package/src/session-ui.ts +3 -0
- package/src/sites-ui.tsx +23 -6
- package/src/timeline/activity-rail.tsx +31 -4
- package/src/timeline/genie-loading.tsx +18 -4
- package/src/timeline/index.ts +4 -0
- package/src/timeline/knowledge-receipt.tsx +100 -0
- package/src/timeline/platform-activity-row.tsx +5 -3
- package/src/timeline/projection.ts +40 -0
- package/src/timeline/retained-image.ts +91 -0
- package/src/timeline/rolling-activity.tsx +83 -0
- package/src/timeline/shared.tsx +33 -12
- package/src/timeline/tool-renderers.tsx +55 -91
- package/src/timeline/turn-summary.tsx +21 -15
- package/src/timeline/types.ts +14 -0
- package/styles/compiled.css +122 -0
- package/styles/index.css +49 -0
- package/dist/chunk-5PQJOCX5.js.map +0 -1
- package/dist/chunk-BRFMGY4L.js.map +0 -1
- package/dist/chunk-BV5A4OTE.js.map +0 -1
- package/dist/chunk-HUMIRRYI.js.map +0 -1
- package/dist/chunk-M2HNWZYR.js.map +0 -1
- package/dist/chunk-WS6D5BHH.js.map +0 -1
- package/dist/platform-activity-row-27LKAWZD.js.map +0 -1
- /package/dist/{computer-viewer-7TEZC6Y2.js.map → computer-viewer-CTFCJVS7.js.map} +0 -0
|
@@ -12,7 +12,15 @@ export type GenieLoadingOptions = {
|
|
|
12
12
|
/** Replace the visual while preserving SDK loading visibility and transitions. */
|
|
13
13
|
render?: (props: GenieLoadingRenderProps) => ReactNode;
|
|
14
14
|
phrases?: readonly string[];
|
|
15
|
-
/**
|
|
15
|
+
/** Native copy overrides. Omitted messages retain the default English copy. */
|
|
16
|
+
messages?: {
|
|
17
|
+
status?: string;
|
|
18
|
+
slowStatus?: string;
|
|
19
|
+
slowText?: string;
|
|
20
|
+
showDetails?: string;
|
|
21
|
+
hideDetails?: string;
|
|
22
|
+
};
|
|
23
|
+
/** Public adapter options; the renderer dependency's declarations stay private. */
|
|
16
24
|
orb?: {
|
|
17
25
|
state?:
|
|
18
26
|
| "working"
|
|
@@ -90,10 +98,14 @@ export function GenieLoading({
|
|
|
90
98
|
</div>
|
|
91
99
|
<div className="og-genie-copy">
|
|
92
100
|
<span className="sr-only" role="status">
|
|
93
|
-
{slow
|
|
101
|
+
{slow
|
|
102
|
+
? (options?.messages?.slowStatus ?? "Preparing your task. Taking longer than usual.")
|
|
103
|
+
: (options?.messages?.status ?? "Preparing your task.")}
|
|
94
104
|
</span>
|
|
95
105
|
<span key={slow ? "slow" : phrase} className="og-genie-phrase" aria-hidden="true">
|
|
96
|
-
{slow
|
|
106
|
+
{slow
|
|
107
|
+
? (options?.messages?.slowText ?? "A little longer than usual…")
|
|
108
|
+
: phrases[phrase % phrases.length]}
|
|
97
109
|
</span>
|
|
98
110
|
{showDetails || detailsOpen ? (
|
|
99
111
|
<button
|
|
@@ -102,7 +114,9 @@ export function GenieLoading({
|
|
|
102
114
|
aria-expanded={detailsOpen}
|
|
103
115
|
onClick={onShowDetails}
|
|
104
116
|
>
|
|
105
|
-
{detailsOpen
|
|
117
|
+
{detailsOpen
|
|
118
|
+
? (options?.messages?.hideDetails ?? "Hide details")
|
|
119
|
+
: (options?.messages?.showDetails ?? "Behind the magic")}
|
|
106
120
|
<span aria-hidden="true"> ↗</span>
|
|
107
121
|
</button>
|
|
108
122
|
) : null}
|
package/src/timeline/index.ts
CHANGED
|
@@ -41,6 +41,7 @@ export type {
|
|
|
41
41
|
MachineInputBatchItem,
|
|
42
42
|
MachineInputMember,
|
|
43
43
|
MemoryItem,
|
|
44
|
+
KnowledgeItem,
|
|
44
45
|
NoticeItem,
|
|
45
46
|
ReasoningItem,
|
|
46
47
|
SandboxItem,
|
|
@@ -139,3 +140,6 @@ export {
|
|
|
139
140
|
v4aToGitFileDiff,
|
|
140
141
|
} from "./parsers";
|
|
141
142
|
export type { ApplyPatchOperation } from "./parsers";
|
|
143
|
+
|
|
144
|
+
export { KnowledgeActivityProvider } from "./knowledge-receipt";
|
|
145
|
+
export type { KnowledgeActivityActions } from "./knowledge-receipt";
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { createContext, useContext, useState, type ReactNode } from "react";
|
|
2
|
+
import { BrainCircuitIcon } from "lucide-react";
|
|
3
|
+
import { ActivityDisclosure, BodyNote } from "./shared";
|
|
4
|
+
|
|
5
|
+
export type KnowledgeActivityActions = {
|
|
6
|
+
onInspect?: ((entryId: string) => void) | undefined;
|
|
7
|
+
onRetryFile?: ((fileId: string) => Promise<boolean | void>) | undefined;
|
|
8
|
+
retryDisabled?: boolean | undefined;
|
|
9
|
+
};
|
|
10
|
+
const KnowledgeActions = createContext<KnowledgeActivityActions>({});
|
|
11
|
+
export function KnowledgeActivityProvider({
|
|
12
|
+
children,
|
|
13
|
+
...actions
|
|
14
|
+
}: KnowledgeActivityActions & { children: ReactNode }) {
|
|
15
|
+
return <KnowledgeActions.Provider value={actions}>{children}</KnowledgeActions.Provider>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** A saved/pending result never opens or blocks the human-input surface. */
|
|
19
|
+
export function KnowledgeReceiptRow(props: {
|
|
20
|
+
outcome: "published" | "pending" | "rejected" | "archived" | "failed";
|
|
21
|
+
entryId?: string | undefined;
|
|
22
|
+
fileId?: string | undefined;
|
|
23
|
+
title?: string | undefined;
|
|
24
|
+
source?: boolean | undefined;
|
|
25
|
+
}) {
|
|
26
|
+
const actions = useContext(KnowledgeActions);
|
|
27
|
+
const [retrying, setRetrying] = useState(false);
|
|
28
|
+
const [retrySent, setRetrySent] = useState(false);
|
|
29
|
+
const [error, setError] = useState<string | null>(null);
|
|
30
|
+
const label =
|
|
31
|
+
props.outcome === "pending"
|
|
32
|
+
? "Knowledge saved for review"
|
|
33
|
+
: props.outcome === "failed"
|
|
34
|
+
? "Source preparation failed"
|
|
35
|
+
: props.outcome === "rejected"
|
|
36
|
+
? "Source remains rejected"
|
|
37
|
+
: props.outcome === "archived"
|
|
38
|
+
? "Knowledge archived"
|
|
39
|
+
: props.source
|
|
40
|
+
? "Source saved to Knowledge"
|
|
41
|
+
: "Saved to Knowledge";
|
|
42
|
+
const actionClass =
|
|
43
|
+
"rounded-og-sm px-1 py-0.5 text-og-sm text-og-fg-muted hover:text-og-fg focus-visible:outline-2 focus-visible:outline-og-accent disabled:opacity-50";
|
|
44
|
+
return (
|
|
45
|
+
<ActivityDisclosure
|
|
46
|
+
icon={<BrainCircuitIcon className="size-3.5" />}
|
|
47
|
+
iconTone="muted"
|
|
48
|
+
title={label}
|
|
49
|
+
preview={props.title}
|
|
50
|
+
>
|
|
51
|
+
{props.outcome === "pending" ? (
|
|
52
|
+
<BodyNote>
|
|
53
|
+
Saved in Needs review. This task continues; future tasks can use it after approval.
|
|
54
|
+
</BodyNote>
|
|
55
|
+
) : null}
|
|
56
|
+
{props.outcome === "failed" ? (
|
|
57
|
+
<BodyNote>
|
|
58
|
+
The original file remains attached. Searchable text has not been retained.
|
|
59
|
+
</BodyNote>
|
|
60
|
+
) : null}
|
|
61
|
+
{props.outcome === "rejected" ? (
|
|
62
|
+
<BodyNote>Processing the file again leaves the review decision unchanged.</BodyNote>
|
|
63
|
+
) : null}
|
|
64
|
+
{props.entryId && actions.onInspect ? (
|
|
65
|
+
<button
|
|
66
|
+
className={actionClass}
|
|
67
|
+
type="button"
|
|
68
|
+
onClick={() => actions.onInspect?.(props.entryId!)}
|
|
69
|
+
>
|
|
70
|
+
{props.outcome === "pending" ? "Review in Knowledge" : "View in Knowledge"}
|
|
71
|
+
</button>
|
|
72
|
+
) : null}
|
|
73
|
+
{props.outcome === "failed" && props.fileId && actions.onRetryFile ? (
|
|
74
|
+
<button
|
|
75
|
+
className={actionClass}
|
|
76
|
+
type="button"
|
|
77
|
+
disabled={retrying || retrySent || actions.retryDisabled}
|
|
78
|
+
onClick={() => {
|
|
79
|
+
setRetrying(true);
|
|
80
|
+
setError(null);
|
|
81
|
+
void actions.onRetryFile!(props.fileId!)
|
|
82
|
+
.then((sent) => {
|
|
83
|
+
if (sent !== false) setRetrySent(true);
|
|
84
|
+
else setError("Could not start the retry. Try again when the chat is ready.");
|
|
85
|
+
})
|
|
86
|
+
.catch(() => setError("Could not start the retry. Please try again."))
|
|
87
|
+
.finally(() => setRetrying(false));
|
|
88
|
+
}}
|
|
89
|
+
>
|
|
90
|
+
{retrying ? "Starting retry…" : retrySent ? "Retry requested" : "Retry preparation"}
|
|
91
|
+
</button>
|
|
92
|
+
) : null}
|
|
93
|
+
{error ? (
|
|
94
|
+
<p role="alert" className="text-og-sm">
|
|
95
|
+
{error}
|
|
96
|
+
</p>
|
|
97
|
+
) : null}
|
|
98
|
+
</ActivityDisclosure>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
@@ -10,7 +10,6 @@ export default function PlatformActivityRow({
|
|
|
10
10
|
t: displayName,
|
|
11
11
|
b: BotIcon,
|
|
12
12
|
m: Markdown,
|
|
13
|
-
r: truncate,
|
|
14
13
|
j,
|
|
15
14
|
s,
|
|
16
15
|
}: {
|
|
@@ -20,7 +19,6 @@ export default function PlatformActivityRow({
|
|
|
20
19
|
t: (name: string) => string;
|
|
21
20
|
b: typeof import("lucide-react").BotIcon;
|
|
22
21
|
m: typeof import("../components/markdown").Markdown;
|
|
23
|
-
r: typeof import("../lib/format").truncate;
|
|
24
22
|
j: RowJsx;
|
|
25
23
|
s: RowJsxs;
|
|
26
24
|
}) {
|
|
@@ -35,7 +33,11 @@ export default function PlatformActivityRow({
|
|
|
35
33
|
children: "Thought",
|
|
36
34
|
}),
|
|
37
35
|
running: item.streaming,
|
|
38
|
-
|
|
36
|
+
compactPreview: item.text.replace(/\*\*|__|`/g, "").replace(/\s+/g, " "),
|
|
37
|
+
preview: j("span", {
|
|
38
|
+
className: "og-reasoning-preview",
|
|
39
|
+
children: j(Markdown, { streaming: item.streaming, children: item.text }),
|
|
40
|
+
}),
|
|
39
41
|
children: j("div", {
|
|
40
42
|
className: "text-og-base leading-6 text-og-fg-muted [&_strong]:text-og-fg-muted",
|
|
41
43
|
children: j(Markdown, { streaming: item.streaming, children: item.text }),
|
|
@@ -1175,6 +1175,45 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
1175
1175
|
break;
|
|
1176
1176
|
}
|
|
1177
1177
|
|
|
1178
|
+
case "instruction.confirmation.recovered": {
|
|
1179
|
+
const receipt = asRecord(payload.receipt);
|
|
1180
|
+
if (receipt.outcome !== "published") break;
|
|
1181
|
+
closeStreamingTail();
|
|
1182
|
+
items.push({
|
|
1183
|
+
kind: "notice",
|
|
1184
|
+
id: event.id,
|
|
1185
|
+
tone: "input",
|
|
1186
|
+
text: "Your previously approved workspace instruction was saved.",
|
|
1187
|
+
occurredAt: event.occurredAt,
|
|
1188
|
+
});
|
|
1189
|
+
break;
|
|
1190
|
+
}
|
|
1191
|
+
case "knowledge.confirmation.recovered":
|
|
1192
|
+
case "knowledge.source.prepared":
|
|
1193
|
+
case "knowledge.source.failed": {
|
|
1194
|
+
const receipt = asRecord(payload.receipt);
|
|
1195
|
+
const outcome = event.type === "knowledge.source.failed" ? "failed" : receipt.outcome;
|
|
1196
|
+
if (
|
|
1197
|
+
(event.type !== "knowledge.confirmation.recovered" &&
|
|
1198
|
+
typeof payload.fileId !== "string") ||
|
|
1199
|
+
!["published", "pending", "rejected", "archived", "failed"].includes(String(outcome))
|
|
1200
|
+
)
|
|
1201
|
+
break;
|
|
1202
|
+
closeStreamingTail();
|
|
1203
|
+
items.push({
|
|
1204
|
+
kind: "knowledge",
|
|
1205
|
+
id: event.id,
|
|
1206
|
+
turnId,
|
|
1207
|
+
fileId: typeof payload.fileId === "string" ? payload.fileId : undefined,
|
|
1208
|
+
filename: typeof payload.filename === "string" ? payload.filename : undefined,
|
|
1209
|
+
entryId: typeof receipt.entryId === "string" ? receipt.entryId : undefined,
|
|
1210
|
+
status: outcome === "failed" ? "failed" : "complete",
|
|
1211
|
+
outcome: outcome as "published" | "pending" | "rejected" | "archived" | "failed",
|
|
1212
|
+
occurredAt: event.occurredAt,
|
|
1213
|
+
});
|
|
1214
|
+
break;
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1178
1217
|
case "memory.saved":
|
|
1179
1218
|
case "memory.corrected": {
|
|
1180
1219
|
// A first-party memory write is a discrete step, not streamed text, so it
|
|
@@ -1517,6 +1556,7 @@ function isActivityItem(item: TimelineItem): item is ActivityItem {
|
|
|
1517
1556
|
case "sandbox":
|
|
1518
1557
|
case "startup-phase":
|
|
1519
1558
|
case "memory":
|
|
1559
|
+
case "knowledge":
|
|
1520
1560
|
case "fleet-decision":
|
|
1521
1561
|
return true;
|
|
1522
1562
|
default:
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from "react";
|
|
2
|
+
import type { RetainedArtifactReference } from "@opengeni/sdk";
|
|
3
|
+
import type { ToolRendererProps } from "./registry";
|
|
4
|
+
type RetainedImageState =
|
|
5
|
+
| { kind: "loading" }
|
|
6
|
+
| { kind: "ready"; url: string }
|
|
7
|
+
| { kind: "unavailable"; label: string }
|
|
8
|
+
| { kind: "error"; message: string };
|
|
9
|
+
|
|
10
|
+
export function useRetainedImageObjectUrl(
|
|
11
|
+
artifact: RetainedArtifactReference,
|
|
12
|
+
load: ToolRendererProps["loadRetainedArtifact"],
|
|
13
|
+
): RetainedImageState {
|
|
14
|
+
const [state, setState] = useState<RetainedImageState>({ kind: "loading" });
|
|
15
|
+
// Function outputs are commonly serialized JSON. Parsing them creates a new
|
|
16
|
+
// object on every render, so depend on the immutable wire value rather than
|
|
17
|
+
// object identity; otherwise the loader can refetch after its own setState.
|
|
18
|
+
const artifactValue = retainedArtifactValue(artifact);
|
|
19
|
+
const stableArtifactRef = useRef({ value: artifactValue, artifact });
|
|
20
|
+
if (stableArtifactRef.current.value !== artifactValue) {
|
|
21
|
+
stableArtifactRef.current = { value: artifactValue, artifact };
|
|
22
|
+
}
|
|
23
|
+
const stableArtifact = stableArtifactRef.current.artifact;
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (!load) {
|
|
26
|
+
setState({ kind: "unavailable", label: "retrieval is not configured" });
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const controller = new AbortController();
|
|
30
|
+
let objectUrl: string | null = null;
|
|
31
|
+
setState({ kind: "loading" });
|
|
32
|
+
void load(stableArtifact, controller.signal)
|
|
33
|
+
.then((source) => {
|
|
34
|
+
if (controller.signal.aborted) return;
|
|
35
|
+
if (!source) {
|
|
36
|
+
setState({ kind: "unavailable", label: "bytes are unavailable" });
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (!(source instanceof Uint8Array)) {
|
|
40
|
+
if (!source.url.trim()) {
|
|
41
|
+
setState({ kind: "unavailable", label: "URL is unavailable" });
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
setState({ kind: "ready", url: source.url });
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
objectUrl = URL.createObjectURL(
|
|
48
|
+
new Blob([source as unknown as BlobPart], { type: stableArtifact.contentType }),
|
|
49
|
+
);
|
|
50
|
+
setState({ kind: "ready", url: objectUrl });
|
|
51
|
+
})
|
|
52
|
+
.catch((error: unknown) => {
|
|
53
|
+
if (controller.signal.aborted) return;
|
|
54
|
+
const status =
|
|
55
|
+
error && typeof error === "object" && "status" in error
|
|
56
|
+
? Number((error as { status?: unknown }).status)
|
|
57
|
+
: null;
|
|
58
|
+
setState(
|
|
59
|
+
status === 404
|
|
60
|
+
? { kind: "unavailable", label: "deleted" }
|
|
61
|
+
: status === 410
|
|
62
|
+
? { kind: "unavailable", label: "expired or unavailable" }
|
|
63
|
+
: { kind: "error", message: "retrieval failed" },
|
|
64
|
+
);
|
|
65
|
+
});
|
|
66
|
+
return () => {
|
|
67
|
+
controller.abort();
|
|
68
|
+
if (objectUrl) URL.revokeObjectURL(objectUrl);
|
|
69
|
+
};
|
|
70
|
+
}, [stableArtifact, load]);
|
|
71
|
+
return state;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function retainedArtifactValue(artifact: RetainedArtifactReference): string {
|
|
75
|
+
return [
|
|
76
|
+
artifact.artifactId,
|
|
77
|
+
artifact.kind,
|
|
78
|
+
artifact.contentType,
|
|
79
|
+
artifact.originalBytes,
|
|
80
|
+
artifact.sha256,
|
|
81
|
+
artifact.retainedAt,
|
|
82
|
+
artifact.dimensions?.width ?? "",
|
|
83
|
+
artifact.dimensions?.height ?? "",
|
|
84
|
+
artifact.retention.policy,
|
|
85
|
+
artifact.retention.expiresAt ?? "",
|
|
86
|
+
artifact.retrieval.method,
|
|
87
|
+
artifact.retrieval.path,
|
|
88
|
+
artifact.retrieval.acceptRanges,
|
|
89
|
+
artifact.retrieval.maxRangeBytes,
|
|
90
|
+
].join("\0");
|
|
91
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { renderActivity } from "./activity-rail";
|
|
2
|
+
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
|
|
3
|
+
import { WrenchIcon } from "lucide-react";
|
|
4
|
+
import { Suspense, useEffect, useState } from "react";
|
|
5
|
+
import { CompactActivityContext } from "./shared";
|
|
6
|
+
import { defaultToolRegistry } from "./tool-renderers";
|
|
7
|
+
import type { ToolRegistry } from "./registry";
|
|
8
|
+
import { toolDisplayName } from "./tool-display-name";
|
|
9
|
+
import type { ActivityItem } from "./types";
|
|
10
|
+
|
|
11
|
+
/** One stable viewport; updates replace its content without moving the conversation. */
|
|
12
|
+
export function RollingActivity({
|
|
13
|
+
items,
|
|
14
|
+
toolRegistry = defaultToolRegistry,
|
|
15
|
+
previousItem,
|
|
16
|
+
}: {
|
|
17
|
+
items: ActivityItem[];
|
|
18
|
+
/** The standalone item visible immediately before this reel mounted. */
|
|
19
|
+
previousItem?: ActivityItem | undefined;
|
|
20
|
+
toolRegistry?: ToolRegistry;
|
|
21
|
+
}) {
|
|
22
|
+
const reduced = useReducedMotion();
|
|
23
|
+
const [mounted, setMounted] = useState(false);
|
|
24
|
+
useEffect(() => setMounted(true), []);
|
|
25
|
+
const work = items.filter((item) => item.kind !== "startup-phase");
|
|
26
|
+
const active = work.filter((item) =>
|
|
27
|
+
item.kind === "reasoning" ? item.streaming : "status" in item && item.status === "running",
|
|
28
|
+
);
|
|
29
|
+
// Advance with the event order; finishing a parallel tool must not replay an older one.
|
|
30
|
+
const item = !mounted && previousItem ? previousItem : work.at(-1);
|
|
31
|
+
if (!item) return null;
|
|
32
|
+
const earlierCount = Math.max(
|
|
33
|
+
0,
|
|
34
|
+
work.findIndex((entry) => entry.id === item.id),
|
|
35
|
+
);
|
|
36
|
+
const fallback = (
|
|
37
|
+
<span className="og-rolling-label">
|
|
38
|
+
<WrenchIcon className="size-3.5" />
|
|
39
|
+
<span>{item.kind === "tool-call" ? toolDisplayName(item.name) : "Working"}</span>
|
|
40
|
+
</span>
|
|
41
|
+
);
|
|
42
|
+
return (
|
|
43
|
+
<span
|
|
44
|
+
className="og-rolling-status"
|
|
45
|
+
data-running={active.some((entry) => entry.id === item.id) ? "true" : undefined}
|
|
46
|
+
>
|
|
47
|
+
<span className="sr-only">
|
|
48
|
+
{item.kind === "tool-call"
|
|
49
|
+
? toolDisplayName(item.name)
|
|
50
|
+
: item.kind === "reasoning"
|
|
51
|
+
? "Thinking"
|
|
52
|
+
: "Working"}
|
|
53
|
+
</span>
|
|
54
|
+
<span className="og-rolling-window" aria-hidden="true">
|
|
55
|
+
<AnimatePresence initial={false} mode="sync">
|
|
56
|
+
<motion.span
|
|
57
|
+
key={item.id}
|
|
58
|
+
className="og-rolling-face"
|
|
59
|
+
initial={{
|
|
60
|
+
opacity: 0,
|
|
61
|
+
y: reduced ? 0 : 24,
|
|
62
|
+
}}
|
|
63
|
+
animate={{ opacity: 1, y: 0 }}
|
|
64
|
+
exit={{
|
|
65
|
+
opacity: 0,
|
|
66
|
+
y: reduced ? 0 : -24,
|
|
67
|
+
}}
|
|
68
|
+
transition={{ duration: reduced ? 0 : 0.4, ease: [0.22, 1, 0.36, 1] }}
|
|
69
|
+
>
|
|
70
|
+
<CompactActivityContext.Provider value={true}>
|
|
71
|
+
<Suspense fallback={fallback}>
|
|
72
|
+
{renderActivity(item, toolRegistry, undefined, undefined, undefined, undefined)}
|
|
73
|
+
</Suspense>
|
|
74
|
+
</CompactActivityContext.Provider>
|
|
75
|
+
</motion.span>
|
|
76
|
+
</AnimatePresence>
|
|
77
|
+
</span>
|
|
78
|
+
{earlierCount > 0 ? (
|
|
79
|
+
<span className="og-rolling-count">{`+${earlierCount} earlier`}</span>
|
|
80
|
+
) : null}
|
|
81
|
+
</span>
|
|
82
|
+
);
|
|
83
|
+
}
|
package/src/timeline/shared.tsx
CHANGED
|
@@ -55,6 +55,8 @@ export function ToolCallTruncationProvider({
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
export type ActivityDisclosureProps = {
|
|
58
|
+
/** Override the rolling preview; null keeps rapidly streaming details out of the header. */
|
|
59
|
+
compactPreview?: ReactNode;
|
|
58
60
|
icon: ReactNode;
|
|
59
61
|
/** Icon tint. Defaults to the muted foreground; renderers pass accent/failed. */
|
|
60
62
|
iconTone?: "accent" | "failed" | "running" | "muted" | undefined;
|
|
@@ -116,6 +118,8 @@ const ICON_TONE: Record<NonNullable<ActivityDisclosureProps["iconTone"]>, string
|
|
|
116
118
|
* inline media, and at most one right-gutter settle chip. Compact by default;
|
|
117
119
|
* the body mounts only when expanded.
|
|
118
120
|
*/
|
|
121
|
+
export const CompactActivityContext = createContext(false);
|
|
122
|
+
|
|
119
123
|
export function ActivityDisclosure({
|
|
120
124
|
icon,
|
|
121
125
|
iconTone: iconToneProp = "muted",
|
|
@@ -123,6 +127,7 @@ export function ActivityDisclosure({
|
|
|
123
127
|
titleMono,
|
|
124
128
|
running,
|
|
125
129
|
preview,
|
|
130
|
+
compactPreview,
|
|
126
131
|
media,
|
|
127
132
|
chip: chipProp,
|
|
128
133
|
failed,
|
|
@@ -150,6 +155,21 @@ export function ActivityDisclosure({
|
|
|
150
155
|
const forcedDefaultOpen = useForcedDefaultOpen();
|
|
151
156
|
const [open, setOpen] = useState(defaultOpen ?? forcedDefaultOpen ?? false);
|
|
152
157
|
const truncation = useContext(ToolCallTruncationContext);
|
|
158
|
+
const compact = useContext(CompactActivityContext);
|
|
159
|
+
if (compact)
|
|
160
|
+
return (
|
|
161
|
+
<span className="og-rolling-label">
|
|
162
|
+
<span className={cn("shrink-0", ICON_TONE[iconTone])}>{icon}</span>
|
|
163
|
+
<span className="og-command-reel">
|
|
164
|
+
<span className="og-reel-title">{title}</span>
|
|
165
|
+
{(compactPreview === undefined ? preview : compactPreview) ? (
|
|
166
|
+
<span className="og-reel-preview text-og-fg-subtle">
|
|
167
|
+
{compactPreview === undefined ? preview : compactPreview}
|
|
168
|
+
</span>
|
|
169
|
+
) : null}
|
|
170
|
+
</span>
|
|
171
|
+
</span>
|
|
172
|
+
);
|
|
153
173
|
const hasBody = expandable && (children != null || truncation != null);
|
|
154
174
|
|
|
155
175
|
// The preview is detail-on-demand: it is suppressed once the row is open so a
|
|
@@ -189,20 +209,21 @@ export function ActivityDisclosure({
|
|
|
189
209
|
<span className="size-3.5 shrink-0" />
|
|
190
210
|
)}
|
|
191
211
|
<span className={cn("shrink-0", ICON_TONE[iconTone])}>{icon}</span>
|
|
192
|
-
<span
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
212
|
+
<span className={cn("og-command-reel", running && "og-command-reel-running")}>
|
|
213
|
+
<span
|
|
214
|
+
className={cn(
|
|
215
|
+
"min-w-0 shrink truncate text-og-base font-medium",
|
|
216
|
+
titleMono && "font-og-mono text-og-sm font-normal",
|
|
217
|
+
)}
|
|
218
|
+
>
|
|
219
|
+
{title}
|
|
220
|
+
</span>
|
|
221
|
+
{previewVisible && !media ? (
|
|
222
|
+
<span className="min-w-0 flex-1 truncate text-og-sm text-og-fg-subtle">{preview}</span>
|
|
223
|
+
) : (
|
|
224
|
+
<span className="flex-1" />
|
|
197
225
|
)}
|
|
198
|
-
>
|
|
199
|
-
{title}
|
|
200
226
|
</span>
|
|
201
|
-
{previewVisible && !media ? (
|
|
202
|
-
<span className="min-w-0 flex-1 truncate text-og-sm text-og-fg-subtle">{preview}</span>
|
|
203
|
-
) : (
|
|
204
|
-
<span className="flex-1" />
|
|
205
|
-
)}
|
|
206
227
|
{/* The right gutter carries at most ONE signal: the media thumbnail, else a
|
|
207
228
|
terse settle chip (hidden once expanded — the body owns the detail). */}
|
|
208
229
|
{media && !open ? (
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { KnowledgeReceiptRow } from "./knowledge-receipt";
|
|
2
|
+
import { useRetainedImageObjectUrl } from "./retained-image";
|
|
1
3
|
import {
|
|
2
4
|
parseSandboxFileArtifactReceipt,
|
|
3
5
|
type GitFileDiff,
|
|
@@ -34,7 +36,7 @@ import {
|
|
|
34
36
|
VideoIcon,
|
|
35
37
|
WrenchIcon,
|
|
36
38
|
} from "lucide-react";
|
|
37
|
-
import {
|
|
39
|
+
import { useContext, useState, type ReactNode } from "react";
|
|
38
40
|
import { formatBytes, stringifyPayload, tryParseJson } from "../lib/format";
|
|
39
41
|
import { useTimelineComputeLabel } from "./compute-label";
|
|
40
42
|
import {
|
|
@@ -71,6 +73,7 @@ import {
|
|
|
71
73
|
TermBlock,
|
|
72
74
|
Thumbnail,
|
|
73
75
|
ActivityDisclosure,
|
|
76
|
+
CompactActivityContext,
|
|
74
77
|
type DisclosureChip,
|
|
75
78
|
} from "./shared";
|
|
76
79
|
import { RawPatch, ToolDiff } from "./tool-diff";
|
|
@@ -96,9 +99,12 @@ const ICON_SIZE = "size-3.5";
|
|
|
96
99
|
* left-to-right.
|
|
97
100
|
*/
|
|
98
101
|
function RunningPreview({ children }: { children: ReactNode }) {
|
|
102
|
+
const compact = useContext(CompactActivityContext);
|
|
99
103
|
return (
|
|
100
104
|
<span className="inline-flex items-center gap-1.5">
|
|
101
|
-
|
|
105
|
+
{!compact && (
|
|
106
|
+
<span className="size-1.5 shrink-0 animate-og-pulse rounded-full bg-og-status-running" />
|
|
107
|
+
)}
|
|
102
108
|
<span className="min-w-0 truncate">{children}</span>
|
|
103
109
|
</span>
|
|
104
110
|
);
|
|
@@ -766,95 +772,6 @@ function ComputerCallRenderer({ item, loadRetainedScreenshot }: ToolRendererProp
|
|
|
766
772
|
);
|
|
767
773
|
}
|
|
768
774
|
|
|
769
|
-
type RetainedImageState =
|
|
770
|
-
| { kind: "loading" }
|
|
771
|
-
| { kind: "ready"; url: string }
|
|
772
|
-
| { kind: "unavailable"; label: string }
|
|
773
|
-
| { kind: "error"; message: string };
|
|
774
|
-
|
|
775
|
-
function useRetainedImageObjectUrl(
|
|
776
|
-
artifact: RetainedArtifactReference,
|
|
777
|
-
load: ToolRendererProps["loadRetainedArtifact"],
|
|
778
|
-
): RetainedImageState {
|
|
779
|
-
const [state, setState] = useState<RetainedImageState>({ kind: "loading" });
|
|
780
|
-
// Function outputs are commonly serialized JSON. Parsing them creates a new
|
|
781
|
-
// object on every render, so depend on the immutable wire value rather than
|
|
782
|
-
// object identity; otherwise the loader can refetch after its own setState.
|
|
783
|
-
const artifactValue = retainedArtifactValue(artifact);
|
|
784
|
-
const stableArtifactRef = useRef({ value: artifactValue, artifact });
|
|
785
|
-
if (stableArtifactRef.current.value !== artifactValue) {
|
|
786
|
-
stableArtifactRef.current = { value: artifactValue, artifact };
|
|
787
|
-
}
|
|
788
|
-
const stableArtifact = stableArtifactRef.current.artifact;
|
|
789
|
-
useEffect(() => {
|
|
790
|
-
if (!load) {
|
|
791
|
-
setState({ kind: "unavailable", label: "retrieval is not configured" });
|
|
792
|
-
return;
|
|
793
|
-
}
|
|
794
|
-
const controller = new AbortController();
|
|
795
|
-
let objectUrl: string | null = null;
|
|
796
|
-
setState({ kind: "loading" });
|
|
797
|
-
void load(stableArtifact, controller.signal)
|
|
798
|
-
.then((source) => {
|
|
799
|
-
if (controller.signal.aborted) return;
|
|
800
|
-
if (!source) {
|
|
801
|
-
setState({ kind: "unavailable", label: "bytes are unavailable" });
|
|
802
|
-
return;
|
|
803
|
-
}
|
|
804
|
-
if (!(source instanceof Uint8Array)) {
|
|
805
|
-
if (!source.url.trim()) {
|
|
806
|
-
setState({ kind: "unavailable", label: "URL is unavailable" });
|
|
807
|
-
return;
|
|
808
|
-
}
|
|
809
|
-
setState({ kind: "ready", url: source.url });
|
|
810
|
-
return;
|
|
811
|
-
}
|
|
812
|
-
objectUrl = URL.createObjectURL(
|
|
813
|
-
new Blob([source as unknown as BlobPart], { type: stableArtifact.contentType }),
|
|
814
|
-
);
|
|
815
|
-
setState({ kind: "ready", url: objectUrl });
|
|
816
|
-
})
|
|
817
|
-
.catch((error: unknown) => {
|
|
818
|
-
if (controller.signal.aborted) return;
|
|
819
|
-
const status =
|
|
820
|
-
error && typeof error === "object" && "status" in error
|
|
821
|
-
? Number((error as { status?: unknown }).status)
|
|
822
|
-
: null;
|
|
823
|
-
setState(
|
|
824
|
-
status === 404
|
|
825
|
-
? { kind: "unavailable", label: "deleted" }
|
|
826
|
-
: status === 410
|
|
827
|
-
? { kind: "unavailable", label: "expired or unavailable" }
|
|
828
|
-
: { kind: "error", message: "retrieval failed" },
|
|
829
|
-
);
|
|
830
|
-
});
|
|
831
|
-
return () => {
|
|
832
|
-
controller.abort();
|
|
833
|
-
if (objectUrl) URL.revokeObjectURL(objectUrl);
|
|
834
|
-
};
|
|
835
|
-
}, [stableArtifact, load]);
|
|
836
|
-
return state;
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
function retainedArtifactValue(artifact: RetainedArtifactReference): string {
|
|
840
|
-
return [
|
|
841
|
-
artifact.artifactId,
|
|
842
|
-
artifact.kind,
|
|
843
|
-
artifact.contentType,
|
|
844
|
-
artifact.originalBytes,
|
|
845
|
-
artifact.sha256,
|
|
846
|
-
artifact.retainedAt,
|
|
847
|
-
artifact.dimensions?.width ?? "",
|
|
848
|
-
artifact.dimensions?.height ?? "",
|
|
849
|
-
artifact.retention.policy,
|
|
850
|
-
artifact.retention.expiresAt ?? "",
|
|
851
|
-
artifact.retrieval.method,
|
|
852
|
-
artifact.retrieval.path,
|
|
853
|
-
artifact.retrieval.acceptRanges,
|
|
854
|
-
artifact.retrieval.maxRangeBytes,
|
|
855
|
-
].join("\0");
|
|
856
|
-
}
|
|
857
|
-
|
|
858
775
|
function RetainedSessionImageDisclosure({
|
|
859
776
|
artifact,
|
|
860
777
|
load,
|
|
@@ -2370,7 +2287,54 @@ function GenericToolIcon({ name }: { name: string }) {
|
|
|
2370
2287
|
|
|
2371
2288
|
/* ---- the default registry -------------------------------------------------- */
|
|
2372
2289
|
|
|
2290
|
+
function KnowledgeSaveRenderer({ item }: ToolRendererProps) {
|
|
2291
|
+
const output = unwrapMcpOutput(item.output);
|
|
2292
|
+
const parsed = tryParseJson(output.text);
|
|
2293
|
+
if (item.status === "running" || output.isError || !parsed || typeof parsed !== "object")
|
|
2294
|
+
return <GenericRenderer item={item} />;
|
|
2295
|
+
const value = parsed as Record<string, unknown>;
|
|
2296
|
+
const receipt = (value.status === "retained" ? value.receipt : value) as
|
|
2297
|
+
| Record<string, unknown>
|
|
2298
|
+
| undefined;
|
|
2299
|
+
if (
|
|
2300
|
+
!receipt ||
|
|
2301
|
+
typeof receipt.entryId !== "string" ||
|
|
2302
|
+
!["published", "pending", "rejected", "archived"].includes(String(receipt.outcome))
|
|
2303
|
+
)
|
|
2304
|
+
return <GenericRenderer item={item} />;
|
|
2305
|
+
const args = parseToolArgs(item.arguments);
|
|
2306
|
+
const entry = args.entry as Record<string, unknown> | undefined;
|
|
2307
|
+
return (
|
|
2308
|
+
<KnowledgeReceiptRow
|
|
2309
|
+
outcome={receipt.outcome as "published" | "pending" | "rejected" | "archived"}
|
|
2310
|
+
entryId={receipt.entryId}
|
|
2311
|
+
title={
|
|
2312
|
+
typeof entry?.title === "string"
|
|
2313
|
+
? entry.title
|
|
2314
|
+
: typeof value.filename === "string"
|
|
2315
|
+
? value.filename
|
|
2316
|
+
: undefined
|
|
2317
|
+
}
|
|
2318
|
+
source={value.status === "retained" || value.retained === true}
|
|
2319
|
+
/>
|
|
2320
|
+
);
|
|
2321
|
+
}
|
|
2322
|
+
|
|
2373
2323
|
const BASE_ENTRIES: ToolRegistryEntry[] = [
|
|
2324
|
+
...[
|
|
2325
|
+
"knowledge_save",
|
|
2326
|
+
"knowledge_archive",
|
|
2327
|
+
"knowledge_retain_file",
|
|
2328
|
+
"knowledge_retain_message",
|
|
2329
|
+
"task_note_promote_knowledge",
|
|
2330
|
+
].flatMap((name) =>
|
|
2331
|
+
[name, `opengeni__${name}`, `mcp__opengeni__${name}`].map((trustedName) => ({
|
|
2332
|
+
match: "name" as const,
|
|
2333
|
+
name: trustedName,
|
|
2334
|
+
matchPrefixedLeaf: false,
|
|
2335
|
+
render: KnowledgeSaveRenderer,
|
|
2336
|
+
})),
|
|
2337
|
+
),
|
|
2374
2338
|
// Provider-native items carry `raw.type` on the wire — this is their source of
|
|
2375
2339
|
// truth and is consulted first by the registry.
|
|
2376
2340
|
{ match: "rawType", type: "apply_patch_call", render: ApplyPatchRenderer },
|