@opengeni/react 1.0.2-canary.37191796234001 → 1.0.2
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 +15 -6
- package/dist/artifacts.js +1 -1
- package/dist/{browser-viewer-GTR5XQ7X.js → browser-viewer-SHYKQRNX.js} +4 -4
- package/dist/{chunk-CR7U7HAR.js → chunk-26ZNDDLZ.js} +3 -3
- package/dist/{chunk-ELCE5SXV.js → chunk-3CTRFMAK.js} +2 -2
- package/dist/{chunk-XOAMRDDK.js → chunk-742CZ3L3.js} +2 -2
- package/dist/{chunk-H2NJQORO.js → chunk-DO544OSV.js} +7 -1
- package/dist/chunk-DO544OSV.js.map +1 -0
- package/dist/{chunk-IQUCRAT7.js → chunk-EESD3PY3.js} +2 -2
- package/dist/{chunk-23J2ZNXT.js → chunk-EN3YTHKR.js} +3 -3
- package/dist/{chunk-JFNEV4LU.js → chunk-GYVRFZDY.js} +3 -3
- package/dist/{chunk-YWFG7ZX7.js → chunk-HDJWDK7W.js} +3 -3
- package/dist/{chunk-NZUI76EM.js → chunk-J6DBS4T3.js} +2 -2
- package/dist/{chunk-SJZDKVW2.js → chunk-JIQYUGVL.js} +4 -4
- package/dist/{chunk-XUW2UWQM.js → chunk-RLUSC2WA.js} +3 -3
- package/dist/{chunk-XUW2UWQM.js.map → chunk-RLUSC2WA.js.map} +1 -1
- package/dist/{chunk-Q5QBEZIQ.js → chunk-RSI7XYXL.js} +2 -2
- package/dist/{chunk-NEXLLTP4.js → chunk-TKOTKRWY.js} +4 -4
- package/dist/{chunk-NEXLLTP4.js.map → chunk-TKOTKRWY.js.map} +1 -1
- package/dist/{chunk-TMJRG4YC.js → chunk-VV5E43LD.js} +432 -180
- package/dist/chunk-VV5E43LD.js.map +1 -0
- package/dist/{chunk-FFA2OVOV.js → chunk-WGXVEJO5.js} +3 -3
- package/dist/{chunk-E3G7SAYZ.js → chunk-YM2DE7BQ.js} +2 -2
- package/dist/components/open-geni-chat.d.ts +6 -3
- package/dist/components/session-conversation.d.ts +6 -3
- package/dist/components/session-proxy-scope.d.ts +21 -0
- package/dist/composer.js +6 -6
- package/dist/{computer-viewer-ENHVAZ2Q.js → computer-viewer-6SRGWRUX.js} +4 -4
- package/dist/index.js +17 -17
- package/dist/index.js.map +1 -1
- package/dist/interaction.js +4 -4
- package/dist/{latest-question-755OW5DS.js → latest-question-HTRIS4KJ.js} +2 -2
- package/dist/lib/host-theme.d.ts +7 -0
- package/dist/machines.js +3 -3
- package/dist/{model-policy-picker-menu-PWRI7R2X.js → model-policy-picker-menu-M54ISBMN.js} +4 -4
- package/dist/model-policy.js +1 -1
- package/dist/realtime.js +3 -3
- package/dist/{sandbox-files-BBC47SLD.js → sandbox-files-GORZOUUJ.js} +4 -4
- package/dist/session-ui.js +9 -9
- package/dist/session.js +5 -5
- package/dist/timeline/turn-summary.d.ts +3 -1
- package/dist/timeline/types.d.ts +6 -0
- package/dist/usage.js +1 -1
- package/package.json +3 -3
- package/src/components/message-timeline.tsx +704 -450
- package/src/components/open-geni-chat.tsx +56 -37
- package/src/components/session-conversation.tsx +87 -73
- package/src/components/session-proxy-scope.tsx +92 -0
- package/src/hooks/use-session-background-commands.ts +2 -2
- package/src/hooks/use-video-artifact-playback.ts +1 -1
- package/src/lib/host-theme.ts +15 -2
- package/src/model-policy.ts +2 -2
- package/src/session-context.ts +3 -3
- package/src/timeline/projection.ts +9 -0
- package/src/timeline/turn-summary.tsx +10 -0
- package/src/timeline/types.ts +6 -0
- package/dist/chunk-H2NJQORO.js.map +0 -1
- package/dist/chunk-TMJRG4YC.js.map +0 -1
- /package/dist/{browser-viewer-GTR5XQ7X.js.map → browser-viewer-SHYKQRNX.js.map} +0 -0
- /package/dist/{chunk-CR7U7HAR.js.map → chunk-26ZNDDLZ.js.map} +0 -0
- /package/dist/{chunk-ELCE5SXV.js.map → chunk-3CTRFMAK.js.map} +0 -0
- /package/dist/{chunk-XOAMRDDK.js.map → chunk-742CZ3L3.js.map} +0 -0
- /package/dist/{chunk-IQUCRAT7.js.map → chunk-EESD3PY3.js.map} +0 -0
- /package/dist/{chunk-23J2ZNXT.js.map → chunk-EN3YTHKR.js.map} +0 -0
- /package/dist/{chunk-JFNEV4LU.js.map → chunk-GYVRFZDY.js.map} +0 -0
- /package/dist/{chunk-YWFG7ZX7.js.map → chunk-HDJWDK7W.js.map} +0 -0
- /package/dist/{chunk-NZUI76EM.js.map → chunk-J6DBS4T3.js.map} +0 -0
- /package/dist/{chunk-SJZDKVW2.js.map → chunk-JIQYUGVL.js.map} +0 -0
- /package/dist/{chunk-Q5QBEZIQ.js.map → chunk-RSI7XYXL.js.map} +0 -0
- /package/dist/{chunk-FFA2OVOV.js.map → chunk-WGXVEJO5.js.map} +0 -0
- /package/dist/{chunk-E3G7SAYZ.js.map → chunk-YM2DE7BQ.js.map} +0 -0
- /package/dist/{computer-viewer-ENHVAZ2Q.js.map → computer-viewer-6SRGWRUX.js.map} +0 -0
- /package/dist/{latest-question-755OW5DS.js.map → latest-question-HTRIS4KJ.js.map} +0 -0
- /package/dist/{model-policy-picker-menu-PWRI7R2X.js.map → model-policy-picker-menu-M54ISBMN.js.map} +0 -0
- /package/dist/{sandbox-files-BBC47SLD.js.map → sandbox-files-GORZOUUJ.js.map} +0 -0
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
import { useOpenGeni, type ClientOverride } from "../session-context";
|
|
12
12
|
import { SessionConversation, type SessionConversationProps } from "./session-conversation";
|
|
13
13
|
import { SessionList, type SessionListLabels, type SessionListProps } from "./session-list";
|
|
14
|
+
import { SessionProxyScope, type SessionProxyBaseUrl } from "./session-proxy-scope";
|
|
14
15
|
|
|
15
16
|
export type OpenGeniChatLabels = SessionListLabels & {
|
|
16
17
|
openChats: string;
|
|
@@ -31,41 +32,47 @@ const DEFAULT_LABELS: Omit<OpenGeniChatLabels, keyof SessionListLabels> = {
|
|
|
31
32
|
newChatUnavailable: "New chats are not enabled for this product.",
|
|
32
33
|
};
|
|
33
34
|
|
|
34
|
-
export type OpenGeniChatProps = ClientOverride &
|
|
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
|
-
|
|
35
|
+
export type OpenGeniChatProps = ClientOverride &
|
|
36
|
+
SessionProxyBaseUrl & {
|
|
37
|
+
/** Controlled selection. `null` shows the new-chat composer. */
|
|
38
|
+
sessionId?: string | null | undefined;
|
|
39
|
+
/** Initial selection when uncontrolled. Defaults to a new chat. */
|
|
40
|
+
defaultSessionId?: string | null | undefined;
|
|
41
|
+
onSessionChange?: ((sessionId: string | null) => void) | undefined;
|
|
42
|
+
/**
|
|
43
|
+
* Create a chat from its first message. Defaults to `client.createSession`
|
|
44
|
+
* with `{ initialMessage, idempotencyKey }`, which `createSessionProxyHandler`
|
|
45
|
+
* accepts when the server supplies a `createSession` hook. Return the new id.
|
|
46
|
+
*/
|
|
47
|
+
createSession?:
|
|
48
|
+
| ((initialMessage: string, idempotencyKey: string) => Promise<string>)
|
|
49
|
+
| undefined;
|
|
50
|
+
/** Hide the "New chat" entry point. */
|
|
51
|
+
newChat?: boolean | undefined;
|
|
52
|
+
/** Forwarded to the conversation (message rendering, tool renderers, composer...). */
|
|
53
|
+
conversationProps?: Omit<
|
|
54
|
+
SessionConversationProps,
|
|
55
|
+
"sessionId" | "client" | "workspaceId" | "baseUrl"
|
|
56
|
+
>;
|
|
57
|
+
/** Forwarded to the list (rename/archive toggles, page size). */
|
|
58
|
+
listProps?: Pick<SessionListProps, "rename" | "archive" | "pageSize"> | undefined;
|
|
59
|
+
labels?: Partial<OpenGeniChatLabels> | undefined;
|
|
60
|
+
className?: string | undefined;
|
|
61
|
+
/** Defaults to filling the host. */
|
|
62
|
+
height?: CSSProperties["height"];
|
|
63
|
+
/**
|
|
64
|
+
* Light or dark. Defaults to `auto`: follow the host page (an enclosing
|
|
65
|
+
* `data-og-theme`, `class="dark"`/`data-theme` on <html> or <body>, the
|
|
66
|
+
* host's `color-scheme`, then its background), not the OS setting alone.
|
|
67
|
+
*/
|
|
68
|
+
theme?: HostThemePreference | undefined;
|
|
69
|
+
/**
|
|
70
|
+
* `host` (default) derives backgrounds and cards from the host background so
|
|
71
|
+
* the chat blends in; `theme` uses the `--og-color-*` surface tokens as they
|
|
72
|
+
* are. Customized surface tokens are always kept.
|
|
73
|
+
*/
|
|
74
|
+
surface?: HostSurfacePreference | undefined;
|
|
75
|
+
};
|
|
69
76
|
|
|
70
77
|
type CreateClient = Partial<Pick<OpenGeniClient, "createSession">>;
|
|
71
78
|
|
|
@@ -73,8 +80,20 @@ type CreateClient = Partial<Pick<OpenGeniClient, "createSession">>;
|
|
|
73
80
|
* A complete chat experience: the user's chat list plus the conversation.
|
|
74
81
|
* The list is a sidebar when the component is wide and a drawer when narrow
|
|
75
82
|
* (container-based, so it adapts inside panels as well as full pages).
|
|
83
|
+
* `<OpenGeniChat baseUrl="/api/opengeni" />` needs no provider: it talks to
|
|
84
|
+
* your session proxy and uses the workspace the proxy resolves.
|
|
76
85
|
*/
|
|
77
|
-
export function OpenGeniChat({
|
|
86
|
+
export function OpenGeniChat({ baseUrl, ...props }: OpenGeniChatProps) {
|
|
87
|
+
if (baseUrl === undefined) return <Chat {...props} />;
|
|
88
|
+
const { client: _client, workspaceId, ...rest } = props;
|
|
89
|
+
return (
|
|
90
|
+
<SessionProxyScope baseUrl={baseUrl} workspaceId={workspaceId}>
|
|
91
|
+
<Chat {...rest} />
|
|
92
|
+
</SessionProxyScope>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function Chat({
|
|
78
97
|
client,
|
|
79
98
|
workspaceId,
|
|
80
99
|
sessionId: controlledSessionId,
|
|
@@ -89,7 +108,7 @@ export function OpenGeniChat({
|
|
|
89
108
|
height = "100%",
|
|
90
109
|
theme,
|
|
91
110
|
surface,
|
|
92
|
-
}: OpenGeniChatProps) {
|
|
111
|
+
}: Omit<OpenGeniChatProps, "baseUrl">) {
|
|
93
112
|
const labels = { ...DEFAULT_LABELS, ...labelOverrides } as OpenGeniChatLabels;
|
|
94
113
|
const scope = { client, workspaceId };
|
|
95
114
|
const context = useOpenGeni(scope);
|
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
import type { UserMessageDisclosureLabels } from "./user-message-body";
|
|
38
38
|
import { conversationTimeline } from "../conversation-timeline";
|
|
39
39
|
import { cn } from "../lib/cn";
|
|
40
|
+
import { SessionProxyScope, type SessionProxyBaseUrl } from "./session-proxy-scope";
|
|
40
41
|
import { useErrorMessage } from "../lib/error-message";
|
|
41
42
|
import {
|
|
42
43
|
useHostTheme,
|
|
@@ -56,81 +57,94 @@ const DEFAULT_CONVERSATION_LABELS: SessionConversationLabels = {
|
|
|
56
57
|
tryAgain: "Try again",
|
|
57
58
|
};
|
|
58
59
|
|
|
59
|
-
export type SessionConversationProps = ClientOverride &
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
60
|
+
export type SessionConversationProps = ClientOverride &
|
|
61
|
+
SessionProxyBaseUrl & {
|
|
62
|
+
sessionId: string;
|
|
63
|
+
/** Host-owned artifact links, previews and other message presentation. */
|
|
64
|
+
renderMessageText?: MessageTimelineProps["renderMessageText"];
|
|
65
|
+
/**
|
|
66
|
+
* Open OpenGeni object links in agent replies (`artifact:`, `sandbox:`,
|
|
67
|
+
* editable artifacts, Sites). Asked first; by default retained files and
|
|
68
|
+
* sandbox files download only when the proxy explicitly enables them, while
|
|
69
|
+
* editable artifacts and Sites stay unavailable until the host resolves them.
|
|
70
|
+
*/
|
|
71
|
+
resolveLink?: OpenGeniLinkResolver | undefined;
|
|
72
|
+
/**
|
|
73
|
+
* Open agent links to editable artifacts and Sites in a host viewer, for
|
|
74
|
+
* example `SessionArtifactViewer` mounted beside the conversation. Asked
|
|
75
|
+
* after `resolveLink`.
|
|
76
|
+
*/
|
|
77
|
+
onOpenArtifact?: ((target: OpenGeniViewerTarget) => void) | undefined;
|
|
78
|
+
/**
|
|
79
|
+
* Inline previews for assistant `opengeni-site` / `opengeni-html` fences.
|
|
80
|
+
* Defaults to the OpenGeni preview (Site reads need the proxy's
|
|
81
|
+
* `artifacts` option); `false` shows the fence as code.
|
|
82
|
+
*/
|
|
83
|
+
renderInteractiveBlock?: MessageTimelineProps["renderInteractiveBlock"] | false;
|
|
84
|
+
/** Product-specific tool-call renderers; defaults to the built-in registry. */
|
|
85
|
+
toolRegistry?: MessageTimelineProps["toolRegistry"];
|
|
86
|
+
/**
|
|
87
|
+
* Replace the "usage limit reached" row for an `allowance_exhausted`
|
|
88
|
+
* refusal, for example to link your own plan or admin page.
|
|
89
|
+
*/
|
|
90
|
+
renderAllowanceExhausted?: MessageTimelineProps["renderAllowanceExhausted"];
|
|
91
|
+
/** Replace the words of the default "usage limit reached" row. */
|
|
92
|
+
allowanceExhaustedLabels?: MessageTimelineProps["allowanceExhaustedLabels"];
|
|
93
|
+
/**
|
|
94
|
+
* File attachments in the composer. Defaults to true; the attach control
|
|
95
|
+
* appears only when the deployment's client config enables file uploads.
|
|
96
|
+
*/
|
|
97
|
+
attachments?: boolean | undefined;
|
|
98
|
+
/**
|
|
99
|
+
* Show the model/reasoning picker. End users of an embedded product rarely
|
|
100
|
+
* choose models, so it is hidden unless this is `true` or the client config
|
|
101
|
+
* reports `modelSelection: true` (`createSessionProxyHandler({ modelSelection: true })`).
|
|
102
|
+
*/
|
|
103
|
+
modelPicker?: boolean | undefined;
|
|
104
|
+
/** Model-picker appearance only; visibility, policy and delivery remain owned here. */
|
|
105
|
+
modelPickerProps?: Pick<ModelPolicyPickerProps, "groupPresentation" | "messages"> | undefined;
|
|
106
|
+
/** Localized actions for already-sent user-message disclosure. */
|
|
107
|
+
userMessageDisclosureLabels?: UserMessageDisclosureLabels | undefined;
|
|
108
|
+
loadSkillReview?: HumanInputSurfaceProps["loadSkillReview"];
|
|
109
|
+
className?: string;
|
|
110
|
+
/** Defaults to filling the host. The host owns available height. */
|
|
111
|
+
height?: CSSProperties["height"];
|
|
112
|
+
/**
|
|
113
|
+
* Light or dark. Defaults to `auto`: follow the host page (an enclosing
|
|
114
|
+
* `data-og-theme`, `class="dark"`/`data-theme` on <html> or <body>, the
|
|
115
|
+
* host's `color-scheme`, then its background), not the OS setting alone.
|
|
116
|
+
*/
|
|
117
|
+
theme?: HostThemePreference | undefined;
|
|
118
|
+
/**
|
|
119
|
+
* `host` (default) derives backgrounds and cards from the host background so
|
|
120
|
+
* the conversation blends in; `theme` uses the `--og-color-*` surface tokens
|
|
121
|
+
* as they are. Customized surface tokens are always kept.
|
|
122
|
+
*/
|
|
123
|
+
surface?: HostSurfacePreference | undefined;
|
|
124
|
+
/** Conversation-owned copy (error actions). */
|
|
125
|
+
labels?: Partial<SessionConversationLabels> | undefined;
|
|
126
|
+
/** Presentation/custom controls only; queue and delivery wiring stay owned here. */
|
|
127
|
+
composerProps?: Omit<
|
|
128
|
+
ChatComposerProps,
|
|
129
|
+
"composer" | "effectiveControl" | "queuedAheadCount" | "attachments"
|
|
130
|
+
>;
|
|
131
|
+
};
|
|
130
132
|
|
|
131
133
|
/** Complete existing-session conversation. Uses the provider's normal SDK client
|
|
132
|
-
* (including Site clients), one shared event feed, and authoritative queue state.
|
|
133
|
-
|
|
134
|
+
* (including Site clients), one shared event feed, and authoritative queue state.
|
|
135
|
+
* `<SessionConversation baseUrl="/api/opengeni" sessionId={id} />` needs no
|
|
136
|
+
* provider: it talks to your session proxy and its resolved workspace. */
|
|
137
|
+
export function SessionConversation({ baseUrl, ...props }: SessionConversationProps) {
|
|
138
|
+
if (baseUrl === undefined) return <RetryingConversation {...props} />;
|
|
139
|
+
const { client: _client, workspaceId, ...rest } = props;
|
|
140
|
+
return (
|
|
141
|
+
<SessionProxyScope baseUrl={baseUrl} workspaceId={workspaceId}>
|
|
142
|
+
<RetryingConversation {...rest} />
|
|
143
|
+
</SessionProxyScope>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function RetryingConversation(props: Omit<SessionConversationProps, "baseUrl">) {
|
|
134
148
|
// A failed initial load retries by remounting the whole conversation.
|
|
135
149
|
const [attempt, setAttempt] = useState(0);
|
|
136
150
|
return (
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { OpenGeniBrowserClient } from "@opengeni/sdk/browser";
|
|
2
|
+
import { useEffect, useMemo, useState, type ReactNode } from "react";
|
|
3
|
+
import { OpenGeniProvider } from "../provider";
|
|
4
|
+
|
|
5
|
+
export type SessionProxyBaseUrl = {
|
|
6
|
+
/**
|
|
7
|
+
* Mount path of your `createSessionProxyHandler` (for example
|
|
8
|
+
* `"/api/opengeni"`). The component then needs no `OpenGeniProvider`,
|
|
9
|
+
* client, or workspace id: it creates the browser client for that path and
|
|
10
|
+
* uses the workspace the proxy resolved for the signed-in user. An explicit
|
|
11
|
+
* `workspaceId` skips that lookup.
|
|
12
|
+
*/
|
|
13
|
+
baseUrl?: string | undefined;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
type ScopeState =
|
|
17
|
+
| { status: "loading" }
|
|
18
|
+
| { status: "ready"; workspaceId: string }
|
|
19
|
+
| { status: "error"; message: string };
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* `OpenGeniProvider` for a same-origin session proxy, from its base URL alone.
|
|
23
|
+
* The proxy's client config names the resolved workspace, so the browser
|
|
24
|
+
* never handles an Opengeni id. Provider-based usage is unchanged.
|
|
25
|
+
*/
|
|
26
|
+
export function SessionProxyScope({
|
|
27
|
+
baseUrl,
|
|
28
|
+
workspaceId,
|
|
29
|
+
children,
|
|
30
|
+
}: {
|
|
31
|
+
baseUrl: string;
|
|
32
|
+
workspaceId?: string | undefined;
|
|
33
|
+
children: ReactNode;
|
|
34
|
+
}) {
|
|
35
|
+
// Version skew between this bundle and the server SDK is tolerated, as for
|
|
36
|
+
// any embedded host: the proxy reports its own contract revision.
|
|
37
|
+
const client = useMemo(
|
|
38
|
+
() => new OpenGeniBrowserClient({ baseUrl, apiContract: "compatible" }),
|
|
39
|
+
[baseUrl],
|
|
40
|
+
);
|
|
41
|
+
const [state, setState] = useState<ScopeState>(
|
|
42
|
+
workspaceId ? { status: "ready", workspaceId } : { status: "loading" },
|
|
43
|
+
);
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
if (workspaceId) {
|
|
46
|
+
setState({ status: "ready", workspaceId });
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
let active = true;
|
|
50
|
+
setState({ status: "loading" });
|
|
51
|
+
client.getClientConfig().then(
|
|
52
|
+
(config) => {
|
|
53
|
+
if (!active) return;
|
|
54
|
+
setState(
|
|
55
|
+
config.workspaceId
|
|
56
|
+
? { status: "ready", workspaceId: config.workspaceId }
|
|
57
|
+
: {
|
|
58
|
+
status: "error",
|
|
59
|
+
message:
|
|
60
|
+
"This server does not report its workspace. Update @opengeni/sdk on the server, or pass workspaceId.",
|
|
61
|
+
},
|
|
62
|
+
);
|
|
63
|
+
},
|
|
64
|
+
(error: unknown) => {
|
|
65
|
+
if (!active) return;
|
|
66
|
+
setState({
|
|
67
|
+
status: "error",
|
|
68
|
+
message: error instanceof Error ? error.message : "Could not load the conversation.",
|
|
69
|
+
});
|
|
70
|
+
},
|
|
71
|
+
);
|
|
72
|
+
return () => {
|
|
73
|
+
active = false;
|
|
74
|
+
};
|
|
75
|
+
}, [client, workspaceId]);
|
|
76
|
+
|
|
77
|
+
if (state.status === "error") {
|
|
78
|
+
return (
|
|
79
|
+
<div role="alert" className="og-root p-3 text-og-sm text-og-danger" data-og-proxy-error="">
|
|
80
|
+
{state.message}
|
|
81
|
+
</div>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
if (state.status === "loading") {
|
|
85
|
+
return <div className="og-root" aria-busy="true" data-og-proxy-loading="" />;
|
|
86
|
+
}
|
|
87
|
+
return (
|
|
88
|
+
<OpenGeniProvider client={client} workspaceId={state.workspaceId}>
|
|
89
|
+
{children}
|
|
90
|
+
</OpenGeniProvider>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
@@ -27,7 +27,7 @@ export function useSessionBackgroundCommands(
|
|
|
27
27
|
async (signal?: AbortSignal) => {
|
|
28
28
|
if (!sessionId) return { commands: [] };
|
|
29
29
|
if (!client.listSessionBackgroundCommands) {
|
|
30
|
-
throw new Error("The configured
|
|
30
|
+
throw new Error("The configured Opengeni client does not support background commands");
|
|
31
31
|
}
|
|
32
32
|
return await client.listSessionBackgroundCommands(workspaceId, sessionId, { signal });
|
|
33
33
|
},
|
|
@@ -49,7 +49,7 @@ export function useSessionBackgroundCommands(
|
|
|
49
49
|
async (commandId: string): Promise<void> => {
|
|
50
50
|
if (!sessionId) return;
|
|
51
51
|
if (!client.cancelSessionBackgroundCommand) {
|
|
52
|
-
throw new Error("The configured
|
|
52
|
+
throw new Error("The configured Opengeni client does not support background commands");
|
|
53
53
|
}
|
|
54
54
|
const ticket = generation.current;
|
|
55
55
|
await client.cancelSessionBackgroundCommand(workspaceId, sessionId, commandId);
|
|
@@ -15,7 +15,7 @@ export function useVideoArtifactPlaybackLoader(
|
|
|
15
15
|
return useMemo(
|
|
16
16
|
() => async (artifactId: string, signal?: AbortSignal) => {
|
|
17
17
|
if (typeof client.createVideoArtifactPlaybackSource !== "function") {
|
|
18
|
-
throw new Error("This
|
|
18
|
+
throw new Error("This Opengeni host does not expose retained-video playback.");
|
|
19
19
|
}
|
|
20
20
|
return await client.createVideoArtifactPlaybackSource(
|
|
21
21
|
workspaceId,
|
package/src/lib/host-theme.ts
CHANGED
|
@@ -310,8 +310,21 @@ export function resolveHostTheme(element: Element): ResolvedHostTheme {
|
|
|
310
310
|
return "light";
|
|
311
311
|
}
|
|
312
312
|
|
|
313
|
-
|
|
314
|
-
|
|
313
|
+
/**
|
|
314
|
+
* Canonical text for comparing a token value with the stock one. The
|
|
315
|
+
* published `compiled.css` is minified (`#333`, `oklch(.52 .16 22)`), so the
|
|
316
|
+
* same color must compare equal in short and long form; otherwise every stock
|
|
317
|
+
* token reads as a host customization and dark values leak into light themes.
|
|
318
|
+
*/
|
|
319
|
+
export function normalizeToken(value: string): string {
|
|
320
|
+
return value
|
|
321
|
+
.trim()
|
|
322
|
+
.toLowerCase()
|
|
323
|
+
.replace(/\s+/g, " ")
|
|
324
|
+
.replace(/#([\da-f])([\da-f])([\da-f])(?![\da-f])/g, "#$1$1$2$2$3$3")
|
|
325
|
+
.replace(/(^|[\s(,/])0+(\.\d)/g, "$1$2")
|
|
326
|
+
.replace(/(\.\d*?)0+(?=[\s),/%]|$)/g, "$1")
|
|
327
|
+
.replace(/\.(?=[\s),/%]|$)/g, "");
|
|
315
328
|
}
|
|
316
329
|
|
|
317
330
|
/**
|
package/src/model-policy.ts
CHANGED
|
@@ -228,7 +228,7 @@ function workspaceProviderPayerSummary(model: ClientModel): string {
|
|
|
228
228
|
if (model.provider === "workspace-anthropic")
|
|
229
229
|
return "Billed to the workspace Anthropic API account";
|
|
230
230
|
if (model.provider === "workspace-claude-subscription")
|
|
231
|
-
return "Uses the workspace Claude subscription · no
|
|
231
|
+
return "Uses the workspace Claude subscription · no Opengeni credits";
|
|
232
232
|
if (model.provider === "workspace-openrouter") {
|
|
233
233
|
return "Billed to the workspace OpenRouter account";
|
|
234
234
|
}
|
|
@@ -242,7 +242,7 @@ function organizationProviderPayerSummary(model: ClientModel): string {
|
|
|
242
242
|
if (model.provider === "organization-anthropic")
|
|
243
243
|
return "Billed to the organization Anthropic API account";
|
|
244
244
|
if (model.provider === "organization-claude-subscription")
|
|
245
|
-
return "Uses the connected Claude subscription · no
|
|
245
|
+
return "Uses the connected Claude subscription · no Opengeni credits";
|
|
246
246
|
if (model.provider === "organization-openrouter") {
|
|
247
247
|
return "Billed to the organization OpenRouter account";
|
|
248
248
|
}
|
package/src/session-context.ts
CHANGED
|
@@ -129,7 +129,7 @@ function useEmbeddedClientRefinement<TClient extends object>(
|
|
|
129
129
|
const workspaceId = override.workspaceId ?? context?.workspaceId;
|
|
130
130
|
if (!candidate || !workspaceId) {
|
|
131
131
|
throw new Error(
|
|
132
|
-
"@opengeni/react: no
|
|
132
|
+
"@opengeni/react: no Opengeni client/workspace available. Wrap the tree in <OpenGeniProvider> or pass { client, workspaceId } to the hook.",
|
|
133
133
|
);
|
|
134
134
|
}
|
|
135
135
|
const methods = candidate as Record<string, unknown>;
|
|
@@ -164,7 +164,7 @@ export function useOpenGeni(override: ClientOverride = {}): OpenGeniContextValue
|
|
|
164
164
|
const workspaceId = override.workspaceId ?? context?.workspaceId;
|
|
165
165
|
if (!client || !workspaceId) {
|
|
166
166
|
throw new Error(
|
|
167
|
-
"@opengeni/react: no
|
|
167
|
+
"@opengeni/react: no Opengeni client/workspace available. Wrap the tree in <OpenGeniProvider> or pass { client, workspaceId } to the hook.",
|
|
168
168
|
);
|
|
169
169
|
}
|
|
170
170
|
return {
|
|
@@ -423,7 +423,7 @@ export function useOpenGeniClient(
|
|
|
423
423
|
const client = override.client ?? context?.client;
|
|
424
424
|
if (!client) {
|
|
425
425
|
throw new Error(
|
|
426
|
-
"@opengeni/react: no
|
|
426
|
+
"@opengeni/react: no Opengeni client available. Wrap the tree in <OpenGeniProvider> or pass { client } to the hook.",
|
|
427
427
|
);
|
|
428
428
|
}
|
|
429
429
|
return client;
|
|
@@ -2163,6 +2163,15 @@ function groupReadableTurns(items: TimelineItem[]): TimelineGroup[] {
|
|
|
2163
2163
|
foldedProse.add(message);
|
|
2164
2164
|
group.work!.details.push({ kind: "item", item: message });
|
|
2165
2165
|
}
|
|
2166
|
+
} else {
|
|
2167
|
+
// Live progress stays primary above the work row, and is also listed in
|
|
2168
|
+
// the work history so an expanded disclosure reads like the settled one.
|
|
2169
|
+
// The timeline folds the outside copies only while that disclosure is open.
|
|
2170
|
+
for (const message of prose) {
|
|
2171
|
+
if (message.phase === "final_answer" || message.text.includes("![")) continue;
|
|
2172
|
+
group.work!.details.push({ kind: "item", item: message });
|
|
2173
|
+
(group.work!.liveNoteIds ??= []).push(message.id);
|
|
2174
|
+
}
|
|
2166
2175
|
}
|
|
2167
2176
|
// Preserve event chronology, not completion timestamps or activity kinds.
|
|
2168
2177
|
group.work!.details.sort((a, b) => {
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
createContext,
|
|
5
5
|
useContext,
|
|
6
6
|
useEffect,
|
|
7
|
+
useLayoutEffect,
|
|
7
8
|
useMemo,
|
|
8
9
|
useRef,
|
|
9
10
|
useState,
|
|
@@ -144,6 +145,8 @@ export type TurnSummaryStatus = Readonly<{
|
|
|
144
145
|
}>;
|
|
145
146
|
|
|
146
147
|
export type TurnSummaryProps = {
|
|
148
|
+
/** Reports the disclosure's actual open state on mount and on every change. */
|
|
149
|
+
onOpenStateChange?: ((open: boolean) => void) | undefined;
|
|
147
150
|
/** The activity items in the turn (used only to compute the facet counts). */
|
|
148
151
|
items: ActivityItem[];
|
|
149
152
|
/**
|
|
@@ -226,6 +229,7 @@ export function TurnSummary({
|
|
|
226
229
|
copyText,
|
|
227
230
|
contextCompactionCount,
|
|
228
231
|
status,
|
|
232
|
+
onOpenStateChange,
|
|
229
233
|
children,
|
|
230
234
|
}: TurnSummaryProps) {
|
|
231
235
|
// An explicit `defaultOpen` always wins; otherwise an ancestor may seed it
|
|
@@ -247,6 +251,12 @@ export function TurnSummary({
|
|
|
247
251
|
const initialSettle = Boolean(settleFold) && !restingOpen && remembered === undefined;
|
|
248
252
|
const [settling, setSettling] = useState(initialSettle);
|
|
249
253
|
const [open, setOpen] = useState(initialSettle ? true : restingOpen);
|
|
254
|
+
const reportOpen = useRef(onOpenStateChange);
|
|
255
|
+
reportOpen.current = onOpenStateChange;
|
|
256
|
+
// Before paint: a listener may need to start motion in the same frame.
|
|
257
|
+
useLayoutEffect(() => {
|
|
258
|
+
reportOpen.current?.(open);
|
|
259
|
+
}, [open]);
|
|
250
260
|
const readerOwnsOpen = useRef(remembered !== undefined);
|
|
251
261
|
useEffect(() => {
|
|
252
262
|
// Readable work can produce primary media or fail after mounting. Reveal
|
package/src/timeline/types.ts
CHANGED
|
@@ -586,6 +586,12 @@ export type TimelineGroup =
|
|
|
586
586
|
responseStartedAt?: string;
|
|
587
587
|
waiting?: { label: string; since: string };
|
|
588
588
|
details: TimelineGroup[];
|
|
589
|
+
/**
|
|
590
|
+
* While the turn is live, its progress notes stay readable above the
|
|
591
|
+
* work row and are also listed in `details`. These are their item ids,
|
|
592
|
+
* so an expanded work disclosure can fold the outside copies away.
|
|
593
|
+
*/
|
|
594
|
+
liveNoteIds?: string[];
|
|
589
595
|
};
|
|
590
596
|
}
|
|
591
597
|
| {
|