@nocobase/portal-template-default 1.0.3 → 1.0.5

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 +17 -17
  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,463 +0,0 @@
1
- import type { InferUIMessageChunk } from "ai";
2
- import { parseNocoBaseSSE, type NocoBaseStreamEvent } from "./stream-parser";
3
- import { StreamCoalescer } from "./stream-coalescer";
4
- import {
5
- getToolCallState,
6
- getToolProviderMetadata,
7
- isRecord,
8
- parseToolInput,
9
- toolCallsFromEvent,
10
- type NocoBaseToolCall,
11
- } from "./stream-event-utils";
12
- import { SubAgentStreamAccumulator } from "./sub-agent-stream";
13
- import type { AIChatMessage } from "./types";
14
-
15
- const VISUAL_DELTA_FLUSH_INTERVAL = 50;
16
- const VISUAL_DELTA_FLUSH_SIZE = 768;
17
- const TOOL_INPUT_FLUSH_INTERVAL = 50;
18
- const TOOL_INPUT_FLUSH_SIZE = 2048;
19
-
20
- type AIChatChunk = InferUIMessageChunk<AIChatMessage>;
21
-
22
- type BufferedVisualDelta = {
23
- type: "text-delta" | "reasoning-delta";
24
- id: string;
25
- delta: string;
26
- };
27
-
28
- type BufferedSubAgentEvent = {
29
- event: NocoBaseStreamEvent;
30
- content: string;
31
- };
32
-
33
- type BufferedSubAgentToolEvent = {
34
- event: NocoBaseStreamEvent;
35
- chunks: NocoBaseToolCall[];
36
- size: number;
37
- };
38
-
39
- const reasoningContent = (event: NocoBaseStreamEvent) => {
40
- if (typeof event.body === "object" && event.body) {
41
- const content = (event.body as { content?: unknown }).content;
42
- return typeof content === "string" ? content : "";
43
- }
44
- return "";
45
- };
46
-
47
- const mergeToolCallChunks = (
48
- current: BufferedSubAgentToolEvent,
49
- incoming: BufferedSubAgentToolEvent
50
- ) => {
51
- const chunks = current.chunks.map((chunk) => ({ ...chunk }));
52
- for (const chunk of incoming.chunks) {
53
- const last = chunks.at(-1);
54
- if (
55
- last &&
56
- !chunk.name &&
57
- (chunk.id === undefined || chunk.id === last.id) &&
58
- (chunk.index === undefined || chunk.index === last.index) &&
59
- typeof chunk.args === "string"
60
- ) {
61
- last.args = `${typeof last.args === "string" ? last.args : ""}${
62
- chunk.args
63
- }`;
64
- } else {
65
- chunks.push({ ...chunk });
66
- }
67
- }
68
- return {
69
- event: incoming.event,
70
- chunks,
71
- size: current.size + incoming.size,
72
- };
73
- };
74
-
75
- export function createNocoBaseUIMessageStream(
76
- stream: ReadableStream<Uint8Array>,
77
- messageId: string | null = `assistant-${crypto.randomUUID()}`,
78
- options: { waitForNewMessage?: boolean; seedMessage?: AIChatMessage } = {}
79
- ): ReadableStream<AIChatChunk> {
80
- return new ReadableStream<AIChatChunk>({
81
- async start(controller) {
82
- let textId = `text-${crypto.randomUUID()}`;
83
- let reasoningId = `reasoning-${crypto.randomUUID()}`;
84
- let textStarted = false;
85
- let reasoningStarted = false;
86
- let currentToolCallId: string | undefined;
87
- let responseStarted = options.waitForNewMessage !== true;
88
- const announcedToolCalls = new Set<string>();
89
- const toolCallNames = new Map<string, string>();
90
- const toolCallInputs = new Map<string, unknown>();
91
- const subAgents = new SubAgentStreamAccumulator(options.seedMessage);
92
- controller.enqueue(
93
- messageId === null ? { type: "start" } : { type: "start", messageId }
94
- );
95
-
96
- const visualDeltas = new StreamCoalescer<string, BufferedVisualDelta>({
97
- interval: VISUAL_DELTA_FLUSH_INTERVAL,
98
- maxSize: VISUAL_DELTA_FLUSH_SIZE,
99
- getSize: (value) => value.delta.length,
100
- merge: (current, incoming) => ({
101
- ...incoming,
102
- delta: `${current.delta}${incoming.delta}`,
103
- }),
104
- onFlush: (_, value) => controller.enqueue(value),
105
- });
106
-
107
- const toolInputDeltas = new StreamCoalescer<string, string>({
108
- interval: TOOL_INPUT_FLUSH_INTERVAL,
109
- maxSize: TOOL_INPUT_FLUSH_SIZE,
110
- getSize: (value) => value.length,
111
- merge: (current, incoming) => `${current}${incoming}`,
112
- onFlush: (toolCallId, inputTextDelta) => {
113
- controller.enqueue({
114
- type: "tool-input-delta",
115
- toolCallId,
116
- inputTextDelta,
117
- });
118
- },
119
- });
120
-
121
- const subAgentToolEvents = new StreamCoalescer<
122
- string,
123
- BufferedSubAgentToolEvent
124
- >({
125
- interval: TOOL_INPUT_FLUSH_INTERVAL,
126
- maxSize: TOOL_INPUT_FLUSH_SIZE,
127
- getSize: (value) => value.size,
128
- merge: mergeToolCallChunks,
129
- onFlush: (_, value) => {
130
- for (const chunk of subAgents.process({
131
- ...value.event,
132
- body: value.chunks,
133
- })) {
134
- controller.enqueue(chunk);
135
- }
136
- },
137
- });
138
-
139
- const enqueueSubAgentToolEvent = (event: NocoBaseStreamEvent) => {
140
- const sessionId = event.sessionId;
141
- if (!sessionId) return;
142
- const chunks = toolCallsFromEvent(event);
143
- subAgentToolEvents.push(sessionId, {
144
- event,
145
- chunks,
146
- size: chunks.reduce(
147
- (size, chunk) =>
148
- size + (typeof chunk.args === "string" ? chunk.args.length : 0),
149
- 0
150
- ),
151
- });
152
- };
153
-
154
- const enqueueVisualDelta = (chunk: BufferedVisualDelta) => {
155
- visualDeltas.push(`${chunk.type}:${chunk.id}`, chunk);
156
- };
157
-
158
- const subAgentNarrativeEvents = new StreamCoalescer<
159
- string,
160
- BufferedSubAgentEvent
161
- >({
162
- interval: VISUAL_DELTA_FLUSH_INTERVAL,
163
- maxSize: VISUAL_DELTA_FLUSH_SIZE,
164
- getSize: (value) => value.content.length,
165
- merge: (current, incoming) => ({
166
- event: incoming.event,
167
- content: `${current.content}${incoming.content}`,
168
- }),
169
- onFlush: (_, pending) => {
170
- const body =
171
- pending.event.type === "reasoning" && isRecord(pending.event.body)
172
- ? { ...pending.event.body, content: pending.content }
173
- : pending.content;
174
- for (const chunk of subAgents.process({ ...pending.event, body })) {
175
- controller.enqueue(chunk);
176
- }
177
- },
178
- });
179
-
180
- const subAgentNarrativeKey = (sessionId: string, type: string) =>
181
- `${sessionId}:${type}`;
182
-
183
- const flushSubAgentNarrativeEvents = (sessionId?: string) => {
184
- if (!sessionId) {
185
- subAgentNarrativeEvents.flushAll();
186
- return;
187
- }
188
- subAgentNarrativeEvents.flush(
189
- subAgentNarrativeKey(sessionId, "reasoning")
190
- );
191
- subAgentNarrativeEvents.flush(
192
- subAgentNarrativeKey(sessionId, "content")
193
- );
194
- };
195
-
196
- const enqueueSubAgentNarrativeEvent = (event: NocoBaseStreamEvent) => {
197
- const sessionId = event.sessionId;
198
- if (!sessionId) return;
199
- const content =
200
- event.type === "content" && typeof event.body === "string"
201
- ? event.body
202
- : event.type === "reasoning" && isRecord(event.body)
203
- ? typeof event.body.content === "string"
204
- ? event.body.content
205
- : ""
206
- : "";
207
- if (!content) return;
208
- const otherType = event.type === "content" ? "reasoning" : "content";
209
- subAgentNarrativeEvents.flush(
210
- subAgentNarrativeKey(sessionId, otherType)
211
- );
212
- subAgentNarrativeEvents.push(
213
- subAgentNarrativeKey(sessionId, event.type),
214
- { event, content }
215
- );
216
- };
217
-
218
- const finishActiveText = () => {
219
- if (!textStarted) return;
220
- visualDeltas.flush(`text-delta:${textId}`);
221
- controller.enqueue({ type: "text-end", id: textId });
222
- textStarted = false;
223
- textId = `text-${crypto.randomUUID()}`;
224
- };
225
-
226
- const finishActiveReasoning = () => {
227
- if (!reasoningStarted) return;
228
- visualDeltas.flush(`reasoning-delta:${reasoningId}`);
229
- controller.enqueue({ type: "reasoning-end", id: reasoningId });
230
- reasoningStarted = false;
231
- reasoningId = `reasoning-${crypto.randomUUID()}`;
232
- };
233
-
234
- const finishActiveNarrative = () => {
235
- finishActiveReasoning();
236
- finishActiveText();
237
- };
238
-
239
- try {
240
- for await (const event of parseNocoBaseSSE(stream)) {
241
- if (event.type !== "tool_call_chunks") {
242
- toolInputDeltas.flushAll();
243
- }
244
- if (event.from === "sub-agent") {
245
- finishActiveNarrative();
246
- if (event.type === "content" || event.type === "reasoning") {
247
- if (event.sessionId) subAgentToolEvents.flush(event.sessionId);
248
- enqueueSubAgentNarrativeEvent(event);
249
- continue;
250
- }
251
- flushSubAgentNarrativeEvents(event.sessionId);
252
- if (event.type === "tool_call_chunks") {
253
- enqueueSubAgentToolEvent(event);
254
- continue;
255
- }
256
- if (event.sessionId) subAgentToolEvents.flush(event.sessionId);
257
- for (const chunk of subAgents.process(event)) {
258
- controller.enqueue(chunk);
259
- }
260
- continue;
261
- }
262
-
263
- flushSubAgentNarrativeEvents();
264
- subAgentToolEvents.flushAll();
265
-
266
- if (event.type === "new_message") {
267
- responseStarted = true;
268
- continue;
269
- }
270
-
271
- if (!responseStarted && event.type !== "error") {
272
- continue;
273
- }
274
-
275
- if (event.type === "content" && typeof event.body === "string") {
276
- finishActiveReasoning();
277
- if (!textStarted) {
278
- controller.enqueue({ type: "text-start", id: textId });
279
- textStarted = true;
280
- }
281
- enqueueVisualDelta({
282
- type: "text-delta",
283
- id: textId,
284
- delta: event.body,
285
- });
286
- }
287
-
288
- if (event.type === "reasoning") {
289
- finishActiveText();
290
- const delta = reasoningContent(event);
291
- if (delta && !reasoningStarted) {
292
- controller.enqueue({
293
- type: "reasoning-start",
294
- id: reasoningId,
295
- });
296
- reasoningStarted = true;
297
- }
298
- if (delta) {
299
- enqueueVisualDelta({
300
- type: "reasoning-delta",
301
- id: reasoningId,
302
- delta,
303
- });
304
- }
305
- }
306
-
307
- if (event.type === "tool_call_chunks") {
308
- finishActiveNarrative();
309
- for (const chunk of toolCallsFromEvent(event)) {
310
- const toolCallId = chunk.id ?? currentToolCallId;
311
- if (!toolCallId) continue;
312
- currentToolCallId = toolCallId;
313
- if (chunk.name && !announcedToolCalls.has(toolCallId)) {
314
- toolCallNames.set(toolCallId, chunk.name);
315
- controller.enqueue({
316
- type: "tool-input-start",
317
- toolCallId,
318
- toolName: chunk.name,
319
- dynamic: true,
320
- });
321
- announcedToolCalls.add(toolCallId);
322
- }
323
- if (typeof chunk.args === "string" && chunk.args) {
324
- const previousInput = toolCallInputs.get(toolCallId);
325
- toolCallInputs.set(
326
- toolCallId,
327
- `${typeof previousInput === "string" ? previousInput : ""}${
328
- chunk.args
329
- }`
330
- );
331
- toolInputDeltas.push(toolCallId, chunk.args);
332
- }
333
- }
334
- }
335
-
336
- if (event.type === "tool_calls") {
337
- finishActiveNarrative();
338
- for (const toolCall of toolCallsFromEvent(event)) {
339
- const toolCallId = toolCall.id ?? `tool-${crypto.randomUUID()}`;
340
- const toolName = toolCall.name ?? "tool";
341
- const toolState = getToolCallState(toolCall);
342
- const { invokeStatus, resultStatus } = toolState;
343
- const toolInput = parseToolInput(
344
- toolCall.args ?? toolCall.input ?? {}
345
- );
346
- toolCallNames.set(toolCallId, toolName);
347
- toolCallInputs.set(toolCallId, toolInput);
348
- controller.enqueue({
349
- type: "tool-input-available",
350
- toolCallId,
351
- toolName,
352
- input: toolInput,
353
- providerMetadata: getToolProviderMetadata(toolCall),
354
- dynamic: true,
355
- });
356
- announcedToolCalls.add(toolCallId);
357
-
358
- if (toolState.failed) {
359
- controller.enqueue({
360
- type: "tool-output-error",
361
- toolCallId,
362
- errorText: String(
363
- toolCall.content ?? toolCall.output ?? "Tool call failed"
364
- ),
365
- dynamic: true,
366
- });
367
- } else if (toolState.completed) {
368
- controller.enqueue({
369
- type: "tool-output-available",
370
- toolCallId,
371
- output: toolCall.output ??
372
- toolCall.content ?? {
373
- status: invokeStatus || resultStatus,
374
- },
375
- dynamic: true,
376
- });
377
- }
378
- }
379
- }
380
-
381
- if (event.type === "tool_call_status" && isRecord(event.body)) {
382
- finishActiveNarrative();
383
- const toolCall = isRecord(event.body.toolCall)
384
- ? (event.body.toolCall as NocoBaseToolCall)
385
- : undefined;
386
- const toolCallId = toolCall?.id;
387
- if (toolCallId) {
388
- const mergedToolCall = {
389
- ...toolCall,
390
- invokeStatus:
391
- typeof event.body.invokeStatus === "string"
392
- ? event.body.invokeStatus
393
- : toolCall.invokeStatus,
394
- status:
395
- typeof event.body.status === "string"
396
- ? event.body.status
397
- : toolCall.status,
398
- content: event.body.content ?? toolCall.content,
399
- } satisfies NocoBaseToolCall;
400
- const toolState = getToolCallState(mergedToolCall);
401
- const { invokeStatus, resultStatus } = toolState;
402
- const toolName =
403
- toolCall.name ?? toolCallNames.get(toolCallId) ?? "tool";
404
- const toolInput =
405
- toolCall.args !== undefined || toolCall.input !== undefined
406
- ? parseToolInput(toolCall.args ?? toolCall.input)
407
- : parseToolInput(toolCallInputs.get(toolCallId) ?? {});
408
- toolCallNames.set(toolCallId, toolName);
409
- toolCallInputs.set(toolCallId, toolInput);
410
- controller.enqueue({
411
- type: "tool-input-available",
412
- toolCallId,
413
- toolName,
414
- input: toolInput,
415
- providerMetadata: getToolProviderMetadata(mergedToolCall),
416
- dynamic: true,
417
- });
418
- announcedToolCalls.add(toolCallId);
419
- if (toolState.failed) {
420
- controller.enqueue({
421
- type: "tool-output-error",
422
- toolCallId,
423
- errorText: String(event.body.content ?? "Tool call failed"),
424
- dynamic: true,
425
- });
426
- } else if (toolState.completed) {
427
- controller.enqueue({
428
- type: "tool-output-available",
429
- toolCallId,
430
- output: event.body.content ?? {
431
- status: invokeStatus || resultStatus,
432
- },
433
- dynamic: true,
434
- });
435
- }
436
- }
437
- }
438
-
439
- if (event.type === "error") {
440
- controller.enqueue({
441
- type: "error",
442
- errorText: String(event.body ?? "AI response failed"),
443
- });
444
- }
445
- }
446
-
447
- toolInputDeltas.flushAll();
448
- visualDeltas.flushAll();
449
- flushSubAgentNarrativeEvents();
450
- subAgentToolEvents.flushAll();
451
- finishActiveNarrative();
452
- controller.enqueue({ type: "finish" });
453
- controller.close();
454
- } catch (error) {
455
- toolInputDeltas.clear();
456
- visualDeltas.clear();
457
- subAgentNarrativeEvents.clear();
458
- subAgentToolEvents.clear();
459
- controller.error(error);
460
- }
461
- },
462
- });
463
- }
@@ -1,94 +0,0 @@
1
- import type { Chat } from "@ai-sdk/react";
2
- import { useCallback, useRef } from "react";
3
- import { getChatToolParts, getNocoBaseToolMetadata } from "./chat-message-utils";
4
- import type { AIChatMessage, AIToolCallDecision } from "./types";
5
-
6
- const isChatRunning = (chat: Chat<AIChatMessage>) =>
7
- chat.status === "streaming" || chat.status === "submitted";
8
-
9
- export function useAutomaticToolApproval({
10
- enabled,
11
- decide,
12
- }: {
13
- enabled: boolean;
14
- decide: (
15
- conversationId: string,
16
- targetChat: Chat<AIChatMessage>,
17
- decision: AIToolCallDecision,
18
- options?: { automatic?: boolean }
19
- ) => Promise<void>;
20
- }) {
21
- const approvedRef = useRef(new Set<string>());
22
- const runningRef = useRef(new Set<string>());
23
- const rerunRef = useRef(new Set<string>());
24
-
25
- const process = useCallback(
26
- async (conversationId: string, targetChat: Chat<AIChatMessage>) => {
27
- if (!enabled || isChatRunning(targetChat)) return;
28
- if (runningRef.current.has(conversationId)) {
29
- rerunRef.current.add(conversationId);
30
- return;
31
- }
32
-
33
- runningRef.current.add(conversationId);
34
- try {
35
- do {
36
- rerunRef.current.delete(conversationId);
37
- const pending = getChatToolParts(targetChat.messages)
38
- .filter(
39
- ({ part }) => getNocoBaseToolMetadata(part)?.autoApprove === true
40
- )
41
- .flatMap(({ message, part }) => {
42
- const key = `${conversationId}:${message.id}:${part.toolCallId}`;
43
- return approvedRef.current.has(key)
44
- ? []
45
- : [{ key, message, part }];
46
- });
47
-
48
- for (const { key, message, part } of pending) {
49
- approvedRef.current.add(key);
50
- try {
51
- await decide(
52
- conversationId,
53
- targetChat,
54
- {
55
- messageId: message.id,
56
- toolCallId: part.toolCallId,
57
- toolName:
58
- part.type === "dynamic-tool"
59
- ? part.toolName
60
- : part.type.slice(5),
61
- decision: "approve",
62
- },
63
- { automatic: true }
64
- );
65
- } catch (error) {
66
- approvedRef.current.delete(key);
67
- throw error;
68
- }
69
- }
70
- } while (rerunRef.current.has(conversationId));
71
- } finally {
72
- runningRef.current.delete(conversationId);
73
- rerunRef.current.delete(conversationId);
74
- }
75
- },
76
- [decide, enabled]
77
- );
78
-
79
- const clearConversation = useCallback((conversationId: string) => {
80
- for (const key of approvedRef.current) {
81
- if (key.startsWith(`${conversationId}:`)) approvedRef.current.delete(key);
82
- }
83
- runningRef.current.delete(conversationId);
84
- rerunRef.current.delete(conversationId);
85
- }, []);
86
-
87
- const reset = useCallback(() => {
88
- approvedRef.current.clear();
89
- runningRef.current.clear();
90
- rerunRef.current.clear();
91
- }, []);
92
-
93
- return { clearConversation, process, reset };
94
- }