@cossistant/react 0.0.3 → 0.0.5
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 +74 -0
- package/_virtual/rolldown_runtime.js +19 -0
- package/conversation.d.ts +26 -3
- package/conversation.d.ts.map +1 -1
- package/hooks/index.d.ts +5 -3
- package/hooks/index.js +7 -5
- package/hooks/private/store/use-conversations-store.d.ts +8 -0
- package/hooks/private/store/use-conversations-store.d.ts.map +1 -1
- package/hooks/private/store/use-conversations-store.js +8 -0
- package/hooks/private/store/use-conversations-store.js.map +1 -1
- package/hooks/private/store/use-store-selector.d.ts +4 -0
- package/hooks/private/store/use-store-selector.d.ts.map +1 -1
- package/hooks/private/store/use-store-selector.js +5 -2
- package/hooks/private/store/use-store-selector.js.map +1 -1
- package/hooks/private/store/use-website-store.d.ts +4 -0
- package/hooks/private/store/use-website-store.d.ts.map +1 -1
- package/hooks/private/store/use-website-store.js +6 -3
- package/hooks/private/store/use-website-store.js.map +1 -1
- package/hooks/private/typing.d.ts +35 -0
- package/hooks/private/typing.d.ts.map +1 -0
- package/hooks/private/typing.js +49 -0
- package/hooks/private/typing.js.map +1 -0
- package/hooks/private/use-client-query.d.ts +5 -0
- package/hooks/private/use-client-query.d.ts.map +1 -1
- package/hooks/private/use-client-query.js +5 -0
- package/hooks/private/use-client-query.js.map +1 -1
- package/hooks/private/use-grouped-messages.d.ts +10 -4
- package/hooks/private/use-grouped-messages.d.ts.map +1 -1
- package/hooks/private/use-grouped-messages.js +24 -4
- 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-rest-client.d.ts.map +1 -1
- package/hooks/private/use-visitor-typing-reporter.d.ts +6 -0
- package/hooks/private/use-visitor-typing-reporter.d.ts.map +1 -1
- package/hooks/private/use-visitor-typing-reporter.js +6 -0
- package/hooks/private/use-visitor-typing-reporter.js.map +1 -1
- package/hooks/use-composer-refocus.d.ts.map +1 -1
- package/hooks/use-conversation-auto-seen.d.ts +9 -0
- package/hooks/use-conversation-auto-seen.d.ts.map +1 -1
- package/hooks/use-conversation-auto-seen.js +44 -3
- package/hooks/use-conversation-auto-seen.js.map +1 -1
- package/hooks/use-conversation-history-page.d.ts.map +1 -1
- package/hooks/use-conversation-history-page.js +16 -18
- package/hooks/use-conversation-history-page.js.map +1 -1
- package/hooks/use-conversation-lifecycle.d.ts.map +1 -1
- package/hooks/use-conversation-lifecycle.js +2 -4
- package/hooks/use-conversation-lifecycle.js.map +1 -1
- package/hooks/use-conversation-page.d.ts +6 -0
- package/hooks/use-conversation-page.d.ts.map +1 -1
- package/hooks/use-conversation-page.js +41 -3
- package/hooks/use-conversation-page.js.map +1 -1
- package/hooks/use-conversation-preview.d.ts +61 -0
- package/hooks/use-conversation-preview.d.ts.map +1 -0
- package/hooks/use-conversation-preview.js +173 -0
- package/hooks/use-conversation-preview.js.map +1 -0
- package/hooks/use-conversation-seen.d.ts +4 -0
- package/hooks/use-conversation-seen.d.ts.map +1 -1
- package/hooks/use-conversation-seen.js +4 -0
- package/hooks/use-conversation-seen.js.map +1 -1
- package/hooks/use-conversation-timeline-items.d.ts +4 -0
- package/hooks/use-conversation-timeline-items.d.ts.map +1 -1
- package/hooks/use-conversation-timeline-items.js +4 -0
- package/hooks/use-conversation-timeline-items.js.map +1 -1
- package/hooks/use-conversation-timeline.d.ts +32 -0
- package/hooks/use-conversation-timeline.d.ts.map +1 -0
- package/hooks/use-conversation-timeline.js +41 -0
- package/hooks/use-conversation-timeline.js.map +1 -0
- package/hooks/use-conversation-typing.d.ts +4 -0
- package/hooks/use-conversation-typing.d.ts.map +1 -1
- package/hooks/use-conversation-typing.js +4 -0
- package/hooks/use-conversation-typing.js.map +1 -1
- package/hooks/use-conversation.d.ts +11 -0
- package/hooks/use-conversation.d.ts.map +1 -1
- package/hooks/use-conversation.js +11 -0
- package/hooks/use-conversation.js.map +1 -1
- package/hooks/use-conversations.d.ts +12 -0
- package/hooks/use-conversations.d.ts.map +1 -1
- package/hooks/use-conversations.js +12 -0
- package/hooks/use-conversations.js.map +1 -1
- package/hooks/use-create-conversation.d.ts +5 -0
- package/hooks/use-create-conversation.d.ts.map +1 -1
- package/hooks/use-create-conversation.js +12 -9
- package/hooks/use-create-conversation.js.map +1 -1
- package/hooks/use-home-page.d.ts.map +1 -1
- package/hooks/use-home-page.js +6 -4
- package/hooks/use-home-page.js.map +1 -1
- package/hooks/use-message-composer.d.ts.map +1 -1
- package/hooks/use-realtime-support.d.ts.map +1 -1
- package/hooks/use-send-message.d.ts +9 -0
- package/hooks/use-send-message.d.ts.map +1 -1
- package/hooks/use-send-message.js +15 -13
- package/hooks/use-send-message.js.map +1 -1
- package/hooks/use-visitor.d.ts.map +1 -1
- package/hooks/use-visitor.js +28 -30
- package/hooks/use-visitor.js.map +1 -1
- package/hooks/use-window-visibility-focus.d.ts +4 -0
- package/hooks/use-window-visibility-focus.d.ts.map +1 -1
- package/hooks/use-window-visibility-focus.js +5 -2
- package/hooks/use-window-visibility-focus.js.map +1 -1
- package/identify-visitor.d.ts +12 -3
- package/identify-visitor.d.ts.map +1 -1
- package/identify-visitor.js +58 -9
- package/identify-visitor.js.map +1 -1
- package/index.d.ts +10 -7
- package/index.js +10 -9
- package/package.json +14 -17
- package/primitives/avatar/avatar.d.ts.map +1 -1
- package/primitives/avatar/fallback.d.ts.map +1 -1
- package/primitives/avatar/fallback.js +1 -3
- package/primitives/avatar/fallback.js.map +1 -1
- package/primitives/avatar/image.d.ts.map +1 -1
- package/primitives/avatar/index.d.ts +1 -0
- package/primitives/bubble.d.ts +2 -0
- package/primitives/bubble.d.ts.map +1 -1
- package/primitives/bubble.js +8 -2
- package/primitives/bubble.js.map +1 -1
- package/primitives/button.d.ts.map +1 -1
- package/primitives/conversation-timeline.d.ts.map +1 -1
- package/primitives/conversation-timeline.js +58 -5
- package/primitives/conversation-timeline.js.map +1 -1
- package/primitives/index.d.ts +1 -0
- package/primitives/index.parts.d.ts +1 -0
- package/primitives/multimodal-input.d.ts.map +1 -1
- package/primitives/timeline-item-group.d.ts +7 -7
- package/primitives/timeline-item-group.d.ts.map +1 -1
- package/primitives/timeline-item-group.js.map +1 -1
- package/primitives/timeline-item.d.ts +1 -1
- package/primitives/timeline-item.d.ts.map +1 -1
- package/primitives/timeline-item.js +7 -1
- package/primitives/timeline-item.js.map +1 -1
- package/primitives/window.d.ts +1 -1
- package/primitives/window.d.ts.map +1 -1
- package/primitives/window.js +4 -4
- package/primitives/window.js.map +1 -1
- package/provider.d.ts +23 -43
- package/provider.d.ts.map +1 -1
- package/provider.js +152 -49
- package/provider.js.map +1 -1
- package/realtime/event-filter.d.ts +4 -0
- package/realtime/event-filter.d.ts.map +1 -1
- package/realtime/event-filter.js +4 -0
- package/realtime/event-filter.js.map +1 -1
- package/realtime/index.js +1 -1
- package/realtime/provider.d.ts +7 -2
- package/realtime/provider.d.ts.map +1 -1
- package/realtime/provider.js +23 -1
- package/realtime/provider.js.map +1 -1
- package/realtime/seen-store.d.ts +13 -0
- package/realtime/seen-store.d.ts.map +1 -1
- package/realtime/seen-store.js +14 -2
- package/realtime/seen-store.js.map +1 -1
- package/realtime/support-provider.d.ts +1 -2
- package/realtime/support-provider.d.ts.map +1 -1
- package/realtime/support-provider.js +19 -20
- package/realtime/support-provider.js.map +1 -1
- package/realtime/typing-store.d.ts +18 -0
- package/realtime/typing-store.d.ts.map +1 -1
- package/realtime/typing-store.js +19 -2
- package/realtime/typing-store.js.map +1 -1
- package/realtime/use-realtime.d.ts +8 -4
- package/realtime/use-realtime.d.ts.map +1 -1
- package/realtime/use-realtime.js +4 -0
- package/realtime/use-realtime.js.map +1 -1
- package/realtime-events.d.ts +17 -3
- package/realtime-events.d.ts.map +1 -1
- package/schemas.d.ts +7 -1
- package/schemas.d.ts.map +1 -1
- package/support/components/avatar-stack.d.ts +8 -4
- package/support/components/avatar-stack.d.ts.map +1 -1
- package/support/components/avatar-stack.js +4 -0
- package/support/components/avatar-stack.js.map +1 -1
- package/support/components/avatar.d.ts +11 -6
- package/support/components/avatar.d.ts.map +1 -1
- package/support/components/avatar.js +4 -0
- package/support/components/avatar.js.map +1 -1
- package/support/components/bubble.d.ts.map +1 -1
- package/support/components/bubble.js +29 -6
- package/support/components/bubble.js.map +1 -1
- package/support/components/button.d.ts +8 -5
- package/support/components/button.d.ts.map +1 -1
- package/support/components/button.js +5 -1
- package/support/components/button.js.map +1 -1
- package/support/components/container.d.ts +0 -1
- package/support/components/container.d.ts.map +1 -1
- package/support/components/container.js +2 -8
- package/support/components/container.js.map +1 -1
- package/support/components/conversation-button-link.d.ts +8 -21
- package/support/components/conversation-button-link.d.ts.map +1 -1
- package/support/components/conversation-button-link.js +62 -178
- package/support/components/conversation-button-link.js.map +1 -1
- package/support/components/conversation-event.d.ts.map +1 -1
- package/support/components/conversation-event.js +4 -0
- package/support/components/conversation-event.js.map +1 -1
- package/support/components/conversation-timeline.d.ts +10 -1
- package/support/components/conversation-timeline.d.ts.map +1 -1
- package/support/components/conversation-timeline.js +63 -57
- package/support/components/conversation-timeline.js.map +1 -1
- package/support/components/cossistant-branding.d.ts +5 -2
- package/support/components/cossistant-branding.d.ts.map +1 -1
- package/support/components/cossistant-branding.js +3 -0
- package/support/components/cossistant-branding.js.map +1 -1
- package/support/components/header.d.ts.map +1 -1
- package/support/components/header.js +2 -2
- package/support/components/header.js.map +1 -1
- package/support/components/icons.d.ts.map +1 -1
- package/support/components/multimodal-input.d.ts.map +1 -1
- package/support/components/multimodal-input.js +5 -24
- package/support/components/multimodal-input.js.map +1 -1
- package/support/components/navigation-tab.d.ts +7 -2
- package/support/components/navigation-tab.d.ts.map +1 -1
- package/support/components/navigation-tab.js +4 -0
- package/support/components/navigation-tab.js.map +1 -1
- package/support/components/support-content.d.ts +1 -1
- package/support/components/support-content.d.ts.map +1 -1
- package/support/components/support-content.js +7 -10
- package/support/components/support-content.js.map +1 -1
- package/support/components/text-effect.d.ts +5 -2
- package/support/components/text-effect.d.ts.map +1 -1
- package/support/components/text-effect.js +4 -0
- package/support/components/text-effect.js.map +1 -1
- package/support/components/timeline-identification-tool.d.ts +7 -0
- package/support/components/timeline-identification-tool.d.ts.map +1 -0
- package/support/components/timeline-identification-tool.js +139 -0
- package/support/components/timeline-identification-tool.js.map +1 -0
- package/support/components/timeline-message-group.d.ts +2 -1
- package/support/components/timeline-message-group.d.ts.map +1 -1
- package/support/components/timeline-message-group.js +4 -19
- package/support/components/timeline-message-group.js.map +1 -1
- package/support/components/timeline-message-item.d.ts +6 -2
- package/support/components/timeline-message-item.d.ts.map +1 -1
- package/support/components/timeline-message-item.js +8 -4
- package/support/components/timeline-message-item.js.map +1 -1
- package/support/components/typing-indicator.d.ts +5 -2
- package/support/components/typing-indicator.d.ts.map +1 -1
- package/support/components/typing-indicator.js +4 -4
- package/support/components/typing-indicator.js.map +1 -1
- package/support/components/watermark.d.ts.map +1 -1
- package/support/context/websocket.d.ts +8 -0
- package/support/context/websocket.d.ts.map +1 -1
- package/support/context/websocket.js +12 -6
- package/support/context/websocket.js.map +1 -1
- package/support/index.d.ts +8 -8
- package/support/index.d.ts.map +1 -1
- package/support/index.js +18 -18
- package/support/index.js.map +1 -1
- package/support/pages/conversation-history.js +46 -54
- package/support/pages/conversation-history.js.map +1 -1
- package/support/pages/conversation.d.ts +3 -6
- package/support/pages/conversation.d.ts.map +1 -1
- package/support/pages/conversation.js +19 -9
- package/support/pages/conversation.js.map +1 -1
- package/support/pages/home.d.ts +2 -2
- package/support/pages/home.d.ts.map +1 -1
- package/support/pages/home.js +64 -77
- package/support/pages/home.js.map +1 -1
- package/support/store/support-store.d.ts +18 -2
- package/support/store/support-store.d.ts.map +1 -1
- package/support/store/support-store.js +20 -5
- package/support/store/support-store.js.map +1 -1
- package/support/{support-CMoDLQoC.css → support-Ck4jy29i.css} +1 -2
- package/support/support-Ck4jy29i.css.map +1 -0
- package/support/text/index.d.ts +15 -2
- package/support/text/index.d.ts.map +1 -1
- package/support/text/index.js +15 -2
- package/support/text/index.js.map +1 -1
- package/support/text/locales/en.js +22 -4
- package/support/text/locales/en.js.map +1 -1
- package/support/text/locales/es.js +18 -0
- package/support/text/locales/es.js.map +1 -1
- package/support/text/locales/fr.js +18 -0
- package/support/text/locales/fr.js.map +1 -1
- package/support/text/locales/keys.d.ts +69 -9
- package/support/text/locales/keys.d.ts.map +1 -1
- package/support/text/locales/keys.js +18 -0
- package/support/text/locales/keys.js.map +1 -1
- package/support/text/runtime.d.ts +21 -0
- package/support/text/runtime.d.ts.map +1 -1
- package/support/text/runtime.js +21 -0
- package/support/text/runtime.js.map +1 -1
- package/support/utils/index.d.ts +4 -0
- package/support/utils/index.d.ts.map +1 -1
- package/support/utils/index.js +4 -1
- package/support/utils/index.js.map +1 -1
- package/support/utils/time.d.ts +3 -0
- package/support/utils/time.d.ts.map +1 -1
- package/support/utils/time.js +3 -0
- package/support/utils/time.js.map +1 -1
- package/support-config.d.ts +2 -1
- package/support-config.d.ts.map +1 -1
- package/support-config.js.map +1 -1
- package/support.css +2 -2
- package/timeline-item.d.ts +10 -0
- package/timeline-item.d.ts.map +1 -1
- package/utils/conversation.d.ts +7 -0
- package/utils/conversation.d.ts.map +1 -0
- package/utils/conversation.js +18 -0
- package/utils/conversation.js.map +1 -0
- package/utils/id.d.ts +3 -0
- package/utils/id.d.ts.map +1 -1
- package/utils/id.js +3 -0
- package/utils/id.js.map +1 -1
- package/utils/index.d.ts +2 -1
- package/utils/index.js +2 -1
- package/utils/metadata-hash.d.ts +12 -0
- package/utils/metadata-hash.d.ts.map +1 -0
- package/utils/metadata-hash.js +26 -0
- package/utils/metadata-hash.js.map +1 -0
- package/utils/text.d.ts +3 -0
- package/utils/text.d.ts.map +1 -1
- package/utils/text.js +3 -0
- package/utils/text.js.map +1 -1
- package/utils/use-render-element.d.ts +3 -0
- package/utils/use-render-element.d.ts.map +1 -1
- package/utils/use-render-element.js +3 -0
- package/utils/use-render-element.js.map +1 -1
- package/support/context/config.d.ts +0 -32
- package/support/context/config.d.ts.map +0 -1
- package/support/context/config.js +0 -27
- package/support/context/config.js.map +0 -1
- package/support/support-CMoDLQoC.css.map +0 -1
|
@@ -1 +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":";;;AA2BA,SAAS,QAAQ,OAAuB;AACvC,KAAI,iBAAiB,MACpB,QAAO;AAGR,QAAO,IAAI,MAAM,OAAO,UAAU,WAAW,QAAQ,gBAAgB;;AAGtE,MAAMA,qBAAyC,EAAE
|
|
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\n/**\n * Lightweight data-fetching abstraction that plugs into the SDK client instead\n * of React Query. It tracks loading/error state, supports polling, window\n * focus refetching and exposes a typed refetch helper.\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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"}
|
|
@@ -18,7 +18,13 @@ type TimelineEventItem = {
|
|
|
18
18
|
item: TimelineItem;
|
|
19
19
|
timestamp: Date;
|
|
20
20
|
};
|
|
21
|
-
type
|
|
21
|
+
type TimelineToolItem = {
|
|
22
|
+
type: "timeline_tool";
|
|
23
|
+
item: TimelineItem;
|
|
24
|
+
tool: string | null;
|
|
25
|
+
timestamp: Date;
|
|
26
|
+
};
|
|
27
|
+
type ConversationItem = GroupedMessage | TimelineEventItem | TimelineToolItem;
|
|
22
28
|
type UseGroupedMessagesOptions = {
|
|
23
29
|
items: TimelineItem[];
|
|
24
30
|
seenData?: ConversationSeen[];
|
|
@@ -38,17 +44,17 @@ declare const useGroupedMessages: ({
|
|
|
38
44
|
currentViewerId,
|
|
39
45
|
viewerType
|
|
40
46
|
}: UseGroupedMessagesOptions) => {
|
|
41
|
-
items:
|
|
47
|
+
items: ConversationItem[];
|
|
42
48
|
seenByMap: Map<string, Set<string>>;
|
|
43
49
|
lastReadMessageMap: Map<string, string>;
|
|
44
50
|
unreadCountMap: Map<string, number>;
|
|
45
51
|
isMessageSeenByViewer: (messageId: string) => boolean;
|
|
46
|
-
getMessageSeenBy: (messageId: string) => string[];
|
|
52
|
+
getMessageSeenBy: (messageId: string) => readonly string[];
|
|
47
53
|
getLastReadMessageId: (userId: string) => string | undefined;
|
|
48
54
|
isLastReadMessage: (messageId: string, userId: string) => boolean;
|
|
49
55
|
getUnreadCount: (userId: string) => number;
|
|
50
56
|
hasUnreadAfter: (messageId: string, userId: string) => boolean;
|
|
51
57
|
};
|
|
52
58
|
//#endregion
|
|
53
|
-
export { ConversationItem, GroupedMessage, TimelineEventItem, UseGroupedMessagesOptions, UseGroupedMessagesProps, useGroupedMessages };
|
|
59
|
+
export { ConversationItem, GroupedMessage, TimelineEventItem, TimelineToolItem, UseGroupedMessagesOptions, UseGroupedMessagesProps, useGroupedMessages };
|
|
54
60
|
//# sourceMappingURL=use-grouped-messages.d.ts.map
|
|
@@ -1 +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;
|
|
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;EAGC,UAAA,EAAA,UAAA;EACL,KAAA,EAAA,YAAA,EAAA;EAGW,cAAA,EAAA,MAAA;EACD,aAAA,EAAA,MAAA;EAAI,gBAAA,EADH,IACG;EAGV,eAAA,EAHM,IAGW;AAM7B,CAAA;AAOY,KAbA,iBAAA,GAagB;EACzB,IAAA,EAAA,gBAAA;EACA,IAAA,EAbI,YAaJ;EACA,SAAA,EAbS,IAaT;CAAgB;AAEP,KAZA,gBAAA,GAYA;EACJ,IAAA,EAAA,eAAA;EACI,IAAA,EAZL,YAYK;EAEE,IAAA,EAAA,MAAA,GAAA,IAAA;EAAU,SAAA,EAZZ,IAYY;AAGxB,CAAA;AAwNa,KApOD,gBAAA,GACT,cAuSF,GAtSE,iBAsSF,GArSE,gBAqSF;AApEkC,KA/NvB,yBAAA,GA+NuB;EAAA,KAAA,EA9N3B,YA8N2B,EAAA;EAAA,QAAA,CAAA,EA7NvB,gBA6NuB,EAAA;EAAA,eAAA,CAAA,EAAA,MAAA;EAKhC,UAAA,CAAA,EAhOW,UAgOX;;KA7NS,uBAAA,GAA0B;;;;;;;cAwNzB;;;;;GAKV"}
|
|
@@ -30,6 +30,7 @@ const getSenderIdAndTypeFromTimelineItem = (item) => {
|
|
|
30
30
|
senderType: SenderType.TEAM_MEMBER
|
|
31
31
|
};
|
|
32
32
|
};
|
|
33
|
+
const EMPTY_STRING_ARRAY = Object.freeze([]);
|
|
33
34
|
const groupTimelineItems = (items) => {
|
|
34
35
|
const result = [];
|
|
35
36
|
let currentGroup = null;
|
|
@@ -46,6 +47,19 @@ const groupTimelineItems = (items) => {
|
|
|
46
47
|
});
|
|
47
48
|
continue;
|
|
48
49
|
}
|
|
50
|
+
if (item.type === "identification") {
|
|
51
|
+
if (currentGroup) {
|
|
52
|
+
result.push(currentGroup);
|
|
53
|
+
currentGroup = null;
|
|
54
|
+
}
|
|
55
|
+
result.push({
|
|
56
|
+
type: "timeline_tool",
|
|
57
|
+
item,
|
|
58
|
+
tool: item.tool ?? null,
|
|
59
|
+
timestamp: toDate(item.createdAt)
|
|
60
|
+
});
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
49
63
|
const { senderId, senderType } = getSenderIdAndTypeFromTimelineItem(item);
|
|
50
64
|
if (currentGroup && currentGroup.senderId === senderId) {
|
|
51
65
|
currentGroup.items.push(item);
|
|
@@ -120,6 +134,7 @@ const useGroupedMessages = ({ items, seenData = [], currentViewerId, viewerType
|
|
|
120
134
|
return useMemo(() => {
|
|
121
135
|
const groupedItems = groupTimelineItems(items);
|
|
122
136
|
const { seenByMap, lastReadMessageMap, unreadCountMap } = buildTimelineReadReceiptData(seenData, items);
|
|
137
|
+
const seenByArrayCache = /* @__PURE__ */ new Map();
|
|
123
138
|
return {
|
|
124
139
|
items: groupedItems,
|
|
125
140
|
seenByMap,
|
|
@@ -131,8 +146,15 @@ const useGroupedMessages = ({ items, seenData = [], currentViewerId, viewerType
|
|
|
131
146
|
return seenBy ? seenBy.has(currentViewerId) : false;
|
|
132
147
|
},
|
|
133
148
|
getMessageSeenBy: (messageId) => {
|
|
149
|
+
if (seenByArrayCache.has(messageId)) return seenByArrayCache.get(messageId) ?? EMPTY_STRING_ARRAY;
|
|
134
150
|
const seenBy = seenByMap.get(messageId);
|
|
135
|
-
|
|
151
|
+
if (!seenBy || seenBy.size === 0) {
|
|
152
|
+
seenByArrayCache.set(messageId, EMPTY_STRING_ARRAY);
|
|
153
|
+
return EMPTY_STRING_ARRAY;
|
|
154
|
+
}
|
|
155
|
+
const result = Object.freeze(Array.from(seenBy));
|
|
156
|
+
seenByArrayCache.set(messageId, result);
|
|
157
|
+
return result;
|
|
136
158
|
},
|
|
137
159
|
getLastReadMessageId: (userId) => lastReadMessageMap.get(userId),
|
|
138
160
|
isLastReadMessage: (messageId, userId) => lastReadMessageMap.get(userId) === messageId,
|
|
@@ -140,9 +162,7 @@ const useGroupedMessages = ({ items, seenData = [], currentViewerId, viewerType
|
|
|
140
162
|
hasUnreadAfter: (messageId, userId) => {
|
|
141
163
|
const lastRead = lastReadMessageMap.get(userId);
|
|
142
164
|
if (!lastRead) return true;
|
|
143
|
-
|
|
144
|
-
const lastReadIndex = items.findIndex((item) => item.id === lastRead);
|
|
145
|
-
return messageIndex < lastReadIndex;
|
|
165
|
+
return items.findIndex((item) => item.id === messageId) < items.findIndex((item) => item.id === lastRead);
|
|
146
166
|
}
|
|
147
167
|
};
|
|
148
168
|
}, [
|
|
@@ -1 +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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"}
|
|
1
|
+
{"version":3,"file":"use-grouped-messages.js","names":["EMPTY_STRING_ARRAY: readonly string[]","result: ConversationItem[]","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 TimelineToolItem = {\n\ttype: \"timeline_tool\";\n\titem: TimelineItem;\n\ttool: string | null;\n\ttimestamp: Date;\n};\n\nexport type ConversationItem =\n\t| GroupedMessage\n\t| TimelineEventItem\n\t| TimelineToolItem;\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\nconst EMPTY_STRING_ARRAY: readonly string[] = Object.freeze([]);\n\n// Helper function to group timeline items (messages only, events stay separate)\nconst groupTimelineItems = (items: TimelineItem[]): ConversationItem[] => {\n\tconst result: ConversationItem[] = [];\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\tif (item.type === \"identification\") {\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 tool item as standalone entry\n\t\t\tresult.push({\n\t\t\t\ttype: \"timeline_tool\",\n\t\t\t\titem,\n\t\t\t\ttool: item.tool ?? null,\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\t// Cache for turning seen sets into stable arrays across renders\n\t\tconst seenByArrayCache = new Map<string, readonly string[]>();\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): readonly string[] => {\n\t\t\t\tif (seenByArrayCache.has(messageId)) {\n\t\t\t\t\treturn seenByArrayCache.get(messageId) ?? EMPTY_STRING_ARRAY;\n\t\t\t\t}\n\n\t\t\t\tconst seenBy = seenByMap.get(messageId);\n\t\t\t\tif (!seenBy || seenBy.size === 0) {\n\t\t\t\t\tseenByArrayCache.set(messageId, EMPTY_STRING_ARRAY);\n\t\t\t\t\treturn EMPTY_STRING_ARRAY;\n\t\t\t\t}\n\n\t\t\t\tconst result = Object.freeze(Array.from(seenBy)) as readonly string[];\n\t\t\t\tseenByArrayCache.set(messageId, result);\n\t\t\t\treturn result;\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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"}
|
|
@@ -1 +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;
|
|
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;IACiC,OAAA,EAAA,MAAA;IAAoB,KAAA,EAApB,IAAoB,EAAA;EAC9C,CAAA,EAAA,GAAA,IAAA,GAD8C,OAC9C,CAAA,IAAA,CAAA;EAAK,OAAA,CAAA,EAAA,CAAA,KAAA,EAAL,KAAK,EAAA,GAAA,IAAA;EAMZ,WAAA,CAAA,EAAA,MAAA;EAGJ,QAAA,CAAA,EAAA,MAAA;EAEA,gBAAA,CAAA,EAAA,MAAA,EAAA;CAIW;AAGJ,KAZH,wBAAA,GAYG;EAAO,OAAA,EAAA,MAAA;EAaT,KAAA,EAtBL,IAsBK,EAAA;EAAsB,YAAA,EAAA,OAAA;EAAA,KAAA,EApB3B,KAoB2B,GAAA,IAAA;EAAA,UAAA,EAAA,CAAA,OAAA,EAAA,MAAA,EAAA,GAAA,IAAA;EAAA,QAAA,EAAA,CAAA,KAAA,EAhBhB,IAgBgB,EAAA,EAAA,GAAA,IAAA;EAAA,UAAA,EAAA,CAAA,KAAA,EAAA,MAAA,EAAA,GAAA,IAAA;EAMhC,UAAA,EAAA,GAAA,GAAA,IAAA;EAAiC,MAAA,EAAA,GAAA,GAnBrB,OAmBqB,CAAA,IAAA,CAAA;EAsJnC,KAAA,EAAA,GAAA,GAAA,IAAA;;;;;;;;;cA5JY;;;;;;IAMV,8BAAiC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-rest-client.d.ts","names":[],"sources":["../../../src/hooks/private/use-rest-client.ts"],"sourcesContent":[],"mappings":";;;KAMY,eAAA;UACH;EADG,KAAA,EAEJ,KAFI,GAAA,IAAe;CAAA
|
|
1
|
+
{"version":3,"file":"use-rest-client.d.ts","names":[],"sources":["../../../src/hooks/private/use-rest-client.ts"],"sourcesContent":[],"mappings":";;;KAMY,eAAA;UACH;EADG,KAAA,EAEJ,KAFI,GAAA,IAAe;AAW3B,CAAA;;;;;;;iBAAgB,SAAA,kEAIb"}
|
|
@@ -10,6 +10,12 @@ type UseVisitorTypingReporterResult = {
|
|
|
10
10
|
handleSubmit: () => void;
|
|
11
11
|
stop: () => void;
|
|
12
12
|
};
|
|
13
|
+
/**
|
|
14
|
+
* Tracks visitor composer activity and reports typing previews to the backend.
|
|
15
|
+
*
|
|
16
|
+
* Handles throttling, keep-alive pings, inactivity fallbacks and ensures a
|
|
17
|
+
* `stop` event is emitted when the component unmounts.
|
|
18
|
+
*/
|
|
13
19
|
declare function useVisitorTypingReporter({
|
|
14
20
|
client,
|
|
15
21
|
conversationId
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-visitor-typing-reporter.d.ts","names":[],"sources":["../../../src/hooks/private/use-visitor-typing-reporter.ts"],"sourcesContent":[],"mappings":";;;KAQK,+BAAA;UACI;EADJ,cAAA,EAAA,MAAA,GAAA,IAAA;AACoB,CAAA;
|
|
1
|
+
{"version":3,"file":"use-visitor-typing-reporter.d.ts","names":[],"sources":["../../../src/hooks/private/use-visitor-typing-reporter.ts"],"sourcesContent":[],"mappings":";;;KAQK,+BAAA;UACI;EADJ,cAAA,EAAA,MAAA,GAAA,IAAA;AACoB,CAAA;AAgBzB,KAZK,8BAAA,GAYmC;EACvC,iBAAA,EAAA,CAAA,KAAA,EAAA,MAAA,EAAA,GAAA,IAAA;EACA,YAAA,EAAA,GAAA,GAAA,IAAA;EACE,IAAA,EAAA,GAAA,GAAA,IAAA;CAAkC;;;;;;;iBAHrB,wBAAA;;;GAGb,kCAAkC"}
|
|
@@ -5,6 +5,12 @@ const PREVIEW_MAX_LENGTH = 2e3;
|
|
|
5
5
|
const SEND_INTERVAL_MS = 800;
|
|
6
6
|
const KEEP_ALIVE_MS = 4e3;
|
|
7
7
|
const STOP_TYPING_DELAY_MS = 2e3;
|
|
8
|
+
/**
|
|
9
|
+
* Tracks visitor composer activity and reports typing previews to the backend.
|
|
10
|
+
*
|
|
11
|
+
* Handles throttling, keep-alive pings, inactivity fallbacks and ensures a
|
|
12
|
+
* `stop` event is emitted when the component unmounts.
|
|
13
|
+
*/
|
|
8
14
|
function useVisitorTypingReporter({ client, conversationId }) {
|
|
9
15
|
const typingActiveRef = useRef(false);
|
|
10
16
|
const lastSentAtRef = useRef(0);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-visitor-typing-reporter.js","names":[],"sources":["../../../src/hooks/private/use-visitor-typing-reporter.ts"],"sourcesContent":["import type { CossistantClient } from \"@cossistant/core\";\nimport { useCallback, useEffect, useRef } from \"react\";\n\nconst PREVIEW_MAX_LENGTH = 2000;\nconst SEND_INTERVAL_MS = 800;\nconst KEEP_ALIVE_MS = 4000;\nconst STOP_TYPING_DELAY_MS = 2000; // Send isTyping: false after 2 seconds of inactivity\n\ntype UseVisitorTypingReporterOptions = {\n\tclient: CossistantClient | null;\n\tconversationId: string | null;\n};\n\ntype UseVisitorTypingReporterResult = {\n\thandleInputChange: (value: string) => void;\n\thandleSubmit: () => void;\n\tstop: () => void;\n};\n\nexport function useVisitorTypingReporter({\n\tclient,\n\tconversationId,\n}: UseVisitorTypingReporterOptions): UseVisitorTypingReporterResult {\n\tconst typingActiveRef = useRef(false);\n\tconst lastSentAtRef = useRef(0);\n\tconst latestPreviewRef = useRef<string>(\"\");\n\tconst keepAliveTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(\n\t\tnull\n\t);\n\tconst stopTypingTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(\n\t\tnull\n\t);\n\n\tconst clearKeepAlive = useCallback(() => {\n\t\tif (keepAliveTimeoutRef.current) {\n\t\t\tclearTimeout(keepAliveTimeoutRef.current);\n\t\t\tkeepAliveTimeoutRef.current = null;\n\t\t}\n\t}, []);\n\n\tconst clearStopTypingTimeout = useCallback(() => {\n\t\tif (stopTypingTimeoutRef.current) {\n\t\t\tclearTimeout(stopTypingTimeoutRef.current);\n\t\t\tstopTypingTimeoutRef.current = null;\n\t\t}\n\t}, []);\n\n\tconst sendTyping = useCallback(\n\t\tasync (isTyping: boolean, preview?: string | null) => {\n\t\t\tif (!(client && conversationId)) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tawait client.setVisitorTyping({\n\t\t\t\t\tconversationId,\n\t\t\t\t\tisTyping,\n\t\t\t\t\tvisitorPreview: preview ?? undefined,\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(\"[Support] Failed to send typing event\", error);\n\t\t\t}\n\t\t},\n\t\t[client, conversationId]\n\t);\n\n\tconst scheduleKeepAlive = useCallback(() => {\n\t\tclearKeepAlive();\n\t\tkeepAliveTimeoutRef.current = setTimeout(() => {\n\t\t\tif (typingActiveRef.current) {\n\t\t\t\tvoid sendTyping(true, latestPreviewRef.current);\n\t\t\t\tscheduleKeepAlive();\n\t\t\t}\n\t\t}, KEEP_ALIVE_MS);\n\t}, [clearKeepAlive, sendTyping]);\n\n\tconst scheduleStopTyping = useCallback(() => {\n\t\tclearStopTypingTimeout();\n\t\tstopTypingTimeoutRef.current = setTimeout(() => {\n\t\t\tif (typingActiveRef.current) {\n\t\t\t\ttypingActiveRef.current = false;\n\t\t\t\tclearKeepAlive();\n\t\t\t\tvoid sendTyping(false);\n\t\t\t}\n\t\t}, STOP_TYPING_DELAY_MS);\n\t}, [clearStopTypingTimeout, clearKeepAlive, sendTyping]);\n\n\tconst handleInputChange = useCallback(\n\t\t(value: string) => {\n\t\t\tif (!(client && conversationId)) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst trimmed = value.trim();\n\t\t\tlatestPreviewRef.current = trimmed.slice(0, PREVIEW_MAX_LENGTH);\n\t\t\tconst now = Date.now();\n\n\t\t\tif (trimmed.length === 0) {\n\t\t\t\tif (typingActiveRef.current) {\n\t\t\t\t\ttypingActiveRef.current = false;\n\t\t\t\t\tlastSentAtRef.current = now;\n\t\t\t\t\tclearKeepAlive();\n\t\t\t\t\tclearStopTypingTimeout();\n\t\t\t\t\tvoid sendTyping(false);\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Schedule auto-stop after inactivity\n\t\t\tscheduleStopTyping();\n\n\t\t\tif (!typingActiveRef.current) {\n\t\t\t\ttypingActiveRef.current = true;\n\t\t\t\tlastSentAtRef.current = now;\n\t\t\t\tvoid sendTyping(true, latestPreviewRef.current);\n\t\t\t\tscheduleKeepAlive();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (now - lastSentAtRef.current >= SEND_INTERVAL_MS) {\n\t\t\t\tlastSentAtRef.current = now;\n\t\t\t\tvoid sendTyping(true, latestPreviewRef.current);\n\t\t\t\tscheduleKeepAlive();\n\t\t\t}\n\t\t},\n\t\t[\n\t\t\tclient,\n\t\t\tconversationId,\n\t\t\tsendTyping,\n\t\t\tscheduleKeepAlive,\n\t\t\tscheduleStopTyping,\n\t\t\tclearKeepAlive,\n\t\t\tclearStopTypingTimeout,\n\t\t]\n\t);\n\n\tconst handleSubmit = useCallback(() => {\n\t\tif (!typingActiveRef.current) {\n\t\t\treturn;\n\t\t}\n\n\t\ttypingActiveRef.current = false;\n\t\tlastSentAtRef.current = Date.now();\n\t\tclearKeepAlive();\n\t\tclearStopTypingTimeout();\n\t\tvoid sendTyping(false);\n\t}, [clearKeepAlive, clearStopTypingTimeout, sendTyping]);\n\n\tconst stop = useCallback(() => {\n\t\tif (!typingActiveRef.current) {\n\t\t\treturn;\n\t\t}\n\n\t\ttypingActiveRef.current = false;\n\t\tlastSentAtRef.current = Date.now();\n\t\tclearKeepAlive();\n\t\tclearStopTypingTimeout();\n\t\tvoid sendTyping(false);\n\t}, [clearKeepAlive, clearStopTypingTimeout, sendTyping]);\n\n\tuseEffect(\n\t\t() => () => {\n\t\t\tif (typingActiveRef.current) {\n\t\t\t\tvoid sendTyping(false);\n\t\t\t}\n\t\t\tclearKeepAlive();\n\t\t\tclearStopTypingTimeout();\n\t\t},\n\t\t[clearKeepAlive, clearStopTypingTimeout, sendTyping]\n\t);\n\n\treturn {\n\t\thandleInputChange,\n\t\thandleSubmit,\n\t\tstop,\n\t};\n}\n"],"mappings":";;;AAGA,MAAM,qBAAqB;AAC3B,MAAM,mBAAmB;AACzB,MAAM,gBAAgB;AACtB,MAAM,uBAAuB
|
|
1
|
+
{"version":3,"file":"use-visitor-typing-reporter.js","names":[],"sources":["../../../src/hooks/private/use-visitor-typing-reporter.ts"],"sourcesContent":["import type { CossistantClient } from \"@cossistant/core\";\nimport { useCallback, useEffect, useRef } from \"react\";\n\nconst PREVIEW_MAX_LENGTH = 2000;\nconst SEND_INTERVAL_MS = 800;\nconst KEEP_ALIVE_MS = 4000;\nconst STOP_TYPING_DELAY_MS = 2000; // Send isTyping: false after 2 seconds of inactivity\n\ntype UseVisitorTypingReporterOptions = {\n\tclient: CossistantClient | null;\n\tconversationId: string | null;\n};\n\ntype UseVisitorTypingReporterResult = {\n\thandleInputChange: (value: string) => void;\n\thandleSubmit: () => void;\n\tstop: () => void;\n};\n\n/**\n * Tracks visitor composer activity and reports typing previews to the backend.\n *\n * Handles throttling, keep-alive pings, inactivity fallbacks and ensures a\n * `stop` event is emitted when the component unmounts.\n */\nexport function useVisitorTypingReporter({\n\tclient,\n\tconversationId,\n}: UseVisitorTypingReporterOptions): UseVisitorTypingReporterResult {\n\tconst typingActiveRef = useRef(false);\n\tconst lastSentAtRef = useRef(0);\n\tconst latestPreviewRef = useRef<string>(\"\");\n\tconst keepAliveTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(\n\t\tnull\n\t);\n\tconst stopTypingTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(\n\t\tnull\n\t);\n\n\tconst clearKeepAlive = useCallback(() => {\n\t\tif (keepAliveTimeoutRef.current) {\n\t\t\tclearTimeout(keepAliveTimeoutRef.current);\n\t\t\tkeepAliveTimeoutRef.current = null;\n\t\t}\n\t}, []);\n\n\tconst clearStopTypingTimeout = useCallback(() => {\n\t\tif (stopTypingTimeoutRef.current) {\n\t\t\tclearTimeout(stopTypingTimeoutRef.current);\n\t\t\tstopTypingTimeoutRef.current = null;\n\t\t}\n\t}, []);\n\n\tconst sendTyping = useCallback(\n\t\tasync (isTyping: boolean, preview?: string | null) => {\n\t\t\tif (!(client && conversationId)) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tawait client.setVisitorTyping({\n\t\t\t\t\tconversationId,\n\t\t\t\t\tisTyping,\n\t\t\t\t\tvisitorPreview: preview ?? undefined,\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(\"[Support] Failed to send typing event\", error);\n\t\t\t}\n\t\t},\n\t\t[client, conversationId]\n\t);\n\n\tconst scheduleKeepAlive = useCallback(() => {\n\t\tclearKeepAlive();\n\t\tkeepAliveTimeoutRef.current = setTimeout(() => {\n\t\t\tif (typingActiveRef.current) {\n\t\t\t\tvoid sendTyping(true, latestPreviewRef.current);\n\t\t\t\tscheduleKeepAlive();\n\t\t\t}\n\t\t}, KEEP_ALIVE_MS);\n\t}, [clearKeepAlive, sendTyping]);\n\n\tconst scheduleStopTyping = useCallback(() => {\n\t\tclearStopTypingTimeout();\n\t\tstopTypingTimeoutRef.current = setTimeout(() => {\n\t\t\tif (typingActiveRef.current) {\n\t\t\t\ttypingActiveRef.current = false;\n\t\t\t\tclearKeepAlive();\n\t\t\t\tvoid sendTyping(false);\n\t\t\t}\n\t\t}, STOP_TYPING_DELAY_MS);\n\t}, [clearStopTypingTimeout, clearKeepAlive, sendTyping]);\n\n\tconst handleInputChange = useCallback(\n\t\t(value: string) => {\n\t\t\tif (!(client && conversationId)) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst trimmed = value.trim();\n\t\t\tlatestPreviewRef.current = trimmed.slice(0, PREVIEW_MAX_LENGTH);\n\t\t\tconst now = Date.now();\n\n\t\t\tif (trimmed.length === 0) {\n\t\t\t\tif (typingActiveRef.current) {\n\t\t\t\t\ttypingActiveRef.current = false;\n\t\t\t\t\tlastSentAtRef.current = now;\n\t\t\t\t\tclearKeepAlive();\n\t\t\t\t\tclearStopTypingTimeout();\n\t\t\t\t\tvoid sendTyping(false);\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Schedule auto-stop after inactivity\n\t\t\tscheduleStopTyping();\n\n\t\t\tif (!typingActiveRef.current) {\n\t\t\t\ttypingActiveRef.current = true;\n\t\t\t\tlastSentAtRef.current = now;\n\t\t\t\tvoid sendTyping(true, latestPreviewRef.current);\n\t\t\t\tscheduleKeepAlive();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (now - lastSentAtRef.current >= SEND_INTERVAL_MS) {\n\t\t\t\tlastSentAtRef.current = now;\n\t\t\t\tvoid sendTyping(true, latestPreviewRef.current);\n\t\t\t\tscheduleKeepAlive();\n\t\t\t}\n\t\t},\n\t\t[\n\t\t\tclient,\n\t\t\tconversationId,\n\t\t\tsendTyping,\n\t\t\tscheduleKeepAlive,\n\t\t\tscheduleStopTyping,\n\t\t\tclearKeepAlive,\n\t\t\tclearStopTypingTimeout,\n\t\t]\n\t);\n\n\tconst handleSubmit = useCallback(() => {\n\t\tif (!typingActiveRef.current) {\n\t\t\treturn;\n\t\t}\n\n\t\ttypingActiveRef.current = false;\n\t\tlastSentAtRef.current = Date.now();\n\t\tclearKeepAlive();\n\t\tclearStopTypingTimeout();\n\t\tvoid sendTyping(false);\n\t}, [clearKeepAlive, clearStopTypingTimeout, sendTyping]);\n\n\tconst stop = useCallback(() => {\n\t\tif (!typingActiveRef.current) {\n\t\t\treturn;\n\t\t}\n\n\t\ttypingActiveRef.current = false;\n\t\tlastSentAtRef.current = Date.now();\n\t\tclearKeepAlive();\n\t\tclearStopTypingTimeout();\n\t\tvoid sendTyping(false);\n\t}, [clearKeepAlive, clearStopTypingTimeout, sendTyping]);\n\n\tuseEffect(\n\t\t() => () => {\n\t\t\tif (typingActiveRef.current) {\n\t\t\t\tvoid sendTyping(false);\n\t\t\t}\n\t\t\tclearKeepAlive();\n\t\t\tclearStopTypingTimeout();\n\t\t},\n\t\t[clearKeepAlive, clearStopTypingTimeout, sendTyping]\n\t);\n\n\treturn {\n\t\thandleInputChange,\n\t\thandleSubmit,\n\t\tstop,\n\t};\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-composer-refocus.d.ts","names":[],"sources":["../../src/hooks/use-composer-refocus.ts"],"sourcesContent":[],"mappings":";;;KAGY,yBAAA;;EAAA,UAAA,EAAA,OAAA;EAMA,YAAA,EAAA,OAAA;
|
|
1
|
+
{"version":3,"file":"use-composer-refocus.d.ts","names":[],"sources":["../../src/hooks/use-composer-refocus.ts"],"sourcesContent":[],"mappings":";;;KAGY,yBAAA;;EAAA,UAAA,EAAA,OAAA;EAMA,YAAA,EAAA,OAAA;AAKZ,CAAA;AAAmC,KALvB,wBAAA,GAKuB;EAAA,aAAA,EAAA,GAAA,GAAA,IAAA;EAAA,QAAA,EAHxB,gBAGwB,CAHP,mBAGO,GAAA,IAAA,CAAA;CAIhC;AAA4B,cAJlB,kBAIkB,EAAA,CAAA;EAAA,QAAA;EAAA,UAAA;EAAA;AAAA,CAAA,EAA5B,yBAA4B,EAAA,GAAA,wBAAA"}
|
|
@@ -26,17 +26,26 @@ type UseConversationAutoSeenOptions = {
|
|
|
26
26
|
* Default: true
|
|
27
27
|
*/
|
|
28
28
|
enabled?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Whether the support widget is currently open/visible.
|
|
31
|
+
* This is required to ensure we only mark conversations as seen when
|
|
32
|
+
* the widget is actually visible to the user.
|
|
33
|
+
* Default: true
|
|
34
|
+
*/
|
|
35
|
+
isWidgetOpen?: boolean;
|
|
29
36
|
};
|
|
30
37
|
/**
|
|
31
38
|
* Automatically marks timeline items as seen when:
|
|
32
39
|
* - A new timeline item arrives from someone else
|
|
33
40
|
* - The page is visible/focused
|
|
41
|
+
* - The support widget is open/visible
|
|
34
42
|
* - The visitor is the current user
|
|
35
43
|
*
|
|
36
44
|
* Also handles:
|
|
37
45
|
* - Fetching and hydrating initial seen data
|
|
38
46
|
* - Preventing duplicate API calls
|
|
39
47
|
* - Page visibility tracking
|
|
48
|
+
* - Widget visibility tracking
|
|
40
49
|
*
|
|
41
50
|
* @example
|
|
42
51
|
* ```tsx
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-conversation-auto-seen.d.ts","names":[],"sources":["../../src/hooks/use-conversation-auto-seen.ts"],"sourcesContent":[],"mappings":";;;;cASa,+BAAA;KAED,8BAAA;EAFC;AAEb
|
|
1
|
+
{"version":3,"file":"use-conversation-auto-seen.d.ts","names":[],"sources":["../../src/hooks/use-conversation-auto-seen.ts"],"sourcesContent":[],"mappings":";;;;cASa,+BAAA;KAED,8BAAA;EAFC;AAEb;AA4DA;UAxDS;;;;;;;;;;;;;oBAgBU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAwCH,uBAAA,UACN"}
|
|
@@ -8,12 +8,14 @@ const CONVERSATION_AUTO_SEEN_DELAY_MS = 2e3;
|
|
|
8
8
|
* Automatically marks timeline items as seen when:
|
|
9
9
|
* - A new timeline item arrives from someone else
|
|
10
10
|
* - The page is visible/focused
|
|
11
|
+
* - The support widget is open/visible
|
|
11
12
|
* - The visitor is the current user
|
|
12
13
|
*
|
|
13
14
|
* Also handles:
|
|
14
15
|
* - Fetching and hydrating initial seen data
|
|
15
16
|
* - Preventing duplicate API calls
|
|
16
17
|
* - Page visibility tracking
|
|
18
|
+
* - Widget visibility tracking
|
|
17
19
|
*
|
|
18
20
|
* @example
|
|
19
21
|
* ```tsx
|
|
@@ -26,11 +28,30 @@ const CONVERSATION_AUTO_SEEN_DELAY_MS = 2e3;
|
|
|
26
28
|
* ```
|
|
27
29
|
*/
|
|
28
30
|
function useConversationAutoSeen(options) {
|
|
29
|
-
const { client, conversationId, visitorId, lastTimelineItem, enabled = true } = options;
|
|
31
|
+
const { client, conversationId, visitorId, lastTimelineItem, enabled = true, isWidgetOpen = true } = options;
|
|
30
32
|
const lastSeenItemIdRef = useRef(null);
|
|
31
33
|
const markSeenInFlightRef = useRef(false);
|
|
32
34
|
const markSeenTimeoutRef = useRef(null);
|
|
33
35
|
const { isPageVisible, hasWindowFocus } = useWindowVisibilityFocus();
|
|
36
|
+
const latestStateRef = useRef({
|
|
37
|
+
enabled,
|
|
38
|
+
isWidgetOpen,
|
|
39
|
+
isPageVisible,
|
|
40
|
+
hasWindowFocus
|
|
41
|
+
});
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
latestStateRef.current = {
|
|
44
|
+
enabled,
|
|
45
|
+
isWidgetOpen,
|
|
46
|
+
isPageVisible,
|
|
47
|
+
hasWindowFocus
|
|
48
|
+
};
|
|
49
|
+
}, [
|
|
50
|
+
enabled,
|
|
51
|
+
isWidgetOpen,
|
|
52
|
+
hasWindowFocus,
|
|
53
|
+
isPageVisible
|
|
54
|
+
]);
|
|
34
55
|
useEffect(() => {
|
|
35
56
|
lastSeenItemIdRef.current = null;
|
|
36
57
|
markSeenInFlightRef.current = false;
|
|
@@ -39,6 +60,16 @@ function useConversationAutoSeen(options) {
|
|
|
39
60
|
markSeenTimeoutRef.current = null;
|
|
40
61
|
}
|
|
41
62
|
}, [conversationId]);
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
if (!isWidgetOpen) {
|
|
65
|
+
if (markSeenTimeoutRef.current) {
|
|
66
|
+
clearTimeout(markSeenTimeoutRef.current);
|
|
67
|
+
markSeenTimeoutRef.current = null;
|
|
68
|
+
}
|
|
69
|
+
markSeenInFlightRef.current = false;
|
|
70
|
+
lastSeenItemIdRef.current = null;
|
|
71
|
+
}
|
|
72
|
+
}, [isWidgetOpen]);
|
|
42
73
|
useEffect(() => {
|
|
43
74
|
if (enabled && client && conversationId) client.getConversationSeenData({ conversationId }).then((response) => {
|
|
44
75
|
if (response.seenData.length > 0) hydrateConversationSeen(conversationId, response.seenData);
|
|
@@ -51,11 +82,18 @@ function useConversationAutoSeen(options) {
|
|
|
51
82
|
conversationId
|
|
52
83
|
]);
|
|
53
84
|
useEffect(() => {
|
|
85
|
+
if (!(isWidgetOpen && enabled)) {
|
|
86
|
+
if (markSeenTimeoutRef.current) {
|
|
87
|
+
clearTimeout(markSeenTimeoutRef.current);
|
|
88
|
+
markSeenTimeoutRef.current = null;
|
|
89
|
+
}
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
54
92
|
if (markSeenTimeoutRef.current) {
|
|
55
93
|
clearTimeout(markSeenTimeoutRef.current);
|
|
56
94
|
markSeenTimeoutRef.current = null;
|
|
57
95
|
}
|
|
58
|
-
if (!(
|
|
96
|
+
if (!(client && conversationId && visitorId && lastTimelineItem && isPageVisible && hasWindowFocus)) return;
|
|
59
97
|
if (lastTimelineItem.visitorId === visitorId) {
|
|
60
98
|
lastSeenItemIdRef.current = lastTimelineItem.id || null;
|
|
61
99
|
return;
|
|
@@ -64,7 +102,9 @@ function useConversationAutoSeen(options) {
|
|
|
64
102
|
if (markSeenInFlightRef.current) return;
|
|
65
103
|
const pendingItemId = lastTimelineItem.id || null;
|
|
66
104
|
markSeenTimeoutRef.current = setTimeout(() => {
|
|
67
|
-
|
|
105
|
+
const { enabled: latestEnabled, isWidgetOpen: latestIsWidgetOpen, isPageVisible: latestPageVisible, hasWindowFocus: latestHasFocus } = latestStateRef.current;
|
|
106
|
+
if (!(client && conversationId && latestEnabled && latestIsWidgetOpen && latestPageVisible && latestHasFocus)) {
|
|
107
|
+
markSeenInFlightRef.current = false;
|
|
68
108
|
markSeenTimeoutRef.current = null;
|
|
69
109
|
return;
|
|
70
110
|
}
|
|
@@ -92,6 +132,7 @@ function useConversationAutoSeen(options) {
|
|
|
92
132
|
};
|
|
93
133
|
}, [
|
|
94
134
|
enabled,
|
|
135
|
+
isWidgetOpen,
|
|
95
136
|
client,
|
|
96
137
|
conversationId,
|
|
97
138
|
visitorId,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-conversation-auto-seen.js","names":[],"sources":["../../src/hooks/use-conversation-auto-seen.ts"],"sourcesContent":["import type { CossistantClient } from \"@cossistant/core\";\nimport type { TimelineItem } from \"@cossistant/types/api/timeline-item\";\nimport { useEffect, useRef } from \"react\";\nimport {\n
|
|
1
|
+
{"version":3,"file":"use-conversation-auto-seen.js","names":[],"sources":["../../src/hooks/use-conversation-auto-seen.ts"],"sourcesContent":["import type { CossistantClient } from \"@cossistant/core\";\nimport type { TimelineItem } from \"@cossistant/types/api/timeline-item\";\nimport { useEffect, useRef } from \"react\";\nimport {\n\thydrateConversationSeen,\n\tupsertConversationSeen,\n} from \"../realtime/seen-store\";\nimport { useWindowVisibilityFocus } from \"./use-window-visibility-focus\";\n\nexport const CONVERSATION_AUTO_SEEN_DELAY_MS = 2000;\n\nexport type UseConversationAutoSeenOptions = {\n\t/**\n\t * The Cossistant client instance.\n\t */\n\tclient: CossistantClient | null;\n\n\t/**\n\t * The real conversation ID. Pass null if no conversation exists yet.\n\t */\n\tconversationId: string | null;\n\n\t/**\n\t * Current visitor ID.\n\t */\n\tvisitorId?: string;\n\n\t/**\n\t * The last timeline item in the conversation.\n\t * Used to determine if we should mark as seen.\n\t */\n\tlastTimelineItem: TimelineItem | null;\n\n\t/**\n\t * Whether to enable auto-seen tracking.\n\t * Default: true\n\t */\n\tenabled?: boolean;\n\n\t/**\n\t * Whether the support widget is currently open/visible.\n\t * This is required to ensure we only mark conversations as seen when\n\t * the widget is actually visible to the user.\n\t * Default: true\n\t */\n\tisWidgetOpen?: boolean;\n};\n\n/**\n * Automatically marks timeline items as seen when:\n * - A new timeline item arrives from someone else\n * - The page is visible/focused\n * - The support widget is open/visible\n * - The visitor is the current user\n *\n * Also handles:\n * - Fetching and hydrating initial seen data\n * - Preventing duplicate API calls\n * - Page visibility tracking\n * - Widget visibility tracking\n *\n * @example\n * ```tsx\n * useConversationAutoSeen({\n * client,\n * conversationId: realConversationId,\n * visitorId: visitor?.id,\n * lastTimelineItem: items[items.length - 1] ?? null,\n * });\n * ```\n */\nexport function useConversationAutoSeen(\n\toptions: UseConversationAutoSeenOptions\n): void {\n\tconst {\n\t\tclient,\n\t\tconversationId,\n\t\tvisitorId,\n\t\tlastTimelineItem,\n\t\tenabled = true,\n\t\tisWidgetOpen = true,\n\t} = options;\n\n\tconst lastSeenItemIdRef = useRef<string | null>(null);\n\tconst markSeenInFlightRef = useRef(false);\n\tconst markSeenTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\tconst { isPageVisible, hasWindowFocus } = useWindowVisibilityFocus();\n\tconst latestStateRef = useRef({\n\t\tenabled,\n\t\tisWidgetOpen,\n\t\tisPageVisible,\n\t\thasWindowFocus,\n\t});\n\n\tuseEffect(() => {\n\t\tlatestStateRef.current = {\n\t\t\tenabled,\n\t\t\tisWidgetOpen,\n\t\t\tisPageVisible,\n\t\t\thasWindowFocus,\n\t\t};\n\t}, [enabled, isWidgetOpen, hasWindowFocus, isPageVisible]);\n\n\t// Reset seen tracking when conversation changes\n\tuseEffect(() => {\n\t\tlastSeenItemIdRef.current = null;\n\t\tmarkSeenInFlightRef.current = false;\n\t\tif (markSeenTimeoutRef.current) {\n\t\t\tclearTimeout(markSeenTimeoutRef.current);\n\t\t\tmarkSeenTimeoutRef.current = null;\n\t\t}\n\t}, [conversationId]);\n\n\t// Clear timeout immediately when widget closes and reset tracking\n\tuseEffect(() => {\n\t\tif (!isWidgetOpen) {\n\t\t\tif (markSeenTimeoutRef.current) {\n\t\t\t\tclearTimeout(markSeenTimeoutRef.current);\n\t\t\t\tmarkSeenTimeoutRef.current = null;\n\t\t\t}\n\t\t\tmarkSeenInFlightRef.current = false;\n\t\t\t// Reset last seen item ID so we don't skip marking when widget reopens\n\t\t\t// This ensures we check again when the widget is reopened\n\t\t\tlastSeenItemIdRef.current = null;\n\t\t}\n\t}, [isWidgetOpen]);\n\n\t// Fetch and hydrate initial seen data when conversation loads\n\tuseEffect(() => {\n\t\tif (enabled && client && conversationId) {\n\t\t\tvoid client\n\t\t\t\t.getConversationSeenData({ conversationId })\n\t\t\t\t.then((response) => {\n\t\t\t\t\tif (response.seenData.length > 0) {\n\t\t\t\t\t\thydrateConversationSeen(conversationId, response.seenData);\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\t.catch((err) => {\n\t\t\t\t\tconsole.error(\"Failed to fetch conversation seen data:\", err);\n\t\t\t\t});\n\t\t}\n\t}, [enabled, client, conversationId]);\n\n\t// Auto-mark timeline items as seen\n\tuseEffect(() => {\n\t\t// Early return if widget is closed - don't process any seen updates\n\t\tif (!(isWidgetOpen && enabled)) {\n\t\t\tif (markSeenTimeoutRef.current) {\n\t\t\t\tclearTimeout(markSeenTimeoutRef.current);\n\t\t\t\tmarkSeenTimeoutRef.current = null;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (markSeenTimeoutRef.current) {\n\t\t\tclearTimeout(markSeenTimeoutRef.current);\n\t\t\tmarkSeenTimeoutRef.current = null;\n\t\t}\n\n\t\tconst shouldMark =\n\t\t\tclient &&\n\t\t\tconversationId &&\n\t\t\tvisitorId &&\n\t\t\tlastTimelineItem &&\n\t\t\tisPageVisible &&\n\t\t\thasWindowFocus;\n\n\t\tif (!shouldMark) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Don't mark our own timeline items as seen via API (we already know we saw them)\n\t\tif (lastTimelineItem.visitorId === visitorId) {\n\t\t\tlastSeenItemIdRef.current = lastTimelineItem.id || null;\n\t\t\treturn;\n\t\t}\n\n\t\t// Already marked this item\n\t\tif (lastSeenItemIdRef.current === lastTimelineItem.id) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Already in flight\n\t\tif (markSeenInFlightRef.current) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst pendingItemId = lastTimelineItem.id || null;\n\n\t\tmarkSeenTimeoutRef.current = setTimeout(() => {\n\t\t\tconst {\n\t\t\t\tenabled: latestEnabled,\n\t\t\t\tisWidgetOpen: latestIsWidgetOpen,\n\t\t\t\tisPageVisible: latestPageVisible,\n\t\t\t\thasWindowFocus: latestHasFocus,\n\t\t\t} = latestStateRef.current;\n\n\t\t\tif (\n\t\t\t\t!(\n\t\t\t\t\tclient &&\n\t\t\t\t\tconversationId &&\n\t\t\t\t\tlatestEnabled &&\n\t\t\t\t\tlatestIsWidgetOpen &&\n\t\t\t\t\tlatestPageVisible &&\n\t\t\t\t\tlatestHasFocus\n\t\t\t\t)\n\t\t\t) {\n\t\t\t\tmarkSeenInFlightRef.current = false;\n\t\t\t\tmarkSeenTimeoutRef.current = null;\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tmarkSeenInFlightRef.current = true;\n\n\t\t\tclient\n\t\t\t\t.markConversationSeen({ conversationId })\n\t\t\t\t.then((response) => {\n\t\t\t\t\tlastSeenItemIdRef.current = pendingItemId;\n\n\t\t\t\t\t// Optimistically update local seen store\n\t\t\t\t\tupsertConversationSeen({\n\t\t\t\t\t\tconversationId,\n\t\t\t\t\t\tactorType: \"visitor\",\n\t\t\t\t\t\tactorId: visitorId,\n\t\t\t\t\t\tlastSeenAt: new Date(response.lastSeenAt),\n\t\t\t\t\t});\n\t\t\t\t})\n\t\t\t\t.catch((err) => {\n\t\t\t\t\tconsole.error(\"Failed to mark conversation as seen:\", err);\n\t\t\t\t})\n\t\t\t\t.finally(() => {\n\t\t\t\t\tmarkSeenInFlightRef.current = false;\n\t\t\t\t\tmarkSeenTimeoutRef.current = null;\n\t\t\t\t});\n\t\t}, CONVERSATION_AUTO_SEEN_DELAY_MS);\n\n\t\treturn () => {\n\t\t\tif (markSeenTimeoutRef.current) {\n\t\t\t\tclearTimeout(markSeenTimeoutRef.current);\n\t\t\t\tmarkSeenTimeoutRef.current = null;\n\t\t\t}\n\t\t};\n\t}, [\n\t\tenabled,\n\t\tisWidgetOpen,\n\t\tclient,\n\t\tconversationId,\n\t\tvisitorId,\n\t\tlastTimelineItem,\n\t\tisPageVisible,\n\t\thasWindowFocus,\n\t]);\n}\n"],"mappings":";;;;;AASA,MAAa,kCAAkC;;;;;;;;;;;;;;;;;;;;;;;;AA8D/C,SAAgB,wBACf,SACO;CACP,MAAM,EACL,QACA,gBACA,WACA,kBACA,UAAU,MACV,eAAe,SACZ;CAEJ,MAAM,oBAAoB,OAAsB,KAAK;CACrD,MAAM,sBAAsB,OAAO,MAAM;CACzC,MAAM,qBAAqB,OAA6C,KAAK;CAC7E,MAAM,EAAE,eAAe,mBAAmB,0BAA0B;CACpE,MAAM,iBAAiB,OAAO;EAC7B;EACA;EACA;EACA;EACA,CAAC;AAEF,iBAAgB;AACf,iBAAe,UAAU;GACxB;GACA;GACA;GACA;GACA;IACC;EAAC;EAAS;EAAc;EAAgB;EAAc,CAAC;AAG1D,iBAAgB;AACf,oBAAkB,UAAU;AAC5B,sBAAoB,UAAU;AAC9B,MAAI,mBAAmB,SAAS;AAC/B,gBAAa,mBAAmB,QAAQ;AACxC,sBAAmB,UAAU;;IAE5B,CAAC,eAAe,CAAC;AAGpB,iBAAgB;AACf,MAAI,CAAC,cAAc;AAClB,OAAI,mBAAmB,SAAS;AAC/B,iBAAa,mBAAmB,QAAQ;AACxC,uBAAmB,UAAU;;AAE9B,uBAAoB,UAAU;AAG9B,qBAAkB,UAAU;;IAE3B,CAAC,aAAa,CAAC;AAGlB,iBAAgB;AACf,MAAI,WAAW,UAAU,eACxB,CAAK,OACH,wBAAwB,EAAE,gBAAgB,CAAC,CAC3C,MAAM,aAAa;AACnB,OAAI,SAAS,SAAS,SAAS,EAC9B,yBAAwB,gBAAgB,SAAS,SAAS;IAE1D,CACD,OAAO,QAAQ;AACf,WAAQ,MAAM,2CAA2C,IAAI;IAC5D;IAEF;EAAC;EAAS;EAAQ;EAAe,CAAC;AAGrC,iBAAgB;AAEf,MAAI,EAAE,gBAAgB,UAAU;AAC/B,OAAI,mBAAmB,SAAS;AAC/B,iBAAa,mBAAmB,QAAQ;AACxC,uBAAmB,UAAU;;AAE9B;;AAGD,MAAI,mBAAmB,SAAS;AAC/B,gBAAa,mBAAmB,QAAQ;AACxC,sBAAmB,UAAU;;AAW9B,MAAI,EAPH,UACA,kBACA,aACA,oBACA,iBACA,gBAGA;AAID,MAAI,iBAAiB,cAAc,WAAW;AAC7C,qBAAkB,UAAU,iBAAiB,MAAM;AACnD;;AAID,MAAI,kBAAkB,YAAY,iBAAiB,GAClD;AAID,MAAI,oBAAoB,QACvB;EAGD,MAAM,gBAAgB,iBAAiB,MAAM;AAE7C,qBAAmB,UAAU,iBAAiB;GAC7C,MAAM,EACL,SAAS,eACT,cAAc,oBACd,eAAe,mBACf,gBAAgB,mBACb,eAAe;AAEnB,OACC,EACC,UACA,kBACA,iBACA,sBACA,qBACA,iBAEA;AACD,wBAAoB,UAAU;AAC9B,uBAAmB,UAAU;AAC7B;;AAGD,uBAAoB,UAAU;AAE9B,UACE,qBAAqB,EAAE,gBAAgB,CAAC,CACxC,MAAM,aAAa;AACnB,sBAAkB,UAAU;AAG5B,2BAAuB;KACtB;KACA,WAAW;KACX,SAAS;KACT,YAAY,IAAI,KAAK,SAAS,WAAW;KACzC,CAAC;KACD,CACD,OAAO,QAAQ;AACf,YAAQ,MAAM,wCAAwC,IAAI;KACzD,CACD,cAAc;AACd,wBAAoB,UAAU;AAC9B,uBAAmB,UAAU;KAC5B;KACD,gCAAgC;AAEnC,eAAa;AACZ,OAAI,mBAAmB,SAAS;AAC/B,iBAAa,mBAAmB,QAAQ;AACxC,uBAAmB,UAAU;;;IAG7B;EACF;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-conversation-history-page.d.ts","names":[],"sources":["../../src/hooks/use-conversation-history-page.ts"],"sourcesContent":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"use-conversation-history-page.d.ts","names":[],"sources":["../../src/hooks/use-conversation-history-page.ts"],"sourcesContent":[],"mappings":";;;KAKY,iCAAA;;AAAZ;AAwBA;;EAIQ,mBAAA,CAAA,EAAA,MAAA;EAGe;;AA0DvB;;;;;;;;;;;;KAjEY,gCAAA;iBAEI;;SAER;wBAGe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA0DP,0BAAA,WACN,oCACP"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { shouldDisplayConversation } from "../utils/conversation.js";
|
|
1
2
|
import { useConversations } from "./use-conversations.js";
|
|
2
3
|
import { useCallback, useMemo, useState } from "react";
|
|
3
4
|
|
|
@@ -50,33 +51,22 @@ import { useCallback, useMemo, useState } from "react";
|
|
|
50
51
|
*/
|
|
51
52
|
function useConversationHistoryPage(options = {}) {
|
|
52
53
|
const { initialVisibleCount = 4, enabled = true, onOpenConversation, onStartConversation } = options;
|
|
53
|
-
const { conversations, isLoading, error } = useConversations({
|
|
54
|
+
const { conversations: allConversations, isLoading, error } = useConversations({
|
|
54
55
|
enabled,
|
|
55
56
|
orderBy: "updatedAt",
|
|
56
57
|
order: "desc"
|
|
57
58
|
});
|
|
59
|
+
const conversations = useMemo(() => allConversations.filter(shouldDisplayConversation), [allConversations]);
|
|
58
60
|
const [visibleCount, setVisibleCount] = useState(initialVisibleCount);
|
|
59
61
|
const { visibleConversations, hasMore, remainingCount } = useMemo(() => {
|
|
60
62
|
const visible = conversations.slice(0, visibleCount);
|
|
61
|
-
const remaining = Math.max(conversations.length -
|
|
63
|
+
const remaining = Math.max(conversations.length - visible.length, 0);
|
|
62
64
|
return {
|
|
63
65
|
visibleConversations: visible,
|
|
64
66
|
hasMore: remaining > 0,
|
|
65
67
|
remainingCount: remaining
|
|
66
68
|
};
|
|
67
69
|
}, [conversations, visibleCount]);
|
|
68
|
-
const showMore = useCallback(() => {
|
|
69
|
-
setVisibleCount((current) => current + initialVisibleCount);
|
|
70
|
-
}, [initialVisibleCount]);
|
|
71
|
-
const showAll = useCallback(() => {
|
|
72
|
-
setVisibleCount(conversations.length);
|
|
73
|
-
}, [conversations.length]);
|
|
74
|
-
const openConversation = useCallback((conversationId) => {
|
|
75
|
-
onOpenConversation?.(conversationId);
|
|
76
|
-
}, [onOpenConversation]);
|
|
77
|
-
const startConversation = useCallback((initialMessage) => {
|
|
78
|
-
onStartConversation?.(initialMessage);
|
|
79
|
-
}, [onStartConversation]);
|
|
80
70
|
return {
|
|
81
71
|
conversations,
|
|
82
72
|
isLoading,
|
|
@@ -85,10 +75,18 @@ function useConversationHistoryPage(options = {}) {
|
|
|
85
75
|
visibleCount,
|
|
86
76
|
hasMore,
|
|
87
77
|
remainingCount,
|
|
88
|
-
showMore
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
78
|
+
showMore: useCallback(() => {
|
|
79
|
+
setVisibleCount((current) => current + initialVisibleCount);
|
|
80
|
+
}, [initialVisibleCount]),
|
|
81
|
+
showAll: useCallback(() => {
|
|
82
|
+
setVisibleCount(conversations.length);
|
|
83
|
+
}, [conversations.length]),
|
|
84
|
+
openConversation: useCallback((conversationId) => {
|
|
85
|
+
onOpenConversation?.(conversationId);
|
|
86
|
+
}, [onOpenConversation]),
|
|
87
|
+
startConversation: useCallback((initialMessage) => {
|
|
88
|
+
onStartConversation?.(initialMessage);
|
|
89
|
+
}, [onStartConversation])
|
|
92
90
|
};
|
|
93
91
|
}
|
|
94
92
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-conversation-history-page.js","names":[],"sources":["../../src/hooks/use-conversation-history-page.ts"],"sourcesContent":["import type { Conversation } from \"@cossistant/types\";\nimport { useCallback, useMemo, useState } from \"react\";\nimport { useConversations } from \"./use-conversations\";\n\nexport type UseConversationHistoryPageOptions = {\n\t/**\n\t * Initial number of conversations to display.\n\t * Default: 4\n\t */\n\tinitialVisibleCount?: number;\n\n\t/**\n\t * Whether to enable conversations fetching.\n\t * Default: true\n\t */\n\tenabled?: boolean;\n\n\t/**\n\t * Callback when user wants to open a conversation.\n\t */\n\tonOpenConversation?: (conversationId: string) => void;\n\n\t/**\n\t * Callback when user wants to start a new conversation.\n\t */\n\tonStartConversation?: (initialMessage?: string) => void;\n};\n\nexport type UseConversationHistoryPageReturn = {\n\t// Conversations data\n\tconversations: Conversation[];\n\tisLoading: boolean;\n\terror: Error | null;\n\n\t// Pagination state\n\tvisibleConversations: Conversation[];\n\tvisibleCount: number;\n\thasMore: boolean;\n\tremainingCount: number;\n\n\t// Actions\n\tshowMore: () => void;\n\tshowAll: () => void;\n\topenConversation: (conversationId: string) => void;\n\tstartConversation: (initialMessage?: string) => void;\n};\n\n/**\n * Main hook for the conversation history page.\n *\n * This hook:\n * - Fetches all conversations\n * - Manages pagination/visible count\n * - Provides navigation actions\n *\n * It encapsulates all conversation history logic, making the component\n * purely presentational.\n *\n * @example\n * ```tsx\n * export function ConversationHistoryPage() {\n * const history = useConversationHistoryPage({\n * initialVisibleCount: 4,\n * onOpenConversation: (id) => {\n * navigate('conversation', { conversationId: id });\n * },\n * onStartConversation: (msg) => {\n * navigate('conversation', { conversationId: PENDING_CONVERSATION_ID, initialMessage: msg });\n * },\n * });\n *\n * return (\n * <>\n * <h1>Conversation History</h1>\n *\n * {history.hasMore && (\n * <button onClick={history.showAll}>\n * +{history.remainingCount} more\n * </button>\n * )}\n *\n * <ul>\n * {history.visibleConversations.map(conv => (\n * <li key={conv.id} onClick={() => history.openConversation(conv.id)}>\n * {conv.title}\n * </li>\n * ))}\n * </ul>\n * </>\n * );\n * }\n * ```\n */\nexport function useConversationHistoryPage(\n\toptions: UseConversationHistoryPageOptions = {}\n): UseConversationHistoryPageReturn {\n\tconst {\n\t\tinitialVisibleCount = 4,\n\t\tenabled = true,\n\t\tonOpenConversation,\n\t\tonStartConversation,\n\t} = options;\n\n\t// Fetch conversations\n\tconst {
|
|
1
|
+
{"version":3,"file":"use-conversation-history-page.js","names":[],"sources":["../../src/hooks/use-conversation-history-page.ts"],"sourcesContent":["import type { Conversation } from \"@cossistant/types\";\nimport { useCallback, useMemo, useState } from \"react\";\nimport { shouldDisplayConversation } from \"../utils/conversation\";\nimport { useConversations } from \"./use-conversations\";\n\nexport type UseConversationHistoryPageOptions = {\n\t/**\n\t * Initial number of conversations to display.\n\t * Default: 4\n\t */\n\tinitialVisibleCount?: number;\n\n\t/**\n\t * Whether to enable conversations fetching.\n\t * Default: true\n\t */\n\tenabled?: boolean;\n\n\t/**\n\t * Callback when user wants to open a conversation.\n\t */\n\tonOpenConversation?: (conversationId: string) => void;\n\n\t/**\n\t * Callback when user wants to start a new conversation.\n\t */\n\tonStartConversation?: (initialMessage?: string) => void;\n};\n\nexport type UseConversationHistoryPageReturn = {\n\t// Conversations data\n\tconversations: Conversation[];\n\tisLoading: boolean;\n\terror: Error | null;\n\n\t// Pagination state\n\tvisibleConversations: Conversation[];\n\tvisibleCount: number;\n\thasMore: boolean;\n\tremainingCount: number;\n\n\t// Actions\n\tshowMore: () => void;\n\tshowAll: () => void;\n\topenConversation: (conversationId: string) => void;\n\tstartConversation: (initialMessage?: string) => void;\n};\n\n/**\n * Main hook for the conversation history page.\n *\n * This hook:\n * - Fetches all conversations\n * - Manages pagination/visible count\n * - Provides navigation actions\n *\n * It encapsulates all conversation history logic, making the component\n * purely presentational.\n *\n * @example\n * ```tsx\n * export function ConversationHistoryPage() {\n * const history = useConversationHistoryPage({\n * initialVisibleCount: 4,\n * onOpenConversation: (id) => {\n * navigate('conversation', { conversationId: id });\n * },\n * onStartConversation: (msg) => {\n * navigate('conversation', { conversationId: PENDING_CONVERSATION_ID, initialMessage: msg });\n * },\n * });\n *\n * return (\n * <>\n * <h1>Conversation History</h1>\n *\n * {history.hasMore && (\n * <button onClick={history.showAll}>\n * +{history.remainingCount} more\n * </button>\n * )}\n *\n * <ul>\n * {history.visibleConversations.map(conv => (\n * <li key={conv.id} onClick={() => history.openConversation(conv.id)}>\n * {conv.title}\n * </li>\n * ))}\n * </ul>\n * </>\n * );\n * }\n * ```\n */\nexport function useConversationHistoryPage(\n\toptions: UseConversationHistoryPageOptions = {}\n): UseConversationHistoryPageReturn {\n\tconst {\n\t\tinitialVisibleCount = 4,\n\t\tenabled = true,\n\t\tonOpenConversation,\n\t\tonStartConversation,\n\t} = options;\n\n\t// Fetch conversations\n\tconst {\n\t\tconversations: allConversations,\n\t\tisLoading,\n\t\terror,\n\t} = useConversations({\n\t\tenabled,\n\t\t// Most recent first\n\t\torderBy: \"updatedAt\",\n\t\torder: \"desc\",\n\t});\n\n\tconst conversations = useMemo(\n\t\t() => allConversations.filter(shouldDisplayConversation),\n\t\t[allConversations]\n\t);\n\n\t// Manage visible count for pagination\n\tconst [visibleCount, setVisibleCount] = useState(initialVisibleCount);\n\n\t// Compute visible conversations and pagination state\n\tconst { visibleConversations, hasMore, remainingCount } = useMemo(() => {\n\t\tconst visible = conversations.slice(0, visibleCount);\n\t\tconst remaining = Math.max(conversations.length - visible.length, 0);\n\n\t\treturn {\n\t\t\tvisibleConversations: visible,\n\t\t\thasMore: remaining > 0,\n\t\t\tremainingCount: remaining,\n\t\t};\n\t}, [conversations, visibleCount]);\n\n\t// Actions\n\tconst showMore = useCallback(() => {\n\t\tsetVisibleCount((current) => current + initialVisibleCount);\n\t}, [initialVisibleCount]);\n\n\tconst showAll = useCallback(() => {\n\t\tsetVisibleCount(conversations.length);\n\t}, [conversations.length]);\n\n\tconst openConversation = useCallback(\n\t\t(conversationId: string) => {\n\t\t\tonOpenConversation?.(conversationId);\n\t\t},\n\t\t[onOpenConversation]\n\t);\n\n\tconst startConversation = useCallback(\n\t\t(initialMessage?: string) => {\n\t\t\tonStartConversation?.(initialMessage);\n\t\t},\n\t\t[onStartConversation]\n\t);\n\n\treturn {\n\t\tconversations,\n\t\tisLoading,\n\t\terror,\n\t\tvisibleConversations,\n\t\tvisibleCount,\n\t\thasMore,\n\t\tremainingCount,\n\t\tshowMore,\n\t\tshowAll,\n\t\topenConversation,\n\t\tstartConversation,\n\t};\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8FA,SAAgB,2BACf,UAA6C,EAAE,EACZ;CACnC,MAAM,EACL,sBAAsB,GACtB,UAAU,MACV,oBACA,wBACG;CAGJ,MAAM,EACL,eAAe,kBACf,WACA,UACG,iBAAiB;EACpB;EAEA,SAAS;EACT,OAAO;EACP,CAAC;CAEF,MAAM,gBAAgB,cACf,iBAAiB,OAAO,0BAA0B,EACxD,CAAC,iBAAiB,CAClB;CAGD,MAAM,CAAC,cAAc,mBAAmB,SAAS,oBAAoB;CAGrE,MAAM,EAAE,sBAAsB,SAAS,mBAAmB,cAAc;EACvE,MAAM,UAAU,cAAc,MAAM,GAAG,aAAa;EACpD,MAAM,YAAY,KAAK,IAAI,cAAc,SAAS,QAAQ,QAAQ,EAAE;AAEpE,SAAO;GACN,sBAAsB;GACtB,SAAS,YAAY;GACrB,gBAAgB;GAChB;IACC,CAAC,eAAe,aAAa,CAAC;AAyBjC,QAAO;EACN;EACA;EACA;EACA;EACA;EACA;EACA;EACA,UA9BgB,kBAAkB;AAClC,oBAAiB,YAAY,UAAU,oBAAoB;KACzD,CAAC,oBAAoB,CAAC;EA6BxB,SA3Be,kBAAkB;AACjC,mBAAgB,cAAc,OAAO;KACnC,CAAC,cAAc,OAAO,CAAC;EA0BzB,kBAxBwB,aACvB,mBAA2B;AAC3B,wBAAqB,eAAe;KAErC,CAAC,mBAAmB,CACpB;EAoBA,mBAlByB,aACxB,mBAA4B;AAC5B,yBAAsB,eAAe;KAEtC,CAAC,oBAAoB,CACrB;EAcA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-conversation-lifecycle.d.ts","names":[],"sources":["../../src/hooks/use-conversation-lifecycle.ts"],"sourcesContent":[],"mappings":";KAGY,0BAAA;EAAA;AAqBZ;AA8BA;AAkCA
|
|
1
|
+
{"version":3,"file":"use-conversation-lifecycle.d.ts","names":[],"sources":["../../src/hooks/use-conversation-lifecycle.ts"],"sourcesContent":[],"mappings":";KAGY,0BAAA;EAAA;AAqBZ;AA8BA;AAkCA;;;;;;;;;;;;;;KAhEY,+BAAA;;;;;;;;;;;;;;;;;;;;;;;;;KA8BA,8BAAA,GAAiC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAkC7B,wBAAA,WACN,kCACP"}
|
|
@@ -38,14 +38,12 @@ function useConversationLifecycle(options = {}) {
|
|
|
38
38
|
});
|
|
39
39
|
}, []);
|
|
40
40
|
const isPending = conversationId === PENDING_CONVERSATION_ID;
|
|
41
|
-
const realConversationId = isPending ? null : conversationId;
|
|
42
|
-
const isNewConversation = useCallback(() => conversationId === PENDING_CONVERSATION_ID, [conversationId]);
|
|
43
41
|
return {
|
|
44
42
|
conversationId,
|
|
45
43
|
isPending,
|
|
46
|
-
realConversationId,
|
|
44
|
+
realConversationId: isPending ? null : conversationId,
|
|
47
45
|
setConversationId,
|
|
48
|
-
isNewConversation
|
|
46
|
+
isNewConversation: useCallback(() => conversationId === PENDING_CONVERSATION_ID, [conversationId])
|
|
49
47
|
};
|
|
50
48
|
}
|
|
51
49
|
|