@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,197 +0,0 @@
1
- import {
2
- AIChatWindow,
3
- ChatInline,
4
- useAIPageElement,
5
- useAIPageElementPicker,
6
- type AIChatComposerAction,
7
- } from "../components";
8
- import { Badge } from "@/components/ui/badge";
9
- import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
10
- import { Input } from "@/components/ui/input";
11
- import { Label } from "@/components/ui/label";
12
- import {
13
- Select,
14
- SelectContent,
15
- SelectItem,
16
- SelectTrigger,
17
- SelectValue,
18
- } from "@/components/ui/select";
19
- import { AIChatProvider, useAIChatBase } from "../providers";
20
- import { Globe2, MousePointer2 } from "lucide-react";
21
- import { useMemo, useState } from "react";
22
-
23
- export function PageElementShowcase() {
24
- return (
25
- <AIChatProvider id="page-element-demo">
26
- <PageElementShowcaseContent />
27
- </AIChatProvider>
28
- );
29
- }
30
-
31
- function PageElementShowcaseContent() {
32
- const [customerName, setCustomerName] = useState("Northwind Studio");
33
- const [contactEmail, setContactEmail] = useState("ops@northwind.test");
34
- const [priority, setPriority] = useState("high");
35
- const [webSearch, setWebSearch] = useState(false);
36
- const { id: chatId, addWorkContext, focusComposer } = useAIChatBase();
37
- const { registeredCount, startPicking } = useAIPageElementPicker();
38
-
39
- const composerActions = useMemo<AIChatComposerAction[]>(
40
- () => [
41
- {
42
- key: "pick-page-element",
43
- label: "Pick page element",
44
- icon: <MousePointer2 />,
45
- disabled: registeredCount === 0,
46
- onClick: () =>
47
- startPicking({
48
- chatId,
49
- onSelect: (item) => {
50
- addWorkContext(item);
51
- focusComposer();
52
- },
53
- }),
54
- },
55
- {
56
- key: "web-search",
57
- label: "Web search",
58
- icon: <Globe2 />,
59
- active: webSearch,
60
- onClick: () => setWebSearch((active) => !active),
61
- },
62
- ],
63
- [
64
- addWorkContext,
65
- chatId,
66
- focusComposer,
67
- registeredCount,
68
- startPicking,
69
- webSearch,
70
- ]
71
- );
72
-
73
- const formRef = useAIPageElement({
74
- id: "customer-intake-form",
75
- title: "Customer intake form",
76
- kind: "form",
77
- getContext: () => ({
78
- form: "customer-intake",
79
- values: { customerName, contactEmail, priority },
80
- }),
81
- });
82
- const detailRef = useAIPageElement({
83
- id: "customer-health-summary",
84
- title: "Customer health summary",
85
- kind: "record-detail",
86
- getContext: () => ({
87
- resource: "customers",
88
- record: {
89
- name: customerName,
90
- plan: "Enterprise",
91
- healthScore: 86,
92
- openRequests: 3,
93
- renewalDate: "2026-09-30",
94
- },
95
- }),
96
- });
97
-
98
- return (
99
- <Card className="gap-0 overflow-hidden py-0">
100
- <div className="grid min-h-[560px] lg:grid-cols-[minmax(0,1fr)_390px]">
101
- <div className="space-y-4 bg-muted/15 p-4 sm:p-5">
102
- <div className="flex flex-wrap items-center justify-between gap-3">
103
- <div>
104
- <div className="text-sm font-medium">Customer workspace</div>
105
- <div className="text-xs text-muted-foreground">
106
- The form and detail card are registered page elements.
107
- </div>
108
- </div>
109
- <Badge variant="outline">2 selectable elements</Badge>
110
- </div>
111
-
112
- <Card ref={formRef} className="transition-shadow">
113
- <CardHeader>
114
- <CardTitle>Customer intake form</CardTitle>
115
- <p className="text-xs leading-5 text-muted-foreground">
116
- Update a value, then pick this form to capture its current
117
- state.
118
- </p>
119
- </CardHeader>
120
- <CardContent className="grid gap-4 sm:grid-cols-2">
121
- <div className="space-y-2 sm:col-span-2">
122
- <Label htmlFor="page-context-customer-name">
123
- Customer name
124
- </Label>
125
- <Input
126
- id="page-context-customer-name"
127
- value={customerName}
128
- onChange={(event) => setCustomerName(event.target.value)}
129
- />
130
- </div>
131
- <div className="space-y-2">
132
- <Label htmlFor="page-context-contact-email">
133
- Contact email
134
- </Label>
135
- <Input
136
- id="page-context-contact-email"
137
- value={contactEmail}
138
- onChange={(event) => setContactEmail(event.target.value)}
139
- />
140
- </div>
141
- <div className="space-y-2">
142
- <Label>Priority</Label>
143
- <Select
144
- value={priority}
145
- onValueChange={(value) => value && setPriority(value)}
146
- >
147
- <SelectTrigger className="w-full">
148
- <SelectValue />
149
- </SelectTrigger>
150
- <SelectContent>
151
- <SelectItem value="low">Low</SelectItem>
152
- <SelectItem value="normal">Normal</SelectItem>
153
- <SelectItem value="high">High</SelectItem>
154
- </SelectContent>
155
- </Select>
156
- </div>
157
- </CardContent>
158
- </Card>
159
-
160
- <Card ref={detailRef} className="transition-shadow">
161
- <CardHeader>
162
- <CardTitle>Customer health summary</CardTitle>
163
- </CardHeader>
164
- <CardContent className="grid grid-cols-2 gap-3 sm:grid-cols-4">
165
- {[
166
- ["Plan", "Enterprise"],
167
- ["Health score", "86 / 100"],
168
- ["Open requests", "3"],
169
- ["Renewal", "Sep 30, 2026"],
170
- ].map(([label, value]) => (
171
- <div
172
- key={label}
173
- className="rounded-lg border bg-background p-3"
174
- >
175
- <div className="text-xs text-muted-foreground">{label}</div>
176
- <div className="mt-1 text-sm font-medium">{value}</div>
177
- </div>
178
- ))}
179
- </CardContent>
180
- </Card>
181
- </div>
182
-
183
- <div className="min-h-0 border-t bg-card lg:border-l lg:border-t-0">
184
- <ChatInline className="h-[560px] rounded-none border-0">
185
- <AIChatWindow
186
- composerActions={composerActions}
187
- showConversationToggle={false}
188
- enableAttachments
189
- attachmentActionIndex={1}
190
- disclaimer={false}
191
- />
192
- </ChatInline>
193
- </div>
194
- </div>
195
- </Card>
196
- );
197
- }
@@ -1,41 +0,0 @@
1
- import { Button } from "@/components/ui/button";
2
- import { Card } from "@/components/ui/card";
3
- import { Check, Copy } from "lucide-react";
4
- import { useState } from "react";
5
-
6
- export function PromptCard({
7
- title,
8
- description,
9
- prompt,
10
- }: {
11
- title: string;
12
- description: string;
13
- prompt: string;
14
- }) {
15
- const [copied, setCopied] = useState(false);
16
-
17
- return (
18
- <Card className="gap-0 overflow-hidden py-0">
19
- <div className="flex items-center justify-between gap-4 border-b px-4 py-3">
20
- <div>
21
- <div className="text-sm font-medium">{title}</div>
22
- <div className="text-xs text-muted-foreground">{description}</div>
23
- </div>
24
- <Button
25
- size="sm"
26
- onClick={async () => {
27
- await navigator.clipboard.writeText(prompt);
28
- setCopied(true);
29
- window.setTimeout(() => setCopied(false), 1500);
30
- }}
31
- >
32
- {copied ? <Check /> : <Copy />}
33
- {copied ? "Copied" : "Copy prompt"}
34
- </Button>
35
- </div>
36
- <pre className="max-h-[620px] overflow-auto whitespace-pre-wrap bg-muted/25 p-5 font-mono text-xs leading-5 text-muted-foreground">
37
- {prompt}
38
- </pre>
39
- </Card>
40
- );
41
- }
@@ -1,452 +0,0 @@
1
- import {
2
- AIChatCompact,
3
- AIChatWindow,
4
- ChatInline,
5
- ChatPage,
6
- useAIPageElementPicker,
7
- type AIChatComposerAction,
8
- } from "../components";
9
- import { Button } from "@/components/ui/button";
10
- import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
11
- import { Input } from "@/components/ui/input";
12
- import {
13
- Select,
14
- SelectContent,
15
- SelectItem,
16
- SelectTrigger,
17
- SelectValue,
18
- } from "@/components/ui/select";
19
- import { Switch } from "@/components/ui/switch";
20
- import { AIChatProvider, useAIChatBase } from "../providers";
21
- import { Check, Copy, Globe2, MousePointer2 } from "lucide-react";
22
- import { useMemo, useState } from "react";
23
- import type { ChatContainer } from "./container-showcase";
24
-
25
- type PromptCapabilities = {
26
- conversationList: boolean;
27
- employeeSelector: boolean;
28
- modelSelector: boolean;
29
- userPrompt: boolean;
30
- pageElement: boolean;
31
- upload: boolean;
32
- webSearch: boolean;
33
- panelExpansion: boolean;
34
- };
35
-
36
- type MessagePresentation = "transcript" | "compact-history-dialog";
37
-
38
- const placements: Array<{
39
- value: ChatContainer;
40
- label: string;
41
- description: string;
42
- }> = [
43
- {
44
- value: "embedded",
45
- label: "Inside the page",
46
- description: "Embed chat in the selected content region.",
47
- },
48
- {
49
- value: "page",
50
- label: "Dedicated page",
51
- description: "Create a full route for the AI conversation.",
52
- },
53
- {
54
- value: "side-panel",
55
- label: "Right side panel",
56
- description: "Push the page narrower while chat is open.",
57
- },
58
- {
59
- value: "dialog",
60
- label: "Dialog",
61
- description: "Open chat from a button or page action.",
62
- },
63
- {
64
- value: "mobile",
65
- label: "Mobile region",
66
- description: "Optimize the embedded container for a narrow viewport.",
67
- },
68
- ];
69
-
70
- const DEFAULT_CAPABILITIES: PromptCapabilities = {
71
- conversationList: true,
72
- employeeSelector: true,
73
- modelSelector: true,
74
- userPrompt: true,
75
- pageElement: true,
76
- upload: true,
77
- webSearch: true,
78
- panelExpansion: true,
79
- };
80
-
81
- export function PromptGenerator() {
82
- return (
83
- <AIChatProvider id="prompt-generator-preview">
84
- <PromptGeneratorContent />
85
- </AIChatProvider>
86
- );
87
- }
88
-
89
- function PromptGeneratorContent() {
90
- const { id: chatId, addWorkContext, focusComposer } = useAIChatBase();
91
- const { registeredCount, startPicking } = useAIPageElementPicker();
92
- const [target, setTarget] = useState("the current page's main content area");
93
- const [placement, setPlacement] = useState<ChatContainer>("embedded");
94
- const [panelWidth, setPanelWidth] = useState("450");
95
- const [messagePresentation, setMessagePresentation] =
96
- useState<MessagePresentation>("transcript");
97
- const [capabilities, setCapabilities] = useState(DEFAULT_CAPABILITIES);
98
- const [copied, setCopied] = useState(false);
99
-
100
- const previewActions = useMemo<AIChatComposerAction[]>(() => {
101
- const actions: AIChatComposerAction[] = [];
102
- if (capabilities.pageElement) {
103
- actions.push({
104
- key: "pick-page-element",
105
- label: "Pick page element",
106
- icon: <MousePointer2 />,
107
- disabled: registeredCount === 0,
108
- onClick: () =>
109
- startPicking({
110
- chatId,
111
- onSelect: (item) => {
112
- addWorkContext(item);
113
- focusComposer();
114
- },
115
- }),
116
- });
117
- }
118
- if (capabilities.webSearch) {
119
- actions.push({
120
- key: "web-search",
121
- label: "Web search",
122
- icon: <Globe2 />,
123
- });
124
- }
125
- return actions;
126
- }, [
127
- addWorkContext,
128
- capabilities.pageElement,
129
- capabilities.webSearch,
130
- chatId,
131
- focusComposer,
132
- registeredCount,
133
- startPicking,
134
- ]);
135
-
136
- const updateCapability = (key: keyof PromptCapabilities, value: boolean) => {
137
- setCapabilities((current) => ({ ...current, [key]: value }));
138
- };
139
-
140
- const prompt = useMemo(() => {
141
- const placementInstruction = {
142
- embedded:
143
- "Render ChatInline inside the target content region and keep its width fluid within the parent container.",
144
- page: "Create a dedicated route and render ChatPage with AIChatWindow filling the available page content.",
145
- "side-panel": `Use ChatSidePanelLayout on the right with a ${panelWidth}px panel. Opening chat must push the page narrower instead of covering it on desktop; mobile may use an overlay.`,
146
- dialog:
147
- "Open ChatDialog from a page action. Keep conversation state in AIProvider/AIChatProvider so closing the dialog does not discard it.",
148
- mobile:
149
- "Render ChatInline in the target region with a mobile-first width around 390px and preserve the same responsive AIChatWindow API.",
150
- }[placement];
151
-
152
- return `Add the standard NocoBase AI conversation to ${
153
- target || "the target page"
154
- }.
155
-
156
- Placement:
157
- - ${placementInstruction}
158
- - Keep AIChatWindow position-independent. The surface wrapper owns placement and dimensions.
159
-
160
- Message presentation:
161
- - ${
162
- messagePresentation === "transcript"
163
- ? "Render the full AIChatMessageList inline as part of AIChatWindow."
164
- : "Use AIChatCompact for the working surface and open AIChatHistoryDialog when the user requests the full transcript."
165
- }
166
-
167
- Conversation capabilities:
168
- - Conversation list: ${capabilities.conversationList}
169
- - AI employee selector: ${capabilities.employeeSelector}
170
- - Model selector: ${capabilities.modelSelector}
171
- - Personalized prompt editor: ${capabilities.userPrompt}
172
- - Pick page element: ${capabilities.pageElement}
173
- - Upload action: ${capabilities.upload}
174
- - Web search action: ${capabilities.webSearch}
175
- - Side panel can expand to a 95% dialog and collapse back: ${
176
- capabilities.panelExpansion
177
- }
178
- - Tool approval follows NocoBase permissions. Always show Allow / Deny when the backend marks a tool as ASK.
179
-
180
- Implementation requirements:
181
- - Use AIProvider at application level and AIChatProvider with a stable id for this chat instance.
182
- - Wrap the application content with AIPageElementProvider when page-element picking is enabled.
183
- - Register selectable React components with useAIPageElement and return serializable business context from getContext.
184
- - Reuse AIChatWindow and the existing providers; do not duplicate message, streaming, reasoning, or tool-call state.
185
- - When the right panel is expandable, keep one AIChatProvider mounted while switching between ChatSidePanel and ChatDialog. Put ChatSurfaceActions in the window header.
186
- - Keep the generic Tool Call shell responsible for status, ASK approval, errors, and disclosure. Register business-specific bodies through AIToolRendererProvider.
187
- - Include the built-in specialized Tool Card renderers. This is part of the standard AI chat capability, not an optional page setting.
188
- - Register browser-side tool implementations through AIProvider.toolInvokers. After all interrupted calls are approved, the provider executes these handlers and sends their results when resuming the NocoBase conversation.
189
- - Pass application actions through composerActions.
190
- - AIChatProvider handles NocoBase ASK decisions and resumes tool execution. Use onToolCallDecision only for application-specific side effects or telemetry.
191
- - Use shadcn/Base UI components only; do not introduce Ant Design, Radix, or Zustand.
192
- - Match the existing NocoBase ChatBox behavior and black/white/gray visual system.`;
193
- }, [capabilities, messagePresentation, panelWidth, placement, target]);
194
-
195
- const previewChat =
196
- messagePresentation === "transcript" ? (
197
- <AIChatWindow
198
- composerActions={previewActions}
199
- showConversationToggle={capabilities.conversationList}
200
- showEmployeeSelector={capabilities.employeeSelector}
201
- showModelSelector={capabilities.modelSelector}
202
- showUserPrompt={capabilities.userPrompt}
203
- enableAttachments={capabilities.upload}
204
- attachmentActionIndex={capabilities.pageElement ? 1 : 0}
205
- disclaimer={false}
206
- onToolCallDecision={async () => undefined}
207
- />
208
- ) : (
209
- <AIChatCompact
210
- className="h-full rounded-none border-0"
211
- composerActions={previewActions}
212
- showEmployeeSelector={capabilities.employeeSelector}
213
- showModelSelector={capabilities.modelSelector}
214
- showUserPrompt={capabilities.userPrompt}
215
- enableAttachments={capabilities.upload}
216
- disclaimer={false}
217
- onToolCallDecision={async () => undefined}
218
- />
219
- );
220
-
221
- const previewSurface = (() => {
222
- if (placement === "page") {
223
- return (
224
- <ChatPage className="h-[430px] min-h-0 w-full max-w-none">
225
- {previewChat}
226
- </ChatPage>
227
- );
228
- }
229
- if (placement === "side-panel") {
230
- return (
231
- <div className="flex h-[430px] w-full overflow-hidden rounded-xl border bg-card shadow-sm">
232
- <div className="hidden min-w-0 flex-1 space-y-4 bg-muted/20 p-5 sm:block">
233
- <div className="h-7 w-40 rounded-md bg-muted" />
234
- <div className="grid grid-cols-2 gap-3">
235
- <div className="h-24 rounded-lg border bg-background" />
236
- <div className="h-24 rounded-lg border bg-background" />
237
- </div>
238
- <div className="h-48 rounded-lg border bg-background" />
239
- </div>
240
- <aside
241
- className="h-full max-w-full shrink-0 border-l bg-card"
242
- style={{ width: `${panelWidth}px` }}
243
- >
244
- {previewChat}
245
- </aside>
246
- </div>
247
- );
248
- }
249
- if (placement === "dialog") {
250
- return (
251
- <div className="relative h-[430px] w-full overflow-hidden rounded-xl border bg-muted/30 p-5">
252
- <div className="space-y-3 opacity-45">
253
- <div className="h-7 w-44 rounded bg-muted-foreground/20" />
254
- <div className="h-24 rounded-lg bg-background" />
255
- <div className="h-48 rounded-lg bg-background" />
256
- </div>
257
- <div className="absolute inset-4 overflow-hidden rounded-xl border bg-card shadow-2xl sm:inset-8">
258
- {previewChat}
259
- </div>
260
- </div>
261
- );
262
- }
263
- if (placement === "mobile") {
264
- return (
265
- <div className="h-[430px] w-full max-w-[390px] overflow-hidden rounded-[2rem] border-4 border-foreground/15 bg-card shadow-lg">
266
- {previewChat}
267
- </div>
268
- );
269
- }
270
- return (
271
- <ChatInline className="h-[430px] w-full max-w-2xl">
272
- {previewChat}
273
- </ChatInline>
274
- );
275
- })();
276
-
277
- return (
278
- <div className="grid items-start gap-5 xl:grid-cols-[380px_minmax(0,1fr)]">
279
- <Card className="gap-0 py-0">
280
- <CardHeader className="border-b py-4">
281
- <CardTitle className="text-base">Describe the integration</CardTitle>
282
- <p className="text-xs leading-5 text-muted-foreground">
283
- Choose where chat belongs and which capabilities the target page
284
- needs.
285
- </p>
286
- </CardHeader>
287
- <CardContent className="space-y-5 p-4">
288
- <div className="space-y-2">
289
- <label className="text-xs font-medium" htmlFor="ai-chat-target">
290
- Target page or region
291
- </label>
292
- <Input
293
- id="ai-chat-target"
294
- value={target}
295
- onChange={(event) => setTarget(event.target.value)}
296
- placeholder="e.g. the ticket detail page"
297
- />
298
- </div>
299
-
300
- <div className="space-y-2">
301
- <div className="text-xs font-medium">Placement</div>
302
- <div className="grid gap-2 sm:grid-cols-2 xl:grid-cols-1">
303
- {placements.map((option) => (
304
- <button
305
- key={option.value}
306
- type="button"
307
- data-active={placement === option.value}
308
- className="rounded-lg border px-3 py-2.5 text-left transition-colors hover:bg-muted/40 data-active:border-foreground/35 data-active:bg-muted/60"
309
- onClick={() => setPlacement(option.value)}
310
- >
311
- <span className="block text-sm font-medium">
312
- {option.label}
313
- </span>
314
- <span className="mt-0.5 block text-xs leading-5 text-muted-foreground">
315
- {option.description}
316
- </span>
317
- </button>
318
- ))}
319
- </div>
320
- </div>
321
-
322
- {placement === "side-panel" ? (
323
- <div className="space-y-2">
324
- <div className="text-xs font-medium">Side panel width</div>
325
- <Select
326
- value={panelWidth}
327
- onValueChange={(value) => value && setPanelWidth(value)}
328
- >
329
- <SelectTrigger className="w-full">
330
- <SelectValue>{panelWidth}px</SelectValue>
331
- </SelectTrigger>
332
- <SelectContent>
333
- <SelectItem value="400">400px</SelectItem>
334
- <SelectItem value="450">450px · NocoBase default</SelectItem>
335
- <SelectItem value="520">520px</SelectItem>
336
- </SelectContent>
337
- </Select>
338
- </div>
339
- ) : null}
340
-
341
- <div className="space-y-2">
342
- <div className="text-xs font-medium">Message presentation</div>
343
- <Select
344
- value={messagePresentation}
345
- onValueChange={(value) =>
346
- value && setMessagePresentation(value as MessagePresentation)
347
- }
348
- >
349
- <SelectTrigger className="w-full">
350
- <SelectValue>
351
- {messagePresentation === "transcript"
352
- ? "Full transcript"
353
- : "Compact + history dialog"}
354
- </SelectValue>
355
- </SelectTrigger>
356
- <SelectContent>
357
- <SelectItem value="transcript">Full transcript</SelectItem>
358
- <SelectItem value="compact-history-dialog">
359
- Compact + history dialog
360
- </SelectItem>
361
- </SelectContent>
362
- </Select>
363
- </div>
364
-
365
- <div className="space-y-1 border-t pt-4">
366
- <div className="mb-2 text-xs font-medium">Capabilities</div>
367
- {(Object.keys(capabilities) as Array<keyof PromptCapabilities>).map(
368
- (key) => (
369
- <label
370
- key={key}
371
- className="flex items-center justify-between gap-4 py-1.5 text-sm"
372
- >
373
- <span className="min-w-0">
374
- <span className="block">{capabilityLabel(key)}</span>
375
- </span>
376
- <Switch
377
- size="sm"
378
- checked={capabilities[key]}
379
- onCheckedChange={(checked) =>
380
- updateCapability(key, checked)
381
- }
382
- />
383
- </label>
384
- )
385
- )}
386
- </div>
387
- </CardContent>
388
- </Card>
389
-
390
- <div className="space-y-5">
391
- <Card className="gap-0 overflow-hidden py-0">
392
- <div className="flex items-center justify-between border-b px-4 py-3">
393
- <div>
394
- <div className="text-sm font-medium">Capabilities preview</div>
395
- <div className="text-xs text-muted-foreground">
396
- The selected controls are rendered on the real AIChatWindow.
397
- </div>
398
- </div>
399
- <code className="rounded-md bg-muted px-2 py-1 text-xs text-muted-foreground">
400
- {placementLabel(placement)}
401
- </code>
402
- </div>
403
- <div className="flex justify-center bg-muted/20 p-3 sm:p-4">
404
- {previewSurface}
405
- </div>
406
- </Card>
407
-
408
- <Card className="gap-0 overflow-hidden py-0">
409
- <div className="flex items-center justify-between border-b px-4 py-3">
410
- <div>
411
- <div className="text-sm font-medium">
412
- Generated implementation prompt
413
- </div>
414
- <div className="text-xs text-muted-foreground">
415
- Updates from the selected page, placement, and capabilities.
416
- </div>
417
- </div>
418
- <Button
419
- size="sm"
420
- onClick={async () => {
421
- await navigator.clipboard.writeText(prompt);
422
- setCopied(true);
423
- window.setTimeout(() => setCopied(false), 1500);
424
- }}
425
- >
426
- {copied ? <Check /> : <Copy />}
427
- {copied ? "Copied" : "Copy prompt"}
428
- </Button>
429
- </div>
430
- <pre className="max-h-[760px] min-h-[520px] overflow-auto whitespace-pre-wrap bg-muted/25 p-5 font-mono text-xs leading-5 text-muted-foreground">
431
- {prompt}
432
- </pre>
433
- </Card>
434
- </div>
435
- </div>
436
- );
437
- }
438
-
439
- const capabilityLabel = (key: keyof PromptCapabilities) =>
440
- ({
441
- conversationList: "Conversation list",
442
- employeeSelector: "AI employee selector",
443
- modelSelector: "Model selector",
444
- userPrompt: "Personalized prompt editor",
445
- pageElement: "Pick page element",
446
- upload: "Upload files",
447
- webSearch: "Web search",
448
- panelExpansion: "Panel expand / collapse",
449
- }[key]);
450
-
451
- const placementLabel = (placement: ChatContainer) =>
452
- placements.find((option) => option.value === placement)?.label ?? placement;