@refraction-ui/react 0.10.0 → 0.12.0
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/index.cjs +271 -171
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +271 -171
- package/dist/index.js.map +1 -1
- package/dist/internal/conversation/index.d.cts +12 -2
- package/dist/internal/conversation/index.d.ts +12 -2
- package/dist/internal/react-conversation/index.d.cts +5 -1
- package/dist/internal/react-conversation/index.d.ts +5 -1
- package/dist/internal/react-cookie-consent/index.d.cts +1 -2
- package/dist/internal/react-cookie-consent/index.d.ts +1 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2731,6 +2731,7 @@ function createConversation(config = {}) {
|
|
|
2731
2731
|
const messagesByConv = /* @__PURE__ */ new Map();
|
|
2732
2732
|
let activeConversationId = config.activeConversationId ?? null;
|
|
2733
2733
|
let openThreadRootId = null;
|
|
2734
|
+
let replyTarget = null;
|
|
2734
2735
|
let status = "idle";
|
|
2735
2736
|
let error = null;
|
|
2736
2737
|
let abortController = null;
|
|
@@ -2752,6 +2753,7 @@ function createConversation(config = {}) {
|
|
|
2752
2753
|
activeConversationId,
|
|
2753
2754
|
messages: activeConversationId ? messagesByConv.get(activeConversationId) ?? [] : [],
|
|
2754
2755
|
openThreadRootId,
|
|
2756
|
+
replyTarget,
|
|
2755
2757
|
threadingMode,
|
|
2756
2758
|
status,
|
|
2757
2759
|
error
|
|
@@ -2848,6 +2850,7 @@ function createConversation(config = {}) {
|
|
|
2848
2850
|
newConversation(opts) {
|
|
2849
2851
|
const conversation = createConversationInternal(opts ?? {});
|
|
2850
2852
|
openThreadRootId = null;
|
|
2853
|
+
replyTarget = null;
|
|
2851
2854
|
emit();
|
|
2852
2855
|
return conversation;
|
|
2853
2856
|
},
|
|
@@ -2855,6 +2858,7 @@ function createConversation(config = {}) {
|
|
|
2855
2858
|
if (!conversations.has(conversationId) || activeConversationId === conversationId) return;
|
|
2856
2859
|
activeConversationId = conversationId;
|
|
2857
2860
|
openThreadRootId = null;
|
|
2861
|
+
replyTarget = null;
|
|
2858
2862
|
emit();
|
|
2859
2863
|
},
|
|
2860
2864
|
deleteConversation(conversationId) {
|
|
@@ -2864,6 +2868,7 @@ function createConversation(config = {}) {
|
|
|
2864
2868
|
if (activeConversationId === conversationId) {
|
|
2865
2869
|
activeConversationId = orderedConversations()[0]?.id ?? null;
|
|
2866
2870
|
openThreadRootId = null;
|
|
2871
|
+
replyTarget = null;
|
|
2867
2872
|
}
|
|
2868
2873
|
emit();
|
|
2869
2874
|
},
|
|
@@ -2901,6 +2906,7 @@ function createConversation(config = {}) {
|
|
|
2901
2906
|
const next = list.filter((m) => !removeIds.has(m.id));
|
|
2902
2907
|
messagesByConv.set(activeConversationId, next);
|
|
2903
2908
|
if (openThreadRootId && removeIds.has(openThreadRootId)) openThreadRootId = null;
|
|
2909
|
+
if (replyTarget && removeIds.has(replyTarget)) replyTarget = openThreadRootId;
|
|
2904
2910
|
emit();
|
|
2905
2911
|
},
|
|
2906
2912
|
react(messageId, emoji) {
|
|
@@ -2931,6 +2937,7 @@ function createConversation(config = {}) {
|
|
|
2931
2937
|
const conversationId = ensureActiveConversation(opts);
|
|
2932
2938
|
const list = messagesByConv.get(conversationId);
|
|
2933
2939
|
const parentId = opts?.replyTo ? rootIdOf(list, opts.replyTo) : void 0;
|
|
2940
|
+
const replyToId = opts?.replyTo;
|
|
2934
2941
|
const isFirstRoot = !parentId && selectRoots(list).length === 0;
|
|
2935
2942
|
const userMsg = {
|
|
2936
2943
|
id: generateId("rfr-msg"),
|
|
@@ -2941,6 +2948,7 @@ function createConversation(config = {}) {
|
|
|
2941
2948
|
timestamp: /* @__PURE__ */ new Date(),
|
|
2942
2949
|
status: "sent",
|
|
2943
2950
|
parentId,
|
|
2951
|
+
replyToId,
|
|
2944
2952
|
attachments: opts?.attachments,
|
|
2945
2953
|
metadata: opts?.metadata
|
|
2946
2954
|
};
|
|
@@ -2997,11 +3005,20 @@ function createConversation(config = {}) {
|
|
|
2997
3005
|
},
|
|
2998
3006
|
openThread(rootId) {
|
|
2999
3007
|
openThreadRootId = rootId;
|
|
3008
|
+
replyTarget = rootId;
|
|
3009
|
+
emit();
|
|
3010
|
+
},
|
|
3011
|
+
replyTo(messageId) {
|
|
3012
|
+
if (!activeConversationId) return;
|
|
3013
|
+
const list = messagesByConv.get(activeConversationId);
|
|
3014
|
+
openThreadRootId = rootIdOf(list, messageId);
|
|
3015
|
+
replyTarget = messageId;
|
|
3000
3016
|
emit();
|
|
3001
3017
|
},
|
|
3002
3018
|
closeThread() {
|
|
3003
|
-
if (openThreadRootId === null) return;
|
|
3019
|
+
if (openThreadRootId === null && replyTarget === null) return;
|
|
3004
3020
|
openThreadRootId = null;
|
|
3021
|
+
replyTarget = null;
|
|
3005
3022
|
emit();
|
|
3006
3023
|
},
|
|
3007
3024
|
setThreadingMode(mode) {
|
|
@@ -3345,6 +3362,7 @@ function useConversation(config) {
|
|
|
3345
3362
|
retryLast: api.retryLast,
|
|
3346
3363
|
stop: api.stop,
|
|
3347
3364
|
openThread: api.openThread,
|
|
3365
|
+
replyTo: api.replyTo,
|
|
3348
3366
|
closeThread: api.closeThread,
|
|
3349
3367
|
setThreadingMode: api.setThreadingMode
|
|
3350
3368
|
};
|
|
@@ -3409,6 +3427,8 @@ function Composer({
|
|
|
3409
3427
|
toolbar = true,
|
|
3410
3428
|
emoji = true,
|
|
3411
3429
|
attachments = true,
|
|
3430
|
+
error,
|
|
3431
|
+
onRetry,
|
|
3412
3432
|
onSubmit,
|
|
3413
3433
|
onStop,
|
|
3414
3434
|
onSlashCommand,
|
|
@@ -3549,148 +3569,154 @@ ${sel}
|
|
|
3549
3569
|
submit();
|
|
3550
3570
|
}
|
|
3551
3571
|
}
|
|
3572
|
+
const iconBtn = "flex h-8 w-8 items-center justify-center rounded-lg text-muted-foreground hover:bg-accent hover:text-foreground";
|
|
3552
3573
|
const toolbarBtn = (label, title, kind) => h(
|
|
3553
3574
|
"button",
|
|
3554
3575
|
{
|
|
3555
3576
|
key: kind,
|
|
3556
3577
|
type: "button",
|
|
3557
3578
|
title,
|
|
3558
|
-
className: "
|
|
3579
|
+
className: cn(iconBtn, "text-xs font-medium"),
|
|
3559
3580
|
onMouseDown: (e) => e.preventDefault(),
|
|
3560
3581
|
// keep textarea selection
|
|
3561
3582
|
onClick: () => format(kind)
|
|
3562
3583
|
},
|
|
3563
3584
|
label
|
|
3564
3585
|
);
|
|
3565
|
-
|
|
3586
|
+
const menu = menuOpen ? h(
|
|
3566
3587
|
"div",
|
|
3567
|
-
{
|
|
3568
|
-
|
|
3569
|
-
|
|
3588
|
+
{
|
|
3589
|
+
className: "absolute bottom-full left-0 z-20 mb-2 w-72 overflow-hidden rounded-xl border border-border bg-popover shadow-lg",
|
|
3590
|
+
role: "listbox"
|
|
3591
|
+
},
|
|
3592
|
+
h(
|
|
3570
3593
|
"div",
|
|
3571
|
-
{ className: "
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
"
|
|
3585
|
-
)
|
|
3586
|
-
|
|
3594
|
+
{ className: "border-b border-border px-3 py-1.5 text-[10px] font-medium uppercase tracking-wide text-muted-foreground" },
|
|
3595
|
+
trigger?.type === "/" ? "Commands" : trigger?.type === "@" ? "Mentions" : "Emoji"
|
|
3596
|
+
),
|
|
3597
|
+
...items.map(
|
|
3598
|
+
(it, i) => h(
|
|
3599
|
+
"button",
|
|
3600
|
+
{
|
|
3601
|
+
key: it.key,
|
|
3602
|
+
type: "button",
|
|
3603
|
+
role: "option",
|
|
3604
|
+
"aria-selected": i === active,
|
|
3605
|
+
className: cn(
|
|
3606
|
+
"flex w-full items-center gap-2 px-3 py-2 text-left text-sm",
|
|
3607
|
+
i === active ? "bg-accent" : "hover:bg-accent/50"
|
|
3608
|
+
),
|
|
3609
|
+
onMouseEnter: () => setActive(i),
|
|
3610
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
3611
|
+
onClick: () => selectItem(i)
|
|
3612
|
+
},
|
|
3613
|
+
it.icon ? h("span", { className: "w-4 text-center text-muted-foreground" }, it.icon) : null,
|
|
3614
|
+
h("span", { className: "flex-1 truncate" }, it.primary),
|
|
3615
|
+
it.secondary ? h("span", { className: "truncate text-xs text-muted-foreground" }, it.secondary) : null
|
|
3587
3616
|
)
|
|
3588
|
-
)
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
toolbarBtn("B", "Bold (\u2318B)", "bold"),
|
|
3594
|
-
toolbarBtn("\u{1D456}", "Italic (\u2318I)", "italic"),
|
|
3595
|
-
toolbarBtn("</>", "Code (\u2318E)", "code"),
|
|
3596
|
-
toolbarBtn("\u{1F517}", "Link (\u2318K)", "link"),
|
|
3597
|
-
toolbarBtn("\u275D", "Quote", "quote"),
|
|
3598
|
-
toolbarBtn("\u2022", "Bulleted list", "ul"),
|
|
3599
|
-
toolbarBtn("1.", "Numbered list", "ol")
|
|
3600
|
-
) : null,
|
|
3601
|
-
// input row (relative for the popup menu)
|
|
3617
|
+
)
|
|
3618
|
+
) : null;
|
|
3619
|
+
return h(
|
|
3620
|
+
"div",
|
|
3621
|
+
{ className: "p-3" },
|
|
3602
3622
|
h(
|
|
3603
3623
|
"div",
|
|
3604
|
-
{ className: "relative
|
|
3605
|
-
|
|
3624
|
+
{ className: "relative" },
|
|
3625
|
+
menu,
|
|
3626
|
+
// unified input card
|
|
3627
|
+
h(
|
|
3606
3628
|
"div",
|
|
3607
3629
|
{
|
|
3608
|
-
className: "
|
|
3609
|
-
role: "listbox"
|
|
3630
|
+
className: "overflow-hidden rounded-2xl border border-border bg-background transition focus-within:border-primary focus-within:ring-2 focus-within:ring-primary/40"
|
|
3610
3631
|
},
|
|
3632
|
+
// error banner
|
|
3633
|
+
error ? h(
|
|
3634
|
+
"div",
|
|
3635
|
+
{ className: "flex items-center gap-2 border-b border-border bg-destructive/5 px-3 py-2 text-xs text-destructive", role: "alert" },
|
|
3636
|
+
h("span", { className: "flex-1 truncate" }, error),
|
|
3637
|
+
onRetry ? h("button", { type: "button", className: "font-medium underline", onClick: () => onRetry() }, "Retry") : null
|
|
3638
|
+
) : null,
|
|
3639
|
+
// attachment chips
|
|
3640
|
+
pending.length > 0 ? h(
|
|
3641
|
+
"div",
|
|
3642
|
+
{ className: "flex flex-wrap gap-2 px-3 pt-3" },
|
|
3643
|
+
...pending.map(
|
|
3644
|
+
(a) => h(
|
|
3645
|
+
"span",
|
|
3646
|
+
{ key: a.id, className: "inline-flex items-center gap-1 rounded-md bg-muted px-2 py-0.5 text-xs" },
|
|
3647
|
+
a.name,
|
|
3648
|
+
h(
|
|
3649
|
+
"button",
|
|
3650
|
+
{ type: "button", className: "text-muted-foreground hover:text-destructive", onClick: () => setPending((p) => p.filter((x) => x.id !== a.id)) },
|
|
3651
|
+
"\u2715"
|
|
3652
|
+
)
|
|
3653
|
+
)
|
|
3654
|
+
)
|
|
3655
|
+
) : null,
|
|
3656
|
+
// textarea (borderless)
|
|
3657
|
+
h("textarea", {
|
|
3658
|
+
ref,
|
|
3659
|
+
className: "block max-h-40 w-full resize-none bg-transparent px-3.5 py-3 text-sm placeholder:text-muted-foreground focus:outline-none",
|
|
3660
|
+
rows: 1,
|
|
3661
|
+
value,
|
|
3662
|
+
placeholder,
|
|
3663
|
+
autoFocus,
|
|
3664
|
+
"aria-label": "Message",
|
|
3665
|
+
onChange: (e) => syncFromTextarea(e.target),
|
|
3666
|
+
onClick: (e) => syncFromTextarea(e.currentTarget),
|
|
3667
|
+
onKeyUp: (e) => syncFromTextarea(e.currentTarget),
|
|
3668
|
+
onKeyDown,
|
|
3669
|
+
onBlur: () => setTimeout(() => setTrigger(null), 120)
|
|
3670
|
+
}),
|
|
3671
|
+
// bottom action bar
|
|
3611
3672
|
h(
|
|
3612
3673
|
"div",
|
|
3613
|
-
{ className: "
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3674
|
+
{ className: "flex items-center gap-0.5 px-2 pb-2" },
|
|
3675
|
+
attachments ? h(
|
|
3676
|
+
React11.Fragment,
|
|
3677
|
+
null,
|
|
3678
|
+
h("input", {
|
|
3679
|
+
ref: fileRef,
|
|
3680
|
+
type: "file",
|
|
3681
|
+
accept: "image/*",
|
|
3682
|
+
multiple: true,
|
|
3683
|
+
className: "hidden",
|
|
3684
|
+
onChange: (e) => {
|
|
3685
|
+
onFiles(e.target.files);
|
|
3686
|
+
e.target.value = "";
|
|
3687
|
+
}
|
|
3688
|
+
}),
|
|
3689
|
+
h("button", { type: "button", className: iconBtn, "aria-label": "Attach image or GIF", onClick: () => fileRef.current?.click() }, "\u{1F4CE}")
|
|
3690
|
+
) : null,
|
|
3691
|
+
attachments && toolbar ? h("span", { className: "mx-1 h-5 w-px bg-border" }) : null,
|
|
3692
|
+
toolbar ? h(
|
|
3693
|
+
React11.Fragment,
|
|
3694
|
+
null,
|
|
3695
|
+
toolbarBtn("B", "Bold (\u2318B)", "bold"),
|
|
3696
|
+
toolbarBtn("\u{1D456}", "Italic (\u2318I)", "italic"),
|
|
3697
|
+
toolbarBtn("</>", "Code (\u2318E)", "code"),
|
|
3698
|
+
toolbarBtn("\u{1F517}", "Link (\u2318K)", "link"),
|
|
3699
|
+
toolbarBtn("\u275D", "Quote", "quote"),
|
|
3700
|
+
toolbarBtn("\u2022", "Bulleted list", "ul"),
|
|
3701
|
+
toolbarBtn("1.", "Numbered list", "ol")
|
|
3702
|
+
) : null,
|
|
3703
|
+
h("div", { className: "flex-1" }),
|
|
3704
|
+
busy ? h(
|
|
3705
|
+
"button",
|
|
3706
|
+
{ type: "button", "aria-label": "Stop", className: "flex h-8 w-8 items-center justify-center rounded-lg bg-primary text-primary-foreground", onClick: () => onStop?.() },
|
|
3707
|
+
"\u25A0"
|
|
3708
|
+
) : h(
|
|
3618
3709
|
"button",
|
|
3619
3710
|
{
|
|
3620
|
-
key: it.key,
|
|
3621
3711
|
type: "button",
|
|
3622
|
-
|
|
3623
|
-
"
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
i === active ? "bg-accent" : "hover:bg-accent/50"
|
|
3627
|
-
),
|
|
3628
|
-
onMouseEnter: () => setActive(i),
|
|
3629
|
-
onMouseDown: (e) => e.preventDefault(),
|
|
3630
|
-
onClick: () => selectItem(i)
|
|
3712
|
+
"aria-label": "Send",
|
|
3713
|
+
className: "flex h-8 w-8 items-center justify-center rounded-lg bg-primary text-base font-semibold text-primary-foreground transition disabled:opacity-40",
|
|
3714
|
+
disabled: !value.trim() && pending.length === 0,
|
|
3715
|
+
onClick: submit
|
|
3631
3716
|
},
|
|
3632
|
-
|
|
3633
|
-
h("span", { className: "flex-1 truncate" }, it.primary),
|
|
3634
|
-
it.secondary ? h("span", { className: "truncate text-xs text-muted-foreground" }, it.secondary) : null
|
|
3717
|
+
"\u2191"
|
|
3635
3718
|
)
|
|
3636
3719
|
)
|
|
3637
|
-
) : null,
|
|
3638
|
-
attachments ? h(
|
|
3639
|
-
React11.Fragment,
|
|
3640
|
-
null,
|
|
3641
|
-
h("input", {
|
|
3642
|
-
ref: fileRef,
|
|
3643
|
-
type: "file",
|
|
3644
|
-
accept: "image/*",
|
|
3645
|
-
multiple: true,
|
|
3646
|
-
className: "hidden",
|
|
3647
|
-
onChange: (e) => {
|
|
3648
|
-
onFiles(e.target.files);
|
|
3649
|
-
e.target.value = "";
|
|
3650
|
-
}
|
|
3651
|
-
}),
|
|
3652
|
-
h(
|
|
3653
|
-
"button",
|
|
3654
|
-
{
|
|
3655
|
-
type: "button",
|
|
3656
|
-
className: "rounded-md border border-border px-2 py-2 text-sm hover:bg-accent",
|
|
3657
|
-
"aria-label": "Attach image or GIF",
|
|
3658
|
-
onClick: () => fileRef.current?.click()
|
|
3659
|
-
},
|
|
3660
|
-
"\u{1F4CE}"
|
|
3661
|
-
)
|
|
3662
|
-
) : null,
|
|
3663
|
-
h("textarea", {
|
|
3664
|
-
ref,
|
|
3665
|
-
className: "max-h-40 flex-1 resize-none rounded-md border border-border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary",
|
|
3666
|
-
rows: 1,
|
|
3667
|
-
value,
|
|
3668
|
-
placeholder,
|
|
3669
|
-
autoFocus,
|
|
3670
|
-
"aria-label": "Message",
|
|
3671
|
-
onChange: (e) => syncFromTextarea(e.target),
|
|
3672
|
-
onClick: (e) => syncFromTextarea(e.currentTarget),
|
|
3673
|
-
onKeyUp: (e) => syncFromTextarea(e.currentTarget),
|
|
3674
|
-
onKeyDown,
|
|
3675
|
-
onBlur: () => setTimeout(() => setTrigger(null), 120)
|
|
3676
|
-
}),
|
|
3677
|
-
busy ? h(
|
|
3678
|
-
"button",
|
|
3679
|
-
{
|
|
3680
|
-
type: "button",
|
|
3681
|
-
className: "rounded-md bg-primary px-3 py-2 text-sm font-medium text-primary-foreground",
|
|
3682
|
-
onClick: () => onStop?.()
|
|
3683
|
-
},
|
|
3684
|
-
"Stop"
|
|
3685
|
-
) : h(
|
|
3686
|
-
"button",
|
|
3687
|
-
{
|
|
3688
|
-
type: "button",
|
|
3689
|
-
className: "rounded-md bg-primary px-3 py-2 text-sm font-medium text-primary-foreground disabled:opacity-50",
|
|
3690
|
-
disabled: !value.trim() && pending.length === 0,
|
|
3691
|
-
onClick: submit
|
|
3692
|
-
},
|
|
3693
|
-
"Send"
|
|
3694
3720
|
)
|
|
3695
3721
|
)
|
|
3696
3722
|
);
|
|
@@ -3807,7 +3833,7 @@ function HoverActions({
|
|
|
3807
3833
|
onToggleEmojis,
|
|
3808
3834
|
align
|
|
3809
3835
|
}) {
|
|
3810
|
-
const {
|
|
3836
|
+
const { replyTo, deleteMessage } = conversation;
|
|
3811
3837
|
return h2(
|
|
3812
3838
|
"div",
|
|
3813
3839
|
{
|
|
@@ -3816,7 +3842,8 @@ function HoverActions({
|
|
|
3816
3842
|
align === "end" && "justify-end"
|
|
3817
3843
|
)
|
|
3818
3844
|
},
|
|
3819
|
-
|
|
3845
|
+
// Reply targets this specific message but groups under the originating root.
|
|
3846
|
+
h2("button", { type: "button", className: "hover:text-foreground", onClick: () => replyTo(message.id) }, "Reply"),
|
|
3820
3847
|
h2("button", { type: "button", className: "hover:text-foreground", onClick: onToggleEmojis }, "React"),
|
|
3821
3848
|
isOwn ? h2("button", { type: "button", className: "hover:text-foreground", onClick: onEdit }, "Edit") : null,
|
|
3822
3849
|
isOwn ? h2("button", { type: "button", className: "hover:text-destructive", onClick: () => deleteMessage(message.id) }, "Delete") : null
|
|
@@ -3878,7 +3905,7 @@ function MessageRow({
|
|
|
3878
3905
|
const inner = h2(
|
|
3879
3906
|
React11.Fragment,
|
|
3880
3907
|
null,
|
|
3881
|
-
quotedParent ? h2(QuotedParent, { parent: quotedParent, onClick: () => openThread(quotedParent.id) }) : null,
|
|
3908
|
+
quotedParent ? h2(QuotedParent, { parent: quotedParent, onClick: () => openThread(rootIdOf(state.messages, quotedParent.id)) }) : null,
|
|
3882
3909
|
editing ? h2(EditField, { message, conversation, onDone: () => setEditing(false) }) : isUser ? h2("div", { className: "inline-block rounded-2xl rounded-br-sm bg-primary/10 px-3 py-2 text-left" }, h2(MessageBody, { message })) : h2(MessageBody, { message }),
|
|
3883
3910
|
h2(Reactions, { message, onReact: (e) => react(message.id, e), align }),
|
|
3884
3911
|
showThreadAffordance && replyCount > 0 ? h2(
|
|
@@ -3978,13 +4005,14 @@ function ThreadPanel({ conversation, currentUserId, composer }) {
|
|
|
3978
4005
|
const rootId = state.openThreadRootId;
|
|
3979
4006
|
if (!rootId) return null;
|
|
3980
4007
|
const messages = selectThreadMessages(state.messages, rootId);
|
|
4008
|
+
const target = state.replyTarget && state.replyTarget !== rootId ? findMessage(state.messages, state.replyTarget) : void 0;
|
|
3981
4009
|
return h2(
|
|
3982
4010
|
"aside",
|
|
3983
4011
|
{ className: "flex w-80 flex-col border-l border-border", "aria-label": "Thread" },
|
|
3984
4012
|
h2(
|
|
3985
4013
|
"div",
|
|
3986
4014
|
{ className: "flex items-center justify-between border-b border-border px-3 py-2" },
|
|
3987
|
-
h2("span", { className: "text-sm font-semibold" },
|
|
4015
|
+
h2("span", { className: "text-sm font-semibold" }, `Thread \xB7 ${messages.length - 1} ${messages.length - 1 === 1 ? "reply" : "replies"}`),
|
|
3988
4016
|
h2("button", { type: "button", className: "text-muted-foreground hover:text-foreground", "aria-label": "Close thread", onClick: () => conversation.closeThread() }, "\u2715")
|
|
3989
4017
|
),
|
|
3990
4018
|
h2(
|
|
@@ -3992,6 +4020,12 @@ function ThreadPanel({ conversation, currentUserId, composer }) {
|
|
|
3992
4020
|
{ className: "flex-1 overflow-y-auto p-1" },
|
|
3993
4021
|
...messages.map((m) => h2(MessageRow, { key: m.id, message: m, conversation, currentUserId, showThreadAffordance: false }))
|
|
3994
4022
|
),
|
|
4023
|
+
target ? h2(
|
|
4024
|
+
"div",
|
|
4025
|
+
{ className: "flex items-center justify-between gap-2 border-t border-border bg-muted/40 px-3 py-1 text-xs text-muted-foreground" },
|
|
4026
|
+
h2("span", { className: "truncate" }, `\u21B3 Replying to ${target.author.name}`),
|
|
4027
|
+
h2("button", { type: "button", className: "hover:text-foreground", onClick: () => conversation.openThread(rootId) }, "Reply to thread instead")
|
|
4028
|
+
) : null,
|
|
3995
4029
|
composer
|
|
3996
4030
|
);
|
|
3997
4031
|
}
|
|
@@ -4030,9 +4064,13 @@ function Chat({
|
|
|
4030
4064
|
const timeline = selectMainTimeline(state.messages, state.threadingMode);
|
|
4031
4065
|
const activeConv = state.conversations.find((c) => c.id === state.activeConversationId);
|
|
4032
4066
|
const busy = state.status === "sending" || state.status === "streaming";
|
|
4067
|
+
const error = state.status === "error" ? state.error : null;
|
|
4068
|
+
const onRetry = () => void conversation.retryLast();
|
|
4033
4069
|
const mainComposer = h2(Composer, {
|
|
4034
4070
|
placeholder,
|
|
4035
4071
|
busy,
|
|
4072
|
+
error,
|
|
4073
|
+
onRetry,
|
|
4036
4074
|
slashCommands,
|
|
4037
4075
|
mentions,
|
|
4038
4076
|
onSlashCommand,
|
|
@@ -4043,11 +4081,13 @@ function Chat({
|
|
|
4043
4081
|
const threadComposer = state.openThreadRootId ? h2(Composer, {
|
|
4044
4082
|
placeholder: "Reply\u2026",
|
|
4045
4083
|
busy,
|
|
4084
|
+
error,
|
|
4085
|
+
onRetry,
|
|
4046
4086
|
slashCommands,
|
|
4047
4087
|
mentions,
|
|
4048
4088
|
onSlashCommand,
|
|
4049
4089
|
toolbar: composerToolbar,
|
|
4050
|
-
onSubmit: (content, atts) => void sendMessage(content, { replyTo: state.openThreadRootId, attachments: atts }),
|
|
4090
|
+
onSubmit: (content, atts) => void sendMessage(content, { replyTo: state.replyTarget ?? state.openThreadRootId, attachments: atts }),
|
|
4051
4091
|
onStop: () => conversation.stop()
|
|
4052
4092
|
}) : null;
|
|
4053
4093
|
const body = timeline.length === 0 ? h2("div", { className: "flex flex-1 items-center justify-center p-6 text-sm text-muted-foreground" }, emptyState ?? "No messages yet. Say hello \u{1F44B}") : h2(
|
|
@@ -4059,8 +4099,10 @@ function Chat({
|
|
|
4059
4099
|
message: m,
|
|
4060
4100
|
conversation,
|
|
4061
4101
|
currentUserId,
|
|
4062
|
-
|
|
4063
|
-
|
|
4102
|
+
// Show the "N replies" count on originating messages in BOTH modes.
|
|
4103
|
+
showThreadAffordance: true,
|
|
4104
|
+
// Inline: quote the specific message replied to (falls back to the root).
|
|
4105
|
+
quotedParent: state.threadingMode === "inline" && m.parentId ? findMessage(state.messages, m.replyToId ?? m.parentId) : void 0
|
|
4064
4106
|
})
|
|
4065
4107
|
)
|
|
4066
4108
|
);
|
|
@@ -4344,14 +4386,56 @@ function useCookieConsent(config) {
|
|
|
4344
4386
|
};
|
|
4345
4387
|
}
|
|
4346
4388
|
var h3 = React11.createElement;
|
|
4347
|
-
var
|
|
4348
|
-
var
|
|
4349
|
-
var
|
|
4389
|
+
var btnBase = "inline-flex items-center justify-center rounded-lg px-3.5 py-2 text-sm font-medium transition-colors";
|
|
4390
|
+
var btnPrimary = cn(btnBase, "bg-primary text-primary-foreground hover:opacity-90");
|
|
4391
|
+
var btnGhost = cn(btnBase, "border border-border hover:bg-accent");
|
|
4392
|
+
var btnLink = "text-sm font-medium text-muted-foreground underline-offset-4 hover:text-foreground hover:underline";
|
|
4393
|
+
function Toggle({
|
|
4394
|
+
checked,
|
|
4395
|
+
disabled,
|
|
4396
|
+
onChange,
|
|
4397
|
+
label
|
|
4398
|
+
}) {
|
|
4399
|
+
if (disabled) {
|
|
4400
|
+
return h3(
|
|
4401
|
+
"span",
|
|
4402
|
+
{ className: "rounded-full bg-muted px-2 py-0.5 text-xs font-medium text-muted-foreground" },
|
|
4403
|
+
"Always on"
|
|
4404
|
+
);
|
|
4405
|
+
}
|
|
4406
|
+
return h3(
|
|
4407
|
+
"button",
|
|
4408
|
+
{
|
|
4409
|
+
type: "button",
|
|
4410
|
+
role: "switch",
|
|
4411
|
+
"aria-checked": checked,
|
|
4412
|
+
"aria-label": label,
|
|
4413
|
+
onClick: () => onChange(!checked),
|
|
4414
|
+
className: cn(
|
|
4415
|
+
"relative inline-flex h-5 w-9 shrink-0 items-center rounded-full transition-colors",
|
|
4416
|
+
checked ? "bg-primary" : "bg-muted"
|
|
4417
|
+
)
|
|
4418
|
+
},
|
|
4419
|
+
h3("span", {
|
|
4420
|
+
className: cn(
|
|
4421
|
+
"inline-block h-4 w-4 transform rounded-full bg-background shadow transition-transform",
|
|
4422
|
+
checked ? "translate-x-[1.125rem]" : "translate-x-0.5"
|
|
4423
|
+
)
|
|
4424
|
+
})
|
|
4425
|
+
);
|
|
4426
|
+
}
|
|
4427
|
+
function CookieIcon() {
|
|
4428
|
+
return h3(
|
|
4429
|
+
"div",
|
|
4430
|
+
{ className: "flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-accent text-xl", "aria-hidden": true },
|
|
4431
|
+
"\u{1F36A}"
|
|
4432
|
+
);
|
|
4433
|
+
}
|
|
4350
4434
|
function CookieConsent({
|
|
4351
4435
|
consent,
|
|
4352
4436
|
position = "bottom",
|
|
4353
4437
|
title = "We use cookies",
|
|
4354
|
-
description = "We use cookies to run the site, remember your preferences, and measure traffic. Choose which
|
|
4438
|
+
description = "We use cookies to run the site, remember your preferences, and measure traffic. Choose which to allow.",
|
|
4355
4439
|
policyUrl,
|
|
4356
4440
|
policyLabel = "Cookie policy",
|
|
4357
4441
|
className
|
|
@@ -4359,64 +4443,80 @@ function CookieConsent({
|
|
|
4359
4443
|
const { state, acceptAll, rejectAll, savePreferences, setPreference } = consent;
|
|
4360
4444
|
const [settings, setSettings] = React11.useState(false);
|
|
4361
4445
|
if (!state.open) return null;
|
|
4362
|
-
const wrapper = cn(
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
);
|
|
4367
|
-
const panel = "mx-auto max-w-3xl rounded-xl border border-border bg-background p-4 shadow-lg";
|
|
4368
|
-
const header = h3(
|
|
4369
|
-
"div",
|
|
4370
|
-
null,
|
|
4371
|
-
h3("h2", { className: "text-base font-semibold" }, title),
|
|
4372
|
-
h3("p", { className: "mt-1 text-sm text-muted-foreground" }, description),
|
|
4373
|
-
policyUrl ? h3("a", { href: policyUrl, target: "_blank", rel: "noreferrer", className: cn(btnLink, "mt-1 inline-block") }, policyLabel) : null
|
|
4374
|
-
);
|
|
4375
|
-
const promptActions = h3(
|
|
4446
|
+
const wrapper = cn("fixed inset-x-0 z-50 p-4", position === "bottom" ? "bottom-0" : "top-0", className);
|
|
4447
|
+
const panel = "mx-auto max-w-2xl overflow-hidden rounded-2xl border border-border bg-background shadow-lg";
|
|
4448
|
+
const policy = policyUrl ? h3("a", { href: policyUrl, target: "_blank", rel: "noreferrer", className: cn(btnLink, "whitespace-nowrap") }, policyLabel) : null;
|
|
4449
|
+
const promptView = h3(
|
|
4376
4450
|
"div",
|
|
4377
|
-
{ className: "
|
|
4378
|
-
h3(
|
|
4379
|
-
h3(
|
|
4380
|
-
|
|
4451
|
+
{ className: "flex flex-col gap-4 p-5 sm:flex-row sm:items-center" },
|
|
4452
|
+
h3(CookieIcon),
|
|
4453
|
+
h3(
|
|
4454
|
+
"div",
|
|
4455
|
+
{ className: "min-w-0 flex-1" },
|
|
4456
|
+
h3("p", { className: "text-sm font-semibold" }, title),
|
|
4457
|
+
h3(
|
|
4458
|
+
"p",
|
|
4459
|
+
{ className: "mt-0.5 text-sm leading-relaxed text-muted-foreground" },
|
|
4460
|
+
description,
|
|
4461
|
+
policy ? h3(React11.Fragment, null, " ", policy) : null
|
|
4462
|
+
)
|
|
4463
|
+
),
|
|
4464
|
+
h3(
|
|
4465
|
+
"div",
|
|
4466
|
+
{ className: "flex flex-wrap items-center gap-2 sm:shrink-0" },
|
|
4467
|
+
h3("button", { type: "button", className: btnLink, onClick: () => setSettings(true) }, "Customize"),
|
|
4468
|
+
h3("button", { type: "button", className: btnGhost, onClick: () => rejectAll() }, "Reject all"),
|
|
4469
|
+
h3("button", { type: "button", className: btnPrimary, onClick: () => acceptAll() }, "Accept all")
|
|
4470
|
+
)
|
|
4381
4471
|
);
|
|
4382
4472
|
const settingsView = h3(
|
|
4383
4473
|
"div",
|
|
4384
|
-
{ className: "
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
h3(
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4474
|
+
{ className: "p-5" },
|
|
4475
|
+
h3(
|
|
4476
|
+
"div",
|
|
4477
|
+
{ className: "flex items-center gap-3" },
|
|
4478
|
+
h3(CookieIcon),
|
|
4479
|
+
h3(
|
|
4480
|
+
"div",
|
|
4481
|
+
null,
|
|
4482
|
+
h3("p", { className: "text-sm font-semibold" }, "Cookie preferences"),
|
|
4483
|
+
h3("p", { className: "text-xs text-muted-foreground" }, "Toggle the categories you want to allow.")
|
|
4484
|
+
)
|
|
4485
|
+
),
|
|
4486
|
+
h3(
|
|
4487
|
+
"div",
|
|
4488
|
+
{ className: "mt-4 space-y-2" },
|
|
4489
|
+
...state.categories.map(
|
|
4490
|
+
(cat) => h3(
|
|
4491
|
+
"div",
|
|
4492
|
+
{ key: cat.id, className: "flex items-center justify-between gap-4 rounded-xl border border-border p-3" },
|
|
4493
|
+
h3(
|
|
4494
|
+
"div",
|
|
4495
|
+
{ className: "min-w-0" },
|
|
4496
|
+
h3("p", { className: "text-sm font-medium" }, cat.label),
|
|
4497
|
+
cat.description ? h3("p", { className: "mt-0.5 text-xs text-muted-foreground" }, cat.description) : null
|
|
4498
|
+
),
|
|
4499
|
+
h3(Toggle, {
|
|
4500
|
+
checked: !!state.preferences[cat.id],
|
|
4501
|
+
disabled: cat.required,
|
|
4502
|
+
label: cat.label,
|
|
4503
|
+
onChange: (v) => setPreference(cat.id, v)
|
|
4504
|
+
})
|
|
4505
|
+
)
|
|
4406
4506
|
)
|
|
4407
4507
|
),
|
|
4408
4508
|
h3(
|
|
4409
4509
|
"div",
|
|
4410
|
-
{ className: "flex flex-wrap items-center gap-2
|
|
4411
|
-
h3("button", { type: "button", className:
|
|
4412
|
-
h3("button", { type: "button", className: btnGhost, onClick: () => acceptAll() }, "Accept all"),
|
|
4413
|
-
h3("button", { type: "button", className:
|
|
4510
|
+
{ className: "mt-4 flex flex-wrap items-center gap-2" },
|
|
4511
|
+
h3("button", { type: "button", className: btnLink, onClick: () => setSettings(false) }, "\u2190 Back"),
|
|
4512
|
+
h3("button", { type: "button", className: cn(btnGhost, "sm:ml-auto"), onClick: () => acceptAll() }, "Accept all"),
|
|
4513
|
+
h3("button", { type: "button", className: btnPrimary, onClick: () => savePreferences(state.preferences) }, "Save preferences")
|
|
4414
4514
|
)
|
|
4415
4515
|
);
|
|
4416
4516
|
return h3(
|
|
4417
4517
|
"div",
|
|
4418
4518
|
{ className: wrapper, role: "dialog", "aria-label": "Cookie consent", "aria-modal": false },
|
|
4419
|
-
h3("div", { className: panel },
|
|
4519
|
+
h3("div", { className: panel }, settings ? settingsView : promptView)
|
|
4420
4520
|
);
|
|
4421
4521
|
}
|
|
4422
4522
|
|