@aiwayds/dsh-tui-pi 2.24.0 → 2.25.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.
@@ -1,8 +1,10 @@
1
1
  /**
2
2
  * Pure model-list assembly for the /model picker — favorites pinned on top,
3
- * hidden models in a dim section at the bottom, everything between. No TUI
4
- * imports: every function here is plain data-in/data-out, so the ordering,
5
- * filtering and toggle semantics stay unit-testable without a terminal
3
+ * hidden models in a dim section at the bottom, everything between. Every
4
+ * section lists its models aggregated under their provider (providers
5
+ * alphabetical, models alphabetical within a provider). No TUI imports:
6
+ * every function here is plain data-in/data-out, so the ordering, filtering
7
+ * and toggle semantics stay unit-testable without a terminal
6
8
  * (test/model-list.test.mjs).
7
9
  */
8
10
  /** One listed model route (provider + model id + display name). */
@@ -30,6 +32,15 @@ export type ModelRow = {
30
32
  };
31
33
  /** Case-insensitive substring match of `query` against name/id/provider. */
32
34
  export declare function matchesModelFilter(model: ListedModel, query: string): boolean;
35
+ /**
36
+ * Display order of every picker section: providers aggregate alphabetically,
37
+ * and within a provider models sort by the label the MODEL column shows
38
+ * (name, falling back to id) with the id as the tiebreak. The listing
39
+ * arrives from pickModel as a Promise.all race across providers, so the
40
+ * input order interleaves providers by resolve speed — this comparator is
41
+ * what makes the rows deterministic.
42
+ */
43
+ export declare function compareListedModels(a: ListedModel, b: ListedModel): number;
33
44
  /**
34
45
  * Toggle `key` in `list`: remove when present, append when absent (join
35
46
  * order = favorite order). Returns a new array; the input is never mutated.
@@ -48,14 +59,18 @@ export declare function narrowStringList(value: unknown): string[];
48
59
  /**
49
60
  * Assemble the picker rows, top to bottom:
50
61
  *
51
- * 1. Favorites (`section: 'favorite'`, ★ prefix applied by renderCell), in
52
- * join order — a favorited model is removed from every other section;
62
+ * 1. Favorites (`section: 'favorite'`, ★ prefix applied by renderCell) —
63
+ * display order is the shared provider-grouped alphabetical order, not
64
+ * the join order of the persisted `favoriteModels` list (persistence
65
+ * keeps join order; only the rendering sorts). A favorited model is
66
+ * removed from every other section;
53
67
  * 2. one full-width divider row — only when at least one favorite is visible
54
68
  * AND something follows it;
55
- * 3. the remaining models (`section: 'normal'`) in listing order;
69
+ * 3. the remaining models (`section: 'normal'`) in the same order;
56
70
  * 4. the Hidden section — only without an active filter and when non-empty:
57
71
  * a `hiddenHeader` row carrying the count, then the hidden models
58
- * (`section: 'hidden'`, rendered dim) that are not also favorited.
72
+ * (`section: 'hidden'`, rendered dim) that are not also favorited,
73
+ * likewise in the shared order.
59
74
  *
60
75
  * An active `filter` keeps the pinned structure but leaves only matching
61
76
  * models per section; stale keys in `favorites`/`hidden` (models no longer
package/lib/model-list.js CHANGED
@@ -1,8 +1,10 @@
1
1
  /**
2
2
  * Pure model-list assembly for the /model picker — favorites pinned on top,
3
- * hidden models in a dim section at the bottom, everything between. No TUI
4
- * imports: every function here is plain data-in/data-out, so the ordering,
5
- * filtering and toggle semantics stay unit-testable without a terminal
3
+ * hidden models in a dim section at the bottom, everything between. Every
4
+ * section lists its models aggregated under their provider (providers
5
+ * alphabetical, models alphabetical within a provider). No TUI imports:
6
+ * every function here is plain data-in/data-out, so the ordering, filtering
7
+ * and toggle semantics stay unit-testable without a terminal
6
8
  * (test/model-list.test.mjs).
7
9
  */
8
10
  /** Composite settings key of a model: `${provider}/${id}`. */
@@ -18,6 +20,22 @@ export function matchesModelFilter(model, query) {
18
20
  || model.id.toLowerCase().includes(needle)
19
21
  || model.provider.toLowerCase().includes(needle));
20
22
  }
23
+ /**
24
+ * Display order of every picker section: providers aggregate alphabetically,
25
+ * and within a provider models sort by the label the MODEL column shows
26
+ * (name, falling back to id) with the id as the tiebreak. The listing
27
+ * arrives from pickModel as a Promise.all race across providers, so the
28
+ * input order interleaves providers by resolve speed — this comparator is
29
+ * what makes the rows deterministic.
30
+ */
31
+ export function compareListedModels(a, b) {
32
+ const byProvider = a.provider.localeCompare(b.provider);
33
+ if (byProvider !== 0)
34
+ return byProvider;
35
+ const labelA = a.name === '' ? a.id : a.name;
36
+ const labelB = b.name === '' ? b.id : b.name;
37
+ return labelA.localeCompare(labelB) || a.id.localeCompare(b.id);
38
+ }
21
39
  /**
22
40
  * Toggle `key` in `list`: remove when present, append when absent (join
23
41
  * order = favorite order). Returns a new array; the input is never mutated.
@@ -44,33 +62,39 @@ export function narrowStringList(value) {
44
62
  /**
45
63
  * Assemble the picker rows, top to bottom:
46
64
  *
47
- * 1. Favorites (`section: 'favorite'`, ★ prefix applied by renderCell), in
48
- * join order — a favorited model is removed from every other section;
65
+ * 1. Favorites (`section: 'favorite'`, ★ prefix applied by renderCell) —
66
+ * display order is the shared provider-grouped alphabetical order, not
67
+ * the join order of the persisted `favoriteModels` list (persistence
68
+ * keeps join order; only the rendering sorts). A favorited model is
69
+ * removed from every other section;
49
70
  * 2. one full-width divider row — only when at least one favorite is visible
50
71
  * AND something follows it;
51
- * 3. the remaining models (`section: 'normal'`) in listing order;
72
+ * 3. the remaining models (`section: 'normal'`) in the same order;
52
73
  * 4. the Hidden section — only without an active filter and when non-empty:
53
74
  * a `hiddenHeader` row carrying the count, then the hidden models
54
- * (`section: 'hidden'`, rendered dim) that are not also favorited.
75
+ * (`section: 'hidden'`, rendered dim) that are not also favorited,
76
+ * likewise in the shared order.
55
77
  *
56
78
  * An active `filter` keeps the pinned structure but leaves only matching
57
79
  * models per section; stale keys in `favorites`/`hidden` (models no longer
58
80
  * listed by any provider) are skipped silently.
59
81
  */
60
82
  export function buildModelRows(models, favorites, hidden, filter = '') {
61
- const byKey = new Map(models.map(model => [modelKey(model), model]));
83
+ const sorted = [...models].sort(compareListedModels);
84
+ const byKey = new Map(sorted.map(model => [modelKey(model), model]));
62
85
  const favKeys = new Set(favorites);
63
86
  const hiddenSet = new Set(hidden);
64
87
  const favRows = favorites
65
88
  .map(key => byKey.get(key))
66
89
  .filter((model) => model !== undefined && matchesModelFilter(model, filter))
90
+ .sort(compareListedModels)
67
91
  .map(model => ({ kind: 'model', section: 'favorite', model }));
68
- const normalRows = models
92
+ const normalRows = sorted
69
93
  .filter(model => !favKeys.has(modelKey(model))
70
94
  && !hiddenSet.has(modelKey(model))
71
95
  && matchesModelFilter(model, filter))
72
96
  .map(model => ({ kind: 'model', section: 'normal', model }));
73
- const hiddenModels = models.filter(model => hiddenSet.has(modelKey(model)) && !favKeys.has(modelKey(model)));
97
+ const hiddenModels = sorted.filter(model => hiddenSet.has(modelKey(model)) && !favKeys.has(modelKey(model)));
74
98
  const showHidden = filter.trim() === '' && hiddenModels.length > 0;
75
99
  const rows = [...favRows];
76
100
  if (favRows.length > 0 && (normalRows.length > 0 || showHidden)) {
@@ -1 +1 @@
1
- {"version":3,"file":"model-list.js","sourceRoot":"","sources":["../src/model-list.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AASH,8DAA8D;AAC9D,MAAM,UAAU,QAAQ,CAAC,KAA2C;IAClE,OAAO,GAAG,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,EAAE,EAAE,CAAA;AACxC,CAAC;AAYD,4EAA4E;AAC5E,MAAM,UAAU,kBAAkB,CAAC,KAAkB,EAAE,KAAa;IAClE,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;IACzC,IAAI,MAAM,KAAK,EAAE;QAAE,OAAO,IAAI,CAAA;IAC9B,OAAO,CACL,KAAK,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC;WACtC,KAAK,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC;WACvC,KAAK,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CACjD,CAAA;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAuB,EAAE,GAAW;IACnE,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,EAAE,GAAG,CAAC,CAAA;AAClF,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,GAAa;IAC3C,OAAO,GAAG,CAAC,IAAI,KAAK,OAAO,IAAI,GAAG,CAAC,OAAO,KAAK,UAAU,CAAA;AAC3D,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,gBAAgB,CAAC,KAAc;IAC7C,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QACzB,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,KAAK,EAAmB,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC;QACrE,CAAC,CAAC,EAAE,CAAA;AACR,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,cAAc,CAC5B,MAA8B,EAC9B,SAA4B,EAC5B,MAAyB,EACzB,MAAM,GAAG,EAAE;IAEX,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAA;IACpE,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,CAAA;IAClC,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAA;IAEjC,MAAM,OAAO,GAAe,SAAS;SAClC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;SAC1B,MAAM,CAAC,CAAC,KAAK,EAAwB,EAAE,CACtC,KAAK,KAAK,SAAS,IAAI,kBAAkB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;SAC1D,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAC,CAAA;IAEhE,MAAM,UAAU,GAAe,MAAM;SAClC,MAAM,CAAC,KAAK,CAAC,EAAE,CACd,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;WAC1B,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;WAC/B,kBAAkB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;SACtC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC,CAAA;IAE9D,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CACzC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;IAClE,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,CAAA;IAElE,MAAM,IAAI,GAAe,CAAC,GAAG,OAAO,CAAC,CAAA;IACrC,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,IAAI,UAAU,CAAC,EAAE,CAAC;QAChE,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAA;IAChC,CAAC;IACD,IAAI,CAAC,IAAI,CAAC,GAAG,UAAU,CAAC,CAAA;IACxB,IAAI,UAAU,EAAE,CAAC;QACf,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,YAAY,CAAC,MAAM,EAAE,CAAC,CAAA;QAC/D,KAAK,MAAM,KAAK,IAAI,YAAY;YAAE,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAA;IAC1F,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC"}
1
+ {"version":3,"file":"model-list.js","sourceRoot":"","sources":["../src/model-list.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiwayds/dsh-tui-pi",
3
- "version": "2.24.0",
3
+ "version": "2.25.0",
4
4
  "description": "A fully-featured pi-style terminal UI for DeepSeek Harness (dsh) — history look-back & fork-at-turn, guided preset switching, live subagent steering, model profiles & themes",
5
5
  "repository": {
6
6
  "type": "git",