@tanstack/ai-elevenlabs 0.1.3 → 0.1.5
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.
|
@@ -27,18 +27,14 @@ async function createElevenLabsConnection(token, _options, clientToolDefs) {
|
|
|
27
27
|
const elevenLabsClientTools = {};
|
|
28
28
|
if (clientToolDefs) {
|
|
29
29
|
for (const tool of clientToolDefs) {
|
|
30
|
-
elevenLabsClientTools[tool.name] = {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
});
|
|
39
|
-
},
|
|
40
|
-
description: tool.description,
|
|
41
|
-
parameters: tool.inputSchema ? tool.inputSchema : { type: "object", properties: {} }
|
|
30
|
+
elevenLabsClientTools[tool.name] = async (params) => {
|
|
31
|
+
if (tool.execute) {
|
|
32
|
+
const result = await tool.execute(params);
|
|
33
|
+
return typeof result === "string" ? result : JSON.stringify(result);
|
|
34
|
+
}
|
|
35
|
+
return JSON.stringify({
|
|
36
|
+
error: `No execute function for tool ${tool.name}`
|
|
37
|
+
});
|
|
42
38
|
};
|
|
43
39
|
}
|
|
44
40
|
}
|
|
@@ -58,18 +54,21 @@ async function createElevenLabsConnection(token, _options, clientToolDefs) {
|
|
|
58
54
|
},
|
|
59
55
|
onMessage: ({ message, source }) => {
|
|
60
56
|
const role = source === "user" ? "user" : "assistant";
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
57
|
+
if (role === "user") {
|
|
58
|
+
emit("transcript", {
|
|
59
|
+
role,
|
|
60
|
+
transcript: message,
|
|
61
|
+
isFinal: true
|
|
62
|
+
});
|
|
63
|
+
} else {
|
|
64
|
+
const realtimeMessage = {
|
|
65
|
+
id: generateMessageId(),
|
|
66
|
+
role,
|
|
67
|
+
timestamp: Date.now(),
|
|
68
|
+
parts: [{ type: "audio", transcript: message }]
|
|
69
|
+
};
|
|
70
|
+
emit("message_complete", { message: realtimeMessage });
|
|
71
|
+
}
|
|
73
72
|
},
|
|
74
73
|
onError: (error) => {
|
|
75
74
|
emit("error", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter.js","sources":["../../../src/realtime/adapter.ts"],"sourcesContent":["import { Conversation } from '@11labs/client'\nimport type {\n AnyClientTool,\n AudioVisualization,\n RealtimeEvent,\n RealtimeEventHandler,\n RealtimeMessage,\n RealtimeMode,\n RealtimeSessionConfig,\n RealtimeStatus,\n RealtimeToken,\n} from '@tanstack/ai'\nimport type { RealtimeAdapter, RealtimeConnection } from '@tanstack/ai-client'\nimport type { ElevenLabsRealtimeOptions } from './types'\n\n/**\n * Creates an ElevenLabs realtime adapter for client-side use.\n *\n * Wraps the @11labs/client SDK for voice conversations.\n *\n * @param options - Optional configuration\n * @returns A RealtimeAdapter for use with RealtimeClient\n *\n * @example\n * ```typescript\n * import { RealtimeClient } from '@tanstack/ai-client'\n * import { elevenlabsRealtime } from '@tanstack/ai-elevenlabs'\n *\n * const client = new RealtimeClient({\n * getToken: () => fetch('/api/realtime-token').then(r => r.json()),\n * adapter: elevenlabsRealtime(),\n * })\n * ```\n */\nexport function elevenlabsRealtime(\n options: ElevenLabsRealtimeOptions = {},\n): RealtimeAdapter {\n return {\n provider: 'elevenlabs',\n\n async connect(\n token: RealtimeToken,\n clientToolDefs?: ReadonlyArray<AnyClientTool>,\n ): Promise<RealtimeConnection> {\n return createElevenLabsConnection(token, options, clientToolDefs)\n },\n }\n}\n\n/**\n * Creates a connection to ElevenLabs conversational AI\n */\nasync function createElevenLabsConnection(\n token: RealtimeToken,\n _options: ElevenLabsRealtimeOptions,\n clientToolDefs?: ReadonlyArray<AnyClientTool>,\n): Promise<RealtimeConnection> {\n const eventHandlers = new Map<RealtimeEvent, Set<RealtimeEventHandler<any>>>()\n let conversation: Awaited<\n ReturnType<typeof Conversation.startSession>\n > | null = null\n let messageIdCounter = 0\n\n // Empty arrays for when visualization isn't available\n const emptyFrequencyData = new Uint8Array(128)\n const emptyTimeDomainData = new Uint8Array(128).fill(128)\n\n // Helper to emit events\n function emit<TEvent extends RealtimeEvent>(\n event: TEvent,\n payload: Parameters<RealtimeEventHandler<TEvent>>[0],\n ) {\n const handlers = eventHandlers.get(event)\n if (handlers) {\n for (const handler of handlers) {\n handler(payload)\n }\n }\n }\n\n function generateMessageId(): string {\n return `el-msg-${Date.now()}-${++messageIdCounter}`\n }\n\n // Convert TanStack tool definitions to ElevenLabs clientTools format\n const elevenLabsClientTools: Record<\n string,\n {\n handler: (params: unknown) => Promise<string>\n description: string\n parameters: Record<string, unknown>\n }\n > = {}\n\n if (clientToolDefs) {\n for (const tool of clientToolDefs) {\n elevenLabsClientTools[tool.name] = {\n handler: async (params: unknown) => {\n if (tool.execute) {\n const result = await tool.execute(params)\n return typeof result === 'string' ? result : JSON.stringify(result)\n }\n return JSON.stringify({\n error: `No execute function for tool ${tool.name}`,\n })\n },\n description: tool.description,\n parameters: tool.inputSchema\n ? (tool.inputSchema as Record<string, unknown>)\n : { type: 'object', properties: {} },\n }\n }\n }\n\n // Build session options\n const sessionOptions: Record<string, unknown> = {\n signedUrl: token.token,\n\n onConnect: () => {\n emit('status_change', { status: 'connected' as RealtimeStatus })\n emit('mode_change', { mode: 'listening' })\n },\n\n onDisconnect: () => {\n emit('status_change', { status: 'idle' as RealtimeStatus })\n emit('mode_change', { mode: 'idle' })\n },\n\n onModeChange: ({ mode }: { mode: string }) => {\n const mappedMode: RealtimeMode =\n mode === 'speaking' ? 'speaking' : 'listening'\n emit('mode_change', { mode: mappedMode })\n },\n\n onMessage: ({ message, source }: { message: string; source: string }) => {\n const role = source === 'user' ? 'user' : 'assistant'\n\n // Emit transcript update\n emit('transcript', {\n role,\n transcript: message,\n isFinal: true,\n })\n\n // Create and emit message\n const realtimeMessage: RealtimeMessage = {\n id: generateMessageId(),\n role,\n timestamp: Date.now(),\n parts: [{ type: 'audio', transcript: message }],\n }\n emit('message_complete', { message: realtimeMessage })\n },\n\n onError: (error: string | Error) => {\n emit('error', {\n error: new Error(\n typeof error === 'string' ? error : error.message || 'Unknown error',\n ),\n })\n },\n }\n\n // Only add clientTools if we have any\n if (Object.keys(elevenLabsClientTools).length > 0) {\n sessionOptions.clientTools = elevenLabsClientTools\n }\n\n // Start the conversation session\n conversation = await Conversation.startSession(\n sessionOptions as Parameters<typeof Conversation.startSession>[0],\n )\n\n // Connection implementation\n const connection: RealtimeConnection = {\n async disconnect() {\n if (conversation) {\n await conversation.endSession()\n conversation = null\n }\n emit('status_change', { status: 'idle' as RealtimeStatus })\n },\n\n async startAudioCapture() {\n // ElevenLabs SDK handles audio capture automatically\n // This is called when the session starts\n emit('mode_change', { mode: 'listening' })\n },\n\n stopAudioCapture() {\n // ElevenLabs SDK handles this\n emit('mode_change', { mode: 'idle' })\n },\n\n sendText(text: string) {\n if (!conversation) return\n conversation.sendUserMessage(text)\n },\n\n sendImage(_imageData: string, _mimeType: string) {\n // ElevenLabs does not support direct image input in the conversation API\n console.warn(\n 'ElevenLabs realtime does not support sending images directly.',\n )\n },\n\n sendToolResult(_callId: string, _result: string) {\n // ElevenLabs client tools are handled via the clientTools handlers\n // registered at session start — results are returned automatically\n },\n\n updateSession(_config: Partial<RealtimeSessionConfig>) {\n // ElevenLabs session config is set at creation time\n console.warn(\n 'ElevenLabs does not support runtime session updates. Configure at connection time.',\n )\n },\n\n interrupt() {\n // ElevenLabs handles interruption automatically via barge-in\n // No explicit API to call\n emit('mode_change', { mode: 'listening' })\n emit('interrupted', {})\n },\n\n on<TEvent extends RealtimeEvent>(\n event: TEvent,\n handler: RealtimeEventHandler<TEvent>,\n ): () => void {\n if (!eventHandlers.has(event)) {\n eventHandlers.set(event, new Set())\n }\n eventHandlers.get(event)!.add(handler)\n\n return () => {\n eventHandlers.get(event)?.delete(handler)\n }\n },\n\n getAudioVisualization(): AudioVisualization {\n return {\n get inputLevel() {\n if (!conversation) return 0\n try {\n return conversation.getInputVolume()\n } catch {\n return 0\n }\n },\n\n get outputLevel() {\n if (!conversation) return 0\n try {\n return conversation.getOutputVolume()\n } catch {\n return 0\n }\n },\n\n getInputFrequencyData() {\n if (!conversation) return emptyFrequencyData\n try {\n return conversation.getInputByteFrequencyData()\n } catch {\n return emptyFrequencyData\n }\n },\n\n getOutputFrequencyData() {\n if (!conversation) return emptyFrequencyData\n try {\n return conversation.getOutputByteFrequencyData()\n } catch {\n return emptyFrequencyData\n }\n },\n\n getInputTimeDomainData() {\n // ElevenLabs SDK doesn't expose time domain data\n return emptyTimeDomainData\n },\n\n getOutputTimeDomainData() {\n // ElevenLabs SDK doesn't expose time domain data\n return emptyTimeDomainData\n },\n\n get inputSampleRate() {\n return 16000\n },\n\n get outputSampleRate() {\n return 16000\n },\n }\n },\n }\n\n return connection\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"adapter.js","sources":["../../../src/realtime/adapter.ts"],"sourcesContent":["import { Conversation } from '@11labs/client'\nimport type {\n AnyClientTool,\n AudioVisualization,\n RealtimeEvent,\n RealtimeEventHandler,\n RealtimeMessage,\n RealtimeMode,\n RealtimeSessionConfig,\n RealtimeStatus,\n RealtimeToken,\n} from '@tanstack/ai'\nimport type { RealtimeAdapter, RealtimeConnection } from '@tanstack/ai-client'\nimport type { ElevenLabsRealtimeOptions } from './types'\n\n/**\n * Creates an ElevenLabs realtime adapter for client-side use.\n *\n * Wraps the @11labs/client SDK for voice conversations.\n *\n * @param options - Optional configuration\n * @returns A RealtimeAdapter for use with RealtimeClient\n *\n * @example\n * ```typescript\n * import { RealtimeClient } from '@tanstack/ai-client'\n * import { elevenlabsRealtime } from '@tanstack/ai-elevenlabs'\n *\n * const client = new RealtimeClient({\n * getToken: () => fetch('/api/realtime-token').then(r => r.json()),\n * adapter: elevenlabsRealtime(),\n * })\n * ```\n */\nexport function elevenlabsRealtime(\n options: ElevenLabsRealtimeOptions = {},\n): RealtimeAdapter {\n return {\n provider: 'elevenlabs',\n\n async connect(\n token: RealtimeToken,\n clientToolDefs?: ReadonlyArray<AnyClientTool>,\n ): Promise<RealtimeConnection> {\n return createElevenLabsConnection(token, options, clientToolDefs)\n },\n }\n}\n\n/**\n * Creates a connection to ElevenLabs conversational AI\n */\nasync function createElevenLabsConnection(\n token: RealtimeToken,\n _options: ElevenLabsRealtimeOptions,\n clientToolDefs?: ReadonlyArray<AnyClientTool>,\n): Promise<RealtimeConnection> {\n const eventHandlers = new Map<RealtimeEvent, Set<RealtimeEventHandler<any>>>()\n let conversation: Awaited<\n ReturnType<typeof Conversation.startSession>\n > | null = null\n let messageIdCounter = 0\n\n // Empty arrays for when visualization isn't available\n const emptyFrequencyData = new Uint8Array(128)\n const emptyTimeDomainData = new Uint8Array(128).fill(128)\n\n // Helper to emit events\n function emit<TEvent extends RealtimeEvent>(\n event: TEvent,\n payload: Parameters<RealtimeEventHandler<TEvent>>[0],\n ) {\n const handlers = eventHandlers.get(event)\n if (handlers) {\n for (const handler of handlers) {\n handler(payload)\n }\n }\n }\n\n function generateMessageId(): string {\n return `el-msg-${Date.now()}-${++messageIdCounter}`\n }\n\n // Convert TanStack tool definitions to ElevenLabs clientTools format.\n // @11labs/client@0.2.0 expects plain async functions, not objects.\n const elevenLabsClientTools: Record<\n string,\n (params: unknown) => Promise<string>\n > = {}\n\n if (clientToolDefs) {\n for (const tool of clientToolDefs) {\n elevenLabsClientTools[tool.name] = async (params: unknown) => {\n if (tool.execute) {\n const result = await tool.execute(params)\n return typeof result === 'string' ? result : JSON.stringify(result)\n }\n return JSON.stringify({\n error: `No execute function for tool ${tool.name}`,\n })\n }\n }\n }\n\n // Build session options\n const sessionOptions: Record<string, unknown> = {\n signedUrl: token.token,\n\n onConnect: () => {\n emit('status_change', { status: 'connected' as RealtimeStatus })\n emit('mode_change', { mode: 'listening' })\n },\n\n onDisconnect: () => {\n emit('status_change', { status: 'idle' as RealtimeStatus })\n emit('mode_change', { mode: 'idle' })\n },\n\n onModeChange: ({ mode }: { mode: string }) => {\n const mappedMode: RealtimeMode =\n mode === 'speaking' ? 'speaking' : 'listening'\n emit('mode_change', { mode: mappedMode })\n },\n\n onMessage: ({ message, source }: { message: string; source: string }) => {\n const role = source === 'user' ? 'user' : 'assistant'\n\n if (role === 'user') {\n // User transcripts: only emit transcript — the RealtimeClient\n // creates the user message when it receives a final transcript\n emit('transcript', {\n role,\n transcript: message,\n isFinal: true,\n })\n } else {\n // Assistant messages: only emit message_complete — the canonical\n // event for finalized assistant messages\n const realtimeMessage: RealtimeMessage = {\n id: generateMessageId(),\n role,\n timestamp: Date.now(),\n parts: [{ type: 'audio', transcript: message }],\n }\n emit('message_complete', { message: realtimeMessage })\n }\n },\n\n onError: (error: string | Error) => {\n emit('error', {\n error: new Error(\n typeof error === 'string' ? error : error.message || 'Unknown error',\n ),\n })\n },\n }\n\n // Only add clientTools if we have any\n if (Object.keys(elevenLabsClientTools).length > 0) {\n sessionOptions.clientTools = elevenLabsClientTools\n }\n\n // Start the conversation session\n conversation = await Conversation.startSession(\n sessionOptions as Parameters<typeof Conversation.startSession>[0],\n )\n\n // Connection implementation\n const connection: RealtimeConnection = {\n async disconnect() {\n if (conversation) {\n await conversation.endSession()\n conversation = null\n }\n emit('status_change', { status: 'idle' as RealtimeStatus })\n },\n\n async startAudioCapture() {\n // ElevenLabs SDK handles audio capture automatically\n // This is called when the session starts\n emit('mode_change', { mode: 'listening' })\n },\n\n stopAudioCapture() {\n // ElevenLabs SDK handles this\n emit('mode_change', { mode: 'idle' })\n },\n\n sendText(text: string) {\n if (!conversation) return\n conversation.sendUserMessage(text)\n },\n\n sendImage(_imageData: string, _mimeType: string) {\n // ElevenLabs does not support direct image input in the conversation API\n console.warn(\n 'ElevenLabs realtime does not support sending images directly.',\n )\n },\n\n sendToolResult(_callId: string, _result: string) {\n // ElevenLabs client tools are handled via the clientTools handlers\n // registered at session start — results are returned automatically\n },\n\n updateSession(_config: Partial<RealtimeSessionConfig>) {\n // ElevenLabs session config is set at creation time\n console.warn(\n 'ElevenLabs does not support runtime session updates. Configure at connection time.',\n )\n },\n\n interrupt() {\n // ElevenLabs handles interruption automatically via barge-in\n // No explicit API to call\n emit('mode_change', { mode: 'listening' })\n emit('interrupted', {})\n },\n\n on<TEvent extends RealtimeEvent>(\n event: TEvent,\n handler: RealtimeEventHandler<TEvent>,\n ): () => void {\n if (!eventHandlers.has(event)) {\n eventHandlers.set(event, new Set())\n }\n eventHandlers.get(event)!.add(handler)\n\n return () => {\n eventHandlers.get(event)?.delete(handler)\n }\n },\n\n getAudioVisualization(): AudioVisualization {\n return {\n get inputLevel() {\n if (!conversation) return 0\n try {\n return conversation.getInputVolume()\n } catch {\n return 0\n }\n },\n\n get outputLevel() {\n if (!conversation) return 0\n try {\n return conversation.getOutputVolume()\n } catch {\n return 0\n }\n },\n\n getInputFrequencyData() {\n if (!conversation) return emptyFrequencyData\n try {\n return conversation.getInputByteFrequencyData()\n } catch {\n return emptyFrequencyData\n }\n },\n\n getOutputFrequencyData() {\n if (!conversation) return emptyFrequencyData\n try {\n return conversation.getOutputByteFrequencyData()\n } catch {\n return emptyFrequencyData\n }\n },\n\n getInputTimeDomainData() {\n // ElevenLabs SDK doesn't expose time domain data\n return emptyTimeDomainData\n },\n\n getOutputTimeDomainData() {\n // ElevenLabs SDK doesn't expose time domain data\n return emptyTimeDomainData\n },\n\n get inputSampleRate() {\n return 16000\n },\n\n get outputSampleRate() {\n return 16000\n },\n }\n },\n }\n\n return connection\n}\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/ai-elevenlabs",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "ElevenLabs adapter for TanStack AI realtime voice",
|
|
5
5
|
"author": "",
|
|
6
6
|
"license": "MIT",
|
|
@@ -34,13 +34,13 @@
|
|
|
34
34
|
"@11labs/client": "^0.2.0"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
|
-
"@tanstack/ai": "^0.
|
|
38
|
-
"@tanstack/ai-client": "^0.7.
|
|
37
|
+
"@tanstack/ai": "^0.11.0",
|
|
38
|
+
"@tanstack/ai-client": "^0.7.11"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@vitest/coverage-v8": "4.0.14",
|
|
42
|
-
"@tanstack/ai": "0.
|
|
43
|
-
"@tanstack/ai-client": "0.7.
|
|
42
|
+
"@tanstack/ai": "0.11.0",
|
|
43
|
+
"@tanstack/ai-client": "0.7.11"
|
|
44
44
|
},
|
|
45
45
|
"scripts": {
|
|
46
46
|
"build": "vite build",
|
package/src/realtime/adapter.ts
CHANGED
|
@@ -82,32 +82,23 @@ async function createElevenLabsConnection(
|
|
|
82
82
|
return `el-msg-${Date.now()}-${++messageIdCounter}`
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
// Convert TanStack tool definitions to ElevenLabs clientTools format
|
|
85
|
+
// Convert TanStack tool definitions to ElevenLabs clientTools format.
|
|
86
|
+
// @11labs/client@0.2.0 expects plain async functions, not objects.
|
|
86
87
|
const elevenLabsClientTools: Record<
|
|
87
88
|
string,
|
|
88
|
-
|
|
89
|
-
handler: (params: unknown) => Promise<string>
|
|
90
|
-
description: string
|
|
91
|
-
parameters: Record<string, unknown>
|
|
92
|
-
}
|
|
89
|
+
(params: unknown) => Promise<string>
|
|
93
90
|
> = {}
|
|
94
91
|
|
|
95
92
|
if (clientToolDefs) {
|
|
96
93
|
for (const tool of clientToolDefs) {
|
|
97
|
-
elevenLabsClientTools[tool.name] = {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
})
|
|
106
|
-
},
|
|
107
|
-
description: tool.description,
|
|
108
|
-
parameters: tool.inputSchema
|
|
109
|
-
? (tool.inputSchema as Record<string, unknown>)
|
|
110
|
-
: { type: 'object', properties: {} },
|
|
94
|
+
elevenLabsClientTools[tool.name] = async (params: unknown) => {
|
|
95
|
+
if (tool.execute) {
|
|
96
|
+
const result = await tool.execute(params)
|
|
97
|
+
return typeof result === 'string' ? result : JSON.stringify(result)
|
|
98
|
+
}
|
|
99
|
+
return JSON.stringify({
|
|
100
|
+
error: `No execute function for tool ${tool.name}`,
|
|
101
|
+
})
|
|
111
102
|
}
|
|
112
103
|
}
|
|
113
104
|
}
|
|
@@ -135,21 +126,25 @@ async function createElevenLabsConnection(
|
|
|
135
126
|
onMessage: ({ message, source }: { message: string; source: string }) => {
|
|
136
127
|
const role = source === 'user' ? 'user' : 'assistant'
|
|
137
128
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
transcript
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
129
|
+
if (role === 'user') {
|
|
130
|
+
// User transcripts: only emit transcript — the RealtimeClient
|
|
131
|
+
// creates the user message when it receives a final transcript
|
|
132
|
+
emit('transcript', {
|
|
133
|
+
role,
|
|
134
|
+
transcript: message,
|
|
135
|
+
isFinal: true,
|
|
136
|
+
})
|
|
137
|
+
} else {
|
|
138
|
+
// Assistant messages: only emit message_complete — the canonical
|
|
139
|
+
// event for finalized assistant messages
|
|
140
|
+
const realtimeMessage: RealtimeMessage = {
|
|
141
|
+
id: generateMessageId(),
|
|
142
|
+
role,
|
|
143
|
+
timestamp: Date.now(),
|
|
144
|
+
parts: [{ type: 'audio', transcript: message }],
|
|
145
|
+
}
|
|
146
|
+
emit('message_complete', { message: realtimeMessage })
|
|
151
147
|
}
|
|
152
|
-
emit('message_complete', { message: realtimeMessage })
|
|
153
148
|
},
|
|
154
149
|
|
|
155
150
|
onError: (error: string | Error) => {
|