@decentnetwork/beagle-ui 0.2.22 → 0.2.24
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 +436 -40
- 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.24";
|
|
2
2
|
// src/icons.jsx
|
|
3
3
|
var ICON_PATHS = {
|
|
4
4
|
// ---- tab bar (the four must feel like one set) ----
|
|
@@ -516,23 +516,39 @@ var actions_default = {
|
|
|
516
516
|
name: "help",
|
|
517
517
|
usage: "/help",
|
|
518
518
|
summary: "List what the Assistant can do.",
|
|
519
|
-
params: {
|
|
519
|
+
params: {
|
|
520
|
+
type: "object",
|
|
521
|
+
properties: {},
|
|
522
|
+
required: []
|
|
523
|
+
},
|
|
520
524
|
confirm: "none",
|
|
521
525
|
scope: "assistant"
|
|
522
526
|
},
|
|
523
527
|
{
|
|
524
528
|
name: "friends-list",
|
|
525
529
|
usage: "/friends list [filter\u2026]",
|
|
526
|
-
summary: "Show friends as a
|
|
530
|
+
summary: "Show friends as a list: name, last heard from, messages in/out. With no filter, everyone. Service peers are never included \u2014 they are /nodes.",
|
|
527
531
|
params: {
|
|
528
532
|
type: "object",
|
|
529
533
|
properties: {
|
|
530
534
|
filters: {
|
|
531
535
|
type: "array",
|
|
532
|
-
items: {
|
|
536
|
+
items: {
|
|
537
|
+
enum: [
|
|
538
|
+
"never-messaged",
|
|
539
|
+
"never-replied",
|
|
540
|
+
"silent",
|
|
541
|
+
"pending"
|
|
542
|
+
]
|
|
543
|
+
},
|
|
533
544
|
description: "Several filters are OR'd together."
|
|
534
545
|
},
|
|
535
|
-
silentDays: {
|
|
546
|
+
silentDays: {
|
|
547
|
+
type: "integer",
|
|
548
|
+
minimum: 1,
|
|
549
|
+
default: 90,
|
|
550
|
+
description: "Argument of the silent filter: nothing from them in this many days."
|
|
551
|
+
}
|
|
536
552
|
},
|
|
537
553
|
required: []
|
|
538
554
|
},
|
|
@@ -542,18 +558,94 @@ var actions_default = {
|
|
|
542
558
|
{
|
|
543
559
|
name: "friends-remove",
|
|
544
560
|
usage: "/friends remove <filter\u2026>",
|
|
545
|
-
summary: "Same
|
|
561
|
+
summary: "Same list with a tick per row and a Remove button. Nothing is removed by typing alone. Service peers are never matched.",
|
|
562
|
+
params: {
|
|
563
|
+
type: "object",
|
|
564
|
+
properties: {
|
|
565
|
+
filters: {
|
|
566
|
+
type: "array",
|
|
567
|
+
items: {
|
|
568
|
+
enum: [
|
|
569
|
+
"never-messaged",
|
|
570
|
+
"never-replied",
|
|
571
|
+
"silent",
|
|
572
|
+
"pending"
|
|
573
|
+
]
|
|
574
|
+
},
|
|
575
|
+
minItems: 1
|
|
576
|
+
},
|
|
577
|
+
silentDays: {
|
|
578
|
+
type: "integer",
|
|
579
|
+
minimum: 1,
|
|
580
|
+
default: 90
|
|
581
|
+
}
|
|
582
|
+
},
|
|
583
|
+
required: [
|
|
584
|
+
"filters"
|
|
585
|
+
]
|
|
586
|
+
},
|
|
587
|
+
confirm: "list",
|
|
588
|
+
scope: "assistant"
|
|
589
|
+
},
|
|
590
|
+
{
|
|
591
|
+
name: "nodes-list",
|
|
592
|
+
usage: "/nodes list [filter\u2026]",
|
|
593
|
+
summary: "Service peers \u2014 network nodes and agents \u2014 with their kind and role. The same filters as /friends list.",
|
|
594
|
+
params: {
|
|
595
|
+
type: "object",
|
|
596
|
+
properties: {
|
|
597
|
+
filters: {
|
|
598
|
+
type: "array",
|
|
599
|
+
items: {
|
|
600
|
+
enum: [
|
|
601
|
+
"never-messaged",
|
|
602
|
+
"never-replied",
|
|
603
|
+
"silent",
|
|
604
|
+
"pending"
|
|
605
|
+
]
|
|
606
|
+
},
|
|
607
|
+
description: "Several filters are OR'd together."
|
|
608
|
+
},
|
|
609
|
+
silentDays: {
|
|
610
|
+
type: "integer",
|
|
611
|
+
minimum: 1,
|
|
612
|
+
default: 90
|
|
613
|
+
}
|
|
614
|
+
},
|
|
615
|
+
required: []
|
|
616
|
+
},
|
|
617
|
+
confirm: "none",
|
|
618
|
+
scope: "assistant"
|
|
619
|
+
},
|
|
620
|
+
{
|
|
621
|
+
name: "nodes-remove",
|
|
622
|
+
usage: "/nodes remove <filter\u2026>",
|
|
623
|
+
summary: "The same table with a tick per row and a Remove button, over service peers only.",
|
|
546
624
|
params: {
|
|
547
625
|
type: "object",
|
|
548
626
|
properties: {
|
|
549
627
|
filters: {
|
|
550
628
|
type: "array",
|
|
551
|
-
items: {
|
|
629
|
+
items: {
|
|
630
|
+
enum: [
|
|
631
|
+
"never-messaged",
|
|
632
|
+
"never-replied",
|
|
633
|
+
"silent",
|
|
634
|
+
"pending"
|
|
635
|
+
]
|
|
636
|
+
},
|
|
637
|
+
description: "Several filters are OR'd together.",
|
|
552
638
|
minItems: 1
|
|
553
639
|
},
|
|
554
|
-
silentDays: {
|
|
640
|
+
silentDays: {
|
|
641
|
+
type: "integer",
|
|
642
|
+
minimum: 1,
|
|
643
|
+
default: 90
|
|
644
|
+
}
|
|
555
645
|
},
|
|
556
|
-
required: [
|
|
646
|
+
required: [
|
|
647
|
+
"filters"
|
|
648
|
+
]
|
|
557
649
|
},
|
|
558
650
|
confirm: "list",
|
|
559
651
|
scope: "assistant"
|
|
@@ -564,8 +656,15 @@ var actions_default = {
|
|
|
564
656
|
summary: "Friends whose name, alias, description or ens name contains the text.",
|
|
565
657
|
params: {
|
|
566
658
|
type: "object",
|
|
567
|
-
properties: {
|
|
568
|
-
|
|
659
|
+
properties: {
|
|
660
|
+
text: {
|
|
661
|
+
type: "string",
|
|
662
|
+
minLength: 1
|
|
663
|
+
}
|
|
664
|
+
},
|
|
665
|
+
required: [
|
|
666
|
+
"text"
|
|
667
|
+
]
|
|
569
668
|
},
|
|
570
669
|
confirm: "none",
|
|
571
670
|
scope: "assistant"
|
|
@@ -577,10 +676,19 @@ var actions_default = {
|
|
|
577
676
|
params: {
|
|
578
677
|
type: "object",
|
|
579
678
|
properties: {
|
|
580
|
-
friend: {
|
|
581
|
-
|
|
679
|
+
friend: {
|
|
680
|
+
type: "string",
|
|
681
|
+
description: "Matches name, alias or userid prefix. Ambiguous = nothing happens, the candidates are listed."
|
|
682
|
+
},
|
|
683
|
+
alias: {
|
|
684
|
+
type: "string",
|
|
685
|
+
minLength: 1
|
|
686
|
+
}
|
|
582
687
|
},
|
|
583
|
-
required: [
|
|
688
|
+
required: [
|
|
689
|
+
"friend",
|
|
690
|
+
"alias"
|
|
691
|
+
]
|
|
584
692
|
},
|
|
585
693
|
confirm: "none",
|
|
586
694
|
scope: "assistant"
|
|
@@ -591,8 +699,14 @@ var actions_default = {
|
|
|
591
699
|
summary: "Stop alerts from one conversation.",
|
|
592
700
|
params: {
|
|
593
701
|
type: "object",
|
|
594
|
-
properties: {
|
|
595
|
-
|
|
702
|
+
properties: {
|
|
703
|
+
friend: {
|
|
704
|
+
type: "string"
|
|
705
|
+
}
|
|
706
|
+
},
|
|
707
|
+
required: [
|
|
708
|
+
"friend"
|
|
709
|
+
]
|
|
596
710
|
},
|
|
597
711
|
confirm: "none",
|
|
598
712
|
scope: "assistant"
|
|
@@ -603,8 +717,14 @@ var actions_default = {
|
|
|
603
717
|
summary: "Alerts from that conversation again.",
|
|
604
718
|
params: {
|
|
605
719
|
type: "object",
|
|
606
|
-
properties: {
|
|
607
|
-
|
|
720
|
+
properties: {
|
|
721
|
+
friend: {
|
|
722
|
+
type: "string"
|
|
723
|
+
}
|
|
724
|
+
},
|
|
725
|
+
required: [
|
|
726
|
+
"friend"
|
|
727
|
+
]
|
|
608
728
|
},
|
|
609
729
|
confirm: "none",
|
|
610
730
|
scope: "assistant"
|
|
@@ -615,8 +735,14 @@ var actions_default = {
|
|
|
615
735
|
summary: "Sound on a new message.",
|
|
616
736
|
params: {
|
|
617
737
|
type: "object",
|
|
618
|
-
properties: {
|
|
619
|
-
|
|
738
|
+
properties: {
|
|
739
|
+
on: {
|
|
740
|
+
type: "boolean"
|
|
741
|
+
}
|
|
742
|
+
},
|
|
743
|
+
required: [
|
|
744
|
+
"on"
|
|
745
|
+
]
|
|
620
746
|
},
|
|
621
747
|
confirm: "none",
|
|
622
748
|
scope: "assistant"
|
|
@@ -627,8 +753,14 @@ var actions_default = {
|
|
|
627
753
|
summary: "Flash the window on a new message.",
|
|
628
754
|
params: {
|
|
629
755
|
type: "object",
|
|
630
|
-
properties: {
|
|
631
|
-
|
|
756
|
+
properties: {
|
|
757
|
+
on: {
|
|
758
|
+
type: "boolean"
|
|
759
|
+
}
|
|
760
|
+
},
|
|
761
|
+
required: [
|
|
762
|
+
"on"
|
|
763
|
+
]
|
|
632
764
|
},
|
|
633
765
|
confirm: "none",
|
|
634
766
|
scope: "assistant"
|
|
@@ -636,8 +768,12 @@ var actions_default = {
|
|
|
636
768
|
{
|
|
637
769
|
name: "status",
|
|
638
770
|
usage: "/status",
|
|
639
|
-
summary: "Friends, pending requests, how far back the history on this device goes, app version.",
|
|
640
|
-
params: {
|
|
771
|
+
summary: "Friends and service peers counted separately, pending requests, how far back the history on this device goes, app version.",
|
|
772
|
+
params: {
|
|
773
|
+
type: "object",
|
|
774
|
+
properties: {},
|
|
775
|
+
required: []
|
|
776
|
+
},
|
|
641
777
|
confirm: "none",
|
|
642
778
|
scope: "assistant"
|
|
643
779
|
}
|
|
@@ -660,7 +796,12 @@ var actions_default = {
|
|
|
660
796
|
usage: "silent [days]",
|
|
661
797
|
summary: "Nothing from them for this many days (default 90).",
|
|
662
798
|
guarded: true,
|
|
663
|
-
arg: {
|
|
799
|
+
arg: {
|
|
800
|
+
name: "days",
|
|
801
|
+
type: "integer",
|
|
802
|
+
default: 90,
|
|
803
|
+
minimum: 1
|
|
804
|
+
}
|
|
664
805
|
},
|
|
665
806
|
{
|
|
666
807
|
name: "pending",
|
|
@@ -678,9 +819,151 @@ var actions_default = {
|
|
|
678
819
|
guard: {
|
|
679
820
|
recentDays: 14,
|
|
680
821
|
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."
|
|
822
|
+
},
|
|
823
|
+
servicePeers: {
|
|
824
|
+
file: "service-peers.json",
|
|
825
|
+
loader: "loadServicePeers()",
|
|
826
|
+
rule: "A peer is a service peer if and only if its userid is in this list. The Assistant never infers it from a name or from having no messages. /friends excludes them entirely; /nodes is only them.",
|
|
827
|
+
later: "A dora / ENS / bridge / directory service returns the same shape; the bundled file is v1."
|
|
681
828
|
}
|
|
682
829
|
};
|
|
683
830
|
|
|
831
|
+
// src/assistant/service-peers.json
|
|
832
|
+
var service_peers_default = {
|
|
833
|
+
version: 1,
|
|
834
|
+
source: "static",
|
|
835
|
+
updated: "2026-09-24",
|
|
836
|
+
note: "Service providers and network nodes: peers that are infrastructure or agents, not people. The Assistant manages them with /nodes, never /friends. Later this list comes from a service (dora / ENS / bridge / directory) in this same shape; until then it ships inside the app.",
|
|
837
|
+
peers: [
|
|
838
|
+
{
|
|
839
|
+
userid: "7xmbDbouSYEA5mbAgWxV2BTwjc59XGcVPqXSZx6VibUd",
|
|
840
|
+
name: "lens",
|
|
841
|
+
kind: "node",
|
|
842
|
+
role: "bridge + web host"
|
|
843
|
+
},
|
|
844
|
+
{
|
|
845
|
+
userid: "4uNnLnAQHVJ7td657MDaz4UnEuhnTW9sRAPSVeAEEQe4",
|
|
846
|
+
name: "tokyo",
|
|
847
|
+
kind: "node",
|
|
848
|
+
role: "exit + relay + dora + meeting"
|
|
849
|
+
},
|
|
850
|
+
{
|
|
851
|
+
userid: "5Aj6uQMd1cNRb9cGT4AwHCN4RdVZjfaGLtgKGhdP1LzN",
|
|
852
|
+
name: "cn",
|
|
853
|
+
kind: "node",
|
|
854
|
+
role: "exit"
|
|
855
|
+
},
|
|
856
|
+
{
|
|
857
|
+
userid: "DZN2L9RV1YkHjqGHMTA6juZhskKA65AD2RyPt4umZVc7",
|
|
858
|
+
name: "callpass",
|
|
859
|
+
kind: "node",
|
|
860
|
+
role: "server"
|
|
861
|
+
},
|
|
862
|
+
{
|
|
863
|
+
userid: "BcGvk2h5XCxBZWJrYwzKpZ8GbxaZRoakEzt2pQpathyA",
|
|
864
|
+
name: "gojipower",
|
|
865
|
+
kind: "node",
|
|
866
|
+
role: "server"
|
|
867
|
+
},
|
|
868
|
+
{
|
|
869
|
+
userid: "BeMbuKf1poh3U4rjw3eeUAKZsbN2qBSjVZdPSvehmHsw",
|
|
870
|
+
name: "snoopy",
|
|
871
|
+
kind: "node",
|
|
872
|
+
role: "dora + meeting + openclaw host"
|
|
873
|
+
},
|
|
874
|
+
{
|
|
875
|
+
userid: "CzwUZv6LfiGM7EXd9vXr6DutG8ASkA3TnNbqQzjKRJN5",
|
|
876
|
+
name: "GFAX",
|
|
877
|
+
kind: "node",
|
|
878
|
+
role: "server"
|
|
879
|
+
},
|
|
880
|
+
{
|
|
881
|
+
userid: "5SxEBHAx49Hknq1VzrA1HDtZHjW31vetX3iMEi4P6pK3",
|
|
882
|
+
name: "gfax118",
|
|
883
|
+
kind: "node",
|
|
884
|
+
role: "server"
|
|
885
|
+
},
|
|
886
|
+
{
|
|
887
|
+
userid: "72a4A7awtn6BqZnBt3eNhZH9MdYXRkM6Ht5HG2sqoiKR",
|
|
888
|
+
name: "nv4090-wsl",
|
|
889
|
+
kind: "node",
|
|
890
|
+
role: "GPU node"
|
|
891
|
+
},
|
|
892
|
+
{
|
|
893
|
+
userid: "6riqxxw18vjwXZEEtFUxbgNBMMe1dcv2kVHqVRQdihjE",
|
|
894
|
+
name: "node-3145",
|
|
895
|
+
kind: "node",
|
|
896
|
+
role: "GPU node (4090w)"
|
|
897
|
+
},
|
|
898
|
+
{
|
|
899
|
+
userid: "6D1PLSVqbSpcxnDMAd8ZXLWycYveeP2hU4g3igDQEqA7",
|
|
900
|
+
name: "node-5123",
|
|
901
|
+
kind: "node",
|
|
902
|
+
role: "node"
|
|
903
|
+
},
|
|
904
|
+
{
|
|
905
|
+
userid: "5EU9Ee3Vai1XwTr4pGaJFt4wmNVPUoYnJsdBM9LwNhrJ",
|
|
906
|
+
name: "node-2998",
|
|
907
|
+
kind: "node",
|
|
908
|
+
role: "node"
|
|
909
|
+
},
|
|
910
|
+
{
|
|
911
|
+
userid: "GujioyKQRucQgw8mwMPNWtYTdNdrooCuFq3R1D4RqjCW",
|
|
912
|
+
name: "beagle-help",
|
|
913
|
+
kind: "agent",
|
|
914
|
+
role: "product support"
|
|
915
|
+
},
|
|
916
|
+
{
|
|
917
|
+
userid: "FhbohSLrj5UjdyFKCEYNeEWAPq3QD9hRg6hsso5ipag2",
|
|
918
|
+
name: "help.beagles.eth",
|
|
919
|
+
kind: "agent",
|
|
920
|
+
role: "bridge agent"
|
|
921
|
+
},
|
|
922
|
+
{
|
|
923
|
+
userid: "9EaFGWuXH2UfREzYMkPPitzjoH6E8tvey4JvqTKtHFJh",
|
|
924
|
+
name: "openclaw",
|
|
925
|
+
kind: "agent",
|
|
926
|
+
role: "OpenClaw agent"
|
|
927
|
+
},
|
|
928
|
+
{
|
|
929
|
+
userid: "6z2o8ojPVjFGRzcffUdd5btvDYAt4tLuwJ8j1jvMBU8p",
|
|
930
|
+
name: "ClawAlan",
|
|
931
|
+
kind: "agent",
|
|
932
|
+
role: "OpenClaw agent on snoopy"
|
|
933
|
+
},
|
|
934
|
+
{
|
|
935
|
+
userid: "G7s3YjVfNLTY5pKBd83qQLAdzDddS7ZstsfrUySwK2Cb",
|
|
936
|
+
name: "interview",
|
|
937
|
+
kind: "agent",
|
|
938
|
+
role: "interview"
|
|
939
|
+
},
|
|
940
|
+
{
|
|
941
|
+
userid: "6jbXjrUWJ4zYvG8BDLnu8KVpoL7rhsdHYSwWER6WTfdt",
|
|
942
|
+
name: "interview editor",
|
|
943
|
+
kind: "agent",
|
|
944
|
+
role: "interview"
|
|
945
|
+
},
|
|
946
|
+
{
|
|
947
|
+
userid: "8BGemh6uqmh4X7KpnPVMeMSU8hZwAc6ZDo4MBRMFrq2L",
|
|
948
|
+
name: "readyplayer",
|
|
949
|
+
kind: "agent",
|
|
950
|
+
role: "readyplayer"
|
|
951
|
+
},
|
|
952
|
+
{
|
|
953
|
+
userid: "Ev1XmWRwtjQf8n33wgTwEhzXnYEyfqTyanehuDE6nczF",
|
|
954
|
+
name: "ReadyPlayer membership manager",
|
|
955
|
+
kind: "agent",
|
|
956
|
+
role: "readyplayer"
|
|
957
|
+
},
|
|
958
|
+
{
|
|
959
|
+
userid: "7xLPH2kjk7Nn8GrsAt7TYwa6b1WMPLkC2XRx7BzhiXYT",
|
|
960
|
+
name: "ai-btc-trader",
|
|
961
|
+
kind: "agent",
|
|
962
|
+
role: "btc signals (245)"
|
|
963
|
+
}
|
|
964
|
+
]
|
|
965
|
+
};
|
|
966
|
+
|
|
684
967
|
// src/assistant/filters.js
|
|
685
968
|
var ASSISTANT_ID = "__assistant__";
|
|
686
969
|
var DAY_MS = 864e5;
|
|
@@ -696,11 +979,11 @@ function parseCommand(line) {
|
|
|
696
979
|
if (!parts.length)
|
|
697
980
|
return null;
|
|
698
981
|
const head = parts[0].toLowerCase();
|
|
699
|
-
if (head === "friends") {
|
|
982
|
+
if (head === "friends" || head === "nodes") {
|
|
700
983
|
const sub = (parts[1] || "").toLowerCase();
|
|
701
984
|
if (sub === "list" || sub === "remove")
|
|
702
|
-
return { ok: true, name:
|
|
703
|
-
return { ok: false, name:
|
|
985
|
+
return { ok: true, name: `${head}-${sub}`, args: parts.slice(2) };
|
|
986
|
+
return { ok: false, name: head, args: parts.slice(1), error: `${head}-subcommand` };
|
|
704
987
|
}
|
|
705
988
|
return { ok: true, name: head, args: parts.slice(1) };
|
|
706
989
|
}
|
|
@@ -727,6 +1010,30 @@ function parseFilters(args) {
|
|
|
727
1010
|
}
|
|
728
1011
|
return { filters, silentDays, unknown };
|
|
729
1012
|
}
|
|
1013
|
+
function servicePeerMap(list) {
|
|
1014
|
+
const map = /* @__PURE__ */ new Map();
|
|
1015
|
+
const peers = list && list.peers || [];
|
|
1016
|
+
for (const p of peers) {
|
|
1017
|
+
if (p && typeof p.userid === "string" && p.userid) {
|
|
1018
|
+
map.set(p.userid, { kind: p.kind === "agent" ? "agent" : "node", role: p.role || "", name: p.name || "" });
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
return map;
|
|
1022
|
+
}
|
|
1023
|
+
function partitionPeers(friends, svcMap) {
|
|
1024
|
+
const people = [];
|
|
1025
|
+
const nodes = [];
|
|
1026
|
+
for (const f of friends != null ? friends : []) {
|
|
1027
|
+
if (!f)
|
|
1028
|
+
continue;
|
|
1029
|
+
const svc = svcMap && svcMap.get(f.userid);
|
|
1030
|
+
if (svc)
|
|
1031
|
+
nodes.push(Object.assign({}, f, { kind: svc.kind, role: svc.role, service: true }));
|
|
1032
|
+
else
|
|
1033
|
+
people.push(f);
|
|
1034
|
+
}
|
|
1035
|
+
return { people, nodes };
|
|
1036
|
+
}
|
|
730
1037
|
var acceptedOrRequestedAt = (f) => f.acceptedAt || f.requestedAt || 0;
|
|
731
1038
|
function isRecent(friend, now, recentDays = RECENT_DAYS) {
|
|
732
1039
|
const at = acceptedOrRequestedAt(friend);
|
|
@@ -797,6 +1104,9 @@ function resolveFriend(friends, token) {
|
|
|
797
1104
|
}
|
|
798
1105
|
|
|
799
1106
|
// src/assistant.jsx
|
|
1107
|
+
function loadServicePeers() {
|
|
1108
|
+
return service_peers_default;
|
|
1109
|
+
}
|
|
800
1110
|
var ASSISTANT_PEER = {
|
|
801
1111
|
id: ASSISTANT_ID,
|
|
802
1112
|
userId: ASSISTANT_ID,
|
|
@@ -839,7 +1149,10 @@ function sinceLine(historySince, lang) {
|
|
|
839
1149
|
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
1150
|
}
|
|
841
1151
|
function friendTable(rows, now, lang) {
|
|
842
|
-
return rows.map((f) =>
|
|
1152
|
+
return rows.map((f) => {
|
|
1153
|
+
const what = f.service ? ` \`${f.kind}${f.role ? " \xB7 " + f.role : ""}\`` : "";
|
|
1154
|
+
return lang === "zh" ? `- **${nameOf(f)}**${what} \u2014 \u6700\u540E\u6536\u5230 ${fmtAgo(f.lastInboundTs, now, lang)} \xB7 \u6536 ${f.inboundCount || 0} / \u53D1 ${f.outboundCount || 0}` : `- **${nameOf(f)}**${what} \u2014 last heard ${fmtAgo(f.lastInboundTs, now, lang)} \xB7 in ${f.inboundCount || 0} / out ${f.outboundCount || 0}`;
|
|
1155
|
+
}).join("\n");
|
|
843
1156
|
}
|
|
844
1157
|
function helpText(lang) {
|
|
845
1158
|
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:";
|
|
@@ -908,7 +1221,15 @@ function useAssistant({ lang, T, muted, onToggleMute, sound, flash, setAlert, on
|
|
|
908
1221
|
}, [lang]);
|
|
909
1222
|
const fetchFriends = async () => {
|
|
910
1223
|
const r = await fetch("/api/assistant/friends").then((x) => x.json());
|
|
911
|
-
|
|
1224
|
+
const { people, nodes } = partitionPeers(r.friends || [], servicePeerMap(loadServicePeers()));
|
|
1225
|
+
return {
|
|
1226
|
+
friends: people,
|
|
1227
|
+
nodes,
|
|
1228
|
+
all: r.friends || [],
|
|
1229
|
+
historySince: r.historySince || null,
|
|
1230
|
+
now: r.now || Date.now(),
|
|
1231
|
+
ver: r.beagleVer || ""
|
|
1232
|
+
};
|
|
912
1233
|
};
|
|
913
1234
|
const listReply = (rows, skipped, data, title) => {
|
|
914
1235
|
if (!rows.length) {
|
|
@@ -940,7 +1261,7 @@ ${cands.map((c) => `- ${nameOf(c)}`).join("\n")}`;
|
|
|
940
1261
|
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
1262
|
}
|
|
942
1263
|
if (cmd.ok === false) {
|
|
943
|
-
return append(asMsg("them", lang === "zh" ?
|
|
1264
|
+
return append(asMsg("them", lang === "zh" ? `\`/${cmd.name}\` \u9700\u8981 \`list\` \u6216 \`remove\`\uFF0C\u4F8B\u5982 \`/${cmd.name} list silent 90\`\u3002` : `\`/${cmd.name}\` needs \`list\` or \`remove\`, for example \`/${cmd.name} list silent 90\`.`));
|
|
944
1265
|
}
|
|
945
1266
|
try {
|
|
946
1267
|
switch (cmd.name) {
|
|
@@ -948,37 +1269,48 @@ ${cands.map((c) => `- ${nameOf(c)}`).join("\n")}`;
|
|
|
948
1269
|
return append(asMsg("them", helpText(lang)));
|
|
949
1270
|
case "status": {
|
|
950
1271
|
const d = await fetchFriends();
|
|
951
|
-
const pending = d.
|
|
1272
|
+
const pending = d.all.filter((f) => f.pending).length;
|
|
1273
|
+
const nodeCount = d.nodes.filter((n) => n.kind === "node").length;
|
|
1274
|
+
const agentCount = d.nodes.length - nodeCount;
|
|
952
1275
|
return append(asMsg("them", lang === "zh" ? `**\u72B6\u6001**
|
|
953
1276
|
|
|
954
1277
|
- \u597D\u53CB\uFF1A${d.friends.length}
|
|
1278
|
+
- \u670D\u52A1\u8282\u70B9\uFF1A${d.nodes.length}\uFF08\u8282\u70B9 ${nodeCount} \xB7 \u667A\u80FD\u4F53 ${agentCount}\uFF09\uFF0C\u7528 \`/nodes list\` \u67E5\u770B
|
|
955
1279
|
- \u5F85\u5BF9\u65B9\u63A5\u53D7\uFF1A${pending}
|
|
956
1280
|
- \u672C\u673A\u5386\u53F2\u8D77\u81EA\uFF1A${fmtDate(d.historySince, lang)}
|
|
957
1281
|
- \u7248\u672C\uFF1A${d.ver}` : `**Status**
|
|
958
1282
|
|
|
959
1283
|
- Friends: ${d.friends.length}
|
|
1284
|
+
- Service peers: ${d.nodes.length} (${nodeCount} node${nodeCount === 1 ? "" : "s"} \xB7 ${agentCount} agent${agentCount === 1 ? "" : "s"}) \u2014 see \`/nodes list\`
|
|
960
1285
|
- Waiting to be accepted: ${pending}
|
|
961
1286
|
- History on this device since: ${fmtDate(d.historySince, lang)}
|
|
962
1287
|
- Version: ${d.ver}`));
|
|
963
1288
|
}
|
|
1289
|
+
case "nodes-list":
|
|
964
1290
|
case "friends-list": {
|
|
1291
|
+
const nodesMode = cmd.name === "nodes-list";
|
|
965
1292
|
const { filters, silentDays, unknown } = parseFilters(cmd.args);
|
|
966
1293
|
if (unknown.length) {
|
|
967
1294
|
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
1295
|
}
|
|
969
1296
|
const d = await fetchFriends();
|
|
970
|
-
const
|
|
971
|
-
const
|
|
1297
|
+
const pool = nodesMode ? d.nodes : d.friends;
|
|
1298
|
+
const { matched, skippedRecent } = selectFriends(pool, filters, { now: d.now, silentDays });
|
|
1299
|
+
const all = nodesMode ? lang === "zh" ? `**\u5168\u90E8\u670D\u52A1\u8282\u70B9** \u2014 ${matched.length}` : `**All service peers** \u2014 ${matched.length}` : lang === "zh" ? `**\u5168\u90E8\u597D\u53CB** \u2014 ${matched.length}` : `**All friends** \u2014 ${matched.length}`;
|
|
1300
|
+
const title = filters.length ? `**${nodesMode ? (lang === "zh" ? "\u670D\u52A1\u8282\u70B9" : "service peers") + ": " : ""}${filters.join(" + ")}**${filters.includes("silent") ? ` (${silentDays}d)` : ""} \u2014 ${matched.length}` : all;
|
|
972
1301
|
return append(asMsg("them", listReply(matched, skippedRecent, d, title)));
|
|
973
1302
|
}
|
|
1303
|
+
case "nodes-remove":
|
|
974
1304
|
case "friends-remove": {
|
|
1305
|
+
const nodesMode = cmd.name === "nodes-remove";
|
|
975
1306
|
const { filters, silentDays, unknown } = parseFilters(cmd.args);
|
|
976
1307
|
if (unknown.length || !filters.length) {
|
|
977
1308
|
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
1309
|
}
|
|
979
1310
|
const d = await fetchFriends();
|
|
980
|
-
const { matched, skippedRecent } = selectFriends(d.friends, filters, { now: d.now, silentDays });
|
|
981
|
-
const
|
|
1311
|
+
const { matched, skippedRecent } = selectFriends(nodesMode ? d.nodes : d.friends, filters, { now: d.now, silentDays });
|
|
1312
|
+
const what = nodesMode ? lang === "zh" ? "\u4E2A\u670D\u52A1\u8282\u70B9" : `service peer${matched.length === 1 ? "" : "s"}` : lang === "zh" ? "\u4F4D\u597D\u53CB" : `friend${matched.length === 1 ? "" : "s"}`;
|
|
1313
|
+
const title = lang === "zh" ? `**\u5C06\u8981\u5220\u9664 ${matched.length} ${what}** \u2014 \u8BF7\u5148\u6838\u5BF9\u8FD9\u4EFD\u540D\u5355` : `**${matched.length} ${what} to remove** \u2014 check the list first`;
|
|
982
1314
|
return append(asMsg(
|
|
983
1315
|
"them",
|
|
984
1316
|
listReply(matched, skippedRecent, d, title),
|
|
@@ -990,7 +1322,7 @@ ${cands.map((c) => `- ${nameOf(c)}`).join("\n")}`;
|
|
|
990
1322
|
if (!q)
|
|
991
1323
|
return append(asMsg("them", lang === "zh" ? "\u7528\u6CD5\uFF1A`/find <\u6587\u5B57>`" : "Usage: `/find <text>`"));
|
|
992
1324
|
const d = await fetchFriends();
|
|
993
|
-
const rows = findFriends(d.friends, q);
|
|
1325
|
+
const rows = findFriends(d.friends.concat(d.nodes), q);
|
|
994
1326
|
const title = lang === "zh" ? `**"${q}"** \u2014 ${rows.length} \u4F4D` : `**"${q}"** \u2014 ${rows.length} found`;
|
|
995
1327
|
return append(asMsg("them", listReply(rows, [], d, title)));
|
|
996
1328
|
}
|
|
@@ -1000,7 +1332,7 @@ ${cands.map((c) => `- ${nameOf(c)}`).join("\n")}`;
|
|
|
1000
1332
|
if (!tok || !alias)
|
|
1001
1333
|
return append(asMsg("them", lang === "zh" ? "\u7528\u6CD5\uFF1A`/rename <\u597D\u53CB> <\u65B0\u540D\u5B57>`" : "Usage: `/rename <friend> <new name>`"));
|
|
1002
1334
|
const d = await fetchFriends();
|
|
1003
|
-
const { match, candidates } = resolveFriend(d.friends, tok);
|
|
1335
|
+
const { match, candidates } = resolveFriend(d.friends.concat(d.nodes), tok);
|
|
1004
1336
|
if (!match)
|
|
1005
1337
|
return append(asMsg("them", candidates.length ? ambiguous(candidates) : notFound(tok)));
|
|
1006
1338
|
const was = nameOf(match);
|
|
@@ -1015,7 +1347,7 @@ ${cands.map((c) => `- ${nameOf(c)}`).join("\n")}`;
|
|
|
1015
1347
|
if (!tok)
|
|
1016
1348
|
return append(asMsg("them", `${lang === "zh" ? "\u7528\u6CD5" : "Usage"}: \`/${cmd.name} <friend>\``));
|
|
1017
1349
|
const d = await fetchFriends();
|
|
1018
|
-
const { match, candidates } = resolveFriend(d.friends, tok);
|
|
1350
|
+
const { match, candidates } = resolveFriend(d.friends.concat(d.nodes), tok);
|
|
1019
1351
|
if (!match)
|
|
1020
1352
|
return append(asMsg("them", candidates.length ? ambiguous(candidates) : notFound(tok)));
|
|
1021
1353
|
const isMuted = (muted || []).includes(match.userid);
|
|
@@ -1065,6 +1397,8 @@ ${cands.map((c) => `- ${nameOf(c)}`).join("\n")}`;
|
|
|
1065
1397
|
}
|
|
1066
1398
|
|
|
1067
1399
|
// src/chat.jsx
|
|
1400
|
+
var DK_CMD_HIST_KEY = "beagle.assistant.history";
|
|
1401
|
+
var DK_CMD_HIST_MAX = 50;
|
|
1068
1402
|
function RequestsBlock({ T, requests, onAct }) {
|
|
1069
1403
|
const [acted, setActed] = React.useState({});
|
|
1070
1404
|
const [failed, setFailed] = React.useState({});
|
|
@@ -2466,6 +2800,59 @@ function Conversation({ T, peer, lang, peers, onOpenChat, thread: threadProp, on
|
|
|
2466
2800
|
setSelMode(false);
|
|
2467
2801
|
};
|
|
2468
2802
|
const [draft, setDraft] = React.useState("");
|
|
2803
|
+
const [hist, setHist] = React.useState([]);
|
|
2804
|
+
const histIdx = React.useRef(null);
|
|
2805
|
+
const histStash = React.useRef("");
|
|
2806
|
+
React.useEffect(() => {
|
|
2807
|
+
histIdx.current = null;
|
|
2808
|
+
histStash.current = "";
|
|
2809
|
+
if (!peer.app) {
|
|
2810
|
+
setHist([]);
|
|
2811
|
+
return;
|
|
2812
|
+
}
|
|
2813
|
+
try {
|
|
2814
|
+
setHist(JSON.parse(localStorage.getItem(DK_CMD_HIST_KEY) || "[]"));
|
|
2815
|
+
} catch (e) {
|
|
2816
|
+
setHist([]);
|
|
2817
|
+
}
|
|
2818
|
+
}, [peer.id, peer.app]);
|
|
2819
|
+
const rememberCommand = (text) => {
|
|
2820
|
+
setHist((h) => {
|
|
2821
|
+
const next = (h[h.length - 1] === text ? h : h.concat(text)).slice(-DK_CMD_HIST_MAX);
|
|
2822
|
+
if (peer.app) {
|
|
2823
|
+
try {
|
|
2824
|
+
localStorage.setItem(DK_CMD_HIST_KEY, JSON.stringify(next));
|
|
2825
|
+
} catch (e) {
|
|
2826
|
+
}
|
|
2827
|
+
}
|
|
2828
|
+
return next;
|
|
2829
|
+
});
|
|
2830
|
+
histIdx.current = null;
|
|
2831
|
+
histStash.current = "";
|
|
2832
|
+
};
|
|
2833
|
+
const walkHistory = (dir) => {
|
|
2834
|
+
if (!hist.length)
|
|
2835
|
+
return false;
|
|
2836
|
+
let i = histIdx.current;
|
|
2837
|
+
if (i === null) {
|
|
2838
|
+
if (dir > 0)
|
|
2839
|
+
return false;
|
|
2840
|
+
histStash.current = draft;
|
|
2841
|
+
i = hist.length - 1;
|
|
2842
|
+
} else {
|
|
2843
|
+
i += dir;
|
|
2844
|
+
if (i >= hist.length) {
|
|
2845
|
+
histIdx.current = null;
|
|
2846
|
+
setDraft(histStash.current);
|
|
2847
|
+
return true;
|
|
2848
|
+
}
|
|
2849
|
+
if (i < 0)
|
|
2850
|
+
i = 0;
|
|
2851
|
+
}
|
|
2852
|
+
histIdx.current = i;
|
|
2853
|
+
setDraft(hist[i]);
|
|
2854
|
+
return true;
|
|
2855
|
+
};
|
|
2469
2856
|
const [dragOver, setDragOver] = React.useState(false);
|
|
2470
2857
|
const [sending, setSending] = React.useState(null);
|
|
2471
2858
|
const [theater, setTheater] = React.useState(null);
|
|
@@ -2488,6 +2875,7 @@ function Conversation({ T, peer, lang, peers, onOpenChat, thread: threadProp, on
|
|
|
2488
2875
|
const sendDraft = () => {
|
|
2489
2876
|
if (draft.trim()) {
|
|
2490
2877
|
followScrollRef.current = true;
|
|
2878
|
+
rememberCommand(draft.trim());
|
|
2491
2879
|
onSend(draft);
|
|
2492
2880
|
setDraft("");
|
|
2493
2881
|
requestAnimationFrame(scrollToBottom);
|
|
@@ -2673,11 +3061,19 @@ ${peer.address}`
|
|
|
2673
3061
|
"input",
|
|
2674
3062
|
{
|
|
2675
3063
|
value: draft,
|
|
2676
|
-
onChange: (e) =>
|
|
3064
|
+
onChange: (e) => {
|
|
3065
|
+
histIdx.current = null;
|
|
3066
|
+
setDraft(e.target.value);
|
|
3067
|
+
},
|
|
2677
3068
|
onKeyDown: (e) => {
|
|
2678
3069
|
if (e.key === "Enter" && !e.shiftKey) {
|
|
2679
3070
|
e.preventDefault();
|
|
2680
3071
|
sendDraft();
|
|
3072
|
+
return;
|
|
3073
|
+
}
|
|
3074
|
+
if (e.key === "ArrowUp" || e.key === "ArrowDown") {
|
|
3075
|
+
if (walkHistory(e.key === "ArrowUp" ? -1 : 1))
|
|
3076
|
+
e.preventDefault();
|
|
2681
3077
|
}
|
|
2682
3078
|
},
|
|
2683
3079
|
placeholder: phone ? `${T.message}\u2026` : `${T.message} ${peer.alias || shortKey(peer.userId, 6, 4)}\u2026`,
|
package/package.json
CHANGED