snow-ai 0.2.23 → 0.2.25
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/dist/agents/compactAgent.d.ts +55 -0
- package/dist/agents/compactAgent.js +301 -0
- package/dist/api/chat.d.ts +0 -8
- package/dist/api/chat.js +1 -144
- package/dist/api/responses.d.ts +0 -11
- package/dist/api/responses.js +1 -189
- package/dist/api/systemPrompt.d.ts +1 -1
- package/dist/api/systemPrompt.js +80 -206
- package/dist/app.d.ts +2 -1
- package/dist/app.js +11 -13
- package/dist/cli.js +23 -3
- package/dist/hooks/useConversation.js +51 -7
- package/dist/hooks/useGlobalNavigation.d.ts +1 -1
- package/dist/hooks/useKeyboardInput.js +14 -8
- package/dist/mcp/filesystem.d.ts +49 -6
- package/dist/mcp/filesystem.js +243 -86
- package/dist/mcp/websearch.d.ts +118 -0
- package/dist/mcp/websearch.js +451 -0
- package/dist/ui/components/ToolResultPreview.js +60 -1
- package/dist/ui/pages/ChatScreen.d.ts +4 -2
- package/dist/ui/pages/ChatScreen.js +62 -14
- package/dist/ui/pages/{ApiConfigScreen.d.ts → ConfigScreen.d.ts} +1 -1
- package/dist/ui/pages/ConfigScreen.js +549 -0
- package/dist/ui/pages/{ModelConfigScreen.d.ts → ProxyConfigScreen.d.ts} +1 -1
- package/dist/ui/pages/ProxyConfigScreen.js +143 -0
- package/dist/ui/pages/WelcomeScreen.js +15 -15
- package/dist/utils/apiConfig.d.ts +8 -2
- package/dist/utils/apiConfig.js +21 -0
- package/dist/utils/commandExecutor.d.ts +1 -1
- package/dist/utils/contextCompressor.js +363 -49
- package/dist/utils/mcpToolsManager.d.ts +1 -1
- package/dist/utils/mcpToolsManager.js +106 -6
- package/dist/utils/resourceMonitor.d.ts +65 -0
- package/dist/utils/resourceMonitor.js +175 -0
- package/dist/utils/retryUtils.js +6 -0
- package/dist/utils/sessionManager.d.ts +1 -0
- package/dist/utils/sessionManager.js +10 -0
- package/dist/utils/textBuffer.js +7 -2
- package/dist/utils/toolExecutor.d.ts +2 -2
- package/dist/utils/toolExecutor.js +4 -4
- package/package.json +5 -1
- package/dist/ui/pages/ApiConfigScreen.js +0 -161
- package/dist/ui/pages/ModelConfigScreen.js +0 -467
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resource Monitor - Track memory usage and potential leaks
|
|
3
|
+
*/
|
|
4
|
+
interface ResourceStats {
|
|
5
|
+
timestamp: number;
|
|
6
|
+
memoryUsage: NodeJS.MemoryUsage;
|
|
7
|
+
activeEncoders: number;
|
|
8
|
+
activeMCPConnections: number;
|
|
9
|
+
}
|
|
10
|
+
declare class ResourceMonitor {
|
|
11
|
+
private stats;
|
|
12
|
+
private readonly maxStatsHistory;
|
|
13
|
+
private activeEncoders;
|
|
14
|
+
private activeMCPConnections;
|
|
15
|
+
private monitoringInterval;
|
|
16
|
+
/**
|
|
17
|
+
* Start monitoring resources
|
|
18
|
+
*/
|
|
19
|
+
startMonitoring(intervalMs?: number): void;
|
|
20
|
+
/**
|
|
21
|
+
* Stop monitoring resources
|
|
22
|
+
*/
|
|
23
|
+
stopMonitoring(): void;
|
|
24
|
+
/**
|
|
25
|
+
* Collect current resource stats
|
|
26
|
+
*/
|
|
27
|
+
private collectStats;
|
|
28
|
+
/**
|
|
29
|
+
* Track encoder creation
|
|
30
|
+
*/
|
|
31
|
+
trackEncoderCreated(): void;
|
|
32
|
+
/**
|
|
33
|
+
* Track encoder freed
|
|
34
|
+
*/
|
|
35
|
+
trackEncoderFreed(): void;
|
|
36
|
+
/**
|
|
37
|
+
* Track MCP connection opened
|
|
38
|
+
*/
|
|
39
|
+
trackMCPConnectionOpened(serviceName: string): void;
|
|
40
|
+
/**
|
|
41
|
+
* Track MCP connection closed
|
|
42
|
+
*/
|
|
43
|
+
trackMCPConnectionClosed(serviceName: string): void;
|
|
44
|
+
/**
|
|
45
|
+
* Get current stats
|
|
46
|
+
*/
|
|
47
|
+
getCurrentStats(): ResourceStats | null;
|
|
48
|
+
/**
|
|
49
|
+
* Get stats history
|
|
50
|
+
*/
|
|
51
|
+
getStatsHistory(): ResourceStats[];
|
|
52
|
+
/**
|
|
53
|
+
* Check if there are potential memory leaks
|
|
54
|
+
*/
|
|
55
|
+
checkForLeaks(): {
|
|
56
|
+
hasLeak: boolean;
|
|
57
|
+
reasons: string[];
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Force garbage collection if available
|
|
61
|
+
*/
|
|
62
|
+
forceGC(): void;
|
|
63
|
+
}
|
|
64
|
+
export declare const resourceMonitor: ResourceMonitor;
|
|
65
|
+
export {};
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resource Monitor - Track memory usage and potential leaks
|
|
3
|
+
*/
|
|
4
|
+
import { logger } from './logger.js';
|
|
5
|
+
class ResourceMonitor {
|
|
6
|
+
constructor() {
|
|
7
|
+
Object.defineProperty(this, "stats", {
|
|
8
|
+
enumerable: true,
|
|
9
|
+
configurable: true,
|
|
10
|
+
writable: true,
|
|
11
|
+
value: []
|
|
12
|
+
});
|
|
13
|
+
Object.defineProperty(this, "maxStatsHistory", {
|
|
14
|
+
enumerable: true,
|
|
15
|
+
configurable: true,
|
|
16
|
+
writable: true,
|
|
17
|
+
value: 100
|
|
18
|
+
});
|
|
19
|
+
Object.defineProperty(this, "activeEncoders", {
|
|
20
|
+
enumerable: true,
|
|
21
|
+
configurable: true,
|
|
22
|
+
writable: true,
|
|
23
|
+
value: 0
|
|
24
|
+
});
|
|
25
|
+
Object.defineProperty(this, "activeMCPConnections", {
|
|
26
|
+
enumerable: true,
|
|
27
|
+
configurable: true,
|
|
28
|
+
writable: true,
|
|
29
|
+
value: 0
|
|
30
|
+
});
|
|
31
|
+
Object.defineProperty(this, "monitoringInterval", {
|
|
32
|
+
enumerable: true,
|
|
33
|
+
configurable: true,
|
|
34
|
+
writable: true,
|
|
35
|
+
value: null
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Start monitoring resources
|
|
40
|
+
*/
|
|
41
|
+
startMonitoring(intervalMs = 30000) {
|
|
42
|
+
if (this.monitoringInterval) {
|
|
43
|
+
return; // Already monitoring
|
|
44
|
+
}
|
|
45
|
+
this.monitoringInterval = setInterval(() => {
|
|
46
|
+
this.collectStats();
|
|
47
|
+
}, intervalMs);
|
|
48
|
+
logger.info('Resource monitoring started');
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Stop monitoring resources
|
|
52
|
+
*/
|
|
53
|
+
stopMonitoring() {
|
|
54
|
+
if (this.monitoringInterval) {
|
|
55
|
+
clearInterval(this.monitoringInterval);
|
|
56
|
+
this.monitoringInterval = null;
|
|
57
|
+
logger.info('Resource monitoring stopped');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Collect current resource stats
|
|
62
|
+
*/
|
|
63
|
+
collectStats() {
|
|
64
|
+
const stats = {
|
|
65
|
+
timestamp: Date.now(),
|
|
66
|
+
memoryUsage: process.memoryUsage(),
|
|
67
|
+
activeEncoders: this.activeEncoders,
|
|
68
|
+
activeMCPConnections: this.activeMCPConnections,
|
|
69
|
+
};
|
|
70
|
+
this.stats.push(stats);
|
|
71
|
+
// Keep only recent history
|
|
72
|
+
if (this.stats.length > this.maxStatsHistory) {
|
|
73
|
+
this.stats.shift();
|
|
74
|
+
}
|
|
75
|
+
// Log warning if memory usage is high
|
|
76
|
+
const heapUsedMB = stats.memoryUsage.heapUsed / 1024 / 1024;
|
|
77
|
+
if (heapUsedMB > 500) {
|
|
78
|
+
logger.warn(`High memory usage detected: ${heapUsedMB.toFixed(2)} MB heap used`);
|
|
79
|
+
}
|
|
80
|
+
// Log debug info periodically (every 5 minutes)
|
|
81
|
+
if (this.stats.length % 10 === 0) {
|
|
82
|
+
logger.info(`Resource stats: Heap ${heapUsedMB.toFixed(2)} MB, Encoders: ${this.activeEncoders}, MCP: ${this.activeMCPConnections}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Track encoder creation
|
|
87
|
+
*/
|
|
88
|
+
trackEncoderCreated() {
|
|
89
|
+
this.activeEncoders++;
|
|
90
|
+
logger.info(`Encoder created (total: ${this.activeEncoders})`);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Track encoder freed
|
|
94
|
+
*/
|
|
95
|
+
trackEncoderFreed() {
|
|
96
|
+
this.activeEncoders--;
|
|
97
|
+
if (this.activeEncoders < 0) {
|
|
98
|
+
logger.warn('Encoder count went negative - possible double-free');
|
|
99
|
+
this.activeEncoders = 0;
|
|
100
|
+
}
|
|
101
|
+
logger.info(`Encoder freed (remaining: ${this.activeEncoders})`);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Track MCP connection opened
|
|
105
|
+
*/
|
|
106
|
+
trackMCPConnectionOpened(serviceName) {
|
|
107
|
+
this.activeMCPConnections++;
|
|
108
|
+
logger.info(`MCP connection opened: ${serviceName} (total: ${this.activeMCPConnections})`);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Track MCP connection closed
|
|
112
|
+
*/
|
|
113
|
+
trackMCPConnectionClosed(serviceName) {
|
|
114
|
+
this.activeMCPConnections--;
|
|
115
|
+
if (this.activeMCPConnections < 0) {
|
|
116
|
+
logger.warn('MCP connection count went negative - possible double-close');
|
|
117
|
+
this.activeMCPConnections = 0;
|
|
118
|
+
}
|
|
119
|
+
logger.info(`MCP connection closed: ${serviceName} (remaining: ${this.activeMCPConnections})`);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Get current stats
|
|
123
|
+
*/
|
|
124
|
+
getCurrentStats() {
|
|
125
|
+
return this.stats.length > 0 ? this.stats[this.stats.length - 1] : null;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Get stats history
|
|
129
|
+
*/
|
|
130
|
+
getStatsHistory() {
|
|
131
|
+
return [...this.stats];
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Check if there are potential memory leaks
|
|
135
|
+
*/
|
|
136
|
+
checkForLeaks() {
|
|
137
|
+
const reasons = [];
|
|
138
|
+
// Check encoder leak
|
|
139
|
+
if (this.activeEncoders > 3) {
|
|
140
|
+
reasons.push(`High encoder count: ${this.activeEncoders} (expected <= 3)`);
|
|
141
|
+
}
|
|
142
|
+
// Check MCP connection leak
|
|
143
|
+
if (this.activeMCPConnections > 5) {
|
|
144
|
+
reasons.push(`High MCP connection count: ${this.activeMCPConnections} (expected <= 5)`);
|
|
145
|
+
}
|
|
146
|
+
// Check memory growth
|
|
147
|
+
if (this.stats.length >= 10) {
|
|
148
|
+
const recent = this.stats.slice(-10);
|
|
149
|
+
const first = recent[0];
|
|
150
|
+
const last = recent[recent.length - 1];
|
|
151
|
+
const growthMB = (last.memoryUsage.heapUsed - first.memoryUsage.heapUsed) / 1024 / 1024;
|
|
152
|
+
if (growthMB > 100) {
|
|
153
|
+
reasons.push(`Memory grew by ${growthMB.toFixed(2)} MB in last 10 samples`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
hasLeak: reasons.length > 0,
|
|
158
|
+
reasons,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Force garbage collection if available
|
|
163
|
+
*/
|
|
164
|
+
forceGC() {
|
|
165
|
+
if (global.gc) {
|
|
166
|
+
logger.info('Forcing garbage collection');
|
|
167
|
+
global.gc();
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
logger.warn('GC not available - run with --expose-gc flag for manual GC');
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// Singleton instance
|
|
175
|
+
export const resourceMonitor = new ResourceMonitor();
|
package/dist/utils/retryUtils.js
CHANGED
|
@@ -64,6 +64,12 @@ function isRetriableError(error) {
|
|
|
64
64
|
errorMessage.includes('unavailable')) {
|
|
65
65
|
return true;
|
|
66
66
|
}
|
|
67
|
+
// Connection terminated by server
|
|
68
|
+
if (errorMessage.includes('terminated') ||
|
|
69
|
+
errorMessage.includes('connection reset') ||
|
|
70
|
+
errorMessage.includes('socket hang up')) {
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
67
73
|
return false;
|
|
68
74
|
}
|
|
69
75
|
/**
|
|
@@ -34,6 +34,7 @@ declare class SessionManager {
|
|
|
34
34
|
setCurrentSession(session: Session): void;
|
|
35
35
|
clearCurrentSession(): void;
|
|
36
36
|
deleteSession(sessionId: string): Promise<boolean>;
|
|
37
|
+
truncateMessages(messageCount: number): Promise<void>;
|
|
37
38
|
}
|
|
38
39
|
export declare const sessionManager: SessionManager;
|
|
39
40
|
export {};
|
|
@@ -168,5 +168,15 @@ class SessionManager {
|
|
|
168
168
|
return false;
|
|
169
169
|
}
|
|
170
170
|
}
|
|
171
|
+
async truncateMessages(messageCount) {
|
|
172
|
+
if (!this.currentSession) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
// Truncate messages array to specified count
|
|
176
|
+
this.currentSession.messages = this.currentSession.messages.slice(0, messageCount);
|
|
177
|
+
this.currentSession.messageCount = this.currentSession.messages.length;
|
|
178
|
+
this.currentSession.updatedAt = Date.now();
|
|
179
|
+
await this.saveSession(this.currentSession);
|
|
180
|
+
}
|
|
171
181
|
}
|
|
172
182
|
export const sessionManager = new SessionManager();
|
package/dist/utils/textBuffer.js
CHANGED
|
@@ -8,10 +8,15 @@ function sanitizeInput(str) {
|
|
|
8
8
|
.replace(/\r\n/g, '\n') // Normalize line endings
|
|
9
9
|
.replace(/\r/g, '\n') // Convert remaining \r to \n
|
|
10
10
|
.replace(/\t/g, ' ') // Convert tabs to spaces
|
|
11
|
-
// Remove focus events -
|
|
12
|
-
// ESC[I and ESC[O are focus events
|
|
11
|
+
// Remove focus events - complete escape sequences and standalone patterns
|
|
12
|
+
// ESC[I and ESC[O are focus events from terminal focus in/out
|
|
13
13
|
.replace(/\x1b\[I/g, '')
|
|
14
14
|
.replace(/\x1b\[O/g, '')
|
|
15
|
+
// Also remove standalone [I and [O if they appear at word boundaries
|
|
16
|
+
// This catches cases where escape sequences arrive fragmented
|
|
17
|
+
// But we preserve legitimate text like "FOO[I]BAR" or "[Inside]"
|
|
18
|
+
.replace(/(?:^|\s)\[I(?:\s|$)/g, ' ')
|
|
19
|
+
.replace(/(?:^|\s)\[O(?:\s|$)/g, ' ')
|
|
15
20
|
// Remove control characters except newlines
|
|
16
21
|
.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '');
|
|
17
22
|
}
|
|
@@ -14,8 +14,8 @@ export interface ToolResult {
|
|
|
14
14
|
/**
|
|
15
15
|
* Execute a single tool call and return the result
|
|
16
16
|
*/
|
|
17
|
-
export declare function executeToolCall(toolCall: ToolCall): Promise<ToolResult>;
|
|
17
|
+
export declare function executeToolCall(toolCall: ToolCall, abortSignal?: AbortSignal, onTokenUpdate?: (tokenCount: number) => void): Promise<ToolResult>;
|
|
18
18
|
/**
|
|
19
19
|
* Execute multiple tool calls in parallel
|
|
20
20
|
*/
|
|
21
|
-
export declare function executeToolCalls(toolCalls: ToolCall[]): Promise<ToolResult[]>;
|
|
21
|
+
export declare function executeToolCalls(toolCalls: ToolCall[], abortSignal?: AbortSignal, onTokenUpdate?: (tokenCount: number) => void): Promise<ToolResult[]>;
|
|
@@ -2,10 +2,10 @@ import { executeMCPTool } from './mcpToolsManager.js';
|
|
|
2
2
|
/**
|
|
3
3
|
* Execute a single tool call and return the result
|
|
4
4
|
*/
|
|
5
|
-
export async function executeToolCall(toolCall) {
|
|
5
|
+
export async function executeToolCall(toolCall, abortSignal, onTokenUpdate) {
|
|
6
6
|
try {
|
|
7
7
|
const args = JSON.parse(toolCall.function.arguments);
|
|
8
|
-
const result = await executeMCPTool(toolCall.function.name, args);
|
|
8
|
+
const result = await executeMCPTool(toolCall.function.name, args, abortSignal, onTokenUpdate);
|
|
9
9
|
return {
|
|
10
10
|
tool_call_id: toolCall.id,
|
|
11
11
|
role: 'tool',
|
|
@@ -23,6 +23,6 @@ export async function executeToolCall(toolCall) {
|
|
|
23
23
|
/**
|
|
24
24
|
* Execute multiple tool calls in parallel
|
|
25
25
|
*/
|
|
26
|
-
export async function executeToolCalls(toolCalls) {
|
|
27
|
-
return Promise.all(toolCalls.map(tc => executeToolCall(tc)));
|
|
26
|
+
export async function executeToolCalls(toolCalls, abortSignal, onTokenUpdate) {
|
|
27
|
+
return Promise.all(toolCalls.map(tc => executeToolCall(tc, abortSignal, onTokenUpdate)));
|
|
28
28
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "snow-ai",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.25",
|
|
4
4
|
"description": "Intelligent Command Line Assistant powered by AI",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
"cli-highlight": "^2.1.11",
|
|
48
48
|
"diff": "^8.0.2",
|
|
49
49
|
"figlet": "^1.8.2",
|
|
50
|
+
"https-proxy-agent": "^7.0.6",
|
|
50
51
|
"ink": "^5.2.1",
|
|
51
52
|
"ink-gradient": "^3.0.0",
|
|
52
53
|
"ink-markdown": "^1.0.4",
|
|
@@ -54,8 +55,10 @@
|
|
|
54
55
|
"ink-spinner": "^5.0.0",
|
|
55
56
|
"ink-text-input": "^6.0.0",
|
|
56
57
|
"ink-tree-select": "^2.3.1",
|
|
58
|
+
"jsdom": "^27.0.0",
|
|
57
59
|
"meow": "^11.0.0",
|
|
58
60
|
"openai": "^6.1.0",
|
|
61
|
+
"puppeteer-core": "^24.25.0",
|
|
59
62
|
"react": "^18.2.0",
|
|
60
63
|
"string-width": "^7.2.0",
|
|
61
64
|
"tiktoken": "^1.0.22",
|
|
@@ -66,6 +69,7 @@
|
|
|
66
69
|
"@types/diff": "^7.0.2",
|
|
67
70
|
"@types/figlet": "^1.7.0",
|
|
68
71
|
"@types/glob": "^8.1.0",
|
|
72
|
+
"@types/jsdom": "^27.0.0",
|
|
69
73
|
"@types/react": "^18.0.32",
|
|
70
74
|
"@types/ws": "^8.5.8",
|
|
71
75
|
"@vdemedes/prettier-config": "^2.0.1",
|
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
import React, { useState, useEffect } from 'react';
|
|
2
|
-
import { Box, Text, useInput } from 'ink';
|
|
3
|
-
import Gradient from 'ink-gradient';
|
|
4
|
-
import { Select, Alert } from '@inkjs/ui';
|
|
5
|
-
import TextInput from 'ink-text-input';
|
|
6
|
-
import { getOpenAiConfig, updateOpenAiConfig, validateApiConfig, } from '../../utils/apiConfig.js';
|
|
7
|
-
export default function ApiConfigScreen({ onBack, onSave, inlineMode = false }) {
|
|
8
|
-
const [baseUrl, setBaseUrl] = useState('');
|
|
9
|
-
const [apiKey, setApiKey] = useState('');
|
|
10
|
-
const [requestMethod, setRequestMethod] = useState('chat');
|
|
11
|
-
const [anthropicBeta, setAnthropicBeta] = useState(false);
|
|
12
|
-
const [currentField, setCurrentField] = useState('baseUrl');
|
|
13
|
-
const [errors, setErrors] = useState([]);
|
|
14
|
-
const [isEditing, setIsEditing] = useState(false);
|
|
15
|
-
const requestMethodOptions = [
|
|
16
|
-
{
|
|
17
|
-
label: 'Chat Completions - Modern chat API (GPT-4, GPT-3.5-turbo)',
|
|
18
|
-
value: 'chat',
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
label: 'Responses - New responses API (2025, with built-in tools)',
|
|
22
|
-
value: 'responses',
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
label: 'Gemini - Google Gemini API',
|
|
26
|
-
value: 'gemini',
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
label: 'Anthropic - Claude API',
|
|
30
|
-
value: 'anthropic',
|
|
31
|
-
},
|
|
32
|
-
];
|
|
33
|
-
useEffect(() => {
|
|
34
|
-
const config = getOpenAiConfig();
|
|
35
|
-
setBaseUrl(config.baseUrl);
|
|
36
|
-
setApiKey(config.apiKey);
|
|
37
|
-
setRequestMethod(config.requestMethod || 'chat');
|
|
38
|
-
setAnthropicBeta(config.anthropicBeta || false);
|
|
39
|
-
}, []);
|
|
40
|
-
useInput((input, key) => {
|
|
41
|
-
// Allow Escape key to exit Select component without changes
|
|
42
|
-
if (isEditing && currentField === 'requestMethod' && key.escape) {
|
|
43
|
-
setIsEditing(false);
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
// Don't handle other input when Select component is active
|
|
47
|
-
if (isEditing && currentField === 'requestMethod') {
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
// Handle save/exit globally
|
|
51
|
-
if (input === 's' && (key.ctrl || key.meta)) {
|
|
52
|
-
const validationErrors = validateApiConfig({ baseUrl, apiKey, requestMethod });
|
|
53
|
-
if (validationErrors.length === 0) {
|
|
54
|
-
updateOpenAiConfig({ baseUrl, apiKey, requestMethod, anthropicBeta });
|
|
55
|
-
setErrors([]);
|
|
56
|
-
onSave();
|
|
57
|
-
}
|
|
58
|
-
else {
|
|
59
|
-
setErrors(validationErrors);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
else if (key.escape) {
|
|
63
|
-
const validationErrors = validateApiConfig({ baseUrl, apiKey, requestMethod });
|
|
64
|
-
if (validationErrors.length === 0) {
|
|
65
|
-
updateOpenAiConfig({ baseUrl, apiKey, requestMethod, anthropicBeta });
|
|
66
|
-
setErrors([]);
|
|
67
|
-
}
|
|
68
|
-
onBack();
|
|
69
|
-
}
|
|
70
|
-
else if (key.return) {
|
|
71
|
-
if (isEditing) {
|
|
72
|
-
// Exit edit mode, return to navigation
|
|
73
|
-
setIsEditing(false);
|
|
74
|
-
}
|
|
75
|
-
else {
|
|
76
|
-
// Enter edit mode for current field (toggle for checkbox)
|
|
77
|
-
if (currentField === 'anthropicBeta') {
|
|
78
|
-
setAnthropicBeta(!anthropicBeta);
|
|
79
|
-
}
|
|
80
|
-
else {
|
|
81
|
-
setIsEditing(true);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
else if (!isEditing && key.upArrow) {
|
|
86
|
-
if (currentField === 'apiKey') {
|
|
87
|
-
setCurrentField('baseUrl');
|
|
88
|
-
}
|
|
89
|
-
else if (currentField === 'requestMethod') {
|
|
90
|
-
setCurrentField('apiKey');
|
|
91
|
-
}
|
|
92
|
-
else if (currentField === 'anthropicBeta') {
|
|
93
|
-
setCurrentField('requestMethod');
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
else if (!isEditing && key.downArrow) {
|
|
97
|
-
if (currentField === 'baseUrl') {
|
|
98
|
-
setCurrentField('apiKey');
|
|
99
|
-
}
|
|
100
|
-
else if (currentField === 'apiKey') {
|
|
101
|
-
setCurrentField('requestMethod');
|
|
102
|
-
}
|
|
103
|
-
else if (currentField === 'requestMethod') {
|
|
104
|
-
setCurrentField('anthropicBeta');
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
return (React.createElement(Box, { flexDirection: "column", padding: 1 },
|
|
109
|
-
!inlineMode && (React.createElement(Box, { marginBottom: 2, borderStyle: "double", borderColor: "cyan", paddingX: 2, paddingY: 1 },
|
|
110
|
-
React.createElement(Box, { flexDirection: "column" },
|
|
111
|
-
React.createElement(Gradient, { name: "rainbow" }, "OpenAI API Configuration"),
|
|
112
|
-
React.createElement(Text, { color: "gray", dimColor: true }, "Configure your OpenAI API settings")))),
|
|
113
|
-
React.createElement(Box, { flexDirection: "column", marginBottom: 2 },
|
|
114
|
-
React.createElement(Box, { marginBottom: 1 },
|
|
115
|
-
React.createElement(Box, { flexDirection: "column" },
|
|
116
|
-
React.createElement(Text, { color: currentField === 'baseUrl' ? 'green' : 'white' },
|
|
117
|
-
currentField === 'baseUrl' ? '❯ ' : ' ',
|
|
118
|
-
"Base URL:"),
|
|
119
|
-
currentField === 'baseUrl' && isEditing && (React.createElement(Box, { marginLeft: 3 },
|
|
120
|
-
React.createElement(TextInput, { value: baseUrl, onChange: setBaseUrl, placeholder: "https://api.openai.com/v1" }))),
|
|
121
|
-
(!isEditing || currentField !== 'baseUrl') && (React.createElement(Box, { marginLeft: 3 },
|
|
122
|
-
React.createElement(Text, { color: "gray" }, baseUrl || 'Not set'))))),
|
|
123
|
-
React.createElement(Box, { marginBottom: 1 },
|
|
124
|
-
React.createElement(Box, { flexDirection: "column" },
|
|
125
|
-
React.createElement(Text, { color: currentField === 'apiKey' ? 'green' : 'white' },
|
|
126
|
-
currentField === 'apiKey' ? '❯ ' : ' ',
|
|
127
|
-
"API Key:"),
|
|
128
|
-
currentField === 'apiKey' && isEditing && (React.createElement(Box, { marginLeft: 3 },
|
|
129
|
-
React.createElement(TextInput, { value: apiKey, onChange: setApiKey, placeholder: "sk-...", mask: "*" }))),
|
|
130
|
-
(!isEditing || currentField !== 'apiKey') && (React.createElement(Box, { marginLeft: 3 },
|
|
131
|
-
React.createElement(Text, { color: "gray" }, apiKey ? '*'.repeat(Math.min(apiKey.length, 20)) : 'Not set'))))),
|
|
132
|
-
React.createElement(Box, { marginBottom: 1 },
|
|
133
|
-
React.createElement(Box, { flexDirection: "column" },
|
|
134
|
-
React.createElement(Text, { color: currentField === 'requestMethod' ? 'green' : 'white' },
|
|
135
|
-
currentField === 'requestMethod' ? '❯ ' : ' ',
|
|
136
|
-
"Request Method:"),
|
|
137
|
-
currentField === 'requestMethod' && isEditing && (React.createElement(Box, { marginLeft: 3 },
|
|
138
|
-
React.createElement(Select, { options: requestMethodOptions, defaultValue: requestMethod, onChange: (value) => {
|
|
139
|
-
setRequestMethod(value);
|
|
140
|
-
setIsEditing(false); // Auto exit edit mode after selection
|
|
141
|
-
} }))),
|
|
142
|
-
(!isEditing || currentField !== 'requestMethod') && (React.createElement(Box, { marginLeft: 3 },
|
|
143
|
-
React.createElement(Text, { color: "gray" }, requestMethodOptions.find(opt => opt.value === requestMethod)?.label || 'Not set'))))),
|
|
144
|
-
React.createElement(Box, { marginBottom: 1 },
|
|
145
|
-
React.createElement(Box, { flexDirection: "column" },
|
|
146
|
-
React.createElement(Text, { color: currentField === 'anthropicBeta' ? 'green' : 'white' },
|
|
147
|
-
currentField === 'anthropicBeta' ? '❯ ' : ' ',
|
|
148
|
-
"Anthropic Beta (for Claude API):"),
|
|
149
|
-
React.createElement(Box, { marginLeft: 3 },
|
|
150
|
-
React.createElement(Text, { color: "gray" },
|
|
151
|
-
anthropicBeta ? '☑ Enabled' : '☐ Disabled',
|
|
152
|
-
" (Press Enter to toggle)"))))),
|
|
153
|
-
errors.length > 0 && (React.createElement(Box, { flexDirection: "column", marginBottom: 2 },
|
|
154
|
-
React.createElement(Text, { color: "red", bold: true }, "Errors:"),
|
|
155
|
-
errors.map((error, index) => (React.createElement(Text, { key: index, color: "red" },
|
|
156
|
-
"\u2022 ",
|
|
157
|
-
error))))),
|
|
158
|
-
React.createElement(Box, { flexDirection: "column" }, isEditing ? (React.createElement(React.Fragment, null,
|
|
159
|
-
React.createElement(Alert, { variant: "info" }, "Editing mode: Press Enter to save and exit editing (Make your changes and press Enter when done)"))) : (React.createElement(React.Fragment, null,
|
|
160
|
-
React.createElement(Alert, { variant: "info" }, "Use \u2191\u2193 to navigate between fields, press Enter to edit, and press Ctrl+S or Esc to save and return"))))));
|
|
161
|
-
}
|