@flamingo-stack/openframe-frontend-core 0.0.719 → 0.0.721
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/assets/index.cjs +6 -0
- package/dist/assets/index.d.ts +1 -0
- package/dist/assets/index.d.ts.map +1 -1
- package/dist/assets/index.js +5 -1
- package/dist/assets/mobile-app-qr.d.ts +17 -0
- package/dist/assets/mobile-app-qr.d.ts.map +1 -0
- package/dist/{chunk-2JLP2X2M.js → chunk-2P2M27EW.js} +2396 -2369
- package/dist/{chunk-IWCPXJEE.js → chunk-2TXGR7IK.js} +138 -63
- package/dist/{chunk-FVQJBII6.js → chunk-3LY7V3RF.js} +3 -3
- package/dist/{chunk-HCFNEKO3.cjs → chunk-3R32ZNA2.cjs} +46 -46
- package/dist/{chunk-WSDT7LYL.js → chunk-3UBWDR65.js} +1 -1
- package/dist/{chunk-NZ5BHDFZ.js → chunk-44Q6MV7C.js} +2 -2
- package/dist/{chunk-CMZ3QTFQ.js → chunk-4I3K6LEH.js} +24 -0
- package/dist/{chunk-ZXCZYMN4.cjs → chunk-4LY6R4TN.cjs} +137 -62
- package/dist/{chunk-EAHOB64X.js → chunk-57UTI7ZH.js} +106 -42
- package/dist/{chunk-XGHGGBM3.js → chunk-5QXE5HMV.js} +4 -4
- package/dist/{chunk-NYWESHOQ.cjs → chunk-6AJIOW3S.cjs} +57 -57
- package/dist/{chunk-4QFWGLZO.js → chunk-6YBUQAOH.js} +1 -1
- package/dist/{chunk-JHQ334WS.js → chunk-742BGXNX.js} +2 -0
- package/dist/{chunk-D6PMVWDI.js → chunk-74N742ZG.js} +6 -6
- package/dist/{chunk-AECTRMGQ.cjs → chunk-7GRPR7XA.cjs} +13 -1
- package/dist/{chunk-4HJRHEXD.cjs → chunk-7Z5FMQYR.cjs} +22 -22
- package/dist/chunk-AFTRPEXO.js +13 -0
- package/dist/{chunk-WBNAEYTQ.cjs → chunk-BYUIFT7Q.cjs} +6 -6
- package/dist/{chunk-LCLDFMDF.cjs → chunk-BYVVE7HP.cjs} +58 -58
- package/dist/{chunk-JPD4NXH6.js → chunk-CO3FOZIH.js} +1 -1
- package/dist/{chunk-E7YCPY7K.cjs → chunk-D2DH6VZL.cjs} +3 -3
- package/dist/{chunk-HWVUR735.cjs → chunk-DFM572VH.cjs} +2 -2
- package/dist/{chunk-YDPKUAP4.js → chunk-DJ4IFQNQ.js} +2 -0
- package/dist/{chunk-5U3KWQXP.js → chunk-E6SMMMKV.js} +13 -13
- package/dist/chunk-EWPR7662.cjs +13 -0
- package/dist/{chunk-SWAVO7BB.cjs → chunk-F7ORKKEF.cjs} +108 -108
- package/dist/{chunk-AGVXKD4K.js → chunk-FSQQZFVG.js} +12 -0
- package/dist/{chunk-ETYLOZOB.cjs → chunk-GCK75D7F.cjs} +109 -22
- package/dist/{chunk-ZKNZ2CXW.cjs → chunk-GGBYZKMS.cjs} +11 -11
- package/dist/{chunk-Y4MB25KW.js → chunk-GYAXV3MZ.js} +3 -3
- package/dist/{chunk-UWQWMGJ7.cjs → chunk-HJMSNOBT.cjs} +1040 -696
- package/dist/{chunk-DFQYAYBH.cjs → chunk-HQIOMFJH.cjs} +2 -0
- package/dist/{chunk-JH2SIWPQ.js → chunk-HXB7SEYG.js} +1611 -1267
- package/dist/{chunk-LE5WKGJV.cjs → chunk-I2254QN6.cjs} +16 -16
- package/dist/{chunk-PSVSEN4J.cjs → chunk-IMTY3GRR.cjs} +2 -0
- package/dist/{chunk-ILNN6QRW.js → chunk-JDBZ4ZHU.js} +3 -3
- package/dist/{chunk-GQ7JJUIT.cjs → chunk-K2JUJ37D.cjs} +25 -1
- package/dist/{chunk-6YPTUHM7.js → chunk-K76ACBR3.js} +3 -3
- package/dist/{chunk-2NASBO7O.js → chunk-KFOAV77K.js} +2 -2
- package/dist/{chunk-CU6NWO2M.js → chunk-LAXKEBDD.js} +14 -18
- package/dist/{chunk-5YAGX5DY.js → chunk-LGKMR7NZ.js} +7 -7
- package/dist/{chunk-E2V2EH2G.cjs → chunk-LODMNSQY.cjs} +16 -16
- package/dist/{chunk-4D6NMPOO.js → chunk-NJ5TBTJG.js} +7 -7
- package/dist/{chunk-TDZ76NCB.cjs → chunk-NMPVK53I.cjs} +29 -29
- package/dist/{chunk-TTVOLEUS.js → chunk-NPSJE7J3.js} +90 -3
- package/dist/{chunk-H5VIMBRN.cjs → chunk-O5EHOJZD.cjs} +4 -4
- package/dist/{chunk-KT3U3ZRP.cjs → chunk-OKE5BIQH.cjs} +8 -8
- package/dist/{chunk-DC4RTYWK.cjs → chunk-QLTXT562.cjs} +5 -5
- package/dist/{chunk-H5V7GRN5.js → chunk-QQCFPMAA.js} +5 -7
- package/dist/{chunk-7BREFV7G.cjs → chunk-QYS4YVEP.cjs} +34 -36
- package/dist/{chunk-7BCY77W6.js → chunk-RCI3PALX.js} +8 -10
- package/dist/{chunk-P3JMAMHP.js → chunk-REETLEL5.js} +1 -1
- package/dist/{chunk-4JIGEK7C.cjs → chunk-RJMWJAJO.cjs} +97 -33
- package/dist/{chunk-WS7WRR5Z.cjs → chunk-RQZQHLNS.cjs} +86 -90
- package/dist/{chunk-FFT56TYB.cjs → chunk-RUZIICXT.cjs} +2 -2
- package/dist/{chunk-AJHSQ5XK.js → chunk-S63QBBT7.js} +6 -6
- package/dist/{chunk-TELAANY6.js → chunk-UXU7Y5BH.js} +2 -2
- package/dist/{chunk-NQVS4G7G.cjs → chunk-UZTOI6FJ.cjs} +773 -746
- package/dist/{chunk-DFEPUYTF.cjs → chunk-VFICDEYJ.cjs} +45 -47
- package/dist/{chunk-LOZLHG2U.cjs → chunk-XE2K53GH.cjs} +20 -20
- package/dist/{chunk-QBCDSTLE.cjs → chunk-XXO52RZR.cjs} +26 -26
- package/dist/{chunk-TTEFN6VO.js → chunk-ZLUFEWZZ.js} +10 -10
- package/dist/{chunk-MA4Q5RBP.cjs → chunk-ZSOFLPQN.cjs} +9 -9
- package/dist/{chunk-KBJJQFYU.js → chunk-ZZEKXGFS.js} +2 -2
- package/dist/{cloud-tenants-FBUO66GW.cjs → cloud-tenants-HZHKRUIW.cjs} +16 -16
- package/dist/{cloud-tenants-DNBPNQNP.js → cloud-tenants-Q4TCJS5Y.js} +15 -15
- package/dist/components/calendar.cjs +4 -4
- package/dist/components/calendar.js +1 -1
- package/dist/components/case-studies/index.cjs +30 -30
- package/dist/components/case-studies/index.js +16 -16
- package/dist/components/chat/ask-prompts.d.ts +10 -2
- package/dist/components/chat/ask-prompts.d.ts.map +1 -1
- package/dist/components/chat/assistant-ask-prompts.d.ts +27 -0
- package/dist/components/chat/assistant-ask-prompts.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/dispatch.d.ts.map +1 -1
- package/dist/components/chat/hooks/index.d.ts +1 -0
- package/dist/components/chat/hooks/index.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-ask-prompts.d.ts +63 -0
- package/dist/components/chat/hooks/use-ask-prompts.d.ts.map +1 -0
- package/dist/components/chat/index.cjs +27 -10
- package/dist/components/chat/index.d.ts +1 -0
- package/dist/components/chat/index.d.ts.map +1 -1
- package/dist/components/chat/index.js +26 -9
- package/dist/components/chat/utils/icon-library.d.ts.map +1 -1
- package/dist/components/contact/index.cjs +17 -17
- package/dist/components/contact/index.js +16 -16
- package/dist/components/docs/index.cjs +20 -20
- package/dist/components/docs/index.js +19 -19
- package/dist/components/embeds/index.cjs +17 -17
- package/dist/components/embeds/index.js +16 -16
- package/dist/components/faq/faq-ask-card.d.ts +44 -0
- package/dist/components/faq/faq-ask-card.d.ts.map +1 -0
- package/dist/components/faq/faq-document-page.d.ts +5 -1
- package/dist/components/faq/faq-document-page.d.ts.map +1 -1
- package/dist/components/faq/faq-section.d.ts +18 -5
- package/dist/components/faq/faq-section.d.ts.map +1 -1
- package/dist/components/faq/index.cjs +19 -18
- package/dist/components/faq/index.d.ts +1 -0
- package/dist/components/faq/index.d.ts.map +1 -1
- package/dist/components/faq/index.js +18 -17
- package/dist/components/features/auth/auth-benefits-panel.d.ts.map +1 -1
- package/dist/components/features/index.cjs +16 -16
- package/dist/components/features/index.js +15 -15
- package/dist/components/help-center-pages/download-apps-page.d.ts +55 -0
- package/dist/components/help-center-pages/download-apps-page.d.ts.map +1 -0
- package/dist/components/help-center-pages/index.cjs +333 -119
- package/dist/components/help-center-pages/index.d.ts +1 -0
- package/dist/components/help-center-pages/index.d.ts.map +1 -1
- package/dist/components/help-center-pages/index.js +251 -37
- package/dist/components/help-center-pages/trust-center-page.d.ts +4 -1
- package/dist/components/help-center-pages/trust-center-page.d.ts.map +1 -1
- package/dist/components/index.cjs +131 -217
- package/dist/components/index.js +236 -322
- package/dist/components/meeting-scheduler/index.cjs +61 -62
- package/dist/components/meeting-scheduler/index.js +18 -19
- package/dist/components/navigation/index.cjs +19 -19
- package/dist/components/navigation/index.js +18 -18
- package/dist/components/navigation/mingo-ai-button.d.ts +1 -1
- package/dist/components/navigation/mingo-ai-button.d.ts.map +1 -1
- package/dist/components/onboarding-guides/index.cjs +19 -20
- package/dist/components/onboarding-guides/index.js +18 -19
- package/dist/components/product-demo/index.cjs +8 -8
- package/dist/components/product-demo/index.js +8 -8
- package/dist/components/related-content/index.cjs +7 -9
- package/dist/components/related-content/index.js +6 -8
- package/dist/components/tickets/index.cjs +21 -21
- package/dist/components/tickets/index.js +20 -20
- package/dist/components/ui/button/button.d.ts.map +1 -1
- package/dist/components/ui/file-manager/index.cjs +11 -11
- package/dist/components/ui/file-manager/index.js +1 -1
- package/dist/components/ui/index.cjs +34 -16
- package/dist/components/ui/index.d.ts +1 -0
- package/dist/components/ui/index.d.ts.map +1 -1
- package/dist/components/ui/index.js +33 -15
- package/dist/components/ui/mobile-app-qr.d.ts +23 -0
- package/dist/components/ui/mobile-app-qr.d.ts.map +1 -0
- package/dist/components/ui/store-badges.d.ts +7 -4
- package/dist/components/ui/store-badges.d.ts.map +1 -1
- package/dist/contexts/assistant-runtime-context.d.ts +74 -0
- package/dist/contexts/assistant-runtime-context.d.ts.map +1 -0
- package/dist/contexts/index.cjs +7 -1
- package/dist/contexts/index.d.ts +1 -0
- package/dist/contexts/index.d.ts.map +1 -1
- package/dist/contexts/index.js +6 -0
- package/dist/{devices-5A5TY4JD.cjs → devices-6XHUFIC6.cjs} +17 -17
- package/dist/{devices-PLDAEUSL.js → devices-NOUGVTES.js} +15 -15
- package/dist/hooks/index.cjs +10 -5
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +9 -4
- package/dist/hooks/use-downloads.d.ts +41 -0
- package/dist/hooks/use-downloads.d.ts.map +1 -0
- package/dist/index.cjs +80 -20
- package/dist/index.js +79 -19
- package/dist/{knowledge-G6Q5KRWV.js → knowledge-2YTUXQ2Z.js} +15 -15
- package/dist/{knowledge-FKTJ723Z.cjs → knowledge-ANEKPMO2.cjs} +17 -17
- package/dist/{logs-CJCXUBU6.cjs → logs-U3IB3HUM.cjs} +25 -25
- package/dist/{logs-QMV6XIC4.js → logs-WVQIONUT.js} +15 -15
- package/dist/{policies-G6D7AX6F.js → policies-66EGDW2H.js} +15 -15
- package/dist/{policies-QMRTK3CY.cjs → policies-UFRN2EQF.cjs} +20 -20
- package/dist/{remote-session-DBD6OSTX.js → remote-session-6WEUJZDC.js} +5 -5
- package/dist/{remote-session-2JVI7RVA.cjs → remote-session-A75X5NPC.cjs} +7 -7
- package/dist/{software-update-PNZMRDBT.cjs → software-update-2MHKL76D.cjs} +20 -20
- package/dist/{software-update-6BQM6BKH.js → software-update-3ZD3MXF3.js} +15 -15
- package/dist/{tickets-board-BHLPAVNJ.cjs → tickets-board-IFWMIZ35.cjs} +9 -9
- package/dist/{tickets-board-PYDNELSK.js → tickets-board-MYXG3V72.js} +4 -4
- package/dist/types/downloads.d.ts +67 -0
- package/dist/types/downloads.d.ts.map +1 -0
- package/dist/types/index.cjs +24 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +16 -1
- package/dist/utils/index.cjs +46 -2
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +40 -3
- package/dist/utils/list-url.d.ts.map +1 -1
- package/dist/utils/mobile-app.cjs +46 -0
- package/dist/utils/mobile-app.d.ts +40 -0
- package/dist/utils/mobile-app.d.ts.map +1 -0
- package/dist/utils/mobile-app.js +40 -0
- package/dist/utils/source-icons.d.ts.map +1 -1
- package/dist/utils/trial.d.ts +18 -0
- package/dist/utils/trial.d.ts.map +1 -0
- package/package.json +7 -1
- package/src/assets/index.ts +1 -0
- package/src/assets/mobile-app-qr.ts +19 -0
- package/src/components/chat/ask-prompts.tsx +25 -3
- package/src/components/chat/assistant-ask-prompts.tsx +78 -0
- package/src/components/chat/entity-cards/dispatch.tsx +38 -4
- package/src/components/chat/hooks/index.ts +12 -0
- package/src/components/chat/hooks/use-ask-prompts.ts +188 -0
- package/src/components/chat/index.ts +1 -0
- package/src/components/chat/utils/icon-library.tsx +6 -0
- package/src/components/faq/faq-ask-card.tsx +115 -0
- package/src/components/faq/faq-document-page.tsx +6 -1
- package/src/components/faq/faq-section.tsx +27 -5
- package/src/components/faq/index.ts +1 -0
- package/src/components/features/auth/auth-benefits-panel.tsx +5 -4
- package/src/components/help-center-pages/download-apps-page.tsx +364 -0
- package/src/components/help-center-pages/index.ts +1 -0
- package/src/components/help-center-pages/trust-center-page.tsx +5 -2
- package/src/components/navigation/mingo-ai-button.tsx +27 -6
- package/src/components/ui/button/button.tsx +3 -0
- package/src/components/ui/index.ts +1 -0
- package/src/components/ui/mobile-app-qr.tsx +40 -0
- package/src/components/ui/store-badges.tsx +17 -9
- package/src/contexts/assistant-runtime-context.tsx +84 -0
- package/src/contexts/index.ts +9 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/use-downloads.ts +138 -0
- package/src/types/downloads.ts +82 -0
- package/src/types/index.ts +1 -0
- package/src/utils/index.ts +3 -0
- package/src/utils/list-url.ts +4 -0
- package/src/utils/mobile-app.ts +57 -0
- package/src/utils/source-icons.ts +10 -2
- package/src/utils/trial.ts +19 -0
- package/dist/chunk-6X3O67OL.cjs +0 -89
- package/dist/chunk-AUMKE74M.cjs +0 -16
- package/dist/chunk-G3GRYDMS.js +0 -16
- package/dist/chunk-RR6PY34K.js +0 -89
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useMemo } from 'react';
|
|
4
|
+
import {
|
|
5
|
+
AssistantRuntimeContext,
|
|
6
|
+
useAssistantRuntime,
|
|
7
|
+
type AssistantOpenRequest,
|
|
8
|
+
} from '../../contexts/assistant-runtime-context';
|
|
9
|
+
import { AskPrompts, type AskPromptsProps } from './ask-prompts';
|
|
10
|
+
import {
|
|
11
|
+
assistantAvailable,
|
|
12
|
+
ASK_PROMPTS_DEFAULT_COUNT,
|
|
13
|
+
useAskPrompts,
|
|
14
|
+
useAssistantOpen,
|
|
15
|
+
useShownAskPrompts,
|
|
16
|
+
} from './hooks/use-ask-prompts';
|
|
17
|
+
|
|
18
|
+
export interface AssistantAskPromptsProps extends Pick<AskPromptsProps, 'align' | 'fadeColor' | 'className'> {
|
|
19
|
+
/** What this row is about: any string the host's questions endpoint knows. Reported with every click. */
|
|
20
|
+
topic?: string;
|
|
21
|
+
/** How many questions the row offers: the same number of slots on every load. */
|
|
22
|
+
count?: number;
|
|
23
|
+
/** The launcher's name. Absent: the assistant's configured name, else the launcher's default. */
|
|
24
|
+
label?: string;
|
|
25
|
+
/** Something else the row waits for (the host is still reading the name it will show). */
|
|
26
|
+
loading?: boolean;
|
|
27
|
+
/** How the chat is opened from THIS row. Absent: the assistant runtime's opener. */
|
|
28
|
+
onOpen?: (request: AssistantOpenRequest) => void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* A row of questions for the host's assistant, wired by itself: the launcher
|
|
33
|
+
* with the assistant's name and glyph, then the questions the host picked for
|
|
34
|
+
* this row's `topic`, each opening the chat and asking it. Everything comes
|
|
35
|
+
* from the nearest assistant runtime (`AssistantRuntimeContext`): whether a
|
|
36
|
+
* chat is there, the identity, the questions endpoint and the opener. While
|
|
37
|
+
* the pick is on its way the row is the SAME block (a chip skeleton in every
|
|
38
|
+
* slot), so nothing moves when the questions land. The row tells the page
|
|
39
|
+
* which questions it shows, so the FAQ's card never repeats one. Nothing
|
|
40
|
+
* renders with no chat, or once the pick has settled empty.
|
|
41
|
+
*/
|
|
42
|
+
export function AssistantAskPrompts({
|
|
43
|
+
topic,
|
|
44
|
+
count = ASK_PROMPTS_DEFAULT_COUNT,
|
|
45
|
+
label,
|
|
46
|
+
loading = false,
|
|
47
|
+
onOpen,
|
|
48
|
+
align,
|
|
49
|
+
fadeColor,
|
|
50
|
+
className,
|
|
51
|
+
}: AssistantAskPromptsProps) {
|
|
52
|
+
const assistant = useAssistantRuntime();
|
|
53
|
+
const { prompts, isLoading, supported } = useAskPrompts({ topic, count });
|
|
54
|
+
const open = useAssistantOpen(topic, onOpen);
|
|
55
|
+
const shown = assistantAvailable(assistant) && supported;
|
|
56
|
+
// Nothing to say where the row shows nothing; `null` while its pick is on its way.
|
|
57
|
+
useShownAskPrompts(shown ? (isLoading ? null : prompts.map(prompt => prompt.id)) : undefined);
|
|
58
|
+
// The row's launcher opens the same chat as its questions, with the row's topic: it reads the runtime.
|
|
59
|
+
const scoped = useMemo(() => (assistant ? { ...assistant, open } : null), [assistant, open]);
|
|
60
|
+
if (!assistantAvailable(assistant) || !supported) return null;
|
|
61
|
+
return (
|
|
62
|
+
<AssistantRuntimeContext.Provider value={scoped}>
|
|
63
|
+
<AskPrompts
|
|
64
|
+
prompts={prompts}
|
|
65
|
+
loading={isLoading || loading}
|
|
66
|
+
count={count}
|
|
67
|
+
source={assistant.source}
|
|
68
|
+
label={label ?? assistant.name ?? undefined}
|
|
69
|
+
icon={assistant.icon}
|
|
70
|
+
onOpen={open}
|
|
71
|
+
onAsk={prompt => assistant.onAsk?.({ promptId: prompt.id, topic })}
|
|
72
|
+
align={align}
|
|
73
|
+
fadeColor={fadeColor}
|
|
74
|
+
className={className}
|
|
75
|
+
/>
|
|
76
|
+
</AssistantRuntimeContext.Provider>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
@@ -56,6 +56,7 @@ import { faqItemAnchor } from '../../../utils/faq-anchor';
|
|
|
56
56
|
import { formatDateUTC as formatDate } from '../../../utils/format';
|
|
57
57
|
import { programMetaFormatters, programMetaLine } from '../../../utils/program-instant';
|
|
58
58
|
import { PROGRAM_META_RENDERERS } from '../../../utils/program-meta-renderers';
|
|
59
|
+
import { EntityIcon } from '../../icon-display';
|
|
59
60
|
import { MingoIcon } from '../../icons';
|
|
60
61
|
import { ArrowRightUpIcon } from '../../icons-v2-generated/arrows/arrow-right-up-icon';
|
|
61
62
|
import { ClickupLogoIcon } from '../../icons-v2-generated/brand-logos/clickup-logo-icon';
|
|
@@ -97,6 +98,7 @@ import { TrophyIcon } from '../../icons-v2-generated/sport/trophy-icon';
|
|
|
97
98
|
import { Rocket02Icon } from '../../icons-v2-generated/vehicles-and-delivery/rocket-02-icon';
|
|
98
99
|
import { TruckFastIcon } from '../../icons-v2-generated/vehicles-and-delivery/truck-fast-icon';
|
|
99
100
|
import type { ActionsMenuGroup } from '../../ui/actions-menu';
|
|
101
|
+
import { VendorIcon } from '../../vendor-icon';
|
|
100
102
|
import { useChatPanel } from '../chat-panel-context';
|
|
101
103
|
import type { ChatRef } from '../chat-ref.types';
|
|
102
104
|
import { useChatCardItem } from '../hooks/use-chat-card-item';
|
|
@@ -114,7 +116,7 @@ import { clickupTaskUrl } from '../utils/external-app-urls';
|
|
|
114
116
|
import { resolveIcon } from '../utils/icon-library';
|
|
115
117
|
import { computeIsNewTab, buildAnchorProps } from '../utils/nav-anchor-props';
|
|
116
118
|
import { readFetchedCardTitle } from '../utils/resolve-fetched-card-href';
|
|
117
|
-
import { getSourceLabel } from '../utils/source-icons';
|
|
119
|
+
import { getSourceLabel, SOURCE_ICON_NAMES } from '../utils/source-icons';
|
|
118
120
|
import { resolveSourceIcon, sourceRowCtxFromRuntime } from '../utils/source-row-cta';
|
|
119
121
|
import { BlockCard } from './block-card';
|
|
120
122
|
import { BlogCardSkeleton } from './blog-card';
|
|
@@ -1429,6 +1431,23 @@ function financialRegistryEntries(): Record<string, ChatCardRegistryEntry> {
|
|
|
1429
1431
|
);
|
|
1430
1432
|
}
|
|
1431
1433
|
|
|
1434
|
+
/** A string field the card route attached to a hydrated ref beside the `ChatRef` ones (`logo_url`, `icon_name`). */
|
|
1435
|
+
function refText(ref: ChatRef | undefined, key: string): string | null {
|
|
1436
|
+
const value = (ref as Record<string, unknown> | undefined)?.[key];
|
|
1437
|
+
return typeof value === 'string' && value.length > 0 ? value : null;
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
/** A vendor's own logo, through the shared `VendorIcon` (its logo rule and proxying); the directory's source icon without one. */
|
|
1441
|
+
function VendorGlyph({ title, logoUrl }: { title: string; logoUrl: string | null }) {
|
|
1442
|
+
if (!logoUrl) return <EntityIcon icon={{ name: SOURCE_ICON_NAMES.vendors }} size={24} />;
|
|
1443
|
+
return <VendorIcon vendor={{ title, logo_url: logoUrl }} size="xs" showBackground={false} />;
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
/** A website page's menu icon (the one its navigation entry draws), through `EntityIcon`; the site's own mark for a page without one. */
|
|
1447
|
+
function SitePageGlyph({ iconName }: { iconName: string | null }) {
|
|
1448
|
+
return <EntityIcon icon={{ name: iconName ?? SOURCE_ICON_NAMES['website-pages'] }} size={24} />;
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1432
1451
|
interface GitHubCardConfig {
|
|
1433
1452
|
label: string;
|
|
1434
1453
|
kind: GitHubActivityKind;
|
|
@@ -1443,7 +1462,12 @@ const GITHUB_CARD_CONFIGS: Record<string, GitHubCardConfig> = {
|
|
|
1443
1462
|
};
|
|
1444
1463
|
interface GlyphCardConfig {
|
|
1445
1464
|
label: string;
|
|
1446
|
-
|
|
1465
|
+
/**
|
|
1466
|
+
* The card's leading glyph. It is handed the hydrated ref, so a type whose
|
|
1467
|
+
* records carry their own mark (a vendor's logo, a page's menu icon) draws
|
|
1468
|
+
* that and keeps a generic glyph for a record without one.
|
|
1469
|
+
*/
|
|
1470
|
+
icon: (ref?: ChatRef) => React.ReactNode;
|
|
1447
1471
|
/**
|
|
1448
1472
|
* Video-bearing ref types: render the media card (cover from the ref's
|
|
1449
1473
|
* `metadata.videoPoster`, else the host's OG placeholder; the type label as the
|
|
@@ -1480,6 +1504,11 @@ const REF_GLYPH_CARD_CONFIGS: Record<string, GlyphCardConfig> = {
|
|
|
1480
1504
|
// A change set: pull requests across repositories declared as one change, with the ClickUp tasks and design
|
|
1481
1505
|
// docs its pull requests are attached to. ONE card for it wherever a set is shown (chat, a design doc's page).
|
|
1482
1506
|
change_set: { label: 'Change set', icon: () => <CodingMergeIcon size={24} /> },
|
|
1507
|
+
// A page of the public website (pricing, product, legal, and every page the navigation lists), read live
|
|
1508
|
+
// from the data the page shows. A glyph card: a page has no cover of its own, and the card opens the page.
|
|
1509
|
+
site_page: { label: 'Website page', icon: ref => <SitePageGlyph iconName={refText(ref, 'icon_name')} /> },
|
|
1510
|
+
// A vendor (product) of the OpenMSP directory. A glyph card; it opens the vendor's page there.
|
|
1511
|
+
vendor: { label: 'Vendor', icon: ref => <VendorGlyph title={ref?.title ?? ''} logoUrl={refText(ref, 'logo_url')} /> },
|
|
1483
1512
|
};
|
|
1484
1513
|
function refGlyphRegistryEntries(): Record<string, ChatCardRegistryEntry> {
|
|
1485
1514
|
return registryEntries(REF_GLYPH_CARD_CONFIGS, (cfg, docType) =>
|
|
@@ -1492,7 +1521,7 @@ function refGlyphRegistryEntries(): Record<string, ChatCardRegistryEntry> {
|
|
|
1492
1521
|
displayRef.metadata?.videoPoster,
|
|
1493
1522
|
opts.extras?.buildOgPlaceholderUrl?.(displayRef.title ?? '') ?? null,
|
|
1494
1523
|
)}
|
|
1495
|
-
fallbackIcon={cfg.icon()}
|
|
1524
|
+
fallbackIcon={cfg.icon(displayRef)}
|
|
1496
1525
|
status={{ label: cfg.label, variant: 'grey' }}
|
|
1497
1526
|
chatRef={displayRef}
|
|
1498
1527
|
isNewTab={opts.isNewTab}
|
|
@@ -1500,7 +1529,12 @@ function refGlyphRegistryEntries(): Record<string, ChatCardRegistryEntry> {
|
|
|
1500
1529
|
menuAriaLabel={`${cfg.label} actions`}
|
|
1501
1530
|
/>
|
|
1502
1531
|
) : (
|
|
1503
|
-
<GlyphChatCard
|
|
1532
|
+
<GlyphChatCard
|
|
1533
|
+
chatRef={displayRef}
|
|
1534
|
+
icon={cfg.icon(displayRef)}
|
|
1535
|
+
isNewTab={opts.isNewTab}
|
|
1536
|
+
discuss={opts.discuss}
|
|
1537
|
+
/>
|
|
1504
1538
|
),
|
|
1505
1539
|
),
|
|
1506
1540
|
);
|
|
@@ -39,3 +39,15 @@ export * from './use-embedded-chat';
|
|
|
39
39
|
// `ChatRuntime.endpoints`, so hub vs. embedders share one image-proxy
|
|
40
40
|
// resolver.
|
|
41
41
|
export * from './use-proxied-image-url';
|
|
42
|
+
export {
|
|
43
|
+
ASK_PROMPTS_DEFAULT_COUNT,
|
|
44
|
+
assistantAvailable,
|
|
45
|
+
buildAskPromptsUrl,
|
|
46
|
+
useAskPrompts,
|
|
47
|
+
useAssistantOpen,
|
|
48
|
+
useShownAskPromptIds,
|
|
49
|
+
useShownAskPrompts,
|
|
50
|
+
type AskPromptsResponse,
|
|
51
|
+
type UseAskPromptsOptions,
|
|
52
|
+
type UseAskPromptsResult,
|
|
53
|
+
} from './use-ask-prompts';
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useCallback, useId, useLayoutEffect, useSyncExternalStore } from 'react';
|
|
4
|
+
import {
|
|
5
|
+
useAssistantRuntime,
|
|
6
|
+
type AssistantOpenRequest,
|
|
7
|
+
type AssistantRuntime,
|
|
8
|
+
} from '../../../contexts/assistant-runtime-context';
|
|
9
|
+
import { useSelfFetch } from '../../../hooks/use-self-fetch';
|
|
10
|
+
import { openAskAi } from '../../navigation/mingo-ai-button';
|
|
11
|
+
import type { AskPrompt } from '../ask-prompts';
|
|
12
|
+
|
|
13
|
+
/** The answer of a host's questions endpoint (`AssistantRuntime.askPromptsUrl`). */
|
|
14
|
+
export interface AskPromptsResponse {
|
|
15
|
+
prompts: AskPrompt[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** How many questions an "ask" surface offers when nothing states a number. */
|
|
19
|
+
export const ASK_PROMPTS_DEFAULT_COUNT = 3;
|
|
20
|
+
|
|
21
|
+
/** The questions URL: the host's endpoint with the surface's count, topic and exclusions. */
|
|
22
|
+
export function buildAskPromptsUrl(
|
|
23
|
+
base: string,
|
|
24
|
+
{ count, topic, exclude = [] }: { count: number; topic?: string; exclude?: readonly string[] },
|
|
25
|
+
): string {
|
|
26
|
+
const params = new URLSearchParams({ count: String(count) });
|
|
27
|
+
if (topic) params.set('section', topic);
|
|
28
|
+
if (exclude.length > 0) params.set('exclude', exclude.join(','));
|
|
29
|
+
return `${base}${base.includes('?') ? '&' : '?'}${params.toString()}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** An "ask" surface has something to open: a runtime is mounted and its chat is there. */
|
|
33
|
+
export function assistantAvailable(assistant: AssistantRuntime | null): assistant is AssistantRuntime {
|
|
34
|
+
return !!assistant?.available;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface UseAskPromptsOptions {
|
|
38
|
+
/**
|
|
39
|
+
* What the surface is about: ANY string the host's endpoint knows (sent as
|
|
40
|
+
* `section`). Questions written for it are picked first. Absent: general ones.
|
|
41
|
+
*/
|
|
42
|
+
topic?: string;
|
|
43
|
+
count?: number;
|
|
44
|
+
/** Ids of questions never to pick (the ones the page already shows). */
|
|
45
|
+
exclude?: readonly string[];
|
|
46
|
+
/** False: nothing is fetched yet (the surface is not ready to pick). Default true. */
|
|
47
|
+
enabled?: boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface UseAskPromptsResult {
|
|
51
|
+
prompts: AskPrompt[];
|
|
52
|
+
/** True until the pick has landed (or failed). False where nothing is fetched. */
|
|
53
|
+
isLoading: boolean;
|
|
54
|
+
/** The host has a questions endpoint and a chat to ask: questions can exist here. */
|
|
55
|
+
supported: boolean;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The questions an "ask" surface offers, picked by the HOST for this visit
|
|
60
|
+
* (`AssistantRuntime.askPromptsUrl`). One pick per mount: read once, never
|
|
61
|
+
* cached, so a visitor keeps the same questions while the page is open and
|
|
62
|
+
* gets a new pick next time. A failed read leaves the list empty. No chat, or
|
|
63
|
+
* no endpoint: nothing is fetched and the list is empty.
|
|
64
|
+
*/
|
|
65
|
+
export function useAskPrompts({
|
|
66
|
+
topic,
|
|
67
|
+
count = ASK_PROMPTS_DEFAULT_COUNT,
|
|
68
|
+
exclude,
|
|
69
|
+
enabled = true,
|
|
70
|
+
}: UseAskPromptsOptions = {}): UseAskPromptsResult {
|
|
71
|
+
const assistant = useAssistantRuntime();
|
|
72
|
+
const base = assistantAvailable(assistant) ? assistant.askPromptsUrl : undefined;
|
|
73
|
+
const url = base && enabled ? buildAskPromptsUrl(base, { count, topic, exclude }) : null;
|
|
74
|
+
const { data, isLoading } = useSelfFetch<AskPromptsResponse>(url);
|
|
75
|
+
return { prompts: data?.prompts ?? [], isLoading: !!base && (!enabled || isLoading), supported: !!base };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* How an "ask" surface opens the chat and asks it: its own `onOpen`, else the
|
|
80
|
+
* nearest assistant runtime's `open`, else the `ask-ai:open` event of the
|
|
81
|
+
* runtime's source (the mounted `EmbeddableChat`). The surface's topic rides
|
|
82
|
+
* every request, so a host with several chats can tell them apart.
|
|
83
|
+
*/
|
|
84
|
+
export function useAssistantOpen(
|
|
85
|
+
topic?: string,
|
|
86
|
+
onOpen?: (request: AssistantOpenRequest) => void,
|
|
87
|
+
): (request: { prompt?: string }) => void {
|
|
88
|
+
const assistant = useAssistantRuntime();
|
|
89
|
+
const runtimeOpen = assistant?.open;
|
|
90
|
+
const source = assistant?.source;
|
|
91
|
+
return useCallback(
|
|
92
|
+
(request: { prompt?: string }) => {
|
|
93
|
+
const full: AssistantOpenRequest = { ...request, topic };
|
|
94
|
+
if (onOpen) onOpen(full);
|
|
95
|
+
else if (runtimeOpen) runtimeOpen(full);
|
|
96
|
+
else openAskAi(source, request);
|
|
97
|
+
},
|
|
98
|
+
[onOpen, runtimeOpen, source, topic],
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ─── The questions the page already shows ────────────────────────────────────
|
|
103
|
+
// Every surface that shows questions says which ones (`useShownAskPrompts`), so
|
|
104
|
+
// a later surface (the FAQ's card) never repeats one, with no wiring by the
|
|
105
|
+
// host. One list per QUESTIONS ENDPOINT (the runtime's `askPromptsUrl`): two
|
|
106
|
+
// assistants on one page read different questions, and never leave out each
|
|
107
|
+
// other's ids. A module store, read through React's external-store hook.
|
|
108
|
+
// `null` for a surface = it is still picking.
|
|
109
|
+
|
|
110
|
+
const NO_IDS: readonly string[] = [];
|
|
111
|
+
|
|
112
|
+
/** One endpoint's list: which questions each surface shows, and who is listening. */
|
|
113
|
+
interface ShownList {
|
|
114
|
+
set(surface: string, ids: readonly string[] | null): void;
|
|
115
|
+
remove(surface: string): void;
|
|
116
|
+
subscribe(listener: () => void): () => void;
|
|
117
|
+
read(): readonly string[] | null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function createShownList(): ShownList {
|
|
121
|
+
const bySurface = new Map<string, readonly string[] | null>();
|
|
122
|
+
const listeners = new Set<() => void>();
|
|
123
|
+
let snapshot: readonly string[] | null = NO_IDS;
|
|
124
|
+
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
|
+
for (const listener of listeners) listener();
|
|
131
|
+
};
|
|
132
|
+
return {
|
|
133
|
+
set(surface, ids) {
|
|
134
|
+
bySurface.set(surface, ids);
|
|
135
|
+
publish();
|
|
136
|
+
},
|
|
137
|
+
remove(surface) {
|
|
138
|
+
bySurface.delete(surface);
|
|
139
|
+
publish();
|
|
140
|
+
},
|
|
141
|
+
subscribe(listener) {
|
|
142
|
+
listeners.add(listener);
|
|
143
|
+
return () => listeners.delete(listener);
|
|
144
|
+
},
|
|
145
|
+
read: () => snapshot,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const shownLists = new Map<string, ShownList>();
|
|
150
|
+
|
|
151
|
+
/** The list a surface belongs to: the questions endpoint of its assistant runtime. */
|
|
152
|
+
function useShownList(): ShownList {
|
|
153
|
+
const scope = useAssistantRuntime()?.askPromptsUrl ?? '';
|
|
154
|
+
let list = shownLists.get(scope);
|
|
155
|
+
if (!list) {
|
|
156
|
+
list = createShownList();
|
|
157
|
+
shownLists.set(scope, list);
|
|
158
|
+
}
|
|
159
|
+
return list;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Say which questions this surface shows. `null`: it is still picking, so a
|
|
164
|
+
* surface that must not repeat them waits. Pass `undefined` to say nothing
|
|
165
|
+
* (the surface shows no questions here).
|
|
166
|
+
*/
|
|
167
|
+
export function useShownAskPrompts(ids: readonly string[] | null | undefined): void {
|
|
168
|
+
const surface = useId();
|
|
169
|
+
const list = useShownList();
|
|
170
|
+
const key = ids === undefined ? undefined : ids === null ? null : ids.join(',');
|
|
171
|
+
// A layout effect: the list holds this surface before any reader's own effects run.
|
|
172
|
+
useLayoutEffect(() => {
|
|
173
|
+
if (key === undefined) return undefined;
|
|
174
|
+
list.set(surface, key === null ? null : key ? key.split(',') : NO_IDS);
|
|
175
|
+
return () => list.remove(surface);
|
|
176
|
+
}, [list, surface, key]);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** The ids of the questions the page's other surfaces of the same assistant show; `null` while one is still picking. */
|
|
180
|
+
export function useShownAskPromptIds(): readonly string[] | null {
|
|
181
|
+
const list = useShownList();
|
|
182
|
+
return useSyncExternalStore(list.subscribe, list.read, () => NO_IDS);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Tests only: forget every surface. */
|
|
186
|
+
export function resetShownAskPrompts(): void {
|
|
187
|
+
shownLists.clear();
|
|
188
|
+
}
|
|
@@ -24,6 +24,7 @@ export * from './chat-message-skeleton';
|
|
|
24
24
|
|
|
25
25
|
export * from './quick-action-chip';
|
|
26
26
|
export * from './ask-prompts';
|
|
27
|
+
export * from './assistant-ask-prompts';
|
|
27
28
|
export * from './quick-action-marquee';
|
|
28
29
|
export * from './quick-action-wall';
|
|
29
30
|
export * from './chat-ticket-list';
|
|
@@ -91,6 +91,11 @@ const OpenmspLogoIcon: IconComponent = ({ size = 16, className }) => (
|
|
|
91
91
|
/>
|
|
92
92
|
);
|
|
93
93
|
const FlamingoLogoIcon = sizedLogo(FlamingoLogo);
|
|
94
|
+
// The same mark as a GLYPH: it takes the colour of the slot it sits in, like every icon beside it (a source
|
|
95
|
+
// chip, a command row, a card), where the brand pink would be the one coloured icon in the list.
|
|
96
|
+
const FlamingoLogoGreyIcon: IconComponent = ({ size = 16, className, color }) => (
|
|
97
|
+
<FlamingoLogo className={className} fill={color ?? 'currentColor'} style={{ width: size, height: size }} />
|
|
98
|
+
);
|
|
94
99
|
// The two community marks size themselves through a `size` prop, the resolver's own contract.
|
|
95
100
|
// Major League GitHub draws its mark in the theme's text colour on its own site
|
|
96
101
|
// (its header, hero and footer), never in an accent: so does every glyph of it.
|
|
@@ -174,6 +179,7 @@ export const ICON_ALIASES: Record<string, IconComponent> = {
|
|
|
174
179
|
'tmcg-logo': TmcgLogoIcon,
|
|
175
180
|
flamingo: FlamingoLogoIcon,
|
|
176
181
|
'flamingo-logo': FlamingoLogoIcon,
|
|
182
|
+
'flamingo-logo-grey': FlamingoLogoGreyIcon,
|
|
177
183
|
megaphone: Megaphone,
|
|
178
184
|
bell: Bell,
|
|
179
185
|
info: Info,
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useMemo, useState } from 'react';
|
|
4
|
+
import {
|
|
5
|
+
AssistantRuntimeContext,
|
|
6
|
+
useAssistantRuntime,
|
|
7
|
+
type AssistantOpenRequest,
|
|
8
|
+
type AssistantRuntime,
|
|
9
|
+
} from '../../contexts/assistant-runtime-context';
|
|
10
|
+
import { useInView } from '../../hooks/ui/use-in-view';
|
|
11
|
+
import { AskCard } from '../chat/ask-prompts';
|
|
12
|
+
import {
|
|
13
|
+
assistantAvailable,
|
|
14
|
+
ASK_PROMPTS_DEFAULT_COUNT,
|
|
15
|
+
useAskPrompts,
|
|
16
|
+
useAssistantOpen,
|
|
17
|
+
useShownAskPromptIds,
|
|
18
|
+
} from '../chat/hooks/use-ask-prompts';
|
|
19
|
+
|
|
20
|
+
/** The card beside a FAQ, as a host may shape it. Every field is optional. */
|
|
21
|
+
export interface FaqAskOptions {
|
|
22
|
+
/**
|
|
23
|
+
* What this FAQ is about: ANY string the host's questions endpoint knows (it
|
|
24
|
+
* is sent as `section`, and reported with every click). Questions written
|
|
25
|
+
* for the topic are picked first. Whoever renders the FAQ passes it. Absent:
|
|
26
|
+
* the entity the FAQ is attached to (`FaqSection`'s `entityType`). With
|
|
27
|
+
* neither, the FAQ has no card: the lib names no topic of its own.
|
|
28
|
+
*/
|
|
29
|
+
topic?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Ids of questions never to offer. Absent: the ones the page's other "ask"
|
|
32
|
+
* surfaces show (`AssistantAskPrompts` rows say so themselves), which the
|
|
33
|
+
* card waits for. `null`: wait.
|
|
34
|
+
*/
|
|
35
|
+
exclude?: readonly string[] | null;
|
|
36
|
+
/** How many questions the card offers. Absent: the runtime's, else three. */
|
|
37
|
+
count?: number;
|
|
38
|
+
/** The card's wording. `{assistant}` is replaced by the assistant's name. Absent: the runtime's, else the lib's. */
|
|
39
|
+
title?: string;
|
|
40
|
+
description?: string;
|
|
41
|
+
/**
|
|
42
|
+
* How the chat is opened and asked from THIS card. Absent: the assistant
|
|
43
|
+
* runtime's `open`, else the `ask-ai:open` event of its source. A FAQ that
|
|
44
|
+
* sits beside its own chat (an embedded one) passes that chat's opener.
|
|
45
|
+
*/
|
|
46
|
+
onOpen?: (request: AssistantOpenRequest) => void;
|
|
47
|
+
}
|
|
48
|
+
|
|
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
|
+
/** How far below the viewport the card is when it picks its questions. */
|
|
56
|
+
const PICK_ROOT_MARGIN = '600px';
|
|
57
|
+
|
|
58
|
+
const fillAssistant = (text: string, name: string) => text.split('{assistant}').join(name);
|
|
59
|
+
|
|
60
|
+
/** True when a FAQ under this runtime shows the card: a chat to open, and a name for the card's words. */
|
|
61
|
+
export function faqAskCardShown(assistant: AssistantRuntime | null): assistant is AssistantRuntime {
|
|
62
|
+
return assistantAvailable(assistant) && !!assistant.name;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The "ask the assistant" card of a FAQ: the assistant's identity and the
|
|
67
|
+
* chat's opener from the nearest assistant runtime, its questions from the
|
|
68
|
+
* host's endpoint. It picks ONCE, when the card first comes near the viewport:
|
|
69
|
+
* by then the page above has said which questions it shows itself, and a
|
|
70
|
+
* visitor who never reaches the FAQ costs no request. The card is the same
|
|
71
|
+
* height before and after the questions arrive. A host with no questions
|
|
72
|
+
* endpoint gets the launcher with no questions.
|
|
73
|
+
*/
|
|
74
|
+
export function FaqAskCard({ topic, exclude, count, title, description, onOpen }: FaqAskOptions & { topic: string }) {
|
|
75
|
+
const assistant = useAssistantRuntime();
|
|
76
|
+
const { ref, inView } = useInView<HTMLDivElement>({ rootMargin: PICK_ROOT_MARGIN });
|
|
77
|
+
const [reached, setReached] = useState(false);
|
|
78
|
+
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
|
+
return (
|
|
97
|
+
<AssistantRuntimeContext.Provider value={scoped}>
|
|
98
|
+
<div ref={ref}>
|
|
99
|
+
<AskCard
|
|
100
|
+
title={fillAssistant(title ?? assistant.askCard?.title ?? DEFAULT_ASK_CARD.title, name)}
|
|
101
|
+
description={cardDescription ? fillAssistant(cardDescription, name) : undefined}
|
|
102
|
+
prompts={prompts}
|
|
103
|
+
// Fixed slots, one question per row: chip skeletons until the pick lands, the same box after.
|
|
104
|
+
count={supported ? slots : 0}
|
|
105
|
+
loading={isLoading}
|
|
106
|
+
source={assistant.source}
|
|
107
|
+
label={name}
|
|
108
|
+
icon={assistant.icon}
|
|
109
|
+
onOpen={open}
|
|
110
|
+
onAsk={prompt => assistant.onAsk?.({ promptId: prompt.id, topic })}
|
|
111
|
+
/>
|
|
112
|
+
</div>
|
|
113
|
+
</AssistantRuntimeContext.Provider>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
import { useRouter } from '../../embed-shims/next-navigation';
|
|
24
24
|
import type { Faq } from '../../types/faq';
|
|
25
25
|
import { PageShell, PageLayout } from '../ui';
|
|
26
|
-
import { FaqSection } from './faq-section';
|
|
26
|
+
import { FaqSection, type FaqSectionProps } from './faq-section';
|
|
27
27
|
import type { FaqSchemaOptions } from './json-ld';
|
|
28
28
|
|
|
29
29
|
export interface FaqDocumentPageProps {
|
|
@@ -54,6 +54,9 @@ export interface FaqDocumentPageProps {
|
|
|
54
54
|
entityId?: number | string;
|
|
55
55
|
/** Minimum FAQ count before the section renders (forwarded to `FaqSection`). */
|
|
56
56
|
minResults?: number;
|
|
57
|
+
/** The "ask the assistant" card beside the questions (forwarded to `FaqSection`): `false` switches it off, an
|
|
58
|
+
* object states its topic, wording and the chat it opens. */
|
|
59
|
+
ask?: FaqSectionProps['ask'];
|
|
57
60
|
}
|
|
58
61
|
|
|
59
62
|
export function FaqDocumentPage({
|
|
@@ -67,6 +70,7 @@ export function FaqDocumentPage({
|
|
|
67
70
|
entityType,
|
|
68
71
|
entityId,
|
|
69
72
|
minResults,
|
|
73
|
+
ask,
|
|
70
74
|
shell = true,
|
|
71
75
|
}: FaqDocumentPageProps) {
|
|
72
76
|
const router = useRouter();
|
|
@@ -93,6 +97,7 @@ export function FaqDocumentPage({
|
|
|
93
97
|
entityType={entityType}
|
|
94
98
|
entityId={entityId}
|
|
95
99
|
minResults={minResults}
|
|
100
|
+
ask={ask}
|
|
96
101
|
/>
|
|
97
102
|
</PageLayout>
|
|
98
103
|
);
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import type React from 'react';
|
|
4
4
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
5
|
+
import { useAssistantRuntime } from '../../contexts/assistant-runtime-context';
|
|
5
6
|
import Link from '../../embed-shims/next-link';
|
|
6
7
|
import { useScrollToHash } from '../../hooks/use-scroll-to-hash';
|
|
7
8
|
import { useSelfFetch } from '../../hooks/use-self-fetch';
|
|
@@ -13,6 +14,7 @@ import { navigateSamePageHash, STICKY_HEADER_OFFSET_PX } from '../../utils/same-
|
|
|
13
14
|
import { buildSuggestionUrl } from '../../utils/suggestion-url';
|
|
14
15
|
import { FaqAccordion, type FaqItem } from '../faq-accordion';
|
|
15
16
|
import { SECTION_HEADING_CLASS } from '../layout/page-heading';
|
|
17
|
+
import { FaqAskCard, faqAskCardShown, type FaqAskOptions } from './faq-ask-card';
|
|
16
18
|
import { buildFaqJsonLdFromFaqs, type FaqSchemaOptions } from './json-ld';
|
|
17
19
|
|
|
18
20
|
export interface FaqSectionProps {
|
|
@@ -48,12 +50,24 @@ export interface FaqSectionProps {
|
|
|
48
50
|
* ('' = same-origin relative). */
|
|
49
51
|
apiBaseUrl?: string;
|
|
50
52
|
/**
|
|
51
|
-
* A block beside the questions
|
|
52
|
-
* content `lg` step the heading, the category nav and this block
|
|
53
|
-
* column and the questions the right one; below it the block
|
|
54
|
-
*
|
|
53
|
+
* A block beside the questions, in place of the "ask the assistant" card.
|
|
54
|
+
* From the content `lg` step the heading, the category nav and this block
|
|
55
|
+
* form a left column and the questions the right one; below it the block
|
|
56
|
+
* follows the questions.
|
|
55
57
|
*/
|
|
56
58
|
aside?: React.ReactNode;
|
|
59
|
+
/**
|
|
60
|
+
* The "ask the assistant" card beside the questions. Shown wherever the
|
|
61
|
+
* host's assistant runtime says a chat is there to open
|
|
62
|
+
* (`AssistantRuntimeContext`: its name, glyph, questions endpoint and
|
|
63
|
+
* opener) AND the FAQ has a topic: `ask.topic` (any string the host's
|
|
64
|
+
* questions endpoint knows), else the `entityType` the FAQ is attached to.
|
|
65
|
+
* No runtime or no topic: the FAQ is the single column it always was; the
|
|
66
|
+
* lib names no topic of its own. `false` switches the card off; the object
|
|
67
|
+
* also states the questions to leave out, the wording and the chat it opens
|
|
68
|
+
* (`onOpen`). An `aside` takes the card's place.
|
|
69
|
+
*/
|
|
70
|
+
ask?: false | FaqAskOptions;
|
|
57
71
|
}
|
|
58
72
|
|
|
59
73
|
const DEFAULT_HEADING_TEXT = 'Frequently Asked Questions';
|
|
@@ -392,8 +406,16 @@ export function FaqSection({
|
|
|
392
406
|
className,
|
|
393
407
|
minResults,
|
|
394
408
|
apiBaseUrl = '',
|
|
395
|
-
aside,
|
|
409
|
+
aside: asideProp,
|
|
410
|
+
ask,
|
|
396
411
|
}: FaqSectionProps) {
|
|
412
|
+
const assistant = useAssistantRuntime();
|
|
413
|
+
// The host's own block wins; else the card, where a chat is there to open and the FAQ has a topic
|
|
414
|
+
// (an empty topic is no topic, as the questions request reads it).
|
|
415
|
+
const askTopic = ask === false ? undefined : ask?.topic || entityType;
|
|
416
|
+
const aside =
|
|
417
|
+
asideProp ??
|
|
418
|
+
(ask !== false && askTopic && faqAskCardShown(assistant) ? <FaqAskCard {...ask} topic={askTopic} /> : undefined);
|
|
397
419
|
const url = buildFaqsUrl(entityType, entityId, minResults, apiBaseUrl);
|
|
398
420
|
// Memoized — useSelfFetch re-syncs on [initialData]; a fresh per-render
|
|
399
421
|
// wrapper object would setState-loop under re-rendering parents.
|
|
@@ -11,3 +11,4 @@
|
|
|
11
11
|
// block and exposed via "./components/faq/json-ld" in package.json#exports.)
|
|
12
12
|
export { FaqSection, type FaqSectionProps } from './faq-section';
|
|
13
13
|
export { FaqDocumentPage, type FaqDocumentPageProps } from './faq-document-page';
|
|
14
|
+
export { FaqAskCard, type FaqAskOptions } from './faq-ask-card';
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import type { ReactNode } from 'react';
|
|
4
4
|
import { cn } from '../../../utils/cn';
|
|
5
|
+
import { TRIAL_LABELS } from '../../../utils/trial';
|
|
5
6
|
import { OpenFrameLogo } from '../../icons';
|
|
6
7
|
import { FileOffIcon } from '../../icons-v2-generated/documents/file-off-icon';
|
|
7
8
|
import { CreditCardXmarkIcon } from '../../icons-v2-generated/finance/credit-card-xmark-icon';
|
|
@@ -29,10 +30,10 @@ const DEFAULT_DESCRIPTION =
|
|
|
29
30
|
'All your core ops in one place - built for MSPs who are done duct-taping tools together. Unified stack, AI-ready, no vendor tax. Just solid software that lets you run lean and fast.';
|
|
30
31
|
|
|
31
32
|
const DEFAULT_BENEFITS: AuthBenefit[] = [
|
|
32
|
-
{ icon: <CreditCardXmarkIcon />, label:
|
|
33
|
-
{ icon: <XmarkAltIcon />, label:
|
|
34
|
-
{ icon: <FlaskVialIcon />, label:
|
|
35
|
-
{ icon: <FileOffIcon />, label:
|
|
33
|
+
{ icon: <CreditCardXmarkIcon />, label: TRIAL_LABELS.noCard },
|
|
34
|
+
{ icon: <XmarkAltIcon />, label: TRIAL_LABELS.cancel },
|
|
35
|
+
{ icon: <FlaskVialIcon />, label: TRIAL_LABELS.length },
|
|
36
|
+
{ icon: <FileOffIcon />, label: TRIAL_LABELS.noContract },
|
|
36
37
|
];
|
|
37
38
|
|
|
38
39
|
/**
|