@cossistant/react 0.0.19 → 0.0.22
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/conversation.d.ts +28 -0
- package/conversation.d.ts.map +1 -1
- package/hooks/index.d.ts +4 -1
- package/hooks/index.js +4 -1
- package/hooks/private/use-default-messages.js +1 -1
- package/hooks/private/use-default-messages.js.map +1 -1
- package/hooks/private/use-grouped-messages.d.ts.map +1 -1
- package/hooks/private/use-grouped-messages.js +4 -18
- package/hooks/private/use-grouped-messages.js.map +1 -1
- package/hooks/private/use-rest-client.js +1 -1
- package/hooks/private/use-rest-client.js.map +1 -1
- package/hooks/private/use-visitor-typing-reporter.js +3 -3
- package/hooks/private/use-visitor-typing-reporter.js.map +1 -1
- package/hooks/use-conversation-auto-seen.d.ts +1 -1
- package/hooks/use-conversation-auto-seen.js +30 -46
- package/hooks/use-conversation-auto-seen.js.map +1 -1
- package/hooks/use-conversation-page.js +1 -1
- package/hooks/use-conversation-page.js.map +1 -1
- package/hooks/use-conversation-seen.d.ts.map +1 -1
- package/hooks/use-conversation-seen.js +7 -3
- package/hooks/use-conversation-seen.js.map +1 -1
- package/hooks/use-new-message-sound.d.ts +23 -0
- package/hooks/use-new-message-sound.d.ts.map +1 -0
- package/hooks/use-new-message-sound.js +34 -0
- package/hooks/use-new-message-sound.js.map +1 -0
- package/hooks/use-send-message.js +1 -1
- package/hooks/use-send-message.js.map +1 -1
- package/hooks/use-sound-effect.d.ts +30 -0
- package/hooks/use-sound-effect.d.ts.map +1 -0
- package/hooks/use-sound-effect.js +104 -0
- package/hooks/use-sound-effect.js.map +1 -0
- package/hooks/use-typing-sound.d.ts +18 -0
- package/hooks/use-typing-sound.d.ts.map +1 -0
- package/hooks/use-typing-sound.js +38 -0
- package/hooks/use-typing-sound.js.map +1 -0
- package/index.d.ts +5 -2
- package/index.js +8 -6
- package/package.json +3 -3
- package/primitives/avatar/image.d.ts +1 -1
- package/primitives/bubble.js +1 -1
- package/primitives/index.d.ts +3 -5
- package/primitives/index.js +3 -9
- package/primitives/index.parts.d.ts +2 -4
- package/primitives/index.parts.js +2 -4
- package/primitives/router.d.ts +19 -20
- package/primitives/router.d.ts.map +1 -1
- package/primitives/router.js +17 -11
- package/primitives/router.js.map +1 -1
- package/realtime/index.js +1 -1
- package/realtime/provider.d.ts +1 -0
- package/realtime/provider.d.ts.map +1 -1
- package/realtime/provider.js +59 -10
- package/realtime/provider.js.map +1 -1
- package/realtime-events.d.ts +14 -0
- package/realtime-events.d.ts.map +1 -1
- package/schemas3.d.ts +7 -0
- package/schemas3.d.ts.map +1 -1
- package/support/components/bubble.d.ts.map +1 -1
- package/support/components/bubble.js +27 -4
- package/support/components/bubble.js.map +1 -1
- package/support/components/conversation-event.js +1 -1
- package/support/components/conversation-event.js.map +1 -1
- package/support/components/conversation-timeline.d.ts.map +1 -1
- package/support/components/conversation-timeline.js +5 -0
- package/support/components/conversation-timeline.js.map +1 -1
- package/support/components/support-content.d.ts +2 -0
- package/support/components/support-content.d.ts.map +1 -1
- package/support/components/support-content.js +5 -2
- package/support/components/support-content.js.map +1 -1
- package/support/components/timeline-message-group.js +2 -2
- package/support/components/timeline-message-group.js.map +1 -1
- package/support/components/timeline-message-item.js +2 -2
- package/support/components/timeline-message-item.js.map +1 -1
- package/support/components/typing-indicator.d.ts.map +1 -1
- package/support/index.d.ts +12 -7
- package/support/index.d.ts.map +1 -1
- package/support/index.js +28 -29
- package/support/index.js.map +1 -1
- package/support/pages/conversation.d.ts.map +1 -1
- package/support/pages/conversation.js +19 -1
- package/support/pages/conversation.js.map +1 -1
- package/support/router.d.ts +19 -9
- package/support/router.d.ts.map +1 -1
- package/support/router.js +31 -30
- package/support/router.js.map +1 -1
- package/support/text/runtime.js +1 -1
- package/support/text/runtime.js.map +1 -1
- package/support/utils/time.d.ts +1 -0
- package/support/utils/time.d.ts.map +1 -1
- package/support/utils/time.js +2 -0
- package/support/utils/time.js.map +1 -1
- package/timeline-item.d.ts +14 -0
- package/timeline-item.d.ts.map +1 -1
- package/primitives/page-registry.d.ts +0 -30
- package/primitives/page-registry.d.ts.map +0 -1
- package/primitives/page-registry.js +0 -45
- package/primitives/page-registry.js.map +0 -1
- package/primitives/page.d.ts +0 -21
- package/primitives/page.d.ts.map +0 -1
- package/primitives/page.js +0 -18
- package/primitives/page.js.map +0 -1
|
@@ -13,12 +13,16 @@ function useConversationSeen(conversationId, options = {}) {
|
|
|
13
13
|
const { initialData } = options;
|
|
14
14
|
const hydratedKeyRef = useRef(null);
|
|
15
15
|
useEffect(() => {
|
|
16
|
-
if (!
|
|
17
|
-
|
|
16
|
+
if (!conversationId) {
|
|
17
|
+
hydratedKeyRef.current = null;
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (!initialData || initialData.length === 0) return;
|
|
21
|
+
const hydrationKey = conversationId;
|
|
18
22
|
if (hydratedKeyRef.current === hydrationKey) return;
|
|
19
23
|
hydrateConversationSeen(conversationId, initialData);
|
|
20
24
|
hydratedKeyRef.current = hydrationKey;
|
|
21
|
-
}, [conversationId
|
|
25
|
+
}, [conversationId]);
|
|
22
26
|
const conversationSeen = useSeenStore((state) => conversationId ? state.conversations[conversationId] ?? null : null);
|
|
23
27
|
return useMemo(() => {
|
|
24
28
|
if (!(conversationId && conversationSeen)) return [];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-conversation-seen.js","names":[],"sources":["../../src/hooks/use-conversation-seen.ts"],"sourcesContent":["import type { ConversationSeen } from \"@cossistant/types/schemas\";\nimport { useEffect, useMemo, useRef, useState } from \"react\";\nimport { hydrateConversationSeen, useSeenStore } from \"../realtime/seen-store\";\n\ntype UseConversationSeenOptions = {\n\tinitialData?: ConversationSeen[];\n};\n\nfunction buildSeenId(\n\tconversationId: string,\n\tactorType: string,\n\tactorId: string\n) {\n\treturn `${conversationId}-${actorType}-${actorId}`;\n}\n\n/**\n * Reads the conversation seen store and optionally hydrates it with SSR\n * payloads.\n */\nexport function useConversationSeen(\n\tconversationId: string | null | undefined,\n\toptions: UseConversationSeenOptions = {}\n): ConversationSeen[] {\n\tconst { initialData } = options;\n\tconst hydratedKeyRef = useRef<string | null>(null);\n\n\tuseEffect(() => {\n\t\
|
|
1
|
+
{"version":3,"file":"use-conversation-seen.js","names":[],"sources":["../../src/hooks/use-conversation-seen.ts"],"sourcesContent":["import type { ConversationSeen } from \"@cossistant/types/schemas\";\nimport { useEffect, useMemo, useRef, useState } from \"react\";\nimport { hydrateConversationSeen, useSeenStore } from \"../realtime/seen-store\";\n\ntype UseConversationSeenOptions = {\n\tinitialData?: ConversationSeen[];\n};\n\nfunction buildSeenId(\n\tconversationId: string,\n\tactorType: string,\n\tactorId: string\n) {\n\treturn `${conversationId}-${actorType}-${actorId}`;\n}\n\n/**\n * Reads the conversation seen store and optionally hydrates it with SSR\n * payloads.\n */\nexport function useConversationSeen(\n\tconversationId: string | null | undefined,\n\toptions: UseConversationSeenOptions = {}\n): ConversationSeen[] {\n\tconst { initialData } = options;\n\tconst hydratedKeyRef = useRef<string | null>(null);\n\n\tuseEffect(() => {\n\t\t// Clear hydration key when conversation changes or is unmounted\n\t\tif (!conversationId) {\n\t\t\thydratedKeyRef.current = null;\n\t\t\treturn;\n\t\t}\n\n\t\t// Skip if no initial data\n\t\tif (!initialData || initialData.length === 0) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Only hydrate once per conversation\n\t\tconst hydrationKey = conversationId;\n\n\t\tif (hydratedKeyRef.current === hydrationKey) {\n\t\t\treturn; // Already hydrated for this conversation\n\t\t}\n\n\t\thydrateConversationSeen(conversationId, initialData);\n\t\thydratedKeyRef.current = hydrationKey;\n\t}, [conversationId]); // Only depend on conversationId, NOT initialData\n\n\tconst conversationSeen = useSeenStore((state) =>\n\t\tconversationId ? (state.conversations[conversationId] ?? null) : null\n\t);\n\n\treturn useMemo(() => {\n\t\tif (!(conversationId && conversationSeen)) {\n\t\t\treturn [];\n\t\t}\n\n\t\treturn Object.values(conversationSeen).map(\n\t\t\t(entry) =>\n\t\t\t\t({\n\t\t\t\t\tid: buildSeenId(conversationId, entry.actorType, entry.actorId),\n\t\t\t\t\tconversationId,\n\t\t\t\t\tuserId: entry.actorType === \"user\" ? entry.actorId : null,\n\t\t\t\t\tvisitorId: entry.actorType === \"visitor\" ? entry.actorId : null,\n\t\t\t\t\taiAgentId: entry.actorType === \"ai_agent\" ? entry.actorId : null,\n\t\t\t\t\tlastSeenAt: entry.lastSeenAt,\n\t\t\t\t\tcreatedAt: entry.lastSeenAt,\n\t\t\t\t\tupdatedAt: entry.lastSeenAt,\n\t\t\t\t\tdeletedAt: null,\n\t\t\t\t}) satisfies ConversationSeen\n\t\t);\n\t}, [conversationId, conversationSeen]);\n}\n\n/**\n * Debounced version of useConversationSeen that delays updates by 500ms\n * to prevent animation conflicts when messages are sent and immediately seen.\n *\n * Use this in UI components where smooth animations are critical.\n */\nexport function useDebouncedConversationSeen(\n\tconversationId: string | null | undefined,\n\toptions: UseConversationSeenOptions = {},\n\tdelay = 500\n): ConversationSeen[] {\n\tconst seenData = useConversationSeen(conversationId, options);\n\tconst [debouncedSeenData, setDebouncedSeenData] =\n\t\tuseState<ConversationSeen[]>(seenData);\n\tconst timeoutRef = useRef<NodeJS.Timeout | null>(null);\n\n\tuseEffect(() => {\n\t\t// Clear any pending timeout\n\t\tif (timeoutRef.current) {\n\t\t\tclearTimeout(timeoutRef.current);\n\t\t}\n\n\t\t// Set new timeout to update after delay\n\t\ttimeoutRef.current = setTimeout(() => {\n\t\t\tsetDebouncedSeenData(seenData);\n\t\t}, delay);\n\n\t\t// Cleanup on unmount or when seenData changes\n\t\treturn () => {\n\t\t\tif (timeoutRef.current) {\n\t\t\t\tclearTimeout(timeoutRef.current);\n\t\t\t}\n\t\t};\n\t}, [seenData, delay]);\n\n\treturn debouncedSeenData;\n}\n"],"mappings":";;;;AAQA,SAAS,YACR,gBACA,WACA,SACC;AACD,QAAO,GAAG,eAAe,GAAG,UAAU,GAAG;;;;;;AAO1C,SAAgB,oBACf,gBACA,UAAsC,EAAE,EACnB;CACrB,MAAM,EAAE,gBAAgB;CACxB,MAAM,iBAAiB,OAAsB,KAAK;AAElD,iBAAgB;AAEf,MAAI,CAAC,gBAAgB;AACpB,kBAAe,UAAU;AACzB;;AAID,MAAI,CAAC,eAAe,YAAY,WAAW,EAC1C;EAID,MAAM,eAAe;AAErB,MAAI,eAAe,YAAY,aAC9B;AAGD,0BAAwB,gBAAgB,YAAY;AACpD,iBAAe,UAAU;IACvB,CAAC,eAAe,CAAC;CAEpB,MAAM,mBAAmB,cAAc,UACtC,iBAAkB,MAAM,cAAc,mBAAmB,OAAQ,KACjE;AAED,QAAO,cAAc;AACpB,MAAI,EAAE,kBAAkB,kBACvB,QAAO,EAAE;AAGV,SAAO,OAAO,OAAO,iBAAiB,CAAC,KACrC,WACC;GACA,IAAI,YAAY,gBAAgB,MAAM,WAAW,MAAM,QAAQ;GAC/D;GACA,QAAQ,MAAM,cAAc,SAAS,MAAM,UAAU;GACrD,WAAW,MAAM,cAAc,YAAY,MAAM,UAAU;GAC3D,WAAW,MAAM,cAAc,aAAa,MAAM,UAAU;GAC5D,YAAY,MAAM;GAClB,WAAW,MAAM;GACjB,WAAW,MAAM;GACjB,WAAW;GACX,EACF;IACC,CAAC,gBAAgB,iBAAiB,CAAC;;;;;;;;AASvC,SAAgB,6BACf,gBACA,UAAsC,EAAE,EACxC,QAAQ,KACa;CACrB,MAAM,WAAW,oBAAoB,gBAAgB,QAAQ;CAC7D,MAAM,CAAC,mBAAmB,wBACzB,SAA6B,SAAS;CACvC,MAAM,aAAa,OAA8B,KAAK;AAEtD,iBAAgB;AAEf,MAAI,WAAW,QACd,cAAa,WAAW,QAAQ;AAIjC,aAAW,UAAU,iBAAiB;AACrC,wBAAqB,SAAS;KAC5B,MAAM;AAGT,eAAa;AACZ,OAAI,WAAW,QACd,cAAa,WAAW,QAAQ;;IAGhC,CAAC,UAAU,MAAM,CAAC;AAErB,QAAO"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//#region src/hooks/use-new-message-sound.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Hook to play a sound when a new message arrives.
|
|
4
|
+
*
|
|
5
|
+
* @param options - Optional configuration for volume and playback speed
|
|
6
|
+
* @returns Function to play the new message sound
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* const playNewMessageSound = useNewMessageSound({ volume: 0.8, playbackRate: 1.1 });
|
|
10
|
+
*
|
|
11
|
+
* useEffect(() => {
|
|
12
|
+
* if (hasNewMessage) {
|
|
13
|
+
* playNewMessageSound();
|
|
14
|
+
* }
|
|
15
|
+
* }, [hasNewMessage]);
|
|
16
|
+
*/
|
|
17
|
+
declare function useNewMessageSound(options?: {
|
|
18
|
+
volume?: number;
|
|
19
|
+
playbackRate?: number;
|
|
20
|
+
}): () => void;
|
|
21
|
+
//#endregion
|
|
22
|
+
export { useNewMessageSound };
|
|
23
|
+
//# sourceMappingURL=use-new-message-sound.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-new-message-sound.d.ts","names":[],"sources":["../../src/hooks/use-new-message-sound.ts"],"sourcesContent":[],"mappings":";;AAqBA;;;;;;;;;;;;;;iBAAgB,kBAAA"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { useSoundEffect } from "./use-sound-effect.js";
|
|
2
|
+
import { useCallback } from "react";
|
|
3
|
+
|
|
4
|
+
//#region src/hooks/use-new-message-sound.ts
|
|
5
|
+
const NEW_MESSAGE_SOUND_PATH = "/sounds/new-message.wav";
|
|
6
|
+
/**
|
|
7
|
+
* Hook to play a sound when a new message arrives.
|
|
8
|
+
*
|
|
9
|
+
* @param options - Optional configuration for volume and playback speed
|
|
10
|
+
* @returns Function to play the new message sound
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* const playNewMessageSound = useNewMessageSound({ volume: 0.8, playbackRate: 1.1 });
|
|
14
|
+
*
|
|
15
|
+
* useEffect(() => {
|
|
16
|
+
* if (hasNewMessage) {
|
|
17
|
+
* playNewMessageSound();
|
|
18
|
+
* }
|
|
19
|
+
* }, [hasNewMessage]);
|
|
20
|
+
*/
|
|
21
|
+
function useNewMessageSound(options) {
|
|
22
|
+
const { play } = useSoundEffect(NEW_MESSAGE_SOUND_PATH, {
|
|
23
|
+
loop: false,
|
|
24
|
+
volume: options?.volume ?? .7,
|
|
25
|
+
playbackRate: options?.playbackRate ?? 1
|
|
26
|
+
});
|
|
27
|
+
return useCallback(() => {
|
|
28
|
+
play();
|
|
29
|
+
}, [play]);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
//#endregion
|
|
33
|
+
export { useNewMessageSound };
|
|
34
|
+
//# sourceMappingURL=use-new-message-sound.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-new-message-sound.js","names":[],"sources":["../../src/hooks/use-new-message-sound.ts"],"sourcesContent":["import { useCallback } from \"react\";\nimport { useSoundEffect } from \"./use-sound-effect\";\n\n// Use a path that can be served from public directory\nconst NEW_MESSAGE_SOUND_PATH = \"/sounds/new-message.wav\";\n\n/**\n * Hook to play a sound when a new message arrives.\n *\n * @param options - Optional configuration for volume and playback speed\n * @returns Function to play the new message sound\n *\n * @example\n * const playNewMessageSound = useNewMessageSound({ volume: 0.8, playbackRate: 1.1 });\n *\n * useEffect(() => {\n * if (hasNewMessage) {\n * playNewMessageSound();\n * }\n * }, [hasNewMessage]);\n */\nexport function useNewMessageSound(options?: {\n\tvolume?: number;\n\tplaybackRate?: number;\n}): () => void {\n\tconst { play } = useSoundEffect(NEW_MESSAGE_SOUND_PATH, {\n\t\tloop: false,\n\t\tvolume: options?.volume ?? 0.7,\n\t\tplaybackRate: options?.playbackRate ?? 1.0,\n\t});\n\n\treturn useCallback(() => {\n\t\tplay();\n\t}, [play]);\n}\n"],"mappings":";;;;AAIA,MAAM,yBAAyB;;;;;;;;;;;;;;;;AAiB/B,SAAgB,mBAAmB,SAGpB;CACd,MAAM,EAAE,SAAS,eAAe,wBAAwB;EACvD,MAAM;EACN,QAAQ,SAAS,UAAU;EAC3B,cAAc,SAAS,gBAAgB;EACvC,CAAC;AAEF,QAAO,kBAAkB;AACxB,QAAM;IACJ,CAAC,KAAK,CAAC"}
|
|
@@ -9,7 +9,7 @@ function toError(error) {
|
|
|
9
9
|
return /* @__PURE__ */ new Error("Unknown error");
|
|
10
10
|
}
|
|
11
11
|
function buildTimelineItemPayload(body, conversationId, visitorId, messageId) {
|
|
12
|
-
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
12
|
+
const nowIso = typeof window !== "undefined" ? (/* @__PURE__ */ new Date()).toISOString() : "";
|
|
13
13
|
return {
|
|
14
14
|
id: messageId ?? generateMessageId(),
|
|
15
15
|
conversationId,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-send-message.js","names":["initialConversation:\n\t\t\t\t\t| CreateConversationResponseBody[\"conversation\"]\n\t\t\t\t\t| undefined","result: SendMessageResult"],"sources":["../../src/hooks/use-send-message.ts"],"sourcesContent":["import type { CossistantClient } from \"@cossistant/core\";\nimport { generateMessageId } from \"@cossistant/core\";\nimport type { CreateConversationResponseBody } from \"@cossistant/types/api/conversation\";\nimport type { TimelineItem } from \"@cossistant/types/api/timeline-item\";\nimport { useCallback, useState } from \"react\";\n\nimport { useSupport } from \"../provider\";\n\nexport type SendMessageOptions = {\n\tconversationId?: string | null;\n\tmessage: string;\n\tfiles?: File[];\n\tdefaultTimelineItems?: TimelineItem[];\n\tvisitorId?: string;\n\t/**\n\t * Optional message ID to use for the optimistic update and API request.\n\t * When not provided, a ULID will be generated on the client.\n\t */\n\tmessageId?: string;\n\tonSuccess?: (conversationId: string, messageId: string) => void;\n\tonError?: (error: Error) => void;\n};\n\nexport type SendMessageResult = {\n\tconversationId: string;\n\tmessageId: string;\n\tconversation?: CreateConversationResponseBody[\"conversation\"];\n\tinitialTimelineItems?: CreateConversationResponseBody[\"initialTimelineItems\"];\n};\n\nexport type UseSendMessageResult = {\n\tmutate: (options: SendMessageOptions) => void;\n\tmutateAsync: (\n\t\toptions: SendMessageOptions\n\t) => Promise<SendMessageResult | null>;\n\tisPending: boolean;\n\terror: Error | null;\n\treset: () => void;\n};\n\nexport type UseSendMessageOptions = {\n\tclient?: CossistantClient;\n};\n\nfunction toError(error: unknown): Error {\n\tif (error instanceof Error) {\n\t\treturn error;\n\t}\n\n\tif (typeof error === \"string\") {\n\t\treturn new Error(error);\n\t}\n\n\treturn new Error(\"Unknown error\");\n}\n\nfunction buildTimelineItemPayload(\n\tbody: string,\n\tconversationId: string,\n\tvisitorId: string | null,\n\tmessageId?: string\n): TimelineItem {\n\tconst nowIso = new Date().toISOString();\n\tconst id = messageId ?? generateMessageId();\n\n\treturn {\n\t\tid,\n\t\tconversationId,\n\t\torganizationId: \"\", // Will be set by backend\n\t\ttype: \"message\" as const,\n\t\ttext: body,\n\t\tparts: [{ type: \"text\" as const, text: body }],\n\t\tvisibility: \"public\" as const,\n\t\tuserId: null,\n\t\taiAgentId: null,\n\t\tvisitorId: visitorId ?? null,\n\t\tcreatedAt: nowIso,\n\t\tdeletedAt: null,\n\t} satisfies TimelineItem;\n}\n\n/**\n * Sends visitor messages while handling optimistic pending conversations and\n * exposing react-query-like mutation state.\n */\nexport function useSendMessage(\n\toptions: UseSendMessageOptions = {}\n): UseSendMessageResult {\n\tconst { client: contextClient } = useSupport();\n\tconst client = options.client ?? contextClient;\n\n\tconst [isPending, setIsPending] = useState(false);\n\tconst [error, setError] = useState<Error | null>(null);\n\n\tconst mutateAsync = useCallback(\n\t\tasync (payload: SendMessageOptions): Promise<SendMessageResult | null> => {\n\t\t\tconst {\n\t\t\t\tconversationId: providedConversationId,\n\t\t\t\tmessage,\n\t\t\t\tdefaultTimelineItems = [],\n\t\t\t\tvisitorId,\n\t\t\t\tmessageId: providedMessageId,\n\t\t\t\tonSuccess,\n\t\t\t\tonError,\n\t\t\t} = payload;\n\n\t\t\tif (!message.trim()) {\n\t\t\t\tconst emptyMessageError = new Error(\"Message cannot be empty\");\n\t\t\t\tsetError(emptyMessageError);\n\t\t\t\tonError?.(emptyMessageError);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tsetIsPending(true);\n\t\t\tsetError(null);\n\n\t\t\ttry {\n\t\t\t\tlet conversationId = providedConversationId ?? undefined;\n\t\t\t\tlet preparedDefaultTimelineItems = defaultTimelineItems;\n\t\t\t\tlet initialConversation:\n\t\t\t\t\t| CreateConversationResponseBody[\"conversation\"]\n\t\t\t\t\t| undefined;\n\n\t\t\t\tif (!conversationId) {\n\t\t\t\t\tconst initiated = client.initiateConversation({\n\t\t\t\t\t\tdefaultTimelineItems,\n\t\t\t\t\t\tvisitorId: visitorId ?? undefined,\n\t\t\t\t\t});\n\t\t\t\t\tconversationId = initiated.conversationId;\n\t\t\t\t\tpreparedDefaultTimelineItems = initiated.defaultTimelineItems;\n\t\t\t\t\tinitialConversation = initiated.conversation;\n\t\t\t\t}\n\n\t\t\t\tconst timelineItemPayload = buildTimelineItemPayload(\n\t\t\t\t\tmessage,\n\t\t\t\t\tconversationId,\n\t\t\t\t\tvisitorId ?? null,\n\t\t\t\t\tprovidedMessageId\n\t\t\t\t);\n\n\t\t\t\tconst response = await client.sendMessage({\n\t\t\t\t\tconversationId,\n\t\t\t\t\titem: {\n\t\t\t\t\t\tid: timelineItemPayload.id,\n\t\t\t\t\t\ttext: timelineItemPayload.text ?? \"\",\n\t\t\t\t\t\ttype:\n\t\t\t\t\t\t\ttimelineItemPayload.type === \"identification\"\n\t\t\t\t\t\t\t\t? \"message\"\n\t\t\t\t\t\t\t\t: timelineItemPayload.type,\n\t\t\t\t\t\tvisibility: timelineItemPayload.visibility,\n\t\t\t\t\t\tuserId: timelineItemPayload.userId,\n\t\t\t\t\t\taiAgentId: timelineItemPayload.aiAgentId,\n\t\t\t\t\t\tvisitorId: timelineItemPayload.visitorId,\n\t\t\t\t\t\tcreatedAt: timelineItemPayload.createdAt,\n\t\t\t\t\t\tparts: timelineItemPayload.parts,\n\t\t\t\t\t},\n\t\t\t\t\tcreateIfPending: true,\n\t\t\t\t});\n\n\t\t\t\tconst messageId = response.item.id;\n\n\t\t\t\tif (!messageId) {\n\t\t\t\t\tthrow new Error(\"SendMessage response missing item.id\");\n\t\t\t\t}\n\n\t\t\t\tconst result: SendMessageResult = {\n\t\t\t\t\tconversationId,\n\t\t\t\t\tmessageId,\n\t\t\t\t};\n\n\t\t\t\tif (\"conversation\" in response && response.conversation) {\n\t\t\t\t\tresult.conversation = response.conversation;\n\t\t\t\t\tresult.initialTimelineItems = response.initialTimelineItems;\n\t\t\t\t} else if (initialConversation) {\n\t\t\t\t\tresult.conversation = initialConversation;\n\t\t\t\t\tresult.initialTimelineItems = preparedDefaultTimelineItems;\n\t\t\t\t}\n\n\t\t\t\tsetIsPending(false);\n\t\t\t\tsetError(null);\n\t\t\t\tonSuccess?.(result.conversationId, result.messageId);\n\t\t\t\treturn result;\n\t\t\t} catch (raw) {\n\t\t\t\tconst normalised = toError(raw);\n\t\t\t\tsetIsPending(false);\n\t\t\t\tsetError(normalised);\n\t\t\t\tonError?.(normalised);\n\t\t\t\tthrow normalised;\n\t\t\t}\n\t\t},\n\t\t[client]\n\t);\n\n\tconst mutate = useCallback(\n\t\t(opts: SendMessageOptions) => {\n\t\t\tvoid mutateAsync(opts).catch(() => {\n\t\t\t\t// Swallow errors to mimic react-query behaviour for mutate\n\t\t\t});\n\t\t},\n\t\t[mutateAsync]\n\t);\n\n\tconst reset = useCallback(() => {\n\t\tsetError(null);\n\t\tsetIsPending(false);\n\t}, []);\n\n\treturn {\n\t\tmutate,\n\t\tmutateAsync,\n\t\tisPending,\n\t\terror,\n\t\treset,\n\t};\n}\n"],"mappings":";;;;;AA4CA,SAAS,QAAQ,OAAuB;AACvC,KAAI,iBAAiB,MACpB,QAAO;AAGR,KAAI,OAAO,UAAU,SACpB,QAAO,IAAI,MAAM,MAAM;AAGxB,wBAAO,IAAI,MAAM,gBAAgB;;AAGlC,SAAS,yBACR,MACA,gBACA,WACA,WACe;CACf,MAAM,
|
|
1
|
+
{"version":3,"file":"use-send-message.js","names":["initialConversation:\n\t\t\t\t\t| CreateConversationResponseBody[\"conversation\"]\n\t\t\t\t\t| undefined","result: SendMessageResult"],"sources":["../../src/hooks/use-send-message.ts"],"sourcesContent":["import type { CossistantClient } from \"@cossistant/core\";\nimport { generateMessageId } from \"@cossistant/core\";\nimport type { CreateConversationResponseBody } from \"@cossistant/types/api/conversation\";\nimport type { TimelineItem } from \"@cossistant/types/api/timeline-item\";\nimport { useCallback, useState } from \"react\";\n\nimport { useSupport } from \"../provider\";\n\nexport type SendMessageOptions = {\n\tconversationId?: string | null;\n\tmessage: string;\n\tfiles?: File[];\n\tdefaultTimelineItems?: TimelineItem[];\n\tvisitorId?: string;\n\t/**\n\t * Optional message ID to use for the optimistic update and API request.\n\t * When not provided, a ULID will be generated on the client.\n\t */\n\tmessageId?: string;\n\tonSuccess?: (conversationId: string, messageId: string) => void;\n\tonError?: (error: Error) => void;\n};\n\nexport type SendMessageResult = {\n\tconversationId: string;\n\tmessageId: string;\n\tconversation?: CreateConversationResponseBody[\"conversation\"];\n\tinitialTimelineItems?: CreateConversationResponseBody[\"initialTimelineItems\"];\n};\n\nexport type UseSendMessageResult = {\n\tmutate: (options: SendMessageOptions) => void;\n\tmutateAsync: (\n\t\toptions: SendMessageOptions\n\t) => Promise<SendMessageResult | null>;\n\tisPending: boolean;\n\terror: Error | null;\n\treset: () => void;\n};\n\nexport type UseSendMessageOptions = {\n\tclient?: CossistantClient;\n};\n\nfunction toError(error: unknown): Error {\n\tif (error instanceof Error) {\n\t\treturn error;\n\t}\n\n\tif (typeof error === \"string\") {\n\t\treturn new Error(error);\n\t}\n\n\treturn new Error(\"Unknown error\");\n}\n\nfunction buildTimelineItemPayload(\n\tbody: string,\n\tconversationId: string,\n\tvisitorId: string | null,\n\tmessageId?: string\n): TimelineItem {\n\tconst nowIso = typeof window !== \"undefined\" ? new Date().toISOString() : \"\";\n\tconst id = messageId ?? generateMessageId();\n\n\treturn {\n\t\tid,\n\t\tconversationId,\n\t\torganizationId: \"\", // Will be set by backend\n\t\ttype: \"message\" as const,\n\t\ttext: body,\n\t\tparts: [{ type: \"text\" as const, text: body }],\n\t\tvisibility: \"public\" as const,\n\t\tuserId: null,\n\t\taiAgentId: null,\n\t\tvisitorId: visitorId ?? null,\n\t\tcreatedAt: nowIso,\n\t\tdeletedAt: null,\n\t} satisfies TimelineItem;\n}\n\n/**\n * Sends visitor messages while handling optimistic pending conversations and\n * exposing react-query-like mutation state.\n */\nexport function useSendMessage(\n\toptions: UseSendMessageOptions = {}\n): UseSendMessageResult {\n\tconst { client: contextClient } = useSupport();\n\tconst client = options.client ?? contextClient;\n\n\tconst [isPending, setIsPending] = useState(false);\n\tconst [error, setError] = useState<Error | null>(null);\n\n\tconst mutateAsync = useCallback(\n\t\tasync (payload: SendMessageOptions): Promise<SendMessageResult | null> => {\n\t\t\tconst {\n\t\t\t\tconversationId: providedConversationId,\n\t\t\t\tmessage,\n\t\t\t\tdefaultTimelineItems = [],\n\t\t\t\tvisitorId,\n\t\t\t\tmessageId: providedMessageId,\n\t\t\t\tonSuccess,\n\t\t\t\tonError,\n\t\t\t} = payload;\n\n\t\t\tif (!message.trim()) {\n\t\t\t\tconst emptyMessageError = new Error(\"Message cannot be empty\");\n\t\t\t\tsetError(emptyMessageError);\n\t\t\t\tonError?.(emptyMessageError);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tsetIsPending(true);\n\t\t\tsetError(null);\n\n\t\t\ttry {\n\t\t\t\tlet conversationId = providedConversationId ?? undefined;\n\t\t\t\tlet preparedDefaultTimelineItems = defaultTimelineItems;\n\t\t\t\tlet initialConversation:\n\t\t\t\t\t| CreateConversationResponseBody[\"conversation\"]\n\t\t\t\t\t| undefined;\n\n\t\t\t\tif (!conversationId) {\n\t\t\t\t\tconst initiated = client.initiateConversation({\n\t\t\t\t\t\tdefaultTimelineItems,\n\t\t\t\t\t\tvisitorId: visitorId ?? undefined,\n\t\t\t\t\t});\n\t\t\t\t\tconversationId = initiated.conversationId;\n\t\t\t\t\tpreparedDefaultTimelineItems = initiated.defaultTimelineItems;\n\t\t\t\t\tinitialConversation = initiated.conversation;\n\t\t\t\t}\n\n\t\t\t\tconst timelineItemPayload = buildTimelineItemPayload(\n\t\t\t\t\tmessage,\n\t\t\t\t\tconversationId,\n\t\t\t\t\tvisitorId ?? null,\n\t\t\t\t\tprovidedMessageId\n\t\t\t\t);\n\n\t\t\t\tconst response = await client.sendMessage({\n\t\t\t\t\tconversationId,\n\t\t\t\t\titem: {\n\t\t\t\t\t\tid: timelineItemPayload.id,\n\t\t\t\t\t\ttext: timelineItemPayload.text ?? \"\",\n\t\t\t\t\t\ttype:\n\t\t\t\t\t\t\ttimelineItemPayload.type === \"identification\"\n\t\t\t\t\t\t\t\t? \"message\"\n\t\t\t\t\t\t\t\t: timelineItemPayload.type,\n\t\t\t\t\t\tvisibility: timelineItemPayload.visibility,\n\t\t\t\t\t\tuserId: timelineItemPayload.userId,\n\t\t\t\t\t\taiAgentId: timelineItemPayload.aiAgentId,\n\t\t\t\t\t\tvisitorId: timelineItemPayload.visitorId,\n\t\t\t\t\t\tcreatedAt: timelineItemPayload.createdAt,\n\t\t\t\t\t\tparts: timelineItemPayload.parts,\n\t\t\t\t\t},\n\t\t\t\t\tcreateIfPending: true,\n\t\t\t\t});\n\n\t\t\t\tconst messageId = response.item.id;\n\n\t\t\t\tif (!messageId) {\n\t\t\t\t\tthrow new Error(\"SendMessage response missing item.id\");\n\t\t\t\t}\n\n\t\t\t\tconst result: SendMessageResult = {\n\t\t\t\t\tconversationId,\n\t\t\t\t\tmessageId,\n\t\t\t\t};\n\n\t\t\t\tif (\"conversation\" in response && response.conversation) {\n\t\t\t\t\tresult.conversation = response.conversation;\n\t\t\t\t\tresult.initialTimelineItems = response.initialTimelineItems;\n\t\t\t\t} else if (initialConversation) {\n\t\t\t\t\tresult.conversation = initialConversation;\n\t\t\t\t\tresult.initialTimelineItems = preparedDefaultTimelineItems;\n\t\t\t\t}\n\n\t\t\t\tsetIsPending(false);\n\t\t\t\tsetError(null);\n\t\t\t\tonSuccess?.(result.conversationId, result.messageId);\n\t\t\t\treturn result;\n\t\t\t} catch (raw) {\n\t\t\t\tconst normalised = toError(raw);\n\t\t\t\tsetIsPending(false);\n\t\t\t\tsetError(normalised);\n\t\t\t\tonError?.(normalised);\n\t\t\t\tthrow normalised;\n\t\t\t}\n\t\t},\n\t\t[client]\n\t);\n\n\tconst mutate = useCallback(\n\t\t(opts: SendMessageOptions) => {\n\t\t\tvoid mutateAsync(opts).catch(() => {\n\t\t\t\t// Swallow errors to mimic react-query behaviour for mutate\n\t\t\t});\n\t\t},\n\t\t[mutateAsync]\n\t);\n\n\tconst reset = useCallback(() => {\n\t\tsetError(null);\n\t\tsetIsPending(false);\n\t}, []);\n\n\treturn {\n\t\tmutate,\n\t\tmutateAsync,\n\t\tisPending,\n\t\terror,\n\t\treset,\n\t};\n}\n"],"mappings":";;;;;AA4CA,SAAS,QAAQ,OAAuB;AACvC,KAAI,iBAAiB,MACpB,QAAO;AAGR,KAAI,OAAO,UAAU,SACpB,QAAO,IAAI,MAAM,MAAM;AAGxB,wBAAO,IAAI,MAAM,gBAAgB;;AAGlC,SAAS,yBACR,MACA,gBACA,WACA,WACe;CACf,MAAM,SAAS,OAAO,WAAW,+BAAc,IAAI,MAAM,EAAC,aAAa,GAAG;AAG1E,QAAO;EACN,IAHU,aAAa,mBAAmB;EAI1C;EACA,gBAAgB;EAChB,MAAM;EACN,MAAM;EACN,OAAO,CAAC;GAAE,MAAM;GAAiB,MAAM;GAAM,CAAC;EAC9C,YAAY;EACZ,QAAQ;EACR,WAAW;EACX,WAAW,aAAa;EACxB,WAAW;EACX,WAAW;EACX;;;;;;AAOF,SAAgB,eACf,UAAiC,EAAE,EACZ;CACvB,MAAM,EAAE,QAAQ,kBAAkB,YAAY;CAC9C,MAAM,SAAS,QAAQ,UAAU;CAEjC,MAAM,CAAC,WAAW,gBAAgB,SAAS,MAAM;CACjD,MAAM,CAAC,OAAO,YAAY,SAAuB,KAAK;CAEtD,MAAM,cAAc,YACnB,OAAO,YAAmE;EACzE,MAAM,EACL,gBAAgB,wBAChB,SACA,uBAAuB,EAAE,EACzB,WACA,WAAW,mBACX,WACA,YACG;AAEJ,MAAI,CAAC,QAAQ,MAAM,EAAE;GACpB,MAAM,oCAAoB,IAAI,MAAM,0BAA0B;AAC9D,YAAS,kBAAkB;AAC3B,aAAU,kBAAkB;AAC5B,UAAO;;AAGR,eAAa,KAAK;AAClB,WAAS,KAAK;AAEd,MAAI;GACH,IAAI,iBAAiB,0BAA0B;GAC/C,IAAI,+BAA+B;GACnC,IAAIA;AAIJ,OAAI,CAAC,gBAAgB;IACpB,MAAM,YAAY,OAAO,qBAAqB;KAC7C;KACA,WAAW,aAAa;KACxB,CAAC;AACF,qBAAiB,UAAU;AAC3B,mCAA+B,UAAU;AACzC,0BAAsB,UAAU;;GAGjC,MAAM,sBAAsB,yBAC3B,SACA,gBACA,aAAa,MACb,kBACA;GAED,MAAM,WAAW,MAAM,OAAO,YAAY;IACzC;IACA,MAAM;KACL,IAAI,oBAAoB;KACxB,MAAM,oBAAoB,QAAQ;KAClC,MACC,oBAAoB,SAAS,mBAC1B,YACA,oBAAoB;KACxB,YAAY,oBAAoB;KAChC,QAAQ,oBAAoB;KAC5B,WAAW,oBAAoB;KAC/B,WAAW,oBAAoB;KAC/B,WAAW,oBAAoB;KAC/B,OAAO,oBAAoB;KAC3B;IACD,iBAAiB;IACjB,CAAC;GAEF,MAAM,YAAY,SAAS,KAAK;AAEhC,OAAI,CAAC,UACJ,OAAM,IAAI,MAAM,uCAAuC;GAGxD,MAAMC,SAA4B;IACjC;IACA;IACA;AAED,OAAI,kBAAkB,YAAY,SAAS,cAAc;AACxD,WAAO,eAAe,SAAS;AAC/B,WAAO,uBAAuB,SAAS;cAC7B,qBAAqB;AAC/B,WAAO,eAAe;AACtB,WAAO,uBAAuB;;AAG/B,gBAAa,MAAM;AACnB,YAAS,KAAK;AACd,eAAY,OAAO,gBAAgB,OAAO,UAAU;AACpD,UAAO;WACC,KAAK;GACb,MAAM,aAAa,QAAQ,IAAI;AAC/B,gBAAa,MAAM;AACnB,YAAS,WAAW;AACpB,aAAU,WAAW;AACrB,SAAM;;IAGR,CAAC,OAAO,CACR;AAgBD,QAAO;EACN,QAfc,aACb,SAA6B;AAC7B,GAAK,YAAY,KAAK,CAAC,YAAY,GAEjC;KAEH,CAAC,YAAY,CACb;EASA;EACA;EACA;EACA,OAVa,kBAAkB;AAC/B,YAAS,KAAK;AACd,gBAAa,MAAM;KACjB,EAAE,CAAC;EAQL"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//#region src/hooks/use-sound-effect.d.ts
|
|
2
|
+
type UseSoundEffectOptions = {
|
|
3
|
+
loop?: boolean;
|
|
4
|
+
volume?: number;
|
|
5
|
+
playbackRate?: number;
|
|
6
|
+
};
|
|
7
|
+
type UseSoundEffectReturn = {
|
|
8
|
+
play: () => void;
|
|
9
|
+
stop: () => void;
|
|
10
|
+
isPlaying: boolean;
|
|
11
|
+
isLoading: boolean;
|
|
12
|
+
error: Error | null;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Hook to play sound effects using the Web Audio API.
|
|
16
|
+
*
|
|
17
|
+
* @param soundPath - Path to the sound file (relative to public directory or absolute URL)
|
|
18
|
+
* @param options - Configuration options for the sound
|
|
19
|
+
* @returns Object with play, stop functions and state
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* const { play, stop, isPlaying } = useSoundEffect('/sounds/notification.wav', {
|
|
23
|
+
* loop: false,
|
|
24
|
+
* volume: 0.5
|
|
25
|
+
* });
|
|
26
|
+
*/
|
|
27
|
+
declare function useSoundEffect(soundPath: string, options?: UseSoundEffectOptions): UseSoundEffectReturn;
|
|
28
|
+
//#endregion
|
|
29
|
+
export { UseSoundEffectOptions, UseSoundEffectReturn, useSoundEffect };
|
|
30
|
+
//# sourceMappingURL=use-sound-effect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-sound-effect.d.ts","names":[],"sources":["../../src/hooks/use-sound-effect.ts"],"sourcesContent":[],"mappings":";KAEY,qBAAA;EAAA,IAAA,CAAA,EAAA,OAAA;EAMA,MAAA,CAAA,EAAA,MAAA;EAqBI,YAAA,CAAA,EAAA,MAAc;;KArBlB,oBAAA;;;;;SAKJ;;;;;;;;;;;;;;;iBAgBQ,cAAA,8BAEN,wBACP"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/hooks/use-sound-effect.ts
|
|
4
|
+
/**
|
|
5
|
+
* Hook to play sound effects using the Web Audio API.
|
|
6
|
+
*
|
|
7
|
+
* @param soundPath - Path to the sound file (relative to public directory or absolute URL)
|
|
8
|
+
* @param options - Configuration options for the sound
|
|
9
|
+
* @returns Object with play, stop functions and state
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* const { play, stop, isPlaying } = useSoundEffect('/sounds/notification.wav', {
|
|
13
|
+
* loop: false,
|
|
14
|
+
* volume: 0.5
|
|
15
|
+
* });
|
|
16
|
+
*/
|
|
17
|
+
function useSoundEffect(soundPath, options = {}) {
|
|
18
|
+
const { loop = false, volume = 1, playbackRate = 1 } = options;
|
|
19
|
+
const [isPlaying, setIsPlaying] = useState(false);
|
|
20
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
21
|
+
const [error, setError] = useState(null);
|
|
22
|
+
const audioContextRef = useRef(null);
|
|
23
|
+
const audioBufferRef = useRef(null);
|
|
24
|
+
const sourceNodeRef = useRef(null);
|
|
25
|
+
const gainNodeRef = useRef(null);
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
let mounted = true;
|
|
28
|
+
const initAudio = async () => {
|
|
29
|
+
try {
|
|
30
|
+
if (!audioContextRef.current) audioContextRef.current = new (window.AudioContext || window.webkitAudioContext)();
|
|
31
|
+
const response = await fetch(soundPath);
|
|
32
|
+
if (!response.ok) throw new Error(`Failed to load sound: ${response.statusText}`);
|
|
33
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
34
|
+
const audioBuffer = await audioContextRef.current.decodeAudioData(arrayBuffer);
|
|
35
|
+
if (mounted) {
|
|
36
|
+
audioBufferRef.current = audioBuffer;
|
|
37
|
+
setIsLoading(false);
|
|
38
|
+
}
|
|
39
|
+
} catch (err) {
|
|
40
|
+
if (mounted) {
|
|
41
|
+
setError(err instanceof Error ? err : /* @__PURE__ */ new Error("Failed to load sound"));
|
|
42
|
+
setIsLoading(false);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
initAudio();
|
|
47
|
+
return () => {
|
|
48
|
+
mounted = false;
|
|
49
|
+
};
|
|
50
|
+
}, [soundPath]);
|
|
51
|
+
const play = useCallback(() => {
|
|
52
|
+
const audioContext = audioContextRef.current;
|
|
53
|
+
const audioBuffer = audioBufferRef.current;
|
|
54
|
+
if (!(audioContext && audioBuffer && !isLoading && !error)) return;
|
|
55
|
+
if (sourceNodeRef.current) try {
|
|
56
|
+
sourceNodeRef.current.stop();
|
|
57
|
+
} catch {}
|
|
58
|
+
const source = audioContext.createBufferSource();
|
|
59
|
+
source.buffer = audioBuffer;
|
|
60
|
+
source.loop = loop;
|
|
61
|
+
source.playbackRate.value = playbackRate;
|
|
62
|
+
const gainNode = audioContext.createGain();
|
|
63
|
+
gainNode.gain.value = volume;
|
|
64
|
+
source.connect(gainNode);
|
|
65
|
+
gainNode.connect(audioContext.destination);
|
|
66
|
+
sourceNodeRef.current = source;
|
|
67
|
+
gainNodeRef.current = gainNode;
|
|
68
|
+
source.onended = () => {
|
|
69
|
+
if (!loop) setIsPlaying(false);
|
|
70
|
+
};
|
|
71
|
+
source.start(0);
|
|
72
|
+
setIsPlaying(true);
|
|
73
|
+
}, [
|
|
74
|
+
loop,
|
|
75
|
+
volume,
|
|
76
|
+
playbackRate,
|
|
77
|
+
isLoading,
|
|
78
|
+
error
|
|
79
|
+
]);
|
|
80
|
+
const stop = useCallback(() => {
|
|
81
|
+
if (sourceNodeRef.current) {
|
|
82
|
+
try {
|
|
83
|
+
sourceNodeRef.current.stop();
|
|
84
|
+
sourceNodeRef.current.disconnect();
|
|
85
|
+
} catch {}
|
|
86
|
+
sourceNodeRef.current = null;
|
|
87
|
+
}
|
|
88
|
+
setIsPlaying(false);
|
|
89
|
+
}, []);
|
|
90
|
+
useEffect(() => () => {
|
|
91
|
+
stop();
|
|
92
|
+
}, [stop]);
|
|
93
|
+
return {
|
|
94
|
+
play,
|
|
95
|
+
stop,
|
|
96
|
+
isPlaying,
|
|
97
|
+
isLoading,
|
|
98
|
+
error
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
//#endregion
|
|
103
|
+
export { useSoundEffect };
|
|
104
|
+
//# sourceMappingURL=use-sound-effect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-sound-effect.js","names":[],"sources":["../../src/hooks/use-sound-effect.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\n\nexport type UseSoundEffectOptions = {\n\tloop?: boolean;\n\tvolume?: number;\n\tplaybackRate?: number;\n};\n\nexport type UseSoundEffectReturn = {\n\tplay: () => void;\n\tstop: () => void;\n\tisPlaying: boolean;\n\tisLoading: boolean;\n\terror: Error | null;\n};\n\n/**\n * Hook to play sound effects using the Web Audio API.\n *\n * @param soundPath - Path to the sound file (relative to public directory or absolute URL)\n * @param options - Configuration options for the sound\n * @returns Object with play, stop functions and state\n *\n * @example\n * const { play, stop, isPlaying } = useSoundEffect('/sounds/notification.wav', {\n * loop: false,\n * volume: 0.5\n * });\n */\nexport function useSoundEffect(\n\tsoundPath: string,\n\toptions: UseSoundEffectOptions = {}\n): UseSoundEffectReturn {\n\tconst { loop = false, volume = 1.0, playbackRate = 1.0 } = options;\n\n\tconst [isPlaying, setIsPlaying] = useState(false);\n\tconst [isLoading, setIsLoading] = useState(true);\n\tconst [error, setError] = useState<Error | null>(null);\n\n\tconst audioContextRef = useRef<AudioContext | null>(null);\n\tconst audioBufferRef = useRef<AudioBuffer | null>(null);\n\tconst sourceNodeRef = useRef<AudioBufferSourceNode | null>(null);\n\tconst gainNodeRef = useRef<GainNode | null>(null);\n\n\t// Initialize audio context and load sound\n\tuseEffect(() => {\n\t\tlet mounted = true;\n\n\t\tconst initAudio = async () => {\n\t\t\ttry {\n\t\t\t\t// Create audio context if it doesn't exist\n\t\t\t\tif (!audioContextRef.current) {\n\t\t\t\t\taudioContextRef.current = new (\n\t\t\t\t\t\twindow.AudioContext ||\n\t\t\t\t\t\t(\n\t\t\t\t\t\t\twindow as typeof window & {\n\t\t\t\t\t\t\t\twebkitAudioContext?: typeof AudioContext;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t).webkitAudioContext\n\t\t\t\t\t)();\n\t\t\t\t}\n\n\t\t\t\t// Load the audio file\n\t\t\t\tconst response = await fetch(soundPath);\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\tthrow new Error(`Failed to load sound: ${response.statusText}`);\n\t\t\t\t}\n\n\t\t\t\tconst arrayBuffer = await response.arrayBuffer();\n\t\t\t\tconst audioBuffer =\n\t\t\t\t\tawait audioContextRef.current.decodeAudioData(arrayBuffer);\n\n\t\t\t\tif (mounted) {\n\t\t\t\t\taudioBufferRef.current = audioBuffer;\n\t\t\t\t\tsetIsLoading(false);\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\tif (mounted) {\n\t\t\t\t\tsetError(\n\t\t\t\t\t\terr instanceof Error ? err : new Error(\"Failed to load sound\")\n\t\t\t\t\t);\n\t\t\t\t\tsetIsLoading(false);\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\n\t\tinitAudio();\n\n\t\treturn () => {\n\t\t\tmounted = false;\n\t\t};\n\t}, [soundPath]);\n\n\t// Play sound\n\tconst play = useCallback(() => {\n\t\tconst audioContext = audioContextRef.current;\n\t\tconst audioBuffer = audioBufferRef.current;\n\n\t\tconst canPlay = audioContext && audioBuffer && !isLoading && !error;\n\n\t\tif (!canPlay) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Stop any currently playing sound\n\t\tif (sourceNodeRef.current) {\n\t\t\ttry {\n\t\t\t\tsourceNodeRef.current.stop();\n\t\t\t} catch {\n\t\t\t\t// Ignore errors if already stopped\n\t\t\t}\n\t\t}\n\n\t\t// Create new source node\n\t\tconst source = audioContext.createBufferSource();\n\t\tsource.buffer = audioBuffer;\n\t\tsource.loop = loop;\n\t\tsource.playbackRate.value = playbackRate;\n\n\t\t// Create gain node for volume control\n\t\tconst gainNode = audioContext.createGain();\n\t\tgainNode.gain.value = volume;\n\n\t\t// Connect nodes: source -> gain -> destination\n\t\tsource.connect(gainNode);\n\t\tgainNode.connect(audioContext.destination);\n\n\t\t// Store references\n\t\tsourceNodeRef.current = source;\n\t\tgainNodeRef.current = gainNode;\n\n\t\t// Handle end event\n\t\tsource.onended = () => {\n\t\t\tif (!loop) {\n\t\t\t\tsetIsPlaying(false);\n\t\t\t}\n\t\t};\n\n\t\t// Start playback\n\t\tsource.start(0);\n\t\tsetIsPlaying(true);\n\t}, [loop, volume, playbackRate, isLoading, error]);\n\n\t// Stop sound\n\tconst stop = useCallback(() => {\n\t\tif (sourceNodeRef.current) {\n\t\t\ttry {\n\t\t\t\tsourceNodeRef.current.stop();\n\t\t\t\tsourceNodeRef.current.disconnect();\n\t\t\t} catch {\n\t\t\t\t// Ignore errors if already stopped\n\t\t\t}\n\t\t\tsourceNodeRef.current = null;\n\t\t}\n\t\tsetIsPlaying(false);\n\t}, []);\n\n\t// Cleanup on unmount\n\tuseEffect(\n\t\t() => () => {\n\t\t\tstop();\n\t\t},\n\t\t[stop]\n\t);\n\n\treturn {\n\t\tplay,\n\t\tstop,\n\t\tisPlaying,\n\t\tisLoading,\n\t\terror,\n\t};\n}\n"],"mappings":";;;;;;;;;;;;;;;;AA6BA,SAAgB,eACf,WACA,UAAiC,EAAE,EACZ;CACvB,MAAM,EAAE,OAAO,OAAO,SAAS,GAAK,eAAe,MAAQ;CAE3D,MAAM,CAAC,WAAW,gBAAgB,SAAS,MAAM;CACjD,MAAM,CAAC,WAAW,gBAAgB,SAAS,KAAK;CAChD,MAAM,CAAC,OAAO,YAAY,SAAuB,KAAK;CAEtD,MAAM,kBAAkB,OAA4B,KAAK;CACzD,MAAM,iBAAiB,OAA2B,KAAK;CACvD,MAAM,gBAAgB,OAAqC,KAAK;CAChE,MAAM,cAAc,OAAwB,KAAK;AAGjD,iBAAgB;EACf,IAAI,UAAU;EAEd,MAAM,YAAY,YAAY;AAC7B,OAAI;AAEH,QAAI,CAAC,gBAAgB,QACpB,iBAAgB,UAAU,KACzB,OAAO,gBAEN,OAGC,qBACA;IAIJ,MAAM,WAAW,MAAM,MAAM,UAAU;AACvC,QAAI,CAAC,SAAS,GACb,OAAM,IAAI,MAAM,yBAAyB,SAAS,aAAa;IAGhE,MAAM,cAAc,MAAM,SAAS,aAAa;IAChD,MAAM,cACL,MAAM,gBAAgB,QAAQ,gBAAgB,YAAY;AAE3D,QAAI,SAAS;AACZ,oBAAe,UAAU;AACzB,kBAAa,MAAM;;YAEZ,KAAK;AACb,QAAI,SAAS;AACZ,cACC,eAAe,QAAQ,sBAAM,IAAI,MAAM,uBAAuB,CAC9D;AACD,kBAAa,MAAM;;;;AAKtB,aAAW;AAEX,eAAa;AACZ,aAAU;;IAET,CAAC,UAAU,CAAC;CAGf,MAAM,OAAO,kBAAkB;EAC9B,MAAM,eAAe,gBAAgB;EACrC,MAAM,cAAc,eAAe;AAInC,MAAI,EAFY,gBAAgB,eAAe,CAAC,aAAa,CAAC,OAG7D;AAID,MAAI,cAAc,QACjB,KAAI;AACH,iBAAc,QAAQ,MAAM;UACrB;EAMT,MAAM,SAAS,aAAa,oBAAoB;AAChD,SAAO,SAAS;AAChB,SAAO,OAAO;AACd,SAAO,aAAa,QAAQ;EAG5B,MAAM,WAAW,aAAa,YAAY;AAC1C,WAAS,KAAK,QAAQ;AAGtB,SAAO,QAAQ,SAAS;AACxB,WAAS,QAAQ,aAAa,YAAY;AAG1C,gBAAc,UAAU;AACxB,cAAY,UAAU;AAGtB,SAAO,gBAAgB;AACtB,OAAI,CAAC,KACJ,cAAa,MAAM;;AAKrB,SAAO,MAAM,EAAE;AACf,eAAa,KAAK;IAChB;EAAC;EAAM;EAAQ;EAAc;EAAW;EAAM,CAAC;CAGlD,MAAM,OAAO,kBAAkB;AAC9B,MAAI,cAAc,SAAS;AAC1B,OAAI;AACH,kBAAc,QAAQ,MAAM;AAC5B,kBAAc,QAAQ,YAAY;WAC3B;AAGR,iBAAc,UAAU;;AAEzB,eAAa,MAAM;IACjB,EAAE,CAAC;AAGN,uBACa;AACX,QAAM;IAEP,CAAC,KAAK,CACN;AAED,QAAO;EACN;EACA;EACA;EACA;EACA;EACA"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//#region src/hooks/use-typing-sound.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Hook to play a looping typing sound while someone is typing.
|
|
4
|
+
*
|
|
5
|
+
* @param isTyping - Whether someone is currently typing
|
|
6
|
+
* @param options - Optional configuration for volume and playback speed
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* const { isTyping } = useTypingIndicator();
|
|
10
|
+
* useTypingSound(isTyping, { volume: 1.0, playbackRate: 1.2 });
|
|
11
|
+
*/
|
|
12
|
+
declare function useTypingSound(isTyping: boolean, options?: {
|
|
13
|
+
volume?: number;
|
|
14
|
+
playbackRate?: number;
|
|
15
|
+
}): void;
|
|
16
|
+
//#endregion
|
|
17
|
+
export { useTypingSound };
|
|
18
|
+
//# sourceMappingURL=use-typing-sound.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-typing-sound.d.ts","names":[],"sources":["../../src/hooks/use-typing-sound.ts"],"sourcesContent":[],"mappings":";;AAiBA;;;;;;;;;iBAAgB,cAAA"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { useSoundEffect } from "./use-sound-effect.js";
|
|
2
|
+
import { useEffect } from "react";
|
|
3
|
+
|
|
4
|
+
//#region src/hooks/use-typing-sound.ts
|
|
5
|
+
const TYPING_SOUND_PATH = "/sounds/typing-loop.wav";
|
|
6
|
+
/**
|
|
7
|
+
* Hook to play a looping typing sound while someone is typing.
|
|
8
|
+
*
|
|
9
|
+
* @param isTyping - Whether someone is currently typing
|
|
10
|
+
* @param options - Optional configuration for volume and playback speed
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* const { isTyping } = useTypingIndicator();
|
|
14
|
+
* useTypingSound(isTyping, { volume: 1.0, playbackRate: 1.2 });
|
|
15
|
+
*/
|
|
16
|
+
function useTypingSound(isTyping, options) {
|
|
17
|
+
const { play, stop, isPlaying } = useSoundEffect(TYPING_SOUND_PATH, {
|
|
18
|
+
loop: true,
|
|
19
|
+
volume: options?.volume ?? 1.2,
|
|
20
|
+
playbackRate: options?.playbackRate ?? 1
|
|
21
|
+
});
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (isTyping && !isPlaying) play();
|
|
24
|
+
else if (!isTyping && isPlaying) stop();
|
|
25
|
+
}, [
|
|
26
|
+
isTyping,
|
|
27
|
+
isPlaying,
|
|
28
|
+
play,
|
|
29
|
+
stop
|
|
30
|
+
]);
|
|
31
|
+
useEffect(() => () => {
|
|
32
|
+
stop();
|
|
33
|
+
}, [stop]);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
//#endregion
|
|
37
|
+
export { useTypingSound };
|
|
38
|
+
//# sourceMappingURL=use-typing-sound.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-typing-sound.js","names":[],"sources":["../../src/hooks/use-typing-sound.ts"],"sourcesContent":["import { useEffect } from \"react\";\nimport { useSoundEffect } from \"./use-sound-effect\";\n\n// Use a data URL or base64 encoded sound, or a CDN URL\n// For now, we'll use a path that can be served from public directory\nconst TYPING_SOUND_PATH = \"/sounds/typing-loop.wav\";\n\n/**\n * Hook to play a looping typing sound while someone is typing.\n *\n * @param isTyping - Whether someone is currently typing\n * @param options - Optional configuration for volume and playback speed\n *\n * @example\n * const { isTyping } = useTypingIndicator();\n * useTypingSound(isTyping, { volume: 1.0, playbackRate: 1.2 });\n */\nexport function useTypingSound(\n\tisTyping: boolean,\n\toptions?: { volume?: number; playbackRate?: number }\n): void {\n\tconst { play, stop, isPlaying } = useSoundEffect(TYPING_SOUND_PATH, {\n\t\tloop: true,\n\t\tvolume: options?.volume ?? 1.2,\n\t\tplaybackRate: options?.playbackRate ?? 1.0,\n\t});\n\n\tuseEffect(() => {\n\t\tif (isTyping && !isPlaying) {\n\t\t\tplay();\n\t\t} else if (!isTyping && isPlaying) {\n\t\t\tstop();\n\t\t}\n\t}, [isTyping, isPlaying, play, stop]);\n\n\t// Cleanup on unmount\n\tuseEffect(\n\t\t() => () => {\n\t\t\tstop();\n\t\t},\n\t\t[stop]\n\t);\n}\n"],"mappings":";;;;AAKA,MAAM,oBAAoB;;;;;;;;;;;AAY1B,SAAgB,eACf,UACA,SACO;CACP,MAAM,EAAE,MAAM,MAAM,cAAc,eAAe,mBAAmB;EACnE,MAAM;EACN,QAAQ,SAAS,UAAU;EAC3B,cAAc,SAAS,gBAAgB;EACvC,CAAC;AAEF,iBAAgB;AACf,MAAI,YAAY,CAAC,UAChB,OAAM;WACI,CAAC,YAAY,UACvB,OAAM;IAEL;EAAC;EAAU;EAAW;EAAM;EAAK,CAAC;AAGrC,uBACa;AACX,QAAM;IAEP,CAAC,KAAK,CACN"}
|
package/index.d.ts
CHANGED
|
@@ -19,15 +19,17 @@ import { UseConversationsOptions, UseConversationsResult, useConversations } fro
|
|
|
19
19
|
import { CreateConversationVariables, UseCreateConversationOptions, UseCreateConversationResult, useCreateConversation } from "./hooks/use-create-conversation.js";
|
|
20
20
|
import { UseHomePageOptions, UseHomePageReturn, useHomePage } from "./hooks/use-home-page.js";
|
|
21
21
|
import { UseMessageComposerOptions, UseMessageComposerReturn, useMessageComposer } from "./hooks/use-message-composer.js";
|
|
22
|
+
import { useNewMessageSound } from "./hooks/use-new-message-sound.js";
|
|
22
23
|
import { UseRealtimeSupportOptions, UseRealtimeSupportResult, useRealtimeSupport } from "./hooks/use-realtime-support.js";
|
|
23
24
|
import { UseScrollMaskOptions, UseScrollMaskReturn, useScrollMask } from "./hooks/use-scroll-mask.js";
|
|
24
25
|
import { SendMessageOptions, SendMessageResult, UseSendMessageOptions, UseSendMessageResult, useSendMessage } from "./hooks/use-send-message.js";
|
|
26
|
+
import { UseSoundEffectOptions, UseSoundEffectReturn, useSoundEffect } from "./hooks/use-sound-effect.js";
|
|
27
|
+
import { useTypingSound } from "./hooks/use-typing-sound.js";
|
|
25
28
|
import { UseVisitorReturn, useVisitor } from "./hooks/use-visitor.js";
|
|
26
29
|
import { WindowVisibilityFocusState, useWindowVisibilityFocus } from "./hooks/use-window-visibility-focus.js";
|
|
27
30
|
import "./hooks/index.js";
|
|
28
31
|
import { IdentifySupportVisitor, IdentifySupportVisitorProps } from "./identify-visitor.js";
|
|
29
32
|
import { SupportConfig, SupportConfigProps } from "./support-config.js";
|
|
30
|
-
import { Page, PageProps } from "./primitives/page.js";
|
|
31
33
|
import { index_d_exports } from "./primitives/index.js";
|
|
32
34
|
import { CossistantContextValue, CossistantProviderProps, SupportContext, SupportProvider, SupportProviderProps, UseSupportValue, useSupport } from "./provider.js";
|
|
33
35
|
import { RealtimeAuthConfig, RealtimeContextValue, RealtimeProvider, RealtimeProviderProps, useRealtimeConnection } from "./realtime/provider.js";
|
|
@@ -36,6 +38,7 @@ import { SupportRealtimeProvider } from "./realtime/support-provider.js";
|
|
|
36
38
|
import { applyConversationTypingEvent, clearTypingFromTimelineItem, clearTypingState, setTypingState } from "./realtime/typing-store.js";
|
|
37
39
|
import { RealtimeEventHandler, RealtimeEventHandlerEntry, RealtimeEventHandlersMap, RealtimeEventMeta, useRealtime } from "./realtime/use-realtime.js";
|
|
38
40
|
import "./realtime/index.js";
|
|
41
|
+
import { CustomPage } from "./support/router.js";
|
|
39
42
|
import { Text, useSupportText } from "./support/text/index.js";
|
|
40
43
|
import { BubbleSlotProps, ContainerSlotProps, RouterSlotProps } from "./support/types.js";
|
|
41
44
|
import { CoButton } from "./support/components/button.js";
|
|
@@ -43,4 +46,4 @@ import { Header } from "./support/components/header.js";
|
|
|
43
46
|
import { WebSocketContextValue, WebSocketProvider, useWebSocket } from "./support/context/websocket.js";
|
|
44
47
|
import { useSupportConfig, useSupportNavigation, useSupportStore } from "./support/store/support-store.js";
|
|
45
48
|
import { DefaultRoutes, NavigationState, RouteRegistry, Support, SupportPage, SupportProps } from "./support/index.js";
|
|
46
|
-
export { BubbleSlotProps, CoButton as Button, CONVERSATION_AUTO_SEEN_DELAY_MS, ContainerSlotProps, ConversationItem, ConversationLifecycleState, ConversationPreviewAssignedAgent, ConversationPreviewLastMessage, ConversationPreviewTypingParticipant, ConversationPreviewTypingState, ConversationTimelineTypingParticipant, ConversationTypingParticipant, CossistantContextValue, CossistantProviderProps, CreateConversationVariables, DefaultRoutes, GroupedMessage, Header, IdentifySupportVisitor, IdentifySupportVisitorProps, NavigationState,
|
|
49
|
+
export { BubbleSlotProps, CoButton as Button, CONVERSATION_AUTO_SEEN_DELAY_MS, ContainerSlotProps, ConversationItem, ConversationLifecycleState, ConversationPreviewAssignedAgent, ConversationPreviewLastMessage, ConversationPreviewTypingParticipant, ConversationPreviewTypingState, ConversationTimelineTypingParticipant, ConversationTypingParticipant, CossistantContextValue, CossistantProviderProps, CreateConversationVariables, CustomPage, DefaultRoutes, GroupedMessage, Header, IdentifySupportVisitor, IdentifySupportVisitorProps, NavigationState, index_d_exports as Primitives, RealtimeAuthConfig, RealtimeContextValue, RealtimeEventHandler, RealtimeEventHandlerEntry, RealtimeEventHandlersMap, RealtimeEventMeta, RealtimeProvider, RealtimeProviderProps, RouteRegistry, RouterSlotProps, SendMessageOptions, SendMessageResult, Support, SupportConfig, SupportConfigProps, SupportContext, SupportLocale, SupportPage, SupportProps, SupportProvider, SupportProviderProps, SupportRealtimeProvider, SupportTextContentOverrides, Text, TimelineEventItem, TimelineToolItem, UseClientResult, UseComposerRefocusOptions, UseComposerRefocusReturn, UseConversationAutoSeenOptions, UseConversationHistoryPageOptions, UseConversationHistoryPageReturn, UseConversationLifecycleOptions, UseConversationLifecycleReturn, UseConversationOptions, UseConversationPageOptions, UseConversationPageReturn, UseConversationPreviewOptions, UseConversationPreviewReturn, UseConversationResult, UseConversationTimelineItemsOptions, UseConversationTimelineItemsResult, UseConversationTimelineOptions, UseConversationTimelineReturn, UseConversationsOptions, UseConversationsResult, UseCreateConversationOptions, UseCreateConversationResult, UseGroupedMessagesOptions, UseGroupedMessagesProps, UseHomePageOptions, UseHomePageReturn, UseMessageComposerOptions, UseMessageComposerReturn, UseMultimodalInputOptions, UseMultimodalInputReturn, UseRealtimeSupportOptions, UseRealtimeSupportResult, UseScrollMaskOptions, UseScrollMaskReturn, UseSendMessageOptions, UseSendMessageResult, UseSoundEffectOptions, UseSoundEffectReturn, UseSupportValue, UseVisitorReturn, WebSocketContextValue, WebSocketProvider, WindowVisibilityFocusState, applyConversationSeenEvent, applyConversationTypingEvent, clearTypingFromTimelineItem, clearTypingState, hydrateConversationSeen, setTypingState, upsertConversationSeen, useClient, useClientQuery, useComposerRefocus, useConversation, useConversationAutoSeen, useConversationHistoryPage, useConversationLifecycle, useConversationPage, useConversationPreview, useConversationSeen, useConversationTimeline, useConversationTimelineItems, useConversationTyping, useConversations, useCreateConversation, useDebouncedConversationSeen, useDefaultMessages, useGroupedMessages, useHomePage, useMessageComposer, useMultimodalInput, useNewMessageSound, useRealtime, useRealtimeConnection, useRealtimeSupport, useScrollMask, useSendMessage, useSoundEffect, useSupport, useSupportConfig, useSupportNavigation, useSupportStore, useSupportText, useTypingSound, useVisitor, useWebSocket, useWindowVisibilityFocus };
|
package/index.js
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import { useClientQuery } from "./hooks/private/use-client-query.js";
|
|
2
2
|
import { useClient } from "./hooks/private/use-rest-client.js";
|
|
3
3
|
import { applyConversationSeenEvent, hydrateConversationSeen, upsertConversationSeen } from "./realtime/seen-store.js";
|
|
4
|
-
import {
|
|
4
|
+
import { RealtimeProvider, useRealtimeConnection } from "./realtime/provider.js";
|
|
5
5
|
import { applyConversationTypingEvent, clearTypingFromTimelineItem, clearTypingState, setTypingState } from "./realtime/typing-store.js";
|
|
6
|
+
import { useRealtime } from "./realtime/use-realtime.js";
|
|
7
|
+
import { SupportRealtimeProvider } from "./realtime/support-provider.js";
|
|
8
|
+
import { SupportConfig } from "./support-config.js";
|
|
6
9
|
import { useScrollMask } from "./hooks/use-scroll-mask.js";
|
|
7
|
-
import { Page } from "./primitives/page.js";
|
|
8
10
|
import { useSupportConfig, useSupportNavigation, useSupportStore } from "./support/store/support-store.js";
|
|
9
11
|
import { primitives_exports } from "./primitives/index.js";
|
|
10
|
-
import { RealtimeProvider, useRealtimeConnection } from "./realtime/provider.js";
|
|
11
|
-
import { useRealtime } from "./realtime/use-realtime.js";
|
|
12
|
-
import { SupportRealtimeProvider } from "./realtime/support-provider.js";
|
|
13
12
|
import { CoButton } from "./support/components/button.js";
|
|
14
13
|
import { Header } from "./support/components/header.js";
|
|
15
14
|
import { Text, useSupportText } from "./support/text/index.js";
|
|
@@ -22,10 +21,13 @@ import { useMultimodalInput } from "./hooks/private/use-multimodal-input.js";
|
|
|
22
21
|
import { useSendMessage } from "./hooks/use-send-message.js";
|
|
23
22
|
import { useMessageComposer } from "./hooks/use-message-composer.js";
|
|
24
23
|
import { useConversationPage } from "./hooks/use-conversation-page.js";
|
|
24
|
+
import { useSoundEffect } from "./hooks/use-sound-effect.js";
|
|
25
|
+
import { useNewMessageSound } from "./hooks/use-new-message-sound.js";
|
|
25
26
|
import { useGroupedMessages } from "./hooks/private/use-grouped-messages.js";
|
|
26
27
|
import { useConversationSeen, useDebouncedConversationSeen } from "./hooks/use-conversation-seen.js";
|
|
27
28
|
import { useConversationTyping } from "./hooks/use-conversation-typing.js";
|
|
28
29
|
import { useConversationTimeline } from "./hooks/use-conversation-timeline.js";
|
|
30
|
+
import { useTypingSound } from "./hooks/use-typing-sound.js";
|
|
29
31
|
import { useComposerRefocus } from "./hooks/use-composer-refocus.js";
|
|
30
32
|
import { useVisitor } from "./hooks/use-visitor.js";
|
|
31
33
|
import { useConversations } from "./hooks/use-conversations.js";
|
|
@@ -40,4 +42,4 @@ import { useCreateConversation } from "./hooks/use-create-conversation.js";
|
|
|
40
42
|
import { useRealtimeSupport } from "./hooks/use-realtime-support.js";
|
|
41
43
|
import { IdentifySupportVisitor } from "./identify-visitor.js";
|
|
42
44
|
|
|
43
|
-
export { CoButton as Button, CONVERSATION_AUTO_SEEN_DELAY_MS, Header, IdentifySupportVisitor,
|
|
45
|
+
export { CoButton as Button, CONVERSATION_AUTO_SEEN_DELAY_MS, Header, IdentifySupportVisitor, primitives_exports as Primitives, RealtimeProvider, Support, SupportConfig, SupportContext, SupportProvider, SupportRealtimeProvider, Text, WebSocketProvider, applyConversationSeenEvent, applyConversationTypingEvent, clearTypingFromTimelineItem, clearTypingState, hydrateConversationSeen, setTypingState, upsertConversationSeen, useClient, useClientQuery, useComposerRefocus, useConversation, useConversationAutoSeen, useConversationHistoryPage, useConversationLifecycle, useConversationPage, useConversationPreview, useConversationSeen, useConversationTimeline, useConversationTimelineItems, useConversationTyping, useConversations, useCreateConversation, useDebouncedConversationSeen, useDefaultMessages, useGroupedMessages, useHomePage, useMessageComposer, useMultimodalInput, useNewMessageSound, useRealtime, useRealtimeConnection, useRealtimeSupport, useScrollMask, useSendMessage, useSoundEffect, useSupport, useSupportConfig, useSupportNavigation, useSupportStore, useSupportText, useTypingSound, useVisitor, useWebSocket, useWindowVisibilityFocus };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cossistant/react",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.22",
|
|
5
5
|
"private": false,
|
|
6
6
|
"author": "Cossistant team",
|
|
7
7
|
"description": "Headless React SDK for building AI-powered support/chat widgets. Hooks + primitives, WS-driven, TypeScript-first. Next.js-ready, Tailwind optional.",
|
|
@@ -88,8 +88,8 @@
|
|
|
88
88
|
"*.css"
|
|
89
89
|
],
|
|
90
90
|
"dependencies": {
|
|
91
|
-
"@cossistant/core": "0.0.
|
|
92
|
-
"@cossistant/types": "0.0.
|
|
91
|
+
"@cossistant/core": "0.0.22",
|
|
92
|
+
"@cossistant/types": "0.0.22",
|
|
93
93
|
"class-variance-authority": "^0.7.1",
|
|
94
94
|
"clsx": "^2.1.1",
|
|
95
95
|
"nanoid": "^5.1.5",
|
|
@@ -15,7 +15,7 @@ type AvatarImageProps = Omit<React$1.ImgHTMLAttributes<HTMLImageElement>, "src"
|
|
|
15
15
|
* Controlled `<img>` that syncs its loading status back to the avatar context
|
|
16
16
|
* so fallbacks know when to display.
|
|
17
17
|
*/
|
|
18
|
-
declare const AvatarImage: React$1.ForwardRefExoticComponent<Omit<React$1.ImgHTMLAttributes<HTMLImageElement>, "
|
|
18
|
+
declare const AvatarImage: React$1.ForwardRefExoticComponent<Omit<React$1.ImgHTMLAttributes<HTMLImageElement>, "src" | "alt"> & {
|
|
19
19
|
src: string;
|
|
20
20
|
alt?: string;
|
|
21
21
|
asChild?: boolean;
|
package/primitives/bubble.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { useRenderElement } from "../utils/use-render-element.js";
|
|
2
1
|
import { useTypingStore } from "../realtime/typing-store.js";
|
|
2
|
+
import { useRenderElement } from "../utils/use-render-element.js";
|
|
3
3
|
import { useSupportConfig } from "../support/store/support-store.js";
|
|
4
4
|
import { useSupport } from "../provider.js";
|
|
5
5
|
import * as React$1 from "react";
|
package/primitives/index.d.ts
CHANGED
|
@@ -7,9 +7,7 @@ import { SupportBubble } from "./bubble.js";
|
|
|
7
7
|
import { Button } from "./button.js";
|
|
8
8
|
import { ConversationTimeline, ConversationTimelineContainer, ConversationTimelineEmpty, ConversationTimelineLoading } from "./conversation-timeline.js";
|
|
9
9
|
import { FileInput, MultimodalInput, SupportInput } from "./multimodal-input.js";
|
|
10
|
-
import {
|
|
11
|
-
import { PageRegistryProvider, usePageRegistry, useRegisterPage } from "./page-registry.js";
|
|
12
|
-
import { Router, RouterProps } from "./router.js";
|
|
10
|
+
import { PageDefinition, Router, RouterProps } from "./router.js";
|
|
13
11
|
import { TimelineItem, TimelineItemContent, TimelineItemTimestamp } from "./timeline-item.js";
|
|
14
12
|
import { TimelineItemGroup, TimelineItemGroupAvatar, TimelineItemGroupContent, TimelineItemGroupHeader, TimelineItemGroupReadIndicator, TimelineItemGroupSeenIndicator } from "./timeline-item-group.js";
|
|
15
13
|
import { SupportWindow } from "./window.js";
|
|
@@ -17,8 +15,8 @@ import "./index.parts.js";
|
|
|
17
15
|
|
|
18
16
|
//#region src/primitives/index.d.ts
|
|
19
17
|
declare namespace index_d_exports {
|
|
20
|
-
export { Avatar, AvatarFallback, AvatarImage, SupportBubble as Bubble, Button, SupportConfig as Config, ConversationTimeline, ConversationTimelineContainer, ConversationTimelineEmpty, ConversationTimelineLoading, FileInput, SupportInput as Input, MultimodalInput,
|
|
18
|
+
export { Avatar, AvatarFallback, AvatarImage, SupportBubble as Bubble, Button, SupportConfig as Config, ConversationTimeline, ConversationTimelineContainer, ConversationTimelineEmpty, ConversationTimelineLoading, FileInput, SupportInput as Input, MultimodalInput, PageDefinition, Router, RouterProps, TimelineItem, TimelineItemContent, TimelineItemGroup, TimelineItemGroupAvatar, TimelineItemGroupContent, TimelineItemGroupHeader, TimelineItemGroupReadIndicator, TimelineItemGroupSeenIndicator, TimelineItemTimestamp, TypingIndicator, TypingIndicatorProps, TypingParticipant, TypingParticipantType, SupportWindow as Window };
|
|
21
19
|
}
|
|
22
20
|
//#endregion
|
|
23
|
-
export { Avatar, AvatarFallback, AvatarImage, SupportBubble as Bubble, Button, SupportConfig as Config, ConversationTimeline, ConversationTimelineContainer, ConversationTimelineEmpty, ConversationTimelineLoading, FileInput, SupportInput as Input, MultimodalInput,
|
|
21
|
+
export { Avatar, AvatarFallback, AvatarImage, SupportBubble as Bubble, Button, SupportConfig as Config, ConversationTimeline, ConversationTimelineContainer, ConversationTimelineEmpty, ConversationTimelineLoading, FileInput, SupportInput as Input, MultimodalInput, PageDefinition, Router, RouterProps, TimelineItem, TimelineItemContent, TimelineItemGroup, TimelineItemGroupAvatar, TimelineItemGroupContent, TimelineItemGroupHeader, TimelineItemGroupReadIndicator, TimelineItemGroupSeenIndicator, TimelineItemTimestamp, TypingIndicator, TypingIndicatorProps, TypingParticipant, TypingParticipantType, SupportWindow as Window, index_d_exports };
|
|
24
22
|
//# sourceMappingURL=index.d.ts.map
|
package/primitives/index.js
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import { __export } from "../_virtual/rolldown_runtime.js";
|
|
2
|
+
import { SupportConfig } from "../support-config.js";
|
|
2
3
|
import { Avatar } from "./avatar/avatar.js";
|
|
3
4
|
import { AvatarFallback } from "./avatar/fallback.js";
|
|
4
5
|
import { AvatarImage } from "./avatar/image.js";
|
|
5
6
|
import { TypingIndicator } from "../support/components/typing-indicator.js";
|
|
6
|
-
import { SupportConfig } from "../support-config.js";
|
|
7
7
|
import { SupportBubble } from "./bubble.js";
|
|
8
8
|
import { Button } from "./button.js";
|
|
9
9
|
import { ConversationTimeline, ConversationTimelineContainer, ConversationTimelineEmpty, ConversationTimelineLoading } from "./conversation-timeline.js";
|
|
10
10
|
import { FileInput, MultimodalInput, SupportInput } from "./multimodal-input.js";
|
|
11
|
-
import { PageRegistryProvider, usePageRegistry, useRegisterPage } from "./page-registry.js";
|
|
12
|
-
import { Page } from "./page.js";
|
|
13
11
|
import { Router } from "./router.js";
|
|
14
12
|
import { TimelineItem, TimelineItemContent, TimelineItemTimestamp } from "./timeline-item.js";
|
|
15
13
|
import { TimelineItemGroup, TimelineItemGroupAvatar, TimelineItemGroupContent, TimelineItemGroupHeader, TimelineItemGroupReadIndicator, TimelineItemGroupSeenIndicator } from "./timeline-item-group.js";
|
|
@@ -30,8 +28,6 @@ var primitives_exports = /* @__PURE__ */ __export({
|
|
|
30
28
|
FileInput: () => FileInput,
|
|
31
29
|
Input: () => SupportInput,
|
|
32
30
|
MultimodalInput: () => MultimodalInput,
|
|
33
|
-
Page: () => Page,
|
|
34
|
-
PageRegistryProvider: () => PageRegistryProvider,
|
|
35
31
|
Router: () => Router,
|
|
36
32
|
TimelineItem: () => TimelineItem,
|
|
37
33
|
TimelineItemContent: () => TimelineItemContent,
|
|
@@ -43,11 +39,9 @@ var primitives_exports = /* @__PURE__ */ __export({
|
|
|
43
39
|
TimelineItemGroupSeenIndicator: () => TimelineItemGroupSeenIndicator,
|
|
44
40
|
TimelineItemTimestamp: () => TimelineItemTimestamp,
|
|
45
41
|
TypingIndicator: () => TypingIndicator,
|
|
46
|
-
Window: () => SupportWindow
|
|
47
|
-
usePageRegistry: () => usePageRegistry,
|
|
48
|
-
useRegisterPage: () => useRegisterPage
|
|
42
|
+
Window: () => SupportWindow
|
|
49
43
|
});
|
|
50
44
|
|
|
51
45
|
//#endregion
|
|
52
|
-
export { Avatar, AvatarFallback, AvatarImage, SupportBubble as Bubble, Button, SupportConfig as Config, ConversationTimeline, ConversationTimelineContainer, ConversationTimelineEmpty, ConversationTimelineLoading, FileInput, SupportInput as Input, MultimodalInput,
|
|
46
|
+
export { Avatar, AvatarFallback, AvatarImage, SupportBubble as Bubble, Button, SupportConfig as Config, ConversationTimeline, ConversationTimelineContainer, ConversationTimelineEmpty, ConversationTimelineLoading, FileInput, SupportInput as Input, MultimodalInput, Router, TimelineItem, TimelineItemContent, TimelineItemGroup, TimelineItemGroupAvatar, TimelineItemGroupContent, TimelineItemGroupHeader, TimelineItemGroupReadIndicator, TimelineItemGroupSeenIndicator, TimelineItemTimestamp, TypingIndicator, SupportWindow as Window, primitives_exports };
|
|
53
47
|
//# sourceMappingURL=index.js.map
|
|
@@ -8,10 +8,8 @@ import { SupportBubble } from "./bubble.js";
|
|
|
8
8
|
import { Button } from "./button.js";
|
|
9
9
|
import { ConversationTimeline, ConversationTimelineContainer, ConversationTimelineEmpty, ConversationTimelineLoading } from "./conversation-timeline.js";
|
|
10
10
|
import { FileInput, MultimodalInput, SupportInput } from "./multimodal-input.js";
|
|
11
|
-
import {
|
|
12
|
-
import { PageRegistryProvider, usePageRegistry, useRegisterPage } from "./page-registry.js";
|
|
13
|
-
import { Router, RouterProps } from "./router.js";
|
|
11
|
+
import { PageDefinition, Router, RouterProps } from "./router.js";
|
|
14
12
|
import { TimelineItem, TimelineItemContent, TimelineItemTimestamp } from "./timeline-item.js";
|
|
15
13
|
import { TimelineItemGroup, TimelineItemGroupAvatar, TimelineItemGroupContent, TimelineItemGroupHeader, TimelineItemGroupReadIndicator, TimelineItemGroupSeenIndicator } from "./timeline-item-group.js";
|
|
16
14
|
import { SupportWindow } from "./window.js";
|
|
17
|
-
export { Avatar, AvatarFallback, AvatarImage, SupportBubble as Bubble, Button, SupportConfig as Config, ConversationTimeline, ConversationTimelineContainer, ConversationTimelineEmpty, ConversationTimelineLoading, FileInput, SupportInput as Input, MultimodalInput,
|
|
15
|
+
export { Avatar, AvatarFallback, AvatarImage, SupportBubble as Bubble, Button, SupportConfig as Config, ConversationTimeline, ConversationTimelineContainer, ConversationTimelineEmpty, ConversationTimelineLoading, FileInput, SupportInput as Input, MultimodalInput, type PageDefinition, Router, type RouterProps, TimelineItem, TimelineItemContent, TimelineItemGroup, TimelineItemGroupAvatar, TimelineItemGroupContent, TimelineItemGroupHeader, TimelineItemGroupReadIndicator, TimelineItemGroupSeenIndicator, TimelineItemTimestamp, TypingIndicator, type TypingIndicatorProps, type TypingParticipant, type TypingParticipantType, SupportWindow as Window };
|