@rytass/bpm-core-react 0.20.1 → 0.21.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/README.md +1 -1
  3. package/dist/approval-instance-list-page.css +1 -1
  4. package/dist/chunks/{FormBuilderView-CFRiw04B.js → FormBuilderView-6KdApi-D.js} +667 -584
  5. package/dist/chunks/FormBuilderView-6KdApi-D.js.map +1 -0
  6. package/dist/chunks/FormBuilderView-CGKkuVFi.cjs +4 -0
  7. package/dist/chunks/FormBuilderView-CGKkuVFi.cjs.map +1 -0
  8. package/dist/chunks/{FormRendererView-lYSaR8CQ.cjs → FormRendererView-B-xycC9S.cjs} +3 -3
  9. package/dist/chunks/{FormRendererView-lYSaR8CQ.cjs.map → FormRendererView-B-xycC9S.cjs.map} +1 -1
  10. package/dist/chunks/{FormRendererView-GfbVWEGa.js → FormRendererView-BcQKUi1A.js} +238 -238
  11. package/dist/chunks/{FormRendererView-GfbVWEGa.js.map → FormRendererView-BcQKUi1A.js.map} +1 -1
  12. package/dist/chunks/approval-instance-list-page-BI5fIXc-.js +724 -0
  13. package/dist/chunks/approval-instance-list-page-BI5fIXc-.js.map +1 -0
  14. package/dist/chunks/approval-instance-list-page-Cv5I1YPX.cjs +2 -0
  15. package/dist/chunks/approval-instance-list-page-Cv5I1YPX.cjs.map +1 -0
  16. package/dist/chunks/{categories-DQUtVEiV.cjs → categories-CyyqG6wG.cjs} +2 -2
  17. package/dist/chunks/{categories-DQUtVEiV.cjs.map → categories-CyyqG6wG.cjs.map} +1 -1
  18. package/dist/chunks/{categories-BAdxjNSA.js → categories-f7E-RJEc.js} +9 -9
  19. package/dist/chunks/{categories-BAdxjNSA.js.map → categories-f7E-RJEc.js.map} +1 -1
  20. package/dist/chunks/{compose-egQlRjuZ.js → compose-DFuLPmOA.js} +5 -5
  21. package/dist/chunks/{compose-egQlRjuZ.js.map → compose-DFuLPmOA.js.map} +1 -1
  22. package/dist/chunks/compose-DK3ocWwG.cjs +2 -0
  23. package/dist/chunks/compose-DK3ocWwG.cjs.map +1 -0
  24. package/dist/chunks/{delegations-CVmtpkRn.cjs → delegations-ClRCwD1I.cjs} +2 -2
  25. package/dist/chunks/{delegations-CVmtpkRn.cjs.map → delegations-ClRCwD1I.cjs.map} +1 -1
  26. package/dist/chunks/{delegations-9-yvWWws.js → delegations-CqND-Qtz.js} +5 -5
  27. package/dist/chunks/{delegations-9-yvWWws.js.map → delegations-CqND-Qtz.js.map} +1 -1
  28. package/dist/chunks/{delegations-CrfjsH94.cjs → delegations-D43VDSz1.cjs} +2 -2
  29. package/dist/chunks/{delegations-CrfjsH94.cjs.map → delegations-D43VDSz1.cjs.map} +1 -1
  30. package/dist/chunks/{delegations-CxnNvTCc.js → delegations-zisffLwk.js} +5 -5
  31. package/dist/chunks/{delegations-CxnNvTCc.js.map → delegations-zisffLwk.js.map} +1 -1
  32. package/dist/chunks/designer-CAUWEKJ4.cjs +69 -0
  33. package/dist/chunks/designer-CAUWEKJ4.cjs.map +1 -0
  34. package/dist/chunks/{designer-CZDlQ9Nf.js → designer-CCy5EbOe.js} +2380 -1786
  35. package/dist/chunks/designer-CCy5EbOe.js.map +1 -0
  36. package/dist/chunks/detail-C5KrUC7s.cjs +5 -0
  37. package/dist/chunks/detail-C5KrUC7s.cjs.map +1 -0
  38. package/dist/chunks/{detail-ByZV1XaI.js → detail-Dnc4PPWO.js} +804 -777
  39. package/dist/chunks/detail-Dnc4PPWO.js.map +1 -0
  40. package/dist/chunks/instance-error-message-BEM4O_Kj.js +208 -0
  41. package/dist/chunks/instance-error-message-BEM4O_Kj.js.map +1 -0
  42. package/dist/chunks/instance-error-message-BpCLCOjp.cjs +2 -0
  43. package/dist/chunks/instance-error-message-BpCLCOjp.cjs.map +1 -0
  44. package/dist/chunks/organization-time-zone-provider-B7CXQril.cjs +2 -0
  45. package/dist/chunks/organization-time-zone-provider-B7CXQril.cjs.map +1 -0
  46. package/dist/chunks/organization-time-zone-provider-DNL8yo4-.js +42 -0
  47. package/dist/chunks/organization-time-zone-provider-DNL8yo4-.js.map +1 -0
  48. package/dist/chunks/{providers-BbCs23sk.js → providers-CQWekD7_.js} +197 -196
  49. package/dist/chunks/providers-CQWekD7_.js.map +1 -0
  50. package/dist/chunks/providers-VUOh6i0i.cjs +2 -0
  51. package/dist/chunks/providers-VUOh6i0i.cjs.map +1 -0
  52. package/dist/components/approval-instance-field-filter-controls.d.cts +12 -0
  53. package/dist/components/approval-instance-field-filter-controls.d.ts +12 -0
  54. package/dist/components/approval-instance-field-filters.d.cts +86 -0
  55. package/dist/components/approval-instance-field-filters.d.ts +86 -0
  56. package/dist/components/approval-instance-list-page.d.cts +4 -0
  57. package/dist/components/approval-instance-list-page.d.ts +4 -0
  58. package/dist/components/notification-drawer-options.d.cts +18 -0
  59. package/dist/components/notification-drawer-options.d.ts +18 -0
  60. package/dist/index.cjs +1 -1
  61. package/dist/index.cjs.map +1 -1
  62. package/dist/index.d.cts +1 -0
  63. package/dist/index.d.ts +1 -0
  64. package/dist/index.js +23 -23
  65. package/dist/index.js.map +1 -1
  66. package/dist/lib/organization-time-zone-provider.d.cts +20 -0
  67. package/dist/lib/organization-time-zone-provider.d.ts +20 -0
  68. package/dist/lib/organization-time-zone.d.cts +8 -0
  69. package/dist/lib/organization-time-zone.d.ts +8 -0
  70. package/dist/lib/providers.d.cts +10 -1
  71. package/dist/lib/providers.d.ts +10 -1
  72. package/dist/next/BPMNextProviders.d.cts +8 -1
  73. package/dist/next/BPMNextProviders.d.ts +8 -1
  74. package/dist/next/index.cjs +1 -1
  75. package/dist/next/index.cjs.map +1 -1
  76. package/dist/next/index.js +12 -11
  77. package/dist/next/index.js.map +1 -1
  78. package/dist/next/workflow-chat-route.cjs +2 -2
  79. package/dist/next/workflow-chat-route.cjs.map +1 -1
  80. package/dist/next/workflow-chat-route.js +13 -13
  81. package/dist/pages/admin/delegations/index.cjs +1 -1
  82. package/dist/pages/admin/delegations/index.js +1 -1
  83. package/dist/pages/delegations/index.cjs +1 -1
  84. package/dist/pages/delegations/index.js +1 -1
  85. package/dist/pages/instances/detail/index.cjs +1 -1
  86. package/dist/pages/instances/detail/index.js +1 -1
  87. package/dist/pages/templates/categories/index.cjs +1 -1
  88. package/dist/pages/templates/categories/index.js +1 -1
  89. package/dist/pages/templates/compose/index.cjs +1 -1
  90. package/dist/pages/templates/compose/index.js +1 -1
  91. package/dist/pages/templates/designer/index.cjs +1 -1
  92. package/dist/pages/templates/designer/index.js +1 -1
  93. package/dist/views/admin/delegations/index.cjs +1 -1
  94. package/dist/views/admin/delegations/index.js +1 -1
  95. package/dist/views/admin/index.cjs +1 -1
  96. package/dist/views/admin/index.js +1 -1
  97. package/dist/views/admin/webhook-endpoints/index.cjs +1 -1
  98. package/dist/views/admin/webhook-endpoints/index.cjs.map +1 -1
  99. package/dist/views/admin/webhook-endpoints/index.js +60 -60
  100. package/dist/views/admin/webhook-endpoints/index.js.map +1 -1
  101. package/dist/views/cc/index.cjs +1 -1
  102. package/dist/views/cc/index.js +1 -1
  103. package/dist/views/delegations/index.cjs +1 -1
  104. package/dist/views/delegations/index.js +1 -1
  105. package/dist/views/forms/builder/FormBuilderView.d.cts +8 -1
  106. package/dist/views/forms/builder/FormBuilderView.d.ts +8 -1
  107. package/dist/views/forms/builder/index.cjs +1 -1
  108. package/dist/views/forms/builder/index.js +1 -1
  109. package/dist/views/forms/renderer/index.cjs +1 -1
  110. package/dist/views/forms/renderer/index.js +1 -1
  111. package/dist/views/instances/approver-filled-form.d.cts +8 -5
  112. package/dist/views/instances/approver-filled-form.d.ts +8 -5
  113. package/dist/views/instances/detail/index.cjs +1 -1
  114. package/dist/views/instances/detail/index.js +1 -1
  115. package/dist/views/instances/detail/sections/InstanceTasksSection.d.cts +2 -1
  116. package/dist/views/instances/detail/sections/InstanceTasksSection.d.ts +2 -1
  117. package/dist/views/instances/detail/sections/shared.d.cts +0 -1
  118. package/dist/views/instances/detail/sections/shared.d.ts +0 -1
  119. package/dist/views/instances/new/index.cjs +1 -1
  120. package/dist/views/instances/new/index.cjs.map +1 -1
  121. package/dist/views/instances/new/index.js +8 -8
  122. package/dist/views/instances/new/index.js.map +1 -1
  123. package/dist/views/search/index.cjs +1 -1
  124. package/dist/views/search/index.js +1 -1
  125. package/dist/views/sent/index.cjs +1 -1
  126. package/dist/views/sent/index.js +1 -1
  127. package/dist/views/templates/categories/index.cjs +1 -1
  128. package/dist/views/templates/categories/index.js +1 -1
  129. package/dist/views/templates/compose/index.cjs +1 -1
  130. package/dist/views/templates/compose/index.js +1 -1
  131. package/dist/views/templates/designer/NotifyWebhookTargetsEditor.d.cts +2 -0
  132. package/dist/views/templates/designer/NotifyWebhookTargetsEditor.d.ts +2 -0
  133. package/dist/views/templates/designer/approver-resolver-model.d.cts +105 -1
  134. package/dist/views/templates/designer/approver-resolver-model.d.ts +105 -1
  135. package/dist/views/templates/designer/dynamic-form-resolver-options.d.cts +5 -0
  136. package/dist/views/templates/designer/dynamic-form-resolver-options.d.ts +5 -0
  137. package/dist/views/templates/designer/index.cjs +1 -1
  138. package/dist/views/templates/designer/index.js +1 -1
  139. package/dist/views/templates/designer/node-actor-resolver-options.d.cts +44 -1
  140. package/dist/views/templates/designer/node-actor-resolver-options.d.ts +44 -1
  141. package/dist/views/templates/designer/notify-recipient-options.d.cts +81 -0
  142. package/dist/views/templates/designer/notify-recipient-options.d.ts +81 -0
  143. package/dist/views/templates/designer/position-level-options.d.cts +3 -1
  144. package/dist/views/templates/designer/position-level-options.d.ts +3 -1
  145. package/dist/views/templates/designer/position-scope.d.cts +51 -0
  146. package/dist/views/templates/designer/position-scope.d.ts +51 -0
  147. package/dist/views/templates/index.cjs +1 -1
  148. package/dist/views/templates/index.cjs.map +1 -1
  149. package/dist/views/templates/index.js +7 -7
  150. package/package.json +3 -3
  151. package/dist/chunks/FormBuilderView-CFRiw04B.js.map +0 -1
  152. package/dist/chunks/FormBuilderView-CpUgbSbt.cjs +0 -3
  153. package/dist/chunks/FormBuilderView-CpUgbSbt.cjs.map +0 -1
  154. package/dist/chunks/approval-instance-list-page-BnI4t5mD.js +0 -279
  155. package/dist/chunks/approval-instance-list-page-BnI4t5mD.js.map +0 -1
  156. package/dist/chunks/approval-instance-list-page-fqvmkBlg.cjs +0 -2
  157. package/dist/chunks/approval-instance-list-page-fqvmkBlg.cjs.map +0 -1
  158. package/dist/chunks/compose-Bb5bUVFd.cjs +0 -2
  159. package/dist/chunks/compose-Bb5bUVFd.cjs.map +0 -1
  160. package/dist/chunks/designer-CX9bhYJS.cjs +0 -69
  161. package/dist/chunks/designer-CX9bhYJS.cjs.map +0 -1
  162. package/dist/chunks/designer-CZDlQ9Nf.js.map +0 -1
  163. package/dist/chunks/detail-ByZV1XaI.js.map +0 -1
  164. package/dist/chunks/detail-ChseSF55.cjs +0 -5
  165. package/dist/chunks/detail-ChseSF55.cjs.map +0 -1
  166. package/dist/chunks/instance-error-message-DTARGbGo.cjs +0 -2
  167. package/dist/chunks/instance-error-message-DTARGbGo.cjs.map +0 -1
  168. package/dist/chunks/instance-error-message-q6gy5evy.js +0 -120
  169. package/dist/chunks/instance-error-message-q6gy5evy.js.map +0 -1
  170. package/dist/chunks/language-provider-Bdnx3Gxl.cjs +0 -2
  171. package/dist/chunks/language-provider-Bdnx3Gxl.cjs.map +0 -1
  172. package/dist/chunks/language-provider-phkWOapf.js +0 -17
  173. package/dist/chunks/language-provider-phkWOapf.js.map +0 -1
  174. package/dist/chunks/providers-BbCs23sk.js.map +0 -1
  175. package/dist/chunks/providers-D_KAgOOu.cjs +0 -2
  176. package/dist/chunks/providers-D_KAgOOu.cjs.map +0 -1
package/dist/index.js CHANGED
@@ -1,46 +1,46 @@
1
1
  "use client";
2
2
  import { n as e, t } from "./chunks/auth-provider-C2b-5mGt.js";
3
3
  import { t as n } from "./chunks/format-date-time-B0LdmWg4.js";
4
- import { i as r, n as i, r as a, t as o } from "./chunks/language-provider-phkWOapf.js";
5
- import { a as s, i as c, n as l, o as u, r as d, t as f } from "./chunks/providers-BbCs23sk.js";
6
- import { n as p, r as m, t as h } from "./chunks/router-adapter-CkxCyzf5.js";
7
- import { n as g, r as _, t as v } from "./chunks/routes-config-BcNaWJWJ.js";
8
- import { n as y, t as b } from "./chunks/use-bpm-member-BPzcNU1g.js";
9
- import { a as x, i as S, n as C, o as w, r as T, t as E } from "./chunks/admin-pickers-DfGxsjpp.js";
10
- import { t as D } from "./chunks/approval-instance-list-page-BnI4t5mD.js";
11
- import { t as O } from "./chunks/bpm-form-field-9B-Det0u.js";
12
- import { t as k } from "./chunks/dashboard-page-BoqNhjT_.js";
13
- import { NavigationIconButton as A } from "@mezzanine-ui/react";
14
- import { jsx as j, jsxs as M } from "react/jsx-runtime";
15
- import { NotificationUnreadIcon as N } from "@mezzanine-ui/icons";
4
+ import { a as r, i, n as a, o, r as s, t as c } from "./chunks/organization-time-zone-provider-DNL8yo4-.js";
5
+ import { a as l, i as u, n as d, o as f, r as p, t as m } from "./chunks/providers-CQWekD7_.js";
6
+ import { n as h, r as g, t as _ } from "./chunks/router-adapter-CkxCyzf5.js";
7
+ import { n as v, r as y, t as b } from "./chunks/routes-config-BcNaWJWJ.js";
8
+ import { n as x, t as S } from "./chunks/use-bpm-member-BPzcNU1g.js";
9
+ import { a as C, i as w, n as T, o as E, r as D, t as O } from "./chunks/admin-pickers-DfGxsjpp.js";
10
+ import { t as k } from "./chunks/approval-instance-list-page-BI5fIXc-.js";
11
+ import { t as A } from "./chunks/bpm-form-field-9B-Det0u.js";
12
+ import { t as j } from "./chunks/dashboard-page-BoqNhjT_.js";
13
+ import { NavigationIconButton as M } from "@mezzanine-ui/react";
14
+ import { jsx as N, jsxs as P } from "react/jsx-runtime";
15
+ import { NotificationUnreadIcon as F } from "@mezzanine-ui/icons";
16
16
  import './index.css';// src/lib/use-bpm-logout.ts
17
- function P() {
17
+ function I() {
18
18
  let { logout: t } = e();
19
19
  return t;
20
20
  }
21
- var F = {
21
+ var L = {
22
22
  root: "bpm_root_jl5S-",
23
23
  badge: "bpm_badge_SIKJk"
24
24
  };
25
25
  // src/components/bpm-notification-bell-button.tsx
26
- function I({ label: e = "通知中心" } = {}) {
27
- let { open: t } = u(), { unreadCount: n } = c(), r = n > 0 ? `${e},${n} 則未讀` : e;
28
- return /* @__PURE__ */ M("span", {
29
- className: F.root,
30
- children: [/* @__PURE__ */ j(A, {
26
+ function R({ label: e = "通知中心" } = {}) {
27
+ let { open: t } = f(), { unreadCount: n } = u(), r = n > 0 ? `${e},${n} 則未讀` : e;
28
+ return /* @__PURE__ */ P("span", {
29
+ className: L.root,
30
+ children: [/* @__PURE__ */ N(M, {
31
31
  "aria-label": r,
32
- icon: N,
32
+ icon: F,
33
33
  onClick: () => {
34
34
  t();
35
35
  },
36
36
  title: e,
37
37
  type: "button"
38
- }), n > 0 ? /* @__PURE__ */ j("span", {
39
- className: F.badge,
38
+ }), n > 0 ? /* @__PURE__ */ N("span", {
39
+ className: L.badge,
40
40
  children: n > 99 ? "99+" : n
41
41
  }) : null]
42
42
  });
43
43
  }
44
- export { D as ApprovalInstanceListPage, t as AuthProvider, O as BPMFormField, o as BPMLanguageProvider, I as BPMNotificationBellButton, v as BPMRoutesProvider, i as BPM_LANGUAGES, a as DEFAULT_BPM_LANGUAGE, k as DashboardPage, E as MemberPicker, l as NotificationDrawer, s as NotificationDrawerProvider, d as NotificationUnreadProvider, C as OrgUnitPicker, T as PositionPicker, f as Providers, h as RouterAdapterProvider, g as createDefaultBPMRoutes, p as defaultBrowserSearchParams, n as formatDateTime, b as isBPMAdminMember, S as readMemberOption, x as readOrgUnitOption, w as readPositionOption, e as useAuth, r as useBPMLanguage, P as useBPMLogout, y as useBPMMember, _ as useBPMRoutes, u as useNotificationDrawer, c as useNotificationUnread, m as useRouterAdapter };
44
+ export { k as ApprovalInstanceListPage, t as AuthProvider, A as BPMFormField, s as BPMLanguageProvider, R as BPMNotificationBellButton, c as BPMOrganizationTimeZoneProvider, b as BPMRoutesProvider, i as BPM_LANGUAGES, r as DEFAULT_BPM_LANGUAGE, j as DashboardPage, O as MemberPicker, d as NotificationDrawer, l as NotificationDrawerProvider, p as NotificationUnreadProvider, T as OrgUnitPicker, D as PositionPicker, m as Providers, _ as RouterAdapterProvider, v as createDefaultBPMRoutes, h as defaultBrowserSearchParams, n as formatDateTime, S as isBPMAdminMember, w as readMemberOption, C as readOrgUnitOption, E as readPositionOption, e as useAuth, o as useBPMLanguage, I as useBPMLogout, x as useBPMMember, a as useBPMOrganizationTimeZone, y as useBPMRoutes, f as useNotificationDrawer, u as useNotificationUnread, g as useRouterAdapter };
45
45
 
46
46
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["styles","useAuth","formatDateTime","useBPMLanguage","BPM_LANGUAGES","DEFAULT_BPM_LANGUAGE","BPMLanguageProvider","NotificationDrawerProvider","useNotificationUnread","NotificationDrawer","useNotificationDrawer","NotificationUnreadProvider","Providers","defaultBrowserSearchParams","useRouterAdapter","RouterAdapterProvider","createDefaultBPMRoutes","useBPMRoutes","BPMRoutesProvider","useBPMMember","isBPMAdminMember","readOrgUnitOption","readMemberOption","OrgUnitPicker","readPositionOption","PositionPicker","MemberPicker","ApprovalInstanceListPage","BPMFormField","DashboardPage","useBPMLogout","logout","bpm_notification_bell_button_module_default","BPMNotificationBellButton","ariaLabel","unreadCount","label","NavigationIconButton","NotificationUnreadIcon","AuthProvider"],"sources":["../src/lib/use-bpm-logout.ts","../src/components/bpm-notification-bell-button.module.scss","../src/components/bpm-notification-bell-button.tsx"],"sourcesContent":["'use client';\n\nimport { useAuth } from './auth-provider';\n\n/**\n * Drop-in logout handler for host navigation menus. Wraps the BPM auth\n * context's logout flow — calls `logoutApi()` against the BPM REST\n * session endpoint, clears the in-memory member, then redirects to the\n * configured `loginPath`.\n *\n * Hosts mount this on their own logout buttons / menu items so they do\n * not need to touch `useAuth()` directly. To customize the post-logout\n * destination, override the `loginPath` prop on `<BPMNextProviders>` or\n * `<AuthProvider>`.\n */\nexport function useBPMLogout(): () => Promise<void> {\n const { logout } = useAuth();\n return logout;\n}\n",".root {\n display: inline-flex;\n position: relative;\n}\n\n.badge {\n align-items: center;\n background: #d92d20;\n border: 1px solid #fff;\n border-radius: 999px;\n color: #fff;\n display: inline-flex;\n font-size: 10px;\n font-weight: 600;\n height: 16px;\n justify-content: center;\n line-height: 1;\n min-width: 16px;\n padding: 0 4px;\n pointer-events: none;\n position: absolute;\n right: -4px;\n top: -4px;\n}\n","'use client';\n\nimport type { ReactElement } from 'react';\nimport { NavigationIconButton } from '@mezzanine-ui/react';\nimport { NotificationUnreadIcon } from '@mezzanine-ui/icons';\nimport { useNotificationDrawer } from '../lib/notification-drawer-provider';\nimport { useNotificationUnread } from '../lib/notification-unread-provider';\nimport styles from './bpm-notification-bell-button.module.scss';\n\nexport interface BPMNotificationBellButtonProps {\n /** Override the aria-label / tooltip. Defaults to `通知中心`. */\n readonly label?: string;\n}\n\n/**\n * Drop-in notification bell. Reads the unread count from\n * `<NotificationUnreadProvider>`, opens the BPM `<NotificationDrawer />`\n * mounted by `<Providers>` (or `<BPMNextProviders>`) on click, and\n * renders a small red badge with the unread count.\n *\n * Use this when the host navigation wants the BPM notification UX with\n * minimum wiring. Hosts that need a fully custom button can skip this\n * widget and consume `useNotificationDrawer()` + `useNotificationUnread()`\n * directly to wire their own trigger.\n *\n * Visual: uses Mezzanine `NavigationIconButton` so the bell aligns with\n * surrounding Mezzanine navigation chrome. The button is decoupled from\n * the `<Navigation>` container — it does not require a Mezzanine\n * navigation tree to render.\n */\nexport function BPMNotificationBellButton({\n label = '通知中心',\n}: BPMNotificationBellButtonProps = {}): ReactElement {\n const { open } = useNotificationDrawer();\n const { unreadCount } = useNotificationUnread();\n const ariaLabel = unreadCount > 0 ? `${label},${unreadCount} 則未讀` : label;\n return (\n <span className={styles.root}>\n <NavigationIconButton\n aria-label={ariaLabel}\n icon={NotificationUnreadIcon}\n onClick={(): void => {\n open();\n }}\n title={label}\n type=\"button\"\n />\n {unreadCount > 0 ? (\n <span className={styles.badge}>\n {unreadCount > 99 ? '99+' : unreadCount}\n </span>\n ) : null}\n </span>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAeA,SAAgB8B,IAAoC;CAClD,IAAM,EAAE,cAAW7B,EAAQ;CAC3B,OAAO8B;AACT;;;;;;AEYA,SAAgBE,EAA0B,EACxC,WAAQ,WAC0B,CAAC,GAAiB;CACpD,IAAM,EAAE,YAASvB,EAAsB,GACjC,EAAE,mBAAgBF,EAAsB,GACxC0B,IAAYC,IAAc,IAAI,GAAGC,EAAM,GAAGD,EAAY,QAAQC;CACpE,OACE,kBAAC,QAAD;EAAM,WAAWpC,EAAO;EAAxB,UAAA,CACE,kBAACqC,GAAD;GACE,cAAYH;GACZ,MAAMI;GACN,eAAqB;IACnB,EAAK;GACP;GACA,OAAOF;GACP,MAAK;EACN,CAAA,GACAD,IAAc,IACb,kBAAC,QAAD;GAAM,WAAWnC,EAAO;GACrB,UAAAmC,IAAc,KAAK,QAAQA;EACxB,CAAA,IACJ,IACA;;AAEV"}
1
+ {"version":3,"file":"index.js","names":["styles","useAuth","formatDateTime","DEFAULT_BPM_LANGUAGE","useBPMOrganizationTimeZone","BPMLanguageProvider","BPMOrganizationTimeZoneProvider","NotificationDrawerProvider","useNotificationUnread","NotificationDrawer","useNotificationDrawer","NotificationUnreadProvider","Providers","defaultBrowserSearchParams","useRouterAdapter","RouterAdapterProvider","createDefaultBPMRoutes","useBPMRoutes","BPMRoutesProvider","useBPMMember","isBPMAdminMember","readOrgUnitOption","readMemberOption","OrgUnitPicker","readPositionOption","PositionPicker","MemberPicker","ApprovalInstanceListPage","BPMFormField","DashboardPage","useBPMLogout","logout","bpm_notification_bell_button_module_default","BPMNotificationBellButton","ariaLabel","unreadCount","label","NavigationIconButton","NotificationUnreadIcon","AuthProvider","BPM_LANGUAGES","useBPMLanguage"],"sources":["../src/lib/use-bpm-logout.ts","../src/components/bpm-notification-bell-button.module.scss","../src/components/bpm-notification-bell-button.tsx"],"sourcesContent":["'use client';\n\nimport { useAuth } from './auth-provider';\n\n/**\n * Drop-in logout handler for host navigation menus. Wraps the BPM auth\n * context's logout flow — calls `logoutApi()` against the BPM REST\n * session endpoint, clears the in-memory member, then redirects to the\n * configured `loginPath`.\n *\n * Hosts mount this on their own logout buttons / menu items so they do\n * not need to touch `useAuth()` directly. To customize the post-logout\n * destination, override the `loginPath` prop on `<BPMNextProviders>` or\n * `<AuthProvider>`.\n */\nexport function useBPMLogout(): () => Promise<void> {\n const { logout } = useAuth();\n return logout;\n}\n",".root {\n display: inline-flex;\n position: relative;\n}\n\n.badge {\n align-items: center;\n background: #d92d20;\n border: 1px solid #fff;\n border-radius: 999px;\n color: #fff;\n display: inline-flex;\n font-size: 10px;\n font-weight: 600;\n height: 16px;\n justify-content: center;\n line-height: 1;\n min-width: 16px;\n padding: 0 4px;\n pointer-events: none;\n position: absolute;\n right: -4px;\n top: -4px;\n}\n","'use client';\n\nimport type { ReactElement } from 'react';\nimport { NavigationIconButton } from '@mezzanine-ui/react';\nimport { NotificationUnreadIcon } from '@mezzanine-ui/icons';\nimport { useNotificationDrawer } from '../lib/notification-drawer-provider';\nimport { useNotificationUnread } from '../lib/notification-unread-provider';\nimport styles from './bpm-notification-bell-button.module.scss';\n\nexport interface BPMNotificationBellButtonProps {\n /** Override the aria-label / tooltip. Defaults to `通知中心`. */\n readonly label?: string;\n}\n\n/**\n * Drop-in notification bell. Reads the unread count from\n * `<NotificationUnreadProvider>`, opens the BPM `<NotificationDrawer />`\n * mounted by `<Providers>` (or `<BPMNextProviders>`) on click, and\n * renders a small red badge with the unread count.\n *\n * Use this when the host navigation wants the BPM notification UX with\n * minimum wiring. Hosts that need a fully custom button can skip this\n * widget and consume `useNotificationDrawer()` + `useNotificationUnread()`\n * directly to wire their own trigger.\n *\n * Visual: uses Mezzanine `NavigationIconButton` so the bell aligns with\n * surrounding Mezzanine navigation chrome. The button is decoupled from\n * the `<Navigation>` container — it does not require a Mezzanine\n * navigation tree to render.\n */\nexport function BPMNotificationBellButton({\n label = '通知中心',\n}: BPMNotificationBellButtonProps = {}): ReactElement {\n const { open } = useNotificationDrawer();\n const { unreadCount } = useNotificationUnread();\n const ariaLabel = unreadCount > 0 ? `${label},${unreadCount} 則未讀` : label;\n return (\n <span className={styles.root}>\n <NavigationIconButton\n aria-label={ariaLabel}\n icon={NotificationUnreadIcon}\n onClick={(): void => {\n open();\n }}\n title={label}\n type=\"button\"\n />\n {unreadCount > 0 ? (\n <span className={styles.badge}>\n {unreadCount > 99 ? '99+' : unreadCount}\n </span>\n ) : null}\n </span>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAeA,SAAgB8B,IAAoC;CAClD,IAAM,EAAE,cAAW7B,EAAQ;CAC3B,OAAO8B;AACT;;;;;;AEYA,SAAgBE,EAA0B,EACxC,WAAQ,WAC0B,CAAC,GAAiB;CACpD,IAAM,EAAE,YAASvB,EAAsB,GACjC,EAAE,mBAAgBF,EAAsB,GACxC0B,IAAYC,IAAc,IAAI,GAAGC,EAAM,GAAGD,EAAY,QAAQC;CACpE,OACE,kBAAC,QAAD;EAAM,WAAWpC,EAAO;EAAxB,UAAA,CACE,kBAACqC,GAAD;GACE,cAAYH;GACZ,MAAMI;GACN,eAAqB;IACnB,EAAK;GACP;GACA,OAAOF;GACP,MAAK;EACN,CAAA,GACAD,IAAc,IACb,kBAAC,QAAD;GAAM,WAAWnC,EAAO;GACrB,UAAAmC,IAAc,KAAK,QAAQA;EACxB,CAAA,IACJ,IACA;;AAEV"}
@@ -0,0 +1,20 @@
1
+ import { ReactElement, ReactNode } from 'react';
2
+ interface BPMOrganizationTimeZoneProviderProps {
3
+ readonly children: ReactNode;
4
+ /**
5
+ * IANA zone the host passes to `BPMRootModule` as `organizationTimeZone`.
6
+ * Blank or missing means `DEFAULT_BPM_ORGANIZATION_TIME_ZONE`; an unknown
7
+ * zone falls back to it with a console warning.
8
+ */
9
+ readonly timeZone?: string;
10
+ }
11
+ /**
12
+ * Tells BPM views which zone "today" is read in against organization effective
13
+ * dates, so the template designer's position pickers and dry run agree with
14
+ * the engine's `organizationTimeZone`.
15
+ *
16
+ * Outside this provider `useBPMOrganizationTimeZone()` answers the default.
17
+ */
18
+ export declare function BPMOrganizationTimeZoneProvider({ children, timeZone, }: BPMOrganizationTimeZoneProviderProps): ReactElement;
19
+ export declare function useBPMOrganizationTimeZone(): string;
20
+ export {};
@@ -0,0 +1,20 @@
1
+ import { ReactElement, ReactNode } from 'react';
2
+ interface BPMOrganizationTimeZoneProviderProps {
3
+ readonly children: ReactNode;
4
+ /**
5
+ * IANA zone the host passes to `BPMRootModule` as `organizationTimeZone`.
6
+ * Blank or missing means `DEFAULT_BPM_ORGANIZATION_TIME_ZONE`; an unknown
7
+ * zone falls back to it with a console warning.
8
+ */
9
+ readonly timeZone?: string;
10
+ }
11
+ /**
12
+ * Tells BPM views which zone "today" is read in against organization effective
13
+ * dates, so the template designer's position pickers and dry run agree with
14
+ * the engine's `organizationTimeZone`.
15
+ *
16
+ * Outside this provider `useBPMOrganizationTimeZone()` answers the default.
17
+ */
18
+ export declare function BPMOrganizationTimeZoneProvider({ children, timeZone, }: BPMOrganizationTimeZoneProviderProps): ReactElement;
19
+ export declare function useBPMOrganizationTimeZone(): string;
20
+ export {};
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Applies the default to a host's `organizationTimeZone`, as the engine's
3
+ * `resolveBPMOrganizationTimeZone` does: blank or missing reads as
4
+ * `DEFAULT_BPM_ORGANIZATION_TIME_ZONE`. Where the engine refuses to start on a
5
+ * zone `Intl` does not know, the browser falls back to the default and warns
6
+ * once per value, so a typo cannot blank the designer.
7
+ */
8
+ export declare function resolveOrganizationTimeZone(value: string | null | undefined): string;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Applies the default to a host's `organizationTimeZone`, as the engine's
3
+ * `resolveBPMOrganizationTimeZone` does: blank or missing reads as
4
+ * `DEFAULT_BPM_ORGANIZATION_TIME_ZONE`. Where the engine refuses to start on a
5
+ * zone `Intl` does not know, the browser falls back to the default and warns
6
+ * once per value, so a typo cannot blank the designer.
7
+ */
8
+ export declare function resolveOrganizationTimeZone(value: string | null | undefined): string;
@@ -11,6 +11,14 @@ interface ProvidersProps {
11
11
  * Independent of `locale`, which only formats calendar dates.
12
12
  */
13
13
  readonly language?: BPMLanguage;
14
+ /**
15
+ * IANA zone in which BPM views read "today" against organization effective
16
+ * dates. Pass the same value given to `BPMRootModule` as
17
+ * `organizationTimeZone`; leave it unset when the API does not set one.
18
+ * Defaults to `DEFAULT_BPM_ORGANIZATION_TIME_ZONE` (`'Asia/Taipei'`); an
19
+ * unknown zone falls back to it with a console warning.
20
+ */
21
+ readonly organizationTimeZone?: string;
14
22
  /**
15
23
  * Public paths that should not trigger redirect to `/login` when there
16
24
  * is no session. Forwarded to `<AuthProvider>`. Defaults to `['/login']`.
@@ -24,6 +32,7 @@ interface ProvidersProps {
24
32
  *
25
33
  * - Mezzanine UI calendar locale (moment-based, `ZH_TW` by default)
26
34
  * - `<BPMLanguageProvider>` (message language, `'zh-TW'` by default)
35
+ * - `<BPMOrganizationTimeZoneProvider>` (organization "today" zone)
27
36
  * - `<AuthProvider>` (BPM session via REST `/auth/*`)
28
37
  * - `<NotificationUnreadProvider>` (polls unread count)
29
38
  * - `<NotificationDrawerProvider>` (controls drawer open/close state)
@@ -35,5 +44,5 @@ interface ProvidersProps {
35
44
  * by the `pages/*` subpath shims when consuming via Next.js, or wired by
36
45
  * hand for other frameworks).
37
46
  */
38
- export declare function Providers({ children, locale, language, publicPaths, loginPath, }: ProvidersProps): ReactElement;
47
+ export declare function Providers({ children, locale, language, organizationTimeZone, publicPaths, loginPath, }: ProvidersProps): ReactElement;
39
48
  export {};
@@ -11,6 +11,14 @@ interface ProvidersProps {
11
11
  * Independent of `locale`, which only formats calendar dates.
12
12
  */
13
13
  readonly language?: BPMLanguage;
14
+ /**
15
+ * IANA zone in which BPM views read "today" against organization effective
16
+ * dates. Pass the same value given to `BPMRootModule` as
17
+ * `organizationTimeZone`; leave it unset when the API does not set one.
18
+ * Defaults to `DEFAULT_BPM_ORGANIZATION_TIME_ZONE` (`'Asia/Taipei'`); an
19
+ * unknown zone falls back to it with a console warning.
20
+ */
21
+ readonly organizationTimeZone?: string;
14
22
  /**
15
23
  * Public paths that should not trigger redirect to `/login` when there
16
24
  * is no session. Forwarded to `<AuthProvider>`. Defaults to `['/login']`.
@@ -24,6 +32,7 @@ interface ProvidersProps {
24
32
  *
25
33
  * - Mezzanine UI calendar locale (moment-based, `ZH_TW` by default)
26
34
  * - `<BPMLanguageProvider>` (message language, `'zh-TW'` by default)
35
+ * - `<BPMOrganizationTimeZoneProvider>` (organization "today" zone)
27
36
  * - `<AuthProvider>` (BPM session via REST `/auth/*`)
28
37
  * - `<NotificationUnreadProvider>` (polls unread count)
29
38
  * - `<NotificationDrawerProvider>` (controls drawer open/close state)
@@ -35,5 +44,5 @@ interface ProvidersProps {
35
44
  * by the `pages/*` subpath shims when consuming via Next.js, or wired by
36
45
  * hand for other frameworks).
37
46
  */
38
- export declare function Providers({ children, locale, language, publicPaths, loginPath, }: ProvidersProps): ReactElement;
47
+ export declare function Providers({ children, locale, language, organizationTimeZone, publicPaths, loginPath, }: ProvidersProps): ReactElement;
39
48
  export {};
@@ -13,6 +13,13 @@ export interface BPMNextProvidersProps {
13
13
  * `<Providers>`. Defaults to `'zh-TW'`.
14
14
  */
15
15
  readonly language?: BPMLanguage;
16
+ /**
17
+ * IANA zone in which BPM views read "today" against organization effective
18
+ * dates — the `organizationTimeZone` given to `BPMRootModule`. Forwarded to
19
+ * the inner `<Providers>`. Defaults to `'Asia/Taipei'`
20
+ * (`DEFAULT_BPM_ORGANIZATION_TIME_ZONE`).
21
+ */
22
+ readonly organizationTimeZone?: string;
16
23
  /**
17
24
  * Routes that must remain accessible without a BPM session — visiting
18
25
  * these does not redirect to `loginPath` even when `member` is null.
@@ -54,4 +61,4 @@ export interface BPMNextProvidersProps {
54
61
  * (BPM internal navigation), additionally wrap with
55
62
  * `<BPMRoutesProvider>` exported from the same subpath.
56
63
  */
57
- export declare function BPMNextProviders({ children, locale, language, publicPaths, loginPath, }: BPMNextProvidersProps): ReactElement;
64
+ export declare function BPMNextProviders({ children, locale, language, organizationTimeZone, publicPaths, loginPath, }: BPMNextProvidersProps): ReactElement;
@@ -13,6 +13,13 @@ export interface BPMNextProvidersProps {
13
13
  * `<Providers>`. Defaults to `'zh-TW'`.
14
14
  */
15
15
  readonly language?: BPMLanguage;
16
+ /**
17
+ * IANA zone in which BPM views read "today" against organization effective
18
+ * dates — the `organizationTimeZone` given to `BPMRootModule`. Forwarded to
19
+ * the inner `<Providers>`. Defaults to `'Asia/Taipei'`
20
+ * (`DEFAULT_BPM_ORGANIZATION_TIME_ZONE`).
21
+ */
22
+ readonly organizationTimeZone?: string;
16
23
  /**
17
24
  * Routes that must remain accessible without a BPM session — visiting
18
25
  * these does not redirect to `loginPath` even when `member` is null.
@@ -54,4 +61,4 @@ export interface BPMNextProvidersProps {
54
61
  * (BPM internal navigation), additionally wrap with
55
62
  * `<BPMRoutesProvider>` exported from the same subpath.
56
63
  */
57
- export declare function BPMNextProviders({ children, locale, language, publicPaths, loginPath, }: BPMNextProvidersProps): ReactElement;
64
+ export declare function BPMNextProviders({ children, locale, language, organizationTimeZone, publicPaths, loginPath, }: BPMNextProvidersProps): ReactElement;
@@ -1,2 +1,2 @@
1
- "use client";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../chunks/providers-D_KAgOOu.cjs"),t=require("../chunks/router-adapter--gYs13E8.cjs"),n=require("../chunks/routes-config-C1j_vFKo.cjs");let r=require("react"),i=require("react/jsx-runtime"),a=require("next/navigation");function o({children:n,locale:o,language:s,publicPaths:c,loginPath:l}){let u=(0,a.useRouter)(),d=(0,a.usePathname)(),f=(0,r.useMemo)(()=>({pathname:d,push:e=>u.push(e),replace:e=>u.replace(e),back:()=>u.back(),searchParams:()=>t.n()}),[u,d]);return(0,i.jsx)(t.t,{value:f,children:(0,i.jsx)(e.t,{locale:o,language:s,publicPaths:c,loginPath:l,children:n})})}exports.BPMNextProviders=o,exports.BPMRoutesProvider=n.t,exports.createDefaultBPMRoutes=n.n,exports.useBPMRoutes=n.r;
1
+ "use client";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../chunks/providers-VUOh6i0i.cjs"),t=require("../chunks/router-adapter--gYs13E8.cjs"),n=require("../chunks/routes-config-C1j_vFKo.cjs");let r=require("react"),i=require("react/jsx-runtime"),a=require("next/navigation");function o({children:n,locale:o,language:s,organizationTimeZone:c,publicPaths:l,loginPath:u}){let d=(0,a.useRouter)(),f=(0,a.usePathname)(),p=(0,r.useMemo)(()=>({pathname:f,push:e=>d.push(e),replace:e=>d.replace(e),back:()=>d.back(),searchParams:()=>t.n()}),[d,f]);return(0,i.jsx)(t.t,{value:p,children:(0,i.jsx)(e.t,{locale:o,language:s,organizationTimeZone:c,publicPaths:l,loginPath:u,children:n})})}exports.BPMNextProviders=o,exports.BPMRoutesProvider=n.t,exports.createDefaultBPMRoutes=n.n,exports.useBPMRoutes=n.r;
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["useRouter","usePathname","useMemo","defaultBrowserSearchParams","RouterAdapterProvider","BPMProviders","require_providers","require_router_adapter","require_routes_config","react","react_jsx_runtime","next_navigation","BPMNextProviders","nextRouter","pathname","adapter","href"],"sources":["../../src/next/BPMNextProviders.tsx"],"sourcesContent":["'use client';\n\n// One-line Next.js App Router shim for BPM. Reads `next/navigation`\n// router/pathname hooks, builds a `RouterAdapter`, then composes\n// `<RouterAdapterProvider>` and the shared `<BPMProviders>`.\n//\n// IMPORTANT: This component intentionally does NOT call\n// `useSearchParams()`. Doing so triggers Next.js's CSR bailout at static\n// prerender, and any `<Suspense fallback={null}>` we wrap around it then\n// erases `<RouterAdapterProvider>` from the SSR/hydration tree — leaving\n// every BPM view to throw \"must be used inside <RouterAdapterProvider>\"\n// when it mounts. None of the shipped BPM views consume\n// `RouterAdapter.searchParams()` anyway; hosts that need reactive query\n// string state should call Next's `useSearchParams()` directly in their\n// page (with their own Suspense). The lazy default getter below is\n// good enough for read-on-demand callers.\n\nimport { useMemo, type ReactElement, type ReactNode } from 'react';\nimport { usePathname, useRouter } from 'next/navigation';\nimport type { CalendarLocale } from '@mezzanine-ui/react/moment';\nimport type { BPMLanguage } from '../lib/language-provider';\nimport { Providers as BPMProviders } from '../lib/providers';\nimport {\n RouterAdapterProvider,\n defaultBrowserSearchParams,\n type RouterAdapter,\n} from '../lib/router-adapter';\n\nexport interface BPMNextProvidersProps {\n readonly children: ReactNode;\n /**\n * Override the Mezzanine calendar locale. Forwarded to the inner\n * `<Providers>`. Defaults to `CalendarLocale.ZH_TW`.\n */\n readonly locale?: CalendarLocale;\n /**\n * Language BPM views write their messages in. Forwarded to the inner\n * `<Providers>`. Defaults to `'zh-TW'`.\n */\n readonly language?: BPMLanguage;\n /**\n * Routes that must remain accessible without a BPM session — visiting\n * these does not redirect to `loginPath` even when `member` is null.\n * Forwarded to `<AuthProvider>`. Defaults to `['/login']`.\n *\n * Hosts that mount BPM under a non-root prefix (see\n * `<BPMRoutesProvider>`) should typically expand this to include their\n * own auth-bypass paths.\n */\n readonly publicPaths?: readonly string[];\n /**\n * Where to redirect unauthenticated users. Forwarded to\n * `<AuthProvider>`. Defaults to `'/login'`. Hosts owning their own\n * login route (the recommended case) override this to their host\n * route, e.g. `'/auth/sign-in'`.\n */\n readonly loginPath?: string;\n}\n\n/**\n * One-line Next.js App Router shim for the full BPM provider stack.\n * Mounts in the host's root layout (or layout for the BPM sub-tree):\n *\n * ```tsx\n * import { BPMNextProviders } from '@rytass/bpm-core-react/next';\n *\n * export default function RootLayout({ children }) {\n * return (\n * <html><body>\n * <BPMNextProviders loginPath=\"/auth/sign-in\">\n * {children}\n * </BPMNextProviders>\n * </body></html>\n * );\n * }\n * ```\n *\n * Forwards every prop on {@link BPMNextProvidersProps} to the inner\n * `<Providers>` and `<AuthProvider>`. For per-feature path remapping\n * (BPM internal navigation), additionally wrap with\n * `<BPMRoutesProvider>` exported from the same subpath.\n */\nexport function BPMNextProviders({\n children,\n locale,\n language,\n publicPaths,\n loginPath,\n}: BPMNextProvidersProps): ReactElement {\n const nextRouter = useRouter();\n const pathname = usePathname();\n\n const adapter = useMemo<RouterAdapter>(\n () => ({\n pathname,\n push: (href: string): void => nextRouter.push(href),\n replace: (href: string): void => nextRouter.replace(href),\n back: (): void => nextRouter.back(),\n searchParams: (): URLSearchParams => defaultBrowserSearchParams(),\n }),\n [nextRouter, pathname],\n );\n\n return (\n <RouterAdapterProvider value={adapter}>\n <BPMProviders\n locale={locale}\n language={language}\n publicPaths={publicPaths}\n loginPath={loginPath}\n >\n {children}\n </BPMProviders>\n </RouterAdapterProvider>\n );\n}\n"],"mappings":"A,4TAkFA,SAAgBY,EAAiB,CAC/B,WACA,SACA,WACA,cACA,aACsC,CACtC,IAAMC,GAAA,EAAab,EAAAA,UAAAA,CAAU,EACvBc,GAAA,EAAWb,EAAAA,YAAAA,CAAY,EAEvBc,GAAA,EAAUb,EAAAA,QAAAA,MACP,CACL,WACA,KAAO,GAAuBW,EAAW,KAAKG,CAAI,EAClD,QAAU,GAAuBH,EAAW,QAAQG,CAAI,EACxD,SAAkBH,EAAW,KAAK,EAClC,iBAAqCV,EAAAA,EAA2B,CAClE,GACA,CAACU,EAAYC,CAAQ,CACvB,EAEA,OACE,EAAAJ,EAAA,IAAA,CAACN,EAAAA,EAAD,CAAuB,MAAOW,EAC5B,UAAA,EAAAL,EAAA,IAAA,CAACL,EAAAA,EAAD,CACU,SACE,WACG,cACF,YAEV,UACW,CAAA,CACO,CAAA,CAE3B,C"}
1
+ {"version":3,"file":"index.cjs","names":["useRouter","usePathname","useMemo","defaultBrowserSearchParams","RouterAdapterProvider","BPMProviders","require_providers","require_router_adapter","require_routes_config","react","react_jsx_runtime","next_navigation","BPMNextProviders","nextRouter","pathname","adapter","href"],"sources":["../../src/next/BPMNextProviders.tsx"],"sourcesContent":["'use client';\n\n// One-line Next.js App Router shim for BPM. Reads `next/navigation`\n// router/pathname hooks, builds a `RouterAdapter`, then composes\n// `<RouterAdapterProvider>` and the shared `<BPMProviders>`.\n//\n// IMPORTANT: This component intentionally does NOT call\n// `useSearchParams()`. Doing so triggers Next.js's CSR bailout at static\n// prerender, and any `<Suspense fallback={null}>` we wrap around it then\n// erases `<RouterAdapterProvider>` from the SSR/hydration tree — leaving\n// every BPM view to throw \"must be used inside <RouterAdapterProvider>\"\n// when it mounts. None of the shipped BPM views consume\n// `RouterAdapter.searchParams()` anyway; hosts that need reactive query\n// string state should call Next's `useSearchParams()` directly in their\n// page (with their own Suspense). The lazy default getter below is\n// good enough for read-on-demand callers.\n\nimport { useMemo, type ReactElement, type ReactNode } from 'react';\nimport { usePathname, useRouter } from 'next/navigation';\nimport type { CalendarLocale } from '@mezzanine-ui/react/moment';\nimport type { BPMLanguage } from '../lib/language-provider';\nimport { Providers as BPMProviders } from '../lib/providers';\nimport {\n RouterAdapterProvider,\n defaultBrowserSearchParams,\n type RouterAdapter,\n} from '../lib/router-adapter';\n\nexport interface BPMNextProvidersProps {\n readonly children: ReactNode;\n /**\n * Override the Mezzanine calendar locale. Forwarded to the inner\n * `<Providers>`. Defaults to `CalendarLocale.ZH_TW`.\n */\n readonly locale?: CalendarLocale;\n /**\n * Language BPM views write their messages in. Forwarded to the inner\n * `<Providers>`. Defaults to `'zh-TW'`.\n */\n readonly language?: BPMLanguage;\n /**\n * IANA zone in which BPM views read \"today\" against organization effective\n * dates — the `organizationTimeZone` given to `BPMRootModule`. Forwarded to\n * the inner `<Providers>`. Defaults to `'Asia/Taipei'`\n * (`DEFAULT_BPM_ORGANIZATION_TIME_ZONE`).\n */\n readonly organizationTimeZone?: string;\n /**\n * Routes that must remain accessible without a BPM session — visiting\n * these does not redirect to `loginPath` even when `member` is null.\n * Forwarded to `<AuthProvider>`. Defaults to `['/login']`.\n *\n * Hosts that mount BPM under a non-root prefix (see\n * `<BPMRoutesProvider>`) should typically expand this to include their\n * own auth-bypass paths.\n */\n readonly publicPaths?: readonly string[];\n /**\n * Where to redirect unauthenticated users. Forwarded to\n * `<AuthProvider>`. Defaults to `'/login'`. Hosts owning their own\n * login route (the recommended case) override this to their host\n * route, e.g. `'/auth/sign-in'`.\n */\n readonly loginPath?: string;\n}\n\n/**\n * One-line Next.js App Router shim for the full BPM provider stack.\n * Mounts in the host's root layout (or layout for the BPM sub-tree):\n *\n * ```tsx\n * import { BPMNextProviders } from '@rytass/bpm-core-react/next';\n *\n * export default function RootLayout({ children }) {\n * return (\n * <html><body>\n * <BPMNextProviders loginPath=\"/auth/sign-in\">\n * {children}\n * </BPMNextProviders>\n * </body></html>\n * );\n * }\n * ```\n *\n * Forwards every prop on {@link BPMNextProvidersProps} to the inner\n * `<Providers>` and `<AuthProvider>`. For per-feature path remapping\n * (BPM internal navigation), additionally wrap with\n * `<BPMRoutesProvider>` exported from the same subpath.\n */\nexport function BPMNextProviders({\n children,\n locale,\n language,\n organizationTimeZone,\n publicPaths,\n loginPath,\n}: BPMNextProvidersProps): ReactElement {\n const nextRouter = useRouter();\n const pathname = usePathname();\n\n const adapter = useMemo<RouterAdapter>(\n () => ({\n pathname,\n push: (href: string): void => nextRouter.push(href),\n replace: (href: string): void => nextRouter.replace(href),\n back: (): void => nextRouter.back(),\n searchParams: (): URLSearchParams => defaultBrowserSearchParams(),\n }),\n [nextRouter, pathname],\n );\n\n return (\n <RouterAdapterProvider value={adapter}>\n <BPMProviders\n locale={locale}\n language={language}\n organizationTimeZone={organizationTimeZone}\n publicPaths={publicPaths}\n loginPath={loginPath}\n >\n {children}\n </BPMProviders>\n </RouterAdapterProvider>\n );\n}\n"],"mappings":"A,4TAyFA,SAAgBY,EAAiB,CAC/B,WACA,SACA,WACA,uBACA,cACA,aACsC,CACtC,IAAMC,GAAA,EAAab,EAAAA,UAAAA,CAAU,EACvBc,GAAA,EAAWb,EAAAA,YAAAA,CAAY,EAEvBc,GAAA,EAAUb,EAAAA,QAAAA,MACP,CACL,WACA,KAAO,GAAuBW,EAAW,KAAKG,CAAI,EAClD,QAAU,GAAuBH,EAAW,QAAQG,CAAI,EACxD,SAAkBH,EAAW,KAAK,EAClC,iBAAqCV,EAAAA,EAA2B,CAClE,GACA,CAACU,EAAYC,CAAQ,CACvB,EAEA,OACE,EAAAJ,EAAA,IAAA,CAACN,EAAAA,EAAD,CAAuB,MAAOW,EAC5B,UAAA,EAAAL,EAAA,IAAA,CAACL,EAAAA,EAAD,CACU,SACE,WACY,uBACT,cACF,YAEV,UACW,CAAA,CACO,CAAA,CAE3B,C"}
@@ -1,26 +1,27 @@
1
1
  "use client";
2
- import { t as e } from "../chunks/providers-BbCs23sk.js";
2
+ import { t as e } from "../chunks/providers-CQWekD7_.js";
3
3
  import { n as t, t as n } from "../chunks/router-adapter-CkxCyzf5.js";
4
4
  import { n as r, r as i, t as a } from "../chunks/routes-config-BcNaWJWJ.js";
5
5
  import { useMemo as o } from "react";
6
6
  import { jsx as s } from "react/jsx-runtime";
7
7
  import { usePathname as c, useRouter as l } from "next/navigation";
8
8
  // src/next/BPMNextProviders.tsx
9
- function u({ children: r, locale: i, language: a, publicPaths: u, loginPath: d }) {
10
- let f = l(), p = c(), m = o(() => ({
11
- pathname: p,
12
- push: (e) => f.push(e),
13
- replace: (e) => f.replace(e),
14
- back: () => f.back(),
9
+ function u({ children: r, locale: i, language: a, organizationTimeZone: u, publicPaths: d, loginPath: f }) {
10
+ let p = l(), m = c(), h = o(() => ({
11
+ pathname: m,
12
+ push: (e) => p.push(e),
13
+ replace: (e) => p.replace(e),
14
+ back: () => p.back(),
15
15
  searchParams: () => t()
16
- }), [f, p]);
16
+ }), [p, m]);
17
17
  return /* @__PURE__ */ s(n, {
18
- value: m,
18
+ value: h,
19
19
  children: /* @__PURE__ */ s(e, {
20
20
  locale: i,
21
21
  language: a,
22
- publicPaths: u,
23
- loginPath: d,
22
+ organizationTimeZone: u,
23
+ publicPaths: d,
24
+ loginPath: f,
24
25
  children: r
25
26
  })
26
27
  });
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["BPMProviders","Providers","defaultBrowserSearchParams","RouterAdapterProvider","createDefaultBPMRoutes","useBPMRoutes","BPMRoutesProvider","BPMNextProviders","nextRouter","useRouter","pathname","usePathname","adapter","useMemo","href"],"sources":["../../src/next/BPMNextProviders.tsx"],"sourcesContent":["'use client';\n\n// One-line Next.js App Router shim for BPM. Reads `next/navigation`\n// router/pathname hooks, builds a `RouterAdapter`, then composes\n// `<RouterAdapterProvider>` and the shared `<BPMProviders>`.\n//\n// IMPORTANT: This component intentionally does NOT call\n// `useSearchParams()`. Doing so triggers Next.js's CSR bailout at static\n// prerender, and any `<Suspense fallback={null}>` we wrap around it then\n// erases `<RouterAdapterProvider>` from the SSR/hydration tree — leaving\n// every BPM view to throw \"must be used inside <RouterAdapterProvider>\"\n// when it mounts. None of the shipped BPM views consume\n// `RouterAdapter.searchParams()` anyway; hosts that need reactive query\n// string state should call Next's `useSearchParams()` directly in their\n// page (with their own Suspense). The lazy default getter below is\n// good enough for read-on-demand callers.\n\nimport { useMemo, type ReactElement, type ReactNode } from 'react';\nimport { usePathname, useRouter } from 'next/navigation';\nimport type { CalendarLocale } from '@mezzanine-ui/react/moment';\nimport type { BPMLanguage } from '../lib/language-provider';\nimport { Providers as BPMProviders } from '../lib/providers';\nimport {\n RouterAdapterProvider,\n defaultBrowserSearchParams,\n type RouterAdapter,\n} from '../lib/router-adapter';\n\nexport interface BPMNextProvidersProps {\n readonly children: ReactNode;\n /**\n * Override the Mezzanine calendar locale. Forwarded to the inner\n * `<Providers>`. Defaults to `CalendarLocale.ZH_TW`.\n */\n readonly locale?: CalendarLocale;\n /**\n * Language BPM views write their messages in. Forwarded to the inner\n * `<Providers>`. Defaults to `'zh-TW'`.\n */\n readonly language?: BPMLanguage;\n /**\n * Routes that must remain accessible without a BPM session — visiting\n * these does not redirect to `loginPath` even when `member` is null.\n * Forwarded to `<AuthProvider>`. Defaults to `['/login']`.\n *\n * Hosts that mount BPM under a non-root prefix (see\n * `<BPMRoutesProvider>`) should typically expand this to include their\n * own auth-bypass paths.\n */\n readonly publicPaths?: readonly string[];\n /**\n * Where to redirect unauthenticated users. Forwarded to\n * `<AuthProvider>`. Defaults to `'/login'`. Hosts owning their own\n * login route (the recommended case) override this to their host\n * route, e.g. `'/auth/sign-in'`.\n */\n readonly loginPath?: string;\n}\n\n/**\n * One-line Next.js App Router shim for the full BPM provider stack.\n * Mounts in the host's root layout (or layout for the BPM sub-tree):\n *\n * ```tsx\n * import { BPMNextProviders } from '@rytass/bpm-core-react/next';\n *\n * export default function RootLayout({ children }) {\n * return (\n * <html><body>\n * <BPMNextProviders loginPath=\"/auth/sign-in\">\n * {children}\n * </BPMNextProviders>\n * </body></html>\n * );\n * }\n * ```\n *\n * Forwards every prop on {@link BPMNextProvidersProps} to the inner\n * `<Providers>` and `<AuthProvider>`. For per-feature path remapping\n * (BPM internal navigation), additionally wrap with\n * `<BPMRoutesProvider>` exported from the same subpath.\n */\nexport function BPMNextProviders({\n children,\n locale,\n language,\n publicPaths,\n loginPath,\n}: BPMNextProvidersProps): ReactElement {\n const nextRouter = useRouter();\n const pathname = usePathname();\n\n const adapter = useMemo<RouterAdapter>(\n () => ({\n pathname,\n push: (href: string): void => nextRouter.push(href),\n replace: (href: string): void => nextRouter.replace(href),\n back: (): void => nextRouter.back(),\n searchParams: (): URLSearchParams => defaultBrowserSearchParams(),\n }),\n [nextRouter, pathname],\n );\n\n return (\n <RouterAdapterProvider value={adapter}>\n <BPMProviders\n locale={locale}\n language={language}\n publicPaths={publicPaths}\n loginPath={loginPath}\n >\n {children}\n </BPMProviders>\n </RouterAdapterProvider>\n );\n}\n"],"mappings":";;;;;;;;AAkFA,SAAgBO,EAAiB,EAC/B,aACA,WACA,aACA,gBACA,gBACsC;CACtC,IAAMC,IAAaC,EAAU,GACvBC,IAAWC,EAAY,GAEvBC,IAAUC,SACP;EACL;EACA,OAAO,MAAuBL,EAAW,KAAKM,CAAI;EAClD,UAAU,MAAuBN,EAAW,QAAQM,CAAI;EACxD,YAAkBN,EAAW,KAAK;EAClC,oBAAqCN,EAA2B;CAClE,IACA,CAACM,GAAYE,CAAQ,CACvB;CAEA,OACE,kBAACP,GAAD;EAAuB,OAAOS;EAC5B,UAAA,kBAACZ,GAAD;GACU;GACE;GACG;GACF;GAEV;EACW,CAAA;CACO,CAAA;AAE3B"}
1
+ {"version":3,"file":"index.js","names":["BPMProviders","Providers","defaultBrowserSearchParams","RouterAdapterProvider","createDefaultBPMRoutes","useBPMRoutes","BPMRoutesProvider","BPMNextProviders","nextRouter","useRouter","pathname","usePathname","adapter","useMemo","href"],"sources":["../../src/next/BPMNextProviders.tsx"],"sourcesContent":["'use client';\n\n// One-line Next.js App Router shim for BPM. Reads `next/navigation`\n// router/pathname hooks, builds a `RouterAdapter`, then composes\n// `<RouterAdapterProvider>` and the shared `<BPMProviders>`.\n//\n// IMPORTANT: This component intentionally does NOT call\n// `useSearchParams()`. Doing so triggers Next.js's CSR bailout at static\n// prerender, and any `<Suspense fallback={null}>` we wrap around it then\n// erases `<RouterAdapterProvider>` from the SSR/hydration tree — leaving\n// every BPM view to throw \"must be used inside <RouterAdapterProvider>\"\n// when it mounts. None of the shipped BPM views consume\n// `RouterAdapter.searchParams()` anyway; hosts that need reactive query\n// string state should call Next's `useSearchParams()` directly in their\n// page (with their own Suspense). The lazy default getter below is\n// good enough for read-on-demand callers.\n\nimport { useMemo, type ReactElement, type ReactNode } from 'react';\nimport { usePathname, useRouter } from 'next/navigation';\nimport type { CalendarLocale } from '@mezzanine-ui/react/moment';\nimport type { BPMLanguage } from '../lib/language-provider';\nimport { Providers as BPMProviders } from '../lib/providers';\nimport {\n RouterAdapterProvider,\n defaultBrowserSearchParams,\n type RouterAdapter,\n} from '../lib/router-adapter';\n\nexport interface BPMNextProvidersProps {\n readonly children: ReactNode;\n /**\n * Override the Mezzanine calendar locale. Forwarded to the inner\n * `<Providers>`. Defaults to `CalendarLocale.ZH_TW`.\n */\n readonly locale?: CalendarLocale;\n /**\n * Language BPM views write their messages in. Forwarded to the inner\n * `<Providers>`. Defaults to `'zh-TW'`.\n */\n readonly language?: BPMLanguage;\n /**\n * IANA zone in which BPM views read \"today\" against organization effective\n * dates — the `organizationTimeZone` given to `BPMRootModule`. Forwarded to\n * the inner `<Providers>`. Defaults to `'Asia/Taipei'`\n * (`DEFAULT_BPM_ORGANIZATION_TIME_ZONE`).\n */\n readonly organizationTimeZone?: string;\n /**\n * Routes that must remain accessible without a BPM session — visiting\n * these does not redirect to `loginPath` even when `member` is null.\n * Forwarded to `<AuthProvider>`. Defaults to `['/login']`.\n *\n * Hosts that mount BPM under a non-root prefix (see\n * `<BPMRoutesProvider>`) should typically expand this to include their\n * own auth-bypass paths.\n */\n readonly publicPaths?: readonly string[];\n /**\n * Where to redirect unauthenticated users. Forwarded to\n * `<AuthProvider>`. Defaults to `'/login'`. Hosts owning their own\n * login route (the recommended case) override this to their host\n * route, e.g. `'/auth/sign-in'`.\n */\n readonly loginPath?: string;\n}\n\n/**\n * One-line Next.js App Router shim for the full BPM provider stack.\n * Mounts in the host's root layout (or layout for the BPM sub-tree):\n *\n * ```tsx\n * import { BPMNextProviders } from '@rytass/bpm-core-react/next';\n *\n * export default function RootLayout({ children }) {\n * return (\n * <html><body>\n * <BPMNextProviders loginPath=\"/auth/sign-in\">\n * {children}\n * </BPMNextProviders>\n * </body></html>\n * );\n * }\n * ```\n *\n * Forwards every prop on {@link BPMNextProvidersProps} to the inner\n * `<Providers>` and `<AuthProvider>`. For per-feature path remapping\n * (BPM internal navigation), additionally wrap with\n * `<BPMRoutesProvider>` exported from the same subpath.\n */\nexport function BPMNextProviders({\n children,\n locale,\n language,\n organizationTimeZone,\n publicPaths,\n loginPath,\n}: BPMNextProvidersProps): ReactElement {\n const nextRouter = useRouter();\n const pathname = usePathname();\n\n const adapter = useMemo<RouterAdapter>(\n () => ({\n pathname,\n push: (href: string): void => nextRouter.push(href),\n replace: (href: string): void => nextRouter.replace(href),\n back: (): void => nextRouter.back(),\n searchParams: (): URLSearchParams => defaultBrowserSearchParams(),\n }),\n [nextRouter, pathname],\n );\n\n return (\n <RouterAdapterProvider value={adapter}>\n <BPMProviders\n locale={locale}\n language={language}\n organizationTimeZone={organizationTimeZone}\n publicPaths={publicPaths}\n loginPath={loginPath}\n >\n {children}\n </BPMProviders>\n </RouterAdapterProvider>\n );\n}\n"],"mappings":";;;;;;;;AAyFA,SAAgBO,EAAiB,EAC/B,aACA,WACA,aACA,yBACA,gBACA,gBACsC;CACtC,IAAMC,IAAaC,EAAU,GACvBC,IAAWC,EAAY,GAEvBC,IAAUC,SACP;EACL;EACA,OAAO,MAAuBL,EAAW,KAAKM,CAAI;EAClD,UAAU,MAAuBN,EAAW,QAAQM,CAAI;EACxD,YAAkBN,EAAW,KAAK;EAClC,oBAAqCN,EAA2B;CAClE,IACA,CAACM,GAAYE,CAAQ,CACvB;CAEA,OACE,kBAACP,GAAD;EAAuB,OAAOS;EAC5B,UAAA,kBAACZ,GAAD;GACU;GACE;GACY;GACT;GACF;GAEV;EACW,CAAA;CACO,CAAA;AAE3B"}
@@ -1,4 +1,4 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../chunks/workflow-chat-prompt-B803qVdt.cjs");let t=require("ai"),n=require("@ai-sdk/openai"),r=require("@rytass/bpm-core-shared");var i=`gpt-5.4-mini`;function a(e){return(0,n.openai)(e.replace(/^openai\//u,``))}function o(){let e=r.WORKFLOW_TOOLSET.map(e=>[e.name,(0,t.tool)({description:e.description,inputSchema:(0,t.jsonSchema)(e.inputSchema)})]);return Object.fromEntries(e)}var s=1048576,c=200,l=4096,u=(e,t)=>new Response(t,{headers:{"Content-Type":`text/plain; charset=utf-8`},status:e});function d(e){return typeof e==`function`?e():e??process.env.BPM_AI_ASSISTANT_ENABLED===`true`}async function f(e,t){if(!t)return!0;try{return await t(e)===!0}catch{return!1}}function p(e){return typeof e==`object`&&!!e&&typeof e.role==`string`&&Array.isArray(e.parts)}async function m(e,t){if(e.body===null)return``;let n=e.body.getReader(),r=[],i=async e=>{let{done:a,value:o}=await n.read();if(a)return e;let s=e+o.byteLength;return s>t?(await n.cancel().catch(()=>void 0),null):(r.push(o),i(s))},a=await i(0);if(a===null)return null;let o=new Uint8Array(a);return r.reduce((e,t)=>(o.set(t,e),e+t.byteLength),0),new TextDecoder().decode(o)}async function h(e,t){if(Number(e.headers.get(`content-length`)??`0`)>t.maxRequestBytes)return{ok:!1,response:u(413,`Request too large`)};let n=await m(e,t.maxRequestBytes);if(n===null)return{ok:!1,response:u(413,`Request too large`)};let r;try{r=JSON.parse(n)}catch{return{ok:!1,response:u(400,`Invalid request`)}}let i=typeof r==`object`&&r?r.messages:void 0;return!Array.isArray(i)||!i.every(p)?{ok:!1,response:u(400,`Invalid request`)}:i.length>t.maxMessages?{ok:!1,response:u(413,`Too many messages`)}:{messages:i.filter(e=>e.role!==`system`),ok:!0}}var g=`http://localhost:17603`,_=`/auth/me`,v=5e3,y=e=>{let t=e?.trim();if(!t)return null;try{let e=new URL(t);return e.protocol===`http:`||e.protocol===`https:`?e.toString().replace(/\/$/u,``):null}catch{return null}},b=e=>{let t=y(e);return t===null?null:new URL(t).origin};function x(e){return y(e)??y(process.env.BPM_API_INTERNAL_URL)??y(process.env.NEXT_PUBLIC_API_AUTH_URL)??b(process.env.NEXT_PUBLIC_API_URL)??(process.env.NODE_ENV===`production`?null:g)}var S=e=>typeof e==`object`&&!!e&&typeof e.memberId==`string`&&e.memberId.length>0;function C(e={}){return async function(t){let n=t.headers.get(`cookie`);if(!n)return!1;let r=x(e.apiBaseUrl);if(r===null)return!1;let i=e.fetch??fetch,a=`${r}${e.sessionPath??`/auth/me`}`;try{let t=await i(a,{cache:`no-store`,headers:{Accept:`application/json`,Cookie:n},method:`GET`,redirect:`manual`,signal:AbortSignal.timeout(e.timeoutMs??5e3)});return t.status===200&&S(await t.json())}catch{return!1}}}function w(e={}){let n=o();return async function(r){if(!d(e.enabled))return u(404,`Not Found`);if(!await f(r,e.authorize))return u(401,`Unauthorized`);let o=await h(r,{maxMessages:e.maxMessages??200,maxRequestBytes:e.maxRequestBytes??1048576});return o.ok?(0,t.streamText)({maxOutputTokens:e.maxOutputTokens??4096,messages:await(0,t.convertToModelMessages)([...o.messages]),model:a(e.model??process.env.BPM_LLM_MODEL??i),system:e.system??`你是「簽核流程設計器」頁面內建的 AI 助理,唯一職責是協助使用者在這個畫布上設計/修改一個簽核審批流程(workflow)。
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../chunks/workflow-chat-prompt-B803qVdt.cjs");let t=require("@rytass/bpm-core-shared"),n=require("ai"),r=require("@ai-sdk/openai");var i=`gpt-5.4-mini`;function a(e){return(0,r.openai)(e.replace(/^openai\//u,``))}function o(){let e=t.WORKFLOW_TOOLSET.map(e=>[e.name,(0,n.tool)({description:e.description,inputSchema:(0,n.jsonSchema)(e.inputSchema)})]);return Object.fromEntries(e)}var s=1048576,c=200,l=4096,u=(e,t)=>new Response(t,{headers:{"Content-Type":`text/plain; charset=utf-8`},status:e});function d(e){return typeof e==`function`?e():e??process.env.BPM_AI_ASSISTANT_ENABLED===`true`}async function f(e,t){if(!t)return!0;try{return await t(e)===!0}catch{return!1}}function p(e){return typeof e==`object`&&!!e&&typeof e.role==`string`&&Array.isArray(e.parts)}async function m(e,t){if(e.body===null)return``;let n=e.body.getReader(),r=[],i=async e=>{let{done:a,value:o}=await n.read();if(a)return e;let s=e+o.byteLength;return s>t?(await n.cancel().catch(()=>void 0),null):(r.push(o),i(s))},a=await i(0);if(a===null)return null;let o=new Uint8Array(a);return r.reduce((e,t)=>(o.set(t,e),e+t.byteLength),0),new TextDecoder().decode(o)}async function h(e,t){if(Number(e.headers.get(`content-length`)??`0`)>t.maxRequestBytes)return{ok:!1,response:u(413,`Request too large`)};let n=await m(e,t.maxRequestBytes);if(n===null)return{ok:!1,response:u(413,`Request too large`)};let r;try{r=JSON.parse(n)}catch{return{ok:!1,response:u(400,`Invalid request`)}}let i=typeof r==`object`&&r?r.messages:void 0;return!Array.isArray(i)||!i.every(p)?{ok:!1,response:u(400,`Invalid request`)}:i.length>t.maxMessages?{ok:!1,response:u(413,`Too many messages`)}:{messages:i.filter(e=>e.role!==`system`),ok:!0}}var g=`http://localhost:17603`,_=`/auth/me`,v=5e3,y=e=>{let t=e?.trim();if(!t)return null;try{let e=new URL(t);return e.protocol===`http:`||e.protocol===`https:`?e.toString().replace(/\/$/u,``):null}catch{return null}},b=e=>{let t=y(e);return t===null?null:new URL(t).origin};function x(e){return y(e)??y(process.env.BPM_API_INTERNAL_URL)??y(process.env.NEXT_PUBLIC_API_AUTH_URL)??b(process.env.NEXT_PUBLIC_API_URL)??(process.env.NODE_ENV===`production`?null:g)}var S=e=>typeof e==`object`&&!!e&&typeof e.memberId==`string`&&e.memberId.length>0;function C(e={}){return async function(t){let n=t.headers.get(`cookie`);if(!n)return!1;let r=x(e.apiBaseUrl);if(r===null)return!1;let i=e.fetch??fetch,a=`${r}${e.sessionPath??`/auth/me`}`;try{let t=await i(a,{cache:`no-store`,headers:{Accept:`application/json`,Cookie:n},method:`GET`,redirect:`manual`,signal:AbortSignal.timeout(e.timeoutMs??5e3)});return t.status===200&&S(await t.json())}catch{return!1}}}function w(e={}){let t=o();return async function(r){if(!d(e.enabled))return u(404,`Not Found`);if(!await f(r,e.authorize))return u(401,`Unauthorized`);let o=await h(r,{maxMessages:e.maxMessages??200,maxRequestBytes:e.maxRequestBytes??1048576});return o.ok?(0,n.streamText)({maxOutputTokens:e.maxOutputTokens??4096,messages:await(0,n.convertToModelMessages)([...o.messages]),model:a(e.model??process.env.BPM_LLM_MODEL??i),system:e.system??`你是「簽核流程設計器」頁面內建的 AI 助理,唯一職責是協助使用者在這個畫布上設計/修改一個簽核審批流程(workflow)。
2
2
 
3
3
  你只能透過系統提供的工具(tools)來操作流程:新增/重命名/刪除節點、連線、設定簽核人、設定條件分流、設定系統節點動作、自動排版、查詢現況與驗證等。除了這些工具,你沒有其他能力。
4
4
 
@@ -23,5 +23,5 @@ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=requi
23
23
 
24
24
  嚴格界線:
25
25
  - 只做「設計這個簽核流程」相關的事。任何與此無關的請求(閒聊、寫詩、寫程式、查天氣/新聞、操作其他系統或頁面、回答一般知識問題等)一律婉拒,並簡短引導使用者回到流程設計,例如:「我只能協助你設計這個簽核流程,需要我幫你新增關卡或設定條件嗎?」
26
- - 不要假裝執行你工具以外的動作,也不要編造結果。`,tools:n}).toUIMessageStreamResponse():o.response}}exports.DEFAULT_BPM_API_SESSION_PATH=_,exports.DEFAULT_BPM_API_SESSION_TIMEOUT_MS=v,exports.DEFAULT_WORKFLOW_CHAT_MAX_MESSAGES=c,exports.DEFAULT_WORKFLOW_CHAT_MAX_OUTPUT_TOKENS=l,exports.DEFAULT_WORKFLOW_CHAT_MAX_REQUEST_BYTES=s,exports.WORKFLOW_CHAT_SYSTEM_PROMPT=e.n,exports.buildWorkflowAiSdkTools=o,exports.createBPMApiSessionAuthorize=C,exports.createWorkflowChatPOST=w,exports.readWorkflowChatMessages=h,exports.resolveBPMApiSessionBaseUrl=x;
26
+ - 不要假裝執行你工具以外的動作,也不要編造結果。`,tools:t}).toUIMessageStreamResponse():o.response}}exports.DEFAULT_BPM_API_SESSION_PATH=_,exports.DEFAULT_BPM_API_SESSION_TIMEOUT_MS=v,exports.DEFAULT_WORKFLOW_CHAT_MAX_MESSAGES=c,exports.DEFAULT_WORKFLOW_CHAT_MAX_OUTPUT_TOKENS=l,exports.DEFAULT_WORKFLOW_CHAT_MAX_REQUEST_BYTES=s,exports.WORKFLOW_CHAT_SYSTEM_PROMPT=e.n,exports.buildWorkflowAiSdkTools=o,exports.createBPMApiSessionAuthorize=C,exports.createWorkflowChatPOST=w,exports.readWorkflowChatMessages=h,exports.resolveBPMApiSessionBaseUrl=x;
27
27
  //# sourceMappingURL=workflow-chat-route.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"workflow-chat-route.cjs","names":["openai","WORKFLOW_TOOLSET","tool","jsonSchema","streamText","convertToModelMessages","require_workflow_chat_prompt","ai","_ai_sdk_openai","_rytass_bpm_core_shared","DEFAULT_MODEL","resolveModel","setting","buildWorkflowAiSdkTools","entries","workflowTool","DEFAULT_WORKFLOW_CHAT_MAX_REQUEST_BYTES","DEFAULT_WORKFLOW_CHAT_MAX_MESSAGES","DEFAULT_WORKFLOW_CHAT_MAX_OUTPUT_TOKENS","plainResponse","text","isEnabled","enabled","isAuthorized","authorize","request","isUIMessage","value","readBodyWithinLimit","reader","chunks","readWithin","done","total","nextTotal","maxBytes","bytes","chunk","offset","readWorkflowChatMessages","limits","body","messages","message","LOCAL_BPM_API_ORIGIN","DEFAULT_BPM_API_SESSION_PATH","DEFAULT_BPM_API_SESSION_TIMEOUT_MS","readAbsoluteUrl","configured","url","readAbsoluteOrigin","resolveBPMApiSessionBaseUrl","apiBaseUrl","isSignedInMember","createBPMApiSessionAuthorize","cookie","baseUrl","options","fetchImpl","response","createWorkflowChatPOST","tools","read"],"sources":["../../src/next/workflow-chat-route.ts"],"sourcesContent":["import {\n convertToModelMessages,\n jsonSchema,\n streamText,\n tool,\n type LanguageModel,\n type Tool,\n type ToolSet,\n type UIMessage,\n} from 'ai';\nimport { openai } from '@ai-sdk/openai';\nimport { WORKFLOW_TOOLSET } from '@rytass/bpm-core-shared';\nimport { WORKFLOW_CHAT_SYSTEM_PROMPT } from '../views/templates/designer/workflow-chat-prompt';\n\n/**\n * Next.js route handler for the workflow designer LLM assistant.\n *\n * The model is advertised the workflow {@link WORKFLOW_TOOLSET} but the tools\n * carry **no `execute`** — so every tool call is forwarded to the browser,\n * where `useChat`'s `onToolCall` runs it against the React designer controller\n * (see `use-workflow-chat.ts`). This route only holds the API key, runs the\n * model, and streams the tool-call plan back. It never touches the canvas.\n *\n * Host usage (apps/client/src/app/api/chat/route.ts):\n * export const runtime = 'nodejs';\n * export const POST = createWorkflowChatPOST({\n * authorize: createBPMApiSessionAuthorize(),\n * });\n *\n * The route answers 404 unless the assistant is enabled\n * (`BPM_AI_ASSISTANT_ENABLED=true`, read per request, or `options.enabled`),\n * runs the host's optional `authorize` hook (401 when it says no), bounds the\n * request (bytes and messages) and the model output (tokens), and drops any\n * `system` message the client sent (Phase 23 P4).\n */\n\n/** Default OpenAI model id (overridable via BPM_LLM_MODEL). */\nconst DEFAULT_MODEL = 'gpt-5.4-mini';\n\n/**\n * Resolve the model via the OpenAI provider directly (reads `OPENAI_API_KEY`).\n * Accepts a bare id (`gpt-5.4-mini`) or a `openai/`-prefixed id; the prefix is\n * stripped. This deployment talks to OpenAI directly — no AI Gateway.\n */\nfunction resolveModel(setting: string): LanguageModel {\n return openai(setting.replace(/^openai\\//u, ''));\n}\n\n/**\n * Strict guardrail: the assistant may ONLY design this approval flow, using the\n * provided tools. Lives server-side so it cannot be overridden by\n * client-supplied messages (the route drops every `system` message a client\n * sends). Shared with the offline Chrome model through `workflow-chat-prompt`.\n */\nexport { WORKFLOW_CHAT_SYSTEM_PROMPT };\n\n/** Build AI SDK tools from WORKFLOW_TOOLSET — no `execute`, so the browser runs them. */\nexport function buildWorkflowAiSdkTools(): ToolSet {\n const entries: readonly (readonly [string, Tool])[] = WORKFLOW_TOOLSET.map(\n (workflowTool) =>\n [\n workflowTool.name,\n tool({\n description: workflowTool.description,\n inputSchema: jsonSchema(\n workflowTool.inputSchema as Parameters<typeof jsonSchema>[0],\n ),\n }),\n ] as const,\n );\n\n return Object.fromEntries(entries) as ToolSet;\n}\n\n/** Upper bound on the request body, in bytes. Tool results carry snapshots. */\nexport const DEFAULT_WORKFLOW_CHAT_MAX_REQUEST_BYTES = 1_048_576;\n/** Upper bound on the messages in one request. */\nexport const DEFAULT_WORKFLOW_CHAT_MAX_MESSAGES = 200;\n/** Upper bound on the tokens the model may write per response. */\nexport const DEFAULT_WORKFLOW_CHAT_MAX_OUTPUT_TOKENS = 4_096;\n\nexport interface WorkflowChatRouteOptions {\n /**\n * Whether the route serves at all. Absent: `BPM_AI_ASSISTANT_ENABLED` is\n * read on every request and must be `'true'`. A disabled route answers 404.\n */\n readonly enabled?: boolean | (() => boolean);\n /**\n * The host's check that the caller may use the assistant (a session, a\n * designer permission). Called before the body is read; `false` (or a\n * throw) answers 401. Absent: no check, so put the route behind the host's\n * own authentication.\n */\n readonly authorize?: (request: Request) => boolean | Promise<boolean>;\n /** Request body ceiling in bytes (413 above it). */\n readonly maxRequestBytes?: number;\n /** Message count ceiling (413 above it). */\n readonly maxMessages?: number;\n /** Output token ceiling passed to the model. */\n readonly maxOutputTokens?: number;\n /** Override the OpenAI model id (else BPM_LLM_MODEL env, else the default). */\n readonly model?: string;\n /** Override the system prompt (else the strict design-only guardrail). */\n readonly system?: string;\n}\n\nconst plainResponse = (status: number, text: string): Response =>\n new Response(text, {\n headers: { 'Content-Type': 'text/plain; charset=utf-8' },\n status,\n });\n\nfunction isEnabled(enabled: WorkflowChatRouteOptions['enabled']): boolean {\n if (typeof enabled === 'function') {\n return enabled();\n }\n\n return enabled ?? process.env.BPM_AI_ASSISTANT_ENABLED === 'true';\n}\n\nasync function isAuthorized(\n request: Request,\n authorize: WorkflowChatRouteOptions['authorize'],\n): Promise<boolean> {\n if (!authorize) {\n return true;\n }\n\n try {\n return (await authorize(request)) === true;\n } catch {\n return false;\n }\n}\n\nfunction isUIMessage(value: unknown): value is UIMessage {\n return (\n typeof value === 'object' &&\n value !== null &&\n typeof (value as { readonly role?: unknown }).role === 'string' &&\n Array.isArray((value as { readonly parts?: unknown }).parts)\n );\n}\n\ntype ReadMessagesResult =\n | { readonly messages: readonly UIMessage[]; readonly ok: true }\n | { readonly ok: false; readonly response: Response };\n\n/**\n * The request body as text, or `null` once it grows past `maxBytes`; reading\n * stops (and the stream is cancelled) at that point.\n */\nasync function readBodyWithinLimit(\n request: Request,\n maxBytes: number,\n): Promise<string | null> {\n if (request.body === null) {\n return '';\n }\n\n const reader = request.body.getReader();\n // Local to this read and appended in place: copying the list per chunk\n // would make a body of many tiny chunks quadratic.\n const chunks: Uint8Array[] = [];\n\n const readWithin = async (total: number): Promise<number | null> => {\n const { done, value } = await reader.read();\n\n if (done) {\n return total;\n }\n\n const nextTotal = total + value.byteLength;\n\n if (nextTotal > maxBytes) {\n await reader.cancel().catch((): void => undefined);\n\n return null;\n }\n\n chunks.push(value);\n\n return readWithin(nextTotal);\n };\n\n const total = await readWithin(0);\n\n if (total === null) {\n return null;\n }\n\n const bytes = new Uint8Array(total);\n\n chunks.reduce((offset, chunk): number => {\n bytes.set(chunk, offset);\n\n return offset + chunk.byteLength;\n }, 0);\n\n return new TextDecoder().decode(bytes);\n}\n\n/**\n * The client's messages, bounded and without any `system` message: the\n * system prompt is the route's alone. The body is read as text first so its\n * size is checked before anything is parsed.\n */\nexport async function readWorkflowChatMessages(\n request: Request,\n limits: Readonly<{ maxMessages: number; maxRequestBytes: number }>,\n): Promise<ReadMessagesResult> {\n const declaredLength = Number(request.headers.get('content-length') ?? '0');\n\n if (declaredLength > limits.maxRequestBytes) {\n return { ok: false, response: plainResponse(413, 'Request too large') };\n }\n\n // A chunked request declares no length: read it with the limit applied as\n // it arrives, so an oversized body is cut off instead of buffered whole.\n const text = await readBodyWithinLimit(request, limits.maxRequestBytes);\n\n if (text === null) {\n return { ok: false, response: plainResponse(413, 'Request too large') };\n }\n\n let body: unknown;\n\n try {\n body = JSON.parse(text);\n } catch {\n return { ok: false, response: plainResponse(400, 'Invalid request') };\n }\n\n const messages: unknown =\n typeof body === 'object' && body !== null\n ? (body as { readonly messages?: unknown }).messages\n : undefined;\n\n if (!Array.isArray(messages) || !messages.every(isUIMessage)) {\n return { ok: false, response: plainResponse(400, 'Invalid request') };\n }\n\n if (messages.length > limits.maxMessages) {\n return { ok: false, response: plainResponse(413, 'Too many messages') };\n }\n\n return {\n messages: messages.filter((message) => message.role !== 'system'),\n ok: true,\n };\n}\n\n/** Where the BPM API listens in local development (`@rytass/bpm-core-client`). */\nconst LOCAL_BPM_API_ORIGIN = 'http://localhost:17603';\n/** The API's \"who is signed in\" endpoint (`apps/api` `GET /auth/me`). */\nexport const DEFAULT_BPM_API_SESSION_PATH = '/auth/me';\n/** How long the session check may take before the route answers 401. */\nexport const DEFAULT_BPM_API_SESSION_TIMEOUT_MS = 5_000;\n\nexport interface BPMApiSessionAuthorizeOptions {\n /**\n * The absolute API origin to ask, e.g. an internal service URL. Absent:\n * read from configuration by {@link resolveBPMApiSessionBaseUrl}.\n */\n readonly apiBaseUrl?: string;\n /** The fetch to use (tests, instrumented hosts). Default: global `fetch`. */\n readonly fetch?: typeof fetch;\n /** The session endpoint path. Default `/auth/me`. */\n readonly sessionPath?: string;\n /** Milliseconds before the check gives up and refuses. Default 5 000. */\n readonly timeoutMs?: number;\n}\n\n/** An absolute http(s) URL without its trailing slash, or `null`. */\nconst readAbsoluteUrl = (value: string | undefined): string | null => {\n const configured = value?.trim();\n\n if (!configured) {\n return null;\n }\n\n try {\n const url = new URL(configured);\n\n return url.protocol === 'http:' || url.protocol === 'https:'\n ? url.toString().replace(/\\/$/u, '')\n : null;\n } catch {\n return null;\n }\n};\n\nconst readAbsoluteOrigin = (value: string | undefined): string | null => {\n const configured = readAbsoluteUrl(value);\n\n return configured === null ? null : new URL(configured).origin;\n};\n\n/**\n * The API origin the session check asks. It comes only from configuration,\n * never from the request: a `Host` or forwarded header the caller controls\n * must not choose the server that decides whether the caller is signed in.\n *\n * 1. `apiBaseUrl` when given;\n * 2. `BPM_API_INTERNAL_URL` (server-only, e.g. the API's in-cluster URL);\n * 3. `NEXT_PUBLIC_API_AUTH_URL` (the `/auth/*` base);\n * 4. the origin of `NEXT_PUBLIC_API_URL` (the GraphQL endpoint);\n * 5. `http://localhost:17603` outside `NODE_ENV=production`.\n *\n * Only absolute http(s) URLs count; a relative setting is ignored. `null`\n * when nothing applies, and the session check then refuses every request.\n */\nexport function resolveBPMApiSessionBaseUrl(\n apiBaseUrl?: string,\n): string | null {\n return (\n readAbsoluteUrl(apiBaseUrl) ??\n readAbsoluteUrl(process.env.BPM_API_INTERNAL_URL) ??\n readAbsoluteUrl(process.env.NEXT_PUBLIC_API_AUTH_URL) ??\n readAbsoluteOrigin(process.env.NEXT_PUBLIC_API_URL) ??\n (process.env.NODE_ENV === 'production' ? null : LOCAL_BPM_API_ORIGIN)\n );\n}\n\nconst isSignedInMember = (value: unknown): boolean =>\n typeof value === 'object' &&\n value !== null &&\n typeof (value as { readonly memberId?: unknown }).memberId === 'string' &&\n (value as { readonly memberId: string }).memberId.length > 0;\n\n/**\n * An `authorize` hook for {@link createWorkflowChatPOST} that lets in only a\n * caller the BPM API recognises as signed in: it forwards the request's\n * `Cookie` header (nothing else) to the API's session endpoint and accepts a\n * `200` whose JSON names a `memberId`. No cookie, any other status, a body\n * of another shape, a network error or a timeout refuses, so the route\n * answers 401. The session itself is never read or kept here.\n *\n * Host usage (apps/client/src/app/api/chat/route.ts):\n * export const POST = createWorkflowChatPOST({\n * authorize: createBPMApiSessionAuthorize(),\n * });\n */\nexport function createBPMApiSessionAuthorize(\n options: BPMApiSessionAuthorizeOptions = {},\n): (request: Request) => Promise<boolean> {\n return async function authorize(request: Request): Promise<boolean> {\n const cookie = request.headers.get('cookie');\n\n if (!cookie) {\n return false;\n }\n\n const baseUrl = resolveBPMApiSessionBaseUrl(options.apiBaseUrl);\n\n if (baseUrl === null) {\n return false;\n }\n\n const fetchImpl = options.fetch ?? fetch;\n const url = `${baseUrl}${options.sessionPath ?? DEFAULT_BPM_API_SESSION_PATH}`;\n\n try {\n const response = await fetchImpl(url, {\n cache: 'no-store',\n headers: { Accept: 'application/json', Cookie: cookie },\n method: 'GET',\n redirect: 'manual',\n signal: AbortSignal.timeout(\n options.timeoutMs ?? DEFAULT_BPM_API_SESSION_TIMEOUT_MS,\n ),\n });\n\n if (response.status !== 200) {\n return false;\n }\n\n return isSignedInMember(await response.json());\n } catch {\n return false;\n }\n };\n}\n\n/** Create the `POST` handler for the workflow chat route. */\nexport function createWorkflowChatPOST(\n options: WorkflowChatRouteOptions = {},\n): (request: Request) => Promise<Response> {\n const tools = buildWorkflowAiSdkTools();\n\n return async function POST(request: Request): Promise<Response> {\n if (!isEnabled(options.enabled)) {\n return plainResponse(404, 'Not Found');\n }\n\n if (!(await isAuthorized(request, options.authorize))) {\n return plainResponse(401, 'Unauthorized');\n }\n\n const read = await readWorkflowChatMessages(request, {\n maxMessages: options.maxMessages ?? DEFAULT_WORKFLOW_CHAT_MAX_MESSAGES,\n maxRequestBytes:\n options.maxRequestBytes ?? DEFAULT_WORKFLOW_CHAT_MAX_REQUEST_BYTES,\n });\n\n if (!read.ok) {\n return read.response;\n }\n\n const result = streamText({\n maxOutputTokens:\n options.maxOutputTokens ?? DEFAULT_WORKFLOW_CHAT_MAX_OUTPUT_TOKENS,\n messages: await convertToModelMessages([...read.messages]),\n model: resolveModel(\n options.model ?? process.env.BPM_LLM_MODEL ?? DEFAULT_MODEL,\n ),\n system: options.system ?? WORKFLOW_CHAT_SYSTEM_PROMPT,\n tools,\n });\n\n return result.toUIMessageStreamResponse();\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"workflow-chat-route.cjs","names":["openai","WORKFLOW_TOOLSET","tool","jsonSchema","streamText","convertToModelMessages","require_workflow_chat_prompt","_rytass_bpm_core_shared","ai","_ai_sdk_openai","DEFAULT_MODEL","resolveModel","setting","buildWorkflowAiSdkTools","entries","workflowTool","DEFAULT_WORKFLOW_CHAT_MAX_REQUEST_BYTES","DEFAULT_WORKFLOW_CHAT_MAX_MESSAGES","DEFAULT_WORKFLOW_CHAT_MAX_OUTPUT_TOKENS","plainResponse","text","isEnabled","enabled","isAuthorized","authorize","request","isUIMessage","value","readBodyWithinLimit","reader","chunks","readWithin","done","total","nextTotal","maxBytes","bytes","chunk","offset","readWorkflowChatMessages","limits","body","messages","message","LOCAL_BPM_API_ORIGIN","DEFAULT_BPM_API_SESSION_PATH","DEFAULT_BPM_API_SESSION_TIMEOUT_MS","readAbsoluteUrl","configured","url","readAbsoluteOrigin","resolveBPMApiSessionBaseUrl","apiBaseUrl","isSignedInMember","createBPMApiSessionAuthorize","cookie","baseUrl","options","fetchImpl","response","createWorkflowChatPOST","tools","read"],"sources":["../../src/next/workflow-chat-route.ts"],"sourcesContent":["import {\n convertToModelMessages,\n jsonSchema,\n streamText,\n tool,\n type LanguageModel,\n type Tool,\n type ToolSet,\n type UIMessage,\n} from 'ai';\nimport { openai } from '@ai-sdk/openai';\nimport { WORKFLOW_TOOLSET } from '@rytass/bpm-core-shared';\nimport { WORKFLOW_CHAT_SYSTEM_PROMPT } from '../views/templates/designer/workflow-chat-prompt';\n\n/**\n * Next.js route handler for the workflow designer LLM assistant.\n *\n * The model is advertised the workflow {@link WORKFLOW_TOOLSET} but the tools\n * carry **no `execute`** — so every tool call is forwarded to the browser,\n * where `useChat`'s `onToolCall` runs it against the React designer controller\n * (see `use-workflow-chat.ts`). This route only holds the API key, runs the\n * model, and streams the tool-call plan back. It never touches the canvas.\n *\n * Host usage (apps/client/src/app/api/chat/route.ts):\n * export const runtime = 'nodejs';\n * export const POST = createWorkflowChatPOST({\n * authorize: createBPMApiSessionAuthorize(),\n * });\n *\n * The route answers 404 unless the assistant is enabled\n * (`BPM_AI_ASSISTANT_ENABLED=true`, read per request, or `options.enabled`),\n * runs the host's optional `authorize` hook (401 when it says no), bounds the\n * request (bytes and messages) and the model output (tokens), and drops any\n * `system` message the client sent (Phase 23 P4).\n */\n\n/** Default OpenAI model id (overridable via BPM_LLM_MODEL). */\nconst DEFAULT_MODEL = 'gpt-5.4-mini';\n\n/**\n * Resolve the model via the OpenAI provider directly (reads `OPENAI_API_KEY`).\n * Accepts a bare id (`gpt-5.4-mini`) or a `openai/`-prefixed id; the prefix is\n * stripped. This deployment talks to OpenAI directly — no AI Gateway.\n */\nfunction resolveModel(setting: string): LanguageModel {\n return openai(setting.replace(/^openai\\//u, ''));\n}\n\n/**\n * Strict guardrail: the assistant may ONLY design this approval flow, using the\n * provided tools. Lives server-side so it cannot be overridden by\n * client-supplied messages (the route drops every `system` message a client\n * sends). Shared with the offline Chrome model through `workflow-chat-prompt`.\n */\nexport { WORKFLOW_CHAT_SYSTEM_PROMPT };\n\n/** Build AI SDK tools from WORKFLOW_TOOLSET — no `execute`, so the browser runs them. */\nexport function buildWorkflowAiSdkTools(): ToolSet {\n const entries: readonly (readonly [string, Tool])[] = WORKFLOW_TOOLSET.map(\n (workflowTool) =>\n [\n workflowTool.name,\n tool({\n description: workflowTool.description,\n inputSchema: jsonSchema(\n workflowTool.inputSchema as Parameters<typeof jsonSchema>[0],\n ),\n }),\n ] as const,\n );\n\n return Object.fromEntries(entries) as ToolSet;\n}\n\n/** Upper bound on the request body, in bytes. Tool results carry snapshots. */\nexport const DEFAULT_WORKFLOW_CHAT_MAX_REQUEST_BYTES = 1_048_576;\n/** Upper bound on the messages in one request. */\nexport const DEFAULT_WORKFLOW_CHAT_MAX_MESSAGES = 200;\n/** Upper bound on the tokens the model may write per response. */\nexport const DEFAULT_WORKFLOW_CHAT_MAX_OUTPUT_TOKENS = 4_096;\n\nexport interface WorkflowChatRouteOptions {\n /**\n * Whether the route serves at all. Absent: `BPM_AI_ASSISTANT_ENABLED` is\n * read on every request and must be `'true'`. A disabled route answers 404.\n */\n readonly enabled?: boolean | (() => boolean);\n /**\n * The host's check that the caller may use the assistant (a session, a\n * designer permission). Called before the body is read; `false` (or a\n * throw) answers 401. Absent: no check, so put the route behind the host's\n * own authentication.\n */\n readonly authorize?: (request: Request) => boolean | Promise<boolean>;\n /** Request body ceiling in bytes (413 above it). */\n readonly maxRequestBytes?: number;\n /** Message count ceiling (413 above it). */\n readonly maxMessages?: number;\n /** Output token ceiling passed to the model. */\n readonly maxOutputTokens?: number;\n /** Override the OpenAI model id (else BPM_LLM_MODEL env, else the default). */\n readonly model?: string;\n /** Override the system prompt (else the strict design-only guardrail). */\n readonly system?: string;\n}\n\nconst plainResponse = (status: number, text: string): Response =>\n new Response(text, {\n headers: { 'Content-Type': 'text/plain; charset=utf-8' },\n status,\n });\n\nfunction isEnabled(enabled: WorkflowChatRouteOptions['enabled']): boolean {\n if (typeof enabled === 'function') {\n return enabled();\n }\n\n return enabled ?? process.env.BPM_AI_ASSISTANT_ENABLED === 'true';\n}\n\nasync function isAuthorized(\n request: Request,\n authorize: WorkflowChatRouteOptions['authorize'],\n): Promise<boolean> {\n if (!authorize) {\n return true;\n }\n\n try {\n return (await authorize(request)) === true;\n } catch {\n return false;\n }\n}\n\nfunction isUIMessage(value: unknown): value is UIMessage {\n return (\n typeof value === 'object' &&\n value !== null &&\n typeof (value as { readonly role?: unknown }).role === 'string' &&\n Array.isArray((value as { readonly parts?: unknown }).parts)\n );\n}\n\ntype ReadMessagesResult =\n | { readonly messages: readonly UIMessage[]; readonly ok: true }\n | { readonly ok: false; readonly response: Response };\n\n/**\n * The request body as text, or `null` once it grows past `maxBytes`; reading\n * stops (and the stream is cancelled) at that point.\n */\nasync function readBodyWithinLimit(\n request: Request,\n maxBytes: number,\n): Promise<string | null> {\n if (request.body === null) {\n return '';\n }\n\n const reader = request.body.getReader();\n // Local to this read and appended in place: copying the list per chunk\n // would make a body of many tiny chunks quadratic.\n const chunks: Uint8Array[] = [];\n\n const readWithin = async (total: number): Promise<number | null> => {\n const { done, value } = await reader.read();\n\n if (done) {\n return total;\n }\n\n const nextTotal = total + value.byteLength;\n\n if (nextTotal > maxBytes) {\n await reader.cancel().catch((): void => undefined);\n\n return null;\n }\n\n chunks.push(value);\n\n return readWithin(nextTotal);\n };\n\n const total = await readWithin(0);\n\n if (total === null) {\n return null;\n }\n\n const bytes = new Uint8Array(total);\n\n chunks.reduce((offset, chunk): number => {\n bytes.set(chunk, offset);\n\n return offset + chunk.byteLength;\n }, 0);\n\n return new TextDecoder().decode(bytes);\n}\n\n/**\n * The client's messages, bounded and without any `system` message: the\n * system prompt is the route's alone. The body is read as text first so its\n * size is checked before anything is parsed.\n */\nexport async function readWorkflowChatMessages(\n request: Request,\n limits: Readonly<{ maxMessages: number; maxRequestBytes: number }>,\n): Promise<ReadMessagesResult> {\n const declaredLength = Number(request.headers.get('content-length') ?? '0');\n\n if (declaredLength > limits.maxRequestBytes) {\n return { ok: false, response: plainResponse(413, 'Request too large') };\n }\n\n // A chunked request declares no length: read it with the limit applied as\n // it arrives, so an oversized body is cut off instead of buffered whole.\n const text = await readBodyWithinLimit(request, limits.maxRequestBytes);\n\n if (text === null) {\n return { ok: false, response: plainResponse(413, 'Request too large') };\n }\n\n let body: unknown;\n\n try {\n body = JSON.parse(text);\n } catch {\n return { ok: false, response: plainResponse(400, 'Invalid request') };\n }\n\n const messages: unknown =\n typeof body === 'object' && body !== null\n ? (body as { readonly messages?: unknown }).messages\n : undefined;\n\n if (!Array.isArray(messages) || !messages.every(isUIMessage)) {\n return { ok: false, response: plainResponse(400, 'Invalid request') };\n }\n\n if (messages.length > limits.maxMessages) {\n return { ok: false, response: plainResponse(413, 'Too many messages') };\n }\n\n return {\n messages: messages.filter((message) => message.role !== 'system'),\n ok: true,\n };\n}\n\n/** Where the BPM API listens in local development (`@rytass/bpm-core-client`). */\nconst LOCAL_BPM_API_ORIGIN = 'http://localhost:17603';\n/** The API's \"who is signed in\" endpoint (`apps/api` `GET /auth/me`). */\nexport const DEFAULT_BPM_API_SESSION_PATH = '/auth/me';\n/** How long the session check may take before the route answers 401. */\nexport const DEFAULT_BPM_API_SESSION_TIMEOUT_MS = 5_000;\n\nexport interface BPMApiSessionAuthorizeOptions {\n /**\n * The absolute API origin to ask, e.g. an internal service URL. Absent:\n * read from configuration by {@link resolveBPMApiSessionBaseUrl}.\n */\n readonly apiBaseUrl?: string;\n /** The fetch to use (tests, instrumented hosts). Default: global `fetch`. */\n readonly fetch?: typeof fetch;\n /** The session endpoint path. Default `/auth/me`. */\n readonly sessionPath?: string;\n /** Milliseconds before the check gives up and refuses. Default 5 000. */\n readonly timeoutMs?: number;\n}\n\n/** An absolute http(s) URL without its trailing slash, or `null`. */\nconst readAbsoluteUrl = (value: string | undefined): string | null => {\n const configured = value?.trim();\n\n if (!configured) {\n return null;\n }\n\n try {\n const url = new URL(configured);\n\n return url.protocol === 'http:' || url.protocol === 'https:'\n ? url.toString().replace(/\\/$/u, '')\n : null;\n } catch {\n return null;\n }\n};\n\nconst readAbsoluteOrigin = (value: string | undefined): string | null => {\n const configured = readAbsoluteUrl(value);\n\n return configured === null ? null : new URL(configured).origin;\n};\n\n/**\n * The API origin the session check asks. It comes only from configuration,\n * never from the request: a `Host` or forwarded header the caller controls\n * must not choose the server that decides whether the caller is signed in.\n *\n * 1. `apiBaseUrl` when given;\n * 2. `BPM_API_INTERNAL_URL` (server-only, e.g. the API's in-cluster URL);\n * 3. `NEXT_PUBLIC_API_AUTH_URL` (the `/auth/*` base);\n * 4. the origin of `NEXT_PUBLIC_API_URL` (the GraphQL endpoint);\n * 5. `http://localhost:17603` outside `NODE_ENV=production`.\n *\n * Only absolute http(s) URLs count; a relative setting is ignored. `null`\n * when nothing applies, and the session check then refuses every request.\n */\nexport function resolveBPMApiSessionBaseUrl(\n apiBaseUrl?: string,\n): string | null {\n return (\n readAbsoluteUrl(apiBaseUrl) ??\n readAbsoluteUrl(process.env.BPM_API_INTERNAL_URL) ??\n readAbsoluteUrl(process.env.NEXT_PUBLIC_API_AUTH_URL) ??\n readAbsoluteOrigin(process.env.NEXT_PUBLIC_API_URL) ??\n (process.env.NODE_ENV === 'production' ? null : LOCAL_BPM_API_ORIGIN)\n );\n}\n\nconst isSignedInMember = (value: unknown): boolean =>\n typeof value === 'object' &&\n value !== null &&\n typeof (value as { readonly memberId?: unknown }).memberId === 'string' &&\n (value as { readonly memberId: string }).memberId.length > 0;\n\n/**\n * An `authorize` hook for {@link createWorkflowChatPOST} that lets in only a\n * caller the BPM API recognises as signed in: it forwards the request's\n * `Cookie` header (nothing else) to the API's session endpoint and accepts a\n * `200` whose JSON names a `memberId`. No cookie, any other status, a body\n * of another shape, a network error or a timeout refuses, so the route\n * answers 401. The session itself is never read or kept here.\n *\n * Host usage (apps/client/src/app/api/chat/route.ts):\n * export const POST = createWorkflowChatPOST({\n * authorize: createBPMApiSessionAuthorize(),\n * });\n */\nexport function createBPMApiSessionAuthorize(\n options: BPMApiSessionAuthorizeOptions = {},\n): (request: Request) => Promise<boolean> {\n return async function authorize(request: Request): Promise<boolean> {\n const cookie = request.headers.get('cookie');\n\n if (!cookie) {\n return false;\n }\n\n const baseUrl = resolveBPMApiSessionBaseUrl(options.apiBaseUrl);\n\n if (baseUrl === null) {\n return false;\n }\n\n const fetchImpl = options.fetch ?? fetch;\n const url = `${baseUrl}${options.sessionPath ?? DEFAULT_BPM_API_SESSION_PATH}`;\n\n try {\n const response = await fetchImpl(url, {\n cache: 'no-store',\n headers: { Accept: 'application/json', Cookie: cookie },\n method: 'GET',\n redirect: 'manual',\n signal: AbortSignal.timeout(\n options.timeoutMs ?? DEFAULT_BPM_API_SESSION_TIMEOUT_MS,\n ),\n });\n\n if (response.status !== 200) {\n return false;\n }\n\n return isSignedInMember(await response.json());\n } catch {\n return false;\n }\n };\n}\n\n/** Create the `POST` handler for the workflow chat route. */\nexport function createWorkflowChatPOST(\n options: WorkflowChatRouteOptions = {},\n): (request: Request) => Promise<Response> {\n const tools = buildWorkflowAiSdkTools();\n\n return async function POST(request: Request): Promise<Response> {\n if (!isEnabled(options.enabled)) {\n return plainResponse(404, 'Not Found');\n }\n\n if (!(await isAuthorized(request, options.authorize))) {\n return plainResponse(401, 'Unauthorized');\n }\n\n const read = await readWorkflowChatMessages(request, {\n maxMessages: options.maxMessages ?? DEFAULT_WORKFLOW_CHAT_MAX_MESSAGES,\n maxRequestBytes:\n options.maxRequestBytes ?? DEFAULT_WORKFLOW_CHAT_MAX_REQUEST_BYTES,\n });\n\n if (!read.ok) {\n return read.response;\n }\n\n const result = streamText({\n maxOutputTokens:\n options.maxOutputTokens ?? DEFAULT_WORKFLOW_CHAT_MAX_OUTPUT_TOKENS,\n messages: await convertToModelMessages([...read.messages]),\n model: resolveModel(\n options.model ?? process.env.BPM_LLM_MODEL ?? DEFAULT_MODEL,\n ),\n system: options.system ?? WORKFLOW_CHAT_SYSTEM_PROMPT,\n tools,\n });\n\n return result.toUIMessageStreamResponse();\n };\n}\n"],"mappings":"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"}
@@ -1,16 +1,16 @@
1
1
  import { n as e } from "../chunks/workflow-chat-prompt-B4wCFKPB.js";
2
- import { convertToModelMessages as t, jsonSchema as n, streamText as r, tool as i } from "ai";
3
- import { openai as a } from "@ai-sdk/openai";
4
- import { WORKFLOW_TOOLSET as o } from "@rytass/bpm-core-shared";
2
+ import { WORKFLOW_TOOLSET as t } from "@rytass/bpm-core-shared";
3
+ import { convertToModelMessages as n, jsonSchema as r, streamText as i, tool as a } from "ai";
4
+ import { openai as o } from "@ai-sdk/openai";
5
5
  // src/next/workflow-chat-route.ts
6
6
  var s = "gpt-5.4-mini";
7
7
  function c(e) {
8
- return a(e.replace(/^openai\//u, ""));
8
+ return o(e.replace(/^openai\//u, ""));
9
9
  }
10
10
  function l() {
11
- let e = o.map((e) => [e.name, i({
11
+ let e = t.map((e) => [e.name, a({
12
12
  description: e.description,
13
- inputSchema: n(e.inputSchema)
13
+ inputSchema: r(e.inputSchema)
14
14
  })]);
15
15
  return Object.fromEntries(e);
16
16
  }
@@ -117,20 +117,20 @@ function E(e = {}) {
117
117
  };
118
118
  }
119
119
  function D(e = {}) {
120
- let n = l();
121
- return async function(i) {
120
+ let t = l();
121
+ return async function(r) {
122
122
  if (!m(e.enabled)) return p(404, "Not Found");
123
- if (!await h(i, e.authorize)) return p(401, "Unauthorized");
124
- let a = await v(i, {
123
+ if (!await h(r, e.authorize)) return p(401, "Unauthorized");
124
+ let a = await v(r, {
125
125
  maxMessages: e.maxMessages ?? 200,
126
126
  maxRequestBytes: e.maxRequestBytes ?? 1048576
127
127
  });
128
- return a.ok ? r({
128
+ return a.ok ? i({
129
129
  maxOutputTokens: e.maxOutputTokens ?? 4096,
130
- messages: await t([...a.messages]),
130
+ messages: await n([...a.messages]),
131
131
  model: c(e.model ?? process.env.BPM_LLM_MODEL ?? s),
132
132
  system: e.system ?? "你是「簽核流程設計器」頁面內建的 AI 助理,唯一職責是協助使用者在這個畫布上設計/修改一個簽核審批流程(workflow)。\n\n你只能透過系統提供的工具(tools)來操作流程:新增/重命名/刪除節點、連線、設定簽核人、設定條件分流、設定系統節點動作、自動排版、查詢現況與驗證等。除了這些工具,你沒有其他能力。\n\n工作守則:\n1. 動作前,先呼叫 get_workflow_snapshot 了解目前畫布的節點與連線,再規劃要呼叫哪些工具。\n2. 盡量用高階工具(insert_approval_step / insert_notification / insert_conditional_branch)一次完成多步;需要細修時再用細顆粒工具。\n3. **重要:要果斷、先把流程畫出來。** 使用者只給一兩句話時,直接依常見公司慣例建立初稿,不要反覆追問細節。簽核人若使用者沒指定,就省略 approverResolver(系統會自動用「直屬主管」為預設,不需要真實會員 id);通知節點同理可省略 action(預設站內通知)。先把結構建好、再請使用者調整。\n4. 工具呼叫不會因為「缺簽核人/缺資料」而失敗;缺的部分會以合理預設補上,並由 validate_workflow 標示。所以放心連續呼叫工具把流程建完,不要因為怕缺欄位就停下來問。\n5. 當使用者「點名」某個人或部門當簽核人/收件者時,先用 search_members(依姓名關鍵字)、list_org_units、list_positions 查到真實 id,再用 set_user_task_approver / set_service_action 指定(如 DIRECT memberIds、ORG_UNIT_MANAGER orgUnitId、POSITION positionId)。查不到就回報並請使用者確認,不要編造 id。\n6. 節點 id 可從 snapshot 取得;連線條件需要先綁定表單欄位(若 list_form_fields 為空,提醒使用者先綁定表單版本)。\n7. **條件分流務必主動補上「其他情況」**:設計 exclusiveGateway(條件分流)時,除了符合條件的分支,一定要保留一條未設條件、標記為預設(isDefault)的「其他情況」路徑當 else(通常指向後續節點或完成)。不要把所有輸出連線都設成條件而沒有預設出口;缺預設出口時,用 set_edge_default 把其中一條設為預設。\n8. 完成後,呼叫 validate_workflow,並用一兩句話說明你建了什麼流程、哪些地方(如簽核人、條件門檻)建議使用者再確認或指定。\n9. 全程使用繁體中文(台灣用語)回覆。\n10. 欄位在各關的顯示與補填用 set_field_access_matrix 設定:nodeIds 只列這次要改的簽核節點,它們的設定會整個換掉,呼叫前先從 get_workflow_snapshot 讀出這些節點既有的「欄位設定」一併傳入。「隱藏」只影響畫面、不是權限,不能用來保密;使用者要求保密時要說明這一點。節點含舊欄位權限設定時,用 remove_legacy_field_permissions 移除(不會自動轉換)。\n\n運算式(CEL)規則:\n- 運算式只在後端檢查與執行,你看不到執行結果,也不能在瀏覽器試算。寫任何運算式(發起權限、簽核人運算式、系統節點寫入欄位的值、Webhook payload、知會對象運算式)之前,先呼叫 get_expression_catalog 取得該位置可用的根、欄位與函式,只使用目錄裡的名稱;宿主函式名稱帶版本(例如 erp_costBand_v1),要照目錄原樣寫。\n- 寫入前一定先用 preview_expression 檢查(LINT)。有 ERROR 就修正後再檢查,不要直接寫入;寫入工具也會再檢查一次,未通過會被拒絕。WARNING 只是提醒,寫入後要用一句話告訴使用者。\n- 條件分流的連線條件優先用 set_edge_condition(依欄位、運算子、值,由系統產生),不要自己拼 CEL。\n- 只有從簽核關卡出去的連線才讀得到 lastDecision;條件分流、系統節點出去的連線 lastDecision 永遠是空值,不要在那裡讀它。\n- 運算式寫進草稿後不會自動發布;發布由使用者在畫面上操作,你不能也不要嘗試發布。\n\n嚴格界線:\n- 只做「設計這個簽核流程」相關的事。任何與此無關的請求(閒聊、寫詩、寫程式、查天氣/新聞、操作其他系統或頁面、回答一般知識問題等)一律婉拒,並簡短引導使用者回到流程設計,例如:「我只能協助你設計這個簽核流程,需要我幫你新增關卡或設定條件嗎?」\n- 不要假裝執行你工具以外的動作,也不要編造結果。",
133
- tools: n
133
+ tools: t
134
134
  }).toUIMessageStreamResponse() : a.response;
135
135
  };
136
136
  }
@@ -1,2 +1,2 @@
1
- Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});const e=require("../../../chunks/delegations-CVmtpkRn.cjs");let t=require("react/jsx-runtime");var n={title:`代理設定 | BPM Admin`,description:`設定簽核代理規則,讓符合範圍的待簽任務自動改派給代理人。`};function r(){return(0,t.jsx)(e.t,{})}exports.default=r,exports.metadata=n;
1
+ Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});const e=require("../../../chunks/delegations-ClRCwD1I.cjs");let t=require("react/jsx-runtime");var n={title:`代理設定 | BPM Admin`,description:`設定簽核代理規則,讓符合範圍的待簽任務自動改派給代理人。`};function r(){return(0,t.jsx)(e.t,{})}exports.default=r,exports.metadata=n;
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1,4 +1,4 @@
1
- import { t as e } from "../../../chunks/delegations-CxnNvTCc.js";
1
+ import { t as e } from "../../../chunks/delegations-zisffLwk.js";
2
2
  import { jsx as t } from "react/jsx-runtime";
3
3
  // src/pages/admin/delegations/index.tsx
4
4
  var n = {
@@ -1,2 +1,2 @@
1
- Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});const e=require("../../chunks/delegations-CrfjsH94.cjs");let t=require("react/jsx-runtime");var n={title:`我的代理 | BPM Admin`,description:`設定自己的簽核代理,讓指定期間內的新待簽任務自動交由代理人處理。`};function r(){return(0,t.jsx)(e.t,{})}exports.default=r,exports.metadata=n;
1
+ Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});const e=require("../../chunks/delegations-D43VDSz1.cjs");let t=require("react/jsx-runtime");var n={title:`我的代理 | BPM Admin`,description:`設定自己的簽核代理,讓指定期間內的新待簽任務自動交由代理人處理。`};function r(){return(0,t.jsx)(e.t,{})}exports.default=r,exports.metadata=n;
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1,4 +1,4 @@
1
- import { t as e } from "../../chunks/delegations-9-yvWWws.js";
1
+ import { t as e } from "../../chunks/delegations-CqND-Qtz.js";
2
2
  import { jsx as t } from "react/jsx-runtime";
3
3
  // src/pages/delegations/index.tsx
4
4
  var n = {
@@ -1,2 +1,2 @@
1
- Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});const e=require("../../../chunks/detail-ChseSF55.cjs");let t=require("react/jsx-runtime");var n={title:`案件詳情 | BPM Admin`};async function r({params:n}){let{id:r}=await n;return(0,t.jsx)(e.t,{instanceId:r})}exports.default=r,exports.metadata=n;
1
+ Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});const e=require("../../../chunks/detail-C5KrUC7s.cjs");let t=require("react/jsx-runtime");var n={title:`案件詳情 | BPM Admin`};async function r({params:n}){let{id:r}=await n;return(0,t.jsx)(e.t,{instanceId:r})}exports.default=r,exports.metadata=n;
2
2
  //# sourceMappingURL=index.cjs.map