@constructor-io/constructorio-ui-asa 1.7.1 → 1.8.0
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 +21 -17
- package/dist/constructorio-ui-asa-bundled.js +18 -18
- package/lib/cjs/components/Chat/AiMessage.js +2 -3
- package/lib/cjs/components/Chat/Chat.js +38 -10
- package/lib/cjs/components/CioAsa/CioAsa.js +6 -2
- package/lib/cjs/components/CioAsaProvider/CioAsaProvider.js +44 -3
- package/lib/cjs/components/ResultsBlock/ResultsBlock.js +0 -1
- package/lib/cjs/components/ResultsBlock/ResultsGroup.js +5 -5
- package/lib/cjs/hooks/agentStream.js +113 -0
- package/lib/cjs/hooks/asaStreamHandlers.js +2 -1
- package/lib/cjs/hooks/persistence/backgroundTurns.js +121 -0
- package/lib/cjs/hooks/persistence/useChatPersistence.js +485 -0
- package/lib/cjs/hooks/persistence/useForeignStream.js +34 -0
- package/lib/cjs/hooks/persistence/useThreadList.js +45 -0
- package/lib/cjs/hooks/persistence/useWriteQueue.js +17 -0
- package/lib/cjs/hooks/useAsaResults.js +93 -160
- package/lib/cjs/hooks/useAsaTracking.js +3 -3
- package/lib/cjs/hooks/useIsMounted.js +15 -0
- package/lib/cjs/hooks/useLatest.js +10 -0
- package/lib/cjs/hooks/useViewportTracking.js +4 -3
- package/lib/cjs/index.js +3 -1
- package/lib/cjs/utils/chatRecordValidation.js +54 -0
- package/lib/cjs/utils/chatSession.js +98 -0
- package/lib/cjs/utils/chatThreads.js +248 -0
- package/lib/cjs/utils/clientOptions.js +11 -0
- package/lib/cjs/utils/localStoragePersistence.js +391 -0
- package/lib/cjs/utils/translate.js +1 -0
- package/lib/cjs/version.js +1 -1
- package/lib/mjs/components/Chat/AiMessage.js +2 -3
- package/lib/mjs/components/Chat/Chat.js +36 -10
- package/lib/mjs/components/CioAsa/CioAsa.js +6 -2
- package/lib/mjs/components/CioAsaProvider/CioAsaProvider.js +44 -4
- package/lib/mjs/components/ResultsBlock/ResultsBlock.js +0 -1
- package/lib/mjs/components/ResultsBlock/ResultsGroup.js +2 -1
- package/lib/mjs/hooks/agentStream.js +106 -0
- package/lib/mjs/hooks/asaStreamHandlers.js +1 -1
- package/lib/mjs/hooks/persistence/backgroundTurns.js +120 -0
- package/lib/mjs/hooks/persistence/useChatPersistence.js +474 -0
- package/lib/mjs/hooks/persistence/useForeignStream.js +31 -0
- package/lib/mjs/hooks/persistence/useThreadList.js +41 -0
- package/lib/mjs/hooks/persistence/useWriteQueue.js +14 -0
- package/lib/mjs/hooks/useAsaResults.js +87 -157
- package/lib/mjs/hooks/useAsaTracking.js +2 -1
- package/lib/mjs/hooks/useIsMounted.js +12 -0
- package/lib/mjs/hooks/useLatest.js +7 -0
- package/lib/mjs/hooks/useViewportTracking.js +3 -3
- package/lib/mjs/index.js +1 -0
- package/lib/mjs/utils/chatRecordValidation.js +51 -0
- package/lib/mjs/utils/chatSession.js +86 -0
- package/lib/mjs/utils/chatThreads.js +219 -0
- package/lib/mjs/utils/clientOptions.js +7 -0
- package/lib/mjs/utils/localStoragePersistence.js +373 -0
- package/lib/mjs/utils/translate.js +1 -0
- package/lib/mjs/version.js +1 -1
- package/lib/types/components/Chat/Chat.d.ts +8 -2
- package/lib/types/components/CioAsa/CioAsa.d.ts +1 -1
- package/lib/types/components/ResultsBlock/ResultsBlock.d.ts +0 -1
- package/lib/types/hooks/agentStream.d.ts +48 -0
- package/lib/types/hooks/asaStreamHandlers.d.ts +1 -0
- package/lib/types/hooks/persistence/backgroundTurns.d.ts +43 -0
- package/lib/types/hooks/persistence/useChatPersistence.d.ts +34 -0
- package/lib/types/hooks/persistence/useForeignStream.d.ts +9 -0
- package/lib/types/hooks/persistence/useThreadList.d.ts +9 -0
- package/lib/types/hooks/persistence/useWriteQueue.d.ts +5 -0
- package/lib/types/hooks/useAsaTracking.d.ts +2 -0
- package/lib/types/hooks/useIsMounted.d.ts +3 -0
- package/lib/types/hooks/useLatest.d.ts +3 -0
- package/lib/types/index.d.ts +2 -1
- package/lib/types/types.d.ts +134 -2
- package/lib/types/utils/chatRecordValidation.d.ts +2 -0
- package/lib/types/utils/chatSession.d.ts +45 -0
- package/lib/types/utils/chatThreads.d.ts +63 -0
- package/lib/types/utils/clientOptions.d.ts +5 -0
- package/lib/types/utils/localStoragePersistence.d.ts +26 -0
- package/lib/types/version.d.ts +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { Dispatch, SetStateAction } from 'react';
|
|
2
|
+
import { ChatMessage } from '../types';
|
|
3
|
+
type SetMessages = Dispatch<SetStateAction<ChatMessage[]>>;
|
|
4
|
+
export interface AgentStreamCallbacks {
|
|
5
|
+
/** The `start` event arrived, possibly carrying the server thread id. */
|
|
6
|
+
onStart: (threadId?: string) => void;
|
|
7
|
+
/** Fires once, on the first event with content, for load-started tracking. */
|
|
8
|
+
onLoadStart: (intentResultId?: string) => void;
|
|
9
|
+
/** The stream ended normally (not on a server error, a failure, or a cancel). */
|
|
10
|
+
onFinish: (result: {
|
|
11
|
+
searchResultCount: number;
|
|
12
|
+
intentResultId?: string;
|
|
13
|
+
}) => void;
|
|
14
|
+
}
|
|
15
|
+
export interface AgentStreamHandle {
|
|
16
|
+
/** The assistant message this stream writes into, so a cancel can settle the right one. */
|
|
17
|
+
assistantId: string;
|
|
18
|
+
/** Stop reading; the pending `done` settles without touching the assistant message. */
|
|
19
|
+
cancel: () => void;
|
|
20
|
+
done: Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
/** Where a running answer goes: the chat showing its conversation, or the background. */
|
|
23
|
+
export interface TurnOwner {
|
|
24
|
+
setMessages: SetMessages;
|
|
25
|
+
onStart: (threadId?: string) => void;
|
|
26
|
+
/** The stream ended, failed or was cancelled. */
|
|
27
|
+
onDone: () => void;
|
|
28
|
+
}
|
|
29
|
+
/** An answer being read; its owner can change mid-stream, so leaving the chat does not stop it. */
|
|
30
|
+
export interface LiveTurn extends AgentStreamHandle {
|
|
31
|
+
owner: TurnOwner;
|
|
32
|
+
/** The assistant message as streamed so far, whoever shows it. */
|
|
33
|
+
assistant: ChatMessage;
|
|
34
|
+
/** From the `start` event. */
|
|
35
|
+
serverThreadId?: string;
|
|
36
|
+
}
|
|
37
|
+
/** Drains an agent stream into the assistant message identified by `assistantId`. */
|
|
38
|
+
export declare function readAgentStream(stream: ReadableStream, assistantId: string, setMessages: SetMessages, callbacks: AgentStreamCallbacks): AgentStreamHandle;
|
|
39
|
+
export interface TurnCallbacks {
|
|
40
|
+
onLoadStart: AgentStreamCallbacks['onLoadStart'];
|
|
41
|
+
/** As `AgentStreamCallbacks.onFinish`, with the server thread the answer belongs to. */
|
|
42
|
+
onFinish: (result: Parameters<AgentStreamCallbacks['onFinish']>[0] & {
|
|
43
|
+
threadId?: string;
|
|
44
|
+
}) => void;
|
|
45
|
+
}
|
|
46
|
+
/** Streams into `assistant`, reporting to the owner `createOwner` returns for the new turn. */
|
|
47
|
+
export declare function startTurn(stream: ReadableStream, assistant: ChatMessage, createOwner: (turn: LiveTurn) => TurnOwner, callbacks: TurnCallbacks): LiveTurn;
|
|
48
|
+
export {};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Dispatch, SetStateAction } from 'react';
|
|
2
2
|
import { ResultGroupMeta, ChatMessage } from '../types';
|
|
3
3
|
export type MessageUpdater = Dispatch<SetStateAction<ChatMessage[]>>;
|
|
4
|
+
export declare function updateMessageById(setMessages: MessageUpdater, id: string, patch: (msg: ChatMessage) => ChatMessage): void;
|
|
4
5
|
export declare function handleSearchResult(data: any, pendingGroup: ResultGroupMeta | null, assistantId: string, setMessages: MessageUpdater): ResultGroupMeta | null;
|
|
5
6
|
export declare function handleMessage(data: any, assistantId: string, setMessages: MessageUpdater): void;
|
|
6
7
|
export declare function handleFollowUpRefinement(data: any, assistantId: string, setMessages: MessageUpdater): void;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { ChatMessage, ChatPersistence } from '../../types';
|
|
2
|
+
import type { LiveTurn, TurnOwner } from '../agentStream';
|
|
3
|
+
import { ChatSession } from '../../utils/chatSession';
|
|
4
|
+
/** An answer that went on after its conversation left the screen, until it is stored or shown again. */
|
|
5
|
+
export declare class BackgroundTurn {
|
|
6
|
+
readonly turn: LiveTurn;
|
|
7
|
+
store: ChatPersistence;
|
|
8
|
+
/** The conversation's own session, forked from the chat that sent the message. */
|
|
9
|
+
readonly session: ChatSession;
|
|
10
|
+
messages: ChatMessage[];
|
|
11
|
+
streaming: boolean;
|
|
12
|
+
/** Whether the latest snapshot is known to be stored. */
|
|
13
|
+
saved: boolean;
|
|
14
|
+
/** Every id the conversation was stored under, so a list read before a re-key still finds it. */
|
|
15
|
+
readonly ids: Set<string>;
|
|
16
|
+
/** Its writes, in order; a chat that takes the turn back writes after them. */
|
|
17
|
+
writes: Promise<void>;
|
|
18
|
+
constructor(turn: LiveTurn, params: {
|
|
19
|
+
store: ChatPersistence;
|
|
20
|
+
session: ChatSession;
|
|
21
|
+
messages: ChatMessage[];
|
|
22
|
+
after: Promise<void>;
|
|
23
|
+
});
|
|
24
|
+
save(): Promise<void>;
|
|
25
|
+
/** Later writes go into `store`. */
|
|
26
|
+
moveTo(store: ChatPersistence): void;
|
|
27
|
+
/** Stores the conversation settled, without awaiting: the page is going away. */
|
|
28
|
+
flush(): void;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Keeps `turn` streaming with no chat showing it. It is saved into `store` at once, on `start`
|
|
32
|
+
* and when done, each write queued behind `after`, and is dropped from here once the last one landed.
|
|
33
|
+
*/
|
|
34
|
+
export declare function continueInBackground(turn: LiveTurn, params: ConstructorParameters<typeof BackgroundTurn>[1]): void;
|
|
35
|
+
/**
|
|
36
|
+
* Takes back the background turn stored in `store` under `threadId` for a chat to show, handing an
|
|
37
|
+
* unfinished one to `createOwner`; `null` if there is none.
|
|
38
|
+
*/
|
|
39
|
+
export declare function resumeBackgroundTurn(store: ChatPersistence, threadId: string, createOwner: (turn: LiveTurn) => TurnOwner): BackgroundTurn | null;
|
|
40
|
+
/** Points the background turns of `from` at `to`; resolves once their writes into `from` settled. */
|
|
41
|
+
export declare function moveBackgroundTurns(from: ChatPersistence, to: ChatPersistence): Promise<void>;
|
|
42
|
+
/** Called whenever a background turn is stored or finishes. */
|
|
43
|
+
export declare function subscribeBackgroundTurns(listener: () => void): () => void;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Dispatch, MutableRefObject, SetStateAction } from 'react';
|
|
2
|
+
import { ChatMessage, ChatPersistence, PersistenceScope } from '../../types';
|
|
3
|
+
import type { LiveTurn, TurnOwner } from '../agentStream';
|
|
4
|
+
import { ChatSession } from '../../utils/chatSession';
|
|
5
|
+
interface Params {
|
|
6
|
+
store: ChatPersistence | undefined;
|
|
7
|
+
/** Whose store it is; a switch from `'guest'` to `'user'` carries the conversation over. */
|
|
8
|
+
scope?: PersistenceScope;
|
|
9
|
+
/** Api key and domain of `store`; a login into another index does not carry anything over. */
|
|
10
|
+
index?: string;
|
|
11
|
+
session: ChatSession;
|
|
12
|
+
initialThreadId?: string;
|
|
13
|
+
messages: ChatMessage[];
|
|
14
|
+
setMessages: Dispatch<SetStateAction<ChatMessage[]>>;
|
|
15
|
+
isStreaming: boolean;
|
|
16
|
+
setIsStreaming: (value: boolean) => void;
|
|
17
|
+
/** This tab's answer streaming into the conversation on screen, if any. */
|
|
18
|
+
turnRef: MutableRefObject<LiveTurn | null>;
|
|
19
|
+
/** Stop the answer currently streaming in this tab, if any. */
|
|
20
|
+
cancelStream: () => void;
|
|
21
|
+
}
|
|
22
|
+
/** Restores, saves and syncs the conversation with `store`; inert when `store` is undefined. */
|
|
23
|
+
export default function useChatPersistence(params: Params): {
|
|
24
|
+
isHydrating: boolean;
|
|
25
|
+
threads: import("../../types").ThreadSummary[];
|
|
26
|
+
activeThreadId: string | null;
|
|
27
|
+
foreignInFlight: boolean;
|
|
28
|
+
beginTurn: () => void;
|
|
29
|
+
screenOwner: (turn: LiveTurn) => TurnOwner;
|
|
30
|
+
clearHistory: () => void;
|
|
31
|
+
newThread: () => void;
|
|
32
|
+
switchThread: (threadId: string) => Promise<void>;
|
|
33
|
+
};
|
|
34
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ChatSession } from '../../utils/chatSession';
|
|
2
|
+
/** An answer streaming in another tab: blocks sending and settles after the grace period. */
|
|
3
|
+
export default function useForeignStream(session: ChatSession): {
|
|
4
|
+
foreignInFlight: boolean;
|
|
5
|
+
setForeignInFlight: import("react").Dispatch<import("react").SetStateAction<boolean>>;
|
|
6
|
+
watch: (remainingMs: number, onExpire: () => void) => void;
|
|
7
|
+
stop: () => void;
|
|
8
|
+
clearTimer: () => void;
|
|
9
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { MutableRefObject } from 'react';
|
|
2
|
+
import { ChatPersistence, ThreadSummary } from '../../types';
|
|
3
|
+
/** The stored thread list, re-read on demand; only the latest read may land. */
|
|
4
|
+
export default function useThreadList(storeRef: MutableRefObject<ChatPersistence | undefined>): {
|
|
5
|
+
threads: ThreadSummary[];
|
|
6
|
+
setThreads: import("react").Dispatch<import("react").SetStateAction<ThreadSummary[]>>;
|
|
7
|
+
refreshThreads: () => Promise<void>;
|
|
8
|
+
invalidate: () => void;
|
|
9
|
+
};
|
|
@@ -14,6 +14,8 @@ export interface TrackResultLoadFinishedArgs {
|
|
|
14
14
|
intent: string;
|
|
15
15
|
searchResultCount: number;
|
|
16
16
|
intentResultId?: string;
|
|
17
|
+
/** Thread the answer belongs to, when it can differ from the hook's `threadId`. */
|
|
18
|
+
threadId?: string;
|
|
17
19
|
}
|
|
18
20
|
export interface TrackResultClickArgs {
|
|
19
21
|
intent: string;
|
package/lib/types/index.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export { default as useAsaTracking } from './hooks/useAsaTracking';
|
|
|
12
12
|
export { default as useCioAsaContext } from './hooks/useCioAsaContext';
|
|
13
13
|
export type { UseAsaTrackingProps, UseAsaTrackingReturn } from './hooks/useAsaTracking';
|
|
14
14
|
export { normalizeItemToProduct } from './utils/productNormalizer';
|
|
15
|
+
export { clearPersistedConversations } from './utils/localStoragePersistence';
|
|
15
16
|
export type { Product, NormalizeOptions } from './utils/productNormalizer';
|
|
16
17
|
export { ConstructorIOClient } from './types';
|
|
17
|
-
export type { Nullable, CioAsaProviderProps, AsaContextValue, CioClientOptions, RequestConfigs, Formatters, UrlHelpers, QueryParamEncodingOptions, DefaultQueryStringMap, Translations, ChatMessage, ChatMessageStatus, FollowUpRefinement, ResultGroup, ResultGroupMeta, SearchResultEventRequest, UseAsaResultsOptions, UseChatReturn, AsaCallbacks, AssistantSubmitSource, AssistantTrackedItem, ChatComponentOverrides, WelcomeScreenOverrides, ChatInputOverrides, AiMessageOverrides, ResultsBlockOverrides, ComponentOverrideProps, IncludeComponentOverrides, ChatHeaderRenderProps, ChatInputRenderProps, WelcomeScreenTitleRenderProps, WelcomeScreenInputRenderProps, SuggestedQuestionsRenderProps, UserMessageRenderProps, AiMessageLoaderRenderProps, AiMessageTextRenderProps, FollowUpRefinementRenderProps, ResultsGroupTitleRenderProps, ResultsViewMoreRenderProps, } from './types';
|
|
18
|
+
export type { Nullable, CioAsaProviderProps, PersistConversationOptions, AsaContextValue, CioClientOptions, RequestConfigs, Formatters, UrlHelpers, QueryParamEncodingOptions, DefaultQueryStringMap, Translations, ChatMessage, ChatMessageStatus, FollowUpRefinement, ResultGroup, ResultGroupMeta, SearchResultEventRequest, UseAsaResultsOptions, UseChatReturn, ClearPersistedConversationsOptions, ThreadSummary, AsaCallbacks, AssistantSubmitSource, AssistantTrackedItem, ChatComponentOverrides, WelcomeScreenOverrides, ChatInputOverrides, AiMessageOverrides, ResultsBlockOverrides, ComponentOverrideProps, IncludeComponentOverrides, ChatHeaderRenderProps, ChatInputRenderProps, WelcomeScreenTitleRenderProps, WelcomeScreenInputRenderProps, SuggestedQuestionsRenderProps, UserMessageRenderProps, AiMessageLoaderRenderProps, AiMessageTextRenderProps, FollowUpRefinementRenderProps, ResultsGroupTitleRenderProps, ResultsViewMoreRenderProps, } from './types';
|
package/lib/types/types.d.ts
CHANGED
|
@@ -21,6 +21,12 @@ export interface AsaContextValue {
|
|
|
21
21
|
urlHelpers: UrlHelpers;
|
|
22
22
|
callbacks?: AsaCallbacks;
|
|
23
23
|
section?: string;
|
|
24
|
+
/** Resolved chat persistence store, or `undefined` when persistence is off. */
|
|
25
|
+
persistence?: ChatPersistence;
|
|
26
|
+
/** Whose store `persistence` is: the guest's (per tab) or a signed-in shopper's (per browser). */
|
|
27
|
+
persistenceScope?: PersistenceScope;
|
|
28
|
+
/** Api key and domain `persistence` is scoped to; a login carries a conversation over only within one. */
|
|
29
|
+
persistenceIndex?: string;
|
|
24
30
|
}
|
|
25
31
|
export interface RequestConfigs extends IAgentParameters {
|
|
26
32
|
intent?: string;
|
|
@@ -35,7 +41,7 @@ export interface UrlHelpers {
|
|
|
35
41
|
getUrlFromState: (state: RequestConfigs, options: QueryParamEncodingOptions) => string;
|
|
36
42
|
defaultQueryStringMap: Readonly<DefaultQueryStringMap>;
|
|
37
43
|
}
|
|
38
|
-
|
|
44
|
+
interface CioAsaProviderBaseProps extends Omit<Partial<AsaContextValue>, 'setCioClientOptions' | 'cioClientOptions' | 'persistence' | 'persistenceScope' | 'persistenceIndex'> {
|
|
39
45
|
apiKey?: string;
|
|
40
46
|
/**
|
|
41
47
|
* A/B test cells to attach to tracking events, as `{ [testName]: cellName }`. Each entry is
|
|
@@ -49,6 +55,32 @@ export interface CioAsaProviderProps extends Omit<Partial<AsaContextValue>, 'set
|
|
|
49
55
|
*/
|
|
50
56
|
testCells?: Record<string, string>;
|
|
51
57
|
}
|
|
58
|
+
/** How conversations are kept in the browser. */
|
|
59
|
+
export interface PersistConversationOptions {
|
|
60
|
+
/**
|
|
61
|
+
* Keep conversations across page loads: a signed-in shopper's in `localStorage` for 7 days,
|
|
62
|
+
* keyed by api key + domain + user id; a guest's in `sessionStorage`, ending with the tab.
|
|
63
|
+
*/
|
|
64
|
+
enabled: boolean;
|
|
65
|
+
}
|
|
66
|
+
interface PersistentProviderProps {
|
|
67
|
+
persistConversation: PersistConversationOptions;
|
|
68
|
+
/**
|
|
69
|
+
* Whose history it is: the signed-in shopper's stable, non-personal id, or `null` for a guest.
|
|
70
|
+
* Change it on login and logout. With `apiKey` it is also set on the client.
|
|
71
|
+
*/
|
|
72
|
+
userId: string | null;
|
|
73
|
+
}
|
|
74
|
+
interface EphemeralProviderProps {
|
|
75
|
+
/** Off, or omitted: the conversation lives in memory and is gone on the next page load. */
|
|
76
|
+
persistConversation?: PersistConversationOptions & {
|
|
77
|
+
enabled: false;
|
|
78
|
+
};
|
|
79
|
+
/** The shopper's id; with `apiKey` it is set on the client. `null` for a guest. */
|
|
80
|
+
userId?: string | null;
|
|
81
|
+
}
|
|
82
|
+
export type CioAsaPersistenceProps = PersistentProviderProps | EphemeralProviderProps;
|
|
83
|
+
export type CioAsaProviderProps = CioAsaProviderBaseProps & CioAsaPersistenceProps;
|
|
52
84
|
export interface UseCioClientProps {
|
|
53
85
|
apiKey?: string;
|
|
54
86
|
cioClient?: Nullable<ConstructorIOClient>;
|
|
@@ -84,6 +116,8 @@ export interface ChatMessage {
|
|
|
84
116
|
threadId?: string;
|
|
85
117
|
/** Narrowing question the agent asked at the end of this turn, from a `follow_up_refinement` event. */
|
|
86
118
|
refinement?: FollowUpRefinement;
|
|
119
|
+
/** The answer never arrived: the page was left or the shopper logged out mid-answer. Unset on a failure. */
|
|
120
|
+
interrupted?: boolean;
|
|
87
121
|
}
|
|
88
122
|
/** A narrowing question with selectable options, emitted by the agent as a `follow_up_refinement` event. */
|
|
89
123
|
export interface FollowUpRefinement {
|
|
@@ -138,14 +172,20 @@ export interface ResultGroupMeta {
|
|
|
138
172
|
}
|
|
139
173
|
export interface ResultGroup {
|
|
140
174
|
group: ResultGroupMeta;
|
|
175
|
+
/** Raw items as the agent sent them; empty on a conversation restored from storage. */
|
|
141
176
|
searchResults: Record<string, unknown>[];
|
|
177
|
+
/** The cards as `normalizeItem` made them; set on a conversation restored from storage. */
|
|
178
|
+
products?: Product[];
|
|
142
179
|
searchResultId?: string;
|
|
143
180
|
intentResultId?: string;
|
|
144
181
|
}
|
|
145
182
|
export interface UseChatReturn {
|
|
146
183
|
messages: ChatMessage[];
|
|
147
184
|
sendMessage: (text: string, source?: AssistantSubmitSource) => void;
|
|
185
|
+
/** True while an answer is streaming, here or, with persistence on, in another tab on the same thread. */
|
|
148
186
|
isStreaming: boolean;
|
|
187
|
+
/** True while this tab's own answer is streaming, the one `abort` can stop. */
|
|
188
|
+
canAbort: boolean;
|
|
149
189
|
/**
|
|
150
190
|
* Cancel the in-flight request, keeping the conversation. The partial reply is settled
|
|
151
191
|
* as `done` and the thread id is kept, so the next message continues the same
|
|
@@ -153,10 +193,101 @@ export interface UseChatReturn {
|
|
|
153
193
|
*/
|
|
154
194
|
abort: () => void;
|
|
155
195
|
clearHistory: () => void;
|
|
196
|
+
/** True while a persisted conversation is being loaded. Always `false` when persistence is off. */
|
|
197
|
+
isHydrating: boolean;
|
|
198
|
+
/** Stored conversations, most recent first. Empty when persistence is off. */
|
|
199
|
+
threads: ThreadSummary[];
|
|
200
|
+
/** Id of the stored conversation currently shown, or `null` for an unsaved one. */
|
|
201
|
+
activeThreadId: string | null;
|
|
202
|
+
/** Start an empty conversation, keeping the current one in storage when persistence is on. */
|
|
203
|
+
newThread: () => void;
|
|
204
|
+
/** Load a stored conversation and continue it. No-op when persistence is off. */
|
|
205
|
+
switchThread: (threadId: string) => Promise<void>;
|
|
156
206
|
}
|
|
157
207
|
export interface UseAsaResultsOptions {
|
|
158
|
-
/**
|
|
208
|
+
/**
|
|
209
|
+
* Seed the thread id to resume a prior conversation. Read once on mount. With persistence
|
|
210
|
+
* enabled, the matching stored transcript is restored too when one exists.
|
|
211
|
+
*/
|
|
159
212
|
initialThreadId?: string;
|
|
213
|
+
/** Makes the card stored for each product: the function `Chat` renders with. Defaults to `normalizeItemToProduct`. */
|
|
214
|
+
normalizeItem?: (item: any) => Product;
|
|
215
|
+
}
|
|
216
|
+
/** Where conversations live: `'session'` ends with the tab, `'local'` survives it. */
|
|
217
|
+
export type StorageArea = 'local' | 'session';
|
|
218
|
+
/** Whose conversations a store holds. */
|
|
219
|
+
export type PersistenceScope = 'guest' | 'user';
|
|
220
|
+
/** A stored conversation. `threadId` is the server thread id, or a `local-` id for non-conversational domains. */
|
|
221
|
+
export interface PersistedChat {
|
|
222
|
+
version: 1;
|
|
223
|
+
threadId: string;
|
|
224
|
+
messages: ChatMessage[];
|
|
225
|
+
createdAt: number;
|
|
226
|
+
updatedAt: number;
|
|
227
|
+
/**
|
|
228
|
+
* Id of the browser tab that last wrote the record. Lets a reloaded tab tell its own
|
|
229
|
+
* interrupted answer (settled at once) from one still streaming in another tab.
|
|
230
|
+
*/
|
|
231
|
+
owner?: string;
|
|
232
|
+
}
|
|
233
|
+
export interface ThreadSummary {
|
|
234
|
+
threadId: string;
|
|
235
|
+
/** First user message, truncated. */
|
|
236
|
+
title: string;
|
|
237
|
+
createdAt: number;
|
|
238
|
+
updatedAt: number;
|
|
239
|
+
/** True while the latest answer is still streaming (possibly in another tab). */
|
|
240
|
+
inFlight: boolean;
|
|
241
|
+
}
|
|
242
|
+
/** Storage contract the chat hook talks to; implemented by `createLocalStoragePersistence`. */
|
|
243
|
+
export interface ChatPersistence {
|
|
244
|
+
/** Most recently updated first. */
|
|
245
|
+
listThreads(): Promise<ThreadSummary[]>;
|
|
246
|
+
getThread(threadId: string): Promise<PersistedChat | null>;
|
|
247
|
+
saveThread(chat: PersistedChat): Promise<void>;
|
|
248
|
+
deleteThread(threadId: string): Promise<void>;
|
|
249
|
+
/**
|
|
250
|
+
* Optional. Whether `threadId` was explicitly deleted (or the whole store was cleared), as
|
|
251
|
+
* opposed to expired or evicted. Without it, a thread that has gone missing is treated as deleted.
|
|
252
|
+
*/
|
|
253
|
+
isThreadDeleted?(threadId: string): Promise<boolean>;
|
|
254
|
+
/**
|
|
255
|
+
* Optional. Write `chat` synchronously, retiring `staleIds` in the same step, for use while the
|
|
256
|
+
* page is unloading: an async write cannot finish there. Falls back to `saveThread` when absent.
|
|
257
|
+
*/
|
|
258
|
+
saveThreadSync?(chat: PersistedChat, staleIds?: string[]): void;
|
|
259
|
+
/**
|
|
260
|
+
* Optional. Notify when stored threads change outside this hook instance (another tab).
|
|
261
|
+
* Returns an unsubscribe function.
|
|
262
|
+
*/
|
|
263
|
+
subscribe?(listener: () => void): () => void;
|
|
264
|
+
}
|
|
265
|
+
/** Which stored history `clearPersistedConversations` deletes; mirror what the provider was given. */
|
|
266
|
+
export interface ClearPersistedConversationsOptions {
|
|
267
|
+
/** The provider's `apiKey`, or the api key of the `cioClient` given to it. */
|
|
268
|
+
apiKey: string;
|
|
269
|
+
/** The `domain` from `staticRequestConfigs`. Default `'chatbot'`, the provider's default. */
|
|
270
|
+
domain?: string;
|
|
271
|
+
/** The shopper whose history to delete. Omit or pass `null` for the guest history. */
|
|
272
|
+
userId?: string | null;
|
|
273
|
+
/** Storage to clear instead of the default: `localStorage` for a shopper, `sessionStorage` for the guest. */
|
|
274
|
+
storage?: Storage;
|
|
275
|
+
}
|
|
276
|
+
export interface LocalStoragePersistenceOptions {
|
|
277
|
+
/** Storage key prefix. Default `'cio-asa:chat'`. */
|
|
278
|
+
key?: string;
|
|
279
|
+
/** Appended to the key to isolate conversations, e.g. per api key + domain. */
|
|
280
|
+
namespace?: string;
|
|
281
|
+
/** Threads idle longer than this are dropped on read. Default 7 days, matching server retention. */
|
|
282
|
+
ttlMs?: number;
|
|
283
|
+
/** Cap on user/assistant turns kept per thread. Unlimited by default. */
|
|
284
|
+
maxTurns?: number;
|
|
285
|
+
/** Cap on threads kept. Unlimited by default. */
|
|
286
|
+
maxThreads?: number;
|
|
287
|
+
/** Which browser storage to use. Default `'local'`. */
|
|
288
|
+
storageArea?: StorageArea;
|
|
289
|
+
/** Storage to use instead of the browser's; takes precedence over `storageArea`. */
|
|
290
|
+
storage?: Storage;
|
|
160
291
|
}
|
|
161
292
|
/** How an intent was submitted: typed input, a welcome-screen suggestion chip, or a refinement chip. */
|
|
162
293
|
export type AssistantSubmitSource = 'input' | 'suggestion' | 'refinement';
|
|
@@ -240,6 +371,7 @@ export type Translations = {
|
|
|
240
371
|
'CioAsa.results.saleBadge'?: string;
|
|
241
372
|
'CioAsa.refinement.ariaLabel'?: string;
|
|
242
373
|
'CioAsa.error.message'?: string;
|
|
374
|
+
'CioAsa.error.interrupted'?: string;
|
|
243
375
|
};
|
|
244
376
|
export interface ChatHeaderRenderProps {
|
|
245
377
|
title: string;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { ChatMessage, PersistedChat } from '../types';
|
|
2
|
+
import { ItemNormalizer } from './chatThreads';
|
|
3
|
+
/** Conversation state that async work must always see current; one object so a reset is one call. */
|
|
4
|
+
export interface ChatSession {
|
|
5
|
+
/** Server thread id, sent with the next request. Never a `local-` id. */
|
|
6
|
+
serverThreadId: string | null;
|
|
7
|
+
/** Id the conversation is stored under: the server id, or a `local-` one before it exists. */
|
|
8
|
+
storageThreadId: string | null;
|
|
9
|
+
/** Local ids whose re-keyed replacement could not be verified yet; deleted on a later save. */
|
|
10
|
+
orphanIds: string[];
|
|
11
|
+
createdAt: number | null;
|
|
12
|
+
/** `updatedAt` of the last snapshot written or applied; newer stored records win over it. */
|
|
13
|
+
lastSyncedAt: number;
|
|
14
|
+
/** There are changes since the last write. */
|
|
15
|
+
dirty: boolean;
|
|
16
|
+
/** The user acted; a load that was still running must not overwrite the result. */
|
|
17
|
+
interacted: boolean;
|
|
18
|
+
/** Bumped to invalidate loads started before the conversation changed. */
|
|
19
|
+
loadRequest: number;
|
|
20
|
+
isStreaming: boolean;
|
|
21
|
+
/** The thread is streaming in another tab; sending is blocked meanwhile. */
|
|
22
|
+
foreignInFlight: boolean;
|
|
23
|
+
idCounter: number;
|
|
24
|
+
/** Per-instance suffix so two tabs cannot mint the same message id in the same millisecond. */
|
|
25
|
+
idSuffix: string;
|
|
26
|
+
/** Makes the card stored for a raw item: the one the conversation is rendered with. */
|
|
27
|
+
normalizeItem: ItemNormalizer;
|
|
28
|
+
}
|
|
29
|
+
export declare function createChatSession(initialThreadId?: string, normalizeItem?: ItemNormalizer): ChatSession;
|
|
30
|
+
/** Forget the current conversation. Returns the id it was stored under, if any. */
|
|
31
|
+
export declare function resetConversation(session: ChatSession): string | null;
|
|
32
|
+
export declare function nextMessageId(session: ChatSession): string;
|
|
33
|
+
/** Whether `messageId` was minted by this session rather than by another tab. */
|
|
34
|
+
export declare function isOwnMessage(session: ChatSession, messageId: string): boolean;
|
|
35
|
+
/** Makes a stored conversation the one this session continues. */
|
|
36
|
+
export declare function adoptStoredChat(session: ChatSession, chat: PersistedChat): void;
|
|
37
|
+
/** A copy that keeps the conversation `session` holds while `session` itself moves on. */
|
|
38
|
+
export declare function forkSession(session: ChatSession): ChatSession;
|
|
39
|
+
/** Makes `session` continue the conversation `from` kept, showing `messages`. */
|
|
40
|
+
export declare function adoptSession(session: ChatSession, from: ChatSession, messages: ChatMessage[]): void;
|
|
41
|
+
/** Builds the record to write and moves the session onto its key; `staleIds` are the records to retire once stored. */
|
|
42
|
+
export declare function prepareSnapshot(session: ChatSession, messages: ChatMessage[], now?: number): {
|
|
43
|
+
snapshot: PersistedChat;
|
|
44
|
+
staleIds: string[];
|
|
45
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { ChatMessage, ChatPersistence, PersistedChat } from '../types';
|
|
2
|
+
import type { Product } from './productNormalizer';
|
|
3
|
+
/** Stored record format; records with another version are ignored on read. */
|
|
4
|
+
export declare const PERSISTED_CHAT_VERSION = 1;
|
|
5
|
+
/** Prefix of the `localStorage` key; the api key, domain and user id are appended to it. */
|
|
6
|
+
export declare const DEFAULT_PERSISTENCE_KEY = "cio-asa:chat";
|
|
7
|
+
/** One week, matching how long the agent itself remembers a thread. */
|
|
8
|
+
export declare const DEFAULT_PERSISTENCE_TTL_MS: number;
|
|
9
|
+
/** How long a stored answer written by another tab is still shown as "typing" before it is settled. */
|
|
10
|
+
export declare const IN_FLIGHT_GRACE_MS: number;
|
|
11
|
+
/** Random token for the ids that must not collide between tabs: threads, tabs and messages. */
|
|
12
|
+
export declare function randomId(): string;
|
|
13
|
+
/** Client-side thread id for a conversation the server has not named yet. */
|
|
14
|
+
export declare function createLocalThreadId(): string;
|
|
15
|
+
/**
|
|
16
|
+
* Per-tab id: survives a reload, differs between tabs, `undefined` on the server. A duplicated tab
|
|
17
|
+
* copies `sessionStorage`, so the id is read back on a reload only, and is kept there only while
|
|
18
|
+
* the page is hidden.
|
|
19
|
+
*/
|
|
20
|
+
export declare function getTabId(): string | undefined;
|
|
21
|
+
/** Whether `threadId` was made by `createLocalThreadId` rather than by the server. */
|
|
22
|
+
export declare function isLocalThreadId(threadId: string | null | undefined): boolean;
|
|
23
|
+
/** Title for a thread list: the first user message, truncated with an ellipsis. */
|
|
24
|
+
export declare function getThreadTitle(messages: ChatMessage[]): string;
|
|
25
|
+
/** Counter to continue message ids from after a restore, so new ids never collide with restored ones. */
|
|
26
|
+
export declare function nextMessageCounter(messages: ChatMessage[]): number;
|
|
27
|
+
/** Whether the latest answer in `messages` has not finished streaming. */
|
|
28
|
+
export declare function isInFlight(messages: ChatMessage[]): boolean;
|
|
29
|
+
/** Settles answers stored mid-stream: `done` when they have content, an interrupted `error` otherwise. */
|
|
30
|
+
export declare function normalizeHydratedMessages(messages: ChatMessage[]): ChatMessage[];
|
|
31
|
+
/** Makes the product card for a raw search-result item. */
|
|
32
|
+
export type ItemNormalizer = (item: Record<string, unknown>) => Product;
|
|
33
|
+
/** Messages as stored: raw items replaced by the cards `normalize` makes; a restored group keeps its cards. */
|
|
34
|
+
export declare function compactMessages(messages: ChatMessage[], normalize: ItemNormalizer): ChatMessage[];
|
|
35
|
+
/**
|
|
36
|
+
* Both lists in stored order; a shared message takes the incoming version unless `preferStored`.
|
|
37
|
+
* A settled incoming version still replaces a stored one mid-stream: it is the later one by definition.
|
|
38
|
+
*/
|
|
39
|
+
export declare function mergeMessages(stored: ChatMessage[], incoming: ChatMessage[], preferStored?: boolean): ChatMessage[];
|
|
40
|
+
/**
|
|
41
|
+
* Saves `snapshot`, then deletes `staleIds` once the new record is read back. Stores may fail
|
|
42
|
+
* without throwing, so both steps are checked by reading: `saved` is whether the snapshot landed,
|
|
43
|
+
* `leftover` the stale ids still stored.
|
|
44
|
+
*/
|
|
45
|
+
export declare function saveThreadAndRetireStale(store: ChatPersistence, snapshot: PersistedChat, staleIds: string[]): Promise<{
|
|
46
|
+
saved: boolean;
|
|
47
|
+
leftover: string[];
|
|
48
|
+
}>;
|
|
49
|
+
/** Finds the server-keyed record another tab re-keyed a local thread into, by its first message. */
|
|
50
|
+
export declare function findRekeyedThread(store: ChatPersistence, firstMessageId: string | undefined): Promise<PersistedChat | null>;
|
|
51
|
+
/** Whether a stored thread still reads as streaming: this tab's own always, another tab's only within the grace. */
|
|
52
|
+
export declare function isThreadStreaming(chat: PersistedChat, now?: number): boolean;
|
|
53
|
+
/** Ms left to show a stored answer as streaming in another tab; `0` when it should be settled. */
|
|
54
|
+
export declare function foreignStreamRemainingMs(chat: PersistedChat, now?: number): number;
|
|
55
|
+
/**
|
|
56
|
+
* Moves every thread of `from` into `to`; copies of the chat on screen are not copied, its owner
|
|
57
|
+
* saves it. A source record is deleted only once `to` holds it after every copy was made, so one
|
|
58
|
+
* copy evicting another, or a save that did not land, leaves the source in place.
|
|
59
|
+
*/
|
|
60
|
+
export declare function moveThreads(from: ChatPersistence, to: ChatPersistence, onScreen?: {
|
|
61
|
+
threadIds?: string[];
|
|
62
|
+
firstMessageId?: string;
|
|
63
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type ConstructorIOClient from '@constructor-io/constructorio-client-javascript';
|
|
2
|
+
import type { ConstructorClientOptions } from '@constructor-io/constructorio-client-javascript/lib/types';
|
|
3
|
+
export type ClientIdentity = Pick<ConstructorClientOptions, 'apiKey' | 'userId'>;
|
|
4
|
+
/** `options` is private in the client's typings; read it here only, so a client bump fails a test, not storage keys. */
|
|
5
|
+
export declare function readClientOptions(client: ConstructorIOClient | null | undefined): ClientIdentity | undefined;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ChatPersistence, ClearPersistedConversationsOptions, LocalStoragePersistenceOptions, StorageArea } from '../types';
|
|
2
|
+
/** No user id, or an empty one: the conversation belongs to a guest. */
|
|
3
|
+
export declare const isGuest: (userId: string | number | null | undefined) => boolean;
|
|
4
|
+
/** The id the client and storage use: `undefined` for a guest (no id, `null` or empty). */
|
|
5
|
+
export declare const shopperId: (userId: string | number | null | undefined) => string | undefined;
|
|
6
|
+
/** Guests live in `sessionStorage`, so their history ends with the tab; shoppers keep theirs in `localStorage`. */
|
|
7
|
+
export declare function storageAreaFor(userId: string | null | undefined): StorageArea;
|
|
8
|
+
/** Namespace the provider stores under: api key, domain and, for a signed-in shopper, the user id. */
|
|
9
|
+
export declare function persistenceNamespace(parts: {
|
|
10
|
+
apiKey: string;
|
|
11
|
+
domain?: string;
|
|
12
|
+
userId?: string | null;
|
|
13
|
+
}): string;
|
|
14
|
+
/**
|
|
15
|
+
* Deletes every stored conversation of one shopper, or of the guest without `userId`; open tabs
|
|
16
|
+
* follow. Resolves once the deletion is recorded.
|
|
17
|
+
*/
|
|
18
|
+
export declare function clearPersistedConversations(options: ClearPersistedConversationsOptions): Promise<void>;
|
|
19
|
+
/** Conversation store on top of `localStorage`; one key per namespace, merged across tabs. */
|
|
20
|
+
export declare function createLocalStoragePersistence(options?: LocalStoragePersistenceOptions): ChatPersistence;
|
|
21
|
+
/**
|
|
22
|
+
* One store per namespace and area for the page, so a chat mounted again finds the answers still
|
|
23
|
+
* streaming into it. Not shared on the server, where the map would outlive the request.
|
|
24
|
+
*/
|
|
25
|
+
export declare function sharedLocalStoragePersistence(namespace: string, storageArea: StorageArea): ChatPersistence;
|
|
26
|
+
export default createLocalStoragePersistence;
|
package/lib/types/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const _default: "1.
|
|
1
|
+
declare const _default: "1.8.0";
|
|
2
2
|
export default _default;
|