@cossistant/core 0.0.29 → 0.0.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/_virtual/rolldown_runtime.js +27 -12
- package/ai-sdk-utils.d.ts +141 -0
- package/ai-sdk-utils.d.ts.map +1 -0
- package/ai-sdk-utils.js +255 -0
- package/ai-sdk-utils.js.map +1 -0
- package/client.d.ts +6 -6
- package/client.d.ts.map +1 -1
- package/client.js +2 -2
- package/client.js.map +1 -1
- package/index.d.ts +3 -1
- package/index.js +3 -1
- package/package.json +1 -1
- package/privacy-filter.d.ts +112 -0
- package/privacy-filter.d.ts.map +1 -0
- package/privacy-filter.js +170 -0
- package/privacy-filter.js.map +1 -0
- package/rest-client.d.ts +4 -4
- package/rest-client.d.ts.map +1 -1
- package/rest-client.js +4 -4
- package/rest-client.js.map +1 -1
- package/store/conversations-store.d.ts +1 -1
- package/store/seen-store.d.ts +2 -2
- package/store/timeline-items-store.d.ts +5 -5
- package/store/timeline-items-store.d.ts.map +1 -1
- package/store/timeline-items-store.js +1 -1
- package/store/timeline-items-store.js.map +1 -1
- package/store/typing-store.d.ts +1 -1
- package/store/typing-store.d.ts.map +1 -1
- package/store/typing-store.js +12 -19
- package/store/typing-store.js.map +1 -1
- package/types/src/api/contact.d.ts.map +1 -0
- package/{conversation.d.ts → types/src/api/conversation.d.ts} +458 -78
- package/types/src/api/conversation.d.ts.map +1 -0
- package/types/src/api/timeline-item.d.ts +602 -0
- package/types/src/api/timeline-item.d.ts.map +1 -0
- package/types/src/api/timeline-item.js +67 -19
- package/types/src/api/timeline-item.js.map +1 -1
- package/types/src/api/upload.d.ts.map +1 -0
- package/{realtime-events.d.ts → types/src/realtime-events.d.ts} +446 -48
- package/types/src/realtime-events.d.ts.map +1 -0
- package/{schemas.d.ts → types/src/schemas.d.ts} +92 -16
- package/types/src/schemas.d.ts.map +1 -0
- package/contact.d.ts.map +0 -1
- package/conversation.d.ts.map +0 -1
- package/realtime-events.d.ts.map +0 -1
- package/schemas.d.ts.map +0 -1
- package/timeline-item.d.ts +0 -298
- package/timeline-item.d.ts.map +0 -1
- package/upload.d.ts.map +0 -1
- /package/{contact.d.ts → types/src/api/contact.d.ts} +0 -0
- /package/{upload.d.ts → types/src/api/upload.d.ts} +0 -0
package/rest-client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rest-client.js","names":["headers: Record<string, string>","body: CreateConversationRequestBody","body: MarkConversationSeenRequestBody","body: SetConversationTypingRequestBody","body: GenerateUploadUrlRequest"],"sources":["../src/rest-client.ts"],"sourcesContent":["import type { IdentifyContactResponse } from \"@cossistant/types/api/contact\";\nimport type {\n\tCreateConversationRequestBody,\n\tCreateConversationResponseBody,\n\tGetConversationRequest,\n\tGetConversationResponse,\n\tGetConversationSeenDataResponse,\n\tListConversationsRequest,\n\tListConversationsResponse,\n\tMarkConversationSeenRequestBody,\n\tMarkConversationSeenResponseBody,\n\tSetConversationTypingRequestBody,\n\tSetConversationTypingResponseBody,\n} from \"@cossistant/types/api/conversation\";\nimport type {\n\tGetConversationTimelineItemsRequest,\n\tGetConversationTimelineItemsResponse,\n\tSendTimelineItemRequest,\n\tSendTimelineItemResponse,\n} from \"@cossistant/types/api/timeline-item\";\nimport type {\n\tGenerateUploadUrlRequest,\n\tGenerateUploadUrlResponse,\n} from \"@cossistant/types/api/upload\";\nimport { logger } from \"./logger\";\nimport {\n\tCossistantAPIError,\n\ttype CossistantConfig,\n\ttype PublicWebsiteResponse,\n\ttype UpdateVisitorRequest,\n\ttype VisitorMetadata,\n\ttype VisitorResponse,\n} from \"./types\";\nimport {\n\tisAllowedMimeType,\n\tMAX_FILE_SIZE,\n\tvalidateFile,\n} from \"./upload-constants\";\nimport { generateConversationId } from \"./utils\";\nimport { collectVisitorData } from \"./visitor-data\";\nimport {\n\tgetExistingVisitorId,\n\tgetVisitorId,\n\tsetVisitorId,\n} from \"./visitor-tracker\";\n\nexport class CossistantRestClient {\n\tprivate config: CossistantConfig;\n\tprivate baseHeaders: Record<string, string>;\n\tprivate publicKey: string;\n\tprivate websiteId: string | null = null;\n\tprivate visitorId: string | null = null;\n\tprivate visitorBlocked = false;\n\n\tconstructor(config: CossistantConfig) {\n\t\tthis.config = config;\n\n\t\t// Get public key from config or environment variables\n\t\tthis.publicKey =\n\t\t\tconfig.publicKey ||\n\t\t\t(typeof process !== \"undefined\"\n\t\t\t\t? process.env.NEXT_PUBLIC_COSSISTANT_API_KEY\n\t\t\t\t: undefined) ||\n\t\t\t(typeof process !== \"undefined\"\n\t\t\t\t? process.env.COSSISTANT_API_KEY\n\t\t\t\t: undefined) ||\n\t\t\t(typeof process !== \"undefined\"\n\t\t\t\t? process.env.NEXT_PUBLIC_COSSISTANT_KEY\n\t\t\t\t: undefined) ||\n\t\t\t\"\";\n\n\t\tif (!this.publicKey) {\n\t\t\tthrow new Error(\n\t\t\t\t\"Public key is required. Please provide it in the config or set NEXT_PUBLIC_COSSISTANT_API_KEY or COSSISTANT_API_KEY environment variable.\"\n\t\t\t);\n\t\t}\n\n\t\tthis.baseHeaders = {\n\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\"X-Public-Key\": this.publicKey,\n\t\t};\n\n\t\tif (config.userId) {\n\t\t\tthis.baseHeaders[\"X-User-ID\"] = config.userId;\n\t\t}\n\n\t\tif (config.organizationId) {\n\t\t\tthis.baseHeaders[\"X-Organization-ID\"] = config.organizationId;\n\t\t}\n\t}\n\n\tprivate normalizeVisitorResponse(payload: VisitorResponse): VisitorResponse {\n\t\tconst contact = payload.contact ? payload.contact : null;\n\t\treturn {\n\t\t\t...payload,\n\t\t\t// Ensure latitude and longitude are numbers or null\n\t\t\tlatitude:\n\t\t\t\ttypeof payload.latitude === \"string\"\n\t\t\t\t\t? Number.parseFloat(payload.latitude)\n\t\t\t\t\t: payload.latitude,\n\t\t\tlongitude:\n\t\t\t\ttypeof payload.longitude === \"string\"\n\t\t\t\t\t? Number.parseFloat(payload.longitude)\n\t\t\t\t\t: payload.longitude,\n\t\t\tcreatedAt: payload.createdAt,\n\t\t\tupdatedAt: payload.updatedAt,\n\t\t\tlastSeenAt: payload.lastSeenAt ? payload.lastSeenAt : null,\n\t\t\tblockedAt: payload.blockedAt ? payload.blockedAt : null,\n\t\t\tcontact: payload.contact ? payload.contact : null,\n\t\t};\n\t}\n\n\tprivate resolveVisitorId(): string {\n\t\tif (this.visitorId) {\n\t\t\treturn this.visitorId;\n\t\t}\n\n\t\tif (this.websiteId) {\n\t\t\tconst storedVisitorId = getVisitorId(this.websiteId);\n\t\t\tif (storedVisitorId) {\n\t\t\t\tthis.visitorId = storedVisitorId;\n\t\t\t\treturn storedVisitorId;\n\t\t\t}\n\t\t}\n\n\t\tthrow new Error(\"Visitor ID is required\");\n\t}\n\n\tprivate async syncVisitorSnapshot(visitorId: string): Promise<void> {\n\t\ttry {\n\t\t\tconst visitorData = await collectVisitorData();\n\t\t\tif (!visitorData) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst payload = Object.entries(visitorData).reduce<\n\t\t\t\tPartial<UpdateVisitorRequest>\n\t\t\t>((acc, [key, value]) => {\n\t\t\t\tif (value === null || value === undefined) {\n\t\t\t\t\treturn acc;\n\t\t\t\t}\n\t\t\t\t(acc as Record<string, unknown>)[key] = value;\n\t\t\t\treturn acc;\n\t\t\t}, {});\n\n\t\t\tif (Object.keys(payload).length === 0) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tawait this.request<VisitorResponse>(`/visitors/${visitorId}`, {\n\t\t\t\tmethod: \"PATCH\",\n\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\theaders: {\n\t\t\t\t\t\"X-Visitor-Id\": visitorId,\n\t\t\t\t},\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tlogger.warn(\"Failed to sync visitor data\", error);\n\t\t}\n\t}\n\n\tprivate async request<T>(\n\t\tpath: string,\n\t\toptions: RequestInit = {}\n\t): Promise<T> {\n\t\tif (this.visitorBlocked) {\n\t\t\tconst method = (options.method ?? \"GET\").toUpperCase();\n\t\t\tconst [rawPath] = path.split(\"?\");\n\t\t\tconst normalizedPath = rawPath?.endsWith(\"/\")\n\t\t\t\t? rawPath.slice(0, -1)\n\t\t\t\t: rawPath;\n\t\t\tconst isWebsitesRoot = normalizedPath === \"/websites\";\n\t\t\tconst isSafeMethod = method === \"GET\" || method === \"HEAD\";\n\n\t\t\tif (!(isWebsitesRoot && isSafeMethod)) {\n\t\t\t\tthrow new CossistantAPIError({\n\t\t\t\t\tcode: \"VISITOR_BLOCKED\",\n\t\t\t\t\tmessage: \"Visitor is blocked and cannot perform this action.\",\n\t\t\t\t\tdetails: { path, method },\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tconst url = `${this.config.apiUrl}${path}`;\n\n\t\tconst response = await fetch(url, {\n\t\t\t...options,\n\t\t\theaders: {\n\t\t\t\t...this.baseHeaders,\n\t\t\t\t...options.headers,\n\t\t\t},\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tconst errorData = await response.json().catch(() => ({}));\n\t\t\tconst statusCode = response.status;\n\t\t\tconst errorCode = errorData.code || `HTTP_${statusCode}`;\n\t\t\tconst serverMessage = errorData.message;\n\n\t\t\t// Determine if this is an authentication/authorization error\n\t\t\tconst isAuthError =\n\t\t\t\tstatusCode === 401 ||\n\t\t\t\tstatusCode === 403 ||\n\t\t\t\terrorCode === \"UNAUTHORIZED\" ||\n\t\t\t\terrorCode === \"FORBIDDEN\" ||\n\t\t\t\terrorCode === \"INVALID_API_KEY\" ||\n\t\t\t\terrorCode === \"API_KEY_EXPIRED\" ||\n\t\t\t\terrorCode === \"API_KEY_MISSING\" ||\n\t\t\t\terrorCode?.toUpperCase().includes(\"AUTH\") ||\n\t\t\t\terrorCode?.toUpperCase().includes(\"API_KEY\");\n\n\t\t\t// Use appropriate error message based on error type\n\t\t\tconst errorMessage = isAuthError\n\t\t\t\t? \"Your Cossistant public API key is invalid, expired, missing or not authorized to access this resource.\"\n\t\t\t\t: serverMessage || `Request failed with status ${statusCode}`;\n\n\t\t\t// Log with appropriate level based on error type\n\t\t\tif (isAuthError) {\n\t\t\t\tlogger.error(errorMessage, {\n\t\t\t\t\tdetails: errorData.details,\n\t\t\t\t\tpath,\n\t\t\t\t\tstatus: statusCode,\n\t\t\t\t\tcode: errorCode,\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tlogger.error(\"API request failed\", {\n\t\t\t\t\tmessage: errorMessage,\n\t\t\t\t\tdetails: errorData.details,\n\t\t\t\t\tpath,\n\t\t\t\t\tstatus: statusCode,\n\t\t\t\t\tcode: errorCode,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tthrow new CossistantAPIError({\n\t\t\t\tcode: errorCode,\n\t\t\t\tmessage: errorMessage,\n\t\t\t\tdetails: errorData.details,\n\t\t\t});\n\t\t}\n\n\t\treturn response.json();\n\t}\n\n\tasync getWebsite(): Promise<PublicWebsiteResponse> {\n\t\t// Make the request with visitor ID if we have one stored\n\t\tconst headers: Record<string, string> = {};\n\n\t\t// First, check if we already know the website ID and have a visitor ID for it\n\t\tif (this.websiteId) {\n\t\t\tconst storedVisitorId = getVisitorId(this.websiteId);\n\t\t\tif (storedVisitorId) {\n\t\t\t\theaders[\"X-Visitor-Id\"] = storedVisitorId;\n\t\t\t}\n\t\t} else {\n\t\t\t// We don't know the website ID yet, but check if we have any existing visitor\n\t\t\t// This prevents creating duplicate visitors on page refresh\n\t\t\tconst existingVisitor = getExistingVisitorId(this.publicKey);\n\t\t\tif (existingVisitor) {\n\t\t\t\theaders[\"X-Visitor-Id\"] = existingVisitor.visitorId;\n\t\t\t\t// Pre-populate our local state\n\t\t\t\tthis.websiteId = existingVisitor.websiteId;\n\t\t\t\tthis.visitorId = existingVisitor.visitorId;\n\t\t\t}\n\t\t}\n\n\t\tconst response = await this.request<PublicWebsiteResponse>(\"/websites\", {\n\t\t\theaders,\n\t\t});\n\n\t\t// Store the website ID for future requests\n\t\tthis.websiteId = response.id;\n\n\t\t// Store the visitor ID if we got one\n\t\tthis.visitorBlocked = response.visitor?.isBlocked ?? false;\n\n\t\tif (response.visitor?.id) {\n\t\t\tif (this.visitorBlocked) {\n\t\t\t\tthis.visitorId = response.visitor.id;\n\t\t\t\tsetVisitorId(response.id, response.visitor.id);\n\t\t\t\treturn response;\n\t\t\t}\n\n\t\t\tthis.visitorId = response.visitor.id;\n\t\t\tsetVisitorId(response.id, response.visitor.id);\n\t\t\tthis.syncVisitorSnapshot(response.visitor.id);\n\t\t}\n\n\t\treturn response;\n\t}\n\n\t// Manually prime website and visitor context when the caller already has it\n\tsetWebsiteContext(websiteId: string, visitorId?: string): void {\n\t\tthis.websiteId = websiteId;\n\t\tif (visitorId) {\n\t\t\tthis.visitorId = visitorId;\n\t\t\tsetVisitorId(websiteId, visitorId);\n\t\t}\n\t}\n\n\tsetVisitorBlocked(isBlocked: boolean): void {\n\t\tthis.visitorBlocked = isBlocked;\n\t}\n\n\tgetCurrentWebsiteId(): string | null {\n\t\treturn this.websiteId;\n\t}\n\n\tgetCurrentVisitorId(): string | null {\n\t\tif (this.visitorId) {\n\t\t\treturn this.visitorId;\n\t\t}\n\n\t\tif (!this.websiteId) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn getVisitorId(this.websiteId) ?? null;\n\t}\n\n\tasync updateVisitorMetadata(\n\t\tmetadata: VisitorMetadata\n\t): Promise<VisitorResponse> {\n\t\tconst visitorId = this.resolveVisitorId();\n\t\tconst response = await this.request<VisitorResponse>(\n\t\t\t`/visitors/${visitorId}/metadata`,\n\t\t\t{\n\t\t\t\tmethod: \"PATCH\",\n\t\t\t\tbody: JSON.stringify({ metadata }),\n\t\t\t\theaders: {\n\t\t\t\t\t\"X-Visitor-Id\": visitorId,\n\t\t\t\t},\n\t\t\t}\n\t\t);\n\n\t\treturn this.normalizeVisitorResponse(response);\n\t}\n\n\t/**\n\t * Identify a visitor by creating or updating their contact information\n\t * This will link the visitor to a contact record that can be tracked across devices\n\t */\n\tasync identify(params: {\n\t\texternalId?: string;\n\t\temail?: string;\n\t\tname?: string;\n\t\timage?: string;\n\t\tmetadata?: Record<string, unknown>;\n\t\tcontactOrganizationId?: string;\n\t}): Promise<IdentifyContactResponse> {\n\t\tconst visitorId = this.resolveVisitorId();\n\n\t\tconst response = await this.request<IdentifyContactResponse>(\n\t\t\t\"/contacts/identify\",\n\t\t\t{\n\t\t\t\tmethod: \"POST\",\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tvisitorId,\n\t\t\t\t\t...params,\n\t\t\t\t}),\n\t\t\t\theaders: {\n\t\t\t\t\t\"X-Visitor-Id\": visitorId,\n\t\t\t\t},\n\t\t\t}\n\t\t);\n\n\t\treturn {\n\t\t\tcontact: {\n\t\t\t\t...response.contact,\n\t\t\t\t// Ensure metadata is properly typed\n\t\t\t\tmetadata:\n\t\t\t\t\ttypeof response.contact.metadata === \"string\"\n\t\t\t\t\t\t? JSON.parse(response.contact.metadata)\n\t\t\t\t\t\t: response.contact.metadata,\n\t\t\t\tcreatedAt: response.contact.createdAt,\n\t\t\t\tupdatedAt: response.contact.updatedAt,\n\t\t\t},\n\t\t\tvisitorId: response.visitorId,\n\t\t};\n\t}\n\n\t/**\n\t * Update metadata for the contact associated with the current visitor\n\t * Note: The visitor must be identified first via the identify() method\n\t */\n\tasync updateContactMetadata(\n\t\tmetadata: Record<string, unknown>\n\t): Promise<VisitorResponse> {\n\t\t// This still uses the visitor metadata endpoint for backward compatibility\n\t\t// The endpoint will internally update the contact metadata\n\t\treturn this.updateVisitorMetadata(metadata as VisitorMetadata);\n\t}\n\n\tasync createConversation(\n\t\tparams: Partial<CreateConversationRequestBody> = {}\n\t): Promise<CreateConversationResponseBody> {\n\t\tconst conversationId = params.conversationId || generateConversationId();\n\n\t\t// Get visitor ID from storage if we have the website ID, or use the provided one\n\t\tconst storedVisitorId = this.websiteId\n\t\t\t? getVisitorId(this.websiteId)\n\t\t\t: undefined;\n\t\tconst visitorId = params.visitorId || storedVisitorId;\n\n\t\tif (!visitorId) {\n\t\t\tthrow new Error(\"Visitor ID is required\");\n\t\t}\n\n\t\tconst body: CreateConversationRequestBody = {\n\t\t\tconversationId,\n\t\t\tvisitorId,\n\t\t\tdefaultTimelineItems: params.defaultTimelineItems || [],\n\t\t\tchannel: params.channel || \"widget\",\n\t\t};\n\n\t\t// Add visitor ID header if available\n\t\tconst headers: Record<string, string> = {};\n\t\tif (visitorId) {\n\t\t\theaders[\"X-Visitor-Id\"] = visitorId;\n\t\t}\n\n\t\tconst response = await this.request<CreateConversationResponseBody>(\n\t\t\t\"/conversations\",\n\t\t\t{\n\t\t\t\tmethod: \"POST\",\n\t\t\t\tbody: JSON.stringify(body),\n\t\t\t\theaders,\n\t\t\t}\n\t\t);\n\n\t\t// Convert date strings to Date objects\n\t\treturn {\n\t\t\tconversation: {\n\t\t\t\t...response.conversation,\n\t\t\t\tcreatedAt: response.conversation.createdAt,\n\t\t\t\tupdatedAt: response.conversation.updatedAt,\n\t\t\t\tdeletedAt: response.conversation.deletedAt ?? null,\n\t\t\t\tlastTimelineItem: response.conversation.lastTimelineItem,\n\t\t\t},\n\t\t\tinitialTimelineItems: response.initialTimelineItems,\n\t\t};\n\t}\n\n\tasync updateConfiguration(config: Partial<CossistantConfig>): Promise<void> {\n\t\tif (config.publicKey) {\n\t\t\tthis.publicKey = config.publicKey;\n\t\t\tthis.baseHeaders[\"X-Public-Key\"] = config.publicKey;\n\t\t}\n\n\t\tif (config.userId) {\n\t\t\tthis.baseHeaders[\"X-User-ID\"] = config.userId;\n\t\t} else if (config.userId === null) {\n\t\t\tconst { \"X-User-ID\": _, ...rest } = this.baseHeaders;\n\t\t\tthis.baseHeaders = rest;\n\t\t}\n\n\t\tif (config.organizationId) {\n\t\t\tthis.baseHeaders[\"X-Organization-ID\"] = config.organizationId;\n\t\t} else if (config.organizationId === null) {\n\t\t\tconst { \"X-Organization-ID\": _, ...rest } = this.baseHeaders;\n\t\t\tthis.baseHeaders = rest;\n\t\t}\n\n\t\tthis.config = { ...this.config, ...config };\n\t}\n\n\tasync listConversations(\n\t\tparams: Partial<ListConversationsRequest> = {}\n\t): Promise<ListConversationsResponse> {\n\t\t// Get visitor ID from storage if we have the website ID, or use the provided one\n\t\tconst storedVisitorId = this.websiteId\n\t\t\t? getVisitorId(this.websiteId)\n\t\t\t: undefined;\n\t\tconst visitorId = params.visitorId || storedVisitorId;\n\n\t\tif (!visitorId) {\n\t\t\tthrow new Error(\"Visitor ID is required\");\n\t\t}\n\n\t\t// Create query parameters\n\t\tconst queryParams = new URLSearchParams();\n\n\t\tif (visitorId) {\n\t\t\tqueryParams.set(\"visitorId\", visitorId);\n\t\t}\n\n\t\tif (params.page) {\n\t\t\tqueryParams.set(\"page\", params.page.toString());\n\t\t}\n\n\t\tif (params.limit) {\n\t\t\tqueryParams.set(\"limit\", params.limit.toString());\n\t\t}\n\n\t\tif (params.status) {\n\t\t\tqueryParams.set(\"status\", params.status);\n\t\t}\n\n\t\tif (params.orderBy) {\n\t\t\tqueryParams.set(\"orderBy\", params.orderBy);\n\t\t}\n\n\t\tif (params.order) {\n\t\t\tqueryParams.set(\"order\", params.order);\n\t\t}\n\n\t\t// Add visitor ID header if available\n\t\tconst headers: Record<string, string> = {};\n\t\tif (visitorId) {\n\t\t\theaders[\"X-Visitor-Id\"] = visitorId;\n\t\t}\n\n\t\tconst response = await this.request<ListConversationsResponse>(\n\t\t\t`/conversations?${queryParams.toString()}`,\n\t\t\t{\n\t\t\t\theaders,\n\t\t\t}\n\t\t);\n\n\t\t// Convert date strings to Date objects\n\t\treturn {\n\t\t\tconversations: response.conversations.map((conv) => ({\n\t\t\t\t...conv,\n\t\t\t\tcreatedAt: conv.createdAt,\n\t\t\t\tupdatedAt: conv.updatedAt,\n\t\t\t\tdeletedAt: conv.deletedAt ?? null,\n\t\t\t\tlastTimelineItem: conv.lastTimelineItem,\n\t\t\t})),\n\t\t\tpagination: response.pagination,\n\t\t};\n\t}\n\n\tasync getConversation(\n\t\tparams: GetConversationRequest\n\t): Promise<GetConversationResponse> {\n\t\t// Get visitor ID from storage if we have the website ID\n\t\tconst visitorId = this.websiteId ? getVisitorId(this.websiteId) : undefined;\n\n\t\t// Add visitor ID header if available\n\t\tconst headers: Record<string, string> = {};\n\t\tif (visitorId) {\n\t\t\theaders[\"X-Visitor-Id\"] = visitorId;\n\t\t}\n\n\t\tconst response = await this.request<GetConversationResponse>(\n\t\t\t`/conversations/${params.conversationId}`,\n\t\t\t{\n\t\t\t\theaders,\n\t\t\t}\n\t\t);\n\n\t\t// Convert date strings to Date objects\n\t\treturn {\n\t\t\tconversation: {\n\t\t\t\t...response.conversation,\n\t\t\t\tcreatedAt: response.conversation.createdAt,\n\t\t\t\tupdatedAt: response.conversation.updatedAt,\n\t\t\t\tdeletedAt: response.conversation.deletedAt ?? null,\n\t\t\t\tlastTimelineItem: response.conversation.lastTimelineItem,\n\t\t\t},\n\t\t};\n\t}\n\n\tasync markConversationSeen(\n\t\tparams: {\n\t\t\tconversationId: string;\n\t\t} & Partial<MarkConversationSeenRequestBody>\n\t): Promise<MarkConversationSeenResponseBody> {\n\t\tconst storedVisitorId = this.websiteId\n\t\t\t? getVisitorId(this.websiteId)\n\t\t\t: undefined;\n\t\tconst visitorId = params.visitorId || storedVisitorId;\n\n\t\tif (!visitorId) {\n\t\t\tthrow new Error(\"Visitor ID is required to mark a conversation as seen\");\n\t\t}\n\n\t\tconst headers: Record<string, string> = {};\n\t\tif (visitorId) {\n\t\t\theaders[\"X-Visitor-Id\"] = visitorId;\n\t\t}\n\n\t\tconst body: MarkConversationSeenRequestBody = {};\n\t\tif (params.visitorId) {\n\t\t\tbody.visitorId = params.visitorId;\n\t\t}\n\n\t\tconst response = await this.request<MarkConversationSeenResponseBody>(\n\t\t\t`/conversations/${params.conversationId}/seen`,\n\t\t\t{\n\t\t\t\tmethod: \"POST\",\n\t\t\t\tbody: JSON.stringify(body),\n\t\t\t\theaders,\n\t\t\t}\n\t\t);\n\n\t\treturn {\n\t\t\tconversationId: response.conversationId,\n\t\t\tlastSeenAt: response.lastSeenAt,\n\t\t};\n\t}\n\n\tasync getConversationSeenData(params: {\n\t\tconversationId: string;\n\t}): Promise<GetConversationSeenDataResponse> {\n\t\tconst response = await this.request<GetConversationSeenDataResponse>(\n\t\t\t`/conversations/${params.conversationId}/seen`,\n\t\t\t{\n\t\t\t\tmethod: \"GET\",\n\t\t\t}\n\t\t);\n\n\t\treturn {\n\t\t\tseenData: response.seenData.map((item) => ({\n\t\t\t\t...item,\n\t\t\t\tlastSeenAt: item.lastSeenAt,\n\t\t\t\tcreatedAt: item.createdAt,\n\t\t\t\tupdatedAt: item.updatedAt,\n\t\t\t\tdeletedAt: item.deletedAt ? item.deletedAt : null,\n\t\t\t})),\n\t\t};\n\t}\n\n\tasync setConversationTyping(params: {\n\t\tconversationId: string;\n\t\tisTyping: boolean;\n\t\tvisitorPreview?: string | null;\n\t\tvisitorId?: string;\n\t}): Promise<SetConversationTypingResponseBody> {\n\t\tconst storedVisitorId = this.websiteId\n\t\t\t? getVisitorId(this.websiteId)\n\t\t\t: undefined;\n\t\tconst visitorId = params.visitorId || storedVisitorId;\n\n\t\tif (!visitorId) {\n\t\t\tthrow new Error(\"Visitor ID is required to report typing state\");\n\t\t}\n\n\t\tconst headers: Record<string, string> = {};\n\t\tif (visitorId) {\n\t\t\theaders[\"X-Visitor-Id\"] = visitorId;\n\t\t}\n\n\t\tconst body: SetConversationTypingRequestBody = {\n\t\t\tisTyping: params.isTyping,\n\t\t};\n\n\t\tif (params.visitorId) {\n\t\t\tbody.visitorId = params.visitorId;\n\t\t}\n\n\t\tif (params.visitorPreview && params.isTyping) {\n\t\t\tbody.visitorPreview = params.visitorPreview.slice(0, 2000);\n\t\t}\n\n\t\tconst response = await this.request<SetConversationTypingResponseBody>(\n\t\t\t`/conversations/${params.conversationId}/typing`,\n\t\t\t{\n\t\t\t\tmethod: \"POST\",\n\t\t\t\tbody: JSON.stringify(body),\n\t\t\t\theaders,\n\t\t\t}\n\t\t);\n\n\t\treturn {\n\t\t\tconversationId: response.conversationId,\n\t\t\tisTyping: response.isTyping,\n\t\t\tvisitorPreview: response.visitorPreview,\n\t\t\tsentAt: response.sentAt,\n\t\t};\n\t}\n\n\tasync sendMessage(\n\t\tparams: SendTimelineItemRequest\n\t): Promise<SendTimelineItemResponse> {\n\t\t// Get visitor ID from storage if we have the website ID\n\t\tconst visitorId = this.websiteId ? getVisitorId(this.websiteId) : undefined;\n\n\t\t// Add visitor ID header if available\n\t\tconst headers: Record<string, string> = {};\n\t\tif (visitorId) {\n\t\t\theaders[\"X-Visitor-Id\"] = visitorId;\n\t\t}\n\n\t\tconst response = await this.request<SendTimelineItemResponse>(\"/messages\", {\n\t\t\tmethod: \"POST\",\n\t\t\tbody: JSON.stringify(params),\n\t\t\theaders,\n\t\t});\n\n\t\treturn {\n\t\t\titem: response.item,\n\t\t};\n\t}\n\n\tasync getConversationTimelineItems(\n\t\tparams: GetConversationTimelineItemsRequest & { conversationId: string }\n\t): Promise<GetConversationTimelineItemsResponse> {\n\t\t// Get visitor ID from storage if we have the website ID\n\t\tconst visitorId = this.websiteId ? getVisitorId(this.websiteId) : undefined;\n\n\t\t// Create query parameters\n\t\tconst queryParams = new URLSearchParams();\n\n\t\tif (params.limit) {\n\t\t\tqueryParams.set(\"limit\", params.limit.toString());\n\t\t}\n\n\t\tif (params.cursor) {\n\t\t\tqueryParams.set(\"cursor\", params.cursor);\n\t\t}\n\n\t\t// Add visitor ID header if available\n\t\tconst headers: Record<string, string> = {};\n\t\tif (visitorId) {\n\t\t\theaders[\"X-Visitor-Id\"] = visitorId;\n\t\t}\n\n\t\tconst response = await this.request<GetConversationTimelineItemsResponse>(\n\t\t\t`/conversations/${params.conversationId}/timeline?${queryParams.toString()}`,\n\t\t\t{\n\t\t\t\theaders,\n\t\t\t}\n\t\t);\n\n\t\treturn {\n\t\t\titems: response.items,\n\t\t\tnextCursor: response.nextCursor,\n\t\t\thasNextPage: response.hasNextPage,\n\t\t};\n\t}\n\n\t/**\n\t * Generate a presigned URL for uploading a file to S3.\n\t * The URL can be used to PUT a file directly to S3.\n\t */\n\tasync generateUploadUrl(\n\t\tparams: Omit<GenerateUploadUrlRequest, \"websiteId\" | \"scope\"> & {\n\t\t\tconversationId: string;\n\t\t}\n\t): Promise<GenerateUploadUrlResponse> {\n\t\tif (!this.websiteId) {\n\t\t\tthrow new Error(\n\t\t\t\t\"Website ID is required. Call getWebsite() first to initialize the client.\"\n\t\t\t);\n\t\t}\n\n\t\tconst visitorId = this.resolveVisitorId();\n\n\t\t// Validate file constraints on client side\n\t\tif (!isAllowedMimeType(params.contentType)) {\n\t\t\tthrow new Error(`File type \"${params.contentType}\" is not allowed`);\n\t\t}\n\n\t\tconst headers: Record<string, string> = {};\n\t\tif (visitorId) {\n\t\t\theaders[\"X-Visitor-Id\"] = visitorId;\n\t\t}\n\n\t\t// Get organization ID from website response (stored during getWebsite)\n\t\t// For now, we'll make an additional call to get website info\n\t\tconst websiteResponse = await this.request<{ organizationId: string }>(\n\t\t\t\"/websites\",\n\t\t\t{ headers }\n\t\t);\n\n\t\tconst body: GenerateUploadUrlRequest = {\n\t\t\tcontentType: params.contentType,\n\t\t\twebsiteId: this.websiteId,\n\t\t\tscope: {\n\t\t\t\ttype: \"conversation\",\n\t\t\t\torganizationId: websiteResponse.organizationId,\n\t\t\t\twebsiteId: this.websiteId,\n\t\t\t\tconversationId: params.conversationId,\n\t\t\t},\n\t\t\tfileName: params.fileName,\n\t\t\tfileExtension: params.fileExtension,\n\t\t\tpath: params.path,\n\t\t\tuseCdn: false, // Files should not go to CDN\n\t\t\texpiresInSeconds: params.expiresInSeconds,\n\t\t};\n\n\t\tconst response = await this.request<GenerateUploadUrlResponse>(\n\t\t\t\"/uploads/sign-url\",\n\t\t\t{\n\t\t\t\tmethod: \"POST\",\n\t\t\t\tbody: JSON.stringify(body),\n\t\t\t\theaders,\n\t\t\t}\n\t\t);\n\n\t\treturn response;\n\t}\n\n\t/**\n\t * Upload a file to S3 using a presigned URL.\n\t * @returns The public URL of the uploaded file\n\t */\n\tasync uploadFile(\n\t\tfile: File,\n\t\tuploadUrl: string,\n\t\tcontentType: string\n\t): Promise<void> {\n\t\t// Validate file before upload\n\t\tconst validationError = validateFile(file);\n\t\tif (validationError) {\n\t\t\tthrow new Error(validationError);\n\t\t}\n\n\t\tconst response = await fetch(uploadUrl, {\n\t\t\tmethod: \"PUT\",\n\t\t\tbody: file,\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": contentType,\n\t\t\t},\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tthrow new Error(\n\t\t\t\t`Failed to upload file: ${response.status} ${response.statusText}`\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Upload multiple files for a conversation message.\n\t * Files are uploaded in parallel and the function returns timeline parts\n\t * that can be included in a message.\n\t */\n\tasync uploadFilesForMessage(\n\t\tfiles: File[],\n\t\tconversationId: string\n\t): Promise<\n\t\tArray<\n\t\t\t| {\n\t\t\t\t\ttype: \"image\";\n\t\t\t\t\turl: string;\n\t\t\t\t\tmediaType: string;\n\t\t\t\t\tfileName?: string;\n\t\t\t\t\tsize?: number;\n\t\t\t }\n\t\t\t| {\n\t\t\t\t\ttype: \"file\";\n\t\t\t\t\turl: string;\n\t\t\t\t\tmediaType: string;\n\t\t\t\t\tfileName?: string;\n\t\t\t\t\tsize?: number;\n\t\t\t }\n\t\t>\n\t> {\n\t\tif (files.length === 0) {\n\t\t\treturn [];\n\t\t}\n\n\t\t// Validate all files first\n\t\tfor (const file of files) {\n\t\t\tconst error = validateFile(file);\n\t\t\tif (error) {\n\t\t\t\tthrow new Error(error);\n\t\t\t}\n\t\t}\n\n\t\t// Upload files in parallel\n\t\tconst uploadPromises = files.map(async (file) => {\n\t\t\t// Generate presigned URL\n\t\t\tconst uploadInfo = await this.generateUploadUrl({\n\t\t\t\tconversationId,\n\t\t\t\tcontentType: file.type,\n\t\t\t\tfileName: file.name,\n\t\t\t});\n\n\t\t\t// Upload file to S3\n\t\t\tawait this.uploadFile(file, uploadInfo.uploadUrl, file.type);\n\n\t\t\t// Return timeline part based on file type\n\t\t\tconst isImage = file.type.startsWith(\"image/\");\n\t\t\treturn {\n\t\t\t\ttype: isImage ? (\"image\" as const) : (\"file\" as const),\n\t\t\t\turl: uploadInfo.publicUrl,\n\t\t\t\tmediaType: file.type,\n\t\t\t\tfileName: file.name,\n\t\t\t\tsize: file.size,\n\t\t\t};\n\t\t});\n\n\t\treturn Promise.all(uploadPromises);\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"rest-client.js","names":["headers: Record<string, string>","body: CreateConversationRequestBody","body: MarkConversationSeenRequestBody","body: SetConversationTypingRequestBody","body: GenerateUploadUrlRequest"],"sources":["../src/rest-client.ts"],"sourcesContent":["import type { IdentifyContactResponse } from \"@cossistant/types/api/contact\";\nimport type {\n\tCreateConversationRequestBody,\n\tCreateConversationResponseBody,\n\tGetConversationRequest,\n\tGetConversationResponse,\n\tGetConversationSeenDataResponse,\n\tListConversationsRequest,\n\tListConversationsResponse,\n\tMarkConversationSeenRequestBody,\n\tMarkConversationSeenResponseBody,\n\tSetConversationTypingRequestBody,\n\tSetConversationTypingResponseBody,\n} from \"@cossistant/types/api/conversation\";\nimport type {\n\tGetConversationTimelineItemsRequest,\n\tGetConversationTimelineItemsResponse,\n\tSendTimelineItemRequest,\n\tSendTimelineItemResponse,\n} from \"@cossistant/types/api/timeline-item\";\nimport type {\n\tGenerateUploadUrlRequest,\n\tGenerateUploadUrlResponse,\n} from \"@cossistant/types/api/upload\";\nimport { logger } from \"./logger\";\nimport {\n\tCossistantAPIError,\n\ttype CossistantConfig,\n\ttype PublicWebsiteResponse,\n\ttype UpdateVisitorRequest,\n\ttype VisitorMetadata,\n\ttype VisitorResponse,\n} from \"./types\";\nimport {\n\tisAllowedMimeType,\n\tMAX_FILE_SIZE,\n\tvalidateFile,\n} from \"./upload-constants\";\nimport { generateConversationId } from \"./utils\";\nimport { collectVisitorData } from \"./visitor-data\";\nimport {\n\tgetExistingVisitorId,\n\tgetVisitorId,\n\tsetVisitorId,\n} from \"./visitor-tracker\";\n\nexport class CossistantRestClient {\n\tprivate config: CossistantConfig;\n\tprivate baseHeaders: Record<string, string>;\n\tprivate publicKey: string;\n\tprivate websiteId: string | null = null;\n\tprivate visitorId: string | null = null;\n\tprivate visitorBlocked = false;\n\n\tconstructor(config: CossistantConfig) {\n\t\tthis.config = config;\n\n\t\t// Get public key from config or environment variables\n\t\t// Next.js: NEXT_PUBLIC_COSSISTANT_API_KEY\n\t\t// React/other: COSSISTANT_API_KEY\n\t\tthis.publicKey =\n\t\t\tconfig.publicKey ||\n\t\t\t(typeof process !== \"undefined\"\n\t\t\t\t? process.env.NEXT_PUBLIC_COSSISTANT_API_KEY\n\t\t\t\t: undefined) ||\n\t\t\t(typeof process !== \"undefined\"\n\t\t\t\t? process.env.COSSISTANT_API_KEY\n\t\t\t\t: undefined) ||\n\t\t\t\"\";\n\n\t\tif (!this.publicKey) {\n\t\t\tthrow new Error(\n\t\t\t\t\"Public key is required. Please provide it in the config or set NEXT_PUBLIC_COSSISTANT_API_KEY (Next.js) or COSSISTANT_API_KEY (React) environment variable.\"\n\t\t\t);\n\t\t}\n\n\t\tthis.baseHeaders = {\n\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\"X-Public-Key\": this.publicKey,\n\t\t};\n\n\t\tif (config.userId) {\n\t\t\tthis.baseHeaders[\"X-User-ID\"] = config.userId;\n\t\t}\n\n\t\tif (config.organizationId) {\n\t\t\tthis.baseHeaders[\"X-Organization-ID\"] = config.organizationId;\n\t\t}\n\t}\n\n\tprivate normalizeVisitorResponse(payload: VisitorResponse): VisitorResponse {\n\t\tconst contact = payload.contact ? payload.contact : null;\n\t\treturn {\n\t\t\t...payload,\n\t\t\t// Ensure latitude and longitude are numbers or null\n\t\t\tlatitude:\n\t\t\t\ttypeof payload.latitude === \"string\"\n\t\t\t\t\t? Number.parseFloat(payload.latitude)\n\t\t\t\t\t: payload.latitude,\n\t\t\tlongitude:\n\t\t\t\ttypeof payload.longitude === \"string\"\n\t\t\t\t\t? Number.parseFloat(payload.longitude)\n\t\t\t\t\t: payload.longitude,\n\t\t\tcreatedAt: payload.createdAt,\n\t\t\tupdatedAt: payload.updatedAt,\n\t\t\tlastSeenAt: payload.lastSeenAt ? payload.lastSeenAt : null,\n\t\t\tblockedAt: payload.blockedAt ? payload.blockedAt : null,\n\t\t\tcontact: payload.contact ? payload.contact : null,\n\t\t};\n\t}\n\n\tprivate resolveVisitorId(): string {\n\t\tif (this.visitorId) {\n\t\t\treturn this.visitorId;\n\t\t}\n\n\t\tif (this.websiteId) {\n\t\t\tconst storedVisitorId = getVisitorId(this.websiteId);\n\t\t\tif (storedVisitorId) {\n\t\t\t\tthis.visitorId = storedVisitorId;\n\t\t\t\treturn storedVisitorId;\n\t\t\t}\n\t\t}\n\n\t\tthrow new Error(\"Visitor ID is required\");\n\t}\n\n\tprivate async syncVisitorSnapshot(visitorId: string): Promise<void> {\n\t\ttry {\n\t\t\tconst visitorData = await collectVisitorData();\n\t\t\tif (!visitorData) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst payload = Object.entries(visitorData).reduce<\n\t\t\t\tPartial<UpdateVisitorRequest>\n\t\t\t>((acc, [key, value]) => {\n\t\t\t\tif (value === null || value === undefined) {\n\t\t\t\t\treturn acc;\n\t\t\t\t}\n\t\t\t\t(acc as Record<string, unknown>)[key] = value;\n\t\t\t\treturn acc;\n\t\t\t}, {});\n\n\t\t\tif (Object.keys(payload).length === 0) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tawait this.request<VisitorResponse>(`/visitors/${visitorId}`, {\n\t\t\t\tmethod: \"PATCH\",\n\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\theaders: {\n\t\t\t\t\t\"X-Visitor-Id\": visitorId,\n\t\t\t\t},\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tlogger.warn(\"Failed to sync visitor data\", error);\n\t\t}\n\t}\n\n\tprivate async request<T>(\n\t\tpath: string,\n\t\toptions: RequestInit = {}\n\t): Promise<T> {\n\t\tif (this.visitorBlocked) {\n\t\t\tconst method = (options.method ?? \"GET\").toUpperCase();\n\t\t\tconst [rawPath] = path.split(\"?\");\n\t\t\tconst normalizedPath = rawPath?.endsWith(\"/\")\n\t\t\t\t? rawPath.slice(0, -1)\n\t\t\t\t: rawPath;\n\t\t\tconst isWebsitesRoot = normalizedPath === \"/websites\";\n\t\t\tconst isSafeMethod = method === \"GET\" || method === \"HEAD\";\n\n\t\t\tif (!(isWebsitesRoot && isSafeMethod)) {\n\t\t\t\tthrow new CossistantAPIError({\n\t\t\t\t\tcode: \"VISITOR_BLOCKED\",\n\t\t\t\t\tmessage: \"Visitor is blocked and cannot perform this action.\",\n\t\t\t\t\tdetails: { path, method },\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tconst url = `${this.config.apiUrl}${path}`;\n\n\t\tconst response = await fetch(url, {\n\t\t\t...options,\n\t\t\theaders: {\n\t\t\t\t...this.baseHeaders,\n\t\t\t\t...options.headers,\n\t\t\t},\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tconst errorData = await response.json().catch(() => ({}));\n\t\t\tconst statusCode = response.status;\n\t\t\tconst errorCode = errorData.code || `HTTP_${statusCode}`;\n\t\t\tconst serverMessage = errorData.message;\n\n\t\t\t// Determine if this is an authentication/authorization error\n\t\t\tconst isAuthError =\n\t\t\t\tstatusCode === 401 ||\n\t\t\t\tstatusCode === 403 ||\n\t\t\t\terrorCode === \"UNAUTHORIZED\" ||\n\t\t\t\terrorCode === \"FORBIDDEN\" ||\n\t\t\t\terrorCode === \"INVALID_API_KEY\" ||\n\t\t\t\terrorCode === \"API_KEY_EXPIRED\" ||\n\t\t\t\terrorCode === \"API_KEY_MISSING\" ||\n\t\t\t\terrorCode?.toUpperCase().includes(\"AUTH\") ||\n\t\t\t\terrorCode?.toUpperCase().includes(\"API_KEY\");\n\n\t\t\t// Use appropriate error message based on error type\n\t\t\tconst errorMessage = isAuthError\n\t\t\t\t? \"Your Cossistant public API key is invalid, expired, missing or not authorized to access this resource.\"\n\t\t\t\t: serverMessage || `Request failed with status ${statusCode}`;\n\n\t\t\t// Log with appropriate level based on error type\n\t\t\tif (isAuthError) {\n\t\t\t\tlogger.error(errorMessage, {\n\t\t\t\t\tdetails: errorData.details,\n\t\t\t\t\tpath,\n\t\t\t\t\tstatus: statusCode,\n\t\t\t\t\tcode: errorCode,\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tlogger.error(\"API request failed\", {\n\t\t\t\t\tmessage: errorMessage,\n\t\t\t\t\tdetails: errorData.details,\n\t\t\t\t\tpath,\n\t\t\t\t\tstatus: statusCode,\n\t\t\t\t\tcode: errorCode,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tthrow new CossistantAPIError({\n\t\t\t\tcode: errorCode,\n\t\t\t\tmessage: errorMessage,\n\t\t\t\tdetails: errorData.details,\n\t\t\t});\n\t\t}\n\n\t\treturn response.json();\n\t}\n\n\tasync getWebsite(): Promise<PublicWebsiteResponse> {\n\t\t// Make the request with visitor ID if we have one stored\n\t\tconst headers: Record<string, string> = {};\n\n\t\t// First, check if we already know the website ID and have a visitor ID for it\n\t\tif (this.websiteId) {\n\t\t\tconst storedVisitorId = getVisitorId(this.websiteId);\n\t\t\tif (storedVisitorId) {\n\t\t\t\theaders[\"X-Visitor-Id\"] = storedVisitorId;\n\t\t\t}\n\t\t} else {\n\t\t\t// We don't know the website ID yet, but check if we have any existing visitor\n\t\t\t// This prevents creating duplicate visitors on page refresh\n\t\t\tconst existingVisitor = getExistingVisitorId(this.publicKey);\n\t\t\tif (existingVisitor) {\n\t\t\t\theaders[\"X-Visitor-Id\"] = existingVisitor.visitorId;\n\t\t\t\t// Pre-populate our local state\n\t\t\t\tthis.websiteId = existingVisitor.websiteId;\n\t\t\t\tthis.visitorId = existingVisitor.visitorId;\n\t\t\t}\n\t\t}\n\n\t\tconst response = await this.request<PublicWebsiteResponse>(\"/websites\", {\n\t\t\theaders,\n\t\t});\n\n\t\t// Store the website ID for future requests\n\t\tthis.websiteId = response.id;\n\n\t\t// Store the visitor ID if we got one\n\t\tthis.visitorBlocked = response.visitor?.isBlocked ?? false;\n\n\t\tif (response.visitor?.id) {\n\t\t\tif (this.visitorBlocked) {\n\t\t\t\tthis.visitorId = response.visitor.id;\n\t\t\t\tsetVisitorId(response.id, response.visitor.id);\n\t\t\t\treturn response;\n\t\t\t}\n\n\t\t\tthis.visitorId = response.visitor.id;\n\t\t\tsetVisitorId(response.id, response.visitor.id);\n\t\t\tthis.syncVisitorSnapshot(response.visitor.id);\n\t\t}\n\n\t\treturn response;\n\t}\n\n\t// Manually prime website and visitor context when the caller already has it\n\tsetWebsiteContext(websiteId: string, visitorId?: string): void {\n\t\tthis.websiteId = websiteId;\n\t\tif (visitorId) {\n\t\t\tthis.visitorId = visitorId;\n\t\t\tsetVisitorId(websiteId, visitorId);\n\t\t}\n\t}\n\n\tsetVisitorBlocked(isBlocked: boolean): void {\n\t\tthis.visitorBlocked = isBlocked;\n\t}\n\n\tgetCurrentWebsiteId(): string | null {\n\t\treturn this.websiteId;\n\t}\n\n\tgetCurrentVisitorId(): string | null {\n\t\tif (this.visitorId) {\n\t\t\treturn this.visitorId;\n\t\t}\n\n\t\tif (!this.websiteId) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn getVisitorId(this.websiteId) ?? null;\n\t}\n\n\tasync updateVisitorMetadata(\n\t\tmetadata: VisitorMetadata\n\t): Promise<VisitorResponse> {\n\t\tconst visitorId = this.resolveVisitorId();\n\t\tconst response = await this.request<VisitorResponse>(\n\t\t\t`/visitors/${visitorId}/metadata`,\n\t\t\t{\n\t\t\t\tmethod: \"PATCH\",\n\t\t\t\tbody: JSON.stringify({ metadata }),\n\t\t\t\theaders: {\n\t\t\t\t\t\"X-Visitor-Id\": visitorId,\n\t\t\t\t},\n\t\t\t}\n\t\t);\n\n\t\treturn this.normalizeVisitorResponse(response);\n\t}\n\n\t/**\n\t * Identify a visitor by creating or updating their contact information\n\t * This will link the visitor to a contact record that can be tracked across devices\n\t */\n\tasync identify(params: {\n\t\texternalId?: string;\n\t\temail?: string;\n\t\tname?: string;\n\t\timage?: string;\n\t\tmetadata?: Record<string, unknown>;\n\t\tcontactOrganizationId?: string;\n\t}): Promise<IdentifyContactResponse> {\n\t\tconst visitorId = this.resolveVisitorId();\n\n\t\tconst response = await this.request<IdentifyContactResponse>(\n\t\t\t\"/contacts/identify\",\n\t\t\t{\n\t\t\t\tmethod: \"POST\",\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tvisitorId,\n\t\t\t\t\t...params,\n\t\t\t\t}),\n\t\t\t\theaders: {\n\t\t\t\t\t\"X-Visitor-Id\": visitorId,\n\t\t\t\t},\n\t\t\t}\n\t\t);\n\n\t\treturn {\n\t\t\tcontact: {\n\t\t\t\t...response.contact,\n\t\t\t\t// Ensure metadata is properly typed\n\t\t\t\tmetadata:\n\t\t\t\t\ttypeof response.contact.metadata === \"string\"\n\t\t\t\t\t\t? JSON.parse(response.contact.metadata)\n\t\t\t\t\t\t: response.contact.metadata,\n\t\t\t\tcreatedAt: response.contact.createdAt,\n\t\t\t\tupdatedAt: response.contact.updatedAt,\n\t\t\t},\n\t\t\tvisitorId: response.visitorId,\n\t\t};\n\t}\n\n\t/**\n\t * Update metadata for the contact associated with the current visitor\n\t * Note: The visitor must be identified first via the identify() method\n\t */\n\tasync updateContactMetadata(\n\t\tmetadata: Record<string, unknown>\n\t): Promise<VisitorResponse> {\n\t\t// This still uses the visitor metadata endpoint for backward compatibility\n\t\t// The endpoint will internally update the contact metadata\n\t\treturn this.updateVisitorMetadata(metadata as VisitorMetadata);\n\t}\n\n\tasync createConversation(\n\t\tparams: Partial<CreateConversationRequestBody> = {}\n\t): Promise<CreateConversationResponseBody> {\n\t\tconst conversationId = params.conversationId || generateConversationId();\n\n\t\t// Get visitor ID from storage if we have the website ID, or use the provided one\n\t\tconst storedVisitorId = this.websiteId\n\t\t\t? getVisitorId(this.websiteId)\n\t\t\t: undefined;\n\t\tconst visitorId = params.visitorId || storedVisitorId;\n\n\t\tif (!visitorId) {\n\t\t\tthrow new Error(\"Visitor ID is required\");\n\t\t}\n\n\t\tconst body: CreateConversationRequestBody = {\n\t\t\tconversationId,\n\t\t\tvisitorId,\n\t\t\tdefaultTimelineItems: params.defaultTimelineItems || [],\n\t\t\tchannel: params.channel || \"widget\",\n\t\t};\n\n\t\t// Add visitor ID header if available\n\t\tconst headers: Record<string, string> = {};\n\t\tif (visitorId) {\n\t\t\theaders[\"X-Visitor-Id\"] = visitorId;\n\t\t}\n\n\t\tconst response = await this.request<CreateConversationResponseBody>(\n\t\t\t\"/conversations\",\n\t\t\t{\n\t\t\t\tmethod: \"POST\",\n\t\t\t\tbody: JSON.stringify(body),\n\t\t\t\theaders,\n\t\t\t}\n\t\t);\n\n\t\t// Convert date strings to Date objects\n\t\treturn {\n\t\t\tconversation: {\n\t\t\t\t...response.conversation,\n\t\t\t\tcreatedAt: response.conversation.createdAt,\n\t\t\t\tupdatedAt: response.conversation.updatedAt,\n\t\t\t\tdeletedAt: response.conversation.deletedAt ?? null,\n\t\t\t\tlastTimelineItem: response.conversation.lastTimelineItem,\n\t\t\t},\n\t\t\tinitialTimelineItems: response.initialTimelineItems,\n\t\t};\n\t}\n\n\tasync updateConfiguration(config: Partial<CossistantConfig>): Promise<void> {\n\t\tif (config.publicKey) {\n\t\t\tthis.publicKey = config.publicKey;\n\t\t\tthis.baseHeaders[\"X-Public-Key\"] = config.publicKey;\n\t\t}\n\n\t\tif (config.userId) {\n\t\t\tthis.baseHeaders[\"X-User-ID\"] = config.userId;\n\t\t} else if (config.userId === null) {\n\t\t\tconst { \"X-User-ID\": _, ...rest } = this.baseHeaders;\n\t\t\tthis.baseHeaders = rest;\n\t\t}\n\n\t\tif (config.organizationId) {\n\t\t\tthis.baseHeaders[\"X-Organization-ID\"] = config.organizationId;\n\t\t} else if (config.organizationId === null) {\n\t\t\tconst { \"X-Organization-ID\": _, ...rest } = this.baseHeaders;\n\t\t\tthis.baseHeaders = rest;\n\t\t}\n\n\t\tthis.config = { ...this.config, ...config };\n\t}\n\n\tasync listConversations(\n\t\tparams: Partial<ListConversationsRequest> = {}\n\t): Promise<ListConversationsResponse> {\n\t\t// Get visitor ID from storage if we have the website ID, or use the provided one\n\t\tconst storedVisitorId = this.websiteId\n\t\t\t? getVisitorId(this.websiteId)\n\t\t\t: undefined;\n\t\tconst visitorId = params.visitorId || storedVisitorId;\n\n\t\tif (!visitorId) {\n\t\t\tthrow new Error(\"Visitor ID is required\");\n\t\t}\n\n\t\t// Create query parameters\n\t\tconst queryParams = new URLSearchParams();\n\n\t\tif (visitorId) {\n\t\t\tqueryParams.set(\"visitorId\", visitorId);\n\t\t}\n\n\t\tif (params.page) {\n\t\t\tqueryParams.set(\"page\", params.page.toString());\n\t\t}\n\n\t\tif (params.limit) {\n\t\t\tqueryParams.set(\"limit\", params.limit.toString());\n\t\t}\n\n\t\tif (params.status) {\n\t\t\tqueryParams.set(\"status\", params.status);\n\t\t}\n\n\t\tif (params.orderBy) {\n\t\t\tqueryParams.set(\"orderBy\", params.orderBy);\n\t\t}\n\n\t\tif (params.order) {\n\t\t\tqueryParams.set(\"order\", params.order);\n\t\t}\n\n\t\t// Add visitor ID header if available\n\t\tconst headers: Record<string, string> = {};\n\t\tif (visitorId) {\n\t\t\theaders[\"X-Visitor-Id\"] = visitorId;\n\t\t}\n\n\t\tconst response = await this.request<ListConversationsResponse>(\n\t\t\t`/conversations?${queryParams.toString()}`,\n\t\t\t{\n\t\t\t\theaders,\n\t\t\t}\n\t\t);\n\n\t\t// Convert date strings to Date objects\n\t\treturn {\n\t\t\tconversations: response.conversations.map((conv) => ({\n\t\t\t\t...conv,\n\t\t\t\tcreatedAt: conv.createdAt,\n\t\t\t\tupdatedAt: conv.updatedAt,\n\t\t\t\tdeletedAt: conv.deletedAt ?? null,\n\t\t\t\tlastTimelineItem: conv.lastTimelineItem,\n\t\t\t})),\n\t\t\tpagination: response.pagination,\n\t\t};\n\t}\n\n\tasync getConversation(\n\t\tparams: GetConversationRequest\n\t): Promise<GetConversationResponse> {\n\t\t// Get visitor ID from storage if we have the website ID\n\t\tconst visitorId = this.websiteId ? getVisitorId(this.websiteId) : undefined;\n\n\t\t// Add visitor ID header if available\n\t\tconst headers: Record<string, string> = {};\n\t\tif (visitorId) {\n\t\t\theaders[\"X-Visitor-Id\"] = visitorId;\n\t\t}\n\n\t\tconst response = await this.request<GetConversationResponse>(\n\t\t\t`/conversations/${params.conversationId}`,\n\t\t\t{\n\t\t\t\theaders,\n\t\t\t}\n\t\t);\n\n\t\t// Convert date strings to Date objects\n\t\treturn {\n\t\t\tconversation: {\n\t\t\t\t...response.conversation,\n\t\t\t\tcreatedAt: response.conversation.createdAt,\n\t\t\t\tupdatedAt: response.conversation.updatedAt,\n\t\t\t\tdeletedAt: response.conversation.deletedAt ?? null,\n\t\t\t\tlastTimelineItem: response.conversation.lastTimelineItem,\n\t\t\t},\n\t\t};\n\t}\n\n\tasync markConversationSeen(\n\t\tparams: {\n\t\t\tconversationId: string;\n\t\t} & Partial<MarkConversationSeenRequestBody>\n\t): Promise<MarkConversationSeenResponseBody> {\n\t\tconst storedVisitorId = this.websiteId\n\t\t\t? getVisitorId(this.websiteId)\n\t\t\t: undefined;\n\t\tconst visitorId = params.visitorId || storedVisitorId;\n\n\t\tif (!visitorId) {\n\t\t\tthrow new Error(\"Visitor ID is required to mark a conversation as seen\");\n\t\t}\n\n\t\tconst headers: Record<string, string> = {};\n\t\tif (visitorId) {\n\t\t\theaders[\"X-Visitor-Id\"] = visitorId;\n\t\t}\n\n\t\tconst body: MarkConversationSeenRequestBody = {};\n\t\tif (params.visitorId) {\n\t\t\tbody.visitorId = params.visitorId;\n\t\t}\n\n\t\tconst response = await this.request<MarkConversationSeenResponseBody>(\n\t\t\t`/conversations/${params.conversationId}/seen`,\n\t\t\t{\n\t\t\t\tmethod: \"POST\",\n\t\t\t\tbody: JSON.stringify(body),\n\t\t\t\theaders,\n\t\t\t}\n\t\t);\n\n\t\treturn {\n\t\t\tconversationId: response.conversationId,\n\t\t\tlastSeenAt: response.lastSeenAt,\n\t\t};\n\t}\n\n\tasync getConversationSeenData(params: {\n\t\tconversationId: string;\n\t}): Promise<GetConversationSeenDataResponse> {\n\t\tconst response = await this.request<GetConversationSeenDataResponse>(\n\t\t\t`/conversations/${params.conversationId}/seen`,\n\t\t\t{\n\t\t\t\tmethod: \"GET\",\n\t\t\t}\n\t\t);\n\n\t\treturn {\n\t\t\tseenData: response.seenData.map((item) => ({\n\t\t\t\t...item,\n\t\t\t\tlastSeenAt: item.lastSeenAt,\n\t\t\t\tcreatedAt: item.createdAt,\n\t\t\t\tupdatedAt: item.updatedAt,\n\t\t\t\tdeletedAt: item.deletedAt ? item.deletedAt : null,\n\t\t\t})),\n\t\t};\n\t}\n\n\tasync setConversationTyping(params: {\n\t\tconversationId: string;\n\t\tisTyping: boolean;\n\t\tvisitorPreview?: string | null;\n\t\tvisitorId?: string;\n\t}): Promise<SetConversationTypingResponseBody> {\n\t\tconst storedVisitorId = this.websiteId\n\t\t\t? getVisitorId(this.websiteId)\n\t\t\t: undefined;\n\t\tconst visitorId = params.visitorId || storedVisitorId;\n\n\t\tif (!visitorId) {\n\t\t\tthrow new Error(\"Visitor ID is required to report typing state\");\n\t\t}\n\n\t\tconst headers: Record<string, string> = {};\n\t\tif (visitorId) {\n\t\t\theaders[\"X-Visitor-Id\"] = visitorId;\n\t\t}\n\n\t\tconst body: SetConversationTypingRequestBody = {\n\t\t\tisTyping: params.isTyping,\n\t\t};\n\n\t\tif (params.visitorId) {\n\t\t\tbody.visitorId = params.visitorId;\n\t\t}\n\n\t\tif (params.visitorPreview && params.isTyping) {\n\t\t\tbody.visitorPreview = params.visitorPreview.slice(0, 2000);\n\t\t}\n\n\t\tconst response = await this.request<SetConversationTypingResponseBody>(\n\t\t\t`/conversations/${params.conversationId}/typing`,\n\t\t\t{\n\t\t\t\tmethod: \"POST\",\n\t\t\t\tbody: JSON.stringify(body),\n\t\t\t\theaders,\n\t\t\t}\n\t\t);\n\n\t\treturn {\n\t\t\tconversationId: response.conversationId,\n\t\t\tisTyping: response.isTyping,\n\t\t\tvisitorPreview: response.visitorPreview,\n\t\t\tsentAt: response.sentAt,\n\t\t};\n\t}\n\n\tasync sendMessage(\n\t\tparams: SendTimelineItemRequest\n\t): Promise<SendTimelineItemResponse> {\n\t\t// Get visitor ID from storage if we have the website ID\n\t\tconst visitorId = this.websiteId ? getVisitorId(this.websiteId) : undefined;\n\n\t\t// Add visitor ID header if available\n\t\tconst headers: Record<string, string> = {};\n\t\tif (visitorId) {\n\t\t\theaders[\"X-Visitor-Id\"] = visitorId;\n\t\t}\n\n\t\tconst response = await this.request<SendTimelineItemResponse>(\"/messages\", {\n\t\t\tmethod: \"POST\",\n\t\t\tbody: JSON.stringify(params),\n\t\t\theaders,\n\t\t});\n\n\t\treturn {\n\t\t\titem: response.item,\n\t\t};\n\t}\n\n\tasync getConversationTimelineItems(\n\t\tparams: GetConversationTimelineItemsRequest & { conversationId: string }\n\t): Promise<GetConversationTimelineItemsResponse> {\n\t\t// Get visitor ID from storage if we have the website ID\n\t\tconst visitorId = this.websiteId ? getVisitorId(this.websiteId) : undefined;\n\n\t\t// Create query parameters\n\t\tconst queryParams = new URLSearchParams();\n\n\t\tif (params.limit) {\n\t\t\tqueryParams.set(\"limit\", params.limit.toString());\n\t\t}\n\n\t\tif (params.cursor) {\n\t\t\tqueryParams.set(\"cursor\", params.cursor);\n\t\t}\n\n\t\t// Add visitor ID header if available\n\t\tconst headers: Record<string, string> = {};\n\t\tif (visitorId) {\n\t\t\theaders[\"X-Visitor-Id\"] = visitorId;\n\t\t}\n\n\t\tconst response = await this.request<GetConversationTimelineItemsResponse>(\n\t\t\t`/conversations/${params.conversationId}/timeline?${queryParams.toString()}`,\n\t\t\t{\n\t\t\t\theaders,\n\t\t\t}\n\t\t);\n\n\t\treturn {\n\t\t\titems: response.items,\n\t\t\tnextCursor: response.nextCursor,\n\t\t\thasNextPage: response.hasNextPage,\n\t\t};\n\t}\n\n\t/**\n\t * Generate a presigned URL for uploading a file to S3.\n\t * The URL can be used to PUT a file directly to S3.\n\t */\n\tasync generateUploadUrl(\n\t\tparams: Omit<GenerateUploadUrlRequest, \"websiteId\" | \"scope\"> & {\n\t\t\tconversationId: string;\n\t\t}\n\t): Promise<GenerateUploadUrlResponse> {\n\t\tif (!this.websiteId) {\n\t\t\tthrow new Error(\n\t\t\t\t\"Website ID is required. Call getWebsite() first to initialize the client.\"\n\t\t\t);\n\t\t}\n\n\t\tconst visitorId = this.resolveVisitorId();\n\n\t\t// Validate file constraints on client side\n\t\tif (!isAllowedMimeType(params.contentType)) {\n\t\t\tthrow new Error(`File type \"${params.contentType}\" is not allowed`);\n\t\t}\n\n\t\tconst headers: Record<string, string> = {};\n\t\tif (visitorId) {\n\t\t\theaders[\"X-Visitor-Id\"] = visitorId;\n\t\t}\n\n\t\t// Get organization ID from website response (stored during getWebsite)\n\t\t// For now, we'll make an additional call to get website info\n\t\tconst websiteResponse = await this.request<{ organizationId: string }>(\n\t\t\t\"/websites\",\n\t\t\t{ headers }\n\t\t);\n\n\t\tconst body: GenerateUploadUrlRequest = {\n\t\t\tcontentType: params.contentType,\n\t\t\twebsiteId: this.websiteId,\n\t\t\tscope: {\n\t\t\t\ttype: \"conversation\",\n\t\t\t\torganizationId: websiteResponse.organizationId,\n\t\t\t\twebsiteId: this.websiteId,\n\t\t\t\tconversationId: params.conversationId,\n\t\t\t},\n\t\t\tfileName: params.fileName,\n\t\t\tfileExtension: params.fileExtension,\n\t\t\tpath: params.path,\n\t\t\tuseCdn: false, // Files should not go to CDN\n\t\t\texpiresInSeconds: params.expiresInSeconds,\n\t\t};\n\n\t\tconst response = await this.request<GenerateUploadUrlResponse>(\n\t\t\t\"/uploads/sign-url\",\n\t\t\t{\n\t\t\t\tmethod: \"POST\",\n\t\t\t\tbody: JSON.stringify(body),\n\t\t\t\theaders,\n\t\t\t}\n\t\t);\n\n\t\treturn response;\n\t}\n\n\t/**\n\t * Upload a file to S3 using a presigned URL.\n\t * @returns The public URL of the uploaded file\n\t */\n\tasync uploadFile(\n\t\tfile: File,\n\t\tuploadUrl: string,\n\t\tcontentType: string\n\t): Promise<void> {\n\t\t// Validate file before upload\n\t\tconst validationError = validateFile(file);\n\t\tif (validationError) {\n\t\t\tthrow new Error(validationError);\n\t\t}\n\n\t\tconst response = await fetch(uploadUrl, {\n\t\t\tmethod: \"PUT\",\n\t\t\tbody: file,\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": contentType,\n\t\t\t},\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tthrow new Error(\n\t\t\t\t`Failed to upload file: ${response.status} ${response.statusText}`\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Upload multiple files for a conversation message.\n\t * Files are uploaded in parallel and the function returns timeline parts\n\t * that can be included in a message.\n\t */\n\tasync uploadFilesForMessage(\n\t\tfiles: File[],\n\t\tconversationId: string\n\t): Promise<\n\t\tArray<\n\t\t\t| {\n\t\t\t\t\ttype: \"image\";\n\t\t\t\t\turl: string;\n\t\t\t\t\tmediaType: string;\n\t\t\t\t\tfileName?: string;\n\t\t\t\t\tsize?: number;\n\t\t\t }\n\t\t\t| {\n\t\t\t\t\ttype: \"file\";\n\t\t\t\t\turl: string;\n\t\t\t\t\tmediaType: string;\n\t\t\t\t\tfileName?: string;\n\t\t\t\t\tsize?: number;\n\t\t\t }\n\t\t>\n\t> {\n\t\tif (files.length === 0) {\n\t\t\treturn [];\n\t\t}\n\n\t\t// Validate all files first\n\t\tfor (const file of files) {\n\t\t\tconst error = validateFile(file);\n\t\t\tif (error) {\n\t\t\t\tthrow new Error(error);\n\t\t\t}\n\t\t}\n\n\t\t// Upload files in parallel\n\t\tconst uploadPromises = files.map(async (file) => {\n\t\t\t// Generate presigned URL\n\t\t\tconst uploadInfo = await this.generateUploadUrl({\n\t\t\t\tconversationId,\n\t\t\t\tcontentType: file.type,\n\t\t\t\tfileName: file.name,\n\t\t\t});\n\n\t\t\t// Upload file to S3\n\t\t\tawait this.uploadFile(file, uploadInfo.uploadUrl, file.type);\n\n\t\t\t// Return timeline part based on file type\n\t\t\tconst isImage = file.type.startsWith(\"image/\");\n\t\t\treturn {\n\t\t\t\ttype: isImage ? (\"image\" as const) : (\"file\" as const),\n\t\t\t\turl: uploadInfo.publicUrl,\n\t\t\t\tmediaType: file.type,\n\t\t\t\tfileName: file.name,\n\t\t\t\tsize: file.size,\n\t\t\t};\n\t\t});\n\n\t\treturn Promise.all(uploadPromises);\n\t}\n}\n"],"mappings":";;;;;;;;AA8CA,IAAa,uBAAb,MAAkC;CACjC,AAAQ;CACR,AAAQ;CACR,AAAQ;CACR,AAAQ,YAA2B;CACnC,AAAQ,YAA2B;CACnC,AAAQ,iBAAiB;CAEzB,YAAY,QAA0B;AACrC,OAAK,SAAS;AAKd,OAAK,YACJ,OAAO,cACN,OAAO,YAAY,cACjB,QAAQ,IAAI,iCACZ,YACF,OAAO,YAAY,cACjB,QAAQ,IAAI,qBACZ,WACH;AAED,MAAI,CAAC,KAAK,UACT,OAAM,IAAI,MACT,8JACA;AAGF,OAAK,cAAc;GAClB,gBAAgB;GAChB,gBAAgB,KAAK;GACrB;AAED,MAAI,OAAO,OACV,MAAK,YAAY,eAAe,OAAO;AAGxC,MAAI,OAAO,eACV,MAAK,YAAY,uBAAuB,OAAO;;CAIjD,AAAQ,yBAAyB,SAA2C;AAC3D,UAAQ,WAAU,QAAQ;AAC1C,SAAO;GACN,GAAG;GAEH,UACC,OAAO,QAAQ,aAAa,WACzB,OAAO,WAAW,QAAQ,SAAS,GACnC,QAAQ;GACZ,WACC,OAAO,QAAQ,cAAc,WAC1B,OAAO,WAAW,QAAQ,UAAU,GACpC,QAAQ;GACZ,WAAW,QAAQ;GACnB,WAAW,QAAQ;GACnB,YAAY,QAAQ,aAAa,QAAQ,aAAa;GACtD,WAAW,QAAQ,YAAY,QAAQ,YAAY;GACnD,SAAS,QAAQ,UAAU,QAAQ,UAAU;GAC7C;;CAGF,AAAQ,mBAA2B;AAClC,MAAI,KAAK,UACR,QAAO,KAAK;AAGb,MAAI,KAAK,WAAW;GACnB,MAAM,kBAAkB,aAAa,KAAK,UAAU;AACpD,OAAI,iBAAiB;AACpB,SAAK,YAAY;AACjB,WAAO;;;AAIT,QAAM,IAAI,MAAM,yBAAyB;;CAG1C,MAAc,oBAAoB,WAAkC;AACnE,MAAI;GACH,MAAM,cAAc,MAAM,oBAAoB;AAC9C,OAAI,CAAC,YACJ;GAGD,MAAM,UAAU,OAAO,QAAQ,YAAY,CAAC,QAEzC,KAAK,CAAC,KAAK,WAAW;AACxB,QAAI,UAAU,QAAQ,UAAU,OAC/B,QAAO;AAER,IAAC,IAAgC,OAAO;AACxC,WAAO;MACL,EAAE,CAAC;AAEN,OAAI,OAAO,KAAK,QAAQ,CAAC,WAAW,EACnC;AAGD,SAAM,KAAK,QAAyB,aAAa,aAAa;IAC7D,QAAQ;IACR,MAAM,KAAK,UAAU,QAAQ;IAC7B,SAAS,EACR,gBAAgB,WAChB;IACD,CAAC;WACM,OAAO;AACf,UAAO,KAAK,+BAA+B,MAAM;;;CAInD,MAAc,QACb,MACA,UAAuB,EAAE,EACZ;AACb,MAAI,KAAK,gBAAgB;GACxB,MAAM,UAAU,QAAQ,UAAU,OAAO,aAAa;GACtD,MAAM,CAAC,WAAW,KAAK,MAAM,IAAI;AAOjC,OAAI,GANmB,SAAS,SAAS,IAAI,GAC1C,QAAQ,MAAM,GAAG,GAAG,GACpB,aACuC,gBACrB,WAAW,SAAS,WAAW,SAGnD,OAAM,IAAI,mBAAmB;IAC5B,MAAM;IACN,SAAS;IACT,SAAS;KAAE;KAAM;KAAQ;IACzB,CAAC;;EAIJ,MAAM,MAAM,GAAG,KAAK,OAAO,SAAS;EAEpC,MAAM,WAAW,MAAM,MAAM,KAAK;GACjC,GAAG;GACH,SAAS;IACR,GAAG,KAAK;IACR,GAAG,QAAQ;IACX;GACD,CAAC;AAEF,MAAI,CAAC,SAAS,IAAI;GACjB,MAAM,YAAY,MAAM,SAAS,MAAM,CAAC,aAAa,EAAE,EAAE;GACzD,MAAM,aAAa,SAAS;GAC5B,MAAM,YAAY,UAAU,QAAQ,QAAQ;GAC5C,MAAM,gBAAgB,UAAU;GAGhC,MAAM,cACL,eAAe,OACf,eAAe,OACf,cAAc,kBACd,cAAc,eACd,cAAc,qBACd,cAAc,qBACd,cAAc,qBACd,WAAW,aAAa,CAAC,SAAS,OAAO,IACzC,WAAW,aAAa,CAAC,SAAS,UAAU;GAG7C,MAAM,eAAe,cAClB,2GACA,iBAAiB,8BAA8B;AAGlD,OAAI,YACH,QAAO,MAAM,cAAc;IAC1B,SAAS,UAAU;IACnB;IACA,QAAQ;IACR,MAAM;IACN,CAAC;OAEF,QAAO,MAAM,sBAAsB;IAClC,SAAS;IACT,SAAS,UAAU;IACnB;IACA,QAAQ;IACR,MAAM;IACN,CAAC;AAGH,SAAM,IAAI,mBAAmB;IAC5B,MAAM;IACN,SAAS;IACT,SAAS,UAAU;IACnB,CAAC;;AAGH,SAAO,SAAS,MAAM;;CAGvB,MAAM,aAA6C;EAElD,MAAMA,UAAkC,EAAE;AAG1C,MAAI,KAAK,WAAW;GACnB,MAAM,kBAAkB,aAAa,KAAK,UAAU;AACpD,OAAI,gBACH,SAAQ,kBAAkB;SAErB;GAGN,MAAM,kBAAkB,qBAAqB,KAAK,UAAU;AAC5D,OAAI,iBAAiB;AACpB,YAAQ,kBAAkB,gBAAgB;AAE1C,SAAK,YAAY,gBAAgB;AACjC,SAAK,YAAY,gBAAgB;;;EAInC,MAAM,WAAW,MAAM,KAAK,QAA+B,aAAa,EACvE,SACA,CAAC;AAGF,OAAK,YAAY,SAAS;AAG1B,OAAK,iBAAiB,SAAS,SAAS,aAAa;AAErD,MAAI,SAAS,SAAS,IAAI;AACzB,OAAI,KAAK,gBAAgB;AACxB,SAAK,YAAY,SAAS,QAAQ;AAClC,iBAAa,SAAS,IAAI,SAAS,QAAQ,GAAG;AAC9C,WAAO;;AAGR,QAAK,YAAY,SAAS,QAAQ;AAClC,gBAAa,SAAS,IAAI,SAAS,QAAQ,GAAG;AAC9C,QAAK,oBAAoB,SAAS,QAAQ,GAAG;;AAG9C,SAAO;;CAIR,kBAAkB,WAAmB,WAA0B;AAC9D,OAAK,YAAY;AACjB,MAAI,WAAW;AACd,QAAK,YAAY;AACjB,gBAAa,WAAW,UAAU;;;CAIpC,kBAAkB,WAA0B;AAC3C,OAAK,iBAAiB;;CAGvB,sBAAqC;AACpC,SAAO,KAAK;;CAGb,sBAAqC;AACpC,MAAI,KAAK,UACR,QAAO,KAAK;AAGb,MAAI,CAAC,KAAK,UACT,QAAO;AAGR,SAAO,aAAa,KAAK,UAAU,IAAI;;CAGxC,MAAM,sBACL,UAC2B;EAC3B,MAAM,YAAY,KAAK,kBAAkB;EACzC,MAAM,WAAW,MAAM,KAAK,QAC3B,aAAa,UAAU,YACvB;GACC,QAAQ;GACR,MAAM,KAAK,UAAU,EAAE,UAAU,CAAC;GAClC,SAAS,EACR,gBAAgB,WAChB;GACD,CACD;AAED,SAAO,KAAK,yBAAyB,SAAS;;;;;;CAO/C,MAAM,SAAS,QAOsB;EACpC,MAAM,YAAY,KAAK,kBAAkB;EAEzC,MAAM,WAAW,MAAM,KAAK,QAC3B,sBACA;GACC,QAAQ;GACR,MAAM,KAAK,UAAU;IACpB;IACA,GAAG;IACH,CAAC;GACF,SAAS,EACR,gBAAgB,WAChB;GACD,CACD;AAED,SAAO;GACN,SAAS;IACR,GAAG,SAAS;IAEZ,UACC,OAAO,SAAS,QAAQ,aAAa,WAClC,KAAK,MAAM,SAAS,QAAQ,SAAS,GACrC,SAAS,QAAQ;IACrB,WAAW,SAAS,QAAQ;IAC5B,WAAW,SAAS,QAAQ;IAC5B;GACD,WAAW,SAAS;GACpB;;;;;;CAOF,MAAM,sBACL,UAC2B;AAG3B,SAAO,KAAK,sBAAsB,SAA4B;;CAG/D,MAAM,mBACL,SAAiD,EAAE,EACT;EAC1C,MAAM,iBAAiB,OAAO,kBAAkB,wBAAwB;EAGxE,MAAM,kBAAkB,KAAK,YAC1B,aAAa,KAAK,UAAU,GAC5B;EACH,MAAM,YAAY,OAAO,aAAa;AAEtC,MAAI,CAAC,UACJ,OAAM,IAAI,MAAM,yBAAyB;EAG1C,MAAMC,OAAsC;GAC3C;GACA;GACA,sBAAsB,OAAO,wBAAwB,EAAE;GACvD,SAAS,OAAO,WAAW;GAC3B;EAGD,MAAMD,UAAkC,EAAE;AAC1C,MAAI,UACH,SAAQ,kBAAkB;EAG3B,MAAM,WAAW,MAAM,KAAK,QAC3B,kBACA;GACC,QAAQ;GACR,MAAM,KAAK,UAAU,KAAK;GAC1B;GACA,CACD;AAGD,SAAO;GACN,cAAc;IACb,GAAG,SAAS;IACZ,WAAW,SAAS,aAAa;IACjC,WAAW,SAAS,aAAa;IACjC,WAAW,SAAS,aAAa,aAAa;IAC9C,kBAAkB,SAAS,aAAa;IACxC;GACD,sBAAsB,SAAS;GAC/B;;CAGF,MAAM,oBAAoB,QAAkD;AAC3E,MAAI,OAAO,WAAW;AACrB,QAAK,YAAY,OAAO;AACxB,QAAK,YAAY,kBAAkB,OAAO;;AAG3C,MAAI,OAAO,OACV,MAAK,YAAY,eAAe,OAAO;WAC7B,OAAO,WAAW,MAAM;GAClC,MAAM,EAAE,aAAa,GAAG,GAAG,SAAS,KAAK;AACzC,QAAK,cAAc;;AAGpB,MAAI,OAAO,eACV,MAAK,YAAY,uBAAuB,OAAO;WACrC,OAAO,mBAAmB,MAAM;GAC1C,MAAM,EAAE,qBAAqB,GAAG,GAAG,SAAS,KAAK;AACjD,QAAK,cAAc;;AAGpB,OAAK,SAAS;GAAE,GAAG,KAAK;GAAQ,GAAG;GAAQ;;CAG5C,MAAM,kBACL,SAA4C,EAAE,EACT;EAErC,MAAM,kBAAkB,KAAK,YAC1B,aAAa,KAAK,UAAU,GAC5B;EACH,MAAM,YAAY,OAAO,aAAa;AAEtC,MAAI,CAAC,UACJ,OAAM,IAAI,MAAM,yBAAyB;EAI1C,MAAM,cAAc,IAAI,iBAAiB;AAEzC,MAAI,UACH,aAAY,IAAI,aAAa,UAAU;AAGxC,MAAI,OAAO,KACV,aAAY,IAAI,QAAQ,OAAO,KAAK,UAAU,CAAC;AAGhD,MAAI,OAAO,MACV,aAAY,IAAI,SAAS,OAAO,MAAM,UAAU,CAAC;AAGlD,MAAI,OAAO,OACV,aAAY,IAAI,UAAU,OAAO,OAAO;AAGzC,MAAI,OAAO,QACV,aAAY,IAAI,WAAW,OAAO,QAAQ;AAG3C,MAAI,OAAO,MACV,aAAY,IAAI,SAAS,OAAO,MAAM;EAIvC,MAAMA,UAAkC,EAAE;AAC1C,MAAI,UACH,SAAQ,kBAAkB;EAG3B,MAAM,WAAW,MAAM,KAAK,QAC3B,kBAAkB,YAAY,UAAU,IACxC,EACC,SACA,CACD;AAGD,SAAO;GACN,eAAe,SAAS,cAAc,KAAK,UAAU;IACpD,GAAG;IACH,WAAW,KAAK;IAChB,WAAW,KAAK;IAChB,WAAW,KAAK,aAAa;IAC7B,kBAAkB,KAAK;IACvB,EAAE;GACH,YAAY,SAAS;GACrB;;CAGF,MAAM,gBACL,QACmC;EAEnC,MAAM,YAAY,KAAK,YAAY,aAAa,KAAK,UAAU,GAAG;EAGlE,MAAMA,UAAkC,EAAE;AAC1C,MAAI,UACH,SAAQ,kBAAkB;EAG3B,MAAM,WAAW,MAAM,KAAK,QAC3B,kBAAkB,OAAO,kBACzB,EACC,SACA,CACD;AAGD,SAAO,EACN,cAAc;GACb,GAAG,SAAS;GACZ,WAAW,SAAS,aAAa;GACjC,WAAW,SAAS,aAAa;GACjC,WAAW,SAAS,aAAa,aAAa;GAC9C,kBAAkB,SAAS,aAAa;GACxC,EACD;;CAGF,MAAM,qBACL,QAG4C;EAC5C,MAAM,kBAAkB,KAAK,YAC1B,aAAa,KAAK,UAAU,GAC5B;EACH,MAAM,YAAY,OAAO,aAAa;AAEtC,MAAI,CAAC,UACJ,OAAM,IAAI,MAAM,wDAAwD;EAGzE,MAAMA,UAAkC,EAAE;AAC1C,MAAI,UACH,SAAQ,kBAAkB;EAG3B,MAAME,OAAwC,EAAE;AAChD,MAAI,OAAO,UACV,MAAK,YAAY,OAAO;EAGzB,MAAM,WAAW,MAAM,KAAK,QAC3B,kBAAkB,OAAO,eAAe,QACxC;GACC,QAAQ;GACR,MAAM,KAAK,UAAU,KAAK;GAC1B;GACA,CACD;AAED,SAAO;GACN,gBAAgB,SAAS;GACzB,YAAY,SAAS;GACrB;;CAGF,MAAM,wBAAwB,QAEe;AAQ5C,SAAO,EACN,WARgB,MAAM,KAAK,QAC3B,kBAAkB,OAAO,eAAe,QACxC,EACC,QAAQ,OACR,CACD,EAGmB,SAAS,KAAK,UAAU;GAC1C,GAAG;GACH,YAAY,KAAK;GACjB,WAAW,KAAK;GAChB,WAAW,KAAK;GAChB,WAAW,KAAK,YAAY,KAAK,YAAY;GAC7C,EAAE,EACH;;CAGF,MAAM,sBAAsB,QAKmB;EAC9C,MAAM,kBAAkB,KAAK,YAC1B,aAAa,KAAK,UAAU,GAC5B;EACH,MAAM,YAAY,OAAO,aAAa;AAEtC,MAAI,CAAC,UACJ,OAAM,IAAI,MAAM,gDAAgD;EAGjE,MAAMF,UAAkC,EAAE;AAC1C,MAAI,UACH,SAAQ,kBAAkB;EAG3B,MAAMG,OAAyC,EAC9C,UAAU,OAAO,UACjB;AAED,MAAI,OAAO,UACV,MAAK,YAAY,OAAO;AAGzB,MAAI,OAAO,kBAAkB,OAAO,SACnC,MAAK,iBAAiB,OAAO,eAAe,MAAM,GAAG,IAAK;EAG3D,MAAM,WAAW,MAAM,KAAK,QAC3B,kBAAkB,OAAO,eAAe,UACxC;GACC,QAAQ;GACR,MAAM,KAAK,UAAU,KAAK;GAC1B;GACA,CACD;AAED,SAAO;GACN,gBAAgB,SAAS;GACzB,UAAU,SAAS;GACnB,gBAAgB,SAAS;GACzB,QAAQ,SAAS;GACjB;;CAGF,MAAM,YACL,QACoC;EAEpC,MAAM,YAAY,KAAK,YAAY,aAAa,KAAK,UAAU,GAAG;EAGlE,MAAMH,UAAkC,EAAE;AAC1C,MAAI,UACH,SAAQ,kBAAkB;AAS3B,SAAO,EACN,OAPgB,MAAM,KAAK,QAAkC,aAAa;GAC1E,QAAQ;GACR,MAAM,KAAK,UAAU,OAAO;GAC5B;GACA,CAAC,EAGc,MACf;;CAGF,MAAM,6BACL,QACgD;EAEhD,MAAM,YAAY,KAAK,YAAY,aAAa,KAAK,UAAU,GAAG;EAGlE,MAAM,cAAc,IAAI,iBAAiB;AAEzC,MAAI,OAAO,MACV,aAAY,IAAI,SAAS,OAAO,MAAM,UAAU,CAAC;AAGlD,MAAI,OAAO,OACV,aAAY,IAAI,UAAU,OAAO,OAAO;EAIzC,MAAMA,UAAkC,EAAE;AAC1C,MAAI,UACH,SAAQ,kBAAkB;EAG3B,MAAM,WAAW,MAAM,KAAK,QAC3B,kBAAkB,OAAO,eAAe,YAAY,YAAY,UAAU,IAC1E,EACC,SACA,CACD;AAED,SAAO;GACN,OAAO,SAAS;GAChB,YAAY,SAAS;GACrB,aAAa,SAAS;GACtB;;;;;;CAOF,MAAM,kBACL,QAGqC;AACrC,MAAI,CAAC,KAAK,UACT,OAAM,IAAI,MACT,4EACA;EAGF,MAAM,YAAY,KAAK,kBAAkB;AAGzC,MAAI,CAAC,kBAAkB,OAAO,YAAY,CACzC,OAAM,IAAI,MAAM,cAAc,OAAO,YAAY,kBAAkB;EAGpE,MAAMA,UAAkC,EAAE;AAC1C,MAAI,UACH,SAAQ,kBAAkB;EAK3B,MAAM,kBAAkB,MAAM,KAAK,QAClC,aACA,EAAE,SAAS,CACX;EAED,MAAMI,OAAiC;GACtC,aAAa,OAAO;GACpB,WAAW,KAAK;GAChB,OAAO;IACN,MAAM;IACN,gBAAgB,gBAAgB;IAChC,WAAW,KAAK;IAChB,gBAAgB,OAAO;IACvB;GACD,UAAU,OAAO;GACjB,eAAe,OAAO;GACtB,MAAM,OAAO;GACb,QAAQ;GACR,kBAAkB,OAAO;GACzB;AAWD,SATiB,MAAM,KAAK,QAC3B,qBACA;GACC,QAAQ;GACR,MAAM,KAAK,UAAU,KAAK;GAC1B;GACA,CACD;;;;;;CASF,MAAM,WACL,MACA,WACA,aACgB;EAEhB,MAAM,kBAAkB,aAAa,KAAK;AAC1C,MAAI,gBACH,OAAM,IAAI,MAAM,gBAAgB;EAGjC,MAAM,WAAW,MAAM,MAAM,WAAW;GACvC,QAAQ;GACR,MAAM;GACN,SAAS,EACR,gBAAgB,aAChB;GACD,CAAC;AAEF,MAAI,CAAC,SAAS,GACb,OAAM,IAAI,MACT,0BAA0B,SAAS,OAAO,GAAG,SAAS,aACtD;;;;;;;CASH,MAAM,sBACL,OACA,gBAkBC;AACD,MAAI,MAAM,WAAW,EACpB,QAAO,EAAE;AAIV,OAAK,MAAM,QAAQ,OAAO;GACzB,MAAM,QAAQ,aAAa,KAAK;AAChC,OAAI,MACH,OAAM,IAAI,MAAM,MAAM;;EAKxB,MAAM,iBAAiB,MAAM,IAAI,OAAO,SAAS;GAEhD,MAAM,aAAa,MAAM,KAAK,kBAAkB;IAC/C;IACA,aAAa,KAAK;IAClB,UAAU,KAAK;IACf,CAAC;AAGF,SAAM,KAAK,WAAW,MAAM,WAAW,WAAW,KAAK,KAAK;AAI5D,UAAO;IACN,MAFe,KAAK,KAAK,WAAW,SAAS,GAE5B,UAAqB;IACtC,KAAK,WAAW;IAChB,WAAW,KAAK;IAChB,UAAU,KAAK;IACf,MAAM,KAAK;IACX;IACA;AAEF,SAAO,QAAQ,IAAI,eAAe"}
|
package/store/seen-store.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { ConversationSeen } from "../schemas.js";
|
|
1
|
+
import { ConversationSeen } from "../types/src/schemas.js";
|
|
2
2
|
import { Store } from "./create-store.js";
|
|
3
|
-
import { RealtimeEvent } from "../realtime-events.js";
|
|
3
|
+
import { RealtimeEvent } from "../types/src/realtime-events.js";
|
|
4
4
|
|
|
5
5
|
//#region src/store/seen-store.d.ts
|
|
6
6
|
type SeenActorType = "visitor" | "user" | "ai_agent";
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { TimelineItem } from "../timeline-item.js";
|
|
1
|
+
import { TimelineItem as TimelineItem$1 } from "../types/src/api/timeline-item.js";
|
|
2
2
|
import { Store } from "./create-store.js";
|
|
3
3
|
import { RealtimeEvent } from "@cossistant/types";
|
|
4
4
|
|
|
5
5
|
//#region src/store/timeline-items-store.d.ts
|
|
6
6
|
type TimelineItemCreatedEvent = RealtimeEvent<"timelineItemCreated">;
|
|
7
7
|
type ConversationTimelineItemsState = {
|
|
8
|
-
items: TimelineItem[];
|
|
8
|
+
items: TimelineItem$1[];
|
|
9
9
|
hasNextPage: boolean;
|
|
10
10
|
nextCursor?: string;
|
|
11
11
|
};
|
|
@@ -14,10 +14,10 @@ type TimelineItemsState = {
|
|
|
14
14
|
};
|
|
15
15
|
type TimelineItemsStore = Store<TimelineItemsState> & {
|
|
16
16
|
ingestPage(conversationId: string, page: ConversationTimelineItemsState): void;
|
|
17
|
-
ingestTimelineItem(item: TimelineItem): void;
|
|
18
|
-
ingestRealtimeTimelineItem(event: TimelineItemCreatedEvent): TimelineItem;
|
|
17
|
+
ingestTimelineItem(item: TimelineItem$1): void;
|
|
18
|
+
ingestRealtimeTimelineItem(event: TimelineItemCreatedEvent): TimelineItem$1;
|
|
19
19
|
removeTimelineItem(conversationId: string, itemId: string): void;
|
|
20
|
-
finalizeTimelineItem(conversationId: string, optimisticId: string, item: TimelineItem): void;
|
|
20
|
+
finalizeTimelineItem(conversationId: string, optimisticId: string, item: TimelineItem$1): void;
|
|
21
21
|
clearConversation(conversationId: string): void;
|
|
22
22
|
};
|
|
23
23
|
declare function createTimelineItemsStore(initialState?: TimelineItemsState): TimelineItemsStore;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"timeline-items-store.d.ts","names":[],"sources":["../../src/store/timeline-items-store.ts"],"sourcesContent":[],"mappings":";;;;;KAOK,wBAAA,GAA2B;KAEpB,8BAAA;EAFP,KAAA,EAGG,
|
|
1
|
+
{"version":3,"file":"timeline-items-store.d.ts","names":[],"sources":["../../src/store/timeline-items-store.ts"],"sourcesContent":[],"mappings":";;;;;KAOK,wBAAA,GAA2B;KAEpB,8BAAA;EAFP,KAAA,EAGG,cAHH,EAAA;EAEO,WAAA,EAAA,OAAA;EAMA,UAAA,CAAA,EAAA,MAAA;AAiMZ,CAAA;AAAuC,KAjM3B,kBAAA,GAiM2B;EAAN,aAAA,EAhMjB,MAgMiB,CAAA,MAAA,EAhMF,8BAgME,CAAA;CAGzB;AAEkB,KALd,kBAAA,GAAqB,KAKP,CALa,kBAKb,CAAA,GAAA;EACS,UAAA,CAAA,cAAA,EAAA,MAAA,EAAA,IAAA,EAH3B,8BAG2B,CAAA,EAAA,IAAA;EAA2B,kBAAA,CAAA,IAAA,EADpC,cACoC,CAAA,EAAA,IAAA;EAKtD,0BAAA,CAAA,KAAA,EAL2B,wBAK3B,CAAA,EALsD,cAKtD;EAAY,kBAAA,CAAA,cAAA,EAAA,MAAA,EAAA,MAAA,EAAA,MAAA,CAAA,EAAA,IAAA;EAKJ,oBAAA,CAAA,cAAwB,EAAA,MACzB,EAAA,YAAA,EACZ,MAAA,EAAA,IAAA,EAPK,cAOa,CAAA,EAAA,IAAA;EA2CL,iBAAA,CAAA,cAA4B,EAAA,MAAA,CAAA,EAAA,IAAA;CAC9B;AAAN,iBA9CQ,wBAAA,CA8CR,YAAA,CAAA,EA7CO,kBA6CP,CAAA,EA5CL,kBA4CK;AAEL,iBAHa,4BAAA,CAGb,KAAA,EAFK,KAEL,CAFW,kBAEX,CAAA,EAAA,cAAA,EAAA,MAAA,CAAA,EAAA,8BAAA,GAAA,SAAA"}
|
|
@@ -126,7 +126,7 @@ function createTimelineItemsStore(initialState = INITIAL_STATE) {
|
|
|
126
126
|
clearConversation(conversationId) {
|
|
127
127
|
store.setState((state) => {
|
|
128
128
|
if (!state.conversations[conversationId]) return state;
|
|
129
|
-
const { [conversationId]: _removed
|
|
129
|
+
const { [conversationId]: _removed, ...rest } = state.conversations;
|
|
130
130
|
return {
|
|
131
131
|
...state,
|
|
132
132
|
conversations: rest
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"timeline-items-store.js","names":["INITIAL_STATE: TimelineItemsState","nextConversation: ConversationTimelineItemsState"],"sources":["../../src/store/timeline-items-store.ts"],"sourcesContent":["import type { RealtimeEvent } from \"@cossistant/types\";\nimport {\n\ttype TimelineItem,\n\ttimelineItemPartsSchema,\n} from \"@cossistant/types/api/timeline-item\";\nimport { createStore, type Store } from \"./create-store\";\n\ntype TimelineItemCreatedEvent = RealtimeEvent<\"timelineItemCreated\">;\n\nexport type ConversationTimelineItemsState = {\n\titems: TimelineItem[];\n\thasNextPage: boolean;\n\tnextCursor?: string;\n};\n\nexport type TimelineItemsState = {\n\tconversations: Record<string, ConversationTimelineItemsState>;\n};\n\nconst INITIAL_STATE: TimelineItemsState = {\n\tconversations: {},\n};\n\nfunction sortTimelineItems(items: TimelineItem[]): TimelineItem[] {\n\treturn [...items].sort((a, b) => a.createdAt.localeCompare(b.createdAt));\n}\n\nfunction isSameTimelineItem(a: TimelineItem, b: TimelineItem): boolean {\n\treturn (\n\t\ta.id === b.id &&\n\t\ta.text === b.text &&\n\t\tnew Date(a.createdAt).getTime() === new Date(b.createdAt).getTime()\n\t);\n}\n\nfunction mergeTimelineItems(\n\texisting: TimelineItem[],\n\tincoming: TimelineItem[]\n): TimelineItem[] {\n\tif (incoming.length === 0) {\n\t\treturn existing;\n\t}\n\n\tconst byId = new Map<string, TimelineItem>();\n\tfor (const item of existing) {\n\t\tif (item.id) {\n\t\t\tbyId.set(item.id, item);\n\t\t}\n\t}\n\n\tlet changed = false;\n\tfor (const item of incoming) {\n\t\tif (!item.id) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst previous = byId.get(item.id);\n\t\tif (!(previous && isSameTimelineItem(previous, item))) {\n\t\t\tchanged = true;\n\t\t}\n\t\tbyId.set(item.id, item);\n\t}\n\n\tif (!changed && byId.size === existing.length) {\n\t\tlet orderStable = true;\n\t\tfor (const item of existing) {\n\t\t\tif (item.id && byId.get(item.id) !== item) {\n\t\t\t\torderStable = false;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (orderStable) {\n\t\t\treturn existing;\n\t\t}\n\t}\n\n\treturn sortTimelineItems(Array.from(byId.values()));\n}\n\nfunction applyPage(\n\tstate: TimelineItemsState,\n\tconversationId: string,\n\tpage: Pick<\n\t\tConversationTimelineItemsState,\n\t\t\"items\" | \"hasNextPage\" | \"nextCursor\"\n\t>\n): TimelineItemsState {\n\tconst existing = state.conversations[conversationId];\n\tconst mergedItems = mergeTimelineItems(existing?.items ?? [], page.items);\n\n\tif (\n\t\texisting &&\n\t\texisting.items === mergedItems &&\n\t\texisting.hasNextPage === page.hasNextPage &&\n\t\texisting.nextCursor === page.nextCursor\n\t) {\n\t\treturn state;\n\t}\n\n\treturn {\n\t\t...state,\n\t\tconversations: {\n\t\t\t...state.conversations,\n\t\t\t[conversationId]: {\n\t\t\t\titems: mergedItems,\n\t\t\t\thasNextPage: page.hasNextPage,\n\t\t\t\tnextCursor: page.nextCursor,\n\t\t\t},\n\t\t},\n\t};\n}\n\nfunction applyTimelineItem(\n\tstate: TimelineItemsState,\n\titem: TimelineItem\n): TimelineItemsState {\n\tconst existing = state.conversations[item.conversationId];\n\tconst mergedItems = mergeTimelineItems(existing?.items ?? [], [item]);\n\n\tif (existing && existing.items === mergedItems) {\n\t\treturn state;\n\t}\n\n\treturn {\n\t\t...state,\n\t\tconversations: {\n\t\t\t...state.conversations,\n\t\t\t[item.conversationId]: {\n\t\t\t\titems: mergedItems,\n\t\t\t\thasNextPage: existing?.hasNextPage ?? false,\n\t\t\t\tnextCursor: existing?.nextCursor,\n\t\t\t},\n\t\t},\n\t};\n}\n\nfunction removeTimelineItem(\n\tstate: TimelineItemsState,\n\tconversationId: string,\n\titemId: string\n): TimelineItemsState {\n\tconst existing = state.conversations[conversationId];\n\tif (!existing) {\n\t\treturn state;\n\t}\n\n\tconst index = existing.items.findIndex((item) => item.id === itemId);\n\tif (index === -1) {\n\t\treturn state;\n\t}\n\n\tconst nextItems = existing.items\n\t\t.slice(0, index)\n\t\t.concat(existing.items.slice(index + 1));\n\n\tconst nextConversation: ConversationTimelineItemsState = {\n\t\t...existing,\n\t\titems: nextItems,\n\t};\n\n\treturn {\n\t\t...state,\n\t\tconversations: {\n\t\t\t...state.conversations,\n\t\t\t[conversationId]: nextConversation,\n\t\t},\n\t};\n}\n\nfunction finalizeTimelineItem(\n\tstate: TimelineItemsState,\n\tconversationId: string,\n\toptimisticId: string,\n\titem: TimelineItem\n): TimelineItemsState {\n\tconst withoutOptimistic = removeTimelineItem(\n\t\tstate,\n\t\tconversationId,\n\t\toptimisticId\n\t);\n\treturn applyTimelineItem(withoutOptimistic, item);\n}\n\n// Normalize timeline item created event\nfunction normalizeRealtimeTimelineItem(\n\tevent: TimelineItemCreatedEvent\n): TimelineItem {\n\tconst raw = event.payload.item;\n\n\tconst parsedParts = timelineItemPartsSchema.parse(raw.parts);\n\n\treturn {\n\t\tid: raw.id,\n\t\tconversationId: raw.conversationId,\n\t\torganizationId: raw.organizationId,\n\t\tvisibility: raw.visibility,\n\t\ttype: raw.type,\n\t\ttext: raw.text ?? null,\n\t\tparts: parsedParts,\n\t\ttool: raw.tool ?? null,\n\t\tuserId: raw.userId,\n\t\tvisitorId: raw.visitorId,\n\t\taiAgentId: raw.aiAgentId,\n\t\tcreatedAt: raw.createdAt,\n\t\tdeletedAt: raw.deletedAt ?? null,\n\t};\n}\n\nexport type TimelineItemsStore = Store<TimelineItemsState> & {\n\tingestPage(\n\t\tconversationId: string,\n\t\tpage: ConversationTimelineItemsState\n\t): void;\n\tingestTimelineItem(item: TimelineItem): void;\n\tingestRealtimeTimelineItem(event: TimelineItemCreatedEvent): TimelineItem;\n\tremoveTimelineItem(conversationId: string, itemId: string): void;\n\tfinalizeTimelineItem(\n\t\tconversationId: string,\n\t\toptimisticId: string,\n\t\titem: TimelineItem\n\t): void;\n\tclearConversation(conversationId: string): void;\n};\n\nexport function createTimelineItemsStore(\n\tinitialState: TimelineItemsState = INITIAL_STATE\n): TimelineItemsStore {\n\tconst store = createStore<TimelineItemsState>(initialState);\n\n\treturn {\n\t\t...store,\n\t\tingestPage(conversationId, page) {\n\t\t\tstore.setState((state) => applyPage(state, conversationId, page));\n\t\t},\n\t\tingestTimelineItem(item) {\n\t\t\tstore.setState((state) => applyTimelineItem(state, item));\n\t\t},\n\t\tingestRealtimeTimelineItem(event) {\n\t\t\tconst item = normalizeRealtimeTimelineItem(event);\n\t\t\tstore.setState((state) => applyTimelineItem(state, item));\n\t\t\treturn item;\n\t\t},\n\t\tremoveTimelineItem(conversationId, itemId) {\n\t\t\tstore.setState((state) =>\n\t\t\t\tremoveTimelineItem(state, conversationId, itemId)\n\t\t\t);\n\t\t},\n\t\tfinalizeTimelineItem(conversationId, optimisticId, item) {\n\t\t\tstore.setState((state) =>\n\t\t\t\tfinalizeTimelineItem(state, conversationId, optimisticId, item)\n\t\t\t);\n\t\t},\n\t\tclearConversation(conversationId) {\n\t\t\tstore.setState((state) => {\n\t\t\t\tif (!state.conversations[conversationId]) {\n\t\t\t\t\treturn state;\n\t\t\t\t}\n\n\t\t\t\tconst { [conversationId]: _removed, ...rest } = state.conversations;\n\n\t\t\t\treturn {\n\t\t\t\t\t...state,\n\t\t\t\t\tconversations: rest,\n\t\t\t\t} satisfies TimelineItemsState;\n\t\t\t});\n\t\t},\n\t};\n}\n\nexport function getConversationTimelineItems(\n\tstore: Store<TimelineItemsState>,\n\tconversationId: string\n): ConversationTimelineItemsState | undefined {\n\treturn store.getState().conversations[conversationId];\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"timeline-items-store.js","names":["INITIAL_STATE: TimelineItemsState","nextConversation: ConversationTimelineItemsState"],"sources":["../../src/store/timeline-items-store.ts"],"sourcesContent":["import type { RealtimeEvent } from \"@cossistant/types\";\nimport {\n\ttype TimelineItem,\n\ttimelineItemPartsSchema,\n} from \"@cossistant/types/api/timeline-item\";\nimport { createStore, type Store } from \"./create-store\";\n\ntype TimelineItemCreatedEvent = RealtimeEvent<\"timelineItemCreated\">;\n\nexport type ConversationTimelineItemsState = {\n\titems: TimelineItem[];\n\thasNextPage: boolean;\n\tnextCursor?: string;\n};\n\nexport type TimelineItemsState = {\n\tconversations: Record<string, ConversationTimelineItemsState>;\n};\n\nconst INITIAL_STATE: TimelineItemsState = {\n\tconversations: {},\n};\n\nfunction sortTimelineItems(items: TimelineItem[]): TimelineItem[] {\n\treturn [...items].sort((a, b) => a.createdAt.localeCompare(b.createdAt));\n}\n\nfunction isSameTimelineItem(a: TimelineItem, b: TimelineItem): boolean {\n\treturn (\n\t\ta.id === b.id &&\n\t\ta.text === b.text &&\n\t\tnew Date(a.createdAt).getTime() === new Date(b.createdAt).getTime()\n\t);\n}\n\nfunction mergeTimelineItems(\n\texisting: TimelineItem[],\n\tincoming: TimelineItem[]\n): TimelineItem[] {\n\tif (incoming.length === 0) {\n\t\treturn existing;\n\t}\n\n\tconst byId = new Map<string, TimelineItem>();\n\tfor (const item of existing) {\n\t\tif (item.id) {\n\t\t\tbyId.set(item.id, item);\n\t\t}\n\t}\n\n\tlet changed = false;\n\tfor (const item of incoming) {\n\t\tif (!item.id) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst previous = byId.get(item.id);\n\t\tif (!(previous && isSameTimelineItem(previous, item))) {\n\t\t\tchanged = true;\n\t\t}\n\t\tbyId.set(item.id, item);\n\t}\n\n\tif (!changed && byId.size === existing.length) {\n\t\tlet orderStable = true;\n\t\tfor (const item of existing) {\n\t\t\tif (item.id && byId.get(item.id) !== item) {\n\t\t\t\torderStable = false;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (orderStable) {\n\t\t\treturn existing;\n\t\t}\n\t}\n\n\treturn sortTimelineItems(Array.from(byId.values()));\n}\n\nfunction applyPage(\n\tstate: TimelineItemsState,\n\tconversationId: string,\n\tpage: Pick<\n\t\tConversationTimelineItemsState,\n\t\t\"items\" | \"hasNextPage\" | \"nextCursor\"\n\t>\n): TimelineItemsState {\n\tconst existing = state.conversations[conversationId];\n\tconst mergedItems = mergeTimelineItems(existing?.items ?? [], page.items);\n\n\tif (\n\t\texisting &&\n\t\texisting.items === mergedItems &&\n\t\texisting.hasNextPage === page.hasNextPage &&\n\t\texisting.nextCursor === page.nextCursor\n\t) {\n\t\treturn state;\n\t}\n\n\treturn {\n\t\t...state,\n\t\tconversations: {\n\t\t\t...state.conversations,\n\t\t\t[conversationId]: {\n\t\t\t\titems: mergedItems,\n\t\t\t\thasNextPage: page.hasNextPage,\n\t\t\t\tnextCursor: page.nextCursor,\n\t\t\t},\n\t\t},\n\t};\n}\n\nfunction applyTimelineItem(\n\tstate: TimelineItemsState,\n\titem: TimelineItem\n): TimelineItemsState {\n\tconst existing = state.conversations[item.conversationId];\n\tconst mergedItems = mergeTimelineItems(existing?.items ?? [], [item]);\n\n\tif (existing && existing.items === mergedItems) {\n\t\treturn state;\n\t}\n\n\treturn {\n\t\t...state,\n\t\tconversations: {\n\t\t\t...state.conversations,\n\t\t\t[item.conversationId]: {\n\t\t\t\titems: mergedItems,\n\t\t\t\thasNextPage: existing?.hasNextPage ?? false,\n\t\t\t\tnextCursor: existing?.nextCursor,\n\t\t\t},\n\t\t},\n\t};\n}\n\nfunction removeTimelineItem(\n\tstate: TimelineItemsState,\n\tconversationId: string,\n\titemId: string\n): TimelineItemsState {\n\tconst existing = state.conversations[conversationId];\n\tif (!existing) {\n\t\treturn state;\n\t}\n\n\tconst index = existing.items.findIndex((item) => item.id === itemId);\n\tif (index === -1) {\n\t\treturn state;\n\t}\n\n\tconst nextItems = existing.items\n\t\t.slice(0, index)\n\t\t.concat(existing.items.slice(index + 1));\n\n\tconst nextConversation: ConversationTimelineItemsState = {\n\t\t...existing,\n\t\titems: nextItems,\n\t};\n\n\treturn {\n\t\t...state,\n\t\tconversations: {\n\t\t\t...state.conversations,\n\t\t\t[conversationId]: nextConversation,\n\t\t},\n\t};\n}\n\nfunction finalizeTimelineItem(\n\tstate: TimelineItemsState,\n\tconversationId: string,\n\toptimisticId: string,\n\titem: TimelineItem\n): TimelineItemsState {\n\tconst withoutOptimistic = removeTimelineItem(\n\t\tstate,\n\t\tconversationId,\n\t\toptimisticId\n\t);\n\treturn applyTimelineItem(withoutOptimistic, item);\n}\n\n// Normalize timeline item created event\nfunction normalizeRealtimeTimelineItem(\n\tevent: TimelineItemCreatedEvent\n): TimelineItem {\n\tconst raw = event.payload.item;\n\n\tconst parsedParts = timelineItemPartsSchema.parse(raw.parts);\n\n\treturn {\n\t\tid: raw.id,\n\t\tconversationId: raw.conversationId,\n\t\torganizationId: raw.organizationId,\n\t\tvisibility: raw.visibility,\n\t\ttype: raw.type,\n\t\ttext: raw.text ?? null,\n\t\tparts: parsedParts,\n\t\ttool: raw.tool ?? null,\n\t\tuserId: raw.userId,\n\t\tvisitorId: raw.visitorId,\n\t\taiAgentId: raw.aiAgentId,\n\t\tcreatedAt: raw.createdAt,\n\t\tdeletedAt: raw.deletedAt ?? null,\n\t};\n}\n\nexport type TimelineItemsStore = Store<TimelineItemsState> & {\n\tingestPage(\n\t\tconversationId: string,\n\t\tpage: ConversationTimelineItemsState\n\t): void;\n\tingestTimelineItem(item: TimelineItem): void;\n\tingestRealtimeTimelineItem(event: TimelineItemCreatedEvent): TimelineItem;\n\tremoveTimelineItem(conversationId: string, itemId: string): void;\n\tfinalizeTimelineItem(\n\t\tconversationId: string,\n\t\toptimisticId: string,\n\t\titem: TimelineItem\n\t): void;\n\tclearConversation(conversationId: string): void;\n};\n\nexport function createTimelineItemsStore(\n\tinitialState: TimelineItemsState = INITIAL_STATE\n): TimelineItemsStore {\n\tconst store = createStore<TimelineItemsState>(initialState);\n\n\treturn {\n\t\t...store,\n\t\tingestPage(conversationId, page) {\n\t\t\tstore.setState((state) => applyPage(state, conversationId, page));\n\t\t},\n\t\tingestTimelineItem(item) {\n\t\t\tstore.setState((state) => applyTimelineItem(state, item));\n\t\t},\n\t\tingestRealtimeTimelineItem(event) {\n\t\t\tconst item = normalizeRealtimeTimelineItem(event);\n\t\t\tstore.setState((state) => applyTimelineItem(state, item));\n\t\t\treturn item;\n\t\t},\n\t\tremoveTimelineItem(conversationId, itemId) {\n\t\t\tstore.setState((state) =>\n\t\t\t\tremoveTimelineItem(state, conversationId, itemId)\n\t\t\t);\n\t\t},\n\t\tfinalizeTimelineItem(conversationId, optimisticId, item) {\n\t\t\tstore.setState((state) =>\n\t\t\t\tfinalizeTimelineItem(state, conversationId, optimisticId, item)\n\t\t\t);\n\t\t},\n\t\tclearConversation(conversationId) {\n\t\t\tstore.setState((state) => {\n\t\t\t\tif (!state.conversations[conversationId]) {\n\t\t\t\t\treturn state;\n\t\t\t\t}\n\n\t\t\t\tconst { [conversationId]: _removed, ...rest } = state.conversations;\n\n\t\t\t\treturn {\n\t\t\t\t\t...state,\n\t\t\t\t\tconversations: rest,\n\t\t\t\t} satisfies TimelineItemsState;\n\t\t\t});\n\t\t},\n\t};\n}\n\nexport function getConversationTimelineItems(\n\tstore: Store<TimelineItemsState>,\n\tconversationId: string\n): ConversationTimelineItemsState | undefined {\n\treturn store.getState().conversations[conversationId];\n}\n"],"mappings":"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"}
|
package/store/typing-store.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typing-store.d.ts","names":[],"sources":["../../src/store/typing-store.ts"],"sourcesContent":[],"mappings":";;;;KAGY,eAAA;KAEA,WAAA;EAFA,SAAA,EAGA,eAHe;EAEf,OAAA,EAAA,MAAW;EAOX,OAAA,EAAA,MAAA,GAAA,IAAA;EAEA,SAAA,EAAA,MAAW;AAMvB,CAAA;AAOK,KAfO,uBAAA,GAA0B,MAiB1B,CAAA,MAAe,EAjB0B,WAiB1B,CAAA;AAItB,KAnBO,WAAA,GAmBS;EAuCT,aAAA,EAzDI,MAyDO,CAAA,MAAA,EAzDQ,uBAyDR,CAAA;CAAS;AAAN,KApDd,uBAAA,GAoDc;EACN,GAAA,CAAA,EAAA,GAAA,GAAA,MAAA;EACG,UAAA,CAAA,EAAA,CAAA,QAAA,EAAA,GAAA,GAAA,IAAA,EAAA,KAAA,EAAA,MAAA,EAAA,GAAA,OAAA;EAAa,YAAA,CAAA,EAAA,CAAA,EAAA,EAAA,OAAA,EAAA,GAAA,IAAA;EAIpB,YAAA,CAAA,EAAA,MAAiB;CAClB;KApDV,aAAA,GAqDU;EACZ,cAAA,EAAA,MAAA;EAAW,SAAA,EApDF,eAoDE;EA+GE,OAAA,EAAA,MAAA;AAOhB,CAAA;AAOA,KA7KK,gBAAA,GAAmB,aA6KoB,GAAA;EA8D5B,QAAA,EAAA,OAAA;
|
|
1
|
+
{"version":3,"file":"typing-store.d.ts","names":[],"sources":["../../src/store/typing-store.ts"],"sourcesContent":[],"mappings":";;;;KAGY,eAAA;KAEA,WAAA;EAFA,SAAA,EAGA,eAHe;EAEf,OAAA,EAAA,MAAW;EAOX,OAAA,EAAA,MAAA,GAAA,IAAA;EAEA,SAAA,EAAA,MAAW;AAMvB,CAAA;AAOK,KAfO,uBAAA,GAA0B,MAiB1B,CAAA,MAAe,EAjB0B,WAiB1B,CAAA;AAItB,KAnBO,WAAA,GAmBS;EAuCT,aAAA,EAzDI,MAyDO,CAAA,MAAA,EAzDQ,uBAyDR,CAAA;CAAS;AAAN,KApDd,uBAAA,GAoDc;EACN,GAAA,CAAA,EAAA,GAAA,GAAA,MAAA;EACG,UAAA,CAAA,EAAA,CAAA,QAAA,EAAA,GAAA,GAAA,IAAA,EAAA,KAAA,EAAA,MAAA,EAAA,GAAA,OAAA;EAAa,YAAA,CAAA,EAAA,CAAA,EAAA,EAAA,OAAA,EAAA,GAAA,IAAA;EAIpB,YAAA,CAAA,EAAA,MAAiB;CAClB;KApDV,aAAA,GAqDU;EACZ,cAAA,EAAA,MAAA;EAAW,SAAA,EApDF,eAoDE;EA+GE,OAAA,EAAA,MAAA;AAOhB,CAAA;AAOA,KA7KK,gBAAA,GAAmB,aA6KoB,GAAA;EA8D5B,QAAA,EAAA,OAAA;EAuDA,OAAA,CAAA,EAAA,MAAA,GAAA,IAAA;EACF,KAAA,CAAA,EAAA,MAAA;CAAN;AAEL,KA9PS,WAAA,GAAc,KA8PvB,CA9P6B,WA8P7B,CAAA,GAAA;EAAuB,SAAA,CAAA,OAAA,EA7PN,gBA6PM,CAAA,EAAA,IAAA;wBA5PH;;;iBAIP,iBAAA,gBACD,4BACA,0BACZ;iBA+Ga,cAAA,QACR,sBACE;iBAKM,gBAAA,QACR,sBACE;iBAKM,4BAAA,QACR,oBACA;;;;;;iBA4DQ,2BAAA,QACR,oBACA;iBAqDQ,qBAAA,QACR,MAAM,uCAEX"}
|
package/store/typing-store.js
CHANGED
|
@@ -8,7 +8,7 @@ function makeKey(conversationId, actorType, actorId) {
|
|
|
8
8
|
function removeEntry(state, conversationId, key) {
|
|
9
9
|
const existingConversation = state.conversations[conversationId];
|
|
10
10
|
if (!(existingConversation && key in existingConversation)) return state;
|
|
11
|
-
const { [key]: _removed
|
|
11
|
+
const { [key]: _removed, ...rest } = existingConversation;
|
|
12
12
|
if (Object.keys(rest).length === 0) {
|
|
13
13
|
const nextConversations = { ...state.conversations };
|
|
14
14
|
delete nextConversations[conversationId];
|
|
@@ -121,24 +121,17 @@ function applyConversationTypingEvent(store, event, options = {}) {
|
|
|
121
121
|
}
|
|
122
122
|
function clearTypingFromTimelineItem(store, event) {
|
|
123
123
|
const { item } = event.payload;
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
136
|
-
if (!(actorType && actorId)) return;
|
|
137
|
-
clearTypingState(store, {
|
|
138
|
-
conversationId: item.conversationId,
|
|
139
|
-
actorType,
|
|
140
|
-
actorId
|
|
141
|
-
});
|
|
124
|
+
if ((item.aiAgentId ? "ai_agent" : item.userId ? "user" : item.visitorId ? "visitor" : null) === "ai_agent") {
|
|
125
|
+
const conversationTyping = store.getState().conversations[item.conversationId];
|
|
126
|
+
if (conversationTyping) for (const key of Object.keys(conversationTyping)) {
|
|
127
|
+
const entry = conversationTyping[key];
|
|
128
|
+
if (entry && entry.actorType !== "ai_agent") store.removeTyping({
|
|
129
|
+
conversationId: item.conversationId,
|
|
130
|
+
actorType: entry.actorType,
|
|
131
|
+
actorId: entry.actorId
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
} else store.clearConversation(item.conversationId);
|
|
142
135
|
}
|
|
143
136
|
function getConversationTyping(store, conversationId) {
|
|
144
137
|
return store.getState().conversations[conversationId];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typing-store.js","names":["entry: TypingEntry","nextConversation: ConversationTypingState","actorType: TypingActorType | null","actorId: string | null"],"sources":["../../src/store/typing-store.ts"],"sourcesContent":["import type { RealtimeEvent } from \"@cossistant/types/realtime-events\";\nimport { createStore, type Store } from \"./create-store\";\n\nexport type TypingActorType = \"visitor\" | \"user\" | \"ai_agent\";\n\nexport type TypingEntry = {\n\tactorType: TypingActorType;\n\tactorId: string;\n\tpreview: string | null;\n\tupdatedAt: number;\n};\n\nexport type ConversationTypingState = Record<string, TypingEntry>;\n\nexport type TypingState = {\n\tconversations: Record<string, ConversationTypingState>;\n};\n\nconst DEFAULT_TTL_MS = 6000;\n\nexport type TypingStoreDependencies = {\n\tnow?: () => number;\n\tsetTimeout?: (callback: () => void, delay: number) => unknown;\n\tclearTimeout?: (id: unknown) => void;\n\tdefaultTtlMs?: number;\n};\n\ntype TypingOptions = {\n\tconversationId: string;\n\tactorType: TypingActorType;\n\tactorId: string;\n};\n\ntype SetTypingOptions = TypingOptions & {\n\tisTyping: boolean;\n\tpreview?: string | null;\n\tttlMs?: number;\n};\n\nfunction makeKey(\n\tconversationId: string,\n\tactorType: TypingActorType,\n\tactorId: string\n): string {\n\treturn `${conversationId}:${actorType}:${actorId}`;\n}\n\nfunction removeEntry(\n\tstate: TypingState,\n\tconversationId: string,\n\tkey: string\n): TypingState {\n\tconst existingConversation = state.conversations[conversationId];\n\tif (!(existingConversation && key in existingConversation)) {\n\t\treturn state;\n\t}\n\n\tconst { [key]: _removed, ...rest } = existingConversation;\n\tif (Object.keys(rest).length === 0) {\n\t\tconst nextConversations = { ...state.conversations };\n\t\tdelete nextConversations[conversationId];\n\t\treturn { conversations: nextConversations } satisfies TypingState;\n\t}\n\n\treturn {\n\t\tconversations: {\n\t\t\t...state.conversations,\n\t\t\t[conversationId]: rest,\n\t\t},\n\t} satisfies TypingState;\n}\n\nexport type TypingStore = Store<TypingState> & {\n\tsetTyping(options: SetTypingOptions): void;\n\tremoveTyping(options: TypingOptions): void;\n\tclearConversation(conversationId: string): void;\n};\n\nexport function createTypingStore(\n\tinitialState: TypingState = { conversations: {} },\n\tdependencies: TypingStoreDependencies = {}\n): TypingStore {\n\tconst {\n\t\tnow = () => Date.now(),\n\t\tsetTimeout: schedule = (callback, delay) =>\n\t\t\tglobalThis.setTimeout(callback, delay),\n\t\tclearTimeout: clearScheduled = (id) =>\n\t\t\tglobalThis.clearTimeout(id as ReturnType<typeof globalThis.setTimeout>),\n\t\tdefaultTtlMs = DEFAULT_TTL_MS,\n\t} = dependencies;\n\n\tconst timers = new Map<string, unknown>();\n\tconst store = createStore<TypingState>({\n\t\tconversations: { ...initialState.conversations },\n\t});\n\n\tconst clearTimer = (key: string) => {\n\t\tconst handle = timers.get(key);\n\t\tif (!handle) {\n\t\t\treturn;\n\t\t}\n\t\ttimers.delete(key);\n\t\tclearScheduled(handle);\n\t};\n\n\tconst scheduleRemoval = (\n\t\tkey: string,\n\t\toptions: TypingOptions,\n\t\tttl: number\n\t) => {\n\t\tclearTimer(key);\n\t\tconst handle = schedule(() => {\n\t\t\ttimers.delete(key);\n\t\t\tstore.setState((state) =>\n\t\t\t\tremoveEntry(state, options.conversationId, key)\n\t\t\t);\n\t\t}, ttl);\n\t\ttimers.set(key, handle);\n\t};\n\n\treturn {\n\t\t...store,\n\t\tsetTyping({\n\t\t\tconversationId,\n\t\t\tactorType,\n\t\t\tactorId,\n\t\t\tisTyping,\n\t\t\tpreview = null,\n\t\t\tttlMs,\n\t\t}) {\n\t\t\tconst key = makeKey(conversationId, actorType, actorId);\n\n\t\t\tif (!isTyping) {\n\t\t\t\tclearTimer(key);\n\t\t\t\tstore.setState((state) => removeEntry(state, conversationId, key));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst entry: TypingEntry = {\n\t\t\t\tactorType,\n\t\t\t\tactorId,\n\t\t\t\tpreview: preview ?? null,\n\t\t\t\tupdatedAt: now(),\n\t\t\t};\n\n\t\t\tstore.setState((state) => {\n\t\t\t\tconst existingConversation = state.conversations[conversationId];\n\n\t\t\t\tconst nextConversation: ConversationTypingState = {\n\t\t\t\t\t...(existingConversation ? { ...existingConversation } : {}),\n\t\t\t\t\t[key]: entry,\n\t\t\t\t};\n\n\t\t\t\treturn {\n\t\t\t\t\tconversations: {\n\t\t\t\t\t\t...state.conversations,\n\t\t\t\t\t\t[conversationId]: nextConversation,\n\t\t\t\t\t},\n\t\t\t\t} satisfies TypingState;\n\t\t\t});\n\n\t\t\tconst timeoutMs = ttlMs ?? defaultTtlMs;\n\t\t\tscheduleRemoval(key, { conversationId, actorType, actorId }, timeoutMs);\n\t\t},\n\t\tremoveTyping({ conversationId, actorType, actorId }) {\n\t\t\tconst key = makeKey(conversationId, actorType, actorId);\n\t\t\tclearTimer(key);\n\t\t\tstore.setState((state) => removeEntry(state, conversationId, key));\n\t\t},\n\t\tclearConversation(conversationId) {\n\t\t\tconst conversation = store.getState().conversations[conversationId];\n\t\t\tif (!conversation) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tfor (const key of Object.keys(conversation)) {\n\t\t\t\tclearTimer(key);\n\t\t\t}\n\n\t\t\tstore.setState((state) => {\n\t\t\t\tif (!(conversationId in state.conversations)) {\n\t\t\t\t\treturn state;\n\t\t\t\t}\n\n\t\t\t\tconst nextConversations = { ...state.conversations };\n\t\t\t\tdelete nextConversations[conversationId];\n\t\t\t\treturn { conversations: nextConversations } satisfies TypingState;\n\t\t\t});\n\t\t},\n\t} satisfies TypingStore;\n}\n\nexport function setTypingState(\n\tstore: TypingStore,\n\toptions: SetTypingOptions\n): void {\n\tstore.setTyping(options);\n}\n\nexport function clearTypingState(\n\tstore: TypingStore,\n\toptions: TypingOptions\n): void {\n\tstore.removeTyping(options);\n}\n\nexport function applyConversationTypingEvent(\n\tstore: TypingStore,\n\tevent: RealtimeEvent<\"conversationTyping\">,\n\toptions: {\n\t\tignoreVisitorId?: string | null;\n\t\tignoreUserId?: string | null;\n\t\tignoreAiAgentId?: string | null;\n\t\tttlMs?: number;\n\t} = {}\n): void {\n\tconst { payload } = event;\n\tlet actorType: TypingActorType | null = null;\n\tlet actorId: string | null = null;\n\n\t// IMPORTANT: Check aiAgentId BEFORE visitorId because the event payload\n\t// always includes visitorId (for routing purposes), even for AI agent typing.\n\t// The presence of aiAgentId specifically identifies AI agent typing.\n\tif (payload.userId) {\n\t\tactorType = \"user\";\n\t\tactorId = payload.userId;\n\t} else if (payload.aiAgentId) {\n\t\tactorType = \"ai_agent\";\n\t\tactorId = payload.aiAgentId;\n\t} else if (payload.visitorId) {\n\t\tactorType = \"visitor\";\n\t\tactorId = payload.visitorId;\n\t}\n\n\tif (!(actorType && actorId)) {\n\t\treturn;\n\t}\n\n\tif (\n\t\t(actorType === \"visitor\" &&\n\t\t\tpayload.visitorId &&\n\t\t\toptions.ignoreVisitorId &&\n\t\t\tpayload.visitorId === options.ignoreVisitorId) ||\n\t\t(actorType === \"user\" &&\n\t\t\tpayload.userId &&\n\t\t\toptions.ignoreUserId &&\n\t\t\tpayload.userId === options.ignoreUserId) ||\n\t\t(actorType === \"ai_agent\" &&\n\t\t\tpayload.aiAgentId &&\n\t\t\toptions.ignoreAiAgentId &&\n\t\t\tpayload.aiAgentId === options.ignoreAiAgentId)\n\t) {\n\t\treturn;\n\t}\n\n\tconst preview =\n\t\tactorType === \"visitor\" ? (payload.visitorPreview ?? null) : null;\n\n\tsetTypingState(store, {\n\t\tconversationId: payload.conversationId,\n\t\tactorType,\n\t\tactorId,\n\t\tisTyping: payload.isTyping,\n\t\tpreview,\n\t\tttlMs: options.ttlMs,\n\t});\n}\n\nexport function clearTypingFromTimelineItem(\n\tstore: TypingStore,\n\tevent: RealtimeEvent<\"timelineItemCreated\">\n): void {\n\tconst { item } = event.payload;\n\tlet actorType: TypingActorType | null = null;\n\tlet actorId: string | null = null;\n\n\t// Check aiAgentId before visitorId for consistency with applyConversationTypingEvent\n\tif (item.userId) {\n\t\tactorType = \"user\";\n\t\tactorId = item.userId;\n\t} else if (item.aiAgentId) {\n\t\tactorType = \"ai_agent\";\n\t\tactorId = item.aiAgentId;\n\t} else if (item.visitorId) {\n\t\tactorType = \"visitor\";\n\t\tactorId = item.visitorId;\n\t}\n\n\tif (!(actorType && actorId)) {\n\t\treturn;\n\t}\n\n\tclearTypingState(store, {\n\t\tconversationId: item.conversationId,\n\t\tactorType,\n\t\tactorId,\n\t});\n}\n\nexport function getConversationTyping(\n\tstore: Store<TypingState>,\n\tconversationId: string\n): ConversationTypingState | undefined {\n\treturn store.getState().conversations[conversationId];\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"typing-store.js","names":["entry: TypingEntry","nextConversation: ConversationTypingState","actorType: TypingActorType | null","actorId: string | null"],"sources":["../../src/store/typing-store.ts"],"sourcesContent":["import type { RealtimeEvent } from \"@cossistant/types/realtime-events\";\nimport { createStore, type Store } from \"./create-store\";\n\nexport type TypingActorType = \"visitor\" | \"user\" | \"ai_agent\";\n\nexport type TypingEntry = {\n\tactorType: TypingActorType;\n\tactorId: string;\n\tpreview: string | null;\n\tupdatedAt: number;\n};\n\nexport type ConversationTypingState = Record<string, TypingEntry>;\n\nexport type TypingState = {\n\tconversations: Record<string, ConversationTypingState>;\n};\n\nconst DEFAULT_TTL_MS = 6000;\n\nexport type TypingStoreDependencies = {\n\tnow?: () => number;\n\tsetTimeout?: (callback: () => void, delay: number) => unknown;\n\tclearTimeout?: (id: unknown) => void;\n\tdefaultTtlMs?: number;\n};\n\ntype TypingOptions = {\n\tconversationId: string;\n\tactorType: TypingActorType;\n\tactorId: string;\n};\n\ntype SetTypingOptions = TypingOptions & {\n\tisTyping: boolean;\n\tpreview?: string | null;\n\tttlMs?: number;\n};\n\nfunction makeKey(\n\tconversationId: string,\n\tactorType: TypingActorType,\n\tactorId: string\n): string {\n\treturn `${conversationId}:${actorType}:${actorId}`;\n}\n\nfunction removeEntry(\n\tstate: TypingState,\n\tconversationId: string,\n\tkey: string\n): TypingState {\n\tconst existingConversation = state.conversations[conversationId];\n\tif (!(existingConversation && key in existingConversation)) {\n\t\treturn state;\n\t}\n\n\tconst { [key]: _removed, ...rest } = existingConversation;\n\tif (Object.keys(rest).length === 0) {\n\t\tconst nextConversations = { ...state.conversations };\n\t\tdelete nextConversations[conversationId];\n\t\treturn { conversations: nextConversations } satisfies TypingState;\n\t}\n\n\treturn {\n\t\tconversations: {\n\t\t\t...state.conversations,\n\t\t\t[conversationId]: rest,\n\t\t},\n\t} satisfies TypingState;\n}\n\nexport type TypingStore = Store<TypingState> & {\n\tsetTyping(options: SetTypingOptions): void;\n\tremoveTyping(options: TypingOptions): void;\n\tclearConversation(conversationId: string): void;\n};\n\nexport function createTypingStore(\n\tinitialState: TypingState = { conversations: {} },\n\tdependencies: TypingStoreDependencies = {}\n): TypingStore {\n\tconst {\n\t\tnow = () => Date.now(),\n\t\tsetTimeout: schedule = (callback, delay) =>\n\t\t\tglobalThis.setTimeout(callback, delay),\n\t\tclearTimeout: clearScheduled = (id) =>\n\t\t\tglobalThis.clearTimeout(id as ReturnType<typeof globalThis.setTimeout>),\n\t\tdefaultTtlMs = DEFAULT_TTL_MS,\n\t} = dependencies;\n\n\tconst timers = new Map<string, unknown>();\n\tconst store = createStore<TypingState>({\n\t\tconversations: { ...initialState.conversations },\n\t});\n\n\tconst clearTimer = (key: string) => {\n\t\tconst handle = timers.get(key);\n\t\tif (!handle) {\n\t\t\treturn;\n\t\t}\n\t\ttimers.delete(key);\n\t\tclearScheduled(handle);\n\t};\n\n\tconst scheduleRemoval = (\n\t\tkey: string,\n\t\toptions: TypingOptions,\n\t\tttl: number\n\t) => {\n\t\tclearTimer(key);\n\t\tconst handle = schedule(() => {\n\t\t\ttimers.delete(key);\n\t\t\tstore.setState((state) =>\n\t\t\t\tremoveEntry(state, options.conversationId, key)\n\t\t\t);\n\t\t}, ttl);\n\t\ttimers.set(key, handle);\n\t};\n\n\treturn {\n\t\t...store,\n\t\tsetTyping({\n\t\t\tconversationId,\n\t\t\tactorType,\n\t\t\tactorId,\n\t\t\tisTyping,\n\t\t\tpreview = null,\n\t\t\tttlMs,\n\t\t}) {\n\t\t\tconst key = makeKey(conversationId, actorType, actorId);\n\n\t\t\tif (!isTyping) {\n\t\t\t\tclearTimer(key);\n\t\t\t\tstore.setState((state) => removeEntry(state, conversationId, key));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst entry: TypingEntry = {\n\t\t\t\tactorType,\n\t\t\t\tactorId,\n\t\t\t\tpreview: preview ?? null,\n\t\t\t\tupdatedAt: now(),\n\t\t\t};\n\n\t\t\tstore.setState((state) => {\n\t\t\t\tconst existingConversation = state.conversations[conversationId];\n\n\t\t\t\tconst nextConversation: ConversationTypingState = {\n\t\t\t\t\t...(existingConversation ? { ...existingConversation } : {}),\n\t\t\t\t\t[key]: entry,\n\t\t\t\t};\n\n\t\t\t\treturn {\n\t\t\t\t\tconversations: {\n\t\t\t\t\t\t...state.conversations,\n\t\t\t\t\t\t[conversationId]: nextConversation,\n\t\t\t\t\t},\n\t\t\t\t} satisfies TypingState;\n\t\t\t});\n\n\t\t\tconst timeoutMs = ttlMs ?? defaultTtlMs;\n\t\t\tscheduleRemoval(key, { conversationId, actorType, actorId }, timeoutMs);\n\t\t},\n\t\tremoveTyping({ conversationId, actorType, actorId }) {\n\t\t\tconst key = makeKey(conversationId, actorType, actorId);\n\t\t\tclearTimer(key);\n\t\t\tstore.setState((state) => removeEntry(state, conversationId, key));\n\t\t},\n\t\tclearConversation(conversationId) {\n\t\t\tconst conversation = store.getState().conversations[conversationId];\n\t\t\tif (!conversation) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tfor (const key of Object.keys(conversation)) {\n\t\t\t\tclearTimer(key);\n\t\t\t}\n\n\t\t\tstore.setState((state) => {\n\t\t\t\tif (!(conversationId in state.conversations)) {\n\t\t\t\t\treturn state;\n\t\t\t\t}\n\n\t\t\t\tconst nextConversations = { ...state.conversations };\n\t\t\t\tdelete nextConversations[conversationId];\n\t\t\t\treturn { conversations: nextConversations } satisfies TypingState;\n\t\t\t});\n\t\t},\n\t} satisfies TypingStore;\n}\n\nexport function setTypingState(\n\tstore: TypingStore,\n\toptions: SetTypingOptions\n): void {\n\tstore.setTyping(options);\n}\n\nexport function clearTypingState(\n\tstore: TypingStore,\n\toptions: TypingOptions\n): void {\n\tstore.removeTyping(options);\n}\n\nexport function applyConversationTypingEvent(\n\tstore: TypingStore,\n\tevent: RealtimeEvent<\"conversationTyping\">,\n\toptions: {\n\t\tignoreVisitorId?: string | null;\n\t\tignoreUserId?: string | null;\n\t\tignoreAiAgentId?: string | null;\n\t\tttlMs?: number;\n\t} = {}\n): void {\n\tconst { payload } = event;\n\tlet actorType: TypingActorType | null = null;\n\tlet actorId: string | null = null;\n\n\t// IMPORTANT: Check aiAgentId BEFORE visitorId because the event payload\n\t// always includes visitorId (for routing purposes), even for AI agent typing.\n\t// The presence of aiAgentId specifically identifies AI agent typing.\n\tif (payload.userId) {\n\t\tactorType = \"user\";\n\t\tactorId = payload.userId;\n\t} else if (payload.aiAgentId) {\n\t\tactorType = \"ai_agent\";\n\t\tactorId = payload.aiAgentId;\n\t} else if (payload.visitorId) {\n\t\tactorType = \"visitor\";\n\t\tactorId = payload.visitorId;\n\t}\n\n\tif (!(actorType && actorId)) {\n\t\treturn;\n\t}\n\n\tif (\n\t\t(actorType === \"visitor\" &&\n\t\t\tpayload.visitorId &&\n\t\t\toptions.ignoreVisitorId &&\n\t\t\tpayload.visitorId === options.ignoreVisitorId) ||\n\t\t(actorType === \"user\" &&\n\t\t\tpayload.userId &&\n\t\t\toptions.ignoreUserId &&\n\t\t\tpayload.userId === options.ignoreUserId) ||\n\t\t(actorType === \"ai_agent\" &&\n\t\t\tpayload.aiAgentId &&\n\t\t\toptions.ignoreAiAgentId &&\n\t\t\tpayload.aiAgentId === options.ignoreAiAgentId)\n\t) {\n\t\treturn;\n\t}\n\n\tconst preview =\n\t\tactorType === \"visitor\" ? (payload.visitorPreview ?? null) : null;\n\n\tsetTypingState(store, {\n\t\tconversationId: payload.conversationId,\n\t\tactorType,\n\t\tactorId,\n\t\tisTyping: payload.isTyping,\n\t\tpreview,\n\t\tttlMs: options.ttlMs,\n\t});\n}\n\nexport function clearTypingFromTimelineItem(\n\tstore: TypingStore,\n\tevent: RealtimeEvent<\"timelineItemCreated\">\n): void {\n\tconst { item } = event.payload;\n\n\t// Determine who sent this message\n\tconst senderType: TypingActorType | null = item.aiAgentId\n\t\t? \"ai_agent\"\n\t\t: item.userId\n\t\t\t? \"user\"\n\t\t\t: item.visitorId\n\t\t\t\t? \"visitor\"\n\t\t\t\t: null;\n\n\t// Strategy for clearing typing based on who sent the message:\n\t//\n\t// 1. VISITOR message → Clear ALL typing\n\t// - A new AI workflow will start, so AI typing should stop\n\t// - User typing should stop too (conversation state changed)\n\t//\n\t// 2. USER message → Clear ALL typing\n\t// - Human took over the conversation\n\t// - AI typing should stop (human is handling)\n\t//\n\t// 3. AI message → DON'T clear AI typing (more messages may come)\n\t// - AI may send multiple messages in sequence\n\t// - Server restarts typing after each message\n\t// - Typing TTL (6s) handles eventual cleanup\n\t// - Clear OTHER typing (visitor/user) just in case\n\n\tif (senderType === \"ai_agent\") {\n\t\t// AI sent a message - keep AI typing alive for multi-message sequences\n\t\t// Only clear non-AI typing entries\n\t\tconst conversationTyping =\n\t\t\tstore.getState().conversations[item.conversationId];\n\t\tif (conversationTyping) {\n\t\t\tfor (const key of Object.keys(conversationTyping)) {\n\t\t\t\tconst entry = conversationTyping[key];\n\t\t\t\tif (entry && entry.actorType !== \"ai_agent\") {\n\t\t\t\t\tstore.removeTyping({\n\t\t\t\t\t\tconversationId: item.conversationId,\n\t\t\t\t\t\tactorType: entry.actorType,\n\t\t\t\t\t\tactorId: entry.actorId,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// Visitor or user sent a message - clear ALL typing\n\t\t// This resets the conversation state properly\n\t\tstore.clearConversation(item.conversationId);\n\t}\n}\n\nexport function getConversationTyping(\n\tstore: Store<TypingState>,\n\tconversationId: string\n): ConversationTypingState | undefined {\n\treturn store.getState().conversations[conversationId];\n}\n"],"mappings":";;;AAkBA,MAAM,iBAAiB;AAqBvB,SAAS,QACR,gBACA,WACA,SACS;AACT,QAAO,GAAG,eAAe,GAAG,UAAU,GAAG;;AAG1C,SAAS,YACR,OACA,gBACA,KACc;CACd,MAAM,uBAAuB,MAAM,cAAc;AACjD,KAAI,EAAE,wBAAwB,OAAO,sBACpC,QAAO;CAGR,MAAM,GAAG,MAAM,UAAU,GAAG,SAAS;AACrC,KAAI,OAAO,KAAK,KAAK,CAAC,WAAW,GAAG;EACnC,MAAM,oBAAoB,EAAE,GAAG,MAAM,eAAe;AACpD,SAAO,kBAAkB;AACzB,SAAO,EAAE,eAAe,mBAAmB;;AAG5C,QAAO,EACN,eAAe;EACd,GAAG,MAAM;GACR,iBAAiB;EAClB,EACD;;AASF,SAAgB,kBACf,eAA4B,EAAE,eAAe,EAAE,EAAE,EACjD,eAAwC,EAAE,EAC5B;CACd,MAAM,EACL,YAAY,KAAK,KAAK,EACtB,YAAY,YAAY,UAAU,UACjC,WAAW,WAAW,UAAU,MAAM,EACvC,cAAc,kBAAkB,OAC/B,WAAW,aAAa,GAA+C,EACxE,eAAe,mBACZ;CAEJ,MAAM,yBAAS,IAAI,KAAsB;CACzC,MAAM,QAAQ,YAAyB,EACtC,eAAe,EAAE,GAAG,aAAa,eAAe,EAChD,CAAC;CAEF,MAAM,cAAc,QAAgB;EACnC,MAAM,SAAS,OAAO,IAAI,IAAI;AAC9B,MAAI,CAAC,OACJ;AAED,SAAO,OAAO,IAAI;AAClB,iBAAe,OAAO;;CAGvB,MAAM,mBACL,KACA,SACA,QACI;AACJ,aAAW,IAAI;EACf,MAAM,SAAS,eAAe;AAC7B,UAAO,OAAO,IAAI;AAClB,SAAM,UAAU,UACf,YAAY,OAAO,QAAQ,gBAAgB,IAAI,CAC/C;KACC,IAAI;AACP,SAAO,IAAI,KAAK,OAAO;;AAGxB,QAAO;EACN,GAAG;EACH,UAAU,EACT,gBACA,WACA,SACA,UACA,UAAU,MACV,SACE;GACF,MAAM,MAAM,QAAQ,gBAAgB,WAAW,QAAQ;AAEvD,OAAI,CAAC,UAAU;AACd,eAAW,IAAI;AACf,UAAM,UAAU,UAAU,YAAY,OAAO,gBAAgB,IAAI,CAAC;AAClE;;GAGD,MAAMA,QAAqB;IAC1B;IACA;IACA,SAAS,WAAW;IACpB,WAAW,KAAK;IAChB;AAED,SAAM,UAAU,UAAU;IACzB,MAAM,uBAAuB,MAAM,cAAc;IAEjD,MAAMC,mBAA4C;KACjD,GAAI,uBAAuB,EAAE,GAAG,sBAAsB,GAAG,EAAE;MAC1D,MAAM;KACP;AAED,WAAO,EACN,eAAe;KACd,GAAG,MAAM;MACR,iBAAiB;KAClB,EACD;KACA;AAGF,mBAAgB,KAAK;IAAE;IAAgB;IAAW;IAAS,EADzC,SAAS,aAC4C;;EAExE,aAAa,EAAE,gBAAgB,WAAW,WAAW;GACpD,MAAM,MAAM,QAAQ,gBAAgB,WAAW,QAAQ;AACvD,cAAW,IAAI;AACf,SAAM,UAAU,UAAU,YAAY,OAAO,gBAAgB,IAAI,CAAC;;EAEnE,kBAAkB,gBAAgB;GACjC,MAAM,eAAe,MAAM,UAAU,CAAC,cAAc;AACpD,OAAI,CAAC,aACJ;AAGD,QAAK,MAAM,OAAO,OAAO,KAAK,aAAa,CAC1C,YAAW,IAAI;AAGhB,SAAM,UAAU,UAAU;AACzB,QAAI,EAAE,kBAAkB,MAAM,eAC7B,QAAO;IAGR,MAAM,oBAAoB,EAAE,GAAG,MAAM,eAAe;AACpD,WAAO,kBAAkB;AACzB,WAAO,EAAE,eAAe,mBAAmB;KAC1C;;EAEH;;AAGF,SAAgB,eACf,OACA,SACO;AACP,OAAM,UAAU,QAAQ;;AAGzB,SAAgB,iBACf,OACA,SACO;AACP,OAAM,aAAa,QAAQ;;AAG5B,SAAgB,6BACf,OACA,OACA,UAKI,EAAE,EACC;CACP,MAAM,EAAE,YAAY;CACpB,IAAIC,YAAoC;CACxC,IAAIC,UAAyB;AAK7B,KAAI,QAAQ,QAAQ;AACnB,cAAY;AACZ,YAAU,QAAQ;YACR,QAAQ,WAAW;AAC7B,cAAY;AACZ,YAAU,QAAQ;YACR,QAAQ,WAAW;AAC7B,cAAY;AACZ,YAAU,QAAQ;;AAGnB,KAAI,EAAE,aAAa,SAClB;AAGD,KACE,cAAc,aACd,QAAQ,aACR,QAAQ,mBACR,QAAQ,cAAc,QAAQ,mBAC9B,cAAc,UACd,QAAQ,UACR,QAAQ,gBACR,QAAQ,WAAW,QAAQ,gBAC3B,cAAc,cACd,QAAQ,aACR,QAAQ,mBACR,QAAQ,cAAc,QAAQ,gBAE/B;CAGD,MAAM,UACL,cAAc,YAAa,QAAQ,kBAAkB,OAAQ;AAE9D,gBAAe,OAAO;EACrB,gBAAgB,QAAQ;EACxB;EACA;EACA,UAAU,QAAQ;EAClB;EACA,OAAO,QAAQ;EACf,CAAC;;AAGH,SAAgB,4BACf,OACA,OACO;CACP,MAAM,EAAE,SAAS,MAAM;AA2BvB,MAxB2C,KAAK,YAC7C,aACA,KAAK,SACJ,SACA,KAAK,YACJ,YACA,UAkBc,YAAY;EAG9B,MAAM,qBACL,MAAM,UAAU,CAAC,cAAc,KAAK;AACrC,MAAI,mBACH,MAAK,MAAM,OAAO,OAAO,KAAK,mBAAmB,EAAE;GAClD,MAAM,QAAQ,mBAAmB;AACjC,OAAI,SAAS,MAAM,cAAc,WAChC,OAAM,aAAa;IAClB,gBAAgB,KAAK;IACrB,WAAW,MAAM;IACjB,SAAS,MAAM;IACf,CAAC;;OAOL,OAAM,kBAAkB,KAAK,eAAe;;AAI9C,SAAgB,sBACf,OACA,gBACsC;AACtC,QAAO,MAAM,UAAU,CAAC,cAAc"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contact.d.ts","names":[],"sources":["../../../../../types/src/api/contact.ts"],"sourcesContent":[],"mappings":";;;;;;;cAmQa,+BAA6B,CAAA,CAAA;;;;;;;;;;;;;;;;;KAQ9B,uBAAA,GAA0B,CAAA,CAAE,aAChC"}
|