@cossistant/react 0.0.26 → 0.0.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/api.d.ts +1 -1
- package/api.d.ts.map +1 -1
- package/checks.d.ts +1 -1
- package/checks.d.ts.map +1 -1
- package/coerce.d.ts +1 -1
- package/coerce.d.ts.map +1 -1
- package/conversation.d.ts +6 -3
- package/conversation.d.ts.map +1 -1
- package/core.d.ts +1 -1
- package/core.d.ts.map +1 -1
- package/errors.d.ts +12 -3
- package/errors.d.ts.map +1 -1
- package/errors2.d.ts +1 -1
- package/errors2.d.ts.map +1 -1
- package/hooks/index.d.ts +3 -2
- package/hooks/index.js +6 -5
- package/hooks/private/store/use-website-store.js +2 -1
- package/hooks/private/store/use-website-store.js.map +1 -1
- package/hooks/private/use-client-query.d.ts +6 -0
- package/hooks/private/use-client-query.d.ts.map +1 -1
- package/hooks/private/use-client-query.js +26 -3
- package/hooks/private/use-client-query.js.map +1 -1
- package/hooks/private/use-grouped-messages.d.ts +8 -5
- package/hooks/private/use-grouped-messages.d.ts.map +1 -1
- package/hooks/private/use-grouped-messages.js +44 -11
- package/hooks/private/use-grouped-messages.js.map +1 -1
- package/hooks/private/use-multimodal-input.d.ts.map +1 -1
- package/hooks/private/use-multimodal-input.js +7 -5
- package/hooks/private/use-multimodal-input.js.map +1 -1
- package/hooks/private/use-visitor-typing-reporter.d.ts +18 -1
- package/hooks/private/use-visitor-typing-reporter.d.ts.map +1 -1
- package/hooks/private/use-visitor-typing-reporter.js +34 -4
- package/hooks/private/use-visitor-typing-reporter.js.map +1 -1
- package/hooks/use-conversation-page.d.ts +1 -0
- package/hooks/use-conversation-page.d.ts.map +1 -1
- package/hooks/use-conversation-page.js +6 -1
- package/hooks/use-conversation-page.js.map +1 -1
- package/hooks/use-conversation-preview.d.ts +2 -1
- package/hooks/use-conversation-preview.d.ts.map +1 -1
- package/hooks/use-conversation-preview.js +1 -1
- package/hooks/use-conversation-preview.js.map +1 -1
- package/hooks/use-conversation-seen.js +1 -1
- package/hooks/use-conversation-seen.js.map +1 -1
- package/hooks/use-conversation-timeline-items.js +2 -1
- package/hooks/use-conversation-timeline-items.js.map +1 -1
- package/hooks/use-conversation-timeline.d.ts.map +1 -1
- package/hooks/use-conversation-timeline.js +1 -3
- package/hooks/use-conversation-timeline.js.map +1 -1
- package/hooks/use-conversation.js +2 -1
- package/hooks/use-conversation.js.map +1 -1
- package/hooks/use-conversations.js +1 -0
- package/hooks/use-conversations.js.map +1 -1
- package/hooks/use-create-conversation.d.ts.map +1 -1
- package/hooks/use-file-upload.d.ts +55 -0
- package/hooks/use-file-upload.d.ts.map +1 -0
- package/hooks/use-file-upload.js +100 -0
- package/hooks/use-file-upload.js.map +1 -0
- package/hooks/use-message-composer.d.ts +11 -0
- package/hooks/use-message-composer.d.ts.map +1 -1
- package/hooks/use-message-composer.js +7 -3
- package/hooks/use-message-composer.js.map +1 -1
- package/hooks/use-send-message.d.ts +1 -0
- package/hooks/use-send-message.d.ts.map +1 -1
- package/hooks/use-send-message.js +63 -11
- package/hooks/use-send-message.js.map +1 -1
- package/index.d.ts +7 -4
- package/index.js +13 -10
- package/json-schema.d.ts +70 -0
- package/json-schema.d.ts.map +1 -0
- package/package.json +4 -3
- package/parse.d.ts +1 -1
- package/parse.d.ts.map +1 -1
- package/primitives/avatar/fallback.d.ts.map +1 -1
- package/primitives/avatar/fallback.js +1 -1
- package/primitives/avatar/fallback.js.map +1 -1
- package/primitives/conversation-timeline.d.ts.map +1 -1
- package/primitives/conversation-timeline.js +10 -5
- package/primitives/conversation-timeline.js.map +1 -1
- package/primitives/day-separator.d.ts +76 -0
- package/primitives/day-separator.d.ts.map +1 -0
- package/primitives/day-separator.js +111 -0
- package/primitives/day-separator.js.map +1 -0
- package/primitives/index.d.ts +5 -3
- package/primitives/index.js +17 -5
- package/primitives/index.parts.d.ts +4 -2
- package/primitives/index.parts.js +5 -3
- package/primitives/timeline-item-attachments.d.ts +100 -0
- package/primitives/timeline-item-attachments.d.ts.map +1 -0
- package/primitives/timeline-item-attachments.js +151 -0
- package/primitives/timeline-item-attachments.js.map +1 -0
- package/primitives/timeline-item-group.d.ts.map +1 -1
- package/primitives/timeline-item-group.js +1 -1
- package/primitives/timeline-item-group.js.map +1 -1
- package/primitives/timeline-item.js +1 -1
- package/primitives/timeline-item.js.map +1 -1
- package/primitives/trigger.d.ts +91 -0
- package/primitives/trigger.d.ts.map +1 -0
- package/primitives/trigger.js +74 -0
- package/primitives/trigger.js.map +1 -0
- package/primitives/window.d.ts +22 -1
- package/primitives/window.d.ts.map +1 -1
- package/primitives/window.js +91 -5
- package/primitives/window.js.map +1 -1
- package/provider.d.ts.map +1 -1
- package/provider.js +8 -3
- package/provider.js.map +1 -1
- package/realtime/index.js +1 -1
- package/realtime/provider.js +1 -1
- package/realtime/support-provider.js +5 -1
- package/realtime/support-provider.js.map +1 -1
- package/realtime-events.d.ts +165 -2
- package/realtime-events.d.ts.map +1 -1
- package/registries.d.ts +1 -1
- package/registries.d.ts.map +1 -1
- package/schemas.d.ts +305 -7
- package/schemas.d.ts.map +1 -1
- package/schemas2.d.ts +29 -4
- package/schemas2.d.ts.map +1 -1
- package/schemas3.d.ts +2 -1
- package/schemas3.d.ts.map +1 -1
- package/standard-schema.d.ts +83 -21
- package/standard-schema.d.ts.map +1 -1
- package/support/components/button.d.ts +1 -1
- package/support/components/content.d.ts +30 -0
- package/support/components/content.d.ts.map +1 -0
- package/support/components/content.js +282 -0
- package/support/components/content.js.map +1 -0
- package/support/components/conversation-button-link.js +1 -1
- package/support/components/conversation-timeline.d.ts +5 -0
- package/support/components/conversation-timeline.d.ts.map +1 -1
- package/support/components/conversation-timeline.js +25 -5
- package/support/components/conversation-timeline.js.map +1 -1
- package/support/components/header.js +1 -1
- package/support/components/image-lightbox.d.ts +49 -0
- package/support/components/image-lightbox.d.ts.map +1 -0
- package/support/components/image-lightbox.js +142 -0
- package/support/components/image-lightbox.js.map +1 -0
- package/support/components/index.d.ts +5 -4
- package/support/components/index.js +4 -4
- package/support/components/multimodal-input.d.ts +4 -1
- package/support/components/multimodal-input.d.ts.map +1 -1
- package/support/components/multimodal-input.js +71 -45
- package/support/components/multimodal-input.js.map +1 -1
- package/support/components/navigation-tab.js +1 -1
- package/support/components/root.d.ts +23 -0
- package/support/components/root.d.ts.map +1 -0
- package/support/components/root.js +36 -0
- package/support/components/root.js.map +1 -0
- package/support/components/timeline-message-item.d.ts.map +1 -1
- package/support/components/timeline-message-item.js +82 -18
- package/support/components/timeline-message-item.js.map +1 -1
- package/support/components/trigger.d.ts +14 -0
- package/support/components/trigger.d.ts.map +1 -0
- package/support/components/{bubble.js → trigger.js} +16 -12
- package/support/components/trigger.js.map +1 -0
- package/support/components/typing-indicator.d.ts.map +1 -1
- package/support/components/typing-indicator.js +1 -0
- package/support/components/typing-indicator.js.map +1 -1
- package/support/context/controlled-state.d.ts +46 -0
- package/support/context/controlled-state.d.ts.map +1 -0
- package/support/context/controlled-state.js +34 -0
- package/support/context/controlled-state.js.map +1 -0
- package/support/context/events.d.ts +103 -0
- package/support/context/events.d.ts.map +1 -0
- package/support/context/events.js +139 -0
- package/support/context/events.js.map +1 -0
- package/support/context/handle.d.ts +90 -0
- package/support/context/handle.d.ts.map +1 -0
- package/support/context/handle.js +79 -0
- package/support/context/handle.js.map +1 -0
- package/support/context/positioning.d.ts +17 -0
- package/support/context/positioning.d.ts.map +1 -0
- package/support/context/positioning.js +26 -0
- package/support/context/positioning.js.map +1 -0
- package/support/context/slots.d.ts +85 -0
- package/support/context/slots.d.ts.map +1 -0
- package/support/context/slots.js +115 -0
- package/support/context/slots.js.map +1 -0
- package/support/context/websocket.d.ts +8 -1
- package/support/context/websocket.d.ts.map +1 -1
- package/support/context/websocket.js +8 -1
- package/support/context/websocket.js.map +1 -1
- package/support/index.d.ts +239 -54
- package/support/index.d.ts.map +1 -1
- package/support/index.js +254 -33
- package/support/index.js.map +1 -1
- package/support/pages/articles.d.ts.map +1 -1
- package/support/pages/articles.js +3 -4
- package/support/pages/articles.js.map +1 -1
- package/support/pages/conversation-history.js +2 -2
- package/support/pages/conversation.js +6 -5
- package/support/pages/conversation.js.map +1 -1
- package/support/pages/home.js +2 -2
- package/support/router.d.ts +52 -12
- package/support/router.d.ts.map +1 -1
- package/support/router.js +78 -30
- package/support/router.js.map +1 -1
- package/support/store/index.d.ts +2 -2
- package/support/store/support-store.d.ts +26 -20
- package/support/store/support-store.d.ts.map +1 -1
- package/support/store/support-store.js +47 -6
- package/support/store/support-store.js.map +1 -1
- package/support/{support-D2EgfIts.css → support-C7Xaw-N6.css} +1 -2
- package/support/support-C7Xaw-N6.css.map +1 -0
- package/support/text/index.d.ts +1 -1
- package/support/text/index.d.ts.map +1 -1
- package/support/text/index.js.map +1 -1
- package/support/types.d.ts +75 -12
- package/support/types.d.ts.map +1 -1
- package/support.css +2 -2
- package/tailwind.css +0 -1
- package/timeline-item.d.ts +68 -2
- package/timeline-item.d.ts.map +1 -1
- package/to-json-schema.d.ts +96 -0
- package/to-json-schema.d.ts.map +1 -0
- package/util.d.ts +6 -2
- package/util.d.ts.map +1 -1
- package/utils/index.d.ts +2 -1
- package/utils/index.js +2 -1
- package/utils/merge-refs.d.ts +30 -0
- package/utils/merge-refs.d.ts.map +1 -0
- package/utils/merge-refs.js +46 -0
- package/utils/merge-refs.js.map +1 -0
- package/utils/use-render-element.d.ts.map +1 -1
- package/utils/use-render-element.js +36 -8
- package/utils/use-render-element.js.map +1 -1
- package/versions.d.ts +2 -2
- package/versions.d.ts.map +1 -1
- package/zod-extensions.d.ts +1 -1
- package/zod-extensions.d.ts.map +1 -1
- package/primitives/bubble.d.ts +0 -38
- package/primitives/bubble.d.ts.map +0 -1
- package/primitives/bubble.js +0 -57
- package/primitives/bubble.js.map +0 -1
- package/support/components/bubble.d.ts +0 -10
- package/support/components/bubble.d.ts.map +0 -1
- package/support/components/bubble.js.map +0 -1
- package/support/components/container.d.ts +0 -13
- package/support/components/container.d.ts.map +0 -1
- package/support/components/container.js +0 -109
- package/support/components/container.js.map +0 -1
- package/support/components/support-content.d.ts +0 -22
- package/support/components/support-content.d.ts.map +0 -1
- package/support/components/support-content.js +0 -48
- package/support/components/support-content.js.map +0 -1
- package/support/support-D2EgfIts.css.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conversation-timeline.js","names":["EMPTY_SEEN_BY_IDS: readonly string[]","EMPTY_SEEN_BY_NAMES: readonly string[]","ConversationTimelineList: React.FC<ConversationTimelineProps>","names: string[]","PrimitiveConversationTimeline"],"sources":["../../../src/support/components/conversation-timeline.tsx"],"sourcesContent":["import type { AvailableAIAgent, AvailableHumanAgent } from \"@cossistant/types\";\nimport type {\n\tTimelineItem,\n\tTimelinePartEvent,\n} from \"@cossistant/types/api/timeline-item\";\nimport type React from \"react\";\nimport { useCallback, useMemo } from \"react\";\nimport { useConversationTimeline } from \"../../hooks/use-conversation-timeline\";\nimport { useTypingSound } from \"../../hooks/use-typing-sound\";\nimport {\n\tConversationTimelineContainer,\n\tConversationTimeline as PrimitiveConversationTimeline,\n} from \"../../primitives/conversation-timeline\";\nimport { cn } from \"../utils\";\nimport { ConversationEvent } from \"./conversation-event\";\nimport { TimelineMessageGroup } from \"./timeline-message-group\";\nimport { TypingIndicator, type TypingParticipant } from \"./typing-indicator\";\n\n// Helper to extract event part from timeline item\nfunction extractEventPart(item: TimelineItem): TimelinePartEvent | null {\n\tif (item.type !== \"event\") {\n\t\treturn null;\n\t}\n\n\tconst eventPart = item.parts.find(\n\t\t(part): part is TimelinePartEvent => part.type === \"event\"\n\t);\n\n\treturn eventPart || null;\n}\n\nconst EMPTY_SEEN_BY_IDS: readonly string[] = Object.freeze([]);\nconst EMPTY_SEEN_BY_NAMES: readonly string[] = Object.freeze([]);\n\nexport type ConversationTimelineToolProps = {\n\titem: TimelineItem;\n\tconversationId: string;\n};\n\nexport type ConversationTimelineToolDefinition = {\n\tcomponent: React.ComponentType<ConversationTimelineToolProps>;\n};\n\nexport type ConversationTimelineTools = Record<\n\tstring,\n\tConversationTimelineToolDefinition\n>;\n\nexport type ConversationTimelineProps = {\n\tconversationId: string;\n\titems: TimelineItem[];\n\tclassName?: string;\n\tavailableAIAgents: AvailableAIAgent[];\n\tavailableHumanAgents: AvailableHumanAgent[];\n\tcurrentVisitorId?: string;\n\ttools?: ConversationTimelineTools;\n};\n\nexport const ConversationTimelineList: React.FC<ConversationTimelineProps> = ({\n\tconversationId,\n\titems: timelineItems,\n\tclassName,\n\tavailableAIAgents = [],\n\tavailableHumanAgents = [],\n\tcurrentVisitorId,\n\ttools,\n}) => {\n\tconst timeline = useConversationTimeline({\n\t\tconversationId,\n\t\titems: timelineItems,\n\t\tcurrentVisitorId,\n\t});\n\n\tconst typingIndicatorParticipants = useMemo(\n\t\t() =>\n\t\t\ttimeline.typingParticipants.map<TypingParticipant>((participant) => ({\n\t\t\t\tid: participant.id,\n\t\t\t\ttype: participant.type,\n\t\t\t})),\n\t\t[timeline.typingParticipants]\n\t);\n\n\t// Play typing sound when someone is typing\n\tuseTypingSound(typingIndicatorParticipants.length > 0, {\n\t\tvolume: 1,\n\t\tplaybackRate: 1.3,\n\t});\n\n\tconst seenNameLookup = useMemo(() => {\n\t\tconst map = new Map<string, string>();\n\n\t\tfor (const agent of availableHumanAgents) {\n\t\t\tif (agent.name) {\n\t\t\t\tmap.set(agent.id, agent.name);\n\t\t\t}\n\t\t}\n\n\t\tfor (const agent of availableAIAgents) {\n\t\t\tif (agent.name) {\n\t\t\t\tmap.set(agent.id, agent.name);\n\t\t\t}\n\t\t}\n\n\t\treturn map;\n\t}, [availableHumanAgents, availableAIAgents]);\n\n\tconst getSeenByNames = useCallback(\n\t\t(ids: readonly string[] = EMPTY_SEEN_BY_IDS): readonly string[] => {\n\t\t\tif (ids.length === 0 || seenNameLookup.size === 0) {\n\t\t\t\treturn EMPTY_SEEN_BY_NAMES;\n\t\t\t}\n\n\t\t\tconst uniqueNames = new Set<string>();\n\t\t\tconst names: string[] = [];\n\n\t\t\tfor (const id of ids) {\n\t\t\t\tconst name = seenNameLookup.get(id);\n\t\t\t\tif (!name || uniqueNames.has(name)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tuniqueNames.add(name);\n\t\t\t\tnames.push(name);\n\t\t\t}\n\n\t\t\tif (names.length === 0) {\n\t\t\t\treturn EMPTY_SEEN_BY_NAMES;\n\t\t\t}\n\n\t\t\treturn Object.freeze(names);\n\t\t},\n\t\t[seenNameLookup]\n\t);\n\n\treturn (\n\t\t<PrimitiveConversationTimeline\n\t\t\tautoScroll={true}\n\t\t\tclassName={cn(\n\t\t\t\t\"overflow-y-scroll scroll-smooth px-3 py-6\",\n\t\t\t\t\"co-scrollbar-thin\",\n\t\t\t\t\"h-full w-full\",\n\t\t\t\tclassName\n\t\t\t)}\n\t\t\tid=\"conversation-timeline\"\n\t\t\titems={timelineItems}\n\t\t>\n\t\t\t<ConversationTimelineContainer className=\"flex min-h-full w-full flex-col gap-3\">\n\t\t\t\t{timeline.groupedMessages.items.map((item, index) => {\n\t\t\t\t\tif (item.type === \"timeline_event\") {\n\t\t\t\t\t\t// Extract event data from parts\n\t\t\t\t\t\tconst eventPart = extractEventPart(item.item);\n\n\t\t\t\t\t\t// Only render if we have valid event data\n\t\t\t\t\t\tif (!eventPart) {\n\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t<ConversationEvent\n\t\t\t\t\t\t\t\tavailableAIAgents={availableAIAgents}\n\t\t\t\t\t\t\t\tavailableHumanAgents={availableHumanAgents}\n\t\t\t\t\t\t\t\tcreatedAt={item.item.createdAt}\n\t\t\t\t\t\t\t\tevent={eventPart}\n\t\t\t\t\t\t\t\tkey={item.item.id ?? `timeline-event-${item.item.createdAt}`}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (item.type === \"timeline_tool\") {\n\t\t\t\t\t\tconst toolName = item.tool ?? item.item.tool ?? item.item.type;\n\t\t\t\t\t\tconst toolDefinition = toolName ? tools?.[toolName] : undefined;\n\n\t\t\t\t\t\tif (!toolDefinition) {\n\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst ToolComponent = toolDefinition.component;\n\n\t\t\t\t\t\tconst toolKey =\n\t\t\t\t\t\t\titem.item.id ?? `${toolName}-${item.item.createdAt}-${index}`;\n\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t<ToolComponent\n\t\t\t\t\t\t\t\tconversationId={conversationId}\n\t\t\t\t\t\t\t\titem={item.item}\n\t\t\t\t\t\t\t\tkey={toolKey}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Only show seen indicator on the LAST message group sent by the visitor\n\t\t\t\t\tconst isLastVisitorGroup =\n\t\t\t\t\t\tindex === timeline.lastVisitorMessageGroupIndex;\n\t\t\t\t\tconst seenByIds =\n\t\t\t\t\t\tisLastVisitorGroup && item.lastMessageId\n\t\t\t\t\t\t\t? timeline.groupedMessages.getMessageSeenBy(item.lastMessageId)\n\t\t\t\t\t\t\t: EMPTY_SEEN_BY_IDS;\n\t\t\t\t\tconst seenByNames =\n\t\t\t\t\t\tseenByIds.length > 0\n\t\t\t\t\t\t\t? getSeenByNames(seenByIds)\n\t\t\t\t\t\t\t: EMPTY_SEEN_BY_NAMES;\n\n\t\t\t\t\t// Use first timeline item ID as stable key\n\t\t\t\t\tconst groupKey =\n\t\t\t\t\t\titem.lastMessageId ??\n\t\t\t\t\t\titem.items?.[0]?.id ??\n\t\t\t\t\t\t`group-${item.items?.[0]?.createdAt ?? index}`;\n\n\t\t\t\t\treturn (\n\t\t\t\t\t\t<TimelineMessageGroup\n\t\t\t\t\t\t\tavailableAIAgents={availableAIAgents}\n\t\t\t\t\t\t\tavailableHumanAgents={availableHumanAgents}\n\t\t\t\t\t\t\tcurrentVisitorId={currentVisitorId}\n\t\t\t\t\t\t\titems={item.items || []}\n\t\t\t\t\t\t\tkey={groupKey}\n\t\t\t\t\t\t\tseenByIds={seenByIds}\n\t\t\t\t\t\t\tseenByNames={seenByNames}\n\t\t\t\t\t\t/>\n\t\t\t\t\t);\n\t\t\t\t})}\n\t\t\t\t<div className=\"h-6 w-full\">\n\t\t\t\t\t{typingIndicatorParticipants.length > 0 ? (\n\t\t\t\t\t\t<TypingIndicator\n\t\t\t\t\t\t\tavailableAIAgents={availableAIAgents}\n\t\t\t\t\t\t\tavailableHumanAgents={availableHumanAgents}\n\t\t\t\t\t\t\tclassName=\"mt-2\"\n\t\t\t\t\t\t\tparticipants={typingIndicatorParticipants}\n\t\t\t\t\t\t/>\n\t\t\t\t\t) : null}\n\t\t\t\t</div>\n\t\t\t</ConversationTimelineContainer>\n\t\t</PrimitiveConversationTimeline>\n\t);\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"conversation-timeline.js","names":["EMPTY_SEEN_BY_IDS: readonly string[]","EMPTY_SEEN_BY_NAMES: readonly string[]","ConversationTimelineList: React.FC<ConversationTimelineProps>","names: string[]","PrimitiveConversationTimeline"],"sources":["../../../src/support/components/conversation-timeline.tsx"],"sourcesContent":["import type { AvailableAIAgent, AvailableHumanAgent } from \"@cossistant/types\";\nimport type {\n\tTimelineItem,\n\tTimelinePartEvent,\n} from \"@cossistant/types/api/timeline-item\";\nimport type React from \"react\";\nimport { useCallback, useMemo } from \"react\";\nimport type { DaySeparatorItem } from \"../../hooks/private/use-grouped-messages\";\nimport { useConversationTimeline } from \"../../hooks/use-conversation-timeline\";\nimport { useTypingSound } from \"../../hooks/use-typing-sound\";\nimport {\n\tConversationTimelineContainer,\n\tConversationTimeline as PrimitiveConversationTimeline,\n} from \"../../primitives/conversation-timeline\";\nimport {\n\tDaySeparator,\n\tDaySeparatorLabel,\n\tDaySeparatorLine,\n\tdefaultFormatDate,\n} from \"../../primitives/day-separator\";\nimport { cn } from \"../utils\";\nimport { ConversationEvent } from \"./conversation-event\";\nimport { TimelineMessageGroup } from \"./timeline-message-group\";\nimport { TypingIndicator, type TypingParticipant } from \"./typing-indicator\";\n\n// Helper to extract event part from timeline item\nfunction extractEventPart(item: TimelineItem): TimelinePartEvent | null {\n\tif (item.type !== \"event\") {\n\t\treturn null;\n\t}\n\n\tconst eventPart = item.parts.find(\n\t\t(part): part is TimelinePartEvent => part.type === \"event\"\n\t);\n\n\treturn eventPart || null;\n}\n\nconst EMPTY_SEEN_BY_IDS: readonly string[] = Object.freeze([]);\nconst EMPTY_SEEN_BY_NAMES: readonly string[] = Object.freeze([]);\n\nexport type ConversationTimelineToolProps = {\n\titem: TimelineItem;\n\tconversationId: string;\n};\n\nexport type ConversationTimelineToolDefinition = {\n\tcomponent: React.ComponentType<ConversationTimelineToolProps>;\n};\n\nexport type ConversationTimelineTools = Record<\n\tstring,\n\tConversationTimelineToolDefinition\n>;\n\nexport type ConversationTimelineProps = {\n\tconversationId: string;\n\titems: TimelineItem[];\n\tclassName?: string;\n\tavailableAIAgents: AvailableAIAgent[];\n\tavailableHumanAgents: AvailableHumanAgent[];\n\tcurrentVisitorId?: string;\n\ttools?: ConversationTimelineTools;\n\trenderDaySeparator?: (props: {\n\t\titem: DaySeparatorItem;\n\t\tformatDate: (date: Date) => string;\n\t}) => React.ReactNode;\n};\n\nexport const ConversationTimelineList: React.FC<ConversationTimelineProps> = ({\n\tconversationId,\n\titems: timelineItems,\n\tclassName,\n\tavailableAIAgents = [],\n\tavailableHumanAgents = [],\n\tcurrentVisitorId,\n\ttools,\n\trenderDaySeparator,\n}) => {\n\tconst timeline = useConversationTimeline({\n\t\tconversationId,\n\t\titems: timelineItems,\n\t\tcurrentVisitorId,\n\t});\n\n\tconst typingIndicatorParticipants = useMemo(\n\t\t() =>\n\t\t\ttimeline.typingParticipants.map<TypingParticipant>((participant) => ({\n\t\t\t\tid: participant.id,\n\t\t\t\ttype: participant.type,\n\t\t\t})),\n\t\t[timeline.typingParticipants]\n\t);\n\n\t// Play typing sound when someone is typing\n\tuseTypingSound(typingIndicatorParticipants.length > 0, {\n\t\tvolume: 1,\n\t\tplaybackRate: 1.3,\n\t});\n\n\tconst seenNameLookup = useMemo(() => {\n\t\tconst map = new Map<string, string>();\n\n\t\tfor (const agent of availableHumanAgents) {\n\t\t\tif (agent.name) {\n\t\t\t\tmap.set(agent.id, agent.name);\n\t\t\t}\n\t\t}\n\n\t\tfor (const agent of availableAIAgents) {\n\t\t\tif (agent.name) {\n\t\t\t\tmap.set(agent.id, agent.name);\n\t\t\t}\n\t\t}\n\n\t\treturn map;\n\t}, [availableHumanAgents, availableAIAgents]);\n\n\tconst getSeenByNames = useCallback(\n\t\t(ids: readonly string[] = EMPTY_SEEN_BY_IDS): readonly string[] => {\n\t\t\tif (ids.length === 0 || seenNameLookup.size === 0) {\n\t\t\t\treturn EMPTY_SEEN_BY_NAMES;\n\t\t\t}\n\n\t\t\tconst uniqueNames = new Set<string>();\n\t\t\tconst names: string[] = [];\n\n\t\t\tfor (const id of ids) {\n\t\t\t\tconst name = seenNameLookup.get(id);\n\t\t\t\tif (!name || uniqueNames.has(name)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tuniqueNames.add(name);\n\t\t\t\tnames.push(name);\n\t\t\t}\n\n\t\t\tif (names.length === 0) {\n\t\t\t\treturn EMPTY_SEEN_BY_NAMES;\n\t\t\t}\n\n\t\t\treturn Object.freeze(names);\n\t\t},\n\t\t[seenNameLookup]\n\t);\n\n\treturn (\n\t\t<PrimitiveConversationTimeline\n\t\t\tautoScroll={true}\n\t\t\tclassName={cn(\n\t\t\t\t\"overflow-y-scroll px-3 py-6\",\n\t\t\t\t\"co-scrollbar-thin\",\n\t\t\t\t\"h-full w-full\",\n\t\t\t\tclassName\n\t\t\t)}\n\t\t\tid=\"conversation-timeline\"\n\t\t\titems={timelineItems}\n\t\t>\n\t\t\t<ConversationTimelineContainer className=\"flex min-h-full w-full flex-col gap-5\">\n\t\t\t\t{timeline.groupedMessages.items.map((item, index) => {\n\t\t\t\t\tif (item.type === \"day_separator\") {\n\t\t\t\t\t\t// Render day separator - allow custom rendering via prop\n\t\t\t\t\t\tif (renderDaySeparator) {\n\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t<div key={`day-separator-${item.dateString}`}>\n\t\t\t\t\t\t\t\t\t{renderDaySeparator({\n\t\t\t\t\t\t\t\t\t\titem,\n\t\t\t\t\t\t\t\t\t\tformatDate: defaultFormatDate,\n\t\t\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Default day separator using the primitive\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t<DaySeparator\n\t\t\t\t\t\t\t\tclassName=\"flex items-center justify-center py-2\"\n\t\t\t\t\t\t\t\tdate={item.date}\n\t\t\t\t\t\t\t\tdateString={item.dateString}\n\t\t\t\t\t\t\t\tkey={`day-separator-${item.dateString}`}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{({ formattedDate }) => (\n\t\t\t\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\t\t\t<DaySeparatorLine className=\"flex-1 border-gray-200 border-t dark:border-gray-700\" />\n\t\t\t\t\t\t\t\t\t\t<DaySeparatorLabel\n\t\t\t\t\t\t\t\t\t\t\tclassName=\"px-3 text-gray-500 text-xs dark:text-gray-400\"\n\t\t\t\t\t\t\t\t\t\t\tformattedDate={formattedDate}\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t<DaySeparatorLine className=\"flex-1 border-gray-200 border-t dark:border-gray-700\" />\n\t\t\t\t\t\t\t\t\t</>\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t</DaySeparator>\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (item.type === \"timeline_event\") {\n\t\t\t\t\t\t// Extract event data from parts\n\t\t\t\t\t\tconst eventPart = extractEventPart(item.item);\n\n\t\t\t\t\t\t// Only render if we have valid event data\n\t\t\t\t\t\tif (!eventPart) {\n\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t<ConversationEvent\n\t\t\t\t\t\t\t\tavailableAIAgents={availableAIAgents}\n\t\t\t\t\t\t\t\tavailableHumanAgents={availableHumanAgents}\n\t\t\t\t\t\t\t\tcreatedAt={item.item.createdAt}\n\t\t\t\t\t\t\t\tevent={eventPart}\n\t\t\t\t\t\t\t\tkey={item.item.id ?? `timeline-event-${item.item.createdAt}`}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (item.type === \"timeline_tool\") {\n\t\t\t\t\t\tconst toolName = item.tool ?? item.item.tool ?? item.item.type;\n\t\t\t\t\t\tconst toolDefinition = toolName ? tools?.[toolName] : undefined;\n\n\t\t\t\t\t\tif (!toolDefinition) {\n\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst ToolComponent = toolDefinition.component;\n\n\t\t\t\t\t\tconst toolKey =\n\t\t\t\t\t\t\titem.item.id ?? `${toolName}-${item.item.createdAt}-${index}`;\n\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t<ToolComponent\n\t\t\t\t\t\t\t\tconversationId={conversationId}\n\t\t\t\t\t\t\t\titem={item.item}\n\t\t\t\t\t\t\t\tkey={toolKey}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Only show seen indicator on the LAST message group sent by the visitor\n\t\t\t\t\tconst isLastVisitorGroup =\n\t\t\t\t\t\tindex === timeline.lastVisitorMessageGroupIndex;\n\t\t\t\t\tconst seenByIds =\n\t\t\t\t\t\tisLastVisitorGroup && item.lastMessageId\n\t\t\t\t\t\t\t? timeline.groupedMessages.getMessageSeenBy(item.lastMessageId)\n\t\t\t\t\t\t\t: EMPTY_SEEN_BY_IDS;\n\t\t\t\t\tconst seenByNames =\n\t\t\t\t\t\tseenByIds.length > 0\n\t\t\t\t\t\t\t? getSeenByNames(seenByIds)\n\t\t\t\t\t\t\t: EMPTY_SEEN_BY_NAMES;\n\n\t\t\t\t\t// Use first timeline item ID as stable key\n\t\t\t\t\tconst groupKey =\n\t\t\t\t\t\titem.lastMessageId ??\n\t\t\t\t\t\titem.items?.[0]?.id ??\n\t\t\t\t\t\t`group-${item.items?.[0]?.createdAt ?? index}`;\n\n\t\t\t\t\treturn (\n\t\t\t\t\t\t<TimelineMessageGroup\n\t\t\t\t\t\t\tavailableAIAgents={availableAIAgents}\n\t\t\t\t\t\t\tavailableHumanAgents={availableHumanAgents}\n\t\t\t\t\t\t\tcurrentVisitorId={currentVisitorId}\n\t\t\t\t\t\t\titems={item.items || []}\n\t\t\t\t\t\t\tkey={groupKey}\n\t\t\t\t\t\t\tseenByIds={seenByIds}\n\t\t\t\t\t\t\tseenByNames={seenByNames}\n\t\t\t\t\t\t/>\n\t\t\t\t\t);\n\t\t\t\t})}\n\t\t\t\t<div className=\"h-6 w-full\">\n\t\t\t\t\t{typingIndicatorParticipants.length > 0 ? (\n\t\t\t\t\t\t<TypingIndicator\n\t\t\t\t\t\t\tavailableAIAgents={availableAIAgents}\n\t\t\t\t\t\t\tavailableHumanAgents={availableHumanAgents}\n\t\t\t\t\t\t\tclassName=\"mt-2\"\n\t\t\t\t\t\t\tparticipants={typingIndicatorParticipants}\n\t\t\t\t\t\t/>\n\t\t\t\t\t) : null}\n\t\t\t\t</div>\n\t\t\t</ConversationTimelineContainer>\n\t\t</PrimitiveConversationTimeline>\n\t);\n};\n"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { cn } from "../utils/index.js";
|
|
2
2
|
import { useSupportConfig } from "../store/support-store.js";
|
|
3
|
-
import { CoButton } from "./button.js";
|
|
4
3
|
import icons_default from "./icons.js";
|
|
4
|
+
import { CoButton } from "./button.js";
|
|
5
5
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
6
|
|
|
7
7
|
//#region src/support/components/header.tsx
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { TimelinePartImage } from "../../timeline-item.js";
|
|
2
|
+
import * as React$1 from "react";
|
|
3
|
+
|
|
4
|
+
//#region src/support/components/image-lightbox.d.ts
|
|
5
|
+
type ImageLightboxProps = {
|
|
6
|
+
/**
|
|
7
|
+
* Array of images to display in the lightbox.
|
|
8
|
+
*/
|
|
9
|
+
images: TimelinePartImage[];
|
|
10
|
+
/**
|
|
11
|
+
* Index of the initially selected image.
|
|
12
|
+
*/
|
|
13
|
+
initialIndex?: number;
|
|
14
|
+
/**
|
|
15
|
+
* Whether the lightbox is open.
|
|
16
|
+
*/
|
|
17
|
+
isOpen: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Callback when the lightbox should close.
|
|
20
|
+
*/
|
|
21
|
+
onClose: () => void;
|
|
22
|
+
/**
|
|
23
|
+
* Optional className for the overlay.
|
|
24
|
+
*/
|
|
25
|
+
className?: string;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Simple image lightbox/modal for viewing full-size images.
|
|
29
|
+
* Supports keyboard navigation (Escape to close, Arrow keys to navigate).
|
|
30
|
+
*/
|
|
31
|
+
declare function ImageLightbox({
|
|
32
|
+
images,
|
|
33
|
+
initialIndex,
|
|
34
|
+
isOpen,
|
|
35
|
+
onClose,
|
|
36
|
+
className
|
|
37
|
+
}: ImageLightboxProps): React$1.ReactElement | null;
|
|
38
|
+
/**
|
|
39
|
+
* Hook to manage lightbox state.
|
|
40
|
+
*/
|
|
41
|
+
declare function useLightbox(): {
|
|
42
|
+
isOpen: boolean;
|
|
43
|
+
selectedIndex: number;
|
|
44
|
+
openLightbox: (index?: number) => void;
|
|
45
|
+
closeLightbox: () => void;
|
|
46
|
+
};
|
|
47
|
+
//#endregion
|
|
48
|
+
export { ImageLightbox, ImageLightboxProps, useLightbox };
|
|
49
|
+
//# sourceMappingURL=image-lightbox.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-lightbox.d.ts","names":[],"sources":["../../../src/support/components/image-lightbox.tsx"],"sourcesContent":[],"mappings":";;;;KASY,kBAAA;;AAAZ;AA2BA;EACC,MAAA,EAxBQ,iBAwBR,EAAA;EACA;;;EAGA,YAAA,CAAA,EAAA,MAAA;EACE;;;EAuJa,MAAA,EAAA,OAAW;;;;;;;;;;;;;;iBA7JX,aAAA;;;;;;GAMb,qBAAqB,OAAA,CAAM;;;;iBAuJd,WAAA,CAAA"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
import { cn } from "../utils/index.js";
|
|
5
|
+
import icons_default from "./icons.js";
|
|
6
|
+
import { useCallback, useEffect, useState } from "react";
|
|
7
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
import { createPortal } from "react-dom";
|
|
9
|
+
|
|
10
|
+
//#region src/support/components/image-lightbox.tsx
|
|
11
|
+
/**
|
|
12
|
+
* Simple image lightbox/modal for viewing full-size images.
|
|
13
|
+
* Supports keyboard navigation (Escape to close, Arrow keys to navigate).
|
|
14
|
+
*/
|
|
15
|
+
function ImageLightbox({ images, initialIndex = 0, isOpen, onClose, className }) {
|
|
16
|
+
const [currentIndex, setCurrentIndex] = useState(initialIndex);
|
|
17
|
+
const [mounted, setMounted] = useState(false);
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
setMounted(true);
|
|
20
|
+
}, []);
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (isOpen) setCurrentIndex(initialIndex);
|
|
23
|
+
}, [isOpen, initialIndex]);
|
|
24
|
+
const handleKeyDown = useCallback((event) => {
|
|
25
|
+
if (!isOpen) return;
|
|
26
|
+
switch (event.key) {
|
|
27
|
+
case "Escape":
|
|
28
|
+
onClose();
|
|
29
|
+
break;
|
|
30
|
+
case "ArrowLeft":
|
|
31
|
+
setCurrentIndex((prev) => prev > 0 ? prev - 1 : images.length - 1);
|
|
32
|
+
break;
|
|
33
|
+
case "ArrowRight":
|
|
34
|
+
setCurrentIndex((prev) => prev < images.length - 1 ? prev + 1 : 0);
|
|
35
|
+
break;
|
|
36
|
+
default: break;
|
|
37
|
+
}
|
|
38
|
+
}, [
|
|
39
|
+
isOpen,
|
|
40
|
+
images.length,
|
|
41
|
+
onClose
|
|
42
|
+
]);
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
45
|
+
return () => {
|
|
46
|
+
document.removeEventListener("keydown", handleKeyDown);
|
|
47
|
+
};
|
|
48
|
+
}, [handleKeyDown]);
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
if (isOpen) document.body.style.overflow = "hidden";
|
|
51
|
+
else document.body.style.overflow = "";
|
|
52
|
+
return () => {
|
|
53
|
+
document.body.style.overflow = "";
|
|
54
|
+
};
|
|
55
|
+
}, [isOpen]);
|
|
56
|
+
if (!(mounted && isOpen) || images.length === 0) return null;
|
|
57
|
+
const currentImage = images[currentIndex];
|
|
58
|
+
const hasMultiple = images.length > 1;
|
|
59
|
+
const handlePrevious = () => {
|
|
60
|
+
setCurrentIndex((prev) => prev > 0 ? prev - 1 : images.length - 1);
|
|
61
|
+
};
|
|
62
|
+
const handleNext = () => {
|
|
63
|
+
setCurrentIndex((prev) => prev < images.length - 1 ? prev + 1 : 0);
|
|
64
|
+
};
|
|
65
|
+
const handleBackdropClick = (event) => {
|
|
66
|
+
if (event.target === event.currentTarget) onClose();
|
|
67
|
+
};
|
|
68
|
+
return createPortal(/* @__PURE__ */ jsxs("div", {
|
|
69
|
+
"aria-label": "Image viewer",
|
|
70
|
+
"aria-modal": "true",
|
|
71
|
+
className: cn("fixed inset-0 z-[99999] flex items-center justify-center bg-black/90 p-4", className),
|
|
72
|
+
onClick: handleBackdropClick,
|
|
73
|
+
onKeyDown: (e) => e.key === "Escape" && onClose(),
|
|
74
|
+
role: "dialog",
|
|
75
|
+
children: [
|
|
76
|
+
/* @__PURE__ */ jsx("button", {
|
|
77
|
+
"aria-label": "Close lightbox",
|
|
78
|
+
className: "absolute top-4 right-4 flex h-10 w-10 items-center justify-center rounded-full bg-white/10 text-white transition-colors hover:bg-white/20 focus:outline-none focus:ring-2 focus:ring-white/50",
|
|
79
|
+
onClick: onClose,
|
|
80
|
+
type: "button",
|
|
81
|
+
children: /* @__PURE__ */ jsx(icons_default, {
|
|
82
|
+
className: "h-6 w-6",
|
|
83
|
+
name: "close"
|
|
84
|
+
})
|
|
85
|
+
}),
|
|
86
|
+
hasMultiple && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("button", {
|
|
87
|
+
"aria-label": "Previous image",
|
|
88
|
+
className: "absolute left-4 flex h-10 w-10 items-center justify-center rounded-full bg-white/10 text-white transition-colors hover:bg-white/20 focus:outline-none focus:ring-2 focus:ring-white/50",
|
|
89
|
+
onClick: handlePrevious,
|
|
90
|
+
type: "button",
|
|
91
|
+
children: /* @__PURE__ */ jsx(icons_default, {
|
|
92
|
+
className: "h-6 w-6",
|
|
93
|
+
name: "arrow-left"
|
|
94
|
+
})
|
|
95
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
96
|
+
"aria-label": "Next image",
|
|
97
|
+
className: "absolute right-4 flex h-10 w-10 items-center justify-center rounded-full bg-white/10 text-white transition-colors hover:bg-white/20 focus:outline-none focus:ring-2 focus:ring-white/50",
|
|
98
|
+
onClick: handleNext,
|
|
99
|
+
type: "button",
|
|
100
|
+
children: /* @__PURE__ */ jsx(icons_default, {
|
|
101
|
+
className: "h-6 w-6",
|
|
102
|
+
name: "arrow-right"
|
|
103
|
+
})
|
|
104
|
+
})] }),
|
|
105
|
+
/* @__PURE__ */ jsx("img", {
|
|
106
|
+
alt: currentImage?.fileName || `Image ${currentIndex + 1}`,
|
|
107
|
+
className: "max-h-[90vh] max-w-[90vw] object-contain",
|
|
108
|
+
src: currentImage?.url
|
|
109
|
+
}),
|
|
110
|
+
hasMultiple && /* @__PURE__ */ jsxs("div", {
|
|
111
|
+
className: "-translate-x-1/2 absolute bottom-4 left-1/2 rounded-full bg-black/50 px-3 py-1 text-sm text-white",
|
|
112
|
+
children: [
|
|
113
|
+
currentIndex + 1,
|
|
114
|
+
" / ",
|
|
115
|
+
images.length
|
|
116
|
+
]
|
|
117
|
+
})
|
|
118
|
+
]
|
|
119
|
+
}), document.body);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Hook to manage lightbox state.
|
|
123
|
+
*/
|
|
124
|
+
function useLightbox() {
|
|
125
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
126
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
127
|
+
return {
|
|
128
|
+
isOpen,
|
|
129
|
+
selectedIndex,
|
|
130
|
+
openLightbox: useCallback((index = 0) => {
|
|
131
|
+
setSelectedIndex(index);
|
|
132
|
+
setIsOpen(true);
|
|
133
|
+
}, []),
|
|
134
|
+
closeLightbox: useCallback(() => {
|
|
135
|
+
setIsOpen(false);
|
|
136
|
+
}, [])
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
//#endregion
|
|
141
|
+
export { ImageLightbox, useLightbox };
|
|
142
|
+
//# sourceMappingURL=image-lightbox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-lightbox.js","names":["Icon"],"sources":["../../../src/support/components/image-lightbox.tsx"],"sourcesContent":["\"use client\";\n\nimport type { TimelinePartImage } from \"@cossistant/types/api/timeline-item\";\nimport type * as React from \"react\";\nimport { useCallback, useEffect, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { cn } from \"../utils\";\nimport Icon from \"./icons\";\n\nexport type ImageLightboxProps = {\n\t/**\n\t * Array of images to display in the lightbox.\n\t */\n\timages: TimelinePartImage[];\n\t/**\n\t * Index of the initially selected image.\n\t */\n\tinitialIndex?: number;\n\t/**\n\t * Whether the lightbox is open.\n\t */\n\tisOpen: boolean;\n\t/**\n\t * Callback when the lightbox should close.\n\t */\n\tonClose: () => void;\n\t/**\n\t * Optional className for the overlay.\n\t */\n\tclassName?: string;\n};\n\n/**\n * Simple image lightbox/modal for viewing full-size images.\n * Supports keyboard navigation (Escape to close, Arrow keys to navigate).\n */\nexport function ImageLightbox({\n\timages,\n\tinitialIndex = 0,\n\tisOpen,\n\tonClose,\n\tclassName,\n}: ImageLightboxProps): React.ReactElement | null {\n\tconst [currentIndex, setCurrentIndex] = useState(initialIndex);\n\tconst [mounted, setMounted] = useState(false);\n\n\t// SSR safety: only render portal after component mounts on client\n\tuseEffect(() => {\n\t\tsetMounted(true);\n\t}, []);\n\n\t// Reset index when lightbox opens with new initial index\n\tuseEffect(() => {\n\t\tif (isOpen) {\n\t\t\tsetCurrentIndex(initialIndex);\n\t\t}\n\t}, [isOpen, initialIndex]);\n\n\t// Handle keyboard navigation\n\tconst handleKeyDown = useCallback(\n\t\t(event: KeyboardEvent) => {\n\t\t\tif (!isOpen) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tswitch (event.key) {\n\t\t\t\tcase \"Escape\":\n\t\t\t\t\tonClose();\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"ArrowLeft\":\n\t\t\t\t\tsetCurrentIndex((prev) => (prev > 0 ? prev - 1 : images.length - 1));\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"ArrowRight\":\n\t\t\t\t\tsetCurrentIndex((prev) => (prev < images.length - 1 ? prev + 1 : 0));\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t},\n\t\t[isOpen, images.length, onClose]\n\t);\n\n\tuseEffect(() => {\n\t\tdocument.addEventListener(\"keydown\", handleKeyDown);\n\t\treturn () => {\n\t\t\tdocument.removeEventListener(\"keydown\", handleKeyDown);\n\t\t};\n\t}, [handleKeyDown]);\n\n\t// Prevent body scroll when lightbox is open\n\tuseEffect(() => {\n\t\tif (isOpen) {\n\t\t\tdocument.body.style.overflow = \"hidden\";\n\t\t} else {\n\t\t\tdocument.body.style.overflow = \"\";\n\t\t}\n\t\treturn () => {\n\t\t\tdocument.body.style.overflow = \"\";\n\t\t};\n\t}, [isOpen]);\n\n\t// Don't render until mounted (SSR safety) or if not open/no images\n\tif (!(mounted && isOpen) || images.length === 0) {\n\t\treturn null;\n\t}\n\n\tconst currentImage = images[currentIndex];\n\tconst hasMultiple = images.length > 1;\n\n\tconst handlePrevious = () => {\n\t\tsetCurrentIndex((prev) => (prev > 0 ? prev - 1 : images.length - 1));\n\t};\n\n\tconst handleNext = () => {\n\t\tsetCurrentIndex((prev) => (prev < images.length - 1 ? prev + 1 : 0));\n\t};\n\n\tconst handleBackdropClick = (event: React.MouseEvent) => {\n\t\tif (event.target === event.currentTarget) {\n\t\t\tonClose();\n\t\t}\n\t};\n\n\t// Render via portal to document.body to escape any CSS containing blocks\n\t// (e.g., transforms on widget containers that break position: fixed)\n\treturn createPortal(\n\t\t// biome-ignore lint/a11y/noNoninteractiveElementInteractions: Dialog backdrop needs click handler for closing\n\t\t<div\n\t\t\taria-label=\"Image viewer\"\n\t\t\taria-modal=\"true\"\n\t\t\tclassName={cn(\n\t\t\t\t\"fixed inset-0 z-[99999] flex items-center justify-center bg-black/90 p-4\",\n\t\t\t\tclassName\n\t\t\t)}\n\t\t\tonClick={handleBackdropClick}\n\t\t\tonKeyDown={(e) => e.key === \"Escape\" && onClose()}\n\t\t\trole=\"dialog\"\n\t\t>\n\t\t\t{/* Close button */}\n\t\t\t<button\n\t\t\t\taria-label=\"Close lightbox\"\n\t\t\t\tclassName=\"absolute top-4 right-4 flex h-10 w-10 items-center justify-center rounded-full bg-white/10 text-white transition-colors hover:bg-white/20 focus:outline-none focus:ring-2 focus:ring-white/50\"\n\t\t\t\tonClick={onClose}\n\t\t\t\ttype=\"button\"\n\t\t\t>\n\t\t\t\t<Icon className=\"h-6 w-6\" name=\"close\" />\n\t\t\t</button>\n\n\t\t\t{/* Navigation buttons */}\n\t\t\t{hasMultiple && (\n\t\t\t\t<>\n\t\t\t\t\t<button\n\t\t\t\t\t\taria-label=\"Previous image\"\n\t\t\t\t\t\tclassName=\"absolute left-4 flex h-10 w-10 items-center justify-center rounded-full bg-white/10 text-white transition-colors hover:bg-white/20 focus:outline-none focus:ring-2 focus:ring-white/50\"\n\t\t\t\t\t\tonClick={handlePrevious}\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<Icon className=\"h-6 w-6\" name=\"arrow-left\" />\n\t\t\t\t\t</button>\n\t\t\t\t\t<button\n\t\t\t\t\t\taria-label=\"Next image\"\n\t\t\t\t\t\tclassName=\"absolute right-4 flex h-10 w-10 items-center justify-center rounded-full bg-white/10 text-white transition-colors hover:bg-white/20 focus:outline-none focus:ring-2 focus:ring-white/50\"\n\t\t\t\t\t\tonClick={handleNext}\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<Icon className=\"h-6 w-6\" name=\"arrow-right\" />\n\t\t\t\t\t</button>\n\t\t\t\t</>\n\t\t\t)}\n\n\t\t\t{/* Image */}\n\t\t\t{/* biome-ignore lint/performance/noImgElement: React package, not Next.js specific */}\n\t\t\t{/* biome-ignore lint/nursery/useImageSize: Dynamic image dimensions not known at render time */}\n\t\t\t<img\n\t\t\t\talt={currentImage?.fileName || `Image ${currentIndex + 1}`}\n\t\t\t\tclassName=\"max-h-[90vh] max-w-[90vw] object-contain\"\n\t\t\t\tsrc={currentImage?.url}\n\t\t\t/>\n\n\t\t\t{/* Image counter */}\n\t\t\t{hasMultiple && (\n\t\t\t\t<div className=\"-translate-x-1/2 absolute bottom-4 left-1/2 rounded-full bg-black/50 px-3 py-1 text-sm text-white\">\n\t\t\t\t\t{currentIndex + 1} / {images.length}\n\t\t\t\t</div>\n\t\t\t)}\n\t\t</div>,\n\t\tdocument.body\n\t);\n}\n\n/**\n * Hook to manage lightbox state.\n */\nexport function useLightbox() {\n\tconst [isOpen, setIsOpen] = useState(false);\n\tconst [selectedIndex, setSelectedIndex] = useState(0);\n\n\tconst openLightbox = useCallback((index = 0) => {\n\t\tsetSelectedIndex(index);\n\t\tsetIsOpen(true);\n\t}, []);\n\n\tconst closeLightbox = useCallback(() => {\n\t\tsetIsOpen(false);\n\t}, []);\n\n\treturn {\n\t\tisOpen,\n\t\tselectedIndex,\n\t\topenLightbox,\n\t\tcloseLightbox,\n\t};\n}\n"],"mappings":";;;;;;;;;;;;;;AAoCA,SAAgB,cAAc,EAC7B,QACA,eAAe,GACf,QACA,SACA,aACiD;CACjD,MAAM,CAAC,cAAc,mBAAmB,SAAS,aAAa;CAC9D,MAAM,CAAC,SAAS,cAAc,SAAS,MAAM;AAG7C,iBAAgB;AACf,aAAW,KAAK;IACd,EAAE,CAAC;AAGN,iBAAgB;AACf,MAAI,OACH,iBAAgB,aAAa;IAE5B,CAAC,QAAQ,aAAa,CAAC;CAG1B,MAAM,gBAAgB,aACpB,UAAyB;AACzB,MAAI,CAAC,OACJ;AAGD,UAAQ,MAAM,KAAd;GACC,KAAK;AACJ,aAAS;AACT;GACD,KAAK;AACJ,qBAAiB,SAAU,OAAO,IAAI,OAAO,IAAI,OAAO,SAAS,EAAG;AACpE;GACD,KAAK;AACJ,qBAAiB,SAAU,OAAO,OAAO,SAAS,IAAI,OAAO,IAAI,EAAG;AACpE;GACD,QACC;;IAGH;EAAC;EAAQ,OAAO;EAAQ;EAAQ,CAChC;AAED,iBAAgB;AACf,WAAS,iBAAiB,WAAW,cAAc;AACnD,eAAa;AACZ,YAAS,oBAAoB,WAAW,cAAc;;IAErD,CAAC,cAAc,CAAC;AAGnB,iBAAgB;AACf,MAAI,OACH,UAAS,KAAK,MAAM,WAAW;MAE/B,UAAS,KAAK,MAAM,WAAW;AAEhC,eAAa;AACZ,YAAS,KAAK,MAAM,WAAW;;IAE9B,CAAC,OAAO,CAAC;AAGZ,KAAI,EAAE,WAAW,WAAW,OAAO,WAAW,EAC7C,QAAO;CAGR,MAAM,eAAe,OAAO;CAC5B,MAAM,cAAc,OAAO,SAAS;CAEpC,MAAM,uBAAuB;AAC5B,mBAAiB,SAAU,OAAO,IAAI,OAAO,IAAI,OAAO,SAAS,EAAG;;CAGrE,MAAM,mBAAmB;AACxB,mBAAiB,SAAU,OAAO,OAAO,SAAS,IAAI,OAAO,IAAI,EAAG;;CAGrE,MAAM,uBAAuB,UAA4B;AACxD,MAAI,MAAM,WAAW,MAAM,cAC1B,UAAS;;AAMX,QAAO,aAEN,qBAAC;EACA,cAAW;EACX,cAAW;EACX,WAAW,GACV,4EACA,UACA;EACD,SAAS;EACT,YAAY,MAAM,EAAE,QAAQ,YAAY,SAAS;EACjD,MAAK;;GAGL,oBAAC;IACA,cAAW;IACX,WAAU;IACV,SAAS;IACT,MAAK;cAEL,oBAACA;KAAK,WAAU;KAAU,MAAK;MAAU;KACjC;GAGR,eACA,4CACC,oBAAC;IACA,cAAW;IACX,WAAU;IACV,SAAS;IACT,MAAK;cAEL,oBAACA;KAAK,WAAU;KAAU,MAAK;MAAe;KACtC,EACT,oBAAC;IACA,cAAW;IACX,WAAU;IACV,SAAS;IACT,MAAK;cAEL,oBAACA;KAAK,WAAU;KAAU,MAAK;MAAgB;KACvC,IACP;GAMJ,oBAAC;IACA,KAAK,cAAc,YAAY,SAAS,eAAe;IACvD,WAAU;IACV,KAAK,cAAc;KAClB;GAGD,eACA,qBAAC;IAAI,WAAU;;KACb,eAAe;KAAE;KAAI,OAAO;;KACxB;;GAEF,EACN,SAAS,KACT;;;;;AAMF,SAAgB,cAAc;CAC7B,MAAM,CAAC,QAAQ,aAAa,SAAS,MAAM;CAC3C,MAAM,CAAC,eAAe,oBAAoB,SAAS,EAAE;AAWrD,QAAO;EACN;EACA;EACA,cAZoB,aAAa,QAAQ,MAAM;AAC/C,oBAAiB,MAAM;AACvB,aAAU,KAAK;KACb,EAAE,CAAC;EAUL,eARqB,kBAAkB;AACvC,aAAU,MAAM;KACd,EAAE,CAAC;EAOL"}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { TypingIndicator, TypingIndicatorProps, TypingParticipant, TypingParticipantType } from "./typing-indicator.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { ContentProps } from "../types.js";
|
|
3
|
+
import { Content } from "./content.js";
|
|
4
4
|
import { ConversationEvent, ConversationEventProps } from "./conversation-event.js";
|
|
5
5
|
import { ConversationTimelineList, ConversationTimelineProps } from "./conversation-timeline.js";
|
|
6
6
|
import { Icon, IconName, IconProps, IconVariant } from "./icons.js";
|
|
7
7
|
import { SendButton } from "./multimodal-input.js";
|
|
8
|
-
import {
|
|
8
|
+
import { Root, RootProps } from "./root.js";
|
|
9
9
|
import { TimelineMessageGroup, TimelineMessageGroupProps } from "./timeline-message-group.js";
|
|
10
10
|
import { TimelineMessageItem, TimelineMessageItemProps } from "./timeline-message-item.js";
|
|
11
|
-
|
|
11
|
+
import { DefaultTrigger, DefaultTriggerProps } from "./trigger.js";
|
|
12
|
+
export { Content, type ContentProps, ConversationEvent, type ConversationEventProps, ConversationTimelineList, type ConversationTimelineProps, DefaultTrigger, type DefaultTriggerProps, Icon, type IconName, type IconProps, type IconVariant, Root, type RootProps, SendButton, TimelineMessageGroup, type TimelineMessageGroupProps, TimelineMessageItem, type TimelineMessageItemProps, TypingIndicator, type TypingIndicatorProps, type TypingParticipant, type TypingParticipantType };
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { TypingIndicator } from "./typing-indicator.js";
|
|
2
|
-
import {
|
|
2
|
+
import { Content } from "./content.js";
|
|
3
|
+
import { Root } from "./root.js";
|
|
3
4
|
import icons_default from "./icons.js";
|
|
5
|
+
import { DefaultTrigger } from "./trigger.js";
|
|
4
6
|
import { ConversationEvent } from "./conversation-event.js";
|
|
5
7
|
import { TimelineMessageItem } from "./timeline-message-item.js";
|
|
6
8
|
import { TimelineMessageGroup } from "./timeline-message-group.js";
|
|
7
9
|
import { ConversationTimelineList } from "./conversation-timeline.js";
|
|
8
10
|
import { SendButton } from "./multimodal-input.js";
|
|
9
|
-
import { Bubble } from "./bubble.js";
|
|
10
|
-
import { SupportContent } from "./support-content.js";
|
|
11
11
|
|
|
12
|
-
export {
|
|
12
|
+
export { Content, ConversationEvent, ConversationTimelineList, DefaultTrigger, icons_default as Icon, Root, SendButton, TimelineMessageGroup, TimelineMessageItem, TypingIndicator };
|
|
@@ -10,17 +10,20 @@ type MultimodalInputProps = {
|
|
|
10
10
|
placeholder?: string;
|
|
11
11
|
disabled?: boolean;
|
|
12
12
|
isSubmitting?: boolean;
|
|
13
|
+
isUploading?: boolean;
|
|
14
|
+
uploadProgress?: number;
|
|
13
15
|
error?: Error | null;
|
|
14
16
|
files?: File[];
|
|
15
17
|
onRemoveFile?: (index: number) => void;
|
|
16
18
|
maxFiles?: number;
|
|
17
19
|
maxFileSize?: number;
|
|
18
|
-
allowedFileTypes?: string
|
|
20
|
+
allowedFileTypes?: string;
|
|
19
21
|
};
|
|
20
22
|
declare const MultimodalInput: React.FC<MultimodalInputProps>;
|
|
21
23
|
type SendButtonProps = {
|
|
22
24
|
className?: string;
|
|
23
25
|
disabled?: boolean;
|
|
26
|
+
isUploading?: boolean;
|
|
24
27
|
};
|
|
25
28
|
declare const SendButton: React.FC<SendButtonProps>;
|
|
26
29
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"multimodal-input.d.ts","names":[],"sources":["../../../src/support/components/multimodal-input.tsx"],"sourcesContent":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"multimodal-input.d.ts","names":[],"sources":["../../../src/support/components/multimodal-input.tsx"],"sourcesContent":[],"mappings":";;;KAiBY,oBAAA;;EAAA,KAAA,EAAA,MAAA;EAKY,QAAA,EAAA,CAAA,KAAA,EAAA,MAAA,EAAA,GAAA,IAAA;EAMf,QAAA,EAAA,GAAA,GAAA,IAAA;EACA,YAAA,CAAA,EAAA,CAAA,KAAA,EAPe,IAOf,EAAA,EAAA,GAAA,IAAA;EAAI,WAAA,CAAA,EAAA,MAAA;EAOA,QAAA,CAAA,EAAA,OA6KZ;EAEW,YAAA,CAAA,EAAA,OAAe;EAMd,WAAA,CAAA,EAmBZ,OAAA;;UAhNQ;UACA;;;;;;cAOI,iBAAiB,KAAA,CAAM,GAAG;KA+K3B,eAAA;;;;;cAMC,YAAY,KAAA,CAAM,GAAG"}
|
|
@@ -3,28 +3,25 @@
|
|
|
3
3
|
|
|
4
4
|
import { cn } from "../utils/index.js";
|
|
5
5
|
import { Button } from "../../primitives/button.js";
|
|
6
|
-
import { MultimodalInput as MultimodalInput$1 } from "../../primitives/multimodal-input.js";
|
|
6
|
+
import { FileInput, MultimodalInput as MultimodalInput$1 } from "../../primitives/multimodal-input.js";
|
|
7
7
|
import icons_default from "./icons.js";
|
|
8
8
|
import { useSupportText } from "../text/index.js";
|
|
9
9
|
import { useComposerRefocus } from "../../hooks/use-composer-refocus.js";
|
|
10
10
|
import { Watermark } from "./watermark.js";
|
|
11
11
|
import { useRef } from "react";
|
|
12
|
-
import {
|
|
12
|
+
import { FILE_INPUT_ACCEPT, MAX_FILES_PER_MESSAGE, MAX_FILE_SIZE, formatFileSize } from "@cossistant/core";
|
|
13
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
13
14
|
|
|
14
15
|
//#region src/support/components/multimodal-input.tsx
|
|
15
|
-
const MultimodalInput = ({ className, value, onChange, onSubmit, onFileSelect, placeholder, disabled = false, isSubmitting = false, error, files = [], onRemoveFile, maxFiles =
|
|
16
|
-
|
|
17
|
-
"application/pdf",
|
|
18
|
-
"text/*"
|
|
19
|
-
] }) => {
|
|
20
|
-
useRef(null);
|
|
16
|
+
const MultimodalInput = ({ className, value, onChange, onSubmit, onFileSelect, placeholder, disabled = false, isSubmitting = false, isUploading = false, uploadProgress = 0, error, files = [], onRemoveFile, maxFiles = MAX_FILES_PER_MESSAGE, maxFileSize = MAX_FILE_SIZE, allowedFileTypes = FILE_INPUT_ACCEPT }) => {
|
|
17
|
+
const fileInputRef = useRef(null);
|
|
21
18
|
const hasContent = value.trim().length > 0 || files.length > 0;
|
|
22
19
|
const { focusComposer, inputRef } = useComposerRefocus({
|
|
23
20
|
disabled,
|
|
24
21
|
hasContent,
|
|
25
|
-
isSubmitting
|
|
22
|
+
isSubmitting: isSubmitting || isUploading
|
|
26
23
|
});
|
|
27
|
-
const canSubmit = !disabled && hasContent;
|
|
24
|
+
const canSubmit = !disabled && hasContent && !isUploading;
|
|
28
25
|
const text = useSupportText();
|
|
29
26
|
const resolvedPlaceholder = placeholder ?? text("component.multimodalInput.placeholder");
|
|
30
27
|
const handleSubmit = () => {
|
|
@@ -39,10 +36,8 @@ const MultimodalInput = ({ className, value, onChange, onSubmit, onFileSelect, p
|
|
|
39
36
|
e.preventDefault();
|
|
40
37
|
handleSubmit();
|
|
41
38
|
};
|
|
42
|
-
const
|
|
43
|
-
if (
|
|
44
|
-
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
45
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
39
|
+
const handleAttachClick = () => {
|
|
40
|
+
if (files.length < maxFiles) fileInputRef.current?.click();
|
|
46
41
|
};
|
|
47
42
|
return /* @__PURE__ */ jsxs("form", {
|
|
48
43
|
className: "flex flex-col gap-2",
|
|
@@ -53,35 +48,47 @@ const MultimodalInput = ({ className, value, onChange, onSubmit, onFileSelect, p
|
|
|
53
48
|
id: "multimodal-input-error",
|
|
54
49
|
children: error.message
|
|
55
50
|
}),
|
|
56
|
-
files.length > 0 && /* @__PURE__ */
|
|
57
|
-
className: "flex flex-
|
|
58
|
-
children:
|
|
59
|
-
className: "flex items-center gap-2
|
|
60
|
-
children: [
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
onRemoveFile && /* @__PURE__ */ jsx("button", {
|
|
74
|
-
"aria-label": text("common.actions.removeFile", { fileName: file.name }),
|
|
75
|
-
className: "ml-1 hover:text-co-destructive",
|
|
76
|
-
onClick: () => onRemoveFile(index),
|
|
77
|
-
type: "button",
|
|
78
|
-
children: /* @__PURE__ */ jsx(icons_default, {
|
|
51
|
+
files.length > 0 && /* @__PURE__ */ jsxs("div", {
|
|
52
|
+
className: "flex flex-col gap-2 p-2",
|
|
53
|
+
children: [isUploading && /* @__PURE__ */ jsxs("div", {
|
|
54
|
+
className: "flex items-center gap-2 text-co-muted-foreground text-xs",
|
|
55
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
56
|
+
className: "h-1 flex-1 overflow-hidden rounded-full bg-co-muted",
|
|
57
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
58
|
+
className: "h-full bg-co-primary transition-all duration-300",
|
|
59
|
+
style: { width: `${uploadProgress}%` }
|
|
60
|
+
})
|
|
61
|
+
}), /* @__PURE__ */ jsxs("span", { children: [uploadProgress, "%"] })]
|
|
62
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
63
|
+
className: "flex flex-wrap gap-2",
|
|
64
|
+
children: files.map((file, index) => /* @__PURE__ */ jsxs("div", {
|
|
65
|
+
className: cn("flex items-center gap-2 rounded-md bg-co-muted px-2 py-1 text-xs", isUploading && "opacity-70"),
|
|
66
|
+
children: [
|
|
67
|
+
/* @__PURE__ */ jsx(icons_default, {
|
|
79
68
|
className: "h-3 w-3",
|
|
80
|
-
name: "
|
|
69
|
+
name: "attachment"
|
|
70
|
+
}),
|
|
71
|
+
/* @__PURE__ */ jsx("span", {
|
|
72
|
+
className: "max-w-[150px] truncate",
|
|
73
|
+
children: file.name
|
|
74
|
+
}),
|
|
75
|
+
/* @__PURE__ */ jsx("span", {
|
|
76
|
+
className: "text-co-muted-foreground",
|
|
77
|
+
children: formatFileSize(file.size)
|
|
78
|
+
}),
|
|
79
|
+
onRemoveFile && !isUploading && /* @__PURE__ */ jsx("button", {
|
|
80
|
+
"aria-label": text("common.actions.removeFile", { fileName: file.name }),
|
|
81
|
+
className: "ml-1 hover:text-co-destructive",
|
|
82
|
+
onClick: () => onRemoveFile(index),
|
|
83
|
+
type: "button",
|
|
84
|
+
children: /* @__PURE__ */ jsx(icons_default, {
|
|
85
|
+
className: "h-3 w-3",
|
|
86
|
+
name: "close"
|
|
87
|
+
})
|
|
81
88
|
})
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
89
|
+
]
|
|
90
|
+
}, `${file.name}-${index}`))
|
|
91
|
+
})]
|
|
85
92
|
}),
|
|
86
93
|
/* @__PURE__ */ jsxs("div", {
|
|
87
94
|
className: "group/multimodal-input flex flex-col rounded border border-co-border bg-co-background ring-offset-2 focus-within:ring-1 focus-within:ring-co-primary/10 dark:bg-co-background-200",
|
|
@@ -98,20 +105,39 @@ const MultimodalInput = ({ className, value, onChange, onSubmit, onFileSelect, p
|
|
|
98
105
|
value
|
|
99
106
|
}), /* @__PURE__ */ jsxs("div", {
|
|
100
107
|
className: "flex items-center justify-between py-1 pr-1 pl-3",
|
|
101
|
-
children: [/* @__PURE__ */ jsx(Watermark, {}), /* @__PURE__ */
|
|
108
|
+
children: [/* @__PURE__ */ jsx(Watermark, {}), /* @__PURE__ */ jsxs("div", {
|
|
102
109
|
className: "flex items-center gap-0.5",
|
|
103
|
-
children: /* @__PURE__ */
|
|
110
|
+
children: [onFileSelect && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("button", {
|
|
111
|
+
"aria-label": text("common.actions.attachFiles"),
|
|
112
|
+
className: cn("group flex h-8 w-8 items-center justify-center rounded-md text-co-muted-foreground hover:bg-co-muted hover:text-co-foreground disabled:cursor-not-allowed disabled:opacity-50", files.length >= maxFiles && "opacity-50"),
|
|
113
|
+
disabled: disabled || isSubmitting || files.length >= maxFiles,
|
|
114
|
+
onClick: handleAttachClick,
|
|
115
|
+
type: "button",
|
|
116
|
+
children: /* @__PURE__ */ jsx(icons_default, {
|
|
117
|
+
className: "h-4 w-4",
|
|
118
|
+
name: "attachment"
|
|
119
|
+
})
|
|
120
|
+
}), /* @__PURE__ */ jsx(FileInput, {
|
|
121
|
+
accept: allowedFileTypes,
|
|
122
|
+
className: "hidden",
|
|
123
|
+
disabled: disabled || isSubmitting || files.length >= maxFiles,
|
|
124
|
+
onFileSelect,
|
|
125
|
+
ref: fileInputRef
|
|
126
|
+
})] }), /* @__PURE__ */ jsx(SendButton, {
|
|
127
|
+
disabled: !canSubmit,
|
|
128
|
+
isUploading
|
|
129
|
+
})]
|
|
104
130
|
})]
|
|
105
131
|
})]
|
|
106
132
|
})
|
|
107
133
|
]
|
|
108
134
|
});
|
|
109
135
|
};
|
|
110
|
-
const SendButton = ({ className, disabled = false }) => /* @__PURE__ */ jsx(Button, {
|
|
136
|
+
const SendButton = ({ className, disabled = false, isUploading = false }) => /* @__PURE__ */ jsx(Button, {
|
|
111
137
|
className: cn("group flex h-8 w-8 items-center justify-center rounded-md text-co-primary hover:bg-co-muted disabled:cursor-not-allowed disabled:opacity-50", className),
|
|
112
138
|
disabled,
|
|
113
139
|
type: "submit",
|
|
114
|
-
children: /* @__PURE__ */ jsx(icons_default, {
|
|
140
|
+
children: isUploading ? /* @__PURE__ */ jsx("div", { className: "h-4 w-4 animate-spin rounded-full border-2 border-co-primary border-t-transparent" }) : /* @__PURE__ */ jsx(icons_default, {
|
|
115
141
|
className: "h-4 w-4",
|
|
116
142
|
filledOnHover: true,
|
|
117
143
|
name: "send"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"multimodal-input.js","names":["MultimodalInput: React.FC<MultimodalInputProps>","Icon","Primitive.MultimodalInput","SendButton: React.FC<SendButtonProps>","Primitive.Button"],"sources":["../../../src/support/components/multimodal-input.tsx"],"sourcesContent":["\"use client\";\n\nimport type React from \"react\";\nimport { useRef } from \"react\";\nimport { useComposerRefocus } from \"../../hooks/use-composer-refocus\";\nimport * as Primitive from \"../../primitives\";\nimport { useSupportText } from \"../text\";\nimport { cn } from \"../utils\";\nimport Icon from \"./icons\";\nimport { Watermark } from \"./watermark\";\n\nexport type MultimodalInputProps = {\n\tclassName?: string;\n\tvalue: string;\n\tonChange: (value: string) => void;\n\tonSubmit: () => void;\n\tonFileSelect?: (files: File[]) => void;\n\tplaceholder?: string;\n\tdisabled?: boolean;\n\tisSubmitting?: boolean;\n\terror?: Error | null;\n\tfiles?: File[];\n\tonRemoveFile?: (index: number) => void;\n\tmaxFiles?: number;\n\tmaxFileSize?: number;\n\tallowedFileTypes?: string[];\n};\n\nexport const MultimodalInput: React.FC<MultimodalInputProps> = ({\n\tclassName,\n\tvalue,\n\tonChange,\n\tonSubmit,\n\tonFileSelect,\n\tplaceholder,\n\tdisabled = false,\n\tisSubmitting = false,\n\terror,\n\tfiles = [],\n\tonRemoveFile,\n\tmaxFiles = 5,\n\tmaxFileSize = 10 * 1024 * 1024, // 10MB\n\tallowedFileTypes = [\"image/*\", \"application/pdf\", \"text/*\"],\n}) => {\n\tconst fileInputRef = useRef<HTMLInputElement>(null);\n\tconst hasContent = value.trim().length > 0 || files.length > 0;\n\tconst { focusComposer, inputRef } = useComposerRefocus({\n\t\tdisabled,\n\t\thasContent,\n\t\tisSubmitting,\n\t});\n\tconst canSubmit = !disabled && hasContent;\n\tconst text = useSupportText();\n\tconst resolvedPlaceholder =\n\t\tplaceholder ?? text(\"component.multimodalInput.placeholder\");\n\n\tconst handleSubmit = () => {\n\t\tif (!canSubmit) {\n\t\t\treturn;\n\t\t}\n\n\t\tonSubmit();\n\t\t// Try focusing immediately for optimistic submission UX, then ensure focus\n\t\t// sticks after the submit button handles the click.\n\t\tfocusComposer();\n\t\trequestAnimationFrame(() => {\n\t\t\tfocusComposer();\n\t\t});\n\t};\n\n\tconst handleFormSubmit = (e: React.FormEvent) => {\n\t\te.preventDefault();\n\t\thandleSubmit();\n\t};\n\n\tconst handleAttachClick = () => {\n\t\tif (files.length < maxFiles) {\n\t\t\tfileInputRef.current?.click();\n\t\t}\n\t};\n\n\tconst formatFileSize = (bytes: number) => {\n\t\tif (bytes < 1024) {\n\t\t\treturn `${bytes} B`;\n\t\t}\n\t\tif (bytes < 1024 * 1024) {\n\t\t\treturn `${(bytes / 1024).toFixed(1)} KB`;\n\t\t}\n\t\treturn `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n\t};\n\n\treturn (\n\t\t<form className=\"flex flex-col gap-2\" onSubmit={handleFormSubmit}>\n\t\t\t{/* Error message */}\n\t\t\t{error && (\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"rounded-md bg-co-destructive-muted p-2 text-co-destructive text-xs\"\n\t\t\t\t\tid=\"multimodal-input-error\"\n\t\t\t\t>\n\t\t\t\t\t{error.message}\n\t\t\t\t</div>\n\t\t\t)}\n\n\t\t\t{/* File attachments */}\n\t\t\t{files.length > 0 && (\n\t\t\t\t<div className=\"flex flex-wrap gap-2 p-2\">\n\t\t\t\t\t{files.map((file, index) => (\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tclassName=\"flex items-center gap-2 rounded-md bg-co-muted px-2 py-1 text-xs\"\n\t\t\t\t\t\t\tkey={`${file.name}-${index}`}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<Icon className=\"h-3 w-3\" name=\"attachment\" />\n\t\t\t\t\t\t\t<span className=\"max-w-[150px] truncate\">{file.name}</span>\n\t\t\t\t\t\t\t<span className=\"text-co-muted-foreground\">\n\t\t\t\t\t\t\t\t{formatFileSize(file.size)}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t{onRemoveFile && (\n\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\taria-label={text(\"common.actions.removeFile\", {\n\t\t\t\t\t\t\t\t\t\tfileName: file.name,\n\t\t\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t\t\t\tclassName=\"ml-1 hover:text-co-destructive\"\n\t\t\t\t\t\t\t\t\tonClick={() => onRemoveFile(index)}\n\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<Icon className=\"h-3 w-3\" name=\"close\" />\n\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t))}\n\t\t\t\t</div>\n\t\t\t)}\n\n\t\t\t{/* Input area */}\n\t\t\t<div className=\"group/multimodal-input flex flex-col rounded border border-co-border bg-co-background ring-offset-2 focus-within:ring-1 focus-within:ring-co-primary/10 dark:bg-co-background-200\">\n\t\t\t\t<Primitive.MultimodalInput\n\t\t\t\t\tautoFocus\n\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\"flex-1 resize-none overflow-hidden p-3 text-co-foreground text-sm placeholder:text-co-primary/50 focus-visible:outline-none\",\n\t\t\t\t\t\tclassName\n\t\t\t\t\t)}\n\t\t\t\t\tdisabled={disabled}\n\t\t\t\t\terror={error}\n\t\t\t\t\tonChange={onChange}\n\t\t\t\t\tonFileSelect={onFileSelect}\n\t\t\t\t\tonSubmit={handleSubmit}\n\t\t\t\t\tplaceholder={resolvedPlaceholder}\n\t\t\t\t\tref={inputRef}\n\t\t\t\t\tvalue={value}\n\t\t\t\t/>\n\n\t\t\t\t<div className=\"flex items-center justify-between py-1 pr-1 pl-3\">\n\t\t\t\t\t<Watermark />\n\n\t\t\t\t\t<div className=\"flex items-center gap-0.5\">\n\t\t\t\t\t\t{/* File attachment button */}\n\t\t\t\t\t\t{/* {onFileSelect && (\n\t\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\taria-label={text(\"common.actions.attachFiles\")}\n\t\t\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\t\t\"group flex h-8 w-8 items-center justify-center rounded-md text-co-muted-foreground hover:bg-co-muted hover:text-co-foreground disabled:cursor-not-allowed disabled:opacity-50\",\n\t\t\t\t\t\t\t\t\t\tfiles.length >= maxFiles && \"opacity-50\"\n\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\tdisabled={\n\t\t\t\t\t\t\t\t\t\tdisabled || isSubmitting || files.length >= maxFiles\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tonClick={handleAttachClick}\n\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<Icon className=\"h-4 w-4\" name=\"attachment\" />\n\t\t\t\t\t\t\t\t</button>\n\n\t\t\t\t\t\t\t\t<Primitive.FileInput\n\t\t\t\t\t\t\t\t\taccept={allowedFileTypes.join(\",\")}\n\t\t\t\t\t\t\t\t\tclassName=\"hidden\"\n\t\t\t\t\t\t\t\t\tdisabled={\n\t\t\t\t\t\t\t\t\t\tdisabled || isSubmitting || files.length >= maxFiles\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tonFileSelect={onFileSelect}\n\t\t\t\t\t\t\t\t\tref={fileInputRef}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t</>\n\t\t\t\t\t\t)} */}\n\n\t\t\t\t\t\t{/* Send button */}\n\t\t\t\t\t\t<SendButton disabled={!canSubmit} />\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</form>\n\t);\n};\n\nexport type SendButtonProps = {\n\tclassName?: string;\n\tdisabled?: boolean;\n};\n\nexport const SendButton: React.FC<SendButtonProps> = ({\n\tclassName,\n\tdisabled = false,\n}) => (\n\t<Primitive.Button\n\t\tclassName={cn(\n\t\t\t\"group flex h-8 w-8 items-center justify-center rounded-md text-co-primary hover:bg-co-muted disabled:cursor-not-allowed disabled:opacity-50\",\n\t\t\tclassName\n\t\t)}\n\t\tdisabled={disabled}\n\t\ttype=\"submit\"\n\t>\n\t\t<Icon className=\"h-4 w-4\" filledOnHover name=\"send\" />\n\t</Primitive.Button>\n);\n"],"mappings":";;;;;;;;;;;;;;AA4BA,MAAaA,mBAAmD,EAC/D,WACA,OACA,UACA,UACA,cACA,aACA,WAAW,OACX,eAAe,OACf,OACA,QAAQ,EAAE,EACV,cACA,WAAW,GACX,cAAc,KAAK,OAAO,MAC1B,mBAAmB;CAAC;CAAW;CAAmB;CAAS,OACtD;AACgB,QAAyB,KAAK;CACnD,MAAM,aAAa,MAAM,MAAM,CAAC,SAAS,KAAK,MAAM,SAAS;CAC7D,MAAM,EAAE,eAAe,aAAa,mBAAmB;EACtD;EACA;EACA;EACA,CAAC;CACF,MAAM,YAAY,CAAC,YAAY;CAC/B,MAAM,OAAO,gBAAgB;CAC7B,MAAM,sBACL,eAAe,KAAK,wCAAwC;CAE7D,MAAM,qBAAqB;AAC1B,MAAI,CAAC,UACJ;AAGD,YAAU;AAGV,iBAAe;AACf,8BAA4B;AAC3B,kBAAe;IACd;;CAGH,MAAM,oBAAoB,MAAuB;AAChD,IAAE,gBAAgB;AAClB,gBAAc;;CASf,MAAM,kBAAkB,UAAkB;AACzC,MAAI,QAAQ,KACX,QAAO,GAAG,MAAM;AAEjB,MAAI,QAAQ,OAAO,KAClB,QAAO,IAAI,QAAQ,MAAM,QAAQ,EAAE,CAAC;AAErC,SAAO,IAAI,SAAS,OAAO,OAAO,QAAQ,EAAE,CAAC;;AAG9C,QACC,qBAAC;EAAK,WAAU;EAAsB,UAAU;;GAE9C,SACA,oBAAC;IACA,WAAU;IACV,IAAG;cAEF,MAAM;KACF;GAIN,MAAM,SAAS,KACf,oBAAC;IAAI,WAAU;cACb,MAAM,KAAK,MAAM,UACjB,qBAAC;KACA,WAAU;;MAGV,oBAACC;OAAK,WAAU;OAAU,MAAK;QAAe;MAC9C,oBAAC;OAAK,WAAU;iBAA0B,KAAK;QAAY;MAC3D,oBAAC;OAAK,WAAU;iBACd,eAAe,KAAK,KAAK;QACpB;MACN,gBACA,oBAAC;OACA,cAAY,KAAK,6BAA6B,EAC7C,UAAU,KAAK,MACf,CAAC;OACF,WAAU;OACV,eAAe,aAAa,MAAM;OAClC,MAAK;iBAEL,oBAACA;QAAK,WAAU;QAAU,MAAK;SAAU;QACjC;;OAjBL,GAAG,KAAK,KAAK,GAAG,QAmBhB,CACL;KACG;GAIP,qBAAC;IAAI,WAAU;eACd,oBAACC;KACA;KACA,WAAW,GACV,+HACA,UACA;KACS;KACH;KACG;KACI;KACd,UAAU;KACV,aAAa;KACb,KAAK;KACE;MACN,EAEF,qBAAC;KAAI,WAAU;gBACd,oBAAC,cAAY,EAEb,oBAAC;MAAI,WAAU;gBAgCd,oBAAC,cAAW,UAAU,CAAC,YAAa;OAC/B;MACD;KACD;;GACA;;AAST,MAAaC,cAAyC,EACrD,WACA,WAAW,YAEX,oBAACC;CACA,WAAW,GACV,+IACA,UACA;CACS;CACV,MAAK;WAEL,oBAACH;EAAK,WAAU;EAAU;EAAc,MAAK;GAAS;EACpC"}
|
|
1
|
+
{"version":3,"file":"multimodal-input.js","names":["MultimodalInput: React.FC<MultimodalInputProps>","Icon","Primitive.MultimodalInput","Primitive.FileInput","SendButton: React.FC<SendButtonProps>","Primitive.Button"],"sources":["../../../src/support/components/multimodal-input.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n\tFILE_INPUT_ACCEPT,\n\tformatFileSize,\n\tMAX_FILE_SIZE,\n\tMAX_FILES_PER_MESSAGE,\n} from \"@cossistant/core\";\nimport type React from \"react\";\nimport { useRef } from \"react\";\nimport { useComposerRefocus } from \"../../hooks/use-composer-refocus\";\nimport * as Primitive from \"../../primitives\";\nimport { useSupportText } from \"../text\";\nimport { cn } from \"../utils\";\nimport Icon from \"./icons\";\nimport { Watermark } from \"./watermark\";\n\nexport type MultimodalInputProps = {\n\tclassName?: string;\n\tvalue: string;\n\tonChange: (value: string) => void;\n\tonSubmit: () => void;\n\tonFileSelect?: (files: File[]) => void;\n\tplaceholder?: string;\n\tdisabled?: boolean;\n\tisSubmitting?: boolean;\n\tisUploading?: boolean;\n\tuploadProgress?: number;\n\terror?: Error | null;\n\tfiles?: File[];\n\tonRemoveFile?: (index: number) => void;\n\tmaxFiles?: number;\n\tmaxFileSize?: number;\n\tallowedFileTypes?: string;\n};\n\nexport const MultimodalInput: React.FC<MultimodalInputProps> = ({\n\tclassName,\n\tvalue,\n\tonChange,\n\tonSubmit,\n\tonFileSelect,\n\tplaceholder,\n\tdisabled = false,\n\tisSubmitting = false,\n\tisUploading = false,\n\tuploadProgress = 0,\n\terror,\n\tfiles = [],\n\tonRemoveFile,\n\tmaxFiles = MAX_FILES_PER_MESSAGE,\n\tmaxFileSize = MAX_FILE_SIZE,\n\tallowedFileTypes = FILE_INPUT_ACCEPT,\n}) => {\n\tconst fileInputRef = useRef<HTMLInputElement>(null);\n\tconst hasContent = value.trim().length > 0 || files.length > 0;\n\tconst { focusComposer, inputRef } = useComposerRefocus({\n\t\tdisabled,\n\t\thasContent,\n\t\tisSubmitting: isSubmitting || isUploading,\n\t});\n\tconst canSubmit = !disabled && hasContent && !isUploading;\n\tconst text = useSupportText();\n\tconst resolvedPlaceholder =\n\t\tplaceholder ?? text(\"component.multimodalInput.placeholder\");\n\n\tconst handleSubmit = () => {\n\t\tif (!canSubmit) {\n\t\t\treturn;\n\t\t}\n\n\t\tonSubmit();\n\t\t// Try focusing immediately for optimistic submission UX, then ensure focus\n\t\t// sticks after the submit button handles the click.\n\t\tfocusComposer();\n\t\trequestAnimationFrame(() => {\n\t\t\tfocusComposer();\n\t\t});\n\t};\n\n\tconst handleFormSubmit = (e: React.FormEvent) => {\n\t\te.preventDefault();\n\t\thandleSubmit();\n\t};\n\n\tconst handleAttachClick = () => {\n\t\tif (files.length < maxFiles) {\n\t\t\tfileInputRef.current?.click();\n\t\t}\n\t};\n\n\treturn (\n\t\t<form className=\"flex flex-col gap-2\" onSubmit={handleFormSubmit}>\n\t\t\t{/* Error message */}\n\t\t\t{error && (\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"rounded-md bg-co-destructive-muted p-2 text-co-destructive text-xs\"\n\t\t\t\t\tid=\"multimodal-input-error\"\n\t\t\t\t>\n\t\t\t\t\t{error.message}\n\t\t\t\t</div>\n\t\t\t)}\n\n\t\t\t{/* File attachments */}\n\t\t\t{files.length > 0 && (\n\t\t\t\t<div className=\"flex flex-col gap-2 p-2\">\n\t\t\t\t\t{/* Upload progress indicator */}\n\t\t\t\t\t{isUploading && (\n\t\t\t\t\t\t<div className=\"flex items-center gap-2 text-co-muted-foreground text-xs\">\n\t\t\t\t\t\t\t<div className=\"h-1 flex-1 overflow-hidden rounded-full bg-co-muted\">\n\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\tclassName=\"h-full bg-co-primary transition-all duration-300\"\n\t\t\t\t\t\t\t\t\tstyle={{ width: `${uploadProgress}%` }}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<span>{uploadProgress}%</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t)}\n\t\t\t\t\t<div className=\"flex flex-wrap gap-2\">\n\t\t\t\t\t\t{files.map((file, index) => (\n\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\t\"flex items-center gap-2 rounded-md bg-co-muted px-2 py-1 text-xs\",\n\t\t\t\t\t\t\t\t\tisUploading && \"opacity-70\"\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\tkey={`${file.name}-${index}`}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<Icon className=\"h-3 w-3\" name=\"attachment\" />\n\t\t\t\t\t\t\t\t<span className=\"max-w-[150px] truncate\">{file.name}</span>\n\t\t\t\t\t\t\t\t<span className=\"text-co-muted-foreground\">\n\t\t\t\t\t\t\t\t\t{formatFileSize(file.size)}\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t{onRemoveFile && !isUploading && (\n\t\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\t\taria-label={text(\"common.actions.removeFile\", {\n\t\t\t\t\t\t\t\t\t\t\tfileName: file.name,\n\t\t\t\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t\t\t\t\tclassName=\"ml-1 hover:text-co-destructive\"\n\t\t\t\t\t\t\t\t\t\tonClick={() => onRemoveFile(index)}\n\t\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t<Icon className=\"h-3 w-3\" name=\"close\" />\n\t\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t))}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t)}\n\n\t\t\t{/* Input area */}\n\t\t\t<div className=\"group/multimodal-input flex flex-col rounded border border-co-border bg-co-background ring-offset-2 focus-within:ring-1 focus-within:ring-co-primary/10 dark:bg-co-background-200\">\n\t\t\t\t<Primitive.MultimodalInput\n\t\t\t\t\tautoFocus\n\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\"flex-1 resize-none overflow-hidden p-3 text-co-foreground text-sm placeholder:text-co-primary/50 focus-visible:outline-none\",\n\t\t\t\t\t\tclassName\n\t\t\t\t\t)}\n\t\t\t\t\tdisabled={disabled}\n\t\t\t\t\terror={error}\n\t\t\t\t\tonChange={onChange}\n\t\t\t\t\tonFileSelect={onFileSelect}\n\t\t\t\t\tonSubmit={handleSubmit}\n\t\t\t\t\tplaceholder={resolvedPlaceholder}\n\t\t\t\t\tref={inputRef}\n\t\t\t\t\tvalue={value}\n\t\t\t\t/>\n\n\t\t\t\t<div className=\"flex items-center justify-between py-1 pr-1 pl-3\">\n\t\t\t\t\t<Watermark />\n\n\t\t\t\t\t<div className=\"flex items-center gap-0.5\">\n\t\t\t\t\t\t{/* File attachment button */}\n\t\t\t\t\t\t{onFileSelect && (\n\t\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\taria-label={text(\"common.actions.attachFiles\")}\n\t\t\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\t\t\"group flex h-8 w-8 items-center justify-center rounded-md text-co-muted-foreground hover:bg-co-muted hover:text-co-foreground disabled:cursor-not-allowed disabled:opacity-50\",\n\t\t\t\t\t\t\t\t\t\tfiles.length >= maxFiles && \"opacity-50\"\n\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\tdisabled={\n\t\t\t\t\t\t\t\t\t\tdisabled || isSubmitting || files.length >= maxFiles\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tonClick={handleAttachClick}\n\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<Icon className=\"h-4 w-4\" name=\"attachment\" />\n\t\t\t\t\t\t\t\t</button>\n\n\t\t\t\t\t\t\t\t<Primitive.FileInput\n\t\t\t\t\t\t\t\t\taccept={allowedFileTypes}\n\t\t\t\t\t\t\t\t\tclassName=\"hidden\"\n\t\t\t\t\t\t\t\t\tdisabled={\n\t\t\t\t\t\t\t\t\t\tdisabled || isSubmitting || files.length >= maxFiles\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tonFileSelect={onFileSelect}\n\t\t\t\t\t\t\t\t\tref={fileInputRef}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t</>\n\t\t\t\t\t\t)}\n\n\t\t\t\t\t\t{/* Send button */}\n\t\t\t\t\t\t<SendButton disabled={!canSubmit} isUploading={isUploading} />\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</form>\n\t);\n};\n\nexport type SendButtonProps = {\n\tclassName?: string;\n\tdisabled?: boolean;\n\tisUploading?: boolean;\n};\n\nexport const SendButton: React.FC<SendButtonProps> = ({\n\tclassName,\n\tdisabled = false,\n\tisUploading = false,\n}) => (\n\t<Primitive.Button\n\t\tclassName={cn(\n\t\t\t\"group flex h-8 w-8 items-center justify-center rounded-md text-co-primary hover:bg-co-muted disabled:cursor-not-allowed disabled:opacity-50\",\n\t\t\tclassName\n\t\t)}\n\t\tdisabled={disabled}\n\t\ttype=\"submit\"\n\t>\n\t\t{isUploading ? (\n\t\t\t<div className=\"h-4 w-4 animate-spin rounded-full border-2 border-co-primary border-t-transparent\" />\n\t\t) : (\n\t\t\t<Icon className=\"h-4 w-4\" filledOnHover name=\"send\" />\n\t\t)}\n\t</Primitive.Button>\n);\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useSupportNavigation } from "../store/support-store.js";
|
|
2
|
-
import { CoButton } from "./button.js";
|
|
3
2
|
import icons_default from "./icons.js";
|
|
3
|
+
import { CoButton } from "./button.js";
|
|
4
4
|
import { Text } from "../text/index.js";
|
|
5
5
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
6
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as React$1 from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/support/components/root.d.ts
|
|
4
|
+
type RootProps = {
|
|
5
|
+
className?: string;
|
|
6
|
+
children: React$1.ReactNode;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Root wrapper component that provides the positioning context.
|
|
10
|
+
* Contains the trigger and content as siblings.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* <Support.Root>
|
|
14
|
+
* <Support.Trigger>Help</Support.Trigger>
|
|
15
|
+
* <Support.Content>
|
|
16
|
+
* <Support.Router />
|
|
17
|
+
* </Support.Content>
|
|
18
|
+
* </Support.Root>
|
|
19
|
+
*/
|
|
20
|
+
declare const Root: React$1.FC<RootProps>;
|
|
21
|
+
//#endregion
|
|
22
|
+
export { Root, RootProps };
|
|
23
|
+
//# sourceMappingURL=root.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"root.d.ts","names":[],"sources":["../../../src/support/components/root.tsx"],"sourcesContent":[],"mappings":";;;KAOY,SAAA;;EAAA,QAAA,EAED,OAAA,CAAM,SAAN;AAeX,CAAA;;;;;;;;;;;;;cAAa,MAAM,OAAA,CAAM,GAAG"}
|