@nocobase/portal-template-default 1.0.3 → 1.0.4

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.
Files changed (170) hide show
  1. package/README.MD +10 -5
  2. package/package.json +1 -1
  3. package/registry/nocobase-ai/components/chat/chat-message.tsx +8 -6
  4. package/registry/nocobase-ai/components/chat/chat-messages.tsx +8 -3
  5. package/registry/nocobase-ai/components/chat/markdown-message.tsx +26 -0
  6. package/registry/nocobase-ai/components/chat/sub-agent-conversation.tsx +3 -3
  7. package/registry/nocobase-ai/demo/configuration-gate.tsx +83 -35
  8. package/registry/nocobase-i18n/README.md +23 -32
  9. package/registry/nocobase-i18n/components/language-switcher.tsx +1 -1
  10. package/registry/nocobase-i18n/demo/index.tsx +2 -2
  11. package/registry/nocobase-i18n/demo/prompt-generator.tsx +3 -2
  12. package/registry/nocobase-i18n/extension.tsx +3 -4
  13. package/registry/nocobase-i18n/index.ts +4 -3
  14. package/registry/nocobase-i18n/locales/en-US.ts +2 -52
  15. package/registry/nocobase-i18n/locales/index.ts +8 -0
  16. package/registry/nocobase-i18n/locales/zh-CN.ts +2 -52
  17. package/registry/nocobase-i18n/provider.tsx +18 -6
  18. package/registry/nocobase-i18n/server-resources.ts +13 -31
  19. package/registry/nocobase-i18n/tests/i18n-regression.mjs +14 -5
  20. package/registry/nocobase-route-surfaces/README.md +21 -0
  21. package/registry/nocobase-route-surfaces/demo/index.tsx +266 -0
  22. package/{src/extensions/nocobase-acl → registry/nocobase-route-surfaces}/demo/lazy-route.tsx +2 -1
  23. package/registry/nocobase-route-surfaces/demo/prompt-generator.tsx +90 -0
  24. package/registry/nocobase-route-surfaces/demo/scenarios.ts +95 -0
  25. package/registry/nocobase-route-surfaces/extension.tsx +119 -0
  26. package/registry/nocobase-route-surfaces/index.ts +7 -0
  27. package/registry/nocobase-route-surfaces/route-dialog.tsx +145 -0
  28. package/registry/nocobase-route-surfaces/route-drawer.tsx +186 -0
  29. package/registry/nocobase-route-surfaces/route-page.tsx +29 -0
  30. package/registry/nocobase-route-surfaces/route-surface-context.ts +13 -0
  31. package/registry/nocobase-route-surfaces/use-refine-unsaved-changes.tsx +106 -0
  32. package/registry/nocobase-route-surfaces/use-route-surface-close.ts +15 -0
  33. package/registry/nocobase-route-surfaces/use-route-surface-state.ts +65 -0
  34. package/registry.config.json +30 -3
  35. package/scripts/registry.mjs +0 -2
  36. package/src/App.tsx +65 -35
  37. package/src/app/extension.ts +1 -2
  38. package/src/app/extensions.tsx +0 -14
  39. package/src/components/app-shell/breadcrumb.tsx +67 -2
  40. package/src/components/app-shell/sidebar.tsx +2 -2
  41. package/src/components/resources/views/list-view.tsx +3 -2
  42. package/src/components/ui/sheet.tsx +22 -21
  43. package/src/lib/nocobase/client.ts +6 -1
  44. package/src/locales/en-US.ts +81 -0
  45. package/src/locales/zh-CN.ts +77 -0
  46. package/src/pages/users/create.tsx +76 -53
  47. package/src/pages/users/edit.tsx +87 -56
  48. package/src/pages/users/index.ts +3 -0
  49. package/src/pages/users/layout.tsx +15 -0
  50. package/src/pages/users/list.tsx +19 -21
  51. package/src/pages/users/role-badges.tsx +43 -0
  52. package/src/pages/users/role-detail.tsx +272 -0
  53. package/src/pages/users/role-utils.ts +6 -0
  54. package/src/pages/users/routes.ts +24 -0
  55. package/src/pages/users/show.tsx +195 -104
  56. package/src/pages/users/types.ts +13 -0
  57. package/src/providers/i18n/index.ts +3 -0
  58. package/{registry/nocobase-i18n → src/providers/i18n}/runtime.ts +63 -16
  59. package/src/providers/system-settings/context.ts +26 -0
  60. package/src/providers/system-settings/index.ts +2 -0
  61. package/src/providers/system-settings/provider.tsx +94 -0
  62. package/src/extensions/nocobase-acl/README.md +0 -19
  63. package/src/extensions/nocobase-acl/components/acl-boundary.tsx +0 -106
  64. package/src/extensions/nocobase-acl/components/index.ts +0 -3
  65. package/src/extensions/nocobase-acl/components/role-options.ts +0 -36
  66. package/src/extensions/nocobase-acl/components/role-switcher.tsx +0 -135
  67. package/src/extensions/nocobase-acl/demo/boundary-api.tsx +0 -186
  68. package/src/extensions/nocobase-acl/demo/components.tsx +0 -231
  69. package/src/extensions/nocobase-acl/demo/index.tsx +0 -505
  70. package/src/extensions/nocobase-acl/demo/prompt-generator.tsx +0 -134
  71. package/src/extensions/nocobase-acl/demo/role-switcher-prompt-generator.tsx +0 -198
  72. package/src/extensions/nocobase-acl/demo/scenario-section.tsx +0 -37
  73. package/src/extensions/nocobase-acl/extension.tsx +0 -71
  74. package/src/extensions/nocobase-acl/index.ts +0 -1
  75. package/src/extensions/nocobase-ai/README.md +0 -70
  76. package/src/extensions/nocobase-ai/adapters/react-hook-form.ts +0 -20
  77. package/src/extensions/nocobase-ai/assets/nocobase-ai-chat.svg +0 -50
  78. package/src/extensions/nocobase-ai/components/ai-root-provider.tsx +0 -32
  79. package/src/extensions/nocobase-ai/components/chat/ai-employee-avatar.tsx +0 -31
  80. package/src/extensions/nocobase-ai/components/chat/chat-attachment.tsx +0 -92
  81. package/src/extensions/nocobase-ai/components/chat/chat-compact.tsx +0 -90
  82. package/src/extensions/nocobase-ai/components/chat/chat-composer.tsx +0 -429
  83. package/src/extensions/nocobase-ai/components/chat/chat-empty-state.tsx +0 -51
  84. package/src/extensions/nocobase-ai/components/chat/chat-header.tsx +0 -88
  85. package/src/extensions/nocobase-ai/components/chat/chat-history-dialog.tsx +0 -77
  86. package/src/extensions/nocobase-ai/components/chat/chat-message.tsx +0 -252
  87. package/src/extensions/nocobase-ai/components/chat/chat-messages.tsx +0 -173
  88. package/src/extensions/nocobase-ai/components/chat/chat-window.tsx +0 -118
  89. package/src/extensions/nocobase-ai/components/chat/conversation-list.tsx +0 -343
  90. package/src/extensions/nocobase-ai/components/chat/markdown-message.tsx +0 -165
  91. package/src/extensions/nocobase-ai/components/chat/model-select-options.tsx +0 -30
  92. package/src/extensions/nocobase-ai/components/chat/reasoning-panel.tsx +0 -29
  93. package/src/extensions/nocobase-ai/components/chat/sub-agent-conversation.tsx +0 -142
  94. package/src/extensions/nocobase-ai/components/chat/tool-call-card.tsx +0 -316
  95. package/src/extensions/nocobase-ai/components/chat/user-prompt-editor.tsx +0 -99
  96. package/src/extensions/nocobase-ai/components/chat/work-context-chip.tsx +0 -114
  97. package/src/extensions/nocobase-ai/components/index.ts +0 -59
  98. package/src/extensions/nocobase-ai/components/page-elements/ai-form.tsx +0 -44
  99. package/src/extensions/nocobase-ai/components/page-elements/page-element-provider.tsx +0 -469
  100. package/src/extensions/nocobase-ai/components/surfaces/chat-dialog.tsx +0 -31
  101. package/src/extensions/nocobase-ai/components/surfaces/chat-inline.tsx +0 -18
  102. package/src/extensions/nocobase-ai/components/surfaces/chat-page.tsx +0 -18
  103. package/src/extensions/nocobase-ai/components/surfaces/chat-side-panel-layout.tsx +0 -54
  104. package/src/extensions/nocobase-ai/components/surfaces/chat-side-panel.tsx +0 -74
  105. package/src/extensions/nocobase-ai/components/surfaces/chat-surface-actions.tsx +0 -54
  106. package/src/extensions/nocobase-ai/components/tools/builtin-tool-renderers.tsx +0 -31
  107. package/src/extensions/nocobase-ai/components/tools/business-report-dialog.tsx +0 -364
  108. package/src/extensions/nocobase-ai/components/tools/business-report-renderer.tsx +0 -122
  109. package/src/extensions/nocobase-ai/components/tools/business-report-utils.tsx +0 -282
  110. package/src/extensions/nocobase-ai/components/tools/chart-renderer.tsx +0 -99
  111. package/src/extensions/nocobase-ai/components/tools/echarts-preview.tsx +0 -59
  112. package/src/extensions/nocobase-ai/components/tools/echarts-runtime-advanced.ts +0 -18
  113. package/src/extensions/nocobase-ai/components/tools/echarts-runtime-common.ts +0 -20
  114. package/src/extensions/nocobase-ai/components/tools/echarts-runtime-components.ts +0 -34
  115. package/src/extensions/nocobase-ai/components/tools/echarts-runtime-hierarchy.ts +0 -24
  116. package/src/extensions/nocobase-ai/components/tools/echarts-runtime.ts +0 -138
  117. package/src/extensions/nocobase-ai/components/tools/sub-agent-renderer.tsx +0 -67
  118. package/src/extensions/nocobase-ai/components/tools/suggestions-renderer.tsx +0 -59
  119. package/src/extensions/nocobase-ai/components/tools/tool-renderer-provider.tsx +0 -55
  120. package/src/extensions/nocobase-ai/components/tools/tool-renderer-utils.ts +0 -18
  121. package/src/extensions/nocobase-ai/components/tools/workflow-renderer.tsx +0 -108
  122. package/src/extensions/nocobase-ai/components/triggers/ai-chat-floating-trigger.tsx +0 -73
  123. package/src/extensions/nocobase-ai/components/triggers/ai-employee-shortcut.tsx +0 -149
  124. package/src/extensions/nocobase-ai/demo/configuration-gate.tsx +0 -49
  125. package/src/extensions/nocobase-ai/demo/container-showcase.tsx +0 -210
  126. package/src/extensions/nocobase-ai/demo/floating.tsx +0 -134
  127. package/src/extensions/nocobase-ai/demo/index.tsx +0 -346
  128. package/src/extensions/nocobase-ai/demo/interaction-showcase.tsx +0 -151
  129. package/src/extensions/nocobase-ai/demo/page-context-prompt-generator.tsx +0 -355
  130. package/src/extensions/nocobase-ai/demo/page-context.tsx +0 -884
  131. package/src/extensions/nocobase-ai/demo/page-element-showcase.tsx +0 -197
  132. package/src/extensions/nocobase-ai/demo/prompt-card.tsx +0 -41
  133. package/src/extensions/nocobase-ai/demo/prompt-generator.tsx +0 -452
  134. package/src/extensions/nocobase-ai/demo/shortcut.tsx +0 -1207
  135. package/src/extensions/nocobase-ai/demo/tool-cards.tsx +0 -530
  136. package/src/extensions/nocobase-ai/extension.tsx +0 -158
  137. package/src/extensions/nocobase-ai/global-ai-chat.tsx +0 -213
  138. package/src/extensions/nocobase-ai/index.ts +0 -3
  139. package/src/extensions/nocobase-ai/providers/ai-provider.tsx +0 -323
  140. package/src/extensions/nocobase-ai/providers/avatars.ts +0 -817
  141. package/src/extensions/nocobase-ai/providers/chat-context.tsx +0 -109
  142. package/src/extensions/nocobase-ai/providers/chat-controller.ts +0 -84
  143. package/src/extensions/nocobase-ai/providers/chat-message-utils.ts +0 -198
  144. package/src/extensions/nocobase-ai/providers/chat-provider.tsx +0 -871
  145. package/src/extensions/nocobase-ai/providers/chat-reducer.ts +0 -147
  146. package/src/extensions/nocobase-ai/providers/chat-task-utils.ts +0 -105
  147. package/src/extensions/nocobase-ai/providers/chat-transport.ts +0 -184
  148. package/src/extensions/nocobase-ai/providers/form-registry.tsx +0 -271
  149. package/src/extensions/nocobase-ai/providers/frontend-tool-registry.tsx +0 -231
  150. package/src/extensions/nocobase-ai/providers/index.ts +0 -61
  151. package/src/extensions/nocobase-ai/providers/model.ts +0 -32
  152. package/src/extensions/nocobase-ai/providers/page-context.tsx +0 -136
  153. package/src/extensions/nocobase-ai/providers/stream-coalescer.ts +0 -60
  154. package/src/extensions/nocobase-ai/providers/stream-event-utils.ts +0 -82
  155. package/src/extensions/nocobase-ai/providers/stream-parser.ts +0 -61
  156. package/src/extensions/nocobase-ai/providers/sub-agent-stream.ts +0 -316
  157. package/src/extensions/nocobase-ai/providers/types.ts +0 -183
  158. package/src/extensions/nocobase-ai/providers/ui-message-stream.ts +0 -463
  159. package/src/extensions/nocobase-ai/providers/use-automatic-tool-approval.ts +0 -94
  160. package/src/extensions/nocobase-ai/providers/use-chat-attachments.ts +0 -206
  161. package/src/extensions/nocobase-ai/providers/use-chat-message-actions.ts +0 -554
  162. package/src/extensions/nocobase-ai/providers/use-chat-runtime.ts +0 -210
  163. package/src/extensions/nocobase-ai/providers/use-chat-work-context.ts +0 -93
  164. package/src/extensions/nocobase-ai/providers/use-conversation-catalog.ts +0 -108
  165. package/src/extensions/nocobase-ai/providers/use-conversation-history.ts +0 -178
  166. package/src/extensions/nocobase-ai/services/index.ts +0 -7
  167. package/src/extensions/nocobase-ai/services/nocobase-ai-service.ts +0 -454
  168. package/src/extensions/nocobase-ai/services/types.ts +0 -82
  169. /package/{registry/nocobase-i18n → src/providers/i18n}/i18n-provider.ts +0 -0
  170. /package/{registry/nocobase-i18n → src/providers/i18n}/locale-store.ts +0 -0
@@ -1,213 +0,0 @@
1
- import {
2
- useEffect,
3
- useMemo,
4
- useState,
5
- type CSSProperties,
6
- type Dispatch,
7
- type PropsWithChildren,
8
- type SetStateAction,
9
- } from "react";
10
- import {
11
- AIChatFloatingTrigger,
12
- NocoBaseAIRootProvider,
13
- useAIPageElementPicker,
14
- type AIChatComposerAction,
15
- } from "./components";
16
- import { AIChatWindow } from "./components/chat/chat-window";
17
- import { ChatDialog } from "./components/surfaces/chat-dialog";
18
- import { ChatSidePanel } from "./components/surfaces/chat-side-panel";
19
- import { ChatSurfaceActions } from "./components/surfaces/chat-surface-actions";
20
- import {
21
- AIChatProvider,
22
- type AIToolInvokerMap,
23
- useAI,
24
- useAIChatBase,
25
- useAIChatControllerState,
26
- useGlobalAIChatController,
27
- } from "./providers";
28
- import { nocobaseAIService } from "./services";
29
- import { Globe2, MousePointer2 } from "lucide-react";
30
-
31
- const DEFAULT_SIDE_PANEL_WIDTH = 450;
32
- export function NocoBaseAIExtensionProvider({
33
- children,
34
- toolInvokers,
35
- }: PropsWithChildren<{ toolInvokers?: AIToolInvokerMap }>) {
36
- return (
37
- <NocoBaseAIRootProvider
38
- service={nocobaseAIService}
39
- toolInvokers={toolInvokers}
40
- >
41
- <NocoBaseAIGlobalEntry>{children}</NocoBaseAIGlobalEntry>
42
- </NocoBaseAIRootProvider>
43
- );
44
- }
45
-
46
- function NocoBaseAIGlobalEntry({ children }: PropsWithChildren) {
47
- const ai = useAI();
48
- const controller = useGlobalAIChatController();
49
- const { open } = useAIChatControllerState(controller);
50
- const [webSearch, setWebSearch] = useState(false);
51
- const [expanded, setExpanded] = useState(false);
52
- const ready =
53
- ai.configurationStatus === "ready" &&
54
- ai.employees.length > 0 &&
55
- ai.hasEnabledModels;
56
-
57
- return (
58
- <div
59
- data-open={ready && open && !expanded}
60
- data-side="right"
61
- className="chat-side-panel-layout @container min-h-svh min-w-0"
62
- style={
63
- {
64
- "--chat-side-panel-width": `${DEFAULT_SIDE_PANEL_WIDTH}px`,
65
- } as CSSProperties
66
- }
67
- >
68
- <div className="min-w-0">{children}</div>
69
- {ready ? (
70
- <AIChatProvider
71
- id="starter-global-ai"
72
- controller={controller}
73
- webSearch={webSearch}
74
- >
75
- <StarterGlobalAIChat
76
- open={open}
77
- onOpenChange={controller.setOpen}
78
- webSearch={webSearch}
79
- setWebSearch={setWebSearch}
80
- expanded={expanded}
81
- setExpanded={setExpanded}
82
- />
83
- </AIChatProvider>
84
- ) : null}
85
- </div>
86
- );
87
- }
88
-
89
- function StarterGlobalAIChat({
90
- open,
91
- onOpenChange,
92
- webSearch,
93
- setWebSearch,
94
- expanded,
95
- setExpanded,
96
- }: {
97
- open: boolean;
98
- onOpenChange: (open: boolean) => void;
99
- webSearch: boolean;
100
- setWebSearch: Dispatch<SetStateAction<boolean>>;
101
- expanded: boolean;
102
- setExpanded: Dispatch<SetStateAction<boolean>>;
103
- }) {
104
- const {
105
- id: chatId,
106
- conversations,
107
- currentModel,
108
- addWorkContext,
109
- focusComposer,
110
- } = useAIChatBase();
111
- const { registeredCount, startPicking } = useAIPageElementPicker();
112
- const supportsWebSearch = currentModel.supportWebSearch === true;
113
- const unreadCount = conversations.filter(
114
- (conversation) => conversation.unread
115
- ).length;
116
-
117
- useEffect(() => {
118
- if (!supportsWebSearch && webSearch) setWebSearch(false);
119
- }, [setWebSearch, supportsWebSearch, webSearch]);
120
-
121
- const composerActions = useMemo<AIChatComposerAction[]>(
122
- () => [
123
- {
124
- key: "pick-page-element",
125
- label: "Pick page element",
126
- icon: <MousePointer2 />,
127
- disabled: registeredCount === 0,
128
- onClick: () => {
129
- if (expanded) setExpanded(false);
130
- startPicking({
131
- chatId,
132
- onSelect: (item) => {
133
- addWorkContext(item);
134
- focusComposer();
135
- },
136
- });
137
- },
138
- },
139
- {
140
- key: "web-search",
141
- label: supportsWebSearch
142
- ? webSearch
143
- ? "Disable web search"
144
- : "Enable web search"
145
- : "Web search is not supported by this model",
146
- icon: <Globe2 />,
147
- active: webSearch,
148
- disabled: !supportsWebSearch,
149
- onClick: () => setWebSearch((active) => !active),
150
- },
151
- ],
152
- [
153
- addWorkContext,
154
- chatId,
155
- expanded,
156
- focusComposer,
157
- registeredCount,
158
- setExpanded,
159
- setWebSearch,
160
- startPicking,
161
- supportsWebSearch,
162
- webSearch,
163
- ]
164
- );
165
- const closeChat = () => {
166
- setExpanded(false);
167
- onOpenChange(false);
168
- };
169
-
170
- return (
171
- <>
172
- <ChatSidePanel
173
- open={open && !expanded}
174
- onOpenChange={onOpenChange}
175
- width={DEFAULT_SIDE_PANEL_WIDTH}
176
- showCloseHandle={false}
177
- >
178
- <AIChatWindow
179
- headerActions={
180
- <ChatSurfaceActions
181
- expanded={false}
182
- onExpandedChange={setExpanded}
183
- onClose={closeChat}
184
- />
185
- }
186
- composerActions={composerActions}
187
- enableAttachments
188
- attachmentActionIndex={1}
189
- />
190
- </ChatSidePanel>
191
- <ChatDialog
192
- open={open && expanded}
193
- onOpenChange={(nextOpen) => {
194
- if (!nextOpen) closeChat();
195
- }}
196
- >
197
- <AIChatWindow
198
- headerActions={
199
- <ChatSurfaceActions
200
- expanded
201
- onExpandedChange={setExpanded}
202
- onClose={closeChat}
203
- />
204
- }
205
- composerActions={composerActions}
206
- enableAttachments
207
- attachmentActionIndex={1}
208
- />
209
- </ChatDialog>
210
- <AIChatFloatingTrigger unreadCount={unreadCount} />
211
- </>
212
- );
213
- }
@@ -1,3 +0,0 @@
1
- export * from "./components";
2
- export * from "./providers";
3
- export * from "./services";
@@ -1,323 +0,0 @@
1
- import {
2
- createContext,
3
- useCallback,
4
- useContext,
5
- useEffect,
6
- useMemo,
7
- useState,
8
- type PropsWithChildren,
9
- } from "react";
10
- import { nocobaseAIService, type AIService } from "../services";
11
- import { NocoBaseChatTransport } from "./chat-transport";
12
- import { useAIChatController, type AIChatController } from "./chat-controller";
13
- import {
14
- AIFormRegistryProvider,
15
- createFormFillerInvoker,
16
- useAIFormRegistry,
17
- } from "./form-registry";
18
- import {
19
- AIFrontendToolRegistryProvider,
20
- createFrontendToolInvokers,
21
- useAIFrontendToolRegistry,
22
- } from "./frontend-tool-registry";
23
- import type {
24
- AIConfigurationStatus,
25
- AIConversation,
26
- AIEmployee,
27
- AIModel,
28
- AIToolCallInvocationContext,
29
- AIToolInvokerMap,
30
- AITransportFactory,
31
- } from "./types";
32
-
33
- const UNCONFIGURED_MODEL: AIModel = {
34
- value: "__unconfigured__",
35
- label: "No enabled model",
36
- configured: false,
37
- };
38
-
39
- const RESERVED_TOOL_INVOKER_NAMES = [
40
- "formFiller",
41
- "loadFrontendTool",
42
- "executeFrontendTool",
43
- ] as const;
44
-
45
- type AIProviderValue = {
46
- configurationStatus: AIConfigurationStatus;
47
- configurationError?: Error;
48
- modelConfigurationError?: Error;
49
- hasEnabledModels: boolean;
50
- employees: AIEmployee[];
51
- models: AIModel[];
52
- globalController: AIChatController;
53
- createTransport: AITransportFactory;
54
- uploadFile: AIService["uploadFile"];
55
- updateEmployeeUserPrompt: (username: string, prompt: string) => Promise<void>;
56
- listConversations: (keyword?: string) => Promise<AIConversation[]>;
57
- getConversationMessages: AIService["getConversationMessages"];
58
- getConversationActiveState: AIService["getConversationActiveState"];
59
- updateConversationTitle: (sessionId: string, title: string) => Promise<void>;
60
- destroyConversation: (sessionId: string) => Promise<void>;
61
- updateToolCallDecision: AIService["updateToolCallDecision"];
62
- invokeToolCall: (
63
- toolName: string,
64
- input: unknown,
65
- context: AIToolCallInvocationContext
66
- ) => Promise<{ handled: boolean; result?: unknown }>;
67
- canAutoApproveToolCall: (
68
- toolName: string,
69
- input: unknown,
70
- context: AIToolCallInvocationContext
71
- ) => boolean;
72
- };
73
-
74
- const AIContext = createContext<AIProviderValue | null>(null);
75
-
76
- export type AIProviderProps = PropsWithChildren<{
77
- employees?: AIEmployee[];
78
- models?: AIModel[];
79
- service?: AIService;
80
- toolInvokers?: AIToolInvokerMap;
81
- globalController?: AIChatController;
82
- }>;
83
-
84
- export function AIProvider(props: AIProviderProps) {
85
- return (
86
- <AIFormRegistryProvider>
87
- <AIFrontendToolRegistryProvider>
88
- <AIProviderRuntime {...props} />
89
- </AIFrontendToolRegistryProvider>
90
- </AIFormRegistryProvider>
91
- );
92
- }
93
-
94
- function AIProviderRuntime({
95
- children,
96
- employees: providedEmployees,
97
- models: providedModels,
98
- service = nocobaseAIService,
99
- toolInvokers,
100
- globalController: providedGlobalController,
101
- }: AIProviderProps) {
102
- const formRegistry = useAIFormRegistry();
103
- const frontendToolRegistry = useAIFrontendToolRegistry();
104
- const resolvedToolInvokers = useMemo<AIToolInvokerMap>(
105
- () => {
106
- const reservedToolCollision = RESERVED_TOOL_INVOKER_NAMES.find(
107
- (name) => toolInvokers?.[name]
108
- );
109
- if (reservedToolCollision) {
110
- throw new Error(
111
- `Tool invoker "${reservedToolCollision}" is built into AIProvider and cannot be overridden`
112
- );
113
- }
114
- return {
115
- ...toolInvokers,
116
- ...createFrontendToolInvokers(frontendToolRegistry),
117
- formFiller: createFormFillerInvoker(formRegistry),
118
- };
119
- },
120
- [formRegistry, frontendToolRegistry, toolInvokers]
121
- );
122
- const [liveConfiguration, setLiveConfiguration] = useState<{
123
- employees: AIEmployee[];
124
- models: AIModel[];
125
- status: AIConfigurationStatus;
126
- error?: Error;
127
- modelError?: Error;
128
- }>({ employees: [], models: [], status: "loading" });
129
- const internalGlobalController = useAIChatController();
130
- const globalController = providedGlobalController ?? internalGlobalController;
131
-
132
- useEffect(() => {
133
- if (providedEmployees && providedModels) {
134
- setLiveConfiguration({
135
- employees: providedEmployees,
136
- models: providedModels.length ? providedModels : [UNCONFIGURED_MODEL],
137
- status: "ready",
138
- modelError: providedModels.length
139
- ? undefined
140
- : new Error("No enabled AI models were provided."),
141
- });
142
- return;
143
- }
144
-
145
- let active = true;
146
- setLiveConfiguration((current) => ({
147
- ...current,
148
- status: "loading",
149
- error: undefined,
150
- }));
151
-
152
- void Promise.allSettled([
153
- providedEmployees ?? service.listEmployees(),
154
- providedModels ?? service.listModels(),
155
- ]).then(([employeesResult, modelsResult]) => {
156
- if (!active) return;
157
- if (employeesResult.status === "rejected") {
158
- setLiveConfiguration({
159
- employees: [],
160
- models: [],
161
- status: "error",
162
- error:
163
- employeesResult.reason instanceof Error
164
- ? employeesResult.reason
165
- : new Error("Unable to load NocoBase AI configuration."),
166
- });
167
- return;
168
- }
169
-
170
- const employees = employeesResult.value;
171
- if (!employees.length) {
172
- setLiveConfiguration({
173
- employees: [],
174
- models: [],
175
- status: "error",
176
- error: new Error(
177
- "No AI employees are available for the current NocoBase user."
178
- ),
179
- });
180
- return;
181
- }
182
-
183
- const models =
184
- modelsResult.status === "fulfilled" ? modelsResult.value : [];
185
- const modelError =
186
- modelsResult.status === "rejected"
187
- ? modelsResult.reason instanceof Error
188
- ? modelsResult.reason
189
- : new Error("Unable to load enabled AI models from NocoBase.")
190
- : models.length
191
- ? undefined
192
- : new Error("No enabled AI models were returned by NocoBase.");
193
- setLiveConfiguration({
194
- employees,
195
- models: models.length ? models : [UNCONFIGURED_MODEL],
196
- status: "ready",
197
- modelError,
198
- });
199
- });
200
-
201
- return () => {
202
- active = false;
203
- };
204
- }, [providedEmployees, providedModels, service]);
205
-
206
- const employees = liveConfiguration.employees;
207
- const models = liveConfiguration.models;
208
- const configurationStatus = liveConfiguration.status;
209
- const configurationError = liveConfiguration.error;
210
- const modelConfigurationError = liveConfiguration.modelError;
211
- const hasEnabledModels = models.some((model) => model.configured !== false);
212
-
213
- const updateEmployeeUserPrompt = useCallback(
214
- async (username: string, prompt: string) => {
215
- await service.updateEmployeeUserPrompt(username, prompt);
216
- setLiveConfiguration((current) => ({
217
- ...current,
218
- employees: current.employees.map((employee) =>
219
- employee.username === username
220
- ? {
221
- ...employee,
222
- userConfig: { ...employee.userConfig, prompt },
223
- }
224
- : employee
225
- ),
226
- }));
227
- },
228
- [service]
229
- );
230
-
231
- const invokeToolCall = useCallback(
232
- async (
233
- toolName: string,
234
- input: unknown,
235
- context: AIToolCallInvocationContext
236
- ) => {
237
- const invoke = resolvedToolInvokers[toolName];
238
- if (!invoke) return { handled: false };
239
- return { handled: true, result: await invoke(input, context) };
240
- },
241
- [resolvedToolInvokers]
242
- );
243
-
244
- const canAutoApproveToolCall = useCallback(
245
- (
246
- toolName: string,
247
- input: unknown,
248
- context: AIToolCallInvocationContext
249
- ) => {
250
- if (toolName === "formFiller") return false;
251
- if (
252
- toolName !== "loadFrontendTool" &&
253
- toolName !== "executeFrontendTool"
254
- ) {
255
- return true;
256
- }
257
- const toolId =
258
- input && typeof input === "object" && !Array.isArray(input)
259
- ? (input as { toolId?: unknown }).toolId
260
- : undefined;
261
- if (
262
- typeof toolId !== "string" ||
263
- context.allowedFrontendToolIds?.includes(toolId) !== true
264
- ) {
265
- return false;
266
- }
267
- const manifest = frontendToolRegistry.getManifest(toolId);
268
- if (!manifest) return false;
269
- return toolName === "loadFrontendTool" || manifest.permission === "ALLOW";
270
- },
271
- [frontendToolRegistry]
272
- );
273
-
274
- const value = useMemo<AIProviderValue>(
275
- () => ({
276
- configurationStatus,
277
- configurationError,
278
- modelConfigurationError,
279
- hasEnabledModels,
280
- employees,
281
- models,
282
- globalController,
283
- uploadFile: service.uploadFile.bind(service),
284
- updateEmployeeUserPrompt,
285
- listConversations: service.listConversations.bind(service),
286
- getConversationMessages: service.getConversationMessages.bind(service),
287
- getConversationActiveState:
288
- service.getConversationActiveState.bind(service),
289
- updateConversationTitle: service.updateConversationTitle.bind(service),
290
- destroyConversation: service.destroyConversation.bind(service),
291
- updateToolCallDecision: service.updateToolCallDecision.bind(service),
292
- invokeToolCall,
293
- canAutoApproveToolCall,
294
- createTransport: (options) =>
295
- new NocoBaseChatTransport({ service, ...options }),
296
- }),
297
- [
298
- configurationError,
299
- canAutoApproveToolCall,
300
- configurationStatus,
301
- employees,
302
- globalController,
303
- hasEnabledModels,
304
- invokeToolCall,
305
- modelConfigurationError,
306
- models,
307
- service,
308
- updateEmployeeUserPrompt,
309
- ]
310
- );
311
-
312
- return <AIContext.Provider value={value}>{children}</AIContext.Provider>;
313
- }
314
-
315
- export function useAI() {
316
- const value = useContext(AIContext);
317
- if (!value) throw new Error("useAI must be used inside AIProvider");
318
- return value;
319
- }
320
-
321
- export function useGlobalAIChatController() {
322
- return useAI().globalController;
323
- }