ai-site-pilot 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +295 -0
- package/dist/api/index.d.mts +78 -0
- package/dist/api/index.d.ts +78 -0
- package/dist/api/index.js +142 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/index.mjs +137 -0
- package/dist/api/index.mjs.map +1 -0
- package/dist/hooks/index.d.mts +69 -0
- package/dist/hooks/index.d.ts +69 -0
- package/dist/hooks/index.js +240 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/index.mjs +237 -0
- package/dist/hooks/index.mjs.map +1 -0
- package/dist/index.d.mts +56 -0
- package/dist/index.d.ts +56 -0
- package/dist/index.js +611 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +597 -0
- package/dist/index.mjs.map +1 -0
- package/dist/styles.css +238 -0
- package/dist/tools/index.d.mts +71 -0
- package/dist/tools/index.d.ts +71 -0
- package/dist/tools/index.js +124 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/index.mjs +119 -0
- package/dist/tools/index.mjs.map +1 -0
- package/dist/types--7jDyUM6.d.mts +32 -0
- package/dist/types--7jDyUM6.d.ts +32 -0
- package/dist/types-DAvVRuXd.d.mts +71 -0
- package/dist/types-DAvVRuXd.d.ts +71 -0
- package/package.json +81 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { streamText } from 'ai';
|
|
2
|
+
|
|
3
|
+
// src/api/createChatHandler.ts
|
|
4
|
+
|
|
5
|
+
// src/api/streaming.ts
|
|
6
|
+
function createSSEEncoder() {
|
|
7
|
+
const encoder = new TextEncoder();
|
|
8
|
+
return {
|
|
9
|
+
encode(event) {
|
|
10
|
+
return encoder.encode(`data: ${JSON.stringify(event)}
|
|
11
|
+
|
|
12
|
+
`);
|
|
13
|
+
},
|
|
14
|
+
encodeText(content) {
|
|
15
|
+
return encoder.encode(`data: ${JSON.stringify({ type: "text", content })}
|
|
16
|
+
|
|
17
|
+
`);
|
|
18
|
+
},
|
|
19
|
+
encodeTool(name, args) {
|
|
20
|
+
return encoder.encode(`data: ${JSON.stringify({ type: "tool", name, args })}
|
|
21
|
+
|
|
22
|
+
`);
|
|
23
|
+
},
|
|
24
|
+
encodeDone() {
|
|
25
|
+
return encoder.encode(`data: ${JSON.stringify({ type: "done" })}
|
|
26
|
+
|
|
27
|
+
`);
|
|
28
|
+
},
|
|
29
|
+
encodeError(message) {
|
|
30
|
+
return encoder.encode(`data: ${JSON.stringify({ type: "error", message })}
|
|
31
|
+
|
|
32
|
+
`);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function getSSEHeaders() {
|
|
37
|
+
return {
|
|
38
|
+
"Content-Type": "text/event-stream",
|
|
39
|
+
"Cache-Control": "no-cache",
|
|
40
|
+
"Connection": "keep-alive"
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
async function* parseSSEStream(reader) {
|
|
44
|
+
const decoder = new TextDecoder();
|
|
45
|
+
let buffer = "";
|
|
46
|
+
while (true) {
|
|
47
|
+
const { done, value } = await reader.read();
|
|
48
|
+
if (done) break;
|
|
49
|
+
buffer += decoder.decode(value, { stream: true });
|
|
50
|
+
const lines = buffer.split("\n");
|
|
51
|
+
buffer = lines.pop() || "";
|
|
52
|
+
for (const line of lines) {
|
|
53
|
+
if (line.startsWith("data: ")) {
|
|
54
|
+
try {
|
|
55
|
+
const data = JSON.parse(line.slice(6));
|
|
56
|
+
yield data;
|
|
57
|
+
} catch {
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// src/api/createChatHandler.ts
|
|
65
|
+
function convertToolsToAISDK(tools) {
|
|
66
|
+
const result = {};
|
|
67
|
+
for (const tool of tools) {
|
|
68
|
+
result[tool.name] = {
|
|
69
|
+
description: tool.description,
|
|
70
|
+
parameters: {
|
|
71
|
+
type: "object",
|
|
72
|
+
properties: tool.parameters.properties,
|
|
73
|
+
required: tool.parameters.required
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
function createChatHandler(config) {
|
|
80
|
+
const { model, systemPrompt, tools = [], temperature = 0.7, maxTokens } = config;
|
|
81
|
+
return async function POST(req) {
|
|
82
|
+
try {
|
|
83
|
+
const body = await req.json();
|
|
84
|
+
const { messages } = body;
|
|
85
|
+
const coreMessages = messages.map((m) => ({
|
|
86
|
+
role: m.role,
|
|
87
|
+
content: m.content
|
|
88
|
+
}));
|
|
89
|
+
const sse = createSSEEncoder();
|
|
90
|
+
const stream = new ReadableStream({
|
|
91
|
+
async start(controller) {
|
|
92
|
+
try {
|
|
93
|
+
const result = streamText({
|
|
94
|
+
model,
|
|
95
|
+
system: systemPrompt,
|
|
96
|
+
messages: coreMessages,
|
|
97
|
+
temperature,
|
|
98
|
+
maxTokens,
|
|
99
|
+
tools: tools.length > 0 ? convertToolsToAISDK(tools) : void 0
|
|
100
|
+
});
|
|
101
|
+
for await (const chunk of (await result).textStream) {
|
|
102
|
+
if (chunk) {
|
|
103
|
+
controller.enqueue(sse.encodeText(chunk));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const finalResult = await result;
|
|
107
|
+
const toolCalls = await finalResult.toolCalls || [];
|
|
108
|
+
for (const toolCall of toolCalls) {
|
|
109
|
+
controller.enqueue(
|
|
110
|
+
sse.encodeTool(toolCall.toolName, toolCall.args)
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
controller.enqueue(sse.encodeDone());
|
|
114
|
+
controller.close();
|
|
115
|
+
} catch (error) {
|
|
116
|
+
console.error("Streaming error:", error);
|
|
117
|
+
controller.enqueue(sse.encodeError("An error occurred during streaming"));
|
|
118
|
+
controller.close();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
return new Response(stream, {
|
|
123
|
+
headers: getSSEHeaders()
|
|
124
|
+
});
|
|
125
|
+
} catch (error) {
|
|
126
|
+
console.error("Chat API error:", error);
|
|
127
|
+
return new Response(JSON.stringify({ error: "Internal server error" }), {
|
|
128
|
+
status: 500,
|
|
129
|
+
headers: { "Content-Type": "application/json" }
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export { createChatHandler, createSSEEncoder, getSSEHeaders, parseSSEStream };
|
|
136
|
+
//# sourceMappingURL=index.mjs.map
|
|
137
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/api/streaming.ts","../../src/api/createChatHandler.ts"],"names":[],"mappings":";;;;;AASO,SAAS,gBAAA,GAAmB;AACjC,EAAA,MAAM,OAAA,GAAU,IAAI,WAAA,EAAY;AAEhC,EAAA,OAAO;AAAA,IACL,OAAO,KAAA,EAAgC;AACrC,MAAA,OAAO,QAAQ,MAAA,CAAO,CAAA,MAAA,EAAS,IAAA,CAAK,SAAA,CAAU,KAAK,CAAC;;AAAA,CAAM,CAAA;AAAA,IAC5D,CAAA;AAAA,IAEA,WAAW,OAAA,EAA6B;AACtC,MAAA,OAAO,OAAA,CAAQ,MAAA,CAAO,CAAA,MAAA,EAAS,IAAA,CAAK,SAAA,CAAU,EAAE,IAAA,EAAM,MAAA,EAAQ,OAAA,EAAS,CAAC;;AAAA,CAAM,CAAA;AAAA,IAChF,CAAA;AAAA,IAEA,UAAA,CAAW,MAAc,IAAA,EAA2C;AAClE,MAAA,OAAO,OAAA,CAAQ,MAAA,CAAO,CAAA,MAAA,EAAS,IAAA,CAAK,SAAA,CAAU,EAAE,IAAA,EAAM,MAAA,EAAQ,IAAA,EAAM,IAAA,EAAM,CAAC;;AAAA,CAAM,CAAA;AAAA,IACnF,CAAA;AAAA,IAEA,UAAA,GAAyB;AACvB,MAAA,OAAO,OAAA,CAAQ,OAAO,CAAA,MAAA,EAAS,IAAA,CAAK,UAAU,EAAE,IAAA,EAAM,MAAA,EAAQ,CAAC;;AAAA,CAAM,CAAA;AAAA,IACvE,CAAA;AAAA,IAEA,YAAY,OAAA,EAA6B;AACvC,MAAA,OAAO,OAAA,CAAQ,MAAA,CAAO,CAAA,MAAA,EAAS,IAAA,CAAK,SAAA,CAAU,EAAE,IAAA,EAAM,OAAA,EAAS,OAAA,EAAS,CAAC;;AAAA,CAAM,CAAA;AAAA,IACjF;AAAA,GACF;AACF;AAKO,SAAS,aAAA,GAA6B;AAC3C,EAAA,OAAO;AAAA,IACL,cAAA,EAAgB,mBAAA;AAAA,IAChB,eAAA,EAAiB,UAAA;AAAA,IACjB,YAAA,EAAc;AAAA,GAChB;AACF;AAKA,gBAAuB,eACrB,MAAA,EAC6B;AAC7B,EAAA,MAAM,OAAA,GAAU,IAAI,WAAA,EAAY;AAChC,EAAA,IAAI,MAAA,GAAS,EAAA;AAEb,EAAA,OAAO,IAAA,EAAM;AACX,IAAA,MAAM,EAAE,IAAA,EAAM,KAAA,EAAM,GAAI,MAAM,OAAO,IAAA,EAAK;AAC1C,IAAA,IAAI,IAAA,EAAM;AAEV,IAAA,MAAA,IAAU,QAAQ,MAAA,CAAO,KAAA,EAAO,EAAE,MAAA,EAAQ,MAAM,CAAA;AAChD,IAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,KAAA,CAAM,IAAI,CAAA;AAC/B,IAAA,MAAA,GAAS,KAAA,CAAM,KAAI,IAAK,EAAA;AAExB,IAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,MAAA,IAAI,IAAA,CAAK,UAAA,CAAW,QAAQ,CAAA,EAAG;AAC7B,QAAA,IAAI;AACF,UAAA,MAAM,OAAO,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,KAAA,CAAM,CAAC,CAAC,CAAA;AACrC,UAAA,MAAM,IAAA;AAAA,QACR,CAAA,CAAA,MAAQ;AAAA,QAER;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;;;AC9CA,SAAS,oBAAoB,KAAA,EAAyB;AACpD,EAAA,MAAM,SAAuE,EAAC;AAE9E,EAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,IAAA,MAAA,CAAO,IAAA,CAAK,IAAI,CAAA,GAAI;AAAA,MAClB,aAAa,IAAA,CAAK,WAAA;AAAA,MAClB,UAAA,EAAY;AAAA,QACV,IAAA,EAAM,QAAA;AAAA,QACN,UAAA,EAAY,KAAK,UAAA,CAAW,UAAA;AAAA,QAC5B,QAAA,EAAU,KAAK,UAAA,CAAW;AAAA;AAC5B,KACF;AAAA,EACF;AAEA,EAAA,OAAO,MAAA;AACT;AAkCO,SAAS,kBAAkB,MAAA,EAA2B;AAC3D,EAAA,MAAM,EAAE,OAAO,YAAA,EAAc,KAAA,GAAQ,EAAC,EAAG,WAAA,GAAc,GAAA,EAAK,SAAA,EAAU,GAAI,MAAA;AAE1E,EAAA,OAAO,eAAe,KAAK,GAAA,EAAiC;AAC1D,IAAA,IAAI;AACF,MAAA,MAAM,IAAA,GAAQ,MAAM,GAAA,CAAI,IAAA,EAAK;AAC7B,MAAA,MAAM,EAAE,UAAS,GAAI,IAAA;AAGrB,MAAA,MAAM,YAAA,GAA8B,QAAA,CAAS,GAAA,CAAI,CAAC,CAAA,MAAO;AAAA,QACvD,MAAM,CAAA,CAAE,IAAA;AAAA,QACR,SAAS,CAAA,CAAE;AAAA,OACb,CAAE,CAAA;AAGF,MAAA,MAAM,MAAM,gBAAA,EAAiB;AAG7B,MAAA,MAAM,MAAA,GAAS,IAAI,cAAA,CAAe;AAAA,QAChC,MAAM,MAAM,UAAA,EAAY;AACtB,UAAA,IAAI;AACF,YAAA,MAAM,SAAS,UAAA,CAAW;AAAA,cACxB,KAAA;AAAA,cACA,MAAA,EAAQ,YAAA;AAAA,cACR,QAAA,EAAU,YAAA;AAAA,cACV,WAAA;AAAA,cACA,SAAA;AAAA,cACA,OAAO,KAAA,CAAM,MAAA,GAAS,CAAA,GAAI,mBAAA,CAAoB,KAAK,CAAA,GAAI,KAAA;AAAA,aACxD,CAAA;AAGD,YAAA,WAAA,MAAiB,KAAA,IAAA,CAAU,MAAM,MAAA,EAAQ,UAAA,EAAY;AACnD,cAAA,IAAI,KAAA,EAAO;AACT,gBAAA,UAAA,CAAW,OAAA,CAAQ,GAAA,CAAI,UAAA,CAAW,KAAK,CAAC,CAAA;AAAA,cAC1C;AAAA,YACF;AAGA,YAAA,MAAM,cAAc,MAAM,MAAA;AAC1B,YAAA,MAAM,SAAA,GAAa,MAAM,WAAA,CAAY,SAAA,IAAc,EAAC;AAEpD,YAAA,KAAA,MAAW,YAAY,SAAA,EAAW;AAChC,cAAA,UAAA,CAAW,OAAA;AAAA,gBACT,GAAA,CAAI,UAAA,CAAW,QAAA,CAAS,QAAA,EAAU,SAAS,IAA+B;AAAA,eAC5E;AAAA,YACF;AAEA,YAAA,UAAA,CAAW,OAAA,CAAQ,GAAA,CAAI,UAAA,EAAY,CAAA;AACnC,YAAA,UAAA,CAAW,KAAA,EAAM;AAAA,UACnB,SAAS,KAAA,EAAO;AACd,YAAA,OAAA,CAAQ,KAAA,CAAM,oBAAoB,KAAK,CAAA;AACvC,YAAA,UAAA,CAAW,OAAA,CAAQ,GAAA,CAAI,WAAA,CAAY,oCAAoC,CAAC,CAAA;AACxE,YAAA,UAAA,CAAW,KAAA,EAAM;AAAA,UACnB;AAAA,QACF;AAAA,OACD,CAAA;AAED,MAAA,OAAO,IAAI,SAAS,MAAA,EAAQ;AAAA,QAC1B,SAAS,aAAA;AAAc,OACxB,CAAA;AAAA,IACH,SAAS,KAAA,EAAO;AACd,MAAA,OAAA,CAAQ,KAAA,CAAM,mBAAmB,KAAK,CAAA;AACtC,MAAA,OAAO,IAAI,SAAS,IAAA,CAAK,SAAA,CAAU,EAAE,KAAA,EAAO,uBAAA,EAAyB,CAAA,EAAG;AAAA,QACtE,MAAA,EAAQ,GAAA;AAAA,QACR,OAAA,EAAS,EAAE,cAAA,EAAgB,kBAAA;AAAmB,OAC/C,CAAA;AAAA,IACH;AAAA,EACF,CAAA;AACF","file":"index.mjs","sourcesContent":["/**\n * SSE streaming utilities\n */\n\nimport type { StreamEvent } from '../types';\n\n/**\n * Create an SSE encoder for streaming responses\n */\nexport function createSSEEncoder() {\n const encoder = new TextEncoder();\n\n return {\n encode(event: StreamEvent): Uint8Array {\n return encoder.encode(`data: ${JSON.stringify(event)}\\n\\n`);\n },\n\n encodeText(content: string): Uint8Array {\n return encoder.encode(`data: ${JSON.stringify({ type: 'text', content })}\\n\\n`);\n },\n\n encodeTool(name: string, args: Record<string, unknown>): Uint8Array {\n return encoder.encode(`data: ${JSON.stringify({ type: 'tool', name, args })}\\n\\n`);\n },\n\n encodeDone(): Uint8Array {\n return encoder.encode(`data: ${JSON.stringify({ type: 'done' })}\\n\\n`);\n },\n\n encodeError(message: string): Uint8Array {\n return encoder.encode(`data: ${JSON.stringify({ type: 'error', message })}\\n\\n`);\n },\n };\n}\n\n/**\n * Create SSE response headers\n */\nexport function getSSEHeaders(): HeadersInit {\n return {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n 'Connection': 'keep-alive',\n };\n}\n\n/**\n * Parse SSE events from a ReadableStream\n */\nexport async function* parseSSEStream(\n reader: ReadableStreamDefaultReader<Uint8Array>\n): AsyncGenerator<StreamEvent> {\n const decoder = new TextDecoder();\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n buffer = lines.pop() || '';\n\n for (const line of lines) {\n if (line.startsWith('data: ')) {\n try {\n const data = JSON.parse(line.slice(6)) as StreamEvent;\n yield data;\n } catch {\n // Skip malformed JSON\n }\n }\n }\n }\n}\n","/**\n * Factory for creating Next.js API route handlers\n */\n\nimport { streamText, type CoreMessage, type LanguageModel } from 'ai';\nimport type { ToolDefinition } from '../tools/types';\nimport { createSSEEncoder, getSSEHeaders } from './streaming';\n\nexport interface ChatHandlerConfig {\n /** The AI model to use (from Vercel AI SDK) */\n model: LanguageModel;\n /** System prompt for the AI */\n systemPrompt: string;\n /** Tool definitions for the AI */\n tools?: ToolDefinition[];\n /** Temperature for response generation (0-1) */\n temperature?: number;\n /** Maximum tokens in response */\n maxTokens?: number;\n}\n\ninterface RequestBody {\n messages: Array<{ role: 'user' | 'assistant'; content: string }>;\n}\n\n/**\n * Convert tool definitions to Vercel AI SDK format\n */\nfunction convertToolsToAISDK(tools: ToolDefinition[]) {\n const result: Record<string, { description: string; parameters: unknown }> = {};\n\n for (const tool of tools) {\n result[tool.name] = {\n description: tool.description,\n parameters: {\n type: 'object',\n properties: tool.parameters.properties,\n required: tool.parameters.required,\n },\n };\n }\n\n return result;\n}\n\n/**\n * Create a Next.js API route handler for chat\n *\n * Works with any Vercel AI SDK compatible model including:\n * - Google Gemini (@ai-sdk/google)\n * - OpenAI (@ai-sdk/openai)\n * - Anthropic (@ai-sdk/anthropic)\n * - And more...\n *\n * @example\n * ```ts\n * // app/api/chat/route.ts\n * import { createChatHandler } from 'ai-site-pilot/api';\n * import { google } from '@ai-sdk/google';\n *\n * export const POST = createChatHandler({\n * model: google('gemini-2.0-flash'),\n * systemPrompt: 'You are a helpful assistant...',\n * tools: myTools,\n * });\n * ```\n *\n * @example Using OpenAI\n * ```ts\n * import { openai } from '@ai-sdk/openai';\n *\n * export const POST = createChatHandler({\n * model: openai('gpt-4o'),\n * systemPrompt: 'You are a helpful assistant...',\n * });\n * ```\n */\nexport function createChatHandler(config: ChatHandlerConfig) {\n const { model, systemPrompt, tools = [], temperature = 0.7, maxTokens } = config;\n\n return async function POST(req: Request): Promise<Response> {\n try {\n const body = (await req.json()) as RequestBody;\n const { messages } = body;\n\n // Convert messages to CoreMessage format\n const coreMessages: CoreMessage[] = messages.map((m) => ({\n role: m.role,\n content: m.content,\n }));\n\n // Create the SSE encoder\n const sse = createSSEEncoder();\n\n // Create a readable stream for SSE\n const stream = new ReadableStream({\n async start(controller) {\n try {\n const result = streamText({\n model,\n system: systemPrompt,\n messages: coreMessages,\n temperature,\n maxTokens,\n tools: tools.length > 0 ? convertToolsToAISDK(tools) : undefined,\n });\n\n // Stream text chunks\n for await (const chunk of (await result).textStream) {\n if (chunk) {\n controller.enqueue(sse.encodeText(chunk));\n }\n }\n\n // Get tool calls from the result\n const finalResult = await result;\n const toolCalls = (await finalResult.toolCalls) || [];\n\n for (const toolCall of toolCalls) {\n controller.enqueue(\n sse.encodeTool(toolCall.toolName, toolCall.args as Record<string, unknown>)\n );\n }\n\n controller.enqueue(sse.encodeDone());\n controller.close();\n } catch (error) {\n console.error('Streaming error:', error);\n controller.enqueue(sse.encodeError('An error occurred during streaming'));\n controller.close();\n }\n },\n });\n\n return new Response(stream, {\n headers: getSSEHeaders(),\n });\n } catch (error) {\n console.error('Chat API error:', error);\n return new Response(JSON.stringify({ error: 'Internal server error' }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n });\n }\n };\n}\n"]}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { C as ChatMessage } from '../types-DAvVRuXd.mjs';
|
|
2
|
+
|
|
3
|
+
interface UseChatOptions {
|
|
4
|
+
/** API endpoint for chat */
|
|
5
|
+
apiEndpoint: string;
|
|
6
|
+
/** Initial messages */
|
|
7
|
+
initialMessages?: ChatMessage[];
|
|
8
|
+
/** Callback when a tool is called */
|
|
9
|
+
onToolCall?: (toolName: string, args: Record<string, unknown>) => void | Promise<void>;
|
|
10
|
+
/** Callback when streaming starts */
|
|
11
|
+
onStreamStart?: () => void;
|
|
12
|
+
/** Callback when streaming ends */
|
|
13
|
+
onStreamEnd?: () => void;
|
|
14
|
+
}
|
|
15
|
+
interface UseChatReturn {
|
|
16
|
+
/** All messages in the conversation */
|
|
17
|
+
messages: ChatMessage[];
|
|
18
|
+
/** Current input value */
|
|
19
|
+
input: string;
|
|
20
|
+
/** Set the input value */
|
|
21
|
+
setInput: (value: string) => void;
|
|
22
|
+
/** Whether the assistant is currently responding */
|
|
23
|
+
isLoading: boolean;
|
|
24
|
+
/** ID of the currently streaming message */
|
|
25
|
+
streamingMessageId: string | null;
|
|
26
|
+
/** Send a message */
|
|
27
|
+
sendMessage: (content?: string) => Promise<void>;
|
|
28
|
+
/** Clear all messages */
|
|
29
|
+
clearMessages: () => void;
|
|
30
|
+
/** Add a message manually */
|
|
31
|
+
addMessage: (message: Omit<ChatMessage, 'id' | 'timestamp'>) => void;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Hook for managing chat state and streaming
|
|
35
|
+
*/
|
|
36
|
+
declare function useChat(options: UseChatOptions): UseChatReturn;
|
|
37
|
+
|
|
38
|
+
interface UseSpeechOptions {
|
|
39
|
+
/** Language for speech recognition */
|
|
40
|
+
lang?: string;
|
|
41
|
+
/** Callback when speech is recognized */
|
|
42
|
+
onResult?: (transcript: string, isFinal: boolean) => void;
|
|
43
|
+
}
|
|
44
|
+
interface UseSpeechReturn {
|
|
45
|
+
/** Whether speech recognition is supported */
|
|
46
|
+
isSupported: boolean;
|
|
47
|
+
/** Whether currently listening */
|
|
48
|
+
isListening: boolean;
|
|
49
|
+
/** Toggle listening on/off */
|
|
50
|
+
toggleListening: () => void;
|
|
51
|
+
/** Start listening */
|
|
52
|
+
startListening: () => void;
|
|
53
|
+
/** Stop listening */
|
|
54
|
+
stopListening: () => void;
|
|
55
|
+
/** Speak text using TTS */
|
|
56
|
+
speak: (text: string) => void;
|
|
57
|
+
/** Whether TTS is enabled */
|
|
58
|
+
ttsEnabled: boolean;
|
|
59
|
+
/** Toggle TTS on/off */
|
|
60
|
+
setTtsEnabled: (enabled: boolean) => void;
|
|
61
|
+
/** Cancel current speech */
|
|
62
|
+
cancelSpeech: () => void;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Hook for speech recognition and text-to-speech
|
|
66
|
+
*/
|
|
67
|
+
declare function useSpeech(options?: UseSpeechOptions): UseSpeechReturn;
|
|
68
|
+
|
|
69
|
+
export { type UseChatOptions, type UseChatReturn, type UseSpeechOptions, type UseSpeechReturn, useChat, useSpeech };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { C as ChatMessage } from '../types-DAvVRuXd.js';
|
|
2
|
+
|
|
3
|
+
interface UseChatOptions {
|
|
4
|
+
/** API endpoint for chat */
|
|
5
|
+
apiEndpoint: string;
|
|
6
|
+
/** Initial messages */
|
|
7
|
+
initialMessages?: ChatMessage[];
|
|
8
|
+
/** Callback when a tool is called */
|
|
9
|
+
onToolCall?: (toolName: string, args: Record<string, unknown>) => void | Promise<void>;
|
|
10
|
+
/** Callback when streaming starts */
|
|
11
|
+
onStreamStart?: () => void;
|
|
12
|
+
/** Callback when streaming ends */
|
|
13
|
+
onStreamEnd?: () => void;
|
|
14
|
+
}
|
|
15
|
+
interface UseChatReturn {
|
|
16
|
+
/** All messages in the conversation */
|
|
17
|
+
messages: ChatMessage[];
|
|
18
|
+
/** Current input value */
|
|
19
|
+
input: string;
|
|
20
|
+
/** Set the input value */
|
|
21
|
+
setInput: (value: string) => void;
|
|
22
|
+
/** Whether the assistant is currently responding */
|
|
23
|
+
isLoading: boolean;
|
|
24
|
+
/** ID of the currently streaming message */
|
|
25
|
+
streamingMessageId: string | null;
|
|
26
|
+
/** Send a message */
|
|
27
|
+
sendMessage: (content?: string) => Promise<void>;
|
|
28
|
+
/** Clear all messages */
|
|
29
|
+
clearMessages: () => void;
|
|
30
|
+
/** Add a message manually */
|
|
31
|
+
addMessage: (message: Omit<ChatMessage, 'id' | 'timestamp'>) => void;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Hook for managing chat state and streaming
|
|
35
|
+
*/
|
|
36
|
+
declare function useChat(options: UseChatOptions): UseChatReturn;
|
|
37
|
+
|
|
38
|
+
interface UseSpeechOptions {
|
|
39
|
+
/** Language for speech recognition */
|
|
40
|
+
lang?: string;
|
|
41
|
+
/** Callback when speech is recognized */
|
|
42
|
+
onResult?: (transcript: string, isFinal: boolean) => void;
|
|
43
|
+
}
|
|
44
|
+
interface UseSpeechReturn {
|
|
45
|
+
/** Whether speech recognition is supported */
|
|
46
|
+
isSupported: boolean;
|
|
47
|
+
/** Whether currently listening */
|
|
48
|
+
isListening: boolean;
|
|
49
|
+
/** Toggle listening on/off */
|
|
50
|
+
toggleListening: () => void;
|
|
51
|
+
/** Start listening */
|
|
52
|
+
startListening: () => void;
|
|
53
|
+
/** Stop listening */
|
|
54
|
+
stopListening: () => void;
|
|
55
|
+
/** Speak text using TTS */
|
|
56
|
+
speak: (text: string) => void;
|
|
57
|
+
/** Whether TTS is enabled */
|
|
58
|
+
ttsEnabled: boolean;
|
|
59
|
+
/** Toggle TTS on/off */
|
|
60
|
+
setTtsEnabled: (enabled: boolean) => void;
|
|
61
|
+
/** Cancel current speech */
|
|
62
|
+
cancelSpeech: () => void;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Hook for speech recognition and text-to-speech
|
|
66
|
+
*/
|
|
67
|
+
declare function useSpeech(options?: UseSpeechOptions): UseSpeechReturn;
|
|
68
|
+
|
|
69
|
+
export { type UseChatOptions, type UseChatReturn, type UseSpeechOptions, type UseSpeechReturn, useChat, useSpeech };
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
|
|
5
|
+
// src/hooks/useChat.ts
|
|
6
|
+
function useChat(options) {
|
|
7
|
+
const { apiEndpoint, initialMessages = [], onToolCall, onStreamStart, onStreamEnd } = options;
|
|
8
|
+
const [messages, setMessages] = react.useState(initialMessages);
|
|
9
|
+
const [input, setInput] = react.useState("");
|
|
10
|
+
const [isLoading, setIsLoading] = react.useState(false);
|
|
11
|
+
const [streamingMessageId, setStreamingMessageId] = react.useState(null);
|
|
12
|
+
const abortControllerRef = react.useRef(null);
|
|
13
|
+
const addMessage = react.useCallback((message) => {
|
|
14
|
+
const newMessage = {
|
|
15
|
+
...message,
|
|
16
|
+
id: Date.now().toString(),
|
|
17
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
18
|
+
};
|
|
19
|
+
setMessages((prev) => [...prev, newMessage]);
|
|
20
|
+
return newMessage;
|
|
21
|
+
}, []);
|
|
22
|
+
const clearMessages = react.useCallback(() => {
|
|
23
|
+
setMessages(initialMessages);
|
|
24
|
+
}, [initialMessages]);
|
|
25
|
+
const sendMessage = react.useCallback(
|
|
26
|
+
async (content) => {
|
|
27
|
+
const messageContent = content || input;
|
|
28
|
+
if (!messageContent.trim() || isLoading) return;
|
|
29
|
+
if (abortControllerRef.current) {
|
|
30
|
+
abortControllerRef.current.abort();
|
|
31
|
+
}
|
|
32
|
+
abortControllerRef.current = new AbortController();
|
|
33
|
+
const userMessage = {
|
|
34
|
+
id: Date.now().toString(),
|
|
35
|
+
role: "user",
|
|
36
|
+
content: messageContent,
|
|
37
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
38
|
+
};
|
|
39
|
+
setMessages((prev) => [...prev, userMessage]);
|
|
40
|
+
setInput("");
|
|
41
|
+
setIsLoading(true);
|
|
42
|
+
onStreamStart?.();
|
|
43
|
+
const assistantMessageId = (Date.now() + 1).toString();
|
|
44
|
+
const assistantMessage = {
|
|
45
|
+
id: assistantMessageId,
|
|
46
|
+
role: "assistant",
|
|
47
|
+
content: "",
|
|
48
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
49
|
+
toolCalls: []
|
|
50
|
+
};
|
|
51
|
+
setMessages((prev) => [...prev, assistantMessage]);
|
|
52
|
+
setStreamingMessageId(assistantMessageId);
|
|
53
|
+
let fullText = "";
|
|
54
|
+
const toolCalls = [];
|
|
55
|
+
try {
|
|
56
|
+
const apiMessages = messages.concat(userMessage).map((m) => ({
|
|
57
|
+
role: m.role,
|
|
58
|
+
content: m.content
|
|
59
|
+
}));
|
|
60
|
+
const response = await fetch(apiEndpoint, {
|
|
61
|
+
method: "POST",
|
|
62
|
+
headers: { "Content-Type": "application/json" },
|
|
63
|
+
body: JSON.stringify({ messages: apiMessages }),
|
|
64
|
+
signal: abortControllerRef.current.signal
|
|
65
|
+
});
|
|
66
|
+
if (!response.ok) {
|
|
67
|
+
throw new Error("Failed to get response");
|
|
68
|
+
}
|
|
69
|
+
const reader = response.body?.getReader();
|
|
70
|
+
if (!reader) throw new Error("No reader available");
|
|
71
|
+
const decoder = new TextDecoder();
|
|
72
|
+
while (true) {
|
|
73
|
+
const { done, value } = await reader.read();
|
|
74
|
+
if (done) break;
|
|
75
|
+
const text = decoder.decode(value);
|
|
76
|
+
const lines = text.split("\n");
|
|
77
|
+
for (const line of lines) {
|
|
78
|
+
if (line.startsWith("data: ")) {
|
|
79
|
+
try {
|
|
80
|
+
const data = JSON.parse(line.slice(6));
|
|
81
|
+
if (data.type === "text") {
|
|
82
|
+
fullText += data.content;
|
|
83
|
+
setMessages(
|
|
84
|
+
(prev) => prev.map(
|
|
85
|
+
(m) => m.id === assistantMessageId ? { ...m, content: fullText } : m
|
|
86
|
+
)
|
|
87
|
+
);
|
|
88
|
+
} else if (data.type === "tool") {
|
|
89
|
+
const toolCall = { name: data.name, args: data.args };
|
|
90
|
+
toolCalls.push(toolCall);
|
|
91
|
+
if (onToolCall) {
|
|
92
|
+
try {
|
|
93
|
+
await onToolCall(data.name, data.args);
|
|
94
|
+
} catch (e) {
|
|
95
|
+
console.error("Tool execution error:", e);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
} else if (data.type === "done") {
|
|
99
|
+
if (toolCalls.length > 0) {
|
|
100
|
+
setMessages(
|
|
101
|
+
(prev) => prev.map(
|
|
102
|
+
(m) => m.id === assistantMessageId ? { ...m, toolCalls } : m
|
|
103
|
+
)
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
if (!fullText) {
|
|
107
|
+
setMessages(
|
|
108
|
+
(prev) => prev.map(
|
|
109
|
+
(m) => m.id === assistantMessageId ? { ...m, content: "I've made some changes. Take a look!" } : m
|
|
110
|
+
)
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
} else if (data.type === "error") {
|
|
114
|
+
throw new Error(data.message);
|
|
115
|
+
}
|
|
116
|
+
} catch {
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
} catch (error) {
|
|
122
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
console.error("Chat error:", error);
|
|
126
|
+
setMessages(
|
|
127
|
+
(prev) => prev.map(
|
|
128
|
+
(m) => m.id === assistantMessageId ? { ...m, content: "Sorry, I encountered an error. Please try again." } : m
|
|
129
|
+
)
|
|
130
|
+
);
|
|
131
|
+
} finally {
|
|
132
|
+
setIsLoading(false);
|
|
133
|
+
setStreamingMessageId(null);
|
|
134
|
+
onStreamEnd?.();
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
[apiEndpoint, input, isLoading, messages, onToolCall, onStreamStart, onStreamEnd]
|
|
138
|
+
);
|
|
139
|
+
return {
|
|
140
|
+
messages,
|
|
141
|
+
input,
|
|
142
|
+
setInput,
|
|
143
|
+
isLoading,
|
|
144
|
+
streamingMessageId,
|
|
145
|
+
sendMessage,
|
|
146
|
+
clearMessages,
|
|
147
|
+
addMessage
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
function useSpeech(options = {}) {
|
|
151
|
+
const { lang = "en-US", onResult } = options;
|
|
152
|
+
const [isSupported, setIsSupported] = react.useState(false);
|
|
153
|
+
const [isListening, setIsListening] = react.useState(false);
|
|
154
|
+
const [ttsEnabled, setTtsEnabled] = react.useState(false);
|
|
155
|
+
const recognitionRef = react.useRef(null);
|
|
156
|
+
react.useEffect(() => {
|
|
157
|
+
if (typeof window === "undefined") return;
|
|
158
|
+
const windowWithSpeech = window;
|
|
159
|
+
const SpeechRecognitionAPI = windowWithSpeech.SpeechRecognition || windowWithSpeech.webkitSpeechRecognition;
|
|
160
|
+
if (SpeechRecognitionAPI) {
|
|
161
|
+
setIsSupported(true);
|
|
162
|
+
recognitionRef.current = new SpeechRecognitionAPI();
|
|
163
|
+
recognitionRef.current.continuous = false;
|
|
164
|
+
recognitionRef.current.interimResults = true;
|
|
165
|
+
recognitionRef.current.lang = lang;
|
|
166
|
+
recognitionRef.current.onresult = (event) => {
|
|
167
|
+
const result = event.results[0];
|
|
168
|
+
const transcript = result[0].transcript;
|
|
169
|
+
onResult?.(transcript, result.isFinal);
|
|
170
|
+
if (result.isFinal) {
|
|
171
|
+
setIsListening(false);
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
recognitionRef.current.onend = () => setIsListening(false);
|
|
175
|
+
recognitionRef.current.onerror = () => setIsListening(false);
|
|
176
|
+
}
|
|
177
|
+
}, [lang, onResult]);
|
|
178
|
+
const startListening = react.useCallback(() => {
|
|
179
|
+
if (!recognitionRef.current) return;
|
|
180
|
+
try {
|
|
181
|
+
recognitionRef.current.start();
|
|
182
|
+
setIsListening(true);
|
|
183
|
+
} catch (e) {
|
|
184
|
+
console.error("Speech recognition error:", e);
|
|
185
|
+
}
|
|
186
|
+
}, []);
|
|
187
|
+
const stopListening = react.useCallback(() => {
|
|
188
|
+
if (!recognitionRef.current) return;
|
|
189
|
+
recognitionRef.current.stop();
|
|
190
|
+
setIsListening(false);
|
|
191
|
+
}, []);
|
|
192
|
+
const toggleListening = react.useCallback(() => {
|
|
193
|
+
if (isListening) {
|
|
194
|
+
stopListening();
|
|
195
|
+
} else {
|
|
196
|
+
startListening();
|
|
197
|
+
}
|
|
198
|
+
}, [isListening, startListening, stopListening]);
|
|
199
|
+
const speak = react.useCallback(
|
|
200
|
+
(text) => {
|
|
201
|
+
if (!ttsEnabled || typeof window === "undefined" || !("speechSynthesis" in window)) {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
speechSynthesis.cancel();
|
|
205
|
+
const utterance = new SpeechSynthesisUtterance(text);
|
|
206
|
+
utterance.rate = 1;
|
|
207
|
+
utterance.pitch = 1;
|
|
208
|
+
const voices = speechSynthesis.getVoices();
|
|
209
|
+
const preferredVoice = voices.find(
|
|
210
|
+
(v) => v.name.includes("Google") || v.name.includes("Samantha") || v.name.includes("Alex")
|
|
211
|
+
);
|
|
212
|
+
if (preferredVoice) {
|
|
213
|
+
utterance.voice = preferredVoice;
|
|
214
|
+
}
|
|
215
|
+
speechSynthesis.speak(utterance);
|
|
216
|
+
},
|
|
217
|
+
[ttsEnabled]
|
|
218
|
+
);
|
|
219
|
+
const cancelSpeech = react.useCallback(() => {
|
|
220
|
+
if (typeof window !== "undefined" && "speechSynthesis" in window) {
|
|
221
|
+
speechSynthesis.cancel();
|
|
222
|
+
}
|
|
223
|
+
}, []);
|
|
224
|
+
return {
|
|
225
|
+
isSupported,
|
|
226
|
+
isListening,
|
|
227
|
+
toggleListening,
|
|
228
|
+
startListening,
|
|
229
|
+
stopListening,
|
|
230
|
+
speak,
|
|
231
|
+
ttsEnabled,
|
|
232
|
+
setTtsEnabled,
|
|
233
|
+
cancelSpeech
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
exports.useChat = useChat;
|
|
238
|
+
exports.useSpeech = useSpeech;
|
|
239
|
+
//# sourceMappingURL=index.js.map
|
|
240
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/hooks/useChat.ts","../../src/hooks/useSpeech.ts"],"names":["useState","useRef","useCallback","useEffect"],"mappings":"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","file":"index.js","sourcesContent":["'use client';\n\nimport { useState, useCallback, useRef } from 'react';\nimport type { ChatMessage, ToolExecution, StreamEvent } from '../types';\n\nexport interface UseChatOptions {\n /** API endpoint for chat */\n apiEndpoint: string;\n /** Initial messages */\n initialMessages?: ChatMessage[];\n /** Callback when a tool is called */\n onToolCall?: (toolName: string, args: Record<string, unknown>) => void | Promise<void>;\n /** Callback when streaming starts */\n onStreamStart?: () => void;\n /** Callback when streaming ends */\n onStreamEnd?: () => void;\n}\n\nexport interface UseChatReturn {\n /** All messages in the conversation */\n messages: ChatMessage[];\n /** Current input value */\n input: string;\n /** Set the input value */\n setInput: (value: string) => void;\n /** Whether the assistant is currently responding */\n isLoading: boolean;\n /** ID of the currently streaming message */\n streamingMessageId: string | null;\n /** Send a message */\n sendMessage: (content?: string) => Promise<void>;\n /** Clear all messages */\n clearMessages: () => void;\n /** Add a message manually */\n addMessage: (message: Omit<ChatMessage, 'id' | 'timestamp'>) => void;\n}\n\n/**\n * Hook for managing chat state and streaming\n */\nexport function useChat(options: UseChatOptions): UseChatReturn {\n const { apiEndpoint, initialMessages = [], onToolCall, onStreamStart, onStreamEnd } = options;\n\n const [messages, setMessages] = useState<ChatMessage[]>(initialMessages);\n const [input, setInput] = useState('');\n const [isLoading, setIsLoading] = useState(false);\n const [streamingMessageId, setStreamingMessageId] = useState<string | null>(null);\n\n const abortControllerRef = useRef<AbortController | null>(null);\n\n const addMessage = useCallback((message: Omit<ChatMessage, 'id' | 'timestamp'>) => {\n const newMessage: ChatMessage = {\n ...message,\n id: Date.now().toString(),\n timestamp: new Date(),\n };\n setMessages((prev) => [...prev, newMessage]);\n return newMessage;\n }, []);\n\n const clearMessages = useCallback(() => {\n setMessages(initialMessages);\n }, [initialMessages]);\n\n const sendMessage = useCallback(\n async (content?: string) => {\n const messageContent = content || input;\n if (!messageContent.trim() || isLoading) return;\n\n // Cancel any ongoing request\n if (abortControllerRef.current) {\n abortControllerRef.current.abort();\n }\n abortControllerRef.current = new AbortController();\n\n // Add user message\n const userMessage: ChatMessage = {\n id: Date.now().toString(),\n role: 'user',\n content: messageContent,\n timestamp: new Date(),\n };\n setMessages((prev) => [...prev, userMessage]);\n setInput('');\n setIsLoading(true);\n onStreamStart?.();\n\n // Add placeholder for assistant message\n const assistantMessageId = (Date.now() + 1).toString();\n const assistantMessage: ChatMessage = {\n id: assistantMessageId,\n role: 'assistant',\n content: '',\n timestamp: new Date(),\n toolCalls: [],\n };\n setMessages((prev) => [...prev, assistantMessage]);\n setStreamingMessageId(assistantMessageId);\n\n let fullText = '';\n const toolCalls: ToolExecution[] = [];\n\n try {\n // Prepare messages for API (convert to format expected by API)\n const apiMessages = messages.concat(userMessage).map((m) => ({\n role: m.role,\n content: m.content,\n }));\n\n const response = await fetch(apiEndpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ messages: apiMessages }),\n signal: abortControllerRef.current.signal,\n });\n\n if (!response.ok) {\n throw new Error('Failed to get response');\n }\n\n const reader = response.body?.getReader();\n if (!reader) throw new Error('No reader available');\n\n const decoder = new TextDecoder();\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n const text = decoder.decode(value);\n const lines = text.split('\\n');\n\n for (const line of lines) {\n if (line.startsWith('data: ')) {\n try {\n const data = JSON.parse(line.slice(6)) as StreamEvent;\n\n if (data.type === 'text') {\n fullText += data.content;\n setMessages((prev) =>\n prev.map((m) =>\n m.id === assistantMessageId ? { ...m, content: fullText } : m\n )\n );\n } else if (data.type === 'tool') {\n const toolCall = { name: data.name, args: data.args };\n toolCalls.push(toolCall);\n\n // Execute tool call callback\n if (onToolCall) {\n try {\n await onToolCall(data.name, data.args);\n } catch (e) {\n console.error('Tool execution error:', e);\n }\n }\n } else if (data.type === 'done') {\n // Update message with tool calls\n if (toolCalls.length > 0) {\n setMessages((prev) =>\n prev.map((m) =>\n m.id === assistantMessageId ? { ...m, toolCalls } : m\n )\n );\n }\n\n // If no text, show default message\n if (!fullText) {\n setMessages((prev) =>\n prev.map((m) =>\n m.id === assistantMessageId\n ? { ...m, content: \"I've made some changes. Take a look!\" }\n : m\n )\n );\n }\n } else if (data.type === 'error') {\n throw new Error(data.message);\n }\n } catch {\n // Skip invalid JSON lines\n }\n }\n }\n }\n } catch (error) {\n if (error instanceof Error && error.name === 'AbortError') {\n // Request was cancelled, ignore\n return;\n }\n\n console.error('Chat error:', error);\n setMessages((prev) =>\n prev.map((m) =>\n m.id === assistantMessageId\n ? { ...m, content: 'Sorry, I encountered an error. Please try again.' }\n : m\n )\n );\n } finally {\n setIsLoading(false);\n setStreamingMessageId(null);\n onStreamEnd?.();\n }\n },\n [apiEndpoint, input, isLoading, messages, onToolCall, onStreamStart, onStreamEnd]\n );\n\n return {\n messages,\n input,\n setInput,\n isLoading,\n streamingMessageId,\n sendMessage,\n clearMessages,\n addMessage,\n };\n}\n","'use client';\n\nimport { useState, useRef, useEffect, useCallback } from 'react';\n\n// Web Speech API types\ninterface SpeechRecognitionEvent extends Event {\n results: SpeechRecognitionResultList;\n}\n\ninterface SpeechRecognitionInstance {\n continuous: boolean;\n interimResults: boolean;\n lang: string;\n start(): void;\n stop(): void;\n onresult: ((event: SpeechRecognitionEvent) => void) | null;\n onend: (() => void) | null;\n onerror: (() => void) | null;\n}\n\nexport interface UseSpeechOptions {\n /** Language for speech recognition */\n lang?: string;\n /** Callback when speech is recognized */\n onResult?: (transcript: string, isFinal: boolean) => void;\n}\n\nexport interface UseSpeechReturn {\n /** Whether speech recognition is supported */\n isSupported: boolean;\n /** Whether currently listening */\n isListening: boolean;\n /** Toggle listening on/off */\n toggleListening: () => void;\n /** Start listening */\n startListening: () => void;\n /** Stop listening */\n stopListening: () => void;\n /** Speak text using TTS */\n speak: (text: string) => void;\n /** Whether TTS is enabled */\n ttsEnabled: boolean;\n /** Toggle TTS on/off */\n setTtsEnabled: (enabled: boolean) => void;\n /** Cancel current speech */\n cancelSpeech: () => void;\n}\n\n/**\n * Hook for speech recognition and text-to-speech\n */\nexport function useSpeech(options: UseSpeechOptions = {}): UseSpeechReturn {\n const { lang = 'en-US', onResult } = options;\n\n const [isSupported, setIsSupported] = useState(false);\n const [isListening, setIsListening] = useState(false);\n const [ttsEnabled, setTtsEnabled] = useState(false);\n\n const recognitionRef = useRef<SpeechRecognitionInstance | null>(null);\n\n // Initialize speech recognition\n useEffect(() => {\n if (typeof window === 'undefined') return;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const windowWithSpeech = window as any;\n const SpeechRecognitionAPI =\n windowWithSpeech.SpeechRecognition || windowWithSpeech.webkitSpeechRecognition;\n\n if (SpeechRecognitionAPI) {\n setIsSupported(true);\n recognitionRef.current = new SpeechRecognitionAPI() as SpeechRecognitionInstance;\n recognitionRef.current.continuous = false;\n recognitionRef.current.interimResults = true;\n recognitionRef.current.lang = lang;\n\n recognitionRef.current.onresult = (event) => {\n const result = event.results[0];\n const transcript = result[0].transcript;\n onResult?.(transcript, result.isFinal);\n\n if (result.isFinal) {\n setIsListening(false);\n }\n };\n\n recognitionRef.current.onend = () => setIsListening(false);\n recognitionRef.current.onerror = () => setIsListening(false);\n }\n }, [lang, onResult]);\n\n const startListening = useCallback(() => {\n if (!recognitionRef.current) return;\n try {\n recognitionRef.current.start();\n setIsListening(true);\n } catch (e) {\n console.error('Speech recognition error:', e);\n }\n }, []);\n\n const stopListening = useCallback(() => {\n if (!recognitionRef.current) return;\n recognitionRef.current.stop();\n setIsListening(false);\n }, []);\n\n const toggleListening = useCallback(() => {\n if (isListening) {\n stopListening();\n } else {\n startListening();\n }\n }, [isListening, startListening, stopListening]);\n\n const speak = useCallback(\n (text: string) => {\n if (!ttsEnabled || typeof window === 'undefined' || !('speechSynthesis' in window)) {\n return;\n }\n\n speechSynthesis.cancel();\n const utterance = new SpeechSynthesisUtterance(text);\n utterance.rate = 1.0;\n utterance.pitch = 1.0;\n\n // Try to find a good voice\n const voices = speechSynthesis.getVoices();\n const preferredVoice = voices.find(\n (v) => v.name.includes('Google') || v.name.includes('Samantha') || v.name.includes('Alex')\n );\n if (preferredVoice) {\n utterance.voice = preferredVoice;\n }\n\n speechSynthesis.speak(utterance);\n },\n [ttsEnabled]\n );\n\n const cancelSpeech = useCallback(() => {\n if (typeof window !== 'undefined' && 'speechSynthesis' in window) {\n speechSynthesis.cancel();\n }\n }, []);\n\n return {\n isSupported,\n isListening,\n toggleListening,\n startListening,\n stopListening,\n speak,\n ttsEnabled,\n setTtsEnabled,\n cancelSpeech,\n };\n}\n"]}
|