@alexkroman1/aai-ui 6.5.0 → 6.6.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/dist/{_utils-CsqbIVGI.js → _utils-B6498_bm.js} +13 -1
- package/dist/_utils.d.ts +10 -0
- package/dist/_workflow-files.d.ts +16 -0
- package/dist/audio.js +15 -7
- package/dist/{chat-view-CK61bWWx.js → chat-view-ByQFf94G.js} +2 -2
- package/dist/components/button.js +30 -25
- package/dist/components/chat-view.js +1 -1
- package/dist/components/controls.js +1 -1
- package/dist/components/message-list.js +1 -1
- package/dist/components/tool-call-block.js +1 -1
- package/dist/{controls-Cy_YVfsa.js → controls-CjG91QJ4.js} +1 -1
- package/dist/default-client/assets/audio-CoVe8N48.js +1 -0
- package/dist/default-client/assets/index-CepvxbNU.js +293 -0
- package/dist/default-client/index.html +1 -1
- package/dist/define-client.js +5 -5
- package/dist/hooks.js +4 -3
- package/dist/index.js +93 -34
- package/dist/{message-list-BwA3rdPi.js → message-list-CpPV7dGx.js} +1 -1
- package/dist/{session-core-ClKdVgRU.js → session-core-CAfYmUbg.js} +33 -23
- package/dist/session-core-reconnect.d.ts +6 -6
- package/dist/session-core.js +1 -1
- package/dist/{tool-call-block-CrLN7xlI.js → tool-call-block-D6pTEPrT.js} +5 -4
- package/dist/use-workflow-form.d.ts +8 -7
- package/dist/use-workflow-stream.d.ts +11 -7
- package/dist/worklets/capture-processor.js +3 -4
- package/dist/worklets/playback-processor.js +3 -4
- package/package.json +2 -2
- package/dist/default-client/assets/audio-fO7SVU64.js +0 -1
- package/dist/default-client/assets/index-aXDcAQ6M.js +0 -293
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
<title>aai</title>
|
|
7
7
|
<link rel="icon" href="./favicon.ico" />
|
|
8
8
|
<style>html, body { background: #FBF8F2; margin: 0; }</style>
|
|
9
|
-
<script type="module" crossorigin src="./assets/index-
|
|
9
|
+
<script type="module" crossorigin src="./assets/index-CepvxbNU.js"></script>
|
|
10
10
|
<link rel="modulepreload" crossorigin href="./assets/client-audio-constants-Ck0IJO4c.js">
|
|
11
11
|
<link rel="stylesheet" crossorigin href="./assets/index-BZlePk3y.css">
|
|
12
12
|
</head>
|
package/dist/define-client.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { r as fetchClientConfig, t as createSessionCore } from "./session-core-
|
|
1
|
+
import { r as fetchClientConfig, t as createSessionCore } from "./session-core-CAfYmUbg.js";
|
|
2
2
|
import { SessionProvider, ThemeProvider } from "./context.js";
|
|
3
|
-
import { t as ChatView } from "./chat-view-
|
|
4
|
-
import { t as pageBaseUrl } from "./_utils-
|
|
5
|
-
import { n as ToolConfigContext } from "./tool-call-block-
|
|
3
|
+
import { t as ChatView } from "./chat-view-ByQFf94G.js";
|
|
4
|
+
import { n as setPageTitle, t as pageBaseUrl } from "./_utils-B6498_bm.js";
|
|
5
|
+
import { n as ToolConfigContext } from "./tool-call-block-D6pTEPrT.js";
|
|
6
6
|
import { SidebarLayout } from "./components/sidebar-layout.js";
|
|
7
7
|
import { StartScreen } from "./components/start-screen.js";
|
|
8
8
|
import { jsx } from "react/jsx-runtime";
|
|
@@ -165,7 +165,7 @@ function client(config) {
|
|
|
165
165
|
resumeSessionId: config.resumeSessionId,
|
|
166
166
|
WebSocket: config.WebSocket
|
|
167
167
|
});
|
|
168
|
-
|
|
168
|
+
setPageTitle(config.name);
|
|
169
169
|
const rootNode = config.component ? createElement(config.component) : createElement(DefaultRoot, {
|
|
170
170
|
platformUrl,
|
|
171
171
|
name: config.name,
|
package/dist/hooks.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useSessionSelector } from "./context.js";
|
|
2
|
-
import {
|
|
2
|
+
import { i as tryParseJSON } from "./_utils-B6498_bm.js";
|
|
3
3
|
import { useEffect, useRef } from "react";
|
|
4
4
|
//#region hooks.ts
|
|
5
5
|
/** Index of the first item whose sequence number is above the watermark (tail scan from the end). */
|
|
@@ -44,8 +44,9 @@ function processToolCallTail(toolCalls, cursor, isSettled, onNew) {
|
|
|
44
44
|
* and invokes `fire` once per tool call reaching `status`.
|
|
45
45
|
*/
|
|
46
46
|
function useToolCallEffect(status, args, fire) {
|
|
47
|
-
const
|
|
48
|
-
const
|
|
47
|
+
const first = args[0];
|
|
48
|
+
const filterName = typeof first === "string" ? first : null;
|
|
49
|
+
const callback = filterName === null ? first : args[1];
|
|
49
50
|
const toolCalls = useSessionSelector((s) => s.toolCalls);
|
|
50
51
|
const cursorRef = useRef({
|
|
51
52
|
seq: 0,
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { i as loadClientConfig, n as buildAgentUrl, r as fetchClientConfig, t as createSessionCore } from "./session-core-
|
|
2
|
-
import { n as Markdown, r as AutoScroll, t as MessageList } from "./message-list-
|
|
1
|
+
import { i as loadClientConfig, n as buildAgentUrl, r as fetchClientConfig, t as createSessionCore } from "./session-core-CAfYmUbg.js";
|
|
2
|
+
import { n as Markdown, r as AutoScroll, t as MessageList } from "./message-list-CpPV7dGx.js";
|
|
3
3
|
import { SessionProvider, ThemeProvider, useSession, useSessionSelector, useTheme } from "./context.js";
|
|
4
4
|
import { r as inkTint } from "./_colors-CcAi2FOU.js";
|
|
5
5
|
import { Button } from "./components/button.js";
|
|
6
|
-
import { t as ChatView } from "./chat-view-
|
|
7
|
-
import { t as pageBaseUrl } from "./_utils-
|
|
8
|
-
import { i as UiUrlChip, n as ApiUrlChip, r as SessionUrlChips, t as Controls } from "./controls-
|
|
9
|
-
import { n as ToolConfigContext, r as ToolCallRow } from "./tool-call-block-
|
|
6
|
+
import { t as ChatView } from "./chat-view-ByQFf94G.js";
|
|
7
|
+
import { n as setPageTitle, t as pageBaseUrl } from "./_utils-B6498_bm.js";
|
|
8
|
+
import { i as UiUrlChip, n as ApiUrlChip, r as SessionUrlChips, t as Controls } from "./controls-CjG91QJ4.js";
|
|
9
|
+
import { n as ToolConfigContext, r as ToolCallRow } from "./tool-call-block-D6pTEPrT.js";
|
|
10
10
|
import { SidebarLayout } from "./components/sidebar-layout.js";
|
|
11
11
|
import { StartScreen } from "./components/start-screen.js";
|
|
12
12
|
import { VOICE_CAPTURE_CONSTRAINTS } from "./types.js";
|
|
@@ -526,6 +526,7 @@ function UploadProgressBar({ upload, className }) {
|
|
|
526
526
|
if (!upload) return null;
|
|
527
527
|
const { name, index, count, loaded, total, fraction } = upload;
|
|
528
528
|
const percent = fraction === void 0 ? void 0 : Math.round(fraction * 100);
|
|
529
|
+
const faint = inkTint(theme.text, theme.surface, 65);
|
|
529
530
|
return /* @__PURE__ */ jsxs("div", {
|
|
530
531
|
className: clsx(className ?? "flex flex-col gap-1.5"),
|
|
531
532
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
@@ -533,11 +534,11 @@ function UploadProgressBar({ upload, className }) {
|
|
|
533
534
|
children: [/* @__PURE__ */ jsx("span", {
|
|
534
535
|
id: labelId,
|
|
535
536
|
className: "truncate",
|
|
536
|
-
style: { color:
|
|
537
|
+
style: { color: faint },
|
|
537
538
|
children: count > 1 ? `Uploading ${name} (${index} of ${count})` : `Uploading ${name}`
|
|
538
539
|
}), /* @__PURE__ */ jsx("span", {
|
|
539
540
|
className: "shrink-0 tabular-nums",
|
|
540
|
-
style: { color:
|
|
541
|
+
style: { color: faint },
|
|
541
542
|
children: total === void 0 ? formatBytes(loaded) : `${formatBytes(loaded)} of ${formatBytes(total)}`
|
|
542
543
|
})]
|
|
543
544
|
}), /* @__PURE__ */ jsx("div", {
|
|
@@ -649,6 +650,25 @@ function filesOf(value) {
|
|
|
649
650
|
const files = value.filter((one) => one instanceof File);
|
|
650
651
|
return files.length > 0 && files.length === value.length ? files : [];
|
|
651
652
|
}
|
|
653
|
+
/**
|
|
654
|
+
* The input properties still carrying a `File` — i.e. the ones that CANNOT survive
|
|
655
|
+
* being sent.
|
|
656
|
+
*
|
|
657
|
+
* A run input is JSON, and `JSON.stringify(new File(…))` is `{}` — no `toJSON`, no
|
|
658
|
+
* own enumerable properties. So a File left in a payload does not fail to send: it
|
|
659
|
+
* arrives as an empty object, and the workflow rejects it against whatever its own
|
|
660
|
+
* schema says the property should be. Measured in production as
|
|
661
|
+
* `Invalid input for workflow "transcribe": recording: Invalid input` — a message
|
|
662
|
+
* about a type, on a form where the user had picked a perfectly good file.
|
|
663
|
+
*
|
|
664
|
+
* Exported beside {@link filesOf} because it is the same question asked at the
|
|
665
|
+
* other end: that one decides which fields to UPLOAD, this one checks that none
|
|
666
|
+
* were missed. Both hooks are the callers.
|
|
667
|
+
*/
|
|
668
|
+
function fileFields(input) {
|
|
669
|
+
if (!isRecord(input)) return [];
|
|
670
|
+
return Object.entries(input).filter(([, value]) => filesOf(value).length > 0).map(([key]) => key);
|
|
671
|
+
}
|
|
652
672
|
//#endregion
|
|
653
673
|
//#region _repeat-until.ts
|
|
654
674
|
/**
|
|
@@ -1137,10 +1157,10 @@ function useWorkflowSubmit(workflow, opts = {}) {
|
|
|
1137
1157
|
const [startError, setStartError] = useState(void 0);
|
|
1138
1158
|
const [upload, setUpload] = useState(void 0);
|
|
1139
1159
|
const getClient = useWorkflowApiRef(api);
|
|
1140
|
-
const tracked = useWorkflowRun(runId, {
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
});
|
|
1160
|
+
const tracked = useWorkflowRun(runId, omitUndefined({
|
|
1161
|
+
api,
|
|
1162
|
+
intervalMs
|
|
1163
|
+
}));
|
|
1144
1164
|
return {
|
|
1145
1165
|
submit: useCallback(async (input) => {
|
|
1146
1166
|
const client = getClient();
|
|
@@ -1247,7 +1267,8 @@ function SchemaField({ name, property, required, upload = false }) {
|
|
|
1247
1267
|
...hint,
|
|
1248
1268
|
...defaults
|
|
1249
1269
|
});
|
|
1250
|
-
|
|
1270
|
+
const type = typeOf(property);
|
|
1271
|
+
switch (type) {
|
|
1251
1272
|
case "boolean": return /* @__PURE__ */ jsx(CheckboxField, {
|
|
1252
1273
|
name,
|
|
1253
1274
|
label,
|
|
@@ -1259,7 +1280,7 @@ function SchemaField({ name, property, required, upload = false }) {
|
|
|
1259
1280
|
name,
|
|
1260
1281
|
label,
|
|
1261
1282
|
required,
|
|
1262
|
-
step:
|
|
1283
|
+
step: type === "integer" ? 1 : "any",
|
|
1263
1284
|
...hint,
|
|
1264
1285
|
...defaults
|
|
1265
1286
|
});
|
|
@@ -1546,7 +1567,7 @@ function useWorkflowProgress(runId, opts = {}) {
|
|
|
1546
1567
|
* @public
|
|
1547
1568
|
*/
|
|
1548
1569
|
function WorkflowProgress({ runId, api, className, placeholder }) {
|
|
1549
|
-
const { progress, streaming, supported } = useWorkflowProgress(runId,
|
|
1570
|
+
const { progress, streaming, supported } = useWorkflowProgress(runId, omitUndefined({ api }));
|
|
1550
1571
|
if (!supported || progress.length === 0) return placeholder ?? null;
|
|
1551
1572
|
return /* @__PURE__ */ jsxs("pre", {
|
|
1552
1573
|
className: clsx(className ?? "whitespace-pre-wrap border-l pl-4 text-xs opacity-70"),
|
|
@@ -1610,7 +1631,7 @@ function WorkflowProgress({ runId, api, className, placeholder }) {
|
|
|
1610
1631
|
*/
|
|
1611
1632
|
function page(config) {
|
|
1612
1633
|
const container = resolveContainer(config.target);
|
|
1613
|
-
|
|
1634
|
+
setPageTitle(config.name);
|
|
1614
1635
|
return mountRoot(container, createElement(ThemeProvider, { value: config.theme }, createElement(config.component)));
|
|
1615
1636
|
}
|
|
1616
1637
|
//#endregion
|
|
@@ -1828,14 +1849,18 @@ function useWorkflowRuns(workflow, opts = {}) {
|
|
|
1828
1849
|
* - **Reporting the bytes.** The same `UploadStatus` `useWorkflowSubmit` reports,
|
|
1829
1850
|
* so `<UploadProgressBar>` renders either without knowing which hook it came from.
|
|
1830
1851
|
*
|
|
1831
|
-
* ## A failed upload
|
|
1852
|
+
* ## A failed upload is RESUMED once, and only then cancels the run
|
|
1832
1853
|
*
|
|
1833
1854
|
* An upload that dies stays in the store, incomplete, and `complete` never becomes
|
|
1834
1855
|
* true — so a run left behind polls until its own abandonment bound and then fails,
|
|
1835
|
-
* minutes after the page has already reported the error.
|
|
1836
|
-
*
|
|
1837
|
-
*
|
|
1838
|
-
*
|
|
1856
|
+
* minutes after the page has already reported the error.
|
|
1857
|
+
*
|
|
1858
|
+
* That used to be the whole story, and it threw away a run and a file together for
|
|
1859
|
+
* what is usually one dropped connection near the end. So a failure gets one more
|
|
1860
|
+
* attempt with `resume: true`, which sends only the windows the store does not
|
|
1861
|
+
* already have (`UploadInfo.ranges`) — the run is still waiting on the same id, so
|
|
1862
|
+
* a resume that succeeds is invisible to it. Cancelling is what happens when THAT
|
|
1863
|
+
* fails too, and it is the honest end to a submission that did not happen.
|
|
1839
1864
|
*/
|
|
1840
1865
|
/**
|
|
1841
1866
|
* Start a workflow run and stream a file into it while it works.
|
|
@@ -1869,10 +1894,10 @@ function useWorkflowStream(workflow, opts = {}) {
|
|
|
1869
1894
|
const [startError, setStartError] = useState(void 0);
|
|
1870
1895
|
const [upload, setUpload] = useState(void 0);
|
|
1871
1896
|
const getClient = useWorkflowApiRef(api);
|
|
1872
|
-
const tracked = useWorkflowRun(runId, {
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
});
|
|
1897
|
+
const tracked = useWorkflowRun(runId, omitUndefined({
|
|
1898
|
+
api,
|
|
1899
|
+
intervalMs
|
|
1900
|
+
}));
|
|
1876
1901
|
return {
|
|
1877
1902
|
submit: useCallback(async (input) => {
|
|
1878
1903
|
const client = getClient();
|
|
@@ -1888,19 +1913,26 @@ function useWorkflowStream(workflow, opts = {}) {
|
|
|
1888
1913
|
...input,
|
|
1889
1914
|
[field]: id
|
|
1890
1915
|
} : input;
|
|
1916
|
+
assertSendable(workflow, payload, field);
|
|
1891
1917
|
started = await client.start(workflow, payload, omitUndefined({ key }));
|
|
1892
1918
|
setRunId(started);
|
|
1893
1919
|
if (!chosen) return;
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
onProgress: (progress) => setUpload({
|
|
1897
|
-
...progress,
|
|
1920
|
+
const send = async (resume) => {
|
|
1921
|
+
await client.uploadStream(id, chosen, {
|
|
1898
1922
|
name: chosen.name,
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1923
|
+
onProgress: (progress) => setUpload({
|
|
1924
|
+
...progress,
|
|
1925
|
+
name: chosen.name,
|
|
1926
|
+
index: 1,
|
|
1927
|
+
count: 1
|
|
1928
|
+
}),
|
|
1929
|
+
...omitUndefined({
|
|
1930
|
+
parallel,
|
|
1931
|
+
resume: resume ? true : void 0
|
|
1932
|
+
})
|
|
1933
|
+
});
|
|
1934
|
+
};
|
|
1935
|
+
await send(false).catch(async () => await send(true));
|
|
1904
1936
|
await client.wake(started).catch(() => void 0);
|
|
1905
1937
|
} catch (err) {
|
|
1906
1938
|
setStartError(errorMessage(err));
|
|
@@ -1926,6 +1958,33 @@ function useWorkflowStream(workflow, opts = {}) {
|
|
|
1926
1958
|
error: startError ?? tracked.error
|
|
1927
1959
|
};
|
|
1928
1960
|
}
|
|
1961
|
+
/**
|
|
1962
|
+
* Refuse a payload carrying a `File`, before a run is started over it.
|
|
1963
|
+
*
|
|
1964
|
+
* A File cannot be SENT: a run input is JSON and `JSON.stringify(new File(…))` is
|
|
1965
|
+
* `{}` (see `fileFields`). So without this the run starts, rejects its own input,
|
|
1966
|
+
* and reports a type error about a property the user filled in correctly — which is
|
|
1967
|
+
* what production did, five times: `Invalid input for workflow "transcribe":
|
|
1968
|
+
* recording: Invalid input`, from a page whose file picker was working.
|
|
1969
|
+
*
|
|
1970
|
+
* The reachable cause is a workflow whose listing declares no `uploads` for the
|
|
1971
|
+
* property the form put a file in — one built before the declaration existed, or a
|
|
1972
|
+
* page pointing at the wrong workflow — so the message names BOTH halves: the
|
|
1973
|
+
* property that carries a file, and the one (if any) the workflow says is its
|
|
1974
|
+
* upload. A message naming only one of them leaves the reader guessing which end
|
|
1975
|
+
* to change.
|
|
1976
|
+
*
|
|
1977
|
+
* A throw rather than a silent repair: this hook cannot know whether the right fix
|
|
1978
|
+
* is to declare the property or to stop sending the file, and guessing either way
|
|
1979
|
+
* would start a run the caller did not mean.
|
|
1980
|
+
*/
|
|
1981
|
+
function assertSendable(workflow, payload, field) {
|
|
1982
|
+
const unsendable = fileFields(payload);
|
|
1983
|
+
if (unsendable.length === 0) return;
|
|
1984
|
+
const carries = unsendable.length === 1 ? "carries a file" : "carry files";
|
|
1985
|
+
const declares = field === void 0 ? "" : ` (it declares "${field}")`;
|
|
1986
|
+
throw new Error(`Cannot start "${workflow}": ${unsendable.join(", ")} ${carries} the workflow does not declare as an upload${declares}. Add the property to \`workflow({ uploads: [...] })\`, or submit an upload id.`);
|
|
1987
|
+
}
|
|
1929
1988
|
/** A fresh upload id: a capability, so it is random rather than derived. */
|
|
1930
1989
|
function randomUploadId() {
|
|
1931
1990
|
return crypto.randomUUID().replaceAll("-", "");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useSessionSelector, useTheme } from "./context.js";
|
|
2
2
|
import { i as primaryTint, r as inkTint } from "./_colors-CcAi2FOU.js";
|
|
3
|
-
import { t as ToolCallBlock } from "./tool-call-block-
|
|
3
|
+
import { t as ToolCallBlock } from "./tool-call-block-D6pTEPrT.js";
|
|
4
4
|
import clsx from "clsx";
|
|
5
5
|
import { StickToBottom } from "use-stick-to-bottom";
|
|
6
6
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -232,12 +232,6 @@ function openReconnectingSocket(urlProvider) {
|
|
|
232
232
|
return new ReconnectingWebSocket(urlProvider, void 0, RECONNECT_OPTIONS);
|
|
233
233
|
}
|
|
234
234
|
/**
|
|
235
|
-
* True while `socket` is a reconnecting socket that will retry after the
|
|
236
|
-
* close event currently being handled. partysocket schedules the retry
|
|
237
|
-
* *before* dispatching `close`, so `retryCount` already names the attempt
|
|
238
|
-
* just scheduled — at `maxRetries` it has given up.
|
|
239
|
-
*/
|
|
240
|
-
/**
|
|
241
235
|
* Force a fresh connection attempt on a reconnecting socket, reporting
|
|
242
236
|
* whether it was one. Used for a failure partysocket cannot see: a socket it
|
|
243
237
|
* considers open and healthy, whose peer never completed OUR handshake.
|
|
@@ -249,6 +243,12 @@ function forceReconnect(socket) {
|
|
|
249
243
|
socket.reconnect();
|
|
250
244
|
return true;
|
|
251
245
|
}
|
|
246
|
+
/**
|
|
247
|
+
* True while `socket` is a reconnecting socket that will retry after the
|
|
248
|
+
* close event currently being handled. partysocket schedules the retry
|
|
249
|
+
* *before* dispatching `close`, so `retryCount` already names the attempt
|
|
250
|
+
* just scheduled — at `maxRetries` it has given up.
|
|
251
|
+
*/
|
|
252
252
|
function reconnectPending(socket) {
|
|
253
253
|
return socket instanceof ReconnectingWebSocket && socket.shouldReconnect && socket.retryCount < RECONNECT_OPTIONS.maxRetries;
|
|
254
254
|
}
|
|
@@ -706,16 +706,15 @@ function createMessageHandlers(deps) {
|
|
|
706
706
|
case "tool.completed": {
|
|
707
707
|
const tcs = getSnapshot().toolCalls;
|
|
708
708
|
const idx = tcs.findIndex((tc) => tc.callId === e.toolCallId);
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
}
|
|
709
|
+
const existing = tcs[idx];
|
|
710
|
+
if (!existing) break;
|
|
711
|
+
const updated = [...tcs];
|
|
712
|
+
updated[idx] = {
|
|
713
|
+
...existing,
|
|
714
|
+
status: "done",
|
|
715
|
+
result: e.result
|
|
716
|
+
};
|
|
717
|
+
updateState({ toolCalls: updated });
|
|
719
718
|
break;
|
|
720
719
|
}
|
|
721
720
|
case "reply.completed":
|
|
@@ -765,7 +764,7 @@ function createMessageHandlers(deps) {
|
|
|
765
764
|
handleErrorEvent(e);
|
|
766
765
|
break;
|
|
767
766
|
case "session.timed-out":
|
|
768
|
-
|
|
767
|
+
conn.retiredByServer = true;
|
|
769
768
|
break;
|
|
770
769
|
default: break;
|
|
771
770
|
}
|
|
@@ -949,13 +948,24 @@ function createSessionCore(options) {
|
|
|
949
948
|
function resetState() {
|
|
950
949
|
updateState(CLEARED_SESSION_STATE);
|
|
951
950
|
}
|
|
951
|
+
/**
|
|
952
|
+
* The socket, when there is one and it can carry a frame.
|
|
953
|
+
*
|
|
954
|
+
* Returning the socket rather than a boolean because every caller needs it
|
|
955
|
+
* next, and a predicate does not narrow `conn.ws` across the call. One
|
|
956
|
+
* spelling of the readiness test, which four call sites used to repeat.
|
|
957
|
+
*/
|
|
958
|
+
function openSocket() {
|
|
959
|
+
return conn.ws?.readyState === WS_OPEN ? conn.ws : null;
|
|
960
|
+
}
|
|
952
961
|
function sendJson(msg) {
|
|
953
|
-
|
|
962
|
+
openSocket()?.send(JSON.stringify(msg));
|
|
954
963
|
}
|
|
955
964
|
function sendAudio(bytes) {
|
|
956
|
-
|
|
957
|
-
if (
|
|
958
|
-
|
|
965
|
+
const ws = openSocket();
|
|
966
|
+
if (!ws) return;
|
|
967
|
+
if (ws.bufferedAmount > MIC_SEND_MAX_BUFFERED_BYTES) return;
|
|
968
|
+
ws.send(bytes);
|
|
959
969
|
}
|
|
960
970
|
const { handleMessage, settleWhenAudioDrained } = createMessageHandlers({
|
|
961
971
|
getSnapshot,
|
|
@@ -1090,7 +1100,7 @@ function createSessionCore(options) {
|
|
|
1090
1100
|
}, { signal: sig });
|
|
1091
1101
|
}
|
|
1092
1102
|
function cancel() {
|
|
1093
|
-
if (!
|
|
1103
|
+
if (!openSocket()) return;
|
|
1094
1104
|
conn.turn.bump();
|
|
1095
1105
|
conn.voiceIO?.flush();
|
|
1096
1106
|
updateState({ state: "listening" });
|
|
@@ -1099,7 +1109,7 @@ function createSessionCore(options) {
|
|
|
1099
1109
|
function reset() {
|
|
1100
1110
|
conn.turn.bump();
|
|
1101
1111
|
conn.voiceIO?.flush();
|
|
1102
|
-
if (
|
|
1112
|
+
if (openSocket()) {
|
|
1103
1113
|
sendJson({ type: "reset" });
|
|
1104
1114
|
return;
|
|
1105
1115
|
}
|
|
@@ -6,12 +6,6 @@ import type { WebSocketConstructor } from "./types.ts";
|
|
|
6
6
|
* session started with.
|
|
7
7
|
*/
|
|
8
8
|
export declare function openReconnectingSocket(urlProvider: () => Promise<string>): InstanceType<WebSocketConstructor>;
|
|
9
|
-
/**
|
|
10
|
-
* True while `socket` is a reconnecting socket that will retry after the
|
|
11
|
-
* close event currently being handled. partysocket schedules the retry
|
|
12
|
-
* *before* dispatching `close`, so `retryCount` already names the attempt
|
|
13
|
-
* just scheduled — at `maxRetries` it has given up.
|
|
14
|
-
*/
|
|
15
9
|
/**
|
|
16
10
|
* Force a fresh connection attempt on a reconnecting socket, reporting
|
|
17
11
|
* whether it was one. Used for a failure partysocket cannot see: a socket it
|
|
@@ -20,4 +14,10 @@ export declare function openReconnectingSocket(urlProvider: () => Promise<string
|
|
|
20
14
|
* reconnect machinery — the caller then treats the failure as terminal.
|
|
21
15
|
*/
|
|
22
16
|
export declare function forceReconnect(socket: unknown): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* True while `socket` is a reconnecting socket that will retry after the
|
|
19
|
+
* close event currently being handled. partysocket schedules the retry
|
|
20
|
+
* *before* dispatching `close`, so `retryCount` already names the attempt
|
|
21
|
+
* just scheduled — at `maxRetries` it has given up.
|
|
22
|
+
*/
|
|
23
23
|
export declare function reconnectPending(socket: unknown): boolean;
|
package/dist/session-core.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as createSessionCore } from "./session-core-
|
|
1
|
+
import { t as createSessionCore } from "./session-core-CAfYmUbg.js";
|
|
2
2
|
export { createSessionCore };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useTheme } from "./context.js";
|
|
2
2
|
import { r as inkTint } from "./_colors-CcAi2FOU.js";
|
|
3
3
|
import { t as Eyebrow } from "./eyebrow-C6ZFuiz6.js";
|
|
4
|
-
import {
|
|
4
|
+
import { i as tryParseJSON, r as truncate } from "./_utils-B6498_bm.js";
|
|
5
5
|
import clsx from "clsx";
|
|
6
6
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
7
|
import { createContext, memo, useContext, useMemo, useState } from "react";
|
|
@@ -40,6 +40,7 @@ function ToolCallRow({ title, detail, pending = false, icon, variant = "default"
|
|
|
40
40
|
const theme = useTheme();
|
|
41
41
|
const sizes = VARIANT_CLASSES[variant];
|
|
42
42
|
const canExpand = children != null;
|
|
43
|
+
const faint = inkTint(theme.text, theme.bg, 65);
|
|
43
44
|
return /* @__PURE__ */ jsxs("div", {
|
|
44
45
|
className: clsx("flex flex-col rounded-md border overflow-hidden", className),
|
|
45
46
|
style: {
|
|
@@ -60,7 +61,7 @@ function ToolCallRow({ title, detail, pending = false, icon, variant = "default"
|
|
|
60
61
|
children: icon
|
|
61
62
|
}) : /* @__PURE__ */ jsx(Eyebrow, {
|
|
62
63
|
className: "shrink-0",
|
|
63
|
-
style: { color:
|
|
64
|
+
style: { color: faint },
|
|
64
65
|
children: "Tool"
|
|
65
66
|
}),
|
|
66
67
|
/* @__PURE__ */ jsx("span", {
|
|
@@ -70,12 +71,12 @@ function ToolCallRow({ title, detail, pending = false, icon, variant = "default"
|
|
|
70
71
|
}),
|
|
71
72
|
/* @__PURE__ */ jsx("span", {
|
|
72
73
|
className: clsx("font-aai-mono truncate flex-1 min-w-0", sizes.title),
|
|
73
|
-
style: { color:
|
|
74
|
+
style: { color: faint },
|
|
74
75
|
children: detail
|
|
75
76
|
}),
|
|
76
77
|
canExpand && /* @__PURE__ */ jsx("span", {
|
|
77
78
|
className: clsx("shrink-0 transition-transform duration-150", sizes.chevron, isOpen && "rotate-90"),
|
|
78
|
-
style: { color:
|
|
79
|
+
style: { color: faint },
|
|
79
80
|
children: "▶"
|
|
80
81
|
})
|
|
81
82
|
]
|
|
@@ -141,13 +141,14 @@ export type UseWorkflowSubmitOptions = {
|
|
|
141
141
|
/**
|
|
142
142
|
* Send each chosen file as concurrent parts instead of in one request.
|
|
143
143
|
*
|
|
144
|
-
* `
|
|
145
|
-
* the wait a form with a recording in it actually spends: the run does
|
|
146
|
-
* until its input is stored, so until the last byte lands there is no
|
|
147
|
-
* watch and nothing for `<WorkflowProgress>` to say. Splitting the file
|
|
148
|
-
* connections is what makes that stretch shorter, and it degrades to the
|
|
149
|
-
* request wherever it would not help — a small file, an older agent — so
|
|
150
|
-
*
|
|
144
|
+
* **On by default.** `false` opts out, `{ partBytes, concurrency }` tunes it.
|
|
145
|
+
* This is the wait a form with a recording in it actually spends: the run does
|
|
146
|
+
* not exist until its input is stored, so until the last byte lands there is no
|
|
147
|
+
* run to watch and nothing for `<WorkflowProgress>` to say. Splitting the file
|
|
148
|
+
* across connections is what makes that stretch shorter, and it degrades to the
|
|
149
|
+
* single request wherever it would not help — a small file, an older agent — so
|
|
150
|
+
* the default costs nothing where it would not have paid. See
|
|
151
|
+
* `UploadOptions.parallel`.
|
|
151
152
|
*/
|
|
152
153
|
parallel?: UploadParallel;
|
|
153
154
|
};
|
|
@@ -48,14 +48,18 @@
|
|
|
48
48
|
* - **Reporting the bytes.** The same `UploadStatus` `useWorkflowSubmit` reports,
|
|
49
49
|
* so `<UploadProgressBar>` renders either without knowing which hook it came from.
|
|
50
50
|
*
|
|
51
|
-
* ## A failed upload
|
|
51
|
+
* ## A failed upload is RESUMED once, and only then cancels the run
|
|
52
52
|
*
|
|
53
53
|
* An upload that dies stays in the store, incomplete, and `complete` never becomes
|
|
54
54
|
* true — so a run left behind polls until its own abandonment bound and then fails,
|
|
55
|
-
* minutes after the page has already reported the error.
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
55
|
+
* minutes after the page has already reported the error.
|
|
56
|
+
*
|
|
57
|
+
* That used to be the whole story, and it threw away a run and a file together for
|
|
58
|
+
* what is usually one dropped connection near the end. So a failure gets one more
|
|
59
|
+
* attempt with `resume: true`, which sends only the windows the store does not
|
|
60
|
+
* already have (`UploadInfo.ranges`) — the run is still waiting on the same id, so
|
|
61
|
+
* a resume that succeeds is invisible to it. Cancelling is what happens when THAT
|
|
62
|
+
* fails too, and it is the honest end to a submission that did not happen.
|
|
59
63
|
*/
|
|
60
64
|
import type { UploadParallel } from "@alexkroman1/aai/workflow-api";
|
|
61
65
|
import type { UploadStatus } from "./use-workflow-form.ts";
|
|
@@ -77,8 +81,8 @@ export type UseWorkflowStreamOptions = {
|
|
|
77
81
|
* the uplink sees the same growing file whether one connection or four are
|
|
78
82
|
* filling it. What changes is only how fast it grows.
|
|
79
83
|
*
|
|
80
|
-
* `
|
|
81
|
-
* `UploadOptions.parallel`.
|
|
84
|
+
* **On by default.** `false` opts out, `{ partBytes, concurrency }` tunes it.
|
|
85
|
+
* See `UploadOptions.parallel`.
|
|
82
86
|
*/
|
|
83
87
|
parallel?: UploadParallel;
|
|
84
88
|
};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { MIC_BUFFER_SECONDS, MIC_SILENCE_PROBE_MS } from "../types.js";
|
|
2
2
|
import { t as workletModuleUrl } from "../_module-url-C_4gRVL0.js";
|
|
3
3
|
//#region worklets/capture-processor.ts
|
|
4
|
-
|
|
4
|
+
/** Raw worklet source — exported so tests can evaluate the processor directly. */
|
|
5
|
+
const captureProcessorSource = `
|
|
5
6
|
class CaptureProcessor extends AudioWorkletProcessor {
|
|
6
7
|
constructor(options) {
|
|
7
8
|
super();
|
|
@@ -91,8 +92,6 @@ class CaptureProcessor extends AudioWorkletProcessor {
|
|
|
91
92
|
|
|
92
93
|
registerProcessor('capture-processor', CaptureProcessor);
|
|
93
94
|
`;
|
|
94
|
-
|
|
95
|
-
const captureProcessorSource = CaptureProcessorWorklet;
|
|
96
|
-
var capture_processor_default = workletModuleUrl(CaptureProcessorWorklet);
|
|
95
|
+
var capture_processor_default = workletModuleUrl(captureProcessorSource);
|
|
97
96
|
//#endregion
|
|
98
97
|
export { captureProcessorSource, capture_processor_default as default };
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { PLAYBACK_BUFFER_SECONDS, PLAYBACK_CONCEAL_FADE_MS, PLAYBACK_CONCEAL_FLOOR, PLAYBACK_FILL_MS, PLAYBACK_PROGRESS_INTERVAL_MS } from "../types.js";
|
|
2
2
|
import { t as workletModuleUrl } from "../_module-url-C_4gRVL0.js";
|
|
3
3
|
//#region worklets/playback-processor.ts
|
|
4
|
-
|
|
4
|
+
/** Raw worklet source — exported so tests can evaluate the processor directly. */
|
|
5
|
+
const playbackProcessorSource = `
|
|
5
6
|
class PlaybackProcessor extends AudioWorkletProcessor {
|
|
6
7
|
constructor(options) {
|
|
7
8
|
super();
|
|
@@ -315,8 +316,6 @@ class PlaybackProcessor extends AudioWorkletProcessor {
|
|
|
315
316
|
|
|
316
317
|
registerProcessor('playback-processor', PlaybackProcessor);
|
|
317
318
|
`;
|
|
318
|
-
|
|
319
|
-
const playbackProcessorSource = PlaybackProcessorWorklet;
|
|
320
|
-
var playback_processor_default = workletModuleUrl(PlaybackProcessorWorklet);
|
|
319
|
+
var playback_processor_default = workletModuleUrl(playbackProcessorSource);
|
|
321
320
|
//#endregion
|
|
322
321
|
export { playback_processor_default as default, playbackProcessorSource };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alexkroman1/aai-ui",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.6.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"remark-gfm": "^4.0.1",
|
|
30
30
|
"use-stick-to-bottom": "^1.1.6",
|
|
31
31
|
"use-sync-external-store": "^1.6.0",
|
|
32
|
-
"@alexkroman1/aai": "6.
|
|
32
|
+
"@alexkroman1/aai": "6.6.0"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
35
|
"react": "^19.0.0",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{o as e,s as t}from"./client-audio-constants-Ck0IJO4c.js";var n={echoCancellation:!0,noiseSuppression:!1,autoGainControl:!1,voiceIsolation:!1};function r(e,t,n){if(e!==t)throw Error(`Browser refused the ${n} sample rate: asked for ${t} Hz, got ${e} Hz`)}function i(e){e.then(e=>{for(let t of e.getTracks())t.stop()}).catch(()=>{})}function a(e,t,n){let r=new AudioWorkletNode(e,`capture-processor`,{channelCount:1,channelCountMode:`explicit`}),i=null;return r.port.onmessage=e=>{let r=e.data;r.event===`chunk`&&r.buffer?t(r.buffer):r.event===`silent`?n?.():r.event===`stopped`&&(i?.(),i=null)},{node:r,start(){r.port.postMessage({event:`start`})},stop(){let{promise:e,resolve:t}=Promise.withResolvers(),n=setTimeout(t,250);return i=()=>{clearTimeout(n),t()},r.port.postMessage({event:`stop`}),e}}}async function o(o){let{sttSampleRate:s,ttsSampleRate:c,captureWorkletSrc:l,playbackWorkletSrc:u,onMicData:d,onError:f,onPlaybackStats:p,onPlaybackProgress:m,onMicSilent:h}=o,g=new AudioContext({sampleRate:c,latencyHint:`playback`}),_=s===c,v=_?g:new AudioContext({sampleRate:s,latencyHint:`interactive`});async function y(){let e=_?[g]:[g,v];await Promise.all(e.map(e=>e.close().catch(e=>{console.warn(`AudioContext close failed:`,e)})))}let b=navigator.mediaDevices.getUserMedia({audio:{deviceId:{ideal:`default`},...n}}),x;try{[x]=await Promise.all([b,g.resume(),v.resume(),v.audioWorklet.addModule(l),g.audioWorklet.addModule(u)]),r(v.sampleRate,s,`capture`),r(g.sampleRate,c,`playback`)}catch(e){throw i(b),await y(),e}let S=v.createMediaStreamSource(x),C=a(v,d,h);S.connect(C.node),C.node.onprocessorerror=()=>{let e=Error(`Audio capture worklet crashed`);console.error(`[aai-ui]`,e.message),f?.(e)},C.start();let w=null,T=null,E=0,D=null,O=new AbortController;function k(){T?.(),T=null,D=null}function A(e){e.stats&&e.stats.concealedSamples>0&&p?.(e.stats),e.reason!==`interrupt`&&(D===null||e.turn===D)&&k()}function j(){if(w)return w;let e=new AudioWorkletNode(g,`playback-processor`);return e.connect(g.destination),e.port.onmessage=e=>{e.data.event===`stop`?A(e.data):e.data.event===`progress`&&m?.(e.data.bufferedMs)},e.onprocessorerror=()=>{let e=Error(`Audio playback worklet crashed`);console.error(`[aai-ui]`,e.message),k(),f?.(e)},w=e,e}let M={enqueue(e){O.signal.aborted||e.byteLength!==0&&j().port.postMessage({event:`write`,buffer:new Uint8Array(e)},[e])},done(){if(!w)return Promise.resolve();let n=++E;if(w.port.postMessage({event:`done`,turn:n}),g.state!==`running`)return D=null,Promise.resolve();let{promise:r,resolve:i}=Promise.withResolvers();T?.();let a=()=>{clearInterval(o),clearTimeout(s),T===a&&(T=null,D=null),i()},o=setInterval(()=>{g.state!==`running`&&a()},t),s=setTimeout(a,e);return T=a,D=n,r},flush(){w&&(k(),w.port.postMessage({event:`interrupt`}))},async close(){if(!O.signal.aborted){O.abort(),await C.stop(),S.disconnect(),C.node.disconnect(),w&&w.disconnect();for(let e of x.getTracks())e.stop();await y()}},async[Symbol.asyncDispose](){await M.close()}};return M}export{o as createVoiceIO};
|