@nocobase/portal-template-default 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.MD +90 -1
- package/components.json +3 -1
- package/package.json +21 -5
- package/registry/nocobase-acl/README.md +19 -0
- package/registry/nocobase-acl/components/acl-boundary.tsx +106 -0
- package/registry/nocobase-acl/components/index.ts +3 -0
- package/registry/nocobase-acl/components/role-options.ts +36 -0
- package/registry/nocobase-acl/components/role-switcher.tsx +135 -0
- package/registry/nocobase-acl/demo/boundary-api.tsx +186 -0
- package/registry/nocobase-acl/demo/components.tsx +231 -0
- package/registry/nocobase-acl/demo/index.tsx +505 -0
- package/registry/nocobase-acl/demo/lazy-route.tsx +10 -0
- package/registry/nocobase-acl/demo/prompt-generator.tsx +134 -0
- package/registry/nocobase-acl/demo/role-switcher-prompt-generator.tsx +198 -0
- package/registry/nocobase-acl/demo/scenario-section.tsx +37 -0
- package/registry/nocobase-acl/extension.tsx +71 -0
- package/registry/nocobase-acl/index.ts +1 -0
- package/registry/nocobase-acl/tests/acl-regression.mjs +314 -0
- package/registry/nocobase-ai/README.md +70 -0
- package/registry/nocobase-ai/adapters/react-hook-form.ts +20 -0
- package/registry/nocobase-ai/assets/nocobase-ai-chat.svg +50 -0
- package/registry/nocobase-ai/components/ai-root-provider.tsx +32 -0
- package/registry/nocobase-ai/components/chat/ai-employee-avatar.tsx +31 -0
- package/registry/nocobase-ai/components/chat/chat-attachment.tsx +92 -0
- package/registry/nocobase-ai/components/chat/chat-compact.tsx +90 -0
- package/registry/nocobase-ai/components/chat/chat-composer.tsx +429 -0
- package/registry/nocobase-ai/components/chat/chat-empty-state.tsx +51 -0
- package/registry/nocobase-ai/components/chat/chat-header.tsx +88 -0
- package/registry/nocobase-ai/components/chat/chat-history-dialog.tsx +77 -0
- package/registry/nocobase-ai/components/chat/chat-message.tsx +252 -0
- package/registry/nocobase-ai/components/chat/chat-messages.tsx +173 -0
- package/registry/nocobase-ai/components/chat/chat-window.tsx +118 -0
- package/registry/nocobase-ai/components/chat/conversation-list.tsx +343 -0
- package/registry/nocobase-ai/components/chat/markdown-message.tsx +165 -0
- package/registry/nocobase-ai/components/chat/model-select-options.tsx +30 -0
- package/registry/nocobase-ai/components/chat/reasoning-panel.tsx +29 -0
- package/registry/nocobase-ai/components/chat/sub-agent-conversation.tsx +142 -0
- package/registry/nocobase-ai/components/chat/tool-call-card.tsx +316 -0
- package/registry/nocobase-ai/components/chat/user-prompt-editor.tsx +99 -0
- package/registry/nocobase-ai/components/chat/work-context-chip.tsx +114 -0
- package/registry/nocobase-ai/components/index.ts +59 -0
- package/registry/nocobase-ai/components/page-elements/ai-form.tsx +44 -0
- package/registry/nocobase-ai/components/page-elements/page-element-provider.tsx +469 -0
- package/registry/nocobase-ai/components/surfaces/chat-dialog.tsx +31 -0
- package/registry/nocobase-ai/components/surfaces/chat-inline.tsx +18 -0
- package/registry/nocobase-ai/components/surfaces/chat-page.tsx +18 -0
- package/registry/nocobase-ai/components/surfaces/chat-side-panel-layout.tsx +54 -0
- package/registry/nocobase-ai/components/surfaces/chat-side-panel.tsx +74 -0
- package/registry/nocobase-ai/components/surfaces/chat-surface-actions.tsx +54 -0
- package/registry/nocobase-ai/components/tools/builtin-tool-renderers.tsx +31 -0
- package/registry/nocobase-ai/components/tools/business-report-dialog.tsx +364 -0
- package/registry/nocobase-ai/components/tools/business-report-renderer.tsx +122 -0
- package/registry/nocobase-ai/components/tools/business-report-utils.tsx +282 -0
- package/registry/nocobase-ai/components/tools/chart-renderer.tsx +99 -0
- package/registry/nocobase-ai/components/tools/echarts-preview.tsx +59 -0
- package/registry/nocobase-ai/components/tools/echarts-runtime-advanced.ts +18 -0
- package/registry/nocobase-ai/components/tools/echarts-runtime-common.ts +20 -0
- package/registry/nocobase-ai/components/tools/echarts-runtime-components.ts +34 -0
- package/registry/nocobase-ai/components/tools/echarts-runtime-hierarchy.ts +24 -0
- package/registry/nocobase-ai/components/tools/echarts-runtime.ts +138 -0
- package/registry/nocobase-ai/components/tools/sub-agent-renderer.tsx +67 -0
- package/registry/nocobase-ai/components/tools/suggestions-renderer.tsx +59 -0
- package/registry/nocobase-ai/components/tools/tool-renderer-provider.tsx +55 -0
- package/registry/nocobase-ai/components/tools/tool-renderer-utils.ts +18 -0
- package/registry/nocobase-ai/components/tools/workflow-renderer.tsx +108 -0
- package/registry/nocobase-ai/components/triggers/ai-chat-floating-trigger.tsx +73 -0
- package/registry/nocobase-ai/components/triggers/ai-employee-shortcut.tsx +149 -0
- package/registry/nocobase-ai/demo/configuration-gate.tsx +49 -0
- package/registry/nocobase-ai/demo/container-showcase.tsx +210 -0
- package/registry/nocobase-ai/demo/floating.tsx +134 -0
- package/registry/nocobase-ai/demo/index.tsx +346 -0
- package/registry/nocobase-ai/demo/interaction-showcase.tsx +151 -0
- package/registry/nocobase-ai/demo/page-context-prompt-generator.tsx +355 -0
- package/registry/nocobase-ai/demo/page-context.tsx +884 -0
- package/registry/nocobase-ai/demo/page-element-showcase.tsx +197 -0
- package/registry/nocobase-ai/demo/prompt-card.tsx +41 -0
- package/registry/nocobase-ai/demo/prompt-generator.tsx +452 -0
- package/registry/nocobase-ai/demo/shortcut.tsx +1207 -0
- package/registry/nocobase-ai/demo/tool-cards.tsx +530 -0
- package/registry/nocobase-ai/extension.tsx +158 -0
- package/registry/nocobase-ai/global-ai-chat.tsx +213 -0
- package/registry/nocobase-ai/index.ts +3 -0
- package/registry/nocobase-ai/providers/ai-provider.tsx +323 -0
- package/registry/nocobase-ai/providers/avatars.ts +817 -0
- package/registry/nocobase-ai/providers/chat-context.tsx +109 -0
- package/registry/nocobase-ai/providers/chat-controller.ts +84 -0
- package/registry/nocobase-ai/providers/chat-message-utils.ts +198 -0
- package/registry/nocobase-ai/providers/chat-provider.tsx +871 -0
- package/registry/nocobase-ai/providers/chat-reducer.ts +147 -0
- package/registry/nocobase-ai/providers/chat-task-utils.ts +105 -0
- package/registry/nocobase-ai/providers/chat-transport.ts +184 -0
- package/registry/nocobase-ai/providers/form-registry.tsx +271 -0
- package/registry/nocobase-ai/providers/frontend-tool-registry.tsx +231 -0
- package/registry/nocobase-ai/providers/index.ts +61 -0
- package/registry/nocobase-ai/providers/model.ts +32 -0
- package/registry/nocobase-ai/providers/page-context.tsx +136 -0
- package/registry/nocobase-ai/providers/stream-coalescer.ts +60 -0
- package/registry/nocobase-ai/providers/stream-event-utils.ts +82 -0
- package/registry/nocobase-ai/providers/stream-parser.ts +61 -0
- package/registry/nocobase-ai/providers/sub-agent-stream.ts +316 -0
- package/registry/nocobase-ai/providers/types.ts +183 -0
- package/registry/nocobase-ai/providers/ui-message-stream.ts +463 -0
- package/registry/nocobase-ai/providers/use-automatic-tool-approval.ts +94 -0
- package/registry/nocobase-ai/providers/use-chat-attachments.ts +206 -0
- package/registry/nocobase-ai/providers/use-chat-message-actions.ts +554 -0
- package/registry/nocobase-ai/providers/use-chat-runtime.ts +210 -0
- package/registry/nocobase-ai/providers/use-chat-work-context.ts +93 -0
- package/registry/nocobase-ai/providers/use-conversation-catalog.ts +108 -0
- package/registry/nocobase-ai/providers/use-conversation-history.ts +178 -0
- package/registry/nocobase-ai/services/index.ts +7 -0
- package/registry/nocobase-ai/services/nocobase-ai-service.ts +454 -0
- package/registry/nocobase-ai/services/types.ts +82 -0
- package/registry/nocobase-ai/tests/business-report-regression.mjs +70 -0
- package/registry/nocobase-ai/tests/form-filler-regression.mjs +129 -0
- package/registry/nocobase-ai/tests/frontend-tool-regression.mjs +128 -0
- package/registry/nocobase-ai/tests/page-context-regression.mjs +48 -0
- package/registry/nocobase-ai/tests/stream-regression.mjs +207 -0
- package/registry/nocobase-i18n/README.md +53 -0
- package/registry/nocobase-i18n/components/index.ts +1 -0
- package/registry/nocobase-i18n/components/language-switcher.tsx +138 -0
- package/registry/nocobase-i18n/demo/index.tsx +209 -0
- package/registry/nocobase-i18n/demo/prompt-generator.tsx +155 -0
- package/registry/nocobase-i18n/extension.tsx +46 -0
- package/registry/nocobase-i18n/i18n-provider.ts +9 -0
- package/registry/nocobase-i18n/index.ts +6 -0
- package/registry/nocobase-i18n/locale-store.ts +134 -0
- package/registry/nocobase-i18n/locales/en-US.ts +89 -0
- package/registry/nocobase-i18n/locales/zh-CN.ts +85 -0
- package/registry/nocobase-i18n/provider.tsx +32 -0
- package/registry/nocobase-i18n/runtime.ts +118 -0
- package/registry/nocobase-i18n/server-resources.ts +184 -0
- package/registry/nocobase-i18n/tests/i18n-regression.mjs +42 -0
- package/registry.config.json +163 -0
- package/scripts/registry.mjs +164 -0
- package/src/App.css +95 -0
- package/src/App.tsx +87 -69
- package/src/app/extension.ts +11 -0
- package/src/app/extensions.tsx +49 -0
- package/src/components/access-control/access-denied.tsx +30 -0
- package/src/components/access-control/acl-bootstrap.tsx +9 -0
- package/src/components/access-control/acl-gate.tsx +36 -0
- package/src/components/access-control/navigate-to-accessible-resource.tsx +17 -0
- package/src/components/access-control/resource-access-guard.tsx +21 -0
- package/src/components/access-control/use-acl-runtime.ts +28 -0
- package/src/components/app-shell/breadcrumb.tsx +39 -3
- package/src/components/app-shell/document-title-handler.tsx +11 -20
- package/src/components/app-shell/header.tsx +22 -4
- package/src/components/app-shell/loading-overlay.tsx +2 -4
- package/src/components/app-shell/loading-state.tsx +15 -0
- package/src/components/app-shell/sidebar.tsx +103 -45
- package/src/components/data-table/data-table-pagination.tsx +27 -7
- package/src/components/data-table/data-table-styles.ts +57 -0
- package/src/components/data-table/data-table.tsx +14 -59
- package/src/components/resources/resource-label.ts +101 -0
- package/src/components/resources/views/create-view.tsx +17 -14
- package/src/components/resources/views/edit-view.tsx +17 -15
- package/src/components/resources/views/list-view.tsx +20 -10
- package/src/components/resources/views/show-view.tsx +20 -18
- package/src/index.tsx +2 -3
- package/src/lib/i18n.ts +161 -0
- package/src/lib/nocobase/acl/action.ts +147 -0
- package/src/lib/nocobase/acl/data-source.ts +24 -0
- package/src/lib/nocobase/acl/index.ts +6 -0
- package/src/lib/nocobase/acl/menu.ts +41 -0
- package/src/lib/nocobase/acl/record-permissions.ts +111 -0
- package/src/lib/nocobase/acl/store.ts +182 -0
- package/src/lib/nocobase/acl/types.ts +96 -0
- package/src/lib/nocobase/client.ts +279 -0
- package/src/lib/nocobase/error.ts +32 -0
- package/src/locales/en-US.ts +82 -0
- package/src/locales/index.ts +13 -0
- package/src/locales/zh-CN.ts +79 -0
- package/src/pages/users/create.tsx +98 -0
- package/src/pages/users/edit.tsx +92 -0
- package/src/pages/users/form-context.ts +57 -0
- package/src/pages/users/form-fields.tsx +211 -0
- package/src/pages/users/list.tsx +205 -0
- package/src/pages/users/show.tsx +187 -0
- package/src/pages/users/types.ts +20 -0
- package/src/providers/access-control.ts +34 -0
- package/src/providers/auth.ts +74 -60
- package/src/providers/constants.ts +3 -37
- package/src/providers/data.ts +242 -85
- package/vite.config.ts +17 -0
- package/src/pages/blog-posts/create.tsx +0 -177
- package/src/pages/blog-posts/edit.tsx +0 -197
- package/src/pages/blog-posts/list.tsx +0 -131
- package/src/pages/blog-posts/show.tsx +0 -81
- package/src/pages/categories/create.tsx +0 -73
- package/src/pages/categories/edit.tsx +0 -73
- package/src/pages/categories/index.ts +0 -4
- package/src/pages/categories/list.tsx +0 -86
- package/src/pages/categories/show.tsx +0 -37
- /package/src/pages/{blog-posts → users}/index.ts +0 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AIChatWindow,
|
|
3
|
+
ChatInline,
|
|
4
|
+
ChatPage,
|
|
5
|
+
type AIChatWindowProps,
|
|
6
|
+
} from "../components";
|
|
7
|
+
import { Button } from "@/components/ui/button";
|
|
8
|
+
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
9
|
+
import {
|
|
10
|
+
AppWindow,
|
|
11
|
+
Columns3,
|
|
12
|
+
Maximize2,
|
|
13
|
+
PanelRight,
|
|
14
|
+
Smartphone,
|
|
15
|
+
} from "lucide-react";
|
|
16
|
+
|
|
17
|
+
export type ChatContainer =
|
|
18
|
+
| "embedded"
|
|
19
|
+
| "page"
|
|
20
|
+
| "side-panel"
|
|
21
|
+
| "dialog"
|
|
22
|
+
| "mobile";
|
|
23
|
+
|
|
24
|
+
const containerOptions: Array<{
|
|
25
|
+
value: ChatContainer;
|
|
26
|
+
title: string;
|
|
27
|
+
description: string;
|
|
28
|
+
action: string;
|
|
29
|
+
icon: typeof AppWindow;
|
|
30
|
+
}> = [
|
|
31
|
+
{
|
|
32
|
+
value: "embedded",
|
|
33
|
+
title: "Embedded block",
|
|
34
|
+
description:
|
|
35
|
+
"Place chat inside a dashboard, record page, or workspace region.",
|
|
36
|
+
action: "Preview block",
|
|
37
|
+
icon: Columns3,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
value: "page",
|
|
41
|
+
title: "Dedicated page",
|
|
42
|
+
description:
|
|
43
|
+
"Give the conversation a full route and the largest working area.",
|
|
44
|
+
action: "Preview page",
|
|
45
|
+
icon: AppWindow,
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
value: "side-panel",
|
|
49
|
+
title: "Push side panel",
|
|
50
|
+
description: "Keep the page operable while the content narrows for chat.",
|
|
51
|
+
action: "Open panel",
|
|
52
|
+
icon: PanelRight,
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
value: "dialog",
|
|
56
|
+
title: "Dialog",
|
|
57
|
+
description:
|
|
58
|
+
"Open a focused conversation from an action without changing route.",
|
|
59
|
+
action: "Open dialog",
|
|
60
|
+
icon: Maximize2,
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
value: "mobile",
|
|
64
|
+
title: "Mobile container",
|
|
65
|
+
description: "Use the same component in a narrow, touch-friendly viewport.",
|
|
66
|
+
action: "Preview mobile",
|
|
67
|
+
icon: Smartphone,
|
|
68
|
+
},
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
export function ContainerShowcase({
|
|
72
|
+
value,
|
|
73
|
+
onValueChange,
|
|
74
|
+
windowProps,
|
|
75
|
+
}: {
|
|
76
|
+
value: ChatContainer;
|
|
77
|
+
onValueChange: (value: ChatContainer) => void;
|
|
78
|
+
windowProps: AIChatWindowProps;
|
|
79
|
+
}) {
|
|
80
|
+
return (
|
|
81
|
+
<div className="space-y-5">
|
|
82
|
+
<div className="grid gap-3 sm:grid-cols-2 xl:grid-cols-5">
|
|
83
|
+
{containerOptions.map((option) => {
|
|
84
|
+
const Icon = option.icon;
|
|
85
|
+
const active = option.value === value;
|
|
86
|
+
return (
|
|
87
|
+
<Card
|
|
88
|
+
key={option.value}
|
|
89
|
+
data-active={active}
|
|
90
|
+
className="gap-0 py-0 transition-colors data-active:border-foreground/35 data-active:bg-muted/20"
|
|
91
|
+
>
|
|
92
|
+
<CardHeader className="p-4">
|
|
93
|
+
<div className="mb-3 flex size-9 items-center justify-center rounded-lg border bg-background">
|
|
94
|
+
<Icon className="size-4 text-muted-foreground" />
|
|
95
|
+
</div>
|
|
96
|
+
<CardTitle className="text-sm">{option.title}</CardTitle>
|
|
97
|
+
<p className="min-h-15 text-xs leading-5 text-muted-foreground">
|
|
98
|
+
{option.description}
|
|
99
|
+
</p>
|
|
100
|
+
</CardHeader>
|
|
101
|
+
<CardContent className="border-t p-3">
|
|
102
|
+
<Button
|
|
103
|
+
variant={active ? "secondary" : "ghost"}
|
|
104
|
+
size="sm"
|
|
105
|
+
className="w-full"
|
|
106
|
+
onClick={() => onValueChange(option.value)}
|
|
107
|
+
>
|
|
108
|
+
{option.action}
|
|
109
|
+
</Button>
|
|
110
|
+
</CardContent>
|
|
111
|
+
</Card>
|
|
112
|
+
);
|
|
113
|
+
})}
|
|
114
|
+
</div>
|
|
115
|
+
|
|
116
|
+
<Card className="gap-0 py-0">
|
|
117
|
+
<div className="flex min-h-12 items-center justify-between border-b px-4">
|
|
118
|
+
<div>
|
|
119
|
+
<div className="text-sm font-medium">Container preview</div>
|
|
120
|
+
<div className="text-xs text-muted-foreground">
|
|
121
|
+
{containerOptions.find((option) => option.value === value)?.title}
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
<code className="rounded-md bg-muted px-2 py-1 text-xs text-muted-foreground">
|
|
125
|
+
{containerComponent(value)}
|
|
126
|
+
</code>
|
|
127
|
+
</div>
|
|
128
|
+
<div className="flex min-h-[650px] justify-center bg-muted/15 p-3 sm:p-5">
|
|
129
|
+
{value === "embedded" ? (
|
|
130
|
+
<ChatInline className="h-[610px] w-full max-w-3xl shadow-sm">
|
|
131
|
+
<AIChatWindow {...windowProps} />
|
|
132
|
+
</ChatInline>
|
|
133
|
+
) : null}
|
|
134
|
+
{value === "page" ? (
|
|
135
|
+
<ChatPage className="h-[610px] min-h-0 w-full">
|
|
136
|
+
<AIChatWindow {...windowProps} />
|
|
137
|
+
</ChatPage>
|
|
138
|
+
) : null}
|
|
139
|
+
{value === "mobile" ? (
|
|
140
|
+
<div className="w-full max-w-[390px] rounded-[2rem] border-4 border-foreground/10 bg-background p-1 shadow-xl">
|
|
141
|
+
<ChatInline className="h-[610px] rounded-[1.55rem] border-0">
|
|
142
|
+
<AIChatWindow {...windowProps} />
|
|
143
|
+
</ChatInline>
|
|
144
|
+
</div>
|
|
145
|
+
) : null}
|
|
146
|
+
{value === "side-panel" || value === "dialog" ? (
|
|
147
|
+
<ContainerLaunchPlaceholder
|
|
148
|
+
mode={value}
|
|
149
|
+
onOpen={() => onValueChange(value)}
|
|
150
|
+
/>
|
|
151
|
+
) : null}
|
|
152
|
+
</div>
|
|
153
|
+
</Card>
|
|
154
|
+
</div>
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function ContainerLaunchPlaceholder({
|
|
159
|
+
mode,
|
|
160
|
+
onOpen,
|
|
161
|
+
}: {
|
|
162
|
+
mode: "side-panel" | "dialog";
|
|
163
|
+
onOpen: () => void;
|
|
164
|
+
}) {
|
|
165
|
+
return (
|
|
166
|
+
<div className="grid w-full max-w-4xl grid-cols-[200px_minmax(0,1fr)] overflow-hidden rounded-xl border bg-background shadow-sm">
|
|
167
|
+
<div className="border-r bg-muted/30 p-4">
|
|
168
|
+
<div className="h-5 w-24 rounded bg-muted" />
|
|
169
|
+
<div className="mt-6 space-y-3">
|
|
170
|
+
<div className="h-8 rounded bg-muted" />
|
|
171
|
+
<div className="h-8 rounded bg-muted/70" />
|
|
172
|
+
<div className="h-8 rounded bg-muted/70" />
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
<div className="flex items-center justify-center p-8">
|
|
176
|
+
<div className="max-w-sm text-center">
|
|
177
|
+
<div className="mx-auto mb-4 h-24 rounded-xl border bg-muted/20" />
|
|
178
|
+
<h3 className="text-sm font-medium">
|
|
179
|
+
{mode === "side-panel"
|
|
180
|
+
? "Chat opens beside the current page"
|
|
181
|
+
: "Chat opens above the current page"}
|
|
182
|
+
</h3>
|
|
183
|
+
<p className="mt-2 text-xs leading-5 text-muted-foreground">
|
|
184
|
+
{mode === "side-panel"
|
|
185
|
+
? "The live preview pushes this page narrower instead of blocking interaction."
|
|
186
|
+
: "The live preview uses a focused dialog while preserving the page underneath."}
|
|
187
|
+
</p>
|
|
188
|
+
<Button className="mt-4" size="sm" onClick={onOpen}>
|
|
189
|
+
{mode === "side-panel" ? "Open side panel" : "Open dialog"}
|
|
190
|
+
</Button>
|
|
191
|
+
</div>
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const containerComponent = (value: ChatContainer) => {
|
|
198
|
+
switch (value) {
|
|
199
|
+
case "page":
|
|
200
|
+
return "<ChatPage />";
|
|
201
|
+
case "side-panel":
|
|
202
|
+
return "<ChatSidePanelLayout />";
|
|
203
|
+
case "dialog":
|
|
204
|
+
return "<ChatDialog />";
|
|
205
|
+
case "mobile":
|
|
206
|
+
return "<ChatInline /> · 390px";
|
|
207
|
+
default:
|
|
208
|
+
return "<ChatInline />";
|
|
209
|
+
}
|
|
210
|
+
};
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AIChatFloatingTrigger,
|
|
3
|
+
AIChatWindow,
|
|
4
|
+
ChatSidePanelLayout,
|
|
5
|
+
} from "../components";
|
|
6
|
+
import { Badge } from "@/components/ui/badge";
|
|
7
|
+
import { Card } from "@/components/ui/card";
|
|
8
|
+
import {
|
|
9
|
+
AIChatProvider,
|
|
10
|
+
useAIChatController,
|
|
11
|
+
useAIChatControllerState,
|
|
12
|
+
} from "../providers";
|
|
13
|
+
import { PromptCard } from "./prompt-card";
|
|
14
|
+
import { AIConfigurationGate } from "./configuration-gate";
|
|
15
|
+
|
|
16
|
+
const floatingPrompt = `Add the standard NocoBase AI floating chat entry to the application layout.
|
|
17
|
+
|
|
18
|
+
Requirements:
|
|
19
|
+
- Render AIChatFloatingTrigger at the lower-right edge of the application.
|
|
20
|
+
- Bind it to the global AI chat controller.
|
|
21
|
+
- Clicking the trigger opens a 450px ChatSidePanelLayout on the right.
|
|
22
|
+
- The side panel must push the desktop page narrower instead of blocking page interaction.
|
|
23
|
+
- Mobile may switch to an overlay presentation.
|
|
24
|
+
- Hide the floating trigger while the side panel is open.
|
|
25
|
+
- Support an unread-count badge.
|
|
26
|
+
- Start a new conversation with the configured default AI employee when opened.
|
|
27
|
+
- Keep the trigger and side-panel surface separate from AIChatWindow.
|
|
28
|
+
- Use shadcn/Base UI components and the existing AIProvider/AIChatProvider runtime.`;
|
|
29
|
+
|
|
30
|
+
export function FloatingChatPage() {
|
|
31
|
+
return (
|
|
32
|
+
<AIConfigurationGate>
|
|
33
|
+
<FloatingChatPageContent />
|
|
34
|
+
</AIConfigurationGate>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function FloatingChatPageContent() {
|
|
39
|
+
const controller = useAIChatController();
|
|
40
|
+
const { open } = useAIChatControllerState(controller);
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<AIChatProvider id="floating-chat-demo" controller={controller}>
|
|
44
|
+
<ChatSidePanelLayout
|
|
45
|
+
open={open}
|
|
46
|
+
onOpenChange={controller.setOpen}
|
|
47
|
+
width={450}
|
|
48
|
+
panel={<AIChatWindow />}
|
|
49
|
+
>
|
|
50
|
+
<div className="space-y-12 pb-12">
|
|
51
|
+
<section className="flex flex-wrap items-start justify-between gap-5 border-b pb-8">
|
|
52
|
+
<div>
|
|
53
|
+
<div className="flex items-center gap-2">
|
|
54
|
+
<Badge variant="secondary">AI Components</Badge>
|
|
55
|
+
<Badge variant="outline">Global entry</Badge>
|
|
56
|
+
</div>
|
|
57
|
+
<h1 className="mt-4 text-3xl font-semibold tracking-[-0.035em]">
|
|
58
|
+
Floating AI Chat
|
|
59
|
+
</h1>
|
|
60
|
+
<p className="mt-3 max-w-3xl text-sm leading-6 text-muted-foreground">
|
|
61
|
+
A global lower-right entry for the shared AI conversation. It is
|
|
62
|
+
intentionally coupled to the push side-panel surface rather than
|
|
63
|
+
treated as a generic chat container.
|
|
64
|
+
</p>
|
|
65
|
+
</div>
|
|
66
|
+
</section>
|
|
67
|
+
|
|
68
|
+
<section className="space-y-5">
|
|
69
|
+
<div>
|
|
70
|
+
<p className="text-xs font-semibold uppercase tracking-[0.16em] text-muted-foreground">
|
|
71
|
+
Interactive preview
|
|
72
|
+
</p>
|
|
73
|
+
<h2 className="mt-2 text-xl font-semibold tracking-tight">
|
|
74
|
+
Global launcher and push side panel
|
|
75
|
+
</h2>
|
|
76
|
+
<p className="mt-1.5 max-w-3xl text-sm leading-6 text-muted-foreground">
|
|
77
|
+
The trigger is constrained to this simulated application in the
|
|
78
|
+
demo. In production its default positioning is fixed to the
|
|
79
|
+
viewport.
|
|
80
|
+
</p>
|
|
81
|
+
</div>
|
|
82
|
+
<Card className="gap-0 overflow-hidden py-0">
|
|
83
|
+
<div className="flex h-12 items-center justify-between border-b px-4">
|
|
84
|
+
<span className="text-sm font-medium">Application shell</span>
|
|
85
|
+
<code className="rounded-md bg-muted px-2 py-1 text-xs text-muted-foreground">
|
|
86
|
+
AIChatFloatingTrigger → ChatSidePanelLayout
|
|
87
|
+
</code>
|
|
88
|
+
</div>
|
|
89
|
+
<div className="relative grid min-h-[560px] grid-cols-[190px_minmax(0,1fr)] overflow-hidden bg-background">
|
|
90
|
+
<div className="border-r bg-muted/25 p-4">
|
|
91
|
+
<div className="h-7 w-28 rounded bg-muted" />
|
|
92
|
+
<div className="mt-8 space-y-3">
|
|
93
|
+
<div className="h-9 rounded-lg bg-muted" />
|
|
94
|
+
<div className="h-9 rounded-lg bg-muted/65" />
|
|
95
|
+
<div className="h-9 rounded-lg bg-muted/65" />
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
<div className="p-6">
|
|
99
|
+
<div className="grid gap-4 sm:grid-cols-3">
|
|
100
|
+
<div className="h-28 rounded-xl border bg-muted/15" />
|
|
101
|
+
<div className="h-28 rounded-xl border bg-muted/15" />
|
|
102
|
+
<div className="h-28 rounded-xl border bg-muted/15" />
|
|
103
|
+
</div>
|
|
104
|
+
<div className="mt-5 h-72 rounded-xl border bg-muted/10" />
|
|
105
|
+
</div>
|
|
106
|
+
<AIChatFloatingTrigger
|
|
107
|
+
controller={controller}
|
|
108
|
+
unreadCount={3}
|
|
109
|
+
position="absolute"
|
|
110
|
+
/>
|
|
111
|
+
</div>
|
|
112
|
+
</Card>
|
|
113
|
+
</section>
|
|
114
|
+
|
|
115
|
+
<section className="space-y-5">
|
|
116
|
+
<div>
|
|
117
|
+
<p className="text-xs font-semibold uppercase tracking-[0.16em] text-muted-foreground">
|
|
118
|
+
Floating chat prompt
|
|
119
|
+
</p>
|
|
120
|
+
<h2 className="mt-2 text-xl font-semibold tracking-tight">
|
|
121
|
+
Generate the global launcher separately
|
|
122
|
+
</h2>
|
|
123
|
+
</div>
|
|
124
|
+
<PromptCard
|
|
125
|
+
title="Add a lower-right AI floating entry"
|
|
126
|
+
description="This prompt only configures the global trigger and its side panel."
|
|
127
|
+
prompt={floatingPrompt}
|
|
128
|
+
/>
|
|
129
|
+
</section>
|
|
130
|
+
</div>
|
|
131
|
+
</ChatSidePanelLayout>
|
|
132
|
+
</AIChatProvider>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AIChatWindow,
|
|
3
|
+
ChatDialog,
|
|
4
|
+
ChatSurfaceActions,
|
|
5
|
+
ChatSidePanelLayout,
|
|
6
|
+
useAIPageElementPicker,
|
|
7
|
+
type AIChatComposerAction,
|
|
8
|
+
type AIChatWindowProps,
|
|
9
|
+
} from "../components";
|
|
10
|
+
import { Badge } from "@/components/ui/badge";
|
|
11
|
+
import { Card } from "@/components/ui/card";
|
|
12
|
+
import {
|
|
13
|
+
Table,
|
|
14
|
+
TableBody,
|
|
15
|
+
TableCell,
|
|
16
|
+
TableHead,
|
|
17
|
+
TableHeader,
|
|
18
|
+
TableRow,
|
|
19
|
+
} from "@/components/ui/table";
|
|
20
|
+
import { AIChatProvider, useAIChatBase } from "../providers";
|
|
21
|
+
import { Globe2, MousePointer2 } from "lucide-react";
|
|
22
|
+
import { useMemo, useState } from "react";
|
|
23
|
+
import { ContainerShowcase, type ChatContainer } from "./container-showcase";
|
|
24
|
+
import { InteractionShowcase } from "./interaction-showcase";
|
|
25
|
+
import { AIConfigurationGate } from "./configuration-gate";
|
|
26
|
+
import { PromptGenerator } from "./prompt-generator";
|
|
27
|
+
|
|
28
|
+
const propRows = [
|
|
29
|
+
[
|
|
30
|
+
"className",
|
|
31
|
+
"string",
|
|
32
|
+
"undefined",
|
|
33
|
+
"Adds layout or sizing classes to the root conversation window.",
|
|
34
|
+
],
|
|
35
|
+
[
|
|
36
|
+
"headerActions",
|
|
37
|
+
"ReactNode",
|
|
38
|
+
"undefined",
|
|
39
|
+
"Adds surface actions such as expand, collapse, or close to the header.",
|
|
40
|
+
],
|
|
41
|
+
[
|
|
42
|
+
"composerActions",
|
|
43
|
+
"AIChatComposerAction[]",
|
|
44
|
+
"[]",
|
|
45
|
+
"Application-specific buttons rendered in the composer toolbar.",
|
|
46
|
+
],
|
|
47
|
+
[
|
|
48
|
+
"showConversationToggle",
|
|
49
|
+
"boolean",
|
|
50
|
+
"true",
|
|
51
|
+
"Shows the conversation-list control.",
|
|
52
|
+
],
|
|
53
|
+
[
|
|
54
|
+
"showNewConversation",
|
|
55
|
+
"boolean",
|
|
56
|
+
"true",
|
|
57
|
+
"Shows the new-conversation action.",
|
|
58
|
+
],
|
|
59
|
+
[
|
|
60
|
+
"showEmployeeSelector",
|
|
61
|
+
"boolean",
|
|
62
|
+
"true",
|
|
63
|
+
"Shows the AI employee selector in the composer.",
|
|
64
|
+
],
|
|
65
|
+
[
|
|
66
|
+
"showModelSelector",
|
|
67
|
+
"boolean",
|
|
68
|
+
"true",
|
|
69
|
+
"Shows the model selector in the composer.",
|
|
70
|
+
],
|
|
71
|
+
[
|
|
72
|
+
"showUserPrompt",
|
|
73
|
+
"boolean",
|
|
74
|
+
"true",
|
|
75
|
+
"Shows the personalized AI employee prompt editor.",
|
|
76
|
+
],
|
|
77
|
+
[
|
|
78
|
+
"enableAttachments",
|
|
79
|
+
"boolean",
|
|
80
|
+
"false",
|
|
81
|
+
"Enables file picker, drag-and-drop, and pasted-image uploads.",
|
|
82
|
+
],
|
|
83
|
+
[
|
|
84
|
+
"attachmentActionIndex",
|
|
85
|
+
"number",
|
|
86
|
+
"0",
|
|
87
|
+
"Places the attachment action at a specific position in the composer toolbar.",
|
|
88
|
+
],
|
|
89
|
+
[
|
|
90
|
+
"onToolCallDecision",
|
|
91
|
+
"(decision: AIToolCallDecision) => void | Promise<void>",
|
|
92
|
+
"undefined",
|
|
93
|
+
"Observes approve, reject, or edit decisions after AIChatProvider has processed them; use it for application side effects or telemetry.",
|
|
94
|
+
],
|
|
95
|
+
[
|
|
96
|
+
"placeholder",
|
|
97
|
+
"string",
|
|
98
|
+
"Message your AI employee…",
|
|
99
|
+
"Composer placeholder text.",
|
|
100
|
+
],
|
|
101
|
+
[
|
|
102
|
+
"disclaimer",
|
|
103
|
+
"ReactNode | false",
|
|
104
|
+
"Default disclaimer",
|
|
105
|
+
"Customizes or hides the footer disclaimer.",
|
|
106
|
+
],
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
export function AIChatPage() {
|
|
110
|
+
return (
|
|
111
|
+
<AIConfigurationGate>
|
|
112
|
+
<AIChatProvider id="ai-chat-demo">
|
|
113
|
+
<AIChatPageContent />
|
|
114
|
+
</AIChatProvider>
|
|
115
|
+
</AIConfigurationGate>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function AIChatPageContent() {
|
|
120
|
+
const [container, setContainer] = useState<ChatContainer>("embedded");
|
|
121
|
+
const [sidePanelOpen, setSidePanelOpen] = useState(false);
|
|
122
|
+
const [dialogOpen, setDialogOpen] = useState(false);
|
|
123
|
+
const [webSearch, setWebSearch] = useState(false);
|
|
124
|
+
const { id: chatId, addWorkContext, focusComposer } = useAIChatBase();
|
|
125
|
+
const { registeredCount, startPicking } = useAIPageElementPicker();
|
|
126
|
+
|
|
127
|
+
const composerActions = useMemo<AIChatComposerAction[]>(
|
|
128
|
+
() => [
|
|
129
|
+
{
|
|
130
|
+
key: "pick-page-element",
|
|
131
|
+
label: "Pick page element",
|
|
132
|
+
icon: <MousePointer2 />,
|
|
133
|
+
disabled: registeredCount === 0,
|
|
134
|
+
onClick: () => {
|
|
135
|
+
if (dialogOpen) {
|
|
136
|
+
setDialogOpen(false);
|
|
137
|
+
setSidePanelOpen(true);
|
|
138
|
+
setContainer("side-panel");
|
|
139
|
+
}
|
|
140
|
+
startPicking({
|
|
141
|
+
chatId,
|
|
142
|
+
onSelect: (item) => {
|
|
143
|
+
addWorkContext(item);
|
|
144
|
+
focusComposer();
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
key: "web-search",
|
|
151
|
+
label: "Web search",
|
|
152
|
+
icon: <Globe2 />,
|
|
153
|
+
active: webSearch,
|
|
154
|
+
onClick: () => {
|
|
155
|
+
setWebSearch((active) => !active);
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
],
|
|
159
|
+
[
|
|
160
|
+
addWorkContext,
|
|
161
|
+
chatId,
|
|
162
|
+
dialogOpen,
|
|
163
|
+
focusComposer,
|
|
164
|
+
registeredCount,
|
|
165
|
+
startPicking,
|
|
166
|
+
webSearch,
|
|
167
|
+
]
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
const windowProps = useMemo<AIChatWindowProps>(
|
|
171
|
+
() => ({
|
|
172
|
+
composerActions,
|
|
173
|
+
enableAttachments: true,
|
|
174
|
+
attachmentActionIndex: 1,
|
|
175
|
+
onToolCallDecision: async () => undefined,
|
|
176
|
+
}),
|
|
177
|
+
[composerActions]
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
const closeSurface = () => {
|
|
181
|
+
setSidePanelOpen(false);
|
|
182
|
+
setDialogOpen(false);
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const sidePanelWindowProps: AIChatWindowProps = {
|
|
186
|
+
...windowProps,
|
|
187
|
+
headerActions: (
|
|
188
|
+
<ChatSurfaceActions
|
|
189
|
+
expanded={false}
|
|
190
|
+
onExpandedChange={(expanded) => {
|
|
191
|
+
if (!expanded) return;
|
|
192
|
+
setSidePanelOpen(false);
|
|
193
|
+
setDialogOpen(true);
|
|
194
|
+
}}
|
|
195
|
+
onClose={closeSurface}
|
|
196
|
+
/>
|
|
197
|
+
),
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const dialogWindowProps: AIChatWindowProps = {
|
|
201
|
+
...windowProps,
|
|
202
|
+
headerActions: (
|
|
203
|
+
<ChatSurfaceActions
|
|
204
|
+
expanded={true}
|
|
205
|
+
onExpandedChange={(nextExpanded) => {
|
|
206
|
+
if (nextExpanded) return;
|
|
207
|
+
setDialogOpen(false);
|
|
208
|
+
setSidePanelOpen(true);
|
|
209
|
+
setContainer("side-panel");
|
|
210
|
+
}}
|
|
211
|
+
onClose={closeSurface}
|
|
212
|
+
/>
|
|
213
|
+
),
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const selectContainer = (nextContainer: ChatContainer) => {
|
|
217
|
+
setContainer(nextContainer);
|
|
218
|
+
setSidePanelOpen(nextContainer === "side-panel");
|
|
219
|
+
setDialogOpen(nextContainer === "dialog");
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
return (
|
|
223
|
+
<ChatSidePanelLayout
|
|
224
|
+
open={sidePanelOpen}
|
|
225
|
+
onOpenChange={setSidePanelOpen}
|
|
226
|
+
width={450}
|
|
227
|
+
panel={<AIChatWindow {...sidePanelWindowProps} />}
|
|
228
|
+
showCloseHandle={false}
|
|
229
|
+
>
|
|
230
|
+
<div className="space-y-14 pb-12">
|
|
231
|
+
<section className="flex flex-wrap items-start justify-between gap-5 border-b pb-8">
|
|
232
|
+
<div>
|
|
233
|
+
<div className="flex items-center gap-2">
|
|
234
|
+
<Badge variant="secondary">AI Components</Badge>
|
|
235
|
+
<Badge variant="outline">Preview</Badge>
|
|
236
|
+
</div>
|
|
237
|
+
<h1 className="mt-4 text-3xl font-semibold tracking-[-0.035em]">
|
|
238
|
+
AI Chat Window
|
|
239
|
+
</h1>
|
|
240
|
+
<p className="mt-3 max-w-3xl text-sm leading-6 text-muted-foreground">
|
|
241
|
+
A position-independent NocoBase AI employee conversation
|
|
242
|
+
component. Explore message interactions independently, place the
|
|
243
|
+
same chat in different containers, then generate an implementation
|
|
244
|
+
prompt for a target page.
|
|
245
|
+
</p>
|
|
246
|
+
</div>
|
|
247
|
+
</section>
|
|
248
|
+
|
|
249
|
+
<section className="space-y-5">
|
|
250
|
+
<SectionTitle
|
|
251
|
+
eyebrow="Container patterns"
|
|
252
|
+
title="Use the same conversation window wherever the product needs it"
|
|
253
|
+
description="The provider owns conversation state. Page, embedded block, push side panel, dialog, and mobile containers only decide placement and dimensions."
|
|
254
|
+
/>
|
|
255
|
+
<ContainerShowcase
|
|
256
|
+
value={container}
|
|
257
|
+
onValueChange={selectContainer}
|
|
258
|
+
windowProps={windowProps}
|
|
259
|
+
/>
|
|
260
|
+
</section>
|
|
261
|
+
|
|
262
|
+
<section className="space-y-5">
|
|
263
|
+
<SectionTitle
|
|
264
|
+
eyebrow="Message presentation"
|
|
265
|
+
title="Choose how much conversation history the page should expose"
|
|
266
|
+
description="Use the complete transcript for conversational work, or a compact worker surface that opens message history only when the user asks for it."
|
|
267
|
+
/>
|
|
268
|
+
<InteractionShowcase />
|
|
269
|
+
</section>
|
|
270
|
+
|
|
271
|
+
<section className="space-y-5">
|
|
272
|
+
<SectionTitle
|
|
273
|
+
eyebrow="Prompt generator"
|
|
274
|
+
title="Describe where chat belongs, then copy an implementation prompt"
|
|
275
|
+
description="This replaces a generic prop configuration panel with a task-oriented generator: choose the target area, placement mode, and required capabilities."
|
|
276
|
+
/>
|
|
277
|
+
<PromptGenerator />
|
|
278
|
+
</section>
|
|
279
|
+
|
|
280
|
+
<section className="space-y-5">
|
|
281
|
+
<SectionTitle
|
|
282
|
+
eyebrow="Component API"
|
|
283
|
+
title="AIChatWindow props"
|
|
284
|
+
description="The core window stays reusable while business pages provide placement, composer actions, and tool-approval behavior."
|
|
285
|
+
/>
|
|
286
|
+
<Card className="gap-0 overflow-hidden py-0">
|
|
287
|
+
<Table>
|
|
288
|
+
<TableHeader>
|
|
289
|
+
<TableRow>
|
|
290
|
+
<TableHead>Prop</TableHead>
|
|
291
|
+
<TableHead>Type</TableHead>
|
|
292
|
+
<TableHead>Default</TableHead>
|
|
293
|
+
<TableHead>Description</TableHead>
|
|
294
|
+
</TableRow>
|
|
295
|
+
</TableHeader>
|
|
296
|
+
<TableBody>
|
|
297
|
+
{propRows.map(([name, type, defaultValue, description]) => (
|
|
298
|
+
<TableRow key={name}>
|
|
299
|
+
<TableCell className="font-mono text-xs font-medium">
|
|
300
|
+
{name}
|
|
301
|
+
</TableCell>
|
|
302
|
+
<TableCell className="font-mono text-xs text-muted-foreground">
|
|
303
|
+
{type}
|
|
304
|
+
</TableCell>
|
|
305
|
+
<TableCell className="font-mono text-xs text-muted-foreground">
|
|
306
|
+
{defaultValue}
|
|
307
|
+
</TableCell>
|
|
308
|
+
<TableCell className="min-w-80 whitespace-normal text-muted-foreground">
|
|
309
|
+
{description}
|
|
310
|
+
</TableCell>
|
|
311
|
+
</TableRow>
|
|
312
|
+
))}
|
|
313
|
+
</TableBody>
|
|
314
|
+
</Table>
|
|
315
|
+
</Card>
|
|
316
|
+
</section>
|
|
317
|
+
|
|
318
|
+
<ChatDialog open={dialogOpen} onOpenChange={setDialogOpen}>
|
|
319
|
+
<AIChatWindow {...dialogWindowProps} />
|
|
320
|
+
</ChatDialog>
|
|
321
|
+
</div>
|
|
322
|
+
</ChatSidePanelLayout>
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function SectionTitle({
|
|
327
|
+
eyebrow,
|
|
328
|
+
title,
|
|
329
|
+
description,
|
|
330
|
+
}: {
|
|
331
|
+
eyebrow: string;
|
|
332
|
+
title: string;
|
|
333
|
+
description: string;
|
|
334
|
+
}) {
|
|
335
|
+
return (
|
|
336
|
+
<div>
|
|
337
|
+
<p className="text-xs font-semibold uppercase tracking-[0.16em] text-muted-foreground">
|
|
338
|
+
{eyebrow}
|
|
339
|
+
</p>
|
|
340
|
+
<h2 className="mt-2 text-xl font-semibold tracking-tight">{title}</h2>
|
|
341
|
+
<p className="mt-1.5 max-w-3xl text-sm leading-6 text-muted-foreground">
|
|
342
|
+
{description}
|
|
343
|
+
</p>
|
|
344
|
+
</div>
|
|
345
|
+
);
|
|
346
|
+
}
|