@brokr/sdk 2.0.0 → 2.1.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 (77) hide show
  1. package/dist/feature.js +22 -2
  2. package/dist/feature.mjs +22 -2
  3. package/dist/index.js +22 -2
  4. package/dist/index.mjs +22 -2
  5. package/dist/next.js +22 -2
  6. package/dist/next.mjs +22 -2
  7. package/dist/react-notifications.js +63 -50
  8. package/dist/react-notifications.mjs +63 -50
  9. package/dist/react-styles.js +386 -169
  10. package/dist/react-styles.mjs +386 -169
  11. package/dist/react-theme.js +6 -4
  12. package/dist/react-theme.mjs +6 -4
  13. package/dist/react.js +644 -317
  14. package/dist/react.mjs +682 -355
  15. package/dist/runtime.js +22 -2
  16. package/dist/runtime.mjs +22 -2
  17. package/dist/src/chat/config.d.ts +2 -0
  18. package/dist/src/chat/config.d.ts.map +1 -1
  19. package/dist/src/gateway.d.ts.map +1 -1
  20. package/dist/src/models.d.ts +2 -0
  21. package/dist/src/models.d.ts.map +1 -1
  22. package/dist/src/react/account/AccountPanel.d.ts.map +1 -1
  23. package/dist/src/react/account/UserButton.d.ts.map +1 -1
  24. package/dist/src/react/chat/AIChat.d.ts.map +1 -1
  25. package/dist/src/react/chat/ChatContext.d.ts +3 -6
  26. package/dist/src/react/chat/ChatContext.d.ts.map +1 -1
  27. package/dist/src/react/chat/ChatInput.d.ts.map +1 -1
  28. package/dist/src/react/chat/MarkdownRenderer.d.ts.map +1 -1
  29. package/dist/src/react/chat/MessagePane.d.ts.map +1 -1
  30. package/dist/src/react/chat/ModelSelector.d.ts +1 -1
  31. package/dist/src/react/chat/ModelSelector.d.ts.map +1 -1
  32. package/dist/src/react/chat/ThreadSidebar.d.ts.map +1 -1
  33. package/dist/src/react/chat/memory.d.ts +12 -0
  34. package/dist/src/react/chat/memory.d.ts.map +1 -0
  35. package/dist/src/react/chat/types.d.ts +6 -0
  36. package/dist/src/react/chat/types.d.ts.map +1 -1
  37. package/dist/src/react/chat/useChat.d.ts +8 -6
  38. package/dist/src/react/chat/useChat.d.ts.map +1 -1
  39. package/dist/src/react/composites/FabAI.d.ts +6 -1
  40. package/dist/src/react/composites/FabAI.d.ts.map +1 -1
  41. package/dist/src/react/composites/fab-context.d.ts +26 -0
  42. package/dist/src/react/composites/fab-context.d.ts.map +1 -0
  43. package/dist/src/react/css/account.d.ts +1 -1
  44. package/dist/src/react/css/account.d.ts.map +1 -1
  45. package/dist/src/react/css/auth.d.ts +1 -1
  46. package/dist/src/react/css/auth.d.ts.map +1 -1
  47. package/dist/src/react/css/chat-extras.d.ts +1 -1
  48. package/dist/src/react/css/chat-extras.d.ts.map +1 -1
  49. package/dist/src/react/css/chat.d.ts +1 -1
  50. package/dist/src/react/css/chat.d.ts.map +1 -1
  51. package/dist/src/react/css/composites.d.ts +1 -1
  52. package/dist/src/react/css/composites.d.ts.map +1 -1
  53. package/dist/src/react/css/gates.d.ts +1 -1
  54. package/dist/src/react/css/gates.d.ts.map +1 -1
  55. package/dist/src/react/css/markdown.d.ts +1 -1
  56. package/dist/src/react/css/markdown.d.ts.map +1 -1
  57. package/dist/src/react/css/notifications.d.ts +1 -1
  58. package/dist/src/react/css/notifications.d.ts.map +1 -1
  59. package/dist/src/react/css/primitives.d.ts +1 -1
  60. package/dist/src/react/css/primitives.d.ts.map +1 -1
  61. package/dist/src/react/css/reset.d.ts +1 -1
  62. package/dist/src/react/css/reset.d.ts.map +1 -1
  63. package/dist/src/react/css/responsive.d.ts +1 -1
  64. package/dist/src/react/css/responsive.d.ts.map +1 -1
  65. package/dist/src/react/css/skeleton.d.ts +1 -1
  66. package/dist/src/react/css/skeleton.d.ts.map +1 -1
  67. package/dist/src/react/css/tokens.d.ts +1 -1
  68. package/dist/src/react/css/tokens.d.ts.map +1 -1
  69. package/dist/src/react/notifications/NotificationBell.d.ts.map +1 -1
  70. package/dist/src/react/notifications/NotificationList.d.ts.map +1 -1
  71. package/dist/src/react/notifications/Toast.d.ts.map +1 -1
  72. package/dist/src/react/payments/Plans.d.ts.map +1 -1
  73. package/dist/src/react/theme.d.ts.map +1 -1
  74. package/dist/src/react/types.d.ts +4 -4
  75. package/dist/src/react/types.d.ts.map +1 -1
  76. package/dist/tsconfig.tsbuildinfo +1 -1
  77. package/package.json +1 -1
package/dist/react.js CHANGED
@@ -4237,10 +4237,12 @@ function getBrokrRootStyle(theme) {
4237
4237
  ...theme.colors.error ? { ["--brokr-error"]: theme.colors.error } : {},
4238
4238
  ...theme.colors.success ? { ["--brokr-success"]: theme.colors.success } : {},
4239
4239
  ...theme.colors.warning ? { ["--brokr-warning"]: theme.colors.warning } : {},
4240
- ...theme.colors.toastBg ? { ["--brokr-toast-bg"]: theme.colors.toastBg } : {},
4241
- ...theme.colors.toastText ? { ["--brokr-toast-text"]: theme.colors.toastText } : {},
4242
- ...theme.colors.toastTextSecondary ? { ["--brokr-toast-text-secondary"]: theme.colors.toastTextSecondary } : {},
4243
- ...theme.colors.toastBorder ? { ["--brokr-toast-border"]: theme.colors.toastBorder } : {},
4240
+ // Toast vars always emitted with smart defaults — toasts stay light in dark mode.
4241
+ // Users can override via theme.colors.toastBg etc.
4242
+ ["--brokr-toast-bg"]: theme.colors.toastBg ?? "#ffffff",
4243
+ ["--brokr-toast-text"]: theme.colors.toastText ?? "#0a0a0a",
4244
+ ["--brokr-toast-text-secondary"]: theme.colors.toastTextSecondary ?? "#52525b",
4245
+ ["--brokr-toast-border"]: theme.colors.toastBorder ?? "#e4e4e7",
4244
4246
  ["--brokr-radius-card"]: `${theme.radii.card}px`,
4245
4247
  ["--brokr-radius-input"]: `${theme.radii.input}px`,
4246
4248
  ["--brokr-radius-button"]: `${theme.radii.button}px`
@@ -4252,6 +4254,7 @@ function getBrokrRootStyle(theme) {
4252
4254
  var import_react3 = __toESM(require("react"));
4253
4255
 
4254
4256
  // src/models.ts
4257
+ var STACK_DEFAULT = "__stack_default__";
4255
4258
  var providers = [
4256
4259
  { id: "deepseek", label: "Deepseek", model: "deepseek-chat", color: "#0EA5E9", free: true, logo: "https://assets.brokr.sh/sdk/deepseek_logo.png" },
4257
4260
  { id: "openai", label: "ChatGPT", model: "gpt-5.4-mini", color: "#10B981", free: false, logo: "https://assets.brokr.sh/sdk/gpt_logo.png" },
@@ -4355,6 +4358,37 @@ function resolveNotificationType(registry, type, data) {
4355
4358
 
4356
4359
  // src/react/notifications/Toast.tsx
4357
4360
  var import_react2 = __toESM(require("react"));
4361
+ function ToastItem({
4362
+ notification,
4363
+ exiting,
4364
+ registry,
4365
+ onDismiss
4366
+ }) {
4367
+ const handleDismiss = (0, import_react2.useCallback)(() => onDismiss(notification.id), [notification.id, onDismiss]);
4368
+ const notifData = notification.data ?? {};
4369
+ const notifType = notifData.type ?? "default";
4370
+ const resolved = resolveNotificationType(registry, notifType, notifData);
4371
+ return /* @__PURE__ */ import_react2.default.createElement(
4372
+ "div",
4373
+ {
4374
+ className: `brokr-toast brokr-toast--${notification.variant}${exiting ? " brokr-toast--exit" : ""}`,
4375
+ role: "alert",
4376
+ "aria-live": "polite"
4377
+ },
4378
+ resolved.image ? /* @__PURE__ */ import_react2.default.createElement("img", { src: resolved.image.url, alt: resolved.image.alt, className: "brokr-toast-provider-logo" }) : /* @__PURE__ */ import_react2.default.createElement("span", { className: `brokr-toast-icon brokr-toast-icon--${notification.variant}` }),
4379
+ /* @__PURE__ */ import_react2.default.createElement("div", { className: "brokr-toast-content" }, /* @__PURE__ */ import_react2.default.createElement("span", { className: "brokr-toast-title" }, notification.title), /* @__PURE__ */ import_react2.default.createElement("span", { className: "brokr-toast-message" }, notification.message)),
4380
+ /* @__PURE__ */ import_react2.default.createElement(
4381
+ "button",
4382
+ {
4383
+ type: "button",
4384
+ className: "brokr-toast-dismiss",
4385
+ onClick: handleDismiss,
4386
+ "aria-label": "Dismiss notification"
4387
+ },
4388
+ /* @__PURE__ */ import_react2.default.createElement("svg", { "aria-hidden": "true", width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }, /* @__PURE__ */ import_react2.default.createElement("path", { d: "M18 6 6 18M6 6l12 12" }))
4389
+ )
4390
+ );
4391
+ }
4358
4392
  var DEFAULT_DURATION = 4e3;
4359
4393
  var DEFAULT_MAX_VISIBLE = 3;
4360
4394
  var EXIT_ANIMATION_MS = 300;
@@ -4365,6 +4399,26 @@ function ToastLayer({ toasts, config, registry, onDismiss }) {
4365
4399
  const duration = config.toast?.duration ?? DEFAULT_DURATION;
4366
4400
  const maxVisible = config.toast?.maxVisible ?? DEFAULT_MAX_VISIBLE;
4367
4401
  const position = config.toast?.position ?? "top-right";
4402
+ const onDismissRef = (0, import_react2.useRef)(onDismiss);
4403
+ onDismissRef.current = onDismiss;
4404
+ const exitingRef = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
4405
+ const dismissToast = (0, import_react2.useCallback)((id) => {
4406
+ if (exitingRef.current.has(id)) return;
4407
+ exitingRef.current.add(id);
4408
+ setEntries(
4409
+ (prev) => prev.map((e) => e.notification.id === id ? { ...e, exiting: true } : e)
4410
+ );
4411
+ setTimeout(() => {
4412
+ setEntries((prev) => prev.filter((e) => e.notification.id !== id));
4413
+ onDismissRef.current(id);
4414
+ exitingRef.current.delete(id);
4415
+ }, EXIT_ANIMATION_MS);
4416
+ const timer = timersRef.current.get(id);
4417
+ if (timer) {
4418
+ clearTimeout(timer);
4419
+ timersRef.current.delete(id);
4420
+ }
4421
+ }, []);
4368
4422
  (0, import_react2.useEffect)(() => {
4369
4423
  for (const toast of toasts) {
4370
4424
  if (processedRef.current.has(toast.id)) continue;
@@ -4383,21 +4437,7 @@ function ToastLayer({ toasts, config, registry, onDismiss }) {
4383
4437
  timersRef.current.set(toast.id, timer);
4384
4438
  }
4385
4439
  }
4386
- }, [toasts, duration, maxVisible]);
4387
- const dismissToast = (0, import_react2.useCallback)((id) => {
4388
- setEntries(
4389
- (prev) => prev.map((e) => e.notification.id === id ? { ...e, exiting: true } : e)
4390
- );
4391
- setTimeout(() => {
4392
- setEntries((prev) => prev.filter((e) => e.notification.id !== id));
4393
- onDismiss(id);
4394
- }, EXIT_ANIMATION_MS);
4395
- const timer = timersRef.current.get(id);
4396
- if (timer) {
4397
- clearTimeout(timer);
4398
- timersRef.current.delete(id);
4399
- }
4400
- }, [onDismiss]);
4440
+ }, [toasts, duration, maxVisible, dismissToast]);
4401
4441
  (0, import_react2.useEffect)(() => {
4402
4442
  return () => {
4403
4443
  for (const timer of timersRef.current.values()) {
@@ -4417,39 +4457,16 @@ function ToastLayer({ toasts, config, registry, onDismiss }) {
4417
4457
  return map[position] ?? "brokr-toast-layer--bottom-right";
4418
4458
  }, [position]);
4419
4459
  if (entries.length === 0) return null;
4420
- return /* @__PURE__ */ import_react2.default.createElement("div", { className: `brokr-toast-layer ${positionClass}` }, entries.map(({ notification, exiting }) => {
4421
- const notifData = notification.data ?? {};
4422
- const notifType = notifData.type ?? "default";
4423
- const resolved = resolveNotificationType(registry, notifType, notifData);
4424
- return /* @__PURE__ */ import_react2.default.createElement(
4425
- "div",
4426
- {
4427
- key: notification.id,
4428
- className: `brokr-toast brokr-toast--${notification.variant}${exiting ? " brokr-toast--exit" : ""}`,
4429
- role: "alert",
4430
- "aria-live": "polite"
4431
- },
4432
- resolved.image ? /* @__PURE__ */ import_react2.default.createElement(
4433
- "img",
4434
- {
4435
- src: resolved.image.url,
4436
- alt: resolved.image.alt,
4437
- className: "brokr-toast-provider-logo"
4438
- }
4439
- ) : /* @__PURE__ */ import_react2.default.createElement("span", { className: `brokr-toast-icon brokr-toast-icon--${notification.variant}` }),
4440
- /* @__PURE__ */ import_react2.default.createElement("div", { className: "brokr-toast-content" }, /* @__PURE__ */ import_react2.default.createElement("span", { className: "brokr-toast-title" }, notification.title), /* @__PURE__ */ import_react2.default.createElement("span", { className: "brokr-toast-message" }, notification.message)),
4441
- /* @__PURE__ */ import_react2.default.createElement(
4442
- "button",
4443
- {
4444
- type: "button",
4445
- className: "brokr-toast-dismiss",
4446
- onClick: () => dismissToast(notification.id),
4447
- "aria-label": "Dismiss notification"
4448
- },
4449
- /* @__PURE__ */ import_react2.default.createElement("svg", { "aria-hidden": "true", width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }, /* @__PURE__ */ import_react2.default.createElement("path", { d: "M18 6 6 18M6 6l12 12" }))
4450
- )
4451
- );
4452
- }));
4460
+ return /* @__PURE__ */ import_react2.default.createElement("div", { className: `brokr-toast-layer ${positionClass}` }, entries.map(({ notification, exiting }) => /* @__PURE__ */ import_react2.default.createElement(
4461
+ ToastItem,
4462
+ {
4463
+ key: notification.id,
4464
+ notification,
4465
+ exiting,
4466
+ registry,
4467
+ onDismiss: dismissToast
4468
+ }
4469
+ )));
4453
4470
  }
4454
4471
 
4455
4472
  // src/react/notifications/provider.tsx
@@ -5565,6 +5582,26 @@ function filterDefined(defaults, overrides) {
5565
5582
  }
5566
5583
 
5567
5584
  // src/react/account/AccountPanel.tsx
5585
+ function AccountTabButton({
5586
+ tab,
5587
+ active,
5588
+ icon,
5589
+ label,
5590
+ onSelect
5591
+ }) {
5592
+ const handleClick = (0, import_react11.useCallback)(() => onSelect(tab), [tab, onSelect]);
5593
+ return /* @__PURE__ */ import_react11.default.createElement(
5594
+ "button",
5595
+ {
5596
+ className: "brokr-account-tab",
5597
+ "data-active": active,
5598
+ onClick: handleClick,
5599
+ type: "button"
5600
+ },
5601
+ /* @__PURE__ */ import_react11.default.createElement("span", { className: "brokr-account-tab-icon" }, icon),
5602
+ /* @__PURE__ */ import_react11.default.createElement("span", null, label)
5603
+ );
5604
+ }
5568
5605
  var TAB_META = {
5569
5606
  general: {
5570
5607
  label: "General"
@@ -5696,16 +5733,15 @@ function AccountPanel(inlineProps) {
5696
5733
  }
5697
5734
  }, []);
5698
5735
  return /* @__PURE__ */ import_react11.default.createElement("section", { className: "brokr-account-panel", "data-density": density }, /* @__PURE__ */ import_react11.default.createElement("aside", { className: "brokr-account-sidebar" }, groupedTabs.map((group) => /* @__PURE__ */ import_react11.default.createElement("div", { className: "brokr-account-sidebar-group", key: group.label }, /* @__PURE__ */ import_react11.default.createElement("span", { className: "brokr-account-sidebar-kicker" }, group.label), /* @__PURE__ */ import_react11.default.createElement("nav", { className: "brokr-account-tab-list", "aria-label": `${group.label} settings` }, group.tabs.map((tab) => /* @__PURE__ */ import_react11.default.createElement(
5699
- "button",
5736
+ AccountTabButton,
5700
5737
  {
5701
- className: "brokr-account-tab",
5702
- "data-active": activeTab === tab,
5703
5738
  key: tab,
5704
- onClick: () => setActiveTab(tab),
5705
- type: "button"
5706
- },
5707
- /* @__PURE__ */ import_react11.default.createElement("span", { className: "brokr-account-tab-icon" }, getTabIcon(tab)),
5708
- /* @__PURE__ */ import_react11.default.createElement("span", null, TAB_META[tab].label)
5739
+ tab,
5740
+ active: activeTab === tab,
5741
+ icon: getTabIcon(tab),
5742
+ label: TAB_META[tab].label,
5743
+ onSelect: setActiveTab
5744
+ }
5709
5745
  )))))), /* @__PURE__ */ import_react11.default.createElement("div", { className: "brokr-account-surface" }, /* @__PURE__ */ import_react11.default.createElement("header", { className: "brokr-account-surface-header" }, /* @__PURE__ */ import_react11.default.createElement("h2", { className: "brokr-title" }, activeTabMeta.label)), activeTab === "general" ? /* @__PURE__ */ import_react11.default.createElement("form", { className: "brokr-account-form", onSubmit: handleSaveProfile }, /* @__PURE__ */ import_react11.default.createElement("section", { className: "brokr-account-card" }, /* @__PURE__ */ import_react11.default.createElement("div", { className: "brokr-account-photo-row" }, /* @__PURE__ */ import_react11.default.createElement(ProfilePhotoButton, { size: 88 }), /* @__PURE__ */ import_react11.default.createElement("div", { className: "brokr-account-photo-copy" }, /* @__PURE__ */ import_react11.default.createElement("strong", null, "Profile photo"), /* @__PURE__ */ import_react11.default.createElement("span", { className: "brokr-copy" }, "PNG, JPEG, and GIF under 10MB"), /* @__PURE__ */ import_react11.default.createElement("span", { className: "brokr-account-photo-hint" }, /* @__PURE__ */ import_react11.default.createElement(UploadIcon, { size: 14 }), "Upload new picture")))), /* @__PURE__ */ import_react11.default.createElement("section", { className: "brokr-account-card brokr-account-field-list" }, /* @__PURE__ */ import_react11.default.createElement("div", { className: "brokr-account-field-row" }, /* @__PURE__ */ import_react11.default.createElement("div", { className: "brokr-account-field-head" }, /* @__PURE__ */ import_react11.default.createElement("label", { className: "brokr-label", htmlFor: "brokr-account-name" }, "Name")), /* @__PURE__ */ import_react11.default.createElement("div", { className: "brokr-account-field-body" }, /* @__PURE__ */ import_react11.default.createElement(
5710
5746
  "input",
5711
5747
  {
@@ -5807,6 +5843,9 @@ function UserButton({
5807
5843
  setOpen(false);
5808
5844
  await signOut();
5809
5845
  }, [signOut]);
5846
+ const stopPropagation = (0, import_react13.useCallback)((event) => {
5847
+ event.stopPropagation();
5848
+ }, []);
5810
5849
  (0, import_react13.useEffect)(() => {
5811
5850
  if (!open) return void 0;
5812
5851
  const handlePointerDown = (event) => {
@@ -5840,13 +5879,13 @@ function UserButton({
5840
5879
  "data-align": align,
5841
5880
  role: "dialog"
5842
5881
  },
5843
- /* @__PURE__ */ import_react13.default.createElement("div", { className: "brokr-account-menu" }, /* @__PURE__ */ import_react13.default.createElement("div", { className: "brokr-account-menu-header" }, /* @__PURE__ */ import_react13.default.createElement(Avatar, { email: summary.email, name: summary.name, src: user.image, size: 44 }), /* @__PURE__ */ import_react13.default.createElement("div", { className: "brokr-account-menu-copy" }, /* @__PURE__ */ import_react13.default.createElement("strong", null, summary.name), /* @__PURE__ */ import_react13.default.createElement("span", null, summary.email))), /* @__PURE__ */ import_react13.default.createElement("div", { className: "brokr-account-menu-actions" }, /* @__PURE__ */ import_react13.default.createElement("button", { className: "brokr-account-menu-action", onClick: openSettings, type: "button" }, /* @__PURE__ */ import_react13.default.createElement("span", { className: "brokr-account-menu-action-icon" }, /* @__PURE__ */ import_react13.default.createElement(SettingsIcon, { size: 15 })), /* @__PURE__ */ import_react13.default.createElement("span", null, "Settings")), /* @__PURE__ */ import_react13.default.createElement("button", { className: "brokr-account-menu-action", onClick: () => void handleSignOut(), type: "button" }, /* @__PURE__ */ import_react13.default.createElement("span", { className: "brokr-account-menu-action-icon" }, /* @__PURE__ */ import_react13.default.createElement(LogoutIcon, { size: 15 })), /* @__PURE__ */ import_react13.default.createElement("span", null, "Sign out"))))
5882
+ /* @__PURE__ */ import_react13.default.createElement("div", { className: "brokr-account-menu" }, /* @__PURE__ */ import_react13.default.createElement("div", { className: "brokr-account-menu-header" }, /* @__PURE__ */ import_react13.default.createElement(Avatar, { email: summary.email, name: summary.name, src: user.image, size: 44 }), /* @__PURE__ */ import_react13.default.createElement("div", { className: "brokr-account-menu-copy" }, /* @__PURE__ */ import_react13.default.createElement("strong", null, summary.name), /* @__PURE__ */ import_react13.default.createElement("span", null, summary.email))), /* @__PURE__ */ import_react13.default.createElement("div", { className: "brokr-account-menu-actions" }, /* @__PURE__ */ import_react13.default.createElement("button", { className: "brokr-account-menu-action", onClick: openSettings, type: "button" }, /* @__PURE__ */ import_react13.default.createElement("span", { className: "brokr-account-menu-action-icon" }, /* @__PURE__ */ import_react13.default.createElement(SettingsIcon, { size: 15 })), /* @__PURE__ */ import_react13.default.createElement("span", null, "Settings")), /* @__PURE__ */ import_react13.default.createElement("button", { className: "brokr-account-menu-action", onClick: handleSignOut, type: "button" }, /* @__PURE__ */ import_react13.default.createElement("span", { className: "brokr-account-menu-action-icon" }, /* @__PURE__ */ import_react13.default.createElement(LogoutIcon, { size: 15 })), /* @__PURE__ */ import_react13.default.createElement("span", null, "Sign out"))))
5844
5883
  ) : null, settingsOpen ? /* @__PURE__ */ import_react13.default.createElement("div", { className: "brokr-modal-backdrop", role: "presentation", onClick: closeSettings }, /* @__PURE__ */ import_react13.default.createElement(
5845
5884
  "div",
5846
5885
  {
5847
5886
  "aria-modal": "true",
5848
5887
  className: "brokr-panel brokr-modal-dialog brokr-account-settings-dialog",
5849
- onClick: (event) => event.stopPropagation(),
5888
+ onClick: stopPropagation,
5850
5889
  role: "dialog"
5851
5890
  },
5852
5891
  /* @__PURE__ */ import_react13.default.createElement("div", { className: "brokr-modal-toolbar" }, /* @__PURE__ */ import_react13.default.createElement("button", { className: "brokr-button-ghost", onClick: closeSettings, type: "button" }, /* @__PURE__ */ import_react13.default.createElement(CloseIcon, { size: 16 }))),
@@ -6042,6 +6081,28 @@ function AuthWall({ children }) {
6042
6081
 
6043
6082
  // src/react/payments/Plans.tsx
6044
6083
  var import_react22 = __toESM(require("react"));
6084
+ function PlanCard({
6085
+ plan,
6086
+ highlight,
6087
+ isPending,
6088
+ onSelect
6089
+ }) {
6090
+ const isHighlighted = highlight === plan.slug;
6091
+ const features = Object.entries(plan.features);
6092
+ const handleClick = (0, import_react22.useCallback)(() => {
6093
+ void onSelect(plan.slug);
6094
+ }, [plan.slug, onSelect]);
6095
+ return /* @__PURE__ */ import_react22.default.createElement("article", { className: "brokr-card brokr-plan-card", "data-highlight": isHighlighted, key: plan.slug }, /* @__PURE__ */ import_react22.default.createElement("div", { className: "brokr-section", style: { gap: "var(--brokr-space-3)" } }, /* @__PURE__ */ import_react22.default.createElement("div", { className: "brokr-brand-row", style: { justifyContent: "space-between" } }, /* @__PURE__ */ import_react22.default.createElement("strong", null, plan.name), plan.isCurrent ? /* @__PURE__ */ import_react22.default.createElement("span", { className: "brokr-badge" }, "Current") : null, isHighlighted && !plan.isCurrent ? /* @__PURE__ */ import_react22.default.createElement("span", { className: "brokr-badge" }, "Recommended") : null), /* @__PURE__ */ import_react22.default.createElement("div", { className: "brokr-plan-price" }, /* @__PURE__ */ import_react22.default.createElement("span", { className: "brokr-plan-value" }, plan.price === 0 ? "$0" : `$${(plan.price / 100).toFixed(0)}`), /* @__PURE__ */ import_react22.default.createElement("span", { className: "brokr-copy" }, "/", plan.interval)), plan.trialDays ? /* @__PURE__ */ import_react22.default.createElement("span", { className: "brokr-copy" }, plan.trialDays, " day trial included.") : null), /* @__PURE__ */ import_react22.default.createElement("ul", { className: "brokr-feature-list" }, features.map(([key, value]) => /* @__PURE__ */ import_react22.default.createElement("li", { className: "brokr-feature-item", key }, /* @__PURE__ */ import_react22.default.createElement(CheckIcon, { size: 14 }), /* @__PURE__ */ import_react22.default.createElement("span", null, featureLabel(key), " \xB7 ", featureValueLabel(value))))), /* @__PURE__ */ import_react22.default.createElement(
6096
+ "button",
6097
+ {
6098
+ className: plan.isCurrent ? "brokr-button-secondary" : "brokr-button",
6099
+ disabled: plan.isCurrent || isPending,
6100
+ onClick: handleClick,
6101
+ type: "button"
6102
+ },
6103
+ plan.isCurrent ? "Current plan" : isPending ? "Redirecting" : "Choose plan"
6104
+ ));
6105
+ }
6045
6106
  function Plans({
6046
6107
  columns,
6047
6108
  highlight,
@@ -6072,34 +6133,16 @@ function Plans({
6072
6133
  setPendingPlan(null);
6073
6134
  }
6074
6135
  }, [checkout, onSelect]);
6075
- return /* @__PURE__ */ import_react22.default.createElement("div", { className: "brokr-section" }, paymentsMode === "sandbox" ? /* @__PURE__ */ import_react22.default.createElement("div", { className: "brokr-inline-message" }, "Sandbox mode \u2014 test cards only.") : null, error ? /* @__PURE__ */ import_react22.default.createElement("div", { className: "brokr-inline-message", "data-tone": "error" }, error) : null, /* @__PURE__ */ import_react22.default.createElement("div", { className: "brokr-grid-auto", style: layoutStyle }, plans.map((plan) => {
6076
- const features = Object.entries(plan.features);
6077
- const isHighlighted = highlight === plan.slug;
6078
- const isPending = pendingPlan === plan.slug;
6079
- const handleClick = () => {
6080
- void handleSelect(plan.slug);
6081
- };
6082
- return /* @__PURE__ */ import_react22.default.createElement(
6083
- "article",
6084
- {
6085
- className: "brokr-card brokr-plan-card",
6086
- "data-highlight": isHighlighted,
6087
- key: plan.slug
6088
- },
6089
- /* @__PURE__ */ import_react22.default.createElement("div", { className: "brokr-section", style: { gap: "0.75rem" } }, /* @__PURE__ */ import_react22.default.createElement("div", { className: "brokr-brand-row", style: { justifyContent: "space-between" } }, /* @__PURE__ */ import_react22.default.createElement("strong", null, plan.name), plan.isCurrent ? /* @__PURE__ */ import_react22.default.createElement("span", { className: "brokr-badge" }, "Current") : null, isHighlighted && !plan.isCurrent ? /* @__PURE__ */ import_react22.default.createElement("span", { className: "brokr-badge" }, "Recommended") : null), /* @__PURE__ */ import_react22.default.createElement("div", { className: "brokr-plan-price" }, /* @__PURE__ */ import_react22.default.createElement("span", { className: "brokr-plan-value" }, plan.price === 0 ? "$0" : `$${(plan.price / 100).toFixed(0)}`), /* @__PURE__ */ import_react22.default.createElement("span", { className: "brokr-copy" }, "/", plan.interval)), plan.trialDays ? /* @__PURE__ */ import_react22.default.createElement("span", { className: "brokr-copy" }, plan.trialDays, " day trial included.") : null),
6090
- /* @__PURE__ */ import_react22.default.createElement(
6091
- "button",
6092
- {
6093
- className: plan.isCurrent ? "brokr-button-secondary" : "brokr-button",
6094
- disabled: plan.isCurrent || isPending,
6095
- onClick: handleClick,
6096
- type: "button"
6097
- },
6098
- plan.isCurrent ? "Current plan" : isPending ? "Redirecting" : "Choose plan"
6099
- ),
6100
- /* @__PURE__ */ import_react22.default.createElement("ul", { className: "brokr-feature-list" }, features.map(([key, value]) => /* @__PURE__ */ import_react22.default.createElement("li", { className: "brokr-feature-item", key }, /* @__PURE__ */ import_react22.default.createElement(CheckIcon, { size: 14 }), /* @__PURE__ */ import_react22.default.createElement("span", null, featureLabel(key), " \xB7 ", featureValueLabel(value)))))
6101
- );
6102
- })));
6136
+ return /* @__PURE__ */ import_react22.default.createElement("div", { className: "brokr-section" }, paymentsMode === "sandbox" ? /* @__PURE__ */ import_react22.default.createElement("div", { className: "brokr-inline-message" }, "Sandbox mode \u2014 test cards only.") : null, error ? /* @__PURE__ */ import_react22.default.createElement("div", { className: "brokr-inline-message", "data-tone": "error" }, error) : null, /* @__PURE__ */ import_react22.default.createElement("div", { className: "brokr-grid-auto", style: layoutStyle }, plans.map((plan) => /* @__PURE__ */ import_react22.default.createElement(
6137
+ PlanCard,
6138
+ {
6139
+ key: plan.slug,
6140
+ plan,
6141
+ highlight,
6142
+ isPending: pendingPlan === plan.slug,
6143
+ onSelect: handleSelect
6144
+ }
6145
+ ))));
6103
6146
  }
6104
6147
 
6105
6148
  // src/react/payments/FeatureMeter.tsx
@@ -6237,7 +6280,7 @@ function AutoReloadToggle({ onChange }) {
6237
6280
  setIsPending(false);
6238
6281
  }
6239
6282
  }, [enabled, onChange]);
6240
- return /* @__PURE__ */ import_react27.default.createElement("div", { className: "brokr-card brokr-gate-card" }, /* @__PURE__ */ import_react27.default.createElement("div", { className: "brokr-brand-row", style: { justifyContent: "space-between" } }, /* @__PURE__ */ import_react27.default.createElement("div", { className: "brokr-section", style: { gap: "0.35rem" } }, /* @__PURE__ */ import_react27.default.createElement("strong", null, "Auto reload"), /* @__PURE__ */ import_react27.default.createElement("span", { className: "brokr-copy" }, helperText)), /* @__PURE__ */ import_react27.default.createElement(
6283
+ return /* @__PURE__ */ import_react27.default.createElement("div", { className: "brokr-card brokr-gate-card" }, /* @__PURE__ */ import_react27.default.createElement("div", { className: "brokr-brand-row", style: { justifyContent: "space-between" } }, /* @__PURE__ */ import_react27.default.createElement("div", { className: "brokr-section", style: { gap: "var(--brokr-space-1)" } }, /* @__PURE__ */ import_react27.default.createElement("strong", null, "Auto reload"), /* @__PURE__ */ import_react27.default.createElement("span", { className: "brokr-copy" }, helperText)), /* @__PURE__ */ import_react27.default.createElement(
6241
6284
  "button",
6242
6285
  {
6243
6286
  className: "brokr-button-secondary",
@@ -6406,6 +6449,39 @@ function isSSEResponse(response) {
6406
6449
  return ct.includes("text/event-stream") && response.body !== null;
6407
6450
  }
6408
6451
 
6452
+ // src/react/chat/memory.ts
6453
+ var MAX_MEMORY_CHARS = 2e3;
6454
+ function serializeMemory(obj, prefix = "") {
6455
+ const lines = [];
6456
+ for (const [key, value] of Object.entries(obj)) {
6457
+ if (value === null || value === void 0) continue;
6458
+ const label = prefix ? `${prefix}.${key}` : key;
6459
+ if (Array.isArray(value)) {
6460
+ lines.push(`${label}: ${value.join(", ")}`);
6461
+ } else if (typeof value === "object") {
6462
+ lines.push(...serializeMemory(value, label));
6463
+ } else {
6464
+ lines.push(`${label}: ${String(value)}`);
6465
+ }
6466
+ }
6467
+ return lines;
6468
+ }
6469
+ function buildEffectivePrompt(prompt, memory) {
6470
+ if (!memory || Object.keys(memory).length === 0) return prompt;
6471
+ let block = serializeMemory(memory).join("\n");
6472
+ if (block.length > MAX_MEMORY_CHARS) {
6473
+ block = block.slice(0, MAX_MEMORY_CHARS);
6474
+ if (typeof console !== "undefined") {
6475
+ console.warn("[brokr] memory exceeded 2000 char limit, truncated");
6476
+ }
6477
+ }
6478
+ const memSection = `[User context]
6479
+ ${block}`;
6480
+ return prompt ? `${memSection}
6481
+
6482
+ ${prompt}` : memSection;
6483
+ }
6484
+
6409
6485
  // src/react/chat/useChat.ts
6410
6486
  function makeId(prefix) {
6411
6487
  return `${prefix}_${crypto.randomUUID()}`;
@@ -6423,7 +6499,9 @@ function useChat(config) {
6423
6499
  const {
6424
6500
  endpoint,
6425
6501
  prompt,
6426
- model: activeModel,
6502
+ explicitModel,
6503
+ displayModel: activeModel,
6504
+ memory,
6427
6505
  persist,
6428
6506
  surface,
6429
6507
  subject,
@@ -6459,9 +6537,25 @@ function useChat(config) {
6459
6537
  const scrollContainerRef = (0, import_react31.useRef)(null);
6460
6538
  const sentinelRef = (0, import_react31.useRef)(null);
6461
6539
  const displaySidebarItems = (0, import_react31.useMemo)(() => {
6462
- if (isControlled) return threadsProp?.map((t) => ({ id: t.id, title: t.title })) ?? [];
6463
- if (isPersist) return serverThreads.map((t) => ({ id: t.id, title: t.title }));
6464
- return memConversations.map((c) => ({ id: c.id, title: c.title || "New chat" }));
6540
+ if (isControlled) {
6541
+ return threadsProp?.map((t) => ({
6542
+ id: t.id,
6543
+ title: t.title,
6544
+ updatedAt: t.updatedAt ?? null
6545
+ })) ?? [];
6546
+ }
6547
+ if (isPersist) {
6548
+ return serverThreads.map((t) => ({
6549
+ id: t.id,
6550
+ title: t.title,
6551
+ updatedAt: t.updatedAt ?? null
6552
+ }));
6553
+ }
6554
+ return memConversations.map((c) => ({
6555
+ id: c.id,
6556
+ title: c.title || "New chat",
6557
+ updatedAt: new Date(c.updatedAt).toISOString()
6558
+ }));
6465
6559
  }, [isControlled, isPersist, threadsProp, serverThreads, memConversations]);
6466
6560
  const activeId = (0, import_react31.useMemo)(() => {
6467
6561
  if (isControlled) return activeThreadIdProp ?? null;
@@ -6642,8 +6736,11 @@ function useChat(config) {
6642
6736
  messages: trimToTokenBudget(
6643
6737
  nextMessages.filter((m) => m.role === "user" || m.role === "assistant" && m.content).map(({ content: mc, role }) => ({ role, content: mc }))
6644
6738
  ),
6645
- model: activeModel,
6646
- ...prompt ? { systemPrompt: prompt } : {}
6739
+ ...explicitModel ? { model: explicitModel } : {},
6740
+ ...(() => {
6741
+ const ep = buildEffectivePrompt(prompt, memory);
6742
+ return ep ? { systemPrompt: ep } : {};
6743
+ })()
6647
6744
  })
6648
6745
  });
6649
6746
  if (!response.ok) {
@@ -6688,8 +6785,10 @@ function useChat(config) {
6688
6785
  ensureMemConversation,
6689
6786
  memConversations,
6690
6787
  endpoint,
6788
+ explicitModel,
6691
6789
  activeModel,
6692
6790
  prompt,
6791
+ memory,
6693
6792
  updateMemMessages,
6694
6793
  appendMemDelta,
6695
6794
  animateTitle,
@@ -6711,9 +6810,12 @@ function useChat(config) {
6711
6810
  content,
6712
6811
  surface,
6713
6812
  subject: subject ?? null,
6714
- model: activeModel,
6813
+ ...explicitModel ? { model: explicitModel } : {},
6715
6814
  userId: userId ?? void 0,
6716
- ...prompt ? { systemPrompt: prompt } : {}
6815
+ ...(() => {
6816
+ const ep = buildEffectivePrompt(prompt, memory);
6817
+ return ep ? { systemPrompt: ep } : {};
6818
+ })()
6717
6819
  })
6718
6820
  });
6719
6821
  if (!response.ok) {
@@ -6768,8 +6870,10 @@ function useChat(config) {
6768
6870
  endpoint,
6769
6871
  surface,
6770
6872
  subject,
6873
+ explicitModel,
6771
6874
  activeModel,
6772
6875
  prompt,
6876
+ memory,
6773
6877
  userId,
6774
6878
  setServerMessages,
6775
6879
  setServerActiveId,
@@ -6927,6 +7031,37 @@ function useChat(config) {
6927
7031
 
6928
7032
  // src/react/chat/ModelSelector.tsx
6929
7033
  var import_react32 = __toESM(require("react"));
7034
+ function ModelOption({
7035
+ provider,
7036
+ isActive,
7037
+ isLocked,
7038
+ onSelect,
7039
+ onCheckout
7040
+ }) {
7041
+ const handleClick = (0, import_react32.useCallback)(() => {
7042
+ if (isLocked) {
7043
+ void onCheckout({ plan: "pro" });
7044
+ } else {
7045
+ onSelect(provider.model);
7046
+ }
7047
+ }, [isLocked, onCheckout, onSelect, provider.model]);
7048
+ return /* @__PURE__ */ import_react32.default.createElement(
7049
+ "button",
7050
+ {
7051
+ "aria-selected": isActive,
7052
+ className: "brokr-model-option",
7053
+ "data-active": isActive,
7054
+ "data-locked": isLocked,
7055
+ disabled: isLocked,
7056
+ onClick: handleClick,
7057
+ title: isLocked ? "Add credits to unlock" : void 0,
7058
+ type: "button"
7059
+ },
7060
+ /* @__PURE__ */ import_react32.default.createElement("img", { alt: "", className: "brokr-model-logo", src: provider.logo }),
7061
+ /* @__PURE__ */ import_react32.default.createElement("span", { className: "brokr-model-option-label" }, provider.label),
7062
+ isLocked ? /* @__PURE__ */ import_react32.default.createElement("span", { className: "brokr-model-lock", "aria-hidden": "true" }, /* @__PURE__ */ import_react32.default.createElement(LockIcon, { size: 13 })) : null
7063
+ );
7064
+ }
6930
7065
  function ModelSelector({
6931
7066
  activeModel,
6932
7067
  setSelectedModel,
@@ -6935,10 +7070,19 @@ function ModelSelector({
6935
7070
  }) {
6936
7071
  const [selectorOpen, setSelectorOpen] = (0, import_react32.useState)(false);
6937
7072
  const selectorRef = (0, import_react32.useRef)(null);
7073
+ const isAuto = activeModel === STACK_DEFAULT || !providers.some((p) => p.model === activeModel);
6938
7074
  const activeProvider = (0, import_react32.useMemo)(
6939
- () => providers.find((p) => p.model === activeModel) ?? providers[0],
6940
- [activeModel]
7075
+ () => isAuto ? void 0 : providers.find((p) => p.model === activeModel) ?? providers[0],
7076
+ [activeModel, isAuto]
6941
7077
  );
7078
+ const handleModelSelect = (0, import_react32.useCallback)((model) => {
7079
+ setSelectedModel(model === STACK_DEFAULT ? null : model);
7080
+ setSelectorOpen(false);
7081
+ }, [setSelectedModel]);
7082
+ const handleAutoSelect = (0, import_react32.useCallback)(() => {
7083
+ setSelectedModel(null);
7084
+ setSelectorOpen(false);
7085
+ }, [setSelectedModel]);
6942
7086
  (0, import_react32.useEffect)(() => {
6943
7087
  if (!selectorOpen) return;
6944
7088
  const onMouseDown = (e) => {
@@ -6966,40 +7110,79 @@ function ModelSelector({
6966
7110
  onClick: handleToggle,
6967
7111
  type: "button"
6968
7112
  },
6969
- activeProvider ? /* @__PURE__ */ import_react32.default.createElement("img", { alt: "", className: "brokr-model-logo", src: activeProvider.logo }) : /* @__PURE__ */ import_react32.default.createElement("span", { className: "brokr-model-dot", style: { background: "currentColor" } }),
6970
- activeProvider?.label ?? "Model",
7113
+ activeProvider ? /* @__PURE__ */ import_react32.default.createElement("img", { alt: "", className: "brokr-model-logo", src: activeProvider.logo }) : /* @__PURE__ */ import_react32.default.createElement("span", { className: "brokr-model-dot", style: { background: "#6B7280" } }),
7114
+ isAuto ? "Auto" : activeProvider?.label ?? "Model",
6971
7115
  /* @__PURE__ */ import_react32.default.createElement(ChevronDownIcon, { size: 13 })
6972
- ), selectorOpen ? /* @__PURE__ */ import_react32.default.createElement("div", { className: "brokr-model-dropdown", role: "listbox" }, providers.map((p) => {
6973
- const locked = !availableProviders.some((a) => a.id === p.id);
6974
- return /* @__PURE__ */ import_react32.default.createElement(
6975
- "button",
6976
- {
6977
- "aria-selected": p.model === activeModel,
6978
- className: "brokr-model-option",
6979
- "data-active": p.model === activeModel,
6980
- "data-locked": locked,
6981
- disabled: locked,
6982
- key: p.id,
6983
- onClick: () => {
6984
- if (locked) {
6985
- void checkout({ plan: "pro" });
6986
- } else {
6987
- setSelectedModel(p.model);
6988
- setSelectorOpen(false);
6989
- }
6990
- },
6991
- title: locked ? "Add credits to unlock" : void 0,
6992
- type: "button"
6993
- },
6994
- /* @__PURE__ */ import_react32.default.createElement("img", { alt: "", className: "brokr-model-logo", src: p.logo }),
6995
- /* @__PURE__ */ import_react32.default.createElement("span", { className: "brokr-model-option-label" }, p.label),
6996
- locked ? /* @__PURE__ */ import_react32.default.createElement("span", { className: "brokr-model-lock", "aria-hidden": "true" }, /* @__PURE__ */ import_react32.default.createElement(LockIcon, { size: 13 })) : null
6997
- );
6998
- })) : null);
7116
+ ), selectorOpen ? /* @__PURE__ */ import_react32.default.createElement("div", { className: "brokr-model-dropdown", role: "listbox" }, /* @__PURE__ */ import_react32.default.createElement(
7117
+ "button",
7118
+ {
7119
+ "aria-selected": isAuto,
7120
+ className: "brokr-model-option",
7121
+ "data-active": isAuto,
7122
+ onClick: handleAutoSelect,
7123
+ type: "button"
7124
+ },
7125
+ /* @__PURE__ */ import_react32.default.createElement("span", { className: "brokr-model-dot", style: { background: "#6B7280" } }),
7126
+ /* @__PURE__ */ import_react32.default.createElement("span", { className: "brokr-model-option-label" }, "Stack Default")
7127
+ ), providers.map((p) => /* @__PURE__ */ import_react32.default.createElement(
7128
+ ModelOption,
7129
+ {
7130
+ key: p.id,
7131
+ provider: p,
7132
+ isActive: !isAuto && p.model === activeModel,
7133
+ isLocked: !availableProviders.some((a) => a.id === p.id),
7134
+ onSelect: handleModelSelect,
7135
+ onCheckout: checkout
7136
+ }
7137
+ ))) : null);
6999
7138
  }
7000
7139
 
7001
7140
  // src/react/chat/ThreadSidebar.tsx
7002
7141
  var import_react33 = __toESM(require("react"));
7142
+ function ThreadItemButton({ id, title, isActive, onSelect }) {
7143
+ const handleClick = (0, import_react33.useCallback)(() => onSelect(id), [id, onSelect]);
7144
+ return /* @__PURE__ */ import_react33.default.createElement(
7145
+ "button",
7146
+ {
7147
+ className: "brokr-ai-chat-conversation",
7148
+ "data-active": isActive,
7149
+ onClick: handleClick,
7150
+ type: "button"
7151
+ },
7152
+ /* @__PURE__ */ import_react33.default.createElement(MessageIcon, { size: 12 }),
7153
+ /* @__PURE__ */ import_react33.default.createElement("span", { className: "brokr-ai-chat-conversation-label" }, title)
7154
+ );
7155
+ }
7156
+ var SIDEBAR_GROUP_ORDER = [
7157
+ "today",
7158
+ "yesterday",
7159
+ "this_week",
7160
+ "this_month",
7161
+ "earlier",
7162
+ "undated"
7163
+ ];
7164
+ var SIDEBAR_GROUP_LABELS = {
7165
+ today: "Today",
7166
+ yesterday: "Yesterday",
7167
+ this_week: "This Week",
7168
+ this_month: "This Month",
7169
+ earlier: "Earlier",
7170
+ undated: "Recent"
7171
+ };
7172
+ function resolveSidebarDateGroup(updatedAt) {
7173
+ if (!updatedAt) return "undated";
7174
+ const parsed = new Date(updatedAt);
7175
+ if (Number.isNaN(parsed.getTime())) return "undated";
7176
+ const now = /* @__PURE__ */ new Date();
7177
+ const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate());
7178
+ const startOfTarget = new Date(parsed.getFullYear(), parsed.getMonth(), parsed.getDate());
7179
+ const diffDays = Math.floor((startOfToday.getTime() - startOfTarget.getTime()) / 864e5);
7180
+ if (diffDays <= 0) return "today";
7181
+ if (diffDays === 1) return "yesterday";
7182
+ if (diffDays < 7) return "this_week";
7183
+ if (diffDays < 30) return "this_month";
7184
+ return "earlier";
7185
+ }
7003
7186
  function ThreadSidebar() {
7004
7187
  const {
7005
7188
  startNewChat,
@@ -7031,14 +7214,31 @@ function ThreadSidebar() {
7031
7214
  const handleRenameChange = (0, import_react33.useCallback)((e) => {
7032
7215
  setRenameValue(e.target.value);
7033
7216
  }, [setRenameValue]);
7217
+ const groupedSidebarItems = (0, import_react33.useMemo)(() => {
7218
+ const grouped = /* @__PURE__ */ new Map();
7219
+ for (const item of displaySidebarItems) {
7220
+ const key = resolveSidebarDateGroup(item.updatedAt);
7221
+ const bucket = grouped.get(key);
7222
+ if (bucket) {
7223
+ bucket.push(item);
7224
+ } else {
7225
+ grouped.set(key, [item]);
7226
+ }
7227
+ }
7228
+ return SIDEBAR_GROUP_ORDER.map((key) => ({
7229
+ key,
7230
+ label: SIDEBAR_GROUP_LABELS[key],
7231
+ items: grouped.get(key) ?? []
7232
+ })).filter((group) => group.items.length > 0);
7233
+ }, [displaySidebarItems]);
7034
7234
  const content = (0, import_react33.useMemo)(() => {
7035
7235
  if (threadsLoading && displaySidebarItems.length === 0) {
7036
- return /* @__PURE__ */ import_react33.default.createElement("div", { style: { display: "grid", gap: "0.5rem", padding: "0 0.65rem" } }, /* @__PURE__ */ import_react33.default.createElement(Skeleton, { width: "75%", height: 14, radius: 6 }), /* @__PURE__ */ import_react33.default.createElement(Skeleton, { width: "60%", height: 14, radius: 6 }), /* @__PURE__ */ import_react33.default.createElement(Skeleton, { width: "85%", height: 14, radius: 6 }));
7236
+ return /* @__PURE__ */ import_react33.default.createElement("div", { className: "brokr-ai-chat-sidebar-skeleton" }, /* @__PURE__ */ import_react33.default.createElement(Skeleton, { width: "75%", height: 14, radius: 6 }), /* @__PURE__ */ import_react33.default.createElement(Skeleton, { width: "60%", height: 14, radius: 6 }), /* @__PURE__ */ import_react33.default.createElement(Skeleton, { width: "85%", height: 14, radius: 6 }));
7037
7237
  }
7038
7238
  if (displaySidebarItems.length === 0) {
7039
7239
  return /* @__PURE__ */ import_react33.default.createElement("div", { className: "brokr-ai-chat-sidebar-empty" }, /* @__PURE__ */ import_react33.default.createElement("p", { className: "brokr-ai-chat-sidebar-empty-text" }, "No conversations yet. Start one above."));
7040
7240
  }
7041
- return /* @__PURE__ */ import_react33.default.createElement("div", { className: "brokr-ai-chat-conversations" }, displaySidebarItems.map((item) => renamingId === item.id ? /* @__PURE__ */ import_react33.default.createElement(
7241
+ return /* @__PURE__ */ import_react33.default.createElement("div", { className: "brokr-ai-chat-sidebar-groups" }, groupedSidebarItems.map((group) => /* @__PURE__ */ import_react33.default.createElement("section", { className: "brokr-ai-chat-sidebar-group", key: group.key }, /* @__PURE__ */ import_react33.default.createElement("span", { className: "brokr-ai-chat-sidebar-kicker" }, group.label), /* @__PURE__ */ import_react33.default.createElement("div", { className: "brokr-ai-chat-conversations" }, group.items.map((item) => renamingId === item.id ? /* @__PURE__ */ import_react33.default.createElement(
7042
7242
  "input",
7043
7243
  {
7044
7244
  autoFocus: true,
@@ -7050,28 +7250,28 @@ function ThreadSidebar() {
7050
7250
  value: renameValue
7051
7251
  }
7052
7252
  ) : /* @__PURE__ */ import_react33.default.createElement(
7053
- "button",
7253
+ ThreadItemButton,
7054
7254
  {
7055
- className: "brokr-ai-chat-conversation",
7056
- "data-active": item.id === activeId,
7057
7255
  key: item.id,
7058
- onClick: () => selectThreadAndCloseSidebar(item.id),
7059
- type: "button"
7060
- },
7061
- item.title
7062
- )));
7256
+ id: item.id,
7257
+ title: item.title,
7258
+ isActive: item.id === activeId,
7259
+ onSelect: selectThreadAndCloseSidebar
7260
+ }
7261
+ ))))));
7063
7262
  }, [
7064
7263
  threadsLoading,
7065
7264
  displaySidebarItems,
7066
7265
  renamingId,
7067
7266
  renameValue,
7267
+ groupedSidebarItems,
7068
7268
  activeId,
7069
7269
  selectThreadAndCloseSidebar,
7070
7270
  handleRenameBlur,
7071
7271
  handleRenameChange,
7072
7272
  handleRenameKeyDown
7073
7273
  ]);
7074
- return /* @__PURE__ */ import_react33.default.createElement(import_react33.default.Fragment, null, /* @__PURE__ */ import_react33.default.createElement("button", { className: "brokr-ai-chat-sidebar-button", onClick: handleNewChat, type: "button" }, /* @__PURE__ */ import_react33.default.createElement(MessageIcon, { size: 16 }), "New chat"), content);
7274
+ return /* @__PURE__ */ import_react33.default.createElement(import_react33.default.Fragment, null, /* @__PURE__ */ import_react33.default.createElement("button", { className: "brokr-ai-chat-sidebar-new-chat", onClick: handleNewChat, type: "button" }, /* @__PURE__ */ import_react33.default.createElement(MessageIcon, { size: 16 }), "New chat"), content);
7075
7275
  }
7076
7276
 
7077
7277
  // src/react/chat/MessagePane.tsx
@@ -7099,6 +7299,12 @@ function parseInline(text) {
7099
7299
  remaining = remaining.slice(boldMatch[0].length);
7100
7300
  continue;
7101
7301
  }
7302
+ const strikethroughMatch = remaining.match(/^~~(.+?)~~/);
7303
+ if (strikethroughMatch) {
7304
+ nodes.push(/* @__PURE__ */ import_react34.default.createElement("del", { key: key++ }, strikethroughMatch[1]));
7305
+ remaining = remaining.slice(strikethroughMatch[0].length);
7306
+ continue;
7307
+ }
7102
7308
  const italicMatch = remaining.match(/^\*(.+?)\*/);
7103
7309
  if (italicMatch) {
7104
7310
  nodes.push(/* @__PURE__ */ import_react34.default.createElement("em", { key: key++ }, italicMatch[1]));
@@ -7115,7 +7321,7 @@ function parseInline(text) {
7115
7321
  remaining = remaining.slice(linkMatch[0].length);
7116
7322
  continue;
7117
7323
  }
7118
- const nextSpecial = remaining.search(/[`*\[]/);
7324
+ const nextSpecial = remaining.search(/[`*~\[]/);
7119
7325
  if (nextSpecial === -1) {
7120
7326
  nodes.push(remaining);
7121
7327
  break;
@@ -7146,6 +7352,15 @@ function CodeBlock({ code, language }) {
7146
7352
  "Copy"
7147
7353
  )), /* @__PURE__ */ import_react34.default.createElement("pre", { className: "brokr-md-codeblock-pre" }, /* @__PURE__ */ import_react34.default.createElement("code", null, code)));
7148
7354
  }
7355
+ function looksLikeTableSeparator(line) {
7356
+ const trimmed = line.trim();
7357
+ return /^\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)+\|?$/.test(trimmed);
7358
+ }
7359
+ function splitTableRow(line) {
7360
+ const trimmed = line.trim();
7361
+ const withoutEdges = trimmed.replace(/^\|/, "").replace(/\|$/, "");
7362
+ return withoutEdges.split("|").map((cell) => cell.trim());
7363
+ }
7149
7364
  function parseBlocks(text) {
7150
7365
  const blocks = [];
7151
7366
  const lines = text.split("\n");
@@ -7175,22 +7390,44 @@ function parseBlocks(text) {
7175
7390
  i++;
7176
7391
  continue;
7177
7392
  }
7393
+ if (line.includes("|") && i + 1 < lines.length && looksLikeTableSeparator(lines[i + 1] ?? "")) {
7394
+ const headers = splitTableRow(line);
7395
+ const rows = [];
7396
+ i += 2;
7397
+ while (i < lines.length) {
7398
+ const candidate = lines[i] ?? "";
7399
+ if (!candidate.trim() || !candidate.includes("|")) break;
7400
+ rows.push(splitTableRow(candidate));
7401
+ i++;
7402
+ }
7403
+ blocks.push({ type: "table", content: "", headers, rows });
7404
+ continue;
7405
+ }
7406
+ if (/^[\s]*>\s?/.test(line)) {
7407
+ const quoteLines = [];
7408
+ while (i < lines.length && /^[\s]*>\s?/.test(lines[i])) {
7409
+ quoteLines.push(lines[i].replace(/^[\s]*>\s?/, ""));
7410
+ i++;
7411
+ }
7412
+ blocks.push({ type: "quote", content: "", items: quoteLines });
7413
+ continue;
7414
+ }
7178
7415
  if (/^[\s]*[-*]\s+/.test(line)) {
7179
7416
  const items = [];
7180
7417
  while (i < lines.length && /^[\s]*[-*]\s+/.test(lines[i])) {
7181
7418
  items.push(lines[i].replace(/^[\s]*[-*]\s+/, ""));
7182
7419
  i++;
7183
7420
  }
7184
- blocks.push({ type: "list", content: "", items });
7421
+ blocks.push({ type: "list", content: "", items, ordered: false });
7185
7422
  continue;
7186
7423
  }
7187
- if (/^[\s]*\d+\.\s+/.test(line)) {
7424
+ if (/^[\s]*\d+[.)]\s+/.test(line)) {
7188
7425
  const items = [];
7189
- while (i < lines.length && /^[\s]*\d+\.\s+/.test(lines[i])) {
7190
- items.push(lines[i].replace(/^[\s]*\d+\.\s+/, ""));
7426
+ while (i < lines.length && /^[\s]*\d+[.)]\s+/.test(lines[i])) {
7427
+ items.push(lines[i].replace(/^[\s]*\d+[.)]\s+/, ""));
7191
7428
  i++;
7192
7429
  }
7193
- blocks.push({ type: "list", content: "", items });
7430
+ blocks.push({ type: "list", content: "", items, ordered: true });
7194
7431
  continue;
7195
7432
  }
7196
7433
  if (!line.trim()) {
@@ -7199,7 +7436,7 @@ function parseBlocks(text) {
7199
7436
  }
7200
7437
  const paraLines = [line];
7201
7438
  i++;
7202
- while (i < lines.length && lines[i].trim() && !lines[i].trimStart().startsWith("```") && !lines[i].match(/^#{1,6}\s/) && !/^(-{3,}|\*{3,}|_{3,})\s*$/.test(lines[i].trim()) && !/^[\s]*[-*]\s+/.test(lines[i]) && !/^[\s]*\d+\.\s+/.test(lines[i])) {
7439
+ while (i < lines.length && lines[i].trim() && !lines[i].trimStart().startsWith("```") && !lines[i].match(/^#{1,6}\s/) && !(lines[i].includes("|") && i + 1 < lines.length && looksLikeTableSeparator(lines[i + 1] ?? "")) && !/^(-{3,}|\*{3,}|_{3,})\s*$/.test(lines[i].trim()) && !/^[\s]*>\s?/.test(lines[i]) && !/^[\s]*[-*]\s+/.test(lines[i]) && !/^[\s]*\d+[.)]\s+/.test(lines[i])) {
7203
7440
  paraLines.push(lines[i]);
7204
7441
  i++;
7205
7442
  }
@@ -7219,7 +7456,14 @@ function renderBlocks(blocks) {
7219
7456
  return /* @__PURE__ */ import_react34.default.createElement(Tag, { className: `brokr-md-heading brokr-md-h${block.level}`, key: idx }, parseInline(block.content));
7220
7457
  }
7221
7458
  case "list":
7222
- return /* @__PURE__ */ import_react34.default.createElement("ul", { className: "brokr-md-list", key: idx }, block.items?.map((item, li) => /* @__PURE__ */ import_react34.default.createElement("li", { key: li }, parseInline(item))));
7459
+ if (block.ordered) {
7460
+ return /* @__PURE__ */ import_react34.default.createElement("ol", { className: "brokr-md-list brokr-md-list-ordered", key: idx }, block.items?.map((item, li) => /* @__PURE__ */ import_react34.default.createElement("li", { key: li }, parseInline(item))));
7461
+ }
7462
+ return /* @__PURE__ */ import_react34.default.createElement("ul", { className: "brokr-md-list brokr-md-list-unordered", key: idx }, block.items?.map((item, li) => /* @__PURE__ */ import_react34.default.createElement("li", { key: li }, parseInline(item))));
7463
+ case "quote":
7464
+ return /* @__PURE__ */ import_react34.default.createElement("blockquote", { className: "brokr-md-quote", key: idx }, block.items?.map((line, lineIndex) => /* @__PURE__ */ import_react34.default.createElement("p", { className: "brokr-md-quote-line", key: lineIndex }, parseInline(line))));
7465
+ case "table":
7466
+ return /* @__PURE__ */ import_react34.default.createElement("div", { className: "brokr-md-table-wrap", key: idx }, /* @__PURE__ */ import_react34.default.createElement("table", { className: "brokr-md-table" }, /* @__PURE__ */ import_react34.default.createElement("thead", null, /* @__PURE__ */ import_react34.default.createElement("tr", null, block.headers?.map((header, headerIndex) => /* @__PURE__ */ import_react34.default.createElement("th", { key: headerIndex }, parseInline(header))))), /* @__PURE__ */ import_react34.default.createElement("tbody", null, block.rows?.map((row, rowIndex) => /* @__PURE__ */ import_react34.default.createElement("tr", { key: rowIndex }, row.map((cell, cellIndex) => /* @__PURE__ */ import_react34.default.createElement("td", { key: cellIndex }, parseInline(cell))))))));
7223
7467
  case "paragraph":
7224
7468
  default:
7225
7469
  return /* @__PURE__ */ import_react34.default.createElement("p", { className: "brokr-md-paragraph", key: idx }, parseInline(block.content));
@@ -7258,6 +7502,12 @@ function MessageBubble({ message, isTyping, user }) {
7258
7502
  }
7259
7503
 
7260
7504
  // src/react/chat/MessagePane.tsx
7505
+ function StarterPromptButton({ prompt, onSend }) {
7506
+ const handleClick = (0, import_react36.useCallback)(() => {
7507
+ void onSend(prompt);
7508
+ }, [prompt, onSend]);
7509
+ return /* @__PURE__ */ import_react36.default.createElement("button", { className: "brokr-ai-chat-starter", onClick: handleClick, type: "button" }, prompt);
7510
+ }
7261
7511
  function MessagePane({ starterPrompts, emptyTitle, emptyCopy, subtitle }) {
7262
7512
  const {
7263
7513
  displayMessages,
@@ -7288,20 +7538,17 @@ function MessagePane({ starterPrompts, emptyTitle, emptyCopy, subtitle }) {
7288
7538
  );
7289
7539
  });
7290
7540
  }, [visibleMessages, isSubmitting, user]);
7291
- return /* @__PURE__ */ import_react36.default.createElement("div", { className: "brokr-ai-chat-thread", "data-empty": isEmpty, ref: scrollContainerRef }, isEmpty ? /* @__PURE__ */ import_react36.default.createElement("div", { className: "brokr-ai-chat-empty" }, /* @__PURE__ */ import_react36.default.createElement(SparkIcon, { size: 28 }), /* @__PURE__ */ import_react36.default.createElement("h2", { className: "brokr-title" }, emptyTitle), subtitle ?? emptyCopy ? /* @__PURE__ */ import_react36.default.createElement("p", { className: "brokr-copy" }, subtitle ?? emptyCopy) : null, starterPrompts.length > 0 ? /* @__PURE__ */ import_react36.default.createElement("div", { className: "brokr-ai-chat-starters" }, starterPrompts.map((sp) => /* @__PURE__ */ import_react36.default.createElement(
7292
- "button",
7293
- {
7294
- className: "brokr-ai-chat-starter",
7295
- key: sp,
7296
- onClick: () => void sendMessage(sp),
7297
- type: "button"
7298
- },
7299
- sp
7300
- ))) : null) : /* @__PURE__ */ import_react36.default.createElement("div", { className: "brokr-ai-chat-thread-inner" }, (isPersist ? hasMoreMessages : memHasMore) ? /* @__PURE__ */ import_react36.default.createElement("div", { ref: sentinelRef, className: "brokr-ai-chat-sentinel" }, loadingOlder ? /* @__PURE__ */ import_react36.default.createElement("div", { style: { display: "flex", justifyContent: "center", padding: "0.5rem" } }, /* @__PURE__ */ import_react36.default.createElement(Skeleton, { width: 120, height: 12, radius: 6 })) : null) : null, messageElements, /* @__PURE__ */ import_react36.default.createElement("div", { ref: bottomRef })));
7541
+ return /* @__PURE__ */ import_react36.default.createElement("div", { className: "brokr-ai-chat-thread", "data-empty": isEmpty, ref: scrollContainerRef }, isEmpty ? /* @__PURE__ */ import_react36.default.createElement("div", { className: "brokr-ai-chat-empty" }, /* @__PURE__ */ import_react36.default.createElement(SparkIcon, { size: 28 }), /* @__PURE__ */ import_react36.default.createElement("h2", { className: "brokr-title" }, emptyTitle), subtitle ?? emptyCopy ? /* @__PURE__ */ import_react36.default.createElement("p", { className: "brokr-copy" }, subtitle ?? emptyCopy) : null, starterPrompts.length > 0 ? /* @__PURE__ */ import_react36.default.createElement("div", { className: "brokr-ai-chat-starters" }, starterPrompts.map((sp) => /* @__PURE__ */ import_react36.default.createElement(StarterPromptButton, { key: sp, prompt: sp, onSend: sendMessage }))) : null) : /* @__PURE__ */ import_react36.default.createElement("div", { className: "brokr-ai-chat-thread-inner" }, (isPersist ? hasMoreMessages : memHasMore) ? /* @__PURE__ */ import_react36.default.createElement("div", { ref: sentinelRef, className: "brokr-ai-chat-sentinel" }, loadingOlder ? /* @__PURE__ */ import_react36.default.createElement("div", { style: { display: "flex", justifyContent: "center", padding: "0.5rem" } }, /* @__PURE__ */ import_react36.default.createElement(Skeleton, { width: 120, height: 12, radius: 6 })) : null) : null, messageElements, /* @__PURE__ */ import_react36.default.createElement("div", { ref: bottomRef })));
7301
7542
  }
7302
7543
 
7303
7544
  // src/react/chat/ChatInput.tsx
7304
7545
  var import_react37 = __toESM(require("react"));
7546
+ function CommandButton({ cmd, chatContext }) {
7547
+ const handleClick = (0, import_react37.useCallback)(() => {
7548
+ void cmd.run(chatContext);
7549
+ }, [cmd, chatContext]);
7550
+ return /* @__PURE__ */ import_react37.default.createElement("button", { className: "brokr-ai-chat-sidebar-button", key: cmd.id, onClick: handleClick, type: "button" }, cmd.text);
7551
+ }
7305
7552
  function ChatInput() {
7306
7553
  const {
7307
7554
  input,
@@ -7332,16 +7579,7 @@ function ChatInput() {
7332
7579
  const handleChange = (0, import_react37.useCallback)((e) => {
7333
7580
  setInput(e.target.value);
7334
7581
  }, [setInput]);
7335
- return /* @__PURE__ */ import_react37.default.createElement("form", { className: "brokr-ai-chat-input-area", onSubmit: handleSubmit }, /* @__PURE__ */ import_react37.default.createElement("div", { className: "brokr-ai-chat-input-container" }, composerCommands.length > 0 ? /* @__PURE__ */ import_react37.default.createElement("div", { className: "brokr-ai-chat-composer-actions" }, composerCommands.map((cmd) => /* @__PURE__ */ import_react37.default.createElement(
7336
- "button",
7337
- {
7338
- className: "brokr-ai-chat-sidebar-button",
7339
- key: cmd.id,
7340
- onClick: () => void cmd.run(chatContext),
7341
- type: "button"
7342
- },
7343
- cmd.text
7344
- ))) : null, /* @__PURE__ */ import_react37.default.createElement("div", { className: "brokr-ai-chat-input-row" }, /* @__PURE__ */ import_react37.default.createElement(
7582
+ return /* @__PURE__ */ import_react37.default.createElement("form", { className: "brokr-ai-chat-input-area", onSubmit: handleSubmit }, /* @__PURE__ */ import_react37.default.createElement("div", { className: "brokr-ai-chat-input-container" }, composerCommands.length > 0 ? /* @__PURE__ */ import_react37.default.createElement("div", { className: "brokr-ai-chat-composer-actions" }, composerCommands.map((cmd) => /* @__PURE__ */ import_react37.default.createElement(CommandButton, { key: cmd.id, cmd, chatContext }))) : null, /* @__PURE__ */ import_react37.default.createElement("div", { className: "brokr-ai-chat-input-row" }, /* @__PURE__ */ import_react37.default.createElement(
7345
7583
  "textarea",
7346
7584
  {
7347
7585
  className: "brokr-ai-chat-textarea",
@@ -7382,6 +7620,7 @@ function AIChat(inlineProps) {
7382
7620
  subtitle,
7383
7621
  model: modelProp,
7384
7622
  modelSelector,
7623
+ memory,
7385
7624
  variant = 1,
7386
7625
  sidebar: sidebarProp,
7387
7626
  threadMenu: threadMenuProp,
@@ -7405,16 +7644,12 @@ function AIChat(inlineProps) {
7405
7644
  const headerVisible = variant !== 3;
7406
7645
  const threadMenuVisible = threadMenuProp !== void 0 ? threadMenuProp : variant !== 3;
7407
7646
  const modelSelectorVisible = (modelSelector !== void 0 ? modelSelector : true) && !modelProp;
7408
- const [selectedModel, setSelectedModel] = (0, import_react38.useState)(() => {
7409
- if (modelProp) return modelProp;
7410
- return providers.find((p) => p.free)?.model ?? providers[0]?.model ?? "";
7411
- });
7412
- (0, import_react38.useEffect)(() => {
7413
- if (modelProp) setSelectedModel(modelProp);
7414
- }, [modelProp]);
7415
- const activeModel = modelProp ?? selectedModel;
7647
+ const [userSelectedModel, setUserSelectedModel] = (0, import_react38.useState)(null);
7648
+ const explicitModel = modelProp ?? userSelectedModel ?? void 0;
7649
+ const displayModel = explicitModel ?? providers.find((p) => p.free)?.model ?? providers[0]?.model ?? "";
7650
+ const activeModel = explicitModel ?? STACK_DEFAULT;
7416
7651
  const activeProvider = (0, import_react38.useMemo)(
7417
- () => providers.find((p) => p.model === activeModel) ?? providers[0],
7652
+ () => activeModel === STACK_DEFAULT ? void 0 : providers.find((p) => p.model === activeModel) ?? providers[0],
7418
7653
  [activeModel]
7419
7654
  );
7420
7655
  const hasBalance = (0, import_react38.useMemo)(
@@ -7428,7 +7663,9 @@ function AIChat(inlineProps) {
7428
7663
  const chat = useChat({
7429
7664
  endpoint,
7430
7665
  prompt,
7431
- model: activeModel,
7666
+ explicitModel,
7667
+ displayModel,
7668
+ memory,
7432
7669
  persist,
7433
7670
  surface,
7434
7671
  subject,
@@ -7480,6 +7717,14 @@ function AIChat(inlineProps) {
7480
7717
  chat.selectThread(id);
7481
7718
  setSidebarOpen(false);
7482
7719
  }, [chat.selectThread]);
7720
+ const handleCopy = (0, import_react38.useCallback)((content) => {
7721
+ navigator.clipboard.writeText(content).catch(() => {
7722
+ });
7723
+ }, []);
7724
+ const handleStartRename = (0, import_react38.useCallback)((threadId) => {
7725
+ setThreadMenuOpenId(null);
7726
+ chat.startRename(threadId);
7727
+ }, [chat.startRename]);
7483
7728
  (0, import_react38.useEffect)(() => {
7484
7729
  if (!threadMenuOpenId) return;
7485
7730
  const onMouseDown = (e) => {
@@ -7528,26 +7773,20 @@ function AIChat(inlineProps) {
7528
7773
  activeThread: chat.activeThread,
7529
7774
  renderedTitle: finalTitle,
7530
7775
  isTitleLoading: chat.isTitleLoading,
7531
- activeModel,
7776
+ activeModel: displayModel,
7532
7777
  activeProvider,
7533
- selectedModel,
7534
- setSelectedModel,
7778
+ selectedModel: displayModel,
7779
+ setSelectedModel: setUserSelectedModel,
7535
7780
  availableProviders,
7536
7781
  sendMessage: chat.sendMessage,
7537
7782
  startNewChat: chat.startNewChat,
7538
7783
  selectThread: chat.selectThread,
7539
7784
  deleteThread: chat.deleteThread,
7540
- handleCopy: (content) => {
7541
- navigator.clipboard.writeText(content).catch(() => {
7542
- });
7543
- },
7785
+ handleCopy,
7544
7786
  renamingId: chat.renamingId,
7545
7787
  renameValue: chat.renameValue,
7546
7788
  setRenameValue: chat.setRenameValue,
7547
- startRename: (threadId) => {
7548
- setThreadMenuOpenId(null);
7549
- chat.startRename(threadId);
7550
- },
7789
+ startRename: handleStartRename,
7551
7790
  submitRename: chat.submitRename,
7552
7791
  displaySidebarItems: chat.displaySidebarItems,
7553
7792
  threadsLoading: chat.threadsLoading,
@@ -7577,9 +7816,9 @@ function AIChat(inlineProps) {
7577
7816
  }), [
7578
7817
  chat,
7579
7818
  finalTitle,
7580
- activeModel,
7819
+ displayModel,
7581
7820
  activeProvider,
7582
- selectedModel,
7821
+ userSelectedModel,
7583
7822
  availableProviders,
7584
7823
  sidebarOpen,
7585
7824
  closeSidebar,
@@ -7616,8 +7855,8 @@ function AIChat(inlineProps) {
7616
7855
  threadMenuRef,
7617
7856
  threadMenuVisible,
7618
7857
  setThreadMenuOpenId,
7619
- activeModel,
7620
- setSelectedModel,
7858
+ activeModel: displayModel,
7859
+ setSelectedModel: setUserSelectedModel,
7621
7860
  availableProviders,
7622
7861
  startRename: chatState.startRename,
7623
7862
  deleteThread: chat.deleteThread
@@ -7633,6 +7872,19 @@ function AIChat(inlineProps) {
7633
7872
  ), /* @__PURE__ */ import_react38.default.createElement(ChatInput, null))
7634
7873
  ));
7635
7874
  }
7875
+ function CommandButton2({ cmd, chatContext }) {
7876
+ const handleClick = (0, import_react38.useCallback)(() => {
7877
+ void cmd.run(chatContext);
7878
+ }, [cmd, chatContext]);
7879
+ return /* @__PURE__ */ import_react38.default.createElement("button", { className: "brokr-ai-chat-sidebar-button", onClick: handleClick, type: "button" }, cmd.text);
7880
+ }
7881
+ function MenuCommandItem({ cmd, chatContext, onClose }) {
7882
+ const handleClick = (0, import_react38.useCallback)(() => {
7883
+ onClose();
7884
+ void cmd.run(chatContext);
7885
+ }, [cmd, chatContext, onClose]);
7886
+ return /* @__PURE__ */ import_react38.default.createElement("button", { className: "brokr-ai-chat-thread-dropdown-item", onClick: handleClick, type: "button" }, cmd.text);
7887
+ }
7636
7888
  function ChatHeader({
7637
7889
  activeId,
7638
7890
  sidebarVisible,
@@ -7656,6 +7908,16 @@ function ChatHeader({
7656
7908
  const handleToggleMenu = (0, import_react38.useCallback)(() => {
7657
7909
  setThreadMenuOpenId(threadMenuOpenId ? null : activeId);
7658
7910
  }, [setThreadMenuOpenId, threadMenuOpenId, activeId]);
7911
+ const closeMenu = (0, import_react38.useCallback)(() => setThreadMenuOpenId(null), [setThreadMenuOpenId]);
7912
+ const handleRename = (0, import_react38.useCallback)(() => {
7913
+ if (activeId) startRename(activeId);
7914
+ }, [activeId, startRename]);
7915
+ const handleDelete = (0, import_react38.useCallback)(() => {
7916
+ if (activeId) {
7917
+ setThreadMenuOpenId(null);
7918
+ void deleteThread(activeId);
7919
+ }
7920
+ }, [activeId, deleteThread, setThreadMenuOpenId]);
7659
7921
  return /* @__PURE__ */ import_react38.default.createElement("header", { className: "brokr-ai-chat-topbar" }, /* @__PURE__ */ import_react38.default.createElement("div", { className: "brokr-ai-chat-topbar-left" }, sidebarVisible ? /* @__PURE__ */ import_react38.default.createElement(
7660
7922
  "button",
7661
7923
  {
@@ -7665,16 +7927,7 @@ function ChatHeader({
7665
7927
  type: "button"
7666
7928
  },
7667
7929
  /* @__PURE__ */ import_react38.default.createElement(MenuIcon, { size: 18 })
7668
- ) : null), /* @__PURE__ */ import_react38.default.createElement("div", { className: "brokr-ai-chat-topbar-actions" }, headerCommands.map((cmd) => /* @__PURE__ */ import_react38.default.createElement(
7669
- "button",
7670
- {
7671
- className: "brokr-ai-chat-sidebar-button",
7672
- key: cmd.id,
7673
- onClick: () => void cmd.run(chatContext),
7674
- type: "button"
7675
- },
7676
- cmd.text
7677
- )), modelSelectorVisible ? /* @__PURE__ */ import_react38.default.createElement(
7930
+ ) : null), /* @__PURE__ */ import_react38.default.createElement("div", { className: "brokr-ai-chat-topbar-actions" }, headerCommands.map((cmd) => /* @__PURE__ */ import_react38.default.createElement(CommandButton2, { key: cmd.id, cmd, chatContext })), modelSelectorVisible ? /* @__PURE__ */ import_react38.default.createElement(
7678
7931
  ModelSelector,
7679
7932
  {
7680
7933
  activeModel,
@@ -7695,32 +7948,25 @@ function ChatHeader({
7695
7948
  "button",
7696
7949
  {
7697
7950
  className: "brokr-ai-chat-thread-dropdown-item",
7698
- onClick: () => startRename(activeId),
7951
+ onClick: handleRename,
7699
7952
  type: "button"
7700
7953
  },
7701
7954
  /* @__PURE__ */ import_react38.default.createElement(PencilIcon, { size: 13 }),
7702
7955
  "Rename"
7703
7956
  ), threadMenuCommands.map((cmd) => /* @__PURE__ */ import_react38.default.createElement(
7704
- "button",
7957
+ MenuCommandItem,
7705
7958
  {
7706
- className: "brokr-ai-chat-thread-dropdown-item",
7707
7959
  key: cmd.id,
7708
- onClick: () => {
7709
- setThreadMenuOpenId(null);
7710
- void cmd.run(chatContext);
7711
- },
7712
- type: "button"
7713
- },
7714
- cmd.text
7960
+ cmd,
7961
+ chatContext,
7962
+ onClose: closeMenu
7963
+ }
7715
7964
  )), /* @__PURE__ */ import_react38.default.createElement(
7716
7965
  "button",
7717
7966
  {
7718
7967
  className: "brokr-ai-chat-thread-dropdown-item",
7719
7968
  "data-tone": "danger",
7720
- onClick: () => {
7721
- setThreadMenuOpenId(null);
7722
- void deleteThread(activeId);
7723
- },
7969
+ onClick: handleDelete,
7724
7970
  type: "button"
7725
7971
  },
7726
7972
  /* @__PURE__ */ import_react38.default.createElement(TrashIcon, { size: 13 }),
@@ -7730,19 +7976,62 @@ function ChatHeader({
7730
7976
 
7731
7977
  // src/react/composites/FabAI.tsx
7732
7978
  var import_react39 = __toESM(require("react"));
7979
+
7980
+ // src/react/composites/fab-context.ts
7981
+ function buildFabSystemPrompt(appContext, brandName, existingPrompt) {
7982
+ if (appContext === false) return existingPrompt;
7983
+ const name = appContext?.name ?? brandName;
7984
+ if (!name && !appContext) return existingPrompt;
7985
+ const parts = [];
7986
+ if (name) parts.push(`You are an AI assistant embedded in ${name}.`);
7987
+ if (appContext?.description) parts.push(`This app is ${appContext.description}.`);
7988
+ if (appContext?.currentPage) parts.push(`The user is currently on: ${appContext.currentPage}.`);
7989
+ if (appContext?.facts?.length) parts.push(...appContext.facts);
7990
+ const autoPrompt = parts.join(" ");
7991
+ return existingPrompt ? `${autoPrompt}
7992
+
7993
+ ${existingPrompt}` : autoPrompt;
7994
+ }
7995
+
7996
+ // src/react/composites/FabAI.tsx
7997
+ function ensureAssistantReply(messages, content) {
7998
+ const normalized = content.trim() ? content : "No response received.";
7999
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
8000
+ if (messages[index]?.role !== "assistant") continue;
8001
+ const next = [...messages];
8002
+ next[index] = { ...next[index], content: normalized };
8003
+ return next;
8004
+ }
8005
+ return [...messages, { role: "assistant", content: normalized }];
8006
+ }
8007
+ function appendAssistantDelta(messages, delta) {
8008
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
8009
+ if (messages[index]?.role !== "assistant") continue;
8010
+ const next = [...messages];
8011
+ next[index] = {
8012
+ ...next[index],
8013
+ content: `${contentToText(next[index].content)}${delta}`
8014
+ };
8015
+ return next;
8016
+ }
8017
+ return [...messages, { role: "assistant", content: delta }];
8018
+ }
7733
8019
  function FabAI({
8020
+ appContext,
7734
8021
  model,
8022
+ memory,
7735
8023
  onSendMessage,
7736
8024
  position = "bottom-right",
7737
- starterPrompts = [],
7738
8025
  systemPrompt
7739
8026
  }) {
7740
- const { can, user } = useBrokr();
8027
+ const { can, user, theme } = useBrokr();
8028
+ const brandName = theme?.brand?.name;
7741
8029
  const [isOpen, setIsOpen] = (0, import_react39.useState)(false);
7742
8030
  const [input, setInput] = (0, import_react39.useState)("");
7743
8031
  const [error, setError] = (0, import_react39.useState)(null);
7744
8032
  const [isSending, setIsSending] = (0, import_react39.useState)(false);
7745
8033
  const [messages, setMessages] = (0, import_react39.useState)([]);
8034
+ const conversationIdRef = (0, import_react39.useRef)(`fab_${crypto.randomUUID()}`);
7746
8035
  const launcherStyle = (0, import_react39.useMemo)(
7747
8036
  () => ({
7748
8037
  left: position === "bottom-left" ? "var(--brokr-space-6)" : void 0,
@@ -7766,37 +8055,75 @@ function FabAI({
7766
8055
  }, []);
7767
8056
  const sendPrompt = (0, import_react39.useCallback)(async (prompt) => {
7768
8057
  const nextPrompt = prompt.trim();
7769
- if (!nextPrompt) return;
7770
- const nextMessages = [...messages, { role: "user", content: nextPrompt }];
8058
+ if (!nextPrompt || isSending) return;
8059
+ const userMessage = { role: "user", content: nextPrompt };
8060
+ const nextMessages = [...messages, userMessage];
8061
+ const optimisticMessages = [...nextMessages, { role: "assistant", content: "" }];
7771
8062
  try {
7772
8063
  setError(null);
7773
8064
  setIsSending(true);
7774
- setMessages(nextMessages);
8065
+ setMessages(optimisticMessages);
7775
8066
  setInput("");
7776
- let responseText = "";
7777
8067
  if (onSendMessage) {
7778
- responseText = await onSendMessage({ messages: nextMessages, model, systemPrompt });
7779
- } else {
7780
- const payload = await postJson(
7781
- "/api/brokr/chat",
7782
- {
7783
- messages: nextMessages,
7784
- model,
7785
- systemPrompt
8068
+ const responseText2 = await onSendMessage({ messages: nextMessages, model, systemPrompt });
8069
+ setMessages((current) => ensureAssistantReply(current, responseText2));
8070
+ return;
8071
+ }
8072
+ const response = await fetch("/api/brokr/chat", {
8073
+ method: "POST",
8074
+ credentials: "include",
8075
+ headers: { "Content-Type": "application/json" },
8076
+ body: JSON.stringify({
8077
+ conversationId: conversationIdRef.current,
8078
+ messages: trimToTokenBudget(
8079
+ nextMessages.filter((message) => message.role === "user" || message.role === "assistant" && Boolean(contentToText(message.content))).map((message) => ({
8080
+ role: message.role,
8081
+ content: contentToText(message.content)
8082
+ }))
8083
+ ),
8084
+ ...model !== void 0 ? { model } : {},
8085
+ ...(() => {
8086
+ const withContext = buildFabSystemPrompt(appContext, brandName, systemPrompt);
8087
+ const ep = buildEffectivePrompt(withContext, memory);
8088
+ return ep ? { systemPrompt: ep } : {};
8089
+ })()
8090
+ })
8091
+ });
8092
+ if (!response.ok) {
8093
+ const payload2 = await response.json().catch(() => ({}));
8094
+ throw new Error(payload2.message ?? payload2.error ?? `Chat failed (${response.status})`);
8095
+ }
8096
+ if (isSSEResponse(response)) {
8097
+ let hasDelta = false;
8098
+ for await (const event of parseSSEStream(response)) {
8099
+ if (event.type === "conversation") {
8100
+ conversationIdRef.current = event.id;
8101
+ } else if (event.type === "delta") {
8102
+ hasDelta = true;
8103
+ setMessages((current) => appendAssistantDelta(current, event.delta));
8104
+ } else if (event.type === "error") {
8105
+ throw new Error(event.message);
7786
8106
  }
7787
- );
7788
- responseText = payload.content ?? payload.response ?? "";
8107
+ }
8108
+ if (!hasDelta) {
8109
+ setMessages((current) => ensureAssistantReply(current, "No response received."));
8110
+ }
8111
+ return;
8112
+ }
8113
+ const payload = await response.json().catch(() => ({}));
8114
+ if (payload.error || payload.message) {
8115
+ throw new Error(payload.message ?? payload.error ?? "AI request failed.");
7789
8116
  }
7790
- setMessages((current) => [...current, {
7791
- role: "assistant",
7792
- content: responseText || "No response received."
7793
- }]);
8117
+ const responseText = payload.content ?? payload.response ?? payload.text ?? "";
8118
+ setMessages((current) => ensureAssistantReply(current, responseText));
7794
8119
  } catch (cause) {
7795
- setError(cause instanceof Error ? cause.message : "Could not send message.");
8120
+ const message = cause instanceof Error ? cause.message : "Could not send message.";
8121
+ setError(message);
8122
+ setMessages((current) => ensureAssistantReply(current, `Error: ${message}`));
7796
8123
  } finally {
7797
8124
  setIsSending(false);
7798
8125
  }
7799
- }, [messages, model, onSendMessage, systemPrompt]);
8126
+ }, [isSending, messages, model, onSendMessage, systemPrompt]);
7800
8127
  const handleSubmit = (0, import_react39.useCallback)(async (event) => {
7801
8128
  event.preventDefault();
7802
8129
  await sendPrompt(input);
@@ -7807,44 +8134,31 @@ function FabAI({
7807
8134
  void sendPrompt(input);
7808
8135
  }
7809
8136
  }, [input, sendPrompt]);
7810
- const handleStarterPrompt = (0, import_react39.useCallback)((prompt) => {
7811
- void sendPrompt(prompt);
7812
- }, [sendPrompt]);
7813
8137
  return /* @__PURE__ */ import_react39.default.createElement(import_react39.default.Fragment, null, /* @__PURE__ */ import_react39.default.createElement("div", { className: "brokr-chat-fab", style: launcherStyle }, /* @__PURE__ */ import_react39.default.createElement(
7814
8138
  "button",
7815
8139
  {
8140
+ "aria-label": isOpen ? "Close AI chat" : "Open AI chat",
7816
8141
  "aria-expanded": isOpen,
7817
8142
  "aria-haspopup": "dialog",
7818
- className: "brokr-button",
8143
+ className: "brokr-chat-fab-trigger",
7819
8144
  onClick: toggleOpen,
7820
8145
  type: "button"
7821
8146
  },
7822
- /* @__PURE__ */ import_react39.default.createElement(MessageIcon, { size: 16 }),
7823
- "Ask AI"
7824
- )), isOpen ? /* @__PURE__ */ import_react39.default.createElement("div", { className: "brokr-panel brokr-chat-panel", role: "dialog" }, /* @__PURE__ */ import_react39.default.createElement("div", { className: "brokr-brand-row", style: { justifyContent: "space-between" } }, /* @__PURE__ */ import_react39.default.createElement("div", { className: "brokr-section", style: { gap: "0.25rem" } }, /* @__PURE__ */ import_react39.default.createElement("strong", null, "AI Chat"), user?.name ? /* @__PURE__ */ import_react39.default.createElement("span", { className: "brokr-copy" }, user.name) : null), /* @__PURE__ */ import_react39.default.createElement("button", { className: "brokr-button-ghost", onClick: handleClose, type: "button" }, /* @__PURE__ */ import_react39.default.createElement(CloseIcon, { size: 16 }))), /* @__PURE__ */ import_react39.default.createElement(
8147
+ /* @__PURE__ */ import_react39.default.createElement(SparkIcon, { size: 18 })
8148
+ )), isOpen ? /* @__PURE__ */ import_react39.default.createElement("div", { className: "brokr-panel brokr-chat-panel", role: "dialog" }, /* @__PURE__ */ import_react39.default.createElement("div", { className: "brokr-brand-row", style: { justifyContent: "space-between" } }, /* @__PURE__ */ import_react39.default.createElement("div", { className: "brokr-section", style: { gap: "var(--brokr-space-1)" } }, /* @__PURE__ */ import_react39.default.createElement("strong", null, "AI Chat"), user?.name ? /* @__PURE__ */ import_react39.default.createElement("span", { className: "brokr-copy" }, user.name) : null), /* @__PURE__ */ import_react39.default.createElement("button", { className: "brokr-button-ghost", onClick: handleClose, type: "button" }, /* @__PURE__ */ import_react39.default.createElement(CloseIcon, { size: 16 }))), /* @__PURE__ */ import_react39.default.createElement(
7825
8149
  "div",
7826
8150
  {
7827
8151
  className: "brokr-chat-messages",
7828
8152
  "data-empty": messages.length === 0
7829
8153
  },
7830
- messages.length === 0 ? /* @__PURE__ */ import_react39.default.createElement("div", { className: "brokr-chat-empty" }, /* @__PURE__ */ import_react39.default.createElement(SparkIcon, { size: 18 }), /* @__PURE__ */ import_react39.default.createElement("div", { className: "brokr-section", style: { gap: "0.35rem" } }, /* @__PURE__ */ import_react39.default.createElement("strong", null, "Send a message to chat with the AI."), /* @__PURE__ */ import_react39.default.createElement("span", { className: "brokr-copy" }, "Ask a question or drop in a starter prompt below.")), starterPrompts.length > 0 ? /* @__PURE__ */ import_react39.default.createElement("div", { className: "brokr-chat-starters" }, starterPrompts.map((prompt) => {
7831
- const handleClick = () => {
7832
- handleStarterPrompt(prompt);
7833
- };
7834
- return /* @__PURE__ */ import_react39.default.createElement(
7835
- "button",
7836
- {
7837
- className: "brokr-chat-starter",
7838
- key: prompt,
7839
- onClick: handleClick,
7840
- type: "button"
7841
- },
7842
- prompt
7843
- );
7844
- })) : null) : null,
7845
- messages.map((message, index) => /* @__PURE__ */ import_react39.default.createElement("div", { className: "brokr-chat-bubble", "data-role": message.role, key: `${message.role}-${index}` }, contentToText(message.content))),
8154
+ messages.length === 0 ? /* @__PURE__ */ import_react39.default.createElement("div", { className: "brokr-chat-empty" }, /* @__PURE__ */ import_react39.default.createElement(SparkIcon, { size: 18 }), /* @__PURE__ */ import_react39.default.createElement("div", { className: "brokr-section", style: { gap: "var(--brokr-space-1)" } }, /* @__PURE__ */ import_react39.default.createElement("strong", null, "Send a message to chat with the AI."), /* @__PURE__ */ import_react39.default.createElement("span", { className: "brokr-copy" }, "Ask a question or drop in a starter prompt below."))) : null,
8155
+ messages.map((message, index) => {
8156
+ const text = contentToText(message.content);
8157
+ const isTyping = message.role === "assistant" && !text && isSending && index === messages.length - 1;
8158
+ return /* @__PURE__ */ import_react39.default.createElement("div", { className: "brokr-chat-bubble", "data-role": message.role, key: `${message.role}-${index}` }, isTyping ? /* @__PURE__ */ import_react39.default.createElement("div", { className: "brokr-ai-chat-typing", "aria-label": "AI is typing" }, /* @__PURE__ */ import_react39.default.createElement("span", null), /* @__PURE__ */ import_react39.default.createElement("span", null), /* @__PURE__ */ import_react39.default.createElement("span", null)) : message.role === "assistant" ? /* @__PURE__ */ import_react39.default.createElement(MarkdownRenderer, { content: text }) : text);
8159
+ }),
7846
8160
  error ? /* @__PURE__ */ import_react39.default.createElement("div", { className: "brokr-inline-message", "data-tone": "error" }, error) : null
7847
- ), /* @__PURE__ */ import_react39.default.createElement("form", { className: "brokr-section", onSubmit: handleSubmit, style: { gap: "0.75rem" } }, /* @__PURE__ */ import_react39.default.createElement(
8161
+ ), /* @__PURE__ */ import_react39.default.createElement("form", { className: "brokr-section", onSubmit: handleSubmit, style: { gap: "var(--brokr-space-3)" } }, /* @__PURE__ */ import_react39.default.createElement(
7848
8162
  "textarea",
7849
8163
  {
7850
8164
  className: "brokr-textarea brokr-chat-input",
@@ -7854,7 +8168,7 @@ function FabAI({
7854
8168
  rows: 2,
7855
8169
  value: input
7856
8170
  }
7857
- ), /* @__PURE__ */ import_react39.default.createElement("button", { className: "brokr-button", disabled: isSending, type: "submit" }, isSending ? "Thinking" : "Send"))) : null);
8171
+ ), /* @__PURE__ */ import_react39.default.createElement("button", { className: "brokr-button", disabled: isSending || !input.trim(), type: "submit" }, isSending ? "Thinking" : "Send"))) : null);
7858
8172
  }
7859
8173
 
7860
8174
  // src/react/composites/SmartUpload.tsx
@@ -7937,7 +8251,7 @@ function SmartUpload({
7937
8251
  const handleBrowse = (0, import_react40.useCallback)(() => {
7938
8252
  inputRef.current?.click();
7939
8253
  }, []);
7940
- return /* @__PURE__ */ import_react40.default.createElement("div", { className: "brokr-card brokr-upload-shell" }, /* @__PURE__ */ import_react40.default.createElement("div", { className: "brokr-brand-row", style: { justifyContent: "space-between" } }, /* @__PURE__ */ import_react40.default.createElement("div", { className: "brokr-section", style: { gap: "0.35rem" } }, /* @__PURE__ */ import_react40.default.createElement("strong", null, "Upload files"), /* @__PURE__ */ import_react40.default.createElement("span", { className: "brokr-copy" }, "Drop a file and let Brokr handle the boring part.")), paymentsMode === "sandbox" ? /* @__PURE__ */ import_react40.default.createElement("span", { className: "brokr-badge brokr-badge-sandbox" }, "Sandbox") : null), /* @__PURE__ */ import_react40.default.createElement(
8254
+ return /* @__PURE__ */ import_react40.default.createElement("div", { className: "brokr-card brokr-upload-shell" }, /* @__PURE__ */ import_react40.default.createElement("div", { className: "brokr-brand-row", style: { justifyContent: "space-between" } }, /* @__PURE__ */ import_react40.default.createElement("div", { className: "brokr-section", style: { gap: "var(--brokr-space-1)" } }, /* @__PURE__ */ import_react40.default.createElement("strong", null, "Upload files"), /* @__PURE__ */ import_react40.default.createElement("span", { className: "brokr-copy" }, "Drop a file and let Brokr handle the boring part.")), paymentsMode === "sandbox" ? /* @__PURE__ */ import_react40.default.createElement("span", { className: "brokr-badge brokr-badge-sandbox" }, "Sandbox") : null), /* @__PURE__ */ import_react40.default.createElement(
7941
8255
  "label",
7942
8256
  {
7943
8257
  className: "brokr-upload-dropzone",
@@ -7962,7 +8276,7 @@ function SmartUpload({
7962
8276
  ref: inputRef,
7963
8277
  type: "file"
7964
8278
  }
7965
- ), fileName ? /* @__PURE__ */ import_react40.default.createElement("div", { className: "brokr-card brokr-upload-file" }, /* @__PURE__ */ import_react40.default.createElement("div", { className: "brokr-section", style: { gap: "0.35rem" } }, /* @__PURE__ */ import_react40.default.createElement("strong", null, fileName), /* @__PURE__ */ import_react40.default.createElement("span", { className: "brokr-copy" }, isUploading ? `Uploading ${progress}%` : progress === 100 ? "Processed" : "Queued")), /* @__PURE__ */ import_react40.default.createElement("div", { className: "brokr-meter-bar", style: { width: "7rem" } }, /* @__PURE__ */ import_react40.default.createElement("div", { className: "brokr-meter-fill", style: { width: `${progress}%` } }))) : null, error ? /* @__PURE__ */ import_react40.default.createElement("div", { className: "brokr-inline-message", "data-tone": "error" }, error) : null);
8279
+ ), fileName ? /* @__PURE__ */ import_react40.default.createElement("div", { className: "brokr-card brokr-upload-file" }, /* @__PURE__ */ import_react40.default.createElement("div", { className: "brokr-section", style: { gap: "var(--brokr-space-1)" } }, /* @__PURE__ */ import_react40.default.createElement("strong", null, fileName), /* @__PURE__ */ import_react40.default.createElement("span", { className: "brokr-copy" }, isUploading ? `Uploading ${progress}%` : progress === 100 ? "Processed" : "Queued")), /* @__PURE__ */ import_react40.default.createElement("div", { className: "brokr-meter-bar" }, /* @__PURE__ */ import_react40.default.createElement("div", { className: "brokr-meter-fill", style: { width: `${progress}%` } }))) : null, error ? /* @__PURE__ */ import_react40.default.createElement("div", { className: "brokr-inline-message", "data-tone": "error" }, error) : null);
7966
8280
  }
7967
8281
 
7968
8282
  // src/react/composites/FeedbackWidget.tsx
@@ -8327,6 +8641,28 @@ function timeAgo(iso) {
8327
8641
  const days = Math.floor(hours / 24);
8328
8642
  return `${days}d ago`;
8329
8643
  }
8644
+ function NotifDropdownItem({
8645
+ notif,
8646
+ registry,
8647
+ onClick
8648
+ }) {
8649
+ const handleClick = (0, import_react44.useCallback)(() => onClick(notif), [notif, onClick]);
8650
+ const notifData = notif.data ?? {};
8651
+ const notifType = notifData.type ?? "default";
8652
+ const resolved = resolveNotificationType(registry, notifType, notifData);
8653
+ return /* @__PURE__ */ import_react44.default.createElement(
8654
+ "button",
8655
+ {
8656
+ type: "button",
8657
+ className: `brokr-notif-item${notif.read ? "" : " brokr-notif-item--unread"}`,
8658
+ onClick: handleClick,
8659
+ role: "menuitem"
8660
+ },
8661
+ resolved.image ? /* @__PURE__ */ import_react44.default.createElement("img", { src: resolved.image.url, alt: resolved.image.alt, className: "brokr-notif-item-logo" }) : /* @__PURE__ */ import_react44.default.createElement("span", { className: `brokr-notif-item-dot brokr-notif-item-dot--${notif.variant}` }),
8662
+ /* @__PURE__ */ import_react44.default.createElement("div", { className: "brokr-notif-item-body" }, /* @__PURE__ */ import_react44.default.createElement("span", { className: "brokr-notif-item-title" }, notif.title), /* @__PURE__ */ import_react44.default.createElement("span", { className: "brokr-notif-item-message" }, notif.message)),
8663
+ /* @__PURE__ */ import_react44.default.createElement("span", { className: "brokr-notif-item-time" }, timeAgo(notif.createdAt))
8664
+ );
8665
+ }
8330
8666
  function NotificationBell() {
8331
8667
  const { notifications, unreadCount, markRead, markAllRead, isLoading, registry } = useNotifications();
8332
8668
  const [open, setOpen] = (0, import_react44.useState)(false);
@@ -8391,7 +8727,8 @@ function NotificationBell() {
8391
8727
  className: "brokr-notif-bell",
8392
8728
  onClick: toggle,
8393
8729
  "aria-label": `Notifications${unreadCount > 0 ? ` (${unreadCount} unread)` : ""}`,
8394
- "aria-expanded": open
8730
+ "aria-expanded": open,
8731
+ "aria-haspopup": "menu"
8395
8732
  },
8396
8733
  /* @__PURE__ */ import_react44.default.createElement("svg", { "aria-hidden": "true", width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react44.default.createElement("path", { d: "M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9" }), /* @__PURE__ */ import_react44.default.createElement("path", { d: "M10.3 21a1.94 1.94 0 0 0 3.4 0" })),
8397
8734
  unreadCount > 0 && /* @__PURE__ */ import_react44.default.createElement("span", { className: "brokr-notif-badge" }, unreadCount > 99 ? "99+" : unreadCount)
@@ -8403,31 +8740,15 @@ function NotificationBell() {
8403
8740
  onClick: markAllRead
8404
8741
  },
8405
8742
  "Mark all read"
8406
- )), /* @__PURE__ */ import_react44.default.createElement("div", { className: "brokr-notif-dropdown-list" }, isLoading ? /* @__PURE__ */ import_react44.default.createElement("div", { className: "brokr-notif-empty" }, /* @__PURE__ */ import_react44.default.createElement("span", { className: "brokr-notif-empty-text" }, "Loading\u2026")) : sorted.length === 0 ? /* @__PURE__ */ import_react44.default.createElement("div", { className: "brokr-notif-empty" }, /* @__PURE__ */ import_react44.default.createElement("span", { className: "brokr-notif-empty-text" }, "No notifications yet")) : sorted.map((notif) => {
8407
- const notifData = notif.data ?? {};
8408
- const notifType = notifData.type ?? "default";
8409
- const resolved = resolveNotificationType(registry, notifType, notifData);
8410
- return /* @__PURE__ */ import_react44.default.createElement(
8411
- "button",
8412
- {
8413
- key: notif.id,
8414
- type: "button",
8415
- className: `brokr-notif-item${notif.read ? "" : " brokr-notif-item--unread"}`,
8416
- onClick: () => handleItemClick(notif),
8417
- role: "menuitem"
8418
- },
8419
- resolved.image ? /* @__PURE__ */ import_react44.default.createElement(
8420
- "img",
8421
- {
8422
- src: resolved.image.url,
8423
- alt: resolved.image.alt,
8424
- className: "brokr-notif-item-logo"
8425
- }
8426
- ) : /* @__PURE__ */ import_react44.default.createElement("span", { className: `brokr-notif-item-dot brokr-notif-item-dot--${notif.variant}` }),
8427
- /* @__PURE__ */ import_react44.default.createElement("div", { className: "brokr-notif-item-body" }, /* @__PURE__ */ import_react44.default.createElement("span", { className: "brokr-notif-item-title" }, notif.title), /* @__PURE__ */ import_react44.default.createElement("span", { className: "brokr-notif-item-message" }, notif.message)),
8428
- /* @__PURE__ */ import_react44.default.createElement("span", { className: "brokr-notif-item-time" }, timeAgo(notif.createdAt))
8429
- );
8430
- }))));
8743
+ )), /* @__PURE__ */ import_react44.default.createElement("div", { className: "brokr-notif-dropdown-list" }, isLoading ? /* @__PURE__ */ import_react44.default.createElement("div", { className: "brokr-notif-empty" }, /* @__PURE__ */ import_react44.default.createElement("span", { className: "brokr-notif-empty-text" }, "Loading\u2026")) : sorted.length === 0 ? /* @__PURE__ */ import_react44.default.createElement("div", { className: "brokr-notif-empty" }, /* @__PURE__ */ import_react44.default.createElement("span", { className: "brokr-notif-empty-text" }, "No notifications yet")) : sorted.map((notif) => /* @__PURE__ */ import_react44.default.createElement(
8744
+ NotifDropdownItem,
8745
+ {
8746
+ key: notif.id,
8747
+ notif,
8748
+ registry,
8749
+ onClick: handleItemClick
8750
+ }
8751
+ )))));
8431
8752
  }
8432
8753
 
8433
8754
  // src/react/notifications/NotificationList.tsx
@@ -8444,6 +8765,27 @@ function formatTimestamp(iso) {
8444
8765
  function NotificationListSkeleton() {
8445
8766
  return /* @__PURE__ */ import_react45.default.createElement("div", { className: "brokr-notif-list-items" }, [1, 2, 3].map((i) => /* @__PURE__ */ import_react45.default.createElement("div", { key: i, className: "brokr-notif-list-row brokr-notif-list-row--skeleton" }, /* @__PURE__ */ import_react45.default.createElement("span", { className: "brokr-notif-item-dot brokr-notif-item-dot--skeleton" }), /* @__PURE__ */ import_react45.default.createElement("div", { className: "brokr-notif-item-body" }, /* @__PURE__ */ import_react45.default.createElement("span", { className: "brokr-notif-item-title brokr-skeleton-line", style: { width: "60%" } }), /* @__PURE__ */ import_react45.default.createElement("span", { className: "brokr-notif-item-message brokr-skeleton-line", style: { width: "80%" } })), /* @__PURE__ */ import_react45.default.createElement("span", { className: "brokr-notif-item-time brokr-skeleton-line", style: { width: 48 } }))));
8446
8767
  }
8768
+ function NotifListItem({
8769
+ notif,
8770
+ registry,
8771
+ onClick
8772
+ }) {
8773
+ const handleClick = (0, import_react45.useCallback)(() => onClick(notif), [notif, onClick]);
8774
+ const notifData = notif.data ?? {};
8775
+ const notifType = notif.type ?? notifData.type ?? "default";
8776
+ const resolved = resolveNotificationType(registry, notifType, notifData);
8777
+ return /* @__PURE__ */ import_react45.default.createElement(
8778
+ "button",
8779
+ {
8780
+ type: "button",
8781
+ className: `brokr-notif-list-row${notif.read ? "" : " brokr-notif-list-row--unread"}`,
8782
+ onClick: handleClick
8783
+ },
8784
+ resolved.image ? /* @__PURE__ */ import_react45.default.createElement("img", { src: resolved.image.url, alt: resolved.image.alt, className: "brokr-notif-item-logo" }) : /* @__PURE__ */ import_react45.default.createElement("span", { className: `brokr-notif-item-dot brokr-notif-item-dot--${notif.variant}` }),
8785
+ /* @__PURE__ */ import_react45.default.createElement("div", { className: "brokr-notif-item-body" }, /* @__PURE__ */ import_react45.default.createElement("span", { className: "brokr-notif-item-title" }, notif.title), /* @__PURE__ */ import_react45.default.createElement("span", { className: "brokr-notif-item-message" }, notif.message)),
8786
+ /* @__PURE__ */ import_react45.default.createElement("span", { className: "brokr-notif-item-time" }, formatTimestamp(notif.createdAt))
8787
+ );
8788
+ }
8447
8789
  function NotificationList() {
8448
8790
  const { notifications, unreadCount, markRead, markAllRead, isLoading, registry } = useNotifications();
8449
8791
  const sorted = (0, import_react45.useMemo)(
@@ -8470,30 +8812,15 @@ function NotificationList() {
8470
8812
  onClick: markAllRead
8471
8813
  },
8472
8814
  "Mark all read"
8473
- )), isLoading ? /* @__PURE__ */ import_react45.default.createElement(NotificationListSkeleton, null) : sorted.length === 0 ? /* @__PURE__ */ import_react45.default.createElement("div", { className: "brokr-notif-empty" }, /* @__PURE__ */ import_react45.default.createElement("svg", { "aria-hidden": "true", width: "40", height: "40", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1", strokeLinecap: "round", strokeLinejoin: "round", style: { opacity: 0.3 } }, /* @__PURE__ */ import_react45.default.createElement("path", { d: "M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9" }), /* @__PURE__ */ import_react45.default.createElement("path", { d: "M10.3 21a1.94 1.94 0 0 0 3.4 0" })), /* @__PURE__ */ import_react45.default.createElement("span", { className: "brokr-notif-empty-text" }, "No notifications yet")) : /* @__PURE__ */ import_react45.default.createElement("div", { className: "brokr-notif-list-items" }, sorted.map((notif) => {
8474
- const notifData = notif.data ?? {};
8475
- const notifType = notif.type ?? notifData.type ?? "default";
8476
- const resolved = resolveNotificationType(registry, notifType, notifData);
8477
- return /* @__PURE__ */ import_react45.default.createElement(
8478
- "button",
8479
- {
8480
- key: notif.id,
8481
- type: "button",
8482
- className: `brokr-notif-list-row${notif.read ? "" : " brokr-notif-list-row--unread"}`,
8483
- onClick: () => handleClick(notif)
8484
- },
8485
- resolved.image ? /* @__PURE__ */ import_react45.default.createElement(
8486
- "img",
8487
- {
8488
- src: resolved.image.url,
8489
- alt: resolved.image.alt,
8490
- className: "brokr-notif-item-logo"
8491
- }
8492
- ) : /* @__PURE__ */ import_react45.default.createElement("span", { className: `brokr-notif-item-dot brokr-notif-item-dot--${notif.variant}` }),
8493
- /* @__PURE__ */ import_react45.default.createElement("div", { className: "brokr-notif-item-body" }, /* @__PURE__ */ import_react45.default.createElement("span", { className: "brokr-notif-item-title" }, notif.title), /* @__PURE__ */ import_react45.default.createElement("span", { className: "brokr-notif-item-message" }, notif.message)),
8494
- /* @__PURE__ */ import_react45.default.createElement("span", { className: "brokr-notif-item-time" }, formatTimestamp(notif.createdAt))
8495
- );
8496
- })));
8815
+ )), isLoading ? /* @__PURE__ */ import_react45.default.createElement(NotificationListSkeleton, null) : sorted.length === 0 ? /* @__PURE__ */ import_react45.default.createElement("div", { className: "brokr-notif-empty" }, /* @__PURE__ */ import_react45.default.createElement("svg", { "aria-hidden": "true", width: "40", height: "40", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1", strokeLinecap: "round", strokeLinejoin: "round", style: { opacity: 0.3 } }, /* @__PURE__ */ import_react45.default.createElement("path", { d: "M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9" }), /* @__PURE__ */ import_react45.default.createElement("path", { d: "M10.3 21a1.94 1.94 0 0 0 3.4 0" })), /* @__PURE__ */ import_react45.default.createElement("span", { className: "brokr-notif-empty-text" }, "No notifications yet")) : /* @__PURE__ */ import_react45.default.createElement("div", { className: "brokr-notif-list-items" }, sorted.map((notif) => /* @__PURE__ */ import_react45.default.createElement(
8816
+ NotifListItem,
8817
+ {
8818
+ key: notif.id,
8819
+ notif,
8820
+ registry,
8821
+ onClick: handleClick
8822
+ }
8823
+ ))));
8497
8824
  }
8498
8825
 
8499
8826
  // src/react/hooks/use-user.ts