@kevin5251984/guild 0.2.12 → 0.2.14

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.
@@ -8,7 +8,7 @@
8
8
  <link rel="icon" href="/favicon-32.png" type="image/png" sizes="32x32" />
9
9
  <link rel="icon" href="/favicon-16.png" type="image/png" sizes="16x16" />
10
10
  <title>Guild — 大廳</title>
11
- <link rel="stylesheet" href="/chat.css?v=stack-clip" />
11
+ <link rel="stylesheet" href="/chat.css?v=press-enamel" />
12
12
  <script src="/i18n.js"></script>
13
13
  <script src="/md.js"></script>
14
14
  </head>
@@ -192,10 +192,9 @@
192
192
  <div class="traj-bar">
193
193
  <strong data-i18n="trace">Trajectory</strong>
194
194
  <span class="traj-src" id="traj-src"></span>
195
- <input id="traj-q" placeholder="搜尋 system / tool / spawn / 結果" data-i18n-placeholder="search.traj" autocomplete="off" />
195
+ <input id="traj-q" placeholder="搜尋 system / tool / 結果" data-i18n-placeholder="search.traj" autocomplete="off" />
196
196
  <button type="button" class="traj-close" id="traj-close" aria-label="關閉" data-i18n-aria="close">×</button>
197
197
  </div>
198
- <div class="traj-kinds" id="traj-kinds"></div>
199
198
  <div class="traj-main">
200
199
  <div class="traj-list" id="traj-list"></div>
201
200
  <div class="traj-detail" id="traj-detail"></div>
@@ -342,26 +341,6 @@
342
341
  const glyph = Array.from(String(name).replace(/\s+/g, ""))[0] || "?";
343
342
  return glyph;
344
343
  }
345
- function portraitUrl(bot) {
346
- const p = bot && typeof bot.portrait === "string" ? bot.portrait.trim() : "";
347
- return p.indexOf("/generated/") === 0 ? p : "";
348
- }
349
- function avatarFace(bot, name) {
350
- const url = portraitUrl(bot);
351
- if (url) return '<img alt="" src="' + escapeHtml(url) + '">';
352
- return escapeHtml(initials((bot && bot.name) || name || "?"));
353
- }
354
- function avatarClass(bot, extra) {
355
- return (extra || "avatar") + (portraitUrl(bot) ? " has-photo" : "");
356
- }
357
- function paintAvatarEl(el, bot) {
358
- if (!el) return;
359
- const url = portraitUrl(bot);
360
- el.classList.toggle("has-photo", Boolean(url));
361
- el.style.background = bot ? hue(bot.handle) : "#333";
362
- if (url) el.innerHTML = '<img alt="" src="' + escapeHtml(url) + '">';
363
- else el.textContent = bot ? initials(bot.name) : "?";
364
- }
365
344
  function escapeHtml(value) {
366
345
  return String(value)
367
346
  .replace(/&/g, "&amp;")
@@ -410,7 +389,7 @@
410
389
  if (name === "read") return t("read");
411
390
  if (name === "write") return t("write");
412
391
  if (name === "list") return t("list");
413
- if (name === "spawn" || name === "read_spawn") return t("spawn");
392
+ if (name === "spawn") return t("spawn");
414
393
  return name || "Tool";
415
394
  }
416
395
  function parseExitCode(output) {
@@ -566,7 +545,6 @@
566
545
  usage.input || usage.output
567
546
  ? t("stats.inOut", {
568
547
  in: formatCount(usage.input || 0),
569
- cache: formatCount(usage.cacheRead || 0),
570
548
  out: formatCount(usage.output || 0),
571
549
  })
572
550
  : usage.estimated
@@ -712,7 +690,7 @@
712
690
  error: err,
713
691
  });
714
692
  }
715
- if (part.name === "spawn" || part.name === "read_spawn") {
693
+ if (part.name === "spawn") {
716
694
  return disclosureRow({
717
695
  title: t("spawn"),
718
696
  icon: iconWrench(),
@@ -1312,12 +1290,10 @@
1312
1290
  const stack = bots
1313
1291
  .map(
1314
1292
  (bot) =>
1315
- '<span class="' +
1316
- avatarClass(bot) +
1317
- '" style="background:' +
1293
+ '<span class="avatar" style="background:' +
1318
1294
  hue(bot.handle) +
1319
1295
  '">' +
1320
- avatarFace(bot) +
1296
+ escapeHtml(initials(bot.name)) +
1321
1297
  "</span>",
1322
1298
  )
1323
1299
  .join("");
@@ -1412,12 +1388,10 @@
1412
1388
  href: "#d/" + encodeURIComponent(bot.id),
1413
1389
  on: state.kind === "dm" && state.id === bot.id,
1414
1390
  av:
1415
- '<span class="' +
1416
- avatarClass(bot) +
1417
- '" style="background:' +
1391
+ '<span class="avatar" style="background:' +
1418
1392
  hue(bot.handle) +
1419
1393
  '">' +
1420
- avatarFace(bot) +
1394
+ escapeHtml(initials(bot.name)) +
1421
1395
  "</span>",
1422
1396
  name: bot.name,
1423
1397
  updatedAt: bot.updatedAt,
@@ -1447,14 +1421,12 @@
1447
1421
  ? '<span class="meta">' + t("members.addDisabled") + "</span>"
1448
1422
  : "";
1449
1423
  return (
1450
- '<div class="members-row"><button type="button" class="' +
1451
- avatarClass(bot, "avatar sm") +
1452
- '" data-bot-id="' +
1424
+ '<div class="members-row"><button type="button" class="avatar sm" data-bot-id="' +
1453
1425
  escapeHtml(bot.id) +
1454
1426
  '" style="background:' +
1455
1427
  hue(bot.handle) +
1456
1428
  '">' +
1457
- avatarFace(bot) +
1429
+ escapeHtml(initials(bot.name)) +
1458
1430
  '</button><span class="who"><strong>' +
1459
1431
  escapeHtml(bot.name) +
1460
1432
  "</strong><em>@" +
@@ -1473,8 +1445,9 @@
1473
1445
  document.getElementById("subtitle").textContent = "";
1474
1446
  wrap.hidden = true;
1475
1447
  document.getElementById("members-pop").hidden = true;
1448
+ av.textContent = bot ? initials(bot.name) : "?";
1449
+ av.style.background = bot ? hue(bot.handle) : "#333";
1476
1450
  av.classList.remove("hash-av");
1477
- paintAvatarEl(av, bot);
1478
1451
  if (bot) {
1479
1452
  av.setAttribute("href", "#");
1480
1453
  av.setAttribute("data-bot-id", bot.id);
@@ -1494,8 +1467,6 @@
1494
1467
  const members = ch ? ch.members || [] : [];
1495
1468
  const locked = isGeneralChannel(ch);
1496
1469
  document.getElementById("subtitle").textContent = "";
1497
- av.classList.remove("has-photo");
1498
- av.innerHTML = "";
1499
1470
  av.textContent = "#";
1500
1471
  av.style.background = "";
1501
1472
  av.classList.add("hash-av");
@@ -1506,12 +1477,10 @@
1506
1477
  document.getElementById("members-stack").innerHTML = shown
1507
1478
  .map(
1508
1479
  (bot) =>
1509
- '<span class="' +
1510
- avatarClass(bot) +
1511
- '" style="background:' +
1480
+ '<span class="avatar" style="background:' +
1512
1481
  hue(bot.handle) +
1513
1482
  '">' +
1514
- avatarFace(bot) +
1483
+ escapeHtml(initials(bot.name)) +
1515
1484
  "</span>",
1516
1485
  )
1517
1486
  .join("");
@@ -1648,16 +1617,14 @@
1648
1617
  const botAttr = ' data-bot-id="' + escapeHtml(bot.id) + '"';
1649
1618
  return {
1650
1619
  av:
1651
- '<button type="button" class="' +
1652
- avatarClass(bot) +
1653
- '"' +
1620
+ '<button type="button" class="avatar"' +
1654
1621
  botAttr +
1655
1622
  ' title="' +
1656
1623
  escapeHtml(bot.name) +
1657
1624
  '" style="background:' +
1658
1625
  hue(bot.handle) +
1659
1626
  '">' +
1660
- avatarFace(bot) +
1627
+ escapeHtml(initials(bot.name)) +
1661
1628
  "</button>",
1662
1629
  head:
1663
1630
  '<div class="msg-head"><button type="button" class="name"' +
@@ -1944,9 +1911,8 @@
1944
1911
  fromBottom <= root.clientHeight + 80;
1945
1912
  const rows = threadMessages();
1946
1913
  if (!rows.length && !currentRoomBusy()) {
1947
- root.innerHTML = hallHasModel()
1948
- ? '<div class="empty-thread">' + t("noMessages") + "</div>"
1949
- : setupThreadHtml();
1914
+ root.innerHTML =
1915
+ '<div class="empty-thread">' + t("noMessages") + "</div>";
1950
1916
  syncThreadChrome();
1951
1917
  return;
1952
1918
  }
@@ -2047,27 +2013,16 @@
2047
2013
  const botAttr = bot
2048
2014
  ? ' data-bot-id="' + escapeHtml(bot.id) + '"'
2049
2015
  : "";
2050
- const av = bot
2051
- ? '<button type="button" class="' +
2052
- avatarClass(bot) +
2053
- '"' +
2054
- botAttr +
2055
- ' title="' +
2056
- escapeHtml(name) +
2057
- '" style="background:' +
2058
- hue(handle || name) +
2059
- '">' +
2060
- avatarFace(bot, name) +
2061
- "</button>"
2062
- : '<button type="button" class="avatar"' +
2063
- botAttr +
2064
- ' title="' +
2065
- escapeHtml(name) +
2066
- '" style="background:' +
2067
- hue(handle || name) +
2068
- '">' +
2069
- escapeHtml(initials(name)) +
2070
- "</button>";
2016
+ const av =
2017
+ '<button type="button" class="avatar"' +
2018
+ botAttr +
2019
+ ' title="' +
2020
+ escapeHtml(name) +
2021
+ '" style="background:' +
2022
+ hue(handle || name) +
2023
+ '">' +
2024
+ escapeHtml(initials(name)) +
2025
+ "</button>";
2071
2026
  const head =
2072
2027
  '<div class="msg-head"><button type="button" class="name"' +
2073
2028
  botAttr +
@@ -2300,12 +2255,10 @@
2300
2255
  bots
2301
2256
  .map(
2302
2257
  (bot) =>
2303
- '<span class="' +
2304
- avatarClass(bot, "queue-av") +
2305
- '" style="background:' +
2258
+ '<span class="queue-av" style="background:' +
2306
2259
  hue(bot.handle) +
2307
2260
  '">' +
2308
- avatarFace(bot) +
2261
+ escapeHtml(initials(bot.name)) +
2309
2262
  "</span>",
2310
2263
  )
2311
2264
  .join("") +
@@ -2876,7 +2829,8 @@
2876
2829
  }
2877
2830
  function showBotCard(bot, anchor) {
2878
2831
  if (!bot || !anchor) return;
2879
- paintAvatarEl(document.getElementById("bot-card-av"), bot);
2832
+ document.getElementById("bot-card-av").textContent = initials(bot.name);
2833
+ document.getElementById("bot-card-av").style.background = hue(bot.handle);
2880
2834
  document.getElementById("bot-card-name").textContent = bot.name;
2881
2835
  document.getElementById("bot-card-handle").textContent = "@" + bot.handle;
2882
2836
  document.getElementById("bot-card-bio").textContent =
@@ -4524,12 +4478,10 @@
4524
4478
  return (
4525
4479
  '<button type="button" data-assign="' +
4526
4480
  escapeHtml(bot.id) +
4527
- '"><span class="' +
4528
- avatarClass(bot, "avatar sm") +
4529
- '" style="background:' +
4481
+ '"><span class="avatar sm" style="background:' +
4530
4482
  hue(bot.handle) +
4531
4483
  '">' +
4532
- avatarFace(bot) +
4484
+ escapeHtml(initials(bot.name)) +
4533
4485
  '</span><span class="assign-copy"><strong>' +
4534
4486
  escapeHtml(bot.name) +
4535
4487
  "</strong><span>@" +
@@ -4757,7 +4709,6 @@
4757
4709
  name: bot.name,
4758
4710
  hint: "@" + bot.handle,
4759
4711
  glyph: initials(bot.name),
4760
- photo: portraitUrl(bot),
4761
4712
  color: hue(bot.handle),
4762
4713
  outside: false,
4763
4714
  })),
@@ -4769,7 +4720,6 @@
4769
4720
  ? t("members.full", { max: CHANNEL_ROSTER_CAP })
4770
4721
  : "@" + bot.handle,
4771
4722
  glyph: initials(bot.name),
4772
- photo: portraitUrl(bot),
4773
4723
  color: hue(bot.handle),
4774
4724
  outside: true,
4775
4725
  })),
@@ -4791,10 +4741,6 @@
4791
4741
  function pickerItemHtml(row, index, selected) {
4792
4742
  const on = index === selected ? " on" : "";
4793
4743
  const avStyle = row.color ? "background:" + row.color : "";
4794
- const photo = row.photo ? String(row.photo) : "";
4795
- const glyphInner = photo
4796
- ? '<img alt="" src="' + escapeHtml(photo) + '">'
4797
- : escapeHtml(row.glyph);
4798
4744
  const badge = row.badge
4799
4745
  ? '<span class="out">' + escapeHtml(row.badge) + "</span>"
4800
4746
  : row.outside
@@ -4805,12 +4751,10 @@
4805
4751
  on +
4806
4752
  '" data-mention="' +
4807
4753
  index +
4808
- '"><span class="mention-glyph' +
4809
- (photo ? " has-photo" : "") +
4810
- '" style="' +
4754
+ '"><span class="mention-glyph" style="' +
4811
4755
  avStyle +
4812
4756
  '">' +
4813
- glyphInner +
4757
+ escapeHtml(row.glyph) +
4814
4758
  "</span><span><strong>" +
4815
4759
  escapeHtml(row.name) +
4816
4760
  "</strong><em>" +
@@ -5210,31 +5154,6 @@
5210
5154
  if (kind === "oauth") return { mark: "⌁", tone: "" };
5211
5155
  return { mark: "●", tone: "" };
5212
5156
  }
5213
- function hallHasModel() {
5214
- return allModels().some((row) => row.ready);
5215
- }
5216
- function setupThreadHtml() {
5217
- return (
5218
- '<div class="empty-thread setup-thread">' +
5219
- '<p class="setup-kicker">' +
5220
- t("setup.kicker") +
5221
- "</p>" +
5222
- '<p class="setup-title">' +
5223
- t("setup.needModel") +
5224
- "</p>" +
5225
- '<p class="setup-body">' +
5226
- t("setup.needModelBody") +
5227
- "</p>" +
5228
- '<p class="setup-acts">' +
5229
- '<a href="/settings/subs">' +
5230
- t("setup.subs") +
5231
- "</a>" +
5232
- '<a href="/settings/keys">' +
5233
- t("setup.keys") +
5234
- "</a>" +
5235
- "</p></div>"
5236
- );
5237
- }
5238
5157
  function allModels() {
5239
5158
  const rows = [];
5240
5159
  for (const p of modelCfg.picker || []) {
@@ -5415,7 +5334,6 @@
5415
5334
  modelCfg = await fetch("/settings/models").then((r) => r.json());
5416
5335
  renderModelList();
5417
5336
  renderNav();
5418
- if (!(state.messages || []).length && !currentRoomBusy()) renderThread();
5419
5337
  }
5420
5338
  async function applyChatModel(ref) {
5421
5339
  if (state.kind !== "dm") return;
@@ -5520,7 +5438,6 @@
5520
5438
  let trajFollow = true;
5521
5439
  let trajLive = false;
5522
5440
  let trajFetch = 0;
5523
- let trajKind = "";
5524
5441
  function trajUrl() {
5525
5442
  return state.kind === "dm"
5526
5443
  ? "/dms/" + encodeURIComponent(state.id) + "/trajectory"
@@ -5547,62 +5464,6 @@
5547
5464
  if (bot) return bot.name + " @" + bot.handle;
5548
5465
  return event.botId;
5549
5466
  }
5550
- function trajKindLabel(kind) {
5551
- if (kind === "spawn") return t("spawn");
5552
- if (kind === "skill") return t("skill");
5553
- return String(kind || "").toUpperCase();
5554
- }
5555
- function trajKindAliases(kind) {
5556
- if (kind === "spawn") {
5557
- return "spawn subagent sub-agent sub agent 子代理";
5558
- }
5559
- return "";
5560
- }
5561
- function trajSpawnCount() {
5562
- return trajEvents.filter((event) => event.kind === "spawn").length;
5563
- }
5564
- function paintTrajSrc() {
5565
- const src = document.getElementById("traj-src");
5566
- const base = src.dataset.base || t("trace.log");
5567
- const n = trajSpawnCount();
5568
- const spawnBit = n ? " · " + n + " " + t("spawn") : "";
5569
- src.textContent =
5570
- (trajLive ? t("trace.live") + " · " : "") + base + spawnBit;
5571
- src.classList.toggle("live", trajLive);
5572
- }
5573
- function renderTrajKinds() {
5574
- const el = document.getElementById("traj-kinds");
5575
- const counts = {};
5576
- trajEvents.forEach((event) => {
5577
- counts[event.kind] = (counts[event.kind] || 0) + 1;
5578
- });
5579
- const keys = ["", "spawn", "tool", "skill", "thinking", "assistant"];
5580
- el.innerHTML = keys
5581
- .map((kind) => {
5582
- const n = kind ? counts[kind] || 0 : trajEvents.length;
5583
- const label = kind ? trajKindLabel(kind) : t("trace.filter.all");
5584
- return (
5585
- '<button type="button" class="traj-filter' +
5586
- (kind === "spawn" ? " spawn" : "") +
5587
- (trajKind === kind ? " on" : "") +
5588
- '" data-kind="' +
5589
- escapeHtml(kind) +
5590
- '">' +
5591
- escapeHtml(label) +
5592
- " " +
5593
- n +
5594
- "</button>"
5595
- );
5596
- })
5597
- .join("");
5598
- el.querySelectorAll("[data-kind]").forEach((btn) => {
5599
- btn.addEventListener("click", () => {
5600
- trajKind = btn.getAttribute("data-kind") || "";
5601
- trajFollow = true;
5602
- renderTrajList();
5603
- });
5604
- });
5605
- }
5606
5467
  function renderTrajDetail(event) {
5607
5468
  const el = document.getElementById("traj-detail");
5608
5469
  if (!event) {
@@ -5617,7 +5478,7 @@
5617
5478
  : "";
5618
5479
  el.innerHTML =
5619
5480
  "<h3>" +
5620
- escapeHtml(trajKindLabel(event.kind)) +
5481
+ escapeHtml(String(event.kind || "").toUpperCase()) +
5621
5482
  "</h3><p class='meta'>" +
5622
5483
  escapeHtml(
5623
5484
  [trajActorLabel(event), when, "turn " + (event.turnId || "")]
@@ -5638,15 +5499,11 @@
5638
5499
  : "");
5639
5500
  }
5640
5501
  function renderTrajList() {
5641
- renderTrajKinds();
5642
5502
  const q = document.getElementById("traj-q").value.trim().toLowerCase();
5643
5503
  const rows = trajEvents.filter((event) => {
5644
- if (trajKind && event.kind !== trajKind) return false;
5645
5504
  if (!q) return true;
5646
5505
  const hay = [
5647
5506
  event.kind,
5648
- trajKindLabel(event.kind),
5649
- trajKindAliases(event.kind),
5650
5507
  trajActor(event),
5651
5508
  trajActorLabel(event),
5652
5509
  event.botId || "",
@@ -5677,12 +5534,10 @@
5677
5534
  ? Array.from(String(bot.name || bot.handle || "?").trim())[0] || "?"
5678
5535
  : Array.from(t("you"))[0] || "?";
5679
5536
  const av = bot
5680
- ? '<span class="' +
5681
- avatarClass(bot, "traj-av") +
5682
- '" style="background:' +
5537
+ ? '<span class="traj-av" style="background:' +
5683
5538
  hue(bot.handle) +
5684
5539
  '">' +
5685
- (portraitUrl(bot) ? avatarFace(bot) : escapeHtml(glyph)) +
5540
+ escapeHtml(glyph) +
5686
5541
  "</span>"
5687
5542
  : '<span class="traj-av you">' + escapeHtml(glyph) + "</span>";
5688
5543
  return (
@@ -5699,7 +5554,7 @@
5699
5554
  '</span><span class="traj-kind ' +
5700
5555
  escapeHtml(event.kind) +
5701
5556
  '">' +
5702
- escapeHtml(trajKindLabel(event.kind)) +
5557
+ escapeHtml(String(event.kind || "").toUpperCase()) +
5703
5558
  '</span><span class="sum">' +
5704
5559
  escapeHtml(event.summary || "") +
5705
5560
  "</span></button>"
@@ -5734,7 +5589,10 @@
5734
5589
  trajLive = liveRows.length > 0;
5735
5590
  if (trajFollow) trajSel = Math.max(0, trajEvents.length - 1);
5736
5591
  else if (!trajEvents[trajSel]) trajSel = Math.max(0, trajEvents.length - 1);
5737
- paintTrajSrc();
5592
+ const src = document.getElementById("traj-src");
5593
+ const base = src.dataset.base || t("trace.log");
5594
+ src.textContent = trajLive ? t("trace.live") + " · " + base : base;
5595
+ src.classList.toggle("live", trajLive);
5738
5596
  renderTrajList();
5739
5597
  }
5740
5598
  async function refreshTraj(opts) {
@@ -5751,7 +5609,10 @@
5751
5609
  if (trajFollow) trajSel = Math.max(0, trajEvents.length - 1);
5752
5610
  else if (!trajEvents[trajSel]) trajSel = Math.max(0, trajEvents.length - 1);
5753
5611
  src.dataset.base = trajSourceLabel(body.source);
5754
- paintTrajSrc();
5612
+ src.textContent = trajLive
5613
+ ? t("trace.live") + " · " + src.dataset.base
5614
+ : src.dataset.base;
5615
+ src.classList.toggle("live", trajLive);
5755
5616
  renderTrajList();
5756
5617
  } catch (err) {
5757
5618
  if (gen !== trajFetch || trajEl.hidden) return;
@@ -5813,10 +5674,7 @@
5813
5674
  renderAttachSheet();
5814
5675
  }
5815
5676
  }
5816
- if (!trajEl.hidden) {
5817
- paintTrajSrc();
5818
- renderTrajList();
5819
- }
5677
+ if (!trajEl.hidden) renderTrajList();
5820
5678
  syncSendButton();
5821
5679
  });
5822
5680
  window.addEventListener("hashchange", () => {
@@ -24,7 +24,7 @@ var I18N_ROWS = [
24
24
  ["search.subagents", "搜尋子代理", "Search subagents"],
25
25
  ["search.mcp", "搜尋 MCP", "Search MCP"],
26
26
  ["search.models", "搜尋全部模型", "Search all models"],
27
- ["search.traj", "搜尋 system / tool / spawn / 子代理 / 結果", "Search system / tool / spawn / subagent / result"],
27
+ ["search.traj", "搜尋 system / tool / spawn / 結果", "Search system / tool / spawn / result"],
28
28
  ["channels", "委託", "Channels"],
29
29
  ["dms", "密談", "Whispers"],
30
30
  ["newChannel", "新委託", "New channel"],
@@ -71,7 +71,7 @@ var I18N_ROWS = [
71
71
  ["stats.rounds", "模型回合", "Agent rounds"],
72
72
  ["stats.cost", "費用", "Cost"],
73
73
  ["stats.estimated", "估算", "Estimated"],
74
- ["stats.inOut", "輸入 {in} · 快取命中 {cache} · 輸出 {out}", "in {in} · cache {cache} · out {out}"],
74
+ ["stats.inOut", "輸入 {in} · 輸出 {out}", "in {in} · out {out}"],
75
75
  ["askAgain", "重新詢問", "Ask again"],
76
76
  ["send", "送出", "Send"],
77
77
  ["jumpBottom", "滾到最新", "Jump to latest"],
@@ -95,11 +95,6 @@ var I18N_ROWS = [
95
95
  ["busy", "忙碌中", "Busy"],
96
96
  ["unread", "未讀", "Unread"],
97
97
  ["noMessages", "還沒有紀錄。寫一句話開始。據點裡記得 @handle。", "No log yet. Say something. In a hall, @handle an adventurer."],
98
- ["setup.kicker", "第一步", "Step 1"],
99
- ["setup.needModel", "還沒接模型。Guild 不能想、也不能跑工具。", "No model yet. Guild cannot think or run tools."],
100
- ["setup.needModelBody", "先連接訂閱或填 API key,套用主模型,再回來點名。", "Connect a subscription or paste an API key, apply a default, then come back to @mention someone."],
101
- ["setup.subs", "連接訂閱", "Connect a subscription"],
102
- ["setup.keys", "填 API key", "Add an API key"],
103
98
  ["dm", "密談", "Whisper"],
104
99
  ["justNow", "剛剛", "Just now"],
105
100
  ["minutesAgo", "{n} 分鐘前", "{n} min ago"],
@@ -145,16 +140,15 @@ var I18N_ROWS = [
145
140
  ["remove", "移除", "Remove"],
146
141
  ["cancelReply", "取消回覆", "Cancel reply"],
147
142
  ["replying", "回覆…", "Replying…"],
148
- ["trace", "軌跡", "Trajectory"],
143
+ ["trace", "Trajectory", "Trajectory"],
149
144
  ["trace.steps", "步驟", "Steps"],
150
145
  ["trace.long", "長輸出", "Long output"],
151
146
  ["trace.pick", "選一筆事件。", "Select an event."],
152
147
  ["trace.empty", "沒有事件。送一則訊息後會開始記錄。", "No events yet. Send a message to start the log."],
153
- ["trace.log", "只追加紀錄", "append-only log"],
148
+ ["trace.log", "append-only log", "append-only log"],
154
149
  ["trace.derived", "由訊息還原", "rebuilt from messages"],
155
150
  ["trace.live", "進行中", "in progress"],
156
151
  ["trace.loading", "讀取中…", "Loading…"],
157
- ["trace.filter.all", "全部", "All"],
158
152
  ["html.preview", "預覽", "Preview"],
159
153
  ["html.code", "原始碼", "Code"],
160
154
  ["html.expand", "放大預覽", "Expand preview"],
@@ -272,7 +266,7 @@ var I18N_ROWS = [
272
266
  ["library.local", "本機", "Host"],
273
267
  ["settings.eyebrow", "Hermes-style", "Hermes-style"],
274
268
  ["settings.title", "模型", "Models"],
275
- ["settings.lede", "第一次:先連訂閱或填 API key,再套用主模型。沒有模型就不能用。訂閱走 OAuthAPI key 寫入 ~/.guild/models.json。", "First time: connect a subscription or paste an API key, then apply a default. Guild does not run without a model. Subscriptions use OAuth. API keys go in ~/.guild/models.json."],
269
+ ["settings.lede", "訂閱帳號走 OAuthAPI key 可同時掛多家供應商,寫入 ~/.guild/models.json。", "Log in with a subscription. API keys can cover several providers and are written to ~/.guild/models.json."],
276
270
  ["settings.accounts", "連接帳號", "Accounts"],
277
271
  ["settings.accountsHint", "用訂閱登入,不必複製金鑰。瀏覽器完成授權後會自動刷新 token。", "Subscription login, no key paste. Tokens refresh after the browser handshake."],
278
272
  ["settings.connect", "連接", "Connect"],
@@ -292,7 +286,7 @@ var I18N_ROWS = [
292
286
  ["settings.provider", "供應商", "Provider"],
293
287
  ["settings.apply", "套用", "Apply"],
294
288
  ["settings.aux", "輔助模型", "Auxiliary models"],
295
- ["settings.auxHint", "只有 Vision、Web extract、SubAgent 可以另選模型。其餘用途一律跟主模型。", "Only Vision, web extract, and SubAgent can use a different model. Everything else follows the default."],
289
+ ["settings.auxHint", "Vision、Web extract、Compression 等每一列都寫出實際模型。跟主模型時仍顯示名稱,不是空白。", "Each row — Vision, web extract, compression, and the rest shows the model in use. Inherited rows still name the default."],
296
290
  ["settings.auxReset", "全部改回主模型", "Reset all to default"],
297
291
  ["settings.useMain", "改回跟主模型", "Follow default"],
298
292
  ["settings.change", "更改", "Change"],
@@ -332,8 +326,6 @@ var I18N_ROWS = [
332
326
  ["studio.streetAlt", "Guild 酒館門口", "Guild inn door"],
333
327
  ["studio.leave", "出門", "Leave"],
334
328
  ["studio.inn", "酒館", "Inn"],
335
- ["studio.idle", "待命", "idle"],
336
- ["studio.diving", "潛水", "diving"],
337
329
  ["studio.streetLine", "夜晚的酒館還開著。點那扇門進去。", "The inn is still open. Tap the door."],
338
330
  ["studio.tonight", "今晚", "Tonight"],
339
331
  ["studio.hire", "招募冒險者", "Recruit an adventurer"],
@@ -392,10 +384,6 @@ var I18N_ROWS = [
392
384
  ["studio.vacant", "空位", "Vacant"],
393
385
  ["studio.vacantLine", "空座位。要招冒險者進來嗎?", "Empty seat. Recruit someone?"],
394
386
  ["studio.hireChip", "招募", "Hire"],
395
- ["studio.genLook", "生成形象", "Generate look"],
396
- ["studio.genLookBusy", "畫臉中…", "Drawing…"],
397
- ["studio.genLookDone", "新形象好了。", "New look ready."],
398
- ["studio.genLookFail", "生圖失敗。到模型頁接 Grok 訂閱或填金鑰再試。", "Image failed. Connect a Grok subscription or add a key on the models page."],
399
387
  ["studio.drink", "……今晚在這兒喝酒。", "…drinking here tonight."],
400
388
  ["studio.notFound", "找不到這名冒險者", "Adventurer not found"],
401
389
  ["studio.editing", "正在編輯 @{handle} · 勾選只屬於這名冒險者", "Editing @{handle} · skills are only for this adventurer"],
@@ -15,7 +15,6 @@
15
15
  rel="stylesheet"
16
16
  />
17
17
  <link rel="stylesheet" href="/style.css" />
18
- <script src="/buddy.js"></script>
19
18
  </head>
20
19
  <body class="office-page">
21
20
  <header class="appbar">
@@ -64,7 +63,53 @@
64
63
  const colors = ["#7c6af7", "#5b8def", "#2ea887", "#e07a3d", "#d4537e"];
65
64
  return colors[seedOf(key) % colors.length];
66
65
  }
66
+ function px(ctx, x, y, w, h, color) {
67
+ ctx.fillStyle = color;
68
+ ctx.fillRect(x, y, w, h);
69
+ }
70
+ function drawBuddy(canvas, shirt, seed) {
71
+ const ctx = canvas.getContext("2d");
72
+ canvas.width = 16;
73
+ canvas.height = 24;
74
+ ctx.imageSmoothingEnabled = false;
75
+ const skin = ["#f0c8a0", "#e2b184", "#c68642", "#8d5524"][seed % 4];
76
+ const hair = ["#2a1c12", "#c4a35a", "#1a1a1a", "#6b2d3c", "#3c4a6e"][seed % 5];
77
+ const outline = "#1b1510";
78
+ const pants = "#2a3140";
79
+ const style = seed % 3;
80
+
81
+ px(ctx, 4, 12, 8, 7, outline);
82
+ px(ctx, 5, 13, 6, 5, shirt);
83
+ px(ctx, 3, 13, 2, 5, shirt);
84
+ px(ctx, 11, 13, 2, 5, shirt);
85
+ px(ctx, 3, 18, 2, 1, skin);
86
+ px(ctx, 11, 18, 2, 1, skin);
87
+
88
+ px(ctx, 4, 4, 8, 8, outline);
89
+ px(ctx, 5, 5, 6, 6, skin);
90
+ px(ctx, 6, 7, 1, 1, outline);
91
+ px(ctx, 9, 7, 1, 1, outline);
92
+ px(ctx, 7, 9, 2, 1, "#c07070");
67
93
 
94
+ if (style === 0) {
95
+ px(ctx, 4, 3, 8, 3, hair);
96
+ px(ctx, 4, 5, 2, 3, hair);
97
+ px(ctx, 10, 5, 2, 3, hair);
98
+ } else if (style === 1) {
99
+ px(ctx, 4, 2, 8, 4, hair);
100
+ px(ctx, 3, 5, 2, 4, hair);
101
+ px(ctx, 11, 5, 2, 4, hair);
102
+ } else {
103
+ px(ctx, 5, 2, 6, 2, hair);
104
+ px(ctx, 4, 4, 8, 2, hair);
105
+ px(ctx, 7, 1, 2, 2, hair);
106
+ }
107
+
108
+ px(ctx, 5, 19, 3, 4, pants);
109
+ px(ctx, 8, 19, 3, 4, pants);
110
+ px(ctx, 5, 23, 3, 1, outline);
111
+ px(ctx, 8, 23, 3, 1, outline);
112
+ }
68
113
  function escapeHtml(value) {
69
114
  return String(value)
70
115
  .replace(/&/g, "&amp;")
@@ -46,7 +46,6 @@
46
46
  </header>
47
47
  <div class="page-body">
48
48
  <p id="flash" class="muted"></p>
49
- <p class="sub" data-i18n="settings.lede">第一次:先連訂閱或填 API key,再套用主模型。沒有模型就不能用。訂閱走 OAuth;API key 寫入 ~/.guild/models.json。</p>
50
49
 
51
50
  <section class="panel" data-sec-panel="main">
52
51
  <h2 data-i18n="settings.main">主模型</h2>
@@ -77,7 +76,7 @@
77
76
  <h2 data-i18n="settings.aux">輔助模型</h2>
78
77
  <button type="button" class="linkish" id="reset-aux" data-i18n="settings.auxReset">全部改回主模型</button>
79
78
  </div>
80
- <p class="sub" data-i18n="settings.auxHint">只有 Vision、Web extract、SubAgent 可以另選模型。其餘用途一律跟主模型。</p>
79
+ <p class="sub" data-i18n="settings.auxHint">Vision、Web extract、Compression 等每一列都寫出實際模型。跟主模型時仍顯示名稱,不是空白。</p>
81
80
  <div id="aux"></div>
82
81
  </section>
83
82