@nocobase/portal-template-default 1.0.3 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/README.MD +10 -5
  2. package/package.json +17 -17
  3. package/registry/nocobase-ai/components/chat/chat-message.tsx +8 -6
  4. package/registry/nocobase-ai/components/chat/chat-messages.tsx +8 -3
  5. package/registry/nocobase-ai/components/chat/markdown-message.tsx +26 -0
  6. package/registry/nocobase-ai/components/chat/sub-agent-conversation.tsx +3 -3
  7. package/registry/nocobase-ai/demo/configuration-gate.tsx +83 -35
  8. package/registry/nocobase-i18n/README.md +23 -32
  9. package/registry/nocobase-i18n/components/language-switcher.tsx +1 -1
  10. package/registry/nocobase-i18n/demo/index.tsx +2 -2
  11. package/registry/nocobase-i18n/demo/prompt-generator.tsx +3 -2
  12. package/registry/nocobase-i18n/extension.tsx +3 -4
  13. package/registry/nocobase-i18n/index.ts +4 -3
  14. package/registry/nocobase-i18n/locales/en-US.ts +2 -52
  15. package/registry/nocobase-i18n/locales/index.ts +8 -0
  16. package/registry/nocobase-i18n/locales/zh-CN.ts +2 -52
  17. package/registry/nocobase-i18n/provider.tsx +18 -6
  18. package/registry/nocobase-i18n/server-resources.ts +13 -31
  19. package/registry/nocobase-i18n/tests/i18n-regression.mjs +14 -5
  20. package/registry/nocobase-route-surfaces/README.md +21 -0
  21. package/registry/nocobase-route-surfaces/demo/index.tsx +266 -0
  22. package/{src/extensions/nocobase-acl → registry/nocobase-route-surfaces}/demo/lazy-route.tsx +2 -1
  23. package/registry/nocobase-route-surfaces/demo/prompt-generator.tsx +90 -0
  24. package/registry/nocobase-route-surfaces/demo/scenarios.ts +95 -0
  25. package/registry/nocobase-route-surfaces/extension.tsx +119 -0
  26. package/registry/nocobase-route-surfaces/index.ts +7 -0
  27. package/registry/nocobase-route-surfaces/route-dialog.tsx +145 -0
  28. package/registry/nocobase-route-surfaces/route-drawer.tsx +186 -0
  29. package/registry/nocobase-route-surfaces/route-page.tsx +29 -0
  30. package/registry/nocobase-route-surfaces/route-surface-context.ts +13 -0
  31. package/registry/nocobase-route-surfaces/use-refine-unsaved-changes.tsx +106 -0
  32. package/registry/nocobase-route-surfaces/use-route-surface-close.ts +15 -0
  33. package/registry/nocobase-route-surfaces/use-route-surface-state.ts +65 -0
  34. package/registry.config.json +30 -3
  35. package/scripts/registry.mjs +0 -2
  36. package/src/App.tsx +65 -35
  37. package/src/app/extension.ts +1 -2
  38. package/src/app/extensions.tsx +0 -14
  39. package/src/components/app-shell/breadcrumb.tsx +67 -2
  40. package/src/components/app-shell/sidebar.tsx +2 -2
  41. package/src/components/resources/views/list-view.tsx +3 -2
  42. package/src/components/ui/sheet.tsx +22 -21
  43. package/src/lib/nocobase/client.ts +6 -1
  44. package/src/locales/en-US.ts +81 -0
  45. package/src/locales/zh-CN.ts +77 -0
  46. package/src/pages/users/create.tsx +76 -53
  47. package/src/pages/users/edit.tsx +87 -56
  48. package/src/pages/users/index.ts +3 -0
  49. package/src/pages/users/layout.tsx +15 -0
  50. package/src/pages/users/list.tsx +19 -21
  51. package/src/pages/users/role-badges.tsx +43 -0
  52. package/src/pages/users/role-detail.tsx +272 -0
  53. package/src/pages/users/role-utils.ts +6 -0
  54. package/src/pages/users/routes.ts +24 -0
  55. package/src/pages/users/show.tsx +195 -104
  56. package/src/pages/users/types.ts +13 -0
  57. package/src/providers/i18n/index.ts +3 -0
  58. package/{registry/nocobase-i18n → src/providers/i18n}/runtime.ts +63 -16
  59. package/src/providers/system-settings/context.ts +26 -0
  60. package/src/providers/system-settings/index.ts +2 -0
  61. package/src/providers/system-settings/provider.tsx +94 -0
  62. package/src/extensions/nocobase-acl/README.md +0 -19
  63. package/src/extensions/nocobase-acl/components/acl-boundary.tsx +0 -106
  64. package/src/extensions/nocobase-acl/components/index.ts +0 -3
  65. package/src/extensions/nocobase-acl/components/role-options.ts +0 -36
  66. package/src/extensions/nocobase-acl/components/role-switcher.tsx +0 -135
  67. package/src/extensions/nocobase-acl/demo/boundary-api.tsx +0 -186
  68. package/src/extensions/nocobase-acl/demo/components.tsx +0 -231
  69. package/src/extensions/nocobase-acl/demo/index.tsx +0 -505
  70. package/src/extensions/nocobase-acl/demo/prompt-generator.tsx +0 -134
  71. package/src/extensions/nocobase-acl/demo/role-switcher-prompt-generator.tsx +0 -198
  72. package/src/extensions/nocobase-acl/demo/scenario-section.tsx +0 -37
  73. package/src/extensions/nocobase-acl/extension.tsx +0 -71
  74. package/src/extensions/nocobase-acl/index.ts +0 -1
  75. package/src/extensions/nocobase-ai/README.md +0 -70
  76. package/src/extensions/nocobase-ai/adapters/react-hook-form.ts +0 -20
  77. package/src/extensions/nocobase-ai/assets/nocobase-ai-chat.svg +0 -50
  78. package/src/extensions/nocobase-ai/components/ai-root-provider.tsx +0 -32
  79. package/src/extensions/nocobase-ai/components/chat/ai-employee-avatar.tsx +0 -31
  80. package/src/extensions/nocobase-ai/components/chat/chat-attachment.tsx +0 -92
  81. package/src/extensions/nocobase-ai/components/chat/chat-compact.tsx +0 -90
  82. package/src/extensions/nocobase-ai/components/chat/chat-composer.tsx +0 -429
  83. package/src/extensions/nocobase-ai/components/chat/chat-empty-state.tsx +0 -51
  84. package/src/extensions/nocobase-ai/components/chat/chat-header.tsx +0 -88
  85. package/src/extensions/nocobase-ai/components/chat/chat-history-dialog.tsx +0 -77
  86. package/src/extensions/nocobase-ai/components/chat/chat-message.tsx +0 -252
  87. package/src/extensions/nocobase-ai/components/chat/chat-messages.tsx +0 -173
  88. package/src/extensions/nocobase-ai/components/chat/chat-window.tsx +0 -118
  89. package/src/extensions/nocobase-ai/components/chat/conversation-list.tsx +0 -343
  90. package/src/extensions/nocobase-ai/components/chat/markdown-message.tsx +0 -165
  91. package/src/extensions/nocobase-ai/components/chat/model-select-options.tsx +0 -30
  92. package/src/extensions/nocobase-ai/components/chat/reasoning-panel.tsx +0 -29
  93. package/src/extensions/nocobase-ai/components/chat/sub-agent-conversation.tsx +0 -142
  94. package/src/extensions/nocobase-ai/components/chat/tool-call-card.tsx +0 -316
  95. package/src/extensions/nocobase-ai/components/chat/user-prompt-editor.tsx +0 -99
  96. package/src/extensions/nocobase-ai/components/chat/work-context-chip.tsx +0 -114
  97. package/src/extensions/nocobase-ai/components/index.ts +0 -59
  98. package/src/extensions/nocobase-ai/components/page-elements/ai-form.tsx +0 -44
  99. package/src/extensions/nocobase-ai/components/page-elements/page-element-provider.tsx +0 -469
  100. package/src/extensions/nocobase-ai/components/surfaces/chat-dialog.tsx +0 -31
  101. package/src/extensions/nocobase-ai/components/surfaces/chat-inline.tsx +0 -18
  102. package/src/extensions/nocobase-ai/components/surfaces/chat-page.tsx +0 -18
  103. package/src/extensions/nocobase-ai/components/surfaces/chat-side-panel-layout.tsx +0 -54
  104. package/src/extensions/nocobase-ai/components/surfaces/chat-side-panel.tsx +0 -74
  105. package/src/extensions/nocobase-ai/components/surfaces/chat-surface-actions.tsx +0 -54
  106. package/src/extensions/nocobase-ai/components/tools/builtin-tool-renderers.tsx +0 -31
  107. package/src/extensions/nocobase-ai/components/tools/business-report-dialog.tsx +0 -364
  108. package/src/extensions/nocobase-ai/components/tools/business-report-renderer.tsx +0 -122
  109. package/src/extensions/nocobase-ai/components/tools/business-report-utils.tsx +0 -282
  110. package/src/extensions/nocobase-ai/components/tools/chart-renderer.tsx +0 -99
  111. package/src/extensions/nocobase-ai/components/tools/echarts-preview.tsx +0 -59
  112. package/src/extensions/nocobase-ai/components/tools/echarts-runtime-advanced.ts +0 -18
  113. package/src/extensions/nocobase-ai/components/tools/echarts-runtime-common.ts +0 -20
  114. package/src/extensions/nocobase-ai/components/tools/echarts-runtime-components.ts +0 -34
  115. package/src/extensions/nocobase-ai/components/tools/echarts-runtime-hierarchy.ts +0 -24
  116. package/src/extensions/nocobase-ai/components/tools/echarts-runtime.ts +0 -138
  117. package/src/extensions/nocobase-ai/components/tools/sub-agent-renderer.tsx +0 -67
  118. package/src/extensions/nocobase-ai/components/tools/suggestions-renderer.tsx +0 -59
  119. package/src/extensions/nocobase-ai/components/tools/tool-renderer-provider.tsx +0 -55
  120. package/src/extensions/nocobase-ai/components/tools/tool-renderer-utils.ts +0 -18
  121. package/src/extensions/nocobase-ai/components/tools/workflow-renderer.tsx +0 -108
  122. package/src/extensions/nocobase-ai/components/triggers/ai-chat-floating-trigger.tsx +0 -73
  123. package/src/extensions/nocobase-ai/components/triggers/ai-employee-shortcut.tsx +0 -149
  124. package/src/extensions/nocobase-ai/demo/configuration-gate.tsx +0 -49
  125. package/src/extensions/nocobase-ai/demo/container-showcase.tsx +0 -210
  126. package/src/extensions/nocobase-ai/demo/floating.tsx +0 -134
  127. package/src/extensions/nocobase-ai/demo/index.tsx +0 -346
  128. package/src/extensions/nocobase-ai/demo/interaction-showcase.tsx +0 -151
  129. package/src/extensions/nocobase-ai/demo/page-context-prompt-generator.tsx +0 -355
  130. package/src/extensions/nocobase-ai/demo/page-context.tsx +0 -884
  131. package/src/extensions/nocobase-ai/demo/page-element-showcase.tsx +0 -197
  132. package/src/extensions/nocobase-ai/demo/prompt-card.tsx +0 -41
  133. package/src/extensions/nocobase-ai/demo/prompt-generator.tsx +0 -452
  134. package/src/extensions/nocobase-ai/demo/shortcut.tsx +0 -1207
  135. package/src/extensions/nocobase-ai/demo/tool-cards.tsx +0 -530
  136. package/src/extensions/nocobase-ai/extension.tsx +0 -158
  137. package/src/extensions/nocobase-ai/global-ai-chat.tsx +0 -213
  138. package/src/extensions/nocobase-ai/index.ts +0 -3
  139. package/src/extensions/nocobase-ai/providers/ai-provider.tsx +0 -323
  140. package/src/extensions/nocobase-ai/providers/avatars.ts +0 -817
  141. package/src/extensions/nocobase-ai/providers/chat-context.tsx +0 -109
  142. package/src/extensions/nocobase-ai/providers/chat-controller.ts +0 -84
  143. package/src/extensions/nocobase-ai/providers/chat-message-utils.ts +0 -198
  144. package/src/extensions/nocobase-ai/providers/chat-provider.tsx +0 -871
  145. package/src/extensions/nocobase-ai/providers/chat-reducer.ts +0 -147
  146. package/src/extensions/nocobase-ai/providers/chat-task-utils.ts +0 -105
  147. package/src/extensions/nocobase-ai/providers/chat-transport.ts +0 -184
  148. package/src/extensions/nocobase-ai/providers/form-registry.tsx +0 -271
  149. package/src/extensions/nocobase-ai/providers/frontend-tool-registry.tsx +0 -231
  150. package/src/extensions/nocobase-ai/providers/index.ts +0 -61
  151. package/src/extensions/nocobase-ai/providers/model.ts +0 -32
  152. package/src/extensions/nocobase-ai/providers/page-context.tsx +0 -136
  153. package/src/extensions/nocobase-ai/providers/stream-coalescer.ts +0 -60
  154. package/src/extensions/nocobase-ai/providers/stream-event-utils.ts +0 -82
  155. package/src/extensions/nocobase-ai/providers/stream-parser.ts +0 -61
  156. package/src/extensions/nocobase-ai/providers/sub-agent-stream.ts +0 -316
  157. package/src/extensions/nocobase-ai/providers/types.ts +0 -183
  158. package/src/extensions/nocobase-ai/providers/ui-message-stream.ts +0 -463
  159. package/src/extensions/nocobase-ai/providers/use-automatic-tool-approval.ts +0 -94
  160. package/src/extensions/nocobase-ai/providers/use-chat-attachments.ts +0 -206
  161. package/src/extensions/nocobase-ai/providers/use-chat-message-actions.ts +0 -554
  162. package/src/extensions/nocobase-ai/providers/use-chat-runtime.ts +0 -210
  163. package/src/extensions/nocobase-ai/providers/use-chat-work-context.ts +0 -93
  164. package/src/extensions/nocobase-ai/providers/use-conversation-catalog.ts +0 -108
  165. package/src/extensions/nocobase-ai/providers/use-conversation-history.ts +0 -178
  166. package/src/extensions/nocobase-ai/services/index.ts +0 -7
  167. package/src/extensions/nocobase-ai/services/nocobase-ai-service.ts +0 -454
  168. package/src/extensions/nocobase-ai/services/types.ts +0 -82
  169. /package/{registry/nocobase-i18n → src/providers/i18n}/i18n-provider.ts +0 -0
  170. /package/{registry/nocobase-i18n → src/providers/i18n}/locale-store.ts +0 -0
@@ -1,67 +0,0 @@
1
- import { cn } from "@/lib/utils";
2
- import { Bot, ChevronDown, LoaderCircle } from "lucide-react";
3
- import { useState } from "react";
4
- import { useAI } from "../../providers";
5
- import { AIEmployeeAvatar } from "../chat/ai-employee-avatar";
6
- import { getNocoBaseToolCallMetadata } from "../chat/tool-call-card";
7
- import type { AIToolRendererProps } from "./tool-renderer-provider";
8
- import { asRecord, asString } from "./tool-renderer-utils";
9
-
10
- export function SubAgentRenderer({ part }: AIToolRendererProps) {
11
- const { employees } = useAI();
12
- const input = asRecord(part.input);
13
- const username = asString(input.username);
14
- const employee = employees.find((item) => item.username === username);
15
- const fallbackName = username
16
- ? `${username.charAt(0).toUpperCase()}${username.slice(1)}`
17
- : "AI employee";
18
- const [expanded, setExpanded] = useState(false);
19
- const question = asString(input.question);
20
- const metadata = getNocoBaseToolCallMetadata(part);
21
- const generating =
22
- part.state !== "output-available" &&
23
- part.state !== "output-error" &&
24
- !["done", "confirmed"].includes(metadata?.invokeStatus ?? "");
25
-
26
- return (
27
- <button
28
- type="button"
29
- className="w-full rounded-lg bg-muted/50 p-2.5 text-left"
30
- onClick={() => question && setExpanded((value) => !value)}
31
- >
32
- <div className="flex items-center gap-2.5">
33
- {employee ? (
34
- <AIEmployeeAvatar employee={employee} className="size-8" />
35
- ) : (
36
- <span className="flex size-8 items-center justify-center rounded-full border bg-background">
37
- <Bot className="size-4" />
38
- </span>
39
- )}
40
- <div className="min-w-0 flex-1">
41
- <div className="truncate text-sm font-medium">
42
- @{employee?.nickname || fallbackName}
43
- </div>
44
- <div className="truncate text-xs text-muted-foreground">
45
- {employee?.position || "Working on a delegated task"}
46
- </div>
47
- </div>
48
- {question ? (
49
- <ChevronDown
50
- className={cn(
51
- "size-4 text-muted-foreground transition-transform",
52
- expanded && "rotate-180"
53
- )}
54
- />
55
- ) : null}
56
- {generating ? (
57
- <LoaderCircle className="size-4 shrink-0 animate-spin text-muted-foreground" />
58
- ) : null}
59
- </div>
60
- {expanded ? (
61
- <p className="mt-2 border-t pt-2 text-xs leading-5 text-muted-foreground">
62
- {question}
63
- </p>
64
- ) : null}
65
- </button>
66
- );
67
- }
@@ -1,59 +0,0 @@
1
- import { Button } from "@/components/ui/button";
2
- import { cn } from "@/lib/utils";
3
- import { useState } from "react";
4
- import { getNocoBaseToolCallMetadata } from "../chat/tool-call-card";
5
- import type { AIToolRendererProps } from "./tool-renderer-provider";
6
- import { asRecord, parseArray } from "./tool-renderer-utils";
7
-
8
- export function SuggestionsRenderer({
9
- part,
10
- disabled,
11
- onEdit,
12
- }: AIToolRendererProps) {
13
- const input = asRecord(part.input);
14
- const metadata = getNocoBaseToolCallMetadata(part);
15
- const options = parseArray(input.options).filter(
16
- (option): option is string => typeof option === "string"
17
- );
18
- const [selected, setSelected] = useState<string>();
19
- const persistedSelection = metadata?.selectedSuggestion;
20
- const hasSelected =
21
- selected !== undefined || persistedSelection !== undefined;
22
- const canSelect =
23
- metadata?.invokeStatus === undefined ||
24
- metadata.invokeStatus === "interrupted";
25
-
26
- if (!options.length) {
27
- return (
28
- <p className="text-xs text-muted-foreground">Generating suggestions…</p>
29
- );
30
- }
31
-
32
- return (
33
- <div className="flex flex-wrap gap-2">
34
- {options.map((option) => (
35
- <Button
36
- key={option}
37
- variant="outline"
38
- size="sm"
39
- className={cn(
40
- "h-auto min-h-8 whitespace-normal text-left",
41
- (selected === option || persistedSelection === option) &&
42
- "border-2 border-dashed bg-muted"
43
- )}
44
- disabled={disabled || hasSelected || !canSelect}
45
- onClick={() => {
46
- setSelected(option);
47
- void Promise.resolve(onEdit({ ...input, option })).catch(() => {
48
- setSelected((current) =>
49
- current === option ? undefined : current
50
- );
51
- });
52
- }}
53
- >
54
- {option}
55
- </Button>
56
- ))}
57
- </div>
58
- );
59
- }
@@ -1,55 +0,0 @@
1
- import {
2
- createContext,
3
- useContext,
4
- useMemo,
5
- type ComponentType,
6
- type PropsWithChildren,
7
- } from "react";
8
- import type { ToolCallPart } from "../chat/tool-call-card";
9
- import { builtInToolRenderers } from "./builtin-tool-renderers";
10
- import { BusinessReportDialogProvider } from "./business-report-dialog";
11
-
12
- export type AIToolRendererProps = {
13
- part: ToolCallPart;
14
- disabled: boolean;
15
- onEdit: (input: unknown) => void | Promise<void>;
16
- onApprove: () => void | Promise<void>;
17
- onReject: (message?: string) => void | Promise<void>;
18
- onRevise: () => void;
19
- };
20
-
21
- export type AIToolRenderer = ComponentType<AIToolRendererProps>;
22
- export type AIToolRendererDefinition = {
23
- component: AIToolRenderer;
24
- handlesApproval?: boolean;
25
- standalone?: boolean;
26
- };
27
- export type AIToolRendererEntry = AIToolRenderer | AIToolRendererDefinition;
28
- export type AIToolRendererMap = Record<string, AIToolRendererEntry>;
29
-
30
- const AIToolRendererContext =
31
- createContext<AIToolRendererMap>(builtInToolRenderers);
32
-
33
- export function AIToolRendererProvider({
34
- renderers,
35
- children,
36
- }: PropsWithChildren<{ renderers?: AIToolRendererMap }>) {
37
- const value = useMemo(
38
- () => ({ ...builtInToolRenderers, ...renderers }),
39
- [renderers]
40
- );
41
-
42
- return (
43
- <BusinessReportDialogProvider>
44
- <AIToolRendererContext.Provider value={value}>
45
- {children}
46
- </AIToolRendererContext.Provider>
47
- </BusinessReportDialogProvider>
48
- );
49
- }
50
-
51
- export function useAIToolRenderer(toolName: string) {
52
- const entry = useContext(AIToolRendererContext)[toolName];
53
- if (!entry) return undefined;
54
- return typeof entry === "function" ? { component: entry } : entry;
55
- }
@@ -1,18 +0,0 @@
1
- export const asRecord = (value: unknown): Record<string, unknown> =>
2
- value && typeof value === "object" && !Array.isArray(value)
3
- ? (value as Record<string, unknown>)
4
- : {};
5
-
6
- export const asString = (value: unknown) =>
7
- typeof value === "string" ? value : "";
8
-
9
- export const parseArray = (value: unknown): unknown[] => {
10
- if (Array.isArray(value)) return value;
11
- if (typeof value !== "string") return [];
12
- try {
13
- const parsed = JSON.parse(value) as unknown;
14
- return Array.isArray(parsed) ? parsed : [];
15
- } catch {
16
- return [];
17
- }
18
- };
@@ -1,108 +0,0 @@
1
- import { Button } from "@/components/ui/button";
2
- import { CheckCircle2, GitBranch } from "lucide-react";
3
- import { useState } from "react";
4
- import { MarkdownMessage } from "../chat/markdown-message";
5
- import { getNocoBaseToolCallMetadata } from "../chat/tool-call-card";
6
- import type { AIToolRendererProps } from "./tool-renderer-provider";
7
- import { asRecord, asString } from "./tool-renderer-utils";
8
-
9
- export function WorkflowRenderer({
10
- part,
11
- disabled,
12
- onApprove,
13
- onReject,
14
- onRevise,
15
- }: AIToolRendererProps) {
16
- const input = asRecord(part.input);
17
- const metadata = getNocoBaseToolCallMetadata(part);
18
- const entries = Object.entries(asRecord(input.result));
19
- const [action, setAction] = useState<"approve" | "reject" | "revise">();
20
- const [decided, setDecided] = useState(false);
21
- const canDecide =
22
- metadata?.invokeStatus === undefined ||
23
- metadata.invokeStatus === "interrupted";
24
- const actionDisabled =
25
- disabled || action !== undefined || decided || !canDecide;
26
-
27
- const runAction = async (
28
- nextAction: "approve" | "reject",
29
- callback: () => void | Promise<void>
30
- ) => {
31
- setAction(nextAction);
32
- try {
33
- await callback();
34
- setDecided(true);
35
- } finally {
36
- setAction(undefined);
37
- }
38
- };
39
-
40
- return (
41
- <div className="rounded-lg border bg-background p-3">
42
- <div className="flex items-center gap-2 text-sm font-medium">
43
- <GitBranch className="size-4" />
44
- {asString(input.workflowTitle) || "Workflow task"}
45
- </div>
46
- <div className="mt-3 grid gap-2 sm:grid-cols-2">
47
- {entries.map(([key, value]) => (
48
- <div key={key} className="rounded-md bg-muted/40 px-2.5 py-2">
49
- <div className="text-[11px] uppercase tracking-wide text-muted-foreground">
50
- {key}
51
- </div>
52
- <div className="mt-0.5 overflow-x-auto text-xs font-medium">
53
- {typeof value === "string" ||
54
- typeof value === "number" ||
55
- typeof value === "boolean" ? (
56
- <MarkdownMessage>{String(value)}</MarkdownMessage>
57
- ) : (
58
- <pre className="whitespace-pre-wrap break-words font-mono text-[11px] leading-5">
59
- {JSON.stringify(value, null, 2)}
60
- </pre>
61
- )}
62
- </div>
63
- </div>
64
- ))}
65
- </div>
66
- {!entries.length ? (
67
- <div className="mt-2 flex items-center gap-2 text-xs text-muted-foreground">
68
- <CheckCircle2 className="size-3.5" /> Ready for review
69
- </div>
70
- ) : null}
71
- <div className="mt-3 flex flex-wrap justify-end gap-2 border-t pt-3">
72
- <Button
73
- variant="ghost"
74
- size="sm"
75
- disabled={actionDisabled}
76
- onClick={() =>
77
- void runAction("reject", () =>
78
- onReject(
79
- "The user rejected this workflow node. Stop. Do not continue, do not reply about the task result, and do not call this tool again. Only state that you understand."
80
- )
81
- )
82
- }
83
- >
84
- {action === "reject" ? "Rejecting…" : "Reject"}
85
- </Button>
86
- <Button
87
- variant="outline"
88
- size="sm"
89
- disabled={actionDisabled}
90
- onClick={() => {
91
- setAction("revise");
92
- onRevise();
93
- setAction(undefined);
94
- }}
95
- >
96
- Revise
97
- </Button>
98
- <Button
99
- size="sm"
100
- disabled={actionDisabled}
101
- onClick={() => void runAction("approve", onApprove)}
102
- >
103
- {action === "approve" ? "Approving…" : "Approve"}
104
- </Button>
105
- </div>
106
- </div>
107
- );
108
- }
@@ -1,73 +0,0 @@
1
- import nocobaseAIChatIcon from "../../assets/nocobase-ai-chat.svg";
2
- import { cn } from "@/lib/utils";
3
- import {
4
- useAI,
5
- useAIChatControllerState,
6
- useGlobalAIChatController,
7
- } from "../../providers";
8
- import type { AIChatController } from "../../providers";
9
-
10
- export type AIChatFloatingTriggerProps = {
11
- aiEmployee?: string;
12
- controller?: AIChatController;
13
- unreadCount?: number;
14
- position?: "fixed" | "absolute";
15
- hideWhenOpen?: boolean;
16
- className?: string;
17
- };
18
-
19
- export function AIChatFloatingTrigger({
20
- aiEmployee,
21
- controller: providedController,
22
- unreadCount = 0,
23
- position = "fixed",
24
- hideWhenOpen = true,
25
- className,
26
- }: AIChatFloatingTriggerProps) {
27
- const ai = useAI();
28
- const globalController = useGlobalAIChatController();
29
- const controller = providedController ?? globalController;
30
- const { open } = useAIChatControllerState(controller);
31
-
32
- if (hideWhenOpen && open) return null;
33
-
34
- const openChat = () => {
35
- const employee = aiEmployee ?? ai.employees[0]?.username;
36
- if (employee) {
37
- controller.triggerTask({ aiEmployee: employee, open: true });
38
- return;
39
- }
40
- controller.open();
41
- };
42
-
43
- return (
44
- <button
45
- type="button"
46
- aria-label="Open AI chat"
47
- className={cn(
48
- "group/ai-floating z-40 flex items-center rounded-l-full border bg-background py-2.5 pr-5 pl-3 shadow-lg transition-[transform,opacity,box-shadow] duration-300 hover:translate-x-0 hover:opacity-100 hover:shadow-xl focus-visible:translate-x-0 focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none",
49
- position === "fixed"
50
- ? "fixed right-0 bottom-10"
51
- : "absolute right-0 bottom-8",
52
- "translate-x-2 opacity-80",
53
- className
54
- )}
55
- onClick={openChat}
56
- >
57
- <span className="relative flex size-[42px] items-center justify-center">
58
- <span className="flex size-full overflow-hidden rounded-lg">
59
- <img
60
- src={nocobaseAIChatIcon}
61
- alt=""
62
- className="size-full object-contain"
63
- />
64
- </span>
65
- {unreadCount > 0 ? (
66
- <span className="absolute -top-1.5 -right-1.5 z-10 flex min-w-4 items-center justify-center rounded-full bg-destructive px-1 text-[10px] leading-4 font-semibold text-white ring-2 ring-background">
67
- {unreadCount > 99 ? "99+" : unreadCount}
68
- </span>
69
- ) : null}
70
- </span>
71
- </button>
72
- );
73
- }
@@ -1,149 +0,0 @@
1
- import {
2
- HoverCard,
3
- HoverCardContent,
4
- HoverCardTrigger,
5
- } from "@/components/ui/hover-card";
6
- import { Button } from "@/components/ui/button";
7
- import { cn } from "@/lib/utils";
8
- import {
9
- useAI,
10
- useAIPageContextScope,
11
- useGlobalAIChatController,
12
- type AIChatController,
13
- type AIEmployee,
14
- type AIEmployeeTask,
15
- type AIWorkContextItem,
16
- } from "../../providers";
17
- import { AIEmployeeAvatar } from "../chat/ai-employee-avatar";
18
- import { Send, TextCursorInput } from "lucide-react";
19
- import { useState } from "react";
20
-
21
- export type AIEmployeeShortcutProps = {
22
- aiEmployee: string | AIEmployee;
23
- tasks?: AIEmployeeTask[];
24
- context?: AIWorkContextItem[];
25
- target?: AIChatController;
26
- auto?: boolean;
27
- size?: number;
28
- label?: string;
29
- showNotice?: boolean;
30
- className?: string;
31
- onTrigger?: (task?: AIEmployeeTask) => void;
32
- };
33
-
34
- export function AIEmployeeShortcut({
35
- aiEmployee,
36
- tasks = [],
37
- context,
38
- target,
39
- auto,
40
- size = 48,
41
- label,
42
- showNotice = false,
43
- className,
44
- onTrigger,
45
- }: AIEmployeeShortcutProps) {
46
- const ai = useAI();
47
- const [focused, setFocused] = useState(false);
48
- const globalController = useGlobalAIChatController();
49
- const inheritedContext = useAIPageContextScope();
50
- const controller = target ?? globalController;
51
- const employee =
52
- typeof aiEmployee === "string"
53
- ? ai.employees.find((item) => item.username === aiEmployee)
54
- : ai.employees.find((item) => item.username === aiEmployee.username) ??
55
- aiEmployee;
56
-
57
- if (!employee) return null;
58
-
59
- const trigger = (task?: AIEmployeeTask) => {
60
- onTrigger?.(task);
61
- controller.triggerTask({
62
- aiEmployee: employee,
63
- task,
64
- tasks: task ? undefined : tasks,
65
- context: context?.length ? context : inheritedContext,
66
- auto,
67
- open: true,
68
- });
69
- };
70
-
71
- const visibleTasks = tasks.filter((task) => task.title);
72
-
73
- return (
74
- <HoverCard>
75
- <HoverCardTrigger
76
- render={
77
- <button
78
- type="button"
79
- className={cn(
80
- "group/ai-shortcut relative inline-flex items-center gap-2 rounded-full outline-none transition-transform hover:scale-[1.04] focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
81
- label && "border bg-background pr-3 shadow-sm",
82
- className
83
- )}
84
- onClick={() => trigger()}
85
- onMouseEnter={() => setFocused(true)}
86
- onMouseLeave={() => setFocused(false)}
87
- />
88
- }
89
- >
90
- <AIEmployeeAvatar
91
- employee={employee}
92
- flip={focused || showNotice}
93
- className="transition-[filter] group-hover/ai-shortcut:brightness-105"
94
- style={{ width: size, height: size }}
95
- />
96
- {label ? <span className="text-sm font-medium">{label}</span> : null}
97
- {showNotice ? (
98
- <span className="absolute top-0 right-0 size-2.5 rounded-full border-2 border-background bg-foreground" />
99
- ) : null}
100
- </HoverCardTrigger>
101
- <HoverCardContent side="top" align="start" className="w-72 p-3">
102
- <div className="flex items-center gap-3">
103
- <AIEmployeeAvatar employee={employee} className="size-10" />
104
- <div className="min-w-0">
105
- <div className="truncate text-sm font-medium">
106
- {employee.nickname}
107
- </div>
108
- {employee.position ? (
109
- <div className="truncate text-xs text-muted-foreground">
110
- {employee.position}
111
- </div>
112
- ) : null}
113
- </div>
114
- </div>
115
- {employee.bio ?? employee.description ? (
116
- <p className="mt-3 border-t pt-3 text-xs leading-5 text-muted-foreground">
117
- {employee.bio ?? employee.description}
118
- </p>
119
- ) : null}
120
- {visibleTasks.length ? (
121
- <div className="mt-3 flex flex-wrap gap-1.5 border-t pt-3">
122
- {visibleTasks.map((task, index) => (
123
- <Button
124
- key={`${task.title}-${index}`}
125
- variant="secondary"
126
- size="sm"
127
- className="h-auto min-h-7 whitespace-normal py-1 text-left"
128
- onClick={(event) => {
129
- event.stopPropagation();
130
- trigger(task);
131
- }}
132
- >
133
- {task.autoSend ? (
134
- <Send className="size-3.5" />
135
- ) : (
136
- <TextCursorInput className="size-3.5" />
137
- )}
138
- <span>{task.title}</span>
139
- <span className="text-[10px] text-muted-foreground">
140
- {task.autoSend ? "Auto send" : "Fill composer"}
141
- </span>
142
- </Button>
143
- ))}
144
- </div>
145
- ) : null}
146
- </HoverCardContent>
147
- </HoverCard>
148
- );
149
- }
@@ -1,49 +0,0 @@
1
- import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
2
- import { Badge } from "@/components/ui/badge";
3
- import { LoadingState } from "@/components/app-shell/loading-state";
4
- import { useAI } from "../providers";
5
- import { CircleAlert } from "lucide-react";
6
- import type { ReactNode } from "react";
7
-
8
- export function AIConfigurationGate({ children }: { children: ReactNode }) {
9
- const {
10
- configurationStatus,
11
- configurationError,
12
- modelConfigurationError,
13
- hasEnabledModels,
14
- } = useAI();
15
-
16
- if (configurationStatus === "ready" && hasEnabledModels) return children;
17
-
18
- const loading = configurationStatus === "loading";
19
- const error = configurationError ?? modelConfigurationError;
20
-
21
- if (loading) return <LoadingState className="min-h-80" />;
22
-
23
- return (
24
- <div className="space-y-6 pb-12">
25
- <section className="border-b pb-8">
26
- <div className="flex items-center gap-2">
27
- <Badge variant="secondary">AI Components</Badge>
28
- <Badge variant="outline">Live API</Badge>
29
- </div>
30
- <h1 className="mt-4 text-3xl font-semibold tracking-[-0.035em]">
31
- NocoBase AI
32
- </h1>
33
- <p className="mt-3 max-w-3xl text-sm leading-6 text-muted-foreground">
34
- Conversations use the AI employees and enabled models configured in
35
- the connected NocoBase application.
36
- </p>
37
- </section>
38
-
39
- <Alert variant="destructive">
40
- <CircleAlert />
41
- <AlertTitle>NocoBase AI is not available</AlertTitle>
42
- <AlertDescription>
43
- {error?.message ??
44
- "Check the NocoBase connection and the AI employees available to the current user."}
45
- </AlertDescription>
46
- </Alert>
47
- </div>
48
- );
49
- }