@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.
Files changed (2) hide show
  1. package/dist/beagle-ui.js +122 -28
  2. package/package.json +1 -1
package/dist/beagle-ui.js CHANGED
@@ -1,4 +1,4 @@
1
- export const BEAGLE_UI_VERSION = "0.2.24";
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
- return service_peers_default;
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
- \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):
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
- Friends added in the last ${RECENT_DAYS} days are never matched by the "never/silent" filters. Removal always asks first.`;
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
- onRemove([...sel]).then((r) => {
1212
- 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` : ""}`);
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 { people, nodes } = partitionPeers(r.friends || [], servicePeerMap(loadServicePeers()));
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)}${skip}
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/beagle-ui",
3
- "version": "0.2.24",
3
+ "version": "0.2.26",
4
4
  "description": "The Beagle chat UI, minus the machine it runs on — shared by the desktop install and the browser build.",
5
5
  "license": "MIT",
6
6
  "type": "module",