@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,530 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ToolCallCard,
|
|
3
|
+
type ToolCallPart,
|
|
4
|
+
} from "../components/chat/tool-call-card";
|
|
5
|
+
import { Badge } from "@/components/ui/badge";
|
|
6
|
+
import { Button } from "@/components/ui/button";
|
|
7
|
+
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
8
|
+
import { Input } from "@/components/ui/input";
|
|
9
|
+
import {
|
|
10
|
+
Select,
|
|
11
|
+
SelectContent,
|
|
12
|
+
SelectItem,
|
|
13
|
+
SelectTrigger,
|
|
14
|
+
SelectValue,
|
|
15
|
+
} from "@/components/ui/select";
|
|
16
|
+
import { Switch } from "@/components/ui/switch";
|
|
17
|
+
import { Textarea } from "@/components/ui/textarea";
|
|
18
|
+
import { Check, Copy } from "lucide-react";
|
|
19
|
+
import { useState } from "react";
|
|
20
|
+
|
|
21
|
+
const tools = {
|
|
22
|
+
suggestions: {
|
|
23
|
+
type: "dynamic-tool",
|
|
24
|
+
toolName: "suggestions",
|
|
25
|
+
toolCallId: "tool-demo-suggestions",
|
|
26
|
+
state: "input-available",
|
|
27
|
+
input: {
|
|
28
|
+
options: [
|
|
29
|
+
"Summarize the operational risks",
|
|
30
|
+
"Draft a weekly support report",
|
|
31
|
+
"Propose the next automation",
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
report: {
|
|
36
|
+
type: "dynamic-tool",
|
|
37
|
+
toolName: "businessReportGenerator",
|
|
38
|
+
toolCallId: "tool-demo-report",
|
|
39
|
+
state: "output-available",
|
|
40
|
+
input: {
|
|
41
|
+
title: "Customer support operations · Weekly review",
|
|
42
|
+
summary:
|
|
43
|
+
"Ticket volume remained stable while SLA exposure concentrated in two queues.",
|
|
44
|
+
markdown: `# Weekly support review
|
|
45
|
+
|
|
46
|
+
## Executive summary
|
|
47
|
+
|
|
48
|
+
Ticket volume remained stable, but unresolved priority requests increased in the onboarding and billing queues.
|
|
49
|
+
|
|
50
|
+
{{ chart:1 }}
|
|
51
|
+
|
|
52
|
+
{{ chart:2 }}
|
|
53
|
+
|
|
54
|
+
## Recommended actions
|
|
55
|
+
|
|
56
|
+
- Reassign unowned priority tickets before the next shift.
|
|
57
|
+
- Add an SLA warning automation for billing requests.
|
|
58
|
+
- Review the onboarding knowledge-base gaps with the support lead.`,
|
|
59
|
+
charts: [
|
|
60
|
+
{
|
|
61
|
+
title: "Open tickets by queue",
|
|
62
|
+
options: {
|
|
63
|
+
xAxis: {
|
|
64
|
+
type: "category",
|
|
65
|
+
data: ["Billing", "Onboarding", "API", "General"],
|
|
66
|
+
},
|
|
67
|
+
yAxis: { type: "value" },
|
|
68
|
+
series: [{ type: "bar", data: [31, 26, 18, 14] }],
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
title: "SLA exposure trend",
|
|
73
|
+
options: {
|
|
74
|
+
xAxis: {
|
|
75
|
+
type: "category",
|
|
76
|
+
data: ["Mon", "Tue", "Wed", "Thu", "Fri"],
|
|
77
|
+
},
|
|
78
|
+
yAxis: { type: "value" },
|
|
79
|
+
series: [{ type: "line", smooth: true, data: [8, 11, 9, 15, 12] }],
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
output: { ready: true },
|
|
85
|
+
},
|
|
86
|
+
chart: {
|
|
87
|
+
type: "dynamic-tool",
|
|
88
|
+
toolName: "chartGenerator",
|
|
89
|
+
toolCallId: "tool-demo-chart",
|
|
90
|
+
state: "output-available",
|
|
91
|
+
input: {
|
|
92
|
+
options: {
|
|
93
|
+
tooltip: { trigger: "axis" },
|
|
94
|
+
xAxis: {
|
|
95
|
+
type: "category",
|
|
96
|
+
data: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
|
|
97
|
+
},
|
|
98
|
+
yAxis: { type: "value" },
|
|
99
|
+
series: [
|
|
100
|
+
{ type: "bar", name: "Tickets", data: [42, 64, 51, 78, 58, 86, 72] },
|
|
101
|
+
],
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
output: { ready: true },
|
|
105
|
+
},
|
|
106
|
+
subAgent: {
|
|
107
|
+
type: "dynamic-tool",
|
|
108
|
+
toolName: "dispatch-sub-agent-task",
|
|
109
|
+
toolCallId: "tool-demo-sub-agent",
|
|
110
|
+
state: "input-available",
|
|
111
|
+
input: {
|
|
112
|
+
username: "dex",
|
|
113
|
+
question:
|
|
114
|
+
"Review the current ticket categories and identify where automation would remove the most repetitive work.",
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
workflow: {
|
|
118
|
+
type: "dynamic-tool",
|
|
119
|
+
toolName: "aiEmployeeWorkflowTaskOutput",
|
|
120
|
+
toolCallId: "tool-demo-workflow",
|
|
121
|
+
state: "input-available",
|
|
122
|
+
input: {
|
|
123
|
+
workflowTitle: "Publish weekly service review",
|
|
124
|
+
result: {
|
|
125
|
+
audience: "Support leadership",
|
|
126
|
+
format: "Markdown report",
|
|
127
|
+
records: 128,
|
|
128
|
+
delivery: "Team workspace",
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
approval: {
|
|
133
|
+
type: "dynamic-tool",
|
|
134
|
+
toolName: "updateRecords",
|
|
135
|
+
toolCallId: "tool-demo-approval",
|
|
136
|
+
state: "input-available",
|
|
137
|
+
input: {
|
|
138
|
+
collection: "tickets",
|
|
139
|
+
action: "Assign priority tickets to the on-call team",
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
preparing: {
|
|
143
|
+
type: "dynamic-tool",
|
|
144
|
+
toolName: "inspectWorkspace",
|
|
145
|
+
toolCallId: "tool-demo-preparing",
|
|
146
|
+
state: "input-streaming",
|
|
147
|
+
input: { scope: "support operations" },
|
|
148
|
+
},
|
|
149
|
+
running: {
|
|
150
|
+
type: "dynamic-tool",
|
|
151
|
+
toolName: "searchRecords",
|
|
152
|
+
toolCallId: "tool-demo-running",
|
|
153
|
+
state: "input-available",
|
|
154
|
+
input: { collection: "tickets", filter: { status: "open" } },
|
|
155
|
+
},
|
|
156
|
+
completed: {
|
|
157
|
+
type: "dynamic-tool",
|
|
158
|
+
toolName: "inspectWorkspace",
|
|
159
|
+
toolCallId: "tool-demo-completed",
|
|
160
|
+
state: "output-available",
|
|
161
|
+
input: { scope: "support operations" },
|
|
162
|
+
output: { reviewed: true },
|
|
163
|
+
},
|
|
164
|
+
failed: {
|
|
165
|
+
type: "dynamic-tool",
|
|
166
|
+
toolName: "publishReport",
|
|
167
|
+
toolCallId: "tool-demo-failed",
|
|
168
|
+
state: "output-error",
|
|
169
|
+
input: { destination: "leadership workspace" },
|
|
170
|
+
errorText: "The current role cannot publish to this workspace.",
|
|
171
|
+
},
|
|
172
|
+
} satisfies Record<string, ToolCallPart>;
|
|
173
|
+
|
|
174
|
+
const specializedCards = [
|
|
175
|
+
[
|
|
176
|
+
"Suggestions",
|
|
177
|
+
"Let the user choose how the AI should continue.",
|
|
178
|
+
tools.suggestions,
|
|
179
|
+
],
|
|
180
|
+
[
|
|
181
|
+
"Business report",
|
|
182
|
+
"Open a generated report for preview and export.",
|
|
183
|
+
tools.report,
|
|
184
|
+
],
|
|
185
|
+
[
|
|
186
|
+
"Chart",
|
|
187
|
+
"Render generated visualization options as an inline preview.",
|
|
188
|
+
tools.chart,
|
|
189
|
+
],
|
|
190
|
+
[
|
|
191
|
+
"Sub-agent",
|
|
192
|
+
"Show delegated AI employee work without expanding the whole payload.",
|
|
193
|
+
tools.subAgent,
|
|
194
|
+
],
|
|
195
|
+
[
|
|
196
|
+
"Workflow output",
|
|
197
|
+
"Present structured workflow results in a business-friendly card.",
|
|
198
|
+
tools.workflow,
|
|
199
|
+
],
|
|
200
|
+
] as const;
|
|
201
|
+
|
|
202
|
+
export function ToolCardsPage() {
|
|
203
|
+
const [decision, setDecision] = useState("Waiting for a decision");
|
|
204
|
+
const [workflowDecision, setWorkflowDecision] = useState(
|
|
205
|
+
"Waiting for workflow review"
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
return (
|
|
209
|
+
<div className="space-y-10 pb-12">
|
|
210
|
+
<section className="flex flex-wrap items-start justify-between gap-5 border-b pb-8">
|
|
211
|
+
<div>
|
|
212
|
+
<div className="flex items-center gap-2">
|
|
213
|
+
<Badge variant="secondary">AI Components</Badge>
|
|
214
|
+
<Badge variant="outline">Tool renderers</Badge>
|
|
215
|
+
</div>
|
|
216
|
+
<h1 className="mt-4 text-3xl font-semibold tracking-[-0.035em]">
|
|
217
|
+
Tool Cards
|
|
218
|
+
</h1>
|
|
219
|
+
<p className="mt-3 max-w-3xl text-sm leading-6 text-muted-foreground">
|
|
220
|
+
Specialized tools render their complete business interaction.
|
|
221
|
+
Tools without a registered renderer fall back to the shared status,
|
|
222
|
+
approval, error, and input disclosure card.
|
|
223
|
+
</p>
|
|
224
|
+
</div>
|
|
225
|
+
</section>
|
|
226
|
+
|
|
227
|
+
<section className="space-y-5">
|
|
228
|
+
<SectionTitle
|
|
229
|
+
eyebrow="Specialized renderers"
|
|
230
|
+
title="Adapt NocoBase tool results to the job they represent"
|
|
231
|
+
description="These cards follow the original AI employee patterns while using the starter's shadcn and Base UI component system."
|
|
232
|
+
/>
|
|
233
|
+
<div className="grid gap-4 lg:grid-cols-2">
|
|
234
|
+
{specializedCards.map(([title, description, part]) => (
|
|
235
|
+
<Card key={title} className="gap-0 py-0">
|
|
236
|
+
<CardHeader className="border-b py-4">
|
|
237
|
+
<CardTitle className="text-base">{title}</CardTitle>
|
|
238
|
+
<p className="text-xs leading-5 text-muted-foreground">
|
|
239
|
+
{description}
|
|
240
|
+
</p>
|
|
241
|
+
</CardHeader>
|
|
242
|
+
<CardContent className="p-4">
|
|
243
|
+
<ToolCallCard
|
|
244
|
+
part={part}
|
|
245
|
+
approval={
|
|
246
|
+
part.toolName === "aiEmployeeWorkflowTaskOutput"
|
|
247
|
+
? { required: true, status: "pending" }
|
|
248
|
+
: undefined
|
|
249
|
+
}
|
|
250
|
+
onDecision={async (nextDecision) => {
|
|
251
|
+
if (part.toolName !== "aiEmployeeWorkflowTaskOutput")
|
|
252
|
+
return;
|
|
253
|
+
setWorkflowDecision(
|
|
254
|
+
nextDecision === "approve"
|
|
255
|
+
? "Workflow output approved"
|
|
256
|
+
: nextDecision === "reject"
|
|
257
|
+
? "Workflow output rejected"
|
|
258
|
+
: "Revision requested"
|
|
259
|
+
);
|
|
260
|
+
}}
|
|
261
|
+
/>
|
|
262
|
+
{part.toolName === "aiEmployeeWorkflowTaskOutput" ? (
|
|
263
|
+
<p className="mt-2 text-xs text-muted-foreground">
|
|
264
|
+
{workflowDecision}
|
|
265
|
+
</p>
|
|
266
|
+
) : null}
|
|
267
|
+
</CardContent>
|
|
268
|
+
</Card>
|
|
269
|
+
))}
|
|
270
|
+
</div>
|
|
271
|
+
</section>
|
|
272
|
+
|
|
273
|
+
<section className="space-y-5">
|
|
274
|
+
<SectionTitle
|
|
275
|
+
eyebrow="Default Tool Card"
|
|
276
|
+
title="One shared shell for every normal tool state"
|
|
277
|
+
description="Tools without a specialized renderer still use the same compact status, input disclosure, error, and permission behavior."
|
|
278
|
+
/>
|
|
279
|
+
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
|
|
280
|
+
{[
|
|
281
|
+
["Preparing input", tools.preparing],
|
|
282
|
+
["Running", tools.running],
|
|
283
|
+
["Completed", tools.completed],
|
|
284
|
+
["Failed", tools.failed],
|
|
285
|
+
].map(([label, part]) => (
|
|
286
|
+
<Card key={label as string} className="gap-0 py-0">
|
|
287
|
+
<CardHeader className="border-b py-3">
|
|
288
|
+
<CardTitle className="text-sm">{label as string}</CardTitle>
|
|
289
|
+
</CardHeader>
|
|
290
|
+
<CardContent className="p-4">
|
|
291
|
+
<ToolCallCard part={part as ToolCallPart} />
|
|
292
|
+
</CardContent>
|
|
293
|
+
</Card>
|
|
294
|
+
))}
|
|
295
|
+
<Card className="gap-0 py-0 md:col-span-2 xl:col-span-2">
|
|
296
|
+
<CardHeader className="border-b py-3">
|
|
297
|
+
<CardTitle className="text-sm">Approval required</CardTitle>
|
|
298
|
+
</CardHeader>
|
|
299
|
+
<CardContent className="p-4">
|
|
300
|
+
<ToolCallCard
|
|
301
|
+
part={tools.approval}
|
|
302
|
+
approval={{ required: true, status: "pending" }}
|
|
303
|
+
onDecision={async (nextDecision) => {
|
|
304
|
+
setDecision(
|
|
305
|
+
nextDecision === "approve"
|
|
306
|
+
? "The tool was allowed."
|
|
307
|
+
: "The tool was denied."
|
|
308
|
+
);
|
|
309
|
+
}}
|
|
310
|
+
/>
|
|
311
|
+
<p className="mt-2 text-xs text-muted-foreground">{decision}</p>
|
|
312
|
+
</CardContent>
|
|
313
|
+
</Card>
|
|
314
|
+
</div>
|
|
315
|
+
</section>
|
|
316
|
+
|
|
317
|
+
<section className="space-y-5">
|
|
318
|
+
<SectionTitle
|
|
319
|
+
eyebrow="Code prompt"
|
|
320
|
+
title="Generate an implementation prompt for a specialized Tool Card"
|
|
321
|
+
description="Choose the nearest existing renderer and describe the business interaction. The generated prompt tells the coding agent exactly where and how to implement it."
|
|
322
|
+
/>
|
|
323
|
+
<ToolCardCodePrompt />
|
|
324
|
+
</section>
|
|
325
|
+
</div>
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
type ReferenceRenderer =
|
|
330
|
+
| "suggestions"
|
|
331
|
+
| "business-report"
|
|
332
|
+
| "chart"
|
|
333
|
+
| "sub-agent"
|
|
334
|
+
| "workflow";
|
|
335
|
+
|
|
336
|
+
const referenceRenderers: Record<
|
|
337
|
+
ReferenceRenderer,
|
|
338
|
+
{ label: string; guidance: string }
|
|
339
|
+
> = {
|
|
340
|
+
suggestions: {
|
|
341
|
+
label: "Suggestions",
|
|
342
|
+
guidance:
|
|
343
|
+
"Follow SuggestionsRenderer for selectable options that call onEdit with the user's choice.",
|
|
344
|
+
},
|
|
345
|
+
"business-report": {
|
|
346
|
+
label: "Business report",
|
|
347
|
+
guidance:
|
|
348
|
+
"Follow BusinessReportRenderer for an inline summary that opens a larger preview or export dialog.",
|
|
349
|
+
},
|
|
350
|
+
chart: {
|
|
351
|
+
label: "Chart",
|
|
352
|
+
guidance:
|
|
353
|
+
"Follow ChartRenderer for a compact visual preview derived from structured tool arguments.",
|
|
354
|
+
},
|
|
355
|
+
"sub-agent": {
|
|
356
|
+
label: "Sub-agent",
|
|
357
|
+
guidance:
|
|
358
|
+
"Follow SubAgentRenderer for a compact entity card with expandable task details.",
|
|
359
|
+
},
|
|
360
|
+
workflow: {
|
|
361
|
+
label: "Workflow output",
|
|
362
|
+
guidance:
|
|
363
|
+
"Follow WorkflowRenderer for structured results and card-owned Reject, Revise, and Approve actions.",
|
|
364
|
+
},
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
function ToolCardCodePrompt() {
|
|
368
|
+
const [toolName, setToolName] = useState("reviewSupportQueue");
|
|
369
|
+
const [reference, setReference] = useState<ReferenceRenderer>("workflow");
|
|
370
|
+
const [behavior, setBehavior] = useState(
|
|
371
|
+
"Show the affected support records, summarize the proposed changes, and let the user approve, request a revision, or reject the operation."
|
|
372
|
+
);
|
|
373
|
+
const [handlesApproval, setHandlesApproval] = useState(true);
|
|
374
|
+
const [copied, setCopied] = useState(false);
|
|
375
|
+
const referenceDefinition = referenceRenderers[reference];
|
|
376
|
+
const prompt = `Implement a specialized NocoBase AI Tool Card for the tool "${
|
|
377
|
+
toolName || "customTool"
|
|
378
|
+
}" in the shadcn Registry source at registry/nocobase-ai.
|
|
379
|
+
|
|
380
|
+
Business interaction:
|
|
381
|
+
${behavior || "Render the tool's business-specific interaction."}
|
|
382
|
+
|
|
383
|
+
Reference implementation:
|
|
384
|
+
- ${referenceDefinition.guidance}
|
|
385
|
+
- Reuse the shared visual language from the existing specialized Tool Cards demo.
|
|
386
|
+
|
|
387
|
+
Implementation requirements:
|
|
388
|
+
- Create a focused renderer component under registry/nocobase-ai/components/tools. It renders the complete business-specific card.
|
|
389
|
+
- Accept AIToolRendererProps: part, disabled, onEdit, onApprove, onReject, and onRevise.
|
|
390
|
+
- Register the renderer for the exact tool name "${
|
|
391
|
+
toolName || "customTool"
|
|
392
|
+
}" in the built-in renderer map, or provide it through AIToolRendererProvider when it belongs to an external Registry package.
|
|
393
|
+
- Set handlesApproval to ${handlesApproval}. ${
|
|
394
|
+
handlesApproval
|
|
395
|
+
? "The specialized body must expose the complete decision UI and call onApprove, onReject, onEdit, or onRevise itself."
|
|
396
|
+
: "Leave approval, status, errors, and disclosure to the shared ToolCallCard shell."
|
|
397
|
+
}
|
|
398
|
+
- Set standalone to true when the renderer replaces the Default Tool Card shell.
|
|
399
|
+
- Do not add tool-name branches to ChatMessage or AIChatMessageList.
|
|
400
|
+
- Do not render a separate raw Output section.
|
|
401
|
+
- Preserve the actual message order: reasoning, assistant text, then the Tool Card in the position used by the NocoBase message renderer.
|
|
402
|
+
- Use shadcn/Base UI components only; do not add Ant Design, Radix, or Zustand.
|
|
403
|
+
- Add a fixed ToolCallPart example to registry/nocobase-ai/demo/tool-cards.tsx so every state and action can be reviewed without calling the backend.
|
|
404
|
+
- Export any public renderer types or components from registry/nocobase-ai/components/index.ts when application code needs them.
|
|
405
|
+
|
|
406
|
+
Verification:
|
|
407
|
+
- Run pnpm registry:preview.
|
|
408
|
+
- Run pnpm exec tsc --noEmit.
|
|
409
|
+
- Run the scoped ESLint check and pnpm registry:build.
|
|
410
|
+
- Verify the card in light and dark themes, narrow chat panels, expanded dialogs, pending approval, completed, and error states.`;
|
|
411
|
+
|
|
412
|
+
return (
|
|
413
|
+
<div className="grid items-start gap-5 xl:grid-cols-[380px_minmax(0,1fr)]">
|
|
414
|
+
<Card className="gap-0 py-0">
|
|
415
|
+
<CardHeader className="border-b py-4">
|
|
416
|
+
<CardTitle className="text-base">Describe the Tool Card</CardTitle>
|
|
417
|
+
<p className="text-xs leading-5 text-muted-foreground">
|
|
418
|
+
The examples above remain the visual reference; these values define
|
|
419
|
+
the implementation task.
|
|
420
|
+
</p>
|
|
421
|
+
</CardHeader>
|
|
422
|
+
<CardContent className="space-y-5 p-4">
|
|
423
|
+
<div className="space-y-2">
|
|
424
|
+
<label className="text-xs font-medium" htmlFor="tool-name">
|
|
425
|
+
Tool name
|
|
426
|
+
</label>
|
|
427
|
+
<Input
|
|
428
|
+
id="tool-name"
|
|
429
|
+
value={toolName}
|
|
430
|
+
onChange={(event) => setToolName(event.target.value)}
|
|
431
|
+
/>
|
|
432
|
+
</div>
|
|
433
|
+
<div className="space-y-2">
|
|
434
|
+
<div className="text-xs font-medium">Closest example</div>
|
|
435
|
+
<Select
|
|
436
|
+
value={reference}
|
|
437
|
+
onValueChange={(value) => {
|
|
438
|
+
if (!value) return;
|
|
439
|
+
const nextReference = value as ReferenceRenderer;
|
|
440
|
+
setReference(nextReference);
|
|
441
|
+
setHandlesApproval(nextReference === "workflow");
|
|
442
|
+
}}
|
|
443
|
+
>
|
|
444
|
+
<SelectTrigger className="w-full">
|
|
445
|
+
<SelectValue>{referenceDefinition.label}</SelectValue>
|
|
446
|
+
</SelectTrigger>
|
|
447
|
+
<SelectContent>
|
|
448
|
+
{Object.entries(referenceRenderers).map(([value, option]) => (
|
|
449
|
+
<SelectItem key={value} value={value}>
|
|
450
|
+
{option.label}
|
|
451
|
+
</SelectItem>
|
|
452
|
+
))}
|
|
453
|
+
</SelectContent>
|
|
454
|
+
</Select>
|
|
455
|
+
</div>
|
|
456
|
+
<div className="space-y-2">
|
|
457
|
+
<label className="text-xs font-medium" htmlFor="tool-behavior">
|
|
458
|
+
Business interaction
|
|
459
|
+
</label>
|
|
460
|
+
<Textarea
|
|
461
|
+
id="tool-behavior"
|
|
462
|
+
className="min-h-28"
|
|
463
|
+
value={behavior}
|
|
464
|
+
onChange={(event) => setBehavior(event.target.value)}
|
|
465
|
+
/>
|
|
466
|
+
</div>
|
|
467
|
+
<label className="flex items-center justify-between gap-4 border-t pt-4 text-sm">
|
|
468
|
+
<span>
|
|
469
|
+
<span className="block font-medium">Card owns approval UI</span>
|
|
470
|
+
<span className="block text-xs text-muted-foreground">
|
|
471
|
+
Enable for actions such as Approve, Revise, and Reject.
|
|
472
|
+
</span>
|
|
473
|
+
</span>
|
|
474
|
+
<Switch
|
|
475
|
+
size="sm"
|
|
476
|
+
checked={handlesApproval}
|
|
477
|
+
onCheckedChange={setHandlesApproval}
|
|
478
|
+
/>
|
|
479
|
+
</label>
|
|
480
|
+
</CardContent>
|
|
481
|
+
</Card>
|
|
482
|
+
<Card className="gap-0 py-0">
|
|
483
|
+
<div className="flex items-center justify-between border-b px-4 py-3">
|
|
484
|
+
<div>
|
|
485
|
+
<div className="text-sm font-medium">Coding prompt</div>
|
|
486
|
+
<div className="text-xs text-muted-foreground">
|
|
487
|
+
Ready to paste into an implementation task.
|
|
488
|
+
</div>
|
|
489
|
+
</div>
|
|
490
|
+
<Button
|
|
491
|
+
size="sm"
|
|
492
|
+
onClick={async () => {
|
|
493
|
+
await navigator.clipboard.writeText(prompt);
|
|
494
|
+
setCopied(true);
|
|
495
|
+
window.setTimeout(() => setCopied(false), 1500);
|
|
496
|
+
}}
|
|
497
|
+
>
|
|
498
|
+
{copied ? <Check /> : <Copy />}
|
|
499
|
+
{copied ? "Copied" : "Copy prompt"}
|
|
500
|
+
</Button>
|
|
501
|
+
</div>
|
|
502
|
+
<pre className="max-h-[720px] min-h-[560px] overflow-auto whitespace-pre-wrap bg-muted/25 p-5 font-mono text-xs leading-5 text-muted-foreground">
|
|
503
|
+
{prompt}
|
|
504
|
+
</pre>
|
|
505
|
+
</Card>
|
|
506
|
+
</div>
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
function SectionTitle({
|
|
511
|
+
eyebrow,
|
|
512
|
+
title,
|
|
513
|
+
description,
|
|
514
|
+
}: {
|
|
515
|
+
eyebrow: string;
|
|
516
|
+
title: string;
|
|
517
|
+
description: string;
|
|
518
|
+
}) {
|
|
519
|
+
return (
|
|
520
|
+
<div>
|
|
521
|
+
<p className="text-xs font-semibold uppercase tracking-[0.16em] text-muted-foreground">
|
|
522
|
+
{eyebrow}
|
|
523
|
+
</p>
|
|
524
|
+
<h2 className="mt-2 text-xl font-semibold tracking-tight">{title}</h2>
|
|
525
|
+
<p className="mt-1.5 max-w-3xl text-sm leading-6 text-muted-foreground">
|
|
526
|
+
{description}
|
|
527
|
+
</p>
|
|
528
|
+
</div>
|
|
529
|
+
);
|
|
530
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { NocoBaseAIExtensionProvider } from "./global-ai-chat";
|
|
2
|
+
import type { AppExtension } from "@/app/extension";
|
|
3
|
+
import { LoadingState } from "@/components/app-shell/loading-state";
|
|
4
|
+
import {
|
|
5
|
+
Bot,
|
|
6
|
+
MessageSquare,
|
|
7
|
+
MousePointer2,
|
|
8
|
+
PanelRight,
|
|
9
|
+
Sparkles,
|
|
10
|
+
Wrench,
|
|
11
|
+
} from "lucide-react";
|
|
12
|
+
import { lazy, Suspense, type ReactNode } from "react";
|
|
13
|
+
import { Outlet, Route } from "react-router";
|
|
14
|
+
|
|
15
|
+
const AIChatPage = lazy(() =>
|
|
16
|
+
import("./demo").then((module) => ({ default: module.AIChatPage }))
|
|
17
|
+
);
|
|
18
|
+
const FloatingChatPage = lazy(() =>
|
|
19
|
+
import("./demo/floating").then((module) => ({
|
|
20
|
+
default: module.FloatingChatPage,
|
|
21
|
+
}))
|
|
22
|
+
);
|
|
23
|
+
const ShortcutPage = lazy(() =>
|
|
24
|
+
import("./demo/shortcut").then((module) => ({
|
|
25
|
+
default: module.ShortcutPage,
|
|
26
|
+
}))
|
|
27
|
+
);
|
|
28
|
+
const PageContextPage = lazy(() =>
|
|
29
|
+
import("./demo/page-context").then((module) => ({
|
|
30
|
+
default: module.PageContextPage,
|
|
31
|
+
}))
|
|
32
|
+
);
|
|
33
|
+
const ToolCardsPage = lazy(() =>
|
|
34
|
+
import("./demo/tool-cards").then((module) => ({
|
|
35
|
+
default: module.ToolCardsPage,
|
|
36
|
+
}))
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
function LazyDemoRoute({ children }: { children: ReactNode }) {
|
|
40
|
+
return (
|
|
41
|
+
<Suspense fallback={<LoadingState className="min-h-[320px]" />}>
|
|
42
|
+
{children}
|
|
43
|
+
</Suspense>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const nocobaseAIExtension: AppExtension = {
|
|
48
|
+
id: "nocobase-ai",
|
|
49
|
+
Provider: NocoBaseAIExtensionProvider,
|
|
50
|
+
resources: [
|
|
51
|
+
{
|
|
52
|
+
name: "ai-components",
|
|
53
|
+
meta: {
|
|
54
|
+
label: "AI Components",
|
|
55
|
+
icon: <Bot />,
|
|
56
|
+
acl: { type: "authenticated" },
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: "ai-chat-window",
|
|
61
|
+
list: "/ai-chat",
|
|
62
|
+
meta: {
|
|
63
|
+
parent: "ai-components",
|
|
64
|
+
label: "Chat window",
|
|
65
|
+
icon: <MessageSquare />,
|
|
66
|
+
description:
|
|
67
|
+
"Build freely with AI while NocoBase keeps the application reliable.",
|
|
68
|
+
acl: { type: "authenticated" },
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: "ai-floating-chat",
|
|
73
|
+
list: "/ai-chat/floating",
|
|
74
|
+
meta: {
|
|
75
|
+
parent: "ai-components",
|
|
76
|
+
label: "Floating chat",
|
|
77
|
+
icon: <PanelRight />,
|
|
78
|
+
acl: { type: "authenticated" },
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: "ai-employee-tasks",
|
|
83
|
+
list: "/ai-chat/shortcut",
|
|
84
|
+
meta: {
|
|
85
|
+
parent: "ai-components",
|
|
86
|
+
label: "Employee tasks",
|
|
87
|
+
icon: <Sparkles />,
|
|
88
|
+
acl: { type: "authenticated" },
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: "ai-page-context",
|
|
93
|
+
list: "/ai-chat/context",
|
|
94
|
+
meta: {
|
|
95
|
+
parent: "ai-components",
|
|
96
|
+
label: "Page context",
|
|
97
|
+
icon: <MousePointer2 />,
|
|
98
|
+
acl: { type: "authenticated" },
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: "ai-tool-cards",
|
|
103
|
+
list: "/ai-chat/tools",
|
|
104
|
+
meta: {
|
|
105
|
+
parent: "ai-components",
|
|
106
|
+
label: "Tool cards",
|
|
107
|
+
icon: <Wrench />,
|
|
108
|
+
acl: { type: "authenticated" },
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
routes: (
|
|
113
|
+
<Route key="nocobase-ai" path="/ai-chat" element={<Outlet />}>
|
|
114
|
+
<Route
|
|
115
|
+
index
|
|
116
|
+
element={
|
|
117
|
+
<LazyDemoRoute>
|
|
118
|
+
<AIChatPage />
|
|
119
|
+
</LazyDemoRoute>
|
|
120
|
+
}
|
|
121
|
+
/>
|
|
122
|
+
<Route
|
|
123
|
+
path="floating"
|
|
124
|
+
element={
|
|
125
|
+
<LazyDemoRoute>
|
|
126
|
+
<FloatingChatPage />
|
|
127
|
+
</LazyDemoRoute>
|
|
128
|
+
}
|
|
129
|
+
/>
|
|
130
|
+
<Route
|
|
131
|
+
path="shortcut"
|
|
132
|
+
element={
|
|
133
|
+
<LazyDemoRoute>
|
|
134
|
+
<ShortcutPage />
|
|
135
|
+
</LazyDemoRoute>
|
|
136
|
+
}
|
|
137
|
+
/>
|
|
138
|
+
<Route
|
|
139
|
+
path="context"
|
|
140
|
+
element={
|
|
141
|
+
<LazyDemoRoute>
|
|
142
|
+
<PageContextPage />
|
|
143
|
+
</LazyDemoRoute>
|
|
144
|
+
}
|
|
145
|
+
/>
|
|
146
|
+
<Route
|
|
147
|
+
path="tools"
|
|
148
|
+
element={
|
|
149
|
+
<LazyDemoRoute>
|
|
150
|
+
<ToolCardsPage />
|
|
151
|
+
</LazyDemoRoute>
|
|
152
|
+
}
|
|
153
|
+
/>
|
|
154
|
+
</Route>
|
|
155
|
+
),
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
export default nocobaseAIExtension;
|