@brokr/sdk 2.1.0 → 2.1.2

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-styles.js +540 -95
  8. package/dist/react-styles.mjs +540 -95
  9. package/dist/react.js +665 -331
  10. package/dist/react.mjs +548 -217
  11. package/dist/runtime.js +22 -2
  12. package/dist/runtime.mjs +22 -2
  13. package/dist/src/chat/config.d.ts +2 -0
  14. package/dist/src/chat/config.d.ts.map +1 -1
  15. package/dist/src/gateway.d.ts.map +1 -1
  16. package/dist/src/models.d.ts +2 -0
  17. package/dist/src/models.d.ts.map +1 -1
  18. package/dist/src/react/chat/AIChat.d.ts.map +1 -1
  19. package/dist/src/react/chat/ChatContext.d.ts +3 -6
  20. package/dist/src/react/chat/ChatContext.d.ts.map +1 -1
  21. package/dist/src/react/chat/MarkdownRenderer.d.ts.map +1 -1
  22. package/dist/src/react/chat/ModelSelector.d.ts +1 -1
  23. package/dist/src/react/chat/ModelSelector.d.ts.map +1 -1
  24. package/dist/src/react/chat/ThreadSidebar.d.ts.map +1 -1
  25. package/dist/src/react/chat/memory.d.ts +12 -0
  26. package/dist/src/react/chat/memory.d.ts.map +1 -0
  27. package/dist/src/react/chat/types.d.ts +6 -0
  28. package/dist/src/react/chat/types.d.ts.map +1 -1
  29. package/dist/src/react/chat/useChat.d.ts +8 -6
  30. package/dist/src/react/chat/useChat.d.ts.map +1 -1
  31. package/dist/src/react/composites/FabAI.d.ts +6 -1
  32. package/dist/src/react/composites/FabAI.d.ts.map +1 -1
  33. package/dist/src/react/composites/fab-context.d.ts +26 -0
  34. package/dist/src/react/composites/fab-context.d.ts.map +1 -0
  35. package/dist/src/react/css/account.d.ts +1 -1
  36. package/dist/src/react/css/account.d.ts.map +1 -1
  37. package/dist/src/react/css/activity-feed.d.ts +2 -0
  38. package/dist/src/react/css/activity-feed.d.ts.map +1 -0
  39. package/dist/src/react/css/auth.d.ts +1 -1
  40. package/dist/src/react/css/auth.d.ts.map +1 -1
  41. package/dist/src/react/css/chat-extras.d.ts +1 -1
  42. package/dist/src/react/css/chat-extras.d.ts.map +1 -1
  43. package/dist/src/react/css/chat.d.ts +1 -1
  44. package/dist/src/react/css/chat.d.ts.map +1 -1
  45. package/dist/src/react/css/composites.d.ts +1 -1
  46. package/dist/src/react/css/composites.d.ts.map +1 -1
  47. package/dist/src/react/css/data-table.d.ts +2 -0
  48. package/dist/src/react/css/data-table.d.ts.map +1 -0
  49. package/dist/src/react/css/gates.d.ts +1 -1
  50. package/dist/src/react/css/gates.d.ts.map +1 -1
  51. package/dist/src/react/css/index.d.ts.map +1 -1
  52. package/dist/src/react/css/markdown.d.ts +1 -1
  53. package/dist/src/react/css/markdown.d.ts.map +1 -1
  54. package/dist/src/react/css/primitives.d.ts +1 -1
  55. package/dist/src/react/css/primitives.d.ts.map +1 -1
  56. package/dist/src/react/css/reset.d.ts +1 -1
  57. package/dist/src/react/css/reset.d.ts.map +1 -1
  58. package/dist/src/react/css/responsive.d.ts +1 -1
  59. package/dist/src/react/css/responsive.d.ts.map +1 -1
  60. package/dist/src/react/css/skeleton.d.ts +1 -1
  61. package/dist/src/react/css/skeleton.d.ts.map +1 -1
  62. package/dist/src/react/css/stats-grid.d.ts +2 -0
  63. package/dist/src/react/css/stats-grid.d.ts.map +1 -0
  64. package/dist/src/react/css/usage-grid.d.ts +2 -0
  65. package/dist/src/react/css/usage-grid.d.ts.map +1 -0
  66. package/dist/src/react/index.d.ts +4 -1
  67. package/dist/src/react/index.d.ts.map +1 -1
  68. package/dist/src/react/notifications/ActivityFeed.d.ts +14 -0
  69. package/dist/src/react/notifications/ActivityFeed.d.ts.map +1 -0
  70. package/dist/src/react/payments/UsageGrid.d.ts +11 -0
  71. package/dist/src/react/payments/UsageGrid.d.ts.map +1 -0
  72. package/dist/src/react/primitives/DataTable.d.ts +28 -0
  73. package/dist/src/react/primitives/DataTable.d.ts.map +1 -0
  74. package/dist/src/react/primitives/StatsGrid.d.ts +18 -0
  75. package/dist/src/react/primitives/StatsGrid.d.ts.map +1 -0
  76. package/dist/tsconfig.tsbuildinfo +1 -1
  77. package/package.json +1 -1
package/dist/react.js CHANGED
@@ -34,6 +34,7 @@ var index_exports = {};
34
34
  __export(index_exports, {
35
35
  AIChat: () => AIChat,
36
36
  AccountPanel: () => AccountPanel,
37
+ ActivityFeed: () => ActivityFeed,
37
38
  AuthPageShell: () => AuthPageShell,
38
39
  AuthWall: () => AuthWall,
39
40
  AutoReloadToggle: () => AutoReloadToggle,
@@ -63,8 +64,10 @@ __export(index_exports, {
63
64
  UpdateBilling: () => UpdateBilling,
64
65
  UpgradePrompt: () => UpgradePrompt,
65
66
  UsageGate: () => UsageGate,
67
+ UsageGrid: () => UsageGrid,
66
68
  UserButton: () => UserButton,
67
69
  defineAccount: () => defineAccount,
70
+ defineBrokrTheme: () => defineBrokrTheme,
68
71
  defineChat: () => defineChat,
69
72
  useBrokr: () => useBrokr,
70
73
  useBrokrTheme: () => useBrokrTheme,
@@ -4254,6 +4257,7 @@ function getBrokrRootStyle(theme) {
4254
4257
  var import_react3 = __toESM(require("react"));
4255
4258
 
4256
4259
  // src/models.ts
4260
+ var STACK_DEFAULT = "__stack_default__";
4257
4261
  var providers = [
4258
4262
  { id: "deepseek", label: "Deepseek", model: "deepseek-chat", color: "#0EA5E9", free: true, logo: "https://assets.brokr.sh/sdk/deepseek_logo.png" },
4259
4263
  { id: "openai", label: "ChatGPT", model: "gpt-5.4-mini", color: "#10B981", free: false, logo: "https://assets.brokr.sh/sdk/gpt_logo.png" },
@@ -6091,7 +6095,7 @@ function PlanCard({
6091
6095
  const handleClick = (0, import_react22.useCallback)(() => {
6092
6096
  void onSelect(plan.slug);
6093
6097
  }, [plan.slug, onSelect]);
6094
- 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(
6098
+ 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(
6095
6099
  "button",
6096
6100
  {
6097
6101
  className: plan.isCurrent ? "brokr-button-secondary" : "brokr-button",
@@ -6100,7 +6104,7 @@ function PlanCard({
6100
6104
  type: "button"
6101
6105
  },
6102
6106
  plan.isCurrent ? "Current plan" : isPending ? "Redirecting" : "Choose plan"
6103
- ), /* @__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))))));
6107
+ ));
6104
6108
  }
6105
6109
  function Plans({
6106
6110
  columns,
@@ -6327,15 +6331,32 @@ function CancelSubscription({ onCancel }) {
6327
6331
  return /* @__PURE__ */ import_react29.default.createElement("div", { className: "brokr-section", style: { gap: "0.75rem" } }, confirming ? /* @__PURE__ */ import_react29.default.createElement("div", { className: "brokr-inline-message" }, "Subscription changes are confirmed in billing. Continue?") : null, /* @__PURE__ */ import_react29.default.createElement("div", { className: "brokr-brand-row" }, /* @__PURE__ */ import_react29.default.createElement("button", { className: "brokr-button-ghost", disabled: isPending, onClick: handlePrimaryClick, type: "button" }, isPending ? "Opening billing" : confirming ? "Continue" : "Cancel subscription"), confirming ? /* @__PURE__ */ import_react29.default.createElement("button", { className: "brokr-button-secondary", onClick: handleDismiss, type: "button" }, "Keep subscription") : null));
6328
6332
  }
6329
6333
 
6334
+ // src/react/payments/UsageGrid.tsx
6335
+ var import_react30 = __toESM(require("react"));
6336
+ function UsageGrid() {
6337
+ const { entitlements, isLoaded } = useBrokr();
6338
+ const meteredFeatures = (0, import_react30.useMemo)(() => {
6339
+ if (!entitlements?.features) return [];
6340
+ return Object.entries(entitlements.features).filter(([_, check]) => check.limitType === "numeric").map(([slug]) => slug);
6341
+ }, [entitlements]);
6342
+ if (!isLoaded) {
6343
+ return /* @__PURE__ */ import_react30.default.createElement("div", { className: "brokr-usage-grid" }, /* @__PURE__ */ import_react30.default.createElement(Skeleton, { height: 72 }), /* @__PURE__ */ import_react30.default.createElement(Skeleton, { height: 72 }));
6344
+ }
6345
+ if (meteredFeatures.length === 0) {
6346
+ return null;
6347
+ }
6348
+ return /* @__PURE__ */ import_react30.default.createElement("div", { className: "brokr-usage-grid" }, meteredFeatures.map((slug) => /* @__PURE__ */ import_react30.default.createElement(FeatureMeter, { key: slug, feature: slug })));
6349
+ }
6350
+
6330
6351
  // src/react/chat/AIChat.tsx
6331
- var import_react38 = __toESM(require("react"));
6352
+ var import_react39 = __toESM(require("react"));
6332
6353
 
6333
6354
  // src/react/chat/ChatContext.tsx
6334
- var import_react30 = require("react");
6335
- var InternalChatContext = (0, import_react30.createContext)(null);
6355
+ var import_react31 = require("react");
6356
+ var InternalChatContext = (0, import_react31.createContext)(null);
6336
6357
  var ChatProvider = InternalChatContext.Provider;
6337
6358
  function useChatState() {
6338
- const ctx = (0, import_react30.useContext)(InternalChatContext);
6359
+ const ctx = (0, import_react31.useContext)(InternalChatContext);
6339
6360
  if (!ctx) {
6340
6361
  throw new Error("useChatState must be used within <AIChat>");
6341
6362
  }
@@ -6343,7 +6364,7 @@ function useChatState() {
6343
6364
  }
6344
6365
 
6345
6366
  // src/react/chat/useChat.ts
6346
- var import_react31 = require("react");
6367
+ var import_react32 = require("react");
6347
6368
 
6348
6369
  // src/ai/types.ts
6349
6370
  function contentToText(content) {
@@ -6448,6 +6469,39 @@ function isSSEResponse(response) {
6448
6469
  return ct.includes("text/event-stream") && response.body !== null;
6449
6470
  }
6450
6471
 
6472
+ // src/react/chat/memory.ts
6473
+ var MAX_MEMORY_CHARS = 2e3;
6474
+ function serializeMemory(obj, prefix = "") {
6475
+ const lines = [];
6476
+ for (const [key, value] of Object.entries(obj)) {
6477
+ if (value === null || value === void 0) continue;
6478
+ const label = prefix ? `${prefix}.${key}` : key;
6479
+ if (Array.isArray(value)) {
6480
+ lines.push(`${label}: ${value.join(", ")}`);
6481
+ } else if (typeof value === "object") {
6482
+ lines.push(...serializeMemory(value, label));
6483
+ } else {
6484
+ lines.push(`${label}: ${String(value)}`);
6485
+ }
6486
+ }
6487
+ return lines;
6488
+ }
6489
+ function buildEffectivePrompt(prompt, memory) {
6490
+ if (!memory || Object.keys(memory).length === 0) return prompt;
6491
+ let block = serializeMemory(memory).join("\n");
6492
+ if (block.length > MAX_MEMORY_CHARS) {
6493
+ block = block.slice(0, MAX_MEMORY_CHARS);
6494
+ if (typeof console !== "undefined") {
6495
+ console.warn("[brokr] memory exceeded 2000 char limit, truncated");
6496
+ }
6497
+ }
6498
+ const memSection = `[User context]
6499
+ ${block}`;
6500
+ return prompt ? `${memSection}
6501
+
6502
+ ${prompt}` : memSection;
6503
+ }
6504
+
6451
6505
  // src/react/chat/useChat.ts
6452
6506
  function makeId(prefix) {
6453
6507
  return `${prefix}_${crypto.randomUUID()}`;
@@ -6465,7 +6519,9 @@ function useChat(config) {
6465
6519
  const {
6466
6520
  endpoint,
6467
6521
  prompt,
6468
- model: activeModel,
6522
+ explicitModel,
6523
+ displayModel: activeModel,
6524
+ memory,
6469
6525
  persist,
6470
6526
  surface,
6471
6527
  subject,
@@ -6483,48 +6539,64 @@ function useChat(config) {
6483
6539
  userId
6484
6540
  } = config;
6485
6541
  const isPersist = persist && !isControlled;
6486
- const [memConversations, setMemConversations] = (0, import_react31.useState)([]);
6487
- const [memActiveId, setMemActiveId] = (0, import_react31.useState)(null);
6488
- const [serverThreads, setServerThreads] = (0, import_react31.useState)([]);
6489
- const [serverActiveId, setServerActiveId] = (0, import_react31.useState)(null);
6490
- const [serverMessages, setServerMessages] = (0, import_react31.useState)([]);
6491
- const [input, setInput] = (0, import_react31.useState)("");
6492
- const [isSubmitting, setIsSubmitting] = (0, import_react31.useState)(false);
6493
- const [error, setError] = (0, import_react31.useState)(null);
6494
- const [threadsLoading, setThreadsLoading] = (0, import_react31.useState)(false);
6495
- const [renamingId, setRenamingId] = (0, import_react31.useState)(null);
6496
- const [renameValue, setRenameValue] = (0, import_react31.useState)("");
6497
- const [hasMoreMessages, setHasMoreMessages] = (0, import_react31.useState)(false);
6498
- const [loadingOlder, setLoadingOlder] = (0, import_react31.useState)(false);
6499
- const textareaRef = (0, import_react31.useRef)(null);
6500
- const bottomRef = (0, import_react31.useRef)(null);
6501
- const scrollContainerRef = (0, import_react31.useRef)(null);
6502
- const sentinelRef = (0, import_react31.useRef)(null);
6503
- const displaySidebarItems = (0, import_react31.useMemo)(() => {
6504
- if (isControlled) return threadsProp?.map((t) => ({ id: t.id, title: t.title })) ?? [];
6505
- if (isPersist) return serverThreads.map((t) => ({ id: t.id, title: t.title }));
6506
- return memConversations.map((c) => ({ id: c.id, title: c.title || "New chat" }));
6542
+ const [memConversations, setMemConversations] = (0, import_react32.useState)([]);
6543
+ const [memActiveId, setMemActiveId] = (0, import_react32.useState)(null);
6544
+ const [serverThreads, setServerThreads] = (0, import_react32.useState)([]);
6545
+ const [serverActiveId, setServerActiveId] = (0, import_react32.useState)(null);
6546
+ const [serverMessages, setServerMessages] = (0, import_react32.useState)([]);
6547
+ const [input, setInput] = (0, import_react32.useState)("");
6548
+ const [isSubmitting, setIsSubmitting] = (0, import_react32.useState)(false);
6549
+ const [error, setError] = (0, import_react32.useState)(null);
6550
+ const [threadsLoading, setThreadsLoading] = (0, import_react32.useState)(false);
6551
+ const [renamingId, setRenamingId] = (0, import_react32.useState)(null);
6552
+ const [renameValue, setRenameValue] = (0, import_react32.useState)("");
6553
+ const [hasMoreMessages, setHasMoreMessages] = (0, import_react32.useState)(false);
6554
+ const [loadingOlder, setLoadingOlder] = (0, import_react32.useState)(false);
6555
+ const textareaRef = (0, import_react32.useRef)(null);
6556
+ const bottomRef = (0, import_react32.useRef)(null);
6557
+ const scrollContainerRef = (0, import_react32.useRef)(null);
6558
+ const sentinelRef = (0, import_react32.useRef)(null);
6559
+ const displaySidebarItems = (0, import_react32.useMemo)(() => {
6560
+ if (isControlled) {
6561
+ return threadsProp?.map((t) => ({
6562
+ id: t.id,
6563
+ title: t.title,
6564
+ updatedAt: t.updatedAt ?? null
6565
+ })) ?? [];
6566
+ }
6567
+ if (isPersist) {
6568
+ return serverThreads.map((t) => ({
6569
+ id: t.id,
6570
+ title: t.title,
6571
+ updatedAt: t.updatedAt ?? null
6572
+ }));
6573
+ }
6574
+ return memConversations.map((c) => ({
6575
+ id: c.id,
6576
+ title: c.title || "New chat",
6577
+ updatedAt: new Date(c.updatedAt).toISOString()
6578
+ }));
6507
6579
  }, [isControlled, isPersist, threadsProp, serverThreads, memConversations]);
6508
- const activeId = (0, import_react31.useMemo)(() => {
6580
+ const activeId = (0, import_react32.useMemo)(() => {
6509
6581
  if (isControlled) return activeThreadIdProp ?? null;
6510
6582
  if (isPersist) return serverActiveId;
6511
6583
  return memActiveId;
6512
6584
  }, [isControlled, isPersist, activeThreadIdProp, serverActiveId, memActiveId]);
6513
- const displayMessages = (0, import_react31.useMemo)(() => {
6585
+ const displayMessages = (0, import_react32.useMemo)(() => {
6514
6586
  if (isControlled) return messagesProp ?? [];
6515
6587
  if (isPersist) return serverMessages;
6516
6588
  return memConversations.find((c) => c.id === memActiveId)?.messages ?? [];
6517
6589
  }, [isControlled, isPersist, messagesProp, serverMessages, memConversations, memActiveId]);
6518
- const activeMemConv = (0, import_react31.useMemo)(
6590
+ const activeMemConv = (0, import_react32.useMemo)(
6519
6591
  () => memConversations.find((c) => c.id === memActiveId) ?? null,
6520
6592
  [memConversations, memActiveId]
6521
6593
  );
6522
- const activeThread = (0, import_react31.useMemo)(() => {
6594
+ const activeThread = (0, import_react32.useMemo)(() => {
6523
6595
  if (isControlled) return threadsProp?.find((t) => t.id === activeId) ?? null;
6524
6596
  if (isPersist) return serverThreads.find((t) => t.id === activeId) ?? null;
6525
6597
  return null;
6526
6598
  }, [isControlled, isPersist, threadsProp, serverThreads, activeId]);
6527
- const renderedTitle = (0, import_react31.useMemo)(() => {
6599
+ const renderedTitle = (0, import_react32.useMemo)(() => {
6528
6600
  if (isPersist && activeThread) return activeThread.title;
6529
6601
  if (!isPersist && activeMemConv) {
6530
6602
  if (activeMemConv.titleState === "loading") return "Naming conversation";
@@ -6533,10 +6605,10 @@ function useChat(config) {
6533
6605
  return "New chat";
6534
6606
  }, [isPersist, activeThread, activeMemConv]);
6535
6607
  const isTitleLoading = !isPersist && activeMemConv?.titleState === "loading";
6536
- (0, import_react31.useEffect)(() => {
6608
+ (0, import_react32.useEffect)(() => {
6537
6609
  bottomRef.current?.scrollIntoView({ behavior: "smooth" });
6538
6610
  }, [displayMessages, activeId]);
6539
- (0, import_react31.useEffect)(() => {
6611
+ (0, import_react32.useEffect)(() => {
6540
6612
  if (!isPersist) return;
6541
6613
  setThreadsLoading(true);
6542
6614
  const base = getBaseEndpoint(endpoint);
@@ -6545,7 +6617,7 @@ function useChat(config) {
6545
6617
  fetch(`${base}/threads?${params}`, { credentials: "include" }).then((r) => r.json()).then((data) => setServerThreads(data.threads ?? [])).catch(() => {
6546
6618
  }).finally(() => setThreadsLoading(false));
6547
6619
  }, [isPersist, endpoint, surface, subject]);
6548
- (0, import_react31.useEffect)(() => {
6620
+ (0, import_react32.useEffect)(() => {
6549
6621
  if (!isPersist || !serverActiveId || isSubmitting) return;
6550
6622
  const base = getBaseEndpoint(endpoint);
6551
6623
  fetch(`${base}/threads/${serverActiveId}/messages?limit=30&offset=0`, { credentials: "include" }).then((r) => r.json()).then((data) => {
@@ -6554,7 +6626,7 @@ function useChat(config) {
6554
6626
  }).catch(() => {
6555
6627
  });
6556
6628
  }, [isPersist, serverActiveId, endpoint, isSubmitting]);
6557
- const loadOlderMessages = (0, import_react31.useCallback)(async () => {
6629
+ const loadOlderMessages = (0, import_react32.useCallback)(async () => {
6558
6630
  if (!isPersist || !serverActiveId || loadingOlder || !hasMoreMessages) return;
6559
6631
  setLoadingOlder(true);
6560
6632
  const base = getBaseEndpoint(endpoint);
@@ -6584,7 +6656,7 @@ function useChat(config) {
6584
6656
  setLoadingOlder(false);
6585
6657
  }
6586
6658
  }, [isPersist, serverActiveId, loadingOlder, hasMoreMessages, endpoint, serverMessages.length]);
6587
- (0, import_react31.useEffect)(() => {
6659
+ (0, import_react32.useEffect)(() => {
6588
6660
  if (!isPersist || !hasMoreMessages) return;
6589
6661
  const sentinel = sentinelRef.current;
6590
6662
  if (!sentinel) return;
@@ -6597,15 +6669,15 @@ function useChat(config) {
6597
6669
  observer.observe(sentinel);
6598
6670
  return () => observer.disconnect();
6599
6671
  }, [isPersist, hasMoreMessages, loadOlderMessages]);
6600
- const notificationsCtx = (0, import_react31.useContext)(NotificationsContext);
6601
- (0, import_react31.useEffect)(() => {
6672
+ const notificationsCtx = (0, import_react32.useContext)(NotificationsContext);
6673
+ (0, import_react32.useEffect)(() => {
6602
6674
  if (!notificationsCtx || !serverActiveId) return;
6603
6675
  notificationsCtx.registerViewingThread(serverActiveId);
6604
6676
  return () => {
6605
6677
  notificationsCtx.unregisterViewingThread(serverActiveId);
6606
6678
  };
6607
6679
  }, [notificationsCtx, serverActiveId]);
6608
- (0, import_react31.useEffect)(() => {
6680
+ (0, import_react32.useEffect)(() => {
6609
6681
  if (!onThreadChange) return;
6610
6682
  if (isControlled || isPersist) {
6611
6683
  onThreadChange(activeThread);
@@ -6626,23 +6698,23 @@ function useChat(config) {
6626
6698
  }
6627
6699
  }
6628
6700
  }, [activeThread, onThreadChange, isControlled, isPersist, memConversations, memActiveId, surface, subject]);
6629
- const updateMemMessages = (0, import_react31.useCallback)((convId, msgs) => {
6701
+ const updateMemMessages = (0, import_react32.useCallback)((convId, msgs) => {
6630
6702
  setMemConversations((prev) => prev.map((c) => c.id === convId ? { ...c, messages: msgs, updatedAt: Date.now() } : c).sort((a, b) => b.updatedAt - a.updatedAt));
6631
6703
  }, []);
6632
- const updateMemTitle = (0, import_react31.useCallback)((convId, t) => {
6704
+ const updateMemTitle = (0, import_react32.useCallback)((convId, t) => {
6633
6705
  setMemConversations((prev) => prev.map((c) => c.id === convId ? { ...c, title: t, titleState: t ? "ready" : c.titleState, updatedAt: Date.now() } : c));
6634
6706
  }, []);
6635
- const setMemTitleState = (0, import_react31.useCallback)((convId, state) => {
6707
+ const setMemTitleState = (0, import_react32.useCallback)((convId, state) => {
6636
6708
  setMemConversations((prev) => prev.map((c) => c.id === convId ? { ...c, titleState: state, updatedAt: Date.now() } : c));
6637
6709
  }, []);
6638
- const appendMemDelta = (0, import_react31.useCallback)((convId, msgId, delta) => {
6710
+ const appendMemDelta = (0, import_react32.useCallback)((convId, msgId, delta) => {
6639
6711
  setMemConversations((prev) => prev.map((c) => c.id === convId ? {
6640
6712
  ...c,
6641
6713
  messages: c.messages.map((m) => m.id === msgId ? { ...m, content: `${m.content}${delta}` } : m),
6642
6714
  updatedAt: Date.now()
6643
6715
  } : c));
6644
6716
  }, []);
6645
- const animateTitle = (0, import_react31.useCallback)(async (convId, raw) => {
6717
+ const animateTitle = (0, import_react32.useCallback)(async (convId, raw) => {
6646
6718
  const safe = sanitizeTitle(raw);
6647
6719
  setMemTitleState(convId, "loading");
6648
6720
  updateMemTitle(convId, "");
@@ -6654,7 +6726,7 @@ function useChat(config) {
6654
6726
  }
6655
6727
  setMemTitleState(convId, "ready");
6656
6728
  }, [setMemTitleState, updateMemTitle]);
6657
- const ensureMemConversation = (0, import_react31.useCallback)(() => {
6729
+ const ensureMemConversation = (0, import_react32.useCallback)(() => {
6658
6730
  if (memActiveId) return memActiveId;
6659
6731
  const id = makeId("conv");
6660
6732
  setMemConversations((prev) => [{
@@ -6667,7 +6739,7 @@ function useChat(config) {
6667
6739
  setMemActiveId(id);
6668
6740
  return id;
6669
6741
  }, [memActiveId]);
6670
- const sendMemoryMessage = (0, import_react31.useCallback)(async (content) => {
6742
+ const sendMemoryMessage = (0, import_react32.useCallback)(async (content) => {
6671
6743
  const convId = ensureMemConversation();
6672
6744
  const userMsg = { id: makeId("msg"), role: "user", content };
6673
6745
  const assistantMsg = { id: makeId("msg"), role: "assistant", content: "", model: activeModel };
@@ -6684,8 +6756,11 @@ function useChat(config) {
6684
6756
  messages: trimToTokenBudget(
6685
6757
  nextMessages.filter((m) => m.role === "user" || m.role === "assistant" && m.content).map(({ content: mc, role }) => ({ role, content: mc }))
6686
6758
  ),
6687
- model: activeModel,
6688
- ...prompt ? { systemPrompt: prompt } : {}
6759
+ ...explicitModel ? { model: explicitModel } : {},
6760
+ ...(() => {
6761
+ const ep = buildEffectivePrompt(prompt, memory);
6762
+ return ep ? { systemPrompt: ep } : {};
6763
+ })()
6689
6764
  })
6690
6765
  });
6691
6766
  if (!response.ok) {
@@ -6730,8 +6805,10 @@ function useChat(config) {
6730
6805
  ensureMemConversation,
6731
6806
  memConversations,
6732
6807
  endpoint,
6808
+ explicitModel,
6733
6809
  activeModel,
6734
6810
  prompt,
6811
+ memory,
6735
6812
  updateMemMessages,
6736
6813
  appendMemDelta,
6737
6814
  animateTitle,
@@ -6739,7 +6816,7 @@ function useChat(config) {
6739
6816
  setMemTitleState,
6740
6817
  onMessage
6741
6818
  ]);
6742
- const sendPersistMessage = (0, import_react31.useCallback)(async (content) => {
6819
+ const sendPersistMessage = (0, import_react32.useCallback)(async (content) => {
6743
6820
  const userMsg = { id: makeId("msg"), role: "user", content, status: "complete" };
6744
6821
  const assistantMsg = { id: makeId("msg"), role: "assistant", content: "", model: activeModel, status: "pending" };
6745
6822
  setServerMessages((prev) => [...prev, userMsg, assistantMsg]);
@@ -6753,9 +6830,12 @@ function useChat(config) {
6753
6830
  content,
6754
6831
  surface,
6755
6832
  subject: subject ?? null,
6756
- model: activeModel,
6833
+ ...explicitModel ? { model: explicitModel } : {},
6757
6834
  userId: userId ?? void 0,
6758
- ...prompt ? { systemPrompt: prompt } : {}
6835
+ ...(() => {
6836
+ const ep = buildEffectivePrompt(prompt, memory);
6837
+ return ep ? { systemPrompt: ep } : {};
6838
+ })()
6759
6839
  })
6760
6840
  });
6761
6841
  if (!response.ok) {
@@ -6810,8 +6890,10 @@ function useChat(config) {
6810
6890
  endpoint,
6811
6891
  surface,
6812
6892
  subject,
6893
+ explicitModel,
6813
6894
  activeModel,
6814
6895
  prompt,
6896
+ memory,
6815
6897
  userId,
6816
6898
  setServerMessages,
6817
6899
  setServerActiveId,
@@ -6819,7 +6901,7 @@ function useChat(config) {
6819
6901
  onMessage,
6820
6902
  onThreadCreate
6821
6903
  ]);
6822
- const sendMessage = (0, import_react31.useCallback)(async (rawText) => {
6904
+ const sendMessage = (0, import_react32.useCallback)(async (rawText) => {
6823
6905
  const content = (rawText ?? input).trim();
6824
6906
  if (!content || isSubmitting) return;
6825
6907
  setInput("");
@@ -6859,7 +6941,7 @@ function useChat(config) {
6859
6941
  setIsSubmitting(false);
6860
6942
  }
6861
6943
  }, [input, isSubmitting, isControlled, isPersist, onSendMessage, activeId, sendPersistMessage, sendMemoryMessage, memActiveId]);
6862
- const startNewChat = (0, import_react31.useCallback)(() => {
6944
+ const startNewChat = (0, import_react32.useCallback)(() => {
6863
6945
  if (isControlled) {
6864
6946
  onThreadSelect?.(null);
6865
6947
  } else if (isPersist) {
@@ -6874,7 +6956,7 @@ function useChat(config) {
6874
6956
  textareaRef.current?.focus();
6875
6957
  });
6876
6958
  }, [isControlled, isPersist, onThreadSelect]);
6877
- const selectThread = (0, import_react31.useCallback)((id) => {
6959
+ const selectThread = (0, import_react32.useCallback)((id) => {
6878
6960
  if (isControlled) {
6879
6961
  onThreadSelect?.(id);
6880
6962
  } else if (isPersist) {
@@ -6885,7 +6967,7 @@ function useChat(config) {
6885
6967
  setError(null);
6886
6968
  }
6887
6969
  }, [isControlled, isPersist, onThreadSelect]);
6888
- const deleteThread = (0, import_react31.useCallback)(async (threadId) => {
6970
+ const deleteThread = (0, import_react32.useCallback)(async (threadId) => {
6889
6971
  if (isControlled) {
6890
6972
  onThreadDelete?.(threadId);
6891
6973
  return;
@@ -6904,12 +6986,12 @@ function useChat(config) {
6904
6986
  if (memActiveId === threadId) setMemActiveId(null);
6905
6987
  }
6906
6988
  }, [isControlled, isPersist, serverActiveId, endpoint, memActiveId, onThreadDelete]);
6907
- const startRename = (0, import_react31.useCallback)((threadId) => {
6989
+ const startRename = (0, import_react32.useCallback)((threadId) => {
6908
6990
  const item = displaySidebarItems.find((t) => t.id === threadId);
6909
6991
  setRenamingId(threadId);
6910
6992
  setRenameValue(item?.title ?? "");
6911
6993
  }, [displaySidebarItems]);
6912
- const submitRename = (0, import_react31.useCallback)(async () => {
6994
+ const submitRename = (0, import_react32.useCallback)(async () => {
6913
6995
  if (!renamingId || !renameValue.trim()) {
6914
6996
  setRenamingId(null);
6915
6997
  return;
@@ -6968,7 +7050,7 @@ function useChat(config) {
6968
7050
  }
6969
7051
 
6970
7052
  // src/react/chat/ModelSelector.tsx
6971
- var import_react32 = __toESM(require("react"));
7053
+ var import_react33 = __toESM(require("react"));
6972
7054
  function ModelOption({
6973
7055
  provider,
6974
7056
  isActive,
@@ -6976,14 +7058,14 @@ function ModelOption({
6976
7058
  onSelect,
6977
7059
  onCheckout
6978
7060
  }) {
6979
- const handleClick = (0, import_react32.useCallback)(() => {
7061
+ const handleClick = (0, import_react33.useCallback)(() => {
6980
7062
  if (isLocked) {
6981
7063
  void onCheckout({ plan: "pro" });
6982
7064
  } else {
6983
7065
  onSelect(provider.model);
6984
7066
  }
6985
7067
  }, [isLocked, onCheckout, onSelect, provider.model]);
6986
- return /* @__PURE__ */ import_react32.default.createElement(
7068
+ return /* @__PURE__ */ import_react33.default.createElement(
6987
7069
  "button",
6988
7070
  {
6989
7071
  "aria-selected": isActive,
@@ -6995,9 +7077,9 @@ function ModelOption({
6995
7077
  title: isLocked ? "Add credits to unlock" : void 0,
6996
7078
  type: "button"
6997
7079
  },
6998
- /* @__PURE__ */ import_react32.default.createElement("img", { alt: "", className: "brokr-model-logo", src: provider.logo }),
6999
- /* @__PURE__ */ import_react32.default.createElement("span", { className: "brokr-model-option-label" }, provider.label),
7000
- isLocked ? /* @__PURE__ */ import_react32.default.createElement("span", { className: "brokr-model-lock", "aria-hidden": "true" }, /* @__PURE__ */ import_react32.default.createElement(LockIcon, { size: 13 })) : null
7080
+ /* @__PURE__ */ import_react33.default.createElement("img", { alt: "", className: "brokr-model-logo", src: provider.logo }),
7081
+ /* @__PURE__ */ import_react33.default.createElement("span", { className: "brokr-model-option-label" }, provider.label),
7082
+ isLocked ? /* @__PURE__ */ import_react33.default.createElement("span", { className: "brokr-model-lock", "aria-hidden": "true" }, /* @__PURE__ */ import_react33.default.createElement(LockIcon, { size: 13 })) : null
7001
7083
  );
7002
7084
  }
7003
7085
  function ModelSelector({
@@ -7006,17 +7088,22 @@ function ModelSelector({
7006
7088
  availableProviders,
7007
7089
  checkout
7008
7090
  }) {
7009
- const [selectorOpen, setSelectorOpen] = (0, import_react32.useState)(false);
7010
- const selectorRef = (0, import_react32.useRef)(null);
7011
- const activeProvider = (0, import_react32.useMemo)(
7012
- () => providers.find((p) => p.model === activeModel) ?? providers[0],
7013
- [activeModel]
7091
+ const [selectorOpen, setSelectorOpen] = (0, import_react33.useState)(false);
7092
+ const selectorRef = (0, import_react33.useRef)(null);
7093
+ const isAuto = activeModel === STACK_DEFAULT || !providers.some((p) => p.model === activeModel);
7094
+ const activeProvider = (0, import_react33.useMemo)(
7095
+ () => isAuto ? void 0 : providers.find((p) => p.model === activeModel) ?? providers[0],
7096
+ [activeModel, isAuto]
7014
7097
  );
7015
- const handleModelSelect = (0, import_react32.useCallback)((model) => {
7016
- setSelectedModel(model);
7098
+ const handleModelSelect = (0, import_react33.useCallback)((model) => {
7099
+ setSelectedModel(model === STACK_DEFAULT ? null : model);
7017
7100
  setSelectorOpen(false);
7018
7101
  }, [setSelectedModel]);
7019
- (0, import_react32.useEffect)(() => {
7102
+ const handleAutoSelect = (0, import_react33.useCallback)(() => {
7103
+ setSelectedModel(null);
7104
+ setSelectorOpen(false);
7105
+ }, [setSelectedModel]);
7106
+ (0, import_react33.useEffect)(() => {
7020
7107
  if (!selectorOpen) return;
7021
7108
  const onMouseDown = (e) => {
7022
7109
  if (!selectorRef.current?.contains(e.target)) setSelectorOpen(false);
@@ -7031,10 +7118,10 @@ function ModelSelector({
7031
7118
  document.removeEventListener("keydown", onKeyDown);
7032
7119
  };
7033
7120
  }, [selectorOpen]);
7034
- const handleToggle = (0, import_react32.useCallback)(() => {
7121
+ const handleToggle = (0, import_react33.useCallback)(() => {
7035
7122
  setSelectorOpen((v) => !v);
7036
7123
  }, []);
7037
- return /* @__PURE__ */ import_react32.default.createElement("div", { className: "brokr-model-selector", ref: selectorRef }, /* @__PURE__ */ import_react32.default.createElement(
7124
+ return /* @__PURE__ */ import_react33.default.createElement("div", { className: "brokr-model-selector", ref: selectorRef }, /* @__PURE__ */ import_react33.default.createElement(
7038
7125
  "button",
7039
7126
  {
7040
7127
  "aria-expanded": selectorOpen,
@@ -7043,15 +7130,26 @@ function ModelSelector({
7043
7130
  onClick: handleToggle,
7044
7131
  type: "button"
7045
7132
  },
7046
- 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" } }),
7047
- activeProvider?.label ?? "Model",
7048
- /* @__PURE__ */ import_react32.default.createElement(ChevronDownIcon, { size: 13 })
7049
- ), selectorOpen ? /* @__PURE__ */ import_react32.default.createElement("div", { className: "brokr-model-dropdown", role: "listbox" }, providers.map((p) => /* @__PURE__ */ import_react32.default.createElement(
7133
+ activeProvider ? /* @__PURE__ */ import_react33.default.createElement("img", { alt: "", className: "brokr-model-logo", src: activeProvider.logo }) : /* @__PURE__ */ import_react33.default.createElement("span", { className: "brokr-model-dot", style: { background: "#6B7280" } }),
7134
+ isAuto ? "Auto" : activeProvider?.label ?? "Model",
7135
+ /* @__PURE__ */ import_react33.default.createElement(ChevronDownIcon, { size: 13 })
7136
+ ), selectorOpen ? /* @__PURE__ */ import_react33.default.createElement("div", { className: "brokr-model-dropdown", role: "listbox" }, /* @__PURE__ */ import_react33.default.createElement(
7137
+ "button",
7138
+ {
7139
+ "aria-selected": isAuto,
7140
+ className: "brokr-model-option",
7141
+ "data-active": isAuto,
7142
+ onClick: handleAutoSelect,
7143
+ type: "button"
7144
+ },
7145
+ /* @__PURE__ */ import_react33.default.createElement("span", { className: "brokr-model-dot", style: { background: "#6B7280" } }),
7146
+ /* @__PURE__ */ import_react33.default.createElement("span", { className: "brokr-model-option-label" }, "Stack Default")
7147
+ ), providers.map((p) => /* @__PURE__ */ import_react33.default.createElement(
7050
7148
  ModelOption,
7051
7149
  {
7052
7150
  key: p.id,
7053
7151
  provider: p,
7054
- isActive: p.model === activeModel,
7152
+ isActive: !isAuto && p.model === activeModel,
7055
7153
  isLocked: !availableProviders.some((a) => a.id === p.id),
7056
7154
  onSelect: handleModelSelect,
7057
7155
  onCheckout: checkout
@@ -7060,10 +7158,10 @@ function ModelSelector({
7060
7158
  }
7061
7159
 
7062
7160
  // src/react/chat/ThreadSidebar.tsx
7063
- var import_react33 = __toESM(require("react"));
7161
+ var import_react34 = __toESM(require("react"));
7064
7162
  function ThreadItemButton({ id, title, isActive, onSelect }) {
7065
- const handleClick = (0, import_react33.useCallback)(() => onSelect(id), [id, onSelect]);
7066
- return /* @__PURE__ */ import_react33.default.createElement(
7163
+ const handleClick = (0, import_react34.useCallback)(() => onSelect(id), [id, onSelect]);
7164
+ return /* @__PURE__ */ import_react34.default.createElement(
7067
7165
  "button",
7068
7166
  {
7069
7167
  className: "brokr-ai-chat-conversation",
@@ -7071,9 +7169,40 @@ function ThreadItemButton({ id, title, isActive, onSelect }) {
7071
7169
  onClick: handleClick,
7072
7170
  type: "button"
7073
7171
  },
7074
- title
7172
+ /* @__PURE__ */ import_react34.default.createElement(MessageIcon, { size: 12 }),
7173
+ /* @__PURE__ */ import_react34.default.createElement("span", { className: "brokr-ai-chat-conversation-label" }, title)
7075
7174
  );
7076
7175
  }
7176
+ var SIDEBAR_GROUP_ORDER = [
7177
+ "today",
7178
+ "yesterday",
7179
+ "this_week",
7180
+ "this_month",
7181
+ "earlier",
7182
+ "undated"
7183
+ ];
7184
+ var SIDEBAR_GROUP_LABELS = {
7185
+ today: "Today",
7186
+ yesterday: "Yesterday",
7187
+ this_week: "This Week",
7188
+ this_month: "This Month",
7189
+ earlier: "Earlier",
7190
+ undated: "Recent"
7191
+ };
7192
+ function resolveSidebarDateGroup(updatedAt) {
7193
+ if (!updatedAt) return "undated";
7194
+ const parsed = new Date(updatedAt);
7195
+ if (Number.isNaN(parsed.getTime())) return "undated";
7196
+ const now = /* @__PURE__ */ new Date();
7197
+ const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate());
7198
+ const startOfTarget = new Date(parsed.getFullYear(), parsed.getMonth(), parsed.getDate());
7199
+ const diffDays = Math.floor((startOfToday.getTime() - startOfTarget.getTime()) / 864e5);
7200
+ if (diffDays <= 0) return "today";
7201
+ if (diffDays === 1) return "yesterday";
7202
+ if (diffDays < 7) return "this_week";
7203
+ if (diffDays < 30) return "this_month";
7204
+ return "earlier";
7205
+ }
7077
7206
  function ThreadSidebar() {
7078
7207
  const {
7079
7208
  startNewChat,
@@ -7087,11 +7216,11 @@ function ThreadSidebar() {
7087
7216
  activeId,
7088
7217
  selectThreadAndCloseSidebar
7089
7218
  } = useChatState();
7090
- const handleNewChat = (0, import_react33.useCallback)(() => {
7219
+ const handleNewChat = (0, import_react34.useCallback)(() => {
7091
7220
  startNewChat();
7092
7221
  closeSidebar();
7093
7222
  }, [startNewChat, closeSidebar]);
7094
- const handleRenameKeyDown = (0, import_react33.useCallback)((e) => {
7223
+ const handleRenameKeyDown = (0, import_react34.useCallback)((e) => {
7095
7224
  if (e.key === "Enter") {
7096
7225
  e.preventDefault();
7097
7226
  void submitRename();
@@ -7099,20 +7228,37 @@ function ThreadSidebar() {
7099
7228
  if (e.key === "Escape") {
7100
7229
  }
7101
7230
  }, [submitRename]);
7102
- const handleRenameBlur = (0, import_react33.useCallback)(() => {
7231
+ const handleRenameBlur = (0, import_react34.useCallback)(() => {
7103
7232
  void submitRename();
7104
7233
  }, [submitRename]);
7105
- const handleRenameChange = (0, import_react33.useCallback)((e) => {
7234
+ const handleRenameChange = (0, import_react34.useCallback)((e) => {
7106
7235
  setRenameValue(e.target.value);
7107
7236
  }, [setRenameValue]);
7108
- const content = (0, import_react33.useMemo)(() => {
7237
+ const groupedSidebarItems = (0, import_react34.useMemo)(() => {
7238
+ const grouped = /* @__PURE__ */ new Map();
7239
+ for (const item of displaySidebarItems) {
7240
+ const key = resolveSidebarDateGroup(item.updatedAt);
7241
+ const bucket = grouped.get(key);
7242
+ if (bucket) {
7243
+ bucket.push(item);
7244
+ } else {
7245
+ grouped.set(key, [item]);
7246
+ }
7247
+ }
7248
+ return SIDEBAR_GROUP_ORDER.map((key) => ({
7249
+ key,
7250
+ label: SIDEBAR_GROUP_LABELS[key],
7251
+ items: grouped.get(key) ?? []
7252
+ })).filter((group) => group.items.length > 0);
7253
+ }, [displaySidebarItems]);
7254
+ const content = (0, import_react34.useMemo)(() => {
7109
7255
  if (threadsLoading && displaySidebarItems.length === 0) {
7110
- 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 }));
7256
+ return /* @__PURE__ */ import_react34.default.createElement("div", { className: "brokr-ai-chat-sidebar-skeleton" }, /* @__PURE__ */ import_react34.default.createElement(Skeleton, { width: "75%", height: 14, radius: 6 }), /* @__PURE__ */ import_react34.default.createElement(Skeleton, { width: "60%", height: 14, radius: 6 }), /* @__PURE__ */ import_react34.default.createElement(Skeleton, { width: "85%", height: 14, radius: 6 }));
7111
7257
  }
7112
7258
  if (displaySidebarItems.length === 0) {
7113
- 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."));
7259
+ return /* @__PURE__ */ import_react34.default.createElement("div", { className: "brokr-ai-chat-sidebar-empty" }, /* @__PURE__ */ import_react34.default.createElement("p", { className: "brokr-ai-chat-sidebar-empty-text" }, "No conversations yet. Start one above."));
7114
7260
  }
7115
- return /* @__PURE__ */ import_react33.default.createElement("div", { className: "brokr-ai-chat-conversations" }, displaySidebarItems.map((item) => renamingId === item.id ? /* @__PURE__ */ import_react33.default.createElement(
7261
+ return /* @__PURE__ */ import_react34.default.createElement("div", { className: "brokr-ai-chat-sidebar-groups" }, groupedSidebarItems.map((group) => /* @__PURE__ */ import_react34.default.createElement("section", { className: "brokr-ai-chat-sidebar-group", key: group.key }, /* @__PURE__ */ import_react34.default.createElement("span", { className: "brokr-ai-chat-sidebar-kicker" }, group.label), /* @__PURE__ */ import_react34.default.createElement("div", { className: "brokr-ai-chat-conversations" }, group.items.map((item) => renamingId === item.id ? /* @__PURE__ */ import_react34.default.createElement(
7116
7262
  "input",
7117
7263
  {
7118
7264
  autoFocus: true,
@@ -7123,7 +7269,7 @@ function ThreadSidebar() {
7123
7269
  onKeyDown: handleRenameKeyDown,
7124
7270
  value: renameValue
7125
7271
  }
7126
- ) : /* @__PURE__ */ import_react33.default.createElement(
7272
+ ) : /* @__PURE__ */ import_react34.default.createElement(
7127
7273
  ThreadItemButton,
7128
7274
  {
7129
7275
  key: item.id,
@@ -7132,29 +7278,30 @@ function ThreadSidebar() {
7132
7278
  isActive: item.id === activeId,
7133
7279
  onSelect: selectThreadAndCloseSidebar
7134
7280
  }
7135
- )));
7281
+ ))))));
7136
7282
  }, [
7137
7283
  threadsLoading,
7138
7284
  displaySidebarItems,
7139
7285
  renamingId,
7140
7286
  renameValue,
7287
+ groupedSidebarItems,
7141
7288
  activeId,
7142
7289
  selectThreadAndCloseSidebar,
7143
7290
  handleRenameBlur,
7144
7291
  handleRenameChange,
7145
7292
  handleRenameKeyDown
7146
7293
  ]);
7147
- 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);
7294
+ return /* @__PURE__ */ import_react34.default.createElement(import_react34.default.Fragment, null, /* @__PURE__ */ import_react34.default.createElement("button", { className: "brokr-ai-chat-sidebar-new-chat", onClick: handleNewChat, type: "button" }, /* @__PURE__ */ import_react34.default.createElement(MessageIcon, { size: 16 }), "New chat"), content);
7148
7295
  }
7149
7296
 
7150
7297
  // src/react/chat/MessagePane.tsx
7151
- var import_react36 = __toESM(require("react"));
7298
+ var import_react37 = __toESM(require("react"));
7152
7299
 
7153
7300
  // src/react/chat/MessageBubble.tsx
7154
- var import_react35 = __toESM(require("react"));
7301
+ var import_react36 = __toESM(require("react"));
7155
7302
 
7156
7303
  // src/react/chat/MarkdownRenderer.tsx
7157
- var import_react34 = __toESM(require("react"));
7304
+ var import_react35 = __toESM(require("react"));
7158
7305
  function parseInline(text) {
7159
7306
  const nodes = [];
7160
7307
  let remaining = text;
@@ -7162,19 +7309,25 @@ function parseInline(text) {
7162
7309
  while (remaining) {
7163
7310
  const codeMatch = remaining.match(/^`([^`]+)`/);
7164
7311
  if (codeMatch) {
7165
- nodes.push(/* @__PURE__ */ import_react34.default.createElement("code", { className: "brokr-md-inline-code", key: key++ }, codeMatch[1]));
7312
+ nodes.push(/* @__PURE__ */ import_react35.default.createElement("code", { className: "brokr-md-inline-code", key: key++ }, codeMatch[1]));
7166
7313
  remaining = remaining.slice(codeMatch[0].length);
7167
7314
  continue;
7168
7315
  }
7169
7316
  const boldMatch = remaining.match(/^\*\*(.+?)\*\*/);
7170
7317
  if (boldMatch) {
7171
- nodes.push(/* @__PURE__ */ import_react34.default.createElement("strong", { key: key++ }, boldMatch[1]));
7318
+ nodes.push(/* @__PURE__ */ import_react35.default.createElement("strong", { key: key++ }, boldMatch[1]));
7172
7319
  remaining = remaining.slice(boldMatch[0].length);
7173
7320
  continue;
7174
7321
  }
7322
+ const strikethroughMatch = remaining.match(/^~~(.+?)~~/);
7323
+ if (strikethroughMatch) {
7324
+ nodes.push(/* @__PURE__ */ import_react35.default.createElement("del", { key: key++ }, strikethroughMatch[1]));
7325
+ remaining = remaining.slice(strikethroughMatch[0].length);
7326
+ continue;
7327
+ }
7175
7328
  const italicMatch = remaining.match(/^\*(.+?)\*/);
7176
7329
  if (italicMatch) {
7177
- nodes.push(/* @__PURE__ */ import_react34.default.createElement("em", { key: key++ }, italicMatch[1]));
7330
+ nodes.push(/* @__PURE__ */ import_react35.default.createElement("em", { key: key++ }, italicMatch[1]));
7178
7331
  remaining = remaining.slice(italicMatch[0].length);
7179
7332
  continue;
7180
7333
  }
@@ -7183,12 +7336,12 @@ function parseInline(text) {
7183
7336
  const rawHref = linkMatch[2];
7184
7337
  const isSafe = /^https?:\/\//i.test(rawHref) || /^mailto:/i.test(rawHref);
7185
7338
  nodes.push(
7186
- /* @__PURE__ */ import_react34.default.createElement("a", { className: "brokr-md-link", href: isSafe ? rawHref : "#", key: key++, rel: "noopener noreferrer", target: "_blank" }, linkMatch[1])
7339
+ /* @__PURE__ */ import_react35.default.createElement("a", { className: "brokr-md-link", href: isSafe ? rawHref : "#", key: key++, rel: "noopener noreferrer", target: "_blank" }, linkMatch[1])
7187
7340
  );
7188
7341
  remaining = remaining.slice(linkMatch[0].length);
7189
7342
  continue;
7190
7343
  }
7191
- const nextSpecial = remaining.search(/[`*\[]/);
7344
+ const nextSpecial = remaining.search(/[`*~\[]/);
7192
7345
  if (nextSpecial === -1) {
7193
7346
  nodes.push(remaining);
7194
7347
  break;
@@ -7204,10 +7357,10 @@ function parseInline(text) {
7204
7357
  return nodes;
7205
7358
  }
7206
7359
  function CodeBlock({ code, language }) {
7207
- const handleCopy = (0, import_react34.useCallback)(() => {
7360
+ const handleCopy = (0, import_react35.useCallback)(() => {
7208
7361
  void navigator.clipboard?.writeText(code);
7209
7362
  }, [code]);
7210
- return /* @__PURE__ */ import_react34.default.createElement("div", { className: "brokr-md-codeblock" }, /* @__PURE__ */ import_react34.default.createElement("div", { className: "brokr-md-codeblock-header" }, /* @__PURE__ */ import_react34.default.createElement("span", { className: "brokr-md-codeblock-lang" }, language || "code"), /* @__PURE__ */ import_react34.default.createElement(
7363
+ return /* @__PURE__ */ import_react35.default.createElement("div", { className: "brokr-md-codeblock" }, /* @__PURE__ */ import_react35.default.createElement("div", { className: "brokr-md-codeblock-header" }, /* @__PURE__ */ import_react35.default.createElement("span", { className: "brokr-md-codeblock-lang" }, language || "code"), /* @__PURE__ */ import_react35.default.createElement(
7211
7364
  "button",
7212
7365
  {
7213
7366
  "aria-label": "Copy code",
@@ -7215,9 +7368,18 @@ function CodeBlock({ code, language }) {
7215
7368
  onClick: handleCopy,
7216
7369
  type: "button"
7217
7370
  },
7218
- /* @__PURE__ */ import_react34.default.createElement(CopyIcon, { size: 13 }),
7371
+ /* @__PURE__ */ import_react35.default.createElement(CopyIcon, { size: 13 }),
7219
7372
  "Copy"
7220
- )), /* @__PURE__ */ import_react34.default.createElement("pre", { className: "brokr-md-codeblock-pre" }, /* @__PURE__ */ import_react34.default.createElement("code", null, code)));
7373
+ )), /* @__PURE__ */ import_react35.default.createElement("pre", { className: "brokr-md-codeblock-pre" }, /* @__PURE__ */ import_react35.default.createElement("code", null, code)));
7374
+ }
7375
+ function looksLikeTableSeparator(line) {
7376
+ const trimmed = line.trim();
7377
+ return /^\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)+\|?$/.test(trimmed);
7378
+ }
7379
+ function splitTableRow(line) {
7380
+ const trimmed = line.trim();
7381
+ const withoutEdges = trimmed.replace(/^\|/, "").replace(/\|$/, "");
7382
+ return withoutEdges.split("|").map((cell) => cell.trim());
7221
7383
  }
7222
7384
  function parseBlocks(text) {
7223
7385
  const blocks = [];
@@ -7248,22 +7410,44 @@ function parseBlocks(text) {
7248
7410
  i++;
7249
7411
  continue;
7250
7412
  }
7413
+ if (line.includes("|") && i + 1 < lines.length && looksLikeTableSeparator(lines[i + 1] ?? "")) {
7414
+ const headers = splitTableRow(line);
7415
+ const rows = [];
7416
+ i += 2;
7417
+ while (i < lines.length) {
7418
+ const candidate = lines[i] ?? "";
7419
+ if (!candidate.trim() || !candidate.includes("|")) break;
7420
+ rows.push(splitTableRow(candidate));
7421
+ i++;
7422
+ }
7423
+ blocks.push({ type: "table", content: "", headers, rows });
7424
+ continue;
7425
+ }
7426
+ if (/^[\s]*>\s?/.test(line)) {
7427
+ const quoteLines = [];
7428
+ while (i < lines.length && /^[\s]*>\s?/.test(lines[i])) {
7429
+ quoteLines.push(lines[i].replace(/^[\s]*>\s?/, ""));
7430
+ i++;
7431
+ }
7432
+ blocks.push({ type: "quote", content: "", items: quoteLines });
7433
+ continue;
7434
+ }
7251
7435
  if (/^[\s]*[-*]\s+/.test(line)) {
7252
7436
  const items = [];
7253
7437
  while (i < lines.length && /^[\s]*[-*]\s+/.test(lines[i])) {
7254
7438
  items.push(lines[i].replace(/^[\s]*[-*]\s+/, ""));
7255
7439
  i++;
7256
7440
  }
7257
- blocks.push({ type: "list", content: "", items });
7441
+ blocks.push({ type: "list", content: "", items, ordered: false });
7258
7442
  continue;
7259
7443
  }
7260
- if (/^[\s]*\d+\.\s+/.test(line)) {
7444
+ if (/^[\s]*\d+[.)]\s+/.test(line)) {
7261
7445
  const items = [];
7262
- while (i < lines.length && /^[\s]*\d+\.\s+/.test(lines[i])) {
7263
- items.push(lines[i].replace(/^[\s]*\d+\.\s+/, ""));
7446
+ while (i < lines.length && /^[\s]*\d+[.)]\s+/.test(lines[i])) {
7447
+ items.push(lines[i].replace(/^[\s]*\d+[.)]\s+/, ""));
7264
7448
  i++;
7265
7449
  }
7266
- blocks.push({ type: "list", content: "", items });
7450
+ blocks.push({ type: "list", content: "", items, ordered: true });
7267
7451
  continue;
7268
7452
  }
7269
7453
  if (!line.trim()) {
@@ -7272,7 +7456,7 @@ function parseBlocks(text) {
7272
7456
  }
7273
7457
  const paraLines = [line];
7274
7458
  i++;
7275
- 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])) {
7459
+ 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])) {
7276
7460
  paraLines.push(lines[i]);
7277
7461
  i++;
7278
7462
  }
@@ -7284,41 +7468,48 @@ function renderBlocks(blocks) {
7284
7468
  return blocks.map((block, idx) => {
7285
7469
  switch (block.type) {
7286
7470
  case "code":
7287
- return /* @__PURE__ */ import_react34.default.createElement(CodeBlock, { code: block.content, key: idx, language: block.language });
7471
+ return /* @__PURE__ */ import_react35.default.createElement(CodeBlock, { code: block.content, key: idx, language: block.language });
7288
7472
  case "divider":
7289
- return /* @__PURE__ */ import_react34.default.createElement("hr", { className: "brokr-md-divider", key: idx });
7473
+ return /* @__PURE__ */ import_react35.default.createElement("hr", { className: "brokr-md-divider", key: idx });
7290
7474
  case "heading": {
7291
7475
  const Tag = `h${Math.min(block.level ?? 3, 6)}`;
7292
- return /* @__PURE__ */ import_react34.default.createElement(Tag, { className: `brokr-md-heading brokr-md-h${block.level}`, key: idx }, parseInline(block.content));
7476
+ return /* @__PURE__ */ import_react35.default.createElement(Tag, { className: `brokr-md-heading brokr-md-h${block.level}`, key: idx }, parseInline(block.content));
7293
7477
  }
7294
7478
  case "list":
7295
- 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))));
7479
+ if (block.ordered) {
7480
+ return /* @__PURE__ */ import_react35.default.createElement("ol", { className: "brokr-md-list brokr-md-list-ordered", key: idx }, block.items?.map((item, li) => /* @__PURE__ */ import_react35.default.createElement("li", { key: li }, parseInline(item))));
7481
+ }
7482
+ return /* @__PURE__ */ import_react35.default.createElement("ul", { className: "brokr-md-list brokr-md-list-unordered", key: idx }, block.items?.map((item, li) => /* @__PURE__ */ import_react35.default.createElement("li", { key: li }, parseInline(item))));
7483
+ case "quote":
7484
+ return /* @__PURE__ */ import_react35.default.createElement("blockquote", { className: "brokr-md-quote", key: idx }, block.items?.map((line, lineIndex) => /* @__PURE__ */ import_react35.default.createElement("p", { className: "brokr-md-quote-line", key: lineIndex }, parseInline(line))));
7485
+ case "table":
7486
+ return /* @__PURE__ */ import_react35.default.createElement("div", { className: "brokr-md-table-wrap", key: idx }, /* @__PURE__ */ import_react35.default.createElement("table", { className: "brokr-md-table" }, /* @__PURE__ */ import_react35.default.createElement("thead", null, /* @__PURE__ */ import_react35.default.createElement("tr", null, block.headers?.map((header, headerIndex) => /* @__PURE__ */ import_react35.default.createElement("th", { key: headerIndex }, parseInline(header))))), /* @__PURE__ */ import_react35.default.createElement("tbody", null, block.rows?.map((row, rowIndex) => /* @__PURE__ */ import_react35.default.createElement("tr", { key: rowIndex }, row.map((cell, cellIndex) => /* @__PURE__ */ import_react35.default.createElement("td", { key: cellIndex }, parseInline(cell))))))));
7296
7487
  case "paragraph":
7297
7488
  default:
7298
- return /* @__PURE__ */ import_react34.default.createElement("p", { className: "brokr-md-paragraph", key: idx }, parseInline(block.content));
7489
+ return /* @__PURE__ */ import_react35.default.createElement("p", { className: "brokr-md-paragraph", key: idx }, parseInline(block.content));
7299
7490
  }
7300
7491
  });
7301
7492
  }
7302
7493
  function MarkdownRenderer({ content }) {
7303
- const rendered = (0, import_react34.useMemo)(() => {
7494
+ const rendered = (0, import_react35.useMemo)(() => {
7304
7495
  if (!content) return null;
7305
7496
  const blocks = parseBlocks(content);
7306
7497
  return renderBlocks(blocks);
7307
7498
  }, [content]);
7308
- return /* @__PURE__ */ import_react34.default.createElement("div", { className: "brokr-md" }, rendered);
7499
+ return /* @__PURE__ */ import_react35.default.createElement("div", { className: "brokr-md" }, rendered);
7309
7500
  }
7310
7501
 
7311
7502
  // src/react/chat/MessageBubble.tsx
7312
7503
  function MessageBubble({ message, isTyping, user }) {
7313
- const handleCopy = (0, import_react35.useCallback)(() => {
7504
+ const handleCopy = (0, import_react36.useCallback)(() => {
7314
7505
  navigator.clipboard.writeText(message.content).catch(() => {
7315
7506
  });
7316
7507
  }, [message.content]);
7317
7508
  if (message.role === "user") {
7318
- return /* @__PURE__ */ import_react35.default.createElement("article", { className: "brokr-ai-chat-message", "data-role": "user" }, /* @__PURE__ */ import_react35.default.createElement("div", { className: "brokr-ai-chat-message-row", "data-role": "user" }, /* @__PURE__ */ import_react35.default.createElement("div", { className: "brokr-ai-chat-message-bubble" }, /* @__PURE__ */ import_react35.default.createElement("div", { className: "brokr-ai-chat-message-content brokr-ai-chat-message-content-user" }, message.content)), /* @__PURE__ */ import_react35.default.createElement(Avatar, { email: user?.email, name: user?.name, src: user?.image })));
7509
+ return /* @__PURE__ */ import_react36.default.createElement("article", { className: "brokr-ai-chat-message", "data-role": "user" }, /* @__PURE__ */ import_react36.default.createElement("div", { className: "brokr-ai-chat-message-row", "data-role": "user" }, /* @__PURE__ */ import_react36.default.createElement("div", { className: "brokr-ai-chat-message-bubble" }, /* @__PURE__ */ import_react36.default.createElement("div", { className: "brokr-ai-chat-message-content brokr-ai-chat-message-content-user" }, message.content)), /* @__PURE__ */ import_react36.default.createElement(Avatar, { email: user?.email, name: user?.name, src: user?.image })));
7319
7510
  }
7320
7511
  const mp = message.model ? resolveProviderByModel(message.model) : null;
7321
- return /* @__PURE__ */ import_react35.default.createElement("article", { className: "brokr-ai-chat-message", "data-role": "assistant" }, /* @__PURE__ */ import_react35.default.createElement("div", { className: "brokr-ai-chat-message-row", "data-role": "assistant" }, mp ? /* @__PURE__ */ import_react35.default.createElement("img", { alt: mp.label, className: "brokr-ai-chat-model-avatar", src: mp.logo }) : null, isTyping ? /* @__PURE__ */ import_react35.default.createElement("div", { className: "brokr-ai-chat-typing", "aria-label": "AI is typing" }, /* @__PURE__ */ import_react35.default.createElement("span", null), /* @__PURE__ */ import_react35.default.createElement("span", null), /* @__PURE__ */ import_react35.default.createElement("span", null)) : message.status === "error" && !message.content ? /* @__PURE__ */ import_react35.default.createElement("div", { className: "brokr-ai-chat-message-wrap" }, /* @__PURE__ */ import_react35.default.createElement("div", { className: "brokr-ai-chat-message-error" }, "Something went wrong generating a reply.")) : /* @__PURE__ */ import_react35.default.createElement("div", { className: "brokr-ai-chat-message-wrap" }, /* @__PURE__ */ import_react35.default.createElement("div", { className: "brokr-ai-chat-message-content" }, /* @__PURE__ */ import_react35.default.createElement(MarkdownRenderer, { content: message.content })), message.content ? /* @__PURE__ */ import_react35.default.createElement(
7512
+ return /* @__PURE__ */ import_react36.default.createElement("article", { className: "brokr-ai-chat-message", "data-role": "assistant" }, /* @__PURE__ */ import_react36.default.createElement("div", { className: "brokr-ai-chat-message-row", "data-role": "assistant" }, mp ? /* @__PURE__ */ import_react36.default.createElement("img", { alt: mp.label, className: "brokr-ai-chat-model-avatar", src: mp.logo }) : null, isTyping ? /* @__PURE__ */ import_react36.default.createElement("div", { className: "brokr-ai-chat-typing", "aria-label": "AI is typing" }, /* @__PURE__ */ import_react36.default.createElement("span", null), /* @__PURE__ */ import_react36.default.createElement("span", null), /* @__PURE__ */ import_react36.default.createElement("span", null)) : message.status === "error" && !message.content ? /* @__PURE__ */ import_react36.default.createElement("div", { className: "brokr-ai-chat-message-wrap" }, /* @__PURE__ */ import_react36.default.createElement("div", { className: "brokr-ai-chat-message-error" }, "Something went wrong generating a reply.")) : /* @__PURE__ */ import_react36.default.createElement("div", { className: "brokr-ai-chat-message-wrap" }, /* @__PURE__ */ import_react36.default.createElement("div", { className: "brokr-ai-chat-message-content" }, /* @__PURE__ */ import_react36.default.createElement(MarkdownRenderer, { content: message.content })), message.content ? /* @__PURE__ */ import_react36.default.createElement(
7322
7513
  "button",
7323
7514
  {
7324
7515
  "aria-label": "Copy message",
@@ -7326,16 +7517,16 @@ function MessageBubble({ message, isTyping, user }) {
7326
7517
  onClick: handleCopy,
7327
7518
  type: "button"
7328
7519
  },
7329
- /* @__PURE__ */ import_react35.default.createElement(CopyIcon, { size: 13 })
7520
+ /* @__PURE__ */ import_react36.default.createElement(CopyIcon, { size: 13 })
7330
7521
  ) : null)));
7331
7522
  }
7332
7523
 
7333
7524
  // src/react/chat/MessagePane.tsx
7334
7525
  function StarterPromptButton({ prompt, onSend }) {
7335
- const handleClick = (0, import_react36.useCallback)(() => {
7526
+ const handleClick = (0, import_react37.useCallback)(() => {
7336
7527
  void onSend(prompt);
7337
7528
  }, [prompt, onSend]);
7338
- return /* @__PURE__ */ import_react36.default.createElement("button", { className: "brokr-ai-chat-starter", onClick: handleClick, type: "button" }, prompt);
7529
+ return /* @__PURE__ */ import_react37.default.createElement("button", { className: "brokr-ai-chat-starter", onClick: handleClick, type: "button" }, prompt);
7339
7530
  }
7340
7531
  function MessagePane({ starterPrompts, emptyTitle, emptyCopy, subtitle }) {
7341
7532
  const {
@@ -7353,10 +7544,10 @@ function MessagePane({ starterPrompts, emptyTitle, emptyCopy, subtitle }) {
7353
7544
  user
7354
7545
  } = useChatState();
7355
7546
  const isEmpty = displayMessages.length === 0;
7356
- const messageElements = (0, import_react36.useMemo)(() => {
7547
+ const messageElements = (0, import_react37.useMemo)(() => {
7357
7548
  return visibleMessages.map((message, index) => {
7358
7549
  const isTyping = message.role === "assistant" && !message.content && (isSubmitting && index === visibleMessages.length - 1 || message.status === "pending" || message.status === "streaming");
7359
- return /* @__PURE__ */ import_react36.default.createElement(
7550
+ return /* @__PURE__ */ import_react37.default.createElement(
7360
7551
  MessageBubble,
7361
7552
  {
7362
7553
  isTyping,
@@ -7367,16 +7558,16 @@ function MessagePane({ starterPrompts, emptyTitle, emptyCopy, subtitle }) {
7367
7558
  );
7368
7559
  });
7369
7560
  }, [visibleMessages, isSubmitting, user]);
7370
- 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 })));
7561
+ return /* @__PURE__ */ import_react37.default.createElement("div", { className: "brokr-ai-chat-thread", "data-empty": isEmpty, ref: scrollContainerRef }, isEmpty ? /* @__PURE__ */ import_react37.default.createElement("div", { className: "brokr-ai-chat-empty" }, /* @__PURE__ */ import_react37.default.createElement(SparkIcon, { size: 28 }), /* @__PURE__ */ import_react37.default.createElement("h2", { className: "brokr-title" }, emptyTitle), subtitle ?? emptyCopy ? /* @__PURE__ */ import_react37.default.createElement("p", { className: "brokr-copy" }, subtitle ?? emptyCopy) : null, starterPrompts.length > 0 ? /* @__PURE__ */ import_react37.default.createElement("div", { className: "brokr-ai-chat-starters" }, starterPrompts.map((sp) => /* @__PURE__ */ import_react37.default.createElement(StarterPromptButton, { key: sp, prompt: sp, onSend: sendMessage }))) : null) : /* @__PURE__ */ import_react37.default.createElement("div", { className: "brokr-ai-chat-thread-inner" }, (isPersist ? hasMoreMessages : memHasMore) ? /* @__PURE__ */ import_react37.default.createElement("div", { ref: sentinelRef, className: "brokr-ai-chat-sentinel" }, loadingOlder ? /* @__PURE__ */ import_react37.default.createElement("div", { style: { display: "flex", justifyContent: "center", padding: "0.5rem" } }, /* @__PURE__ */ import_react37.default.createElement(Skeleton, { width: 120, height: 12, radius: 6 })) : null) : null, messageElements, /* @__PURE__ */ import_react37.default.createElement("div", { ref: bottomRef })));
7371
7562
  }
7372
7563
 
7373
7564
  // src/react/chat/ChatInput.tsx
7374
- var import_react37 = __toESM(require("react"));
7565
+ var import_react38 = __toESM(require("react"));
7375
7566
  function CommandButton({ cmd, chatContext }) {
7376
- const handleClick = (0, import_react37.useCallback)(() => {
7567
+ const handleClick = (0, import_react38.useCallback)(() => {
7377
7568
  void cmd.run(chatContext);
7378
7569
  }, [cmd, chatContext]);
7379
- return /* @__PURE__ */ import_react37.default.createElement("button", { className: "brokr-ai-chat-sidebar-button", key: cmd.id, onClick: handleClick, type: "button" }, cmd.text);
7570
+ return /* @__PURE__ */ import_react38.default.createElement("button", { className: "brokr-ai-chat-sidebar-button", key: cmd.id, onClick: handleClick, type: "button" }, cmd.text);
7380
7571
  }
7381
7572
  function ChatInput() {
7382
7573
  const {
@@ -7389,26 +7580,26 @@ function ChatInput() {
7389
7580
  composerCommands,
7390
7581
  chatContext
7391
7582
  } = useChatState();
7392
- (0, import_react37.useEffect)(() => {
7583
+ (0, import_react38.useEffect)(() => {
7393
7584
  const el = textareaRef.current;
7394
7585
  if (!el) return;
7395
7586
  el.style.height = "auto";
7396
7587
  el.style.height = `${Math.min(el.scrollHeight, 168)}px`;
7397
7588
  }, [input, textareaRef]);
7398
- const handleSubmit = (0, import_react37.useCallback)((e) => {
7589
+ const handleSubmit = (0, import_react38.useCallback)((e) => {
7399
7590
  e.preventDefault();
7400
7591
  void sendMessage();
7401
7592
  }, [sendMessage]);
7402
- const handleKeyDown = (0, import_react37.useCallback)((e) => {
7593
+ const handleKeyDown = (0, import_react38.useCallback)((e) => {
7403
7594
  if (e.key === "Enter" && !e.shiftKey) {
7404
7595
  e.preventDefault();
7405
7596
  void sendMessage();
7406
7597
  }
7407
7598
  }, [sendMessage]);
7408
- const handleChange = (0, import_react37.useCallback)((e) => {
7599
+ const handleChange = (0, import_react38.useCallback)((e) => {
7409
7600
  setInput(e.target.value);
7410
7601
  }, [setInput]);
7411
- 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(
7602
+ return /* @__PURE__ */ import_react38.default.createElement("form", { className: "brokr-ai-chat-input-area", onSubmit: handleSubmit }, /* @__PURE__ */ import_react38.default.createElement("div", { className: "brokr-ai-chat-input-container" }, composerCommands.length > 0 ? /* @__PURE__ */ import_react38.default.createElement("div", { className: "brokr-ai-chat-composer-actions" }, composerCommands.map((cmd) => /* @__PURE__ */ import_react38.default.createElement(CommandButton, { key: cmd.id, cmd, chatContext }))) : null, /* @__PURE__ */ import_react38.default.createElement("div", { className: "brokr-ai-chat-input-row" }, /* @__PURE__ */ import_react38.default.createElement(
7412
7603
  "textarea",
7413
7604
  {
7414
7605
  className: "brokr-ai-chat-textarea",
@@ -7419,15 +7610,15 @@ function ChatInput() {
7419
7610
  rows: 1,
7420
7611
  value: input
7421
7612
  }
7422
- ), /* @__PURE__ */ import_react37.default.createElement(
7613
+ ), /* @__PURE__ */ import_react38.default.createElement(
7423
7614
  "button",
7424
7615
  {
7425
7616
  className: "brokr-ai-chat-send",
7426
7617
  disabled: isSubmitting || !input.trim(),
7427
7618
  type: "submit"
7428
7619
  },
7429
- isSubmitting ? /* @__PURE__ */ import_react37.default.createElement(SparkIcon, { size: 16 }) : /* @__PURE__ */ import_react37.default.createElement(ArrowRightIcon, { size: 16 })
7430
- ))), error ? /* @__PURE__ */ import_react37.default.createElement("div", { className: "brokr-inline-message", "data-tone": "error" }, error) : null);
7620
+ isSubmitting ? /* @__PURE__ */ import_react38.default.createElement(SparkIcon, { size: 16 }) : /* @__PURE__ */ import_react38.default.createElement(ArrowRightIcon, { size: 16 })
7621
+ ))), error ? /* @__PURE__ */ import_react38.default.createElement("div", { className: "brokr-inline-message", "data-tone": "error" }, error) : null);
7431
7622
  }
7432
7623
 
7433
7624
  // src/react/chat/AIChat.tsx
@@ -7449,6 +7640,7 @@ function AIChat(inlineProps) {
7449
7640
  subtitle,
7450
7641
  model: modelProp,
7451
7642
  modelSelector,
7643
+ memory,
7452
7644
  variant = 1,
7453
7645
  sidebar: sidebarProp,
7454
7646
  threadMenu: threadMenuProp,
@@ -7472,30 +7664,28 @@ function AIChat(inlineProps) {
7472
7664
  const headerVisible = variant !== 3;
7473
7665
  const threadMenuVisible = threadMenuProp !== void 0 ? threadMenuProp : variant !== 3;
7474
7666
  const modelSelectorVisible = (modelSelector !== void 0 ? modelSelector : true) && !modelProp;
7475
- const [selectedModel, setSelectedModel] = (0, import_react38.useState)(() => {
7476
- if (modelProp) return modelProp;
7477
- return providers.find((p) => p.free)?.model ?? providers[0]?.model ?? "";
7478
- });
7479
- (0, import_react38.useEffect)(() => {
7480
- if (modelProp) setSelectedModel(modelProp);
7481
- }, [modelProp]);
7482
- const activeModel = modelProp ?? selectedModel;
7483
- const activeProvider = (0, import_react38.useMemo)(
7484
- () => providers.find((p) => p.model === activeModel) ?? providers[0],
7667
+ const [userSelectedModel, setUserSelectedModel] = (0, import_react39.useState)(null);
7668
+ const explicitModel = modelProp ?? userSelectedModel ?? void 0;
7669
+ const displayModel = explicitModel ?? providers.find((p) => p.free)?.model ?? providers[0]?.model ?? "";
7670
+ const activeModel = explicitModel ?? STACK_DEFAULT;
7671
+ const activeProvider = (0, import_react39.useMemo)(
7672
+ () => activeModel === STACK_DEFAULT ? void 0 : providers.find((p) => p.model === activeModel) ?? providers[0],
7485
7673
  [activeModel]
7486
7674
  );
7487
- const hasBalance = (0, import_react38.useMemo)(
7675
+ const hasBalance = (0, import_react39.useMemo)(
7488
7676
  () => billing === null || billing.balanceCents === null || billing.balanceCents > 0 || billing.hasPaymentMethod,
7489
7677
  [billing]
7490
7678
  );
7491
- const availableProviders = (0, import_react38.useMemo)(
7679
+ const availableProviders = (0, import_react39.useMemo)(
7492
7680
  () => hasBalance ? providers : providers.filter((p) => p.free),
7493
7681
  [hasBalance]
7494
7682
  );
7495
7683
  const chat = useChat({
7496
7684
  endpoint,
7497
7685
  prompt,
7498
- model: activeModel,
7686
+ explicitModel,
7687
+ displayModel,
7688
+ memory,
7499
7689
  persist,
7500
7690
  surface,
7501
7691
  subject,
@@ -7512,8 +7702,8 @@ function AIChat(inlineProps) {
7512
7702
  onThreadChange,
7513
7703
  userId: user?.id
7514
7704
  });
7515
- const shellRef = (0, import_react38.useRef)(null);
7516
- (0, import_react38.useEffect)(() => {
7705
+ const shellRef = (0, import_react39.useRef)(null);
7706
+ (0, import_react39.useEffect)(() => {
7517
7707
  const el = shellRef.current;
7518
7708
  if (!el) return;
7519
7709
  let raf = 0;
@@ -7539,23 +7729,23 @@ function AIChat(inlineProps) {
7539
7729
  ro?.disconnect();
7540
7730
  };
7541
7731
  }, []);
7542
- const [sidebarOpen, setSidebarOpen] = (0, import_react38.useState)(false);
7543
- const [threadMenuOpenId, setThreadMenuOpenId] = (0, import_react38.useState)(null);
7544
- const threadMenuRef = (0, import_react38.useRef)(null);
7545
- const closeSidebar = (0, import_react38.useCallback)(() => setSidebarOpen(false), []);
7546
- const selectThreadAndCloseSidebar = (0, import_react38.useCallback)((id) => {
7732
+ const [sidebarOpen, setSidebarOpen] = (0, import_react39.useState)(false);
7733
+ const [threadMenuOpenId, setThreadMenuOpenId] = (0, import_react39.useState)(null);
7734
+ const threadMenuRef = (0, import_react39.useRef)(null);
7735
+ const closeSidebar = (0, import_react39.useCallback)(() => setSidebarOpen(false), []);
7736
+ const selectThreadAndCloseSidebar = (0, import_react39.useCallback)((id) => {
7547
7737
  chat.selectThread(id);
7548
7738
  setSidebarOpen(false);
7549
7739
  }, [chat.selectThread]);
7550
- const handleCopy = (0, import_react38.useCallback)((content) => {
7740
+ const handleCopy = (0, import_react39.useCallback)((content) => {
7551
7741
  navigator.clipboard.writeText(content).catch(() => {
7552
7742
  });
7553
7743
  }, []);
7554
- const handleStartRename = (0, import_react38.useCallback)((threadId) => {
7744
+ const handleStartRename = (0, import_react39.useCallback)((threadId) => {
7555
7745
  setThreadMenuOpenId(null);
7556
7746
  chat.startRename(threadId);
7557
7747
  }, [chat.startRename]);
7558
- (0, import_react38.useEffect)(() => {
7748
+ (0, import_react39.useEffect)(() => {
7559
7749
  if (!threadMenuOpenId) return;
7560
7750
  const onMouseDown = (e) => {
7561
7751
  if (!threadMenuRef.current?.contains(e.target)) setThreadMenuOpenId(null);
@@ -7563,7 +7753,7 @@ function AIChat(inlineProps) {
7563
7753
  document.addEventListener("mousedown", onMouseDown);
7564
7754
  return () => document.removeEventListener("mousedown", onMouseDown);
7565
7755
  }, [threadMenuOpenId]);
7566
- (0, import_react38.useEffect)(() => {
7756
+ (0, import_react39.useEffect)(() => {
7567
7757
  if (!sidebarOpen) return;
7568
7758
  const onKeyDown = (e) => {
7569
7759
  if (e.key === "Escape") setSidebarOpen(false);
@@ -7571,10 +7761,10 @@ function AIChat(inlineProps) {
7571
7761
  document.addEventListener("keydown", onKeyDown);
7572
7762
  return () => document.removeEventListener("keydown", onKeyDown);
7573
7763
  }, [sidebarOpen]);
7574
- const finalTitle = (0, import_react38.useMemo)(() => {
7764
+ const finalTitle = (0, import_react39.useMemo)(() => {
7575
7765
  return chat.renderedTitle || title;
7576
7766
  }, [chat.renderedTitle, title]);
7577
- const chatContext = (0, import_react38.useMemo)(() => ({
7767
+ const chatContext = (0, import_react39.useMemo)(() => ({
7578
7768
  thread: chat.activeThread,
7579
7769
  messages: chat.displayMessages,
7580
7770
  user,
@@ -7582,19 +7772,19 @@ function AIChat(inlineProps) {
7582
7772
  newThread: chat.startNewChat,
7583
7773
  setThread: chat.selectThread
7584
7774
  }), [chat.activeThread, chat.displayMessages, user, chat.sendMessage, chat.startNewChat, chat.selectThread]);
7585
- const headerCommands = (0, import_react38.useMemo)(
7775
+ const headerCommands = (0, import_react39.useMemo)(
7586
7776
  () => commands.filter((c) => c.location === "header" && (!c.show || c.show(chatContext))),
7587
7777
  [commands, chatContext]
7588
7778
  );
7589
- const composerCommands = (0, import_react38.useMemo)(
7779
+ const composerCommands = (0, import_react39.useMemo)(
7590
7780
  () => commands.filter((c) => c.location === "composer" && (!c.show || c.show(chatContext))),
7591
7781
  [commands, chatContext]
7592
7782
  );
7593
- const threadMenuCommands = (0, import_react38.useMemo)(
7783
+ const threadMenuCommands = (0, import_react39.useMemo)(
7594
7784
  () => commands.filter((c) => c.location === "threadMenu" && (!c.show || c.show(chatContext))),
7595
7785
  [commands, chatContext]
7596
7786
  );
7597
- const chatState = (0, import_react38.useMemo)(() => ({
7787
+ const chatState = (0, import_react39.useMemo)(() => ({
7598
7788
  displayMessages: chat.displayMessages,
7599
7789
  visibleMessages: chat.displayMessages,
7600
7790
  isSubmitting: chat.isSubmitting,
@@ -7603,10 +7793,10 @@ function AIChat(inlineProps) {
7603
7793
  activeThread: chat.activeThread,
7604
7794
  renderedTitle: finalTitle,
7605
7795
  isTitleLoading: chat.isTitleLoading,
7606
- activeModel,
7796
+ activeModel: displayModel,
7607
7797
  activeProvider,
7608
- selectedModel,
7609
- setSelectedModel,
7798
+ selectedModel: displayModel,
7799
+ setSelectedModel: setUserSelectedModel,
7610
7800
  availableProviders,
7611
7801
  sendMessage: chat.sendMessage,
7612
7802
  startNewChat: chat.startNewChat,
@@ -7646,9 +7836,9 @@ function AIChat(inlineProps) {
7646
7836
  }), [
7647
7837
  chat,
7648
7838
  finalTitle,
7649
- activeModel,
7839
+ displayModel,
7650
7840
  activeProvider,
7651
- selectedModel,
7841
+ userSelectedModel,
7652
7842
  availableProviders,
7653
7843
  sidebarOpen,
7654
7844
  closeSidebar,
@@ -7661,16 +7851,16 @@ function AIChat(inlineProps) {
7661
7851
  checkout,
7662
7852
  user
7663
7853
  ]);
7664
- return /* @__PURE__ */ import_react38.default.createElement(ChatProvider, { value: chatState }, /* @__PURE__ */ import_react38.default.createElement(
7854
+ return /* @__PURE__ */ import_react39.default.createElement(ChatProvider, { value: chatState }, /* @__PURE__ */ import_react39.default.createElement(
7665
7855
  "section",
7666
7856
  {
7667
7857
  className: "brokr-ai-chat-shell",
7668
7858
  "data-sidebar": sidebarVisible,
7669
7859
  ref: shellRef
7670
7860
  },
7671
- sidebarVisible ? /* @__PURE__ */ import_react38.default.createElement("aside", { className: "brokr-ai-chat-sidebar brokr-ai-chat-sidebar-desktop" }, /* @__PURE__ */ import_react38.default.createElement(ThreadSidebar, null)) : null,
7672
- sidebarOpen ? /* @__PURE__ */ import_react38.default.createElement(import_react38.default.Fragment, null, /* @__PURE__ */ import_react38.default.createElement("div", { className: "brokr-ai-chat-drawer-backdrop", onClick: closeSidebar }), /* @__PURE__ */ import_react38.default.createElement("aside", { className: "brokr-ai-chat-drawer" }, /* @__PURE__ */ import_react38.default.createElement(ThreadSidebar, null))) : null,
7673
- /* @__PURE__ */ import_react38.default.createElement("div", { className: "brokr-ai-chat-stage", "data-noheader": !headerVisible }, headerVisible ? /* @__PURE__ */ import_react38.default.createElement(
7861
+ sidebarVisible ? /* @__PURE__ */ import_react39.default.createElement("aside", { className: "brokr-ai-chat-sidebar brokr-ai-chat-sidebar-desktop" }, /* @__PURE__ */ import_react39.default.createElement(ThreadSidebar, null)) : null,
7862
+ sidebarOpen ? /* @__PURE__ */ import_react39.default.createElement(import_react39.default.Fragment, null, /* @__PURE__ */ import_react39.default.createElement("div", { className: "brokr-ai-chat-drawer-backdrop", onClick: closeSidebar }), /* @__PURE__ */ import_react39.default.createElement("aside", { className: "brokr-ai-chat-drawer" }, /* @__PURE__ */ import_react39.default.createElement(ThreadSidebar, null))) : null,
7863
+ /* @__PURE__ */ import_react39.default.createElement("div", { className: "brokr-ai-chat-stage", "data-noheader": !headerVisible }, headerVisible ? /* @__PURE__ */ import_react39.default.createElement(
7674
7864
  ChatHeader,
7675
7865
  {
7676
7866
  activeId: chat.activeId,
@@ -7685,13 +7875,13 @@ function AIChat(inlineProps) {
7685
7875
  threadMenuRef,
7686
7876
  threadMenuVisible,
7687
7877
  setThreadMenuOpenId,
7688
- activeModel,
7689
- setSelectedModel,
7878
+ activeModel: displayModel,
7879
+ setSelectedModel: setUserSelectedModel,
7690
7880
  availableProviders,
7691
7881
  startRename: chatState.startRename,
7692
7882
  deleteThread: chat.deleteThread
7693
7883
  }
7694
- ) : null, /* @__PURE__ */ import_react38.default.createElement(
7884
+ ) : null, /* @__PURE__ */ import_react39.default.createElement(
7695
7885
  MessagePane,
7696
7886
  {
7697
7887
  starterPrompts,
@@ -7699,21 +7889,21 @@ function AIChat(inlineProps) {
7699
7889
  emptyCopy,
7700
7890
  subtitle
7701
7891
  }
7702
- ), /* @__PURE__ */ import_react38.default.createElement(ChatInput, null))
7892
+ ), /* @__PURE__ */ import_react39.default.createElement(ChatInput, null))
7703
7893
  ));
7704
7894
  }
7705
7895
  function CommandButton2({ cmd, chatContext }) {
7706
- const handleClick = (0, import_react38.useCallback)(() => {
7896
+ const handleClick = (0, import_react39.useCallback)(() => {
7707
7897
  void cmd.run(chatContext);
7708
7898
  }, [cmd, chatContext]);
7709
- return /* @__PURE__ */ import_react38.default.createElement("button", { className: "brokr-ai-chat-sidebar-button", onClick: handleClick, type: "button" }, cmd.text);
7899
+ return /* @__PURE__ */ import_react39.default.createElement("button", { className: "brokr-ai-chat-sidebar-button", onClick: handleClick, type: "button" }, cmd.text);
7710
7900
  }
7711
7901
  function MenuCommandItem({ cmd, chatContext, onClose }) {
7712
- const handleClick = (0, import_react38.useCallback)(() => {
7902
+ const handleClick = (0, import_react39.useCallback)(() => {
7713
7903
  onClose();
7714
7904
  void cmd.run(chatContext);
7715
7905
  }, [cmd, chatContext, onClose]);
7716
- return /* @__PURE__ */ import_react38.default.createElement("button", { className: "brokr-ai-chat-thread-dropdown-item", onClick: handleClick, type: "button" }, cmd.text);
7906
+ return /* @__PURE__ */ import_react39.default.createElement("button", { className: "brokr-ai-chat-thread-dropdown-item", onClick: handleClick, type: "button" }, cmd.text);
7717
7907
  }
7718
7908
  function ChatHeader({
7719
7909
  activeId,
@@ -7734,21 +7924,21 @@ function ChatHeader({
7734
7924
  startRename,
7735
7925
  deleteThread
7736
7926
  }) {
7737
- const handleOpenSidebar = (0, import_react38.useCallback)(() => setSidebarOpen(true), [setSidebarOpen]);
7738
- const handleToggleMenu = (0, import_react38.useCallback)(() => {
7927
+ const handleOpenSidebar = (0, import_react39.useCallback)(() => setSidebarOpen(true), [setSidebarOpen]);
7928
+ const handleToggleMenu = (0, import_react39.useCallback)(() => {
7739
7929
  setThreadMenuOpenId(threadMenuOpenId ? null : activeId);
7740
7930
  }, [setThreadMenuOpenId, threadMenuOpenId, activeId]);
7741
- const closeMenu = (0, import_react38.useCallback)(() => setThreadMenuOpenId(null), [setThreadMenuOpenId]);
7742
- const handleRename = (0, import_react38.useCallback)(() => {
7931
+ const closeMenu = (0, import_react39.useCallback)(() => setThreadMenuOpenId(null), [setThreadMenuOpenId]);
7932
+ const handleRename = (0, import_react39.useCallback)(() => {
7743
7933
  if (activeId) startRename(activeId);
7744
7934
  }, [activeId, startRename]);
7745
- const handleDelete = (0, import_react38.useCallback)(() => {
7935
+ const handleDelete = (0, import_react39.useCallback)(() => {
7746
7936
  if (activeId) {
7747
7937
  setThreadMenuOpenId(null);
7748
7938
  void deleteThread(activeId);
7749
7939
  }
7750
7940
  }, [activeId, deleteThread, setThreadMenuOpenId]);
7751
- 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(
7941
+ return /* @__PURE__ */ import_react39.default.createElement("header", { className: "brokr-ai-chat-topbar" }, /* @__PURE__ */ import_react39.default.createElement("div", { className: "brokr-ai-chat-topbar-left" }, sidebarVisible ? /* @__PURE__ */ import_react39.default.createElement(
7752
7942
  "button",
7753
7943
  {
7754
7944
  "aria-label": "Open sidebar",
@@ -7756,8 +7946,8 @@ function ChatHeader({
7756
7946
  onClick: handleOpenSidebar,
7757
7947
  type: "button"
7758
7948
  },
7759
- /* @__PURE__ */ import_react38.default.createElement(MenuIcon, { size: 18 })
7760
- ) : 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(
7949
+ /* @__PURE__ */ import_react39.default.createElement(MenuIcon, { size: 18 })
7950
+ ) : null), /* @__PURE__ */ import_react39.default.createElement("div", { className: "brokr-ai-chat-topbar-actions" }, headerCommands.map((cmd) => /* @__PURE__ */ import_react39.default.createElement(CommandButton2, { key: cmd.id, cmd, chatContext })), modelSelectorVisible ? /* @__PURE__ */ import_react39.default.createElement(
7761
7951
  ModelSelector,
7762
7952
  {
7763
7953
  activeModel,
@@ -7765,7 +7955,7 @@ function ChatHeader({
7765
7955
  checkout,
7766
7956
  setSelectedModel
7767
7957
  }
7768
- ) : null, activeId && threadMenuVisible ? /* @__PURE__ */ import_react38.default.createElement("div", { className: "brokr-ai-chat-thread-menu-wrap", ref: threadMenuRef }, /* @__PURE__ */ import_react38.default.createElement(
7958
+ ) : null, activeId && threadMenuVisible ? /* @__PURE__ */ import_react39.default.createElement("div", { className: "brokr-ai-chat-thread-menu-wrap", ref: threadMenuRef }, /* @__PURE__ */ import_react39.default.createElement(
7769
7959
  "button",
7770
7960
  {
7771
7961
  "aria-label": "Thread actions",
@@ -7773,17 +7963,17 @@ function ChatHeader({
7773
7963
  onClick: handleToggleMenu,
7774
7964
  type: "button"
7775
7965
  },
7776
- /* @__PURE__ */ import_react38.default.createElement(MoreHorizontalIcon, { size: 14 })
7777
- ), threadMenuOpenId ? /* @__PURE__ */ import_react38.default.createElement("div", { className: "brokr-ai-chat-thread-dropdown" }, /* @__PURE__ */ import_react38.default.createElement(
7966
+ /* @__PURE__ */ import_react39.default.createElement(MoreHorizontalIcon, { size: 14 })
7967
+ ), threadMenuOpenId ? /* @__PURE__ */ import_react39.default.createElement("div", { className: "brokr-ai-chat-thread-dropdown" }, /* @__PURE__ */ import_react39.default.createElement(
7778
7968
  "button",
7779
7969
  {
7780
7970
  className: "brokr-ai-chat-thread-dropdown-item",
7781
7971
  onClick: handleRename,
7782
7972
  type: "button"
7783
7973
  },
7784
- /* @__PURE__ */ import_react38.default.createElement(PencilIcon, { size: 13 }),
7974
+ /* @__PURE__ */ import_react39.default.createElement(PencilIcon, { size: 13 }),
7785
7975
  "Rename"
7786
- ), threadMenuCommands.map((cmd) => /* @__PURE__ */ import_react38.default.createElement(
7976
+ ), threadMenuCommands.map((cmd) => /* @__PURE__ */ import_react39.default.createElement(
7787
7977
  MenuCommandItem,
7788
7978
  {
7789
7979
  key: cmd.id,
@@ -7791,7 +7981,7 @@ function ChatHeader({
7791
7981
  chatContext,
7792
7982
  onClose: closeMenu
7793
7983
  }
7794
- )), /* @__PURE__ */ import_react38.default.createElement(
7984
+ )), /* @__PURE__ */ import_react39.default.createElement(
7795
7985
  "button",
7796
7986
  {
7797
7987
  className: "brokr-ai-chat-thread-dropdown-item",
@@ -7799,118 +7989,196 @@ function ChatHeader({
7799
7989
  onClick: handleDelete,
7800
7990
  type: "button"
7801
7991
  },
7802
- /* @__PURE__ */ import_react38.default.createElement(TrashIcon, { size: 13 }),
7992
+ /* @__PURE__ */ import_react39.default.createElement(TrashIcon, { size: 13 }),
7803
7993
  "Delete"
7804
7994
  )) : null) : null));
7805
7995
  }
7806
7996
 
7807
7997
  // src/react/composites/FabAI.tsx
7808
- var import_react39 = __toESM(require("react"));
7809
- function StarterButton({ prompt, onSelect }) {
7810
- const handleClick = (0, import_react39.useCallback)(() => onSelect(prompt), [prompt, onSelect]);
7811
- return /* @__PURE__ */ import_react39.default.createElement("button", { className: "brokr-chat-starter", onClick: handleClick, type: "button" }, prompt);
7998
+ var import_react40 = __toESM(require("react"));
7999
+
8000
+ // src/react/composites/fab-context.ts
8001
+ function buildFabSystemPrompt(appContext, brandName, existingPrompt) {
8002
+ if (appContext === false) return existingPrompt;
8003
+ const name = appContext?.name ?? brandName;
8004
+ if (!name && !appContext) return existingPrompt;
8005
+ const parts = [];
8006
+ if (name) parts.push(`You are an AI assistant embedded in ${name}.`);
8007
+ if (appContext?.description) parts.push(`This app is ${appContext.description}.`);
8008
+ if (appContext?.currentPage) parts.push(`The user is currently on: ${appContext.currentPage}.`);
8009
+ if (appContext?.facts?.length) parts.push(...appContext.facts);
8010
+ const autoPrompt = parts.join(" ");
8011
+ return existingPrompt ? `${autoPrompt}
8012
+
8013
+ ${existingPrompt}` : autoPrompt;
8014
+ }
8015
+
8016
+ // src/react/composites/FabAI.tsx
8017
+ function ensureAssistantReply(messages, content) {
8018
+ const normalized = content.trim() ? content : "No response received.";
8019
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
8020
+ if (messages[index]?.role !== "assistant") continue;
8021
+ const next = [...messages];
8022
+ next[index] = { ...next[index], content: normalized };
8023
+ return next;
8024
+ }
8025
+ return [...messages, { role: "assistant", content: normalized }];
8026
+ }
8027
+ function appendAssistantDelta(messages, delta) {
8028
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
8029
+ if (messages[index]?.role !== "assistant") continue;
8030
+ const next = [...messages];
8031
+ next[index] = {
8032
+ ...next[index],
8033
+ content: `${contentToText(next[index].content)}${delta}`
8034
+ };
8035
+ return next;
8036
+ }
8037
+ return [...messages, { role: "assistant", content: delta }];
7812
8038
  }
7813
8039
  function FabAI({
8040
+ appContext,
7814
8041
  model,
8042
+ memory,
7815
8043
  onSendMessage,
7816
8044
  position = "bottom-right",
7817
- starterPrompts = [],
7818
8045
  systemPrompt
7819
8046
  }) {
7820
- const { can, user } = useBrokr();
7821
- const [isOpen, setIsOpen] = (0, import_react39.useState)(false);
7822
- const [input, setInput] = (0, import_react39.useState)("");
7823
- const [error, setError] = (0, import_react39.useState)(null);
7824
- const [isSending, setIsSending] = (0, import_react39.useState)(false);
7825
- const [messages, setMessages] = (0, import_react39.useState)([]);
7826
- const launcherStyle = (0, import_react39.useMemo)(
8047
+ const { can, user, theme } = useBrokr();
8048
+ const brandName = theme?.brand?.name;
8049
+ const [isOpen, setIsOpen] = (0, import_react40.useState)(false);
8050
+ const [input, setInput] = (0, import_react40.useState)("");
8051
+ const [error, setError] = (0, import_react40.useState)(null);
8052
+ const [isSending, setIsSending] = (0, import_react40.useState)(false);
8053
+ const [messages, setMessages] = (0, import_react40.useState)([]);
8054
+ const conversationIdRef = (0, import_react40.useRef)(`fab_${crypto.randomUUID()}`);
8055
+ const launcherStyle = (0, import_react40.useMemo)(
7827
8056
  () => ({
7828
8057
  left: position === "bottom-left" ? "var(--brokr-space-6)" : void 0,
7829
8058
  right: position === "bottom-right" ? "var(--brokr-space-6)" : void 0
7830
8059
  }),
7831
8060
  [position]
7832
8061
  );
7833
- const canChat = (0, import_react39.useMemo)(() => can("ai.chat"), [can]);
7834
- const toggleOpen = (0, import_react39.useCallback)(() => {
8062
+ const canChat = (0, import_react40.useMemo)(() => can("ai.chat"), [can]);
8063
+ const toggleOpen = (0, import_react40.useCallback)(() => {
7835
8064
  if (!canChat) {
7836
8065
  redirectTo("/pricing");
7837
8066
  return;
7838
8067
  }
7839
8068
  setIsOpen((current) => !current);
7840
8069
  }, [canChat]);
7841
- const handleInputChange = (0, import_react39.useCallback)((event) => {
8070
+ const handleInputChange = (0, import_react40.useCallback)((event) => {
7842
8071
  setInput(event.target.value);
7843
8072
  }, []);
7844
- const handleClose = (0, import_react39.useCallback)(() => {
8073
+ const handleClose = (0, import_react40.useCallback)(() => {
7845
8074
  setIsOpen(false);
7846
8075
  }, []);
7847
- const sendPrompt = (0, import_react39.useCallback)(async (prompt) => {
8076
+ const sendPrompt = (0, import_react40.useCallback)(async (prompt) => {
7848
8077
  const nextPrompt = prompt.trim();
7849
- if (!nextPrompt) return;
7850
- const nextMessages = [...messages, { role: "user", content: nextPrompt }];
8078
+ if (!nextPrompt || isSending) return;
8079
+ const userMessage = { role: "user", content: nextPrompt };
8080
+ const nextMessages = [...messages, userMessage];
8081
+ const optimisticMessages = [...nextMessages, { role: "assistant", content: "" }];
7851
8082
  try {
7852
8083
  setError(null);
7853
8084
  setIsSending(true);
7854
- setMessages(nextMessages);
8085
+ setMessages(optimisticMessages);
7855
8086
  setInput("");
7856
- let responseText = "";
7857
8087
  if (onSendMessage) {
7858
- responseText = await onSendMessage({ messages: nextMessages, model, systemPrompt });
7859
- } else {
7860
- const payload = await postJson(
7861
- "/api/brokr/chat",
7862
- {
7863
- messages: nextMessages,
7864
- model,
7865
- systemPrompt
8088
+ const responseText2 = await onSendMessage({ messages: nextMessages, model, systemPrompt });
8089
+ setMessages((current) => ensureAssistantReply(current, responseText2));
8090
+ return;
8091
+ }
8092
+ const response = await fetch("/api/brokr/chat", {
8093
+ method: "POST",
8094
+ credentials: "include",
8095
+ headers: { "Content-Type": "application/json" },
8096
+ body: JSON.stringify({
8097
+ conversationId: conversationIdRef.current,
8098
+ messages: trimToTokenBudget(
8099
+ nextMessages.filter((message) => message.role === "user" || message.role === "assistant" && Boolean(contentToText(message.content))).map((message) => ({
8100
+ role: message.role,
8101
+ content: contentToText(message.content)
8102
+ }))
8103
+ ),
8104
+ ...model !== void 0 ? { model } : {},
8105
+ ...(() => {
8106
+ const withContext = buildFabSystemPrompt(appContext, brandName, systemPrompt);
8107
+ const ep = buildEffectivePrompt(withContext, memory);
8108
+ return ep ? { systemPrompt: ep } : {};
8109
+ })()
8110
+ })
8111
+ });
8112
+ if (!response.ok) {
8113
+ const payload2 = await response.json().catch(() => ({}));
8114
+ throw new Error(payload2.message ?? payload2.error ?? `Chat failed (${response.status})`);
8115
+ }
8116
+ if (isSSEResponse(response)) {
8117
+ let hasDelta = false;
8118
+ for await (const event of parseSSEStream(response)) {
8119
+ if (event.type === "conversation") {
8120
+ conversationIdRef.current = event.id;
8121
+ } else if (event.type === "delta") {
8122
+ hasDelta = true;
8123
+ setMessages((current) => appendAssistantDelta(current, event.delta));
8124
+ } else if (event.type === "error") {
8125
+ throw new Error(event.message);
7866
8126
  }
7867
- );
7868
- responseText = payload.content ?? payload.response ?? "";
8127
+ }
8128
+ if (!hasDelta) {
8129
+ setMessages((current) => ensureAssistantReply(current, "No response received."));
8130
+ }
8131
+ return;
7869
8132
  }
7870
- setMessages((current) => [...current, {
7871
- role: "assistant",
7872
- content: responseText || "No response received."
7873
- }]);
8133
+ const payload = await response.json().catch(() => ({}));
8134
+ if (payload.error || payload.message) {
8135
+ throw new Error(payload.message ?? payload.error ?? "AI request failed.");
8136
+ }
8137
+ const responseText = payload.content ?? payload.response ?? payload.text ?? "";
8138
+ setMessages((current) => ensureAssistantReply(current, responseText));
7874
8139
  } catch (cause) {
7875
- setError(cause instanceof Error ? cause.message : "Could not send message.");
8140
+ const message = cause instanceof Error ? cause.message : "Could not send message.";
8141
+ setError(message);
8142
+ setMessages((current) => ensureAssistantReply(current, `Error: ${message}`));
7876
8143
  } finally {
7877
8144
  setIsSending(false);
7878
8145
  }
7879
- }, [messages, model, onSendMessage, systemPrompt]);
7880
- const handleSubmit = (0, import_react39.useCallback)(async (event) => {
8146
+ }, [isSending, messages, model, onSendMessage, systemPrompt]);
8147
+ const handleSubmit = (0, import_react40.useCallback)(async (event) => {
7881
8148
  event.preventDefault();
7882
8149
  await sendPrompt(input);
7883
8150
  }, [input, sendPrompt]);
7884
- const handleKeyDown = (0, import_react39.useCallback)((event) => {
8151
+ const handleKeyDown = (0, import_react40.useCallback)((event) => {
7885
8152
  if (event.key === "Enter" && !event.shiftKey) {
7886
8153
  event.preventDefault();
7887
8154
  void sendPrompt(input);
7888
8155
  }
7889
8156
  }, [input, sendPrompt]);
7890
- const handleStarterPrompt = (0, import_react39.useCallback)((prompt) => {
7891
- void sendPrompt(prompt);
7892
- }, [sendPrompt]);
7893
- 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(
8157
+ return /* @__PURE__ */ import_react40.default.createElement(import_react40.default.Fragment, null, /* @__PURE__ */ import_react40.default.createElement("div", { className: "brokr-chat-fab", style: launcherStyle }, /* @__PURE__ */ import_react40.default.createElement(
7894
8158
  "button",
7895
8159
  {
8160
+ "aria-label": isOpen ? "Close AI chat" : "Open AI chat",
7896
8161
  "aria-expanded": isOpen,
7897
8162
  "aria-haspopup": "dialog",
7898
- className: "brokr-button",
8163
+ className: "brokr-chat-fab-trigger",
7899
8164
  onClick: toggleOpen,
7900
8165
  type: "button"
7901
8166
  },
7902
- /* @__PURE__ */ import_react39.default.createElement(MessageIcon, { size: 16 }),
7903
- "Ask AI"
7904
- )), 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(
8167
+ /* @__PURE__ */ import_react40.default.createElement(SparkIcon, { size: 18 })
8168
+ )), isOpen ? /* @__PURE__ */ import_react40.default.createElement("div", { className: "brokr-panel brokr-chat-panel", role: "dialog" }, /* @__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, "AI Chat"), user?.name ? /* @__PURE__ */ import_react40.default.createElement("span", { className: "brokr-copy" }, user.name) : null), /* @__PURE__ */ import_react40.default.createElement("button", { className: "brokr-button-ghost", onClick: handleClose, type: "button" }, /* @__PURE__ */ import_react40.default.createElement(CloseIcon, { size: 16 }))), /* @__PURE__ */ import_react40.default.createElement(
7905
8169
  "div",
7906
8170
  {
7907
8171
  className: "brokr-chat-messages",
7908
8172
  "data-empty": messages.length === 0
7909
8173
  },
7910
- 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.")), starterPrompts.length > 0 ? /* @__PURE__ */ import_react39.default.createElement("div", { className: "brokr-chat-starters" }, starterPrompts.map((prompt) => /* @__PURE__ */ import_react39.default.createElement(StarterButton, { key: prompt, prompt, onSelect: handleStarterPrompt }))) : null) : null,
7911
- 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))),
7912
- error ? /* @__PURE__ */ import_react39.default.createElement("div", { className: "brokr-inline-message", "data-tone": "error" }, error) : null
7913
- ), /* @__PURE__ */ import_react39.default.createElement("form", { className: "brokr-section", onSubmit: handleSubmit, style: { gap: "var(--brokr-space-3)" } }, /* @__PURE__ */ import_react39.default.createElement(
8174
+ messages.length === 0 ? /* @__PURE__ */ import_react40.default.createElement("div", { className: "brokr-chat-empty" }, /* @__PURE__ */ import_react40.default.createElement(SparkIcon, { size: 18 }), /* @__PURE__ */ import_react40.default.createElement("div", { className: "brokr-section", style: { gap: "var(--brokr-space-1)" } }, /* @__PURE__ */ import_react40.default.createElement("strong", null, "Send a message to chat with the AI."), /* @__PURE__ */ import_react40.default.createElement("span", { className: "brokr-copy" }, "Ask a question or drop in a starter prompt below."))) : null,
8175
+ messages.map((message, index) => {
8176
+ const text = contentToText(message.content);
8177
+ const isTyping = message.role === "assistant" && !text && isSending && index === messages.length - 1;
8178
+ return /* @__PURE__ */ import_react40.default.createElement("div", { className: "brokr-chat-bubble", "data-role": message.role, key: `${message.role}-${index}` }, isTyping ? /* @__PURE__ */ import_react40.default.createElement("div", { className: "brokr-ai-chat-typing", "aria-label": "AI is typing" }, /* @__PURE__ */ import_react40.default.createElement("span", null), /* @__PURE__ */ import_react40.default.createElement("span", null), /* @__PURE__ */ import_react40.default.createElement("span", null)) : message.role === "assistant" ? /* @__PURE__ */ import_react40.default.createElement(MarkdownRenderer, { content: text }) : text);
8179
+ }),
8180
+ error ? /* @__PURE__ */ import_react40.default.createElement("div", { className: "brokr-inline-message", "data-tone": "error" }, error) : null
8181
+ ), /* @__PURE__ */ import_react40.default.createElement("form", { className: "brokr-section", onSubmit: handleSubmit, style: { gap: "var(--brokr-space-3)" } }, /* @__PURE__ */ import_react40.default.createElement(
7914
8182
  "textarea",
7915
8183
  {
7916
8184
  className: "brokr-textarea brokr-chat-input",
@@ -7920,11 +8188,11 @@ function FabAI({
7920
8188
  rows: 2,
7921
8189
  value: input
7922
8190
  }
7923
- ), /* @__PURE__ */ import_react39.default.createElement("button", { className: "brokr-button", disabled: isSending, type: "submit" }, isSending ? "Thinking" : "Send"))) : null);
8191
+ ), /* @__PURE__ */ import_react40.default.createElement("button", { className: "brokr-button", disabled: isSending || !input.trim(), type: "submit" }, isSending ? "Thinking" : "Send"))) : null);
7924
8192
  }
7925
8193
 
7926
8194
  // src/react/composites/SmartUpload.tsx
7927
- var import_react40 = __toESM(require("react"));
8195
+ var import_react41 = __toESM(require("react"));
7928
8196
  function SmartUpload({
7929
8197
  accept,
7930
8198
  maxSize = 500 * 1024 * 1024,
@@ -7932,17 +8200,17 @@ function SmartUpload({
7932
8200
  purpose = "general"
7933
8201
  }) {
7934
8202
  const { paymentsMode } = useBrokr();
7935
- const inputId = (0, import_react40.useId)();
7936
- const inputRef = (0, import_react40.useRef)(null);
7937
- const [dragActive, setDragActive] = (0, import_react40.useState)(false);
7938
- const [error, setError] = (0, import_react40.useState)(null);
7939
- const [fileName, setFileName] = (0, import_react40.useState)(null);
7940
- const [progress, setProgress] = (0, import_react40.useState)(0);
7941
- const [isUploading, setIsUploading] = (0, import_react40.useState)(false);
7942
- const helperText = (0, import_react40.useMemo)(() => {
8203
+ const inputId = (0, import_react41.useId)();
8204
+ const inputRef = (0, import_react41.useRef)(null);
8205
+ const [dragActive, setDragActive] = (0, import_react41.useState)(false);
8206
+ const [error, setError] = (0, import_react41.useState)(null);
8207
+ const [fileName, setFileName] = (0, import_react41.useState)(null);
8208
+ const [progress, setProgress] = (0, import_react41.useState)(0);
8209
+ const [isUploading, setIsUploading] = (0, import_react41.useState)(false);
8210
+ const helperText = (0, import_react41.useMemo)(() => {
7943
8211
  return `${Math.round(maxSize / (1024 * 1024))} MB max file size.`;
7944
8212
  }, [maxSize]);
7945
- const beginUpload = (0, import_react40.useCallback)((file) => {
8213
+ const beginUpload = (0, import_react41.useCallback)((file) => {
7946
8214
  if (file.size > maxSize) {
7947
8215
  setError(`That file is larger than ${Math.round(maxSize / (1024 * 1024))} MB.`);
7948
8216
  return;
@@ -7979,31 +8247,31 @@ function SmartUpload({
7979
8247
  body.append("purpose", purpose);
7980
8248
  request.send(body);
7981
8249
  }, [maxSize, onUpload, purpose]);
7982
- const handleInputChange = (0, import_react40.useCallback)((event) => {
8250
+ const handleInputChange = (0, import_react41.useCallback)((event) => {
7983
8251
  const file = event.target.files?.[0];
7984
8252
  event.target.value = "";
7985
8253
  if (!file) return;
7986
8254
  beginUpload(file);
7987
8255
  }, [beginUpload]);
7988
- const handleDrop = (0, import_react40.useCallback)((event) => {
8256
+ const handleDrop = (0, import_react41.useCallback)((event) => {
7989
8257
  event.preventDefault();
7990
8258
  setDragActive(false);
7991
8259
  const file = event.dataTransfer.files?.[0];
7992
8260
  if (!file) return;
7993
8261
  beginUpload(file);
7994
8262
  }, [beginUpload]);
7995
- const handleDragEnter = (0, import_react40.useCallback)((event) => {
8263
+ const handleDragEnter = (0, import_react41.useCallback)((event) => {
7996
8264
  event.preventDefault();
7997
8265
  setDragActive(true);
7998
8266
  }, []);
7999
- const handleDragLeave = (0, import_react40.useCallback)((event) => {
8267
+ const handleDragLeave = (0, import_react41.useCallback)((event) => {
8000
8268
  event.preventDefault();
8001
8269
  setDragActive(false);
8002
8270
  }, []);
8003
- const handleBrowse = (0, import_react40.useCallback)(() => {
8271
+ const handleBrowse = (0, import_react41.useCallback)(() => {
8004
8272
  inputRef.current?.click();
8005
8273
  }, []);
8006
- 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(
8274
+ return /* @__PURE__ */ import_react41.default.createElement("div", { className: "brokr-card brokr-upload-shell" }, /* @__PURE__ */ import_react41.default.createElement("div", { className: "brokr-brand-row", style: { justifyContent: "space-between" } }, /* @__PURE__ */ import_react41.default.createElement("div", { className: "brokr-section", style: { gap: "var(--brokr-space-1)" } }, /* @__PURE__ */ import_react41.default.createElement("strong", null, "Upload files"), /* @__PURE__ */ import_react41.default.createElement("span", { className: "brokr-copy" }, "Drop a file and let Brokr handle the boring part.")), paymentsMode === "sandbox" ? /* @__PURE__ */ import_react41.default.createElement("span", { className: "brokr-badge brokr-badge-sandbox" }, "Sandbox") : null), /* @__PURE__ */ import_react41.default.createElement(
8007
8275
  "label",
8008
8276
  {
8009
8277
  className: "brokr-upload-dropzone",
@@ -8014,11 +8282,11 @@ function SmartUpload({
8014
8282
  onDragOver: handleDragEnter,
8015
8283
  onDrop: handleDrop
8016
8284
  },
8017
- /* @__PURE__ */ import_react40.default.createElement(UploadIcon, { size: 24 }),
8018
- /* @__PURE__ */ import_react40.default.createElement("strong", null, "Drag and drop a file here or choose one"),
8019
- /* @__PURE__ */ import_react40.default.createElement("span", { className: "brokr-copy" }, helperText),
8020
- /* @__PURE__ */ import_react40.default.createElement("button", { className: "brokr-button-secondary", onClick: handleBrowse, type: "button" }, "Choose file")
8021
- ), /* @__PURE__ */ import_react40.default.createElement(
8285
+ /* @__PURE__ */ import_react41.default.createElement(UploadIcon, { size: 24 }),
8286
+ /* @__PURE__ */ import_react41.default.createElement("strong", null, "Drag and drop a file here or choose one"),
8287
+ /* @__PURE__ */ import_react41.default.createElement("span", { className: "brokr-copy" }, helperText),
8288
+ /* @__PURE__ */ import_react41.default.createElement("button", { className: "brokr-button-secondary", onClick: handleBrowse, type: "button" }, "Choose file")
8289
+ ), /* @__PURE__ */ import_react41.default.createElement(
8022
8290
  "input",
8023
8291
  {
8024
8292
  accept,
@@ -8028,31 +8296,31 @@ function SmartUpload({
8028
8296
  ref: inputRef,
8029
8297
  type: "file"
8030
8298
  }
8031
- ), 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);
8299
+ ), fileName ? /* @__PURE__ */ import_react41.default.createElement("div", { className: "brokr-card brokr-upload-file" }, /* @__PURE__ */ import_react41.default.createElement("div", { className: "brokr-section", style: { gap: "var(--brokr-space-1)" } }, /* @__PURE__ */ import_react41.default.createElement("strong", null, fileName), /* @__PURE__ */ import_react41.default.createElement("span", { className: "brokr-copy" }, isUploading ? `Uploading ${progress}%` : progress === 100 ? "Processed" : "Queued")), /* @__PURE__ */ import_react41.default.createElement("div", { className: "brokr-meter-bar" }, /* @__PURE__ */ import_react41.default.createElement("div", { className: "brokr-meter-fill", style: { width: `${progress}%` } }))) : null, error ? /* @__PURE__ */ import_react41.default.createElement("div", { className: "brokr-inline-message", "data-tone": "error" }, error) : null);
8032
8300
  }
8033
8301
 
8034
8302
  // src/react/composites/FeedbackWidget.tsx
8035
- var import_react41 = __toESM(require("react"));
8303
+ var import_react42 = __toESM(require("react"));
8036
8304
  function FeedbackWidget({
8037
8305
  context,
8038
8306
  onSubmit
8039
8307
  }) {
8040
8308
  const { user } = useBrokr();
8041
- const [rating, setRating] = (0, import_react41.useState)(null);
8042
- const [text, setText] = (0, import_react41.useState)("");
8043
- const [error, setError] = (0, import_react41.useState)(null);
8044
- const [message, setMessage] = (0, import_react41.useState)(null);
8045
- const [isPending, setIsPending] = (0, import_react41.useState)(false);
8046
- const handleTextChange = (0, import_react41.useCallback)((event) => {
8309
+ const [rating, setRating] = (0, import_react42.useState)(null);
8310
+ const [text, setText] = (0, import_react42.useState)("");
8311
+ const [error, setError] = (0, import_react42.useState)(null);
8312
+ const [message, setMessage] = (0, import_react42.useState)(null);
8313
+ const [isPending, setIsPending] = (0, import_react42.useState)(false);
8314
+ const handleTextChange = (0, import_react42.useCallback)((event) => {
8047
8315
  setText(event.target.value);
8048
8316
  }, []);
8049
- const handleRateUp = (0, import_react41.useCallback)(() => {
8317
+ const handleRateUp = (0, import_react42.useCallback)(() => {
8050
8318
  setRating("up");
8051
8319
  }, []);
8052
- const handleRateDown = (0, import_react41.useCallback)(() => {
8320
+ const handleRateDown = (0, import_react42.useCallback)(() => {
8053
8321
  setRating("down");
8054
8322
  }, []);
8055
- const handleSubmit = (0, import_react41.useCallback)(async (event) => {
8323
+ const handleSubmit = (0, import_react42.useCallback)(async (event) => {
8056
8324
  event.preventDefault();
8057
8325
  if (!rating) {
8058
8326
  setError("Choose a direction first.");
@@ -8082,7 +8350,7 @@ function FeedbackWidget({
8082
8350
  setIsPending(false);
8083
8351
  }
8084
8352
  }, [context, onSubmit, rating, text, user?.id]);
8085
- return /* @__PURE__ */ import_react41.default.createElement("form", { className: "brokr-card brokr-feedback-shell", onSubmit: handleSubmit }, /* @__PURE__ */ import_react41.default.createElement("div", { className: "brokr-section", style: { gap: "0.5rem" } }, /* @__PURE__ */ import_react41.default.createElement("strong", null, "How did this feel?"), /* @__PURE__ */ import_react41.default.createElement("p", { className: "brokr-copy" }, "Tight signal only. Tell us what helped or what felt off.")), /* @__PURE__ */ import_react41.default.createElement("div", { className: "brokr-feedback-rating" }, /* @__PURE__ */ import_react41.default.createElement(
8353
+ return /* @__PURE__ */ import_react42.default.createElement("form", { className: "brokr-card brokr-feedback-shell", onSubmit: handleSubmit }, /* @__PURE__ */ import_react42.default.createElement("div", { className: "brokr-section", style: { gap: "0.5rem" } }, /* @__PURE__ */ import_react42.default.createElement("strong", null, "How did this feel?"), /* @__PURE__ */ import_react42.default.createElement("p", { className: "brokr-copy" }, "Tight signal only. Tell us what helped or what felt off.")), /* @__PURE__ */ import_react42.default.createElement("div", { className: "brokr-feedback-rating" }, /* @__PURE__ */ import_react42.default.createElement(
8086
8354
  "button",
8087
8355
  {
8088
8356
  className: "brokr-rating-button",
@@ -8091,7 +8359,7 @@ function FeedbackWidget({
8091
8359
  type: "button"
8092
8360
  },
8093
8361
  "This worked"
8094
- ), /* @__PURE__ */ import_react41.default.createElement(
8362
+ ), /* @__PURE__ */ import_react42.default.createElement(
8095
8363
  "button",
8096
8364
  {
8097
8365
  className: "brokr-rating-button",
@@ -8100,7 +8368,7 @@ function FeedbackWidget({
8100
8368
  type: "button"
8101
8369
  },
8102
8370
  "Needs work"
8103
- )), /* @__PURE__ */ import_react41.default.createElement(
8371
+ )), /* @__PURE__ */ import_react42.default.createElement(
8104
8372
  "textarea",
8105
8373
  {
8106
8374
  className: "brokr-textarea",
@@ -8109,11 +8377,11 @@ function FeedbackWidget({
8109
8377
  rows: 4,
8110
8378
  value: text
8111
8379
  }
8112
- ), error ? /* @__PURE__ */ import_react41.default.createElement("div", { className: "brokr-inline-message", "data-tone": "error" }, error) : null, message ? /* @__PURE__ */ import_react41.default.createElement("div", { className: "brokr-inline-message" }, message) : null, /* @__PURE__ */ import_react41.default.createElement("button", { className: "brokr-button", disabled: isPending, type: "submit" }, isPending ? "Sending" : "Send feedback"));
8380
+ ), error ? /* @__PURE__ */ import_react42.default.createElement("div", { className: "brokr-inline-message", "data-tone": "error" }, error) : null, message ? /* @__PURE__ */ import_react42.default.createElement("div", { className: "brokr-inline-message" }, message) : null, /* @__PURE__ */ import_react42.default.createElement("button", { className: "brokr-button", disabled: isPending, type: "submit" }, isPending ? "Sending" : "Send feedback"));
8113
8381
  }
8114
8382
 
8115
8383
  // src/react/BrokrErrorBoundary.tsx
8116
- var import_react42 = __toESM(require("react"));
8384
+ var import_react43 = __toESM(require("react"));
8117
8385
 
8118
8386
  // src/fix-registry.ts
8119
8387
  var FIX_REGISTRY = {
@@ -8337,7 +8605,7 @@ var BrokrError = class extends Error {
8337
8605
  };
8338
8606
 
8339
8607
  // src/react/BrokrErrorBoundary.tsx
8340
- var BrokrErrorBoundary = class extends import_react42.default.Component {
8608
+ var BrokrErrorBoundary = class extends import_react43.default.Component {
8341
8609
  constructor() {
8342
8610
  super(...arguments);
8343
8611
  this.state = { hasError: false, error: null };
@@ -8349,11 +8617,11 @@ var BrokrErrorBoundary = class extends import_react42.default.Component {
8349
8617
  if (this.state.hasError) {
8350
8618
  if (this.props.fallback) return this.props.fallback;
8351
8619
  const message = this.state.error instanceof BrokrError ? this.state.error.toUserMessage() : "Something went wrong.";
8352
- return import_react42.default.createElement(
8620
+ return import_react43.default.createElement(
8353
8621
  "div",
8354
8622
  { style: { padding: 24, textAlign: "center" } },
8355
- import_react42.default.createElement("p", null, message),
8356
- import_react42.default.createElement(
8623
+ import_react43.default.createElement("p", null, message),
8624
+ import_react43.default.createElement(
8357
8625
  "button",
8358
8626
  {
8359
8627
  onClick: () => this.setState({ hasError: false, error: null }),
@@ -8367,13 +8635,13 @@ var BrokrErrorBoundary = class extends import_react42.default.Component {
8367
8635
  }
8368
8636
  };
8369
8637
 
8370
- // src/react/notifications/NotificationBell.tsx
8371
- var import_react44 = __toESM(require("react"));
8638
+ // src/react/notifications/ActivityFeed.tsx
8639
+ var import_react45 = __toESM(require("react"));
8372
8640
 
8373
8641
  // src/react/notifications/use-notifications.ts
8374
- var import_react43 = require("react");
8642
+ var import_react44 = require("react");
8375
8643
  function useNotifications() {
8376
- const ctx = (0, import_react43.useContext)(NotificationsContext);
8644
+ const ctx = (0, import_react44.useContext)(NotificationsContext);
8377
8645
  if (!ctx) {
8378
8646
  throw new Error(
8379
8647
  "useNotifications() requires <BrokrProvider notifications> or <BrokrProvider notifications={config}>. Pass the notifications prop to enable the notification system."
@@ -8382,7 +8650,7 @@ function useNotifications() {
8382
8650
  return ctx;
8383
8651
  }
8384
8652
 
8385
- // src/react/notifications/NotificationBell.tsx
8653
+ // src/react/notifications/ActivityFeed.tsx
8386
8654
  function timeAgo(iso) {
8387
8655
  const diff = Date.now() - new Date(iso).getTime();
8388
8656
  const mins = Math.floor(diff / 6e4);
@@ -8393,16 +8661,79 @@ function timeAgo(iso) {
8393
8661
  const days = Math.floor(hours / 24);
8394
8662
  return `${days}d ago`;
8395
8663
  }
8664
+ function FeedItem({
8665
+ item,
8666
+ formatter
8667
+ }) {
8668
+ if (formatter) {
8669
+ return /* @__PURE__ */ import_react45.default.createElement("div", { className: "brokr-feed-item" }, formatter(item));
8670
+ }
8671
+ return /* @__PURE__ */ import_react45.default.createElement("div", { className: "brokr-feed-item" }, /* @__PURE__ */ import_react45.default.createElement("span", { className: `brokr-feed-dot brokr-feed-dot--${item.variant}` }), /* @__PURE__ */ import_react45.default.createElement("div", { className: "brokr-feed-content" }, /* @__PURE__ */ import_react45.default.createElement("span", { className: "brokr-feed-title" }, item.title), item.message ? /* @__PURE__ */ import_react45.default.createElement("span", { className: "brokr-feed-message" }, item.message) : null), /* @__PURE__ */ import_react45.default.createElement("span", { className: "brokr-feed-time" }, timeAgo(item.createdAt)));
8672
+ }
8673
+ function ActivityFeed({
8674
+ filter,
8675
+ maxItems = 20,
8676
+ formatters,
8677
+ emptyState
8678
+ }) {
8679
+ const { notifications, isLoading } = useNotifications();
8680
+ const items = (0, import_react45.useMemo)(() => {
8681
+ let filtered = notifications;
8682
+ if (filter && filter.length > 0) {
8683
+ const set = new Set(filter);
8684
+ filtered = notifications.filter(
8685
+ (n) => set.has(n.variant) || n.type && set.has(n.type)
8686
+ );
8687
+ }
8688
+ return [...filtered].sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()).slice(0, maxItems);
8689
+ }, [notifications, filter, maxItems]);
8690
+ const getFormatter = (0, import_react45.useCallback)(
8691
+ (item) => {
8692
+ if (!formatters) return void 0;
8693
+ if (item.type && formatters[item.type]) return formatters[item.type];
8694
+ if (formatters[item.variant]) return formatters[item.variant];
8695
+ return void 0;
8696
+ },
8697
+ [formatters]
8698
+ );
8699
+ if (isLoading) {
8700
+ return /* @__PURE__ */ import_react45.default.createElement("div", { className: "brokr-feed" }, [1, 2, 3].map((i) => /* @__PURE__ */ import_react45.default.createElement("div", { className: "brokr-feed-item", key: i }, /* @__PURE__ */ import_react45.default.createElement("span", { className: "brokr-feed-dot" }), /* @__PURE__ */ import_react45.default.createElement("div", { className: "brokr-feed-content" }, /* @__PURE__ */ import_react45.default.createElement("span", { className: "brokr-skeleton", style: { width: "60%", height: 12 } }), /* @__PURE__ */ import_react45.default.createElement("span", { className: "brokr-skeleton", style: { width: "80%", height: 12 } })), /* @__PURE__ */ import_react45.default.createElement("span", { className: "brokr-skeleton", style: { width: 40, height: 12 } }))));
8701
+ }
8702
+ if (items.length === 0) {
8703
+ return /* @__PURE__ */ import_react45.default.createElement("div", { className: "brokr-feed" }, /* @__PURE__ */ import_react45.default.createElement("div", { className: "brokr-feed-empty" }, emptyState ?? /* @__PURE__ */ import_react45.default.createElement("span", { className: "brokr-copy" }, "No activity yet.")));
8704
+ }
8705
+ return /* @__PURE__ */ import_react45.default.createElement("div", { className: "brokr-feed" }, items.map((item) => /* @__PURE__ */ import_react45.default.createElement(
8706
+ FeedItem,
8707
+ {
8708
+ key: item.id,
8709
+ item,
8710
+ formatter: getFormatter(item)
8711
+ }
8712
+ )));
8713
+ }
8714
+
8715
+ // src/react/notifications/NotificationBell.tsx
8716
+ var import_react46 = __toESM(require("react"));
8717
+ function timeAgo2(iso) {
8718
+ const diff = Date.now() - new Date(iso).getTime();
8719
+ const mins = Math.floor(diff / 6e4);
8720
+ if (mins < 1) return "just now";
8721
+ if (mins < 60) return `${mins}m ago`;
8722
+ const hours = Math.floor(mins / 60);
8723
+ if (hours < 24) return `${hours}h ago`;
8724
+ const days = Math.floor(hours / 24);
8725
+ return `${days}d ago`;
8726
+ }
8396
8727
  function NotifDropdownItem({
8397
8728
  notif,
8398
8729
  registry,
8399
8730
  onClick
8400
8731
  }) {
8401
- const handleClick = (0, import_react44.useCallback)(() => onClick(notif), [notif, onClick]);
8732
+ const handleClick = (0, import_react46.useCallback)(() => onClick(notif), [notif, onClick]);
8402
8733
  const notifData = notif.data ?? {};
8403
8734
  const notifType = notifData.type ?? "default";
8404
8735
  const resolved = resolveNotificationType(registry, notifType, notifData);
8405
- return /* @__PURE__ */ import_react44.default.createElement(
8736
+ return /* @__PURE__ */ import_react46.default.createElement(
8406
8737
  "button",
8407
8738
  {
8408
8739
  type: "button",
@@ -8410,18 +8741,18 @@ function NotifDropdownItem({
8410
8741
  onClick: handleClick,
8411
8742
  role: "menuitem"
8412
8743
  },
8413
- 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}` }),
8414
- /* @__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)),
8415
- /* @__PURE__ */ import_react44.default.createElement("span", { className: "brokr-notif-item-time" }, timeAgo(notif.createdAt))
8744
+ resolved.image ? /* @__PURE__ */ import_react46.default.createElement("img", { src: resolved.image.url, alt: resolved.image.alt, className: "brokr-notif-item-logo" }) : /* @__PURE__ */ import_react46.default.createElement("span", { className: `brokr-notif-item-dot brokr-notif-item-dot--${notif.variant}` }),
8745
+ /* @__PURE__ */ import_react46.default.createElement("div", { className: "brokr-notif-item-body" }, /* @__PURE__ */ import_react46.default.createElement("span", { className: "brokr-notif-item-title" }, notif.title), /* @__PURE__ */ import_react46.default.createElement("span", { className: "brokr-notif-item-message" }, notif.message)),
8746
+ /* @__PURE__ */ import_react46.default.createElement("span", { className: "brokr-notif-item-time" }, timeAgo2(notif.createdAt))
8416
8747
  );
8417
8748
  }
8418
8749
  function NotificationBell() {
8419
8750
  const { notifications, unreadCount, markRead, markAllRead, isLoading, registry } = useNotifications();
8420
- const [open, setOpen] = (0, import_react44.useState)(false);
8421
- const containerRef = (0, import_react44.useRef)(null);
8422
- const markReadTimerRef = (0, import_react44.useRef)(null);
8423
- const toggle = (0, import_react44.useCallback)(() => setOpen((o) => !o), []);
8424
- (0, import_react44.useEffect)(() => {
8751
+ const [open, setOpen] = (0, import_react46.useState)(false);
8752
+ const containerRef = (0, import_react46.useRef)(null);
8753
+ const markReadTimerRef = (0, import_react46.useRef)(null);
8754
+ const toggle = (0, import_react46.useCallback)(() => setOpen((o) => !o), []);
8755
+ (0, import_react46.useEffect)(() => {
8425
8756
  if (markReadTimerRef.current) {
8426
8757
  clearTimeout(markReadTimerRef.current);
8427
8758
  markReadTimerRef.current = null;
@@ -8438,7 +8769,7 @@ function NotificationBell() {
8438
8769
  }
8439
8770
  };
8440
8771
  }, [open, unreadCount, markAllRead]);
8441
- (0, import_react44.useEffect)(() => {
8772
+ (0, import_react46.useEffect)(() => {
8442
8773
  if (!open) return;
8443
8774
  function handleClick(e) {
8444
8775
  if (containerRef.current && !containerRef.current.contains(e.target)) {
@@ -8448,7 +8779,7 @@ function NotificationBell() {
8448
8779
  document.addEventListener("mousedown", handleClick);
8449
8780
  return () => document.removeEventListener("mousedown", handleClick);
8450
8781
  }, [open]);
8451
- (0, import_react44.useEffect)(() => {
8782
+ (0, import_react46.useEffect)(() => {
8452
8783
  if (!open) return;
8453
8784
  function handleKey(e) {
8454
8785
  if (e.key === "Escape") setOpen(false);
@@ -8456,13 +8787,13 @@ function NotificationBell() {
8456
8787
  document.addEventListener("keydown", handleKey);
8457
8788
  return () => document.removeEventListener("keydown", handleKey);
8458
8789
  }, [open]);
8459
- const sorted = (0, import_react44.useMemo)(
8790
+ const sorted = (0, import_react46.useMemo)(
8460
8791
  () => [...notifications].sort(
8461
8792
  (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
8462
8793
  ),
8463
8794
  [notifications]
8464
8795
  );
8465
- const handleItemClick = (0, import_react44.useCallback)((notif) => {
8796
+ const handleItemClick = (0, import_react46.useCallback)((notif) => {
8466
8797
  if (!notif.read) markRead(notif.id);
8467
8798
  const notifData = notif.data ?? {};
8468
8799
  const notifType = notif.type ?? notifData.type ?? "default";
@@ -8472,7 +8803,7 @@ function NotificationBell() {
8472
8803
  window.location.assign(href);
8473
8804
  }
8474
8805
  }, [markRead, registry]);
8475
- return /* @__PURE__ */ import_react44.default.createElement("div", { className: "brokr-notif-bell-wrap", ref: containerRef }, /* @__PURE__ */ import_react44.default.createElement(
8806
+ return /* @__PURE__ */ import_react46.default.createElement("div", { className: "brokr-notif-bell-wrap", ref: containerRef }, /* @__PURE__ */ import_react46.default.createElement(
8476
8807
  "button",
8477
8808
  {
8478
8809
  type: "button",
@@ -8482,9 +8813,9 @@ function NotificationBell() {
8482
8813
  "aria-expanded": open,
8483
8814
  "aria-haspopup": "menu"
8484
8815
  },
8485
- /* @__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" })),
8486
- unreadCount > 0 && /* @__PURE__ */ import_react44.default.createElement("span", { className: "brokr-notif-badge" }, unreadCount > 99 ? "99+" : unreadCount)
8487
- ), open && /* @__PURE__ */ import_react44.default.createElement("div", { className: "brokr-notif-dropdown", role: "menu" }, /* @__PURE__ */ import_react44.default.createElement("div", { className: "brokr-notif-dropdown-header" }, /* @__PURE__ */ import_react44.default.createElement("span", { className: "brokr-notif-dropdown-title" }, "Notifications"), unreadCount > 0 && /* @__PURE__ */ import_react44.default.createElement(
8816
+ /* @__PURE__ */ import_react46.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_react46.default.createElement("path", { d: "M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9" }), /* @__PURE__ */ import_react46.default.createElement("path", { d: "M10.3 21a1.94 1.94 0 0 0 3.4 0" })),
8817
+ unreadCount > 0 && /* @__PURE__ */ import_react46.default.createElement("span", { className: "brokr-notif-badge" }, unreadCount > 99 ? "99+" : unreadCount)
8818
+ ), open && /* @__PURE__ */ import_react46.default.createElement("div", { className: "brokr-notif-dropdown", role: "menu" }, /* @__PURE__ */ import_react46.default.createElement("div", { className: "brokr-notif-dropdown-header" }, /* @__PURE__ */ import_react46.default.createElement("span", { className: "brokr-notif-dropdown-title" }, "Notifications"), unreadCount > 0 && /* @__PURE__ */ import_react46.default.createElement(
8488
8819
  "button",
8489
8820
  {
8490
8821
  type: "button",
@@ -8492,7 +8823,7 @@ function NotificationBell() {
8492
8823
  onClick: markAllRead
8493
8824
  },
8494
8825
  "Mark all read"
8495
- )), /* @__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(
8826
+ )), /* @__PURE__ */ import_react46.default.createElement("div", { className: "brokr-notif-dropdown-list" }, isLoading ? /* @__PURE__ */ import_react46.default.createElement("div", { className: "brokr-notif-empty" }, /* @__PURE__ */ import_react46.default.createElement("span", { className: "brokr-notif-empty-text" }, "Loading\u2026")) : sorted.length === 0 ? /* @__PURE__ */ import_react46.default.createElement("div", { className: "brokr-notif-empty" }, /* @__PURE__ */ import_react46.default.createElement("span", { className: "brokr-notif-empty-text" }, "No notifications yet")) : sorted.map((notif) => /* @__PURE__ */ import_react46.default.createElement(
8496
8827
  NotifDropdownItem,
8497
8828
  {
8498
8829
  key: notif.id,
@@ -8504,7 +8835,7 @@ function NotificationBell() {
8504
8835
  }
8505
8836
 
8506
8837
  // src/react/notifications/NotificationList.tsx
8507
- var import_react45 = __toESM(require("react"));
8838
+ var import_react47 = __toESM(require("react"));
8508
8839
  function formatTimestamp(iso) {
8509
8840
  const date = new Date(iso);
8510
8841
  return new Intl.DateTimeFormat("en-US", {
@@ -8515,38 +8846,38 @@ function formatTimestamp(iso) {
8515
8846
  }).format(date);
8516
8847
  }
8517
8848
  function NotificationListSkeleton() {
8518
- 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 } }))));
8849
+ return /* @__PURE__ */ import_react47.default.createElement("div", { className: "brokr-notif-list-items" }, [1, 2, 3].map((i) => /* @__PURE__ */ import_react47.default.createElement("div", { key: i, className: "brokr-notif-list-row brokr-notif-list-row--skeleton" }, /* @__PURE__ */ import_react47.default.createElement("span", { className: "brokr-notif-item-dot brokr-notif-item-dot--skeleton" }), /* @__PURE__ */ import_react47.default.createElement("div", { className: "brokr-notif-item-body" }, /* @__PURE__ */ import_react47.default.createElement("span", { className: "brokr-notif-item-title brokr-skeleton-line", style: { width: "60%" } }), /* @__PURE__ */ import_react47.default.createElement("span", { className: "brokr-notif-item-message brokr-skeleton-line", style: { width: "80%" } })), /* @__PURE__ */ import_react47.default.createElement("span", { className: "brokr-notif-item-time brokr-skeleton-line", style: { width: 48 } }))));
8519
8850
  }
8520
8851
  function NotifListItem({
8521
8852
  notif,
8522
8853
  registry,
8523
8854
  onClick
8524
8855
  }) {
8525
- const handleClick = (0, import_react45.useCallback)(() => onClick(notif), [notif, onClick]);
8856
+ const handleClick = (0, import_react47.useCallback)(() => onClick(notif), [notif, onClick]);
8526
8857
  const notifData = notif.data ?? {};
8527
8858
  const notifType = notif.type ?? notifData.type ?? "default";
8528
8859
  const resolved = resolveNotificationType(registry, notifType, notifData);
8529
- return /* @__PURE__ */ import_react45.default.createElement(
8860
+ return /* @__PURE__ */ import_react47.default.createElement(
8530
8861
  "button",
8531
8862
  {
8532
8863
  type: "button",
8533
8864
  className: `brokr-notif-list-row${notif.read ? "" : " brokr-notif-list-row--unread"}`,
8534
8865
  onClick: handleClick
8535
8866
  },
8536
- 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}` }),
8537
- /* @__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)),
8538
- /* @__PURE__ */ import_react45.default.createElement("span", { className: "brokr-notif-item-time" }, formatTimestamp(notif.createdAt))
8867
+ resolved.image ? /* @__PURE__ */ import_react47.default.createElement("img", { src: resolved.image.url, alt: resolved.image.alt, className: "brokr-notif-item-logo" }) : /* @__PURE__ */ import_react47.default.createElement("span", { className: `brokr-notif-item-dot brokr-notif-item-dot--${notif.variant}` }),
8868
+ /* @__PURE__ */ import_react47.default.createElement("div", { className: "brokr-notif-item-body" }, /* @__PURE__ */ import_react47.default.createElement("span", { className: "brokr-notif-item-title" }, notif.title), /* @__PURE__ */ import_react47.default.createElement("span", { className: "brokr-notif-item-message" }, notif.message)),
8869
+ /* @__PURE__ */ import_react47.default.createElement("span", { className: "brokr-notif-item-time" }, formatTimestamp(notif.createdAt))
8539
8870
  );
8540
8871
  }
8541
8872
  function NotificationList() {
8542
8873
  const { notifications, unreadCount, markRead, markAllRead, isLoading, registry } = useNotifications();
8543
- const sorted = (0, import_react45.useMemo)(
8874
+ const sorted = (0, import_react47.useMemo)(
8544
8875
  () => [...notifications].sort(
8545
8876
  (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
8546
8877
  ),
8547
8878
  [notifications]
8548
8879
  );
8549
- const handleClick = (0, import_react45.useCallback)((notif) => {
8880
+ const handleClick = (0, import_react47.useCallback)((notif) => {
8550
8881
  if (!notif.read) markRead(notif.id);
8551
8882
  const notifData = notif.data ?? {};
8552
8883
  const notifType = notif.type ?? notifData.type ?? "default";
@@ -8556,7 +8887,7 @@ function NotificationList() {
8556
8887
  window.location.assign(href);
8557
8888
  }
8558
8889
  }, [markRead, registry]);
8559
- return /* @__PURE__ */ import_react45.default.createElement("div", { className: "brokr-notif-list" }, /* @__PURE__ */ import_react45.default.createElement("div", { className: "brokr-notif-list-header" }, /* @__PURE__ */ import_react45.default.createElement("span", { className: "brokr-notif-list-title" }, "Notifications"), unreadCount > 0 && /* @__PURE__ */ import_react45.default.createElement(
8890
+ return /* @__PURE__ */ import_react47.default.createElement("div", { className: "brokr-notif-list" }, /* @__PURE__ */ import_react47.default.createElement("div", { className: "brokr-notif-list-header" }, /* @__PURE__ */ import_react47.default.createElement("span", { className: "brokr-notif-list-title" }, "Notifications"), unreadCount > 0 && /* @__PURE__ */ import_react47.default.createElement(
8560
8891
  "button",
8561
8892
  {
8562
8893
  type: "button",
@@ -8564,7 +8895,7 @@ function NotificationList() {
8564
8895
  onClick: markAllRead
8565
8896
  },
8566
8897
  "Mark all read"
8567
- )), 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(
8898
+ )), isLoading ? /* @__PURE__ */ import_react47.default.createElement(NotificationListSkeleton, null) : sorted.length === 0 ? /* @__PURE__ */ import_react47.default.createElement("div", { className: "brokr-notif-empty" }, /* @__PURE__ */ import_react47.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_react47.default.createElement("path", { d: "M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9" }), /* @__PURE__ */ import_react47.default.createElement("path", { d: "M10.3 21a1.94 1.94 0 0 0 3.4 0" })), /* @__PURE__ */ import_react47.default.createElement("span", { className: "brokr-notif-empty-text" }, "No notifications yet")) : /* @__PURE__ */ import_react47.default.createElement("div", { className: "brokr-notif-list-items" }, sorted.map((notif) => /* @__PURE__ */ import_react47.default.createElement(
8568
8899
  NotifListItem,
8569
8900
  {
8570
8901
  key: notif.id,
@@ -8605,6 +8936,7 @@ function defineAccount(config) {
8605
8936
  0 && (module.exports = {
8606
8937
  AIChat,
8607
8938
  AccountPanel,
8939
+ ActivityFeed,
8608
8940
  AuthPageShell,
8609
8941
  AuthWall,
8610
8942
  AutoReloadToggle,
@@ -8634,8 +8966,10 @@ function defineAccount(config) {
8634
8966
  UpdateBilling,
8635
8967
  UpgradePrompt,
8636
8968
  UsageGate,
8969
+ UsageGrid,
8637
8970
  UserButton,
8638
8971
  defineAccount,
8972
+ defineBrokrTheme,
8639
8973
  defineChat,
8640
8974
  useBrokr,
8641
8975
  useBrokrTheme,