@decentnetwork/beagle-ui 0.2.20 → 0.2.22
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/beagle-ui.js +601 -17
- package/package.json +1 -1
package/dist/beagle-ui.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const BEAGLE_UI_VERSION = "0.2.
|
|
1
|
+
export const BEAGLE_UI_VERSION = "0.2.22";
|
|
2
2
|
// src/icons.jsx
|
|
3
3
|
var ICON_PATHS = {
|
|
4
4
|
// ---- tab bar (the four must feel like one set) ----
|
|
@@ -506,6 +506,564 @@ Object.assign(window, {
|
|
|
506
506
|
dkCopy
|
|
507
507
|
});
|
|
508
508
|
|
|
509
|
+
// src/assistant/actions.json
|
|
510
|
+
var actions_default = {
|
|
511
|
+
version: 1,
|
|
512
|
+
scope: "assistant",
|
|
513
|
+
note: "The Assistant's action list. /help is generated from it, the router dispatches from it, and iOS/Android implement it. A later natural-language layer translates a sentence into one of these actions and nothing else. See docs/assistant/SPEC.md.",
|
|
514
|
+
actions: [
|
|
515
|
+
{
|
|
516
|
+
name: "help",
|
|
517
|
+
usage: "/help",
|
|
518
|
+
summary: "List what the Assistant can do.",
|
|
519
|
+
params: { type: "object", properties: {}, required: [] },
|
|
520
|
+
confirm: "none",
|
|
521
|
+
scope: "assistant"
|
|
522
|
+
},
|
|
523
|
+
{
|
|
524
|
+
name: "friends-list",
|
|
525
|
+
usage: "/friends list [filter\u2026]",
|
|
526
|
+
summary: "Show friends as a table: name, last heard from, messages in/out. With no filter, everyone.",
|
|
527
|
+
params: {
|
|
528
|
+
type: "object",
|
|
529
|
+
properties: {
|
|
530
|
+
filters: {
|
|
531
|
+
type: "array",
|
|
532
|
+
items: { enum: ["never-messaged", "never-replied", "silent", "pending"] },
|
|
533
|
+
description: "Several filters are OR'd together."
|
|
534
|
+
},
|
|
535
|
+
silentDays: { type: "integer", minimum: 1, default: 90, description: "Argument of the silent filter: nothing from them in this many days." }
|
|
536
|
+
},
|
|
537
|
+
required: []
|
|
538
|
+
},
|
|
539
|
+
confirm: "none",
|
|
540
|
+
scope: "assistant"
|
|
541
|
+
},
|
|
542
|
+
{
|
|
543
|
+
name: "friends-remove",
|
|
544
|
+
usage: "/friends remove <filter\u2026>",
|
|
545
|
+
summary: "Same table with a tick per row and a Remove button. Nothing is removed by typing alone.",
|
|
546
|
+
params: {
|
|
547
|
+
type: "object",
|
|
548
|
+
properties: {
|
|
549
|
+
filters: {
|
|
550
|
+
type: "array",
|
|
551
|
+
items: { enum: ["never-messaged", "never-replied", "silent", "pending"] },
|
|
552
|
+
minItems: 1
|
|
553
|
+
},
|
|
554
|
+
silentDays: { type: "integer", minimum: 1, default: 90 }
|
|
555
|
+
},
|
|
556
|
+
required: ["filters"]
|
|
557
|
+
},
|
|
558
|
+
confirm: "list",
|
|
559
|
+
scope: "assistant"
|
|
560
|
+
},
|
|
561
|
+
{
|
|
562
|
+
name: "find",
|
|
563
|
+
usage: "/find <text>",
|
|
564
|
+
summary: "Friends whose name, alias, description or ens name contains the text.",
|
|
565
|
+
params: {
|
|
566
|
+
type: "object",
|
|
567
|
+
properties: { text: { type: "string", minLength: 1 } },
|
|
568
|
+
required: ["text"]
|
|
569
|
+
},
|
|
570
|
+
confirm: "none",
|
|
571
|
+
scope: "assistant"
|
|
572
|
+
},
|
|
573
|
+
{
|
|
574
|
+
name: "rename",
|
|
575
|
+
usage: "/rename <friend> <new name>",
|
|
576
|
+
summary: "Set a friend's alias \u2014 what you see them as, on this device.",
|
|
577
|
+
params: {
|
|
578
|
+
type: "object",
|
|
579
|
+
properties: {
|
|
580
|
+
friend: { type: "string", description: "Matches name, alias or userid prefix. Ambiguous = nothing happens, the candidates are listed." },
|
|
581
|
+
alias: { type: "string", minLength: 1 }
|
|
582
|
+
},
|
|
583
|
+
required: ["friend", "alias"]
|
|
584
|
+
},
|
|
585
|
+
confirm: "none",
|
|
586
|
+
scope: "assistant"
|
|
587
|
+
},
|
|
588
|
+
{
|
|
589
|
+
name: "mute",
|
|
590
|
+
usage: "/mute <friend>",
|
|
591
|
+
summary: "Stop alerts from one conversation.",
|
|
592
|
+
params: {
|
|
593
|
+
type: "object",
|
|
594
|
+
properties: { friend: { type: "string" } },
|
|
595
|
+
required: ["friend"]
|
|
596
|
+
},
|
|
597
|
+
confirm: "none",
|
|
598
|
+
scope: "assistant"
|
|
599
|
+
},
|
|
600
|
+
{
|
|
601
|
+
name: "unmute",
|
|
602
|
+
usage: "/unmute <friend>",
|
|
603
|
+
summary: "Alerts from that conversation again.",
|
|
604
|
+
params: {
|
|
605
|
+
type: "object",
|
|
606
|
+
properties: { friend: { type: "string" } },
|
|
607
|
+
required: ["friend"]
|
|
608
|
+
},
|
|
609
|
+
confirm: "none",
|
|
610
|
+
scope: "assistant"
|
|
611
|
+
},
|
|
612
|
+
{
|
|
613
|
+
name: "sound",
|
|
614
|
+
usage: "/sound on|off",
|
|
615
|
+
summary: "Sound on a new message.",
|
|
616
|
+
params: {
|
|
617
|
+
type: "object",
|
|
618
|
+
properties: { on: { type: "boolean" } },
|
|
619
|
+
required: ["on"]
|
|
620
|
+
},
|
|
621
|
+
confirm: "none",
|
|
622
|
+
scope: "assistant"
|
|
623
|
+
},
|
|
624
|
+
{
|
|
625
|
+
name: "flash",
|
|
626
|
+
usage: "/flash on|off",
|
|
627
|
+
summary: "Flash the window on a new message.",
|
|
628
|
+
params: {
|
|
629
|
+
type: "object",
|
|
630
|
+
properties: { on: { type: "boolean" } },
|
|
631
|
+
required: ["on"]
|
|
632
|
+
},
|
|
633
|
+
confirm: "none",
|
|
634
|
+
scope: "assistant"
|
|
635
|
+
},
|
|
636
|
+
{
|
|
637
|
+
name: "status",
|
|
638
|
+
usage: "/status",
|
|
639
|
+
summary: "Friends, pending requests, how far back the history on this device goes, app version.",
|
|
640
|
+
params: { type: "object", properties: {}, required: [] },
|
|
641
|
+
confirm: "none",
|
|
642
|
+
scope: "assistant"
|
|
643
|
+
}
|
|
644
|
+
],
|
|
645
|
+
filters: [
|
|
646
|
+
{
|
|
647
|
+
name: "never-messaged",
|
|
648
|
+
usage: "never-messaged",
|
|
649
|
+
summary: "No message either way.",
|
|
650
|
+
guarded: true
|
|
651
|
+
},
|
|
652
|
+
{
|
|
653
|
+
name: "never-replied",
|
|
654
|
+
usage: "never-replied",
|
|
655
|
+
summary: "You wrote at least once, they never did.",
|
|
656
|
+
guarded: true
|
|
657
|
+
},
|
|
658
|
+
{
|
|
659
|
+
name: "silent",
|
|
660
|
+
usage: "silent [days]",
|
|
661
|
+
summary: "Nothing from them for this many days (default 90).",
|
|
662
|
+
guarded: true,
|
|
663
|
+
arg: { name: "days", type: "integer", default: 90, minimum: 1 }
|
|
664
|
+
},
|
|
665
|
+
{
|
|
666
|
+
name: "pending",
|
|
667
|
+
usage: "pending",
|
|
668
|
+
summary: "A request you sent that was never accepted.",
|
|
669
|
+
guarded: false
|
|
670
|
+
}
|
|
671
|
+
],
|
|
672
|
+
dropped: [
|
|
673
|
+
{
|
|
674
|
+
name: "never-called",
|
|
675
|
+
reason: "The message store holds no call records on any platform, so the predicate cannot be evaluated. /api/assistant/friends returns lastCallTs: null and the filter is not offered."
|
|
676
|
+
}
|
|
677
|
+
],
|
|
678
|
+
guard: {
|
|
679
|
+
recentDays: 14,
|
|
680
|
+
applies: "Filters marked guarded never match a friend accepted (or, with no acceptance stamp, requested) less than recentDays ago; the result says how many were skipped."
|
|
681
|
+
}
|
|
682
|
+
};
|
|
683
|
+
|
|
684
|
+
// src/assistant/filters.js
|
|
685
|
+
var ASSISTANT_ID = "__assistant__";
|
|
686
|
+
var DAY_MS = 864e5;
|
|
687
|
+
var RECENT_DAYS = 14;
|
|
688
|
+
var DEFAULT_SILENT_DAYS = 90;
|
|
689
|
+
var FILTER_NAMES = ["never-messaged", "never-replied", "silent", "pending"];
|
|
690
|
+
var GUARDED = /* @__PURE__ */ new Set(["never-messaged", "never-replied", "silent"]);
|
|
691
|
+
function parseCommand(line) {
|
|
692
|
+
const text = String(line != null ? line : "").trim();
|
|
693
|
+
if (!text.startsWith("/"))
|
|
694
|
+
return null;
|
|
695
|
+
const parts = text.slice(1).split(/\s+/).filter(Boolean);
|
|
696
|
+
if (!parts.length)
|
|
697
|
+
return null;
|
|
698
|
+
const head = parts[0].toLowerCase();
|
|
699
|
+
if (head === "friends") {
|
|
700
|
+
const sub = (parts[1] || "").toLowerCase();
|
|
701
|
+
if (sub === "list" || sub === "remove")
|
|
702
|
+
return { ok: true, name: `friends-${sub}`, args: parts.slice(2) };
|
|
703
|
+
return { ok: false, name: "friends", args: parts.slice(1), error: "friends-subcommand" };
|
|
704
|
+
}
|
|
705
|
+
return { ok: true, name: head, args: parts.slice(1) };
|
|
706
|
+
}
|
|
707
|
+
function parseFilters(args) {
|
|
708
|
+
const filters = [];
|
|
709
|
+
const unknown = [];
|
|
710
|
+
let silentDays = DEFAULT_SILENT_DAYS;
|
|
711
|
+
const words = (args != null ? args : []).map((a) => String(a).toLowerCase());
|
|
712
|
+
for (let i = 0; i < words.length; i++) {
|
|
713
|
+
const w = words[i];
|
|
714
|
+
if (!FILTER_NAMES.includes(w)) {
|
|
715
|
+
unknown.push(words[i]);
|
|
716
|
+
continue;
|
|
717
|
+
}
|
|
718
|
+
if (w === "silent") {
|
|
719
|
+
const n = Number(words[i + 1]);
|
|
720
|
+
if (Number.isInteger(n) && n > 0) {
|
|
721
|
+
silentDays = n;
|
|
722
|
+
i++;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
if (!filters.includes(w))
|
|
726
|
+
filters.push(w);
|
|
727
|
+
}
|
|
728
|
+
return { filters, silentDays, unknown };
|
|
729
|
+
}
|
|
730
|
+
var acceptedOrRequestedAt = (f) => f.acceptedAt || f.requestedAt || 0;
|
|
731
|
+
function isRecent(friend, now, recentDays = RECENT_DAYS) {
|
|
732
|
+
const at = acceptedOrRequestedAt(friend);
|
|
733
|
+
return at > 0 && now - at < recentDays * DAY_MS;
|
|
734
|
+
}
|
|
735
|
+
function matchesFilter(friend, filter, { now, silentDays = DEFAULT_SILENT_DAYS } = {}) {
|
|
736
|
+
const inCount = friend.inboundCount || 0;
|
|
737
|
+
const outCount = friend.outboundCount || 0;
|
|
738
|
+
switch (filter) {
|
|
739
|
+
case "never-messaged":
|
|
740
|
+
return inCount === 0 && outCount === 0;
|
|
741
|
+
case "never-replied":
|
|
742
|
+
return outCount > 0 && inCount === 0;
|
|
743
|
+
case "silent":
|
|
744
|
+
return !friend.lastInboundTs || now - friend.lastInboundTs > silentDays * DAY_MS;
|
|
745
|
+
case "pending":
|
|
746
|
+
return friend.pending === true;
|
|
747
|
+
default:
|
|
748
|
+
return false;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
function selectFriends(friends, filters, { now = Date.now(), silentDays = DEFAULT_SILENT_DAYS, recentDays = RECENT_DAYS } = {}) {
|
|
752
|
+
const pool = (friends != null ? friends : []).filter((f) => f && f.userid !== ASSISTANT_ID && !f.pinned);
|
|
753
|
+
if (!filters || !filters.length)
|
|
754
|
+
return { matched: pool.slice(), skippedRecent: [] };
|
|
755
|
+
const matched = [];
|
|
756
|
+
const skippedRecent = [];
|
|
757
|
+
for (const f of pool) {
|
|
758
|
+
let hit = false;
|
|
759
|
+
let guardedOnly = false;
|
|
760
|
+
for (const filter of filters) {
|
|
761
|
+
if (!matchesFilter(f, filter, { now, silentDays }))
|
|
762
|
+
continue;
|
|
763
|
+
if (GUARDED.has(filter) && isRecent(f, now, recentDays)) {
|
|
764
|
+
guardedOnly = true;
|
|
765
|
+
continue;
|
|
766
|
+
}
|
|
767
|
+
hit = true;
|
|
768
|
+
break;
|
|
769
|
+
}
|
|
770
|
+
if (hit)
|
|
771
|
+
matched.push(f);
|
|
772
|
+
else if (guardedOnly)
|
|
773
|
+
skippedRecent.push(f);
|
|
774
|
+
}
|
|
775
|
+
return { matched, skippedRecent };
|
|
776
|
+
}
|
|
777
|
+
function findFriends(friends, text) {
|
|
778
|
+
const q = String(text != null ? text : "").trim().toLowerCase();
|
|
779
|
+
if (!q)
|
|
780
|
+
return [];
|
|
781
|
+
return (friends != null ? friends : []).filter((f) => f && f.userid !== ASSISTANT_ID).filter((f) => [f.name, f.alias, f.description, f.ens].some((v) => typeof v === "string" && v.toLowerCase().includes(q)));
|
|
782
|
+
}
|
|
783
|
+
function resolveFriend(friends, token) {
|
|
784
|
+
const q = String(token != null ? token : "").trim().toLowerCase();
|
|
785
|
+
if (!q)
|
|
786
|
+
return { match: null, candidates: [] };
|
|
787
|
+
const pool = (friends != null ? friends : []).filter((f) => f && f.userid !== ASSISTANT_ID);
|
|
788
|
+
const exact = pool.filter((f) => [f.name, f.alias].some((v) => typeof v === "string" && v.toLowerCase() === q));
|
|
789
|
+
if (exact.length === 1)
|
|
790
|
+
return { match: exact[0], candidates: exact };
|
|
791
|
+
if (exact.length > 1)
|
|
792
|
+
return { match: null, candidates: exact };
|
|
793
|
+
const partial = pool.filter((f) => [f.name, f.alias].some((v) => typeof v === "string" && v.toLowerCase().startsWith(q)) || typeof f.userid === "string" && f.userid.toLowerCase().startsWith(q));
|
|
794
|
+
if (partial.length === 1)
|
|
795
|
+
return { match: partial[0], candidates: partial };
|
|
796
|
+
return { match: null, candidates: partial };
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// src/assistant.jsx
|
|
800
|
+
var ASSISTANT_PEER = {
|
|
801
|
+
id: ASSISTANT_ID,
|
|
802
|
+
userId: ASSISTANT_ID,
|
|
803
|
+
alias: "Beagle Assistant",
|
|
804
|
+
address: "",
|
|
805
|
+
online: true,
|
|
806
|
+
app: true,
|
|
807
|
+
pinned: true,
|
|
808
|
+
agent: false,
|
|
809
|
+
pending: false,
|
|
810
|
+
via: null,
|
|
811
|
+
ping: null,
|
|
812
|
+
ip: "",
|
|
813
|
+
unread: 0,
|
|
814
|
+
lastMsg: "",
|
|
815
|
+
lastTime: "",
|
|
816
|
+
lastTs: 0,
|
|
817
|
+
lastDir: null,
|
|
818
|
+
acceptedAt: 0,
|
|
819
|
+
requestedAt: 0,
|
|
820
|
+
wire: "163",
|
|
821
|
+
avatarUrl: null,
|
|
822
|
+
punkId: null
|
|
823
|
+
};
|
|
824
|
+
var fmtDate = (ts, lang) => !ts ? lang === "zh" ? "\u4ECE\u672A" : "never" : new Date(ts).toLocaleDateString(lang === "zh" ? "zh-CN" : "en-GB", { year: "numeric", month: "short", day: "numeric" });
|
|
825
|
+
var fmtAgo = (ts, now, lang) => {
|
|
826
|
+
if (!ts)
|
|
827
|
+
return lang === "zh" ? "\u4ECE\u672A" : "never";
|
|
828
|
+
const days = Math.floor((now - ts) / 864e5);
|
|
829
|
+
if (days <= 0)
|
|
830
|
+
return lang === "zh" ? "\u4ECA\u5929" : "today";
|
|
831
|
+
if (days === 1)
|
|
832
|
+
return lang === "zh" ? "1 \u5929\u524D" : "1 day ago";
|
|
833
|
+
return lang === "zh" ? `${days} \u5929\u524D` : `${days} days ago`;
|
|
834
|
+
};
|
|
835
|
+
var nameOf = (f) => f.alias || f.name || f.userid;
|
|
836
|
+
function sinceLine(historySince, lang) {
|
|
837
|
+
if (!historySince)
|
|
838
|
+
return lang === "zh" ? "*\u57FA\u4E8E\u672C\u8BBE\u5907\u4E0A\u7684\u6D88\u606F\uFF08\u672C\u673A\u6682\u65E0\u5386\u53F2\u8BB0\u5F55\uFF09*" : "*based on messages on this device (no history here yet)*";
|
|
839
|
+
return lang === "zh" ? `*\u57FA\u4E8E\u672C\u8BBE\u5907\u81EA ${fmtDate(historySince, lang)} \u8D77\u7684\u6D88\u606F*` : `*based on messages on this device since ${fmtDate(historySince, lang)}*`;
|
|
840
|
+
}
|
|
841
|
+
function friendTable(rows, now, lang) {
|
|
842
|
+
return rows.map((f) => lang === "zh" ? `- **${nameOf(f)}** \u2014 \u6700\u540E\u6536\u5230 ${fmtAgo(f.lastInboundTs, now, lang)} \xB7 \u6536 ${f.inboundCount || 0} / \u53D1 ${f.outboundCount || 0}` : `- **${nameOf(f)}** \u2014 last heard ${fmtAgo(f.lastInboundTs, now, lang)} \xB7 in ${f.inboundCount || 0} / out ${f.outboundCount || 0}`).join("\n");
|
|
843
|
+
}
|
|
844
|
+
function helpText(lang) {
|
|
845
|
+
const intro = lang === "zh" ? "**Beagle \u52A9\u624B** \u2014 \u53EA\u5728\u672C\u673A\u8FD0\u884C\uFF0C\u4E0D\u4F1A\u628A\u4EFB\u4F55\u5185\u5BB9\u53D1\u5230\u7F51\u7EDC\u4E0A\u3002\n\n\u53EF\u7528\u547D\u4EE4\uFF1A" : "**Beagle Assistant** \u2014 runs on this machine only; nothing here is sent over the network.\n\nCommands:";
|
|
846
|
+
const cmds = actions_default.actions.map((a) => `- \`${a.usage}\` \u2014 ${a.summary}`).join("\n");
|
|
847
|
+
const filters = actions_default.filters.map((f) => `- \`${f.usage}\` \u2014 ${f.summary}`).join("\n");
|
|
848
|
+
const tail = lang === "zh" ? `\u7B5B\u9009\u6761\u4EF6\uFF08\u53EF\u7EC4\u5408\uFF0C\u53D6\u5E76\u96C6\uFF09\uFF1A
|
|
849
|
+
${filters}
|
|
850
|
+
|
|
851
|
+
\u6700\u8FD1 ${RECENT_DAYS} \u5929\u5185\u6DFB\u52A0\u7684\u597D\u53CB\u4E0D\u4F1A\u88AB"\u4ECE\u672A/\u6C89\u9ED8"\u7C7B\u7B5B\u9009\u9009\u4E2D\u3002\u5220\u9664\u6C38\u8FDC\u9700\u8981\u5148\u786E\u8BA4\u3002` : `Filters (combine them; a friend matching any one is listed):
|
|
852
|
+
${filters}
|
|
853
|
+
|
|
854
|
+
Friends added in the last ${RECENT_DAYS} days are never matched by the "never/silent" filters. Removal always asks first.`;
|
|
855
|
+
return `${intro}
|
|
856
|
+
${cmds}
|
|
857
|
+
|
|
858
|
+
${tail}`;
|
|
859
|
+
}
|
|
860
|
+
var seq = 0;
|
|
861
|
+
var asMsg = (from, text, extra) => Object.assign({
|
|
862
|
+
id: `as-${Date.now()}-${seq++}`,
|
|
863
|
+
from,
|
|
864
|
+
time: (/* @__PURE__ */ new Date()).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }),
|
|
865
|
+
text
|
|
866
|
+
}, extra);
|
|
867
|
+
function AssistantPanel({ m, T, lang, onRemove }) {
|
|
868
|
+
const rows = m.assistant.rows || [];
|
|
869
|
+
const [sel, setSel] = React.useState(() => new Set(rows.map((r) => r.userid)));
|
|
870
|
+
const [busy, setBusy] = React.useState(false);
|
|
871
|
+
const [done, setDone] = React.useState(null);
|
|
872
|
+
if (!rows.length)
|
|
873
|
+
return null;
|
|
874
|
+
const toggle = (id) => setSel((s) => {
|
|
875
|
+
const n = new Set(s);
|
|
876
|
+
n.has(id) ? n.delete(id) : n.add(id);
|
|
877
|
+
return n;
|
|
878
|
+
});
|
|
879
|
+
const label = lang === "zh" ? `\u5220\u9664 ${sel.size} \u4F4D` : `Remove ${sel.size}`;
|
|
880
|
+
return /* @__PURE__ */ React.createElement("div", { style: { marginTop: 8, border: "1px solid var(--line)", borderRadius: 10, overflow: "hidden" } }, rows.map((r) => /* @__PURE__ */ React.createElement("label", { key: r.userid, style: {
|
|
881
|
+
display: "flex",
|
|
882
|
+
alignItems: "center",
|
|
883
|
+
gap: 8,
|
|
884
|
+
padding: "7px 10px",
|
|
885
|
+
borderBottom: "1px solid var(--line)",
|
|
886
|
+
fontSize: 13,
|
|
887
|
+
cursor: done ? "default" : "pointer"
|
|
888
|
+
} }, /* @__PURE__ */ React.createElement(
|
|
889
|
+
"input",
|
|
890
|
+
{
|
|
891
|
+
type: "checkbox",
|
|
892
|
+
checked: sel.has(r.userid),
|
|
893
|
+
disabled: !!done || busy,
|
|
894
|
+
onChange: () => toggle(r.userid)
|
|
895
|
+
}
|
|
896
|
+
), /* @__PURE__ */ React.createElement("span", { style: { flex: 1 } }, nameOf(r)), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11, color: "var(--faint)" } }, r.userid.slice(0, 8), "\u2026"))), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 10, padding: "8px 10px" } }, done ? /* @__PURE__ */ React.createElement("span", { style: { fontSize: 12.5, color: "var(--faint)" } }, done) : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Btn, { size: "sm", tone: "danger", disabled: busy || !sel.size, onClick: () => {
|
|
897
|
+
setBusy(true);
|
|
898
|
+
onRemove([...sel]).then((r) => {
|
|
899
|
+
setDone(lang === "zh" ? `\u5DF2\u5220\u9664 ${r.removed} \u4F4D${r.failed ? `\uFF0C${r.failed} \u4F4D\u5931\u8D25` : ""}` : `Removed ${r.removed}${r.failed ? `, ${r.failed} failed` : ""}`);
|
|
900
|
+
}).finally(() => setBusy(false));
|
|
901
|
+
} }, busy ? T.working || "Working\u2026" : label), /* @__PURE__ */ React.createElement("span", { style: { fontSize: 12, color: "var(--faint)" } }, lang === "zh" ? "\u53D6\u6D88\u52FE\u9009\u53EF\u4FDD\u7559" : "Untick anyone you want to keep"))));
|
|
902
|
+
}
|
|
903
|
+
function useAssistant({ lang, T, muted, onToggleMute, sound, flash, setAlert, onReloadFriends }) {
|
|
904
|
+
const [thread, setThread] = React.useState(() => [{ day: (/* @__PURE__ */ new Date()).toLocaleDateString() }, asMsg("them", helpText(lang))]);
|
|
905
|
+
const append = React.useCallback((...ms) => setThread((t) => t.concat(ms)), []);
|
|
906
|
+
React.useEffect(() => {
|
|
907
|
+
setThread((t) => t.length === 2 ? [t[0], asMsg("them", helpText(lang))] : t);
|
|
908
|
+
}, [lang]);
|
|
909
|
+
const fetchFriends = async () => {
|
|
910
|
+
const r = await fetch("/api/assistant/friends").then((x) => x.json());
|
|
911
|
+
return { friends: r.friends || [], historySince: r.historySince || null, now: r.now || Date.now(), ver: r.beagleVer || "" };
|
|
912
|
+
};
|
|
913
|
+
const listReply = (rows, skipped, data, title) => {
|
|
914
|
+
if (!rows.length) {
|
|
915
|
+
return `${title}
|
|
916
|
+
|
|
917
|
+
${lang === "zh" ? "\u6CA1\u6709\u7B26\u5408\u7684\u597D\u53CB\u3002" : "No friends match."}
|
|
918
|
+
|
|
919
|
+
${sinceLine(data.historySince, lang)}`;
|
|
920
|
+
}
|
|
921
|
+
const skip = skipped.length ? lang === "zh" ? `
|
|
922
|
+
|
|
923
|
+
*\u8DF3\u8FC7\u4E86 ${skipped.length} \u4F4D\u6700\u8FD1 ${RECENT_DAYS} \u5929\u5185\u6DFB\u52A0\u7684\u597D\u53CB\u3002*` : `
|
|
924
|
+
|
|
925
|
+
*${skipped.length} friend${skipped.length > 1 ? "s" : ""} added in the last ${RECENT_DAYS} days ${skipped.length > 1 ? "were" : "was"} skipped.*` : "";
|
|
926
|
+
return `${title}
|
|
927
|
+
|
|
928
|
+
${friendTable(rows, data.now, lang)}${skip}
|
|
929
|
+
|
|
930
|
+
${sinceLine(data.historySince, lang)}`;
|
|
931
|
+
};
|
|
932
|
+
const ambiguous = (cands) => lang === "zh" ? `\u6709 ${cands.length} \u4F4D\u597D\u53CB\u7B26\u5408\uFF0C\u8BF7\u8BF4\u5F97\u66F4\u5177\u4F53\u4E00\u4E9B\uFF1A
|
|
933
|
+
${cands.map((c) => `- ${nameOf(c)}`).join("\n")}` : `That matches ${cands.length} friends \u2014 be more specific:
|
|
934
|
+
${cands.map((c) => `- ${nameOf(c)}`).join("\n")}`;
|
|
935
|
+
const notFound = (tok) => lang === "zh" ? `\u6CA1\u6709\u627E\u5230\u597D\u53CB "${tok}"\u3002` : `No friend matches "${tok}".`;
|
|
936
|
+
const handle = React.useCallback(async (text) => {
|
|
937
|
+
append(asMsg("me", text, { status: "read" }));
|
|
938
|
+
const cmd = parseCommand(text);
|
|
939
|
+
if (!cmd) {
|
|
940
|
+
return append(asMsg("them", lang === "zh" ? "\u6211\u53EA\u8BA4\u8BC6\u4EE5 `/` \u5F00\u5934\u7684\u547D\u4EE4\u3002\u8F93\u5165 `/help` \u770B\u770B\u80FD\u505A\u4EC0\u4E48\u3002" : "I only understand commands starting with `/`. Type `/help` to see them."));
|
|
941
|
+
}
|
|
942
|
+
if (cmd.ok === false) {
|
|
943
|
+
return append(asMsg("them", lang === "zh" ? "`/friends` \u9700\u8981 `list` \u6216 `remove`\uFF0C\u4F8B\u5982 `/friends list silent 90`\u3002" : "`/friends` needs `list` or `remove`, for example `/friends list silent 90`."));
|
|
944
|
+
}
|
|
945
|
+
try {
|
|
946
|
+
switch (cmd.name) {
|
|
947
|
+
case "help":
|
|
948
|
+
return append(asMsg("them", helpText(lang)));
|
|
949
|
+
case "status": {
|
|
950
|
+
const d = await fetchFriends();
|
|
951
|
+
const pending = d.friends.filter((f) => f.pending).length;
|
|
952
|
+
return append(asMsg("them", lang === "zh" ? `**\u72B6\u6001**
|
|
953
|
+
|
|
954
|
+
- \u597D\u53CB\uFF1A${d.friends.length}
|
|
955
|
+
- \u5F85\u5BF9\u65B9\u63A5\u53D7\uFF1A${pending}
|
|
956
|
+
- \u672C\u673A\u5386\u53F2\u8D77\u81EA\uFF1A${fmtDate(d.historySince, lang)}
|
|
957
|
+
- \u7248\u672C\uFF1A${d.ver}` : `**Status**
|
|
958
|
+
|
|
959
|
+
- Friends: ${d.friends.length}
|
|
960
|
+
- Waiting to be accepted: ${pending}
|
|
961
|
+
- History on this device since: ${fmtDate(d.historySince, lang)}
|
|
962
|
+
- Version: ${d.ver}`));
|
|
963
|
+
}
|
|
964
|
+
case "friends-list": {
|
|
965
|
+
const { filters, silentDays, unknown } = parseFilters(cmd.args);
|
|
966
|
+
if (unknown.length) {
|
|
967
|
+
return append(asMsg("them", lang === "zh" ? `\u4E0D\u8BA4\u8BC6\u7684\u7B5B\u9009\u6761\u4EF6\uFF1A${unknown.join(", ")}\u3002\u53EF\u7528\uFF1A${actions_default.filters.map((f) => f.name).join(", ")}\u3002` : `I don't know the filter${unknown.length > 1 ? "s" : ""} ${unknown.join(", ")}. Try: ${actions_default.filters.map((f) => f.name).join(", ")}.`));
|
|
968
|
+
}
|
|
969
|
+
const d = await fetchFriends();
|
|
970
|
+
const { matched, skippedRecent } = selectFriends(d.friends, filters, { now: d.now, silentDays });
|
|
971
|
+
const title = filters.length ? `**${filters.join(" + ")}**${filters.includes("silent") ? ` (${silentDays}d)` : ""} \u2014 ${matched.length}` : lang === "zh" ? `**\u5168\u90E8\u597D\u53CB** \u2014 ${matched.length}` : `**All friends** \u2014 ${matched.length}`;
|
|
972
|
+
return append(asMsg("them", listReply(matched, skippedRecent, d, title)));
|
|
973
|
+
}
|
|
974
|
+
case "friends-remove": {
|
|
975
|
+
const { filters, silentDays, unknown } = parseFilters(cmd.args);
|
|
976
|
+
if (unknown.length || !filters.length) {
|
|
977
|
+
return append(asMsg("them", lang === "zh" ? `\u5220\u9664\u9700\u8981\u7B5B\u9009\u6761\u4EF6\uFF0C\u4F8B\u5982 \`/friends remove never-messaged\`\u3002\u53EF\u7528\uFF1A${actions_default.filters.map((f) => f.name).join(", ")}\u3002` : `Removing needs a filter, for example \`/friends remove never-messaged\`. Available: ${actions_default.filters.map((f) => f.name).join(", ")}.`));
|
|
978
|
+
}
|
|
979
|
+
const d = await fetchFriends();
|
|
980
|
+
const { matched, skippedRecent } = selectFriends(d.friends, filters, { now: d.now, silentDays });
|
|
981
|
+
const title = lang === "zh" ? `**\u5C06\u8981\u5220\u9664 ${matched.length} \u4F4D\u597D\u53CB** \u2014 \u8BF7\u5148\u6838\u5BF9\u8FD9\u4EFD\u540D\u5355` : `**${matched.length} friend${matched.length === 1 ? "" : "s"} to remove** \u2014 check the list first`;
|
|
982
|
+
return append(asMsg(
|
|
983
|
+
"them",
|
|
984
|
+
listReply(matched, skippedRecent, d, title),
|
|
985
|
+
matched.length ? { assistant: { kind: "confirm", rows: matched } } : void 0
|
|
986
|
+
));
|
|
987
|
+
}
|
|
988
|
+
case "find": {
|
|
989
|
+
const q = cmd.args.join(" ");
|
|
990
|
+
if (!q)
|
|
991
|
+
return append(asMsg("them", lang === "zh" ? "\u7528\u6CD5\uFF1A`/find <\u6587\u5B57>`" : "Usage: `/find <text>`"));
|
|
992
|
+
const d = await fetchFriends();
|
|
993
|
+
const rows = findFriends(d.friends, q);
|
|
994
|
+
const title = lang === "zh" ? `**"${q}"** \u2014 ${rows.length} \u4F4D` : `**"${q}"** \u2014 ${rows.length} found`;
|
|
995
|
+
return append(asMsg("them", listReply(rows, [], d, title)));
|
|
996
|
+
}
|
|
997
|
+
case "rename": {
|
|
998
|
+
const [tok, ...rest] = cmd.args;
|
|
999
|
+
const alias = rest.join(" ").trim();
|
|
1000
|
+
if (!tok || !alias)
|
|
1001
|
+
return append(asMsg("them", lang === "zh" ? "\u7528\u6CD5\uFF1A`/rename <\u597D\u53CB> <\u65B0\u540D\u5B57>`" : "Usage: `/rename <friend> <new name>`"));
|
|
1002
|
+
const d = await fetchFriends();
|
|
1003
|
+
const { match, candidates } = resolveFriend(d.friends, tok);
|
|
1004
|
+
if (!match)
|
|
1005
|
+
return append(asMsg("them", candidates.length ? ambiguous(candidates) : notFound(tok)));
|
|
1006
|
+
const was = nameOf(match);
|
|
1007
|
+
await dkApi.alias(match.userid, alias);
|
|
1008
|
+
if (onReloadFriends)
|
|
1009
|
+
onReloadFriends();
|
|
1010
|
+
return append(asMsg("them", lang === "zh" ? `\u5DF2\u628A "${was}" \u6539\u540D\u4E3A "${alias}"\u3002` : `Renamed "${was}" to "${alias}".`));
|
|
1011
|
+
}
|
|
1012
|
+
case "mute":
|
|
1013
|
+
case "unmute": {
|
|
1014
|
+
const tok = cmd.args.join(" ").trim();
|
|
1015
|
+
if (!tok)
|
|
1016
|
+
return append(asMsg("them", `${lang === "zh" ? "\u7528\u6CD5" : "Usage"}: \`/${cmd.name} <friend>\``));
|
|
1017
|
+
const d = await fetchFriends();
|
|
1018
|
+
const { match, candidates } = resolveFriend(d.friends, tok);
|
|
1019
|
+
if (!match)
|
|
1020
|
+
return append(asMsg("them", candidates.length ? ambiguous(candidates) : notFound(tok)));
|
|
1021
|
+
const isMuted = (muted || []).includes(match.userid);
|
|
1022
|
+
const want = cmd.name === "mute";
|
|
1023
|
+
if (isMuted !== want)
|
|
1024
|
+
onToggleMute(match.userid);
|
|
1025
|
+
return append(asMsg("them", want ? lang === "zh" ? `\u5DF2\u9759\u97F3 "${nameOf(match)}"\u3002` : `Muted "${nameOf(match)}".` : lang === "zh" ? `\u5DF2\u53D6\u6D88\u9759\u97F3 "${nameOf(match)}"\u3002` : `Unmuted "${nameOf(match)}".`));
|
|
1026
|
+
}
|
|
1027
|
+
case "sound":
|
|
1028
|
+
case "flash": {
|
|
1029
|
+
const word = (cmd.args[0] || "").toLowerCase();
|
|
1030
|
+
if (word !== "on" && word !== "off") {
|
|
1031
|
+
return append(asMsg("them", `${lang === "zh" ? "\u7528\u6CD5" : "Usage"}: \`/${cmd.name} on|off\``));
|
|
1032
|
+
}
|
|
1033
|
+
const on = word === "on";
|
|
1034
|
+
setAlert(cmd.name, on);
|
|
1035
|
+
const what = cmd.name === "sound" ? lang === "zh" ? "\u63D0\u793A\u97F3" : "Sound" : lang === "zh" ? "\u7A97\u53E3\u95EA\u70C1" : "Window flash";
|
|
1036
|
+
return append(asMsg("them", lang === "zh" ? `${what}\u5DF2${on ? "\u5F00\u542F" : "\u5173\u95ED"}\u3002` : `${what} ${on ? "on" : "off"}.`));
|
|
1037
|
+
}
|
|
1038
|
+
default:
|
|
1039
|
+
return append(asMsg("them", lang === "zh" ? `\u4E0D\u8BA4\u8BC6\u7684\u547D\u4EE4 \`/${cmd.name}\`\u3002\u8F93\u5165 \`/help\` \u770B\u770B\u53EF\u7528\u547D\u4EE4\u3002` : `I don't know \`/${cmd.name}\`. Type \`/help\` for the list.`));
|
|
1040
|
+
}
|
|
1041
|
+
} catch (e) {
|
|
1042
|
+
return append(asMsg("them", lang === "zh" ? `\u51FA\u9519\u4E86\uFF1A${e && e.message || e}` : `That failed: ${e && e.message || e}`));
|
|
1043
|
+
}
|
|
1044
|
+
}, [append, lang, muted, onToggleMute, setAlert, onReloadFriends]);
|
|
1045
|
+
const removeFriends = React.useCallback(async (ids) => {
|
|
1046
|
+
let removed = 0;
|
|
1047
|
+
const failed = [];
|
|
1048
|
+
for (const id of ids) {
|
|
1049
|
+
try {
|
|
1050
|
+
const r = await dkApi.remove(id);
|
|
1051
|
+
if (r && r.ok === false)
|
|
1052
|
+
failed.push(id);
|
|
1053
|
+
else
|
|
1054
|
+
removed++;
|
|
1055
|
+
} catch (e) {
|
|
1056
|
+
failed.push(id);
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
if (onReloadFriends)
|
|
1060
|
+
onReloadFriends();
|
|
1061
|
+
append(asMsg("them", failed.length ? lang === "zh" ? `\u5DF2\u5220\u9664 ${removed} \u4F4D\uFF0C${failed.length} \u4F4D\u5931\u8D25\u3002` : `Removed ${removed}, ${failed.length} failed.` : lang === "zh" ? `\u5DF2\u5220\u9664 ${removed} \u4F4D\u597D\u53CB\u3002` : `Removed ${removed} friend${removed === 1 ? "" : "s"}.`));
|
|
1062
|
+
return { removed, failed: failed.length };
|
|
1063
|
+
}, [append, lang, onReloadFriends]);
|
|
1064
|
+
return { thread, handle, removeFriends };
|
|
1065
|
+
}
|
|
1066
|
+
|
|
509
1067
|
// src/chat.jsx
|
|
510
1068
|
function RequestsBlock({ T, requests, onAct }) {
|
|
511
1069
|
const [acted, setActed] = React.useState({});
|
|
@@ -587,7 +1145,7 @@ function PeerRow({ peer, T, active, onClick, muted }) {
|
|
|
587
1145
|
whiteSpace: "nowrap",
|
|
588
1146
|
overflow: "hidden",
|
|
589
1147
|
textOverflow: "ellipsis"
|
|
590
|
-
} }, peer.alias || shortKey(peer.userId, 8, 5)), peer.agent && /* @__PURE__ */ React.createElement(Icon, { name: "bot", size: 13, color: "var(--faint)", stroke: 2 }), muted && /* @__PURE__ */ React.createElement(Icon, { name: "bellOff", size: 12, color: "var(--faint)", stroke: 2, title: T && T.mutedTag || "sound muted" }), peer.wire === "64" && /* @__PURE__ */ React.createElement(Tag, { tone: "off", style: { height: 15, fontSize: 9.5, padding: "0 4px" } }, "w64")), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 6, marginTop: 2 } }, peer.ip ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11, color: "var(--faint)" } }, peer.ip), /* @__PURE__ */ React.createElement("span", { style: { color: "var(--line)", fontSize: 11 } }, "\xB7")) : null, /* @__PURE__ */ React.createElement("span", { style: {
|
|
1148
|
+
} }, peer.alias || shortKey(peer.userId, 8, 5)), peer.agent && /* @__PURE__ */ React.createElement(Icon, { name: "bot", size: 13, color: "var(--faint)", stroke: 2 }), peer.app && /* @__PURE__ */ React.createElement(Tag, { tone: "accent", style: { height: 15, fontSize: 9.5, padding: "0 4px" } }, "APP"), muted && /* @__PURE__ */ React.createElement(Icon, { name: "bellOff", size: 12, color: "var(--faint)", stroke: 2, title: T && T.mutedTag || "sound muted" }), peer.wire === "64" && /* @__PURE__ */ React.createElement(Tag, { tone: "off", style: { height: 15, fontSize: 9.5, padding: "0 4px" } }, "w64")), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 6, marginTop: 2 } }, peer.ip ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11, color: "var(--faint)" } }, peer.ip), /* @__PURE__ */ React.createElement("span", { style: { color: "var(--line)", fontSize: 11 } }, "\xB7")) : null, /* @__PURE__ */ React.createElement("span", { style: {
|
|
591
1149
|
fontSize: 11,
|
|
592
1150
|
color: "var(--dim)",
|
|
593
1151
|
whiteSpace: "nowrap",
|
|
@@ -643,6 +1201,8 @@ function PeerSidebar({ T, peers, requests, activeId, onSelect, onAct, onAdd, pre
|
|
|
643
1201
|
const s = (p.alias || "") + " " + p.userId + " " + p.ip;
|
|
644
1202
|
return s.toLowerCase().includes(q.toLowerCase());
|
|
645
1203
|
}).slice().sort((a, b) => {
|
|
1204
|
+
if (!!a.pinned !== !!b.pinned)
|
|
1205
|
+
return a.pinned ? -1 : 1;
|
|
646
1206
|
const ta = dkActivityTs(a), tb = dkActivityTs(b);
|
|
647
1207
|
if (ta !== tb)
|
|
648
1208
|
return tb - ta;
|
|
@@ -1308,7 +1868,7 @@ function DkChatForm({ form, submitted, peer, onSubmit }) {
|
|
|
1308
1868
|
return null;
|
|
1309
1869
|
}), err && /* @__PURE__ */ React.createElement("div", { style: { color: "var(--danger, #ff6b6b)", fontSize: 11.5, fontFamily: "var(--mono)" } }, err));
|
|
1310
1870
|
}
|
|
1311
|
-
function Msg({ m, peer, T, onTheater, onDelete, onCancel, onRetry, onRetryMsg, onReveal, onOpenFile, onCall, onFormSubmit, onNameCard, answered, selMode, selected, onToggleSel, busy, isGroup }) {
|
|
1871
|
+
function Msg({ m, peer, T, lang, onTheater, onDelete, onCancel, onRetry, onRetryMsg, onReveal, onOpenFile, onCall, onFormSubmit, onNameCard, onAssistantRemove, answered, selMode, selected, onToggleSel, busy, isGroup }) {
|
|
1312
1872
|
const mine = m.from === "me";
|
|
1313
1873
|
const nativeMedia = !m.file ? dkNativeMediaFromText(m.text) : null;
|
|
1314
1874
|
const grp = isGroup && m.dir !== "out" && !m.file && !nativeMedia ? dkGroupParse(m.text) : null;
|
|
@@ -1637,7 +2197,7 @@ function Msg({ m, peer, T, onTheater, onDelete, onCancel, onRetry, onRetryMsg, o
|
|
|
1637
2197
|
letterSpacing: -0.1,
|
|
1638
2198
|
wordBreak: "break-word"
|
|
1639
2199
|
} }, /* @__PURE__ */ React.createElement(MarkdownText, { text: read.text, onNameCard })));
|
|
1640
|
-
})(), read.form && /* @__PURE__ */ React.createElement("div", { style: { marginTop: 6 } }, /* @__PURE__ */ React.createElement(DkChatForm, { form: read.form, submitted, peer, onSubmit: onFormSubmit })), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 4, margin: "3px 3px 0" } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 10, color: "var(--faint)" } }, m.time), (() => {
|
|
2200
|
+
})(), read.form && /* @__PURE__ */ React.createElement("div", { style: { marginTop: 6 } }, /* @__PURE__ */ React.createElement(DkChatForm, { form: read.form, submitted, peer, onSubmit: onFormSubmit })), m.assistant && onAssistantRemove && /* @__PURE__ */ React.createElement(AssistantPanel, { m, T, lang, onRemove: onAssistantRemove }), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 4, margin: "3px 3px 0" } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 10, color: "var(--faint)" } }, m.time), (() => {
|
|
1641
2201
|
const sentDone = mine && m.status && m.status !== "queued" && m.status !== "sending" && m.status !== "failed";
|
|
1642
2202
|
const via = !mine ? m.via : sentDone ? m.via === "offline" ? "offline" : "online" : null;
|
|
1643
2203
|
if (!via)
|
|
@@ -1682,7 +2242,7 @@ function Msg({ m, peer, T, onTheater, onDelete, onCancel, onRetry, onRetryMsg, o
|
|
|
1682
2242
|
) : mine && m.via === "offline" && m.status !== "queued" && m.status !== "sending" ? /* @__PURE__ */ React.createElement(Icon, { name: "check", size: 12, stroke: 2.2, color: "#f59e0b", title: T.viaOutOffline || "posted to the offline relay \u2014 they get it when they come online" }) : mine && m.status === "sent" && m.confirmed === false ? /* @__PURE__ */ React.createElement(Icon, { name: "check", size: 12, stroke: 2.2, color: "var(--faint)", title: T.sentUnconfirmed || "sent \u2014 not confirmed by the peer" }) : mine && m.status && /* @__PURE__ */ React.createElement(Icon, { name: "checkCheck", size: 12, stroke: 2.2, color: m.status === "read" ? "var(--accent)" : "var(--faint)" })))
|
|
1683
2243
|
);
|
|
1684
2244
|
}
|
|
1685
|
-
function Conversation({ T, peer, lang, peers, onOpenChat, thread: threadProp, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread, hasEarlier, onLoadEarlier, muted, onToggleMute, phone, onBack }) {
|
|
2245
|
+
function Conversation({ T, peer, lang, peers, onOpenChat, thread: threadProp, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread, hasEarlier, onLoadEarlier, onAssistantRemove, muted, onToggleMute, phone, onBack }) {
|
|
1686
2246
|
const scrollRef = React.useRef(null);
|
|
1687
2247
|
const prependRef = React.useRef(null);
|
|
1688
2248
|
const [earlierBusy, setEarlierBusy] = React.useState(false);
|
|
@@ -1987,7 +2547,11 @@ function Conversation({ T, peer, lang, peers, onOpenChat, thread: threadProp, on
|
|
|
1987
2547
|
style: { width: 40, height: 44, flexShrink: 0, border: "none", background: "transparent", cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center", padding: 0 }
|
|
1988
2548
|
},
|
|
1989
2549
|
/* @__PURE__ */ React.createElement(Icon, { name: "chevronLeft", size: 26, stroke: 2.2, color: "var(--accent)" })
|
|
1990
|
-
), /* @__PURE__ */ React.createElement("div", { onClick: () => setPubProfile(true), title: T && T.pubOpen || "View public profile", style: { cursor: "pointer", flexShrink: 0 } }, /* @__PURE__ */ React.createElement(DkAvatar, { peer, size: 34, radius: 8 })), /* @__PURE__ */ React.createElement("div", { style: { minWidth: 0, flex: phone ? 1 : void 0 } }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8, minWidth: 0 } }, /* @__PURE__ */ React.createElement("span", { onClick: () => setPubProfile(true), style: { fontFamily: peer.alias ? "var(--ui)" : "var(--mono)", fontSize: 15, fontWeight: 700, color: "var(--text)", cursor: "pointer", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", minWidth: 0 } }, peer.alias || shortKey(peer.userId, phone ? 6 : 10, phone ? 4 : 6)), peer.
|
|
2550
|
+
), /* @__PURE__ */ React.createElement("div", { onClick: () => setPubProfile(true), title: T && T.pubOpen || "View public profile", style: { cursor: "pointer", flexShrink: 0 } }, /* @__PURE__ */ React.createElement(DkAvatar, { peer, size: 34, radius: 8 })), /* @__PURE__ */ React.createElement("div", { style: { minWidth: 0, flex: phone ? 1 : void 0 } }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8, minWidth: 0 } }, /* @__PURE__ */ React.createElement("span", { onClick: () => setPubProfile(true), style: { fontFamily: peer.alias ? "var(--ui)" : "var(--mono)", fontSize: 15, fontWeight: 700, color: "var(--text)", cursor: "pointer", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", minWidth: 0 } }, peer.alias || shortKey(peer.userId, phone ? 6 : 10, phone ? 4 : 6)), peer.app && !phone && /* @__PURE__ */ React.createElement(Tag, { tone: "accent" }, "APP"), peer.agent && !phone && /* @__PURE__ */ React.createElement(Tag, { tone: "accent" }, "agent"), isGroup && !phone && /* @__PURE__ */ React.createElement(Tag, null, T && T.groupTag || "group"), !phone && !peer.app && /* @__PURE__ */ React.createElement(RouteTag, { peer })), peer.app ? (
|
|
2551
|
+
// The internal id is not an address anyone can use and not a name
|
|
2552
|
+
// anyone chose; saying what this row IS is more use than showing it.
|
|
2553
|
+
/* @__PURE__ */ React.createElement("div", { style: { marginTop: 2, fontFamily: "var(--mono)", fontSize: 11.5, color: "var(--faint)" } }, T && T.assistantSub || "Local \xB7 APP")
|
|
2554
|
+
) : phone ? /* @__PURE__ */ React.createElement("div", { style: { marginTop: 2, display: "flex" } }, /* @__PURE__ */ React.createElement(RouteTag, { peer })) : /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8, marginTop: 2 } }, /* @__PURE__ */ React.createElement(Mono, { size: 11.5, dim: true, copy: peer.userId, title: peer.userId }, shortKey(peer.userId, 10, 6)), /* @__PURE__ */ React.createElement("span", { style: { color: "var(--line)" } }, "\xB7"), /* @__PURE__ */ React.createElement("button", { onClick: () => onOpenNet(peer), title: T && T.openNet || "Network view", style: { background: "none", border: "none", cursor: "pointer", padding: 0, color: "var(--accent)", display: "inline-flex", alignItems: "center" } }, /* @__PURE__ */ React.createElement(Icon, { name: "network", size: 12, stroke: 2 })), /* @__PURE__ */ React.createElement(Mono, { size: 11.5, dim: true, copy: peer.ip, title: peer.ip }, peer.ip), peer.address && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("span", { style: { color: "var(--line)" } }, "\xB7"), /* @__PURE__ */ React.createElement(
|
|
1991
2555
|
Mono,
|
|
1992
2556
|
{
|
|
1993
2557
|
size: 11.5,
|
|
@@ -2005,7 +2569,7 @@ ${peer.address}`
|
|
|
2005
2569
|
title: muted ? T && T.unmuteSound || "Turn sound back on for this chat" : T && T.muteSound || "Mute sound for this chat",
|
|
2006
2570
|
onClick: () => onToggleMute(peer.id)
|
|
2007
2571
|
}
|
|
2008
|
-
), onCall && /* @__PURE__ */ React.createElement(Btn, { icon: "phone", title: T && T.audioCall || "Audio call", onClick: () => onCall(peer.userId, false) }), onCall && /* @__PURE__ */ React.createElement(Btn, { icon: "video", title: T && T.videoCall || "Video call", onClick: () => onCall(peer.userId, true) }), onSendRtcFile && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
|
|
2572
|
+
), onCall && !peer.app && /* @__PURE__ */ React.createElement(Btn, { icon: "phone", title: T && T.audioCall || "Audio call", onClick: () => onCall(peer.userId, false) }), onCall && !peer.app && /* @__PURE__ */ React.createElement(Btn, { icon: "video", title: T && T.videoCall || "Video call", onClick: () => onCall(peer.userId, true) }), onSendRtcFile && !peer.app && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
|
|
2009
2573
|
"input",
|
|
2010
2574
|
{
|
|
2011
2575
|
ref: rtcFileRef,
|
|
@@ -2019,19 +2583,19 @@ ${peer.address}`
|
|
|
2019
2583
|
), !phone && /* @__PURE__ */ React.createElement(Btn, { icon: "file", title: T && T.sendWebrtcFile || "Send file via WebRTC", onClick: () => rtcFileRef.current && rtcFileRef.current.click() })), /* @__PURE__ */ React.createElement("div", { style: { position: "relative" } }, /* @__PURE__ */ React.createElement(Btn, { icon: "more", onClick: () => setMenu((v) => !v) }), menu && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", { onClick: () => setMenu(false), style: { position: "fixed", inset: 0, zIndex: 40 } }), /* @__PURE__ */ React.createElement("div", { style: { position: "absolute", right: 0, top: 36, zIndex: 50, width: 180, background: "var(--panel-2)", border: "1px solid var(--line)", borderRadius: 9, padding: 6, boxShadow: "0 14px 40px rgba(0,0,0,0.4)" } }, phone && onToggleMute && /* @__PURE__ */ React.createElement(MenuItem, { icon: muted ? "bell" : "bellOff", label: muted ? T && T.unmuteSound || "Turn sound back on" : T && T.muteSound || "Mute sound", onClick: () => {
|
|
2020
2584
|
setMenu(false);
|
|
2021
2585
|
onToggleMute(peer.id);
|
|
2022
|
-
} }), phone && onSendRtcFile && /* @__PURE__ */ React.createElement(MenuItem, { icon: "file", label: T && T.sendWebrtcFile || "Send file via WebRTC", onClick: () => {
|
|
2586
|
+
} }), phone && onSendRtcFile && !peer.app && /* @__PURE__ */ React.createElement(MenuItem, { icon: "file", label: T && T.sendWebrtcFile || "Send file via WebRTC", onClick: () => {
|
|
2023
2587
|
setMenu(false);
|
|
2024
2588
|
rtcFileRef.current && rtcFileRef.current.click();
|
|
2025
|
-
} }), phone && /* @__PURE__ */ React.createElement(MenuItem, { icon: "network", label: T && T.openNet || "Network view", onClick: () => {
|
|
2589
|
+
} }), phone && !peer.app && /* @__PURE__ */ React.createElement(MenuItem, { icon: "network", label: T && T.openNet || "Network view", onClick: () => {
|
|
2026
2590
|
setMenu(false);
|
|
2027
2591
|
onOpenNet(peer);
|
|
2028
|
-
} }), /* @__PURE__ */ React.createElement(MenuItem, { icon: "hash", label: T.alias, onClick: () => {
|
|
2592
|
+
} }), !peer.app && /* @__PURE__ */ React.createElement(MenuItem, { icon: "hash", label: T.alias, onClick: () => {
|
|
2029
2593
|
setMenu(false);
|
|
2030
2594
|
onAlias(peer);
|
|
2031
2595
|
} }), /* @__PURE__ */ React.createElement(MenuItem, { icon: "check", label: T.selectDelete || "Select & delete", onClick: () => {
|
|
2032
2596
|
setMenu(false);
|
|
2033
2597
|
setSelMode(true);
|
|
2034
|
-
} }), /* @__PURE__ */ React.createElement("div", { style: { height: 1, background: "var(--line)", margin: "5px 4px" } }), /* @__PURE__ */ React.createElement(MenuItem, { icon: "trash", label: T.remove, danger: true, onClick: () => {
|
|
2598
|
+
} }), !peer.app && /* @__PURE__ */ React.createElement("div", { style: { height: 1, background: "var(--line)", margin: "5px 4px" } }), !peer.app && /* @__PURE__ */ React.createElement(MenuItem, { icon: "trash", label: T.remove, danger: true, onClick: () => {
|
|
2035
2599
|
setMenu(false);
|
|
2036
2600
|
onRemove(peer);
|
|
2037
2601
|
} }))))),
|
|
@@ -2053,8 +2617,10 @@ ${peer.address}`
|
|
|
2053
2617
|
m,
|
|
2054
2618
|
peer,
|
|
2055
2619
|
T,
|
|
2620
|
+
lang,
|
|
2056
2621
|
isGroup,
|
|
2057
2622
|
onTheater: setTheater,
|
|
2623
|
+
onAssistantRemove,
|
|
2058
2624
|
onDelete: (id) => doDelete([id]),
|
|
2059
2625
|
onCancel: doCancel,
|
|
2060
2626
|
onRetry: doRetry,
|
|
@@ -2103,7 +2669,7 @@ ${peer.address}`
|
|
|
2103
2669
|
e.target.value = "";
|
|
2104
2670
|
}
|
|
2105
2671
|
}
|
|
2106
|
-
), /* @__PURE__ */ React.createElement(Btn, { icon: "paperclip", title: T.sendFile, onClick: () => fileRef.current && fileRef.current.click() }), /* @__PURE__ */ React.createElement(
|
|
2672
|
+
), !peer.app && /* @__PURE__ */ React.createElement(Btn, { icon: "paperclip", title: T.sendFile, onClick: () => fileRef.current && fileRef.current.click() }), /* @__PURE__ */ React.createElement(
|
|
2107
2673
|
"input",
|
|
2108
2674
|
{
|
|
2109
2675
|
value: draft,
|
|
@@ -2206,12 +2772,12 @@ function MenuItem({ icon, label, onClick, danger }) {
|
|
|
2206
2772
|
function ChatEmpty({ T }) {
|
|
2207
2773
|
return /* @__PURE__ */ React.createElement("div", { style: { flex: 1, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: 12, color: "var(--faint)", background: "var(--bg)" } }, /* @__PURE__ */ React.createElement(Icon, { name: "message", size: 40, stroke: 1.4, color: "var(--line)" }), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 13 } }, T.pickPeer));
|
|
2208
2774
|
}
|
|
2209
|
-
function ChatTab({ T, lang, peers, requests, activeId, thread, onSelect, onAct, onAdd, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread, hasEarlier, onLoadEarlier, prefillAddr, onPrefillConsumed, muted, onToggleMute, phone, onBack }) {
|
|
2775
|
+
function ChatTab({ T, lang, peers, requests, activeId, thread, onSelect, onAct, onAdd, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread, hasEarlier, onLoadEarlier, onAssistantRemove, prefillAddr, onPrefillConsumed, muted, onToggleMute, phone, onBack }) {
|
|
2210
2776
|
const peer = peers.find((p) => p.id === activeId);
|
|
2211
2777
|
if (phone) {
|
|
2212
|
-
return peer ? /* @__PURE__ */ React.createElement(Conversation, { T, peer, lang, peers, onOpenChat: onSelect, thread, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread, hasEarlier, onLoadEarlier, muted: !!muted && muted.includes(peer.id), onToggleMute, phone: true, onBack }) : /* @__PURE__ */ React.createElement(PeerSidebar, { T, peers, requests, activeId, onSelect, onAct, onAdd, prefillAddr, onPrefillConsumed, muted, wide: true });
|
|
2778
|
+
return peer ? /* @__PURE__ */ React.createElement(Conversation, { T, peer, lang, peers, onOpenChat: onSelect, thread, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread, hasEarlier, onLoadEarlier, onAssistantRemove, muted: !!muted && muted.includes(peer.id), onToggleMute, phone: true, onBack }) : /* @__PURE__ */ React.createElement(PeerSidebar, { T, peers, requests, activeId, onSelect, onAct, onAdd, prefillAddr, onPrefillConsumed, muted, wide: true });
|
|
2213
2779
|
}
|
|
2214
|
-
return /* @__PURE__ */ React.createElement("div", { style: { flex: 1, display: "flex", minWidth: 0, minHeight: 0 } }, /* @__PURE__ */ React.createElement(PeerSidebar, { T, peers, requests, activeId, onSelect, onAct, onAdd, prefillAddr, onPrefillConsumed, muted }), peer ? /* @__PURE__ */ React.createElement(Conversation, { T, peer, lang, peers, onOpenChat: onSelect, thread, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread, hasEarlier, onLoadEarlier, muted: !!muted && muted.includes(peer.id), onToggleMute }) : /* @__PURE__ */ React.createElement(ChatEmpty, { T }));
|
|
2780
|
+
return /* @__PURE__ */ React.createElement("div", { style: { flex: 1, display: "flex", minWidth: 0, minHeight: 0 } }, /* @__PURE__ */ React.createElement(PeerSidebar, { T, peers, requests, activeId, onSelect, onAct, onAdd, prefillAddr, onPrefillConsumed, muted }), peer ? /* @__PURE__ */ React.createElement(Conversation, { T, peer, lang, peers, onOpenChat: onSelect, thread, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread, hasEarlier, onLoadEarlier, onAssistantRemove, muted: !!muted && muted.includes(peer.id), onToggleMute }) : /* @__PURE__ */ React.createElement(ChatEmpty, { T }));
|
|
2215
2781
|
}
|
|
2216
2782
|
Object.assign(window, { ChatTab });
|
|
2217
2783
|
|
|
@@ -4996,6 +5562,7 @@ var STR = {
|
|
|
4996
5562
|
autoAcceptLabel: "Auto-accept friend requests",
|
|
4997
5563
|
autoAcceptSub: "Off: requests wait in the sidebar for your approval.",
|
|
4998
5564
|
groupSenderUnknown: "The group relays only this display name. Register Beagle as a group agent (/agent add <beagle address> in the group) to receive full member identities you can add as friends.",
|
|
5565
|
+
assistantSub: "Local \xB7 APP",
|
|
4999
5566
|
loadEarlier: "Load earlier messages",
|
|
5000
5567
|
loadingEarlier: "Loading\u2026",
|
|
5001
5568
|
pendingFriend: "Waiting for them to accept your friend request \u2014 messages will queue and deliver once they do.",
|
|
@@ -5190,6 +5757,7 @@ var STR = {
|
|
|
5190
5757
|
autoAcceptLabel: "\u81EA\u52A8\u63A5\u53D7\u597D\u53CB\u8BF7\u6C42",
|
|
5191
5758
|
autoAcceptSub: "\u5173\u95ED\u540E\uFF0C\u597D\u53CB\u8BF7\u6C42\u4F1A\u7B49\u5F85\u4F60\u5728\u4FA7\u8FB9\u680F\u624B\u52A8\u786E\u8BA4\u3002",
|
|
5192
5759
|
groupSenderUnknown: "\u7FA4\u670D\u52A1\u53EA\u8F6C\u53D1\u4E86\u663E\u793A\u540D\uFF0C\u62FF\u4E0D\u5230\u8BE5\u6210\u5458\u7684\u5B8C\u6574\u8EAB\u4EFD\u3002\u5728\u7FA4\u91CC\u6267\u884C /agent add <beagle \u5730\u5740> \u628A Beagle \u6CE8\u518C\u4E3A\u7FA4 agent \u540E\uFF0C\u5373\u53EF\u6536\u5230\u5B8C\u6574\u8EAB\u4EFD\u5E76\u4E00\u952E\u52A0\u597D\u53CB\u3002",
|
|
5760
|
+
assistantSub: "\u672C\u673A \xB7 APP",
|
|
5193
5761
|
loadEarlier: "\u52A0\u8F7D\u66F4\u65E9\u7684\u6D88\u606F",
|
|
5194
5762
|
loadingEarlier: "\u52A0\u8F7D\u4E2D\u2026",
|
|
5195
5763
|
pendingFriend: "\u7B49\u5F85\u5BF9\u65B9\u63A5\u53D7\u597D\u53CB\u8BF7\u6C42 \u2014 \u6D88\u606F\u4F1A\u5148\u6392\u961F\uFF0C\u5BF9\u65B9\u63A5\u53D7\u540E\u81EA\u52A8\u9001\u8FBE\u3002",
|
|
@@ -5536,7 +6104,7 @@ function DkApp() {
|
|
|
5536
6104
|
sessionStorage.setItem(key, "1");
|
|
5537
6105
|
window.location.reload();
|
|
5538
6106
|
}, [me && me.beagleVer]);
|
|
5539
|
-
const peers = data.peers;
|
|
6107
|
+
const peers = React.useMemo(() => [ASSISTANT_PEER].concat(data.peers || []), [data.peers]);
|
|
5540
6108
|
const [pendingAddr, setPendingAddr] = React.useState(() => dkParseDeepLinkAddress());
|
|
5541
6109
|
React.useEffect(() => {
|
|
5542
6110
|
const onHash = () => {
|
|
@@ -5600,6 +6168,16 @@ function DkApp() {
|
|
|
5600
6168
|
flash: t.alertFlash !== false,
|
|
5601
6169
|
muted: mutedIds
|
|
5602
6170
|
});
|
|
6171
|
+
const assistant = useAssistant({
|
|
6172
|
+
lang: t.lang,
|
|
6173
|
+
T,
|
|
6174
|
+
muted: mutedIds,
|
|
6175
|
+
onToggleMute,
|
|
6176
|
+
sound: t.alertSound !== false,
|
|
6177
|
+
flash: t.alertFlash !== false,
|
|
6178
|
+
setAlert: (which, on) => setTweak(which === "sound" ? "alertSound" : "alertFlash", on),
|
|
6179
|
+
onReloadFriends: () => data.refresh()
|
|
6180
|
+
});
|
|
5603
6181
|
const callCtl = useCallController(me.userId, (peerId, video, direction) => {
|
|
5604
6182
|
const text = `WebRTC ${video ? "video" : "audio"} call: ${direction}`;
|
|
5605
6183
|
dkApi.logLocal(peerId, direction === "outgoing" ? "out" : "in", text).then(() => {
|
|
@@ -5662,6 +6240,8 @@ function DkApp() {
|
|
|
5662
6240
|
React.useEffect(() => {
|
|
5663
6241
|
if (!activeId)
|
|
5664
6242
|
return;
|
|
6243
|
+
if (activeId === ASSISTANT_ID)
|
|
6244
|
+
return;
|
|
5665
6245
|
data.loadThread(activeId, { reset: true });
|
|
5666
6246
|
dkApi.markRead(activeId).then(data.refresh);
|
|
5667
6247
|
const iv = setInterval(() => {
|
|
@@ -5705,6 +6285,10 @@ function DkApp() {
|
|
|
5705
6285
|
const onSend = (text) => {
|
|
5706
6286
|
if (!activeId || !text || !text.trim())
|
|
5707
6287
|
return;
|
|
6288
|
+
if (activeId === ASSISTANT_ID) {
|
|
6289
|
+
assistant.handle(text.trim());
|
|
6290
|
+
return;
|
|
6291
|
+
}
|
|
5708
6292
|
dkApi.send(activeId, text.trim()).then(() => data.loadThread(activeId)).then(data.refresh);
|
|
5709
6293
|
};
|
|
5710
6294
|
const peerTakesRtcFile = (id) => {
|
|
@@ -5812,7 +6396,7 @@ function DkApp() {
|
|
|
5812
6396
|
fontWeight: 600,
|
|
5813
6397
|
background: tab === n.id ? "var(--accent)" : "transparent",
|
|
5814
6398
|
color: tab === n.id ? "#fff" : "var(--dim)"
|
|
5815
|
-
} }, /* @__PURE__ */ React.createElement(Icon, { name: n.icon, size: 14, stroke: 2, color: tab === n.id ? "#fff" : "var(--dim)" }), n.label))), tab === "chat" && /* @__PURE__ */ React.createElement(ChatTab, { T, lang: t.lang, phone, onBack: closeConversation, peers, requests, activeId, thread: data.threads[activeId], onSelect, onAct, onAdd, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread: () => activeId && data.loadThread(activeId), hasEarlier: !!(activeId && data.earlier[activeId]), onLoadEarlier: () => activeId && data.loadEarlier(activeId), prefillAddr: pendingAddr, onPrefillConsumed: () => setPendingAddr(""), muted: mutedIds, onToggleMute }), tab === "here" && /* @__PURE__ */ React.createElement(DiscoverTab, { T, kind: "bridge", peers, meId: me.userId, onAdd, onOpenChat }), tab === "recommended" && /* @__PURE__ */ React.createElement(DiscoverTab, { T, kind: "recommended", peers, meId: me.userId, onAdd, onOpenChat }), tab === "registered" && /* @__PURE__ */ React.createElement(DiscoverTab, { T, kind: "registered", peers, meId: me.userId, onAdd, onOpenChat }), tab === "network" && /* @__PURE__ */ React.createElement(NetworkTab, { T, me, peers, exits, activeExit, reqCount: requests.length, onSetExit, onOpenChat, backend, onArmLan: armLan, onCancelLan: cancelLan }), tab === "apps" && /* @__PURE__ */ React.createElement(AppsTab, { T, t }), tab === "profile" && /* @__PURE__ */ React.createElement(ProfileTab, { T, me, onEdit, t, setTweak, onSetAvatar: (p) => dkApi.setProfileFull(p).then((r) => {
|
|
6399
|
+
} }, /* @__PURE__ */ React.createElement(Icon, { name: n.icon, size: 14, stroke: 2, color: tab === n.id ? "#fff" : "var(--dim)" }), n.label))), tab === "chat" && /* @__PURE__ */ React.createElement(ChatTab, { T, lang: t.lang, phone, onBack: closeConversation, peers, requests, activeId, thread: activeId === ASSISTANT_ID ? assistant.thread : data.threads[activeId], onSelect, onAct, onAdd, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread: () => activeId && data.loadThread(activeId), hasEarlier: !!(activeId && activeId !== ASSISTANT_ID && data.earlier[activeId]), onAssistantRemove: activeId === ASSISTANT_ID ? assistant.removeFriends : null, onLoadEarlier: () => activeId && data.loadEarlier(activeId), prefillAddr: pendingAddr, onPrefillConsumed: () => setPendingAddr(""), muted: mutedIds, onToggleMute }), tab === "here" && /* @__PURE__ */ React.createElement(DiscoverTab, { T, kind: "bridge", peers, meId: me.userId, onAdd, onOpenChat }), tab === "recommended" && /* @__PURE__ */ React.createElement(DiscoverTab, { T, kind: "recommended", peers, meId: me.userId, onAdd, onOpenChat }), tab === "registered" && /* @__PURE__ */ React.createElement(DiscoverTab, { T, kind: "registered", peers, meId: me.userId, onAdd, onOpenChat }), tab === "network" && /* @__PURE__ */ React.createElement(NetworkTab, { T, me, peers, exits, activeExit, reqCount: requests.length, onSetExit, onOpenChat, backend, onArmLan: armLan, onCancelLan: cancelLan }), tab === "apps" && /* @__PURE__ */ React.createElement(AppsTab, { T, t }), tab === "profile" && /* @__PURE__ */ React.createElement(ProfileTab, { T, me, onEdit, t, setTweak, onSetAvatar: (p) => dkApi.setProfileFull(p).then((r) => {
|
|
5816
6400
|
data.refresh();
|
|
5817
6401
|
return r;
|
|
5818
6402
|
}) })), phone && !inConversation && /* @__PURE__ */ React.createElement("div", { style: { flexShrink: 0, display: "flex", borderTop: "1px solid var(--line)", background: "var(--rail)", paddingBottom: "env(safe-area-inset-bottom, 0px)" } }, phoneNav.map((n) => /* @__PURE__ */ React.createElement(TabBarBtn, { key: n.id, icon: n.icon, label: n.label, active: n.active, badge: n.badge, onClick: n.go }))), data.locked && /* @__PURE__ */ React.createElement(
|
package/package.json
CHANGED