@cossistant/react 0.0.1
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 +13 -0
- package/conversation.d.ts +312 -0
- package/conversation.d.ts.map +1 -0
- package/hooks/index.d.ts +23 -0
- package/hooks/index.js +24 -0
- package/hooks/private/store/use-conversations-store.d.ts +13 -0
- package/hooks/private/store/use-conversations-store.d.ts.map +1 -0
- package/hooks/private/store/use-conversations-store.js +34 -0
- package/hooks/private/store/use-conversations-store.js.map +1 -0
- package/hooks/private/store/use-store-selector.d.ts +10 -0
- package/hooks/private/store/use-store-selector.d.ts.map +1 -0
- package/hooks/private/store/use-store-selector.js +17 -0
- package/hooks/private/store/use-store-selector.js.map +1 -0
- package/hooks/private/store/use-website-store.d.ts +18 -0
- package/hooks/private/store/use-website-store.d.ts.map +1 -0
- package/hooks/private/store/use-website-store.js +39 -0
- package/hooks/private/store/use-website-store.js.map +1 -0
- package/hooks/private/use-client-query.d.ts +25 -0
- package/hooks/private/use-client-query.d.ts.map +1 -0
- package/hooks/private/use-client-query.js +122 -0
- package/hooks/private/use-client-query.js.map +1 -0
- package/hooks/private/use-default-messages.d.ts +18 -0
- package/hooks/private/use-default-messages.d.ts.map +1 -0
- package/hooks/private/use-default-messages.js +45 -0
- package/hooks/private/use-default-messages.js.map +1 -0
- package/hooks/private/use-grouped-messages.d.ts +54 -0
- package/hooks/private/use-grouped-messages.d.ts.map +1 -0
- package/hooks/private/use-grouped-messages.js +157 -0
- package/hooks/private/use-grouped-messages.js.map +1 -0
- package/hooks/private/use-multimodal-input.d.ts +40 -0
- package/hooks/private/use-multimodal-input.d.ts.map +1 -0
- package/hooks/private/use-multimodal-input.js +129 -0
- package/hooks/private/use-multimodal-input.js.map +1 -0
- package/hooks/private/use-rest-client.d.ts +17 -0
- package/hooks/private/use-rest-client.d.ts.map +1 -0
- package/hooks/private/use-rest-client.js +41 -0
- package/hooks/private/use-rest-client.js.map +1 -0
- package/hooks/private/use-visitor-typing-reporter.d.ts +19 -0
- package/hooks/private/use-visitor-typing-reporter.d.ts.map +1 -0
- package/hooks/private/use-visitor-typing-reporter.js +140 -0
- package/hooks/private/use-visitor-typing-reporter.js.map +1 -0
- package/hooks/use-composer-refocus.d.ts +20 -0
- package/hooks/use-composer-refocus.d.ts.map +1 -0
- package/hooks/use-composer-refocus.js +32 -0
- package/hooks/use-composer-refocus.js.map +1 -0
- package/hooks/use-conversation-auto-seen.d.ts +54 -0
- package/hooks/use-conversation-auto-seen.d.ts.map +1 -0
- package/hooks/use-conversation-auto-seen.js +106 -0
- package/hooks/use-conversation-auto-seen.js.map +1 -0
- package/hooks/use-conversation-history-page.d.ts +86 -0
- package/hooks/use-conversation-history-page.d.ts.map +1 -0
- package/hooks/use-conversation-history-page.js +97 -0
- package/hooks/use-conversation-history-page.js.map +1 -0
- package/hooks/use-conversation-lifecycle.d.ts +80 -0
- package/hooks/use-conversation-lifecycle.d.ts.map +1 -0
- package/hooks/use-conversation-lifecycle.js +54 -0
- package/hooks/use-conversation-lifecycle.js.map +1 -0
- package/hooks/use-conversation-page.d.ts +82 -0
- package/hooks/use-conversation-page.d.ts.map +1 -0
- package/hooks/use-conversation-page.js +138 -0
- package/hooks/use-conversation-page.js.map +1 -0
- package/hooks/use-conversation-seen.d.ts +17 -0
- package/hooks/use-conversation-seen.d.ts.map +1 -0
- package/hooks/use-conversation-seen.js +58 -0
- package/hooks/use-conversation-seen.js.map +1 -0
- package/hooks/use-conversation-timeline-items.d.ts +21 -0
- package/hooks/use-conversation-timeline-items.d.ts.map +1 -0
- package/hooks/use-conversation-timeline-items.js +87 -0
- package/hooks/use-conversation-timeline-items.js.map +1 -0
- package/hooks/use-conversation-typing.d.ts +13 -0
- package/hooks/use-conversation-typing.d.ts.map +1 -0
- package/hooks/use-conversation-typing.js +34 -0
- package/hooks/use-conversation-typing.js.map +1 -0
- package/hooks/use-conversation.d.ts +18 -0
- package/hooks/use-conversation.d.ts.map +1 -0
- package/hooks/use-conversation.js +44 -0
- package/hooks/use-conversation.js.map +1 -0
- package/hooks/use-conversations.d.ts +20 -0
- package/hooks/use-conversations.d.ts.map +1 -0
- package/hooks/use-conversations.js +68 -0
- package/hooks/use-conversations.js.map +1 -0
- package/hooks/use-create-conversation.d.ts +30 -0
- package/hooks/use-create-conversation.d.ts.map +1 -0
- package/hooks/use-create-conversation.js +67 -0
- package/hooks/use-create-conversation.js.map +1 -0
- package/hooks/use-home-page.d.ts +82 -0
- package/hooks/use-home-page.d.ts.map +1 -0
- package/hooks/use-home-page.js +89 -0
- package/hooks/use-home-page.js.map +1 -0
- package/hooks/use-message-composer.d.ts +88 -0
- package/hooks/use-message-composer.d.ts.map +1 -0
- package/hooks/use-message-composer.js +94 -0
- package/hooks/use-message-composer.js.map +1 -0
- package/hooks/use-realtime-support.d.ts +25 -0
- package/hooks/use-realtime-support.d.ts.map +1 -0
- package/hooks/use-realtime-support.js +29 -0
- package/hooks/use-realtime-support.js.map +1 -0
- package/hooks/use-send-message.d.ts +34 -0
- package/hooks/use-send-message.d.ts.map +1 -0
- package/hooks/use-send-message.js +118 -0
- package/hooks/use-send-message.js.map +1 -0
- package/hooks/use-visitor.d.ts +28 -0
- package/hooks/use-visitor.d.ts.map +1 -0
- package/hooks/use-visitor.js +59 -0
- package/hooks/use-visitor.js.map +1 -0
- package/hooks/use-window-visibility-focus.d.ts +9 -0
- package/hooks/use-window-visibility-focus.d.ts.map +1 -0
- package/hooks/use-window-visibility-focus.js +53 -0
- package/hooks/use-window-visibility-focus.js.map +1 -0
- package/identify-visitor.d.ts +18 -0
- package/identify-visitor.d.ts.map +1 -0
- package/identify-visitor.js +26 -0
- package/identify-visitor.js.map +1 -0
- package/index.d.ts +38 -0
- package/index.js +38 -0
- package/package.json +121 -0
- package/primitives/avatar/avatar.d.ts +31 -0
- package/primitives/avatar/avatar.d.ts.map +1 -0
- package/primitives/avatar/avatar.js +49 -0
- package/primitives/avatar/avatar.js.map +1 -0
- package/primitives/avatar/fallback.d.ts +24 -0
- package/primitives/avatar/fallback.d.ts.map +1 -0
- package/primitives/avatar/fallback.js +57 -0
- package/primitives/avatar/fallback.js.map +1 -0
- package/primitives/avatar/image.d.ts +27 -0
- package/primitives/avatar/image.d.ts.map +1 -0
- package/primitives/avatar/image.js +58 -0
- package/primitives/avatar/image.js.map +1 -0
- package/primitives/avatar/index.d.ts +4 -0
- package/primitives/avatar/index.js +5 -0
- package/primitives/avatar/index.parts.d.ts +4 -0
- package/primitives/avatar/index.parts.js +5 -0
- package/primitives/bubble.d.ts +28 -0
- package/primitives/bubble.d.ts.map +1 -0
- package/primitives/bubble.js +43 -0
- package/primitives/bubble.js.map +1 -0
- package/primitives/button.d.ts +19 -0
- package/primitives/button.d.ts.map +1 -0
- package/primitives/button.js +27 -0
- package/primitives/button.js.map +1 -0
- package/primitives/conversation-timeline.d.ts +86 -0
- package/primitives/conversation-timeline.d.ts.map +1 -0
- package/primitives/conversation-timeline.js +119 -0
- package/primitives/conversation-timeline.js.map +1 -0
- package/primitives/index.d.ts +20 -0
- package/primitives/index.d.ts.map +1 -0
- package/primitives/index.js +45 -0
- package/primitives/index.js.map +1 -0
- package/primitives/index.parts.d.ts +13 -0
- package/primitives/index.parts.js +14 -0
- package/primitives/multimodal-input.d.ts +53 -0
- package/primitives/multimodal-input.d.ts.map +1 -0
- package/primitives/multimodal-input.js +106 -0
- package/primitives/multimodal-input.js.map +1 -0
- package/primitives/timeline-item-group.d.ts +166 -0
- package/primitives/timeline-item-group.d.ts.map +1 -0
- package/primitives/timeline-item-group.js +204 -0
- package/primitives/timeline-item-group.js.map +1 -0
- package/primitives/timeline-item.d.ts +75 -0
- package/primitives/timeline-item.d.ts.map +1 -0
- package/primitives/timeline-item.js +145 -0
- package/primitives/timeline-item.js.map +1 -0
- package/primitives/window.d.ts +31 -0
- package/primitives/window.d.ts.map +1 -0
- package/primitives/window.js +58 -0
- package/primitives/window.js.map +1 -0
- package/provider.d.ts +95 -0
- package/provider.d.ts.map +1 -0
- package/provider.js +124 -0
- package/provider.js.map +1 -0
- package/realtime/event-filter.d.ts +8 -0
- package/realtime/event-filter.d.ts.map +1 -0
- package/realtime/event-filter.js +21 -0
- package/realtime/event-filter.js.map +1 -0
- package/realtime/index.d.ts +6 -0
- package/realtime/index.js +7 -0
- package/realtime/provider.d.ts +57 -0
- package/realtime/provider.d.ts.map +1 -0
- package/realtime/provider.js +351 -0
- package/realtime/provider.js.map +1 -0
- package/realtime/seen-store.d.ts +23 -0
- package/realtime/seen-store.d.ts.map +1 -0
- package/realtime/seen-store.js +34 -0
- package/realtime/seen-store.js.map +1 -0
- package/realtime/support-provider.d.ts +17 -0
- package/realtime/support-provider.d.ts.map +1 -0
- package/realtime/support-provider.js +54 -0
- package/realtime/support-provider.js.map +1 -0
- package/realtime/typing-store.d.ts +30 -0
- package/realtime/typing-store.d.ts.map +1 -0
- package/realtime/typing-store.js +34 -0
- package/realtime/typing-store.js.map +1 -0
- package/realtime/use-realtime.d.ts +29 -0
- package/realtime/use-realtime.d.ts.map +1 -0
- package/realtime/use-realtime.js +47 -0
- package/realtime/use-realtime.js.map +1 -0
- package/realtime-events.d.ts +344 -0
- package/realtime-events.d.ts.map +1 -0
- package/schemas.d.ts +90 -0
- package/schemas.d.ts.map +1 -0
- package/support/components/avatar-stack.d.ts +45 -0
- package/support/components/avatar-stack.d.ts.map +1 -0
- package/support/components/avatar-stack.js +72 -0
- package/support/components/avatar-stack.js.map +1 -0
- package/support/components/avatar.d.ts +15 -0
- package/support/components/avatar.d.ts.map +1 -0
- package/support/components/avatar.js +23 -0
- package/support/components/avatar.js.map +1 -0
- package/support/components/bubble.d.ts +10 -0
- package/support/components/bubble.d.ts.map +1 -0
- package/support/components/bubble.js +95 -0
- package/support/components/bubble.js.map +1 -0
- package/support/components/button.d.ts +20 -0
- package/support/components/button.d.ts.map +1 -0
- package/support/components/button.js +41 -0
- package/support/components/button.js.map +1 -0
- package/support/components/container.d.ts +14 -0
- package/support/components/container.d.ts.map +1 -0
- package/support/components/container.js +115 -0
- package/support/components/container.js.map +1 -0
- package/support/components/conversation-button-link.d.ts +34 -0
- package/support/components/conversation-button-link.d.ts.map +1 -0
- package/support/components/conversation-button-link.js +195 -0
- package/support/components/conversation-button-link.js.map +1 -0
- package/support/components/conversation-event.d.ts +14 -0
- package/support/components/conversation-event.d.ts.map +1 -0
- package/support/components/conversation-event.js +76 -0
- package/support/components/conversation-event.js.map +1 -0
- package/support/components/conversation-timeline.d.ts +17 -0
- package/support/components/conversation-timeline.d.ts.map +1 -0
- package/support/components/conversation-timeline.js +95 -0
- package/support/components/conversation-timeline.js.map +1 -0
- package/support/components/cossistant-branding.d.ts +12 -0
- package/support/components/cossistant-branding.d.ts.map +1 -0
- package/support/components/cossistant-branding.js +22 -0
- package/support/components/cossistant-branding.js.map +1 -0
- package/support/components/header.d.ts +11 -0
- package/support/components/header.d.ts.map +1 -0
- package/support/components/header.js +43 -0
- package/support/components/header.js.map +1 -0
- package/support/components/icons.d.ts +21 -0
- package/support/components/icons.d.ts.map +1 -0
- package/support/components/icons.js +131 -0
- package/support/components/icons.js.map +1 -0
- package/support/components/index.d.ts +11 -0
- package/support/components/index.js +12 -0
- package/support/components/multimodal-input.d.ts +28 -0
- package/support/components/multimodal-input.d.ts.map +1 -0
- package/support/components/multimodal-input.js +138 -0
- package/support/components/multimodal-input.js.map +1 -0
- package/support/components/navigation-tab.d.ts +7 -0
- package/support/components/navigation-tab.d.ts.map +1 -0
- package/support/components/navigation-tab.js +40 -0
- package/support/components/navigation-tab.js.map +1 -0
- package/support/components/support-content.d.ts +22 -0
- package/support/components/support-content.d.ts.map +1 -0
- package/support/components/support-content.js +50 -0
- package/support/components/support-content.js.map +1 -0
- package/support/components/text-effect.d.ts +49 -0
- package/support/components/text-effect.d.ts.map +1 -0
- package/support/components/text-effect.js +221 -0
- package/support/components/text-effect.js.map +1 -0
- package/support/components/timeline-message-group.d.ts +16 -0
- package/support/components/timeline-message-group.d.ts.map +1 -0
- package/support/components/timeline-message-group.js +117 -0
- package/support/components/timeline-message-group.js.map +1 -0
- package/support/components/timeline-message-item.d.ts +17 -0
- package/support/components/timeline-message-item.d.ts.map +1 -0
- package/support/components/timeline-message-item.js +42 -0
- package/support/components/timeline-message-item.js.map +1 -0
- package/support/components/typing-indicator.d.ts +26 -0
- package/support/components/typing-indicator.d.ts.map +1 -0
- package/support/components/typing-indicator.js +37 -0
- package/support/components/typing-indicator.js.map +1 -0
- package/support/components/watermark.d.ts +8 -0
- package/support/components/watermark.d.ts.map +1 -0
- package/support/components/watermark.js +34 -0
- package/support/components/watermark.js.map +1 -0
- package/support/context/config.d.ts +32 -0
- package/support/context/config.d.ts.map +1 -0
- package/support/context/config.js +27 -0
- package/support/context/config.js.map +1 -0
- package/support/context/websocket.d.ts +22 -0
- package/support/context/websocket.d.ts.map +1 -0
- package/support/context/websocket.js +113 -0
- package/support/context/websocket.js.map +1 -0
- package/support/index.d.ts +39 -0
- package/support/index.d.ts.map +1 -0
- package/support/index.js +43 -0
- package/support/index.js.map +1 -0
- package/support/pages/articles.d.ts +7 -0
- package/support/pages/articles.d.ts.map +1 -0
- package/support/pages/articles.js +39 -0
- package/support/pages/articles.js.map +1 -0
- package/support/pages/conversation-history.d.ts +18 -0
- package/support/pages/conversation-history.d.ts.map +1 -0
- package/support/pages/conversation-history.js +120 -0
- package/support/pages/conversation-history.js.map +1 -0
- package/support/pages/conversation.d.ts +32 -0
- package/support/pages/conversation.d.ts.map +1 -0
- package/support/pages/conversation.js +92 -0
- package/support/pages/conversation.js.map +1 -0
- package/support/pages/home.d.ts +20 -0
- package/support/pages/home.d.ts.map +1 -0
- package/support/pages/home.js +184 -0
- package/support/pages/home.js.map +1 -0
- package/support/router.d.ts +14 -0
- package/support/router.d.ts.map +1 -0
- package/support/router.js +31 -0
- package/support/router.js.map +1 -0
- package/support/store/index.d.ts +2 -0
- package/support/store/index.js +3 -0
- package/support/store/support-store.d.ts +42 -0
- package/support/store/support-store.d.ts.map +1 -0
- package/support/store/support-store.js +66 -0
- package/support/store/support-store.js.map +1 -0
- package/support/support-CMoDLQoC.css +408 -0
- package/support/support-CMoDLQoC.css.map +1 -0
- package/support/support.js +1 -0
- package/support/text/index.d.ts +35 -0
- package/support/text/index.d.ts.map +1 -0
- package/support/text/index.js +71 -0
- package/support/text/index.js.map +1 -0
- package/support/text/locales/en.d.ts +7 -0
- package/support/text/locales/en.d.ts.map +1 -0
- package/support/text/locales/en.js +65 -0
- package/support/text/locales/en.js.map +1 -0
- package/support/text/locales/es.d.ts +7 -0
- package/support/text/locales/es.d.ts.map +1 -0
- package/support/text/locales/es.js +64 -0
- package/support/text/locales/es.js.map +1 -0
- package/support/text/locales/fr.d.ts +7 -0
- package/support/text/locales/fr.d.ts.map +1 -0
- package/support/text/locales/fr.js +64 -0
- package/support/text/locales/fr.js.map +1 -0
- package/support/text/locales/keys.d.ts +216 -0
- package/support/text/locales/keys.d.ts.map +1 -0
- package/support/text/locales/keys.js +54 -0
- package/support/text/locales/keys.js.map +1 -0
- package/support/text/runtime.d.ts +17 -0
- package/support/text/runtime.d.ts.map +1 -0
- package/support/text/runtime.js +156 -0
- package/support/text/runtime.js.map +1 -0
- package/support/utils/index.d.ts +7 -0
- package/support/utils/index.d.ts.map +1 -0
- package/support/utils/index.js +11 -0
- package/support/utils/index.js.map +1 -0
- package/support/utils/time.d.ts +5 -0
- package/support/utils/time.d.ts.map +1 -0
- package/support/utils/time.js +28 -0
- package/support/utils/time.js.map +1 -0
- package/support-config.d.ts +20 -0
- package/support-config.d.ts.map +1 -0
- package/support-config.js +25 -0
- package/support-config.js.map +1 -0
- package/support.css +2 -0
- package/timeline-item.d.ts +133 -0
- package/timeline-item.d.ts.map +1 -0
- package/utils/id.d.ts +6 -0
- package/utils/id.d.ts.map +1 -0
- package/utils/id.js +13 -0
- package/utils/id.js.map +1 -0
- package/utils/index.d.ts +3 -0
- package/utils/index.js +4 -0
- package/utils/text.d.ts +5 -0
- package/utils/text.d.ts.map +1 -0
- package/utils/text.js +9 -0
- package/utils/text.js.map +1 -0
- package/utils/use-render-element.d.ts +22 -0
- package/utils/use-render-element.d.ts.map +1 -0
- package/utils/use-render-element.js +35 -0
- package/utils/use-render-element.js.map +1 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/hooks/private/use-client-query.ts
|
|
4
|
+
function toError(error) {
|
|
5
|
+
if (error instanceof Error) return error;
|
|
6
|
+
return new Error(typeof error === "string" ? error : "Unknown error");
|
|
7
|
+
}
|
|
8
|
+
const EMPTY_DEPENDENCIES = [];
|
|
9
|
+
function useClientQuery(options) {
|
|
10
|
+
const { client, queryFn, enabled = true, refetchInterval = false, refetchOnWindowFocus = true, refetchOnMount = true, initialData, initialArgs, dependencies = EMPTY_DEPENDENCIES } = options;
|
|
11
|
+
const [data, setData] = useState(initialData);
|
|
12
|
+
const [error, setError] = useState(null);
|
|
13
|
+
const [isLoading, setIsLoading] = useState(initialData === void 0 && Boolean(enabled));
|
|
14
|
+
const dataRef = useRef(data);
|
|
15
|
+
dataRef.current = data;
|
|
16
|
+
const argsRef = useRef(initialArgs);
|
|
17
|
+
const fetchIdRef = useRef(0);
|
|
18
|
+
const hasMountedRef = useRef(false);
|
|
19
|
+
const hasFetchedRef = useRef(initialData !== void 0);
|
|
20
|
+
const isMountedRef = useRef(true);
|
|
21
|
+
const queryFnRef = useRef(queryFn);
|
|
22
|
+
queryFnRef.current = queryFn;
|
|
23
|
+
useEffect(() => () => {
|
|
24
|
+
isMountedRef.current = false;
|
|
25
|
+
}, []);
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
argsRef.current = initialArgs;
|
|
28
|
+
}, [initialArgs]);
|
|
29
|
+
const execute = useCallback(async (args, ignoreEnabled = false) => {
|
|
30
|
+
if (!(enabled || ignoreEnabled)) return dataRef.current;
|
|
31
|
+
const nextArgs = args ?? argsRef.current;
|
|
32
|
+
argsRef.current = nextArgs;
|
|
33
|
+
const fetchId = fetchIdRef.current + 1;
|
|
34
|
+
fetchIdRef.current = fetchId;
|
|
35
|
+
setIsLoading(true);
|
|
36
|
+
setError(null);
|
|
37
|
+
try {
|
|
38
|
+
const result = await queryFnRef.current(client, nextArgs);
|
|
39
|
+
if (!isMountedRef.current || fetchId !== fetchIdRef.current) return dataRef.current;
|
|
40
|
+
dataRef.current = result;
|
|
41
|
+
setData(result);
|
|
42
|
+
setError(null);
|
|
43
|
+
setIsLoading(false);
|
|
44
|
+
hasFetchedRef.current = true;
|
|
45
|
+
return result;
|
|
46
|
+
} catch (raw) {
|
|
47
|
+
if (!isMountedRef.current || fetchId !== fetchIdRef.current) return dataRef.current;
|
|
48
|
+
const normalized = toError(raw);
|
|
49
|
+
setError(normalized);
|
|
50
|
+
setIsLoading(false);
|
|
51
|
+
throw normalized;
|
|
52
|
+
}
|
|
53
|
+
}, [client, enabled]);
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
if (!enabled) {
|
|
56
|
+
setIsLoading(false);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const shouldFetchInitially = hasMountedRef.current ? true : refetchOnMount || !hasFetchedRef.current;
|
|
60
|
+
hasMountedRef.current = true;
|
|
61
|
+
if (!shouldFetchInitially) return;
|
|
62
|
+
execute(argsRef.current);
|
|
63
|
+
}, [
|
|
64
|
+
enabled,
|
|
65
|
+
execute,
|
|
66
|
+
refetchOnMount,
|
|
67
|
+
...dependencies
|
|
68
|
+
]);
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
if (!enabled) return;
|
|
71
|
+
if (refetchInterval === false || refetchInterval === null || refetchInterval <= 0 || typeof window === "undefined") return;
|
|
72
|
+
const timer = window.setInterval(() => {
|
|
73
|
+
execute(argsRef.current);
|
|
74
|
+
}, refetchInterval);
|
|
75
|
+
return () => {
|
|
76
|
+
window.clearInterval(timer);
|
|
77
|
+
};
|
|
78
|
+
}, [
|
|
79
|
+
enabled,
|
|
80
|
+
execute,
|
|
81
|
+
refetchInterval
|
|
82
|
+
]);
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
if (!refetchOnWindowFocus || typeof window === "undefined" || typeof document === "undefined") return;
|
|
85
|
+
const handleRefetch = () => {
|
|
86
|
+
if (!enabled) return;
|
|
87
|
+
execute(argsRef.current);
|
|
88
|
+
};
|
|
89
|
+
const onFocus = () => {
|
|
90
|
+
handleRefetch();
|
|
91
|
+
};
|
|
92
|
+
const onVisibilityChange = () => {
|
|
93
|
+
if (document.visibilityState === "visible") handleRefetch();
|
|
94
|
+
};
|
|
95
|
+
window.addEventListener("focus", onFocus);
|
|
96
|
+
document.addEventListener("visibilitychange", onVisibilityChange);
|
|
97
|
+
return () => {
|
|
98
|
+
window.removeEventListener("focus", onFocus);
|
|
99
|
+
document.removeEventListener("visibilitychange", onVisibilityChange);
|
|
100
|
+
};
|
|
101
|
+
}, [
|
|
102
|
+
enabled,
|
|
103
|
+
execute,
|
|
104
|
+
refetchOnWindowFocus
|
|
105
|
+
]);
|
|
106
|
+
const refetch = useCallback(async (args) => execute(args, true), [execute]);
|
|
107
|
+
return useMemo(() => ({
|
|
108
|
+
data,
|
|
109
|
+
error,
|
|
110
|
+
isLoading,
|
|
111
|
+
refetch
|
|
112
|
+
}), [
|
|
113
|
+
data,
|
|
114
|
+
error,
|
|
115
|
+
isLoading,
|
|
116
|
+
refetch
|
|
117
|
+
]);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
//#endregion
|
|
121
|
+
export { useClientQuery };
|
|
122
|
+
//# sourceMappingURL=use-client-query.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-client-query.js","names":["EMPTY_DEPENDENCIES: readonly unknown[]","raw: unknown"],"sources":["../../../src/hooks/private/use-client-query.ts"],"sourcesContent":["import type { CossistantClient } from \"@cossistant/core\";\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\n\ntype QueryFn<TData, TArgs> = (\n\tclient: CossistantClient,\n\targs?: TArgs | undefined\n) => Promise<TData>;\n\ntype UseClientQueryOptions<TData, TArgs> = {\n\tclient: CossistantClient;\n\tqueryFn: QueryFn<TData, TArgs>;\n\tenabled?: boolean;\n\trefetchInterval?: number | false;\n\trefetchOnWindowFocus?: boolean;\n\trefetchOnMount?: boolean;\n\tinitialData?: TData;\n\tinitialArgs?: TArgs;\n\tdependencies?: readonly unknown[];\n};\n\ntype UseClientQueryResult<TData, TArgs> = {\n\tdata: TData | undefined;\n\terror: Error | null;\n\tisLoading: boolean;\n\trefetch: (args?: TArgs) => Promise<TData | undefined>;\n};\n\nfunction toError(error: unknown): Error {\n\tif (error instanceof Error) {\n\t\treturn error;\n\t}\n\n\treturn new Error(typeof error === \"string\" ? error : \"Unknown error\");\n}\n\nconst EMPTY_DEPENDENCIES: readonly unknown[] = [];\n\nexport function useClientQuery<TData, TArgs = void>(\n\toptions: UseClientQueryOptions<TData, TArgs>\n): UseClientQueryResult<TData, TArgs> {\n\tconst {\n\t\tclient,\n\t\tqueryFn,\n\t\tenabled = true,\n\t\trefetchInterval = false,\n\t\trefetchOnWindowFocus = true,\n\t\trefetchOnMount = true,\n\t\tinitialData,\n\t\tinitialArgs,\n\t\tdependencies = EMPTY_DEPENDENCIES,\n\t} = options;\n\n\tconst [data, setData] = useState<TData | undefined>(initialData);\n\tconst [error, setError] = useState<Error | null>(null);\n\tconst [isLoading, setIsLoading] = useState(\n\t\tinitialData === undefined && Boolean(enabled)\n\t);\n\n\tconst dataRef = useRef(data);\n\tdataRef.current = data;\n\n\tconst argsRef = useRef<TArgs | undefined>(initialArgs);\n\tconst fetchIdRef = useRef(0);\n\tconst hasMountedRef = useRef(false);\n\tconst hasFetchedRef = useRef(initialData !== undefined);\n\tconst isMountedRef = useRef(true);\n\tconst queryFnRef = useRef(queryFn);\n\n\tqueryFnRef.current = queryFn;\n\n\tuseEffect(\n\t\t() => () => {\n\t\t\tisMountedRef.current = false;\n\t\t},\n\t\t[]\n\t);\n\n\tuseEffect(() => {\n\t\targsRef.current = initialArgs;\n\t}, [initialArgs]);\n\n\tconst execute = useCallback(\n\t\tasync (args?: TArgs, ignoreEnabled = false): Promise<TData | undefined> => {\n\t\t\tif (!(enabled || ignoreEnabled)) {\n\t\t\t\treturn dataRef.current;\n\t\t\t}\n\n\t\t\tconst nextArgs = args ?? argsRef.current;\n\t\t\targsRef.current = nextArgs;\n\n\t\t\tconst fetchId = fetchIdRef.current + 1;\n\t\t\tfetchIdRef.current = fetchId;\n\n\t\t\tsetIsLoading(true);\n\t\t\tsetError(null);\n\n\t\t\ttry {\n\t\t\t\tconst result = await queryFnRef.current(client, nextArgs);\n\n\t\t\t\tif (!isMountedRef.current || fetchId !== fetchIdRef.current) {\n\t\t\t\t\treturn dataRef.current;\n\t\t\t\t}\n\n\t\t\t\tdataRef.current = result;\n\t\t\t\tsetData(result);\n\t\t\t\tsetError(null);\n\t\t\t\tsetIsLoading(false);\n\t\t\t\thasFetchedRef.current = true;\n\n\t\t\t\treturn result;\n\t\t\t} catch (raw: unknown) {\n\t\t\t\tif (!isMountedRef.current || fetchId !== fetchIdRef.current) {\n\t\t\t\t\treturn dataRef.current;\n\t\t\t\t}\n\n\t\t\t\tconst normalized = toError(raw);\n\t\t\t\tsetError(normalized);\n\t\t\t\tsetIsLoading(false);\n\n\t\t\t\tthrow normalized;\n\t\t\t}\n\t\t},\n\t\t[client, enabled]\n\t);\n\n\tuseEffect(() => {\n\t\tif (!enabled) {\n\t\t\tsetIsLoading(false);\n\t\t\treturn;\n\t\t}\n\n\t\tconst shouldFetchInitially = hasMountedRef.current\n\t\t\t? true\n\t\t\t: refetchOnMount || !hasFetchedRef.current;\n\n\t\thasMountedRef.current = true;\n\n\t\tif (!shouldFetchInitially) {\n\t\t\treturn;\n\t\t}\n\n\t\tvoid execute(argsRef.current);\n\t}, [enabled, execute, refetchOnMount, ...dependencies]);\n\n\tuseEffect(() => {\n\t\tif (!enabled) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (\n\t\t\trefetchInterval === false ||\n\t\t\trefetchInterval === null ||\n\t\t\trefetchInterval <= 0 ||\n\t\t\ttypeof window === \"undefined\"\n\t\t) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst timer = window.setInterval(() => {\n\t\t\tvoid execute(argsRef.current);\n\t\t}, refetchInterval);\n\n\t\treturn () => {\n\t\t\twindow.clearInterval(timer);\n\t\t};\n\t}, [enabled, execute, refetchInterval]);\n\n\tuseEffect(() => {\n\t\tif (\n\t\t\t!refetchOnWindowFocus ||\n\t\t\ttypeof window === \"undefined\" ||\n\t\t\ttypeof document === \"undefined\"\n\t\t) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst handleRefetch = () => {\n\t\t\tif (!enabled) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tvoid execute(argsRef.current);\n\t\t};\n\n\t\tconst onFocus = () => {\n\t\t\tvoid handleRefetch();\n\t\t};\n\n\t\tconst onVisibilityChange = () => {\n\t\t\tif (document.visibilityState === \"visible\") {\n\t\t\t\tvoid handleRefetch();\n\t\t\t}\n\t\t};\n\n\t\twindow.addEventListener(\"focus\", onFocus);\n\t\tdocument.addEventListener(\"visibilitychange\", onVisibilityChange);\n\n\t\treturn () => {\n\t\t\twindow.removeEventListener(\"focus\", onFocus);\n\t\t\tdocument.removeEventListener(\"visibilitychange\", onVisibilityChange);\n\t\t};\n\t}, [enabled, execute, refetchOnWindowFocus]);\n\n\tconst refetch = useCallback(\n\t\tasync (args?: TArgs) => execute(args, true),\n\t\t[execute]\n\t);\n\n\treturn useMemo(\n\t\t() => ({\n\t\t\tdata,\n\t\t\terror,\n\t\t\tisLoading,\n\t\t\trefetch,\n\t\t}),\n\t\t[data, error, isLoading, refetch]\n\t);\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { TimelineItem } from "../../timeline-item.js";
|
|
2
|
+
|
|
3
|
+
//#region src/hooks/private/use-default-messages.d.ts
|
|
4
|
+
type UseDefaultMessagesParams = {
|
|
5
|
+
conversationId: string;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Mirrors the provider-configured default messages into timeline items so
|
|
9
|
+
* that welcome content renders immediately while the backend conversation is
|
|
10
|
+
* still being created. Agent fallbacks are resolved against available humans
|
|
11
|
+
* and AI agents exposed by the provider context.
|
|
12
|
+
*/
|
|
13
|
+
declare function useDefaultMessages({
|
|
14
|
+
conversationId
|
|
15
|
+
}: UseDefaultMessagesParams): TimelineItem[];
|
|
16
|
+
//#endregion
|
|
17
|
+
export { useDefaultMessages };
|
|
18
|
+
//# sourceMappingURL=use-default-messages.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-default-messages.d.ts","names":[],"sources":["../../../src/hooks/private/use-default-messages.ts"],"sourcesContent":[],"mappings":";;;KAMK,wBAAA;;AAJmE,CAAA;AAcxE;;;;;;iBAAgB,kBAAA;;GAEb,2BAA2B"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { useSupport } from "../../provider.js";
|
|
2
|
+
import { useMemo } from "react";
|
|
3
|
+
import { generateMessageId } from "@cossistant/core";
|
|
4
|
+
import { SenderType } from "@cossistant/types";
|
|
5
|
+
|
|
6
|
+
//#region src/hooks/private/use-default-messages.ts
|
|
7
|
+
/**
|
|
8
|
+
* Mirrors the provider-configured default messages into timeline items so
|
|
9
|
+
* that welcome content renders immediately while the backend conversation is
|
|
10
|
+
* still being created. Agent fallbacks are resolved against available humans
|
|
11
|
+
* and AI agents exposed by the provider context.
|
|
12
|
+
*/
|
|
13
|
+
function useDefaultMessages({ conversationId }) {
|
|
14
|
+
const { defaultMessages, availableAIAgents, availableHumanAgents } = useSupport();
|
|
15
|
+
return useMemo(() => defaultMessages.map((message, index) => {
|
|
16
|
+
const messageId = generateMessageId();
|
|
17
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
18
|
+
return {
|
|
19
|
+
id: messageId,
|
|
20
|
+
conversationId,
|
|
21
|
+
organizationId: "",
|
|
22
|
+
type: "message",
|
|
23
|
+
text: message.content,
|
|
24
|
+
parts: [{
|
|
25
|
+
type: "text",
|
|
26
|
+
text: message.content
|
|
27
|
+
}],
|
|
28
|
+
visibility: "public",
|
|
29
|
+
userId: message.senderType === SenderType.TEAM_MEMBER ? message.senderId || availableHumanAgents[0]?.id || null : null,
|
|
30
|
+
aiAgentId: message.senderType === SenderType.AI ? message.senderId || availableAIAgents[0]?.id || null : null,
|
|
31
|
+
visitorId: message.senderType === SenderType.VISITOR ? message.senderId || null : null,
|
|
32
|
+
createdAt: timestamp,
|
|
33
|
+
deletedAt: null
|
|
34
|
+
};
|
|
35
|
+
}), [
|
|
36
|
+
defaultMessages,
|
|
37
|
+
availableHumanAgents[0]?.id,
|
|
38
|
+
availableAIAgents[0]?.id,
|
|
39
|
+
conversationId
|
|
40
|
+
]);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
//#endregion
|
|
44
|
+
export { useDefaultMessages };
|
|
45
|
+
//# sourceMappingURL=use-default-messages.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-default-messages.js","names":[],"sources":["../../../src/hooks/private/use-default-messages.ts"],"sourcesContent":["import { generateMessageId } from \"@cossistant/core\";\nimport { SenderType } from \"@cossistant/types\";\nimport type { TimelineItem } from \"@cossistant/types/api/timeline-item\";\nimport { useMemo } from \"react\";\nimport { useSupport } from \"../../provider\";\n\ntype UseDefaultMessagesParams = {\n\tconversationId: string;\n};\n\n/**\n * Mirrors the provider-configured default messages into timeline items so\n * that welcome content renders immediately while the backend conversation is\n * still being created. Agent fallbacks are resolved against available humans\n * and AI agents exposed by the provider context.\n */\nexport function useDefaultMessages({\n\tconversationId,\n}: UseDefaultMessagesParams): TimelineItem[] {\n\tconst { defaultMessages, availableAIAgents, availableHumanAgents } =\n\t\tuseSupport();\n\n\tconst memoisedDefaultTimelineItems = useMemo(\n\t\t() =>\n\t\t\tdefaultMessages.map((message, index) => {\n\t\t\t\tconst messageId = generateMessageId();\n\t\t\t\tconst timestamp = new Date().toISOString();\n\n\t\t\t\treturn {\n\t\t\t\t\tid: messageId,\n\t\t\t\t\tconversationId,\n\t\t\t\t\torganizationId: \"\", // Not available for default messages\n\t\t\t\t\ttype: \"message\" as const,\n\t\t\t\t\ttext: message.content,\n\t\t\t\t\tparts: [{ type: \"text\" as const, text: message.content }],\n\t\t\t\t\tvisibility: \"public\" as const,\n\t\t\t\t\tuserId:\n\t\t\t\t\t\tmessage.senderType === SenderType.TEAM_MEMBER\n\t\t\t\t\t\t\t? message.senderId || availableHumanAgents[0]?.id || null\n\t\t\t\t\t\t\t: null,\n\t\t\t\t\taiAgentId:\n\t\t\t\t\t\tmessage.senderType === SenderType.AI\n\t\t\t\t\t\t\t? message.senderId || availableAIAgents[0]?.id || null\n\t\t\t\t\t\t\t: null,\n\t\t\t\t\tvisitorId:\n\t\t\t\t\t\tmessage.senderType === SenderType.VISITOR\n\t\t\t\t\t\t\t? message.senderId || null\n\t\t\t\t\t\t\t: null,\n\t\t\t\t\tcreatedAt: timestamp,\n\t\t\t\t\tdeletedAt: null,\n\t\t\t\t} satisfies TimelineItem;\n\t\t\t}),\n\t\t[\n\t\t\tdefaultMessages,\n\t\t\tavailableHumanAgents[0]?.id,\n\t\t\tavailableAIAgents[0]?.id,\n\t\t\tconversationId,\n\t\t]\n\t);\n\n\treturn memoisedDefaultTimelineItems;\n}\n"],"mappings":";;;;;;;;;;;;AAgBA,SAAgB,mBAAmB,EAClC,kBAC4C;CAC5C,MAAM,EAAE,iBAAiB,mBAAmB,yBAC3C,YAAY;AAwCb,QAtCqC,cAEnC,gBAAgB,KAAK,SAAS,UAAU;EACvC,MAAM,YAAY,mBAAmB;EACrC,MAAM,6BAAY,IAAI,MAAM,EAAC,aAAa;AAE1C,SAAO;GACN,IAAI;GACJ;GACA,gBAAgB;GAChB,MAAM;GACN,MAAM,QAAQ;GACd,OAAO,CAAC;IAAE,MAAM;IAAiB,MAAM,QAAQ;IAAS,CAAC;GACzD,YAAY;GACZ,QACC,QAAQ,eAAe,WAAW,cAC/B,QAAQ,YAAY,qBAAqB,IAAI,MAAM,OACnD;GACJ,WACC,QAAQ,eAAe,WAAW,KAC/B,QAAQ,YAAY,kBAAkB,IAAI,MAAM,OAChD;GACJ,WACC,QAAQ,eAAe,WAAW,UAC/B,QAAQ,YAAY,OACpB;GACJ,WAAW;GACX,WAAW;GACX;GACA,EACH;EACC;EACA,qBAAqB,IAAI;EACzB,kBAAkB,IAAI;EACtB;EACA,CACD"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { TimelineItem } from "../../timeline-item.js";
|
|
2
|
+
import { ConversationSeen } from "../../schemas.js";
|
|
3
|
+
import { SenderType } from "@cossistant/types";
|
|
4
|
+
|
|
5
|
+
//#region src/hooks/private/use-grouped-messages.d.ts
|
|
6
|
+
type GroupedMessage = {
|
|
7
|
+
type: "message_group";
|
|
8
|
+
senderId: string;
|
|
9
|
+
senderType: SenderType;
|
|
10
|
+
items: TimelineItem[];
|
|
11
|
+
firstMessageId: string;
|
|
12
|
+
lastMessageId: string;
|
|
13
|
+
firstMessageTime: Date;
|
|
14
|
+
lastMessageTime: Date;
|
|
15
|
+
};
|
|
16
|
+
type TimelineEventItem = {
|
|
17
|
+
type: "timeline_event";
|
|
18
|
+
item: TimelineItem;
|
|
19
|
+
timestamp: Date;
|
|
20
|
+
};
|
|
21
|
+
type ConversationItem = GroupedMessage | TimelineEventItem;
|
|
22
|
+
type UseGroupedMessagesOptions = {
|
|
23
|
+
items: TimelineItem[];
|
|
24
|
+
seenData?: ConversationSeen[];
|
|
25
|
+
currentViewerId?: string;
|
|
26
|
+
viewerType?: SenderType;
|
|
27
|
+
};
|
|
28
|
+
type UseGroupedMessagesProps = UseGroupedMessagesOptions;
|
|
29
|
+
/**
|
|
30
|
+
* Batches sequential timeline items from the same sender into groups and enriches
|
|
31
|
+
* them with read-receipt helpers so UIs can render conversation timelines with
|
|
32
|
+
* minimal effort. Seen data is normalised into quick lookup maps for unread
|
|
33
|
+
* indicators.
|
|
34
|
+
*/
|
|
35
|
+
declare const useGroupedMessages: ({
|
|
36
|
+
items,
|
|
37
|
+
seenData,
|
|
38
|
+
currentViewerId,
|
|
39
|
+
viewerType
|
|
40
|
+
}: UseGroupedMessagesOptions) => {
|
|
41
|
+
items: (GroupedMessage | TimelineEventItem)[];
|
|
42
|
+
seenByMap: Map<string, Set<string>>;
|
|
43
|
+
lastReadMessageMap: Map<string, string>;
|
|
44
|
+
unreadCountMap: Map<string, number>;
|
|
45
|
+
isMessageSeenByViewer: (messageId: string) => boolean;
|
|
46
|
+
getMessageSeenBy: (messageId: string) => string[];
|
|
47
|
+
getLastReadMessageId: (userId: string) => string | undefined;
|
|
48
|
+
isLastReadMessage: (messageId: string, userId: string) => boolean;
|
|
49
|
+
getUnreadCount: (userId: string) => number;
|
|
50
|
+
hasUnreadAfter: (messageId: string, userId: string) => boolean;
|
|
51
|
+
};
|
|
52
|
+
//#endregion
|
|
53
|
+
export { ConversationItem, GroupedMessage, TimelineEventItem, UseGroupedMessagesOptions, UseGroupedMessagesProps, useGroupedMessages };
|
|
54
|
+
//# sourceMappingURL=use-grouped-messages.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-grouped-messages.d.ts","names":[],"sources":["../../../src/hooks/private/use-grouped-messages.ts"],"sourcesContent":[],"mappings":";;;;;KAKY,cAAA;;EAAA,QAAA,EAAA,MAAA;EAAc,UAAA,EAGb,UAHa;OAGb,EACL,YADK,EAAA;gBACL,EAAA,MAAA;eAGW,EAAA,MAAA;kBACD,EADC,IACD;EAAI,eAAA,EAAJ,IAAI;AAGtB,CAAA;AAA6B,KAAjB,iBAAA,GAAiB;MAEtB,EAAA,gBAAA;MACK,EADL,YACK;EAAI,SAAA,EAAJ,IAAI;AAGhB,CAAA;AAA4B,KAAhB,gBAAA,GAAmB,cAAH,GAAoB,iBAApB;AAAG,KAEnB,yBAAA,GAFmB;OAAiB,EAGxC,YAHwC,EAAA;EAAiB,QAAA,CAAA,EAIrD,gBAJqD,EAAA;EAErD,eAAA,CAAA,EAAA,MAAA;EAAyB,UAAA,CAAA,EAIvB,UAJuB;;AAEzB,KAKA,uBAAA,GAA0B,yBAL1B;;;AAKZ;AAuMA;;;AAAmC,cAAtB,kBAAsB,EAAA,CAAA;EAAA,KAAA;EAAA,QAAA;EAAA,eAAA;EAAA;AAAA,CAAA,EAKhC,yBALgC,EAAA,GAAA;OAAA,EAAA,eAAA,oBAAA,CAAA,EAAA;WAAA,KAAA,CAAA,MAAA,KAAA,CAAA,MAAA,CAAA,CAAA;oBAKhC,KAAA,CAAA,MAAA,EAAA,MAAA,CAAA"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import { SenderType } from "@cossistant/types";
|
|
3
|
+
|
|
4
|
+
//#region src/hooks/private/use-grouped-messages.ts
|
|
5
|
+
const getTimestamp = (date) => {
|
|
6
|
+
if (!date) return 0;
|
|
7
|
+
if (typeof date === "string") return new Date(date).getTime();
|
|
8
|
+
return date.getTime();
|
|
9
|
+
};
|
|
10
|
+
const toDate = (date) => {
|
|
11
|
+
if (!date) return /* @__PURE__ */ new Date();
|
|
12
|
+
if (typeof date === "string") return new Date(date);
|
|
13
|
+
return date;
|
|
14
|
+
};
|
|
15
|
+
const getSenderIdAndTypeFromTimelineItem = (item) => {
|
|
16
|
+
if (item.visitorId) return {
|
|
17
|
+
senderId: item.visitorId,
|
|
18
|
+
senderType: SenderType.VISITOR
|
|
19
|
+
};
|
|
20
|
+
if (item.aiAgentId) return {
|
|
21
|
+
senderId: item.aiAgentId,
|
|
22
|
+
senderType: SenderType.AI
|
|
23
|
+
};
|
|
24
|
+
if (item.userId) return {
|
|
25
|
+
senderId: item.userId,
|
|
26
|
+
senderType: SenderType.TEAM_MEMBER
|
|
27
|
+
};
|
|
28
|
+
return {
|
|
29
|
+
senderId: item.id || "default-sender",
|
|
30
|
+
senderType: SenderType.TEAM_MEMBER
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
const groupTimelineItems = (items) => {
|
|
34
|
+
const result = [];
|
|
35
|
+
let currentGroup = null;
|
|
36
|
+
for (const item of items) {
|
|
37
|
+
if (item.type === "event") {
|
|
38
|
+
if (currentGroup) {
|
|
39
|
+
result.push(currentGroup);
|
|
40
|
+
currentGroup = null;
|
|
41
|
+
}
|
|
42
|
+
result.push({
|
|
43
|
+
type: "timeline_event",
|
|
44
|
+
item,
|
|
45
|
+
timestamp: toDate(item.createdAt)
|
|
46
|
+
});
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const { senderId, senderType } = getSenderIdAndTypeFromTimelineItem(item);
|
|
50
|
+
if (currentGroup && currentGroup.senderId === senderId) {
|
|
51
|
+
currentGroup.items.push(item);
|
|
52
|
+
currentGroup.lastMessageId = item.id || currentGroup.lastMessageId;
|
|
53
|
+
currentGroup.lastMessageTime = toDate(item.createdAt);
|
|
54
|
+
} else {
|
|
55
|
+
if (currentGroup) result.push(currentGroup);
|
|
56
|
+
currentGroup = {
|
|
57
|
+
type: "message_group",
|
|
58
|
+
senderId,
|
|
59
|
+
senderType,
|
|
60
|
+
items: [item],
|
|
61
|
+
firstMessageId: item.id || "",
|
|
62
|
+
lastMessageId: item.id || "",
|
|
63
|
+
firstMessageTime: toDate(item.createdAt),
|
|
64
|
+
lastMessageTime: toDate(item.createdAt)
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (currentGroup) result.push(currentGroup);
|
|
69
|
+
return result;
|
|
70
|
+
};
|
|
71
|
+
const buildTimelineReadReceiptData = (seenData, items) => {
|
|
72
|
+
const seenByMap = /* @__PURE__ */ new Map();
|
|
73
|
+
const lastReadMessageMap = /* @__PURE__ */ new Map();
|
|
74
|
+
const unreadCountMap = /* @__PURE__ */ new Map();
|
|
75
|
+
for (const item of items) if (item.type === "message" && item.id) seenByMap.set(item.id, /* @__PURE__ */ new Set());
|
|
76
|
+
const sortedItems = [...items].filter((item) => item.type === "message").sort((a, b) => getTimestamp(a.createdAt) - getTimestamp(b.createdAt));
|
|
77
|
+
for (const seen of seenData) {
|
|
78
|
+
let seenTime = getTimestamp(seen.updatedAt);
|
|
79
|
+
const viewerId = seen.userId || seen.visitorId || seen.aiAgentId;
|
|
80
|
+
if (!viewerId) continue;
|
|
81
|
+
const lastItemByViewer = sortedItems.filter((item) => {
|
|
82
|
+
if (seen.userId) return item.userId === viewerId;
|
|
83
|
+
if (seen.visitorId) return item.visitorId === viewerId;
|
|
84
|
+
if (seen.aiAgentId) return item.aiAgentId === viewerId;
|
|
85
|
+
return false;
|
|
86
|
+
}).at(-1);
|
|
87
|
+
if (lastItemByViewer) {
|
|
88
|
+
const lastItemTime = getTimestamp(lastItemByViewer.createdAt);
|
|
89
|
+
if (lastItemTime > seenTime) seenTime = lastItemTime;
|
|
90
|
+
}
|
|
91
|
+
let lastReadItem = null;
|
|
92
|
+
let unreadCount = 0;
|
|
93
|
+
let hasPassedLastSeen = false;
|
|
94
|
+
for (const item of sortedItems) if (getTimestamp(item.createdAt) <= seenTime && !hasPassedLastSeen) {
|
|
95
|
+
if (item.id) {
|
|
96
|
+
const seenBy = seenByMap.get(item.id);
|
|
97
|
+
if (seenBy) seenBy.add(viewerId);
|
|
98
|
+
}
|
|
99
|
+
lastReadItem = item;
|
|
100
|
+
} else {
|
|
101
|
+
hasPassedLastSeen = true;
|
|
102
|
+
unreadCount++;
|
|
103
|
+
}
|
|
104
|
+
if (lastReadItem?.id) lastReadMessageMap.set(viewerId, lastReadItem.id);
|
|
105
|
+
unreadCountMap.set(viewerId, unreadCount);
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
seenByMap,
|
|
109
|
+
lastReadMessageMap,
|
|
110
|
+
unreadCountMap
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* Batches sequential timeline items from the same sender into groups and enriches
|
|
115
|
+
* them with read-receipt helpers so UIs can render conversation timelines with
|
|
116
|
+
* minimal effort. Seen data is normalised into quick lookup maps for unread
|
|
117
|
+
* indicators.
|
|
118
|
+
*/
|
|
119
|
+
const useGroupedMessages = ({ items, seenData = [], currentViewerId, viewerType }) => {
|
|
120
|
+
return useMemo(() => {
|
|
121
|
+
const groupedItems = groupTimelineItems(items);
|
|
122
|
+
const { seenByMap, lastReadMessageMap, unreadCountMap } = buildTimelineReadReceiptData(seenData, items);
|
|
123
|
+
return {
|
|
124
|
+
items: groupedItems,
|
|
125
|
+
seenByMap,
|
|
126
|
+
lastReadMessageMap,
|
|
127
|
+
unreadCountMap,
|
|
128
|
+
isMessageSeenByViewer: (messageId) => {
|
|
129
|
+
if (!currentViewerId) return false;
|
|
130
|
+
const seenBy = seenByMap.get(messageId);
|
|
131
|
+
return seenBy ? seenBy.has(currentViewerId) : false;
|
|
132
|
+
},
|
|
133
|
+
getMessageSeenBy: (messageId) => {
|
|
134
|
+
const seenBy = seenByMap.get(messageId);
|
|
135
|
+
return seenBy ? Array.from(seenBy) : [];
|
|
136
|
+
},
|
|
137
|
+
getLastReadMessageId: (userId) => lastReadMessageMap.get(userId),
|
|
138
|
+
isLastReadMessage: (messageId, userId) => lastReadMessageMap.get(userId) === messageId,
|
|
139
|
+
getUnreadCount: (userId) => unreadCountMap.get(userId) || 0,
|
|
140
|
+
hasUnreadAfter: (messageId, userId) => {
|
|
141
|
+
const lastRead = lastReadMessageMap.get(userId);
|
|
142
|
+
if (!lastRead) return true;
|
|
143
|
+
const messageIndex = items.findIndex((item) => item.id === messageId);
|
|
144
|
+
const lastReadIndex = items.findIndex((item) => item.id === lastRead);
|
|
145
|
+
return messageIndex < lastReadIndex;
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
}, [
|
|
149
|
+
items,
|
|
150
|
+
seenData,
|
|
151
|
+
currentViewerId
|
|
152
|
+
]);
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
//#endregion
|
|
156
|
+
export { useGroupedMessages };
|
|
157
|
+
//# sourceMappingURL=use-grouped-messages.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-grouped-messages.js","names":["result: Array<GroupedMessage | TimelineEventItem>","currentGroup: GroupedMessage | null","lastReadItem: TimelineItem | null"],"sources":["../../../src/hooks/private/use-grouped-messages.ts"],"sourcesContent":["import { SenderType } from \"@cossistant/types\";\nimport type { TimelineItem } from \"@cossistant/types/api/timeline-item\";\nimport type { ConversationSeen } from \"@cossistant/types/schemas\";\nimport { useMemo } from \"react\";\n\nexport type GroupedMessage = {\n\ttype: \"message_group\";\n\tsenderId: string;\n\tsenderType: SenderType;\n\titems: TimelineItem[];\n\tfirstMessageId: string;\n\tlastMessageId: string;\n\tfirstMessageTime: Date;\n\tlastMessageTime: Date;\n};\n\nexport type TimelineEventItem = {\n\ttype: \"timeline_event\";\n\titem: TimelineItem;\n\ttimestamp: Date;\n};\n\nexport type ConversationItem = GroupedMessage | TimelineEventItem;\n\nexport type UseGroupedMessagesOptions = {\n\titems: TimelineItem[];\n\tseenData?: ConversationSeen[];\n\tcurrentViewerId?: string; // The ID of the current viewer (visitor, user, or AI agent)\n\tviewerType?: SenderType; // Type of the current viewer\n};\n\nexport type UseGroupedMessagesProps = UseGroupedMessagesOptions;\n\n// Helper function to safely get timestamp from Date or string\nconst getTimestamp = (date: Date | string | null | undefined): number => {\n\tif (!date) {\n\t\treturn 0;\n\t}\n\tif (typeof date === \"string\") {\n\t\treturn new Date(date).getTime();\n\t}\n\treturn date.getTime();\n};\n\n// Helper function to safely convert to Date\nconst toDate = (date: Date | string | null | undefined): Date => {\n\tif (!date) {\n\t\treturn new Date();\n\t}\n\tif (typeof date === \"string\") {\n\t\treturn new Date(date);\n\t}\n\treturn date;\n};\n\n// Helper to determine sender ID and type from a timeline item\nconst getSenderIdAndTypeFromTimelineItem = (\n\titem: TimelineItem\n): { senderId: string; senderType: SenderType } => {\n\tif (item.visitorId) {\n\t\treturn { senderId: item.visitorId, senderType: SenderType.VISITOR };\n\t}\n\tif (item.aiAgentId) {\n\t\treturn { senderId: item.aiAgentId, senderType: SenderType.AI };\n\t}\n\tif (item.userId) {\n\t\treturn { senderId: item.userId, senderType: SenderType.TEAM_MEMBER };\n\t}\n\n\t// Fallback\n\treturn {\n\t\tsenderId: item.id || \"default-sender\",\n\t\tsenderType: SenderType.TEAM_MEMBER,\n\t};\n};\n\n// Helper function to group timeline items (messages only, events stay separate)\nconst groupTimelineItems = (\n\titems: TimelineItem[]\n): Array<GroupedMessage | TimelineEventItem> => {\n\tconst result: Array<GroupedMessage | TimelineEventItem> = [];\n\tlet currentGroup: GroupedMessage | null = null;\n\n\tfor (const item of items) {\n\t\t// Events don't get grouped\n\t\tif (item.type === \"event\") {\n\t\t\t// Finalize any existing group\n\t\t\tif (currentGroup) {\n\t\t\t\tresult.push(currentGroup);\n\t\t\t\tcurrentGroup = null;\n\t\t\t}\n\n\t\t\t// Add event as standalone item\n\t\t\tresult.push({\n\t\t\t\ttype: \"timeline_event\",\n\t\t\t\titem,\n\t\t\t\ttimestamp: toDate(item.createdAt),\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Group messages by sender\n\t\tconst { senderId, senderType } = getSenderIdAndTypeFromTimelineItem(item);\n\n\t\tif (currentGroup && currentGroup.senderId === senderId) {\n\t\t\t// Add to existing group\n\t\t\tcurrentGroup.items.push(item);\n\t\t\tcurrentGroup.lastMessageId = item.id || currentGroup.lastMessageId;\n\t\t\tcurrentGroup.lastMessageTime = toDate(item.createdAt);\n\t\t} else {\n\t\t\t// Finalize previous group if exists\n\t\t\tif (currentGroup) {\n\t\t\t\tresult.push(currentGroup);\n\t\t\t}\n\n\t\t\t// Start new group\n\t\t\tcurrentGroup = {\n\t\t\t\ttype: \"message_group\",\n\t\t\t\tsenderId,\n\t\t\t\tsenderType,\n\t\t\t\titems: [item],\n\t\t\t\tfirstMessageId: item.id || \"\",\n\t\t\t\tlastMessageId: item.id || \"\",\n\t\t\t\tfirstMessageTime: toDate(item.createdAt),\n\t\t\t\tlastMessageTime: toDate(item.createdAt),\n\t\t\t};\n\t\t}\n\t}\n\n\tif (currentGroup) {\n\t\tresult.push(currentGroup);\n\t}\n\n\treturn result;\n};\n\n// Build read receipt data for timeline items\nconst buildTimelineReadReceiptData = (\n\tseenData: ConversationSeen[],\n\titems: TimelineItem[]\n) => {\n\tconst seenByMap = new Map<string, Set<string>>();\n\tconst lastReadMessageMap = new Map<string, string>();\n\tconst unreadCountMap = new Map<string, number>();\n\n\t// Initialize map for all message-type timeline items\n\tfor (const item of items) {\n\t\tif (item.type === \"message\" && item.id) {\n\t\t\tseenByMap.set(item.id, new Set());\n\t\t}\n\t}\n\n\t// Sort items by time to process in order\n\tconst sortedItems = [...items]\n\t\t.filter((item) => item.type === \"message\")\n\t\t.sort((a, b) => getTimestamp(a.createdAt) - getTimestamp(b.createdAt));\n\n\t// Process seen data for each viewer\n\tfor (const seen of seenData) {\n\t\tlet seenTime = getTimestamp(seen.updatedAt);\n\t\tconst viewerId = seen.userId || seen.visitorId || seen.aiAgentId;\n\t\tif (!viewerId) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Find the last message sent by this viewer\n\t\tconst lastItemByViewer = sortedItems\n\t\t\t.filter((item) => {\n\t\t\t\tif (seen.userId) {\n\t\t\t\t\treturn item.userId === viewerId;\n\t\t\t\t}\n\t\t\t\tif (seen.visitorId) {\n\t\t\t\t\treturn item.visitorId === viewerId;\n\t\t\t\t}\n\t\t\t\tif (seen.aiAgentId) {\n\t\t\t\t\treturn item.aiAgentId === viewerId;\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t\t})\n\t\t\t.at(-1);\n\n\t\tif (lastItemByViewer) {\n\t\t\tconst lastItemTime = getTimestamp(lastItemByViewer.createdAt);\n\t\t\tif (lastItemTime > seenTime) {\n\t\t\t\tseenTime = lastItemTime;\n\t\t\t}\n\t\t}\n\n\t\tlet lastReadItem: TimelineItem | null = null;\n\t\tlet unreadCount = 0;\n\t\tlet hasPassedLastSeen = false;\n\n\t\t// Process items in chronological order\n\t\tfor (const item of sortedItems) {\n\t\t\tconst itemTime = getTimestamp(item.createdAt);\n\n\t\t\tif (itemTime <= seenTime && !hasPassedLastSeen) {\n\t\t\t\t// This item has been seen\n\t\t\t\tif (item.id) {\n\t\t\t\t\tconst seenBy = seenByMap.get(item.id);\n\t\t\t\t\tif (seenBy) {\n\t\t\t\t\t\tseenBy.add(viewerId);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tlastReadItem = item;\n\t\t\t} else {\n\t\t\t\t// This item is unread\n\t\t\t\thasPassedLastSeen = true;\n\t\t\t\tunreadCount++;\n\t\t\t}\n\t\t}\n\n\t\t// Store the last read item for this viewer\n\t\tif (lastReadItem?.id) {\n\t\t\tlastReadMessageMap.set(viewerId, lastReadItem.id);\n\t\t}\n\n\t\t// Store unread count\n\t\tunreadCountMap.set(viewerId, unreadCount);\n\t}\n\n\treturn { seenByMap, lastReadMessageMap, unreadCountMap };\n};\n\n/**\n * Batches sequential timeline items from the same sender into groups and enriches\n * them with read-receipt helpers so UIs can render conversation timelines with\n * minimal effort. Seen data is normalised into quick lookup maps for unread\n * indicators.\n */\nexport const useGroupedMessages = ({\n\titems,\n\tseenData = [],\n\tcurrentViewerId,\n\tviewerType,\n}: UseGroupedMessagesOptions) => {\n\treturn useMemo(() => {\n\t\tconst groupedItems = groupTimelineItems(items);\n\n\t\t// Build read receipt data\n\t\tconst { seenByMap, lastReadMessageMap, unreadCountMap } =\n\t\t\tbuildTimelineReadReceiptData(seenData, items);\n\n\t\treturn {\n\t\t\titems: groupedItems,\n\t\t\tseenByMap,\n\t\t\tlastReadMessageMap,\n\t\t\tunreadCountMap,\n\n\t\t\tisMessageSeenByViewer: (messageId: string): boolean => {\n\t\t\t\tif (!currentViewerId) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tconst seenBy = seenByMap.get(messageId);\n\t\t\t\treturn seenBy ? seenBy.has(currentViewerId) : false;\n\t\t\t},\n\n\t\t\tgetMessageSeenBy: (messageId: string): string[] => {\n\t\t\t\tconst seenBy = seenByMap.get(messageId);\n\t\t\t\treturn seenBy ? Array.from(seenBy) : [];\n\t\t\t},\n\n\t\t\tgetLastReadMessageId: (userId: string): string | undefined =>\n\t\t\t\tlastReadMessageMap.get(userId),\n\n\t\t\tisLastReadMessage: (messageId: string, userId: string): boolean =>\n\t\t\t\tlastReadMessageMap.get(userId) === messageId,\n\n\t\t\tgetUnreadCount: (userId: string): number =>\n\t\t\t\tunreadCountMap.get(userId) || 0,\n\n\t\t\thasUnreadAfter: (messageId: string, userId: string): boolean => {\n\t\t\t\tconst lastRead = lastReadMessageMap.get(userId);\n\t\t\t\tif (!lastRead) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\n\t\t\t\tconst messageIndex = items.findIndex((item) => item.id === messageId);\n\t\t\t\tconst lastReadIndex = items.findIndex((item) => item.id === lastRead);\n\n\t\t\t\treturn messageIndex < lastReadIndex;\n\t\t\t},\n\t\t};\n\t}, [items, seenData, currentViewerId]);\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
//#region src/hooks/private/use-multimodal-input.d.ts
|
|
2
|
+
type UseMultimodalInputOptions = {
|
|
3
|
+
onSubmit?: (data: {
|
|
4
|
+
message: string;
|
|
5
|
+
files: File[];
|
|
6
|
+
}) => void | Promise<void>;
|
|
7
|
+
onError?: (error: Error) => void;
|
|
8
|
+
maxFileSize?: number;
|
|
9
|
+
maxFiles?: number;
|
|
10
|
+
allowedFileTypes?: string[];
|
|
11
|
+
};
|
|
12
|
+
type UseMultimodalInputReturn = {
|
|
13
|
+
message: string;
|
|
14
|
+
files: File[];
|
|
15
|
+
isSubmitting: boolean;
|
|
16
|
+
error: Error | null;
|
|
17
|
+
setMessage: (message: string) => void;
|
|
18
|
+
addFiles: (files: File[]) => void;
|
|
19
|
+
removeFile: (index: number) => void;
|
|
20
|
+
clearFiles: () => void;
|
|
21
|
+
submit: () => Promise<void>;
|
|
22
|
+
reset: () => void;
|
|
23
|
+
isValid: boolean;
|
|
24
|
+
canSubmit: boolean;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Manages message text, file attachments and validation for the multimodal
|
|
28
|
+
* composer component. Provides ergonomic helpers for submit flows and error
|
|
29
|
+
* reporting.
|
|
30
|
+
*/
|
|
31
|
+
declare const useMultimodalInput: ({
|
|
32
|
+
onSubmit,
|
|
33
|
+
onError,
|
|
34
|
+
maxFileSize,
|
|
35
|
+
maxFiles,
|
|
36
|
+
allowedFileTypes
|
|
37
|
+
}?: UseMultimodalInputOptions) => UseMultimodalInputReturn;
|
|
38
|
+
//#endregion
|
|
39
|
+
export { UseMultimodalInputOptions, UseMultimodalInputReturn, useMultimodalInput };
|
|
40
|
+
//# sourceMappingURL=use-multimodal-input.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-multimodal-input.d.ts","names":[],"sources":["../../../src/hooks/private/use-multimodal-input.ts"],"sourcesContent":[],"mappings":";KAEY,yBAAA;EAAA,QAAA,CAAA,EAAA,CAAA,IAAA,EAAA;IAAyB,OAAA,EAAA,MAAA;IACQ,KAAA,EAAA,IAAA,EAAA;KAAoB,GAAA,IAAA,GAAA,OAAA,CAAA,IAAA,CAAA;SAC9C,CAAA,EAAA,CAAA,KAAA,EAAA,KAAA,EAAA,GAAA,IAAA;EAAK,WAAA,CAAA,EAAA,MAAA;EAMZ,QAAA,CAAA,EAAA,MAAA;EAAwB,gBAAA,CAAA,EAAA,MAAA,EAAA;;AAK5B,KALI,wBAAA,GAKJ;SAIW,EAAA,MAAA;OAGJ,EATP,IASO,EAAA;EAAO,YAAA,EAAA,OAAA;EAaT,KAAA,EApBL,KAoBK,GAAA,IAAA;EA4JZ,UAAA,EAAA,CAAA,OAAA,EAAA,MAAA,EAAA,GAAA,IAAA;UA5JkC,EAAA,CAAA,KAAA,EAhBhB,IAgBgB,EAAA,EAAA,GAAA,IAAA;YAAA,EAAA,CAAA,KAAA,EAAA,MAAA,EAAA,GAAA,IAAA;YAAA,EAAA,GAAA,GAAA,IAAA;QAAA,EAAA,GAAA,GAbpB,OAaoB,CAAA,IAAA,CAAA;OAAA,EAAA,GAAA,GAAA,IAAA;SAMhC,EAAA,OAAA;WAAiC,EAAA,OAAA;CAsJnC;;;;;;cA5JY;;;;;;IAMV,8BAAiC"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { useCallback, useRef, useState } from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/hooks/private/use-multimodal-input.ts
|
|
4
|
+
/**
|
|
5
|
+
* Manages message text, file attachments and validation for the multimodal
|
|
6
|
+
* composer component. Provides ergonomic helpers for submit flows and error
|
|
7
|
+
* reporting.
|
|
8
|
+
*/
|
|
9
|
+
const useMultimodalInput = ({ onSubmit, onError, maxFileSize = 10 * 1024 * 1024, maxFiles = 5, allowedFileTypes = [
|
|
10
|
+
"image/*",
|
|
11
|
+
"application/pdf",
|
|
12
|
+
"text/*"
|
|
13
|
+
] } = {}) => {
|
|
14
|
+
const [message, setMessage] = useState("");
|
|
15
|
+
const [files, setFiles] = useState([]);
|
|
16
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
17
|
+
const [error, setError] = useState(null);
|
|
18
|
+
const fileUrlsRef = useRef([]);
|
|
19
|
+
const validateFile = useCallback((file) => {
|
|
20
|
+
if (file.size > maxFileSize) return `File "${file.name}" exceeds maximum size of ${maxFileSize / 1024 / 1024}MB`;
|
|
21
|
+
if (allowedFileTypes.length > 0) {
|
|
22
|
+
if (!allowedFileTypes.some((type) => {
|
|
23
|
+
if (type.endsWith("/*")) {
|
|
24
|
+
const baseType = type.slice(0, -2);
|
|
25
|
+
return file.type.startsWith(baseType);
|
|
26
|
+
}
|
|
27
|
+
return file.type === type;
|
|
28
|
+
})) return `File type "${file.type}" is not allowed`;
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}, [maxFileSize, allowedFileTypes]);
|
|
32
|
+
const addFiles = useCallback((newFiles) => {
|
|
33
|
+
setError(null);
|
|
34
|
+
if (files.length + newFiles.length > maxFiles) {
|
|
35
|
+
const err = /* @__PURE__ */ new Error(`Cannot add files: maximum ${maxFiles} files allowed`);
|
|
36
|
+
setError(err);
|
|
37
|
+
onError?.(err);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
for (const file of newFiles) {
|
|
41
|
+
const validationError = validateFile(file);
|
|
42
|
+
if (validationError) {
|
|
43
|
+
const err = new Error(validationError);
|
|
44
|
+
setError(err);
|
|
45
|
+
onError?.(err);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
setFiles((prev) => [...prev, ...newFiles]);
|
|
50
|
+
}, [
|
|
51
|
+
files.length,
|
|
52
|
+
maxFiles,
|
|
53
|
+
validateFile,
|
|
54
|
+
onError
|
|
55
|
+
]);
|
|
56
|
+
const removeFile = useCallback((index) => {
|
|
57
|
+
setFiles((prev) => {
|
|
58
|
+
const newFiles = [...prev];
|
|
59
|
+
newFiles.splice(index, 1);
|
|
60
|
+
if (fileUrlsRef.current[index]) {
|
|
61
|
+
URL.revokeObjectURL(fileUrlsRef.current[index]);
|
|
62
|
+
fileUrlsRef.current.splice(index, 1);
|
|
63
|
+
}
|
|
64
|
+
return newFiles;
|
|
65
|
+
});
|
|
66
|
+
setError(null);
|
|
67
|
+
}, []);
|
|
68
|
+
const clearFiles = useCallback(() => {
|
|
69
|
+
for (const url of fileUrlsRef.current) URL.revokeObjectURL(url);
|
|
70
|
+
fileUrlsRef.current = [];
|
|
71
|
+
setFiles([]);
|
|
72
|
+
setError(null);
|
|
73
|
+
}, []);
|
|
74
|
+
const reset = useCallback(() => {
|
|
75
|
+
setMessage("");
|
|
76
|
+
clearFiles();
|
|
77
|
+
setError(null);
|
|
78
|
+
setIsSubmitting(false);
|
|
79
|
+
}, [clearFiles]);
|
|
80
|
+
const submit = useCallback(async () => {
|
|
81
|
+
if (!onSubmit) return;
|
|
82
|
+
if (!message.trim() && files.length === 0) {
|
|
83
|
+
const err = /* @__PURE__ */ new Error("Please provide a message or attach files");
|
|
84
|
+
setError(err);
|
|
85
|
+
onError?.(err);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
setIsSubmitting(true);
|
|
89
|
+
setError(null);
|
|
90
|
+
try {
|
|
91
|
+
await onSubmit({
|
|
92
|
+
message: message.trim(),
|
|
93
|
+
files
|
|
94
|
+
});
|
|
95
|
+
reset();
|
|
96
|
+
} catch (err) {
|
|
97
|
+
const _error = err instanceof Error ? err : /* @__PURE__ */ new Error("Failed to submit");
|
|
98
|
+
setError(_error);
|
|
99
|
+
onError?.(_error);
|
|
100
|
+
} finally {
|
|
101
|
+
setIsSubmitting(false);
|
|
102
|
+
}
|
|
103
|
+
}, [
|
|
104
|
+
message,
|
|
105
|
+
files,
|
|
106
|
+
onSubmit,
|
|
107
|
+
onError,
|
|
108
|
+
reset
|
|
109
|
+
]);
|
|
110
|
+
const isValid = message.trim().length > 0 || files.length > 0;
|
|
111
|
+
return {
|
|
112
|
+
message,
|
|
113
|
+
files,
|
|
114
|
+
isSubmitting,
|
|
115
|
+
error,
|
|
116
|
+
setMessage,
|
|
117
|
+
addFiles,
|
|
118
|
+
removeFile,
|
|
119
|
+
clearFiles,
|
|
120
|
+
submit,
|
|
121
|
+
reset,
|
|
122
|
+
isValid,
|
|
123
|
+
canSubmit: isValid && !isSubmitting && !error
|
|
124
|
+
};
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
//#endregion
|
|
128
|
+
export { useMultimodalInput };
|
|
129
|
+
//# sourceMappingURL=use-multimodal-input.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-multimodal-input.js","names":[],"sources":["../../../src/hooks/private/use-multimodal-input.ts"],"sourcesContent":["import { useCallback, useRef, useState } from \"react\";\n\nexport type UseMultimodalInputOptions = {\n\tonSubmit?: (data: { message: string; files: File[] }) => void | Promise<void>;\n\tonError?: (error: Error) => void;\n\tmaxFileSize?: number; // in bytes\n\tmaxFiles?: number;\n\tallowedFileTypes?: string[]; // MIME types\n};\n\nexport type UseMultimodalInputReturn = {\n\t// State\n\tmessage: string;\n\tfiles: File[];\n\tisSubmitting: boolean;\n\terror: Error | null;\n\n\t// Actions\n\tsetMessage: (message: string) => void;\n\taddFiles: (files: File[]) => void;\n\tremoveFile: (index: number) => void;\n\tclearFiles: () => void;\n\tsubmit: () => Promise<void>;\n\treset: () => void;\n\n\t// Validation\n\tisValid: boolean;\n\tcanSubmit: boolean;\n};\n\n/**\n * Manages message text, file attachments and validation for the multimodal\n * composer component. Provides ergonomic helpers for submit flows and error\n * reporting.\n */\nexport const useMultimodalInput = ({\n\tonSubmit,\n\tonError,\n\tmaxFileSize = 10 * 1024 * 1024, // 10MB default\n\tmaxFiles = 5,\n\tallowedFileTypes = [\"image/*\", \"application/pdf\", \"text/*\"],\n}: UseMultimodalInputOptions = {}): UseMultimodalInputReturn => {\n\tconst [message, setMessage] = useState(\"\");\n\tconst [files, setFiles] = useState<File[]>([]);\n\tconst [isSubmitting, setIsSubmitting] = useState(false);\n\tconst [error, setError] = useState<Error | null>(null);\n\n\t// Use ref to prevent re-renders when tracking file URLs\n\tconst fileUrlsRef = useRef<string[]>([]);\n\n\t// Validation helpers\n\tconst validateFile = useCallback(\n\t\t(file: File): string | null => {\n\t\t\tif (file.size > maxFileSize) {\n\t\t\t\treturn `File \"${file.name}\" exceeds maximum size of ${maxFileSize / 1024 / 1024}MB`;\n\t\t\t}\n\n\t\t\tif (allowedFileTypes.length > 0) {\n\t\t\t\tconst isAllowed = allowedFileTypes.some((type) => {\n\t\t\t\t\tif (type.endsWith(\"/*\")) {\n\t\t\t\t\t\tconst baseType = type.slice(0, -2);\n\t\t\t\t\t\treturn file.type.startsWith(baseType);\n\t\t\t\t\t}\n\t\t\t\t\treturn file.type === type;\n\t\t\t\t});\n\n\t\t\t\tif (!isAllowed) {\n\t\t\t\t\treturn `File type \"${file.type}\" is not allowed`;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn null;\n\t\t},\n\t\t[maxFileSize, allowedFileTypes]\n\t);\n\n\t// Actions\n\tconst addFiles = useCallback(\n\t\t(newFiles: File[]) => {\n\t\t\tsetError(null);\n\n\t\t\t// Check max files limit\n\t\t\tif (files.length + newFiles.length > maxFiles) {\n\t\t\t\tconst err = new Error(\n\t\t\t\t\t`Cannot add files: maximum ${maxFiles} files allowed`\n\t\t\t\t);\n\t\t\t\tsetError(err);\n\t\t\t\tonError?.(err);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Validate each file\n\t\t\tfor (const file of newFiles) {\n\t\t\t\tconst validationError = validateFile(file);\n\t\t\t\tif (validationError) {\n\t\t\t\t\tconst err = new Error(validationError);\n\t\t\t\t\tsetError(err);\n\t\t\t\t\tonError?.(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tsetFiles((prev) => [...prev, ...newFiles]);\n\t\t},\n\t\t[files.length, maxFiles, validateFile, onError]\n\t);\n\n\tconst removeFile = useCallback((index: number) => {\n\t\tsetFiles((prev) => {\n\t\t\tconst newFiles = [...prev];\n\t\t\tnewFiles.splice(index, 1);\n\n\t\t\t// Clean up object URL if it exists\n\t\t\tif (fileUrlsRef.current[index]) {\n\t\t\t\tURL.revokeObjectURL(fileUrlsRef.current[index]);\n\t\t\t\tfileUrlsRef.current.splice(index, 1);\n\t\t\t}\n\n\t\t\treturn newFiles;\n\t\t});\n\t\tsetError(null);\n\t}, []);\n\n\tconst clearFiles = useCallback(() => {\n\t\t// Clean up all object URLs\n\t\tfor (const url of fileUrlsRef.current) {\n\t\t\tURL.revokeObjectURL(url);\n\t\t}\n\t\tfileUrlsRef.current = [];\n\n\t\tsetFiles([]);\n\t\tsetError(null);\n\t}, []);\n\n\tconst reset = useCallback(() => {\n\t\tsetMessage(\"\");\n\t\tclearFiles();\n\t\tsetError(null);\n\t\tsetIsSubmitting(false);\n\t}, [clearFiles]);\n\n\tconst submit = useCallback(async () => {\n\t\tif (!onSubmit) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!message.trim() && files.length === 0) {\n\t\t\tconst err = new Error(\"Please provide a message or attach files\");\n\t\t\tsetError(err);\n\t\t\tonError?.(err);\n\t\t\treturn;\n\t\t}\n\n\t\tsetIsSubmitting(true);\n\t\tsetError(null);\n\n\t\ttry {\n\t\t\tawait onSubmit({ message: message.trim(), files });\n\t\t\treset();\n\t\t} catch (err) {\n\t\t\tconst _error = err instanceof Error ? err : new Error(\"Failed to submit\");\n\t\t\tsetError(_error);\n\t\t\tonError?.(_error);\n\t\t} finally {\n\t\t\tsetIsSubmitting(false);\n\t\t}\n\t}, [message, files, onSubmit, onError, reset]);\n\n\t// Computed values\n\tconst isValid = message.trim().length > 0 || files.length > 0;\n\tconst canSubmit = isValid && !isSubmitting && !error;\n\n\treturn {\n\t\t// State\n\t\tmessage,\n\t\tfiles,\n\t\tisSubmitting,\n\t\terror,\n\n\t\t// Actions\n\t\tsetMessage,\n\t\taddFiles,\n\t\tremoveFile,\n\t\tclearFiles,\n\t\tsubmit,\n\t\treset,\n\n\t\t// Validation\n\t\tisValid,\n\t\tcanSubmit,\n\t};\n};\n"],"mappings":"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"}
|