@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.js CHANGED
@@ -4254,6 +4254,7 @@ function getBrokrRootStyle(theme) {
4254
4254
  var import_react3 = __toESM(require("react"));
4255
4255
 
4256
4256
  // src/models.ts
4257
+ var STACK_DEFAULT = "__stack_default__";
4257
4258
  var providers = [
4258
4259
  { id: "deepseek", label: "Deepseek", model: "deepseek-chat", color: "#0EA5E9", free: true, logo: "https://assets.brokr.sh/sdk/deepseek_logo.png" },
4259
4260
  { id: "openai", label: "ChatGPT", model: "gpt-5.4-mini", color: "#10B981", free: false, logo: "https://assets.brokr.sh/sdk/gpt_logo.png" },
@@ -6091,7 +6092,7 @@ function PlanCard({
6091
6092
  const handleClick = (0, import_react22.useCallback)(() => {
6092
6093
  void onSelect(plan.slug);
6093
6094
  }, [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(
6095
+ return /* @__PURE__ */ import_react22.default.createElement("article", { className: "brokr-card brokr-plan-card", "data-highlight": isHighlighted, key: plan.slug }, /* @__PURE__ */ import_react22.default.createElement("div", { className: "brokr-section", style: { gap: "var(--brokr-space-3)" } }, /* @__PURE__ */ import_react22.default.createElement("div", { className: "brokr-brand-row", style: { justifyContent: "space-between" } }, /* @__PURE__ */ import_react22.default.createElement("strong", null, plan.name), plan.isCurrent ? /* @__PURE__ */ import_react22.default.createElement("span", { className: "brokr-badge" }, "Current") : null, isHighlighted && !plan.isCurrent ? /* @__PURE__ */ import_react22.default.createElement("span", { className: "brokr-badge" }, "Recommended") : null), /* @__PURE__ */ import_react22.default.createElement("div", { className: "brokr-plan-price" }, /* @__PURE__ */ import_react22.default.createElement("span", { className: "brokr-plan-value" }, plan.price === 0 ? "$0" : `$${(plan.price / 100).toFixed(0)}`), /* @__PURE__ */ import_react22.default.createElement("span", { className: "brokr-copy" }, "/", plan.interval)), plan.trialDays ? /* @__PURE__ */ import_react22.default.createElement("span", { className: "brokr-copy" }, plan.trialDays, " day trial included.") : null), /* @__PURE__ */ import_react22.default.createElement("ul", { className: "brokr-feature-list" }, features.map(([key, value]) => /* @__PURE__ */ import_react22.default.createElement("li", { className: "brokr-feature-item", key }, /* @__PURE__ */ import_react22.default.createElement(CheckIcon, { size: 14 }), /* @__PURE__ */ import_react22.default.createElement("span", null, featureLabel(key), " \xB7 ", featureValueLabel(value))))), /* @__PURE__ */ import_react22.default.createElement(
6095
6096
  "button",
6096
6097
  {
6097
6098
  className: plan.isCurrent ? "brokr-button-secondary" : "brokr-button",
@@ -6100,7 +6101,7 @@ function PlanCard({
6100
6101
  type: "button"
6101
6102
  },
6102
6103
  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))))));
6104
+ ));
6104
6105
  }
6105
6106
  function Plans({
6106
6107
  columns,
@@ -6448,6 +6449,39 @@ function isSSEResponse(response) {
6448
6449
  return ct.includes("text/event-stream") && response.body !== null;
6449
6450
  }
6450
6451
 
6452
+ // src/react/chat/memory.ts
6453
+ var MAX_MEMORY_CHARS = 2e3;
6454
+ function serializeMemory(obj, prefix = "") {
6455
+ const lines = [];
6456
+ for (const [key, value] of Object.entries(obj)) {
6457
+ if (value === null || value === void 0) continue;
6458
+ const label = prefix ? `${prefix}.${key}` : key;
6459
+ if (Array.isArray(value)) {
6460
+ lines.push(`${label}: ${value.join(", ")}`);
6461
+ } else if (typeof value === "object") {
6462
+ lines.push(...serializeMemory(value, label));
6463
+ } else {
6464
+ lines.push(`${label}: ${String(value)}`);
6465
+ }
6466
+ }
6467
+ return lines;
6468
+ }
6469
+ function buildEffectivePrompt(prompt, memory) {
6470
+ if (!memory || Object.keys(memory).length === 0) return prompt;
6471
+ let block = serializeMemory(memory).join("\n");
6472
+ if (block.length > MAX_MEMORY_CHARS) {
6473
+ block = block.slice(0, MAX_MEMORY_CHARS);
6474
+ if (typeof console !== "undefined") {
6475
+ console.warn("[brokr] memory exceeded 2000 char limit, truncated");
6476
+ }
6477
+ }
6478
+ const memSection = `[User context]
6479
+ ${block}`;
6480
+ return prompt ? `${memSection}
6481
+
6482
+ ${prompt}` : memSection;
6483
+ }
6484
+
6451
6485
  // src/react/chat/useChat.ts
6452
6486
  function makeId(prefix) {
6453
6487
  return `${prefix}_${crypto.randomUUID()}`;
@@ -6465,7 +6499,9 @@ function useChat(config) {
6465
6499
  const {
6466
6500
  endpoint,
6467
6501
  prompt,
6468
- model: activeModel,
6502
+ explicitModel,
6503
+ displayModel: activeModel,
6504
+ memory,
6469
6505
  persist,
6470
6506
  surface,
6471
6507
  subject,
@@ -6501,9 +6537,25 @@ function useChat(config) {
6501
6537
  const scrollContainerRef = (0, import_react31.useRef)(null);
6502
6538
  const sentinelRef = (0, import_react31.useRef)(null);
6503
6539
  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" }));
6540
+ if (isControlled) {
6541
+ return threadsProp?.map((t) => ({
6542
+ id: t.id,
6543
+ title: t.title,
6544
+ updatedAt: t.updatedAt ?? null
6545
+ })) ?? [];
6546
+ }
6547
+ if (isPersist) {
6548
+ return serverThreads.map((t) => ({
6549
+ id: t.id,
6550
+ title: t.title,
6551
+ updatedAt: t.updatedAt ?? null
6552
+ }));
6553
+ }
6554
+ return memConversations.map((c) => ({
6555
+ id: c.id,
6556
+ title: c.title || "New chat",
6557
+ updatedAt: new Date(c.updatedAt).toISOString()
6558
+ }));
6507
6559
  }, [isControlled, isPersist, threadsProp, serverThreads, memConversations]);
6508
6560
  const activeId = (0, import_react31.useMemo)(() => {
6509
6561
  if (isControlled) return activeThreadIdProp ?? null;
@@ -6684,8 +6736,11 @@ function useChat(config) {
6684
6736
  messages: trimToTokenBudget(
6685
6737
  nextMessages.filter((m) => m.role === "user" || m.role === "assistant" && m.content).map(({ content: mc, role }) => ({ role, content: mc }))
6686
6738
  ),
6687
- model: activeModel,
6688
- ...prompt ? { systemPrompt: prompt } : {}
6739
+ ...explicitModel ? { model: explicitModel } : {},
6740
+ ...(() => {
6741
+ const ep = buildEffectivePrompt(prompt, memory);
6742
+ return ep ? { systemPrompt: ep } : {};
6743
+ })()
6689
6744
  })
6690
6745
  });
6691
6746
  if (!response.ok) {
@@ -6730,8 +6785,10 @@ function useChat(config) {
6730
6785
  ensureMemConversation,
6731
6786
  memConversations,
6732
6787
  endpoint,
6788
+ explicitModel,
6733
6789
  activeModel,
6734
6790
  prompt,
6791
+ memory,
6735
6792
  updateMemMessages,
6736
6793
  appendMemDelta,
6737
6794
  animateTitle,
@@ -6753,9 +6810,12 @@ function useChat(config) {
6753
6810
  content,
6754
6811
  surface,
6755
6812
  subject: subject ?? null,
6756
- model: activeModel,
6813
+ ...explicitModel ? { model: explicitModel } : {},
6757
6814
  userId: userId ?? void 0,
6758
- ...prompt ? { systemPrompt: prompt } : {}
6815
+ ...(() => {
6816
+ const ep = buildEffectivePrompt(prompt, memory);
6817
+ return ep ? { systemPrompt: ep } : {};
6818
+ })()
6759
6819
  })
6760
6820
  });
6761
6821
  if (!response.ok) {
@@ -6810,8 +6870,10 @@ function useChat(config) {
6810
6870
  endpoint,
6811
6871
  surface,
6812
6872
  subject,
6873
+ explicitModel,
6813
6874
  activeModel,
6814
6875
  prompt,
6876
+ memory,
6815
6877
  userId,
6816
6878
  setServerMessages,
6817
6879
  setServerActiveId,
@@ -7008,12 +7070,17 @@ function ModelSelector({
7008
7070
  }) {
7009
7071
  const [selectorOpen, setSelectorOpen] = (0, import_react32.useState)(false);
7010
7072
  const selectorRef = (0, import_react32.useRef)(null);
7073
+ const isAuto = activeModel === STACK_DEFAULT || !providers.some((p) => p.model === activeModel);
7011
7074
  const activeProvider = (0, import_react32.useMemo)(
7012
- () => providers.find((p) => p.model === activeModel) ?? providers[0],
7013
- [activeModel]
7075
+ () => isAuto ? void 0 : providers.find((p) => p.model === activeModel) ?? providers[0],
7076
+ [activeModel, isAuto]
7014
7077
  );
7015
7078
  const handleModelSelect = (0, import_react32.useCallback)((model) => {
7016
- setSelectedModel(model);
7079
+ setSelectedModel(model === STACK_DEFAULT ? null : model);
7080
+ setSelectorOpen(false);
7081
+ }, [setSelectedModel]);
7082
+ const handleAutoSelect = (0, import_react32.useCallback)(() => {
7083
+ setSelectedModel(null);
7017
7084
  setSelectorOpen(false);
7018
7085
  }, [setSelectedModel]);
7019
7086
  (0, import_react32.useEffect)(() => {
@@ -7043,15 +7110,26 @@ function ModelSelector({
7043
7110
  onClick: handleToggle,
7044
7111
  type: "button"
7045
7112
  },
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",
7113
+ activeProvider ? /* @__PURE__ */ import_react32.default.createElement("img", { alt: "", className: "brokr-model-logo", src: activeProvider.logo }) : /* @__PURE__ */ import_react32.default.createElement("span", { className: "brokr-model-dot", style: { background: "#6B7280" } }),
7114
+ isAuto ? "Auto" : activeProvider?.label ?? "Model",
7048
7115
  /* @__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(
7116
+ ), selectorOpen ? /* @__PURE__ */ import_react32.default.createElement("div", { className: "brokr-model-dropdown", role: "listbox" }, /* @__PURE__ */ import_react32.default.createElement(
7117
+ "button",
7118
+ {
7119
+ "aria-selected": isAuto,
7120
+ className: "brokr-model-option",
7121
+ "data-active": isAuto,
7122
+ onClick: handleAutoSelect,
7123
+ type: "button"
7124
+ },
7125
+ /* @__PURE__ */ import_react32.default.createElement("span", { className: "brokr-model-dot", style: { background: "#6B7280" } }),
7126
+ /* @__PURE__ */ import_react32.default.createElement("span", { className: "brokr-model-option-label" }, "Stack Default")
7127
+ ), providers.map((p) => /* @__PURE__ */ import_react32.default.createElement(
7050
7128
  ModelOption,
7051
7129
  {
7052
7130
  key: p.id,
7053
7131
  provider: p,
7054
- isActive: p.model === activeModel,
7132
+ isActive: !isAuto && p.model === activeModel,
7055
7133
  isLocked: !availableProviders.some((a) => a.id === p.id),
7056
7134
  onSelect: handleModelSelect,
7057
7135
  onCheckout: checkout
@@ -7071,9 +7149,40 @@ function ThreadItemButton({ id, title, isActive, onSelect }) {
7071
7149
  onClick: handleClick,
7072
7150
  type: "button"
7073
7151
  },
7074
- title
7152
+ /* @__PURE__ */ import_react33.default.createElement(MessageIcon, { size: 12 }),
7153
+ /* @__PURE__ */ import_react33.default.createElement("span", { className: "brokr-ai-chat-conversation-label" }, title)
7075
7154
  );
7076
7155
  }
7156
+ var SIDEBAR_GROUP_ORDER = [
7157
+ "today",
7158
+ "yesterday",
7159
+ "this_week",
7160
+ "this_month",
7161
+ "earlier",
7162
+ "undated"
7163
+ ];
7164
+ var SIDEBAR_GROUP_LABELS = {
7165
+ today: "Today",
7166
+ yesterday: "Yesterday",
7167
+ this_week: "This Week",
7168
+ this_month: "This Month",
7169
+ earlier: "Earlier",
7170
+ undated: "Recent"
7171
+ };
7172
+ function resolveSidebarDateGroup(updatedAt) {
7173
+ if (!updatedAt) return "undated";
7174
+ const parsed = new Date(updatedAt);
7175
+ if (Number.isNaN(parsed.getTime())) return "undated";
7176
+ const now = /* @__PURE__ */ new Date();
7177
+ const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate());
7178
+ const startOfTarget = new Date(parsed.getFullYear(), parsed.getMonth(), parsed.getDate());
7179
+ const diffDays = Math.floor((startOfToday.getTime() - startOfTarget.getTime()) / 864e5);
7180
+ if (diffDays <= 0) return "today";
7181
+ if (diffDays === 1) return "yesterday";
7182
+ if (diffDays < 7) return "this_week";
7183
+ if (diffDays < 30) return "this_month";
7184
+ return "earlier";
7185
+ }
7077
7186
  function ThreadSidebar() {
7078
7187
  const {
7079
7188
  startNewChat,
@@ -7105,14 +7214,31 @@ function ThreadSidebar() {
7105
7214
  const handleRenameChange = (0, import_react33.useCallback)((e) => {
7106
7215
  setRenameValue(e.target.value);
7107
7216
  }, [setRenameValue]);
7217
+ const groupedSidebarItems = (0, import_react33.useMemo)(() => {
7218
+ const grouped = /* @__PURE__ */ new Map();
7219
+ for (const item of displaySidebarItems) {
7220
+ const key = resolveSidebarDateGroup(item.updatedAt);
7221
+ const bucket = grouped.get(key);
7222
+ if (bucket) {
7223
+ bucket.push(item);
7224
+ } else {
7225
+ grouped.set(key, [item]);
7226
+ }
7227
+ }
7228
+ return SIDEBAR_GROUP_ORDER.map((key) => ({
7229
+ key,
7230
+ label: SIDEBAR_GROUP_LABELS[key],
7231
+ items: grouped.get(key) ?? []
7232
+ })).filter((group) => group.items.length > 0);
7233
+ }, [displaySidebarItems]);
7108
7234
  const content = (0, import_react33.useMemo)(() => {
7109
7235
  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 }));
7236
+ return /* @__PURE__ */ import_react33.default.createElement("div", { className: "brokr-ai-chat-sidebar-skeleton" }, /* @__PURE__ */ import_react33.default.createElement(Skeleton, { width: "75%", height: 14, radius: 6 }), /* @__PURE__ */ import_react33.default.createElement(Skeleton, { width: "60%", height: 14, radius: 6 }), /* @__PURE__ */ import_react33.default.createElement(Skeleton, { width: "85%", height: 14, radius: 6 }));
7111
7237
  }
7112
7238
  if (displaySidebarItems.length === 0) {
7113
7239
  return /* @__PURE__ */ import_react33.default.createElement("div", { className: "brokr-ai-chat-sidebar-empty" }, /* @__PURE__ */ import_react33.default.createElement("p", { className: "brokr-ai-chat-sidebar-empty-text" }, "No conversations yet. Start one above."));
7114
7240
  }
7115
- return /* @__PURE__ */ import_react33.default.createElement("div", { className: "brokr-ai-chat-conversations" }, displaySidebarItems.map((item) => renamingId === item.id ? /* @__PURE__ */ import_react33.default.createElement(
7241
+ return /* @__PURE__ */ import_react33.default.createElement("div", { className: "brokr-ai-chat-sidebar-groups" }, groupedSidebarItems.map((group) => /* @__PURE__ */ import_react33.default.createElement("section", { className: "brokr-ai-chat-sidebar-group", key: group.key }, /* @__PURE__ */ import_react33.default.createElement("span", { className: "brokr-ai-chat-sidebar-kicker" }, group.label), /* @__PURE__ */ import_react33.default.createElement("div", { className: "brokr-ai-chat-conversations" }, group.items.map((item) => renamingId === item.id ? /* @__PURE__ */ import_react33.default.createElement(
7116
7242
  "input",
7117
7243
  {
7118
7244
  autoFocus: true,
@@ -7132,19 +7258,20 @@ function ThreadSidebar() {
7132
7258
  isActive: item.id === activeId,
7133
7259
  onSelect: selectThreadAndCloseSidebar
7134
7260
  }
7135
- )));
7261
+ ))))));
7136
7262
  }, [
7137
7263
  threadsLoading,
7138
7264
  displaySidebarItems,
7139
7265
  renamingId,
7140
7266
  renameValue,
7267
+ groupedSidebarItems,
7141
7268
  activeId,
7142
7269
  selectThreadAndCloseSidebar,
7143
7270
  handleRenameBlur,
7144
7271
  handleRenameChange,
7145
7272
  handleRenameKeyDown
7146
7273
  ]);
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);
7274
+ return /* @__PURE__ */ import_react33.default.createElement(import_react33.default.Fragment, null, /* @__PURE__ */ import_react33.default.createElement("button", { className: "brokr-ai-chat-sidebar-new-chat", onClick: handleNewChat, type: "button" }, /* @__PURE__ */ import_react33.default.createElement(MessageIcon, { size: 16 }), "New chat"), content);
7148
7275
  }
7149
7276
 
7150
7277
  // src/react/chat/MessagePane.tsx
@@ -7172,6 +7299,12 @@ function parseInline(text) {
7172
7299
  remaining = remaining.slice(boldMatch[0].length);
7173
7300
  continue;
7174
7301
  }
7302
+ const strikethroughMatch = remaining.match(/^~~(.+?)~~/);
7303
+ if (strikethroughMatch) {
7304
+ nodes.push(/* @__PURE__ */ import_react34.default.createElement("del", { key: key++ }, strikethroughMatch[1]));
7305
+ remaining = remaining.slice(strikethroughMatch[0].length);
7306
+ continue;
7307
+ }
7175
7308
  const italicMatch = remaining.match(/^\*(.+?)\*/);
7176
7309
  if (italicMatch) {
7177
7310
  nodes.push(/* @__PURE__ */ import_react34.default.createElement("em", { key: key++ }, italicMatch[1]));
@@ -7188,7 +7321,7 @@ function parseInline(text) {
7188
7321
  remaining = remaining.slice(linkMatch[0].length);
7189
7322
  continue;
7190
7323
  }
7191
- const nextSpecial = remaining.search(/[`*\[]/);
7324
+ const nextSpecial = remaining.search(/[`*~\[]/);
7192
7325
  if (nextSpecial === -1) {
7193
7326
  nodes.push(remaining);
7194
7327
  break;
@@ -7219,6 +7352,15 @@ function CodeBlock({ code, language }) {
7219
7352
  "Copy"
7220
7353
  )), /* @__PURE__ */ import_react34.default.createElement("pre", { className: "brokr-md-codeblock-pre" }, /* @__PURE__ */ import_react34.default.createElement("code", null, code)));
7221
7354
  }
7355
+ function looksLikeTableSeparator(line) {
7356
+ const trimmed = line.trim();
7357
+ return /^\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)+\|?$/.test(trimmed);
7358
+ }
7359
+ function splitTableRow(line) {
7360
+ const trimmed = line.trim();
7361
+ const withoutEdges = trimmed.replace(/^\|/, "").replace(/\|$/, "");
7362
+ return withoutEdges.split("|").map((cell) => cell.trim());
7363
+ }
7222
7364
  function parseBlocks(text) {
7223
7365
  const blocks = [];
7224
7366
  const lines = text.split("\n");
@@ -7248,22 +7390,44 @@ function parseBlocks(text) {
7248
7390
  i++;
7249
7391
  continue;
7250
7392
  }
7393
+ if (line.includes("|") && i + 1 < lines.length && looksLikeTableSeparator(lines[i + 1] ?? "")) {
7394
+ const headers = splitTableRow(line);
7395
+ const rows = [];
7396
+ i += 2;
7397
+ while (i < lines.length) {
7398
+ const candidate = lines[i] ?? "";
7399
+ if (!candidate.trim() || !candidate.includes("|")) break;
7400
+ rows.push(splitTableRow(candidate));
7401
+ i++;
7402
+ }
7403
+ blocks.push({ type: "table", content: "", headers, rows });
7404
+ continue;
7405
+ }
7406
+ if (/^[\s]*>\s?/.test(line)) {
7407
+ const quoteLines = [];
7408
+ while (i < lines.length && /^[\s]*>\s?/.test(lines[i])) {
7409
+ quoteLines.push(lines[i].replace(/^[\s]*>\s?/, ""));
7410
+ i++;
7411
+ }
7412
+ blocks.push({ type: "quote", content: "", items: quoteLines });
7413
+ continue;
7414
+ }
7251
7415
  if (/^[\s]*[-*]\s+/.test(line)) {
7252
7416
  const items = [];
7253
7417
  while (i < lines.length && /^[\s]*[-*]\s+/.test(lines[i])) {
7254
7418
  items.push(lines[i].replace(/^[\s]*[-*]\s+/, ""));
7255
7419
  i++;
7256
7420
  }
7257
- blocks.push({ type: "list", content: "", items });
7421
+ blocks.push({ type: "list", content: "", items, ordered: false });
7258
7422
  continue;
7259
7423
  }
7260
- if (/^[\s]*\d+\.\s+/.test(line)) {
7424
+ if (/^[\s]*\d+[.)]\s+/.test(line)) {
7261
7425
  const items = [];
7262
- while (i < lines.length && /^[\s]*\d+\.\s+/.test(lines[i])) {
7263
- items.push(lines[i].replace(/^[\s]*\d+\.\s+/, ""));
7426
+ while (i < lines.length && /^[\s]*\d+[.)]\s+/.test(lines[i])) {
7427
+ items.push(lines[i].replace(/^[\s]*\d+[.)]\s+/, ""));
7264
7428
  i++;
7265
7429
  }
7266
- blocks.push({ type: "list", content: "", items });
7430
+ blocks.push({ type: "list", content: "", items, ordered: true });
7267
7431
  continue;
7268
7432
  }
7269
7433
  if (!line.trim()) {
@@ -7272,7 +7436,7 @@ function parseBlocks(text) {
7272
7436
  }
7273
7437
  const paraLines = [line];
7274
7438
  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])) {
7439
+ while (i < lines.length && lines[i].trim() && !lines[i].trimStart().startsWith("```") && !lines[i].match(/^#{1,6}\s/) && !(lines[i].includes("|") && i + 1 < lines.length && looksLikeTableSeparator(lines[i + 1] ?? "")) && !/^(-{3,}|\*{3,}|_{3,})\s*$/.test(lines[i].trim()) && !/^[\s]*>\s?/.test(lines[i]) && !/^[\s]*[-*]\s+/.test(lines[i]) && !/^[\s]*\d+[.)]\s+/.test(lines[i])) {
7276
7440
  paraLines.push(lines[i]);
7277
7441
  i++;
7278
7442
  }
@@ -7292,7 +7456,14 @@ function renderBlocks(blocks) {
7292
7456
  return /* @__PURE__ */ import_react34.default.createElement(Tag, { className: `brokr-md-heading brokr-md-h${block.level}`, key: idx }, parseInline(block.content));
7293
7457
  }
7294
7458
  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))));
7459
+ if (block.ordered) {
7460
+ return /* @__PURE__ */ import_react34.default.createElement("ol", { className: "brokr-md-list brokr-md-list-ordered", key: idx }, block.items?.map((item, li) => /* @__PURE__ */ import_react34.default.createElement("li", { key: li }, parseInline(item))));
7461
+ }
7462
+ return /* @__PURE__ */ import_react34.default.createElement("ul", { className: "brokr-md-list brokr-md-list-unordered", key: idx }, block.items?.map((item, li) => /* @__PURE__ */ import_react34.default.createElement("li", { key: li }, parseInline(item))));
7463
+ case "quote":
7464
+ return /* @__PURE__ */ import_react34.default.createElement("blockquote", { className: "brokr-md-quote", key: idx }, block.items?.map((line, lineIndex) => /* @__PURE__ */ import_react34.default.createElement("p", { className: "brokr-md-quote-line", key: lineIndex }, parseInline(line))));
7465
+ case "table":
7466
+ return /* @__PURE__ */ import_react34.default.createElement("div", { className: "brokr-md-table-wrap", key: idx }, /* @__PURE__ */ import_react34.default.createElement("table", { className: "brokr-md-table" }, /* @__PURE__ */ import_react34.default.createElement("thead", null, /* @__PURE__ */ import_react34.default.createElement("tr", null, block.headers?.map((header, headerIndex) => /* @__PURE__ */ import_react34.default.createElement("th", { key: headerIndex }, parseInline(header))))), /* @__PURE__ */ import_react34.default.createElement("tbody", null, block.rows?.map((row, rowIndex) => /* @__PURE__ */ import_react34.default.createElement("tr", { key: rowIndex }, row.map((cell, cellIndex) => /* @__PURE__ */ import_react34.default.createElement("td", { key: cellIndex }, parseInline(cell))))))));
7296
7467
  case "paragraph":
7297
7468
  default:
7298
7469
  return /* @__PURE__ */ import_react34.default.createElement("p", { className: "brokr-md-paragraph", key: idx }, parseInline(block.content));
@@ -7449,6 +7620,7 @@ function AIChat(inlineProps) {
7449
7620
  subtitle,
7450
7621
  model: modelProp,
7451
7622
  modelSelector,
7623
+ memory,
7452
7624
  variant = 1,
7453
7625
  sidebar: sidebarProp,
7454
7626
  threadMenu: threadMenuProp,
@@ -7472,16 +7644,12 @@ function AIChat(inlineProps) {
7472
7644
  const headerVisible = variant !== 3;
7473
7645
  const threadMenuVisible = threadMenuProp !== void 0 ? threadMenuProp : variant !== 3;
7474
7646
  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;
7647
+ const [userSelectedModel, setUserSelectedModel] = (0, import_react38.useState)(null);
7648
+ const explicitModel = modelProp ?? userSelectedModel ?? void 0;
7649
+ const displayModel = explicitModel ?? providers.find((p) => p.free)?.model ?? providers[0]?.model ?? "";
7650
+ const activeModel = explicitModel ?? STACK_DEFAULT;
7483
7651
  const activeProvider = (0, import_react38.useMemo)(
7484
- () => providers.find((p) => p.model === activeModel) ?? providers[0],
7652
+ () => activeModel === STACK_DEFAULT ? void 0 : providers.find((p) => p.model === activeModel) ?? providers[0],
7485
7653
  [activeModel]
7486
7654
  );
7487
7655
  const hasBalance = (0, import_react38.useMemo)(
@@ -7495,7 +7663,9 @@ function AIChat(inlineProps) {
7495
7663
  const chat = useChat({
7496
7664
  endpoint,
7497
7665
  prompt,
7498
- model: activeModel,
7666
+ explicitModel,
7667
+ displayModel,
7668
+ memory,
7499
7669
  persist,
7500
7670
  surface,
7501
7671
  subject,
@@ -7603,10 +7773,10 @@ function AIChat(inlineProps) {
7603
7773
  activeThread: chat.activeThread,
7604
7774
  renderedTitle: finalTitle,
7605
7775
  isTitleLoading: chat.isTitleLoading,
7606
- activeModel,
7776
+ activeModel: displayModel,
7607
7777
  activeProvider,
7608
- selectedModel,
7609
- setSelectedModel,
7778
+ selectedModel: displayModel,
7779
+ setSelectedModel: setUserSelectedModel,
7610
7780
  availableProviders,
7611
7781
  sendMessage: chat.sendMessage,
7612
7782
  startNewChat: chat.startNewChat,
@@ -7646,9 +7816,9 @@ function AIChat(inlineProps) {
7646
7816
  }), [
7647
7817
  chat,
7648
7818
  finalTitle,
7649
- activeModel,
7819
+ displayModel,
7650
7820
  activeProvider,
7651
- selectedModel,
7821
+ userSelectedModel,
7652
7822
  availableProviders,
7653
7823
  sidebarOpen,
7654
7824
  closeSidebar,
@@ -7685,8 +7855,8 @@ function AIChat(inlineProps) {
7685
7855
  threadMenuRef,
7686
7856
  threadMenuVisible,
7687
7857
  setThreadMenuOpenId,
7688
- activeModel,
7689
- setSelectedModel,
7858
+ activeModel: displayModel,
7859
+ setSelectedModel: setUserSelectedModel,
7690
7860
  availableProviders,
7691
7861
  startRename: chatState.startRename,
7692
7862
  deleteThread: chat.deleteThread
@@ -7806,23 +7976,62 @@ function ChatHeader({
7806
7976
 
7807
7977
  // src/react/composites/FabAI.tsx
7808
7978
  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);
7979
+
7980
+ // src/react/composites/fab-context.ts
7981
+ function buildFabSystemPrompt(appContext, brandName, existingPrompt) {
7982
+ if (appContext === false) return existingPrompt;
7983
+ const name = appContext?.name ?? brandName;
7984
+ if (!name && !appContext) return existingPrompt;
7985
+ const parts = [];
7986
+ if (name) parts.push(`You are an AI assistant embedded in ${name}.`);
7987
+ if (appContext?.description) parts.push(`This app is ${appContext.description}.`);
7988
+ if (appContext?.currentPage) parts.push(`The user is currently on: ${appContext.currentPage}.`);
7989
+ if (appContext?.facts?.length) parts.push(...appContext.facts);
7990
+ const autoPrompt = parts.join(" ");
7991
+ return existingPrompt ? `${autoPrompt}
7992
+
7993
+ ${existingPrompt}` : autoPrompt;
7994
+ }
7995
+
7996
+ // src/react/composites/FabAI.tsx
7997
+ function ensureAssistantReply(messages, content) {
7998
+ const normalized = content.trim() ? content : "No response received.";
7999
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
8000
+ if (messages[index]?.role !== "assistant") continue;
8001
+ const next = [...messages];
8002
+ next[index] = { ...next[index], content: normalized };
8003
+ return next;
8004
+ }
8005
+ return [...messages, { role: "assistant", content: normalized }];
8006
+ }
8007
+ function appendAssistantDelta(messages, delta) {
8008
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
8009
+ if (messages[index]?.role !== "assistant") continue;
8010
+ const next = [...messages];
8011
+ next[index] = {
8012
+ ...next[index],
8013
+ content: `${contentToText(next[index].content)}${delta}`
8014
+ };
8015
+ return next;
8016
+ }
8017
+ return [...messages, { role: "assistant", content: delta }];
7812
8018
  }
7813
8019
  function FabAI({
8020
+ appContext,
7814
8021
  model,
8022
+ memory,
7815
8023
  onSendMessage,
7816
8024
  position = "bottom-right",
7817
- starterPrompts = [],
7818
8025
  systemPrompt
7819
8026
  }) {
7820
- const { can, user } = useBrokr();
8027
+ const { can, user, theme } = useBrokr();
8028
+ const brandName = theme?.brand?.name;
7821
8029
  const [isOpen, setIsOpen] = (0, import_react39.useState)(false);
7822
8030
  const [input, setInput] = (0, import_react39.useState)("");
7823
8031
  const [error, setError] = (0, import_react39.useState)(null);
7824
8032
  const [isSending, setIsSending] = (0, import_react39.useState)(false);
7825
8033
  const [messages, setMessages] = (0, import_react39.useState)([]);
8034
+ const conversationIdRef = (0, import_react39.useRef)(`fab_${crypto.randomUUID()}`);
7826
8035
  const launcherStyle = (0, import_react39.useMemo)(
7827
8036
  () => ({
7828
8037
  left: position === "bottom-left" ? "var(--brokr-space-6)" : void 0,
@@ -7846,37 +8055,75 @@ function FabAI({
7846
8055
  }, []);
7847
8056
  const sendPrompt = (0, import_react39.useCallback)(async (prompt) => {
7848
8057
  const nextPrompt = prompt.trim();
7849
- if (!nextPrompt) return;
7850
- const nextMessages = [...messages, { role: "user", content: nextPrompt }];
8058
+ if (!nextPrompt || isSending) return;
8059
+ const userMessage = { role: "user", content: nextPrompt };
8060
+ const nextMessages = [...messages, userMessage];
8061
+ const optimisticMessages = [...nextMessages, { role: "assistant", content: "" }];
7851
8062
  try {
7852
8063
  setError(null);
7853
8064
  setIsSending(true);
7854
- setMessages(nextMessages);
8065
+ setMessages(optimisticMessages);
7855
8066
  setInput("");
7856
- let responseText = "";
7857
8067
  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
8068
+ const responseText2 = await onSendMessage({ messages: nextMessages, model, systemPrompt });
8069
+ setMessages((current) => ensureAssistantReply(current, responseText2));
8070
+ return;
8071
+ }
8072
+ const response = await fetch("/api/brokr/chat", {
8073
+ method: "POST",
8074
+ credentials: "include",
8075
+ headers: { "Content-Type": "application/json" },
8076
+ body: JSON.stringify({
8077
+ conversationId: conversationIdRef.current,
8078
+ messages: trimToTokenBudget(
8079
+ nextMessages.filter((message) => message.role === "user" || message.role === "assistant" && Boolean(contentToText(message.content))).map((message) => ({
8080
+ role: message.role,
8081
+ content: contentToText(message.content)
8082
+ }))
8083
+ ),
8084
+ ...model !== void 0 ? { model } : {},
8085
+ ...(() => {
8086
+ const withContext = buildFabSystemPrompt(appContext, brandName, systemPrompt);
8087
+ const ep = buildEffectivePrompt(withContext, memory);
8088
+ return ep ? { systemPrompt: ep } : {};
8089
+ })()
8090
+ })
8091
+ });
8092
+ if (!response.ok) {
8093
+ const payload2 = await response.json().catch(() => ({}));
8094
+ throw new Error(payload2.message ?? payload2.error ?? `Chat failed (${response.status})`);
8095
+ }
8096
+ if (isSSEResponse(response)) {
8097
+ let hasDelta = false;
8098
+ for await (const event of parseSSEStream(response)) {
8099
+ if (event.type === "conversation") {
8100
+ conversationIdRef.current = event.id;
8101
+ } else if (event.type === "delta") {
8102
+ hasDelta = true;
8103
+ setMessages((current) => appendAssistantDelta(current, event.delta));
8104
+ } else if (event.type === "error") {
8105
+ throw new Error(event.message);
7866
8106
  }
7867
- );
7868
- responseText = payload.content ?? payload.response ?? "";
8107
+ }
8108
+ if (!hasDelta) {
8109
+ setMessages((current) => ensureAssistantReply(current, "No response received."));
8110
+ }
8111
+ return;
7869
8112
  }
7870
- setMessages((current) => [...current, {
7871
- role: "assistant",
7872
- content: responseText || "No response received."
7873
- }]);
8113
+ const payload = await response.json().catch(() => ({}));
8114
+ if (payload.error || payload.message) {
8115
+ throw new Error(payload.message ?? payload.error ?? "AI request failed.");
8116
+ }
8117
+ const responseText = payload.content ?? payload.response ?? payload.text ?? "";
8118
+ setMessages((current) => ensureAssistantReply(current, responseText));
7874
8119
  } catch (cause) {
7875
- setError(cause instanceof Error ? cause.message : "Could not send message.");
8120
+ const message = cause instanceof Error ? cause.message : "Could not send message.";
8121
+ setError(message);
8122
+ setMessages((current) => ensureAssistantReply(current, `Error: ${message}`));
7876
8123
  } finally {
7877
8124
  setIsSending(false);
7878
8125
  }
7879
- }, [messages, model, onSendMessage, systemPrompt]);
8126
+ }, [isSending, messages, model, onSendMessage, systemPrompt]);
7880
8127
  const handleSubmit = (0, import_react39.useCallback)(async (event) => {
7881
8128
  event.preventDefault();
7882
8129
  await sendPrompt(input);
@@ -7887,28 +8134,29 @@ function FabAI({
7887
8134
  void sendPrompt(input);
7888
8135
  }
7889
8136
  }, [input, sendPrompt]);
7890
- const handleStarterPrompt = (0, import_react39.useCallback)((prompt) => {
7891
- void sendPrompt(prompt);
7892
- }, [sendPrompt]);
7893
8137
  return /* @__PURE__ */ import_react39.default.createElement(import_react39.default.Fragment, null, /* @__PURE__ */ import_react39.default.createElement("div", { className: "brokr-chat-fab", style: launcherStyle }, /* @__PURE__ */ import_react39.default.createElement(
7894
8138
  "button",
7895
8139
  {
8140
+ "aria-label": isOpen ? "Close AI chat" : "Open AI chat",
7896
8141
  "aria-expanded": isOpen,
7897
8142
  "aria-haspopup": "dialog",
7898
- className: "brokr-button",
8143
+ className: "brokr-chat-fab-trigger",
7899
8144
  onClick: toggleOpen,
7900
8145
  type: "button"
7901
8146
  },
7902
- /* @__PURE__ */ import_react39.default.createElement(MessageIcon, { size: 16 }),
7903
- "Ask AI"
8147
+ /* @__PURE__ */ import_react39.default.createElement(SparkIcon, { size: 18 })
7904
8148
  )), isOpen ? /* @__PURE__ */ import_react39.default.createElement("div", { className: "brokr-panel brokr-chat-panel", role: "dialog" }, /* @__PURE__ */ import_react39.default.createElement("div", { className: "brokr-brand-row", style: { justifyContent: "space-between" } }, /* @__PURE__ */ import_react39.default.createElement("div", { className: "brokr-section", style: { gap: "var(--brokr-space-1)" } }, /* @__PURE__ */ import_react39.default.createElement("strong", null, "AI Chat"), user?.name ? /* @__PURE__ */ import_react39.default.createElement("span", { className: "brokr-copy" }, user.name) : null), /* @__PURE__ */ import_react39.default.createElement("button", { className: "brokr-button-ghost", onClick: handleClose, type: "button" }, /* @__PURE__ */ import_react39.default.createElement(CloseIcon, { size: 16 }))), /* @__PURE__ */ import_react39.default.createElement(
7905
8149
  "div",
7906
8150
  {
7907
8151
  className: "brokr-chat-messages",
7908
8152
  "data-empty": messages.length === 0
7909
8153
  },
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))),
8154
+ messages.length === 0 ? /* @__PURE__ */ import_react39.default.createElement("div", { className: "brokr-chat-empty" }, /* @__PURE__ */ import_react39.default.createElement(SparkIcon, { size: 18 }), /* @__PURE__ */ import_react39.default.createElement("div", { className: "brokr-section", style: { gap: "var(--brokr-space-1)" } }, /* @__PURE__ */ import_react39.default.createElement("strong", null, "Send a message to chat with the AI."), /* @__PURE__ */ import_react39.default.createElement("span", { className: "brokr-copy" }, "Ask a question or drop in a starter prompt below."))) : null,
8155
+ messages.map((message, index) => {
8156
+ const text = contentToText(message.content);
8157
+ const isTyping = message.role === "assistant" && !text && isSending && index === messages.length - 1;
8158
+ return /* @__PURE__ */ import_react39.default.createElement("div", { className: "brokr-chat-bubble", "data-role": message.role, key: `${message.role}-${index}` }, isTyping ? /* @__PURE__ */ import_react39.default.createElement("div", { className: "brokr-ai-chat-typing", "aria-label": "AI is typing" }, /* @__PURE__ */ import_react39.default.createElement("span", null), /* @__PURE__ */ import_react39.default.createElement("span", null), /* @__PURE__ */ import_react39.default.createElement("span", null)) : message.role === "assistant" ? /* @__PURE__ */ import_react39.default.createElement(MarkdownRenderer, { content: text }) : text);
8159
+ }),
7912
8160
  error ? /* @__PURE__ */ import_react39.default.createElement("div", { className: "brokr-inline-message", "data-tone": "error" }, error) : null
7913
8161
  ), /* @__PURE__ */ import_react39.default.createElement("form", { className: "brokr-section", onSubmit: handleSubmit, style: { gap: "var(--brokr-space-3)" } }, /* @__PURE__ */ import_react39.default.createElement(
7914
8162
  "textarea",
@@ -7920,7 +8168,7 @@ function FabAI({
7920
8168
  rows: 2,
7921
8169
  value: input
7922
8170
  }
7923
- ), /* @__PURE__ */ import_react39.default.createElement("button", { className: "brokr-button", disabled: isSending, type: "submit" }, isSending ? "Thinking" : "Send"))) : null);
8171
+ ), /* @__PURE__ */ import_react39.default.createElement("button", { className: "brokr-button", disabled: isSending || !input.trim(), type: "submit" }, isSending ? "Thinking" : "Send"))) : null);
7924
8172
  }
7925
8173
 
7926
8174
  // src/react/composites/SmartUpload.tsx