@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,109 +0,0 @@
1
- import { createContext, useContext } from "react";
2
- import type {
3
- AIChatAttachment,
4
- AIChatMessage,
5
- AIConversation,
6
- AIEmployee,
7
- AIEmployeeTask,
8
- AIEmployeeTaskTrigger,
9
- AIModel,
10
- AIToolCallDecision,
11
- AIWorkContextItem,
12
- } from "./types";
13
-
14
- export type AIChatContextValue = {
15
- id: string;
16
- messages: AIChatMessage[];
17
- status: "submitted" | "streaming" | "ready" | "error";
18
- error?: Error;
19
- employees: AIEmployee[];
20
- models: AIModel[];
21
- currentEmployee: AIEmployee;
22
- currentModel: AIModel;
23
- activeConversation?: AIConversation;
24
- activeConversationId: string;
25
- conversations: AIConversation[];
26
- conversationsLoading: boolean;
27
- conversationSearch: string;
28
- messagesLoading: boolean;
29
- historyError?: Error;
30
- interactionError?: Error;
31
- conversationListOpen: boolean;
32
- availableTasks: AIEmployeeTask[];
33
- composerFocusRequest: number;
34
- draft: string;
35
- attachments: AIChatAttachment[];
36
- uploadingAttachments: boolean;
37
- workContext: AIWorkContextItem[];
38
- editingMessageId?: string;
39
- setDraft: (value: string) => void;
40
- uploadFiles: (files: File[]) => Promise<void>;
41
- removeAttachment: (uid: string) => void;
42
- addWorkContext: (item: AIWorkContextItem) => void;
43
- removeWorkContext: (item: AIWorkContextItem) => void;
44
- send: () => Promise<void>;
45
- stop: () => Promise<void>;
46
- regenerate: () => Promise<void>;
47
- retryMessage: (message: AIChatMessage) => Promise<void>;
48
- decideToolCall: (decision: AIToolCallDecision) => Promise<void>;
49
- startNewConversation: () => void;
50
- selectConversation: (conversationId: string) => void;
51
- renameConversation: (conversationId: string, title: string) => Promise<void>;
52
- removeConversation: (conversationId: string) => Promise<void>;
53
- searchConversations: (keyword: string) => Promise<void>;
54
- setConversationListOpen: (open: boolean) => void;
55
- selectEmployee: (username: string) => void;
56
- selectModel: (model: string) => void;
57
- startEditingMessage: (message: AIChatMessage) => Promise<void>;
58
- cancelEditingMessage: () => void;
59
- saveUserPrompt: (prompt: string) => Promise<void>;
60
- triggerTask: (options: AIEmployeeTaskTrigger) => Promise<void>;
61
- runTask: (task: AIEmployeeTask) => void;
62
- focusComposer: () => void;
63
- };
64
-
65
- export type AIChatMessagesContextValue = Pick<AIChatContextValue, "messages">;
66
- export type AIChatStatusContextValue = Pick<
67
- AIChatContextValue,
68
- "status" | "error"
69
- >;
70
- export type AIChatBaseContextValue = Omit<
71
- AIChatContextValue,
72
- keyof AIChatMessagesContextValue | keyof AIChatStatusContextValue
73
- >;
74
-
75
- export const AIChatContext = createContext<AIChatBaseContextValue | null>(null);
76
- export const AIChatMessagesContext =
77
- createContext<AIChatMessagesContextValue | null>(null);
78
- export const AIChatStatusContext =
79
- createContext<AIChatStatusContextValue | null>(null);
80
-
81
- export function useAIChatBase() {
82
- const value = useContext(AIChatContext);
83
- if (!value) throw new Error("useAIChatBase must be used inside AIChatProvider");
84
- return value;
85
- }
86
-
87
- export function useAIChatMessages() {
88
- const value = useContext(AIChatMessagesContext);
89
- if (!value) {
90
- throw new Error("useAIChatMessages must be used inside AIChatProvider");
91
- }
92
- return value;
93
- }
94
-
95
- export function useAIChatStatus() {
96
- const value = useContext(AIChatStatusContext);
97
- if (!value) {
98
- throw new Error("useAIChatStatus must be used inside AIChatProvider");
99
- }
100
- return value;
101
- }
102
-
103
- export function useAIChat() {
104
- return {
105
- ...useAIChatBase(),
106
- ...useAIChatMessages(),
107
- ...useAIChatStatus(),
108
- };
109
- }
@@ -1,84 +0,0 @@
1
- import { useRef, useSyncExternalStore } from "react";
2
- import type { AIEmployeeTaskTrigger } from "./types";
3
-
4
- export type AIChatControllerSnapshot = {
5
- open: boolean;
6
- };
7
-
8
- export type AIChatController = {
9
- getSnapshot: () => AIChatControllerSnapshot;
10
- subscribe: (listener: () => void) => () => void;
11
- setOpen: (open: boolean) => void;
12
- open: () => void;
13
- close: () => void;
14
- triggerTask: (options: AIEmployeeTaskTrigger) => void;
15
- bindTaskHandler: (
16
- handler: (options: AIEmployeeTaskTrigger) => void | Promise<void>
17
- ) => () => void;
18
- };
19
-
20
- export function createAIChatController(): AIChatController {
21
- let snapshot: AIChatControllerSnapshot = { open: false };
22
- let taskHandler:
23
- | ((options: AIEmployeeTaskTrigger) => void | Promise<void>)
24
- | undefined;
25
- const listeners = new Set<() => void>();
26
- const pendingTasks: AIEmployeeTaskTrigger[] = [];
27
-
28
- const setOpen = (open: boolean) => {
29
- if (snapshot.open === open) return;
30
- snapshot = { open };
31
- listeners.forEach((listener) => listener());
32
- };
33
-
34
- return {
35
- getSnapshot: () => snapshot,
36
- subscribe: (listener) => {
37
- listeners.add(listener);
38
- return () => listeners.delete(listener);
39
- },
40
- setOpen,
41
- open: () => setOpen(true),
42
- close: () => setOpen(false),
43
- triggerTask: (options) => {
44
- if (taskHandler) {
45
- void taskHandler(options);
46
- return;
47
- }
48
- pendingTasks.push(options);
49
- if (pendingTasks.length > 20) pendingTasks.shift();
50
- },
51
- bindTaskHandler: (handler) => {
52
- if (taskHandler && taskHandler !== handler) {
53
- console.warn(
54
- "An AIChatController should be bound to only one AIChatProvider at a time."
55
- );
56
- }
57
- taskHandler = handler;
58
- pendingTasks.splice(0).forEach((task) => void handler(task));
59
- return () => {
60
- if (taskHandler === handler) taskHandler = undefined;
61
- };
62
- },
63
- };
64
- }
65
-
66
- export function useAIChatController() {
67
- const controllerRef = useRef<AIChatController | null>(null);
68
- if (!controllerRef.current) {
69
- controllerRef.current = createAIChatController();
70
- }
71
- return controllerRef.current;
72
- }
73
-
74
- const visibleControllerSnapshot: AIChatControllerSnapshot = { open: true };
75
- const subscribeToVisibleController = () => () => undefined;
76
- const getVisibleControllerSnapshot = () => visibleControllerSnapshot;
77
-
78
- export function useAIChatControllerState(controller?: AIChatController) {
79
- return useSyncExternalStore(
80
- controller?.subscribe ?? subscribeToVisibleController,
81
- controller?.getSnapshot ?? getVisibleControllerSnapshot,
82
- controller?.getSnapshot ?? getVisibleControllerSnapshot
83
- );
84
- }
@@ -1,198 +0,0 @@
1
- import type { AIChatMessage, AISubAgentConversation } from "./types";
2
-
3
- export type AIToolPart = Extract<
4
- AIChatMessage["parts"][number],
5
- { type: `tool-${string}` | "dynamic-tool" }
6
- >;
7
-
8
- export type AISubAgentPart = Extract<
9
- AIChatMessage["parts"][number],
10
- { type: "data-subAgent" }
11
- >;
12
-
13
- export const isAIToolPart = (
14
- part: AIChatMessage["parts"][number]
15
- ): part is AIToolPart =>
16
- part.type === "dynamic-tool" || part.type.startsWith("tool-");
17
-
18
- export const isAISubAgentPart = (
19
- part: AIChatMessage["parts"][number]
20
- ): part is AISubAgentPart => part.type === "data-subAgent";
21
-
22
- export const getNocoBaseToolMetadata = (part: AIToolPart) => {
23
- if (!("callProviderMetadata" in part)) return undefined;
24
- const metadata = part.callProviderMetadata?.nocobase;
25
- return metadata && typeof metadata === "object"
26
- ? (metadata as { autoApprove?: unknown })
27
- : undefined;
28
- };
29
-
30
- const getMessageToolCallIds = (message: AIChatMessage) =>
31
- new Set(
32
- message.parts
33
- .filter(isAIToolPart)
34
- .map((part) => part.toolCallId)
35
- .filter(Boolean)
36
- );
37
-
38
- const getMessageText = (message: AIChatMessage) =>
39
- message.parts
40
- .filter((part) => part.type === "text")
41
- .map((part) => part.text)
42
- .join("\n");
43
-
44
- const getSubAgentSessionIds = (message: AIChatMessage) =>
45
- new Set(
46
- message.parts
47
- .filter(isAISubAgentPart)
48
- .map((part) => part.data.sessionId)
49
- .filter(Boolean)
50
- );
51
-
52
- const reconcileConversation = (
53
- current: AISubAgentConversation,
54
- refreshed: AISubAgentConversation
55
- ): AISubAgentConversation => ({
56
- ...refreshed,
57
- messages: reconcileRefreshedMessages(current.messages, refreshed.messages),
58
- });
59
-
60
- const reconcileSubAgents = (
61
- currentMessage: AIChatMessage,
62
- refreshedMessage: AIChatMessage
63
- ) => {
64
- const current = currentMessage.parts.filter(isAISubAgentPart);
65
- return refreshedMessage.parts.map((part) => {
66
- if (!isAISubAgentPart(part)) return part;
67
- const currentPart = current.find(
68
- (item) => item.data.sessionId === part.data.sessionId
69
- );
70
- return currentPart
71
- ? {
72
- ...part,
73
- data: reconcileConversation(currentPart.data, part.data),
74
- }
75
- : part;
76
- });
77
- };
78
-
79
- export const reconcileRefreshedMessages = (
80
- current: AIChatMessage[],
81
- refreshed: AIChatMessage[]
82
- ) => {
83
- const usedCurrentIds = new Set<string>();
84
- const byServerMessageId = new Map<string, AIChatMessage>();
85
- const bySubAgentSessionId = new Map<string, AIChatMessage[]>();
86
- const byToolCallId = new Map<string, AIChatMessage[]>();
87
- const byRoleAndText = new Map<string, AIChatMessage[]>();
88
- const addToIndex = (
89
- index: Map<string, AIChatMessage[]>,
90
- key: string,
91
- message: AIChatMessage
92
- ) => {
93
- const messages = index.get(key);
94
- if (messages) messages.push(message);
95
- else index.set(key, [message]);
96
- };
97
- for (const message of current) {
98
- const serverMessageId = message.metadata?.serverMessageId;
99
- if (serverMessageId) byServerMessageId.set(serverMessageId, message);
100
- for (const sessionId of getSubAgentSessionIds(message)) {
101
- addToIndex(bySubAgentSessionId, sessionId, message);
102
- }
103
- for (const toolCallId of getMessageToolCallIds(message)) {
104
- addToIndex(byToolCallId, toolCallId, message);
105
- }
106
- const text = getMessageText(message);
107
- if (text) addToIndex(byRoleAndText, `${message.role}\0${text}`, message);
108
- }
109
- const firstUnused = (messages: AIChatMessage[] | undefined) =>
110
- messages?.find((message) => !usedCurrentIds.has(message.id));
111
-
112
- return refreshed.map((serverMessage) => {
113
- const serverToolCallIds = getMessageToolCallIds(serverMessage);
114
- const serverMessageId = serverMessage.metadata?.serverMessageId;
115
- let match = serverMessageId
116
- ? byServerMessageId.get(serverMessageId)
117
- : undefined;
118
- if (match && usedCurrentIds.has(match.id)) match = undefined;
119
- const serverSubAgentSessionIds = getSubAgentSessionIds(serverMessage);
120
- if (!match && serverSubAgentSessionIds.size) {
121
- for (const sessionId of serverSubAgentSessionIds) {
122
- match = firstUnused(bySubAgentSessionId.get(sessionId));
123
- if (match?.role === serverMessage.role) break;
124
- match = undefined;
125
- }
126
- }
127
- if (!match && !serverSubAgentSessionIds.size && serverToolCallIds.size) {
128
- for (const toolCallId of serverToolCallIds) {
129
- match = firstUnused(byToolCallId.get(toolCallId));
130
- if (match?.role === serverMessage.role) break;
131
- match = undefined;
132
- }
133
- }
134
- if (!match && !serverSubAgentSessionIds.size && !serverToolCallIds.size) {
135
- const serverText = getMessageText(serverMessage);
136
- if (serverText) {
137
- match = firstUnused(
138
- byRoleAndText.get(`${serverMessage.role}\0${serverText}`)
139
- );
140
- }
141
- }
142
- if (!match) return serverMessage;
143
- usedCurrentIds.add(match.id);
144
- return {
145
- ...serverMessage,
146
- id: match.id,
147
- parts: reconcileSubAgents(match, serverMessage),
148
- };
149
- });
150
- };
151
-
152
- export type ResolvedChatMessage = {
153
- rootMessage: AIChatMessage;
154
- targetMessage: AIChatMessage;
155
- rootIndex: number;
156
- };
157
-
158
- export const findChatMessage = (
159
- messages: AIChatMessage[],
160
- messageId: string
161
- ): ResolvedChatMessage | undefined => {
162
- const findNestedMessage = (
163
- message: AIChatMessage
164
- ): AIChatMessage | undefined => {
165
- if (message.id === messageId) return message;
166
- for (const part of message.parts) {
167
- if (!isAISubAgentPart(part)) continue;
168
- for (const nestedMessage of part.data.messages) {
169
- const match = findNestedMessage(nestedMessage);
170
- if (match) return match;
171
- }
172
- }
173
- return undefined;
174
- };
175
-
176
- for (const [rootIndex, rootMessage] of messages.entries()) {
177
- const targetMessage = findNestedMessage(rootMessage);
178
- if (targetMessage) return { rootMessage, targetMessage, rootIndex };
179
- }
180
- return undefined;
181
- };
182
-
183
- export const getChatToolParts = (messages: AIChatMessage[]) => {
184
- const result: Array<{ message: AIChatMessage; part: AIToolPart }> = [];
185
- const visit = (items: AIChatMessage[]) => {
186
- for (const message of items) {
187
- for (const part of message.parts) {
188
- if (isAIToolPart(part)) {
189
- result.push({ message, part });
190
- } else if (isAISubAgentPart(part)) {
191
- visit(part.data.messages);
192
- }
193
- }
194
- }
195
- };
196
- visit(messages);
197
- return result;
198
- };