@opengeni/react 5.0.4 → 5.0.5-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 +3 -0
- package/dist/{chunk-SCZ6SUBW.js → chunk-H63SQTGI.js} +1258 -1837
- package/dist/chunk-H63SQTGI.js.map +1 -0
- package/dist/chunk-HQV2I5HV.js +124 -0
- package/dist/chunk-HQV2I5HV.js.map +1 -0
- package/dist/chunk-M2HNWZYR.js +835 -0
- package/dist/chunk-M2HNWZYR.js.map +1 -0
- package/dist/{chunk-Z7WBDB4E.js → chunk-V3WX3TQG.js} +283 -391
- package/dist/chunk-V3WX3TQG.js.map +1 -0
- package/dist/components/message-timeline.d.ts +3 -1
- package/dist/components/sandbox-workspace.d.ts +5 -0
- package/dist/composer.js +2 -1
- package/dist/connect-chooser.d.ts +1 -0
- package/dist/connect-panel.d.ts +2 -1
- package/dist/connect.d.ts +7 -0
- package/dist/connect.js +792 -183
- package/dist/connect.js.map +1 -1
- package/dist/connection-catalog.d.ts +33 -0
- package/dist/connection-installed.d.ts +15 -0
- package/dist/connection-logo.d.ts +6 -0
- package/dist/connection-type-picker.d.ts +6 -0
- package/dist/device-authorization.d.ts +9 -1
- package/dist/index.js +39 -12
- package/dist/index.js.map +1 -1
- package/dist/plugin-details.d.ts +14 -0
- package/dist/plugin-discovery.d.ts +14 -0
- package/dist/session-ui.d.ts +3 -0
- package/dist/session-ui.js +113 -2
- package/dist/session-ui.js.map +1 -1
- package/dist/skill-discovery.d.ts +29 -0
- package/dist/timeline/activity-rail.d.ts +3 -1
- package/dist/timeline/genie-loading.d.ts +24 -0
- package/dist/timeline/startup-preference.d.ts +3 -0
- package/dist/timeline/startup-timings.d.ts +4 -0
- package/package.json +4 -3
- package/src/components/message-timeline.tsx +127 -20
- package/src/components/sandbox-workspace.tsx +29 -0
- package/src/connect-chooser.tsx +81 -21
- package/src/connect-panel.tsx +7 -1
- package/src/connect.ts +21 -0
- package/src/connection-catalog.tsx +153 -0
- package/src/connection-installed.tsx +45 -0
- package/src/connection-logo.tsx +29 -0
- package/src/connection-type-picker.tsx +36 -0
- package/src/device-authorization.tsx +9 -1
- package/src/hooks/use-codex-accounts.ts +1 -0
- package/src/plugin-details.tsx +182 -0
- package/src/plugin-discovery.tsx +161 -0
- package/src/session-ui.ts +3 -0
- package/src/skill-discovery.tsx +152 -0
- package/src/timeline/activity-rail.tsx +75 -4
- package/src/timeline/genie-loading.tsx +112 -0
- package/src/timeline/startup-preference.ts +33 -0
- package/src/timeline/startup-timings.tsx +145 -0
- package/src/timeline/turn-summary.tsx +8 -3
- package/styles/compiled.css +73 -0
- package/styles/connect.css +149 -1
- package/styles/index.css +15 -0
- package/dist/chunk-SCZ6SUBW.js.map +0 -1
- package/dist/chunk-Z7WBDB4E.js.map +0 -1
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type PluginDiscoveryItem } from "@opengeni/sdk";
|
|
2
|
+
/** Embeddable plugin overview. Hosts own the dialog and installation actions. */
|
|
3
|
+
export declare function PluginDetails({ item, onInstall, busy, installed, error, connections, onConnect, }: {
|
|
4
|
+
item: PluginDiscoveryItem;
|
|
5
|
+
onInstall?: () => void;
|
|
6
|
+
busy?: boolean;
|
|
7
|
+
installed?: boolean;
|
|
8
|
+
error?: string | null;
|
|
9
|
+
connections?: Record<string, boolean>;
|
|
10
|
+
onConnect?: (server: {
|
|
11
|
+
name: string;
|
|
12
|
+
endpoint: string | null;
|
|
13
|
+
}) => void;
|
|
14
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { PluginDiscoveryItem, PluginDiscoveryPage } from "@opengeni/sdk";
|
|
2
|
+
export type PluginDiscoveryProps = {
|
|
3
|
+
client: {
|
|
4
|
+
discoverPlugins(workspaceId: string, options?: {
|
|
5
|
+
query?: string;
|
|
6
|
+
provider?: string;
|
|
7
|
+
offset?: number;
|
|
8
|
+
}): Promise<PluginDiscoveryPage>;
|
|
9
|
+
};
|
|
10
|
+
workspaceId: string;
|
|
11
|
+
query: string;
|
|
12
|
+
onOpen: (item: PluginDiscoveryItem) => void;
|
|
13
|
+
};
|
|
14
|
+
export declare function PluginDiscovery(props: PluginDiscoveryProps): import("react").JSX.Element;
|
package/dist/session-ui.d.ts
CHANGED
|
@@ -19,3 +19,6 @@ export type { QueueSurfaceProps } from "./components/queue-surface.js";
|
|
|
19
19
|
export { SessionChrome, sessionChromeGoalPillExplanation, sessionChromeGoalPillLabel, sessionChromeGoalPillState, } from "./components/session-chrome.js";
|
|
20
20
|
export type { SessionChromeAgentsSignal, SessionChromeProps, SessionChromeSignalId, SessionChromeSignalTone, } from "./components/session-chrome.js";
|
|
21
21
|
export { SessionCommandsPanel } from "./components/session-commands-panel.js";
|
|
22
|
+
export { StartupTimings } from "./timeline/startup-timings.js";
|
|
23
|
+
export { useStartupDetails, setStartupDetails } from "./timeline/startup-preference.js";
|
|
24
|
+
export type { GenieLoadingOptions } from "./timeline/genie-loading.js";
|
package/dist/session-ui.js
CHANGED
|
@@ -13,15 +13,19 @@ import {
|
|
|
13
13
|
sessionChromeGoalPillExplanation,
|
|
14
14
|
sessionChromeGoalPillLabel,
|
|
15
15
|
sessionChromeGoalPillState,
|
|
16
|
+
setStartupDetails,
|
|
17
|
+
useStartupDetails,
|
|
16
18
|
userMessageLikelyNeedsDisclosure
|
|
17
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-H63SQTGI.js";
|
|
18
20
|
import "./chunk-YBM6CQRU.js";
|
|
19
21
|
import "./chunk-5M6VTFJ5.js";
|
|
20
22
|
import "./chunk-ZYRGEKWS.js";
|
|
21
23
|
import "./chunk-NOSYBS2D.js";
|
|
22
24
|
import "./chunk-PAJU5KKL.js";
|
|
25
|
+
import "./chunk-M2HNWZYR.js";
|
|
23
26
|
import "./chunk-JVYQRRC4.js";
|
|
24
|
-
import "./chunk-
|
|
27
|
+
import "./chunk-V3WX3TQG.js";
|
|
28
|
+
import "./chunk-HQV2I5HV.js";
|
|
25
29
|
import "./chunk-R4KRSFGA.js";
|
|
26
30
|
import "./chunk-VZTQ73XT.js";
|
|
27
31
|
import {
|
|
@@ -33,6 +37,110 @@ import "./chunk-KZ73RL76.js";
|
|
|
33
37
|
import "./chunk-NEXLLTP4.js";
|
|
34
38
|
import "./chunk-VRBL3F5E.js";
|
|
35
39
|
import "./chunk-22KP6LR5.js";
|
|
40
|
+
|
|
41
|
+
// src/timeline/startup-timings.tsx
|
|
42
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
43
|
+
var LABELS = {
|
|
44
|
+
queue: "Worker queue",
|
|
45
|
+
sandbox: "Sandbox",
|
|
46
|
+
rig: "Rig",
|
|
47
|
+
repository: "Repository",
|
|
48
|
+
files: "Files",
|
|
49
|
+
tools: "Tools",
|
|
50
|
+
model_preparation: "Runtime & model request",
|
|
51
|
+
provider_first_byte: "First model response"
|
|
52
|
+
};
|
|
53
|
+
function StartupTimings({ phases }) {
|
|
54
|
+
if (!phases.length)
|
|
55
|
+
return /* @__PURE__ */ jsx("p", { className: "text-og-sm text-og-fg-subtle", children: "No startup timings recorded yet." });
|
|
56
|
+
const byTurn = /* @__PURE__ */ new Map();
|
|
57
|
+
for (const phase of phases) {
|
|
58
|
+
const group = byTurn.get(phase.turnId) ?? [];
|
|
59
|
+
group.push(phase);
|
|
60
|
+
byTurn.set(phase.turnId, group);
|
|
61
|
+
}
|
|
62
|
+
const turns = [...byTurn.entries()].map(([turnId, turnPhases]) => ({
|
|
63
|
+
turnId,
|
|
64
|
+
phases: turnPhases,
|
|
65
|
+
startedAt: turnPhases.reduce(
|
|
66
|
+
(earliest, phase) => phase.startedAt < earliest ? phase.startedAt : earliest,
|
|
67
|
+
turnPhases[0].startedAt
|
|
68
|
+
)
|
|
69
|
+
})).sort((a, b) => b.startedAt.localeCompare(a.startedAt));
|
|
70
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
71
|
+
/* @__PURE__ */ jsx("p", { className: "text-og-xs text-og-fg-subtle", children: "Each turn has its own startup. Phases can overlap; durations are not additive." }),
|
|
72
|
+
turns.map((turn, index) => /* @__PURE__ */ jsx(
|
|
73
|
+
StartupTurn,
|
|
74
|
+
{
|
|
75
|
+
...turn,
|
|
76
|
+
latest: index === 0 && turn.turnId !== null
|
|
77
|
+
},
|
|
78
|
+
turn.turnId ?? "unassigned"
|
|
79
|
+
))
|
|
80
|
+
] });
|
|
81
|
+
}
|
|
82
|
+
function StartupTurn({
|
|
83
|
+
turnId,
|
|
84
|
+
phases,
|
|
85
|
+
startedAt,
|
|
86
|
+
latest
|
|
87
|
+
}) {
|
|
88
|
+
const status = phases.some((phase) => phase.status === "failed") ? "Failed" : phases.some((phase) => phase.status === "running") ? "In progress" : phases.some((phase) => phase.status === "cancelled") ? "Interrupted" : "Ready";
|
|
89
|
+
return /* @__PURE__ */ jsxs("details", { open: latest, className: "group border-b border-og-border pb-3", children: [
|
|
90
|
+
/* @__PURE__ */ jsxs("summary", { className: "cursor-pointer rounded py-3 text-og-sm text-og-fg-muted focus-visible:outline-2 focus-visible:outline-og-accent", children: [
|
|
91
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium", children: turnId === null ? "Unassigned events" : latest ? "Latest turn" : "Earlier turn" }),
|
|
92
|
+
/* @__PURE__ */ jsx("span", { className: "ml-2 text-og-xs text-og-fg-subtle", children: new Date(startedAt).toLocaleTimeString([], {
|
|
93
|
+
hour: "2-digit",
|
|
94
|
+
minute: "2-digit",
|
|
95
|
+
second: "2-digit"
|
|
96
|
+
}) }),
|
|
97
|
+
/* @__PURE__ */ jsx(
|
|
98
|
+
"span",
|
|
99
|
+
{
|
|
100
|
+
className: status === "Failed" ? "ml-2 text-og-xs text-og-status-failed" : "ml-2 text-og-xs text-og-fg-subtle",
|
|
101
|
+
children: status
|
|
102
|
+
}
|
|
103
|
+
),
|
|
104
|
+
/* @__PURE__ */ jsx("span", { className: "mt-1 block break-all pl-4 font-og-mono text-og-xs text-og-fg-subtle", children: turnId ?? "No turn ID recorded" })
|
|
105
|
+
] }),
|
|
106
|
+
/* @__PURE__ */ jsxs("div", { className: "pb-2", children: [
|
|
107
|
+
/* @__PURE__ */ jsxs("p", { className: "text-og-xs text-og-fg-subtle", children: [
|
|
108
|
+
"Started ",
|
|
109
|
+
/* @__PURE__ */ jsx("time", { dateTime: startedAt, children: new Date(startedAt).toLocaleString() })
|
|
110
|
+
] }),
|
|
111
|
+
/* @__PURE__ */ jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-left text-og-xs", children: [
|
|
112
|
+
/* @__PURE__ */ jsxs("caption", { className: "sr-only", children: [
|
|
113
|
+
"Startup phases for ",
|
|
114
|
+
turnId ?? "unassigned events"
|
|
115
|
+
] }),
|
|
116
|
+
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "border-b border-og-border text-og-fg-subtle", children: [
|
|
117
|
+
/* @__PURE__ */ jsx("th", { scope: "col", className: "py-2 font-medium", children: "Phase" }),
|
|
118
|
+
/* @__PURE__ */ jsx("th", { scope: "col", className: "px-3 py-2 font-medium", children: "Status" }),
|
|
119
|
+
/* @__PURE__ */ jsx("th", { scope: "col", className: "py-2 text-right font-medium", children: "Duration" })
|
|
120
|
+
] }) }),
|
|
121
|
+
/* @__PURE__ */ jsx("tbody", { children: phases.map((phase) => /* @__PURE__ */ jsxs(
|
|
122
|
+
"tr",
|
|
123
|
+
{
|
|
124
|
+
className: "border-b border-og-border/40",
|
|
125
|
+
title: `Turn: ${phase.turnId ?? "unknown"} \xB7 Started: ${phase.startedAt}`,
|
|
126
|
+
children: [
|
|
127
|
+
/* @__PURE__ */ jsx("th", { scope: "row", className: "py-2.5 font-normal text-og-fg-muted", children: LABELS[phase.phase] }),
|
|
128
|
+
/* @__PURE__ */ jsx(
|
|
129
|
+
"td",
|
|
130
|
+
{
|
|
131
|
+
className: `px-3 py-2.5 ${phase.status === "failed" ? "text-og-status-failed" : "text-og-fg-subtle"}`,
|
|
132
|
+
children: phase.status === "running" ? "In progress" : phase.status
|
|
133
|
+
}
|
|
134
|
+
),
|
|
135
|
+
/* @__PURE__ */ jsx("td", { className: "py-2.5 text-right font-og-mono tabular-nums text-og-fg-muted", children: phase.durationMs === null ? "\u2014" : phase.durationMs < 1e3 ? `${Math.round(phase.durationMs)} ms` : `${(phase.durationMs / 1e3).toFixed(1)} s` })
|
|
136
|
+
]
|
|
137
|
+
},
|
|
138
|
+
phase.id
|
|
139
|
+
)) })
|
|
140
|
+
] }) })
|
|
141
|
+
] })
|
|
142
|
+
] });
|
|
143
|
+
}
|
|
36
144
|
export {
|
|
37
145
|
ApprovalSurface,
|
|
38
146
|
BUILT_IN_TURN_SUMMARY_FACET_IDS,
|
|
@@ -43,12 +151,15 @@ export {
|
|
|
43
151
|
SessionChrome,
|
|
44
152
|
SessionCommandsPanel,
|
|
45
153
|
SessionConversation,
|
|
154
|
+
StartupTimings,
|
|
46
155
|
TimelineRow,
|
|
47
156
|
UserMessageBody,
|
|
48
157
|
createOlderHistoryLoadReceipt,
|
|
49
158
|
sessionChromeGoalPillExplanation,
|
|
50
159
|
sessionChromeGoalPillLabel,
|
|
51
160
|
sessionChromeGoalPillState,
|
|
161
|
+
setStartupDetails,
|
|
162
|
+
useStartupDetails,
|
|
52
163
|
userMessageLikelyNeedsDisclosure
|
|
53
164
|
};
|
|
54
165
|
//# sourceMappingURL=session-ui.js.map
|
package/dist/session-ui.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/timeline/startup-timings.tsx"],"sourcesContent":["import type { StartupPhaseItem } from \"./types\";\n\nconst LABELS: Record<StartupPhaseItem[\"phase\"], string> = {\n queue: \"Worker queue\",\n sandbox: \"Sandbox\",\n rig: \"Rig\",\n repository: \"Repository\",\n files: \"Files\",\n tools: \"Tools\",\n model_preparation: \"Runtime & model request\",\n provider_first_byte: \"First model response\",\n};\n\nexport function StartupTimings({ phases }: { phases: StartupPhaseItem[] }) {\n if (!phases.length)\n return <p className=\"text-og-sm text-og-fg-subtle\">No startup timings recorded yet.</p>;\n const byTurn = new Map<string | null, StartupPhaseItem[]>();\n for (const phase of phases) {\n const group = byTurn.get(phase.turnId) ?? [];\n group.push(phase);\n byTurn.set(phase.turnId, group);\n }\n const turns = [...byTurn.entries()]\n .map(([turnId, turnPhases]) => ({\n turnId,\n phases: turnPhases,\n startedAt: turnPhases.reduce(\n (earliest, phase) => (phase.startedAt < earliest ? phase.startedAt : earliest),\n turnPhases[0]!.startedAt,\n ),\n }))\n .sort((a, b) => b.startedAt.localeCompare(a.startedAt));\n return (\n <div className=\"space-y-3\">\n <p className=\"text-og-xs text-og-fg-subtle\">\n Each turn has its own startup. Phases can overlap; durations are not additive.\n </p>\n {turns.map((turn, index) => (\n <StartupTurn\n key={turn.turnId ?? \"unassigned\"}\n {...turn}\n latest={index === 0 && turn.turnId !== null}\n />\n ))}\n </div>\n );\n}\n\nfunction StartupTurn({\n turnId,\n phases,\n startedAt,\n latest,\n}: {\n turnId: string | null;\n phases: StartupPhaseItem[];\n startedAt: string;\n latest: boolean;\n}) {\n const status = phases.some((phase) => phase.status === \"failed\")\n ? \"Failed\"\n : phases.some((phase) => phase.status === \"running\")\n ? \"In progress\"\n : phases.some((phase) => phase.status === \"cancelled\")\n ? \"Interrupted\"\n : \"Ready\";\n return (\n <details open={latest} className=\"group border-b border-og-border pb-3\">\n <summary className=\"cursor-pointer rounded py-3 text-og-sm text-og-fg-muted focus-visible:outline-2 focus-visible:outline-og-accent\">\n <span className=\"font-medium\">\n {turnId === null ? \"Unassigned events\" : latest ? \"Latest turn\" : \"Earlier turn\"}\n </span>\n <span className=\"ml-2 text-og-xs text-og-fg-subtle\">\n {new Date(startedAt).toLocaleTimeString([], {\n hour: \"2-digit\",\n minute: \"2-digit\",\n second: \"2-digit\",\n })}\n </span>\n <span\n className={\n status === \"Failed\"\n ? \"ml-2 text-og-xs text-og-status-failed\"\n : \"ml-2 text-og-xs text-og-fg-subtle\"\n }\n >\n {status}\n </span>\n <span className=\"mt-1 block break-all pl-4 font-og-mono text-og-xs text-og-fg-subtle\">\n {turnId ?? \"No turn ID recorded\"}\n </span>\n </summary>\n <div className=\"pb-2\">\n <p className=\"text-og-xs text-og-fg-subtle\">\n Started <time dateTime={startedAt}>{new Date(startedAt).toLocaleString()}</time>\n </p>\n <div className=\"overflow-x-auto\">\n <table className=\"w-full text-left text-og-xs\">\n <caption className=\"sr-only\">\n Startup phases for {turnId ?? \"unassigned events\"}\n </caption>\n <thead>\n <tr className=\"border-b border-og-border text-og-fg-subtle\">\n <th scope=\"col\" className=\"py-2 font-medium\">\n Phase\n </th>\n <th scope=\"col\" className=\"px-3 py-2 font-medium\">\n Status\n </th>\n <th scope=\"col\" className=\"py-2 text-right font-medium\">\n Duration\n </th>\n </tr>\n </thead>\n <tbody>\n {phases.map((phase) => (\n <tr\n key={phase.id}\n className=\"border-b border-og-border/40\"\n title={`Turn: ${phase.turnId ?? \"unknown\"} · Started: ${phase.startedAt}`}\n >\n <th scope=\"row\" className=\"py-2.5 font-normal text-og-fg-muted\">\n {LABELS[phase.phase]}\n </th>\n <td\n className={`px-3 py-2.5 ${phase.status === \"failed\" ? \"text-og-status-failed\" : \"text-og-fg-subtle\"}`}\n >\n {phase.status === \"running\" ? \"In progress\" : phase.status}\n </td>\n <td className=\"py-2.5 text-right font-og-mono tabular-nums text-og-fg-muted\">\n {phase.durationMs === null\n ? \"—\"\n : phase.durationMs < 1000\n ? `${Math.round(phase.durationMs)} ms`\n : `${(phase.durationMs / 1000).toFixed(1)} s`}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </div>\n </details>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type SkillDiscoveryItem = {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
source: string;
|
|
5
|
+
installs: number;
|
|
6
|
+
url: string;
|
|
7
|
+
};
|
|
8
|
+
export type SkillDiscoveryPage = {
|
|
9
|
+
items: SkillDiscoveryItem[];
|
|
10
|
+
nextCursor: null;
|
|
11
|
+
};
|
|
12
|
+
export type SkillDiscoveryClient = {
|
|
13
|
+
searchPublicSkills(workspaceId: string, query: string): Promise<SkillDiscoveryPage>;
|
|
14
|
+
};
|
|
15
|
+
export type SkillDiscoveryProps = {
|
|
16
|
+
installedSkills?: readonly {
|
|
17
|
+
name: string;
|
|
18
|
+
repositoryUrl: string;
|
|
19
|
+
sourceUrl: string;
|
|
20
|
+
}[];
|
|
21
|
+
client: SkillDiscoveryClient;
|
|
22
|
+
workspaceId: string;
|
|
23
|
+
query: string;
|
|
24
|
+
onImport: (url: string) => void;
|
|
25
|
+
canManage: boolean;
|
|
26
|
+
onSearch?: (() => void) | undefined;
|
|
27
|
+
};
|
|
28
|
+
/** Host-owned query and import flow; browse presentation is reusable independently. */
|
|
29
|
+
export declare function SkillDiscovery(props: SkillDiscoveryProps): import("react").JSX.Element;
|
|
@@ -2,6 +2,8 @@ import type { RetainedArtifactLoader, RetainedScreenshotLoader, ToolRegistry } f
|
|
|
2
2
|
import type { ActivityItem } from "./types.js";
|
|
3
3
|
export type ActivityRailProps = {
|
|
4
4
|
items: ActivityItem[];
|
|
5
|
+
/** The owning turn remains active between individual phase receipts. */
|
|
6
|
+
startupActive?: boolean;
|
|
5
7
|
/** Renderer registry for tool calls. Defaults to {@link defaultToolRegistry}. */
|
|
6
8
|
toolRegistry?: ToolRegistry | undefined;
|
|
7
9
|
/** Drill into a spawned worker session. */
|
|
@@ -19,4 +21,4 @@ export type ActivityRailProps = {
|
|
|
19
21
|
bare?: boolean | undefined;
|
|
20
22
|
className?: string | undefined;
|
|
21
23
|
};
|
|
22
|
-
export declare function ActivityRail({ items, toolRegistry, onOpenSession, onMemoryClick, loadRetainedScreenshot, loadRetainedArtifact, bare, className, }: ActivityRailProps): import("react").JSX.Element;
|
|
24
|
+
export declare function ActivityRail({ items, startupActive, toolRegistry, onOpenSession, onMemoryClick, loadRetainedScreenshot, loadRetainedArtifact, bare, className, }: ActivityRailProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
export type GenieLoadingRenderProps = {
|
|
3
|
+
startedAt: string;
|
|
4
|
+
detailsOpen: boolean;
|
|
5
|
+
onShowDetails: () => void;
|
|
6
|
+
};
|
|
7
|
+
export type GenieLoadingOptions = {
|
|
8
|
+
/** Replace the visual while preserving SDK loading visibility and transitions. */
|
|
9
|
+
render?: (props: GenieLoadingRenderProps) => ReactNode;
|
|
10
|
+
phrases?: readonly string[];
|
|
11
|
+
/** Public options stay independent of the renderer's declaration layout. */
|
|
12
|
+
orb?: {
|
|
13
|
+
state?: "working" | "searching" | "solving" | "listening" | "connecting" | "weaving" | "composing" | "breathing" | "shaping";
|
|
14
|
+
size?: 64 | 20;
|
|
15
|
+
speed?: number;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
export declare const GenieLoadingOptionsContext: import("react").Context<GenieLoadingOptions | undefined>;
|
|
19
|
+
/** Decorative copy never substitutes for a failure or claims measurable progress. */
|
|
20
|
+
export declare function GenieLoading({ startedAt, onShowDetails, detailsOpen, }: {
|
|
21
|
+
startedAt: string;
|
|
22
|
+
onShowDetails: () => void;
|
|
23
|
+
detailsOpen?: boolean;
|
|
24
|
+
}): string | number | bigint | boolean | import("react").JSX.Element | Iterable<ReactNode> | Promise<string | number | bigint | boolean | Iterable<ReactNode> | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | import("react").ReactPortal | null | undefined> | null | undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/react",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.5-canary.1",
|
|
4
4
|
"description": "React hooks and styled components for OpenGeni: live session streaming, chat composer, message timeline, session status, and fleet views — token-themed (CSS variables), dark-first, built on Tailwind v4 + Radix + Motion.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -133,8 +133,8 @@
|
|
|
133
133
|
"@dnd-kit/utilities": "3.2.2",
|
|
134
134
|
"@fontsource-variable/noto-sans-arabic": "5.2.10",
|
|
135
135
|
"@fontsource-variable/noto-sans-jp": "5.2.10",
|
|
136
|
-
"@opengeni/connect": "^0.2.0",
|
|
137
|
-
"@opengeni/sdk": "^5.0.
|
|
136
|
+
"@opengeni/connect": "^0.2.0-canary.3",
|
|
137
|
+
"@opengeni/sdk": "^5.0.5-canary.1",
|
|
138
138
|
"clsx": "^2.1.1",
|
|
139
139
|
"dequal": "2.0.3",
|
|
140
140
|
"lucide-react": "^1.8.0",
|
|
@@ -144,6 +144,7 @@
|
|
|
144
144
|
"react-resizable-panels": "^4.11.2",
|
|
145
145
|
"remark-gfm": "^4.0.1",
|
|
146
146
|
"tailwind-merge": "^3.5.0",
|
|
147
|
+
"thinking-orbs": "^0.3.1",
|
|
147
148
|
"virtua": "^0.49.2"
|
|
148
149
|
},
|
|
149
150
|
"peerDependencies": {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { GenieLoadingOptionsContext, type GenieLoadingOptions } from "../timeline/genie-loading";
|
|
1
2
|
import { ChildSessionLink } from "./child-session-link";
|
|
3
|
+
import { useStartupDetails } from "../timeline/startup-preference";
|
|
2
4
|
import type {
|
|
3
5
|
DraftTimelineAnnotation,
|
|
4
6
|
MediaGenerationResult,
|
|
@@ -26,7 +28,7 @@ import {
|
|
|
26
28
|
XCircleIcon,
|
|
27
29
|
} from "lucide-react";
|
|
28
30
|
import type { ComponentType } from "react";
|
|
29
|
-
import { AnimatePresence, motion } from "motion/react";
|
|
31
|
+
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
|
|
30
32
|
import { Collapsible } from "radix-ui";
|
|
31
33
|
import {
|
|
32
34
|
Component,
|
|
@@ -191,6 +193,7 @@ export type MessageTimelineProps = {
|
|
|
191
193
|
*/
|
|
192
194
|
computeLabel?: string | null | undefined;
|
|
193
195
|
/** Customize collapsed turn facets for this timeline instance. */
|
|
196
|
+
genieLoading?: GenieLoadingOptions | undefined;
|
|
194
197
|
turnSummary?: TurnSummaryOptions | undefined;
|
|
195
198
|
/** Follow new events when pinned to the bottom. Defaults to true. */
|
|
196
199
|
autoFollow?: boolean | undefined;
|
|
@@ -417,6 +420,7 @@ export function MessageTimeline({
|
|
|
417
420
|
loadRetainedArtifact,
|
|
418
421
|
loadVideoArtifactPlayback,
|
|
419
422
|
computeLabel = null,
|
|
423
|
+
genieLoading,
|
|
420
424
|
turnSummary,
|
|
421
425
|
autoFollow = true,
|
|
422
426
|
onAnnotate,
|
|
@@ -590,6 +594,20 @@ export function MessageTimeline({
|
|
|
590
594
|
previousBulkFirstKeyRef.current !== firstGroupKey;
|
|
591
595
|
const bulkRender = allGroups.length > 0 && (bulkActive || firstKeyChangedForBulk);
|
|
592
596
|
const groups = useStableTimelineGroupKeys(allGroups, !bulkRender);
|
|
597
|
+
const turnsWithOutput = useMemo(
|
|
598
|
+
() =>
|
|
599
|
+
new Set(
|
|
600
|
+
resolvedItems.flatMap((item) =>
|
|
601
|
+
"turnId" in item &&
|
|
602
|
+
item.turnId &&
|
|
603
|
+
(item.kind === "tool-call" ||
|
|
604
|
+
((item.kind === "agent-message" || item.kind === "reasoning") && item.text.trim()))
|
|
605
|
+
? [item.turnId]
|
|
606
|
+
: [],
|
|
607
|
+
),
|
|
608
|
+
),
|
|
609
|
+
[resolvedItems],
|
|
610
|
+
);
|
|
593
611
|
|
|
594
612
|
const applyCanSkipTipCatchup = useCallback((value: boolean) => {
|
|
595
613
|
if (canSkipTipCatchupRef.current !== value) {
|
|
@@ -919,6 +937,7 @@ export function MessageTimeline({
|
|
|
919
937
|
loadRetainedScreenshot,
|
|
920
938
|
loadRetainedArtifact,
|
|
921
939
|
loadVideoArtifactPlayback,
|
|
940
|
+
genieLoading,
|
|
922
941
|
turnSummary,
|
|
923
942
|
},
|
|
924
943
|
}),
|
|
@@ -934,6 +953,7 @@ export function MessageTimeline({
|
|
|
934
953
|
renderMessageText,
|
|
935
954
|
resolveProviderLogo,
|
|
936
955
|
toolRegistry,
|
|
956
|
+
genieLoading,
|
|
937
957
|
turnSummary,
|
|
938
958
|
userMessageDisclosureContext,
|
|
939
959
|
],
|
|
@@ -2007,6 +2027,12 @@ export function MessageTimeline({
|
|
|
2007
2027
|
groupKey={key}
|
|
2008
2028
|
group={group}
|
|
2009
2029
|
nextGroup={groups[index + 1]?.group}
|
|
2030
|
+
startupDismissed={
|
|
2031
|
+
group.kind === "activity" &&
|
|
2032
|
+
group.items.some(
|
|
2033
|
+
(item) => item.turnId && turnsWithOutput.has(item.turnId),
|
|
2034
|
+
)
|
|
2035
|
+
}
|
|
2010
2036
|
entranceEnabled={entranceEnabled}
|
|
2011
2037
|
liveEntranceEnabled={
|
|
2012
2038
|
group.kind === "activity" ? !bulkRender : undefined
|
|
@@ -2178,7 +2204,24 @@ function useStableTimelineGroupKeys(
|
|
|
2178
2204
|
// Retain only same-kind matches. Activity → turn wrap must NOT keep the
|
|
2179
2205
|
// activity chip's React key: that reused a collapsed TurnSummary and
|
|
2180
2206
|
// skipped the settle beat (insta-collapse / content flash).
|
|
2181
|
-
|
|
2207
|
+
const startupCompletion =
|
|
2208
|
+
previous?.group.kind === "activity" &&
|
|
2209
|
+
previous.group.items.every(
|
|
2210
|
+
(item) =>
|
|
2211
|
+
item.kind === "startup-phase" || (item.kind === "reasoning" && !item.text.trim()),
|
|
2212
|
+
) &&
|
|
2213
|
+
group.kind === "turn" &&
|
|
2214
|
+
group.outcome === "complete" &&
|
|
2215
|
+
group.groups.every(
|
|
2216
|
+
(child) =>
|
|
2217
|
+
child.kind === "activity" &&
|
|
2218
|
+
child.items.every((item) => item.kind === "startup-phase"),
|
|
2219
|
+
);
|
|
2220
|
+
if (
|
|
2221
|
+
previous &&
|
|
2222
|
+
(previous.group.kind === group.kind || startupCompletion) &&
|
|
2223
|
+
!usedKeys.has(previous.key)
|
|
2224
|
+
) {
|
|
2182
2225
|
retainedGroup = previous;
|
|
2183
2226
|
break;
|
|
2184
2227
|
}
|
|
@@ -2298,6 +2341,7 @@ type TimelineGroupEntryProps = {
|
|
|
2298
2341
|
groupKey: string;
|
|
2299
2342
|
group: TimelineGroup;
|
|
2300
2343
|
nextGroup?: TimelineGroup | undefined;
|
|
2344
|
+
startupDismissed: boolean;
|
|
2301
2345
|
entranceEnabled: boolean;
|
|
2302
2346
|
liveEntranceEnabled?: boolean | undefined;
|
|
2303
2347
|
context: TimelineGroupEntryContext;
|
|
@@ -2320,6 +2364,7 @@ type TimelineGroupBehaviorProps = {
|
|
|
2320
2364
|
loadRetainedScreenshot: MessageTimelineProps["loadRetainedScreenshot"];
|
|
2321
2365
|
loadRetainedArtifact: MessageTimelineProps["loadRetainedArtifact"];
|
|
2322
2366
|
loadVideoArtifactPlayback: MessageTimelineProps["loadVideoArtifactPlayback"];
|
|
2367
|
+
genieLoading: MessageTimelineProps["genieLoading"];
|
|
2323
2368
|
turnSummary: MessageTimelineProps["turnSummary"];
|
|
2324
2369
|
};
|
|
2325
2370
|
|
|
@@ -2332,10 +2377,12 @@ const TimelineGroupEntry = memo(function TimelineGroupEntry({
|
|
|
2332
2377
|
groupKey,
|
|
2333
2378
|
group,
|
|
2334
2379
|
nextGroup,
|
|
2380
|
+
startupDismissed,
|
|
2335
2381
|
entranceEnabled,
|
|
2336
2382
|
liveEntranceEnabled,
|
|
2337
2383
|
context,
|
|
2338
2384
|
}: TimelineGroupEntryProps) {
|
|
2385
|
+
const reducedMotion = useReducedMotion();
|
|
2339
2386
|
const { behavior, userMessageDisclosureContext } = context;
|
|
2340
2387
|
const contextCompactionCount =
|
|
2341
2388
|
group.kind === "turn"
|
|
@@ -2347,23 +2394,45 @@ const TimelineGroupEntry = memo(function TimelineGroupEntry({
|
|
|
2347
2394
|
? 1
|
|
2348
2395
|
: 0;
|
|
2349
2396
|
return (
|
|
2350
|
-
<
|
|
2351
|
-
<
|
|
2352
|
-
<
|
|
2353
|
-
<
|
|
2354
|
-
<
|
|
2355
|
-
{
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2397
|
+
<GenieLoadingOptionsContext.Provider value={behavior.genieLoading}>
|
|
2398
|
+
<div data-og-timeline-group-anchor="" data-og-group-key={groupKey}>
|
|
2399
|
+
<EntranceAnimationProvider value={entranceEnabled} liveValue={liveEntranceEnabled}>
|
|
2400
|
+
<TimelineGroupRenderBoundary resetKeys={[group, behavior]}>
|
|
2401
|
+
<UserMessageDisclosureProvider value={userMessageDisclosureContext}>
|
|
2402
|
+
<AnimatePresence initial={false}>
|
|
2403
|
+
<motion.div
|
|
2404
|
+
key={
|
|
2405
|
+
!startupDismissed &&
|
|
2406
|
+
group.kind === "activity" &&
|
|
2407
|
+
group.items.every(
|
|
2408
|
+
(item) =>
|
|
2409
|
+
item.kind === "startup-phase" ||
|
|
2410
|
+
(item.kind === "reasoning" && !item.text.trim()),
|
|
2411
|
+
)
|
|
2412
|
+
? "preparation"
|
|
2413
|
+
: "content"
|
|
2414
|
+
}
|
|
2415
|
+
initial={false}
|
|
2416
|
+
exit={{ opacity: 0, height: 0 }}
|
|
2417
|
+
transition={{ duration: reducedMotion ? 0 : 0.2 }}
|
|
2418
|
+
>
|
|
2419
|
+
<TimelineGroupView
|
|
2420
|
+
{...behavior}
|
|
2421
|
+
group={group}
|
|
2422
|
+
foldLiveCluster={isAgentProgress(nextGroup)}
|
|
2423
|
+
startupDismissed={startupDismissed}
|
|
2424
|
+
trailingAgentText={trailingAgentTextAfterTurn(group, nextGroup)}
|
|
2425
|
+
contextCompactionCount={
|
|
2426
|
+
contextCompactionCount > 0 ? contextCompactionCount : undefined
|
|
2427
|
+
}
|
|
2428
|
+
/>
|
|
2429
|
+
</motion.div>
|
|
2430
|
+
</AnimatePresence>
|
|
2431
|
+
</UserMessageDisclosureProvider>
|
|
2432
|
+
</TimelineGroupRenderBoundary>
|
|
2433
|
+
</EntranceAnimationProvider>
|
|
2434
|
+
</div>
|
|
2435
|
+
</GenieLoadingOptionsContext.Provider>
|
|
2367
2436
|
);
|
|
2368
2437
|
});
|
|
2369
2438
|
|
|
@@ -2389,6 +2458,7 @@ const TimelineGroupView = memo(function TimelineGroupView({
|
|
|
2389
2458
|
insideTurn = false,
|
|
2390
2459
|
nestClusterChips = false,
|
|
2391
2460
|
foldLiveCluster = false,
|
|
2461
|
+
startupDismissed = false,
|
|
2392
2462
|
trailingAgentText,
|
|
2393
2463
|
contextCompactionCount,
|
|
2394
2464
|
}: {
|
|
@@ -2412,6 +2482,7 @@ const TimelineGroupView = memo(function TimelineGroupView({
|
|
|
2412
2482
|
behind a neutral chip — the one place activity without an outcome still
|
|
2413
2483
|
folds, bounding the DOM of days-long autonomous turns. */
|
|
2414
2484
|
foldLiveCluster?: boolean;
|
|
2485
|
+
startupDismissed?: boolean;
|
|
2415
2486
|
/** Rendering inside an expanded turn group: the outer chip already owns the
|
|
2416
2487
|
failure surface, so nested chips stay tinted but quiet (no repeated
|
|
2417
2488
|
failure text, no auto-open) — one loud error, N calm sub-expands. */
|
|
@@ -2432,6 +2503,7 @@ const TimelineGroupView = memo(function TimelineGroupView({
|
|
|
2432
2503
|
/** Secondary chip facet when this fold sits next to a compaction landmark. */
|
|
2433
2504
|
contextCompactionCount?: number | undefined;
|
|
2434
2505
|
}) {
|
|
2506
|
+
const startupDetails = useStartupDetails();
|
|
2435
2507
|
const enter = useEntranceAnimation();
|
|
2436
2508
|
const settleChrome = useTurnSettleOpen();
|
|
2437
2509
|
const foldMemory = useFoldMemory();
|
|
@@ -2463,6 +2535,29 @@ const TimelineGroupView = memo(function TimelineGroupView({
|
|
|
2463
2535
|
group.kind === "turn" ? !!(enter && !insideTurn && !turnDefaultOpen) : liveActivitySettle;
|
|
2464
2536
|
switch (group.kind) {
|
|
2465
2537
|
case "activity":
|
|
2538
|
+
// Preparation is one quiet surface, not a fold with eight technical steps.
|
|
2539
|
+
if (
|
|
2540
|
+
!startupDetails &&
|
|
2541
|
+
!insideTurn &&
|
|
2542
|
+
!group.outcome &&
|
|
2543
|
+
group.items.every(
|
|
2544
|
+
(item) =>
|
|
2545
|
+
item.kind === "startup-phase" || (item.kind === "reasoning" && !item.text.trim()),
|
|
2546
|
+
)
|
|
2547
|
+
) {
|
|
2548
|
+
return (
|
|
2549
|
+
<ActivityRail
|
|
2550
|
+
items={group.items}
|
|
2551
|
+
startupActive={!startupDismissed && !foldLiveCluster}
|
|
2552
|
+
bare
|
|
2553
|
+
toolRegistry={toolRegistry}
|
|
2554
|
+
onOpenSession={onOpenSession}
|
|
2555
|
+
onMemoryClick={onMemoryClick}
|
|
2556
|
+
loadRetainedScreenshot={loadRetainedScreenshot}
|
|
2557
|
+
loadRetainedArtifact={loadRetainedArtifact}
|
|
2558
|
+
/>
|
|
2559
|
+
);
|
|
2560
|
+
}
|
|
2466
2561
|
if (insideTurn) {
|
|
2467
2562
|
// Nested chips whenever the parent has ≥2 clusters. During outer settle
|
|
2468
2563
|
// chrome they stay force-open so structure is visible and height stays
|
|
@@ -2542,6 +2637,18 @@ const TimelineGroupView = memo(function TimelineGroupView({
|
|
|
2542
2637
|
);
|
|
2543
2638
|
case "turn": {
|
|
2544
2639
|
const activityItems = flattenActivityItems(group.groups);
|
|
2640
|
+
if (
|
|
2641
|
+
!startupDetails &&
|
|
2642
|
+
group.outcome === "complete" &&
|
|
2643
|
+
group.groups.every(
|
|
2644
|
+
(child) =>
|
|
2645
|
+
child.kind === "activity" &&
|
|
2646
|
+
child.items.every(
|
|
2647
|
+
(item) => item.kind === "startup-phase" && item.status === "complete",
|
|
2648
|
+
),
|
|
2649
|
+
)
|
|
2650
|
+
)
|
|
2651
|
+
return <ActivityRail items={activityItems} startupActive={false} bare />;
|
|
2545
2652
|
// Second-layer chips only when there are natural multi-cluster seams —
|
|
2546
2653
|
// otherwise the outer turn chip alone is enough ("N steps" wrapping one
|
|
2547
2654
|
// more "N steps" was the redundant double fold).
|
|
@@ -2727,7 +2834,7 @@ function isAgentProgress(next: TimelineGroup | undefined): boolean {
|
|
|
2727
2834
|
return (
|
|
2728
2835
|
next.kind === "activity" ||
|
|
2729
2836
|
next.kind === "turn" ||
|
|
2730
|
-
(next.kind === "item" && next.item.kind === "agent-message")
|
|
2837
|
+
(next.kind === "item" && next.item.kind === "agent-message" && next.item.text.trim().length > 0)
|
|
2731
2838
|
);
|
|
2732
2839
|
}
|
|
2733
2840
|
|
|
@@ -1002,6 +1002,8 @@ export type SandboxWorkspaceProps = ClientOverride & {
|
|
|
1002
1002
|
* omitted the workbench decides from the durable workspace capture. */
|
|
1003
1003
|
initialTab?: string | undefined;
|
|
1004
1004
|
onActiveTabChange?: ((activeTab: string) => void) | undefined;
|
|
1005
|
+
/** Open a built-in or host-injected tab. A new requestId reopens the same tab. */
|
|
1006
|
+
openTabRequest?: { tab: string; requestId: number } | null | undefined;
|
|
1005
1007
|
initialFilePath?: string | null | undefined;
|
|
1006
1008
|
onFilePathChange?: ((path: string | null) => void) | undefined;
|
|
1007
1009
|
/** Presentation-only filter for Files tree nodes and selected-file viewing. */
|
|
@@ -1061,6 +1063,7 @@ export function SandboxWorkspace(props: SandboxWorkspaceProps): ReactNode {
|
|
|
1061
1063
|
trailingTabs,
|
|
1062
1064
|
initialTab,
|
|
1063
1065
|
onActiveTabChange,
|
|
1066
|
+
openTabRequest,
|
|
1064
1067
|
initialFilePath,
|
|
1065
1068
|
onFilePathChange,
|
|
1066
1069
|
isFileNodeVisible,
|
|
@@ -1102,6 +1105,32 @@ export function SandboxWorkspace(props: SandboxWorkspaceProps): ReactNode {
|
|
|
1102
1105
|
const nextFileRequestId = useRef(0);
|
|
1103
1106
|
const nextComputerRequestId = useRef(0);
|
|
1104
1107
|
const selectedTab = storedSelection?.sessionId === sessionId ? storedSelection.tab : null;
|
|
1108
|
+
const handledTabRequest = useRef<{ sessionId: string; requestId: number } | null>(null);
|
|
1109
|
+
useEffect(() => {
|
|
1110
|
+
if (
|
|
1111
|
+
!openTabRequest ||
|
|
1112
|
+
(handledTabRequest.current?.sessionId === sessionId &&
|
|
1113
|
+
handledTabRequest.current.requestId === openTabRequest.requestId)
|
|
1114
|
+
)
|
|
1115
|
+
return;
|
|
1116
|
+
const available =
|
|
1117
|
+
[...(leadingTabs ?? []), ...(trailingTabs ?? [])].some(
|
|
1118
|
+
(tab) => tab.id === openTabRequest.tab,
|
|
1119
|
+
) || (surfaces ?? WORKBENCH_SURFACES).some((tab) => tab === openTabRequest.tab);
|
|
1120
|
+
if (!available) return;
|
|
1121
|
+
handledTabRequest.current = { sessionId, requestId: openTabRequest.requestId };
|
|
1122
|
+
setStoredSelection({ sessionId, tab: openTabRequest.tab });
|
|
1123
|
+
onActiveTabChange?.(openTabRequest.tab);
|
|
1124
|
+
onCollapsedChange?.(false);
|
|
1125
|
+
}, [
|
|
1126
|
+
openTabRequest,
|
|
1127
|
+
sessionId,
|
|
1128
|
+
leadingTabs,
|
|
1129
|
+
trailingTabs,
|
|
1130
|
+
surfaces,
|
|
1131
|
+
onActiveTabChange,
|
|
1132
|
+
onCollapsedChange,
|
|
1133
|
+
]);
|
|
1105
1134
|
const activeTabHint = selectedTab ?? initialTab ?? leadingTabs?.[0]?.id ?? null;
|
|
1106
1135
|
const openFile = useCallback(
|
|
1107
1136
|
(path: string, line?: number | null) => {
|