@alexkroman1/aai-ui 0.12.2 → 0.13.0
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 +257 -0
- package/dist/_colors-CpZO-88A.js +78 -0
- package/dist/_module-url-C13kAJ87.js +15 -0
- package/dist/_page-shell.d.ts +11 -0
- package/dist/_recover-run.d.ts +79 -0
- package/dist/_repeat-until.d.ts +30 -0
- package/dist/_run-controls.d.ts +33 -0
- package/dist/_submission-state.d.ts +92 -0
- package/dist/_tap-to-talk-state.d.ts +88 -0
- package/dist/_utils-Mi-SCFA1.js +47 -0
- package/dist/_utils.d.ts +26 -0
- package/dist/_web-storage.d.ts +43 -0
- package/dist/_workflow-api-ref.d.ts +37 -0
- package/dist/_workflow-files.d.ts +34 -0
- package/dist/_workflow-test-defs.d.ts +38 -0
- package/dist/aai-logo-D8pMVn9_.js +85 -0
- package/dist/agent-state-labels.d.ts +65 -0
- package/dist/agent-state.d.ts +108 -0
- package/dist/audio/capture.d.ts +44 -0
- package/dist/audio/index.d.ts +18 -0
- package/dist/audio/index.js +431 -0
- package/dist/audio/preconnect.d.ts +87 -0
- package/dist/audio/voice-io.d.ts +112 -0
- package/dist/build-default-client.d.ts +1 -0
- package/dist/chat-view-BCbI7gD4.js +396 -0
- package/dist/client-config-C7fQrioM.js +139 -0
- package/dist/client-config.d.ts +119 -0
- package/dist/client-dir.d.ts +44 -0
- package/dist/client-dir.js +58 -0
- package/dist/client-identity.d.ts +131 -0
- package/dist/client-tool.d.ts +51 -0
- package/dist/components/_colors.d.ts +73 -0
- package/dist/components/_form-readiness.d.ts +56 -0
- package/dist/components/_form-values.d.ts +19 -0
- package/dist/components/aai-logo.d.ts +12 -0
- package/dist/components/audio-result.d.ts +94 -0
- package/dist/components/auto-scroll.d.ts +74 -0
- package/dist/components/bullet-list.d.ts +74 -0
- package/dist/components/button.d.ts +58 -0
- package/dist/components/button.js +85 -0
- package/dist/components/chat-view.d.ts +35 -0
- package/dist/components/chat-view.js +3 -0
- package/dist/components/console-shell.d.ts +85 -0
- package/dist/components/controls.d.ts +33 -0
- package/dist/components/controls.js +49 -0
- package/dist/components/conversation-view.d.ts +130 -0
- package/dist/components/eyebrow.d.ts +12 -0
- package/dist/components/facts.d.ts +81 -0
- package/dist/components/form-fields.d.ts +142 -0
- package/dist/components/form-types.d.ts +73 -0
- package/dist/components/form.d.ts +96 -0
- package/dist/components/markdown.d.ts +57 -0
- package/dist/components/message-list.d.ts +48 -0
- package/dist/components/message-list.js +3 -0
- package/dist/components/session-controls.d.ts +125 -0
- package/dist/components/session-error-banner.d.ts +69 -0
- package/dist/components/session-state-dot.d.ts +111 -0
- package/dist/components/sidebar-layout.d.ts +42 -0
- package/dist/components/sidebar-layout.js +56 -0
- package/dist/components/start-screen.d.ts +37 -0
- package/dist/components/start-screen.js +71 -0
- package/dist/components/tool-call-block.d.ts +28 -0
- package/dist/components/tool-call-block.js +3 -0
- package/dist/components/tool-call-row.d.ts +75 -0
- package/dist/components/tool-config-context.d.ts +22 -0
- package/dist/components/upload-progress.d.ts +76 -0
- package/dist/components/url-chips.d.ts +29 -0
- package/dist/components/workflow-fields.d.ts +46 -0
- package/dist/components/workflow-pending-note.d.ts +81 -0
- package/dist/components/workflow-progress.d.ts +81 -0
- package/dist/components/workflow-run-error.d.ts +48 -0
- package/dist/components/workflow-run-panel.d.ts +79 -0
- package/dist/context.d.ts +288 -0
- package/dist/context.js +382 -0
- package/dist/conversation-log.d.ts +88 -0
- package/dist/default-client/assets/_module-url-BX0RuRU2.js +1 -0
- package/dist/default-client/assets/audio-cGFGID-2.js +1 -0
- package/dist/default-client/assets/capture-processor-BUABoo3_.js +90 -0
- package/dist/default-client/assets/index-8ihb1Urm.css +2 -0
- package/dist/default-client/assets/index-BQURVARl.js +126 -0
- package/dist/default-client/assets/playback-processor-ABOa11j0.js +314 -0
- package/dist/default-client/favicon.ico +0 -0
- package/dist/default-client/index.html +15 -0
- package/dist/default-client.d.ts +23 -0
- package/dist/define-client.d.ts +162 -26
- package/dist/define-client.js +201 -55
- package/dist/eyebrow-UfmSz9yy.js +27 -0
- package/dist/hooks-DN4gHYsj.js +182 -0
- package/dist/hooks.d.ts +172 -0
- package/dist/hooks.js +3 -0
- package/dist/hooks.test-d.d.ts +14 -0
- package/dist/inbox-protocol.d.ts +128 -0
- package/dist/inbox.d.ts +132 -0
- package/dist/index.d.ts +129 -32
- package/dist/index.js +5503 -18
- package/dist/internal.d.ts +43 -0
- package/dist/internal.js +8 -0
- package/dist/linked-client.d.ts +70 -0
- package/dist/message-list-DHZcLhRA.js +682 -0
- package/dist/notice-player.d.ts +70 -0
- package/dist/page.d.ts +114 -0
- package/dist/phone.d.ts +50 -0
- package/dist/route-fetch.d.ts +79 -0
- package/dist/session/audio-effects.d.ts +31 -0
- package/dist/session/audio-setup.d.ts +57 -0
- package/dist/session/audio-state.d.ts +157 -0
- package/dist/session/browser-session.d.ts +30 -0
- package/dist/session/close.d.ts +26 -0
- package/dist/session/dial.d.ts +65 -0
- package/dist/session/handshake.d.ts +60 -0
- package/dist/session/index.d.ts +17 -0
- package/dist/session/index.js +2 -0
- package/dist/session/messages.d.ts +63 -0
- package/dist/session/mic.d.ts +64 -0
- package/dist/session/preconnect.d.ts +48 -0
- package/dist/session/reconnect.d.ts +25 -0
- package/dist/session/resume-store.d.ts +57 -0
- package/dist/session/state.d.ts +146 -0
- package/dist/session/ticket.d.ts +44 -0
- package/dist/session/types.d.ts +476 -0
- package/dist/session/url.d.ts +17 -0
- package/dist/session/user-turn.d.ts +55 -0
- package/dist/session-CE0Ipsb3.js +2129 -0
- package/dist/stored-value.d.ts +103 -0
- package/dist/tool-call-block-uXtMOUWN.js +204 -0
- package/dist/tool-config-context-Es4YUzV2.js +19 -0
- package/dist/types.d.ts +272 -34
- package/dist/types.js +35 -4
- package/dist/upload/files.d.ts +83 -0
- package/dist/upload/index.d.ts +15 -0
- package/dist/upload/pause.d.ts +31 -0
- package/dist/upload/recall.d.ts +67 -0
- package/dist/upload/report.d.ts +26 -0
- package/dist/upload/session.d.ts +99 -0
- package/dist/url-chips-oyy_Eiqh.js +239 -0
- package/dist/use-client-runs.d.ts +87 -0
- package/dist/use-conversation-log.d.ts +86 -0
- package/dist/use-conversation.d.ts +131 -0
- package/dist/use-copy.d.ts +74 -0
- package/dist/use-download-url.d.ts +84 -0
- package/dist/use-flash.d.ts +61 -0
- package/dist/use-inbox.d.ts +101 -0
- package/dist/use-push-to-talk.d.ts +105 -0
- package/dist/use-route-mutation.d.ts +113 -0
- package/dist/use-route.d.ts +82 -0
- package/dist/use-run-key.d.ts +133 -0
- package/dist/use-session-controls.d.ts +69 -0
- package/dist/use-session-id.d.ts +57 -0
- package/dist/use-tap-to-talk.d.ts +138 -0
- package/dist/use-user-transcript-uyHhzy4d.js +73 -0
- package/dist/use-user-transcript.d.ts +75 -0
- package/dist/use-workflow-form.d.ts +285 -0
- package/dist/use-workflow-progress.d.ts +137 -0
- package/dist/use-workflow-run-DdhWmbx-.js +697 -0
- package/dist/use-workflow-run.d.ts +100 -0
- package/dist/use-workflow-runs.d.ts +71 -0
- package/dist/use-workflow-stream.d.ts +154 -0
- package/dist/use-workflows.d.ts +77 -0
- package/dist/workflow-client.d.ts +136 -0
- package/dist/workflow-def-types.d.ts +31 -0
- package/dist/workflow-events.d.ts +39 -0
- package/dist/workflow-field-kind.d.ts +95 -0
- package/dist/workflow-status-labels.d.ts +35 -0
- package/dist/worklets/_module-url.d.ts +10 -0
- package/dist/worklets/_playback-bench-harness.d.ts +181 -0
- package/dist/worklets/_playback-bench-host.d.ts +63 -0
- package/dist/worklets/_playback-bench-page.d.ts +65 -0
- package/dist/worklets/_tts-trace-harness.d.ts +142 -0
- package/dist/worklets/_worklet-test-utils.d.ts +27 -0
- package/dist/worklets/capture-processor.d.ts +4 -2
- package/dist/worklets/capture-processor.js +73 -33
- package/dist/worklets/playback-processor.d.ts +4 -2
- package/dist/worklets/playback-processor.js +258 -42
- package/package.json +58 -31
- package/styles.css +152 -30
- package/dist/_components/app.d.ts +0 -23
- package/dist/_components/app.js +0 -41
- package/dist/_components/button.d.ts +0 -50
- package/dist/_components/button.js +0 -46
- package/dist/_components/chat-view.d.ts +0 -29
- package/dist/_components/chat-view.js +0 -61
- package/dist/_components/controls.d.ts +0 -18
- package/dist/_components/controls.js +0 -37
- package/dist/_components/error-banner.d.ts +0 -21
- package/dist/_components/error-banner.js +0 -27
- package/dist/_components/message-bubble.d.ts +0 -23
- package/dist/_components/message-bubble.js +0 -35
- package/dist/_components/message-list.d.ts +0 -21
- package/dist/_components/message-list.js +0 -76
- package/dist/_components/sidebar-layout.d.ts +0 -21
- package/dist/_components/sidebar-layout.js +0 -36
- package/dist/_components/start-screen.d.ts +0 -26
- package/dist/_components/start-screen.js +0 -51
- package/dist/_components/state-indicator.d.ts +0 -29
- package/dist/_components/state-indicator.js +0 -37
- package/dist/_components/thinking-indicator.d.ts +0 -16
- package/dist/_components/thinking-indicator.js +0 -34
- package/dist/_components/tool-call-block.d.ts +0 -27
- package/dist/_components/tool-call-block.js +0 -118
- package/dist/_components/tool-icons.d.ts +0 -17
- package/dist/_components/tool-icons.js +0 -128
- package/dist/_components/transcript.d.ts +0 -25
- package/dist/_components/transcript.js +0 -35
- package/dist/audio.d.ts +0 -45
- package/dist/audio.js +0 -121
- package/dist/client-context.d.ts +0 -33
- package/dist/client-context.js +0 -15
- package/dist/client-handler.d.ts +0 -43
- package/dist/session-CWB7vmqz.js +0 -429
- package/dist/session.d.ts +0 -69
- package/dist/session.js +0 -2
- package/dist/signals.d.ts +0 -120
- package/dist/signals.js +0 -161
- package/dist/tsdown.config.d.ts +0 -2
- package/dist/types.test-d.d.ts +0 -7
package/dist/types.js
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { CAPTURE_STOP_ACK_TIMEOUT_MS, CLIENT_AUDIO_LEAD_MS, HEARD_AUDIO_LAG_MS, MIC_BUFFER_SECONDS, MIC_SEND_MAX_BUFFERED_BYTES, MIC_SILENCE_PROBE_MS, PACER_BURST_MS, PIPELINE_PLAYBACK_GRACE_MS, PLAYBACK_BUFFER_SECONDS, PLAYBACK_CONCEAL_FADE_MS, PLAYBACK_CONCEAL_FLOOR, PLAYBACK_DONE_MAX_WAIT_MS, PLAYBACK_DONE_POLL_MS, PLAYBACK_FILL_MS, PLAYBACK_PROGRESS_INTERVAL_MS } from "@alexkroman1/aai/internal";
|
|
2
|
+
//#region src/types.ts
|
|
3
|
+
/**
|
|
4
|
+
* `getUserMedia` audio constraints for every capture path in this package.
|
|
5
|
+
*
|
|
6
|
+
* Defined once because four copies of this object drifted apart trivially, and
|
|
7
|
+
* the flags are not cosmetic — each one rewrites the signal before STT (and
|
|
8
|
+
* before the sync path's energy VAD) ever sees it:
|
|
9
|
+
*
|
|
10
|
+
* - **`autoGainControl: false`** — AGC continuously retargets level, which
|
|
11
|
+
* means riding the noise floor up through silence. An energy VAD calibrated
|
|
12
|
+
* against a moving floor is calibrated against nothing.
|
|
13
|
+
* - **`noiseSuppression: false`** / **`voiceIsolation: false`** — both discard
|
|
14
|
+
* signal to make speech sound cleaner to a human, and both can gate a quiet
|
|
15
|
+
* room to *exact* zeros, which is also what a dead microphone looks like
|
|
16
|
+
* (see `MIC_SILENCE_PROBE_MS`).
|
|
17
|
+
* - **`echoCancellation: true`** — this one stays on. The mic is open while
|
|
18
|
+
* the agent speaks (barge-in needs it), so without AEC the agent hears
|
|
19
|
+
* itself and interrupts its own reply.
|
|
20
|
+
*
|
|
21
|
+
* Cast because `voiceIsolation` is newer than TypeScript's DOM lib.
|
|
22
|
+
*
|
|
23
|
+
* On `@alexkroman1/aai-ui/internal` rather than the root barrel, for the same
|
|
24
|
+
* reason as the audio budgets above: it is a framework decision with no
|
|
25
|
+
* `mountClient()` field to set, and the root is the authoring surface. A custom
|
|
26
|
+
* chrome that bypasses `mountClient()` and opens its own microphone reaches it there
|
|
27
|
+
* alongside the providers it also needs.
|
|
28
|
+
*/
|
|
29
|
+
const VOICE_CAPTURE_CONSTRAINTS = {
|
|
30
|
+
echoCancellation: true,
|
|
31
|
+
noiseSuppression: false,
|
|
32
|
+
autoGainControl: false,
|
|
33
|
+
voiceIsolation: false
|
|
34
|
+
};
|
|
4
35
|
//#endregion
|
|
5
|
-
export { MIC_BUFFER_SECONDS };
|
|
36
|
+
export { CAPTURE_STOP_ACK_TIMEOUT_MS, CLIENT_AUDIO_LEAD_MS, HEARD_AUDIO_LAG_MS, MIC_BUFFER_SECONDS, MIC_SEND_MAX_BUFFERED_BYTES, MIC_SILENCE_PROBE_MS, PACER_BURST_MS, PIPELINE_PLAYBACK_GRACE_MS, PLAYBACK_BUFFER_SECONDS, PLAYBACK_CONCEAL_FADE_MS, PLAYBACK_CONCEAL_FLOOR, PLAYBACK_DONE_MAX_WAIT_MS, PLAYBACK_DONE_POLL_MS, PLAYBACK_FILL_MS, PLAYBACK_PROGRESS_INTERVAL_MS, VOICE_CAPTURE_CONSTRAINTS };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turning a form's `File`s into stored upload ids, pauses and all.
|
|
3
|
+
*
|
|
4
|
+
* Split out of `use-workflow-form.ts` for the 500-line cap, and the seam is a
|
|
5
|
+
* real one: that module is the two HOOKS and the state between them, where this
|
|
6
|
+
* is the walk over a submitted input — which is the only part of it that knows
|
|
7
|
+
* what a `File` is, holds a loop, and survives being re-entered.
|
|
8
|
+
*
|
|
9
|
+
* `_`-internal. `useWorkflowSubmit` is the only caller; `useWorkflowStream` sends
|
|
10
|
+
* one file rather than walking an input and shares only the gate underneath both
|
|
11
|
+
* (`upload/session.ts`).
|
|
12
|
+
*/
|
|
13
|
+
import type { UploadParallelOption } from "@alexkroman1/aai/workflow-api";
|
|
14
|
+
import type { UploadStatus } from "../use-workflow-form.ts";
|
|
15
|
+
import type { WorkflowApi } from "../workflow-client.ts";
|
|
16
|
+
import { type UploadGate } from "./session.ts";
|
|
17
|
+
/**
|
|
18
|
+
* What one submission's uploads know about themselves, across pauses.
|
|
19
|
+
*
|
|
20
|
+
* Held by the hook rather than by the walk below, because the walk RE-RUNS: a
|
|
21
|
+
* pause unwinds nothing, so resuming re-enters `uploadFiles` with the same input
|
|
22
|
+
* and the same session, and every file whose bytes are already in is skipped by
|
|
23
|
+
* `stored` rather than sent again.
|
|
24
|
+
*
|
|
25
|
+
* Which is also why the ids live here. A resumable upload is one whose id
|
|
26
|
+
* outlives the attempt that began it — that is the whole mechanism — so an id
|
|
27
|
+
* minted per attempt would make each round a fresh upload of the whole file.
|
|
28
|
+
*/
|
|
29
|
+
export type UploadSession = {
|
|
30
|
+
/**
|
|
31
|
+
* What this form's remembered ids are filed under (`upload/recall.ts`).
|
|
32
|
+
*
|
|
33
|
+
* The workflow's name, so two forms on one page do not read each other's
|
|
34
|
+
* entries. It is not a SAFETY boundary and does not need to be — a recalled id
|
|
35
|
+
* is checked against the agent before anything is sent to it — it just keeps a
|
|
36
|
+
* form from spending a round trip on another form's upload.
|
|
37
|
+
*/
|
|
38
|
+
scope: string;
|
|
39
|
+
/** The id each file is being stored under, minted once. */
|
|
40
|
+
ids: Map<File, string>;
|
|
41
|
+
/** Files whose every byte has landed, by the id they landed under. */
|
|
42
|
+
stored: Map<File, string>;
|
|
43
|
+
/**
|
|
44
|
+
* Files that have had an attempt, so the next one must claim the id as its own.
|
|
45
|
+
*
|
|
46
|
+
* `sendThroughGate` tracks this itself WITHIN one file's attempts. What this
|
|
47
|
+
* carries is the attempt made by a page load that is gone: an id recalled from
|
|
48
|
+
* storage was claimed by whoever minted it, so the first attempt of this load is
|
|
49
|
+
* a resume even though this load has sent nothing.
|
|
50
|
+
*/
|
|
51
|
+
tried: Set<File>;
|
|
52
|
+
/** The person's pause. */
|
|
53
|
+
gate: UploadGate;
|
|
54
|
+
};
|
|
55
|
+
/** A fresh session for one submission of `workflow`. */
|
|
56
|
+
export declare function createUploadSession(workflow: string): UploadSession;
|
|
57
|
+
/**
|
|
58
|
+
* Replace every `File` in a submitted form with the id of a stored upload,
|
|
59
|
+
* reporting how far each one has got.
|
|
60
|
+
*
|
|
61
|
+
* Sequential rather than `Promise.all`: these are large bodies, and a form with
|
|
62
|
+
* two 200 MB recordings should send them one after another rather than compete
|
|
63
|
+
* for the same connection. That is also what makes a single bar honest — one
|
|
64
|
+
* file is in flight at a time, and `index`/`count` say which.
|
|
65
|
+
*
|
|
66
|
+
* Anything that is not a `File` (or an array of them) passes through untouched,
|
|
67
|
+
* so this is invisible to every form that has none — including one whose values
|
|
68
|
+
* are not an object at all, which `submit` accepts.
|
|
69
|
+
*
|
|
70
|
+
* ## `uploadStream`, not `upload`, and the id is the reason
|
|
71
|
+
*
|
|
72
|
+
* The difference between the two calls is only who mints the id — and that is
|
|
73
|
+
* exactly what decides whether an interrupted upload can be picked up again. An
|
|
74
|
+
* `upload` mints its own at the END and hands it back, so a caller whose upload
|
|
75
|
+
* died has nothing to name what was stored and no choice but to send the file
|
|
76
|
+
* again. A `uploadStream` is told the id up front, so the windows already in the
|
|
77
|
+
* store are addressable, which is what both a pause and a server restart need.
|
|
78
|
+
*
|
|
79
|
+
* Nothing else about the submission changes: the run is still started after the
|
|
80
|
+
* last byte lands, so the incomplete record a streamed upload leaves along the
|
|
81
|
+
* way is one nobody reads.
|
|
82
|
+
*/
|
|
83
|
+
export declare function uploadFiles(api: WorkflowApi, input: unknown, report: (status: UploadStatus) => void, parallel: UploadParallelOption | undefined, session: UploadSession): Promise<unknown>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The workflow hooks' upload machinery, as the rest of the package sees it:
|
|
3
|
+
* claiming an id per file and transferring it (`files.ts`), the pause gate
|
|
4
|
+
* (`session.ts`, `pause.ts`), the reload recall (`recall.ts`) and the report
|
|
5
|
+
* coalescing (`report.ts`). Everything not re-exported here is private to
|
|
6
|
+
* `upload/` — guard-invariants rule 37 fails an import from outside it that
|
|
7
|
+
* names any module but this one.
|
|
8
|
+
*
|
|
9
|
+
* @module
|
|
10
|
+
*/
|
|
11
|
+
export { createUploadSession, type UploadSession, uploadFiles } from "./files.ts";
|
|
12
|
+
export { useUploadPause } from "./pause.ts";
|
|
13
|
+
export { recallUploadId, rememberUploadId } from "./recall.ts";
|
|
14
|
+
export { coalesceUploadReports } from "./report.ts";
|
|
15
|
+
export { createUploadGate, randomUploadId, sendThroughGate, type UploadGate, } from "./session.ts";
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two buttons a page puts over a live upload, bound to whichever gate the
|
|
3
|
+
* submission is currently holding.
|
|
4
|
+
*
|
|
5
|
+
* `useWorkflowSubmit` and `useWorkflowStream` both own an {@link UploadGate} and
|
|
6
|
+
* both reported the pause the same way: park the gate, then fold `paused` into
|
|
7
|
+
* the status the bar is already drawing. Folded rather than replaced, because
|
|
8
|
+
* everything else about that status — which file, how far, of how many — is
|
|
9
|
+
* still true. Two copies of that rule are two copies that can stop agreeing
|
|
10
|
+
* about what a paused bar says.
|
|
11
|
+
*/
|
|
12
|
+
import { type Dispatch, type SetStateAction } from "react";
|
|
13
|
+
import type { UploadStatus } from "../use-workflow-form.ts";
|
|
14
|
+
import type { UploadGate } from "./session.ts";
|
|
15
|
+
/** What {@link useUploadPause} returns — see {@link WorkflowSubmission}. */
|
|
16
|
+
export type UploadPause = {
|
|
17
|
+
/** Stop the bytes in flight and hold the uploader. */
|
|
18
|
+
pauseUpload: () => void;
|
|
19
|
+
/** Send whatever the store is still missing. */
|
|
20
|
+
resumeUpload: () => void;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Bind pause/resume to the live submission's gate.
|
|
24
|
+
*
|
|
25
|
+
* @param getGate - Reads the gate out of the caller's ref, so the callbacks stay
|
|
26
|
+
* stable across submissions rather than being re-created per gate.
|
|
27
|
+
* @param setUpload - The status setter the progress bar renders from.
|
|
28
|
+
*
|
|
29
|
+
* @internal
|
|
30
|
+
*/
|
|
31
|
+
export declare function useUploadPause(getGate: () => UploadGate | undefined, setUpload: Dispatch<SetStateAction<UploadStatus | undefined>>): UploadPause;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where an upload's ID survives a page RELOAD.
|
|
3
|
+
*
|
|
4
|
+
* A streamed upload is resumable because its id outlives the attempt that began
|
|
5
|
+
* it — `upload/files.ts` says so, and `upload/session.ts` turns that into a
|
|
6
|
+
* pause a person can press. Both of them hold the id in MEMORY: the walk's
|
|
7
|
+
* `UploadSession` lives in a `useRef`, so a reload was the one interruption the
|
|
8
|
+
* mechanism could not survive. Everything else was already in place — the windows
|
|
9
|
+
* were still in the store, the agent could still name them
|
|
10
|
+
* (`UploadInfo.ranges`), and the id was minted in the browser — and the browser
|
|
11
|
+
* had thrown away the only name for them. So a person who refreshed at 90% of a
|
|
12
|
+
* 200 MB recording sent the whole file again, which is the one interruption they
|
|
13
|
+
* are most likely to cause on purpose.
|
|
14
|
+
*
|
|
15
|
+
* This is that name, written down. It is what tus-js-client's `urlStorage` and
|
|
16
|
+
* Uppy's Golden Retriever sell, in the shape `session/resume-store.ts` already
|
|
17
|
+
* uses for a session id.
|
|
18
|
+
*
|
|
19
|
+
* ## A FINGERPRINT, because a `File` has no name a page can address
|
|
20
|
+
*
|
|
21
|
+
* A file from a picker carries no path and no handle, so the key is what
|
|
22
|
+
* tus-js-client fingerprints on: size, last-modified, type and name. Two
|
|
23
|
+
* different files agreeing on all four is the case this cannot tell apart — and
|
|
24
|
+
* the reason NOTHING here decides to resume. `upload/files.ts` asks the agent
|
|
25
|
+
* what the id actually holds before sending a byte to it, so a wrong hit costs
|
|
26
|
+
* one `GET` and a fresh id rather than a corrupted upload.
|
|
27
|
+
*
|
|
28
|
+
* ## `sessionStorage`, deliberately
|
|
29
|
+
*
|
|
30
|
+
* The same call `session/resume-store.ts` makes, for a reason that happens to be
|
|
31
|
+
* stronger here: a reload and a same-tab navigation are exactly what this is for,
|
|
32
|
+
* and an id from yesterday names an upload the agent's sweep has very likely
|
|
33
|
+
* already collected. A tab is also the boundary the walk itself has — two tabs
|
|
34
|
+
* uploading the same recording are two submissions.
|
|
35
|
+
*
|
|
36
|
+
* Every access is guarded. Storage throws outright in Safari private mode and
|
|
37
|
+
* under a blocking policy, and an upload that cannot be REMEMBERED must degrade
|
|
38
|
+
* to the upload we would have done anyway rather than failing to start.
|
|
39
|
+
*/
|
|
40
|
+
/**
|
|
41
|
+
* The id this file was last being stored under in this tab, if any.
|
|
42
|
+
*
|
|
43
|
+
* A hit is a CANDIDATE and never a decision — see the module doc.
|
|
44
|
+
*
|
|
45
|
+
* @internal
|
|
46
|
+
*/
|
|
47
|
+
export declare function recallUploadId(scope: string, file: File): string | undefined;
|
|
48
|
+
/**
|
|
49
|
+
* Remember the id this file is being stored under.
|
|
50
|
+
*
|
|
51
|
+
* Called before the first byte leaves rather than after the last one lands: the
|
|
52
|
+
* reload this exists for happens in between, and an id written at the end is an
|
|
53
|
+
* id written for the one case that did not need it.
|
|
54
|
+
*
|
|
55
|
+
* @internal
|
|
56
|
+
*/
|
|
57
|
+
export declare function rememberUploadId(scope: string, file: File, id: string): void;
|
|
58
|
+
/**
|
|
59
|
+
* Forget it: the agent holds nothing resumable under this id.
|
|
60
|
+
*
|
|
61
|
+
* The other half of the agent deciding. Without it a swept upload is re-read on
|
|
62
|
+
* every submission of the same file for the life of the tab, which is a round
|
|
63
|
+
* trip spent learning the same 404.
|
|
64
|
+
*
|
|
65
|
+
* @internal
|
|
66
|
+
*/
|
|
67
|
+
export declare function forgetUploadId(scope: string, file: File): void;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rate-limiting the upload progress report to what a bar can actually show.
|
|
3
|
+
*
|
|
4
|
+
* `onProgress` fires on every `XMLHttpRequest` progress event, and the parts
|
|
5
|
+
* uploader runs `UPLOAD_PART_CONCURRENCY` of those at once — so the raw rate is
|
|
6
|
+
* on the order of a hundred reports a second, sustained for the whole upload,
|
|
7
|
+
* which for the multi-hundred-megabyte files this feature exists for is
|
|
8
|
+
* minutes. Each one is a `setState`, and each re-renders the enclosing `<Form>`
|
|
9
|
+
* subtree: every field `<WorkflowFields>` rendered, the bar, and
|
|
10
|
+
* `<WorkflowProgress>` beside it. Progress events arrive as separate
|
|
11
|
+
* macrotasks, so React's automatic batching does not merge them.
|
|
12
|
+
*
|
|
13
|
+
* The bar has at most ~100 visually distinct states. Dropping a report that
|
|
14
|
+
* would render identically is therefore free, and it is the whole mechanism —
|
|
15
|
+
* no timer, so the LAST report of a burst is never left unrendered waiting for
|
|
16
|
+
* a window to close.
|
|
17
|
+
*/
|
|
18
|
+
import type { UploadStatus } from "../use-workflow-form.ts";
|
|
19
|
+
/**
|
|
20
|
+
* Wrap an `UploadStatus` setter so redundant reports never reach React.
|
|
21
|
+
*
|
|
22
|
+
* Clearing the status (`undefined`) always passes and resets the comparison —
|
|
23
|
+
* it ends one file's bar, and the next file must not be coalesced against the
|
|
24
|
+
* previous one's last frame.
|
|
25
|
+
*/
|
|
26
|
+
export declare function coalesceUploadReports(set: (status: UploadStatus | undefined) => void): (status: UploadStatus | undefined) => void;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Driving ONE resumable upload from a page: its id, and the gate a person can
|
|
3
|
+
* close.
|
|
4
|
+
*
|
|
5
|
+
* The SDK already survives the server going away — a round that fails for a
|
|
6
|
+
* reason that looks like an outage re-enters itself with `resume: true` and sends
|
|
7
|
+
* only the windows that are missing (`aai/sdk/_upload-resume.ts`). This is the
|
|
8
|
+
* same mechanism turned around and handed to the PERSON: pausing is an outage
|
|
9
|
+
* they caused, resuming is the round after it, and the store cannot tell the two
|
|
10
|
+
* apart because there is nothing to tell apart.
|
|
11
|
+
*
|
|
12
|
+
* ## Why a gate rather than a boolean
|
|
13
|
+
*
|
|
14
|
+
* Pausing has to do two things that a `paused` flag cannot: stop the bytes that
|
|
15
|
+
* are already on the wire, and hold the loop that would send the next ones. So it
|
|
16
|
+
* is an `AbortController` and a promise — abort what is in flight, and park the
|
|
17
|
+
* uploader on `settle()` until somebody opens the gate again.
|
|
18
|
+
*
|
|
19
|
+
* The controller is REPLACED on resume rather than reused, because an aborted
|
|
20
|
+
* signal stays aborted; a caller therefore has to read {@link UploadGate.signal}
|
|
21
|
+
* fresh for each attempt rather than capturing it once.
|
|
22
|
+
*
|
|
23
|
+
* ## The loop this is built for
|
|
24
|
+
*
|
|
25
|
+
* ```ts no-check
|
|
26
|
+
* while (!gate.cancelled) {
|
|
27
|
+
* await gate.settle();
|
|
28
|
+
* if (gate.cancelled) break;
|
|
29
|
+
* try {
|
|
30
|
+
* await api.uploadStream(id, file, { signal: gate.signal, resume: tried });
|
|
31
|
+
* break;
|
|
32
|
+
* } catch (err) {
|
|
33
|
+
* // An abort that is not a cancel is a PAUSE: go back and wait.
|
|
34
|
+
* if (!isAbort(err) || gate.cancelled) throw err;
|
|
35
|
+
* }
|
|
36
|
+
* }
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* **The catch keys off the abort, not off `gate.paused`,** and that is the one
|
|
40
|
+
* subtle part. Pausing and immediately resuming — a double-click, or a person
|
|
41
|
+
* changing their mind inside the round trip — resolves the gate before the
|
|
42
|
+
* rejection this abort caused has even landed, so a `paused` check would read
|
|
43
|
+
* `false` and rethrow an `AbortError` as though the upload had failed. Every
|
|
44
|
+
* abort reaching that catch was caused by this gate, so every one of them is a
|
|
45
|
+
* pause unless the gate was cancelled outright.
|
|
46
|
+
*/
|
|
47
|
+
/** A person's pause, as the uploader sees it. */
|
|
48
|
+
export type UploadGate = {
|
|
49
|
+
/** Whether the gate is currently closed. For rendering, not for control flow. */
|
|
50
|
+
readonly paused: boolean;
|
|
51
|
+
/** Whether the upload was abandoned. A cancelled gate never opens again. */
|
|
52
|
+
readonly cancelled: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* The signal for the NEXT attempt. Re-read it per attempt: resuming installs a
|
|
55
|
+
* fresh controller, and the previous one stays aborted forever.
|
|
56
|
+
*/
|
|
57
|
+
readonly signal: AbortSignal;
|
|
58
|
+
/** Stop the bytes in flight and hold the uploader. A no-op when already closed. */
|
|
59
|
+
pause: () => void;
|
|
60
|
+
/** Open the gate and install a fresh signal. A no-op when not paused. */
|
|
61
|
+
resume: () => void;
|
|
62
|
+
/**
|
|
63
|
+
* Abandon the upload for good.
|
|
64
|
+
*
|
|
65
|
+
* Distinct from a pause in exactly one way that matters to the loop above: it
|
|
66
|
+
* releases the gate rather than holding it, so an uploader parked on `settle()`
|
|
67
|
+
* wakes up and leaves instead of waiting for a resume that is not coming. What
|
|
68
|
+
* a caller does with a cancelled upload's stored windows is its own business —
|
|
69
|
+
* they stay in the store, addressable by an id only the caller has.
|
|
70
|
+
*/
|
|
71
|
+
cancel: () => void;
|
|
72
|
+
/** Resolves as soon as the gate is open — immediately, when it already is. */
|
|
73
|
+
settle: () => Promise<void>;
|
|
74
|
+
};
|
|
75
|
+
/** Whether this rejection is an abort, in either of the two shapes runtimes throw. */
|
|
76
|
+
export declare function isAbortError(err: unknown): boolean;
|
|
77
|
+
/** A fresh upload id: a capability, so it is random rather than derived. */
|
|
78
|
+
export declare function randomUploadId(): string;
|
|
79
|
+
/**
|
|
80
|
+
* Send one file, waiting out however many pauses the person takes.
|
|
81
|
+
*
|
|
82
|
+
* The loop from the module doc, written once: both hooks need exactly this and a
|
|
83
|
+
* second copy of it is a second place for the abort/pause distinction to be got
|
|
84
|
+
* wrong. `send` is handed whether this attempt must CLAIM the id as its own —
|
|
85
|
+
* false the first time, since a fresh id has nothing to resume and saying
|
|
86
|
+
* otherwise waives the refusal that makes a caller-chosen id safe.
|
|
87
|
+
*
|
|
88
|
+
* Throws whatever `send` threw, except an abort the gate caused. A cancelled gate
|
|
89
|
+
* throws too: the caller distinguishes it by reading `gate.cancelled`, which is
|
|
90
|
+
* how an abandoned submission unwinds without being reported as a failure.
|
|
91
|
+
*/
|
|
92
|
+
export declare function sendThroughGate(gate: UploadGate, send: (resume: boolean) => Promise<void>): Promise<void>;
|
|
93
|
+
/**
|
|
94
|
+
* A gate, open.
|
|
95
|
+
*
|
|
96
|
+
* One per upload rather than one per hook: the id and the windows already stored
|
|
97
|
+
* belong to a file, so a gate that outlived its file would resume something else.
|
|
98
|
+
*/
|
|
99
|
+
export declare function createUploadGate(): UploadGate;
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { n as pageBaseUrl } from "./_utils-Mi-SCFA1.js";
|
|
2
|
+
import { useSessionSelector, useTheme } from "./context.js";
|
|
3
|
+
import { a as inkTint, i as focusRingStyle, n as FOCUS_RING } from "./_colors-CpZO-88A.js";
|
|
4
|
+
import { useEffect, useRef, useState } from "react";
|
|
5
|
+
import clsx from "clsx";
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
//#region src/use-flash.ts
|
|
8
|
+
/**
|
|
9
|
+
* A value that shows itself for a moment and then goes away.
|
|
10
|
+
*
|
|
11
|
+
* The "Copied" on a copy button, the "Saved" under an editor, the "Sent" on a
|
|
12
|
+
* submit. There were three hand-rolled copies of this — two in the studio
|
|
13
|
+
* front-end and one inside this package's own URL chips — each with the same
|
|
14
|
+
* 1500ms and the same comment explaining the same two things the naive version
|
|
15
|
+
* gets wrong. It is one primitive now, and it is PUBLIC because a custom chrome
|
|
16
|
+
* is exactly the caller that needs it: the moment a client renders its own copy
|
|
17
|
+
* button or its own save note, it is writing the timer again.
|
|
18
|
+
*/
|
|
19
|
+
/** How long a flashed value stays up. The one duration every caller used. */
|
|
20
|
+
const FLASH_MS = 1500;
|
|
21
|
+
/**
|
|
22
|
+
* A transient value: set it, and it clears itself after `ms`.
|
|
23
|
+
*
|
|
24
|
+
* @remarks
|
|
25
|
+
* The two things this holds that a `useState` plus a `setTimeout` at the call
|
|
26
|
+
* site reliably gets wrong:
|
|
27
|
+
*
|
|
28
|
+
* - **At most ONE live timer.** A second flash while the first is still up
|
|
29
|
+
* re-arms rather than stacking, so the new value gets its full window
|
|
30
|
+
* instead of being cleared early by the previous click's timeout.
|
|
31
|
+
* - **Nothing fires after unmount.** The timer is cleared on teardown, so a
|
|
32
|
+
* chip clicked and then scrolled out of the tree does not `setState` on a
|
|
33
|
+
* component React has already thrown away.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```tsx
|
|
37
|
+
* import { useFlash } from "@alexkroman1/aai-ui";
|
|
38
|
+
*
|
|
39
|
+
* function SaveNote({ onSave }: { onSave: () => Promise<void> }) {
|
|
40
|
+
* const { value: note, flash } = useFlash<string>();
|
|
41
|
+
* return (
|
|
42
|
+
* <button type="button" onClick={() => void onSave().then(() => flash("Saved"))}>
|
|
43
|
+
* {note ?? "Save"}
|
|
44
|
+
* </button>
|
|
45
|
+
* );
|
|
46
|
+
* }
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* @typeParam T - What is being flashed.
|
|
50
|
+
* @param ms - How long the value stays up. Defaults to 1500ms, which is what
|
|
51
|
+
* every caller wanted: long enough to read a word, short enough that the
|
|
52
|
+
* control is back to its real label before the reader looks again.
|
|
53
|
+
*
|
|
54
|
+
* @public
|
|
55
|
+
*/
|
|
56
|
+
function useFlash(ms = FLASH_MS) {
|
|
57
|
+
const [value, setValue] = useState(null);
|
|
58
|
+
const timer = useRef(void 0);
|
|
59
|
+
useEffect(() => () => clearTimeout(timer.current), []);
|
|
60
|
+
const flash = (next) => {
|
|
61
|
+
setValue(next);
|
|
62
|
+
clearTimeout(timer.current);
|
|
63
|
+
timer.current = setTimeout(() => setValue(null), ms);
|
|
64
|
+
};
|
|
65
|
+
return {
|
|
66
|
+
value,
|
|
67
|
+
flash
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
//#endregion
|
|
71
|
+
//#region src/use-copy.ts
|
|
72
|
+
/**
|
|
73
|
+
* Copy-to-clipboard with a per-item flash on the button that triggered it.
|
|
74
|
+
*
|
|
75
|
+
* Anywhere a UI hands the reader a string to paste somewhere else — a session
|
|
76
|
+
* URL, a webhook, a `curl` line — the button wants the same three things, and
|
|
77
|
+
* two of them are easy to get wrong:
|
|
78
|
+
*
|
|
79
|
+
* - The flash is keyed by the copied TEXT, so one row's "Copied" does not light
|
|
80
|
+
* up every button beside it.
|
|
81
|
+
* - A FAILED write is reported rather than swallowed. There is no clipboard in
|
|
82
|
+
* an insecure context, and a browser may refuse the permission; a `catch`
|
|
83
|
+
* that does nothing leaves a button that visibly does nothing when clicked,
|
|
84
|
+
* which reads as a broken page rather than as a blocked capability.
|
|
85
|
+
* - The single-timer / clear-on-unmount half is {@link useFlash}'s.
|
|
86
|
+
*/
|
|
87
|
+
/**
|
|
88
|
+
* One copier for a group of copy buttons.
|
|
89
|
+
*
|
|
90
|
+
* @remarks
|
|
91
|
+
* Call it ONCE per group and pass the {@link UseCopyResult} down, rather than once per
|
|
92
|
+
* button: the flash is shared, so clicking a second row clears the first row's
|
|
93
|
+
* "Copied" — which is what makes a list of URLs readable, since two rows
|
|
94
|
+
* claiming to be on the clipboard is a lie about one of them.
|
|
95
|
+
*
|
|
96
|
+
* A chip whose idle text is its own name rather than the word "Copy" passes
|
|
97
|
+
* that name to {@link UseCopyResult.label}; the two outcome words are fixed, for the
|
|
98
|
+
* reason that member's own doc gives.
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```tsx
|
|
102
|
+
* import { useCopy } from "@alexkroman1/aai-ui";
|
|
103
|
+
*
|
|
104
|
+
* function UrlList({ urls }: { urls: readonly string[] }) {
|
|
105
|
+
* const copier = useCopy();
|
|
106
|
+
* return (
|
|
107
|
+
* <ul>
|
|
108
|
+
* {urls.map((url) => (
|
|
109
|
+
* <li key={url}>
|
|
110
|
+
* <code>{url}</code>
|
|
111
|
+
* <button type="button" onClick={() => copier.copy(url)}>
|
|
112
|
+
* {copier.label(url)}
|
|
113
|
+
* </button>
|
|
114
|
+
* </li>
|
|
115
|
+
* ))}
|
|
116
|
+
* </ul>
|
|
117
|
+
* );
|
|
118
|
+
* }
|
|
119
|
+
* ```
|
|
120
|
+
*
|
|
121
|
+
* @public
|
|
122
|
+
*/
|
|
123
|
+
function useCopy() {
|
|
124
|
+
const { value: copied, flash } = useFlash();
|
|
125
|
+
const copy = (text) => {
|
|
126
|
+
const write = navigator.clipboard?.writeText(text);
|
|
127
|
+
if (write === void 0) {
|
|
128
|
+
flash({
|
|
129
|
+
text,
|
|
130
|
+
ok: false
|
|
131
|
+
});
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
write.then(() => flash({
|
|
135
|
+
text,
|
|
136
|
+
ok: true
|
|
137
|
+
}), () => flash({
|
|
138
|
+
text,
|
|
139
|
+
ok: false
|
|
140
|
+
}));
|
|
141
|
+
};
|
|
142
|
+
const label = (text, idle = "Copy") => {
|
|
143
|
+
if (copied?.text !== text) return idle;
|
|
144
|
+
return copied.ok ? "Copied" : "Failed";
|
|
145
|
+
};
|
|
146
|
+
return {
|
|
147
|
+
copy,
|
|
148
|
+
label,
|
|
149
|
+
didCopy: (text) => copied?.text === text && copied.ok
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
//#endregion
|
|
153
|
+
//#region src/components/url-chips.tsx
|
|
154
|
+
/** @jsxImportSource react */
|
|
155
|
+
/**
|
|
156
|
+
* A compact labeled chip showing a URL. Click to copy.
|
|
157
|
+
*
|
|
158
|
+
* The label is what makes a pair of these readable — on its own a bare URL
|
|
159
|
+
* leaves you guessing whether it's the page or the socket. It also carries the
|
|
160
|
+
* copy OUTCOME, which is the whole reason the flash is `useCopy`'s rather than
|
|
161
|
+
* this file's: the hand-rolled version swallowed a rejected write, so on an
|
|
162
|
+
* insecure context or a denied permission the chip changed nothing at all and
|
|
163
|
+
* read as a dead button.
|
|
164
|
+
*
|
|
165
|
+
* @internal
|
|
166
|
+
*/
|
|
167
|
+
function UrlChip({ label, url, hint, testId, className }) {
|
|
168
|
+
const theme = useTheme();
|
|
169
|
+
const copier = useCopy();
|
|
170
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
171
|
+
type: "button",
|
|
172
|
+
onClick: () => copier.copy(url),
|
|
173
|
+
title: `${hint} (click to copy)\n${url}`,
|
|
174
|
+
className: clsx("flex items-center gap-1.5 min-w-0 appearance-none m-0 px-2 py-1 rounded-aai border cursor-pointer text-[11px] leading-none font-aai-mono", FOCUS_RING, className),
|
|
175
|
+
style: {
|
|
176
|
+
background: inkTint(theme.text, theme.surface, 3),
|
|
177
|
+
borderColor: theme.border,
|
|
178
|
+
color: inkTint(theme.text, theme.surface, 65),
|
|
179
|
+
...focusRingStyle(theme.primary)
|
|
180
|
+
},
|
|
181
|
+
"data-testid": testId,
|
|
182
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
183
|
+
className: "uppercase tracking-wide shrink-0",
|
|
184
|
+
style: { color: inkTint(theme.text, theme.surface, 75) },
|
|
185
|
+
children: copier.label(url, label)
|
|
186
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
187
|
+
className: "truncate",
|
|
188
|
+
"data-testid": `${testId}-url`,
|
|
189
|
+
children: url
|
|
190
|
+
})]
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* The session's shareable UI URL — the page this UI is served from, what
|
|
195
|
+
* you'd send someone to talk to the agent.
|
|
196
|
+
*
|
|
197
|
+
* @internal
|
|
198
|
+
*/
|
|
199
|
+
function UiUrlChip({ className }) {
|
|
200
|
+
return /* @__PURE__ */ jsx(UrlChip, {
|
|
201
|
+
label: "UI",
|
|
202
|
+
url: pageBaseUrl(),
|
|
203
|
+
hint: "Shareable UI",
|
|
204
|
+
testId: "ui-url-chip",
|
|
205
|
+
className
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* The session's programmatic WebSocket endpoint — the URL a script or backend
|
|
210
|
+
* can connect to directly instead of using this UI.
|
|
211
|
+
*
|
|
212
|
+
* @internal
|
|
213
|
+
*/
|
|
214
|
+
function ApiUrlChip({ className }) {
|
|
215
|
+
const apiUrl = useSessionSelector((s) => s.apiUrl);
|
|
216
|
+
return /* @__PURE__ */ jsx(UrlChip, {
|
|
217
|
+
label: "API",
|
|
218
|
+
url: apiUrl,
|
|
219
|
+
hint: "API endpoint",
|
|
220
|
+
testId: "api-url-chip",
|
|
221
|
+
className
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* The UI and API URLs side by side. They answer the same question — "how do I
|
|
226
|
+
* reach this agent?" — so they belong together and each needs its label to be
|
|
227
|
+
* told apart. Rendered by the default shell in every session mode (S2S,
|
|
228
|
+
* pipeline).
|
|
229
|
+
*
|
|
230
|
+
* @internal
|
|
231
|
+
*/
|
|
232
|
+
function SessionUrlChips({ className }) {
|
|
233
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
234
|
+
className: clsx("flex items-center gap-1.5 min-w-0", className),
|
|
235
|
+
children: [/* @__PURE__ */ jsx(UiUrlChip, { className: "min-w-0 flex-1" }), /* @__PURE__ */ jsx(ApiUrlChip, { className: "min-w-0 flex-1" })]
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
//#endregion
|
|
239
|
+
export { useFlash as a, useCopy as i, SessionUrlChips as n, UiUrlChip as r, ApiUrlChip as t };
|