@carlonicora/nextjs-jsonapi 1.77.3 → 1.79.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 +12 -0
- package/dist/AssistantInterface-DfDcz0gJ.d.ts +12 -0
- package/dist/AssistantMessageInterface-DWnbd6J7.d.ts +36 -0
- package/dist/AssistantMessageInterface-Mla6kgPe.d.mts +36 -0
- package/dist/{AuthComponent-Blbs06ud.d.ts → AuthComponent-B6DIk8Vf.d.ts} +1 -1
- package/dist/{AuthComponent-huIaK5rm.d.mts → AuthComponent-BKI0ZbtD.d.mts} +1 -1
- package/dist/{BlockNoteEditor-7HAAXN3H.mjs → BlockNoteEditor-6CBDTVKV.mjs} +4 -4
- package/dist/{BlockNoteEditor-UB7T7V67.js → BlockNoteEditor-EH4HWI7H.js} +14 -14
- package/dist/{BlockNoteEditor-UB7T7V67.js.map → BlockNoteEditor-EH4HWI7H.js.map} +1 -1
- package/dist/RbacTypes-BTbr27Ew.d.mts +43 -0
- package/dist/RbacTypes-BTbr27Ew.d.ts +43 -0
- package/dist/{auth.interface-CQJ6A2Cj.d.ts → auth.interface-BBUgMZzs.d.ts} +1 -1
- package/dist/{auth.interface-Bdq7-8iV.d.mts → auth.interface-XYEREOD6.d.mts} +1 -1
- package/dist/billing/index.js +346 -346
- package/dist/billing/index.mjs +3 -3
- package/dist/{chunk-FKLP4NED.js → chunk-5IEWLLLD.js} +379 -18
- package/dist/chunk-5IEWLLLD.js.map +1 -0
- package/dist/{chunk-XI35ALWY.mjs → chunk-BKM5U3DE.mjs} +362 -1
- package/dist/chunk-BKM5U3DE.mjs.map +1 -0
- package/dist/{chunk-F44ET4AC.mjs → chunk-ENRSFVOS.mjs} +2657 -2264
- package/dist/chunk-ENRSFVOS.mjs.map +1 -0
- package/dist/{chunk-JOJZRGZL.mjs → chunk-MEWXQEVE.mjs} +38 -29
- package/dist/{chunk-JOJZRGZL.mjs.map → chunk-MEWXQEVE.mjs.map} +1 -1
- package/dist/{chunk-OTZEXASK.js → chunk-TWDSDTHU.js} +39 -30
- package/dist/chunk-TWDSDTHU.js.map +1 -0
- package/dist/{chunk-CV7UOUKQ.js → chunk-ZDP3MBUI.js} +1813 -1420
- package/dist/chunk-ZDP3MBUI.js.map +1 -0
- package/dist/client/index.d.mts +6 -24
- package/dist/client/index.d.ts +6 -24
- package/dist/client/index.js +4 -10
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +3 -9
- package/dist/components/index.d.mts +51 -34
- package/dist/components/index.d.ts +51 -34
- package/dist/components/index.js +4 -4
- package/dist/components/index.js.map +1 -1
- package/dist/components/index.mjs +9 -9
- package/dist/{config-B3jKt9P7.d.ts → config-B5oBQVEA.d.ts} +1 -1
- package/dist/{config-DkHF61xA.d.mts → config-Bx_uh22h.d.mts} +1 -1
- package/dist/contexts/index.d.mts +65 -4
- package/dist/contexts/index.d.ts +65 -4
- package/dist/contexts/index.js +12 -4
- package/dist/contexts/index.js.map +1 -1
- package/dist/contexts/index.mjs +11 -3
- package/dist/core/index.d.mts +126 -11
- package/dist/core/index.d.ts +126 -11
- package/dist/core/index.js +16 -2
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +15 -1
- package/dist/index.d.mts +118 -20
- package/dist/index.d.ts +118 -20
- package/dist/index.js +19 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +18 -2
- package/dist/{notification.interface-DG6obXUH.d.mts → notification.interface-DLZGtV7Z.d.mts} +1 -1
- package/dist/{notification.interface-DcSuc9CL.d.ts → notification.interface-aLEJbA_g.d.ts} +1 -1
- package/dist/{s3.service-DGilbikH.d.mts → s3.service-CVgLWaDc.d.mts} +2 -2
- package/dist/{s3.service-DjwEQJPe.d.ts → s3.service-SLlX0Zbz.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/useDataListRetriever-BqJSFBck.d.mts +33 -0
- package/dist/useDataListRetriever-BqJSFBck.d.ts +33 -0
- package/dist/{useSocket-CmzVtg32.d.mts → useSocket-BkxHHujj.d.mts} +1 -1
- package/dist/{useSocket-8eUtnL7J.d.ts → useSocket-CMDjWFYm.d.ts} +1 -1
- package/package.json +1 -1
- package/src/client/index.ts +0 -4
- package/src/components/index.ts +2 -3
- package/src/contexts/index.ts +2 -0
- package/src/core/index.ts +4 -0
- package/src/core/registry/ModuleRegistry.ts +10 -0
- package/src/features/assistant/AssistantModule.ts +19 -0
- package/src/features/assistant/components/containers/AssistantContainer.tsx +56 -0
- package/src/features/assistant/components/containers/__tests__/AssistantContainer.spec.tsx +101 -0
- package/src/features/assistant/components/index.ts +1 -0
- package/src/features/assistant/components/parts/AssistantComposer.tsx +56 -0
- package/src/features/assistant/components/parts/AssistantEmptyState.tsx +47 -0
- package/src/features/assistant/components/parts/AssistantSidebar.tsx +64 -0
- package/src/features/assistant/components/parts/AssistantStatusLine.tsx +19 -0
- package/src/features/assistant/components/parts/AssistantThread.tsx +36 -0
- package/src/features/assistant/components/parts/AssistantThreadHeader.tsx +91 -0
- package/src/features/assistant/components/parts/__tests__/AssistantComposer.spec.tsx +32 -0
- package/src/features/assistant/components/parts/__tests__/AssistantEmptyState.spec.tsx +27 -0
- package/src/features/assistant/components/parts/__tests__/AssistantSidebar.spec.tsx +58 -0
- package/src/features/assistant/components/parts/__tests__/AssistantStatusLine.spec.tsx +19 -0
- package/src/features/assistant/components/parts/__tests__/AssistantThread.spec.tsx +39 -0
- package/src/features/assistant/components/parts/__tests__/AssistantThreadHeader.spec.tsx +67 -0
- package/src/features/assistant/contexts/AssistantContext.tsx +255 -0
- package/src/features/assistant/contexts/__tests__/AssistantContext.spec.tsx +375 -0
- package/src/features/assistant/data/Assistant.ts +37 -0
- package/src/features/assistant/data/AssistantInterface.ts +11 -0
- package/src/features/assistant/data/AssistantService.ts +79 -0
- package/src/features/assistant/data/index.ts +3 -0
- package/src/features/assistant/index.ts +2 -0
- package/src/features/assistant/utils/__tests__/groupThreadsByBucket.spec.ts +24 -0
- package/src/features/assistant/utils/__tests__/resolveReferenceableModules.spec.ts +92 -0
- package/src/features/assistant/utils/groupThreadsByBucket.ts +26 -0
- package/src/features/assistant/utils/resolveReferenceableModules.ts +14 -0
- package/src/features/assistant-message/AssistantMessageModule.ts +28 -0
- package/src/features/assistant-message/components/MessageItem.tsx +60 -0
- package/src/features/assistant-message/components/MessageList.tsx +38 -0
- package/src/features/assistant-message/components/__tests__/MessageItem.spec.tsx +108 -0
- package/src/features/assistant-message/components/index.ts +2 -0
- package/src/features/assistant-message/components/parts/ReferenceBadges.tsx +46 -0
- package/src/features/assistant-message/components/parts/SuggestedFollowUps.tsx +52 -0
- package/src/features/assistant-message/components/parts/__tests__/ReferenceBadges.spec.tsx +59 -0
- package/src/features/assistant-message/components/parts/__tests__/SuggestedFollowUps.spec.tsx +29 -0
- package/src/features/assistant-message/data/AssistantMessage.ts +95 -0
- package/src/features/assistant-message/data/AssistantMessageInterface.ts +21 -0
- package/src/features/assistant-message/data/AssistantMessageService.ts +40 -0
- package/src/features/assistant-message/data/__tests__/AssistantMessage.spec.ts +158 -0
- package/src/features/assistant-message/data/index.ts +3 -0
- package/src/features/assistant-message/index.ts +2 -0
- package/src/features/rbac/components/RbacContainer.tsx +318 -49
- package/src/features/rbac/components/RbacPermissionPicker.tsx +144 -121
- package/src/features/rbac/contexts/RbacContext.tsx +209 -0
- package/src/features/rbac/contexts/index.ts +1 -0
- package/src/features/rbac/data/RbacMatrixModel.ts +84 -0
- package/src/features/rbac/data/RbacService.ts +61 -33
- package/src/features/rbac/data/RbacTypes.ts +28 -0
- package/src/features/rbac/data/index.ts +1 -0
- package/src/features/rbac/index.ts +1 -10
- package/src/features/rbac/rbac.module.ts +13 -0
- package/src/features/user/contexts/CurrentUserContext.tsx +5 -13
- package/src/features/user/contexts/__tests__/CurrentUserContext.spec.tsx +141 -0
- package/src/index.ts +4 -0
- package/dist/HowToInterface-BKhnkzBp.d.ts +0 -17
- package/dist/HowToInterface-Cj8OuQFf.d.mts +0 -17
- package/dist/ModulePathsInterface-BrdqgteS.d.mts +0 -31
- package/dist/ModulePathsInterface-DJKs7s_s.d.ts +0 -31
- package/dist/chunk-CV7UOUKQ.js.map +0 -1
- package/dist/chunk-F44ET4AC.mjs.map +0 -1
- package/dist/chunk-FKLP4NED.js.map +0 -1
- package/dist/chunk-OTZEXASK.js.map +0 -1
- package/dist/chunk-XI35ALWY.mjs.map +0 -1
- package/dist/useRbacState-C88O-5L8.d.ts +0 -77
- package/dist/useRbacState-mqYiRp3J.d.mts +0 -77
- package/src/features/rbac/components/RbacFeatureSection.tsx +0 -66
- package/src/features/rbac/components/RbacModuleTable.tsx +0 -121
- package/src/features/rbac/components/RbacToolbar.tsx +0 -40
- package/src/features/rbac/hooks/useRbacState.test.ts +0 -180
- package/src/features/rbac/hooks/useRbacState.ts +0 -319
- package/src/features/rbac/utils/RbacMigrationGenerator.test.ts +0 -124
- package/src/features/rbac/utils/RbacMigrationGenerator.ts +0 -184
- /package/dist/{BlockNoteEditor-7HAAXN3H.mjs.map → BlockNoteEditor-6CBDTVKV.mjs.map} +0 -0
package/dist/server/index.js
CHANGED
|
@@ -15,7 +15,7 @@ var _chunk3ZPK4QOBjs = require('../chunk-3ZPK4QOB.js');
|
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
var
|
|
18
|
+
var _chunk5IEWLLLDjs = require('../chunk-5IEWLLLD.js');
|
|
19
19
|
require('../chunk-LXKSUWAV.js');
|
|
20
20
|
require('../chunk-IBS6NI7D.js');
|
|
21
21
|
|
|
@@ -86,7 +86,7 @@ var ServerSession = class {
|
|
|
86
86
|
if (!rawModules) return false;
|
|
87
87
|
const modules = JSON.parse(_pako2.default.ungzip(Buffer.from(rawModules, "base64"), { to: "string" }));
|
|
88
88
|
const selectedModule = modules.find((module) => module.id === params.module.moduleId);
|
|
89
|
-
return
|
|
89
|
+
return _chunk5IEWLLLDjs.checkPermissionsFromServer.call(void 0, {
|
|
90
90
|
module: params.module,
|
|
91
91
|
action: params.action,
|
|
92
92
|
data: params.data,
|
|
@@ -296,5 +296,5 @@ _chunk7QVYU63Ejs.__name.call(void 0, ServerJsonApiDelete, "ServerJsonApiDelete")
|
|
|
296
296
|
|
|
297
297
|
|
|
298
298
|
|
|
299
|
-
exports.ServerAuthService =
|
|
299
|
+
exports.ServerAuthService = _chunk5IEWLLLDjs.AuthService; exports.ServerCompanyService = _chunk5IEWLLLDjs.CompanyService; exports.ServerContentService = _chunk5IEWLLLDjs.ContentService; exports.ServerFeatureService = _chunk5IEWLLLDjs.FeatureService; exports.ServerJsonApiDelete = ServerJsonApiDelete; exports.ServerJsonApiGet = ServerJsonApiGet; exports.ServerJsonApiPatch = ServerJsonApiPatch; exports.ServerJsonApiPost = ServerJsonApiPost; exports.ServerJsonApiPut = ServerJsonApiPut; exports.ServerNotificationService = _chunk5IEWLLLDjs.NotificationService; exports.ServerPushService = _chunk5IEWLLLDjs.PushService; exports.ServerRoleService = _chunk5IEWLLLDjs.RoleService; exports.ServerS3Service = _chunk5IEWLLLDjs.S3Service; exports.ServerSession = ServerSession; exports.ServerUserService = _chunk5IEWLLLDjs.UserService; exports.configureServerJsonApi = configureServerJsonApi; exports.getServerApiUrl = getServerApiUrl; exports.getServerAppUrl = getServerAppUrl; exports.getServerToken = _chunkYUO55Q5Ajs.getServerToken; exports.getServerTrackablePages = getServerTrackablePages; exports.invalidateCacheTag = invalidateCacheTag; exports.invalidateCacheTags = invalidateCacheTags; exports.serverRequest = _chunk3ZPK4QOBjs.serverRequest;
|
|
300
300
|
//# sourceMappingURL=index.js.map
|
package/dist/server/index.mjs
CHANGED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
type PageInfo = {
|
|
2
|
+
startItem: number;
|
|
3
|
+
endItem: number;
|
|
4
|
+
pageSize: number;
|
|
5
|
+
};
|
|
6
|
+
type DataListRetriever<T> = {
|
|
7
|
+
ready?: boolean;
|
|
8
|
+
setReady: (state: boolean) => void;
|
|
9
|
+
isLoaded: boolean;
|
|
10
|
+
data: T[] | undefined;
|
|
11
|
+
total?: number;
|
|
12
|
+
next?: (onlyNewRecords?: boolean) => Promise<void>;
|
|
13
|
+
previous?: (onlyNewRecords?: boolean) => Promise<void>;
|
|
14
|
+
search: (search: string) => Promise<void>;
|
|
15
|
+
refresh: () => Promise<void>;
|
|
16
|
+
addAdditionalParameter: (key: string, value: any | null) => void;
|
|
17
|
+
removeAdditionalParameter: (key: string) => void;
|
|
18
|
+
setRefreshedElement: (element: T) => void;
|
|
19
|
+
removeElement: (element: T) => void;
|
|
20
|
+
isSearch: boolean;
|
|
21
|
+
pageInfo?: PageInfo;
|
|
22
|
+
};
|
|
23
|
+
declare function useDataListRetriever<T>(params: {
|
|
24
|
+
ready?: boolean;
|
|
25
|
+
retriever: (params: any) => Promise<T[]>;
|
|
26
|
+
retrieverParams?: any;
|
|
27
|
+
search?: string;
|
|
28
|
+
addAdditionalParameter?: (key: string, value: any | null) => void;
|
|
29
|
+
requiresSearch?: boolean;
|
|
30
|
+
module: any;
|
|
31
|
+
}): DataListRetriever<T>;
|
|
32
|
+
|
|
33
|
+
export { type DataListRetriever as D, useDataListRetriever as u };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
type PageInfo = {
|
|
2
|
+
startItem: number;
|
|
3
|
+
endItem: number;
|
|
4
|
+
pageSize: number;
|
|
5
|
+
};
|
|
6
|
+
type DataListRetriever<T> = {
|
|
7
|
+
ready?: boolean;
|
|
8
|
+
setReady: (state: boolean) => void;
|
|
9
|
+
isLoaded: boolean;
|
|
10
|
+
data: T[] | undefined;
|
|
11
|
+
total?: number;
|
|
12
|
+
next?: (onlyNewRecords?: boolean) => Promise<void>;
|
|
13
|
+
previous?: (onlyNewRecords?: boolean) => Promise<void>;
|
|
14
|
+
search: (search: string) => Promise<void>;
|
|
15
|
+
refresh: () => Promise<void>;
|
|
16
|
+
addAdditionalParameter: (key: string, value: any | null) => void;
|
|
17
|
+
removeAdditionalParameter: (key: string) => void;
|
|
18
|
+
setRefreshedElement: (element: T) => void;
|
|
19
|
+
removeElement: (element: T) => void;
|
|
20
|
+
isSearch: boolean;
|
|
21
|
+
pageInfo?: PageInfo;
|
|
22
|
+
};
|
|
23
|
+
declare function useDataListRetriever<T>(params: {
|
|
24
|
+
ready?: boolean;
|
|
25
|
+
retriever: (params: any) => Promise<T[]>;
|
|
26
|
+
retrieverParams?: any;
|
|
27
|
+
search?: string;
|
|
28
|
+
addAdditionalParameter?: (key: string, value: any | null) => void;
|
|
29
|
+
requiresSearch?: boolean;
|
|
30
|
+
module: any;
|
|
31
|
+
}): DataListRetriever<T>;
|
|
32
|
+
|
|
33
|
+
export { type DataListRetriever as D, useDataListRetriever as u };
|
package/package.json
CHANGED
package/src/client/index.ts
CHANGED
|
@@ -27,10 +27,6 @@ export * from "../features/user/hooks";
|
|
|
27
27
|
export * from "../features/oauth/hooks";
|
|
28
28
|
export * from "../features/company/hooks/useSubscriptionStatus";
|
|
29
29
|
|
|
30
|
-
// RBAC hooks and utils
|
|
31
|
-
export { useRbacState } from "../features/rbac/hooks/useRbacState";
|
|
32
|
-
export { generateMigrationFile, downloadMigrationFile } from "../features/rbac/utils/RbacMigrationGenerator";
|
|
33
|
-
|
|
34
30
|
registerTableGenerator("roles", useRoleTableStructure);
|
|
35
31
|
registerTableGenerator("users", useUserTableStructure);
|
|
36
32
|
registerTableGenerator("companies", useCompanyTableStructure);
|
package/src/components/index.ts
CHANGED
|
@@ -20,6 +20,8 @@ export * from "../features/auth/components";
|
|
|
20
20
|
export * from "../features/company/components";
|
|
21
21
|
export * from "../features/content/components";
|
|
22
22
|
export * from "../features/how-to/components";
|
|
23
|
+
export * from "../features/assistant/components";
|
|
24
|
+
export * from "../features/assistant-message/components";
|
|
23
25
|
export * from "../features/feature/components";
|
|
24
26
|
export * from "../features/notification/components";
|
|
25
27
|
export * from "../features/onboarding/components";
|
|
@@ -29,9 +31,6 @@ export * from "../features/user/components";
|
|
|
29
31
|
export * from "../features/oauth/components";
|
|
30
32
|
export * from "../features/waitlist/components";
|
|
31
33
|
export { RbacContainer } from "../features/rbac/components/RbacContainer";
|
|
32
|
-
export { RbacToolbar } from "../features/rbac/components/RbacToolbar";
|
|
33
|
-
export { RbacFeatureSection } from "../features/rbac/components/RbacFeatureSection";
|
|
34
|
-
export { RbacModuleTable } from "../features/rbac/components/RbacModuleTable";
|
|
35
34
|
export { RbacPermissionCell } from "../features/rbac/components/RbacPermissionCell";
|
|
36
35
|
export { RbacPermissionPicker } from "../features/rbac/components/RbacPermissionPicker";
|
|
37
36
|
|
package/src/contexts/index.ts
CHANGED
|
@@ -4,6 +4,8 @@ export * from "../features/onboarding/contexts";
|
|
|
4
4
|
export * from "../features/role/contexts/RoleContext";
|
|
5
5
|
export * from "../features/user/contexts";
|
|
6
6
|
export * from "../features/how-to/contexts/HowToContext";
|
|
7
|
+
export * from "../features/rbac/contexts/RbacContext";
|
|
8
|
+
export * from "../features/assistant/contexts/AssistantContext";
|
|
7
9
|
export * from "./CommonContext";
|
|
8
10
|
export * from "./HeaderChildrenContext";
|
|
9
11
|
export * from "./HeaderLeftContentContext";
|
package/src/core/index.ts
CHANGED
|
@@ -61,6 +61,10 @@ export * from "../features/content/content.module";
|
|
|
61
61
|
export * from "../features/content/data";
|
|
62
62
|
export * from "../features/how-to/HowToModule";
|
|
63
63
|
export * from "../features/how-to/data";
|
|
64
|
+
export * from "../features/assistant/AssistantModule";
|
|
65
|
+
export * from "../features/assistant/data";
|
|
66
|
+
export * from "../features/assistant-message/AssistantMessageModule";
|
|
67
|
+
export * from "../features/assistant-message/data";
|
|
64
68
|
export * from "../features/feature/data";
|
|
65
69
|
export * from "../features/feature/feature.module";
|
|
66
70
|
export * from "../features/module";
|
|
@@ -16,6 +16,8 @@ export interface FoundationModuleDefinitions {
|
|
|
16
16
|
Module: ModuleWithPermissions;
|
|
17
17
|
Content: ModuleWithPermissions;
|
|
18
18
|
HowTo: ModuleWithPermissions;
|
|
19
|
+
Assistant: ModuleWithPermissions;
|
|
20
|
+
AssistantMessage: ModuleWithPermissions;
|
|
19
21
|
// Billing modules - READ: all users, UPDATE: CompanyAdministrator, ADMIN: Administrator
|
|
20
22
|
Billing: ModuleWithPermissions;
|
|
21
23
|
StripeCustomer: ModuleWithPermissions;
|
|
@@ -52,6 +54,7 @@ export interface FoundationModuleDefinitions {
|
|
|
52
54
|
// RBAC modules
|
|
53
55
|
PermissionMapping: ModuleWithPermissions;
|
|
54
56
|
ModulePaths: ModuleWithPermissions;
|
|
57
|
+
RbacMatrix: ModuleWithPermissions;
|
|
55
58
|
// Audit modules
|
|
56
59
|
AuditLog: ModuleWithPermissions;
|
|
57
60
|
}
|
|
@@ -171,6 +174,13 @@ class ModuleRegistryClass {
|
|
|
171
174
|
|
|
172
175
|
return response;
|
|
173
176
|
}
|
|
177
|
+
|
|
178
|
+
getAll(): ApiRequestDataTypeInterface[] {
|
|
179
|
+
if (this._modules.size === 0) {
|
|
180
|
+
tryBootstrap();
|
|
181
|
+
}
|
|
182
|
+
return Array.from(this._modules.values());
|
|
183
|
+
}
|
|
174
184
|
}
|
|
175
185
|
|
|
176
186
|
export const ModuleRegistry = new ModuleRegistryClass();
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { BotIcon } from "lucide-react";
|
|
2
|
+
import { createJsonApiInclusion } from "../../core";
|
|
3
|
+
import { ModuleFactory } from "../../permissions";
|
|
4
|
+
import { Assistant } from "./data/Assistant";
|
|
5
|
+
|
|
6
|
+
export const AssistantModule = (factory: ModuleFactory) =>
|
|
7
|
+
factory({
|
|
8
|
+
pageUrl: "/assistants",
|
|
9
|
+
name: "assistants",
|
|
10
|
+
model: Assistant,
|
|
11
|
+
moduleId: "2b39fd68-6a41-4f73-a2d0-4c8e8e3f9a42",
|
|
12
|
+
icon: BotIcon,
|
|
13
|
+
identifier: ["title"],
|
|
14
|
+
inclusions: {
|
|
15
|
+
lists: {
|
|
16
|
+
fields: [createJsonApiInclusion("assistants", [`title`])],
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { RoundPageContainer } from "../../../../components/containers/RoundPageContainer";
|
|
4
|
+
import { Modules } from "../../../../core";
|
|
5
|
+
import { useAssistantContext } from "../../contexts/AssistantContext";
|
|
6
|
+
import { AssistantSidebar } from "../parts/AssistantSidebar";
|
|
7
|
+
import { AssistantEmptyState } from "../parts/AssistantEmptyState";
|
|
8
|
+
import { AssistantThreadHeader } from "../parts/AssistantThreadHeader";
|
|
9
|
+
import { AssistantThread } from "../parts/AssistantThread";
|
|
10
|
+
import { AssistantComposer } from "../parts/AssistantComposer";
|
|
11
|
+
|
|
12
|
+
export function AssistantContainer() {
|
|
13
|
+
const ctx = useAssistantContext();
|
|
14
|
+
const showThread = !!ctx.assistant || ctx.sending || ctx.messages.length > 0;
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<RoundPageContainer module={Modules.Assistant} fullWidth>
|
|
18
|
+
<div className="bg-background flex h-full w-full overflow-hidden rounded-lg border">
|
|
19
|
+
<AssistantSidebar
|
|
20
|
+
threads={ctx.threads}
|
|
21
|
+
activeId={ctx.assistant?.id}
|
|
22
|
+
onSelect={ctx.selectThread}
|
|
23
|
+
onNew={ctx.startNew}
|
|
24
|
+
/>
|
|
25
|
+
<main className="flex flex-1 flex-col">
|
|
26
|
+
{!showThread ? (
|
|
27
|
+
<AssistantEmptyState onSend={ctx.sendMessage} />
|
|
28
|
+
) : (
|
|
29
|
+
<>
|
|
30
|
+
{ctx.assistant ? (
|
|
31
|
+
<AssistantThreadHeader
|
|
32
|
+
assistant={ctx.assistant}
|
|
33
|
+
onRename={(title) => ctx.renameThread(ctx.assistant!.id, title)}
|
|
34
|
+
onDelete={() => ctx.deleteThread(ctx.assistant!.id)}
|
|
35
|
+
/>
|
|
36
|
+
) : (
|
|
37
|
+
<div className="flex items-center justify-between border-b px-5 py-3" aria-hidden>
|
|
38
|
+
<div className="h-5" />
|
|
39
|
+
</div>
|
|
40
|
+
)}
|
|
41
|
+
<AssistantThread
|
|
42
|
+
messages={ctx.messages}
|
|
43
|
+
sending={ctx.sending}
|
|
44
|
+
status={ctx.status}
|
|
45
|
+
onSelectFollowUp={ctx.sendMessage}
|
|
46
|
+
failedMessageIds={ctx.failedMessageIds}
|
|
47
|
+
onRetry={ctx.retrySend}
|
|
48
|
+
/>
|
|
49
|
+
<AssistantComposer onSend={ctx.sendMessage} disabled={ctx.sending} />
|
|
50
|
+
</>
|
|
51
|
+
)}
|
|
52
|
+
</main>
|
|
53
|
+
</div>
|
|
54
|
+
</RoundPageContainer>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
vi.mock("../../../../../contexts/SocketContext", () => ({
|
|
2
|
+
useSocketContext: () => ({ socket: null, isConnected: false }),
|
|
3
|
+
}));
|
|
4
|
+
|
|
5
|
+
vi.mock("../../../../../components/containers/RoundPageContainer", () => ({
|
|
6
|
+
RoundPageContainer: ({ children }: { children: React.ReactNode }) => <>{children}</>,
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
import { describe, it, expect, vi, beforeAll, beforeEach } from "vitest";
|
|
10
|
+
import { render, screen, waitFor } from "@testing-library/react";
|
|
11
|
+
import { AssistantProvider } from "../../../contexts/AssistantContext";
|
|
12
|
+
import { AssistantService } from "../../../data/AssistantService";
|
|
13
|
+
import type { JsonApiHydratedDataInterface } from "../../../../../core";
|
|
14
|
+
import { AssistantContainer } from "../AssistantContainer";
|
|
15
|
+
import { ModuleRegistry } from "../../../../../core/registry/ModuleRegistry";
|
|
16
|
+
import { DataClassRegistry } from "../../../../../core/registry/DataClassRegistry";
|
|
17
|
+
import { Assistant } from "../../../data/Assistant";
|
|
18
|
+
import { AssistantMessage } from "../../../../assistant-message/data/AssistantMessage";
|
|
19
|
+
|
|
20
|
+
beforeAll(() => {
|
|
21
|
+
Element.prototype.scrollIntoView = vi.fn();
|
|
22
|
+
const assistantModule = { name: "assistants", model: Assistant } as any;
|
|
23
|
+
const assistantMessageModule = { name: "assistant-messages", model: AssistantMessage } as any;
|
|
24
|
+
DataClassRegistry.registerObjectClass(assistantModule, Assistant);
|
|
25
|
+
DataClassRegistry.registerObjectClass(assistantMessageModule, AssistantMessage);
|
|
26
|
+
ModuleRegistry.register("Assistant" as any, assistantModule as any);
|
|
27
|
+
ModuleRegistry.register("AssistantMessage" as any, assistantMessageModule as any);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
AssistantService.findMany = vi.fn().mockResolvedValue([]);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
function buildAssistantDehydrated({
|
|
35
|
+
id,
|
|
36
|
+
title = "Stub",
|
|
37
|
+
}: {
|
|
38
|
+
id: string;
|
|
39
|
+
title?: string;
|
|
40
|
+
}): JsonApiHydratedDataInterface {
|
|
41
|
+
return {
|
|
42
|
+
jsonApi: {
|
|
43
|
+
type: "assistants",
|
|
44
|
+
id,
|
|
45
|
+
attributes: { title, messageCount: 0 },
|
|
46
|
+
},
|
|
47
|
+
included: [],
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
describe("AssistantContainer", () => {
|
|
52
|
+
it("shows empty state when no assistant is active", async () => {
|
|
53
|
+
render(
|
|
54
|
+
<AssistantProvider>
|
|
55
|
+
<AssistantContainer />
|
|
56
|
+
</AssistantProvider>,
|
|
57
|
+
);
|
|
58
|
+
// AssistantEmptyState renders heading with translation key
|
|
59
|
+
await waitFor(() =>
|
|
60
|
+
expect(screen.getByRole("heading", { name: "features.assistant.empty_state.title" })).toBeInTheDocument(),
|
|
61
|
+
);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("shows thread header + composer when an assistant is active", async () => {
|
|
65
|
+
const active = buildAssistantDehydrated({ id: "a1", title: "T" });
|
|
66
|
+
render(
|
|
67
|
+
<AssistantProvider dehydratedAssistant={active}>
|
|
68
|
+
<AssistantContainer />
|
|
69
|
+
</AssistantProvider>,
|
|
70
|
+
);
|
|
71
|
+
// Header shows the assistant title
|
|
72
|
+
expect(screen.getByText("T")).toBeInTheDocument();
|
|
73
|
+
// Composer has a textarea
|
|
74
|
+
expect(screen.getByRole("textbox")).toBeInTheDocument();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("shows thread view (not empty state) when sending a first message before assistant resolves", async () => {
|
|
78
|
+
// Keep `AssistantService.create` pending so the assistant is never set.
|
|
79
|
+
AssistantService.create = vi.fn().mockImplementation(() => new Promise(() => {}));
|
|
80
|
+
|
|
81
|
+
render(
|
|
82
|
+
<AssistantProvider>
|
|
83
|
+
<AssistantContainer />
|
|
84
|
+
</AssistantProvider>,
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
// Kick off a send via the composer. The AssistantEmptyState renders its own composer.
|
|
88
|
+
const textarea = await screen.findByRole("textbox");
|
|
89
|
+
const user = (await import("@testing-library/user-event")).default.setup();
|
|
90
|
+
await user.type(textarea, "first one");
|
|
91
|
+
await user.keyboard("{Enter}");
|
|
92
|
+
|
|
93
|
+
// Empty-state title disappears; optimistic user bubble visible.
|
|
94
|
+
await waitFor(() => {
|
|
95
|
+
expect(screen.queryByRole("heading", { name: "features.assistant.empty_state.title" })).not.toBeInTheDocument();
|
|
96
|
+
});
|
|
97
|
+
expect(screen.getByText("first one")).toBeInTheDocument();
|
|
98
|
+
// Status line is showing ("thinking…" translation key renders literally in tests).
|
|
99
|
+
expect(screen.getByText("features.assistant.thinking")).toBeInTheDocument();
|
|
100
|
+
});
|
|
101
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./containers/AssistantContainer";
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState, type KeyboardEvent } from "react";
|
|
4
|
+
import { useTranslations } from "next-intl";
|
|
5
|
+
import { ArrowUp } from "lucide-react";
|
|
6
|
+
import { Button, Textarea } from "../../../../shadcnui";
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
onSend: (content: string) => Promise<void>;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
value?: string;
|
|
12
|
+
onValueChange?: (v: string) => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function AssistantComposer({ onSend, disabled, value: controlled, onValueChange }: Props) {
|
|
16
|
+
const t = useTranslations();
|
|
17
|
+
const [internal, setInternal] = useState("");
|
|
18
|
+
const value = controlled ?? internal;
|
|
19
|
+
const setValue = onValueChange ?? setInternal;
|
|
20
|
+
|
|
21
|
+
const canSend = value.trim().length > 0 && !disabled;
|
|
22
|
+
|
|
23
|
+
const submit = async () => {
|
|
24
|
+
if (!canSend) return;
|
|
25
|
+
const payload = value.trim();
|
|
26
|
+
setValue("");
|
|
27
|
+
await onSend(payload);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const onKeyDown = (e: KeyboardEvent<HTMLTextAreaElement>) => {
|
|
31
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
32
|
+
e.preventDefault();
|
|
33
|
+
void submit();
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<div className="flex flex-col gap-1 border-t p-4">
|
|
39
|
+
<div className="bg-muted/30 flex items-end gap-2 rounded-lg border p-2">
|
|
40
|
+
<Textarea
|
|
41
|
+
value={value}
|
|
42
|
+
onChange={(e) => setValue(e.target.value)}
|
|
43
|
+
onKeyDown={onKeyDown}
|
|
44
|
+
placeholder={t("features.assistant.composer_placeholder")}
|
|
45
|
+
disabled={disabled}
|
|
46
|
+
rows={2}
|
|
47
|
+
className="min-h-[48px] resize-none border-0 bg-transparent focus-visible:ring-0"
|
|
48
|
+
/>
|
|
49
|
+
<Button onClick={submit} disabled={!canSend} size="sm" className="h-8">
|
|
50
|
+
<ArrowUp className="mr-1 h-4 w-4" /> {t("ui.buttons.save")}
|
|
51
|
+
</Button>
|
|
52
|
+
</div>
|
|
53
|
+
<div className="text-muted-foreground text-right text-xs">{t("features.assistant.keyboard_hint")}</div>
|
|
54
|
+
</div>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { useTranslations } from "next-intl";
|
|
5
|
+
import { Sparkles } from "lucide-react";
|
|
6
|
+
import { AssistantComposer } from "./AssistantComposer";
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
onSend: (content: string) => Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const STARTER_KEYS = ["a", "b", "c", "d"] as const;
|
|
13
|
+
|
|
14
|
+
export function AssistantEmptyState({ onSend }: Props) {
|
|
15
|
+
const t = useTranslations();
|
|
16
|
+
const [draft, setDraft] = useState("");
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<div className="flex h-full w-full items-center justify-center p-10">
|
|
20
|
+
<div className="flex w-full max-w-2xl flex-col gap-6">
|
|
21
|
+
<div className="text-center">
|
|
22
|
+
<div className="mx-auto 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">
|
|
23
|
+
<Sparkles className="h-5 w-5" />
|
|
24
|
+
</div>
|
|
25
|
+
<h3 className="text-foreground text-xl font-semibold">{t("features.assistant.empty_state.title")}</h3>
|
|
26
|
+
<p className="text-muted-foreground mt-1 text-sm">{t("features.assistant.empty_state.subtitle")}</p>
|
|
27
|
+
</div>
|
|
28
|
+
<AssistantComposer value={draft} onValueChange={setDraft} onSend={onSend} />
|
|
29
|
+
<div className="grid grid-cols-2 gap-2">
|
|
30
|
+
{STARTER_KEYS.map((k) => {
|
|
31
|
+
const text = t(`features.assistant.starters.${k}`);
|
|
32
|
+
return (
|
|
33
|
+
<button
|
|
34
|
+
key={k}
|
|
35
|
+
type="button"
|
|
36
|
+
className="border-border bg-muted/30 hover:bg-muted rounded-lg border p-3 text-left text-sm"
|
|
37
|
+
onClick={() => setDraft(text)}
|
|
38
|
+
>
|
|
39
|
+
{text}
|
|
40
|
+
</button>
|
|
41
|
+
);
|
|
42
|
+
})}
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useTranslations } from "next-intl";
|
|
4
|
+
import { Plus } from "lucide-react";
|
|
5
|
+
import { Button } from "../../../../shadcnui";
|
|
6
|
+
import type { AssistantInterface } from "../../data/AssistantInterface";
|
|
7
|
+
import { groupThreadsByBucket } from "../../utils/groupThreadsByBucket";
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
threads: AssistantInterface[];
|
|
11
|
+
activeId?: string;
|
|
12
|
+
onSelect: (id: string) => void;
|
|
13
|
+
onNew: () => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function AssistantSidebar({ threads, activeId, onSelect, onNew }: Props) {
|
|
17
|
+
const t = useTranslations();
|
|
18
|
+
const groups = groupThreadsByBucket(threads);
|
|
19
|
+
|
|
20
|
+
const renderSection = (label: string, items: AssistantInterface[]) => {
|
|
21
|
+
if (items.length === 0) return null;
|
|
22
|
+
return (
|
|
23
|
+
<div className="mb-2">
|
|
24
|
+
<div className="text-muted-foreground px-2 py-1 text-[10px] font-semibold uppercase tracking-wider">
|
|
25
|
+
{label}
|
|
26
|
+
</div>
|
|
27
|
+
{items.map((thread) => (
|
|
28
|
+
<button
|
|
29
|
+
key={thread.id}
|
|
30
|
+
type="button"
|
|
31
|
+
onClick={() => onSelect(thread.id)}
|
|
32
|
+
className={
|
|
33
|
+
"block w-full truncate rounded-md px-2 py-1.5 text-left text-sm " +
|
|
34
|
+
(activeId === thread.id ? "bg-primary/10 text-primary" : "hover:bg-muted text-foreground")
|
|
35
|
+
}
|
|
36
|
+
>
|
|
37
|
+
{thread.title}
|
|
38
|
+
</button>
|
|
39
|
+
))}
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<aside className="bg-muted/30 flex w-64 flex-col border-r">
|
|
46
|
+
<div className="border-b p-3">
|
|
47
|
+
<Button onClick={onNew} className="w-full" size="sm">
|
|
48
|
+
<Plus className="mr-1 h-4 w-4" /> {t("features.assistant.new")}
|
|
49
|
+
</Button>
|
|
50
|
+
</div>
|
|
51
|
+
<div className="flex-1 overflow-y-auto p-2">
|
|
52
|
+
{threads.length === 0 ? (
|
|
53
|
+
<div className="text-muted-foreground mt-6 text-center text-xs">{t("features.assistant.empty_sidebar")}</div>
|
|
54
|
+
) : (
|
|
55
|
+
<>
|
|
56
|
+
{renderSection(t("features.assistant.bucket_today"), groups.today)}
|
|
57
|
+
{renderSection(t("features.assistant.bucket_week"), groups.thisWeek)}
|
|
58
|
+
{renderSection(t("features.assistant.bucket_earlier"), groups.earlier)}
|
|
59
|
+
</>
|
|
60
|
+
)}
|
|
61
|
+
</div>
|
|
62
|
+
</aside>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useTranslations } from "next-intl";
|
|
4
|
+
import { Loader2 } from "lucide-react";
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
status?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function AssistantStatusLine({ status }: Props) {
|
|
11
|
+
const t = useTranslations();
|
|
12
|
+
const text = status ?? t("features.assistant.thinking");
|
|
13
|
+
return (
|
|
14
|
+
<div className="text-muted-foreground flex items-center gap-2 px-4 py-2 text-sm">
|
|
15
|
+
<Loader2 className="h-4 w-4 animate-spin" />
|
|
16
|
+
<span>{text}</span>
|
|
17
|
+
</div>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef } from "react";
|
|
4
|
+
import type { AssistantMessageInterface } from "../../../assistant-message/data/AssistantMessageInterface";
|
|
5
|
+
import { MessageList } from "../../../assistant-message/components/MessageList";
|
|
6
|
+
import { AssistantStatusLine } from "./AssistantStatusLine";
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
messages: AssistantMessageInterface[];
|
|
10
|
+
sending: boolean;
|
|
11
|
+
status?: string;
|
|
12
|
+
onSelectFollowUp: (q: string) => void;
|
|
13
|
+
failedMessageIds?: Set<string>;
|
|
14
|
+
onRetry?: (tempId: string) => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function AssistantThread({ messages, sending, status, onSelectFollowUp, failedMessageIds, onRetry }: Props) {
|
|
18
|
+
const endRef = useRef<HTMLDivElement>(null);
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
endRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
22
|
+
}, [messages.length, sending]);
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<div className="flex-1 overflow-y-auto px-6 py-5">
|
|
26
|
+
<MessageList
|
|
27
|
+
messages={messages}
|
|
28
|
+
onSelectFollowUp={onSelectFollowUp}
|
|
29
|
+
failedMessageIds={failedMessageIds}
|
|
30
|
+
onRetry={onRetry}
|
|
31
|
+
/>
|
|
32
|
+
{sending && <AssistantStatusLine status={status} />}
|
|
33
|
+
<div ref={endRef} />
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
}
|