dsh-neotui 0.1.27 → 0.1.29

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/panels.js +72 -27
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-neotui",
3
- "version": "0.1.27",
3
+ "version": "0.1.29",
4
4
  "description": "Neo-TUI: mouse-driven terminal UI client for DeepSeek Harness (B-tier per dsh-tui-design.md)",
5
5
  "type": "module",
6
6
  "bin": {
package/src/panels.js CHANGED
@@ -1818,6 +1818,65 @@ export class SettingsPanel extends Widget {
1818
1818
 
1819
1819
  // ---- Model provider management (simple form buffer) ----
1820
1820
 
1821
+ /** A centered standalone edit buffer (ControlPanel-style): its own Input with
1822
+ * a visible caret, full key isolation from NORMAL/INSERT routing, and paste
1823
+ * support — Enter commits, Esc cancels. */
1824
+ export class EditPopup extends Popup {
1825
+ constructor(app, { title, value, onCommit }) {
1826
+ const w = Math.min(80, app.screen.w - 8);
1827
+ const h = Math.min(16, app.screen.h - 6);
1828
+ super({
1829
+ x: Math.floor((app.screen.w - w) / 2), y: Math.floor((app.screen.h - h) / 2),
1830
+ w, h, title,
1831
+ lines: [],
1832
+ buttons: [],
1833
+ onAction: () => {},
1834
+ });
1835
+ this.app = app;
1836
+ this.onCommit = onCommit;
1837
+ this.input = new Input({
1838
+ x: this.x + 2, y: this.y + h - 2, w: w - 4, h: 1,
1839
+ multi: true, maxLines: 4, app,
1840
+ prompt: "> ", placeholder: "输入值…(Ctrl+Shift+V 粘贴,Enter 确定,Esc 取消)",
1841
+ });
1842
+ // the cursor starts at the END of the existing value: typing appends and
1843
+ // edits in place instead of wiping the original (modify, not replace)
1844
+ this.input.setValue(String(value ?? ""));
1845
+ this.#layout();
1846
+ }
1847
+ #layout() {
1848
+ const lines = [];
1849
+ lines.push([{ t: " 当前值预览:", fg: K.DIM, underline: true }]);
1850
+ const v = this.input.value;
1851
+ if (v === "") lines.push([{ t: "(空)", fg: K.FAINT }]);
1852
+ else for (const ln of v.split("\n").slice(0, 6)) lines.push([{ t: " " + truncate(ln, this.w - 6), fg: K.TXT }]);
1853
+ lines.push([{ t: "" }]);
1854
+ this.lines = lines;
1855
+ }
1856
+ render(screen) {
1857
+ super.render(screen);
1858
+ this.input.render(screen);
1859
+ }
1860
+ onKey(ev) {
1861
+ if (ev.type === "key" && ev.name === "escape") { this.app.closeOverlay(); this.app.focus(this.app.chat); return true; }
1862
+ if (ev.type === "key" && ev.name === "enter") {
1863
+ const v = this.input.value;
1864
+ this.app.closeOverlay();
1865
+ this.app.focus(this.app.chat);
1866
+ this.onCommit?.(v);
1867
+ return true;
1868
+ }
1869
+ const handled = this.input.onKey(ev);
1870
+ if (handled) this.#layout();
1871
+ this.app.redraw();
1872
+ return true;
1873
+ }
1874
+ onMouse(ev) {
1875
+ if (this.input.inside(ev.x, ev.y)) { this.input.onMouse(ev); this.app.redraw(); return true; }
1876
+ return super.onMouse(ev);
1877
+ }
1878
+ }
1879
+
1821
1880
  export class ModelPanel extends Widget {
1822
1881
  constructor(app) {
1823
1882
  super({ x: 30, y: 0, w: app.screen.w - 30, h: app.screen.h - 1 });
@@ -1842,15 +1901,13 @@ export class ModelPanel extends Widget {
1842
1901
  this.scanning = false;
1843
1902
  const listW = 26;
1844
1903
  this.listView = new ScrollView({ x: this.x + 1, y: this.y + 1, w: listW, h: this.h - 2, showScrollbar: true });
1845
- this.formView = new ScrollView({ x: this.x + listW + 1, y: this.y + 1, w: this.w - listW - 2, h: this.h - 3, showScrollbar: true });
1846
- this.input = new Input({ x: this.x + listW + 1, y: this.y + this.h - 2, w: this.w - listW - 2, h: 1, prompt: "值: ", placeholder: "Enter 提交 · Esc 取消" });
1904
+ this.formView = new ScrollView({ x: this.x + listW + 1, y: this.y + 1, w: this.w - listW - 2, h: this.h - 2, showScrollbar: true });
1847
1905
  }
1848
1906
  relayout(x, y, w, h) {
1849
1907
  this.x = x; this.y = y; this.w = w; this.h = h;
1850
1908
  const listW = 26;
1851
1909
  this.listView.x = x + 1; this.listView.y = y + 1; this.listView.w = listW; this.listView.h = h - 2;
1852
- this.formView.x = x + listW + 1; this.formView.y = y + 1; this.formView.w = w - listW - 2; this.formView.h = h - 3;
1853
- this.input.x = x + listW + 1; this.input.y = y + h - 2; this.input.w = w - listW - 2;
1910
+ this.formView.x = x + listW + 1; this.formView.y = y + 1; this.formView.w = w - listW - 2; this.formView.h = h - 2;
1854
1911
  }
1855
1912
  async load() {
1856
1913
  try {
@@ -1875,6 +1932,7 @@ export class ModelPanel extends Widget {
1875
1932
  // the route key only appears for a brand-new draft (rename once)
1876
1933
  if (this.draftRoute === route) items.push({ kind: "field", key: "route", label: "路由名", value: route });
1877
1934
  items.push({ kind: "field", key: "displayName", label: "显示名", value: p.displayName ?? "" });
1935
+ items.push({ kind: "field", key: "api", label: "协议 api", value: p.api ?? "openai-completions" });
1878
1936
  items.push({ kind: "field", key: "baseURL", label: "baseURL", value: p.baseURL ?? "" });
1879
1937
  items.push({ kind: "field", key: "apiKeyEnv", label: "apiKeyEnv", value: p.apiKeyEnv ?? "", note: "环境变量名(密钥不落盘)" });
1880
1938
  // models are NOT flat here: one 模型管理 entry summarizing the first
@@ -1988,22 +2046,17 @@ export class ModelPanel extends Widget {
1988
2046
  screen.text(this.x + 1, this.y, " 模型供应商", { fg: K.DIM });
1989
2047
  this.listView.render(screen);
1990
2048
  this.formView.render(screen);
1991
- if (this.editing) {
1992
- screen.text(this.x + 28, this.y + this.h - 3, `编辑 ${this.editing.label}`, { fg: K.WARN, bold: true });
1993
- this.input.render(screen);
1994
- }
1995
2049
  }
1996
2050
  #startEdit(label, value, commit) {
1997
- this.editing = { label, commit };
1998
- this.input.setValue(value ?? "", { select: true });
1999
- this.app.redraw();
2000
- }
2001
- #commitEdit() {
2002
- if (!this.editing) return;
2003
- const { label, commit } = this.editing;
2004
- this.editing = null;
2005
- commit(this.input.value);
2006
- this.#rebuild();
2051
+ // a REAL standalone edit buffer in the middle of the window: own caret,
2052
+ // isolated from NORMAL/INSERT routing, paste supported
2053
+ const popup = new EditPopup(this.app, {
2054
+ title: `编辑 ${label}`,
2055
+ value,
2056
+ onCommit: (text) => { commit(text); this.#rebuild(); this.app.redraw(); },
2057
+ });
2058
+ this.app.overlay = popup;
2059
+ this.app.focus(popup.input);
2007
2060
  this.app.redraw();
2008
2061
  }
2009
2062
  #activateItem() {
@@ -2206,13 +2259,6 @@ export class ModelPanel extends Widget {
2206
2259
  }
2207
2260
  onKey(ev) {
2208
2261
  if (ev.type !== "key") return false;
2209
- if (this.editing) {
2210
- if (ev.name === "escape") { this.editing = null; this.#rebuild(); return true; }
2211
- if (ev.name === "enter") { this.#commitEdit(); return true; }
2212
- const handled = this.input.onKey(ev);
2213
- if (handled) this.app.redraw();
2214
- return true;
2215
- }
2216
2262
  if (this.scanMode) {
2217
2263
  if (ev.name === "escape") { this.scanMode = false; this.#rebuild(); return true; }
2218
2264
  if (ev.name === "up") { this.scanCursor = Math.max(0, this.scanCursor - 1); this.#rebuild(); this.app.redraw(); return true; }
@@ -2245,7 +2291,7 @@ export class ModelPanel extends Widget {
2245
2291
  if (ev.name === "enter") { this.#activateItem(); return true; }
2246
2292
  return false;
2247
2293
  }
2248
- if (ev.name === "escape") { this.editing = null; return false; } // App falls back to chat mode
2294
+ if (ev.name === "escape") { return false; } // App falls back to chat mode
2249
2295
  // dual-focus navigation: ↑/↓ move the cursor INSIDE the focused region —
2250
2296
  // the provider column in list focus, the option rows in form focus.
2251
2297
  // → enters the form, ← returns to the list.
@@ -2280,7 +2326,6 @@ export class ModelPanel extends Widget {
2280
2326
  if (ev.kind === "wheel-up") { this.formView.scroll(-3); return true; }
2281
2327
  if (ev.kind === "wheel-down") { this.formView.scroll(3); return true; }
2282
2328
  if (ev.kind !== "press" || ev.button !== 0) return false;
2283
- if (this.editing && this.input.inside(ev.x, ev.y)) return this.input.onMouse(ev);
2284
2329
  if (ev.x < this.x + 26) {
2285
2330
  const idx = ev.y - this.listView.y + this.listView.scrollY;
2286
2331
  // one click = select AND open (same as Enter)