@codex-infinity/pi-infinity 0.60.2 → 0.61.1

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 (245) hide show
  1. package/CHANGELOG.md +45 -1
  2. package/dist/bun/cli.d.ts.map +1 -1
  3. package/dist/bun/cli.js +1 -0
  4. package/dist/bun/cli.js.map +1 -1
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/cli/config-selector.js.map +1 -1
  7. package/dist/cli/file-processor.js.map +1 -1
  8. package/dist/cli/initial-message.js.map +1 -1
  9. package/dist/cli/list-models.js.map +1 -1
  10. package/dist/cli/session-picker.d.ts.map +1 -1
  11. package/dist/cli/session-picker.js +2 -1
  12. package/dist/cli/session-picker.js.map +1 -1
  13. package/dist/cli.d.ts.map +1 -1
  14. package/dist/cli.js +1 -0
  15. package/dist/cli.js.map +1 -1
  16. package/dist/config.js.map +1 -1
  17. package/dist/core/agent-session.d.ts.map +1 -1
  18. package/dist/core/agent-session.js +50 -69
  19. package/dist/core/agent-session.js.map +1 -1
  20. package/dist/core/auth-storage.js +4 -8
  21. package/dist/core/auth-storage.js.map +1 -1
  22. package/dist/core/bash-executor.js.map +1 -1
  23. package/dist/core/compaction/branch-summarization.js.map +1 -1
  24. package/dist/core/compaction/compaction.js.map +1 -1
  25. package/dist/core/compaction/utils.js.map +1 -1
  26. package/dist/core/event-bus.js.map +1 -1
  27. package/dist/core/exec.js.map +1 -1
  28. package/dist/core/export-html/ansi-to-html.js.map +1 -1
  29. package/dist/core/export-html/index.js.map +1 -1
  30. package/dist/core/export-html/template.css +43 -13
  31. package/dist/core/export-html/template.html +1 -0
  32. package/dist/core/export-html/template.js +107 -0
  33. package/dist/core/export-html/tool-renderer.js.map +1 -1
  34. package/dist/core/extensions/index.d.ts +1 -1
  35. package/dist/core/extensions/index.d.ts.map +1 -1
  36. package/dist/core/extensions/index.js.map +1 -1
  37. package/dist/core/extensions/loader.d.ts.map +1 -1
  38. package/dist/core/extensions/loader.js +4 -4
  39. package/dist/core/extensions/loader.js.map +1 -1
  40. package/dist/core/extensions/runner.d.ts +1 -1
  41. package/dist/core/extensions/runner.d.ts.map +1 -1
  42. package/dist/core/extensions/runner.js +62 -56
  43. package/dist/core/extensions/runner.js.map +1 -1
  44. package/dist/core/extensions/types.d.ts +4 -3
  45. package/dist/core/extensions/types.d.ts.map +1 -1
  46. package/dist/core/extensions/types.js.map +1 -1
  47. package/dist/core/extensions/wrapper.js.map +1 -1
  48. package/dist/core/footer-data-provider.d.ts +9 -2
  49. package/dist/core/footer-data-provider.d.ts.map +1 -1
  50. package/dist/core/footer-data-provider.js +92 -20
  51. package/dist/core/footer-data-provider.js.map +1 -1
  52. package/dist/core/index.d.ts +1 -1
  53. package/dist/core/index.d.ts.map +1 -1
  54. package/dist/core/index.js.map +1 -1
  55. package/dist/core/keybindings.d.ts +268 -51
  56. package/dist/core/keybindings.d.ts.map +1 -1
  57. package/dist/core/keybindings.js +220 -143
  58. package/dist/core/keybindings.js.map +1 -1
  59. package/dist/core/messages.js.map +1 -1
  60. package/dist/core/model-registry.d.ts +1 -0
  61. package/dist/core/model-registry.d.ts.map +1 -1
  62. package/dist/core/model-registry.js +29 -21
  63. package/dist/core/model-registry.js.map +1 -1
  64. package/dist/core/model-resolver.d.ts.map +1 -1
  65. package/dist/core/model-resolver.js +4 -4
  66. package/dist/core/model-resolver.js.map +1 -1
  67. package/dist/core/package-manager.js +0 -6
  68. package/dist/core/package-manager.js.map +1 -1
  69. package/dist/core/prompt-templates.js.map +1 -1
  70. package/dist/core/resolve-config-value.js.map +1 -1
  71. package/dist/core/resource-loader.js +0 -37
  72. package/dist/core/resource-loader.js.map +1 -1
  73. package/dist/core/sdk.d.ts +2 -2
  74. package/dist/core/sdk.d.ts.map +1 -1
  75. package/dist/core/sdk.js +4 -4
  76. package/dist/core/sdk.js.map +1 -1
  77. package/dist/core/session-manager.d.ts +5 -0
  78. package/dist/core/session-manager.d.ts.map +1 -1
  79. package/dist/core/session-manager.js +8 -12
  80. package/dist/core/session-manager.js.map +1 -1
  81. package/dist/core/settings-manager.js +7 -16
  82. package/dist/core/settings-manager.js.map +1 -1
  83. package/dist/core/skills.js.map +1 -1
  84. package/dist/core/system-prompt.js.map +1 -1
  85. package/dist/core/timings.js.map +1 -1
  86. package/dist/core/tools/bash.js.map +1 -1
  87. package/dist/core/tools/edit-diff.js.map +1 -1
  88. package/dist/core/tools/edit.d.ts.map +1 -1
  89. package/dist/core/tools/edit.js +3 -2
  90. package/dist/core/tools/edit.js.map +1 -1
  91. package/dist/core/tools/file-mutation-queue.d.ts +6 -0
  92. package/dist/core/tools/file-mutation-queue.d.ts.map +1 -0
  93. package/dist/core/tools/file-mutation-queue.js +37 -0
  94. package/dist/core/tools/file-mutation-queue.js.map +1 -0
  95. package/dist/core/tools/find.js.map +1 -1
  96. package/dist/core/tools/grep.js.map +1 -1
  97. package/dist/core/tools/index.d.ts +1 -0
  98. package/dist/core/tools/index.d.ts.map +1 -1
  99. package/dist/core/tools/index.js +1 -0
  100. package/dist/core/tools/index.js.map +1 -1
  101. package/dist/core/tools/ls.js.map +1 -1
  102. package/dist/core/tools/path-utils.js.map +1 -1
  103. package/dist/core/tools/read.js.map +1 -1
  104. package/dist/core/tools/truncate.js.map +1 -1
  105. package/dist/core/tools/write.d.ts.map +1 -1
  106. package/dist/core/tools/write.js +6 -3
  107. package/dist/core/tools/write.js.map +1 -1
  108. package/dist/index.d.ts +3 -3
  109. package/dist/index.d.ts.map +1 -1
  110. package/dist/index.js +2 -2
  111. package/dist/index.js.map +1 -1
  112. package/dist/main.d.ts.map +1 -1
  113. package/dist/main.js +10 -5
  114. package/dist/main.js.map +1 -1
  115. package/dist/migrations.js.map +1 -1
  116. package/dist/modes/interactive/components/armin.js +6 -10
  117. package/dist/modes/interactive/components/armin.js.map +1 -1
  118. package/dist/modes/interactive/components/assistant-message.js +0 -4
  119. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  120. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  121. package/dist/modes/interactive/components/bash-execution.js +8 -14
  122. package/dist/modes/interactive/components/bash-execution.js.map +1 -1
  123. package/dist/modes/interactive/components/bordered-loader.d.ts.map +1 -1
  124. package/dist/modes/interactive/components/bordered-loader.js +1 -4
  125. package/dist/modes/interactive/components/bordered-loader.js.map +1 -1
  126. package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
  127. package/dist/modes/interactive/components/branch-summary-message.js +3 -5
  128. package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
  129. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
  130. package/dist/modes/interactive/components/compaction-summary-message.js +3 -5
  131. package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
  132. package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
  133. package/dist/modes/interactive/components/config-selector.js +14 -23
  134. package/dist/modes/interactive/components/config-selector.js.map +1 -1
  135. package/dist/modes/interactive/components/countdown-timer.js +0 -5
  136. package/dist/modes/interactive/components/countdown-timer.js.map +1 -1
  137. package/dist/modes/interactive/components/custom-editor.d.ts +3 -3
  138. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  139. package/dist/modes/interactive/components/custom-editor.js +7 -14
  140. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  141. package/dist/modes/interactive/components/custom-message.js +1 -6
  142. package/dist/modes/interactive/components/custom-message.js.map +1 -1
  143. package/dist/modes/interactive/components/daxnuts.js +6 -8
  144. package/dist/modes/interactive/components/daxnuts.js.map +1 -1
  145. package/dist/modes/interactive/components/diff.js.map +1 -1
  146. package/dist/modes/interactive/components/dynamic-border.js +0 -1
  147. package/dist/modes/interactive/components/dynamic-border.js.map +1 -1
  148. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  149. package/dist/modes/interactive/components/extension-editor.js +10 -15
  150. package/dist/modes/interactive/components/extension-editor.js.map +1 -1
  151. package/dist/modes/interactive/components/extension-input.d.ts.map +1 -1
  152. package/dist/modes/interactive/components/extension-input.js +7 -13
  153. package/dist/modes/interactive/components/extension-input.js.map +1 -1
  154. package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
  155. package/dist/modes/interactive/components/extension-selector.js +9 -16
  156. package/dist/modes/interactive/components/extension-selector.js.map +1 -1
  157. package/dist/modes/interactive/components/footer.js +1 -3
  158. package/dist/modes/interactive/components/footer.js.map +1 -1
  159. package/dist/modes/interactive/components/index.d.ts +1 -1
  160. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  161. package/dist/modes/interactive/components/index.js +1 -1
  162. package/dist/modes/interactive/components/index.js.map +1 -1
  163. package/dist/modes/interactive/components/keybinding-hints.d.ts +3 -36
  164. package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -1
  165. package/dist/modes/interactive/components/keybinding-hints.js +5 -44
  166. package/dist/modes/interactive/components/keybinding-hints.js.map +1 -1
  167. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  168. package/dist/modes/interactive/components/login-dialog.js +9 -15
  169. package/dist/modes/interactive/components/login-dialog.js.map +1 -1
  170. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  171. package/dist/modes/interactive/components/model-selector.js +20 -28
  172. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  173. package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
  174. package/dist/modes/interactive/components/oauth-selector.js +8 -13
  175. package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
  176. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
  177. package/dist/modes/interactive/components/scoped-models-selector.js +13 -17
  178. package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
  179. package/dist/modes/interactive/components/session-selector-search.js.map +1 -1
  180. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  181. package/dist/modes/interactive/components/session-selector.js +65 -93
  182. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  183. package/dist/modes/interactive/components/settings-selector.js +0 -2
  184. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  185. package/dist/modes/interactive/components/show-images-selector.js +0 -1
  186. package/dist/modes/interactive/components/show-images-selector.js.map +1 -1
  187. package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
  188. package/dist/modes/interactive/components/skill-invocation-message.js +3 -5
  189. package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
  190. package/dist/modes/interactive/components/theme-selector.js +0 -2
  191. package/dist/modes/interactive/components/theme-selector.js.map +1 -1
  192. package/dist/modes/interactive/components/thinking-selector.js +0 -1
  193. package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
  194. package/dist/modes/interactive/components/tool-execution.d.ts +7 -0
  195. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  196. package/dist/modes/interactive/components/tool-execution.js +57 -28
  197. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  198. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
  199. package/dist/modes/interactive/components/tree-selector.js +32 -46
  200. package/dist/modes/interactive/components/tree-selector.js.map +1 -1
  201. package/dist/modes/interactive/components/user-message-selector.d.ts.map +1 -1
  202. package/dist/modes/interactive/components/user-message-selector.js +9 -12
  203. package/dist/modes/interactive/components/user-message-selector.js.map +1 -1
  204. package/dist/modes/interactive/components/user-message.js.map +1 -1
  205. package/dist/modes/interactive/components/visual-truncate.js.map +1 -1
  206. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  207. package/dist/modes/interactive/interactive-mode.js +155 -165
  208. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  209. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  210. package/dist/modes/interactive/theme/theme.js +49 -42
  211. package/dist/modes/interactive/theme/theme.js.map +1 -1
  212. package/dist/modes/print-mode.js.map +1 -1
  213. package/dist/modes/rpc/jsonl.js.map +1 -1
  214. package/dist/modes/rpc/rpc-client.js +6 -7
  215. package/dist/modes/rpc/rpc-client.js.map +1 -1
  216. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  217. package/dist/modes/rpc/rpc-mode.js +4 -1
  218. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  219. package/dist/utils/changelog.js.map +1 -1
  220. package/dist/utils/child-process.js.map +1 -1
  221. package/dist/utils/clipboard-image.js.map +1 -1
  222. package/dist/utils/clipboard.js.map +1 -1
  223. package/dist/utils/exif-orientation.js.map +1 -1
  224. package/dist/utils/frontmatter.js.map +1 -1
  225. package/dist/utils/git.js.map +1 -1
  226. package/dist/utils/image-convert.js.map +1 -1
  227. package/dist/utils/image-resize.js.map +1 -1
  228. package/dist/utils/mime.js.map +1 -1
  229. package/dist/utils/photon.js.map +1 -1
  230. package/dist/utils/shell.js.map +1 -1
  231. package/dist/utils/sleep.js.map +1 -1
  232. package/dist/utils/tools-manager.js.map +1 -1
  233. package/docs/extensions.md +42 -5
  234. package/docs/keybindings.md +101 -112
  235. package/examples/extensions/antigravity-image-gen.ts +5 -3
  236. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  237. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  238. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  239. package/examples/extensions/custom-provider-qwen-cli/package.json +1 -1
  240. package/examples/extensions/subagent/index.ts +7 -5
  241. package/examples/extensions/tool-override.ts +9 -7
  242. package/examples/extensions/truncated-tool.ts +6 -3
  243. package/examples/extensions/with-deps/package-lock.json +2 -2
  244. package/examples/extensions/with-deps/package.json +1 -1
  245. package/package.json +4 -4
@@ -1,5 +1,5 @@
1
1
  import { modelsAreEqual } from "@mariozechner/pi-ai";
2
- import { Container, fuzzyFilter, getEditorKeybindings, Input, Spacer, Text, } from "@mariozechner/pi-tui";
2
+ import { Container, fuzzyFilter, getKeybindings, Input, Spacer, Text, } from "@mariozechner/pi-tui";
3
3
  import { theme } from "../theme/theme.js";
4
4
  import { DynamicBorder } from "./dynamic-border.js";
5
5
  import { keyHint } from "./keybinding-hints.js";
@@ -7,9 +7,6 @@ import { keyHint } from "./keybinding-hints.js";
7
7
  * Component that renders a model selector with search
8
8
  */
9
9
  export class ModelSelectorComponent extends Container {
10
- searchInput;
11
- // Focusable implementation - propagate to searchInput for IME cursor positioning
12
- _focused = false;
13
10
  get focused() {
14
11
  return this._focused;
15
12
  }
@@ -17,25 +14,16 @@ export class ModelSelectorComponent extends Container {
17
14
  this._focused = value;
18
15
  this.searchInput.focused = value;
19
16
  }
20
- listContainer;
21
- allModels = [];
22
- scopedModelItems = [];
23
- activeModels = [];
24
- filteredModels = [];
25
- selectedIndex = 0;
26
- currentModel;
27
- settingsManager;
28
- modelRegistry;
29
- onSelectCallback;
30
- onCancelCallback;
31
- errorMessage;
32
- tui;
33
- scopedModels;
34
- scope = "all";
35
- scopeText;
36
- scopeHintText;
37
17
  constructor(tui, currentModel, settingsManager, modelRegistry, scopedModels, onSelect, onCancel, initialSearchInput) {
38
18
  super();
19
+ // Focusable implementation - propagate to searchInput for IME cursor positioning
20
+ this._focused = false;
21
+ this.allModels = [];
22
+ this.scopedModelItems = [];
23
+ this.activeModels = [];
24
+ this.filteredModels = [];
25
+ this.selectedIndex = 0;
26
+ this.scope = "all";
39
27
  this.tui = tui;
40
28
  this.currentModel = currentModel;
41
29
  this.settingsManager = settingsManager;
@@ -117,6 +105,10 @@ export class ModelSelectorComponent extends Container {
117
105
  return;
118
106
  }
119
107
  this.allModels = this.sortModels(models);
108
+ this.scopedModels = this.scopedModels.map((scoped) => {
109
+ const refreshed = this.modelRegistry.find(scoped.model.provider, scoped.model.id);
110
+ return refreshed ? { ...scoped, model: refreshed } : scoped;
111
+ });
120
112
  this.scopedModelItems = this.sortModels(this.scopedModels.map((scoped) => ({
121
113
  provider: scoped.model.provider,
122
114
  id: scoped.model.id,
@@ -146,7 +138,7 @@ export class ModelSelectorComponent extends Container {
146
138
  return `${theme.fg("muted", "Scope: ")}${allText}${theme.fg("muted", " | ")}${scopedText}`;
147
139
  }
148
140
  getScopeHintText() {
149
- return keyHint("tab", "scope") + theme.fg("muted", " (all/scoped)");
141
+ return keyHint("tui.input.tab", "scope") + theme.fg("muted", " (all/scoped)");
150
142
  }
151
143
  setScope(scope) {
152
144
  if (this.scope === scope)
@@ -217,8 +209,8 @@ export class ModelSelectorComponent extends Container {
217
209
  }
218
210
  }
219
211
  handleInput(keyData) {
220
- const kb = getEditorKeybindings();
221
- if (kb.matches(keyData, "tab")) {
212
+ const kb = getKeybindings();
213
+ if (kb.matches(keyData, "tui.input.tab")) {
222
214
  if (this.scopedModelItems.length > 0) {
223
215
  const nextScope = this.scope === "all" ? "scoped" : "all";
224
216
  this.setScope(nextScope);
@@ -229,28 +221,28 @@ export class ModelSelectorComponent extends Container {
229
221
  return;
230
222
  }
231
223
  // Up arrow - wrap to bottom when at top
232
- if (kb.matches(keyData, "selectUp")) {
224
+ if (kb.matches(keyData, "tui.select.up")) {
233
225
  if (this.filteredModels.length === 0)
234
226
  return;
235
227
  this.selectedIndex = this.selectedIndex === 0 ? this.filteredModels.length - 1 : this.selectedIndex - 1;
236
228
  this.updateList();
237
229
  }
238
230
  // Down arrow - wrap to top when at bottom
239
- else if (kb.matches(keyData, "selectDown")) {
231
+ else if (kb.matches(keyData, "tui.select.down")) {
240
232
  if (this.filteredModels.length === 0)
241
233
  return;
242
234
  this.selectedIndex = this.selectedIndex === this.filteredModels.length - 1 ? 0 : this.selectedIndex + 1;
243
235
  this.updateList();
244
236
  }
245
237
  // Enter
246
- else if (kb.matches(keyData, "selectConfirm")) {
238
+ else if (kb.matches(keyData, "tui.select.confirm")) {
247
239
  const selectedModel = this.filteredModels[this.selectedIndex];
248
240
  if (selectedModel) {
249
241
  this.handleSelect(selectedModel.model);
250
242
  }
251
243
  }
252
244
  // Escape or Ctrl+C
253
- else if (kb.matches(keyData, "selectCancel")) {
245
+ else if (kb.matches(keyData, "tui.select.cancel")) {
254
246
  this.onCancelCallback();
255
247
  }
256
248
  // Pass everything else to search input
@@ -1 +1 @@
1
- {"version":3,"file":"model-selector.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/model-selector.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Model, modelsAreEqual } from \"@mariozechner/pi-ai\";\nimport {\n\tContainer,\n\ttype Focusable,\n\tfuzzyFilter,\n\tgetEditorKeybindings,\n\tInput,\n\tSpacer,\n\tText,\n\ttype TUI,\n} from \"@mariozechner/pi-tui\";\nimport type { ModelRegistry } from \"../../../core/model-registry.js\";\nimport type { SettingsManager } from \"../../../core/settings-manager.js\";\nimport { theme } from \"../theme/theme.js\";\nimport { DynamicBorder } from \"./dynamic-border.js\";\nimport { keyHint } from \"./keybinding-hints.js\";\n\ninterface ModelItem {\n\tprovider: string;\n\tid: string;\n\tmodel: Model<any>;\n}\n\ninterface ScopedModelItem {\n\tmodel: Model<any>;\n\tthinkingLevel?: string;\n}\n\ntype ModelScope = \"all\" | \"scoped\";\n\n/**\n * Component that renders a model selector with search\n */\nexport class ModelSelectorComponent extends Container implements Focusable {\n\tprivate searchInput: Input;\n\n\t// Focusable implementation - propagate to searchInput for IME cursor positioning\n\tprivate _focused = false;\n\tget focused(): boolean {\n\t\treturn this._focused;\n\t}\n\tset focused(value: boolean) {\n\t\tthis._focused = value;\n\t\tthis.searchInput.focused = value;\n\t}\n\tprivate listContainer: Container;\n\tprivate allModels: ModelItem[] = [];\n\tprivate scopedModelItems: ModelItem[] = [];\n\tprivate activeModels: ModelItem[] = [];\n\tprivate filteredModels: ModelItem[] = [];\n\tprivate selectedIndex: number = 0;\n\tprivate currentModel?: Model<any>;\n\tprivate settingsManager: SettingsManager;\n\tprivate modelRegistry: ModelRegistry;\n\tprivate onSelectCallback: (model: Model<any>) => void;\n\tprivate onCancelCallback: () => void;\n\tprivate errorMessage?: string;\n\tprivate tui: TUI;\n\tprivate scopedModels: ReadonlyArray<ScopedModelItem>;\n\tprivate scope: ModelScope = \"all\";\n\tprivate scopeText?: Text;\n\tprivate scopeHintText?: Text;\n\n\tconstructor(\n\t\ttui: TUI,\n\t\tcurrentModel: Model<any> | undefined,\n\t\tsettingsManager: SettingsManager,\n\t\tmodelRegistry: ModelRegistry,\n\t\tscopedModels: ReadonlyArray<ScopedModelItem>,\n\t\tonSelect: (model: Model<any>) => void,\n\t\tonCancel: () => void,\n\t\tinitialSearchInput?: string,\n\t) {\n\t\tsuper();\n\n\t\tthis.tui = tui;\n\t\tthis.currentModel = currentModel;\n\t\tthis.settingsManager = settingsManager;\n\t\tthis.modelRegistry = modelRegistry;\n\t\tthis.scopedModels = scopedModels;\n\t\tthis.scope = scopedModels.length > 0 ? \"scoped\" : \"all\";\n\t\tthis.onSelectCallback = onSelect;\n\t\tthis.onCancelCallback = onCancel;\n\n\t\t// Add top border\n\t\tthis.addChild(new DynamicBorder());\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Add hint about model filtering\n\t\tif (scopedModels.length > 0) {\n\t\t\tthis.scopeText = new Text(this.getScopeText(), 0, 0);\n\t\t\tthis.addChild(this.scopeText);\n\t\t\tthis.scopeHintText = new Text(this.getScopeHintText(), 0, 0);\n\t\t\tthis.addChild(this.scopeHintText);\n\t\t} else {\n\t\t\tconst hintText = \"Only showing models with configured API keys (see README for details)\";\n\t\t\tthis.addChild(new Text(theme.fg(\"warning\", hintText), 0, 0));\n\t\t}\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Create search input\n\t\tthis.searchInput = new Input();\n\t\tif (initialSearchInput) {\n\t\t\tthis.searchInput.setValue(initialSearchInput);\n\t\t}\n\t\tthis.searchInput.onSubmit = () => {\n\t\t\t// Enter on search input selects the first filtered item\n\t\t\tif (this.filteredModels[this.selectedIndex]) {\n\t\t\t\tthis.handleSelect(this.filteredModels[this.selectedIndex].model);\n\t\t\t}\n\t\t};\n\t\tthis.addChild(this.searchInput);\n\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Create list container\n\t\tthis.listContainer = new Container();\n\t\tthis.addChild(this.listContainer);\n\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Add bottom border\n\t\tthis.addChild(new DynamicBorder());\n\n\t\t// Load models and do initial render\n\t\tthis.loadModels().then(() => {\n\t\t\tif (initialSearchInput) {\n\t\t\t\tthis.filterModels(initialSearchInput);\n\t\t\t} else {\n\t\t\t\tthis.updateList();\n\t\t\t}\n\t\t\t// Request re-render after models are loaded\n\t\t\tthis.tui.requestRender();\n\t\t});\n\t}\n\n\tprivate async loadModels(): Promise<void> {\n\t\tlet models: ModelItem[];\n\n\t\t// Refresh to pick up any changes to models.json\n\t\tthis.modelRegistry.refresh();\n\n\t\t// Check for models.json errors\n\t\tconst loadError = this.modelRegistry.getError();\n\t\tif (loadError) {\n\t\t\tthis.errorMessage = loadError;\n\t\t}\n\n\t\t// Load available models (built-in models still work even if models.json failed)\n\t\ttry {\n\t\t\tconst availableModels = await this.modelRegistry.getAvailable();\n\t\t\tmodels = availableModels.map((model: Model<any>) => ({\n\t\t\t\tprovider: model.provider,\n\t\t\t\tid: model.id,\n\t\t\t\tmodel,\n\t\t\t}));\n\t\t} catch (error) {\n\t\t\tthis.allModels = [];\n\t\t\tthis.scopedModelItems = [];\n\t\t\tthis.activeModels = [];\n\t\t\tthis.filteredModels = [];\n\t\t\tthis.errorMessage = error instanceof Error ? error.message : String(error);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.allModels = this.sortModels(models);\n\t\tthis.scopedModelItems = this.sortModels(\n\t\t\tthis.scopedModels.map((scoped) => ({\n\t\t\t\tprovider: scoped.model.provider,\n\t\t\t\tid: scoped.model.id,\n\t\t\t\tmodel: scoped.model,\n\t\t\t})),\n\t\t);\n\t\tthis.activeModels = this.scope === \"scoped\" ? this.scopedModelItems : this.allModels;\n\t\tthis.filteredModels = this.activeModels;\n\t\tthis.selectedIndex = Math.min(this.selectedIndex, Math.max(0, this.filteredModels.length - 1));\n\t}\n\n\tprivate sortModels(models: ModelItem[]): ModelItem[] {\n\t\tconst sorted = [...models];\n\t\t// Sort: current model first, then by provider\n\t\tsorted.sort((a, b) => {\n\t\t\tconst aIsCurrent = modelsAreEqual(this.currentModel, a.model);\n\t\t\tconst bIsCurrent = modelsAreEqual(this.currentModel, b.model);\n\t\t\tif (aIsCurrent && !bIsCurrent) return -1;\n\t\t\tif (!aIsCurrent && bIsCurrent) return 1;\n\t\t\treturn a.provider.localeCompare(b.provider);\n\t\t});\n\t\treturn sorted;\n\t}\n\n\tprivate getScopeText(): string {\n\t\tconst allText = this.scope === \"all\" ? theme.fg(\"accent\", \"all\") : theme.fg(\"muted\", \"all\");\n\t\tconst scopedText = this.scope === \"scoped\" ? theme.fg(\"accent\", \"scoped\") : theme.fg(\"muted\", \"scoped\");\n\t\treturn `${theme.fg(\"muted\", \"Scope: \")}${allText}${theme.fg(\"muted\", \" | \")}${scopedText}`;\n\t}\n\n\tprivate getScopeHintText(): string {\n\t\treturn keyHint(\"tab\", \"scope\") + theme.fg(\"muted\", \" (all/scoped)\");\n\t}\n\n\tprivate setScope(scope: ModelScope): void {\n\t\tif (this.scope === scope) return;\n\t\tthis.scope = scope;\n\t\tthis.activeModels = this.scope === \"scoped\" ? this.scopedModelItems : this.allModels;\n\t\tthis.selectedIndex = 0;\n\t\tthis.filterModels(this.searchInput.getValue());\n\t\tif (this.scopeText) {\n\t\t\tthis.scopeText.setText(this.getScopeText());\n\t\t}\n\t}\n\n\tprivate filterModels(query: string): void {\n\t\tthis.filteredModels = query\n\t\t\t? fuzzyFilter(\n\t\t\t\t\tthis.activeModels,\n\t\t\t\t\tquery,\n\t\t\t\t\t({ id, provider }) => `${id} ${provider} ${provider}/${id} ${provider} ${id}`,\n\t\t\t\t)\n\t\t\t: this.activeModels;\n\t\tthis.selectedIndex = Math.min(this.selectedIndex, Math.max(0, this.filteredModels.length - 1));\n\t\tthis.updateList();\n\t}\n\n\tprivate updateList(): void {\n\t\tthis.listContainer.clear();\n\n\t\tconst maxVisible = 10;\n\t\tconst startIndex = Math.max(\n\t\t\t0,\n\t\t\tMath.min(this.selectedIndex - Math.floor(maxVisible / 2), this.filteredModels.length - maxVisible),\n\t\t);\n\t\tconst endIndex = Math.min(startIndex + maxVisible, this.filteredModels.length);\n\n\t\t// Show visible slice of filtered models\n\t\tfor (let i = startIndex; i < endIndex; i++) {\n\t\t\tconst item = this.filteredModels[i];\n\t\t\tif (!item) continue;\n\n\t\t\tconst isSelected = i === this.selectedIndex;\n\t\t\tconst isCurrent = modelsAreEqual(this.currentModel, item.model);\n\n\t\t\tlet line = \"\";\n\t\t\tif (isSelected) {\n\t\t\t\tconst prefix = theme.fg(\"accent\", \"→ \");\n\t\t\t\tconst modelText = `${item.id}`;\n\t\t\t\tconst providerBadge = theme.fg(\"muted\", `[${item.provider}]`);\n\t\t\t\tconst checkmark = isCurrent ? theme.fg(\"success\", \" ✓\") : \"\";\n\t\t\t\tline = `${prefix + theme.fg(\"accent\", modelText)} ${providerBadge}${checkmark}`;\n\t\t\t} else {\n\t\t\t\tconst modelText = ` ${item.id}`;\n\t\t\t\tconst providerBadge = theme.fg(\"muted\", `[${item.provider}]`);\n\t\t\t\tconst checkmark = isCurrent ? theme.fg(\"success\", \" ✓\") : \"\";\n\t\t\t\tline = `${modelText} ${providerBadge}${checkmark}`;\n\t\t\t}\n\n\t\t\tthis.listContainer.addChild(new Text(line, 0, 0));\n\t\t}\n\n\t\t// Add scroll indicator if needed\n\t\tif (startIndex > 0 || endIndex < this.filteredModels.length) {\n\t\t\tconst scrollInfo = theme.fg(\"muted\", ` (${this.selectedIndex + 1}/${this.filteredModels.length})`);\n\t\t\tthis.listContainer.addChild(new Text(scrollInfo, 0, 0));\n\t\t}\n\n\t\t// Show error message or \"no results\" if empty\n\t\tif (this.errorMessage) {\n\t\t\t// Show error in red\n\t\t\tconst errorLines = this.errorMessage.split(\"\\n\");\n\t\t\tfor (const line of errorLines) {\n\t\t\t\tthis.listContainer.addChild(new Text(theme.fg(\"error\", line), 0, 0));\n\t\t\t}\n\t\t} else if (this.filteredModels.length === 0) {\n\t\t\tthis.listContainer.addChild(new Text(theme.fg(\"muted\", \" No matching models\"), 0, 0));\n\t\t} else {\n\t\t\tconst selected = this.filteredModels[this.selectedIndex];\n\t\t\tthis.listContainer.addChild(new Spacer(1));\n\t\t\tthis.listContainer.addChild(new Text(theme.fg(\"muted\", ` Model Name: ${selected.model.name}`), 0, 0));\n\t\t}\n\t}\n\n\thandleInput(keyData: string): void {\n\t\tconst kb = getEditorKeybindings();\n\t\tif (kb.matches(keyData, \"tab\")) {\n\t\t\tif (this.scopedModelItems.length > 0) {\n\t\t\t\tconst nextScope: ModelScope = this.scope === \"all\" ? \"scoped\" : \"all\";\n\t\t\t\tthis.setScope(nextScope);\n\t\t\t\tif (this.scopeHintText) {\n\t\t\t\t\tthis.scopeHintText.setText(this.getScopeHintText());\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\t// Up arrow - wrap to bottom when at top\n\t\tif (kb.matches(keyData, \"selectUp\")) {\n\t\t\tif (this.filteredModels.length === 0) return;\n\t\t\tthis.selectedIndex = this.selectedIndex === 0 ? this.filteredModels.length - 1 : this.selectedIndex - 1;\n\t\t\tthis.updateList();\n\t\t}\n\t\t// Down arrow - wrap to top when at bottom\n\t\telse if (kb.matches(keyData, \"selectDown\")) {\n\t\t\tif (this.filteredModels.length === 0) return;\n\t\t\tthis.selectedIndex = this.selectedIndex === this.filteredModels.length - 1 ? 0 : this.selectedIndex + 1;\n\t\t\tthis.updateList();\n\t\t}\n\t\t// Enter\n\t\telse if (kb.matches(keyData, \"selectConfirm\")) {\n\t\t\tconst selectedModel = this.filteredModels[this.selectedIndex];\n\t\t\tif (selectedModel) {\n\t\t\t\tthis.handleSelect(selectedModel.model);\n\t\t\t}\n\t\t}\n\t\t// Escape or Ctrl+C\n\t\telse if (kb.matches(keyData, \"selectCancel\")) {\n\t\t\tthis.onCancelCallback();\n\t\t}\n\t\t// Pass everything else to search input\n\t\telse {\n\t\t\tthis.searchInput.handleInput(keyData);\n\t\t\tthis.filterModels(this.searchInput.getValue());\n\t\t}\n\t}\n\n\tprivate handleSelect(model: Model<any>): void {\n\t\t// Save as new default\n\t\tthis.settingsManager.setDefaultModelAndProvider(model.provider, model.id);\n\t\tthis.onSelectCallback(model);\n\t}\n\n\tgetSearchInput(): Input {\n\t\treturn this.searchInput;\n\t}\n}\n"]}
1
+ {"version":3,"file":"model-selector.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/model-selector.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Model, modelsAreEqual } from \"@mariozechner/pi-ai\";\nimport {\n\tContainer,\n\ttype Focusable,\n\tfuzzyFilter,\n\tgetKeybindings,\n\tInput,\n\tSpacer,\n\tText,\n\ttype TUI,\n} from \"@mariozechner/pi-tui\";\nimport type { ModelRegistry } from \"../../../core/model-registry.js\";\nimport type { SettingsManager } from \"../../../core/settings-manager.js\";\nimport { theme } from \"../theme/theme.js\";\nimport { DynamicBorder } from \"./dynamic-border.js\";\nimport { keyHint } from \"./keybinding-hints.js\";\n\ninterface ModelItem {\n\tprovider: string;\n\tid: string;\n\tmodel: Model<any>;\n}\n\ninterface ScopedModelItem {\n\tmodel: Model<any>;\n\tthinkingLevel?: string;\n}\n\ntype ModelScope = \"all\" | \"scoped\";\n\n/**\n * Component that renders a model selector with search\n */\nexport class ModelSelectorComponent extends Container implements Focusable {\n\tprivate searchInput: Input;\n\n\t// Focusable implementation - propagate to searchInput for IME cursor positioning\n\tprivate _focused = false;\n\tget focused(): boolean {\n\t\treturn this._focused;\n\t}\n\tset focused(value: boolean) {\n\t\tthis._focused = value;\n\t\tthis.searchInput.focused = value;\n\t}\n\tprivate listContainer: Container;\n\tprivate allModels: ModelItem[] = [];\n\tprivate scopedModelItems: ModelItem[] = [];\n\tprivate activeModels: ModelItem[] = [];\n\tprivate filteredModels: ModelItem[] = [];\n\tprivate selectedIndex: number = 0;\n\tprivate currentModel?: Model<any>;\n\tprivate settingsManager: SettingsManager;\n\tprivate modelRegistry: ModelRegistry;\n\tprivate onSelectCallback: (model: Model<any>) => void;\n\tprivate onCancelCallback: () => void;\n\tprivate errorMessage?: string;\n\tprivate tui: TUI;\n\tprivate scopedModels: ReadonlyArray<ScopedModelItem>;\n\tprivate scope: ModelScope = \"all\";\n\tprivate scopeText?: Text;\n\tprivate scopeHintText?: Text;\n\n\tconstructor(\n\t\ttui: TUI,\n\t\tcurrentModel: Model<any> | undefined,\n\t\tsettingsManager: SettingsManager,\n\t\tmodelRegistry: ModelRegistry,\n\t\tscopedModels: ReadonlyArray<ScopedModelItem>,\n\t\tonSelect: (model: Model<any>) => void,\n\t\tonCancel: () => void,\n\t\tinitialSearchInput?: string,\n\t) {\n\t\tsuper();\n\n\t\tthis.tui = tui;\n\t\tthis.currentModel = currentModel;\n\t\tthis.settingsManager = settingsManager;\n\t\tthis.modelRegistry = modelRegistry;\n\t\tthis.scopedModels = scopedModels;\n\t\tthis.scope = scopedModels.length > 0 ? \"scoped\" : \"all\";\n\t\tthis.onSelectCallback = onSelect;\n\t\tthis.onCancelCallback = onCancel;\n\n\t\t// Add top border\n\t\tthis.addChild(new DynamicBorder());\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Add hint about model filtering\n\t\tif (scopedModels.length > 0) {\n\t\t\tthis.scopeText = new Text(this.getScopeText(), 0, 0);\n\t\t\tthis.addChild(this.scopeText);\n\t\t\tthis.scopeHintText = new Text(this.getScopeHintText(), 0, 0);\n\t\t\tthis.addChild(this.scopeHintText);\n\t\t} else {\n\t\t\tconst hintText = \"Only showing models with configured API keys (see README for details)\";\n\t\t\tthis.addChild(new Text(theme.fg(\"warning\", hintText), 0, 0));\n\t\t}\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Create search input\n\t\tthis.searchInput = new Input();\n\t\tif (initialSearchInput) {\n\t\t\tthis.searchInput.setValue(initialSearchInput);\n\t\t}\n\t\tthis.searchInput.onSubmit = () => {\n\t\t\t// Enter on search input selects the first filtered item\n\t\t\tif (this.filteredModels[this.selectedIndex]) {\n\t\t\t\tthis.handleSelect(this.filteredModels[this.selectedIndex].model);\n\t\t\t}\n\t\t};\n\t\tthis.addChild(this.searchInput);\n\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Create list container\n\t\tthis.listContainer = new Container();\n\t\tthis.addChild(this.listContainer);\n\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Add bottom border\n\t\tthis.addChild(new DynamicBorder());\n\n\t\t// Load models and do initial render\n\t\tthis.loadModels().then(() => {\n\t\t\tif (initialSearchInput) {\n\t\t\t\tthis.filterModels(initialSearchInput);\n\t\t\t} else {\n\t\t\t\tthis.updateList();\n\t\t\t}\n\t\t\t// Request re-render after models are loaded\n\t\t\tthis.tui.requestRender();\n\t\t});\n\t}\n\n\tprivate async loadModels(): Promise<void> {\n\t\tlet models: ModelItem[];\n\n\t\t// Refresh to pick up any changes to models.json\n\t\tthis.modelRegistry.refresh();\n\n\t\t// Check for models.json errors\n\t\tconst loadError = this.modelRegistry.getError();\n\t\tif (loadError) {\n\t\t\tthis.errorMessage = loadError;\n\t\t}\n\n\t\t// Load available models (built-in models still work even if models.json failed)\n\t\ttry {\n\t\t\tconst availableModels = await this.modelRegistry.getAvailable();\n\t\t\tmodels = availableModels.map((model: Model<any>) => ({\n\t\t\t\tprovider: model.provider,\n\t\t\t\tid: model.id,\n\t\t\t\tmodel,\n\t\t\t}));\n\t\t} catch (error) {\n\t\t\tthis.allModels = [];\n\t\t\tthis.scopedModelItems = [];\n\t\t\tthis.activeModels = [];\n\t\t\tthis.filteredModels = [];\n\t\t\tthis.errorMessage = error instanceof Error ? error.message : String(error);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.allModels = this.sortModels(models);\n\t\tthis.scopedModels = this.scopedModels.map((scoped) => {\n\t\t\tconst refreshed = this.modelRegistry.find(scoped.model.provider, scoped.model.id);\n\t\t\treturn refreshed ? { ...scoped, model: refreshed } : scoped;\n\t\t});\n\t\tthis.scopedModelItems = this.sortModels(\n\t\t\tthis.scopedModels.map((scoped) => ({\n\t\t\t\tprovider: scoped.model.provider,\n\t\t\t\tid: scoped.model.id,\n\t\t\t\tmodel: scoped.model,\n\t\t\t})),\n\t\t);\n\t\tthis.activeModels = this.scope === \"scoped\" ? this.scopedModelItems : this.allModels;\n\t\tthis.filteredModels = this.activeModels;\n\t\tthis.selectedIndex = Math.min(this.selectedIndex, Math.max(0, this.filteredModels.length - 1));\n\t}\n\n\tprivate sortModels(models: ModelItem[]): ModelItem[] {\n\t\tconst sorted = [...models];\n\t\t// Sort: current model first, then by provider\n\t\tsorted.sort((a, b) => {\n\t\t\tconst aIsCurrent = modelsAreEqual(this.currentModel, a.model);\n\t\t\tconst bIsCurrent = modelsAreEqual(this.currentModel, b.model);\n\t\t\tif (aIsCurrent && !bIsCurrent) return -1;\n\t\t\tif (!aIsCurrent && bIsCurrent) return 1;\n\t\t\treturn a.provider.localeCompare(b.provider);\n\t\t});\n\t\treturn sorted;\n\t}\n\n\tprivate getScopeText(): string {\n\t\tconst allText = this.scope === \"all\" ? theme.fg(\"accent\", \"all\") : theme.fg(\"muted\", \"all\");\n\t\tconst scopedText = this.scope === \"scoped\" ? theme.fg(\"accent\", \"scoped\") : theme.fg(\"muted\", \"scoped\");\n\t\treturn `${theme.fg(\"muted\", \"Scope: \")}${allText}${theme.fg(\"muted\", \" | \")}${scopedText}`;\n\t}\n\n\tprivate getScopeHintText(): string {\n\t\treturn keyHint(\"tui.input.tab\", \"scope\") + theme.fg(\"muted\", \" (all/scoped)\");\n\t}\n\n\tprivate setScope(scope: ModelScope): void {\n\t\tif (this.scope === scope) return;\n\t\tthis.scope = scope;\n\t\tthis.activeModels = this.scope === \"scoped\" ? this.scopedModelItems : this.allModels;\n\t\tthis.selectedIndex = 0;\n\t\tthis.filterModels(this.searchInput.getValue());\n\t\tif (this.scopeText) {\n\t\t\tthis.scopeText.setText(this.getScopeText());\n\t\t}\n\t}\n\n\tprivate filterModels(query: string): void {\n\t\tthis.filteredModels = query\n\t\t\t? fuzzyFilter(\n\t\t\t\t\tthis.activeModels,\n\t\t\t\t\tquery,\n\t\t\t\t\t({ id, provider }) => `${id} ${provider} ${provider}/${id} ${provider} ${id}`,\n\t\t\t\t)\n\t\t\t: this.activeModels;\n\t\tthis.selectedIndex = Math.min(this.selectedIndex, Math.max(0, this.filteredModels.length - 1));\n\t\tthis.updateList();\n\t}\n\n\tprivate updateList(): void {\n\t\tthis.listContainer.clear();\n\n\t\tconst maxVisible = 10;\n\t\tconst startIndex = Math.max(\n\t\t\t0,\n\t\t\tMath.min(this.selectedIndex - Math.floor(maxVisible / 2), this.filteredModels.length - maxVisible),\n\t\t);\n\t\tconst endIndex = Math.min(startIndex + maxVisible, this.filteredModels.length);\n\n\t\t// Show visible slice of filtered models\n\t\tfor (let i = startIndex; i < endIndex; i++) {\n\t\t\tconst item = this.filteredModels[i];\n\t\t\tif (!item) continue;\n\n\t\t\tconst isSelected = i === this.selectedIndex;\n\t\t\tconst isCurrent = modelsAreEqual(this.currentModel, item.model);\n\n\t\t\tlet line = \"\";\n\t\t\tif (isSelected) {\n\t\t\t\tconst prefix = theme.fg(\"accent\", \"→ \");\n\t\t\t\tconst modelText = `${item.id}`;\n\t\t\t\tconst providerBadge = theme.fg(\"muted\", `[${item.provider}]`);\n\t\t\t\tconst checkmark = isCurrent ? theme.fg(\"success\", \" ✓\") : \"\";\n\t\t\t\tline = `${prefix + theme.fg(\"accent\", modelText)} ${providerBadge}${checkmark}`;\n\t\t\t} else {\n\t\t\t\tconst modelText = ` ${item.id}`;\n\t\t\t\tconst providerBadge = theme.fg(\"muted\", `[${item.provider}]`);\n\t\t\t\tconst checkmark = isCurrent ? theme.fg(\"success\", \" ✓\") : \"\";\n\t\t\t\tline = `${modelText} ${providerBadge}${checkmark}`;\n\t\t\t}\n\n\t\t\tthis.listContainer.addChild(new Text(line, 0, 0));\n\t\t}\n\n\t\t// Add scroll indicator if needed\n\t\tif (startIndex > 0 || endIndex < this.filteredModels.length) {\n\t\t\tconst scrollInfo = theme.fg(\"muted\", ` (${this.selectedIndex + 1}/${this.filteredModels.length})`);\n\t\t\tthis.listContainer.addChild(new Text(scrollInfo, 0, 0));\n\t\t}\n\n\t\t// Show error message or \"no results\" if empty\n\t\tif (this.errorMessage) {\n\t\t\t// Show error in red\n\t\t\tconst errorLines = this.errorMessage.split(\"\\n\");\n\t\t\tfor (const line of errorLines) {\n\t\t\t\tthis.listContainer.addChild(new Text(theme.fg(\"error\", line), 0, 0));\n\t\t\t}\n\t\t} else if (this.filteredModels.length === 0) {\n\t\t\tthis.listContainer.addChild(new Text(theme.fg(\"muted\", \" No matching models\"), 0, 0));\n\t\t} else {\n\t\t\tconst selected = this.filteredModels[this.selectedIndex];\n\t\t\tthis.listContainer.addChild(new Spacer(1));\n\t\t\tthis.listContainer.addChild(new Text(theme.fg(\"muted\", ` Model Name: ${selected.model.name}`), 0, 0));\n\t\t}\n\t}\n\n\thandleInput(keyData: string): void {\n\t\tconst kb = getKeybindings();\n\t\tif (kb.matches(keyData, \"tui.input.tab\")) {\n\t\t\tif (this.scopedModelItems.length > 0) {\n\t\t\t\tconst nextScope: ModelScope = this.scope === \"all\" ? \"scoped\" : \"all\";\n\t\t\t\tthis.setScope(nextScope);\n\t\t\t\tif (this.scopeHintText) {\n\t\t\t\t\tthis.scopeHintText.setText(this.getScopeHintText());\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\t// Up arrow - wrap to bottom when at top\n\t\tif (kb.matches(keyData, \"tui.select.up\")) {\n\t\t\tif (this.filteredModels.length === 0) return;\n\t\t\tthis.selectedIndex = this.selectedIndex === 0 ? this.filteredModels.length - 1 : this.selectedIndex - 1;\n\t\t\tthis.updateList();\n\t\t}\n\t\t// Down arrow - wrap to top when at bottom\n\t\telse if (kb.matches(keyData, \"tui.select.down\")) {\n\t\t\tif (this.filteredModels.length === 0) return;\n\t\t\tthis.selectedIndex = this.selectedIndex === this.filteredModels.length - 1 ? 0 : this.selectedIndex + 1;\n\t\t\tthis.updateList();\n\t\t}\n\t\t// Enter\n\t\telse if (kb.matches(keyData, \"tui.select.confirm\")) {\n\t\t\tconst selectedModel = this.filteredModels[this.selectedIndex];\n\t\t\tif (selectedModel) {\n\t\t\t\tthis.handleSelect(selectedModel.model);\n\t\t\t}\n\t\t}\n\t\t// Escape or Ctrl+C\n\t\telse if (kb.matches(keyData, \"tui.select.cancel\")) {\n\t\t\tthis.onCancelCallback();\n\t\t}\n\t\t// Pass everything else to search input\n\t\telse {\n\t\t\tthis.searchInput.handleInput(keyData);\n\t\t\tthis.filterModels(this.searchInput.getValue());\n\t\t}\n\t}\n\n\tprivate handleSelect(model: Model<any>): void {\n\t\t// Save as new default\n\t\tthis.settingsManager.setDefaultModelAndProvider(model.provider, model.id);\n\t\tthis.onSelectCallback(model);\n\t}\n\n\tgetSearchInput(): Input {\n\t\treturn this.searchInput;\n\t}\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"oauth-selector.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/oauth-selector.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAA+C,MAAM,sBAAsB,CAAC;AAC9F,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAIjE;;GAEG;AACH,qBAAa,sBAAuB,SAAQ,SAAS;IACpD,OAAO,CAAC,aAAa,CAAY;IACjC,OAAO,CAAC,YAAY,CAAgC;IACpD,OAAO,CAAC,aAAa,CAAa;IAClC,OAAO,CAAC,IAAI,CAAqB;IACjC,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,gBAAgB,CAA+B;IACvD,OAAO,CAAC,gBAAgB,CAAa;IAErC,YACC,IAAI,EAAE,OAAO,GAAG,QAAQ,EACxB,WAAW,EAAE,WAAW,EACxB,QAAQ,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,EACtC,QAAQ,EAAE,MAAM,IAAI,EAgCpB;IAED,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,UAAU;IAmClB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAuBjC;CACD","sourcesContent":["import type { OAuthProviderInterface } from \"@mariozechner/pi-ai\";\nimport { getOAuthProviders } from \"@mariozechner/pi-ai/oauth\";\nimport { Container, getEditorKeybindings, Spacer, TruncatedText } from \"@mariozechner/pi-tui\";\nimport type { AuthStorage } from \"../../../core/auth-storage.js\";\nimport { theme } from \"../theme/theme.js\";\nimport { DynamicBorder } from \"./dynamic-border.js\";\n\n/**\n * Component that renders an OAuth provider selector\n */\nexport class OAuthSelectorComponent extends Container {\n\tprivate listContainer: Container;\n\tprivate allProviders: OAuthProviderInterface[] = [];\n\tprivate selectedIndex: number = 0;\n\tprivate mode: \"login\" | \"logout\";\n\tprivate authStorage: AuthStorage;\n\tprivate onSelectCallback: (providerId: string) => void;\n\tprivate onCancelCallback: () => void;\n\n\tconstructor(\n\t\tmode: \"login\" | \"logout\",\n\t\tauthStorage: AuthStorage,\n\t\tonSelect: (providerId: string) => void,\n\t\tonCancel: () => void,\n\t) {\n\t\tsuper();\n\n\t\tthis.mode = mode;\n\t\tthis.authStorage = authStorage;\n\t\tthis.onSelectCallback = onSelect;\n\t\tthis.onCancelCallback = onCancel;\n\n\t\t// Load all OAuth providers\n\t\tthis.loadProviders();\n\n\t\t// Add top border\n\t\tthis.addChild(new DynamicBorder());\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Add title\n\t\tconst title = mode === \"login\" ? \"Select provider to login:\" : \"Select provider to logout:\";\n\t\tthis.addChild(new TruncatedText(theme.bold(title)));\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Create list container\n\t\tthis.listContainer = new Container();\n\t\tthis.addChild(this.listContainer);\n\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Add bottom border\n\t\tthis.addChild(new DynamicBorder());\n\n\t\t// Initial render\n\t\tthis.updateList();\n\t}\n\n\tprivate loadProviders(): void {\n\t\tthis.allProviders = getOAuthProviders();\n\t}\n\n\tprivate updateList(): void {\n\t\tthis.listContainer.clear();\n\n\t\tfor (let i = 0; i < this.allProviders.length; i++) {\n\t\t\tconst provider = this.allProviders[i];\n\t\t\tif (!provider) continue;\n\n\t\t\tconst isSelected = i === this.selectedIndex;\n\n\t\t\t// Check if user is logged in for this provider\n\t\t\tconst credentials = this.authStorage.get(provider.id);\n\t\t\tconst isLoggedIn = credentials?.type === \"oauth\";\n\t\t\tconst statusIndicator = isLoggedIn ? theme.fg(\"success\", \" ✓ logged in\") : \"\";\n\n\t\t\tlet line = \"\";\n\t\t\tif (isSelected) {\n\t\t\t\tconst prefix = theme.fg(\"accent\", \"→ \");\n\t\t\t\tconst text = theme.fg(\"accent\", provider.name);\n\t\t\t\tline = prefix + text + statusIndicator;\n\t\t\t} else {\n\t\t\t\tconst text = ` ${provider.name}`;\n\t\t\t\tline = text + statusIndicator;\n\t\t\t}\n\n\t\t\tthis.listContainer.addChild(new TruncatedText(line, 0, 0));\n\t\t}\n\n\t\t// Show \"no providers\" if empty\n\t\tif (this.allProviders.length === 0) {\n\t\t\tconst message =\n\t\t\t\tthis.mode === \"login\" ? \"No OAuth providers available\" : \"No OAuth providers logged in. Use /login first.\";\n\t\t\tthis.listContainer.addChild(new TruncatedText(theme.fg(\"muted\", ` ${message}`), 0, 0));\n\t\t}\n\t}\n\n\thandleInput(keyData: string): void {\n\t\tconst kb = getEditorKeybindings();\n\t\t// Up arrow\n\t\tif (kb.matches(keyData, \"selectUp\")) {\n\t\t\tthis.selectedIndex = Math.max(0, this.selectedIndex - 1);\n\t\t\tthis.updateList();\n\t\t}\n\t\t// Down arrow\n\t\telse if (kb.matches(keyData, \"selectDown\")) {\n\t\t\tthis.selectedIndex = Math.min(this.allProviders.length - 1, this.selectedIndex + 1);\n\t\t\tthis.updateList();\n\t\t}\n\t\t// Enter\n\t\telse if (kb.matches(keyData, \"selectConfirm\")) {\n\t\t\tconst selectedProvider = this.allProviders[this.selectedIndex];\n\t\t\tif (selectedProvider) {\n\t\t\t\tthis.onSelectCallback(selectedProvider.id);\n\t\t\t}\n\t\t}\n\t\t// Escape or Ctrl+C\n\t\telse if (kb.matches(keyData, \"selectCancel\")) {\n\t\t\tthis.onCancelCallback();\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"oauth-selector.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/oauth-selector.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAyC,MAAM,sBAAsB,CAAC;AACxF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAIjE;;GAEG;AACH,qBAAa,sBAAuB,SAAQ,SAAS;IACpD,OAAO,CAAC,aAAa,CAAY;IACjC,OAAO,CAAC,YAAY,CAAgC;IACpD,OAAO,CAAC,aAAa,CAAa;IAClC,OAAO,CAAC,IAAI,CAAqB;IACjC,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,gBAAgB,CAA+B;IACvD,OAAO,CAAC,gBAAgB,CAAa;IAErC,YACC,IAAI,EAAE,OAAO,GAAG,QAAQ,EACxB,WAAW,EAAE,WAAW,EACxB,QAAQ,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,EACtC,QAAQ,EAAE,MAAM,IAAI,EAgCpB;IAED,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,UAAU;IAmClB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAuBjC;CACD","sourcesContent":["import type { OAuthProviderInterface } from \"@mariozechner/pi-ai\";\nimport { getOAuthProviders } from \"@mariozechner/pi-ai/oauth\";\nimport { Container, getKeybindings, Spacer, TruncatedText } from \"@mariozechner/pi-tui\";\nimport type { AuthStorage } from \"../../../core/auth-storage.js\";\nimport { theme } from \"../theme/theme.js\";\nimport { DynamicBorder } from \"./dynamic-border.js\";\n\n/**\n * Component that renders an OAuth provider selector\n */\nexport class OAuthSelectorComponent extends Container {\n\tprivate listContainer: Container;\n\tprivate allProviders: OAuthProviderInterface[] = [];\n\tprivate selectedIndex: number = 0;\n\tprivate mode: \"login\" | \"logout\";\n\tprivate authStorage: AuthStorage;\n\tprivate onSelectCallback: (providerId: string) => void;\n\tprivate onCancelCallback: () => void;\n\n\tconstructor(\n\t\tmode: \"login\" | \"logout\",\n\t\tauthStorage: AuthStorage,\n\t\tonSelect: (providerId: string) => void,\n\t\tonCancel: () => void,\n\t) {\n\t\tsuper();\n\n\t\tthis.mode = mode;\n\t\tthis.authStorage = authStorage;\n\t\tthis.onSelectCallback = onSelect;\n\t\tthis.onCancelCallback = onCancel;\n\n\t\t// Load all OAuth providers\n\t\tthis.loadProviders();\n\n\t\t// Add top border\n\t\tthis.addChild(new DynamicBorder());\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Add title\n\t\tconst title = mode === \"login\" ? \"Select provider to login:\" : \"Select provider to logout:\";\n\t\tthis.addChild(new TruncatedText(theme.bold(title)));\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Create list container\n\t\tthis.listContainer = new Container();\n\t\tthis.addChild(this.listContainer);\n\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Add bottom border\n\t\tthis.addChild(new DynamicBorder());\n\n\t\t// Initial render\n\t\tthis.updateList();\n\t}\n\n\tprivate loadProviders(): void {\n\t\tthis.allProviders = getOAuthProviders();\n\t}\n\n\tprivate updateList(): void {\n\t\tthis.listContainer.clear();\n\n\t\tfor (let i = 0; i < this.allProviders.length; i++) {\n\t\t\tconst provider = this.allProviders[i];\n\t\t\tif (!provider) continue;\n\n\t\t\tconst isSelected = i === this.selectedIndex;\n\n\t\t\t// Check if user is logged in for this provider\n\t\t\tconst credentials = this.authStorage.get(provider.id);\n\t\t\tconst isLoggedIn = credentials?.type === \"oauth\";\n\t\t\tconst statusIndicator = isLoggedIn ? theme.fg(\"success\", \" ✓ logged in\") : \"\";\n\n\t\t\tlet line = \"\";\n\t\t\tif (isSelected) {\n\t\t\t\tconst prefix = theme.fg(\"accent\", \"→ \");\n\t\t\t\tconst text = theme.fg(\"accent\", provider.name);\n\t\t\t\tline = prefix + text + statusIndicator;\n\t\t\t} else {\n\t\t\t\tconst text = ` ${provider.name}`;\n\t\t\t\tline = text + statusIndicator;\n\t\t\t}\n\n\t\t\tthis.listContainer.addChild(new TruncatedText(line, 0, 0));\n\t\t}\n\n\t\t// Show \"no providers\" if empty\n\t\tif (this.allProviders.length === 0) {\n\t\t\tconst message =\n\t\t\t\tthis.mode === \"login\" ? \"No OAuth providers available\" : \"No OAuth providers logged in. Use /login first.\";\n\t\t\tthis.listContainer.addChild(new TruncatedText(theme.fg(\"muted\", ` ${message}`), 0, 0));\n\t\t}\n\t}\n\n\thandleInput(keyData: string): void {\n\t\tconst kb = getKeybindings();\n\t\t// Up arrow\n\t\tif (kb.matches(keyData, \"tui.select.up\")) {\n\t\t\tthis.selectedIndex = Math.max(0, this.selectedIndex - 1);\n\t\t\tthis.updateList();\n\t\t}\n\t\t// Down arrow\n\t\telse if (kb.matches(keyData, \"tui.select.down\")) {\n\t\t\tthis.selectedIndex = Math.min(this.allProviders.length - 1, this.selectedIndex + 1);\n\t\t\tthis.updateList();\n\t\t}\n\t\t// Enter\n\t\telse if (kb.matches(keyData, \"tui.select.confirm\")) {\n\t\t\tconst selectedProvider = this.allProviders[this.selectedIndex];\n\t\t\tif (selectedProvider) {\n\t\t\t\tthis.onSelectCallback(selectedProvider.id);\n\t\t\t}\n\t\t}\n\t\t// Escape or Ctrl+C\n\t\telse if (kb.matches(keyData, \"tui.select.cancel\")) {\n\t\t\tthis.onCancelCallback();\n\t\t}\n\t}\n}\n"]}
@@ -1,20 +1,15 @@
1
1
  import { getOAuthProviders } from "@mariozechner/pi-ai/oauth";
2
- import { Container, getEditorKeybindings, Spacer, TruncatedText } from "@mariozechner/pi-tui";
2
+ import { Container, getKeybindings, Spacer, TruncatedText } from "@mariozechner/pi-tui";
3
3
  import { theme } from "../theme/theme.js";
4
4
  import { DynamicBorder } from "./dynamic-border.js";
5
5
  /**
6
6
  * Component that renders an OAuth provider selector
7
7
  */
8
8
  export class OAuthSelectorComponent extends Container {
9
- listContainer;
10
- allProviders = [];
11
- selectedIndex = 0;
12
- mode;
13
- authStorage;
14
- onSelectCallback;
15
- onCancelCallback;
16
9
  constructor(mode, authStorage, onSelect, onCancel) {
17
10
  super();
11
+ this.allProviders = [];
12
+ this.selectedIndex = 0;
18
13
  this.mode = mode;
19
14
  this.authStorage = authStorage;
20
15
  this.onSelectCallback = onSelect;
@@ -70,26 +65,26 @@ export class OAuthSelectorComponent extends Container {
70
65
  }
71
66
  }
72
67
  handleInput(keyData) {
73
- const kb = getEditorKeybindings();
68
+ const kb = getKeybindings();
74
69
  // Up arrow
75
- if (kb.matches(keyData, "selectUp")) {
70
+ if (kb.matches(keyData, "tui.select.up")) {
76
71
  this.selectedIndex = Math.max(0, this.selectedIndex - 1);
77
72
  this.updateList();
78
73
  }
79
74
  // Down arrow
80
- else if (kb.matches(keyData, "selectDown")) {
75
+ else if (kb.matches(keyData, "tui.select.down")) {
81
76
  this.selectedIndex = Math.min(this.allProviders.length - 1, this.selectedIndex + 1);
82
77
  this.updateList();
83
78
  }
84
79
  // Enter
85
- else if (kb.matches(keyData, "selectConfirm")) {
80
+ else if (kb.matches(keyData, "tui.select.confirm")) {
86
81
  const selectedProvider = this.allProviders[this.selectedIndex];
87
82
  if (selectedProvider) {
88
83
  this.onSelectCallback(selectedProvider.id);
89
84
  }
90
85
  }
91
86
  // Escape or Ctrl+C
92
- else if (kb.matches(keyData, "selectCancel")) {
87
+ else if (kb.matches(keyData, "tui.select.cancel")) {
93
88
  this.onCancelCallback();
94
89
  }
95
90
  }
@@ -1 +1 @@
1
- {"version":3,"file":"oauth-selector.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/oauth-selector.ts"],"names":[],"mappings":"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","sourcesContent":["import type { OAuthProviderInterface } from \"@mariozechner/pi-ai\";\nimport { getOAuthProviders } from \"@mariozechner/pi-ai/oauth\";\nimport { Container, getEditorKeybindings, Spacer, TruncatedText } from \"@mariozechner/pi-tui\";\nimport type { AuthStorage } from \"../../../core/auth-storage.js\";\nimport { theme } from \"../theme/theme.js\";\nimport { DynamicBorder } from \"./dynamic-border.js\";\n\n/**\n * Component that renders an OAuth provider selector\n */\nexport class OAuthSelectorComponent extends Container {\n\tprivate listContainer: Container;\n\tprivate allProviders: OAuthProviderInterface[] = [];\n\tprivate selectedIndex: number = 0;\n\tprivate mode: \"login\" | \"logout\";\n\tprivate authStorage: AuthStorage;\n\tprivate onSelectCallback: (providerId: string) => void;\n\tprivate onCancelCallback: () => void;\n\n\tconstructor(\n\t\tmode: \"login\" | \"logout\",\n\t\tauthStorage: AuthStorage,\n\t\tonSelect: (providerId: string) => void,\n\t\tonCancel: () => void,\n\t) {\n\t\tsuper();\n\n\t\tthis.mode = mode;\n\t\tthis.authStorage = authStorage;\n\t\tthis.onSelectCallback = onSelect;\n\t\tthis.onCancelCallback = onCancel;\n\n\t\t// Load all OAuth providers\n\t\tthis.loadProviders();\n\n\t\t// Add top border\n\t\tthis.addChild(new DynamicBorder());\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Add title\n\t\tconst title = mode === \"login\" ? \"Select provider to login:\" : \"Select provider to logout:\";\n\t\tthis.addChild(new TruncatedText(theme.bold(title)));\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Create list container\n\t\tthis.listContainer = new Container();\n\t\tthis.addChild(this.listContainer);\n\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Add bottom border\n\t\tthis.addChild(new DynamicBorder());\n\n\t\t// Initial render\n\t\tthis.updateList();\n\t}\n\n\tprivate loadProviders(): void {\n\t\tthis.allProviders = getOAuthProviders();\n\t}\n\n\tprivate updateList(): void {\n\t\tthis.listContainer.clear();\n\n\t\tfor (let i = 0; i < this.allProviders.length; i++) {\n\t\t\tconst provider = this.allProviders[i];\n\t\t\tif (!provider) continue;\n\n\t\t\tconst isSelected = i === this.selectedIndex;\n\n\t\t\t// Check if user is logged in for this provider\n\t\t\tconst credentials = this.authStorage.get(provider.id);\n\t\t\tconst isLoggedIn = credentials?.type === \"oauth\";\n\t\t\tconst statusIndicator = isLoggedIn ? theme.fg(\"success\", \" ✓ logged in\") : \"\";\n\n\t\t\tlet line = \"\";\n\t\t\tif (isSelected) {\n\t\t\t\tconst prefix = theme.fg(\"accent\", \"→ \");\n\t\t\t\tconst text = theme.fg(\"accent\", provider.name);\n\t\t\t\tline = prefix + text + statusIndicator;\n\t\t\t} else {\n\t\t\t\tconst text = ` ${provider.name}`;\n\t\t\t\tline = text + statusIndicator;\n\t\t\t}\n\n\t\t\tthis.listContainer.addChild(new TruncatedText(line, 0, 0));\n\t\t}\n\n\t\t// Show \"no providers\" if empty\n\t\tif (this.allProviders.length === 0) {\n\t\t\tconst message =\n\t\t\t\tthis.mode === \"login\" ? \"No OAuth providers available\" : \"No OAuth providers logged in. Use /login first.\";\n\t\t\tthis.listContainer.addChild(new TruncatedText(theme.fg(\"muted\", ` ${message}`), 0, 0));\n\t\t}\n\t}\n\n\thandleInput(keyData: string): void {\n\t\tconst kb = getEditorKeybindings();\n\t\t// Up arrow\n\t\tif (kb.matches(keyData, \"selectUp\")) {\n\t\t\tthis.selectedIndex = Math.max(0, this.selectedIndex - 1);\n\t\t\tthis.updateList();\n\t\t}\n\t\t// Down arrow\n\t\telse if (kb.matches(keyData, \"selectDown\")) {\n\t\t\tthis.selectedIndex = Math.min(this.allProviders.length - 1, this.selectedIndex + 1);\n\t\t\tthis.updateList();\n\t\t}\n\t\t// Enter\n\t\telse if (kb.matches(keyData, \"selectConfirm\")) {\n\t\t\tconst selectedProvider = this.allProviders[this.selectedIndex];\n\t\t\tif (selectedProvider) {\n\t\t\t\tthis.onSelectCallback(selectedProvider.id);\n\t\t\t}\n\t\t}\n\t\t// Escape or Ctrl+C\n\t\telse if (kb.matches(keyData, \"selectCancel\")) {\n\t\t\tthis.onCancelCallback();\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"oauth-selector.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/oauth-selector.ts"],"names":[],"mappings":"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","sourcesContent":["import type { OAuthProviderInterface } from \"@mariozechner/pi-ai\";\nimport { getOAuthProviders } from \"@mariozechner/pi-ai/oauth\";\nimport { Container, getKeybindings, Spacer, TruncatedText } from \"@mariozechner/pi-tui\";\nimport type { AuthStorage } from \"../../../core/auth-storage.js\";\nimport { theme } from \"../theme/theme.js\";\nimport { DynamicBorder } from \"./dynamic-border.js\";\n\n/**\n * Component that renders an OAuth provider selector\n */\nexport class OAuthSelectorComponent extends Container {\n\tprivate listContainer: Container;\n\tprivate allProviders: OAuthProviderInterface[] = [];\n\tprivate selectedIndex: number = 0;\n\tprivate mode: \"login\" | \"logout\";\n\tprivate authStorage: AuthStorage;\n\tprivate onSelectCallback: (providerId: string) => void;\n\tprivate onCancelCallback: () => void;\n\n\tconstructor(\n\t\tmode: \"login\" | \"logout\",\n\t\tauthStorage: AuthStorage,\n\t\tonSelect: (providerId: string) => void,\n\t\tonCancel: () => void,\n\t) {\n\t\tsuper();\n\n\t\tthis.mode = mode;\n\t\tthis.authStorage = authStorage;\n\t\tthis.onSelectCallback = onSelect;\n\t\tthis.onCancelCallback = onCancel;\n\n\t\t// Load all OAuth providers\n\t\tthis.loadProviders();\n\n\t\t// Add top border\n\t\tthis.addChild(new DynamicBorder());\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Add title\n\t\tconst title = mode === \"login\" ? \"Select provider to login:\" : \"Select provider to logout:\";\n\t\tthis.addChild(new TruncatedText(theme.bold(title)));\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Create list container\n\t\tthis.listContainer = new Container();\n\t\tthis.addChild(this.listContainer);\n\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Add bottom border\n\t\tthis.addChild(new DynamicBorder());\n\n\t\t// Initial render\n\t\tthis.updateList();\n\t}\n\n\tprivate loadProviders(): void {\n\t\tthis.allProviders = getOAuthProviders();\n\t}\n\n\tprivate updateList(): void {\n\t\tthis.listContainer.clear();\n\n\t\tfor (let i = 0; i < this.allProviders.length; i++) {\n\t\t\tconst provider = this.allProviders[i];\n\t\t\tif (!provider) continue;\n\n\t\t\tconst isSelected = i === this.selectedIndex;\n\n\t\t\t// Check if user is logged in for this provider\n\t\t\tconst credentials = this.authStorage.get(provider.id);\n\t\t\tconst isLoggedIn = credentials?.type === \"oauth\";\n\t\t\tconst statusIndicator = isLoggedIn ? theme.fg(\"success\", \" ✓ logged in\") : \"\";\n\n\t\t\tlet line = \"\";\n\t\t\tif (isSelected) {\n\t\t\t\tconst prefix = theme.fg(\"accent\", \"→ \");\n\t\t\t\tconst text = theme.fg(\"accent\", provider.name);\n\t\t\t\tline = prefix + text + statusIndicator;\n\t\t\t} else {\n\t\t\t\tconst text = ` ${provider.name}`;\n\t\t\t\tline = text + statusIndicator;\n\t\t\t}\n\n\t\t\tthis.listContainer.addChild(new TruncatedText(line, 0, 0));\n\t\t}\n\n\t\t// Show \"no providers\" if empty\n\t\tif (this.allProviders.length === 0) {\n\t\t\tconst message =\n\t\t\t\tthis.mode === \"login\" ? \"No OAuth providers available\" : \"No OAuth providers logged in. Use /login first.\";\n\t\t\tthis.listContainer.addChild(new TruncatedText(theme.fg(\"muted\", ` ${message}`), 0, 0));\n\t\t}\n\t}\n\n\thandleInput(keyData: string): void {\n\t\tconst kb = getKeybindings();\n\t\t// Up arrow\n\t\tif (kb.matches(keyData, \"tui.select.up\")) {\n\t\t\tthis.selectedIndex = Math.max(0, this.selectedIndex - 1);\n\t\t\tthis.updateList();\n\t\t}\n\t\t// Down arrow\n\t\telse if (kb.matches(keyData, \"tui.select.down\")) {\n\t\t\tthis.selectedIndex = Math.min(this.allProviders.length - 1, this.selectedIndex + 1);\n\t\t\tthis.updateList();\n\t\t}\n\t\t// Enter\n\t\telse if (kb.matches(keyData, \"tui.select.confirm\")) {\n\t\t\tconst selectedProvider = this.allProviders[this.selectedIndex];\n\t\t\tif (selectedProvider) {\n\t\t\t\tthis.onSelectCallback(selectedProvider.id);\n\t\t\t}\n\t\t}\n\t\t// Escape or Ctrl+C\n\t\telse if (kb.matches(keyData, \"tui.select.cancel\")) {\n\t\t\tthis.onCancelCallback();\n\t\t}\n\t}\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"scoped-models-selector.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/scoped-models-selector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EACN,SAAS,EACT,KAAK,SAAS,EAGd,KAAK,EAKL,MAAM,sBAAsB,CAAC;AA2D9B,MAAM,WAAW,YAAY;IAC5B,SAAS,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;IACxB,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC7B,qEAAqE;IACrE,sBAAsB,EAAE,OAAO,CAAC;CAChC;AAED,MAAM,WAAW,eAAe;IAC/B,gEAAgE;IAChE,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IAC3D,sEAAsE;IACtE,SAAS,EAAE,CAAC,eAAe,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAC/C,0EAA0E;IAC1E,WAAW,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAC7C,yCAAyC;IACzC,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,sFAAsF;IACtF,gBAAgB,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACnF,QAAQ,EAAE,MAAM,IAAI,CAAC;CACrB;AAED;;;GAGG;AACH,qBAAa,6BAA8B,SAAQ,SAAU,YAAW,SAAS;IAChF,OAAO,CAAC,UAAU,CAAsC;IACxD,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,UAAU,CAAoB;IACtC,OAAO,CAAC,aAAa,CAAmB;IACxC,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,WAAW,CAAQ;IAG3B,OAAO,CAAC,QAAQ,CAAS;IACzB,IAAI,OAAO,IAAI,OAAO,CAErB;IACD,IAAI,OAAO,CAAC,KAAK,EAAE,OAAO,EAGzB;IACD,OAAO,CAAC,aAAa,CAAY;IACjC,OAAO,CAAC,UAAU,CAAO;IACzB,OAAO,CAAC,SAAS,CAAkB;IACnC,OAAO,CAAC,UAAU,CAAM;IACxB,OAAO,CAAC,OAAO,CAAS;IAExB,YAAY,MAAM,EAAE,YAAY,EAAE,SAAS,EAAE,eAAe,EAoC3D;IAED,OAAO,CAAC,UAAU;IAWlB,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,OAAO;IASf,OAAO,CAAC,UAAU;IAuClB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAmH9B;IAED,cAAc,IAAI,KAAK,CAEtB;CACD","sourcesContent":["import type { Model } from \"@mariozechner/pi-ai\";\nimport {\n\tContainer,\n\ttype Focusable,\n\tfuzzyFilter,\n\tgetEditorKeybindings,\n\tInput,\n\tKey,\n\tmatchesKey,\n\tSpacer,\n\tText,\n} from \"@mariozechner/pi-tui\";\nimport { theme } from \"../theme/theme.js\";\nimport { DynamicBorder } from \"./dynamic-border.js\";\n\n// EnabledIds: null = all enabled (no filter), string[] = explicit ordered list\ntype EnabledIds = string[] | null;\n\nfunction isEnabled(enabledIds: EnabledIds, id: string): boolean {\n\treturn enabledIds === null || enabledIds.includes(id);\n}\n\nfunction toggle(enabledIds: EnabledIds, id: string): EnabledIds {\n\tif (enabledIds === null) return [id]; // First toggle: start with only this one\n\tconst index = enabledIds.indexOf(id);\n\tif (index >= 0) return [...enabledIds.slice(0, index), ...enabledIds.slice(index + 1)];\n\treturn [...enabledIds, id];\n}\n\nfunction enableAll(enabledIds: EnabledIds, allIds: string[], targetIds?: string[]): EnabledIds {\n\tif (enabledIds === null) return null; // Already all enabled\n\tconst targets = targetIds ?? allIds;\n\tconst result = [...enabledIds];\n\tfor (const id of targets) {\n\t\tif (!result.includes(id)) result.push(id);\n\t}\n\treturn result.length === allIds.length ? null : result;\n}\n\nfunction clearAll(enabledIds: EnabledIds, allIds: string[], targetIds?: string[]): EnabledIds {\n\tif (enabledIds === null) {\n\t\treturn targetIds ? allIds.filter((id) => !targetIds.includes(id)) : [];\n\t}\n\tconst targets = new Set(targetIds ?? enabledIds);\n\treturn enabledIds.filter((id) => !targets.has(id));\n}\n\nfunction move(enabledIds: EnabledIds, allIds: string[], id: string, delta: number): EnabledIds {\n\tconst list = enabledIds ?? [...allIds];\n\tconst index = list.indexOf(id);\n\tif (index < 0) return list;\n\tconst newIndex = index + delta;\n\tif (newIndex < 0 || newIndex >= list.length) return list;\n\tconst result = [...list];\n\t[result[index], result[newIndex]] = [result[newIndex], result[index]];\n\treturn result;\n}\n\nfunction getSortedIds(enabledIds: EnabledIds, allIds: string[]): string[] {\n\tif (enabledIds === null) return allIds;\n\tconst enabledSet = new Set(enabledIds);\n\treturn [...enabledIds, ...allIds.filter((id) => !enabledSet.has(id))];\n}\n\ninterface ModelItem {\n\tfullId: string;\n\tmodel: Model<any>;\n\tenabled: boolean;\n}\n\nexport interface ModelsConfig {\n\tallModels: Model<any>[];\n\tenabledModelIds: Set<string>;\n\t/** true if enabledModels setting is defined (empty = all enabled) */\n\thasEnabledModelsFilter: boolean;\n}\n\nexport interface ModelsCallbacks {\n\t/** Called when a model is toggled (session-only, no persist) */\n\tonModelToggle: (modelId: string, enabled: boolean) => void;\n\t/** Called when user wants to persist current selection to settings */\n\tonPersist: (enabledModelIds: string[]) => void;\n\t/** Called when user enables all models. Returns list of all model IDs. */\n\tonEnableAll: (allModelIds: string[]) => void;\n\t/** Called when user clears all models */\n\tonClearAll: () => void;\n\t/** Called when user toggles all models for a provider. Returns affected model IDs. */\n\tonToggleProvider: (provider: string, modelIds: string[], enabled: boolean) => void;\n\tonCancel: () => void;\n}\n\n/**\n * Component for enabling/disabling models for Ctrl+P cycling.\n * Changes are session-only until explicitly persisted with Ctrl+S.\n */\nexport class ScopedModelsSelectorComponent extends Container implements Focusable {\n\tprivate modelsById: Map<string, Model<any>> = new Map();\n\tprivate allIds: string[] = [];\n\tprivate enabledIds: EnabledIds = null;\n\tprivate filteredItems: ModelItem[] = [];\n\tprivate selectedIndex = 0;\n\tprivate searchInput: Input;\n\n\t// Focusable implementation - propagate to searchInput for IME cursor positioning\n\tprivate _focused = false;\n\tget focused(): boolean {\n\t\treturn this._focused;\n\t}\n\tset focused(value: boolean) {\n\t\tthis._focused = value;\n\t\tthis.searchInput.focused = value;\n\t}\n\tprivate listContainer: Container;\n\tprivate footerText: Text;\n\tprivate callbacks: ModelsCallbacks;\n\tprivate maxVisible = 15;\n\tprivate isDirty = false;\n\n\tconstructor(config: ModelsConfig, callbacks: ModelsCallbacks) {\n\t\tsuper();\n\t\tthis.callbacks = callbacks;\n\n\t\tfor (const model of config.allModels) {\n\t\t\tconst fullId = `${model.provider}/${model.id}`;\n\t\t\tthis.modelsById.set(fullId, model);\n\t\t\tthis.allIds.push(fullId);\n\t\t}\n\n\t\tthis.enabledIds = config.hasEnabledModelsFilter ? [...config.enabledModelIds] : null;\n\t\tthis.filteredItems = this.buildItems();\n\n\t\t// Header\n\t\tthis.addChild(new DynamicBorder());\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new Text(theme.fg(\"accent\", theme.bold(\"Model Configuration\")), 0, 0));\n\t\tthis.addChild(new Text(theme.fg(\"muted\", \"Session-only. Ctrl+S to save to settings.\"), 0, 0));\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Search input\n\t\tthis.searchInput = new Input();\n\t\tthis.addChild(this.searchInput);\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// List container\n\t\tthis.listContainer = new Container();\n\t\tthis.addChild(this.listContainer);\n\n\t\t// Footer hint\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.footerText = new Text(this.getFooterText(), 0, 0);\n\t\tthis.addChild(this.footerText);\n\n\t\tthis.addChild(new DynamicBorder());\n\t\tthis.updateList();\n\t}\n\n\tprivate buildItems(): ModelItem[] {\n\t\t// Filter out IDs that no longer have a corresponding model (e.g., after logout)\n\t\treturn getSortedIds(this.enabledIds, this.allIds)\n\t\t\t.filter((id) => this.modelsById.has(id))\n\t\t\t.map((id) => ({\n\t\t\t\tfullId: id,\n\t\t\t\tmodel: this.modelsById.get(id)!,\n\t\t\t\tenabled: isEnabled(this.enabledIds, id),\n\t\t\t}));\n\t}\n\n\tprivate getFooterText(): string {\n\t\tconst enabledCount = this.enabledIds?.length ?? this.allIds.length;\n\t\tconst allEnabled = this.enabledIds === null;\n\t\tconst countText = allEnabled ? \"all enabled\" : `${enabledCount}/${this.allIds.length} enabled`;\n\t\tconst parts = [\"Enter toggle\", \"^A all\", \"^X clear\", \"^P provider\", \"Alt+↑↓ reorder\", \"^S save\", countText];\n\t\treturn this.isDirty\n\t\t\t? theme.fg(\"dim\", ` ${parts.join(\" · \")} `) + theme.fg(\"warning\", \"(unsaved)\")\n\t\t\t: theme.fg(\"dim\", ` ${parts.join(\" · \")}`);\n\t}\n\n\tprivate refresh(): void {\n\t\tconst query = this.searchInput.getValue();\n\t\tconst items = this.buildItems();\n\t\tthis.filteredItems = query ? fuzzyFilter(items, query, (i) => `${i.model.id} ${i.model.provider}`) : items;\n\t\tthis.selectedIndex = Math.min(this.selectedIndex, Math.max(0, this.filteredItems.length - 1));\n\t\tthis.updateList();\n\t\tthis.footerText.setText(this.getFooterText());\n\t}\n\n\tprivate updateList(): void {\n\t\tthis.listContainer.clear();\n\n\t\tif (this.filteredItems.length === 0) {\n\t\t\tthis.listContainer.addChild(new Text(theme.fg(\"muted\", \" No matching models\"), 0, 0));\n\t\t\treturn;\n\t\t}\n\n\t\tconst startIndex = Math.max(\n\t\t\t0,\n\t\t\tMath.min(this.selectedIndex - Math.floor(this.maxVisible / 2), this.filteredItems.length - this.maxVisible),\n\t\t);\n\t\tconst endIndex = Math.min(startIndex + this.maxVisible, this.filteredItems.length);\n\t\tconst allEnabled = this.enabledIds === null;\n\n\t\tfor (let i = startIndex; i < endIndex; i++) {\n\t\t\tconst item = this.filteredItems[i]!;\n\t\t\tconst isSelected = i === this.selectedIndex;\n\t\t\tconst prefix = isSelected ? theme.fg(\"accent\", \"→ \") : \" \";\n\t\t\tconst modelText = isSelected ? theme.fg(\"accent\", item.model.id) : item.model.id;\n\t\t\tconst providerBadge = theme.fg(\"muted\", ` [${item.model.provider}]`);\n\t\t\tconst status = allEnabled ? \"\" : item.enabled ? theme.fg(\"success\", \" ✓\") : theme.fg(\"dim\", \" ✗\");\n\t\t\tthis.listContainer.addChild(new Text(`${prefix}${modelText}${providerBadge}${status}`, 0, 0));\n\t\t}\n\n\t\t// Add scroll indicator if needed\n\t\tif (startIndex > 0 || endIndex < this.filteredItems.length) {\n\t\t\tthis.listContainer.addChild(\n\t\t\t\tnew Text(theme.fg(\"muted\", ` (${this.selectedIndex + 1}/${this.filteredItems.length})`), 0, 0),\n\t\t\t);\n\t\t}\n\n\t\tif (this.filteredItems.length > 0) {\n\t\t\tconst selected = this.filteredItems[this.selectedIndex];\n\t\t\tthis.listContainer.addChild(new Spacer(1));\n\t\t\tthis.listContainer.addChild(new Text(theme.fg(\"muted\", ` Model Name: ${selected.model.name}`), 0, 0));\n\t\t}\n\t}\n\n\thandleInput(data: string): void {\n\t\tconst kb = getEditorKeybindings();\n\n\t\t// Navigation\n\t\tif (kb.matches(data, \"selectUp\")) {\n\t\t\tif (this.filteredItems.length === 0) return;\n\t\t\tthis.selectedIndex = this.selectedIndex === 0 ? this.filteredItems.length - 1 : this.selectedIndex - 1;\n\t\t\tthis.updateList();\n\t\t\treturn;\n\t\t}\n\t\tif (kb.matches(data, \"selectDown\")) {\n\t\t\tif (this.filteredItems.length === 0) return;\n\t\t\tthis.selectedIndex = this.selectedIndex === this.filteredItems.length - 1 ? 0 : this.selectedIndex + 1;\n\t\t\tthis.updateList();\n\t\t\treturn;\n\t\t}\n\n\t\t// Alt+Up/Down - Reorder enabled models\n\t\tif (matchesKey(data, Key.alt(\"up\")) || matchesKey(data, Key.alt(\"down\"))) {\n\t\t\tconst item = this.filteredItems[this.selectedIndex];\n\t\t\tif (item && isEnabled(this.enabledIds, item.fullId)) {\n\t\t\t\tconst delta = matchesKey(data, Key.alt(\"up\")) ? -1 : 1;\n\t\t\t\tconst enabledList = this.enabledIds ?? this.allIds;\n\t\t\t\tconst currentIndex = enabledList.indexOf(item.fullId);\n\t\t\t\tconst newIndex = currentIndex + delta;\n\t\t\t\t// Only move if within bounds\n\t\t\t\tif (newIndex >= 0 && newIndex < enabledList.length) {\n\t\t\t\t\tthis.enabledIds = move(this.enabledIds, this.allIds, item.fullId, delta);\n\t\t\t\t\tthis.isDirty = true;\n\t\t\t\t\tthis.selectedIndex += delta;\n\t\t\t\t\tthis.refresh();\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Toggle on Enter\n\t\tif (matchesKey(data, Key.enter)) {\n\t\t\tconst item = this.filteredItems[this.selectedIndex];\n\t\t\tif (item) {\n\t\t\t\tconst wasAllEnabled = this.enabledIds === null;\n\t\t\t\tthis.enabledIds = toggle(this.enabledIds, item.fullId);\n\t\t\t\tthis.isDirty = true;\n\t\t\t\tif (wasAllEnabled) this.callbacks.onClearAll();\n\t\t\t\tthis.callbacks.onModelToggle(item.fullId, isEnabled(this.enabledIds, item.fullId));\n\t\t\t\tthis.refresh();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Ctrl+A - Enable all (filtered if search active, otherwise all)\n\t\tif (matchesKey(data, Key.ctrl(\"a\"))) {\n\t\t\tconst targetIds = this.searchInput.getValue() ? this.filteredItems.map((i) => i.fullId) : undefined;\n\t\t\tthis.enabledIds = enableAll(this.enabledIds, this.allIds, targetIds);\n\t\t\tthis.isDirty = true;\n\t\t\tthis.callbacks.onEnableAll(targetIds ?? this.allIds);\n\t\t\tthis.refresh();\n\t\t\treturn;\n\t\t}\n\n\t\t// Ctrl+X - Clear all (filtered if search active, otherwise all)\n\t\tif (matchesKey(data, Key.ctrl(\"x\"))) {\n\t\t\tconst targetIds = this.searchInput.getValue() ? this.filteredItems.map((i) => i.fullId) : undefined;\n\t\t\tthis.enabledIds = clearAll(this.enabledIds, this.allIds, targetIds);\n\t\t\tthis.isDirty = true;\n\t\t\tthis.callbacks.onClearAll();\n\t\t\tthis.refresh();\n\t\t\treturn;\n\t\t}\n\n\t\t// Ctrl+P - Toggle provider of current item\n\t\tif (matchesKey(data, Key.ctrl(\"p\"))) {\n\t\t\tconst item = this.filteredItems[this.selectedIndex];\n\t\t\tif (item) {\n\t\t\t\tconst provider = item.model.provider;\n\t\t\t\tconst providerIds = this.allIds.filter((id) => this.modelsById.get(id)!.provider === provider);\n\t\t\t\tconst allEnabled = providerIds.every((id) => isEnabled(this.enabledIds, id));\n\t\t\t\tthis.enabledIds = allEnabled\n\t\t\t\t\t? clearAll(this.enabledIds, this.allIds, providerIds)\n\t\t\t\t\t: enableAll(this.enabledIds, this.allIds, providerIds);\n\t\t\t\tthis.isDirty = true;\n\t\t\t\tthis.callbacks.onToggleProvider(provider, providerIds, !allEnabled);\n\t\t\t\tthis.refresh();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Ctrl+S - Save/persist to settings\n\t\tif (matchesKey(data, Key.ctrl(\"s\"))) {\n\t\t\tthis.callbacks.onPersist(this.enabledIds ?? [...this.allIds]);\n\t\t\tthis.isDirty = false;\n\t\t\tthis.footerText.setText(this.getFooterText());\n\t\t\treturn;\n\t\t}\n\n\t\t// Ctrl+C - clear search or cancel if empty\n\t\tif (matchesKey(data, Key.ctrl(\"c\"))) {\n\t\t\tif (this.searchInput.getValue()) {\n\t\t\t\tthis.searchInput.setValue(\"\");\n\t\t\t\tthis.refresh();\n\t\t\t} else {\n\t\t\t\tthis.callbacks.onCancel();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Escape - cancel\n\t\tif (matchesKey(data, Key.escape)) {\n\t\t\tthis.callbacks.onCancel();\n\t\t\treturn;\n\t\t}\n\n\t\t// Pass everything else to search input\n\t\tthis.searchInput.handleInput(data);\n\t\tthis.refresh();\n\t}\n\n\tgetSearchInput(): Input {\n\t\treturn this.searchInput;\n\t}\n}\n"]}
1
+ {"version":3,"file":"scoped-models-selector.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/scoped-models-selector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EACN,SAAS,EACT,KAAK,SAAS,EAGd,KAAK,EAKL,MAAM,sBAAsB,CAAC;AA2D9B,MAAM,WAAW,YAAY;IAC5B,SAAS,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;IACxB,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC7B,qEAAqE;IACrE,sBAAsB,EAAE,OAAO,CAAC;CAChC;AAED,MAAM,WAAW,eAAe;IAC/B,gEAAgE;IAChE,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IAC3D,sEAAsE;IACtE,SAAS,EAAE,CAAC,eAAe,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAC/C,0EAA0E;IAC1E,WAAW,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAC7C,yCAAyC;IACzC,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,sFAAsF;IACtF,gBAAgB,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACnF,QAAQ,EAAE,MAAM,IAAI,CAAC;CACrB;AAED;;;GAGG;AACH,qBAAa,6BAA8B,SAAQ,SAAU,YAAW,SAAS;IAChF,OAAO,CAAC,UAAU,CAAsC;IACxD,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,UAAU,CAAoB;IACtC,OAAO,CAAC,aAAa,CAAmB;IACxC,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,WAAW,CAAQ;IAG3B,OAAO,CAAC,QAAQ,CAAS;IACzB,IAAI,OAAO,IAAI,OAAO,CAErB;IACD,IAAI,OAAO,CAAC,KAAK,EAAE,OAAO,EAGzB;IACD,OAAO,CAAC,aAAa,CAAY;IACjC,OAAO,CAAC,UAAU,CAAO;IACzB,OAAO,CAAC,SAAS,CAAkB;IACnC,OAAO,CAAC,UAAU,CAAM;IACxB,OAAO,CAAC,OAAO,CAAS;IAExB,YAAY,MAAM,EAAE,YAAY,EAAE,SAAS,EAAE,eAAe,EAoC3D;IAED,OAAO,CAAC,UAAU;IAWlB,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,OAAO;IASf,OAAO,CAAC,UAAU;IAuClB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAmH9B;IAED,cAAc,IAAI,KAAK,CAEtB;CACD","sourcesContent":["import type { Model } from \"@mariozechner/pi-ai\";\nimport {\n\tContainer,\n\ttype Focusable,\n\tfuzzyFilter,\n\tgetKeybindings,\n\tInput,\n\tKey,\n\tmatchesKey,\n\tSpacer,\n\tText,\n} from \"@mariozechner/pi-tui\";\nimport { theme } from \"../theme/theme.js\";\nimport { DynamicBorder } from \"./dynamic-border.js\";\n\n// EnabledIds: null = all enabled (no filter), string[] = explicit ordered list\ntype EnabledIds = string[] | null;\n\nfunction isEnabled(enabledIds: EnabledIds, id: string): boolean {\n\treturn enabledIds === null || enabledIds.includes(id);\n}\n\nfunction toggle(enabledIds: EnabledIds, id: string): EnabledIds {\n\tif (enabledIds === null) return [id]; // First toggle: start with only this one\n\tconst index = enabledIds.indexOf(id);\n\tif (index >= 0) return [...enabledIds.slice(0, index), ...enabledIds.slice(index + 1)];\n\treturn [...enabledIds, id];\n}\n\nfunction enableAll(enabledIds: EnabledIds, allIds: string[], targetIds?: string[]): EnabledIds {\n\tif (enabledIds === null) return null; // Already all enabled\n\tconst targets = targetIds ?? allIds;\n\tconst result = [...enabledIds];\n\tfor (const id of targets) {\n\t\tif (!result.includes(id)) result.push(id);\n\t}\n\treturn result.length === allIds.length ? null : result;\n}\n\nfunction clearAll(enabledIds: EnabledIds, allIds: string[], targetIds?: string[]): EnabledIds {\n\tif (enabledIds === null) {\n\t\treturn targetIds ? allIds.filter((id) => !targetIds.includes(id)) : [];\n\t}\n\tconst targets = new Set(targetIds ?? enabledIds);\n\treturn enabledIds.filter((id) => !targets.has(id));\n}\n\nfunction move(enabledIds: EnabledIds, allIds: string[], id: string, delta: number): EnabledIds {\n\tconst list = enabledIds ?? [...allIds];\n\tconst index = list.indexOf(id);\n\tif (index < 0) return list;\n\tconst newIndex = index + delta;\n\tif (newIndex < 0 || newIndex >= list.length) return list;\n\tconst result = [...list];\n\t[result[index], result[newIndex]] = [result[newIndex], result[index]];\n\treturn result;\n}\n\nfunction getSortedIds(enabledIds: EnabledIds, allIds: string[]): string[] {\n\tif (enabledIds === null) return allIds;\n\tconst enabledSet = new Set(enabledIds);\n\treturn [...enabledIds, ...allIds.filter((id) => !enabledSet.has(id))];\n}\n\ninterface ModelItem {\n\tfullId: string;\n\tmodel: Model<any>;\n\tenabled: boolean;\n}\n\nexport interface ModelsConfig {\n\tallModels: Model<any>[];\n\tenabledModelIds: Set<string>;\n\t/** true if enabledModels setting is defined (empty = all enabled) */\n\thasEnabledModelsFilter: boolean;\n}\n\nexport interface ModelsCallbacks {\n\t/** Called when a model is toggled (session-only, no persist) */\n\tonModelToggle: (modelId: string, enabled: boolean) => void;\n\t/** Called when user wants to persist current selection to settings */\n\tonPersist: (enabledModelIds: string[]) => void;\n\t/** Called when user enables all models. Returns list of all model IDs. */\n\tonEnableAll: (allModelIds: string[]) => void;\n\t/** Called when user clears all models */\n\tonClearAll: () => void;\n\t/** Called when user toggles all models for a provider. Returns affected model IDs. */\n\tonToggleProvider: (provider: string, modelIds: string[], enabled: boolean) => void;\n\tonCancel: () => void;\n}\n\n/**\n * Component for enabling/disabling models for Ctrl+P cycling.\n * Changes are session-only until explicitly persisted with Ctrl+S.\n */\nexport class ScopedModelsSelectorComponent extends Container implements Focusable {\n\tprivate modelsById: Map<string, Model<any>> = new Map();\n\tprivate allIds: string[] = [];\n\tprivate enabledIds: EnabledIds = null;\n\tprivate filteredItems: ModelItem[] = [];\n\tprivate selectedIndex = 0;\n\tprivate searchInput: Input;\n\n\t// Focusable implementation - propagate to searchInput for IME cursor positioning\n\tprivate _focused = false;\n\tget focused(): boolean {\n\t\treturn this._focused;\n\t}\n\tset focused(value: boolean) {\n\t\tthis._focused = value;\n\t\tthis.searchInput.focused = value;\n\t}\n\tprivate listContainer: Container;\n\tprivate footerText: Text;\n\tprivate callbacks: ModelsCallbacks;\n\tprivate maxVisible = 15;\n\tprivate isDirty = false;\n\n\tconstructor(config: ModelsConfig, callbacks: ModelsCallbacks) {\n\t\tsuper();\n\t\tthis.callbacks = callbacks;\n\n\t\tfor (const model of config.allModels) {\n\t\t\tconst fullId = `${model.provider}/${model.id}`;\n\t\t\tthis.modelsById.set(fullId, model);\n\t\t\tthis.allIds.push(fullId);\n\t\t}\n\n\t\tthis.enabledIds = config.hasEnabledModelsFilter ? [...config.enabledModelIds] : null;\n\t\tthis.filteredItems = this.buildItems();\n\n\t\t// Header\n\t\tthis.addChild(new DynamicBorder());\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new Text(theme.fg(\"accent\", theme.bold(\"Model Configuration\")), 0, 0));\n\t\tthis.addChild(new Text(theme.fg(\"muted\", \"Session-only. Ctrl+S to save to settings.\"), 0, 0));\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Search input\n\t\tthis.searchInput = new Input();\n\t\tthis.addChild(this.searchInput);\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// List container\n\t\tthis.listContainer = new Container();\n\t\tthis.addChild(this.listContainer);\n\n\t\t// Footer hint\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.footerText = new Text(this.getFooterText(), 0, 0);\n\t\tthis.addChild(this.footerText);\n\n\t\tthis.addChild(new DynamicBorder());\n\t\tthis.updateList();\n\t}\n\n\tprivate buildItems(): ModelItem[] {\n\t\t// Filter out IDs that no longer have a corresponding model (e.g., after logout)\n\t\treturn getSortedIds(this.enabledIds, this.allIds)\n\t\t\t.filter((id) => this.modelsById.has(id))\n\t\t\t.map((id) => ({\n\t\t\t\tfullId: id,\n\t\t\t\tmodel: this.modelsById.get(id)!,\n\t\t\t\tenabled: isEnabled(this.enabledIds, id),\n\t\t\t}));\n\t}\n\n\tprivate getFooterText(): string {\n\t\tconst enabledCount = this.enabledIds?.length ?? this.allIds.length;\n\t\tconst allEnabled = this.enabledIds === null;\n\t\tconst countText = allEnabled ? \"all enabled\" : `${enabledCount}/${this.allIds.length} enabled`;\n\t\tconst parts = [\"Enter toggle\", \"^A all\", \"^X clear\", \"^P provider\", \"Alt+↑↓ reorder\", \"^S save\", countText];\n\t\treturn this.isDirty\n\t\t\t? theme.fg(\"dim\", ` ${parts.join(\" · \")} `) + theme.fg(\"warning\", \"(unsaved)\")\n\t\t\t: theme.fg(\"dim\", ` ${parts.join(\" · \")}`);\n\t}\n\n\tprivate refresh(): void {\n\t\tconst query = this.searchInput.getValue();\n\t\tconst items = this.buildItems();\n\t\tthis.filteredItems = query ? fuzzyFilter(items, query, (i) => `${i.model.id} ${i.model.provider}`) : items;\n\t\tthis.selectedIndex = Math.min(this.selectedIndex, Math.max(0, this.filteredItems.length - 1));\n\t\tthis.updateList();\n\t\tthis.footerText.setText(this.getFooterText());\n\t}\n\n\tprivate updateList(): void {\n\t\tthis.listContainer.clear();\n\n\t\tif (this.filteredItems.length === 0) {\n\t\t\tthis.listContainer.addChild(new Text(theme.fg(\"muted\", \" No matching models\"), 0, 0));\n\t\t\treturn;\n\t\t}\n\n\t\tconst startIndex = Math.max(\n\t\t\t0,\n\t\t\tMath.min(this.selectedIndex - Math.floor(this.maxVisible / 2), this.filteredItems.length - this.maxVisible),\n\t\t);\n\t\tconst endIndex = Math.min(startIndex + this.maxVisible, this.filteredItems.length);\n\t\tconst allEnabled = this.enabledIds === null;\n\n\t\tfor (let i = startIndex; i < endIndex; i++) {\n\t\t\tconst item = this.filteredItems[i]!;\n\t\t\tconst isSelected = i === this.selectedIndex;\n\t\t\tconst prefix = isSelected ? theme.fg(\"accent\", \"→ \") : \" \";\n\t\t\tconst modelText = isSelected ? theme.fg(\"accent\", item.model.id) : item.model.id;\n\t\t\tconst providerBadge = theme.fg(\"muted\", ` [${item.model.provider}]`);\n\t\t\tconst status = allEnabled ? \"\" : item.enabled ? theme.fg(\"success\", \" ✓\") : theme.fg(\"dim\", \" ✗\");\n\t\t\tthis.listContainer.addChild(new Text(`${prefix}${modelText}${providerBadge}${status}`, 0, 0));\n\t\t}\n\n\t\t// Add scroll indicator if needed\n\t\tif (startIndex > 0 || endIndex < this.filteredItems.length) {\n\t\t\tthis.listContainer.addChild(\n\t\t\t\tnew Text(theme.fg(\"muted\", ` (${this.selectedIndex + 1}/${this.filteredItems.length})`), 0, 0),\n\t\t\t);\n\t\t}\n\n\t\tif (this.filteredItems.length > 0) {\n\t\t\tconst selected = this.filteredItems[this.selectedIndex];\n\t\t\tthis.listContainer.addChild(new Spacer(1));\n\t\t\tthis.listContainer.addChild(new Text(theme.fg(\"muted\", ` Model Name: ${selected.model.name}`), 0, 0));\n\t\t}\n\t}\n\n\thandleInput(data: string): void {\n\t\tconst kb = getKeybindings();\n\n\t\t// Navigation\n\t\tif (kb.matches(data, \"tui.select.up\")) {\n\t\t\tif (this.filteredItems.length === 0) return;\n\t\t\tthis.selectedIndex = this.selectedIndex === 0 ? this.filteredItems.length - 1 : this.selectedIndex - 1;\n\t\t\tthis.updateList();\n\t\t\treturn;\n\t\t}\n\t\tif (kb.matches(data, \"tui.select.down\")) {\n\t\t\tif (this.filteredItems.length === 0) return;\n\t\t\tthis.selectedIndex = this.selectedIndex === this.filteredItems.length - 1 ? 0 : this.selectedIndex + 1;\n\t\t\tthis.updateList();\n\t\t\treturn;\n\t\t}\n\n\t\t// Alt+Up/Down - Reorder enabled models\n\t\tif (matchesKey(data, Key.alt(\"up\")) || matchesKey(data, Key.alt(\"down\"))) {\n\t\t\tconst item = this.filteredItems[this.selectedIndex];\n\t\t\tif (item && isEnabled(this.enabledIds, item.fullId)) {\n\t\t\t\tconst delta = matchesKey(data, Key.alt(\"up\")) ? -1 : 1;\n\t\t\t\tconst enabledList = this.enabledIds ?? this.allIds;\n\t\t\t\tconst currentIndex = enabledList.indexOf(item.fullId);\n\t\t\t\tconst newIndex = currentIndex + delta;\n\t\t\t\t// Only move if within bounds\n\t\t\t\tif (newIndex >= 0 && newIndex < enabledList.length) {\n\t\t\t\t\tthis.enabledIds = move(this.enabledIds, this.allIds, item.fullId, delta);\n\t\t\t\t\tthis.isDirty = true;\n\t\t\t\t\tthis.selectedIndex += delta;\n\t\t\t\t\tthis.refresh();\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Toggle on Enter\n\t\tif (matchesKey(data, Key.enter)) {\n\t\t\tconst item = this.filteredItems[this.selectedIndex];\n\t\t\tif (item) {\n\t\t\t\tconst wasAllEnabled = this.enabledIds === null;\n\t\t\t\tthis.enabledIds = toggle(this.enabledIds, item.fullId);\n\t\t\t\tthis.isDirty = true;\n\t\t\t\tif (wasAllEnabled) this.callbacks.onClearAll();\n\t\t\t\tthis.callbacks.onModelToggle(item.fullId, isEnabled(this.enabledIds, item.fullId));\n\t\t\t\tthis.refresh();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Ctrl+A - Enable all (filtered if search active, otherwise all)\n\t\tif (matchesKey(data, Key.ctrl(\"a\"))) {\n\t\t\tconst targetIds = this.searchInput.getValue() ? this.filteredItems.map((i) => i.fullId) : undefined;\n\t\t\tthis.enabledIds = enableAll(this.enabledIds, this.allIds, targetIds);\n\t\t\tthis.isDirty = true;\n\t\t\tthis.callbacks.onEnableAll(targetIds ?? this.allIds);\n\t\t\tthis.refresh();\n\t\t\treturn;\n\t\t}\n\n\t\t// Ctrl+X - Clear all (filtered if search active, otherwise all)\n\t\tif (matchesKey(data, Key.ctrl(\"x\"))) {\n\t\t\tconst targetIds = this.searchInput.getValue() ? this.filteredItems.map((i) => i.fullId) : undefined;\n\t\t\tthis.enabledIds = clearAll(this.enabledIds, this.allIds, targetIds);\n\t\t\tthis.isDirty = true;\n\t\t\tthis.callbacks.onClearAll();\n\t\t\tthis.refresh();\n\t\t\treturn;\n\t\t}\n\n\t\t// Ctrl+P - Toggle provider of current item\n\t\tif (matchesKey(data, Key.ctrl(\"p\"))) {\n\t\t\tconst item = this.filteredItems[this.selectedIndex];\n\t\t\tif (item) {\n\t\t\t\tconst provider = item.model.provider;\n\t\t\t\tconst providerIds = this.allIds.filter((id) => this.modelsById.get(id)!.provider === provider);\n\t\t\t\tconst allEnabled = providerIds.every((id) => isEnabled(this.enabledIds, id));\n\t\t\t\tthis.enabledIds = allEnabled\n\t\t\t\t\t? clearAll(this.enabledIds, this.allIds, providerIds)\n\t\t\t\t\t: enableAll(this.enabledIds, this.allIds, providerIds);\n\t\t\t\tthis.isDirty = true;\n\t\t\t\tthis.callbacks.onToggleProvider(provider, providerIds, !allEnabled);\n\t\t\t\tthis.refresh();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Ctrl+S - Save/persist to settings\n\t\tif (matchesKey(data, Key.ctrl(\"s\"))) {\n\t\t\tthis.callbacks.onPersist(this.enabledIds ?? [...this.allIds]);\n\t\t\tthis.isDirty = false;\n\t\t\tthis.footerText.setText(this.getFooterText());\n\t\t\treturn;\n\t\t}\n\n\t\t// Ctrl+C - clear search or cancel if empty\n\t\tif (matchesKey(data, Key.ctrl(\"c\"))) {\n\t\t\tif (this.searchInput.getValue()) {\n\t\t\t\tthis.searchInput.setValue(\"\");\n\t\t\t\tthis.refresh();\n\t\t\t} else {\n\t\t\t\tthis.callbacks.onCancel();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Escape - cancel\n\t\tif (matchesKey(data, Key.escape)) {\n\t\t\tthis.callbacks.onCancel();\n\t\t\treturn;\n\t\t}\n\n\t\t// Pass everything else to search input\n\t\tthis.searchInput.handleInput(data);\n\t\tthis.refresh();\n\t}\n\n\tgetSearchInput(): Input {\n\t\treturn this.searchInput;\n\t}\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { Container, fuzzyFilter, getEditorKeybindings, Input, Key, matchesKey, Spacer, Text, } from "@mariozechner/pi-tui";
1
+ import { Container, fuzzyFilter, getKeybindings, Input, Key, matchesKey, Spacer, Text, } from "@mariozechner/pi-tui";
2
2
  import { theme } from "../theme/theme.js";
3
3
  import { DynamicBorder } from "./dynamic-border.js";
4
4
  function isEnabled(enabledIds, id) {
@@ -53,14 +53,6 @@ function getSortedIds(enabledIds, allIds) {
53
53
  * Changes are session-only until explicitly persisted with Ctrl+S.
54
54
  */
55
55
  export class ScopedModelsSelectorComponent extends Container {
56
- modelsById = new Map();
57
- allIds = [];
58
- enabledIds = null;
59
- filteredItems = [];
60
- selectedIndex = 0;
61
- searchInput;
62
- // Focusable implementation - propagate to searchInput for IME cursor positioning
63
- _focused = false;
64
56
  get focused() {
65
57
  return this._focused;
66
58
  }
@@ -68,13 +60,17 @@ export class ScopedModelsSelectorComponent extends Container {
68
60
  this._focused = value;
69
61
  this.searchInput.focused = value;
70
62
  }
71
- listContainer;
72
- footerText;
73
- callbacks;
74
- maxVisible = 15;
75
- isDirty = false;
76
63
  constructor(config, callbacks) {
77
64
  super();
65
+ this.modelsById = new Map();
66
+ this.allIds = [];
67
+ this.enabledIds = null;
68
+ this.filteredItems = [];
69
+ this.selectedIndex = 0;
70
+ // Focusable implementation - propagate to searchInput for IME cursor positioning
71
+ this._focused = false;
72
+ this.maxVisible = 15;
73
+ this.isDirty = false;
78
74
  this.callbacks = callbacks;
79
75
  for (const model of config.allModels) {
80
76
  const fullId = `${model.provider}/${model.id}`;
@@ -159,16 +155,16 @@ export class ScopedModelsSelectorComponent extends Container {
159
155
  }
160
156
  }
161
157
  handleInput(data) {
162
- const kb = getEditorKeybindings();
158
+ const kb = getKeybindings();
163
159
  // Navigation
164
- if (kb.matches(data, "selectUp")) {
160
+ if (kb.matches(data, "tui.select.up")) {
165
161
  if (this.filteredItems.length === 0)
166
162
  return;
167
163
  this.selectedIndex = this.selectedIndex === 0 ? this.filteredItems.length - 1 : this.selectedIndex - 1;
168
164
  this.updateList();
169
165
  return;
170
166
  }
171
- if (kb.matches(data, "selectDown")) {
167
+ if (kb.matches(data, "tui.select.down")) {
172
168
  if (this.filteredItems.length === 0)
173
169
  return;
174
170
  this.selectedIndex = this.selectedIndex === this.filteredItems.length - 1 ? 0 : this.selectedIndex + 1;