@kevin5251984/guild 0.2.17 → 0.2.19
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/package.json +1 -1
- package/src/catalog/subagents.ts +2 -1
- package/src/chat-parts.ts +6 -1
- package/src/compact.ts +27 -0
- package/src/db.ts +199 -64
- package/src/generate.ts +58 -27
- package/src/handlers.ts +341 -54
- package/src/harness.ts +21 -8
- package/src/image-gen.ts +2 -1
- package/src/llm.ts +400 -58
- package/src/memory.ts +69 -0
- package/src/mention.ts +119 -12
- package/src/oauth.ts +202 -25
- package/src/opencode-free.ts +247 -0
- package/src/public/buddy.js +432 -0
- package/src/public/chat.css +174 -72
- package/src/public/chat.html +212 -43
- package/src/public/i18n.js +41 -6
- package/src/public/index.html +1 -46
- package/src/public/mobile.css +491 -0
- package/src/public/mobile.html +784 -0
- package/src/public/settings.html +188 -27
- package/src/public/skills-add.html +8 -0
- package/src/public/studio.html +171 -117
- package/src/public/style.css +220 -26
- package/src/public/subagents-add.html +8 -0
- package/src/router.ts +76 -5
- package/src/store.ts +161 -5
- package/src/subagent.ts +233 -6
- package/src/tools.ts +124 -18
- package/src/trajectory.ts +72 -9
- package/src/usage.ts +10 -0
- package/vendor/protocol/src/index.ts +14 -1
package/src/public/settings.html
CHANGED
|
@@ -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="/style.css" />
|
|
11
|
+
<link rel="stylesheet" href="/style.css?v=aux-align" />
|
|
12
12
|
<script src="/i18n.js"></script>
|
|
13
13
|
</head>
|
|
14
14
|
<body class="app settings-page" data-i18n-page="settings" data-section="main">
|
|
@@ -46,6 +46,7 @@
|
|
|
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>
|
|
49
50
|
|
|
50
51
|
<section class="panel" data-sec-panel="main">
|
|
51
52
|
<h2 data-i18n="settings.main">主模型</h2>
|
|
@@ -76,7 +77,7 @@
|
|
|
76
77
|
<h2 data-i18n="settings.aux">輔助模型</h2>
|
|
77
78
|
<button type="button" class="linkish" id="reset-aux" data-i18n="settings.auxReset">全部改回主模型</button>
|
|
78
79
|
</div>
|
|
79
|
-
<p class="sub" data-i18n="settings.auxHint"
|
|
80
|
+
<p class="sub" data-i18n="settings.auxHint">只有 Vision、Web extract、SubAgent 可以另選模型。其餘用途一律跟主模型。</p>
|
|
80
81
|
<div id="aux"></div>
|
|
81
82
|
</section>
|
|
82
83
|
|
|
@@ -144,6 +145,18 @@
|
|
|
144
145
|
let auxRole = null;
|
|
145
146
|
let keyTab = 0;
|
|
146
147
|
const PRESETS = {
|
|
148
|
+
"opencode-free": {
|
|
149
|
+
id: "opencode-free",
|
|
150
|
+
name: "OpenCode Free",
|
|
151
|
+
baseUrl: "https://opencode.ai/zen/v1",
|
|
152
|
+
api: "openai-completions",
|
|
153
|
+
apiKey: "",
|
|
154
|
+
models: [
|
|
155
|
+
{ id: "muse-spark-1.2-contributor-free", name: "Muse Spark 1.2 Contributor Free" },
|
|
156
|
+
{ id: "laguna-s-2.1-free", name: "Laguna S 2.1 Free" },
|
|
157
|
+
{ id: "mimo-v2.5-free", name: "Mimo V2.5 Free" },
|
|
158
|
+
],
|
|
159
|
+
},
|
|
147
160
|
openai: {
|
|
148
161
|
id: "openai",
|
|
149
162
|
name: "OpenAI",
|
|
@@ -260,7 +273,7 @@
|
|
|
260
273
|
'<option value="' +
|
|
261
274
|
escapeHtml(m.id) +
|
|
262
275
|
'">' +
|
|
263
|
-
escapeHtml(m.
|
|
276
|
+
escapeHtml(prettyModelName(m.id, m.name)) +
|
|
264
277
|
"</option>",
|
|
265
278
|
)
|
|
266
279
|
.join("");
|
|
@@ -275,11 +288,27 @@
|
|
|
275
288
|
function groupReady(id) {
|
|
276
289
|
return Boolean((data.picker || []).find((p) => p.id === id && p.ready));
|
|
277
290
|
}
|
|
291
|
+
function prettyModelName(id, given) {
|
|
292
|
+
const raw = String(id || "");
|
|
293
|
+
const label = String(given || "").trim();
|
|
294
|
+
if (label && label !== raw) return label;
|
|
295
|
+
const core = (raw.split("/").pop() || raw).trim();
|
|
296
|
+
const spaced = core
|
|
297
|
+
.replace(/[:_]+/g, "-")
|
|
298
|
+
.replace(/([A-Za-z])(\d)/g, "$1 $2")
|
|
299
|
+
.replace(/-/g, " ")
|
|
300
|
+
.replace(/\s+/g, " ")
|
|
301
|
+
.trim();
|
|
302
|
+
if (!spaced) return raw;
|
|
303
|
+
return spaced.replace(/\b([a-z])/g, function (ch) {
|
|
304
|
+
return ch.toUpperCase();
|
|
305
|
+
});
|
|
306
|
+
}
|
|
278
307
|
function modelLabel(ref) {
|
|
279
308
|
if (!ref || !ref.provider || !ref.model) return t("settings.noDefault");
|
|
280
309
|
const group = pickerGroups().find((p) => p.id === ref.provider);
|
|
281
310
|
const hit = ((group && group.models) || []).find((m) => m.id === ref.model);
|
|
282
|
-
const name = (hit && hit.name)
|
|
311
|
+
const name = prettyModelName(ref.model, hit && hit.name);
|
|
283
312
|
const vendor = (group && group.name) || ref.provider;
|
|
284
313
|
return name + " · " + vendor;
|
|
285
314
|
}
|
|
@@ -388,12 +417,46 @@
|
|
|
388
417
|
if (key.length <= 10) return key;
|
|
389
418
|
return key.slice(0, 5) + "…" + key.slice(-5);
|
|
390
419
|
}
|
|
420
|
+
function isKeylessProvider(id) {
|
|
421
|
+
return id === "opencode-free" || id === "free" || id === "opencode_free";
|
|
422
|
+
}
|
|
391
423
|
function storedBadge(p) {
|
|
424
|
+
if (isKeylessProvider(p.id)) {
|
|
425
|
+
return '<span class="badge">' + t("settings.keyless") + "</span>";
|
|
426
|
+
}
|
|
392
427
|
if (p.stored === "env") return '<span class="badge">env</span>';
|
|
393
428
|
if (p.stored === "literal") return '<span class="badge">' + t("settings.filled") + "</span>";
|
|
394
429
|
return '<span class="badge empty">' + t("settings.emptyKey") + "</span>";
|
|
395
430
|
}
|
|
431
|
+
function probeFor(id) {
|
|
432
|
+
return ((data.probe || []).find((row) => row.id === id) || null);
|
|
433
|
+
}
|
|
434
|
+
function probeBadge(id) {
|
|
435
|
+
const row = probeFor(id);
|
|
436
|
+
if (!row) return "";
|
|
437
|
+
if (row.ok && row.status === 429) {
|
|
438
|
+
return '<span class="badge wait probe-flag">' + t("settings.probeBusy") + "</span>";
|
|
439
|
+
}
|
|
440
|
+
if (row.ok) return '<span class="badge probe-flag">' + t("settings.probeOk") + "</span>";
|
|
441
|
+
return '<span class="badge empty probe-flag">' + t("settings.probeFail") + "</span>";
|
|
442
|
+
}
|
|
443
|
+
function probeHint() {
|
|
444
|
+
const probe = data.probe || [];
|
|
445
|
+
if (!probe.length) return "";
|
|
446
|
+
const skip = probe.filter((row) => !row.ok);
|
|
447
|
+
if (!skip.length) return "";
|
|
448
|
+
return (
|
|
449
|
+
'<p class="hint">' +
|
|
450
|
+
t("settings.syncedSkip", {
|
|
451
|
+
ok: probe.filter((row) => row.ok).length,
|
|
452
|
+
names: skip.map((row) => row.id).join("、"),
|
|
453
|
+
}) +
|
|
454
|
+
"</p>"
|
|
455
|
+
);
|
|
456
|
+
}
|
|
396
457
|
function cardHtml(p, index) {
|
|
458
|
+
const lock = isKeylessProvider(p.id);
|
|
459
|
+
const ro = lock ? " readonly" : "";
|
|
397
460
|
const models = (p.models || [])
|
|
398
461
|
.map((m, i) => {
|
|
399
462
|
return (
|
|
@@ -401,7 +464,9 @@
|
|
|
401
464
|
'" data-f="id" value="' + escapeHtml(m.id || "") +
|
|
402
465
|
'" placeholder="model id" /><input data-p="' + index + '" data-m="' + i +
|
|
403
466
|
'" data-f="name" value="' + escapeHtml(m.name || "") +
|
|
404
|
-
'" placeholder="' + t("settings.displayName") + '"
|
|
467
|
+
'" placeholder="' + t("settings.displayName") + '" />' +
|
|
468
|
+
probeBadge(m.id) +
|
|
469
|
+
'<button type="button" class="secondary" data-del-model="' +
|
|
405
470
|
index + ":" + i + '">×</button></div>'
|
|
406
471
|
);
|
|
407
472
|
})
|
|
@@ -409,19 +474,38 @@
|
|
|
409
474
|
return (
|
|
410
475
|
'<div class="provider-card" data-index="' + index + '">' +
|
|
411
476
|
'<div class="card-head"><h3>' + escapeHtml(p.name || p.id) + storedBadge(p) +
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
477
|
+
"</h3>" +
|
|
478
|
+
(lock
|
|
479
|
+
? ""
|
|
480
|
+
: '<button type="button" class="secondary" data-del-provider="' + index + '">' + t("settings.delete") + "</button>") +
|
|
481
|
+
"</div>" +
|
|
482
|
+
"<label>ID</label><input data-p=\"" + index + '" data-f="id" value="' + escapeHtml(p.id) + '"' + ro + " />" +
|
|
483
|
+
"<label>" + t("settings.name") + "</label><input data-p=\"" + index + '" data-f="name" value="' + escapeHtml(p.name || "") + '"' + ro + " />" +
|
|
484
|
+
"<label>baseUrl</label><input data-p=\"" + index + '" data-f="baseUrl" value="' + escapeHtml(p.baseUrl || "") + '"' + ro + " />" +
|
|
485
|
+
'<div class="row-2"><div><label>API</label><select data-p="' + index + '" data-f="api"' +
|
|
486
|
+
(lock ? " disabled" : "") + ">" +
|
|
417
487
|
'<option value="openai-completions"' + (p.api !== "anthropic-messages" && p.api !== "openai-responses" ? " selected" : "") + ">openai-completions</option>" +
|
|
418
488
|
'<option value="anthropic-messages"' + (p.api === "anthropic-messages" ? " selected" : "") + ">anthropic-messages</option>" +
|
|
419
489
|
'<option value="openai-responses"' + (p.api === "openai-responses" ? " selected" : "") + ">openai-responses</option></select></div>" +
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
490
|
+
(isKeylessProvider(p.id)
|
|
491
|
+
? '<div><label>API key</label><p class="hint">' + t("settings.keyless") + "</p></div>"
|
|
492
|
+
: '<div><label>API key</label><input type="text" data-p="' + index + '" data-f="apiKey" spellcheck="false" autocomplete="off" autocapitalize="off" value="' +
|
|
493
|
+
escapeHtml(p.apiKeyPreview || "") + '" placeholder="$ENV_VAR 或 sk-…" /></div>') +
|
|
494
|
+
"</div>" +
|
|
495
|
+
(isKeylessProvider(p.id)
|
|
496
|
+
? '<p class="hint">' + t("settings.opencodeFreeHint") + "</p>"
|
|
497
|
+
: "") +
|
|
498
|
+
'<div class="model-head"><label>' + t("model") + "</label>" +
|
|
499
|
+
(isKeylessProvider(p.id)
|
|
500
|
+
? '<button type="button" class="sync-free" data-sync-free="1"><span class="sync-ico" aria-hidden="true">↻</span>' + t("settings.sync") + "</button>"
|
|
501
|
+
: "") +
|
|
502
|
+
"</div>" +
|
|
503
|
+
models +
|
|
504
|
+
(lock ? probeHint() : "") +
|
|
423
505
|
'<button type="button" class="secondary" data-add-model="' + index + '" style="margin-top:0.55rem">' + t("settings.addModel") + "</button>" +
|
|
424
|
-
'<div class="card-foot"><p class="hint">' +
|
|
506
|
+
'<div class="card-foot"><p class="hint">' +
|
|
507
|
+
t(isKeylessProvider(p.id) ? "settings.saveKeylessHint" : "settings.saveKeyHint") +
|
|
508
|
+
"</p>" +
|
|
425
509
|
'<button type="button" data-save-keys="' + index + '">' +
|
|
426
510
|
t("settings.saveKey") +
|
|
427
511
|
"</button></div></div>"
|
|
@@ -434,11 +518,19 @@
|
|
|
434
518
|
const p = (data.providers || [])[index];
|
|
435
519
|
if (!p) return;
|
|
436
520
|
const id = card.querySelector('[data-f="id"]').value.trim();
|
|
437
|
-
if (
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
521
|
+
if (isKeylessProvider(p.id) || isKeylessProvider(id)) {
|
|
522
|
+
p.id = "opencode-free";
|
|
523
|
+
p.name = "OpenCode Free";
|
|
524
|
+
p.baseUrl = "https://opencode.ai/zen/v1";
|
|
525
|
+
p.api = "openai-completions";
|
|
526
|
+
} else {
|
|
527
|
+
if (id) p.id = id;
|
|
528
|
+
p.name = card.querySelector('[data-f="name"]').value.trim();
|
|
529
|
+
p.baseUrl = card.querySelector('[data-f="baseUrl"]').value.trim();
|
|
530
|
+
p.api = card.querySelector('[data-f="api"]').value;
|
|
531
|
+
}
|
|
532
|
+
const keyEl = card.querySelector('[data-f="apiKey"]');
|
|
533
|
+
const key = keyEl ? keyEl.value.trim() : "";
|
|
442
534
|
if (key && key !== (p.apiKeyPreview || "")) {
|
|
443
535
|
p.apiKey = key;
|
|
444
536
|
p.apiKeyPreview = maskApiKey(key);
|
|
@@ -454,8 +546,7 @@
|
|
|
454
546
|
});
|
|
455
547
|
p.models = models;
|
|
456
548
|
}
|
|
457
|
-
function
|
|
458
|
-
flushVisibleCard();
|
|
549
|
+
function snapshotProviders() {
|
|
459
550
|
const providers = {};
|
|
460
551
|
(data.providers || []).forEach((p) => {
|
|
461
552
|
if (!p.id) return;
|
|
@@ -469,6 +560,10 @@
|
|
|
469
560
|
});
|
|
470
561
|
return providers;
|
|
471
562
|
}
|
|
563
|
+
function collectKeys() {
|
|
564
|
+
flushVisibleCard();
|
|
565
|
+
return snapshotProviders();
|
|
566
|
+
}
|
|
472
567
|
function presetLabel(key, preset) {
|
|
473
568
|
return key === "custom" ? t("settings.custom") : (preset && preset.name) || key;
|
|
474
569
|
}
|
|
@@ -730,6 +825,68 @@
|
|
|
730
825
|
document.getElementById("add-provider-cancel").addEventListener("click", () => {
|
|
731
826
|
document.getElementById("add-provider-dialog").close();
|
|
732
827
|
});
|
|
828
|
+
async function syncOpenCodeFree() {
|
|
829
|
+
const btn = document.querySelector("[data-sync-free]");
|
|
830
|
+
if (btn) btn.disabled = true;
|
|
831
|
+
flash("", t("settings.syncing"));
|
|
832
|
+
try {
|
|
833
|
+
const res = await fetch("/settings/models/opencode-free/sync", {
|
|
834
|
+
method: "POST",
|
|
835
|
+
});
|
|
836
|
+
const body = await res.json().catch(() => ({}));
|
|
837
|
+
if (!res.ok) {
|
|
838
|
+
flash("error", body.error || t("settings.syncFailed"));
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
data = body;
|
|
842
|
+
const probe = Array.isArray(body.probe) ? body.probe : [];
|
|
843
|
+
const ok = probe.filter((row) => row.ok).length;
|
|
844
|
+
const skip = probe.filter((row) => !row.ok);
|
|
845
|
+
render();
|
|
846
|
+
if (probe.length && !ok) {
|
|
847
|
+
flash("error", t("settings.syncedNone"));
|
|
848
|
+
} else if (skip.length) {
|
|
849
|
+
flash(
|
|
850
|
+
"ok",
|
|
851
|
+
t("settings.syncedSkip", {
|
|
852
|
+
ok: ok,
|
|
853
|
+
names: skip.map((row) => row.id).join("、"),
|
|
854
|
+
}),
|
|
855
|
+
);
|
|
856
|
+
} else {
|
|
857
|
+
const n =
|
|
858
|
+
ok ||
|
|
859
|
+
(((data.picker || []).find((p) => p.id === "opencode-free") || {}).models || [])
|
|
860
|
+
.length;
|
|
861
|
+
flash("ok", t("settings.synced", { n: n }));
|
|
862
|
+
}
|
|
863
|
+
} catch (err) {
|
|
864
|
+
flash("error", err.message || t("settings.syncFailed"));
|
|
865
|
+
} finally {
|
|
866
|
+
if (btn) btn.disabled = false;
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
async function deleteProvider(index) {
|
|
870
|
+
const doomed = (data.providers || [])[index];
|
|
871
|
+
if (!doomed || isKeylessProvider(doomed.id)) return;
|
|
872
|
+
const name = doomed.name || doomed.id;
|
|
873
|
+
data.providers.splice(index, 1);
|
|
874
|
+
if (keyTab >= data.providers.length) keyTab = Math.max(0, data.providers.length - 1);
|
|
875
|
+
const res = await fetch("/settings/models", {
|
|
876
|
+
method: "PUT",
|
|
877
|
+
headers: { "content-type": "application/json" },
|
|
878
|
+
body: JSON.stringify({ providers: snapshotProviders() }),
|
|
879
|
+
});
|
|
880
|
+
const body = await res.json().catch(() => ({}));
|
|
881
|
+
if (!res.ok) {
|
|
882
|
+
flash("error", body.error || t("deleteFailed"));
|
|
883
|
+
await load();
|
|
884
|
+
return;
|
|
885
|
+
}
|
|
886
|
+
data = body;
|
|
887
|
+
render();
|
|
888
|
+
flash("health", t("settings.deletedProvider", { name: name }));
|
|
889
|
+
}
|
|
733
890
|
async function saveProviderCard(index) {
|
|
734
891
|
const card = document.querySelector('.provider-card[data-index="' + index + '"]');
|
|
735
892
|
const label = card
|
|
@@ -762,17 +919,21 @@
|
|
|
762
919
|
if (p) input.value = p.apiKeyPreview || "";
|
|
763
920
|
});
|
|
764
921
|
document.getElementById("providers").addEventListener("click", (event) => {
|
|
765
|
-
const el = event.target;
|
|
922
|
+
const el = event.target.closest ? event.target : event.target.parentElement;
|
|
923
|
+
if (!el || !el.closest) return;
|
|
766
924
|
if (el.closest("[data-save-keys]")) {
|
|
767
925
|
const i = Number(el.closest("[data-save-keys]").getAttribute("data-save-keys"));
|
|
768
926
|
saveProviderCard(i);
|
|
769
927
|
return;
|
|
770
928
|
}
|
|
771
|
-
if (el.closest("[data-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
929
|
+
if (el.closest("[data-sync-free]")) {
|
|
930
|
+
syncOpenCodeFree();
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
933
|
+
if (el.closest("[data-del-provider]")) {
|
|
934
|
+
const i = Number(el.closest("[data-del-provider]").getAttribute("data-del-provider"));
|
|
935
|
+
deleteProvider(i);
|
|
936
|
+
return;
|
|
776
937
|
}
|
|
777
938
|
if (el.closest("[data-add-model]")) {
|
|
778
939
|
flushVisibleCard();
|
|
@@ -95,7 +95,12 @@
|
|
|
95
95
|
return;
|
|
96
96
|
}
|
|
97
97
|
const button = document.getElementById("ai-generate");
|
|
98
|
+
if (button.disabled || button.getAttribute("aria-busy") === "true") return;
|
|
98
99
|
button.disabled = true;
|
|
100
|
+
button.setAttribute("aria-busy", "true");
|
|
101
|
+
button.classList.add("is-busy");
|
|
102
|
+
const idle = button.textContent;
|
|
103
|
+
button.textContent = t("studio.generating");
|
|
99
104
|
try {
|
|
100
105
|
const response = await fetch("/generate", {
|
|
101
106
|
method: "POST",
|
|
@@ -115,6 +120,9 @@
|
|
|
115
120
|
);
|
|
116
121
|
} finally {
|
|
117
122
|
button.disabled = false;
|
|
123
|
+
button.removeAttribute("aria-busy");
|
|
124
|
+
button.classList.remove("is-busy");
|
|
125
|
+
button.textContent = idle;
|
|
118
126
|
}
|
|
119
127
|
});
|
|
120
128
|
|