@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.
- package/dist/feature.js +22 -2
- package/dist/feature.mjs +22 -2
- package/dist/index.js +22 -2
- package/dist/index.mjs +22 -2
- package/dist/next.js +22 -2
- package/dist/next.mjs +22 -2
- package/dist/react-styles.js +273 -94
- package/dist/react-styles.mjs +273 -94
- package/dist/react.js +329 -81
- package/dist/react.mjs +335 -87
- package/dist/runtime.js +22 -2
- package/dist/runtime.mjs +22 -2
- package/dist/src/chat/config.d.ts +2 -0
- package/dist/src/chat/config.d.ts.map +1 -1
- package/dist/src/gateway.d.ts.map +1 -1
- package/dist/src/models.d.ts +2 -0
- package/dist/src/models.d.ts.map +1 -1
- package/dist/src/react/chat/AIChat.d.ts.map +1 -1
- package/dist/src/react/chat/ChatContext.d.ts +3 -6
- package/dist/src/react/chat/ChatContext.d.ts.map +1 -1
- package/dist/src/react/chat/MarkdownRenderer.d.ts.map +1 -1
- package/dist/src/react/chat/ModelSelector.d.ts +1 -1
- package/dist/src/react/chat/ModelSelector.d.ts.map +1 -1
- package/dist/src/react/chat/ThreadSidebar.d.ts.map +1 -1
- package/dist/src/react/chat/memory.d.ts +12 -0
- package/dist/src/react/chat/memory.d.ts.map +1 -0
- package/dist/src/react/chat/types.d.ts +6 -0
- package/dist/src/react/chat/types.d.ts.map +1 -1
- package/dist/src/react/chat/useChat.d.ts +8 -6
- package/dist/src/react/chat/useChat.d.ts.map +1 -1
- package/dist/src/react/composites/FabAI.d.ts +6 -1
- package/dist/src/react/composites/FabAI.d.ts.map +1 -1
- package/dist/src/react/composites/fab-context.d.ts +26 -0
- package/dist/src/react/composites/fab-context.d.ts.map +1 -0
- package/dist/src/react/css/account.d.ts +1 -1
- package/dist/src/react/css/account.d.ts.map +1 -1
- package/dist/src/react/css/auth.d.ts +1 -1
- package/dist/src/react/css/auth.d.ts.map +1 -1
- package/dist/src/react/css/chat-extras.d.ts +1 -1
- package/dist/src/react/css/chat-extras.d.ts.map +1 -1
- package/dist/src/react/css/chat.d.ts +1 -1
- package/dist/src/react/css/chat.d.ts.map +1 -1
- package/dist/src/react/css/composites.d.ts +1 -1
- package/dist/src/react/css/composites.d.ts.map +1 -1
- package/dist/src/react/css/gates.d.ts +1 -1
- package/dist/src/react/css/gates.d.ts.map +1 -1
- package/dist/src/react/css/markdown.d.ts +1 -1
- package/dist/src/react/css/markdown.d.ts.map +1 -1
- package/dist/src/react/css/primitives.d.ts +1 -1
- package/dist/src/react/css/primitives.d.ts.map +1 -1
- package/dist/src/react/css/reset.d.ts +1 -1
- package/dist/src/react/css/reset.d.ts.map +1 -1
- package/dist/src/react/css/responsive.d.ts +1 -1
- package/dist/src/react/css/responsive.d.ts.map +1 -1
- package/dist/src/react/css/skeleton.d.ts +1 -1
- package/dist/src/react/css/skeleton.d.ts.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- 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
|
-
)
|
|
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
|
-
|
|
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)
|
|
6505
|
-
|
|
6506
|
-
|
|
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:
|
|
6688
|
-
...
|
|
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:
|
|
6813
|
+
...explicitModel ? { model: explicitModel } : {},
|
|
6757
6814
|
userId: userId ?? void 0,
|
|
6758
|
-
...
|
|
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: "
|
|
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" },
|
|
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
|
-
|
|
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", {
|
|
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" },
|
|
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-
|
|
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
|
|
7424
|
+
if (/^[\s]*\d+[.)]\s+/.test(line)) {
|
|
7261
7425
|
const items = [];
|
|
7262
|
-
while (i < lines.length && /^[\s]*\d
|
|
7263
|
-
items.push(lines[i].replace(/^[\s]*\d
|
|
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
|
|
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
|
-
|
|
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 [
|
|
7476
|
-
|
|
7477
|
-
|
|
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
|
-
|
|
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
|
-
|
|
7819
|
+
displayModel,
|
|
7650
7820
|
activeProvider,
|
|
7651
|
-
|
|
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
|
-
|
|
7810
|
-
|
|
7811
|
-
|
|
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
|
|
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(
|
|
8065
|
+
setMessages(optimisticMessages);
|
|
7855
8066
|
setInput("");
|
|
7856
|
-
let responseText = "";
|
|
7857
8067
|
if (onSendMessage) {
|
|
7858
|
-
|
|
7859
|
-
|
|
7860
|
-
|
|
7861
|
-
|
|
7862
|
-
|
|
7863
|
-
|
|
7864
|
-
|
|
7865
|
-
|
|
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
|
-
|
|
8107
|
+
}
|
|
8108
|
+
if (!hasDelta) {
|
|
8109
|
+
setMessages((current) => ensureAssistantReply(current, "No response received."));
|
|
8110
|
+
}
|
|
8111
|
+
return;
|
|
7869
8112
|
}
|
|
7870
|
-
|
|
7871
|
-
|
|
7872
|
-
|
|
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
|
-
|
|
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-
|
|
8143
|
+
className: "brokr-chat-fab-trigger",
|
|
7899
8144
|
onClick: toggleOpen,
|
|
7900
8145
|
type: "button"
|
|
7901
8146
|
},
|
|
7902
|
-
/* @__PURE__ */ import_react39.default.createElement(
|
|
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."))
|
|
7911
|
-
messages.map((message, index) =>
|
|
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
|