@usereq/widget 0.2.24 → 1.0.0-experimental.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.
@@ -1,55 +0,0 @@
1
- import type { CSSProperties } from "react";
2
-
3
- export type ChatWidgetAppearance = {
4
- avatarOrbColor1?: string | null;
5
- avatarOrbColor2?: string | null;
6
- actionText?: string | null;
7
- };
8
-
9
- export type ResolvedChatWidgetAppearance = Required<ChatWidgetAppearance>;
10
-
11
- export const DEFAULT_CHAT_WIDGET_APPEARANCE: ResolvedChatWidgetAppearance = {
12
- avatarOrbColor1: "#2563eb",
13
- avatarOrbColor2: "#60a5fa",
14
- actionText: "Need help?",
15
- };
16
-
17
- export function resolveChatWidgetAppearance(
18
- appearance?: Partial<ChatWidgetAppearance> | null,
19
- ): ResolvedChatWidgetAppearance {
20
- return {
21
- avatarOrbColor1:
22
- normalizeString(appearance?.avatarOrbColor1) ??
23
- DEFAULT_CHAT_WIDGET_APPEARANCE.avatarOrbColor1,
24
- avatarOrbColor2:
25
- normalizeString(appearance?.avatarOrbColor2) ??
26
- DEFAULT_CHAT_WIDGET_APPEARANCE.avatarOrbColor2,
27
- actionText:
28
- normalizeString(appearance?.actionText) ??
29
- DEFAULT_CHAT_WIDGET_APPEARANCE.actionText,
30
- };
31
- }
32
-
33
- export function getChatWidgetGradientStyle(
34
- appearance?: Partial<ChatWidgetAppearance> | null,
35
- ): CSSProperties {
36
- const resolved = resolveChatWidgetAppearance(appearance);
37
- return {
38
- background: `linear-gradient(135deg, ${resolved.avatarOrbColor1}, ${resolved.avatarOrbColor2})`,
39
- };
40
- }
41
-
42
- export function getChatWidgetAvatarStyle(
43
- appearance?: Partial<ChatWidgetAppearance> | null,
44
- ): CSSProperties {
45
- const resolved = resolveChatWidgetAppearance(appearance);
46
- return {
47
- background: `linear-gradient(135deg, ${resolved.avatarOrbColor1}, ${resolved.avatarOrbColor2})`,
48
- boxShadow: `0 10px 24px color-mix(in srgb, ${resolved.avatarOrbColor1} 22%, transparent)`,
49
- };
50
- }
51
-
52
- function normalizeString(value: string | null | undefined): string | null {
53
- const trimmed = value?.trim();
54
- return trimmed ? trimmed : null;
55
- }
@@ -1,52 +0,0 @@
1
- /**
2
- * Staggered reveal timing for assistant message turns. Pure helper (no React)
3
- * used by the custom element to drip assistant replies in one-by-one with a
4
- * brief "typing" hold between them.
5
- */
6
-
7
- export type AssistantMessageRevealSource = {
8
- id: string;
9
- content: string;
10
- };
11
-
12
- export type AssistantMessageRevealStep<
13
- T extends AssistantMessageRevealSource = AssistantMessageRevealSource,
14
- > = {
15
- message: T;
16
- delayMs: number;
17
- };
18
-
19
- export function buildAssistantMessageRevealSteps<
20
- T extends AssistantMessageRevealSource,
21
- >(
22
- messages: T[],
23
- options?: {
24
- initialDelayMs?: number;
25
- typingHoldMs?: number;
26
- interMessageDelayMs?: number;
27
- interMessageDelayJitterMs?: number;
28
- },
29
- ): AssistantMessageRevealStep<T>[] {
30
- if (messages.length === 0) {
31
- return [];
32
- }
33
-
34
- const initialDelayMs = options?.initialDelayMs ?? 0;
35
- const typingHoldMs = options?.typingHoldMs ?? 1260;
36
- const interMessageDelayMs = options?.interMessageDelayMs ?? 1520;
37
- const jitterMs = Math.max(0, options?.interMessageDelayJitterMs ?? 100);
38
-
39
- return messages.map((message, index) => {
40
- const baseDelay =
41
- index === 0 ? initialDelayMs + typingHoldMs : interMessageDelayMs;
42
- const jitter =
43
- index === 0 || jitterMs === 0
44
- ? 0
45
- : Math.round((Math.random() * 2 - 1) * jitterMs);
46
-
47
- return {
48
- message,
49
- delayMs: Math.max(0, baseDelay + jitter),
50
- };
51
- });
52
- }
@@ -1,288 +0,0 @@
1
- export const STOP_CONFIRMATION_KIND = "stop_confirmation" as const;
2
- export const STOP_CONFIRMATION_RESULT_KIND =
3
- "stop_confirmation_result" as const;
4
-
5
- export const DEFAULT_STOP_CONVERSATION_PROMPT =
6
- "Do you want to stop this conversation?";
7
-
8
- export type StopConfirmationState = "pending" | "accepted" | "rejected";
9
- export type StopConfirmationDecision = "accepted" | "rejected";
10
-
11
- export type StopConfirmationPromptEnvelope = {
12
- kind: typeof STOP_CONFIRMATION_KIND;
13
- confirmationId: string;
14
- state: StopConfirmationState;
15
- prompt: string;
16
- };
17
-
18
- export type StopConfirmationResultEnvelope = {
19
- kind: typeof STOP_CONFIRMATION_RESULT_KIND;
20
- confirmationId: string;
21
- decision: StopConfirmationDecision;
22
- };
23
-
24
- export type StopConfirmationEnvelope =
25
- | StopConfirmationPromptEnvelope
26
- | StopConfirmationResultEnvelope;
27
-
28
- export type StopConfirmationMessageLike = {
29
- id: string;
30
- content: string;
31
- createdAt?: string;
32
- };
33
-
34
- export type StopConfirmationRenderItem<T extends StopConfirmationMessageLike> =
35
- | {
36
- type: "message";
37
- message: T;
38
- }
39
- | {
40
- type: "confirmation";
41
- message: T;
42
- envelope: StopConfirmationPromptEnvelope;
43
- latestEnvelope: StopConfirmationEnvelope;
44
- };
45
-
46
- function isRecord(value: unknown): value is Record<string, unknown> {
47
- return typeof value === "object" && value !== null && !Array.isArray(value);
48
- }
49
-
50
- function normalizeState(value: unknown): StopConfirmationState {
51
- return value === "accepted" || value === "rejected" ? value : "pending";
52
- }
53
-
54
- function normalizeDecision(value: unknown): StopConfirmationDecision | null {
55
- return value === "accepted" || value === "rejected" ? value : null;
56
- }
57
-
58
- function normalizePrompt(value: unknown): string {
59
- if (typeof value !== "string") {
60
- return DEFAULT_STOP_CONVERSATION_PROMPT;
61
- }
62
- const trimmed = value.trim();
63
- return trimmed.length > 0 ? trimmed : DEFAULT_STOP_CONVERSATION_PROMPT;
64
- }
65
-
66
- export function createStopConfirmationPromptEnvelope(input: {
67
- confirmationId: string;
68
- prompt?: string;
69
- state?: StopConfirmationState;
70
- }): StopConfirmationPromptEnvelope {
71
- return {
72
- kind: STOP_CONFIRMATION_KIND,
73
- confirmationId: input.confirmationId,
74
- state: normalizeState(input.state),
75
- prompt: normalizePrompt(input.prompt),
76
- };
77
- }
78
-
79
- export function createStopConfirmationResultEnvelope(input: {
80
- confirmationId: string;
81
- decision: StopConfirmationDecision;
82
- }): StopConfirmationResultEnvelope {
83
- return {
84
- kind: STOP_CONFIRMATION_RESULT_KIND,
85
- confirmationId: input.confirmationId,
86
- decision: input.decision,
87
- };
88
- }
89
-
90
- export function encodeStopConfirmationEnvelope(
91
- envelope: StopConfirmationEnvelope,
92
- ): string {
93
- return JSON.stringify(envelope);
94
- }
95
-
96
- export function encodeStopConfirmationPrompt(input: {
97
- confirmationId: string;
98
- prompt?: string;
99
- state?: StopConfirmationState;
100
- }): string {
101
- return encodeStopConfirmationEnvelope(
102
- createStopConfirmationPromptEnvelope(input),
103
- );
104
- }
105
-
106
- export function encodeStopConfirmationResult(input: {
107
- confirmationId: string;
108
- decision: StopConfirmationDecision;
109
- }): string {
110
- return encodeStopConfirmationEnvelope(
111
- createStopConfirmationResultEnvelope(input),
112
- );
113
- }
114
-
115
- export function parseStopConfirmationEnvelope(
116
- content: string | null | undefined,
117
- ): StopConfirmationEnvelope | null {
118
- if (typeof content !== "string" || content.trim() === "") {
119
- return null;
120
- }
121
-
122
- let parsed: unknown;
123
- try {
124
- parsed = JSON.parse(content);
125
- } catch {
126
- return null;
127
- }
128
-
129
- if (!isRecord(parsed) || typeof parsed.kind !== "string") {
130
- return null;
131
- }
132
-
133
- if (parsed.kind === STOP_CONFIRMATION_KIND) {
134
- if (typeof parsed.confirmationId !== "string" || !parsed.confirmationId.trim()) {
135
- return null;
136
- }
137
-
138
- return {
139
- kind: STOP_CONFIRMATION_KIND,
140
- confirmationId: parsed.confirmationId,
141
- state: normalizeState(parsed.state),
142
- prompt: normalizePrompt(parsed.prompt),
143
- };
144
- }
145
-
146
- if (parsed.kind === STOP_CONFIRMATION_RESULT_KIND) {
147
- const decision = normalizeDecision(parsed.decision);
148
- if (
149
- typeof parsed.confirmationId !== "string" ||
150
- !parsed.confirmationId.trim() ||
151
- decision == null
152
- ) {
153
- return null;
154
- }
155
-
156
- return {
157
- kind: STOP_CONFIRMATION_RESULT_KIND,
158
- confirmationId: parsed.confirmationId,
159
- decision,
160
- };
161
- }
162
-
163
- return null;
164
- }
165
-
166
- export function foldStopConfirmationMessages<T extends StopConfirmationMessageLike>(
167
- messages: T[],
168
- ): StopConfirmationRenderItem<T>[] {
169
- const byConfirmationId = new Map<
170
- string,
171
- {
172
- firstIndex: number;
173
- latestIndex: number;
174
- latestEnvelope: StopConfirmationEnvelope;
175
- }
176
- >();
177
-
178
- messages.forEach((message, index) => {
179
- const envelope = parseStopConfirmationEnvelope(message.content);
180
- if (!envelope) {
181
- return;
182
- }
183
-
184
- const entry = byConfirmationId.get(envelope.confirmationId);
185
- const sortValue = safeCreatedAtValue(message.createdAt, index);
186
- if (!entry) {
187
- byConfirmationId.set(envelope.confirmationId, {
188
- firstIndex: index,
189
- latestIndex: index,
190
- latestEnvelope: envelope,
191
- });
192
- return;
193
- }
194
-
195
- const currentSortValue = safeCreatedAtValue(
196
- messages[entry.latestIndex]?.createdAt,
197
- entry.latestIndex,
198
- );
199
-
200
- if (sortValue >= currentSortValue) {
201
- byConfirmationId.set(envelope.confirmationId, {
202
- firstIndex: entry.firstIndex,
203
- latestIndex: index,
204
- latestEnvelope: envelope,
205
- });
206
- }
207
- });
208
-
209
- const renderItems: StopConfirmationRenderItem<T>[] = [];
210
-
211
- messages.forEach((message, index) => {
212
- const envelope = parseStopConfirmationEnvelope(message.content);
213
- if (!envelope) {
214
- renderItems.push({ type: "message", message });
215
- return;
216
- }
217
-
218
- const entry = byConfirmationId.get(envelope.confirmationId);
219
- if (!entry || entry.firstIndex !== index) {
220
- return;
221
- }
222
-
223
- const promptEnvelope =
224
- entry.latestEnvelope.kind === STOP_CONFIRMATION_KIND
225
- ? entry.latestEnvelope
226
- : createStopConfirmationPromptEnvelope({
227
- confirmationId: entry.latestEnvelope.confirmationId,
228
- prompt: DEFAULT_STOP_CONVERSATION_PROMPT,
229
- });
230
-
231
- renderItems.push({
232
- type: "confirmation",
233
- message,
234
- envelope: promptEnvelope,
235
- latestEnvelope: entry.latestEnvelope,
236
- });
237
- });
238
-
239
- return renderItems;
240
- }
241
-
242
- export function getPendingStopConfirmationId<T extends StopConfirmationMessageLike>(
243
- messages: T[],
244
- ): string | null {
245
- const latestByConfirmationId = new Map<
246
- string,
247
- {
248
- sortValue: number;
249
- envelope: StopConfirmationEnvelope;
250
- }
251
- >();
252
-
253
- messages.forEach((message, index) => {
254
- const envelope = parseStopConfirmationEnvelope(message.content);
255
- if (!envelope) {
256
- return;
257
- }
258
-
259
- const sortValue = safeCreatedAtValue(message.createdAt, index);
260
- const current = latestByConfirmationId.get(envelope.confirmationId);
261
- if (!current || sortValue >= current.sortValue) {
262
- latestByConfirmationId.set(envelope.confirmationId, {
263
- sortValue,
264
- envelope,
265
- });
266
- }
267
- });
268
-
269
- for (const [confirmationId, entry] of latestByConfirmationId.entries()) {
270
- if (
271
- entry.envelope.kind === STOP_CONFIRMATION_KIND &&
272
- entry.envelope.state === "pending"
273
- ) {
274
- return confirmationId;
275
- }
276
- }
277
-
278
- return null;
279
- }
280
-
281
- function safeCreatedAtValue(value: string | undefined, fallbackIndex: number): number {
282
- if (typeof value !== "string") {
283
- return fallbackIndex;
284
- }
285
-
286
- const timestamp = Date.parse(value);
287
- return Number.isNaN(timestamp) ? fallbackIndex : timestamp;
288
- }