@carlonicora/nextjs-jsonapi 1.107.1 → 1.109.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-BYgI5z1-.d.mts → AssistantInterface-B1c8FhGA.d.mts} +2 -0
- package/dist/{AssistantInterface-DfDcz0gJ.d.ts → AssistantInterface-BBUHxOCd.d.ts} +2 -0
- package/dist/{AssistantMessageInterface-BpEhx2pC.d.ts → AssistantMessageInterface-Cs1yb-gF.d.ts} +3 -1
- package/dist/{AssistantMessageInterface-DJ3Me16Y.d.mts → AssistantMessageInterface-DQ3mH5L8.d.mts} +3 -1
- package/dist/{AuthComponent-B6DIk8Vf.d.ts → AuthComponent-Cd7lcYif.d.ts} +1 -1
- package/dist/{AuthComponent-BKI0ZbtD.d.mts → AuthComponent-DdxCFgUZ.d.mts} +1 -1
- package/dist/{BlockNoteEditor-RWRVIEZC.js → BlockNoteEditor-3XYBZLWO.js} +20 -19
- package/dist/BlockNoteEditor-3XYBZLWO.js.map +1 -0
- package/dist/{BlockNoteEditor-7TSK7PNG.mjs → BlockNoteEditor-EBFZG7AL.mjs} +5 -4
- package/dist/{BlockNoteEditor-7TSK7PNG.mjs.map → BlockNoteEditor-EBFZG7AL.mjs.map} +1 -1
- package/dist/{auth.interface-BBUgMZzs.d.ts → auth.interface-8b601idJ.d.ts} +1 -1
- package/dist/{auth.interface-XYEREOD6.d.mts → auth.interface-CXBF8Mhi.d.mts} +1 -1
- package/dist/billing/index.js +347 -346
- package/dist/billing/index.js.map +1 -1
- package/dist/billing/index.mjs +4 -3
- package/dist/billing/index.mjs.map +1 -1
- package/dist/chunk-3J7RQBF3.js +123 -0
- package/dist/chunk-3J7RQBF3.js.map +1 -0
- package/dist/{chunk-VLDLERJN.js → chunk-7E3O52U5.js} +15 -8
- package/dist/chunk-7E3O52U5.js.map +1 -0
- package/dist/{chunk-RXXZGPC3.js → chunk-CFI4WZ5R.js} +159 -113
- package/dist/chunk-CFI4WZ5R.js.map +1 -0
- package/dist/chunk-CQID6RCF.mjs +38 -0
- package/dist/chunk-CQID6RCF.mjs.map +1 -0
- package/dist/{chunk-WSOPEIRP.mjs → chunk-CRTVAQEK.mjs} +46 -27
- package/dist/chunk-CRTVAQEK.mjs.map +1 -0
- package/dist/{chunk-N3NVIPSU.mjs → chunk-MSNNAHDB.mjs} +129 -83
- package/dist/{chunk-N3NVIPSU.mjs.map → chunk-MSNNAHDB.mjs.map} +1 -1
- package/dist/chunk-MZTKPPET.mjs +123 -0
- package/dist/chunk-MZTKPPET.mjs.map +1 -0
- package/dist/{chunk-2IRWQVG4.js → chunk-UHO3KUUH.js} +842 -823
- package/dist/chunk-UHO3KUUH.js.map +1 -0
- package/dist/{chunk-CFECWLHH.mjs → chunk-UOYIWJEJ.mjs} +10 -3
- package/dist/chunk-UOYIWJEJ.mjs.map +1 -0
- package/dist/chunk-YQQHAFBS.js +38 -0
- package/dist/chunk-YQQHAFBS.js.map +1 -0
- package/dist/client/index.d.mts +8 -16
- package/dist/client/index.d.ts +8 -16
- package/dist/client/index.js +5 -4
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +4 -3
- package/dist/components/index.d.mts +8 -7
- package/dist/components/index.d.ts +8 -7
- package/dist/components/index.js +5 -4
- package/dist/components/index.js.map +1 -1
- package/dist/components/index.mjs +4 -3
- package/dist/{config-CLQynoaa.d.ts → config-CN23v3eJ.d.ts} +4 -1
- package/dist/{config-k61pe_o2.d.mts → config-gh88Qn4h.d.mts} +4 -1
- package/dist/contexts/index.d.mts +18 -7
- package/dist/contexts/index.d.ts +18 -7
- package/dist/contexts/index.js +5 -4
- package/dist/contexts/index.js.map +1 -1
- package/dist/contexts/index.mjs +4 -3
- package/dist/core/index.d.mts +44 -11
- package/dist/core/index.d.ts +44 -11
- package/dist/core/index.js +2 -2
- package/dist/core/index.mjs +1 -1
- package/dist/features/help/index.css +29 -0
- package/dist/features/help/index.css.map +1 -0
- package/dist/features/help/index.d.mts +115 -0
- package/dist/features/help/index.d.ts +115 -0
- package/dist/features/help/index.js +532 -0
- package/dist/features/help/index.js.map +1 -0
- package/dist/features/help/index.mjs +532 -0
- package/dist/features/help/index.mjs.map +1 -0
- package/dist/features/help/server/createHelpAssetRouteHandler.d.mts +11 -0
- package/dist/features/help/server/createHelpAssetRouteHandler.d.ts +11 -0
- package/dist/features/help/server/createHelpAssetRouteHandler.js +43 -0
- package/dist/features/help/server/createHelpAssetRouteHandler.js.map +1 -0
- package/dist/features/help/server/createHelpAssetRouteHandler.mjs +43 -0
- package/dist/features/help/server/createHelpAssetRouteHandler.mjs.map +1 -0
- package/dist/features/help/server.d.mts +71 -0
- package/dist/features/help/server.d.ts +71 -0
- package/dist/features/help/server.js +123 -0
- package/dist/features/help/server.js.map +1 -0
- package/dist/features/help/server.mjs +123 -0
- package/dist/features/help/server.mjs.map +1 -0
- package/dist/help-content-config.interface-B9L02u9i.d.mts +50 -0
- package/dist/help-content-config.interface-B9L02u9i.d.ts +50 -0
- package/dist/index.d.mts +10 -8
- package/dist/index.d.ts +10 -8
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3 -2
- package/dist/{notification.interface-aLEJbA_g.d.ts → notification.interface-C1T1C2ee.d.ts} +1 -100
- package/dist/{notification.interface-DLZGtV7Z.d.mts → notification.interface-DIxR23eS.d.mts} +1 -100
- package/dist/{s3.service-CVgLWaDc.d.mts → s3.service-0BTClOYO.d.mts} +2 -2
- package/dist/{s3.service-SLlX0Zbz.d.ts → s3.service-CT27Fm1s.d.ts} +2 -2
- package/dist/server/index.d.mts +4 -3
- package/dist/server/index.d.ts +4 -3
- package/dist/server/index.js +3 -3
- package/dist/server/index.mjs +1 -1
- package/dist/types-CQSjy7et.d.mts +101 -0
- package/dist/types-DHOxe8rc.d.ts +101 -0
- package/dist/usePageUrlGenerator-tjq2mlDV.d.ts +14 -0
- package/dist/usePageUrlGenerator-uOnyJ6j2.d.mts +14 -0
- package/dist/{useSocket-BkxHHujj.d.mts → useSocket-B1fMIr17.d.mts} +1 -1
- package/dist/{useSocket-CMDjWFYm.d.ts → useSocket-BdJTBXKv.d.ts} +1 -1
- package/package.json +20 -1
- package/src/client/config.ts +9 -1
- package/src/core/registry/helpStore.ts +45 -0
- package/src/features/assistant/contexts/AssistantContext.tsx +35 -19
- package/src/features/assistant/contexts/__tests__/AssistantContext.spec.tsx +66 -6
- 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 +18 -8
- package/src/features/assistant-message/components/parts/MessageSourcesPanel.tsx +6 -4
- package/src/features/assistant-message/components/parts/tabs/ContentsTab.tsx +5 -1
- package/src/features/assistant-message/components/parts/tabs/ReferencesTab.tsx +2 -1
- package/src/features/assistant-message/data/AssistantMessage.ts +27 -1
- package/src/features/assistant-message/data/AssistantMessageInterface.ts +1 -0
- package/src/features/assistant-message/data/__tests__/AssistantMessage.spec.ts +7 -3
- package/src/features/help/components/HelpArticleBody.tsx +54 -0
- package/src/features/help/components/HelpAskAi.tsx +36 -0
- package/src/features/help/components/HelpAssistantSheet.tsx +53 -0
- package/src/features/help/components/HelpHeader.tsx +40 -0
- package/src/features/help/components/HelpHint.tsx +77 -0
- package/src/features/help/components/HelpSearchResultRow.tsx +51 -0
- package/src/features/help/components/HelpSideNav.tsx +84 -0
- package/src/features/help/components/HelpTOC.tsx +49 -0
- package/src/features/help/components/__tests__/HelpAskAi.spec.tsx +68 -0
- package/src/features/help/components/__tests__/HelpAssistantSheet.spec.tsx +36 -0
- package/src/features/help/components/__tests__/HelpHint.spec.tsx +50 -0
- package/src/features/help/components/__tests__/HelpSearchResultRow.spec.tsx +59 -0
- package/src/features/help/components/__tests__/HelpSideNav.spec.tsx +52 -0
- package/src/features/help/components/mdx/Callout.tsx +21 -0
- package/src/features/help/components/mdx/EntityRef.tsx +18 -0
- package/src/features/help/components/mdx/KeyBinding.tsx +6 -0
- package/src/features/help/components/mdx/Related.tsx +33 -0
- package/src/features/help/components/mdx/Screenshot.tsx +9 -0
- package/src/features/help/components/mdx/Steps.tsx +21 -0
- package/src/features/help/components/mdx/Video.tsx +8 -0
- package/src/features/help/components/mdx/mdx-server-components.ts +23 -0
- package/src/features/help/components/mdx/mdxComponents.ts +9 -0
- package/src/features/help/contexts/HelpContext.spec.tsx +28 -0
- package/src/features/help/contexts/HelpContext.tsx +24 -0
- package/src/features/help/hooks/useHelp.ts +1 -0
- package/src/features/help/hooks/useHelpArticle.ts +7 -0
- package/src/features/help/hooks/useHelpFilter.ts +27 -0
- package/src/features/help/hooks/useHelpManifest.ts +5 -0
- package/src/features/help/i18n-keys.ts +34 -0
- package/src/features/help/index.ts +27 -0
- package/src/features/help/interfaces/help-content-config.interface.ts +17 -0
- package/src/features/help/server/__tests__/createHelpAssetRouteHandler.spec.ts +43 -0
- package/src/features/help/server/createHelpAssetRouteHandler.ts +35 -0
- package/src/features/help/server/generateHelpArticleMetadata.ts +18 -0
- package/src/features/help/server/generateHelpArticleStaticParams.ts +7 -0
- package/src/features/help/server/generateHelpModeStaticParams.ts +5 -0
- package/src/features/help/server/getHelpContent.ts +17 -0
- package/src/features/help/server/index.ts +8 -0
- package/src/features/help/server/serializeHelpArticle.tsx +46 -0
- package/src/features/help/server-entry.ts +20 -0
- package/src/features/help/types/help-article.types.ts +37 -0
- package/src/features/help/utils/__tests__/helpNavigation.spec.ts +70 -0
- package/src/features/help/utils/articleUrl.ts +13 -0
- package/src/features/help/utils/helpNavigation.ts +29 -0
- package/src/features/how-to/HowToModule.ts +1 -1
- package/src/features/how-to/data/HowTo.ts +21 -3
- package/src/features/how-to/data/HowToInterface.ts +1 -0
- package/src/index.ts +4 -0
- package/src/shadcnui/ui/context-menu.tsx +3 -1
- package/src/shadcnui/ui/popover.tsx +3 -1
- package/dist/BlockNoteEditor-RWRVIEZC.js.map +0 -1
- package/dist/chunk-2IRWQVG4.js.map +0 -1
- package/dist/chunk-CFECWLHH.mjs.map +0 -1
- package/dist/chunk-RXXZGPC3.js.map +0 -1
- package/dist/chunk-VLDLERJN.js.map +0 -1
- package/dist/chunk-WSOPEIRP.mjs.map +0 -1
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { LucideIcon } from 'lucide-react';
|
|
2
|
+
import { A as ApiRequestDataTypeInterface, F as FieldSelector } from './ApiRequestDataTypeInterface-CYEcRUrh.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Permission actions
|
|
6
|
+
*/
|
|
7
|
+
declare enum Action {
|
|
8
|
+
Read = "read",
|
|
9
|
+
Create = "create",
|
|
10
|
+
Update = "update",
|
|
11
|
+
Delete = "delete"
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Generic permission check type.
|
|
15
|
+
* Can be a boolean or a function that checks permissions dynamically.
|
|
16
|
+
* @template T - The data type being checked
|
|
17
|
+
* @template U - The user type (defaults to PermissionUser)
|
|
18
|
+
*/
|
|
19
|
+
type PermissionCheck<T, U = PermissionUser> = boolean | ((user?: U | string, data?: T) => boolean);
|
|
20
|
+
/**
|
|
21
|
+
* Page URL configuration for modules
|
|
22
|
+
*/
|
|
23
|
+
type PageUrl = {
|
|
24
|
+
pageUrl?: string;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Module permission definition wrapper
|
|
28
|
+
*/
|
|
29
|
+
type ModulePermissionDefinition<T> = {
|
|
30
|
+
interface: T;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Base module definition
|
|
34
|
+
*/
|
|
35
|
+
type ModuleDefinition = {
|
|
36
|
+
pageUrl?: string;
|
|
37
|
+
name: string;
|
|
38
|
+
model: any;
|
|
39
|
+
feature?: string;
|
|
40
|
+
moduleId?: string;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Permission configuration for a module.
|
|
44
|
+
* Can be a boolean (allow/deny all) or a string path for dynamic checks.
|
|
45
|
+
*/
|
|
46
|
+
interface PermissionConfig {
|
|
47
|
+
create: boolean | string;
|
|
48
|
+
read: boolean | string;
|
|
49
|
+
update: boolean | string;
|
|
50
|
+
delete: boolean | string;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Generic interface for a module that has permissions.
|
|
54
|
+
* Apps should ensure their Module class implements this.
|
|
55
|
+
*/
|
|
56
|
+
interface PermissionModule {
|
|
57
|
+
id: string;
|
|
58
|
+
permissions: PermissionConfig;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Generic interface for a user that has modules with permissions.
|
|
62
|
+
* Apps should ensure their User class implements this.
|
|
63
|
+
*/
|
|
64
|
+
interface PermissionUser {
|
|
65
|
+
id: string;
|
|
66
|
+
modules: PermissionModule[];
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Module definition with permissions - extends ApiRequestDataTypeInterface
|
|
70
|
+
*/
|
|
71
|
+
type ModuleWithPermissions = ApiRequestDataTypeInterface & {
|
|
72
|
+
pageUrl?: string;
|
|
73
|
+
feature?: string;
|
|
74
|
+
moduleId?: string;
|
|
75
|
+
icon?: LucideIcon;
|
|
76
|
+
inclusions?: Record<string, {
|
|
77
|
+
types?: string[];
|
|
78
|
+
fields?: FieldSelector<any>[];
|
|
79
|
+
include?: string[];
|
|
80
|
+
}>;
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* Factory type for creating module definitions
|
|
84
|
+
*/
|
|
85
|
+
type ModuleFactory = (params: {
|
|
86
|
+
pageUrl?: string;
|
|
87
|
+
name: string;
|
|
88
|
+
cache?: string | "days" | "default" | "hours" | "max" | "minutes" | "seconds" | "weeks";
|
|
89
|
+
model: any;
|
|
90
|
+
feature?: string;
|
|
91
|
+
moduleId?: string;
|
|
92
|
+
icon?: LucideIcon;
|
|
93
|
+
identifier?: string[];
|
|
94
|
+
inclusions?: Record<string, {
|
|
95
|
+
types?: string[];
|
|
96
|
+
fields?: FieldSelector<any>[];
|
|
97
|
+
include?: string[];
|
|
98
|
+
}>;
|
|
99
|
+
}) => ModuleWithPermissions;
|
|
100
|
+
|
|
101
|
+
export { Action as A, type ModuleWithPermissions as M, type PageUrl as P, type PermissionCheck as a, type ModulePermissionDefinition as b, type ModuleDefinition as c, type PermissionConfig as d, type PermissionModule as e, type PermissionUser as f, type ModuleFactory as g };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { P as PageUrl } from './types-DHOxe8rc.js';
|
|
2
|
+
|
|
3
|
+
declare function usePageUrlGenerator(): (params: {
|
|
4
|
+
page?: PageUrl | string;
|
|
5
|
+
id?: string;
|
|
6
|
+
childPage?: PageUrl | string;
|
|
7
|
+
childId?: string;
|
|
8
|
+
additionalParameters?: {
|
|
9
|
+
[key: string]: string | string[] | undefined;
|
|
10
|
+
};
|
|
11
|
+
language?: string;
|
|
12
|
+
}) => string;
|
|
13
|
+
|
|
14
|
+
export { usePageUrlGenerator as u };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { P as PageUrl } from './types-CQSjy7et.mjs';
|
|
2
|
+
|
|
3
|
+
declare function usePageUrlGenerator(): (params: {
|
|
4
|
+
page?: PageUrl | string;
|
|
5
|
+
id?: string;
|
|
6
|
+
childPage?: PageUrl | string;
|
|
7
|
+
childId?: string;
|
|
8
|
+
additionalParameters?: {
|
|
9
|
+
[key: string]: string | string[] | undefined;
|
|
10
|
+
};
|
|
11
|
+
language?: string;
|
|
12
|
+
}) => string;
|
|
13
|
+
|
|
14
|
+
export { usePageUrlGenerator as u };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@carlonicora/nextjs-jsonapi",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.109.0",
|
|
4
4
|
"description": "Next.js JSON:API client with server/client support and caching",
|
|
5
5
|
"author": "Carlo Nicora",
|
|
6
6
|
"license": "GPL-3.0-or-later",
|
|
@@ -52,6 +52,21 @@
|
|
|
52
52
|
"types": "./dist/billing/index.d.ts",
|
|
53
53
|
"import": "./dist/billing/index.mjs",
|
|
54
54
|
"require": "./dist/billing/index.js"
|
|
55
|
+
},
|
|
56
|
+
"./help-asset-route": {
|
|
57
|
+
"types": "./dist/features/help/server/createHelpAssetRouteHandler.d.ts",
|
|
58
|
+
"import": "./dist/features/help/server/createHelpAssetRouteHandler.mjs",
|
|
59
|
+
"require": "./dist/features/help/server/createHelpAssetRouteHandler.js"
|
|
60
|
+
},
|
|
61
|
+
"./help": {
|
|
62
|
+
"types": "./dist/features/help/index.d.ts",
|
|
63
|
+
"import": "./dist/features/help/index.mjs",
|
|
64
|
+
"require": "./dist/features/help/index.js"
|
|
65
|
+
},
|
|
66
|
+
"./help/server": {
|
|
67
|
+
"types": "./dist/features/help/server.d.ts",
|
|
68
|
+
"import": "./dist/features/help/server.mjs",
|
|
69
|
+
"require": "./dist/features/help/server.js"
|
|
55
70
|
}
|
|
56
71
|
},
|
|
57
72
|
"scripts": {
|
|
@@ -139,6 +154,10 @@
|
|
|
139
154
|
"react-markdown": "^10.1.0",
|
|
140
155
|
"react-resizable-panels": "^4.11.1",
|
|
141
156
|
"recharts": "^3.8.1",
|
|
157
|
+
"fuse.js": "^7.0.0",
|
|
158
|
+
"next-mdx-remote": "^5.0.0",
|
|
159
|
+
"rehype-autolink-headings": "^7.1.0",
|
|
160
|
+
"rehype-slug": "^6.0.0",
|
|
142
161
|
"remark-gfm": "^4.0.1",
|
|
143
162
|
"shadcn": "^4.8.0",
|
|
144
163
|
"shepherd.js": "^15.2.2",
|
package/src/client/config.ts
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import { ModuleWithPermissions } from "../permissions/types";
|
|
4
4
|
import { setBootstrapper } from "../core/registry/bootstrapStore";
|
|
5
|
+
import { _setStaticHelpContent } from "../core/registry/helpStore";
|
|
6
|
+
import type { HelpContentConfig } from "../features/help/interfaces/help-content-config.interface";
|
|
5
7
|
|
|
6
8
|
// Config storage for client-side contexts
|
|
7
9
|
let _clientConfig: {
|
|
@@ -16,6 +18,7 @@ let _clientConfig: {
|
|
|
16
18
|
/**
|
|
17
19
|
* Configure the JSON:API client. This is the main configuration function.
|
|
18
20
|
* This is typically called during app initialization.
|
|
21
|
+
* @param config.helpContent - Optional help-content config (manifest, brand, redirects). Forwarded to the help feature's globalThis-backed store; not stored on the client config.
|
|
19
22
|
*/
|
|
20
23
|
export function configureJsonApi(config: {
|
|
21
24
|
apiUrl: string;
|
|
@@ -24,8 +27,13 @@ export function configureJsonApi(config: {
|
|
|
24
27
|
bootstrapper?: () => void;
|
|
25
28
|
additionalHeaders?: Record<string, string>;
|
|
26
29
|
stripePublishableKey?: string;
|
|
30
|
+
helpContent?: HelpContentConfig;
|
|
27
31
|
}): void {
|
|
28
|
-
|
|
32
|
+
const { helpContent, ...rest } = config;
|
|
33
|
+
_clientConfig = rest;
|
|
34
|
+
if (helpContent) {
|
|
35
|
+
_setStaticHelpContent(helpContent);
|
|
36
|
+
}
|
|
29
37
|
// Register and call bootstrapper to register all modules
|
|
30
38
|
if (config.bootstrapper) {
|
|
31
39
|
setBootstrapper(config.bootstrapper);
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized help-content store accessible from client and server contexts.
|
|
3
|
+
* Uses globalThis Symbol keys to persist across HMR/Turbopack reloads and to
|
|
4
|
+
* bridge between the client-side `configureJsonApi` (in `client/config.ts`)
|
|
5
|
+
* and server-side consumers (HelpProvider, getHelpContent, serializeHelpArticle).
|
|
6
|
+
*
|
|
7
|
+
* NO external dependencies to avoid circular imports.
|
|
8
|
+
*
|
|
9
|
+
* Pattern mirrors `bootstrapStore.ts`.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// Help content config shape is duplicated here as `unknown` to avoid pulling
|
|
13
|
+
// the help feature into this core file. Callers cast on read.
|
|
14
|
+
type HelpReader = (article: { path: string }) => Promise<string>;
|
|
15
|
+
|
|
16
|
+
const HELP_CONTENT_KEY = Symbol.for("nextjs-jsonapi:helpContent");
|
|
17
|
+
const HELP_READER_KEY = Symbol.for("nextjs-jsonapi:helpReader");
|
|
18
|
+
|
|
19
|
+
const globalStore = globalThis as unknown as {
|
|
20
|
+
[HELP_CONTENT_KEY]?: unknown | null;
|
|
21
|
+
[HELP_READER_KEY]?: HelpReader | null;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
if (globalStore[HELP_CONTENT_KEY] === undefined) {
|
|
25
|
+
globalStore[HELP_CONTENT_KEY] = null;
|
|
26
|
+
}
|
|
27
|
+
if (globalStore[HELP_READER_KEY] === undefined) {
|
|
28
|
+
globalStore[HELP_READER_KEY] = null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function _setStaticHelpContent(cfg: unknown | null): void {
|
|
32
|
+
globalStore[HELP_CONTENT_KEY] = cfg;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function _getStaticHelpContent<T = unknown>(): T | null {
|
|
36
|
+
return (globalStore[HELP_CONTENT_KEY] as T | null) ?? null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function setHelpReader(reader: HelpReader): void {
|
|
40
|
+
globalStore[HELP_READER_KEY] = reader;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function _getStaticHelpReader(): HelpReader | null {
|
|
44
|
+
return globalStore[HELP_READER_KEY] ?? null;
|
|
45
|
+
}
|
|
@@ -20,7 +20,7 @@ interface AssistantContextValue {
|
|
|
20
20
|
sending: boolean;
|
|
21
21
|
status?: string;
|
|
22
22
|
failedMessageIds: Set<string>;
|
|
23
|
-
sendMessage(content: string): Promise<void>;
|
|
23
|
+
sendMessage(content: string, opts?: { howToMode?: boolean; limitToHowToId?: string }): Promise<void>;
|
|
24
24
|
retrySend(tempId: string): Promise<void>;
|
|
25
25
|
selectThread(id: string): Promise<void>;
|
|
26
26
|
startNew(): void;
|
|
@@ -34,10 +34,17 @@ interface Props {
|
|
|
34
34
|
children: React.ReactNode;
|
|
35
35
|
dehydratedAssistant?: JsonApiHydratedDataInterface;
|
|
36
36
|
dehydratedMessages?: JsonApiHydratedDataInterface[];
|
|
37
|
+
/**
|
|
38
|
+
* When `true` (default), the provider mutates the browser URL on
|
|
39
|
+
* create/selectThread/startNew (e.g. `/assistants/{id}`). Set to `false`
|
|
40
|
+
* when the assistant is hosted inside a sheet / overlay so the user's
|
|
41
|
+
* current route is preserved.
|
|
42
|
+
*/
|
|
43
|
+
manageUrl?: boolean;
|
|
37
44
|
}
|
|
38
45
|
|
|
39
46
|
function stripOptimistic(list: AssistantMessageInterface[]): AssistantMessageInterface[] {
|
|
40
|
-
return list.filter((m) => !m.
|
|
47
|
+
return list.filter((m) => !m.isOptimistic);
|
|
41
48
|
}
|
|
42
49
|
|
|
43
50
|
function nextPosition(list: AssistantMessageInterface[]): number {
|
|
@@ -55,7 +62,7 @@ function withPatchedTitle(source: AssistantInterface, title: string): AssistantI
|
|
|
55
62
|
});
|
|
56
63
|
}
|
|
57
64
|
|
|
58
|
-
export function AssistantProvider({ children, dehydratedAssistant, dehydratedMessages }: Props) {
|
|
65
|
+
export function AssistantProvider({ children, dehydratedAssistant, dehydratedMessages, manageUrl = true }: Props) {
|
|
59
66
|
const t = useTranslations();
|
|
60
67
|
const generateUrl = usePageUrlGenerator();
|
|
61
68
|
|
|
@@ -73,7 +80,7 @@ export function AssistantProvider({ children, dehydratedAssistant, dehydratedMes
|
|
|
73
80
|
const { socket } = useSocketContext();
|
|
74
81
|
|
|
75
82
|
const sendMessage = useCallback(
|
|
76
|
-
async (content: string) => {
|
|
83
|
+
async (content: string, opts?: { howToMode?: boolean; limitToHowToId?: string }) => {
|
|
77
84
|
const trimmed = content.trim();
|
|
78
85
|
if (!trimmed) return;
|
|
79
86
|
|
|
@@ -94,18 +101,24 @@ export function AssistantProvider({ children, dehydratedAssistant, dehydratedMes
|
|
|
94
101
|
|
|
95
102
|
try {
|
|
96
103
|
if (!assistant) {
|
|
97
|
-
const created = await AssistantService.create({
|
|
104
|
+
const created = await AssistantService.create({
|
|
105
|
+
firstMessage: trimmed,
|
|
106
|
+
howToMode: opts?.howToMode,
|
|
107
|
+
limitToHowToId: opts?.limitToHowToId,
|
|
108
|
+
});
|
|
98
109
|
const msgs = await AssistantMessageService.findByAssistant({ assistantId: created.id });
|
|
99
110
|
setAssistant(created);
|
|
100
111
|
setMessages(msgs);
|
|
101
112
|
setThreads((prev) => [created, ...prev]);
|
|
102
|
-
if (typeof window !== "undefined") {
|
|
113
|
+
if (manageUrl && typeof window !== "undefined") {
|
|
103
114
|
window.history.replaceState(null, "", `/assistants/${created.id}`);
|
|
104
115
|
}
|
|
105
116
|
} else {
|
|
106
117
|
const result = await AssistantService.appendMessage({
|
|
107
118
|
assistantId: assistant.id,
|
|
108
119
|
content: trimmed,
|
|
120
|
+
howToMode: opts?.howToMode,
|
|
121
|
+
limitToHowToId: opts?.limitToHowToId,
|
|
109
122
|
});
|
|
110
123
|
setMessages((prev) => [...stripOptimistic(prev), ...result]);
|
|
111
124
|
}
|
|
@@ -142,17 +155,20 @@ export function AssistantProvider({ children, dehydratedAssistant, dehydratedMes
|
|
|
142
155
|
[messages, sendMessage],
|
|
143
156
|
);
|
|
144
157
|
|
|
145
|
-
const selectThread = useCallback(
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
158
|
+
const selectThread = useCallback(
|
|
159
|
+
async (id: string) => {
|
|
160
|
+
const [target, msgs] = await Promise.all([
|
|
161
|
+
AssistantService.findOne({ id }),
|
|
162
|
+
AssistantMessageService.findByAssistant({ assistantId: id }),
|
|
163
|
+
]);
|
|
164
|
+
setAssistant(target);
|
|
165
|
+
setMessages(msgs);
|
|
166
|
+
if (manageUrl && typeof window !== "undefined") {
|
|
167
|
+
window.history.replaceState(null, "", `/assistants/${id}`);
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
[manageUrl],
|
|
171
|
+
);
|
|
156
172
|
|
|
157
173
|
const renameThread = useCallback(async (id: string, title: string) => {
|
|
158
174
|
await AssistantService.rename({ id, title });
|
|
@@ -164,10 +180,10 @@ export function AssistantProvider({ children, dehydratedAssistant, dehydratedMes
|
|
|
164
180
|
setAssistant(undefined);
|
|
165
181
|
setMessages([]);
|
|
166
182
|
setFailedMessageIds(new Set());
|
|
167
|
-
if (typeof window !== "undefined") {
|
|
183
|
+
if (manageUrl && typeof window !== "undefined") {
|
|
168
184
|
window.history.replaceState(null, "", "/assistants");
|
|
169
185
|
}
|
|
170
|
-
}, []);
|
|
186
|
+
}, [manageUrl]);
|
|
171
187
|
|
|
172
188
|
const deleteThread = useCallback(async (id: string) => {
|
|
173
189
|
await AssistantService.delete({ id });
|
|
@@ -264,7 +264,7 @@ describe("AssistantContext", () => {
|
|
|
264
264
|
|
|
265
265
|
// Before the server responds, the optimistic user bubble must be visible.
|
|
266
266
|
expect(result.current.messages.map((m) => m.content)).toContain("follow-up");
|
|
267
|
-
expect(result.current.messages.some((m) => m.
|
|
267
|
+
expect(result.current.messages.some((m) => m.isOptimistic && m.role === "user")).toBe(true);
|
|
268
268
|
expect(result.current.sending).toBe(true);
|
|
269
269
|
|
|
270
270
|
await act(async () => {
|
|
@@ -276,7 +276,7 @@ describe("AssistantContext", () => {
|
|
|
276
276
|
});
|
|
277
277
|
|
|
278
278
|
// After reconciliation, no tmp-* remains, and server messages are appended.
|
|
279
|
-
expect(result.current.messages.some((m) => m.
|
|
279
|
+
expect(result.current.messages.some((m) => m.isOptimistic)).toBe(false);
|
|
280
280
|
expect(result.current.messages.map((m) => m.content)).toEqual(["follow-up", "reply"]);
|
|
281
281
|
expect(result.current.sending).toBe(false);
|
|
282
282
|
});
|
|
@@ -307,7 +307,7 @@ describe("AssistantContext", () => {
|
|
|
307
307
|
|
|
308
308
|
// Before the server responds: thread has exactly the optimistic user bubble.
|
|
309
309
|
expect(result.current.messages).toHaveLength(1);
|
|
310
|
-
expect(result.current.messages[0].
|
|
310
|
+
expect(result.current.messages[0].isOptimistic).toBe(true);
|
|
311
311
|
expect(result.current.messages[0].content).toBe("first question");
|
|
312
312
|
expect(result.current.assistant).toBeUndefined();
|
|
313
313
|
expect(result.current.sending).toBe(true);
|
|
@@ -319,11 +319,71 @@ describe("AssistantContext", () => {
|
|
|
319
319
|
|
|
320
320
|
// After reconciliation: assistant set, URL replaced, server messages only.
|
|
321
321
|
expect(result.current.assistant?.id).toBe("a-1");
|
|
322
|
-
expect(result.current.messages.some((m) => m.
|
|
322
|
+
expect(result.current.messages.some((m) => m.isOptimistic)).toBe(false);
|
|
323
323
|
expect(result.current.messages).toHaveLength(2);
|
|
324
324
|
expect(replaceState).toHaveBeenCalledWith(null, "", "/assistants/a-1");
|
|
325
325
|
});
|
|
326
326
|
|
|
327
|
+
it("forwards howToMode to AssistantService.create on first send", async () => {
|
|
328
|
+
const replaceState = vi.spyOn(window.history, "replaceState").mockImplementation(() => {});
|
|
329
|
+
const created = buildAssistantStub({ id: "a-1" });
|
|
330
|
+
AssistantService.create = vi.fn().mockResolvedValue(created);
|
|
331
|
+
AssistantMessageService.findByAssistant = vi.fn().mockResolvedValue([]);
|
|
332
|
+
|
|
333
|
+
const { result } = renderHook(() => useAssistantContext(), {
|
|
334
|
+
wrapper: ({ children }) => <AssistantProvider>{children}</AssistantProvider>,
|
|
335
|
+
});
|
|
336
|
+
await act(async () => {
|
|
337
|
+
await result.current.sendMessage("hi", { howToMode: true });
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
expect(AssistantService.create).toHaveBeenCalledWith(
|
|
341
|
+
expect.objectContaining({ firstMessage: "hi", howToMode: true }),
|
|
342
|
+
);
|
|
343
|
+
replaceState.mockRestore();
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
it("forwards howToMode to AssistantService.appendMessage on follow-up send", async () => {
|
|
347
|
+
const existing = buildAssistantDehydrated({ id: "a-2", title: "Existing" });
|
|
348
|
+
AssistantService.appendMessage = vi.fn().mockResolvedValue([]);
|
|
349
|
+
|
|
350
|
+
const { result } = renderHook(() => useAssistantContext(), {
|
|
351
|
+
wrapper: ({ children }) => <AssistantProvider dehydratedAssistant={existing}>{children}</AssistantProvider>,
|
|
352
|
+
});
|
|
353
|
+
await act(async () => {
|
|
354
|
+
await result.current.sendMessage("follow up", { howToMode: true, limitToHowToId: "ht-1" });
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
expect(AssistantService.appendMessage).toHaveBeenCalledWith(
|
|
358
|
+
expect.objectContaining({
|
|
359
|
+
assistantId: "a-2",
|
|
360
|
+
content: "follow up",
|
|
361
|
+
howToMode: true,
|
|
362
|
+
limitToHowToId: "ht-1",
|
|
363
|
+
}),
|
|
364
|
+
);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
it("calls service without opts when called with content only (regression)", async () => {
|
|
368
|
+
const replaceState = vi.spyOn(window.history, "replaceState").mockImplementation(() => {});
|
|
369
|
+
const created = buildAssistantStub({ id: "a-1" });
|
|
370
|
+
AssistantService.create = vi.fn().mockResolvedValue(created);
|
|
371
|
+
AssistantMessageService.findByAssistant = vi.fn().mockResolvedValue([]);
|
|
372
|
+
|
|
373
|
+
const { result } = renderHook(() => useAssistantContext(), {
|
|
374
|
+
wrapper: ({ children }) => <AssistantProvider>{children}</AssistantProvider>,
|
|
375
|
+
});
|
|
376
|
+
await act(async () => {
|
|
377
|
+
await result.current.sendMessage("hi");
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
expect(AssistantService.create).toHaveBeenCalledWith(expect.objectContaining({ firstMessage: "hi" }));
|
|
381
|
+
const call = (AssistantService.create as ReturnType<typeof vi.fn>).mock.calls[0][0];
|
|
382
|
+
expect(call.howToMode).toBeUndefined();
|
|
383
|
+
expect(call.limitToHowToId).toBeUndefined();
|
|
384
|
+
replaceState.mockRestore();
|
|
385
|
+
});
|
|
386
|
+
|
|
327
387
|
it("sendMessage failure: optimistic message stays and its id lands in failedMessageIds", async () => {
|
|
328
388
|
const existing = buildAssistantDehydrated({ id: "a-x", title: "Ex" });
|
|
329
389
|
AssistantService.appendMessage = vi.fn().mockRejectedValue(new Error("boom"));
|
|
@@ -335,7 +395,7 @@ describe("AssistantContext", () => {
|
|
|
335
395
|
await result.current.sendMessage("oops").catch(() => {});
|
|
336
396
|
});
|
|
337
397
|
|
|
338
|
-
const optimistic = result.current.messages.find((m) => m.
|
|
398
|
+
const optimistic = result.current.messages.find((m) => m.isOptimistic);
|
|
339
399
|
expect(optimistic).toBeDefined();
|
|
340
400
|
expect(optimistic!.content).toBe("oops");
|
|
341
401
|
expect(result.current.failedMessageIds.has(optimistic!.id)).toBe(true);
|
|
@@ -368,7 +428,7 @@ describe("AssistantContext", () => {
|
|
|
368
428
|
});
|
|
369
429
|
|
|
370
430
|
expect(result.current.failedMessageIds.has(failedId!)).toBe(false);
|
|
371
|
-
expect(result.current.messages.some((m) => m.
|
|
431
|
+
expect(result.current.messages.some((m) => m.isOptimistic)).toBe(false);
|
|
372
432
|
expect(result.current.messages.map((m) => m.content)).toEqual(["retry-me", "ok"]);
|
|
373
433
|
expect(appendMock).toHaveBeenCalledTimes(2);
|
|
374
434
|
});
|
|
@@ -29,6 +29,8 @@ export class Assistant extends AbstractApiData implements AssistantInterface {
|
|
|
29
29
|
attributes: {
|
|
30
30
|
content: data.firstMessage,
|
|
31
31
|
...(data.title !== undefined ? { title: data.title } : {}),
|
|
32
|
+
...(data.howToMode !== undefined ? { howToMode: data.howToMode } : {}),
|
|
33
|
+
...(data.limitToHowToId !== undefined ? { limitToHowToId: data.limitToHowToId } : {}),
|
|
32
34
|
},
|
|
33
35
|
},
|
|
34
36
|
included: [],
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AbstractService, EndpointCreator, HttpMethod, Modules } from "../../../core";
|
|
2
|
-
import {
|
|
2
|
+
import { AssistantMessage } from "../../assistant-message/data/AssistantMessage";
|
|
3
3
|
import { AssistantMessageInterface } from "../../assistant-message/data/AssistantMessageInterface";
|
|
4
|
+
import { AssistantInput, AssistantInterface } from "./AssistantInterface";
|
|
4
5
|
|
|
5
6
|
export class AssistantService extends AbstractService {
|
|
6
7
|
static async findOne(params: { id: string }): Promise<AssistantInterface> {
|
|
@@ -33,8 +34,18 @@ export class AssistantService extends AbstractService {
|
|
|
33
34
|
/**
|
|
34
35
|
* Sends a new user message to an existing assistant thread. The agent turn
|
|
35
36
|
* runs synchronously; the response is a two-element list: [user, assistant].
|
|
37
|
+
*
|
|
38
|
+
* Uses the dedicated AssistantMessage.createAppendMessageJsonApi method to
|
|
39
|
+
* build the JSON:API envelope; this is the architecture-compliant pairing
|
|
40
|
+
* with `overridesJsonApiCreation: true`.
|
|
36
41
|
*/
|
|
37
|
-
static async appendMessage(params: {
|
|
42
|
+
static async appendMessage(params: {
|
|
43
|
+
assistantId: string;
|
|
44
|
+
content: string;
|
|
45
|
+
howToMode?: boolean;
|
|
46
|
+
limitToHowToId?: string;
|
|
47
|
+
}): Promise<AssistantMessageInterface[]> {
|
|
48
|
+
const message = new AssistantMessage();
|
|
38
49
|
return this.callApi<AssistantMessageInterface[]>({
|
|
39
50
|
type: Modules.AssistantMessage,
|
|
40
51
|
method: HttpMethod.POST,
|
|
@@ -43,12 +54,11 @@ export class AssistantService extends AbstractService {
|
|
|
43
54
|
id: params.assistantId,
|
|
44
55
|
childEndpoint: Modules.AssistantMessage,
|
|
45
56
|
}).generate(),
|
|
46
|
-
input: {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
},
|
|
57
|
+
input: message.createAppendMessageJsonApi({
|
|
58
|
+
content: params.content,
|
|
59
|
+
howToMode: params.howToMode,
|
|
60
|
+
limitToHowToId: params.limitToHowToId,
|
|
61
|
+
}),
|
|
52
62
|
overridesJsonApiCreation: true,
|
|
53
63
|
});
|
|
54
64
|
}
|
|
@@ -55,12 +55,14 @@ export function MessageSourcesPanel({ message, isLatestAssistant, onSelectFollow
|
|
|
55
55
|
const suggestionsCount = isLatestAssistant ? message.suggestedQuestions.length : 0;
|
|
56
56
|
|
|
57
57
|
const contentsCount = useMemo(() => {
|
|
58
|
-
|
|
59
|
-
//
|
|
60
|
-
//
|
|
58
|
+
// Count unique nodeIds across citations. Mirrors what ContentsTab actually
|
|
59
|
+
// renders (one row per cited source), instead of `sources.size` which can
|
|
60
|
+
// include entities the fetch returned but no citation references.
|
|
61
61
|
const ids = new Set<string>();
|
|
62
62
|
for (const c of message.citations) {
|
|
63
|
-
if (c.nodeId)
|
|
63
|
+
if (!c.nodeId) continue;
|
|
64
|
+
if (sources && !sources.has(c.nodeId)) continue;
|
|
65
|
+
ids.add(c.nodeId);
|
|
64
66
|
}
|
|
65
67
|
return ids.size;
|
|
66
68
|
}, [message.citations, sources]);
|
|
@@ -64,7 +64,11 @@ export function ContentsTab({ citations, sources }: Props) {
|
|
|
64
64
|
} catch {
|
|
65
65
|
return null;
|
|
66
66
|
}
|
|
67
|
-
|
|
67
|
+
// Help-content HowTos are public articles, not admin records: route to
|
|
68
|
+
// /help/<mode>/<slug> instead of the module's /administration/howtos
|
|
69
|
+
// page when the entity carries a helpContentSlug.
|
|
70
|
+
const helpContentSlug = (source as any).helpContentSlug as string | undefined;
|
|
71
|
+
const href = helpContentSlug ? `/help/${helpContentSlug}` : generate({ page: module, id: source.id });
|
|
68
72
|
const name = (source as any).name ?? source.identifier;
|
|
69
73
|
return (
|
|
70
74
|
<TableRow key={`${source.type}/${source.id}`}>
|
|
@@ -33,7 +33,8 @@ export function ReferencesTab({ references }: Props) {
|
|
|
33
33
|
} catch {
|
|
34
34
|
return null;
|
|
35
35
|
}
|
|
36
|
-
const
|
|
36
|
+
const helpContentSlug = (ref as any).helpContentSlug as string | undefined;
|
|
37
|
+
const href = helpContentSlug ? `/help/${helpContentSlug}` : generate({ page: module, id: ref.id });
|
|
37
38
|
return (
|
|
38
39
|
<TableRow key={`${ref.type}/${ref.id}`}>
|
|
39
40
|
<TableCell>
|