@alexkroman1/aai-ui 0.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.
- package/LICENSE +21 -0
- package/dist/_components/app.d.ts +5 -0
- package/dist/_components/app.js +23 -0
- package/dist/_components/button.d.ts +11 -0
- package/dist/_components/button.js +22 -0
- package/dist/_components/chat-view.d.ts +5 -0
- package/dist/_components/chat-view.js +37 -0
- package/dist/_components/controls.d.ts +4 -0
- package/dist/_components/controls.js +23 -0
- package/dist/_components/error-banner.d.ts +7 -0
- package/dist/_components/error-banner.js +13 -0
- package/dist/_components/message-bubble.d.ts +7 -0
- package/dist/_components/message-bubble.js +19 -0
- package/dist/_components/message-list.d.ts +4 -0
- package/dist/_components/message-list.js +59 -0
- package/dist/_components/sidebar-layout.d.ts +21 -0
- package/dist/_components/sidebar-layout.js +36 -0
- package/dist/_components/start-screen.d.ts +26 -0
- package/dist/_components/start-screen.js +51 -0
- package/dist/_components/state-indicator.d.ts +7 -0
- package/dist/_components/state-indicator.js +15 -0
- package/dist/_components/thinking-indicator.d.ts +5 -0
- package/dist/_components/thinking-indicator.js +22 -0
- package/dist/_components/tool-call-block.d.ts +7 -0
- package/dist/_components/tool-call-block.js +96 -0
- package/dist/_components/tool-icons.d.ts +17 -0
- package/dist/_components/tool-icons.js +128 -0
- package/dist/_components/transcript.d.ts +7 -0
- package/dist/_components/transcript.js +17 -0
- package/dist/_jsdom-setup.d.ts +0 -0
- package/dist/audio.d.ts +45 -0
- package/dist/audio.js +111 -0
- package/dist/components.d.ts +31 -0
- package/dist/components.js +17 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +19 -0
- package/dist/mount-context.d.ts +33 -0
- package/dist/mount-context.js +15 -0
- package/dist/mount.d.ts +51 -0
- package/dist/mount.js +70 -0
- package/dist/session.d.ts +100 -0
- package/dist/session.js +359 -0
- package/dist/signals.d.ts +89 -0
- package/dist/signals.js +138 -0
- package/dist/tsdown.config.d.ts +2 -0
- package/dist/types.d.ts +75 -0
- package/dist/types.js +5 -0
- package/dist/worklets/capture-processor.d.ts +2 -0
- package/dist/worklets/capture-processor.js +57 -0
- package/dist/worklets/playback-processor.d.ts +2 -0
- package/dist/worklets/playback-processor.js +105 -0
- package/package.json +56 -0
- package/styles.css +74 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
//#region worklets/capture-processor.ts
|
|
2
|
+
const script = new Blob([`
|
|
3
|
+
class CaptureProcessor extends AudioWorkletProcessor {
|
|
4
|
+
constructor(options) {
|
|
5
|
+
super();
|
|
6
|
+
this.recording = false;
|
|
7
|
+
const opts = options.processorOptions || {};
|
|
8
|
+
this.fromRate = opts.contextRate || sampleRate;
|
|
9
|
+
this.toRate = opts.sttSampleRate || sampleRate;
|
|
10
|
+
this.ratio = this.fromRate / this.toRate;
|
|
11
|
+
this.needsResample = this.fromRate !== this.toRate;
|
|
12
|
+
this.port.onmessage = (e) => {
|
|
13
|
+
if (e.data.event === 'start') this.recording = true;
|
|
14
|
+
else if (e.data.event === 'stop') this.recording = false;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
resample(input) {
|
|
19
|
+
const ratio = this.ratio;
|
|
20
|
+
const outLen = Math.ceil(input.length / ratio);
|
|
21
|
+
const out = new Float32Array(outLen);
|
|
22
|
+
for (let i = 0; i < outLen; i++) {
|
|
23
|
+
const srcIdx = i * ratio;
|
|
24
|
+
const idx = srcIdx | 0;
|
|
25
|
+
const frac = srcIdx - idx;
|
|
26
|
+
const a = input[idx];
|
|
27
|
+
const b = idx + 1 < input.length ? input[idx + 1] : a;
|
|
28
|
+
out[i] = a + frac * (b - a);
|
|
29
|
+
}
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
process(inputs) {
|
|
34
|
+
const input = inputs[0];
|
|
35
|
+
if (!input || !input[0] || !this.recording) return true;
|
|
36
|
+
|
|
37
|
+
const raw = input[0];
|
|
38
|
+
const samples = this.needsResample ? this.resample(raw) : raw;
|
|
39
|
+
|
|
40
|
+
// Convert Float32 -> Int16
|
|
41
|
+
const buffer = new ArrayBuffer(samples.length * 2);
|
|
42
|
+
const view = new DataView(buffer);
|
|
43
|
+
for (let i = 0; i < samples.length; i++) {
|
|
44
|
+
const s = Math.max(-1, Math.min(1, samples[i]));
|
|
45
|
+
view.setInt16(i * 2, s < 0 ? s * 0x8000 : s * 0x7fff, true);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
this.port.postMessage({ event: 'chunk', buffer }, [buffer]);
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
registerProcessor('capture-processor', CaptureProcessor);
|
|
54
|
+
`], { type: "application/javascript" });
|
|
55
|
+
const src = URL.createObjectURL(script);
|
|
56
|
+
//#endregion
|
|
57
|
+
export { src as default };
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
//#region worklets/playback-processor.ts
|
|
2
|
+
const script = new Blob([`
|
|
3
|
+
class PlaybackProcessor extends AudioWorkletProcessor {
|
|
4
|
+
constructor(options) {
|
|
5
|
+
super();
|
|
6
|
+
this.interrupted = false;
|
|
7
|
+
this.isDone = false;
|
|
8
|
+
this.playing = false;
|
|
9
|
+
const rate = options.processorOptions?.sampleRate ?? 24000;
|
|
10
|
+
// Wait for ~400ms of audio before starting.
|
|
11
|
+
// If 'done' arrives first (short utterance), start immediately.
|
|
12
|
+
this.jitterSamples = Math.floor(rate * 0.4);
|
|
13
|
+
// Carry-over byte for split samples across chunks
|
|
14
|
+
this.carry = null;
|
|
15
|
+
// Float32 sample buffer — 60s at the context sample rate
|
|
16
|
+
this.samples = new Float32Array(rate * 60);
|
|
17
|
+
this.writePos = 0;
|
|
18
|
+
this.readPos = 0;
|
|
19
|
+
// Report playback position every ~50ms for word-level text sync
|
|
20
|
+
this.progressInterval = Math.floor(rate * 0.05);
|
|
21
|
+
this.samplesSinceProgress = 0;
|
|
22
|
+
|
|
23
|
+
this.port.onmessage = (e) => {
|
|
24
|
+
const d = e.data;
|
|
25
|
+
if (d.event === 'write') {
|
|
26
|
+
this.ingestBytes(d.buffer);
|
|
27
|
+
} else if (d.event === 'interrupt') {
|
|
28
|
+
this.interrupted = true;
|
|
29
|
+
} else if (d.event === 'done') {
|
|
30
|
+
this.isDone = true;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
ingestBytes(uint8) {
|
|
36
|
+
let bytes = uint8;
|
|
37
|
+
|
|
38
|
+
if (this.carry !== null) {
|
|
39
|
+
const merged = new Uint8Array(1 + bytes.length);
|
|
40
|
+
merged[0] = this.carry;
|
|
41
|
+
merged.set(bytes, 1);
|
|
42
|
+
bytes = merged;
|
|
43
|
+
this.carry = null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (bytes.length % 2 !== 0) {
|
|
47
|
+
this.carry = bytes[bytes.length - 1];
|
|
48
|
+
bytes = bytes.subarray(0, bytes.length - 1);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (bytes.length === 0) return;
|
|
52
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.length);
|
|
53
|
+
const numSamples = bytes.length / 2;
|
|
54
|
+
for (let i = 0; i < numSamples; i++) {
|
|
55
|
+
this.samples[this.writePos++] = view.getInt16(i * 2, true) / 0x8000;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
process(inputs, outputs) {
|
|
60
|
+
const out = outputs[0][0];
|
|
61
|
+
if (this.interrupted) {
|
|
62
|
+
this.port.postMessage({ event: 'stop' });
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const avail = this.writePos - this.readPos;
|
|
67
|
+
|
|
68
|
+
// Wait for jitter buffer to fill, unless done (short utterance)
|
|
69
|
+
if (!this.playing) {
|
|
70
|
+
if (avail >= this.jitterSamples || this.isDone) {
|
|
71
|
+
this.playing = true;
|
|
72
|
+
} else {
|
|
73
|
+
out.fill(0);
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (avail > 0) {
|
|
79
|
+
const n = Math.min(avail, out.length);
|
|
80
|
+
out.set(this.samples.subarray(this.readPos, this.readPos + n));
|
|
81
|
+
this.readPos += n;
|
|
82
|
+
this.samplesSinceProgress += n;
|
|
83
|
+
if (this.samplesSinceProgress >= this.progressInterval) {
|
|
84
|
+
this.port.postMessage({ event: 'progress', readPos: this.readPos });
|
|
85
|
+
this.samplesSinceProgress = 0;
|
|
86
|
+
}
|
|
87
|
+
out.fill(0, n);
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// No data: output silence, stop only when done
|
|
92
|
+
out.fill(0);
|
|
93
|
+
if (this.isDone) {
|
|
94
|
+
this.port.postMessage({ event: 'stop' });
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
registerProcessor('playback-processor', PlaybackProcessor);
|
|
102
|
+
`], { type: "application/javascript" });
|
|
103
|
+
const src = URL.createObjectURL(script);
|
|
104
|
+
//#endregion
|
|
105
|
+
export { src as default };
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@alexkroman1/aai-ui",
|
|
3
|
+
"version": "0.9.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"files": [
|
|
6
|
+
"dist",
|
|
7
|
+
"styles.css"
|
|
8
|
+
],
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"source": "./index.ts",
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./styles.css": "./styles.css",
|
|
16
|
+
"./session": {
|
|
17
|
+
"source": "./session.ts",
|
|
18
|
+
"types": "./dist/session.d.ts",
|
|
19
|
+
"import": "./dist/session.js"
|
|
20
|
+
},
|
|
21
|
+
"./components": {
|
|
22
|
+
"source": "./components.ts",
|
|
23
|
+
"types": "./dist/components.d.ts",
|
|
24
|
+
"import": "./dist/components.js"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"clsx": "^2.1.1",
|
|
29
|
+
"@alexkroman1/aai": "0.9.0"
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"@preact/signals": "^2.8.2",
|
|
33
|
+
"preact": "^10.29.0",
|
|
34
|
+
"tailwindcss": "^4.2.1"
|
|
35
|
+
},
|
|
36
|
+
"peerDependenciesMeta": {
|
|
37
|
+
"tailwindcss": {
|
|
38
|
+
"optional": true
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@preact/signals": "^2.8.2",
|
|
43
|
+
"@testing-library/preact": "^3.2.4",
|
|
44
|
+
"jsdom": "^29.0.1",
|
|
45
|
+
"preact": "^10.29.0",
|
|
46
|
+
"tsdown": "^0.21.4"
|
|
47
|
+
},
|
|
48
|
+
"engines": {
|
|
49
|
+
"node": ">=22"
|
|
50
|
+
},
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "tsdown && tsc -p tsconfig.build.json",
|
|
53
|
+
"typecheck": "tsc --noEmit",
|
|
54
|
+
"lint": "biome check ."
|
|
55
|
+
}
|
|
56
|
+
}
|
package/styles.css
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=IBM+Plex+Mono:wght@400;500&display=swap");
|
|
2
|
+
@import "tailwindcss";
|
|
3
|
+
@source "./";
|
|
4
|
+
@source "../dist/ui/";
|
|
5
|
+
|
|
6
|
+
@theme {
|
|
7
|
+
--color-aai-bg: #101010;
|
|
8
|
+
--color-aai-surface: #151515;
|
|
9
|
+
--color-aai-surface-faint: rgba(255, 255, 255, 0.031);
|
|
10
|
+
--color-aai-surface-hover: rgba(255, 255, 255, 0.059);
|
|
11
|
+
--color-aai-border: #282828;
|
|
12
|
+
--color-aai-primary: #fab283;
|
|
13
|
+
--color-aai-text: rgba(255, 255, 255, 0.936);
|
|
14
|
+
--color-aai-text-secondary: rgba(255, 255, 255, 0.618);
|
|
15
|
+
--color-aai-text-muted: rgba(255, 255, 255, 0.284);
|
|
16
|
+
--color-aai-text-dim: rgba(255, 255, 255, 0.422);
|
|
17
|
+
--color-aai-error: #e06c75;
|
|
18
|
+
--color-aai-ring: #56b6c2;
|
|
19
|
+
--color-aai-state-disconnected: rgba(255, 255, 255, 0.422);
|
|
20
|
+
--color-aai-state-connecting: rgba(255, 255, 255, 0.422);
|
|
21
|
+
--color-aai-state-ready: #7fd88f;
|
|
22
|
+
--color-aai-state-listening: #56b6c2;
|
|
23
|
+
--color-aai-state-thinking: #f5a742;
|
|
24
|
+
--color-aai-state-speaking: #e06c75;
|
|
25
|
+
--color-aai-state-error: #e06c75;
|
|
26
|
+
--radius-aai: 6px;
|
|
27
|
+
--font-aai: "Inter", system-ui, -apple-system, sans-serif;
|
|
28
|
+
--font-aai-mono: "IBM Plex Mono", monospace;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@layer base {
|
|
32
|
+
html,
|
|
33
|
+
body {
|
|
34
|
+
margin: 0;
|
|
35
|
+
padding: 0;
|
|
36
|
+
background: var(--color-aai-bg);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@keyframes aai-bounce {
|
|
41
|
+
0%,
|
|
42
|
+
80%,
|
|
43
|
+
100% {
|
|
44
|
+
opacity: 0.3;
|
|
45
|
+
transform: scale(0.8);
|
|
46
|
+
}
|
|
47
|
+
40% {
|
|
48
|
+
opacity: 1;
|
|
49
|
+
transform: scale(1);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@keyframes aai-shimmer {
|
|
54
|
+
0% {
|
|
55
|
+
background-position: -200% 0;
|
|
56
|
+
}
|
|
57
|
+
100% {
|
|
58
|
+
background-position: 200% 0;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.tool-shimmer {
|
|
63
|
+
background: linear-gradient(
|
|
64
|
+
90deg,
|
|
65
|
+
var(--color-aai-text) 25%,
|
|
66
|
+
var(--color-aai-text-dim) 50%,
|
|
67
|
+
var(--color-aai-text) 75%
|
|
68
|
+
);
|
|
69
|
+
background-size: 200% 100%;
|
|
70
|
+
-webkit-background-clip: text;
|
|
71
|
+
background-clip: text;
|
|
72
|
+
-webkit-text-fill-color: transparent;
|
|
73
|
+
animation: aai-shimmer 2s ease-in-out infinite;
|
|
74
|
+
}
|