@alexkroman1/aai-ui 6.4.0 → 6.5.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/{_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 +84 -29
- 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 +13 -1
- package/dist/use-workflow-stream.d.ts +14 -0
- 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
|
/**
|
|
@@ -1068,7 +1088,7 @@ function useWorkflows(opts = {}) {
|
|
|
1068
1088
|
* so this is invisible to every form that has none — including one whose values
|
|
1069
1089
|
* are not an object at all, which `submit` accepts.
|
|
1070
1090
|
*/
|
|
1071
|
-
async function uploadFiles(api, input, report) {
|
|
1091
|
+
async function uploadFiles(api, input, report, parallel) {
|
|
1072
1092
|
if (!isRecord(input)) return input;
|
|
1073
1093
|
const entries = Object.entries(input);
|
|
1074
1094
|
const count = entries.reduce((total, [, value]) => total + filesOf(value).length, 0);
|
|
@@ -1080,10 +1100,13 @@ async function uploadFiles(api, input, report) {
|
|
|
1080
1100
|
index,
|
|
1081
1101
|
count
|
|
1082
1102
|
};
|
|
1083
|
-
return (await api.upload(file, {
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1103
|
+
return (await api.upload(file, {
|
|
1104
|
+
onProgress: (progress) => report({
|
|
1105
|
+
...position,
|
|
1106
|
+
...progress
|
|
1107
|
+
}),
|
|
1108
|
+
...omitUndefined({ parallel })
|
|
1109
|
+
})).id;
|
|
1087
1110
|
};
|
|
1088
1111
|
const out = {};
|
|
1089
1112
|
for (const [name, value] of entries) {
|
|
@@ -1128,16 +1151,16 @@ async function uploadFiles(api, input, report) {
|
|
|
1128
1151
|
* @public
|
|
1129
1152
|
*/
|
|
1130
1153
|
function useWorkflowSubmit(workflow, opts = {}) {
|
|
1131
|
-
const { api, key, wait, intervalMs } = opts;
|
|
1154
|
+
const { api, key, wait, intervalMs, parallel } = opts;
|
|
1132
1155
|
const [runId, setRunId] = useState(void 0);
|
|
1133
1156
|
const [starting, setStarting] = useState(false);
|
|
1134
1157
|
const [startError, setStartError] = useState(void 0);
|
|
1135
1158
|
const [upload, setUpload] = useState(void 0);
|
|
1136
1159
|
const getClient = useWorkflowApiRef(api);
|
|
1137
|
-
const tracked = useWorkflowRun(runId, {
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
});
|
|
1160
|
+
const tracked = useWorkflowRun(runId, omitUndefined({
|
|
1161
|
+
api,
|
|
1162
|
+
intervalMs
|
|
1163
|
+
}));
|
|
1141
1164
|
return {
|
|
1142
1165
|
submit: useCallback(async (input) => {
|
|
1143
1166
|
const client = getClient();
|
|
@@ -1146,7 +1169,7 @@ function useWorkflowSubmit(workflow, opts = {}) {
|
|
|
1146
1169
|
setRunId(void 0);
|
|
1147
1170
|
try {
|
|
1148
1171
|
const options = omitUndefined({ key });
|
|
1149
|
-
const started = await uploadFiles(client, input, setUpload);
|
|
1172
|
+
const started = await uploadFiles(client, input, setUpload, parallel);
|
|
1150
1173
|
setRunId(wait === void 0 ? await client.start(workflow, started, options) : (await client.startAndWait(workflow, started, {
|
|
1151
1174
|
...options,
|
|
1152
1175
|
wait
|
|
@@ -1161,6 +1184,7 @@ function useWorkflowSubmit(workflow, opts = {}) {
|
|
|
1161
1184
|
workflow,
|
|
1162
1185
|
key,
|
|
1163
1186
|
wait,
|
|
1187
|
+
parallel,
|
|
1164
1188
|
getClient
|
|
1165
1189
|
]),
|
|
1166
1190
|
reset: useCallback(() => {
|
|
@@ -1243,7 +1267,8 @@ function SchemaField({ name, property, required, upload = false }) {
|
|
|
1243
1267
|
...hint,
|
|
1244
1268
|
...defaults
|
|
1245
1269
|
});
|
|
1246
|
-
|
|
1270
|
+
const type = typeOf(property);
|
|
1271
|
+
switch (type) {
|
|
1247
1272
|
case "boolean": return /* @__PURE__ */ jsx(CheckboxField, {
|
|
1248
1273
|
name,
|
|
1249
1274
|
label,
|
|
@@ -1255,7 +1280,7 @@ function SchemaField({ name, property, required, upload = false }) {
|
|
|
1255
1280
|
name,
|
|
1256
1281
|
label,
|
|
1257
1282
|
required,
|
|
1258
|
-
step:
|
|
1283
|
+
step: type === "integer" ? 1 : "any",
|
|
1259
1284
|
...hint,
|
|
1260
1285
|
...defaults
|
|
1261
1286
|
});
|
|
@@ -1542,7 +1567,7 @@ function useWorkflowProgress(runId, opts = {}) {
|
|
|
1542
1567
|
* @public
|
|
1543
1568
|
*/
|
|
1544
1569
|
function WorkflowProgress({ runId, api, className, placeholder }) {
|
|
1545
|
-
const { progress, streaming, supported } = useWorkflowProgress(runId,
|
|
1570
|
+
const { progress, streaming, supported } = useWorkflowProgress(runId, omitUndefined({ api }));
|
|
1546
1571
|
if (!supported || progress.length === 0) return placeholder ?? null;
|
|
1547
1572
|
return /* @__PURE__ */ jsxs("pre", {
|
|
1548
1573
|
className: clsx(className ?? "whitespace-pre-wrap border-l pl-4 text-xs opacity-70"),
|
|
@@ -1606,7 +1631,7 @@ function WorkflowProgress({ runId, api, className, placeholder }) {
|
|
|
1606
1631
|
*/
|
|
1607
1632
|
function page(config) {
|
|
1608
1633
|
const container = resolveContainer(config.target);
|
|
1609
|
-
|
|
1634
|
+
setPageTitle(config.name);
|
|
1610
1635
|
return mountRoot(container, createElement(ThemeProvider, { value: config.theme }, createElement(config.component)));
|
|
1611
1636
|
}
|
|
1612
1637
|
//#endregion
|
|
@@ -1859,16 +1884,16 @@ function useWorkflowRuns(workflow, opts = {}) {
|
|
|
1859
1884
|
* @public
|
|
1860
1885
|
*/
|
|
1861
1886
|
function useWorkflowStream(workflow, opts = {}) {
|
|
1862
|
-
const { api, key, intervalMs } = opts;
|
|
1887
|
+
const { api, key, intervalMs, parallel } = opts;
|
|
1863
1888
|
const [runId, setRunId] = useState(void 0);
|
|
1864
1889
|
const [starting, setStarting] = useState(false);
|
|
1865
1890
|
const [startError, setStartError] = useState(void 0);
|
|
1866
1891
|
const [upload, setUpload] = useState(void 0);
|
|
1867
1892
|
const getClient = useWorkflowApiRef(api);
|
|
1868
|
-
const tracked = useWorkflowRun(runId, {
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
});
|
|
1893
|
+
const tracked = useWorkflowRun(runId, omitUndefined({
|
|
1894
|
+
api,
|
|
1895
|
+
intervalMs
|
|
1896
|
+
}));
|
|
1872
1897
|
return {
|
|
1873
1898
|
submit: useCallback(async (input) => {
|
|
1874
1899
|
const client = getClient();
|
|
@@ -1884,6 +1909,7 @@ function useWorkflowStream(workflow, opts = {}) {
|
|
|
1884
1909
|
...input,
|
|
1885
1910
|
[field]: id
|
|
1886
1911
|
} : input;
|
|
1912
|
+
assertSendable(workflow, payload, field);
|
|
1887
1913
|
started = await client.start(workflow, payload, omitUndefined({ key }));
|
|
1888
1914
|
setRunId(started);
|
|
1889
1915
|
if (!chosen) return;
|
|
@@ -1894,7 +1920,8 @@ function useWorkflowStream(workflow, opts = {}) {
|
|
|
1894
1920
|
name: chosen.name,
|
|
1895
1921
|
index: 1,
|
|
1896
1922
|
count: 1
|
|
1897
|
-
})
|
|
1923
|
+
}),
|
|
1924
|
+
...omitUndefined({ parallel })
|
|
1898
1925
|
});
|
|
1899
1926
|
await client.wake(started).catch(() => void 0);
|
|
1900
1927
|
} catch (err) {
|
|
@@ -1907,6 +1934,7 @@ function useWorkflowStream(workflow, opts = {}) {
|
|
|
1907
1934
|
}, [
|
|
1908
1935
|
workflow,
|
|
1909
1936
|
key,
|
|
1937
|
+
parallel,
|
|
1910
1938
|
getClient
|
|
1911
1939
|
]),
|
|
1912
1940
|
reset: useCallback(() => {
|
|
@@ -1920,6 +1948,33 @@ function useWorkflowStream(workflow, opts = {}) {
|
|
|
1920
1948
|
error: startError ?? tracked.error
|
|
1921
1949
|
};
|
|
1922
1950
|
}
|
|
1951
|
+
/**
|
|
1952
|
+
* Refuse a payload carrying a `File`, before a run is started over it.
|
|
1953
|
+
*
|
|
1954
|
+
* A File cannot be SENT: a run input is JSON and `JSON.stringify(new File(…))` is
|
|
1955
|
+
* `{}` (see `fileFields`). So without this the run starts, rejects its own input,
|
|
1956
|
+
* and reports a type error about a property the user filled in correctly — which is
|
|
1957
|
+
* what production did, five times: `Invalid input for workflow "transcribe":
|
|
1958
|
+
* recording: Invalid input`, from a page whose file picker was working.
|
|
1959
|
+
*
|
|
1960
|
+
* The reachable cause is a workflow whose listing declares no `uploads` for the
|
|
1961
|
+
* property the form put a file in — one built before the declaration existed, or a
|
|
1962
|
+
* page pointing at the wrong workflow — so the message names BOTH halves: the
|
|
1963
|
+
* property that carries a file, and the one (if any) the workflow says is its
|
|
1964
|
+
* upload. A message naming only one of them leaves the reader guessing which end
|
|
1965
|
+
* to change.
|
|
1966
|
+
*
|
|
1967
|
+
* A throw rather than a silent repair: this hook cannot know whether the right fix
|
|
1968
|
+
* is to declare the property or to stop sending the file, and guessing either way
|
|
1969
|
+
* would start a run the caller did not mean.
|
|
1970
|
+
*/
|
|
1971
|
+
function assertSendable(workflow, payload, field) {
|
|
1972
|
+
const unsendable = fileFields(payload);
|
|
1973
|
+
if (unsendable.length === 0) return;
|
|
1974
|
+
const carries = unsendable.length === 1 ? "carries a file" : "carry files";
|
|
1975
|
+
const declares = field === void 0 ? "" : ` (it declares "${field}")`;
|
|
1976
|
+
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.`);
|
|
1977
|
+
}
|
|
1923
1978
|
/** A fresh upload id: a capability, so it is random rather than derived. */
|
|
1924
1979
|
function randomUploadId() {
|
|
1925
1980
|
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
|
]
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
* followed from the same id either way.
|
|
34
34
|
*/
|
|
35
35
|
import { type WorkflowSummary } from "@alexkroman1/aai";
|
|
36
|
-
import type { UploadProgress } from "@alexkroman1/aai/workflow-api";
|
|
36
|
+
import type { UploadParallel, UploadProgress } from "@alexkroman1/aai/workflow-api";
|
|
37
37
|
import type { WorkflowApi, WorkflowRun } from "./workflow-client.ts";
|
|
38
38
|
/** Options for {@link useWorkflows}. */
|
|
39
39
|
export type UseWorkflowsOptions = {
|
|
@@ -138,6 +138,18 @@ export type UseWorkflowSubmitOptions = {
|
|
|
138
138
|
wait?: number;
|
|
139
139
|
/** How often the fallback poll re-reads a live run. */
|
|
140
140
|
intervalMs?: number;
|
|
141
|
+
/**
|
|
142
|
+
* Send each chosen file as concurrent parts instead of in one request.
|
|
143
|
+
*
|
|
144
|
+
* `true` for the defaults, or `{ partBytes, concurrency }` to tune them. This is
|
|
145
|
+
* the wait a form with a recording in it actually spends: the run does not exist
|
|
146
|
+
* until its input is stored, so until the last byte lands there is no run to
|
|
147
|
+
* watch and nothing for `<WorkflowProgress>` to say. Splitting the file across
|
|
148
|
+
* connections is what makes that stretch shorter, and it degrades to the single
|
|
149
|
+
* request wherever it would not help — a small file, an older agent — so turning
|
|
150
|
+
* it on is safe for every form. See `UploadOptions.parallel`.
|
|
151
|
+
*/
|
|
152
|
+
parallel?: UploadParallel;
|
|
141
153
|
};
|
|
142
154
|
/**
|
|
143
155
|
* Start a workflow from a form, and follow the run it creates.
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
* thrown away with the run; a caller who wants to resume instead drives
|
|
58
58
|
* `api.uploadStream` and `api.uploadInfo` directly.
|
|
59
59
|
*/
|
|
60
|
+
import type { UploadParallel } from "@alexkroman1/aai/workflow-api";
|
|
60
61
|
import type { UploadStatus } from "./use-workflow-form.ts";
|
|
61
62
|
import type { WorkflowApi, WorkflowRun } from "./workflow-client.ts";
|
|
62
63
|
/** Options for {@link useWorkflowStream}. */
|
|
@@ -67,6 +68,19 @@ export type UseWorkflowStreamOptions = {
|
|
|
67
68
|
key?: string;
|
|
68
69
|
/** How often the fallback poll re-reads a live run. */
|
|
69
70
|
intervalMs?: number;
|
|
71
|
+
/**
|
|
72
|
+
* Send the file as concurrent parts instead of in one streaming request.
|
|
73
|
+
*
|
|
74
|
+
* It COMPOSES with what this hook is for rather than competing with it: the run
|
|
75
|
+
* still starts before the bytes, and the store still publishes how far the file
|
|
76
|
+
* is readable — that number is the CONTIGUOUS prefix, so a run reading ahead of
|
|
77
|
+
* the uplink sees the same growing file whether one connection or four are
|
|
78
|
+
* filling it. What changes is only how fast it grows.
|
|
79
|
+
*
|
|
80
|
+
* `true` for the defaults, or `{ partBytes, concurrency }` to tune them. See
|
|
81
|
+
* `UploadOptions.parallel`.
|
|
82
|
+
*/
|
|
83
|
+
parallel?: UploadParallel;
|
|
70
84
|
};
|
|
71
85
|
/** What {@link useWorkflowStream} returns. */
|
|
72
86
|
export type WorkflowStreamSubmission<R = unknown> = {
|
|
@@ -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.5.1",
|
|
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.5.1"
|
|
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};
|