@vanillaskyai/video 0.8.0 → 0.8.2

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/react.js CHANGED
@@ -2255,6 +2255,17 @@ function reducer(state, action) {
2255
2255
  error: void 0
2256
2256
  };
2257
2257
  }
2258
+ case "restore": {
2259
+ const restored = {
2260
+ ...initialState(state.muted),
2261
+ capabilities: state.capabilities,
2262
+ welcome: state.welcome,
2263
+ turns: action.turns,
2264
+ playerKey: state.playerKey
2265
+ };
2266
+ const latest = action.turns.at(-1);
2267
+ return latest ? reducer(restored, { type: "select", id: latest.id }) : restored;
2268
+ }
2258
2269
  case "reset":
2259
2270
  return { ...initialState(state.muted), capabilities: state.capabilities, welcome: state.welcome };
2260
2271
  }
@@ -2338,6 +2349,9 @@ function prepareVisualScene(scene, mode) {
2338
2349
  return overMedia ? { ...shown, textArchetype: "subtle" } : shown;
2339
2350
  }
2340
2351
  function useVideoChat(options = {}) {
2352
+ return useVideoChatSession(options).chat;
2353
+ }
2354
+ function useVideoChatSession(options = {}) {
2341
2355
  const optionsRef = useRef3(options);
2342
2356
  optionsRef.current = options;
2343
2357
  const voiceWarningRef = useRef3(() => void 0);
@@ -2861,6 +2875,12 @@ function useVideoChat(options = {}) {
2861
2875
  voiceRef.current.resume();
2862
2876
  dispatch({ type: "reset" });
2863
2877
  }, [cancel]);
2878
+ const restoreSession = useCallback2((turns) => {
2879
+ cancel("Restoring session");
2880
+ heldRef.current = false;
2881
+ voiceRef.current.resume();
2882
+ dispatch({ type: "restore", turns: turns.filter((turn) => turn.completed && turn.video).slice(-100) });
2883
+ }, [cancel]);
2864
2884
  const setMuted = useCallback2((muted) => dispatch({ type: "mute", value: muted }), []);
2865
2885
  const currentTurn = state.turns.at(-1);
2866
2886
  const shownTurn = state.turns.find((turn) => turn.id === state.shownTurnId) ?? currentTurn;
@@ -2907,7 +2927,7 @@ function useVideoChat(options = {}) {
2907
2927
  if (id) dispatch({ type: "error", id, error: errorFrom(cause) });
2908
2928
  }
2909
2929
  } : void 0;
2910
- return {
2930
+ const chat = {
2911
2931
  ask,
2912
2932
  cancel,
2913
2933
  pause,
@@ -2934,15 +2954,16 @@ function useVideoChat(options = {}) {
2934
2954
  playerKey: state.playerKey,
2935
2955
  playerProps
2936
2956
  };
2957
+ return { chat, restoreSession };
2937
2958
  }
2938
2959
 
2939
2960
  // src/video-chat/video-chat.tsx
2940
- import { useCallback as useCallback5, useEffect as useEffect8, useId, useMemo as useMemo2, useRef as useRef6, useState as useState5 } from "react";
2961
+ import { useCallback as useCallback6, useEffect as useEffect9, useId, useLayoutEffect, useMemo as useMemo2, useRef as useRef7, useState as useState6 } from "react";
2941
2962
 
2942
2963
  // src/video-chat/modes.ts
2943
2964
  var visualModes = [
2944
- { id: "templates", label: "Templates only", note: "Rendered scenes \xB7 free and instant" },
2945
- { id: "full", label: "Full AI video", note: "Every beat filmed \xB7 billed per clip" }
2965
+ { id: "templates", label: "Templates only", note: "Animated text, diagrams and charts" },
2966
+ { id: "full", label: "Full AI video", note: "AI-generated footage \xB7 billed per clip" }
2946
2967
  ];
2947
2968
  var defaultMode = visualModes[0];
2948
2969
  var modeById = (id) => visualModes.find((mode) => mode.id === id) ?? defaultMode;
@@ -3406,14 +3427,77 @@ function useVoiceInput(onTranscript, transcriptionAvailable = false, options = {
3406
3427
  return { supported, listening, thinking, error, toggle, stop };
3407
3428
  }
3408
3429
 
3430
+ // src/video-chat/use-immersive-controls.ts
3431
+ import { useCallback as useCallback5, useEffect as useEffect8, useRef as useRef6, useState as useState5 } from "react";
3432
+ function useImmersiveControls(playing, pinned, hasCaptions = false) {
3433
+ const [visible, setVisible] = useState5(true);
3434
+ const hovered = useRef6(false);
3435
+ const focused = useRef6(false);
3436
+ const previousCaptions = useRef6(false);
3437
+ const timer = useRef6(null);
3438
+ const clearTimer = useCallback5(() => {
3439
+ if (timer.current !== null) clearTimeout(timer.current);
3440
+ timer.current = null;
3441
+ }, []);
3442
+ const reveal = useCallback5(() => {
3443
+ clearTimer();
3444
+ setVisible(true);
3445
+ if (playing && !pinned && !hovered.current && !focused.current) {
3446
+ timer.current = setTimeout(() => {
3447
+ timer.current = null;
3448
+ setVisible(false);
3449
+ }, 2e3);
3450
+ }
3451
+ }, [clearTimer, playing, pinned]);
3452
+ useEffect8(() => {
3453
+ const firstCaption = hasCaptions && !previousCaptions.current;
3454
+ previousCaptions.current = hasCaptions;
3455
+ if (firstCaption && playing && !pinned && !focused.current) {
3456
+ hovered.current = false;
3457
+ clearTimer();
3458
+ setVisible(false);
3459
+ } else {
3460
+ reveal();
3461
+ }
3462
+ return clearTimer;
3463
+ }, [hasCaptions, playing, pinned, reveal, clearTimer]);
3464
+ const onPointerEnter = useCallback5((event) => {
3465
+ if (event?.pointerType === "touch") return;
3466
+ hovered.current = true;
3467
+ reveal();
3468
+ }, [reveal]);
3469
+ const onPointerLeave = useCallback5(() => {
3470
+ if (!hovered.current) return;
3471
+ hovered.current = false;
3472
+ reveal();
3473
+ }, [reveal]);
3474
+ const onFocusCapture = useCallback5((event) => {
3475
+ focused.current = event.target.matches(":focus-visible");
3476
+ reveal();
3477
+ }, [reveal]);
3478
+ const onBlurCapture = useCallback5((event) => {
3479
+ if (focused.current && !event.currentTarget.contains(event.relatedTarget)) {
3480
+ focused.current = false;
3481
+ reveal();
3482
+ }
3483
+ }, [reveal]);
3484
+ return { visible, reveal, onPointerEnter, onPointerLeave, onFocusCapture, onBlurCapture };
3485
+ }
3486
+
3487
+ // src/video-chat/logo.tsx
3488
+ import { jsx as jsx6 } from "react/jsx-runtime";
3489
+ function Logo() {
3490
+ return /* @__PURE__ */ jsx6("img", { className: "brand-logo", src: new URL("./assets/vanillasky-logo.svg", import.meta.url).href, alt: "VanillaSky", width: 144, height: 36 });
3491
+ }
3492
+
3409
3493
  // src/video-chat/video-chat.tsx
3410
- import { Fragment as Fragment4, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
3494
+ import { Fragment as Fragment4, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
3411
3495
  var DESKTOP_WIDTH = 900;
3412
3496
  function useViewportOrientation() {
3413
- const [portrait, setPortrait] = useState5(() => typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia(`(max-width: ${DESKTOP_WIDTH - 1}px)`).matches : false);
3414
- useEffect8(() => {
3497
+ const [portrait, setPortrait] = useState6(() => typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia(`(max-width: ${DESKTOP_WIDTH - 1}px) and (orientation: portrait)`).matches : false);
3498
+ useEffect9(() => {
3415
3499
  if (typeof window.matchMedia !== "function") return;
3416
- const query = window.matchMedia(`(max-width: ${DESKTOP_WIDTH - 1}px)`);
3500
+ const query = window.matchMedia(`(max-width: ${DESKTOP_WIDTH - 1}px) and (orientation: portrait)`);
3417
3501
  const update = () => setPortrait(query.matches);
3418
3502
  update();
3419
3503
  query.addEventListener("change", update);
@@ -3428,8 +3512,8 @@ var STEPS = [
3428
3512
  "Still filming\u2026"
3429
3513
  ];
3430
3514
  function useFilmingStep(active, key) {
3431
- const [step, setStep] = useState5(0);
3432
- useEffect8(() => {
3515
+ const [step, setStep] = useState6(0);
3516
+ useEffect9(() => {
3433
3517
  if (!active) return;
3434
3518
  setStep(0);
3435
3519
  const stepper = window.setInterval(() => setStep((current) => current + 1), 3e3);
@@ -3438,22 +3522,29 @@ function useFilmingStep(active, key) {
3438
3522
  return STEPS[Math.min(step, STEPS.length - 1)];
3439
3523
  }
3440
3524
  function Waveform({ active, listening }) {
3441
- return /* @__PURE__ */ jsx6("span", { className: `waveform${active ? " on" : ""}${listening ? " hearing" : ""}`, "aria-hidden": "true", children: [0, 1, 2, 3, 4].map((bar) => /* @__PURE__ */ jsx6("span", { style: { animationDelay: `${bar * 140}ms`, animationDuration: `${900 + bar * 130}ms` } }, bar)) });
3525
+ return /* @__PURE__ */ jsx7("span", { className: `waveform${active ? " on" : ""}${listening ? " hearing" : ""}`, "aria-hidden": "true", children: [0, 1, 2, 3, 4].map((bar) => /* @__PURE__ */ jsx7("span", { style: { animationDelay: `${bar * 140}ms`, animationDuration: `${900 + bar * 130}ms` } }, bar)) });
3442
3526
  }
3443
3527
  function VideoChat({ options = {}, className, welcomeTitle }) {
3444
- const [draft, setDraft] = useState5("");
3445
- const [themeId, setThemeId] = useState5(defaultTheme.id);
3446
- const [modeId, setModeId] = useState5(options.mode ?? defaultMode.id);
3447
- const [historyOpen, setHistoryOpen] = useState5(false);
3448
- const [settingsOpen, setSettingsOpen] = useState5(false);
3449
- const [sheetOpen, setSheetOpen] = useState5(false);
3528
+ const [draft, setDraft] = useState6("");
3529
+ const [savedSessions, setSavedSessions] = useState6([]);
3530
+ const [themeId, setThemeId] = useState6(defaultTheme.id);
3531
+ const [modeId, setModeId] = useState6(options.mode ?? defaultMode.id);
3532
+ const [historyOpen, setHistoryOpen] = useState6(false);
3533
+ const [settingsOpen, setSettingsOpen] = useState6(false);
3534
+ const [aboutOpen, setAboutOpen] = useState6(false);
3535
+ const [captionsOn, setCaptionsOn] = useState6(true);
3536
+ const [captionsExpanded, setCaptionsExpanded] = useState6(false);
3537
+ const [alwaysShowControls, setAlwaysShowControls] = useState6(false);
3538
+ const [editing, setEditing] = useState6(false);
3539
+ const resumeAfterInput = useRef7(false);
3540
+ const panelRef = useRef7(null);
3541
+ const composerRef = useRef7(null);
3450
3542
  const viewportOrientation = useViewportOrientation();
3451
- const [themeChoice, setThemeChoice] = useState5("system");
3452
3543
  const theme = themeById(themeId);
3453
3544
  const sessionOrientation = options.orientation ?? viewportOrientation;
3454
3545
  const { brand: configuredBrand, style: configuredStyle, ...sessionOptions } = options;
3455
3546
  const presentationBrand = configuredBrand ?? theme.brand;
3456
- const chat = useVideoChat({
3547
+ const { chat, restoreSession } = useVideoChatSession({
3457
3548
  ...sessionOptions,
3458
3549
  mode: modeId,
3459
3550
  orientation: sessionOrientation,
@@ -3464,16 +3555,13 @@ function VideoChat({ options = {}, className, welcomeTitle }) {
3464
3555
  const historyId = `${instanceId}-history`;
3465
3556
  const settingsId = `${instanceId}-settings`;
3466
3557
  const promptId = `${instanceId}-prompt`;
3467
- const responseTitleId = `${instanceId}-response-title`;
3468
- const appearanceName = `${instanceId}-appearance`;
3469
3558
  const visualsName = `${instanceId}-visuals`;
3470
3559
  const styleName = `${instanceId}-style`;
3471
- const inputRef = useRef6(null);
3472
- const historyRef = useRef6(null);
3473
- const historyButtonRef = useRef6(null);
3474
- const settingsRef = useRef6(null);
3475
- const settingsButtonRef = useRef6(null);
3476
- const sheetRef = useRef6(null);
3560
+ const inputRef = useRef7(null);
3561
+ const historyRef = useRef7(null);
3562
+ const historyButtonRef = useRef7(null);
3563
+ const settingsRef = useRef7(null);
3564
+ const settingsButtonRef = useRef7(null);
3477
3565
  const listen = useVoiceInput(setDraft, chat.capabilities?.transcription ?? false, {
3478
3566
  endpoint: options.endpoint,
3479
3567
  headers: options.headers,
@@ -3482,40 +3570,49 @@ function VideoChat({ options = {}, className, welcomeTitle }) {
3482
3570
  });
3483
3571
  const historySurfaces = useMemo2(() => [historyRef, historyButtonRef], []);
3484
3572
  const settingsSurfaces = useMemo2(() => [settingsRef, settingsButtonRef], []);
3485
- const noSurfaces = useMemo2(() => [], []);
3486
- const closeHistory = useCallback5(() => setHistoryOpen(false), []);
3487
- const closeSettings = useCallback5(() => setSettingsOpen(false), []);
3488
- const closeSheet = useCallback5(() => setSheetOpen(false), []);
3573
+ const closeHistory = useCallback6(() => setHistoryOpen(false), []);
3574
+ const closeSettings = useCallback6(() => setSettingsOpen(false), []);
3489
3575
  useDismiss(historyOpen, closeHistory, historySurfaces);
3490
3576
  useDismiss(settingsOpen, closeSettings, settingsSurfaces);
3491
- useDismiss(sheetOpen, closeSheet, noSurfaces);
3492
- useFocusTrap(sheetOpen, sheetRef);
3493
- const ask = useCallback5((value) => {
3577
+ useFocusTrap(settingsOpen, settingsRef);
3578
+ useFocusTrap(historyOpen, historyRef);
3579
+ const ask = useCallback6((value) => {
3494
3580
  const prompt = (typeof value === "string" ? value : value.prompt).trim();
3495
3581
  if (!prompt) return;
3496
3582
  setDraft("");
3497
3583
  listen.stop();
3498
3584
  setHistoryOpen(false);
3499
3585
  setSettingsOpen(false);
3500
- setSheetOpen(false);
3586
+ setCaptionsExpanded(false);
3587
+ setEditing(false);
3588
+ resumeAfterInput.current = false;
3589
+ inputRef.current?.blur();
3501
3590
  void chat.ask(prompt, typeof value === "string" ? void 0 : {
3502
3591
  openingMedia: value.media,
3503
3592
  opening: value.opening
3504
3593
  });
3505
3594
  }, [chat, listen]);
3506
- const newSession = useCallback5(() => {
3595
+ const newSession = useCallback6(() => {
3507
3596
  if (chat.turns.length === 0) {
3508
3597
  inputRef.current?.focus();
3509
3598
  return;
3510
3599
  }
3600
+ listen.stop();
3601
+ setDraft("");
3602
+ const completed = chat.turns.filter((turn) => turn.completed && turn.video);
3603
+ if (completed.length) setSavedSessions((sessions) => [{ id: completed[0].id, turns: completed }, ...sessions].slice(0, 10));
3511
3604
  chat.reset();
3512
3605
  setHistoryOpen(false);
3513
3606
  setSettingsOpen(false);
3514
- setSheetOpen(false);
3515
- }, [chat]);
3607
+ setCaptionsExpanded(false);
3608
+ setEditing(false);
3609
+ resumeAfterInput.current = false;
3610
+ inputRef.current?.blur();
3611
+ }, [chat, listen]);
3516
3612
  const current = chat.currentTurn;
3517
3613
  const shown = chat.shownTurn;
3518
3614
  const showing = chat.playerProps != null;
3615
+ const ambientPoster = shown?.openingMedia?.type === "image" ? shown.openingMedia.url : shown?.openingMedia?.posterUrl;
3519
3616
  const waitingForPicture = chat.turns.length > 0 && !showing && (chat.status === "composing" || chat.status === "playing" || chat.status === "paused");
3520
3617
  const filmingStep = useFilmingStep(waitingForPicture, current?.id);
3521
3618
  const status = chat.turns.length === 0 ? "idle" : chat.status === "composing" ? "drawing" : chat.status === "playing" ? "narrating" : chat.status === "error" || chat.status === "cancelled" ? "ended" : chat.status;
@@ -3524,47 +3621,99 @@ function VideoChat({ options = {}, className, welcomeTitle }) {
3524
3621
  const availableModes = visualModes.filter((option) => chat.availableModes.includes(option.id));
3525
3622
  const selectedMode = modeById(modeId);
3526
3623
  const line = chat.caption ?? "";
3527
- const transport = status === "narrating" ? { label: "Pause", action: chat.pause, icon: /* @__PURE__ */ jsx6(Stop, {}) } : status === "paused" ? { label: "Continue", action: chat.resume, icon: /* @__PURE__ */ jsx6(Play, {}) } : status === "ended" && shown?.completed && shown.video ? { label: "Play again", action: chat.replay, icon: /* @__PURE__ */ jsx6(Replay, {}) } : void 0;
3624
+ const fullTranscript = shown?.video ? [shown.opening, ...shown.video.scenes.map((scene) => scene.narration)].filter((entry) => Boolean(entry)) : chat.transcript;
3625
+ const transport = status === "narrating" ? { label: "Pause", action: chat.pause, icon: /* @__PURE__ */ jsx7(Stop, {}) } : status === "paused" ? { label: "Continue", action: chat.resume, icon: /* @__PURE__ */ jsx7(Play, {}) } : status === "ended" && shown?.completed && shown.video ? { label: "Play again", action: chat.replay, icon: /* @__PURE__ */ jsx7(Replay, {}) } : void 0;
3626
+ const cancelInput = useCallback6(() => {
3627
+ listen.stop();
3628
+ setDraft("");
3629
+ setEditing(false);
3630
+ inputRef.current?.blur();
3631
+ if (resumeAfterInput.current) chat.resume();
3632
+ resumeAfterInput.current = false;
3633
+ }, [listen, chat]);
3634
+ const beginInput = (speak = false) => {
3635
+ if (!editing) {
3636
+ resumeAfterInput.current = chat.status === "playing" || chat.status === "composing";
3637
+ if (resumeAfterInput.current) chat.pause();
3638
+ }
3639
+ setEditing(true);
3640
+ setSettingsOpen(false);
3641
+ setHistoryOpen(false);
3642
+ if (speak) listen.toggle();
3643
+ };
3644
+ const controls = useImmersiveControls(
3645
+ status === "narrating" || Boolean(line) && status !== "paused",
3646
+ alwaysShowControls || captionsExpanded || editing || historyOpen || settingsOpen || listen.listening || listen.thinking || Boolean(chat.error || listen.error),
3647
+ captionsOn && Boolean(line)
3648
+ );
3649
+ const captionControls = useImmersiveControls(Boolean(line), captionsExpanded, false);
3650
+ const controlEvents = {
3651
+ onPointerEnter: controls.onPointerEnter,
3652
+ onPointerLeave: controls.onPointerLeave,
3653
+ onFocusCapture: controls.onFocusCapture,
3654
+ onBlurCapture: controls.onBlurCapture
3655
+ };
3656
+ useLayoutEffect(() => {
3657
+ const composer = composerRef.current;
3658
+ if (!composer) return;
3659
+ const measure = () => panelRef.current?.style.setProperty("--composer-height", `${composer.getBoundingClientRect().height}px`);
3660
+ measure();
3661
+ if (typeof ResizeObserver === "undefined") return;
3662
+ const observer = new ResizeObserver(measure);
3663
+ observer.observe(composer);
3664
+ return () => observer.disconnect();
3665
+ }, []);
3528
3666
  return /* @__PURE__ */ jsxs6(
3529
3667
  "div",
3530
3668
  {
3531
3669
  className: `vanillasky-video-chat${className ? ` ${className}` : ""}`,
3532
3670
  "data-orientation": stageOrientation,
3533
- "data-theme": themeChoice,
3671
+ "data-controls-visible": controls.visible,
3672
+ tabIndex: 0,
3673
+ "aria-label": "Video conversation",
3674
+ onPointerMove: controls.reveal,
3675
+ onPointerDown: controls.reveal,
3676
+ onKeyDownCapture: controls.reveal,
3534
3677
  children: [
3535
- /* @__PURE__ */ jsxs6("header", { className: "chrome", children: [
3678
+ /* @__PURE__ */ jsxs6("header", { className: "chrome", ...controlEvents, children: [
3679
+ /* @__PURE__ */ jsx7("div", { className: "session-brand", children: /* @__PURE__ */ jsx7(Logo, {}) }),
3536
3680
  /* @__PURE__ */ jsxs6("div", { className: "group", children: [
3537
- /* @__PURE__ */ jsx6(
3681
+ /* @__PURE__ */ jsxs6(
3538
3682
  "button",
3539
3683
  {
3540
3684
  type: "button",
3541
3685
  className: "round",
3542
- "aria-label": chat.turns.length === 0 ? "Ask a prompt" : "New session",
3686
+ "aria-label": "New session",
3543
3687
  onClick: newSession,
3544
- children: /* @__PURE__ */ jsx6(Plus, {})
3688
+ children: [
3689
+ /* @__PURE__ */ jsx7(Plus, {}),
3690
+ /* @__PURE__ */ jsx7("span", { className: "nav-label", children: "New session" })
3691
+ ]
3545
3692
  }
3546
3693
  ),
3547
- chat.turns.length > 0 && /* @__PURE__ */ jsxs6(
3694
+ /* @__PURE__ */ jsxs6(
3548
3695
  "button",
3549
3696
  {
3550
3697
  ref: historyButtonRef,
3551
3698
  type: "button",
3552
3699
  className: "pill",
3553
3700
  "aria-expanded": historyOpen,
3701
+ "aria-label": "History",
3702
+ "aria-haspopup": "dialog",
3554
3703
  "aria-controls": historyId,
3555
3704
  onClick: () => {
3556
3705
  setHistoryOpen((open) => !open);
3557
3706
  setSettingsOpen(false);
3558
3707
  },
3559
3708
  children: [
3560
- chat.turns.length,
3561
- " asked"
3709
+ /* @__PURE__ */ jsx7(Replay, {}),
3710
+ /* @__PURE__ */ jsx7("span", { className: "nav-label", children: "History" })
3562
3711
  ]
3563
3712
  }
3564
3713
  )
3565
3714
  ] }),
3566
3715
  /* @__PURE__ */ jsxs6("div", { className: "group", children: [
3567
- /* @__PURE__ */ jsx6(
3716
+ /* @__PURE__ */ jsx7(
3568
3717
  "button",
3569
3718
  {
3570
3719
  ref: settingsButtonRef,
@@ -3578,10 +3727,10 @@ function VideoChat({ options = {}, className, welcomeTitle }) {
3578
3727
  setSettingsOpen((open) => !open);
3579
3728
  setHistoryOpen(false);
3580
3729
  },
3581
- children: /* @__PURE__ */ jsx6(Gear, {})
3730
+ children: /* @__PURE__ */ jsx7(Gear, {})
3582
3731
  }
3583
3732
  ),
3584
- /* @__PURE__ */ jsx6(
3733
+ /* @__PURE__ */ jsx7(
3585
3734
  "button",
3586
3735
  {
3587
3736
  type: "button",
@@ -3589,60 +3738,102 @@ function VideoChat({ options = {}, className, welcomeTitle }) {
3589
3738
  "aria-label": chat.muted ? "Turn the voice on" : "Turn the voice off",
3590
3739
  "aria-pressed": chat.muted,
3591
3740
  onClick: () => chat.setMuted(!chat.muted),
3592
- children: chat.muted ? /* @__PURE__ */ jsx6(Muted, {}) : /* @__PURE__ */ jsx6(Sound, {})
3741
+ children: chat.muted ? /* @__PURE__ */ jsx7(Muted, {}) : /* @__PURE__ */ jsx7(Sound, {})
3593
3742
  }
3594
3743
  )
3595
3744
  ] })
3596
3745
  ] }),
3597
3746
  /* @__PURE__ */ jsxs6("div", { className: "stage-area", children: [
3598
3747
  /* @__PURE__ */ jsxs6("div", { className: "stage", style: { background: themeBackground(presentationBrand) }, children: [
3748
+ showing && /* @__PURE__ */ jsx7("div", { className: "ambient-media", "aria-hidden": "true", children: ambientPoster && /* @__PURE__ */ jsx7("img", { className: "frame-media", src: ambientPoster, alt: "" }) }),
3599
3749
  !showing && /* @__PURE__ */ jsxs6(Fragment4, { children: [
3600
- /* @__PURE__ */ jsx6("div", { className: "ground", "aria-hidden": "true" }),
3750
+ /* @__PURE__ */ jsx7("div", { className: "ground", "aria-hidden": "true" }),
3601
3751
  shown?.openingMedia && /* @__PURE__ */ jsxs6(Fragment4, { children: [
3602
- /* @__PURE__ */ jsx6(Frame, { media: shown.openingMedia, poster: true }),
3603
- /* @__PURE__ */ jsx6("div", { className: "opening-wash", "aria-hidden": "true" })
3752
+ /* @__PURE__ */ jsx7(Frame, { media: shown.openingMedia, poster: true }),
3753
+ /* @__PURE__ */ jsx7("div", { className: "opening-wash", "aria-hidden": "true" })
3604
3754
  ] }),
3605
- chat.turns.length === 0 && /* @__PURE__ */ jsx6(Welcome, { data: chat.welcome, onAsk: ask, title: welcomeTitle }),
3755
+ chat.turns.length === 0 && /* @__PURE__ */ jsx7(Welcome, { data: chat.welcome, onAsk: ask, title: welcomeTitle }),
3606
3756
  shown?.prompt && /* @__PURE__ */ jsxs6("div", { className: "asked", children: [
3607
- /* @__PURE__ */ jsx6("p", { className: "asked-prompt", children: shown.prompt }),
3608
- waitingForPicture && /* @__PURE__ */ jsx6("p", { className: "asked-step", "aria-live": "polite", children: filmingStep })
3757
+ /* @__PURE__ */ jsx7("p", { className: "asked-prompt", children: shown.prompt }),
3758
+ waitingForPicture && /* @__PURE__ */ jsx7("p", { className: "asked-step", "aria-live": "polite", children: filmingStep })
3609
3759
  ] })
3610
3760
  ] }),
3611
- chat.playerProps && /* @__PURE__ */ jsx6(
3761
+ chat.playerProps && /* @__PURE__ */ jsx7("div", { className: "player-fit", style: { width: stageOrientation === "portrait" ? "min(100cqw, 56.25cqh)" : "min(100cqw, 177.7778cqh)" }, children: /* @__PURE__ */ jsx7(
3612
3762
  VideoPlayer,
3613
3763
  {
3614
3764
  ...chat.playerProps,
3615
3765
  templates: options.templates,
3766
+ orientation: stageOrientation,
3616
3767
  responsiveBreakpoint: DESKTOP_WIDTH,
3617
3768
  ariaLabel: chat.playerProps.video ? "Replay" : "The response"
3618
3769
  },
3619
3770
  chat.playerKey
3620
- ),
3771
+ ) }),
3621
3772
  showing && chat.playbackEnded && chat.suggestions.length > 0 && /* @__PURE__ */ jsxs6("div", { className: "ending", children: [
3622
- /* @__PURE__ */ jsx6("div", { className: "ending-wash", "aria-hidden": "true" }),
3773
+ /* @__PURE__ */ jsx7("div", { className: "ending-wash", "aria-hidden": "true" }),
3623
3774
  /* @__PURE__ */ jsxs6("div", { className: "ending-body", children: [
3624
- /* @__PURE__ */ jsx6("p", { className: "ending-label", children: "Ask next" }),
3625
- /* @__PURE__ */ jsx6(SuggestionCards, { suggestions: [...chat.suggestions], label: "Follow-up prompts", onAsk: ask })
3775
+ /* @__PURE__ */ jsx7("p", { className: "ending-label", children: "Ask next" }),
3776
+ /* @__PURE__ */ jsx7(SuggestionCards, { suggestions: [...chat.suggestions], label: "Follow-up prompts", onAsk: ask })
3626
3777
  ] })
3627
3778
  ] })
3628
3779
  ] }),
3629
- historyOpen && /* @__PURE__ */ jsx6("nav", { ref: historyRef, id: historyId, className: "sheet-popover history", "aria-label": "Prompts this session", children: chat.turns.map((turn, index) => /* @__PURE__ */ jsxs6(
3630
- "button",
3631
- {
3632
- type: "button",
3633
- className: turn.id === shown?.id ? "current" : void 0,
3634
- disabled: !turn.completed || !turn.video,
3635
- onClick: () => {
3636
- chat.selectTurn(turn.id);
3637
- setHistoryOpen(false);
3780
+ historyOpen && /* @__PURE__ */ jsxs6("nav", { ref: historyRef, id: historyId, className: "sheet-popover history", role: "dialog", "aria-modal": "true", "aria-label": "History", children: [
3781
+ /* @__PURE__ */ jsxs6("div", { className: "popover-heading", children: [
3782
+ /* @__PURE__ */ jsx7("h2", { children: "History" }),
3783
+ /* @__PURE__ */ jsx7("button", { type: "button", className: "round", "aria-label": "Close history", onClick: closeHistory, children: /* @__PURE__ */ jsx7(Close, {}) })
3784
+ ] }),
3785
+ /* @__PURE__ */ jsx7("h3", { className: "section-label", children: "Current session" }),
3786
+ chat.turns.length === 0 && /* @__PURE__ */ jsx7("p", { className: "history-empty", children: "Your questions will appear here." }),
3787
+ chat.turns.map((turn, index) => /* @__PURE__ */ jsxs6(
3788
+ "button",
3789
+ {
3790
+ type: "button",
3791
+ className: `history-row${turn.id === shown?.id ? " current" : ""}`,
3792
+ "aria-current": turn.id === shown?.id ? true : void 0,
3793
+ disabled: !turn.completed || !turn.video,
3794
+ onClick: () => {
3795
+ listen.stop();
3796
+ setDraft("");
3797
+ setEditing(false);
3798
+ resumeAfterInput.current = false;
3799
+ chat.selectTurn(turn.id);
3800
+ setHistoryOpen(false);
3801
+ },
3802
+ children: [
3803
+ /* @__PURE__ */ jsx7("span", { className: "index", children: String(index + 1).padStart(2, "0") }),
3804
+ /* @__PURE__ */ jsxs6("span", { className: "prompt", children: [
3805
+ turn.prompt,
3806
+ /* @__PURE__ */ jsx7("small", { children: turn.id === shown?.id ? "Now showing" : turn.completed ? "Play answer" : "Unfinished answer" })
3807
+ ] })
3808
+ ]
3638
3809
  },
3639
- children: [
3640
- /* @__PURE__ */ jsx6("span", { className: "index", children: String(index + 1).padStart(2, "0") }),
3641
- /* @__PURE__ */ jsx6("span", { className: "prompt", children: turn.prompt })
3642
- ]
3643
- },
3644
- turn.id
3645
- )) }),
3810
+ turn.id
3811
+ )),
3812
+ savedSessions.length > 0 && /* @__PURE__ */ jsxs6(Fragment4, { children: [
3813
+ /* @__PURE__ */ jsx7("h3", { className: "section-label", children: "Earlier sessions" }),
3814
+ savedSessions.map((session) => /* @__PURE__ */ jsxs6("button", { type: "button", className: "history-row", onClick: () => {
3815
+ listen.stop();
3816
+ setDraft("");
3817
+ setEditing(false);
3818
+ resumeAfterInput.current = false;
3819
+ const completed = chat.turns.filter((turn) => turn.completed && turn.video);
3820
+ setSavedSessions((sessions) => [...completed.length ? [{ id: completed[0].id, turns: completed }] : [], ...sessions.filter((entry) => entry.id !== session.id)].slice(0, 10));
3821
+ restoreSession(session.turns);
3822
+ setHistoryOpen(false);
3823
+ setCaptionsExpanded(false);
3824
+ }, children: [
3825
+ /* @__PURE__ */ jsx7(Replay, {}),
3826
+ /* @__PURE__ */ jsxs6("span", { className: "prompt", children: [
3827
+ session.turns[0]?.prompt,
3828
+ /* @__PURE__ */ jsxs6("small", { children: [
3829
+ session.turns.length,
3830
+ " ",
3831
+ session.turns.length === 1 ? "answer" : "answers"
3832
+ ] })
3833
+ ] })
3834
+ ] }, session.id))
3835
+ ] })
3836
+ ] }),
3646
3837
  settingsOpen && /* @__PURE__ */ jsxs6(
3647
3838
  "div",
3648
3839
  {
@@ -3651,97 +3842,151 @@ function VideoChat({ options = {}, className, welcomeTitle }) {
3651
3842
  className: "sheet-popover settings",
3652
3843
  role: "dialog",
3653
3844
  "aria-label": "Settings",
3845
+ "aria-modal": "true",
3654
3846
  children: [
3655
- /* @__PURE__ */ jsxs6("fieldset", { children: [
3656
- /* @__PURE__ */ jsx6("legend", { children: "Appearance" }),
3657
- [["system", "Match system"], ["light", "Light"], ["dark", "Dark"]].map(([value, label]) => /* @__PURE__ */ jsxs6("label", { children: [
3658
- /* @__PURE__ */ jsx6("input", { type: "radio", name: appearanceName, checked: value === themeChoice, onChange: () => setThemeChoice(value) }),
3659
- /* @__PURE__ */ jsx6("span", { children: /* @__PURE__ */ jsx6("strong", { children: label }) })
3660
- ] }, value))
3847
+ /* @__PURE__ */ jsxs6("div", { className: "popover-heading", children: [
3848
+ /* @__PURE__ */ jsx7("h2", { children: "Settings" }),
3849
+ /* @__PURE__ */ jsx7("button", { type: "button", className: "round", "aria-label": "Close settings", onClick: closeSettings, children: /* @__PURE__ */ jsx7(Close, {}) })
3850
+ ] }),
3851
+ /* @__PURE__ */ jsxs6("fieldset", { className: "playback-options", children: [
3852
+ /* @__PURE__ */ jsx7("legend", { children: "Watching" }),
3853
+ /* @__PURE__ */ jsxs6("label", { className: "switch-row", children: [
3854
+ /* @__PURE__ */ jsxs6("span", { children: [
3855
+ /* @__PURE__ */ jsx7("strong", { children: "Subtitles" }),
3856
+ /* @__PURE__ */ jsx7("small", { children: "Read along with the answer" })
3857
+ ] }),
3858
+ /* @__PURE__ */ jsx7("input", { type: "checkbox", role: "switch", checked: captionsOn, onChange: (event) => {
3859
+ setCaptionsOn(event.target.checked);
3860
+ setCaptionsExpanded(false);
3861
+ } })
3862
+ ] }),
3863
+ /* @__PURE__ */ jsxs6("label", { className: "switch-row", children: [
3864
+ /* @__PURE__ */ jsxs6("span", { children: [
3865
+ /* @__PURE__ */ jsx7("strong", { children: "Keep controls visible" }),
3866
+ /* @__PURE__ */ jsx7("small", { children: "Keep the input bar on screen" })
3867
+ ] }),
3868
+ /* @__PURE__ */ jsx7("input", { type: "checkbox", role: "switch", checked: alwaysShowControls, onChange: (event) => setAlwaysShowControls(event.target.checked) })
3869
+ ] })
3661
3870
  ] }),
3662
- /* @__PURE__ */ jsx6("p", { className: "settings-note", children: "Visuals and style apply to your next prompt." }),
3663
- /* @__PURE__ */ jsxs6("fieldset", { children: [
3664
- /* @__PURE__ */ jsx6("legend", { children: "Visuals" }),
3665
- availableModes.map((option) => /* @__PURE__ */ jsxs6("label", { children: [
3666
- /* @__PURE__ */ jsx6("input", { type: "radio", name: visualsName, checked: option.id === selectedMode.id, onChange: () => setModeId(option.id) }),
3871
+ /* @__PURE__ */ jsxs6("fieldset", { className: "visual-options", children: [
3872
+ /* @__PURE__ */ jsx7("legend", { children: "Video creation" }),
3873
+ availableModes.map((option) => /* @__PURE__ */ jsxs6("label", { className: "choice-row", children: [
3874
+ /* @__PURE__ */ jsx7("input", { type: "radio", name: visualsName, checked: option.id === selectedMode.id, onChange: () => setModeId(option.id) }),
3667
3875
  /* @__PURE__ */ jsxs6("span", { children: [
3668
- /* @__PURE__ */ jsx6("strong", { children: option.label }),
3669
- option.note
3876
+ /* @__PURE__ */ jsx7("strong", { children: option.label }),
3877
+ /* @__PURE__ */ jsx7("small", { children: option.note })
3670
3878
  ] })
3671
3879
  ] }, option.id))
3672
3880
  ] }),
3673
- /* @__PURE__ */ jsxs6("fieldset", { children: [
3674
- /* @__PURE__ */ jsx6("legend", { children: "Style" }),
3881
+ /* @__PURE__ */ jsxs6("fieldset", { className: "style-options", children: [
3882
+ /* @__PURE__ */ jsx7("legend", { children: "Video style" }),
3675
3883
  themes.map((option) => /* @__PURE__ */ jsxs6("label", { children: [
3676
- /* @__PURE__ */ jsx6("input", { type: "radio", name: styleName, checked: option.id === themeId, onChange: () => setThemeId(option.id) }),
3677
- /* @__PURE__ */ jsx6("span", { children: /* @__PURE__ */ jsx6("strong", { children: option.label }) })
3884
+ /* @__PURE__ */ jsx7("input", { type: "radio", name: styleName, checked: option.id === themeId, onChange: () => setThemeId(option.id) }),
3885
+ /* @__PURE__ */ jsx7("span", { children: /* @__PURE__ */ jsx7("strong", { children: option.label }) })
3678
3886
  ] }, option.id))
3887
+ ] }),
3888
+ /* @__PURE__ */ jsx7("p", { className: "settings-note", children: "Creation and style changes apply to your next question." }),
3889
+ /* @__PURE__ */ jsxs6("nav", { className: "developer-links", "aria-label": "Build with VanillaSky", children: [
3890
+ /* @__PURE__ */ jsx7("p", { className: "section-label", children: "Build with VanillaSky" }),
3891
+ /* @__PURE__ */ jsxs6("a", { href: "https://github.com/VanillaSkyAi/video/blob/main/docs/getting-started.md", target: "_blank", rel: "noopener noreferrer", children: [
3892
+ "Docs",
3893
+ /* @__PURE__ */ jsx7("span", { "aria-hidden": "true", children: "\u2197" })
3894
+ ] }),
3895
+ /* @__PURE__ */ jsxs6("button", { type: "button", "aria-expanded": aboutOpen, "aria-controls": `${instanceId}-about`, onClick: () => setAboutOpen((open) => !open), children: [
3896
+ "About",
3897
+ /* @__PURE__ */ jsx7("span", { "aria-hidden": "true", children: aboutOpen ? "\u2212" : "+" })
3898
+ ] }),
3899
+ /* @__PURE__ */ jsx7("div", { id: `${instanceId}-about`, className: "developer-about", role: "region", "aria-label": "About VanillaSky", hidden: !aboutOpen, children: /* @__PURE__ */ jsx7("p", { children: "VanillaSky is an open-source SDK for conversations that answer in video. Developers connect their own AI providers through their application server." }) }),
3900
+ /* @__PURE__ */ jsxs6("a", { href: "https://github.com/VanillaSkyAi/video", target: "_blank", rel: "noopener noreferrer", children: [
3901
+ "GitHub",
3902
+ /* @__PURE__ */ jsx7("span", { "aria-hidden": "true", children: "\u2197" })
3903
+ ] })
3679
3904
  ] })
3680
3905
  ]
3681
3906
  }
3682
3907
  )
3683
3908
  ] }),
3684
- /* @__PURE__ */ jsx6("div", { className: "panel", children: /* @__PURE__ */ jsxs6("div", { className: "panel-inner", children: [
3685
- /* @__PURE__ */ jsxs6("div", { className: "line-row", children: [
3686
- /* @__PURE__ */ jsx6("p", { className: "line", "aria-live": "polite", children: line }),
3687
- shown?.video && /* @__PURE__ */ jsxs6("button", { type: "button", className: "full-response", onClick: () => setSheetOpen(true), children: [
3688
- "Full response ",
3689
- /* @__PURE__ */ jsx6(ChevronUp, {})
3690
- ] })
3691
- ] }),
3909
+ /* @__PURE__ */ jsx7("div", { ref: panelRef, className: "panel", "data-input-visible": controls.visible || !captionsOn || !line, children: /* @__PURE__ */ jsxs6("div", { className: "panel-inner", children: [
3910
+ /* @__PURE__ */ jsx7("div", { className: "caption-slot", "data-captions": captionsOn && Boolean(line), "aria-hidden": !captionsOn || !line, children: /* @__PURE__ */ jsx7("div", { className: "caption-clip", children: /* @__PURE__ */ jsxs6(
3911
+ "div",
3912
+ {
3913
+ className: "line-row",
3914
+ "data-expanded": captionsExpanded,
3915
+ "data-actions-visible": captionControls.visible,
3916
+ onPointerMove: captionControls.onPointerEnter,
3917
+ onPointerLeave: captionControls.onPointerLeave,
3918
+ onPointerDown: captionControls.reveal,
3919
+ onFocusCapture: captionControls.onFocusCapture,
3920
+ onBlurCapture: captionControls.onBlurCapture,
3921
+ children: [
3922
+ captionsOn && line && /* @__PURE__ */ jsxs6("div", { className: "caption-actions", children: [
3923
+ /* @__PURE__ */ jsxs6("button", { type: "button", className: "caption-action", "aria-label": captionsExpanded ? "Collapse subtitles" : "Expand subtitles", "aria-expanded": captionsExpanded, onClick: () => setCaptionsExpanded((open) => !open), children: [
3924
+ captionsExpanded ? "Collapse" : "Expand",
3925
+ /* @__PURE__ */ jsx7(ChevronUp, {})
3926
+ ] }),
3927
+ /* @__PURE__ */ jsx7("button", { type: "button", className: "caption-action", "aria-label": "Hide subtitles", onClick: () => {
3928
+ setCaptionsOn(false);
3929
+ setCaptionsExpanded(false);
3930
+ }, children: /* @__PURE__ */ jsx7(Close, {}) })
3931
+ ] }),
3932
+ captionsExpanded ? /* @__PURE__ */ jsx7("div", { className: "expanded-captions", role: "region", tabIndex: 0, "aria-label": "Expanded subtitles", children: fullTranscript.map((entry, index) => /* @__PURE__ */ jsx7("p", { children: entry }, index)) }) : /* @__PURE__ */ jsx7("p", { className: "line", "aria-live": "polite", children: line })
3933
+ ]
3934
+ }
3935
+ ) }) }),
3692
3936
  (chat.error || listen.error) && /* @__PURE__ */ jsxs6("p", { className: "error", role: "status", children: [
3693
- /* @__PURE__ */ jsx6(Warning, {}),
3694
- /* @__PURE__ */ jsx6("span", { children: chat.error?.message ?? listen.error })
3937
+ /* @__PURE__ */ jsx7(Warning, {}),
3938
+ /* @__PURE__ */ jsx7("span", { children: chat.error?.message ?? listen.error })
3695
3939
  ] }),
3696
- /* @__PURE__ */ jsxs6("form", { className: "composer", onSubmit: (event) => {
3697
- event.preventDefault();
3698
- ask(draft);
3699
- }, children: [
3700
- /* @__PURE__ */ jsx6(Waveform, { active: listen.listening || chat.speaking && !chat.muted && status !== "paused", listening: listen.listening }),
3701
- /* @__PURE__ */ jsx6("label", { className: "sr-only", htmlFor: promptId, children: "Prompt" }),
3702
- /* @__PURE__ */ jsx6(
3703
- "textarea",
3704
- {
3705
- id: promptId,
3706
- ref: inputRef,
3707
- rows: 1,
3708
- value: draft,
3709
- placeholder: listen.listening ? "Listening\u2026" : listen.thinking ? "Working out what you said\u2026" : status === "narrating" ? "Talking \u2014 type to jump in\u2026" : chat.turns.length > 0 ? "Ask a follow-up\u2026" : "Ask anything\u2026",
3710
- onChange: (event) => setDraft(event.target.value),
3711
- onKeyDown: (event) => {
3712
- if (event.key === "Enter" && !event.shiftKey) {
3713
- event.preventDefault();
3714
- ask(draft);
3940
+ /* @__PURE__ */ jsxs6("div", { ref: composerRef, className: "conversation-composer", "data-editing": editing, ...controlEvents, children: [
3941
+ (listen.listening || listen.thinking) && /* @__PURE__ */ jsx7("div", { className: "composer-meta", children: /* @__PURE__ */ jsx7("span", { role: "status", children: listen.listening ? "Listening\u2026 Tap the mic to finish, then review and send." : "Turning your words into a draft\u2026" }) }),
3942
+ /* @__PURE__ */ jsxs6("form", { className: "composer", "aria-label": "Ask a question", onSubmit: (event) => {
3943
+ event.preventDefault();
3944
+ ask(draft);
3945
+ }, children: [
3946
+ transport && /* @__PURE__ */ jsx7("button", { type: "button", className: "ghost transport", "aria-label": transport.label, onClick: () => {
3947
+ listen.stop();
3948
+ setEditing(false);
3949
+ resumeAfterInput.current = false;
3950
+ inputRef.current?.blur();
3951
+ transport.action();
3952
+ }, children: transport.icon }),
3953
+ /* @__PURE__ */ jsx7(Waveform, { active: listen.listening || chat.speaking && !chat.muted && status !== "paused", listening: listen.listening }),
3954
+ /* @__PURE__ */ jsx7("label", { className: "sr-only", htmlFor: promptId, children: "Prompt" }),
3955
+ /* @__PURE__ */ jsx7(
3956
+ "textarea",
3957
+ {
3958
+ id: promptId,
3959
+ ref: inputRef,
3960
+ rows: 1,
3961
+ value: draft,
3962
+ placeholder: listen.listening ? "Listening\u2026" : chat.turns.length ? "Ask a follow-up\u2026" : "Ask anything\u2026",
3963
+ onFocus: () => {
3964
+ if (!editing) beginInput();
3965
+ },
3966
+ onChange: (event) => {
3967
+ setDraft(event.target.value);
3968
+ event.target.style.height = "auto";
3969
+ event.target.style.height = `${Math.min(event.target.scrollHeight, 120)}px`;
3970
+ },
3971
+ onKeyDown: (event) => {
3972
+ if (event.key === "Escape") {
3973
+ event.stopPropagation();
3974
+ cancelInput();
3975
+ }
3976
+ if (event.key === "Enter" && !event.shiftKey && !event.nativeEvent.isComposing) {
3977
+ event.preventDefault();
3978
+ ask(draft);
3979
+ }
3715
3980
  }
3716
3981
  }
3717
- }
3718
- ),
3719
- transport && /* @__PURE__ */ jsx6("button", { type: "button", className: "ghost transport", "aria-label": transport.label, onClick: transport.action, children: transport.icon }),
3720
- listen.supported && /* @__PURE__ */ jsx6(
3721
- "button",
3722
- {
3723
- type: "button",
3724
- className: `ghost${listen.listening ? " listening" : ""}`,
3725
- "aria-label": listen.listening ? "Stop listening" : "Ask by voice",
3726
- "aria-pressed": listen.listening,
3727
- disabled: listen.thinking,
3728
- onClick: listen.toggle,
3729
- children: /* @__PURE__ */ jsx6(Mic, {})
3730
- }
3731
- ),
3732
- /* @__PURE__ */ jsx6("button", { type: "submit", className: "send", "aria-label": "Ask", disabled: !draft.trim() || status === "drawing", children: /* @__PURE__ */ jsx6(Send, {}) })
3733
- ] })
3734
- ] }) }),
3735
- sheetOpen && shown && /* @__PURE__ */ jsxs6("div", { className: "sheet-layer", children: [
3736
- /* @__PURE__ */ jsx6("button", { type: "button", className: "scrim", tabIndex: -1, "aria-hidden": "true", onClick: () => setSheetOpen(false) }),
3737
- /* @__PURE__ */ jsxs6("article", { ref: sheetRef, className: "sheet", role: "dialog", "aria-modal": "true", "aria-labelledby": responseTitleId, children: [
3738
- /* @__PURE__ */ jsxs6("header", { children: [
3739
- /* @__PURE__ */ jsx6("h1", { id: responseTitleId, children: shown.prompt }),
3740
- /* @__PURE__ */ jsx6("button", { type: "button", className: "round", "aria-label": "Close the full response", onClick: () => setSheetOpen(false), children: /* @__PURE__ */ jsx6(Close, {}) })
3741
- ] }),
3742
- /* @__PURE__ */ jsx6("div", { className: "sheet-body", children: chat.transcript.map((entry, index) => /* @__PURE__ */ jsx6("p", { children: entry }, `${index}-${entry}`)) })
3743
- ] })
3744
- ] })
3982
+ ),
3983
+ listen.supported && /* @__PURE__ */ jsx7("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__ */ jsx7(Mic, {}) }),
3984
+ /* @__PURE__ */ jsx7("button", { type: "submit", className: "send", "aria-label": "Ask", disabled: !draft.trim() || listen.thinking, children: /* @__PURE__ */ jsx7(Send, {}) }),
3985
+ editing && /* @__PURE__ */ jsx7("button", { type: "button", className: "ghost", "aria-label": "Cancel question", onClick: cancelInput, children: /* @__PURE__ */ jsx7(Close, {}) })
3986
+ ] })
3987
+ ] }),
3988
+ !showing && chat.turns.length === 0 && /* @__PURE__ */ jsx7("p", { className: "dock-hint", children: "Speak or type. See where it takes you." })
3989
+ ] }) })
3745
3990
  ]
3746
3991
  }
3747
3992
  );