@sevenfold/setto-client 0.27.0 → 0.28.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,6 +7,8 @@ type Message = {
7
7
  id?: string;
8
8
  role: 'visitor' | 'assistant' | 'human' | 'system';
9
9
  content: string;
10
+ /** Server insert time; absent on local optimistic and streaming turns. */
11
+ created_at?: string;
10
12
  reveal?: boolean;
11
13
  avatarUrl?: string | null;
12
14
  authorName?: string | null;
@@ -20,9 +22,23 @@ type Message = {
20
22
  export declare function nextWordBoundary(content: string, visibleLength: number): number;
21
23
  export declare function sineOut(progress: number): number;
22
24
  export declare function isAtChatBottom(scrollHeight: number, scrollTop: number, clientHeight: number): boolean;
25
+ export declare function chatComposerSpace(height: number): number;
23
26
  export declare function AgentJoinedEvent({ message }: {
24
27
  message: Message;
25
28
  }): import("react/jsx-runtime").JSX.Element;
29
+ /**
30
+ * A teammate is composing. The row opts out of the transcript's polite live
31
+ * region so start/stop bursts are not announced, while its label stays
32
+ * readable when a screen-reader user browses the log.
33
+ */
34
+ export declare function TeamTypingRow({ label, avatarUrl }: {
35
+ label: string;
36
+ avatarUrl: string | null;
37
+ }): import("react/jsx-runtime").JSX.Element;
38
+ /** "Sett"/"Seen" under the visitor's latest message; not announced live. */
39
+ export declare function SeenMarker({ label }: {
40
+ label: string;
41
+ }): import("react/jsx-runtime").JSX.Element;
26
42
  export declare function SiteChatMarkdown({ content, reveal }: {
27
43
  content: string;
28
44
  reveal?: boolean;
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Pure decisions behind the visitor widget's Realtime doorbell, typing
3
+ * indicators and read markers. Realtime carries no chat data: every event only
4
+ * prompts the widget to refetch through the visitor-bound HTTP API, so these
5
+ * helpers never see message text, names or ids in a payload.
6
+ */
7
+ /** Fallback poll while no Realtime channel is subscribed (today's cadence). */
8
+ export declare const POLL_FALLBACK_MS = 5000;
9
+ /** Slow safety poll while the conversation channel is subscribed. */
10
+ export declare const POLL_SUBSCRIBED_MS = 30000;
11
+ /** Bursts of `changed` events collapse into one refetch. */
12
+ export declare const CHANGED_DEBOUNCE_MS = 250;
13
+ /** A visitor sends `typing: true` at most this often while composing. */
14
+ export declare const TYPING_THROTTLE_MS = 2500;
15
+ /** A team typing row disappears this long after the last `typing: true`. */
16
+ export declare const TYPING_EXPIRY_MS = 6000;
17
+ export type ChatSide = 'team' | 'visitor';
18
+ export type TypingSignal = {
19
+ side: ChatSide;
20
+ typing: boolean;
21
+ };
22
+ export type ReadSignal = {
23
+ side: ChatSide;
24
+ at: string;
25
+ };
26
+ /** The subset of a transcript message these decisions read. */
27
+ export type TimelineMessage = {
28
+ id?: string;
29
+ role: string;
30
+ kind?: string;
31
+ created_at?: string;
32
+ authorName?: string | null;
33
+ avatarUrl?: string | null;
34
+ };
35
+ export declare function pollIntervalMs(subscribed: boolean): number;
36
+ /** Only a server-issued conversation topic is joined; clients never build one. */
37
+ export declare function conversationTopic(value: unknown): string | null;
38
+ export declare function parseTypingSignal(payload: unknown): TypingSignal | null;
39
+ export declare function parseReadSignal(payload: unknown): ReadSignal | null;
40
+ /**
41
+ * Epoch milliseconds with sub-millisecond precision. Postgres returns
42
+ * microseconds (`…:05.123456+00:00`), which engines may truncate or reject, so
43
+ * the fraction is added separately and a missing zone is read as UTC.
44
+ */
45
+ export declare function timestampValue(value: string | null | undefined): number | null;
46
+ /** Monotonic merge: keep whichever valid timestamp is later. */
47
+ export declare function laterTimestamp(current: string | null, next: string | null | undefined): string | null;
48
+ export type TypingSender = {
49
+ /** Call on every draft change. */
50
+ update(draft: string): void;
51
+ /** Draft emptied, message sent, composer blurred or channel leaving. */
52
+ stop(): void;
53
+ /** Forget local state without sending (the channel is gone). */
54
+ reset(): void;
55
+ };
56
+ /**
57
+ * Throttles visitor typing signals. `send` returns false when the channel is
58
+ * not subscribed, in which case nothing is considered sent.
59
+ */
60
+ export declare function createTypingSender(send: (typing: boolean) => boolean, now?: () => number): TypingSender;
61
+ /** The widget shows only team typing; returns the new expiry deadline. */
62
+ export declare function nextTeamTypingDeadline(current: number | null, signal: TypingSignal, now: number): number | null;
63
+ /** Identity of the newest human reply, used to hide typing once it lands. */
64
+ export declare function latestHumanMessageKey(messages: readonly TimelineMessage[]): string | null;
65
+ /** First name and photo of the latest teammate who replied or joined. */
66
+ export declare function teamTypingIdentity(messages: readonly TimelineMessage[]): {
67
+ name: string | null;
68
+ avatarUrl: string | null;
69
+ };
70
+ export declare function teamTypingLabel(norwegian: boolean, name: string | null): string;
71
+ /**
72
+ * Index of the visitor message that carries "Sett"/"Seen", or -1. Only the
73
+ * latest visitor message qualifies, and only once it has a server timestamp
74
+ * that a teammate's read receipt has reached.
75
+ */
76
+ export declare function seenVisitorMessageIndex(messages: readonly TimelineMessage[], humanJoined: boolean, teamReadAt: string | null): number;
77
+ export type ReadMarkerContext = {
78
+ humanJoined: boolean;
79
+ chatOpen: boolean;
80
+ pageVisible: boolean;
81
+ /** The latest `through` already sent or in flight for this conversation. */
82
+ lastSent: string | null;
83
+ };
84
+ /**
85
+ * The `through` value to POST as the visitor's read marker, or null when no
86
+ * marker should be sent: no human has joined, the chat is closed or hidden, or
87
+ * nothing newer than the last marker has arrived from Barista or the team.
88
+ */
89
+ export declare function readMarkerThrough(messages: readonly TimelineMessage[], context: ReadMarkerContext): string | null;
90
+ export type RefreshTicket = {
91
+ conversationId: string | null;
92
+ /** Increments whenever a visitor turn starts. */
93
+ turn: number;
94
+ /** Increments whenever a newer refetch or conversation load starts. */
95
+ request: number;
96
+ };
97
+ /**
98
+ * What to do with a conversation refetch when it resolves. A response that
99
+ * lands during (or overlapped) a visitor turn would replace the locally
100
+ * streaming answer, so it is deferred and re-requested once the turn ends.
101
+ */
102
+ export declare function refreshOutcome(started: RefreshTicket, current: RefreshTicket & {
103
+ busy: boolean;
104
+ }): 'apply' | 'defer' | 'drop';