dexto 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (223) hide show
  1. package/README.md +62 -7
  2. package/dist/agents/agent-template.yml +2 -2
  3. package/dist/agents/coding-agent/coding-agent.yml +22 -16
  4. package/dist/agents/database-agent/database-agent.yml +2 -2
  5. package/dist/agents/default-agent.yml +7 -5
  6. package/dist/agents/github-agent/github-agent.yml +2 -2
  7. package/dist/agents/product-name-researcher/product-name-researcher.yml +2 -2
  8. package/dist/agents/talk2pdf-agent/talk2pdf-agent.yml +2 -2
  9. package/dist/analytics/events.d.ts +13 -6
  10. package/dist/analytics/events.d.ts.map +1 -1
  11. package/dist/analytics/index.d.ts +1 -1
  12. package/dist/analytics/index.d.ts.map +1 -1
  13. package/dist/analytics/index.js +6 -2
  14. package/dist/api/server-hono.d.ts.map +1 -1
  15. package/dist/api/server-hono.js +27 -5
  16. package/dist/cli/cli-subscriber.d.ts +4 -0
  17. package/dist/cli/cli-subscriber.d.ts.map +1 -1
  18. package/dist/cli/cli-subscriber.js +40 -2
  19. package/dist/cli/commands/create-app.d.ts +16 -14
  20. package/dist/cli/commands/create-app.d.ts.map +1 -1
  21. package/dist/cli/commands/create-app.js +626 -102
  22. package/dist/cli/commands/create-image.d.ts +7 -0
  23. package/dist/cli/commands/create-image.d.ts.map +1 -0
  24. package/dist/cli/commands/create-image.js +201 -0
  25. package/dist/cli/commands/helpers/formatters.js +7 -7
  26. package/dist/cli/commands/index.d.ts +2 -1
  27. package/dist/cli/commands/index.d.ts.map +1 -1
  28. package/dist/cli/commands/index.js +2 -1
  29. package/dist/cli/commands/init-app.js +7 -7
  30. package/dist/cli/commands/install.d.ts +0 -3
  31. package/dist/cli/commands/install.d.ts.map +1 -1
  32. package/dist/cli/commands/install.js +10 -35
  33. package/dist/cli/commands/interactive-commands/command-parser.js +7 -7
  34. package/dist/cli/commands/interactive-commands/general-commands.js +1 -1
  35. package/dist/cli/commands/interactive-commands/prompt-commands.js +11 -11
  36. package/dist/cli/commands/interactive-commands/system/system-commands.js +3 -3
  37. package/dist/cli/commands/list-agents.js +2 -2
  38. package/dist/cli/commands/session-commands.js +16 -16
  39. package/dist/cli/commands/setup.d.ts +13 -5
  40. package/dist/cli/commands/setup.d.ts.map +1 -1
  41. package/dist/cli/commands/setup.js +860 -65
  42. package/dist/cli/commands/which.js +1 -1
  43. package/dist/cli/ink-cli/components/ApprovalPrompt.d.ts +2 -0
  44. package/dist/cli/ink-cli/components/ApprovalPrompt.d.ts.map +1 -1
  45. package/dist/cli/ink-cli/components/ApprovalPrompt.js +29 -7
  46. package/dist/cli/ink-cli/components/CustomInput.js +1 -1
  47. package/dist/cli/ink-cli/components/EditableMultiLineInput.js +4 -4
  48. package/dist/cli/ink-cli/components/ElicitationForm.d.ts.map +1 -1
  49. package/dist/cli/ink-cli/components/ElicitationForm.js +6 -6
  50. package/dist/cli/ink-cli/components/ErrorBoundary.d.ts.map +1 -1
  51. package/dist/cli/ink-cli/components/ErrorBoundary.js +1 -1
  52. package/dist/cli/ink-cli/components/Footer.d.ts.map +1 -1
  53. package/dist/cli/ink-cli/components/Footer.js +1 -1
  54. package/dist/cli/ink-cli/components/HistorySearchBar.d.ts.map +1 -1
  55. package/dist/cli/ink-cli/components/HistorySearchBar.js +1 -1
  56. package/dist/cli/ink-cli/components/MultiLineInput.d.ts.map +1 -1
  57. package/dist/cli/ink-cli/components/MultiLineInput.js +3 -3
  58. package/dist/cli/ink-cli/components/ResourceAutocomplete.d.ts.map +1 -1
  59. package/dist/cli/ink-cli/components/ResourceAutocomplete.js +4 -4
  60. package/dist/cli/ink-cli/components/SlashCommandAutocomplete.js +3 -3
  61. package/dist/cli/ink-cli/components/StatusBar.d.ts.map +1 -1
  62. package/dist/cli/ink-cli/components/StatusBar.js +7 -5
  63. package/dist/cli/ink-cli/components/TextBufferInput.d.ts.map +1 -1
  64. package/dist/cli/ink-cli/components/TextBufferInput.js +6 -6
  65. package/dist/cli/ink-cli/components/base/BaseAutocomplete.js +4 -4
  66. package/dist/cli/ink-cli/components/base/BaseSelector.js +2 -2
  67. package/dist/cli/ink-cli/components/chat/Footer.js +1 -1
  68. package/dist/cli/ink-cli/components/chat/Header.d.ts.map +1 -1
  69. package/dist/cli/ink-cli/components/chat/Header.js +2 -4
  70. package/dist/cli/ink-cli/components/chat/MessageItem.d.ts.map +1 -1
  71. package/dist/cli/ink-cli/components/chat/MessageItem.js +5 -5
  72. package/dist/cli/ink-cli/components/chat/MessageList.js +1 -1
  73. package/dist/cli/ink-cli/components/chat/QueuedMessagesDisplay.js +1 -1
  74. package/dist/cli/ink-cli/components/chat/ToolIcon.d.ts +1 -1
  75. package/dist/cli/ink-cli/components/chat/ToolIcon.js +4 -4
  76. package/dist/cli/ink-cli/components/chat/styled-boxes/ConfigBox.js +1 -1
  77. package/dist/cli/ink-cli/components/chat/styled-boxes/HelpBox.js +1 -1
  78. package/dist/cli/ink-cli/components/chat/styled-boxes/LogConfigBox.js +2 -2
  79. package/dist/cli/ink-cli/components/chat/styled-boxes/SessionHistoryBox.js +5 -5
  80. package/dist/cli/ink-cli/components/chat/styled-boxes/SessionListBox.js +2 -2
  81. package/dist/cli/ink-cli/components/chat/styled-boxes/ShortcutsBox.js +1 -1
  82. package/dist/cli/ink-cli/components/chat/styled-boxes/StatsBox.js +1 -1
  83. package/dist/cli/ink-cli/components/chat/styled-boxes/StyledBox.js +2 -2
  84. package/dist/cli/ink-cli/components/modes/AlternateBufferCLI.d.ts.map +1 -1
  85. package/dist/cli/ink-cli/components/modes/AlternateBufferCLI.js +1 -1
  86. package/dist/cli/ink-cli/components/modes/StaticCLI.d.ts.map +1 -1
  87. package/dist/cli/ink-cli/components/modes/StaticCLI.js +1 -1
  88. package/dist/cli/ink-cli/components/overlays/ApiKeyInput.js +1 -1
  89. package/dist/cli/ink-cli/components/overlays/CustomModelWizard.d.ts +10 -2
  90. package/dist/cli/ink-cli/components/overlays/CustomModelWizard.d.ts.map +1 -1
  91. package/dist/cli/ink-cli/components/overlays/CustomModelWizard.js +198 -89
  92. package/dist/cli/ink-cli/components/overlays/LogLevelSelector.d.ts.map +1 -1
  93. package/dist/cli/ink-cli/components/overlays/LogLevelSelector.js +2 -2
  94. package/dist/cli/ink-cli/components/overlays/McpAddChoice.d.ts.map +1 -1
  95. package/dist/cli/ink-cli/components/overlays/McpAddChoice.js +1 -1
  96. package/dist/cli/ink-cli/components/overlays/McpAddSelector.js +1 -1
  97. package/dist/cli/ink-cli/components/overlays/McpCustomTypeSelector.d.ts.map +1 -1
  98. package/dist/cli/ink-cli/components/overlays/McpCustomTypeSelector.js +2 -2
  99. package/dist/cli/ink-cli/components/overlays/McpCustomWizard.js +1 -1
  100. package/dist/cli/ink-cli/components/overlays/McpRemoveSelector.d.ts.map +1 -1
  101. package/dist/cli/ink-cli/components/overlays/McpRemoveSelector.js +1 -1
  102. package/dist/cli/ink-cli/components/overlays/McpSelector.d.ts.map +1 -1
  103. package/dist/cli/ink-cli/components/overlays/McpSelector.js +1 -1
  104. package/dist/cli/ink-cli/components/overlays/McpServerActions.d.ts.map +1 -1
  105. package/dist/cli/ink-cli/components/overlays/McpServerActions.js +2 -2
  106. package/dist/cli/ink-cli/components/overlays/McpServerList.d.ts.map +1 -1
  107. package/dist/cli/ink-cli/components/overlays/McpServerList.js +1 -1
  108. package/dist/cli/ink-cli/components/overlays/ModelSelectorRefactored.d.ts +5 -5
  109. package/dist/cli/ink-cli/components/overlays/ModelSelectorRefactored.d.ts.map +1 -1
  110. package/dist/cli/ink-cli/components/overlays/ModelSelectorRefactored.js +222 -68
  111. package/dist/cli/ink-cli/components/overlays/PromptAddChoice.d.ts.map +1 -1
  112. package/dist/cli/ink-cli/components/overlays/PromptAddChoice.js +2 -2
  113. package/dist/cli/ink-cli/components/overlays/PromptAddWizard.js +1 -1
  114. package/dist/cli/ink-cli/components/overlays/PromptDeleteSelector.d.ts.map +1 -1
  115. package/dist/cli/ink-cli/components/overlays/PromptDeleteSelector.js +2 -2
  116. package/dist/cli/ink-cli/components/overlays/PromptList.d.ts.map +1 -1
  117. package/dist/cli/ink-cli/components/overlays/PromptList.js +2 -2
  118. package/dist/cli/ink-cli/components/overlays/SearchOverlay.d.ts.map +1 -1
  119. package/dist/cli/ink-cli/components/overlays/SearchOverlay.js +4 -4
  120. package/dist/cli/ink-cli/components/overlays/SessionSubcommandSelector.d.ts.map +1 -1
  121. package/dist/cli/ink-cli/components/overlays/SessionSubcommandSelector.js +1 -1
  122. package/dist/cli/ink-cli/components/overlays/StreamSelector.d.ts.map +1 -1
  123. package/dist/cli/ink-cli/components/overlays/StreamSelector.js +1 -1
  124. package/dist/cli/ink-cli/components/overlays/ToolBrowser.js +12 -12
  125. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/LocalModelWizard.d.ts +25 -0
  126. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/LocalModelWizard.d.ts.map +1 -0
  127. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/LocalModelWizard.js +609 -0
  128. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/index.d.ts +15 -0
  129. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/index.d.ts.map +1 -0
  130. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/index.js +14 -0
  131. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/provider-config.d.ts +33 -0
  132. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/provider-config.d.ts.map +1 -0
  133. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/provider-config.js +419 -0
  134. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/ApiKeyStep.d.ts +25 -0
  135. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/ApiKeyStep.d.ts.map +1 -0
  136. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/ApiKeyStep.js +29 -0
  137. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/ProviderSelector.d.ts +17 -0
  138. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/ProviderSelector.d.ts.map +1 -0
  139. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/ProviderSelector.js +11 -0
  140. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/SetupInfoBanner.d.ts +20 -0
  141. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/SetupInfoBanner.d.ts.map +1 -0
  142. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/SetupInfoBanner.js +10 -0
  143. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/WizardStepInput.d.ts +30 -0
  144. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/WizardStepInput.d.ts.map +1 -0
  145. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/WizardStepInput.js +13 -0
  146. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/index.d.ts +8 -0
  147. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/index.d.ts.map +1 -0
  148. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/index.js +7 -0
  149. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/types.d.ts +79 -0
  150. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/types.d.ts.map +1 -0
  151. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/types.js +38 -0
  152. package/dist/cli/ink-cli/components/renderers/DiffRenderer.js +2 -2
  153. package/dist/cli/ink-cli/components/renderers/FilePreviewRenderer.js +1 -1
  154. package/dist/cli/ink-cli/components/renderers/FileRenderer.js +4 -4
  155. package/dist/cli/ink-cli/components/renderers/GenericRenderer.js +2 -2
  156. package/dist/cli/ink-cli/components/renderers/SearchRenderer.js +1 -1
  157. package/dist/cli/ink-cli/components/renderers/ShellRenderer.js +3 -3
  158. package/dist/cli/ink-cli/components/renderers/diff-shared.js +1 -1
  159. package/dist/cli/ink-cli/components/shared/MarkdownText.d.ts.map +1 -1
  160. package/dist/cli/ink-cli/components/shared/MarkdownText.js +8 -6
  161. package/dist/cli/ink-cli/containers/InputContainer.d.ts.map +1 -1
  162. package/dist/cli/ink-cli/containers/InputContainer.js +23 -1
  163. package/dist/cli/ink-cli/containers/OverlayContainer.d.ts.map +1 -1
  164. package/dist/cli/ink-cli/containers/OverlayContainer.js +80 -24
  165. package/dist/cli/ink-cli/hooks/useAgentEvents.d.ts +1 -1
  166. package/dist/cli/ink-cli/hooks/useAgentEvents.d.ts.map +1 -1
  167. package/dist/cli/ink-cli/hooks/useAgentEvents.js +5 -1
  168. package/dist/cli/ink-cli/hooks/useCLIState.d.ts +1 -1
  169. package/dist/cli/ink-cli/hooks/useCLIState.d.ts.map +1 -1
  170. package/dist/cli/ink-cli/hooks/useCLIState.js +4 -2
  171. package/dist/cli/ink-cli/services/processStream.d.ts.map +1 -1
  172. package/dist/cli/ink-cli/services/processStream.js +77 -9
  173. package/dist/cli/ink-cli/state/types.d.ts +3 -2
  174. package/dist/cli/ink-cli/state/types.d.ts.map +1 -1
  175. package/dist/cli/ink-cli/utils/messageFormatting.d.ts +5 -0
  176. package/dist/cli/ink-cli/utils/messageFormatting.d.ts.map +1 -1
  177. package/dist/cli/ink-cli/utils/messageFormatting.js +59 -1
  178. package/dist/cli/ink-cli/utils/toolUtils.d.ts.map +1 -1
  179. package/dist/cli/ink-cli/utils/toolUtils.js +2 -0
  180. package/dist/cli/utils/api-key-setup.d.ts +54 -4
  181. package/dist/cli/utils/api-key-setup.d.ts.map +1 -1
  182. package/dist/cli/utils/api-key-setup.js +433 -107
  183. package/dist/cli/utils/api-key-verification.d.ts +17 -0
  184. package/dist/cli/utils/api-key-verification.d.ts.map +1 -0
  185. package/dist/cli/utils/api-key-verification.js +211 -0
  186. package/dist/cli/utils/config-validation.d.ts +22 -2
  187. package/dist/cli/utils/config-validation.d.ts.map +1 -1
  188. package/dist/cli/utils/config-validation.js +354 -25
  189. package/dist/cli/utils/local-model-setup.d.ts +46 -0
  190. package/dist/cli/utils/local-model-setup.d.ts.map +1 -0
  191. package/dist/cli/utils/local-model-setup.js +662 -0
  192. package/dist/cli/utils/options.js +1 -1
  193. package/dist/cli/utils/prompt-helpers.d.ts +47 -0
  194. package/dist/cli/utils/prompt-helpers.d.ts.map +1 -0
  195. package/dist/cli/utils/prompt-helpers.js +66 -0
  196. package/dist/cli/utils/provider-setup.d.ts +66 -8
  197. package/dist/cli/utils/provider-setup.d.ts.map +1 -1
  198. package/dist/cli/utils/provider-setup.js +324 -84
  199. package/dist/cli/utils/scaffolding-utils.d.ts +76 -0
  200. package/dist/cli/utils/scaffolding-utils.d.ts.map +1 -0
  201. package/dist/cli/utils/scaffolding-utils.js +246 -0
  202. package/dist/cli/utils/setup-utils.d.ts +16 -0
  203. package/dist/cli/utils/setup-utils.d.ts.map +1 -1
  204. package/dist/cli/utils/setup-utils.js +72 -21
  205. package/dist/cli/utils/template-engine.d.ts +65 -0
  206. package/dist/cli/utils/template-engine.d.ts.map +1 -0
  207. package/dist/cli/utils/template-engine.js +1089 -0
  208. package/dist/config/cli-overrides.d.ts +44 -1
  209. package/dist/config/cli-overrides.d.ts.map +1 -1
  210. package/dist/config/cli-overrides.js +102 -0
  211. package/dist/index.js +315 -53
  212. package/dist/webui/assets/index-8j-KMkX1.js +2054 -0
  213. package/dist/webui/assets/index-c_AX24V4.css +1 -0
  214. package/dist/webui/index.html +3 -9
  215. package/dist/webui/logos/aws-color.svg +1 -0
  216. package/dist/webui/logos/dexto/dexto_logo.svg +1 -1
  217. package/dist/webui/logos/dexto/dexto_logo_light.svg +6 -6
  218. package/dist/webui/logos/glama.svg +7 -0
  219. package/dist/webui/logos/litellm.svg +7 -0
  220. package/dist/webui/logos/openrouter.svg +1 -0
  221. package/package.json +8 -7
  222. package/dist/webui/assets/index-BkwPkZpd.css +0 -1
  223. package/dist/webui/assets/index-D9u1XfyH.js +0 -2025
@@ -0,0 +1,609 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ /**
3
+ * LocalModelWizard Component
4
+ * Specialized wizard for adding local GGUF models with:
5
+ * - Registry model selection with download support
6
+ * - Custom GGUF file path option
7
+ * - Download progress display
8
+ */
9
+ import { useState, useEffect, forwardRef, useImperativeHandle, useCallback } from 'react';
10
+ import { Box, Text } from 'ink';
11
+ import * as fs from 'fs';
12
+ import * as path from 'path';
13
+ import { spawn } from 'child_process';
14
+ import { saveCustomModel, getAllInstalledModels, addInstalledModel, removeInstalledModel, getModelsDirectory, formatSize, getDextoGlobalPath, } from '@dexto/agent-management';
15
+ import { promises as fsPromises } from 'fs';
16
+ import { getAllLocalModels, getLocalModelById, getRecommendedLocalModels, downloadModel, isNodeLlamaCppInstalled, } from '@dexto/core';
17
+ import { SetupInfoBanner } from './shared/index.js';
18
+ const MAX_VISIBLE_ITEMS = 8;
19
+ /**
20
+ * Specialized wizard for local GGUF model setup.
21
+ * Similar UX to CLI setup flow but in Ink.
22
+ */
23
+ const LocalModelWizard = forwardRef(function LocalModelWizard({ isVisible, onComplete, onClose }, ref) {
24
+ const [step, setStep] = useState('select-model');
25
+ const [models, setModels] = useState([]);
26
+ const [installedIds, setInstalledIds] = useState(new Set());
27
+ const [selectedIndex, setSelectedIndex] = useState(0);
28
+ const [scrollOffset, setScrollOffset] = useState(0);
29
+ const [showAllModels, setShowAllModels] = useState(false);
30
+ const [isLoading, setIsLoading] = useState(true);
31
+ const [error, setError] = useState(null);
32
+ const [nodeLlamaInstalled, setNodeLlamaInstalled] = useState(true);
33
+ const [nodeLlamaChecked, setNodeLlamaChecked] = useState(false); // Track if we've checked installation
34
+ const [isInstallingNodeLlama, setIsInstallingNodeLlama] = useState(false);
35
+ const [installConfirmIndex, setInstallConfirmIndex] = useState(0); // 0 = Yes, 1 = No
36
+ const [installSpinnerFrame, setInstallSpinnerFrame] = useState(0);
37
+ const [refreshTrigger, setRefreshTrigger] = useState(0); // Increment to trigger data reload
38
+ // Custom path input state
39
+ const [customPath, setCustomPath] = useState('');
40
+ // Display name input state
41
+ const [displayName, setDisplayName] = useState('');
42
+ const [selectedModelId, setSelectedModelId] = useState(null);
43
+ const [selectedModelPath, setSelectedModelPath] = useState(null);
44
+ // Download state
45
+ const [downloadProgress, setDownloadProgress] = useState(null);
46
+ const [downloadError, setDownloadError] = useState(null);
47
+ // Installed model options state
48
+ const [installedOptionIndex, setInstalledOptionIndex] = useState(0);
49
+ const [selectedInstalledModel, setSelectedInstalledModel] = useState(null);
50
+ const [isDeleting, setIsDeleting] = useState(false);
51
+ // Reset state when becoming visible
52
+ useEffect(() => {
53
+ if (!isVisible)
54
+ return;
55
+ setStep('select-model');
56
+ setSelectedIndex(0);
57
+ setScrollOffset(0);
58
+ setShowAllModels(false);
59
+ setCustomPath('');
60
+ setDisplayName('');
61
+ setSelectedModelId(null);
62
+ setSelectedModelPath(null);
63
+ setDownloadProgress(null);
64
+ setDownloadError(null);
65
+ setInstalledOptionIndex(0);
66
+ setSelectedInstalledModel(null);
67
+ setIsDeleting(false);
68
+ setError(null);
69
+ setIsInstallingNodeLlama(false);
70
+ setInstallConfirmIndex(0);
71
+ setInstallSpinnerFrame(0);
72
+ setNodeLlamaChecked(false);
73
+ }, [isVisible]);
74
+ // Spinner animation for installation
75
+ useEffect(() => {
76
+ if (!isInstallingNodeLlama)
77
+ return;
78
+ const spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
79
+ const interval = setInterval(() => {
80
+ setInstallSpinnerFrame((prev) => (prev + 1) % spinnerFrames.length);
81
+ }, 80);
82
+ return () => clearInterval(interval);
83
+ }, [isInstallingNodeLlama]);
84
+ // Load models when visible or when showAllModels changes
85
+ useEffect(() => {
86
+ if (!isVisible)
87
+ return;
88
+ let cancelled = false;
89
+ const loadData = async () => {
90
+ setIsLoading(true);
91
+ try {
92
+ // Check if node-llama-cpp is installed
93
+ // Skip if we've already checked AND it's installed (prevents re-check after install)
94
+ if (!nodeLlamaChecked || !nodeLlamaInstalled) {
95
+ const installed = await isNodeLlamaCppInstalled();
96
+ if (!cancelled) {
97
+ setNodeLlamaInstalled(installed);
98
+ setNodeLlamaChecked(true);
99
+ if (!installed) {
100
+ setStep('install-node-llama');
101
+ setIsLoading(false);
102
+ return;
103
+ }
104
+ }
105
+ }
106
+ // Get installed models
107
+ const installedModels = await getAllInstalledModels();
108
+ const installedSet = new Set(installedModels.map((m) => m.id));
109
+ if (!cancelled) {
110
+ setInstalledIds(installedSet);
111
+ }
112
+ // Get registry models based on showAllModels flag
113
+ const registryModels = showAllModels
114
+ ? getAllLocalModels()
115
+ : getRecommendedLocalModels();
116
+ const options = registryModels.map((m) => ({
117
+ id: m.id,
118
+ name: m.name,
119
+ description: m.description,
120
+ sizeBytes: m.sizeBytes,
121
+ isInstalled: installedSet.has(m.id),
122
+ minVRAM: m.minVRAM,
123
+ }));
124
+ if (!cancelled) {
125
+ setModels(options);
126
+ setIsLoading(false);
127
+ }
128
+ }
129
+ catch (err) {
130
+ if (!cancelled) {
131
+ setError(err instanceof Error ? err.message : 'Failed to load models');
132
+ setIsLoading(false);
133
+ }
134
+ }
135
+ };
136
+ void loadData();
137
+ return () => {
138
+ cancelled = true;
139
+ };
140
+ }, [isVisible, showAllModels, refreshTrigger, nodeLlamaInstalled, nodeLlamaChecked]);
141
+ // Calculate scroll offset
142
+ useEffect(() => {
143
+ const itemCount = models.length + 3; // +3 for special options
144
+ if (selectedIndex < scrollOffset) {
145
+ setScrollOffset(selectedIndex);
146
+ }
147
+ else if (selectedIndex >= scrollOffset + MAX_VISIBLE_ITEMS) {
148
+ setScrollOffset(Math.min(selectedIndex - MAX_VISIBLE_ITEMS + 1, itemCount - MAX_VISIBLE_ITEMS));
149
+ }
150
+ }, [selectedIndex, models.length, scrollOffset]);
151
+ // Handle model selection
152
+ const handleSelectModel = useCallback(async (modelId) => {
153
+ // Check if already installed - show options (Use / Delete)
154
+ if (installedIds.has(modelId)) {
155
+ const modelInfo = getLocalModelById(modelId);
156
+ const installedModels = await getAllInstalledModels();
157
+ const installedModel = installedModels.find((m) => m.id === modelId);
158
+ setSelectedInstalledModel({
159
+ id: modelId,
160
+ filePath: installedModel?.filePath || '',
161
+ displayName: modelInfo?.name || modelId,
162
+ });
163
+ setInstalledOptionIndex(0);
164
+ setStep('installed-options');
165
+ return;
166
+ }
167
+ // Need to download - start download
168
+ setSelectedModelId(modelId);
169
+ setStep('downloading');
170
+ setDownloadProgress(null);
171
+ setDownloadError(null);
172
+ try {
173
+ const result = await downloadModel(modelId, {
174
+ targetDir: getModelsDirectory(),
175
+ events: {
176
+ onProgress: (progress) => {
177
+ setDownloadProgress(progress);
178
+ },
179
+ onComplete: () => {
180
+ // Download complete
181
+ },
182
+ onError: (_id, err) => {
183
+ setDownloadError(err.message);
184
+ },
185
+ },
186
+ });
187
+ // Register the installed model
188
+ const modelInfo = getLocalModelById(modelId);
189
+ const installedModel = {
190
+ id: modelId,
191
+ filePath: result.filePath,
192
+ sizeBytes: result.sizeBytes,
193
+ downloadedAt: new Date().toISOString(),
194
+ source: 'huggingface',
195
+ filename: modelInfo?.filename || path.basename(result.filePath),
196
+ };
197
+ if (result.sha256) {
198
+ installedModel.sha256 = result.sha256;
199
+ }
200
+ await addInstalledModel(installedModel);
201
+ // Registry models are tracked in state.json, not custom-models.json
202
+ // Just complete - the model will appear in the selector via getAllInstalledModels()
203
+ onComplete({
204
+ name: modelId,
205
+ provider: 'local',
206
+ displayName: modelInfo?.name || modelId,
207
+ });
208
+ }
209
+ catch (err) {
210
+ setDownloadError(err instanceof Error ? err.message : 'Download failed');
211
+ }
212
+ }, [installedIds, onComplete]);
213
+ // Handle custom path submission
214
+ const handleCustomPathSubmit = useCallback(async () => {
215
+ const trimmedPath = customPath.trim();
216
+ // Validate path
217
+ if (!trimmedPath) {
218
+ setError('File path is required');
219
+ return;
220
+ }
221
+ if (!trimmedPath.toLowerCase().endsWith('.gguf')) {
222
+ setError('File must have .gguf extension');
223
+ return;
224
+ }
225
+ // Expand ~ to home directory
226
+ const expandedPath = trimmedPath.startsWith('~')
227
+ ? trimmedPath.replace('~', process.env.HOME || '')
228
+ : trimmedPath;
229
+ if (!path.isAbsolute(expandedPath)) {
230
+ setError('Please enter an absolute path (starting with / or ~)');
231
+ return;
232
+ }
233
+ if (!fs.existsSync(expandedPath)) {
234
+ setError(`File not found: ${trimmedPath}`);
235
+ return;
236
+ }
237
+ // Path is valid - move to display name step
238
+ setSelectedModelPath(expandedPath);
239
+ const filename = path.basename(expandedPath, '.gguf');
240
+ setDisplayName(filename);
241
+ setStep('display-name');
242
+ setError(null);
243
+ }, [customPath]);
244
+ // Handle display name submission
245
+ const handleDisplayNameSubmit = useCallback(async () => {
246
+ if (!selectedModelPath)
247
+ return;
248
+ const trimmedName = displayName.trim();
249
+ const filename = path.basename(selectedModelPath, '.gguf');
250
+ // Generate model ID from filename
251
+ const modelId = filename
252
+ .toLowerCase()
253
+ .replace(/\s+/g, '-')
254
+ .replace(/[^a-z0-9-]/g, '')
255
+ .substring(0, 50);
256
+ const model = {
257
+ name: modelId,
258
+ provider: 'local',
259
+ filePath: selectedModelPath,
260
+ displayName: trimmedName || filename,
261
+ };
262
+ try {
263
+ await saveCustomModel(model);
264
+ onComplete(model);
265
+ }
266
+ catch (err) {
267
+ setError(err instanceof Error ? err.message : 'Failed to save model');
268
+ }
269
+ }, [selectedModelPath, displayName, onComplete]);
270
+ // Handle installed model option selection
271
+ const handleInstalledOption = useCallback(async () => {
272
+ if (!selectedInstalledModel)
273
+ return;
274
+ if (installedOptionIndex === 0) {
275
+ // "Use this model" option
276
+ onComplete({
277
+ name: selectedInstalledModel.id,
278
+ provider: 'local',
279
+ displayName: selectedInstalledModel.displayName,
280
+ });
281
+ }
282
+ else {
283
+ // "Delete model" option
284
+ setIsDeleting(true);
285
+ setError(null);
286
+ try {
287
+ // Delete the GGUF file from disk
288
+ if (selectedInstalledModel.filePath) {
289
+ try {
290
+ await fsPromises.unlink(selectedInstalledModel.filePath);
291
+ }
292
+ catch (err) {
293
+ // File might already be deleted - continue
294
+ const nodeErr = err;
295
+ if (nodeErr.code !== 'ENOENT') {
296
+ throw err;
297
+ }
298
+ }
299
+ }
300
+ // Remove from state.json
301
+ await removeInstalledModel(selectedInstalledModel.id);
302
+ // Refresh the model list
303
+ setInstalledIds((prev) => {
304
+ const next = new Set(prev);
305
+ next.delete(selectedInstalledModel.id);
306
+ return next;
307
+ });
308
+ // Go back to model selection
309
+ setStep('select-model');
310
+ setSelectedInstalledModel(null);
311
+ }
312
+ catch (err) {
313
+ setError(err instanceof Error ? err.message : 'Failed to delete model');
314
+ }
315
+ finally {
316
+ setIsDeleting(false);
317
+ }
318
+ }
319
+ }, [selectedInstalledModel, installedOptionIndex, onComplete]);
320
+ // Install node-llama-cpp to global deps directory
321
+ const installNodeLlamaCpp = useCallback(async () => {
322
+ const depsDir = getDextoGlobalPath('deps');
323
+ // Ensure deps directory exists
324
+ if (!fs.existsSync(depsDir)) {
325
+ fs.mkdirSync(depsDir, { recursive: true });
326
+ }
327
+ // Initialize package.json if it doesn't exist
328
+ const packageJsonPath = path.join(depsDir, 'package.json');
329
+ if (!fs.existsSync(packageJsonPath)) {
330
+ fs.writeFileSync(packageJsonPath, JSON.stringify({
331
+ name: 'dexto-deps',
332
+ version: '1.0.0',
333
+ private: true,
334
+ description: 'Native dependencies for Dexto',
335
+ }, null, 2));
336
+ }
337
+ return new Promise((resolve) => {
338
+ const child = spawn('npm', ['install', 'node-llama-cpp'], {
339
+ stdio: ['ignore', 'ignore', 'pipe'], // stdin ignored, stdout ignored (not needed), stderr piped for errors
340
+ cwd: depsDir,
341
+ });
342
+ let stderr = '';
343
+ child.stderr?.on('data', (data) => {
344
+ stderr += data.toString();
345
+ });
346
+ child.on('close', (code) => {
347
+ if (code === 0) {
348
+ resolve(true);
349
+ }
350
+ else {
351
+ setError(`Installation failed: ${stderr.slice(0, 200)}`);
352
+ resolve(false);
353
+ }
354
+ });
355
+ child.on('error', (err) => {
356
+ setError(`Installation failed: ${err.message}`);
357
+ resolve(false);
358
+ });
359
+ });
360
+ }, []);
361
+ // Handle install confirmation
362
+ const handleInstallConfirm = useCallback(async () => {
363
+ if (installConfirmIndex === 1) {
364
+ // User chose "No"
365
+ onClose();
366
+ return;
367
+ }
368
+ // User chose "Yes" - start installation
369
+ setIsInstallingNodeLlama(true);
370
+ setError(null);
371
+ const success = await installNodeLlamaCpp();
372
+ setIsInstallingNodeLlama(false);
373
+ if (success) {
374
+ // Trust npm's exit code - set states and go directly to model selection
375
+ setNodeLlamaInstalled(true);
376
+ setNodeLlamaChecked(true);
377
+ setStep('select-model');
378
+ setIsLoading(true);
379
+ // Trigger reload of models
380
+ setRefreshTrigger((prev) => prev + 1);
381
+ }
382
+ else {
383
+ // Error should already be set by installNodeLlamaCpp, but ensure we show something
384
+ setError((prev) => prev || 'Installation failed. Check your internet connection and try again.');
385
+ }
386
+ }, [installConfirmIndex, installNodeLlamaCpp, onClose]);
387
+ // Handle input
388
+ useImperativeHandle(ref, () => ({
389
+ handleInput: (input, key) => {
390
+ if (!isVisible)
391
+ return false;
392
+ // Escape to go back/close
393
+ if (key.escape) {
394
+ if (step === 'custom-path' ||
395
+ step === 'display-name' ||
396
+ step === 'installed-options') {
397
+ setStep('select-model');
398
+ setError(null);
399
+ setSelectedInstalledModel(null);
400
+ return true;
401
+ }
402
+ if (step === 'downloading' && downloadError) {
403
+ setStep('select-model');
404
+ setDownloadError(null);
405
+ return true;
406
+ }
407
+ onClose();
408
+ return true;
409
+ }
410
+ // Handle based on current step
411
+ if (step === 'install-node-llama') {
412
+ if (isInstallingNodeLlama)
413
+ return true; // Don't allow input while installing
414
+ if (key.upArrow || key.downArrow) {
415
+ setInstallConfirmIndex((prev) => (prev === 0 ? 1 : 0));
416
+ return true;
417
+ }
418
+ if (key.return) {
419
+ void handleInstallConfirm();
420
+ return true;
421
+ }
422
+ return true;
423
+ }
424
+ if (step === 'select-model') {
425
+ const itemCount = models.length + 3; // +3 for special options
426
+ if (key.upArrow) {
427
+ setSelectedIndex((prev) => (prev > 0 ? prev - 1 : itemCount - 1));
428
+ return true;
429
+ }
430
+ if (key.downArrow) {
431
+ setSelectedIndex((prev) => (prev < itemCount - 1 ? prev + 1 : 0));
432
+ return true;
433
+ }
434
+ if (key.return) {
435
+ // Special options are at the end
436
+ const showAllIndex = models.length;
437
+ const customPathIndex = models.length + 1;
438
+ const backIndex = models.length + 2;
439
+ if (selectedIndex === backIndex) {
440
+ onClose();
441
+ return true;
442
+ }
443
+ if (selectedIndex === showAllIndex) {
444
+ setShowAllModels(!showAllModels);
445
+ setSelectedIndex(0);
446
+ setScrollOffset(0);
447
+ return true;
448
+ }
449
+ if (selectedIndex === customPathIndex) {
450
+ setStep('custom-path');
451
+ setCustomPath('');
452
+ setError(null);
453
+ return true;
454
+ }
455
+ // Model selected
456
+ const model = models[selectedIndex];
457
+ if (model) {
458
+ void handleSelectModel(model.id);
459
+ }
460
+ return true;
461
+ }
462
+ return true; // Consume all input in select mode
463
+ }
464
+ if (step === 'custom-path') {
465
+ if (key.return) {
466
+ void handleCustomPathSubmit();
467
+ return true;
468
+ }
469
+ if (key.backspace || key.delete) {
470
+ setCustomPath((prev) => prev.slice(0, -1));
471
+ setError(null);
472
+ return true;
473
+ }
474
+ if (input && !key.ctrl && !key.meta) {
475
+ setCustomPath((prev) => prev + input);
476
+ setError(null);
477
+ return true;
478
+ }
479
+ return true;
480
+ }
481
+ if (step === 'display-name') {
482
+ if (key.return) {
483
+ void handleDisplayNameSubmit();
484
+ return true;
485
+ }
486
+ if (key.backspace || key.delete) {
487
+ setDisplayName((prev) => prev.slice(0, -1));
488
+ return true;
489
+ }
490
+ if (input && !key.ctrl && !key.meta) {
491
+ setDisplayName((prev) => prev + input);
492
+ return true;
493
+ }
494
+ return true;
495
+ }
496
+ if (step === 'installed-options') {
497
+ if (isDeleting)
498
+ return true; // Don't allow input while deleting
499
+ if (key.upArrow || key.downArrow) {
500
+ setInstalledOptionIndex((prev) => (prev === 0 ? 1 : 0));
501
+ return true;
502
+ }
503
+ if (key.return) {
504
+ void handleInstalledOption();
505
+ return true;
506
+ }
507
+ return true;
508
+ }
509
+ if (step === 'downloading') {
510
+ // Only allow escape if there's an error
511
+ return true;
512
+ }
513
+ return false;
514
+ },
515
+ }), [
516
+ isVisible,
517
+ step,
518
+ models,
519
+ selectedIndex,
520
+ showAllModels,
521
+ customPath,
522
+ displayName,
523
+ downloadError,
524
+ isDeleting,
525
+ installedOptionIndex,
526
+ handleSelectModel,
527
+ handleCustomPathSubmit,
528
+ handleDisplayNameSubmit,
529
+ handleInstalledOption,
530
+ handleInstallConfirm,
531
+ isInstallingNodeLlama,
532
+ installConfirmIndex,
533
+ onClose,
534
+ ]);
535
+ if (!isVisible)
536
+ return null;
537
+ // Node-llama-cpp install prompt
538
+ if (step === 'install-node-llama') {
539
+ const options = [
540
+ { label: 'Yes', description: 'Install now (may take 1-2 minutes)' },
541
+ { label: 'No', description: 'Go back' },
542
+ ];
543
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, marginTop: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "yellow", children: "Dependency Required" }) }), _jsx(Text, { children: "Local model execution requires node-llama-cpp." }), _jsx(Text, { color: "gray", children: "This will compile native bindings for your system." }), isInstallingNodeLlama ? (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Box, { children: _jsxs(Text, { color: "cyan", children: [['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'][installSpinnerFrame], ' ', "Installing node-llama-cpp (compiling native bindings)..."] }) }), _jsx(Text, { color: "gray", children: "This may take 1-2 minutes." })] })) : (_jsxs(_Fragment, { children: [_jsx(Box, { marginTop: 1, marginBottom: 1, children: _jsx(Text, { children: "Install node-llama-cpp now?" }) }), options.map((option, idx) => (_jsxs(Box, { children: [_jsxs(Text, { color: idx === installConfirmIndex ? 'cyan' : 'white', children: [idx === installConfirmIndex ? '❯ ' : ' ', option.label] }), idx === installConfirmIndex && (_jsxs(Text, { color: "gray", children: [" - ", option.description] }))] }, option.label)))] })), error && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "red", children: error }) })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", children: isInstallingNodeLlama
544
+ ? 'Please wait...'
545
+ : '↑↓ navigate • Enter select • Esc cancel' }) })] }));
546
+ }
547
+ // Loading state
548
+ if (isLoading) {
549
+ return (_jsx(Box, { flexDirection: "column", borderStyle: "round", borderColor: "green", paddingX: 1, marginTop: 1, children: _jsx(Text, { color: "gray", children: "Loading models..." }) }));
550
+ }
551
+ // Download progress
552
+ if (step === 'downloading') {
553
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "green", paddingX: 1, marginTop: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "green", children: "Downloading Model" }) }), downloadError ? (_jsxs(_Fragment, { children: [_jsxs(Text, { color: "red", children: ["Download failed: ", downloadError] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", children: "Press Esc to go back" }) })] })) : downloadProgress ? (_jsxs(_Fragment, { children: [_jsxs(Text, { children: [selectedModelId, ": ", downloadProgress.percentage.toFixed(1), "%"] }), _jsxs(Text, { color: "gray", children: [formatSize(downloadProgress.bytesDownloaded), " /", ' ', formatSize(downloadProgress.totalBytes), downloadProgress.speed
554
+ ? ` • ${formatSize(downloadProgress.speed)}/s`
555
+ : '', downloadProgress.eta
556
+ ? ` • ETA: ${Math.round(downloadProgress.eta)}s`
557
+ : ''] }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: "green", children: '█'.repeat(Math.min(20, Math.floor(downloadProgress.percentage / 5))) }), _jsx(Text, { color: "gray", children: '░'.repeat(Math.max(0, 20 - Math.floor(downloadProgress.percentage / 5))) })] })] })) : (_jsx(Text, { color: "gray", children: "Starting download..." }))] }));
558
+ }
559
+ // Installed model options (Use / Delete)
560
+ if (step === 'installed-options' && selectedInstalledModel) {
561
+ const options = [
562
+ { label: 'Use this model', description: 'Select this model for chat' },
563
+ { label: 'Delete model', description: 'Remove from disk and uninstall' },
564
+ ];
565
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "green", paddingX: 1, marginTop: 1, children: [_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { bold: true, color: "green", children: selectedInstalledModel.displayName }), _jsx(Text, { color: "gray", children: " (installed)" })] }), isDeleting ? (_jsx(Text, { color: "yellow", children: "Deleting model..." })) : (_jsx(_Fragment, { children: options.map((option, idx) => (_jsxs(Box, { children: [_jsxs(Text, { color: idx === installedOptionIndex ? 'cyan' : 'white', children: [idx === installedOptionIndex ? '❯ ' : ' ', option.label] }), idx === installedOptionIndex && (_jsxs(Text, { color: "gray", children: [" - ", option.description] }))] }, option.label))) })), error && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "red", children: error }) })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", children: isDeleting ? 'Please wait...' : 'Enter to select • Esc to go back' }) })] }));
566
+ }
567
+ // Custom path input
568
+ if (step === 'custom-path') {
569
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "green", paddingX: 1, marginTop: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "green", children: "Custom GGUF File" }) }), _jsx(Text, { children: "Enter path to GGUF file:" }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: "gray", children: "> " }), _jsx(Text, { children: customPath }), _jsx(Text, { color: "cyan", children: "\u258C" })] }), _jsx(Text, { color: "gray", children: "e.g., /path/to/model.gguf or ~/models/llama.gguf" }), error && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "red", children: error }) })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", children: "Enter to continue \u2022 Esc to go back" }) })] }));
570
+ }
571
+ // Display name input
572
+ if (step === 'display-name') {
573
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "green", paddingX: 1, marginTop: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "green", children: "Display Name" }) }), _jsx(Text, { children: "Display name (optional):" }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: "gray", children: "> " }), _jsx(Text, { children: displayName }), _jsx(Text, { color: "cyan", children: "\u258C" })] }), error && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "red", children: error }) })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", children: "Enter to save \u2022 Esc to go back" }) })] }));
574
+ }
575
+ // Model selection
576
+ const allItems = [
577
+ ...models,
578
+ { type: 'show-all' },
579
+ { type: 'custom-path' },
580
+ { type: 'back' },
581
+ ];
582
+ const visibleStart = scrollOffset;
583
+ const visibleEnd = Math.min(scrollOffset + MAX_VISIBLE_ITEMS, allItems.length);
584
+ const visibleItems = allItems.slice(visibleStart, visibleEnd);
585
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "green", paddingX: 1, marginTop: 1, children: [_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { bold: true, color: "green", children: "Local Model" }), _jsxs(Text, { color: "gray", children: [' ', "(", selectedIndex + 1, "/", allItems.length, ")"] })] }), _jsx(SetupInfoBanner, { title: "Local Models", description: "Select a model to download, or use a custom GGUF file. Models run completely on your machine - free, private, and offline.", docsUrl: "https://docs.dexto.ai/docs/guides/supported-llm-providers#local-models" }), visibleItems.map((item, visibleIndex) => {
586
+ const actualIndex = scrollOffset + visibleIndex;
587
+ const isSelected = actualIndex === selectedIndex;
588
+ if ('type' in item) {
589
+ if (item.type === 'show-all') {
590
+ return (_jsxs(Box, { paddingY: 0, children: [_jsxs(Text, { color: isSelected ? 'cyan' : 'blue', bold: isSelected, children: [isSelected ? '› ' : ' ', showAllModels
591
+ ? '↩ Show recommended'
592
+ : '... Show all models'] }), _jsxs(Text, { color: "gray", children: [' ', "(", getAllLocalModels().length, " available)"] })] }, "show-all"));
593
+ }
594
+ if (item.type === 'custom-path') {
595
+ return (_jsx(Box, { paddingY: 0, children: _jsxs(Text, { color: isSelected ? 'cyan' : 'blue', bold: isSelected, children: [isSelected ? '› ' : ' ', "... Use custom GGUF file"] }) }, "custom-path"));
596
+ }
597
+ if (item.type === 'back') {
598
+ return (_jsx(Box, { paddingY: 0, children: _jsxs(Text, { color: isSelected ? 'cyan' : 'gray', bold: isSelected, children: [isSelected ? '› ' : ' ', "\u2190 Back"] }) }, "back"));
599
+ }
600
+ }
601
+ // Model option
602
+ const model = item;
603
+ const statusIcon = model.isInstalled ? '✓' : '○';
604
+ const statusColor = model.isInstalled ? 'green' : 'gray';
605
+ const vramHint = model.minVRAM ? `${model.minVRAM}GB+ VRAM` : 'CPU OK';
606
+ return (_jsxs(Box, { paddingY: 0, children: [_jsx(Text, { color: isSelected ? 'cyan' : 'white', bold: isSelected, children: isSelected ? '› ' : ' ' }), _jsxs(Text, { color: statusColor, children: [statusIcon, " "] }), _jsx(Text, { color: isSelected ? 'cyan' : 'white', bold: isSelected, children: model.name }), _jsxs(Text, { color: "gray", children: [' ', formatSize(model.sizeBytes), " | ", vramHint, model.isInstalled ? ' (installed)' : ''] })] }, model.id));
607
+ }), allItems.length > MAX_VISIBLE_ITEMS && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "gray", children: [scrollOffset > 0 ? '↑ more above ' : '', visibleEnd < allItems.length ? '↓ more below' : ''] }) })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", children: "\u2191\u2193 navigate \u2022 Enter select \u2022 Esc back" }) }), error && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "red", children: error }) }))] }));
608
+ });
609
+ export default LocalModelWizard;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * CustomModelWizard module exports.
3
+ *
4
+ * Architecture:
5
+ * - types.ts: Shared interfaces (WizardStep, ProviderConfig, validators)
6
+ * - provider-config.ts: Provider registry with steps, display names, validation
7
+ * - shared/: Reusable UI components (ProviderSelector, WizardStepInput, etc.)
8
+ *
9
+ * The main CustomModelWizard.tsx uses these modules instead of having
10
+ * all provider-specific logic scattered throughout.
11
+ */
12
+ export * from './types.js';
13
+ export * from './provider-config.js';
14
+ export * from './shared/index.js';
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../src/cli/ink-cli/components/overlays/custom-model-wizard/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,cAAc,YAAY,CAAC;AAC3B,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * CustomModelWizard module exports.
3
+ *
4
+ * Architecture:
5
+ * - types.ts: Shared interfaces (WizardStep, ProviderConfig, validators)
6
+ * - provider-config.ts: Provider registry with steps, display names, validation
7
+ * - shared/: Reusable UI components (ProviderSelector, WizardStepInput, etc.)
8
+ *
9
+ * The main CustomModelWizard.tsx uses these modules instead of having
10
+ * all provider-specific logic scattered throughout.
11
+ */
12
+ export * from './types.js';
13
+ export * from './provider-config.js';
14
+ export * from './shared/index.js';