@xingjisoft/neutrino 0.2.1 → 0.4.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/README.md +312 -436
- package/assets/pro/backend_output_model.txt +5 -0
- package/assets/pro/claude-module-prompt.txt +65 -0
- package/assets/pro/common.txt +9 -0
- package/assets/pro/confirm_model.txt +5 -0
- package/assets/pro/front_output_model.txt +9 -0
- package/assets/pro/genimage_model.txt +1 -0
- package/assets/pro/main_model.txt +5 -0
- package/assets/pro/model-template.json +376 -0
- package/assets/pro/modeling_model.txt +5 -0
- package/assets/pro/plan_model.txt +8 -0
- package/assets/pro/thinking_model.txt +3 -0
- package/dist/app.js +43 -7
- package/dist/bin.js +5 -2
- package/dist/browser.js +57 -0
- package/dist/config.js +179 -26
- package/dist/index.js +1 -1
- package/dist/llm/approval.js +6 -2
- package/dist/llm/client.js +197 -28
- package/dist/llm/subagent.js +37 -1
- package/dist/pro/agents.js +104 -0
- package/dist/pro/config.js +88 -0
- package/dist/pro/preflight.js +172 -0
- package/dist/screens/chat.js +54 -38
- package/dist/screens/modelSelect.js +15 -4
- package/dist/screens/settings.js +9 -7
- package/dist/screens/start.js +48 -15
- package/dist/ui.js +58 -0
- package/dist/web/hub.js +31 -4
- package/dist/web/server.js +611 -50
- package/dist/web/session.js +320 -20
- package/dist/web/settings.js +117 -24
- package/package.json +37 -37
- package/web/README.md +158 -16
- package/web/app.js +1603 -128
- package/web/i18n.js +382 -0
- package/web/index.html +289 -37
- package/web/markdown.js +710 -0
- package/web/neutrino-pro.gif +0 -0
- package/web/neutrino.png +0 -0
- package/web/pro.js +184 -0
- package/web/settings.js +322 -193
- package/web/style.css +1378 -42
package/web/pro.js
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { t, getLanguage } from "./i18n.js";
|
|
2
|
+
|
|
3
|
+
const ROLES = {
|
|
4
|
+
main_model: "Main model", thinking_model: "Reasoning model",
|
|
5
|
+
front_output_model: "Frontend model", backend_output_model: "Backend model",
|
|
6
|
+
confirm_model: "Approval model", genimage_model: "Image model",
|
|
7
|
+
plan_model: "Planning model", modeling_model: "Modeling model",
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/** The preset is a conversation choice; effective fallbacks belong to one task. */
|
|
11
|
+
export function createProUI({ changePreset }) {
|
|
12
|
+
const control = document.getElementById("pro-preset-control");
|
|
13
|
+
if (!control) return null;
|
|
14
|
+
const label = document.getElementById("pro-preset-label");
|
|
15
|
+
const slider = document.getElementById("pro-preset-slider");
|
|
16
|
+
const stops = document.getElementById("pro-slider-stops");
|
|
17
|
+
const range = document.getElementById("pro-preset-range");
|
|
18
|
+
const name = document.getElementById("pro-preset-name");
|
|
19
|
+
const ticks = document.getElementById("pro-preset-ticks");
|
|
20
|
+
const models = document.getElementById("pro-preset-models");
|
|
21
|
+
const note = document.getElementById("pro-preset-note");
|
|
22
|
+
const feedback = document.getElementById("pro-preset-feedback");
|
|
23
|
+
const fallbackDialog = document.getElementById("pro-fallback-dialog");
|
|
24
|
+
const fallbackRows = document.getElementById("pro-fallback-rows");
|
|
25
|
+
const fallbackError = document.getElementById("pro-fallback-error");
|
|
26
|
+
const seenChecks = new Set();
|
|
27
|
+
let presets = [];
|
|
28
|
+
let selected = "";
|
|
29
|
+
let committed = "";
|
|
30
|
+
let defaultPreset = "";
|
|
31
|
+
let target = null;
|
|
32
|
+
let snapshot = null;
|
|
33
|
+
let applying = false;
|
|
34
|
+
let pending = Promise.resolve();
|
|
35
|
+
let failure = null;
|
|
36
|
+
const title = preset => getLanguage() === "zh" ? preset.chinese_name || preset.name : preset.name;
|
|
37
|
+
const presetFor = value => presets.find(entry => entry.name === value);
|
|
38
|
+
const modelName = value => value == null || value === "null" || value === "" ? t("Reserved") : value;
|
|
39
|
+
const reference = value => value?.provider ? `${value.provider}/${modelName(value.model)}` : modelName(value?.model);
|
|
40
|
+
|
|
41
|
+
function render() {
|
|
42
|
+
const preset = presetFor(selected) || presets[0];
|
|
43
|
+
control.classList.toggle("hidden", !preset);
|
|
44
|
+
if (!preset) return;
|
|
45
|
+
selected = preset.name;
|
|
46
|
+
const busy = Boolean(snapshot?.busy || snapshot?.approval || applying);
|
|
47
|
+
label.textContent = `Pro · ${title(preset)}`;
|
|
48
|
+
name.textContent = title(preset);
|
|
49
|
+
const selectedIndex = presets.indexOf(preset);
|
|
50
|
+
const lastIndex = presets.length - 1;
|
|
51
|
+
const position = lastIndex > 0 ? selectedIndex / lastIndex : 0;
|
|
52
|
+
slider.setAttribute("style", `--pro-slider-position: ${position}`);
|
|
53
|
+
slider.classList.toggle("is-disabled", busy);
|
|
54
|
+
control.classList.toggle("is-max", lastIndex > 0 && selectedIndex === lastIndex);
|
|
55
|
+
stops.replaceChildren();
|
|
56
|
+
presets.forEach((_entry, index) => {
|
|
57
|
+
const stop = document.createElement("span");
|
|
58
|
+
stop.className = "pro-slider-stop";
|
|
59
|
+
stop.setAttribute("style", `left: ${lastIndex > 0 ? index / lastIndex * 100 : 0}%`);
|
|
60
|
+
stop.classList.toggle("is-filled", index <= selectedIndex);
|
|
61
|
+
stops.appendChild(stop);
|
|
62
|
+
});
|
|
63
|
+
range.max = String(Math.max(0, presets.length - 1));
|
|
64
|
+
range.value = String(selectedIndex);
|
|
65
|
+
range.disabled = busy;
|
|
66
|
+
range.setAttribute("aria-valuetext", title(preset));
|
|
67
|
+
ticks.replaceChildren();
|
|
68
|
+
presets.forEach((entry, index) => {
|
|
69
|
+
const tick = document.createElement("button");
|
|
70
|
+
tick.type = "button";
|
|
71
|
+
tick.textContent = title(entry);
|
|
72
|
+
tick.disabled = busy;
|
|
73
|
+
tick.setAttribute("style", `left: ${lastIndex > 0 ? index / lastIndex * 100 : 0}%`);
|
|
74
|
+
tick.setAttribute("aria-pressed", String(entry.name === selected));
|
|
75
|
+
tick.addEventListener("click", () => choose(entry.name));
|
|
76
|
+
ticks.appendChild(tick);
|
|
77
|
+
});
|
|
78
|
+
models.replaceChildren();
|
|
79
|
+
for (const role of Object.keys(ROLES)) {
|
|
80
|
+
const configured = preset["model-setting"]?.find(entry => entry.type === role);
|
|
81
|
+
const effective = snapshot?.pro?.preset === selected && !snapshot.pro.checking
|
|
82
|
+
? snapshot.pro.models?.find(entry => entry.role === role) : null;
|
|
83
|
+
const row = document.createElement("div");
|
|
84
|
+
row.className = "pro-model-row";
|
|
85
|
+
const roleLabel = document.createElement("span");
|
|
86
|
+
roleLabel.textContent = t(ROLES[role]);
|
|
87
|
+
const value = document.createElement("span");
|
|
88
|
+
value.textContent = modelName(effective?.model ?? configured?.model);
|
|
89
|
+
value.title = [effective?.provider ?? configured?.provider, effective?.intensity ?? configured?.intensity].filter(Boolean).join(" · ");
|
|
90
|
+
if (effective && effective.tier !== selected) {
|
|
91
|
+
value.classList.add("pro-model-fallback");
|
|
92
|
+
value.title += ` · ${t("Temporary fallback")}: ${effective.tier}`;
|
|
93
|
+
}
|
|
94
|
+
row.append(roleLabel, value);
|
|
95
|
+
models.appendChild(row);
|
|
96
|
+
}
|
|
97
|
+
note.textContent = snapshot?.pro?.checking
|
|
98
|
+
? t("Checking every professional model before this task…")
|
|
99
|
+
: busy ? t("Pause the current turn to change presets.")
|
|
100
|
+
: t("Models are checked before each task. Unavailable roles temporarily use a lower preset.");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function choose(value) {
|
|
104
|
+
if (snapshot?.busy || snapshot?.approval || applying || !presetFor(value)) return;
|
|
105
|
+
const previous = committed || selected;
|
|
106
|
+
const originalTarget = target;
|
|
107
|
+
selected = value;
|
|
108
|
+
failure = null;
|
|
109
|
+
feedback.textContent = "";
|
|
110
|
+
applying = true;
|
|
111
|
+
render();
|
|
112
|
+
pending = Promise.resolve().then(() => changePreset(value)).then(() => {
|
|
113
|
+
if (target === originalTarget) committed = value;
|
|
114
|
+
}).catch(error => {
|
|
115
|
+
if (target !== originalTarget) return;
|
|
116
|
+
failure = error;
|
|
117
|
+
selected = previous;
|
|
118
|
+
feedback.textContent = error.message;
|
|
119
|
+
}).finally(() => { applying = false; render(); });
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function showFallbacks(pro, key) {
|
|
123
|
+
if (!pro || pro.checking || !pro.checkId) return;
|
|
124
|
+
const id = `${key || ""}:${pro.checkId}`;
|
|
125
|
+
if (seenChecks.has(id)) return;
|
|
126
|
+
seenChecks.add(id);
|
|
127
|
+
if (!(pro.fallbacks?.length) && !pro.error) return;
|
|
128
|
+
fallbackRows.replaceChildren();
|
|
129
|
+
for (const fallback of pro.fallbacks || []) {
|
|
130
|
+
const row = document.createElement("div");
|
|
131
|
+
row.className = "pro-fallback-row";
|
|
132
|
+
const role = document.createElement("strong");
|
|
133
|
+
role.textContent = t(ROLES[fallback.role] || fallback.role);
|
|
134
|
+
const from = document.createElement("span");
|
|
135
|
+
from.textContent = `${reference(fallback.requested)} → ${fallback.replacement ? reference(fallback.replacement) : t("No available model")}`;
|
|
136
|
+
const detail = document.createElement("small");
|
|
137
|
+
detail.textContent = [fallback.reason, fallback.replacement?.tier].filter(Boolean).join(" · ");
|
|
138
|
+
row.append(role, from, detail);
|
|
139
|
+
fallbackRows.appendChild(row);
|
|
140
|
+
}
|
|
141
|
+
fallbackError.textContent = pro.error || "";
|
|
142
|
+
fallbackError.classList.toggle("hidden", !pro.error);
|
|
143
|
+
if (!fallbackDialog.open) fallbackDialog.showModal();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
range.addEventListener("input", () => {
|
|
147
|
+
if (snapshot?.busy || applying) return;
|
|
148
|
+
selected = presets[Number(range.value)]?.name || selected;
|
|
149
|
+
render();
|
|
150
|
+
});
|
|
151
|
+
range.addEventListener("change", () => choose(selected));
|
|
152
|
+
control.addEventListener("keydown", event => {
|
|
153
|
+
if (event.key === "Escape") { control.open = false; event.stopPropagation(); }
|
|
154
|
+
});
|
|
155
|
+
document.addEventListener("pointerdown", event => {
|
|
156
|
+
if (control.open && !control.contains(event.target)) control.open = false;
|
|
157
|
+
});
|
|
158
|
+
document.getElementById("pro-fallback-close").addEventListener("click", () => fallbackDialog.close());
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
load(catalog) {
|
|
162
|
+
presets = Array.isArray(catalog?.pro?.presets) ? catalog.pro.presets : [];
|
|
163
|
+
defaultPreset = catalog?.pro?.preset || presets[0]?.name || "";
|
|
164
|
+
if (!presetFor(selected) || (!target && !applying)) selected = defaultPreset;
|
|
165
|
+
if (!committed) committed = selected;
|
|
166
|
+
render();
|
|
167
|
+
},
|
|
168
|
+
show(info, key) {
|
|
169
|
+
if (target !== key) {
|
|
170
|
+
selected = info?.pro?.preset || info?.pro_preset || defaultPreset;
|
|
171
|
+
committed = selected;
|
|
172
|
+
target = key;
|
|
173
|
+
failure = null;
|
|
174
|
+
feedback.textContent = "";
|
|
175
|
+
} else if (!applying && info?.pro?.preset) committed = selected = info.pro.preset;
|
|
176
|
+
snapshot = info;
|
|
177
|
+
render();
|
|
178
|
+
showFallbacks(info?.pro, key);
|
|
179
|
+
},
|
|
180
|
+
refresh: render,
|
|
181
|
+
preset: () => selected,
|
|
182
|
+
async flush() { await pending; if (failure) throw failure; },
|
|
183
|
+
};
|
|
184
|
+
}
|