@alexkroman1/aai-ui 1.8.2 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/dist/_colors-DJUordGv.js +19 -0
  2. package/dist/_utils-5cs73OrA.js +16 -0
  3. package/dist/_utils.d.ts +4 -0
  4. package/dist/aai-logo-B8lDmsut.js +84 -0
  5. package/dist/audio.d.ts +9 -2
  6. package/dist/audio.js +41 -24
  7. package/dist/chat-view-C1XbqDk0.js +186 -0
  8. package/dist/components/_colors.d.ts +26 -0
  9. package/dist/components/aai-logo.d.ts +7 -6
  10. package/dist/components/button.d.ts +11 -13
  11. package/dist/components/button.js +7 -16
  12. package/dist/components/chat-view.d.ts +8 -19
  13. package/dist/components/chat-view.js +2 -98
  14. package/dist/components/controls.d.ts +1 -1
  15. package/dist/components/controls.js +2 -39
  16. package/dist/components/eyebrow.d.ts +12 -0
  17. package/dist/components/message-list.d.ts +1 -1
  18. package/dist/components/message-list.js +126 -56
  19. package/dist/components/sidebar-layout.d.ts +1 -1
  20. package/dist/components/start-screen.d.ts +4 -3
  21. package/dist/components/start-screen.js +20 -14
  22. package/dist/components/text-controls.d.ts +14 -0
  23. package/dist/components/tool-call-block.d.ts +5 -3
  24. package/dist/components/tool-call-block.js +1 -1
  25. package/dist/components/url-chips.d.ts +28 -0
  26. package/dist/context.d.ts +30 -4
  27. package/dist/context.js +86 -10
  28. package/dist/controls-BngrPbOC.js +135 -0
  29. package/dist/default-client/assets/audio-Cs-6t_Wd.js +1 -0
  30. package/dist/default-client/assets/capture-processor-C19oBn4L.js +105 -0
  31. package/dist/default-client/assets/index-Bf4ZTNcx.js +73 -0
  32. package/dist/default-client/assets/index-Bzlh9i7w.css +2 -0
  33. package/dist/default-client/assets/playback-processor-BtlzAH78.js +149 -0
  34. package/dist/default-client/index.html +3 -3
  35. package/dist/define-client.d.ts +2 -2
  36. package/dist/define-client.js +18 -24
  37. package/dist/eyebrow-C6ZFuiz6.js +27 -0
  38. package/dist/hooks.js +71 -42
  39. package/dist/index.d.ts +3 -1
  40. package/dist/index.js +6 -5
  41. package/dist/session-core-D3NDaySY.js +652 -0
  42. package/dist/session-core-messages.d.ts +50 -0
  43. package/dist/session-core-types.d.ts +147 -0
  44. package/dist/session-core-upload.d.ts +16 -0
  45. package/dist/session-core.d.ts +2 -68
  46. package/dist/session-core.js +1 -455
  47. package/dist/{tool-call-block-wby_jyoY.js → tool-call-block-7f1GTG-P.js} +33 -31
  48. package/dist/types.d.ts +29 -4
  49. package/dist/types.js +10 -1
  50. package/dist/worklets/capture-processor.d.ts +2 -0
  51. package/dist/worklets/capture-processor.js +80 -25
  52. package/dist/worklets/playback-processor.d.ts +2 -0
  53. package/dist/worklets/playback-processor.js +80 -29
  54. package/package.json +18 -18
  55. package/styles.css +30 -0
  56. package/dist/_react-test-utils.d.ts +0 -86
  57. package/dist/aai-logo-BqFv6JU6.js +0 -21
  58. package/dist/default-client/assets/audio-UowwhmYo.js +0 -1
  59. package/dist/default-client/assets/capture-processor-C26lSiVr.js +0 -53
  60. package/dist/default-client/assets/index-BZqVGR2o.css +0 -2
  61. package/dist/default-client/assets/index-YW9WUhbL.js +0 -49
  62. package/dist/default-client/assets/playback-processor-dSA8Im99.js +0 -101
@@ -0,0 +1,2 @@
1
+ /*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
2
+ @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-duration:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--spacing:.25rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--font-weight-normal:400;--font-weight-medium:500;--tracking-wide:.025em;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--ease-in-out:cubic-bezier(.4, 0, .2, 1);--animate-pulse:pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--font-aai:"Monument Grotesk", "ABC Monument Grotesk", ui-sans-serif, system-ui, -apple-system, sans-serif;--font-aai-serif:"Source Serif 4", "Source Serif Pro", Charter, "Iowan Old Style", Georgia, serif;--font-aai-mono:"JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;--radius-aai:4px}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}html,body{margin:0;padding:0}}@layer components;@layer utilities{.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.container{width:100%}@media (width>=40rem){.container{max-width:40rem}}@media (width>=48rem){.container{max-width:48rem}}@media (width>=64rem){.container{max-width:64rem}}@media (width>=80rem){.container{max-width:80rem}}@media (width>=96rem){.container{max-width:96rem}}.m-0{margin:0}.mx-auto{margin-inline:auto}.mt-2{margin-top:calc(var(--spacing) * 2)}.mr-2{margin-right:calc(var(--spacing) * 2)}.ml-auto{margin-left:auto}.box-border{box-sizing:border-box}.block{display:block}.flex{display:flex}.hidden{display:none}.inline{display:inline}.inline-flex{display:inline-flex}.h-1\.5{height:calc(var(--spacing) * 1.5)}.h-2{height:calc(var(--spacing) * 2)}.h-4{height:calc(var(--spacing) * 4)}.h-9{height:calc(var(--spacing) * 9)}.h-11{height:calc(var(--spacing) * 11)}.h-\[7px\]{height:7px}.h-screen{height:100vh}.max-h-64{max-height:calc(var(--spacing) * 64)}.min-h-0{min-height:0}.min-h-5{min-height:calc(var(--spacing) * 5)}.w-1\.5{width:calc(var(--spacing) * 1.5)}.w-2{width:calc(var(--spacing) * 2)}.w-4{width:calc(var(--spacing) * 4)}.w-\[7px\]{width:7px}.w-fit{width:fit-content}.w-full{width:100%}.max-w-75{max-width:calc(var(--spacing) * 75)}.max-w-105{max-width:calc(var(--spacing) * 105)}.max-w-190{max-width:calc(var(--spacing) * 190)}.max-w-\[55\%\]{max-width:55%}.max-w-\[60\%\]{max-width:60%}.max-w-\[82\%\]{max-width:82%}.max-w-\[min\(78\%\,64ch\)\]{max-width:min(78%,64ch)}.min-w-0{min-width:0}.flex-1{flex:1}.shrink-0{flex-shrink:0}.rotate-90{rotate:90deg}.animate-pulse{animation:var(--animate-pulse)}.cursor-pointer{cursor:pointer}.\[scrollbar-width\:none\]{scrollbar-width:none}.appearance-none{appearance:none}.flex-col{flex-direction:column}.items-center{align-items:center}.items-end{align-items:flex-end}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-1{gap:var(--spacing)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-2\.5{gap:calc(var(--spacing) * 2.5)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-5{gap:calc(var(--spacing) * 5)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.rounded-aai{border-radius:var(--radius-aai)}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-none{--tw-border-style:none;border-style:none}.bg-transparent{background-color:#0000}.p-7{padding:calc(var(--spacing) * 7)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-3\.5{padding-inline:calc(var(--spacing) * 3.5)}.px-5{padding-inline:calc(var(--spacing) * 5)}.px-6{padding-inline:calc(var(--spacing) * 6)}.px-7{padding-inline:calc(var(--spacing) * 7)}.px-10{padding-inline:calc(var(--spacing) * 10)}.py-1{padding-block:var(--spacing)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-8{padding-block:calc(var(--spacing) * 8)}.py-12{padding-block:calc(var(--spacing) * 12)}.text-center{text-align:center}.text-left{text-align:left}.font-aai{font-family:var(--font-aai)}.font-aai-mono{font-family:var(--font-aai-mono)}.font-aai-serif{font-family:var(--font-aai-serif)}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[12px\]{font-size:12px}.text-\[13px\]{font-size:13px}.text-\[15px\]{font-size:15px}.text-\[22px\]{font-size:22px}.text-\[32px\]{font-size:32px}.leading-4{--tw-leading:calc(var(--spacing) * 4);line-height:calc(var(--spacing) * 4)}.leading-\[1\.2\]{--tw-leading:1.2;line-height:1.2}.leading-\[1\.15\]{--tw-leading:1.15;line-height:1.15}.leading-\[22px\]{--tw-leading:22px;line-height:22px}.leading-\[23px\]{--tw-leading:23px;line-height:23px}.leading-\[130\%\]{--tw-leading:130%;line-height:130%}.leading-none{--tw-leading:1;line-height:1}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.tracking-\[-0\.2px\]{--tw-tracking:-.2px;letter-spacing:-.2px}.tracking-\[1\.2px\]{--tw-tracking:1.2px;letter-spacing:1.2px}.tracking-\[1\.4px\]{--tw-tracking:1.4px;letter-spacing:1.4px}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.text-balance{text-wrap:balance}.wrap-break-word{overflow-wrap:break-word}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.uppercase{text-transform:uppercase}.ring{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-150{--tw-duration:.15s;transition-duration:.15s}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.outline-none{--tw-outline-style:none;outline-style:none}.select-none{-webkit-user-select:none;user-select:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}@media (width>=40rem){.sm\:px-16{padding-inline:calc(var(--spacing) * 16)}.sm\:py-14{padding-block:calc(var(--spacing) * 14)}}}@keyframes aai-pulse{0%,to{opacity:1;transform:scale(1)}50%{opacity:.45;transform:scale(.82)}}@keyframes aai-bounce{0%,80%,to{opacity:.3;transform:scale(.8)}40%{opacity:1;transform:scale(1)}}@keyframes aai-shimmer{0%{background-position:-200% 0}to{background-position:200% 0}}.tool-shimmer{-webkit-text-fill-color:transparent;background:linear-gradient(90deg,currentColor 25%,#0000 50%,currentColor 75%) 0 0/200% 100%;-webkit-background-clip:text;background-clip:text;animation:2s infinite aai-shimmer}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@keyframes pulse{50%{opacity:.5}}
@@ -0,0 +1,149 @@
1
+ var e=new Blob([`
2
+ class PlaybackProcessor extends AudioWorkletProcessor {
3
+ constructor(options) {
4
+ super();
5
+ const rate = options.processorOptions?.sampleRate ?? 24000;
6
+ // Wait for ~400ms of audio before starting.
7
+ // If 'done' arrives first (short utterance), start immediately.
8
+ this.jitterSamples = Math.floor(rate * 0.4);
9
+ // Float32 ring buffer — 60s at the context sample rate. Allocated once for
10
+ // the node's lifetime; per-turn state resets via resetTurn(). writePos and
11
+ // readPos are absolute (monotonic) sample counts; the buffer is indexed
12
+ // modulo capacity so a reply longer than 60s keeps playing instead of
13
+ // writing past the end and going silent.
14
+ this.capacity = rate * 60;
15
+ this.samples = new Float32Array(this.capacity);
16
+ // Platform endianness probe: the wire format is PCM16 little-endian, so
17
+ // the Int16Array fast path in ingestBytes is only valid on LE hosts
18
+ // (every shipping browser target; the DataView path is the fallback).
19
+ this.littleEndian = new Uint8Array(new Uint16Array([1]).buffer)[0] === 1;
20
+ this.resetTurn();
21
+
22
+ this.port.onmessage = (e) => {
23
+ const d = e.data;
24
+ if (d.event === 'write') {
25
+ this.ingestBytes(d.buffer);
26
+ } else if (d.event === 'interrupt') {
27
+ this.interrupted = true;
28
+ } else if (d.event === 'done') {
29
+ this.isDone = true;
30
+ }
31
+ };
32
+ }
33
+
34
+ // Reset per-turn state so the node is reusable across replies without
35
+ // reallocating the sample buffer or re-instantiating the worklet.
36
+ resetTurn() {
37
+ this.interrupted = false;
38
+ this.isDone = false;
39
+ this.playing = false;
40
+ // Carry-over byte for split samples across chunks
41
+ this.carry = null;
42
+ this.writePos = 0;
43
+ this.readPos = 0;
44
+ }
45
+
46
+ // End the current turn: notify the host and rearm for the next reply.
47
+ // Must NOT return false from process() — a processor that stops is dead
48
+ // for good, forcing a new node (and buffer) per reply.
49
+ stopTurn() {
50
+ this.port.postMessage({ event: 'stop' });
51
+ this.resetTurn();
52
+ }
53
+
54
+ ingestBytes(uint8) {
55
+ let bytes = uint8;
56
+
57
+ if (this.carry !== null) {
58
+ const merged = new Uint8Array(1 + bytes.length);
59
+ merged[0] = this.carry;
60
+ merged.set(bytes, 1);
61
+ bytes = merged;
62
+ this.carry = null;
63
+ }
64
+
65
+ if (bytes.length % 2 !== 0) {
66
+ this.carry = bytes[bytes.length - 1];
67
+ bytes = bytes.subarray(0, bytes.length - 1);
68
+ }
69
+
70
+ if (bytes.length === 0) return;
71
+ const numSamples = bytes.length / 2;
72
+ const cap = this.capacity;
73
+ const samples = this.samples;
74
+ if (this.littleEndian && (bytes.byteOffset & 1) === 0) {
75
+ // Fast path: 2-byte-aligned LE bytes wrap directly as an Int16Array;
76
+ // copy wrap-aware in at most two runs with no per-sample DataView call
77
+ // or modulo. This runs on the realtime audio thread.
78
+ const int16 = new Int16Array(bytes.buffer, bytes.byteOffset, numSamples);
79
+ let src = 0;
80
+ let dst = this.writePos % cap;
81
+ while (src < numSamples) {
82
+ const run = Math.min(numSamples - src, cap - dst);
83
+ for (let j = 0; j < run; j++) {
84
+ samples[dst + j] = int16[src + j] / 0x8000;
85
+ }
86
+ src += run;
87
+ dst = 0;
88
+ }
89
+ } else {
90
+ // Odd byte offset (or big-endian host): fall back to per-sample reads.
91
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.length);
92
+ let dst = this.writePos % cap;
93
+ for (let i = 0; i < numSamples; i++) {
94
+ samples[dst] = view.getInt16(i * 2, true) / 0x8000;
95
+ dst++;
96
+ if (dst === cap) dst = 0;
97
+ }
98
+ }
99
+ this.writePos += numSamples;
100
+ // If the producer outran the consumer by more than the buffer holds, drop
101
+ // the oldest unplayed audio rather than reading samples we've overwritten.
102
+ if (this.writePos - this.readPos > this.capacity) {
103
+ this.readPos = this.writePos - this.capacity;
104
+ }
105
+ }
106
+
107
+ process(inputs, outputs) {
108
+ const out = outputs[0][0];
109
+ if (this.interrupted) {
110
+ out.fill(0);
111
+ this.stopTurn();
112
+ return true;
113
+ }
114
+
115
+ const avail = this.writePos - this.readPos;
116
+
117
+ // Wait for jitter buffer to fill, unless done (short utterance)
118
+ if (!this.playing) {
119
+ if (avail >= this.jitterSamples || this.isDone) {
120
+ this.playing = true;
121
+ } else {
122
+ out.fill(0);
123
+ return true;
124
+ }
125
+ }
126
+
127
+ if (avail > 0) {
128
+ const n = Math.min(avail, out.length);
129
+ // Copy from the ring buffer, splitting across the wrap boundary.
130
+ const start = this.readPos % this.capacity;
131
+ const first = Math.min(n, this.capacity - start);
132
+ out.set(this.samples.subarray(start, start + first), 0);
133
+ if (n > first) out.set(this.samples.subarray(0, n - first), first);
134
+ this.readPos += n;
135
+ out.fill(0, n);
136
+ return true;
137
+ }
138
+
139
+ // No data: output silence, end the turn only when done
140
+ out.fill(0);
141
+ if (this.isDone) {
142
+ this.stopTurn();
143
+ }
144
+ return true;
145
+ }
146
+ }
147
+
148
+ registerProcessor('playback-processor', PlaybackProcessor);
149
+ `],{type:`application/javascript`}),t=URL.createObjectURL(e);export{t as default};
@@ -5,9 +5,9 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
6
6
  <title>aai</title>
7
7
  <link rel="icon" href="data:," />
8
- <style>html, body { background: #101010; margin: 0; }</style>
9
- <script type="module" crossorigin src="./assets/index-YW9WUhbL.js"></script>
10
- <link rel="stylesheet" crossorigin href="./assets/index-BZqVGR2o.css">
8
+ <style>html, body { background: #FBF8F2; margin: 0; }</style>
9
+ <script type="module" crossorigin src="./assets/index-Bf4ZTNcx.js"></script>
10
+ <link rel="stylesheet" crossorigin href="./assets/index-Bzlh9i7w.css">
11
11
  </head>
12
12
  <body>
13
13
  <main id="app"></main>
@@ -1,8 +1,8 @@
1
1
  /** @jsxImportSource react */
2
2
  import { type ComponentType } from "react";
3
3
  import { type ToolDisplayConfig } from "./components/tool-config-context.ts";
4
- import { type SessionCore, type WebSocketConstructor } from "./session-core.ts";
5
- import type { ClientTheme } from "./types.ts";
4
+ import { type SessionCore } from "./session-core.ts";
5
+ import type { ClientTheme, WebSocketConstructor } from "./types.ts";
6
6
  /**
7
7
  * Base options shared by both client tiers.
8
8
  *
@@ -1,9 +1,9 @@
1
1
  import { SessionProvider, ThemeProvider } from "./context.js";
2
- import { n as ToolConfigContext } from "./tool-call-block-wby_jyoY.js";
3
- import { ChatView } from "./components/chat-view.js";
2
+ import { n as ToolConfigContext } from "./tool-call-block-7f1GTG-P.js";
3
+ import { t as ChatView } from "./chat-view-C1XbqDk0.js";
4
4
  import { SidebarLayout } from "./components/sidebar-layout.js";
5
5
  import { StartScreen } from "./components/start-screen.js";
6
- import { createSessionCore } from "./session-core.js";
6
+ import { t as createSessionCore } from "./session-core-D3NDaySY.js";
7
7
  import { createElement } from "react";
8
8
  import { jsx } from "react/jsx-runtime";
9
9
  import { flushSync } from "react-dom";
@@ -21,15 +21,14 @@ function resolveContainer(target = "#app") {
21
21
  * Wraps StartScreen → (SidebarLayout →) ChatView.
22
22
  */
23
23
  function DefaultShell({ name, Sidebar, sidebarWidth }) {
24
- const chat = /* @__PURE__ */ jsx(ChatView, { ...name !== void 0 ? { title: name } : {} });
25
- const inner = Sidebar ? /* @__PURE__ */ jsx(SidebarLayout, {
26
- sidebar: /* @__PURE__ */ jsx(Sidebar, {}),
27
- ...sidebarWidth !== void 0 ? { sidebarWidth } : {},
28
- children: chat
29
- }) : chat;
24
+ const chat = /* @__PURE__ */ jsx(ChatView, { title: name });
30
25
  return /* @__PURE__ */ jsx(StartScreen, {
31
- ...name !== void 0 ? { title: name } : {},
32
- children: inner
26
+ title: name,
27
+ children: Sidebar ? /* @__PURE__ */ jsx(SidebarLayout, {
28
+ sidebar: /* @__PURE__ */ jsx(Sidebar, {}),
29
+ sidebarWidth,
30
+ children: chat
31
+ }) : chat
33
32
  });
34
33
  }
35
34
  /**
@@ -67,22 +66,17 @@ function client(config) {
67
66
  platformUrl: config.platformUrl ?? globalThis.location.origin + globalThis.location.pathname,
68
67
  onSessionId: config.onSessionId,
69
68
  resumeSessionId: config.resumeSessionId,
70
- ...config.WebSocket ? { WebSocket: config.WebSocket } : {}
69
+ WebSocket: config.WebSocket
71
70
  });
72
- let RootComponent;
73
- if ("component" in config && config.component) RootComponent = config.component;
74
- else {
75
- const { name, sidebar: Sidebar, sidebarWidth } = config;
76
- RootComponent = () => createElement(DefaultShell, {
77
- ...name !== void 0 ? { name } : {},
78
- ...Sidebar !== void 0 ? { Sidebar } : {},
79
- ...sidebarWidth !== void 0 ? { sidebarWidth } : {}
80
- });
81
- }
82
- const toolConfig = "tools" in config && config.tools ? config.tools : {};
71
+ const rootNode = config.component ? createElement(config.component) : createElement(DefaultShell, {
72
+ name: config.name,
73
+ Sidebar: config.sidebar,
74
+ sidebarWidth: config.sidebarWidth
75
+ });
76
+ const toolConfig = config.tools ?? {};
83
77
  const root = createRoot(container);
84
78
  flushSync(() => {
85
- root.render(createElement(ToolConfigContext.Provider, { value: toolConfig }, createElement(ThemeProvider, config.theme !== void 0 ? { value: config.theme } : {}, createElement(SessionProvider, { value: session }, createElement(RootComponent)))));
79
+ root.render(createElement(ToolConfigContext.Provider, { value: toolConfig }, createElement(ThemeProvider, { value: config.theme }, createElement(SessionProvider, { value: session }, rootNode))));
86
80
  });
87
81
  const handle = {
88
82
  session,
@@ -0,0 +1,27 @@
1
+ import { useTheme } from "./context.js";
2
+ import clsx from "clsx";
3
+ import { jsx } from "react/jsx-runtime";
4
+ //#region components/eyebrow.tsx
5
+ /** @jsxImportSource react */
6
+ /**
7
+ * Eyebrow label — the design system's small outlined pill with ALL-CAPS,
8
+ * tracked text ("VOICE AGENT", "LISTENING", …). Used as a section label on
9
+ * the start screen and as the live status chip in the chat header.
10
+ *
11
+ * @internal
12
+ */
13
+ function Eyebrow({ children, className, style, ...rest }) {
14
+ const theme = useTheme();
15
+ return /* @__PURE__ */ jsx("span", {
16
+ className: clsx("inline-flex items-center gap-1.5 px-2 py-1 rounded-aai border leading-none", "font-aai text-[10px] font-medium tracking-[1.2px] uppercase", className),
17
+ style: {
18
+ borderColor: theme.border,
19
+ color: theme.text,
20
+ ...style
21
+ },
22
+ ...rest,
23
+ children
24
+ });
25
+ }
26
+ //#endregion
27
+ export { Eyebrow as t };
package/dist/hooks.js CHANGED
@@ -1,73 +1,102 @@
1
- import { useSession } from "./context.js";
1
+ import { useSessionSelector } from "./context.js";
2
+ import { n as tryParseJSON } from "./_utils-5cs73OrA.js";
2
3
  import { useEffect, useRef } from "react";
3
4
  //#region hooks.ts
4
- function tryParseJSON(str) {
5
- if (!str) return str;
6
- try {
7
- return JSON.parse(str);
8
- } catch {
9
- return str;
5
+ /** Index of the first item whose sequence number is above the watermark (tail scan from the end). */
6
+ function tailStart(items, seqOf, watermark) {
7
+ let start = items.length;
8
+ for (let i = items.length - 1; i >= 0; i--) {
9
+ const item = items[i];
10
+ if (!item || seqOf(item) <= watermark) break;
11
+ start = i;
10
12
  }
13
+ return start;
11
14
  }
12
15
  /**
13
- * Shared effect loop: fires `onNew` once per tool call that passes `shouldProcess`.
14
- * Clears seen IDs when toolCalls is empty (session reset).
16
+ * Process the unprocessed tail of `toolCalls`: fire `onNew` once per item that
17
+ * has settled (per `isSettled`), then advance the watermark past the leading
18
+ * run of settled items. Resets the cursor when the list is empty (session
19
+ * reset), matching the previous seen-set behavior.
15
20
  */
16
- function processToolCalls(toolCalls, seen, shouldProcess, onNew) {
21
+ function processToolCallTail(toolCalls, cursor, isSettled, onNew) {
17
22
  if (toolCalls.length === 0) {
18
- seen.clear();
23
+ cursor.seq = 0;
24
+ cursor.fired.clear();
19
25
  return;
20
26
  }
21
- for (const tc of toolCalls) {
22
- if (!shouldProcess(tc)) continue;
23
- if (seen.has(tc.callId)) continue;
24
- seen.add(tc.callId);
27
+ const start = tailStart(toolCalls, (tc) => tc.seq, cursor.seq);
28
+ for (let i = start; i < toolCalls.length; i++) {
29
+ const tc = toolCalls[i];
30
+ if (!(tc && isSettled(tc)) || cursor.fired.has(tc.callId)) continue;
31
+ cursor.fired.add(tc.callId);
25
32
  onNew(tc);
26
33
  }
34
+ for (let i = start; i < toolCalls.length; i++) {
35
+ const tc = toolCalls[i];
36
+ if (!(tc && isSettled(tc))) break;
37
+ cursor.fired.delete(tc.callId);
38
+ cursor.seq = tc.seq;
39
+ }
27
40
  }
28
- function useToolResult(...args) {
41
+ /**
42
+ * Shared scaffold for the tool-call lifecycle hooks: parses the optional
43
+ * `(toolName, callback)` / `(callback)` overload args, dedups by call ID,
44
+ * and invokes `fire` once per tool call reaching `status`.
45
+ */
46
+ function useToolCallEffect(status, args, fire) {
29
47
  const filterName = typeof args[0] === "string" ? args[0] : null;
30
48
  const callback = typeof args[0] === "string" ? args[1] : args[0];
31
- const session = useSession();
32
- const seenRef = useRef(/* @__PURE__ */ new Set());
49
+ const toolCalls = useSessionSelector((s) => s.toolCalls);
50
+ const cursorRef = useRef({
51
+ seq: 0,
52
+ fired: /* @__PURE__ */ new Set()
53
+ });
33
54
  const callbackRef = useRef(callback);
34
55
  callbackRef.current = callback;
56
+ const fireRef = useRef(fire);
57
+ fireRef.current = fire;
35
58
  useEffect(() => {
36
- processToolCalls(session.toolCalls, seenRef.current, (tc) => tc.status === "done" && (!filterName || tc.name === filterName), (tc) => {
37
- const parsed = tryParseJSON(tc.result);
38
- if (filterName) callbackRef.current(parsed, tc);
39
- else callbackRef.current(tc.name, parsed, tc);
59
+ processToolCallTail(toolCalls, cursorRef.current, (tc) => status === "pending" || tc.status === status, (tc) => {
60
+ if (tc.status !== status) return;
61
+ if (filterName && tc.name !== filterName) return;
62
+ fireRef.current(callbackRef.current, tc, filterName !== null);
40
63
  });
41
- }, [session.toolCalls, filterName]);
64
+ }, [
65
+ toolCalls,
66
+ filterName,
67
+ status
68
+ ]);
69
+ }
70
+ function useToolResult(...args) {
71
+ useToolCallEffect("done", args, (callback, tc, filtered) => {
72
+ const parsed = tryParseJSON(tc.result);
73
+ if (filtered) callback(parsed, tc);
74
+ else callback(tc.name, parsed, tc);
75
+ });
42
76
  }
43
77
  function useEvent(event, callback) {
44
- const session = useSession();
45
- const seenRef = useRef(/* @__PURE__ */ new Set());
78
+ const customEvents = useSessionSelector((s) => s.customEvents);
79
+ const watermarkRef = useRef(0);
46
80
  const callbackRef = useRef(callback);
47
81
  callbackRef.current = callback;
48
82
  useEffect(() => {
49
- if (session.customEvents.length === 0) {
50
- seenRef.current.clear();
83
+ if (customEvents.length === 0) {
84
+ watermarkRef.current = 0;
51
85
  return;
52
86
  }
53
- for (const ce of session.customEvents) {
54
- if (ce.event !== event) continue;
55
- if (seenRef.current.has(ce.id)) continue;
56
- seenRef.current.add(ce.id);
57
- callbackRef.current(ce.data);
87
+ const start = tailStart(customEvents, (ce) => ce.id, watermarkRef.current);
88
+ for (let i = start; i < customEvents.length; i++) {
89
+ const ce = customEvents[i];
90
+ if (!ce) continue;
91
+ watermarkRef.current = ce.id;
92
+ if (ce.event === event) callbackRef.current(ce.data);
58
93
  }
59
- }, [session.customEvents, event]);
94
+ }, [customEvents, event]);
60
95
  }
61
96
  function useToolCallStart(...args) {
62
- const filterName = typeof args[0] === "string" ? args[0] : null;
63
- const callback = typeof args[0] === "string" ? args[1] : args[0];
64
- const session = useSession();
65
- const seenRef = useRef(/* @__PURE__ */ new Set());
66
- const callbackRef = useRef(callback);
67
- callbackRef.current = callback;
68
- useEffect(() => {
69
- processToolCalls(session.toolCalls, seenRef.current, (tc) => tc.status === "pending" && (!filterName || tc.name === filterName), (tc) => callbackRef.current(tc));
70
- }, [session.toolCalls, filterName]);
97
+ useToolCallEffect("pending", args, (callback, tc) => {
98
+ callback(tc);
99
+ });
71
100
  }
72
101
  //#endregion
73
102
  export { useEvent, useToolCallStart, useToolResult };
package/dist/index.d.ts CHANGED
@@ -3,10 +3,12 @@ export { ChatView } from "./components/chat-view.tsx";
3
3
  export { MessageList } from "./components/message-list.tsx";
4
4
  export { SidebarLayout } from "./components/sidebar-layout.tsx";
5
5
  export { StartScreen } from "./components/start-screen.tsx";
6
+ export { TextControls } from "./components/text-controls.tsx";
6
7
  export type { ToolDisplayConfig } from "./components/tool-config-context.ts";
7
8
  export { ToolConfigContext } from "./components/tool-config-context.ts";
9
+ export { ApiUrlChip, SessionUrlChips, UiUrlChip } from "./components/url-chips.tsx";
8
10
  export type { Session } from "./context.ts";
9
- export { SessionProvider, ThemeProvider, useSession, useTheme } from "./context.ts";
11
+ export { SessionProvider, ThemeProvider, useSession, useSessionSelector, useTheme, } from "./context.ts";
10
12
  export type { ClientConfig, ClientHandle, } from "./define-client.tsx";
11
13
  export { client } from "./define-client.tsx";
12
14
  export { useEvent, useToolCallStart, useToolResult } from "./hooks.ts";
package/dist/index.js CHANGED
@@ -1,11 +1,12 @@
1
- import { SessionProvider, ThemeProvider, useSession, useTheme } from "./context.js";
1
+ import { SessionProvider, ThemeProvider, useSession, useSessionSelector, useTheme } from "./context.js";
2
2
  import { Button } from "./components/button.js";
3
- import { n as ToolConfigContext } from "./tool-call-block-wby_jyoY.js";
3
+ import { i as UiUrlChip, n as ApiUrlChip, r as SessionUrlChips } from "./controls-BngrPbOC.js";
4
+ import { n as ToolConfigContext } from "./tool-call-block-7f1GTG-P.js";
4
5
  import { MessageList } from "./components/message-list.js";
5
- import { ChatView } from "./components/chat-view.js";
6
+ import { n as TextControls, t as ChatView } from "./chat-view-C1XbqDk0.js";
6
7
  import { SidebarLayout } from "./components/sidebar-layout.js";
7
8
  import { StartScreen } from "./components/start-screen.js";
8
- import { createSessionCore } from "./session-core.js";
9
+ import { t as createSessionCore } from "./session-core-D3NDaySY.js";
9
10
  import { client } from "./define-client.js";
10
11
  import { useEvent, useToolCallStart, useToolResult } from "./hooks.js";
11
- export { Button, ChatView, MessageList, SessionProvider, SidebarLayout, StartScreen, ThemeProvider, ToolConfigContext, client, createSessionCore, useEvent, useSession, useTheme, useToolCallStart, useToolResult };
12
+ export { ApiUrlChip, Button, ChatView, MessageList, SessionProvider, SessionUrlChips, SidebarLayout, StartScreen, TextControls, ThemeProvider, ToolConfigContext, UiUrlChip, client, createSessionCore, useEvent, useSession, useSessionSelector, useTheme, useToolCallStart, useToolResult };