@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.
- package/package.json +1 -1
- package/scripts/registry.mjs +2 -0
- 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
|
@@ -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
|
+
}
|