@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.
- package/LICENSE +21 -0
- package/README.md +30 -0
- package/dist/chunk-D2EVSUZX.js +27 -0
- package/dist/chunk-Q3TJAJ54.js +165 -0
- package/dist/chunk-QHDIEE7O.js +453 -0
- package/dist/chunk-S3S2RNO6.js +671 -0
- package/dist/chunk-ZGYHDC5G.js +423 -0
- package/dist/index.cjs +1316 -0
- package/dist/index.d.cts +432 -0
- package/dist/index.d.ts +432 -0
- package/dist/index.js +735 -0
- package/dist/native/index.cjs +1667 -0
- package/dist/native/index.d.cts +285 -0
- package/dist/native/index.d.ts +285 -0
- package/dist/native/index.js +949 -0
- package/dist/organicParticlesShaders-DIva0vBO.d.cts +693 -0
- package/dist/organicParticlesShaders-DIva0vBO.d.ts +693 -0
- package/dist/react/index.cjs +1670 -0
- package/dist/react/index.d.cts +423 -0
- package/dist/react/index.d.ts +423 -0
- package/dist/react/index.js +952 -0
- package/dist/route/agent.cjs +981 -0
- package/dist/route/agent.d.cts +20 -0
- package/dist/route/agent.d.ts +20 -0
- package/dist/route/agent.js +17 -0
- package/dist/route/audio-tags.cjs +975 -0
- package/dist/route/audio-tags.d.cts +16 -0
- package/dist/route/audio-tags.d.ts +16 -0
- package/dist/route/audio-tags.js +13 -0
- package/dist/route/index.cjs +987 -0
- package/dist/route/index.d.cts +115 -0
- package/dist/route/index.d.ts +115 -0
- package/dist/route/index.js +12 -0
- package/dist/route/signed-url.cjs +975 -0
- package/dist/route/signed-url.d.cts +12 -0
- package/dist/route/signed-url.d.ts +12 -0
- package/dist/route/signed-url.js +13 -0
- package/dist/route/tts-preview.cjs +975 -0
- package/dist/route/tts-preview.d.cts +17 -0
- package/dist/route/tts-preview.d.ts +17 -0
- package/dist/route/tts-preview.js +13 -0
- package/dist/server.cjs +999 -0
- package/dist/server.d.cts +88 -0
- package/dist/server.d.ts +88 -0
- package/dist/server.js +11 -0
- package/dist/types-BKJtOUbE.d.cts +352 -0
- package/dist/types-BKJtOUbE.d.ts +352 -0
- package/package.json +148 -0
|
@@ -0,0 +1,949 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_ORGANIC_PARTICLES_CONFIG,
|
|
3
|
+
DEFAULT_ORGANIC_SPHERE_CONFIG,
|
|
4
|
+
ORGANIC_PARTICLES_FRAGMENT_SHADER,
|
|
5
|
+
ORGANIC_PARTICLES_VERTEX_SHADER,
|
|
6
|
+
ORGANIC_SPHERE_FRAGMENT_SHADER,
|
|
7
|
+
ORGANIC_SPHERE_VERTEX_SHADER,
|
|
8
|
+
PERLIN_4D,
|
|
9
|
+
appendChatQuery,
|
|
10
|
+
appendTranscriptLine,
|
|
11
|
+
buildSessionPayload,
|
|
12
|
+
createMemoryAddTool,
|
|
13
|
+
createMemoryContextInjector,
|
|
14
|
+
createPostJson,
|
|
15
|
+
createTranscriptDisconnectHandler,
|
|
16
|
+
fetchSignedUrl,
|
|
17
|
+
resolveOrganicParticlesConfig,
|
|
18
|
+
resolveOrganicSphereConfig,
|
|
19
|
+
withProjectQuery
|
|
20
|
+
} from "../chunk-S3S2RNO6.js";
|
|
21
|
+
import "../chunk-Q3TJAJ54.js";
|
|
22
|
+
|
|
23
|
+
// src/native/ShogoVoiceProvider.tsx
|
|
24
|
+
import { ConversationProvider as ElevenLabsConversationProvider } from "@elevenlabs/react-native";
|
|
25
|
+
import { jsx } from "react/jsx-runtime";
|
|
26
|
+
function ShogoVoiceProvider(props) {
|
|
27
|
+
const { agentId, children } = props;
|
|
28
|
+
return /* @__PURE__ */ jsx(ElevenLabsConversationProvider, { agentId, children });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// src/native/useVoiceConversation.ts
|
|
32
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
33
|
+
import { AppState } from "react-native";
|
|
34
|
+
import { useConversation } from "@elevenlabs/react-native";
|
|
35
|
+
function useVoiceConversation(options) {
|
|
36
|
+
const {
|
|
37
|
+
characterName,
|
|
38
|
+
signedUrlPath = "/api/voice/signed-url",
|
|
39
|
+
memoryAddPath = "/api/memory/add",
|
|
40
|
+
memoryRetrievePath = "/api/memory/retrieve",
|
|
41
|
+
transcriptIngestPath = "/api/memory/ingest",
|
|
42
|
+
autoInjectMemory = true,
|
|
43
|
+
includeMemoryTool = true,
|
|
44
|
+
clientTools = {},
|
|
45
|
+
onTranscript,
|
|
46
|
+
shogoApiKey,
|
|
47
|
+
projectId,
|
|
48
|
+
agentName,
|
|
49
|
+
// RN fetch has no real "same-origin" — apps are always cross-
|
|
50
|
+
// origin to the backend. Default to `'omit'` for the bearer
|
|
51
|
+
// path and `'include'` for the cookie path so the cookie jar
|
|
52
|
+
// gets used when the consumer set up auth that way.
|
|
53
|
+
fetchCredentials = shogoApiKey ? "omit" : "include",
|
|
54
|
+
onError,
|
|
55
|
+
onMessage,
|
|
56
|
+
requestPermissions,
|
|
57
|
+
conversationId: optionConversationId,
|
|
58
|
+
dynamicVariables
|
|
59
|
+
} = options;
|
|
60
|
+
const authHeaders = useCallback(() => {
|
|
61
|
+
return shogoApiKey ? { authorization: `Bearer ${shogoApiKey}` } : {};
|
|
62
|
+
}, [shogoApiKey]);
|
|
63
|
+
const resolvedSignedUrlPath = withProjectQuery(signedUrlPath, { projectId, agentName });
|
|
64
|
+
const transcriptRef = useRef([]);
|
|
65
|
+
const lastInjectedRef = useRef("");
|
|
66
|
+
const weStartedSessionRef = useRef(false);
|
|
67
|
+
const isRestartingRef = useRef(false);
|
|
68
|
+
const [convaiConversationId, setConvaiConversationId] = useState(null);
|
|
69
|
+
const conversationRef = useRef(null);
|
|
70
|
+
const postJson = useCallback(
|
|
71
|
+
createPostJson({ authHeaders, fetchCredentials }),
|
|
72
|
+
[fetchCredentials, authHeaders]
|
|
73
|
+
);
|
|
74
|
+
const injectMemoryContext = useCallback(
|
|
75
|
+
createMemoryContextInjector({
|
|
76
|
+
postJson,
|
|
77
|
+
memoryRetrievePath,
|
|
78
|
+
conversationRef,
|
|
79
|
+
enabled: autoInjectMemory,
|
|
80
|
+
lastInjectedRef
|
|
81
|
+
}),
|
|
82
|
+
[autoInjectMemory, memoryRetrievePath, postJson]
|
|
83
|
+
);
|
|
84
|
+
const handleTranscriptOnDisconnect = useCallback(
|
|
85
|
+
createTranscriptDisconnectHandler({
|
|
86
|
+
isRestartingRef,
|
|
87
|
+
weStartedSessionRef,
|
|
88
|
+
transcriptRef,
|
|
89
|
+
onTranscript,
|
|
90
|
+
transcriptIngestPath,
|
|
91
|
+
postJson
|
|
92
|
+
}),
|
|
93
|
+
[onTranscript, postJson, transcriptIngestPath]
|
|
94
|
+
);
|
|
95
|
+
const builtInTools = includeMemoryTool ? { add_memory: createMemoryAddTool({ postJson, memoryAddPath }) } : {};
|
|
96
|
+
const mergedClientTools = { ...builtInTools, ...clientTools };
|
|
97
|
+
const conversation = useConversation({
|
|
98
|
+
clientTools: mergedClientTools,
|
|
99
|
+
onConnect: (info) => {
|
|
100
|
+
const id = info?.conversationId;
|
|
101
|
+
if (typeof id === "string" && id.length > 0) {
|
|
102
|
+
setConvaiConversationId(id);
|
|
103
|
+
}
|
|
104
|
+
if (!isRestartingRef.current) {
|
|
105
|
+
transcriptRef.current = [];
|
|
106
|
+
lastInjectedRef.current = "";
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
onDisconnect: () => {
|
|
110
|
+
handleTranscriptOnDisconnect();
|
|
111
|
+
if (!isRestartingRef.current) {
|
|
112
|
+
setConvaiConversationId(null);
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
onError: (e) => {
|
|
116
|
+
onError?.(e);
|
|
117
|
+
},
|
|
118
|
+
onMessage: (m) => {
|
|
119
|
+
const msg = m;
|
|
120
|
+
if (!msg?.message || !msg?.source) return;
|
|
121
|
+
appendTranscriptLine(msg, { transcriptRef, weStartedSessionRef });
|
|
122
|
+
onMessage?.({ source: msg.source, message: msg.message });
|
|
123
|
+
if (autoInjectMemory && msg.source === "user" && msg.message.trim().length >= 3 && weStartedSessionRef.current) {
|
|
124
|
+
void injectMemoryContext(msg.message);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
const { status, isSpeaking, isListening, isMuted, setMuted, startSession, endSession } = conversation;
|
|
129
|
+
conversationRef.current = conversation;
|
|
130
|
+
useEffect(() => {
|
|
131
|
+
const sub = AppState.addEventListener(
|
|
132
|
+
"change",
|
|
133
|
+
(nextState) => {
|
|
134
|
+
if (nextState !== "background" && nextState !== "inactive") return;
|
|
135
|
+
if (!weStartedSessionRef.current || transcriptRef.current.length === 0) return;
|
|
136
|
+
const transcript = transcriptRef.current.join("\n").trim();
|
|
137
|
+
transcriptRef.current = [];
|
|
138
|
+
if (transcript.length < 20) return;
|
|
139
|
+
void postJson(transcriptIngestPath, { transcript }).catch(() => {
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
);
|
|
143
|
+
return () => {
|
|
144
|
+
sub.remove();
|
|
145
|
+
};
|
|
146
|
+
}, [postJson, transcriptIngestPath]);
|
|
147
|
+
const startInternal = useCallback(
|
|
148
|
+
async (opts) => {
|
|
149
|
+
if (status === "connected" || status === "connecting") {
|
|
150
|
+
try {
|
|
151
|
+
endSession();
|
|
152
|
+
await new Promise((r) => setTimeout(r, 120));
|
|
153
|
+
} catch {
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (requestPermissions) {
|
|
157
|
+
await requestPermissions();
|
|
158
|
+
}
|
|
159
|
+
const data = await fetchSignedUrl({
|
|
160
|
+
path: resolvedSignedUrlPath,
|
|
161
|
+
fetchCredentials,
|
|
162
|
+
authHeaders
|
|
163
|
+
});
|
|
164
|
+
const ctx = data.userContext || "No prior memories yet.";
|
|
165
|
+
weStartedSessionRef.current = true;
|
|
166
|
+
const sessionPayload = buildSessionPayload({
|
|
167
|
+
signedUrl: data.signedUrl,
|
|
168
|
+
characterName,
|
|
169
|
+
userContext: ctx,
|
|
170
|
+
agentPromptOverride: data.agentPromptOverride,
|
|
171
|
+
suppressFirstMessage: opts?.suppressFirstMessage,
|
|
172
|
+
conversationId: optionConversationId,
|
|
173
|
+
dynamicVariables
|
|
174
|
+
});
|
|
175
|
+
await startSession(sessionPayload);
|
|
176
|
+
},
|
|
177
|
+
[
|
|
178
|
+
status,
|
|
179
|
+
endSession,
|
|
180
|
+
resolvedSignedUrlPath,
|
|
181
|
+
fetchCredentials,
|
|
182
|
+
dynamicVariables,
|
|
183
|
+
startSession,
|
|
184
|
+
characterName,
|
|
185
|
+
authHeaders,
|
|
186
|
+
requestPermissions,
|
|
187
|
+
optionConversationId
|
|
188
|
+
]
|
|
189
|
+
);
|
|
190
|
+
const start = useCallback(
|
|
191
|
+
async (opts) => {
|
|
192
|
+
await startInternal(opts);
|
|
193
|
+
},
|
|
194
|
+
[startInternal]
|
|
195
|
+
);
|
|
196
|
+
const end = useCallback(() => {
|
|
197
|
+
endSession();
|
|
198
|
+
}, [endSession]);
|
|
199
|
+
const restart = useCallback(
|
|
200
|
+
async (opts) => {
|
|
201
|
+
const suppress = opts?.suppressFirstMessage ?? true;
|
|
202
|
+
isRestartingRef.current = true;
|
|
203
|
+
try {
|
|
204
|
+
try {
|
|
205
|
+
endSession();
|
|
206
|
+
} catch {
|
|
207
|
+
}
|
|
208
|
+
await new Promise((r) => setTimeout(r, 120));
|
|
209
|
+
await startInternal({ suppressFirstMessage: suppress });
|
|
210
|
+
} finally {
|
|
211
|
+
isRestartingRef.current = false;
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
[endSession, startInternal]
|
|
215
|
+
);
|
|
216
|
+
const sendContextualUpdate = useCallback((text) => {
|
|
217
|
+
conversationRef.current?.sendContextualUpdate(text);
|
|
218
|
+
}, []);
|
|
219
|
+
const sendUserMessage = useCallback((text) => {
|
|
220
|
+
const ref = conversationRef.current;
|
|
221
|
+
if (!ref) return;
|
|
222
|
+
if (typeof ref.sendUserMessage === "function") {
|
|
223
|
+
ref.sendUserMessage(text);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
ref.sendContextualUpdate(text);
|
|
227
|
+
try {
|
|
228
|
+
ref.sendUserActivity?.();
|
|
229
|
+
} catch {
|
|
230
|
+
}
|
|
231
|
+
}, []);
|
|
232
|
+
const sendUserActivity = useCallback(() => {
|
|
233
|
+
conversationRef.current?.sendUserActivity?.();
|
|
234
|
+
}, []);
|
|
235
|
+
const getOutputByteFrequencyData = useCallback(() => {
|
|
236
|
+
try {
|
|
237
|
+
const fn = conversation.getOutputByteFrequencyData;
|
|
238
|
+
return fn ? fn() : null;
|
|
239
|
+
} catch {
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
}, [conversation]);
|
|
243
|
+
return {
|
|
244
|
+
start,
|
|
245
|
+
end,
|
|
246
|
+
restart,
|
|
247
|
+
status,
|
|
248
|
+
isSpeaking,
|
|
249
|
+
isListening,
|
|
250
|
+
isMuted,
|
|
251
|
+
setMuted,
|
|
252
|
+
getOutputByteFrequencyData,
|
|
253
|
+
sendContextualUpdate,
|
|
254
|
+
sendUserMessage,
|
|
255
|
+
sendUserActivity,
|
|
256
|
+
conversationId: optionConversationId ?? convaiConversationId,
|
|
257
|
+
convaiConversationId
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// src/native/useShogoVoice.ts
|
|
262
|
+
function useShogoVoice(opts = {}) {
|
|
263
|
+
return useVoiceConversation({
|
|
264
|
+
characterName: opts.characterName ?? "Shogo",
|
|
265
|
+
signedUrlPath: opts.signedUrlPath ?? "/api/voice/signed-url",
|
|
266
|
+
fetchCredentials: opts.fetchCredentials ?? "include",
|
|
267
|
+
...opts
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// src/native/useChatConversation.ts
|
|
272
|
+
import { useCallback as useCallback2, useMemo, useRef as useRef2 } from "react";
|
|
273
|
+
import { useChat } from "@ai-sdk/react";
|
|
274
|
+
import { DefaultChatTransport } from "ai";
|
|
275
|
+
var DEFAULT_API_PATH = "/api/chat/turn";
|
|
276
|
+
var DEFAULT_CHAT_ID = "shogo-chat";
|
|
277
|
+
function syntheticId(prefix) {
|
|
278
|
+
return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
279
|
+
}
|
|
280
|
+
function useChatConversation(options = {}) {
|
|
281
|
+
const {
|
|
282
|
+
api = DEFAULT_API_PATH,
|
|
283
|
+
shogoApiKey,
|
|
284
|
+
projectId,
|
|
285
|
+
agentName,
|
|
286
|
+
conversationId,
|
|
287
|
+
initialMessages,
|
|
288
|
+
clientTools = {},
|
|
289
|
+
tools,
|
|
290
|
+
// RN fetch has no real "same-origin" — apps are always cross-
|
|
291
|
+
// origin to the backend. Default to `'omit'` for the bearer
|
|
292
|
+
// path and `'include'` for the cookie path so the cookie jar
|
|
293
|
+
// gets used when the consumer set up auth that way.
|
|
294
|
+
fetchCredentials = shogoApiKey ? "omit" : "include",
|
|
295
|
+
id,
|
|
296
|
+
onError,
|
|
297
|
+
dynamicVariables
|
|
298
|
+
} = options;
|
|
299
|
+
const resolvedApi = useMemo(
|
|
300
|
+
() => appendChatQuery(api, { projectId, conversationId, agentName }),
|
|
301
|
+
[api, projectId, conversationId, agentName]
|
|
302
|
+
);
|
|
303
|
+
const headers = useMemo(() => {
|
|
304
|
+
const h = {};
|
|
305
|
+
if (shogoApiKey) h.authorization = `Bearer ${shogoApiKey}`;
|
|
306
|
+
return h;
|
|
307
|
+
}, [shogoApiKey]);
|
|
308
|
+
const body = useMemo(() => {
|
|
309
|
+
const b = {};
|
|
310
|
+
if (projectId) b.projectId = projectId;
|
|
311
|
+
if (agentName) b.agentName = agentName;
|
|
312
|
+
if (conversationId) b.conversationId = conversationId;
|
|
313
|
+
if (tools && tools.length > 0) b.tools = tools;
|
|
314
|
+
if (dynamicVariables && Object.keys(dynamicVariables).length > 0) {
|
|
315
|
+
b.dynamicVariables = dynamicVariables;
|
|
316
|
+
}
|
|
317
|
+
return b;
|
|
318
|
+
}, [projectId, agentName, conversationId, tools, dynamicVariables]);
|
|
319
|
+
const transport = useMemo(
|
|
320
|
+
() => new DefaultChatTransport({
|
|
321
|
+
api: resolvedApi,
|
|
322
|
+
credentials: fetchCredentials,
|
|
323
|
+
headers,
|
|
324
|
+
body
|
|
325
|
+
}),
|
|
326
|
+
[resolvedApi, fetchCredentials, headers, body]
|
|
327
|
+
);
|
|
328
|
+
const toolsRef = useRef2(clientTools);
|
|
329
|
+
toolsRef.current = clientTools;
|
|
330
|
+
const chatRef = useRef2(null);
|
|
331
|
+
const resolvedId = id ?? (conversationId ? `shogo-chat:${conversationId}` : DEFAULT_CHAT_ID);
|
|
332
|
+
const chat = useChat({
|
|
333
|
+
id: resolvedId,
|
|
334
|
+
transport,
|
|
335
|
+
messages: initialMessages,
|
|
336
|
+
async onToolCall({ toolCall }) {
|
|
337
|
+
const name = toolCall.toolName;
|
|
338
|
+
const fn = toolsRef.current[name];
|
|
339
|
+
const current = chatRef.current;
|
|
340
|
+
if (!current) return;
|
|
341
|
+
if (!fn) {
|
|
342
|
+
await current.addToolOutput({
|
|
343
|
+
state: "output-error",
|
|
344
|
+
tool: name,
|
|
345
|
+
toolCallId: toolCall.toolCallId,
|
|
346
|
+
errorText: `Unknown chat tool: ${String(name)}`
|
|
347
|
+
});
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
try {
|
|
351
|
+
const params = typeof toolCall.input === "object" && toolCall.input !== null ? toolCall.input : {};
|
|
352
|
+
const output = await fn(params);
|
|
353
|
+
await current.addToolOutput({
|
|
354
|
+
tool: name,
|
|
355
|
+
toolCallId: toolCall.toolCallId,
|
|
356
|
+
output
|
|
357
|
+
});
|
|
358
|
+
} catch (err) {
|
|
359
|
+
await current.addToolOutput({
|
|
360
|
+
state: "output-error",
|
|
361
|
+
tool: name,
|
|
362
|
+
toolCallId: toolCall.toolCallId,
|
|
363
|
+
errorText: err?.message ?? String(err)
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
},
|
|
367
|
+
onError(err) {
|
|
368
|
+
onError?.(err);
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
chatRef.current = chat;
|
|
372
|
+
const sendMessage = useCallback2(
|
|
373
|
+
async (text) => {
|
|
374
|
+
if (!text.trim()) return;
|
|
375
|
+
await chatRef.current?.sendMessage({ text });
|
|
376
|
+
},
|
|
377
|
+
[]
|
|
378
|
+
);
|
|
379
|
+
const setMessages = useCallback2((messages) => {
|
|
380
|
+
chatRef.current?.setMessages?.(messages);
|
|
381
|
+
}, []);
|
|
382
|
+
const appendAssistantMessage = useCallback2((text) => {
|
|
383
|
+
const next = {
|
|
384
|
+
id: syntheticId("synthetic-assistant"),
|
|
385
|
+
role: "assistant",
|
|
386
|
+
parts: [{ type: "text", text }]
|
|
387
|
+
};
|
|
388
|
+
const current = chatRef.current;
|
|
389
|
+
if (!current) return;
|
|
390
|
+
const nextList = [...current.messages, next];
|
|
391
|
+
current.setMessages?.(nextList);
|
|
392
|
+
}, []);
|
|
393
|
+
const appendUserMessage = useCallback2((text) => {
|
|
394
|
+
const next = {
|
|
395
|
+
id: syntheticId("synthetic-user"),
|
|
396
|
+
role: "user",
|
|
397
|
+
parts: [{ type: "text", text }]
|
|
398
|
+
};
|
|
399
|
+
const current = chatRef.current;
|
|
400
|
+
if (!current) return;
|
|
401
|
+
const nextList = [...current.messages, next];
|
|
402
|
+
current.setMessages?.(nextList);
|
|
403
|
+
}, []);
|
|
404
|
+
const status = chat.status;
|
|
405
|
+
return {
|
|
406
|
+
messages: chat.messages,
|
|
407
|
+
sendMessage,
|
|
408
|
+
setMessages,
|
|
409
|
+
appendAssistantMessage,
|
|
410
|
+
appendUserMessage,
|
|
411
|
+
status,
|
|
412
|
+
conversationId: conversationId ?? null
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// src/native/useShogoChat.ts
|
|
417
|
+
function useShogoChat(opts = {}) {
|
|
418
|
+
return useChatConversation({
|
|
419
|
+
api: opts.api ?? "/api/chat/turn",
|
|
420
|
+
fetchCredentials: opts.fetchCredentials ?? "include",
|
|
421
|
+
...opts
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// src/native/OrganicSphere.tsx
|
|
426
|
+
import { useEffect as useEffect2, useMemo as useMemo2, useRef as useRef3 } from "react";
|
|
427
|
+
import { PixelRatio } from "react-native";
|
|
428
|
+
import { GLView } from "expo-gl";
|
|
429
|
+
import { Renderer } from "expo-three";
|
|
430
|
+
import * as THREE from "three";
|
|
431
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
432
|
+
function seededUnit(seed, salt) {
|
|
433
|
+
const s = Math.sin((seed + salt * 7919) * 12.9898 + salt * 78.233) * 43758.5453;
|
|
434
|
+
return s - Math.floor(s);
|
|
435
|
+
}
|
|
436
|
+
function OrganicSphere({
|
|
437
|
+
getFrequencyData,
|
|
438
|
+
active = true,
|
|
439
|
+
config: configProp,
|
|
440
|
+
style
|
|
441
|
+
}) {
|
|
442
|
+
const resolvedConfig = useMemo2(
|
|
443
|
+
() => resolveOrganicSphereConfig(configProp),
|
|
444
|
+
[configProp]
|
|
445
|
+
);
|
|
446
|
+
const configRef = useRef3(resolvedConfig);
|
|
447
|
+
useEffect2(() => {
|
|
448
|
+
configRef.current = resolvedConfig;
|
|
449
|
+
}, [resolvedConfig]);
|
|
450
|
+
const getFrequencyDataRef = useRef3(getFrequencyData);
|
|
451
|
+
const activeRef = useRef3(active);
|
|
452
|
+
useEffect2(() => {
|
|
453
|
+
getFrequencyDataRef.current = getFrequencyData;
|
|
454
|
+
}, [getFrequencyData]);
|
|
455
|
+
useEffect2(() => {
|
|
456
|
+
activeRef.current = active;
|
|
457
|
+
}, [active]);
|
|
458
|
+
const disposeRef = useRef3(null);
|
|
459
|
+
useEffect2(() => {
|
|
460
|
+
return () => {
|
|
461
|
+
disposeRef.current?.();
|
|
462
|
+
disposeRef.current = null;
|
|
463
|
+
};
|
|
464
|
+
}, []);
|
|
465
|
+
const initSubdivisions = resolvedConfig.subdivisions;
|
|
466
|
+
const initMaxPixelRatio = resolvedConfig.maxPixelRatio;
|
|
467
|
+
const initBackgroundColor = resolvedConfig.backgroundColor;
|
|
468
|
+
const onContextCreate = useMemo2(
|
|
469
|
+
() => async (gl) => {
|
|
470
|
+
const cfg0 = configRef.current;
|
|
471
|
+
const width = gl.drawingBufferWidth;
|
|
472
|
+
const height = gl.drawingBufferHeight;
|
|
473
|
+
const pixelRatio = Math.min(initMaxPixelRatio ?? PixelRatio.get(), 2);
|
|
474
|
+
const scene = new THREE.Scene();
|
|
475
|
+
if (initBackgroundColor) scene.background = new THREE.Color(initBackgroundColor);
|
|
476
|
+
const camera = new THREE.PerspectiveCamera(
|
|
477
|
+
cfg0.fov,
|
|
478
|
+
width / height || 1,
|
|
479
|
+
0.1,
|
|
480
|
+
100
|
|
481
|
+
);
|
|
482
|
+
camera.position.set(0, 0, cfg0.cameraZ);
|
|
483
|
+
scene.add(camera);
|
|
484
|
+
const renderer = new Renderer({
|
|
485
|
+
gl,
|
|
486
|
+
antialias: true,
|
|
487
|
+
alpha: initBackgroundColor === null,
|
|
488
|
+
powerPreference: "high-performance"
|
|
489
|
+
});
|
|
490
|
+
renderer.setSize(width / pixelRatio, height / pixelRatio, false);
|
|
491
|
+
renderer.setPixelRatio(pixelRatio);
|
|
492
|
+
renderer.setClearColor(0, initBackgroundColor === null ? 0 : 1);
|
|
493
|
+
const geometry = new THREE.SphereGeometry(1, initSubdivisions, initSubdivisions);
|
|
494
|
+
geometry.computeTangents();
|
|
495
|
+
const lightAPos = new THREE.Vector3().setFromSpherical(
|
|
496
|
+
new THREE.Spherical(1, 0.615, 2.049)
|
|
497
|
+
);
|
|
498
|
+
const lightBPos = new THREE.Vector3().setFromSpherical(
|
|
499
|
+
new THREE.Spherical(1, 2.561, -1.844)
|
|
500
|
+
);
|
|
501
|
+
const lightAColorObj = new THREE.Color(cfg0.lightAColor);
|
|
502
|
+
const lightBColorObj = new THREE.Color(cfg0.lightBColor);
|
|
503
|
+
const rimColorObj = new THREE.Color(cfg0.rimColor ?? cfg0.lightBColor);
|
|
504
|
+
const material = new THREE.ShaderMaterial({
|
|
505
|
+
uniforms: {
|
|
506
|
+
uLightAColor: { value: lightAColorObj },
|
|
507
|
+
uLightAPosition: { value: lightAPos },
|
|
508
|
+
uLightAIntensity: { value: cfg0.lightAIntensity },
|
|
509
|
+
uLightBColor: { value: lightBColorObj },
|
|
510
|
+
uLightBPosition: { value: lightBPos },
|
|
511
|
+
uLightBIntensity: { value: cfg0.lightBIntensity },
|
|
512
|
+
uSubdivision: {
|
|
513
|
+
value: new THREE.Vector2(initSubdivisions, initSubdivisions)
|
|
514
|
+
},
|
|
515
|
+
uOffset: { value: new THREE.Vector3() },
|
|
516
|
+
uDistortionFrequency: { value: cfg0.distortionFrequency },
|
|
517
|
+
uDistortionStrength: { value: cfg0.high.idle },
|
|
518
|
+
uDisplacementFrequency: { value: cfg0.displacementFrequency },
|
|
519
|
+
uDisplacementStrength: { value: cfg0.volume.idle },
|
|
520
|
+
uFresnelOffset: { value: cfg0.fresnelOffset },
|
|
521
|
+
uFresnelMultiplier: { value: cfg0.medium.idle },
|
|
522
|
+
uFresnelPower: { value: cfg0.fresnelPower },
|
|
523
|
+
uRimColor: { value: rimColorObj },
|
|
524
|
+
uRimThreshold: { value: cfg0.rimThreshold },
|
|
525
|
+
uRimPower: { value: cfg0.rimPower },
|
|
526
|
+
uTime: { value: 0 }
|
|
527
|
+
},
|
|
528
|
+
defines: { USE_TANGENT: "" },
|
|
529
|
+
vertexShader: ORGANIC_SPHERE_VERTEX_SHADER,
|
|
530
|
+
fragmentShader: ORGANIC_SPHERE_FRAGMENT_SHADER
|
|
531
|
+
});
|
|
532
|
+
const mesh = new THREE.Mesh(geometry, material);
|
|
533
|
+
scene.add(mesh);
|
|
534
|
+
const seedInitialPhase = (seed) => ({
|
|
535
|
+
phi: seededUnit(seed, 1) * Math.PI,
|
|
536
|
+
theta: seededUnit(seed, 2) * Math.PI * 2
|
|
537
|
+
});
|
|
538
|
+
const initialPhase = seedInitialPhase(cfg0.seed);
|
|
539
|
+
const offsetSpherical = new THREE.Spherical(1, initialPhase.phi, initialPhase.theta);
|
|
540
|
+
const offsetDirection = new THREE.Vector3().setFromSpherical(offsetSpherical);
|
|
541
|
+
const variations = {
|
|
542
|
+
volume: { target: 0, current: cfg0.volume.idle },
|
|
543
|
+
medium: { target: 0, current: cfg0.medium.idle },
|
|
544
|
+
high: { target: 0, current: cfg0.high.idle },
|
|
545
|
+
low: { target: 0, current: cfg0.low.idle }
|
|
546
|
+
};
|
|
547
|
+
const resetAnimationState = (cfg) => {
|
|
548
|
+
material.uniforms.uTime.value = 0;
|
|
549
|
+
material.uniforms.uOffset.value.set(0, 0, 0);
|
|
550
|
+
const phase = seedInitialPhase(cfg.seed);
|
|
551
|
+
offsetSpherical.radius = 1;
|
|
552
|
+
offsetSpherical.phi = phase.phi;
|
|
553
|
+
offsetSpherical.theta = phase.theta;
|
|
554
|
+
variations.volume.current = cfg.volume.idle;
|
|
555
|
+
variations.medium.current = cfg.medium.idle;
|
|
556
|
+
variations.high.current = cfg.high.idle;
|
|
557
|
+
variations.low.current = cfg.low.idle;
|
|
558
|
+
variations.volume.target = cfg.volume.idle;
|
|
559
|
+
variations.medium.target = cfg.medium.idle;
|
|
560
|
+
variations.high.target = cfg.high.idle;
|
|
561
|
+
variations.low.target = cfg.low.idle;
|
|
562
|
+
};
|
|
563
|
+
const LEVEL_COUNT = 8;
|
|
564
|
+
function computeLevels(buf) {
|
|
565
|
+
const bins = Math.floor(buf.length / LEVEL_COUNT);
|
|
566
|
+
const levels = new Array(LEVEL_COUNT).fill(0);
|
|
567
|
+
if (bins <= 0) return levels;
|
|
568
|
+
for (let i = 0; i < LEVEL_COUNT; i++) {
|
|
569
|
+
let sum = 0;
|
|
570
|
+
for (let j = 0; j < bins; j++) sum += buf[i * bins + j];
|
|
571
|
+
levels[i] = sum / bins / 256;
|
|
572
|
+
}
|
|
573
|
+
return levels;
|
|
574
|
+
}
|
|
575
|
+
function bandTarget(reactivity, levels, sourceLevel) {
|
|
576
|
+
if (!levels) return reactivity.idle;
|
|
577
|
+
return Math.max(
|
|
578
|
+
reactivity.floor,
|
|
579
|
+
reactivity.floor + sourceLevel * reactivity.gain
|
|
580
|
+
);
|
|
581
|
+
}
|
|
582
|
+
let rafId = 0;
|
|
583
|
+
let lastTs = Date.now();
|
|
584
|
+
let disposed = false;
|
|
585
|
+
let wasActive = activeRef.current;
|
|
586
|
+
let lastSeed = cfg0.seed;
|
|
587
|
+
const render = () => {
|
|
588
|
+
if (disposed) return;
|
|
589
|
+
const now = Date.now();
|
|
590
|
+
const delta = Math.min(now - lastTs, 100);
|
|
591
|
+
lastTs = now;
|
|
592
|
+
const cfg = configRef.current;
|
|
593
|
+
lightAColorObj.set(cfg.lightAColor);
|
|
594
|
+
lightBColorObj.set(cfg.lightBColor);
|
|
595
|
+
rimColorObj.set(cfg.rimColor ?? cfg.lightBColor);
|
|
596
|
+
material.uniforms.uLightAIntensity.value = cfg.lightAIntensity;
|
|
597
|
+
material.uniforms.uLightBIntensity.value = cfg.lightBIntensity;
|
|
598
|
+
material.uniforms.uDistortionFrequency.value = cfg.distortionFrequency;
|
|
599
|
+
material.uniforms.uDisplacementFrequency.value = cfg.displacementFrequency;
|
|
600
|
+
material.uniforms.uFresnelOffset.value = cfg.fresnelOffset;
|
|
601
|
+
material.uniforms.uFresnelPower.value = cfg.fresnelPower;
|
|
602
|
+
material.uniforms.uRimThreshold.value = cfg.rimThreshold;
|
|
603
|
+
material.uniforms.uRimPower.value = cfg.rimPower;
|
|
604
|
+
if (camera.position.z !== cfg.cameraZ) camera.position.z = cfg.cameraZ;
|
|
605
|
+
if (camera.fov !== cfg.fov) {
|
|
606
|
+
camera.fov = cfg.fov;
|
|
607
|
+
camera.updateProjectionMatrix();
|
|
608
|
+
}
|
|
609
|
+
const isActive = activeRef.current;
|
|
610
|
+
if (isActive && !wasActive || cfg.seed !== lastSeed) {
|
|
611
|
+
resetAnimationState(cfg);
|
|
612
|
+
lastSeed = cfg.seed;
|
|
613
|
+
}
|
|
614
|
+
wasActive = isActive;
|
|
615
|
+
if (isActive) {
|
|
616
|
+
let levels = null;
|
|
617
|
+
if (getFrequencyDataRef.current) {
|
|
618
|
+
try {
|
|
619
|
+
const buf = getFrequencyDataRef.current();
|
|
620
|
+
if (buf && buf.length > 0) levels = computeLevels(buf);
|
|
621
|
+
} catch {
|
|
622
|
+
levels = null;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
const l0 = levels ? levels[0] || 0 : 0;
|
|
626
|
+
const l1 = levels ? levels[1] || 0 : 0;
|
|
627
|
+
const l2 = levels ? levels[2] || 0 : 0;
|
|
628
|
+
const loudest = Math.max(l0, l1, l2);
|
|
629
|
+
const update = (variation, reactivity, sourceLevel) => {
|
|
630
|
+
variation.target = bandTarget(reactivity, levels, sourceLevel);
|
|
631
|
+
const easing = variation.target > variation.current ? reactivity.attack : reactivity.decay;
|
|
632
|
+
variation.current += (variation.target - variation.current) * easing * delta;
|
|
633
|
+
};
|
|
634
|
+
update(variations.volume, cfg.volume, loudest);
|
|
635
|
+
update(variations.medium, cfg.medium, l1);
|
|
636
|
+
update(variations.high, cfg.high, l2);
|
|
637
|
+
update(variations.low, cfg.low, l0);
|
|
638
|
+
const timeFrequency = variations.low.current;
|
|
639
|
+
const elapsed = delta * timeFrequency;
|
|
640
|
+
material.uniforms.uDisplacementStrength.value = variations.volume.current;
|
|
641
|
+
material.uniforms.uDistortionStrength.value = variations.high.current;
|
|
642
|
+
material.uniforms.uFresnelMultiplier.value = variations.medium.current;
|
|
643
|
+
const offsetTime = (material.uniforms.uTime.value + elapsed) * 0.3;
|
|
644
|
+
offsetSpherical.phi = (Math.sin(offsetTime * 1e-3) * Math.sin(offsetTime * 321e-5) * 0.5 + 0.5) * Math.PI;
|
|
645
|
+
offsetSpherical.theta = (Math.sin(offsetTime * 1e-4) * Math.sin(offsetTime * 321e-6) * 0.5 + 0.5) * Math.PI * 2;
|
|
646
|
+
offsetDirection.setFromSpherical(offsetSpherical);
|
|
647
|
+
offsetDirection.multiplyScalar(timeFrequency * 2);
|
|
648
|
+
material.uniforms.uOffset.value.add(offsetDirection);
|
|
649
|
+
material.uniforms.uTime.value += elapsed;
|
|
650
|
+
}
|
|
651
|
+
renderer.render(scene, camera);
|
|
652
|
+
gl.endFrameEXP();
|
|
653
|
+
rafId = requestAnimationFrame(render);
|
|
654
|
+
};
|
|
655
|
+
rafId = requestAnimationFrame(render);
|
|
656
|
+
const dispose = () => {
|
|
657
|
+
if (disposed) return;
|
|
658
|
+
disposed = true;
|
|
659
|
+
cancelAnimationFrame(rafId);
|
|
660
|
+
geometry.dispose();
|
|
661
|
+
material.dispose();
|
|
662
|
+
renderer.dispose();
|
|
663
|
+
};
|
|
664
|
+
disposeRef.current = dispose;
|
|
665
|
+
},
|
|
666
|
+
[initSubdivisions, initMaxPixelRatio, initBackgroundColor]
|
|
667
|
+
);
|
|
668
|
+
return /* @__PURE__ */ jsx2(
|
|
669
|
+
GLView,
|
|
670
|
+
{
|
|
671
|
+
style,
|
|
672
|
+
onContextCreate
|
|
673
|
+
},
|
|
674
|
+
`${initSubdivisions}-${initMaxPixelRatio ?? "auto"}-${initBackgroundColor ?? "transparent"}`
|
|
675
|
+
);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// src/native/OrganicParticles.tsx
|
|
679
|
+
import { useEffect as useEffect3, useMemo as useMemo3, useRef as useRef4 } from "react";
|
|
680
|
+
import { PixelRatio as PixelRatio2 } from "react-native";
|
|
681
|
+
import { GLView as GLView2 } from "expo-gl";
|
|
682
|
+
import { Renderer as Renderer2 } from "expo-three";
|
|
683
|
+
import * as THREE2 from "three";
|
|
684
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
685
|
+
function seededUnit2(seed, salt) {
|
|
686
|
+
const s = Math.sin((seed + salt * 7919) * 12.9898 + salt * 78.233) * 43758.5453;
|
|
687
|
+
return s - Math.floor(s);
|
|
688
|
+
}
|
|
689
|
+
function buildParticleAttributes(count, innerRadius, outerRadius, seed) {
|
|
690
|
+
const positions = new Float32Array(count * 3);
|
|
691
|
+
const seeds = new Float32Array(count);
|
|
692
|
+
for (let i = 0; i < count; i++) {
|
|
693
|
+
const u = seededUnit2(seed, i * 4 + 1);
|
|
694
|
+
const v = seededUnit2(seed, i * 4 + 2);
|
|
695
|
+
const w = seededUnit2(seed, i * 4 + 3);
|
|
696
|
+
const s = seededUnit2(seed, i * 4 + 4);
|
|
697
|
+
const theta = v * Math.PI * 2;
|
|
698
|
+
const phi = Math.acos(2 * w - 1);
|
|
699
|
+
const r = innerRadius + u * Math.max(0, outerRadius - innerRadius);
|
|
700
|
+
const sinPhi = Math.sin(phi);
|
|
701
|
+
positions[i * 3 + 0] = r * sinPhi * Math.cos(theta);
|
|
702
|
+
positions[i * 3 + 1] = r * sinPhi * Math.sin(theta);
|
|
703
|
+
positions[i * 3 + 2] = r * Math.cos(phi);
|
|
704
|
+
seeds[i] = s;
|
|
705
|
+
}
|
|
706
|
+
return { positions, seeds };
|
|
707
|
+
}
|
|
708
|
+
function OrganicParticles({
|
|
709
|
+
getFrequencyData,
|
|
710
|
+
active = true,
|
|
711
|
+
config: configProp,
|
|
712
|
+
style
|
|
713
|
+
}) {
|
|
714
|
+
const resolvedConfig = useMemo3(
|
|
715
|
+
() => resolveOrganicParticlesConfig(configProp),
|
|
716
|
+
[configProp]
|
|
717
|
+
);
|
|
718
|
+
const configRef = useRef4(resolvedConfig);
|
|
719
|
+
useEffect3(() => {
|
|
720
|
+
configRef.current = resolvedConfig;
|
|
721
|
+
}, [resolvedConfig]);
|
|
722
|
+
const getFrequencyDataRef = useRef4(getFrequencyData);
|
|
723
|
+
const activeRef = useRef4(active);
|
|
724
|
+
useEffect3(() => {
|
|
725
|
+
getFrequencyDataRef.current = getFrequencyData;
|
|
726
|
+
}, [getFrequencyData]);
|
|
727
|
+
useEffect3(() => {
|
|
728
|
+
activeRef.current = active;
|
|
729
|
+
}, [active]);
|
|
730
|
+
const disposeRef = useRef4(null);
|
|
731
|
+
useEffect3(() => {
|
|
732
|
+
return () => {
|
|
733
|
+
disposeRef.current?.();
|
|
734
|
+
disposeRef.current = null;
|
|
735
|
+
};
|
|
736
|
+
}, []);
|
|
737
|
+
const initCount = resolvedConfig.count;
|
|
738
|
+
const initInnerRadius = resolvedConfig.innerRadius;
|
|
739
|
+
const initOuterRadius = resolvedConfig.outerRadius;
|
|
740
|
+
const initSeed = resolvedConfig.seed;
|
|
741
|
+
const initMaxPixelRatio = resolvedConfig.maxPixelRatio;
|
|
742
|
+
const initBackgroundColor = resolvedConfig.backgroundColor;
|
|
743
|
+
const onContextCreate = useMemo3(
|
|
744
|
+
() => async (gl) => {
|
|
745
|
+
const cfg0 = configRef.current;
|
|
746
|
+
const width = gl.drawingBufferWidth;
|
|
747
|
+
const height = gl.drawingBufferHeight;
|
|
748
|
+
const pixelRatio = Math.min(initMaxPixelRatio ?? PixelRatio2.get(), 2);
|
|
749
|
+
const scene = new THREE2.Scene();
|
|
750
|
+
if (initBackgroundColor) scene.background = new THREE2.Color(initBackgroundColor);
|
|
751
|
+
const camera = new THREE2.PerspectiveCamera(
|
|
752
|
+
cfg0.fov,
|
|
753
|
+
width / height || 1,
|
|
754
|
+
0.1,
|
|
755
|
+
100
|
|
756
|
+
);
|
|
757
|
+
camera.position.set(0, 0, cfg0.cameraZ);
|
|
758
|
+
scene.add(camera);
|
|
759
|
+
const renderer = new Renderer2({
|
|
760
|
+
gl,
|
|
761
|
+
antialias: true,
|
|
762
|
+
alpha: initBackgroundColor === null,
|
|
763
|
+
powerPreference: "high-performance"
|
|
764
|
+
});
|
|
765
|
+
renderer.setSize(width / pixelRatio, height / pixelRatio, false);
|
|
766
|
+
renderer.setPixelRatio(pixelRatio);
|
|
767
|
+
renderer.setClearColor(0, initBackgroundColor === null ? 0 : 1);
|
|
768
|
+
const { positions, seeds } = buildParticleAttributes(
|
|
769
|
+
initCount,
|
|
770
|
+
initInnerRadius,
|
|
771
|
+
initOuterRadius,
|
|
772
|
+
initSeed
|
|
773
|
+
);
|
|
774
|
+
const geometry = new THREE2.BufferGeometry();
|
|
775
|
+
geometry.setAttribute("aBasePosition", new THREE2.BufferAttribute(positions, 3));
|
|
776
|
+
geometry.setAttribute("aSeed", new THREE2.BufferAttribute(seeds, 1));
|
|
777
|
+
geometry.setAttribute("position", new THREE2.BufferAttribute(positions, 3));
|
|
778
|
+
const colorCoolObj = new THREE2.Color(cfg0.colorCool);
|
|
779
|
+
const colorHotObj = new THREE2.Color(cfg0.colorHot);
|
|
780
|
+
const material = new THREE2.ShaderMaterial({
|
|
781
|
+
uniforms: {
|
|
782
|
+
uTime: { value: 0 },
|
|
783
|
+
uNoiseFrequency: { value: cfg0.noiseFrequency },
|
|
784
|
+
uNoiseStrength: { value: cfg0.noiseStrength },
|
|
785
|
+
uDisplacement: { value: cfg0.volume.idle },
|
|
786
|
+
uSwirl: { value: cfg0.high.idle },
|
|
787
|
+
uSwirlTimeRate: { value: cfg0.swirlTimeRate },
|
|
788
|
+
uSizeBase: { value: cfg0.sizeBase },
|
|
789
|
+
uSizeAudio: { value: cfg0.medium.idle },
|
|
790
|
+
uPixelRatio: { value: pixelRatio },
|
|
791
|
+
uColorCool: { value: colorCoolObj },
|
|
792
|
+
uColorHot: { value: colorHotObj },
|
|
793
|
+
uOpacity: { value: cfg0.opacity },
|
|
794
|
+
uSoftness: { value: cfg0.softness }
|
|
795
|
+
},
|
|
796
|
+
vertexShader: ORGANIC_PARTICLES_VERTEX_SHADER,
|
|
797
|
+
fragmentShader: ORGANIC_PARTICLES_FRAGMENT_SHADER,
|
|
798
|
+
transparent: true,
|
|
799
|
+
depthWrite: false,
|
|
800
|
+
blending: THREE2.AdditiveBlending
|
|
801
|
+
});
|
|
802
|
+
const points = new THREE2.Points(geometry, material);
|
|
803
|
+
scene.add(points);
|
|
804
|
+
const variations = {
|
|
805
|
+
volume: { target: 0, current: cfg0.volume.idle },
|
|
806
|
+
medium: { target: 0, current: cfg0.medium.idle },
|
|
807
|
+
high: { target: 0, current: cfg0.high.idle },
|
|
808
|
+
low: { target: 0, current: cfg0.low.idle }
|
|
809
|
+
};
|
|
810
|
+
const LEVEL_COUNT = 8;
|
|
811
|
+
function computeLevels(buf) {
|
|
812
|
+
const bins = Math.floor(buf.length / LEVEL_COUNT);
|
|
813
|
+
const levels = new Array(LEVEL_COUNT).fill(0);
|
|
814
|
+
if (bins <= 0) return levels;
|
|
815
|
+
for (let i = 0; i < LEVEL_COUNT; i++) {
|
|
816
|
+
let sum = 0;
|
|
817
|
+
for (let j = 0; j < bins; j++) sum += buf[i * bins + j];
|
|
818
|
+
levels[i] = sum / bins / 256;
|
|
819
|
+
}
|
|
820
|
+
return levels;
|
|
821
|
+
}
|
|
822
|
+
function bandTarget(reactivity, levels, sourceLevel) {
|
|
823
|
+
if (!levels) return reactivity.idle;
|
|
824
|
+
return Math.max(
|
|
825
|
+
reactivity.floor,
|
|
826
|
+
reactivity.floor + sourceLevel * reactivity.gain
|
|
827
|
+
);
|
|
828
|
+
}
|
|
829
|
+
const rotationState = { y: 0 };
|
|
830
|
+
const resetAnimationState = (cfg) => {
|
|
831
|
+
material.uniforms.uTime.value = 0;
|
|
832
|
+
rotationState.y = 0;
|
|
833
|
+
points.rotation.set(0, 0, 0);
|
|
834
|
+
variations.volume.current = cfg.volume.idle;
|
|
835
|
+
variations.medium.current = cfg.medium.idle;
|
|
836
|
+
variations.high.current = cfg.high.idle;
|
|
837
|
+
variations.low.current = cfg.low.idle;
|
|
838
|
+
variations.volume.target = cfg.volume.idle;
|
|
839
|
+
variations.medium.target = cfg.medium.idle;
|
|
840
|
+
variations.high.target = cfg.high.idle;
|
|
841
|
+
variations.low.target = cfg.low.idle;
|
|
842
|
+
};
|
|
843
|
+
let rafId = 0;
|
|
844
|
+
let lastTs = Date.now();
|
|
845
|
+
let disposed = false;
|
|
846
|
+
let wasActive = activeRef.current;
|
|
847
|
+
const render = () => {
|
|
848
|
+
if (disposed) return;
|
|
849
|
+
const now = Date.now();
|
|
850
|
+
const delta = Math.min(now - lastTs, 100);
|
|
851
|
+
lastTs = now;
|
|
852
|
+
const cfg = configRef.current;
|
|
853
|
+
colorCoolObj.set(cfg.colorCool);
|
|
854
|
+
colorHotObj.set(cfg.colorHot);
|
|
855
|
+
material.uniforms.uNoiseFrequency.value = cfg.noiseFrequency;
|
|
856
|
+
material.uniforms.uNoiseStrength.value = cfg.noiseStrength;
|
|
857
|
+
material.uniforms.uSwirlTimeRate.value = cfg.swirlTimeRate;
|
|
858
|
+
material.uniforms.uSizeBase.value = cfg.sizeBase;
|
|
859
|
+
material.uniforms.uOpacity.value = cfg.opacity;
|
|
860
|
+
material.uniforms.uSoftness.value = cfg.softness;
|
|
861
|
+
if (camera.position.z !== cfg.cameraZ) camera.position.z = cfg.cameraZ;
|
|
862
|
+
if (camera.fov !== cfg.fov) {
|
|
863
|
+
camera.fov = cfg.fov;
|
|
864
|
+
camera.updateProjectionMatrix();
|
|
865
|
+
}
|
|
866
|
+
const isActive = activeRef.current;
|
|
867
|
+
if (isActive && !wasActive) {
|
|
868
|
+
resetAnimationState(cfg);
|
|
869
|
+
}
|
|
870
|
+
wasActive = isActive;
|
|
871
|
+
if (isActive) {
|
|
872
|
+
let levels = null;
|
|
873
|
+
if (getFrequencyDataRef.current) {
|
|
874
|
+
try {
|
|
875
|
+
const buf = getFrequencyDataRef.current();
|
|
876
|
+
if (buf && buf.length > 0) levels = computeLevels(buf);
|
|
877
|
+
} catch {
|
|
878
|
+
levels = null;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
const l0 = levels ? levels[0] || 0 : 0;
|
|
882
|
+
const l1 = levels ? levels[1] || 0 : 0;
|
|
883
|
+
const l2 = levels ? levels[2] || 0 : 0;
|
|
884
|
+
const loudest = Math.max(l0, l1, l2);
|
|
885
|
+
const update = (variation, reactivity, sourceLevel) => {
|
|
886
|
+
variation.target = bandTarget(reactivity, levels, sourceLevel);
|
|
887
|
+
const easing = variation.target > variation.current ? reactivity.attack : reactivity.decay;
|
|
888
|
+
variation.current += (variation.target - variation.current) * easing * delta;
|
|
889
|
+
};
|
|
890
|
+
update(variations.volume, cfg.volume, loudest);
|
|
891
|
+
update(variations.medium, cfg.medium, l1);
|
|
892
|
+
update(variations.high, cfg.high, l2);
|
|
893
|
+
update(variations.low, cfg.low, l0);
|
|
894
|
+
const timeFrequency = variations.low.current;
|
|
895
|
+
const elapsed = delta * timeFrequency;
|
|
896
|
+
material.uniforms.uDisplacement.value = variations.volume.current;
|
|
897
|
+
material.uniforms.uSizeAudio.value = variations.medium.current;
|
|
898
|
+
material.uniforms.uSwirl.value = variations.high.current;
|
|
899
|
+
material.uniforms.uTime.value += elapsed;
|
|
900
|
+
rotationState.y += delta * cfg.rotationBias + variations.high.current * delta * 8e-4;
|
|
901
|
+
points.rotation.y = rotationState.y;
|
|
902
|
+
} else {
|
|
903
|
+
material.uniforms.uTime.value += delta * cfg.idleNoiseRate;
|
|
904
|
+
}
|
|
905
|
+
renderer.render(scene, camera);
|
|
906
|
+
gl.endFrameEXP();
|
|
907
|
+
rafId = requestAnimationFrame(render);
|
|
908
|
+
};
|
|
909
|
+
rafId = requestAnimationFrame(render);
|
|
910
|
+
const dispose = () => {
|
|
911
|
+
if (disposed) return;
|
|
912
|
+
disposed = true;
|
|
913
|
+
cancelAnimationFrame(rafId);
|
|
914
|
+
geometry.dispose();
|
|
915
|
+
material.dispose();
|
|
916
|
+
renderer.dispose();
|
|
917
|
+
};
|
|
918
|
+
disposeRef.current = dispose;
|
|
919
|
+
},
|
|
920
|
+
[initCount, initInnerRadius, initOuterRadius, initSeed, initMaxPixelRatio, initBackgroundColor]
|
|
921
|
+
);
|
|
922
|
+
return /* @__PURE__ */ jsx3(
|
|
923
|
+
GLView2,
|
|
924
|
+
{
|
|
925
|
+
style,
|
|
926
|
+
onContextCreate
|
|
927
|
+
},
|
|
928
|
+
`${initCount}-${initInnerRadius}-${initOuterRadius}-${initSeed}-${initMaxPixelRatio ?? "auto"}-${initBackgroundColor ?? "transparent"}`
|
|
929
|
+
);
|
|
930
|
+
}
|
|
931
|
+
export {
|
|
932
|
+
DEFAULT_ORGANIC_PARTICLES_CONFIG,
|
|
933
|
+
DEFAULT_ORGANIC_SPHERE_CONFIG,
|
|
934
|
+
ORGANIC_PARTICLES_FRAGMENT_SHADER,
|
|
935
|
+
ORGANIC_PARTICLES_VERTEX_SHADER,
|
|
936
|
+
ORGANIC_SPHERE_FRAGMENT_SHADER,
|
|
937
|
+
ORGANIC_SPHERE_VERTEX_SHADER,
|
|
938
|
+
OrganicParticles,
|
|
939
|
+
OrganicSphere,
|
|
940
|
+
PERLIN_4D,
|
|
941
|
+
ShogoVoiceProvider,
|
|
942
|
+
resolveOrganicParticlesConfig,
|
|
943
|
+
resolveOrganicSphereConfig,
|
|
944
|
+
useChatConversation,
|
|
945
|
+
useShogoChat,
|
|
946
|
+
useShogoVoice,
|
|
947
|
+
useVoiceConversation
|
|
948
|
+
};
|
|
949
|
+
//# sourceMappingURL=index.js.map
|