@kevin5251984/guild 0.2.19 → 0.2.20

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.
@@ -167,12 +167,7 @@
167
167
  <a class="model-choice" href="/settings" data-i18n="model.settings">狀態欄</a>
168
168
  </div>
169
169
  </div>
170
- <select id="model-reasoning" hidden>
171
- <option value="minimal" data-i18n="reason.min">最低</option>
172
- <option value="low" data-i18n="reason.low">低</option>
173
- <option value="medium" data-i18n="reason.mid">中</option>
174
- <option value="high" data-i18n="reason.high">高</option>
175
- </select>
170
+ <select id="model-reasoning" hidden></select>
176
171
  <input type="checkbox" id="model-fast" hidden />
177
172
  </div>
178
173
  </div>
@@ -266,6 +261,13 @@
266
261
  </div>
267
262
  <script>
268
263
  const COLORS = ["#7c6af7", "#5b8def", "#2ea887", "#e07a3d", "#d4537e"];
264
+ const SEAT_HUE = {
265
+ infra: "#5b8def",
266
+ pm: "#2ea887",
267
+ rd: "#e07a3d",
268
+ design: "#7c6af7",
269
+ marketing: "#d4537e",
270
+ };
269
271
  const CHANNEL_ROSTER_CAP = 6;
270
272
  function isGeneralChannel(ch) {
271
273
  return Boolean(ch && (ch.id === "channel-general" || ch.name === "general"));
@@ -321,6 +323,8 @@
321
323
  };
322
324
 
323
325
  function hue(key) {
326
+ const seat = SEAT_HUE[String(key).toLowerCase()];
327
+ if (seat) return seat;
324
328
  let n = 0;
325
329
  for (const ch of String(key)) n = (n + ch.charCodeAt(0) * 17) % COLORS.length;
326
330
  return COLORS[n];
@@ -1036,14 +1040,48 @@
1036
1040
  }
1037
1041
  return out;
1038
1042
  }
1043
+ function deferredHandles(text) {
1044
+ const raw = String(text || "");
1045
+ const out = [];
1046
+ const add = (name) => {
1047
+ const key = String(name || "").toLowerCase();
1048
+ if (key && out.indexOf(key) < 0) out.push(key);
1049
+ };
1050
+ const scan = raw.replace(/```[\s\S]*?```/g, " ");
1051
+ const cue =
1052
+ /(?:完成後|通過後|最後(?:才|由)?|才需要|才叫|才交)[^@\n]{0,24}@([A-Za-z0-9_-]+)/gi;
1053
+ const bare =
1054
+ /(?:完成後|才需要|才叫)\s*(?:call|ask|由)\s*`?@?([A-Za-z0-9_-]+)`?/gi;
1055
+ let row;
1056
+ while ((row = cue.exec(scan))) add(row[1]);
1057
+ while ((row = bare.exec(scan))) add(row[1]);
1058
+ const later =
1059
+ /^[ \t]*(?:\d+[.)、]\s*|[-*•]\s+)(?:通過後|最後|然後|之後|完成後)\s*`?@([A-Za-z0-9_-]+)`?/i;
1060
+ const lines = raw.split(/\r?\n/);
1061
+ for (let i = 0; i < lines.length; i++) {
1062
+ const hit = lines[i].match(later);
1063
+ if (hit) add(hit[1]);
1064
+ }
1065
+ return out;
1066
+ }
1039
1067
  function summonedBotIds(text) {
1040
1068
  const raw = String(text || "");
1069
+ const deferred = new Set(deferredHandles(raw));
1070
+ const members = new Set(
1071
+ state.kind === "channel"
1072
+ ? ((currentChannel() && currentChannel().memberIds) || [])
1073
+ : state.kind === "dm"
1074
+ ? [state.id]
1075
+ : [],
1076
+ );
1041
1077
  const known = new Map(
1042
- (state.bots || []).map((bot) => [String(bot.handle).toLowerCase(), bot.id]),
1078
+ (state.bots || [])
1079
+ .filter((bot) => !members.size || members.has(bot.id))
1080
+ .map((bot) => [String(bot.handle).toLowerCase(), bot.id]),
1043
1081
  );
1044
1082
  const out = [];
1045
- const push = (id) => {
1046
- if (id && !out.includes(id)) out.push(id);
1083
+ const push = (id, handle) => {
1084
+ if (id && !deferred.has(handle) && !out.includes(id)) out.push(id);
1047
1085
  };
1048
1086
  let fence = false;
1049
1087
  const lines = raw.split(/\r?\n/);
@@ -1060,11 +1098,21 @@
1060
1098
  if (!m) continue;
1061
1099
  const names = m[1].match(/@([A-Za-z0-9_-]+)/g) || [];
1062
1100
  for (let n = 0; n < names.length; n++) {
1063
- push(known.get(names[n].slice(1).toLowerCase()));
1101
+ const handle = names[n].slice(1).toLowerCase();
1102
+ push(known.get(handle), handle);
1064
1103
  }
1065
1104
  }
1066
1105
  if (out.length) return out;
1067
- return mentionedBotIds(raw).slice(0, 1);
1106
+ return mentionedBotIds(raw)
1107
+ .filter((id) => {
1108
+ const bot = (state.bots || []).find((row) => row.id === id);
1109
+ return (
1110
+ bot &&
1111
+ (!members.size || members.has(id)) &&
1112
+ !deferred.has(String(bot.handle).toLowerCase())
1113
+ );
1114
+ })
1115
+ .slice(0, 1);
1068
1116
  }
1069
1117
  function lastBotAuthor() {
1070
1118
  const msgs = state.messages || [];
@@ -1244,7 +1292,20 @@
1244
1292
  }
1245
1293
  function botModelMeta(bot) {
1246
1294
  const model = botModelLabel(bot) || "—";
1247
- const reason = reasonLabel((modelCfg && modelCfg.reasoning) || "medium");
1295
+ const spec = (() => {
1296
+ const ref = botModelRef(bot);
1297
+ if (!ref) return null;
1298
+ const hit = allModels().find(
1299
+ (row) => row.provider === ref.provider && row.model === ref.model,
1300
+ );
1301
+ return (hit && hit.reasoning) || null;
1302
+ })();
1303
+ const reasonVal = clampEffort(
1304
+ modelCfg && modelCfg.reasoning,
1305
+ spec,
1306
+ Boolean(modelCfg && modelCfg.fast),
1307
+ );
1308
+ const reason = reasonVal ? reasonLabel(reasonVal) : "—";
1248
1309
  const provider = botProviderLabel(bot) || "—";
1249
1310
  return model + " · " + reason + " · " + provider;
1250
1311
  }
@@ -5332,6 +5393,7 @@
5332
5393
  group: p.name || p.id,
5333
5394
  ready: Boolean(p.ready),
5334
5395
  kind: p.kind || "key",
5396
+ reasoning: m.reasoning || null,
5335
5397
  });
5336
5398
  }
5337
5399
  }
@@ -5345,10 +5407,45 @@
5345
5407
  return hit ? hit.name : ref.model;
5346
5408
  }
5347
5409
  function reasonLabel(value) {
5410
+ if (value === "none") return t("reason.none");
5348
5411
  if (value === "minimal") return t("reason.min");
5349
5412
  if (value === "low") return t("reason.low");
5413
+ if (value === "medium") return t("reason.mid");
5350
5414
  if (value === "high") return t("reason.high");
5351
- return t("reason.mid");
5415
+ if (value === "xhigh") return t("reason.xhigh");
5416
+ if (value === "max") return t("reason.max");
5417
+ return value || t("reason.mid");
5418
+ }
5419
+ function activeReasoningSpec() {
5420
+ const ref = activeModelRef();
5421
+ if (!ref) return null;
5422
+ const hit = allModels().find(
5423
+ (row) => row.provider === ref.provider && row.model === ref.model,
5424
+ );
5425
+ return (hit && hit.reasoning) || null;
5426
+ }
5427
+ function effortChoices(spec) {
5428
+ const raw = spec && spec.supportedEfforts;
5429
+ if (!raw || !raw.length) return [];
5430
+ return spec.mandatory
5431
+ ? raw.filter((key) => key !== "none")
5432
+ : raw.slice();
5433
+ }
5434
+ function clampEffort(want, spec, fast) {
5435
+ const choices = effortChoices(spec);
5436
+ if (!choices.length) return "";
5437
+ if (fast) {
5438
+ if (choices.indexOf("low") >= 0) return "low";
5439
+ if (choices.indexOf("minimal") >= 0) return "minimal";
5440
+ return choices[choices.length - 1];
5441
+ }
5442
+ if (want && choices.indexOf(want) >= 0) return want;
5443
+ if (spec.defaultEffort && choices.indexOf(spec.defaultEffort) >= 0) {
5444
+ return spec.defaultEffort;
5445
+ }
5446
+ if (choices.indexOf("high") >= 0) return "high";
5447
+ if (choices.indexOf("medium") >= 0) return "medium";
5448
+ return choices[0];
5352
5449
  }
5353
5450
  function speedLabel() {
5354
5451
  return modelCfg.fast ? t("speed.fast") : t("speed.normal");
@@ -5450,25 +5547,44 @@
5450
5547
  }
5451
5548
  document.getElementById("model-results").innerHTML =
5452
5549
  html || '<p class="model-empty">' + t("noModels") + "</p>";
5453
- document.getElementById("model-reasoning").value = modelCfg.reasoning || "medium";
5454
5550
  document.getElementById("model-fast").checked = Boolean(modelCfg.fast);
5455
- const reason = reasonLabel(modelCfg.reasoning || "medium");
5551
+ const spec = activeReasoningSpec();
5552
+ const choices = effortChoices(spec);
5553
+ const reasonVal = clampEffort(
5554
+ modelCfg.reasoning,
5555
+ spec,
5556
+ Boolean(modelCfg.fast),
5557
+ );
5558
+ const reasonSel = document.getElementById("model-reasoning");
5559
+ reasonSel.innerHTML = choices
5560
+ .map(
5561
+ (value) =>
5562
+ '<option value="' +
5563
+ value +
5564
+ '">' +
5565
+ reasonLabel(value) +
5566
+ "</option>",
5567
+ )
5568
+ .join("");
5569
+ if (reasonVal) reasonSel.value = reasonVal;
5570
+ const reasonRow = document.querySelector('.model-nav [data-sheet="reasoning"]');
5571
+ if (reasonRow) reasonRow.hidden = !choices.length;
5572
+ if (!choices.length) {
5573
+ const pane = document.getElementById("model-sheet-reasoning");
5574
+ if (pane && !pane.hidden) showModelSheet("models");
5575
+ }
5576
+ const reason = choices.length ? reasonLabel(reasonVal) : "";
5456
5577
  const speed = speedLabel();
5457
5578
  const name = displayName(current);
5458
5579
  document.getElementById("model-btn-label").textContent = current
5459
- ? name + " " + reason
5580
+ ? name + (reason ? " " + reason : "")
5460
5581
  : name;
5461
5582
  document.getElementById("model-row-model").textContent = name;
5462
- document.getElementById("model-row-reason").textContent = reason;
5583
+ document.getElementById("model-row-reason").textContent = reason || "—";
5463
5584
  document.getElementById("model-row-speed").textContent = speed;
5464
- document.getElementById("model-reason-list").innerHTML = [
5465
- ["minimal", t("reason.min")],
5466
- ["low", t("reason.low")],
5467
- ["medium", t("reason.mid")],
5468
- ["high", t("reason.high")],
5469
- ]
5470
- .map(([value, label]) => {
5471
- const on = (modelCfg.reasoning || "medium") === value ? " on" : "";
5585
+ document.getElementById("model-reason-list").innerHTML = choices
5586
+ .map((value) => {
5587
+ const on = reasonVal === value ? " on" : "";
5472
5588
  const tick = on ? '<span class="tick">✓</span>' : "";
5473
5589
  return (
5474
5590
  '<button type="button" class="model-choice' +
@@ -5476,7 +5592,7 @@
5476
5592
  '" data-reason="' +
5477
5593
  value +
5478
5594
  '">' +
5479
- label +
5595
+ reasonLabel(value) +
5480
5596
  tick +
5481
5597
  "</button>"
5482
5598
  );
@@ -119,10 +119,13 @@ var I18N_ROWS = [
119
119
  ["local", "本地", "Local"],
120
120
  ["you", "你", "You"],
121
121
  ["emptyParen", "(空)", "(empty)"],
122
+ ["reason.none", "關閉", "Off"],
122
123
  ["reason.min", "最低", "Min"],
123
124
  ["reason.low", "低", "Low"],
124
125
  ["reason.mid", "中", "Mid"],
125
126
  ["reason.high", "高", "High"],
127
+ ["reason.xhigh", "極高", "Extra high"],
128
+ ["reason.max", "最大", "Max"],
126
129
  ["speed.normal", "標準", "Standard"],
127
130
  ["speed.fast", "快速", "Fast"],
128
131
  ["model", "模型", "Model"],
@@ -8,6 +8,7 @@
8
8
  --panel: #10141A;
9
9
  --lift: #161C24;
10
10
  --card: #161C24;
11
+ --bubble: #1A222C;
11
12
  --you: #242C36;
12
13
  --text: #E8E4DC;
13
14
  --muted: #8B8F96;
@@ -15,6 +16,10 @@
15
16
  --fill: #141A22;
16
17
  --ok: #9fdfb2;
17
18
  --danger: #f0a8a8;
19
+ --press: 0.97;
20
+ --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
21
+ --press-ms: 140ms;
22
+ --hover-ms: 160ms;
18
23
  --tap: 44px;
19
24
  --focus: 0 0 0 2px var(--bg), 0 0 0 3px var(--steel);
20
25
  --vvh: 100dvh;
@@ -48,6 +53,58 @@ textarea:focus-visible {
48
53
  outline: none;
49
54
  box-shadow: var(--focus);
50
55
  }
56
+ /* Enamel press language, shared with the hall: paper / steel / ghost. */
57
+ .navlist a,
58
+ .back,
59
+ .turn-stop,
60
+ .turn-steer,
61
+ .steer,
62
+ .send,
63
+ .stop-all,
64
+ .mention-item {
65
+ transition:
66
+ background-color var(--hover-ms) ease,
67
+ border-color var(--hover-ms) ease,
68
+ color var(--hover-ms) ease,
69
+ transform var(--press-ms) var(--ease-out),
70
+ opacity var(--hover-ms) ease;
71
+ }
72
+ .navlist a:active,
73
+ .back:active,
74
+ .turn-stop:active:not(:disabled),
75
+ .turn-steer:active:not(:disabled),
76
+ .steer:active:not(:disabled),
77
+ .send:active:not(:disabled),
78
+ .stop-all:active:not(:disabled),
79
+ .mention-item:active {
80
+ transform: scale(var(--press));
81
+ }
82
+ @media (prefers-reduced-motion: reduce) {
83
+ .navlist a,
84
+ .back,
85
+ .turn-stop,
86
+ .turn-steer,
87
+ .steer,
88
+ .send,
89
+ .stop-all,
90
+ .mention-item {
91
+ transition:
92
+ background-color var(--hover-ms) ease,
93
+ border-color var(--hover-ms) ease,
94
+ color var(--hover-ms) ease,
95
+ opacity var(--hover-ms) ease;
96
+ }
97
+ .navlist a:active,
98
+ .back:active,
99
+ .turn-stop:active,
100
+ .turn-steer:active,
101
+ .steer:active,
102
+ .send:active,
103
+ .stop-all:active,
104
+ .mention-item:active {
105
+ transform: none;
106
+ }
107
+ }
51
108
  .screen {
52
109
  display: flex;
53
110
  flex-direction: column;
@@ -214,6 +271,7 @@ textarea:focus-visible {
214
271
  font-weight: 650;
215
272
  color: #fff;
216
273
  background: var(--steel);
274
+ box-shadow: 0 0 0 1px color-mix(in srgb, var(--steel) 38%, transparent);
217
275
  }
218
276
  .avatar img {
219
277
  width: 100%;
@@ -242,6 +300,7 @@ textarea:focus-visible {
242
300
  white-space: pre-wrap;
243
301
  }
244
302
  .msg.you .bubble { background: var(--you); }
303
+ .msg.bot .bubble { background: var(--bubble); }
245
304
  .assistant-text {
246
305
  margin: 0;
247
306
  color: var(--text);
@@ -264,7 +323,7 @@ textarea:focus-visible {
264
323
  .assistant-text .md-fence {
265
324
  margin: 0.35rem 0 0.7rem;
266
325
  border-radius: 12px;
267
- background: #1a1a1a;
326
+ background: var(--fill);
268
327
  overflow: auto;
269
328
  }
270
329
  .assistant-text .md-fence-bar {
@@ -301,11 +360,11 @@ textarea:focus-visible {
301
360
  white-space: nowrap;
302
361
  background: linear-gradient(
303
362
  90deg,
304
- #E8E4DC 0%,
305
- #E8E4DC 40%,
306
- #8B8F96 50%,
307
- #E8E4DC 60%,
308
- #E8E4DC 100%
363
+ var(--text) 0%,
364
+ var(--text) 40%,
365
+ var(--muted) 50%,
366
+ var(--text) 60%,
367
+ var(--text) 100%
309
368
  );
310
369
  background-position: 100% 0;
311
370
  background-size: 250% 100%;
@@ -319,8 +378,8 @@ textarea:focus-visible {
319
378
  font-size: 13px;
320
379
  font-weight: 400;
321
380
  font-variant-numeric: tabular-nums;
322
- color: #6f6f6f;
323
- -webkit-text-fill-color: #6f6f6f;
381
+ color: var(--muted);
382
+ -webkit-text-fill-color: var(--muted);
324
383
  }
325
384
  @keyframes m-shimmer { to { background-position: 0 0; } }
326
385
  @media (prefers-reduced-motion: reduce) {
@@ -343,15 +402,22 @@ textarea:focus-visible {
343
402
  line-height: 20px;
344
403
  color: var(--muted);
345
404
  }
346
- .live-name { color: #cfcfcf; font-weight: 500; }
405
+ .live-name { color: var(--text); font-weight: 500; }
347
406
  .live-detail {
348
407
  overflow: hidden;
349
408
  text-overflow: ellipsis;
350
409
  white-space: nowrap;
351
410
  }
352
411
  .live-step.running .live-name { color: var(--text); }
353
- .turn-actions { margin-top: 4px; }
412
+ .turn-actions {
413
+ display: flex;
414
+ flex-wrap: wrap;
415
+ gap: 8px;
416
+ margin-top: 4px;
417
+ }
354
418
  .turn-stop,
419
+ .turn-steer,
420
+ .steer,
355
421
  .send,
356
422
  .stop-all {
357
423
  display: inline-flex;
@@ -369,19 +435,44 @@ textarea:focus-visible {
369
435
  font-weight: 620;
370
436
  cursor: pointer;
371
437
  }
438
+ /* Stop is ghost steel: --fill at rest, --lift on hover, danger ink only. */
372
439
  .turn-stop,
373
440
  .stop-all {
374
441
  color: var(--danger);
442
+ background: var(--fill);
443
+ border-color: color-mix(in srgb, var(--danger) 32%, var(--line));
444
+ }
445
+ .turn-stop:hover,
446
+ .stop-all:hover {
375
447
  background: var(--lift);
376
- border-color: color-mix(in srgb, var(--danger) 35%, var(--line));
448
+ border-color: color-mix(in srgb, var(--danger) 45%, var(--line));
377
449
  }
450
+ /* Steer rides the steel rail, never a filled danger slab. */
451
+ .turn-steer,
452
+ .steer {
453
+ color: var(--steel);
454
+ background: var(--fill);
455
+ border-color: color-mix(in srgb, var(--steel) 38%, var(--line));
456
+ }
457
+ .turn-steer:hover,
458
+ .steer:hover {
459
+ background: var(--lift);
460
+ color: var(--text);
461
+ border-color: color-mix(in srgb, var(--steel) 55%, var(--line));
462
+ }
463
+ /* The one paper CTA on the page. */
378
464
  .send {
379
- background: var(--steel);
465
+ background: var(--paper);
380
466
  color: var(--ink);
381
467
  border-color: transparent;
382
468
  }
469
+ .send:hover:not(:disabled) {
470
+ background: color-mix(in srgb, var(--paper) 88%, white);
471
+ }
383
472
  .send:disabled,
384
- .stop-all:disabled {
473
+ .stop-all:disabled,
474
+ .steer:disabled,
475
+ .turn-stop:disabled {
385
476
  opacity: 0.45;
386
477
  cursor: default;
387
478
  }
@@ -407,9 +498,17 @@ textarea:focus-visible {
407
498
  gap: 0.5rem;
408
499
  }
409
500
  .composer-actions .send,
410
- .composer-actions .stop-all {
501
+ .composer-actions .stop-all,
502
+ .composer-actions .steer {
411
503
  flex: 1;
412
504
  }
505
+ .composer-steer {
506
+ color: var(--muted);
507
+ font-size: 0.75rem;
508
+ line-height: 1.4;
509
+ padding: 0 0.15rem;
510
+ }
511
+ .composer-steer[hidden] { display: none; }
413
512
  #draft {
414
513
  display: block;
415
514
  width: 100%;
@@ -472,6 +571,7 @@ textarea:focus-visible {
472
571
  color: #fff;
473
572
  background: var(--steel);
474
573
  flex: none;
574
+ box-shadow: 0 0 0 1px color-mix(in srgb, var(--steel) 38%, transparent);
475
575
  }
476
576
  .flash {
477
577
  position: fixed;
@@ -489,3 +589,9 @@ textarea:focus-visible {
489
589
  z-index: 8;
490
590
  }
491
591
  .flash.on { opacity: 1; }
592
+ /* Errors read as danger ink on enamel lift, never a filled red slab. */
593
+ .flash.err {
594
+ background: var(--lift);
595
+ color: var(--danger);
596
+ border: 1px solid color-mix(in srgb, var(--danger) 35%, var(--line));
597
+ }