@decentnetwork/beagle-ui 0.2.24 → 0.2.26
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 +122 -28
- 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.26";
|
|
2
2
|
// src/icons.jsx
|
|
3
3
|
var ICON_PATHS = {
|
|
4
4
|
// ---- tab bar (the four must feel like one set) ----
|
|
@@ -650,6 +650,18 @@ var actions_default = {
|
|
|
650
650
|
confirm: "list",
|
|
651
651
|
scope: "assistant"
|
|
652
652
|
},
|
|
653
|
+
{
|
|
654
|
+
name: "nodes-refresh",
|
|
655
|
+
usage: "/nodes refresh",
|
|
656
|
+
summary: "Look for friends that are provably service peers (the shipped exit table, this machine's dora roster) and offer to reclassify them. Nothing changes until you tick and confirm.",
|
|
657
|
+
params: {
|
|
658
|
+
type: "object",
|
|
659
|
+
properties: {},
|
|
660
|
+
required: []
|
|
661
|
+
},
|
|
662
|
+
confirm: "list",
|
|
663
|
+
scope: "assistant"
|
|
664
|
+
},
|
|
653
665
|
{
|
|
654
666
|
name: "find",
|
|
655
667
|
usage: "/find <text>",
|
|
@@ -824,7 +836,8 @@ var actions_default = {
|
|
|
824
836
|
file: "service-peers.json",
|
|
825
837
|
loader: "loadServicePeers()",
|
|
826
838
|
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."
|
|
839
|
+
later: "A dora / ENS / bridge / directory service returns the same shape; the bundled file is v1.",
|
|
840
|
+
refresh: "/nodes refresh asks GET /api/assistant/service-candidates for evidence-backed candidates and writes the ticked ones to this machine's overlay (POST /api/assistant/service-peers, stored at <configDir>/service-peers-local.json). The shipped list is never edited at runtime; the overlay wins on the same userid."
|
|
828
841
|
}
|
|
829
842
|
};
|
|
830
843
|
|
|
@@ -983,6 +996,8 @@ function parseCommand(line) {
|
|
|
983
996
|
const sub = (parts[1] || "").toLowerCase();
|
|
984
997
|
if (sub === "list" || sub === "remove")
|
|
985
998
|
return { ok: true, name: `${head}-${sub}`, args: parts.slice(2) };
|
|
999
|
+
if (head === "nodes" && sub === "refresh")
|
|
1000
|
+
return { ok: true, name: "nodes-refresh", args: parts.slice(2) };
|
|
986
1001
|
return { ok: false, name: head, args: parts.slice(1), error: `${head}-subcommand` };
|
|
987
1002
|
}
|
|
988
1003
|
return { ok: true, name: head, args: parts.slice(1) };
|
|
@@ -1020,6 +1035,19 @@ function servicePeerMap(list) {
|
|
|
1020
1035
|
}
|
|
1021
1036
|
return map;
|
|
1022
1037
|
}
|
|
1038
|
+
function mergeServicePeers(bundled, local) {
|
|
1039
|
+
const byId = /* @__PURE__ */ new Map();
|
|
1040
|
+
for (const p of bundled && bundled.peers || [])
|
|
1041
|
+
if (p && p.userid)
|
|
1042
|
+
byId.set(p.userid, { ...p, source: "app" });
|
|
1043
|
+
for (const p of local && local.peers || [])
|
|
1044
|
+
if (p && p.userid)
|
|
1045
|
+
byId.set(p.userid, { ...p, source: "local" });
|
|
1046
|
+
return { version: 1, source: "merged", peers: [...byId.values()] };
|
|
1047
|
+
}
|
|
1048
|
+
function newCandidates(candidates, svcMap) {
|
|
1049
|
+
return (candidates != null ? candidates : []).filter((c) => c && c.userid && !(svcMap && svcMap.has(c.userid)));
|
|
1050
|
+
}
|
|
1023
1051
|
function partitionPeers(friends, svcMap) {
|
|
1024
1052
|
const people = [];
|
|
1025
1053
|
const nodes = [];
|
|
@@ -1104,8 +1132,13 @@ function resolveFriend(friends, token) {
|
|
|
1104
1132
|
}
|
|
1105
1133
|
|
|
1106
1134
|
// src/assistant.jsx
|
|
1107
|
-
function loadServicePeers() {
|
|
1108
|
-
|
|
1135
|
+
async function loadServicePeers() {
|
|
1136
|
+
try {
|
|
1137
|
+
const local = await fetch("/api/assistant/service-peers").then((r) => r.json());
|
|
1138
|
+
return mergeServicePeers(service_peers_default, local);
|
|
1139
|
+
} catch (e) {
|
|
1140
|
+
return service_peers_default;
|
|
1141
|
+
}
|
|
1109
1142
|
}
|
|
1110
1143
|
var ASSISTANT_PEER = {
|
|
1111
1144
|
id: ASSISTANT_ID,
|
|
@@ -1161,15 +1194,18 @@ function helpText(lang) {
|
|
|
1161
1194
|
const tail = lang === "zh" ? `\u7B5B\u9009\u6761\u4EF6\uFF08\u53EF\u7EC4\u5408\uFF0C\u53D6\u5E76\u96C6\uFF09\uFF1A
|
|
1162
1195
|
${filters}
|
|
1163
1196
|
|
|
1164
|
-
|
|
1197
|
+
\`/rename\`\u3001\`/mute\`\u3001\`/find\` \u5BF9\u597D\u53CB\u548C\u670D\u52A1\u8282\u70B9\u90FD\u9002\u7528\uFF1B\u53EA\u6709\u5217\u8868\u548C\u5220\u9664\u5206\u6210\u4E24\u8FB9\u3002
|
|
1198
|
+
\u6700\u8FD1 ${RECENT_DAYS} \u5929\u5185\u6DFB\u52A0\u7684\u597D\u53CB\u4E0D\u4F1A\u88AB"\u4ECE\u672A/\u6C89\u9ED8"\u7C7B\u7B5B\u9009\u9009\u4E2D\uFF0C\u5E76\u4F1A\u5217\u51FA\u88AB\u8DF3\u8FC7\u7684\u540D\u5B57\u3002\u5220\u9664\u6C38\u8FDC\u9700\u8981\u5148\u786E\u8BA4\uFF0C\u53D6\u6D88\u52FE\u9009\u5373\u53EF\u4FDD\u7559\u3002` : `Filters (combine them; a friend matching any one is listed):
|
|
1165
1199
|
${filters}
|
|
1166
1200
|
|
|
1167
|
-
|
|
1201
|
+
\`/rename\`, \`/mute\` and \`/find\` work on friends and service peers alike \u2014 only listing and removing are split in two.
|
|
1202
|
+
Friends added in the last ${RECENT_DAYS} days are never matched by the "never/silent" filters, and the ones held back are named. Removal always asks first: untick anyone you want to keep.`;
|
|
1168
1203
|
return `${intro}
|
|
1169
1204
|
${cmds}
|
|
1170
1205
|
|
|
1171
1206
|
${tail}`;
|
|
1172
1207
|
}
|
|
1208
|
+
var SKIP_NAMES_MAX = 10;
|
|
1173
1209
|
var seq = 0;
|
|
1174
1210
|
var asMsg = (from, text, extra) => Object.assign({
|
|
1175
1211
|
id: `as-${Date.now()}-${seq++}`,
|
|
@@ -1177,8 +1213,9 @@ var asMsg = (from, text, extra) => Object.assign({
|
|
|
1177
1213
|
time: (/* @__PURE__ */ new Date()).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }),
|
|
1178
1214
|
text
|
|
1179
1215
|
}, extra);
|
|
1180
|
-
function AssistantPanel({ m, T, lang, onRemove }) {
|
|
1216
|
+
function AssistantPanel({ m, T, lang, onRemove, onAdopt }) {
|
|
1181
1217
|
const rows = m.assistant.rows || [];
|
|
1218
|
+
const adopt = m.assistant.kind === "adopt";
|
|
1182
1219
|
const [sel, setSel] = React.useState(() => new Set(rows.map((r) => r.userid)));
|
|
1183
1220
|
const [busy, setBusy] = React.useState(false);
|
|
1184
1221
|
const [done, setDone] = React.useState(null);
|
|
@@ -1189,7 +1226,7 @@ function AssistantPanel({ m, T, lang, onRemove }) {
|
|
|
1189
1226
|
n.has(id) ? n.delete(id) : n.add(id);
|
|
1190
1227
|
return n;
|
|
1191
1228
|
});
|
|
1192
|
-
const label = lang === "zh" ? `\u5220\u9664 ${sel.size} \u4F4D` : `Remove ${sel.size}`;
|
|
1229
|
+
const label = adopt ? lang === "zh" ? `\u52A0\u5165 ${sel.size} \u4E2A` : `Add ${sel.size} as ${sel.size === 1 ? "a node" : "nodes"}` : lang === "zh" ? `\u5220\u9664 ${sel.size} \u4F4D` : `Remove ${sel.size}`;
|
|
1193
1230
|
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: {
|
|
1194
1231
|
display: "flex",
|
|
1195
1232
|
alignItems: "center",
|
|
@@ -1206,12 +1243,14 @@ function AssistantPanel({ m, T, lang, onRemove }) {
|
|
|
1206
1243
|
disabled: !!done || busy,
|
|
1207
1244
|
onChange: () => toggle(r.userid)
|
|
1208
1245
|
}
|
|
1209
|
-
), /* @__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: () => {
|
|
1246
|
+
), /* @__PURE__ */ React.createElement("span", { style: { flex: 1 } }, nameOf(r), r.why && /* @__PURE__ */ React.createElement("span", { style: { color: "var(--faint)", fontSize: 11.5 } }, ` \u2014 ${r.why}`)), /* @__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: adopt ? "solid" : "danger", disabled: busy || !sel.size, onClick: () => {
|
|
1210
1247
|
setBusy(true);
|
|
1211
|
-
|
|
1212
|
-
|
|
1248
|
+
const picked = rows.filter((r) => sel.has(r.userid));
|
|
1249
|
+
const run = adopt ? onAdopt(picked) : onRemove([...sel]);
|
|
1250
|
+
run.then((r) => {
|
|
1251
|
+
setDone(adopt ? lang === "zh" ? `\u5DF2\u52A0\u5165 ${r.added} \u4E2A\u670D\u52A1\u8282\u70B9` : `Added ${r.added} as service peers` : lang === "zh" ? `\u5DF2\u5220\u9664 ${r.removed} \u4F4D${r.failed ? `\uFF0C${r.failed} \u4F4D\u5931\u8D25` : ""}` : `Removed ${r.removed}${r.failed ? `, ${r.failed} failed` : ""}`);
|
|
1213
1252
|
}).finally(() => setBusy(false));
|
|
1214
|
-
} }, 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"))));
|
|
1253
|
+
} }, busy ? T.working || "Working\u2026" : label), /* @__PURE__ */ React.createElement("span", { style: { fontSize: 12, color: "var(--faint)" } }, adopt ? lang === "zh" ? "\u53D6\u6D88\u52FE\u9009\u5373\u4FDD\u6301\u4E3A\u597D\u53CB" : "Untick any you want to keep as a friend" : lang === "zh" ? "\u53D6\u6D88\u52FE\u9009\u53EF\u4FDD\u7559" : "Untick anyone you want to keep"))));
|
|
1215
1254
|
}
|
|
1216
1255
|
function useAssistant({ lang, T, muted, onToggleMute, sound, flash, setAlert, onReloadFriends }) {
|
|
1217
1256
|
const [thread, setThread] = React.useState(() => [{ day: (/* @__PURE__ */ new Date()).toLocaleDateString() }, asMsg("them", helpText(lang))]);
|
|
@@ -1221,32 +1260,41 @@ function useAssistant({ lang, T, muted, onToggleMute, sound, flash, setAlert, on
|
|
|
1221
1260
|
}, [lang]);
|
|
1222
1261
|
const fetchFriends = async () => {
|
|
1223
1262
|
const r = await fetch("/api/assistant/friends").then((x) => x.json());
|
|
1224
|
-
const
|
|
1263
|
+
const svcMap = servicePeerMap(await loadServicePeers());
|
|
1264
|
+
const { people, nodes } = partitionPeers(r.friends || [], svcMap);
|
|
1225
1265
|
return {
|
|
1226
1266
|
friends: people,
|
|
1227
1267
|
nodes,
|
|
1228
1268
|
all: r.friends || [],
|
|
1269
|
+
svcMap,
|
|
1229
1270
|
historySince: r.historySince || null,
|
|
1230
1271
|
now: r.now || Date.now(),
|
|
1231
1272
|
ver: r.beagleVer || ""
|
|
1232
1273
|
};
|
|
1233
1274
|
};
|
|
1275
|
+
const skipLine = (skipped) => {
|
|
1276
|
+
if (!skipped.length)
|
|
1277
|
+
return "";
|
|
1278
|
+
const shown = skipped.slice(0, SKIP_NAMES_MAX).map(nameOf);
|
|
1279
|
+
const more = skipped.length - shown.length;
|
|
1280
|
+
const names = shown.join(", ") + (more > 0 ? lang === "zh" ? ` \u7B49 ${more} \u4F4D` : `, and ${more} more` : "");
|
|
1281
|
+
return lang === "zh" ? `
|
|
1282
|
+
|
|
1283
|
+
*\u8DF3\u8FC7\u4E86 ${skipped.length} \u4F4D\u6700\u8FD1 ${RECENT_DAYS} \u5929\u5185\u6DFB\u52A0\u7684\u597D\u53CB\uFF1A${names}\u3002*` : `
|
|
1284
|
+
|
|
1285
|
+
*Skipped ${skipped.length} added in the last ${RECENT_DAYS} days: ${names}.*`;
|
|
1286
|
+
};
|
|
1234
1287
|
const listReply = (rows, skipped, data, title) => {
|
|
1235
1288
|
if (!rows.length) {
|
|
1236
1289
|
return `${title}
|
|
1237
1290
|
|
|
1238
|
-
${lang === "zh" ? "\u6CA1\u6709\u7B26\u5408\u7684\u597D\u53CB\u3002" : "No friends match."}
|
|
1291
|
+
${lang === "zh" ? "\u6CA1\u6709\u7B26\u5408\u7684\u597D\u53CB\u3002" : "No friends match."}${skipLine(skipped)}
|
|
1239
1292
|
|
|
1240
1293
|
${sinceLine(data.historySince, lang)}`;
|
|
1241
1294
|
}
|
|
1242
|
-
const skip = skipped.length ? lang === "zh" ? `
|
|
1243
|
-
|
|
1244
|
-
*\u8DF3\u8FC7\u4E86 ${skipped.length} \u4F4D\u6700\u8FD1 ${RECENT_DAYS} \u5929\u5185\u6DFB\u52A0\u7684\u597D\u53CB\u3002*` : `
|
|
1245
|
-
|
|
1246
|
-
*${skipped.length} friend${skipped.length > 1 ? "s" : ""} added in the last ${RECENT_DAYS} days ${skipped.length > 1 ? "were" : "was"} skipped.*` : "";
|
|
1247
1295
|
return `${title}
|
|
1248
1296
|
|
|
1249
|
-
${friendTable(rows, data.now, lang)}${
|
|
1297
|
+
${friendTable(rows, data.now, lang)}${skipLine(skipped)}
|
|
1250
1298
|
|
|
1251
1299
|
${sinceLine(data.historySince, lang)}`;
|
|
1252
1300
|
};
|
|
@@ -1285,6 +1333,33 @@ ${cands.map((c) => `- ${nameOf(c)}`).join("\n")}`;
|
|
|
1285
1333
|
- Waiting to be accepted: ${pending}
|
|
1286
1334
|
- History on this device since: ${fmtDate(d.historySince, lang)}
|
|
1287
1335
|
- Version: ${d.ver}`));
|
|
1336
|
+
}
|
|
1337
|
+
case "nodes-refresh": {
|
|
1338
|
+
const d = await fetchFriends();
|
|
1339
|
+
const r = await fetch("/api/assistant/service-candidates").then((x) => x.json());
|
|
1340
|
+
const fresh = newCandidates(r.candidates || [], d.svcMap);
|
|
1341
|
+
const src = r.sources || {};
|
|
1342
|
+
const where = lang === "zh" ? `\u67E5\u8BE2\u6765\u6E90\uFF1A\u5185\u7F6E\u51FA\u53E3\u8868\uFF08${src.exits || 0} \u6761\uFF09${src.dora == null ? "\uFF0C\u672C\u673A\u4E0D\u662F dora" : `\uFF0C\u672C\u673A dora \u540D\u518C\uFF08${src.dora} \u6761\uFF09`}` : `Looked at: the shipped exit table (${src.exits || 0})${src.dora == null ? ", and this machine is not a dora" : `, and this machine's dora roster (${src.dora})`}`;
|
|
1343
|
+
if (!fresh.length) {
|
|
1344
|
+
return append(asMsg("them", lang === "zh" ? `**\u6CA1\u6709\u65B0\u7684\u670D\u52A1\u8282\u70B9**
|
|
1345
|
+
|
|
1346
|
+
\u73B0\u6709 ${d.nodes.length} \u4E2A\u5DF2\u8BC6\u522B\u3002${where}\u3002
|
|
1347
|
+
|
|
1348
|
+
*\u53EA\u91C7\u7528\u8BC1\u636E\uFF0C\u4E0D\u9760\u540D\u5B57\u731C\u6D4B\u3002*` : `**Nothing new**
|
|
1349
|
+
|
|
1350
|
+
${d.nodes.length} service peers are already classified. ${where}.
|
|
1351
|
+
|
|
1352
|
+
*Evidence only \u2014 names are never guessed from.*`));
|
|
1353
|
+
}
|
|
1354
|
+
const title = lang === "zh" ? `**\u53D1\u73B0 ${fresh.length} \u4E2A\u53EF\u4EE5\u6807\u4E3A\u670D\u52A1\u8282\u70B9\u7684\u597D\u53CB** \u2014 \u52FE\u9009\u540E\u624D\u4F1A\u751F\u6548
|
|
1355
|
+
|
|
1356
|
+
${where}\u3002` : `**${fresh.length === 1 ? "1 friend looks" : `${fresh.length} friends look`} like service peers** \u2014 nothing changes until you tick and confirm
|
|
1357
|
+
|
|
1358
|
+
${where}.`;
|
|
1359
|
+
const lines = fresh.map((c) => `- **${c.name}** \`${c.kind}${c.role ? " \xB7 " + c.role : ""}\` \u2014 ${c.why}`).join("\n");
|
|
1360
|
+
return append(asMsg("them", `${title}
|
|
1361
|
+
|
|
1362
|
+
${lines}`, { assistant: { kind: "adopt", rows: fresh } }));
|
|
1288
1363
|
}
|
|
1289
1364
|
case "nodes-list":
|
|
1290
1365
|
case "friends-list": {
|
|
@@ -1374,6 +1449,24 @@ ${cands.map((c) => `- ${nameOf(c)}`).join("\n")}`;
|
|
|
1374
1449
|
return append(asMsg("them", lang === "zh" ? `\u51FA\u9519\u4E86\uFF1A${e && e.message || e}` : `That failed: ${e && e.message || e}`));
|
|
1375
1450
|
}
|
|
1376
1451
|
}, [append, lang, muted, onToggleMute, setAlert, onReloadFriends]);
|
|
1452
|
+
const adoptServicePeers = React.useCallback(async (rows) => {
|
|
1453
|
+
const add = rows.map((r) => ({ userid: r.userid, name: r.name, kind: r.kind, role: r.role }));
|
|
1454
|
+
let added = 0;
|
|
1455
|
+
try {
|
|
1456
|
+
const r = await fetch("/api/assistant/service-peers", {
|
|
1457
|
+
method: "POST",
|
|
1458
|
+
headers: { "content-type": "application/json" },
|
|
1459
|
+
body: JSON.stringify({ add })
|
|
1460
|
+
}).then((x) => x.json());
|
|
1461
|
+
added = r && r.ok ? add.length : 0;
|
|
1462
|
+
} catch (e) {
|
|
1463
|
+
added = 0;
|
|
1464
|
+
}
|
|
1465
|
+
if (onReloadFriends)
|
|
1466
|
+
onReloadFriends();
|
|
1467
|
+
append(asMsg("them", added ? lang === "zh" ? `\u5DF2\u628A ${added} \u4F4D\u6807\u4E3A\u670D\u52A1\u8282\u70B9\uFF0C\u5B83\u4EEC\u4E0D\u518D\u51FA\u73B0\u5728 \`/friends\` \u4E2D\u3002` : `${added} now count as service peers \u2014 they no longer appear in \`/friends\`.` : lang === "zh" ? "\u5199\u5165\u5931\u8D25\uFF0C\u6CA1\u6709\u4EFB\u4F55\u6539\u52A8\u3002" : "That write failed; nothing changed."));
|
|
1468
|
+
return { added };
|
|
1469
|
+
}, [append, lang, onReloadFriends]);
|
|
1377
1470
|
const removeFriends = React.useCallback(async (ids) => {
|
|
1378
1471
|
let removed = 0;
|
|
1379
1472
|
const failed = [];
|
|
@@ -1393,7 +1486,7 @@ ${cands.map((c) => `- ${nameOf(c)}`).join("\n")}`;
|
|
|
1393
1486
|
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"}.`));
|
|
1394
1487
|
return { removed, failed: failed.length };
|
|
1395
1488
|
}, [append, lang, onReloadFriends]);
|
|
1396
|
-
return { thread, handle, removeFriends };
|
|
1489
|
+
return { thread, handle, removeFriends, adoptServicePeers };
|
|
1397
1490
|
}
|
|
1398
1491
|
|
|
1399
1492
|
// src/chat.jsx
|
|
@@ -2202,7 +2295,7 @@ function DkChatForm({ form, submitted, peer, onSubmit }) {
|
|
|
2202
2295
|
return null;
|
|
2203
2296
|
}), err && /* @__PURE__ */ React.createElement("div", { style: { color: "var(--danger, #ff6b6b)", fontSize: 11.5, fontFamily: "var(--mono)" } }, err));
|
|
2204
2297
|
}
|
|
2205
|
-
function Msg({ m, peer, T, lang, onTheater, onDelete, onCancel, onRetry, onRetryMsg, onReveal, onOpenFile, onCall, onFormSubmit, onNameCard, onAssistantRemove, answered, selMode, selected, onToggleSel, busy, isGroup }) {
|
|
2298
|
+
function Msg({ m, peer, T, lang, onTheater, onDelete, onCancel, onRetry, onRetryMsg, onReveal, onOpenFile, onCall, onFormSubmit, onNameCard, onAssistantRemove, onAssistantAdopt, answered, selMode, selected, onToggleSel, busy, isGroup }) {
|
|
2206
2299
|
const mine = m.from === "me";
|
|
2207
2300
|
const nativeMedia = !m.file ? dkNativeMediaFromText(m.text) : null;
|
|
2208
2301
|
const grp = isGroup && m.dir !== "out" && !m.file && !nativeMedia ? dkGroupParse(m.text) : null;
|
|
@@ -2531,7 +2624,7 @@ function Msg({ m, peer, T, lang, onTheater, onDelete, onCancel, onRetry, onRetry
|
|
|
2531
2624
|
letterSpacing: -0.1,
|
|
2532
2625
|
wordBreak: "break-word"
|
|
2533
2626
|
} }, /* @__PURE__ */ React.createElement(MarkdownText, { text: read.text, onNameCard })));
|
|
2534
|
-
})(), 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), (() => {
|
|
2627
|
+
})(), 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, onAdopt: onAssistantAdopt }), /* @__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), (() => {
|
|
2535
2628
|
const sentDone = mine && m.status && m.status !== "queued" && m.status !== "sending" && m.status !== "failed";
|
|
2536
2629
|
const via = !mine ? m.via : sentDone ? m.via === "offline" ? "offline" : "online" : null;
|
|
2537
2630
|
if (!via)
|
|
@@ -2576,7 +2669,7 @@ function Msg({ m, peer, T, lang, onTheater, onDelete, onCancel, onRetry, onRetry
|
|
|
2576
2669
|
) : 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)" })))
|
|
2577
2670
|
);
|
|
2578
2671
|
}
|
|
2579
|
-
function Conversation({ T, peer, lang, peers, onOpenChat, thread: threadProp, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread, hasEarlier, onLoadEarlier, onAssistantRemove, muted, onToggleMute, phone, onBack }) {
|
|
2672
|
+
function Conversation({ T, peer, lang, peers, onOpenChat, thread: threadProp, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread, hasEarlier, onLoadEarlier, onAssistantRemove, onAssistantAdopt, muted, onToggleMute, phone, onBack }) {
|
|
2580
2673
|
const scrollRef = React.useRef(null);
|
|
2581
2674
|
const prependRef = React.useRef(null);
|
|
2582
2675
|
const [earlierBusy, setEarlierBusy] = React.useState(false);
|
|
@@ -3009,6 +3102,7 @@ ${peer.address}`
|
|
|
3009
3102
|
isGroup,
|
|
3010
3103
|
onTheater: setTheater,
|
|
3011
3104
|
onAssistantRemove,
|
|
3105
|
+
onAssistantAdopt,
|
|
3012
3106
|
onDelete: (id) => doDelete([id]),
|
|
3013
3107
|
onCancel: doCancel,
|
|
3014
3108
|
onRetry: doRetry,
|
|
@@ -3168,12 +3262,12 @@ function MenuItem({ icon, label, onClick, danger }) {
|
|
|
3168
3262
|
function ChatEmpty({ T }) {
|
|
3169
3263
|
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));
|
|
3170
3264
|
}
|
|
3171
|
-
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 }) {
|
|
3265
|
+
function ChatTab({ T, lang, peers, requests, activeId, thread, onSelect, onAct, onAdd, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread, hasEarlier, onLoadEarlier, onAssistantRemove, onAssistantAdopt, prefillAddr, onPrefillConsumed, muted, onToggleMute, phone, onBack }) {
|
|
3172
3266
|
const peer = peers.find((p) => p.id === activeId);
|
|
3173
3267
|
if (phone) {
|
|
3174
|
-
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 });
|
|
3268
|
+
return peer ? /* @__PURE__ */ React.createElement(Conversation, { T, peer, lang, peers, onOpenChat: onSelect, thread, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread, hasEarlier, onLoadEarlier, onAssistantRemove, onAssistantAdopt, 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 });
|
|
3175
3269
|
}
|
|
3176
|
-
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 }));
|
|
3270
|
+
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, onAssistantAdopt, muted: !!muted && muted.includes(peer.id), onToggleMute }) : /* @__PURE__ */ React.createElement(ChatEmpty, { T }));
|
|
3177
3271
|
}
|
|
3178
3272
|
Object.assign(window, { ChatTab });
|
|
3179
3273
|
|
|
@@ -6792,7 +6886,7 @@ function DkApp() {
|
|
|
6792
6886
|
fontWeight: 600,
|
|
6793
6887
|
background: tab === n.id ? "var(--accent)" : "transparent",
|
|
6794
6888
|
color: tab === n.id ? "#fff" : "var(--dim)"
|
|
6795
|
-
} }, /* @__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) => {
|
|
6889
|
+
} }, /* @__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, onAssistantAdopt: activeId === ASSISTANT_ID ? assistant.adoptServicePeers : 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) => {
|
|
6796
6890
|
data.refresh();
|
|
6797
6891
|
return r;
|
|
6798
6892
|
}) })), 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