@brokr/sdk 2.1.0 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) 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 +273 -94
  8. package/dist/react-styles.mjs +273 -94
  9. package/dist/react.js +329 -81
  10. package/dist/react.mjs +335 -87
  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/auth.d.ts +1 -1
  38. package/dist/src/react/css/auth.d.ts.map +1 -1
  39. package/dist/src/react/css/chat-extras.d.ts +1 -1
  40. package/dist/src/react/css/chat-extras.d.ts.map +1 -1
  41. package/dist/src/react/css/chat.d.ts +1 -1
  42. package/dist/src/react/css/chat.d.ts.map +1 -1
  43. package/dist/src/react/css/composites.d.ts +1 -1
  44. package/dist/src/react/css/composites.d.ts.map +1 -1
  45. package/dist/src/react/css/gates.d.ts +1 -1
  46. package/dist/src/react/css/gates.d.ts.map +1 -1
  47. package/dist/src/react/css/markdown.d.ts +1 -1
  48. package/dist/src/react/css/markdown.d.ts.map +1 -1
  49. package/dist/src/react/css/primitives.d.ts +1 -1
  50. package/dist/src/react/css/primitives.d.ts.map +1 -1
  51. package/dist/src/react/css/reset.d.ts +1 -1
  52. package/dist/src/react/css/reset.d.ts.map +1 -1
  53. package/dist/src/react/css/responsive.d.ts +1 -1
  54. package/dist/src/react/css/responsive.d.ts.map +1 -1
  55. package/dist/src/react/css/skeleton.d.ts +1 -1
  56. package/dist/src/react/css/skeleton.d.ts.map +1 -1
  57. package/dist/tsconfig.tsbuildinfo +1 -1
  58. package/package.json +1 -1
package/dist/react.mjs CHANGED
@@ -4197,6 +4197,7 @@ import React2, {
4197
4197
  } from "react";
4198
4198
 
4199
4199
  // src/models.ts
4200
+ var STACK_DEFAULT = "__stack_default__";
4200
4201
  var providers = [
4201
4202
  { id: "deepseek", label: "Deepseek", model: "deepseek-chat", color: "#0EA5E9", free: true, logo: "https://assets.brokr.sh/sdk/deepseek_logo.png" },
4202
4203
  { id: "openai", label: "ChatGPT", model: "gpt-5.4-mini", color: "#10B981", free: false, logo: "https://assets.brokr.sh/sdk/gpt_logo.png" },
@@ -6047,7 +6048,7 @@ function PlanCard({
6047
6048
  const handleClick = useCallback12(() => {
6048
6049
  void onSelect(plan.slug);
6049
6050
  }, [plan.slug, onSelect]);
6050
- return /* @__PURE__ */ React21.createElement("article", { className: "brokr-card brokr-plan-card", "data-highlight": isHighlighted, key: plan.slug }, /* @__PURE__ */ React21.createElement("div", { className: "brokr-section", style: { gap: "var(--brokr-space-3)" } }, /* @__PURE__ */ React21.createElement("div", { className: "brokr-brand-row", style: { justifyContent: "space-between" } }, /* @__PURE__ */ React21.createElement("strong", null, plan.name), plan.isCurrent ? /* @__PURE__ */ React21.createElement("span", { className: "brokr-badge" }, "Current") : null, isHighlighted && !plan.isCurrent ? /* @__PURE__ */ React21.createElement("span", { className: "brokr-badge" }, "Recommended") : null), /* @__PURE__ */ React21.createElement("div", { className: "brokr-plan-price" }, /* @__PURE__ */ React21.createElement("span", { className: "brokr-plan-value" }, plan.price === 0 ? "$0" : `$${(plan.price / 100).toFixed(0)}`), /* @__PURE__ */ React21.createElement("span", { className: "brokr-copy" }, "/", plan.interval)), plan.trialDays ? /* @__PURE__ */ React21.createElement("span", { className: "brokr-copy" }, plan.trialDays, " day trial included.") : null), /* @__PURE__ */ React21.createElement(
6051
+ return /* @__PURE__ */ React21.createElement("article", { className: "brokr-card brokr-plan-card", "data-highlight": isHighlighted, key: plan.slug }, /* @__PURE__ */ React21.createElement("div", { className: "brokr-section", style: { gap: "var(--brokr-space-3)" } }, /* @__PURE__ */ React21.createElement("div", { className: "brokr-brand-row", style: { justifyContent: "space-between" } }, /* @__PURE__ */ React21.createElement("strong", null, plan.name), plan.isCurrent ? /* @__PURE__ */ React21.createElement("span", { className: "brokr-badge" }, "Current") : null, isHighlighted && !plan.isCurrent ? /* @__PURE__ */ React21.createElement("span", { className: "brokr-badge" }, "Recommended") : null), /* @__PURE__ */ React21.createElement("div", { className: "brokr-plan-price" }, /* @__PURE__ */ React21.createElement("span", { className: "brokr-plan-value" }, plan.price === 0 ? "$0" : `$${(plan.price / 100).toFixed(0)}`), /* @__PURE__ */ React21.createElement("span", { className: "brokr-copy" }, "/", plan.interval)), plan.trialDays ? /* @__PURE__ */ React21.createElement("span", { className: "brokr-copy" }, plan.trialDays, " day trial included.") : null), /* @__PURE__ */ React21.createElement("ul", { className: "brokr-feature-list" }, features.map(([key, value]) => /* @__PURE__ */ React21.createElement("li", { className: "brokr-feature-item", key }, /* @__PURE__ */ React21.createElement(CheckIcon, { size: 14 }), /* @__PURE__ */ React21.createElement("span", null, featureLabel(key), " \xB7 ", featureValueLabel(value))))), /* @__PURE__ */ React21.createElement(
6051
6052
  "button",
6052
6053
  {
6053
6054
  className: plan.isCurrent ? "brokr-button-secondary" : "brokr-button",
@@ -6056,7 +6057,7 @@ function PlanCard({
6056
6057
  type: "button"
6057
6058
  },
6058
6059
  plan.isCurrent ? "Current plan" : isPending ? "Redirecting" : "Choose plan"
6059
- ), /* @__PURE__ */ React21.createElement("ul", { className: "brokr-feature-list" }, features.map(([key, value]) => /* @__PURE__ */ React21.createElement("li", { className: "brokr-feature-item", key }, /* @__PURE__ */ React21.createElement(CheckIcon, { size: 14 }), /* @__PURE__ */ React21.createElement("span", null, featureLabel(key), " \xB7 ", featureValueLabel(value))))));
6060
+ ));
6060
6061
  }
6061
6062
  function Plans({
6062
6063
  columns,
@@ -6410,6 +6411,39 @@ function isSSEResponse(response) {
6410
6411
  return ct.includes("text/event-stream") && response.body !== null;
6411
6412
  }
6412
6413
 
6414
+ // src/react/chat/memory.ts
6415
+ var MAX_MEMORY_CHARS = 2e3;
6416
+ function serializeMemory(obj, prefix = "") {
6417
+ const lines = [];
6418
+ for (const [key, value] of Object.entries(obj)) {
6419
+ if (value === null || value === void 0) continue;
6420
+ const label = prefix ? `${prefix}.${key}` : key;
6421
+ if (Array.isArray(value)) {
6422
+ lines.push(`${label}: ${value.join(", ")}`);
6423
+ } else if (typeof value === "object") {
6424
+ lines.push(...serializeMemory(value, label));
6425
+ } else {
6426
+ lines.push(`${label}: ${String(value)}`);
6427
+ }
6428
+ }
6429
+ return lines;
6430
+ }
6431
+ function buildEffectivePrompt(prompt, memory) {
6432
+ if (!memory || Object.keys(memory).length === 0) return prompt;
6433
+ let block = serializeMemory(memory).join("\n");
6434
+ if (block.length > MAX_MEMORY_CHARS) {
6435
+ block = block.slice(0, MAX_MEMORY_CHARS);
6436
+ if (typeof console !== "undefined") {
6437
+ console.warn("[brokr] memory exceeded 2000 char limit, truncated");
6438
+ }
6439
+ }
6440
+ const memSection = `[User context]
6441
+ ${block}`;
6442
+ return prompt ? `${memSection}
6443
+
6444
+ ${prompt}` : memSection;
6445
+ }
6446
+
6413
6447
  // src/react/chat/useChat.ts
6414
6448
  function makeId(prefix) {
6415
6449
  return `${prefix}_${crypto.randomUUID()}`;
@@ -6427,7 +6461,9 @@ function useChat(config) {
6427
6461
  const {
6428
6462
  endpoint,
6429
6463
  prompt,
6430
- model: activeModel,
6464
+ explicitModel,
6465
+ displayModel: activeModel,
6466
+ memory,
6431
6467
  persist,
6432
6468
  surface,
6433
6469
  subject,
@@ -6463,9 +6499,25 @@ function useChat(config) {
6463
6499
  const scrollContainerRef = useRef5(null);
6464
6500
  const sentinelRef = useRef5(null);
6465
6501
  const displaySidebarItems = useMemo18(() => {
6466
- if (isControlled) return threadsProp?.map((t) => ({ id: t.id, title: t.title })) ?? [];
6467
- if (isPersist) return serverThreads.map((t) => ({ id: t.id, title: t.title }));
6468
- return memConversations.map((c) => ({ id: c.id, title: c.title || "New chat" }));
6502
+ if (isControlled) {
6503
+ return threadsProp?.map((t) => ({
6504
+ id: t.id,
6505
+ title: t.title,
6506
+ updatedAt: t.updatedAt ?? null
6507
+ })) ?? [];
6508
+ }
6509
+ if (isPersist) {
6510
+ return serverThreads.map((t) => ({
6511
+ id: t.id,
6512
+ title: t.title,
6513
+ updatedAt: t.updatedAt ?? null
6514
+ }));
6515
+ }
6516
+ return memConversations.map((c) => ({
6517
+ id: c.id,
6518
+ title: c.title || "New chat",
6519
+ updatedAt: new Date(c.updatedAt).toISOString()
6520
+ }));
6469
6521
  }, [isControlled, isPersist, threadsProp, serverThreads, memConversations]);
6470
6522
  const activeId = useMemo18(() => {
6471
6523
  if (isControlled) return activeThreadIdProp ?? null;
@@ -6646,8 +6698,11 @@ function useChat(config) {
6646
6698
  messages: trimToTokenBudget(
6647
6699
  nextMessages.filter((m) => m.role === "user" || m.role === "assistant" && m.content).map(({ content: mc, role }) => ({ role, content: mc }))
6648
6700
  ),
6649
- model: activeModel,
6650
- ...prompt ? { systemPrompt: prompt } : {}
6701
+ ...explicitModel ? { model: explicitModel } : {},
6702
+ ...(() => {
6703
+ const ep = buildEffectivePrompt(prompt, memory);
6704
+ return ep ? { systemPrompt: ep } : {};
6705
+ })()
6651
6706
  })
6652
6707
  });
6653
6708
  if (!response.ok) {
@@ -6692,8 +6747,10 @@ function useChat(config) {
6692
6747
  ensureMemConversation,
6693
6748
  memConversations,
6694
6749
  endpoint,
6750
+ explicitModel,
6695
6751
  activeModel,
6696
6752
  prompt,
6753
+ memory,
6697
6754
  updateMemMessages,
6698
6755
  appendMemDelta,
6699
6756
  animateTitle,
@@ -6715,9 +6772,12 @@ function useChat(config) {
6715
6772
  content,
6716
6773
  surface,
6717
6774
  subject: subject ?? null,
6718
- model: activeModel,
6775
+ ...explicitModel ? { model: explicitModel } : {},
6719
6776
  userId: userId ?? void 0,
6720
- ...prompt ? { systemPrompt: prompt } : {}
6777
+ ...(() => {
6778
+ const ep = buildEffectivePrompt(prompt, memory);
6779
+ return ep ? { systemPrompt: ep } : {};
6780
+ })()
6721
6781
  })
6722
6782
  });
6723
6783
  if (!response.ok) {
@@ -6772,8 +6832,10 @@ function useChat(config) {
6772
6832
  endpoint,
6773
6833
  surface,
6774
6834
  subject,
6835
+ explicitModel,
6775
6836
  activeModel,
6776
6837
  prompt,
6838
+ memory,
6777
6839
  userId,
6778
6840
  setServerMessages,
6779
6841
  setServerActiveId,
@@ -6970,12 +7032,17 @@ function ModelSelector({
6970
7032
  }) {
6971
7033
  const [selectorOpen, setSelectorOpen] = useState17(false);
6972
7034
  const selectorRef = useRef6(null);
7035
+ const isAuto = activeModel === STACK_DEFAULT || !providers.some((p) => p.model === activeModel);
6973
7036
  const activeProvider = useMemo19(
6974
- () => providers.find((p) => p.model === activeModel) ?? providers[0],
6975
- [activeModel]
7037
+ () => isAuto ? void 0 : providers.find((p) => p.model === activeModel) ?? providers[0],
7038
+ [activeModel, isAuto]
6976
7039
  );
6977
7040
  const handleModelSelect = useCallback17((model) => {
6978
- setSelectedModel(model);
7041
+ setSelectedModel(model === STACK_DEFAULT ? null : model);
7042
+ setSelectorOpen(false);
7043
+ }, [setSelectedModel]);
7044
+ const handleAutoSelect = useCallback17(() => {
7045
+ setSelectedModel(null);
6979
7046
  setSelectorOpen(false);
6980
7047
  }, [setSelectedModel]);
6981
7048
  useEffect10(() => {
@@ -7005,15 +7072,26 @@ function ModelSelector({
7005
7072
  onClick: handleToggle,
7006
7073
  type: "button"
7007
7074
  },
7008
- activeProvider ? /* @__PURE__ */ React29.createElement("img", { alt: "", className: "brokr-model-logo", src: activeProvider.logo }) : /* @__PURE__ */ React29.createElement("span", { className: "brokr-model-dot", style: { background: "currentColor" } }),
7009
- activeProvider?.label ?? "Model",
7075
+ activeProvider ? /* @__PURE__ */ React29.createElement("img", { alt: "", className: "brokr-model-logo", src: activeProvider.logo }) : /* @__PURE__ */ React29.createElement("span", { className: "brokr-model-dot", style: { background: "#6B7280" } }),
7076
+ isAuto ? "Auto" : activeProvider?.label ?? "Model",
7010
7077
  /* @__PURE__ */ React29.createElement(ChevronDownIcon, { size: 13 })
7011
- ), selectorOpen ? /* @__PURE__ */ React29.createElement("div", { className: "brokr-model-dropdown", role: "listbox" }, providers.map((p) => /* @__PURE__ */ React29.createElement(
7078
+ ), selectorOpen ? /* @__PURE__ */ React29.createElement("div", { className: "brokr-model-dropdown", role: "listbox" }, /* @__PURE__ */ React29.createElement(
7079
+ "button",
7080
+ {
7081
+ "aria-selected": isAuto,
7082
+ className: "brokr-model-option",
7083
+ "data-active": isAuto,
7084
+ onClick: handleAutoSelect,
7085
+ type: "button"
7086
+ },
7087
+ /* @__PURE__ */ React29.createElement("span", { className: "brokr-model-dot", style: { background: "#6B7280" } }),
7088
+ /* @__PURE__ */ React29.createElement("span", { className: "brokr-model-option-label" }, "Stack Default")
7089
+ ), providers.map((p) => /* @__PURE__ */ React29.createElement(
7012
7090
  ModelOption,
7013
7091
  {
7014
7092
  key: p.id,
7015
7093
  provider: p,
7016
- isActive: p.model === activeModel,
7094
+ isActive: !isAuto && p.model === activeModel,
7017
7095
  isLocked: !availableProviders.some((a) => a.id === p.id),
7018
7096
  onSelect: handleModelSelect,
7019
7097
  onCheckout: checkout
@@ -7033,9 +7111,40 @@ function ThreadItemButton({ id, title, isActive, onSelect }) {
7033
7111
  onClick: handleClick,
7034
7112
  type: "button"
7035
7113
  },
7036
- title
7114
+ /* @__PURE__ */ React30.createElement(MessageIcon, { size: 12 }),
7115
+ /* @__PURE__ */ React30.createElement("span", { className: "brokr-ai-chat-conversation-label" }, title)
7037
7116
  );
7038
7117
  }
7118
+ var SIDEBAR_GROUP_ORDER = [
7119
+ "today",
7120
+ "yesterday",
7121
+ "this_week",
7122
+ "this_month",
7123
+ "earlier",
7124
+ "undated"
7125
+ ];
7126
+ var SIDEBAR_GROUP_LABELS = {
7127
+ today: "Today",
7128
+ yesterday: "Yesterday",
7129
+ this_week: "This Week",
7130
+ this_month: "This Month",
7131
+ earlier: "Earlier",
7132
+ undated: "Recent"
7133
+ };
7134
+ function resolveSidebarDateGroup(updatedAt) {
7135
+ if (!updatedAt) return "undated";
7136
+ const parsed = new Date(updatedAt);
7137
+ if (Number.isNaN(parsed.getTime())) return "undated";
7138
+ const now = /* @__PURE__ */ new Date();
7139
+ const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate());
7140
+ const startOfTarget = new Date(parsed.getFullYear(), parsed.getMonth(), parsed.getDate());
7141
+ const diffDays = Math.floor((startOfToday.getTime() - startOfTarget.getTime()) / 864e5);
7142
+ if (diffDays <= 0) return "today";
7143
+ if (diffDays === 1) return "yesterday";
7144
+ if (diffDays < 7) return "this_week";
7145
+ if (diffDays < 30) return "this_month";
7146
+ return "earlier";
7147
+ }
7039
7148
  function ThreadSidebar() {
7040
7149
  const {
7041
7150
  startNewChat,
@@ -7067,14 +7176,31 @@ function ThreadSidebar() {
7067
7176
  const handleRenameChange = useCallback18((e) => {
7068
7177
  setRenameValue(e.target.value);
7069
7178
  }, [setRenameValue]);
7179
+ const groupedSidebarItems = useMemo20(() => {
7180
+ const grouped = /* @__PURE__ */ new Map();
7181
+ for (const item of displaySidebarItems) {
7182
+ const key = resolveSidebarDateGroup(item.updatedAt);
7183
+ const bucket = grouped.get(key);
7184
+ if (bucket) {
7185
+ bucket.push(item);
7186
+ } else {
7187
+ grouped.set(key, [item]);
7188
+ }
7189
+ }
7190
+ return SIDEBAR_GROUP_ORDER.map((key) => ({
7191
+ key,
7192
+ label: SIDEBAR_GROUP_LABELS[key],
7193
+ items: grouped.get(key) ?? []
7194
+ })).filter((group) => group.items.length > 0);
7195
+ }, [displaySidebarItems]);
7070
7196
  const content = useMemo20(() => {
7071
7197
  if (threadsLoading && displaySidebarItems.length === 0) {
7072
- return /* @__PURE__ */ React30.createElement("div", { style: { display: "grid", gap: "0.5rem", padding: "0 0.65rem" } }, /* @__PURE__ */ React30.createElement(Skeleton, { width: "75%", height: 14, radius: 6 }), /* @__PURE__ */ React30.createElement(Skeleton, { width: "60%", height: 14, radius: 6 }), /* @__PURE__ */ React30.createElement(Skeleton, { width: "85%", height: 14, radius: 6 }));
7198
+ return /* @__PURE__ */ React30.createElement("div", { className: "brokr-ai-chat-sidebar-skeleton" }, /* @__PURE__ */ React30.createElement(Skeleton, { width: "75%", height: 14, radius: 6 }), /* @__PURE__ */ React30.createElement(Skeleton, { width: "60%", height: 14, radius: 6 }), /* @__PURE__ */ React30.createElement(Skeleton, { width: "85%", height: 14, radius: 6 }));
7073
7199
  }
7074
7200
  if (displaySidebarItems.length === 0) {
7075
7201
  return /* @__PURE__ */ React30.createElement("div", { className: "brokr-ai-chat-sidebar-empty" }, /* @__PURE__ */ React30.createElement("p", { className: "brokr-ai-chat-sidebar-empty-text" }, "No conversations yet. Start one above."));
7076
7202
  }
7077
- return /* @__PURE__ */ React30.createElement("div", { className: "brokr-ai-chat-conversations" }, displaySidebarItems.map((item) => renamingId === item.id ? /* @__PURE__ */ React30.createElement(
7203
+ return /* @__PURE__ */ React30.createElement("div", { className: "brokr-ai-chat-sidebar-groups" }, groupedSidebarItems.map((group) => /* @__PURE__ */ React30.createElement("section", { className: "brokr-ai-chat-sidebar-group", key: group.key }, /* @__PURE__ */ React30.createElement("span", { className: "brokr-ai-chat-sidebar-kicker" }, group.label), /* @__PURE__ */ React30.createElement("div", { className: "brokr-ai-chat-conversations" }, group.items.map((item) => renamingId === item.id ? /* @__PURE__ */ React30.createElement(
7078
7204
  "input",
7079
7205
  {
7080
7206
  autoFocus: true,
@@ -7094,19 +7220,20 @@ function ThreadSidebar() {
7094
7220
  isActive: item.id === activeId,
7095
7221
  onSelect: selectThreadAndCloseSidebar
7096
7222
  }
7097
- )));
7223
+ ))))));
7098
7224
  }, [
7099
7225
  threadsLoading,
7100
7226
  displaySidebarItems,
7101
7227
  renamingId,
7102
7228
  renameValue,
7229
+ groupedSidebarItems,
7103
7230
  activeId,
7104
7231
  selectThreadAndCloseSidebar,
7105
7232
  handleRenameBlur,
7106
7233
  handleRenameChange,
7107
7234
  handleRenameKeyDown
7108
7235
  ]);
7109
- return /* @__PURE__ */ React30.createElement(React30.Fragment, null, /* @__PURE__ */ React30.createElement("button", { className: "brokr-ai-chat-sidebar-button", onClick: handleNewChat, type: "button" }, /* @__PURE__ */ React30.createElement(MessageIcon, { size: 16 }), "New chat"), content);
7236
+ return /* @__PURE__ */ React30.createElement(React30.Fragment, null, /* @__PURE__ */ React30.createElement("button", { className: "brokr-ai-chat-sidebar-new-chat", onClick: handleNewChat, type: "button" }, /* @__PURE__ */ React30.createElement(MessageIcon, { size: 16 }), "New chat"), content);
7110
7237
  }
7111
7238
 
7112
7239
  // src/react/chat/MessagePane.tsx
@@ -7134,6 +7261,12 @@ function parseInline(text) {
7134
7261
  remaining = remaining.slice(boldMatch[0].length);
7135
7262
  continue;
7136
7263
  }
7264
+ const strikethroughMatch = remaining.match(/^~~(.+?)~~/);
7265
+ if (strikethroughMatch) {
7266
+ nodes.push(/* @__PURE__ */ React31.createElement("del", { key: key++ }, strikethroughMatch[1]));
7267
+ remaining = remaining.slice(strikethroughMatch[0].length);
7268
+ continue;
7269
+ }
7137
7270
  const italicMatch = remaining.match(/^\*(.+?)\*/);
7138
7271
  if (italicMatch) {
7139
7272
  nodes.push(/* @__PURE__ */ React31.createElement("em", { key: key++ }, italicMatch[1]));
@@ -7150,7 +7283,7 @@ function parseInline(text) {
7150
7283
  remaining = remaining.slice(linkMatch[0].length);
7151
7284
  continue;
7152
7285
  }
7153
- const nextSpecial = remaining.search(/[`*\[]/);
7286
+ const nextSpecial = remaining.search(/[`*~\[]/);
7154
7287
  if (nextSpecial === -1) {
7155
7288
  nodes.push(remaining);
7156
7289
  break;
@@ -7181,6 +7314,15 @@ function CodeBlock({ code, language }) {
7181
7314
  "Copy"
7182
7315
  )), /* @__PURE__ */ React31.createElement("pre", { className: "brokr-md-codeblock-pre" }, /* @__PURE__ */ React31.createElement("code", null, code)));
7183
7316
  }
7317
+ function looksLikeTableSeparator(line) {
7318
+ const trimmed = line.trim();
7319
+ return /^\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)+\|?$/.test(trimmed);
7320
+ }
7321
+ function splitTableRow(line) {
7322
+ const trimmed = line.trim();
7323
+ const withoutEdges = trimmed.replace(/^\|/, "").replace(/\|$/, "");
7324
+ return withoutEdges.split("|").map((cell) => cell.trim());
7325
+ }
7184
7326
  function parseBlocks(text) {
7185
7327
  const blocks = [];
7186
7328
  const lines = text.split("\n");
@@ -7210,22 +7352,44 @@ function parseBlocks(text) {
7210
7352
  i++;
7211
7353
  continue;
7212
7354
  }
7355
+ if (line.includes("|") && i + 1 < lines.length && looksLikeTableSeparator(lines[i + 1] ?? "")) {
7356
+ const headers = splitTableRow(line);
7357
+ const rows = [];
7358
+ i += 2;
7359
+ while (i < lines.length) {
7360
+ const candidate = lines[i] ?? "";
7361
+ if (!candidate.trim() || !candidate.includes("|")) break;
7362
+ rows.push(splitTableRow(candidate));
7363
+ i++;
7364
+ }
7365
+ blocks.push({ type: "table", content: "", headers, rows });
7366
+ continue;
7367
+ }
7368
+ if (/^[\s]*>\s?/.test(line)) {
7369
+ const quoteLines = [];
7370
+ while (i < lines.length && /^[\s]*>\s?/.test(lines[i])) {
7371
+ quoteLines.push(lines[i].replace(/^[\s]*>\s?/, ""));
7372
+ i++;
7373
+ }
7374
+ blocks.push({ type: "quote", content: "", items: quoteLines });
7375
+ continue;
7376
+ }
7213
7377
  if (/^[\s]*[-*]\s+/.test(line)) {
7214
7378
  const items = [];
7215
7379
  while (i < lines.length && /^[\s]*[-*]\s+/.test(lines[i])) {
7216
7380
  items.push(lines[i].replace(/^[\s]*[-*]\s+/, ""));
7217
7381
  i++;
7218
7382
  }
7219
- blocks.push({ type: "list", content: "", items });
7383
+ blocks.push({ type: "list", content: "", items, ordered: false });
7220
7384
  continue;
7221
7385
  }
7222
- if (/^[\s]*\d+\.\s+/.test(line)) {
7386
+ if (/^[\s]*\d+[.)]\s+/.test(line)) {
7223
7387
  const items = [];
7224
- while (i < lines.length && /^[\s]*\d+\.\s+/.test(lines[i])) {
7225
- items.push(lines[i].replace(/^[\s]*\d+\.\s+/, ""));
7388
+ while (i < lines.length && /^[\s]*\d+[.)]\s+/.test(lines[i])) {
7389
+ items.push(lines[i].replace(/^[\s]*\d+[.)]\s+/, ""));
7226
7390
  i++;
7227
7391
  }
7228
- blocks.push({ type: "list", content: "", items });
7392
+ blocks.push({ type: "list", content: "", items, ordered: true });
7229
7393
  continue;
7230
7394
  }
7231
7395
  if (!line.trim()) {
@@ -7234,7 +7398,7 @@ function parseBlocks(text) {
7234
7398
  }
7235
7399
  const paraLines = [line];
7236
7400
  i++;
7237
- 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])) {
7401
+ 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])) {
7238
7402
  paraLines.push(lines[i]);
7239
7403
  i++;
7240
7404
  }
@@ -7254,7 +7418,14 @@ function renderBlocks(blocks) {
7254
7418
  return /* @__PURE__ */ React31.createElement(Tag, { className: `brokr-md-heading brokr-md-h${block.level}`, key: idx }, parseInline(block.content));
7255
7419
  }
7256
7420
  case "list":
7257
- return /* @__PURE__ */ React31.createElement("ul", { className: "brokr-md-list", key: idx }, block.items?.map((item, li) => /* @__PURE__ */ React31.createElement("li", { key: li }, parseInline(item))));
7421
+ if (block.ordered) {
7422
+ return /* @__PURE__ */ React31.createElement("ol", { className: "brokr-md-list brokr-md-list-ordered", key: idx }, block.items?.map((item, li) => /* @__PURE__ */ React31.createElement("li", { key: li }, parseInline(item))));
7423
+ }
7424
+ return /* @__PURE__ */ React31.createElement("ul", { className: "brokr-md-list brokr-md-list-unordered", key: idx }, block.items?.map((item, li) => /* @__PURE__ */ React31.createElement("li", { key: li }, parseInline(item))));
7425
+ case "quote":
7426
+ return /* @__PURE__ */ React31.createElement("blockquote", { className: "brokr-md-quote", key: idx }, block.items?.map((line, lineIndex) => /* @__PURE__ */ React31.createElement("p", { className: "brokr-md-quote-line", key: lineIndex }, parseInline(line))));
7427
+ case "table":
7428
+ return /* @__PURE__ */ React31.createElement("div", { className: "brokr-md-table-wrap", key: idx }, /* @__PURE__ */ React31.createElement("table", { className: "brokr-md-table" }, /* @__PURE__ */ React31.createElement("thead", null, /* @__PURE__ */ React31.createElement("tr", null, block.headers?.map((header, headerIndex) => /* @__PURE__ */ React31.createElement("th", { key: headerIndex }, parseInline(header))))), /* @__PURE__ */ React31.createElement("tbody", null, block.rows?.map((row, rowIndex) => /* @__PURE__ */ React31.createElement("tr", { key: rowIndex }, row.map((cell, cellIndex) => /* @__PURE__ */ React31.createElement("td", { key: cellIndex }, parseInline(cell))))))));
7258
7429
  case "paragraph":
7259
7430
  default:
7260
7431
  return /* @__PURE__ */ React31.createElement("p", { className: "brokr-md-paragraph", key: idx }, parseInline(block.content));
@@ -7411,6 +7582,7 @@ function AIChat(inlineProps) {
7411
7582
  subtitle,
7412
7583
  model: modelProp,
7413
7584
  modelSelector,
7585
+ memory,
7414
7586
  variant = 1,
7415
7587
  sidebar: sidebarProp,
7416
7588
  threadMenu: threadMenuProp,
@@ -7434,16 +7606,12 @@ function AIChat(inlineProps) {
7434
7606
  const headerVisible = variant !== 3;
7435
7607
  const threadMenuVisible = threadMenuProp !== void 0 ? threadMenuProp : variant !== 3;
7436
7608
  const modelSelectorVisible = (modelSelector !== void 0 ? modelSelector : true) && !modelProp;
7437
- const [selectedModel, setSelectedModel] = useState18(() => {
7438
- if (modelProp) return modelProp;
7439
- return providers.find((p) => p.free)?.model ?? providers[0]?.model ?? "";
7440
- });
7441
- useEffect12(() => {
7442
- if (modelProp) setSelectedModel(modelProp);
7443
- }, [modelProp]);
7444
- const activeModel = modelProp ?? selectedModel;
7609
+ const [userSelectedModel, setUserSelectedModel] = useState18(null);
7610
+ const explicitModel = modelProp ?? userSelectedModel ?? void 0;
7611
+ const displayModel = explicitModel ?? providers.find((p) => p.free)?.model ?? providers[0]?.model ?? "";
7612
+ const activeModel = explicitModel ?? STACK_DEFAULT;
7445
7613
  const activeProvider = useMemo23(
7446
- () => providers.find((p) => p.model === activeModel) ?? providers[0],
7614
+ () => activeModel === STACK_DEFAULT ? void 0 : providers.find((p) => p.model === activeModel) ?? providers[0],
7447
7615
  [activeModel]
7448
7616
  );
7449
7617
  const hasBalance = useMemo23(
@@ -7457,7 +7625,9 @@ function AIChat(inlineProps) {
7457
7625
  const chat = useChat({
7458
7626
  endpoint,
7459
7627
  prompt,
7460
- model: activeModel,
7628
+ explicitModel,
7629
+ displayModel,
7630
+ memory,
7461
7631
  persist,
7462
7632
  surface,
7463
7633
  subject,
@@ -7565,10 +7735,10 @@ function AIChat(inlineProps) {
7565
7735
  activeThread: chat.activeThread,
7566
7736
  renderedTitle: finalTitle,
7567
7737
  isTitleLoading: chat.isTitleLoading,
7568
- activeModel,
7738
+ activeModel: displayModel,
7569
7739
  activeProvider,
7570
- selectedModel,
7571
- setSelectedModel,
7740
+ selectedModel: displayModel,
7741
+ setSelectedModel: setUserSelectedModel,
7572
7742
  availableProviders,
7573
7743
  sendMessage: chat.sendMessage,
7574
7744
  startNewChat: chat.startNewChat,
@@ -7608,9 +7778,9 @@ function AIChat(inlineProps) {
7608
7778
  }), [
7609
7779
  chat,
7610
7780
  finalTitle,
7611
- activeModel,
7781
+ displayModel,
7612
7782
  activeProvider,
7613
- selectedModel,
7783
+ userSelectedModel,
7614
7784
  availableProviders,
7615
7785
  sidebarOpen,
7616
7786
  closeSidebar,
@@ -7647,8 +7817,8 @@ function AIChat(inlineProps) {
7647
7817
  threadMenuRef,
7648
7818
  threadMenuVisible,
7649
7819
  setThreadMenuOpenId,
7650
- activeModel,
7651
- setSelectedModel,
7820
+ activeModel: displayModel,
7821
+ setSelectedModel: setUserSelectedModel,
7652
7822
  availableProviders,
7653
7823
  startRename: chatState.startRename,
7654
7824
  deleteThread: chat.deleteThread
@@ -7767,24 +7937,63 @@ function ChatHeader({
7767
7937
  }
7768
7938
 
7769
7939
  // src/react/composites/FabAI.tsx
7770
- import React36, { useCallback as useCallback24, useMemo as useMemo24, useState as useState19 } from "react";
7771
- function StarterButton({ prompt, onSelect }) {
7772
- const handleClick = useCallback24(() => onSelect(prompt), [prompt, onSelect]);
7773
- return /* @__PURE__ */ React36.createElement("button", { className: "brokr-chat-starter", onClick: handleClick, type: "button" }, prompt);
7940
+ import React36, { useCallback as useCallback24, useMemo as useMemo24, useRef as useRef8, useState as useState19 } from "react";
7941
+
7942
+ // src/react/composites/fab-context.ts
7943
+ function buildFabSystemPrompt(appContext, brandName, existingPrompt) {
7944
+ if (appContext === false) return existingPrompt;
7945
+ const name = appContext?.name ?? brandName;
7946
+ if (!name && !appContext) return existingPrompt;
7947
+ const parts = [];
7948
+ if (name) parts.push(`You are an AI assistant embedded in ${name}.`);
7949
+ if (appContext?.description) parts.push(`This app is ${appContext.description}.`);
7950
+ if (appContext?.currentPage) parts.push(`The user is currently on: ${appContext.currentPage}.`);
7951
+ if (appContext?.facts?.length) parts.push(...appContext.facts);
7952
+ const autoPrompt = parts.join(" ");
7953
+ return existingPrompt ? `${autoPrompt}
7954
+
7955
+ ${existingPrompt}` : autoPrompt;
7956
+ }
7957
+
7958
+ // src/react/composites/FabAI.tsx
7959
+ function ensureAssistantReply(messages, content) {
7960
+ const normalized = content.trim() ? content : "No response received.";
7961
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
7962
+ if (messages[index]?.role !== "assistant") continue;
7963
+ const next = [...messages];
7964
+ next[index] = { ...next[index], content: normalized };
7965
+ return next;
7966
+ }
7967
+ return [...messages, { role: "assistant", content: normalized }];
7968
+ }
7969
+ function appendAssistantDelta(messages, delta) {
7970
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
7971
+ if (messages[index]?.role !== "assistant") continue;
7972
+ const next = [...messages];
7973
+ next[index] = {
7974
+ ...next[index],
7975
+ content: `${contentToText(next[index].content)}${delta}`
7976
+ };
7977
+ return next;
7978
+ }
7979
+ return [...messages, { role: "assistant", content: delta }];
7774
7980
  }
7775
7981
  function FabAI({
7982
+ appContext,
7776
7983
  model,
7984
+ memory,
7777
7985
  onSendMessage,
7778
7986
  position = "bottom-right",
7779
- starterPrompts = [],
7780
7987
  systemPrompt
7781
7988
  }) {
7782
- const { can, user } = useBrokr();
7989
+ const { can, user, theme } = useBrokr();
7990
+ const brandName = theme?.brand?.name;
7783
7991
  const [isOpen, setIsOpen] = useState19(false);
7784
7992
  const [input, setInput] = useState19("");
7785
7993
  const [error, setError] = useState19(null);
7786
7994
  const [isSending, setIsSending] = useState19(false);
7787
7995
  const [messages, setMessages] = useState19([]);
7996
+ const conversationIdRef = useRef8(`fab_${crypto.randomUUID()}`);
7788
7997
  const launcherStyle = useMemo24(
7789
7998
  () => ({
7790
7999
  left: position === "bottom-left" ? "var(--brokr-space-6)" : void 0,
@@ -7808,37 +8017,75 @@ function FabAI({
7808
8017
  }, []);
7809
8018
  const sendPrompt = useCallback24(async (prompt) => {
7810
8019
  const nextPrompt = prompt.trim();
7811
- if (!nextPrompt) return;
7812
- const nextMessages = [...messages, { role: "user", content: nextPrompt }];
8020
+ if (!nextPrompt || isSending) return;
8021
+ const userMessage = { role: "user", content: nextPrompt };
8022
+ const nextMessages = [...messages, userMessage];
8023
+ const optimisticMessages = [...nextMessages, { role: "assistant", content: "" }];
7813
8024
  try {
7814
8025
  setError(null);
7815
8026
  setIsSending(true);
7816
- setMessages(nextMessages);
8027
+ setMessages(optimisticMessages);
7817
8028
  setInput("");
7818
- let responseText = "";
7819
8029
  if (onSendMessage) {
7820
- responseText = await onSendMessage({ messages: nextMessages, model, systemPrompt });
7821
- } else {
7822
- const payload = await postJson(
7823
- "/api/brokr/chat",
7824
- {
7825
- messages: nextMessages,
7826
- model,
7827
- systemPrompt
8030
+ const responseText2 = await onSendMessage({ messages: nextMessages, model, systemPrompt });
8031
+ setMessages((current) => ensureAssistantReply(current, responseText2));
8032
+ return;
8033
+ }
8034
+ const response = await fetch("/api/brokr/chat", {
8035
+ method: "POST",
8036
+ credentials: "include",
8037
+ headers: { "Content-Type": "application/json" },
8038
+ body: JSON.stringify({
8039
+ conversationId: conversationIdRef.current,
8040
+ messages: trimToTokenBudget(
8041
+ nextMessages.filter((message) => message.role === "user" || message.role === "assistant" && Boolean(contentToText(message.content))).map((message) => ({
8042
+ role: message.role,
8043
+ content: contentToText(message.content)
8044
+ }))
8045
+ ),
8046
+ ...model !== void 0 ? { model } : {},
8047
+ ...(() => {
8048
+ const withContext = buildFabSystemPrompt(appContext, brandName, systemPrompt);
8049
+ const ep = buildEffectivePrompt(withContext, memory);
8050
+ return ep ? { systemPrompt: ep } : {};
8051
+ })()
8052
+ })
8053
+ });
8054
+ if (!response.ok) {
8055
+ const payload2 = await response.json().catch(() => ({}));
8056
+ throw new Error(payload2.message ?? payload2.error ?? `Chat failed (${response.status})`);
8057
+ }
8058
+ if (isSSEResponse(response)) {
8059
+ let hasDelta = false;
8060
+ for await (const event of parseSSEStream(response)) {
8061
+ if (event.type === "conversation") {
8062
+ conversationIdRef.current = event.id;
8063
+ } else if (event.type === "delta") {
8064
+ hasDelta = true;
8065
+ setMessages((current) => appendAssistantDelta(current, event.delta));
8066
+ } else if (event.type === "error") {
8067
+ throw new Error(event.message);
7828
8068
  }
7829
- );
7830
- responseText = payload.content ?? payload.response ?? "";
8069
+ }
8070
+ if (!hasDelta) {
8071
+ setMessages((current) => ensureAssistantReply(current, "No response received."));
8072
+ }
8073
+ return;
7831
8074
  }
7832
- setMessages((current) => [...current, {
7833
- role: "assistant",
7834
- content: responseText || "No response received."
7835
- }]);
8075
+ const payload = await response.json().catch(() => ({}));
8076
+ if (payload.error || payload.message) {
8077
+ throw new Error(payload.message ?? payload.error ?? "AI request failed.");
8078
+ }
8079
+ const responseText = payload.content ?? payload.response ?? payload.text ?? "";
8080
+ setMessages((current) => ensureAssistantReply(current, responseText));
7836
8081
  } catch (cause) {
7837
- setError(cause instanceof Error ? cause.message : "Could not send message.");
8082
+ const message = cause instanceof Error ? cause.message : "Could not send message.";
8083
+ setError(message);
8084
+ setMessages((current) => ensureAssistantReply(current, `Error: ${message}`));
7838
8085
  } finally {
7839
8086
  setIsSending(false);
7840
8087
  }
7841
- }, [messages, model, onSendMessage, systemPrompt]);
8088
+ }, [isSending, messages, model, onSendMessage, systemPrompt]);
7842
8089
  const handleSubmit = useCallback24(async (event) => {
7843
8090
  event.preventDefault();
7844
8091
  await sendPrompt(input);
@@ -7849,28 +8096,29 @@ function FabAI({
7849
8096
  void sendPrompt(input);
7850
8097
  }
7851
8098
  }, [input, sendPrompt]);
7852
- const handleStarterPrompt = useCallback24((prompt) => {
7853
- void sendPrompt(prompt);
7854
- }, [sendPrompt]);
7855
8099
  return /* @__PURE__ */ React36.createElement(React36.Fragment, null, /* @__PURE__ */ React36.createElement("div", { className: "brokr-chat-fab", style: launcherStyle }, /* @__PURE__ */ React36.createElement(
7856
8100
  "button",
7857
8101
  {
8102
+ "aria-label": isOpen ? "Close AI chat" : "Open AI chat",
7858
8103
  "aria-expanded": isOpen,
7859
8104
  "aria-haspopup": "dialog",
7860
- className: "brokr-button",
8105
+ className: "brokr-chat-fab-trigger",
7861
8106
  onClick: toggleOpen,
7862
8107
  type: "button"
7863
8108
  },
7864
- /* @__PURE__ */ React36.createElement(MessageIcon, { size: 16 }),
7865
- "Ask AI"
8109
+ /* @__PURE__ */ React36.createElement(SparkIcon, { size: 18 })
7866
8110
  )), isOpen ? /* @__PURE__ */ React36.createElement("div", { className: "brokr-panel brokr-chat-panel", role: "dialog" }, /* @__PURE__ */ React36.createElement("div", { className: "brokr-brand-row", style: { justifyContent: "space-between" } }, /* @__PURE__ */ React36.createElement("div", { className: "brokr-section", style: { gap: "var(--brokr-space-1)" } }, /* @__PURE__ */ React36.createElement("strong", null, "AI Chat"), user?.name ? /* @__PURE__ */ React36.createElement("span", { className: "brokr-copy" }, user.name) : null), /* @__PURE__ */ React36.createElement("button", { className: "brokr-button-ghost", onClick: handleClose, type: "button" }, /* @__PURE__ */ React36.createElement(CloseIcon, { size: 16 }))), /* @__PURE__ */ React36.createElement(
7867
8111
  "div",
7868
8112
  {
7869
8113
  className: "brokr-chat-messages",
7870
8114
  "data-empty": messages.length === 0
7871
8115
  },
7872
- messages.length === 0 ? /* @__PURE__ */ React36.createElement("div", { className: "brokr-chat-empty" }, /* @__PURE__ */ React36.createElement(SparkIcon, { size: 18 }), /* @__PURE__ */ React36.createElement("div", { className: "brokr-section", style: { gap: "var(--brokr-space-1)" } }, /* @__PURE__ */ React36.createElement("strong", null, "Send a message to chat with the AI."), /* @__PURE__ */ React36.createElement("span", { className: "brokr-copy" }, "Ask a question or drop in a starter prompt below.")), starterPrompts.length > 0 ? /* @__PURE__ */ React36.createElement("div", { className: "brokr-chat-starters" }, starterPrompts.map((prompt) => /* @__PURE__ */ React36.createElement(StarterButton, { key: prompt, prompt, onSelect: handleStarterPrompt }))) : null) : null,
7873
- messages.map((message, index) => /* @__PURE__ */ React36.createElement("div", { className: "brokr-chat-bubble", "data-role": message.role, key: `${message.role}-${index}` }, contentToText(message.content))),
8116
+ messages.length === 0 ? /* @__PURE__ */ React36.createElement("div", { className: "brokr-chat-empty" }, /* @__PURE__ */ React36.createElement(SparkIcon, { size: 18 }), /* @__PURE__ */ React36.createElement("div", { className: "brokr-section", style: { gap: "var(--brokr-space-1)" } }, /* @__PURE__ */ React36.createElement("strong", null, "Send a message to chat with the AI."), /* @__PURE__ */ React36.createElement("span", { className: "brokr-copy" }, "Ask a question or drop in a starter prompt below."))) : null,
8117
+ messages.map((message, index) => {
8118
+ const text = contentToText(message.content);
8119
+ const isTyping = message.role === "assistant" && !text && isSending && index === messages.length - 1;
8120
+ return /* @__PURE__ */ React36.createElement("div", { className: "brokr-chat-bubble", "data-role": message.role, key: `${message.role}-${index}` }, isTyping ? /* @__PURE__ */ React36.createElement("div", { className: "brokr-ai-chat-typing", "aria-label": "AI is typing" }, /* @__PURE__ */ React36.createElement("span", null), /* @__PURE__ */ React36.createElement("span", null), /* @__PURE__ */ React36.createElement("span", null)) : message.role === "assistant" ? /* @__PURE__ */ React36.createElement(MarkdownRenderer, { content: text }) : text);
8121
+ }),
7874
8122
  error ? /* @__PURE__ */ React36.createElement("div", { className: "brokr-inline-message", "data-tone": "error" }, error) : null
7875
8123
  ), /* @__PURE__ */ React36.createElement("form", { className: "brokr-section", onSubmit: handleSubmit, style: { gap: "var(--brokr-space-3)" } }, /* @__PURE__ */ React36.createElement(
7876
8124
  "textarea",
@@ -7882,7 +8130,7 @@ function FabAI({
7882
8130
  rows: 2,
7883
8131
  value: input
7884
8132
  }
7885
- ), /* @__PURE__ */ React36.createElement("button", { className: "brokr-button", disabled: isSending, type: "submit" }, isSending ? "Thinking" : "Send"))) : null);
8133
+ ), /* @__PURE__ */ React36.createElement("button", { className: "brokr-button", disabled: isSending || !input.trim(), type: "submit" }, isSending ? "Thinking" : "Send"))) : null);
7886
8134
  }
7887
8135
 
7888
8136
  // src/react/composites/SmartUpload.tsx
@@ -7890,7 +8138,7 @@ import React37, {
7890
8138
  useCallback as useCallback25,
7891
8139
  useId as useId2,
7892
8140
  useMemo as useMemo25,
7893
- useRef as useRef8,
8141
+ useRef as useRef9,
7894
8142
  useState as useState20
7895
8143
  } from "react";
7896
8144
  function SmartUpload({
@@ -7901,7 +8149,7 @@ function SmartUpload({
7901
8149
  }) {
7902
8150
  const { paymentsMode } = useBrokr();
7903
8151
  const inputId = useId2();
7904
- const inputRef = useRef8(null);
8152
+ const inputRef = useRef9(null);
7905
8153
  const [dragActive, setDragActive] = useState20(false);
7906
8154
  const [error, setError] = useState20(null);
7907
8155
  const [fileName, setFileName] = useState20(null);
@@ -8336,7 +8584,7 @@ var BrokrErrorBoundary = class extends React39.Component {
8336
8584
  };
8337
8585
 
8338
8586
  // src/react/notifications/NotificationBell.tsx
8339
- import React40, { useCallback as useCallback27, useEffect as useEffect13, useMemo as useMemo26, useRef as useRef9, useState as useState22 } from "react";
8587
+ import React40, { useCallback as useCallback27, useEffect as useEffect13, useMemo as useMemo26, useRef as useRef10, useState as useState22 } from "react";
8340
8588
 
8341
8589
  // src/react/notifications/use-notifications.ts
8342
8590
  import { useContext as useContext4 } from "react";
@@ -8386,8 +8634,8 @@ function NotifDropdownItem({
8386
8634
  function NotificationBell() {
8387
8635
  const { notifications, unreadCount, markRead, markAllRead, isLoading, registry } = useNotifications();
8388
8636
  const [open, setOpen] = useState22(false);
8389
- const containerRef = useRef9(null);
8390
- const markReadTimerRef = useRef9(null);
8637
+ const containerRef = useRef10(null);
8638
+ const markReadTimerRef = useRef10(null);
8391
8639
  const toggle = useCallback27(() => setOpen((o) => !o), []);
8392
8640
  useEffect13(() => {
8393
8641
  if (markReadTimerRef.current) {