ai-site-pilot 0.4.5 → 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 CHANGED
@@ -146,18 +146,61 @@ See all models at [openrouter.ai/models](https://openrouter.ai/models)
146
146
 
147
147
  ### `createHandler()`
148
148
 
149
- 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)
150
152
 
151
153
  ```typescript
152
154
  import { createHandler } from 'ai-site-pilot/api';
153
155
 
154
156
  export const POST = createHandler({
155
- // Required
156
- systemPrompt: 'You are a helpful assistant...',
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
157
200
 
158
201
  // Optional
159
202
  apiKey: process.env.OPENROUTER_API_KEY, // Uses env var by default
160
- model: 'google/gemini-2.0-flash-exp:free', // Default
203
+ model: 'google/gemini-2.0-flash-exp:free', // Default (free!)
161
204
  tools: [myTool1, myTool2],
162
205
  temperature: 0.7,
163
206
  siteUrl: 'https://mysite.com', // Shown in OpenRouter dashboard
@@ -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 HandlerConfig {
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 };
@@ -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 HandlerConfig {
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
@@ -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":"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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 * 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"]}
@@ -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
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-site-pilot",
3
- "version": "0.4.5",
3
+ "version": "0.5.0",
4
4
  "description": "AI chat widget that can control and navigate your website. Works with any AI model via OpenRouter.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",