@nocobase/portal-template-default 1.0.9 → 1.0.11
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/dist/assets/{LineDraw-C0m1GONT.js → LineDraw-iG-ADes-.js} +1 -1
- package/dist/assets/{SymbolDraw-CCospe5G.js → SymbolDraw-DXoSw1tM.js} +1 -1
- package/dist/assets/VisualMapping-C_ee4Eht.js +1 -0
- package/dist/assets/auth-method-demo-DaOCuvsq.js +1 -0
- package/dist/assets/auto-login-provider-5Uxza9Ms.js +2 -0
- package/dist/assets/auto-login-provider-DyaPGK2D.js +1 -0
- package/dist/assets/auto-redirect-provider-CfwRvYOI.js +1 -0
- package/dist/assets/auto-redirect-provider-Cr3jmYts.js +1 -0
- package/dist/assets/auto-redirect-provider-QkJQRBbG.js +1 -0
- package/dist/assets/calendar-DjDRfD5t.js +1 -0
- package/dist/assets/card-DuQBOS6A.js +1 -0
- package/dist/assets/cas-sign-in-button-CyqG1QME.js +1 -0
- package/dist/assets/components-D-ptYjX_.js +20 -0
- package/dist/assets/configuration-gate-C08mkkiX.js +1 -0
- package/dist/assets/{createClipPathFromCoordSys-uEftn1Mq.js → createClipPathFromCoordSys-wj34-b1B.js} +1 -1
- package/dist/assets/createSeriesDataSimply-D0A6SAVQ.js +1 -0
- package/dist/assets/customGraphicKeyframeAnimation-DWrhrHL_.js +3 -0
- package/dist/assets/demo-BYWHeKUe.js +1 -0
- package/dist/assets/demo-CPWoNtx8.js +1 -0
- package/dist/assets/demo-CyvJk5zV.js +1 -0
- package/dist/assets/demo-D4OiKplc.js +1 -0
- package/dist/assets/demo-DBueJg0m.js +1 -0
- package/dist/assets/demo-DkIhyiRK.js +1 -0
- package/dist/assets/demo-tpTjO0sC.js +1 -0
- package/dist/assets/dingtalk-sign-in-button-DSWsSruv.js +1 -0
- package/dist/assets/echarts-preview-DxHNfq1C.js +1 -0
- package/dist/assets/{echarts-runtime-CEgJbv68.js → echarts-runtime-D8kXn6ME.js} +3 -3
- package/dist/assets/{echarts-runtime-advanced-Buhm2BHz.js → echarts-runtime-advanced-QY8v95qK.js} +1 -1
- package/dist/assets/{echarts-runtime-common-DLAdBAIa.js → echarts-runtime-common-D-yqAK-r.js} +1 -1
- package/dist/assets/{echarts-runtime-components-ypnyLkYx.js → echarts-runtime-components-P4-i8jPU.js} +1 -1
- package/dist/assets/{echarts-runtime-hierarchy-ClStquCF.js → echarts-runtime-hierarchy-ByQZ-DbP.js} +1 -1
- package/dist/assets/floating-BFVEyPEp.js +15 -0
- package/dist/assets/index-BgCbGDMc.js +43 -0
- package/dist/assets/index-Btk35Nx-.js +23 -0
- package/dist/assets/index-CwNxd66J.js +1 -0
- package/dist/assets/index-MB-jfQld.js +686 -0
- package/dist/assets/index-NH3aqqQz.css +1 -0
- package/dist/assets/index-gFtCXH3o.js +58 -0
- package/dist/assets/index-um5hdKdZ.js +23 -0
- package/dist/assets/{labelHelper-C137W5gU.js → labelHelper-DQqRy3a_.js} +1 -1
- package/dist/assets/ldap-sign-in-form--6Gt3yGq.js +1 -0
- package/dist/assets/oidc-sign-in-button-CQdRcR_p.js +1 -0
- package/dist/assets/{page-context-Cxx11gzg.js → page-context-CnQ4GYM7.js} +10 -10
- package/dist/assets/prompt-card-B2RS4nt2.js +1 -0
- package/dist/assets/prompt-output-DZHRM2ft.js +1 -0
- package/dist/assets/saml-sign-in-button-BHL5a0JV.js +1 -0
- package/dist/assets/{sectorHelper-Fqgz3uEk.js → sectorHelper-DN_Vv6OL.js} +1 -1
- package/dist/assets/shortcut-C5x5LQWU.js +47 -0
- package/dist/assets/sms-sign-in-form-DTX9GV2I.js +1 -0
- package/dist/assets/{static.browser-CfQQzERe.js → static.browser-jI1psK0d.js} +8 -8
- package/dist/assets/{switch-DrxImI8F.js → switch-CRL8PmRI.js} +1 -1
- package/dist/assets/{tool-cards-BGfsY8JF.js → tool-cards-DFttLAfo.js} +6 -7
- package/dist/assets/use-authenticator-sign-in-CRGjzXju.js +1 -0
- package/dist/assets/use-dingtalk-sign-in-8WYzJ2jf.js +1 -0
- package/dist/assets/use-wecom-sign-in-C9j8wfCD.js +1 -0
- package/dist/assets/wecom-sign-in-button-mIS1zGSU.js +1 -0
- package/dist/index.html +2 -2
- package/dist/index.raw.html +5 -2
- 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/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/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/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/prompt-card-BsYMW_rq.js +0 -1
- package/dist/assets/shortcut-xtghDP63.js +0 -47
- 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,47 +0,0 @@
|
|
|
1
|
-
import{r as n,ao as ge,ap as St,aq as tt,ar as st,as as se,j as e,at as It,au as at,av as Pt,aw as At,ax as nt,ay as xe,az as Mt,aA as Tt,aB as rt,aC as ot,aD as fe,aE as Et,aF as Rt,aG as $t,c as le,b as Ut,aH as Bt,aI as it,aJ as Dt,aK as Vt,aL as Wt,aM as Ft,aN as Fe,aO as zt,aP as Ot,aQ as Lt,aR as qt,aS as ze,aT as Kt,aU as Gt,aV as Ht,aW as _t,aX as Xt,aY as Jt,E as Qt,aZ as lt,ak as Yt,a_ as Zt,l as Se,a$ as es,w as he,ae as Oe,x as Pe,y as Ae,A as Me,a9 as Le,ag as ts,b0 as Ie,m as Y,n as Z,o as ee,p as te,v as W,I as qe,B as pe,Q as ss,X as as,q as ns,b1 as rs,T as Ke,ah as os,b2 as is}from"./index-C6xNKxpg.js";import{C as Ge}from"./chat-inline-xpQ9iq4p.js";import{A as Te}from"./ai-employee-shortcut-B253WWow.js";import{u as ls,S as He}from"./switch-DrxImI8F.js";import{P as ds}from"./prompt-card-BsYMW_rq.js";import{A as cs}from"./configuration-gate-Dwdq_lxf.js";const us=n.createContext({disabled:!1});function ms(){return n.useContext(us)}const ps=n.createContext(void 0);function hs(s=!0){const a=n.useContext(ps);if(a===void 0&&!s)throw new Error(ge(3));return a}const dt=n.createContext(void 0);function ct(){const s=n.useContext(dt);if(s===void 0)throw new Error(ge(10));return s}const xs={value:()=>null},fs=n.forwardRef(function(a,l){const{render:x,className:P,disabled:m=!1,hiddenUntilFound:h,keepMounted:d,loopFocus:k,onValueChange:f,multiple:M=!1,orientation:v="vertical",value:T,defaultValue:j,style:S,...y}=a,E=St(),I=n.useMemo(()=>{if(T===void 0)return j??[]},[T,j]),C=n.useRef([]),[c,w]=tt({controlled:T,default:I,name:"Accordion",state:"value"}),V=st(($,L,g)=>{if(M)if(L){const N=c.slice();if(N.push($),f==null||f(N,g),g.isCanceled)return;w(N)}else{const N=c.filter(K=>K!==$);if(f==null||f(N,g),g.isCanceled)return;w(N)}else{const N=c[0]===$?[]:[$];if(f==null||f(N,g),g.isCanceled)return;w(N)}}),b=n.useMemo(()=>({value:c,disabled:m,orientation:v}),[c,m,v]),R=n.useMemo(()=>({disabled:m,handleValueChange:V,hiddenUntilFound:h??!1,keepMounted:d??!1,state:b,value:c}),[m,V,h,d,b,c]),p=se("div",a,{state:b,ref:l,props:[{dir:E},y],stateAttributesMapping:xs});return e.jsx(dt.Provider,{value:R,children:e.jsx(It,{elementsRef:C,children:p})})}),ut=n.createContext(void 0);function Ee(){const s=n.useContext(ut);if(s===void 0)throw new Error(ge(9));return s}let gs=(function(s){return s.index="data-index",s.disabled="data-disabled",s.open="data-open",s})({});const Re={...Pt,index:s=>Number.isInteger(s)?{[gs.index]:String(s)}:null,...at,value:()=>null},bs=n.forwardRef(function(a,l){const{className:x,disabled:P=!1,onOpenChange:m,render:h,value:d,style:k,...f}=a,{ref:M,index:v}=At(),T=nt(l,M),{disabled:j,handleValueChange:S,state:y,value:E}=ct(),I=xe(),C=d??I,c=P||j,w=n.useMemo(()=>{if(!E)return!1;for(let U=0;U<E.length;U+=1)if(E[U]===C)return!0;return!1},[E,C]),V=st((U,F)=>{m==null||m(U,F),!F.isCanceled&&S(C,U,F)}),b=Mt({open:w,onOpenChange:V,disabled:c}),R=n.useMemo(()=>({open:b.open,disabled:b.disabled,transitionStatus:b.transitionStatus}),[b.open,b.disabled,b.transitionStatus]),p=n.useMemo(()=>({...b,onOpenChange:V,state:R}),[b,R,V]),$=n.useMemo(()=>({...y,hidden:!w&&!b.mounted,index:v,disabled:c,open:w}),[b.mounted,c,v,w,y]),L=xe(),[g,N]=n.useState(),K=n.useMemo(()=>({open:w,state:$,setTriggerId:N,triggerId:g??L}),[L,w,$,N,g]),G=se("div",a,{state:$,ref:T,props:f,stateAttributesMapping:Re});return e.jsx(Tt.Provider,{value:p,children:e.jsx(ut.Provider,{value:K,children:G})})}),vs=n.forwardRef(function(a,l){const{render:x,className:P,style:m,...h}=a,{state:d}=Ee();return se("h3",a,{state:d,ref:l,props:h,stateAttributesMapping:Re})}),ks=n.forwardRef(function(a,l){const{disabled:x,className:P,id:m,render:h,nativeButton:d=!0,style:k,...f}=a,{panelId:M,open:v,handleTrigger:T,disabled:j}=rt(),S=x||j,{getButtonProps:y,buttonRef:E}=ot({disabled:S,focusableWhenDisabled:!0,native:d}),{state:I,setTriggerId:C,triggerId:c}=Ee();return fe(()=>(m&&C(m),()=>{C(void 0)}),[m,C]),se("button",a,{state:I,ref:[l,E],props:[{"aria-controls":v?M:void 0,"aria-expanded":v,id:c,onClick:T},f,y],stateAttributesMapping:Et})});let _e=(function(s){return s.accordionPanelHeight="--accordion-panel-height",s.accordionPanelWidth="--accordion-panel-width",s})({});const js=n.forwardRef(function(a,l){const{className:x,hiddenUntilFound:P,keepMounted:m,id:h,render:d,style:k,...f}=a,{hiddenUntilFound:M,keepMounted:v}=ct(),{mounted:T,onOpenChange:j,open:S,panelId:y,setMounted:E,setOpen:I,setPanelIdState:C,transitionStatus:c}=rt(),w=P??M,V=m??v;fe(()=>{if(h)return C(h),()=>{C(void 0)}},[h,C]);const{height:b,props:R,ref:p,shouldPreventOpenAnimation:$,shouldRender:L,transitionStatus:g,width:N}=Rt({externalRef:l,hiddenUntilFound:w,id:h??y,keepMounted:V,mounted:T,onOpenChange:j,open:S,setMounted:E,setOpen:I,transitionStatus:c}),{state:K,triggerId:G}=Ee(),U={...K,transitionStatus:g},F=$t(k,U),ae=se("div",{...a,style:void 0},{state:U,ref:p,props:[R,{"aria-labelledby":G,role:"region",style:{[_e.accordionPanelHeight]:b===void 0?"auto":`${b}px`,[_e.accordionPanelWidth]:N===void 0?"auto":`${N}px`}},f,F?{style:F}:void 0,$?{style:{animationName:"none"}}:void 0],stateAttributesMapping:Re});return L?ae:null});function ys({className:s,...a}){return e.jsx(fs,{"data-slot":"accordion",className:le("flex w-full flex-col",s),...a})}function ws({className:s,...a}){return e.jsx(bs,{"data-slot":"accordion-item",className:le("not-last:border-b",s),...a})}function Cs({className:s,children:a,...l}){return e.jsx(vs,{className:"flex",children:e.jsxs(ks,{"data-slot":"accordion-trigger",className:le("group/accordion-trigger relative flex flex-1 items-start justify-between rounded-lg border border-transparent py-2.5 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:after:border-ring aria-disabled:pointer-events-none aria-disabled:opacity-50 **:data-[slot=accordion-trigger-icon]:ml-auto **:data-[slot=accordion-trigger-icon]:size-4 **:data-[slot=accordion-trigger-icon]:text-muted-foreground",s),...l,children:[a,e.jsx(Ut,{"data-slot":"accordion-trigger-icon",className:"pointer-events-none shrink-0 group-aria-expanded/accordion-trigger:hidden"}),e.jsx(Bt,{"data-slot":"accordion-trigger-icon",className:"pointer-events-none hidden shrink-0 group-aria-expanded/accordion-trigger:inline"})]})})}function Ns({className:s,children:a,...l}){return e.jsx(js,{"data-slot":"accordion-content",className:"overflow-hidden text-sm data-open:animate-accordion-down data-closed:animate-accordion-up",...l,children:e.jsx("div",{className:le("h-(--accordion-panel-height) pt-0 pb-2.5 data-ending-style:h-0 data-starting-style:h-0 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4",s),children:a})})}function Ss(s){if(!s)return null;for(const a of s.elements){const l=a.tagName;if(l==="BUTTON"||l==="INPUT"){const x=a;if(x.type==="submit")return x}}return null}let Xe=(function(s){return s.checked="data-checked",s.unchecked="data-unchecked",s.indeterminate="data-indeterminate",s.disabled="data-disabled",s.readonly="data-readonly",s.required="data-required",s.valid="data-valid",s.invalid="data-invalid",s.touched="data-touched",s.dirty="data-dirty",s.filled="data-filled",s.focused="data-focused",s})({});function mt(s){return n.useMemo(()=>({checked(a){return s.indeterminate?{}:a?{[Xe.checked]:""}:{[Xe.unchecked]:""}},...it}),[s.indeterminate])}const pt=n.createContext(void 0);function Is(){const s=n.useContext(pt);if(s===void 0)throw new Error(ge(14));return s}const Ps="data-parent",As=n.forwardRef(function(a,l){const{checked:x,className:P,defaultChecked:m=!1,"aria-labelledby":h,disabled:d=!1,form:k,id:f,indeterminate:M=!1,inputRef:v,name:T,onCheckedChange:j,parent:S=!1,readOnly:y=!1,render:E,required:I=!1,uncheckedValue:C,value:c,nativeButton:w=!1,style:V,...b}=a,{clearErrors:R}=Dt(),{disabled:p,name:$,setDirty:L,setFilled:g,setFocused:N,setTouched:K,state:G,validationMode:U,validityData:F,validation:ae}=Vt(),de=ms(),{labelId:ce,controlId:t,registerControlId:o,getDescriptionProps:r}=Wt(),i=hs(),z=i==null?void 0:i.parent,H=z&&i.allValues,A=p||de.disabled||(i==null?void 0:i.disabled)||d,ne=$??T,B=c??ne,$e=xe(),xt=xe();let _=t;H?_=S?xt:`${z.id}-${B}`:f&&(_=f);let be={};H&&(S?be=i.parent.getParentProps():B&&(be=i.parent.getChildProps(B)));const{checked:Ue=x,indeterminate:ft=M,onCheckedChange:ve,...gt}=be,re=i==null?void 0:i.value,Be=i==null?void 0:i.setValue,De=i==null?void 0:i.defaultValue,ke=n.useRef(null),ue=Ft(()=>Symbol("checkbox-control")),je=n.useRef(!1),{getButtonProps:bt,buttonRef:vt}=ot({disabled:A,native:w}),oe=(i==null?void 0:i.validation)??ae,[O,kt]=tt({controlled:B&&re&&!S?re.includes(B):Ue,default:B&&De&&!S?De.includes(B):m,name:"Checkbox",state:"checked"}),ye=H?!!Ue:O,ie=H&&ft||M;fe(()=>{o!==Fe&&(je.current=!0,o(ue.current,_))},[_,o,ue]),n.useEffect(()=>{const u=ue.current;return()=>{!je.current||o===Fe||(je.current=!1,o(u,void 0))}},[o,ue]),zt(ke,$e,O,void 0,!i&&!A,T);const X=n.useRef(null),jt=nt(v,X,oe.inputRef,oe.registerInput),yt=ls(h,ce,X,!w,_??void 0);fe(()=>{X.current&&(X.current.indeterminate=ie,O&&g(!0))},[O,ie,g]),Ot(O,()=>{i||(R(ne),g(O),L(O!==F.initialValue),oe.change(O))});const wt=Lt({checked:O,disabled:A,form:k,name:S?void 0:ne,id:w?void 0:_??void 0,required:I,ref:jt,style:ne?Kt:Gt,tabIndex:-1,type:"checkbox","aria-hidden":!0,onChange(u){if(u.nativeEvent.defaultPrevented)return;if(y){u.preventDefault();return}const q=u.currentTarget.checked,J=Ht(_t,u.nativeEvent);if(j==null||j(q,J),!J.isCanceled&&(ve==null||ve(q,J),!J.isCanceled&&(kt(q),B&&re&&Be&&!S&&!H))){const Q=q?[...re,B]:re.filter(me=>me!==B);Be(Q,J)}},onFocus(){var u;(u=ke.current)==null||u.focus()}},c!==void 0?{value:(i?O&&c:c)||""}:qt,r,u=>oe.getValidationProps(A,u));n.useEffect(()=>{if(!z||!B)return;const u=z.disabledStatesRef.current;return u.set(B,A),()=>{u.delete(B)}},[z,A,B]);const we=n.useMemo(()=>({...G,checked:ye,disabled:A,readOnly:y,required:I,indeterminate:ie}),[G,ye,A,y,I,ie]),Ct=mt(we),Nt=se("span",a,{state:we,ref:[vt,ke,l,i==null?void 0:i.registerControlRef],props:[{id:w?_??void 0:$e,role:"checkbox","aria-checked":ie?"mixed":ye,"aria-readonly":y||void 0,"aria-required":I||void 0,"aria-labelledby":yt,[Ps]:S?"":void 0,onFocus(){A||N(!0)},onBlur(){const u=X.current;u&&(K(!0),N(!1),U==="onBlur"&&oe.commit(i?re:u.checked))},onKeyDown(u){var Ve;if(u.key!=="Enter"||(u.preventBaseUIHandler(),u.defaultPrevented))return;const q=((Ve=X.current)==null?void 0:Ve.form)??null,J=u.currentTarget,Q=u.nativeEvent,me=u.preventDefault,Ce=Q.preventDefault;let Ne=!1;u.preventDefault=()=>{Ne=!0,me.call(u)},Q.preventDefault=()=>{Ne=!0,Ce.call(Q)},Ce.call(Q),ze(J).queueMicrotask(()=>{var We;u.preventDefault=me,Q.preventDefault=Ce,Ne||(We=Ss(q))==null||We.click()})},onClick(u){if(y||A)return;u.preventDefault();const q=X.current;q&&q.dispatchEvent(new(ze(q)).PointerEvent("click",{bubbles:!0,shiftKey:u.shiftKey,ctrlKey:u.ctrlKey,altKey:u.altKey,metaKey:u.metaKey}))}},b,gt,bt,r,u=>oe.getValidationProps(A,u)],stateAttributesMapping:Ct});return e.jsxs(pt.Provider,{value:we,children:[Nt,!O&&!i&&ne&&!S&&C!==void 0&&e.jsx("input",{type:"hidden",form:k,name:ne,value:C,disabled:A}),e.jsx("input",{...wt,suppressHydrationWarning:!0})]})}),Ms=n.forwardRef(function(a,l){const{render:x,className:P,style:m,keepMounted:h=!1,...d}=a,k=Is(),f=k.checked||k.indeterminate,{mounted:M,transitionStatus:v,setMounted:T}=Xt(f),j=n.useRef(null),S={...k,transitionStatus:v};Jt({open:f,ref:j,onComplete(){f||T(!1)}});const E={...mt(k),...at,...it},I=h||M,C=se("span",a,{ref:[l,j],state:S,stateAttributesMapping:E,props:d});return I?C:null});function Ts({className:s,...a}){return e.jsx(As,{"data-slot":"checkbox",className:le("peer relative flex size-4 shrink-0 items-center justify-center rounded-[4px] border border-input transition-colors outline-none group-has-disabled/field:opacity-50 after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 aria-invalid:aria-checked:border-primary dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-checked:border-primary data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary",s),...a,children:e.jsx(Ms,{"data-slot":"checkbox-indicator",className:"grid place-content-center text-current transition-none [&>svg]:size-3.5",children:e.jsx(Qt,{})})})}const Je={title:"Analyze this ticket",message:{system:"Review the current ticket, identify operational risk, and suggest the next action.",user:"Analyze the current ticket and recommend the next action."},autoSend:!0,model:{llmService:"openai",model:"gpt-5"},webSearch:!0,skillSettings:{skills:["ticket-analysis"],tools:["inspect-record"]}},Qe={title:"Draft a customer reply",message:{user:"Draft a concise customer reply for the current ticket."},autoSend:!1,skillSettings:{skills:["response-drafting"],tools:["inspect-record"]}},Ye={title:"Review operational risk",message:{system:"Review the current queue, identify operational risk, and prioritize the next actions.",user:"Review the current ticket queue and identify operational risk."},autoSend:!1,skillSettings:{skills:["ticket-analysis"],tools:["inspect-record"]}},D={id:"TK-1042",title:"Payment callback delayed",status:"Open",requester:"Northwind Finance",createdAt:"July 22, 2026 · 09:42",description:"Payment succeeded, but the callback reached the order service twelve minutes late. The customer needs an impact assessment and a response before the next settlement window."},Es=[D,{id:"TK-1041",title:"Unable to update profile"},{id:"TK-1038",title:"Invoice export formatting"}],ht=s=>!["nathan","dara"].includes(s.username.toLowerCase());function Ls(){return e.jsx(cs,{children:e.jsx(Rs,{})})}function Rs(){const{employees:s}=lt(),a=Yt(),l=s.filter(ht),x=l.find(k=>k.username.toLowerCase()==="atlas")??l[0],P=l.find(k=>k.username!==x.username)??x,m=l.find(k=>k.username.toLowerCase()==="atlas")??l.find(k=>k.username!==x.username)??x,h=Zt({id:"support-ticket-detail",title:`${D.id} · ${D.title}`,kind:"record-detail",getContext:()=>({resource:"supportTickets",record:D})}),d=n.useMemo(()=>({[x.username]:[Je,Qe],...P.username!==x.username?{[P.username]:[Ye]}:{}}),[x.username,P.username]);return e.jsxs("div",{className:"space-y-12 pb-12",children:[e.jsx("section",{className:"flex flex-wrap items-start justify-between gap-5 border-b pb-8",children:e.jsxs("div",{children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(Se,{variant:"secondary",children:"AI Components"}),e.jsx(Se,{variant:"outline",children:"Employee capability"})]}),e.jsx("h1",{className:"mt-4 text-3xl font-semibold tracking-[-0.035em]",children:"Employee tasks"}),e.jsx("p",{className:"mt-3 max-w-3xl text-sm leading-6 text-muted-foreground",children:"Bind reusable tasks to AI employees. Tasks can appear directly inside a chat when the employee is selected, or be triggered from contextual buttons elsewhere in the application."})]})}),e.jsxs("section",{className:"space-y-5",children:[e.jsxs("div",{children:[e.jsx("p",{className:"text-xs font-semibold uppercase tracking-[0.16em] text-muted-foreground",children:"Scenario 1 · Multiple tasks"}),e.jsx("h2",{className:"mt-2 text-xl font-semibold tracking-tight",children:"Trigger employee tasks from a business record"}),e.jsx("p",{className:"mt-1.5 max-w-3xl text-sm leading-6 text-muted-foreground",children:"The Shortcut component can still sit in a detail header and provide the current record as work context. Opening it shows the configured analysis and reply tasks below the employee greeting."})]}),e.jsx(es,{context:{type:"page-element",id:"support-ticket-detail",title:`${D.id} · ${D.title}`},children:e.jsxs(he,{ref:h,className:"gap-0 overflow-hidden py-0",children:[e.jsxs("div",{className:"flex flex-wrap items-start justify-between gap-4 border-b p-5",children:[e.jsxs("div",{className:"min-w-0",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("span",{className:"font-mono text-xs text-muted-foreground",children:D.id}),e.jsx(Se,{variant:"outline",children:D.status})]}),e.jsx("h3",{className:"mt-2 text-lg font-semibold tracking-tight",children:D.title})]}),e.jsx(Te,{aiEmployee:x.username,tasks:[Je,Qe],label:`Ask ${x.nickname}`,size:34})]}),e.jsxs("div",{className:"grid gap-5 p-5 sm:grid-cols-2",children:[e.jsxs("div",{children:[e.jsx("div",{className:"text-xs text-muted-foreground",children:"Requester"}),e.jsx("div",{className:"mt-1 text-sm font-medium",children:D.requester})]}),e.jsxs("div",{children:[e.jsx("div",{className:"text-xs text-muted-foreground",children:"Created"}),e.jsx("div",{className:"mt-1 text-sm font-medium",children:D.createdAt})]})]}),e.jsxs("div",{className:"border-t p-5",children:[e.jsx("div",{className:"text-xs text-muted-foreground",children:"Description"}),e.jsx("p",{className:"mt-2 max-w-3xl text-sm leading-6",children:D.description})]})]})})]}),e.jsxs("section",{className:"space-y-5",children:[e.jsxs("div",{children:[e.jsx("p",{className:"text-xs font-semibold uppercase tracking-[0.16em] text-muted-foreground",children:"Scenario 2 · Explicit target"}),e.jsx("h2",{className:"mt-2 text-xl font-semibold tracking-tight",children:"Send a shortcut task to a designated embedded chat"}),e.jsxs("p",{className:"mt-1.5 max-w-3xl text-sm leading-6 text-muted-foreground",children:["The shortcut receives the embedded chat Controller directly. Its Provider explicitly configures ",m.nickname," and an employee task set. It does not need a global target ID and cannot accidentally trigger another conversation on the same page."]})]}),e.jsx(Oe,{id:"shortcut-embedded-chat",controller:a,defaultEmployee:m.username,children:e.jsxs("div",{className:"grid items-stretch gap-5 xl:grid-cols-[320px_minmax(0,1fr)]",children:[e.jsxs(he,{children:[e.jsx(Pe,{children:e.jsx(Ae,{className:"text-base",children:"Risk review workspace"})}),e.jsxs(Me,{className:"space-y-5",children:[e.jsx("p",{className:"text-sm leading-6 text-muted-foreground",children:"The embedded chat starts without preset tasks. Clicking the Shortcut injects “Review operational risk” into this specific conversation, where the user can choose it before the request is placed in the composer."}),e.jsx(Te,{aiEmployee:m.username,target:a,tasks:[Ye],auto:!1,context:[{type:"table",title:"Open ticket queue",content:Es}],label:`Ask ${m.nickname}`,size:36})]})]}),e.jsx(Ge,{className:"h-[560px] min-h-0",children:e.jsx(Le,{})})]})})]}),e.jsxs("section",{className:"space-y-5",children:[e.jsxs("div",{children:[e.jsx("p",{className:"text-xs font-semibold uppercase tracking-[0.16em] text-muted-foreground",children:"Scenario 3 · Chat-bound tasks"}),e.jsx("h2",{className:"mt-2 text-xl font-semibold tracking-tight",children:"Show tasks when the selected employee changes"}),e.jsx("p",{className:"mt-1.5 max-w-3xl text-sm leading-6 text-muted-foreground",children:"This conversation window binds task lists directly to business AI employees. Starting a new conversation or switching employees in the composer immediately replaces the empty-state tasks. Employees without a binding keep the normal greeting-only state."})]}),e.jsxs("div",{className:"grid items-stretch gap-5 xl:grid-cols-[320px_minmax(0,1fr)]",children:[e.jsxs(he,{children:[e.jsx(Pe,{children:e.jsx(Ae,{className:"text-base",children:"Employee task bindings"})}),e.jsxs(Me,{className:"space-y-4 text-sm text-muted-foreground",children:[e.jsxs("div",{className:"rounded-lg border bg-muted/25 p-3 font-mono text-xs leading-5",children:[e.jsx("div",{children:x.username}),e.jsx("div",{className:"pl-4",children:"Analyze this ticket"}),e.jsx("div",{className:"pl-4",children:"Draft a customer reply"}),P.username!==x.username?e.jsxs(e.Fragment,{children:[e.jsx("div",{className:"mt-2",children:P.username}),e.jsx("div",{className:"pl-4",children:"Review operational risk"})]}):null]}),e.jsx("p",{children:"Switch employees from the bottom of the chat to preview each configured task list."}),e.jsxs("p",{children:["The same ",e.jsx("code",{children:"employeeTasks"})," parameter works in page, embedded, side-panel, dialog, and mobile containers."]})]})]}),e.jsx(Oe,{id:"employee-task-scenario-chat",defaultEmployee:x.username,employeeTasks:d,children:e.jsx(Ge,{className:"h-[620px] min-h-0",children:e.jsx(Le,{})})})]})]}),e.jsx(Bs,{embeddedController:a})]})}const $s=[{value:"ticket-analysis",label:"Ticket analysis"},{value:"response-drafting",label:"Response drafting"},{value:"workflow-design",label:"Workflow design"}],Us=[{value:"inspect-record",label:"Inspect record"},{value:"search-records",label:"Search records"},{value:"update-record",label:"Update record"}],Ze=[{id:"analyze-ticket",employee:"",title:"Analyze this ticket",background:"Identify operational risk and recommend the next action.",userMessage:"Analyze the current ticket and recommend the next action.",autoSend:!0,workContext:[{type:"page-element",id:"support-ticket-detail",title:`${D.id} · ${D.title}`}],model:"gpt-5",webSearch:!0,skillsMode:"custom",skills:["ticket-analysis"],toolsMode:"custom",tools:["inspect-record"]},{id:"draft-reply",employee:"",title:"Draft a customer reply",background:"Write a concise and helpful response using the ticket context.",userMessage:"Draft a customer reply for the current ticket.",autoSend:!1,workContext:[],model:"default",webSearch:!1,skillsMode:"custom",skills:["response-drafting"],toolsMode:"preset",tools:[]}];function Bs({embeddedController:s}){var U,F,ae,de,ce;const{employees:a,models:l}=lt(),{registeredCount:x,startPicking:P}=ts(),m=a.filter(ht),h=m.find(t=>t.username.toLowerCase()==="atlas")??m[0],[d,k]=n.useState("shortcut"),[f,M]=n.useState("the ticket detail header actions"),[v,T]=n.useState(h.username),[j,S]=n.useState(h.username),[y,E]=n.useState("global-side-panel"),[I,C]=n.useState("embedded"),[c,w]=n.useState(()=>Ze.map(t=>({...t,employee:h.username}))),[V,b]=n.useState(Ze[0].id),R=d==="chat-tasks"?c.filter(t=>t.employee===j):c,p=R.find(t=>t.id===V)??R[0],$=n.useMemo(()=>c.map(t=>{const o=Ie(l,t.model),r=t.skillsMode==="custom"||t.toolsMode==="custom";return{title:t.title,message:{system:t.background||void 0,user:t.userMessage||void 0,workContext:t.workContext.length?t.workContext:void 0},autoSend:t.autoSend,model:o?{llmService:o.llmService,model:o.value}:void 0,webSearch:t.webSearch,skillSettings:r?{skills:t.skillsMode==="custom"?t.skills:void 0,tools:t.toolsMode==="custom"?t.tools:void 0}:void 0}}),[l,c]),L=n.useMemo(()=>{const t=(r,i)=>{var H;const z=r.model==="default"?"Use the AI employee default":((H=l.find(A=>A.value===r.model))==null?void 0:H.label)??r.model;return`${i+1}. ${r.title}
|
|
2
|
-
- Background: ${r.background||"None"}
|
|
3
|
-
- Default user message: ${r.userMessage||"None"}
|
|
4
|
-
- Send automatically: ${r.autoSend}
|
|
5
|
-
- Work context: ${r.workContext.map(A=>A.title??A.id).join(", ")||"Inherit the surrounding context"}
|
|
6
|
-
- Model: ${z}
|
|
7
|
-
- Web search: ${r.webSearch}
|
|
8
|
-
- Skills: ${r.skillsMode==="preset"?"Use the AI employee defaults":r.skills.join(", ")||"Disabled"}
|
|
9
|
-
- Tools: ${r.toolsMode==="preset"?"Use the AI employee defaults":r.tools.join(", ")||"Disabled"}`},o=d==="chat-tasks"?Array.from(new Set(c.map(r=>r.employee))).map(r=>{const i=c.filter(z=>z.employee===r);return`AI employee: ${r}
|
|
10
|
-
${i.map(t).join(`
|
|
11
|
-
`)}`}).join(`
|
|
12
|
-
|
|
13
|
-
`):c.map(t).join(`
|
|
14
|
-
`);if(d==="chat-tasks"){const r={page:"a dedicated page",embedded:"an embedded block in the page","side-panel":"a push side panel",dialog:"a dialog"}[I];return`Add an AI conversation window to ${f} using ${r}.
|
|
15
|
-
|
|
16
|
-
Employee task bindings:
|
|
17
|
-
${o}
|
|
18
|
-
|
|
19
|
-
Implementation requirements:
|
|
20
|
-
- Render AIChatWindow in the selected container and wrap it with AIChatProvider.
|
|
21
|
-
- Configure AIChatProvider with employeeTasks grouped by AI employee username.
|
|
22
|
-
- When the user starts a new conversation or switches AI employees, show the matching tasks below that employee’s greeting.
|
|
23
|
-
- Employees without configured tasks must keep the greeting-only empty state.
|
|
24
|
-
- Selecting a task must respect autoSend: either send immediately or fill the composer for review.
|
|
25
|
-
- Resolve task message.workContext when the task starts so selected page elements use their latest values.
|
|
26
|
-
- When a task has no message.workContext, inherit the context surrounding the Shortcut or AIChatProvider.
|
|
27
|
-
- Preserve every task’s background prompt, model override, Web search setting, Skills, Tools, and work context.
|
|
28
|
-
- Keep the employeeTasks capability independent of the container so the same configuration works in page, embedded, side-panel, dialog, and mobile layouts.
|
|
29
|
-
- Use the existing AIProvider/AIChatProvider runtime with shadcn/Base UI components.`}return`Add an AI employee shortcut to ${f}.
|
|
30
|
-
|
|
31
|
-
Shortcut configuration:
|
|
32
|
-
- AI employee: ${v}
|
|
33
|
-
- Target conversation: ${y==="global-side-panel"?"the default global push side panel":"the embedded chat rendered in this page section"}
|
|
34
|
-
- Include the current record or form values as work context.
|
|
35
|
-
|
|
36
|
-
Tasks:
|
|
37
|
-
${o}
|
|
38
|
-
|
|
39
|
-
Implementation requirements:
|
|
40
|
-
- Use AIEmployeeShortcut and pass all configured tasks through its tasks prop.
|
|
41
|
-
- When multiple tasks are provided, open a new conversation and show the tasks below the AI employee greeting.
|
|
42
|
-
- Selecting a task must respect that task's autoSend setting: either send immediately or fill the composer for review.
|
|
43
|
-
- Resolve task message.workContext when the task starts so selected page elements use their latest values.
|
|
44
|
-
- When a task has no message.workContext, inherit the context surrounding the Shortcut or AIChatProvider.
|
|
45
|
-
- Preserve each task's background prompt, work context, model override, Web search setting, Skills, and Tools.
|
|
46
|
-
- ${y==="global-side-panel"?"Use the global AI chat controller; do not pass a target prop.":"Create a dedicated controller with useAIChatController(), bind it to the embedded AIChatProvider, and pass it through the shortcut target prop."}
|
|
47
|
-
- Use the existing AIProvider and AIChatProvider runtime with shadcn/Base UI components.`},[I,v,d,f,l,y,c]),g=t=>{p&&w(o=>o.map(r=>r.id===p.id?{...r,...t}:r))},N=()=>{const t=`task-${crypto.randomUUID()}`;w(o=>[...o,{id:t,employee:d==="chat-tasks"?j:v,title:`Task ${o.length+1}`,background:"",userMessage:"",autoSend:!1,workContext:[],model:"default",webSearch:!1,skillsMode:"preset",skills:[],toolsMode:"preset",tools:[]}]),b(t)},K=t=>{if(c.length===1)return;const o=c.filter(r=>r.id!==t);if(w(o),V===t){const r=d==="chat-tasks"?o.find(i=>i.employee===j):o[0];b((r==null?void 0:r.id)??"")}},G=(t,o,r)=>{if(!p)return;const i=p[t];g({[t]:r?[...i,o]:i.filter(z=>z!==o)})};return e.jsxs("section",{className:"space-y-5",children:[e.jsxs("div",{children:[e.jsx("p",{className:"text-xs font-semibold uppercase tracking-[0.16em] text-muted-foreground",children:"Employee tasks prompt"}),e.jsx("h2",{className:"mt-2 text-xl font-semibold tracking-tight",children:"Generate an integration prompt"}),e.jsx("p",{className:"mt-1.5 max-w-3xl text-sm leading-6 text-muted-foreground",children:"Choose whether tasks are exposed through a contextual Shortcut or directly by the selected employee in a chat. Both modes share the same task, model, Web search, Skills, and Tools configuration."})]}),e.jsxs("div",{className:"grid items-start gap-5 xl:grid-cols-[520px_minmax(0,1fr)]",children:[e.jsxs(he,{className:"gap-0 py-0",children:[e.jsx(Pe,{className:"py-4",children:e.jsx(Ae,{className:"text-base",children:"Employee task configuration"})}),e.jsxs(Me,{className:"space-y-4 pb-4",children:[e.jsxs("label",{className:"block space-y-2 text-xs font-medium",children:[e.jsx("span",{children:"Integration"}),e.jsxs(Y,{value:d,onValueChange:t=>{if(!t)return;const o=t;k(o),M(o==="shortcut"?"the ticket detail header actions":"the ticket detail page");const r=o==="chat-tasks"?c.find(i=>i.employee===j):c[0];b((r==null?void 0:r.id)??"")},children:[e.jsx(Z,{className:"w-full",children:e.jsx(ee,{children:d==="shortcut"?"Contextual Shortcut":"Tasks inside a chat"})}),e.jsxs(te,{children:[e.jsx(W,{value:"shortcut",children:"Contextual Shortcut"}),e.jsx(W,{value:"chat-tasks",children:"Tasks inside a chat"})]})]})]}),d==="chat-tasks"?e.jsxs("label",{className:"block space-y-2 border-t pt-4 text-xs font-medium",children:[e.jsx("span",{children:"AI employee"}),e.jsxs(Y,{value:j,onValueChange:t=>{var o;t&&(S(t),b(((o=c.find(r=>r.employee===t))==null?void 0:o.id)??""))},children:[e.jsx(Z,{className:"w-full",children:e.jsx(ee,{children:((U=m.find(t=>t.username===j))==null?void 0:U.nickname)??j})}),e.jsx(te,{children:m.map(t=>e.jsx(W,{value:t.username,children:t.nickname},t.username))})]}),e.jsx("span",{className:"block font-normal leading-5 text-muted-foreground",children:"Select an employee first, then configure the tasks shown in that employee’s new-conversation state."})]}):null,e.jsxs("div",{className:"grid gap-4 sm:grid-cols-2",children:[d==="shortcut"?e.jsxs("label",{className:"block space-y-2 text-xs font-medium",children:[e.jsx("span",{children:"AI employee"}),e.jsxs(Y,{value:v,onValueChange:t=>t&&T(t),children:[e.jsx(Z,{className:"w-full",children:e.jsx(ee,{children:((F=m.find(t=>t.username===v))==null?void 0:F.nickname)??v})}),e.jsx(te,{children:m.map(t=>e.jsx(W,{value:t.username,children:t.nickname},t.username))})]})]}):null,e.jsxs("label",{className:"block space-y-2 text-xs font-medium",children:[e.jsx("span",{children:"Placement"}),e.jsx(qe,{value:f,onChange:t=>M(t.target.value)})]}),d==="chat-tasks"?e.jsxs("label",{className:"block space-y-2 text-xs font-medium",children:[e.jsx("span",{children:"Chat container"}),e.jsxs(Y,{value:I,onValueChange:t=>t&&C(t),children:[e.jsx(Z,{className:"w-full",children:e.jsx(ee,{children:{page:"Page",embedded:"Embedded block","side-panel":"Side panel",dialog:"Dialog"}[I]})}),e.jsxs(te,{children:[e.jsx(W,{value:"page",children:"Page"}),e.jsx(W,{value:"embedded",children:"Embedded block"}),e.jsx(W,{value:"side-panel",children:"Side panel"}),e.jsx(W,{value:"dialog",children:"Dialog"})]})]})]}):null]}),d==="shortcut"?e.jsxs("label",{className:"block space-y-2 text-xs font-medium",children:[e.jsx("span",{children:"Target conversation"}),e.jsxs(Y,{value:y,onValueChange:t=>t&&E(t),children:[e.jsx(Z,{className:"w-full",children:e.jsx(ee,{children:y==="global-side-panel"?"Global side panel":"Embedded chat"})}),e.jsxs(te,{children:[e.jsx(W,{value:"global-side-panel",children:"Global side panel"}),e.jsx(W,{value:"embedded",children:"Embedded chat"})]})]})]}):null,e.jsxs("div",{className:"space-y-2 border-t pt-4",children:[e.jsxs("div",{className:"flex items-center justify-between gap-3",children:[e.jsxs("div",{children:[e.jsx("div",{className:"text-sm font-medium",children:"Tasks"}),e.jsxs("div",{className:"text-xs text-muted-foreground",children:[R.length," configured task",R.length===1?"":"s",d==="chat-tasks"?" for this employee":""]})]}),e.jsxs(pe,{variant:"outline",size:"sm",onClick:N,children:[e.jsx(ss,{})," Add task"]})]}),e.jsx("div",{className:"grid gap-2 sm:grid-cols-2",children:R.map((t,o)=>e.jsxs("div",{className:t.id===(p==null?void 0:p.id)?"flex items-center gap-2 rounded-lg border border-foreground/25 bg-muted/60 p-1":"flex items-center gap-2 rounded-lg border p-1",children:[e.jsxs("button",{type:"button",className:"min-w-0 flex-1 rounded-md px-2 py-1.5 text-left",onClick:()=>b(t.id),children:[e.jsxs("span",{className:"block truncate text-xs font-medium",children:[o+1,". ",t.title]}),e.jsxs("span",{className:"block text-[10px] text-muted-foreground",children:[t.autoSend?"Auto send":"Fill composer",t.workContext.length?" · Task context":"",t.webSearch?" · Web search":""]})]}),e.jsx(pe,{variant:"ghost",size:"icon-xs",disabled:c.length===1,"aria-label":`Remove ${t.title}`,onClick:()=>K(t.id),children:e.jsx(as,{})})]},t.id))})]}),p?e.jsxs("div",{className:"space-y-3 border-t pt-4",children:[e.jsxs("div",{className:"grid gap-3 sm:grid-cols-2",children:[e.jsxs("label",{className:"block space-y-2 text-xs font-medium",children:[e.jsx("span",{children:"Title"}),e.jsx(qe,{value:p.title,onChange:t=>g({title:t.target.value})})]}),e.jsxs("label",{className:"block space-y-2 text-xs font-medium",children:[e.jsx("span",{children:"Model"}),e.jsxs(Y,{value:p.model,onValueChange:t=>t&&g({model:t}),children:[e.jsx(Z,{className:"w-full",children:e.jsx(ee,{className:"min-w-0 overflow-hidden",children:e.jsx("span",{className:"block min-w-0 truncate",title:p.model==="default"?"Use employee default":((ae=Ie(l,p.model))==null?void 0:ae.label)??p.model,children:p.model==="default"?"Use employee default":((de=Ie(l,p.model))==null?void 0:de.label)??p.model})})}),e.jsxs(te,{children:[e.jsx(W,{value:"default",children:"Use employee default"}),e.jsx(ns,{}),e.jsx(rs,{models:l})]})]})]})]}),e.jsxs("label",{className:"block space-y-2 text-xs font-medium",children:[e.jsx("span",{children:"Default user message"}),e.jsx(Ke,{className:"min-h-16",value:p.userMessage,onChange:t=>g({userMessage:t.target.value})})]}),e.jsxs("label",{className:"flex items-center justify-between gap-4 rounded-lg border px-3 py-2.5 text-sm",children:[e.jsxs("span",{children:[e.jsx("span",{className:"block font-medium",children:"Auto send"}),e.jsx("span",{className:"block text-xs text-muted-foreground",children:"Otherwise the message is placed in the composer."})]}),e.jsx(He,{size:"sm",checked:p.autoSend,onCheckedChange:t=>g({autoSend:t})})]}),e.jsxs("div",{className:"space-y-3 rounded-lg border px-3 py-2.5",children:[e.jsxs("div",{className:"flex items-start justify-between gap-3",children:[e.jsxs("span",{children:[e.jsx("span",{className:"block text-sm font-medium",children:"Work context"}),e.jsx("span",{className:"block text-xs text-muted-foreground",children:"Select a page context for this task. Without one, the task inherits its surrounding context."})]}),e.jsxs(pe,{variant:"outline",size:"sm",disabled:x===0,onClick:()=>P({onSelect:t=>g({workContext:[{type:t.type,id:t.id,title:t.title,kind:t.kind}]})}),children:[e.jsx(os,{})," Pick context"]})]}),p.workContext.map((t,o)=>e.jsxs("div",{className:"flex items-center gap-2 rounded-md bg-muted/50 px-2 py-1.5 text-xs",children:[e.jsx("span",{className:"min-w-0 flex-1 truncate",children:t.title??t.id??t.type}),e.jsx(pe,{variant:"ghost",size:"icon-xs","aria-label":"Remove task context",onClick:()=>g({workContext:[]}),children:e.jsx(is,{})})]},`${t.type}:${t.id??o}`))]}),e.jsx(ys,{children:e.jsxs(ws,{value:"advanced-task-settings",className:"rounded-lg border px-3",children:[e.jsx(Cs,{className:"no-underline hover:no-underline",children:e.jsxs("span",{children:[e.jsx("span",{className:"block",children:"Advanced task settings"}),e.jsx("span",{className:"block text-xs font-normal text-muted-foreground",children:"Background, Web search, Skills, and Tools"})]})}),e.jsxs(Ns,{className:"space-y-4 pt-1",children:[e.jsxs("label",{className:"block space-y-2 text-xs font-medium",children:[e.jsx("span",{children:"Background"}),e.jsx(Ke,{className:"min-h-16",value:p.background,onChange:t=>g({background:t.target.value})})]}),e.jsxs("label",{className:"flex items-center justify-between gap-4 rounded-lg border px-3 py-2.5 text-sm",children:[e.jsxs("span",{children:[e.jsx("span",{className:"block font-medium",children:"Web search"}),e.jsx("span",{className:"block text-xs text-muted-foreground",children:"Subject to the selected LLM service capability."})]}),e.jsx(He,{size:"sm",checked:p.webSearch,onCheckedChange:t=>g({webSearch:t})})]}),e.jsx(et,{title:"Skills",mode:p.skillsMode,values:p.skills,options:$s,onModeChange:t=>g({skillsMode:t}),onToggle:(t,o)=>G("skills",t,o)}),e.jsx(et,{title:"Tools",mode:p.toolsMode,values:p.tools,options:Us,onModeChange:t=>g({toolsMode:t}),onToggle:(t,o)=>G("tools",t,o)})]})]})})]}):null,e.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-3 border-t pt-4",children:[e.jsxs("div",{children:[e.jsx("div",{className:"text-xs font-medium",children:d==="shortcut"?"Live shortcut preview":"Chat-bound task configuration"}),e.jsx("div",{className:"text-[11px] text-muted-foreground",children:d==="shortcut"?"Multiple tasks appear in the chat empty state after opening.":"Use the third scenario above to preview employee switching."})]}),d==="shortcut"?e.jsx(Te,{aiEmployee:v,tasks:$,target:y==="embedded"?s:void 0,label:`Ask ${((ce=m.find(t=>t.username===v))==null?void 0:ce.nickname)??v}`,size:34}):null]})]})]}),e.jsx(ds,{title:d==="shortcut"?"Add an AI employee task shortcut":"Add employee tasks to a chat",description:d==="shortcut"?"The prompt includes every task, capability override, and target conversation.":"The prompt groups configured tasks by business AI employee and selected chat container.",prompt:L})]})]})}function et({title:s,mode:a,values:l,options:x,onModeChange:P,onToggle:m}){return e.jsxs("div",{className:"space-y-3 rounded-lg border p-3",children:[e.jsxs("div",{className:"flex items-center justify-between gap-4",children:[e.jsxs("div",{children:[e.jsx("div",{className:"text-sm font-medium",children:s}),e.jsx("div",{className:"text-xs text-muted-foreground",children:"Preset inherits the AI employee configuration."})]}),e.jsxs(Y,{value:a,onValueChange:h=>h&&P(h),children:[e.jsx(Z,{size:"sm",className:"w-28",children:e.jsx(ee,{children:a==="preset"?"Preset":"Custom"})}),e.jsxs(te,{children:[e.jsx(W,{value:"preset",children:"Preset"}),e.jsx(W,{value:"custom",children:"Custom"})]})]})]}),a==="custom"?e.jsx("div",{className:"grid gap-2 sm:grid-cols-2",children:x.map(h=>e.jsxs("label",{className:"flex items-center gap-2 rounded-md bg-muted/40 px-2.5 py-2 text-xs",children:[e.jsx(Ts,{checked:l.includes(h.value),onCheckedChange:d=>m(h.value,d)}),e.jsx("span",{children:h.label})]},h.value))}):null]})}export{Ls as ShortcutPage};
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
# NocoBase ACL UI
|
|
2
|
-
|
|
3
|
-
Reusable ACL composition components for the NocoBase Refine starter.
|
|
4
|
-
|
|
5
|
-
After installation, import the components from `@/extensions/nocobase-acl`.
|
|
6
|
-
|
|
7
|
-
- `AclPage` combines page-level permissions.
|
|
8
|
-
- `AclRegion` hides or replaces individual data regions.
|
|
9
|
-
- `AclField` applies NocoBase field whitelists.
|
|
10
|
-
- `RoleSwitcher` can be placed in any application surface. It supports
|
|
11
|
-
normal roles, Anonymous when enabled, and Full permissions through the
|
|
12
|
-
`__union__` role when the system role mode allows it.
|
|
13
|
-
|
|
14
|
-
Mark non-collection resources with `meta.acl.type = "authenticated"`, or use
|
|
15
|
-
the `collection`, `snippet`, and `route` ACL metadata variants when the resource
|
|
16
|
-
should participate in NocoBase permission checks.
|
|
17
|
-
|
|
18
|
-
The underlying `accessControlProvider`, `roles:check` cache, record permissions,
|
|
19
|
-
and Refine integration are provided by the compatible Starter.
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import type { AppExtension } from "@/app/extension";
|
|
2
|
-
import { Blocks, PanelsTopLeft, ShieldCheck } from "lucide-react";
|
|
3
|
-
import { lazy } from "react";
|
|
4
|
-
import { Outlet, Route } from "react-router";
|
|
5
|
-
import { LazyAclRoute } from "./demo/lazy-route";
|
|
6
|
-
|
|
7
|
-
const AclComponentsPage = lazy(() =>
|
|
8
|
-
import("./demo/components").then((module) => ({
|
|
9
|
-
default: module.AclComponentsPage,
|
|
10
|
-
}))
|
|
11
|
-
);
|
|
12
|
-
const AclPatternsPage = lazy(() =>
|
|
13
|
-
import("./demo").then((module) => ({ default: module.AclPatternsPage }))
|
|
14
|
-
);
|
|
15
|
-
|
|
16
|
-
const nocobaseAclExtension: AppExtension = {
|
|
17
|
-
id: "nocobase-acl",
|
|
18
|
-
resources: [
|
|
19
|
-
{
|
|
20
|
-
name: "acl-integration",
|
|
21
|
-
meta: {
|
|
22
|
-
label: "Access control",
|
|
23
|
-
icon: <ShieldCheck />,
|
|
24
|
-
description: "NocoBase ACL integration for admin applications.",
|
|
25
|
-
acl: { type: "authenticated" },
|
|
26
|
-
},
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
name: "acl-components",
|
|
30
|
-
list: "/acl",
|
|
31
|
-
meta: {
|
|
32
|
-
parent: "acl-integration",
|
|
33
|
-
label: "Role switcher",
|
|
34
|
-
icon: <Blocks />,
|
|
35
|
-
acl: { type: "authenticated" },
|
|
36
|
-
},
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
name: "acl-patterns",
|
|
40
|
-
list: "/acl/patterns",
|
|
41
|
-
meta: {
|
|
42
|
-
parent: "acl-integration",
|
|
43
|
-
label: "Permission patterns",
|
|
44
|
-
icon: <PanelsTopLeft />,
|
|
45
|
-
acl: { type: "authenticated" },
|
|
46
|
-
},
|
|
47
|
-
},
|
|
48
|
-
],
|
|
49
|
-
routes: (
|
|
50
|
-
<Route key="nocobase-acl" path="/acl" element={<Outlet />}>
|
|
51
|
-
<Route
|
|
52
|
-
index
|
|
53
|
-
element={
|
|
54
|
-
<LazyAclRoute>
|
|
55
|
-
<AclComponentsPage />
|
|
56
|
-
</LazyAclRoute>
|
|
57
|
-
}
|
|
58
|
-
/>
|
|
59
|
-
<Route
|
|
60
|
-
path="patterns"
|
|
61
|
-
element={
|
|
62
|
-
<LazyAclRoute>
|
|
63
|
-
<AclPatternsPage />
|
|
64
|
-
</LazyAclRoute>
|
|
65
|
-
}
|
|
66
|
-
/>
|
|
67
|
-
</Route>
|
|
68
|
-
),
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
export default nocobaseAclExtension;
|
|
@@ -1,314 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { createServer } from "vite";
|
|
3
|
-
|
|
4
|
-
const server = await createServer({
|
|
5
|
-
appType: "custom",
|
|
6
|
-
logLevel: "silent",
|
|
7
|
-
server: { middlewareMode: true },
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
try {
|
|
11
|
-
const {
|
|
12
|
-
canAccessWithSnapshot,
|
|
13
|
-
getAclDataForDataSource,
|
|
14
|
-
resolveActionPermission,
|
|
15
|
-
} = await server.ssrLoadModule("/src/lib/nocobase/acl/action.ts");
|
|
16
|
-
const { resolveAclDataSourceKey } = await server.ssrLoadModule(
|
|
17
|
-
"/src/lib/nocobase/acl/data-source.ts"
|
|
18
|
-
);
|
|
19
|
-
const { filterMenuItemsByAcl, findFirstAccessibleRoute } =
|
|
20
|
-
await server.ssrLoadModule("/src/lib/nocobase/acl/menu.ts");
|
|
21
|
-
const {
|
|
22
|
-
clearRecordPermissions,
|
|
23
|
-
getRecordActionPermission,
|
|
24
|
-
updateRecordPermissions,
|
|
25
|
-
} = await server.ssrLoadModule("/src/lib/nocobase/acl/record-permissions.ts");
|
|
26
|
-
const { RoleSwitcher } = await server.ssrLoadModule(
|
|
27
|
-
"/registry/nocobase-acl/components/role-switcher.tsx"
|
|
28
|
-
);
|
|
29
|
-
const { getRoleOptions, resolveRoleTitle } = await server.ssrLoadModule(
|
|
30
|
-
"/registry/nocobase-acl/components/role-options.ts"
|
|
31
|
-
);
|
|
32
|
-
|
|
33
|
-
const snapshot = {
|
|
34
|
-
status: "ready",
|
|
35
|
-
version: 1,
|
|
36
|
-
data: {
|
|
37
|
-
role: "editor",
|
|
38
|
-
roleMode: "allow-use-union",
|
|
39
|
-
resources: ["blog_posts"],
|
|
40
|
-
actionAlias: {
|
|
41
|
-
list: "view",
|
|
42
|
-
get: "view",
|
|
43
|
-
},
|
|
44
|
-
actions: {
|
|
45
|
-
"blog_posts:view": { fields: ["id", "title"] },
|
|
46
|
-
"blog_posts:update": { fields: ["title"] },
|
|
47
|
-
},
|
|
48
|
-
strategy: {
|
|
49
|
-
actions: ["view:all"],
|
|
50
|
-
},
|
|
51
|
-
snippets: ["pm.*"],
|
|
52
|
-
},
|
|
53
|
-
meta: {},
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
assert.deepEqual(
|
|
57
|
-
resolveActionPermission({
|
|
58
|
-
snapshot,
|
|
59
|
-
resource: "blog_posts",
|
|
60
|
-
action: "list",
|
|
61
|
-
}),
|
|
62
|
-
{ fields: ["id", "title"] }
|
|
63
|
-
);
|
|
64
|
-
assert.equal(
|
|
65
|
-
canAccessWithSnapshot(snapshot, {
|
|
66
|
-
resource: "blog_posts",
|
|
67
|
-
action: "create",
|
|
68
|
-
}),
|
|
69
|
-
false
|
|
70
|
-
);
|
|
71
|
-
|
|
72
|
-
const externalSnapshot = {
|
|
73
|
-
...snapshot,
|
|
74
|
-
data: {
|
|
75
|
-
...snapshot.data,
|
|
76
|
-
allowAll: true,
|
|
77
|
-
actionAlias: { list: "view" },
|
|
78
|
-
},
|
|
79
|
-
meta: {
|
|
80
|
-
dataSources: {
|
|
81
|
-
analytics: {
|
|
82
|
-
allowAll: false,
|
|
83
|
-
resources: ["orders"],
|
|
84
|
-
actionAlias: { list: "read" },
|
|
85
|
-
actions: { "orders:read": {} },
|
|
86
|
-
},
|
|
87
|
-
},
|
|
88
|
-
},
|
|
89
|
-
};
|
|
90
|
-
assert.deepEqual(getAclDataForDataSource(externalSnapshot, "analytics"), {
|
|
91
|
-
...externalSnapshot.data,
|
|
92
|
-
allowAll: false,
|
|
93
|
-
resources: ["orders"],
|
|
94
|
-
actionAlias: { list: "read" },
|
|
95
|
-
actions: { "orders:read": {} },
|
|
96
|
-
snippets: externalSnapshot.data.snippets,
|
|
97
|
-
});
|
|
98
|
-
assert.equal(
|
|
99
|
-
canAccessWithSnapshot(externalSnapshot, {
|
|
100
|
-
resource: "orders",
|
|
101
|
-
action: "list",
|
|
102
|
-
params: { meta: { dataSourceKey: "analytics" } },
|
|
103
|
-
}),
|
|
104
|
-
true
|
|
105
|
-
);
|
|
106
|
-
assert.equal(
|
|
107
|
-
resolveAclDataSourceKey({
|
|
108
|
-
acl: {
|
|
109
|
-
type: "collection",
|
|
110
|
-
dataSourceKey: "analytics",
|
|
111
|
-
},
|
|
112
|
-
}),
|
|
113
|
-
"analytics"
|
|
114
|
-
);
|
|
115
|
-
assert.equal(
|
|
116
|
-
resolveAclDataSourceKey(
|
|
117
|
-
{ dataSourceKey: "reporting" },
|
|
118
|
-
{
|
|
119
|
-
acl: {
|
|
120
|
-
type: "collection",
|
|
121
|
-
dataSourceKey: "analytics",
|
|
122
|
-
},
|
|
123
|
-
}
|
|
124
|
-
),
|
|
125
|
-
"reporting"
|
|
126
|
-
);
|
|
127
|
-
assert.equal(
|
|
128
|
-
getRecordActionPermission({
|
|
129
|
-
resource: "blog_posts",
|
|
130
|
-
action: "update",
|
|
131
|
-
id: 3,
|
|
132
|
-
}),
|
|
133
|
-
undefined
|
|
134
|
-
);
|
|
135
|
-
assert.equal(
|
|
136
|
-
canAccessWithSnapshot(snapshot, {
|
|
137
|
-
resource: "categories",
|
|
138
|
-
action: "list",
|
|
139
|
-
}),
|
|
140
|
-
true
|
|
141
|
-
);
|
|
142
|
-
assert.equal(
|
|
143
|
-
canAccessWithSnapshot(snapshot, {
|
|
144
|
-
resource: "settings",
|
|
145
|
-
action: "list",
|
|
146
|
-
params: {
|
|
147
|
-
resource: {
|
|
148
|
-
name: "settings",
|
|
149
|
-
meta: { acl: { type: "snippet", name: "*" } },
|
|
150
|
-
},
|
|
151
|
-
},
|
|
152
|
-
}),
|
|
153
|
-
true
|
|
154
|
-
);
|
|
155
|
-
assert.equal(
|
|
156
|
-
canAccessWithSnapshot(
|
|
157
|
-
{
|
|
158
|
-
...snapshot,
|
|
159
|
-
data: {
|
|
160
|
-
...snapshot.data,
|
|
161
|
-
snippets: ["pm", "pm.*", "!pm.data-source-manager*"],
|
|
162
|
-
},
|
|
163
|
-
},
|
|
164
|
-
{
|
|
165
|
-
resource: "settings",
|
|
166
|
-
action: "list",
|
|
167
|
-
params: {
|
|
168
|
-
resource: {
|
|
169
|
-
name: "settings",
|
|
170
|
-
meta: {
|
|
171
|
-
acl: { type: "snippet", name: "pm.data-source-manager" },
|
|
172
|
-
},
|
|
173
|
-
},
|
|
174
|
-
},
|
|
175
|
-
}
|
|
176
|
-
),
|
|
177
|
-
false
|
|
178
|
-
);
|
|
179
|
-
assert.equal(
|
|
180
|
-
updateRecordPermissions({
|
|
181
|
-
resource: "blog_posts",
|
|
182
|
-
recordIds: [4],
|
|
183
|
-
allowedActions: { update: [] },
|
|
184
|
-
}),
|
|
185
|
-
true
|
|
186
|
-
);
|
|
187
|
-
assert.equal(
|
|
188
|
-
getRecordActionPermission({
|
|
189
|
-
resource: "blog_posts",
|
|
190
|
-
action: "update",
|
|
191
|
-
id: 4,
|
|
192
|
-
}),
|
|
193
|
-
false
|
|
194
|
-
);
|
|
195
|
-
assert.equal(
|
|
196
|
-
canAccessWithSnapshot(snapshot, {
|
|
197
|
-
resource: "settings",
|
|
198
|
-
action: "list",
|
|
199
|
-
params: {
|
|
200
|
-
resource: {
|
|
201
|
-
name: "settings",
|
|
202
|
-
meta: { acl: { type: "snippet", name: "pm.acl" } },
|
|
203
|
-
},
|
|
204
|
-
},
|
|
205
|
-
}),
|
|
206
|
-
true
|
|
207
|
-
);
|
|
208
|
-
assert.equal(
|
|
209
|
-
canAccessWithSnapshot(snapshot, {
|
|
210
|
-
resource: "blog_posts",
|
|
211
|
-
action: "edit",
|
|
212
|
-
params: { field: "title" },
|
|
213
|
-
}),
|
|
214
|
-
true
|
|
215
|
-
);
|
|
216
|
-
assert.equal(
|
|
217
|
-
canAccessWithSnapshot(snapshot, {
|
|
218
|
-
resource: "blog_posts",
|
|
219
|
-
action: "edit",
|
|
220
|
-
params: { field: "status" },
|
|
221
|
-
}),
|
|
222
|
-
false
|
|
223
|
-
);
|
|
224
|
-
|
|
225
|
-
clearRecordPermissions();
|
|
226
|
-
updateRecordPermissions({
|
|
227
|
-
resource: "blog_posts",
|
|
228
|
-
recordIds: [1, 2],
|
|
229
|
-
allowedActions: {
|
|
230
|
-
view: [1, 2],
|
|
231
|
-
update: [1],
|
|
232
|
-
destroy: [],
|
|
233
|
-
},
|
|
234
|
-
});
|
|
235
|
-
assert.equal(
|
|
236
|
-
canAccessWithSnapshot(snapshot, {
|
|
237
|
-
resource: "blog_posts",
|
|
238
|
-
action: "edit",
|
|
239
|
-
params: { id: 1 },
|
|
240
|
-
}),
|
|
241
|
-
true
|
|
242
|
-
);
|
|
243
|
-
assert.equal(
|
|
244
|
-
canAccessWithSnapshot(snapshot, {
|
|
245
|
-
resource: "blog_posts",
|
|
246
|
-
action: "edit",
|
|
247
|
-
params: { id: 2 },
|
|
248
|
-
}),
|
|
249
|
-
false
|
|
250
|
-
);
|
|
251
|
-
assert.equal(
|
|
252
|
-
updateRecordPermissions({
|
|
253
|
-
resource: "blog_posts",
|
|
254
|
-
recordIds: [1, 2],
|
|
255
|
-
allowedActions: {
|
|
256
|
-
view: [1, 2],
|
|
257
|
-
update: [1],
|
|
258
|
-
destroy: [],
|
|
259
|
-
},
|
|
260
|
-
}),
|
|
261
|
-
false
|
|
262
|
-
);
|
|
263
|
-
|
|
264
|
-
const filteredMenu = filterMenuItemsByAcl(
|
|
265
|
-
[
|
|
266
|
-
{
|
|
267
|
-
key: "restricted-parent",
|
|
268
|
-
name: "restricted-parent",
|
|
269
|
-
route: "/restricted-parent",
|
|
270
|
-
children: [
|
|
271
|
-
{
|
|
272
|
-
key: "public-child",
|
|
273
|
-
name: "public-child",
|
|
274
|
-
route: "/public-child",
|
|
275
|
-
meta: { acl: { type: "authenticated" } },
|
|
276
|
-
children: [],
|
|
277
|
-
},
|
|
278
|
-
],
|
|
279
|
-
},
|
|
280
|
-
],
|
|
281
|
-
{
|
|
282
|
-
...snapshot,
|
|
283
|
-
data: { ...snapshot.data, strategy: { actions: [] } },
|
|
284
|
-
}
|
|
285
|
-
);
|
|
286
|
-
assert.equal(filteredMenu[0].route, undefined);
|
|
287
|
-
assert.equal(findFirstAccessibleRoute(filteredMenu), "/public-child");
|
|
288
|
-
|
|
289
|
-
assert.deepEqual(
|
|
290
|
-
getRoleOptions({
|
|
291
|
-
roles: [
|
|
292
|
-
{ name: "admin", title: "Administrator" },
|
|
293
|
-
{ name: "member", title: "Member" },
|
|
294
|
-
],
|
|
295
|
-
roleMode: "allow-use-union",
|
|
296
|
-
allowAnonymous: true,
|
|
297
|
-
}),
|
|
298
|
-
[
|
|
299
|
-
{ name: "__union__", title: "Full permissions" },
|
|
300
|
-
{ name: "admin", title: "Administrator" },
|
|
301
|
-
{ name: "member", title: "Member" },
|
|
302
|
-
{ name: "anonymous", title: "Anonymous" },
|
|
303
|
-
]
|
|
304
|
-
);
|
|
305
|
-
assert.equal(
|
|
306
|
-
resolveRoleTitle({ name: "admin", title: '{{t("Admin")}}' }),
|
|
307
|
-
"Admin"
|
|
308
|
-
);
|
|
309
|
-
|
|
310
|
-
assert.equal(typeof RoleSwitcher, "function");
|
|
311
|
-
console.log("NocoBase ACL regression tests passed");
|
|
312
|
-
} finally {
|
|
313
|
-
await server.close();
|
|
314
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Dialog,
|
|
3
|
-
DialogContent,
|
|
4
|
-
DialogDescription,
|
|
5
|
-
DialogTitle,
|
|
6
|
-
} from "@/components/ui/dialog";
|
|
7
|
-
import type { PropsWithChildren } from "react";
|
|
8
|
-
|
|
9
|
-
export function ChatDialog({
|
|
10
|
-
open,
|
|
11
|
-
onOpenChange,
|
|
12
|
-
children,
|
|
13
|
-
}: PropsWithChildren<{
|
|
14
|
-
open: boolean;
|
|
15
|
-
onOpenChange: (open: boolean) => void;
|
|
16
|
-
}>) {
|
|
17
|
-
return (
|
|
18
|
-
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
19
|
-
<DialogContent
|
|
20
|
-
showCloseButton={false}
|
|
21
|
-
className="h-[95svh] w-[95vw] max-w-[95vw] gap-0 overflow-hidden rounded-xl p-0 sm:max-w-[95vw]"
|
|
22
|
-
>
|
|
23
|
-
<DialogTitle className="sr-only">NocoBase AI employee</DialogTitle>
|
|
24
|
-
<DialogDescription className="sr-only">
|
|
25
|
-
Expanded AI conversation window.
|
|
26
|
-
</DialogDescription>
|
|
27
|
-
{children}
|
|
28
|
-
</DialogContent>
|
|
29
|
-
</Dialog>
|
|
30
|
-
);
|
|
31
|
-
}
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { Button } from "@/components/ui/button";
|
|
2
|
-
import { cn } from "@/lib/utils";
|
|
3
|
-
import { PanelLeftClose, PanelRightClose } from "lucide-react";
|
|
4
|
-
import { useEffect, type CSSProperties, type PropsWithChildren } from "react";
|
|
5
|
-
|
|
6
|
-
export type ChatSidePanelProps = PropsWithChildren<{
|
|
7
|
-
open: boolean;
|
|
8
|
-
onOpenChange: (open: boolean) => void;
|
|
9
|
-
side?: "left" | "right";
|
|
10
|
-
width?: number | string;
|
|
11
|
-
closeOnEscape?: boolean;
|
|
12
|
-
showCloseHandle?: boolean;
|
|
13
|
-
}>;
|
|
14
|
-
|
|
15
|
-
export function ChatSidePanel({
|
|
16
|
-
open,
|
|
17
|
-
onOpenChange,
|
|
18
|
-
side = "right",
|
|
19
|
-
width = 450,
|
|
20
|
-
closeOnEscape = true,
|
|
21
|
-
showCloseHandle = true,
|
|
22
|
-
children,
|
|
23
|
-
}: ChatSidePanelProps) {
|
|
24
|
-
useEffect(() => {
|
|
25
|
-
if (!open || !closeOnEscape) return;
|
|
26
|
-
const onKeyDown = (event: KeyboardEvent) => {
|
|
27
|
-
if (event.key !== "Escape" || event.defaultPrevented) return;
|
|
28
|
-
const openDialog = document.querySelector(
|
|
29
|
-
'[role="dialog"][data-state="open"], [role="alertdialog"][data-state="open"]'
|
|
30
|
-
);
|
|
31
|
-
if (openDialog) return;
|
|
32
|
-
onOpenChange(false);
|
|
33
|
-
};
|
|
34
|
-
window.addEventListener("keydown", onKeyDown);
|
|
35
|
-
return () => window.removeEventListener("keydown", onKeyDown);
|
|
36
|
-
}, [closeOnEscape, onOpenChange, open]);
|
|
37
|
-
|
|
38
|
-
if (!open) return null;
|
|
39
|
-
|
|
40
|
-
const panelWidth = typeof width === "number" ? `${width}px` : width;
|
|
41
|
-
|
|
42
|
-
return (
|
|
43
|
-
<aside
|
|
44
|
-
role="complementary"
|
|
45
|
-
aria-label="NocoBase AI employee"
|
|
46
|
-
data-side={side}
|
|
47
|
-
className={cn(
|
|
48
|
-
"fixed inset-y-0 z-50 max-w-full overscroll-contain bg-background shadow-2xl animate-in duration-200",
|
|
49
|
-
side === "right"
|
|
50
|
-
? "right-0 border-l slide-in-from-right"
|
|
51
|
-
: "left-0 border-r slide-in-from-left"
|
|
52
|
-
)}
|
|
53
|
-
style={{ width: panelWidth } as CSSProperties}
|
|
54
|
-
>
|
|
55
|
-
{showCloseHandle ? (
|
|
56
|
-
<Button
|
|
57
|
-
variant="outline"
|
|
58
|
-
size="icon-sm"
|
|
59
|
-
className={cn(
|
|
60
|
-
"absolute top-1/2 z-40 size-9 -translate-y-1/2 rounded-full bg-background shadow-md before:absolute before:-inset-2",
|
|
61
|
-
side === "right"
|
|
62
|
-
? "left-0 -translate-x-1/2"
|
|
63
|
-
: "right-0 translate-x-1/2"
|
|
64
|
-
)}
|
|
65
|
-
aria-label="Close side panel"
|
|
66
|
-
onClick={() => onOpenChange(false)}
|
|
67
|
-
>
|
|
68
|
-
{side === "right" ? <PanelRightClose /> : <PanelLeftClose />}
|
|
69
|
-
</Button>
|
|
70
|
-
) : null}
|
|
71
|
-
{children}
|
|
72
|
-
</aside>
|
|
73
|
-
);
|
|
74
|
-
}
|