@vanillaskyai/video 0.10.18 → 0.10.20
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/CHANGELOG.md +13 -0
- package/dist/react.js +446 -227
- package/dist/server.js +1 -0
- package/docs/reference/protocol.md +10 -0
- package/package.json +1 -1
- package/starters/video-chat/package.json +1 -1
- package/starters/video-chat/stock.ts +48 -37
- package/styles/video-chat.css +56 -36
package/dist/react.js
CHANGED
|
@@ -1262,6 +1262,77 @@ var VideoError = class extends Error {
|
|
|
1262
1262
|
}
|
|
1263
1263
|
};
|
|
1264
1264
|
|
|
1265
|
+
// src/video-chat/caption-progress.ts
|
|
1266
|
+
function createCaptionVoice(source, now = () => performance.now()) {
|
|
1267
|
+
const durations = /* @__PURE__ */ new Map();
|
|
1268
|
+
let held = false;
|
|
1269
|
+
let current;
|
|
1270
|
+
const read = () => {
|
|
1271
|
+
const state = current;
|
|
1272
|
+
if (!state || !state.started || state.signal.aborted) return;
|
|
1273
|
+
const audioTime = !state.browser ? source.getCurrentTime?.() : void 0;
|
|
1274
|
+
const realClock = typeof audioTime === "number" && Number.isFinite(audioTime);
|
|
1275
|
+
if (!held) {
|
|
1276
|
+
state.elapsed = realClock ? Math.max(state.offset, audioTime) : state.offset + (state.since === void 0 ? 0 : (now() - state.since) / 1e3);
|
|
1277
|
+
}
|
|
1278
|
+
return { text: state.text, elapsedSeconds: Math.min(state.duration, Math.max(0, state.elapsed)), durationSeconds: state.duration, timing: realClock ? "audio" : "estimated" };
|
|
1279
|
+
};
|
|
1280
|
+
const reset = () => {
|
|
1281
|
+
current = void 0;
|
|
1282
|
+
};
|
|
1283
|
+
const voice = {
|
|
1284
|
+
get supportsOffsets() {
|
|
1285
|
+
return source.supportsOffsets;
|
|
1286
|
+
},
|
|
1287
|
+
...source.getCurrentTime ? { getCurrentTime: () => source.getCurrentTime() } : {},
|
|
1288
|
+
async prepare(text, options) {
|
|
1289
|
+
const prepared = await source.prepare(text, options);
|
|
1290
|
+
if (!options?.signal?.aborted && Number.isFinite(prepared.seconds) && prepared.seconds > 0) {
|
|
1291
|
+
if (durations.size >= 60) durations.delete(durations.keys().next().value);
|
|
1292
|
+
durations.set(text.trim(), prepared.seconds);
|
|
1293
|
+
}
|
|
1294
|
+
return prepared;
|
|
1295
|
+
},
|
|
1296
|
+
async speak(text, options) {
|
|
1297
|
+
const state = { text, duration: durations.get(text.trim()) ?? Math.max(1, text.trim().split(/\s+/u).length / 2.5), offset: options.offsetSeconds ?? 0, elapsed: options.offsetSeconds ?? 0, started: false, since: void 0, browser: false, signal: options.signal };
|
|
1298
|
+
current = state;
|
|
1299
|
+
const aborted = () => {
|
|
1300
|
+
if (current === state) reset();
|
|
1301
|
+
};
|
|
1302
|
+
options.signal.addEventListener("abort", aborted, { once: true });
|
|
1303
|
+
try {
|
|
1304
|
+
await source.speak(text, { ...options, onStart: (kind) => {
|
|
1305
|
+
if (current !== state || state.signal.aborted) return;
|
|
1306
|
+
if (!state.started) {
|
|
1307
|
+
state.started = true;
|
|
1308
|
+
state.browser = kind === "browser";
|
|
1309
|
+
state.since = now();
|
|
1310
|
+
}
|
|
1311
|
+
options.onStart?.(kind);
|
|
1312
|
+
} });
|
|
1313
|
+
} finally {
|
|
1314
|
+
options.signal.removeEventListener("abort", aborted);
|
|
1315
|
+
if (current === state) reset();
|
|
1316
|
+
}
|
|
1317
|
+
},
|
|
1318
|
+
pause() {
|
|
1319
|
+
read();
|
|
1320
|
+
held = true;
|
|
1321
|
+
source.pause();
|
|
1322
|
+
},
|
|
1323
|
+
resume() {
|
|
1324
|
+
if (held && current?.started) {
|
|
1325
|
+
current.offset = current.elapsed;
|
|
1326
|
+
current.since = now();
|
|
1327
|
+
}
|
|
1328
|
+
held = false;
|
|
1329
|
+
source.resume();
|
|
1330
|
+
},
|
|
1331
|
+
setMuted: (muted) => source.setMuted(muted)
|
|
1332
|
+
};
|
|
1333
|
+
return { voice, getCaptionProgress: read, reset };
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1265
1336
|
// src/player/recover-scene-media.ts
|
|
1266
1337
|
function recoverSceneMedia(scene) {
|
|
1267
1338
|
if (scene.templateId === "cinemaMedia") {
|
|
@@ -1276,7 +1347,7 @@ function recoverSceneMedia(scene) {
|
|
|
1276
1347
|
}
|
|
1277
1348
|
|
|
1278
1349
|
// src/video-chat/use-video-chat.ts
|
|
1279
|
-
import { useCallback as useCallback2, useEffect as useEffect4, useReducer, useRef as useRef3 } from "react";
|
|
1350
|
+
import { useCallback as useCallback2, useEffect as useEffect4, useMemo as useMemo2, useReducer, useRef as useRef3 } from "react";
|
|
1280
1351
|
|
|
1281
1352
|
// src/protocol/scene-timeline.ts
|
|
1282
1353
|
function createQueue() {
|
|
@@ -1905,6 +1976,9 @@ function reducer(state, action) {
|
|
|
1905
1976
|
return { ...state, capabilities: action.value };
|
|
1906
1977
|
case "welcome":
|
|
1907
1978
|
return { ...state, welcome: action.value };
|
|
1979
|
+
case "resolved-mode":
|
|
1980
|
+
if (state.turns.at(-1)?.id !== action.id) return state;
|
|
1981
|
+
return { ...state, turns: replaceTurn(state.turns, action.id, (turn) => ({ ...turn, mode: action.mode })) };
|
|
1908
1982
|
case "start":
|
|
1909
1983
|
return {
|
|
1910
1984
|
...state,
|
|
@@ -2141,7 +2215,10 @@ function useVideoChatSession(options = {}) {
|
|
|
2141
2215
|
onFallback: () => voiceWarningRef.current()
|
|
2142
2216
|
});
|
|
2143
2217
|
}
|
|
2144
|
-
const
|
|
2218
|
+
const rawVoice = options.voice ?? ownedVoiceRef.current;
|
|
2219
|
+
const captionVoice = useMemo2(() => createCaptionVoice(rawVoice), [rawVoice]);
|
|
2220
|
+
useEffect4(() => () => captionVoice.reset(), [captionVoice]);
|
|
2221
|
+
const voice = captionVoice.voice;
|
|
2145
2222
|
const voiceRef = useRef3(voice);
|
|
2146
2223
|
voiceRef.current = voice;
|
|
2147
2224
|
const unavailableVoiceLines = useRef3(/* @__PURE__ */ new Set());
|
|
@@ -2296,7 +2373,7 @@ function useVideoChatSession(options = {}) {
|
|
|
2296
2373
|
throw new VideoError("timeoutMs must be positive", { code: "invalid_option" });
|
|
2297
2374
|
}
|
|
2298
2375
|
const timeout = setTimeout(() => controller.abort(new DOMException("Video chat timed out", "TimeoutError")), timeoutMs);
|
|
2299
|
-
|
|
2376
|
+
let mode = currentOptions.mode ?? "cinematic";
|
|
2300
2377
|
const orientation = currentOptions.orientation ?? "landscape";
|
|
2301
2378
|
const id = (currentOptions.createTurnId ?? defaultTurnId)();
|
|
2302
2379
|
const conversation = conversationFor(stateRef.current.turns);
|
|
@@ -2441,6 +2518,12 @@ function useVideoChatSession(options = {}) {
|
|
|
2441
2518
|
if (!response.body || !response.headers.get("content-type")?.includes("text/event-stream")) {
|
|
2442
2519
|
throw new VideoError("Video chat endpoint did not return a video stream", { code: "invalid_response" });
|
|
2443
2520
|
}
|
|
2521
|
+
const resolvedMode = response.headers.get("x-vanillasky-resolved-video-mode");
|
|
2522
|
+
if (isCurrent() && (resolvedMode === "pexels" || resolvedMode === "cinematic")) {
|
|
2523
|
+
mode = resolvedMode;
|
|
2524
|
+
dispatch({ type: "resolved-mode", id, mode });
|
|
2525
|
+
if (firstFrameRef.current?.turnId === id) firstFrameRef.current.mode = mode;
|
|
2526
|
+
}
|
|
2444
2527
|
const planned = [];
|
|
2445
2528
|
const lines = spokenHook ? [spokenHook] : [];
|
|
2446
2529
|
const pending = [];
|
|
@@ -2818,14 +2901,117 @@ function useVideoChatSession(options = {}) {
|
|
|
2818
2901
|
playerKey: state.playerKey,
|
|
2819
2902
|
playerProps
|
|
2820
2903
|
};
|
|
2821
|
-
return { chat, restoreSession };
|
|
2904
|
+
return { chat, restoreSession, getCaptionProgress: captionVoice.getCaptionProgress };
|
|
2905
|
+
}
|
|
2906
|
+
|
|
2907
|
+
// src/video-chat/caption-pages.tsx
|
|
2908
|
+
import { useEffect as useEffect5, useLayoutEffect, useRef as useRef4, useState as useState3 } from "react";
|
|
2909
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
2910
|
+
function splitCaptionPages(text, fits) {
|
|
2911
|
+
const pages = [];
|
|
2912
|
+
let rest = Array.from(text);
|
|
2913
|
+
while (rest.length) {
|
|
2914
|
+
let low = 1;
|
|
2915
|
+
let high = rest.length;
|
|
2916
|
+
while (low < high) {
|
|
2917
|
+
const middle = Math.ceil((low + high) / 2);
|
|
2918
|
+
if (fits(rest.slice(0, middle).join(""))) low = middle;
|
|
2919
|
+
else high = middle - 1;
|
|
2920
|
+
}
|
|
2921
|
+
let end = low;
|
|
2922
|
+
if (end < rest.length) {
|
|
2923
|
+
const space = rest.slice(0, end + 1).lastIndexOf(" ");
|
|
2924
|
+
if (space > 0) end = space + 1;
|
|
2925
|
+
}
|
|
2926
|
+
pages.push(rest.slice(0, end).join(""));
|
|
2927
|
+
rest = rest.slice(end);
|
|
2928
|
+
}
|
|
2929
|
+
return pages;
|
|
2930
|
+
}
|
|
2931
|
+
function captionPageAt(pages, elapsed, duration) {
|
|
2932
|
+
const total = pages.reduce((sum, page) => sum + page.length, 0);
|
|
2933
|
+
const position = duration > 0 ? Math.max(0, elapsed / duration) * total : 0;
|
|
2934
|
+
let end = 0;
|
|
2935
|
+
for (let index = 0; index < pages.length - 1; index++) {
|
|
2936
|
+
end += pages[index].length;
|
|
2937
|
+
if (position < end) return index;
|
|
2938
|
+
}
|
|
2939
|
+
return Math.max(0, pages.length - 1);
|
|
2940
|
+
}
|
|
2941
|
+
function CaptionPages({ text, getProgress }) {
|
|
2942
|
+
const line = useRef4(null);
|
|
2943
|
+
const lastProgress = useRef4(void 0);
|
|
2944
|
+
const getter = useRef4(getProgress);
|
|
2945
|
+
getter.current = getProgress;
|
|
2946
|
+
const [source, setSource] = useState3(text);
|
|
2947
|
+
const [pages, setPages] = useState3([]);
|
|
2948
|
+
const [index, setIndex] = useState3(0);
|
|
2949
|
+
const [timing, setTiming] = useState3();
|
|
2950
|
+
useLayoutEffect(() => {
|
|
2951
|
+
const element = line.current;
|
|
2952
|
+
if (!element) return;
|
|
2953
|
+
const measure = () => {
|
|
2954
|
+
const width = element.getBoundingClientRect().width;
|
|
2955
|
+
if (!width) {
|
|
2956
|
+
setPages([source]);
|
|
2957
|
+
return;
|
|
2958
|
+
}
|
|
2959
|
+
const style = getComputedStyle(element);
|
|
2960
|
+
const probe = element.cloneNode(false);
|
|
2961
|
+
probe.removeAttribute("aria-live");
|
|
2962
|
+
probe.setAttribute("aria-hidden", "true");
|
|
2963
|
+
Object.assign(probe.style, { position: "absolute", visibility: "hidden", pointerEvents: "none", width: `${width}px`, height: "auto", maxHeight: "none", inset: "0 auto auto 0", animation: "none" });
|
|
2964
|
+
element.parentElement.append(probe);
|
|
2965
|
+
const height = parseFloat(style.lineHeight) * 2;
|
|
2966
|
+
const next = splitCaptionPages(source, (candidate) => {
|
|
2967
|
+
probe.textContent = candidate;
|
|
2968
|
+
return probe.getBoundingClientRect().height <= height + 0.5;
|
|
2969
|
+
});
|
|
2970
|
+
probe.remove();
|
|
2971
|
+
setPages(next);
|
|
2972
|
+
};
|
|
2973
|
+
measure();
|
|
2974
|
+
let previousWidth = element.getBoundingClientRect().width;
|
|
2975
|
+
let previousFont = getComputedStyle(element).font;
|
|
2976
|
+
const observer = typeof ResizeObserver === "undefined" ? void 0 : new ResizeObserver(() => {
|
|
2977
|
+
const width = element.getBoundingClientRect().width;
|
|
2978
|
+
const font = getComputedStyle(element).font;
|
|
2979
|
+
if (width !== previousWidth || font !== previousFont) {
|
|
2980
|
+
previousWidth = width;
|
|
2981
|
+
previousFont = font;
|
|
2982
|
+
measure();
|
|
2983
|
+
}
|
|
2984
|
+
});
|
|
2985
|
+
observer?.observe(element);
|
|
2986
|
+
document.fonts?.addEventListener("loadingdone", measure);
|
|
2987
|
+
return () => {
|
|
2988
|
+
observer?.disconnect();
|
|
2989
|
+
document.fonts?.removeEventListener("loadingdone", measure);
|
|
2990
|
+
};
|
|
2991
|
+
}, [source]);
|
|
2992
|
+
useEffect5(() => {
|
|
2993
|
+
let frame = 0;
|
|
2994
|
+
const update = () => {
|
|
2995
|
+
const observed = text ? getter.current() : void 0;
|
|
2996
|
+
if (observed) lastProgress.current = { caption: text, progress: observed };
|
|
2997
|
+
const progress = observed ?? (lastProgress.current?.caption === text ? lastProgress.current.progress : void 0);
|
|
2998
|
+
const nextSource = progress?.text ?? text;
|
|
2999
|
+
setSource((current) => current === nextSource ? current : nextSource);
|
|
3000
|
+
setTiming(progress?.timing);
|
|
3001
|
+
setIndex(nextSource !== source ? 0 : captionPageAt(pages, progress?.elapsedSeconds ?? 0, progress?.durationSeconds ?? 0));
|
|
3002
|
+
frame = requestAnimationFrame(update);
|
|
3003
|
+
};
|
|
3004
|
+
update();
|
|
3005
|
+
return () => cancelAnimationFrame(frame);
|
|
3006
|
+
}, [text, source, pages]);
|
|
3007
|
+
return /* @__PURE__ */ jsx3("p", { ref: line, className: "line", "aria-live": "polite", "data-caption-timing": timing, "data-caption-page": index, children: pages[index] ?? "" });
|
|
2822
3008
|
}
|
|
2823
3009
|
|
|
2824
3010
|
// src/video-chat/video-chat.tsx
|
|
2825
|
-
import { useCallback as useCallback6, useEffect as
|
|
3011
|
+
import { useCallback as useCallback6, useEffect as useEffect10, useId, useLayoutEffect as useLayoutEffect3, useMemo as useMemo3, useRef as useRef9, useState as useState8 } from "react";
|
|
2826
3012
|
|
|
2827
3013
|
// src/video-chat/icons.tsx
|
|
2828
|
-
import { jsx as
|
|
3014
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
2829
3015
|
var stroke = {
|
|
2830
3016
|
fill: "none",
|
|
2831
3017
|
stroke: "currentColor",
|
|
@@ -2834,50 +3020,50 @@ var stroke = {
|
|
|
2834
3020
|
strokeLinejoin: "round"
|
|
2835
3021
|
};
|
|
2836
3022
|
function Glyph({ children }) {
|
|
2837
|
-
return /* @__PURE__ */
|
|
3023
|
+
return /* @__PURE__ */ jsx4("svg", { viewBox: "0 0 24 24", width: "18", height: "18", "aria-hidden": "true", ...stroke, children });
|
|
2838
3024
|
}
|
|
2839
3025
|
var Sessions = () => /* @__PURE__ */ jsxs3(Glyph, { children: [
|
|
2840
|
-
/* @__PURE__ */
|
|
2841
|
-
/* @__PURE__ */
|
|
3026
|
+
/* @__PURE__ */ jsx4("path", { d: "M5 3h10a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2H8l-5 3V5a2 2 0 0 1 2-2Z" }),
|
|
3027
|
+
/* @__PURE__ */ jsx4("path", { d: "M17 8h2a2 2 0 0 1 2 2v11l-4-3h-6a2 2 0 0 1-2-2v-2" })
|
|
2842
3028
|
] });
|
|
2843
|
-
var Plus = () => /* @__PURE__ */
|
|
3029
|
+
var Plus = () => /* @__PURE__ */ jsx4(Glyph, { children: /* @__PURE__ */ jsx4("path", { d: "M12 5v14M5 12h14" }) });
|
|
2844
3030
|
var Gear = () => /* @__PURE__ */ jsxs3(Glyph, { children: [
|
|
2845
|
-
/* @__PURE__ */
|
|
2846
|
-
/* @__PURE__ */
|
|
3031
|
+
/* @__PURE__ */ jsx4("circle", { cx: "12", cy: "12", r: "3" }),
|
|
3032
|
+
/* @__PURE__ */ jsx4("path", { d: "M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.6a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1Z" })
|
|
2847
3033
|
] });
|
|
2848
3034
|
var Sound = () => /* @__PURE__ */ jsxs3(Glyph, { children: [
|
|
2849
|
-
/* @__PURE__ */
|
|
2850
|
-
/* @__PURE__ */
|
|
3035
|
+
/* @__PURE__ */ jsx4("path", { d: "M11 5 6 9H2v6h4l5 4z" }),
|
|
3036
|
+
/* @__PURE__ */ jsx4("path", { d: "M15.5 8.5a5 5 0 0 1 0 7M18.5 5.5a9 9 0 0 1 0 13" })
|
|
2851
3037
|
] });
|
|
2852
3038
|
var Muted = () => /* @__PURE__ */ jsxs3(Glyph, { children: [
|
|
2853
|
-
/* @__PURE__ */
|
|
2854
|
-
/* @__PURE__ */
|
|
3039
|
+
/* @__PURE__ */ jsx4("path", { d: "M11 5 6 9H2v6h4l5 4z" }),
|
|
3040
|
+
/* @__PURE__ */ jsx4("path", { d: "m22 9-6 6M16 9l6 6" })
|
|
2855
3041
|
] });
|
|
2856
3042
|
var Mic = () => /* @__PURE__ */ jsxs3(Glyph, { children: [
|
|
2857
|
-
/* @__PURE__ */
|
|
2858
|
-
/* @__PURE__ */
|
|
3043
|
+
/* @__PURE__ */ jsx4("rect", { x: "9", y: "2", width: "6", height: "12", rx: "3" }),
|
|
3044
|
+
/* @__PURE__ */ jsx4("path", { d: "M5 11a7 7 0 0 0 14 0M12 18v4" })
|
|
2859
3045
|
] });
|
|
2860
|
-
var Send = () => /* @__PURE__ */
|
|
3046
|
+
var Send = () => /* @__PURE__ */ jsx4(Glyph, { children: /* @__PURE__ */ jsx4("path", { d: "M12 19V5M5 12l7-7 7 7" }) });
|
|
2861
3047
|
var Stop = () => /* @__PURE__ */ jsxs3("svg", { viewBox: "0 0 24 24", width: "18", height: "18", "aria-hidden": "true", fill: "currentColor", children: [
|
|
2862
|
-
/* @__PURE__ */
|
|
2863
|
-
/* @__PURE__ */
|
|
3048
|
+
/* @__PURE__ */ jsx4("rect", { x: "7", y: "5", width: "3.4", height: "14", rx: "1.4" }),
|
|
3049
|
+
/* @__PURE__ */ jsx4("rect", { x: "13.6", y: "5", width: "3.4", height: "14", rx: "1.4" })
|
|
2864
3050
|
] });
|
|
2865
|
-
var Play = () => /* @__PURE__ */
|
|
3051
|
+
var Play = () => /* @__PURE__ */ jsx4("svg", { viewBox: "0 0 24 24", width: "18", height: "18", "aria-hidden": "true", fill: "currentColor", children: /* @__PURE__ */ jsx4("path", { d: "M8 5.5v13l11-6.5z" }) });
|
|
2866
3052
|
var Replay = () => /* @__PURE__ */ jsxs3(Glyph, { children: [
|
|
2867
|
-
/* @__PURE__ */
|
|
2868
|
-
/* @__PURE__ */
|
|
3053
|
+
/* @__PURE__ */ jsx4("path", { d: "M3 12a9 9 0 1 0 3-6.7L3 8" }),
|
|
3054
|
+
/* @__PURE__ */ jsx4("path", { d: "M3 3v5h5" })
|
|
2869
3055
|
] });
|
|
2870
|
-
var ChevronUp = () => /* @__PURE__ */
|
|
2871
|
-
var Close = () => /* @__PURE__ */
|
|
3056
|
+
var ChevronUp = () => /* @__PURE__ */ jsx4("svg", { viewBox: "0 0 24 24", width: "14", height: "14", "aria-hidden": "true", ...stroke, children: /* @__PURE__ */ jsx4("path", { d: "m6 15 6-6 6 6" }) });
|
|
3057
|
+
var Close = () => /* @__PURE__ */ jsx4(Glyph, { children: /* @__PURE__ */ jsx4("path", { d: "M6 6l12 12M18 6 6 18" }) });
|
|
2872
3058
|
var Warning = () => /* @__PURE__ */ jsxs3(Glyph, { children: [
|
|
2873
|
-
/* @__PURE__ */
|
|
2874
|
-
/* @__PURE__ */
|
|
3059
|
+
/* @__PURE__ */ jsx4("circle", { cx: "12", cy: "12", r: "9" }),
|
|
3060
|
+
/* @__PURE__ */ jsx4("path", { d: "M12 8v5M12 16.2v.1" })
|
|
2875
3061
|
] });
|
|
2876
3062
|
|
|
2877
3063
|
// src/video-chat/use-dismiss.ts
|
|
2878
|
-
import { useEffect as
|
|
3064
|
+
import { useEffect as useEffect6 } from "react";
|
|
2879
3065
|
function useDismiss(open, close, surfaces) {
|
|
2880
|
-
|
|
3066
|
+
useEffect6(() => {
|
|
2881
3067
|
if (!open) return;
|
|
2882
3068
|
const onKeyDown = (event) => {
|
|
2883
3069
|
if (event.key !== "Escape") return;
|
|
@@ -2899,7 +3085,7 @@ function useDismiss(open, close, surfaces) {
|
|
|
2899
3085
|
}, [open, close, surfaces]);
|
|
2900
3086
|
}
|
|
2901
3087
|
function useFocusTrap(active, surface) {
|
|
2902
|
-
|
|
3088
|
+
useEffect6(() => {
|
|
2903
3089
|
if (!active) return;
|
|
2904
3090
|
const returnTo = document.activeElement;
|
|
2905
3091
|
const focusable = () => Array.from(
|
|
@@ -2932,12 +3118,12 @@ function useFocusTrap(active, surface) {
|
|
|
2932
3118
|
}
|
|
2933
3119
|
|
|
2934
3120
|
// src/video-chat/suggestion-cards.tsx
|
|
2935
|
-
import { useCallback as useCallback3, useEffect as
|
|
2936
|
-
import { Fragment as Fragment2, jsx as
|
|
3121
|
+
import { useCallback as useCallback3, useEffect as useEffect7, useRef as useRef5, useState as useState4 } from "react";
|
|
3122
|
+
import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
2937
3123
|
function Frame({ media, poster, playing, onReady, onError, revealWhenReady = false }) {
|
|
2938
|
-
const video =
|
|
2939
|
-
const [playingUrl, setPlayingUrl] =
|
|
2940
|
-
|
|
3124
|
+
const video = useRef5(null);
|
|
3125
|
+
const [playingUrl, setPlayingUrl] = useState4(null);
|
|
3126
|
+
useEffect7(() => {
|
|
2941
3127
|
const element = video.current;
|
|
2942
3128
|
if (!element || playing === void 0) return;
|
|
2943
3129
|
if (playing) void element.play().catch(() => void 0);
|
|
@@ -2949,9 +3135,9 @@ function Frame({ media, poster, playing, onReady, onError, revealWhenReady = fal
|
|
|
2949
3135
|
onReady?.();
|
|
2950
3136
|
};
|
|
2951
3137
|
const appearance = revealWhenReady ? { opacity: playingUrl === media.url ? 1 : 0, transition: "opacity 200ms ease" } : void 0;
|
|
2952
|
-
if (media.type === "image") return /* @__PURE__ */
|
|
3138
|
+
if (media.type === "image") return /* @__PURE__ */ jsx5("img", { className: "frame-media", src: media.url, alt: "", style: appearance, onLoad: ready, onError });
|
|
2953
3139
|
return /* @__PURE__ */ jsxs4(Fragment2, { children: [
|
|
2954
|
-
/* @__PURE__ */
|
|
3140
|
+
/* @__PURE__ */ jsx5(
|
|
2955
3141
|
"video",
|
|
2956
3142
|
{
|
|
2957
3143
|
ref: video,
|
|
@@ -2972,24 +3158,24 @@ function Frame({ media, poster, playing, onReady, onError, revealWhenReady = fal
|
|
|
2972
3158
|
"aria-hidden": "true"
|
|
2973
3159
|
}
|
|
2974
3160
|
),
|
|
2975
|
-
poster && media.posterUrl && playingUrl !== media.url && /* @__PURE__ */
|
|
3161
|
+
poster && media.posterUrl && playingUrl !== media.url && /* @__PURE__ */ jsx5("img", { className: "frame-media frame-poster", src: media.posterUrl, alt: "", onLoad: onReady })
|
|
2976
3162
|
] });
|
|
2977
3163
|
}
|
|
2978
3164
|
function SuggestionCards({ suggestions, label, onAsk }) {
|
|
2979
|
-
const railRef =
|
|
2980
|
-
const [at, setAt] =
|
|
2981
|
-
const [taken, setTaken] =
|
|
3165
|
+
const railRef = useRef5(null);
|
|
3166
|
+
const [at, setAt] = useState4(0);
|
|
3167
|
+
const [taken, setTaken] = useState4(false);
|
|
2982
3168
|
const take = useCallback3((index) => {
|
|
2983
3169
|
setTaken(true);
|
|
2984
3170
|
setAt(index);
|
|
2985
3171
|
}, []);
|
|
2986
|
-
|
|
3172
|
+
useEffect7(() => {
|
|
2987
3173
|
if (taken || suggestions.length < 2) return;
|
|
2988
3174
|
if (window.matchMedia?.("(prefers-reduced-motion: reduce)").matches) return;
|
|
2989
3175
|
const tour = window.setInterval(() => setAt((index) => (index + 1) % suggestions.length), 5e3);
|
|
2990
3176
|
return () => window.clearInterval(tour);
|
|
2991
3177
|
}, [taken, suggestions.length]);
|
|
2992
|
-
|
|
3178
|
+
useEffect7(() => {
|
|
2993
3179
|
const rail = railRef.current;
|
|
2994
3180
|
const card = rail?.children[at];
|
|
2995
3181
|
if (!rail || !card) return;
|
|
@@ -2999,7 +3185,7 @@ function SuggestionCards({ suggestions, label, onAsk }) {
|
|
|
2999
3185
|
}, [at]);
|
|
3000
3186
|
if (suggestions.length === 0) return null;
|
|
3001
3187
|
return /* @__PURE__ */ jsxs4(Fragment2, { children: [
|
|
3002
|
-
/* @__PURE__ */
|
|
3188
|
+
/* @__PURE__ */ jsx5("ul", { className: "cards", ref: railRef, "aria-label": label, children: suggestions.map((card, index) => /* @__PURE__ */ jsx5("li", { children: /* @__PURE__ */ jsxs4(
|
|
3003
3189
|
"button",
|
|
3004
3190
|
{
|
|
3005
3191
|
type: "button",
|
|
@@ -3008,13 +3194,13 @@ function SuggestionCards({ suggestions, label, onAsk }) {
|
|
|
3008
3194
|
onPointerEnter: () => take(index),
|
|
3009
3195
|
onClick: () => onAsk(card),
|
|
3010
3196
|
children: [
|
|
3011
|
-
/* @__PURE__ */
|
|
3012
|
-
/* @__PURE__ */
|
|
3013
|
-
/* @__PURE__ */
|
|
3197
|
+
/* @__PURE__ */ jsx5(Frame, { media: card.media, poster: true, playing: index === at }),
|
|
3198
|
+
/* @__PURE__ */ jsx5("span", { className: "card-wash", "aria-hidden": "true" }),
|
|
3199
|
+
/* @__PURE__ */ jsx5("span", { className: "card-prompt", children: card.prompt })
|
|
3014
3200
|
]
|
|
3015
3201
|
}
|
|
3016
3202
|
) }, card.prompt)) }),
|
|
3017
|
-
suggestions.length > 1 && /* @__PURE__ */
|
|
3203
|
+
suggestions.length > 1 && /* @__PURE__ */ jsx5("div", { className: "card-dots", role: "group", "aria-label": `${label} navigation`, children: suggestions.map((card, index) => /* @__PURE__ */ jsx5(
|
|
3018
3204
|
"button",
|
|
3019
3205
|
{
|
|
3020
3206
|
type: "button",
|
|
@@ -3029,57 +3215,68 @@ function SuggestionCards({ suggestions, label, onAsk }) {
|
|
|
3029
3215
|
}
|
|
3030
3216
|
|
|
3031
3217
|
// src/video-chat/welcome.tsx
|
|
3032
|
-
import { Fragment as Fragment3, jsx as
|
|
3218
|
+
import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
3033
3219
|
function Welcome({ data, onAsk, title }) {
|
|
3034
3220
|
return /* @__PURE__ */ jsxs5("div", { className: "welcome", children: [
|
|
3035
|
-
/* @__PURE__ */
|
|
3036
|
-
/* @__PURE__ */
|
|
3221
|
+
/* @__PURE__ */ jsx6(Frame, { media: data?.hero ?? null, poster: true }),
|
|
3222
|
+
/* @__PURE__ */ jsx6("div", { className: "welcome-wash", "aria-hidden": "true" }),
|
|
3037
3223
|
/* @__PURE__ */ jsxs5("div", { className: "welcome-body", children: [
|
|
3038
|
-
/* @__PURE__ */
|
|
3224
|
+
/* @__PURE__ */ jsx6("h1", { className: "welcome-title", children: title ?? /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
3039
3225
|
"An AI chat that responds",
|
|
3040
|
-
/* @__PURE__ */
|
|
3041
|
-
/* @__PURE__ */
|
|
3226
|
+
/* @__PURE__ */ jsx6("br", {}),
|
|
3227
|
+
/* @__PURE__ */ jsx6("em", { children: "in video, not text." })
|
|
3042
3228
|
] }) }),
|
|
3043
|
-
/* @__PURE__ */
|
|
3229
|
+
/* @__PURE__ */ jsx6(SuggestionCards, { suggestions: data?.cards ?? [], label: "Suggested prompts", onAsk })
|
|
3044
3230
|
] })
|
|
3045
3231
|
] });
|
|
3046
3232
|
}
|
|
3047
3233
|
|
|
3048
3234
|
// src/video-chat/opening-chapter.tsx
|
|
3049
|
-
import { useLayoutEffect, useRef as
|
|
3050
|
-
import { jsx as
|
|
3051
|
-
function OpeningChapter({ title }) {
|
|
3052
|
-
const root =
|
|
3053
|
-
const [size, setSize] =
|
|
3054
|
-
|
|
3235
|
+
import { useLayoutEffect as useLayoutEffect2, useRef as useRef6, useState as useState5 } from "react";
|
|
3236
|
+
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
3237
|
+
function OpeningChapter({ title, preparing = false }) {
|
|
3238
|
+
const root = useRef6(null);
|
|
3239
|
+
const [size, setSize] = useState5({ width: 1080, height: 1080 });
|
|
3240
|
+
const [titleBottom, setTitleBottom] = useState5(0);
|
|
3241
|
+
useLayoutEffect2(() => {
|
|
3055
3242
|
const element = root.current;
|
|
3056
3243
|
if (!element) return;
|
|
3057
3244
|
const measure = () => {
|
|
3058
3245
|
const { width, height } = element.getBoundingClientRect();
|
|
3059
3246
|
if (width > 0 && height > 0) setSize({ width, height });
|
|
3247
|
+
const title3 = element.querySelector('[data-title-composition="centered"]');
|
|
3248
|
+
if (title3) setTitleBottom(title3.getBoundingClientRect().bottom - element.getBoundingClientRect().top);
|
|
3060
3249
|
};
|
|
3061
3250
|
measure();
|
|
3062
3251
|
if (typeof ResizeObserver === "undefined") return;
|
|
3063
3252
|
const observer = new ResizeObserver(measure);
|
|
3064
3253
|
observer.observe(element);
|
|
3254
|
+
const title2 = element.querySelector('[data-title-composition="centered"]');
|
|
3255
|
+
if (title2) observer.observe(title2);
|
|
3065
3256
|
return () => observer.disconnect();
|
|
3066
|
-
}, []);
|
|
3067
|
-
return /* @__PURE__ */
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3257
|
+
}, [title]);
|
|
3258
|
+
return /* @__PURE__ */ jsxs6("div", { ref: root, "data-opening-chapter": true, className: "opening-chapter", children: [
|
|
3259
|
+
/* @__PURE__ */ jsx7(
|
|
3260
|
+
TitleSceneTemplate,
|
|
3261
|
+
{
|
|
3262
|
+
variables: { title },
|
|
3263
|
+
style: {},
|
|
3264
|
+
width: size.width,
|
|
3265
|
+
height: size.height,
|
|
3266
|
+
progress: 0.3,
|
|
3267
|
+
beatIntensity: 0,
|
|
3268
|
+
safeZone: { top: 0, right: 0, bottom: 0, left: 0 }
|
|
3269
|
+
}
|
|
3270
|
+
),
|
|
3271
|
+
preparing && /* @__PURE__ */ jsxs6("div", { className: "video-preparation", style: { top: titleBottom + 24 }, role: "status", "aria-label": "Video preparation", children: [
|
|
3272
|
+
/* @__PURE__ */ jsx7("span", { className: "video-preparation-spinner", "aria-hidden": "true" }),
|
|
3273
|
+
/* @__PURE__ */ jsx7("span", { className: "video-preparation-text", children: "Preparing your video\u2026" })
|
|
3274
|
+
] })
|
|
3275
|
+
] });
|
|
3079
3276
|
}
|
|
3080
3277
|
|
|
3081
3278
|
// src/video-chat/use-voice-input.ts
|
|
3082
|
-
import { useCallback as useCallback4, useEffect as
|
|
3279
|
+
import { useCallback as useCallback4, useEffect as useEffect8, useRef as useRef7, useState as useState6 } from "react";
|
|
3083
3280
|
function recognitionConstructor() {
|
|
3084
3281
|
if (typeof window === "undefined") return void 0;
|
|
3085
3282
|
const holder = window;
|
|
@@ -3149,16 +3346,16 @@ async function recordAndTranscribe(signal, ready, captured, options) {
|
|
|
3149
3346
|
return clip ? transcribeRecording(clip, signal, options) : "";
|
|
3150
3347
|
}
|
|
3151
3348
|
function useVoiceInput(onTranscript, transcriptionAvailable = false, options = {}) {
|
|
3152
|
-
const [supported, setSupported] =
|
|
3153
|
-
const [listening, setListening] =
|
|
3154
|
-
const [error, setError] =
|
|
3155
|
-
const [thinking, setThinking] =
|
|
3156
|
-
const recognitionRef =
|
|
3157
|
-
const useRecorderRef =
|
|
3158
|
-
const operationRef =
|
|
3159
|
-
const handlerRef =
|
|
3349
|
+
const [supported, setSupported] = useState6(false);
|
|
3350
|
+
const [listening, setListening] = useState6(false);
|
|
3351
|
+
const [error, setError] = useState6();
|
|
3352
|
+
const [thinking, setThinking] = useState6(false);
|
|
3353
|
+
const recognitionRef = useRef7(void 0);
|
|
3354
|
+
const useRecorderRef = useRef7(false);
|
|
3355
|
+
const operationRef = useRef7(void 0);
|
|
3356
|
+
const handlerRef = useRef7(onTranscript);
|
|
3160
3357
|
handlerRef.current = onTranscript;
|
|
3161
|
-
|
|
3358
|
+
useEffect8(() => {
|
|
3162
3359
|
setSupported(supportsVoiceInput(transcriptionAvailable));
|
|
3163
3360
|
}, [transcriptionAvailable]);
|
|
3164
3361
|
const stop = useCallback4(() => {
|
|
@@ -3171,7 +3368,7 @@ function useVoiceInput(onTranscript, transcriptionAvailable = false, options = {
|
|
|
3171
3368
|
setListening(false);
|
|
3172
3369
|
setThinking(false);
|
|
3173
3370
|
}, []);
|
|
3174
|
-
|
|
3371
|
+
useEffect8(() => () => {
|
|
3175
3372
|
recognitionRef.current?.abort();
|
|
3176
3373
|
recognitionRef.current = void 0;
|
|
3177
3374
|
const operation = operationRef.current;
|
|
@@ -3283,13 +3480,13 @@ function useVoiceInput(onTranscript, transcriptionAvailable = false, options = {
|
|
|
3283
3480
|
}
|
|
3284
3481
|
|
|
3285
3482
|
// src/video-chat/use-immersive-controls.ts
|
|
3286
|
-
import { useCallback as useCallback5, useEffect as
|
|
3483
|
+
import { useCallback as useCallback5, useEffect as useEffect9, useRef as useRef8, useState as useState7 } from "react";
|
|
3287
3484
|
function useImmersiveControls(playing, pinned, hasCaptions = false) {
|
|
3288
|
-
const [visible, setVisible] =
|
|
3289
|
-
const hovered =
|
|
3290
|
-
const focused =
|
|
3291
|
-
const previousCaptions =
|
|
3292
|
-
const timer =
|
|
3485
|
+
const [visible, setVisible] = useState7(true);
|
|
3486
|
+
const hovered = useRef8(false);
|
|
3487
|
+
const focused = useRef8(false);
|
|
3488
|
+
const previousCaptions = useRef8(false);
|
|
3489
|
+
const timer = useRef8(null);
|
|
3293
3490
|
const clearTimer = useCallback5(() => {
|
|
3294
3491
|
if (timer.current !== null) clearTimeout(timer.current);
|
|
3295
3492
|
timer.current = null;
|
|
@@ -3304,7 +3501,7 @@ function useImmersiveControls(playing, pinned, hasCaptions = false) {
|
|
|
3304
3501
|
}, 2e3);
|
|
3305
3502
|
}
|
|
3306
3503
|
}, [clearTimer, playing, pinned]);
|
|
3307
|
-
|
|
3504
|
+
useEffect9(() => {
|
|
3308
3505
|
const firstCaption = hasCaptions && !previousCaptions.current;
|
|
3309
3506
|
previousCaptions.current = hasCaptions;
|
|
3310
3507
|
if (firstCaption && playing && !pinned && !focused.current) {
|
|
@@ -3340,9 +3537,9 @@ function useImmersiveControls(playing, pinned, hasCaptions = false) {
|
|
|
3340
3537
|
}
|
|
3341
3538
|
|
|
3342
3539
|
// src/video-chat/logo.tsx
|
|
3343
|
-
import { jsx as
|
|
3540
|
+
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
3344
3541
|
function Logo() {
|
|
3345
|
-
return /* @__PURE__ */
|
|
3542
|
+
return /* @__PURE__ */ jsx8("img", { className: "brand-logo", src: new URL("./assets/vanillasky-logo.svg", import.meta.url).href, alt: "VanillaSky", width: 144, height: 36 });
|
|
3346
3543
|
}
|
|
3347
3544
|
|
|
3348
3545
|
// src/video-chat/modes.ts
|
|
@@ -3353,11 +3550,11 @@ var visualModes = [
|
|
|
3353
3550
|
var defaultMode = visualModes[0];
|
|
3354
3551
|
|
|
3355
3552
|
// src/video-chat/video-chat.tsx
|
|
3356
|
-
import { Fragment as Fragment4, jsx as
|
|
3553
|
+
import { Fragment as Fragment4, jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
3357
3554
|
var DESKTOP_WIDTH = 900;
|
|
3358
3555
|
function useViewportOrientation() {
|
|
3359
|
-
const [portrait, setPortrait] =
|
|
3360
|
-
|
|
3556
|
+
const [portrait, setPortrait] = useState8(() => typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia(`(max-width: ${DESKTOP_WIDTH - 1}px) and (orientation: portrait)`).matches : false);
|
|
3557
|
+
useEffect10(() => {
|
|
3361
3558
|
if (typeof window.matchMedia !== "function") return;
|
|
3362
3559
|
const query = window.matchMedia(`(max-width: ${DESKTOP_WIDTH - 1}px) and (orientation: portrait)`);
|
|
3363
3560
|
const update = () => setPortrait(query.matches);
|
|
@@ -3368,47 +3565,59 @@ function useViewportOrientation() {
|
|
|
3368
3565
|
return portrait ? "portrait" : "landscape";
|
|
3369
3566
|
}
|
|
3370
3567
|
function Waveform({ active, listening }) {
|
|
3371
|
-
return /* @__PURE__ */
|
|
3568
|
+
return /* @__PURE__ */ jsx9("span", { className: `waveform${active ? " on" : ""}${listening ? " hearing" : ""}`, "aria-hidden": "true", children: [0, 1, 2, 3, 4].map((bar) => /* @__PURE__ */ jsx9("span", { style: { animationDelay: `${bar * 140}ms`, animationDuration: `${900 + bar * 130}ms` } }, bar)) });
|
|
3372
3569
|
}
|
|
3373
3570
|
function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice = false }) {
|
|
3374
|
-
const [dismissedNoticeTurn, setDismissedNoticeTurn] =
|
|
3375
|
-
const [draft, setDraft] =
|
|
3376
|
-
const [selectedMode, setSelectedMode] =
|
|
3377
|
-
const [savedSessions, setSavedSessions] =
|
|
3378
|
-
const [historyOpen, setHistoryOpen] =
|
|
3379
|
-
const [settingsOpen, setSettingsOpen] =
|
|
3380
|
-
const [aboutOpen, setAboutOpen] =
|
|
3381
|
-
const [captionsOn, setCaptionsOn] =
|
|
3382
|
-
const [captionsExpanded, setCaptionsExpanded] =
|
|
3383
|
-
const [alwaysShowControls, setAlwaysShowControls] =
|
|
3384
|
-
const [editing, setEditing] =
|
|
3385
|
-
const resumeAfterInput =
|
|
3386
|
-
const panelRef =
|
|
3387
|
-
const composerRef =
|
|
3571
|
+
const [dismissedNoticeTurn, setDismissedNoticeTurn] = useState8();
|
|
3572
|
+
const [draft, setDraft] = useState8("");
|
|
3573
|
+
const [selectedMode, setSelectedMode] = useState8();
|
|
3574
|
+
const [savedSessions, setSavedSessions] = useState8([]);
|
|
3575
|
+
const [historyOpen, setHistoryOpen] = useState8(false);
|
|
3576
|
+
const [settingsOpen, setSettingsOpen] = useState8(false);
|
|
3577
|
+
const [aboutOpen, setAboutOpen] = useState8(false);
|
|
3578
|
+
const [captionsOn, setCaptionsOn] = useState8(true);
|
|
3579
|
+
const [captionsExpanded, setCaptionsExpanded] = useState8(false);
|
|
3580
|
+
const [alwaysShowControls, setAlwaysShowControls] = useState8(false);
|
|
3581
|
+
const [editing, setEditing] = useState8(false);
|
|
3582
|
+
const resumeAfterInput = useRef9(false);
|
|
3583
|
+
const panelRef = useRef9(null);
|
|
3584
|
+
const composerRef = useRef9(null);
|
|
3388
3585
|
const viewportOrientation = useViewportOrientation();
|
|
3389
3586
|
const sessionOrientation = options.orientation ?? viewportOrientation;
|
|
3390
|
-
const { chat, restoreSession } = useVideoChatSession({
|
|
3587
|
+
const { chat, restoreSession, getCaptionProgress } = useVideoChatSession({
|
|
3391
3588
|
...options,
|
|
3392
3589
|
orientation: sessionOrientation,
|
|
3393
3590
|
mode: selectedMode ?? options.mode
|
|
3394
3591
|
});
|
|
3592
|
+
const observedMode = useRef9(void 0);
|
|
3593
|
+
useEffect10(() => {
|
|
3594
|
+
const turn = chat.currentTurn;
|
|
3595
|
+
if (!turn?.mode) {
|
|
3596
|
+
observedMode.current = void 0;
|
|
3597
|
+
return;
|
|
3598
|
+
}
|
|
3599
|
+
const previous = observedMode.current;
|
|
3600
|
+
observedMode.current = { id: turn.id, mode: turn.mode };
|
|
3601
|
+
if (turn.id !== chat.shownTurn?.id) return;
|
|
3602
|
+
if (previous?.id !== turn.id || previous.mode !== turn.mode) setSelectedMode(turn.mode);
|
|
3603
|
+
}, [chat.currentTurn, chat.shownTurn?.id]);
|
|
3395
3604
|
const instanceId = useId();
|
|
3396
3605
|
const historyId = `${instanceId}-history`;
|
|
3397
3606
|
const settingsId = `${instanceId}-settings`;
|
|
3398
3607
|
const promptId = `${instanceId}-prompt`;
|
|
3399
|
-
const inputRef =
|
|
3400
|
-
const historyRef =
|
|
3401
|
-
const historyButtonRef =
|
|
3402
|
-
const settingsRef =
|
|
3403
|
-
const settingsButtonRef =
|
|
3608
|
+
const inputRef = useRef9(null);
|
|
3609
|
+
const historyRef = useRef9(null);
|
|
3610
|
+
const historyButtonRef = useRef9(null);
|
|
3611
|
+
const settingsRef = useRef9(null);
|
|
3612
|
+
const settingsButtonRef = useRef9(null);
|
|
3404
3613
|
const listen = useVoiceInput(setDraft, chat.capabilities?.transcription ?? false, {
|
|
3405
3614
|
endpoint: options.endpoint,
|
|
3406
3615
|
headers: options.headers,
|
|
3407
3616
|
credentials: options.credentials,
|
|
3408
3617
|
fetcher: options.fetcher
|
|
3409
3618
|
});
|
|
3410
|
-
const historySurfaces =
|
|
3411
|
-
const settingsSurfaces =
|
|
3619
|
+
const historySurfaces = useMemo3(() => [historyRef, historyButtonRef], []);
|
|
3620
|
+
const settingsSurfaces = useMemo3(() => [settingsRef, settingsButtonRef], []);
|
|
3412
3621
|
const closeHistory = useCallback6(() => setHistoryOpen(false), []);
|
|
3413
3622
|
const closeSettings = useCallback6(() => setSettingsOpen(false), []);
|
|
3414
3623
|
useDismiss(historyOpen, closeHistory, historySurfaces);
|
|
@@ -3452,12 +3661,19 @@ function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice =
|
|
|
3452
3661
|
const shown = chat.shownTurn;
|
|
3453
3662
|
const showing = chat.playerProps != null;
|
|
3454
3663
|
const handoffKey = `${shown?.id ?? ""}:${chat.playerKey}`;
|
|
3455
|
-
const [presentedBody, setPresentedBody] =
|
|
3456
|
-
const handoff =
|
|
3664
|
+
const [presentedBody, setPresentedBody] = useState8();
|
|
3665
|
+
const handoff = useRef9({ key: handoffKey, live: showing, active: false, frame: 0 });
|
|
3457
3666
|
const handoffStopped = chat.status === "error" || chat.status === "cancelled";
|
|
3458
3667
|
const waitingForBody = showing && presentedBody !== handoffKey;
|
|
3459
3668
|
const openingChapter = Boolean(shown?.prompt) && (!showing || waitingForBody) && chat.status !== "error" && chat.status !== "cancelled" && chat.status !== "ended";
|
|
3460
|
-
|
|
3669
|
+
const [preparingKey, setPreparingKey] = useState8();
|
|
3670
|
+
useEffect10(() => {
|
|
3671
|
+
setPreparingKey(void 0);
|
|
3672
|
+
if (!openingChapter || !shown?.opening || chat.speaking) return;
|
|
3673
|
+
const timer = setTimeout(() => setPreparingKey(shown.id), 1e3);
|
|
3674
|
+
return () => clearTimeout(timer);
|
|
3675
|
+
}, [openingChapter, shown?.opening, shown?.id, chat.speaking]);
|
|
3676
|
+
useLayoutEffect3(() => {
|
|
3461
3677
|
const current = { key: handoffKey, live: showing && !handoffStopped, active: openingChapter && showing, frame: 0 };
|
|
3462
3678
|
handoff.current = current;
|
|
3463
3679
|
return () => {
|
|
@@ -3490,9 +3706,12 @@ function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice =
|
|
|
3490
3706
|
const status = chat.turns.length === 0 ? "idle" : chat.status === "composing" ? "drawing" : chat.status === "playing" ? "narrating" : chat.status === "error" || chat.status === "cancelled" ? "ended" : chat.status;
|
|
3491
3707
|
const shownOrientation = shown?.orientation ?? sessionOrientation;
|
|
3492
3708
|
const stageOrientation = shown?.fixedOrientation ? shownOrientation : sessionOrientation;
|
|
3709
|
+
useEffect10(() => {
|
|
3710
|
+
setCaptionsExpanded(false);
|
|
3711
|
+
}, [chat.playbackEnded, shown?.id, chat.playerKey]);
|
|
3493
3712
|
const line = chat.caption ?? "";
|
|
3494
3713
|
const fullTranscript = shown?.video ? [shown.opening, ...shown.video.scenes.map((scene) => scene.narration)].filter((entry) => Boolean(entry)) : chat.transcript;
|
|
3495
|
-
const transport = status === "narrating" ? { label: "Pause", action: chat.pause, icon: /* @__PURE__ */
|
|
3714
|
+
const transport = status === "narrating" ? { label: "Pause", action: chat.pause, icon: /* @__PURE__ */ jsx9(Stop, {}) } : status === "paused" ? { label: "Continue", action: chat.resume, icon: /* @__PURE__ */ jsx9(Play, {}) } : status === "ended" && shown?.completed && shown.video ? { label: "Play again", action: chat.replay, icon: /* @__PURE__ */ jsx9(Replay, {}) } : void 0;
|
|
3496
3715
|
const cancelInput = useCallback6(() => {
|
|
3497
3716
|
listen.stop();
|
|
3498
3717
|
setDraft("");
|
|
@@ -3523,7 +3742,7 @@ function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice =
|
|
|
3523
3742
|
onFocusCapture: controls.onFocusCapture,
|
|
3524
3743
|
onBlurCapture: controls.onBlurCapture
|
|
3525
3744
|
};
|
|
3526
|
-
|
|
3745
|
+
useLayoutEffect3(() => {
|
|
3527
3746
|
const composer = composerRef.current;
|
|
3528
3747
|
if (!composer) return;
|
|
3529
3748
|
const measure = () => panelRef.current?.style.setProperty("--composer-height", `${composer.getBoundingClientRect().height}px`);
|
|
@@ -3533,7 +3752,7 @@ function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice =
|
|
|
3533
3752
|
observer.observe(composer);
|
|
3534
3753
|
return () => observer.disconnect();
|
|
3535
3754
|
}, []);
|
|
3536
|
-
return /* @__PURE__ */
|
|
3755
|
+
return /* @__PURE__ */ jsxs7(
|
|
3537
3756
|
"div",
|
|
3538
3757
|
{
|
|
3539
3758
|
className: `vanillasky-video-chat${className ? ` ${className}` : ""}`,
|
|
@@ -3545,13 +3764,10 @@ function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice =
|
|
|
3545
3764
|
onPointerDown: controls.reveal,
|
|
3546
3765
|
onKeyDownCapture: controls.reveal,
|
|
3547
3766
|
children: [
|
|
3548
|
-
/* @__PURE__ */
|
|
3549
|
-
/* @__PURE__ */
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
] }),
|
|
3553
|
-
/* @__PURE__ */ jsxs6("div", { className: "group", children: [
|
|
3554
|
-
/* @__PURE__ */ jsxs6(
|
|
3767
|
+
/* @__PURE__ */ jsxs7("header", { className: "chrome", ...controlEvents, children: [
|
|
3768
|
+
/* @__PURE__ */ jsx9("div", { className: "session-brand", children: /* @__PURE__ */ jsx9("a", { className: "home-link", href: "/", "aria-label": "Home", children: /* @__PURE__ */ jsx9(Logo, {}) }) }),
|
|
3769
|
+
/* @__PURE__ */ jsxs7("div", { className: "group", children: [
|
|
3770
|
+
/* @__PURE__ */ jsxs7(
|
|
3555
3771
|
"button",
|
|
3556
3772
|
{
|
|
3557
3773
|
ref: historyButtonRef,
|
|
@@ -3566,12 +3782,12 @@ function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice =
|
|
|
3566
3782
|
setSettingsOpen(false);
|
|
3567
3783
|
},
|
|
3568
3784
|
children: [
|
|
3569
|
-
/* @__PURE__ */
|
|
3570
|
-
/* @__PURE__ */
|
|
3785
|
+
/* @__PURE__ */ jsx9(Sessions, {}),
|
|
3786
|
+
/* @__PURE__ */ jsx9("span", { className: "nav-label", children: "Sessions" })
|
|
3571
3787
|
]
|
|
3572
3788
|
}
|
|
3573
3789
|
),
|
|
3574
|
-
/* @__PURE__ */
|
|
3790
|
+
/* @__PURE__ */ jsx9(
|
|
3575
3791
|
"button",
|
|
3576
3792
|
{
|
|
3577
3793
|
ref: settingsButtonRef,
|
|
@@ -3585,10 +3801,10 @@ function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice =
|
|
|
3585
3801
|
setSettingsOpen((open) => !open);
|
|
3586
3802
|
setHistoryOpen(false);
|
|
3587
3803
|
},
|
|
3588
|
-
children: /* @__PURE__ */
|
|
3804
|
+
children: /* @__PURE__ */ jsx9(Gear, {})
|
|
3589
3805
|
}
|
|
3590
3806
|
),
|
|
3591
|
-
/* @__PURE__ */
|
|
3807
|
+
/* @__PURE__ */ jsx9(
|
|
3592
3808
|
"button",
|
|
3593
3809
|
{
|
|
3594
3810
|
type: "button",
|
|
@@ -3596,16 +3812,16 @@ function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice =
|
|
|
3596
3812
|
"aria-label": chat.muted ? "Turn the voice on" : "Turn the voice off",
|
|
3597
3813
|
"aria-pressed": chat.muted,
|
|
3598
3814
|
onClick: () => chat.setMuted(!chat.muted),
|
|
3599
|
-
children: chat.muted ? /* @__PURE__ */
|
|
3815
|
+
children: chat.muted ? /* @__PURE__ */ jsx9(Muted, {}) : /* @__PURE__ */ jsx9(Sound, {})
|
|
3600
3816
|
}
|
|
3601
3817
|
)
|
|
3602
3818
|
] })
|
|
3603
3819
|
] }),
|
|
3604
|
-
/* @__PURE__ */
|
|
3605
|
-
/* @__PURE__ */
|
|
3606
|
-
openingChapter && /* @__PURE__ */
|
|
3607
|
-
!showing && chat.turns.length === 0 && /* @__PURE__ */
|
|
3608
|
-
chat.playerProps && /* @__PURE__ */
|
|
3820
|
+
/* @__PURE__ */ jsxs7("div", { className: "stage-area", children: [
|
|
3821
|
+
/* @__PURE__ */ jsxs7("div", { className: "stage", style: { background: "#000" }, children: [
|
|
3822
|
+
openingChapter && /* @__PURE__ */ jsx9(OpeningChapter, { preparing: preparingKey === shown.id && !chat.speaking, title: openingTitle.length > 120 ? `${openingTitle.slice(0, 117).trimEnd()}\u2026` : openingTitle }, shown.id),
|
|
3823
|
+
!showing && chat.turns.length === 0 && /* @__PURE__ */ jsx9(Welcome, { data: chat.welcome, onAsk: ask, title: welcomeTitle }),
|
|
3824
|
+
chat.playerProps && /* @__PURE__ */ jsx9("div", { className: "player-fit", style: { width: stageOrientation === "portrait" ? "min(100cqw, 56.25cqh)" : "min(100cqw, 177.7778cqh)" }, children: /* @__PURE__ */ jsx9(
|
|
3609
3825
|
VideoPlayer,
|
|
3610
3826
|
{
|
|
3611
3827
|
...chat.playerProps,
|
|
@@ -3617,29 +3833,29 @@ function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice =
|
|
|
3617
3833
|
},
|
|
3618
3834
|
chat.playerKey
|
|
3619
3835
|
) }),
|
|
3620
|
-
showing && chat.playbackEnded && chat.suggestions.length > 0 && /* @__PURE__ */
|
|
3621
|
-
/* @__PURE__ */
|
|
3622
|
-
/* @__PURE__ */
|
|
3623
|
-
/* @__PURE__ */
|
|
3624
|
-
/* @__PURE__ */
|
|
3836
|
+
showing && chat.playbackEnded && chat.suggestions.length > 0 && /* @__PURE__ */ jsxs7("div", { className: "ending", children: [
|
|
3837
|
+
/* @__PURE__ */ jsx9("div", { className: "ending-wash", "aria-hidden": "true" }),
|
|
3838
|
+
/* @__PURE__ */ jsxs7("div", { className: "ending-body", children: [
|
|
3839
|
+
/* @__PURE__ */ jsx9("p", { className: "ending-label", children: "Ask next" }),
|
|
3840
|
+
/* @__PURE__ */ jsx9(SuggestionCards, { suggestions: [...chat.suggestions], label: "Follow-up prompts", onAsk: ask })
|
|
3625
3841
|
] })
|
|
3626
3842
|
] })
|
|
3627
3843
|
] }),
|
|
3628
|
-
historyOpen && /* @__PURE__ */
|
|
3629
|
-
/* @__PURE__ */
|
|
3630
|
-
/* @__PURE__ */
|
|
3631
|
-
/* @__PURE__ */
|
|
3844
|
+
historyOpen && /* @__PURE__ */ jsxs7("nav", { ref: historyRef, id: historyId, className: "sheet-popover history", role: "dialog", "aria-modal": "true", "aria-label": "Sessions", children: [
|
|
3845
|
+
/* @__PURE__ */ jsxs7("div", { className: "popover-heading", children: [
|
|
3846
|
+
/* @__PURE__ */ jsx9("h2", { children: "Sessions" }),
|
|
3847
|
+
/* @__PURE__ */ jsx9("button", { type: "button", className: "round", "aria-label": "Close sessions", onClick: closeHistory, children: /* @__PURE__ */ jsx9(Close, {}) })
|
|
3632
3848
|
] }),
|
|
3633
|
-
/* @__PURE__ */
|
|
3634
|
-
/* @__PURE__ */
|
|
3635
|
-
/* @__PURE__ */
|
|
3849
|
+
/* @__PURE__ */ jsxs7("button", { type: "button", className: "history-row session-new", "aria-label": "New session", onClick: newSession, children: [
|
|
3850
|
+
/* @__PURE__ */ jsx9(Plus, {}),
|
|
3851
|
+
/* @__PURE__ */ jsxs7("span", { className: "prompt", children: [
|
|
3636
3852
|
"New session",
|
|
3637
|
-
/* @__PURE__ */
|
|
3853
|
+
/* @__PURE__ */ jsx9("small", { children: "Start a fresh conversation" })
|
|
3638
3854
|
] })
|
|
3639
3855
|
] }),
|
|
3640
|
-
/* @__PURE__ */
|
|
3641
|
-
chat.turns.length === 0 && /* @__PURE__ */
|
|
3642
|
-
chat.turns.map((turn, index) => /* @__PURE__ */
|
|
3856
|
+
/* @__PURE__ */ jsx9("h3", { className: "section-label", children: "Current session" }),
|
|
3857
|
+
chat.turns.length === 0 && /* @__PURE__ */ jsx9("p", { className: "history-empty", children: "Your questions will appear here." }),
|
|
3858
|
+
chat.turns.map((turn, index) => /* @__PURE__ */ jsxs7(
|
|
3643
3859
|
"button",
|
|
3644
3860
|
{
|
|
3645
3861
|
type: "button",
|
|
@@ -3655,18 +3871,18 @@ function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice =
|
|
|
3655
3871
|
setHistoryOpen(false);
|
|
3656
3872
|
},
|
|
3657
3873
|
children: [
|
|
3658
|
-
/* @__PURE__ */
|
|
3659
|
-
/* @__PURE__ */
|
|
3874
|
+
/* @__PURE__ */ jsx9("span", { className: "index", children: String(index + 1).padStart(2, "0") }),
|
|
3875
|
+
/* @__PURE__ */ jsxs7("span", { className: "prompt", children: [
|
|
3660
3876
|
turn.prompt,
|
|
3661
|
-
/* @__PURE__ */
|
|
3877
|
+
/* @__PURE__ */ jsx9("small", { children: turn.id === shown?.id ? "Now showing" : turn.completed ? "Play answer" : "Unfinished answer" })
|
|
3662
3878
|
] })
|
|
3663
3879
|
]
|
|
3664
3880
|
},
|
|
3665
3881
|
turn.id
|
|
3666
3882
|
)),
|
|
3667
|
-
savedSessions.length > 0 && /* @__PURE__ */
|
|
3668
|
-
/* @__PURE__ */
|
|
3669
|
-
savedSessions.map((session) => /* @__PURE__ */
|
|
3883
|
+
savedSessions.length > 0 && /* @__PURE__ */ jsxs7(Fragment4, { children: [
|
|
3884
|
+
/* @__PURE__ */ jsx9("h3", { className: "section-label", children: "Earlier sessions" }),
|
|
3885
|
+
savedSessions.map((session) => /* @__PURE__ */ jsxs7("button", { type: "button", className: "history-row", onClick: () => {
|
|
3670
3886
|
listen.stop();
|
|
3671
3887
|
setDraft("");
|
|
3672
3888
|
setEditing(false);
|
|
@@ -3677,10 +3893,10 @@ function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice =
|
|
|
3677
3893
|
setHistoryOpen(false);
|
|
3678
3894
|
setCaptionsExpanded(false);
|
|
3679
3895
|
}, children: [
|
|
3680
|
-
/* @__PURE__ */
|
|
3681
|
-
/* @__PURE__ */
|
|
3896
|
+
/* @__PURE__ */ jsx9(Replay, {}),
|
|
3897
|
+
/* @__PURE__ */ jsxs7("span", { className: "prompt", children: [
|
|
3682
3898
|
session.turns[0]?.prompt,
|
|
3683
|
-
/* @__PURE__ */
|
|
3899
|
+
/* @__PURE__ */ jsxs7("small", { children: [
|
|
3684
3900
|
session.turns.length,
|
|
3685
3901
|
" ",
|
|
3686
3902
|
session.turns.length === 1 ? "answer" : "answers"
|
|
@@ -3689,7 +3905,7 @@ function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice =
|
|
|
3689
3905
|
] }, session.id))
|
|
3690
3906
|
] })
|
|
3691
3907
|
] }),
|
|
3692
|
-
settingsOpen && /* @__PURE__ */
|
|
3908
|
+
settingsOpen && /* @__PURE__ */ jsxs7(
|
|
3693
3909
|
"div",
|
|
3694
3910
|
{
|
|
3695
3911
|
ref: settingsRef,
|
|
@@ -3699,18 +3915,18 @@ function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice =
|
|
|
3699
3915
|
"aria-label": "Settings",
|
|
3700
3916
|
"aria-modal": "true",
|
|
3701
3917
|
children: [
|
|
3702
|
-
/* @__PURE__ */
|
|
3703
|
-
/* @__PURE__ */
|
|
3704
|
-
/* @__PURE__ */
|
|
3918
|
+
/* @__PURE__ */ jsxs7("div", { className: "popover-heading", children: [
|
|
3919
|
+
/* @__PURE__ */ jsx9("h2", { children: "Settings" }),
|
|
3920
|
+
/* @__PURE__ */ jsx9("button", { type: "button", className: "round", "aria-label": "Close settings", onClick: closeSettings, children: /* @__PURE__ */ jsx9(Close, {}) })
|
|
3705
3921
|
] }),
|
|
3706
|
-
/* @__PURE__ */
|
|
3707
|
-
/* @__PURE__ */
|
|
3708
|
-
visualModes.filter((mode) => chat.availableModes.includes(mode.id)).map((mode) => /* @__PURE__ */
|
|
3709
|
-
/* @__PURE__ */
|
|
3710
|
-
/* @__PURE__ */
|
|
3711
|
-
/* @__PURE__ */
|
|
3922
|
+
/* @__PURE__ */ jsxs7("fieldset", { className: "playback-options", children: [
|
|
3923
|
+
/* @__PURE__ */ jsx9("legend", { children: "Video source" }),
|
|
3924
|
+
visualModes.filter((mode) => chat.availableModes.includes(mode.id)).map((mode) => /* @__PURE__ */ jsxs7("label", { className: "switch-row", children: [
|
|
3925
|
+
/* @__PURE__ */ jsxs7("span", { children: [
|
|
3926
|
+
/* @__PURE__ */ jsx9("strong", { children: mode.label }),
|
|
3927
|
+
/* @__PURE__ */ jsx9("small", { children: mode.note })
|
|
3712
3928
|
] }),
|
|
3713
|
-
/* @__PURE__ */
|
|
3929
|
+
/* @__PURE__ */ jsx9(
|
|
3714
3930
|
"input",
|
|
3715
3931
|
{
|
|
3716
3932
|
type: "radio",
|
|
@@ -3722,48 +3938,51 @@ function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice =
|
|
|
3722
3938
|
)
|
|
3723
3939
|
] }, mode.id))
|
|
3724
3940
|
] }),
|
|
3725
|
-
/* @__PURE__ */
|
|
3726
|
-
/* @__PURE__ */
|
|
3727
|
-
/* @__PURE__ */
|
|
3728
|
-
/* @__PURE__ */
|
|
3729
|
-
/* @__PURE__ */
|
|
3730
|
-
/* @__PURE__ */
|
|
3941
|
+
/* @__PURE__ */ jsxs7("fieldset", { className: "playback-options", children: [
|
|
3942
|
+
/* @__PURE__ */ jsx9("legend", { children: "Watching" }),
|
|
3943
|
+
/* @__PURE__ */ jsxs7("label", { className: "switch-row", children: [
|
|
3944
|
+
/* @__PURE__ */ jsxs7("span", { children: [
|
|
3945
|
+
/* @__PURE__ */ jsx9("strong", { children: "Subtitles" }),
|
|
3946
|
+
/* @__PURE__ */ jsx9("small", { children: "Read along with the answer" })
|
|
3731
3947
|
] }),
|
|
3732
|
-
/* @__PURE__ */
|
|
3948
|
+
/* @__PURE__ */ jsx9("input", { type: "checkbox", role: "switch", checked: captionsOn, onChange: (event) => {
|
|
3733
3949
|
setCaptionsOn(event.target.checked);
|
|
3734
3950
|
setCaptionsExpanded(false);
|
|
3735
3951
|
} })
|
|
3736
3952
|
] }),
|
|
3737
|
-
/* @__PURE__ */
|
|
3738
|
-
/* @__PURE__ */
|
|
3739
|
-
/* @__PURE__ */
|
|
3740
|
-
/* @__PURE__ */
|
|
3953
|
+
/* @__PURE__ */ jsxs7("label", { className: "switch-row", children: [
|
|
3954
|
+
/* @__PURE__ */ jsxs7("span", { children: [
|
|
3955
|
+
/* @__PURE__ */ jsx9("strong", { children: "Keep controls visible" }),
|
|
3956
|
+
/* @__PURE__ */ jsx9("small", { children: "Keep the input bar on screen" })
|
|
3741
3957
|
] }),
|
|
3742
|
-
/* @__PURE__ */
|
|
3958
|
+
/* @__PURE__ */ jsx9("input", { type: "checkbox", role: "switch", checked: alwaysShowControls, onChange: (event) => setAlwaysShowControls(event.target.checked) })
|
|
3743
3959
|
] })
|
|
3744
3960
|
] }),
|
|
3745
|
-
/* @__PURE__ */
|
|
3746
|
-
/* @__PURE__ */
|
|
3747
|
-
/* @__PURE__ */
|
|
3961
|
+
/* @__PURE__ */ jsxs7("nav", { className: "developer-links", "aria-label": "Build with VanillaSky", children: [
|
|
3962
|
+
/* @__PURE__ */ jsx9("p", { className: "section-label", children: "Build with VanillaSky" }),
|
|
3963
|
+
/* @__PURE__ */ jsxs7("a", { href: "https://github.com/VanillaSkyAi/video/blob/main/docs/getting-started.md", target: "_blank", rel: "noopener noreferrer", children: [
|
|
3748
3964
|
"Docs",
|
|
3749
|
-
/* @__PURE__ */
|
|
3965
|
+
/* @__PURE__ */ jsx9("span", { "aria-hidden": "true", children: "\u2197" })
|
|
3750
3966
|
] }),
|
|
3751
|
-
/* @__PURE__ */
|
|
3967
|
+
/* @__PURE__ */ jsxs7("button", { type: "button", "aria-expanded": aboutOpen, "aria-controls": `${instanceId}-about`, onClick: () => setAboutOpen((open) => !open), children: [
|
|
3752
3968
|
"About",
|
|
3753
|
-
/* @__PURE__ */
|
|
3969
|
+
/* @__PURE__ */ jsx9("span", { "aria-hidden": "true", children: aboutOpen ? "\u2212" : "+" })
|
|
3754
3970
|
] }),
|
|
3755
|
-
/* @__PURE__ */
|
|
3756
|
-
/* @__PURE__ */
|
|
3971
|
+
/* @__PURE__ */ jsx9("div", { id: `${instanceId}-about`, className: "developer-about", role: "region", "aria-label": "About VanillaSky", hidden: !aboutOpen, children: /* @__PURE__ */ jsx9("p", { children: "VanillaSky is an open-source SDK for conversations that answer in video. Developers connect their own AI providers through their application server." }) }),
|
|
3972
|
+
/* @__PURE__ */ jsxs7("a", { href: "https://github.com/VanillaSkyAi/video", target: "_blank", rel: "noopener noreferrer", children: [
|
|
3757
3973
|
"GitHub",
|
|
3758
|
-
/* @__PURE__ */
|
|
3974
|
+
/* @__PURE__ */ jsx9("span", { "aria-hidden": "true", children: "\u2197" })
|
|
3759
3975
|
] })
|
|
3760
3976
|
] })
|
|
3761
3977
|
]
|
|
3762
3978
|
}
|
|
3763
3979
|
)
|
|
3764
3980
|
] }),
|
|
3765
|
-
/* @__PURE__ */
|
|
3766
|
-
/* @__PURE__ */
|
|
3981
|
+
/* @__PURE__ */ jsx9("div", { ref: panelRef, className: "panel", "data-input-visible": controls.visible || !captionsOn || !line, children: /* @__PURE__ */ jsxs7("div", { className: "panel-inner", children: [
|
|
3982
|
+
/* @__PURE__ */ jsx9("div", { className: "caption-slot", "data-captions": captionsOn && Boolean(line), "aria-hidden": !captionsOn || !line, children: /* @__PURE__ */ jsx9("div", { className: "caption-clip", children: chat.playbackEnded && !captionsExpanded ? /* @__PURE__ */ jsxs7("button", { type: "button", className: "transcript-toggle", "aria-expanded": false, onClick: () => setCaptionsExpanded(true), children: [
|
|
3983
|
+
"Show transcript",
|
|
3984
|
+
/* @__PURE__ */ jsx9(ChevronUp, {})
|
|
3985
|
+
] }) : /* @__PURE__ */ jsxs7(
|
|
3767
3986
|
"div",
|
|
3768
3987
|
{
|
|
3769
3988
|
className: "line-row",
|
|
@@ -3776,44 +3995,44 @@ function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice =
|
|
|
3776
3995
|
onFocusCapture: captionControls.onFocusCapture,
|
|
3777
3996
|
onBlurCapture: captionControls.onBlurCapture,
|
|
3778
3997
|
children: [
|
|
3779
|
-
captionsOn && line && /* @__PURE__ */
|
|
3780
|
-
/* @__PURE__ */
|
|
3998
|
+
captionsOn && line && /* @__PURE__ */ jsxs7("div", { className: "caption-actions", children: [
|
|
3999
|
+
/* @__PURE__ */ jsxs7("button", { type: "button", className: "caption-action", "aria-label": captionsExpanded ? "Collapse subtitles" : "Expand subtitles", "aria-expanded": captionsExpanded, onClick: () => setCaptionsExpanded((open) => !open), children: [
|
|
3781
4000
|
captionsExpanded ? "Collapse" : "Expand",
|
|
3782
|
-
/* @__PURE__ */
|
|
4001
|
+
/* @__PURE__ */ jsx9(ChevronUp, {})
|
|
3783
4002
|
] }),
|
|
3784
|
-
/* @__PURE__ */
|
|
4003
|
+
/* @__PURE__ */ jsx9("button", { type: "button", className: "caption-action", "aria-label": "Hide subtitles", onClick: () => {
|
|
3785
4004
|
setCaptionsOn(false);
|
|
3786
4005
|
setCaptionsExpanded(false);
|
|
3787
|
-
}, children: /* @__PURE__ */
|
|
4006
|
+
}, children: /* @__PURE__ */ jsx9(Close, {}) })
|
|
3788
4007
|
] }),
|
|
3789
|
-
captionsExpanded ? /* @__PURE__ */
|
|
4008
|
+
captionsExpanded ? /* @__PURE__ */ jsx9("div", { className: "expanded-captions", role: "region", tabIndex: 0, "aria-label": "Expanded subtitles", children: fullTranscript.map((entry, index) => /* @__PURE__ */ jsx9("p", { children: entry }, index)) }) : /* @__PURE__ */ jsx9(CaptionPages, { text: openingChapter && line === shown?.opening ? "" : line, getProgress: getCaptionProgress }, `${shown?.id}:${chat.playerKey}`)
|
|
3790
4009
|
]
|
|
3791
4010
|
}
|
|
3792
4011
|
) }) }),
|
|
3793
|
-
showRecoveryNotice && chat.shownTurn && dismissedNoticeTurn !== chat.shownTurn.id && chat.warnings.includes(MEDIA_RECOVERY_NOTICE) && !chat.error && /* @__PURE__ */
|
|
3794
|
-
/* @__PURE__ */
|
|
3795
|
-
/* @__PURE__ */
|
|
4012
|
+
showRecoveryNotice && chat.shownTurn && dismissedNoticeTurn !== chat.shownTurn.id && chat.warnings.includes(MEDIA_RECOVERY_NOTICE) && !chat.error && /* @__PURE__ */ jsxs7("div", { className: "recovery-notice", children: [
|
|
4013
|
+
/* @__PURE__ */ jsx9("p", { role: "status", children: MEDIA_RECOVERY_NOTICE }),
|
|
4014
|
+
/* @__PURE__ */ jsx9("button", { type: "button", className: "round", "aria-label": "Dismiss notice", onClick: () => setDismissedNoticeTurn(chat.shownTurn?.id), children: /* @__PURE__ */ jsx9(Close, {}) })
|
|
3796
4015
|
] }),
|
|
3797
|
-
(chat.error || listen.error) && /* @__PURE__ */
|
|
3798
|
-
/* @__PURE__ */
|
|
3799
|
-
/* @__PURE__ */
|
|
4016
|
+
(chat.error || listen.error) && /* @__PURE__ */ jsxs7("p", { className: "error", role: "status", children: [
|
|
4017
|
+
/* @__PURE__ */ jsx9(Warning, {}),
|
|
4018
|
+
/* @__PURE__ */ jsx9("span", { children: chat.error?.message ?? listen.error })
|
|
3800
4019
|
] }),
|
|
3801
|
-
/* @__PURE__ */
|
|
3802
|
-
(listen.listening || listen.thinking) && /* @__PURE__ */
|
|
3803
|
-
/* @__PURE__ */
|
|
4020
|
+
/* @__PURE__ */ jsxs7("div", { ref: composerRef, className: "conversation-composer", "data-editing": editing, ...controlEvents, children: [
|
|
4021
|
+
(listen.listening || listen.thinking) && /* @__PURE__ */ jsx9("div", { className: "composer-meta", children: /* @__PURE__ */ jsx9("span", { role: "status", children: listen.listening ? "Listening\u2026 Tap the mic to finish, then review and send." : "Turning your words into a draft\u2026" }) }),
|
|
4022
|
+
/* @__PURE__ */ jsxs7("form", { className: "composer", "aria-label": "Ask a question", onSubmit: (event) => {
|
|
3804
4023
|
event.preventDefault();
|
|
3805
4024
|
ask(draft);
|
|
3806
4025
|
}, children: [
|
|
3807
|
-
transport && /* @__PURE__ */
|
|
4026
|
+
transport && /* @__PURE__ */ jsx9("button", { type: "button", className: "ghost transport", "aria-label": transport.label, onClick: () => {
|
|
3808
4027
|
listen.stop();
|
|
3809
4028
|
setEditing(false);
|
|
3810
4029
|
resumeAfterInput.current = false;
|
|
3811
4030
|
inputRef.current?.blur();
|
|
3812
4031
|
transport.action();
|
|
3813
4032
|
}, children: transport.icon }),
|
|
3814
|
-
/* @__PURE__ */
|
|
3815
|
-
/* @__PURE__ */
|
|
3816
|
-
/* @__PURE__ */
|
|
4033
|
+
/* @__PURE__ */ jsx9(Waveform, { active: listen.listening || chat.speaking && !chat.muted && status !== "paused", listening: listen.listening }),
|
|
4034
|
+
/* @__PURE__ */ jsx9("label", { className: "sr-only", htmlFor: promptId, children: "Prompt" }),
|
|
4035
|
+
/* @__PURE__ */ jsx9(
|
|
3817
4036
|
"textarea",
|
|
3818
4037
|
{
|
|
3819
4038
|
id: promptId,
|
|
@@ -3841,12 +4060,12 @@ function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice =
|
|
|
3841
4060
|
}
|
|
3842
4061
|
}
|
|
3843
4062
|
),
|
|
3844
|
-
listen.supported && /* @__PURE__ */
|
|
3845
|
-
/* @__PURE__ */
|
|
3846
|
-
editing && /* @__PURE__ */
|
|
4063
|
+
listen.supported && /* @__PURE__ */ jsx9("button", { type: "button", className: `ghost${listen.listening ? " listening" : ""}`, "aria-label": listen.listening ? "Stop listening" : "Ask by voice", "aria-pressed": listen.listening, disabled: listen.thinking, onClick: () => beginInput(true), children: /* @__PURE__ */ jsx9(Mic, {}) }),
|
|
4064
|
+
/* @__PURE__ */ jsx9("button", { type: "submit", className: "send", "aria-label": "Ask", disabled: !draft.trim() || listen.thinking, children: /* @__PURE__ */ jsx9(Send, {}) }),
|
|
4065
|
+
editing && /* @__PURE__ */ jsx9("button", { type: "button", className: "ghost", "aria-label": "Cancel question", onClick: cancelInput, children: /* @__PURE__ */ jsx9(Close, {}) })
|
|
3847
4066
|
] })
|
|
3848
4067
|
] }),
|
|
3849
|
-
!showing && chat.turns.length === 0 && /* @__PURE__ */
|
|
4068
|
+
!showing && chat.turns.length === 0 && /* @__PURE__ */ jsx9("p", { className: "dock-hint", children: "Speak or type. See where it takes you." })
|
|
3850
4069
|
] }) })
|
|
3851
4070
|
]
|
|
3852
4071
|
}
|