@mordn/chat-widget 0.1.1 → 0.1.4

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.
@@ -1,4 +1,5 @@
1
- export { Conversation, Message, NewConversation, NewMessage, conversations, createChat, db, deleteConversation, getConversations, loadChat, messages, saveChat, updateConversationTitle } from '../db/index.mjs';
1
+ export { createChat, db, deleteConversation, getConversations, loadChat, saveChat, updateConversationTitle } from '../db/index.mjs';
2
+ export { C as Conversation, M as Message, N as NewConversation, a as NewMessage, c as conversations, m as messages } from '../index-uiN6exzS.mjs';
2
3
  export { and, asc, desc, eq, or, sql } from 'drizzle-orm';
3
4
  import 'ai';
4
5
  import 'drizzle-orm/postgres-js';
@@ -1,4 +1,5 @@
1
- export { Conversation, Message, NewConversation, NewMessage, conversations, createChat, db, deleteConversation, getConversations, loadChat, messages, saveChat, updateConversationTitle } from '../db/index.js';
1
+ export { createChat, db, deleteConversation, getConversations, loadChat, saveChat, updateConversationTitle } from '../db/index.js';
2
+ export { C as Conversation, M as Message, N as NewConversation, a as NewMessage, c as conversations, m as messages } from '../index-uiN6exzS.js';
2
3
  export { and, asc, desc, eq, or, sql } from 'drizzle-orm';
3
4
  import 'ai';
4
5
  import 'drizzle-orm/postgres-js';
package/dist/api/index.js CHANGED
@@ -176,8 +176,9 @@ async function saveChat({
176
176
  for (const msg of newMessages) {
177
177
  const textPart = msg.parts?.find((p) => p.type === "text");
178
178
  const fileParts = msg.parts?.filter((p) => p.type === "file") || [];
179
+ const messageId = msg.role === "assistant" ? (0, import_ai.generateId)() : msg.id;
179
180
  await db.insert(messages).values({
180
- id: msg.id,
181
+ id: messageId,
181
182
  conversationId: chatId,
182
183
  role: msg.role,
183
184
  content: textPart?.text || "",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/api/index.ts","../../src/db/chat-store.ts","../../src/db/index.ts","../../src/db/schema.ts"],"sourcesContent":["/**\n * API Helpers for Chat Widget\n *\n * These utilities help you create the required API routes for the chat widget.\n * Copy the route examples to your Next.js app/api folder.\n */\n\nexport {\n createChat,\n loadChat,\n saveChat,\n getConversations,\n updateConversationTitle,\n deleteConversation\n} from '../db/chat-store';\n\nexport { db, conversations, messages } from '../db';\nexport type { Conversation, Message, NewConversation, NewMessage } from '../db/schema';\n\n// Re-export drizzle utilities for convenience\nexport { eq, and, or, desc, asc, sql } from 'drizzle-orm';\n","import { generateId, UIMessage } from 'ai';\nimport { db, conversations, messages } from './index';\nimport { eq, desc, asc, sql } from 'drizzle-orm';\n\n/**\n * Create a new conversation\n */\nexport async function createChat(userId: string): Promise<string> {\n const id = generateId();\n\n await db.insert(conversations).values({\n id,\n userId,\n title: 'New Chat',\n metadata: {},\n });\n\n return id;\n}\n\n/**\n * Load messages for a conversation\n */\nexport async function loadChat(conversationId: string): Promise<UIMessage[]> {\n try {\n const dbMessages = await db\n .select()\n .from(messages)\n .where(eq(messages.conversationId, conversationId))\n .orderBy(asc(messages.createdAt));\n\n if (!dbMessages.length) return [];\n\n // Convert database messages to UIMessage format\n return dbMessages.map((msg) => {\n // If we have metadata with parts, use those (includes reasoning)\n const metadata = msg.metadata as { parts?: any[] } | null;\n if (metadata?.parts && Array.isArray(metadata.parts)) {\n return {\n id: msg.id,\n role: msg.role as 'user' | 'assistant' | 'system',\n parts: metadata.parts,\n createdAt: msg.createdAt,\n };\n }\n\n // Fallback to simple text message\n return {\n id: msg.id,\n role: msg.role as 'user' | 'assistant' | 'system',\n parts: [{ type: 'text', text: msg.content }],\n createdAt: msg.createdAt,\n };\n });\n } catch (error) {\n console.error('Error loading chat:', error);\n return [];\n }\n}\n\n/**\n * Update conversation title\n */\nexport async function updateConversationTitle(\n chatId: string,\n title: string\n): Promise<void> {\n try {\n await db\n .update(conversations)\n .set({ title, updatedAt: new Date() })\n .where(eq(conversations.id, chatId));\n } catch (error) {\n console.error('Error updating conversation title:', error);\n }\n}\n\n/**\n * Save messages to a conversation\n */\nexport async function saveChat({\n chatId,\n messages: chatMessages,\n model,\n userId,\n}: {\n chatId: string;\n messages: UIMessage[];\n model?: string;\n userId: string;\n}): Promise<void> {\n if (!userId) {\n console.error('userId is required for saveChat');\n return;\n }\n\n try {\n // Verify conversation exists\n const existingConv = await db\n .select({ id: conversations.id, title: conversations.title })\n .from(conversations)\n .where(eq(conversations.id, chatId))\n .limit(1);\n\n if (!existingConv.length) {\n console.error('Conversation not found:', chatId);\n return;\n }\n\n const conv = existingConv[0];\n\n // Update title if this is the first user message and title is still \"New Chat\"\n if (conv.title === 'New Chat') {\n const firstUserMessage = chatMessages.find((m) => m.role === 'user');\n if (firstUserMessage) {\n const textPart = firstUserMessage.parts?.find((p) => p.type === 'text') as { text: string } | undefined;\n if (textPart?.text) {\n const newTitle = textPart.text.slice(0, 100);\n await updateConversationTitle(chatId, newTitle);\n }\n }\n }\n\n // Get existing message IDs from database\n const existingMessages = await db\n .select({ id: messages.id })\n .from(messages)\n .where(eq(messages.conversationId, chatId));\n\n const existingIds = new Set(existingMessages.map((m) => m.id));\n\n // Insert only new messages\n const newMessages = chatMessages.filter((msg) => !existingIds.has(msg.id));\n\n if (newMessages.length > 0) {\n for (const msg of newMessages) {\n const textPart = msg.parts?.find((p) => p.type === 'text') as { text: string } | undefined;\n const fileParts = msg.parts?.filter((p) => p.type === 'file') || [];\n\n await db.insert(messages).values({\n id: msg.id,\n conversationId: chatId,\n role: msg.role,\n content: textPart?.text || '',\n files: fileParts,\n model: model || 'openai/gpt-4o-mini',\n metadata: { parts: msg.parts || [] },\n });\n }\n\n // Update conversation's updatedAt\n await db\n .update(conversations)\n .set({ updatedAt: new Date() })\n .where(eq(conversations.id, chatId));\n }\n } catch (error) {\n console.error('Error saving chat:', error);\n }\n}\n\n/**\n * Get all conversations for a user\n */\nexport async function getConversations(userId: string) {\n try {\n const result = await db\n .select({\n id: conversations.id,\n title: conversations.title,\n createdAt: conversations.createdAt,\n updatedAt: conversations.updatedAt,\n metadata: conversations.metadata,\n messageCount: sql<number>`(\n SELECT COUNT(*) FROM ${messages}\n WHERE ${messages.conversationId} = ${conversations.id}\n )`,\n })\n .from(conversations)\n .where(eq(conversations.userId, userId))\n .orderBy(desc(conversations.updatedAt));\n\n return result;\n } catch (error) {\n console.error('Error getting conversations:', error);\n return [];\n }\n}\n\n/**\n * Delete a conversation and all its messages\n */\nexport async function deleteConversation(chatId: string): Promise<void> {\n try {\n // Messages are deleted automatically due to cascade\n await db.delete(conversations).where(eq(conversations.id, chatId));\n } catch (error) {\n console.error('Error deleting conversation:', error);\n }\n}\n","import 'server-only';\nimport { drizzle } from 'drizzle-orm/postgres-js';\nimport postgres from 'postgres';\nimport * as schema from './schema';\n\n// Create postgres connection\nconst connectionString = process.env.DATABASE_URL!;\n\n// Disable prefetch as it is not supported for \"Transaction\" pool mode\nconst client = postgres(connectionString, { prepare: false });\n\n// Create drizzle database instance\nexport const db = drizzle(client, { schema });\n\n// Export schema for convenience\nexport * from './schema';\n\n// Export chat store functions\nexport * from './chat-store';\n","import { pgTable, text, timestamp, jsonb, index } from 'drizzle-orm/pg-core';\n\n/**\n * Conversations table\n * Stores chat conversation metadata\n */\nexport const conversations = pgTable('conversations', {\n id: text('id').primaryKey(),\n userId: text('user_id').notNull(),\n title: text('title').notNull().default('New Chat'),\n metadata: jsonb('metadata'),\n createdAt: timestamp('created_at').defaultNow().notNull(),\n updatedAt: timestamp('updated_at').defaultNow().notNull(),\n}, (table) => [\n index('conversations_user_id_idx').on(table.userId),\n index('conversations_updated_at_idx').on(table.updatedAt),\n]);\n\n/**\n * Messages table\n * Stores individual chat messages\n */\nexport const messages = pgTable('messages', {\n id: text('id').primaryKey(),\n conversationId: text('conversation_id').notNull().references(() => conversations.id, { onDelete: 'cascade' }),\n role: text('role').notNull(), // 'user' | 'assistant' | 'system'\n content: text('content').notNull(),\n files: jsonb('files'), // Array of file attachments\n model: text('model'), // AI model used\n metadata: jsonb('metadata'), // Additional data (parts, reasoning, etc.)\n createdAt: timestamp('created_at').defaultNow().notNull(),\n}, (table) => [\n index('messages_conversation_id_idx').on(table.conversationId),\n index('messages_created_at_idx').on(table.createdAt),\n]);\n\n// Type exports for use in application code\nexport type Conversation = typeof conversations.$inferSelect;\nexport type NewConversation = typeof conversations.$inferInsert;\nexport type Message = typeof messages.$inferSelect;\nexport type NewMessage = typeof messages.$inferInsert;\n"],"mappings":"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","names":["postgres","import_drizzle_orm"]}
1
+ {"version":3,"sources":["../../src/api/index.ts","../../src/db/chat-store.ts","../../src/db/index.ts","../../src/db/schema.ts"],"sourcesContent":["/**\n * API Helpers for Chat Widget\n *\n * These utilities help you create the required API routes for the chat widget.\n * Copy the route examples to your Next.js app/api folder.\n */\n\nexport {\n createChat,\n loadChat,\n saveChat,\n getConversations,\n updateConversationTitle,\n deleteConversation\n} from '../db/chat-store';\n\nexport { db, conversations, messages } from '../db';\nexport type { Conversation, Message, NewConversation, NewMessage } from '../db/schema';\n\n// Re-export drizzle utilities for convenience\nexport { eq, and, or, desc, asc, sql } from 'drizzle-orm';\n","import { generateId, UIMessage } from 'ai';\nimport { db, conversations, messages } from './index';\nimport { eq, desc, asc, sql } from 'drizzle-orm';\n\n/**\n * Create a new conversation\n */\nexport async function createChat(userId: string): Promise<string> {\n const id = generateId();\n\n await db.insert(conversations).values({\n id,\n userId,\n title: 'New Chat',\n metadata: {},\n });\n\n return id;\n}\n\n/**\n * Load messages for a conversation\n */\nexport async function loadChat(conversationId: string): Promise<UIMessage[]> {\n try {\n const dbMessages = await db\n .select()\n .from(messages)\n .where(eq(messages.conversationId, conversationId))\n .orderBy(asc(messages.createdAt));\n\n if (!dbMessages.length) return [];\n\n // Convert database messages to UIMessage format\n return dbMessages.map((msg) => {\n // If we have metadata with parts, use those (includes reasoning)\n const metadata = msg.metadata as { parts?: any[] } | null;\n if (metadata?.parts && Array.isArray(metadata.parts)) {\n return {\n id: msg.id,\n role: msg.role as 'user' | 'assistant' | 'system',\n parts: metadata.parts,\n createdAt: msg.createdAt,\n };\n }\n\n // Fallback to simple text message\n return {\n id: msg.id,\n role: msg.role as 'user' | 'assistant' | 'system',\n parts: [{ type: 'text', text: msg.content }],\n createdAt: msg.createdAt,\n };\n });\n } catch (error) {\n console.error('Error loading chat:', error);\n return [];\n }\n}\n\n/**\n * Update conversation title\n */\nexport async function updateConversationTitle(\n chatId: string,\n title: string\n): Promise<void> {\n try {\n await db\n .update(conversations)\n .set({ title, updatedAt: new Date() })\n .where(eq(conversations.id, chatId));\n } catch (error) {\n console.error('Error updating conversation title:', error);\n }\n}\n\n/**\n * Save messages to a conversation\n */\nexport async function saveChat({\n chatId,\n messages: chatMessages,\n model,\n userId,\n}: {\n chatId: string;\n messages: UIMessage[];\n model?: string;\n userId: string;\n}): Promise<void> {\n if (!userId) {\n console.error('userId is required for saveChat');\n return;\n }\n\n try {\n // Verify conversation exists\n const existingConv = await db\n .select({ id: conversations.id, title: conversations.title })\n .from(conversations)\n .where(eq(conversations.id, chatId))\n .limit(1);\n\n if (!existingConv.length) {\n console.error('Conversation not found:', chatId);\n return;\n }\n\n const conv = existingConv[0];\n\n // Update title if this is the first user message and title is still \"New Chat\"\n if (conv.title === 'New Chat') {\n const firstUserMessage = chatMessages.find((m) => m.role === 'user');\n if (firstUserMessage) {\n const textPart = firstUserMessage.parts?.find((p) => p.type === 'text') as { text: string } | undefined;\n if (textPart?.text) {\n const newTitle = textPart.text.slice(0, 100);\n await updateConversationTitle(chatId, newTitle);\n }\n }\n }\n\n // Get existing message IDs from database\n const existingMessages = await db\n .select({ id: messages.id })\n .from(messages)\n .where(eq(messages.conversationId, chatId));\n\n const existingIds = new Set(existingMessages.map((m) => m.id));\n\n // Insert only new messages\n const newMessages = chatMessages.filter((msg) => !existingIds.has(msg.id));\n\n if (newMessages.length > 0) {\n for (const msg of newMessages) {\n const textPart = msg.parts?.find((p) => p.type === 'text') as { text: string } | undefined;\n const fileParts = msg.parts?.filter((p) => p.type === 'file') || [];\n\n // Generate ID for assistant messages, use existing ID for user messages\n const messageId = msg.role === 'assistant' ? generateId() : msg.id;\n\n await db.insert(messages).values({\n id: messageId,\n conversationId: chatId,\n role: msg.role,\n content: textPart?.text || '',\n files: fileParts,\n model: model || 'openai/gpt-4o-mini',\n metadata: { parts: msg.parts || [] },\n });\n }\n\n // Update conversation's updatedAt\n await db\n .update(conversations)\n .set({ updatedAt: new Date() })\n .where(eq(conversations.id, chatId));\n }\n } catch (error) {\n console.error('Error saving chat:', error);\n }\n}\n\n/**\n * Get all conversations for a user\n */\nexport async function getConversations(userId: string) {\n try {\n const result = await db\n .select({\n id: conversations.id,\n title: conversations.title,\n createdAt: conversations.createdAt,\n updatedAt: conversations.updatedAt,\n metadata: conversations.metadata,\n messageCount: sql<number>`(\n SELECT COUNT(*) FROM ${messages}\n WHERE ${messages.conversationId} = ${conversations.id}\n )`,\n })\n .from(conversations)\n .where(eq(conversations.userId, userId))\n .orderBy(desc(conversations.updatedAt));\n\n return result;\n } catch (error) {\n console.error('Error getting conversations:', error);\n return [];\n }\n}\n\n/**\n * Delete a conversation and all its messages\n */\nexport async function deleteConversation(chatId: string): Promise<void> {\n try {\n // Messages are deleted automatically due to cascade\n await db.delete(conversations).where(eq(conversations.id, chatId));\n } catch (error) {\n console.error('Error deleting conversation:', error);\n }\n}\n","import 'server-only';\nimport { drizzle } from 'drizzle-orm/postgres-js';\nimport postgres from 'postgres';\nimport * as schema from './schema';\n\n// Create postgres connection\nconst connectionString = process.env.DATABASE_URL!;\n\n// Disable prefetch as it is not supported for \"Transaction\" pool mode\nconst client = postgres(connectionString, { prepare: false });\n\n// Create drizzle database instance\nexport const db = drizzle(client, { schema });\n\n// Export schema for convenience\nexport * from './schema';\n\n// Export chat store functions\nexport * from './chat-store';\n","import { pgTable, text, timestamp, jsonb, index } from 'drizzle-orm/pg-core';\n\n/**\n * Conversations table\n * Stores chat conversation metadata\n */\nexport const conversations = pgTable('conversations', {\n id: text('id').primaryKey(),\n userId: text('user_id').notNull(),\n title: text('title').notNull().default('New Chat'),\n metadata: jsonb('metadata'),\n createdAt: timestamp('created_at').defaultNow().notNull(),\n updatedAt: timestamp('updated_at').defaultNow().notNull(),\n}, (table) => [\n index('conversations_user_id_idx').on(table.userId),\n index('conversations_updated_at_idx').on(table.updatedAt),\n]);\n\n/**\n * Messages table\n * Stores individual chat messages\n */\nexport const messages = pgTable('messages', {\n id: text('id').primaryKey(),\n conversationId: text('conversation_id').notNull().references(() => conversations.id, { onDelete: 'cascade' }),\n role: text('role').notNull(), // 'user' | 'assistant' | 'system'\n content: text('content').notNull(),\n files: jsonb('files'), // Array of file attachments\n model: text('model'), // AI model used\n metadata: jsonb('metadata'), // Additional data (parts, reasoning, etc.)\n createdAt: timestamp('created_at').defaultNow().notNull(),\n}, (table) => [\n index('messages_conversation_id_idx').on(table.conversationId),\n index('messages_created_at_idx').on(table.createdAt),\n]);\n\n// Type exports for use in application code\nexport type Conversation = typeof conversations.$inferSelect;\nexport type NewConversation = typeof conversations.$inferInsert;\nexport type Message = typeof messages.$inferSelect;\nexport type NewMessage = typeof messages.$inferInsert;\n"],"mappings":"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","names":["postgres","import_drizzle_orm"]}
@@ -132,8 +132,9 @@ async function saveChat({
132
132
  for (const msg of newMessages) {
133
133
  const textPart = msg.parts?.find((p) => p.type === "text");
134
134
  const fileParts = msg.parts?.filter((p) => p.type === "file") || [];
135
+ const messageId = msg.role === "assistant" ? generateId() : msg.id;
135
136
  await db.insert(messages).values({
136
- id: msg.id,
137
+ id: messageId,
137
138
  conversationId: chatId,
138
139
  role: msg.role,
139
140
  content: textPart?.text || "",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/db/chat-store.ts","../../src/db/index.ts","../../src/db/schema.ts","../../src/api/index.ts"],"sourcesContent":["import { generateId, UIMessage } from 'ai';\nimport { db, conversations, messages } from './index';\nimport { eq, desc, asc, sql } from 'drizzle-orm';\n\n/**\n * Create a new conversation\n */\nexport async function createChat(userId: string): Promise<string> {\n const id = generateId();\n\n await db.insert(conversations).values({\n id,\n userId,\n title: 'New Chat',\n metadata: {},\n });\n\n return id;\n}\n\n/**\n * Load messages for a conversation\n */\nexport async function loadChat(conversationId: string): Promise<UIMessage[]> {\n try {\n const dbMessages = await db\n .select()\n .from(messages)\n .where(eq(messages.conversationId, conversationId))\n .orderBy(asc(messages.createdAt));\n\n if (!dbMessages.length) return [];\n\n // Convert database messages to UIMessage format\n return dbMessages.map((msg) => {\n // If we have metadata with parts, use those (includes reasoning)\n const metadata = msg.metadata as { parts?: any[] } | null;\n if (metadata?.parts && Array.isArray(metadata.parts)) {\n return {\n id: msg.id,\n role: msg.role as 'user' | 'assistant' | 'system',\n parts: metadata.parts,\n createdAt: msg.createdAt,\n };\n }\n\n // Fallback to simple text message\n return {\n id: msg.id,\n role: msg.role as 'user' | 'assistant' | 'system',\n parts: [{ type: 'text', text: msg.content }],\n createdAt: msg.createdAt,\n };\n });\n } catch (error) {\n console.error('Error loading chat:', error);\n return [];\n }\n}\n\n/**\n * Update conversation title\n */\nexport async function updateConversationTitle(\n chatId: string,\n title: string\n): Promise<void> {\n try {\n await db\n .update(conversations)\n .set({ title, updatedAt: new Date() })\n .where(eq(conversations.id, chatId));\n } catch (error) {\n console.error('Error updating conversation title:', error);\n }\n}\n\n/**\n * Save messages to a conversation\n */\nexport async function saveChat({\n chatId,\n messages: chatMessages,\n model,\n userId,\n}: {\n chatId: string;\n messages: UIMessage[];\n model?: string;\n userId: string;\n}): Promise<void> {\n if (!userId) {\n console.error('userId is required for saveChat');\n return;\n }\n\n try {\n // Verify conversation exists\n const existingConv = await db\n .select({ id: conversations.id, title: conversations.title })\n .from(conversations)\n .where(eq(conversations.id, chatId))\n .limit(1);\n\n if (!existingConv.length) {\n console.error('Conversation not found:', chatId);\n return;\n }\n\n const conv = existingConv[0];\n\n // Update title if this is the first user message and title is still \"New Chat\"\n if (conv.title === 'New Chat') {\n const firstUserMessage = chatMessages.find((m) => m.role === 'user');\n if (firstUserMessage) {\n const textPart = firstUserMessage.parts?.find((p) => p.type === 'text') as { text: string } | undefined;\n if (textPart?.text) {\n const newTitle = textPart.text.slice(0, 100);\n await updateConversationTitle(chatId, newTitle);\n }\n }\n }\n\n // Get existing message IDs from database\n const existingMessages = await db\n .select({ id: messages.id })\n .from(messages)\n .where(eq(messages.conversationId, chatId));\n\n const existingIds = new Set(existingMessages.map((m) => m.id));\n\n // Insert only new messages\n const newMessages = chatMessages.filter((msg) => !existingIds.has(msg.id));\n\n if (newMessages.length > 0) {\n for (const msg of newMessages) {\n const textPart = msg.parts?.find((p) => p.type === 'text') as { text: string } | undefined;\n const fileParts = msg.parts?.filter((p) => p.type === 'file') || [];\n\n await db.insert(messages).values({\n id: msg.id,\n conversationId: chatId,\n role: msg.role,\n content: textPart?.text || '',\n files: fileParts,\n model: model || 'openai/gpt-4o-mini',\n metadata: { parts: msg.parts || [] },\n });\n }\n\n // Update conversation's updatedAt\n await db\n .update(conversations)\n .set({ updatedAt: new Date() })\n .where(eq(conversations.id, chatId));\n }\n } catch (error) {\n console.error('Error saving chat:', error);\n }\n}\n\n/**\n * Get all conversations for a user\n */\nexport async function getConversations(userId: string) {\n try {\n const result = await db\n .select({\n id: conversations.id,\n title: conversations.title,\n createdAt: conversations.createdAt,\n updatedAt: conversations.updatedAt,\n metadata: conversations.metadata,\n messageCount: sql<number>`(\n SELECT COUNT(*) FROM ${messages}\n WHERE ${messages.conversationId} = ${conversations.id}\n )`,\n })\n .from(conversations)\n .where(eq(conversations.userId, userId))\n .orderBy(desc(conversations.updatedAt));\n\n return result;\n } catch (error) {\n console.error('Error getting conversations:', error);\n return [];\n }\n}\n\n/**\n * Delete a conversation and all its messages\n */\nexport async function deleteConversation(chatId: string): Promise<void> {\n try {\n // Messages are deleted automatically due to cascade\n await db.delete(conversations).where(eq(conversations.id, chatId));\n } catch (error) {\n console.error('Error deleting conversation:', error);\n }\n}\n","import 'server-only';\nimport { drizzle } from 'drizzle-orm/postgres-js';\nimport postgres from 'postgres';\nimport * as schema from './schema';\n\n// Create postgres connection\nconst connectionString = process.env.DATABASE_URL!;\n\n// Disable prefetch as it is not supported for \"Transaction\" pool mode\nconst client = postgres(connectionString, { prepare: false });\n\n// Create drizzle database instance\nexport const db = drizzle(client, { schema });\n\n// Export schema for convenience\nexport * from './schema';\n\n// Export chat store functions\nexport * from './chat-store';\n","import { pgTable, text, timestamp, jsonb, index } from 'drizzle-orm/pg-core';\n\n/**\n * Conversations table\n * Stores chat conversation metadata\n */\nexport const conversations = pgTable('conversations', {\n id: text('id').primaryKey(),\n userId: text('user_id').notNull(),\n title: text('title').notNull().default('New Chat'),\n metadata: jsonb('metadata'),\n createdAt: timestamp('created_at').defaultNow().notNull(),\n updatedAt: timestamp('updated_at').defaultNow().notNull(),\n}, (table) => [\n index('conversations_user_id_idx').on(table.userId),\n index('conversations_updated_at_idx').on(table.updatedAt),\n]);\n\n/**\n * Messages table\n * Stores individual chat messages\n */\nexport const messages = pgTable('messages', {\n id: text('id').primaryKey(),\n conversationId: text('conversation_id').notNull().references(() => conversations.id, { onDelete: 'cascade' }),\n role: text('role').notNull(), // 'user' | 'assistant' | 'system'\n content: text('content').notNull(),\n files: jsonb('files'), // Array of file attachments\n model: text('model'), // AI model used\n metadata: jsonb('metadata'), // Additional data (parts, reasoning, etc.)\n createdAt: timestamp('created_at').defaultNow().notNull(),\n}, (table) => [\n index('messages_conversation_id_idx').on(table.conversationId),\n index('messages_created_at_idx').on(table.createdAt),\n]);\n\n// Type exports for use in application code\nexport type Conversation = typeof conversations.$inferSelect;\nexport type NewConversation = typeof conversations.$inferInsert;\nexport type Message = typeof messages.$inferSelect;\nexport type NewMessage = typeof messages.$inferInsert;\n","/**\n * API Helpers for Chat Widget\n *\n * These utilities help you create the required API routes for the chat widget.\n * Copy the route examples to your Next.js app/api folder.\n */\n\nexport {\n createChat,\n loadChat,\n saveChat,\n getConversations,\n updateConversationTitle,\n deleteConversation\n} from '../db/chat-store';\n\nexport { db, conversations, messages } from '../db';\nexport type { Conversation, Message, NewConversation, NewMessage } from '../db/schema';\n\n// Re-export drizzle utilities for convenience\nexport { eq, and, or, desc, asc, sql } from 'drizzle-orm';\n"],"mappings":"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","names":["eq","desc","asc","sql"]}
1
+ {"version":3,"sources":["../../src/db/chat-store.ts","../../src/db/index.ts","../../src/db/schema.ts","../../src/api/index.ts"],"sourcesContent":["import { generateId, UIMessage } from 'ai';\nimport { db, conversations, messages } from './index';\nimport { eq, desc, asc, sql } from 'drizzle-orm';\n\n/**\n * Create a new conversation\n */\nexport async function createChat(userId: string): Promise<string> {\n const id = generateId();\n\n await db.insert(conversations).values({\n id,\n userId,\n title: 'New Chat',\n metadata: {},\n });\n\n return id;\n}\n\n/**\n * Load messages for a conversation\n */\nexport async function loadChat(conversationId: string): Promise<UIMessage[]> {\n try {\n const dbMessages = await db\n .select()\n .from(messages)\n .where(eq(messages.conversationId, conversationId))\n .orderBy(asc(messages.createdAt));\n\n if (!dbMessages.length) return [];\n\n // Convert database messages to UIMessage format\n return dbMessages.map((msg) => {\n // If we have metadata with parts, use those (includes reasoning)\n const metadata = msg.metadata as { parts?: any[] } | null;\n if (metadata?.parts && Array.isArray(metadata.parts)) {\n return {\n id: msg.id,\n role: msg.role as 'user' | 'assistant' | 'system',\n parts: metadata.parts,\n createdAt: msg.createdAt,\n };\n }\n\n // Fallback to simple text message\n return {\n id: msg.id,\n role: msg.role as 'user' | 'assistant' | 'system',\n parts: [{ type: 'text', text: msg.content }],\n createdAt: msg.createdAt,\n };\n });\n } catch (error) {\n console.error('Error loading chat:', error);\n return [];\n }\n}\n\n/**\n * Update conversation title\n */\nexport async function updateConversationTitle(\n chatId: string,\n title: string\n): Promise<void> {\n try {\n await db\n .update(conversations)\n .set({ title, updatedAt: new Date() })\n .where(eq(conversations.id, chatId));\n } catch (error) {\n console.error('Error updating conversation title:', error);\n }\n}\n\n/**\n * Save messages to a conversation\n */\nexport async function saveChat({\n chatId,\n messages: chatMessages,\n model,\n userId,\n}: {\n chatId: string;\n messages: UIMessage[];\n model?: string;\n userId: string;\n}): Promise<void> {\n if (!userId) {\n console.error('userId is required for saveChat');\n return;\n }\n\n try {\n // Verify conversation exists\n const existingConv = await db\n .select({ id: conversations.id, title: conversations.title })\n .from(conversations)\n .where(eq(conversations.id, chatId))\n .limit(1);\n\n if (!existingConv.length) {\n console.error('Conversation not found:', chatId);\n return;\n }\n\n const conv = existingConv[0];\n\n // Update title if this is the first user message and title is still \"New Chat\"\n if (conv.title === 'New Chat') {\n const firstUserMessage = chatMessages.find((m) => m.role === 'user');\n if (firstUserMessage) {\n const textPart = firstUserMessage.parts?.find((p) => p.type === 'text') as { text: string } | undefined;\n if (textPart?.text) {\n const newTitle = textPart.text.slice(0, 100);\n await updateConversationTitle(chatId, newTitle);\n }\n }\n }\n\n // Get existing message IDs from database\n const existingMessages = await db\n .select({ id: messages.id })\n .from(messages)\n .where(eq(messages.conversationId, chatId));\n\n const existingIds = new Set(existingMessages.map((m) => m.id));\n\n // Insert only new messages\n const newMessages = chatMessages.filter((msg) => !existingIds.has(msg.id));\n\n if (newMessages.length > 0) {\n for (const msg of newMessages) {\n const textPart = msg.parts?.find((p) => p.type === 'text') as { text: string } | undefined;\n const fileParts = msg.parts?.filter((p) => p.type === 'file') || [];\n\n // Generate ID for assistant messages, use existing ID for user messages\n const messageId = msg.role === 'assistant' ? generateId() : msg.id;\n\n await db.insert(messages).values({\n id: messageId,\n conversationId: chatId,\n role: msg.role,\n content: textPart?.text || '',\n files: fileParts,\n model: model || 'openai/gpt-4o-mini',\n metadata: { parts: msg.parts || [] },\n });\n }\n\n // Update conversation's updatedAt\n await db\n .update(conversations)\n .set({ updatedAt: new Date() })\n .where(eq(conversations.id, chatId));\n }\n } catch (error) {\n console.error('Error saving chat:', error);\n }\n}\n\n/**\n * Get all conversations for a user\n */\nexport async function getConversations(userId: string) {\n try {\n const result = await db\n .select({\n id: conversations.id,\n title: conversations.title,\n createdAt: conversations.createdAt,\n updatedAt: conversations.updatedAt,\n metadata: conversations.metadata,\n messageCount: sql<number>`(\n SELECT COUNT(*) FROM ${messages}\n WHERE ${messages.conversationId} = ${conversations.id}\n )`,\n })\n .from(conversations)\n .where(eq(conversations.userId, userId))\n .orderBy(desc(conversations.updatedAt));\n\n return result;\n } catch (error) {\n console.error('Error getting conversations:', error);\n return [];\n }\n}\n\n/**\n * Delete a conversation and all its messages\n */\nexport async function deleteConversation(chatId: string): Promise<void> {\n try {\n // Messages are deleted automatically due to cascade\n await db.delete(conversations).where(eq(conversations.id, chatId));\n } catch (error) {\n console.error('Error deleting conversation:', error);\n }\n}\n","import 'server-only';\nimport { drizzle } from 'drizzle-orm/postgres-js';\nimport postgres from 'postgres';\nimport * as schema from './schema';\n\n// Create postgres connection\nconst connectionString = process.env.DATABASE_URL!;\n\n// Disable prefetch as it is not supported for \"Transaction\" pool mode\nconst client = postgres(connectionString, { prepare: false });\n\n// Create drizzle database instance\nexport const db = drizzle(client, { schema });\n\n// Export schema for convenience\nexport * from './schema';\n\n// Export chat store functions\nexport * from './chat-store';\n","import { pgTable, text, timestamp, jsonb, index } from 'drizzle-orm/pg-core';\n\n/**\n * Conversations table\n * Stores chat conversation metadata\n */\nexport const conversations = pgTable('conversations', {\n id: text('id').primaryKey(),\n userId: text('user_id').notNull(),\n title: text('title').notNull().default('New Chat'),\n metadata: jsonb('metadata'),\n createdAt: timestamp('created_at').defaultNow().notNull(),\n updatedAt: timestamp('updated_at').defaultNow().notNull(),\n}, (table) => [\n index('conversations_user_id_idx').on(table.userId),\n index('conversations_updated_at_idx').on(table.updatedAt),\n]);\n\n/**\n * Messages table\n * Stores individual chat messages\n */\nexport const messages = pgTable('messages', {\n id: text('id').primaryKey(),\n conversationId: text('conversation_id').notNull().references(() => conversations.id, { onDelete: 'cascade' }),\n role: text('role').notNull(), // 'user' | 'assistant' | 'system'\n content: text('content').notNull(),\n files: jsonb('files'), // Array of file attachments\n model: text('model'), // AI model used\n metadata: jsonb('metadata'), // Additional data (parts, reasoning, etc.)\n createdAt: timestamp('created_at').defaultNow().notNull(),\n}, (table) => [\n index('messages_conversation_id_idx').on(table.conversationId),\n index('messages_created_at_idx').on(table.createdAt),\n]);\n\n// Type exports for use in application code\nexport type Conversation = typeof conversations.$inferSelect;\nexport type NewConversation = typeof conversations.$inferInsert;\nexport type Message = typeof messages.$inferSelect;\nexport type NewMessage = typeof messages.$inferInsert;\n","/**\n * API Helpers for Chat Widget\n *\n * These utilities help you create the required API routes for the chat widget.\n * Copy the route examples to your Next.js app/api folder.\n */\n\nexport {\n createChat,\n loadChat,\n saveChat,\n getConversations,\n updateConversationTitle,\n deleteConversation\n} from '../db/chat-store';\n\nexport { db, conversations, messages } from '../db';\nexport type { Conversation, Message, NewConversation, NewMessage } from '../db/schema';\n\n// Re-export drizzle utilities for convenience\nexport { eq, and, or, desc, asc, sql } from 'drizzle-orm';\n"],"mappings":"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","names":["eq","desc","asc","sql"]}
@@ -0,0 +1,457 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
25
+
26
+ // src/cli/init.ts
27
+ var fs = __toESM(require("fs"));
28
+ var path = __toESM(require("path"));
29
+ var readline = __toESM(require("readline"));
30
+ var rl = readline.createInterface({
31
+ input: process.stdin,
32
+ output: process.stdout
33
+ });
34
+ function ask(question) {
35
+ return new Promise((resolve) => {
36
+ rl.question(question, (answer) => {
37
+ resolve(answer.toLowerCase());
38
+ });
39
+ });
40
+ }
41
+ async function confirm(message) {
42
+ const answer = await ask(`${message} (y/n): `);
43
+ return answer === "y" || answer === "yes";
44
+ }
45
+ function detectAppDir() {
46
+ if (fs.existsSync(path.join(process.cwd(), "src", "app"))) {
47
+ return path.join(process.cwd(), "src", "app");
48
+ }
49
+ if (fs.existsSync(path.join(process.cwd(), "app"))) {
50
+ return path.join(process.cwd(), "app");
51
+ }
52
+ return path.join(process.cwd(), "src", "app");
53
+ }
54
+ async function writeFileWithConfirm(filePath, content) {
55
+ if (fs.existsSync(filePath)) {
56
+ const overwrite = await confirm(`File ${path.relative(process.cwd(), filePath)} already exists. Overwrite?`);
57
+ if (!overwrite) {
58
+ console.log(` Skipped: ${path.relative(process.cwd(), filePath)}`);
59
+ return false;
60
+ }
61
+ }
62
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
63
+ fs.writeFileSync(filePath, content);
64
+ console.log(` Created: ${path.relative(process.cwd(), filePath)}`);
65
+ return true;
66
+ }
67
+ var MAIN_ROUTE = `import { saveChat, updateConversationTitle, db, conversations, messages, eq } from '@mordn/chat-widget/api';
68
+ import { convertToModelMessages, streamText, UIMessage } from 'ai';
69
+
70
+ export const maxDuration = 30;
71
+
72
+ // ============================================
73
+ // DEVELOPER CONFIG - Set these for your app
74
+ // ============================================
75
+ const DEVELOPER_CONFIG = {
76
+ model: 'openai/gpt-4o', // Your AI model (provider/model format)
77
+ systemPrompt: 'You are a helpful assistant',
78
+ temperature: 0.7,
79
+ };
80
+
81
+ export async function POST(req: Request) {
82
+ try {
83
+ const body = await req.json();
84
+ const userId = req.headers.get('X-User-Id');
85
+
86
+ if (!userId) {
87
+ return new Response('userId is required in X-User-Id header', { status: 400 });
88
+ }
89
+
90
+ const chatMessages: UIMessage[] = body.messages || [];
91
+ const id: string = body.id || 'temp-id';
92
+
93
+ const { model, systemPrompt, temperature } = DEVELOPER_CONFIG;
94
+
95
+ // Check if conversation exists, create if not
96
+ const existingConv = await db
97
+ .select({ id: conversations.id })
98
+ .from(conversations)
99
+ .where(eq(conversations.id, id))
100
+ .limit(1);
101
+
102
+ if (!existingConv.length) {
103
+ await db.insert(conversations).values({
104
+ id,
105
+ userId,
106
+ title: 'New Chat',
107
+ metadata: {},
108
+ });
109
+ }
110
+
111
+ // Save the new user message
112
+ const userMessages = chatMessages.filter(msg => msg.role === 'user');
113
+ if (userMessages.length > 0) {
114
+ const newUserMessage = userMessages[userMessages.length - 1];
115
+ const textPart = newUserMessage.parts?.find(p => p.type === 'text') as { text: string } | undefined;
116
+ const fileParts = newUserMessage.parts?.filter(p => p.type === 'file') || [];
117
+
118
+ const existingMsg = await db
119
+ .select({ id: messages.id })
120
+ .from(messages)
121
+ .where(eq(messages.id, newUserMessage.id))
122
+ .limit(1);
123
+
124
+ if (!existingMsg.length) {
125
+ await db.insert(messages).values({
126
+ id: newUserMessage.id,
127
+ conversationId: id,
128
+ role: newUserMessage.role,
129
+ content: textPart?.text || '',
130
+ files: fileParts,
131
+ model: model,
132
+ metadata: { parts: newUserMessage.parts || [] },
133
+ });
134
+ }
135
+
136
+ // Update conversation title if needed
137
+ if (textPart?.text) {
138
+ const conv = await db
139
+ .select({ title: conversations.title })
140
+ .from(conversations)
141
+ .where(eq(conversations.id, id))
142
+ .limit(1);
143
+
144
+ if (conv[0]?.title === 'New Chat') {
145
+ await updateConversationTitle(id, textPart.text.slice(0, 100));
146
+ }
147
+ }
148
+ }
149
+
150
+ // Transform messages for AI (handle images)
151
+ const transformedMessages = chatMessages.map(msg => {
152
+ if (msg.role === 'user' && msg.parts) {
153
+ const textPart = msg.parts.find(p => p.type === 'text');
154
+ const fileParts = msg.parts.filter(p => p.type === 'file');
155
+
156
+ if (fileParts.length > 0) {
157
+ const content: any[] = [];
158
+ if (textPart && 'text' in textPart) {
159
+ content.push({ type: 'text', text: textPart.text });
160
+ }
161
+ for (const file of fileParts) {
162
+ if ('mediaType' in file && (file as any).mediaType?.startsWith('image/')) {
163
+ content.push({ type: 'image', image: (file as any).url });
164
+ }
165
+ }
166
+ return { ...msg, content };
167
+ }
168
+ }
169
+ return msg;
170
+ });
171
+
172
+ const result = streamText({
173
+ model: model,
174
+ messages: convertToModelMessages(transformedMessages),
175
+ system: systemPrompt,
176
+ temperature: temperature,
177
+ });
178
+
179
+ return result.toUIMessageStreamResponse({
180
+ sendSources: true,
181
+ sendReasoning: true,
182
+ onFinish: ({ messages: finalMessages }) => {
183
+ if (finalMessages.length > 0) {
184
+ saveChat({ chatId: id, messages: finalMessages, model, userId });
185
+ }
186
+ },
187
+ });
188
+ } catch (error) {
189
+ console.error('Chat API error:', error);
190
+ return new Response(JSON.stringify({ error: 'Internal server error' }), {
191
+ status: 500,
192
+ headers: { 'Content-Type': 'application/json' },
193
+ });
194
+ }
195
+ }
196
+ `;
197
+ var HISTORY_ROUTE = `import { NextResponse } from 'next/server';
198
+ import { getConversations } from '@mordn/chat-widget/api';
199
+
200
+ export async function GET(request: Request) {
201
+ try {
202
+ const url = new URL(request.url);
203
+ const userId = url.searchParams.get('userId') || request.headers.get('X-User-Id');
204
+
205
+ if (!userId) {
206
+ return NextResponse.json({ error: 'userId is required' }, { status: 400 });
207
+ }
208
+
209
+ const conversationsData = await getConversations(userId);
210
+
211
+ const conversations = conversationsData.map(conv => ({
212
+ id: conv.id,
213
+ title: conv.title,
214
+ created_at: conv.createdAt,
215
+ updated_at: conv.updatedAt,
216
+ metadata: conv.metadata,
217
+ message_count: conv.messageCount,
218
+ }));
219
+
220
+ return NextResponse.json({ conversations });
221
+ } catch (error) {
222
+ console.error('Error in chat history API:', error);
223
+ return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
224
+ }
225
+ }
226
+ `;
227
+ var CONVERSATION_ROUTE = `import { NextResponse } from 'next/server';
228
+ import { db, conversations, messages, eq, and, asc } from '@mordn/chat-widget/api';
229
+
230
+ export async function GET(
231
+ request: Request,
232
+ { params }: { params: Promise<{ conversationId: string }> }
233
+ ) {
234
+ try {
235
+ const { conversationId } = await params;
236
+ const url = new URL(request.url);
237
+ const userId = url.searchParams.get('userId') || request.headers.get('X-User-Id');
238
+
239
+ if (!userId) {
240
+ return NextResponse.json({ error: 'userId is required' }, { status: 400 });
241
+ }
242
+
243
+ // Verify the conversation belongs to the user
244
+ const conv = await db
245
+ .select({
246
+ id: conversations.id,
247
+ title: conversations.title,
248
+ metadata: conversations.metadata,
249
+ })
250
+ .from(conversations)
251
+ .where(and(
252
+ eq(conversations.id, conversationId),
253
+ eq(conversations.userId, userId)
254
+ ))
255
+ .limit(1);
256
+
257
+ if (!conv.length) {
258
+ return NextResponse.json({ error: 'Conversation not found' }, { status: 404 });
259
+ }
260
+
261
+ const conversation = conv[0];
262
+
263
+ const dbMessages = await db
264
+ .select()
265
+ .from(messages)
266
+ .where(eq(messages.conversationId, conversationId))
267
+ .orderBy(asc(messages.createdAt))
268
+ .limit(1000);
269
+
270
+ const transformedMessages = dbMessages.map(msg => {
271
+ const metadata = msg.metadata as { parts?: any[] } | null;
272
+
273
+ if (metadata?.parts && Array.isArray(metadata.parts)) {
274
+ return {
275
+ id: msg.id,
276
+ role: msg.role,
277
+ content: msg.content,
278
+ created_at: msg.createdAt,
279
+ parts: metadata.parts
280
+ };
281
+ }
282
+
283
+ return {
284
+ id: msg.id,
285
+ role: msg.role,
286
+ content: msg.content,
287
+ created_at: msg.createdAt,
288
+ parts: msg.content ? [{ type: 'text', text: msg.content }] : undefined
289
+ };
290
+ });
291
+
292
+ return NextResponse.json({ conversation, messages: transformedMessages });
293
+ } catch (error) {
294
+ console.error('Error loading conversation:', error);
295
+ return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
296
+ }
297
+ }
298
+ `;
299
+ var UPLOAD_ROUTE = `import { createClient } from '@supabase/supabase-js';
300
+ import { nanoid } from 'nanoid';
301
+
302
+ export async function POST(req: Request) {
303
+ try {
304
+ const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
305
+ const supabaseServiceKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
306
+
307
+ // Check for required environment variables
308
+ if (!supabaseUrl || !supabaseServiceKey) {
309
+ console.error('Missing Supabase environment variables. Please set NEXT_PUBLIC_SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY');
310
+ return Response.json({
311
+ error: 'File upload is not configured. Please set up Supabase Storage environment variables.'
312
+ }, { status: 503 });
313
+ }
314
+
315
+ const formData = await req.formData();
316
+ const file = formData.get('file') as File;
317
+ const conversationId = formData.get('conversationId') as string;
318
+ const userId = formData.get('userId') as string;
319
+
320
+ if (!file) {
321
+ return Response.json({ error: 'No file provided' }, { status: 400 });
322
+ }
323
+
324
+ if (!userId) {
325
+ return Response.json({ error: 'userId is required' }, { status: 400 });
326
+ }
327
+
328
+ // Only images supported
329
+ if (!file.type.startsWith('image/')) {
330
+ return Response.json({ error: 'Only image files are supported' }, { status: 400 });
331
+ }
332
+
333
+ // 5MB limit
334
+ if (file.size > 5 * 1024 * 1024) {
335
+ return Response.json({ error: 'File size exceeds 5MB limit' }, { status: 400 });
336
+ }
337
+
338
+ const supabase = createClient(supabaseUrl, supabaseServiceKey);
339
+
340
+ const timestamp = Date.now();
341
+ const randomId = nanoid(8);
342
+ const safeFilename = file.name.replace(/[^a-zA-Z0-9.-]/g, '_');
343
+ const filePath = \`\${userId}/\${conversationId || 'default'}/\${timestamp}-\${randomId}-\${safeFilename}\`;
344
+
345
+ const fileBuffer = await file.arrayBuffer();
346
+
347
+ const { error: uploadError } = await supabase.storage
348
+ .from('chat-attachments')
349
+ .upload(filePath, fileBuffer, {
350
+ contentType: file.type,
351
+ upsert: false,
352
+ });
353
+
354
+ if (uploadError) {
355
+ console.error('Upload error:', uploadError);
356
+ return Response.json({ error: 'Failed to upload file' }, { status: 500 });
357
+ }
358
+
359
+ const { data: urlData } = supabase.storage
360
+ .from('chat-attachments')
361
+ .getPublicUrl(filePath);
362
+
363
+ return Response.json({
364
+ url: urlData.publicUrl,
365
+ filename: file.name,
366
+ mediaType: file.type,
367
+ size: file.size,
368
+ type: 'file',
369
+ });
370
+ } catch (error) {
371
+ console.error('Upload API error:', error);
372
+ return Response.json({ error: 'Internal server error' }, { status: 500 });
373
+ }
374
+ }
375
+ `;
376
+ var DRIZZLE_CONFIG = `import 'dotenv/config';
377
+ import { defineConfig } from 'drizzle-kit';
378
+
379
+ export default defineConfig({
380
+ schema: './node_modules/@mordn/chat-widget/dist/schema/index.js',
381
+ out: './drizzle',
382
+ dialect: 'postgresql',
383
+ dbCredentials: {
384
+ url: process.env.DATABASE_URL!,
385
+ },
386
+ });
387
+ `;
388
+ var ENV_EXAMPLE = `# Database (Required)
389
+ DATABASE_URL="postgresql://postgres.xxx:[PASSWORD]@aws-0-region.pooler.supabase.com:6543/postgres"
390
+
391
+ # AI Gateway (Required)
392
+ AI_GATEWAY_API_KEY="your-ai-gateway-key"
393
+
394
+ # Supabase Storage (Optional - for file uploads)
395
+ NEXT_PUBLIC_SUPABASE_URL="https://xxx.supabase.co"
396
+ SUPABASE_SERVICE_ROLE_KEY="your-service-role-key"
397
+ `;
398
+ async function init() {
399
+ console.log("\n@mordn/chat-widget init\n");
400
+ console.log("This will create the required API routes and configuration files.\n");
401
+ const appDir = detectAppDir();
402
+ const apiChatDir = path.join(appDir, "api", "chat");
403
+ console.log(`Detected app directory: ${path.relative(process.cwd(), appDir)}
404
+ `);
405
+ let filesCreated = 0;
406
+ console.log("Creating API routes...");
407
+ if (await writeFileWithConfirm(path.join(apiChatDir, "route.ts"), MAIN_ROUTE)) {
408
+ filesCreated++;
409
+ }
410
+ if (await writeFileWithConfirm(path.join(apiChatDir, "history", "route.ts"), HISTORY_ROUTE)) {
411
+ filesCreated++;
412
+ }
413
+ if (await writeFileWithConfirm(path.join(apiChatDir, "history", "[conversationId]", "route.ts"), CONVERSATION_ROUTE)) {
414
+ filesCreated++;
415
+ }
416
+ const createUpload = await confirm("\nCreate file upload route? (requires Supabase Storage)");
417
+ let uploadRouteCreated = false;
418
+ if (createUpload) {
419
+ if (await writeFileWithConfirm(path.join(apiChatDir, "upload", "route.ts"), UPLOAD_ROUTE)) {
420
+ filesCreated++;
421
+ uploadRouteCreated = true;
422
+ }
423
+ }
424
+ console.log("\nCreating configuration files...");
425
+ if (await writeFileWithConfirm(path.join(process.cwd(), "drizzle.config.ts"), DRIZZLE_CONFIG)) {
426
+ filesCreated++;
427
+ }
428
+ if (await writeFileWithConfirm(path.join(process.cwd(), ".env.example"), ENV_EXAMPLE)) {
429
+ filesCreated++;
430
+ }
431
+ console.log(`
432
+ \u2713 Created ${filesCreated} files
433
+ `);
434
+ console.log("Next steps:");
435
+ console.log(" 1. Copy .env.example to .env.local and fill in your credentials");
436
+ console.log(" 2. Run: npx drizzle-kit push");
437
+ if (uploadRouteCreated) {
438
+ console.log(' 3. Create a "chat-attachments" bucket in Supabase Storage');
439
+ console.log(" 4. Add the ChatWidget with file uploads enabled:\n");
440
+ console.log(" import { ChatWidget } from '@mordn/chat-widget';");
441
+ console.log(" import '@mordn/chat-widget/styles.css';");
442
+ console.log("");
443
+ console.log(' <ChatWidget userId="user-123" features={{ fileUpload: true }} />\n');
444
+ } else {
445
+ console.log(" 3. Add the ChatWidget to your app:\n");
446
+ console.log(" import { ChatWidget } from '@mordn/chat-widget';");
447
+ console.log(" import '@mordn/chat-widget/styles.css';");
448
+ console.log("");
449
+ console.log(' <ChatWidget userId="user-123" />\n');
450
+ }
451
+ rl.close();
452
+ }
453
+ init().catch((error) => {
454
+ console.error("Error:", error);
455
+ rl.close();
456
+ process.exit(1);
457
+ });