@coffer-org/plugin-webchat 7.1.0 → 7.2.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/{ChatWidget-BhGpxS9f.js → ChatWidget-BIPLpTMp.js} +419 -200
- package/dist/web.js +1 -1
- package/locales/en.json +4 -3
- package/locales/ru.json +5 -3
- package/locales/uk.json +5 -3
- package/package.json +3 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as __fedReact from "react";
|
|
2
|
-
import { Dialog, DialogContent, Icon, MarkdownView,
|
|
3
|
-
import { createContext, createElement, forwardRef, memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
+
import { Button, Dialog, DialogContent, Icon, MarkdownView, fileSizeLabel, uploadFile, useMinWidth } from "@coffer-org/web-ui";
|
|
3
|
+
import { createContext, createElement, forwardRef, memo, useCallback, useContext, useEffect, useId, useMemo, useRef, useState } from "react";
|
|
4
4
|
import { useLocation } from "react-router-dom";
|
|
5
5
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
6
|
//#region \0rolldown/runtime.js
|
|
@@ -496,14 +496,9 @@ var ChevronDown = createLucideIcon("chevron-down", [["path", {
|
|
|
496
496
|
* This source code is licensed under the ISC license.
|
|
497
497
|
* See the LICENSE file in the root directory of this source tree.
|
|
498
498
|
*/
|
|
499
|
-
var
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
r: "10",
|
|
503
|
-
key: "1mglay"
|
|
504
|
-
}], ["path", {
|
|
505
|
-
d: "M12 6v6l4 2",
|
|
506
|
-
key: "mmk7yg"
|
|
499
|
+
var ChevronRight = createLucideIcon("chevron-right", [["path", {
|
|
500
|
+
d: "m9 18 6-6-6-6",
|
|
501
|
+
key: "mthhwq"
|
|
507
502
|
}]]);
|
|
508
503
|
/**
|
|
509
504
|
* @license lucide-react v1.24.0 - ISC
|
|
@@ -511,6 +506,162 @@ var Clock = createLucideIcon("clock", [["circle", {
|
|
|
511
506
|
* This source code is licensed under the ISC license.
|
|
512
507
|
* See the LICENSE file in the root directory of this source tree.
|
|
513
508
|
*/
|
|
509
|
+
var FileArchive = createLucideIcon("file-archive", [
|
|
510
|
+
["path", {
|
|
511
|
+
d: "M13.659 22H18a2 2 0 0 0 2-2V8a2.4 2.4 0 0 0-.706-1.706l-3.588-3.588A2.4 2.4 0 0 0 14 2H6a2 2 0 0 0-2 2v11.5",
|
|
512
|
+
key: "4pqfef"
|
|
513
|
+
}],
|
|
514
|
+
["path", {
|
|
515
|
+
d: "M14 2v5a1 1 0 0 0 1 1h5",
|
|
516
|
+
key: "wfsgrz"
|
|
517
|
+
}],
|
|
518
|
+
["path", {
|
|
519
|
+
d: "M8 12v-1",
|
|
520
|
+
key: "1ej8lb"
|
|
521
|
+
}],
|
|
522
|
+
["path", {
|
|
523
|
+
d: "M8 18v-2",
|
|
524
|
+
key: "qcmpov"
|
|
525
|
+
}],
|
|
526
|
+
["path", {
|
|
527
|
+
d: "M8 7V6",
|
|
528
|
+
key: "1nbb54"
|
|
529
|
+
}],
|
|
530
|
+
["circle", {
|
|
531
|
+
cx: "8",
|
|
532
|
+
cy: "20",
|
|
533
|
+
r: "2",
|
|
534
|
+
key: "ckkr5m"
|
|
535
|
+
}]
|
|
536
|
+
]);
|
|
537
|
+
/**
|
|
538
|
+
* @license lucide-react v1.24.0 - ISC
|
|
539
|
+
*
|
|
540
|
+
* This source code is licensed under the ISC license.
|
|
541
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
542
|
+
*/
|
|
543
|
+
var FileCode = createLucideIcon("file-code", [
|
|
544
|
+
["path", {
|
|
545
|
+
d: "M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z",
|
|
546
|
+
key: "1oefj6"
|
|
547
|
+
}],
|
|
548
|
+
["path", {
|
|
549
|
+
d: "M14 2v5a1 1 0 0 0 1 1h5",
|
|
550
|
+
key: "wfsgrz"
|
|
551
|
+
}],
|
|
552
|
+
["path", {
|
|
553
|
+
d: "M10 12.5 8 15l2 2.5",
|
|
554
|
+
key: "1tg20x"
|
|
555
|
+
}],
|
|
556
|
+
["path", {
|
|
557
|
+
d: "m14 12.5 2 2.5-2 2.5",
|
|
558
|
+
key: "yinavb"
|
|
559
|
+
}]
|
|
560
|
+
]);
|
|
561
|
+
/**
|
|
562
|
+
* @license lucide-react v1.24.0 - ISC
|
|
563
|
+
*
|
|
564
|
+
* This source code is licensed under the ISC license.
|
|
565
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
566
|
+
*/
|
|
567
|
+
var FileHeadphone = createLucideIcon("file-headphone", [
|
|
568
|
+
["path", {
|
|
569
|
+
d: "M4 6.835V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.706.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2h-.343",
|
|
570
|
+
key: "1vfytu"
|
|
571
|
+
}],
|
|
572
|
+
["path", {
|
|
573
|
+
d: "M14 2v5a1 1 0 0 0 1 1h5",
|
|
574
|
+
key: "wfsgrz"
|
|
575
|
+
}],
|
|
576
|
+
["path", {
|
|
577
|
+
d: "M2 19a2 2 0 0 1 4 0v1a2 2 0 0 1-4 0v-4a6 6 0 0 1 12 0v4a2 2 0 0 1-4 0v-1a2 2 0 0 1 4 0",
|
|
578
|
+
key: "1etmh7"
|
|
579
|
+
}]
|
|
580
|
+
]);
|
|
581
|
+
/**
|
|
582
|
+
* @license lucide-react v1.24.0 - ISC
|
|
583
|
+
*
|
|
584
|
+
* This source code is licensed under the ISC license.
|
|
585
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
586
|
+
*/
|
|
587
|
+
var FileImage = createLucideIcon("file-image", [
|
|
588
|
+
["path", {
|
|
589
|
+
d: "M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z",
|
|
590
|
+
key: "1oefj6"
|
|
591
|
+
}],
|
|
592
|
+
["path", {
|
|
593
|
+
d: "M14 2v5a1 1 0 0 0 1 1h5",
|
|
594
|
+
key: "wfsgrz"
|
|
595
|
+
}],
|
|
596
|
+
["circle", {
|
|
597
|
+
cx: "10",
|
|
598
|
+
cy: "12",
|
|
599
|
+
r: "2",
|
|
600
|
+
key: "737tya"
|
|
601
|
+
}],
|
|
602
|
+
["path", {
|
|
603
|
+
d: "m20 17-1.296-1.296a2.41 2.41 0 0 0-3.408 0L9 22",
|
|
604
|
+
key: "wt3hpn"
|
|
605
|
+
}]
|
|
606
|
+
]);
|
|
607
|
+
/**
|
|
608
|
+
* @license lucide-react v1.24.0 - ISC
|
|
609
|
+
*
|
|
610
|
+
* This source code is licensed under the ISC license.
|
|
611
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
612
|
+
*/
|
|
613
|
+
var FilePlay = createLucideIcon("file-play", [
|
|
614
|
+
["path", {
|
|
615
|
+
d: "M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z",
|
|
616
|
+
key: "1oefj6"
|
|
617
|
+
}],
|
|
618
|
+
["path", {
|
|
619
|
+
d: "M14 2v5a1 1 0 0 0 1 1h5",
|
|
620
|
+
key: "wfsgrz"
|
|
621
|
+
}],
|
|
622
|
+
["path", {
|
|
623
|
+
d: "M15.033 13.44a.647.647 0 0 1 0 1.12l-4.065 2.352a.645.645 0 0 1-.968-.56v-4.704a.645.645 0 0 1 .967-.56z",
|
|
624
|
+
key: "1tzo1f"
|
|
625
|
+
}]
|
|
626
|
+
]);
|
|
627
|
+
/**
|
|
628
|
+
* @license lucide-react v1.24.0 - ISC
|
|
629
|
+
*
|
|
630
|
+
* This source code is licensed under the ISC license.
|
|
631
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
632
|
+
*/
|
|
633
|
+
var FileSpreadsheet = createLucideIcon("file-spreadsheet", [
|
|
634
|
+
["path", {
|
|
635
|
+
d: "M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z",
|
|
636
|
+
key: "1oefj6"
|
|
637
|
+
}],
|
|
638
|
+
["path", {
|
|
639
|
+
d: "M14 2v5a1 1 0 0 0 1 1h5",
|
|
640
|
+
key: "wfsgrz"
|
|
641
|
+
}],
|
|
642
|
+
["path", {
|
|
643
|
+
d: "M8 13h2",
|
|
644
|
+
key: "yr2amv"
|
|
645
|
+
}],
|
|
646
|
+
["path", {
|
|
647
|
+
d: "M14 13h2",
|
|
648
|
+
key: "un5t4a"
|
|
649
|
+
}],
|
|
650
|
+
["path", {
|
|
651
|
+
d: "M8 17h2",
|
|
652
|
+
key: "2yhykz"
|
|
653
|
+
}],
|
|
654
|
+
["path", {
|
|
655
|
+
d: "M14 17h2",
|
|
656
|
+
key: "10kma7"
|
|
657
|
+
}]
|
|
658
|
+
]);
|
|
659
|
+
/**
|
|
660
|
+
* @license lucide-react v1.24.0 - ISC
|
|
661
|
+
*
|
|
662
|
+
* This source code is licensed under the ISC license.
|
|
663
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
664
|
+
*/
|
|
514
665
|
var FileText = createLucideIcon("file-text", [
|
|
515
666
|
["path", {
|
|
516
667
|
d: "M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z",
|
|
@@ -615,6 +766,19 @@ var MessageCircle = createLucideIcon("message-circle", [["path", {
|
|
|
615
766
|
* This source code is licensed under the ISC license.
|
|
616
767
|
* See the LICENSE file in the root directory of this source tree.
|
|
617
768
|
*/
|
|
769
|
+
var MessagesSquare = createLucideIcon("messages-square", [["path", {
|
|
770
|
+
d: "M16 10a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 14.286V4a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z",
|
|
771
|
+
key: "1n2ejm"
|
|
772
|
+
}], ["path", {
|
|
773
|
+
d: "M20 9a2 2 0 0 1 2 2v10.286a.71.71 0 0 1-1.212.502l-2.202-2.202A2 2 0 0 0 17.172 19H10a2 2 0 0 1-2-2v-1",
|
|
774
|
+
key: "1qfcsi"
|
|
775
|
+
}]]);
|
|
776
|
+
/**
|
|
777
|
+
* @license lucide-react v1.24.0 - ISC
|
|
778
|
+
*
|
|
779
|
+
* This source code is licensed under the ISC license.
|
|
780
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
781
|
+
*/
|
|
618
782
|
var Minimize2 = createLucideIcon("minimize-2", [
|
|
619
783
|
["path", {
|
|
620
784
|
d: "m14 10 7-7",
|
|
@@ -702,16 +866,6 @@ var Square = createLucideIcon("square", [["rect", {
|
|
|
702
866
|
* This source code is licensed under the ISC license.
|
|
703
867
|
* See the LICENSE file in the root directory of this source tree.
|
|
704
868
|
*/
|
|
705
|
-
var Star = createLucideIcon("star", [["path", {
|
|
706
|
-
d: "M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z",
|
|
707
|
-
key: "r04s7s"
|
|
708
|
-
}]]);
|
|
709
|
-
/**
|
|
710
|
-
* @license lucide-react v1.24.0 - ISC
|
|
711
|
-
*
|
|
712
|
-
* This source code is licensed under the ISC license.
|
|
713
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
714
|
-
*/
|
|
715
869
|
var Upload = createLucideIcon("upload", [
|
|
716
870
|
["path", {
|
|
717
871
|
d: "M12 3v12",
|
|
@@ -1376,6 +1530,149 @@ function ChatMarkdown({ text, className }) {
|
|
|
1376
1530
|
});
|
|
1377
1531
|
}
|
|
1378
1532
|
//#endregion
|
|
1533
|
+
//#region src/ui/ChatAttachments.tsx
|
|
1534
|
+
/**
|
|
1535
|
+
* The ONE presentation of a chat attachment — the files staged in the composer and the files a
|
|
1536
|
+
* sent turn carries are drawn by this component, so the two cannot drift apart.
|
|
1537
|
+
*
|
|
1538
|
+
* Three decisions are worth stating, because each replaces something that was there before:
|
|
1539
|
+
*
|
|
1540
|
+
* - **A file is a ROW, not a word.** The bubble used to print `a.label ?? a.name` as bare text
|
|
1541
|
+
* in a wrapping flex, which for eight uploads meant eight lines of 40-character storage names
|
|
1542
|
+
* running to the bubble's edge and clipping mid-name. A row states the three facts a file has
|
|
1543
|
+
* — what kind it is, what it is called, how big it is — and states them in the same places
|
|
1544
|
+
* every time, which is what makes a stack of them scannable.
|
|
1545
|
+
* - **The name breaks in the MIDDLE, not at the end.** The extension is the most informative
|
|
1546
|
+
* part of a long filename and the part a tail-truncation always eats. The stem takes the
|
|
1547
|
+
* truncation (`truncate`), the extension is pinned beside it (`shrink-0`), so `…_2026.pdf`
|
|
1548
|
+
* keeps saying `.pdf` at any width. CSS does this, not a character budget — the same row
|
|
1549
|
+
* works in a 380px panel and in the 1040px modal without measuring anything.
|
|
1550
|
+
* - **Past two files the group COLLAPSES.** A turn carrying eight PDFs is a turn whose text a
|
|
1551
|
+
* reader still has to reach; the attachments are its luggage, not its content. Two rows say
|
|
1552
|
+
* what was sent, `+N` says how much more there is, and one click shows the rest.
|
|
1553
|
+
*
|
|
1554
|
+
* The rows are inert on purpose: an attachment names itself and does nothing else. Opening one
|
|
1555
|
+
* would be a second file-viewing surface beside the record page's, and the chat is not where a
|
|
1556
|
+
* file is read (decided 2026-09-15).
|
|
1557
|
+
*/
|
|
1558
|
+
/** How many rows stand before the rest fold away. Two is what a bubble can carry without the
|
|
1559
|
+
* luggage outweighing the message — see the component's note. */
|
|
1560
|
+
var COLLAPSE_AFTER = 2;
|
|
1561
|
+
/** Extension families, in the order a name is tested against them. */
|
|
1562
|
+
var BY_EXT = [
|
|
1563
|
+
[/\.(png|jpe?g|gif|webp|avif|svg|bmp|heic|heif)$/i, FileImage],
|
|
1564
|
+
[/\.(mp3|wav|ogg|oga|flac|m4a|aac|opus)$/i, FileHeadphone],
|
|
1565
|
+
[/\.(mp4|mov|mkv|webm|avi|m4v)$/i, FilePlay],
|
|
1566
|
+
[/\.(zip|rar|7z|tar|gz|tgz|bz2|xz)$/i, FileArchive],
|
|
1567
|
+
[/\.(csv|tsv|xlsx?|ods|numbers)$/i, FileSpreadsheet],
|
|
1568
|
+
[/\.(json|xml|ya?ml|toml|ini|js|jsx|ts|tsx|css|html?|sh|py|sql)$/i, FileCode]
|
|
1569
|
+
];
|
|
1570
|
+
/**
|
|
1571
|
+
* What KIND of file this is, at a glance. The mime type is the server's answer and is trusted
|
|
1572
|
+
* first; an upload that arrives with none (or with the `application/octet-stream` a browser
|
|
1573
|
+
* falls back to) is read from its extension instead, which is the only fact left.
|
|
1574
|
+
*/
|
|
1575
|
+
function attachmentIcon(a) {
|
|
1576
|
+
const mime = a.mime?.toLowerCase().split(";", 1)[0] ?? "";
|
|
1577
|
+
if (mime.startsWith("image/")) return FileImage;
|
|
1578
|
+
if (mime.startsWith("audio/")) return FileHeadphone;
|
|
1579
|
+
if (mime.startsWith("video/")) return FilePlay;
|
|
1580
|
+
if (mime === "text/csv" || mime.includes("spreadsheet") || mime.includes("excel")) return FileSpreadsheet;
|
|
1581
|
+
if (mime === "application/json" || mime === "application/xml" || mime.startsWith("text/x")) return FileCode;
|
|
1582
|
+
if (mime === "application/zip" || mime.includes("compressed") || mime.includes("tar")) return FileArchive;
|
|
1583
|
+
const name = a.label ?? a.name;
|
|
1584
|
+
for (const [re, icon] of BY_EXT) if (re.test(name)) return icon;
|
|
1585
|
+
return FileText;
|
|
1586
|
+
}
|
|
1587
|
+
/**
|
|
1588
|
+
* Split a filename into the part that may be truncated and the part that may not. A leading dot
|
|
1589
|
+
* (`.gitignore`) is not an extension, and neither is a "dot" eleven characters from the end of a
|
|
1590
|
+
* sentence-like name — an extension is short, so anything longer is left in the stem where the
|
|
1591
|
+
* truncation can reach it.
|
|
1592
|
+
*/
|
|
1593
|
+
function splitFileName(name) {
|
|
1594
|
+
const dot = name.lastIndexOf(".");
|
|
1595
|
+
if (dot <= 0 || name.length - dot > 11) return {
|
|
1596
|
+
stem: name,
|
|
1597
|
+
ext: ""
|
|
1598
|
+
};
|
|
1599
|
+
return {
|
|
1600
|
+
stem: name.slice(0, dot),
|
|
1601
|
+
ext: name.slice(dot)
|
|
1602
|
+
};
|
|
1603
|
+
}
|
|
1604
|
+
function AttachmentRow({ a, onRemove }) {
|
|
1605
|
+
const { t } = useTranslation();
|
|
1606
|
+
const name = a.label ?? a.name;
|
|
1607
|
+
const { stem, ext } = splitFileName(name);
|
|
1608
|
+
const size = fileSizeLabel(a.size);
|
|
1609
|
+
return /* @__PURE__ */ jsxs("li", {
|
|
1610
|
+
className: "flex min-w-0 items-center gap-2 rounded-[calc(var(--radius-md)+1px)] border border-border bg-background py-1 pr-1.5 pl-2",
|
|
1611
|
+
children: [
|
|
1612
|
+
/* @__PURE__ */ jsx(attachmentIcon(a), {
|
|
1613
|
+
size: 14,
|
|
1614
|
+
"aria-hidden": true,
|
|
1615
|
+
className: "shrink-0 text-muted-foreground"
|
|
1616
|
+
}),
|
|
1617
|
+
/* @__PURE__ */ jsxs("span", {
|
|
1618
|
+
className: "flex min-w-0 flex-1 text-xs",
|
|
1619
|
+
title: name,
|
|
1620
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
1621
|
+
className: "truncate",
|
|
1622
|
+
children: stem
|
|
1623
|
+
}), ext && /* @__PURE__ */ jsx("span", {
|
|
1624
|
+
className: "shrink-0 text-muted-foreground",
|
|
1625
|
+
children: ext
|
|
1626
|
+
})]
|
|
1627
|
+
}),
|
|
1628
|
+
size && /* @__PURE__ */ jsx("span", {
|
|
1629
|
+
className: "shrink-0 text-[0.6875rem] tabular-nums text-muted-foreground",
|
|
1630
|
+
children: size
|
|
1631
|
+
}),
|
|
1632
|
+
onRemove && /* @__PURE__ */ jsx("button", {
|
|
1633
|
+
type: "button",
|
|
1634
|
+
"aria-label": t("webchat.removeAttachment", { name }),
|
|
1635
|
+
title: t("webchat.removeAttachment", { name }),
|
|
1636
|
+
className: "shrink-0 rounded p-0.5 text-muted-foreground hover:text-destructive focus-visible:outline-2 focus-visible:outline-ring",
|
|
1637
|
+
onClick: onRemove,
|
|
1638
|
+
children: /* @__PURE__ */ jsx(X, { size: 13 })
|
|
1639
|
+
})
|
|
1640
|
+
]
|
|
1641
|
+
});
|
|
1642
|
+
}
|
|
1643
|
+
function ChatAttachments({ items, onRemove, className = "" }) {
|
|
1644
|
+
const { t } = useTranslation();
|
|
1645
|
+
const [expanded, setExpanded] = useState(false);
|
|
1646
|
+
const listId = useId();
|
|
1647
|
+
if (!items.length) return null;
|
|
1648
|
+
const hidden = expanded ? 0 : Math.max(0, items.length - COLLAPSE_AFTER);
|
|
1649
|
+
const shown = hidden ? items.slice(0, COLLAPSE_AFTER) : items;
|
|
1650
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1651
|
+
className: `flex flex-col gap-1 ${className}`,
|
|
1652
|
+
"data-chat-attachments": true,
|
|
1653
|
+
children: [/* @__PURE__ */ jsx("ul", {
|
|
1654
|
+
id: listId,
|
|
1655
|
+
"aria-label": t("webchat.attachments"),
|
|
1656
|
+
className: "flex flex-col gap-1",
|
|
1657
|
+
children: shown.map((a, i) => /* @__PURE__ */ jsx(AttachmentRow, {
|
|
1658
|
+
a,
|
|
1659
|
+
...onRemove ? { onRemove: () => onRemove(i) } : {}
|
|
1660
|
+
}, `${a.name}-${i}`))
|
|
1661
|
+
}), items.length > COLLAPSE_AFTER && /* @__PURE__ */ jsxs("button", {
|
|
1662
|
+
type: "button",
|
|
1663
|
+
"aria-expanded": expanded,
|
|
1664
|
+
"aria-controls": listId,
|
|
1665
|
+
className: "inline-flex items-center gap-1 self-start rounded px-1 py-0.5 text-xs text-muted-foreground hover:text-foreground focus-visible:outline-2 focus-visible:outline-ring",
|
|
1666
|
+
onClick: () => setExpanded((v) => !v),
|
|
1667
|
+
children: [/* @__PURE__ */ jsx(ChevronDown, {
|
|
1668
|
+
size: 12,
|
|
1669
|
+
"aria-hidden": true,
|
|
1670
|
+
className: expanded ? "rotate-180 transition-transform" : "transition-transform"
|
|
1671
|
+
}), expanded ? t("webchat.fewerFiles") : t("webchat.moreFiles", { count: hidden })]
|
|
1672
|
+
})]
|
|
1673
|
+
});
|
|
1674
|
+
}
|
|
1675
|
+
//#endregion
|
|
1379
1676
|
//#region src/ui/ChatMessages.tsx
|
|
1380
1677
|
function ChatMessages({ messages, pending, pendingReasoning, trailing }) {
|
|
1381
1678
|
const { t } = useTranslation();
|
|
@@ -1389,8 +1686,15 @@ function ChatMessages({ messages, pending, pendingReasoning, trailing }) {
|
|
|
1389
1686
|
"data-chat-list": true,
|
|
1390
1687
|
className: "flex min-h-0 flex-1 flex-col gap-3 overflow-y-auto p-2",
|
|
1391
1688
|
children: [!messages.length && pending === null && pendingReasoning === null ? /* @__PURE__ */ jsx("div", {
|
|
1392
|
-
className: "flex-1
|
|
1393
|
-
children:
|
|
1689
|
+
className: "grid flex-1 place-items-center px-6 text-center text-sm text-muted-foreground",
|
|
1690
|
+
children: /* @__PURE__ */ jsxs("span", {
|
|
1691
|
+
className: "flex flex-col items-center gap-2",
|
|
1692
|
+
children: [/* @__PURE__ */ jsx(MessagesSquare, {
|
|
1693
|
+
size: 28,
|
|
1694
|
+
"aria-hidden": true,
|
|
1695
|
+
className: "text-faint"
|
|
1696
|
+
}), t("webchat.empty")]
|
|
1697
|
+
})
|
|
1394
1698
|
}) : /* @__PURE__ */ jsxs(Fragment, { children: [messages.map((m) => /* @__PURE__ */ jsx(Bubble, {
|
|
1395
1699
|
role: m.role,
|
|
1396
1700
|
text: m.text,
|
|
@@ -1412,23 +1716,23 @@ function ChatMessages({ messages, pending, pendingReasoning, trailing }) {
|
|
|
1412
1716
|
var Bubble = memo(function Bubble({ role, text, reasoning, attachments }) {
|
|
1413
1717
|
const { t } = useTranslation();
|
|
1414
1718
|
if (role === "user") return /* @__PURE__ */ jsxs("div", {
|
|
1415
|
-
className: "
|
|
1416
|
-
children: [attachments?.length ? /* @__PURE__ */ jsx(
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
className: "inline-flex max-w-full items-center rounded-md bg-background px-1.5 py-0.5 text-xs",
|
|
1420
|
-
title: a.name,
|
|
1421
|
-
children: a.label ?? a.name
|
|
1422
|
-
}, a.name))
|
|
1719
|
+
className: "max-w-[85%] min-w-0 self-end rounded-2xl rounded-br-sm border border-border bg-accent px-3 py-2 text-[0.8125rem] break-words whitespace-pre-wrap",
|
|
1720
|
+
children: [attachments?.length ? /* @__PURE__ */ jsx(ChatAttachments, {
|
|
1721
|
+
items: attachments,
|
|
1722
|
+
className: `-mx-1 ${text ? "mb-2" : ""}`
|
|
1423
1723
|
}) : null, text]
|
|
1424
1724
|
});
|
|
1425
1725
|
return /* @__PURE__ */ jsxs("div", {
|
|
1426
|
-
className: "
|
|
1726
|
+
className: "max-w-[95%] min-w-0 self-start rounded-2xl rounded-bl-sm bg-muted px-3 py-2 text-sm break-words",
|
|
1427
1727
|
children: [reasoning ? /* @__PURE__ */ jsxs("details", {
|
|
1428
|
-
className: "mb-2 rounded-
|
|
1429
|
-
children: [/* @__PURE__ */
|
|
1430
|
-
className: "cursor-pointer select-none text-xs text-muted-foreground",
|
|
1431
|
-
children:
|
|
1728
|
+
className: "group/reason -mx-1 mb-2 rounded-[calc(var(--radius-md)+1px)] border border-border bg-background px-2 py-1",
|
|
1729
|
+
children: [/* @__PURE__ */ jsxs("summary", {
|
|
1730
|
+
className: "flex cursor-pointer list-none items-center gap-1 select-none text-xs text-muted-foreground hover:text-foreground focus-visible:outline-2 focus-visible:outline-ring [&::-webkit-details-marker]:hidden",
|
|
1731
|
+
children: [/* @__PURE__ */ jsx(ChevronRight, {
|
|
1732
|
+
size: 12,
|
|
1733
|
+
"aria-hidden": true,
|
|
1734
|
+
className: "shrink-0 transition-transform group-open/reason:rotate-90"
|
|
1735
|
+
}), t("webchat.reasoning")]
|
|
1432
1736
|
}), /* @__PURE__ */ jsx(ChatMarkdown, {
|
|
1433
1737
|
text: reasoning,
|
|
1434
1738
|
className: "md-static md-chat md-chat-sub mt-1 text-muted-foreground"
|
|
@@ -1441,81 +1745,23 @@ var Bubble = memo(function Bubble({ role, text, reasoning, attachments }) {
|
|
|
1441
1745
|
});
|
|
1442
1746
|
//#endregion
|
|
1443
1747
|
//#region src/ui/AgentPicker.tsx
|
|
1444
|
-
var MAX_STARS = 5;
|
|
1445
|
-
/** Rating pair: "3 star, 3 clock". Two digits read at a glance and stay the same
|
|
1446
|
-
* width on every row, which a free-text hint ("fastest and cheapest" next to
|
|
1447
|
-
* "quick") does not. Both are read the same way — the star is what you get, the
|
|
1448
|
-
* clock is what it costs you in waiting — so a heavier model raises both numbers.
|
|
1449
|
-
* Falls back to the hint when the agent publishes no ratings. */
|
|
1450
|
-
function PresetRating({ quality, time, hint }) {
|
|
1451
|
-
const { t } = useTranslation();
|
|
1452
|
-
if (quality === void 0 || time === void 0) return hint ? /* @__PURE__ */ jsx("span", {
|
|
1453
|
-
className: "shrink-0 text-muted-foreground",
|
|
1454
|
-
children: hint
|
|
1455
|
-
}) : null;
|
|
1456
|
-
const both = quality !== time;
|
|
1457
|
-
return /* @__PURE__ */ jsxs("span", {
|
|
1458
|
-
className: "flex shrink-0 items-center gap-1.5 text-muted-foreground tabular-nums",
|
|
1459
|
-
children: [/* @__PURE__ */ jsxs("span", {
|
|
1460
|
-
className: "flex items-center gap-0.5",
|
|
1461
|
-
title: t("webchat.quality", { value: quality }),
|
|
1462
|
-
children: [
|
|
1463
|
-
quality,
|
|
1464
|
-
/* @__PURE__ */ jsx(Star, {
|
|
1465
|
-
size: 11,
|
|
1466
|
-
"aria-hidden": true,
|
|
1467
|
-
className: "shrink-0"
|
|
1468
|
-
}),
|
|
1469
|
-
/* @__PURE__ */ jsx("span", {
|
|
1470
|
-
className: "sr-only",
|
|
1471
|
-
children: t("webchat.quality", { value: quality })
|
|
1472
|
-
})
|
|
1473
|
-
]
|
|
1474
|
-
}), both ? /* @__PURE__ */ jsxs("span", {
|
|
1475
|
-
className: "flex items-center gap-0.5",
|
|
1476
|
-
title: t("webchat.time", { value: time }),
|
|
1477
|
-
children: [
|
|
1478
|
-
time,
|
|
1479
|
-
/* @__PURE__ */ jsx(Clock, {
|
|
1480
|
-
size: 11,
|
|
1481
|
-
"aria-hidden": true,
|
|
1482
|
-
className: "shrink-0"
|
|
1483
|
-
}),
|
|
1484
|
-
/* @__PURE__ */ jsx("span", {
|
|
1485
|
-
className: "sr-only",
|
|
1486
|
-
children: t("webchat.time", { value: time })
|
|
1487
|
-
})
|
|
1488
|
-
]
|
|
1489
|
-
}) : /* @__PURE__ */ jsx("span", {
|
|
1490
|
-
className: "sr-only",
|
|
1491
|
-
children: t("webchat.time", { value: time })
|
|
1492
|
-
})]
|
|
1493
|
-
});
|
|
1494
|
-
}
|
|
1495
1748
|
/**
|
|
1496
|
-
*
|
|
1497
|
-
*
|
|
1498
|
-
*
|
|
1499
|
-
*
|
|
1500
|
-
*
|
|
1749
|
+
* Flatten the catalog to one list: with a single registered agent the agent level is
|
|
1750
|
+
* noise, and with several the agent name simply prefixes its presets.
|
|
1751
|
+
*
|
|
1752
|
+
* ONE mode, a list of names. Until 2026-09-16 there was a second — a row of stars in the
|
|
1753
|
+
* composer, where a preset's `quality` rating addressed it by rung and the model name was
|
|
1754
|
+
* never printed. It is gone, with the ratings it read. A star count can only address a
|
|
1755
|
+
* preset when the presets form a single totally ordered line, and `claude-agent`'s do
|
|
1756
|
+
* not: nothing published ranks `sonnet-high` against `opus-low`, and the cheaper of the
|
|
1757
|
+
* two is not the weaker one. The list that used to be the FALLBACK — for several agents,
|
|
1758
|
+
* a gap in the ladder, a preset with no rating — was always the honest control, and is
|
|
1759
|
+
* now the only one. See
|
|
1760
|
+
* `docs/superpowers/specs/2026-09-16-agent-preset-is-a-model-choice-design.md`.
|
|
1501
1761
|
*/
|
|
1502
|
-
function starLadder(rows, multi) {
|
|
1503
|
-
if (multi || rows.length < 2 || rows.length > MAX_STARS) return null;
|
|
1504
|
-
const byLevel = Array.from({ length: rows.length });
|
|
1505
|
-
for (const r of rows) {
|
|
1506
|
-
const q = r.quality;
|
|
1507
|
-
if (q === void 0 || !Number.isInteger(q) || q < 1 || q > rows.length) return null;
|
|
1508
|
-
if (byLevel[q - 1]) return null;
|
|
1509
|
-
byLevel[q - 1] = r;
|
|
1510
|
-
}
|
|
1511
|
-
return byLevel.every((r) => r !== void 0) ? byLevel : null;
|
|
1512
|
-
}
|
|
1513
|
-
/** Flatten the catalog to one list: with a single registered agent the agent
|
|
1514
|
-
* level is noise, and with several the agent name simply prefixes its presets. */
|
|
1515
1762
|
function AgentPicker({ agents, defaultAgentId, selection, onChange }) {
|
|
1516
1763
|
const { t } = useTranslation();
|
|
1517
1764
|
const [open, setOpen] = useState(false);
|
|
1518
|
-
const [preview, setPreview] = useState(null);
|
|
1519
1765
|
const rootRef = useRef(null);
|
|
1520
1766
|
const triggerRef = useRef(null);
|
|
1521
1767
|
useEffect(() => {
|
|
@@ -1542,48 +1788,10 @@ function AgentPicker({ agents, defaultAgentId, selection, onChange }) {
|
|
|
1542
1788
|
presetId: p.id,
|
|
1543
1789
|
label: multi ? `${a.title} · ${p.title}` : p.title,
|
|
1544
1790
|
hint: p.hint,
|
|
1545
|
-
quality: p.quality,
|
|
1546
|
-
time: p.time,
|
|
1547
1791
|
isDefault: p.default === true
|
|
1548
1792
|
})));
|
|
1549
1793
|
if (rows.length === 0) return null;
|
|
1550
1794
|
const current = rows.find((r) => r.agentId === selection.agentId && r.presetId === selection.presetId) ?? rows.find((r) => r.agentId === defaultAgentId && r.isDefault) ?? rows.find((r) => r.isDefault) ?? rows[0];
|
|
1551
|
-
const ladder = starLadder(rows, multi);
|
|
1552
|
-
if (ladder) {
|
|
1553
|
-
const level = ladder.indexOf(current) + 1;
|
|
1554
|
-
const shown = preview ?? level;
|
|
1555
|
-
const levelName = (row, n) => `${row.label} · ${t("webchat.level", {
|
|
1556
|
-
value: n,
|
|
1557
|
-
max: ladder.length
|
|
1558
|
-
})}`;
|
|
1559
|
-
return /* @__PURE__ */ jsx("ul", {
|
|
1560
|
-
"aria-label": t("webchat.agentPicker"),
|
|
1561
|
-
className: "flex shrink-0 items-center",
|
|
1562
|
-
onPointerLeave: () => setPreview(null),
|
|
1563
|
-
children: ladder.map((r, i) => {
|
|
1564
|
-
const n = i + 1;
|
|
1565
|
-
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("button", {
|
|
1566
|
-
type: "button",
|
|
1567
|
-
...r === current ? { "aria-current": true } : {},
|
|
1568
|
-
"aria-label": levelName(r, n),
|
|
1569
|
-
className: "flex rounded p-1 focus-visible:outline-2 focus-visible:outline-ring",
|
|
1570
|
-
onPointerEnter: () => setPreview(n),
|
|
1571
|
-
onFocus: () => setPreview(n),
|
|
1572
|
-
onBlur: () => setPreview(null),
|
|
1573
|
-
onClick: () => onChange({
|
|
1574
|
-
agentId: r.agentId,
|
|
1575
|
-
presetId: r.presetId
|
|
1576
|
-
}),
|
|
1577
|
-
children: /* @__PURE__ */ jsx(Star, {
|
|
1578
|
-
size: 16,
|
|
1579
|
-
"aria-hidden": true,
|
|
1580
|
-
className: n <= shown ? "fill-current" : "text-border",
|
|
1581
|
-
style: n <= shown ? { color: toneVar("yellow") } : void 0
|
|
1582
|
-
})
|
|
1583
|
-
}) }, `${r.agentId}/${r.presetId}`);
|
|
1584
|
-
})
|
|
1585
|
-
});
|
|
1586
|
-
}
|
|
1587
1795
|
return /* @__PURE__ */ jsxs("div", {
|
|
1588
1796
|
className: "relative",
|
|
1589
1797
|
ref: rootRef,
|
|
@@ -1596,7 +1804,7 @@ function AgentPicker({ agents, defaultAgentId, selection, onChange }) {
|
|
|
1596
1804
|
onClick: () => setOpen((v) => !v),
|
|
1597
1805
|
children: [/* @__PURE__ */ jsx("span", {
|
|
1598
1806
|
className: "max-w-40 truncate",
|
|
1599
|
-
children: current.label
|
|
1807
|
+
children: current.hint ? `${current.label} · ${current.hint}` : current.label
|
|
1600
1808
|
}), /* @__PURE__ */ jsx(ChevronDown, {
|
|
1601
1809
|
size: 12,
|
|
1602
1810
|
className: "shrink-0"
|
|
@@ -1626,11 +1834,10 @@ function AgentPicker({ agents, defaultAgentId, selection, onChange }) {
|
|
|
1626
1834
|
className: "min-w-0 flex-1 truncate",
|
|
1627
1835
|
children: r.label
|
|
1628
1836
|
}),
|
|
1629
|
-
/* @__PURE__ */ jsx(
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
})
|
|
1837
|
+
r.hint ? /* @__PURE__ */ jsx("span", {
|
|
1838
|
+
className: "shrink-0 text-muted-foreground",
|
|
1839
|
+
children: r.hint
|
|
1840
|
+
}) : null
|
|
1634
1841
|
]
|
|
1635
1842
|
}) }, `${r.agentId}/${r.presetId}`);
|
|
1636
1843
|
})
|
|
@@ -1649,29 +1856,12 @@ function ChatComposer({ streaming, onSend, onStop, attachments, uploading, uploa
|
|
|
1649
1856
|
return /* @__PURE__ */ jsxs("div", {
|
|
1650
1857
|
className: "flex shrink-0 flex-col gap-2 border-t border-border p-2",
|
|
1651
1858
|
children: [(attachments.length > 0 || uploading || uploadError) && /* @__PURE__ */ jsxs("div", {
|
|
1652
|
-
className: "flex flex-
|
|
1653
|
-
"data-chat-attachments": true,
|
|
1859
|
+
className: "flex flex-col gap-1.5 px-1",
|
|
1654
1860
|
children: [
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
size: 13,
|
|
1660
|
-
className: "shrink-0 text-muted-foreground"
|
|
1661
|
-
}),
|
|
1662
|
-
/* @__PURE__ */ jsx("span", {
|
|
1663
|
-
className: "max-w-48 truncate",
|
|
1664
|
-
children: a.label ?? a.name
|
|
1665
|
-
}),
|
|
1666
|
-
/* @__PURE__ */ jsx("button", {
|
|
1667
|
-
type: "button",
|
|
1668
|
-
"aria-label": t("webchat.removeAttachment", { name: a.label ?? a.name }),
|
|
1669
|
-
className: "shrink-0 rounded p-0.5 text-muted-foreground hover:text-destructive",
|
|
1670
|
-
onClick: () => onRemoveAttachment(i),
|
|
1671
|
-
children: /* @__PURE__ */ jsx(X, { size: 13 })
|
|
1672
|
-
})
|
|
1673
|
-
]
|
|
1674
|
-
}, `${a.name}-${i}`)),
|
|
1861
|
+
/* @__PURE__ */ jsx(ChatAttachments, {
|
|
1862
|
+
items: attachments,
|
|
1863
|
+
onRemove: onRemoveAttachment
|
|
1864
|
+
}),
|
|
1675
1865
|
uploading && /* @__PURE__ */ jsxs("div", {
|
|
1676
1866
|
className: "flex min-w-32 flex-1 items-center gap-2",
|
|
1677
1867
|
"aria-label": t("webchat.uploading"),
|
|
@@ -1705,7 +1895,7 @@ function ChatComposer({ streaming, onSend, onStop, attachments, uploading, uploa
|
|
|
1705
1895
|
children: t(uploadError)
|
|
1706
1896
|
}), canRetryUpload && /* @__PURE__ */ jsxs("button", {
|
|
1707
1897
|
type: "button",
|
|
1708
|
-
className: "inline-flex shrink-0 items-center gap-1 rounded px-1.5 py-0.5 font-medium text-primary hover:bg-muted",
|
|
1898
|
+
className: "inline-flex shrink-0 items-center gap-1 rounded px-1.5 py-0.5 font-medium text-primary hover:bg-muted focus-visible:outline-2 focus-visible:outline-ring",
|
|
1709
1899
|
onClick: onRetryUpload,
|
|
1710
1900
|
children: [
|
|
1711
1901
|
/* @__PURE__ */ jsx(RotateCcw, { size: 12 }),
|
|
@@ -1718,7 +1908,7 @@ function ChatComposer({ streaming, onSend, onStop, attachments, uploading, uploa
|
|
|
1718
1908
|
}), /* @__PURE__ */ jsxs("div", {
|
|
1719
1909
|
className: "flex flex-col gap-1",
|
|
1720
1910
|
children: [/* @__PURE__ */ jsx("textarea", {
|
|
1721
|
-
className: "w-full resize-none rounded-lg border border-border bg-background px-3 py-2 text-sm
|
|
1911
|
+
className: "max-h-32 w-full resize-none rounded-lg border border-border bg-background px-3 py-2 text-sm transition-colors outline-none placeholder:text-faint focus:border-ring focus:ring-2 focus:ring-ring",
|
|
1722
1912
|
rows: 1,
|
|
1723
1913
|
value: text,
|
|
1724
1914
|
placeholder: t("webchat.placeholder"),
|
|
@@ -1733,12 +1923,16 @@ function ChatComposer({ streaming, onSend, onStop, attachments, uploading, uploa
|
|
|
1733
1923
|
className: "flex items-center gap-1",
|
|
1734
1924
|
children: [
|
|
1735
1925
|
attachmentsAllowed && /* @__PURE__ */ jsxs("label", {
|
|
1736
|
-
|
|
1737
|
-
className: `shrink-0 cursor-pointer
|
|
1738
|
-
children: [/* @__PURE__ */ jsx(Paperclip, {
|
|
1926
|
+
title: t("webchat.attach"),
|
|
1927
|
+
className: `flex h-8 w-8 shrink-0 cursor-pointer items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-within:ring-2 focus-within:ring-ring ${uploading ? "pointer-events-none opacity-60" : ""}`,
|
|
1928
|
+
children: [/* @__PURE__ */ jsx(Paperclip, {
|
|
1929
|
+
size: 18,
|
|
1930
|
+
"aria-hidden": true
|
|
1931
|
+
}), /* @__PURE__ */ jsx("input", {
|
|
1739
1932
|
type: "file",
|
|
1740
1933
|
multiple: true,
|
|
1741
|
-
|
|
1934
|
+
"aria-label": t("webchat.attach"),
|
|
1935
|
+
className: "sr-only",
|
|
1742
1936
|
disabled: uploading,
|
|
1743
1937
|
onChange: (e) => {
|
|
1744
1938
|
if (e.target.files?.length) onPickFiles(e.target.files);
|
|
@@ -1753,14 +1947,23 @@ function ChatComposer({ streaming, onSend, onStop, attachments, uploading, uploa
|
|
|
1753
1947
|
onChange: onChooseAgent
|
|
1754
1948
|
}),
|
|
1755
1949
|
/* @__PURE__ */ jsx("div", { className: "flex-1" }),
|
|
1756
|
-
streaming ? /* @__PURE__ */ jsx(
|
|
1950
|
+
streaming ? /* @__PURE__ */ jsx(Button, {
|
|
1951
|
+
type: "button",
|
|
1952
|
+
variant: "ghost",
|
|
1953
|
+
size: "iconSm",
|
|
1757
1954
|
"aria-label": t("webchat.stop"),
|
|
1758
|
-
|
|
1955
|
+
title: t("webchat.stop"),
|
|
1956
|
+
className: "shrink-0",
|
|
1759
1957
|
onClick: onStop,
|
|
1760
1958
|
children: /* @__PURE__ */ jsx(Square, { size: 18 })
|
|
1761
|
-
}) : /* @__PURE__ */ jsx(
|
|
1959
|
+
}) : /* @__PURE__ */ jsx(Button, {
|
|
1960
|
+
type: "button",
|
|
1961
|
+
variant: "ghost",
|
|
1962
|
+
size: "iconSm",
|
|
1762
1963
|
"aria-label": t("webchat.send"),
|
|
1763
|
-
|
|
1964
|
+
title: t("webchat.send"),
|
|
1965
|
+
disabled: !text.trim(),
|
|
1966
|
+
className: "shrink-0 hover:text-primary",
|
|
1764
1967
|
onClick: submit,
|
|
1765
1968
|
children: /* @__PURE__ */ jsx(Send, { size: 18 })
|
|
1766
1969
|
})
|
|
@@ -1775,12 +1978,15 @@ function ChatThreadList({ threads, activeId, onOpen, onNew, onTogglePrivate, cla
|
|
|
1775
1978
|
const { t } = useTranslation();
|
|
1776
1979
|
return /* @__PURE__ */ jsxs("aside", {
|
|
1777
1980
|
"aria-label": t("webchat.threads"),
|
|
1778
|
-
className: `${className} border-r border-border
|
|
1981
|
+
className: `${className} flex flex-col border-r border-border`,
|
|
1779
1982
|
children: [/* @__PURE__ */ jsxs("button", {
|
|
1780
|
-
className: "flex items-center gap-2 px-3 py-2 text-sm hover:bg-muted",
|
|
1983
|
+
className: "flex items-center gap-2 border-b border-border px-3 py-2 text-sm font-medium text-primary hover:bg-muted focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-ring",
|
|
1781
1984
|
onClick: onNew,
|
|
1782
1985
|
children: [
|
|
1783
|
-
/* @__PURE__ */ jsx(Plus, {
|
|
1986
|
+
/* @__PURE__ */ jsx(Plus, {
|
|
1987
|
+
size: 16,
|
|
1988
|
+
"aria-hidden": true
|
|
1989
|
+
}),
|
|
1784
1990
|
" ",
|
|
1785
1991
|
t("webchat.newConversation")
|
|
1786
1992
|
]
|
|
@@ -1789,10 +1995,10 @@ function ChatThreadList({ threads, activeId, onOpen, onNew, onTogglePrivate, cla
|
|
|
1789
1995
|
children: threads.map((th) => {
|
|
1790
1996
|
const active = th.convId === activeId;
|
|
1791
1997
|
return /* @__PURE__ */ jsxs("div", {
|
|
1792
|
-
className: `flex items-center gap-1
|
|
1998
|
+
className: `flex items-center gap-1 border-l-2 pr-2 pl-1 ${active ? "border-ring bg-accent" : "border-transparent hover:bg-muted"}`,
|
|
1793
1999
|
children: [/* @__PURE__ */ jsxs("button", {
|
|
1794
2000
|
onClick: () => onOpen(th.convId),
|
|
1795
|
-
className: `min-w-0 flex-1 py-2 pl-2 text-left text-sm ${active ? "font-medium" : ""}`,
|
|
2001
|
+
className: `min-w-0 flex-1 rounded-sm py-2 pl-2 text-left text-sm focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-ring ${active ? "font-medium" : ""}`,
|
|
1796
2002
|
children: [/* @__PURE__ */ jsx("span", {
|
|
1797
2003
|
className: "block truncate",
|
|
1798
2004
|
children: th.title || t("webchat.newConversation")
|
|
@@ -2047,36 +2253,49 @@ function ChatWidget() {
|
|
|
2047
2253
|
const hasConversation = chat.messages.length > 0 || chat.pending !== null;
|
|
2048
2254
|
const activeTitle = chat.threads.find((th) => th.convId === chat.convId)?.title;
|
|
2049
2255
|
const header = /* @__PURE__ */ jsxs("div", {
|
|
2050
|
-
className: "flex shrink-0 items-center justify-between gap-2 border-b border-border px-3 py-
|
|
2256
|
+
className: "flex shrink-0 items-center justify-between gap-2 border-b border-border px-3 py-1.5",
|
|
2051
2257
|
children: [/* @__PURE__ */ jsx("span", {
|
|
2052
2258
|
className: "min-w-0 truncate text-sm font-medium",
|
|
2053
2259
|
children: activeTitle || t("webchat.title")
|
|
2054
2260
|
}), /* @__PURE__ */ jsxs("div", {
|
|
2055
|
-
className: "flex shrink-0 items-center gap-
|
|
2261
|
+
className: "-mr-1 flex shrink-0 items-center gap-0.5",
|
|
2056
2262
|
children: [
|
|
2057
|
-
|
|
2263
|
+
hasConversation && /* @__PURE__ */ jsx(Button, {
|
|
2264
|
+
type: "button",
|
|
2265
|
+
variant: "ghost",
|
|
2266
|
+
size: "iconSm",
|
|
2267
|
+
"aria-label": t("webchat.newConversation"),
|
|
2268
|
+
title: t("webchat.newConversation"),
|
|
2269
|
+
onClick: startNew,
|
|
2270
|
+
children: /* @__PURE__ */ jsx(Plus, { size: 16 })
|
|
2271
|
+
}),
|
|
2272
|
+
!hasSidebar && /* @__PURE__ */ jsx(Button, {
|
|
2273
|
+
type: "button",
|
|
2274
|
+
variant: "ghost",
|
|
2275
|
+
size: "iconSm",
|
|
2058
2276
|
"aria-label": t("webchat.threads"),
|
|
2277
|
+
title: t("webchat.threads"),
|
|
2059
2278
|
"aria-expanded": threadsOpen,
|
|
2060
|
-
className:
|
|
2279
|
+
className: threadsOpen ? "bg-muted text-foreground" : "",
|
|
2061
2280
|
onClick: () => setThreadsOpen((v) => !v),
|
|
2062
2281
|
children: /* @__PURE__ */ jsx(List, { size: 16 })
|
|
2063
2282
|
}),
|
|
2064
|
-
|
|
2065
|
-
"aria-label": t("webchat.newConversation"),
|
|
2066
|
-
className: "rounded p-1 hover:bg-muted",
|
|
2067
|
-
onClick: startNew,
|
|
2068
|
-
children: /* @__PURE__ */ jsx(Plus, { size: 16 })
|
|
2069
|
-
}),
|
|
2070
|
-
chatModes(wide).includes("panel") && /* @__PURE__ */ jsx("button", {
|
|
2283
|
+
chatModes(wide).includes("panel") && /* @__PURE__ */ jsx(Button, {
|
|
2071
2284
|
ref: expandBtnRef,
|
|
2285
|
+
type: "button",
|
|
2286
|
+
variant: "ghost",
|
|
2287
|
+
size: "iconSm",
|
|
2072
2288
|
"aria-label": mode === "modal" ? t("webchat.collapse") : t("webchat.expand"),
|
|
2073
|
-
|
|
2289
|
+
title: mode === "modal" ? t("webchat.collapse") : t("webchat.expand"),
|
|
2074
2290
|
onClick: () => setMode(stepMode(mode, wide, mode === "modal" ? -1 : 1)),
|
|
2075
2291
|
children: mode === "modal" ? /* @__PURE__ */ jsx(Minimize2, { size: 16 }) : /* @__PURE__ */ jsx(Maximize2, { size: 16 })
|
|
2076
2292
|
}),
|
|
2077
|
-
/* @__PURE__ */ jsx(
|
|
2293
|
+
/* @__PURE__ */ jsx(Button, {
|
|
2294
|
+
type: "button",
|
|
2295
|
+
variant: "ghost",
|
|
2296
|
+
size: "iconSm",
|
|
2078
2297
|
"aria-label": t("webchat.close"),
|
|
2079
|
-
|
|
2298
|
+
title: t("webchat.close"),
|
|
2080
2299
|
onClick: () => setMode("bubble"),
|
|
2081
2300
|
children: /* @__PURE__ */ jsx(X, { size: 16 })
|
|
2082
2301
|
})
|
package/dist/web.js
CHANGED
|
@@ -11,7 +11,7 @@ import { definePluginUI } from "@coffer-org/web-ui";
|
|
|
11
11
|
var ui_default = definePluginUI({ slots: [{
|
|
12
12
|
slot: "overlay",
|
|
13
13
|
id: "webchat",
|
|
14
|
-
load: () => import("./ChatWidget-
|
|
14
|
+
load: () => import("./ChatWidget-BIPLpTMp.js")
|
|
15
15
|
}] });
|
|
16
16
|
//#endregion
|
|
17
17
|
export { ui_default as default };
|
package/locales/en.json
CHANGED
|
@@ -21,6 +21,10 @@
|
|
|
21
21
|
"stop": "Stop",
|
|
22
22
|
"attach": "Attach file",
|
|
23
23
|
"removeAttachment": "Remove {{name}}",
|
|
24
|
+
"attachments": "Attached files",
|
|
25
|
+
"moreFiles_one": "{{count}} more file",
|
|
26
|
+
"moreFiles_other": "{{count}} more files",
|
|
27
|
+
"fewerFiles": "Show fewer",
|
|
24
28
|
"uploading": "Uploading…",
|
|
25
29
|
"uploadProgress": "Upload progress",
|
|
26
30
|
"uploadTooLarge": "File is too large (maximum 25 MB).",
|
|
@@ -32,9 +36,6 @@
|
|
|
32
36
|
"error": "Error: {{message}}",
|
|
33
37
|
"reasoning": "Reasoning",
|
|
34
38
|
"agentPicker": "Agent and preset",
|
|
35
|
-
"level": "Level {{value}} of {{max}}",
|
|
36
|
-
"quality": "Quality {{value}} of 5",
|
|
37
|
-
"time": "Time {{value}} of 5",
|
|
38
39
|
"settings": {
|
|
39
40
|
"label": "Web chat",
|
|
40
41
|
"reasoning_display": "Reasoning display",
|
package/locales/ru.json
CHANGED
|
@@ -21,6 +21,11 @@
|
|
|
21
21
|
"stop": "Стоп",
|
|
22
22
|
"attach": "Прикрепить файл",
|
|
23
23
|
"removeAttachment": "Удалить {{name}}",
|
|
24
|
+
"attachments": "Прикреплённые файлы",
|
|
25
|
+
"moreFiles_one": "ещё {{count}} файл",
|
|
26
|
+
"moreFiles_few": "ещё {{count}} файла",
|
|
27
|
+
"moreFiles_many": "ещё {{count}} файлов",
|
|
28
|
+
"fewerFiles": "Свернуть",
|
|
24
29
|
"uploading": "Загрузка…",
|
|
25
30
|
"uploadProgress": "Прогресс загрузки",
|
|
26
31
|
"uploadTooLarge": "Файл слишком большой (максимум 25 МБ).",
|
|
@@ -32,9 +37,6 @@
|
|
|
32
37
|
"error": "Ошибка: {{message}}",
|
|
33
38
|
"reasoning": "Рассуждения",
|
|
34
39
|
"agentPicker": "Агент и пресет",
|
|
35
|
-
"level": "Уровень {{value}} из {{max}}",
|
|
36
|
-
"quality": "Качество {{value}} из 5",
|
|
37
|
-
"time": "Время {{value}} из 5",
|
|
38
40
|
"settings": {
|
|
39
41
|
"label": "Чат на сайте",
|
|
40
42
|
"reasoning_display": "Показ рассуждений",
|
package/locales/uk.json
CHANGED
|
@@ -21,6 +21,11 @@
|
|
|
21
21
|
"stop": "Стоп",
|
|
22
22
|
"attach": "Додати файл",
|
|
23
23
|
"removeAttachment": "Видалити {{name}}",
|
|
24
|
+
"attachments": "Прикріплені файли",
|
|
25
|
+
"moreFiles_one": "ще {{count}} файл",
|
|
26
|
+
"moreFiles_few": "ще {{count}} файли",
|
|
27
|
+
"moreFiles_many": "ще {{count}} файлів",
|
|
28
|
+
"fewerFiles": "Згорнути",
|
|
24
29
|
"uploading": "Завантаження…",
|
|
25
30
|
"uploadProgress": "Прогрес завантаження",
|
|
26
31
|
"uploadTooLarge": "Файл завеликий (максимум 25 МБ).",
|
|
@@ -32,9 +37,6 @@
|
|
|
32
37
|
"error": "Помилка: {{message}}",
|
|
33
38
|
"reasoning": "Міркування",
|
|
34
39
|
"agentPicker": "Агент і пресет",
|
|
35
|
-
"level": "Рівень {{value}} з {{max}}",
|
|
36
|
-
"quality": "Якість {{value}} з 5",
|
|
37
|
-
"time": "Час {{value}} з 5",
|
|
38
40
|
"settings": {
|
|
39
41
|
"label": "Чат на сайті",
|
|
40
42
|
"reasoning_display": "Показ міркувань",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coffer-org/plugin-webchat",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=24"
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@coffer-org/markdown": "^7.4.0",
|
|
31
|
-
"@coffer-org/sdk": "^7.4.
|
|
32
|
-
"@coffer-org/server": "^7.
|
|
31
|
+
"@coffer-org/sdk": "^7.4.1",
|
|
32
|
+
"@coffer-org/server": "^7.5.0",
|
|
33
33
|
"lucide-react": "^1.17.0"
|
|
34
34
|
},
|
|
35
35
|
"coffer": {
|