@nocobase/portal-template-default 1.0.9 → 1.0.10
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/.env.example +5 -0
- package/.github/workflows/publish.yml +106 -0
- package/.gitignore +33 -0
- package/AGENTS.md +19 -0
- package/README.MD +61 -180
- package/package.json +3 -5
- package/scripts/build-html.mjs +12 -0
- package/src/App.css +2 -0
- package/src/App.tsx +35 -186
- package/src/app/development.tsx +154 -0
- package/src/app/extension.ts +5 -0
- package/src/app/extensions.tsx +18 -4
- package/src/app/routes.tsx +65 -0
- package/src/components/access-control/acl-bootstrap.tsx +11 -3
- package/src/components/access-control/acl-gate.tsx +7 -6
- package/src/components/access-control/can-access.tsx +17 -0
- package/src/components/access-control/navigate-to-accessible-resource.tsx +8 -3
- package/src/components/app-shell/sidebar.tsx +21 -5
- package/src/components/auth/auto-redirect-provider.tsx +2 -1
- package/src/extensions/nocobase-acl/README.md +74 -0
- package/{registry → src/extensions}/nocobase-acl/components/acl-boundary.tsx +22 -25
- package/{registry → src/extensions}/nocobase-acl/components/role-switcher.tsx +26 -23
- package/src/extensions/nocobase-acl/demo/acl-preview-provider.tsx +81 -0
- package/{registry → src/extensions}/nocobase-acl/demo/boundary-api.tsx +58 -0
- package/{registry → src/extensions}/nocobase-acl/demo/index.tsx +243 -105
- package/{registry → src/extensions}/nocobase-acl/demo/prompt-generator.tsx +3 -1
- package/src/extensions/nocobase-acl/extension.tsx +73 -0
- package/{registry → src/extensions}/nocobase-ai/README.md +21 -5
- package/{registry → src/extensions}/nocobase-ai/components/chat/chat-composer.tsx +3 -1
- package/{registry → src/extensions}/nocobase-ai/components/chat/chat-messages.tsx +38 -3
- package/{registry → src/extensions}/nocobase-ai/components/chat/tool-call-card.tsx +1 -9
- package/{registry → src/extensions}/nocobase-ai/components/index.ts +5 -0
- package/src/extensions/nocobase-ai/components/surfaces/chat-dialog.tsx +17 -0
- package/src/extensions/nocobase-ai/components/surfaces/chat-side-panel.tsx +35 -0
- package/src/extensions/nocobase-ai/components/surfaces/chat-surface.tsx +108 -0
- package/{registry → src/extensions}/nocobase-ai/components/triggers/ai-chat-floating-trigger.tsx +2 -2
- package/{registry → src/extensions}/nocobase-ai/demo/configuration-gate.tsx +4 -1
- package/{registry → src/extensions}/nocobase-ai/demo/container-showcase.tsx +2 -2
- package/{registry → src/extensions}/nocobase-ai/demo/floating.tsx +45 -15
- package/{registry → src/extensions}/nocobase-ai/demo/index.tsx +116 -74
- package/{registry → src/extensions}/nocobase-ai/demo/prompt-generator.tsx +3 -3
- package/{registry → src/extensions}/nocobase-ai/demo/tool-cards.tsx +2 -3
- package/src/extensions/nocobase-ai/extension.tsx +160 -0
- package/{registry → src/extensions}/nocobase-ai/global-ai-chat.tsx +12 -28
- package/{registry → src/extensions}/nocobase-ai/providers/ai-provider.tsx +14 -1
- package/{registry → src/extensions}/nocobase-ai/providers/chat-context.tsx +1 -0
- package/{registry → src/extensions}/nocobase-ai/providers/chat-provider.tsx +29 -8
- package/{registry → src/extensions}/nocobase-ai/providers/use-chat-attachments.ts +6 -1
- package/{registry → src/extensions}/nocobase-ai/providers/use-chat-work-context.ts +4 -1
- package/{registry → src/extensions}/nocobase-route-surfaces/README.md +2 -1
- package/{registry → src/extensions}/nocobase-route-surfaces/demo/index.tsx +33 -7
- package/{registry → src/extensions}/nocobase-route-surfaces/demo/scenarios.ts +20 -14
- package/{registry → src/extensions}/nocobase-route-surfaces/extension.tsx +55 -52
- package/src/{pages/users → extensions/nocobase-users-example}/create.tsx +4 -4
- package/src/{pages/users → extensions/nocobase-users-example}/edit.tsx +5 -9
- package/src/extensions/nocobase-users-example/extension.tsx +109 -0
- package/src/{pages/users → extensions/nocobase-users-example}/form-context.ts +11 -9
- package/src/extensions/nocobase-users-example/layout.tsx +16 -0
- package/src/{pages/users → extensions/nocobase-users-example}/list.tsx +1 -1
- package/src/extensions/nocobase-users-example/locales/en-US.ts +77 -0
- package/src/extensions/nocobase-users-example/locales/index.ts +8 -0
- package/src/extensions/nocobase-users-example/locales/zh-CN.ts +73 -0
- package/src/extensions/nocobase-users-example/optional-ai.ts +67 -0
- package/src/{pages/users → extensions/nocobase-users-example}/role-detail.tsx +4 -15
- package/src/{pages/users → extensions/nocobase-users-example}/routes.ts +1 -4
- package/src/extensions/nocobase-users-example/show.tsx +266 -0
- package/src/lib/nocobase/acl/context.ts +32 -0
- package/src/lib/nocobase/acl/evaluator.ts +186 -0
- package/src/lib/nocobase/acl/hooks.ts +40 -0
- package/src/lib/nocobase/acl/index.ts +4 -1
- package/src/lib/nocobase/acl/menu.ts +6 -6
- package/src/lib/nocobase/acl/record-permissions.ts +37 -7
- package/src/lib/nocobase/acl/store-provider.tsx +14 -0
- package/src/lib/nocobase/acl/store.ts +92 -86
- package/src/lib/nocobase/acl/types.ts +52 -32
- package/src/lib/nocobase/auth-session.ts +128 -0
- package/src/lib/nocobase/client.ts +48 -50
- package/src/locales/en-US.ts +0 -78
- package/src/locales/index.ts +2 -7
- package/src/locales/zh-CN.ts +0 -74
- package/src/providers/access-control.ts +43 -8
- package/src/providers/auth-callback.ts +1 -0
- package/src/providers/auth.ts +24 -26
- package/src/providers/constants.ts +0 -4
- package/src/providers/data.ts +14 -26
- package/src/providers/runtime-config.ts +40 -0
- package/src/vite-env.d.ts +9 -0
- package/tests/auth-session-regression.mjs +101 -0
- package/tsconfig.json +2 -1
- package/vite.config.ts +9 -1
- package/dist/assets/CoordinateSystem-C8P-Cleg.js +0 -1
- package/dist/assets/LegendVisualProvider-Bdgkckq-.js +0 -1
- package/dist/assets/LineDraw-C0m1GONT.js +0 -1
- package/dist/assets/SymbolDraw-CCospe5G.js +0 -1
- package/dist/assets/VisualMapping-Cbv1v7Cd.js +0 -1
- package/dist/assets/ai-employee-shortcut-B253WWow.js +0 -1
- package/dist/assets/calendar-B3dXXyIv.js +0 -1
- package/dist/assets/chat-inline-xpQ9iq4p.js +0 -1
- package/dist/assets/chat-side-panel-layout-BVWsp5rr.js +0 -1
- package/dist/assets/components-ChhJaNRc.js +0 -20
- package/dist/assets/configuration-gate-Dwdq_lxf.js +0 -1
- package/dist/assets/createClipPathFromCoordSys-uEftn1Mq.js +0 -1
- package/dist/assets/createSeriesDataSimply-CI7l7Ufv.js +0 -1
- package/dist/assets/customGraphicKeyframeAnimation-DE3jSfMd.js +0 -3
- package/dist/assets/echarts-preview-C-cNUtJF.js +0 -1
- package/dist/assets/echarts-runtime-CEgJbv68.js +0 -58
- package/dist/assets/echarts-runtime-advanced-Buhm2BHz.js +0 -1
- package/dist/assets/echarts-runtime-common-DLAdBAIa.js +0 -1
- package/dist/assets/echarts-runtime-components-ypnyLkYx.js +0 -1
- package/dist/assets/echarts-runtime-hierarchy-ClStquCF.js +0 -1
- package/dist/assets/floating-BarTmn-b.js +0 -13
- package/dist/assets/index-BJIcYTdc.js +0 -53
- package/dist/assets/index-C6xNKxpg.js +0 -656
- package/dist/assets/index-D4JQi1TM.js +0 -54
- package/dist/assets/index-YA7-AUO8.css +0 -1
- package/dist/assets/index-g8zWVpP9.js +0 -23
- package/dist/assets/labelHelper-C137W5gU.js +0 -1
- package/dist/assets/nocobase-ai-chat-VTMaZFQB.svg +0 -50
- package/dist/assets/page-context-Cxx11gzg.js +0 -73
- package/dist/assets/prompt-card-BsYMW_rq.js +0 -1
- package/dist/assets/sectorHelper-Fqgz3uEk.js +0 -1
- package/dist/assets/shortcut-xtghDP63.js +0 -47
- package/dist/assets/static.browser-CfQQzERe.js +0 -30
- package/dist/assets/switch-DrxImI8F.js +0 -1
- package/dist/assets/tool-cards-BGfsY8JF.js +0 -41
- package/dist/favicon.ico +0 -0
- package/dist/index.html +0 -40
- package/dist/index.raw.html +0 -46
- package/dist/logo-dark.png +0 -0
- package/dist/logo-mark-dark.png +0 -0
- package/dist/logo-mark.png +0 -0
- package/dist/logo.png +0 -0
- package/registry/nocobase-acl/README.md +0 -19
- package/registry/nocobase-acl/extension.tsx +0 -71
- package/registry/nocobase-acl/tests/acl-regression.mjs +0 -314
- package/registry/nocobase-ai/components/surfaces/chat-dialog.tsx +0 -31
- package/registry/nocobase-ai/components/surfaces/chat-side-panel.tsx +0 -74
- package/registry/nocobase-ai/extension.tsx +0 -158
- package/registry/nocobase-ai/tests/business-report-regression.mjs +0 -70
- package/registry/nocobase-ai/tests/form-filler-regression.mjs +0 -129
- package/registry/nocobase-ai/tests/frontend-tool-regression.mjs +0 -128
- package/registry/nocobase-ai/tests/page-context-regression.mjs +0 -48
- package/registry/nocobase-ai/tests/stream-regression.mjs +0 -207
- package/registry/nocobase-auth-cas/README.md +0 -6
- package/registry/nocobase-auth-cas/cas-sign-in-button.tsx +0 -41
- package/registry/nocobase-auth-cas/demo.tsx +0 -20
- package/registry/nocobase-auth-cas/extension.tsx +0 -46
- package/registry/nocobase-auth-cas/index.ts +0 -1
- package/registry/nocobase-auth-cas/use-cas-sign-in.ts +0 -29
- package/registry/nocobase-auth-dingtalk/README.md +0 -7
- package/registry/nocobase-auth-dingtalk/auto-login-provider.tsx +0 -154
- package/registry/nocobase-auth-dingtalk/demo.tsx +0 -23
- package/registry/nocobase-auth-dingtalk/dingtalk-sign-in-button.tsx +0 -45
- package/registry/nocobase-auth-dingtalk/extension.tsx +0 -55
- package/registry/nocobase-auth-dingtalk/index.ts +0 -4
- package/registry/nocobase-auth-dingtalk/use-dingtalk-sign-in.ts +0 -64
- package/registry/nocobase-auth-ldap/README.md +0 -6
- package/registry/nocobase-auth-ldap/demo.tsx +0 -21
- package/registry/nocobase-auth-ldap/extension.tsx +0 -46
- package/registry/nocobase-auth-ldap/index.ts +0 -1
- package/registry/nocobase-auth-ldap/ldap-sign-in-form.tsx +0 -69
- package/registry/nocobase-auth-ldap/use-ldap-sign-in.ts +0 -5
- package/registry/nocobase-auth-oidc/README.md +0 -11
- package/registry/nocobase-auth-oidc/auto-redirect-provider.tsx +0 -13
- package/registry/nocobase-auth-oidc/demo.tsx +0 -20
- package/registry/nocobase-auth-oidc/extension.tsx +0 -51
- package/registry/nocobase-auth-oidc/index.ts +0 -1
- package/registry/nocobase-auth-oidc/oidc-sign-in-button.tsx +0 -44
- package/registry/nocobase-auth-oidc/use-oidc-sign-in.ts +0 -58
- package/registry/nocobase-auth-saml/README.md +0 -6
- package/registry/nocobase-auth-saml/auto-redirect-provider.tsx +0 -13
- package/registry/nocobase-auth-saml/demo.tsx +0 -20
- package/registry/nocobase-auth-saml/extension.tsx +0 -51
- package/registry/nocobase-auth-saml/index.ts +0 -1
- package/registry/nocobase-auth-saml/saml-sign-in-button.tsx +0 -43
- package/registry/nocobase-auth-saml/use-saml-sign-in.ts +0 -51
- package/registry/nocobase-auth-sms/README.md +0 -7
- package/registry/nocobase-auth-sms/demo.tsx +0 -25
- package/registry/nocobase-auth-sms/extension.tsx +0 -46
- package/registry/nocobase-auth-sms/index.ts +0 -1
- package/registry/nocobase-auth-sms/sms-sign-in-form.tsx +0 -107
- package/registry/nocobase-auth-sms/use-sms-sign-in.ts +0 -88
- package/registry/nocobase-auth-wecom/README.md +0 -6
- package/registry/nocobase-auth-wecom/auto-login-provider.tsx +0 -21
- package/registry/nocobase-auth-wecom/demo.tsx +0 -23
- package/registry/nocobase-auth-wecom/extension.tsx +0 -49
- package/registry/nocobase-auth-wecom/index.ts +0 -1
- package/registry/nocobase-auth-wecom/use-wecom-sign-in.ts +0 -62
- package/registry/nocobase-auth-wecom/wecom-sign-in-button.tsx +0 -70
- package/registry/nocobase-i18n/README.md +0 -44
- package/registry/nocobase-i18n/components/index.ts +0 -1
- package/registry/nocobase-i18n/components/language-switcher.tsx +0 -138
- package/registry/nocobase-i18n/demo/index.tsx +0 -209
- package/registry/nocobase-i18n/demo/prompt-generator.tsx +0 -132
- package/registry/nocobase-i18n/extension.tsx +0 -45
- package/registry/nocobase-i18n/index.ts +0 -7
- package/registry/nocobase-i18n/locales/en-US.ts +0 -39
- package/registry/nocobase-i18n/locales/index.ts +0 -8
- package/registry/nocobase-i18n/locales/zh-CN.ts +0 -35
- package/registry/nocobase-i18n/provider.tsx +0 -44
- package/registry/nocobase-i18n/server-resources.ts +0 -166
- package/registry/nocobase-i18n/tests/i18n-regression.mjs +0 -51
- package/registry.config.json +0 -302
- package/scripts/registry.mjs +0 -164
- package/src/components/access-control/resource-access-guard.tsx +0 -21
- package/src/components/access-control/use-acl-runtime.ts +0 -28
- package/src/lib/nocobase/acl/action.ts +0 -147
- package/src/pages/users/layout.tsx +0 -15
- package/src/pages/users/show.tsx +0 -278
- /package/{registry → src/extensions}/nocobase-acl/components/index.ts +0 -0
- /package/{registry → src/extensions}/nocobase-acl/components/role-options.ts +0 -0
- /package/{registry → src/extensions}/nocobase-acl/demo/components.tsx +0 -0
- /package/{registry → src/extensions}/nocobase-acl/demo/lazy-route.tsx +0 -0
- /package/{registry → src/extensions}/nocobase-acl/demo/role-switcher-prompt-generator.tsx +0 -0
- /package/{registry → src/extensions}/nocobase-acl/demo/scenario-section.tsx +0 -0
- /package/{registry → src/extensions}/nocobase-acl/index.ts +0 -0
- /package/{registry → src/extensions}/nocobase-ai/adapters/react-hook-form.ts +0 -0
- /package/{registry → src/extensions}/nocobase-ai/assets/nocobase-ai-chat.svg +0 -0
- /package/{registry → src/extensions}/nocobase-ai/components/ai-root-provider.tsx +0 -0
- /package/{registry → src/extensions}/nocobase-ai/components/chat/ai-employee-avatar.tsx +0 -0
- /package/{registry → src/extensions}/nocobase-ai/components/chat/chat-attachment.tsx +0 -0
- /package/{registry → src/extensions}/nocobase-ai/components/chat/chat-compact.tsx +0 -0
- /package/{registry → src/extensions}/nocobase-ai/components/chat/chat-empty-state.tsx +0 -0
- /package/{registry → src/extensions}/nocobase-ai/components/chat/chat-header.tsx +0 -0
- /package/{registry → src/extensions}/nocobase-ai/components/chat/chat-history-dialog.tsx +0 -0
- /package/{registry → src/extensions}/nocobase-ai/components/chat/chat-message.tsx +0 -0
- /package/{registry → src/extensions}/nocobase-ai/components/chat/chat-window.tsx +0 -0
- /package/{registry → src/extensions}/nocobase-ai/components/chat/conversation-list.tsx +0 -0
- /package/{registry → src/extensions}/nocobase-ai/components/chat/markdown-message.tsx +0 -0
- /package/{registry → src/extensions}/nocobase-ai/components/chat/model-select-options.tsx +0 -0
- /package/{registry → src/extensions}/nocobase-ai/components/chat/reasoning-panel.tsx +0 -0
- /package/{registry → src/extensions}/nocobase-ai/components/chat/sub-agent-conversation.tsx +0 -0
- /package/{registry → src/extensions}/nocobase-ai/components/chat/user-prompt-editor.tsx +0 -0
- /package/{registry → src/extensions}/nocobase-ai/components/chat/work-context-chip.tsx +0 -0
- /package/{registry → src/extensions}/nocobase-ai/components/page-elements/ai-form.tsx +0 -0
- /package/{registry → src/extensions}/nocobase-ai/components/page-elements/page-element-provider.tsx +0 -0
- /package/{registry → src/extensions}/nocobase-ai/components/surfaces/chat-inline.tsx +0 -0
- /package/{registry → src/extensions}/nocobase-ai/components/surfaces/chat-page.tsx +0 -0
- /package/{registry → src/extensions}/nocobase-ai/components/surfaces/chat-side-panel-layout.tsx +0 -0
- /package/{registry → src/extensions}/nocobase-ai/components/surfaces/chat-surface-actions.tsx +0 -0
- /package/{registry → src/extensions}/nocobase-ai/components/tools/builtin-tool-renderers.tsx +0 -0
- /package/{registry → src/extensions}/nocobase-ai/components/tools/business-report-dialog.tsx +0 -0
- /package/{registry → src/extensions}/nocobase-ai/components/tools/business-report-renderer.tsx +0 -0
- /package/{registry → src/extensions}/nocobase-ai/components/tools/business-report-utils.tsx +0 -0
- /package/{registry → src/extensions}/nocobase-ai/components/tools/chart-renderer.tsx +0 -0
- /package/{registry → src/extensions}/nocobase-ai/components/tools/echarts-preview.tsx +0 -0
- /package/{registry → src/extensions}/nocobase-ai/components/tools/echarts-runtime-advanced.ts +0 -0
- /package/{registry → src/extensions}/nocobase-ai/components/tools/echarts-runtime-common.ts +0 -0
- /package/{registry → src/extensions}/nocobase-ai/components/tools/echarts-runtime-components.ts +0 -0
- /package/{registry → src/extensions}/nocobase-ai/components/tools/echarts-runtime-hierarchy.ts +0 -0
- /package/{registry → src/extensions}/nocobase-ai/components/tools/echarts-runtime.ts +0 -0
- /package/{registry → src/extensions}/nocobase-ai/components/tools/sub-agent-renderer.tsx +0 -0
- /package/{registry → src/extensions}/nocobase-ai/components/tools/suggestions-renderer.tsx +0 -0
- /package/{registry → src/extensions}/nocobase-ai/components/tools/tool-renderer-provider.tsx +0 -0
- /package/{registry → src/extensions}/nocobase-ai/components/tools/tool-renderer-utils.ts +0 -0
- /package/{registry → src/extensions}/nocobase-ai/components/tools/workflow-renderer.tsx +0 -0
- /package/{registry → src/extensions}/nocobase-ai/components/triggers/ai-employee-shortcut.tsx +0 -0
- /package/{registry → src/extensions}/nocobase-ai/demo/interaction-showcase.tsx +0 -0
- /package/{registry → src/extensions}/nocobase-ai/demo/page-context-prompt-generator.tsx +0 -0
- /package/{registry → src/extensions}/nocobase-ai/demo/page-context.tsx +0 -0
- /package/{registry → src/extensions}/nocobase-ai/demo/page-element-showcase.tsx +0 -0
- /package/{registry → src/extensions}/nocobase-ai/demo/prompt-card.tsx +0 -0
- /package/{registry → src/extensions}/nocobase-ai/demo/shortcut.tsx +0 -0
- /package/{registry → src/extensions}/nocobase-ai/index.ts +0 -0
- /package/{registry → src/extensions}/nocobase-ai/providers/avatars.ts +0 -0
- /package/{registry → src/extensions}/nocobase-ai/providers/chat-controller.ts +0 -0
- /package/{registry → src/extensions}/nocobase-ai/providers/chat-message-utils.ts +0 -0
- /package/{registry → src/extensions}/nocobase-ai/providers/chat-reducer.ts +0 -0
- /package/{registry → src/extensions}/nocobase-ai/providers/chat-task-utils.ts +0 -0
- /package/{registry → src/extensions}/nocobase-ai/providers/chat-transport.ts +0 -0
- /package/{registry → src/extensions}/nocobase-ai/providers/form-registry.tsx +0 -0
- /package/{registry → src/extensions}/nocobase-ai/providers/frontend-tool-registry.tsx +0 -0
- /package/{registry → src/extensions}/nocobase-ai/providers/index.ts +0 -0
- /package/{registry → src/extensions}/nocobase-ai/providers/model.ts +0 -0
- /package/{registry → src/extensions}/nocobase-ai/providers/page-context.tsx +0 -0
- /package/{registry → src/extensions}/nocobase-ai/providers/stream-coalescer.ts +0 -0
- /package/{registry → src/extensions}/nocobase-ai/providers/stream-event-utils.ts +0 -0
- /package/{registry → src/extensions}/nocobase-ai/providers/stream-parser.ts +0 -0
- /package/{registry → src/extensions}/nocobase-ai/providers/sub-agent-stream.ts +0 -0
- /package/{registry → src/extensions}/nocobase-ai/providers/types.ts +0 -0
- /package/{registry → src/extensions}/nocobase-ai/providers/ui-message-stream.ts +0 -0
- /package/{registry → src/extensions}/nocobase-ai/providers/use-automatic-tool-approval.ts +0 -0
- /package/{registry → src/extensions}/nocobase-ai/providers/use-chat-message-actions.ts +0 -0
- /package/{registry → src/extensions}/nocobase-ai/providers/use-chat-runtime.ts +0 -0
- /package/{registry → src/extensions}/nocobase-ai/providers/use-conversation-catalog.ts +0 -0
- /package/{registry → src/extensions}/nocobase-ai/providers/use-conversation-history.ts +0 -0
- /package/{registry → src/extensions}/nocobase-ai/services/index.ts +0 -0
- /package/{registry → src/extensions}/nocobase-ai/services/nocobase-ai-service.ts +0 -0
- /package/{registry → src/extensions}/nocobase-ai/services/types.ts +0 -0
- /package/{registry → src/extensions}/nocobase-route-surfaces/demo/lazy-route.tsx +0 -0
- /package/{registry → src/extensions}/nocobase-route-surfaces/demo/prompt-generator.tsx +0 -0
- /package/{registry → src/extensions}/nocobase-route-surfaces/index.ts +0 -0
- /package/{registry → src/extensions}/nocobase-route-surfaces/route-dialog.tsx +0 -0
- /package/{registry → src/extensions}/nocobase-route-surfaces/route-drawer.tsx +0 -0
- /package/{registry → src/extensions}/nocobase-route-surfaces/route-page.tsx +0 -0
- /package/{registry → src/extensions}/nocobase-route-surfaces/route-surface-context.ts +0 -0
- /package/{registry → src/extensions}/nocobase-route-surfaces/use-refine-unsaved-changes.tsx +0 -0
- /package/{registry → src/extensions}/nocobase-route-surfaces/use-route-surface-close.ts +0 -0
- /package/{registry → src/extensions}/nocobase-route-surfaces/use-route-surface-state.ts +0 -0
- /package/src/{pages/users → extensions/nocobase-users-example}/form-fields.tsx +0 -0
- /package/src/{pages/users → extensions/nocobase-users-example}/index.ts +0 -0
- /package/src/{pages/users → extensions/nocobase-users-example}/role-badges.tsx +0 -0
- /package/src/{pages/users → extensions/nocobase-users-example}/role-utils.ts +0 -0
- /package/src/{pages/users → extensions/nocobase-users-example}/types.ts +0 -0
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AIChatWindow,
|
|
3
|
-
|
|
3
|
+
ChatSurface,
|
|
4
4
|
ChatSurfaceActions,
|
|
5
|
-
ChatSidePanelLayout,
|
|
6
5
|
useAIPageElementPicker,
|
|
7
6
|
type AIChatComposerAction,
|
|
8
7
|
type AIChatWindowProps,
|
|
@@ -19,7 +18,7 @@ import {
|
|
|
19
18
|
} from "@/components/ui/table";
|
|
20
19
|
import { AIChatProvider, useAIChatBase } from "../providers";
|
|
21
20
|
import { Globe2, MousePointer2 } from "lucide-react";
|
|
22
|
-
import { useMemo, useState } from "react";
|
|
21
|
+
import { useMemo, useState, type CSSProperties } from "react";
|
|
23
22
|
import { ContainerShowcase, type ChatContainer } from "./container-showcase";
|
|
24
23
|
import { InteractionShowcase } from "./interaction-showcase";
|
|
25
24
|
import { AIConfigurationGate } from "./configuration-gate";
|
|
@@ -106,6 +105,46 @@ const propRows = [
|
|
|
106
105
|
],
|
|
107
106
|
];
|
|
108
107
|
|
|
108
|
+
const surfacePropRows = [
|
|
109
|
+
[
|
|
110
|
+
"variant",
|
|
111
|
+
'"side-panel" | "dialog"',
|
|
112
|
+
"required",
|
|
113
|
+
"Changes only the outer presentation while keeping the same chat window mounted.",
|
|
114
|
+
],
|
|
115
|
+
["open", "boolean", "required", "Controls whether the surface is open."],
|
|
116
|
+
[
|
|
117
|
+
"onOpenChange",
|
|
118
|
+
"(open: boolean) => void",
|
|
119
|
+
"required",
|
|
120
|
+
"Receives close requests from Escape, the dialog backdrop, or surface actions.",
|
|
121
|
+
],
|
|
122
|
+
[
|
|
123
|
+
"side",
|
|
124
|
+
'"left" | "right"',
|
|
125
|
+
'"right"',
|
|
126
|
+
"Chooses the side used by the side-panel variant.",
|
|
127
|
+
],
|
|
128
|
+
[
|
|
129
|
+
"width",
|
|
130
|
+
"number | string",
|
|
131
|
+
"450",
|
|
132
|
+
"Sets the width used by the side-panel variant.",
|
|
133
|
+
],
|
|
134
|
+
[
|
|
135
|
+
"closeOnEscape",
|
|
136
|
+
"boolean",
|
|
137
|
+
"true",
|
|
138
|
+
"Allows Escape to close the active surface.",
|
|
139
|
+
],
|
|
140
|
+
[
|
|
141
|
+
"showCloseHandle",
|
|
142
|
+
"boolean",
|
|
143
|
+
"false",
|
|
144
|
+
"Shows an outside close handle for the side-panel variant.",
|
|
145
|
+
],
|
|
146
|
+
];
|
|
147
|
+
|
|
109
148
|
export function AIChatPage() {
|
|
110
149
|
return (
|
|
111
150
|
<AIConfigurationGate>
|
|
@@ -118,8 +157,7 @@ export function AIChatPage() {
|
|
|
118
157
|
|
|
119
158
|
function AIChatPageContent() {
|
|
120
159
|
const [container, setContainer] = useState<ChatContainer>("embedded");
|
|
121
|
-
const [
|
|
122
|
-
const [dialogOpen, setDialogOpen] = useState(false);
|
|
160
|
+
const [surfaceOpen, setSurfaceOpen] = useState(false);
|
|
123
161
|
const [webSearch, setWebSearch] = useState(false);
|
|
124
162
|
const { id: chatId, addWorkContext, focusComposer } = useAIChatBase();
|
|
125
163
|
const { registeredCount, startPicking } = useAIPageElementPicker();
|
|
@@ -132,9 +170,7 @@ function AIChatPageContent() {
|
|
|
132
170
|
icon: <MousePointer2 />,
|
|
133
171
|
disabled: registeredCount === 0,
|
|
134
172
|
onClick: () => {
|
|
135
|
-
if (
|
|
136
|
-
setDialogOpen(false);
|
|
137
|
-
setSidePanelOpen(true);
|
|
173
|
+
if (surfaceOpen && container === "dialog") {
|
|
138
174
|
setContainer("side-panel");
|
|
139
175
|
}
|
|
140
176
|
startPicking({
|
|
@@ -159,10 +195,11 @@ function AIChatPageContent() {
|
|
|
159
195
|
[
|
|
160
196
|
addWorkContext,
|
|
161
197
|
chatId,
|
|
162
|
-
|
|
198
|
+
container,
|
|
163
199
|
focusComposer,
|
|
164
200
|
registeredCount,
|
|
165
201
|
startPicking,
|
|
202
|
+
surfaceOpen,
|
|
166
203
|
webSearch,
|
|
167
204
|
]
|
|
168
205
|
);
|
|
@@ -178,35 +215,16 @@ function AIChatPageContent() {
|
|
|
178
215
|
);
|
|
179
216
|
|
|
180
217
|
const closeSurface = () => {
|
|
181
|
-
|
|
182
|
-
setDialogOpen(false);
|
|
218
|
+
setSurfaceOpen(false);
|
|
183
219
|
};
|
|
184
220
|
|
|
185
|
-
const
|
|
221
|
+
const surfaceWindowProps: AIChatWindowProps = {
|
|
186
222
|
...windowProps,
|
|
187
223
|
headerActions: (
|
|
188
224
|
<ChatSurfaceActions
|
|
189
|
-
expanded={
|
|
225
|
+
expanded={container === "dialog"}
|
|
190
226
|
onExpandedChange={(expanded) => {
|
|
191
|
-
|
|
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");
|
|
227
|
+
setContainer(expanded ? "dialog" : "side-panel");
|
|
210
228
|
}}
|
|
211
229
|
onClose={closeSurface}
|
|
212
230
|
/>
|
|
@@ -215,17 +233,21 @@ function AIChatPageContent() {
|
|
|
215
233
|
|
|
216
234
|
const selectContainer = (nextContainer: ChatContainer) => {
|
|
217
235
|
setContainer(nextContainer);
|
|
218
|
-
|
|
219
|
-
|
|
236
|
+
setSurfaceOpen(
|
|
237
|
+
nextContainer === "side-panel" || nextContainer === "dialog"
|
|
238
|
+
);
|
|
220
239
|
};
|
|
221
240
|
|
|
222
241
|
return (
|
|
223
|
-
<
|
|
224
|
-
open={
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
242
|
+
<div
|
|
243
|
+
data-open={surfaceOpen && container === "side-panel"}
|
|
244
|
+
data-side="right"
|
|
245
|
+
className="chat-side-panel-layout @container min-w-0"
|
|
246
|
+
style={
|
|
247
|
+
{
|
|
248
|
+
"--chat-side-panel-width": "450px",
|
|
249
|
+
} as CSSProperties
|
|
250
|
+
}
|
|
229
251
|
>
|
|
230
252
|
<div className="space-y-14 pb-12">
|
|
231
253
|
<section className="flex flex-wrap items-start justify-between gap-5 border-b pb-8">
|
|
@@ -277,49 +299,69 @@ function AIChatPageContent() {
|
|
|
277
299
|
<PromptGenerator />
|
|
278
300
|
</section>
|
|
279
301
|
|
|
302
|
+
<section className="space-y-5">
|
|
303
|
+
<SectionTitle
|
|
304
|
+
eyebrow="Component API"
|
|
305
|
+
title="ChatSurface props"
|
|
306
|
+
description="Use variant as the single presentation switch. The child AIChatWindow remains the same React instance while the surface changes shape."
|
|
307
|
+
/>
|
|
308
|
+
<PropsTable rows={surfacePropRows} />
|
|
309
|
+
</section>
|
|
310
|
+
|
|
280
311
|
<section className="space-y-5">
|
|
281
312
|
<SectionTitle
|
|
282
313
|
eyebrow="Component API"
|
|
283
314
|
title="AIChatWindow props"
|
|
284
315
|
description="The core window stays reusable while business pages provide placement, composer actions, and tool-approval behavior."
|
|
285
316
|
/>
|
|
286
|
-
<
|
|
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>
|
|
317
|
+
<PropsTable rows={propRows} />
|
|
316
318
|
</section>
|
|
317
319
|
|
|
318
|
-
<ChatDialog open={dialogOpen} onOpenChange={setDialogOpen}>
|
|
319
|
-
<AIChatWindow {...dialogWindowProps} />
|
|
320
|
-
</ChatDialog>
|
|
321
320
|
</div>
|
|
322
|
-
|
|
321
|
+
<ChatSurface
|
|
322
|
+
open={surfaceOpen}
|
|
323
|
+
variant={container === "dialog" ? "dialog" : "side-panel"}
|
|
324
|
+
onOpenChange={setSurfaceOpen}
|
|
325
|
+
width={450}
|
|
326
|
+
>
|
|
327
|
+
<AIChatWindow {...surfaceWindowProps} />
|
|
328
|
+
</ChatSurface>
|
|
329
|
+
</div>
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function PropsTable({ rows }: { rows: string[][] }) {
|
|
334
|
+
return (
|
|
335
|
+
<Card className="gap-0 overflow-hidden py-0">
|
|
336
|
+
<Table>
|
|
337
|
+
<TableHeader>
|
|
338
|
+
<TableRow>
|
|
339
|
+
<TableHead>Prop</TableHead>
|
|
340
|
+
<TableHead>Type</TableHead>
|
|
341
|
+
<TableHead>Default</TableHead>
|
|
342
|
+
<TableHead>Description</TableHead>
|
|
343
|
+
</TableRow>
|
|
344
|
+
</TableHeader>
|
|
345
|
+
<TableBody>
|
|
346
|
+
{rows.map(([name, type, defaultValue, description]) => (
|
|
347
|
+
<TableRow key={name}>
|
|
348
|
+
<TableCell className="font-mono text-xs font-medium">
|
|
349
|
+
{name}
|
|
350
|
+
</TableCell>
|
|
351
|
+
<TableCell className="font-mono text-xs text-muted-foreground">
|
|
352
|
+
{type}
|
|
353
|
+
</TableCell>
|
|
354
|
+
<TableCell className="font-mono text-xs text-muted-foreground">
|
|
355
|
+
{defaultValue}
|
|
356
|
+
</TableCell>
|
|
357
|
+
<TableCell className="min-w-80 whitespace-normal text-muted-foreground">
|
|
358
|
+
{description}
|
|
359
|
+
</TableCell>
|
|
360
|
+
</TableRow>
|
|
361
|
+
))}
|
|
362
|
+
</TableBody>
|
|
363
|
+
</Table>
|
|
364
|
+
</Card>
|
|
323
365
|
);
|
|
324
366
|
}
|
|
325
367
|
|
|
@@ -141,9 +141,9 @@ function PromptGeneratorContent() {
|
|
|
141
141
|
embedded:
|
|
142
142
|
"Render ChatInline inside the target content region and keep its width fluid within the parent container.",
|
|
143
143
|
page: "Create a dedicated route and render ChatPage with AIChatWindow filling the available page content.",
|
|
144
|
-
"side-panel": `Use
|
|
144
|
+
"side-panel": `Use ChatSurface with variant="side-panel" on the right and a ${panelWidth}px width. Opening chat must push the page narrower instead of covering it on desktop; mobile may use an overlay.`,
|
|
145
145
|
dialog:
|
|
146
|
-
"Open
|
|
146
|
+
"Open ChatSurface with variant=\"dialog\" from a page action. Keep conversation state in AIProvider/AIChatProvider so closing the dialog does not discard it.",
|
|
147
147
|
mobile:
|
|
148
148
|
"Render ChatInline in the target region with a mobile-first width around 390px and preserve the same responsive AIChatWindow API.",
|
|
149
149
|
}[placement];
|
|
@@ -181,7 +181,7 @@ Implementation requirements:
|
|
|
181
181
|
- Wrap the application content with AIPageElementProvider when page-element picking is enabled.
|
|
182
182
|
- Register selectable React components with useAIPageElement and return serializable business context from getContext.
|
|
183
183
|
- Reuse AIChatWindow and the existing providers; do not duplicate message, streaming, reasoning, or tool-call state.
|
|
184
|
-
- When the right panel is expandable,
|
|
184
|
+
- When the right panel is expandable, render one AIChatWindow inside ChatSurface and change only its variant between "side-panel" and "dialog". Put ChatSurfaceActions in the window header.
|
|
185
185
|
- Keep the generic Tool Call shell responsible for status, ASK approval, errors, and disclosure. Register business-specific bodies through AIToolRendererProvider.
|
|
186
186
|
- Include the built-in specialized Tool Card renderers. This is part of the standard AI chat capability, not an optional page setting.
|
|
187
187
|
- Register browser-side tool implementations through AIProvider.toolInvokers. After all interrupted calls are approved, the provider executes these handlers and sends their results when resuming the NocoBase conversation.
|
|
@@ -402,9 +402,8 @@ Implementation requirements:
|
|
|
402
402
|
- Export any public renderer types or components from registry/nocobase-ai/components/index.ts when application code needs them.
|
|
403
403
|
|
|
404
404
|
Verification:
|
|
405
|
-
- Run pnpm
|
|
406
|
-
- Run
|
|
407
|
-
- Run the scoped ESLint check and pnpm registry:build.
|
|
405
|
+
- Run pnpm build from the Registry source repository.
|
|
406
|
+
- Run the relevant regression checks and a scoped ESLint check.
|
|
408
407
|
- Verify the card in light and dark themes, narrow chat panels, expanded dialogs, pending approval, completed, and error states.`;
|
|
409
408
|
|
|
410
409
|
return (
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { NocoBaseAIExtensionProvider } from "./global-ai-chat";
|
|
2
|
+
import type { AppExtension } from "@/app/extensions";
|
|
3
|
+
import { LoadingState } from "@/components/app-shell/loading-state";
|
|
4
|
+
import {
|
|
5
|
+
Bot,
|
|
6
|
+
MessageSquare,
|
|
7
|
+
MousePointer2,
|
|
8
|
+
PanelRight,
|
|
9
|
+
Sparkles,
|
|
10
|
+
Wrench,
|
|
11
|
+
} from "lucide-react";
|
|
12
|
+
import { lazy, Suspense, type ReactNode } from "react";
|
|
13
|
+
import { Outlet, Route } from "react-router";
|
|
14
|
+
|
|
15
|
+
const AIChatPage = lazy(() =>
|
|
16
|
+
import("./demo").then((module) => ({ default: module.AIChatPage }))
|
|
17
|
+
);
|
|
18
|
+
const FloatingChatPage = lazy(() =>
|
|
19
|
+
import("./demo/floating").then((module) => ({
|
|
20
|
+
default: module.FloatingChatPage,
|
|
21
|
+
}))
|
|
22
|
+
);
|
|
23
|
+
const ShortcutPage = lazy(() =>
|
|
24
|
+
import("./demo/shortcut").then((module) => ({
|
|
25
|
+
default: module.ShortcutPage,
|
|
26
|
+
}))
|
|
27
|
+
);
|
|
28
|
+
const PageContextPage = lazy(() =>
|
|
29
|
+
import("./demo/page-context").then((module) => ({
|
|
30
|
+
default: module.PageContextPage,
|
|
31
|
+
}))
|
|
32
|
+
);
|
|
33
|
+
const ToolCardsPage = lazy(() =>
|
|
34
|
+
import("./demo/tool-cards").then((module) => ({
|
|
35
|
+
default: module.ToolCardsPage,
|
|
36
|
+
}))
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
function LazyDemoRoute({ children }: { children: ReactNode }) {
|
|
40
|
+
return (
|
|
41
|
+
<Suspense fallback={<LoadingState className="min-h-[320px]" />}>
|
|
42
|
+
{children}
|
|
43
|
+
</Suspense>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const nocobaseAIExtension: AppExtension = {
|
|
48
|
+
id: "nocobase-ai",
|
|
49
|
+
Provider: NocoBaseAIExtensionProvider,
|
|
50
|
+
dev: {
|
|
51
|
+
resources: [
|
|
52
|
+
{
|
|
53
|
+
name: "ai-components",
|
|
54
|
+
meta: {
|
|
55
|
+
label: "AI Components",
|
|
56
|
+
icon: <Bot />,
|
|
57
|
+
acl: { type: "authenticated" },
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: "ai-chat-window",
|
|
62
|
+
list: "ai-chat",
|
|
63
|
+
meta: {
|
|
64
|
+
parent: "ai-components",
|
|
65
|
+
label: "Chat window",
|
|
66
|
+
icon: <MessageSquare />,
|
|
67
|
+
description:
|
|
68
|
+
"Build freely with AI while NocoBase keeps the application reliable.",
|
|
69
|
+
acl: { type: "authenticated" },
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: "ai-floating-chat",
|
|
74
|
+
list: "ai-chat/floating",
|
|
75
|
+
meta: {
|
|
76
|
+
parent: "ai-components",
|
|
77
|
+
label: "Floating chat",
|
|
78
|
+
icon: <PanelRight />,
|
|
79
|
+
acl: { type: "authenticated" },
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: "ai-employee-tasks",
|
|
84
|
+
list: "ai-chat/shortcut",
|
|
85
|
+
meta: {
|
|
86
|
+
parent: "ai-components",
|
|
87
|
+
label: "Employee tasks",
|
|
88
|
+
icon: <Sparkles />,
|
|
89
|
+
acl: { type: "authenticated" },
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: "ai-page-context",
|
|
94
|
+
list: "ai-chat/context",
|
|
95
|
+
meta: {
|
|
96
|
+
parent: "ai-components",
|
|
97
|
+
label: "Page context",
|
|
98
|
+
icon: <MousePointer2 />,
|
|
99
|
+
acl: { type: "authenticated" },
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
name: "ai-tool-cards",
|
|
104
|
+
list: "ai-chat/tools",
|
|
105
|
+
meta: {
|
|
106
|
+
parent: "ai-components",
|
|
107
|
+
label: "Tool cards",
|
|
108
|
+
icon: <Wrench />,
|
|
109
|
+
acl: { type: "authenticated" },
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
routes: (
|
|
114
|
+
<Route key="nocobase-ai" path="ai-chat" element={<Outlet />}>
|
|
115
|
+
<Route
|
|
116
|
+
index
|
|
117
|
+
element={
|
|
118
|
+
<LazyDemoRoute>
|
|
119
|
+
<AIChatPage />
|
|
120
|
+
</LazyDemoRoute>
|
|
121
|
+
}
|
|
122
|
+
/>
|
|
123
|
+
<Route
|
|
124
|
+
path="floating"
|
|
125
|
+
element={
|
|
126
|
+
<LazyDemoRoute>
|
|
127
|
+
<FloatingChatPage />
|
|
128
|
+
</LazyDemoRoute>
|
|
129
|
+
}
|
|
130
|
+
/>
|
|
131
|
+
<Route
|
|
132
|
+
path="shortcut"
|
|
133
|
+
element={
|
|
134
|
+
<LazyDemoRoute>
|
|
135
|
+
<ShortcutPage />
|
|
136
|
+
</LazyDemoRoute>
|
|
137
|
+
}
|
|
138
|
+
/>
|
|
139
|
+
<Route
|
|
140
|
+
path="context"
|
|
141
|
+
element={
|
|
142
|
+
<LazyDemoRoute>
|
|
143
|
+
<PageContextPage />
|
|
144
|
+
</LazyDemoRoute>
|
|
145
|
+
}
|
|
146
|
+
/>
|
|
147
|
+
<Route
|
|
148
|
+
path="tools"
|
|
149
|
+
element={
|
|
150
|
+
<LazyDemoRoute>
|
|
151
|
+
<ToolCardsPage />
|
|
152
|
+
</LazyDemoRoute>
|
|
153
|
+
}
|
|
154
|
+
/>
|
|
155
|
+
</Route>
|
|
156
|
+
),
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
export default nocobaseAIExtension;
|
|
@@ -14,8 +14,7 @@ import {
|
|
|
14
14
|
type AIChatComposerAction,
|
|
15
15
|
} from "./components";
|
|
16
16
|
import { AIChatWindow } from "./components/chat/chat-window";
|
|
17
|
-
import {
|
|
18
|
-
import { ChatSidePanel } from "./components/surfaces/chat-side-panel";
|
|
17
|
+
import { ChatSurface } from "./components/surfaces/chat-surface";
|
|
19
18
|
import { ChatSurfaceActions } from "./components/surfaces/chat-surface-actions";
|
|
20
19
|
import {
|
|
21
20
|
AIChatProvider,
|
|
@@ -51,8 +50,7 @@ function NocoBaseAIGlobalEntry({ children }: PropsWithChildren) {
|
|
|
51
50
|
const [expanded, setExpanded] = useState(false);
|
|
52
51
|
const ready =
|
|
53
52
|
ai.configurationStatus === "ready" &&
|
|
54
|
-
ai.employees.length > 0
|
|
55
|
-
ai.hasEnabledModels;
|
|
53
|
+
ai.employees.length > 0;
|
|
56
54
|
|
|
57
55
|
return (
|
|
58
56
|
<div
|
|
@@ -166,38 +164,24 @@ function StarterGlobalAIChat({
|
|
|
166
164
|
setExpanded(false);
|
|
167
165
|
onOpenChange(false);
|
|
168
166
|
};
|
|
167
|
+
const handleSurfaceOpenChange = (nextOpen: boolean) => {
|
|
168
|
+
if (!nextOpen) setExpanded(false);
|
|
169
|
+
onOpenChange(nextOpen);
|
|
170
|
+
};
|
|
169
171
|
|
|
170
172
|
return (
|
|
171
173
|
<>
|
|
172
|
-
<
|
|
173
|
-
open={open
|
|
174
|
-
|
|
174
|
+
<ChatSurface
|
|
175
|
+
open={open}
|
|
176
|
+
variant={expanded ? "dialog" : "side-panel"}
|
|
177
|
+
onOpenChange={handleSurfaceOpenChange}
|
|
175
178
|
width={DEFAULT_SIDE_PANEL_WIDTH}
|
|
176
179
|
showCloseHandle={false}
|
|
177
180
|
>
|
|
178
181
|
<AIChatWindow
|
|
179
182
|
headerActions={
|
|
180
183
|
<ChatSurfaceActions
|
|
181
|
-
expanded={
|
|
182
|
-
onExpandedChange={setExpanded}
|
|
183
|
-
onClose={closeChat}
|
|
184
|
-
/>
|
|
185
|
-
}
|
|
186
|
-
composerActions={composerActions}
|
|
187
|
-
enableAttachments
|
|
188
|
-
attachmentActionIndex={1}
|
|
189
|
-
/>
|
|
190
|
-
</ChatSidePanel>
|
|
191
|
-
<ChatDialog
|
|
192
|
-
open={open && expanded}
|
|
193
|
-
onOpenChange={(nextOpen) => {
|
|
194
|
-
if (!nextOpen) closeChat();
|
|
195
|
-
}}
|
|
196
|
-
>
|
|
197
|
-
<AIChatWindow
|
|
198
|
-
headerActions={
|
|
199
|
-
<ChatSurfaceActions
|
|
200
|
-
expanded
|
|
184
|
+
expanded={expanded}
|
|
201
185
|
onExpandedChange={setExpanded}
|
|
202
186
|
onClose={closeChat}
|
|
203
187
|
/>
|
|
@@ -206,7 +190,7 @@ function StarterGlobalAIChat({
|
|
|
206
190
|
enableAttachments
|
|
207
191
|
attachmentActionIndex={1}
|
|
208
192
|
/>
|
|
209
|
-
</
|
|
193
|
+
</ChatSurface>
|
|
210
194
|
<AIChatFloatingTrigger unreadCount={unreadCount} />
|
|
211
195
|
</>
|
|
212
196
|
);
|
|
@@ -125,7 +125,20 @@ function AIProviderRuntime({
|
|
|
125
125
|
status: AIConfigurationStatus;
|
|
126
126
|
error?: Error;
|
|
127
127
|
modelError?: Error;
|
|
128
|
-
}>(
|
|
128
|
+
}>(() =>
|
|
129
|
+
providedEmployees && providedModels
|
|
130
|
+
? {
|
|
131
|
+
employees: providedEmployees,
|
|
132
|
+
models: providedModels.length
|
|
133
|
+
? providedModels
|
|
134
|
+
: [UNCONFIGURED_MODEL],
|
|
135
|
+
status: "ready",
|
|
136
|
+
modelError: providedModels.length
|
|
137
|
+
? undefined
|
|
138
|
+
: new Error("No enabled AI models were provided."),
|
|
139
|
+
}
|
|
140
|
+
: { employees: [], models: [], status: "loading" }
|
|
141
|
+
);
|
|
129
142
|
const internalGlobalController = useAIChatController();
|
|
130
143
|
const globalController = providedGlobalController ?? internalGlobalController;
|
|
131
144
|
|
|
@@ -52,14 +52,27 @@ import {
|
|
|
52
52
|
type AIChatMessage,
|
|
53
53
|
type AIChatTaskRuntime,
|
|
54
54
|
type AIConversation,
|
|
55
|
+
type AIEmployee,
|
|
55
56
|
type AIEmployeeTask,
|
|
56
57
|
type AIEmployeeTasks,
|
|
57
58
|
type AIEmployeeTaskTrigger,
|
|
59
|
+
type AIModel,
|
|
58
60
|
type AIWorkContextItem,
|
|
59
61
|
} from "./types";
|
|
60
62
|
|
|
61
63
|
const EMPTY_TASKS: AIEmployeeTask[] = [];
|
|
62
64
|
const EMPTY_EMPLOYEE_TASKS: AIEmployeeTasks = {};
|
|
65
|
+
const UNAVAILABLE_EMPLOYEE: AIEmployee = {
|
|
66
|
+
username: "__unavailable__",
|
|
67
|
+
nickname: "AI employee",
|
|
68
|
+
position: "Not configured",
|
|
69
|
+
greeting: "Configure an AI employee and model to start chatting.",
|
|
70
|
+
};
|
|
71
|
+
const UNAVAILABLE_MODEL: AIModel = {
|
|
72
|
+
value: "__unavailable__",
|
|
73
|
+
label: "No enabled model",
|
|
74
|
+
configured: false,
|
|
75
|
+
};
|
|
63
76
|
|
|
64
77
|
export type AIChatProviderProps = PropsWithChildren<{
|
|
65
78
|
id: string;
|
|
@@ -197,16 +210,19 @@ export function AIChatProvider({
|
|
|
197
210
|
dispatch,
|
|
198
211
|
});
|
|
199
212
|
|
|
200
|
-
const
|
|
213
|
+
const configuredEmployee =
|
|
201
214
|
ai.employees.find(
|
|
202
215
|
(employee) => employee.username === state.selectedEmployeeUsername
|
|
203
216
|
) ?? ai.employees[0];
|
|
204
|
-
const
|
|
217
|
+
const configuredModel =
|
|
205
218
|
findAIModel(ai.models, state.selectedModel) ?? ai.models[0];
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
219
|
+
const currentEmployee = configuredEmployee ?? UNAVAILABLE_EMPLOYEE;
|
|
220
|
+
const currentModel = configuredModel ?? UNAVAILABLE_MODEL;
|
|
221
|
+
const canSend = Boolean(
|
|
222
|
+
configuredEmployee &&
|
|
223
|
+
configuredModel &&
|
|
224
|
+
configuredModel.configured !== false
|
|
225
|
+
);
|
|
210
226
|
|
|
211
227
|
const getActiveConversationId = useCallback(
|
|
212
228
|
() => stateRef.current.activeConversationId,
|
|
@@ -271,7 +287,7 @@ export function AIChatProvider({
|
|
|
271
287
|
const conversation = currentState.conversations.find(
|
|
272
288
|
(item) => item.id === currentId
|
|
273
289
|
);
|
|
274
|
-
if (!employee || !model) return;
|
|
290
|
+
if (!employee || !model || model.configured === false) return;
|
|
275
291
|
const currentAttachments = getConversationAttachments(currentId);
|
|
276
292
|
const unresolvedWorkContext = getConversationWorkContext(currentId);
|
|
277
293
|
if (
|
|
@@ -712,6 +728,7 @@ export function AIChatProvider({
|
|
|
712
728
|
models: ai.models,
|
|
713
729
|
currentEmployee,
|
|
714
730
|
currentModel,
|
|
731
|
+
canSend,
|
|
715
732
|
activeConversation,
|
|
716
733
|
activeConversationId: state.activeConversationId,
|
|
717
734
|
conversations: state.conversations,
|
|
@@ -797,7 +814,9 @@ export function AIChatProvider({
|
|
|
797
814
|
startEditingMessage,
|
|
798
815
|
cancelEditingMessage,
|
|
799
816
|
saveUserPrompt: (prompt) =>
|
|
800
|
-
|
|
817
|
+
configuredEmployee
|
|
818
|
+
? ai.updateEmployeeUserPrompt(configuredEmployee.username, prompt)
|
|
819
|
+
: Promise.resolve(),
|
|
801
820
|
triggerTask,
|
|
802
821
|
runTask,
|
|
803
822
|
focusComposer: requestComposerFocus,
|
|
@@ -818,6 +837,8 @@ export function AIChatProvider({
|
|
|
818
837
|
editingMessageId,
|
|
819
838
|
currentEmployee,
|
|
820
839
|
currentModel,
|
|
840
|
+
canSend,
|
|
841
|
+
configuredEmployee,
|
|
821
842
|
getConfiguredTaskSet,
|
|
822
843
|
invalidateConversationHistory,
|
|
823
844
|
invalidatePendingInteraction,
|
|
@@ -179,8 +179,13 @@ export function useChatAttachments(activeConversationId: string) {
|
|
|
179
179
|
}),
|
|
180
180
|
[]
|
|
181
181
|
);
|
|
182
|
+
// Falls back to the shared EMPTY_ATTACHMENTS rather than a fresh `[]`: callers compare the
|
|
183
|
+
// result by identity across an await to detect "the user changed the attachments mid-send", and
|
|
184
|
+
// a new array each call would make that check fire on every send from a conversation with no
|
|
185
|
+
// attachments.
|
|
182
186
|
const getConversationAttachments = useCallback(
|
|
183
|
-
(conversationId: string) =>
|
|
187
|
+
(conversationId: string) =>
|
|
188
|
+
draftsRef.current[conversationId] ?? EMPTY_ATTACHMENTS,
|
|
184
189
|
[]
|
|
185
190
|
);
|
|
186
191
|
const attachments = drafts[activeConversationId] ?? EMPTY_ATTACHMENTS;
|