@nocobase/portal-template-default 1.0.2 → 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.
Files changed (110) hide show
  1. package/package.json +1 -1
  2. package/scripts/registry.mjs +2 -0
  3. package/src/extensions/nocobase-acl/README.md +19 -0
  4. package/src/extensions/nocobase-acl/components/acl-boundary.tsx +106 -0
  5. package/src/extensions/nocobase-acl/components/index.ts +3 -0
  6. package/src/extensions/nocobase-acl/components/role-options.ts +36 -0
  7. package/src/extensions/nocobase-acl/components/role-switcher.tsx +135 -0
  8. package/src/extensions/nocobase-acl/demo/boundary-api.tsx +186 -0
  9. package/src/extensions/nocobase-acl/demo/components.tsx +231 -0
  10. package/src/extensions/nocobase-acl/demo/index.tsx +505 -0
  11. package/src/extensions/nocobase-acl/demo/lazy-route.tsx +10 -0
  12. package/src/extensions/nocobase-acl/demo/prompt-generator.tsx +134 -0
  13. package/src/extensions/nocobase-acl/demo/role-switcher-prompt-generator.tsx +198 -0
  14. package/src/extensions/nocobase-acl/demo/scenario-section.tsx +37 -0
  15. package/src/extensions/nocobase-acl/extension.tsx +71 -0
  16. package/src/extensions/nocobase-acl/index.ts +1 -0
  17. package/src/extensions/nocobase-ai/README.md +70 -0
  18. package/src/extensions/nocobase-ai/adapters/react-hook-form.ts +20 -0
  19. package/src/extensions/nocobase-ai/assets/nocobase-ai-chat.svg +50 -0
  20. package/src/extensions/nocobase-ai/components/ai-root-provider.tsx +32 -0
  21. package/src/extensions/nocobase-ai/components/chat/ai-employee-avatar.tsx +31 -0
  22. package/src/extensions/nocobase-ai/components/chat/chat-attachment.tsx +92 -0
  23. package/src/extensions/nocobase-ai/components/chat/chat-compact.tsx +90 -0
  24. package/src/extensions/nocobase-ai/components/chat/chat-composer.tsx +429 -0
  25. package/src/extensions/nocobase-ai/components/chat/chat-empty-state.tsx +51 -0
  26. package/src/extensions/nocobase-ai/components/chat/chat-header.tsx +88 -0
  27. package/src/extensions/nocobase-ai/components/chat/chat-history-dialog.tsx +77 -0
  28. package/src/extensions/nocobase-ai/components/chat/chat-message.tsx +252 -0
  29. package/src/extensions/nocobase-ai/components/chat/chat-messages.tsx +173 -0
  30. package/src/extensions/nocobase-ai/components/chat/chat-window.tsx +118 -0
  31. package/src/extensions/nocobase-ai/components/chat/conversation-list.tsx +343 -0
  32. package/src/extensions/nocobase-ai/components/chat/markdown-message.tsx +165 -0
  33. package/src/extensions/nocobase-ai/components/chat/model-select-options.tsx +30 -0
  34. package/src/extensions/nocobase-ai/components/chat/reasoning-panel.tsx +29 -0
  35. package/src/extensions/nocobase-ai/components/chat/sub-agent-conversation.tsx +142 -0
  36. package/src/extensions/nocobase-ai/components/chat/tool-call-card.tsx +316 -0
  37. package/src/extensions/nocobase-ai/components/chat/user-prompt-editor.tsx +99 -0
  38. package/src/extensions/nocobase-ai/components/chat/work-context-chip.tsx +114 -0
  39. package/src/extensions/nocobase-ai/components/index.ts +59 -0
  40. package/src/extensions/nocobase-ai/components/page-elements/ai-form.tsx +44 -0
  41. package/src/extensions/nocobase-ai/components/page-elements/page-element-provider.tsx +469 -0
  42. package/src/extensions/nocobase-ai/components/surfaces/chat-dialog.tsx +31 -0
  43. package/src/extensions/nocobase-ai/components/surfaces/chat-inline.tsx +18 -0
  44. package/src/extensions/nocobase-ai/components/surfaces/chat-page.tsx +18 -0
  45. package/src/extensions/nocobase-ai/components/surfaces/chat-side-panel-layout.tsx +54 -0
  46. package/src/extensions/nocobase-ai/components/surfaces/chat-side-panel.tsx +74 -0
  47. package/src/extensions/nocobase-ai/components/surfaces/chat-surface-actions.tsx +54 -0
  48. package/src/extensions/nocobase-ai/components/tools/builtin-tool-renderers.tsx +31 -0
  49. package/src/extensions/nocobase-ai/components/tools/business-report-dialog.tsx +364 -0
  50. package/src/extensions/nocobase-ai/components/tools/business-report-renderer.tsx +122 -0
  51. package/src/extensions/nocobase-ai/components/tools/business-report-utils.tsx +282 -0
  52. package/src/extensions/nocobase-ai/components/tools/chart-renderer.tsx +99 -0
  53. package/src/extensions/nocobase-ai/components/tools/echarts-preview.tsx +59 -0
  54. package/src/extensions/nocobase-ai/components/tools/echarts-runtime-advanced.ts +18 -0
  55. package/src/extensions/nocobase-ai/components/tools/echarts-runtime-common.ts +20 -0
  56. package/src/extensions/nocobase-ai/components/tools/echarts-runtime-components.ts +34 -0
  57. package/src/extensions/nocobase-ai/components/tools/echarts-runtime-hierarchy.ts +24 -0
  58. package/src/extensions/nocobase-ai/components/tools/echarts-runtime.ts +138 -0
  59. package/src/extensions/nocobase-ai/components/tools/sub-agent-renderer.tsx +67 -0
  60. package/src/extensions/nocobase-ai/components/tools/suggestions-renderer.tsx +59 -0
  61. package/src/extensions/nocobase-ai/components/tools/tool-renderer-provider.tsx +55 -0
  62. package/src/extensions/nocobase-ai/components/tools/tool-renderer-utils.ts +18 -0
  63. package/src/extensions/nocobase-ai/components/tools/workflow-renderer.tsx +108 -0
  64. package/src/extensions/nocobase-ai/components/triggers/ai-chat-floating-trigger.tsx +73 -0
  65. package/src/extensions/nocobase-ai/components/triggers/ai-employee-shortcut.tsx +149 -0
  66. package/src/extensions/nocobase-ai/demo/configuration-gate.tsx +49 -0
  67. package/src/extensions/nocobase-ai/demo/container-showcase.tsx +210 -0
  68. package/src/extensions/nocobase-ai/demo/floating.tsx +134 -0
  69. package/src/extensions/nocobase-ai/demo/index.tsx +346 -0
  70. package/src/extensions/nocobase-ai/demo/interaction-showcase.tsx +151 -0
  71. package/src/extensions/nocobase-ai/demo/page-context-prompt-generator.tsx +355 -0
  72. package/src/extensions/nocobase-ai/demo/page-context.tsx +884 -0
  73. package/src/extensions/nocobase-ai/demo/page-element-showcase.tsx +197 -0
  74. package/src/extensions/nocobase-ai/demo/prompt-card.tsx +41 -0
  75. package/src/extensions/nocobase-ai/demo/prompt-generator.tsx +452 -0
  76. package/src/extensions/nocobase-ai/demo/shortcut.tsx +1207 -0
  77. package/src/extensions/nocobase-ai/demo/tool-cards.tsx +530 -0
  78. package/src/extensions/nocobase-ai/extension.tsx +158 -0
  79. package/src/extensions/nocobase-ai/global-ai-chat.tsx +213 -0
  80. package/src/extensions/nocobase-ai/index.ts +3 -0
  81. package/src/extensions/nocobase-ai/providers/ai-provider.tsx +323 -0
  82. package/src/extensions/nocobase-ai/providers/avatars.ts +817 -0
  83. package/src/extensions/nocobase-ai/providers/chat-context.tsx +109 -0
  84. package/src/extensions/nocobase-ai/providers/chat-controller.ts +84 -0
  85. package/src/extensions/nocobase-ai/providers/chat-message-utils.ts +198 -0
  86. package/src/extensions/nocobase-ai/providers/chat-provider.tsx +871 -0
  87. package/src/extensions/nocobase-ai/providers/chat-reducer.ts +147 -0
  88. package/src/extensions/nocobase-ai/providers/chat-task-utils.ts +105 -0
  89. package/src/extensions/nocobase-ai/providers/chat-transport.ts +184 -0
  90. package/src/extensions/nocobase-ai/providers/form-registry.tsx +271 -0
  91. package/src/extensions/nocobase-ai/providers/frontend-tool-registry.tsx +231 -0
  92. package/src/extensions/nocobase-ai/providers/index.ts +61 -0
  93. package/src/extensions/nocobase-ai/providers/model.ts +32 -0
  94. package/src/extensions/nocobase-ai/providers/page-context.tsx +136 -0
  95. package/src/extensions/nocobase-ai/providers/stream-coalescer.ts +60 -0
  96. package/src/extensions/nocobase-ai/providers/stream-event-utils.ts +82 -0
  97. package/src/extensions/nocobase-ai/providers/stream-parser.ts +61 -0
  98. package/src/extensions/nocobase-ai/providers/sub-agent-stream.ts +316 -0
  99. package/src/extensions/nocobase-ai/providers/types.ts +183 -0
  100. package/src/extensions/nocobase-ai/providers/ui-message-stream.ts +463 -0
  101. package/src/extensions/nocobase-ai/providers/use-automatic-tool-approval.ts +94 -0
  102. package/src/extensions/nocobase-ai/providers/use-chat-attachments.ts +206 -0
  103. package/src/extensions/nocobase-ai/providers/use-chat-message-actions.ts +554 -0
  104. package/src/extensions/nocobase-ai/providers/use-chat-runtime.ts +210 -0
  105. package/src/extensions/nocobase-ai/providers/use-chat-work-context.ts +93 -0
  106. package/src/extensions/nocobase-ai/providers/use-conversation-catalog.ts +108 -0
  107. package/src/extensions/nocobase-ai/providers/use-conversation-history.ts +178 -0
  108. package/src/extensions/nocobase-ai/services/index.ts +7 -0
  109. package/src/extensions/nocobase-ai/services/nocobase-ai-service.ts +454 -0
  110. package/src/extensions/nocobase-ai/services/types.ts +82 -0
@@ -0,0 +1,151 @@
1
+ import {
2
+ AIChatCompact,
3
+ AIChatMessageList,
4
+ ChatInline,
5
+ type AIChatComposerAction,
6
+ } from "../components";
7
+ import { AIChatProvider, type AIChatMessage } from "../providers";
8
+ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
9
+ import { Badge } from "@/components/ui/badge";
10
+ import { Button } from "@/components/ui/button";
11
+ import { ArrowUp, Globe2, Menu, Paperclip, PlusCircle } from "lucide-react";
12
+
13
+ const showcaseMessages = [
14
+ {
15
+ id: "message-presentation-user",
16
+ role: "user",
17
+ parts: [
18
+ {
19
+ type: "text",
20
+ text: "Review the support workspace and propose the most useful dashboard.",
21
+ },
22
+ ],
23
+ },
24
+ {
25
+ id: "message-presentation-assistant",
26
+ role: "assistant",
27
+ parts: [
28
+ {
29
+ type: "reasoning",
30
+ text: "I should inspect the existing collections first, then keep the recommendation focused on operational decisions.",
31
+ state: "done",
32
+ },
33
+ {
34
+ type: "dynamic-tool",
35
+ toolName: "inspectDataModel",
36
+ toolCallId: "message-presentation-tool",
37
+ state: "output-available",
38
+ input: { collections: ["tickets", "categories", "users"] },
39
+ output: { collectionsFound: 3 },
40
+ },
41
+ {
42
+ type: "text",
43
+ text: "Start with open workload, ownership gaps, SLA risk, and the created-versus-resolved trend. That gives the team one clear operational view without duplicating the underlying NocoBase rules.",
44
+ },
45
+ ],
46
+ },
47
+ ] satisfies AIChatMessage[];
48
+
49
+ const compactActions: AIChatComposerAction[] = [
50
+ {
51
+ key: "web-search",
52
+ label: "Web search",
53
+ icon: <Globe2 />,
54
+ },
55
+ ];
56
+
57
+ export function InteractionShowcase() {
58
+ return (
59
+ <div className="grid items-start gap-4 xl:grid-cols-2">
60
+ <Card className="gap-0 py-0">
61
+ <CardHeader className="border-b py-4">
62
+ <div className="flex items-center gap-2">
63
+ <Badge variant="outline">Inline messages</Badge>
64
+ <CardTitle className="text-base">
65
+ Standard conversation transcript
66
+ </CardTitle>
67
+ </div>
68
+ <p className="text-xs leading-5 text-muted-foreground">
69
+ This fixed example shows where reasoning, a normal tool call, and
70
+ the final assistant response appear in one message sequence.
71
+ </p>
72
+ </CardHeader>
73
+ <CardContent className="bg-muted/15 p-4">
74
+ <ChatInline className="h-[520px] bg-background shadow-sm">
75
+ <div className="flex h-full min-h-0 flex-col">
76
+ <div className="relative flex h-12 shrink-0 items-center justify-between border-b px-2.5">
77
+ <Button
78
+ variant="ghost"
79
+ size="icon-sm"
80
+ aria-label="Conversations"
81
+ >
82
+ <Menu />
83
+ </Button>
84
+ <div className="pointer-events-none absolute left-1/2 max-w-[55%] -translate-x-1/2 truncate text-sm font-medium">
85
+ Support workspace review
86
+ </div>
87
+ <Button
88
+ variant="ghost"
89
+ size="icon-sm"
90
+ aria-label="New conversation"
91
+ >
92
+ <PlusCircle />
93
+ </Button>
94
+ </div>
95
+ <AIChatMessageList
96
+ className="h-full"
97
+ messages={showcaseMessages}
98
+ showMessageActions={false}
99
+ />
100
+ <div className="shrink-0 bg-card px-4 pt-2 pb-3">
101
+ <div className="rounded-xl border bg-background px-3 py-2.5 shadow-sm">
102
+ <div className="min-h-10 text-sm text-muted-foreground">
103
+ Message your AI employee…
104
+ </div>
105
+ <div className="flex items-center justify-between">
106
+ <Button
107
+ variant="ghost"
108
+ size="icon-sm"
109
+ aria-label="Attach file"
110
+ >
111
+ <Paperclip />
112
+ </Button>
113
+ <Button size="icon-sm" aria-label="Send message">
114
+ <ArrowUp />
115
+ </Button>
116
+ </div>
117
+ </div>
118
+ </div>
119
+ </div>
120
+ </ChatInline>
121
+ </CardContent>
122
+ </Card>
123
+
124
+ <Card className="gap-0 py-0">
125
+ <CardHeader className="border-b py-4">
126
+ <div className="flex items-center gap-2">
127
+ <Badge variant="outline">Compact composer</Badge>
128
+ <CardTitle className="text-base">
129
+ Open the transcript only when needed
130
+ </CardTitle>
131
+ </div>
132
+ <p className="text-xs leading-5 text-muted-foreground">
133
+ The compact variant keeps only the chat header and composer. Its
134
+ history button opens a dialog with conversation switching and the
135
+ selected transcript.
136
+ </p>
137
+ </CardHeader>
138
+ <CardContent className="bg-muted/15 p-4">
139
+ <AIChatProvider id="message-presentation-compact-demo">
140
+ <AIChatCompact
141
+ className="bg-background"
142
+ composerActions={compactActions}
143
+ showModelSelector={false}
144
+ disclaimer={false}
145
+ />
146
+ </AIChatProvider>
147
+ </CardContent>
148
+ </Card>
149
+ </div>
150
+ );
151
+ }
@@ -0,0 +1,355 @@
1
+ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
2
+ import { Input } from "@/components/ui/input";
3
+ import { Label } from "@/components/ui/label";
4
+ import {
5
+ Select,
6
+ SelectContent,
7
+ SelectItem,
8
+ SelectTrigger,
9
+ SelectValue,
10
+ } from "@/components/ui/select";
11
+ import { Switch } from "@/components/ui/switch";
12
+ import { Textarea } from "@/components/ui/textarea";
13
+ import { useMemo, useState } from "react";
14
+ import { useAI } from "../providers";
15
+ import { PromptCard } from "./prompt-card";
16
+
17
+ type ContextPattern = "shortcut" | "preset" | "scope" | "manual";
18
+ type PageCapability = "context" | "form-filler" | "custom-tool";
19
+
20
+ const isBusinessEmployee = (username: string) =>
21
+ !["nathan", "dara"].includes(username.toLowerCase());
22
+
23
+ export function PageContextPromptGenerator() {
24
+ const { employees } = useAI();
25
+ const availableEmployees = employees.filter((employee) =>
26
+ isBusinessEmployee(employee.username)
27
+ );
28
+ const [employeeUsername, setEmployeeUsername] = useState(
29
+ availableEmployees[0]?.username ?? employees[0]?.username ?? ""
30
+ );
31
+ const [sceneTitle, setSceneTitle] = useState("Customer renewal workspace");
32
+ const [sceneBrief, setSceneBrief] = useState(
33
+ "Show a customer renewal detail with account health, contract value, renewal date, owner, recent activity, and a working AI conversation beside it."
34
+ );
35
+ const [taskTitle, setTaskTitle] = useState("Review renewal risk");
36
+ const [taskMessage, setTaskMessage] = useState(
37
+ "Review this customer renewal, identify risks, and recommend the next action."
38
+ );
39
+ const [autoSend, setAutoSend] = useState(false);
40
+ const [pattern, setPattern] = useState<ContextPattern>("shortcut");
41
+ const [contextId, setContextId] = useState("renewal-detail");
42
+ const [contextTitle, setContextTitle] = useState("Customer renewal detail");
43
+ const [capability, setCapability] = useState<PageCapability>("context");
44
+ const [toolName, setToolName] = useState("update_renewal_plan");
45
+ const [toolAction, setToolAction] = useState(
46
+ "Update the renewal risk level and the visible follow-up note on the page."
47
+ );
48
+ const [toolPermission, setToolPermission] = useState<"ASK" | "ALLOW">("ASK");
49
+
50
+ const prompt = useMemo(() => {
51
+ const employee =
52
+ employees.find((item) => item.username === employeeUsername)?.nickname ??
53
+ employeeUsername;
54
+ const contextReference = `{ type: "page-element", id: "${contextId}", title: "${contextTitle}" }`;
55
+ const taskAutoSend = pattern === "manual" ? false : autoSend;
56
+ const patternInstructions = {
57
+ shortcut: `Place an AIEmployeeShortcut in the page header, visually separated from the registered business element. Target the embedded chat controller and configure this task on its tasks prop:
58
+ - Title: ${taskTitle}
59
+ - User message: ${taskMessage}
60
+ - Auto send: ${taskAutoSend}
61
+ - Set task.message.workContext to [${contextReference}].
62
+
63
+ The Shortcut must remain outside the registered element so the scene clearly demonstrates an explicit task-to-context reference.`,
64
+ preset: `Do not render an AIEmployeeShortcut. Configure AIChatProvider.employeeTasks for ${employee} (${employeeUsername}) and show this task in the embedded conversation empty state:
65
+ - Title: ${taskTitle}
66
+ - User message: ${taskMessage}
67
+ - Auto send: ${taskAutoSend}
68
+ - Set task.message.workContext to [${contextReference}].
69
+
70
+ The task should disappear after a conversation starts and must resolve the latest page values when triggered.`,
71
+ scope: `Place an AIEmployeeShortcut in the scene header and wrap both the business work area and chat surface with:
72
+
73
+ <AIPageContextScope context={${contextReference}}>
74
+ {/* Complete business scene and conversation */}
75
+ </AIPageContextScope>
76
+
77
+ Configure the Shortcut task without message.workContext so it inherits the surrounding context. Keep the registered element and task visibly within the same scoped scene.`,
78
+ manual: `Do not pre-attach workContext to a Shortcut or preset task. Add a "Pick page element" composer action using useAIPageElementPicker. When the user selects an element, call addWorkContext(item) and focusComposer().
79
+
80
+ Show "${taskMessage}" as a suggested prompt near the chat, but keep it in the composer for review. The user must pick context manually before sending, so this scene must not auto-send.`,
81
+ }[pattern];
82
+
83
+ const capabilityInstructions = {
84
+ context: `Build the primary work area as a polished, read-only business detail view based on the scene brief. Include at least five realistic fields, a status indicator, and a short recent-activity section. Register the complete detail region with useAIPageElementHandle and return its current serializable values from getContext. Reuse handle.ref on the visible element and handle.context wherever the scene needs a context reference.`,
85
+ "form-filler": `Build the primary work area with two connected parts: realistic source material and an editable business form derived from the scene brief. Use React Hook Form and register the form with useAIForm using id "${contextId}", meaningful field definitions, getValues, and setValues.
86
+
87
+ Use the fixed built-in formFiller Tool. Do not register Form filler as a custom frontend Tool and do not add formFiller manually to task.skillSettings: the Registry activates it automatically when a registered form context is sent. The Tool may fill declared editable values only and must never submit or save the form. Include a visible reset action and import applyReactHookFormValues from the optional adapters/react-hook-form Registry item for setValues.`,
88
+ "custom-tool": `Register a custom frontend Tool on the useAIPageElement descriptor:
89
+ - Name: ${toolName}
90
+ - Business action: ${toolAction}
91
+ - Permission: ${toolPermission}
92
+ - Provide a concise description, JSON inputSchema, and execute(args).
93
+
94
+ Build visible state in the business work area that clearly changes when this Tool succeeds, including before/after values or a status/note update. Keep it separate from the fixed formFiller Tool. The execute function must update only the current React page state and return a serializable result. NocoBase runtime handles ASK / ALLOW execution behavior.
95
+ `,
96
+ }[capability];
97
+
98
+ return `Build a complete, self-contained NocoBase AI page-context scene.
99
+
100
+ Scene
101
+ - Title: ${sceneTitle}
102
+ - Business brief: ${sceneBrief}
103
+ - Create a dedicated demo page or scene component rather than adding an isolated integration snippet to an unspecified page.
104
+ - Use realistic fixed sample data so the scene works immediately without additional backend setup.
105
+ - Add a clear page header, a primary business work area, and an embedded AI conversation in a responsive two-column layout. Stack the conversation below the work area on smaller screens.
106
+ - Keep one AIChatProvider mounted for the entire scene and target the same chat controller from any Shortcut.
107
+
108
+ AI employee and task
109
+ - Employee: ${employee} (${employeeUsername})
110
+ - Task: ${taskTitle}
111
+ - Message: ${taskMessage}
112
+ - Auto send: ${taskAutoSend}
113
+
114
+ Business context
115
+ - Context id: ${contextId}
116
+ - Context title: ${contextTitle}
117
+ - Register a visible, meaningful business element with this stable id.
118
+ - Resolve its content at task or message send time so the AI always receives the latest displayed values.
119
+ - Prefer useAIPageElementHandle so the registered ref and context reference cannot drift apart.
120
+
121
+ AI interaction pattern
122
+ ${patternInstructions}
123
+
124
+ Business surface and capability
125
+ ${capabilityInstructions}
126
+
127
+ Implementation requirements
128
+ - Use AIProvider and AIPageElementProvider from the NocoBase AI Registry.
129
+ - Use AIChatProvider, ChatInline, AIChatWindow, and useAIChatController to create the working conversation beside the business surface.
130
+ - The finished result must demonstrate the entire user journey: inspect or edit the business surface, trigger or compose the AI request, see the selected context, and observe the response or page update.
131
+ - Keep page context serializable. Never pass React instances, DOM nodes, form instances, or callback functions to the backend.
132
+ - Public React APIs use context id, not Flow Model uid.
133
+ - Page-context resolution errors must block sending and remain visible to the user; do not silently send an unresolved reference.
134
+ - Explicit task message.workContext takes precedence over trigger context and AIPageContextScope.
135
+ - A task without explicit context inherits the surrounding scope.
136
+ - Manual Pick affects only the current composed message.
137
+ - Preserve NocoBase streaming, conversation history, Tool approval, and resume behavior.
138
+ - Use shadcn/Base UI components and the existing Registry components.
139
+ - Deliver the complete component code, including sample state, context registration, task configuration, chat layout, and all visible business UI.`;
140
+ }, [
141
+ autoSend,
142
+ capability,
143
+ contextId,
144
+ contextTitle,
145
+ employeeUsername,
146
+ employees,
147
+ pattern,
148
+ sceneBrief,
149
+ sceneTitle,
150
+ taskMessage,
151
+ taskTitle,
152
+ toolAction,
153
+ toolName,
154
+ toolPermission,
155
+ ]);
156
+
157
+ return (
158
+ <div className="grid items-start gap-5 xl:grid-cols-[390px_minmax(0,1fr)]">
159
+ <Card className="gap-0 py-0">
160
+ <CardHeader className="border-b py-4">
161
+ <CardTitle>Scene settings</CardTitle>
162
+ </CardHeader>
163
+ <CardContent className="space-y-5 py-5">
164
+ <div className="space-y-2">
165
+ <Label htmlFor="context-generator-scene-title">Scene title</Label>
166
+ <Input
167
+ id="context-generator-scene-title"
168
+ value={sceneTitle}
169
+ onChange={(event) => setSceneTitle(event.target.value)}
170
+ />
171
+ </div>
172
+ <div className="space-y-2">
173
+ <Label htmlFor="context-generator-scene-brief">
174
+ Business scene
175
+ </Label>
176
+ <Textarea
177
+ id="context-generator-scene-brief"
178
+ value={sceneBrief}
179
+ onChange={(event) => setSceneBrief(event.target.value)}
180
+ className="min-h-28"
181
+ />
182
+ </div>
183
+
184
+ <div className="space-y-2">
185
+ <Label>AI employee</Label>
186
+ <Select
187
+ value={employeeUsername}
188
+ onValueChange={(value) => value && setEmployeeUsername(value)}
189
+ >
190
+ <SelectTrigger className="w-full">
191
+ <SelectValue />
192
+ </SelectTrigger>
193
+ <SelectContent>
194
+ {(availableEmployees.length
195
+ ? availableEmployees
196
+ : employees
197
+ ).map((employee) => (
198
+ <SelectItem key={employee.username} value={employee.username}>
199
+ {employee.nickname}
200
+ </SelectItem>
201
+ ))}
202
+ </SelectContent>
203
+ </Select>
204
+ </div>
205
+
206
+ <div className="space-y-2">
207
+ <Label htmlFor="context-generator-task-title">Task title</Label>
208
+ <Input
209
+ id="context-generator-task-title"
210
+ value={taskTitle}
211
+ onChange={(event) => setTaskTitle(event.target.value)}
212
+ />
213
+ </div>
214
+ <div className="space-y-2">
215
+ <Label htmlFor="context-generator-task-message">Task message</Label>
216
+ <Textarea
217
+ id="context-generator-task-message"
218
+ value={taskMessage}
219
+ onChange={(event) => setTaskMessage(event.target.value)}
220
+ className="min-h-24"
221
+ />
222
+ </div>
223
+ <label className="flex items-center justify-between gap-4 rounded-lg border px-3 py-2.5">
224
+ <span>
225
+ <span className="block text-sm font-medium">Auto send</span>
226
+ <span className="block text-xs text-muted-foreground">
227
+ Otherwise fill the composer for review.
228
+ </span>
229
+ </span>
230
+ <Switch
231
+ checked={pattern === "manual" ? false : autoSend}
232
+ disabled={pattern === "manual"}
233
+ onCheckedChange={setAutoSend}
234
+ />
235
+ </label>
236
+ {pattern === "manual" ? (
237
+ <p className="-mt-3 text-xs leading-5 text-muted-foreground">
238
+ Manual Pick keeps the message in the composer so context can be
239
+ selected before sending.
240
+ </p>
241
+ ) : null}
242
+
243
+ <div className="space-y-2">
244
+ <Label>Context integration</Label>
245
+ <Select
246
+ value={pattern}
247
+ onValueChange={(value) =>
248
+ value && setPattern(value as ContextPattern)
249
+ }
250
+ >
251
+ <SelectTrigger className="w-full">
252
+ <SelectValue />
253
+ </SelectTrigger>
254
+ <SelectContent>
255
+ <SelectItem value="shortcut">Shortcut task</SelectItem>
256
+ <SelectItem value="preset">Conversation preset task</SelectItem>
257
+ <SelectItem value="scope">Scope inheritance</SelectItem>
258
+ <SelectItem value="manual">Manual Pick</SelectItem>
259
+ </SelectContent>
260
+ </Select>
261
+ </div>
262
+
263
+ <div className="grid gap-4 sm:grid-cols-2 xl:grid-cols-1">
264
+ <div className="space-y-2">
265
+ <Label htmlFor="context-generator-id">Context id</Label>
266
+ <Input
267
+ id="context-generator-id"
268
+ value={contextId}
269
+ onChange={(event) => setContextId(event.target.value)}
270
+ />
271
+ </div>
272
+ <div className="space-y-2">
273
+ <Label htmlFor="context-generator-title">Context title</Label>
274
+ <Input
275
+ id="context-generator-title"
276
+ value={contextTitle}
277
+ onChange={(event) => setContextTitle(event.target.value)}
278
+ />
279
+ </div>
280
+ </div>
281
+
282
+ <div className="space-y-2">
283
+ <Label>Page capability</Label>
284
+ <Select
285
+ value={capability}
286
+ onValueChange={(value) =>
287
+ value && setCapability(value as PageCapability)
288
+ }
289
+ >
290
+ <SelectTrigger className="w-full">
291
+ <SelectValue />
292
+ </SelectTrigger>
293
+ <SelectContent>
294
+ <SelectItem value="context">Context only</SelectItem>
295
+ <SelectItem value="form-filler">
296
+ Built-in Form filler
297
+ </SelectItem>
298
+ <SelectItem value="custom-tool">
299
+ Custom frontend Tool
300
+ </SelectItem>
301
+ </SelectContent>
302
+ </Select>
303
+ </div>
304
+
305
+ {capability === "custom-tool" ? (
306
+ <div className="space-y-4 rounded-lg border p-3">
307
+ <div className="space-y-2">
308
+ <Label htmlFor="context-generator-tool-name">Tool name</Label>
309
+ <Input
310
+ id="context-generator-tool-name"
311
+ value={toolName}
312
+ onChange={(event) => setToolName(event.target.value)}
313
+ />
314
+ </div>
315
+ <div className="space-y-2">
316
+ <Label htmlFor="context-generator-tool-action">
317
+ Business action
318
+ </Label>
319
+ <Textarea
320
+ id="context-generator-tool-action"
321
+ value={toolAction}
322
+ onChange={(event) => setToolAction(event.target.value)}
323
+ className="min-h-20"
324
+ />
325
+ </div>
326
+ <div className="space-y-2">
327
+ <Label>Permission</Label>
328
+ <Select
329
+ value={toolPermission}
330
+ onValueChange={(value) =>
331
+ value && setToolPermission(value as "ASK" | "ALLOW")
332
+ }
333
+ >
334
+ <SelectTrigger className="w-full">
335
+ <SelectValue />
336
+ </SelectTrigger>
337
+ <SelectContent>
338
+ <SelectItem value="ASK">ASK</SelectItem>
339
+ <SelectItem value="ALLOW">ALLOW</SelectItem>
340
+ </SelectContent>
341
+ </Select>
342
+ </div>
343
+ </div>
344
+ ) : null}
345
+ </CardContent>
346
+ </Card>
347
+
348
+ <PromptCard
349
+ title="Complete page context scene prompt"
350
+ description="Copy this prompt to generate the business page, AI interaction, context binding, and working conversation as one complete scene."
351
+ prompt={prompt}
352
+ />
353
+ </div>
354
+ );
355
+ }