ai-site-pilot 0.2.3 → 0.3.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 +131 -2
- package/dist/api/index.d.mts +39 -1
- package/dist/api/index.d.ts +39 -1
- package/dist/api/index.js +98 -0
- package/dist/api/index.js.map +1 -1
- package/dist/api/index.mjs +98 -1
- package/dist/api/index.mjs.map +1 -1
- package/dist/styles.css +11 -0
- package/package.json +14 -3
package/README.md
CHANGED
|
@@ -40,12 +40,51 @@ The AI doesn't automatically know your site structure—you teach it via the sys
|
|
|
40
40
|
|
|
41
41
|
```bash
|
|
42
42
|
npm install ai-site-pilot
|
|
43
|
+
|
|
44
|
+
# Choose your backend (pick ONE):
|
|
45
|
+
npm install @google/genai # Gemini (recommended, no AI SDK needed)
|
|
46
|
+
npm install ai @ai-sdk/google # Vercel AI SDK with Gemini
|
|
47
|
+
npm install ai @ai-sdk/openai # Vercel AI SDK with OpenAI
|
|
43
48
|
```
|
|
44
49
|
|
|
45
50
|
## Quick Start
|
|
46
51
|
|
|
47
52
|
### 1. Create the API Route
|
|
48
53
|
|
|
54
|
+
**Option A: Gemini Direct (Recommended)** - No Vercel AI SDK needed!
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
// app/api/chat/route.ts
|
|
58
|
+
import { createGeminiHandler } from 'ai-site-pilot/api';
|
|
59
|
+
import { defineTool } from 'ai-site-pilot/tools';
|
|
60
|
+
|
|
61
|
+
const navigateTool = defineTool({
|
|
62
|
+
name: 'navigate',
|
|
63
|
+
description: 'Navigate to a section of the page',
|
|
64
|
+
parameters: {
|
|
65
|
+
type: 'object',
|
|
66
|
+
properties: {
|
|
67
|
+
section: {
|
|
68
|
+
type: 'string',
|
|
69
|
+
description: 'Section to navigate to',
|
|
70
|
+
enum: ['home', 'products', 'about', 'contact'],
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
required: ['section'],
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
export const POST = createGeminiHandler({
|
|
78
|
+
apiKey: process.env.GOOGLE_GENERATIVE_AI_API_KEY,
|
|
79
|
+
model: 'gemini-2.0-flash',
|
|
80
|
+
systemPrompt: `You are a helpful assistant for our website.
|
|
81
|
+
You can navigate users to different sections using the navigate tool.`,
|
|
82
|
+
tools: [navigateTool],
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Option B: Vercel AI SDK** - Works with any AI SDK provider
|
|
87
|
+
|
|
49
88
|
```typescript
|
|
50
89
|
// app/api/chat/route.ts
|
|
51
90
|
import { createChatHandler } from 'ai-site-pilot/api';
|
|
@@ -152,9 +191,25 @@ interface SitePilotFeatures {
|
|
|
152
191
|
}
|
|
153
192
|
```
|
|
154
193
|
|
|
194
|
+
### `createGeminiHandler()`
|
|
195
|
+
|
|
196
|
+
Factory for creating Next.js API route handlers using Gemini directly (no AI SDK needed).
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
import { createGeminiHandler } from 'ai-site-pilot/api';
|
|
200
|
+
|
|
201
|
+
export const POST = createGeminiHandler({
|
|
202
|
+
apiKey: process.env.GOOGLE_GENERATIVE_AI_API_KEY, // Optional, uses env var by default
|
|
203
|
+
model: 'gemini-2.0-flash', // Default: gemini-2.0-flash
|
|
204
|
+
systemPrompt: 'You are a helpful assistant...',
|
|
205
|
+
tools: [myTool1, myTool2],
|
|
206
|
+
temperature: 0.7,
|
|
207
|
+
});
|
|
208
|
+
```
|
|
209
|
+
|
|
155
210
|
### `createChatHandler()`
|
|
156
211
|
|
|
157
|
-
Factory for creating Next.js API route handlers.
|
|
212
|
+
Factory for creating Next.js API route handlers using Vercel AI SDK.
|
|
158
213
|
|
|
159
214
|
```typescript
|
|
160
215
|
import { createChatHandler } from 'ai-site-pilot/api';
|
|
@@ -318,11 +373,85 @@ const tools = [
|
|
|
318
373
|
];
|
|
319
374
|
```
|
|
320
375
|
|
|
376
|
+
## Custom API Implementation
|
|
377
|
+
|
|
378
|
+
If you need to implement your own API route (e.g., using a different AI provider), the widget expects Server-Sent Events (SSE) in this format:
|
|
379
|
+
|
|
380
|
+
### SSE Streaming Format
|
|
381
|
+
|
|
382
|
+
```
|
|
383
|
+
data: {"type":"text","content":"Hello, "}
|
|
384
|
+
|
|
385
|
+
data: {"type":"text","content":"how can I help?"}
|
|
386
|
+
|
|
387
|
+
data: {"type":"tool","name":"navigate","args":{"section":"products"}}
|
|
388
|
+
|
|
389
|
+
data: {"type":"done"}
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
#### Event Types
|
|
393
|
+
|
|
394
|
+
| Type | Description | Example |
|
|
395
|
+
|------|-------------|---------|
|
|
396
|
+
| `text` | Streaming text chunk | `{"type":"text","content":"Hello"}` |
|
|
397
|
+
| `tool` | Tool/function call | `{"type":"tool","name":"navigate","args":{"section":"home"}}` |
|
|
398
|
+
| `done` | Stream complete | `{"type":"done"}` |
|
|
399
|
+
| `error` | Error occurred | `{"type":"error","message":"Something went wrong"}` |
|
|
400
|
+
|
|
401
|
+
### SSE Utilities
|
|
402
|
+
|
|
403
|
+
Use the built-in SSE helpers for custom implementations:
|
|
404
|
+
|
|
405
|
+
```typescript
|
|
406
|
+
import { createSSEEncoder, getSSEHeaders } from 'ai-site-pilot/api';
|
|
407
|
+
|
|
408
|
+
export async function POST(req: Request) {
|
|
409
|
+
const sse = createSSEEncoder();
|
|
410
|
+
|
|
411
|
+
const stream = new ReadableStream({
|
|
412
|
+
async start(controller) {
|
|
413
|
+
// Stream text
|
|
414
|
+
controller.enqueue(sse.encodeText('Hello!'));
|
|
415
|
+
|
|
416
|
+
// Send tool call
|
|
417
|
+
controller.enqueue(sse.encodeTool('navigate', { section: 'products' }));
|
|
418
|
+
|
|
419
|
+
// Done
|
|
420
|
+
controller.enqueue(sse.encodeDone());
|
|
421
|
+
controller.close();
|
|
422
|
+
},
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
return new Response(stream, { headers: getSSEHeaders() });
|
|
426
|
+
}
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
## Handling Tool-Only Responses
|
|
430
|
+
|
|
431
|
+
When the AI calls tools without providing text, you can customize the fallback message:
|
|
432
|
+
|
|
433
|
+
```typescript
|
|
434
|
+
import { SitePilot, createFallbackMessageGenerator } from 'ai-site-pilot';
|
|
435
|
+
|
|
436
|
+
const generateFallback = createFallbackMessageGenerator({
|
|
437
|
+
navigate: (args) => `Scrolled to **${args.section}** section.`,
|
|
438
|
+
filter_products: (args) => `Showing **${args.category}** products.`,
|
|
439
|
+
search: (args) => `Found results for "${args.query}".`,
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
<SitePilot
|
|
443
|
+
apiEndpoint="/api/chat"
|
|
444
|
+
generateFallbackMessage={generateFallback}
|
|
445
|
+
/>
|
|
446
|
+
```
|
|
447
|
+
|
|
321
448
|
## Requirements
|
|
322
449
|
|
|
323
450
|
- React 18+ or React 19
|
|
324
451
|
- Next.js 13+ (for API routes)
|
|
325
|
-
-
|
|
452
|
+
- One of:
|
|
453
|
+
- `@google/genai` (Gemini direct - recommended)
|
|
454
|
+
- `ai` + provider package (Vercel AI SDK)
|
|
326
455
|
|
|
327
456
|
## License
|
|
328
457
|
|
package/dist/api/index.d.mts
CHANGED
|
@@ -52,6 +52,44 @@ interface ChatHandlerConfig {
|
|
|
52
52
|
*/
|
|
53
53
|
declare function createChatHandler(config: ChatHandlerConfig): (req: Request) => Promise<Response>;
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Gemini-specific handler using @google/genai directly
|
|
57
|
+
* No Vercel AI SDK required!
|
|
58
|
+
*/
|
|
59
|
+
|
|
60
|
+
interface GeminiHandlerConfig {
|
|
61
|
+
/** Google AI API key (or set GOOGLE_GENERATIVE_AI_API_KEY env var) */
|
|
62
|
+
apiKey?: string;
|
|
63
|
+
/** Gemini model to use (default: gemini-2.0-flash) */
|
|
64
|
+
model?: string;
|
|
65
|
+
/** System prompt for the AI */
|
|
66
|
+
systemPrompt: string;
|
|
67
|
+
/** Tool definitions for the AI */
|
|
68
|
+
tools?: ToolDefinition[];
|
|
69
|
+
/** Temperature for response generation (0-1) */
|
|
70
|
+
temperature?: number;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Create a Next.js API route handler for Gemini
|
|
74
|
+
*
|
|
75
|
+
* Uses @google/genai directly - no Vercel AI SDK required!
|
|
76
|
+
* Works with Gemini 2.0+ models.
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```ts
|
|
80
|
+
* // app/api/chat/route.ts
|
|
81
|
+
* import { createGeminiHandler } from 'ai-site-pilot/api';
|
|
82
|
+
*
|
|
83
|
+
* export const POST = createGeminiHandler({
|
|
84
|
+
* apiKey: process.env.GOOGLE_GENERATIVE_AI_API_KEY,
|
|
85
|
+
* model: 'gemini-2.0-flash',
|
|
86
|
+
* systemPrompt: 'You are a helpful assistant...',
|
|
87
|
+
* tools: myTools,
|
|
88
|
+
* });
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
declare function createGeminiHandler(config: GeminiHandlerConfig): (req: Request) => Promise<Response>;
|
|
92
|
+
|
|
55
93
|
/**
|
|
56
94
|
* SSE streaming utilities
|
|
57
95
|
*/
|
|
@@ -75,4 +113,4 @@ declare function getSSEHeaders(): HeadersInit;
|
|
|
75
113
|
*/
|
|
76
114
|
declare function parseSSEStream(reader: ReadableStreamDefaultReader<Uint8Array>): AsyncGenerator<StreamEvent>;
|
|
77
115
|
|
|
78
|
-
export { type ChatHandlerConfig, createChatHandler, createSSEEncoder, getSSEHeaders, parseSSEStream };
|
|
116
|
+
export { type ChatHandlerConfig, type GeminiHandlerConfig, createChatHandler, createGeminiHandler, createSSEEncoder, getSSEHeaders, parseSSEStream };
|
package/dist/api/index.d.ts
CHANGED
|
@@ -52,6 +52,44 @@ interface ChatHandlerConfig {
|
|
|
52
52
|
*/
|
|
53
53
|
declare function createChatHandler(config: ChatHandlerConfig): (req: Request) => Promise<Response>;
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Gemini-specific handler using @google/genai directly
|
|
57
|
+
* No Vercel AI SDK required!
|
|
58
|
+
*/
|
|
59
|
+
|
|
60
|
+
interface GeminiHandlerConfig {
|
|
61
|
+
/** Google AI API key (or set GOOGLE_GENERATIVE_AI_API_KEY env var) */
|
|
62
|
+
apiKey?: string;
|
|
63
|
+
/** Gemini model to use (default: gemini-2.0-flash) */
|
|
64
|
+
model?: string;
|
|
65
|
+
/** System prompt for the AI */
|
|
66
|
+
systemPrompt: string;
|
|
67
|
+
/** Tool definitions for the AI */
|
|
68
|
+
tools?: ToolDefinition[];
|
|
69
|
+
/** Temperature for response generation (0-1) */
|
|
70
|
+
temperature?: number;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Create a Next.js API route handler for Gemini
|
|
74
|
+
*
|
|
75
|
+
* Uses @google/genai directly - no Vercel AI SDK required!
|
|
76
|
+
* Works with Gemini 2.0+ models.
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```ts
|
|
80
|
+
* // app/api/chat/route.ts
|
|
81
|
+
* import { createGeminiHandler } from 'ai-site-pilot/api';
|
|
82
|
+
*
|
|
83
|
+
* export const POST = createGeminiHandler({
|
|
84
|
+
* apiKey: process.env.GOOGLE_GENERATIVE_AI_API_KEY,
|
|
85
|
+
* model: 'gemini-2.0-flash',
|
|
86
|
+
* systemPrompt: 'You are a helpful assistant...',
|
|
87
|
+
* tools: myTools,
|
|
88
|
+
* });
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
declare function createGeminiHandler(config: GeminiHandlerConfig): (req: Request) => Promise<Response>;
|
|
92
|
+
|
|
55
93
|
/**
|
|
56
94
|
* SSE streaming utilities
|
|
57
95
|
*/
|
|
@@ -75,4 +113,4 @@ declare function getSSEHeaders(): HeadersInit;
|
|
|
75
113
|
*/
|
|
76
114
|
declare function parseSSEStream(reader: ReadableStreamDefaultReader<Uint8Array>): AsyncGenerator<StreamEvent>;
|
|
77
115
|
|
|
78
|
-
export { type ChatHandlerConfig, createChatHandler, createSSEEncoder, getSSEHeaders, parseSSEStream };
|
|
116
|
+
export { type ChatHandlerConfig, type GeminiHandlerConfig, createChatHandler, createGeminiHandler, createSSEEncoder, getSSEHeaders, parseSSEStream };
|
package/dist/api/index.js
CHANGED
|
@@ -134,7 +134,105 @@ function createChatHandler(config) {
|
|
|
134
134
|
};
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
+
// src/api/createGeminiHandler.ts
|
|
138
|
+
function convertToolsToGemini(tools) {
|
|
139
|
+
return tools.map((tool) => ({
|
|
140
|
+
name: tool.name,
|
|
141
|
+
description: tool.description,
|
|
142
|
+
parameters: {
|
|
143
|
+
type: "object",
|
|
144
|
+
properties: Object.fromEntries(
|
|
145
|
+
Object.entries(tool.parameters.properties).map(([key, value]) => [
|
|
146
|
+
key,
|
|
147
|
+
{
|
|
148
|
+
type: value.type,
|
|
149
|
+
description: value.description,
|
|
150
|
+
enum: value.enum
|
|
151
|
+
}
|
|
152
|
+
])
|
|
153
|
+
),
|
|
154
|
+
required: tool.parameters.required
|
|
155
|
+
}
|
|
156
|
+
}));
|
|
157
|
+
}
|
|
158
|
+
function createGeminiHandler(config) {
|
|
159
|
+
const {
|
|
160
|
+
apiKey = process.env.GOOGLE_GENERATIVE_AI_API_KEY,
|
|
161
|
+
model = "gemini-2.0-flash",
|
|
162
|
+
systemPrompt,
|
|
163
|
+
tools = [],
|
|
164
|
+
temperature = 0.7
|
|
165
|
+
} = config;
|
|
166
|
+
return async function POST(req) {
|
|
167
|
+
if (!apiKey) {
|
|
168
|
+
return new Response(
|
|
169
|
+
JSON.stringify({ error: "Google AI API key not configured" }),
|
|
170
|
+
{ status: 500, headers: { "Content-Type": "application/json" } }
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
try {
|
|
174
|
+
const { GoogleGenAI } = await import('@google/genai');
|
|
175
|
+
const ai = new GoogleGenAI({ apiKey });
|
|
176
|
+
const body = await req.json();
|
|
177
|
+
const { messages } = body;
|
|
178
|
+
const geminiMessages = messages.map((m) => ({
|
|
179
|
+
role: m.role === "assistant" ? "model" : "user",
|
|
180
|
+
parts: [{ text: m.content }]
|
|
181
|
+
}));
|
|
182
|
+
const sse = createSSEEncoder();
|
|
183
|
+
const stream = new ReadableStream({
|
|
184
|
+
async start(controller) {
|
|
185
|
+
try {
|
|
186
|
+
const response = await ai.models.generateContentStream({
|
|
187
|
+
model,
|
|
188
|
+
contents: geminiMessages,
|
|
189
|
+
config: {
|
|
190
|
+
systemInstruction: systemPrompt,
|
|
191
|
+
temperature,
|
|
192
|
+
tools: tools.length > 0 ? [{
|
|
193
|
+
functionDeclarations: convertToolsToGemini(tools)
|
|
194
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
195
|
+
}] : void 0
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
for await (const chunk of response) {
|
|
199
|
+
const parts = chunk.candidates?.[0]?.content?.parts || [];
|
|
200
|
+
for (const part of parts) {
|
|
201
|
+
if ("text" in part && part.text) {
|
|
202
|
+
controller.enqueue(sse.encodeText(part.text));
|
|
203
|
+
}
|
|
204
|
+
if ("functionCall" in part && part.functionCall) {
|
|
205
|
+
controller.enqueue(
|
|
206
|
+
sse.encodeTool(
|
|
207
|
+
part.functionCall.name,
|
|
208
|
+
part.functionCall.args
|
|
209
|
+
)
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
controller.enqueue(sse.encodeDone());
|
|
215
|
+
controller.close();
|
|
216
|
+
} catch (error) {
|
|
217
|
+
console.error("Gemini streaming error:", error);
|
|
218
|
+
controller.enqueue(sse.encodeError("An error occurred during streaming"));
|
|
219
|
+
controller.close();
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
return new Response(stream, { headers: getSSEHeaders() });
|
|
224
|
+
} catch (error) {
|
|
225
|
+
console.error("Gemini handler error:", error);
|
|
226
|
+
return new Response(
|
|
227
|
+
JSON.stringify({ error: "Internal server error" }),
|
|
228
|
+
{ status: 500, headers: { "Content-Type": "application/json" } }
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
137
234
|
exports.createChatHandler = createChatHandler;
|
|
235
|
+
exports.createGeminiHandler = createGeminiHandler;
|
|
138
236
|
exports.createSSEEncoder = createSSEEncoder;
|
|
139
237
|
exports.getSSEHeaders = getSSEHeaders;
|
|
140
238
|
exports.parseSSEStream = parseSSEStream;
|
package/dist/api/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/api/streaming.ts","../../src/api/createChatHandler.ts"],"names":["streamText"],"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,SAASA,aAAA,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.js","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"]}
|
|
1
|
+
{"version":3,"sources":["../../src/api/streaming.ts","../../src/api/createChatHandler.ts","../../src/api/createGeminiHandler.ts"],"names":["streamText"],"mappings":"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","file":"index.js","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","/**\n * Gemini-specific handler using @google/genai directly\n * No Vercel AI SDK required!\n */\n\nimport type { ToolDefinition } from '../tools/types';\nimport { createSSEEncoder, getSSEHeaders } from './streaming';\n\nexport interface GeminiHandlerConfig {\n /** Google AI API key (or set GOOGLE_GENERATIVE_AI_API_KEY env var) */\n apiKey?: string;\n /** Gemini model to use (default: gemini-2.0-flash) */\n model?: string;\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}\n\ninterface RequestBody {\n messages: Array<{ role: 'user' | 'assistant'; content: string }>;\n}\n\n/**\n * Convert tool definitions to Gemini function declaration format\n * Uses 'as unknown' to handle SDK type differences across versions\n */\nfunction convertToolsToGemini(tools: ToolDefinition[]): unknown[] {\n return tools.map(tool => ({\n name: tool.name,\n description: tool.description,\n parameters: {\n type: 'object',\n properties: Object.fromEntries(\n Object.entries(tool.parameters.properties).map(([key, value]) => [\n key,\n {\n type: value.type,\n description: value.description,\n enum: value.enum,\n },\n ])\n ),\n required: tool.parameters.required,\n },\n }));\n}\n\n/**\n * Create a Next.js API route handler for Gemini\n *\n * Uses @google/genai directly - no Vercel AI SDK required!\n * Works with Gemini 2.0+ models.\n *\n * @example\n * ```ts\n * // app/api/chat/route.ts\n * import { createGeminiHandler } from 'ai-site-pilot/api';\n *\n * export const POST = createGeminiHandler({\n * apiKey: process.env.GOOGLE_GENERATIVE_AI_API_KEY,\n * model: 'gemini-2.0-flash',\n * systemPrompt: 'You are a helpful assistant...',\n * tools: myTools,\n * });\n * ```\n */\nexport function createGeminiHandler(config: GeminiHandlerConfig) {\n const {\n apiKey = process.env.GOOGLE_GENERATIVE_AI_API_KEY,\n model = 'gemini-2.0-flash',\n systemPrompt,\n tools = [],\n temperature = 0.7,\n } = config;\n\n return async function POST(req: Request): Promise<Response> {\n if (!apiKey) {\n return new Response(\n JSON.stringify({ error: 'Google AI API key not configured' }),\n { status: 500, headers: { 'Content-Type': 'application/json' } }\n );\n }\n\n try {\n // Dynamic import to avoid bundling issues\n const { GoogleGenAI } = await import('@google/genai');\n const ai = new GoogleGenAI({ apiKey });\n\n const body = (await req.json()) as RequestBody;\n const { messages } = body;\n\n // Convert messages to Gemini format\n const geminiMessages = messages.map((m) => ({\n role: m.role === 'assistant' ? 'model' as const : 'user' as const,\n parts: [{ text: m.content }],\n }));\n\n const sse = createSSEEncoder();\n\n const stream = new ReadableStream({\n async start(controller) {\n try {\n const response = await ai.models.generateContentStream({\n model,\n contents: geminiMessages,\n config: {\n systemInstruction: systemPrompt,\n temperature,\n tools: tools.length > 0 ? [{\n functionDeclarations: convertToolsToGemini(tools),\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n }] as any : undefined,\n },\n });\n\n for await (const chunk of response) {\n const parts = chunk.candidates?.[0]?.content?.parts || [];\n\n for (const part of parts) {\n // Handle text chunks\n if ('text' in part && part.text) {\n controller.enqueue(sse.encodeText(part.text));\n }\n\n // Handle function calls\n if ('functionCall' in part && part.functionCall) {\n controller.enqueue(\n sse.encodeTool(\n part.functionCall.name as string,\n part.functionCall.args as Record<string, unknown>\n )\n );\n }\n }\n }\n\n controller.enqueue(sse.encodeDone());\n controller.close();\n } catch (error) {\n console.error('Gemini 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, { headers: getSSEHeaders() });\n } catch (error) {\n console.error('Gemini handler error:', error);\n return new Response(\n JSON.stringify({ error: 'Internal server error' }),\n { status: 500, headers: { 'Content-Type': 'application/json' } }\n );\n }\n };\n}\n"]}
|
package/dist/api/index.mjs
CHANGED
|
@@ -132,6 +132,103 @@ function createChatHandler(config) {
|
|
|
132
132
|
};
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
-
|
|
135
|
+
// src/api/createGeminiHandler.ts
|
|
136
|
+
function convertToolsToGemini(tools) {
|
|
137
|
+
return tools.map((tool) => ({
|
|
138
|
+
name: tool.name,
|
|
139
|
+
description: tool.description,
|
|
140
|
+
parameters: {
|
|
141
|
+
type: "object",
|
|
142
|
+
properties: Object.fromEntries(
|
|
143
|
+
Object.entries(tool.parameters.properties).map(([key, value]) => [
|
|
144
|
+
key,
|
|
145
|
+
{
|
|
146
|
+
type: value.type,
|
|
147
|
+
description: value.description,
|
|
148
|
+
enum: value.enum
|
|
149
|
+
}
|
|
150
|
+
])
|
|
151
|
+
),
|
|
152
|
+
required: tool.parameters.required
|
|
153
|
+
}
|
|
154
|
+
}));
|
|
155
|
+
}
|
|
156
|
+
function createGeminiHandler(config) {
|
|
157
|
+
const {
|
|
158
|
+
apiKey = process.env.GOOGLE_GENERATIVE_AI_API_KEY,
|
|
159
|
+
model = "gemini-2.0-flash",
|
|
160
|
+
systemPrompt,
|
|
161
|
+
tools = [],
|
|
162
|
+
temperature = 0.7
|
|
163
|
+
} = config;
|
|
164
|
+
return async function POST(req) {
|
|
165
|
+
if (!apiKey) {
|
|
166
|
+
return new Response(
|
|
167
|
+
JSON.stringify({ error: "Google AI API key not configured" }),
|
|
168
|
+
{ status: 500, headers: { "Content-Type": "application/json" } }
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
try {
|
|
172
|
+
const { GoogleGenAI } = await import('@google/genai');
|
|
173
|
+
const ai = new GoogleGenAI({ apiKey });
|
|
174
|
+
const body = await req.json();
|
|
175
|
+
const { messages } = body;
|
|
176
|
+
const geminiMessages = messages.map((m) => ({
|
|
177
|
+
role: m.role === "assistant" ? "model" : "user",
|
|
178
|
+
parts: [{ text: m.content }]
|
|
179
|
+
}));
|
|
180
|
+
const sse = createSSEEncoder();
|
|
181
|
+
const stream = new ReadableStream({
|
|
182
|
+
async start(controller) {
|
|
183
|
+
try {
|
|
184
|
+
const response = await ai.models.generateContentStream({
|
|
185
|
+
model,
|
|
186
|
+
contents: geminiMessages,
|
|
187
|
+
config: {
|
|
188
|
+
systemInstruction: systemPrompt,
|
|
189
|
+
temperature,
|
|
190
|
+
tools: tools.length > 0 ? [{
|
|
191
|
+
functionDeclarations: convertToolsToGemini(tools)
|
|
192
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
193
|
+
}] : void 0
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
for await (const chunk of response) {
|
|
197
|
+
const parts = chunk.candidates?.[0]?.content?.parts || [];
|
|
198
|
+
for (const part of parts) {
|
|
199
|
+
if ("text" in part && part.text) {
|
|
200
|
+
controller.enqueue(sse.encodeText(part.text));
|
|
201
|
+
}
|
|
202
|
+
if ("functionCall" in part && part.functionCall) {
|
|
203
|
+
controller.enqueue(
|
|
204
|
+
sse.encodeTool(
|
|
205
|
+
part.functionCall.name,
|
|
206
|
+
part.functionCall.args
|
|
207
|
+
)
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
controller.enqueue(sse.encodeDone());
|
|
213
|
+
controller.close();
|
|
214
|
+
} catch (error) {
|
|
215
|
+
console.error("Gemini streaming error:", error);
|
|
216
|
+
controller.enqueue(sse.encodeError("An error occurred during streaming"));
|
|
217
|
+
controller.close();
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
return new Response(stream, { headers: getSSEHeaders() });
|
|
222
|
+
} catch (error) {
|
|
223
|
+
console.error("Gemini handler error:", error);
|
|
224
|
+
return new Response(
|
|
225
|
+
JSON.stringify({ error: "Internal server error" }),
|
|
226
|
+
{ status: 500, headers: { "Content-Type": "application/json" } }
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export { createChatHandler, createGeminiHandler, createSSEEncoder, getSSEHeaders, parseSSEStream };
|
|
136
233
|
//# sourceMappingURL=index.mjs.map
|
|
137
234
|
//# sourceMappingURL=index.mjs.map
|
package/dist/api/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/api/streaming.ts","../../src/api/createChatHandler.ts"],"names":[],"mappings":"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","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"]}
|
|
1
|
+
{"version":3,"sources":["../../src/api/streaming.ts","../../src/api/createChatHandler.ts","../../src/api/createGeminiHandler.ts"],"names":[],"mappings":"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","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","/**\n * Gemini-specific handler using @google/genai directly\n * No Vercel AI SDK required!\n */\n\nimport type { ToolDefinition } from '../tools/types';\nimport { createSSEEncoder, getSSEHeaders } from './streaming';\n\nexport interface GeminiHandlerConfig {\n /** Google AI API key (or set GOOGLE_GENERATIVE_AI_API_KEY env var) */\n apiKey?: string;\n /** Gemini model to use (default: gemini-2.0-flash) */\n model?: string;\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}\n\ninterface RequestBody {\n messages: Array<{ role: 'user' | 'assistant'; content: string }>;\n}\n\n/**\n * Convert tool definitions to Gemini function declaration format\n * Uses 'as unknown' to handle SDK type differences across versions\n */\nfunction convertToolsToGemini(tools: ToolDefinition[]): unknown[] {\n return tools.map(tool => ({\n name: tool.name,\n description: tool.description,\n parameters: {\n type: 'object',\n properties: Object.fromEntries(\n Object.entries(tool.parameters.properties).map(([key, value]) => [\n key,\n {\n type: value.type,\n description: value.description,\n enum: value.enum,\n },\n ])\n ),\n required: tool.parameters.required,\n },\n }));\n}\n\n/**\n * Create a Next.js API route handler for Gemini\n *\n * Uses @google/genai directly - no Vercel AI SDK required!\n * Works with Gemini 2.0+ models.\n *\n * @example\n * ```ts\n * // app/api/chat/route.ts\n * import { createGeminiHandler } from 'ai-site-pilot/api';\n *\n * export const POST = createGeminiHandler({\n * apiKey: process.env.GOOGLE_GENERATIVE_AI_API_KEY,\n * model: 'gemini-2.0-flash',\n * systemPrompt: 'You are a helpful assistant...',\n * tools: myTools,\n * });\n * ```\n */\nexport function createGeminiHandler(config: GeminiHandlerConfig) {\n const {\n apiKey = process.env.GOOGLE_GENERATIVE_AI_API_KEY,\n model = 'gemini-2.0-flash',\n systemPrompt,\n tools = [],\n temperature = 0.7,\n } = config;\n\n return async function POST(req: Request): Promise<Response> {\n if (!apiKey) {\n return new Response(\n JSON.stringify({ error: 'Google AI API key not configured' }),\n { status: 500, headers: { 'Content-Type': 'application/json' } }\n );\n }\n\n try {\n // Dynamic import to avoid bundling issues\n const { GoogleGenAI } = await import('@google/genai');\n const ai = new GoogleGenAI({ apiKey });\n\n const body = (await req.json()) as RequestBody;\n const { messages } = body;\n\n // Convert messages to Gemini format\n const geminiMessages = messages.map((m) => ({\n role: m.role === 'assistant' ? 'model' as const : 'user' as const,\n parts: [{ text: m.content }],\n }));\n\n const sse = createSSEEncoder();\n\n const stream = new ReadableStream({\n async start(controller) {\n try {\n const response = await ai.models.generateContentStream({\n model,\n contents: geminiMessages,\n config: {\n systemInstruction: systemPrompt,\n temperature,\n tools: tools.length > 0 ? [{\n functionDeclarations: convertToolsToGemini(tools),\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n }] as any : undefined,\n },\n });\n\n for await (const chunk of response) {\n const parts = chunk.candidates?.[0]?.content?.parts || [];\n\n for (const part of parts) {\n // Handle text chunks\n if ('text' in part && part.text) {\n controller.enqueue(sse.encodeText(part.text));\n }\n\n // Handle function calls\n if ('functionCall' in part && part.functionCall) {\n controller.enqueue(\n sse.encodeTool(\n part.functionCall.name as string,\n part.functionCall.args as Record<string, unknown>\n )\n );\n }\n }\n }\n\n controller.enqueue(sse.encodeDone());\n controller.close();\n } catch (error) {\n console.error('Gemini 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, { headers: getSSEHeaders() });\n } catch (error) {\n console.error('Gemini handler error:', error);\n return new Response(\n JSON.stringify({ error: 'Internal server error' }),\n { status: 500, headers: { 'Content-Type': 'application/json' } }\n );\n }\n };\n}\n"]}
|
package/dist/styles.css
CHANGED
|
@@ -13,6 +13,12 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
.pilot-container {
|
|
16
|
+
/* Container should not interfere with fixed children */
|
|
17
|
+
position: relative;
|
|
18
|
+
z-index: 199;
|
|
19
|
+
pointer-events: none;
|
|
20
|
+
|
|
21
|
+
/* CSS custom properties for theming */
|
|
16
22
|
--pilot-bg: #0F0720;
|
|
17
23
|
--pilot-bg-95: rgba(15, 7, 32, 0.95);
|
|
18
24
|
--pilot-text: #ffffff;
|
|
@@ -31,6 +37,11 @@
|
|
|
31
37
|
--pilot-accent-glow-strong: hsla(var(--pilot-accent-h), var(--pilot-accent-s), var(--pilot-accent-l), 0.3);
|
|
32
38
|
}
|
|
33
39
|
|
|
40
|
+
/* Fixed children need pointer events enabled */
|
|
41
|
+
.pilot-container > * {
|
|
42
|
+
pointer-events: auto;
|
|
43
|
+
}
|
|
44
|
+
|
|
34
45
|
/* Panel */
|
|
35
46
|
.pilot-panel {
|
|
36
47
|
background: var(--pilot-bg-95);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-site-pilot",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "AI chat widget that can control and navigate your website. Full-stack solution with streaming, tool system, and polished UI.",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -68,18 +68,29 @@
|
|
|
68
68
|
},
|
|
69
69
|
"peerDependencies": {
|
|
70
70
|
"react": "^18.0.0 || ^19.0.0",
|
|
71
|
-
"react-dom": "^18.0.0 || ^19.0.0"
|
|
71
|
+
"react-dom": "^18.0.0 || ^19.0.0",
|
|
72
|
+
"ai": "^4.0.0 || ^5.0.0",
|
|
73
|
+
"@google/genai": ">=1.0.0"
|
|
74
|
+
},
|
|
75
|
+
"peerDependenciesMeta": {
|
|
76
|
+
"ai": {
|
|
77
|
+
"optional": true
|
|
78
|
+
},
|
|
79
|
+
"@google/genai": {
|
|
80
|
+
"optional": true
|
|
81
|
+
}
|
|
72
82
|
},
|
|
73
83
|
"dependencies": {
|
|
74
|
-
"ai": "^4.0.0",
|
|
75
84
|
"framer-motion": "^11.0.0",
|
|
76
85
|
"lucide-react": "^0.400.0",
|
|
77
86
|
"react-markdown": "^9.0.0"
|
|
78
87
|
},
|
|
79
88
|
"devDependencies": {
|
|
89
|
+
"@google/genai": "^1.0.0",
|
|
80
90
|
"@types/node": "^20.0.0",
|
|
81
91
|
"@types/react": "^18.0.0",
|
|
82
92
|
"@types/react-dom": "^18.0.0",
|
|
93
|
+
"ai": "^4.0.0",
|
|
83
94
|
"tsup": "^8.0.0",
|
|
84
95
|
"typescript": "^5.0.0"
|
|
85
96
|
}
|