fauna-api 0.0.0-stage → 1.0.676
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/brands.d.ts +2 -0
- package/dist/brands.js +2 -0
- package/dist/index.d.ts +64 -0
- package/dist/index.js +121 -0
- package/dist/src/ant/chat.d.ts +130 -0
- package/dist/src/ant/chat.js +467 -0
- package/dist/src/ant/format.d.ts +40 -0
- package/dist/src/ant/format.js +143 -0
- package/dist/src/ant/graph.d.ts +130 -0
- package/dist/src/ant/graph.js +449 -0
- package/dist/src/ant/index.d.ts +16 -0
- package/dist/src/ant/index.js +61 -0
- package/dist/src/ant/persona.d.ts +151 -0
- package/dist/src/ant/persona.js +136 -0
- package/dist/src/ant/timeline.d.ts +283 -0
- package/dist/src/ant/timeline.js +1025 -0
- package/dist/src/authentification.d.ts +45 -0
- package/dist/src/authentification.js +194 -0
- package/dist/src/billing.d.ts +135 -0
- package/dist/src/billing.js +128 -0
- package/dist/src/calendar.d.ts +83 -0
- package/dist/src/calendar.js +133 -0
- package/dist/src/chats.d.ts +113 -0
- package/dist/src/chats.js +100 -0
- package/dist/src/cicada/contacts.d.ts +31 -0
- package/dist/src/cicada/contacts.js +28 -0
- package/dist/src/cicada/conversations.d.ts +73 -0
- package/dist/src/cicada/conversations.js +236 -0
- package/dist/src/cicada/inboxes.d.ts +48 -0
- package/dist/src/cicada/inboxes.js +82 -0
- package/dist/src/cicada/index.d.ts +16 -0
- package/dist/src/cicada/index.js +61 -0
- package/dist/src/cicada/phone.d.ts +48 -0
- package/dist/src/cicada/phone.js +306 -0
- package/dist/src/drive.d.ts +80 -0
- package/dist/src/drive.js +472 -0
- package/dist/src/extensions/index.d.ts +4 -0
- package/dist/src/extensions/index.js +5 -0
- package/dist/src/extensions/web/index.d.ts +6 -0
- package/dist/src/extensions/web/index.js +7 -0
- package/dist/src/extensions/web/session.d.ts +106 -0
- package/dist/src/extensions/web/session.js +469 -0
- package/dist/src/extensions/web/tools.d.ts +13 -0
- package/dist/src/extensions/web/tools.js +72 -0
- package/dist/src/friends.d.ts +39 -0
- package/dist/src/friends.js +119 -0
- package/dist/src/groups.d.ts +82 -0
- package/dist/src/groups.js +269 -0
- package/dist/src/inbox.d.ts +11 -0
- package/dist/src/inbox.js +35 -0
- package/dist/src/integrations.d.ts +52 -0
- package/dist/src/integrations.js +205 -0
- package/dist/src/magpie/explorer.d.ts +14 -0
- package/dist/src/magpie/explorer.js +36 -0
- package/dist/src/magpie/index.d.ts +16 -0
- package/dist/src/magpie/index.js +61 -0
- package/dist/src/magpie/portfolio.d.ts +52 -0
- package/dist/src/magpie/portfolio.js +179 -0
- package/dist/src/magpie/quotes.d.ts +35 -0
- package/dist/src/magpie/quotes.js +47 -0
- package/dist/src/magpie/room.d.ts +61 -0
- package/dist/src/magpie/room.js +238 -0
- package/dist/src/maps.d.ts +159 -0
- package/dist/src/maps.js +171 -0
- package/dist/src/marmot/browser.d.ts +139 -0
- package/dist/src/marmot/browser.js +389 -0
- package/dist/src/marmot/index.d.ts +20 -0
- package/dist/src/marmot/index.js +65 -0
- package/dist/src/marmot/media.d.ts +56 -0
- package/dist/src/marmot/media.js +265 -0
- package/dist/src/marmot/research.d.ts +337 -0
- package/dist/src/marmot/research.js +1138 -0
- package/dist/src/marmot/scraper.d.ts +59 -0
- package/dist/src/marmot/scraper.js +94 -0
- package/dist/src/marmot/web.d.ts +402 -0
- package/dist/src/marmot/web.js +2586 -0
- package/dist/src/marmot/workspace.d.ts +81 -0
- package/dist/src/marmot/workspace.js +263 -0
- package/dist/src/parrot/agent.d.ts +53 -0
- package/dist/src/parrot/agent.js +93 -0
- package/dist/src/parrot/chat.d.ts +84 -0
- package/dist/src/parrot/chat.js +237 -0
- package/dist/src/parrot/format.d.ts +128 -0
- package/dist/src/parrot/format.js +639 -0
- package/dist/src/parrot/generative.d.ts +261 -0
- package/dist/src/parrot/generative.js +185 -0
- package/dist/src/parrot/index.d.ts +22 -0
- package/dist/src/parrot/index.js +67 -0
- package/dist/src/parrot/room.d.ts +225 -0
- package/dist/src/parrot/room.js +466 -0
- package/dist/src/parrot/routing.d.ts +90 -0
- package/dist/src/parrot/routing.js +78 -0
- package/dist/src/parrot/share.d.ts +126 -0
- package/dist/src/parrot/share.js +261 -0
- package/dist/src/parrot/socket.d.ts +41 -0
- package/dist/src/parrot/socket.js +478 -0
- package/dist/src/payments.d.ts +73 -0
- package/dist/src/payments.js +107 -0
- package/dist/src/private/cards.css +75 -0
- package/dist/src/rooms.d.ts +76 -0
- package/dist/src/rooms.js +168 -0
- package/dist/src/shared/schema.d.ts +24 -0
- package/dist/src/shared/schema.js +1 -0
- package/dist/src/shared/server.d.ts +65 -0
- package/dist/src/shared/server.js +302 -0
- package/dist/src/termite/environment.d.ts +110 -0
- package/dist/src/termite/environment.js +263 -0
- package/dist/src/termite/index.d.ts +14 -0
- package/dist/src/termite/index.js +59 -0
- package/dist/src/termite/os.d.ts +326 -0
- package/dist/src/termite/os.js +572 -0
- package/dist/src/termite/versioning.d.ts +94 -0
- package/dist/src/termite/versioning.js +217 -0
- package/dist/src/tickets.d.ts +43 -0
- package/dist/src/tickets.js +138 -0
- package/dist/src/travel.d.ts +85 -0
- package/dist/src/travel.js +44 -0
- package/dist/src/ui/authentification.d.ts +24 -0
- package/dist/src/ui/authentification.js +177 -0
- package/dist/src/ui/brands.css +11 -0
- package/dist/src/ui/brands.d.ts +7 -0
- package/dist/src/ui/brands.js +14 -0
- package/dist/src/ui/browser.d.ts +74 -0
- package/dist/src/ui/browser.js +470 -0
- package/dist/src/ui/chat.d.ts +183 -0
- package/dist/src/ui/chat.js +2379 -0
- package/dist/src/ui/chatPanel.d.ts +109 -0
- package/dist/src/ui/chatPanel.js +768 -0
- package/dist/src/ui/chatSequence.d.ts +36 -0
- package/dist/src/ui/chatSequence.js +42 -0
- package/dist/src/ui/crown.d.ts +19 -0
- package/dist/src/ui/crown.js +199 -0
- package/dist/src/ui/cursor.d.ts +73 -0
- package/dist/src/ui/cursor.js +175 -0
- package/dist/src/ui/device.d.ts +33 -0
- package/dist/src/ui/device.js +49 -0
- package/dist/src/ui/drive.d.ts +69 -0
- package/dist/src/ui/drive.js +1243 -0
- package/dist/src/ui/generative.d.ts +39 -0
- package/dist/src/ui/generative.js +313 -0
- package/dist/src/ui/hover.d.ts +6 -0
- package/dist/src/ui/hover.js +210 -0
- package/dist/src/ui/index.d.ts +42 -0
- package/dist/src/ui/index.js +87 -0
- package/dist/src/ui/integrations.d.ts +13 -0
- package/dist/src/ui/integrations.js +175 -0
- package/dist/src/ui/maps.d.ts +87 -0
- package/dist/src/ui/maps.js +547 -0
- package/dist/src/ui/roster.d.ts +32 -0
- package/dist/src/ui/roster.js +163 -0
- package/dist/src/ui/talk.d.ts +12 -0
- package/dist/src/ui/talk.js +560 -0
- package/dist/src/ui/tickets.d.ts +13 -0
- package/dist/src/ui/tickets.js +966 -0
- package/dist/src/ui/toast.d.ts +7 -0
- package/dist/src/ui/toast.js +258 -0
- package/dist/src/ui/tokenText.d.ts +18 -0
- package/dist/src/ui/tokenText.js +96 -0
- package/dist/src/ui/tokenized.d.ts +42 -0
- package/dist/src/ui/tokenized.js +622 -0
- package/dist/src/ui/ui.css +4763 -0
- package/dist/src/ui/view.d.ts +23 -0
- package/dist/src/ui/view.js +584 -0
- package/dist/src/view.d.ts +113 -0
- package/dist/src/view.js +386 -0
- package/dist/src/webhooks.d.ts +66 -0
- package/dist/src/webhooks.js +78 -0
- package/dist/ui.d.ts +1 -0
- package/dist/ui.js +1 -0
- package/package.json +89 -6
- package/README.md +0 -3
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
import { Fauna } from 'fauna-api';
|
|
2
|
+
import gsap from 'gsap';
|
|
3
|
+
import { createDemographic, createSelector, stampModelAvatar, stampUserAvatar } from './generative.js';
|
|
4
|
+
const MIC_RATE = 16000;
|
|
5
|
+
const PLAY_RATE = 24000;
|
|
6
|
+
function observeMemoryTool(credentials, onMemory) {
|
|
7
|
+
return {
|
|
8
|
+
function: {
|
|
9
|
+
name: 'observe_memory',
|
|
10
|
+
description: 'Store a memory for long-term recall across conversations. Call this when: (1) the user shares a surprising or non-obvious fact about themselves, their preferences, or their world, (2) the user describes a personal experience, story, or emotional moment, (3) the user reveals latent intent, what they actually care about beneath what they said, (4) the user corrects you or clarifies something important, (5) you notice a pattern in what the user values or how they think. Do NOT store trivial small talk or things that are obvious from context. The observation should be a concise, standalone statement that would be useful to recall later without the original conversation.',
|
|
11
|
+
parameters: {
|
|
12
|
+
type: 'OBJECT',
|
|
13
|
+
properties: {
|
|
14
|
+
observation: {
|
|
15
|
+
type: 'STRING',
|
|
16
|
+
description: 'A concise, standalone observation worth remembering. Write it as a fact or insight, not as a quote.',
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
required: ['observation'],
|
|
20
|
+
},
|
|
21
|
+
behavior: 'BLOCKING',
|
|
22
|
+
},
|
|
23
|
+
evaluate: async (params) => {
|
|
24
|
+
const observation = String(params?.observation ?? '').trim();
|
|
25
|
+
if (!observation)
|
|
26
|
+
return JSON.stringify({ error: 'observation required.' });
|
|
27
|
+
await Fauna.Parrot.Agent.observe(credentials, observation);
|
|
28
|
+
onMemory(observation);
|
|
29
|
+
return JSON.stringify({ ok: true });
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
class PcmPlayer {
|
|
34
|
+
constructor() {
|
|
35
|
+
Object.defineProperty(this, "ctx", {
|
|
36
|
+
enumerable: true,
|
|
37
|
+
configurable: true,
|
|
38
|
+
writable: true,
|
|
39
|
+
value: void 0
|
|
40
|
+
});
|
|
41
|
+
Object.defineProperty(this, "next", {
|
|
42
|
+
enumerable: true,
|
|
43
|
+
configurable: true,
|
|
44
|
+
writable: true,
|
|
45
|
+
value: 0
|
|
46
|
+
});
|
|
47
|
+
Object.defineProperty(this, "closed", {
|
|
48
|
+
enumerable: true,
|
|
49
|
+
configurable: true,
|
|
50
|
+
writable: true,
|
|
51
|
+
value: false
|
|
52
|
+
});
|
|
53
|
+
this.ctx = new AudioContext({ sampleRate: PLAY_RATE });
|
|
54
|
+
void this.ctx.resume();
|
|
55
|
+
}
|
|
56
|
+
push(bytes) {
|
|
57
|
+
if (this.closed || this.ctx.state === 'closed' || bytes.length < 2)
|
|
58
|
+
return;
|
|
59
|
+
const samples = Math.floor(bytes.length / 2);
|
|
60
|
+
const buffer = this.ctx.createBuffer(1, samples, PLAY_RATE);
|
|
61
|
+
const channel = buffer.getChannelData(0);
|
|
62
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
63
|
+
for (let i = 0; i < samples; i++)
|
|
64
|
+
channel[i] = view.getInt16(i * 2, true) / 32768;
|
|
65
|
+
const source = this.ctx.createBufferSource();
|
|
66
|
+
source.buffer = buffer;
|
|
67
|
+
source.connect(this.ctx.destination);
|
|
68
|
+
const now = this.ctx.currentTime;
|
|
69
|
+
if (this.next < now)
|
|
70
|
+
this.next = now;
|
|
71
|
+
source.start(this.next);
|
|
72
|
+
this.next += buffer.duration;
|
|
73
|
+
}
|
|
74
|
+
close() {
|
|
75
|
+
if (this.closed)
|
|
76
|
+
return;
|
|
77
|
+
this.closed = true;
|
|
78
|
+
void this.ctx.close();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function resample(input, fromRate, toRate) {
|
|
82
|
+
if (fromRate === toRate)
|
|
83
|
+
return input;
|
|
84
|
+
const ratio = fromRate / toRate;
|
|
85
|
+
const out = new Float32Array(Math.max(1, Math.floor(input.length / ratio)));
|
|
86
|
+
for (let i = 0; i < out.length; i++) {
|
|
87
|
+
const src = i * ratio;
|
|
88
|
+
const j = Math.floor(src);
|
|
89
|
+
const frac = src - j;
|
|
90
|
+
const a = input[j] ?? 0;
|
|
91
|
+
const b = input[j + 1] ?? a;
|
|
92
|
+
out[i] = a + (b - a) * frac;
|
|
93
|
+
}
|
|
94
|
+
return out;
|
|
95
|
+
}
|
|
96
|
+
function encodePcm16(samples) {
|
|
97
|
+
const out = new Uint8Array(samples.length * 2);
|
|
98
|
+
const view = new DataView(out.buffer);
|
|
99
|
+
for (let i = 0; i < samples.length; i++) {
|
|
100
|
+
const sample = Math.max(-1, Math.min(1, samples[i]));
|
|
101
|
+
view.setInt16(i * 2, sample < 0 ? sample * 0x8000 : sample * 0x7fff, true);
|
|
102
|
+
}
|
|
103
|
+
return out;
|
|
104
|
+
}
|
|
105
|
+
async function* captureMic(signal) {
|
|
106
|
+
const stream = await navigator.mediaDevices.getUserMedia({
|
|
107
|
+
audio: { channelCount: 1, echoCancellation: true, noiseSuppression: true },
|
|
108
|
+
video: false,
|
|
109
|
+
});
|
|
110
|
+
const ctx = new AudioContext();
|
|
111
|
+
void ctx.resume();
|
|
112
|
+
const source = ctx.createMediaStreamSource(stream);
|
|
113
|
+
const node = ctx.createScriptProcessor(4096, 1, 1);
|
|
114
|
+
const mute = ctx.createGain();
|
|
115
|
+
mute.gain.value = 0;
|
|
116
|
+
const pending = [];
|
|
117
|
+
let wake;
|
|
118
|
+
node.onaudioprocess = (event) => {
|
|
119
|
+
const pcm = encodePcm16(resample(event.inputBuffer.getChannelData(0), ctx.sampleRate, MIC_RATE));
|
|
120
|
+
if (pcm.length === 0)
|
|
121
|
+
return;
|
|
122
|
+
pending.push(pcm);
|
|
123
|
+
wake?.();
|
|
124
|
+
};
|
|
125
|
+
source.connect(node);
|
|
126
|
+
node.connect(mute);
|
|
127
|
+
mute.connect(ctx.destination);
|
|
128
|
+
const onAbort = () => wake?.();
|
|
129
|
+
signal.addEventListener('abort', onAbort);
|
|
130
|
+
try {
|
|
131
|
+
while (!signal.aborted) {
|
|
132
|
+
if (pending.length === 0) {
|
|
133
|
+
await new Promise((resolve) => { wake = resolve; });
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
yield pending.shift();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
finally {
|
|
140
|
+
signal.removeEventListener('abort', onAbort);
|
|
141
|
+
node.disconnect();
|
|
142
|
+
source.disconnect();
|
|
143
|
+
mute.disconnect();
|
|
144
|
+
for (const track of stream.getTracks())
|
|
145
|
+
track.stop();
|
|
146
|
+
void ctx.close();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
async function startVoiceSession(credentials, model, hooks = {}, voice) {
|
|
150
|
+
const controller = new AbortController();
|
|
151
|
+
const player = new PcmPlayer();
|
|
152
|
+
const observe = observeMemoryTool(credentials, (observation) => hooks.onMemory?.(observation));
|
|
153
|
+
void (async () => {
|
|
154
|
+
try {
|
|
155
|
+
const audio = captureMic(controller.signal);
|
|
156
|
+
for await (const event of Fauna.Parrot.Generative.generateVoice(credentials, model, {
|
|
157
|
+
audio,
|
|
158
|
+
tools: observe,
|
|
159
|
+
voice,
|
|
160
|
+
signal: controller.signal,
|
|
161
|
+
})) {
|
|
162
|
+
if (event.event === 'audio')
|
|
163
|
+
player.push(event.delta);
|
|
164
|
+
else if (event.event === 'input_transcript')
|
|
165
|
+
hooks.onInput?.(event.delta);
|
|
166
|
+
else if (event.event === 'output_transcript')
|
|
167
|
+
hooks.onOutput?.(event.delta);
|
|
168
|
+
else if (event.event === 'speech_started')
|
|
169
|
+
hooks.onSpeechStart?.();
|
|
170
|
+
else if (event.event === 'speech_stopped')
|
|
171
|
+
hooks.onSpeechStop?.();
|
|
172
|
+
else if (event.event === 'error')
|
|
173
|
+
hooks.onError?.(event.message);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
catch (err) {
|
|
177
|
+
if (!controller.signal.aborted)
|
|
178
|
+
hooks.onError?.(err?.message ?? String(err));
|
|
179
|
+
}
|
|
180
|
+
finally {
|
|
181
|
+
player.close();
|
|
182
|
+
if (!controller.signal.aborted)
|
|
183
|
+
controller.abort();
|
|
184
|
+
hooks.onDone?.();
|
|
185
|
+
}
|
|
186
|
+
})();
|
|
187
|
+
return {
|
|
188
|
+
hangUp: () => {
|
|
189
|
+
if (!controller.signal.aborted)
|
|
190
|
+
controller.abort();
|
|
191
|
+
player.close();
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
function statusLabel(phase) {
|
|
196
|
+
if (phase === 'connecting')
|
|
197
|
+
return 'Connecting';
|
|
198
|
+
if (phase === 'listening')
|
|
199
|
+
return 'Listening';
|
|
200
|
+
if (phase === 'talking')
|
|
201
|
+
return 'Talking';
|
|
202
|
+
return 'Voice';
|
|
203
|
+
}
|
|
204
|
+
function profileFromCredentials(credentials) {
|
|
205
|
+
if (!('email' in credentials))
|
|
206
|
+
return undefined;
|
|
207
|
+
return { email: credentials.email, displayName: credentials.displayName, icon: credentials.icon };
|
|
208
|
+
}
|
|
209
|
+
export function createTalk(container, options) {
|
|
210
|
+
const assistantLabel = options.assistantLabel ?? 'Assistant';
|
|
211
|
+
let phase = 'idle';
|
|
212
|
+
let generation = 0;
|
|
213
|
+
let starting = false;
|
|
214
|
+
let session;
|
|
215
|
+
let model = Fauna.Parrot.Generative.Voice.GPT_REALTIME;
|
|
216
|
+
let voice = { age: 35 };
|
|
217
|
+
let modelIcons = {};
|
|
218
|
+
const userProfile = profileFromCredentials(options.credentials);
|
|
219
|
+
const turns = [];
|
|
220
|
+
const root = document.createElement('div');
|
|
221
|
+
root.className = 'talk';
|
|
222
|
+
const stage = document.createElement('div');
|
|
223
|
+
stage.className = 'talk-stage';
|
|
224
|
+
const orb = document.createElement('div');
|
|
225
|
+
orb.className = 'talk-orb talk-orb-idle';
|
|
226
|
+
orb.setAttribute('aria-hidden', 'true');
|
|
227
|
+
const ringA = document.createElement('span');
|
|
228
|
+
ringA.className = 'talk-orb-ring';
|
|
229
|
+
const ringB = document.createElement('span');
|
|
230
|
+
ringB.className = 'talk-orb-ring';
|
|
231
|
+
const core = document.createElement('span');
|
|
232
|
+
core.className = 'talk-orb-core';
|
|
233
|
+
orb.append(ringA, ringB, core);
|
|
234
|
+
const status = document.createElement('p');
|
|
235
|
+
status.className = 'talk-status';
|
|
236
|
+
status.textContent = 'Voice';
|
|
237
|
+
const error = document.createElement('p');
|
|
238
|
+
error.className = 'talk-error';
|
|
239
|
+
error.hidden = true;
|
|
240
|
+
const controls = document.createElement('div');
|
|
241
|
+
controls.className = 'talk-controls';
|
|
242
|
+
const selectorEl = document.createElement('div');
|
|
243
|
+
const demographicEl = document.createElement('div');
|
|
244
|
+
const action = document.createElement('button');
|
|
245
|
+
action.type = 'button';
|
|
246
|
+
action.className = 'talk-start';
|
|
247
|
+
action.textContent = 'Start Listening';
|
|
248
|
+
controls.append(selectorEl, demographicEl, action);
|
|
249
|
+
stage.append(orb, status, error, controls);
|
|
250
|
+
const log = document.createElement('div');
|
|
251
|
+
log.className = 'talk-log';
|
|
252
|
+
root.append(stage, log);
|
|
253
|
+
container.appendChild(root);
|
|
254
|
+
const paintPhase = () => {
|
|
255
|
+
orb.className = `talk-orb talk-orb-${phase}`;
|
|
256
|
+
status.textContent = statusLabel(phase);
|
|
257
|
+
const live = phase !== 'idle';
|
|
258
|
+
action.textContent = live ? 'Hang up' : 'Start Listening';
|
|
259
|
+
action.className = live ? 'talk-hang-up' : 'talk-start';
|
|
260
|
+
};
|
|
261
|
+
const setPhase = (next) => {
|
|
262
|
+
phase = next;
|
|
263
|
+
paintPhase();
|
|
264
|
+
};
|
|
265
|
+
const setError = (message) => {
|
|
266
|
+
error.textContent = message;
|
|
267
|
+
error.hidden = !message;
|
|
268
|
+
};
|
|
269
|
+
const scrollLog = () => {
|
|
270
|
+
log.scrollTop = log.scrollHeight;
|
|
271
|
+
};
|
|
272
|
+
const stopReveal = (turn) => {
|
|
273
|
+
if (turn.raf === null)
|
|
274
|
+
return;
|
|
275
|
+
cancelAnimationFrame(turn.raf);
|
|
276
|
+
turn.raf = null;
|
|
277
|
+
};
|
|
278
|
+
const paintChunk = (turn, chunk) => {
|
|
279
|
+
if (!chunk)
|
|
280
|
+
return;
|
|
281
|
+
const bit = document.createElement('span');
|
|
282
|
+
bit.className = 'talk-type';
|
|
283
|
+
bit.textContent = chunk;
|
|
284
|
+
turn.body.appendChild(bit);
|
|
285
|
+
};
|
|
286
|
+
const tickReveal = (turn) => {
|
|
287
|
+
turn.raf = null;
|
|
288
|
+
const remaining = turn.text.length - turn.shown;
|
|
289
|
+
if (remaining <= 0)
|
|
290
|
+
return;
|
|
291
|
+
const step = remaining > 36 ? Math.ceil(remaining / 4) : remaining > 10 ? 3 : 1;
|
|
292
|
+
const chunk = turn.text.slice(turn.shown, turn.shown + step);
|
|
293
|
+
turn.shown += chunk.length;
|
|
294
|
+
paintChunk(turn, chunk);
|
|
295
|
+
scrollLog();
|
|
296
|
+
if (turn.shown < turn.text.length) {
|
|
297
|
+
turn.raf = requestAnimationFrame(() => tickReveal(turn));
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
const feedTurn = (turn, delta) => {
|
|
301
|
+
if (!delta)
|
|
302
|
+
return;
|
|
303
|
+
turn.text += delta;
|
|
304
|
+
if (turn.raf === null)
|
|
305
|
+
turn.raf = requestAnimationFrame(() => tickReveal(turn));
|
|
306
|
+
};
|
|
307
|
+
const plopMessage = (el, role) => {
|
|
308
|
+
const origin = role === 'user' ? '100% 100%' : role === 'memory' ? '50% 100%' : '0% 100%';
|
|
309
|
+
gsap.killTweensOf(el);
|
|
310
|
+
gsap.fromTo(el, {
|
|
311
|
+
autoAlpha: 0,
|
|
312
|
+
scale: role === 'memory' ? 0.84 : 0.9,
|
|
313
|
+
y: 16,
|
|
314
|
+
transformOrigin: origin,
|
|
315
|
+
}, {
|
|
316
|
+
duration: 0.46,
|
|
317
|
+
autoAlpha: 1,
|
|
318
|
+
scale: 1,
|
|
319
|
+
y: 0,
|
|
320
|
+
ease: 'back.out(1.8)',
|
|
321
|
+
clearProps: 'transform',
|
|
322
|
+
});
|
|
323
|
+
};
|
|
324
|
+
const seal = () => {
|
|
325
|
+
const last = turns[turns.length - 1];
|
|
326
|
+
if (last)
|
|
327
|
+
last.live = false;
|
|
328
|
+
};
|
|
329
|
+
const appendTurn = (role, text) => {
|
|
330
|
+
if (!text)
|
|
331
|
+
return;
|
|
332
|
+
const last = turns[turns.length - 1];
|
|
333
|
+
if (last && last.role === role && last.live) {
|
|
334
|
+
feedTurn(last, text);
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
const el = document.createElement('div');
|
|
338
|
+
const body = document.createElement(role === 'memory' ? 'span' : 'p');
|
|
339
|
+
body.className = role === 'memory' ? 'talk-memory-text' : 'talk-bubble-text';
|
|
340
|
+
if (role === 'memory') {
|
|
341
|
+
el.className = 'talk-memory';
|
|
342
|
+
const icon = document.createElement('span');
|
|
343
|
+
icon.className = 'material-symbols-outlined';
|
|
344
|
+
icon.textContent = 'psychology';
|
|
345
|
+
el.append(icon, body);
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
el.className = `talk-bubble talk-bubble-${role}`;
|
|
349
|
+
el.setAttribute('aria-label', role === 'user' ? (userProfile?.displayName ?? 'You') : assistantLabel);
|
|
350
|
+
el.append(body);
|
|
351
|
+
if (role === 'user')
|
|
352
|
+
stampUserAvatar(el, userProfile);
|
|
353
|
+
else
|
|
354
|
+
stampModelAvatar(el, model, modelIcons);
|
|
355
|
+
}
|
|
356
|
+
log.appendChild(el);
|
|
357
|
+
const turn = { role, text: '', shown: 0, live: role !== 'memory', el, body, raf: null };
|
|
358
|
+
turns.push(turn);
|
|
359
|
+
plopMessage(el, role);
|
|
360
|
+
feedTurn(turn, text);
|
|
361
|
+
scrollLog();
|
|
362
|
+
};
|
|
363
|
+
const clearLog = () => {
|
|
364
|
+
for (const turn of turns) {
|
|
365
|
+
stopReveal(turn);
|
|
366
|
+
gsap.killTweensOf(turn.el);
|
|
367
|
+
}
|
|
368
|
+
turns.length = 0;
|
|
369
|
+
log.innerHTML = '';
|
|
370
|
+
};
|
|
371
|
+
const hangUp = () => {
|
|
372
|
+
generation += 1;
|
|
373
|
+
starting = false;
|
|
374
|
+
session?.hangUp();
|
|
375
|
+
session = undefined;
|
|
376
|
+
setPhase('idle');
|
|
377
|
+
};
|
|
378
|
+
let pulsed = false;
|
|
379
|
+
const stopPulse = () => {
|
|
380
|
+
gsap.killTweensOf(action);
|
|
381
|
+
gsap.set(action, { scale: 1 });
|
|
382
|
+
};
|
|
383
|
+
const startPulse = () => {
|
|
384
|
+
gsap.to(action, {
|
|
385
|
+
scale: 1.025,
|
|
386
|
+
duration: 0.9,
|
|
387
|
+
yoyo: true,
|
|
388
|
+
repeat: -1,
|
|
389
|
+
ease: 'sine.inOut',
|
|
390
|
+
});
|
|
391
|
+
};
|
|
392
|
+
let split = false;
|
|
393
|
+
const revealStage = () => {
|
|
394
|
+
if (split)
|
|
395
|
+
return;
|
|
396
|
+
split = true;
|
|
397
|
+
gsap.fromTo(root, {
|
|
398
|
+
gridTemplateColumns: '100% 0%',
|
|
399
|
+
}, {
|
|
400
|
+
duration: 0.7,
|
|
401
|
+
ease: 'power2.inOut',
|
|
402
|
+
gridTemplateColumns: '38% 62%',
|
|
403
|
+
onComplete: () => root.classList.add('is-split'),
|
|
404
|
+
});
|
|
405
|
+
gsap.to(log, {
|
|
406
|
+
opacity: 1,
|
|
407
|
+
duration: 0.45,
|
|
408
|
+
delay: 0.18,
|
|
409
|
+
ease: 'power2.out',
|
|
410
|
+
});
|
|
411
|
+
};
|
|
412
|
+
const start = async () => {
|
|
413
|
+
if (session || starting)
|
|
414
|
+
return;
|
|
415
|
+
if (!pulsed) {
|
|
416
|
+
pulsed = true;
|
|
417
|
+
stopPulse();
|
|
418
|
+
revealStage();
|
|
419
|
+
}
|
|
420
|
+
const token = generation + 1;
|
|
421
|
+
generation = token;
|
|
422
|
+
starting = true;
|
|
423
|
+
clearLog();
|
|
424
|
+
setError('');
|
|
425
|
+
setPhase('connecting');
|
|
426
|
+
try {
|
|
427
|
+
const next = await startVoiceSession(options.credentials, model, {
|
|
428
|
+
onInput: (delta) => {
|
|
429
|
+
if (generation !== token)
|
|
430
|
+
return;
|
|
431
|
+
appendTurn('user', delta);
|
|
432
|
+
},
|
|
433
|
+
onOutput: (delta) => {
|
|
434
|
+
if (generation !== token)
|
|
435
|
+
return;
|
|
436
|
+
setPhase('talking');
|
|
437
|
+
appendTurn('assistant', delta);
|
|
438
|
+
},
|
|
439
|
+
onSpeechStart: () => {
|
|
440
|
+
if (generation !== token)
|
|
441
|
+
return;
|
|
442
|
+
seal();
|
|
443
|
+
setPhase('listening');
|
|
444
|
+
},
|
|
445
|
+
onSpeechStop: () => {
|
|
446
|
+
if (generation !== token)
|
|
447
|
+
return;
|
|
448
|
+
seal();
|
|
449
|
+
if (phase === 'listening')
|
|
450
|
+
setPhase('talking');
|
|
451
|
+
},
|
|
452
|
+
onMemory: (observation) => {
|
|
453
|
+
if (generation !== token)
|
|
454
|
+
return;
|
|
455
|
+
seal();
|
|
456
|
+
appendTurn('memory', observation);
|
|
457
|
+
},
|
|
458
|
+
onError: (message) => {
|
|
459
|
+
if (generation !== token)
|
|
460
|
+
return;
|
|
461
|
+
console.error(message);
|
|
462
|
+
setError(message);
|
|
463
|
+
},
|
|
464
|
+
onDone: () => {
|
|
465
|
+
if (generation !== token)
|
|
466
|
+
return;
|
|
467
|
+
session = undefined;
|
|
468
|
+
starting = false;
|
|
469
|
+
setPhase('idle');
|
|
470
|
+
},
|
|
471
|
+
}, voice);
|
|
472
|
+
if (generation !== token) {
|
|
473
|
+
next.hangUp();
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
session = next;
|
|
477
|
+
starting = false;
|
|
478
|
+
setPhase('listening');
|
|
479
|
+
}
|
|
480
|
+
catch (err) {
|
|
481
|
+
if (generation !== token)
|
|
482
|
+
return;
|
|
483
|
+
session = undefined;
|
|
484
|
+
starting = false;
|
|
485
|
+
setPhase('idle');
|
|
486
|
+
setError(err?.message ?? 'Could not start voice.');
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
const restart = () => {
|
|
490
|
+
if (phase === 'idle' && !starting && !session)
|
|
491
|
+
return;
|
|
492
|
+
hangUp();
|
|
493
|
+
void start();
|
|
494
|
+
};
|
|
495
|
+
const selector = createSelector(selectorEl, {
|
|
496
|
+
value: model,
|
|
497
|
+
onChange: (value) => {
|
|
498
|
+
const next = value;
|
|
499
|
+
if (next === model)
|
|
500
|
+
return;
|
|
501
|
+
model = next;
|
|
502
|
+
restart();
|
|
503
|
+
},
|
|
504
|
+
});
|
|
505
|
+
const demographic = createDemographic(demographicEl, {
|
|
506
|
+
value: voice,
|
|
507
|
+
omit: ['income'],
|
|
508
|
+
onChange: (next) => {
|
|
509
|
+
voice = next;
|
|
510
|
+
restart();
|
|
511
|
+
},
|
|
512
|
+
});
|
|
513
|
+
void Fauna.Parrot.Routing.getAll()
|
|
514
|
+
.then(({ voice: rows }) => {
|
|
515
|
+
const icons = {};
|
|
516
|
+
for (const row of rows ?? []) {
|
|
517
|
+
if (row.icon)
|
|
518
|
+
icons[row.model] = row.icon;
|
|
519
|
+
}
|
|
520
|
+
modelIcons = icons;
|
|
521
|
+
selector.setOptions((rows ?? []).map((row) => ({
|
|
522
|
+
value: row.model,
|
|
523
|
+
name: row.name,
|
|
524
|
+
icon: row.icon,
|
|
525
|
+
})));
|
|
526
|
+
const first = rows?.[0]?.model;
|
|
527
|
+
if (first) {
|
|
528
|
+
model = first;
|
|
529
|
+
selector.setValue(first);
|
|
530
|
+
}
|
|
531
|
+
for (const el of log.querySelectorAll('.talk-bubble-assistant')) {
|
|
532
|
+
stampModelAvatar(el, model, modelIcons);
|
|
533
|
+
}
|
|
534
|
+
})
|
|
535
|
+
.catch((err) => console.error('Failed to load voice models:', err));
|
|
536
|
+
action.onclick = () => {
|
|
537
|
+
if (phase === 'idle')
|
|
538
|
+
void start();
|
|
539
|
+
else {
|
|
540
|
+
hangUp();
|
|
541
|
+
options.onHangUp?.();
|
|
542
|
+
}
|
|
543
|
+
};
|
|
544
|
+
paintPhase();
|
|
545
|
+
startPulse();
|
|
546
|
+
return {
|
|
547
|
+
start,
|
|
548
|
+
hangUp,
|
|
549
|
+
destroy: () => {
|
|
550
|
+
stopPulse();
|
|
551
|
+
gsap.killTweensOf(root);
|
|
552
|
+
gsap.killTweensOf(log);
|
|
553
|
+
clearLog();
|
|
554
|
+
hangUp();
|
|
555
|
+
selector.destroy();
|
|
556
|
+
demographic.destroy();
|
|
557
|
+
root.remove();
|
|
558
|
+
},
|
|
559
|
+
};
|
|
560
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Fauna } from "fauna-api";
|
|
3
|
+
export type TicketsOptions = {
|
|
4
|
+
credentials: Fauna.Authentification.Credentials;
|
|
5
|
+
source?: string;
|
|
6
|
+
ticketId?: string;
|
|
7
|
+
onClose?: () => void;
|
|
8
|
+
};
|
|
9
|
+
export declare function Tickets(props: TicketsOptions): React.JSX.Element;
|
|
10
|
+
export declare function Support(props: TicketsOptions): React.JSX.Element;
|
|
11
|
+
export declare function createTickets(container: HTMLElement, options: TicketsOptions): {
|
|
12
|
+
destroy: () => void;
|
|
13
|
+
};
|