aiexecode 1.0.66 → 1.0.69
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.
Potentially problematic release.
This version of aiexecode might be problematic. Click here for more details.
- package/config_template/settings.json +1 -3
- package/index.js +46 -71
- package/package.json +1 -12
- package/payload_viewer/out/404/index.html +1 -1
- package/payload_viewer/out/404.html +1 -1
- package/payload_viewer/out/index.html +1 -1
- package/payload_viewer/out/index.txt +1 -1
- package/payload_viewer/web_server.js +0 -163
- package/prompts/completion_judge.txt +11 -7
- package/src/ai_based/completion_judge.js +97 -6
- package/src/ai_based/orchestrator.js +71 -3
- package/src/ai_based/pip_package_installer.js +14 -12
- package/src/ai_based/pip_package_lookup.js +13 -10
- package/src/commands/apikey.js +8 -34
- package/src/commands/help.js +3 -4
- package/src/commands/model.js +17 -74
- package/src/commands/reasoning_effort.js +1 -1
- package/src/config/feature_flags.js +0 -12
- package/src/{ui → frontend}/App.js +23 -25
- package/src/frontend/README.md +81 -0
- package/src/{ui/components/SuggestionsDisplay.js → frontend/components/AutocompleteMenu.js} +3 -3
- package/src/{ui/components/HistoryItemDisplay.js → frontend/components/ConversationItem.js} +37 -89
- package/src/{ui → frontend}/components/CurrentModelView.js +3 -5
- package/src/{ui → frontend}/components/Footer.js +4 -6
- package/src/{ui → frontend}/components/Header.js +2 -5
- package/src/{ui/components/InputPrompt.js → frontend/components/Input.js} +16 -54
- package/src/frontend/components/ModelListView.js +106 -0
- package/src/{ui → frontend}/components/ModelUpdatedView.js +3 -5
- package/src/{ui → frontend}/components/SessionSpinner.js +3 -3
- package/src/{ui → frontend}/components/SetupWizard.js +8 -101
- package/src/{ui → frontend}/components/ToolApprovalPrompt.js +16 -14
- package/src/frontend/design/themeColors.js +42 -0
- package/src/{ui → frontend}/index.js +7 -7
- package/src/frontend/utils/inputBuffer.js +441 -0
- package/src/{ui/utils/markdownRenderer.js → frontend/utils/markdownParser.js} +3 -3
- package/src/{ui/utils/ConsolePatcher.js → frontend/utils/outputRedirector.js} +9 -9
- package/src/{ui/utils/codeColorizer.js → frontend/utils/syntaxHighlighter.js} +2 -3
- package/src/system/ai_request.js +145 -595
- package/src/system/code_executer.js +111 -16
- package/src/system/file_integrity.js +5 -7
- package/src/system/log.js +3 -3
- package/src/system/mcp_integration.js +15 -13
- package/src/system/output_helper.js +0 -20
- package/src/system/session.js +97 -23
- package/src/system/session_memory.js +2 -82
- package/src/system/system_info.js +1 -1
- package/src/system/ui_events.js +0 -43
- package/src/tools/code_editor.js +17 -2
- package/src/tools/file_reader.js +17 -2
- package/src/tools/glob.js +9 -1
- package/src/tools/response_message.js +0 -2
- package/src/tools/ripgrep.js +9 -1
- package/src/tools/web_downloader.js +9 -1
- package/src/util/config.js +3 -8
- package/src/util/debug_log.js +4 -11
- package/src/util/mcp_config_manager.js +3 -5
- package/src/util/output_formatter.js +0 -47
- package/src/util/prompt_loader.js +3 -4
- package/src/util/safe_fs.js +60 -0
- package/src/util/setup_wizard.js +1 -3
- package/src/util/text_formatter.js +0 -86
- package/src/config/claude_models.js +0 -195
- package/src/ui/README.md +0 -208
- package/src/ui/api.js +0 -167
- package/src/ui/components/AgenticProgressDisplay.js +0 -126
- package/src/ui/components/Composer.js +0 -55
- package/src/ui/components/LoadingIndicator.js +0 -54
- package/src/ui/components/ModelListView.js +0 -214
- package/src/ui/components/Notifications.js +0 -55
- package/src/ui/components/StreamingIndicator.js +0 -36
- package/src/ui/contexts/AppContext.js +0 -25
- package/src/ui/contexts/StreamingContext.js +0 -20
- package/src/ui/contexts/UIStateContext.js +0 -117
- package/src/ui/example-usage.js +0 -180
- package/src/ui/hooks/useTerminalResize.js +0 -39
- package/src/ui/themes/semantic-tokens.js +0 -73
- package/src/ui/utils/text-buffer.js +0 -975
- /package/payload_viewer/out/_next/static/{t0WTsjXST7ISD1Boa6ifx → 7FgHZugvVp3pn1XStUYUJ}/_buildManifest.js +0 -0
- /package/payload_viewer/out/_next/static/{t0WTsjXST7ISD1Boa6ifx → 7FgHZugvVp3pn1XStUYUJ}/_clientMiddlewareManifest.json +0 -0
- /package/payload_viewer/out/_next/static/{t0WTsjXST7ISD1Boa6ifx → 7FgHZugvVp3pn1XStUYUJ}/_ssgManifest.js +0 -0
- /package/src/{ui → frontend}/components/BlankLine.js +0 -0
- /package/src/{ui → frontend}/components/FileDiffViewer.js +0 -0
- /package/src/{ui → frontend}/components/HelpView.js +0 -0
- /package/src/{ui → frontend}/hooks/useCompletion.js +0 -0
- /package/src/{ui → frontend}/hooks/useKeypress.js +0 -0
- /package/src/{ui → frontend}/utils/diffUtils.js +0 -0
- /package/src/{ui → frontend}/utils/renderInkComponent.js +0 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# UI System Documentation
|
|
2
|
+
|
|
3
|
+
이 디렉토리는 Ink 기반의 터미널 UI 시스템을 포함합니다.
|
|
4
|
+
|
|
5
|
+
## 구조
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
ui/
|
|
9
|
+
├── App.js # 메인 앱 진입점
|
|
10
|
+
├── index.js # UI 렌더링 진입점
|
|
11
|
+
├── components/ # UI 컴포넌트
|
|
12
|
+
│ ├── Header.js # 헤더 (로고)
|
|
13
|
+
│ ├── Footer.js # 푸터 (상태 정보)
|
|
14
|
+
│ ├── ConversationItem.js # 개별 대화 아이템
|
|
15
|
+
│ ├── Input.js # 입력 컴포넌트
|
|
16
|
+
│ ├── SessionSpinner.js # 세션 처리 스피너
|
|
17
|
+
│ ├── AutocompleteMenu.js # 자동완성 메뉴
|
|
18
|
+
│ ├── BlankLine.js # 빈 줄 컴포넌트
|
|
19
|
+
│ ├── FileDiffViewer.js # 파일 diff 뷰어
|
|
20
|
+
│ ├── ToolApprovalPrompt.js # 도구 승인 프롬프트
|
|
21
|
+
│ ├── SetupWizard.js # 초기 설정 마법사
|
|
22
|
+
│ ├── HelpView.js # 도움말 뷰
|
|
23
|
+
│ ├── CurrentModelView.js # 현재 모델 뷰
|
|
24
|
+
│ ├── ModelListView.js # 모델 목록 뷰
|
|
25
|
+
│ └── ModelUpdatedView.js # 모델 변경 확인 뷰
|
|
26
|
+
├── hooks/ # Custom hooks
|
|
27
|
+
│ ├── useKeypress.js # 키보드 이벤트
|
|
28
|
+
│ └── useCompletion.js # 자동완성
|
|
29
|
+
├── design/ # 디자인 시스템
|
|
30
|
+
│ └── themeColors.js # 테마 색상
|
|
31
|
+
└── utils/ # 유틸리티
|
|
32
|
+
├── inputBuffer.js # 입력 버퍼 (멀티라인)
|
|
33
|
+
├── outputRedirector.js # 출력 리다이렉터
|
|
34
|
+
├── syntaxHighlighter.js # 문법 하이라이터
|
|
35
|
+
├── markdownParser.js # 마크다운 파서
|
|
36
|
+
├── diffUtils.js # Diff 유틸리티
|
|
37
|
+
└── renderInkComponent.js # Ink 컴포넌트 렌더러
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## 아키텍처
|
|
41
|
+
|
|
42
|
+
### 레이아웃 구조
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
┌──────────────────────────────────┐
|
|
46
|
+
│ App (Main Layout) │
|
|
47
|
+
│ ├─ Header (Static) │
|
|
48
|
+
│ ├─ History (Static) │
|
|
49
|
+
│ ├─ SessionSpinner (Dynamic) │
|
|
50
|
+
│ ├─ Input │
|
|
51
|
+
│ └─ Footer │
|
|
52
|
+
└──────────────────────────────────┘
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## UI 상태 관리
|
|
56
|
+
|
|
57
|
+
UI 상태는 `uiEvents` 모듈을 통해 관리됩니다. 자세한 내용은 `src/system/ui_events.js`를 참조하세요.
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
## 메시지 타입
|
|
61
|
+
|
|
62
|
+
| Type | Icon | Color | Usage |
|
|
63
|
+
|------|------|-------|-------|
|
|
64
|
+
| `user` | `>` | Accent | 사용자 입력 |
|
|
65
|
+
| `assistant` | `◆` | Info | AI 응답 |
|
|
66
|
+
| `system` | `ℹ` | Secondary | 시스템 메시지 |
|
|
67
|
+
| `error` | `✗` | Error | 에러 메시지 |
|
|
68
|
+
| `tool` | `⚙` | Success | 도구 실행 결과 |
|
|
69
|
+
| `thinking` | `💭` | Link | AI 사고 과정 |
|
|
70
|
+
|
|
71
|
+
## 애니메이션
|
|
72
|
+
|
|
73
|
+
### SessionSpinner
|
|
74
|
+
- ink-spinner 라이브러리 사용
|
|
75
|
+
- 경과 시간 표시 (초/분)
|
|
76
|
+
|
|
77
|
+
## 성능 최적화
|
|
78
|
+
|
|
79
|
+
1. **Static 컴포넌트**: 불변 히스토리는 Static으로 렌더링하여 리렌더링 방지
|
|
80
|
+
2. **Dynamic 영역**: 진행 중인 작업만 동적으로 업데이트
|
|
81
|
+
3. **조건부 렌더링**: 필요한 컴포넌트만 표시
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Autocomplete menu component for command suggestions
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import React from 'react';
|
|
6
6
|
import { Box, Text } from 'ink';
|
|
7
|
-
import { theme } from '../
|
|
7
|
+
import { theme } from '../design/themeColors.js';
|
|
8
8
|
|
|
9
|
-
export function
|
|
9
|
+
export function AutocompleteMenu({ suggestions = [], activeIndex = 0 }) {
|
|
10
10
|
if (suggestions.length === 0) {
|
|
11
11
|
return null;
|
|
12
12
|
}
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* ConversationItem - Renders individual conversation items
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import React, { useState, useEffect } from 'react';
|
|
6
6
|
import { Box, Text } from 'ink';
|
|
7
|
-
import { theme } from '../
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import { renderMarkdown } from '../utils/markdownRenderer.js';
|
|
7
|
+
import { theme } from '../design/themeColors.js';
|
|
8
|
+
import { colorizeCode } from '../utils/syntaxHighlighter.js';
|
|
9
|
+
import { renderMarkdown } from '../utils/markdownParser.js';
|
|
11
10
|
import { FileDiffViewer } from './FileDiffViewer.js';
|
|
12
11
|
import { getToolDisplayName, formatToolCall } from '../../system/tool_registry.js';
|
|
13
12
|
import { getFileSnapshot } from '../../system/file_integrity.js';
|
|
@@ -18,12 +17,9 @@ import { createHash } from 'crypto';
|
|
|
18
17
|
import { createDebugLogger } from '../../util/debug_log.js';
|
|
19
18
|
import { prepareEditReplaceDiff } from '../utils/diffUtils.js';
|
|
20
19
|
|
|
21
|
-
const debugLog = createDebugLogger('ui_components.log', '
|
|
20
|
+
const debugLog = createDebugLogger('ui_components.log', 'ConversationItem');
|
|
22
21
|
const OLDSTRING_LOG_FILE = join(DEBUG_LOG_DIR, 'oldstring.txt');
|
|
23
22
|
|
|
24
|
-
// 스트리밍 인디케이터 및 operations 렌더링 활성화 여부
|
|
25
|
-
const RENDER_STREAMING_INDICATORS = false;
|
|
26
|
-
|
|
27
23
|
// Evidence logging for old_string NOT FOUND issues
|
|
28
24
|
function logOldStringEvidence(evidence) {
|
|
29
25
|
try {
|
|
@@ -183,7 +179,7 @@ function StandardDisplay({ item, isPending, hasFollowingResult, nextItem, isLast
|
|
|
183
179
|
|
|
184
180
|
debugLog('---------- StandardDisplay START ----------');
|
|
185
181
|
debugLog(`type: ${type}, toolName: ${toolName || 'N/A'}`);
|
|
186
|
-
debugLog(`text: ${text?.substring(0, 100) || 'N/A'}...`);
|
|
182
|
+
debugLog(`text: ${typeof text === 'string' ? text?.substring(0, 100) : JSON.stringify(text)?.substring(0, 100) || 'N/A'}...`);
|
|
187
183
|
debugLog(`hasFollowingResult: ${hasFollowingResult}, isLastInBatch: ${isLastInBatch}`);
|
|
188
184
|
|
|
189
185
|
// tool_start는 args를, tool_result는 toolInput을 사용
|
|
@@ -756,38 +752,21 @@ function StandardDisplay({ item, isPending, hasFollowingResult, nextItem, isLast
|
|
|
756
752
|
React.createElement(Text, {
|
|
757
753
|
color: theme.text.secondary
|
|
758
754
|
}, ` ${formattedArgs}`)
|
|
759
|
-
)
|
|
760
|
-
|
|
761
|
-
RENDER_STREAMING_INDICATORS && operations.length > 0 && React.createElement(Box, {
|
|
762
|
-
flexDirection: "column",
|
|
763
|
-
marginLeft: 2,
|
|
764
|
-
marginTop: 1
|
|
765
|
-
},
|
|
766
|
-
operations.map((op, index) =>
|
|
767
|
-
React.createElement(OperationDisplay, {
|
|
768
|
-
key: op.id || index,
|
|
769
|
-
operation: op,
|
|
770
|
-
isPending
|
|
771
|
-
})
|
|
772
|
-
)
|
|
773
|
-
),
|
|
774
|
-
|
|
775
|
-
RENDER_STREAMING_INDICATORS && isPending && item.isStreaming && React.createElement(StreamingIndicator, {
|
|
776
|
-
message: item.streamingMessage || 'Processing...'
|
|
777
|
-
})
|
|
755
|
+
)
|
|
778
756
|
);
|
|
779
757
|
}
|
|
780
758
|
|
|
781
|
-
debugLog(`Rendering default display: icon="${config.icon}", text="${text?.substring(0, 50)}..."`);
|
|
759
|
+
debugLog(`Rendering default display: icon="${config.icon}", text="${typeof text === 'string' ? text?.substring(0, 50) : JSON.stringify(text)?.substring(0, 50) || 'N/A'}..."`);
|
|
782
760
|
debugLog(`marginBottom: ${marginBottom}, marginTop: ${marginTop}`);
|
|
783
761
|
debugLog(`operations count: ${operations.length}`);
|
|
784
762
|
debugLog('---------- StandardDisplay END ----------');
|
|
785
763
|
|
|
786
764
|
// Check if this is a denied tool result
|
|
787
765
|
// Don't display tool_result when denied (already shown in tool_start)
|
|
788
|
-
const
|
|
789
|
-
|
|
790
|
-
|
|
766
|
+
const textStr = typeof text === 'string' ? text : '';
|
|
767
|
+
const isDeniedResult = type === 'tool_result' &&
|
|
768
|
+
(textStr.includes('User denied tool execution') ||
|
|
769
|
+
textStr.includes('User denied code execution'));
|
|
791
770
|
|
|
792
771
|
if (isDeniedResult) {
|
|
793
772
|
// Don't display tool_result when denied (already shown in tool_start)
|
|
@@ -798,41 +777,41 @@ function StandardDisplay({ item, isPending, hasFollowingResult, nextItem, isLast
|
|
|
798
777
|
const isErrorResult = type === 'tool_result' && originalResult?.operation_successful === false;
|
|
799
778
|
const textColor = isErrorResult ? theme.status.error : theme.text.primary;
|
|
800
779
|
|
|
780
|
+
// text가 구조화된 포맷인지 확인 (format_tool_result에서 반환)
|
|
781
|
+
let textContent;
|
|
782
|
+
if (text && typeof text === 'object' && text.type === 'formatted' && Array.isArray(text.parts)) {
|
|
783
|
+
// 구조화된 parts를 렌더링
|
|
784
|
+
textContent = React.createElement(Box, { flexDirection: "row" },
|
|
785
|
+
...text.parts.map((part, idx) =>
|
|
786
|
+
React.createElement(Text, {
|
|
787
|
+
key: idx,
|
|
788
|
+
color: part.style?.color || textColor,
|
|
789
|
+
bold: part.style?.bold || false
|
|
790
|
+
}, part.text)
|
|
791
|
+
)
|
|
792
|
+
);
|
|
793
|
+
} else {
|
|
794
|
+
// 일반 문자열
|
|
795
|
+
textContent = React.createElement(Text, {
|
|
796
|
+
color: textColor
|
|
797
|
+
}, text);
|
|
798
|
+
}
|
|
799
|
+
|
|
801
800
|
return React.createElement(Box, { flexDirection: "column", marginBottom, marginTop, marginLeft: 2 },
|
|
802
801
|
React.createElement(Box, { flexDirection: "row" },
|
|
803
802
|
React.createElement(Text, { color: config.color, bold: config.bold }, config.icon),
|
|
804
803
|
React.createElement(Box, { flexDirection: "column", flexGrow: 1 },
|
|
805
|
-
|
|
806
|
-
color: textColor
|
|
807
|
-
}, text)
|
|
804
|
+
textContent
|
|
808
805
|
)
|
|
809
|
-
)
|
|
810
|
-
|
|
811
|
-
RENDER_STREAMING_INDICATORS && operations.length > 0 && React.createElement(Box, {
|
|
812
|
-
flexDirection: "column",
|
|
813
|
-
marginLeft: 2,
|
|
814
|
-
marginTop: 1
|
|
815
|
-
},
|
|
816
|
-
operations.map((op, index) =>
|
|
817
|
-
React.createElement(OperationDisplay, {
|
|
818
|
-
key: op.id || index,
|
|
819
|
-
operation: op,
|
|
820
|
-
isPending
|
|
821
|
-
})
|
|
822
|
-
)
|
|
823
|
-
),
|
|
824
|
-
|
|
825
|
-
RENDER_STREAMING_INDICATORS && isPending && item.isStreaming && React.createElement(StreamingIndicator, {
|
|
826
|
-
message: item.streamingMessage || 'Processing...'
|
|
827
|
-
})
|
|
806
|
+
)
|
|
828
807
|
);
|
|
829
808
|
}
|
|
830
809
|
|
|
831
|
-
export function
|
|
832
|
-
debugLog('==========
|
|
810
|
+
export function ConversationItem({ item, isPending = false, terminalWidth, nextItem, isLastInBatch = false }) {
|
|
811
|
+
debugLog('========== ConversationItem RENDER ==========');
|
|
833
812
|
debugLog(`Item type: ${item.type}`);
|
|
834
813
|
debugLog(`Item toolName: ${item.toolName || 'N/A'}`);
|
|
835
|
-
debugLog(`Item text: ${item.text?.substring(0, 100) || 'N/A'}...`);
|
|
814
|
+
debugLog(`Item text: ${typeof item.text === 'string' ? item.text?.substring(0, 100) : JSON.stringify(item.text)?.substring(0, 100) || 'N/A'}...`);
|
|
836
815
|
debugLog(`isPending: ${isPending}, isLastInBatch: ${isLastInBatch}`);
|
|
837
816
|
debugLog(`nextItem: ${nextItem ? nextItem.type : 'null'}`);
|
|
838
817
|
|
|
@@ -856,34 +835,3 @@ export function HistoryItemDisplay({ item, isPending = false, terminalWidth, nex
|
|
|
856
835
|
|
|
857
836
|
return result;
|
|
858
837
|
}
|
|
859
|
-
|
|
860
|
-
const OPERATION_STATUS = {
|
|
861
|
-
running: { icon: '⟳', color: theme.status.info },
|
|
862
|
-
completed: { icon: '✓', color: theme.status.success },
|
|
863
|
-
error: { icon: '✗', color: theme.status.error },
|
|
864
|
-
pending: { icon: '○', color: theme.text.secondary },
|
|
865
|
-
default: { icon: '•', color: theme.text.secondary }
|
|
866
|
-
};
|
|
867
|
-
|
|
868
|
-
function getOperationStatus(status) {
|
|
869
|
-
return OPERATION_STATUS[status] || OPERATION_STATUS.default;
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
function OperationDisplay({ operation, isPending }) {
|
|
873
|
-
const { name, status, description, progress } = operation;
|
|
874
|
-
const statusConfig = getOperationStatus(status);
|
|
875
|
-
|
|
876
|
-
return React.createElement(Box, { flexDirection: "column", marginBottom: 0 },
|
|
877
|
-
React.createElement(Box, { flexDirection: "row" },
|
|
878
|
-
React.createElement(Text, { color: statusConfig.color }, `${statusConfig.icon} `),
|
|
879
|
-
React.createElement(Text, { color: theme.text.primary }, name),
|
|
880
|
-
description && React.createElement(Text, {
|
|
881
|
-
color: theme.text.secondary,
|
|
882
|
-
dimColor: true
|
|
883
|
-
}, ` - ${description}`)
|
|
884
|
-
),
|
|
885
|
-
progress && React.createElement(Box, { marginLeft: 2 },
|
|
886
|
-
React.createElement(Text, { color: theme.text.secondary }, progress)
|
|
887
|
-
)
|
|
888
|
-
);
|
|
889
|
-
}
|
|
@@ -4,17 +4,15 @@
|
|
|
4
4
|
|
|
5
5
|
import React from 'react';
|
|
6
6
|
import { Box, Text } from 'ink';
|
|
7
|
-
import { theme } from '../
|
|
7
|
+
import { theme } from '../design/themeColors.js';
|
|
8
8
|
|
|
9
9
|
export function CurrentModelView({ provider, modelId, modelInfo }) {
|
|
10
10
|
const providerColors = {
|
|
11
|
-
openai: 'cyan'
|
|
12
|
-
anthropic: 'magenta'
|
|
11
|
+
openai: 'cyan'
|
|
13
12
|
};
|
|
14
13
|
|
|
15
14
|
const providerEmojis = {
|
|
16
|
-
openai: '🤖'
|
|
17
|
-
anthropic: '🧠'
|
|
15
|
+
openai: '🤖'
|
|
18
16
|
};
|
|
19
17
|
|
|
20
18
|
return React.createElement(Box, {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import React from 'react';
|
|
6
6
|
import { Box, Text } from 'ink';
|
|
7
|
-
import { theme } from '../
|
|
7
|
+
import { theme } from '../design/themeColors.js';
|
|
8
8
|
import { OPENAI_MODELS } from '../../config/openai_models.js';
|
|
9
9
|
|
|
10
10
|
export function Footer({ model = 'gpt-4', reasoningEffort = null, cwd = process.cwd() }) {
|
|
@@ -18,10 +18,8 @@ export function Footer({ model = 'gpt-4', reasoningEffort = null, cwd = process.
|
|
|
18
18
|
displayModel = `${model} (${reasoningEffort})`;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
return React.createElement(Box, {
|
|
22
|
-
React.createElement(
|
|
23
|
-
|
|
24
|
-
React.createElement(Box, null,
|
|
25
|
-
React.createElement(Text, { color: theme.text.accent }, displayModel))
|
|
21
|
+
return React.createElement(Box, { paddingX: 1, marginTop: 0, gap: 1 },
|
|
22
|
+
React.createElement(Text, { color: theme.brand.dark }, displayModel),
|
|
23
|
+
React.createElement(Text, { color: theme.brand.dark }, displayCwd)
|
|
26
24
|
);
|
|
27
25
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import React from 'react';
|
|
6
6
|
import { Box, Text } from 'ink';
|
|
7
7
|
import Gradient from 'ink-gradient';
|
|
8
|
-
import { theme } from '../
|
|
8
|
+
import { theme } from '../design/themeColors.js';
|
|
9
9
|
|
|
10
10
|
// const ASCII_LOGO = `
|
|
11
11
|
// d8888 8888888 8888888888 Y88b d88P 8888888888 888
|
|
@@ -27,10 +27,7 @@ export function Header({ version = '1.0.0' }) {
|
|
|
27
27
|
by 코드깎는노인
|
|
28
28
|
`.split('\n').map(line => line.trim()).join("\n");
|
|
29
29
|
return React.createElement(Box, { flexDirection: "column", marginBottom: 1, marginLeft: 2 },
|
|
30
|
-
theme.
|
|
31
|
-
? React.createElement(Gradient, { colors: theme.ui.gradient },
|
|
32
|
-
React.createElement(Text, null, ASCII_LOGO))
|
|
33
|
-
: React.createElement(Text, { color: theme.text.accent }, ASCII_LOGO),
|
|
30
|
+
React.createElement(Text, { color: theme.brand.light }, ASCII_LOGO),
|
|
34
31
|
React.createElement(Box, { justifyContent: "flex-left" },
|
|
35
32
|
React.createElement(Text, { color: theme.text.secondary }, `AIEXEcode v${version}`)
|
|
36
33
|
)
|
|
@@ -1,26 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Advanced
|
|
2
|
+
* Advanced Input component with multi-line support
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import React, { useCallback, useState, useRef, useEffect, useMemo } from 'react';
|
|
6
6
|
import { Box, Text } from 'ink';
|
|
7
|
-
import { theme } from '../
|
|
7
|
+
import { theme } from '../design/themeColors.js';
|
|
8
8
|
import { useKeypress, keyMatchers, Command } from '../hooks/useKeypress.js';
|
|
9
9
|
import { useCompletion } from '../hooks/useCompletion.js';
|
|
10
|
-
import {
|
|
11
|
-
import { cpSlice, cpLen } from '../utils/
|
|
10
|
+
import { AutocompleteMenu } from './AutocompleteMenu.js';
|
|
11
|
+
import { cpSlice, cpLen } from '../utils/inputBuffer.js';
|
|
12
12
|
import { uiEvents } from '../../system/ui_events.js';
|
|
13
13
|
import chalk from 'chalk';
|
|
14
14
|
import stringWidth from 'string-width';
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
function InputPromptComponent({ buffer, onSubmit, onClearScreen, onExit, commands = [], placeholder = ' Type your message...', focus = true, isSessionRunning = false }) {
|
|
19
|
-
const [ctrlCPressedOnce, setCtrlCPressedOnce] = useState(false);
|
|
20
|
-
const [ctrlDPressedOnce, setCtrlDPressedOnce] = useState(false);
|
|
16
|
+
function InputPromptComponent({ buffer, onSubmit, onClearScreen, onExit, commands = [], placeholder = ' What should I do?', focus = true, isSessionRunning = false }) {
|
|
21
17
|
const [isExiting, setIsExiting] = useState(false);
|
|
22
|
-
const ctrlCTimerRef = useRef(null);
|
|
23
|
-
const ctrlDTimerRef = useRef(null);
|
|
24
18
|
|
|
25
19
|
const completionRaw = useCompletion(buffer, commands);
|
|
26
20
|
|
|
@@ -35,28 +29,6 @@ function InputPromptComponent({ buffer, onSubmit, onClearScreen, onExit, command
|
|
|
35
29
|
]);
|
|
36
30
|
|
|
37
31
|
|
|
38
|
-
const handleExit = useCallback((
|
|
39
|
-
pressedOnce,
|
|
40
|
-
setPressedOnce,
|
|
41
|
-
timerRef
|
|
42
|
-
) => {
|
|
43
|
-
if (pressedOnce) {
|
|
44
|
-
if (timerRef.current) {
|
|
45
|
-
clearTimeout(timerRef.current);
|
|
46
|
-
}
|
|
47
|
-
// Always call onExit callback
|
|
48
|
-
if (onExit) {
|
|
49
|
-
onExit();
|
|
50
|
-
}
|
|
51
|
-
} else {
|
|
52
|
-
setPressedOnce(true);
|
|
53
|
-
timerRef.current = setTimeout(() => {
|
|
54
|
-
setPressedOnce(false);
|
|
55
|
-
timerRef.current = null;
|
|
56
|
-
}, CTRL_EXIT_PROMPT_DURATION_MS);
|
|
57
|
-
}
|
|
58
|
-
}, [onExit]);
|
|
59
|
-
|
|
60
32
|
const handleSubmitAndClear = useCallback((submittedValue) => {
|
|
61
33
|
buffer.setText('');
|
|
62
34
|
onSubmit(submittedValue);
|
|
@@ -86,7 +58,11 @@ function InputPromptComponent({ buffer, onSubmit, onClearScreen, onExit, command
|
|
|
86
58
|
|
|
87
59
|
if (keyMatchers[Command.EXIT](key)) {
|
|
88
60
|
if (buffer.text.length > 0) return;
|
|
89
|
-
|
|
61
|
+
// Direct exit
|
|
62
|
+
setIsExiting(true);
|
|
63
|
+
if (onExit) {
|
|
64
|
+
onExit();
|
|
65
|
+
}
|
|
90
66
|
return;
|
|
91
67
|
}
|
|
92
68
|
|
|
@@ -244,23 +220,14 @@ function InputPromptComponent({ buffer, onSubmit, onClearScreen, onExit, command
|
|
|
244
220
|
buffer,
|
|
245
221
|
onClearScreen,
|
|
246
222
|
handleSubmitAndClear,
|
|
247
|
-
ctrlCPressedOnce,
|
|
248
|
-
ctrlDPressedOnce,
|
|
249
|
-
handleExit,
|
|
250
223
|
completion,
|
|
251
224
|
isSessionRunning,
|
|
252
|
-
isExiting
|
|
225
|
+
isExiting,
|
|
226
|
+
onExit
|
|
253
227
|
]);
|
|
254
228
|
|
|
255
229
|
useKeypress(handleInput, { isActive: focus });
|
|
256
230
|
|
|
257
|
-
useEffect(() => {
|
|
258
|
-
return () => {
|
|
259
|
-
if (ctrlCTimerRef.current) clearTimeout(ctrlCTimerRef.current);
|
|
260
|
-
if (ctrlDTimerRef.current) clearTimeout(ctrlDTimerRef.current);
|
|
261
|
-
};
|
|
262
|
-
}, []);
|
|
263
|
-
|
|
264
231
|
// Extract all needed values in one go to avoid multiple getter calls
|
|
265
232
|
const linesToRender = useMemo(() => buffer.viewportVisualLines, [buffer.viewportVisualLines]);
|
|
266
233
|
const [cursorVisualRowAbsolute, cursorVisualColAbsolute] = useMemo(() => buffer.visualCursor, [buffer.visualCursor]);
|
|
@@ -269,14 +236,9 @@ function InputPromptComponent({ buffer, onSubmit, onClearScreen, onExit, command
|
|
|
269
236
|
const bufferTextLength = useMemo(() => buffer.text.length, [buffer.text]);
|
|
270
237
|
|
|
271
238
|
return React.createElement(Box, { flexDirection: "column", flexGrow: 1 },
|
|
272
|
-
ctrlCPressedOnce && React.createElement(Text, { color: theme.status.warning },
|
|
273
|
-
'Press Ctrl+C again to exit.'),
|
|
274
|
-
ctrlDPressedOnce && React.createElement(Text, { color: theme.status.warning },
|
|
275
|
-
'Press Ctrl+D again to exit.'),
|
|
276
|
-
|
|
277
239
|
React.createElement(Box, {
|
|
278
240
|
borderStyle: "round",
|
|
279
|
-
borderColor:
|
|
241
|
+
borderColor: theme.brand.light,
|
|
280
242
|
paddingX: 1,
|
|
281
243
|
flexDirection: "row",
|
|
282
244
|
alignItems: "flex-start",
|
|
@@ -285,7 +247,7 @@ function InputPromptComponent({ buffer, onSubmit, onClearScreen, onExit, command
|
|
|
285
247
|
flexShrink: 0 // Prevent input from being squeezed by other elements
|
|
286
248
|
// Don't set explicit width - let Ink calculate it
|
|
287
249
|
},
|
|
288
|
-
React.createElement(Text, { color: theme.
|
|
250
|
+
React.createElement(Text, { color: theme.brand.light }, '> '),
|
|
289
251
|
React.createElement(Box, { flexGrow: 1, flexDirection: "column" },
|
|
290
252
|
bufferTextLength === 0 && placeholder
|
|
291
253
|
? (focus
|
|
@@ -316,7 +278,7 @@ function InputPromptComponent({ buffer, onSubmit, onClearScreen, onExit, command
|
|
|
316
278
|
)
|
|
317
279
|
),
|
|
318
280
|
|
|
319
|
-
completion.showSuggestions && React.createElement(
|
|
281
|
+
completion.showSuggestions && React.createElement(AutocompleteMenu, {
|
|
320
282
|
suggestions: completion.suggestions,
|
|
321
283
|
activeIndex: completion.activeSuggestionIndex
|
|
322
284
|
})
|
|
@@ -325,4 +287,4 @@ function InputPromptComponent({ buffer, onSubmit, onClearScreen, onExit, command
|
|
|
325
287
|
|
|
326
288
|
// Don't memoize - buffer state changes frequently and we need to react to it
|
|
327
289
|
// The buffer object reference is stable (from useRef), but its internal state changes
|
|
328
|
-
export const
|
|
290
|
+
export const Input = InputPromptComponent;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model List View Component - Ink-based UI for displaying available models
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import { Box, Text } from 'ink';
|
|
7
|
+
import { theme } from '../design/themeColors.js';
|
|
8
|
+
|
|
9
|
+
export function ModelListView({ openaiModels }) {
|
|
10
|
+
const sections = [
|
|
11
|
+
// Header
|
|
12
|
+
React.createElement(Box, {
|
|
13
|
+
key: 'header',
|
|
14
|
+
borderStyle: 'double',
|
|
15
|
+
borderColor: 'gray',
|
|
16
|
+
paddingX: 2,
|
|
17
|
+
paddingY: 0,
|
|
18
|
+
justifyContent: 'center'
|
|
19
|
+
},
|
|
20
|
+
React.createElement(Text, {
|
|
21
|
+
bold: true,
|
|
22
|
+
color: 'whiteBright'
|
|
23
|
+
}, 'Available AI Models')
|
|
24
|
+
),
|
|
25
|
+
|
|
26
|
+
React.createElement(Text, { key: 'spacer1' }, null),
|
|
27
|
+
|
|
28
|
+
// OpenAI Section
|
|
29
|
+
React.createElement(Box, {
|
|
30
|
+
key: 'openai-section',
|
|
31
|
+
flexDirection: 'column',
|
|
32
|
+
borderStyle: 'round',
|
|
33
|
+
borderColor: 'gray',
|
|
34
|
+
paddingX: 2,
|
|
35
|
+
paddingY: 1
|
|
36
|
+
},
|
|
37
|
+
React.createElement(Box, { flexDirection: 'column' },
|
|
38
|
+
React.createElement(Text, {
|
|
39
|
+
bold: true,
|
|
40
|
+
color: 'cyan'
|
|
41
|
+
}, '🤖 OpenAI Models'),
|
|
42
|
+
React.createElement(Text, {
|
|
43
|
+
dimColor: true,
|
|
44
|
+
italic: true
|
|
45
|
+
}, 'GPT-5 Series (Latest generation, recommended)'),
|
|
46
|
+
React.createElement(Text, null)
|
|
47
|
+
),
|
|
48
|
+
|
|
49
|
+
openaiModels.map((model, index) =>
|
|
50
|
+
React.createElement(Box, {
|
|
51
|
+
key: model.id,
|
|
52
|
+
flexDirection: 'column',
|
|
53
|
+
marginBottom: index < openaiModels.length - 1 ? 1 : 0
|
|
54
|
+
},
|
|
55
|
+
React.createElement(Box, { flexDirection: 'row', gap: 1 },
|
|
56
|
+
React.createElement(Text, { color: 'green', bold: true }, '•'),
|
|
57
|
+
React.createElement(Text, {
|
|
58
|
+
color: 'white',
|
|
59
|
+
bold: true
|
|
60
|
+
}, model.id)
|
|
61
|
+
),
|
|
62
|
+
React.createElement(Box, {
|
|
63
|
+
flexDirection: 'column',
|
|
64
|
+
marginLeft: 2,
|
|
65
|
+
gap: 0
|
|
66
|
+
},
|
|
67
|
+
React.createElement(Text, {
|
|
68
|
+
color: theme.text.secondary
|
|
69
|
+
}, `${model.name} - ${model.description}`),
|
|
70
|
+
React.createElement(Text, {
|
|
71
|
+
dimColor: true,
|
|
72
|
+
italic: true
|
|
73
|
+
}, `💰 $${model.pricing.input}/$${model.pricing.output} (input/output per 1M tokens)`)
|
|
74
|
+
)
|
|
75
|
+
)
|
|
76
|
+
)
|
|
77
|
+
)
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
sections.push(
|
|
81
|
+
React.createElement(Text, { key: 'spacer4' }, null),
|
|
82
|
+
|
|
83
|
+
// Footer
|
|
84
|
+
React.createElement(Box, {
|
|
85
|
+
key: 'footer',
|
|
86
|
+
flexDirection: 'column',
|
|
87
|
+
borderStyle: 'single',
|
|
88
|
+
borderColor: 'gray',
|
|
89
|
+
paddingX: 2,
|
|
90
|
+
paddingY: 1
|
|
91
|
+
},
|
|
92
|
+
React.createElement(Text, { bold: true }, '📖 Usage'),
|
|
93
|
+
React.createElement(Text, null, ' /model <model-id>'),
|
|
94
|
+
React.createElement(Text, { dimColor: true }, ' Example: /model gpt-5'),
|
|
95
|
+
React.createElement(Text, null),
|
|
96
|
+
React.createElement(Text, { bold: true }, '🔗 More Info'),
|
|
97
|
+
React.createElement(Text, { color: theme.text.link }, ' OpenAI: https://platform.openai.com/docs/pricing')
|
|
98
|
+
)
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
return React.createElement(Box, {
|
|
102
|
+
flexDirection: 'column',
|
|
103
|
+
paddingX: 2,
|
|
104
|
+
paddingY: 1
|
|
105
|
+
}, ...sections);
|
|
106
|
+
}
|
|
@@ -4,17 +4,15 @@
|
|
|
4
4
|
|
|
5
5
|
import React from 'react';
|
|
6
6
|
import { Box, Text } from 'ink';
|
|
7
|
-
import { theme } from '../
|
|
7
|
+
import { theme } from '../design/themeColors.js';
|
|
8
8
|
|
|
9
9
|
export function ModelUpdatedView({ provider, modelId, modelInfo, settingsFile, warning }) {
|
|
10
10
|
const providerColors = {
|
|
11
|
-
openai: 'cyan'
|
|
12
|
-
anthropic: 'magenta'
|
|
11
|
+
openai: 'cyan'
|
|
13
12
|
};
|
|
14
13
|
|
|
15
14
|
const providerEmojis = {
|
|
16
|
-
openai: '🤖'
|
|
17
|
-
anthropic: '🧠'
|
|
15
|
+
openai: '🤖'
|
|
18
16
|
};
|
|
19
17
|
|
|
20
18
|
return React.createElement(Box, {
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import React, { useState, useEffect, memo } from 'react';
|
|
7
7
|
import { Box, Text } from 'ink';
|
|
8
8
|
import Spinner from 'ink-spinner';
|
|
9
|
-
import { theme } from '../
|
|
9
|
+
import { theme } from '../design/themeColors.js';
|
|
10
10
|
|
|
11
11
|
// Internal component that handles the animation
|
|
12
12
|
const SpinnerContent = memo(function SpinnerContent({ message, elapsedSeconds }) {
|
|
@@ -26,11 +26,11 @@ const SpinnerContent = memo(function SpinnerContent({ message, elapsedSeconds })
|
|
|
26
26
|
marginBottom: 0
|
|
27
27
|
},
|
|
28
28
|
React.createElement(Box, { marginRight: 1 },
|
|
29
|
-
React.createElement(Text, { color: theme.
|
|
29
|
+
React.createElement(Text, { color: theme.brand.light },
|
|
30
30
|
React.createElement(Spinner, { type: 'dots' })
|
|
31
31
|
)
|
|
32
32
|
),
|
|
33
|
-
React.createElement(Text, { color: theme.
|
|
33
|
+
React.createElement(Text, { color: theme.brand.light }, message),
|
|
34
34
|
React.createElement(Text, { color: theme.text.secondary },
|
|
35
35
|
` (${formatTime(elapsedSeconds)})`
|
|
36
36
|
)
|