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