ai-site-pilot 0.4.4 → 0.5.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 +133 -4
- package/dist/api/index.d.mts +81 -4
- package/dist/api/index.d.ts +81 -4
- package/dist/api/index.js +77 -1
- package/dist/api/index.js.map +1 -1
- package/dist/api/index.mjs +77 -2
- package/dist/api/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,6 +23,35 @@ Works with any AI model (Gemini, GPT-4, Claude, Llama) via [OpenRouter](https://
|
|
|
23
23
|
npm install ai-site-pilot
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
+
## Setup
|
|
27
|
+
|
|
28
|
+
### Tailwind CSS Configuration (Required)
|
|
29
|
+
|
|
30
|
+
If you're using Tailwind CSS, add ai-site-pilot to your content config so Tailwind generates the necessary classes:
|
|
31
|
+
|
|
32
|
+
```js
|
|
33
|
+
// tailwind.config.js or tailwind.config.ts
|
|
34
|
+
module.exports = {
|
|
35
|
+
content: [
|
|
36
|
+
'./app/**/*.{js,ts,jsx,tsx}',
|
|
37
|
+
'./components/**/*.{js,ts,jsx,tsx}',
|
|
38
|
+
// Add this line:
|
|
39
|
+
'./node_modules/ai-site-pilot/dist/**/*.{js,mjs}',
|
|
40
|
+
],
|
|
41
|
+
// ...
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**Without this, the button text ("Ask AI") and other responsive styles won't work correctly.**
|
|
46
|
+
|
|
47
|
+
### Import Styles
|
|
48
|
+
|
|
49
|
+
Make sure to import the CSS file in your component:
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
import 'ai-site-pilot/styles.css';
|
|
53
|
+
```
|
|
54
|
+
|
|
26
55
|
## Quick Start
|
|
27
56
|
|
|
28
57
|
### 1. Get an OpenRouter API Key
|
|
@@ -117,18 +146,61 @@ See all models at [openrouter.ai/models](https://openrouter.ai/models)
|
|
|
117
146
|
|
|
118
147
|
### `createHandler()`
|
|
119
148
|
|
|
120
|
-
Creates a Next.js API route handler.
|
|
149
|
+
Creates a Next.js API route handler. You can use either `systemPrompt` (manual) or `siteContent` (auto-generated).
|
|
150
|
+
|
|
151
|
+
**Option 1: Auto-generated prompt with `siteContent`** (Recommended)
|
|
121
152
|
|
|
122
153
|
```typescript
|
|
123
154
|
import { createHandler } from 'ai-site-pilot/api';
|
|
124
155
|
|
|
125
156
|
export const POST = createHandler({
|
|
126
|
-
|
|
127
|
-
|
|
157
|
+
model: 'google/gemini-2.0-flash-exp:free',
|
|
158
|
+
siteContent: {
|
|
159
|
+
name: 'Acme Dance Studio',
|
|
160
|
+
type: 'dance studio',
|
|
161
|
+
description: 'Premier dance education since 1995',
|
|
162
|
+
personality: 'warm and encouraging',
|
|
163
|
+
pages: ['home', 'classes', 'teachers', 'schedule', 'contact'],
|
|
164
|
+
items: [
|
|
165
|
+
{ id: 'ballet', name: 'Ballet', category: 'class', description: 'Classical ballet for ages 3-adult', price: '$80/month' },
|
|
166
|
+
{ id: 'jazz', name: 'Jazz', category: 'class', description: 'High-energy jazz for ages 6+', price: '$75/month' },
|
|
167
|
+
{ id: 'sarah', name: 'Sarah Johnson', category: 'teacher', description: 'Owner & lead instructor, 15 years experience' },
|
|
168
|
+
],
|
|
169
|
+
faqs: [
|
|
170
|
+
{ question: 'What should I wear?', answer: 'Leotard and ballet slippers for ballet, comfortable athletic wear for jazz.' },
|
|
171
|
+
],
|
|
172
|
+
contact: {
|
|
173
|
+
email: 'info@acmedance.com',
|
|
174
|
+
phone: '555-1234',
|
|
175
|
+
hours: 'Mon-Sat 9am-8pm',
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
tools: [navigateTool, showClassTool],
|
|
179
|
+
});
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
The AI automatically knows about your content and can answer questions like "What classes do you offer?" with specific details.
|
|
183
|
+
|
|
184
|
+
**Option 2: Manual `systemPrompt`**
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
export const POST = createHandler({
|
|
188
|
+
systemPrompt: 'You are a helpful assistant for Acme Dance Studio...',
|
|
189
|
+
tools: [navigateTool],
|
|
190
|
+
});
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
**Full options:**
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
createHandler({
|
|
197
|
+
// Content (use ONE of these)
|
|
198
|
+
siteContent: { ... }, // Auto-generate prompt from your content
|
|
199
|
+
systemPrompt: '...', // Or write your own prompt
|
|
128
200
|
|
|
129
201
|
// Optional
|
|
130
202
|
apiKey: process.env.OPENROUTER_API_KEY, // Uses env var by default
|
|
131
|
-
model: 'google/gemini-2.0-flash-exp:free', // Default
|
|
203
|
+
model: 'google/gemini-2.0-flash-exp:free', // Default (free!)
|
|
132
204
|
tools: [myTool1, myTool2],
|
|
133
205
|
temperature: 0.7,
|
|
134
206
|
siteUrl: 'https://mysite.com', // Shown in OpenRouter dashboard
|
|
@@ -245,8 +317,65 @@ const generateFallback = createFallbackMessageGenerator({
|
|
|
245
317
|
|
|
246
318
|
- React 18+ or React 19
|
|
247
319
|
- Next.js 13+ (for API routes)
|
|
320
|
+
- Tailwind CSS (for responsive styles)
|
|
248
321
|
- OpenRouter API key (free at [openrouter.ai](https://openrouter.ai))
|
|
249
322
|
|
|
323
|
+
## Troubleshooting
|
|
324
|
+
|
|
325
|
+
### Button only shows icon, no "Ask AI" text
|
|
326
|
+
|
|
327
|
+
Your Tailwind config isn't scanning the package. Add this to your `tailwind.config.js`:
|
|
328
|
+
|
|
329
|
+
```js
|
|
330
|
+
content: [
|
|
331
|
+
// ... your paths
|
|
332
|
+
'./node_modules/ai-site-pilot/dist/**/*.{js,mjs}',
|
|
333
|
+
]
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### Theme accent color not working
|
|
337
|
+
|
|
338
|
+
Make sure you're using the `accent` prop (preset) or `accentColor` prop (custom hex):
|
|
339
|
+
|
|
340
|
+
```tsx
|
|
341
|
+
// Using preset
|
|
342
|
+
<SitePilot theme={{ accent: 'pink' }} />
|
|
343
|
+
|
|
344
|
+
// Using custom color
|
|
345
|
+
<SitePilot theme={{ accentColor: '#ec4899' }} />
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
Don't set CSS variables directly—use the component props.
|
|
349
|
+
|
|
350
|
+
### Tools not executing
|
|
351
|
+
|
|
352
|
+
1. Make sure you're handling tool calls in `onToolCall`:
|
|
353
|
+
```tsx
|
|
354
|
+
<SitePilot
|
|
355
|
+
onToolCall={(name, args) => {
|
|
356
|
+
console.log('Tool called:', name, args);
|
|
357
|
+
// Handle the tool...
|
|
358
|
+
}}
|
|
359
|
+
/>
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
2. Check browser console for errors
|
|
363
|
+
|
|
364
|
+
### "I've made some changes" generic message
|
|
365
|
+
|
|
366
|
+
Use `generateFallbackMessage` to customize messages when the AI uses tools without text:
|
|
367
|
+
|
|
368
|
+
```tsx
|
|
369
|
+
import { createFallbackMessageGenerator } from 'ai-site-pilot';
|
|
370
|
+
|
|
371
|
+
const generateFallback = createFallbackMessageGenerator({
|
|
372
|
+
navigate: (args) => `Navigated to ${args.section}`,
|
|
373
|
+
filter: (args) => `Filtered by ${args.category}`,
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
<SitePilot generateFallbackMessage={generateFallback} />
|
|
377
|
+
```
|
|
378
|
+
|
|
250
379
|
## License
|
|
251
380
|
|
|
252
381
|
MIT
|
package/dist/api/index.d.mts
CHANGED
|
@@ -1,19 +1,85 @@
|
|
|
1
1
|
import { T as ToolDefinition } from '../types--7jDyUM6.mjs';
|
|
2
2
|
import { S as StreamEvent } from '../types-UmyDfs25.mjs';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Auto-generate system prompts from site content
|
|
6
|
+
* Makes it easy for any site to get a well-structured AI assistant
|
|
7
|
+
*/
|
|
8
|
+
interface SiteContentItem {
|
|
9
|
+
/** Unique identifier for the item */
|
|
10
|
+
id: string;
|
|
11
|
+
/** Display name */
|
|
12
|
+
name: string;
|
|
13
|
+
/** Category/type (e.g., 'product', 'service', 'team', 'class') */
|
|
14
|
+
category?: string;
|
|
15
|
+
/** Description of the item */
|
|
16
|
+
description?: string;
|
|
17
|
+
/** Additional metadata */
|
|
18
|
+
[key: string]: unknown;
|
|
19
|
+
}
|
|
20
|
+
interface SiteContent {
|
|
21
|
+
/** Name of the business/site */
|
|
22
|
+
name: string;
|
|
23
|
+
/** Brief description of what the site/business does */
|
|
24
|
+
description?: string;
|
|
25
|
+
/** Type of business (e.g., 'dance studio', 'portfolio', 'e-commerce') */
|
|
26
|
+
type?: string;
|
|
27
|
+
/** Personality/tone for the assistant (e.g., 'friendly', 'professional', 'warm') */
|
|
28
|
+
personality?: string;
|
|
29
|
+
/** Available pages/sections users can navigate to */
|
|
30
|
+
pages?: string[];
|
|
31
|
+
/** Content items (products, services, team members, etc.) */
|
|
32
|
+
items?: SiteContentItem[];
|
|
33
|
+
/** FAQs */
|
|
34
|
+
faqs?: Array<{
|
|
35
|
+
question: string;
|
|
36
|
+
answer: string;
|
|
37
|
+
}>;
|
|
38
|
+
/** Contact information */
|
|
39
|
+
contact?: {
|
|
40
|
+
email?: string;
|
|
41
|
+
phone?: string;
|
|
42
|
+
address?: string;
|
|
43
|
+
hours?: string;
|
|
44
|
+
};
|
|
45
|
+
/** Additional context to include in the prompt */
|
|
46
|
+
additionalContext?: string;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Generate a system prompt from site content
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```ts
|
|
53
|
+
* const prompt = generateSystemPrompt({
|
|
54
|
+
* name: 'Acme Dance Studio',
|
|
55
|
+
* type: 'dance studio',
|
|
56
|
+
* personality: 'warm and encouraging',
|
|
57
|
+
* pages: ['home', 'classes', 'teachers', 'schedule', 'contact'],
|
|
58
|
+
* items: [
|
|
59
|
+
* { id: 'ballet', name: 'Ballet', category: 'class', description: 'Classical ballet for ages 3-adult' },
|
|
60
|
+
* { id: 'jazz', name: 'Jazz', category: 'class', description: 'High-energy jazz for ages 6+' },
|
|
61
|
+
* { id: 'sarah', name: 'Sarah Johnson', category: 'teacher', description: 'Owner, 15 years experience' },
|
|
62
|
+
* ],
|
|
63
|
+
* contact: {
|
|
64
|
+
* email: 'info@acmedance.com',
|
|
65
|
+
* phone: '555-1234',
|
|
66
|
+
* },
|
|
67
|
+
* });
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
declare function generateSystemPrompt(content: SiteContent): string;
|
|
71
|
+
|
|
4
72
|
/**
|
|
5
73
|
* Universal chat handler using OpenRouter
|
|
6
74
|
* Works with any model: Gemini, GPT-4, Claude, Llama, etc.
|
|
7
75
|
* No SDK required - just standard fetch.
|
|
8
76
|
*/
|
|
9
77
|
|
|
10
|
-
interface
|
|
78
|
+
interface BaseHandlerConfig {
|
|
11
79
|
/** OpenRouter API key (or set OPENROUTER_API_KEY env var) */
|
|
12
80
|
apiKey?: string;
|
|
13
81
|
/** Model to use (e.g., 'google/gemini-2.0-flash-exp:free', 'openai/gpt-4o', 'anthropic/claude-3.5-sonnet') */
|
|
14
82
|
model?: string;
|
|
15
|
-
/** System prompt for the AI */
|
|
16
|
-
systemPrompt: string;
|
|
17
83
|
/** Tool definitions for the AI */
|
|
18
84
|
tools?: ToolDefinition[];
|
|
19
85
|
/** Temperature for response generation (0-1) */
|
|
@@ -23,6 +89,17 @@ interface HandlerConfig {
|
|
|
23
89
|
/** Your app name (shown in OpenRouter dashboard) */
|
|
24
90
|
siteName?: string;
|
|
25
91
|
}
|
|
92
|
+
interface HandlerConfigWithPrompt extends BaseHandlerConfig {
|
|
93
|
+
/** System prompt for the AI (use this OR siteContent, not both) */
|
|
94
|
+
systemPrompt: string;
|
|
95
|
+
siteContent?: never;
|
|
96
|
+
}
|
|
97
|
+
interface HandlerConfigWithContent extends BaseHandlerConfig {
|
|
98
|
+
/** Site content to auto-generate system prompt (use this OR systemPrompt, not both) */
|
|
99
|
+
siteContent: SiteContent;
|
|
100
|
+
systemPrompt?: never;
|
|
101
|
+
}
|
|
102
|
+
type HandlerConfig = HandlerConfigWithPrompt | HandlerConfigWithContent;
|
|
26
103
|
/**
|
|
27
104
|
* Create a Next.js API route handler using OpenRouter
|
|
28
105
|
*
|
|
@@ -69,4 +146,4 @@ declare function getSSEHeaders(): HeadersInit;
|
|
|
69
146
|
*/
|
|
70
147
|
declare function parseSSEStream(reader: ReadableStreamDefaultReader<Uint8Array>): AsyncGenerator<StreamEvent>;
|
|
71
148
|
|
|
72
|
-
export { type HandlerConfig, createHandler, createSSEEncoder, getSSEHeaders, parseSSEStream };
|
|
149
|
+
export { type HandlerConfig, type SiteContent, type SiteContentItem, createHandler, createSSEEncoder, generateSystemPrompt, getSSEHeaders, parseSSEStream };
|
package/dist/api/index.d.ts
CHANGED
|
@@ -1,19 +1,85 @@
|
|
|
1
1
|
import { T as ToolDefinition } from '../types--7jDyUM6.js';
|
|
2
2
|
import { S as StreamEvent } from '../types-UmyDfs25.js';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Auto-generate system prompts from site content
|
|
6
|
+
* Makes it easy for any site to get a well-structured AI assistant
|
|
7
|
+
*/
|
|
8
|
+
interface SiteContentItem {
|
|
9
|
+
/** Unique identifier for the item */
|
|
10
|
+
id: string;
|
|
11
|
+
/** Display name */
|
|
12
|
+
name: string;
|
|
13
|
+
/** Category/type (e.g., 'product', 'service', 'team', 'class') */
|
|
14
|
+
category?: string;
|
|
15
|
+
/** Description of the item */
|
|
16
|
+
description?: string;
|
|
17
|
+
/** Additional metadata */
|
|
18
|
+
[key: string]: unknown;
|
|
19
|
+
}
|
|
20
|
+
interface SiteContent {
|
|
21
|
+
/** Name of the business/site */
|
|
22
|
+
name: string;
|
|
23
|
+
/** Brief description of what the site/business does */
|
|
24
|
+
description?: string;
|
|
25
|
+
/** Type of business (e.g., 'dance studio', 'portfolio', 'e-commerce') */
|
|
26
|
+
type?: string;
|
|
27
|
+
/** Personality/tone for the assistant (e.g., 'friendly', 'professional', 'warm') */
|
|
28
|
+
personality?: string;
|
|
29
|
+
/** Available pages/sections users can navigate to */
|
|
30
|
+
pages?: string[];
|
|
31
|
+
/** Content items (products, services, team members, etc.) */
|
|
32
|
+
items?: SiteContentItem[];
|
|
33
|
+
/** FAQs */
|
|
34
|
+
faqs?: Array<{
|
|
35
|
+
question: string;
|
|
36
|
+
answer: string;
|
|
37
|
+
}>;
|
|
38
|
+
/** Contact information */
|
|
39
|
+
contact?: {
|
|
40
|
+
email?: string;
|
|
41
|
+
phone?: string;
|
|
42
|
+
address?: string;
|
|
43
|
+
hours?: string;
|
|
44
|
+
};
|
|
45
|
+
/** Additional context to include in the prompt */
|
|
46
|
+
additionalContext?: string;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Generate a system prompt from site content
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```ts
|
|
53
|
+
* const prompt = generateSystemPrompt({
|
|
54
|
+
* name: 'Acme Dance Studio',
|
|
55
|
+
* type: 'dance studio',
|
|
56
|
+
* personality: 'warm and encouraging',
|
|
57
|
+
* pages: ['home', 'classes', 'teachers', 'schedule', 'contact'],
|
|
58
|
+
* items: [
|
|
59
|
+
* { id: 'ballet', name: 'Ballet', category: 'class', description: 'Classical ballet for ages 3-adult' },
|
|
60
|
+
* { id: 'jazz', name: 'Jazz', category: 'class', description: 'High-energy jazz for ages 6+' },
|
|
61
|
+
* { id: 'sarah', name: 'Sarah Johnson', category: 'teacher', description: 'Owner, 15 years experience' },
|
|
62
|
+
* ],
|
|
63
|
+
* contact: {
|
|
64
|
+
* email: 'info@acmedance.com',
|
|
65
|
+
* phone: '555-1234',
|
|
66
|
+
* },
|
|
67
|
+
* });
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
declare function generateSystemPrompt(content: SiteContent): string;
|
|
71
|
+
|
|
4
72
|
/**
|
|
5
73
|
* Universal chat handler using OpenRouter
|
|
6
74
|
* Works with any model: Gemini, GPT-4, Claude, Llama, etc.
|
|
7
75
|
* No SDK required - just standard fetch.
|
|
8
76
|
*/
|
|
9
77
|
|
|
10
|
-
interface
|
|
78
|
+
interface BaseHandlerConfig {
|
|
11
79
|
/** OpenRouter API key (or set OPENROUTER_API_KEY env var) */
|
|
12
80
|
apiKey?: string;
|
|
13
81
|
/** Model to use (e.g., 'google/gemini-2.0-flash-exp:free', 'openai/gpt-4o', 'anthropic/claude-3.5-sonnet') */
|
|
14
82
|
model?: string;
|
|
15
|
-
/** System prompt for the AI */
|
|
16
|
-
systemPrompt: string;
|
|
17
83
|
/** Tool definitions for the AI */
|
|
18
84
|
tools?: ToolDefinition[];
|
|
19
85
|
/** Temperature for response generation (0-1) */
|
|
@@ -23,6 +89,17 @@ interface HandlerConfig {
|
|
|
23
89
|
/** Your app name (shown in OpenRouter dashboard) */
|
|
24
90
|
siteName?: string;
|
|
25
91
|
}
|
|
92
|
+
interface HandlerConfigWithPrompt extends BaseHandlerConfig {
|
|
93
|
+
/** System prompt for the AI (use this OR siteContent, not both) */
|
|
94
|
+
systemPrompt: string;
|
|
95
|
+
siteContent?: never;
|
|
96
|
+
}
|
|
97
|
+
interface HandlerConfigWithContent extends BaseHandlerConfig {
|
|
98
|
+
/** Site content to auto-generate system prompt (use this OR systemPrompt, not both) */
|
|
99
|
+
siteContent: SiteContent;
|
|
100
|
+
systemPrompt?: never;
|
|
101
|
+
}
|
|
102
|
+
type HandlerConfig = HandlerConfigWithPrompt | HandlerConfigWithContent;
|
|
26
103
|
/**
|
|
27
104
|
* Create a Next.js API route handler using OpenRouter
|
|
28
105
|
*
|
|
@@ -69,4 +146,4 @@ declare function getSSEHeaders(): HeadersInit;
|
|
|
69
146
|
*/
|
|
70
147
|
declare function parseSSEStream(reader: ReadableStreamDefaultReader<Uint8Array>): AsyncGenerator<StreamEvent>;
|
|
71
148
|
|
|
72
|
-
export { type HandlerConfig, createHandler, createSSEEncoder, getSSEHeaders, parseSSEStream };
|
|
149
|
+
export { type HandlerConfig, type SiteContent, type SiteContentItem, createHandler, createSSEEncoder, generateSystemPrompt, getSSEHeaders, parseSSEStream };
|
package/dist/api/index.js
CHANGED
|
@@ -59,6 +59,81 @@ async function* parseSSEStream(reader) {
|
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
// src/api/generateSystemPrompt.ts
|
|
63
|
+
function generateSystemPrompt(content) {
|
|
64
|
+
const {
|
|
65
|
+
name,
|
|
66
|
+
description,
|
|
67
|
+
type,
|
|
68
|
+
personality = "helpful and friendly",
|
|
69
|
+
pages = [],
|
|
70
|
+
items = [],
|
|
71
|
+
faqs = [],
|
|
72
|
+
contact,
|
|
73
|
+
additionalContext
|
|
74
|
+
} = content;
|
|
75
|
+
const sections = [];
|
|
76
|
+
sections.push(`You are the AI assistant for ${name}${type ? `, a ${type}` : ""}.${description ? ` ${description}` : ""}`);
|
|
77
|
+
sections.push(`Your personality is ${personality}. Be conversational but concise.`);
|
|
78
|
+
const itemsByCategory = items.reduce((acc, item) => {
|
|
79
|
+
const cat = item.category || "other";
|
|
80
|
+
if (!acc[cat]) acc[cat] = [];
|
|
81
|
+
acc[cat].push(item);
|
|
82
|
+
return acc;
|
|
83
|
+
}, {});
|
|
84
|
+
if (Object.keys(itemsByCategory).length > 0) {
|
|
85
|
+
sections.push("\n## Available Content\n");
|
|
86
|
+
for (const [category, categoryItems] of Object.entries(itemsByCategory)) {
|
|
87
|
+
const categoryTitle = category.charAt(0).toUpperCase() + category.slice(1) + "s";
|
|
88
|
+
sections.push(`### ${categoryTitle}`);
|
|
89
|
+
for (const item of categoryItems) {
|
|
90
|
+
let itemLine = `- **${item.name}** (id: "${item.id}")`;
|
|
91
|
+
if (item.description) {
|
|
92
|
+
itemLine += `: ${item.description}`;
|
|
93
|
+
}
|
|
94
|
+
const extraFields = Object.entries(item).filter(([k]) => !["id", "name", "category", "description"].includes(k)).map(([k, v]) => `${k}: ${v}`).join(", ");
|
|
95
|
+
if (extraFields) {
|
|
96
|
+
itemLine += ` [${extraFields}]`;
|
|
97
|
+
}
|
|
98
|
+
sections.push(itemLine);
|
|
99
|
+
}
|
|
100
|
+
sections.push("");
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (pages.length > 0) {
|
|
104
|
+
sections.push(`## Site Sections
|
|
105
|
+
Users can navigate to: ${pages.join(", ")}`);
|
|
106
|
+
}
|
|
107
|
+
if (faqs.length > 0) {
|
|
108
|
+
sections.push("\n## Frequently Asked Questions");
|
|
109
|
+
for (const faq of faqs) {
|
|
110
|
+
sections.push(`**Q: ${faq.question}**`);
|
|
111
|
+
sections.push(`A: ${faq.answer}
|
|
112
|
+
`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (contact) {
|
|
116
|
+
sections.push("\n## Contact Information");
|
|
117
|
+
if (contact.email) sections.push(`- Email: ${contact.email}`);
|
|
118
|
+
if (contact.phone) sections.push(`- Phone: ${contact.phone}`);
|
|
119
|
+
if (contact.address) sections.push(`- Address: ${contact.address}`);
|
|
120
|
+
if (contact.hours) sections.push(`- Hours: ${contact.hours}`);
|
|
121
|
+
}
|
|
122
|
+
if (additionalContext) {
|
|
123
|
+
sections.push(`
|
|
124
|
+
## Additional Information
|
|
125
|
+
${additionalContext}`);
|
|
126
|
+
}
|
|
127
|
+
sections.push(`
|
|
128
|
+
## Instructions
|
|
129
|
+
- When users ask about specific items, provide detailed information from the content above
|
|
130
|
+
- Use the available tools to navigate users to relevant pages or show them specific items
|
|
131
|
+
- If you don't know something, say so honestly - don't make up information
|
|
132
|
+
- Keep responses concise but helpful
|
|
133
|
+
- Reference specific items by their id when using tools`);
|
|
134
|
+
return sections.join("\n");
|
|
135
|
+
}
|
|
136
|
+
|
|
62
137
|
// src/api/createHandler.ts
|
|
63
138
|
function convertTools(tools) {
|
|
64
139
|
return tools.map((tool) => ({
|
|
@@ -87,12 +162,12 @@ function createHandler(config) {
|
|
|
87
162
|
const {
|
|
88
163
|
apiKey = process.env.OPENROUTER_API_KEY,
|
|
89
164
|
model = "google/gemini-2.0-flash-exp:free",
|
|
90
|
-
systemPrompt,
|
|
91
165
|
tools = [],
|
|
92
166
|
temperature = 0.7,
|
|
93
167
|
siteUrl,
|
|
94
168
|
siteName
|
|
95
169
|
} = config;
|
|
170
|
+
const systemPrompt = "siteContent" in config && config.siteContent ? generateSystemPrompt(config.siteContent) : config.systemPrompt;
|
|
96
171
|
return async function POST(req) {
|
|
97
172
|
if (!apiKey) {
|
|
98
173
|
return new Response(
|
|
@@ -217,6 +292,7 @@ function createHandler(config) {
|
|
|
217
292
|
|
|
218
293
|
exports.createHandler = createHandler;
|
|
219
294
|
exports.createSSEEncoder = createSSEEncoder;
|
|
295
|
+
exports.generateSystemPrompt = generateSystemPrompt;
|
|
220
296
|
exports.getSSEHeaders = getSSEHeaders;
|
|
221
297
|
exports.parseSSEStream = parseSSEStream;
|
|
222
298
|
//# sourceMappingURL=index.js.map
|
package/dist/api/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/api/streaming.ts","../../src/api/createHandler.ts"],"names":[],"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 * Universal chat handler using OpenRouter\n * Works with any model: Gemini, GPT-4, Claude, Llama, etc.\n * No SDK required - just standard fetch.\n */\n\nimport type { ToolDefinition } from '../tools/types';\nimport { createSSEEncoder, getSSEHeaders } from './streaming';\n\nexport interface HandlerConfig {\n /** OpenRouter API key (or set OPENROUTER_API_KEY env var) */\n apiKey?: string;\n /** Model to use (e.g., 'google/gemini-2.0-flash-exp:free', 'openai/gpt-4o', 'anthropic/claude-3.5-sonnet') */\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 /** Your site URL (shown in OpenRouter dashboard) */\n siteUrl?: string;\n /** Your app name (shown in OpenRouter dashboard) */\n siteName?: string;\n}\n\ninterface RequestBody {\n messages: Array<{ role: 'user' | 'assistant'; content: string }>;\n}\n\ninterface OpenRouterMessage {\n role: 'system' | 'user' | 'assistant';\n content: string;\n}\n\ninterface OpenRouterTool {\n type: 'function';\n function: {\n name: string;\n description: string;\n parameters: {\n type: 'object';\n properties: Record<string, unknown>;\n required: string[];\n };\n };\n}\n\n/**\n * Convert tool definitions to OpenAI/OpenRouter format\n */\nfunction convertTools(tools: ToolDefinition[]): OpenRouterTool[] {\n return tools.map(tool => ({\n type: 'function' as const,\n function: {\n name: tool.name,\n description: tool.description,\n parameters: {\n type: 'object' as const,\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 ...(value.enum && { enum: value.enum }),\n },\n ])\n ),\n required: tool.parameters.required,\n },\n },\n }));\n}\n\n/**\n * Create a Next.js API route handler using OpenRouter\n *\n * Works with any model - just change the model string:\n * - 'google/gemini-2.0-flash-exp:free' (free!)\n * - 'openai/gpt-4o'\n * - 'anthropic/claude-3.5-sonnet'\n * - 'meta-llama/llama-3.1-70b-instruct'\n *\n * @example\n * ```ts\n * // app/api/chat/route.ts\n * import { createHandler } from 'ai-site-pilot/api';\n *\n * export const POST = createHandler({\n * model: 'google/gemini-2.0-flash-exp:free',\n * systemPrompt: 'You are a helpful assistant...',\n * tools: myTools,\n * });\n * ```\n */\nexport function createHandler(config: HandlerConfig) {\n const {\n apiKey = process.env.OPENROUTER_API_KEY,\n model = 'google/gemini-2.0-flash-exp:free',\n systemPrompt,\n tools = [],\n temperature = 0.7,\n siteUrl,\n siteName,\n } = config;\n\n return async function POST(req: Request): Promise<Response> {\n if (!apiKey) {\n return new Response(\n JSON.stringify({ error: 'OpenRouter API key not configured. Get one at https://openrouter.ai' }),\n { status: 500, headers: { 'Content-Type': 'application/json' } }\n );\n }\n\n try {\n const body = (await req.json()) as RequestBody;\n const { messages } = body;\n\n // Build messages array with system prompt\n const openRouterMessages: OpenRouterMessage[] = [\n { role: 'system', content: systemPrompt },\n ...messages.map((m) => ({\n role: m.role === 'assistant' ? 'assistant' as const : 'user' as const,\n content: m.content,\n })),\n ];\n\n const sse = createSSEEncoder();\n\n const stream = new ReadableStream({\n async start(controller) {\n try {\n const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${apiKey}`,\n 'Content-Type': 'application/json',\n 'HTTP-Referer': siteUrl || '',\n 'X-Title': siteName || '',\n },\n body: JSON.stringify({\n model,\n messages: openRouterMessages,\n temperature,\n stream: true,\n ...(tools.length > 0 && { tools: convertTools(tools) }),\n }),\n });\n\n if (!response.ok) {\n const error = await response.text();\n console.error('OpenRouter error:', error);\n controller.enqueue(sse.encodeError('Failed to get AI response'));\n controller.enqueue(sse.encodeDone());\n controller.close();\n return;\n }\n\n const reader = response.body?.getReader();\n if (!reader) {\n controller.enqueue(sse.encodeError('No response stream'));\n controller.enqueue(sse.encodeDone());\n controller.close();\n return;\n }\n\n const decoder = new TextDecoder();\n let buffer = '';\n\n // Accumulate tool calls across stream chunks\n // OpenRouter streams tool calls in pieces: name first, then arguments in chunks\n const pendingToolCalls: Map<number, { name: string; arguments: string }> = new Map();\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 const data = line.slice(6);\n if (data === '[DONE]') continue;\n\n try {\n const parsed = JSON.parse(data);\n const delta = parsed.choices?.[0]?.delta;\n\n // Handle text content\n if (delta?.content) {\n controller.enqueue(sse.encodeText(delta.content));\n }\n\n // Handle tool calls - accumulate across chunks\n if (delta?.tool_calls) {\n for (const toolCall of delta.tool_calls) {\n const index = toolCall.index ?? 0;\n\n // Get or create pending tool call\n let pending = pendingToolCalls.get(index);\n if (!pending) {\n pending = { name: '', arguments: '' };\n pendingToolCalls.set(index, pending);\n }\n\n // Accumulate name (usually comes in first chunk)\n if (toolCall.function?.name) {\n pending.name = toolCall.function.name;\n }\n\n // Accumulate arguments (streamed in chunks)\n if (toolCall.function?.arguments) {\n pending.arguments += toolCall.function.arguments;\n }\n }\n }\n } catch {\n // Skip malformed JSON\n }\n }\n }\n }\n\n // Emit all accumulated tool calls now that streaming is complete\n for (const [, toolCall] of pendingToolCalls) {\n if (toolCall.name && toolCall.arguments) {\n try {\n const args = JSON.parse(toolCall.arguments);\n controller.enqueue(sse.encodeTool(toolCall.name, args));\n } catch (e) {\n console.error('Failed to parse tool arguments:', toolCall.arguments, e);\n }\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, { headers: getSSEHeaders() });\n } catch (error) {\n console.error('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"]}
|
|
1
|
+
{"version":3,"sources":["../../src/api/streaming.ts","../../src/api/generateSystemPrompt.ts","../../src/api/createHandler.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;;;ACRO,SAAS,qBAAqB,OAAA,EAA8B;AACjE,EAAA,MAAM;AAAA,IACJ,IAAA;AAAA,IACA,WAAA;AAAA,IACA,IAAA;AAAA,IACA,WAAA,GAAc,sBAAA;AAAA,IACd,QAAQ,EAAC;AAAA,IACT,QAAQ,EAAC;AAAA,IACT,OAAO,EAAC;AAAA,IACR,OAAA;AAAA,IACA;AAAA,GACF,GAAI,OAAA;AAEJ,EAAA,MAAM,WAAqB,EAAC;AAG5B,EAAA,QAAA,CAAS,IAAA,CAAK,CAAA,6BAAA,EAAgC,IAAI,CAAA,EAAG,OAAO,CAAA,IAAA,EAAO,IAAI,CAAA,CAAA,GAAK,EAAE,IAAI,WAAA,GAAc,CAAA,CAAA,EAAI,WAAW,CAAA,CAAA,GAAK,EAAE,CAAA,CAAE,CAAA;AACxH,EAAA,QAAA,CAAS,IAAA,CAAK,CAAA,oBAAA,EAAuB,WAAW,CAAA,gCAAA,CAAkC,CAAA;AAGlF,EAAA,MAAM,eAAA,GAAkB,KAAA,CAAM,MAAA,CAAO,CAAC,KAAK,IAAA,KAAS;AAClD,IAAA,MAAM,GAAA,GAAM,KAAK,QAAA,IAAY,OAAA;AAC7B,IAAA,IAAI,CAAC,GAAA,CAAI,GAAG,GAAG,GAAA,CAAI,GAAG,IAAI,EAAC;AAC3B,IAAA,GAAA,CAAI,GAAG,CAAA,CAAE,IAAA,CAAK,IAAI,CAAA;AAClB,IAAA,OAAO,GAAA;AAAA,EACT,CAAA,EAAG,EAAuC,CAAA;AAG1C,EAAA,IAAI,MAAA,CAAO,IAAA,CAAK,eAAe,CAAA,CAAE,SAAS,CAAA,EAAG;AAC3C,IAAA,QAAA,CAAS,KAAK,0BAA0B,CAAA;AAExC,IAAA,KAAA,MAAW,CAAC,QAAA,EAAU,aAAa,KAAK,MAAA,CAAO,OAAA,CAAQ,eAAe,CAAA,EAAG;AACvE,MAAA,MAAM,aAAA,GAAgB,QAAA,CAAS,MAAA,CAAO,CAAC,CAAA,CAAE,aAAY,GAAI,QAAA,CAAS,KAAA,CAAM,CAAC,CAAA,GAAI,GAAA;AAC7E,MAAA,QAAA,CAAS,IAAA,CAAK,CAAA,IAAA,EAAO,aAAa,CAAA,CAAE,CAAA;AAEpC,MAAA,KAAA,MAAW,QAAQ,aAAA,EAAe;AAChC,QAAA,IAAI,WAAW,CAAA,IAAA,EAAO,IAAA,CAAK,IAAI,CAAA,SAAA,EAAY,KAAK,EAAE,CAAA,EAAA,CAAA;AAClD,QAAA,IAAI,KAAK,WAAA,EAAa;AACpB,UAAA,QAAA,IAAY,CAAA,EAAA,EAAK,KAAK,WAAW,CAAA,CAAA;AAAA,QACnC;AAEA,QAAA,MAAM,WAAA,GAAc,MAAA,CAAO,OAAA,CAAQ,IAAI,EACpC,MAAA,CAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAA,EAAM,MAAA,EAAQ,YAAY,aAAa,CAAA,CAAE,QAAA,CAAS,CAAC,CAAC,CAAA,CACtE,GAAA,CAAI,CAAC,CAAC,CAAA,EAAG,CAAC,CAAA,KAAM,CAAA,EAAG,CAAC,CAAA,EAAA,EAAK,CAAC,CAAA,CAAE,CAAA,CAC5B,KAAK,IAAI,CAAA;AACZ,QAAA,IAAI,WAAA,EAAa;AACf,UAAA,QAAA,IAAY,KAAK,WAAW,CAAA,CAAA,CAAA;AAAA,QAC9B;AACA,QAAA,QAAA,CAAS,KAAK,QAAQ,CAAA;AAAA,MACxB;AACA,MAAA,QAAA,CAAS,KAAK,EAAE,CAAA;AAAA,IAClB;AAAA,EACF;AAGA,EAAA,IAAI,KAAA,CAAM,SAAS,CAAA,EAAG;AACpB,IAAA,QAAA,CAAS,IAAA,CAAK,CAAA;AAAA,uBAAA,EAA4C,KAAA,CAAM,IAAA,CAAK,IAAI,CAAC,CAAA,CAAE,CAAA;AAAA,EAC9E;AAGA,EAAA,IAAI,IAAA,CAAK,SAAS,CAAA,EAAG;AACnB,IAAA,QAAA,CAAS,KAAK,iCAAiC,CAAA;AAC/C,IAAA,KAAA,MAAW,OAAO,IAAA,EAAM;AACtB,MAAA,QAAA,CAAS,IAAA,CAAK,CAAA,KAAA,EAAQ,GAAA,CAAI,QAAQ,CAAA,EAAA,CAAI,CAAA;AACtC,MAAA,QAAA,CAAS,IAAA,CAAK,CAAA,GAAA,EAAM,GAAA,CAAI,MAAM;AAAA,CAAI,CAAA;AAAA,IACpC;AAAA,EACF;AAGA,EAAA,IAAI,OAAA,EAAS;AACX,IAAA,QAAA,CAAS,KAAK,0BAA0B,CAAA;AACxC,IAAA,IAAI,QAAQ,KAAA,EAAO,QAAA,CAAS,KAAK,CAAA,SAAA,EAAY,OAAA,CAAQ,KAAK,CAAA,CAAE,CAAA;AAC5D,IAAA,IAAI,QAAQ,KAAA,EAAO,QAAA,CAAS,KAAK,CAAA,SAAA,EAAY,OAAA,CAAQ,KAAK,CAAA,CAAE,CAAA;AAC5D,IAAA,IAAI,QAAQ,OAAA,EAAS,QAAA,CAAS,KAAK,CAAA,WAAA,EAAc,OAAA,CAAQ,OAAO,CAAA,CAAE,CAAA;AAClE,IAAA,IAAI,QAAQ,KAAA,EAAO,QAAA,CAAS,KAAK,CAAA,SAAA,EAAY,OAAA,CAAQ,KAAK,CAAA,CAAE,CAAA;AAAA,EAC9D;AAGA,EAAA,IAAI,iBAAA,EAAmB;AACrB,IAAA,QAAA,CAAS,IAAA,CAAK;AAAA;AAAA,EAAgC,iBAAiB,CAAA,CAAE,CAAA;AAAA,EACnE;AAGA,EAAA,QAAA,CAAS,IAAA,CAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,uDAAA,CAMwC,CAAA;AAEtD,EAAA,OAAO,QAAA,CAAS,KAAK,IAAI,CAAA;AAC3B;;;AC9FA,SAAS,aAAa,KAAA,EAA2C;AAC/D,EAAA,OAAO,KAAA,CAAM,IAAI,CAAA,IAAA,MAAS;AAAA,IACxB,IAAA,EAAM,UAAA;AAAA,IACN,QAAA,EAAU;AAAA,MACR,MAAM,IAAA,CAAK,IAAA;AAAA,MACX,aAAa,IAAA,CAAK,WAAA;AAAA,MAClB,UAAA,EAAY;AAAA,QACV,IAAA,EAAM,QAAA;AAAA,QACN,YAAY,MAAA,CAAO,WAAA;AAAA,UACjB,MAAA,CAAO,OAAA,CAAQ,IAAA,CAAK,UAAA,CAAW,UAAU,CAAA,CAAE,GAAA,CAAI,CAAC,CAAC,GAAA,EAAK,KAAK,CAAA,KAAM;AAAA,YAC/D,GAAA;AAAA,YACA;AAAA,cACE,MAAM,KAAA,CAAM,IAAA;AAAA,cACZ,aAAa,KAAA,CAAM,WAAA;AAAA,cACnB,GAAI,KAAA,CAAM,IAAA,IAAQ,EAAE,IAAA,EAAM,MAAM,IAAA;AAAK;AACvC,WACD;AAAA,SACH;AAAA,QACA,QAAA,EAAU,KAAK,UAAA,CAAW;AAAA;AAC5B;AACF,GACF,CAAE,CAAA;AACJ;AAuBO,SAAS,cAAc,MAAA,EAAuB;AACnD,EAAA,MAAM;AAAA,IACJ,MAAA,GAAS,QAAQ,GAAA,CAAI,kBAAA;AAAA,IACrB,KAAA,GAAQ,kCAAA;AAAA,IACR,QAAQ,EAAC;AAAA,IACT,WAAA,GAAc,GAAA;AAAA,IACd,OAAA;AAAA,IACA;AAAA,GACF,GAAI,MAAA;AAGJ,EAAA,MAAM,YAAA,GAAe,iBAAiB,MAAA,IAAU,MAAA,CAAO,cACnD,oBAAA,CAAqB,MAAA,CAAO,WAAW,CAAA,GACtC,MAAA,CAAmC,YAAA;AAExC,EAAA,OAAO,eAAe,KAAK,GAAA,EAAiC;AAC1D,IAAA,IAAI,CAAC,MAAA,EAAQ;AACX,MAAA,OAAO,IAAI,QAAA;AAAA,QACT,IAAA,CAAK,SAAA,CAAU,EAAE,KAAA,EAAO,uEAAuE,CAAA;AAAA,QAC/F,EAAE,MAAA,EAAQ,GAAA,EAAK,SAAS,EAAE,cAAA,EAAgB,oBAAmB;AAAE,OACjE;AAAA,IACF;AAEA,IAAA,IAAI;AACF,MAAA,MAAM,IAAA,GAAQ,MAAM,GAAA,CAAI,IAAA,EAAK;AAC7B,MAAA,MAAM,EAAE,UAAS,GAAI,IAAA;AAGrB,MAAA,MAAM,kBAAA,GAA0C;AAAA,QAC9C,EAAE,IAAA,EAAM,QAAA,EAAU,OAAA,EAAS,YAAA,EAAa;AAAA,QACxC,GAAG,QAAA,CAAS,GAAA,CAAI,CAAC,CAAA,MAAO;AAAA,UACtB,IAAA,EAAM,CAAA,CAAE,IAAA,KAAS,WAAA,GAAc,WAAA,GAAuB,MAAA;AAAA,UACtD,SAAS,CAAA,CAAE;AAAA,SACb,CAAE;AAAA,OACJ;AAEA,MAAA,MAAM,MAAM,gBAAA,EAAiB;AAE7B,MAAA,MAAM,MAAA,GAAS,IAAI,cAAA,CAAe;AAAA,QAChC,MAAM,MAAM,UAAA,EAAY;AACtB,UAAA,IAAI;AACF,YAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,+CAAA,EAAiD;AAAA,cAC5E,MAAA,EAAQ,MAAA;AAAA,cACR,OAAA,EAAS;AAAA,gBACP,eAAA,EAAiB,UAAU,MAAM,CAAA,CAAA;AAAA,gBACjC,cAAA,EAAgB,kBAAA;AAAA,gBAChB,gBAAgB,OAAA,IAAW,EAAA;AAAA,gBAC3B,WAAW,QAAA,IAAY;AAAA,eACzB;AAAA,cACA,IAAA,EAAM,KAAK,SAAA,CAAU;AAAA,gBACnB,KAAA;AAAA,gBACA,QAAA,EAAU,kBAAA;AAAA,gBACV,WAAA;AAAA,gBACA,MAAA,EAAQ,IAAA;AAAA,gBACR,GAAI,MAAM,MAAA,GAAS,CAAA,IAAK,EAAE,KAAA,EAAO,YAAA,CAAa,KAAK,CAAA;AAAE,eACtD;AAAA,aACF,CAAA;AAED,YAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,cAAA,MAAM,KAAA,GAAQ,MAAM,QAAA,CAAS,IAAA,EAAK;AAClC,cAAA,OAAA,CAAQ,KAAA,CAAM,qBAAqB,KAAK,CAAA;AACxC,cAAA,UAAA,CAAW,OAAA,CAAQ,GAAA,CAAI,WAAA,CAAY,2BAA2B,CAAC,CAAA;AAC/D,cAAA,UAAA,CAAW,OAAA,CAAQ,GAAA,CAAI,UAAA,EAAY,CAAA;AACnC,cAAA,UAAA,CAAW,KAAA,EAAM;AACjB,cAAA;AAAA,YACF;AAEA,YAAA,MAAM,MAAA,GAAS,QAAA,CAAS,IAAA,EAAM,SAAA,EAAU;AACxC,YAAA,IAAI,CAAC,MAAA,EAAQ;AACX,cAAA,UAAA,CAAW,OAAA,CAAQ,GAAA,CAAI,WAAA,CAAY,oBAAoB,CAAC,CAAA;AACxD,cAAA,UAAA,CAAW,OAAA,CAAQ,GAAA,CAAI,UAAA,EAAY,CAAA;AACnC,cAAA,UAAA,CAAW,KAAA,EAAM;AACjB,cAAA;AAAA,YACF;AAEA,YAAA,MAAM,OAAA,GAAU,IAAI,WAAA,EAAY;AAChC,YAAA,IAAI,MAAA,GAAS,EAAA;AAIb,YAAA,MAAM,gBAAA,uBAAyE,GAAA,EAAI;AAEnF,YAAA,OAAO,IAAA,EAAM;AACX,cAAA,MAAM,EAAE,IAAA,EAAM,KAAA,EAAM,GAAI,MAAM,OAAO,IAAA,EAAK;AAC1C,cAAA,IAAI,IAAA,EAAM;AAEV,cAAA,MAAA,IAAU,QAAQ,MAAA,CAAO,KAAA,EAAO,EAAE,MAAA,EAAQ,MAAM,CAAA;AAChD,cAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,KAAA,CAAM,IAAI,CAAA;AAC/B,cAAA,MAAA,GAAS,KAAA,CAAM,KAAI,IAAK,EAAA;AAExB,cAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,gBAAA,IAAI,IAAA,CAAK,UAAA,CAAW,QAAQ,CAAA,EAAG;AAC7B,kBAAA,MAAM,IAAA,GAAO,IAAA,CAAK,KAAA,CAAM,CAAC,CAAA;AACzB,kBAAA,IAAI,SAAS,QAAA,EAAU;AAEvB,kBAAA,IAAI;AACF,oBAAA,MAAM,MAAA,GAAS,IAAA,CAAK,KAAA,CAAM,IAAI,CAAA;AAC9B,oBAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,OAAA,GAAU,CAAC,CAAA,EAAG,KAAA;AAGnC,oBAAA,IAAI,OAAO,OAAA,EAAS;AAClB,sBAAA,UAAA,CAAW,OAAA,CAAQ,GAAA,CAAI,UAAA,CAAW,KAAA,CAAM,OAAO,CAAC,CAAA;AAAA,oBAClD;AAGA,oBAAA,IAAI,OAAO,UAAA,EAAY;AACrB,sBAAA,KAAA,MAAW,QAAA,IAAY,MAAM,UAAA,EAAY;AACvC,wBAAA,MAAM,KAAA,GAAQ,SAAS,KAAA,IAAS,CAAA;AAGhC,wBAAA,IAAI,OAAA,GAAU,gBAAA,CAAiB,GAAA,CAAI,KAAK,CAAA;AACxC,wBAAA,IAAI,CAAC,OAAA,EAAS;AACZ,0BAAA,OAAA,GAAU,EAAE,IAAA,EAAM,EAAA,EAAI,SAAA,EAAW,EAAA,EAAG;AACpC,0BAAA,gBAAA,CAAiB,GAAA,CAAI,OAAO,OAAO,CAAA;AAAA,wBACrC;AAGA,wBAAA,IAAI,QAAA,CAAS,UAAU,IAAA,EAAM;AAC3B,0BAAA,OAAA,CAAQ,IAAA,GAAO,SAAS,QAAA,CAAS,IAAA;AAAA,wBACnC;AAGA,wBAAA,IAAI,QAAA,CAAS,UAAU,SAAA,EAAW;AAChC,0BAAA,OAAA,CAAQ,SAAA,IAAa,SAAS,QAAA,CAAS,SAAA;AAAA,wBACzC;AAAA,sBACF;AAAA,oBACF;AAAA,kBACF,CAAA,CAAA,MAAQ;AAAA,kBAER;AAAA,gBACF;AAAA,cACF;AAAA,YACF;AAGA,YAAA,KAAA,MAAW,GAAG,QAAQ,CAAA,IAAK,gBAAA,EAAkB;AAC3C,cAAA,IAAI,QAAA,CAAS,IAAA,IAAQ,QAAA,CAAS,SAAA,EAAW;AACvC,gBAAA,IAAI;AACF,kBAAA,MAAM,IAAA,GAAO,IAAA,CAAK,KAAA,CAAM,QAAA,CAAS,SAAS,CAAA;AAC1C,kBAAA,UAAA,CAAW,QAAQ,GAAA,CAAI,UAAA,CAAW,QAAA,CAAS,IAAA,EAAM,IAAI,CAAC,CAAA;AAAA,gBACxD,SAAS,CAAA,EAAG;AACV,kBAAA,OAAA,CAAQ,KAAA,CAAM,iCAAA,EAAmC,QAAA,CAAS,SAAA,EAAW,CAAC,CAAA;AAAA,gBACxE;AAAA,cACF;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,QAAA,CAAS,MAAA,EAAQ,EAAE,OAAA,EAAS,aAAA,IAAiB,CAAA;AAAA,IAC1D,SAAS,KAAA,EAAO;AACd,MAAA,OAAA,CAAQ,KAAA,CAAM,kBAAkB,KAAK,CAAA;AACrC,MAAA,OAAO,IAAI,QAAA;AAAA,QACT,IAAA,CAAK,SAAA,CAAU,EAAE,KAAA,EAAO,yBAAyB,CAAA;AAAA,QACjD,EAAE,MAAA,EAAQ,GAAA,EAAK,SAAS,EAAE,cAAA,EAAgB,oBAAmB;AAAE,OACjE;AAAA,IACF;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 * Auto-generate system prompts from site content\n * Makes it easy for any site to get a well-structured AI assistant\n */\n\nexport interface SiteContentItem {\n /** Unique identifier for the item */\n id: string;\n /** Display name */\n name: string;\n /** Category/type (e.g., 'product', 'service', 'team', 'class') */\n category?: string;\n /** Description of the item */\n description?: string;\n /** Additional metadata */\n [key: string]: unknown;\n}\n\nexport interface SiteContent {\n /** Name of the business/site */\n name: string;\n /** Brief description of what the site/business does */\n description?: string;\n /** Type of business (e.g., 'dance studio', 'portfolio', 'e-commerce') */\n type?: string;\n /** Personality/tone for the assistant (e.g., 'friendly', 'professional', 'warm') */\n personality?: string;\n /** Available pages/sections users can navigate to */\n pages?: string[];\n /** Content items (products, services, team members, etc.) */\n items?: SiteContentItem[];\n /** FAQs */\n faqs?: Array<{ question: string; answer: string }>;\n /** Contact information */\n contact?: {\n email?: string;\n phone?: string;\n address?: string;\n hours?: string;\n };\n /** Additional context to include in the prompt */\n additionalContext?: string;\n}\n\n/**\n * Generate a system prompt from site content\n *\n * @example\n * ```ts\n * const prompt = generateSystemPrompt({\n * name: 'Acme Dance Studio',\n * type: 'dance studio',\n * personality: 'warm and encouraging',\n * pages: ['home', 'classes', 'teachers', 'schedule', 'contact'],\n * items: [\n * { id: 'ballet', name: 'Ballet', category: 'class', description: 'Classical ballet for ages 3-adult' },\n * { id: 'jazz', name: 'Jazz', category: 'class', description: 'High-energy jazz for ages 6+' },\n * { id: 'sarah', name: 'Sarah Johnson', category: 'teacher', description: 'Owner, 15 years experience' },\n * ],\n * contact: {\n * email: 'info@acmedance.com',\n * phone: '555-1234',\n * },\n * });\n * ```\n */\nexport function generateSystemPrompt(content: SiteContent): string {\n const {\n name,\n description,\n type,\n personality = 'helpful and friendly',\n pages = [],\n items = [],\n faqs = [],\n contact,\n additionalContext,\n } = content;\n\n const sections: string[] = [];\n\n // Role and personality\n sections.push(`You are the AI assistant for ${name}${type ? `, a ${type}` : ''}.${description ? ` ${description}` : ''}`);\n sections.push(`Your personality is ${personality}. Be conversational but concise.`);\n\n // Group items by category\n const itemsByCategory = items.reduce((acc, item) => {\n const cat = item.category || 'other';\n if (!acc[cat]) acc[cat] = [];\n acc[cat].push(item);\n return acc;\n }, {} as Record<string, SiteContentItem[]>);\n\n // Content sections\n if (Object.keys(itemsByCategory).length > 0) {\n sections.push('\\n## Available Content\\n');\n\n for (const [category, categoryItems] of Object.entries(itemsByCategory)) {\n const categoryTitle = category.charAt(0).toUpperCase() + category.slice(1) + 's';\n sections.push(`### ${categoryTitle}`);\n\n for (const item of categoryItems) {\n let itemLine = `- **${item.name}** (id: \"${item.id}\")`;\n if (item.description) {\n itemLine += `: ${item.description}`;\n }\n // Include any extra fields\n const extraFields = Object.entries(item)\n .filter(([k]) => !['id', 'name', 'category', 'description'].includes(k))\n .map(([k, v]) => `${k}: ${v}`)\n .join(', ');\n if (extraFields) {\n itemLine += ` [${extraFields}]`;\n }\n sections.push(itemLine);\n }\n sections.push('');\n }\n }\n\n // Pages/Navigation\n if (pages.length > 0) {\n sections.push(`## Site Sections\\nUsers can navigate to: ${pages.join(', ')}`);\n }\n\n // FAQs\n if (faqs.length > 0) {\n sections.push('\\n## Frequently Asked Questions');\n for (const faq of faqs) {\n sections.push(`**Q: ${faq.question}**`);\n sections.push(`A: ${faq.answer}\\n`);\n }\n }\n\n // Contact info\n if (contact) {\n sections.push('\\n## Contact Information');\n if (contact.email) sections.push(`- Email: ${contact.email}`);\n if (contact.phone) sections.push(`- Phone: ${contact.phone}`);\n if (contact.address) sections.push(`- Address: ${contact.address}`);\n if (contact.hours) sections.push(`- Hours: ${contact.hours}`);\n }\n\n // Additional context\n if (additionalContext) {\n sections.push(`\\n## Additional Information\\n${additionalContext}`);\n }\n\n // Instructions\n sections.push(`\n## Instructions\n- When users ask about specific items, provide detailed information from the content above\n- Use the available tools to navigate users to relevant pages or show them specific items\n- If you don't know something, say so honestly - don't make up information\n- Keep responses concise but helpful\n- Reference specific items by their id when using tools`);\n\n return sections.join('\\n');\n}\n","/**\n * Universal chat handler using OpenRouter\n * Works with any model: Gemini, GPT-4, Claude, Llama, etc.\n * No SDK required - just standard fetch.\n */\n\nimport type { ToolDefinition } from '../tools/types';\nimport { createSSEEncoder, getSSEHeaders } from './streaming';\nimport { generateSystemPrompt, type SiteContent } from './generateSystemPrompt';\n\ninterface BaseHandlerConfig {\n /** OpenRouter API key (or set OPENROUTER_API_KEY env var) */\n apiKey?: string;\n /** Model to use (e.g., 'google/gemini-2.0-flash-exp:free', 'openai/gpt-4o', 'anthropic/claude-3.5-sonnet') */\n model?: string;\n /** Tool definitions for the AI */\n tools?: ToolDefinition[];\n /** Temperature for response generation (0-1) */\n temperature?: number;\n /** Your site URL (shown in OpenRouter dashboard) */\n siteUrl?: string;\n /** Your app name (shown in OpenRouter dashboard) */\n siteName?: string;\n}\n\ninterface HandlerConfigWithPrompt extends BaseHandlerConfig {\n /** System prompt for the AI (use this OR siteContent, not both) */\n systemPrompt: string;\n siteContent?: never;\n}\n\ninterface HandlerConfigWithContent extends BaseHandlerConfig {\n /** Site content to auto-generate system prompt (use this OR systemPrompt, not both) */\n siteContent: SiteContent;\n systemPrompt?: never;\n}\n\nexport type HandlerConfig = HandlerConfigWithPrompt | HandlerConfigWithContent;\n\ninterface RequestBody {\n messages: Array<{ role: 'user' | 'assistant'; content: string }>;\n}\n\ninterface OpenRouterMessage {\n role: 'system' | 'user' | 'assistant';\n content: string;\n}\n\ninterface OpenRouterTool {\n type: 'function';\n function: {\n name: string;\n description: string;\n parameters: {\n type: 'object';\n properties: Record<string, unknown>;\n required: string[];\n };\n };\n}\n\n/**\n * Convert tool definitions to OpenAI/OpenRouter format\n */\nfunction convertTools(tools: ToolDefinition[]): OpenRouterTool[] {\n return tools.map(tool => ({\n type: 'function' as const,\n function: {\n name: tool.name,\n description: tool.description,\n parameters: {\n type: 'object' as const,\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 ...(value.enum && { enum: value.enum }),\n },\n ])\n ),\n required: tool.parameters.required,\n },\n },\n }));\n}\n\n/**\n * Create a Next.js API route handler using OpenRouter\n *\n * Works with any model - just change the model string:\n * - 'google/gemini-2.0-flash-exp:free' (free!)\n * - 'openai/gpt-4o'\n * - 'anthropic/claude-3.5-sonnet'\n * - 'meta-llama/llama-3.1-70b-instruct'\n *\n * @example\n * ```ts\n * // app/api/chat/route.ts\n * import { createHandler } from 'ai-site-pilot/api';\n *\n * export const POST = createHandler({\n * model: 'google/gemini-2.0-flash-exp:free',\n * systemPrompt: 'You are a helpful assistant...',\n * tools: myTools,\n * });\n * ```\n */\nexport function createHandler(config: HandlerConfig) {\n const {\n apiKey = process.env.OPENROUTER_API_KEY,\n model = 'google/gemini-2.0-flash-exp:free',\n tools = [],\n temperature = 0.7,\n siteUrl,\n siteName,\n } = config;\n\n // Generate system prompt from siteContent or use provided systemPrompt\n const systemPrompt = 'siteContent' in config && config.siteContent\n ? generateSystemPrompt(config.siteContent)\n : (config as HandlerConfigWithPrompt).systemPrompt;\n\n return async function POST(req: Request): Promise<Response> {\n if (!apiKey) {\n return new Response(\n JSON.stringify({ error: 'OpenRouter API key not configured. Get one at https://openrouter.ai' }),\n { status: 500, headers: { 'Content-Type': 'application/json' } }\n );\n }\n\n try {\n const body = (await req.json()) as RequestBody;\n const { messages } = body;\n\n // Build messages array with system prompt\n const openRouterMessages: OpenRouterMessage[] = [\n { role: 'system', content: systemPrompt },\n ...messages.map((m) => ({\n role: m.role === 'assistant' ? 'assistant' as const : 'user' as const,\n content: m.content,\n })),\n ];\n\n const sse = createSSEEncoder();\n\n const stream = new ReadableStream({\n async start(controller) {\n try {\n const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${apiKey}`,\n 'Content-Type': 'application/json',\n 'HTTP-Referer': siteUrl || '',\n 'X-Title': siteName || '',\n },\n body: JSON.stringify({\n model,\n messages: openRouterMessages,\n temperature,\n stream: true,\n ...(tools.length > 0 && { tools: convertTools(tools) }),\n }),\n });\n\n if (!response.ok) {\n const error = await response.text();\n console.error('OpenRouter error:', error);\n controller.enqueue(sse.encodeError('Failed to get AI response'));\n controller.enqueue(sse.encodeDone());\n controller.close();\n return;\n }\n\n const reader = response.body?.getReader();\n if (!reader) {\n controller.enqueue(sse.encodeError('No response stream'));\n controller.enqueue(sse.encodeDone());\n controller.close();\n return;\n }\n\n const decoder = new TextDecoder();\n let buffer = '';\n\n // Accumulate tool calls across stream chunks\n // OpenRouter streams tool calls in pieces: name first, then arguments in chunks\n const pendingToolCalls: Map<number, { name: string; arguments: string }> = new Map();\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 const data = line.slice(6);\n if (data === '[DONE]') continue;\n\n try {\n const parsed = JSON.parse(data);\n const delta = parsed.choices?.[0]?.delta;\n\n // Handle text content\n if (delta?.content) {\n controller.enqueue(sse.encodeText(delta.content));\n }\n\n // Handle tool calls - accumulate across chunks\n if (delta?.tool_calls) {\n for (const toolCall of delta.tool_calls) {\n const index = toolCall.index ?? 0;\n\n // Get or create pending tool call\n let pending = pendingToolCalls.get(index);\n if (!pending) {\n pending = { name: '', arguments: '' };\n pendingToolCalls.set(index, pending);\n }\n\n // Accumulate name (usually comes in first chunk)\n if (toolCall.function?.name) {\n pending.name = toolCall.function.name;\n }\n\n // Accumulate arguments (streamed in chunks)\n if (toolCall.function?.arguments) {\n pending.arguments += toolCall.function.arguments;\n }\n }\n }\n } catch {\n // Skip malformed JSON\n }\n }\n }\n }\n\n // Emit all accumulated tool calls now that streaming is complete\n for (const [, toolCall] of pendingToolCalls) {\n if (toolCall.name && toolCall.arguments) {\n try {\n const args = JSON.parse(toolCall.arguments);\n controller.enqueue(sse.encodeTool(toolCall.name, args));\n } catch (e) {\n console.error('Failed to parse tool arguments:', toolCall.arguments, e);\n }\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, { headers: getSSEHeaders() });\n } catch (error) {\n console.error('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
|
@@ -57,6 +57,81 @@ async function* parseSSEStream(reader) {
|
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
// src/api/generateSystemPrompt.ts
|
|
61
|
+
function generateSystemPrompt(content) {
|
|
62
|
+
const {
|
|
63
|
+
name,
|
|
64
|
+
description,
|
|
65
|
+
type,
|
|
66
|
+
personality = "helpful and friendly",
|
|
67
|
+
pages = [],
|
|
68
|
+
items = [],
|
|
69
|
+
faqs = [],
|
|
70
|
+
contact,
|
|
71
|
+
additionalContext
|
|
72
|
+
} = content;
|
|
73
|
+
const sections = [];
|
|
74
|
+
sections.push(`You are the AI assistant for ${name}${type ? `, a ${type}` : ""}.${description ? ` ${description}` : ""}`);
|
|
75
|
+
sections.push(`Your personality is ${personality}. Be conversational but concise.`);
|
|
76
|
+
const itemsByCategory = items.reduce((acc, item) => {
|
|
77
|
+
const cat = item.category || "other";
|
|
78
|
+
if (!acc[cat]) acc[cat] = [];
|
|
79
|
+
acc[cat].push(item);
|
|
80
|
+
return acc;
|
|
81
|
+
}, {});
|
|
82
|
+
if (Object.keys(itemsByCategory).length > 0) {
|
|
83
|
+
sections.push("\n## Available Content\n");
|
|
84
|
+
for (const [category, categoryItems] of Object.entries(itemsByCategory)) {
|
|
85
|
+
const categoryTitle = category.charAt(0).toUpperCase() + category.slice(1) + "s";
|
|
86
|
+
sections.push(`### ${categoryTitle}`);
|
|
87
|
+
for (const item of categoryItems) {
|
|
88
|
+
let itemLine = `- **${item.name}** (id: "${item.id}")`;
|
|
89
|
+
if (item.description) {
|
|
90
|
+
itemLine += `: ${item.description}`;
|
|
91
|
+
}
|
|
92
|
+
const extraFields = Object.entries(item).filter(([k]) => !["id", "name", "category", "description"].includes(k)).map(([k, v]) => `${k}: ${v}`).join(", ");
|
|
93
|
+
if (extraFields) {
|
|
94
|
+
itemLine += ` [${extraFields}]`;
|
|
95
|
+
}
|
|
96
|
+
sections.push(itemLine);
|
|
97
|
+
}
|
|
98
|
+
sections.push("");
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (pages.length > 0) {
|
|
102
|
+
sections.push(`## Site Sections
|
|
103
|
+
Users can navigate to: ${pages.join(", ")}`);
|
|
104
|
+
}
|
|
105
|
+
if (faqs.length > 0) {
|
|
106
|
+
sections.push("\n## Frequently Asked Questions");
|
|
107
|
+
for (const faq of faqs) {
|
|
108
|
+
sections.push(`**Q: ${faq.question}**`);
|
|
109
|
+
sections.push(`A: ${faq.answer}
|
|
110
|
+
`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (contact) {
|
|
114
|
+
sections.push("\n## Contact Information");
|
|
115
|
+
if (contact.email) sections.push(`- Email: ${contact.email}`);
|
|
116
|
+
if (contact.phone) sections.push(`- Phone: ${contact.phone}`);
|
|
117
|
+
if (contact.address) sections.push(`- Address: ${contact.address}`);
|
|
118
|
+
if (contact.hours) sections.push(`- Hours: ${contact.hours}`);
|
|
119
|
+
}
|
|
120
|
+
if (additionalContext) {
|
|
121
|
+
sections.push(`
|
|
122
|
+
## Additional Information
|
|
123
|
+
${additionalContext}`);
|
|
124
|
+
}
|
|
125
|
+
sections.push(`
|
|
126
|
+
## Instructions
|
|
127
|
+
- When users ask about specific items, provide detailed information from the content above
|
|
128
|
+
- Use the available tools to navigate users to relevant pages or show them specific items
|
|
129
|
+
- If you don't know something, say so honestly - don't make up information
|
|
130
|
+
- Keep responses concise but helpful
|
|
131
|
+
- Reference specific items by their id when using tools`);
|
|
132
|
+
return sections.join("\n");
|
|
133
|
+
}
|
|
134
|
+
|
|
60
135
|
// src/api/createHandler.ts
|
|
61
136
|
function convertTools(tools) {
|
|
62
137
|
return tools.map((tool) => ({
|
|
@@ -85,12 +160,12 @@ function createHandler(config) {
|
|
|
85
160
|
const {
|
|
86
161
|
apiKey = process.env.OPENROUTER_API_KEY,
|
|
87
162
|
model = "google/gemini-2.0-flash-exp:free",
|
|
88
|
-
systemPrompt,
|
|
89
163
|
tools = [],
|
|
90
164
|
temperature = 0.7,
|
|
91
165
|
siteUrl,
|
|
92
166
|
siteName
|
|
93
167
|
} = config;
|
|
168
|
+
const systemPrompt = "siteContent" in config && config.siteContent ? generateSystemPrompt(config.siteContent) : config.systemPrompt;
|
|
94
169
|
return async function POST(req) {
|
|
95
170
|
if (!apiKey) {
|
|
96
171
|
return new Response(
|
|
@@ -213,6 +288,6 @@ function createHandler(config) {
|
|
|
213
288
|
};
|
|
214
289
|
}
|
|
215
290
|
|
|
216
|
-
export { createHandler, createSSEEncoder, getSSEHeaders, parseSSEStream };
|
|
291
|
+
export { createHandler, createSSEEncoder, generateSystemPrompt, getSSEHeaders, parseSSEStream };
|
|
217
292
|
//# sourceMappingURL=index.mjs.map
|
|
218
293
|
//# sourceMappingURL=index.mjs.map
|
package/dist/api/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/api/streaming.ts","../../src/api/createHandler.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 * Universal chat handler using OpenRouter\n * Works with any model: Gemini, GPT-4, Claude, Llama, etc.\n * No SDK required - just standard fetch.\n */\n\nimport type { ToolDefinition } from '../tools/types';\nimport { createSSEEncoder, getSSEHeaders } from './streaming';\n\nexport interface HandlerConfig {\n /** OpenRouter API key (or set OPENROUTER_API_KEY env var) */\n apiKey?: string;\n /** Model to use (e.g., 'google/gemini-2.0-flash-exp:free', 'openai/gpt-4o', 'anthropic/claude-3.5-sonnet') */\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 /** Your site URL (shown in OpenRouter dashboard) */\n siteUrl?: string;\n /** Your app name (shown in OpenRouter dashboard) */\n siteName?: string;\n}\n\ninterface RequestBody {\n messages: Array<{ role: 'user' | 'assistant'; content: string }>;\n}\n\ninterface OpenRouterMessage {\n role: 'system' | 'user' | 'assistant';\n content: string;\n}\n\ninterface OpenRouterTool {\n type: 'function';\n function: {\n name: string;\n description: string;\n parameters: {\n type: 'object';\n properties: Record<string, unknown>;\n required: string[];\n };\n };\n}\n\n/**\n * Convert tool definitions to OpenAI/OpenRouter format\n */\nfunction convertTools(tools: ToolDefinition[]): OpenRouterTool[] {\n return tools.map(tool => ({\n type: 'function' as const,\n function: {\n name: tool.name,\n description: tool.description,\n parameters: {\n type: 'object' as const,\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 ...(value.enum && { enum: value.enum }),\n },\n ])\n ),\n required: tool.parameters.required,\n },\n },\n }));\n}\n\n/**\n * Create a Next.js API route handler using OpenRouter\n *\n * Works with any model - just change the model string:\n * - 'google/gemini-2.0-flash-exp:free' (free!)\n * - 'openai/gpt-4o'\n * - 'anthropic/claude-3.5-sonnet'\n * - 'meta-llama/llama-3.1-70b-instruct'\n *\n * @example\n * ```ts\n * // app/api/chat/route.ts\n * import { createHandler } from 'ai-site-pilot/api';\n *\n * export const POST = createHandler({\n * model: 'google/gemini-2.0-flash-exp:free',\n * systemPrompt: 'You are a helpful assistant...',\n * tools: myTools,\n * });\n * ```\n */\nexport function createHandler(config: HandlerConfig) {\n const {\n apiKey = process.env.OPENROUTER_API_KEY,\n model = 'google/gemini-2.0-flash-exp:free',\n systemPrompt,\n tools = [],\n temperature = 0.7,\n siteUrl,\n siteName,\n } = config;\n\n return async function POST(req: Request): Promise<Response> {\n if (!apiKey) {\n return new Response(\n JSON.stringify({ error: 'OpenRouter API key not configured. Get one at https://openrouter.ai' }),\n { status: 500, headers: { 'Content-Type': 'application/json' } }\n );\n }\n\n try {\n const body = (await req.json()) as RequestBody;\n const { messages } = body;\n\n // Build messages array with system prompt\n const openRouterMessages: OpenRouterMessage[] = [\n { role: 'system', content: systemPrompt },\n ...messages.map((m) => ({\n role: m.role === 'assistant' ? 'assistant' as const : 'user' as const,\n content: m.content,\n })),\n ];\n\n const sse = createSSEEncoder();\n\n const stream = new ReadableStream({\n async start(controller) {\n try {\n const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${apiKey}`,\n 'Content-Type': 'application/json',\n 'HTTP-Referer': siteUrl || '',\n 'X-Title': siteName || '',\n },\n body: JSON.stringify({\n model,\n messages: openRouterMessages,\n temperature,\n stream: true,\n ...(tools.length > 0 && { tools: convertTools(tools) }),\n }),\n });\n\n if (!response.ok) {\n const error = await response.text();\n console.error('OpenRouter error:', error);\n controller.enqueue(sse.encodeError('Failed to get AI response'));\n controller.enqueue(sse.encodeDone());\n controller.close();\n return;\n }\n\n const reader = response.body?.getReader();\n if (!reader) {\n controller.enqueue(sse.encodeError('No response stream'));\n controller.enqueue(sse.encodeDone());\n controller.close();\n return;\n }\n\n const decoder = new TextDecoder();\n let buffer = '';\n\n // Accumulate tool calls across stream chunks\n // OpenRouter streams tool calls in pieces: name first, then arguments in chunks\n const pendingToolCalls: Map<number, { name: string; arguments: string }> = new Map();\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 const data = line.slice(6);\n if (data === '[DONE]') continue;\n\n try {\n const parsed = JSON.parse(data);\n const delta = parsed.choices?.[0]?.delta;\n\n // Handle text content\n if (delta?.content) {\n controller.enqueue(sse.encodeText(delta.content));\n }\n\n // Handle tool calls - accumulate across chunks\n if (delta?.tool_calls) {\n for (const toolCall of delta.tool_calls) {\n const index = toolCall.index ?? 0;\n\n // Get or create pending tool call\n let pending = pendingToolCalls.get(index);\n if (!pending) {\n pending = { name: '', arguments: '' };\n pendingToolCalls.set(index, pending);\n }\n\n // Accumulate name (usually comes in first chunk)\n if (toolCall.function?.name) {\n pending.name = toolCall.function.name;\n }\n\n // Accumulate arguments (streamed in chunks)\n if (toolCall.function?.arguments) {\n pending.arguments += toolCall.function.arguments;\n }\n }\n }\n } catch {\n // Skip malformed JSON\n }\n }\n }\n }\n\n // Emit all accumulated tool calls now that streaming is complete\n for (const [, toolCall] of pendingToolCalls) {\n if (toolCall.name && toolCall.arguments) {\n try {\n const args = JSON.parse(toolCall.arguments);\n controller.enqueue(sse.encodeTool(toolCall.name, args));\n } catch (e) {\n console.error('Failed to parse tool arguments:', toolCall.arguments, e);\n }\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, { headers: getSSEHeaders() });\n } catch (error) {\n console.error('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"]}
|
|
1
|
+
{"version":3,"sources":["../../src/api/streaming.ts","../../src/api/generateSystemPrompt.ts","../../src/api/createHandler.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 * Auto-generate system prompts from site content\n * Makes it easy for any site to get a well-structured AI assistant\n */\n\nexport interface SiteContentItem {\n /** Unique identifier for the item */\n id: string;\n /** Display name */\n name: string;\n /** Category/type (e.g., 'product', 'service', 'team', 'class') */\n category?: string;\n /** Description of the item */\n description?: string;\n /** Additional metadata */\n [key: string]: unknown;\n}\n\nexport interface SiteContent {\n /** Name of the business/site */\n name: string;\n /** Brief description of what the site/business does */\n description?: string;\n /** Type of business (e.g., 'dance studio', 'portfolio', 'e-commerce') */\n type?: string;\n /** Personality/tone for the assistant (e.g., 'friendly', 'professional', 'warm') */\n personality?: string;\n /** Available pages/sections users can navigate to */\n pages?: string[];\n /** Content items (products, services, team members, etc.) */\n items?: SiteContentItem[];\n /** FAQs */\n faqs?: Array<{ question: string; answer: string }>;\n /** Contact information */\n contact?: {\n email?: string;\n phone?: string;\n address?: string;\n hours?: string;\n };\n /** Additional context to include in the prompt */\n additionalContext?: string;\n}\n\n/**\n * Generate a system prompt from site content\n *\n * @example\n * ```ts\n * const prompt = generateSystemPrompt({\n * name: 'Acme Dance Studio',\n * type: 'dance studio',\n * personality: 'warm and encouraging',\n * pages: ['home', 'classes', 'teachers', 'schedule', 'contact'],\n * items: [\n * { id: 'ballet', name: 'Ballet', category: 'class', description: 'Classical ballet for ages 3-adult' },\n * { id: 'jazz', name: 'Jazz', category: 'class', description: 'High-energy jazz for ages 6+' },\n * { id: 'sarah', name: 'Sarah Johnson', category: 'teacher', description: 'Owner, 15 years experience' },\n * ],\n * contact: {\n * email: 'info@acmedance.com',\n * phone: '555-1234',\n * },\n * });\n * ```\n */\nexport function generateSystemPrompt(content: SiteContent): string {\n const {\n name,\n description,\n type,\n personality = 'helpful and friendly',\n pages = [],\n items = [],\n faqs = [],\n contact,\n additionalContext,\n } = content;\n\n const sections: string[] = [];\n\n // Role and personality\n sections.push(`You are the AI assistant for ${name}${type ? `, a ${type}` : ''}.${description ? ` ${description}` : ''}`);\n sections.push(`Your personality is ${personality}. Be conversational but concise.`);\n\n // Group items by category\n const itemsByCategory = items.reduce((acc, item) => {\n const cat = item.category || 'other';\n if (!acc[cat]) acc[cat] = [];\n acc[cat].push(item);\n return acc;\n }, {} as Record<string, SiteContentItem[]>);\n\n // Content sections\n if (Object.keys(itemsByCategory).length > 0) {\n sections.push('\\n## Available Content\\n');\n\n for (const [category, categoryItems] of Object.entries(itemsByCategory)) {\n const categoryTitle = category.charAt(0).toUpperCase() + category.slice(1) + 's';\n sections.push(`### ${categoryTitle}`);\n\n for (const item of categoryItems) {\n let itemLine = `- **${item.name}** (id: \"${item.id}\")`;\n if (item.description) {\n itemLine += `: ${item.description}`;\n }\n // Include any extra fields\n const extraFields = Object.entries(item)\n .filter(([k]) => !['id', 'name', 'category', 'description'].includes(k))\n .map(([k, v]) => `${k}: ${v}`)\n .join(', ');\n if (extraFields) {\n itemLine += ` [${extraFields}]`;\n }\n sections.push(itemLine);\n }\n sections.push('');\n }\n }\n\n // Pages/Navigation\n if (pages.length > 0) {\n sections.push(`## Site Sections\\nUsers can navigate to: ${pages.join(', ')}`);\n }\n\n // FAQs\n if (faqs.length > 0) {\n sections.push('\\n## Frequently Asked Questions');\n for (const faq of faqs) {\n sections.push(`**Q: ${faq.question}**`);\n sections.push(`A: ${faq.answer}\\n`);\n }\n }\n\n // Contact info\n if (contact) {\n sections.push('\\n## Contact Information');\n if (contact.email) sections.push(`- Email: ${contact.email}`);\n if (contact.phone) sections.push(`- Phone: ${contact.phone}`);\n if (contact.address) sections.push(`- Address: ${contact.address}`);\n if (contact.hours) sections.push(`- Hours: ${contact.hours}`);\n }\n\n // Additional context\n if (additionalContext) {\n sections.push(`\\n## Additional Information\\n${additionalContext}`);\n }\n\n // Instructions\n sections.push(`\n## Instructions\n- When users ask about specific items, provide detailed information from the content above\n- Use the available tools to navigate users to relevant pages or show them specific items\n- If you don't know something, say so honestly - don't make up information\n- Keep responses concise but helpful\n- Reference specific items by their id when using tools`);\n\n return sections.join('\\n');\n}\n","/**\n * Universal chat handler using OpenRouter\n * Works with any model: Gemini, GPT-4, Claude, Llama, etc.\n * No SDK required - just standard fetch.\n */\n\nimport type { ToolDefinition } from '../tools/types';\nimport { createSSEEncoder, getSSEHeaders } from './streaming';\nimport { generateSystemPrompt, type SiteContent } from './generateSystemPrompt';\n\ninterface BaseHandlerConfig {\n /** OpenRouter API key (or set OPENROUTER_API_KEY env var) */\n apiKey?: string;\n /** Model to use (e.g., 'google/gemini-2.0-flash-exp:free', 'openai/gpt-4o', 'anthropic/claude-3.5-sonnet') */\n model?: string;\n /** Tool definitions for the AI */\n tools?: ToolDefinition[];\n /** Temperature for response generation (0-1) */\n temperature?: number;\n /** Your site URL (shown in OpenRouter dashboard) */\n siteUrl?: string;\n /** Your app name (shown in OpenRouter dashboard) */\n siteName?: string;\n}\n\ninterface HandlerConfigWithPrompt extends BaseHandlerConfig {\n /** System prompt for the AI (use this OR siteContent, not both) */\n systemPrompt: string;\n siteContent?: never;\n}\n\ninterface HandlerConfigWithContent extends BaseHandlerConfig {\n /** Site content to auto-generate system prompt (use this OR systemPrompt, not both) */\n siteContent: SiteContent;\n systemPrompt?: never;\n}\n\nexport type HandlerConfig = HandlerConfigWithPrompt | HandlerConfigWithContent;\n\ninterface RequestBody {\n messages: Array<{ role: 'user' | 'assistant'; content: string }>;\n}\n\ninterface OpenRouterMessage {\n role: 'system' | 'user' | 'assistant';\n content: string;\n}\n\ninterface OpenRouterTool {\n type: 'function';\n function: {\n name: string;\n description: string;\n parameters: {\n type: 'object';\n properties: Record<string, unknown>;\n required: string[];\n };\n };\n}\n\n/**\n * Convert tool definitions to OpenAI/OpenRouter format\n */\nfunction convertTools(tools: ToolDefinition[]): OpenRouterTool[] {\n return tools.map(tool => ({\n type: 'function' as const,\n function: {\n name: tool.name,\n description: tool.description,\n parameters: {\n type: 'object' as const,\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 ...(value.enum && { enum: value.enum }),\n },\n ])\n ),\n required: tool.parameters.required,\n },\n },\n }));\n}\n\n/**\n * Create a Next.js API route handler using OpenRouter\n *\n * Works with any model - just change the model string:\n * - 'google/gemini-2.0-flash-exp:free' (free!)\n * - 'openai/gpt-4o'\n * - 'anthropic/claude-3.5-sonnet'\n * - 'meta-llama/llama-3.1-70b-instruct'\n *\n * @example\n * ```ts\n * // app/api/chat/route.ts\n * import { createHandler } from 'ai-site-pilot/api';\n *\n * export const POST = createHandler({\n * model: 'google/gemini-2.0-flash-exp:free',\n * systemPrompt: 'You are a helpful assistant...',\n * tools: myTools,\n * });\n * ```\n */\nexport function createHandler(config: HandlerConfig) {\n const {\n apiKey = process.env.OPENROUTER_API_KEY,\n model = 'google/gemini-2.0-flash-exp:free',\n tools = [],\n temperature = 0.7,\n siteUrl,\n siteName,\n } = config;\n\n // Generate system prompt from siteContent or use provided systemPrompt\n const systemPrompt = 'siteContent' in config && config.siteContent\n ? generateSystemPrompt(config.siteContent)\n : (config as HandlerConfigWithPrompt).systemPrompt;\n\n return async function POST(req: Request): Promise<Response> {\n if (!apiKey) {\n return new Response(\n JSON.stringify({ error: 'OpenRouter API key not configured. Get one at https://openrouter.ai' }),\n { status: 500, headers: { 'Content-Type': 'application/json' } }\n );\n }\n\n try {\n const body = (await req.json()) as RequestBody;\n const { messages } = body;\n\n // Build messages array with system prompt\n const openRouterMessages: OpenRouterMessage[] = [\n { role: 'system', content: systemPrompt },\n ...messages.map((m) => ({\n role: m.role === 'assistant' ? 'assistant' as const : 'user' as const,\n content: m.content,\n })),\n ];\n\n const sse = createSSEEncoder();\n\n const stream = new ReadableStream({\n async start(controller) {\n try {\n const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${apiKey}`,\n 'Content-Type': 'application/json',\n 'HTTP-Referer': siteUrl || '',\n 'X-Title': siteName || '',\n },\n body: JSON.stringify({\n model,\n messages: openRouterMessages,\n temperature,\n stream: true,\n ...(tools.length > 0 && { tools: convertTools(tools) }),\n }),\n });\n\n if (!response.ok) {\n const error = await response.text();\n console.error('OpenRouter error:', error);\n controller.enqueue(sse.encodeError('Failed to get AI response'));\n controller.enqueue(sse.encodeDone());\n controller.close();\n return;\n }\n\n const reader = response.body?.getReader();\n if (!reader) {\n controller.enqueue(sse.encodeError('No response stream'));\n controller.enqueue(sse.encodeDone());\n controller.close();\n return;\n }\n\n const decoder = new TextDecoder();\n let buffer = '';\n\n // Accumulate tool calls across stream chunks\n // OpenRouter streams tool calls in pieces: name first, then arguments in chunks\n const pendingToolCalls: Map<number, { name: string; arguments: string }> = new Map();\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 const data = line.slice(6);\n if (data === '[DONE]') continue;\n\n try {\n const parsed = JSON.parse(data);\n const delta = parsed.choices?.[0]?.delta;\n\n // Handle text content\n if (delta?.content) {\n controller.enqueue(sse.encodeText(delta.content));\n }\n\n // Handle tool calls - accumulate across chunks\n if (delta?.tool_calls) {\n for (const toolCall of delta.tool_calls) {\n const index = toolCall.index ?? 0;\n\n // Get or create pending tool call\n let pending = pendingToolCalls.get(index);\n if (!pending) {\n pending = { name: '', arguments: '' };\n pendingToolCalls.set(index, pending);\n }\n\n // Accumulate name (usually comes in first chunk)\n if (toolCall.function?.name) {\n pending.name = toolCall.function.name;\n }\n\n // Accumulate arguments (streamed in chunks)\n if (toolCall.function?.arguments) {\n pending.arguments += toolCall.function.arguments;\n }\n }\n }\n } catch {\n // Skip malformed JSON\n }\n }\n }\n }\n\n // Emit all accumulated tool calls now that streaming is complete\n for (const [, toolCall] of pendingToolCalls) {\n if (toolCall.name && toolCall.arguments) {\n try {\n const args = JSON.parse(toolCall.arguments);\n controller.enqueue(sse.encodeTool(toolCall.name, args));\n } catch (e) {\n console.error('Failed to parse tool arguments:', toolCall.arguments, e);\n }\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, { headers: getSSEHeaders() });\n } catch (error) {\n console.error('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/package.json
CHANGED