micro-models-agent 2.4.0 → 2.8.0
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/CHANGELOG.md +109 -0
- package/README.md +88 -38
- package/bin/mma.mjs +41 -41
- package/dist/assets/app.js +246 -3032
- package/dist/assets/hljs.js +1244 -0
- package/dist/assets/index.html +15 -1
- package/dist/assets/js/api.js +28 -0
- package/dist/assets/js/dialog.js +105 -0
- package/dist/assets/js/dom.js +157 -0
- package/dist/assets/js/feed.js +995 -0
- package/dist/assets/js/format.js +114 -0
- package/dist/assets/js/i18n.js +46 -0
- package/dist/assets/js/settings.js +1126 -0
- package/dist/assets/js/store.js +79 -0
- package/dist/assets/js/tabs.js +672 -0
- package/dist/assets/md.js +277 -0
- package/dist/assets/setup.js +557 -0
- package/dist/assets/smd.js +1665 -0
- package/dist/assets/styles.css +271 -21
- package/dist/i18n/en.json +171 -3
- package/dist/i18n/ru.json +171 -3
- package/dist/main.js +4958 -2588
- package/dist/modules/browser/bridge-server.mjs +3 -3
- package/package.json +10 -2
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* First-run setup wizard for `mma web` (browser).
|
|
3
|
+
*
|
|
4
|
+
* Self-contained: loaded before `app.js`, exposes `window.MmaSetup.maybeOpen()`.
|
|
5
|
+
* `maybeOpen()` returns a Promise<boolean> that resolves `true` when the server
|
|
6
|
+
* reports `setupRequired` and the wizard has taken over the screen (it hides the
|
|
7
|
+
* chat chrome via `body.setup-active` and reloads the page on success). On any
|
|
8
|
+
* other case it resolves `false` and `app.js` boots the chat as usual.
|
|
9
|
+
*
|
|
10
|
+
* Lives outside `app.js` so the chat bundle stays focused; talks to the
|
|
11
|
+
* `/api/setup*` endpoints from `setup-api.ts`. Style follows
|
|
12
|
+
* docs/design/ui/STYLE.md (rows, glyphs, no boxed panels).
|
|
13
|
+
*/
|
|
14
|
+
(() => {
|
|
15
|
+
"use strict";
|
|
16
|
+
|
|
17
|
+
const token = new URLSearchParams(location.search).get("token") || "";
|
|
18
|
+
const gate = document.getElementById("setupGate");
|
|
19
|
+
|
|
20
|
+
const STEPS = ["language", "provider", "key", "model", "context", "security", "review"];
|
|
21
|
+
|
|
22
|
+
const I18N = { locale: "en", dicts: {} };
|
|
23
|
+
|
|
24
|
+
function tr(key, params) {
|
|
25
|
+
const dict = I18N.dicts[I18N.locale] || I18N.dicts.en || {};
|
|
26
|
+
const en = I18N.dicts.en || {};
|
|
27
|
+
let template = dict[key];
|
|
28
|
+
if (template === undefined) template = en[key];
|
|
29
|
+
if (template === undefined) return key;
|
|
30
|
+
if (params) {
|
|
31
|
+
Object.keys(params).forEach((k) => {
|
|
32
|
+
template = template.split("{" + k + "}").join(String(params[k]));
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return template;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function pluralCount(baseKey, n) {
|
|
39
|
+
let form;
|
|
40
|
+
if (I18N.locale === "ru") {
|
|
41
|
+
const m10 = n % 10;
|
|
42
|
+
const m100 = n % 100;
|
|
43
|
+
if (m10 === 1 && m100 !== 11) form = "one";
|
|
44
|
+
else if (m10 >= 2 && m10 <= 4 && (m100 < 10 || m100 >= 20)) form = "few";
|
|
45
|
+
else form = "many";
|
|
46
|
+
} else {
|
|
47
|
+
form = n === 1 ? "one" : "many";
|
|
48
|
+
}
|
|
49
|
+
const key = baseKey + "_" + form;
|
|
50
|
+
const s = tr(key, { count: n });
|
|
51
|
+
return s === key ? tr(baseKey, { count: n }) : s;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function api(path, opts = {}) {
|
|
55
|
+
const headers = Object.assign({}, opts.headers || {});
|
|
56
|
+
if (opts.body !== undefined) headers["content-type"] = "application/json";
|
|
57
|
+
if (token) headers["x-mma-token"] = token;
|
|
58
|
+
let res;
|
|
59
|
+
try {
|
|
60
|
+
res = await fetch(path, {
|
|
61
|
+
method: opts.method || "GET",
|
|
62
|
+
headers,
|
|
63
|
+
body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined,
|
|
64
|
+
});
|
|
65
|
+
} catch (err) {
|
|
66
|
+
return { ok: false, status: 0, data: null };
|
|
67
|
+
}
|
|
68
|
+
let data = null;
|
|
69
|
+
try {
|
|
70
|
+
data = await res.json();
|
|
71
|
+
} catch (err) {
|
|
72
|
+
data = null;
|
|
73
|
+
}
|
|
74
|
+
return { ok: res.ok, status: res.status, data };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function h(tag, cls, text) {
|
|
78
|
+
const node = document.createElement(tag);
|
|
79
|
+
if (cls) node.className = cls;
|
|
80
|
+
if (text != null) node.textContent = text;
|
|
81
|
+
return node;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function loadI18n() {
|
|
85
|
+
const r = await api("/api/i18n");
|
|
86
|
+
if (r.ok && r.data) {
|
|
87
|
+
I18N.locale = r.data.locale || "en";
|
|
88
|
+
I18N.dicts = r.data.dicts || {};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const state = {
|
|
93
|
+
display: null,
|
|
94
|
+
step: 0,
|
|
95
|
+
answers: {
|
|
96
|
+
provider: "",
|
|
97
|
+
apiBase: "",
|
|
98
|
+
apiKey: "",
|
|
99
|
+
model: "",
|
|
100
|
+
contextWindow: 32768,
|
|
101
|
+
maxToolIterations: 1000,
|
|
102
|
+
locale: "en",
|
|
103
|
+
securityBashBlock: false,
|
|
104
|
+
securityFlagsBlock: false,
|
|
105
|
+
securityPathsRestrict: true,
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
let opened = false;
|
|
110
|
+
|
|
111
|
+
async function maybeOpen() {
|
|
112
|
+
if (!gate || opened) return opened;
|
|
113
|
+
const r = await api("/api/setup");
|
|
114
|
+
if (!r.ok || !r.data || !r.data.required) return false;
|
|
115
|
+
|
|
116
|
+
state.display = r.data;
|
|
117
|
+
if (r.data.defaults) {
|
|
118
|
+
state.answers.contextWindow = r.data.defaults.contextWindow || state.answers.contextWindow;
|
|
119
|
+
state.answers.maxToolIterations =
|
|
120
|
+
r.data.defaults.maxToolIterations || state.answers.maxToolIterations;
|
|
121
|
+
}
|
|
122
|
+
const first = (r.data.providers || [])[0];
|
|
123
|
+
if (first) {
|
|
124
|
+
state.answers.provider = first.type;
|
|
125
|
+
if (first.baseUrl) state.answers.apiBase = first.baseUrl;
|
|
126
|
+
}
|
|
127
|
+
await loadI18n();
|
|
128
|
+
state.answers.locale = I18N.locale || "en";
|
|
129
|
+
|
|
130
|
+
document.body.classList.add("setup-active");
|
|
131
|
+
gate.hidden = false;
|
|
132
|
+
opened = true;
|
|
133
|
+
render();
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// --- rendering -------------------------------------------------------------
|
|
138
|
+
|
|
139
|
+
function render() {
|
|
140
|
+
const wrap = h("div", "setup-wrap");
|
|
141
|
+
const title = h("h1", "setup-h");
|
|
142
|
+
title.appendChild(h("span", "glyph", "◇"));
|
|
143
|
+
title.appendChild(document.createTextNode(tr("web.setup.title")));
|
|
144
|
+
wrap.appendChild(title);
|
|
145
|
+
wrap.appendChild(h("p", "setup-sub", tr("web.setup.subtitle")));
|
|
146
|
+
wrap.appendChild(renderSteps());
|
|
147
|
+
wrap.appendChild(renderBody());
|
|
148
|
+
wrap.appendChild(renderActions());
|
|
149
|
+
gate.innerHTML = "";
|
|
150
|
+
gate.appendChild(wrap);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function renderSteps() {
|
|
154
|
+
const box = h("div", "setup-steps");
|
|
155
|
+
STEPS.forEach((name, i) => {
|
|
156
|
+
if (i) box.appendChild(h("span", "sep", "·"));
|
|
157
|
+
const cls = i === state.step ? "s on" : i < state.step ? "s done" : "s";
|
|
158
|
+
box.appendChild(h("span", cls, (i < state.step ? "✓ " : "") + tr("web.setup.step." + name)));
|
|
159
|
+
});
|
|
160
|
+
return box;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function stepBody(name) {
|
|
164
|
+
const box = h("div", "setup-step");
|
|
165
|
+
box.appendChild(h("h2", "setup-step-title", tr("web.setup." + name + ".title")));
|
|
166
|
+
const hint = tr("web.setup." + name + ".hint");
|
|
167
|
+
if (hint !== "web.setup." + name + ".hint") box.appendChild(h("p", "setup-hint", hint));
|
|
168
|
+
return box;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function field(label, input) {
|
|
172
|
+
const box = h("div", "setup-field");
|
|
173
|
+
box.appendChild(h("label", "setup-label", label));
|
|
174
|
+
box.appendChild(input);
|
|
175
|
+
return box;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function inputEl(value, type, placeholder) {
|
|
179
|
+
const input = h("input", "setup-input");
|
|
180
|
+
input.type = type || "text";
|
|
181
|
+
input.value = value || "";
|
|
182
|
+
if (placeholder) input.placeholder = placeholder;
|
|
183
|
+
input.addEventListener("input", () => {
|
|
184
|
+
msg = null;
|
|
185
|
+
});
|
|
186
|
+
return input;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
let msg = null;
|
|
190
|
+
function messageEl() {
|
|
191
|
+
const el = h("div", "setup-msg");
|
|
192
|
+
if (msg) {
|
|
193
|
+
el.className = "setup-msg " + msg.kind;
|
|
194
|
+
el.textContent = msg.text;
|
|
195
|
+
}
|
|
196
|
+
return el;
|
|
197
|
+
}
|
|
198
|
+
function setMsg(box, kind, text) {
|
|
199
|
+
msg = { kind, text };
|
|
200
|
+
const el = box.querySelector(".setup-msg");
|
|
201
|
+
if (el) {
|
|
202
|
+
el.className = "setup-msg " + kind;
|
|
203
|
+
el.textContent = text;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function renderBody() {
|
|
208
|
+
const name = STEPS[state.step];
|
|
209
|
+
if (name === "language") return renderLanguage();
|
|
210
|
+
if (name === "provider") return renderProvider();
|
|
211
|
+
if (name === "key") return renderKey();
|
|
212
|
+
if (name === "model") return renderModel();
|
|
213
|
+
if (name === "context") return renderContext();
|
|
214
|
+
if (name === "security") return renderSecurity();
|
|
215
|
+
return renderReview();
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function renderLanguage() {
|
|
219
|
+
const box = stepBody("language");
|
|
220
|
+
(state.display.languages || []).forEach((l) => {
|
|
221
|
+
const btn = h("button", "setup-opt" + (state.answers.locale === l.value ? " on" : ""));
|
|
222
|
+
btn.type = "button";
|
|
223
|
+
btn.appendChild(h("span", "g", state.answers.locale === l.value ? "✓" : "◇"));
|
|
224
|
+
btn.appendChild(h("span", "t", l.label));
|
|
225
|
+
btn.addEventListener("click", async () => {
|
|
226
|
+
state.answers.locale = l.value;
|
|
227
|
+
const r = await api("/api/setup/locale", { method: "POST", body: { locale: l.value } });
|
|
228
|
+
if (r.ok && r.data && r.data.dicts) {
|
|
229
|
+
I18N.dicts = r.data.dicts;
|
|
230
|
+
I18N.locale = l.value;
|
|
231
|
+
}
|
|
232
|
+
render();
|
|
233
|
+
});
|
|
234
|
+
box.appendChild(btn);
|
|
235
|
+
});
|
|
236
|
+
return box;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function renderProvider() {
|
|
240
|
+
const box = stepBody("provider");
|
|
241
|
+
(state.display.providers || []).forEach((p) => {
|
|
242
|
+
const on = state.answers.provider === p.type;
|
|
243
|
+
const btn = h("button", "setup-opt" + (on ? " on" : ""));
|
|
244
|
+
btn.type = "button";
|
|
245
|
+
btn.appendChild(h("span", "g", on ? "✓" : "◇"));
|
|
246
|
+
btn.appendChild(h("span", "t", p.label));
|
|
247
|
+
btn.addEventListener("click", () => {
|
|
248
|
+
state.answers.provider = p.type;
|
|
249
|
+
state.answers.apiBase = p.baseUrl || "";
|
|
250
|
+
render();
|
|
251
|
+
});
|
|
252
|
+
box.appendChild(btn);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
const input = inputEl(state.answers.apiBase, "text", "http://localhost:1234/v1");
|
|
256
|
+
input.addEventListener("input", () => {
|
|
257
|
+
state.answers.apiBase = input.value.trim();
|
|
258
|
+
});
|
|
259
|
+
box.appendChild(field(tr("web.setup.provider.base_url"), input));
|
|
260
|
+
|
|
261
|
+
if (state.answers.provider === "openai-compat" || state.answers.provider === "lmstudio") {
|
|
262
|
+
const scan = h("button", "setup-btn", tr("web.setup.provider.scan"));
|
|
263
|
+
scan.type = "button";
|
|
264
|
+
const results = h("div");
|
|
265
|
+
scan.addEventListener("click", async () => {
|
|
266
|
+
scan.disabled = true;
|
|
267
|
+
setMsg(box, "", "");
|
|
268
|
+
const r = await api("/api/setup/scan", { method: "POST" });
|
|
269
|
+
scan.disabled = false;
|
|
270
|
+
const servers = (r.ok && r.data && r.data.servers) || [];
|
|
271
|
+
if (!r.ok) {
|
|
272
|
+
setMsg(box, "err", tr("web.setup.provider.scan_failed"));
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
if (!servers.length) {
|
|
276
|
+
setMsg(box, "warn", tr("web.setup.provider.scan_none"));
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
setMsg(box, "ok", pluralCount("web.setup.provider.scan_found", servers.length));
|
|
280
|
+
results.innerHTML = "";
|
|
281
|
+
servers.forEach((srv) => {
|
|
282
|
+
const b = h("button", "setup-opt");
|
|
283
|
+
b.type = "button";
|
|
284
|
+
b.appendChild(h("span", "g", "↳"));
|
|
285
|
+
b.appendChild(h("span", "t", srv.label));
|
|
286
|
+
b.appendChild(h("span", "d", srv.url));
|
|
287
|
+
b.addEventListener("click", () => {
|
|
288
|
+
state.answers.apiBase = srv.url;
|
|
289
|
+
render();
|
|
290
|
+
});
|
|
291
|
+
results.appendChild(b);
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
box.appendChild(scan);
|
|
295
|
+
box.appendChild(results);
|
|
296
|
+
}
|
|
297
|
+
box.appendChild(messageEl());
|
|
298
|
+
return box;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function renderKey() {
|
|
302
|
+
const box = stepBody("key");
|
|
303
|
+
const input = inputEl(state.answers.apiKey, "password", tr("web.setup.key.placeholder"));
|
|
304
|
+
input.addEventListener("input", () => {
|
|
305
|
+
state.answers.apiKey = input.value;
|
|
306
|
+
});
|
|
307
|
+
box.appendChild(field(tr("web.setup.key.label"), input));
|
|
308
|
+
box.appendChild(messageEl());
|
|
309
|
+
return box;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function renderModel() {
|
|
313
|
+
const box = stepBody("model");
|
|
314
|
+
const input = inputEl(state.answers.model, "text", "qwen/qwen3.5-9b");
|
|
315
|
+
input.addEventListener("input", () => {
|
|
316
|
+
state.answers.model = input.value.trim();
|
|
317
|
+
});
|
|
318
|
+
box.appendChild(field(tr("web.setup.model.name"), input));
|
|
319
|
+
|
|
320
|
+
const results = h("div");
|
|
321
|
+
const row = h("div", "setup-row-inline");
|
|
322
|
+
const load = h("button", "setup-btn", tr("web.setup.model.load"));
|
|
323
|
+
load.type = "button";
|
|
324
|
+
const test = h("button", "setup-btn", tr("web.setup.model.test"));
|
|
325
|
+
test.type = "button";
|
|
326
|
+
|
|
327
|
+
load.addEventListener("click", async () => {
|
|
328
|
+
load.disabled = true;
|
|
329
|
+
setMsg(box, "", "");
|
|
330
|
+
const r = await api("/api/setup/models", {
|
|
331
|
+
method: "POST",
|
|
332
|
+
body: { baseUrl: state.answers.apiBase, apiKey: state.answers.apiKey },
|
|
333
|
+
});
|
|
334
|
+
load.disabled = false;
|
|
335
|
+
const models = (r.ok && r.data && r.data.models) || [];
|
|
336
|
+
if (!models.length) {
|
|
337
|
+
setMsg(box, "warn", tr("web.setup.model.load_none"));
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
setMsg(box, "ok", pluralCount("web.setup.model.load_found", models.length));
|
|
341
|
+
results.innerHTML = "";
|
|
342
|
+
models.slice(0, 20).forEach((m) => {
|
|
343
|
+
const b = h("button", "setup-opt" + (state.answers.model === m ? " on" : ""));
|
|
344
|
+
b.type = "button";
|
|
345
|
+
b.appendChild(h("span", "g", state.answers.model === m ? "✓" : "◇"));
|
|
346
|
+
b.appendChild(h("span", "t", m));
|
|
347
|
+
b.addEventListener("click", () => {
|
|
348
|
+
state.answers.model = m;
|
|
349
|
+
input.value = m;
|
|
350
|
+
render();
|
|
351
|
+
});
|
|
352
|
+
results.appendChild(b);
|
|
353
|
+
});
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
test.addEventListener("click", async () => {
|
|
357
|
+
if (!state.answers.model) {
|
|
358
|
+
setMsg(box, "err", tr("web.setup.model.required"));
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
test.disabled = true;
|
|
362
|
+
setMsg(box, "", tr("web.setup.model.testing"));
|
|
363
|
+
const r = await api("/api/setup/test", {
|
|
364
|
+
method: "POST",
|
|
365
|
+
body: {
|
|
366
|
+
baseUrl: state.answers.apiBase,
|
|
367
|
+
apiKey: state.answers.apiKey,
|
|
368
|
+
model: state.answers.model,
|
|
369
|
+
},
|
|
370
|
+
});
|
|
371
|
+
test.disabled = false;
|
|
372
|
+
if (r.ok && r.data && r.data.ok) setMsg(box, "ok", tr("web.setup.model.test_ok"));
|
|
373
|
+
else setMsg(box, "warn", tr("web.setup.model.test_fail"));
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
row.appendChild(load);
|
|
377
|
+
row.appendChild(test);
|
|
378
|
+
box.appendChild(row);
|
|
379
|
+
box.appendChild(results);
|
|
380
|
+
box.appendChild(messageEl());
|
|
381
|
+
return box;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function renderContext() {
|
|
385
|
+
const box = stepBody("context");
|
|
386
|
+
const cw = h("input", "setup-input");
|
|
387
|
+
cw.type = "number";
|
|
388
|
+
cw.min = "1024";
|
|
389
|
+
cw.step = "1024";
|
|
390
|
+
cw.value = String(state.answers.contextWindow);
|
|
391
|
+
cw.addEventListener("input", () => {
|
|
392
|
+
state.answers.contextWindow = parseInt(cw.value, 10) || 0;
|
|
393
|
+
});
|
|
394
|
+
box.appendChild(field(tr("web.setup.context.contextWindow"), cw));
|
|
395
|
+
|
|
396
|
+
const it = h("input", "setup-input");
|
|
397
|
+
it.type = "number";
|
|
398
|
+
it.min = "1";
|
|
399
|
+
it.value = String(state.answers.maxToolIterations);
|
|
400
|
+
it.addEventListener("input", () => {
|
|
401
|
+
state.answers.maxToolIterations = parseInt(it.value, 10) || 0;
|
|
402
|
+
});
|
|
403
|
+
box.appendChild(field(tr("web.setup.context.maxToolIterations"), it));
|
|
404
|
+
box.appendChild(messageEl());
|
|
405
|
+
return box;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function renderSecurity() {
|
|
409
|
+
const box = stepBody("security");
|
|
410
|
+
const toggles = [
|
|
411
|
+
["securityPathsRestrict", "web.setup.security.paths"],
|
|
412
|
+
["securityBashBlock", "web.setup.security.bash"],
|
|
413
|
+
["securityFlagsBlock", "web.setup.security.flags"],
|
|
414
|
+
];
|
|
415
|
+
toggles.forEach(([key, labelKey]) => {
|
|
416
|
+
const row = h("label", "setup-toggle");
|
|
417
|
+
const cb = h("input");
|
|
418
|
+
cb.type = "checkbox";
|
|
419
|
+
cb.checked = Boolean(state.answers[key]);
|
|
420
|
+
cb.addEventListener("change", () => {
|
|
421
|
+
state.answers[key] = cb.checked;
|
|
422
|
+
});
|
|
423
|
+
row.appendChild(cb);
|
|
424
|
+
row.appendChild(h("span", null, tr(labelKey)));
|
|
425
|
+
box.appendChild(row);
|
|
426
|
+
});
|
|
427
|
+
return box;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function securitySummary() {
|
|
431
|
+
const on = [];
|
|
432
|
+
if (state.answers.securityPathsRestrict) on.push(tr("web.setup.security.paths"));
|
|
433
|
+
if (state.answers.securityBashBlock) on.push(tr("web.setup.security.bash"));
|
|
434
|
+
if (state.answers.securityFlagsBlock) on.push(tr("web.setup.security.flags"));
|
|
435
|
+
return on.length ? on.join(", ") : tr("web.setup.review.security_off");
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function maskedKey() {
|
|
439
|
+
const k = state.answers.apiKey;
|
|
440
|
+
if (!k) return tr("web.setup.review.not_needed");
|
|
441
|
+
return k.length <= 4 ? "••••" : "••••" + k.slice(-4);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function renderReview() {
|
|
445
|
+
const box = stepBody("review");
|
|
446
|
+
const rows = [
|
|
447
|
+
[tr("web.setup.review.provider"), state.answers.provider],
|
|
448
|
+
[tr("web.setup.review.baseUrl"), state.answers.apiBase],
|
|
449
|
+
[tr("web.setup.review.apiKey"), maskedKey()],
|
|
450
|
+
[tr("web.setup.review.model"), state.answers.model],
|
|
451
|
+
[tr("web.setup.review.context"), String(state.answers.contextWindow)],
|
|
452
|
+
[tr("web.setup.review.iterations"), String(state.answers.maxToolIterations)],
|
|
453
|
+
[tr("web.setup.review.locale"), state.answers.locale],
|
|
454
|
+
[tr("web.setup.review.security"), securitySummary()],
|
|
455
|
+
];
|
|
456
|
+
const summary = h("div", "setup-summary");
|
|
457
|
+
rows.forEach(([k, v]) => {
|
|
458
|
+
const r = h("div", "r");
|
|
459
|
+
r.appendChild(h("span", "k", k));
|
|
460
|
+
r.appendChild(h("span", "v", v));
|
|
461
|
+
summary.appendChild(r);
|
|
462
|
+
});
|
|
463
|
+
box.appendChild(summary);
|
|
464
|
+
box.appendChild(messageEl());
|
|
465
|
+
return box;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function validate() {
|
|
469
|
+
const name = STEPS[state.step];
|
|
470
|
+
if (name === "provider" && !state.answers.apiBase) return tr("web.setup.provider.required");
|
|
471
|
+
if (name === "model" && !state.answers.model) return tr("web.setup.model.required");
|
|
472
|
+
if (name === "context") {
|
|
473
|
+
if (!(state.answers.contextWindow > 0)) return tr("web.setup.context.required");
|
|
474
|
+
if (!(state.answers.maxToolIterations > 0)) return tr("web.setup.context.required");
|
|
475
|
+
}
|
|
476
|
+
return null;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function renderActions() {
|
|
480
|
+
const box = h("div", "setup-actions");
|
|
481
|
+
const back = h("button", "setup-btn", tr("web.setup.back"));
|
|
482
|
+
back.type = "button";
|
|
483
|
+
back.disabled = state.step === 0;
|
|
484
|
+
back.addEventListener("click", () => {
|
|
485
|
+
if (state.step === 0) return;
|
|
486
|
+
msg = null;
|
|
487
|
+
state.step -= 1;
|
|
488
|
+
render();
|
|
489
|
+
});
|
|
490
|
+
box.appendChild(back);
|
|
491
|
+
box.appendChild(h("span", "spacer"));
|
|
492
|
+
|
|
493
|
+
if (state.step < STEPS.length - 1) {
|
|
494
|
+
const next = h("button", "setup-btn primary", tr("web.setup.next"));
|
|
495
|
+
next.type = "button";
|
|
496
|
+
next.addEventListener("click", () => {
|
|
497
|
+
const err = validate();
|
|
498
|
+
if (err) {
|
|
499
|
+
setMsg(gate.querySelector(".setup-step"), "err", err);
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
msg = null;
|
|
503
|
+
state.step += 1;
|
|
504
|
+
render();
|
|
505
|
+
});
|
|
506
|
+
box.appendChild(next);
|
|
507
|
+
} else {
|
|
508
|
+
const finish = h("button", "setup-btn primary", tr("web.setup.finish"));
|
|
509
|
+
finish.type = "button";
|
|
510
|
+
finish.addEventListener("click", () => finishSetup(finish));
|
|
511
|
+
box.appendChild(finish);
|
|
512
|
+
}
|
|
513
|
+
return box;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
async function finishSetup(btn) {
|
|
517
|
+
const err = validate();
|
|
518
|
+
if (err) {
|
|
519
|
+
setMsg(gate.querySelector(".setup-step"), "err", err);
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
btn.disabled = true;
|
|
523
|
+
setMsg(gate.querySelector(".setup-step"), "", tr("web.setup.saving"));
|
|
524
|
+
const r = await api("/api/setup", {
|
|
525
|
+
method: "POST",
|
|
526
|
+
body: {
|
|
527
|
+
provider: state.answers.provider,
|
|
528
|
+
apiBase: state.answers.apiBase,
|
|
529
|
+
apiKey: state.answers.apiKey,
|
|
530
|
+
model: state.answers.model,
|
|
531
|
+
contextWindow: state.answers.contextWindow,
|
|
532
|
+
maxToolIterations: state.answers.maxToolIterations,
|
|
533
|
+
locale: state.answers.locale,
|
|
534
|
+
securityBashBlock: state.answers.securityBashBlock,
|
|
535
|
+
securityFlagsBlock: state.answers.securityFlagsBlock,
|
|
536
|
+
securityPathsRestrict: state.answers.securityPathsRestrict,
|
|
537
|
+
},
|
|
538
|
+
});
|
|
539
|
+
if (!r.ok) {
|
|
540
|
+
btn.disabled = false;
|
|
541
|
+
setMsg(gate.querySelector(".setup-step"), "err", tr("web.setup.failed"));
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
if (r.data && r.data.warning) {
|
|
545
|
+
setMsg(
|
|
546
|
+
gate.querySelector(".setup-step"),
|
|
547
|
+
"warn",
|
|
548
|
+
tr("web.setup.saved_warning", { warning: r.data.warning }),
|
|
549
|
+
);
|
|
550
|
+
setTimeout(() => location.reload(), 1500);
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
location.reload();
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
window.MmaSetup = { maybeOpen };
|
|
557
|
+
})();
|