@brokr/sdk 2.0.0 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/feature.js +22 -2
- package/dist/feature.mjs +22 -2
- package/dist/index.js +22 -2
- package/dist/index.mjs +22 -2
- package/dist/next.js +22 -2
- package/dist/next.mjs +22 -2
- package/dist/react-notifications.js +63 -50
- package/dist/react-notifications.mjs +63 -50
- package/dist/react-styles.js +386 -169
- package/dist/react-styles.mjs +386 -169
- package/dist/react-theme.js +6 -4
- package/dist/react-theme.mjs +6 -4
- package/dist/react.js +644 -317
- package/dist/react.mjs +682 -355
- 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/account/AccountPanel.d.ts.map +1 -1
- package/dist/src/react/account/UserButton.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/ChatInput.d.ts.map +1 -1
- package/dist/src/react/chat/MarkdownRenderer.d.ts.map +1 -1
- package/dist/src/react/chat/MessagePane.d.ts.map +1 -1
- package/dist/src/react/chat/ModelSelector.d.ts +1 -1
- package/dist/src/react/chat/ModelSelector.d.ts.map +1 -1
- package/dist/src/react/chat/ThreadSidebar.d.ts.map +1 -1
- package/dist/src/react/chat/memory.d.ts +12 -0
- package/dist/src/react/chat/memory.d.ts.map +1 -0
- package/dist/src/react/chat/types.d.ts +6 -0
- package/dist/src/react/chat/types.d.ts.map +1 -1
- package/dist/src/react/chat/useChat.d.ts +8 -6
- package/dist/src/react/chat/useChat.d.ts.map +1 -1
- package/dist/src/react/composites/FabAI.d.ts +6 -1
- package/dist/src/react/composites/FabAI.d.ts.map +1 -1
- package/dist/src/react/composites/fab-context.d.ts +26 -0
- package/dist/src/react/composites/fab-context.d.ts.map +1 -0
- package/dist/src/react/css/account.d.ts +1 -1
- package/dist/src/react/css/account.d.ts.map +1 -1
- package/dist/src/react/css/auth.d.ts +1 -1
- package/dist/src/react/css/auth.d.ts.map +1 -1
- package/dist/src/react/css/chat-extras.d.ts +1 -1
- package/dist/src/react/css/chat-extras.d.ts.map +1 -1
- package/dist/src/react/css/chat.d.ts +1 -1
- package/dist/src/react/css/chat.d.ts.map +1 -1
- package/dist/src/react/css/composites.d.ts +1 -1
- package/dist/src/react/css/composites.d.ts.map +1 -1
- package/dist/src/react/css/gates.d.ts +1 -1
- package/dist/src/react/css/gates.d.ts.map +1 -1
- package/dist/src/react/css/markdown.d.ts +1 -1
- package/dist/src/react/css/markdown.d.ts.map +1 -1
- package/dist/src/react/css/notifications.d.ts +1 -1
- package/dist/src/react/css/notifications.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/tokens.d.ts +1 -1
- package/dist/src/react/css/tokens.d.ts.map +1 -1
- package/dist/src/react/notifications/NotificationBell.d.ts.map +1 -1
- package/dist/src/react/notifications/NotificationList.d.ts.map +1 -1
- package/dist/src/react/notifications/Toast.d.ts.map +1 -1
- package/dist/src/react/payments/Plans.d.ts.map +1 -1
- package/dist/src/react/theme.d.ts.map +1 -1
- package/dist/src/react/types.d.ts +4 -4
- package/dist/src/react/types.d.ts.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
package/dist/react.mjs
CHANGED
|
@@ -4173,10 +4173,12 @@ function getBrokrRootStyle(theme) {
|
|
|
4173
4173
|
...theme.colors.error ? { ["--brokr-error"]: theme.colors.error } : {},
|
|
4174
4174
|
...theme.colors.success ? { ["--brokr-success"]: theme.colors.success } : {},
|
|
4175
4175
|
...theme.colors.warning ? { ["--brokr-warning"]: theme.colors.warning } : {},
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4176
|
+
// Toast vars always emitted with smart defaults — toasts stay light in dark mode.
|
|
4177
|
+
// Users can override via theme.colors.toastBg etc.
|
|
4178
|
+
["--brokr-toast-bg"]: theme.colors.toastBg ?? "#ffffff",
|
|
4179
|
+
["--brokr-toast-text"]: theme.colors.toastText ?? "#0a0a0a",
|
|
4180
|
+
["--brokr-toast-text-secondary"]: theme.colors.toastTextSecondary ?? "#52525b",
|
|
4181
|
+
["--brokr-toast-border"]: theme.colors.toastBorder ?? "#e4e4e7",
|
|
4180
4182
|
["--brokr-radius-card"]: `${theme.radii.card}px`,
|
|
4181
4183
|
["--brokr-radius-input"]: `${theme.radii.input}px`,
|
|
4182
4184
|
["--brokr-radius-button"]: `${theme.radii.button}px`
|
|
@@ -4195,6 +4197,7 @@ import React2, {
|
|
|
4195
4197
|
} from "react";
|
|
4196
4198
|
|
|
4197
4199
|
// src/models.ts
|
|
4200
|
+
var STACK_DEFAULT = "__stack_default__";
|
|
4198
4201
|
var providers = [
|
|
4199
4202
|
{ id: "deepseek", label: "Deepseek", model: "deepseek-chat", color: "#0EA5E9", free: true, logo: "https://assets.brokr.sh/sdk/deepseek_logo.png" },
|
|
4200
4203
|
{ id: "openai", label: "ChatGPT", model: "gpt-5.4-mini", color: "#10B981", free: false, logo: "https://assets.brokr.sh/sdk/gpt_logo.png" },
|
|
@@ -4298,6 +4301,37 @@ function resolveNotificationType(registry, type, data) {
|
|
|
4298
4301
|
|
|
4299
4302
|
// src/react/notifications/Toast.tsx
|
|
4300
4303
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
4304
|
+
function ToastItem({
|
|
4305
|
+
notification,
|
|
4306
|
+
exiting,
|
|
4307
|
+
registry,
|
|
4308
|
+
onDismiss
|
|
4309
|
+
}) {
|
|
4310
|
+
const handleDismiss = useCallback(() => onDismiss(notification.id), [notification.id, onDismiss]);
|
|
4311
|
+
const notifData = notification.data ?? {};
|
|
4312
|
+
const notifType = notifData.type ?? "default";
|
|
4313
|
+
const resolved = resolveNotificationType(registry, notifType, notifData);
|
|
4314
|
+
return /* @__PURE__ */ React.createElement(
|
|
4315
|
+
"div",
|
|
4316
|
+
{
|
|
4317
|
+
className: `brokr-toast brokr-toast--${notification.variant}${exiting ? " brokr-toast--exit" : ""}`,
|
|
4318
|
+
role: "alert",
|
|
4319
|
+
"aria-live": "polite"
|
|
4320
|
+
},
|
|
4321
|
+
resolved.image ? /* @__PURE__ */ React.createElement("img", { src: resolved.image.url, alt: resolved.image.alt, className: "brokr-toast-provider-logo" }) : /* @__PURE__ */ React.createElement("span", { className: `brokr-toast-icon brokr-toast-icon--${notification.variant}` }),
|
|
4322
|
+
/* @__PURE__ */ React.createElement("div", { className: "brokr-toast-content" }, /* @__PURE__ */ React.createElement("span", { className: "brokr-toast-title" }, notification.title), /* @__PURE__ */ React.createElement("span", { className: "brokr-toast-message" }, notification.message)),
|
|
4323
|
+
/* @__PURE__ */ React.createElement(
|
|
4324
|
+
"button",
|
|
4325
|
+
{
|
|
4326
|
+
type: "button",
|
|
4327
|
+
className: "brokr-toast-dismiss",
|
|
4328
|
+
onClick: handleDismiss,
|
|
4329
|
+
"aria-label": "Dismiss notification"
|
|
4330
|
+
},
|
|
4331
|
+
/* @__PURE__ */ React.createElement("svg", { "aria-hidden": "true", width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }, /* @__PURE__ */ React.createElement("path", { d: "M18 6 6 18M6 6l12 12" }))
|
|
4332
|
+
)
|
|
4333
|
+
);
|
|
4334
|
+
}
|
|
4301
4335
|
var DEFAULT_DURATION = 4e3;
|
|
4302
4336
|
var DEFAULT_MAX_VISIBLE = 3;
|
|
4303
4337
|
var EXIT_ANIMATION_MS = 300;
|
|
@@ -4308,6 +4342,26 @@ function ToastLayer({ toasts, config, registry, onDismiss }) {
|
|
|
4308
4342
|
const duration = config.toast?.duration ?? DEFAULT_DURATION;
|
|
4309
4343
|
const maxVisible = config.toast?.maxVisible ?? DEFAULT_MAX_VISIBLE;
|
|
4310
4344
|
const position = config.toast?.position ?? "top-right";
|
|
4345
|
+
const onDismissRef = useRef(onDismiss);
|
|
4346
|
+
onDismissRef.current = onDismiss;
|
|
4347
|
+
const exitingRef = useRef(/* @__PURE__ */ new Set());
|
|
4348
|
+
const dismissToast = useCallback((id) => {
|
|
4349
|
+
if (exitingRef.current.has(id)) return;
|
|
4350
|
+
exitingRef.current.add(id);
|
|
4351
|
+
setEntries(
|
|
4352
|
+
(prev) => prev.map((e) => e.notification.id === id ? { ...e, exiting: true } : e)
|
|
4353
|
+
);
|
|
4354
|
+
setTimeout(() => {
|
|
4355
|
+
setEntries((prev) => prev.filter((e) => e.notification.id !== id));
|
|
4356
|
+
onDismissRef.current(id);
|
|
4357
|
+
exitingRef.current.delete(id);
|
|
4358
|
+
}, EXIT_ANIMATION_MS);
|
|
4359
|
+
const timer = timersRef.current.get(id);
|
|
4360
|
+
if (timer) {
|
|
4361
|
+
clearTimeout(timer);
|
|
4362
|
+
timersRef.current.delete(id);
|
|
4363
|
+
}
|
|
4364
|
+
}, []);
|
|
4311
4365
|
useEffect(() => {
|
|
4312
4366
|
for (const toast of toasts) {
|
|
4313
4367
|
if (processedRef.current.has(toast.id)) continue;
|
|
@@ -4326,21 +4380,7 @@ function ToastLayer({ toasts, config, registry, onDismiss }) {
|
|
|
4326
4380
|
timersRef.current.set(toast.id, timer);
|
|
4327
4381
|
}
|
|
4328
4382
|
}
|
|
4329
|
-
}, [toasts, duration, maxVisible]);
|
|
4330
|
-
const dismissToast = useCallback((id) => {
|
|
4331
|
-
setEntries(
|
|
4332
|
-
(prev) => prev.map((e) => e.notification.id === id ? { ...e, exiting: true } : e)
|
|
4333
|
-
);
|
|
4334
|
-
setTimeout(() => {
|
|
4335
|
-
setEntries((prev) => prev.filter((e) => e.notification.id !== id));
|
|
4336
|
-
onDismiss(id);
|
|
4337
|
-
}, EXIT_ANIMATION_MS);
|
|
4338
|
-
const timer = timersRef.current.get(id);
|
|
4339
|
-
if (timer) {
|
|
4340
|
-
clearTimeout(timer);
|
|
4341
|
-
timersRef.current.delete(id);
|
|
4342
|
-
}
|
|
4343
|
-
}, [onDismiss]);
|
|
4383
|
+
}, [toasts, duration, maxVisible, dismissToast]);
|
|
4344
4384
|
useEffect(() => {
|
|
4345
4385
|
return () => {
|
|
4346
4386
|
for (const timer of timersRef.current.values()) {
|
|
@@ -4360,39 +4400,16 @@ function ToastLayer({ toasts, config, registry, onDismiss }) {
|
|
|
4360
4400
|
return map[position] ?? "brokr-toast-layer--bottom-right";
|
|
4361
4401
|
}, [position]);
|
|
4362
4402
|
if (entries.length === 0) return null;
|
|
4363
|
-
return /* @__PURE__ */ React.createElement("div", { className: `brokr-toast-layer ${positionClass}` }, entries.map(({ notification, exiting }) =>
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
"aria-live": "polite"
|
|
4374
|
-
},
|
|
4375
|
-
resolved.image ? /* @__PURE__ */ React.createElement(
|
|
4376
|
-
"img",
|
|
4377
|
-
{
|
|
4378
|
-
src: resolved.image.url,
|
|
4379
|
-
alt: resolved.image.alt,
|
|
4380
|
-
className: "brokr-toast-provider-logo"
|
|
4381
|
-
}
|
|
4382
|
-
) : /* @__PURE__ */ React.createElement("span", { className: `brokr-toast-icon brokr-toast-icon--${notification.variant}` }),
|
|
4383
|
-
/* @__PURE__ */ React.createElement("div", { className: "brokr-toast-content" }, /* @__PURE__ */ React.createElement("span", { className: "brokr-toast-title" }, notification.title), /* @__PURE__ */ React.createElement("span", { className: "brokr-toast-message" }, notification.message)),
|
|
4384
|
-
/* @__PURE__ */ React.createElement(
|
|
4385
|
-
"button",
|
|
4386
|
-
{
|
|
4387
|
-
type: "button",
|
|
4388
|
-
className: "brokr-toast-dismiss",
|
|
4389
|
-
onClick: () => dismissToast(notification.id),
|
|
4390
|
-
"aria-label": "Dismiss notification"
|
|
4391
|
-
},
|
|
4392
|
-
/* @__PURE__ */ React.createElement("svg", { "aria-hidden": "true", width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }, /* @__PURE__ */ React.createElement("path", { d: "M18 6 6 18M6 6l12 12" }))
|
|
4393
|
-
)
|
|
4394
|
-
);
|
|
4395
|
-
}));
|
|
4403
|
+
return /* @__PURE__ */ React.createElement("div", { className: `brokr-toast-layer ${positionClass}` }, entries.map(({ notification, exiting }) => /* @__PURE__ */ React.createElement(
|
|
4404
|
+
ToastItem,
|
|
4405
|
+
{
|
|
4406
|
+
key: notification.id,
|
|
4407
|
+
notification,
|
|
4408
|
+
exiting,
|
|
4409
|
+
registry,
|
|
4410
|
+
onDismiss: dismissToast
|
|
4411
|
+
}
|
|
4412
|
+
)));
|
|
4396
4413
|
}
|
|
4397
4414
|
|
|
4398
4415
|
// src/react/notifications/provider.tsx
|
|
@@ -5515,6 +5532,26 @@ function filterDefined(defaults, overrides) {
|
|
|
5515
5532
|
}
|
|
5516
5533
|
|
|
5517
5534
|
// src/react/account/AccountPanel.tsx
|
|
5535
|
+
function AccountTabButton({
|
|
5536
|
+
tab,
|
|
5537
|
+
active,
|
|
5538
|
+
icon,
|
|
5539
|
+
label,
|
|
5540
|
+
onSelect
|
|
5541
|
+
}) {
|
|
5542
|
+
const handleClick = useCallback8(() => onSelect(tab), [tab, onSelect]);
|
|
5543
|
+
return /* @__PURE__ */ React10.createElement(
|
|
5544
|
+
"button",
|
|
5545
|
+
{
|
|
5546
|
+
className: "brokr-account-tab",
|
|
5547
|
+
"data-active": active,
|
|
5548
|
+
onClick: handleClick,
|
|
5549
|
+
type: "button"
|
|
5550
|
+
},
|
|
5551
|
+
/* @__PURE__ */ React10.createElement("span", { className: "brokr-account-tab-icon" }, icon),
|
|
5552
|
+
/* @__PURE__ */ React10.createElement("span", null, label)
|
|
5553
|
+
);
|
|
5554
|
+
}
|
|
5518
5555
|
var TAB_META = {
|
|
5519
5556
|
general: {
|
|
5520
5557
|
label: "General"
|
|
@@ -5646,16 +5683,15 @@ function AccountPanel(inlineProps) {
|
|
|
5646
5683
|
}
|
|
5647
5684
|
}, []);
|
|
5648
5685
|
return /* @__PURE__ */ React10.createElement("section", { className: "brokr-account-panel", "data-density": density }, /* @__PURE__ */ React10.createElement("aside", { className: "brokr-account-sidebar" }, groupedTabs.map((group) => /* @__PURE__ */ React10.createElement("div", { className: "brokr-account-sidebar-group", key: group.label }, /* @__PURE__ */ React10.createElement("span", { className: "brokr-account-sidebar-kicker" }, group.label), /* @__PURE__ */ React10.createElement("nav", { className: "brokr-account-tab-list", "aria-label": `${group.label} settings` }, group.tabs.map((tab) => /* @__PURE__ */ React10.createElement(
|
|
5649
|
-
|
|
5686
|
+
AccountTabButton,
|
|
5650
5687
|
{
|
|
5651
|
-
className: "brokr-account-tab",
|
|
5652
|
-
"data-active": activeTab === tab,
|
|
5653
5688
|
key: tab,
|
|
5654
|
-
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
|
|
5689
|
+
tab,
|
|
5690
|
+
active: activeTab === tab,
|
|
5691
|
+
icon: getTabIcon(tab),
|
|
5692
|
+
label: TAB_META[tab].label,
|
|
5693
|
+
onSelect: setActiveTab
|
|
5694
|
+
}
|
|
5659
5695
|
)))))), /* @__PURE__ */ React10.createElement("div", { className: "brokr-account-surface" }, /* @__PURE__ */ React10.createElement("header", { className: "brokr-account-surface-header" }, /* @__PURE__ */ React10.createElement("h2", { className: "brokr-title" }, activeTabMeta.label)), activeTab === "general" ? /* @__PURE__ */ React10.createElement("form", { className: "brokr-account-form", onSubmit: handleSaveProfile }, /* @__PURE__ */ React10.createElement("section", { className: "brokr-account-card" }, /* @__PURE__ */ React10.createElement("div", { className: "brokr-account-photo-row" }, /* @__PURE__ */ React10.createElement(ProfilePhotoButton, { size: 88 }), /* @__PURE__ */ React10.createElement("div", { className: "brokr-account-photo-copy" }, /* @__PURE__ */ React10.createElement("strong", null, "Profile photo"), /* @__PURE__ */ React10.createElement("span", { className: "brokr-copy" }, "PNG, JPEG, and GIF under 10MB"), /* @__PURE__ */ React10.createElement("span", { className: "brokr-account-photo-hint" }, /* @__PURE__ */ React10.createElement(UploadIcon, { size: 14 }), "Upload new picture")))), /* @__PURE__ */ React10.createElement("section", { className: "brokr-account-card brokr-account-field-list" }, /* @__PURE__ */ React10.createElement("div", { className: "brokr-account-field-row" }, /* @__PURE__ */ React10.createElement("div", { className: "brokr-account-field-head" }, /* @__PURE__ */ React10.createElement("label", { className: "brokr-label", htmlFor: "brokr-account-name" }, "Name")), /* @__PURE__ */ React10.createElement("div", { className: "brokr-account-field-body" }, /* @__PURE__ */ React10.createElement(
|
|
5660
5696
|
"input",
|
|
5661
5697
|
{
|
|
@@ -5763,6 +5799,9 @@ function UserButton({
|
|
|
5763
5799
|
setOpen(false);
|
|
5764
5800
|
await signOut();
|
|
5765
5801
|
}, [signOut]);
|
|
5802
|
+
const stopPropagation = useCallback9((event) => {
|
|
5803
|
+
event.stopPropagation();
|
|
5804
|
+
}, []);
|
|
5766
5805
|
useEffect7(() => {
|
|
5767
5806
|
if (!open) return void 0;
|
|
5768
5807
|
const handlePointerDown = (event) => {
|
|
@@ -5796,13 +5835,13 @@ function UserButton({
|
|
|
5796
5835
|
"data-align": align,
|
|
5797
5836
|
role: "dialog"
|
|
5798
5837
|
},
|
|
5799
|
-
/* @__PURE__ */ React12.createElement("div", { className: "brokr-account-menu" }, /* @__PURE__ */ React12.createElement("div", { className: "brokr-account-menu-header" }, /* @__PURE__ */ React12.createElement(Avatar, { email: summary.email, name: summary.name, src: user.image, size: 44 }), /* @__PURE__ */ React12.createElement("div", { className: "brokr-account-menu-copy" }, /* @__PURE__ */ React12.createElement("strong", null, summary.name), /* @__PURE__ */ React12.createElement("span", null, summary.email))), /* @__PURE__ */ React12.createElement("div", { className: "brokr-account-menu-actions" }, /* @__PURE__ */ React12.createElement("button", { className: "brokr-account-menu-action", onClick: openSettings, type: "button" }, /* @__PURE__ */ React12.createElement("span", { className: "brokr-account-menu-action-icon" }, /* @__PURE__ */ React12.createElement(SettingsIcon, { size: 15 })), /* @__PURE__ */ React12.createElement("span", null, "Settings")), /* @__PURE__ */ React12.createElement("button", { className: "brokr-account-menu-action", onClick:
|
|
5838
|
+
/* @__PURE__ */ React12.createElement("div", { className: "brokr-account-menu" }, /* @__PURE__ */ React12.createElement("div", { className: "brokr-account-menu-header" }, /* @__PURE__ */ React12.createElement(Avatar, { email: summary.email, name: summary.name, src: user.image, size: 44 }), /* @__PURE__ */ React12.createElement("div", { className: "brokr-account-menu-copy" }, /* @__PURE__ */ React12.createElement("strong", null, summary.name), /* @__PURE__ */ React12.createElement("span", null, summary.email))), /* @__PURE__ */ React12.createElement("div", { className: "brokr-account-menu-actions" }, /* @__PURE__ */ React12.createElement("button", { className: "brokr-account-menu-action", onClick: openSettings, type: "button" }, /* @__PURE__ */ React12.createElement("span", { className: "brokr-account-menu-action-icon" }, /* @__PURE__ */ React12.createElement(SettingsIcon, { size: 15 })), /* @__PURE__ */ React12.createElement("span", null, "Settings")), /* @__PURE__ */ React12.createElement("button", { className: "brokr-account-menu-action", onClick: handleSignOut, type: "button" }, /* @__PURE__ */ React12.createElement("span", { className: "brokr-account-menu-action-icon" }, /* @__PURE__ */ React12.createElement(LogoutIcon, { size: 15 })), /* @__PURE__ */ React12.createElement("span", null, "Sign out"))))
|
|
5800
5839
|
) : null, settingsOpen ? /* @__PURE__ */ React12.createElement("div", { className: "brokr-modal-backdrop", role: "presentation", onClick: closeSettings }, /* @__PURE__ */ React12.createElement(
|
|
5801
5840
|
"div",
|
|
5802
5841
|
{
|
|
5803
5842
|
"aria-modal": "true",
|
|
5804
5843
|
className: "brokr-panel brokr-modal-dialog brokr-account-settings-dialog",
|
|
5805
|
-
onClick:
|
|
5844
|
+
onClick: stopPropagation,
|
|
5806
5845
|
role: "dialog"
|
|
5807
5846
|
},
|
|
5808
5847
|
/* @__PURE__ */ React12.createElement("div", { className: "brokr-modal-toolbar" }, /* @__PURE__ */ React12.createElement("button", { className: "brokr-button-ghost", onClick: closeSettings, type: "button" }, /* @__PURE__ */ React12.createElement(CloseIcon, { size: 16 }))),
|
|
@@ -5998,6 +6037,28 @@ function AuthWall({ children }) {
|
|
|
5998
6037
|
|
|
5999
6038
|
// src/react/payments/Plans.tsx
|
|
6000
6039
|
import React21, { useCallback as useCallback12, useMemo as useMemo13, useState as useState12 } from "react";
|
|
6040
|
+
function PlanCard({
|
|
6041
|
+
plan,
|
|
6042
|
+
highlight,
|
|
6043
|
+
isPending,
|
|
6044
|
+
onSelect
|
|
6045
|
+
}) {
|
|
6046
|
+
const isHighlighted = highlight === plan.slug;
|
|
6047
|
+
const features = Object.entries(plan.features);
|
|
6048
|
+
const handleClick = useCallback12(() => {
|
|
6049
|
+
void onSelect(plan.slug);
|
|
6050
|
+
}, [plan.slug, onSelect]);
|
|
6051
|
+
return /* @__PURE__ */ React21.createElement("article", { className: "brokr-card brokr-plan-card", "data-highlight": isHighlighted, key: plan.slug }, /* @__PURE__ */ React21.createElement("div", { className: "brokr-section", style: { gap: "var(--brokr-space-3)" } }, /* @__PURE__ */ React21.createElement("div", { className: "brokr-brand-row", style: { justifyContent: "space-between" } }, /* @__PURE__ */ React21.createElement("strong", null, plan.name), plan.isCurrent ? /* @__PURE__ */ React21.createElement("span", { className: "brokr-badge" }, "Current") : null, isHighlighted && !plan.isCurrent ? /* @__PURE__ */ React21.createElement("span", { className: "brokr-badge" }, "Recommended") : null), /* @__PURE__ */ React21.createElement("div", { className: "brokr-plan-price" }, /* @__PURE__ */ React21.createElement("span", { className: "brokr-plan-value" }, plan.price === 0 ? "$0" : `$${(plan.price / 100).toFixed(0)}`), /* @__PURE__ */ React21.createElement("span", { className: "brokr-copy" }, "/", plan.interval)), plan.trialDays ? /* @__PURE__ */ React21.createElement("span", { className: "brokr-copy" }, plan.trialDays, " day trial included.") : null), /* @__PURE__ */ React21.createElement("ul", { className: "brokr-feature-list" }, features.map(([key, value]) => /* @__PURE__ */ React21.createElement("li", { className: "brokr-feature-item", key }, /* @__PURE__ */ React21.createElement(CheckIcon, { size: 14 }), /* @__PURE__ */ React21.createElement("span", null, featureLabel(key), " \xB7 ", featureValueLabel(value))))), /* @__PURE__ */ React21.createElement(
|
|
6052
|
+
"button",
|
|
6053
|
+
{
|
|
6054
|
+
className: plan.isCurrent ? "brokr-button-secondary" : "brokr-button",
|
|
6055
|
+
disabled: plan.isCurrent || isPending,
|
|
6056
|
+
onClick: handleClick,
|
|
6057
|
+
type: "button"
|
|
6058
|
+
},
|
|
6059
|
+
plan.isCurrent ? "Current plan" : isPending ? "Redirecting" : "Choose plan"
|
|
6060
|
+
));
|
|
6061
|
+
}
|
|
6001
6062
|
function Plans({
|
|
6002
6063
|
columns,
|
|
6003
6064
|
highlight,
|
|
@@ -6028,34 +6089,16 @@ function Plans({
|
|
|
6028
6089
|
setPendingPlan(null);
|
|
6029
6090
|
}
|
|
6030
6091
|
}, [checkout, onSelect]);
|
|
6031
|
-
return /* @__PURE__ */ React21.createElement("div", { className: "brokr-section" }, paymentsMode === "sandbox" ? /* @__PURE__ */ React21.createElement("div", { className: "brokr-inline-message" }, "Sandbox mode \u2014 test cards only.") : null, error ? /* @__PURE__ */ React21.createElement("div", { className: "brokr-inline-message", "data-tone": "error" }, error) : null, /* @__PURE__ */ React21.createElement("div", { className: "brokr-grid-auto", style: layoutStyle }, plans.map((plan) =>
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
6035
|
-
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
className: "brokr-card brokr-plan-card",
|
|
6042
|
-
"data-highlight": isHighlighted,
|
|
6043
|
-
key: plan.slug
|
|
6044
|
-
},
|
|
6045
|
-
/* @__PURE__ */ React21.createElement("div", { className: "brokr-section", style: { gap: "0.75rem" } }, /* @__PURE__ */ React21.createElement("div", { className: "brokr-brand-row", style: { justifyContent: "space-between" } }, /* @__PURE__ */ React21.createElement("strong", null, plan.name), plan.isCurrent ? /* @__PURE__ */ React21.createElement("span", { className: "brokr-badge" }, "Current") : null, isHighlighted && !plan.isCurrent ? /* @__PURE__ */ React21.createElement("span", { className: "brokr-badge" }, "Recommended") : null), /* @__PURE__ */ React21.createElement("div", { className: "brokr-plan-price" }, /* @__PURE__ */ React21.createElement("span", { className: "brokr-plan-value" }, plan.price === 0 ? "$0" : `$${(plan.price / 100).toFixed(0)}`), /* @__PURE__ */ React21.createElement("span", { className: "brokr-copy" }, "/", plan.interval)), plan.trialDays ? /* @__PURE__ */ React21.createElement("span", { className: "brokr-copy" }, plan.trialDays, " day trial included.") : null),
|
|
6046
|
-
/* @__PURE__ */ React21.createElement(
|
|
6047
|
-
"button",
|
|
6048
|
-
{
|
|
6049
|
-
className: plan.isCurrent ? "brokr-button-secondary" : "brokr-button",
|
|
6050
|
-
disabled: plan.isCurrent || isPending,
|
|
6051
|
-
onClick: handleClick,
|
|
6052
|
-
type: "button"
|
|
6053
|
-
},
|
|
6054
|
-
plan.isCurrent ? "Current plan" : isPending ? "Redirecting" : "Choose plan"
|
|
6055
|
-
),
|
|
6056
|
-
/* @__PURE__ */ React21.createElement("ul", { className: "brokr-feature-list" }, features.map(([key, value]) => /* @__PURE__ */ React21.createElement("li", { className: "brokr-feature-item", key }, /* @__PURE__ */ React21.createElement(CheckIcon, { size: 14 }), /* @__PURE__ */ React21.createElement("span", null, featureLabel(key), " \xB7 ", featureValueLabel(value)))))
|
|
6057
|
-
);
|
|
6058
|
-
})));
|
|
6092
|
+
return /* @__PURE__ */ React21.createElement("div", { className: "brokr-section" }, paymentsMode === "sandbox" ? /* @__PURE__ */ React21.createElement("div", { className: "brokr-inline-message" }, "Sandbox mode \u2014 test cards only.") : null, error ? /* @__PURE__ */ React21.createElement("div", { className: "brokr-inline-message", "data-tone": "error" }, error) : null, /* @__PURE__ */ React21.createElement("div", { className: "brokr-grid-auto", style: layoutStyle }, plans.map((plan) => /* @__PURE__ */ React21.createElement(
|
|
6093
|
+
PlanCard,
|
|
6094
|
+
{
|
|
6095
|
+
key: plan.slug,
|
|
6096
|
+
plan,
|
|
6097
|
+
highlight,
|
|
6098
|
+
isPending: pendingPlan === plan.slug,
|
|
6099
|
+
onSelect: handleSelect
|
|
6100
|
+
}
|
|
6101
|
+
))));
|
|
6059
6102
|
}
|
|
6060
6103
|
|
|
6061
6104
|
// src/react/payments/FeatureMeter.tsx
|
|
@@ -6193,7 +6236,7 @@ function AutoReloadToggle({ onChange }) {
|
|
|
6193
6236
|
setIsPending(false);
|
|
6194
6237
|
}
|
|
6195
6238
|
}, [enabled, onChange]);
|
|
6196
|
-
return /* @__PURE__ */ React26.createElement("div", { className: "brokr-card brokr-gate-card" }, /* @__PURE__ */ React26.createElement("div", { className: "brokr-brand-row", style: { justifyContent: "space-between" } }, /* @__PURE__ */ React26.createElement("div", { className: "brokr-section", style: { gap: "
|
|
6239
|
+
return /* @__PURE__ */ React26.createElement("div", { className: "brokr-card brokr-gate-card" }, /* @__PURE__ */ React26.createElement("div", { className: "brokr-brand-row", style: { justifyContent: "space-between" } }, /* @__PURE__ */ React26.createElement("div", { className: "brokr-section", style: { gap: "var(--brokr-space-1)" } }, /* @__PURE__ */ React26.createElement("strong", null, "Auto reload"), /* @__PURE__ */ React26.createElement("span", { className: "brokr-copy" }, helperText)), /* @__PURE__ */ React26.createElement(
|
|
6197
6240
|
"button",
|
|
6198
6241
|
{
|
|
6199
6242
|
className: "brokr-button-secondary",
|
|
@@ -6243,7 +6286,7 @@ function CancelSubscription({ onCancel }) {
|
|
|
6243
6286
|
|
|
6244
6287
|
// src/react/chat/AIChat.tsx
|
|
6245
6288
|
import React35, {
|
|
6246
|
-
useCallback as
|
|
6289
|
+
useCallback as useCallback23,
|
|
6247
6290
|
useEffect as useEffect12,
|
|
6248
6291
|
useMemo as useMemo23,
|
|
6249
6292
|
useRef as useRef7,
|
|
@@ -6368,6 +6411,39 @@ function isSSEResponse(response) {
|
|
|
6368
6411
|
return ct.includes("text/event-stream") && response.body !== null;
|
|
6369
6412
|
}
|
|
6370
6413
|
|
|
6414
|
+
// src/react/chat/memory.ts
|
|
6415
|
+
var MAX_MEMORY_CHARS = 2e3;
|
|
6416
|
+
function serializeMemory(obj, prefix = "") {
|
|
6417
|
+
const lines = [];
|
|
6418
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
6419
|
+
if (value === null || value === void 0) continue;
|
|
6420
|
+
const label = prefix ? `${prefix}.${key}` : key;
|
|
6421
|
+
if (Array.isArray(value)) {
|
|
6422
|
+
lines.push(`${label}: ${value.join(", ")}`);
|
|
6423
|
+
} else if (typeof value === "object") {
|
|
6424
|
+
lines.push(...serializeMemory(value, label));
|
|
6425
|
+
} else {
|
|
6426
|
+
lines.push(`${label}: ${String(value)}`);
|
|
6427
|
+
}
|
|
6428
|
+
}
|
|
6429
|
+
return lines;
|
|
6430
|
+
}
|
|
6431
|
+
function buildEffectivePrompt(prompt, memory) {
|
|
6432
|
+
if (!memory || Object.keys(memory).length === 0) return prompt;
|
|
6433
|
+
let block = serializeMemory(memory).join("\n");
|
|
6434
|
+
if (block.length > MAX_MEMORY_CHARS) {
|
|
6435
|
+
block = block.slice(0, MAX_MEMORY_CHARS);
|
|
6436
|
+
if (typeof console !== "undefined") {
|
|
6437
|
+
console.warn("[brokr] memory exceeded 2000 char limit, truncated");
|
|
6438
|
+
}
|
|
6439
|
+
}
|
|
6440
|
+
const memSection = `[User context]
|
|
6441
|
+
${block}`;
|
|
6442
|
+
return prompt ? `${memSection}
|
|
6443
|
+
|
|
6444
|
+
${prompt}` : memSection;
|
|
6445
|
+
}
|
|
6446
|
+
|
|
6371
6447
|
// src/react/chat/useChat.ts
|
|
6372
6448
|
function makeId(prefix) {
|
|
6373
6449
|
return `${prefix}_${crypto.randomUUID()}`;
|
|
@@ -6385,7 +6461,9 @@ function useChat(config) {
|
|
|
6385
6461
|
const {
|
|
6386
6462
|
endpoint,
|
|
6387
6463
|
prompt,
|
|
6388
|
-
|
|
6464
|
+
explicitModel,
|
|
6465
|
+
displayModel: activeModel,
|
|
6466
|
+
memory,
|
|
6389
6467
|
persist,
|
|
6390
6468
|
surface,
|
|
6391
6469
|
subject,
|
|
@@ -6421,9 +6499,25 @@ function useChat(config) {
|
|
|
6421
6499
|
const scrollContainerRef = useRef5(null);
|
|
6422
6500
|
const sentinelRef = useRef5(null);
|
|
6423
6501
|
const displaySidebarItems = useMemo18(() => {
|
|
6424
|
-
if (isControlled)
|
|
6425
|
-
|
|
6426
|
-
|
|
6502
|
+
if (isControlled) {
|
|
6503
|
+
return threadsProp?.map((t) => ({
|
|
6504
|
+
id: t.id,
|
|
6505
|
+
title: t.title,
|
|
6506
|
+
updatedAt: t.updatedAt ?? null
|
|
6507
|
+
})) ?? [];
|
|
6508
|
+
}
|
|
6509
|
+
if (isPersist) {
|
|
6510
|
+
return serverThreads.map((t) => ({
|
|
6511
|
+
id: t.id,
|
|
6512
|
+
title: t.title,
|
|
6513
|
+
updatedAt: t.updatedAt ?? null
|
|
6514
|
+
}));
|
|
6515
|
+
}
|
|
6516
|
+
return memConversations.map((c) => ({
|
|
6517
|
+
id: c.id,
|
|
6518
|
+
title: c.title || "New chat",
|
|
6519
|
+
updatedAt: new Date(c.updatedAt).toISOString()
|
|
6520
|
+
}));
|
|
6427
6521
|
}, [isControlled, isPersist, threadsProp, serverThreads, memConversations]);
|
|
6428
6522
|
const activeId = useMemo18(() => {
|
|
6429
6523
|
if (isControlled) return activeThreadIdProp ?? null;
|
|
@@ -6604,8 +6698,11 @@ function useChat(config) {
|
|
|
6604
6698
|
messages: trimToTokenBudget(
|
|
6605
6699
|
nextMessages.filter((m) => m.role === "user" || m.role === "assistant" && m.content).map(({ content: mc, role }) => ({ role, content: mc }))
|
|
6606
6700
|
),
|
|
6607
|
-
model:
|
|
6608
|
-
...
|
|
6701
|
+
...explicitModel ? { model: explicitModel } : {},
|
|
6702
|
+
...(() => {
|
|
6703
|
+
const ep = buildEffectivePrompt(prompt, memory);
|
|
6704
|
+
return ep ? { systemPrompt: ep } : {};
|
|
6705
|
+
})()
|
|
6609
6706
|
})
|
|
6610
6707
|
});
|
|
6611
6708
|
if (!response.ok) {
|
|
@@ -6650,8 +6747,10 @@ function useChat(config) {
|
|
|
6650
6747
|
ensureMemConversation,
|
|
6651
6748
|
memConversations,
|
|
6652
6749
|
endpoint,
|
|
6750
|
+
explicitModel,
|
|
6653
6751
|
activeModel,
|
|
6654
6752
|
prompt,
|
|
6753
|
+
memory,
|
|
6655
6754
|
updateMemMessages,
|
|
6656
6755
|
appendMemDelta,
|
|
6657
6756
|
animateTitle,
|
|
@@ -6673,9 +6772,12 @@ function useChat(config) {
|
|
|
6673
6772
|
content,
|
|
6674
6773
|
surface,
|
|
6675
6774
|
subject: subject ?? null,
|
|
6676
|
-
model:
|
|
6775
|
+
...explicitModel ? { model: explicitModel } : {},
|
|
6677
6776
|
userId: userId ?? void 0,
|
|
6678
|
-
...
|
|
6777
|
+
...(() => {
|
|
6778
|
+
const ep = buildEffectivePrompt(prompt, memory);
|
|
6779
|
+
return ep ? { systemPrompt: ep } : {};
|
|
6780
|
+
})()
|
|
6679
6781
|
})
|
|
6680
6782
|
});
|
|
6681
6783
|
if (!response.ok) {
|
|
@@ -6730,8 +6832,10 @@ function useChat(config) {
|
|
|
6730
6832
|
endpoint,
|
|
6731
6833
|
surface,
|
|
6732
6834
|
subject,
|
|
6835
|
+
explicitModel,
|
|
6733
6836
|
activeModel,
|
|
6734
6837
|
prompt,
|
|
6838
|
+
memory,
|
|
6735
6839
|
userId,
|
|
6736
6840
|
setServerMessages,
|
|
6737
6841
|
setServerActiveId,
|
|
@@ -6889,6 +6993,37 @@ function useChat(config) {
|
|
|
6889
6993
|
|
|
6890
6994
|
// src/react/chat/ModelSelector.tsx
|
|
6891
6995
|
import React29, { useCallback as useCallback17, useEffect as useEffect10, useMemo as useMemo19, useRef as useRef6, useState as useState17 } from "react";
|
|
6996
|
+
function ModelOption({
|
|
6997
|
+
provider,
|
|
6998
|
+
isActive,
|
|
6999
|
+
isLocked,
|
|
7000
|
+
onSelect,
|
|
7001
|
+
onCheckout
|
|
7002
|
+
}) {
|
|
7003
|
+
const handleClick = useCallback17(() => {
|
|
7004
|
+
if (isLocked) {
|
|
7005
|
+
void onCheckout({ plan: "pro" });
|
|
7006
|
+
} else {
|
|
7007
|
+
onSelect(provider.model);
|
|
7008
|
+
}
|
|
7009
|
+
}, [isLocked, onCheckout, onSelect, provider.model]);
|
|
7010
|
+
return /* @__PURE__ */ React29.createElement(
|
|
7011
|
+
"button",
|
|
7012
|
+
{
|
|
7013
|
+
"aria-selected": isActive,
|
|
7014
|
+
className: "brokr-model-option",
|
|
7015
|
+
"data-active": isActive,
|
|
7016
|
+
"data-locked": isLocked,
|
|
7017
|
+
disabled: isLocked,
|
|
7018
|
+
onClick: handleClick,
|
|
7019
|
+
title: isLocked ? "Add credits to unlock" : void 0,
|
|
7020
|
+
type: "button"
|
|
7021
|
+
},
|
|
7022
|
+
/* @__PURE__ */ React29.createElement("img", { alt: "", className: "brokr-model-logo", src: provider.logo }),
|
|
7023
|
+
/* @__PURE__ */ React29.createElement("span", { className: "brokr-model-option-label" }, provider.label),
|
|
7024
|
+
isLocked ? /* @__PURE__ */ React29.createElement("span", { className: "brokr-model-lock", "aria-hidden": "true" }, /* @__PURE__ */ React29.createElement(LockIcon, { size: 13 })) : null
|
|
7025
|
+
);
|
|
7026
|
+
}
|
|
6892
7027
|
function ModelSelector({
|
|
6893
7028
|
activeModel,
|
|
6894
7029
|
setSelectedModel,
|
|
@@ -6897,10 +7032,19 @@ function ModelSelector({
|
|
|
6897
7032
|
}) {
|
|
6898
7033
|
const [selectorOpen, setSelectorOpen] = useState17(false);
|
|
6899
7034
|
const selectorRef = useRef6(null);
|
|
7035
|
+
const isAuto = activeModel === STACK_DEFAULT || !providers.some((p) => p.model === activeModel);
|
|
6900
7036
|
const activeProvider = useMemo19(
|
|
6901
|
-
() => providers.find((p) => p.model === activeModel) ?? providers[0],
|
|
6902
|
-
[activeModel]
|
|
7037
|
+
() => isAuto ? void 0 : providers.find((p) => p.model === activeModel) ?? providers[0],
|
|
7038
|
+
[activeModel, isAuto]
|
|
6903
7039
|
);
|
|
7040
|
+
const handleModelSelect = useCallback17((model) => {
|
|
7041
|
+
setSelectedModel(model === STACK_DEFAULT ? null : model);
|
|
7042
|
+
setSelectorOpen(false);
|
|
7043
|
+
}, [setSelectedModel]);
|
|
7044
|
+
const handleAutoSelect = useCallback17(() => {
|
|
7045
|
+
setSelectedModel(null);
|
|
7046
|
+
setSelectorOpen(false);
|
|
7047
|
+
}, [setSelectedModel]);
|
|
6904
7048
|
useEffect10(() => {
|
|
6905
7049
|
if (!selectorOpen) return;
|
|
6906
7050
|
const onMouseDown = (e) => {
|
|
@@ -6928,40 +7072,79 @@ function ModelSelector({
|
|
|
6928
7072
|
onClick: handleToggle,
|
|
6929
7073
|
type: "button"
|
|
6930
7074
|
},
|
|
6931
|
-
activeProvider ? /* @__PURE__ */ React29.createElement("img", { alt: "", className: "brokr-model-logo", src: activeProvider.logo }) : /* @__PURE__ */ React29.createElement("span", { className: "brokr-model-dot", style: { background: "
|
|
6932
|
-
activeProvider?.label ?? "Model",
|
|
7075
|
+
activeProvider ? /* @__PURE__ */ React29.createElement("img", { alt: "", className: "brokr-model-logo", src: activeProvider.logo }) : /* @__PURE__ */ React29.createElement("span", { className: "brokr-model-dot", style: { background: "#6B7280" } }),
|
|
7076
|
+
isAuto ? "Auto" : activeProvider?.label ?? "Model",
|
|
6933
7077
|
/* @__PURE__ */ React29.createElement(ChevronDownIcon, { size: 13 })
|
|
6934
|
-
), selectorOpen ? /* @__PURE__ */ React29.createElement("div", { className: "brokr-model-dropdown", role: "listbox" },
|
|
6935
|
-
|
|
6936
|
-
|
|
6937
|
-
"
|
|
6938
|
-
|
|
6939
|
-
|
|
6940
|
-
|
|
6941
|
-
|
|
6942
|
-
|
|
6943
|
-
|
|
6944
|
-
|
|
6945
|
-
|
|
6946
|
-
|
|
6947
|
-
|
|
6948
|
-
|
|
6949
|
-
|
|
6950
|
-
|
|
6951
|
-
|
|
6952
|
-
|
|
6953
|
-
|
|
6954
|
-
|
|
6955
|
-
|
|
6956
|
-
/* @__PURE__ */ React29.createElement("img", { alt: "", className: "brokr-model-logo", src: p.logo }),
|
|
6957
|
-
/* @__PURE__ */ React29.createElement("span", { className: "brokr-model-option-label" }, p.label),
|
|
6958
|
-
locked ? /* @__PURE__ */ React29.createElement("span", { className: "brokr-model-lock", "aria-hidden": "true" }, /* @__PURE__ */ React29.createElement(LockIcon, { size: 13 })) : null
|
|
6959
|
-
);
|
|
6960
|
-
})) : null);
|
|
7078
|
+
), selectorOpen ? /* @__PURE__ */ React29.createElement("div", { className: "brokr-model-dropdown", role: "listbox" }, /* @__PURE__ */ React29.createElement(
|
|
7079
|
+
"button",
|
|
7080
|
+
{
|
|
7081
|
+
"aria-selected": isAuto,
|
|
7082
|
+
className: "brokr-model-option",
|
|
7083
|
+
"data-active": isAuto,
|
|
7084
|
+
onClick: handleAutoSelect,
|
|
7085
|
+
type: "button"
|
|
7086
|
+
},
|
|
7087
|
+
/* @__PURE__ */ React29.createElement("span", { className: "brokr-model-dot", style: { background: "#6B7280" } }),
|
|
7088
|
+
/* @__PURE__ */ React29.createElement("span", { className: "brokr-model-option-label" }, "Stack Default")
|
|
7089
|
+
), providers.map((p) => /* @__PURE__ */ React29.createElement(
|
|
7090
|
+
ModelOption,
|
|
7091
|
+
{
|
|
7092
|
+
key: p.id,
|
|
7093
|
+
provider: p,
|
|
7094
|
+
isActive: !isAuto && p.model === activeModel,
|
|
7095
|
+
isLocked: !availableProviders.some((a) => a.id === p.id),
|
|
7096
|
+
onSelect: handleModelSelect,
|
|
7097
|
+
onCheckout: checkout
|
|
7098
|
+
}
|
|
7099
|
+
))) : null);
|
|
6961
7100
|
}
|
|
6962
7101
|
|
|
6963
7102
|
// src/react/chat/ThreadSidebar.tsx
|
|
6964
7103
|
import React30, { useCallback as useCallback18, useMemo as useMemo20 } from "react";
|
|
7104
|
+
function ThreadItemButton({ id, title, isActive, onSelect }) {
|
|
7105
|
+
const handleClick = useCallback18(() => onSelect(id), [id, onSelect]);
|
|
7106
|
+
return /* @__PURE__ */ React30.createElement(
|
|
7107
|
+
"button",
|
|
7108
|
+
{
|
|
7109
|
+
className: "brokr-ai-chat-conversation",
|
|
7110
|
+
"data-active": isActive,
|
|
7111
|
+
onClick: handleClick,
|
|
7112
|
+
type: "button"
|
|
7113
|
+
},
|
|
7114
|
+
/* @__PURE__ */ React30.createElement(MessageIcon, { size: 12 }),
|
|
7115
|
+
/* @__PURE__ */ React30.createElement("span", { className: "brokr-ai-chat-conversation-label" }, title)
|
|
7116
|
+
);
|
|
7117
|
+
}
|
|
7118
|
+
var SIDEBAR_GROUP_ORDER = [
|
|
7119
|
+
"today",
|
|
7120
|
+
"yesterday",
|
|
7121
|
+
"this_week",
|
|
7122
|
+
"this_month",
|
|
7123
|
+
"earlier",
|
|
7124
|
+
"undated"
|
|
7125
|
+
];
|
|
7126
|
+
var SIDEBAR_GROUP_LABELS = {
|
|
7127
|
+
today: "Today",
|
|
7128
|
+
yesterday: "Yesterday",
|
|
7129
|
+
this_week: "This Week",
|
|
7130
|
+
this_month: "This Month",
|
|
7131
|
+
earlier: "Earlier",
|
|
7132
|
+
undated: "Recent"
|
|
7133
|
+
};
|
|
7134
|
+
function resolveSidebarDateGroup(updatedAt) {
|
|
7135
|
+
if (!updatedAt) return "undated";
|
|
7136
|
+
const parsed = new Date(updatedAt);
|
|
7137
|
+
if (Number.isNaN(parsed.getTime())) return "undated";
|
|
7138
|
+
const now = /* @__PURE__ */ new Date();
|
|
7139
|
+
const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
7140
|
+
const startOfTarget = new Date(parsed.getFullYear(), parsed.getMonth(), parsed.getDate());
|
|
7141
|
+
const diffDays = Math.floor((startOfToday.getTime() - startOfTarget.getTime()) / 864e5);
|
|
7142
|
+
if (diffDays <= 0) return "today";
|
|
7143
|
+
if (diffDays === 1) return "yesterday";
|
|
7144
|
+
if (diffDays < 7) return "this_week";
|
|
7145
|
+
if (diffDays < 30) return "this_month";
|
|
7146
|
+
return "earlier";
|
|
7147
|
+
}
|
|
6965
7148
|
function ThreadSidebar() {
|
|
6966
7149
|
const {
|
|
6967
7150
|
startNewChat,
|
|
@@ -6993,14 +7176,31 @@ function ThreadSidebar() {
|
|
|
6993
7176
|
const handleRenameChange = useCallback18((e) => {
|
|
6994
7177
|
setRenameValue(e.target.value);
|
|
6995
7178
|
}, [setRenameValue]);
|
|
7179
|
+
const groupedSidebarItems = useMemo20(() => {
|
|
7180
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
7181
|
+
for (const item of displaySidebarItems) {
|
|
7182
|
+
const key = resolveSidebarDateGroup(item.updatedAt);
|
|
7183
|
+
const bucket = grouped.get(key);
|
|
7184
|
+
if (bucket) {
|
|
7185
|
+
bucket.push(item);
|
|
7186
|
+
} else {
|
|
7187
|
+
grouped.set(key, [item]);
|
|
7188
|
+
}
|
|
7189
|
+
}
|
|
7190
|
+
return SIDEBAR_GROUP_ORDER.map((key) => ({
|
|
7191
|
+
key,
|
|
7192
|
+
label: SIDEBAR_GROUP_LABELS[key],
|
|
7193
|
+
items: grouped.get(key) ?? []
|
|
7194
|
+
})).filter((group) => group.items.length > 0);
|
|
7195
|
+
}, [displaySidebarItems]);
|
|
6996
7196
|
const content = useMemo20(() => {
|
|
6997
7197
|
if (threadsLoading && displaySidebarItems.length === 0) {
|
|
6998
|
-
return /* @__PURE__ */ React30.createElement("div", {
|
|
7198
|
+
return /* @__PURE__ */ React30.createElement("div", { className: "brokr-ai-chat-sidebar-skeleton" }, /* @__PURE__ */ React30.createElement(Skeleton, { width: "75%", height: 14, radius: 6 }), /* @__PURE__ */ React30.createElement(Skeleton, { width: "60%", height: 14, radius: 6 }), /* @__PURE__ */ React30.createElement(Skeleton, { width: "85%", height: 14, radius: 6 }));
|
|
6999
7199
|
}
|
|
7000
7200
|
if (displaySidebarItems.length === 0) {
|
|
7001
7201
|
return /* @__PURE__ */ React30.createElement("div", { className: "brokr-ai-chat-sidebar-empty" }, /* @__PURE__ */ React30.createElement("p", { className: "brokr-ai-chat-sidebar-empty-text" }, "No conversations yet. Start one above."));
|
|
7002
7202
|
}
|
|
7003
|
-
return /* @__PURE__ */ React30.createElement("div", { className: "brokr-ai-chat-conversations" },
|
|
7203
|
+
return /* @__PURE__ */ React30.createElement("div", { className: "brokr-ai-chat-sidebar-groups" }, groupedSidebarItems.map((group) => /* @__PURE__ */ React30.createElement("section", { className: "brokr-ai-chat-sidebar-group", key: group.key }, /* @__PURE__ */ React30.createElement("span", { className: "brokr-ai-chat-sidebar-kicker" }, group.label), /* @__PURE__ */ React30.createElement("div", { className: "brokr-ai-chat-conversations" }, group.items.map((item) => renamingId === item.id ? /* @__PURE__ */ React30.createElement(
|
|
7004
7204
|
"input",
|
|
7005
7205
|
{
|
|
7006
7206
|
autoFocus: true,
|
|
@@ -7012,32 +7212,32 @@ function ThreadSidebar() {
|
|
|
7012
7212
|
value: renameValue
|
|
7013
7213
|
}
|
|
7014
7214
|
) : /* @__PURE__ */ React30.createElement(
|
|
7015
|
-
|
|
7215
|
+
ThreadItemButton,
|
|
7016
7216
|
{
|
|
7017
|
-
className: "brokr-ai-chat-conversation",
|
|
7018
|
-
"data-active": item.id === activeId,
|
|
7019
7217
|
key: item.id,
|
|
7020
|
-
|
|
7021
|
-
|
|
7022
|
-
|
|
7023
|
-
|
|
7024
|
-
|
|
7218
|
+
id: item.id,
|
|
7219
|
+
title: item.title,
|
|
7220
|
+
isActive: item.id === activeId,
|
|
7221
|
+
onSelect: selectThreadAndCloseSidebar
|
|
7222
|
+
}
|
|
7223
|
+
))))));
|
|
7025
7224
|
}, [
|
|
7026
7225
|
threadsLoading,
|
|
7027
7226
|
displaySidebarItems,
|
|
7028
7227
|
renamingId,
|
|
7029
7228
|
renameValue,
|
|
7229
|
+
groupedSidebarItems,
|
|
7030
7230
|
activeId,
|
|
7031
7231
|
selectThreadAndCloseSidebar,
|
|
7032
7232
|
handleRenameBlur,
|
|
7033
7233
|
handleRenameChange,
|
|
7034
7234
|
handleRenameKeyDown
|
|
7035
7235
|
]);
|
|
7036
|
-
return /* @__PURE__ */ React30.createElement(React30.Fragment, null, /* @__PURE__ */ React30.createElement("button", { className: "brokr-ai-chat-sidebar-
|
|
7236
|
+
return /* @__PURE__ */ React30.createElement(React30.Fragment, null, /* @__PURE__ */ React30.createElement("button", { className: "brokr-ai-chat-sidebar-new-chat", onClick: handleNewChat, type: "button" }, /* @__PURE__ */ React30.createElement(MessageIcon, { size: 16 }), "New chat"), content);
|
|
7037
7237
|
}
|
|
7038
7238
|
|
|
7039
7239
|
// src/react/chat/MessagePane.tsx
|
|
7040
|
-
import React33, { useMemo as useMemo22 } from "react";
|
|
7240
|
+
import React33, { useCallback as useCallback21, useMemo as useMemo22 } from "react";
|
|
7041
7241
|
|
|
7042
7242
|
// src/react/chat/MessageBubble.tsx
|
|
7043
7243
|
import React32, { useCallback as useCallback20 } from "react";
|
|
@@ -7061,6 +7261,12 @@ function parseInline(text) {
|
|
|
7061
7261
|
remaining = remaining.slice(boldMatch[0].length);
|
|
7062
7262
|
continue;
|
|
7063
7263
|
}
|
|
7264
|
+
const strikethroughMatch = remaining.match(/^~~(.+?)~~/);
|
|
7265
|
+
if (strikethroughMatch) {
|
|
7266
|
+
nodes.push(/* @__PURE__ */ React31.createElement("del", { key: key++ }, strikethroughMatch[1]));
|
|
7267
|
+
remaining = remaining.slice(strikethroughMatch[0].length);
|
|
7268
|
+
continue;
|
|
7269
|
+
}
|
|
7064
7270
|
const italicMatch = remaining.match(/^\*(.+?)\*/);
|
|
7065
7271
|
if (italicMatch) {
|
|
7066
7272
|
nodes.push(/* @__PURE__ */ React31.createElement("em", { key: key++ }, italicMatch[1]));
|
|
@@ -7077,7 +7283,7 @@ function parseInline(text) {
|
|
|
7077
7283
|
remaining = remaining.slice(linkMatch[0].length);
|
|
7078
7284
|
continue;
|
|
7079
7285
|
}
|
|
7080
|
-
const nextSpecial = remaining.search(/[
|
|
7286
|
+
const nextSpecial = remaining.search(/[`*~\[]/);
|
|
7081
7287
|
if (nextSpecial === -1) {
|
|
7082
7288
|
nodes.push(remaining);
|
|
7083
7289
|
break;
|
|
@@ -7108,6 +7314,15 @@ function CodeBlock({ code, language }) {
|
|
|
7108
7314
|
"Copy"
|
|
7109
7315
|
)), /* @__PURE__ */ React31.createElement("pre", { className: "brokr-md-codeblock-pre" }, /* @__PURE__ */ React31.createElement("code", null, code)));
|
|
7110
7316
|
}
|
|
7317
|
+
function looksLikeTableSeparator(line) {
|
|
7318
|
+
const trimmed = line.trim();
|
|
7319
|
+
return /^\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)+\|?$/.test(trimmed);
|
|
7320
|
+
}
|
|
7321
|
+
function splitTableRow(line) {
|
|
7322
|
+
const trimmed = line.trim();
|
|
7323
|
+
const withoutEdges = trimmed.replace(/^\|/, "").replace(/\|$/, "");
|
|
7324
|
+
return withoutEdges.split("|").map((cell) => cell.trim());
|
|
7325
|
+
}
|
|
7111
7326
|
function parseBlocks(text) {
|
|
7112
7327
|
const blocks = [];
|
|
7113
7328
|
const lines = text.split("\n");
|
|
@@ -7137,22 +7352,44 @@ function parseBlocks(text) {
|
|
|
7137
7352
|
i++;
|
|
7138
7353
|
continue;
|
|
7139
7354
|
}
|
|
7355
|
+
if (line.includes("|") && i + 1 < lines.length && looksLikeTableSeparator(lines[i + 1] ?? "")) {
|
|
7356
|
+
const headers = splitTableRow(line);
|
|
7357
|
+
const rows = [];
|
|
7358
|
+
i += 2;
|
|
7359
|
+
while (i < lines.length) {
|
|
7360
|
+
const candidate = lines[i] ?? "";
|
|
7361
|
+
if (!candidate.trim() || !candidate.includes("|")) break;
|
|
7362
|
+
rows.push(splitTableRow(candidate));
|
|
7363
|
+
i++;
|
|
7364
|
+
}
|
|
7365
|
+
blocks.push({ type: "table", content: "", headers, rows });
|
|
7366
|
+
continue;
|
|
7367
|
+
}
|
|
7368
|
+
if (/^[\s]*>\s?/.test(line)) {
|
|
7369
|
+
const quoteLines = [];
|
|
7370
|
+
while (i < lines.length && /^[\s]*>\s?/.test(lines[i])) {
|
|
7371
|
+
quoteLines.push(lines[i].replace(/^[\s]*>\s?/, ""));
|
|
7372
|
+
i++;
|
|
7373
|
+
}
|
|
7374
|
+
blocks.push({ type: "quote", content: "", items: quoteLines });
|
|
7375
|
+
continue;
|
|
7376
|
+
}
|
|
7140
7377
|
if (/^[\s]*[-*]\s+/.test(line)) {
|
|
7141
7378
|
const items = [];
|
|
7142
7379
|
while (i < lines.length && /^[\s]*[-*]\s+/.test(lines[i])) {
|
|
7143
7380
|
items.push(lines[i].replace(/^[\s]*[-*]\s+/, ""));
|
|
7144
7381
|
i++;
|
|
7145
7382
|
}
|
|
7146
|
-
blocks.push({ type: "list", content: "", items });
|
|
7383
|
+
blocks.push({ type: "list", content: "", items, ordered: false });
|
|
7147
7384
|
continue;
|
|
7148
7385
|
}
|
|
7149
|
-
if (/^[\s]*\d
|
|
7386
|
+
if (/^[\s]*\d+[.)]\s+/.test(line)) {
|
|
7150
7387
|
const items = [];
|
|
7151
|
-
while (i < lines.length && /^[\s]*\d
|
|
7152
|
-
items.push(lines[i].replace(/^[\s]*\d
|
|
7388
|
+
while (i < lines.length && /^[\s]*\d+[.)]\s+/.test(lines[i])) {
|
|
7389
|
+
items.push(lines[i].replace(/^[\s]*\d+[.)]\s+/, ""));
|
|
7153
7390
|
i++;
|
|
7154
7391
|
}
|
|
7155
|
-
blocks.push({ type: "list", content: "", items });
|
|
7392
|
+
blocks.push({ type: "list", content: "", items, ordered: true });
|
|
7156
7393
|
continue;
|
|
7157
7394
|
}
|
|
7158
7395
|
if (!line.trim()) {
|
|
@@ -7161,7 +7398,7 @@ function parseBlocks(text) {
|
|
|
7161
7398
|
}
|
|
7162
7399
|
const paraLines = [line];
|
|
7163
7400
|
i++;
|
|
7164
|
-
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
|
|
7401
|
+
while (i < lines.length && lines[i].trim() && !lines[i].trimStart().startsWith("```") && !lines[i].match(/^#{1,6}\s/) && !(lines[i].includes("|") && i + 1 < lines.length && looksLikeTableSeparator(lines[i + 1] ?? "")) && !/^(-{3,}|\*{3,}|_{3,})\s*$/.test(lines[i].trim()) && !/^[\s]*>\s?/.test(lines[i]) && !/^[\s]*[-*]\s+/.test(lines[i]) && !/^[\s]*\d+[.)]\s+/.test(lines[i])) {
|
|
7165
7402
|
paraLines.push(lines[i]);
|
|
7166
7403
|
i++;
|
|
7167
7404
|
}
|
|
@@ -7181,7 +7418,14 @@ function renderBlocks(blocks) {
|
|
|
7181
7418
|
return /* @__PURE__ */ React31.createElement(Tag, { className: `brokr-md-heading brokr-md-h${block.level}`, key: idx }, parseInline(block.content));
|
|
7182
7419
|
}
|
|
7183
7420
|
case "list":
|
|
7184
|
-
|
|
7421
|
+
if (block.ordered) {
|
|
7422
|
+
return /* @__PURE__ */ React31.createElement("ol", { className: "brokr-md-list brokr-md-list-ordered", key: idx }, block.items?.map((item, li) => /* @__PURE__ */ React31.createElement("li", { key: li }, parseInline(item))));
|
|
7423
|
+
}
|
|
7424
|
+
return /* @__PURE__ */ React31.createElement("ul", { className: "brokr-md-list brokr-md-list-unordered", key: idx }, block.items?.map((item, li) => /* @__PURE__ */ React31.createElement("li", { key: li }, parseInline(item))));
|
|
7425
|
+
case "quote":
|
|
7426
|
+
return /* @__PURE__ */ React31.createElement("blockquote", { className: "brokr-md-quote", key: idx }, block.items?.map((line, lineIndex) => /* @__PURE__ */ React31.createElement("p", { className: "brokr-md-quote-line", key: lineIndex }, parseInline(line))));
|
|
7427
|
+
case "table":
|
|
7428
|
+
return /* @__PURE__ */ React31.createElement("div", { className: "brokr-md-table-wrap", key: idx }, /* @__PURE__ */ React31.createElement("table", { className: "brokr-md-table" }, /* @__PURE__ */ React31.createElement("thead", null, /* @__PURE__ */ React31.createElement("tr", null, block.headers?.map((header, headerIndex) => /* @__PURE__ */ React31.createElement("th", { key: headerIndex }, parseInline(header))))), /* @__PURE__ */ React31.createElement("tbody", null, block.rows?.map((row, rowIndex) => /* @__PURE__ */ React31.createElement("tr", { key: rowIndex }, row.map((cell, cellIndex) => /* @__PURE__ */ React31.createElement("td", { key: cellIndex }, parseInline(cell))))))));
|
|
7185
7429
|
case "paragraph":
|
|
7186
7430
|
default:
|
|
7187
7431
|
return /* @__PURE__ */ React31.createElement("p", { className: "brokr-md-paragraph", key: idx }, parseInline(block.content));
|
|
@@ -7220,6 +7464,12 @@ function MessageBubble({ message, isTyping, user }) {
|
|
|
7220
7464
|
}
|
|
7221
7465
|
|
|
7222
7466
|
// src/react/chat/MessagePane.tsx
|
|
7467
|
+
function StarterPromptButton({ prompt, onSend }) {
|
|
7468
|
+
const handleClick = useCallback21(() => {
|
|
7469
|
+
void onSend(prompt);
|
|
7470
|
+
}, [prompt, onSend]);
|
|
7471
|
+
return /* @__PURE__ */ React33.createElement("button", { className: "brokr-ai-chat-starter", onClick: handleClick, type: "button" }, prompt);
|
|
7472
|
+
}
|
|
7223
7473
|
function MessagePane({ starterPrompts, emptyTitle, emptyCopy, subtitle }) {
|
|
7224
7474
|
const {
|
|
7225
7475
|
displayMessages,
|
|
@@ -7250,20 +7500,17 @@ function MessagePane({ starterPrompts, emptyTitle, emptyCopy, subtitle }) {
|
|
|
7250
7500
|
);
|
|
7251
7501
|
});
|
|
7252
7502
|
}, [visibleMessages, isSubmitting, user]);
|
|
7253
|
-
return /* @__PURE__ */ React33.createElement("div", { className: "brokr-ai-chat-thread", "data-empty": isEmpty, ref: scrollContainerRef }, isEmpty ? /* @__PURE__ */ React33.createElement("div", { className: "brokr-ai-chat-empty" }, /* @__PURE__ */ React33.createElement(SparkIcon, { size: 28 }), /* @__PURE__ */ React33.createElement("h2", { className: "brokr-title" }, emptyTitle), subtitle ?? emptyCopy ? /* @__PURE__ */ React33.createElement("p", { className: "brokr-copy" }, subtitle ?? emptyCopy) : null, starterPrompts.length > 0 ? /* @__PURE__ */ React33.createElement("div", { className: "brokr-ai-chat-starters" }, starterPrompts.map((sp) => /* @__PURE__ */ React33.createElement(
|
|
7254
|
-
"button",
|
|
7255
|
-
{
|
|
7256
|
-
className: "brokr-ai-chat-starter",
|
|
7257
|
-
key: sp,
|
|
7258
|
-
onClick: () => void sendMessage(sp),
|
|
7259
|
-
type: "button"
|
|
7260
|
-
},
|
|
7261
|
-
sp
|
|
7262
|
-
))) : null) : /* @__PURE__ */ React33.createElement("div", { className: "brokr-ai-chat-thread-inner" }, (isPersist ? hasMoreMessages : memHasMore) ? /* @__PURE__ */ React33.createElement("div", { ref: sentinelRef, className: "brokr-ai-chat-sentinel" }, loadingOlder ? /* @__PURE__ */ React33.createElement("div", { style: { display: "flex", justifyContent: "center", padding: "0.5rem" } }, /* @__PURE__ */ React33.createElement(Skeleton, { width: 120, height: 12, radius: 6 })) : null) : null, messageElements, /* @__PURE__ */ React33.createElement("div", { ref: bottomRef })));
|
|
7503
|
+
return /* @__PURE__ */ React33.createElement("div", { className: "brokr-ai-chat-thread", "data-empty": isEmpty, ref: scrollContainerRef }, isEmpty ? /* @__PURE__ */ React33.createElement("div", { className: "brokr-ai-chat-empty" }, /* @__PURE__ */ React33.createElement(SparkIcon, { size: 28 }), /* @__PURE__ */ React33.createElement("h2", { className: "brokr-title" }, emptyTitle), subtitle ?? emptyCopy ? /* @__PURE__ */ React33.createElement("p", { className: "brokr-copy" }, subtitle ?? emptyCopy) : null, starterPrompts.length > 0 ? /* @__PURE__ */ React33.createElement("div", { className: "brokr-ai-chat-starters" }, starterPrompts.map((sp) => /* @__PURE__ */ React33.createElement(StarterPromptButton, { key: sp, prompt: sp, onSend: sendMessage }))) : null) : /* @__PURE__ */ React33.createElement("div", { className: "brokr-ai-chat-thread-inner" }, (isPersist ? hasMoreMessages : memHasMore) ? /* @__PURE__ */ React33.createElement("div", { ref: sentinelRef, className: "brokr-ai-chat-sentinel" }, loadingOlder ? /* @__PURE__ */ React33.createElement("div", { style: { display: "flex", justifyContent: "center", padding: "0.5rem" } }, /* @__PURE__ */ React33.createElement(Skeleton, { width: 120, height: 12, radius: 6 })) : null) : null, messageElements, /* @__PURE__ */ React33.createElement("div", { ref: bottomRef })));
|
|
7263
7504
|
}
|
|
7264
7505
|
|
|
7265
7506
|
// src/react/chat/ChatInput.tsx
|
|
7266
|
-
import React34, { useCallback as
|
|
7507
|
+
import React34, { useCallback as useCallback22, useEffect as useEffect11 } from "react";
|
|
7508
|
+
function CommandButton({ cmd, chatContext }) {
|
|
7509
|
+
const handleClick = useCallback22(() => {
|
|
7510
|
+
void cmd.run(chatContext);
|
|
7511
|
+
}, [cmd, chatContext]);
|
|
7512
|
+
return /* @__PURE__ */ React34.createElement("button", { className: "brokr-ai-chat-sidebar-button", key: cmd.id, onClick: handleClick, type: "button" }, cmd.text);
|
|
7513
|
+
}
|
|
7267
7514
|
function ChatInput() {
|
|
7268
7515
|
const {
|
|
7269
7516
|
input,
|
|
@@ -7281,29 +7528,20 @@ function ChatInput() {
|
|
|
7281
7528
|
el.style.height = "auto";
|
|
7282
7529
|
el.style.height = `${Math.min(el.scrollHeight, 168)}px`;
|
|
7283
7530
|
}, [input, textareaRef]);
|
|
7284
|
-
const handleSubmit =
|
|
7531
|
+
const handleSubmit = useCallback22((e) => {
|
|
7285
7532
|
e.preventDefault();
|
|
7286
7533
|
void sendMessage();
|
|
7287
7534
|
}, [sendMessage]);
|
|
7288
|
-
const handleKeyDown =
|
|
7535
|
+
const handleKeyDown = useCallback22((e) => {
|
|
7289
7536
|
if (e.key === "Enter" && !e.shiftKey) {
|
|
7290
7537
|
e.preventDefault();
|
|
7291
7538
|
void sendMessage();
|
|
7292
7539
|
}
|
|
7293
7540
|
}, [sendMessage]);
|
|
7294
|
-
const handleChange =
|
|
7541
|
+
const handleChange = useCallback22((e) => {
|
|
7295
7542
|
setInput(e.target.value);
|
|
7296
7543
|
}, [setInput]);
|
|
7297
|
-
return /* @__PURE__ */ React34.createElement("form", { className: "brokr-ai-chat-input-area", onSubmit: handleSubmit }, /* @__PURE__ */ React34.createElement("div", { className: "brokr-ai-chat-input-container" }, composerCommands.length > 0 ? /* @__PURE__ */ React34.createElement("div", { className: "brokr-ai-chat-composer-actions" }, composerCommands.map((cmd) => /* @__PURE__ */ React34.createElement(
|
|
7298
|
-
"button",
|
|
7299
|
-
{
|
|
7300
|
-
className: "brokr-ai-chat-sidebar-button",
|
|
7301
|
-
key: cmd.id,
|
|
7302
|
-
onClick: () => void cmd.run(chatContext),
|
|
7303
|
-
type: "button"
|
|
7304
|
-
},
|
|
7305
|
-
cmd.text
|
|
7306
|
-
))) : null, /* @__PURE__ */ React34.createElement("div", { className: "brokr-ai-chat-input-row" }, /* @__PURE__ */ React34.createElement(
|
|
7544
|
+
return /* @__PURE__ */ React34.createElement("form", { className: "brokr-ai-chat-input-area", onSubmit: handleSubmit }, /* @__PURE__ */ React34.createElement("div", { className: "brokr-ai-chat-input-container" }, composerCommands.length > 0 ? /* @__PURE__ */ React34.createElement("div", { className: "brokr-ai-chat-composer-actions" }, composerCommands.map((cmd) => /* @__PURE__ */ React34.createElement(CommandButton, { key: cmd.id, cmd, chatContext }))) : null, /* @__PURE__ */ React34.createElement("div", { className: "brokr-ai-chat-input-row" }, /* @__PURE__ */ React34.createElement(
|
|
7307
7545
|
"textarea",
|
|
7308
7546
|
{
|
|
7309
7547
|
className: "brokr-ai-chat-textarea",
|
|
@@ -7344,6 +7582,7 @@ function AIChat(inlineProps) {
|
|
|
7344
7582
|
subtitle,
|
|
7345
7583
|
model: modelProp,
|
|
7346
7584
|
modelSelector,
|
|
7585
|
+
memory,
|
|
7347
7586
|
variant = 1,
|
|
7348
7587
|
sidebar: sidebarProp,
|
|
7349
7588
|
threadMenu: threadMenuProp,
|
|
@@ -7367,16 +7606,12 @@ function AIChat(inlineProps) {
|
|
|
7367
7606
|
const headerVisible = variant !== 3;
|
|
7368
7607
|
const threadMenuVisible = threadMenuProp !== void 0 ? threadMenuProp : variant !== 3;
|
|
7369
7608
|
const modelSelectorVisible = (modelSelector !== void 0 ? modelSelector : true) && !modelProp;
|
|
7370
|
-
const [
|
|
7371
|
-
|
|
7372
|
-
|
|
7373
|
-
|
|
7374
|
-
useEffect12(() => {
|
|
7375
|
-
if (modelProp) setSelectedModel(modelProp);
|
|
7376
|
-
}, [modelProp]);
|
|
7377
|
-
const activeModel = modelProp ?? selectedModel;
|
|
7609
|
+
const [userSelectedModel, setUserSelectedModel] = useState18(null);
|
|
7610
|
+
const explicitModel = modelProp ?? userSelectedModel ?? void 0;
|
|
7611
|
+
const displayModel = explicitModel ?? providers.find((p) => p.free)?.model ?? providers[0]?.model ?? "";
|
|
7612
|
+
const activeModel = explicitModel ?? STACK_DEFAULT;
|
|
7378
7613
|
const activeProvider = useMemo23(
|
|
7379
|
-
() => providers.find((p) => p.model === activeModel) ?? providers[0],
|
|
7614
|
+
() => activeModel === STACK_DEFAULT ? void 0 : providers.find((p) => p.model === activeModel) ?? providers[0],
|
|
7380
7615
|
[activeModel]
|
|
7381
7616
|
);
|
|
7382
7617
|
const hasBalance = useMemo23(
|
|
@@ -7390,7 +7625,9 @@ function AIChat(inlineProps) {
|
|
|
7390
7625
|
const chat = useChat({
|
|
7391
7626
|
endpoint,
|
|
7392
7627
|
prompt,
|
|
7393
|
-
|
|
7628
|
+
explicitModel,
|
|
7629
|
+
displayModel,
|
|
7630
|
+
memory,
|
|
7394
7631
|
persist,
|
|
7395
7632
|
surface,
|
|
7396
7633
|
subject,
|
|
@@ -7437,11 +7674,19 @@ function AIChat(inlineProps) {
|
|
|
7437
7674
|
const [sidebarOpen, setSidebarOpen] = useState18(false);
|
|
7438
7675
|
const [threadMenuOpenId, setThreadMenuOpenId] = useState18(null);
|
|
7439
7676
|
const threadMenuRef = useRef7(null);
|
|
7440
|
-
const closeSidebar =
|
|
7441
|
-
const selectThreadAndCloseSidebar =
|
|
7677
|
+
const closeSidebar = useCallback23(() => setSidebarOpen(false), []);
|
|
7678
|
+
const selectThreadAndCloseSidebar = useCallback23((id) => {
|
|
7442
7679
|
chat.selectThread(id);
|
|
7443
7680
|
setSidebarOpen(false);
|
|
7444
7681
|
}, [chat.selectThread]);
|
|
7682
|
+
const handleCopy = useCallback23((content) => {
|
|
7683
|
+
navigator.clipboard.writeText(content).catch(() => {
|
|
7684
|
+
});
|
|
7685
|
+
}, []);
|
|
7686
|
+
const handleStartRename = useCallback23((threadId) => {
|
|
7687
|
+
setThreadMenuOpenId(null);
|
|
7688
|
+
chat.startRename(threadId);
|
|
7689
|
+
}, [chat.startRename]);
|
|
7445
7690
|
useEffect12(() => {
|
|
7446
7691
|
if (!threadMenuOpenId) return;
|
|
7447
7692
|
const onMouseDown = (e) => {
|
|
@@ -7490,26 +7735,20 @@ function AIChat(inlineProps) {
|
|
|
7490
7735
|
activeThread: chat.activeThread,
|
|
7491
7736
|
renderedTitle: finalTitle,
|
|
7492
7737
|
isTitleLoading: chat.isTitleLoading,
|
|
7493
|
-
activeModel,
|
|
7738
|
+
activeModel: displayModel,
|
|
7494
7739
|
activeProvider,
|
|
7495
|
-
selectedModel,
|
|
7496
|
-
setSelectedModel,
|
|
7740
|
+
selectedModel: displayModel,
|
|
7741
|
+
setSelectedModel: setUserSelectedModel,
|
|
7497
7742
|
availableProviders,
|
|
7498
7743
|
sendMessage: chat.sendMessage,
|
|
7499
7744
|
startNewChat: chat.startNewChat,
|
|
7500
7745
|
selectThread: chat.selectThread,
|
|
7501
7746
|
deleteThread: chat.deleteThread,
|
|
7502
|
-
handleCopy
|
|
7503
|
-
navigator.clipboard.writeText(content).catch(() => {
|
|
7504
|
-
});
|
|
7505
|
-
},
|
|
7747
|
+
handleCopy,
|
|
7506
7748
|
renamingId: chat.renamingId,
|
|
7507
7749
|
renameValue: chat.renameValue,
|
|
7508
7750
|
setRenameValue: chat.setRenameValue,
|
|
7509
|
-
startRename:
|
|
7510
|
-
setThreadMenuOpenId(null);
|
|
7511
|
-
chat.startRename(threadId);
|
|
7512
|
-
},
|
|
7751
|
+
startRename: handleStartRename,
|
|
7513
7752
|
submitRename: chat.submitRename,
|
|
7514
7753
|
displaySidebarItems: chat.displaySidebarItems,
|
|
7515
7754
|
threadsLoading: chat.threadsLoading,
|
|
@@ -7539,9 +7778,9 @@ function AIChat(inlineProps) {
|
|
|
7539
7778
|
}), [
|
|
7540
7779
|
chat,
|
|
7541
7780
|
finalTitle,
|
|
7542
|
-
|
|
7781
|
+
displayModel,
|
|
7543
7782
|
activeProvider,
|
|
7544
|
-
|
|
7783
|
+
userSelectedModel,
|
|
7545
7784
|
availableProviders,
|
|
7546
7785
|
sidebarOpen,
|
|
7547
7786
|
closeSidebar,
|
|
@@ -7578,8 +7817,8 @@ function AIChat(inlineProps) {
|
|
|
7578
7817
|
threadMenuRef,
|
|
7579
7818
|
threadMenuVisible,
|
|
7580
7819
|
setThreadMenuOpenId,
|
|
7581
|
-
activeModel,
|
|
7582
|
-
setSelectedModel,
|
|
7820
|
+
activeModel: displayModel,
|
|
7821
|
+
setSelectedModel: setUserSelectedModel,
|
|
7583
7822
|
availableProviders,
|
|
7584
7823
|
startRename: chatState.startRename,
|
|
7585
7824
|
deleteThread: chat.deleteThread
|
|
@@ -7595,6 +7834,19 @@ function AIChat(inlineProps) {
|
|
|
7595
7834
|
), /* @__PURE__ */ React35.createElement(ChatInput, null))
|
|
7596
7835
|
));
|
|
7597
7836
|
}
|
|
7837
|
+
function CommandButton2({ cmd, chatContext }) {
|
|
7838
|
+
const handleClick = useCallback23(() => {
|
|
7839
|
+
void cmd.run(chatContext);
|
|
7840
|
+
}, [cmd, chatContext]);
|
|
7841
|
+
return /* @__PURE__ */ React35.createElement("button", { className: "brokr-ai-chat-sidebar-button", onClick: handleClick, type: "button" }, cmd.text);
|
|
7842
|
+
}
|
|
7843
|
+
function MenuCommandItem({ cmd, chatContext, onClose }) {
|
|
7844
|
+
const handleClick = useCallback23(() => {
|
|
7845
|
+
onClose();
|
|
7846
|
+
void cmd.run(chatContext);
|
|
7847
|
+
}, [cmd, chatContext, onClose]);
|
|
7848
|
+
return /* @__PURE__ */ React35.createElement("button", { className: "brokr-ai-chat-thread-dropdown-item", onClick: handleClick, type: "button" }, cmd.text);
|
|
7849
|
+
}
|
|
7598
7850
|
function ChatHeader({
|
|
7599
7851
|
activeId,
|
|
7600
7852
|
sidebarVisible,
|
|
@@ -7614,10 +7866,20 @@ function ChatHeader({
|
|
|
7614
7866
|
startRename,
|
|
7615
7867
|
deleteThread
|
|
7616
7868
|
}) {
|
|
7617
|
-
const handleOpenSidebar =
|
|
7618
|
-
const handleToggleMenu =
|
|
7869
|
+
const handleOpenSidebar = useCallback23(() => setSidebarOpen(true), [setSidebarOpen]);
|
|
7870
|
+
const handleToggleMenu = useCallback23(() => {
|
|
7619
7871
|
setThreadMenuOpenId(threadMenuOpenId ? null : activeId);
|
|
7620
7872
|
}, [setThreadMenuOpenId, threadMenuOpenId, activeId]);
|
|
7873
|
+
const closeMenu = useCallback23(() => setThreadMenuOpenId(null), [setThreadMenuOpenId]);
|
|
7874
|
+
const handleRename = useCallback23(() => {
|
|
7875
|
+
if (activeId) startRename(activeId);
|
|
7876
|
+
}, [activeId, startRename]);
|
|
7877
|
+
const handleDelete = useCallback23(() => {
|
|
7878
|
+
if (activeId) {
|
|
7879
|
+
setThreadMenuOpenId(null);
|
|
7880
|
+
void deleteThread(activeId);
|
|
7881
|
+
}
|
|
7882
|
+
}, [activeId, deleteThread, setThreadMenuOpenId]);
|
|
7621
7883
|
return /* @__PURE__ */ React35.createElement("header", { className: "brokr-ai-chat-topbar" }, /* @__PURE__ */ React35.createElement("div", { className: "brokr-ai-chat-topbar-left" }, sidebarVisible ? /* @__PURE__ */ React35.createElement(
|
|
7622
7884
|
"button",
|
|
7623
7885
|
{
|
|
@@ -7627,16 +7889,7 @@ function ChatHeader({
|
|
|
7627
7889
|
type: "button"
|
|
7628
7890
|
},
|
|
7629
7891
|
/* @__PURE__ */ React35.createElement(MenuIcon, { size: 18 })
|
|
7630
|
-
) : null), /* @__PURE__ */ React35.createElement("div", { className: "brokr-ai-chat-topbar-actions" }, headerCommands.map((cmd) => /* @__PURE__ */ React35.createElement(
|
|
7631
|
-
"button",
|
|
7632
|
-
{
|
|
7633
|
-
className: "brokr-ai-chat-sidebar-button",
|
|
7634
|
-
key: cmd.id,
|
|
7635
|
-
onClick: () => void cmd.run(chatContext),
|
|
7636
|
-
type: "button"
|
|
7637
|
-
},
|
|
7638
|
-
cmd.text
|
|
7639
|
-
)), modelSelectorVisible ? /* @__PURE__ */ React35.createElement(
|
|
7892
|
+
) : null), /* @__PURE__ */ React35.createElement("div", { className: "brokr-ai-chat-topbar-actions" }, headerCommands.map((cmd) => /* @__PURE__ */ React35.createElement(CommandButton2, { key: cmd.id, cmd, chatContext })), modelSelectorVisible ? /* @__PURE__ */ React35.createElement(
|
|
7640
7893
|
ModelSelector,
|
|
7641
7894
|
{
|
|
7642
7895
|
activeModel,
|
|
@@ -7657,32 +7910,25 @@ function ChatHeader({
|
|
|
7657
7910
|
"button",
|
|
7658
7911
|
{
|
|
7659
7912
|
className: "brokr-ai-chat-thread-dropdown-item",
|
|
7660
|
-
onClick:
|
|
7913
|
+
onClick: handleRename,
|
|
7661
7914
|
type: "button"
|
|
7662
7915
|
},
|
|
7663
7916
|
/* @__PURE__ */ React35.createElement(PencilIcon, { size: 13 }),
|
|
7664
7917
|
"Rename"
|
|
7665
7918
|
), threadMenuCommands.map((cmd) => /* @__PURE__ */ React35.createElement(
|
|
7666
|
-
|
|
7919
|
+
MenuCommandItem,
|
|
7667
7920
|
{
|
|
7668
|
-
className: "brokr-ai-chat-thread-dropdown-item",
|
|
7669
7921
|
key: cmd.id,
|
|
7670
|
-
|
|
7671
|
-
|
|
7672
|
-
|
|
7673
|
-
|
|
7674
|
-
type: "button"
|
|
7675
|
-
},
|
|
7676
|
-
cmd.text
|
|
7922
|
+
cmd,
|
|
7923
|
+
chatContext,
|
|
7924
|
+
onClose: closeMenu
|
|
7925
|
+
}
|
|
7677
7926
|
)), /* @__PURE__ */ React35.createElement(
|
|
7678
7927
|
"button",
|
|
7679
7928
|
{
|
|
7680
7929
|
className: "brokr-ai-chat-thread-dropdown-item",
|
|
7681
7930
|
"data-tone": "danger",
|
|
7682
|
-
onClick:
|
|
7683
|
-
setThreadMenuOpenId(null);
|
|
7684
|
-
void deleteThread(activeId);
|
|
7685
|
-
},
|
|
7931
|
+
onClick: handleDelete,
|
|
7686
7932
|
type: "button"
|
|
7687
7933
|
},
|
|
7688
7934
|
/* @__PURE__ */ React35.createElement(TrashIcon, { size: 13 }),
|
|
@@ -7691,20 +7937,63 @@ function ChatHeader({
|
|
|
7691
7937
|
}
|
|
7692
7938
|
|
|
7693
7939
|
// src/react/composites/FabAI.tsx
|
|
7694
|
-
import React36, { useCallback as
|
|
7940
|
+
import React36, { useCallback as useCallback24, useMemo as useMemo24, useRef as useRef8, useState as useState19 } from "react";
|
|
7941
|
+
|
|
7942
|
+
// src/react/composites/fab-context.ts
|
|
7943
|
+
function buildFabSystemPrompt(appContext, brandName, existingPrompt) {
|
|
7944
|
+
if (appContext === false) return existingPrompt;
|
|
7945
|
+
const name = appContext?.name ?? brandName;
|
|
7946
|
+
if (!name && !appContext) return existingPrompt;
|
|
7947
|
+
const parts = [];
|
|
7948
|
+
if (name) parts.push(`You are an AI assistant embedded in ${name}.`);
|
|
7949
|
+
if (appContext?.description) parts.push(`This app is ${appContext.description}.`);
|
|
7950
|
+
if (appContext?.currentPage) parts.push(`The user is currently on: ${appContext.currentPage}.`);
|
|
7951
|
+
if (appContext?.facts?.length) parts.push(...appContext.facts);
|
|
7952
|
+
const autoPrompt = parts.join(" ");
|
|
7953
|
+
return existingPrompt ? `${autoPrompt}
|
|
7954
|
+
|
|
7955
|
+
${existingPrompt}` : autoPrompt;
|
|
7956
|
+
}
|
|
7957
|
+
|
|
7958
|
+
// src/react/composites/FabAI.tsx
|
|
7959
|
+
function ensureAssistantReply(messages, content) {
|
|
7960
|
+
const normalized = content.trim() ? content : "No response received.";
|
|
7961
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
7962
|
+
if (messages[index]?.role !== "assistant") continue;
|
|
7963
|
+
const next = [...messages];
|
|
7964
|
+
next[index] = { ...next[index], content: normalized };
|
|
7965
|
+
return next;
|
|
7966
|
+
}
|
|
7967
|
+
return [...messages, { role: "assistant", content: normalized }];
|
|
7968
|
+
}
|
|
7969
|
+
function appendAssistantDelta(messages, delta) {
|
|
7970
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
7971
|
+
if (messages[index]?.role !== "assistant") continue;
|
|
7972
|
+
const next = [...messages];
|
|
7973
|
+
next[index] = {
|
|
7974
|
+
...next[index],
|
|
7975
|
+
content: `${contentToText(next[index].content)}${delta}`
|
|
7976
|
+
};
|
|
7977
|
+
return next;
|
|
7978
|
+
}
|
|
7979
|
+
return [...messages, { role: "assistant", content: delta }];
|
|
7980
|
+
}
|
|
7695
7981
|
function FabAI({
|
|
7982
|
+
appContext,
|
|
7696
7983
|
model,
|
|
7984
|
+
memory,
|
|
7697
7985
|
onSendMessage,
|
|
7698
7986
|
position = "bottom-right",
|
|
7699
|
-
starterPrompts = [],
|
|
7700
7987
|
systemPrompt
|
|
7701
7988
|
}) {
|
|
7702
|
-
const { can, user } = useBrokr();
|
|
7989
|
+
const { can, user, theme } = useBrokr();
|
|
7990
|
+
const brandName = theme?.brand?.name;
|
|
7703
7991
|
const [isOpen, setIsOpen] = useState19(false);
|
|
7704
7992
|
const [input, setInput] = useState19("");
|
|
7705
7993
|
const [error, setError] = useState19(null);
|
|
7706
7994
|
const [isSending, setIsSending] = useState19(false);
|
|
7707
7995
|
const [messages, setMessages] = useState19([]);
|
|
7996
|
+
const conversationIdRef = useRef8(`fab_${crypto.randomUUID()}`);
|
|
7708
7997
|
const launcherStyle = useMemo24(
|
|
7709
7998
|
() => ({
|
|
7710
7999
|
left: position === "bottom-left" ? "var(--brokr-space-6)" : void 0,
|
|
@@ -7713,100 +8002,125 @@ function FabAI({
|
|
|
7713
8002
|
[position]
|
|
7714
8003
|
);
|
|
7715
8004
|
const canChat = useMemo24(() => can("ai.chat"), [can]);
|
|
7716
|
-
const toggleOpen =
|
|
8005
|
+
const toggleOpen = useCallback24(() => {
|
|
7717
8006
|
if (!canChat) {
|
|
7718
8007
|
redirectTo("/pricing");
|
|
7719
8008
|
return;
|
|
7720
8009
|
}
|
|
7721
8010
|
setIsOpen((current) => !current);
|
|
7722
8011
|
}, [canChat]);
|
|
7723
|
-
const handleInputChange =
|
|
8012
|
+
const handleInputChange = useCallback24((event) => {
|
|
7724
8013
|
setInput(event.target.value);
|
|
7725
8014
|
}, []);
|
|
7726
|
-
const handleClose =
|
|
8015
|
+
const handleClose = useCallback24(() => {
|
|
7727
8016
|
setIsOpen(false);
|
|
7728
8017
|
}, []);
|
|
7729
|
-
const sendPrompt =
|
|
8018
|
+
const sendPrompt = useCallback24(async (prompt) => {
|
|
7730
8019
|
const nextPrompt = prompt.trim();
|
|
7731
|
-
if (!nextPrompt) return;
|
|
7732
|
-
const
|
|
8020
|
+
if (!nextPrompt || isSending) return;
|
|
8021
|
+
const userMessage = { role: "user", content: nextPrompt };
|
|
8022
|
+
const nextMessages = [...messages, userMessage];
|
|
8023
|
+
const optimisticMessages = [...nextMessages, { role: "assistant", content: "" }];
|
|
7733
8024
|
try {
|
|
7734
8025
|
setError(null);
|
|
7735
8026
|
setIsSending(true);
|
|
7736
|
-
setMessages(
|
|
8027
|
+
setMessages(optimisticMessages);
|
|
7737
8028
|
setInput("");
|
|
7738
|
-
let responseText = "";
|
|
7739
8029
|
if (onSendMessage) {
|
|
7740
|
-
|
|
7741
|
-
|
|
7742
|
-
|
|
7743
|
-
|
|
7744
|
-
|
|
7745
|
-
|
|
7746
|
-
|
|
7747
|
-
|
|
8030
|
+
const responseText2 = await onSendMessage({ messages: nextMessages, model, systemPrompt });
|
|
8031
|
+
setMessages((current) => ensureAssistantReply(current, responseText2));
|
|
8032
|
+
return;
|
|
8033
|
+
}
|
|
8034
|
+
const response = await fetch("/api/brokr/chat", {
|
|
8035
|
+
method: "POST",
|
|
8036
|
+
credentials: "include",
|
|
8037
|
+
headers: { "Content-Type": "application/json" },
|
|
8038
|
+
body: JSON.stringify({
|
|
8039
|
+
conversationId: conversationIdRef.current,
|
|
8040
|
+
messages: trimToTokenBudget(
|
|
8041
|
+
nextMessages.filter((message) => message.role === "user" || message.role === "assistant" && Boolean(contentToText(message.content))).map((message) => ({
|
|
8042
|
+
role: message.role,
|
|
8043
|
+
content: contentToText(message.content)
|
|
8044
|
+
}))
|
|
8045
|
+
),
|
|
8046
|
+
...model !== void 0 ? { model } : {},
|
|
8047
|
+
...(() => {
|
|
8048
|
+
const withContext = buildFabSystemPrompt(appContext, brandName, systemPrompt);
|
|
8049
|
+
const ep = buildEffectivePrompt(withContext, memory);
|
|
8050
|
+
return ep ? { systemPrompt: ep } : {};
|
|
8051
|
+
})()
|
|
8052
|
+
})
|
|
8053
|
+
});
|
|
8054
|
+
if (!response.ok) {
|
|
8055
|
+
const payload2 = await response.json().catch(() => ({}));
|
|
8056
|
+
throw new Error(payload2.message ?? payload2.error ?? `Chat failed (${response.status})`);
|
|
8057
|
+
}
|
|
8058
|
+
if (isSSEResponse(response)) {
|
|
8059
|
+
let hasDelta = false;
|
|
8060
|
+
for await (const event of parseSSEStream(response)) {
|
|
8061
|
+
if (event.type === "conversation") {
|
|
8062
|
+
conversationIdRef.current = event.id;
|
|
8063
|
+
} else if (event.type === "delta") {
|
|
8064
|
+
hasDelta = true;
|
|
8065
|
+
setMessages((current) => appendAssistantDelta(current, event.delta));
|
|
8066
|
+
} else if (event.type === "error") {
|
|
8067
|
+
throw new Error(event.message);
|
|
7748
8068
|
}
|
|
7749
|
-
|
|
7750
|
-
|
|
8069
|
+
}
|
|
8070
|
+
if (!hasDelta) {
|
|
8071
|
+
setMessages((current) => ensureAssistantReply(current, "No response received."));
|
|
8072
|
+
}
|
|
8073
|
+
return;
|
|
8074
|
+
}
|
|
8075
|
+
const payload = await response.json().catch(() => ({}));
|
|
8076
|
+
if (payload.error || payload.message) {
|
|
8077
|
+
throw new Error(payload.message ?? payload.error ?? "AI request failed.");
|
|
7751
8078
|
}
|
|
7752
|
-
|
|
7753
|
-
|
|
7754
|
-
content: responseText || "No response received."
|
|
7755
|
-
}]);
|
|
8079
|
+
const responseText = payload.content ?? payload.response ?? payload.text ?? "";
|
|
8080
|
+
setMessages((current) => ensureAssistantReply(current, responseText));
|
|
7756
8081
|
} catch (cause) {
|
|
7757
|
-
|
|
8082
|
+
const message = cause instanceof Error ? cause.message : "Could not send message.";
|
|
8083
|
+
setError(message);
|
|
8084
|
+
setMessages((current) => ensureAssistantReply(current, `Error: ${message}`));
|
|
7758
8085
|
} finally {
|
|
7759
8086
|
setIsSending(false);
|
|
7760
8087
|
}
|
|
7761
|
-
}, [messages, model, onSendMessage, systemPrompt]);
|
|
7762
|
-
const handleSubmit =
|
|
8088
|
+
}, [isSending, messages, model, onSendMessage, systemPrompt]);
|
|
8089
|
+
const handleSubmit = useCallback24(async (event) => {
|
|
7763
8090
|
event.preventDefault();
|
|
7764
8091
|
await sendPrompt(input);
|
|
7765
8092
|
}, [input, sendPrompt]);
|
|
7766
|
-
const handleKeyDown =
|
|
8093
|
+
const handleKeyDown = useCallback24((event) => {
|
|
7767
8094
|
if (event.key === "Enter" && !event.shiftKey) {
|
|
7768
8095
|
event.preventDefault();
|
|
7769
8096
|
void sendPrompt(input);
|
|
7770
8097
|
}
|
|
7771
8098
|
}, [input, sendPrompt]);
|
|
7772
|
-
const handleStarterPrompt = useCallback23((prompt) => {
|
|
7773
|
-
void sendPrompt(prompt);
|
|
7774
|
-
}, [sendPrompt]);
|
|
7775
8099
|
return /* @__PURE__ */ React36.createElement(React36.Fragment, null, /* @__PURE__ */ React36.createElement("div", { className: "brokr-chat-fab", style: launcherStyle }, /* @__PURE__ */ React36.createElement(
|
|
7776
8100
|
"button",
|
|
7777
8101
|
{
|
|
8102
|
+
"aria-label": isOpen ? "Close AI chat" : "Open AI chat",
|
|
7778
8103
|
"aria-expanded": isOpen,
|
|
7779
8104
|
"aria-haspopup": "dialog",
|
|
7780
|
-
className: "brokr-
|
|
8105
|
+
className: "brokr-chat-fab-trigger",
|
|
7781
8106
|
onClick: toggleOpen,
|
|
7782
8107
|
type: "button"
|
|
7783
8108
|
},
|
|
7784
|
-
/* @__PURE__ */ React36.createElement(
|
|
7785
|
-
|
|
7786
|
-
)), isOpen ? /* @__PURE__ */ React36.createElement("div", { className: "brokr-panel brokr-chat-panel", role: "dialog" }, /* @__PURE__ */ React36.createElement("div", { className: "brokr-brand-row", style: { justifyContent: "space-between" } }, /* @__PURE__ */ React36.createElement("div", { className: "brokr-section", style: { gap: "0.25rem" } }, /* @__PURE__ */ React36.createElement("strong", null, "AI Chat"), user?.name ? /* @__PURE__ */ React36.createElement("span", { className: "brokr-copy" }, user.name) : null), /* @__PURE__ */ React36.createElement("button", { className: "brokr-button-ghost", onClick: handleClose, type: "button" }, /* @__PURE__ */ React36.createElement(CloseIcon, { size: 16 }))), /* @__PURE__ */ React36.createElement(
|
|
8109
|
+
/* @__PURE__ */ React36.createElement(SparkIcon, { size: 18 })
|
|
8110
|
+
)), isOpen ? /* @__PURE__ */ React36.createElement("div", { className: "brokr-panel brokr-chat-panel", role: "dialog" }, /* @__PURE__ */ React36.createElement("div", { className: "brokr-brand-row", style: { justifyContent: "space-between" } }, /* @__PURE__ */ React36.createElement("div", { className: "brokr-section", style: { gap: "var(--brokr-space-1)" } }, /* @__PURE__ */ React36.createElement("strong", null, "AI Chat"), user?.name ? /* @__PURE__ */ React36.createElement("span", { className: "brokr-copy" }, user.name) : null), /* @__PURE__ */ React36.createElement("button", { className: "brokr-button-ghost", onClick: handleClose, type: "button" }, /* @__PURE__ */ React36.createElement(CloseIcon, { size: 16 }))), /* @__PURE__ */ React36.createElement(
|
|
7787
8111
|
"div",
|
|
7788
8112
|
{
|
|
7789
8113
|
className: "brokr-chat-messages",
|
|
7790
8114
|
"data-empty": messages.length === 0
|
|
7791
8115
|
},
|
|
7792
|
-
messages.length === 0 ? /* @__PURE__ */ React36.createElement("div", { className: "brokr-chat-empty" }, /* @__PURE__ */ React36.createElement(SparkIcon, { size: 18 }), /* @__PURE__ */ React36.createElement("div", { className: "brokr-section", style: { gap: "
|
|
7793
|
-
|
|
7794
|
-
|
|
7795
|
-
|
|
7796
|
-
return /* @__PURE__ */ React36.createElement(
|
|
7797
|
-
|
|
7798
|
-
{
|
|
7799
|
-
className: "brokr-chat-starter",
|
|
7800
|
-
key: prompt,
|
|
7801
|
-
onClick: handleClick,
|
|
7802
|
-
type: "button"
|
|
7803
|
-
},
|
|
7804
|
-
prompt
|
|
7805
|
-
);
|
|
7806
|
-
})) : null) : null,
|
|
7807
|
-
messages.map((message, index) => /* @__PURE__ */ React36.createElement("div", { className: "brokr-chat-bubble", "data-role": message.role, key: `${message.role}-${index}` }, contentToText(message.content))),
|
|
8116
|
+
messages.length === 0 ? /* @__PURE__ */ React36.createElement("div", { className: "brokr-chat-empty" }, /* @__PURE__ */ React36.createElement(SparkIcon, { size: 18 }), /* @__PURE__ */ React36.createElement("div", { className: "brokr-section", style: { gap: "var(--brokr-space-1)" } }, /* @__PURE__ */ React36.createElement("strong", null, "Send a message to chat with the AI."), /* @__PURE__ */ React36.createElement("span", { className: "brokr-copy" }, "Ask a question or drop in a starter prompt below."))) : null,
|
|
8117
|
+
messages.map((message, index) => {
|
|
8118
|
+
const text = contentToText(message.content);
|
|
8119
|
+
const isTyping = message.role === "assistant" && !text && isSending && index === messages.length - 1;
|
|
8120
|
+
return /* @__PURE__ */ React36.createElement("div", { className: "brokr-chat-bubble", "data-role": message.role, key: `${message.role}-${index}` }, isTyping ? /* @__PURE__ */ React36.createElement("div", { className: "brokr-ai-chat-typing", "aria-label": "AI is typing" }, /* @__PURE__ */ React36.createElement("span", null), /* @__PURE__ */ React36.createElement("span", null), /* @__PURE__ */ React36.createElement("span", null)) : message.role === "assistant" ? /* @__PURE__ */ React36.createElement(MarkdownRenderer, { content: text }) : text);
|
|
8121
|
+
}),
|
|
7808
8122
|
error ? /* @__PURE__ */ React36.createElement("div", { className: "brokr-inline-message", "data-tone": "error" }, error) : null
|
|
7809
|
-
), /* @__PURE__ */ React36.createElement("form", { className: "brokr-section", onSubmit: handleSubmit, style: { gap: "
|
|
8123
|
+
), /* @__PURE__ */ React36.createElement("form", { className: "brokr-section", onSubmit: handleSubmit, style: { gap: "var(--brokr-space-3)" } }, /* @__PURE__ */ React36.createElement(
|
|
7810
8124
|
"textarea",
|
|
7811
8125
|
{
|
|
7812
8126
|
className: "brokr-textarea brokr-chat-input",
|
|
@@ -7816,15 +8130,15 @@ function FabAI({
|
|
|
7816
8130
|
rows: 2,
|
|
7817
8131
|
value: input
|
|
7818
8132
|
}
|
|
7819
|
-
), /* @__PURE__ */ React36.createElement("button", { className: "brokr-button", disabled: isSending, type: "submit" }, isSending ? "Thinking" : "Send"))) : null);
|
|
8133
|
+
), /* @__PURE__ */ React36.createElement("button", { className: "brokr-button", disabled: isSending || !input.trim(), type: "submit" }, isSending ? "Thinking" : "Send"))) : null);
|
|
7820
8134
|
}
|
|
7821
8135
|
|
|
7822
8136
|
// src/react/composites/SmartUpload.tsx
|
|
7823
8137
|
import React37, {
|
|
7824
|
-
useCallback as
|
|
8138
|
+
useCallback as useCallback25,
|
|
7825
8139
|
useId as useId2,
|
|
7826
8140
|
useMemo as useMemo25,
|
|
7827
|
-
useRef as
|
|
8141
|
+
useRef as useRef9,
|
|
7828
8142
|
useState as useState20
|
|
7829
8143
|
} from "react";
|
|
7830
8144
|
function SmartUpload({
|
|
@@ -7835,7 +8149,7 @@ function SmartUpload({
|
|
|
7835
8149
|
}) {
|
|
7836
8150
|
const { paymentsMode } = useBrokr();
|
|
7837
8151
|
const inputId = useId2();
|
|
7838
|
-
const inputRef =
|
|
8152
|
+
const inputRef = useRef9(null);
|
|
7839
8153
|
const [dragActive, setDragActive] = useState20(false);
|
|
7840
8154
|
const [error, setError] = useState20(null);
|
|
7841
8155
|
const [fileName, setFileName] = useState20(null);
|
|
@@ -7844,7 +8158,7 @@ function SmartUpload({
|
|
|
7844
8158
|
const helperText = useMemo25(() => {
|
|
7845
8159
|
return `${Math.round(maxSize / (1024 * 1024))} MB max file size.`;
|
|
7846
8160
|
}, [maxSize]);
|
|
7847
|
-
const beginUpload =
|
|
8161
|
+
const beginUpload = useCallback25((file) => {
|
|
7848
8162
|
if (file.size > maxSize) {
|
|
7849
8163
|
setError(`That file is larger than ${Math.round(maxSize / (1024 * 1024))} MB.`);
|
|
7850
8164
|
return;
|
|
@@ -7881,31 +8195,31 @@ function SmartUpload({
|
|
|
7881
8195
|
body.append("purpose", purpose);
|
|
7882
8196
|
request.send(body);
|
|
7883
8197
|
}, [maxSize, onUpload, purpose]);
|
|
7884
|
-
const handleInputChange =
|
|
8198
|
+
const handleInputChange = useCallback25((event) => {
|
|
7885
8199
|
const file = event.target.files?.[0];
|
|
7886
8200
|
event.target.value = "";
|
|
7887
8201
|
if (!file) return;
|
|
7888
8202
|
beginUpload(file);
|
|
7889
8203
|
}, [beginUpload]);
|
|
7890
|
-
const handleDrop =
|
|
8204
|
+
const handleDrop = useCallback25((event) => {
|
|
7891
8205
|
event.preventDefault();
|
|
7892
8206
|
setDragActive(false);
|
|
7893
8207
|
const file = event.dataTransfer.files?.[0];
|
|
7894
8208
|
if (!file) return;
|
|
7895
8209
|
beginUpload(file);
|
|
7896
8210
|
}, [beginUpload]);
|
|
7897
|
-
const handleDragEnter =
|
|
8211
|
+
const handleDragEnter = useCallback25((event) => {
|
|
7898
8212
|
event.preventDefault();
|
|
7899
8213
|
setDragActive(true);
|
|
7900
8214
|
}, []);
|
|
7901
|
-
const handleDragLeave =
|
|
8215
|
+
const handleDragLeave = useCallback25((event) => {
|
|
7902
8216
|
event.preventDefault();
|
|
7903
8217
|
setDragActive(false);
|
|
7904
8218
|
}, []);
|
|
7905
|
-
const handleBrowse =
|
|
8219
|
+
const handleBrowse = useCallback25(() => {
|
|
7906
8220
|
inputRef.current?.click();
|
|
7907
8221
|
}, []);
|
|
7908
|
-
return /* @__PURE__ */ React37.createElement("div", { className: "brokr-card brokr-upload-shell" }, /* @__PURE__ */ React37.createElement("div", { className: "brokr-brand-row", style: { justifyContent: "space-between" } }, /* @__PURE__ */ React37.createElement("div", { className: "brokr-section", style: { gap: "
|
|
8222
|
+
return /* @__PURE__ */ React37.createElement("div", { className: "brokr-card brokr-upload-shell" }, /* @__PURE__ */ React37.createElement("div", { className: "brokr-brand-row", style: { justifyContent: "space-between" } }, /* @__PURE__ */ React37.createElement("div", { className: "brokr-section", style: { gap: "var(--brokr-space-1)" } }, /* @__PURE__ */ React37.createElement("strong", null, "Upload files"), /* @__PURE__ */ React37.createElement("span", { className: "brokr-copy" }, "Drop a file and let Brokr handle the boring part.")), paymentsMode === "sandbox" ? /* @__PURE__ */ React37.createElement("span", { className: "brokr-badge brokr-badge-sandbox" }, "Sandbox") : null), /* @__PURE__ */ React37.createElement(
|
|
7909
8223
|
"label",
|
|
7910
8224
|
{
|
|
7911
8225
|
className: "brokr-upload-dropzone",
|
|
@@ -7930,11 +8244,11 @@ function SmartUpload({
|
|
|
7930
8244
|
ref: inputRef,
|
|
7931
8245
|
type: "file"
|
|
7932
8246
|
}
|
|
7933
|
-
), fileName ? /* @__PURE__ */ React37.createElement("div", { className: "brokr-card brokr-upload-file" }, /* @__PURE__ */ React37.createElement("div", { className: "brokr-section", style: { gap: "
|
|
8247
|
+
), fileName ? /* @__PURE__ */ React37.createElement("div", { className: "brokr-card brokr-upload-file" }, /* @__PURE__ */ React37.createElement("div", { className: "brokr-section", style: { gap: "var(--brokr-space-1)" } }, /* @__PURE__ */ React37.createElement("strong", null, fileName), /* @__PURE__ */ React37.createElement("span", { className: "brokr-copy" }, isUploading ? `Uploading ${progress}%` : progress === 100 ? "Processed" : "Queued")), /* @__PURE__ */ React37.createElement("div", { className: "brokr-meter-bar" }, /* @__PURE__ */ React37.createElement("div", { className: "brokr-meter-fill", style: { width: `${progress}%` } }))) : null, error ? /* @__PURE__ */ React37.createElement("div", { className: "brokr-inline-message", "data-tone": "error" }, error) : null);
|
|
7934
8248
|
}
|
|
7935
8249
|
|
|
7936
8250
|
// src/react/composites/FeedbackWidget.tsx
|
|
7937
|
-
import React38, { useCallback as
|
|
8251
|
+
import React38, { useCallback as useCallback26, useState as useState21 } from "react";
|
|
7938
8252
|
function FeedbackWidget({
|
|
7939
8253
|
context,
|
|
7940
8254
|
onSubmit
|
|
@@ -7945,16 +8259,16 @@ function FeedbackWidget({
|
|
|
7945
8259
|
const [error, setError] = useState21(null);
|
|
7946
8260
|
const [message, setMessage] = useState21(null);
|
|
7947
8261
|
const [isPending, setIsPending] = useState21(false);
|
|
7948
|
-
const handleTextChange =
|
|
8262
|
+
const handleTextChange = useCallback26((event) => {
|
|
7949
8263
|
setText(event.target.value);
|
|
7950
8264
|
}, []);
|
|
7951
|
-
const handleRateUp =
|
|
8265
|
+
const handleRateUp = useCallback26(() => {
|
|
7952
8266
|
setRating("up");
|
|
7953
8267
|
}, []);
|
|
7954
|
-
const handleRateDown =
|
|
8268
|
+
const handleRateDown = useCallback26(() => {
|
|
7955
8269
|
setRating("down");
|
|
7956
8270
|
}, []);
|
|
7957
|
-
const handleSubmit =
|
|
8271
|
+
const handleSubmit = useCallback26(async (event) => {
|
|
7958
8272
|
event.preventDefault();
|
|
7959
8273
|
if (!rating) {
|
|
7960
8274
|
setError("Choose a direction first.");
|
|
@@ -8270,7 +8584,7 @@ var BrokrErrorBoundary = class extends React39.Component {
|
|
|
8270
8584
|
};
|
|
8271
8585
|
|
|
8272
8586
|
// src/react/notifications/NotificationBell.tsx
|
|
8273
|
-
import React40, { useCallback as
|
|
8587
|
+
import React40, { useCallback as useCallback27, useEffect as useEffect13, useMemo as useMemo26, useRef as useRef10, useState as useState22 } from "react";
|
|
8274
8588
|
|
|
8275
8589
|
// src/react/notifications/use-notifications.ts
|
|
8276
8590
|
import { useContext as useContext4 } from "react";
|
|
@@ -8295,12 +8609,34 @@ function timeAgo(iso) {
|
|
|
8295
8609
|
const days = Math.floor(hours / 24);
|
|
8296
8610
|
return `${days}d ago`;
|
|
8297
8611
|
}
|
|
8612
|
+
function NotifDropdownItem({
|
|
8613
|
+
notif,
|
|
8614
|
+
registry,
|
|
8615
|
+
onClick
|
|
8616
|
+
}) {
|
|
8617
|
+
const handleClick = useCallback27(() => onClick(notif), [notif, onClick]);
|
|
8618
|
+
const notifData = notif.data ?? {};
|
|
8619
|
+
const notifType = notifData.type ?? "default";
|
|
8620
|
+
const resolved = resolveNotificationType(registry, notifType, notifData);
|
|
8621
|
+
return /* @__PURE__ */ React40.createElement(
|
|
8622
|
+
"button",
|
|
8623
|
+
{
|
|
8624
|
+
type: "button",
|
|
8625
|
+
className: `brokr-notif-item${notif.read ? "" : " brokr-notif-item--unread"}`,
|
|
8626
|
+
onClick: handleClick,
|
|
8627
|
+
role: "menuitem"
|
|
8628
|
+
},
|
|
8629
|
+
resolved.image ? /* @__PURE__ */ React40.createElement("img", { src: resolved.image.url, alt: resolved.image.alt, className: "brokr-notif-item-logo" }) : /* @__PURE__ */ React40.createElement("span", { className: `brokr-notif-item-dot brokr-notif-item-dot--${notif.variant}` }),
|
|
8630
|
+
/* @__PURE__ */ React40.createElement("div", { className: "brokr-notif-item-body" }, /* @__PURE__ */ React40.createElement("span", { className: "brokr-notif-item-title" }, notif.title), /* @__PURE__ */ React40.createElement("span", { className: "brokr-notif-item-message" }, notif.message)),
|
|
8631
|
+
/* @__PURE__ */ React40.createElement("span", { className: "brokr-notif-item-time" }, timeAgo(notif.createdAt))
|
|
8632
|
+
);
|
|
8633
|
+
}
|
|
8298
8634
|
function NotificationBell() {
|
|
8299
8635
|
const { notifications, unreadCount, markRead, markAllRead, isLoading, registry } = useNotifications();
|
|
8300
8636
|
const [open, setOpen] = useState22(false);
|
|
8301
|
-
const containerRef =
|
|
8302
|
-
const markReadTimerRef =
|
|
8303
|
-
const toggle =
|
|
8637
|
+
const containerRef = useRef10(null);
|
|
8638
|
+
const markReadTimerRef = useRef10(null);
|
|
8639
|
+
const toggle = useCallback27(() => setOpen((o) => !o), []);
|
|
8304
8640
|
useEffect13(() => {
|
|
8305
8641
|
if (markReadTimerRef.current) {
|
|
8306
8642
|
clearTimeout(markReadTimerRef.current);
|
|
@@ -8342,7 +8678,7 @@ function NotificationBell() {
|
|
|
8342
8678
|
),
|
|
8343
8679
|
[notifications]
|
|
8344
8680
|
);
|
|
8345
|
-
const handleItemClick =
|
|
8681
|
+
const handleItemClick = useCallback27((notif) => {
|
|
8346
8682
|
if (!notif.read) markRead(notif.id);
|
|
8347
8683
|
const notifData = notif.data ?? {};
|
|
8348
8684
|
const notifType = notif.type ?? notifData.type ?? "default";
|
|
@@ -8359,7 +8695,8 @@ function NotificationBell() {
|
|
|
8359
8695
|
className: "brokr-notif-bell",
|
|
8360
8696
|
onClick: toggle,
|
|
8361
8697
|
"aria-label": `Notifications${unreadCount > 0 ? ` (${unreadCount} unread)` : ""}`,
|
|
8362
|
-
"aria-expanded": open
|
|
8698
|
+
"aria-expanded": open,
|
|
8699
|
+
"aria-haspopup": "menu"
|
|
8363
8700
|
},
|
|
8364
8701
|
/* @__PURE__ */ React40.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__ */ React40.createElement("path", { d: "M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9" }), /* @__PURE__ */ React40.createElement("path", { d: "M10.3 21a1.94 1.94 0 0 0 3.4 0" })),
|
|
8365
8702
|
unreadCount > 0 && /* @__PURE__ */ React40.createElement("span", { className: "brokr-notif-badge" }, unreadCount > 99 ? "99+" : unreadCount)
|
|
@@ -8371,35 +8708,19 @@ function NotificationBell() {
|
|
|
8371
8708
|
onClick: markAllRead
|
|
8372
8709
|
},
|
|
8373
8710
|
"Mark all read"
|
|
8374
|
-
)), /* @__PURE__ */ React40.createElement("div", { className: "brokr-notif-dropdown-list" }, isLoading ? /* @__PURE__ */ React40.createElement("div", { className: "brokr-notif-empty" }, /* @__PURE__ */ React40.createElement("span", { className: "brokr-notif-empty-text" }, "Loading\u2026")) : sorted.length === 0 ? /* @__PURE__ */ React40.createElement("div", { className: "brokr-notif-empty" }, /* @__PURE__ */ React40.createElement("span", { className: "brokr-notif-empty-text" }, "No notifications yet")) : sorted.map((notif) =>
|
|
8375
|
-
|
|
8376
|
-
|
|
8377
|
-
|
|
8378
|
-
|
|
8379
|
-
|
|
8380
|
-
|
|
8381
|
-
|
|
8382
|
-
|
|
8383
|
-
className: `brokr-notif-item${notif.read ? "" : " brokr-notif-item--unread"}`,
|
|
8384
|
-
onClick: () => handleItemClick(notif),
|
|
8385
|
-
role: "menuitem"
|
|
8386
|
-
},
|
|
8387
|
-
resolved.image ? /* @__PURE__ */ React40.createElement(
|
|
8388
|
-
"img",
|
|
8389
|
-
{
|
|
8390
|
-
src: resolved.image.url,
|
|
8391
|
-
alt: resolved.image.alt,
|
|
8392
|
-
className: "brokr-notif-item-logo"
|
|
8393
|
-
}
|
|
8394
|
-
) : /* @__PURE__ */ React40.createElement("span", { className: `brokr-notif-item-dot brokr-notif-item-dot--${notif.variant}` }),
|
|
8395
|
-
/* @__PURE__ */ React40.createElement("div", { className: "brokr-notif-item-body" }, /* @__PURE__ */ React40.createElement("span", { className: "brokr-notif-item-title" }, notif.title), /* @__PURE__ */ React40.createElement("span", { className: "brokr-notif-item-message" }, notif.message)),
|
|
8396
|
-
/* @__PURE__ */ React40.createElement("span", { className: "brokr-notif-item-time" }, timeAgo(notif.createdAt))
|
|
8397
|
-
);
|
|
8398
|
-
}))));
|
|
8711
|
+
)), /* @__PURE__ */ React40.createElement("div", { className: "brokr-notif-dropdown-list" }, isLoading ? /* @__PURE__ */ React40.createElement("div", { className: "brokr-notif-empty" }, /* @__PURE__ */ React40.createElement("span", { className: "brokr-notif-empty-text" }, "Loading\u2026")) : sorted.length === 0 ? /* @__PURE__ */ React40.createElement("div", { className: "brokr-notif-empty" }, /* @__PURE__ */ React40.createElement("span", { className: "brokr-notif-empty-text" }, "No notifications yet")) : sorted.map((notif) => /* @__PURE__ */ React40.createElement(
|
|
8712
|
+
NotifDropdownItem,
|
|
8713
|
+
{
|
|
8714
|
+
key: notif.id,
|
|
8715
|
+
notif,
|
|
8716
|
+
registry,
|
|
8717
|
+
onClick: handleItemClick
|
|
8718
|
+
}
|
|
8719
|
+
)))));
|
|
8399
8720
|
}
|
|
8400
8721
|
|
|
8401
8722
|
// src/react/notifications/NotificationList.tsx
|
|
8402
|
-
import React41, { useCallback as
|
|
8723
|
+
import React41, { useCallback as useCallback28, useMemo as useMemo27 } from "react";
|
|
8403
8724
|
function formatTimestamp(iso) {
|
|
8404
8725
|
const date = new Date(iso);
|
|
8405
8726
|
return new Intl.DateTimeFormat("en-US", {
|
|
@@ -8412,6 +8733,27 @@ function formatTimestamp(iso) {
|
|
|
8412
8733
|
function NotificationListSkeleton() {
|
|
8413
8734
|
return /* @__PURE__ */ React41.createElement("div", { className: "brokr-notif-list-items" }, [1, 2, 3].map((i) => /* @__PURE__ */ React41.createElement("div", { key: i, className: "brokr-notif-list-row brokr-notif-list-row--skeleton" }, /* @__PURE__ */ React41.createElement("span", { className: "brokr-notif-item-dot brokr-notif-item-dot--skeleton" }), /* @__PURE__ */ React41.createElement("div", { className: "brokr-notif-item-body" }, /* @__PURE__ */ React41.createElement("span", { className: "brokr-notif-item-title brokr-skeleton-line", style: { width: "60%" } }), /* @__PURE__ */ React41.createElement("span", { className: "brokr-notif-item-message brokr-skeleton-line", style: { width: "80%" } })), /* @__PURE__ */ React41.createElement("span", { className: "brokr-notif-item-time brokr-skeleton-line", style: { width: 48 } }))));
|
|
8414
8735
|
}
|
|
8736
|
+
function NotifListItem({
|
|
8737
|
+
notif,
|
|
8738
|
+
registry,
|
|
8739
|
+
onClick
|
|
8740
|
+
}) {
|
|
8741
|
+
const handleClick = useCallback28(() => onClick(notif), [notif, onClick]);
|
|
8742
|
+
const notifData = notif.data ?? {};
|
|
8743
|
+
const notifType = notif.type ?? notifData.type ?? "default";
|
|
8744
|
+
const resolved = resolveNotificationType(registry, notifType, notifData);
|
|
8745
|
+
return /* @__PURE__ */ React41.createElement(
|
|
8746
|
+
"button",
|
|
8747
|
+
{
|
|
8748
|
+
type: "button",
|
|
8749
|
+
className: `brokr-notif-list-row${notif.read ? "" : " brokr-notif-list-row--unread"}`,
|
|
8750
|
+
onClick: handleClick
|
|
8751
|
+
},
|
|
8752
|
+
resolved.image ? /* @__PURE__ */ React41.createElement("img", { src: resolved.image.url, alt: resolved.image.alt, className: "brokr-notif-item-logo" }) : /* @__PURE__ */ React41.createElement("span", { className: `brokr-notif-item-dot brokr-notif-item-dot--${notif.variant}` }),
|
|
8753
|
+
/* @__PURE__ */ React41.createElement("div", { className: "brokr-notif-item-body" }, /* @__PURE__ */ React41.createElement("span", { className: "brokr-notif-item-title" }, notif.title), /* @__PURE__ */ React41.createElement("span", { className: "brokr-notif-item-message" }, notif.message)),
|
|
8754
|
+
/* @__PURE__ */ React41.createElement("span", { className: "brokr-notif-item-time" }, formatTimestamp(notif.createdAt))
|
|
8755
|
+
);
|
|
8756
|
+
}
|
|
8415
8757
|
function NotificationList() {
|
|
8416
8758
|
const { notifications, unreadCount, markRead, markAllRead, isLoading, registry } = useNotifications();
|
|
8417
8759
|
const sorted = useMemo27(
|
|
@@ -8420,7 +8762,7 @@ function NotificationList() {
|
|
|
8420
8762
|
),
|
|
8421
8763
|
[notifications]
|
|
8422
8764
|
);
|
|
8423
|
-
const handleClick =
|
|
8765
|
+
const handleClick = useCallback28((notif) => {
|
|
8424
8766
|
if (!notif.read) markRead(notif.id);
|
|
8425
8767
|
const notifData = notif.data ?? {};
|
|
8426
8768
|
const notifType = notif.type ?? notifData.type ?? "default";
|
|
@@ -8438,30 +8780,15 @@ function NotificationList() {
|
|
|
8438
8780
|
onClick: markAllRead
|
|
8439
8781
|
},
|
|
8440
8782
|
"Mark all read"
|
|
8441
|
-
)), isLoading ? /* @__PURE__ */ React41.createElement(NotificationListSkeleton, null) : sorted.length === 0 ? /* @__PURE__ */ React41.createElement("div", { className: "brokr-notif-empty" }, /* @__PURE__ */ React41.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__ */ React41.createElement("path", { d: "M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9" }), /* @__PURE__ */ React41.createElement("path", { d: "M10.3 21a1.94 1.94 0 0 0 3.4 0" })), /* @__PURE__ */ React41.createElement("span", { className: "brokr-notif-empty-text" }, "No notifications yet")) : /* @__PURE__ */ React41.createElement("div", { className: "brokr-notif-list-items" }, sorted.map((notif) =>
|
|
8442
|
-
|
|
8443
|
-
|
|
8444
|
-
|
|
8445
|
-
|
|
8446
|
-
|
|
8447
|
-
|
|
8448
|
-
|
|
8449
|
-
|
|
8450
|
-
className: `brokr-notif-list-row${notif.read ? "" : " brokr-notif-list-row--unread"}`,
|
|
8451
|
-
onClick: () => handleClick(notif)
|
|
8452
|
-
},
|
|
8453
|
-
resolved.image ? /* @__PURE__ */ React41.createElement(
|
|
8454
|
-
"img",
|
|
8455
|
-
{
|
|
8456
|
-
src: resolved.image.url,
|
|
8457
|
-
alt: resolved.image.alt,
|
|
8458
|
-
className: "brokr-notif-item-logo"
|
|
8459
|
-
}
|
|
8460
|
-
) : /* @__PURE__ */ React41.createElement("span", { className: `brokr-notif-item-dot brokr-notif-item-dot--${notif.variant}` }),
|
|
8461
|
-
/* @__PURE__ */ React41.createElement("div", { className: "brokr-notif-item-body" }, /* @__PURE__ */ React41.createElement("span", { className: "brokr-notif-item-title" }, notif.title), /* @__PURE__ */ React41.createElement("span", { className: "brokr-notif-item-message" }, notif.message)),
|
|
8462
|
-
/* @__PURE__ */ React41.createElement("span", { className: "brokr-notif-item-time" }, formatTimestamp(notif.createdAt))
|
|
8463
|
-
);
|
|
8464
|
-
})));
|
|
8783
|
+
)), isLoading ? /* @__PURE__ */ React41.createElement(NotificationListSkeleton, null) : sorted.length === 0 ? /* @__PURE__ */ React41.createElement("div", { className: "brokr-notif-empty" }, /* @__PURE__ */ React41.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__ */ React41.createElement("path", { d: "M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9" }), /* @__PURE__ */ React41.createElement("path", { d: "M10.3 21a1.94 1.94 0 0 0 3.4 0" })), /* @__PURE__ */ React41.createElement("span", { className: "brokr-notif-empty-text" }, "No notifications yet")) : /* @__PURE__ */ React41.createElement("div", { className: "brokr-notif-list-items" }, sorted.map((notif) => /* @__PURE__ */ React41.createElement(
|
|
8784
|
+
NotifListItem,
|
|
8785
|
+
{
|
|
8786
|
+
key: notif.id,
|
|
8787
|
+
notif,
|
|
8788
|
+
registry,
|
|
8789
|
+
onClick: handleClick
|
|
8790
|
+
}
|
|
8791
|
+
))));
|
|
8465
8792
|
}
|
|
8466
8793
|
|
|
8467
8794
|
// src/react/hooks/use-user.ts
|