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.
- package/README.md +62 -7
- package/dist/agents/agent-template.yml +2 -2
- package/dist/agents/coding-agent/coding-agent.yml +22 -16
- package/dist/agents/database-agent/database-agent.yml +2 -2
- package/dist/agents/default-agent.yml +7 -5
- package/dist/agents/github-agent/github-agent.yml +2 -2
- package/dist/agents/product-name-researcher/product-name-researcher.yml +2 -2
- package/dist/agents/talk2pdf-agent/talk2pdf-agent.yml +2 -2
- package/dist/analytics/events.d.ts +13 -6
- package/dist/analytics/events.d.ts.map +1 -1
- package/dist/analytics/index.d.ts +1 -1
- package/dist/analytics/index.d.ts.map +1 -1
- package/dist/analytics/index.js +6 -2
- package/dist/api/server-hono.d.ts.map +1 -1
- package/dist/api/server-hono.js +27 -5
- package/dist/cli/cli-subscriber.d.ts +4 -0
- package/dist/cli/cli-subscriber.d.ts.map +1 -1
- package/dist/cli/cli-subscriber.js +40 -2
- package/dist/cli/commands/create-app.d.ts +16 -14
- package/dist/cli/commands/create-app.d.ts.map +1 -1
- package/dist/cli/commands/create-app.js +626 -102
- package/dist/cli/commands/create-image.d.ts +7 -0
- package/dist/cli/commands/create-image.d.ts.map +1 -0
- package/dist/cli/commands/create-image.js +201 -0
- package/dist/cli/commands/helpers/formatters.js +7 -7
- package/dist/cli/commands/index.d.ts +2 -1
- package/dist/cli/commands/index.d.ts.map +1 -1
- package/dist/cli/commands/index.js +2 -1
- package/dist/cli/commands/init-app.js +7 -7
- package/dist/cli/commands/install.d.ts +0 -3
- package/dist/cli/commands/install.d.ts.map +1 -1
- package/dist/cli/commands/install.js +10 -35
- package/dist/cli/commands/interactive-commands/command-parser.js +7 -7
- package/dist/cli/commands/interactive-commands/general-commands.js +1 -1
- package/dist/cli/commands/interactive-commands/prompt-commands.js +11 -11
- package/dist/cli/commands/interactive-commands/system/system-commands.js +3 -3
- package/dist/cli/commands/list-agents.js +2 -2
- package/dist/cli/commands/session-commands.js +16 -16
- package/dist/cli/commands/setup.d.ts +13 -5
- package/dist/cli/commands/setup.d.ts.map +1 -1
- package/dist/cli/commands/setup.js +860 -65
- package/dist/cli/commands/which.js +1 -1
- package/dist/cli/ink-cli/components/ApprovalPrompt.d.ts +2 -0
- package/dist/cli/ink-cli/components/ApprovalPrompt.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/ApprovalPrompt.js +29 -7
- package/dist/cli/ink-cli/components/CustomInput.js +1 -1
- package/dist/cli/ink-cli/components/EditableMultiLineInput.js +4 -4
- package/dist/cli/ink-cli/components/ElicitationForm.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/ElicitationForm.js +6 -6
- package/dist/cli/ink-cli/components/ErrorBoundary.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/ErrorBoundary.js +1 -1
- package/dist/cli/ink-cli/components/Footer.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/Footer.js +1 -1
- package/dist/cli/ink-cli/components/HistorySearchBar.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/HistorySearchBar.js +1 -1
- package/dist/cli/ink-cli/components/MultiLineInput.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/MultiLineInput.js +3 -3
- package/dist/cli/ink-cli/components/ResourceAutocomplete.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/ResourceAutocomplete.js +4 -4
- package/dist/cli/ink-cli/components/SlashCommandAutocomplete.js +3 -3
- package/dist/cli/ink-cli/components/StatusBar.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/StatusBar.js +7 -5
- package/dist/cli/ink-cli/components/TextBufferInput.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/TextBufferInput.js +6 -6
- package/dist/cli/ink-cli/components/base/BaseAutocomplete.js +4 -4
- package/dist/cli/ink-cli/components/base/BaseSelector.js +2 -2
- package/dist/cli/ink-cli/components/chat/Footer.js +1 -1
- package/dist/cli/ink-cli/components/chat/Header.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/chat/Header.js +2 -4
- package/dist/cli/ink-cli/components/chat/MessageItem.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/chat/MessageItem.js +5 -5
- package/dist/cli/ink-cli/components/chat/MessageList.js +1 -1
- package/dist/cli/ink-cli/components/chat/QueuedMessagesDisplay.js +1 -1
- package/dist/cli/ink-cli/components/chat/ToolIcon.d.ts +1 -1
- package/dist/cli/ink-cli/components/chat/ToolIcon.js +4 -4
- package/dist/cli/ink-cli/components/chat/styled-boxes/ConfigBox.js +1 -1
- package/dist/cli/ink-cli/components/chat/styled-boxes/HelpBox.js +1 -1
- package/dist/cli/ink-cli/components/chat/styled-boxes/LogConfigBox.js +2 -2
- package/dist/cli/ink-cli/components/chat/styled-boxes/SessionHistoryBox.js +5 -5
- package/dist/cli/ink-cli/components/chat/styled-boxes/SessionListBox.js +2 -2
- package/dist/cli/ink-cli/components/chat/styled-boxes/ShortcutsBox.js +1 -1
- package/dist/cli/ink-cli/components/chat/styled-boxes/StatsBox.js +1 -1
- package/dist/cli/ink-cli/components/chat/styled-boxes/StyledBox.js +2 -2
- package/dist/cli/ink-cli/components/modes/AlternateBufferCLI.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/modes/AlternateBufferCLI.js +1 -1
- package/dist/cli/ink-cli/components/modes/StaticCLI.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/modes/StaticCLI.js +1 -1
- package/dist/cli/ink-cli/components/overlays/ApiKeyInput.js +1 -1
- package/dist/cli/ink-cli/components/overlays/CustomModelWizard.d.ts +10 -2
- package/dist/cli/ink-cli/components/overlays/CustomModelWizard.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/overlays/CustomModelWizard.js +198 -89
- package/dist/cli/ink-cli/components/overlays/LogLevelSelector.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/overlays/LogLevelSelector.js +2 -2
- package/dist/cli/ink-cli/components/overlays/McpAddChoice.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/overlays/McpAddChoice.js +1 -1
- package/dist/cli/ink-cli/components/overlays/McpAddSelector.js +1 -1
- package/dist/cli/ink-cli/components/overlays/McpCustomTypeSelector.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/overlays/McpCustomTypeSelector.js +2 -2
- package/dist/cli/ink-cli/components/overlays/McpCustomWizard.js +1 -1
- package/dist/cli/ink-cli/components/overlays/McpRemoveSelector.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/overlays/McpRemoveSelector.js +1 -1
- package/dist/cli/ink-cli/components/overlays/McpSelector.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/overlays/McpSelector.js +1 -1
- package/dist/cli/ink-cli/components/overlays/McpServerActions.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/overlays/McpServerActions.js +2 -2
- package/dist/cli/ink-cli/components/overlays/McpServerList.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/overlays/McpServerList.js +1 -1
- package/dist/cli/ink-cli/components/overlays/ModelSelectorRefactored.d.ts +5 -5
- package/dist/cli/ink-cli/components/overlays/ModelSelectorRefactored.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/overlays/ModelSelectorRefactored.js +222 -68
- package/dist/cli/ink-cli/components/overlays/PromptAddChoice.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/overlays/PromptAddChoice.js +2 -2
- package/dist/cli/ink-cli/components/overlays/PromptAddWizard.js +1 -1
- package/dist/cli/ink-cli/components/overlays/PromptDeleteSelector.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/overlays/PromptDeleteSelector.js +2 -2
- package/dist/cli/ink-cli/components/overlays/PromptList.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/overlays/PromptList.js +2 -2
- package/dist/cli/ink-cli/components/overlays/SearchOverlay.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/overlays/SearchOverlay.js +4 -4
- package/dist/cli/ink-cli/components/overlays/SessionSubcommandSelector.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/overlays/SessionSubcommandSelector.js +1 -1
- package/dist/cli/ink-cli/components/overlays/StreamSelector.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/overlays/StreamSelector.js +1 -1
- package/dist/cli/ink-cli/components/overlays/ToolBrowser.js +12 -12
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/LocalModelWizard.d.ts +25 -0
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/LocalModelWizard.d.ts.map +1 -0
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/LocalModelWizard.js +609 -0
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/index.d.ts +15 -0
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/index.d.ts.map +1 -0
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/index.js +14 -0
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/provider-config.d.ts +33 -0
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/provider-config.d.ts.map +1 -0
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/provider-config.js +419 -0
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/ApiKeyStep.d.ts +25 -0
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/ApiKeyStep.d.ts.map +1 -0
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/ApiKeyStep.js +29 -0
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/ProviderSelector.d.ts +17 -0
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/ProviderSelector.d.ts.map +1 -0
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/ProviderSelector.js +11 -0
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/SetupInfoBanner.d.ts +20 -0
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/SetupInfoBanner.d.ts.map +1 -0
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/SetupInfoBanner.js +10 -0
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/WizardStepInput.d.ts +30 -0
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/WizardStepInput.d.ts.map +1 -0
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/WizardStepInput.js +13 -0
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/index.d.ts +8 -0
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/index.d.ts.map +1 -0
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/shared/index.js +7 -0
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/types.d.ts +79 -0
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/types.d.ts.map +1 -0
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/types.js +38 -0
- package/dist/cli/ink-cli/components/renderers/DiffRenderer.js +2 -2
- package/dist/cli/ink-cli/components/renderers/FilePreviewRenderer.js +1 -1
- package/dist/cli/ink-cli/components/renderers/FileRenderer.js +4 -4
- package/dist/cli/ink-cli/components/renderers/GenericRenderer.js +2 -2
- package/dist/cli/ink-cli/components/renderers/SearchRenderer.js +1 -1
- package/dist/cli/ink-cli/components/renderers/ShellRenderer.js +3 -3
- package/dist/cli/ink-cli/components/renderers/diff-shared.js +1 -1
- package/dist/cli/ink-cli/components/shared/MarkdownText.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/shared/MarkdownText.js +8 -6
- package/dist/cli/ink-cli/containers/InputContainer.d.ts.map +1 -1
- package/dist/cli/ink-cli/containers/InputContainer.js +23 -1
- package/dist/cli/ink-cli/containers/OverlayContainer.d.ts.map +1 -1
- package/dist/cli/ink-cli/containers/OverlayContainer.js +80 -24
- package/dist/cli/ink-cli/hooks/useAgentEvents.d.ts +1 -1
- package/dist/cli/ink-cli/hooks/useAgentEvents.d.ts.map +1 -1
- package/dist/cli/ink-cli/hooks/useAgentEvents.js +5 -1
- package/dist/cli/ink-cli/hooks/useCLIState.d.ts +1 -1
- package/dist/cli/ink-cli/hooks/useCLIState.d.ts.map +1 -1
- package/dist/cli/ink-cli/hooks/useCLIState.js +4 -2
- package/dist/cli/ink-cli/services/processStream.d.ts.map +1 -1
- package/dist/cli/ink-cli/services/processStream.js +77 -9
- package/dist/cli/ink-cli/state/types.d.ts +3 -2
- package/dist/cli/ink-cli/state/types.d.ts.map +1 -1
- package/dist/cli/ink-cli/utils/messageFormatting.d.ts +5 -0
- package/dist/cli/ink-cli/utils/messageFormatting.d.ts.map +1 -1
- package/dist/cli/ink-cli/utils/messageFormatting.js +59 -1
- package/dist/cli/ink-cli/utils/toolUtils.d.ts.map +1 -1
- package/dist/cli/ink-cli/utils/toolUtils.js +2 -0
- package/dist/cli/utils/api-key-setup.d.ts +54 -4
- package/dist/cli/utils/api-key-setup.d.ts.map +1 -1
- package/dist/cli/utils/api-key-setup.js +433 -107
- package/dist/cli/utils/api-key-verification.d.ts +17 -0
- package/dist/cli/utils/api-key-verification.d.ts.map +1 -0
- package/dist/cli/utils/api-key-verification.js +211 -0
- package/dist/cli/utils/config-validation.d.ts +22 -2
- package/dist/cli/utils/config-validation.d.ts.map +1 -1
- package/dist/cli/utils/config-validation.js +354 -25
- package/dist/cli/utils/local-model-setup.d.ts +46 -0
- package/dist/cli/utils/local-model-setup.d.ts.map +1 -0
- package/dist/cli/utils/local-model-setup.js +662 -0
- package/dist/cli/utils/options.js +1 -1
- package/dist/cli/utils/prompt-helpers.d.ts +47 -0
- package/dist/cli/utils/prompt-helpers.d.ts.map +1 -0
- package/dist/cli/utils/prompt-helpers.js +66 -0
- package/dist/cli/utils/provider-setup.d.ts +66 -8
- package/dist/cli/utils/provider-setup.d.ts.map +1 -1
- package/dist/cli/utils/provider-setup.js +324 -84
- package/dist/cli/utils/scaffolding-utils.d.ts +76 -0
- package/dist/cli/utils/scaffolding-utils.d.ts.map +1 -0
- package/dist/cli/utils/scaffolding-utils.js +246 -0
- package/dist/cli/utils/setup-utils.d.ts +16 -0
- package/dist/cli/utils/setup-utils.d.ts.map +1 -1
- package/dist/cli/utils/setup-utils.js +72 -21
- package/dist/cli/utils/template-engine.d.ts +65 -0
- package/dist/cli/utils/template-engine.d.ts.map +1 -0
- package/dist/cli/utils/template-engine.js +1089 -0
- package/dist/config/cli-overrides.d.ts +44 -1
- package/dist/config/cli-overrides.d.ts.map +1 -1
- package/dist/config/cli-overrides.js +102 -0
- package/dist/index.js +315 -53
- package/dist/webui/assets/index-8j-KMkX1.js +2054 -0
- package/dist/webui/assets/index-c_AX24V4.css +1 -0
- package/dist/webui/index.html +3 -9
- package/dist/webui/logos/aws-color.svg +1 -0
- package/dist/webui/logos/dexto/dexto_logo.svg +1 -1
- package/dist/webui/logos/dexto/dexto_logo_light.svg +6 -6
- package/dist/webui/logos/glama.svg +7 -0
- package/dist/webui/logos/litellm.svg +7 -0
- package/dist/webui/logos/openrouter.svg +1 -0
- package/package.json +8 -7
- package/dist/webui/assets/index-BkwPkZpd.css +0 -1
- 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';
|