@shogo-ai/voice 1.4.1

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 (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +30 -0
  3. package/dist/chunk-D2EVSUZX.js +27 -0
  4. package/dist/chunk-Q3TJAJ54.js +165 -0
  5. package/dist/chunk-QHDIEE7O.js +453 -0
  6. package/dist/chunk-S3S2RNO6.js +671 -0
  7. package/dist/chunk-ZGYHDC5G.js +423 -0
  8. package/dist/index.cjs +1316 -0
  9. package/dist/index.d.cts +432 -0
  10. package/dist/index.d.ts +432 -0
  11. package/dist/index.js +735 -0
  12. package/dist/native/index.cjs +1667 -0
  13. package/dist/native/index.d.cts +285 -0
  14. package/dist/native/index.d.ts +285 -0
  15. package/dist/native/index.js +949 -0
  16. package/dist/organicParticlesShaders-DIva0vBO.d.cts +693 -0
  17. package/dist/organicParticlesShaders-DIva0vBO.d.ts +693 -0
  18. package/dist/react/index.cjs +1670 -0
  19. package/dist/react/index.d.cts +423 -0
  20. package/dist/react/index.d.ts +423 -0
  21. package/dist/react/index.js +952 -0
  22. package/dist/route/agent.cjs +981 -0
  23. package/dist/route/agent.d.cts +20 -0
  24. package/dist/route/agent.d.ts +20 -0
  25. package/dist/route/agent.js +17 -0
  26. package/dist/route/audio-tags.cjs +975 -0
  27. package/dist/route/audio-tags.d.cts +16 -0
  28. package/dist/route/audio-tags.d.ts +16 -0
  29. package/dist/route/audio-tags.js +13 -0
  30. package/dist/route/index.cjs +987 -0
  31. package/dist/route/index.d.cts +115 -0
  32. package/dist/route/index.d.ts +115 -0
  33. package/dist/route/index.js +12 -0
  34. package/dist/route/signed-url.cjs +975 -0
  35. package/dist/route/signed-url.d.cts +12 -0
  36. package/dist/route/signed-url.d.ts +12 -0
  37. package/dist/route/signed-url.js +13 -0
  38. package/dist/route/tts-preview.cjs +975 -0
  39. package/dist/route/tts-preview.d.cts +17 -0
  40. package/dist/route/tts-preview.d.ts +17 -0
  41. package/dist/route/tts-preview.js +13 -0
  42. package/dist/server.cjs +999 -0
  43. package/dist/server.d.cts +88 -0
  44. package/dist/server.d.ts +88 -0
  45. package/dist/server.js +11 -0
  46. package/dist/types-BKJtOUbE.d.cts +352 -0
  47. package/dist/types-BKJtOUbE.d.ts +352 -0
  48. package/package.json +148 -0
@@ -0,0 +1,1667 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/native/index.ts
31
+ var native_exports = {};
32
+ __export(native_exports, {
33
+ DEFAULT_ORGANIC_PARTICLES_CONFIG: () => DEFAULT_ORGANIC_PARTICLES_CONFIG,
34
+ DEFAULT_ORGANIC_SPHERE_CONFIG: () => DEFAULT_ORGANIC_SPHERE_CONFIG,
35
+ ORGANIC_PARTICLES_FRAGMENT_SHADER: () => ORGANIC_PARTICLES_FRAGMENT_SHADER,
36
+ ORGANIC_PARTICLES_VERTEX_SHADER: () => ORGANIC_PARTICLES_VERTEX_SHADER,
37
+ ORGANIC_SPHERE_FRAGMENT_SHADER: () => ORGANIC_SPHERE_FRAGMENT_SHADER,
38
+ ORGANIC_SPHERE_VERTEX_SHADER: () => ORGANIC_SPHERE_VERTEX_SHADER,
39
+ OrganicParticles: () => OrganicParticles,
40
+ OrganicSphere: () => OrganicSphere,
41
+ PERLIN_4D: () => PERLIN_4D,
42
+ ShogoVoiceProvider: () => ShogoVoiceProvider,
43
+ resolveOrganicParticlesConfig: () => resolveOrganicParticlesConfig,
44
+ resolveOrganicSphereConfig: () => resolveOrganicSphereConfig,
45
+ useChatConversation: () => useChatConversation,
46
+ useShogoChat: () => useShogoChat,
47
+ useShogoVoice: () => useShogoVoice,
48
+ useVoiceConversation: () => useVoiceConversation
49
+ });
50
+ module.exports = __toCommonJS(native_exports);
51
+
52
+ // src/native/ShogoVoiceProvider.tsx
53
+ var import_react_native = require("@elevenlabs/react-native");
54
+ var import_jsx_runtime = require("react/jsx-runtime");
55
+ function ShogoVoiceProvider(props) {
56
+ const { agentId, children } = props;
57
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native.ConversationProvider, { agentId, children });
58
+ }
59
+
60
+ // src/native/useVoiceConversation.ts
61
+ var import_react = require("react");
62
+ var import_react_native2 = require("react-native");
63
+ var import_react_native3 = require("@elevenlabs/react-native");
64
+
65
+ // src/shared/postJson.ts
66
+ function createPostJson({
67
+ authHeaders,
68
+ fetchCredentials
69
+ }) {
70
+ return async function postJson(path, body) {
71
+ return fetch(path, {
72
+ method: "POST",
73
+ headers: { "content-type": "application/json", ...authHeaders() },
74
+ credentials: fetchCredentials,
75
+ body: JSON.stringify(body)
76
+ });
77
+ };
78
+ }
79
+
80
+ // src/shared/memory.ts
81
+ function createMemoryAddTool({
82
+ postJson,
83
+ memoryAddPath
84
+ }) {
85
+ return async (params) => {
86
+ const fact = typeof params.fact === "string" ? params.fact : "";
87
+ if (!fact.trim()) return "Memory save failed: empty fact.";
88
+ try {
89
+ const res = await postJson(memoryAddPath, { fact });
90
+ if (!res.ok) return "Failed to save memory.";
91
+ return "Memory saved.";
92
+ } catch {
93
+ return "Memory save failed.";
94
+ }
95
+ };
96
+ }
97
+ function createMemoryContextInjector({
98
+ postJson,
99
+ memoryRetrievePath,
100
+ conversationRef,
101
+ enabled,
102
+ lastInjectedRef
103
+ }) {
104
+ return async function inject(userText) {
105
+ if (!enabled) return;
106
+ try {
107
+ const res = await postJson(memoryRetrievePath, { query: userText, limit: 4 });
108
+ if (!res.ok) return;
109
+ const data = await res.json();
110
+ const results = data.results ?? [];
111
+ if (!results.length) return;
112
+ const lines = results.map((r) => `- ${r.chunk.trim().replace(/\s+/g, " ").slice(0, 200)}`).join("\n");
113
+ if (lines === lastInjectedRef.current) return;
114
+ lastInjectedRef.current = lines;
115
+ const payload = `Relevant memory about this user:
116
+ ${lines}`;
117
+ conversationRef.current?.sendContextualUpdate(payload);
118
+ } catch {
119
+ }
120
+ };
121
+ }
122
+
123
+ // src/audioTags.ts
124
+ var AUDIO_TAGS = [
125
+ // Emotions
126
+ { tag: "happy", group: "emotion", label: "Happy", description: "Warm, upbeat delivery.", example: "[happy] Oh, I\u2019m so glad you stopped by." },
127
+ { tag: "sad", group: "emotion", label: "Sad", description: "Subdued, heavy-hearted tone.", example: "[sad] I wish things had gone differently." },
128
+ { tag: "excited", group: "emotion", label: "Excited", description: "Energized, eager delivery.", example: "[excited] Wait until you hear this!" },
129
+ { tag: "curious", group: "emotion", label: "Curious", description: "Inquisitive, rising inflection.", example: "[curious] Hmm, what did they look like?" },
130
+ { tag: "sarcastic", group: "emotion", label: "Sarcastic", description: "Dry, ironic tone.", example: "[sarcastic] Oh, fantastic. Another Monday." },
131
+ { tag: "angry", group: "emotion", label: "Angry", description: "Sharp, irritated edge.", example: "[angry] I told you not to touch that." },
132
+ { tag: "nervous", group: "emotion", label: "Nervous", description: "Uneasy, faltering delivery.", example: "[nervous] I\u2014 I didn\u2019t think you\u2019d actually come." },
133
+ { tag: "confident", group: "emotion", label: "Confident", description: "Grounded, assured delivery.", example: "[confident] Trust me. I\u2019ve done this before." },
134
+ // Delivery
135
+ { tag: "whispers", group: "delivery", label: "Whispers", description: "Hushed, close-mic delivery.", example: "[whispers] Don\u2019t say it too loud." },
136
+ { tag: "shouts", group: "delivery", label: "Shouts", description: "Loud, projected delivery.", example: "[shouts] Look out behind you!" },
137
+ { tag: "softly", group: "delivery", label: "Softly", description: "Gentle, quiet tone.", example: "[softly] It\u2019s okay. I\u2019m here." },
138
+ { tag: "slowly", group: "delivery", label: "Slowly", description: "Deliberate pacing.", example: "[slowly] Tell me\u2026 exactly\u2026 what happened." },
139
+ { tag: "quickly", group: "delivery", label: "Quickly", description: "Rapid, urgent pacing.", example: "[quickly] We need to go, right now." },
140
+ // Reactions (non-verbal sounds)
141
+ { tag: "laughs", group: "reaction", label: "Laughs", description: "A genuine laugh.", example: "That\u2019s ridiculous [laughs]." },
142
+ { tag: "chuckles", group: "reaction", label: "Chuckles", description: "A small, warm chuckle.", example: "[chuckles] You always say that." },
143
+ { tag: "sighs", group: "reaction", label: "Sighs", description: "An audible sigh.", example: "[sighs] Alright, fine. Let\u2019s do it." },
144
+ { tag: "gasps", group: "reaction", label: "Gasps", description: "A sharp inhale of surprise.", example: "[gasps] You didn\u2019t!" },
145
+ { tag: "hesitates", group: "reaction", label: "Hesitates", description: "A pause with uncertainty.", example: "[hesitates] I\u2019m not sure I should say." },
146
+ { tag: "clears throat", group: "reaction", label: "Clears throat", description: "Throat-clearing beat.", example: "[clears throat] As I was saying\u2026" }
147
+ ];
148
+ var KNOWN_TAG_SET = new Set(AUDIO_TAGS.map((t) => t.tag.toLowerCase()));
149
+ function stripAudioTags(text) {
150
+ if (!text) return "";
151
+ return text.replace(/\[([a-z][a-z ]{0,24})\]/gi, (match, inner) => {
152
+ const key = inner.trim().toLowerCase();
153
+ return KNOWN_TAG_SET.has(key) ? "" : match;
154
+ }).replace(/[ \t]{2,}/g, " ").replace(/\s+([,.!?;:])/g, "$1").trim();
155
+ }
156
+
157
+ // src/shared/transcript.ts
158
+ function createTranscriptDisconnectHandler({
159
+ isRestartingRef,
160
+ weStartedSessionRef,
161
+ transcriptRef,
162
+ onTranscript,
163
+ transcriptIngestPath,
164
+ postJson
165
+ }) {
166
+ return function handleTranscriptOnDisconnect() {
167
+ if (isRestartingRef.current) {
168
+ return;
169
+ }
170
+ if (!weStartedSessionRef.current) {
171
+ transcriptRef.current = [];
172
+ return;
173
+ }
174
+ weStartedSessionRef.current = false;
175
+ const transcript = transcriptRef.current.join("\n");
176
+ transcriptRef.current = [];
177
+ if (onTranscript) {
178
+ void onTranscript(transcript);
179
+ return;
180
+ }
181
+ const trimmed = transcript.trim();
182
+ if (trimmed.length < 20) return;
183
+ void postJson(transcriptIngestPath, { transcript: trimmed }).catch(() => {
184
+ });
185
+ };
186
+ }
187
+ function appendTranscriptLine(msg, { transcriptRef, weStartedSessionRef }) {
188
+ if (!msg?.message || !msg?.source) return;
189
+ if (!weStartedSessionRef.current) return;
190
+ const speaker = msg.source === "user" ? "User" : "Agent";
191
+ const cleaned = msg.source === "user" ? msg.message : stripAudioTags(msg.message);
192
+ if (!cleaned) return;
193
+ transcriptRef.current.push(`${speaker}: ${cleaned}`);
194
+ }
195
+
196
+ // src/shared/sessionPayload.ts
197
+ function sanitizeDynamicVariables(raw) {
198
+ if (raw == null) return {};
199
+ const out = {};
200
+ for (const [k, v] of Object.entries(raw)) {
201
+ if (v == null) continue;
202
+ out[k] = typeof v === "string" ? v : String(v);
203
+ }
204
+ return out;
205
+ }
206
+ function buildSessionPayload(opts) {
207
+ const dynamicVariables = {
208
+ ...sanitizeDynamicVariables(opts.dynamicVariables),
209
+ character_name: opts.characterName,
210
+ user_context: opts.userContext
211
+ };
212
+ if (typeof opts.conversationId === "string" && opts.conversationId.length > 0) {
213
+ dynamicVariables.conversation_id = opts.conversationId;
214
+ }
215
+ const payload = {
216
+ signedUrl: opts.signedUrl,
217
+ dynamicVariables
218
+ };
219
+ const agentOverrides = {};
220
+ if (opts.suppressFirstMessage) {
221
+ agentOverrides.firstMessage = "";
222
+ }
223
+ if (typeof opts.agentPromptOverride === "string" && opts.agentPromptOverride.length > 0) {
224
+ agentOverrides.prompt = { prompt: opts.agentPromptOverride };
225
+ }
226
+ if (Object.keys(agentOverrides).length > 0) {
227
+ payload.overrides = { agent: agentOverrides };
228
+ }
229
+ return payload;
230
+ }
231
+ async function fetchSignedUrl({
232
+ path,
233
+ fetchCredentials,
234
+ authHeaders
235
+ }) {
236
+ const res = await fetch(path, {
237
+ credentials: fetchCredentials,
238
+ headers: authHeaders()
239
+ });
240
+ if (!res.ok) throw new Error(`Signed URL request failed: ${res.status}`);
241
+ return await res.json();
242
+ }
243
+ function withProjectQuery(signedUrlPath, params) {
244
+ const tail = [];
245
+ if (typeof params.projectId === "string" && params.projectId.length > 0) {
246
+ tail.push(`projectId=${encodeURIComponent(params.projectId)}`);
247
+ }
248
+ if (typeof params.agentName === "string" && params.agentName.length > 0) {
249
+ tail.push(`agentName=${encodeURIComponent(params.agentName)}`);
250
+ }
251
+ if (tail.length === 0) return signedUrlPath;
252
+ const sep = signedUrlPath.includes("?") ? "&" : "?";
253
+ return `${signedUrlPath}${sep}${tail.join("&")}`;
254
+ }
255
+
256
+ // src/native/useVoiceConversation.ts
257
+ function useVoiceConversation(options) {
258
+ const {
259
+ characterName,
260
+ signedUrlPath = "/api/voice/signed-url",
261
+ memoryAddPath = "/api/memory/add",
262
+ memoryRetrievePath = "/api/memory/retrieve",
263
+ transcriptIngestPath = "/api/memory/ingest",
264
+ autoInjectMemory = true,
265
+ includeMemoryTool = true,
266
+ clientTools = {},
267
+ onTranscript,
268
+ shogoApiKey,
269
+ projectId,
270
+ agentName,
271
+ // RN fetch has no real "same-origin" — apps are always cross-
272
+ // origin to the backend. Default to `'omit'` for the bearer
273
+ // path and `'include'` for the cookie path so the cookie jar
274
+ // gets used when the consumer set up auth that way.
275
+ fetchCredentials = shogoApiKey ? "omit" : "include",
276
+ onError,
277
+ onMessage,
278
+ requestPermissions,
279
+ conversationId: optionConversationId,
280
+ dynamicVariables
281
+ } = options;
282
+ const authHeaders = (0, import_react.useCallback)(() => {
283
+ return shogoApiKey ? { authorization: `Bearer ${shogoApiKey}` } : {};
284
+ }, [shogoApiKey]);
285
+ const resolvedSignedUrlPath = withProjectQuery(signedUrlPath, { projectId, agentName });
286
+ const transcriptRef = (0, import_react.useRef)([]);
287
+ const lastInjectedRef = (0, import_react.useRef)("");
288
+ const weStartedSessionRef = (0, import_react.useRef)(false);
289
+ const isRestartingRef = (0, import_react.useRef)(false);
290
+ const [convaiConversationId, setConvaiConversationId] = (0, import_react.useState)(null);
291
+ const conversationRef = (0, import_react.useRef)(null);
292
+ const postJson = (0, import_react.useCallback)(
293
+ createPostJson({ authHeaders, fetchCredentials }),
294
+ [fetchCredentials, authHeaders]
295
+ );
296
+ const injectMemoryContext = (0, import_react.useCallback)(
297
+ createMemoryContextInjector({
298
+ postJson,
299
+ memoryRetrievePath,
300
+ conversationRef,
301
+ enabled: autoInjectMemory,
302
+ lastInjectedRef
303
+ }),
304
+ [autoInjectMemory, memoryRetrievePath, postJson]
305
+ );
306
+ const handleTranscriptOnDisconnect = (0, import_react.useCallback)(
307
+ createTranscriptDisconnectHandler({
308
+ isRestartingRef,
309
+ weStartedSessionRef,
310
+ transcriptRef,
311
+ onTranscript,
312
+ transcriptIngestPath,
313
+ postJson
314
+ }),
315
+ [onTranscript, postJson, transcriptIngestPath]
316
+ );
317
+ const builtInTools = includeMemoryTool ? { add_memory: createMemoryAddTool({ postJson, memoryAddPath }) } : {};
318
+ const mergedClientTools = { ...builtInTools, ...clientTools };
319
+ const conversation = (0, import_react_native3.useConversation)({
320
+ clientTools: mergedClientTools,
321
+ onConnect: (info) => {
322
+ const id = info?.conversationId;
323
+ if (typeof id === "string" && id.length > 0) {
324
+ setConvaiConversationId(id);
325
+ }
326
+ if (!isRestartingRef.current) {
327
+ transcriptRef.current = [];
328
+ lastInjectedRef.current = "";
329
+ }
330
+ },
331
+ onDisconnect: () => {
332
+ handleTranscriptOnDisconnect();
333
+ if (!isRestartingRef.current) {
334
+ setConvaiConversationId(null);
335
+ }
336
+ },
337
+ onError: (e) => {
338
+ onError?.(e);
339
+ },
340
+ onMessage: (m) => {
341
+ const msg = m;
342
+ if (!msg?.message || !msg?.source) return;
343
+ appendTranscriptLine(msg, { transcriptRef, weStartedSessionRef });
344
+ onMessage?.({ source: msg.source, message: msg.message });
345
+ if (autoInjectMemory && msg.source === "user" && msg.message.trim().length >= 3 && weStartedSessionRef.current) {
346
+ void injectMemoryContext(msg.message);
347
+ }
348
+ }
349
+ });
350
+ const { status, isSpeaking, isListening, isMuted, setMuted, startSession, endSession } = conversation;
351
+ conversationRef.current = conversation;
352
+ (0, import_react.useEffect)(() => {
353
+ const sub = import_react_native2.AppState.addEventListener(
354
+ "change",
355
+ (nextState) => {
356
+ if (nextState !== "background" && nextState !== "inactive") return;
357
+ if (!weStartedSessionRef.current || transcriptRef.current.length === 0) return;
358
+ const transcript = transcriptRef.current.join("\n").trim();
359
+ transcriptRef.current = [];
360
+ if (transcript.length < 20) return;
361
+ void postJson(transcriptIngestPath, { transcript }).catch(() => {
362
+ });
363
+ }
364
+ );
365
+ return () => {
366
+ sub.remove();
367
+ };
368
+ }, [postJson, transcriptIngestPath]);
369
+ const startInternal = (0, import_react.useCallback)(
370
+ async (opts) => {
371
+ if (status === "connected" || status === "connecting") {
372
+ try {
373
+ endSession();
374
+ await new Promise((r) => setTimeout(r, 120));
375
+ } catch {
376
+ }
377
+ }
378
+ if (requestPermissions) {
379
+ await requestPermissions();
380
+ }
381
+ const data = await fetchSignedUrl({
382
+ path: resolvedSignedUrlPath,
383
+ fetchCredentials,
384
+ authHeaders
385
+ });
386
+ const ctx = data.userContext || "No prior memories yet.";
387
+ weStartedSessionRef.current = true;
388
+ const sessionPayload = buildSessionPayload({
389
+ signedUrl: data.signedUrl,
390
+ characterName,
391
+ userContext: ctx,
392
+ agentPromptOverride: data.agentPromptOverride,
393
+ suppressFirstMessage: opts?.suppressFirstMessage,
394
+ conversationId: optionConversationId,
395
+ dynamicVariables
396
+ });
397
+ await startSession(sessionPayload);
398
+ },
399
+ [
400
+ status,
401
+ endSession,
402
+ resolvedSignedUrlPath,
403
+ fetchCredentials,
404
+ dynamicVariables,
405
+ startSession,
406
+ characterName,
407
+ authHeaders,
408
+ requestPermissions,
409
+ optionConversationId
410
+ ]
411
+ );
412
+ const start = (0, import_react.useCallback)(
413
+ async (opts) => {
414
+ await startInternal(opts);
415
+ },
416
+ [startInternal]
417
+ );
418
+ const end = (0, import_react.useCallback)(() => {
419
+ endSession();
420
+ }, [endSession]);
421
+ const restart = (0, import_react.useCallback)(
422
+ async (opts) => {
423
+ const suppress = opts?.suppressFirstMessage ?? true;
424
+ isRestartingRef.current = true;
425
+ try {
426
+ try {
427
+ endSession();
428
+ } catch {
429
+ }
430
+ await new Promise((r) => setTimeout(r, 120));
431
+ await startInternal({ suppressFirstMessage: suppress });
432
+ } finally {
433
+ isRestartingRef.current = false;
434
+ }
435
+ },
436
+ [endSession, startInternal]
437
+ );
438
+ const sendContextualUpdate = (0, import_react.useCallback)((text) => {
439
+ conversationRef.current?.sendContextualUpdate(text);
440
+ }, []);
441
+ const sendUserMessage = (0, import_react.useCallback)((text) => {
442
+ const ref = conversationRef.current;
443
+ if (!ref) return;
444
+ if (typeof ref.sendUserMessage === "function") {
445
+ ref.sendUserMessage(text);
446
+ return;
447
+ }
448
+ ref.sendContextualUpdate(text);
449
+ try {
450
+ ref.sendUserActivity?.();
451
+ } catch {
452
+ }
453
+ }, []);
454
+ const sendUserActivity = (0, import_react.useCallback)(() => {
455
+ conversationRef.current?.sendUserActivity?.();
456
+ }, []);
457
+ const getOutputByteFrequencyData = (0, import_react.useCallback)(() => {
458
+ try {
459
+ const fn = conversation.getOutputByteFrequencyData;
460
+ return fn ? fn() : null;
461
+ } catch {
462
+ return null;
463
+ }
464
+ }, [conversation]);
465
+ return {
466
+ start,
467
+ end,
468
+ restart,
469
+ status,
470
+ isSpeaking,
471
+ isListening,
472
+ isMuted,
473
+ setMuted,
474
+ getOutputByteFrequencyData,
475
+ sendContextualUpdate,
476
+ sendUserMessage,
477
+ sendUserActivity,
478
+ conversationId: optionConversationId ?? convaiConversationId,
479
+ convaiConversationId
480
+ };
481
+ }
482
+
483
+ // src/native/useShogoVoice.ts
484
+ function useShogoVoice(opts = {}) {
485
+ return useVoiceConversation({
486
+ characterName: opts.characterName ?? "Shogo",
487
+ signedUrlPath: opts.signedUrlPath ?? "/api/voice/signed-url",
488
+ fetchCredentials: opts.fetchCredentials ?? "include",
489
+ ...opts
490
+ });
491
+ }
492
+
493
+ // src/native/useChatConversation.ts
494
+ var import_react2 = require("react");
495
+ var import_react3 = require("@ai-sdk/react");
496
+ var import_ai = require("ai");
497
+
498
+ // src/shared/chatUrl.ts
499
+ function appendChatQuery(basePath, params) {
500
+ const tail = [];
501
+ for (const [k, v] of Object.entries(params)) {
502
+ if (typeof v === "string" && v.length > 0) {
503
+ tail.push(`${k}=${encodeURIComponent(v)}`);
504
+ }
505
+ }
506
+ if (tail.length === 0) return basePath;
507
+ const sep = basePath.includes("?") ? "&" : "?";
508
+ return `${basePath}${sep}${tail.join("&")}`;
509
+ }
510
+
511
+ // src/native/useChatConversation.ts
512
+ var DEFAULT_API_PATH = "/api/chat/turn";
513
+ var DEFAULT_CHAT_ID = "shogo-chat";
514
+ function syntheticId(prefix) {
515
+ return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
516
+ }
517
+ function useChatConversation(options = {}) {
518
+ const {
519
+ api = DEFAULT_API_PATH,
520
+ shogoApiKey,
521
+ projectId,
522
+ agentName,
523
+ conversationId,
524
+ initialMessages,
525
+ clientTools = {},
526
+ tools,
527
+ // RN fetch has no real "same-origin" — apps are always cross-
528
+ // origin to the backend. Default to `'omit'` for the bearer
529
+ // path and `'include'` for the cookie path so the cookie jar
530
+ // gets used when the consumer set up auth that way.
531
+ fetchCredentials = shogoApiKey ? "omit" : "include",
532
+ id,
533
+ onError,
534
+ dynamicVariables
535
+ } = options;
536
+ const resolvedApi = (0, import_react2.useMemo)(
537
+ () => appendChatQuery(api, { projectId, conversationId, agentName }),
538
+ [api, projectId, conversationId, agentName]
539
+ );
540
+ const headers = (0, import_react2.useMemo)(() => {
541
+ const h = {};
542
+ if (shogoApiKey) h.authorization = `Bearer ${shogoApiKey}`;
543
+ return h;
544
+ }, [shogoApiKey]);
545
+ const body = (0, import_react2.useMemo)(() => {
546
+ const b = {};
547
+ if (projectId) b.projectId = projectId;
548
+ if (agentName) b.agentName = agentName;
549
+ if (conversationId) b.conversationId = conversationId;
550
+ if (tools && tools.length > 0) b.tools = tools;
551
+ if (dynamicVariables && Object.keys(dynamicVariables).length > 0) {
552
+ b.dynamicVariables = dynamicVariables;
553
+ }
554
+ return b;
555
+ }, [projectId, agentName, conversationId, tools, dynamicVariables]);
556
+ const transport = (0, import_react2.useMemo)(
557
+ () => new import_ai.DefaultChatTransport({
558
+ api: resolvedApi,
559
+ credentials: fetchCredentials,
560
+ headers,
561
+ body
562
+ }),
563
+ [resolvedApi, fetchCredentials, headers, body]
564
+ );
565
+ const toolsRef = (0, import_react2.useRef)(clientTools);
566
+ toolsRef.current = clientTools;
567
+ const chatRef = (0, import_react2.useRef)(null);
568
+ const resolvedId = id ?? (conversationId ? `shogo-chat:${conversationId}` : DEFAULT_CHAT_ID);
569
+ const chat = (0, import_react3.useChat)({
570
+ id: resolvedId,
571
+ transport,
572
+ messages: initialMessages,
573
+ async onToolCall({ toolCall }) {
574
+ const name = toolCall.toolName;
575
+ const fn = toolsRef.current[name];
576
+ const current = chatRef.current;
577
+ if (!current) return;
578
+ if (!fn) {
579
+ await current.addToolOutput({
580
+ state: "output-error",
581
+ tool: name,
582
+ toolCallId: toolCall.toolCallId,
583
+ errorText: `Unknown chat tool: ${String(name)}`
584
+ });
585
+ return;
586
+ }
587
+ try {
588
+ const params = typeof toolCall.input === "object" && toolCall.input !== null ? toolCall.input : {};
589
+ const output = await fn(params);
590
+ await current.addToolOutput({
591
+ tool: name,
592
+ toolCallId: toolCall.toolCallId,
593
+ output
594
+ });
595
+ } catch (err) {
596
+ await current.addToolOutput({
597
+ state: "output-error",
598
+ tool: name,
599
+ toolCallId: toolCall.toolCallId,
600
+ errorText: err?.message ?? String(err)
601
+ });
602
+ }
603
+ },
604
+ onError(err) {
605
+ onError?.(err);
606
+ }
607
+ });
608
+ chatRef.current = chat;
609
+ const sendMessage = (0, import_react2.useCallback)(
610
+ async (text) => {
611
+ if (!text.trim()) return;
612
+ await chatRef.current?.sendMessage({ text });
613
+ },
614
+ []
615
+ );
616
+ const setMessages = (0, import_react2.useCallback)((messages) => {
617
+ chatRef.current?.setMessages?.(messages);
618
+ }, []);
619
+ const appendAssistantMessage = (0, import_react2.useCallback)((text) => {
620
+ const next = {
621
+ id: syntheticId("synthetic-assistant"),
622
+ role: "assistant",
623
+ parts: [{ type: "text", text }]
624
+ };
625
+ const current = chatRef.current;
626
+ if (!current) return;
627
+ const nextList = [...current.messages, next];
628
+ current.setMessages?.(nextList);
629
+ }, []);
630
+ const appendUserMessage = (0, import_react2.useCallback)((text) => {
631
+ const next = {
632
+ id: syntheticId("synthetic-user"),
633
+ role: "user",
634
+ parts: [{ type: "text", text }]
635
+ };
636
+ const current = chatRef.current;
637
+ if (!current) return;
638
+ const nextList = [...current.messages, next];
639
+ current.setMessages?.(nextList);
640
+ }, []);
641
+ const status = chat.status;
642
+ return {
643
+ messages: chat.messages,
644
+ sendMessage,
645
+ setMessages,
646
+ appendAssistantMessage,
647
+ appendUserMessage,
648
+ status,
649
+ conversationId: conversationId ?? null
650
+ };
651
+ }
652
+
653
+ // src/native/useShogoChat.ts
654
+ function useShogoChat(opts = {}) {
655
+ return useChatConversation({
656
+ api: opts.api ?? "/api/chat/turn",
657
+ fetchCredentials: opts.fetchCredentials ?? "include",
658
+ ...opts
659
+ });
660
+ }
661
+
662
+ // src/native/OrganicSphere.tsx
663
+ var import_react4 = require("react");
664
+ var import_react_native4 = require("react-native");
665
+ var import_expo_gl = require("expo-gl");
666
+ var import_expo_three = require("expo-three");
667
+ var THREE = __toESM(require("three"), 1);
668
+
669
+ // src/react/shaders/organicSphereShaders.ts
670
+ var PERLIN_4D = (
671
+ /* glsl */
672
+ `
673
+ vec4 permute(vec4 x){return mod(((x*34.0)+1.0)*x, 289.0);}
674
+ vec4 taylorInvSqrt(vec4 r){return 1.79284291400159 - 0.85373472095314 * r;}
675
+ vec4 fade(vec4 t) {return t*t*t*(t*(t*6.0-15.0)+10.0);}
676
+
677
+ float perlin4d(vec4 P){
678
+ vec4 Pi0 = floor(P);
679
+ vec4 Pi1 = Pi0 + 1.0;
680
+ Pi0 = mod(Pi0, 289.0);
681
+ Pi1 = mod(Pi1, 289.0);
682
+ vec4 Pf0 = fract(P);
683
+ vec4 Pf1 = Pf0 - 1.0;
684
+ vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x);
685
+ vec4 iy = vec4(Pi0.yy, Pi1.yy);
686
+ vec4 iz0 = vec4(Pi0.zzzz);
687
+ vec4 iz1 = vec4(Pi1.zzzz);
688
+ vec4 iw0 = vec4(Pi0.wwww);
689
+ vec4 iw1 = vec4(Pi1.wwww);
690
+
691
+ vec4 ixy = permute(permute(ix) + iy);
692
+ vec4 ixy0 = permute(ixy + iz0);
693
+ vec4 ixy1 = permute(ixy + iz1);
694
+ vec4 ixy00 = permute(ixy0 + iw0);
695
+ vec4 ixy01 = permute(ixy0 + iw1);
696
+ vec4 ixy10 = permute(ixy1 + iw0);
697
+ vec4 ixy11 = permute(ixy1 + iw1);
698
+
699
+ vec4 gx00 = ixy00 / 7.0;
700
+ vec4 gy00 = floor(gx00) / 7.0;
701
+ vec4 gz00 = floor(gy00) / 6.0;
702
+ gx00 = fract(gx00) - 0.5;
703
+ gy00 = fract(gy00) - 0.5;
704
+ gz00 = fract(gz00) - 0.5;
705
+ vec4 gw00 = vec4(0.75) - abs(gx00) - abs(gy00) - abs(gz00);
706
+ vec4 sw00 = step(gw00, vec4(0.0));
707
+ gx00 -= sw00 * (step(0.0, gx00) - 0.5);
708
+ gy00 -= sw00 * (step(0.0, gy00) - 0.5);
709
+
710
+ vec4 gx01 = ixy01 / 7.0;
711
+ vec4 gy01 = floor(gx01) / 7.0;
712
+ vec4 gz01 = floor(gy01) / 6.0;
713
+ gx01 = fract(gx01) - 0.5;
714
+ gy01 = fract(gy01) - 0.5;
715
+ gz01 = fract(gz01) - 0.5;
716
+ vec4 gw01 = vec4(0.75) - abs(gx01) - abs(gy01) - abs(gz01);
717
+ vec4 sw01 = step(gw01, vec4(0.0));
718
+ gx01 -= sw01 * (step(0.0, gx01) - 0.5);
719
+ gy01 -= sw01 * (step(0.0, gy01) - 0.5);
720
+
721
+ vec4 gx10 = ixy10 / 7.0;
722
+ vec4 gy10 = floor(gx10) / 7.0;
723
+ vec4 gz10 = floor(gy10) / 6.0;
724
+ gx10 = fract(gx10) - 0.5;
725
+ gy10 = fract(gy10) - 0.5;
726
+ gz10 = fract(gz10) - 0.5;
727
+ vec4 gw10 = vec4(0.75) - abs(gx10) - abs(gy10) - abs(gz10);
728
+ vec4 sw10 = step(gw10, vec4(0.0));
729
+ gx10 -= sw10 * (step(0.0, gx10) - 0.5);
730
+ gy10 -= sw10 * (step(0.0, gy10) - 0.5);
731
+
732
+ vec4 gx11 = ixy11 / 7.0;
733
+ vec4 gy11 = floor(gx11) / 7.0;
734
+ vec4 gz11 = floor(gy11) / 6.0;
735
+ gx11 = fract(gx11) - 0.5;
736
+ gy11 = fract(gy11) - 0.5;
737
+ gz11 = fract(gz11) - 0.5;
738
+ vec4 gw11 = vec4(0.75) - abs(gx11) - abs(gy11) - abs(gz11);
739
+ vec4 sw11 = step(gw11, vec4(0.0));
740
+ gx11 -= sw11 * (step(0.0, gx11) - 0.5);
741
+ gy11 -= sw11 * (step(0.0, gy11) - 0.5);
742
+
743
+ vec4 g0000 = vec4(gx00.x,gy00.x,gz00.x,gw00.x);
744
+ vec4 g1000 = vec4(gx00.y,gy00.y,gz00.y,gw00.y);
745
+ vec4 g0100 = vec4(gx00.z,gy00.z,gz00.z,gw00.z);
746
+ vec4 g1100 = vec4(gx00.w,gy00.w,gz00.w,gw00.w);
747
+ vec4 g0010 = vec4(gx10.x,gy10.x,gz10.x,gw10.x);
748
+ vec4 g1010 = vec4(gx10.y,gy10.y,gz10.y,gw10.y);
749
+ vec4 g0110 = vec4(gx10.z,gy10.z,gz10.z,gw10.z);
750
+ vec4 g1110 = vec4(gx10.w,gy10.w,gz10.w,gw10.w);
751
+ vec4 g0001 = vec4(gx01.x,gy01.x,gz01.x,gw01.x);
752
+ vec4 g1001 = vec4(gx01.y,gy01.y,gz01.y,gw01.y);
753
+ vec4 g0101 = vec4(gx01.z,gy01.z,gz01.z,gw01.z);
754
+ vec4 g1101 = vec4(gx01.w,gy01.w,gz01.w,gw01.w);
755
+ vec4 g0011 = vec4(gx11.x,gy11.x,gz11.x,gw11.x);
756
+ vec4 g1011 = vec4(gx11.y,gy11.y,gz11.y,gw11.y);
757
+ vec4 g0111 = vec4(gx11.z,gy11.z,gz11.z,gw11.z);
758
+ vec4 g1111 = vec4(gx11.w,gy11.w,gz11.w,gw11.w);
759
+
760
+ vec4 norm00 = taylorInvSqrt(vec4(dot(g0000, g0000), dot(g0100, g0100), dot(g1000, g1000), dot(g1100, g1100)));
761
+ g0000 *= norm00.x; g0100 *= norm00.y; g1000 *= norm00.z; g1100 *= norm00.w;
762
+
763
+ vec4 norm01 = taylorInvSqrt(vec4(dot(g0001, g0001), dot(g0101, g0101), dot(g1001, g1001), dot(g1101, g1101)));
764
+ g0001 *= norm01.x; g0101 *= norm01.y; g1001 *= norm01.z; g1101 *= norm01.w;
765
+
766
+ vec4 norm10 = taylorInvSqrt(vec4(dot(g0010, g0010), dot(g0110, g0110), dot(g1010, g1010), dot(g1110, g1110)));
767
+ g0010 *= norm10.x; g0110 *= norm10.y; g1010 *= norm10.z; g1110 *= norm10.w;
768
+
769
+ vec4 norm11 = taylorInvSqrt(vec4(dot(g0011, g0011), dot(g0111, g0111), dot(g1011, g1011), dot(g1111, g1111)));
770
+ g0011 *= norm11.x; g0111 *= norm11.y; g1011 *= norm11.z; g1111 *= norm11.w;
771
+
772
+ float n0000 = dot(g0000, Pf0);
773
+ float n1000 = dot(g1000, vec4(Pf1.x, Pf0.yzw));
774
+ float n0100 = dot(g0100, vec4(Pf0.x, Pf1.y, Pf0.zw));
775
+ float n1100 = dot(g1100, vec4(Pf1.xy, Pf0.zw));
776
+ float n0010 = dot(g0010, vec4(Pf0.xy, Pf1.z, Pf0.w));
777
+ float n1010 = dot(g1010, vec4(Pf1.x, Pf0.y, Pf1.z, Pf0.w));
778
+ float n0110 = dot(g0110, vec4(Pf0.x, Pf1.yz, Pf0.w));
779
+ float n1110 = dot(g1110, vec4(Pf1.xyz, Pf0.w));
780
+ float n0001 = dot(g0001, vec4(Pf0.xyz, Pf1.w));
781
+ float n1001 = dot(g1001, vec4(Pf1.x, Pf0.yz, Pf1.w));
782
+ float n0101 = dot(g0101, vec4(Pf0.x, Pf1.y, Pf0.z, Pf1.w));
783
+ float n1101 = dot(g1101, vec4(Pf1.xy, Pf0.z, Pf1.w));
784
+ float n0011 = dot(g0011, vec4(Pf0.xy, Pf1.zw));
785
+ float n1011 = dot(g1011, vec4(Pf1.x, Pf0.y, Pf1.zw));
786
+ float n0111 = dot(g0111, vec4(Pf0.x, Pf1.yzw));
787
+ float n1111 = dot(g1111, Pf1);
788
+
789
+ vec4 fade_xyzw = fade(Pf0);
790
+ vec4 n_0w = mix(vec4(n0000, n1000, n0100, n1100), vec4(n0001, n1001, n0101, n1101), fade_xyzw.w);
791
+ vec4 n_1w = mix(vec4(n0010, n1010, n0110, n1110), vec4(n0011, n1011, n0111, n1111), fade_xyzw.w);
792
+ vec4 n_zw = mix(n_0w, n_1w, fade_xyzw.z);
793
+ vec2 n_yzw = mix(n_zw.xy, n_zw.zw, fade_xyzw.y);
794
+ float n_xyzw = mix(n_yzw.x, n_yzw.y, fade_xyzw.x);
795
+ return 2.2 * n_xyzw;
796
+ }
797
+ `
798
+ );
799
+ var ORGANIC_SPHERE_VERTEX_SHADER = (
800
+ /* glsl */
801
+ `
802
+ #define M_PI 3.1415926535897932384626433832795
803
+
804
+ uniform vec3 uLightAColor;
805
+ uniform vec3 uLightAPosition;
806
+ uniform float uLightAIntensity;
807
+ uniform vec3 uLightBColor;
808
+ uniform vec3 uLightBPosition;
809
+ uniform float uLightBIntensity;
810
+
811
+ uniform vec2 uSubdivision;
812
+
813
+ uniform vec3 uOffset;
814
+
815
+ uniform float uDistortionFrequency;
816
+ uniform float uDistortionStrength;
817
+ uniform float uDisplacementFrequency;
818
+ uniform float uDisplacementStrength;
819
+
820
+ uniform float uFresnelOffset;
821
+ uniform float uFresnelMultiplier;
822
+ uniform float uFresnelPower;
823
+
824
+ uniform vec3 uRimColor;
825
+ uniform float uRimThreshold;
826
+ uniform float uRimPower;
827
+
828
+ uniform float uTime;
829
+
830
+ varying vec3 vColor;
831
+
832
+ ${PERLIN_4D}
833
+
834
+ vec3 getDisplacedPosition(vec3 _position)
835
+ {
836
+ vec3 distoredPosition = _position;
837
+ distoredPosition += perlin4d(vec4(distoredPosition * uDistortionFrequency + uOffset, uTime)) * uDistortionStrength;
838
+
839
+ float perlinStrength = perlin4d(vec4(distoredPosition * uDisplacementFrequency + uOffset, uTime));
840
+
841
+ vec3 displacedPosition = _position;
842
+ displacedPosition += normalize(_position) * perlinStrength * uDisplacementStrength;
843
+
844
+ return displacedPosition;
845
+ }
846
+
847
+ void main()
848
+ {
849
+ vec3 displacedPosition = getDisplacedPosition(position);
850
+ vec4 viewPosition = viewMatrix * vec4(displacedPosition, 1.0);
851
+ gl_Position = projectionMatrix * viewPosition;
852
+
853
+ float distanceA = (M_PI * 2.0) / uSubdivision.x;
854
+ float distanceB = M_PI / uSubdivision.x;
855
+
856
+ vec3 biTangent = cross(normal, tangent.xyz);
857
+
858
+ vec3 positionA = position + tangent.xyz * distanceA;
859
+ vec3 displacedPositionA = getDisplacedPosition(positionA);
860
+
861
+ vec3 positionB = position + biTangent.xyz * distanceB;
862
+ vec3 displacedPositionB = getDisplacedPosition(positionB);
863
+
864
+ vec3 computedNormal = cross(displacedPositionA - displacedPosition.xyz, displacedPositionB - displacedPosition.xyz);
865
+ computedNormal = normalize(computedNormal);
866
+
867
+ vec3 viewDirection = normalize(displacedPosition.xyz - cameraPosition);
868
+ float fresnel = uFresnelOffset + (1.0 + dot(viewDirection, computedNormal)) * uFresnelMultiplier;
869
+ fresnel = pow(max(0.0, fresnel), uFresnelPower);
870
+
871
+ float lightAIntensity = max(0.0, - dot(computedNormal.xyz, normalize(- uLightAPosition))) * uLightAIntensity;
872
+ float lightBIntensity = max(0.0, - dot(computedNormal.xyz, normalize(- uLightBPosition))) * uLightBIntensity;
873
+
874
+ // Clamp mix factors to [0, 1] so a high lightIntensity*fresnel (common on
875
+ // displacement peaks and loud audio) cannot extrapolate past the orange
876
+ // light color and saturate R, producing yellow-white hotspots.
877
+ vec3 color = vec3(0.0);
878
+ color = mix(color, uLightAColor, clamp(lightAIntensity * fresnel, 0.0, 1.0));
879
+ color = mix(color, uLightBColor, clamp(lightBIntensity * fresnel, 0.0, 1.0));
880
+ color = mix(color, uRimColor, clamp(pow(max(0.0, fresnel - uRimThreshold), uRimPower), 0.0, 1.0));
881
+
882
+ vColor = color;
883
+ }
884
+ `
885
+ );
886
+ var ORGANIC_SPHERE_FRAGMENT_SHADER = (
887
+ /* glsl */
888
+ `
889
+ varying vec3 vColor;
890
+
891
+ void main()
892
+ {
893
+ gl_FragColor = vec4(vColor, 1.0);
894
+ }
895
+ `
896
+ );
897
+
898
+ // src/react/sphereConfig.ts
899
+ var DEFAULT_ORGANIC_SPHERE_CONFIG = {
900
+ // Lighting & rim
901
+ lightAColor: "#c2410c",
902
+ lightBColor: "#fb923c",
903
+ lightAIntensity: 2.13,
904
+ lightBIntensity: 1.61,
905
+ rimColor: null,
906
+ rimThreshold: 0.92,
907
+ rimPower: 5,
908
+ // Static shape
909
+ distortionFrequency: 1.5,
910
+ displacementFrequency: 2.12,
911
+ fresnelOffset: -1.609,
912
+ fresnelPower: 1.793,
913
+ // Audio reactivity
914
+ volume: {
915
+ idle: 0.152,
916
+ gain: 0.3,
917
+ floor: 0,
918
+ attack: 0.03,
919
+ decay: 2e-3
920
+ },
921
+ medium: {
922
+ idle: 3.587,
923
+ gain: 2,
924
+ floor: 3.587,
925
+ attack: 8e-3,
926
+ decay: 4e-3
927
+ },
928
+ high: {
929
+ idle: 0.65,
930
+ gain: 5,
931
+ floor: 0.5,
932
+ attack: 0.02,
933
+ decay: 1e-3
934
+ },
935
+ low: {
936
+ idle: 3e-4,
937
+ gain: 3e-3,
938
+ floor: 1e-4,
939
+ attack: 5e-3,
940
+ decay: 2e-3
941
+ },
942
+ // Camera
943
+ cameraZ: 3.84,
944
+ fov: 35,
945
+ // Determinism
946
+ seed: 0,
947
+ // Init-only
948
+ subdivisions: 512,
949
+ maxPixelRatio: null,
950
+ backgroundColor: null
951
+ };
952
+ function resolveOrganicSphereConfig(partial) {
953
+ const base = DEFAULT_ORGANIC_SPHERE_CONFIG;
954
+ if (!partial) return { ...base, volume: { ...base.volume }, medium: { ...base.medium }, high: { ...base.high }, low: { ...base.low } };
955
+ return {
956
+ ...base,
957
+ ...partial,
958
+ volume: { ...base.volume, ...partial.volume ?? {} },
959
+ medium: { ...base.medium, ...partial.medium ?? {} },
960
+ high: { ...base.high, ...partial.high ?? {} },
961
+ low: { ...base.low, ...partial.low ?? {} }
962
+ };
963
+ }
964
+
965
+ // src/native/OrganicSphere.tsx
966
+ var import_jsx_runtime2 = require("react/jsx-runtime");
967
+ function seededUnit(seed, salt) {
968
+ const s = Math.sin((seed + salt * 7919) * 12.9898 + salt * 78.233) * 43758.5453;
969
+ return s - Math.floor(s);
970
+ }
971
+ function OrganicSphere({
972
+ getFrequencyData,
973
+ active = true,
974
+ config: configProp,
975
+ style
976
+ }) {
977
+ const resolvedConfig = (0, import_react4.useMemo)(
978
+ () => resolveOrganicSphereConfig(configProp),
979
+ [configProp]
980
+ );
981
+ const configRef = (0, import_react4.useRef)(resolvedConfig);
982
+ (0, import_react4.useEffect)(() => {
983
+ configRef.current = resolvedConfig;
984
+ }, [resolvedConfig]);
985
+ const getFrequencyDataRef = (0, import_react4.useRef)(getFrequencyData);
986
+ const activeRef = (0, import_react4.useRef)(active);
987
+ (0, import_react4.useEffect)(() => {
988
+ getFrequencyDataRef.current = getFrequencyData;
989
+ }, [getFrequencyData]);
990
+ (0, import_react4.useEffect)(() => {
991
+ activeRef.current = active;
992
+ }, [active]);
993
+ const disposeRef = (0, import_react4.useRef)(null);
994
+ (0, import_react4.useEffect)(() => {
995
+ return () => {
996
+ disposeRef.current?.();
997
+ disposeRef.current = null;
998
+ };
999
+ }, []);
1000
+ const initSubdivisions = resolvedConfig.subdivisions;
1001
+ const initMaxPixelRatio = resolvedConfig.maxPixelRatio;
1002
+ const initBackgroundColor = resolvedConfig.backgroundColor;
1003
+ const onContextCreate = (0, import_react4.useMemo)(
1004
+ () => async (gl) => {
1005
+ const cfg0 = configRef.current;
1006
+ const width = gl.drawingBufferWidth;
1007
+ const height = gl.drawingBufferHeight;
1008
+ const pixelRatio = Math.min(initMaxPixelRatio ?? import_react_native4.PixelRatio.get(), 2);
1009
+ const scene = new THREE.Scene();
1010
+ if (initBackgroundColor) scene.background = new THREE.Color(initBackgroundColor);
1011
+ const camera = new THREE.PerspectiveCamera(
1012
+ cfg0.fov,
1013
+ width / height || 1,
1014
+ 0.1,
1015
+ 100
1016
+ );
1017
+ camera.position.set(0, 0, cfg0.cameraZ);
1018
+ scene.add(camera);
1019
+ const renderer = new import_expo_three.Renderer({
1020
+ gl,
1021
+ antialias: true,
1022
+ alpha: initBackgroundColor === null,
1023
+ powerPreference: "high-performance"
1024
+ });
1025
+ renderer.setSize(width / pixelRatio, height / pixelRatio, false);
1026
+ renderer.setPixelRatio(pixelRatio);
1027
+ renderer.setClearColor(0, initBackgroundColor === null ? 0 : 1);
1028
+ const geometry = new THREE.SphereGeometry(1, initSubdivisions, initSubdivisions);
1029
+ geometry.computeTangents();
1030
+ const lightAPos = new THREE.Vector3().setFromSpherical(
1031
+ new THREE.Spherical(1, 0.615, 2.049)
1032
+ );
1033
+ const lightBPos = new THREE.Vector3().setFromSpherical(
1034
+ new THREE.Spherical(1, 2.561, -1.844)
1035
+ );
1036
+ const lightAColorObj = new THREE.Color(cfg0.lightAColor);
1037
+ const lightBColorObj = new THREE.Color(cfg0.lightBColor);
1038
+ const rimColorObj = new THREE.Color(cfg0.rimColor ?? cfg0.lightBColor);
1039
+ const material = new THREE.ShaderMaterial({
1040
+ uniforms: {
1041
+ uLightAColor: { value: lightAColorObj },
1042
+ uLightAPosition: { value: lightAPos },
1043
+ uLightAIntensity: { value: cfg0.lightAIntensity },
1044
+ uLightBColor: { value: lightBColorObj },
1045
+ uLightBPosition: { value: lightBPos },
1046
+ uLightBIntensity: { value: cfg0.lightBIntensity },
1047
+ uSubdivision: {
1048
+ value: new THREE.Vector2(initSubdivisions, initSubdivisions)
1049
+ },
1050
+ uOffset: { value: new THREE.Vector3() },
1051
+ uDistortionFrequency: { value: cfg0.distortionFrequency },
1052
+ uDistortionStrength: { value: cfg0.high.idle },
1053
+ uDisplacementFrequency: { value: cfg0.displacementFrequency },
1054
+ uDisplacementStrength: { value: cfg0.volume.idle },
1055
+ uFresnelOffset: { value: cfg0.fresnelOffset },
1056
+ uFresnelMultiplier: { value: cfg0.medium.idle },
1057
+ uFresnelPower: { value: cfg0.fresnelPower },
1058
+ uRimColor: { value: rimColorObj },
1059
+ uRimThreshold: { value: cfg0.rimThreshold },
1060
+ uRimPower: { value: cfg0.rimPower },
1061
+ uTime: { value: 0 }
1062
+ },
1063
+ defines: { USE_TANGENT: "" },
1064
+ vertexShader: ORGANIC_SPHERE_VERTEX_SHADER,
1065
+ fragmentShader: ORGANIC_SPHERE_FRAGMENT_SHADER
1066
+ });
1067
+ const mesh = new THREE.Mesh(geometry, material);
1068
+ scene.add(mesh);
1069
+ const seedInitialPhase = (seed) => ({
1070
+ phi: seededUnit(seed, 1) * Math.PI,
1071
+ theta: seededUnit(seed, 2) * Math.PI * 2
1072
+ });
1073
+ const initialPhase = seedInitialPhase(cfg0.seed);
1074
+ const offsetSpherical = new THREE.Spherical(1, initialPhase.phi, initialPhase.theta);
1075
+ const offsetDirection = new THREE.Vector3().setFromSpherical(offsetSpherical);
1076
+ const variations = {
1077
+ volume: { target: 0, current: cfg0.volume.idle },
1078
+ medium: { target: 0, current: cfg0.medium.idle },
1079
+ high: { target: 0, current: cfg0.high.idle },
1080
+ low: { target: 0, current: cfg0.low.idle }
1081
+ };
1082
+ const resetAnimationState = (cfg) => {
1083
+ material.uniforms.uTime.value = 0;
1084
+ material.uniforms.uOffset.value.set(0, 0, 0);
1085
+ const phase = seedInitialPhase(cfg.seed);
1086
+ offsetSpherical.radius = 1;
1087
+ offsetSpherical.phi = phase.phi;
1088
+ offsetSpherical.theta = phase.theta;
1089
+ variations.volume.current = cfg.volume.idle;
1090
+ variations.medium.current = cfg.medium.idle;
1091
+ variations.high.current = cfg.high.idle;
1092
+ variations.low.current = cfg.low.idle;
1093
+ variations.volume.target = cfg.volume.idle;
1094
+ variations.medium.target = cfg.medium.idle;
1095
+ variations.high.target = cfg.high.idle;
1096
+ variations.low.target = cfg.low.idle;
1097
+ };
1098
+ const LEVEL_COUNT = 8;
1099
+ function computeLevels(buf) {
1100
+ const bins = Math.floor(buf.length / LEVEL_COUNT);
1101
+ const levels = new Array(LEVEL_COUNT).fill(0);
1102
+ if (bins <= 0) return levels;
1103
+ for (let i = 0; i < LEVEL_COUNT; i++) {
1104
+ let sum = 0;
1105
+ for (let j = 0; j < bins; j++) sum += buf[i * bins + j];
1106
+ levels[i] = sum / bins / 256;
1107
+ }
1108
+ return levels;
1109
+ }
1110
+ function bandTarget(reactivity, levels, sourceLevel) {
1111
+ if (!levels) return reactivity.idle;
1112
+ return Math.max(
1113
+ reactivity.floor,
1114
+ reactivity.floor + sourceLevel * reactivity.gain
1115
+ );
1116
+ }
1117
+ let rafId = 0;
1118
+ let lastTs = Date.now();
1119
+ let disposed = false;
1120
+ let wasActive = activeRef.current;
1121
+ let lastSeed = cfg0.seed;
1122
+ const render = () => {
1123
+ if (disposed) return;
1124
+ const now = Date.now();
1125
+ const delta = Math.min(now - lastTs, 100);
1126
+ lastTs = now;
1127
+ const cfg = configRef.current;
1128
+ lightAColorObj.set(cfg.lightAColor);
1129
+ lightBColorObj.set(cfg.lightBColor);
1130
+ rimColorObj.set(cfg.rimColor ?? cfg.lightBColor);
1131
+ material.uniforms.uLightAIntensity.value = cfg.lightAIntensity;
1132
+ material.uniforms.uLightBIntensity.value = cfg.lightBIntensity;
1133
+ material.uniforms.uDistortionFrequency.value = cfg.distortionFrequency;
1134
+ material.uniforms.uDisplacementFrequency.value = cfg.displacementFrequency;
1135
+ material.uniforms.uFresnelOffset.value = cfg.fresnelOffset;
1136
+ material.uniforms.uFresnelPower.value = cfg.fresnelPower;
1137
+ material.uniforms.uRimThreshold.value = cfg.rimThreshold;
1138
+ material.uniforms.uRimPower.value = cfg.rimPower;
1139
+ if (camera.position.z !== cfg.cameraZ) camera.position.z = cfg.cameraZ;
1140
+ if (camera.fov !== cfg.fov) {
1141
+ camera.fov = cfg.fov;
1142
+ camera.updateProjectionMatrix();
1143
+ }
1144
+ const isActive = activeRef.current;
1145
+ if (isActive && !wasActive || cfg.seed !== lastSeed) {
1146
+ resetAnimationState(cfg);
1147
+ lastSeed = cfg.seed;
1148
+ }
1149
+ wasActive = isActive;
1150
+ if (isActive) {
1151
+ let levels = null;
1152
+ if (getFrequencyDataRef.current) {
1153
+ try {
1154
+ const buf = getFrequencyDataRef.current();
1155
+ if (buf && buf.length > 0) levels = computeLevels(buf);
1156
+ } catch {
1157
+ levels = null;
1158
+ }
1159
+ }
1160
+ const l0 = levels ? levels[0] || 0 : 0;
1161
+ const l1 = levels ? levels[1] || 0 : 0;
1162
+ const l2 = levels ? levels[2] || 0 : 0;
1163
+ const loudest = Math.max(l0, l1, l2);
1164
+ const update = (variation, reactivity, sourceLevel) => {
1165
+ variation.target = bandTarget(reactivity, levels, sourceLevel);
1166
+ const easing = variation.target > variation.current ? reactivity.attack : reactivity.decay;
1167
+ variation.current += (variation.target - variation.current) * easing * delta;
1168
+ };
1169
+ update(variations.volume, cfg.volume, loudest);
1170
+ update(variations.medium, cfg.medium, l1);
1171
+ update(variations.high, cfg.high, l2);
1172
+ update(variations.low, cfg.low, l0);
1173
+ const timeFrequency = variations.low.current;
1174
+ const elapsed = delta * timeFrequency;
1175
+ material.uniforms.uDisplacementStrength.value = variations.volume.current;
1176
+ material.uniforms.uDistortionStrength.value = variations.high.current;
1177
+ material.uniforms.uFresnelMultiplier.value = variations.medium.current;
1178
+ const offsetTime = (material.uniforms.uTime.value + elapsed) * 0.3;
1179
+ offsetSpherical.phi = (Math.sin(offsetTime * 1e-3) * Math.sin(offsetTime * 321e-5) * 0.5 + 0.5) * Math.PI;
1180
+ offsetSpherical.theta = (Math.sin(offsetTime * 1e-4) * Math.sin(offsetTime * 321e-6) * 0.5 + 0.5) * Math.PI * 2;
1181
+ offsetDirection.setFromSpherical(offsetSpherical);
1182
+ offsetDirection.multiplyScalar(timeFrequency * 2);
1183
+ material.uniforms.uOffset.value.add(offsetDirection);
1184
+ material.uniforms.uTime.value += elapsed;
1185
+ }
1186
+ renderer.render(scene, camera);
1187
+ gl.endFrameEXP();
1188
+ rafId = requestAnimationFrame(render);
1189
+ };
1190
+ rafId = requestAnimationFrame(render);
1191
+ const dispose = () => {
1192
+ if (disposed) return;
1193
+ disposed = true;
1194
+ cancelAnimationFrame(rafId);
1195
+ geometry.dispose();
1196
+ material.dispose();
1197
+ renderer.dispose();
1198
+ };
1199
+ disposeRef.current = dispose;
1200
+ },
1201
+ [initSubdivisions, initMaxPixelRatio, initBackgroundColor]
1202
+ );
1203
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1204
+ import_expo_gl.GLView,
1205
+ {
1206
+ style,
1207
+ onContextCreate
1208
+ },
1209
+ `${initSubdivisions}-${initMaxPixelRatio ?? "auto"}-${initBackgroundColor ?? "transparent"}`
1210
+ );
1211
+ }
1212
+
1213
+ // src/native/OrganicParticles.tsx
1214
+ var import_react5 = require("react");
1215
+ var import_react_native5 = require("react-native");
1216
+ var import_expo_gl2 = require("expo-gl");
1217
+ var import_expo_three2 = require("expo-three");
1218
+ var THREE2 = __toESM(require("three"), 1);
1219
+
1220
+ // src/react/shaders/organicParticlesShaders.ts
1221
+ var ORGANIC_PARTICLES_VERTEX_SHADER = (
1222
+ /* glsl */
1223
+ `
1224
+ ${PERLIN_4D}
1225
+
1226
+ attribute vec3 aBasePosition;
1227
+ attribute float aSeed;
1228
+
1229
+ uniform float uTime;
1230
+ uniform float uNoiseFrequency;
1231
+ uniform float uNoiseStrength;
1232
+ uniform float uDisplacement;
1233
+ uniform float uSwirl;
1234
+ uniform float uSwirlTimeRate;
1235
+ uniform float uSizeBase;
1236
+ uniform float uSizeAudio;
1237
+ uniform float uPixelRatio;
1238
+
1239
+ varying float vHeat;
1240
+ varying float vSeed;
1241
+
1242
+ void main() {
1243
+ vec3 pos = aBasePosition;
1244
+
1245
+ // 4D Perlin sampled in particle space. The 4th axis is time so the
1246
+ // cloud evolves over time without tiling.
1247
+ float n = perlin4d(vec4(pos * uNoiseFrequency, uTime));
1248
+
1249
+ // Per-particle phase so the cloud doesn't breathe in lockstep.
1250
+ float phaseShift = (aSeed - 0.5) * 0.5;
1251
+ float breath = sin(uTime * 0.0008 + aSeed * 6.2831) * 0.5 + 0.5;
1252
+
1253
+ // Outward push: noise + audio-driven displacement, scaled by seed so
1254
+ // some particles dart further than others on the same beat.
1255
+ vec3 radial = normalize(pos);
1256
+ float push = n * uNoiseStrength + uDisplacement * (0.6 + aSeed * 0.8) + breath * 0.05;
1257
+ pos += radial * push;
1258
+
1259
+ // Twist around Y. Amount scales with height so the cloud spirals
1260
+ // rather than rigid-rotating. uSwirlTimeRate is 0 by default so the
1261
+ // swirl is driven by audio (uSwirl) alone; crank it for a constant
1262
+ // drift.
1263
+ float swirlAngle = pos.y * uSwirl + uTime * uSwirlTimeRate + phaseShift;
1264
+ float c = cos(swirlAngle);
1265
+ float s = sin(swirlAngle);
1266
+ pos.xz = mat2(c, -s, s, c) * pos.xz;
1267
+
1268
+ vec4 mvPos = modelViewMatrix * vec4(pos, 1.0);
1269
+ gl_Position = projectionMatrix * mvPos;
1270
+
1271
+ // Point size: base + audio boost, gently perspective-attenuated. At
1272
+ // the default camera (z ~= 3.84) a uSizeBase of 1 renders roughly one
1273
+ // device pixel \u2014 intentionally tiny so 40k+ additively-blended points
1274
+ // read as fine dust rather than a solid blob.
1275
+ float size = uSizeBase + uSizeAudio * (0.5 + aSeed);
1276
+ gl_PointSize = max(1.0, size * uPixelRatio * (2.0 / max(0.1, -mvPos.z)));
1277
+
1278
+ // Heat: 0 at core, 1 at fringe. Used by the fragment to lerp color.
1279
+ vHeat = clamp((length(pos) - 0.4) / 1.4, 0.0, 1.0);
1280
+ vSeed = aSeed;
1281
+ }
1282
+ `
1283
+ );
1284
+ var ORGANIC_PARTICLES_FRAGMENT_SHADER = (
1285
+ /* glsl */
1286
+ `
1287
+ precision mediump float;
1288
+
1289
+ uniform vec3 uColorCool;
1290
+ uniform vec3 uColorHot;
1291
+ uniform float uOpacity;
1292
+ uniform float uSoftness;
1293
+
1294
+ varying float vHeat;
1295
+ varying float vSeed;
1296
+
1297
+ void main() {
1298
+ vec2 centered = gl_PointCoord - vec2(0.5);
1299
+ float distSq = dot(centered, centered);
1300
+ if (distSq > 0.25) discard;
1301
+
1302
+ // Soft circular falloff. uSoftness in [0.5, 1] controls how hard the
1303
+ // edge is: 0.5 = sharp disc, 1 = feathered blob.
1304
+ float innerR = 0.25 * (1.0 - uSoftness);
1305
+ float alpha = smoothstep(0.25, innerR, distSq);
1306
+
1307
+ // Warm/cool gradient, with a tiny seed-based tint wobble so the cloud
1308
+ // doesn't look banded.
1309
+ float wobble = (vSeed - 0.5) * 0.15;
1310
+ vec3 color = mix(uColorCool, uColorHot, clamp(vHeat + wobble, 0.0, 1.0));
1311
+
1312
+ gl_FragColor = vec4(color, alpha * uOpacity);
1313
+ }
1314
+ `
1315
+ );
1316
+
1317
+ // src/react/particlesConfig.ts
1318
+ var DEFAULT_ORGANIC_PARTICLES_CONFIG = {
1319
+ // Cloud shape
1320
+ count: 4e4,
1321
+ innerRadius: 0.6,
1322
+ outerRadius: 1.1,
1323
+ // Motion. Constant rotation / swirl drift default to 0 so the cloud
1324
+ // only spins in response to audio — but `idleNoiseRate` keeps the
1325
+ // Perlin field shimmering slightly so the cloud doesn't look frozen
1326
+ // while paused.
1327
+ noiseFrequency: 1.4,
1328
+ noiseStrength: 0.22,
1329
+ rotationBias: 0,
1330
+ swirlTimeRate: 0,
1331
+ idleNoiseRate: 1e-4,
1332
+ // Rendering. Additive blending stacks contributions, so keep opacity
1333
+ // low (<0.3) or 40k particles will saturate to white. sizeBase ~1
1334
+ // renders as tiny dust; crank it higher for softer plasma look.
1335
+ sizeBase: 1,
1336
+ softness: 0.55,
1337
+ opacity: 0.18,
1338
+ // Color
1339
+ colorCool: "#c2410c",
1340
+ colorHot: "#fb923c",
1341
+ // Audio reactivity
1342
+ volume: {
1343
+ idle: 0.02,
1344
+ gain: 0.4,
1345
+ floor: 0,
1346
+ attack: 0.03,
1347
+ decay: 25e-4
1348
+ },
1349
+ medium: {
1350
+ idle: 0,
1351
+ gain: 4,
1352
+ floor: 0,
1353
+ attack: 0.01,
1354
+ decay: 4e-3
1355
+ },
1356
+ high: {
1357
+ idle: 0.2,
1358
+ gain: 1.2,
1359
+ floor: 0.15,
1360
+ attack: 0.015,
1361
+ decay: 2e-3
1362
+ },
1363
+ low: {
1364
+ idle: 4e-4,
1365
+ gain: 4e-3,
1366
+ floor: 1e-4,
1367
+ attack: 5e-3,
1368
+ decay: 2e-3
1369
+ },
1370
+ // Camera
1371
+ cameraZ: 3.84,
1372
+ fov: 35,
1373
+ // Determinism
1374
+ seed: 0,
1375
+ // Init-only
1376
+ maxPixelRatio: null,
1377
+ backgroundColor: null
1378
+ };
1379
+ function resolveOrganicParticlesConfig(partial) {
1380
+ const base = DEFAULT_ORGANIC_PARTICLES_CONFIG;
1381
+ if (!partial) {
1382
+ return {
1383
+ ...base,
1384
+ volume: { ...base.volume },
1385
+ medium: { ...base.medium },
1386
+ high: { ...base.high },
1387
+ low: { ...base.low }
1388
+ };
1389
+ }
1390
+ return {
1391
+ ...base,
1392
+ ...partial,
1393
+ volume: { ...base.volume, ...partial.volume ?? {} },
1394
+ medium: { ...base.medium, ...partial.medium ?? {} },
1395
+ high: { ...base.high, ...partial.high ?? {} },
1396
+ low: { ...base.low, ...partial.low ?? {} }
1397
+ };
1398
+ }
1399
+
1400
+ // src/native/OrganicParticles.tsx
1401
+ var import_jsx_runtime3 = require("react/jsx-runtime");
1402
+ function seededUnit2(seed, salt) {
1403
+ const s = Math.sin((seed + salt * 7919) * 12.9898 + salt * 78.233) * 43758.5453;
1404
+ return s - Math.floor(s);
1405
+ }
1406
+ function buildParticleAttributes(count, innerRadius, outerRadius, seed) {
1407
+ const positions = new Float32Array(count * 3);
1408
+ const seeds = new Float32Array(count);
1409
+ for (let i = 0; i < count; i++) {
1410
+ const u = seededUnit2(seed, i * 4 + 1);
1411
+ const v = seededUnit2(seed, i * 4 + 2);
1412
+ const w = seededUnit2(seed, i * 4 + 3);
1413
+ const s = seededUnit2(seed, i * 4 + 4);
1414
+ const theta = v * Math.PI * 2;
1415
+ const phi = Math.acos(2 * w - 1);
1416
+ const r = innerRadius + u * Math.max(0, outerRadius - innerRadius);
1417
+ const sinPhi = Math.sin(phi);
1418
+ positions[i * 3 + 0] = r * sinPhi * Math.cos(theta);
1419
+ positions[i * 3 + 1] = r * sinPhi * Math.sin(theta);
1420
+ positions[i * 3 + 2] = r * Math.cos(phi);
1421
+ seeds[i] = s;
1422
+ }
1423
+ return { positions, seeds };
1424
+ }
1425
+ function OrganicParticles({
1426
+ getFrequencyData,
1427
+ active = true,
1428
+ config: configProp,
1429
+ style
1430
+ }) {
1431
+ const resolvedConfig = (0, import_react5.useMemo)(
1432
+ () => resolveOrganicParticlesConfig(configProp),
1433
+ [configProp]
1434
+ );
1435
+ const configRef = (0, import_react5.useRef)(resolvedConfig);
1436
+ (0, import_react5.useEffect)(() => {
1437
+ configRef.current = resolvedConfig;
1438
+ }, [resolvedConfig]);
1439
+ const getFrequencyDataRef = (0, import_react5.useRef)(getFrequencyData);
1440
+ const activeRef = (0, import_react5.useRef)(active);
1441
+ (0, import_react5.useEffect)(() => {
1442
+ getFrequencyDataRef.current = getFrequencyData;
1443
+ }, [getFrequencyData]);
1444
+ (0, import_react5.useEffect)(() => {
1445
+ activeRef.current = active;
1446
+ }, [active]);
1447
+ const disposeRef = (0, import_react5.useRef)(null);
1448
+ (0, import_react5.useEffect)(() => {
1449
+ return () => {
1450
+ disposeRef.current?.();
1451
+ disposeRef.current = null;
1452
+ };
1453
+ }, []);
1454
+ const initCount = resolvedConfig.count;
1455
+ const initInnerRadius = resolvedConfig.innerRadius;
1456
+ const initOuterRadius = resolvedConfig.outerRadius;
1457
+ const initSeed = resolvedConfig.seed;
1458
+ const initMaxPixelRatio = resolvedConfig.maxPixelRatio;
1459
+ const initBackgroundColor = resolvedConfig.backgroundColor;
1460
+ const onContextCreate = (0, import_react5.useMemo)(
1461
+ () => async (gl) => {
1462
+ const cfg0 = configRef.current;
1463
+ const width = gl.drawingBufferWidth;
1464
+ const height = gl.drawingBufferHeight;
1465
+ const pixelRatio = Math.min(initMaxPixelRatio ?? import_react_native5.PixelRatio.get(), 2);
1466
+ const scene = new THREE2.Scene();
1467
+ if (initBackgroundColor) scene.background = new THREE2.Color(initBackgroundColor);
1468
+ const camera = new THREE2.PerspectiveCamera(
1469
+ cfg0.fov,
1470
+ width / height || 1,
1471
+ 0.1,
1472
+ 100
1473
+ );
1474
+ camera.position.set(0, 0, cfg0.cameraZ);
1475
+ scene.add(camera);
1476
+ const renderer = new import_expo_three2.Renderer({
1477
+ gl,
1478
+ antialias: true,
1479
+ alpha: initBackgroundColor === null,
1480
+ powerPreference: "high-performance"
1481
+ });
1482
+ renderer.setSize(width / pixelRatio, height / pixelRatio, false);
1483
+ renderer.setPixelRatio(pixelRatio);
1484
+ renderer.setClearColor(0, initBackgroundColor === null ? 0 : 1);
1485
+ const { positions, seeds } = buildParticleAttributes(
1486
+ initCount,
1487
+ initInnerRadius,
1488
+ initOuterRadius,
1489
+ initSeed
1490
+ );
1491
+ const geometry = new THREE2.BufferGeometry();
1492
+ geometry.setAttribute("aBasePosition", new THREE2.BufferAttribute(positions, 3));
1493
+ geometry.setAttribute("aSeed", new THREE2.BufferAttribute(seeds, 1));
1494
+ geometry.setAttribute("position", new THREE2.BufferAttribute(positions, 3));
1495
+ const colorCoolObj = new THREE2.Color(cfg0.colorCool);
1496
+ const colorHotObj = new THREE2.Color(cfg0.colorHot);
1497
+ const material = new THREE2.ShaderMaterial({
1498
+ uniforms: {
1499
+ uTime: { value: 0 },
1500
+ uNoiseFrequency: { value: cfg0.noiseFrequency },
1501
+ uNoiseStrength: { value: cfg0.noiseStrength },
1502
+ uDisplacement: { value: cfg0.volume.idle },
1503
+ uSwirl: { value: cfg0.high.idle },
1504
+ uSwirlTimeRate: { value: cfg0.swirlTimeRate },
1505
+ uSizeBase: { value: cfg0.sizeBase },
1506
+ uSizeAudio: { value: cfg0.medium.idle },
1507
+ uPixelRatio: { value: pixelRatio },
1508
+ uColorCool: { value: colorCoolObj },
1509
+ uColorHot: { value: colorHotObj },
1510
+ uOpacity: { value: cfg0.opacity },
1511
+ uSoftness: { value: cfg0.softness }
1512
+ },
1513
+ vertexShader: ORGANIC_PARTICLES_VERTEX_SHADER,
1514
+ fragmentShader: ORGANIC_PARTICLES_FRAGMENT_SHADER,
1515
+ transparent: true,
1516
+ depthWrite: false,
1517
+ blending: THREE2.AdditiveBlending
1518
+ });
1519
+ const points = new THREE2.Points(geometry, material);
1520
+ scene.add(points);
1521
+ const variations = {
1522
+ volume: { target: 0, current: cfg0.volume.idle },
1523
+ medium: { target: 0, current: cfg0.medium.idle },
1524
+ high: { target: 0, current: cfg0.high.idle },
1525
+ low: { target: 0, current: cfg0.low.idle }
1526
+ };
1527
+ const LEVEL_COUNT = 8;
1528
+ function computeLevels(buf) {
1529
+ const bins = Math.floor(buf.length / LEVEL_COUNT);
1530
+ const levels = new Array(LEVEL_COUNT).fill(0);
1531
+ if (bins <= 0) return levels;
1532
+ for (let i = 0; i < LEVEL_COUNT; i++) {
1533
+ let sum = 0;
1534
+ for (let j = 0; j < bins; j++) sum += buf[i * bins + j];
1535
+ levels[i] = sum / bins / 256;
1536
+ }
1537
+ return levels;
1538
+ }
1539
+ function bandTarget(reactivity, levels, sourceLevel) {
1540
+ if (!levels) return reactivity.idle;
1541
+ return Math.max(
1542
+ reactivity.floor,
1543
+ reactivity.floor + sourceLevel * reactivity.gain
1544
+ );
1545
+ }
1546
+ const rotationState = { y: 0 };
1547
+ const resetAnimationState = (cfg) => {
1548
+ material.uniforms.uTime.value = 0;
1549
+ rotationState.y = 0;
1550
+ points.rotation.set(0, 0, 0);
1551
+ variations.volume.current = cfg.volume.idle;
1552
+ variations.medium.current = cfg.medium.idle;
1553
+ variations.high.current = cfg.high.idle;
1554
+ variations.low.current = cfg.low.idle;
1555
+ variations.volume.target = cfg.volume.idle;
1556
+ variations.medium.target = cfg.medium.idle;
1557
+ variations.high.target = cfg.high.idle;
1558
+ variations.low.target = cfg.low.idle;
1559
+ };
1560
+ let rafId = 0;
1561
+ let lastTs = Date.now();
1562
+ let disposed = false;
1563
+ let wasActive = activeRef.current;
1564
+ const render = () => {
1565
+ if (disposed) return;
1566
+ const now = Date.now();
1567
+ const delta = Math.min(now - lastTs, 100);
1568
+ lastTs = now;
1569
+ const cfg = configRef.current;
1570
+ colorCoolObj.set(cfg.colorCool);
1571
+ colorHotObj.set(cfg.colorHot);
1572
+ material.uniforms.uNoiseFrequency.value = cfg.noiseFrequency;
1573
+ material.uniforms.uNoiseStrength.value = cfg.noiseStrength;
1574
+ material.uniforms.uSwirlTimeRate.value = cfg.swirlTimeRate;
1575
+ material.uniforms.uSizeBase.value = cfg.sizeBase;
1576
+ material.uniforms.uOpacity.value = cfg.opacity;
1577
+ material.uniforms.uSoftness.value = cfg.softness;
1578
+ if (camera.position.z !== cfg.cameraZ) camera.position.z = cfg.cameraZ;
1579
+ if (camera.fov !== cfg.fov) {
1580
+ camera.fov = cfg.fov;
1581
+ camera.updateProjectionMatrix();
1582
+ }
1583
+ const isActive = activeRef.current;
1584
+ if (isActive && !wasActive) {
1585
+ resetAnimationState(cfg);
1586
+ }
1587
+ wasActive = isActive;
1588
+ if (isActive) {
1589
+ let levels = null;
1590
+ if (getFrequencyDataRef.current) {
1591
+ try {
1592
+ const buf = getFrequencyDataRef.current();
1593
+ if (buf && buf.length > 0) levels = computeLevels(buf);
1594
+ } catch {
1595
+ levels = null;
1596
+ }
1597
+ }
1598
+ const l0 = levels ? levels[0] || 0 : 0;
1599
+ const l1 = levels ? levels[1] || 0 : 0;
1600
+ const l2 = levels ? levels[2] || 0 : 0;
1601
+ const loudest = Math.max(l0, l1, l2);
1602
+ const update = (variation, reactivity, sourceLevel) => {
1603
+ variation.target = bandTarget(reactivity, levels, sourceLevel);
1604
+ const easing = variation.target > variation.current ? reactivity.attack : reactivity.decay;
1605
+ variation.current += (variation.target - variation.current) * easing * delta;
1606
+ };
1607
+ update(variations.volume, cfg.volume, loudest);
1608
+ update(variations.medium, cfg.medium, l1);
1609
+ update(variations.high, cfg.high, l2);
1610
+ update(variations.low, cfg.low, l0);
1611
+ const timeFrequency = variations.low.current;
1612
+ const elapsed = delta * timeFrequency;
1613
+ material.uniforms.uDisplacement.value = variations.volume.current;
1614
+ material.uniforms.uSizeAudio.value = variations.medium.current;
1615
+ material.uniforms.uSwirl.value = variations.high.current;
1616
+ material.uniforms.uTime.value += elapsed;
1617
+ rotationState.y += delta * cfg.rotationBias + variations.high.current * delta * 8e-4;
1618
+ points.rotation.y = rotationState.y;
1619
+ } else {
1620
+ material.uniforms.uTime.value += delta * cfg.idleNoiseRate;
1621
+ }
1622
+ renderer.render(scene, camera);
1623
+ gl.endFrameEXP();
1624
+ rafId = requestAnimationFrame(render);
1625
+ };
1626
+ rafId = requestAnimationFrame(render);
1627
+ const dispose = () => {
1628
+ if (disposed) return;
1629
+ disposed = true;
1630
+ cancelAnimationFrame(rafId);
1631
+ geometry.dispose();
1632
+ material.dispose();
1633
+ renderer.dispose();
1634
+ };
1635
+ disposeRef.current = dispose;
1636
+ },
1637
+ [initCount, initInnerRadius, initOuterRadius, initSeed, initMaxPixelRatio, initBackgroundColor]
1638
+ );
1639
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1640
+ import_expo_gl2.GLView,
1641
+ {
1642
+ style,
1643
+ onContextCreate
1644
+ },
1645
+ `${initCount}-${initInnerRadius}-${initOuterRadius}-${initSeed}-${initMaxPixelRatio ?? "auto"}-${initBackgroundColor ?? "transparent"}`
1646
+ );
1647
+ }
1648
+ // Annotate the CommonJS export names for ESM import in node:
1649
+ 0 && (module.exports = {
1650
+ DEFAULT_ORGANIC_PARTICLES_CONFIG,
1651
+ DEFAULT_ORGANIC_SPHERE_CONFIG,
1652
+ ORGANIC_PARTICLES_FRAGMENT_SHADER,
1653
+ ORGANIC_PARTICLES_VERTEX_SHADER,
1654
+ ORGANIC_SPHERE_FRAGMENT_SHADER,
1655
+ ORGANIC_SPHERE_VERTEX_SHADER,
1656
+ OrganicParticles,
1657
+ OrganicSphere,
1658
+ PERLIN_4D,
1659
+ ShogoVoiceProvider,
1660
+ resolveOrganicParticlesConfig,
1661
+ resolveOrganicSphereConfig,
1662
+ useChatConversation,
1663
+ useShogoChat,
1664
+ useShogoVoice,
1665
+ useVoiceConversation
1666
+ });
1667
+ //# sourceMappingURL=index.cjs.map