@opengeni/react 0.29.0 → 0.33.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/dist/approvals.d.ts +14 -0
- package/dist/{chunk-M7X4JZOD.js → chunk-4QOQ2DSR.js} +8 -1
- package/dist/chunk-4QOQ2DSR.js.map +1 -0
- package/dist/{chunk-HQFPO7VX.js → chunk-7CJOAW3V.js} +24 -3
- package/dist/{chunk-HQFPO7VX.js.map → chunk-7CJOAW3V.js.map} +1 -1
- package/dist/{chunk-CSJBF3QJ.js → chunk-A2WITZTI.js} +377 -113
- package/dist/chunk-A2WITZTI.js.map +1 -0
- package/dist/{chunk-BC6RKIHS.js → chunk-GZ4KR67U.js} +11 -11
- package/dist/{chunk-AVLMIWYO.js → chunk-KZRUYCL4.js} +12 -6
- package/dist/chunk-KZRUYCL4.js.map +1 -0
- package/dist/{chunk-IPVOVOW6.js → chunk-MS3FBZ6A.js} +2539 -2174
- package/dist/chunk-MS3FBZ6A.js.map +1 -0
- package/dist/{chunk-OKXCECLG.js → chunk-TCCE5ZU5.js} +171 -22
- package/dist/chunk-TCCE5ZU5.js.map +1 -0
- package/dist/chunk-U255M5EZ.js +178 -0
- package/dist/chunk-U255M5EZ.js.map +1 -0
- package/dist/chunk-ZNNE2VES.js +6413 -0
- package/dist/chunk-ZNNE2VES.js.map +1 -0
- package/dist/client.d.ts +33 -0
- package/dist/commands/index.d.ts +3 -0
- package/dist/commands/registry.d.ts +41 -0
- package/dist/commands/types.d.ts +85 -0
- package/dist/components/approval-surface.d.ts +28 -0
- package/dist/components/chat-composer.d.ts +51 -0
- package/dist/components/code-editor.d.ts +43 -0
- package/dist/components/command-palette.d.ts +25 -0
- package/dist/components/composer-transcription-control.d.ts +36 -0
- package/dist/components/composer.d.ts +277 -0
- package/dist/components/copy-button.d.ts +26 -0
- package/dist/components/desktop-viewer.d.ts +85 -0
- package/dist/components/diff-view.d.ts +38 -0
- package/dist/components/enrollment-consent.d.ts +33 -0
- package/dist/components/enrollment-device-flow.d.ts +27 -0
- package/dist/components/file-browser.d.ts +47 -0
- package/dist/components/fleet-tile.d.ts +18 -0
- package/dist/components/human-input-form.d.ts +47 -0
- package/dist/components/machine-card.d.ts +21 -0
- package/dist/components/machine-dock-bar.d.ts +31 -0
- package/dist/components/machine-health-pill.d.ts +9 -0
- package/dist/components/machine-metrics.d.ts +15 -0
- package/dist/components/machine-status-pill.d.ts +54 -0
- package/dist/components/machines/health.d.ts +25 -0
- package/dist/components/machines/machine-detail.d.ts +14 -0
- package/dist/components/machines/metric-history-chart.d.ts +24 -0
- package/dist/components/machines/metric-sparkline.d.ts +9 -0
- package/dist/components/machines/series.d.ts +24 -0
- package/dist/components/machines-dashboard.d.ts +30 -0
- package/dist/components/markdown.d.ts +31 -0
- package/dist/components/message-timeline.d.ts +98 -0
- package/dist/components/model-picker.d.ts +19 -0
- package/dist/components/pierre-diff.d.ts +34 -0
- package/dist/components/pierre-file.d.ts +27 -0
- package/dist/components/queue-draft-policy.d.ts +7 -0
- package/dist/components/queue-surface-implementation.d.ts +20 -0
- package/dist/components/queue-surface-state.d.ts +12 -0
- package/dist/components/queue-surface.d.ts +12 -0
- package/dist/components/sandbox-files.d.ts +49 -0
- package/dist/components/sandbox-terminal.d.ts +123 -0
- package/dist/components/sandbox-workspace.d.ts +128 -0
- package/dist/components/session-chrome.d.ts +70 -0
- package/dist/components/session-status.d.ts +25 -0
- package/dist/components/soften-streaming-markdown.d.ts +13 -0
- package/dist/components/stream-reveal.d.ts +37 -0
- package/dist/components/tip-follow.d.ts +139 -0
- package/dist/components/tooltip.d.ts +16 -0
- package/dist/components/workbench-changes.d.ts +34 -0
- package/dist/components/workspace-dock.d.ts +35 -0
- package/dist/composer.d.ts +11 -7
- package/dist/composer.js +9 -6
- package/dist/hooks/internal.d.ts +46 -0
- package/dist/hooks/use-available-models.d.ts +37 -0
- package/dist/hooks/use-billing-usage.d.ts +26 -0
- package/dist/hooks/use-codex-accounts.d.ts +69 -0
- package/dist/{use-composer-ClHy1rkL.d.ts → hooks/use-composer.d.ts} +16 -24
- package/dist/hooks/use-desktop-stream.d.ts +43 -0
- package/dist/hooks/use-environments.d.ts +35 -0
- package/dist/{use-file-attachments-BmT8WtqO.d.ts → hooks/use-file-attachments.d.ts} +6 -9
- package/dist/hooks/use-goal.d.ts +46 -0
- package/dist/hooks/use-human-input.d.ts +26 -0
- package/dist/hooks/use-last-started-turn-policy.d.ts +27 -0
- package/dist/hooks/use-machine-chip.d.ts +54 -0
- package/dist/hooks/use-machines.d.ts +81 -0
- package/dist/hooks/use-packs.d.ts +27 -0
- package/dist/hooks/use-relay-frame-stream.d.ts +53 -0
- package/dist/hooks/use-rigs.d.ts +87 -0
- package/dist/hooks/use-sandbox-files.d.ts +108 -0
- package/dist/hooks/use-sandbox-git.d.ts +58 -0
- package/dist/hooks/use-sandbox-terminal.d.ts +62 -0
- package/dist/hooks/use-scheduled-tasks.d.ts +15 -0
- package/dist/hooks/use-session-capabilities.d.ts +83 -0
- package/dist/hooks/use-session-control.d.ts +22 -0
- package/dist/hooks/use-session-events.d.ts +92 -0
- package/dist/hooks/use-session-lineage.d.ts +17 -0
- package/dist/hooks/use-session-mcp-approval-policy.d.ts +23 -0
- package/dist/hooks/use-session.d.ts +23 -0
- package/dist/hooks/use-slash-commands.d.ts +66 -0
- package/dist/hooks/use-terminal-stream.d.ts +46 -0
- package/dist/hooks/use-transcription.d.ts +73 -0
- package/dist/hooks/use-turn-queue.d.ts +44 -0
- package/dist/hooks/use-voice-input.d.ts +25 -0
- package/dist/hooks/use-windowed-sections.d.ts +42 -0
- package/dist/hooks/use-workspace-capture.d.ts +49 -0
- package/dist/hooks/use-workspace-edit.d.ts +73 -0
- package/dist/hooks/use-workspace-sessions.d.ts +30 -0
- package/dist/hooks/use-workspaces.d.ts +22 -0
- package/dist/human-input.d.ts +18 -0
- package/dist/index.d.ts +144 -2119
- package/dist/index.js +421 -324
- package/dist/index.js.map +1 -1
- package/dist/lib/clipboard.d.ts +7 -0
- package/dist/lib/cn.d.ts +3 -0
- package/dist/lib/format.d.ts +48 -0
- package/dist/lib/git-patch.d.ts +8 -0
- package/dist/lib/motion-inspect.d.ts +5 -0
- package/dist/lib/motion.d.ts +14 -0
- package/dist/lib/noto-sans-arabic-loader.d.ts +1 -0
- package/dist/lib/noto-sans-jp-loader.generated.d.ts +1 -0
- package/dist/lib/relay-wire.d.ts +30 -0
- package/dist/lib/use-portal-token-style.d.ts +9 -0
- package/dist/lib/use-theme-type.d.ts +5 -0
- package/dist/lib/use-unicode-fonts.d.ts +12 -0
- package/dist/lib/xterm-renderer.d.ts +19 -0
- package/dist/lib/xterm-theme.d.ts +75 -0
- package/dist/machines.d.ts +30 -413
- package/dist/machines.js +3 -3
- package/dist/model-policy.d.ts +32 -0
- package/dist/model-policy.js +33 -0
- package/dist/model-policy.js.map +1 -0
- package/dist/provider.d.ts +16 -0
- package/dist/{queue-surface-implementation-UQ535OKQ.js → queue-surface-implementation-THPAKGMD.js} +2 -2
- package/dist/{queue-surface-implementation-UQ535OKQ.js.map → queue-surface-implementation-THPAKGMD.js.map} +1 -1
- package/dist/session-context.d.ts +82 -0
- package/dist/session-ui.d.ts +10 -7
- package/dist/session-ui.js +9 -5
- package/dist/session.d.ts +28 -6
- package/dist/session.js +11 -11
- package/dist/timeline/activity-rail.d.ts +19 -0
- package/dist/timeline/disclosure-context.d.ts +14 -0
- package/dist/timeline/entrance.d.ts +14 -0
- package/dist/timeline/fleet-decision-projection.d.ts +9 -0
- package/dist/timeline/fleet-decision-row.d.ts +11 -0
- package/dist/timeline/fold-memory.d.ts +15 -0
- package/dist/timeline/index.d.ts +18 -0
- package/dist/timeline/parsers.d.ts +96 -0
- package/dist/timeline/projection.d.ts +38 -0
- package/dist/timeline/registry.d.ts +40 -0
- package/dist/timeline/screenshot-lightbox.d.ts +26 -0
- package/dist/timeline/seen-activity-ids.d.ts +2 -0
- package/dist/timeline/shared.d.ts +125 -0
- package/dist/timeline/tool-diff.d.ts +8 -0
- package/dist/timeline/tool-display-name.d.ts +10 -0
- package/dist/timeline/tool-renderers.d.ts +5 -0
- package/dist/timeline/turn-summary.d.ts +116 -0
- package/dist/{use-turn-queue-D_1a8W_l.d.ts → timeline/types.d.ts} +41 -69
- package/dist/types/machines.d.ts +11 -0
- package/package.json +8 -4
- package/src/client.ts +1 -0
- package/src/components/composer-transcription-control.tsx +278 -130
- package/src/components/composer.tsx +20 -15
- package/src/components/copy-button.tsx +127 -0
- package/src/components/markdown.tsx +231 -32
- package/src/components/message-timeline.tsx +1304 -378
- package/src/components/model-picker.tsx +83 -43
- package/src/components/queue-surface-implementation.tsx +1 -1
- package/src/components/session-chrome.tsx +942 -0
- package/src/components/soften-streaming-markdown.ts +205 -0
- package/src/components/stream-reveal.ts +195 -0
- package/src/components/tip-follow.ts +459 -0
- package/src/components/tooltip.tsx +103 -0
- package/src/composer.ts +6 -0
- package/src/hooks/internal.ts +30 -4
- package/src/hooks/use-available-models.ts +51 -4
- package/src/hooks/use-composer.ts +23 -3
- package/src/hooks/use-last-started-turn-policy.ts +95 -0
- package/src/hooks/use-session-events.ts +359 -5
- package/src/hooks/use-voice-input.ts +232 -0
- package/src/index.ts +47 -1
- package/src/lib/clipboard.ts +58 -0
- package/src/lib/format.ts +18 -0
- package/src/lib/motion-inspect.ts +5 -0
- package/src/lib/motion.ts +80 -0
- package/src/model-policy.ts +210 -0
- package/src/session-ui.ts +7 -0
- package/src/timeline/activity-rail.tsx +46 -7
- package/src/timeline/entrance.tsx +10 -0
- package/src/timeline/fold-memory.ts +52 -0
- package/src/timeline/index.ts +9 -1
- package/src/timeline/projection.ts +226 -41
- package/src/timeline/seen-activity-ids.ts +19 -0
- package/src/timeline/shared.tsx +1 -1
- package/src/timeline/tool-renderers.tsx +75 -6
- package/src/timeline/turn-summary.tsx +387 -97
- package/src/timeline/types.ts +21 -0
- package/styles/index.css +99 -7
- package/styles/tokens.css +44 -3
- package/dist/chunk-AVLMIWYO.js.map +0 -1
- package/dist/chunk-CSJBF3QJ.js.map +0 -1
- package/dist/chunk-IPVOVOW6.js.map +0 -1
- package/dist/chunk-M7X4JZOD.js.map +0 -1
- package/dist/chunk-OKXCECLG.js.map +0 -1
- package/dist/chunk-ZMNPFZWL.js +0 -3918
- package/dist/chunk-ZMNPFZWL.js.map +0 -1
- package/dist/composer-BMzimGTO.d.ts +0 -528
- package/dist/session-DaONt-dY.d.ts +0 -264
- package/dist/session-context-CAjlaeZb.d.ts +0 -84
- package/dist/session-ui-B5_roA95.d.ts +0 -259
- /package/dist/{chunk-BC6RKIHS.js.map → chunk-GZ4KR67U.js.map} +0 -0
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
/* ----------------------------------------------------------------------------
|
|
2
|
+
Tip-follow camera (pure)
|
|
3
|
+
|
|
4
|
+
DOM truth is immediate; the camera eases down toward the layout tip.
|
|
5
|
+
No React, no DOM — the shell reads metrics, calls step, writes scrollTop.
|
|
6
|
+
|
|
7
|
+
One law while pinned — second-order camera (no hard first bite):
|
|
8
|
+
|
|
9
|
+
desiredVel = debt / τ
|
|
10
|
+
scrollVel → exponential approach to desiredVel (accel τ = k·τ)
|
|
11
|
+
scrollTop += scrollVel · dt (clamp: no overshoot past tip)
|
|
12
|
+
|
|
13
|
+
- Debt jumps with layout; velocity cannot — so motion eases in, then as
|
|
14
|
+
debt falls desiredVel→0 and velocity eases out (soft settle).
|
|
15
|
+
- τ still adapts (calm / catch-up / line / idle settle).
|
|
16
|
+
- maxStep is a speed ceiling only, never a one-frame glue.
|
|
17
|
+
- Shrink is separate: compensate Δh so collapse doesn't fight the ease
|
|
18
|
+
- Snap only for first paint / reduced motion / cold oversized jumps
|
|
19
|
+
-------------------------------------------------------------------------- */
|
|
20
|
+
|
|
21
|
+
import { MOTION_INSPECT_SCALE } from "../lib/motion-inspect";
|
|
22
|
+
|
|
23
|
+
const _t = (ms: number) => ms * MOTION_INSPECT_SCALE;
|
|
24
|
+
const _s = (pxPerSec: number) => pxPerSec / MOTION_INSPECT_SCALE;
|
|
25
|
+
|
|
26
|
+
/** Calm ease while live: remaining debt shrinks ~63% every τ_max ms. */
|
|
27
|
+
export const TIP_FOLLOW_TAU_MAX_MS = _t(720);
|
|
28
|
+
/** Catch-up ease under large tip-debt (reader far behind / big paste). */
|
|
29
|
+
export const TIP_FOLLOW_TAU_MIN_MS = _t(200);
|
|
30
|
+
/**
|
|
31
|
+
* Idle settle τ — stream quiet, soft landing into the tip. Longer than live
|
|
32
|
+
* calm so the last pixels decelerate instead of linear-crawling then stopping.
|
|
33
|
+
*/
|
|
34
|
+
export const TIP_FOLLOW_SETTLE_TAU_MS = _t(960);
|
|
35
|
+
/** Debt that saturates urgency toward τ_min. */
|
|
36
|
+
export const TIP_FOLLOW_CATCHUP_DEBT_PX = 240;
|
|
37
|
+
/** Keep the rAF alive this long after the last height growth. */
|
|
38
|
+
export const TIP_FOLLOW_HOT_IDLE_MS = _t(320);
|
|
39
|
+
/** Cold oversized debt snaps (session switch / huge fold), not eases. */
|
|
40
|
+
export const TIP_FOLLOW_SNAP_PX = 480;
|
|
41
|
+
/** Soft speed floor while live with tiny debt (px/s). */
|
|
42
|
+
export const TIP_FOLLOW_CALM_MAX_PX_S = _s(42);
|
|
43
|
+
/** Soft speed ceiling while catching a fast stream / modest tip-debt (px/s). */
|
|
44
|
+
export const TIP_FOLLOW_BURST_MAX_PX_S = _s(420);
|
|
45
|
+
/** Ceiling for huge tip-debt / large single-frame walls (px/s). */
|
|
46
|
+
export const TIP_FOLLOW_SURGE_MAX_PX_S = _s(1800);
|
|
47
|
+
/**
|
|
48
|
+
* @deprecated Position growth-track removed — camera is velocity-smoothed.
|
|
49
|
+
* Kept so older tests/imports do not break.
|
|
50
|
+
*/
|
|
51
|
+
export const TIP_FOLLOW_GROWTH_TRACK = 0;
|
|
52
|
+
/** @deprecated */
|
|
53
|
+
export const TIP_FOLLOW_LINE_TRACK = 0;
|
|
54
|
+
/** @deprecated Sub-line special-case removed with growth-track. */
|
|
55
|
+
export const TIP_FOLLOW_TOKEN_GROWTH_PX = 10;
|
|
56
|
+
/** Hot line-sized tip-debt uses the short {@link TIP_FOLLOW_LINE_TAU_MS}. */
|
|
57
|
+
export const TIP_FOLLOW_LINE_GROWTH_PX = 48;
|
|
58
|
+
/** Hot τ ceiling while closing a line-sized tip-debt (ms). */
|
|
59
|
+
export const TIP_FOLLOW_LINE_TAU_MS = _t(140);
|
|
60
|
+
/**
|
|
61
|
+
* Velocity ease-in/out: scrollVel approaches desiredVel with this fraction of τ.
|
|
62
|
+
* Lower → snappier accel; higher → softer start (and slower reaction).
|
|
63
|
+
*/
|
|
64
|
+
export const TIP_FOLLOW_ACCEL_TAU_FRAC = 0.45;
|
|
65
|
+
/** @deprecated Soft-rise FLIP removed — line glue was the bug. */
|
|
66
|
+
export const TIP_FOLLOW_SOFT_RISE_MS = 0;
|
|
67
|
+
/** @deprecated */
|
|
68
|
+
export const TIP_FOLLOW_SOFT_RISE_MAX_PX = 96;
|
|
69
|
+
/** @deprecated */
|
|
70
|
+
export const TIP_FOLLOW_SOFT_RISE_EASING = "cubic-bezier(0.22, 1, 0.36, 1)";
|
|
71
|
+
/** Ignore sub-pixel / reflow height noise for shrink compensate. */
|
|
72
|
+
export const TIP_FOLLOW_SHRINK_EPS_PX = 4;
|
|
73
|
+
/** Growth speed (px/s) that saturates velocity urgency. */
|
|
74
|
+
export const TIP_FOLLOW_VELOCITY_REF_PX_S = _s(420);
|
|
75
|
+
/**
|
|
76
|
+
* Tip-debt below which growth velocity must not tighten τ.
|
|
77
|
+
* Sparse tokens are high-velocity by nature; only arm once actually behind.
|
|
78
|
+
*/
|
|
79
|
+
export const TIP_FOLLOW_VELOCITY_ARM_DEBT_PX = 72;
|
|
80
|
+
/** EMA decay τ for growthVelocity when height is flat (ms). */
|
|
81
|
+
export const TIP_FOLLOW_VELOCITY_DECAY_MS = _t(180);
|
|
82
|
+
/** Reader-up pixels above clamp budget that count as leaving the tip. */
|
|
83
|
+
export const TIP_FOLLOW_READER_UP_EPS_PX = 2;
|
|
84
|
+
/** @deprecated Shrink lock removed; kept so old imports do not break. */
|
|
85
|
+
export const TIP_FOLLOW_SHRINK_LOCK_MS = 0;
|
|
86
|
+
/** @deprecated Absorb thresholds removed — growth track is continuous. */
|
|
87
|
+
export const TIP_FOLLOW_GROWTH_ABSORB_PX = 96;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* ScrollTop decrease not explained by a maxScroll clamp (fold / composer
|
|
91
|
+
* shrink). Fold clamp: top falls by ≈ maxScroll fall → 0. Real scroll-up:
|
|
92
|
+
* top falls while maxScroll holds → positive. No timers.
|
|
93
|
+
*/
|
|
94
|
+
export function readerScrollUpPx(
|
|
95
|
+
prevTop: number,
|
|
96
|
+
nextTop: number,
|
|
97
|
+
prevMaxScroll: number,
|
|
98
|
+
nextMaxScroll: number,
|
|
99
|
+
): number {
|
|
100
|
+
const topDelta = prevTop - nextTop;
|
|
101
|
+
const clampBudget = Math.max(0, prevMaxScroll - nextMaxScroll);
|
|
102
|
+
return topDelta - clampBudget;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export type TipFollowState = {
|
|
106
|
+
running: boolean;
|
|
107
|
+
hotUntil: number;
|
|
108
|
+
lastTs: number;
|
|
109
|
+
lastHeight: number;
|
|
110
|
+
lastGrowthAt: number;
|
|
111
|
+
/** EMA of recent height growth (px/s); nudges τ / ceiling under bursts. */
|
|
112
|
+
growthVelocity: number;
|
|
113
|
+
/** Camera velocity toward tip (px/s). Smoothed — never jumps with debt. */
|
|
114
|
+
scrollVelocity: number;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
export type TipFollowStepInput = {
|
|
118
|
+
scrollTop: number;
|
|
119
|
+
scrollHeight: number;
|
|
120
|
+
clientHeight: number;
|
|
121
|
+
now: number;
|
|
122
|
+
pinned: boolean;
|
|
123
|
+
reducedMotion: boolean;
|
|
124
|
+
revealed: boolean;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export type TipFollowStepResult = {
|
|
128
|
+
scrollTop: number;
|
|
129
|
+
state: TipFollowState;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
export function createTipFollowState(): TipFollowState {
|
|
133
|
+
return {
|
|
134
|
+
running: false,
|
|
135
|
+
hotUntil: 0,
|
|
136
|
+
lastTs: 0,
|
|
137
|
+
lastHeight: 0,
|
|
138
|
+
lastGrowthAt: 0,
|
|
139
|
+
growthVelocity: 0,
|
|
140
|
+
scrollVelocity: 0,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function tipFollowCancel(state: TipFollowState): TipFollowState {
|
|
145
|
+
return {
|
|
146
|
+
...state,
|
|
147
|
+
running: false,
|
|
148
|
+
lastTs: 0,
|
|
149
|
+
scrollVelocity: 0,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Record content height. Positive growth extends the hot window so sparse
|
|
155
|
+
* lines do not tear down the follow loop between appends.
|
|
156
|
+
*/
|
|
157
|
+
export function tipFollowNoteGrowth(
|
|
158
|
+
state: TipFollowState,
|
|
159
|
+
height: number,
|
|
160
|
+
now: number,
|
|
161
|
+
): TipFollowState {
|
|
162
|
+
const previous = state.lastHeight;
|
|
163
|
+
if (previous <= 0) {
|
|
164
|
+
return { ...state, lastHeight: height };
|
|
165
|
+
}
|
|
166
|
+
if (height <= previous) {
|
|
167
|
+
return { ...state, lastHeight: height };
|
|
168
|
+
}
|
|
169
|
+
const dt = state.lastGrowthAt > 0 ? now - state.lastGrowthAt : 0;
|
|
170
|
+
let growthVelocity = state.growthVelocity;
|
|
171
|
+
if (dt > 0) {
|
|
172
|
+
const instant = ((height - previous) / dt) * 1000;
|
|
173
|
+
growthVelocity = growthVelocity * 0.65 + instant * 0.35;
|
|
174
|
+
}
|
|
175
|
+
return {
|
|
176
|
+
...state,
|
|
177
|
+
lastHeight: height,
|
|
178
|
+
lastGrowthAt: now,
|
|
179
|
+
growthVelocity,
|
|
180
|
+
hotUntil: now + TIP_FOLLOW_HOT_IDLE_MS,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Adaptive τ: small debt → calm; large debt / armed velocity → catch-up.
|
|
186
|
+
* While hot, floor urgency so live streams are not stuck on calm τ.
|
|
187
|
+
* Idle settle uses the longer settle τ.
|
|
188
|
+
*/
|
|
189
|
+
export function tipFollowTauMs(
|
|
190
|
+
debtPx: number,
|
|
191
|
+
growthVelocityPxPerSec = 0,
|
|
192
|
+
settling = false,
|
|
193
|
+
hot = false,
|
|
194
|
+
): number {
|
|
195
|
+
if (settling) {
|
|
196
|
+
return TIP_FOLLOW_SETTLE_TAU_MS;
|
|
197
|
+
}
|
|
198
|
+
const absDebt = Math.abs(debtPx);
|
|
199
|
+
const debtT = Math.min(1, absDebt / TIP_FOLLOW_CATCHUP_DEBT_PX);
|
|
200
|
+
let velUrgency = 0;
|
|
201
|
+
if (absDebt >= TIP_FOLLOW_VELOCITY_ARM_DEBT_PX) {
|
|
202
|
+
const velT = Math.min(1, Math.max(0, growthVelocityPxPerSec) / TIP_FOLLOW_VELOCITY_REF_PX_S);
|
|
203
|
+
velUrgency = velT * 0.55;
|
|
204
|
+
}
|
|
205
|
+
const hotUrgency = hot ? 0.65 : 0;
|
|
206
|
+
const urgency = Math.min(1, Math.max(debtT, velUrgency, hotUrgency));
|
|
207
|
+
let tau = TIP_FOLLOW_TAU_MAX_MS + (TIP_FOLLOW_TAU_MIN_MS - TIP_FOLLOW_TAU_MAX_MS) * urgency;
|
|
208
|
+
// Line-sized live debt: close over ~LINE_TAU so the glide finishes before
|
|
209
|
+
// the next newline without needing 1:1 tip-glue.
|
|
210
|
+
if (hot && absDebt > 0 && absDebt <= TIP_FOLLOW_LINE_GROWTH_PX) {
|
|
211
|
+
tau = Math.min(tau, TIP_FOLLOW_LINE_TAU_MS);
|
|
212
|
+
}
|
|
213
|
+
return tau;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Soft speed ceiling from continuous signals (debt, velocity EMA, this-frame
|
|
218
|
+
* growth). No mode switches — larger inputs raise the ceiling smoothly.
|
|
219
|
+
*/
|
|
220
|
+
export function tipFollowMaxStepPx(
|
|
221
|
+
debtPx: number,
|
|
222
|
+
growthVelocityPxPerSec: number,
|
|
223
|
+
dtMs: number,
|
|
224
|
+
frameGrowthPx = 0,
|
|
225
|
+
hot = false,
|
|
226
|
+
): number {
|
|
227
|
+
const absDebt = Math.abs(debtPx);
|
|
228
|
+
const debtT = Math.min(1, absDebt / TIP_FOLLOW_CATCHUP_DEBT_PX);
|
|
229
|
+
const velT = Math.min(1, Math.max(0, growthVelocityPxPerSec) / TIP_FOLLOW_VELOCITY_REF_PX_S);
|
|
230
|
+
const armed = absDebt >= TIP_FOLLOW_VELOCITY_ARM_DEBT_PX;
|
|
231
|
+
const blend = Math.max(debtT, armed ? velT : 0, hot ? 0.35 : 0);
|
|
232
|
+
let maxPxS =
|
|
233
|
+
TIP_FOLLOW_CALM_MAX_PX_S +
|
|
234
|
+
(TIP_FOLLOW_BURST_MAX_PX_S - TIP_FOLLOW_CALM_MAX_PX_S) * blend * blend;
|
|
235
|
+
if (hot && frameGrowthPx > 0 && dtMs > 0) {
|
|
236
|
+
// Allow tracking this frame's growth rate (growthTrack uses a fraction).
|
|
237
|
+
maxPxS = Math.max(maxPxS, (frameGrowthPx / dtMs) * 1000);
|
|
238
|
+
}
|
|
239
|
+
if (hot && growthVelocityPxPerSec > TIP_FOLLOW_CALM_MAX_PX_S) {
|
|
240
|
+
// Live stream: slightly outrun the EMA so tip-debt cannot ratchet forever.
|
|
241
|
+
maxPxS = Math.max(maxPxS, Math.min(TIP_FOLLOW_BURST_MAX_PX_S, growthVelocityPxPerSec * 1.25));
|
|
242
|
+
}
|
|
243
|
+
if (hot && absDebt > 8) {
|
|
244
|
+
// Continuous debt horizon (no catch-up cliff at CATCHUP_DEBT).
|
|
245
|
+
maxPxS = Math.max(maxPxS, Math.min(TIP_FOLLOW_SURGE_MAX_PX_S, absDebt / 0.2));
|
|
246
|
+
} else if (absDebt >= TIP_FOLLOW_CATCHUP_DEBT_PX) {
|
|
247
|
+
maxPxS = Math.max(maxPxS, Math.min(TIP_FOLLOW_SURGE_MAX_PX_S, absDebt / 0.12));
|
|
248
|
+
}
|
|
249
|
+
return (maxPxS / 1000) * dtMs;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function targetScrollTop(scrollHeight: number, clientHeight: number): number {
|
|
253
|
+
return Math.max(0, scrollHeight - clientHeight);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Counter-offset for soft-rise after tip-glue: start from the mid-flight
|
|
258
|
+
* translateY (if any) plus this frame's glued scroll delta, capped.
|
|
259
|
+
* Shell applies translateY(from) → 0 over {@link TIP_FOLLOW_SOFT_RISE_MS}.
|
|
260
|
+
*/
|
|
261
|
+
export function tipFollowSoftRiseFrom(
|
|
262
|
+
currentTranslateY: number,
|
|
263
|
+
gluedScrollDelta: number,
|
|
264
|
+
maxPx: number = TIP_FOLLOW_SOFT_RISE_MAX_PX,
|
|
265
|
+
): number {
|
|
266
|
+
const stacked = Math.max(0, currentTranslateY) + Math.max(0, gluedScrollDelta);
|
|
267
|
+
return Math.min(maxPx, stacked);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** Read live translateY from a transformed content column (mid soft-rise). */
|
|
271
|
+
export function readTranslateY(el: HTMLElement): number {
|
|
272
|
+
const raw = getComputedStyle(el).transform;
|
|
273
|
+
if (!raw || raw === "none") {
|
|
274
|
+
return 0;
|
|
275
|
+
}
|
|
276
|
+
try {
|
|
277
|
+
return new DOMMatrixReadOnly(raw).m42;
|
|
278
|
+
} catch {
|
|
279
|
+
return 0;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Cancel the tip-glue pop: start at translateY(+δ) and ease to 0.
|
|
285
|
+
* Uses WAAPI — a same-frame CSS transition never paints the counter-offset,
|
|
286
|
+
* so the scroll jump stays an instant one-line pop.
|
|
287
|
+
*/
|
|
288
|
+
export function tipFollowPlaySoftRise(
|
|
289
|
+
el: HTMLElement,
|
|
290
|
+
gluedScrollDelta: number,
|
|
291
|
+
opts?: {
|
|
292
|
+
durationMs?: number;
|
|
293
|
+
maxPx?: number;
|
|
294
|
+
easing?: string;
|
|
295
|
+
},
|
|
296
|
+
): void {
|
|
297
|
+
if (gluedScrollDelta <= 0.5) {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
const durationMs = opts?.durationMs ?? TIP_FOLLOW_SOFT_RISE_MS;
|
|
301
|
+
const maxPx = opts?.maxPx ?? TIP_FOLLOW_SOFT_RISE_MAX_PX;
|
|
302
|
+
const easing = opts?.easing ?? TIP_FOLLOW_SOFT_RISE_EASING;
|
|
303
|
+
const from = tipFollowSoftRiseFrom(readTranslateY(el), gluedScrollDelta, maxPx);
|
|
304
|
+
if (from <= 0.5) {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
// Drop any in-flight rise / leftover CSS transition before starting clean.
|
|
308
|
+
if (typeof el.getAnimations === "function") {
|
|
309
|
+
for (const animation of el.getAnimations()) {
|
|
310
|
+
animation.cancel();
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
el.style.transition = "";
|
|
314
|
+
// Hold the counter-offset in style until WAAPI’s first keyframe applies.
|
|
315
|
+
// Clearing to "" here painted one frame of hard tip-glue (the pop).
|
|
316
|
+
el.style.transform = `translateY(${from}px)`;
|
|
317
|
+
if (typeof el.animate !== "function") {
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
const animation = el.animate(
|
|
321
|
+
[{ transform: `translateY(${from}px)` }, { transform: "translateY(0px)" }],
|
|
322
|
+
// `backwards`: first keyframe applies immediately (no gap after cancel).
|
|
323
|
+
{ duration: durationMs, easing, fill: "backwards" },
|
|
324
|
+
);
|
|
325
|
+
const clearHold = () => {
|
|
326
|
+
if (el.style.transform === `translateY(${from}px)`) {
|
|
327
|
+
el.style.transform = "";
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
animation.addEventListener("finish", clearHold, { once: true });
|
|
331
|
+
animation.addEventListener("cancel", clearHold, { once: true });
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/** Stop soft-rise and clear any CSS transform residue. */
|
|
335
|
+
export function tipFollowClearSoftRise(el: HTMLElement): void {
|
|
336
|
+
if (typeof el.getAnimations === "function") {
|
|
337
|
+
for (const animation of el.getAnimations()) {
|
|
338
|
+
animation.cancel();
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
el.style.transition = "";
|
|
342
|
+
el.style.transform = "";
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Height left the document (settle collapse / composer). Keep the same pixels
|
|
347
|
+
* on screen: scrollTop -= Δh, clamped to the new max.
|
|
348
|
+
*/
|
|
349
|
+
export function tipFollowCompensateShrink(
|
|
350
|
+
scrollTop: number,
|
|
351
|
+
previousHeight: number,
|
|
352
|
+
nextHeight: number,
|
|
353
|
+
clientHeight: number,
|
|
354
|
+
epsPx: number = TIP_FOLLOW_SHRINK_EPS_PX,
|
|
355
|
+
): number {
|
|
356
|
+
const delta = previousHeight - nextHeight;
|
|
357
|
+
if (delta <= epsPx) {
|
|
358
|
+
return scrollTop;
|
|
359
|
+
}
|
|
360
|
+
const maxScroll = Math.max(0, nextHeight - clientHeight);
|
|
361
|
+
return Math.max(0, Math.min(maxScroll, scrollTop - delta));
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* One camera step. Shell schedules the next rAF while `result.state.running`.
|
|
366
|
+
*/
|
|
367
|
+
export function tipFollowStep(
|
|
368
|
+
state: TipFollowState,
|
|
369
|
+
input: TipFollowStepInput,
|
|
370
|
+
): TipFollowStepResult {
|
|
371
|
+
const { scrollTop, scrollHeight, clientHeight, now, pinned, reducedMotion, revealed } = input;
|
|
372
|
+
const previousHeight = state.lastHeight;
|
|
373
|
+
const frameGrowth =
|
|
374
|
+
previousHeight > 0 && scrollHeight > previousHeight ? scrollHeight - previousHeight : 0;
|
|
375
|
+
const grew = frameGrowth > 0;
|
|
376
|
+
let noted = tipFollowNoteGrowth(state, scrollHeight, now);
|
|
377
|
+
const target = targetScrollTop(scrollHeight, clientHeight);
|
|
378
|
+
const debt = target - scrollTop;
|
|
379
|
+
const hot = now < noted.hotUntil;
|
|
380
|
+
const settling = !hot && Math.abs(debt) < TIP_FOLLOW_CATCHUP_DEBT_PX;
|
|
381
|
+
|
|
382
|
+
if (!pinned) {
|
|
383
|
+
return {
|
|
384
|
+
scrollTop,
|
|
385
|
+
state: tipFollowCancel(noted),
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (!revealed || reducedMotion) {
|
|
390
|
+
return {
|
|
391
|
+
scrollTop: target,
|
|
392
|
+
state: tipFollowCancel(noted),
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (Math.abs(debt) > TIP_FOLLOW_SNAP_PX && !hot) {
|
|
397
|
+
return {
|
|
398
|
+
scrollTop: target,
|
|
399
|
+
state: tipFollowCancel(noted),
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const dt = noted.lastTs === 0 ? 16 : Math.min(64, Math.max(8, now - noted.lastTs));
|
|
404
|
+
if (!grew && noted.growthVelocity > 0) {
|
|
405
|
+
const decayed = noted.growthVelocity * Math.exp(-dt / TIP_FOLLOW_VELOCITY_DECAY_MS);
|
|
406
|
+
noted = {
|
|
407
|
+
...noted,
|
|
408
|
+
growthVelocity: decayed < 1 ? 0 : decayed,
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (Math.abs(debt) <= 0.75) {
|
|
413
|
+
return {
|
|
414
|
+
scrollTop: Math.abs(debt) > 0 ? target : scrollTop,
|
|
415
|
+
state: {
|
|
416
|
+
...tipFollowCancel(noted),
|
|
417
|
+
growthVelocity: hot ? noted.growthVelocity : 0,
|
|
418
|
+
},
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const tau = tipFollowTauMs(debt, noted.growthVelocity, settling, hot);
|
|
423
|
+
const dtSec = dt / 1000;
|
|
424
|
+
const tauSec = Math.max(tau / 1000, 1e-3);
|
|
425
|
+
// P-gain: held desiredVel would close debt in ~τ. Velocity cannot jump —
|
|
426
|
+
// it eases toward desiredVel, so a new line accelerates in then settles out.
|
|
427
|
+
const desiredVel = debt / tauSec;
|
|
428
|
+
const accelTauSec = Math.max(tauSec * TIP_FOLLOW_ACCEL_TAU_FRAC, 0.02);
|
|
429
|
+
const velAlpha = 1 - Math.exp(-dtSec / accelTauSec);
|
|
430
|
+
let scrollVelocity =
|
|
431
|
+
(noted.scrollVelocity ?? 0) + (desiredVel - (noted.scrollVelocity ?? 0)) * velAlpha;
|
|
432
|
+
|
|
433
|
+
const maxStep = tipFollowMaxStepPx(debt, noted.growthVelocity, dt, frameGrowth, hot);
|
|
434
|
+
const maxVel = maxStep / dtSec;
|
|
435
|
+
if (Math.abs(scrollVelocity) > maxVel) {
|
|
436
|
+
scrollVelocity = Math.sign(scrollVelocity) * maxVel;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
let next = scrollTop + scrollVelocity * dtSec;
|
|
440
|
+
// Never overshoot the tip — stop and kill velocity on arrival.
|
|
441
|
+
if (debt > 0 && next >= target - 0.35) {
|
|
442
|
+
next = target;
|
|
443
|
+
scrollVelocity = 0;
|
|
444
|
+
} else if (debt < 0 && next <= target + 0.35) {
|
|
445
|
+
next = target;
|
|
446
|
+
scrollVelocity = 0;
|
|
447
|
+
}
|
|
448
|
+
next = Math.max(0, next);
|
|
449
|
+
|
|
450
|
+
return {
|
|
451
|
+
scrollTop: next,
|
|
452
|
+
state: {
|
|
453
|
+
...noted,
|
|
454
|
+
scrollVelocity,
|
|
455
|
+
running: Math.abs(target - next) > 0.35 || Math.abs(scrollVelocity) > 1,
|
|
456
|
+
lastTs: now,
|
|
457
|
+
},
|
|
458
|
+
};
|
|
459
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token-styled Radix tooltip — SDK counterpart to apps/web `components/ui/tooltip`.
|
|
3
|
+
* Portals copy `--og-*` tokens so embeds keep the host theme outside `.og-root`.
|
|
4
|
+
*
|
|
5
|
+
* Background/foreground are applied as inline styles (not only Tailwind
|
|
6
|
+
* utilities): `.og-root` sets `color: var(--og-color-fg)` after utilities in the
|
|
7
|
+
* consumer CSS cascade, which would otherwise paint light-on-light inverted
|
|
8
|
+
* tips and look like a blank white bubble.
|
|
9
|
+
*/
|
|
10
|
+
import { Tooltip as TooltipPrimitive } from "radix-ui";
|
|
11
|
+
import {
|
|
12
|
+
createContext,
|
|
13
|
+
useContext,
|
|
14
|
+
useRef,
|
|
15
|
+
type ComponentProps,
|
|
16
|
+
type CSSProperties,
|
|
17
|
+
type RefObject,
|
|
18
|
+
} from "react";
|
|
19
|
+
|
|
20
|
+
import { cn } from "../lib/cn";
|
|
21
|
+
import { usePortalTokenStyle } from "../lib/use-portal-token-style";
|
|
22
|
+
|
|
23
|
+
const TooltipSourceContext = createContext<RefObject<HTMLElement | null> | null>(null);
|
|
24
|
+
|
|
25
|
+
function TooltipProvider({
|
|
26
|
+
delayDuration = 300,
|
|
27
|
+
...props
|
|
28
|
+
}: ComponentProps<typeof TooltipPrimitive.Provider>) {
|
|
29
|
+
return <TooltipPrimitive.Provider delayDuration={delayDuration} {...props} />;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function Tooltip({ children, ...props }: ComponentProps<typeof TooltipPrimitive.Root>) {
|
|
33
|
+
const sourceRef = useRef<HTMLElement | null>(null);
|
|
34
|
+
return (
|
|
35
|
+
<TooltipSourceContext.Provider value={sourceRef}>
|
|
36
|
+
<TooltipPrimitive.Root data-slot="tooltip" {...props}>
|
|
37
|
+
{children}
|
|
38
|
+
</TooltipPrimitive.Root>
|
|
39
|
+
</TooltipSourceContext.Provider>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function TooltipTrigger({
|
|
44
|
+
asChild = true,
|
|
45
|
+
...props
|
|
46
|
+
}: ComponentProps<typeof TooltipPrimitive.Trigger>) {
|
|
47
|
+
const sourceRef = useContext(TooltipSourceContext);
|
|
48
|
+
return (
|
|
49
|
+
<TooltipPrimitive.Trigger
|
|
50
|
+
data-slot="tooltip-trigger"
|
|
51
|
+
asChild={asChild}
|
|
52
|
+
ref={(node) => {
|
|
53
|
+
if (sourceRef) sourceRef.current = node;
|
|
54
|
+
}}
|
|
55
|
+
{...props}
|
|
56
|
+
/>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const tooltipSurfaceStyle: CSSProperties = {
|
|
61
|
+
backgroundColor: "var(--og-color-fg)",
|
|
62
|
+
color: "var(--og-color-bg)",
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
function TooltipContent({
|
|
66
|
+
className,
|
|
67
|
+
sideOffset = 6,
|
|
68
|
+
children,
|
|
69
|
+
style,
|
|
70
|
+
...props
|
|
71
|
+
}: ComponentProps<typeof TooltipPrimitive.Content>) {
|
|
72
|
+
const sourceRef = useContext(TooltipSourceContext);
|
|
73
|
+
const fallbackRef = useRef<HTMLElement | null>(null);
|
|
74
|
+
const portalStyle = usePortalTokenStyle(sourceRef ?? fallbackRef);
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<TooltipPrimitive.Portal>
|
|
78
|
+
<TooltipPrimitive.Content
|
|
79
|
+
data-slot="tooltip-content"
|
|
80
|
+
sideOffset={sideOffset}
|
|
81
|
+
className={cn(
|
|
82
|
+
"og-root z-50 w-fit max-w-xs origin-[var(--radix-tooltip-content-transform-origin)]",
|
|
83
|
+
"rounded-og-md px-2.5 py-1.5 text-og-xs leading-4 text-balance shadow-og-md",
|
|
84
|
+
"animate-og-enter data-[state=closed]:opacity-0 data-[state=closed]:transition-opacity",
|
|
85
|
+
className,
|
|
86
|
+
)}
|
|
87
|
+
style={{ ...portalStyle, ...tooltipSurfaceStyle, ...style }}
|
|
88
|
+
{...props}
|
|
89
|
+
>
|
|
90
|
+
{children}
|
|
91
|
+
<TooltipPrimitive.Arrow
|
|
92
|
+
className="z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]"
|
|
93
|
+
style={{
|
|
94
|
+
backgroundColor: "var(--og-color-fg)",
|
|
95
|
+
fill: "var(--og-color-fg)",
|
|
96
|
+
}}
|
|
97
|
+
/>
|
|
98
|
+
</TooltipPrimitive.Content>
|
|
99
|
+
</TooltipPrimitive.Portal>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger };
|
package/src/composer.ts
CHANGED
|
@@ -58,3 +58,9 @@ export type {
|
|
|
58
58
|
ComposerTranscriptionControlProps,
|
|
59
59
|
ComposerTranscriptionMessages,
|
|
60
60
|
} from "./components/composer-transcription-control";
|
|
61
|
+
export { useVoiceInput } from "./hooks/use-voice-input";
|
|
62
|
+
export type {
|
|
63
|
+
UseVoiceInputOptions,
|
|
64
|
+
UseVoiceInputResult,
|
|
65
|
+
VoiceInputStatus,
|
|
66
|
+
} from "./hooks/use-voice-input";
|
package/src/hooks/internal.ts
CHANGED
|
@@ -236,6 +236,7 @@ export function useSessionEventTrigger(
|
|
|
236
236
|
}, [match, onEvent, reconcileBeforeLive]);
|
|
237
237
|
const consumedRef = useRef(0);
|
|
238
238
|
const feedKeyRef = useRef<string | null>(null);
|
|
239
|
+
const sharedFeedHasEventsRef = useRef(false);
|
|
239
240
|
|
|
240
241
|
// Shared-log mode: scan only the unseen tail on every append.
|
|
241
242
|
useEffect(() => {
|
|
@@ -244,12 +245,37 @@ export function useSessionEventTrigger(
|
|
|
244
245
|
}
|
|
245
246
|
const feedKey = `${workspaceId}\u0000${sessionId}`;
|
|
246
247
|
const firstSequence = events[0]?.sequence ?? 0;
|
|
247
|
-
|
|
248
|
-
// cursor) restarts consumption from the top of the shared log.
|
|
249
|
-
if (feedKeyRef.current !== feedKey || firstSequence > consumedRef.current + 1) {
|
|
248
|
+
if (feedKeyRef.current !== feedKey) {
|
|
250
249
|
feedKeyRef.current = feedKey;
|
|
251
|
-
|
|
250
|
+
sharedFeedHasEventsRef.current = events.length > 0;
|
|
251
|
+
// The caller has already loaded its authoritative initial projection.
|
|
252
|
+
// Seed from the shared log's current tail so mounting a large historical
|
|
253
|
+
// session cannot replay every old event as a new live trigger.
|
|
254
|
+
consumedRef.current = events.at(-1)?.sequence ?? 0;
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
const firstNonEmptyBatch = !sharedFeedHasEventsRef.current && events.length > 0;
|
|
258
|
+
if (firstNonEmptyBatch || firstSequence > consumedRef.current + 1) {
|
|
259
|
+
// The usual shared feed mounts empty, then receives a historical tail.
|
|
260
|
+
// A later discontinuity can likewise replace the retained browser
|
|
261
|
+
// window. In either case, reconcile once from the latest relevant event
|
|
262
|
+
// instead of replaying the whole retained window as live traffic.
|
|
263
|
+
sharedFeedHasEventsRef.current = events.length > 0;
|
|
264
|
+
consumedRef.current = events.at(-1)?.sequence ?? consumedRef.current;
|
|
265
|
+
let latestMatch: SessionEvent | undefined;
|
|
266
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
267
|
+
const event = events[index];
|
|
268
|
+
if (event && matchRef.current(event)) {
|
|
269
|
+
latestMatch = event;
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
if (latestMatch) {
|
|
274
|
+
onEventRef.current(latestMatch);
|
|
275
|
+
}
|
|
276
|
+
return;
|
|
252
277
|
}
|
|
278
|
+
sharedFeedHasEventsRef.current ||= events.length > 0;
|
|
253
279
|
for (const event of events) {
|
|
254
280
|
if (event.sequence <= consumedRef.current) {
|
|
255
281
|
continue;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { WorkspaceModelCatalogModel } from "@opengeni/sdk";
|
|
2
2
|
import { useCallback } from "react";
|
|
3
|
+
import { projectPickerRows, sortPickerRows, type PickerModelRow } from "../model-policy";
|
|
3
4
|
import { useOpenGeniClient, type ClientOverride } from "../provider";
|
|
4
5
|
import { usePolledValue } from "./internal";
|
|
5
6
|
|
|
@@ -10,9 +11,22 @@ export type UseAvailableModelsOptions = Pick<ClientOverride, "client"> & {
|
|
|
10
11
|
};
|
|
11
12
|
|
|
12
13
|
export type UseAvailableModelsResult = {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
models: import("@opengeni/sdk").ClientModel[];
|
|
15
|
+
defaultModel: string | null;
|
|
16
|
+
loading: boolean;
|
|
17
|
+
error: Error | null;
|
|
18
|
+
refresh: () => Promise<void>;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type UseWorkspaceModelCatalogOptions = Pick<ClientOverride, "client"> & {
|
|
22
|
+
workspaceId: string | null;
|
|
23
|
+
pollIntervalMs?: number | undefined;
|
|
24
|
+
enabled?: boolean | undefined;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type UseWorkspaceModelCatalogResult = {
|
|
28
|
+
models: WorkspaceModelCatalogModel[];
|
|
29
|
+
rows: PickerModelRow[];
|
|
16
30
|
defaultModel: string | null;
|
|
17
31
|
loading: boolean;
|
|
18
32
|
error: Error | null;
|
|
@@ -42,3 +56,36 @@ export function useAvailableModels(
|
|
|
42
56
|
refresh: state.refresh,
|
|
43
57
|
};
|
|
44
58
|
}
|
|
59
|
+
|
|
60
|
+
/** Workspace-scoped catalog with selectability and billing-class picker rows. */
|
|
61
|
+
export function useWorkspaceModelCatalog(
|
|
62
|
+
options: UseWorkspaceModelCatalogOptions,
|
|
63
|
+
): UseWorkspaceModelCatalogResult {
|
|
64
|
+
const client = useOpenGeniClient(options);
|
|
65
|
+
const load = useCallback(async () => {
|
|
66
|
+
if (!options.workspaceId) {
|
|
67
|
+
return { models: [], defaultModel: null };
|
|
68
|
+
}
|
|
69
|
+
const [catalog, config] = await Promise.all([
|
|
70
|
+
client.getWorkspaceModelCatalog(options.workspaceId),
|
|
71
|
+
client.getClientConfig(),
|
|
72
|
+
]);
|
|
73
|
+
return {
|
|
74
|
+
models: catalog.models,
|
|
75
|
+
defaultModel: config.defaultModel,
|
|
76
|
+
};
|
|
77
|
+
}, [client, options.workspaceId]);
|
|
78
|
+
const state = usePolledValue(load, {
|
|
79
|
+
pollIntervalMs: options.pollIntervalMs,
|
|
80
|
+
enabled: options.enabled !== false && Boolean(options.workspaceId),
|
|
81
|
+
});
|
|
82
|
+
const rows = sortPickerRows(projectPickerRows(state.data?.models ?? []));
|
|
83
|
+
return {
|
|
84
|
+
models: state.data?.models ?? [],
|
|
85
|
+
rows,
|
|
86
|
+
defaultModel: state.data?.defaultModel ?? null,
|
|
87
|
+
loading: state.loading,
|
|
88
|
+
error: state.error,
|
|
89
|
+
refresh: state.refresh,
|
|
90
|
+
};
|
|
91
|
+
}
|