dsh-neotui 0.1.24 → 0.1.26
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/panels.js +73 -28
package/package.json
CHANGED
package/src/panels.js
CHANGED
|
@@ -1833,6 +1833,8 @@ export class ModelPanel extends Widget {
|
|
|
1833
1833
|
this.modelsSel = -1; // selected model row (shows its subfields)
|
|
1834
1834
|
this.draftRoute = null; // the un-saved new provider's route (shows the rename field)
|
|
1835
1835
|
this.editing = null; // { label, commit } while the inline editor is open
|
|
1836
|
+
this.sub = null; // the 模型管理 sub-buffer ({ cursor })
|
|
1837
|
+
this.subItems = [];
|
|
1836
1838
|
this.scanMode = false;
|
|
1837
1839
|
this.scanItems = [];
|
|
1838
1840
|
this.scanSel = new Set();
|
|
@@ -1875,6 +1877,22 @@ export class ModelPanel extends Widget {
|
|
|
1875
1877
|
items.push({ kind: "field", key: "displayName", label: "显示名", value: p.displayName ?? "" });
|
|
1876
1878
|
items.push({ kind: "field", key: "baseURL", label: "baseURL", value: p.baseURL ?? "" });
|
|
1877
1879
|
items.push({ kind: "field", key: "apiKeyEnv", label: "apiKeyEnv", value: p.apiKeyEnv ?? "", note: "环境变量名(密钥不落盘)" });
|
|
1880
|
+
// models are NOT flat here: one 模型管理 entry summarizing the first
|
|
1881
|
+
// five, which opens its own sub-buffer (scan on top, model form below)
|
|
1882
|
+
const models = p.models ?? [];
|
|
1883
|
+
const names = models.slice(0, 5).map((m) => m.id || "(未命名)").join(" · ");
|
|
1884
|
+
items.push({ kind: "button", label: "模型管理", sub: names + (models.length > 5 ? " · …" : ""), action: () => this.#openModels() });
|
|
1885
|
+
items.push({ kind: "button", label: "💾 保存配置", action: () => this.#save() });
|
|
1886
|
+
items.push({ kind: "button", label: "🗑 删除供应商", action: () => this.#deleteProvider() });
|
|
1887
|
+
return items;
|
|
1888
|
+
}
|
|
1889
|
+
/** The 模型管理 sub-buffer: scan first, then the model-info form rows. */
|
|
1890
|
+
#subItems() {
|
|
1891
|
+
const route = this.#route();
|
|
1892
|
+
if (route == null) return [];
|
|
1893
|
+
const p = this.#profile(route);
|
|
1894
|
+
const items = [];
|
|
1895
|
+
items.push({ kind: "button", label: "🔄 自动扫描可用模型(读 /models 端点)", action: () => this.#scan() });
|
|
1878
1896
|
const models = p.models ?? [];
|
|
1879
1897
|
for (let mi = 0; mi < models.length; mi++) {
|
|
1880
1898
|
const m = models[mi];
|
|
@@ -1888,12 +1906,15 @@ export class ModelPanel extends Widget {
|
|
|
1888
1906
|
}
|
|
1889
1907
|
items.push({ kind: "button", label: "+ 添加模型", action: () => this.#addModel() });
|
|
1890
1908
|
items.push({ kind: "button", label: "🗑 删除选中模型", action: () => this.#deleteModel() });
|
|
1891
|
-
items.push({ kind: "button", label: "🔄 自动扫描模型(读 /models 端点)", action: () => this.#scan() });
|
|
1892
1909
|
items.push({ kind: "button", label: "◉ 设为当前会话模型(选中模型)", action: () => this.#setDefaultModel() });
|
|
1893
|
-
items.push({ kind: "button", label: "💾 保存配置", action: () => this.#save() });
|
|
1894
|
-
items.push({ kind: "button", label: "🗑 删除供应商", action: () => this.#deleteProvider() });
|
|
1895
1910
|
return items;
|
|
1896
1911
|
}
|
|
1912
|
+
#openModels() {
|
|
1913
|
+
this.sub = { cursor: 0 };
|
|
1914
|
+
this.modelsSel = -1;
|
|
1915
|
+
this.#rebuild();
|
|
1916
|
+
this.app.redraw();
|
|
1917
|
+
}
|
|
1897
1918
|
#rebuild() {
|
|
1898
1919
|
// left list: the cursor is ALWAYS visible (● on the selected provider),
|
|
1899
1920
|
// and the row being edited shows ✎ — the mode only changes the color.
|
|
@@ -1930,11 +1951,16 @@ export class ModelPanel extends Widget {
|
|
|
1930
1951
|
formLines.push([{ t: " Enter 添加选中 · Esc 取消扫描", fg: K.FAINT }]);
|
|
1931
1952
|
this.formItems = [];
|
|
1932
1953
|
} else {
|
|
1933
|
-
this.
|
|
1954
|
+
const isSub = this.sub != null;
|
|
1955
|
+
const items = isSub ? this.#subItems() : this.#formRows();
|
|
1956
|
+
if (isSub) this.subItems = items;
|
|
1957
|
+
else this.formItems = items;
|
|
1934
1958
|
const w = Math.max(30, this.formView.w - 4);
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1959
|
+
const cursor = isSub ? this.sub.cursor : this.formIdx;
|
|
1960
|
+
if (isSub) formLines.push([{ t: ` 模型管理 — ${truncate(this.#profile(route).displayName || route, 30)} (Esc 返回)`, fg: K.ACCENT, bold: true }]);
|
|
1961
|
+
for (let i = 0; i < items.length; i++) {
|
|
1962
|
+
const it = items[i];
|
|
1963
|
+
const cur = i === cursor;
|
|
1938
1964
|
let t;
|
|
1939
1965
|
if (it.kind === "field") {
|
|
1940
1966
|
const v = it.value === "" || it.value == null ? "(空)" : String(it.value);
|
|
@@ -1946,8 +1972,12 @@ export class ModelPanel extends Widget {
|
|
|
1946
1972
|
t = ` ${cur ? "▸" : " "} ${it.label}`;
|
|
1947
1973
|
}
|
|
1948
1974
|
formLines.push([{ t: truncate(t, w), fg: cur ? T.SELFG : T.TXT, bg: cur ? T.MENUSEL : T.BG2 }]);
|
|
1975
|
+
// the 模型管理 preview: an indented, non-focusable summary line
|
|
1976
|
+
if (!isSub && it.kind === "button" && it.sub) {
|
|
1977
|
+
formLines.push([{ t: ` ${truncate(it.sub, w - 8)}`, fg: K.FAINT, bg: T.BG2 }]);
|
|
1978
|
+
}
|
|
1949
1979
|
}
|
|
1950
|
-
formLines.push([{ t: " ↑/↓
|
|
1980
|
+
formLines.push([{ t: isSub ? " ↑/↓ 移动 · Enter 编辑或执行 · Esc 返回" : " ↑/↓ 移动 · → 进入选项 · ← 返回列表 · Enter 编辑或执行 · Esc 退出", fg: K.FAINT }]);
|
|
1951
1981
|
}
|
|
1952
1982
|
this.formView.setLines(formLines);
|
|
1953
1983
|
}
|
|
@@ -1999,8 +2029,10 @@ export class ModelPanel extends Widget {
|
|
|
1999
2029
|
this.app.redraw();
|
|
2000
2030
|
return;
|
|
2001
2031
|
}
|
|
2002
|
-
// form
|
|
2003
|
-
const
|
|
2032
|
+
// form (or the 模型管理 sub-buffer — same item kinds, different source)
|
|
2033
|
+
const items = this.sub != null ? this.subItems : this.formItems;
|
|
2034
|
+
const idx = this.sub != null ? this.sub.cursor : this.formIdx;
|
|
2035
|
+
const it = items[idx];
|
|
2004
2036
|
if (!it) return;
|
|
2005
2037
|
const route = this.#route();
|
|
2006
2038
|
const p = this.#profile(route);
|
|
@@ -2194,40 +2226,47 @@ export class ModelPanel extends Widget {
|
|
|
2194
2226
|
if (ev.name === "enter") { this.#scanCommit(); return true; }
|
|
2195
2227
|
return false;
|
|
2196
2228
|
}
|
|
2229
|
+
if (this.sub != null) {
|
|
2230
|
+
// inside the 模型管理 sub-buffer: ↑/↓ walk its rows; Esc returns
|
|
2231
|
+
if (ev.name === "escape") { this.sub = null; this.#rebuild(); return true; }
|
|
2232
|
+
if (ev.name === "up" || (ev.name === "char" && ev.key === "k" && !ev.ctrl)) {
|
|
2233
|
+
this.sub.cursor = Math.max(0, this.sub.cursor - 1);
|
|
2234
|
+
this.app.redraw();
|
|
2235
|
+
return true;
|
|
2236
|
+
}
|
|
2237
|
+
if (ev.name === "down" || (ev.name === "char" && ev.key === "j" && !ev.ctrl)) {
|
|
2238
|
+
this.sub.cursor = Math.min(Math.max(0, this.#subItems().length - 1), this.sub.cursor + 1);
|
|
2239
|
+
this.app.redraw();
|
|
2240
|
+
return true;
|
|
2241
|
+
}
|
|
2242
|
+
if (ev.name === "enter") { this.#activateItem(); return true; }
|
|
2243
|
+
return false;
|
|
2244
|
+
}
|
|
2197
2245
|
if (ev.name === "escape") { this.editing = null; return false; } // App falls back to chat mode
|
|
2198
|
-
//
|
|
2199
|
-
//
|
|
2246
|
+
// dual-focus navigation: ↑/↓ move the cursor INSIDE the focused region —
|
|
2247
|
+
// the provider column in list focus, the option rows in form focus.
|
|
2248
|
+
// → enters the form, ← returns to the list.
|
|
2200
2249
|
if (ev.name === "up" || (ev.name === "char" && ev.key === "k" && !ev.ctrl)) {
|
|
2201
|
-
this.sel = Math.max(0, this.sel - 1);
|
|
2202
|
-
this.
|
|
2250
|
+
if (this.mode === "list") this.sel = Math.max(0, this.sel - 1);
|
|
2251
|
+
else this.formIdx = Math.max(0, this.formIdx - 1);
|
|
2203
2252
|
this.#rebuild();
|
|
2204
2253
|
this.app.redraw();
|
|
2205
2254
|
return true;
|
|
2206
2255
|
}
|
|
2207
2256
|
if (ev.name === "down" || (ev.name === "char" && ev.key === "j" && !ev.ctrl)) {
|
|
2208
|
-
this.sel = Math.min(this.routes.length, this.sel + 1);
|
|
2209
|
-
this.
|
|
2257
|
+
if (this.mode === "list") this.sel = Math.min(this.routes.length, this.sel + 1);
|
|
2258
|
+
else this.formIdx = Math.min(Math.max(0, this.formItems.length - 1), this.formIdx + 1);
|
|
2210
2259
|
this.#rebuild();
|
|
2211
2260
|
this.app.redraw();
|
|
2212
2261
|
return true;
|
|
2213
2262
|
}
|
|
2214
|
-
// →/Tab: into the form, then down its rows; ←/Shift+Tab: up the form,
|
|
2215
|
-
// and at the top back to the provider list
|
|
2216
2263
|
if (ev.name === "right" || (ev.name === "char" && ev.key === "l" && !ev.ctrl) || ev.name === "tab") {
|
|
2217
|
-
if (this.#route() != null) {
|
|
2218
|
-
if (this.mode === "form") this.formIdx = Math.min(Math.max(0, this.formItems.length - 1), this.formIdx + 1);
|
|
2219
|
-
else this.mode = "form";
|
|
2220
|
-
this.#rebuild();
|
|
2221
|
-
}
|
|
2264
|
+
if (this.#route() != null && this.mode !== "form") { this.mode = "form"; this.#rebuild(); }
|
|
2222
2265
|
this.app.redraw();
|
|
2223
2266
|
return true;
|
|
2224
2267
|
}
|
|
2225
2268
|
if (ev.name === "left" || (ev.name === "char" && ev.key === "h" && !ev.ctrl) || ev.name === "backtab") {
|
|
2226
|
-
if (this.mode === "form") {
|
|
2227
|
-
if (this.formIdx > 0) this.formIdx--;
|
|
2228
|
-
else this.mode = "list";
|
|
2229
|
-
this.#rebuild();
|
|
2230
|
-
}
|
|
2269
|
+
if (this.mode === "form") { this.mode = "list"; this.#rebuild(); }
|
|
2231
2270
|
this.app.redraw();
|
|
2232
2271
|
return true;
|
|
2233
2272
|
}
|
|
@@ -2257,6 +2296,12 @@ export class ModelPanel extends Widget {
|
|
|
2257
2296
|
return false;
|
|
2258
2297
|
}
|
|
2259
2298
|
const idx = ev.y - this.formView.y + this.formView.scrollY;
|
|
2299
|
+
if (this.sub != null) {
|
|
2300
|
+
// the sub-buffer's title row is visual-only; rows below it are items
|
|
2301
|
+
const itemIdx = idx - 1;
|
|
2302
|
+
if (itemIdx >= 0 && itemIdx < this.#subItems().length) { this.sub.cursor = itemIdx; this.#activateItem(); return true; }
|
|
2303
|
+
return false;
|
|
2304
|
+
}
|
|
2260
2305
|
if (idx >= 0 && idx < this.formItems.length) { this.formIdx = idx; this.mode = "form"; this.#activateItem(); return true; }
|
|
2261
2306
|
return false;
|
|
2262
2307
|
}
|