@contractspec/module.ai-chat 1.46.1 → 1.48.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.
@@ -1 +1 @@
1
- {"version":3,"file":"conversation-store.js","names":["fullConversation: ChatConversation","fullMessage: ChatMessage","results: ChatConversation[]"],"sources":["../../src/core/conversation-store.ts"],"sourcesContent":["/**\n * Conversation storage interface and implementations\n */\nimport type {\n ChatConversation,\n ChatMessage,\n ConversationStatus,\n} from './message-types';\n\n/**\n * Interface for conversation persistence\n */\nexport interface ConversationStore {\n /**\n * Get a conversation by ID\n */\n get(conversationId: string): Promise<ChatConversation | null>;\n\n /**\n * Create a new conversation\n */\n create(\n conversation: Omit<ChatConversation, 'id' | 'createdAt' | 'updatedAt'>\n ): Promise<ChatConversation>;\n\n /**\n * Update conversation properties\n */\n update(\n conversationId: string,\n updates: Partial<\n Pick<ChatConversation, 'title' | 'status' | 'summary' | 'metadata'>\n >\n ): Promise<ChatConversation | null>;\n\n /**\n * Append a message to a conversation\n */\n appendMessage(\n conversationId: string,\n message: Omit<\n ChatMessage,\n 'id' | 'conversationId' | 'createdAt' | 'updatedAt'\n >\n ): Promise<ChatMessage>;\n\n /**\n * Update a message in a conversation\n */\n updateMessage(\n conversationId: string,\n messageId: string,\n updates: Partial<ChatMessage>\n ): Promise<ChatMessage | null>;\n\n /**\n * Delete a conversation\n */\n delete(conversationId: string): Promise<boolean>;\n\n /**\n * List conversations with optional filters\n */\n list(options?: {\n status?: ConversationStatus;\n limit?: number;\n offset?: number;\n }): Promise<ChatConversation[]>;\n\n /**\n * Search conversations by content\n */\n search(query: string, limit?: number): Promise<ChatConversation[]>;\n}\n\n/**\n * Generate a unique ID\n */\nfunction generateId(prefix: string): string {\n return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;\n}\n\n/**\n * In-memory conversation store for development and testing\n */\nexport class InMemoryConversationStore implements ConversationStore {\n private readonly conversations = new Map<string, ChatConversation>();\n\n async get(conversationId: string): Promise<ChatConversation | null> {\n return this.conversations.get(conversationId) ?? null;\n }\n\n async create(\n conversation: Omit<ChatConversation, 'id' | 'createdAt' | 'updatedAt'>\n ): Promise<ChatConversation> {\n const now = new Date();\n const fullConversation: ChatConversation = {\n ...conversation,\n id: generateId('conv'),\n createdAt: now,\n updatedAt: now,\n };\n this.conversations.set(fullConversation.id, fullConversation);\n return fullConversation;\n }\n\n async update(\n conversationId: string,\n updates: Partial<\n Pick<ChatConversation, 'title' | 'status' | 'summary' | 'metadata'>\n >\n ): Promise<ChatConversation | null> {\n const conversation = this.conversations.get(conversationId);\n if (!conversation) return null;\n\n const updated = {\n ...conversation,\n ...updates,\n updatedAt: new Date(),\n };\n this.conversations.set(conversationId, updated);\n return updated;\n }\n\n async appendMessage(\n conversationId: string,\n message: Omit<\n ChatMessage,\n 'id' | 'conversationId' | 'createdAt' | 'updatedAt'\n >\n ): Promise<ChatMessage> {\n const conversation = this.conversations.get(conversationId);\n if (!conversation) {\n throw new Error(`Conversation ${conversationId} not found`);\n }\n\n const now = new Date();\n const fullMessage: ChatMessage = {\n ...message,\n id: generateId('msg'),\n conversationId,\n createdAt: now,\n updatedAt: now,\n };\n\n conversation.messages.push(fullMessage);\n conversation.updatedAt = now;\n return fullMessage;\n }\n\n async updateMessage(\n conversationId: string,\n messageId: string,\n updates: Partial<ChatMessage>\n ): Promise<ChatMessage | null> {\n const conversation = this.conversations.get(conversationId);\n if (!conversation) return null;\n\n const messageIndex = conversation.messages.findIndex(\n (m) => m.id === messageId\n );\n if (messageIndex === -1) return null;\n\n const message = conversation.messages[messageIndex];\n if (!message) return null;\n\n const updated = {\n ...message,\n ...updates,\n updatedAt: new Date(),\n };\n conversation.messages[messageIndex] = updated;\n conversation.updatedAt = new Date();\n return updated;\n }\n\n async delete(conversationId: string): Promise<boolean> {\n return this.conversations.delete(conversationId);\n }\n\n async list(options?: {\n status?: ConversationStatus;\n limit?: number;\n offset?: number;\n }): Promise<ChatConversation[]> {\n let results = Array.from(this.conversations.values());\n\n if (options?.status) {\n results = results.filter((c) => c.status === options.status);\n }\n\n // Sort by updatedAt descending\n results.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime());\n\n const offset = options?.offset ?? 0;\n const limit = options?.limit ?? 100;\n return results.slice(offset, offset + limit);\n }\n\n async search(query: string, limit = 20): Promise<ChatConversation[]> {\n const lowerQuery = query.toLowerCase();\n const results: ChatConversation[] = [];\n\n for (const conversation of this.conversations.values()) {\n // Search in title\n if (conversation.title?.toLowerCase().includes(lowerQuery)) {\n results.push(conversation);\n continue;\n }\n\n // Search in messages\n const hasMatch = conversation.messages.some((m) =>\n m.content.toLowerCase().includes(lowerQuery)\n );\n if (hasMatch) {\n results.push(conversation);\n }\n\n if (results.length >= limit) break;\n }\n\n return results;\n }\n\n /**\n * Clear all conversations (for testing)\n */\n clear(): void {\n this.conversations.clear();\n }\n}\n\n/**\n * Create an in-memory conversation store\n */\nexport function createInMemoryConversationStore(): ConversationStore {\n return new InMemoryConversationStore();\n}\n"],"mappings":";;;;AA8EA,SAAS,WAAW,QAAwB;AAC1C,QAAO,GAAG,OAAO,GAAG,KAAK,KAAK,CAAC,GAAG,KAAK,QAAQ,CAAC,SAAS,GAAG,CAAC,MAAM,GAAG,GAAG;;;;;AAM3E,IAAa,4BAAb,MAAoE;CAClE,AAAiB,gCAAgB,IAAI,KAA+B;CAEpE,MAAM,IAAI,gBAA0D;AAClE,SAAO,KAAK,cAAc,IAAI,eAAe,IAAI;;CAGnD,MAAM,OACJ,cAC2B;EAC3B,MAAM,sBAAM,IAAI,MAAM;EACtB,MAAMA,mBAAqC;GACzC,GAAG;GACH,IAAI,WAAW,OAAO;GACtB,WAAW;GACX,WAAW;GACZ;AACD,OAAK,cAAc,IAAI,iBAAiB,IAAI,iBAAiB;AAC7D,SAAO;;CAGT,MAAM,OACJ,gBACA,SAGkC;EAClC,MAAM,eAAe,KAAK,cAAc,IAAI,eAAe;AAC3D,MAAI,CAAC,aAAc,QAAO;EAE1B,MAAM,UAAU;GACd,GAAG;GACH,GAAG;GACH,2BAAW,IAAI,MAAM;GACtB;AACD,OAAK,cAAc,IAAI,gBAAgB,QAAQ;AAC/C,SAAO;;CAGT,MAAM,cACJ,gBACA,SAIsB;EACtB,MAAM,eAAe,KAAK,cAAc,IAAI,eAAe;AAC3D,MAAI,CAAC,aACH,OAAM,IAAI,MAAM,gBAAgB,eAAe,YAAY;EAG7D,MAAM,sBAAM,IAAI,MAAM;EACtB,MAAMC,cAA2B;GAC/B,GAAG;GACH,IAAI,WAAW,MAAM;GACrB;GACA,WAAW;GACX,WAAW;GACZ;AAED,eAAa,SAAS,KAAK,YAAY;AACvC,eAAa,YAAY;AACzB,SAAO;;CAGT,MAAM,cACJ,gBACA,WACA,SAC6B;EAC7B,MAAM,eAAe,KAAK,cAAc,IAAI,eAAe;AAC3D,MAAI,CAAC,aAAc,QAAO;EAE1B,MAAM,eAAe,aAAa,SAAS,WACxC,MAAM,EAAE,OAAO,UACjB;AACD,MAAI,iBAAiB,GAAI,QAAO;EAEhC,MAAM,UAAU,aAAa,SAAS;AACtC,MAAI,CAAC,QAAS,QAAO;EAErB,MAAM,UAAU;GACd,GAAG;GACH,GAAG;GACH,2BAAW,IAAI,MAAM;GACtB;AACD,eAAa,SAAS,gBAAgB;AACtC,eAAa,4BAAY,IAAI,MAAM;AACnC,SAAO;;CAGT,MAAM,OAAO,gBAA0C;AACrD,SAAO,KAAK,cAAc,OAAO,eAAe;;CAGlD,MAAM,KAAK,SAIqB;EAC9B,IAAI,UAAU,MAAM,KAAK,KAAK,cAAc,QAAQ,CAAC;AAErD,MAAI,SAAS,OACX,WAAU,QAAQ,QAAQ,MAAM,EAAE,WAAW,QAAQ,OAAO;AAI9D,UAAQ,MAAM,GAAG,MAAM,EAAE,UAAU,SAAS,GAAG,EAAE,UAAU,SAAS,CAAC;EAErE,MAAM,SAAS,SAAS,UAAU;EAClC,MAAM,QAAQ,SAAS,SAAS;AAChC,SAAO,QAAQ,MAAM,QAAQ,SAAS,MAAM;;CAG9C,MAAM,OAAO,OAAe,QAAQ,IAAiC;EACnE,MAAM,aAAa,MAAM,aAAa;EACtC,MAAMC,UAA8B,EAAE;AAEtC,OAAK,MAAM,gBAAgB,KAAK,cAAc,QAAQ,EAAE;AAEtD,OAAI,aAAa,OAAO,aAAa,CAAC,SAAS,WAAW,EAAE;AAC1D,YAAQ,KAAK,aAAa;AAC1B;;AAOF,OAHiB,aAAa,SAAS,MAAM,MAC3C,EAAE,QAAQ,aAAa,CAAC,SAAS,WAAW,CAC7C,CAEC,SAAQ,KAAK,aAAa;AAG5B,OAAI,QAAQ,UAAU,MAAO;;AAG/B,SAAO;;;;;CAMT,QAAc;AACZ,OAAK,cAAc,OAAO;;;;;;AAO9B,SAAgB,kCAAqD;AACnE,QAAO,IAAI,2BAA2B"}
1
+ {"version":3,"file":"conversation-store.js","names":[],"sources":["../../src/core/conversation-store.ts"],"sourcesContent":["/**\n * Conversation storage interface and implementations\n */\nimport type {\n ChatConversation,\n ChatMessage,\n ConversationStatus,\n} from './message-types';\n\n/**\n * Interface for conversation persistence\n */\nexport interface ConversationStore {\n /**\n * Get a conversation by ID\n */\n get(conversationId: string): Promise<ChatConversation | null>;\n\n /**\n * Create a new conversation\n */\n create(\n conversation: Omit<ChatConversation, 'id' | 'createdAt' | 'updatedAt'>\n ): Promise<ChatConversation>;\n\n /**\n * Update conversation properties\n */\n update(\n conversationId: string,\n updates: Partial<\n Pick<ChatConversation, 'title' | 'status' | 'summary' | 'metadata'>\n >\n ): Promise<ChatConversation | null>;\n\n /**\n * Append a message to a conversation\n */\n appendMessage(\n conversationId: string,\n message: Omit<\n ChatMessage,\n 'id' | 'conversationId' | 'createdAt' | 'updatedAt'\n >\n ): Promise<ChatMessage>;\n\n /**\n * Update a message in a conversation\n */\n updateMessage(\n conversationId: string,\n messageId: string,\n updates: Partial<ChatMessage>\n ): Promise<ChatMessage | null>;\n\n /**\n * Delete a conversation\n */\n delete(conversationId: string): Promise<boolean>;\n\n /**\n * List conversations with optional filters\n */\n list(options?: {\n status?: ConversationStatus;\n limit?: number;\n offset?: number;\n }): Promise<ChatConversation[]>;\n\n /**\n * Search conversations by content\n */\n search(query: string, limit?: number): Promise<ChatConversation[]>;\n}\n\n/**\n * Generate a unique ID\n */\nfunction generateId(prefix: string): string {\n return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;\n}\n\n/**\n * In-memory conversation store for development and testing\n */\nexport class InMemoryConversationStore implements ConversationStore {\n private readonly conversations = new Map<string, ChatConversation>();\n\n async get(conversationId: string): Promise<ChatConversation | null> {\n return this.conversations.get(conversationId) ?? null;\n }\n\n async create(\n conversation: Omit<ChatConversation, 'id' | 'createdAt' | 'updatedAt'>\n ): Promise<ChatConversation> {\n const now = new Date();\n const fullConversation: ChatConversation = {\n ...conversation,\n id: generateId('conv'),\n createdAt: now,\n updatedAt: now,\n };\n this.conversations.set(fullConversation.id, fullConversation);\n return fullConversation;\n }\n\n async update(\n conversationId: string,\n updates: Partial<\n Pick<ChatConversation, 'title' | 'status' | 'summary' | 'metadata'>\n >\n ): Promise<ChatConversation | null> {\n const conversation = this.conversations.get(conversationId);\n if (!conversation) return null;\n\n const updated = {\n ...conversation,\n ...updates,\n updatedAt: new Date(),\n };\n this.conversations.set(conversationId, updated);\n return updated;\n }\n\n async appendMessage(\n conversationId: string,\n message: Omit<\n ChatMessage,\n 'id' | 'conversationId' | 'createdAt' | 'updatedAt'\n >\n ): Promise<ChatMessage> {\n const conversation = this.conversations.get(conversationId);\n if (!conversation) {\n throw new Error(`Conversation ${conversationId} not found`);\n }\n\n const now = new Date();\n const fullMessage: ChatMessage = {\n ...message,\n id: generateId('msg'),\n conversationId,\n createdAt: now,\n updatedAt: now,\n };\n\n conversation.messages.push(fullMessage);\n conversation.updatedAt = now;\n return fullMessage;\n }\n\n async updateMessage(\n conversationId: string,\n messageId: string,\n updates: Partial<ChatMessage>\n ): Promise<ChatMessage | null> {\n const conversation = this.conversations.get(conversationId);\n if (!conversation) return null;\n\n const messageIndex = conversation.messages.findIndex(\n (m) => m.id === messageId\n );\n if (messageIndex === -1) return null;\n\n const message = conversation.messages[messageIndex];\n if (!message) return null;\n\n const updated = {\n ...message,\n ...updates,\n updatedAt: new Date(),\n };\n conversation.messages[messageIndex] = updated;\n conversation.updatedAt = new Date();\n return updated;\n }\n\n async delete(conversationId: string): Promise<boolean> {\n return this.conversations.delete(conversationId);\n }\n\n async list(options?: {\n status?: ConversationStatus;\n limit?: number;\n offset?: number;\n }): Promise<ChatConversation[]> {\n let results = Array.from(this.conversations.values());\n\n if (options?.status) {\n results = results.filter((c) => c.status === options.status);\n }\n\n // Sort by updatedAt descending\n results.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime());\n\n const offset = options?.offset ?? 0;\n const limit = options?.limit ?? 100;\n return results.slice(offset, offset + limit);\n }\n\n async search(query: string, limit = 20): Promise<ChatConversation[]> {\n const lowerQuery = query.toLowerCase();\n const results: ChatConversation[] = [];\n\n for (const conversation of this.conversations.values()) {\n // Search in title\n if (conversation.title?.toLowerCase().includes(lowerQuery)) {\n results.push(conversation);\n continue;\n }\n\n // Search in messages\n const hasMatch = conversation.messages.some((m) =>\n m.content.toLowerCase().includes(lowerQuery)\n );\n if (hasMatch) {\n results.push(conversation);\n }\n\n if (results.length >= limit) break;\n }\n\n return results;\n }\n\n /**\n * Clear all conversations (for testing)\n */\n clear(): void {\n this.conversations.clear();\n }\n}\n\n/**\n * Create an in-memory conversation store\n */\nexport function createInMemoryConversationStore(): ConversationStore {\n return new InMemoryConversationStore();\n}\n"],"mappings":";;;;AA8EA,SAAS,WAAW,QAAwB;AAC1C,QAAO,GAAG,OAAO,GAAG,KAAK,KAAK,CAAC,GAAG,KAAK,QAAQ,CAAC,SAAS,GAAG,CAAC,MAAM,GAAG,GAAG;;;;;AAM3E,IAAa,4BAAb,MAAoE;CAClE,AAAiB,gCAAgB,IAAI,KAA+B;CAEpE,MAAM,IAAI,gBAA0D;AAClE,SAAO,KAAK,cAAc,IAAI,eAAe,IAAI;;CAGnD,MAAM,OACJ,cAC2B;EAC3B,MAAM,sBAAM,IAAI,MAAM;EACtB,MAAM,mBAAqC;GACzC,GAAG;GACH,IAAI,WAAW,OAAO;GACtB,WAAW;GACX,WAAW;GACZ;AACD,OAAK,cAAc,IAAI,iBAAiB,IAAI,iBAAiB;AAC7D,SAAO;;CAGT,MAAM,OACJ,gBACA,SAGkC;EAClC,MAAM,eAAe,KAAK,cAAc,IAAI,eAAe;AAC3D,MAAI,CAAC,aAAc,QAAO;EAE1B,MAAM,UAAU;GACd,GAAG;GACH,GAAG;GACH,2BAAW,IAAI,MAAM;GACtB;AACD,OAAK,cAAc,IAAI,gBAAgB,QAAQ;AAC/C,SAAO;;CAGT,MAAM,cACJ,gBACA,SAIsB;EACtB,MAAM,eAAe,KAAK,cAAc,IAAI,eAAe;AAC3D,MAAI,CAAC,aACH,OAAM,IAAI,MAAM,gBAAgB,eAAe,YAAY;EAG7D,MAAM,sBAAM,IAAI,MAAM;EACtB,MAAM,cAA2B;GAC/B,GAAG;GACH,IAAI,WAAW,MAAM;GACrB;GACA,WAAW;GACX,WAAW;GACZ;AAED,eAAa,SAAS,KAAK,YAAY;AACvC,eAAa,YAAY;AACzB,SAAO;;CAGT,MAAM,cACJ,gBACA,WACA,SAC6B;EAC7B,MAAM,eAAe,KAAK,cAAc,IAAI,eAAe;AAC3D,MAAI,CAAC,aAAc,QAAO;EAE1B,MAAM,eAAe,aAAa,SAAS,WACxC,MAAM,EAAE,OAAO,UACjB;AACD,MAAI,iBAAiB,GAAI,QAAO;EAEhC,MAAM,UAAU,aAAa,SAAS;AACtC,MAAI,CAAC,QAAS,QAAO;EAErB,MAAM,UAAU;GACd,GAAG;GACH,GAAG;GACH,2BAAW,IAAI,MAAM;GACtB;AACD,eAAa,SAAS,gBAAgB;AACtC,eAAa,4BAAY,IAAI,MAAM;AACnC,SAAO;;CAGT,MAAM,OAAO,gBAA0C;AACrD,SAAO,KAAK,cAAc,OAAO,eAAe;;CAGlD,MAAM,KAAK,SAIqB;EAC9B,IAAI,UAAU,MAAM,KAAK,KAAK,cAAc,QAAQ,CAAC;AAErD,MAAI,SAAS,OACX,WAAU,QAAQ,QAAQ,MAAM,EAAE,WAAW,QAAQ,OAAO;AAI9D,UAAQ,MAAM,GAAG,MAAM,EAAE,UAAU,SAAS,GAAG,EAAE,UAAU,SAAS,CAAC;EAErE,MAAM,SAAS,SAAS,UAAU;EAClC,MAAM,QAAQ,SAAS,SAAS;AAChC,SAAO,QAAQ,MAAM,QAAQ,SAAS,MAAM;;CAG9C,MAAM,OAAO,OAAe,QAAQ,IAAiC;EACnE,MAAM,aAAa,MAAM,aAAa;EACtC,MAAM,UAA8B,EAAE;AAEtC,OAAK,MAAM,gBAAgB,KAAK,cAAc,QAAQ,EAAE;AAEtD,OAAI,aAAa,OAAO,aAAa,CAAC,SAAS,WAAW,EAAE;AAC1D,YAAQ,KAAK,aAAa;AAC1B;;AAOF,OAHiB,aAAa,SAAS,MAAM,MAC3C,EAAE,QAAQ,aAAa,CAAC,SAAS,WAAW,CAC7C,CAEC,SAAQ,KAAK,aAAa;AAG5B,OAAI,QAAQ,UAAU,MAAO;;AAG/B,SAAO;;;;;CAMT,QAAc;AACZ,OAAK,cAAc,OAAO;;;;;;AAO9B,SAAgB,kCAAqD;AACnE,QAAO,IAAI,2BAA2B"}
package/dist/events.d.ts CHANGED
@@ -1,84 +1,84 @@
1
- import * as _contractspec_lib_schema119 from "@contractspec/lib.schema";
2
- import * as _contractspec_lib_contracts6 from "@contractspec/lib.contracts";
1
+ import * as _contractspec_lib_contracts0 from "@contractspec/lib.contracts";
2
+ import * as _contractspec_lib_schema0 from "@contractspec/lib.schema";
3
3
 
4
4
  //#region src/events.d.ts
5
- declare const MessageSentEvent: _contractspec_lib_contracts6.EventSpec<_contractspec_lib_schema119.SchemaModel<{
5
+ declare const MessageSentEvent: _contractspec_lib_contracts0.EventSpec<_contractspec_lib_schema0.SchemaModel<{
6
6
  id: {
7
- type: _contractspec_lib_schema119.FieldType<string, string>;
7
+ type: _contractspec_lib_schema0.FieldType<string, string>;
8
8
  isOptional: false;
9
9
  };
10
10
  role: {
11
- type: _contractspec_lib_schema119.FieldType<string, string>;
11
+ type: _contractspec_lib_schema0.FieldType<string, string>;
12
12
  isOptional: false;
13
13
  };
14
14
  content: {
15
- type: _contractspec_lib_schema119.FieldType<string, string>;
15
+ type: _contractspec_lib_schema0.FieldType<string, string>;
16
16
  isOptional: false;
17
17
  };
18
18
  status: {
19
- type: _contractspec_lib_schema119.FieldType<string, string>;
19
+ type: _contractspec_lib_schema0.FieldType<string, string>;
20
20
  isOptional: false;
21
21
  };
22
22
  createdAt: {
23
- type: _contractspec_lib_schema119.FieldType<Date, string>;
23
+ type: _contractspec_lib_schema0.FieldType<Date, string>;
24
24
  isOptional: false;
25
25
  };
26
26
  }>>;
27
- declare const MessageReceivedEvent: _contractspec_lib_contracts6.EventSpec<_contractspec_lib_schema119.SchemaModel<{
27
+ declare const MessageReceivedEvent: _contractspec_lib_contracts0.EventSpec<_contractspec_lib_schema0.SchemaModel<{
28
28
  id: {
29
- type: _contractspec_lib_schema119.FieldType<string, string>;
29
+ type: _contractspec_lib_schema0.FieldType<string, string>;
30
30
  isOptional: false;
31
31
  };
32
32
  role: {
33
- type: _contractspec_lib_schema119.FieldType<string, string>;
33
+ type: _contractspec_lib_schema0.FieldType<string, string>;
34
34
  isOptional: false;
35
35
  };
36
36
  content: {
37
- type: _contractspec_lib_schema119.FieldType<string, string>;
37
+ type: _contractspec_lib_schema0.FieldType<string, string>;
38
38
  isOptional: false;
39
39
  };
40
40
  status: {
41
- type: _contractspec_lib_schema119.FieldType<string, string>;
41
+ type: _contractspec_lib_schema0.FieldType<string, string>;
42
42
  isOptional: false;
43
43
  };
44
44
  createdAt: {
45
- type: _contractspec_lib_schema119.FieldType<Date, string>;
45
+ type: _contractspec_lib_schema0.FieldType<Date, string>;
46
46
  isOptional: false;
47
47
  };
48
48
  }>>;
49
- declare const ConversationCreatedEvent: _contractspec_lib_contracts6.EventSpec<_contractspec_lib_schema119.SchemaModel<{
49
+ declare const ConversationCreatedEvent: _contractspec_lib_contracts0.EventSpec<_contractspec_lib_schema0.SchemaModel<{
50
50
  id: {
51
- type: _contractspec_lib_schema119.FieldType<string, string>;
51
+ type: _contractspec_lib_schema0.FieldType<string, string>;
52
52
  isOptional: false;
53
53
  };
54
54
  title: {
55
- type: _contractspec_lib_schema119.FieldType<string, string>;
55
+ type: _contractspec_lib_schema0.FieldType<string, string>;
56
56
  isOptional: true;
57
57
  };
58
58
  status: {
59
- type: _contractspec_lib_schema119.FieldType<string, string>;
59
+ type: _contractspec_lib_schema0.FieldType<string, string>;
60
60
  isOptional: false;
61
61
  };
62
62
  messages: {
63
- type: _contractspec_lib_schema119.SchemaModel<{
63
+ type: _contractspec_lib_schema0.SchemaModel<{
64
64
  id: {
65
- type: _contractspec_lib_schema119.FieldType<string, string>;
65
+ type: _contractspec_lib_schema0.FieldType<string, string>;
66
66
  isOptional: false;
67
67
  };
68
68
  role: {
69
- type: _contractspec_lib_schema119.FieldType<string, string>;
69
+ type: _contractspec_lib_schema0.FieldType<string, string>;
70
70
  isOptional: false;
71
71
  };
72
72
  content: {
73
- type: _contractspec_lib_schema119.FieldType<string, string>;
73
+ type: _contractspec_lib_schema0.FieldType<string, string>;
74
74
  isOptional: false;
75
75
  };
76
76
  status: {
77
- type: _contractspec_lib_schema119.FieldType<string, string>;
77
+ type: _contractspec_lib_schema0.FieldType<string, string>;
78
78
  isOptional: false;
79
79
  };
80
80
  createdAt: {
81
- type: _contractspec_lib_schema119.FieldType<Date, string>;
81
+ type: _contractspec_lib_schema0.FieldType<Date, string>;
82
82
  isOptional: false;
83
83
  };
84
84
  }>;
@@ -86,27 +86,27 @@ declare const ConversationCreatedEvent: _contractspec_lib_contracts6.EventSpec<_
86
86
  isOptional: false;
87
87
  };
88
88
  provider: {
89
- type: _contractspec_lib_schema119.FieldType<string, string>;
89
+ type: _contractspec_lib_schema0.FieldType<string, string>;
90
90
  isOptional: false;
91
91
  };
92
92
  model: {
93
- type: _contractspec_lib_schema119.FieldType<string, string>;
93
+ type: _contractspec_lib_schema0.FieldType<string, string>;
94
94
  isOptional: false;
95
95
  };
96
96
  }>>;
97
- declare const ConversationDeletedEvent: _contractspec_lib_contracts6.EventSpec<_contractspec_lib_schema119.SchemaModel<{
97
+ declare const ConversationDeletedEvent: _contractspec_lib_contracts0.EventSpec<_contractspec_lib_schema0.SchemaModel<{
98
98
  id: {
99
- type: _contractspec_lib_schema119.FieldType<string, string>;
99
+ type: _contractspec_lib_schema0.FieldType<string, string>;
100
100
  isOptional: false;
101
101
  };
102
102
  }>>;
103
- declare const ChatErrorEvent: _contractspec_lib_contracts6.EventSpec<_contractspec_lib_schema119.SchemaModel<{
103
+ declare const ChatErrorEvent: _contractspec_lib_contracts0.EventSpec<_contractspec_lib_schema0.SchemaModel<{
104
104
  code: {
105
- type: _contractspec_lib_schema119.FieldType<string, string>;
105
+ type: _contractspec_lib_schema0.FieldType<string, string>;
106
106
  isOptional: false;
107
107
  };
108
108
  message: {
109
- type: _contractspec_lib_schema119.FieldType<string, string>;
109
+ type: _contractspec_lib_schema0.FieldType<string, string>;
110
110
  isOptional: false;
111
111
  };
112
112
  }>>;
@@ -1 +1 @@
1
- {"version":3,"file":"events.d.ts","names":[],"sources":["../src/events.ts"],"sourcesContent":[],"mappings":";;;;cAIa,kBAAgB,4BAAA,CAAA,sCAAA;;UAU3B,2BAAA,CAAA;;EAVW,CAAA;EAUX,IAAA,EAAA;;;;;;;EAV2B,CAAA;EAAA,MAAA,EAAA;IAYhB,IAAA,uCAUX,CAAA,MAAA,EAAA,MAAA,CAAA;IAAA,UAAA,EAAA,KAAA;;;;;;;AAV+B,cAApB,oBAAoB,EAAA,4BAAA,CAAA,SAAA,6BAAA,WAAA,CAAA;EAAA,EAAA,EAAA;IAYpB,IAAA,EAFX,2BAAA,CAAA,SAYA,CAAA,MAAA,EAAA,MAAA,CAAA;IAAA,UAAA,EAAA,KAAA;;;;;;;;;;;;;EAVmC,CAAA;EAAA,SAAA,EAAA;IAYxB,IAAA,uCAeX,KAAA,EAAA,MAAA,CAAA;IAAA,UAAA,EAAA,KAAA;;CAfmC,CAAA,CAAA;AAAA,cAZxB,wBAYwB,EAZA,4BAAA,CAAA,SAYA,6BAZA,WAYA,CAAA;EAiBxB,EAAA,EAAA;IAgBX,IAAA,EAnCA,2BAAA,CAAA,SAmCA,CAAA,MAAA,EAAA,MAAA,CAAA;;;EAhByB,KAAA,EAAA;IAAA,IAAA,uCAAA,CAAA,MAAA,EAAA,MAAA,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAjBd,0BAAwB,4BAAA,CAAA,sCAAA;;UAenC,2BAAA,CAAA;;;;cAEW,gBAAc,4BAAA,CAAA,sCAAA;;UAgBzB,2BAAA,CAAA"}
1
+ {"version":3,"file":"events.d.ts","names":[],"sources":["../src/events.ts"],"sourcesContent":[],"mappings":";;;;cAIa,kBAAgB,4BAAA,CAAA,oCAAA;;UAU3B,yBAAA,CAAA;;EAVW,CAAA;EAUX,IAAA,EAAA;;;;;;;EAV2B,CAAA;EAAA,MAAA,EAAA;IAYhB,IAAA,qCAUX,CAAA,MAAA,EAAA,MAAA,CAAA;IAAA,UAAA,EAAA,KAAA;;;;;;;AAV+B,cAApB,oBAAoB,EAAA,4BAAA,CAAA,SAAA,2BAAA,WAAA,CAAA;EAAA,EAAA,EAAA;IAYpB,IAAA,EAFX,yBAAA,CAAA,SAYA,CAAA,MAAA,EAAA,MAAA,CAAA;IAAA,UAAA,EAAA,KAAA;;;;;;;;;;;;;EAVmC,CAAA;EAAA,SAAA,EAAA;IAYxB,IAAA,qCAeX,KAAA,EAAA,MAAA,CAAA;IAAA,UAAA,EAAA,KAAA;;CAfmC,CAAA,CAAA;AAAA,cAZxB,wBAYwB,EAZA,4BAAA,CAAA,SAYA,2BAZA,WAYA,CAAA;EAiBxB,EAAA,EAAA;IAgBX,IAAA,EAnCA,yBAAA,CAAA,SAmCA,CAAA,MAAA,EAAA,MAAA,CAAA;;;EAhByB,KAAA,EAAA;IAAA,IAAA,qCAAA,CAAA,MAAA,EAAA,MAAA,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAjBd,0BAAwB,4BAAA,CAAA,oCAAA;;UAenC,yBAAA,CAAA;;;;cAEW,gBAAc,4BAAA,CAAA,oCAAA;;UAgBzB,yBAAA,CAAA"}
package/dist/events.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { ChatConversationModel, ChatMessageModel } from "./schema.js";
2
- import { ScalarTypeEnum, defineSchemaModel } from "@contractspec/lib.schema";
3
2
  import { defineEvent } from "@contractspec/lib.contracts";
3
+ import { ScalarTypeEnum, defineSchemaModel } from "@contractspec/lib.schema";
4
4
 
5
5
  //#region src/events.ts
6
6
  const MessageSentEvent = defineEvent({
@@ -1,5 +1,5 @@
1
1
  import { ChatAttachment } from "../../core/message-types.js";
2
- import * as react_jsx_runtime1 from "react/jsx-runtime";
2
+ import * as react_jsx_runtime2 from "react/jsx-runtime";
3
3
 
4
4
  //#region src/presentation/components/ChatInput.d.ts
5
5
  interface ChatInputProps {
@@ -29,7 +29,7 @@ declare function ChatInput({
29
29
  className,
30
30
  showAttachments,
31
31
  maxAttachments
32
- }: ChatInputProps): react_jsx_runtime1.JSX.Element;
32
+ }: ChatInputProps): react_jsx_runtime2.JSX.Element;
33
33
  //#endregion
34
34
  export { ChatInput };
35
35
  //# sourceMappingURL=ChatInput.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ChatInput.js","names":["newAttachments: ChatAttachment[]","content"],"sources":["../../../src/presentation/components/ChatInput.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { cn } from '@contractspec/lib.ui-kit-web/ui/utils';\nimport { Textarea } from '@contractspec/lib.design-system';\nimport { Button } from '@contractspec/lib.design-system';\nimport { Send, Paperclip, X, Loader2, FileText, Code } from 'lucide-react';\nimport type { ChatAttachment } from '../../core/message-types';\n\nexport interface ChatInputProps {\n /** Called when a message is sent */\n onSend: (content: string, attachments?: ChatAttachment[]) => void;\n /** Whether input is disabled (e.g., during streaming) */\n disabled?: boolean;\n /** Whether currently loading/streaming */\n isLoading?: boolean;\n /** Placeholder text */\n placeholder?: string;\n /** Additional class name */\n className?: string;\n /** Show attachment button */\n showAttachments?: boolean;\n /** Max attachments allowed */\n maxAttachments?: number;\n}\n\n/**\n * Chat input component with attachment support\n */\nexport function ChatInput({\n onSend,\n disabled = false,\n isLoading = false,\n placeholder = 'Type a message...',\n className,\n showAttachments = true,\n maxAttachments = 5,\n}: ChatInputProps) {\n const [content, setContent] = React.useState('');\n const [attachments, setAttachments] = React.useState<ChatAttachment[]>([]);\n const textareaRef = React.useRef<HTMLTextAreaElement>(null);\n const fileInputRef = React.useRef<HTMLInputElement>(null);\n\n const canSend = content.trim().length > 0 || attachments.length > 0;\n\n const handleSubmit = React.useCallback(\n (e?: React.FormEvent) => {\n e?.preventDefault();\n if (!canSend || disabled || isLoading) return;\n\n onSend(content.trim(), attachments.length > 0 ? attachments : undefined);\n setContent('');\n setAttachments([]);\n\n // Focus back on textarea\n textareaRef.current?.focus();\n },\n [canSend, content, attachments, disabled, isLoading, onSend]\n );\n\n const handleKeyDown = React.useCallback(\n (e: React.KeyboardEvent) => {\n // Submit on Enter (without Shift)\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault();\n handleSubmit();\n }\n },\n [handleSubmit]\n );\n\n const handleFileSelect = React.useCallback(\n async (e: React.ChangeEvent<HTMLInputElement>) => {\n const files = e.target.files;\n if (!files) return;\n\n const newAttachments: ChatAttachment[] = [];\n\n for (const file of Array.from(files)) {\n if (attachments.length + newAttachments.length >= maxAttachments) break;\n\n const content = await file.text();\n const extension = file.name.split('.').pop()?.toLowerCase() ?? '';\n const isCode = [\n 'ts',\n 'tsx',\n 'js',\n 'jsx',\n 'py',\n 'go',\n 'rs',\n 'java',\n ].includes(extension);\n\n newAttachments.push({\n id: `att_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`,\n type: isCode ? 'code' : 'file',\n name: file.name,\n content,\n mimeType: file.type,\n size: file.size,\n });\n }\n\n setAttachments((prev) => [...prev, ...newAttachments]);\n\n // Reset file input\n e.target.value = '';\n },\n [attachments.length, maxAttachments]\n );\n\n const removeAttachment = React.useCallback((id: string) => {\n setAttachments((prev) => prev.filter((a) => a.id !== id));\n }, []);\n\n return (\n <div className={cn('flex flex-col gap-2', className)}>\n {/* Attachments preview */}\n {attachments.length > 0 && (\n <div className=\"flex flex-wrap gap-2\">\n {attachments.map((attachment) => (\n <div\n key={attachment.id}\n className={cn(\n 'flex items-center gap-1.5 rounded-md px-2 py-1',\n 'bg-muted text-muted-foreground text-sm'\n )}\n >\n {attachment.type === 'code' ? (\n <Code className=\"h-3.5 w-3.5\" />\n ) : (\n <FileText className=\"h-3.5 w-3.5\" />\n )}\n <span className=\"max-w-[150px] truncate\">{attachment.name}</span>\n <button\n type=\"button\"\n onClick={() => removeAttachment(attachment.id)}\n className=\"hover:text-foreground\"\n aria-label={`Remove ${attachment.name}`}\n >\n <X className=\"h-3.5 w-3.5\" />\n </button>\n </div>\n ))}\n </div>\n )}\n\n {/* Input form */}\n <form onSubmit={handleSubmit} className=\"flex items-end gap-2\">\n {/* Attachment button */}\n {showAttachments && (\n <>\n <input\n ref={fileInputRef}\n type=\"file\"\n multiple\n accept=\".ts,.tsx,.js,.jsx,.json,.md,.txt,.py,.go,.rs,.java,.yaml,.yml\"\n onChange={handleFileSelect}\n className=\"hidden\"\n aria-label=\"Attach files\"\n />\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onPress={() => fileInputRef.current?.click()}\n disabled={disabled || attachments.length >= maxAttachments}\n aria-label=\"Attach files\"\n >\n <Paperclip className=\"h-4 w-4\" />\n </Button>\n </>\n )}\n\n {/* Text input */}\n <div className=\"relative flex-1\">\n <Textarea\n value={content}\n onChange={(e) => setContent(e.target.value)}\n onKeyDown={handleKeyDown}\n placeholder={placeholder}\n disabled={disabled}\n className={cn(\n 'max-h-[200px] min-h-[44px] resize-none pr-12',\n 'focus-visible:ring-1'\n )}\n rows={1}\n aria-label=\"Chat message\"\n />\n </div>\n\n {/* Send button */}\n <Button\n type=\"submit\"\n disabled={!canSend || disabled || isLoading}\n size=\"sm\"\n aria-label={isLoading ? 'Sending...' : 'Send message'}\n >\n {isLoading ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Send className=\"h-4 w-4\" />\n )}\n </Button>\n </form>\n\n {/* Helper text */}\n <p className=\"text-muted-foreground text-xs\">\n Press Enter to send, Shift+Enter for new line\n </p>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ChatInput.js","names":["content"],"sources":["../../../src/presentation/components/ChatInput.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { cn } from '@contractspec/lib.ui-kit-web/ui/utils';\nimport { Textarea } from '@contractspec/lib.design-system';\nimport { Button } from '@contractspec/lib.design-system';\nimport { Send, Paperclip, X, Loader2, FileText, Code } from 'lucide-react';\nimport type { ChatAttachment } from '../../core/message-types';\n\nexport interface ChatInputProps {\n /** Called when a message is sent */\n onSend: (content: string, attachments?: ChatAttachment[]) => void;\n /** Whether input is disabled (e.g., during streaming) */\n disabled?: boolean;\n /** Whether currently loading/streaming */\n isLoading?: boolean;\n /** Placeholder text */\n placeholder?: string;\n /** Additional class name */\n className?: string;\n /** Show attachment button */\n showAttachments?: boolean;\n /** Max attachments allowed */\n maxAttachments?: number;\n}\n\n/**\n * Chat input component with attachment support\n */\nexport function ChatInput({\n onSend,\n disabled = false,\n isLoading = false,\n placeholder = 'Type a message...',\n className,\n showAttachments = true,\n maxAttachments = 5,\n}: ChatInputProps) {\n const [content, setContent] = React.useState('');\n const [attachments, setAttachments] = React.useState<ChatAttachment[]>([]);\n const textareaRef = React.useRef<HTMLTextAreaElement>(null);\n const fileInputRef = React.useRef<HTMLInputElement>(null);\n\n const canSend = content.trim().length > 0 || attachments.length > 0;\n\n const handleSubmit = React.useCallback(\n (e?: React.FormEvent) => {\n e?.preventDefault();\n if (!canSend || disabled || isLoading) return;\n\n onSend(content.trim(), attachments.length > 0 ? attachments : undefined);\n setContent('');\n setAttachments([]);\n\n // Focus back on textarea\n textareaRef.current?.focus();\n },\n [canSend, content, attachments, disabled, isLoading, onSend]\n );\n\n const handleKeyDown = React.useCallback(\n (e: React.KeyboardEvent) => {\n // Submit on Enter (without Shift)\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault();\n handleSubmit();\n }\n },\n [handleSubmit]\n );\n\n const handleFileSelect = React.useCallback(\n async (e: React.ChangeEvent<HTMLInputElement>) => {\n const files = e.target.files;\n if (!files) return;\n\n const newAttachments: ChatAttachment[] = [];\n\n for (const file of Array.from(files)) {\n if (attachments.length + newAttachments.length >= maxAttachments) break;\n\n const content = await file.text();\n const extension = file.name.split('.').pop()?.toLowerCase() ?? '';\n const isCode = [\n 'ts',\n 'tsx',\n 'js',\n 'jsx',\n 'py',\n 'go',\n 'rs',\n 'java',\n ].includes(extension);\n\n newAttachments.push({\n id: `att_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`,\n type: isCode ? 'code' : 'file',\n name: file.name,\n content,\n mimeType: file.type,\n size: file.size,\n });\n }\n\n setAttachments((prev) => [...prev, ...newAttachments]);\n\n // Reset file input\n e.target.value = '';\n },\n [attachments.length, maxAttachments]\n );\n\n const removeAttachment = React.useCallback((id: string) => {\n setAttachments((prev) => prev.filter((a) => a.id !== id));\n }, []);\n\n return (\n <div className={cn('flex flex-col gap-2', className)}>\n {/* Attachments preview */}\n {attachments.length > 0 && (\n <div className=\"flex flex-wrap gap-2\">\n {attachments.map((attachment) => (\n <div\n key={attachment.id}\n className={cn(\n 'flex items-center gap-1.5 rounded-md px-2 py-1',\n 'bg-muted text-muted-foreground text-sm'\n )}\n >\n {attachment.type === 'code' ? (\n <Code className=\"h-3.5 w-3.5\" />\n ) : (\n <FileText className=\"h-3.5 w-3.5\" />\n )}\n <span className=\"max-w-[150px] truncate\">{attachment.name}</span>\n <button\n type=\"button\"\n onClick={() => removeAttachment(attachment.id)}\n className=\"hover:text-foreground\"\n aria-label={`Remove ${attachment.name}`}\n >\n <X className=\"h-3.5 w-3.5\" />\n </button>\n </div>\n ))}\n </div>\n )}\n\n {/* Input form */}\n <form onSubmit={handleSubmit} className=\"flex items-end gap-2\">\n {/* Attachment button */}\n {showAttachments && (\n <>\n <input\n ref={fileInputRef}\n type=\"file\"\n multiple\n accept=\".ts,.tsx,.js,.jsx,.json,.md,.txt,.py,.go,.rs,.java,.yaml,.yml\"\n onChange={handleFileSelect}\n className=\"hidden\"\n aria-label=\"Attach files\"\n />\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onPress={() => fileInputRef.current?.click()}\n disabled={disabled || attachments.length >= maxAttachments}\n aria-label=\"Attach files\"\n >\n <Paperclip className=\"h-4 w-4\" />\n </Button>\n </>\n )}\n\n {/* Text input */}\n <div className=\"relative flex-1\">\n <Textarea\n value={content}\n onChange={(e) => setContent(e.target.value)}\n onKeyDown={handleKeyDown}\n placeholder={placeholder}\n disabled={disabled}\n className={cn(\n 'max-h-[200px] min-h-[44px] resize-none pr-12',\n 'focus-visible:ring-1'\n )}\n rows={1}\n aria-label=\"Chat message\"\n />\n </div>\n\n {/* Send button */}\n <Button\n type=\"submit\"\n disabled={!canSend || disabled || isLoading}\n size=\"sm\"\n aria-label={isLoading ? 'Sending...' : 'Send message'}\n >\n {isLoading ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Send className=\"h-4 w-4\" />\n )}\n </Button>\n </form>\n\n {/* Helper text */}\n <p className=\"text-muted-foreground text-xs\">\n Press Enter to send, Shift+Enter for new line\n </p>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { ChatMessage as ChatMessage$1 } from "../../core/message-types.js";
2
- import * as react_jsx_runtime0 from "react/jsx-runtime";
2
+ import * as react_jsx_runtime1 from "react/jsx-runtime";
3
3
 
4
4
  //#region src/presentation/components/ChatMessage.d.ts
5
5
  interface ChatMessageProps {
@@ -18,7 +18,7 @@ declare function ChatMessage({
18
18
  className,
19
19
  showCopy,
20
20
  showAvatar
21
- }: ChatMessageProps): react_jsx_runtime0.JSX.Element;
21
+ }: ChatMessageProps): react_jsx_runtime1.JSX.Element;
22
22
  //#endregion
23
23
  export { ChatMessage };
24
24
  //# sourceMappingURL=ChatMessage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ChatMessage.js","names":["blocks: { language: string; code: string; raw: string }[]","parts: React.ReactNode[]"],"sources":["../../../src/presentation/components/ChatMessage.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { cn } from '@contractspec/lib.ui-kit-web/ui/utils';\nimport { Avatar, AvatarFallback } from '@contractspec/lib.ui-kit-web/ui/avatar';\nimport { Skeleton } from '@contractspec/lib.ui-kit-web/ui/skeleton';\nimport { Bot, User, AlertCircle, Copy, Check } from 'lucide-react';\nimport { Button } from '@contractspec/lib.design-system';\nimport type { ChatMessage as ChatMessageType } from '../../core/message-types';\nimport { CodePreview } from './CodePreview';\n\nexport interface ChatMessageProps {\n message: ChatMessageType;\n className?: string;\n /** Show copy button */\n showCopy?: boolean;\n /** Show avatar */\n showAvatar?: boolean;\n}\n\n/**\n * Extract code blocks from message content\n */\nfunction extractCodeBlocks(\n content: string\n): { language: string; code: string; raw: string }[] {\n const codeBlockRegex = /```(\\w+)?\\n([\\s\\S]*?)```/g;\n const blocks: { language: string; code: string; raw: string }[] = [];\n let match;\n\n while ((match = codeBlockRegex.exec(content)) !== null) {\n blocks.push({\n language: match[1] ?? 'text',\n code: match[2] ?? '',\n raw: match[0],\n });\n }\n\n return blocks;\n}\n\n/**\n * Render message content with code blocks\n */\nfunction MessageContent({ content }: { content: string }) {\n const codeBlocks = extractCodeBlocks(content);\n\n if (codeBlocks.length === 0) {\n return <p className=\"whitespace-pre-wrap\">{content}</p>;\n }\n\n // Split content by code blocks and render\n let remaining = content;\n const parts: React.ReactNode[] = [];\n let key = 0;\n\n for (const block of codeBlocks) {\n const [before, after] = remaining.split(block.raw);\n if (before) {\n parts.push(\n <p key={key++} className=\"whitespace-pre-wrap\">\n {before.trim()}\n </p>\n );\n }\n parts.push(\n <CodePreview\n key={key++}\n code={block.code}\n language={block.language}\n className=\"my-2\"\n />\n );\n remaining = after ?? '';\n }\n\n if (remaining.trim()) {\n parts.push(\n <p key={key++} className=\"whitespace-pre-wrap\">\n {remaining.trim()}\n </p>\n );\n }\n\n return <>{parts}</>;\n}\n\n/**\n * Chat message component\n */\nexport function ChatMessage({\n message,\n className,\n showCopy = true,\n showAvatar = true,\n}: ChatMessageProps) {\n const [copied, setCopied] = React.useState(false);\n\n const isUser = message.role === 'user';\n const isError = message.status === 'error';\n const isStreaming = message.status === 'streaming';\n\n const handleCopy = React.useCallback(async () => {\n await navigator.clipboard.writeText(message.content);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n }, [message.content]);\n\n return (\n <div\n className={cn(\n 'group flex gap-3',\n isUser && 'flex-row-reverse',\n className\n )}\n >\n {showAvatar && (\n <Avatar className=\"h-8 w-8 shrink-0\">\n <AvatarFallback\n className={cn(\n isUser ? 'bg-primary text-primary-foreground' : 'bg-muted'\n )}\n >\n {isUser ? (\n <User className=\"h-4 w-4\" />\n ) : (\n <Bot className=\"h-4 w-4\" />\n )}\n </AvatarFallback>\n </Avatar>\n )}\n\n <div\n className={cn('flex max-w-[80%] flex-col gap-1', isUser && 'items-end')}\n >\n <div\n className={cn(\n 'rounded-2xl px-4 py-2',\n isUser\n ? 'bg-primary text-primary-foreground'\n : 'bg-muted text-foreground',\n isError && 'border-destructive bg-destructive/10 border'\n )}\n >\n {isError && message.error ? (\n <div className=\"flex items-start gap-2\">\n <AlertCircle className=\"text-destructive mt-0.5 h-4 w-4 shrink-0\" />\n <div>\n <p className=\"text-destructive font-medium\">\n {message.error.code}\n </p>\n <p className=\"text-muted-foreground text-sm\">\n {message.error.message}\n </p>\n </div>\n </div>\n ) : isStreaming && !message.content ? (\n <div className=\"flex flex-col gap-2\">\n <Skeleton className=\"h-4 w-48\" />\n <Skeleton className=\"h-4 w-32\" />\n </div>\n ) : (\n <MessageContent content={message.content} />\n )}\n </div>\n\n {/* Message meta */}\n <div\n className={cn(\n 'flex items-center gap-2 text-xs',\n 'text-muted-foreground opacity-0 transition-opacity',\n 'group-hover:opacity-100'\n )}\n >\n <span>\n {new Date(message.createdAt).toLocaleTimeString([], {\n hour: '2-digit',\n minute: '2-digit',\n })}\n </span>\n\n {message.usage && (\n <span>\n {message.usage.inputTokens + message.usage.outputTokens} tokens\n </span>\n )}\n\n {showCopy && !isUser && message.content && (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-6 w-6 p-0\"\n onPress={handleCopy}\n aria-label={copied ? 'Copied' : 'Copy message'}\n >\n {copied ? (\n <Check className=\"h-3 w-3\" />\n ) : (\n <Copy className=\"h-3 w-3\" />\n )}\n </Button>\n )}\n </div>\n\n {/* Reasoning (for models that support it) */}\n {message.reasoning && (\n <details className=\"text-muted-foreground mt-2 text-sm\">\n <summary className=\"cursor-pointer hover:underline\">\n View reasoning\n </summary>\n <div className=\"bg-muted mt-1 rounded-md p-2\">\n <p className=\"whitespace-pre-wrap\">{message.reasoning}</p>\n </div>\n </details>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ChatMessage.js","names":[],"sources":["../../../src/presentation/components/ChatMessage.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { cn } from '@contractspec/lib.ui-kit-web/ui/utils';\nimport { Avatar, AvatarFallback } from '@contractspec/lib.ui-kit-web/ui/avatar';\nimport { Skeleton } from '@contractspec/lib.ui-kit-web/ui/skeleton';\nimport { Bot, User, AlertCircle, Copy, Check } from 'lucide-react';\nimport { Button } from '@contractspec/lib.design-system';\nimport type { ChatMessage as ChatMessageType } from '../../core/message-types';\nimport { CodePreview } from './CodePreview';\n\nexport interface ChatMessageProps {\n message: ChatMessageType;\n className?: string;\n /** Show copy button */\n showCopy?: boolean;\n /** Show avatar */\n showAvatar?: boolean;\n}\n\n/**\n * Extract code blocks from message content\n */\nfunction extractCodeBlocks(\n content: string\n): { language: string; code: string; raw: string }[] {\n const codeBlockRegex = /```(\\w+)?\\n([\\s\\S]*?)```/g;\n const blocks: { language: string; code: string; raw: string }[] = [];\n let match;\n\n while ((match = codeBlockRegex.exec(content)) !== null) {\n blocks.push({\n language: match[1] ?? 'text',\n code: match[2] ?? '',\n raw: match[0],\n });\n }\n\n return blocks;\n}\n\n/**\n * Render message content with code blocks\n */\nfunction MessageContent({ content }: { content: string }) {\n const codeBlocks = extractCodeBlocks(content);\n\n if (codeBlocks.length === 0) {\n return <p className=\"whitespace-pre-wrap\">{content}</p>;\n }\n\n // Split content by code blocks and render\n let remaining = content;\n const parts: React.ReactNode[] = [];\n let key = 0;\n\n for (const block of codeBlocks) {\n const [before, after] = remaining.split(block.raw);\n if (before) {\n parts.push(\n <p key={key++} className=\"whitespace-pre-wrap\">\n {before.trim()}\n </p>\n );\n }\n parts.push(\n <CodePreview\n key={key++}\n code={block.code}\n language={block.language}\n className=\"my-2\"\n />\n );\n remaining = after ?? '';\n }\n\n if (remaining.trim()) {\n parts.push(\n <p key={key++} className=\"whitespace-pre-wrap\">\n {remaining.trim()}\n </p>\n );\n }\n\n return <>{parts}</>;\n}\n\n/**\n * Chat message component\n */\nexport function ChatMessage({\n message,\n className,\n showCopy = true,\n showAvatar = true,\n}: ChatMessageProps) {\n const [copied, setCopied] = React.useState(false);\n\n const isUser = message.role === 'user';\n const isError = message.status === 'error';\n const isStreaming = message.status === 'streaming';\n\n const handleCopy = React.useCallback(async () => {\n await navigator.clipboard.writeText(message.content);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n }, [message.content]);\n\n return (\n <div\n className={cn(\n 'group flex gap-3',\n isUser && 'flex-row-reverse',\n className\n )}\n >\n {showAvatar && (\n <Avatar className=\"h-8 w-8 shrink-0\">\n <AvatarFallback\n className={cn(\n isUser ? 'bg-primary text-primary-foreground' : 'bg-muted'\n )}\n >\n {isUser ? (\n <User className=\"h-4 w-4\" />\n ) : (\n <Bot className=\"h-4 w-4\" />\n )}\n </AvatarFallback>\n </Avatar>\n )}\n\n <div\n className={cn('flex max-w-[80%] flex-col gap-1', isUser && 'items-end')}\n >\n <div\n className={cn(\n 'rounded-2xl px-4 py-2',\n isUser\n ? 'bg-primary text-primary-foreground'\n : 'bg-muted text-foreground',\n isError && 'border-destructive bg-destructive/10 border'\n )}\n >\n {isError && message.error ? (\n <div className=\"flex items-start gap-2\">\n <AlertCircle className=\"text-destructive mt-0.5 h-4 w-4 shrink-0\" />\n <div>\n <p className=\"text-destructive font-medium\">\n {message.error.code}\n </p>\n <p className=\"text-muted-foreground text-sm\">\n {message.error.message}\n </p>\n </div>\n </div>\n ) : isStreaming && !message.content ? (\n <div className=\"flex flex-col gap-2\">\n <Skeleton className=\"h-4 w-48\" />\n <Skeleton className=\"h-4 w-32\" />\n </div>\n ) : (\n <MessageContent content={message.content} />\n )}\n </div>\n\n {/* Message meta */}\n <div\n className={cn(\n 'flex items-center gap-2 text-xs',\n 'text-muted-foreground opacity-0 transition-opacity',\n 'group-hover:opacity-100'\n )}\n >\n <span>\n {new Date(message.createdAt).toLocaleTimeString([], {\n hour: '2-digit',\n minute: '2-digit',\n })}\n </span>\n\n {message.usage && (\n <span>\n {message.usage.inputTokens + message.usage.outputTokens} tokens\n </span>\n )}\n\n {showCopy && !isUser && message.content && (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-6 w-6 p-0\"\n onPress={handleCopy}\n aria-label={copied ? 'Copied' : 'Copy message'}\n >\n {copied ? (\n <Check className=\"h-3 w-3\" />\n ) : (\n <Copy className=\"h-3 w-3\" />\n )}\n </Button>\n )}\n </div>\n\n {/* Reasoning (for models that support it) */}\n {message.reasoning && (\n <details className=\"text-muted-foreground mt-2 text-sm\">\n <summary className=\"cursor-pointer hover:underline\">\n View reasoning\n </summary>\n <div className=\"bg-muted mt-1 rounded-md p-2\">\n <p className=\"whitespace-pre-wrap\">{message.reasoning}</p>\n </div>\n </details>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import * as react_jsx_runtime4 from "react/jsx-runtime";
1
+ import * as react_jsx_runtime0 from "react/jsx-runtime";
2
2
 
3
3
  //#region src/presentation/components/CodePreview.d.ts
4
4
  interface CodePreviewProps {
@@ -34,7 +34,7 @@ declare function CodePreview({
34
34
  onExecute,
35
35
  showDownload,
36
36
  maxHeight
37
- }: CodePreviewProps): react_jsx_runtime4.JSX.Element;
37
+ }: CodePreviewProps): react_jsx_runtime0.JSX.Element;
38
38
  //#endregion
39
39
  export { CodePreview };
40
40
  //# sourceMappingURL=CodePreview.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"CodePreview.js","names":["LANGUAGE_NAMES: Record<string, string>"],"sources":["../../../src/presentation/components/CodePreview.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { cn } from '@contractspec/lib.ui-kit-web/ui/utils';\nimport { Button } from '@contractspec/lib.design-system';\nimport { Copy, Check, Play, Download } from 'lucide-react';\n\nexport interface CodePreviewProps {\n /** Code content */\n code: string;\n /** Programming language */\n language?: string;\n /** File name */\n filename?: string;\n /** Additional class name */\n className?: string;\n /** Show copy button */\n showCopy?: boolean;\n /** Show execute button (for applicable languages) */\n showExecute?: boolean;\n /** Called when execute is clicked */\n onExecute?: (code: string) => void;\n /** Show download button */\n showDownload?: boolean;\n /** Max height before scroll */\n maxHeight?: number;\n}\n\n/**\n * Language display names\n */\nconst LANGUAGE_NAMES: Record<string, string> = {\n ts: 'TypeScript',\n tsx: 'TypeScript (React)',\n typescript: 'TypeScript',\n js: 'JavaScript',\n jsx: 'JavaScript (React)',\n javascript: 'JavaScript',\n json: 'JSON',\n md: 'Markdown',\n yaml: 'YAML',\n yml: 'YAML',\n bash: 'Bash',\n sh: 'Shell',\n sql: 'SQL',\n py: 'Python',\n python: 'Python',\n go: 'Go',\n rust: 'Rust',\n rs: 'Rust',\n};\n\n/**\n * Code preview component with syntax highlighting placeholder\n */\nexport function CodePreview({\n code,\n language = 'text',\n filename,\n className,\n showCopy = true,\n showExecute = false,\n onExecute,\n showDownload = false,\n maxHeight = 400,\n}: CodePreviewProps) {\n const [copied, setCopied] = React.useState(false);\n\n const displayLanguage = LANGUAGE_NAMES[language.toLowerCase()] ?? language;\n const lines = code.split('\\n');\n\n const handleCopy = React.useCallback(async () => {\n await navigator.clipboard.writeText(code);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n }, [code]);\n\n const handleDownload = React.useCallback(() => {\n const blob = new Blob([code], { type: 'text/plain' });\n const url = URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url;\n a.download = filename ?? `code.${language}`;\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n URL.revokeObjectURL(url);\n }, [code, filename, language]);\n\n return (\n <div\n className={cn(\n 'overflow-hidden rounded-lg border',\n 'bg-muted/50',\n className\n )}\n >\n {/* Header */}\n <div\n className={cn(\n 'flex items-center justify-between px-3 py-1.5',\n 'bg-muted/80 border-b'\n )}\n >\n <div className=\"flex items-center gap-2 text-sm\">\n {filename && (\n <span className=\"text-foreground font-mono\">{filename}</span>\n )}\n <span className=\"text-muted-foreground\">{displayLanguage}</span>\n </div>\n\n <div className=\"flex items-center gap-1\">\n {showExecute && onExecute && (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onPress={() => onExecute(code)}\n className=\"h-7 w-7 p-0\"\n aria-label=\"Execute code\"\n >\n <Play className=\"h-3.5 w-3.5\" />\n </Button>\n )}\n\n {showDownload && (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onPress={handleDownload}\n className=\"h-7 w-7 p-0\"\n aria-label=\"Download code\"\n >\n <Download className=\"h-3.5 w-3.5\" />\n </Button>\n )}\n\n {showCopy && (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onPress={handleCopy}\n className=\"h-7 w-7 p-0\"\n aria-label={copied ? 'Copied' : 'Copy code'}\n >\n {copied ? (\n <Check className=\"h-3.5 w-3.5 text-green-500\" />\n ) : (\n <Copy className=\"h-3.5 w-3.5\" />\n )}\n </Button>\n )}\n </div>\n </div>\n\n {/* Code content */}\n <div className=\"overflow-auto\" style={{ maxHeight }}>\n <pre className=\"p-3\">\n <code className=\"text-sm\">\n {lines.map((line, i) => (\n <div key={i} className=\"flex\">\n <span className=\"text-muted-foreground mr-4 w-8 text-right select-none\">\n {i + 1}\n </span>\n <span className=\"flex-1\">{line || ' '}</span>\n </div>\n ))}\n </code>\n </pre>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;AA+BA,MAAMA,iBAAyC;CAC7C,IAAI;CACJ,KAAK;CACL,YAAY;CACZ,IAAI;CACJ,KAAK;CACL,YAAY;CACZ,MAAM;CACN,IAAI;CACJ,MAAM;CACN,KAAK;CACL,MAAM;CACN,IAAI;CACJ,KAAK;CACL,IAAI;CACJ,QAAQ;CACR,IAAI;CACJ,MAAM;CACN,IAAI;CACL;;;;AAKD,SAAgB,YAAY,EAC1B,MACA,WAAW,QACX,UACA,WACA,WAAW,MACX,cAAc,OACd,WACA,eAAe,OACf,YAAY,OACO;CACnB,MAAM,CAAC,QAAQ,aAAa,MAAM,SAAS,MAAM;CAEjD,MAAM,kBAAkB,eAAe,SAAS,aAAa,KAAK;CAClE,MAAM,QAAQ,KAAK,MAAM,KAAK;CAE9B,MAAM,aAAa,MAAM,YAAY,YAAY;AAC/C,QAAM,UAAU,UAAU,UAAU,KAAK;AACzC,YAAU,KAAK;AACf,mBAAiB,UAAU,MAAM,EAAE,IAAK;IACvC,CAAC,KAAK,CAAC;CAEV,MAAM,iBAAiB,MAAM,kBAAkB;EAC7C,MAAM,OAAO,IAAI,KAAK,CAAC,KAAK,EAAE,EAAE,MAAM,cAAc,CAAC;EACrD,MAAM,MAAM,IAAI,gBAAgB,KAAK;EACrC,MAAM,IAAI,SAAS,cAAc,IAAI;AACrC,IAAE,OAAO;AACT,IAAE,WAAW,YAAY,QAAQ;AACjC,WAAS,KAAK,YAAY,EAAE;AAC5B,IAAE,OAAO;AACT,WAAS,KAAK,YAAY,EAAE;AAC5B,MAAI,gBAAgB,IAAI;IACvB;EAAC;EAAM;EAAU;EAAS,CAAC;AAE9B,QACE,qBAAC;EACC,WAAW,GACT,qCACA,eACA,UACD;aAGD,qBAAC;GACC,WAAW,GACT,iDACA,uBACD;cAED,qBAAC;IAAI,WAAU;eACZ,YACC,oBAAC;KAAK,WAAU;eAA6B;MAAgB,EAE/D,oBAAC;KAAK,WAAU;eAAyB;MAAuB;KAC5D,EAEN,qBAAC;IAAI,WAAU;;KACZ,eAAe,aACd,oBAAC;MACC,SAAQ;MACR,MAAK;MACL,eAAe,UAAU,KAAK;MAC9B,WAAU;MACV,cAAW;gBAEX,oBAAC,QAAK,WAAU,gBAAgB;OACzB;KAGV,gBACC,oBAAC;MACC,SAAQ;MACR,MAAK;MACL,SAAS;MACT,WAAU;MACV,cAAW;gBAEX,oBAAC,YAAS,WAAU,gBAAgB;OAC7B;KAGV,YACC,oBAAC;MACC,SAAQ;MACR,MAAK;MACL,SAAS;MACT,WAAU;MACV,cAAY,SAAS,WAAW;gBAE/B,SACC,oBAAC,SAAM,WAAU,+BAA+B,GAEhD,oBAAC,QAAK,WAAU,gBAAgB;OAE3B;;KAEP;IACF,EAGN,oBAAC;GAAI,WAAU;GAAgB,OAAO,EAAE,WAAW;aACjD,oBAAC;IAAI,WAAU;cACb,oBAAC;KAAK,WAAU;eACb,MAAM,KAAK,MAAM,MAChB,qBAAC;MAAY,WAAU;iBACrB,oBAAC;OAAK,WAAU;iBACb,IAAI;QACA,EACP,oBAAC;OAAK,WAAU;iBAAU,QAAQ;QAAW;QAJrC,EAKJ,CACN;MACG;KACH;IACF;GACF"}
1
+ {"version":3,"file":"CodePreview.js","names":[],"sources":["../../../src/presentation/components/CodePreview.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { cn } from '@contractspec/lib.ui-kit-web/ui/utils';\nimport { Button } from '@contractspec/lib.design-system';\nimport { Copy, Check, Play, Download } from 'lucide-react';\n\nexport interface CodePreviewProps {\n /** Code content */\n code: string;\n /** Programming language */\n language?: string;\n /** File name */\n filename?: string;\n /** Additional class name */\n className?: string;\n /** Show copy button */\n showCopy?: boolean;\n /** Show execute button (for applicable languages) */\n showExecute?: boolean;\n /** Called when execute is clicked */\n onExecute?: (code: string) => void;\n /** Show download button */\n showDownload?: boolean;\n /** Max height before scroll */\n maxHeight?: number;\n}\n\n/**\n * Language display names\n */\nconst LANGUAGE_NAMES: Record<string, string> = {\n ts: 'TypeScript',\n tsx: 'TypeScript (React)',\n typescript: 'TypeScript',\n js: 'JavaScript',\n jsx: 'JavaScript (React)',\n javascript: 'JavaScript',\n json: 'JSON',\n md: 'Markdown',\n yaml: 'YAML',\n yml: 'YAML',\n bash: 'Bash',\n sh: 'Shell',\n sql: 'SQL',\n py: 'Python',\n python: 'Python',\n go: 'Go',\n rust: 'Rust',\n rs: 'Rust',\n};\n\n/**\n * Code preview component with syntax highlighting placeholder\n */\nexport function CodePreview({\n code,\n language = 'text',\n filename,\n className,\n showCopy = true,\n showExecute = false,\n onExecute,\n showDownload = false,\n maxHeight = 400,\n}: CodePreviewProps) {\n const [copied, setCopied] = React.useState(false);\n\n const displayLanguage = LANGUAGE_NAMES[language.toLowerCase()] ?? language;\n const lines = code.split('\\n');\n\n const handleCopy = React.useCallback(async () => {\n await navigator.clipboard.writeText(code);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n }, [code]);\n\n const handleDownload = React.useCallback(() => {\n const blob = new Blob([code], { type: 'text/plain' });\n const url = URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url;\n a.download = filename ?? `code.${language}`;\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n URL.revokeObjectURL(url);\n }, [code, filename, language]);\n\n return (\n <div\n className={cn(\n 'overflow-hidden rounded-lg border',\n 'bg-muted/50',\n className\n )}\n >\n {/* Header */}\n <div\n className={cn(\n 'flex items-center justify-between px-3 py-1.5',\n 'bg-muted/80 border-b'\n )}\n >\n <div className=\"flex items-center gap-2 text-sm\">\n {filename && (\n <span className=\"text-foreground font-mono\">{filename}</span>\n )}\n <span className=\"text-muted-foreground\">{displayLanguage}</span>\n </div>\n\n <div className=\"flex items-center gap-1\">\n {showExecute && onExecute && (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onPress={() => onExecute(code)}\n className=\"h-7 w-7 p-0\"\n aria-label=\"Execute code\"\n >\n <Play className=\"h-3.5 w-3.5\" />\n </Button>\n )}\n\n {showDownload && (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onPress={handleDownload}\n className=\"h-7 w-7 p-0\"\n aria-label=\"Download code\"\n >\n <Download className=\"h-3.5 w-3.5\" />\n </Button>\n )}\n\n {showCopy && (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onPress={handleCopy}\n className=\"h-7 w-7 p-0\"\n aria-label={copied ? 'Copied' : 'Copy code'}\n >\n {copied ? (\n <Check className=\"h-3.5 w-3.5 text-green-500\" />\n ) : (\n <Copy className=\"h-3.5 w-3.5\" />\n )}\n </Button>\n )}\n </div>\n </div>\n\n {/* Code content */}\n <div className=\"overflow-auto\" style={{ maxHeight }}>\n <pre className=\"p-3\">\n <code className=\"text-sm\">\n {lines.map((line, i) => (\n <div key={i} className=\"flex\">\n <span className=\"text-muted-foreground mr-4 w-8 text-right select-none\">\n {i + 1}\n </span>\n <span className=\"flex-1\">{line || ' '}</span>\n </div>\n ))}\n </code>\n </pre>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;AA+BA,MAAM,iBAAyC;CAC7C,IAAI;CACJ,KAAK;CACL,YAAY;CACZ,IAAI;CACJ,KAAK;CACL,YAAY;CACZ,MAAM;CACN,IAAI;CACJ,MAAM;CACN,KAAK;CACL,MAAM;CACN,IAAI;CACJ,KAAK;CACL,IAAI;CACJ,QAAQ;CACR,IAAI;CACJ,MAAM;CACN,IAAI;CACL;;;;AAKD,SAAgB,YAAY,EAC1B,MACA,WAAW,QACX,UACA,WACA,WAAW,MACX,cAAc,OACd,WACA,eAAe,OACf,YAAY,OACO;CACnB,MAAM,CAAC,QAAQ,aAAa,MAAM,SAAS,MAAM;CAEjD,MAAM,kBAAkB,eAAe,SAAS,aAAa,KAAK;CAClE,MAAM,QAAQ,KAAK,MAAM,KAAK;CAE9B,MAAM,aAAa,MAAM,YAAY,YAAY;AAC/C,QAAM,UAAU,UAAU,UAAU,KAAK;AACzC,YAAU,KAAK;AACf,mBAAiB,UAAU,MAAM,EAAE,IAAK;IACvC,CAAC,KAAK,CAAC;CAEV,MAAM,iBAAiB,MAAM,kBAAkB;EAC7C,MAAM,OAAO,IAAI,KAAK,CAAC,KAAK,EAAE,EAAE,MAAM,cAAc,CAAC;EACrD,MAAM,MAAM,IAAI,gBAAgB,KAAK;EACrC,MAAM,IAAI,SAAS,cAAc,IAAI;AACrC,IAAE,OAAO;AACT,IAAE,WAAW,YAAY,QAAQ;AACjC,WAAS,KAAK,YAAY,EAAE;AAC5B,IAAE,OAAO;AACT,WAAS,KAAK,YAAY,EAAE;AAC5B,MAAI,gBAAgB,IAAI;IACvB;EAAC;EAAM;EAAU;EAAS,CAAC;AAE9B,QACE,qBAAC;EACC,WAAW,GACT,qCACA,eACA,UACD;aAGD,qBAAC;GACC,WAAW,GACT,iDACA,uBACD;cAED,qBAAC;IAAI,WAAU;eACZ,YACC,oBAAC;KAAK,WAAU;eAA6B;MAAgB,EAE/D,oBAAC;KAAK,WAAU;eAAyB;MAAuB;KAC5D,EAEN,qBAAC;IAAI,WAAU;;KACZ,eAAe,aACd,oBAAC;MACC,SAAQ;MACR,MAAK;MACL,eAAe,UAAU,KAAK;MAC9B,WAAU;MACV,cAAW;gBAEX,oBAAC,QAAK,WAAU,gBAAgB;OACzB;KAGV,gBACC,oBAAC;MACC,SAAQ;MACR,MAAK;MACL,SAAS;MACT,WAAU;MACV,cAAW;gBAEX,oBAAC,YAAS,WAAU,gBAAgB;OAC7B;KAGV,YACC,oBAAC;MACC,SAAQ;MACR,MAAK;MACL,SAAS;MACT,WAAU;MACV,cAAY,SAAS,WAAW;gBAE/B,SACC,oBAAC,SAAM,WAAU,+BAA+B,GAEhD,oBAAC,QAAK,WAAU,gBAAgB;OAE3B;;KAEP;IACF,EAGN,oBAAC;GAAI,WAAU;GAAgB,OAAO,EAAE,WAAW;aACjD,oBAAC;IAAI,WAAU;cACb,oBAAC;KAAK,WAAU;eACb,MAAM,KAAK,MAAM,MAChB,qBAAC;MAAY,WAAU;iBACrB,oBAAC;OAAK,WAAU;iBACb,IAAI;QACA,EACP,oBAAC;OAAK,WAAU;iBAAU,QAAQ;QAAW;QAJrC,EAKJ,CACN;MACG;KACH;IACF;GACF"}
@@ -1,5 +1,5 @@
1
1
  import { WorkspaceSummary } from "../../context/workspace-context.js";
2
- import * as react_jsx_runtime3 from "react/jsx-runtime";
2
+ import * as react_jsx_runtime4 from "react/jsx-runtime";
3
3
 
4
4
  //#region src/presentation/components/ContextIndicator.d.ts
5
5
  interface ContextIndicatorProps {
@@ -20,7 +20,7 @@ declare function ContextIndicator({
20
20
  active,
21
21
  className,
22
22
  showDetails
23
- }: ContextIndicatorProps): react_jsx_runtime3.JSX.Element;
23
+ }: ContextIndicatorProps): react_jsx_runtime4.JSX.Element;
24
24
  //#endregion
25
25
  export { ContextIndicator };
26
26
  //# sourceMappingURL=ContextIndicator.d.ts.map
@@ -1,4 +1,4 @@
1
- import * as react_jsx_runtime2 from "react/jsx-runtime";
1
+ import * as react_jsx_runtime3 from "react/jsx-runtime";
2
2
  import { ProviderMode, ProviderName } from "@contractspec/lib.ai-providers";
3
3
 
4
4
  //#region src/presentation/components/ModelPicker.d.ts
@@ -33,7 +33,7 @@ declare function ModelPicker({
33
33
  availableProviders,
34
34
  className,
35
35
  compact
36
- }: ModelPickerProps): react_jsx_runtime2.JSX.Element;
36
+ }: ModelPickerProps): react_jsx_runtime3.JSX.Element;
37
37
  //#endregion
38
38
  export { ModelPicker };
39
39
  //# sourceMappingURL=ModelPicker.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ModelPicker.js","names":["PROVIDER_ICONS: Record<ProviderName, React.ReactNode>","PROVIDER_NAMES: Record<ProviderName, string>","MODE_BADGES: Record<\n ProviderMode,\n { label: string; variant: 'default' | 'secondary' | 'outline' }\n>","models: ModelInfo[]"],"sources":["../../../src/presentation/components/ModelPicker.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { cn } from '@contractspec/lib.ui-kit-web/ui/utils';\nimport { Button } from '@contractspec/lib.design-system';\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@contractspec/lib.ui-kit-web/ui/select';\nimport { Badge } from '@contractspec/lib.ui-kit-web/ui/badge';\nimport { Label } from '@contractspec/lib.ui-kit-web/ui/label';\nimport { Bot, Cloud, Cpu, Sparkles } from 'lucide-react';\nimport {\n getModelsForProvider,\n type ModelInfo,\n type ProviderMode,\n type ProviderName,\n} from '@contractspec/lib.ai-providers';\n\nexport interface ModelSelection {\n provider: ProviderName;\n model: string;\n mode: ProviderMode;\n}\n\nexport interface ModelPickerProps {\n /** Currently selected provider/model */\n value: ModelSelection;\n /** Called when selection changes */\n onChange: (value: ModelSelection) => void;\n /** Available providers (with availability info) */\n availableProviders?: {\n provider: ProviderName;\n available: boolean;\n mode: ProviderMode;\n reason?: string;\n }[];\n /** Additional class name */\n className?: string;\n /** Compact mode (smaller) */\n compact?: boolean;\n}\n\nconst PROVIDER_ICONS: Record<ProviderName, React.ReactNode> = {\n ollama: <Cpu className=\"h-4 w-4\" />,\n openai: <Bot className=\"h-4 w-4\" />,\n anthropic: <Sparkles className=\"h-4 w-4\" />,\n mistral: <Cloud className=\"h-4 w-4\" />,\n gemini: <Sparkles className=\"h-4 w-4\" />,\n};\n\nconst PROVIDER_NAMES: Record<ProviderName, string> = {\n ollama: 'Ollama (Local)',\n openai: 'OpenAI',\n anthropic: 'Anthropic',\n mistral: 'Mistral',\n gemini: 'Google Gemini',\n};\n\nconst MODE_BADGES: Record<\n ProviderMode,\n { label: string; variant: 'default' | 'secondary' | 'outline' }\n> = {\n local: { label: 'Local', variant: 'secondary' },\n byok: { label: 'BYOK', variant: 'outline' },\n managed: { label: 'Managed', variant: 'default' },\n};\n\n/**\n * Model picker component for selecting AI provider and model\n */\nexport function ModelPicker({\n value,\n onChange,\n availableProviders,\n className,\n compact = false,\n}: ModelPickerProps) {\n const providers = availableProviders ?? [\n { provider: 'ollama' as const, available: true, mode: 'local' as const },\n { provider: 'openai' as const, available: true, mode: 'byok' as const },\n { provider: 'anthropic' as const, available: true, mode: 'byok' as const },\n { provider: 'mistral' as const, available: true, mode: 'byok' as const },\n { provider: 'gemini' as const, available: true, mode: 'byok' as const },\n ];\n\n const models: ModelInfo[] = getModelsForProvider(value.provider);\n const selectedModel = models.find((m) => m.id === value.model);\n\n const handleProviderChange = React.useCallback(\n (providerName: string) => {\n const provider = providerName as ProviderName;\n const providerInfo = providers.find((p) => p.provider === provider);\n const providerModels = getModelsForProvider(provider);\n const defaultModel = providerModels[0]?.id ?? '';\n\n onChange({\n provider,\n model: defaultModel,\n mode: providerInfo?.mode ?? 'byok',\n });\n },\n [onChange, providers]\n );\n\n const handleModelChange = React.useCallback(\n (modelId: string) => {\n onChange({\n ...value,\n model: modelId,\n });\n },\n [onChange, value]\n );\n\n if (compact) {\n return (\n <div className={cn('flex items-center gap-2', className)}>\n <Select value={value.provider} onValueChange={handleProviderChange}>\n <SelectTrigger className=\"w-[140px]\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {providers.map((p) => (\n <SelectItem\n key={p.provider}\n value={p.provider}\n disabled={!p.available}\n >\n <div className=\"flex items-center gap-2\">\n {PROVIDER_ICONS[p.provider]}\n <span>{PROVIDER_NAMES[p.provider]}</span>\n </div>\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n\n <Select value={value.model} onValueChange={handleModelChange}>\n <SelectTrigger className=\"w-[160px]\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {models.map((m) => (\n <SelectItem key={m.id} value={m.id}>\n {m.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n );\n }\n\n return (\n <div className={cn('flex flex-col gap-3', className)}>\n {/* Provider selection */}\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"provider-selection\" className=\"text-sm font-medium\">\n Provider\n </Label>\n <div className=\"flex flex-wrap gap-2\" id=\"provider-selection\">\n {providers.map((p) => (\n <Button\n key={p.provider}\n variant={value.provider === p.provider ? 'default' : 'outline'}\n size=\"sm\"\n onPress={() => p.available && handleProviderChange(p.provider)}\n disabled={!p.available}\n className={cn(!p.available && 'opacity-50')}\n >\n {PROVIDER_ICONS[p.provider]}\n <span>{PROVIDER_NAMES[p.provider]}</span>\n <Badge variant={MODE_BADGES[p.mode].variant} className=\"ml-1\">\n {MODE_BADGES[p.mode].label}\n </Badge>\n </Button>\n ))}\n </div>\n </div>\n\n {/* Model selection */}\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"model-picker\" className=\"text-sm font-medium\">\n Model\n </Label>\n <Select\n name=\"model-picker\"\n value={value.model}\n onValueChange={handleModelChange}\n >\n <SelectTrigger>\n <SelectValue placeholder=\"Select a model\" />\n </SelectTrigger>\n <SelectContent>\n {models.map((m) => (\n <SelectItem key={m.id} value={m.id}>\n <div className=\"flex items-center gap-2\">\n <span>{m.name}</span>\n <span className=\"text-muted-foreground text-xs\">\n {Math.round(m.contextWindow / 1000)}K\n </span>\n {m.capabilities.vision && (\n <Badge variant=\"outline\" className=\"text-xs\">\n Vision\n </Badge>\n )}\n {m.capabilities.reasoning && (\n <Badge variant=\"outline\" className=\"text-xs\">\n Reasoning\n </Badge>\n )}\n </div>\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n {/* Model info */}\n {selectedModel && (\n <div className=\"text-muted-foreground flex flex-wrap gap-2 text-xs\">\n <span>\n Context: {Math.round(selectedModel.contextWindow / 1000)}K tokens\n </span>\n {selectedModel.capabilities.vision && <span>• Vision</span>}\n {selectedModel.capabilities.tools && <span>• Tools</span>}\n {selectedModel.capabilities.reasoning && <span>• Reasoning</span>}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ModelPicker.js","names":[],"sources":["../../../src/presentation/components/ModelPicker.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { cn } from '@contractspec/lib.ui-kit-web/ui/utils';\nimport { Button } from '@contractspec/lib.design-system';\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@contractspec/lib.ui-kit-web/ui/select';\nimport { Badge } from '@contractspec/lib.ui-kit-web/ui/badge';\nimport { Label } from '@contractspec/lib.ui-kit-web/ui/label';\nimport { Bot, Cloud, Cpu, Sparkles } from 'lucide-react';\nimport {\n getModelsForProvider,\n type ModelInfo,\n type ProviderMode,\n type ProviderName,\n} from '@contractspec/lib.ai-providers';\n\nexport interface ModelSelection {\n provider: ProviderName;\n model: string;\n mode: ProviderMode;\n}\n\nexport interface ModelPickerProps {\n /** Currently selected provider/model */\n value: ModelSelection;\n /** Called when selection changes */\n onChange: (value: ModelSelection) => void;\n /** Available providers (with availability info) */\n availableProviders?: {\n provider: ProviderName;\n available: boolean;\n mode: ProviderMode;\n reason?: string;\n }[];\n /** Additional class name */\n className?: string;\n /** Compact mode (smaller) */\n compact?: boolean;\n}\n\nconst PROVIDER_ICONS: Record<ProviderName, React.ReactNode> = {\n ollama: <Cpu className=\"h-4 w-4\" />,\n openai: <Bot className=\"h-4 w-4\" />,\n anthropic: <Sparkles className=\"h-4 w-4\" />,\n mistral: <Cloud className=\"h-4 w-4\" />,\n gemini: <Sparkles className=\"h-4 w-4\" />,\n};\n\nconst PROVIDER_NAMES: Record<ProviderName, string> = {\n ollama: 'Ollama (Local)',\n openai: 'OpenAI',\n anthropic: 'Anthropic',\n mistral: 'Mistral',\n gemini: 'Google Gemini',\n};\n\nconst MODE_BADGES: Record<\n ProviderMode,\n { label: string; variant: 'default' | 'secondary' | 'outline' }\n> = {\n local: { label: 'Local', variant: 'secondary' },\n byok: { label: 'BYOK', variant: 'outline' },\n managed: { label: 'Managed', variant: 'default' },\n};\n\n/**\n * Model picker component for selecting AI provider and model\n */\nexport function ModelPicker({\n value,\n onChange,\n availableProviders,\n className,\n compact = false,\n}: ModelPickerProps) {\n const providers = availableProviders ?? [\n { provider: 'ollama' as const, available: true, mode: 'local' as const },\n { provider: 'openai' as const, available: true, mode: 'byok' as const },\n { provider: 'anthropic' as const, available: true, mode: 'byok' as const },\n { provider: 'mistral' as const, available: true, mode: 'byok' as const },\n { provider: 'gemini' as const, available: true, mode: 'byok' as const },\n ];\n\n const models: ModelInfo[] = getModelsForProvider(value.provider);\n const selectedModel = models.find((m) => m.id === value.model);\n\n const handleProviderChange = React.useCallback(\n (providerName: string) => {\n const provider = providerName as ProviderName;\n const providerInfo = providers.find((p) => p.provider === provider);\n const providerModels = getModelsForProvider(provider);\n const defaultModel = providerModels[0]?.id ?? '';\n\n onChange({\n provider,\n model: defaultModel,\n mode: providerInfo?.mode ?? 'byok',\n });\n },\n [onChange, providers]\n );\n\n const handleModelChange = React.useCallback(\n (modelId: string) => {\n onChange({\n ...value,\n model: modelId,\n });\n },\n [onChange, value]\n );\n\n if (compact) {\n return (\n <div className={cn('flex items-center gap-2', className)}>\n <Select value={value.provider} onValueChange={handleProviderChange}>\n <SelectTrigger className=\"w-[140px]\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {providers.map((p) => (\n <SelectItem\n key={p.provider}\n value={p.provider}\n disabled={!p.available}\n >\n <div className=\"flex items-center gap-2\">\n {PROVIDER_ICONS[p.provider]}\n <span>{PROVIDER_NAMES[p.provider]}</span>\n </div>\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n\n <Select value={value.model} onValueChange={handleModelChange}>\n <SelectTrigger className=\"w-[160px]\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {models.map((m) => (\n <SelectItem key={m.id} value={m.id}>\n {m.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n );\n }\n\n return (\n <div className={cn('flex flex-col gap-3', className)}>\n {/* Provider selection */}\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"provider-selection\" className=\"text-sm font-medium\">\n Provider\n </Label>\n <div className=\"flex flex-wrap gap-2\" id=\"provider-selection\">\n {providers.map((p) => (\n <Button\n key={p.provider}\n variant={value.provider === p.provider ? 'default' : 'outline'}\n size=\"sm\"\n onPress={() => p.available && handleProviderChange(p.provider)}\n disabled={!p.available}\n className={cn(!p.available && 'opacity-50')}\n >\n {PROVIDER_ICONS[p.provider]}\n <span>{PROVIDER_NAMES[p.provider]}</span>\n <Badge variant={MODE_BADGES[p.mode].variant} className=\"ml-1\">\n {MODE_BADGES[p.mode].label}\n </Badge>\n </Button>\n ))}\n </div>\n </div>\n\n {/* Model selection */}\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"model-picker\" className=\"text-sm font-medium\">\n Model\n </Label>\n <Select\n name=\"model-picker\"\n value={value.model}\n onValueChange={handleModelChange}\n >\n <SelectTrigger>\n <SelectValue placeholder=\"Select a model\" />\n </SelectTrigger>\n <SelectContent>\n {models.map((m) => (\n <SelectItem key={m.id} value={m.id}>\n <div className=\"flex items-center gap-2\">\n <span>{m.name}</span>\n <span className=\"text-muted-foreground text-xs\">\n {Math.round(m.contextWindow / 1000)}K\n </span>\n {m.capabilities.vision && (\n <Badge variant=\"outline\" className=\"text-xs\">\n Vision\n </Badge>\n )}\n {m.capabilities.reasoning && (\n <Badge variant=\"outline\" className=\"text-xs\">\n Reasoning\n </Badge>\n )}\n </div>\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n {/* Model info */}\n {selectedModel && (\n <div className=\"text-muted-foreground flex flex-wrap gap-2 text-xs\">\n <span>\n Context: {Math.round(selectedModel.contextWindow / 1000)}K tokens\n </span>\n {selectedModel.capabilities.vision && <span>• Vision</span>}\n {selectedModel.capabilities.tools && <span>• Tools</span>}\n {selectedModel.capabilities.reasoning && <span>• Reasoning</span>}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;AA8CA,MAAM,iBAAwD;CAC5D,QAAQ,oBAAC,OAAI,WAAU,YAAY;CACnC,QAAQ,oBAAC,OAAI,WAAU,YAAY;CACnC,WAAW,oBAAC,YAAS,WAAU,YAAY;CAC3C,SAAS,oBAAC,SAAM,WAAU,YAAY;CACtC,QAAQ,oBAAC,YAAS,WAAU,YAAY;CACzC;AAED,MAAM,iBAA+C;CACnD,QAAQ;CACR,QAAQ;CACR,WAAW;CACX,SAAS;CACT,QAAQ;CACT;AAED,MAAM,cAGF;CACF,OAAO;EAAE,OAAO;EAAS,SAAS;EAAa;CAC/C,MAAM;EAAE,OAAO;EAAQ,SAAS;EAAW;CAC3C,SAAS;EAAE,OAAO;EAAW,SAAS;EAAW;CAClD;;;;AAKD,SAAgB,YAAY,EAC1B,OACA,UACA,oBACA,WACA,UAAU,SACS;CACnB,MAAM,YAAY,sBAAsB;EACtC;GAAE,UAAU;GAAmB,WAAW;GAAM,MAAM;GAAkB;EACxE;GAAE,UAAU;GAAmB,WAAW;GAAM,MAAM;GAAiB;EACvE;GAAE,UAAU;GAAsB,WAAW;GAAM,MAAM;GAAiB;EAC1E;GAAE,UAAU;GAAoB,WAAW;GAAM,MAAM;GAAiB;EACxE;GAAE,UAAU;GAAmB,WAAW;GAAM,MAAM;GAAiB;EACxE;CAED,MAAM,SAAsB,qBAAqB,MAAM,SAAS;CAChE,MAAM,gBAAgB,OAAO,MAAM,MAAM,EAAE,OAAO,MAAM,MAAM;CAE9D,MAAM,uBAAuB,MAAM,aAChC,iBAAyB;EACxB,MAAM,WAAW;EACjB,MAAM,eAAe,UAAU,MAAM,MAAM,EAAE,aAAa,SAAS;AAInE,WAAS;GACP;GACA,OALqB,qBAAqB,SAAS,CACjB,IAAI,MAAM;GAK5C,MAAM,cAAc,QAAQ;GAC7B,CAAC;IAEJ,CAAC,UAAU,UAAU,CACtB;CAED,MAAM,oBAAoB,MAAM,aAC7B,YAAoB;AACnB,WAAS;GACP,GAAG;GACH,OAAO;GACR,CAAC;IAEJ,CAAC,UAAU,MAAM,CAClB;AAED,KAAI,QACF,QACE,qBAAC;EAAI,WAAW,GAAG,2BAA2B,UAAU;aACtD,qBAAC;GAAO,OAAO,MAAM;GAAU,eAAe;cAC5C,oBAAC;IAAc,WAAU;cACvB,oBAAC,gBAAc;KACD,EAChB,oBAAC,2BACE,UAAU,KAAK,MACd,oBAAC;IAEC,OAAO,EAAE;IACT,UAAU,CAAC,EAAE;cAEb,qBAAC;KAAI,WAAU;gBACZ,eAAe,EAAE,WAClB,oBAAC,oBAAM,eAAe,EAAE,YAAiB;MACrC;MAPD,EAAE,SAQI,CACb,GACY;IACT,EAET,qBAAC;GAAO,OAAO,MAAM;GAAO,eAAe;cACzC,oBAAC;IAAc,WAAU;cACvB,oBAAC,gBAAc;KACD,EAChB,oBAAC,2BACE,OAAO,KAAK,MACX,oBAAC;IAAsB,OAAO,EAAE;cAC7B,EAAE;MADY,EAAE,GAEN,CACb,GACY;IACT;GACL;AAIV,QACE,qBAAC;EAAI,WAAW,GAAG,uBAAuB,UAAU;;GAElD,qBAAC;IAAI,WAAU;eACb,oBAAC;KAAM,SAAQ;KAAqB,WAAU;eAAsB;MAE5D,EACR,oBAAC;KAAI,WAAU;KAAuB,IAAG;eACtC,UAAU,KAAK,MACd,qBAAC;MAEC,SAAS,MAAM,aAAa,EAAE,WAAW,YAAY;MACrD,MAAK;MACL,eAAe,EAAE,aAAa,qBAAqB,EAAE,SAAS;MAC9D,UAAU,CAAC,EAAE;MACb,WAAW,GAAG,CAAC,EAAE,aAAa,aAAa;;OAE1C,eAAe,EAAE;OAClB,oBAAC,oBAAM,eAAe,EAAE,YAAiB;OACzC,oBAAC;QAAM,SAAS,YAAY,EAAE,MAAM;QAAS,WAAU;kBACpD,YAAY,EAAE,MAAM;SACf;;QAXH,EAAE,SAYA,CACT;MACE;KACF;GAGN,qBAAC;IAAI,WAAU;eACb,oBAAC;KAAM,SAAQ;KAAe,WAAU;eAAsB;MAEtD,EACR,qBAAC;KACC,MAAK;KACL,OAAO,MAAM;KACb,eAAe;gBAEf,oBAAC,2BACC,oBAAC,eAAY,aAAY,mBAAmB,GAC9B,EAChB,oBAAC,2BACE,OAAO,KAAK,MACX,oBAAC;MAAsB,OAAO,EAAE;gBAC9B,qBAAC;OAAI,WAAU;;QACb,oBAAC,oBAAM,EAAE,OAAY;QACrB,qBAAC;SAAK,WAAU;oBACb,KAAK,MAAM,EAAE,gBAAgB,IAAK,EAAC;UAC/B;QACN,EAAE,aAAa,UACd,oBAAC;SAAM,SAAQ;SAAU,WAAU;mBAAU;UAErC;QAET,EAAE,aAAa,aACd,oBAAC;SAAM,SAAQ;SAAU,WAAU;mBAAU;UAErC;;QAEN;QAhBS,EAAE,GAiBN,CACb,GACY;MACT;KACL;GAGL,iBACC,qBAAC;IAAI,WAAU;;KACb,qBAAC;MAAK;MACM,KAAK,MAAM,cAAc,gBAAgB,IAAK;MAAC;SACpD;KACN,cAAc,aAAa,UAAU,oBAAC,oBAAK,aAAe;KAC1D,cAAc,aAAa,SAAS,oBAAC,oBAAK,YAAc;KACxD,cAAc,aAAa,aAAa,oBAAC,oBAAK,gBAAkB;;KAC7D;;GAEJ"}
@@ -1 +1 @@
1
- {"version":3,"file":"useChat.js","names":["userMessage: ChatMessage","assistantMessage: ChatMessage","error"],"sources":["../../../src/presentation/hooks/useChat.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport type {\n ChatAttachment,\n ChatConversation,\n ChatMessage,\n} from '../../core/message-types';\nimport { ChatService } from '../../core/chat-service';\nimport {\n createProvider,\n type ProviderMode,\n type ProviderName,\n} from '@contractspec/lib.ai-providers';\n\n/**\n * Options for useChat hook\n */\nexport interface UseChatOptions {\n /** Provider to use */\n provider?: ProviderName;\n /** Provider mode */\n mode?: ProviderMode;\n /** Model to use */\n model?: string;\n /** API key for BYOK mode */\n apiKey?: string;\n /** API proxy URL for managed mode */\n proxyUrl?: string;\n /** Initial conversation ID to resume */\n conversationId?: string;\n /** System prompt override */\n systemPrompt?: string;\n /** Enable streaming */\n streaming?: boolean;\n /** Called when a message is sent */\n onSend?: (message: ChatMessage) => void;\n /** Called when a response is received */\n onResponse?: (message: ChatMessage) => void;\n /** Called on error */\n onError?: (error: Error) => void;\n /** Called when usage is recorded */\n onUsage?: (usage: { inputTokens: number; outputTokens: number }) => void;\n}\n\n/**\n * Return type for useChat hook\n */\nexport interface UseChatReturn {\n /** Current messages */\n messages: ChatMessage[];\n /** Current conversation */\n conversation: ChatConversation | null;\n /** Whether currently loading/streaming */\n isLoading: boolean;\n /** Current error */\n error: Error | null;\n /** Send a message */\n sendMessage: (\n content: string,\n attachments?: ChatAttachment[]\n ) => Promise<void>;\n /** Clear conversation and start fresh */\n clearConversation: () => void;\n /** Set conversation ID to resume */\n setConversationId: (id: string | null) => void;\n /** Regenerate last response */\n regenerate: () => Promise<void>;\n /** Stop current generation */\n stop: () => void;\n}\n\n/**\n * Hook for managing AI chat state\n */\nexport function useChat(options: UseChatOptions = {}): UseChatReturn {\n const {\n provider = 'openai',\n mode = 'byok',\n model,\n apiKey,\n proxyUrl,\n conversationId: initialConversationId,\n systemPrompt,\n streaming = true,\n onSend,\n onResponse,\n onError,\n onUsage,\n } = options;\n\n const [messages, setMessages] = React.useState<ChatMessage[]>([]);\n const [conversation, setConversation] =\n React.useState<ChatConversation | null>(null);\n const [isLoading, setIsLoading] = React.useState(false);\n const [error, setError] = React.useState<Error | null>(null);\n const [conversationId, setConversationId] = React.useState<string | null>(\n initialConversationId ?? null\n );\n\n const abortControllerRef = React.useRef<AbortController | null>(null);\n const chatServiceRef = React.useRef<ChatService | null>(null);\n\n // Initialize chat service\n React.useEffect(() => {\n const chatProvider = createProvider({\n provider,\n model,\n apiKey,\n proxyUrl,\n });\n\n chatServiceRef.current = new ChatService({\n provider: chatProvider,\n systemPrompt,\n onUsage,\n });\n }, [provider, mode, model, apiKey, proxyUrl, systemPrompt, onUsage]);\n\n // Load existing conversation\n React.useEffect(() => {\n if (!conversationId || !chatServiceRef.current) return;\n\n const loadConversation = async () => {\n if (!chatServiceRef.current) return;\n\n const conv = await chatServiceRef.current.getConversation(conversationId);\n if (conv) {\n setConversation(conv);\n setMessages(conv.messages);\n }\n };\n\n loadConversation().catch(console.error);\n }, [conversationId]);\n\n const sendMessage = React.useCallback(\n async (content: string, attachments?: ChatAttachment[]) => {\n if (!chatServiceRef.current) {\n throw new Error('Chat service not initialized');\n }\n\n setIsLoading(true);\n setError(null);\n\n // Create abort controller\n abortControllerRef.current = new AbortController();\n\n try {\n // Add user message immediately\n const userMessage: ChatMessage = {\n id: `msg_${Date.now()}`,\n conversationId: conversationId ?? '',\n role: 'user',\n content,\n status: 'completed',\n createdAt: new Date(),\n updatedAt: new Date(),\n attachments,\n };\n setMessages((prev) => [...prev, userMessage]);\n onSend?.(userMessage);\n\n if (streaming) {\n // Streaming mode\n const result = await chatServiceRef.current.stream({\n conversationId: conversationId ?? undefined,\n content,\n attachments,\n });\n\n // Update conversation ID if new\n if (!conversationId) {\n setConversationId(result.conversationId);\n }\n\n // Add placeholder for assistant message\n const assistantMessage: ChatMessage = {\n id: result.messageId,\n conversationId: result.conversationId,\n role: 'assistant',\n content: '',\n status: 'streaming',\n createdAt: new Date(),\n updatedAt: new Date(),\n };\n setMessages((prev) => [...prev, assistantMessage]);\n\n // Process stream\n let fullContent = '';\n for await (const chunk of result.stream) {\n if (chunk.type === 'text' && chunk.content) {\n fullContent += chunk.content;\n setMessages((prev) =>\n prev.map((m) =>\n m.id === result.messageId ? { ...m, content: fullContent } : m\n )\n );\n } else if (chunk.type === 'done') {\n setMessages((prev) =>\n prev.map((m) =>\n m.id === result.messageId\n ? {\n ...m,\n status: 'completed',\n usage: chunk.usage,\n updatedAt: new Date(),\n }\n : m\n )\n );\n onResponse?.(\n messages.find((m) => m.id === result.messageId) ??\n assistantMessage\n );\n } else if (chunk.type === 'error') {\n setMessages((prev) =>\n prev.map((m) =>\n m.id === result.messageId\n ? {\n ...m,\n status: 'error',\n error: chunk.error,\n updatedAt: new Date(),\n }\n : m\n )\n );\n if (chunk.error) {\n const err = new Error(chunk.error.message);\n setError(err);\n onError?.(err);\n }\n }\n }\n } else {\n // Non-streaming mode\n const result = await chatServiceRef.current.send({\n conversationId: conversationId ?? undefined,\n content,\n attachments,\n });\n\n setConversation(result.conversation);\n setMessages(result.conversation.messages);\n\n if (!conversationId) {\n setConversationId(result.conversation.id);\n }\n\n onResponse?.(result.message);\n }\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n setError(error);\n onError?.(error);\n } finally {\n setIsLoading(false);\n abortControllerRef.current = null;\n }\n },\n [conversationId, streaming, onSend, onResponse, onError, messages]\n );\n\n const clearConversation = React.useCallback(() => {\n setMessages([]);\n setConversation(null);\n setConversationId(null);\n setError(null);\n }, []);\n\n const regenerate = React.useCallback(async () => {\n // Find the last user message\n const lastUserMessageIndex = messages.findLastIndex(\n (m) => m.role === 'user'\n );\n if (lastUserMessageIndex === -1) return;\n\n const lastUserMessage = messages[lastUserMessageIndex];\n if (!lastUserMessage) return;\n\n // Remove the last assistant message\n setMessages((prev) => prev.slice(0, lastUserMessageIndex + 1));\n\n // Resend\n await sendMessage(lastUserMessage.content, lastUserMessage.attachments);\n }, [messages, sendMessage]);\n\n const stop = React.useCallback(() => {\n abortControllerRef.current?.abort();\n setIsLoading(false);\n }, []);\n\n return {\n messages,\n conversation,\n isLoading,\n error,\n sendMessage,\n clearConversation,\n setConversationId,\n regenerate,\n stop,\n };\n}\n"],"mappings":";;;;;;;;;;AA2EA,SAAgB,QAAQ,UAA0B,EAAE,EAAiB;CACnE,MAAM,EACJ,WAAW,UACX,OAAO,QACP,OACA,QACA,UACA,gBAAgB,uBAChB,cACA,YAAY,MACZ,QACA,YACA,SACA,YACE;CAEJ,MAAM,CAAC,UAAU,eAAe,MAAM,SAAwB,EAAE,CAAC;CACjE,MAAM,CAAC,cAAc,mBACnB,MAAM,SAAkC,KAAK;CAC/C,MAAM,CAAC,WAAW,gBAAgB,MAAM,SAAS,MAAM;CACvD,MAAM,CAAC,OAAO,YAAY,MAAM,SAAuB,KAAK;CAC5D,MAAM,CAAC,gBAAgB,qBAAqB,MAAM,SAChD,yBAAyB,KAC1B;CAED,MAAM,qBAAqB,MAAM,OAA+B,KAAK;CACrE,MAAM,iBAAiB,MAAM,OAA2B,KAAK;AAG7D,OAAM,gBAAgB;AAQpB,iBAAe,UAAU,IAAI,YAAY;GACvC,UARmB,eAAe;IAClC;IACA;IACA;IACA;IACD,CAAC;GAIA;GACA;GACD,CAAC;IACD;EAAC;EAAU;EAAM;EAAO;EAAQ;EAAU;EAAc;EAAQ,CAAC;AAGpE,OAAM,gBAAgB;AACpB,MAAI,CAAC,kBAAkB,CAAC,eAAe,QAAS;EAEhD,MAAM,mBAAmB,YAAY;AACnC,OAAI,CAAC,eAAe,QAAS;GAE7B,MAAM,OAAO,MAAM,eAAe,QAAQ,gBAAgB,eAAe;AACzE,OAAI,MAAM;AACR,oBAAgB,KAAK;AACrB,gBAAY,KAAK,SAAS;;;AAI9B,oBAAkB,CAAC,MAAM,QAAQ,MAAM;IACtC,CAAC,eAAe,CAAC;CAEpB,MAAM,cAAc,MAAM,YACxB,OAAO,SAAiB,gBAAmC;AACzD,MAAI,CAAC,eAAe,QAClB,OAAM,IAAI,MAAM,+BAA+B;AAGjD,eAAa,KAAK;AAClB,WAAS,KAAK;AAGd,qBAAmB,UAAU,IAAI,iBAAiB;AAElD,MAAI;GAEF,MAAMA,cAA2B;IAC/B,IAAI,OAAO,KAAK,KAAK;IACrB,gBAAgB,kBAAkB;IAClC,MAAM;IACN;IACA,QAAQ;IACR,2BAAW,IAAI,MAAM;IACrB,2BAAW,IAAI,MAAM;IACrB;IACD;AACD,gBAAa,SAAS,CAAC,GAAG,MAAM,YAAY,CAAC;AAC7C,YAAS,YAAY;AAErB,OAAI,WAAW;IAEb,MAAM,SAAS,MAAM,eAAe,QAAQ,OAAO;KACjD,gBAAgB,kBAAkB;KAClC;KACA;KACD,CAAC;AAGF,QAAI,CAAC,eACH,mBAAkB,OAAO,eAAe;IAI1C,MAAMC,mBAAgC;KACpC,IAAI,OAAO;KACX,gBAAgB,OAAO;KACvB,MAAM;KACN,SAAS;KACT,QAAQ;KACR,2BAAW,IAAI,MAAM;KACrB,2BAAW,IAAI,MAAM;KACtB;AACD,iBAAa,SAAS,CAAC,GAAG,MAAM,iBAAiB,CAAC;IAGlD,IAAI,cAAc;AAClB,eAAW,MAAM,SAAS,OAAO,OAC/B,KAAI,MAAM,SAAS,UAAU,MAAM,SAAS;AAC1C,oBAAe,MAAM;AACrB,kBAAa,SACX,KAAK,KAAK,MACR,EAAE,OAAO,OAAO,YAAY;MAAE,GAAG;MAAG,SAAS;MAAa,GAAG,EAC9D,CACF;eACQ,MAAM,SAAS,QAAQ;AAChC,kBAAa,SACX,KAAK,KAAK,MACR,EAAE,OAAO,OAAO,YACZ;MACE,GAAG;MACH,QAAQ;MACR,OAAO,MAAM;MACb,2BAAW,IAAI,MAAM;MACtB,GACD,EACL,CACF;AACD,kBACE,SAAS,MAAM,MAAM,EAAE,OAAO,OAAO,UAAU,IAC7C,iBACH;eACQ,MAAM,SAAS,SAAS;AACjC,kBAAa,SACX,KAAK,KAAK,MACR,EAAE,OAAO,OAAO,YACZ;MACE,GAAG;MACH,QAAQ;MACR,OAAO,MAAM;MACb,2BAAW,IAAI,MAAM;MACtB,GACD,EACL,CACF;AACD,SAAI,MAAM,OAAO;MACf,MAAM,MAAM,IAAI,MAAM,MAAM,MAAM,QAAQ;AAC1C,eAAS,IAAI;AACb,gBAAU,IAAI;;;UAIf;IAEL,MAAM,SAAS,MAAM,eAAe,QAAQ,KAAK;KAC/C,gBAAgB,kBAAkB;KAClC;KACA;KACD,CAAC;AAEF,oBAAgB,OAAO,aAAa;AACpC,gBAAY,OAAO,aAAa,SAAS;AAEzC,QAAI,CAAC,eACH,mBAAkB,OAAO,aAAa,GAAG;AAG3C,iBAAa,OAAO,QAAQ;;WAEvB,KAAK;GACZ,MAAMC,UAAQ,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,IAAI,CAAC;AACjE,YAASA,QAAM;AACf,aAAUA,QAAM;YACR;AACR,gBAAa,MAAM;AACnB,sBAAmB,UAAU;;IAGjC;EAAC;EAAgB;EAAW;EAAQ;EAAY;EAAS;EAAS,CACnE;AA+BD,QAAO;EACL;EACA;EACA;EACA;EACA;EACA,mBAnCwB,MAAM,kBAAkB;AAChD,eAAY,EAAE,CAAC;AACf,mBAAgB,KAAK;AACrB,qBAAkB,KAAK;AACvB,YAAS,KAAK;KACb,EAAE,CAAC;EA+BJ;EACA,YA9BiB,MAAM,YAAY,YAAY;GAE/C,MAAM,uBAAuB,SAAS,eACnC,MAAM,EAAE,SAAS,OACnB;AACD,OAAI,yBAAyB,GAAI;GAEjC,MAAM,kBAAkB,SAAS;AACjC,OAAI,CAAC,gBAAiB;AAGtB,gBAAa,SAAS,KAAK,MAAM,GAAG,uBAAuB,EAAE,CAAC;AAG9D,SAAM,YAAY,gBAAgB,SAAS,gBAAgB,YAAY;KACtE,CAAC,UAAU,YAAY,CAAC;EAgBzB,MAdW,MAAM,kBAAkB;AACnC,sBAAmB,SAAS,OAAO;AACnC,gBAAa,MAAM;KAClB,EAAE,CAAC;EAYL"}
1
+ {"version":3,"file":"useChat.js","names":["error"],"sources":["../../../src/presentation/hooks/useChat.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport type {\n ChatAttachment,\n ChatConversation,\n ChatMessage,\n} from '../../core/message-types';\nimport { ChatService } from '../../core/chat-service';\nimport {\n createProvider,\n type ProviderMode,\n type ProviderName,\n} from '@contractspec/lib.ai-providers';\n\n/**\n * Options for useChat hook\n */\nexport interface UseChatOptions {\n /** Provider to use */\n provider?: ProviderName;\n /** Provider mode */\n mode?: ProviderMode;\n /** Model to use */\n model?: string;\n /** API key for BYOK mode */\n apiKey?: string;\n /** API proxy URL for managed mode */\n proxyUrl?: string;\n /** Initial conversation ID to resume */\n conversationId?: string;\n /** System prompt override */\n systemPrompt?: string;\n /** Enable streaming */\n streaming?: boolean;\n /** Called when a message is sent */\n onSend?: (message: ChatMessage) => void;\n /** Called when a response is received */\n onResponse?: (message: ChatMessage) => void;\n /** Called on error */\n onError?: (error: Error) => void;\n /** Called when usage is recorded */\n onUsage?: (usage: { inputTokens: number; outputTokens: number }) => void;\n}\n\n/**\n * Return type for useChat hook\n */\nexport interface UseChatReturn {\n /** Current messages */\n messages: ChatMessage[];\n /** Current conversation */\n conversation: ChatConversation | null;\n /** Whether currently loading/streaming */\n isLoading: boolean;\n /** Current error */\n error: Error | null;\n /** Send a message */\n sendMessage: (\n content: string,\n attachments?: ChatAttachment[]\n ) => Promise<void>;\n /** Clear conversation and start fresh */\n clearConversation: () => void;\n /** Set conversation ID to resume */\n setConversationId: (id: string | null) => void;\n /** Regenerate last response */\n regenerate: () => Promise<void>;\n /** Stop current generation */\n stop: () => void;\n}\n\n/**\n * Hook for managing AI chat state\n */\nexport function useChat(options: UseChatOptions = {}): UseChatReturn {\n const {\n provider = 'openai',\n mode = 'byok',\n model,\n apiKey,\n proxyUrl,\n conversationId: initialConversationId,\n systemPrompt,\n streaming = true,\n onSend,\n onResponse,\n onError,\n onUsage,\n } = options;\n\n const [messages, setMessages] = React.useState<ChatMessage[]>([]);\n const [conversation, setConversation] =\n React.useState<ChatConversation | null>(null);\n const [isLoading, setIsLoading] = React.useState(false);\n const [error, setError] = React.useState<Error | null>(null);\n const [conversationId, setConversationId] = React.useState<string | null>(\n initialConversationId ?? null\n );\n\n const abortControllerRef = React.useRef<AbortController | null>(null);\n const chatServiceRef = React.useRef<ChatService | null>(null);\n\n // Initialize chat service\n React.useEffect(() => {\n const chatProvider = createProvider({\n provider,\n model,\n apiKey,\n proxyUrl,\n });\n\n chatServiceRef.current = new ChatService({\n provider: chatProvider,\n systemPrompt,\n onUsage,\n });\n }, [provider, mode, model, apiKey, proxyUrl, systemPrompt, onUsage]);\n\n // Load existing conversation\n React.useEffect(() => {\n if (!conversationId || !chatServiceRef.current) return;\n\n const loadConversation = async () => {\n if (!chatServiceRef.current) return;\n\n const conv = await chatServiceRef.current.getConversation(conversationId);\n if (conv) {\n setConversation(conv);\n setMessages(conv.messages);\n }\n };\n\n loadConversation().catch(console.error);\n }, [conversationId]);\n\n const sendMessage = React.useCallback(\n async (content: string, attachments?: ChatAttachment[]) => {\n if (!chatServiceRef.current) {\n throw new Error('Chat service not initialized');\n }\n\n setIsLoading(true);\n setError(null);\n\n // Create abort controller\n abortControllerRef.current = new AbortController();\n\n try {\n // Add user message immediately\n const userMessage: ChatMessage = {\n id: `msg_${Date.now()}`,\n conversationId: conversationId ?? '',\n role: 'user',\n content,\n status: 'completed',\n createdAt: new Date(),\n updatedAt: new Date(),\n attachments,\n };\n setMessages((prev) => [...prev, userMessage]);\n onSend?.(userMessage);\n\n if (streaming) {\n // Streaming mode\n const result = await chatServiceRef.current.stream({\n conversationId: conversationId ?? undefined,\n content,\n attachments,\n });\n\n // Update conversation ID if new\n if (!conversationId) {\n setConversationId(result.conversationId);\n }\n\n // Add placeholder for assistant message\n const assistantMessage: ChatMessage = {\n id: result.messageId,\n conversationId: result.conversationId,\n role: 'assistant',\n content: '',\n status: 'streaming',\n createdAt: new Date(),\n updatedAt: new Date(),\n };\n setMessages((prev) => [...prev, assistantMessage]);\n\n // Process stream\n let fullContent = '';\n for await (const chunk of result.stream) {\n if (chunk.type === 'text' && chunk.content) {\n fullContent += chunk.content;\n setMessages((prev) =>\n prev.map((m) =>\n m.id === result.messageId ? { ...m, content: fullContent } : m\n )\n );\n } else if (chunk.type === 'done') {\n setMessages((prev) =>\n prev.map((m) =>\n m.id === result.messageId\n ? {\n ...m,\n status: 'completed',\n usage: chunk.usage,\n updatedAt: new Date(),\n }\n : m\n )\n );\n onResponse?.(\n messages.find((m) => m.id === result.messageId) ??\n assistantMessage\n );\n } else if (chunk.type === 'error') {\n setMessages((prev) =>\n prev.map((m) =>\n m.id === result.messageId\n ? {\n ...m,\n status: 'error',\n error: chunk.error,\n updatedAt: new Date(),\n }\n : m\n )\n );\n if (chunk.error) {\n const err = new Error(chunk.error.message);\n setError(err);\n onError?.(err);\n }\n }\n }\n } else {\n // Non-streaming mode\n const result = await chatServiceRef.current.send({\n conversationId: conversationId ?? undefined,\n content,\n attachments,\n });\n\n setConversation(result.conversation);\n setMessages(result.conversation.messages);\n\n if (!conversationId) {\n setConversationId(result.conversation.id);\n }\n\n onResponse?.(result.message);\n }\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n setError(error);\n onError?.(error);\n } finally {\n setIsLoading(false);\n abortControllerRef.current = null;\n }\n },\n [conversationId, streaming, onSend, onResponse, onError, messages]\n );\n\n const clearConversation = React.useCallback(() => {\n setMessages([]);\n setConversation(null);\n setConversationId(null);\n setError(null);\n }, []);\n\n const regenerate = React.useCallback(async () => {\n // Find the last user message\n const lastUserMessageIndex = messages.findLastIndex(\n (m) => m.role === 'user'\n );\n if (lastUserMessageIndex === -1) return;\n\n const lastUserMessage = messages[lastUserMessageIndex];\n if (!lastUserMessage) return;\n\n // Remove the last assistant message\n setMessages((prev) => prev.slice(0, lastUserMessageIndex + 1));\n\n // Resend\n await sendMessage(lastUserMessage.content, lastUserMessage.attachments);\n }, [messages, sendMessage]);\n\n const stop = React.useCallback(() => {\n abortControllerRef.current?.abort();\n setIsLoading(false);\n }, []);\n\n return {\n messages,\n conversation,\n isLoading,\n error,\n sendMessage,\n clearConversation,\n setConversationId,\n regenerate,\n stop,\n };\n}\n"],"mappings":"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"}