@nocobase/portal-template-default 1.0.1 → 1.0.3
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/README.MD +22 -3
- package/package.json +4 -2
- package/registry/nocobase-acl/components/role-options.ts +7 -0
- package/registry/nocobase-acl/components/role-switcher.tsx +9 -4
- package/registry/nocobase-acl/demo/boundary-api.tsx +87 -16
- package/registry/nocobase-acl/demo/components.tsx +32 -11
- package/registry/nocobase-acl/demo/index.tsx +45 -14
- package/registry/nocobase-acl/demo/lazy-route.tsx +2 -7
- package/registry/nocobase-acl/demo/prompt-generator.tsx +52 -33
- package/registry/nocobase-acl/demo/role-switcher-prompt-generator.tsx +28 -9
- package/registry/nocobase-acl/extension.tsx +1 -1
- package/registry/nocobase-acl/tests/acl-regression.mjs +7 -6
- package/registry/nocobase-ai/components/chat/chat-messages.tsx +3 -4
- package/registry/nocobase-ai/components/chat/conversation-list.tsx +2 -4
- package/registry/nocobase-ai/components/tools/business-report-dialog.tsx +16 -19
- package/registry/nocobase-ai/components/tools/chart-renderer.tsx +3 -14
- package/registry/nocobase-ai/components/tools/echarts-preview.tsx +2 -5
- package/registry/nocobase-ai/demo/configuration-gate.tsx +9 -12
- package/registry/nocobase-ai/extension.tsx +2 -7
- package/registry/nocobase-i18n/README.md +53 -0
- package/registry/nocobase-i18n/components/index.ts +1 -0
- package/registry/nocobase-i18n/components/language-switcher.tsx +138 -0
- package/registry/nocobase-i18n/demo/index.tsx +209 -0
- package/registry/nocobase-i18n/demo/prompt-generator.tsx +155 -0
- package/registry/nocobase-i18n/extension.tsx +46 -0
- package/registry/nocobase-i18n/i18n-provider.ts +9 -0
- package/registry/nocobase-i18n/index.ts +6 -0
- package/registry/nocobase-i18n/locale-store.ts +134 -0
- package/registry/nocobase-i18n/locales/en-US.ts +89 -0
- package/registry/nocobase-i18n/locales/zh-CN.ts +85 -0
- package/registry/nocobase-i18n/provider.tsx +32 -0
- package/registry/nocobase-i18n/runtime.ts +118 -0
- package/registry/nocobase-i18n/server-resources.ts +184 -0
- package/registry/nocobase-i18n/tests/i18n-regression.mjs +42 -0
- package/registry.config.json +34 -14
- package/scripts/registry.mjs +5 -0
- package/src/App.tsx +27 -86
- package/src/app/extension.ts +3 -1
- package/src/app/extensions.tsx +21 -0
- package/src/components/access-control/acl-gate.tsx +4 -10
- package/src/components/app-shell/breadcrumb.tsx +39 -3
- package/src/components/app-shell/document-title-handler.tsx +11 -20
- package/src/components/app-shell/header.tsx +18 -3
- package/src/components/app-shell/loading-overlay.tsx +2 -4
- package/src/components/app-shell/loading-state.tsx +15 -0
- package/src/components/app-shell/sidebar.tsx +75 -47
- package/src/components/data-table/data-table-pagination.tsx +27 -7
- package/src/components/data-table/data-table-styles.ts +57 -0
- package/src/components/data-table/data-table.tsx +14 -59
- package/src/components/resources/resource-label.ts +101 -0
- package/src/components/resources/views/create-view.tsx +17 -14
- package/src/components/resources/views/edit-view.tsx +17 -15
- package/src/components/resources/views/list-view.tsx +20 -10
- package/src/components/resources/views/show-view.tsx +20 -18
- package/src/extensions/nocobase-acl/README.md +19 -0
- package/src/extensions/nocobase-acl/components/acl-boundary.tsx +106 -0
- package/src/extensions/nocobase-acl/components/index.ts +3 -0
- package/src/extensions/nocobase-acl/components/role-options.ts +36 -0
- package/src/extensions/nocobase-acl/components/role-switcher.tsx +135 -0
- package/src/extensions/nocobase-acl/demo/boundary-api.tsx +186 -0
- package/src/extensions/nocobase-acl/demo/components.tsx +231 -0
- package/src/extensions/nocobase-acl/demo/index.tsx +505 -0
- package/src/extensions/nocobase-acl/demo/lazy-route.tsx +10 -0
- package/src/extensions/nocobase-acl/demo/prompt-generator.tsx +134 -0
- package/src/extensions/nocobase-acl/demo/role-switcher-prompt-generator.tsx +198 -0
- package/src/extensions/nocobase-acl/demo/scenario-section.tsx +37 -0
- package/src/extensions/nocobase-acl/extension.tsx +71 -0
- package/src/extensions/nocobase-acl/index.ts +1 -0
- package/src/extensions/nocobase-ai/README.md +70 -0
- package/src/extensions/nocobase-ai/adapters/react-hook-form.ts +20 -0
- package/src/extensions/nocobase-ai/assets/nocobase-ai-chat.svg +50 -0
- package/src/extensions/nocobase-ai/components/ai-root-provider.tsx +32 -0
- package/src/extensions/nocobase-ai/components/chat/ai-employee-avatar.tsx +31 -0
- package/src/extensions/nocobase-ai/components/chat/chat-attachment.tsx +92 -0
- package/src/extensions/nocobase-ai/components/chat/chat-compact.tsx +90 -0
- package/src/extensions/nocobase-ai/components/chat/chat-composer.tsx +429 -0
- package/src/extensions/nocobase-ai/components/chat/chat-empty-state.tsx +51 -0
- package/src/extensions/nocobase-ai/components/chat/chat-header.tsx +88 -0
- package/src/extensions/nocobase-ai/components/chat/chat-history-dialog.tsx +77 -0
- package/src/extensions/nocobase-ai/components/chat/chat-message.tsx +252 -0
- package/src/extensions/nocobase-ai/components/chat/chat-messages.tsx +173 -0
- package/src/extensions/nocobase-ai/components/chat/chat-window.tsx +118 -0
- package/src/extensions/nocobase-ai/components/chat/conversation-list.tsx +343 -0
- package/src/extensions/nocobase-ai/components/chat/markdown-message.tsx +165 -0
- package/src/extensions/nocobase-ai/components/chat/model-select-options.tsx +30 -0
- package/src/extensions/nocobase-ai/components/chat/reasoning-panel.tsx +29 -0
- package/src/extensions/nocobase-ai/components/chat/sub-agent-conversation.tsx +142 -0
- package/src/extensions/nocobase-ai/components/chat/tool-call-card.tsx +316 -0
- package/src/extensions/nocobase-ai/components/chat/user-prompt-editor.tsx +99 -0
- package/src/extensions/nocobase-ai/components/chat/work-context-chip.tsx +114 -0
- package/src/extensions/nocobase-ai/components/index.ts +59 -0
- package/src/extensions/nocobase-ai/components/page-elements/ai-form.tsx +44 -0
- package/src/extensions/nocobase-ai/components/page-elements/page-element-provider.tsx +469 -0
- package/src/extensions/nocobase-ai/components/surfaces/chat-dialog.tsx +31 -0
- package/src/extensions/nocobase-ai/components/surfaces/chat-inline.tsx +18 -0
- package/src/extensions/nocobase-ai/components/surfaces/chat-page.tsx +18 -0
- package/src/extensions/nocobase-ai/components/surfaces/chat-side-panel-layout.tsx +54 -0
- package/src/extensions/nocobase-ai/components/surfaces/chat-side-panel.tsx +74 -0
- package/src/extensions/nocobase-ai/components/surfaces/chat-surface-actions.tsx +54 -0
- package/src/extensions/nocobase-ai/components/tools/builtin-tool-renderers.tsx +31 -0
- package/src/extensions/nocobase-ai/components/tools/business-report-dialog.tsx +364 -0
- package/src/extensions/nocobase-ai/components/tools/business-report-renderer.tsx +122 -0
- package/src/extensions/nocobase-ai/components/tools/business-report-utils.tsx +282 -0
- package/src/extensions/nocobase-ai/components/tools/chart-renderer.tsx +99 -0
- package/src/extensions/nocobase-ai/components/tools/echarts-preview.tsx +59 -0
- package/src/extensions/nocobase-ai/components/tools/echarts-runtime-advanced.ts +18 -0
- package/src/extensions/nocobase-ai/components/tools/echarts-runtime-common.ts +20 -0
- package/src/extensions/nocobase-ai/components/tools/echarts-runtime-components.ts +34 -0
- package/src/extensions/nocobase-ai/components/tools/echarts-runtime-hierarchy.ts +24 -0
- package/src/extensions/nocobase-ai/components/tools/echarts-runtime.ts +138 -0
- package/src/extensions/nocobase-ai/components/tools/sub-agent-renderer.tsx +67 -0
- package/src/extensions/nocobase-ai/components/tools/suggestions-renderer.tsx +59 -0
- package/src/extensions/nocobase-ai/components/tools/tool-renderer-provider.tsx +55 -0
- package/src/extensions/nocobase-ai/components/tools/tool-renderer-utils.ts +18 -0
- package/src/extensions/nocobase-ai/components/tools/workflow-renderer.tsx +108 -0
- package/src/extensions/nocobase-ai/components/triggers/ai-chat-floating-trigger.tsx +73 -0
- package/src/extensions/nocobase-ai/components/triggers/ai-employee-shortcut.tsx +149 -0
- package/src/extensions/nocobase-ai/demo/configuration-gate.tsx +49 -0
- package/src/extensions/nocobase-ai/demo/container-showcase.tsx +210 -0
- package/src/extensions/nocobase-ai/demo/floating.tsx +134 -0
- package/src/extensions/nocobase-ai/demo/index.tsx +346 -0
- package/src/extensions/nocobase-ai/demo/interaction-showcase.tsx +151 -0
- package/src/extensions/nocobase-ai/demo/page-context-prompt-generator.tsx +355 -0
- package/src/extensions/nocobase-ai/demo/page-context.tsx +884 -0
- package/src/extensions/nocobase-ai/demo/page-element-showcase.tsx +197 -0
- package/src/extensions/nocobase-ai/demo/prompt-card.tsx +41 -0
- package/src/extensions/nocobase-ai/demo/prompt-generator.tsx +452 -0
- package/src/extensions/nocobase-ai/demo/shortcut.tsx +1207 -0
- package/src/extensions/nocobase-ai/demo/tool-cards.tsx +530 -0
- package/src/extensions/nocobase-ai/extension.tsx +158 -0
- package/src/extensions/nocobase-ai/global-ai-chat.tsx +213 -0
- package/src/extensions/nocobase-ai/index.ts +3 -0
- package/src/extensions/nocobase-ai/providers/ai-provider.tsx +323 -0
- package/src/extensions/nocobase-ai/providers/avatars.ts +817 -0
- package/src/extensions/nocobase-ai/providers/chat-context.tsx +109 -0
- package/src/extensions/nocobase-ai/providers/chat-controller.ts +84 -0
- package/src/extensions/nocobase-ai/providers/chat-message-utils.ts +198 -0
- package/src/extensions/nocobase-ai/providers/chat-provider.tsx +871 -0
- package/src/extensions/nocobase-ai/providers/chat-reducer.ts +147 -0
- package/src/extensions/nocobase-ai/providers/chat-task-utils.ts +105 -0
- package/src/extensions/nocobase-ai/providers/chat-transport.ts +184 -0
- package/src/extensions/nocobase-ai/providers/form-registry.tsx +271 -0
- package/src/extensions/nocobase-ai/providers/frontend-tool-registry.tsx +231 -0
- package/src/extensions/nocobase-ai/providers/index.ts +61 -0
- package/src/extensions/nocobase-ai/providers/model.ts +32 -0
- package/src/extensions/nocobase-ai/providers/page-context.tsx +136 -0
- package/src/extensions/nocobase-ai/providers/stream-coalescer.ts +60 -0
- package/src/extensions/nocobase-ai/providers/stream-event-utils.ts +82 -0
- package/src/extensions/nocobase-ai/providers/stream-parser.ts +61 -0
- package/src/extensions/nocobase-ai/providers/sub-agent-stream.ts +316 -0
- package/src/extensions/nocobase-ai/providers/types.ts +183 -0
- package/src/extensions/nocobase-ai/providers/ui-message-stream.ts +463 -0
- package/src/extensions/nocobase-ai/providers/use-automatic-tool-approval.ts +94 -0
- package/src/extensions/nocobase-ai/providers/use-chat-attachments.ts +206 -0
- package/src/extensions/nocobase-ai/providers/use-chat-message-actions.ts +554 -0
- package/src/extensions/nocobase-ai/providers/use-chat-runtime.ts +210 -0
- package/src/extensions/nocobase-ai/providers/use-chat-work-context.ts +93 -0
- package/src/extensions/nocobase-ai/providers/use-conversation-catalog.ts +108 -0
- package/src/extensions/nocobase-ai/providers/use-conversation-history.ts +178 -0
- package/src/extensions/nocobase-ai/services/index.ts +7 -0
- package/src/extensions/nocobase-ai/services/nocobase-ai-service.ts +454 -0
- package/src/extensions/nocobase-ai/services/types.ts +82 -0
- package/src/index.tsx +2 -3
- package/src/lib/i18n.ts +161 -0
- package/src/lib/nocobase/client.ts +19 -2
- package/src/locales/en-US.ts +82 -0
- package/src/locales/index.ts +13 -0
- package/src/locales/zh-CN.ts +79 -0
- package/src/pages/users/create.tsx +98 -0
- package/src/pages/users/edit.tsx +92 -0
- package/src/pages/users/form-context.ts +57 -0
- package/src/pages/users/form-fields.tsx +211 -0
- package/src/pages/users/list.tsx +205 -0
- package/src/pages/users/show.tsx +187 -0
- package/src/pages/users/types.ts +20 -0
- package/src/providers/constants.ts +1 -0
- package/src/pages/blog-posts/create.tsx +0 -177
- package/src/pages/blog-posts/edit.tsx +0 -197
- package/src/pages/blog-posts/list.tsx +0 -131
- package/src/pages/blog-posts/show.tsx +0 -81
- package/src/pages/categories/create.tsx +0 -73
- package/src/pages/categories/edit.tsx +0 -73
- package/src/pages/categories/index.ts +0 -4
- package/src/pages/categories/list.tsx +0 -86
- package/src/pages/categories/show.tsx +0 -37
- /package/src/pages/{blog-posts → users}/index.ts +0 -0
|
@@ -0,0 +1,109 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
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
|
+
};
|