@carlonicora/nextjs-jsonapi 3.12.0 → 3.13.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/dist/{AssistantInterface-DwdBzS9f.d.ts → AssistantInterface-RvZzlcA_.d.ts} +2 -0
- package/dist/{AssistantInterface-DMh-zApr.d.mts → AssistantInterface-jL56dyNc.d.mts} +2 -0
- package/dist/{AssistantMessageInterface-0zyVingf.d.ts → AssistantMessageInterface-CuaMDs2i.d.ts} +1 -1
- package/dist/{AssistantMessageInterface-ddyG114R.d.mts → AssistantMessageInterface-Du3ShThp.d.mts} +1 -1
- package/dist/{BlockNoteEditor-6G27S6P3.js → BlockNoteEditor-CFVSHS46.js} +19 -19
- package/dist/{BlockNoteEditor-6G27S6P3.js.map → BlockNoteEditor-CFVSHS46.js.map} +1 -1
- package/dist/{BlockNoteEditor-CR5KH4AI.mjs → BlockNoteEditor-SERY337S.mjs} +4 -4
- package/dist/{BlockNoteViewer-5MITJ7AD.js → BlockNoteViewer-CWFPYYIU.js} +3 -3
- package/dist/{BlockNoteViewer-5MITJ7AD.js.map → BlockNoteViewer-CWFPYYIU.js.map} +1 -1
- package/dist/{BlockNoteViewer-NJKFUNXP.mjs → BlockNoteViewer-P2VKEFRN.mjs} +2 -2
- package/dist/{ai-connection.module-28f-hux0.d.mts → HandbookSectionInterface-BOxIdBdd.d.mts} +54 -1
- package/dist/{ai-connection.module-Bqwh_DnR.d.ts → HandbookSectionInterface-CAXxbDw0.d.ts} +54 -1
- package/dist/{auth.interface-B6ZQLGjS.d.ts → auth.interface-BC1jp_ni.d.ts} +1 -1
- package/dist/{auth.interface-jYD8Gc4P.d.mts → auth.interface-dR2-6gPM.d.mts} +1 -1
- package/dist/billing/index.js +401 -401
- package/dist/billing/index.mjs +3 -3
- package/dist/{chunk-L7DZSTFU.js → chunk-IB7DTXWG.js} +846 -129
- package/dist/chunk-IB7DTXWG.js.map +1 -0
- package/dist/{chunk-OKGGYCO5.js → chunk-LVE3BJ6K.js} +2088 -1134
- package/dist/chunk-LVE3BJ6K.js.map +1 -0
- package/dist/{chunk-Q2BREXH3.mjs → chunk-OSFYCSTW.mjs} +724 -7
- package/dist/chunk-OSFYCSTW.mjs.map +1 -0
- package/dist/{chunk-JEGL7KHK.mjs → chunk-UQXE3SME.mjs} +2081 -1127
- package/dist/chunk-UQXE3SME.mjs.map +1 -0
- package/dist/{chunk-HVNEWDRF.mjs → chunk-VZKJIAND.mjs} +2 -2
- package/dist/{chunk-4ETVCRQD.js → chunk-XH4IURUD.js} +7 -7
- package/dist/{chunk-4ETVCRQD.js.map → chunk-XH4IURUD.js.map} +1 -1
- package/dist/client/index.d.mts +2 -2
- package/dist/client/index.d.ts +2 -2
- package/dist/client/index.js +4 -4
- package/dist/client/index.mjs +3 -3
- package/dist/components/index.d.mts +258 -7
- package/dist/components/index.d.ts +258 -7
- package/dist/components/index.js +30 -4
- package/dist/components/index.js.map +1 -1
- package/dist/components/index.mjs +29 -3
- package/dist/contexts/index.d.mts +17 -5
- package/dist/contexts/index.d.ts +17 -5
- package/dist/contexts/index.js +4 -4
- package/dist/contexts/index.mjs +3 -3
- package/dist/core/index.d.mts +491 -11
- package/dist/core/index.d.ts +491 -11
- package/dist/core/index.js +38 -2
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +37 -1
- package/dist/features/help/index.js +37 -37
- package/dist/features/help/index.mjs +3 -3
- package/dist/features/tokenusage/index.js +101 -101
- package/dist/features/tokenusage/index.mjs +3 -3
- package/dist/index.d.mts +7 -7
- package/dist/index.d.ts +7 -7
- package/dist/index.js +39 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +38 -2
- package/dist/{notification.interface-DYF56CtE.d.ts → notification.interface-CjzV_KII.d.ts} +1 -1
- package/dist/{notification.interface-BWOCvKRP.d.mts → notification.interface-MnRt1Tjm.d.mts} +1 -1
- package/dist/{s3.service-D_dswjgz.d.mts → s3.service-C0Yq2Sx3.d.mts} +2 -2
- package/dist/{s3.service-DserU9VK.d.ts → s3.service-CUGarZs3.d.ts} +2 -2
- package/dist/server/index.d.mts +3 -3
- package/dist/server/index.d.ts +3 -3
- package/dist/server/index.js +3 -3
- package/dist/server/index.mjs +1 -1
- package/dist/{useSocket-BZG9oX-L.d.mts → useSocket-BcH7l7B8.d.mts} +1 -1
- package/dist/{useSocket-CiyTtYFj.d.ts → useSocket-DCZd_eJk.d.ts} +1 -1
- package/package.json +1 -1
- package/src/components/containers/ReactMarkdownContainer.tsx +61 -11
- package/src/components/index.ts +1 -0
- package/src/core/index.ts +5 -0
- package/src/core/registry/ModuleRegistry.ts +4 -0
- package/src/features/assistant/components/parts/AssistantComposer.tsx +22 -3
- package/src/features/assistant/components/parts/AssistantSidebar.tsx +13 -3
- package/src/features/assistant/components/parts/AssistantThreadHeader.tsx +10 -3
- package/src/features/assistant/contexts/AssistantContext.tsx +30 -3
- package/src/features/assistant/contexts/__tests__/AssistantContext.spec.tsx +32 -0
- package/src/features/assistant/data/Assistant.ts +2 -0
- package/src/features/assistant/data/AssistantInterface.ts +2 -0
- package/src/features/assistant/data/AssistantService.ts +4 -0
- package/src/features/assistant-message/components/parts/MessageSourcesContainer.tsx +5 -1
- package/src/features/assistant-message/data/AssistantMessage.ts +27 -2
- package/src/features/content/data/content.interface.ts +1 -1
- package/src/features/handbook/HandbookPageModule.ts +41 -0
- package/src/features/handbook/HandbookSectionModule.ts +35 -0
- package/src/features/handbook/HandbookThreadMessageModule.ts +21 -0
- package/src/features/handbook/HandbookThreadModule.ts +29 -0
- package/src/features/handbook/components/containers/HandbookAdminContainer.tsx +17 -0
- package/src/features/handbook/components/containers/HandbookAskContainer.tsx +142 -0
- package/src/features/handbook/components/containers/HandbookAskSheet.tsx +149 -0
- package/src/features/handbook/components/containers/HandbookPageContainer.tsx +143 -0
- package/src/features/handbook/components/containers/__tests__/HandbookAskContainer.spec.tsx +268 -0
- package/src/features/handbook/components/containers/__tests__/HandbookAskSheet.spec.tsx +143 -0
- package/src/features/handbook/components/containers/__tests__/HandbookPageContainer.spec.tsx +288 -0
- package/src/features/handbook/components/details/HandbookPageDetails.tsx +27 -0
- package/src/features/handbook/components/details/HandbookPageNeighbours.tsx +72 -0
- package/src/features/handbook/components/details/HandbookPageToc.tsx +165 -0
- package/src/features/handbook/components/forms/HandbookSyncButton.tsx +43 -0
- package/src/features/handbook/components/forms/__tests__/HandbookSyncButton.spec.tsx +42 -0
- package/src/features/handbook/components/index.ts +12 -0
- package/src/features/handbook/components/lists/HandbookContents.tsx +196 -0
- package/src/features/handbook/components/lists/HandbookSectionGroup.tsx +153 -0
- package/src/features/handbook/components/lists/__tests__/HandbookContents.spec.tsx +247 -0
- package/src/features/handbook/components/parts/HandbookIndexStatus.tsx +62 -0
- package/src/features/handbook/components/parts/HandbookPageNavigator.tsx +143 -0
- package/src/features/handbook/contexts/HandbookContext.tsx +109 -0
- package/src/features/handbook/contexts/__tests__/HandbookContext.spec.tsx +131 -0
- package/src/features/handbook/data/HandbookPage.spec.ts +136 -0
- package/src/features/handbook/data/HandbookPage.ts +99 -0
- package/src/features/handbook/data/HandbookPageFields.ts +31 -0
- package/src/features/handbook/data/HandbookPageInput.ts +8 -0
- package/src/features/handbook/data/HandbookPageInterface.ts +29 -0
- package/src/features/handbook/data/HandbookPageService.ts +60 -0
- package/src/features/handbook/data/HandbookSection.spec.ts +58 -0
- package/src/features/handbook/data/HandbookSection.ts +73 -0
- package/src/features/handbook/data/HandbookSectionInput.ts +12 -0
- package/src/features/handbook/data/HandbookSectionInterface.ts +26 -0
- package/src/features/handbook/data/HandbookSectionService.ts +26 -0
- package/src/features/handbook/data/HandbookThread.ts +114 -0
- package/src/features/handbook/data/HandbookThreadInterface.ts +31 -0
- package/src/features/handbook/data/HandbookThreadMessage.ts +144 -0
- package/src/features/handbook/data/HandbookThreadMessageInterface.ts +35 -0
- package/src/features/handbook/data/HandbookThreadMessageService.ts +30 -0
- package/src/features/handbook/data/HandbookThreadService.ts +86 -0
- package/src/features/handbook/data/__tests__/HandbookThreadService.spec.ts +112 -0
- package/src/features/handbook/data/handbookDisplay.spec.ts +92 -0
- package/src/features/handbook/data/handbookDisplay.ts +55 -0
- package/src/features/handbook/data/index.ts +16 -0
- package/src/features/handbook/hooks/__tests__/useHandbookAsk.spec.ts +198 -0
- package/src/features/handbook/hooks/useHandbookAsk.ts +213 -0
- package/src/features/handbook/i18n-keys.ts +46 -0
- package/src/features/handbook/index.ts +7 -0
- package/src/features/how-to/components/forms/HowToEditor.tsx +0 -1
- package/src/features/how-to/components/forms/HowToReindexButton.spec.tsx +84 -0
- package/src/features/how-to/components/forms/HowToReindexButton.tsx +46 -0
- package/src/features/how-to/components/index.ts +1 -0
- package/src/features/how-to/components/lists/HowToList.tsx +5 -1
- package/src/features/how-to/data/HowTo.spec.ts +61 -1
- package/src/features/how-to/data/HowToService.ts +13 -0
- package/dist/chunk-JEGL7KHK.mjs.map +0 -1
- package/dist/chunk-L7DZSTFU.js.map +0 -1
- package/dist/chunk-OKGGYCO5.js.map +0 -1
- package/dist/chunk-Q2BREXH3.mjs.map +0 -1
- /package/dist/{BlockNoteEditor-CR5KH4AI.mjs.map → BlockNoteEditor-SERY337S.mjs.map} +0 -0
- /package/dist/{BlockNoteViewer-NJKFUNXP.mjs.map → BlockNoteViewer-P2VKEFRN.mjs.map} +0 -0
- /package/dist/{chunk-HVNEWDRF.mjs.map → chunk-VZKJIAND.mjs.map} +0 -0
|
@@ -16,6 +16,10 @@ export interface FoundationModuleDefinitions {
|
|
|
16
16
|
Module: ModuleWithPermissions;
|
|
17
17
|
Content: ModuleWithPermissions;
|
|
18
18
|
HowTo: ModuleWithPermissions;
|
|
19
|
+
HandbookPage: ModuleWithPermissions;
|
|
20
|
+
HandbookSection: ModuleWithPermissions;
|
|
21
|
+
HandbookThread: ModuleWithPermissions;
|
|
22
|
+
HandbookThreadMessage: ModuleWithPermissions;
|
|
19
23
|
Assistant: ModuleWithPermissions;
|
|
20
24
|
AssistantMessage: ModuleWithPermissions;
|
|
21
25
|
AssistantAction: ModuleWithPermissions;
|
|
@@ -10,9 +10,28 @@ interface Props {
|
|
|
10
10
|
disabled?: boolean;
|
|
11
11
|
value?: string;
|
|
12
12
|
onValueChange?: (v: string) => void;
|
|
13
|
+
/**
|
|
14
|
+
* Textarea placeholder. Defaults to the assistant's own copy ("ask me about a
|
|
15
|
+
* matter, a client, a document…"), which is wrong on any surface that is not
|
|
16
|
+
* the law-firm assistant.
|
|
17
|
+
*/
|
|
18
|
+
placeholder?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Label of the send button. Defaults to `ui.buttons.save`, which reads
|
|
21
|
+
* "Salva" — correct nowhere in a chat, kept only so existing callers do not
|
|
22
|
+
* move.
|
|
23
|
+
*/
|
|
24
|
+
sendLabel?: string;
|
|
13
25
|
}
|
|
14
26
|
|
|
15
|
-
export function AssistantComposer({
|
|
27
|
+
export function AssistantComposer({
|
|
28
|
+
onSend,
|
|
29
|
+
disabled,
|
|
30
|
+
value: controlled,
|
|
31
|
+
onValueChange,
|
|
32
|
+
placeholder,
|
|
33
|
+
sendLabel,
|
|
34
|
+
}: Props) {
|
|
16
35
|
const t = useTranslations();
|
|
17
36
|
const [internal, setInternal] = useState("");
|
|
18
37
|
const value = controlled ?? internal;
|
|
@@ -41,13 +60,13 @@ export function AssistantComposer({ onSend, disabled, value: controlled, onValue
|
|
|
41
60
|
value={value}
|
|
42
61
|
onChange={(e) => setValue(e.target.value)}
|
|
43
62
|
onKeyDown={onKeyDown}
|
|
44
|
-
placeholder={t("features.assistant.composer_placeholder")}
|
|
63
|
+
placeholder={placeholder ?? t("features.assistant.composer_placeholder")}
|
|
45
64
|
disabled={disabled}
|
|
46
65
|
rows={2}
|
|
47
66
|
className="min-h-[48px] resize-none border-0 bg-transparent focus-visible:ring-0"
|
|
48
67
|
/>
|
|
49
68
|
<Button onClick={submit} disabled={!canSend} size="sm" className="h-8">
|
|
50
|
-
<ArrowUp className="me-1 h-4 w-4" /> {t("features.assistant.send")}
|
|
69
|
+
<ArrowUp className="me-1 h-4 w-4" /> {sendLabel ?? t("features.assistant.send")}
|
|
51
70
|
</Button>
|
|
52
71
|
</div>
|
|
53
72
|
<div className="text-muted-foreground text-end text-xs">{t("features.assistant.keyboard_hint")}</div>
|
|
@@ -12,9 +12,17 @@ interface Props {
|
|
|
12
12
|
activeId?: string;
|
|
13
13
|
onSelect: (id: string) => void;
|
|
14
14
|
onNew: () => void;
|
|
15
|
+
/**
|
|
16
|
+
* Label of the "start a new thread" button. Defaults to the assistant's own
|
|
17
|
+
* copy; a surface that is not the assistant (the handbook chat) passes its
|
|
18
|
+
* own so the control does not read "New assistant" on a handbook page.
|
|
19
|
+
*/
|
|
20
|
+
newLabel?: string;
|
|
21
|
+
/** Shown when there are no threads. Same rationale as `newLabel`. */
|
|
22
|
+
emptyLabel?: string;
|
|
15
23
|
}
|
|
16
24
|
|
|
17
|
-
export function AssistantSidebar({ threads, activeId, onSelect, onNew }: Props) {
|
|
25
|
+
export function AssistantSidebar({ threads, activeId, onSelect, onNew, newLabel, emptyLabel }: Props) {
|
|
18
26
|
const t = useTranslations();
|
|
19
27
|
const groups = groupThreadsByBucket(threads);
|
|
20
28
|
|
|
@@ -44,12 +52,14 @@ export function AssistantSidebar({ threads, activeId, onSelect, onNew }: Props)
|
|
|
44
52
|
<aside className="bg-muted/30 flex w-64 flex-col border-e">
|
|
45
53
|
<div className="border-b p-3">
|
|
46
54
|
<Button onClick={onNew} className="w-full" size="sm">
|
|
47
|
-
<Plus className="me-1 h-4 w-4" /> {t("features.assistant.new")}
|
|
55
|
+
<Plus className="me-1 h-4 w-4" /> {newLabel ?? t("features.assistant.new")}
|
|
48
56
|
</Button>
|
|
49
57
|
</div>
|
|
50
58
|
<div className="flex-1 overflow-y-auto p-2">
|
|
51
59
|
{threads.length === 0 ? (
|
|
52
|
-
<div className="text-muted-foreground mt-6 text-center text-xs">
|
|
60
|
+
<div className="text-muted-foreground mt-6 text-center text-xs">
|
|
61
|
+
{emptyLabel ?? t("features.assistant.empty_sidebar")}
|
|
62
|
+
</div>
|
|
53
63
|
) : (
|
|
54
64
|
<>
|
|
55
65
|
{renderSection(t("features.assistant.bucket_today"), groups.today)}
|
|
@@ -21,9 +21,16 @@ interface Props {
|
|
|
21
21
|
assistant: AssistantInterface;
|
|
22
22
|
onRename: (title: string) => Promise<void>;
|
|
23
23
|
onDelete: () => Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Placeholder of the rename field. Defaults to the assistant's own copy
|
|
26
|
+
* ("name this assistant"), which misnames the thread on any other surface.
|
|
27
|
+
*/
|
|
28
|
+
renamePlaceholder?: string;
|
|
29
|
+
/** Body of the delete confirmation. Same rationale as `renamePlaceholder`. */
|
|
30
|
+
deleteConfirmLabel?: string;
|
|
24
31
|
}
|
|
25
32
|
|
|
26
|
-
export function AssistantThreadHeader({ assistant, onRename, onDelete }: Props) {
|
|
33
|
+
export function AssistantThreadHeader({ assistant, onRename, onDelete, renamePlaceholder, deleteConfirmLabel }: Props) {
|
|
27
34
|
const t = useTranslations();
|
|
28
35
|
const [renameValue, setRenameValue] = useState(assistant.title);
|
|
29
36
|
const [renameOpen, setRenameOpen] = useState(false);
|
|
@@ -56,7 +63,7 @@ export function AssistantThreadHeader({ assistant, onRename, onDelete }: Props)
|
|
|
56
63
|
<Input
|
|
57
64
|
value={renameValue}
|
|
58
65
|
onChange={(e) => setRenameValue(e.target.value)}
|
|
59
|
-
placeholder={t("features.assistant.rename_placeholder")}
|
|
66
|
+
placeholder={renamePlaceholder ?? t("features.assistant.rename_placeholder")}
|
|
60
67
|
/>
|
|
61
68
|
<Button onClick={handleRename} size="sm">
|
|
62
69
|
{t("ui.buttons.save")}
|
|
@@ -73,7 +80,7 @@ export function AssistantThreadHeader({ assistant, onRename, onDelete }: Props)
|
|
|
73
80
|
/>
|
|
74
81
|
<DialogContent>
|
|
75
82
|
<DialogHeader>
|
|
76
|
-
<DialogTitle>{t("features.assistant.delete_confirm")}</DialogTitle>
|
|
83
|
+
<DialogTitle>{deleteConfirmLabel ?? t("features.assistant.delete_confirm")}</DialogTitle>
|
|
77
84
|
</DialogHeader>
|
|
78
85
|
<DialogFooter>
|
|
79
86
|
<Button variant="outline" onClick={() => setDeleteOpen(false)}>
|
|
@@ -22,7 +22,13 @@ interface AssistantContextValue {
|
|
|
22
22
|
failedMessageIds: Set<string>;
|
|
23
23
|
sendMessage(
|
|
24
24
|
content: string,
|
|
25
|
-
opts?: {
|
|
25
|
+
opts?: {
|
|
26
|
+
howToMode?: boolean;
|
|
27
|
+
limitToHowToId?: string;
|
|
28
|
+
handbookMode?: boolean;
|
|
29
|
+
limitToHandbookPageId?: string;
|
|
30
|
+
contentBlocks?: unknown[];
|
|
31
|
+
},
|
|
26
32
|
): Promise<void>;
|
|
27
33
|
retrySend(tempId: string): Promise<void>;
|
|
28
34
|
selectThread(id: string): Promise<void>;
|
|
@@ -55,6 +61,13 @@ interface Props {
|
|
|
55
61
|
* it, so a scoped surface never shows or creates cross-scope threads.
|
|
56
62
|
*/
|
|
57
63
|
scope?: { type: string; id: string };
|
|
64
|
+
/**
|
|
65
|
+
* Confines every turn of this provider to one retrieval mode. The handbook
|
|
66
|
+
* surface sets `{ handbookMode: true }` so the flag does not have to be
|
|
67
|
+
* threaded through the composer on each send — the same reason `scope`
|
|
68
|
+
* exists on this provider.
|
|
69
|
+
*/
|
|
70
|
+
retrievalMode?: { handbookMode?: boolean; limitToHandbookPageId?: string };
|
|
58
71
|
/**
|
|
59
72
|
* Builds the browser URL for a thread. Defaults to `/assistants/{id}` (or
|
|
60
73
|
* `/assistants` when no thread is active), which is only correct for the
|
|
@@ -100,6 +113,7 @@ export function AssistantProvider({
|
|
|
100
113
|
titleOverride,
|
|
101
114
|
breadcrumbsOverride,
|
|
102
115
|
scope,
|
|
116
|
+
retrievalMode,
|
|
103
117
|
threadUrl,
|
|
104
118
|
}: Props) {
|
|
105
119
|
const t = useTranslations();
|
|
@@ -130,7 +144,16 @@ export function AssistantProvider({
|
|
|
130
144
|
const { socket } = useSocketContext();
|
|
131
145
|
|
|
132
146
|
const sendMessage = useCallback(
|
|
133
|
-
async (
|
|
147
|
+
async (
|
|
148
|
+
content: string,
|
|
149
|
+
opts?: {
|
|
150
|
+
howToMode?: boolean;
|
|
151
|
+
limitToHowToId?: string;
|
|
152
|
+
handbookMode?: boolean;
|
|
153
|
+
limitToHandbookPageId?: string;
|
|
154
|
+
contentBlocks?: unknown[];
|
|
155
|
+
},
|
|
156
|
+
) => {
|
|
134
157
|
const trimmed = content.trim();
|
|
135
158
|
if (!trimmed) return;
|
|
136
159
|
|
|
@@ -155,6 +178,8 @@ export function AssistantProvider({
|
|
|
155
178
|
firstMessage: trimmed,
|
|
156
179
|
howToMode: opts?.howToMode,
|
|
157
180
|
limitToHowToId: opts?.limitToHowToId,
|
|
181
|
+
handbookMode: opts?.handbookMode ?? retrievalMode?.handbookMode,
|
|
182
|
+
limitToHandbookPageId: opts?.limitToHandbookPageId ?? retrievalMode?.limitToHandbookPageId,
|
|
158
183
|
contentBlocks: opts?.contentBlocks,
|
|
159
184
|
boundContent: scope,
|
|
160
185
|
};
|
|
@@ -180,6 +205,8 @@ export function AssistantProvider({
|
|
|
180
205
|
content: trimmed,
|
|
181
206
|
howToMode: opts?.howToMode,
|
|
182
207
|
limitToHowToId: opts?.limitToHowToId,
|
|
208
|
+
handbookMode: opts?.handbookMode ?? retrievalMode?.handbookMode,
|
|
209
|
+
limitToHandbookPageId: opts?.limitToHandbookPageId ?? retrievalMode?.limitToHandbookPageId,
|
|
183
210
|
contentBlocks: opts?.contentBlocks,
|
|
184
211
|
});
|
|
185
212
|
setMessages((prev) => [...stripOptimistic(prev), ...result]);
|
|
@@ -196,7 +223,7 @@ export function AssistantProvider({
|
|
|
196
223
|
setStatus(undefined);
|
|
197
224
|
}
|
|
198
225
|
},
|
|
199
|
-
[assistant, messages, socket, operatorMode, scope, manageUrl, resolveThreadUrl],
|
|
226
|
+
[assistant, messages, socket, operatorMode, scope, retrievalMode, manageUrl, resolveThreadUrl],
|
|
200
227
|
);
|
|
201
228
|
|
|
202
229
|
const appendResolvedMessage = useCallback((message: AssistantMessageInterface) => {
|
|
@@ -455,6 +455,38 @@ describe("AssistantContext", () => {
|
|
|
455
455
|
);
|
|
456
456
|
});
|
|
457
457
|
|
|
458
|
+
it("forwards handbookMode passed on the send", async () => {
|
|
459
|
+
const existing = buildAssistantDehydrated({ id: "a-3", title: "Existing" });
|
|
460
|
+
AssistantService.appendMessage = vi.fn().mockResolvedValue([]);
|
|
461
|
+
|
|
462
|
+
const { result } = renderHook(() => useAssistantContext(), {
|
|
463
|
+
wrapper: ({ children }) => <AssistantProvider dehydratedAssistant={existing}>{children}</AssistantProvider>,
|
|
464
|
+
});
|
|
465
|
+
await act(async () => {
|
|
466
|
+
await result.current.sendMessage("Come funziona il modulo chunk?", { handbookMode: true });
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
expect(AssistantService.appendMessage).toHaveBeenCalledWith(expect.objectContaining({ handbookMode: true }));
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
it("forwards the provider's retrievalMode when the send passes nothing", async () => {
|
|
473
|
+
const existing = buildAssistantDehydrated({ id: "a-4", title: "Existing" });
|
|
474
|
+
AssistantService.appendMessage = vi.fn().mockResolvedValue([]);
|
|
475
|
+
|
|
476
|
+
const { result } = renderHook(() => useAssistantContext(), {
|
|
477
|
+
wrapper: ({ children }) => (
|
|
478
|
+
<AssistantProvider dehydratedAssistant={existing} retrievalMode={{ handbookMode: true }}>
|
|
479
|
+
{children}
|
|
480
|
+
</AssistantProvider>
|
|
481
|
+
),
|
|
482
|
+
});
|
|
483
|
+
await act(async () => {
|
|
484
|
+
await result.current.sendMessage("Come funziona il modulo chunk?");
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
expect(AssistantService.appendMessage).toHaveBeenCalledWith(expect.objectContaining({ handbookMode: true }));
|
|
488
|
+
});
|
|
489
|
+
|
|
458
490
|
it("calls service without opts when called with content only (regression)", async () => {
|
|
459
491
|
const replaceState = vi.spyOn(window.history, "replaceState").mockImplementation(() => {});
|
|
460
492
|
const created = buildAssistantStub({ id: "a-1" });
|
|
@@ -58,6 +58,8 @@ export class Assistant extends AbstractApiData implements AssistantInterface {
|
|
|
58
58
|
...(data.title !== undefined ? { title: data.title } : {}),
|
|
59
59
|
...(data.howToMode !== undefined ? { howToMode: data.howToMode } : {}),
|
|
60
60
|
...(data.limitToHowToId !== undefined ? { limitToHowToId: data.limitToHowToId } : {}),
|
|
61
|
+
...(data.handbookMode !== undefined ? { handbookMode: data.handbookMode } : {}),
|
|
62
|
+
...(data.limitToHandbookPageId !== undefined ? { limitToHandbookPageId: data.limitToHandbookPageId } : {}),
|
|
61
63
|
},
|
|
62
64
|
...(data.boundContent !== undefined
|
|
63
65
|
? {
|
|
@@ -5,6 +5,8 @@ export type AssistantInput = {
|
|
|
5
5
|
title?: string;
|
|
6
6
|
howToMode?: boolean;
|
|
7
7
|
limitToHowToId?: string;
|
|
8
|
+
handbookMode?: boolean;
|
|
9
|
+
limitToHandbookPageId?: string;
|
|
8
10
|
/**
|
|
9
11
|
* BlockNote document behind `firstMessage`. Sent alongside the flattened
|
|
10
12
|
* text so the server can re-derive the content and read `@`-mentions from
|
|
@@ -67,6 +67,8 @@ export class AssistantService extends AbstractService {
|
|
|
67
67
|
content: string;
|
|
68
68
|
howToMode?: boolean;
|
|
69
69
|
limitToHowToId?: string;
|
|
70
|
+
handbookMode?: boolean;
|
|
71
|
+
limitToHandbookPageId?: string;
|
|
70
72
|
/** BlockNote document. Serialised by the model into `content`; never its own attribute. */
|
|
71
73
|
contentBlocks?: unknown[];
|
|
72
74
|
}): Promise<AssistantMessageInterface[]> {
|
|
@@ -83,6 +85,8 @@ export class AssistantService extends AbstractService {
|
|
|
83
85
|
content: params.content,
|
|
84
86
|
howToMode: params.howToMode,
|
|
85
87
|
limitToHowToId: params.limitToHowToId,
|
|
88
|
+
handbookMode: params.handbookMode,
|
|
89
|
+
limitToHandbookPageId: params.limitToHandbookPageId,
|
|
86
90
|
contentBlocks: params.contentBlocks,
|
|
87
91
|
}),
|
|
88
92
|
overridesJsonApiCreation: true,
|
|
@@ -87,7 +87,11 @@ export function MessageSourcesContainer({ message, isLatestAssistant, onSelectFo
|
|
|
87
87
|
|
|
88
88
|
useEffect(() => {
|
|
89
89
|
if (groups.size === 0) {
|
|
90
|
-
|
|
90
|
+
// Functional update, and a no-op when it is already empty. A bare
|
|
91
|
+
// `setResolved([])` sets a NEW array on every run, so a caller whose
|
|
92
|
+
// `message.citations` identity changes per render loops here until React
|
|
93
|
+
// gives up with "Maximum update depth exceeded".
|
|
94
|
+
setResolved((current) => (current.length === 0 ? current : []));
|
|
91
95
|
return;
|
|
92
96
|
}
|
|
93
97
|
let cancelled = false;
|
|
@@ -129,6 +129,8 @@ export class AssistantMessage extends AbstractApiData implements AssistantMessag
|
|
|
129
129
|
content: string;
|
|
130
130
|
howToMode?: boolean;
|
|
131
131
|
limitToHowToId?: string;
|
|
132
|
+
handbookMode?: boolean;
|
|
133
|
+
limitToHandbookPageId?: string;
|
|
132
134
|
contentBlocks?: unknown[];
|
|
133
135
|
}) {
|
|
134
136
|
return {
|
|
@@ -142,18 +144,41 @@ export class AssistantMessage extends AbstractApiData implements AssistantMessag
|
|
|
142
144
|
content: params.contentBlocks !== undefined ? JSON.stringify(params.contentBlocks) : params.content,
|
|
143
145
|
...(params.howToMode !== undefined ? { howToMode: params.howToMode } : {}),
|
|
144
146
|
...(params.limitToHowToId !== undefined ? { limitToHowToId: params.limitToHowToId } : {}),
|
|
147
|
+
...(params.handbookMode !== undefined ? { handbookMode: params.handbookMode } : {}),
|
|
148
|
+
...(params.limitToHandbookPageId !== undefined
|
|
149
|
+
? { limitToHandbookPageId: params.limitToHandbookPageId }
|
|
150
|
+
: {}),
|
|
145
151
|
},
|
|
146
152
|
},
|
|
147
153
|
};
|
|
148
154
|
}
|
|
149
155
|
|
|
150
156
|
static buildOptimistic(params: { content: string; position: number; assistantId?: string }): AssistantMessage {
|
|
157
|
+
return AssistantMessage.buildLocal({ ...params, role: "user", isOptimistic: true });
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* A message that exists only on the client, for a surface that renders the
|
|
162
|
+
* chat presentation without persisting a thread.
|
|
163
|
+
*
|
|
164
|
+
* `buildOptimistic` is the user-turn case and delegates here. The handbook
|
|
165
|
+
* ask surface is the other caller: it has no Assistant and no stored
|
|
166
|
+
* messages, but it renders through MessageList/MessageItem like every other
|
|
167
|
+
* chat in the product, so it needs both roles.
|
|
168
|
+
*/
|
|
169
|
+
static buildLocal(params: {
|
|
170
|
+
role: AssistantMessageRole;
|
|
171
|
+
content: string;
|
|
172
|
+
position: number;
|
|
173
|
+
assistantId?: string;
|
|
174
|
+
isOptimistic?: boolean;
|
|
175
|
+
}): AssistantMessage {
|
|
151
176
|
const msg = new AssistantMessage();
|
|
152
177
|
const jsonApi: Record<string, unknown> = {
|
|
153
178
|
id: uuidv4(),
|
|
154
179
|
type: Modules.AssistantMessage.name,
|
|
155
180
|
attributes: {
|
|
156
|
-
role:
|
|
181
|
+
role: params.role,
|
|
157
182
|
content: params.content,
|
|
158
183
|
position: params.position,
|
|
159
184
|
},
|
|
@@ -164,7 +189,7 @@ export class AssistantMessage extends AbstractApiData implements AssistantMessag
|
|
|
164
189
|
};
|
|
165
190
|
}
|
|
166
191
|
msg.rehydrate({ jsonApi: jsonApi as any, included: [] });
|
|
167
|
-
msg._isOptimistic =
|
|
192
|
+
msg._isOptimistic = params.isOptimistic ?? false;
|
|
168
193
|
return msg;
|
|
169
194
|
}
|
|
170
195
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { BookOpenIcon } from "lucide-react";
|
|
2
|
+
import { createJsonApiInclusion } from "../../core";
|
|
3
|
+
import { ModuleFactory } from "../../permissions";
|
|
4
|
+
import { HandbookPage } from "./data/HandbookPage";
|
|
5
|
+
|
|
6
|
+
export const HandbookPageModule = (factory: ModuleFactory) =>
|
|
7
|
+
factory({
|
|
8
|
+
moduleId: "b1f1c2d6-6a0e-4e93-9b6d-3f5f4a2c81e7",
|
|
9
|
+
pageUrl: "/administration/handbook",
|
|
10
|
+
name: "handbookpages",
|
|
11
|
+
model: HandbookPage,
|
|
12
|
+
icon: BookOpenIcon,
|
|
13
|
+
inclusions: {
|
|
14
|
+
lists: {
|
|
15
|
+
// `content` is deliberately omitted: the list renders titles and
|
|
16
|
+
// status, and a thousand pages of markdown do not belong in a list
|
|
17
|
+
// payload. `section`, `order` and `summary` are the index fields the
|
|
18
|
+
// contents page groups, orders and describes pages by, so they are the
|
|
19
|
+
// one part of the payload the list surface cannot do without.
|
|
20
|
+
//
|
|
21
|
+
// `displayTitle` and `displaySummary` are the translated halves of the
|
|
22
|
+
// two strings the contents page prints, and a sparse fieldset that
|
|
23
|
+
// omitted them would leave every row in English. `displayContent` is
|
|
24
|
+
// NOT here for the same reason `content` is not: it is the whole
|
|
25
|
+
// markdown body, and it belongs to the detail read alone.
|
|
26
|
+
fields: [
|
|
27
|
+
createJsonApiInclusion("handbookpages", [
|
|
28
|
+
"path",
|
|
29
|
+
"title",
|
|
30
|
+
"displayTitle",
|
|
31
|
+
"section",
|
|
32
|
+
"order",
|
|
33
|
+
"summary",
|
|
34
|
+
"displaySummary",
|
|
35
|
+
"wordCount",
|
|
36
|
+
"aiStatus",
|
|
37
|
+
]),
|
|
38
|
+
],
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { LibraryIcon } from "lucide-react";
|
|
2
|
+
import { createJsonApiInclusion } from "../../core";
|
|
3
|
+
import { ModuleFactory } from "../../permissions";
|
|
4
|
+
import { HandbookSection } from "./data/HandbookSection";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* One top-level directory of the documentation tree. Like the thread message,
|
|
8
|
+
* it has no `pageUrl` — a section has no page of its own; it is a heading on
|
|
9
|
+
* the contents surface and nothing more.
|
|
10
|
+
*/
|
|
11
|
+
export const HandbookSectionModule = (factory: ModuleFactory) =>
|
|
12
|
+
factory({
|
|
13
|
+
moduleId: "6b0a2f74-1c53-4a0e-9c1d-2e7b4a9d6f30",
|
|
14
|
+
name: "handbooksections",
|
|
15
|
+
model: HandbookSection,
|
|
16
|
+
icon: LibraryIcon,
|
|
17
|
+
inclusions: {
|
|
18
|
+
lists: {
|
|
19
|
+
// `HandbookSectionService.findMany` is the ONLY read of this resource and
|
|
20
|
+
// it limits to these fields, so the translated halves have to be here:
|
|
21
|
+
// without them every section heading on the contents page and in the
|
|
22
|
+
// reader's navigator would stay English however good the translation is.
|
|
23
|
+
fields: [
|
|
24
|
+
createJsonApiInclusion("handbooksections", [
|
|
25
|
+
"key",
|
|
26
|
+
"title",
|
|
27
|
+
"displayTitle",
|
|
28
|
+
"summary",
|
|
29
|
+
"displaySummary",
|
|
30
|
+
"order",
|
|
31
|
+
]),
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { MessageCircleQuestionIcon } from "lucide-react";
|
|
2
|
+
import { createJsonApiInclusion } from "../../core";
|
|
3
|
+
import { ModuleFactory } from "../../permissions";
|
|
4
|
+
import { HandbookThreadMessage } from "./data/HandbookThreadMessage";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* One turn of a handbook conversation. Like the thread, it has no `pageUrl` —
|
|
8
|
+
* a message is only ever read as part of its thread.
|
|
9
|
+
*/
|
|
10
|
+
export const HandbookThreadMessageModule = (factory: ModuleFactory) =>
|
|
11
|
+
factory({
|
|
12
|
+
moduleId: "bef354f2-70e1-4f4f-9953-10d3d74cad0c",
|
|
13
|
+
name: "handbookthreadmessages",
|
|
14
|
+
model: HandbookThreadMessage,
|
|
15
|
+
icon: MessageCircleQuestionIcon,
|
|
16
|
+
inclusions: {
|
|
17
|
+
lists: {
|
|
18
|
+
fields: [createJsonApiInclusion("handbookthreadmessages", ["role", "content", "position", "sources"])],
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { MessagesSquareIcon } from "lucide-react";
|
|
2
|
+
import { createJsonApiInclusion } from "../../core";
|
|
3
|
+
import { ModuleFactory } from "../../permissions";
|
|
4
|
+
import { HandbookThread } from "./data/HandbookThread";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A persisted handbook conversation.
|
|
8
|
+
*
|
|
9
|
+
* It carries no `pageUrl`: a thread is selected inside the ask surface
|
|
10
|
+
* (`/administration/handbook/chat`), never routed to on its own, and a
|
|
11
|
+
* `pageUrl` here would add a phantom entry to the RBAC path map.
|
|
12
|
+
*
|
|
13
|
+
* `lists` deliberately narrows to the title — the sidebar renders nothing else,
|
|
14
|
+
* and side-loading every message of every thread into the list payload is what
|
|
15
|
+
* the single-resource endpoint is for.
|
|
16
|
+
*/
|
|
17
|
+
export const HandbookThreadModule = (factory: ModuleFactory) =>
|
|
18
|
+
factory({
|
|
19
|
+
moduleId: "aea692c4-7163-49bd-9ddc-1052cc06fa14",
|
|
20
|
+
name: "handbookthreads",
|
|
21
|
+
model: HandbookThread,
|
|
22
|
+
icon: MessagesSquareIcon,
|
|
23
|
+
identifier: ["title"],
|
|
24
|
+
inclusions: {
|
|
25
|
+
lists: {
|
|
26
|
+
fields: [createJsonApiInclusion("handbookthreads", ["title"])],
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import HandbookContents from "../lists/HandbookContents";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The administration route's entry point for the manual.
|
|
7
|
+
*
|
|
8
|
+
* `RoundPageContainer` moved down into `HandbookContents`, which is the only
|
|
9
|
+
* component that can supply it: the container's title bar reads its heading
|
|
10
|
+
* from `useSharedContext()`, so `HandbookProvider` has to wrap it, and the
|
|
11
|
+
* provider's `functions` (the ask launcher, Sincronizza and the status line)
|
|
12
|
+
* are built from the state `HandbookContents` holds. `HandbookAskContainer`
|
|
13
|
+
* composes its own surface the same way.
|
|
14
|
+
*/
|
|
15
|
+
export default function HandbookAdminContainer() {
|
|
16
|
+
return <HandbookContents />;
|
|
17
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { MessagesSquareIcon, Sparkles } from "lucide-react";
|
|
4
|
+
import { useTranslations } from "next-intl";
|
|
5
|
+
import { useMemo } from "react";
|
|
6
|
+
|
|
7
|
+
import { RoundPageContainer } from "../../../../components/containers/RoundPageContainer";
|
|
8
|
+
import { SectionHeader } from "../../../../components/typography";
|
|
9
|
+
import { Modules } from "../../../../core";
|
|
10
|
+
import { AssistantComposer } from "../../../assistant/components/parts/AssistantComposer";
|
|
11
|
+
import { AssistantSidebar } from "../../../assistant/components/parts/AssistantSidebar";
|
|
12
|
+
import { AssistantThread } from "../../../assistant/components/parts/AssistantThread";
|
|
13
|
+
import { HandbookProvider } from "../../contexts/HandbookContext";
|
|
14
|
+
import { useHandbookAsk } from "../../hooks/useHandbookAsk";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The handbook ask surface.
|
|
18
|
+
*
|
|
19
|
+
* Composition is copied from `AssistantPageContainer` — the full-page assistant
|
|
20
|
+
* — top to bottom: the thread list lives in the page's details panel (which
|
|
21
|
+
* renders it on the trailing side) and the conversation fills the card. The
|
|
22
|
+
* transcript is rendered by the SAME components as every other chat in the
|
|
23
|
+
* product (`AssistantSidebar`, `AssistantThread`),
|
|
24
|
+
* which take their data as plain props and are typed against the structural
|
|
25
|
+
* `AssistantInterface` / `AssistantMessageInterface` that `HandbookThread` and
|
|
26
|
+
* `HandbookThreadMessage` implement.
|
|
27
|
+
*
|
|
28
|
+
* Two deliberate deviations from that reference, both forced by the data source:
|
|
29
|
+
*
|
|
30
|
+
* 1. It cannot MOUNT `AssistantPageContainer`, which reads
|
|
31
|
+
* `useAssistantContext()`. That provider is bound to the `Assistant` entity,
|
|
32
|
+
* which is company scoped, and a platform administrator has no company — so
|
|
33
|
+
* the handbook owns its own threads, and the state that provider would hold
|
|
34
|
+
* lives in `useHandbookAsk` — shared with the ask sheet, which is the same
|
|
35
|
+
* conversation on a different surface.
|
|
36
|
+
* 2. The composer is the plain `AssistantComposer`, not the BlockNote one. The
|
|
37
|
+
* BlockNote composer exists so a first message can carry `@`-mentions of
|
|
38
|
+
* product entities; the handbook agent answers from documentation pages and
|
|
39
|
+
* has no entities to mention, so that machinery would be dead weight.
|
|
40
|
+
*
|
|
41
|
+
* Every label is passed in from the `handbook.*` namespace. The assistant's own
|
|
42
|
+
* copy ("New assistant", "no assistants yet", "ask me about a matter, a client,
|
|
43
|
+
* a document…") is wrong on this surface, so the shared parts take optional
|
|
44
|
+
* label props that default to their existing text.
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
type HandbookAskContainerProps = {
|
|
48
|
+
/** Thread to open on mount, when the route carries one. */
|
|
49
|
+
handbookThreadId?: string;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export function HandbookAskContainer({ handbookThreadId }: HandbookAskContainerProps = {}) {
|
|
53
|
+
const t = useTranslations();
|
|
54
|
+
|
|
55
|
+
// The conversation itself lives in `useHandbookAsk`, because the ask sheet
|
|
56
|
+
// holds the same one. The route keeps the default `syncUrl: true`, so the
|
|
57
|
+
// address bar still follows the open thread exactly as it did when this
|
|
58
|
+
// component owned the state.
|
|
59
|
+
const { threads, thread, messages, sending, ask, selectThread, startNew } = useHandbookAsk({ handbookThreadId });
|
|
60
|
+
|
|
61
|
+
const showThread = !!thread || sending || messages.length > 0;
|
|
62
|
+
// Memoised, and not as a micro-optimisation: it was a fresh object on every
|
|
63
|
+
// render being fed into RoundPageContainer, which is the shape that produces
|
|
64
|
+
// React's "Maximum update depth exceeded".
|
|
65
|
+
const details = useMemo(
|
|
66
|
+
() => (
|
|
67
|
+
// The panel supplies its own padding and border; cancel both so the list
|
|
68
|
+
// reads as the panel body rather than a card inside it.
|
|
69
|
+
<div className="-m-4 flex h-full min-h-0 [&>aside]:w-full [&>aside]:border-e-0 [&>aside]:bg-transparent">
|
|
70
|
+
<AssistantSidebar
|
|
71
|
+
threads={threads}
|
|
72
|
+
activeId={thread?.id}
|
|
73
|
+
onSelect={selectThread}
|
|
74
|
+
onNew={startNew}
|
|
75
|
+
newLabel={t("handbook.chat.new")}
|
|
76
|
+
emptyLabel={t("handbook.chat.empty_sidebar")}
|
|
77
|
+
/>
|
|
78
|
+
</div>
|
|
79
|
+
),
|
|
80
|
+
[selectThread, startNew, t, thread?.id, threads],
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
// `RoundPageContainer` reads the page heading from the shared context, and
|
|
85
|
+
// `forceHeader` is what renders it — without a provider the hook throws.
|
|
86
|
+
// `HandbookProvider` is that provider for every handbook surface; the ask
|
|
87
|
+
// is the one that overrides the title, because it is a conversation about
|
|
88
|
+
// the manual rather than a page of it.
|
|
89
|
+
<HandbookProvider titleType={t("handbook.chat.title")}>
|
|
90
|
+
<RoundPageContainer
|
|
91
|
+
module={Modules.HandbookPage}
|
|
92
|
+
id={thread?.id}
|
|
93
|
+
fullWidth
|
|
94
|
+
forceHeader
|
|
95
|
+
defaultDetailsOpen
|
|
96
|
+
detailsTitle={t("handbook.chat.list_title")}
|
|
97
|
+
detailsIcon={<MessagesSquareIcon />}
|
|
98
|
+
details={details}
|
|
99
|
+
>
|
|
100
|
+
{/* No `rounded-lg border` here. `AssistantPageContainer` has them on
|
|
101
|
+
this wrapper, but RoundPageContainer already draws the card's border
|
|
102
|
+
around this child, so copying them doubles it. a360's own chat
|
|
103
|
+
content — ConversationMessageContainer — is a bare
|
|
104
|
+
`flex h-full w-full min-h-0 flex-col`, and that is the shape this
|
|
105
|
+
app's pages are built to. */}
|
|
106
|
+
<div className="flex h-full min-h-0 w-full flex-col overflow-hidden">
|
|
107
|
+
{showThread ? (
|
|
108
|
+
<>
|
|
109
|
+
{/* No per-thread header. `RoundPageContainer` already renders the
|
|
110
|
+
page header above this card, and a second title row inside it
|
|
111
|
+
is the double header. `AssistantThreadHeader` also carries
|
|
112
|
+
rename/delete, which this surface does not offer —
|
|
113
|
+
`ConversationMessageContainer` has no such row either. */}
|
|
114
|
+
<AssistantThread
|
|
115
|
+
messages={messages}
|
|
116
|
+
sending={sending}
|
|
117
|
+
status={t("handbook.chat.pending")}
|
|
118
|
+
onSelectFollowUp={ask}
|
|
119
|
+
/>
|
|
120
|
+
</>
|
|
121
|
+
) : (
|
|
122
|
+
<div className="flex flex-1 items-center justify-center p-10">
|
|
123
|
+
<div className="flex max-w-2xl flex-col items-center text-center">
|
|
124
|
+
<span className="mb-3 flex h-10 w-10 items-center justify-center rounded-full bg-gradient-to-br from-blue-400 to-violet-500 text-white">
|
|
125
|
+
<Sparkles className="h-5 w-5" />
|
|
126
|
+
</span>
|
|
127
|
+
<SectionHeader>{t("handbook.chat.empty_state.title")}</SectionHeader>
|
|
128
|
+
<p className="text-muted-foreground mt-1 text-sm">{t("handbook.chat.empty_state.subtitle")}</p>
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
)}
|
|
132
|
+
<AssistantComposer
|
|
133
|
+
onSend={ask}
|
|
134
|
+
disabled={sending}
|
|
135
|
+
placeholder={t("handbook.chat.placeholder")}
|
|
136
|
+
sendLabel={t("handbook.chat.ask")}
|
|
137
|
+
/>
|
|
138
|
+
</div>
|
|
139
|
+
</RoundPageContainer>
|
|
140
|
+
</HandbookProvider>
|
|
141
|
+
);
|
|
142
|
+
}
|