@flamingo-stack/openframe-frontend-core 0.0.726 → 0.0.727
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/dist/{chunk-3XDVGHL4.js → chunk-33AFAVBF.js} +2 -2
- package/dist/{chunk-R3BOJNZI.cjs → chunk-3D2SCG2E.cjs} +16 -16
- package/dist/{chunk-RSE3MU4G.cjs → chunk-3N3QA47Y.cjs} +11 -11
- package/dist/{chunk-COFT3YPA.js → chunk-3Q7ZNY4B.js} +5 -5
- package/dist/{chunk-3UZET2G7.js → chunk-4FQ5ZICE.js} +2 -2
- package/dist/{chunk-Q2G7NVFH.cjs → chunk-AM2RHT3H.cjs} +280 -280
- package/dist/{chunk-NZYMMYWN.js → chunk-AU4WWLFP.js} +1 -1
- package/dist/{chunk-RVSNXG6J.cjs → chunk-BKTV3VOD.cjs} +483 -418
- package/dist/{chunk-4ND65E2B.cjs → chunk-BMOEUIFI.cjs} +35 -35
- package/dist/{chunk-JYJBL7GD.js → chunk-C6Y22J32.js} +1 -1
- package/dist/{chunk-DXDLCYCM.cjs → chunk-CGL3TJ2A.cjs} +3 -3
- package/dist/{chunk-SBPPM65Y.js → chunk-CIAAU44V.js} +257 -192
- package/dist/{chunk-X2PG6XWF.cjs → chunk-CNBGSWLN.cjs} +9 -9
- package/dist/{chunk-4VRHDFF2.js → chunk-FIKAJKPE.js} +1 -1
- package/dist/{chunk-MZ3TMXIX.cjs → chunk-FLCYWEEB.cjs} +3 -3
- package/dist/{chunk-YVWJ3TRY.js → chunk-GKJFSE72.js} +5 -5
- package/dist/{chunk-NLWIBA55.js → chunk-HVMH67Z7.js} +3 -3
- package/dist/{chunk-MQ3ZM7FJ.js → chunk-J274GOBJ.js} +1 -1
- package/dist/{chunk-5SWQ2ZU5.cjs → chunk-MAGVU2HZ.cjs} +26 -26
- package/dist/{chunk-VPABPC2M.cjs → chunk-MGVT5RT6.cjs} +29 -56
- package/dist/{chunk-C5KMQLJP.js → chunk-N27CSEDR.js} +1 -1
- package/dist/{chunk-MZY6LBM5.cjs → chunk-N2PHC6RX.cjs} +3 -3
- package/dist/{chunk-AZKVHJA5.js → chunk-NQRFGXDG.js} +2 -2
- package/dist/{chunk-XEIDJ6SL.cjs → chunk-QJVG5V46.cjs} +65 -65
- package/dist/{chunk-Z3VAFLYK.cjs → chunk-RZFTEUV4.cjs} +9 -9
- package/dist/{chunk-EHYAOIE4.cjs → chunk-U6HOC7JD.cjs} +8 -8
- package/dist/{chunk-SCTS3EMJ.js → chunk-URDJXB75.js} +3 -3
- package/dist/{chunk-MM66P4X4.js → chunk-VTXIY33G.js} +22 -49
- package/dist/{chunk-FMFCPX3R.cjs → chunk-WWJOQGSH.cjs} +33 -33
- package/dist/{chunk-VKR56R3U.js → chunk-WXL5G7WC.js} +3 -3
- package/dist/{cloud-tenants-NH7VE7KE.js → cloud-tenants-65EUPGVC.js} +2 -2
- package/dist/{cloud-tenants-5JTQ2ENQ.cjs → cloud-tenants-PT5QPWWU.cjs} +3 -3
- package/dist/components/case-studies/index.cjs +14 -14
- package/dist/components/case-studies/index.js +3 -3
- package/dist/components/chat/assistant-ask-prompts.d.ts +34 -14
- package/dist/components/chat/assistant-ask-prompts.d.ts.map +1 -1
- package/dist/components/chat/hooks/index.d.ts +1 -1
- package/dist/components/chat/hooks/index.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-ask-prompts.d.ts +34 -7
- package/dist/components/chat/hooks/use-ask-prompts.d.ts.map +1 -1
- package/dist/components/chat/index.cjs +2 -4
- package/dist/components/chat/index.js +3 -5
- package/dist/components/contact/index.cjs +4 -4
- package/dist/components/contact/index.js +3 -3
- package/dist/components/docs/index.cjs +7 -7
- package/dist/components/docs/index.js +6 -6
- package/dist/components/embeds/index.cjs +4 -4
- package/dist/components/embeds/index.js +3 -3
- package/dist/components/faq/faq-ask-card.d.ts +8 -11
- package/dist/components/faq/faq-ask-card.d.ts.map +1 -1
- package/dist/components/faq/index.cjs +4 -4
- package/dist/components/faq/index.js +3 -3
- package/dist/components/features/index.cjs +3 -3
- package/dist/components/features/index.js +2 -2
- package/dist/components/help-center-pages/index.cjs +71 -71
- package/dist/components/help-center-pages/index.js +13 -13
- package/dist/components/index.cjs +35 -37
- package/dist/components/index.js +14 -16
- package/dist/components/meeting-scheduler/index.cjs +19 -19
- package/dist/components/meeting-scheduler/index.js +2 -2
- package/dist/components/navigation/index.cjs +6 -6
- package/dist/components/navigation/index.js +5 -5
- package/dist/components/onboarding-guides/index.cjs +5 -5
- package/dist/components/onboarding-guides/index.js +4 -4
- package/dist/components/product-demo/index.cjs +6 -6
- package/dist/components/product-demo/index.js +6 -6
- package/dist/components/tickets/index.cjs +7 -7
- package/dist/components/tickets/index.js +6 -6
- package/dist/components/ui/index.cjs +3 -5
- package/dist/components/ui/index.js +4 -6
- package/dist/{devices-OC7JEI6U.js → devices-J253LQNQ.js} +2 -2
- package/dist/{devices-PV76NGQO.cjs → devices-ZPVLATZA.cjs} +4 -4
- package/dist/index.cjs +7 -9
- package/dist/index.js +8 -10
- package/dist/{knowledge-5AYFIVKD.cjs → knowledge-3FAF5T7Q.cjs} +4 -4
- package/dist/{knowledge-L7WQ7IEP.js → knowledge-IMTMYURI.js} +2 -2
- package/dist/{logs-MGFHGVIE.js → logs-55GOSGVE.js} +2 -2
- package/dist/{logs-4F77RJXJ.cjs → logs-UL7BQEXP.cjs} +11 -11
- package/dist/{policies-WRDZFNIB.js → policies-KRDF563O.js} +2 -2
- package/dist/{policies-VCGM3C5Z.cjs → policies-O5F2XSCY.cjs} +7 -7
- package/dist/{software-update-WVG2GDZO.js → software-update-NXSC4OVM.js} +2 -2
- package/dist/{software-update-YD2MNL3K.cjs → software-update-R2ASCRWP.cjs} +6 -6
- package/package.json +1 -1
- package/src/components/chat/assistant-ask-prompts.tsx +84 -39
- package/src/components/chat/hooks/index.ts +3 -2
- package/src/components/chat/hooks/use-ask-prompts.ts +140 -56
- package/src/components/faq/faq-ask-card.tsx +22 -67
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { useCallback, useId, useLayoutEffect, useSyncExternalStore } from 'react';
|
|
3
|
+
import { useCallback, useId, useLayoutEffect, useMemo, useState, useSyncExternalStore } from 'react';
|
|
4
4
|
import {
|
|
5
5
|
useAssistantRuntime,
|
|
6
6
|
type AssistantOpenRequest,
|
|
@@ -71,8 +71,10 @@ export function useAskPrompts({
|
|
|
71
71
|
const assistant = useAssistantRuntime();
|
|
72
72
|
const base = assistantAvailable(assistant) ? assistant.askPromptsUrl : undefined;
|
|
73
73
|
const url = base && enabled ? buildAskPromptsUrl(base, { count, topic, exclude }) : null;
|
|
74
|
-
const { data,
|
|
75
|
-
|
|
74
|
+
const { data, dataUrl, error } = useSelfFetch<AskPromptsResponse>(url);
|
|
75
|
+
// Landed for THIS url (or failed): never the frame between a url appearing and its fetch starting.
|
|
76
|
+
const landed = url !== null && (dataUrl === url || error);
|
|
77
|
+
return { prompts: landed ? (data?.prompts ?? []) : [], isLoading: !!base && !landed, supported: !!base };
|
|
76
78
|
}
|
|
77
79
|
|
|
78
80
|
/**
|
|
@@ -99,90 +101,172 @@ export function useAssistantOpen(
|
|
|
99
101
|
);
|
|
100
102
|
}
|
|
101
103
|
|
|
102
|
-
// ───
|
|
103
|
-
// Every surface that
|
|
104
|
-
// a
|
|
105
|
-
//
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
//
|
|
104
|
+
// ─── One pick at a time, and never a question twice ──────────────────────────
|
|
105
|
+
// Every surface that offers questions (a row in a section, the FAQ's card)
|
|
106
|
+
// takes a TURN before it picks, in the order the surfaces came to need one:
|
|
107
|
+
// document order for the ones a page mounts together, later for one that waits
|
|
108
|
+
// to be reached (the FAQ's card). A surface picks once every surface ahead of
|
|
109
|
+
// it has settled, and leaves out every question the page already shows. So no
|
|
110
|
+
// two surfaces of a page can show the same question, whatever their topics,
|
|
111
|
+
// with no wiring by the host. One queue per QUESTIONS ENDPOINT (the runtime's
|
|
112
|
+
// `askPromptsUrl`): two assistants on one page read different questions, and
|
|
113
|
+
// never wait for or leave out each other's. A module store, read through
|
|
114
|
+
// React's external-store hook.
|
|
109
115
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
remove(surface: string): void;
|
|
116
|
+
/** One endpoint's queue: each surface's turn and the questions it settled on (`null`: not yet). */
|
|
117
|
+
interface AskQueue {
|
|
118
|
+
enter(surface: string): void;
|
|
119
|
+
settle(surface: string, ids: readonly string[] | null): void;
|
|
120
|
+
leave(surface: string): void;
|
|
116
121
|
subscribe(listener: () => void): () => void;
|
|
117
|
-
|
|
122
|
+
/**
|
|
123
|
+
* What `surface` must leave out, as a comma-joined list ('' for nothing),
|
|
124
|
+
* once it may pick; `null` while it has no turn or a surface ahead of it is
|
|
125
|
+
* still picking. A string, so React can compare two readings.
|
|
126
|
+
*/
|
|
127
|
+
turnOf(surface: string): string | null;
|
|
118
128
|
}
|
|
119
129
|
|
|
120
|
-
function
|
|
121
|
-
const
|
|
130
|
+
function createAskQueue(): AskQueue {
|
|
131
|
+
const entries = new Map<string, { turn: number; ids: readonly string[] | null }>();
|
|
122
132
|
const listeners = new Set<() => void>();
|
|
123
|
-
let
|
|
133
|
+
let next = 0;
|
|
124
134
|
const publish = (): void => {
|
|
125
|
-
const lists = [...bySurface.values()];
|
|
126
|
-
const next = lists.some(list => list === null) ? null : lists.flatMap(list => list ?? []);
|
|
127
|
-
const same = next === null ? snapshot === null : snapshot !== null && next.join(',') === snapshot.join(',');
|
|
128
|
-
if (same) return;
|
|
129
|
-
snapshot = next === null ? null : next.length > 0 ? next : NO_IDS;
|
|
130
135
|
for (const listener of listeners) listener();
|
|
131
136
|
};
|
|
132
137
|
return {
|
|
133
|
-
|
|
134
|
-
|
|
138
|
+
enter(surface) {
|
|
139
|
+
if (entries.has(surface)) return;
|
|
140
|
+
entries.set(surface, { turn: next++, ids: null });
|
|
135
141
|
publish();
|
|
136
142
|
},
|
|
137
|
-
|
|
138
|
-
|
|
143
|
+
settle(surface, ids) {
|
|
144
|
+
const entry = entries.get(surface);
|
|
145
|
+
if (!entry || (entry.ids?.join(',') ?? null) === (ids?.join(',') ?? null)) return;
|
|
146
|
+
entry.ids = ids;
|
|
139
147
|
publish();
|
|
140
148
|
},
|
|
149
|
+
leave(surface) {
|
|
150
|
+
if (entries.delete(surface)) publish();
|
|
151
|
+
},
|
|
141
152
|
subscribe(listener) {
|
|
142
153
|
listeners.add(listener);
|
|
143
154
|
return () => listeners.delete(listener);
|
|
144
155
|
},
|
|
145
|
-
|
|
156
|
+
turnOf(surface) {
|
|
157
|
+
const own = entries.get(surface);
|
|
158
|
+
if (!own) return null;
|
|
159
|
+
const shown: string[] = [];
|
|
160
|
+
for (const [other, entry] of entries) {
|
|
161
|
+
if (other === surface) continue;
|
|
162
|
+
if (entry.ids === null) {
|
|
163
|
+
if (entry.turn < own.turn) return null;
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
shown.push(...entry.ids);
|
|
167
|
+
}
|
|
168
|
+
return shown.join(',');
|
|
169
|
+
},
|
|
146
170
|
};
|
|
147
171
|
}
|
|
148
172
|
|
|
149
|
-
const
|
|
173
|
+
const askQueues = new Map<string, AskQueue>();
|
|
150
174
|
|
|
151
|
-
/** The
|
|
152
|
-
function
|
|
175
|
+
/** The queue a surface belongs to: the questions endpoint of its assistant runtime. */
|
|
176
|
+
function useAskQueue(): AskQueue {
|
|
153
177
|
const scope = useAssistantRuntime()?.askPromptsUrl ?? '';
|
|
154
|
-
let
|
|
155
|
-
if (!
|
|
156
|
-
|
|
157
|
-
|
|
178
|
+
let queue = askQueues.get(scope);
|
|
179
|
+
if (!queue) {
|
|
180
|
+
queue = createAskQueue();
|
|
181
|
+
askQueues.set(scope, queue);
|
|
158
182
|
}
|
|
159
|
-
return
|
|
183
|
+
return queue;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export interface UseAskSurfaceOptions {
|
|
187
|
+
/** What the surface is about (see `UseAskPromptsOptions.topic`). */
|
|
188
|
+
topic?: string;
|
|
189
|
+
count?: number;
|
|
190
|
+
/**
|
|
191
|
+
* Ids of questions never to offer. Absent: the ones the page's other
|
|
192
|
+
* surfaces show, which this one waits for. A list: exactly those, picked at
|
|
193
|
+
* once with no wait (the surfaces after it still leave its questions out).
|
|
194
|
+
* `null`: the surface waits and never picks.
|
|
195
|
+
*/
|
|
196
|
+
exclude?: readonly string[] | null;
|
|
197
|
+
/** False: the surface is not ready to pick yet (it takes its turn when it is). Default true. */
|
|
198
|
+
enabled?: boolean;
|
|
199
|
+
/** How the chat is opened from THIS surface. Absent: the assistant runtime's opener. */
|
|
200
|
+
onOpen?: (request: AssistantOpenRequest) => void;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export interface UseAskSurfaceResult extends UseAskPromptsResult {
|
|
204
|
+
/** The nearest assistant runtime; `null` with none mounted. */
|
|
205
|
+
assistant: AssistantRuntime | null;
|
|
206
|
+
/** The same runtime with THIS surface's opener: what the surface's launcher reads. */
|
|
207
|
+
scoped: AssistantRuntime | null;
|
|
208
|
+
/** Opens the chat (and asks, with a prompt), carrying the surface's topic. */
|
|
209
|
+
open: (request: { prompt?: string }) => void;
|
|
210
|
+
/** Reports a question that was asked (the runtime's `onAsk`, with the topic). */
|
|
211
|
+
reportAsk: (prompt: AskPrompt) => void;
|
|
160
212
|
}
|
|
161
213
|
|
|
162
214
|
/**
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
215
|
+
* THE wiring of an "ask" surface, whatever it looks like: the assistant
|
|
216
|
+
* runtime, the surface's questions (picked on its turn, never one the page
|
|
217
|
+
* already shows), the opener and the click report. `AssistantAskPrompts` is its
|
|
218
|
+
* one component; nothing else picks questions for a page.
|
|
166
219
|
*/
|
|
167
|
-
export function
|
|
220
|
+
export function useAskSurface({
|
|
221
|
+
topic,
|
|
222
|
+
count = ASK_PROMPTS_DEFAULT_COUNT,
|
|
223
|
+
exclude,
|
|
224
|
+
enabled = true,
|
|
225
|
+
onOpen,
|
|
226
|
+
}: UseAskSurfaceOptions = {}): UseAskSurfaceResult {
|
|
227
|
+
const assistant = useAssistantRuntime();
|
|
168
228
|
const surface = useId();
|
|
169
|
-
const
|
|
170
|
-
const
|
|
171
|
-
|
|
229
|
+
const queue = useAskQueue();
|
|
230
|
+
const picks = assistantAvailable(assistant) && !!assistant.askPromptsUrl && enabled && exclude !== null;
|
|
231
|
+
|
|
232
|
+
// A layout effect: surfaces a page mounts together take their turns in document order.
|
|
172
233
|
useLayoutEffect(() => {
|
|
173
|
-
if (
|
|
174
|
-
|
|
175
|
-
return () =>
|
|
176
|
-
}, [
|
|
177
|
-
|
|
234
|
+
if (!picks) return undefined;
|
|
235
|
+
queue.enter(surface);
|
|
236
|
+
return () => queue.leave(surface);
|
|
237
|
+
}, [queue, surface, picks]);
|
|
238
|
+
|
|
239
|
+
const turn = useSyncExternalStore(
|
|
240
|
+
queue.subscribe,
|
|
241
|
+
() => queue.turnOf(surface),
|
|
242
|
+
() => null,
|
|
243
|
+
);
|
|
244
|
+
// ONE pick per mount: what the page showed when this surface's turn came is what it leaves out, for good.
|
|
245
|
+
const [shownAtTurn, setShownAtTurn] = useState<string | null>(null);
|
|
246
|
+
if (picks && turn !== null && shownAtTurn === null) setShownAtTurn(turn);
|
|
247
|
+
const leftOut = useMemo(() => exclude ?? (shownAtTurn ? shownAtTurn.split(',') : undefined), [exclude, shownAtTurn]);
|
|
248
|
+
|
|
249
|
+
const { prompts, isLoading, supported } = useAskPrompts({
|
|
250
|
+
topic,
|
|
251
|
+
count,
|
|
252
|
+
exclude: leftOut,
|
|
253
|
+
// Told exactly what to leave out, a surface needs nobody's pick: it never waits for a turn.
|
|
254
|
+
enabled: picks && (exclude !== undefined || shownAtTurn !== null),
|
|
255
|
+
});
|
|
256
|
+
const settled = picks && !isLoading ? prompts.map(prompt => prompt.id).join(',') : null;
|
|
257
|
+
useLayoutEffect(() => {
|
|
258
|
+
queue.settle(surface, settled === null ? null : settled ? settled.split(',') : []);
|
|
259
|
+
}, [queue, surface, settled]);
|
|
178
260
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
const
|
|
182
|
-
|
|
261
|
+
const open = useAssistantOpen(topic, onOpen);
|
|
262
|
+
const scoped = useMemo(() => (assistant ? { ...assistant, open } : null), [assistant, open]);
|
|
263
|
+
const onAsk = assistant?.onAsk;
|
|
264
|
+
const reportAsk = useCallback((prompt: AskPrompt) => onAsk?.({ promptId: prompt.id, topic }), [onAsk, topic]);
|
|
265
|
+
// A surface that waits (not reached, or told to) is loading wherever questions can exist.
|
|
266
|
+
return { assistant, scoped, prompts, isLoading: supported && (!picks || isLoading), supported, open, reportAsk };
|
|
183
267
|
}
|
|
184
268
|
|
|
185
269
|
/** Tests only: forget every surface. */
|
|
186
|
-
export function
|
|
187
|
-
|
|
270
|
+
export function resetAskSurfaces(): void {
|
|
271
|
+
askQueues.clear();
|
|
188
272
|
}
|
|
@@ -1,21 +1,10 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
AssistantRuntimeContext,
|
|
6
|
-
useAssistantRuntime,
|
|
7
|
-
type AssistantOpenRequest,
|
|
8
|
-
type AssistantRuntime,
|
|
9
|
-
} from '../../contexts/assistant-runtime-context';
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import type { AssistantOpenRequest, AssistantRuntime } from '../../contexts/assistant-runtime-context';
|
|
10
5
|
import { useInView } from '../../hooks/ui/use-in-view';
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
assistantAvailable,
|
|
14
|
-
ASK_PROMPTS_DEFAULT_COUNT,
|
|
15
|
-
useAskPrompts,
|
|
16
|
-
useAssistantOpen,
|
|
17
|
-
useShownAskPromptIds,
|
|
18
|
-
} from '../chat/hooks/use-ask-prompts';
|
|
6
|
+
import { AssistantAskPrompts } from '../chat/assistant-ask-prompts';
|
|
7
|
+
import { assistantAvailable } from '../chat/hooks/use-ask-prompts';
|
|
19
8
|
|
|
20
9
|
/** The card beside a FAQ, as a host may shape it. Every field is optional. */
|
|
21
10
|
export interface FaqAskOptions {
|
|
@@ -29,8 +18,7 @@ export interface FaqAskOptions {
|
|
|
29
18
|
topic?: string;
|
|
30
19
|
/**
|
|
31
20
|
* Ids of questions never to offer. Absent: the ones the page's other "ask"
|
|
32
|
-
* surfaces show
|
|
33
|
-
* card waits for. `null`: wait.
|
|
21
|
+
* surfaces show, which the card waits for. `null`: wait.
|
|
34
22
|
*/
|
|
35
23
|
exclude?: readonly string[] | null;
|
|
36
24
|
/** How many questions the card offers. Absent: the runtime's, else three. */
|
|
@@ -46,70 +34,37 @@ export interface FaqAskOptions {
|
|
|
46
34
|
onOpen?: (request: AssistantOpenRequest) => void;
|
|
47
35
|
}
|
|
48
36
|
|
|
49
|
-
/** The lib's own wording, when neither the host nor the runtime states it. */
|
|
50
|
-
const DEFAULT_ASK_CARD = {
|
|
51
|
-
title: 'Still deciding?',
|
|
52
|
-
description: '{assistant} answers from our docs and customer stories.',
|
|
53
|
-
} as const;
|
|
54
|
-
|
|
55
37
|
/** How far below the viewport the card is when it picks its questions. */
|
|
56
38
|
const PICK_ROOT_MARGIN = '600px';
|
|
57
39
|
|
|
58
|
-
const fillAssistant = (text: string, name: string) => text.split('{assistant}').join(name);
|
|
59
|
-
|
|
60
40
|
/** True when a FAQ under this runtime shows the card: a chat to open, and a name for the card's words. */
|
|
61
41
|
export function faqAskCardShown(assistant: AssistantRuntime | null): assistant is AssistantRuntime {
|
|
62
42
|
return assistantAvailable(assistant) && !!assistant.name;
|
|
63
43
|
}
|
|
64
44
|
|
|
65
45
|
/**
|
|
66
|
-
* The "ask the assistant" card of a FAQ:
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
* height before and after the questions arrive. A host with no questions
|
|
72
|
-
* endpoint gets the launcher with no questions.
|
|
46
|
+
* The "ask the assistant" card of a FAQ: THE ask surface (`AssistantAskPrompts`)
|
|
47
|
+
* as a card, told when to pick. It picks ONCE, when the card first comes near
|
|
48
|
+
* the viewport: a visitor who never reaches the FAQ costs no request, and the
|
|
49
|
+
* card then leaves out every question the page above already shows. The card
|
|
50
|
+
* is the same height before and after the questions arrive.
|
|
73
51
|
*/
|
|
74
52
|
export function FaqAskCard({ topic, exclude, count, title, description, onOpen }: FaqAskOptions & { topic: string }) {
|
|
75
|
-
const assistant = useAssistantRuntime();
|
|
76
53
|
const { ref, inView } = useInView<HTMLDivElement>({ rootMargin: PICK_ROOT_MARGIN });
|
|
77
54
|
const [reached, setReached] = useState(false);
|
|
78
55
|
if (inView && !reached) setReached(true);
|
|
79
|
-
|
|
80
|
-
const shownElsewhere = useShownAskPromptIds();
|
|
81
|
-
const excluded = exclude === undefined ? shownElsewhere : exclude;
|
|
82
|
-
const slots = count ?? assistant?.askCard?.count ?? ASK_PROMPTS_DEFAULT_COUNT;
|
|
83
|
-
const { prompts, isLoading, supported } = useAskPrompts({
|
|
84
|
-
topic,
|
|
85
|
-
count: slots,
|
|
86
|
-
exclude: excluded ?? undefined,
|
|
87
|
-
enabled: reached && excluded !== null,
|
|
88
|
-
});
|
|
89
|
-
const open = useAssistantOpen(topic, onOpen);
|
|
90
|
-
// The card's launcher opens the same chat as its questions, with the card's topic: it reads the runtime.
|
|
91
|
-
const scoped = useMemo(() => (assistant ? { ...assistant, open } : null), [assistant, open]);
|
|
92
|
-
|
|
93
|
-
if (!faqAskCardShown(assistant) || !assistant.name) return null;
|
|
94
|
-
const name = assistant.name;
|
|
95
|
-
const cardDescription = description ?? assistant.askCard?.description ?? DEFAULT_ASK_CARD.description;
|
|
96
56
|
return (
|
|
97
|
-
<
|
|
98
|
-
<
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
onOpen={open}
|
|
110
|
-
onAsk={prompt => assistant.onAsk?.({ promptId: prompt.id, topic })}
|
|
111
|
-
/>
|
|
112
|
-
</div>
|
|
113
|
-
</AssistantRuntimeContext.Provider>
|
|
57
|
+
<div ref={ref}>
|
|
58
|
+
<AssistantAskPrompts
|
|
59
|
+
variant="card"
|
|
60
|
+
topic={topic}
|
|
61
|
+
count={count}
|
|
62
|
+
exclude={exclude}
|
|
63
|
+
enabled={reached}
|
|
64
|
+
title={title}
|
|
65
|
+
description={description}
|
|
66
|
+
onOpen={onOpen}
|
|
67
|
+
/>
|
|
68
|
+
</div>
|
|
114
69
|
);
|
|
115
70
|
}
|