thatgfsj-code 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cmd/index.js +56 -8
- package/dist/cmd/index.js.map +1 -1
- package/dist/llm/anthropic.d.ts +4 -17
- package/dist/llm/anthropic.d.ts.map +1 -1
- package/dist/llm/anthropic.js +54 -12
- package/dist/llm/anthropic.js.map +1 -1
- package/dist/llm/gemini.d.ts +3 -16
- package/dist/llm/gemini.d.ts.map +1 -1
- package/dist/llm/gemini.js +26 -22
- package/dist/llm/gemini.js.map +1 -1
- package/dist/llm/index.d.ts +5 -19
- package/dist/llm/index.d.ts.map +1 -1
- package/dist/llm/index.js +87 -87
- package/dist/llm/index.js.map +1 -1
- package/dist/llm/openai.d.ts +9 -23
- package/dist/llm/openai.d.ts.map +1 -1
- package/dist/llm/openai.js +48 -14
- package/dist/llm/openai.js.map +1 -1
- package/dist/llm/provider.d.ts +11 -16
- package/dist/llm/provider.d.ts.map +1 -1
- package/dist/tui/input.d.ts +0 -1
- package/dist/tui/input.d.ts.map +1 -1
- package/dist/tui/input.js +2 -5
- package/dist/tui/input.js.map +1 -1
- package/dist/tui/output.d.ts +27 -12
- package/dist/tui/output.d.ts.map +1 -1
- package/dist/tui/output.js +176 -40
- package/dist/tui/output.js.map +1 -1
- package/dist/tui/repl.d.ts +0 -10
- package/dist/tui/repl.d.ts.map +1 -1
- package/dist/tui/repl.js +76 -42
- package/dist/tui/repl.js.map +1 -1
- package/dist/tui/welcome.d.ts +1 -11
- package/dist/tui/welcome.d.ts.map +1 -1
- package/dist/tui/welcome.js +60 -56
- package/dist/tui/welcome.js.map +1 -1
- package/package.json +1 -1
- package/src/cmd/index.ts +49 -8
- package/src/llm/anthropic.ts +60 -14
- package/src/llm/gemini.ts +31 -24
- package/src/llm/index.ts +94 -92
- package/src/llm/openai.ts +58 -15
- package/src/llm/provider.ts +12 -16
- package/src/tui/input.ts +2 -5
- package/src/tui/output.ts +193 -40
- package/src/tui/repl.ts +82 -43
- package/src/tui/welcome.ts +62 -59
package/src/tui/output.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* REPL Output
|
|
3
|
-
* Migrated from old src/repl/output.ts
|
|
2
|
+
* REPL Output - Beautiful terminal UI
|
|
4
3
|
*/
|
|
5
4
|
|
|
6
5
|
import chalk from 'chalk';
|
|
@@ -8,76 +7,230 @@ import ora, { Ora } from 'ora';
|
|
|
8
7
|
|
|
9
8
|
export class REPLOutput {
|
|
10
9
|
private spinner: Ora | null = null;
|
|
10
|
+
private inAssistantBlock = false;
|
|
11
|
+
private currentLineHasContent = false;
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
// ── Banner ──────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
printBanner(): void {
|
|
16
|
+
console.log();
|
|
17
|
+
console.log(chalk.cyan.bold(' ⚡ Thatgfsj Code') + chalk.gray(' v0.4.1'));
|
|
18
|
+
console.log(chalk.gray(' AI Coding Assistant'));
|
|
19
|
+
console.log(chalk.gray(' ' + '─'.repeat(52)));
|
|
20
|
+
console.log();
|
|
14
21
|
}
|
|
15
22
|
|
|
16
|
-
|
|
17
|
-
|
|
23
|
+
// ── Prompt ──────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
getPrompt(): string {
|
|
26
|
+
return chalk.cyan.bold('❯ ');
|
|
18
27
|
}
|
|
19
28
|
|
|
20
|
-
|
|
21
|
-
|
|
29
|
+
// ── User Input ──────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
printUserInput(input: string): void {
|
|
32
|
+
console.log();
|
|
33
|
+
console.log(chalk.gray(' ┌─ ') + chalk.bold('You'));
|
|
34
|
+
for (const line of input.split('\n')) {
|
|
35
|
+
console.log(chalk.gray(' │ ') + line);
|
|
36
|
+
}
|
|
37
|
+
console.log(chalk.gray(' └─'));
|
|
22
38
|
}
|
|
23
39
|
|
|
24
|
-
|
|
25
|
-
|
|
40
|
+
// ── Assistant Streaming ─────────────────────────────────
|
|
41
|
+
|
|
42
|
+
beginAssistant(): void {
|
|
43
|
+
if (this.inAssistantBlock) return;
|
|
44
|
+
this.inAssistantBlock = true;
|
|
45
|
+
this.currentLineHasContent = false;
|
|
46
|
+
console.log();
|
|
47
|
+
process.stdout.write(chalk.cyan(' ┌─ ') + chalk.bold('AI') + '\n' + chalk.cyan(' │ '));
|
|
26
48
|
}
|
|
27
49
|
|
|
28
|
-
|
|
29
|
-
|
|
50
|
+
writeChunk(chunk: string): void {
|
|
51
|
+
if (!this.inAssistantBlock) this.beginAssistant();
|
|
52
|
+
|
|
53
|
+
for (const char of chunk) {
|
|
54
|
+
if (char === '\n') {
|
|
55
|
+
process.stdout.write('\n' + chalk.cyan(' │ '));
|
|
56
|
+
this.currentLineHasContent = false;
|
|
57
|
+
} else {
|
|
58
|
+
process.stdout.write(char);
|
|
59
|
+
this.currentLineHasContent = true;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
30
62
|
}
|
|
31
63
|
|
|
32
|
-
|
|
33
|
-
this.
|
|
34
|
-
|
|
35
|
-
|
|
64
|
+
endAssistant(): void {
|
|
65
|
+
if (!this.inAssistantBlock) return;
|
|
66
|
+
if (this.currentLineHasContent) {
|
|
67
|
+
process.stdout.write('\n');
|
|
68
|
+
}
|
|
69
|
+
process.stdout.write(chalk.cyan(' └─') + '\n\n');
|
|
70
|
+
this.inAssistantBlock = false;
|
|
71
|
+
this.currentLineHasContent = false;
|
|
36
72
|
}
|
|
37
73
|
|
|
38
|
-
|
|
39
|
-
|
|
74
|
+
// ── Tool Calls ──────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
printToolCall(name: string, args: string): void {
|
|
77
|
+
if (this.inAssistantBlock) this.endAssistant();
|
|
78
|
+
|
|
79
|
+
console.log();
|
|
80
|
+
console.log(chalk.yellow(' ⚙ ') + chalk.bold(name) + chalk.gray(' ─────────────────────────'));
|
|
81
|
+
|
|
82
|
+
if (args) {
|
|
83
|
+
try {
|
|
84
|
+
const obj = JSON.parse(args);
|
|
85
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
86
|
+
let display: string;
|
|
87
|
+
if (typeof val === 'string') {
|
|
88
|
+
display = val.length > 120 ? val.slice(0, 120) + '...' : val;
|
|
89
|
+
} else {
|
|
90
|
+
display = JSON.stringify(val);
|
|
91
|
+
if (display.length > 120) display = display.slice(0, 120) + '...';
|
|
92
|
+
}
|
|
93
|
+
console.log(chalk.gray(' ' + key + ': ') + chalk.white(display));
|
|
94
|
+
}
|
|
95
|
+
} catch {
|
|
96
|
+
const display = args.length > 150 ? args.slice(0, 150) + '...' : args;
|
|
97
|
+
console.log(chalk.gray(' ') + display);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
printToolResult(output: string, isError = false): void {
|
|
103
|
+
if (!output) return;
|
|
104
|
+
|
|
105
|
+
const lines = output.split('\n');
|
|
106
|
+
const maxLines = 25;
|
|
107
|
+
const display = lines.slice(0, maxLines);
|
|
108
|
+
|
|
109
|
+
for (const line of display) {
|
|
110
|
+
if (isError) {
|
|
111
|
+
console.log(chalk.red(' ✖ ') + line);
|
|
112
|
+
} else {
|
|
113
|
+
console.log(chalk.gray(' │ ') + line);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (lines.length > maxLines) {
|
|
118
|
+
console.log(chalk.gray(' │ ') + chalk.dim(`... +${lines.length - maxLines} more lines`));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
printToolEnd(): void {
|
|
123
|
+
console.log(chalk.yellow(' ────────────────────────────────────'));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ── Thinking ────────────────────────────────────────────
|
|
127
|
+
|
|
128
|
+
startThinking(): void {
|
|
129
|
+
this.spinner = ora({
|
|
130
|
+
text: chalk.gray('Thinking'),
|
|
131
|
+
color: 'cyan',
|
|
132
|
+
spinner: 'dots',
|
|
133
|
+
}).start();
|
|
40
134
|
}
|
|
41
135
|
|
|
42
|
-
|
|
136
|
+
startExecuting(toolName: string): void {
|
|
137
|
+
this.spinner = ora({
|
|
138
|
+
text: chalk.gray(`Executing ${chalk.yellow(toolName)}`),
|
|
139
|
+
color: 'cyan',
|
|
140
|
+
spinner: 'dots',
|
|
141
|
+
}).start();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
stopThinking(): void {
|
|
43
145
|
if (this.spinner) {
|
|
44
146
|
this.spinner.stop();
|
|
45
147
|
this.spinner = null;
|
|
46
148
|
}
|
|
47
149
|
}
|
|
48
150
|
|
|
49
|
-
|
|
151
|
+
stopThinkingFail(msg: string): void {
|
|
50
152
|
if (this.spinner) {
|
|
51
|
-
this.spinner.fail(
|
|
153
|
+
this.spinner.fail(chalk.red(msg));
|
|
52
154
|
this.spinner = null;
|
|
53
155
|
}
|
|
54
156
|
}
|
|
55
157
|
|
|
158
|
+
// ── Messages ────────────────────────────────────────────
|
|
159
|
+
|
|
160
|
+
info(msg: string): void {
|
|
161
|
+
console.log(chalk.gray(' ') + msg);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
success(msg: string): void {
|
|
165
|
+
console.log(chalk.green(' ✅ ') + msg);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
error(msg: string): void {
|
|
169
|
+
console.log();
|
|
170
|
+
console.log(chalk.red(' ┌─ Error'));
|
|
171
|
+
for (const line of msg.split('\n')) {
|
|
172
|
+
console.log(chalk.red(' │ ') + line);
|
|
173
|
+
}
|
|
174
|
+
console.log(chalk.red(' └─'));
|
|
175
|
+
console.log();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
warning(msg: string): void {
|
|
179
|
+
console.log(chalk.yellow(' ⚠ ') + msg);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
dim(msg: string): void {
|
|
183
|
+
console.log(chalk.dim(' ' + msg));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ── Layout ──────────────────────────────────────────────
|
|
187
|
+
|
|
188
|
+
divider(): void {
|
|
189
|
+
console.log(chalk.gray(' ' + '─'.repeat(52)));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
spacer(): void {
|
|
193
|
+
console.log();
|
|
194
|
+
}
|
|
195
|
+
|
|
56
196
|
clear(): void {
|
|
57
197
|
console.clear();
|
|
58
198
|
}
|
|
59
199
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
200
|
+
// ── Sections ────────────────────────────────────────────
|
|
201
|
+
|
|
202
|
+
section(title: string, items: Array<{ label: string; value: string }>): void {
|
|
203
|
+
console.log();
|
|
204
|
+
console.log(chalk.bold(' ' + title));
|
|
205
|
+
this.divider();
|
|
206
|
+
for (const item of items) {
|
|
207
|
+
console.log(' ' + chalk.gray(item.label.padEnd(14)) + chalk.white(item.value));
|
|
208
|
+
}
|
|
209
|
+
console.log();
|
|
67
210
|
}
|
|
68
211
|
|
|
212
|
+
// ── Help ────────────────────────────────────────────────
|
|
213
|
+
|
|
69
214
|
printHelp(): void {
|
|
70
|
-
console.log(
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
215
|
+
console.log();
|
|
216
|
+
console.log(chalk.bold(' Commands'));
|
|
217
|
+
this.divider();
|
|
218
|
+
const cmds: [string, string][] = [
|
|
219
|
+
['help', 'Show this help'],
|
|
220
|
+
['tools', 'List available tools'],
|
|
221
|
+
['model', 'Show current model info'],
|
|
222
|
+
['clear', 'Clear screen'],
|
|
223
|
+
['exit', 'Exit'],
|
|
224
|
+
];
|
|
225
|
+
for (const [cmd, desc] of cmds) {
|
|
226
|
+
console.log(' ' + chalk.cyan(cmd.padEnd(14)) + chalk.gray(desc));
|
|
227
|
+
}
|
|
228
|
+
console.log();
|
|
229
|
+
console.log(chalk.bold(' Keyboard'));
|
|
230
|
+
this.divider();
|
|
231
|
+
console.log(' ' + chalk.cyan('↑ / ↓'.padEnd(14)) + chalk.gray('Browse command history'));
|
|
232
|
+
console.log(' ' + chalk.cyan('Tab'.padEnd(14)) + chalk.gray('Auto-complete'));
|
|
233
|
+
console.log(' ' + chalk.cyan('Ctrl+C'.padEnd(14)) + chalk.gray('Exit'));
|
|
234
|
+
console.log();
|
|
82
235
|
}
|
|
83
236
|
}
|
package/src/tui/repl.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* REPL - Main interactive loop
|
|
3
|
-
* Migrated from old src/repl/loop.ts + src/index.ts interactive mode
|
|
4
3
|
*/
|
|
5
4
|
|
|
6
5
|
import chalk from 'chalk';
|
|
@@ -21,53 +20,45 @@ export class REPL {
|
|
|
21
20
|
this.app = app;
|
|
22
21
|
}
|
|
23
22
|
|
|
24
|
-
/**
|
|
25
|
-
* Start the REPL
|
|
26
|
-
*/
|
|
27
23
|
async start(): Promise<void> {
|
|
28
24
|
this.running = true;
|
|
29
25
|
|
|
30
|
-
// Show banner
|
|
31
26
|
this.output.clear();
|
|
32
27
|
this.output.printBanner();
|
|
33
|
-
this.output.printInfo('Type "help" for available commands\n');
|
|
34
|
-
this.output.printDivider();
|
|
35
28
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
29
|
+
const cfg = this.app.config.get();
|
|
30
|
+
this.output.section('Session', [
|
|
31
|
+
{ label: 'Provider', value: cfg.provider },
|
|
32
|
+
{ label: 'Model', value: cfg.model },
|
|
33
|
+
{ label: 'Project', value: ProjectContext.detectType() },
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
this.output.info('Type ' + chalk.cyan('help') + ' for commands');
|
|
37
|
+
this.output.spacer();
|
|
40
38
|
|
|
41
|
-
// Main loop
|
|
42
39
|
while (this.running) {
|
|
43
40
|
try {
|
|
44
|
-
const userInput = await this.input.prompt();
|
|
45
|
-
|
|
41
|
+
const userInput = await this.input.prompt(this.output.getPrompt());
|
|
46
42
|
if (!userInput.trim()) continue;
|
|
47
43
|
|
|
48
|
-
// Handle commands
|
|
49
44
|
const handled = await this.handleCommand(userInput.trim());
|
|
50
45
|
if (handled) continue;
|
|
51
46
|
|
|
52
|
-
// Process with AI
|
|
53
47
|
await this.processInput(userInput.trim());
|
|
54
48
|
} catch (error: any) {
|
|
55
49
|
if (error.message === 'SIGINT') continue;
|
|
56
|
-
this.output.
|
|
50
|
+
this.output.error(error.message);
|
|
57
51
|
}
|
|
58
52
|
}
|
|
59
53
|
}
|
|
60
54
|
|
|
61
|
-
/**
|
|
62
|
-
* Handle built-in commands
|
|
63
|
-
*/
|
|
64
55
|
private async handleCommand(input: string): Promise<boolean> {
|
|
65
56
|
const cmd = input.toLowerCase();
|
|
66
57
|
|
|
67
58
|
switch (cmd) {
|
|
68
59
|
case 'exit':
|
|
69
60
|
case 'quit':
|
|
70
|
-
this.output.
|
|
61
|
+
this.output.dim('Goodbye! 👋');
|
|
71
62
|
this.running = false;
|
|
72
63
|
this.input.close();
|
|
73
64
|
return true;
|
|
@@ -82,50 +73,98 @@ export class REPL {
|
|
|
82
73
|
return true;
|
|
83
74
|
|
|
84
75
|
case 'tools':
|
|
85
|
-
this.output.
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
76
|
+
this.output.section('Available Tools',
|
|
77
|
+
this.app.tools.list().map(t => ({
|
|
78
|
+
label: t.name,
|
|
79
|
+
value: t.description,
|
|
80
|
+
}))
|
|
81
|
+
);
|
|
90
82
|
return true;
|
|
91
83
|
|
|
92
|
-
case 'model':
|
|
93
|
-
this.
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
84
|
+
case 'model': {
|
|
85
|
+
const c = this.app.config.get();
|
|
86
|
+
this.output.section('Current Model', [
|
|
87
|
+
{ label: 'Provider', value: c.provider },
|
|
88
|
+
{ label: 'Model', value: c.model },
|
|
89
|
+
{ label: 'Base URL', value: c.baseUrl || 'default' },
|
|
90
|
+
{ label: 'API Key', value: c.apiKey ? '••••' + c.apiKey.slice(-4) : 'not set' },
|
|
91
|
+
]);
|
|
99
92
|
return true;
|
|
93
|
+
}
|
|
100
94
|
|
|
101
95
|
default:
|
|
102
96
|
return false;
|
|
103
97
|
}
|
|
104
98
|
}
|
|
105
99
|
|
|
106
|
-
/**
|
|
107
|
-
* Process user input with AI
|
|
108
|
-
*/
|
|
109
100
|
private async processInput(input: string): Promise<void> {
|
|
110
101
|
this.app.session.addMessage('user', input);
|
|
102
|
+
this.output.printUserInput(input);
|
|
103
|
+
this.output.startThinking();
|
|
111
104
|
|
|
112
|
-
this.output.startSpinner('Thinking...');
|
|
113
105
|
let fullResponse = '';
|
|
106
|
+
let hasStartedOutput = false;
|
|
114
107
|
|
|
115
108
|
try {
|
|
116
109
|
const stream = this.app.getAgent().run(this.app.session.getMessages());
|
|
110
|
+
|
|
117
111
|
for await (const chunk of stream) {
|
|
118
|
-
this.output.
|
|
119
|
-
|
|
112
|
+
this.output.stopThinking();
|
|
113
|
+
|
|
114
|
+
const parts = chunk.split('\n');
|
|
115
|
+
for (const part of parts) {
|
|
116
|
+
// Tool message: @@TOOL@@{json}
|
|
117
|
+
if (part.startsWith('@@TOOL@@')) {
|
|
118
|
+
try {
|
|
119
|
+
const data = JSON.parse(part.slice(8));
|
|
120
|
+
if (data.action === 'call') {
|
|
121
|
+
if (hasStartedOutput) {
|
|
122
|
+
this.output.endAssistant();
|
|
123
|
+
hasStartedOutput = false;
|
|
124
|
+
}
|
|
125
|
+
this.output.printToolCall(data.name, data.args || '');
|
|
126
|
+
// Show executing state
|
|
127
|
+
this.output.startExecuting(data.name);
|
|
128
|
+
} else if (data.action === 'result') {
|
|
129
|
+
this.output.stopThinking();
|
|
130
|
+
this.output.printToolResult(data.output || data.error || '', !!data.error);
|
|
131
|
+
this.output.printToolEnd();
|
|
132
|
+
}
|
|
133
|
+
} catch {
|
|
134
|
+
this.output.writeChunk(part + '\n');
|
|
135
|
+
}
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Regular text
|
|
140
|
+
if (part) {
|
|
141
|
+
if (!hasStartedOutput) {
|
|
142
|
+
this.output.beginAssistant();
|
|
143
|
+
hasStartedOutput = true;
|
|
144
|
+
}
|
|
145
|
+
this.output.writeChunk(part + '\n');
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
120
149
|
fullResponse += chunk;
|
|
121
150
|
}
|
|
122
151
|
|
|
123
|
-
|
|
152
|
+
if (hasStartedOutput) {
|
|
153
|
+
this.output.endAssistant();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Don't save empty responses
|
|
157
|
+
if (fullResponse.trim()) {
|
|
158
|
+
this.app.session.addMessage('assistant', fullResponse);
|
|
159
|
+
this.app.session.truncate();
|
|
160
|
+
}
|
|
124
161
|
|
|
125
|
-
this.app.session.addMessage('assistant', fullResponse);
|
|
126
|
-
this.app.session.truncate();
|
|
127
162
|
} catch (error: any) {
|
|
128
|
-
|
|
163
|
+
if (hasStartedOutput) {
|
|
164
|
+
this.output.endAssistant();
|
|
165
|
+
}
|
|
166
|
+
this.output.stopThinkingFail(error.message);
|
|
167
|
+
this.output.error(error.message);
|
|
129
168
|
}
|
|
130
169
|
}
|
|
131
170
|
}
|
package/src/tui/welcome.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Welcome / Setup Wizard
|
|
3
|
-
* Supports all providers + custom relay stations (中转站)
|
|
2
|
+
* Welcome / Setup Wizard - Clean UI
|
|
4
3
|
*/
|
|
5
4
|
|
|
6
5
|
import chalk from 'chalk';
|
|
@@ -11,119 +10,124 @@ import { homedir } from 'os';
|
|
|
11
10
|
import { PROVIDERS, getModelsForProvider, listProviders, isCustomProvider } from '../config/providers.js';
|
|
12
11
|
import type { ProviderName } from '../config/types.js';
|
|
13
12
|
|
|
13
|
+
const line = chalk.gray('─'.repeat(52));
|
|
14
|
+
|
|
14
15
|
export class WelcomeScreen {
|
|
15
|
-
|
|
16
|
-
* Show welcome if no API key is configured
|
|
17
|
-
*/
|
|
16
|
+
|
|
18
17
|
static show(hasApiKey: boolean): void {
|
|
19
18
|
if (hasApiKey) return;
|
|
20
19
|
|
|
21
|
-
|
|
22
|
-
console.log(chalk.cyan
|
|
23
|
-
console.log(chalk.
|
|
24
|
-
console.log(
|
|
25
|
-
console.log(
|
|
26
|
-
console.log(chalk.
|
|
27
|
-
console.log(
|
|
20
|
+
console.log();
|
|
21
|
+
console.log(chalk.cyan.bold(' ⚡ Thatgfsj Code') + chalk.gray(' v0.4.1'));
|
|
22
|
+
console.log(chalk.gray(' AI Coding Assistant'));
|
|
23
|
+
console.log(line);
|
|
24
|
+
console.log();
|
|
25
|
+
console.log(chalk.yellow(' ⚠ No API key configured'));
|
|
26
|
+
console.log();
|
|
27
|
+
console.log(chalk.gray(' Run ') + chalk.cyan.bold('gfcode init') + chalk.gray(' to set up your provider.'));
|
|
28
|
+
console.log();
|
|
29
|
+
console.log(chalk.gray(' Supported providers:'));
|
|
30
|
+
console.log(chalk.gray(' OpenAI · Claude · DeepSeek · Kimi · GLM · Gemini · 中转站'));
|
|
31
|
+
console.log(line);
|
|
28
32
|
console.log();
|
|
29
33
|
}
|
|
30
34
|
|
|
31
|
-
/**
|
|
32
|
-
* Interactive setup wizard
|
|
33
|
-
*/
|
|
34
35
|
static async interactiveSetup(): Promise<void> {
|
|
35
36
|
console.clear();
|
|
36
|
-
console.log(
|
|
37
|
+
console.log();
|
|
38
|
+
console.log(chalk.cyan.bold(' ⚡ Thatgfsj Code') + chalk.gray(' - Setup'));
|
|
39
|
+
console.log(line);
|
|
40
|
+
console.log();
|
|
37
41
|
|
|
38
42
|
const rl = readline.createInterface({
|
|
39
43
|
input: process.stdin,
|
|
40
44
|
output: process.stdout,
|
|
41
45
|
});
|
|
42
46
|
|
|
43
|
-
const
|
|
47
|
+
const ask = (prompt: string): Promise<string> =>
|
|
44
48
|
new Promise(resolve => rl.question(prompt, answer => resolve(answer.trim())));
|
|
45
49
|
|
|
46
50
|
try {
|
|
47
|
-
// Step 1:
|
|
48
|
-
console.log(chalk.
|
|
51
|
+
// ── Step 1: Provider ────────────────────────────────
|
|
52
|
+
console.log(chalk.bold(' 1. Choose Provider'));
|
|
53
|
+
console.log();
|
|
49
54
|
const providers = listProviders();
|
|
50
55
|
providers.forEach((p, i) => {
|
|
51
|
-
const num = (i + 1).toString().padStart(2);
|
|
52
|
-
console.log(
|
|
56
|
+
const num = chalk.cyan((i + 1).toString().padStart(2));
|
|
57
|
+
console.log(` ${num} ${p.name}`);
|
|
53
58
|
});
|
|
54
59
|
console.log();
|
|
55
60
|
|
|
56
|
-
const choice = await
|
|
61
|
+
const choice = await ask(chalk.cyan(' ❯ '));
|
|
57
62
|
const selected = providers[parseInt(choice) - 1] || providers[0];
|
|
58
63
|
const providerName = selected.key;
|
|
59
64
|
|
|
60
|
-
// Step 2:
|
|
61
|
-
console.log(
|
|
65
|
+
// ── Step 2: API Key ─────────────────────────────────
|
|
66
|
+
console.log();
|
|
67
|
+
console.log(chalk.bold(' 2. API Key'));
|
|
68
|
+
console.log();
|
|
62
69
|
|
|
63
70
|
let baseUrl: string | undefined;
|
|
64
71
|
|
|
65
72
|
if (isCustomProvider(providerName)) {
|
|
66
|
-
|
|
67
|
-
console.log(chalk.
|
|
68
|
-
console.log(chalk.gray(' 支持 OpenAI 兼容 或 Anthropic 兼容 的中转站'));
|
|
73
|
+
console.log(chalk.yellow(' Custom Provider (Relay Station)'));
|
|
74
|
+
console.log(chalk.gray(' OpenAI-compatible or Anthropic-compatible'));
|
|
69
75
|
console.log();
|
|
70
|
-
baseUrl = await
|
|
76
|
+
baseUrl = await ask(chalk.cyan(' Base URL ❯ '));
|
|
71
77
|
if (!baseUrl) {
|
|
72
|
-
console.log(chalk.red('
|
|
78
|
+
console.log(chalk.red(' ❌ Base URL required'));
|
|
73
79
|
rl.close();
|
|
74
80
|
return;
|
|
75
81
|
}
|
|
76
|
-
// Remove trailing slash
|
|
77
82
|
baseUrl = baseUrl.replace(/\/+$/, '');
|
|
78
|
-
// Remove /v1 suffix if present (we'll add it in the provider)
|
|
79
|
-
// Actually keep it as-is since the user knows their endpoint
|
|
80
83
|
}
|
|
81
84
|
|
|
82
|
-
const apiKey = await
|
|
85
|
+
const apiKey = await ask(chalk.cyan(' API Key ❯ '));
|
|
83
86
|
|
|
84
|
-
// Step 3:
|
|
85
|
-
console.log(
|
|
87
|
+
// ── Step 3: Model ───────────────────────────────────
|
|
88
|
+
console.log();
|
|
89
|
+
console.log(chalk.bold(' 3. Choose Model'));
|
|
90
|
+
console.log();
|
|
86
91
|
|
|
87
92
|
if (isCustomProvider(providerName)) {
|
|
88
|
-
|
|
89
|
-
console.log(chalk.gray('
|
|
90
|
-
console.log(
|
|
91
|
-
const model = await
|
|
93
|
+
console.log(chalk.gray(' Enter model name for your relay station'));
|
|
94
|
+
console.log(chalk.gray(' e.g. gpt-4o-mini, claude-sonnet-4-20250514'));
|
|
95
|
+
console.log();
|
|
96
|
+
const model = await ask(chalk.cyan(' Model ❯ '));
|
|
92
97
|
if (!model) {
|
|
93
|
-
console.log(chalk.red('
|
|
98
|
+
console.log(chalk.red(' ❌ Model name required'));
|
|
94
99
|
rl.close();
|
|
95
100
|
return;
|
|
96
101
|
}
|
|
97
|
-
|
|
98
|
-
// Save config
|
|
99
102
|
this.saveConfig(providerName, model, apiKey, baseUrl);
|
|
100
103
|
} else {
|
|
101
|
-
// Standard provider: show model list
|
|
102
104
|
const models = getModelsForProvider(providerName);
|
|
103
105
|
models.forEach((m, i) => {
|
|
104
|
-
const num = (i + 1).toString().padStart(2);
|
|
105
|
-
console.log(
|
|
106
|
+
const num = chalk.cyan((i + 1).toString().padStart(2));
|
|
107
|
+
console.log(` ${num} ${chalk.white(m.name)} ${chalk.gray(m.desc)}`);
|
|
106
108
|
});
|
|
107
109
|
console.log();
|
|
108
110
|
|
|
109
|
-
const modelChoice = await
|
|
111
|
+
const modelChoice = await ask(chalk.cyan(' ❯ '));
|
|
110
112
|
const selectedModel = models[parseInt(modelChoice) - 1] || models[0];
|
|
111
|
-
|
|
112
113
|
this.saveConfig(providerName, selectedModel.id, apiKey, baseUrl);
|
|
113
114
|
}
|
|
114
115
|
|
|
115
|
-
|
|
116
|
-
console.log(
|
|
117
|
-
console.log(
|
|
116
|
+
// ── Done ────────────────────────────────────────────
|
|
117
|
+
console.log();
|
|
118
|
+
console.log(line);
|
|
119
|
+
console.log(chalk.green.bold(' ✅ Configuration saved!'));
|
|
120
|
+
console.log();
|
|
121
|
+
console.log(chalk.gray(' Config: ') + chalk.white(join(homedir(), '.thatgfsj', 'config.json')));
|
|
122
|
+
console.log(chalk.gray(' Run ') + chalk.cyan.bold('gfcode') + chalk.gray(' to start.'));
|
|
123
|
+
console.log(line);
|
|
124
|
+
console.log();
|
|
118
125
|
|
|
119
126
|
} finally {
|
|
120
127
|
rl.close();
|
|
121
128
|
}
|
|
122
129
|
}
|
|
123
130
|
|
|
124
|
-
/**
|
|
125
|
-
* Save config to file
|
|
126
|
-
*/
|
|
127
131
|
private static saveConfig(provider: ProviderName, model: string, apiKey: string, baseUrl?: string): void {
|
|
128
132
|
const configDir = join(homedir(), '.thatgfsj');
|
|
129
133
|
const configPath = join(configDir, 'config.json');
|
|
@@ -140,16 +144,15 @@ export class WelcomeScreen {
|
|
|
140
144
|
maxTokens: 4096,
|
|
141
145
|
};
|
|
142
146
|
|
|
143
|
-
if (baseUrl)
|
|
144
|
-
config.baseUrl = baseUrl;
|
|
145
|
-
}
|
|
147
|
+
if (baseUrl) config.baseUrl = baseUrl;
|
|
146
148
|
|
|
147
149
|
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
148
150
|
|
|
149
|
-
console.log(
|
|
150
|
-
console.log(chalk.gray(
|
|
151
|
+
console.log();
|
|
152
|
+
console.log(chalk.gray(' Provider: ') + chalk.white(provider));
|
|
153
|
+
console.log(chalk.gray(' Model: ') + chalk.white(model));
|
|
151
154
|
if (baseUrl) {
|
|
152
|
-
console.log(chalk.gray(
|
|
155
|
+
console.log(chalk.gray(' URL: ') + chalk.white(baseUrl));
|
|
153
156
|
}
|
|
154
157
|
}
|
|
155
158
|
}
|