thatgfsj-code 0.4.1 → 0.6.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 +7 -12
- package/dist/app/index.d.ts +6 -14
- package/dist/app/index.d.ts.map +1 -1
- package/dist/app/index.js +29 -31
- package/dist/app/index.js.map +1 -1
- package/dist/cmd/index.d.ts +1 -2
- package/dist/cmd/index.d.ts.map +1 -1
- package/dist/cmd/index.js +58 -47
- package/dist/cmd/index.js.map +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +1 -0
- package/dist/config/index.js.map +1 -1
- package/dist/config/types.d.ts +1 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/llm/index.d.ts.map +1 -1
- package/dist/llm/index.js +0 -1
- package/dist/llm/index.js.map +1 -1
- package/dist/tui/app.d.ts +9 -0
- package/dist/tui/app.d.ts.map +1 -0
- package/dist/tui/app.js +40 -0
- package/dist/tui/app.js.map +1 -0
- package/dist/tui/components/ChatList.d.ts +14 -0
- package/dist/tui/components/ChatList.d.ts.map +1 -0
- package/dist/tui/components/ChatList.js +11 -0
- package/dist/tui/components/ChatList.js.map +1 -0
- package/dist/tui/components/ChatMessage.d.ts +14 -0
- package/dist/tui/components/ChatMessage.d.ts.map +1 -0
- package/dist/tui/components/ChatMessage.js +17 -0
- package/dist/tui/components/ChatMessage.js.map +1 -0
- package/dist/tui/components/Header.d.ts +9 -0
- package/dist/tui/components/Header.d.ts.map +1 -0
- package/dist/tui/components/Header.js +6 -0
- package/dist/tui/components/Header.js.map +1 -0
- package/dist/tui/components/Markdown.d.ts +8 -0
- package/dist/tui/components/Markdown.d.ts.map +1 -0
- package/dist/tui/components/Markdown.js +31 -0
- package/dist/tui/components/Markdown.js.map +1 -0
- package/dist/tui/components/Thinking.d.ts +8 -0
- package/dist/tui/components/Thinking.d.ts.map +1 -0
- package/dist/tui/components/Thinking.js +7 -0
- package/dist/tui/components/Thinking.js.map +1 -0
- package/dist/tui/components/ToolCall.d.ts +14 -0
- package/dist/tui/components/ToolCall.d.ts.map +1 -0
- package/dist/tui/components/ToolCall.js +37 -0
- package/dist/tui/components/ToolCall.js.map +1 -0
- package/dist/tui/components/UserInput.d.ts +9 -0
- package/dist/tui/components/UserInput.d.ts.map +1 -0
- package/dist/tui/components/UserInput.js +82 -0
- package/dist/tui/components/UserInput.js.map +1 -0
- package/dist/tui/hooks/useChat.d.ts +11 -0
- package/dist/tui/hooks/useChat.d.ts.map +1 -0
- package/dist/tui/hooks/useChat.js +138 -0
- package/dist/tui/hooks/useChat.js.map +1 -0
- package/dist/tui/hooks/useCommands.d.ts +10 -0
- package/dist/tui/hooks/useCommands.d.ts.map +1 -0
- package/dist/tui/hooks/useCommands.js +51 -0
- package/dist/tui/hooks/useCommands.js.map +1 -0
- package/dist/tui/index.d.ts +2 -4
- package/dist/tui/index.d.ts.map +1 -1
- package/dist/tui/index.js +2 -4
- package/dist/tui/index.js.map +1 -1
- package/dist/tui/output.d.ts +12 -3
- package/dist/tui/output.d.ts.map +1 -1
- package/dist/tui/output.js +43 -47
- package/dist/tui/output.js.map +1 -1
- package/dist/tui/repl.d.ts.map +1 -1
- package/dist/tui/repl.js +42 -28
- package/dist/tui/repl.js.map +1 -1
- package/dist/tui/welcome.d.ts.map +1 -1
- package/dist/tui/welcome.js +23 -5
- package/dist/tui/welcome.js.map +1 -1
- package/install.ps1 +171 -192
- package/package.json +9 -1
- package/src/app/index.ts +29 -34
- package/src/cmd/{index.ts → index.tsx} +52 -47
- package/src/config/index.ts +1 -0
- package/src/config/types.ts +1 -0
- package/src/llm/index.ts +0 -1
- package/src/tui/app.tsx +60 -0
- package/src/tui/components/ChatList.tsx +29 -0
- package/src/tui/components/ChatMessage.tsx +51 -0
- package/src/tui/components/Header.tsx +22 -0
- package/src/tui/components/Markdown.tsx +35 -0
- package/src/tui/components/Thinking.tsx +19 -0
- package/src/tui/components/ToolCall.tsx +72 -0
- package/src/tui/components/UserInput.tsx +101 -0
- package/src/tui/hooks/useChat.ts +154 -0
- package/src/tui/hooks/useCommands.ts +63 -0
- package/src/tui/index.ts +2 -4
- package/src/tui/output.ts +43 -44
- package/src/tui/repl.ts +40 -29
- package/src/tui/welcome.ts +25 -6
- package/tsconfig.json +5 -3
- package/src/app/agent.ts +0 -140
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { useState, useCallback, useRef } from 'react';
|
|
2
|
+
import type { MessageData } from '../components/ChatMessage.js';
|
|
3
|
+
import type { ToolCallData } from '../components/ToolCall.js';
|
|
4
|
+
import type { App } from '../../app/index.js';
|
|
5
|
+
|
|
6
|
+
interface ChatState {
|
|
7
|
+
messages: MessageData[];
|
|
8
|
+
isThinking: boolean;
|
|
9
|
+
streaming: string;
|
|
10
|
+
streamingToolCalls: ToolCallData[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function useChat(app: App) {
|
|
14
|
+
const [state, setState] = useState<ChatState>({
|
|
15
|
+
messages: [],
|
|
16
|
+
isThinking: false,
|
|
17
|
+
streaming: '',
|
|
18
|
+
streamingToolCalls: [],
|
|
19
|
+
});
|
|
20
|
+
const processingRef = useRef(false);
|
|
21
|
+
|
|
22
|
+
const sendMessage = useCallback(async (input: string) => {
|
|
23
|
+
if (processingRef.current) return;
|
|
24
|
+
processingRef.current = true;
|
|
25
|
+
|
|
26
|
+
// Add user message
|
|
27
|
+
setState(prev => ({
|
|
28
|
+
...prev,
|
|
29
|
+
messages: [...prev.messages, { role: 'user', content: input }],
|
|
30
|
+
isThinking: true,
|
|
31
|
+
streaming: '',
|
|
32
|
+
streamingToolCalls: [],
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
app.session.addMessage('user', input);
|
|
36
|
+
|
|
37
|
+
let fullContent = '';
|
|
38
|
+
let currentToolCalls: ToolCallData[] = [];
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const stream = app.streamResponse();
|
|
42
|
+
|
|
43
|
+
for await (const chunk of stream) {
|
|
44
|
+
// Check for tool messages
|
|
45
|
+
if (chunk.includes('@@TOOL@@')) {
|
|
46
|
+
const parts = chunk.split('\n');
|
|
47
|
+
for (const part of parts) {
|
|
48
|
+
if (part.startsWith('@@TOOL@@')) {
|
|
49
|
+
try {
|
|
50
|
+
const data = JSON.parse(part.slice(8));
|
|
51
|
+
if (data.action === 'call') {
|
|
52
|
+
// Tool call - show it immediately
|
|
53
|
+
currentToolCalls.push({
|
|
54
|
+
name: data.name,
|
|
55
|
+
args: data.args || '',
|
|
56
|
+
});
|
|
57
|
+
setState(prev => ({
|
|
58
|
+
...prev,
|
|
59
|
+
isThinking: false,
|
|
60
|
+
streamingToolCalls: [...currentToolCalls],
|
|
61
|
+
}));
|
|
62
|
+
} else if (data.action === 'result') {
|
|
63
|
+
// Tool result - update the last tool call
|
|
64
|
+
const lastIdx = currentToolCalls.length - 1;
|
|
65
|
+
if (lastIdx >= 0) {
|
|
66
|
+
currentToolCalls[lastIdx] = {
|
|
67
|
+
...currentToolCalls[lastIdx],
|
|
68
|
+
result: data.output || data.error || '',
|
|
69
|
+
isError: !!data.error,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
setState(prev => ({
|
|
73
|
+
...prev,
|
|
74
|
+
streamingToolCalls: [...currentToolCalls],
|
|
75
|
+
isThinking: true, // Continue thinking after tool
|
|
76
|
+
}));
|
|
77
|
+
}
|
|
78
|
+
} catch {
|
|
79
|
+
// Not a tool message, treat as text
|
|
80
|
+
fullContent += part;
|
|
81
|
+
setState(prev => ({
|
|
82
|
+
...prev,
|
|
83
|
+
isThinking: false,
|
|
84
|
+
streaming: fullContent,
|
|
85
|
+
}));
|
|
86
|
+
}
|
|
87
|
+
} else if (part) {
|
|
88
|
+
fullContent += part;
|
|
89
|
+
setState(prev => ({
|
|
90
|
+
...prev,
|
|
91
|
+
isThinking: false,
|
|
92
|
+
streaming: fullContent,
|
|
93
|
+
}));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
} else {
|
|
97
|
+
// Regular text chunk
|
|
98
|
+
fullContent += chunk;
|
|
99
|
+
setState(prev => ({
|
|
100
|
+
...prev,
|
|
101
|
+
isThinking: false,
|
|
102
|
+
streaming: fullContent,
|
|
103
|
+
}));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Finalize: add the complete message
|
|
108
|
+
if (fullContent.trim() || currentToolCalls.length > 0) {
|
|
109
|
+
app.session.addMessage('assistant', fullContent);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
setState(prev => ({
|
|
113
|
+
...prev,
|
|
114
|
+
messages: [
|
|
115
|
+
...prev.messages,
|
|
116
|
+
...(fullContent.trim() || currentToolCalls.length > 0
|
|
117
|
+
? [{
|
|
118
|
+
role: 'assistant' as const,
|
|
119
|
+
content: fullContent,
|
|
120
|
+
toolCalls: currentToolCalls.length > 0 ? currentToolCalls : undefined,
|
|
121
|
+
}]
|
|
122
|
+
: []),
|
|
123
|
+
],
|
|
124
|
+
streaming: '',
|
|
125
|
+
streamingToolCalls: [],
|
|
126
|
+
isThinking: false,
|
|
127
|
+
}));
|
|
128
|
+
|
|
129
|
+
app.session.truncate();
|
|
130
|
+
|
|
131
|
+
} catch (error: any) {
|
|
132
|
+
setState(prev => ({
|
|
133
|
+
...prev,
|
|
134
|
+
messages: [
|
|
135
|
+
...prev.messages,
|
|
136
|
+
...(fullContent.trim()
|
|
137
|
+
? [{ role: 'assistant' as const, content: fullContent }]
|
|
138
|
+
: []),
|
|
139
|
+
{ role: 'assistant', content: `Error: ${error.message}` },
|
|
140
|
+
],
|
|
141
|
+
streaming: '',
|
|
142
|
+
streamingToolCalls: [],
|
|
143
|
+
isThinking: false,
|
|
144
|
+
}));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
processingRef.current = false;
|
|
148
|
+
}, [app]);
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
...state,
|
|
152
|
+
sendMessage,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import type { App } from '../../app/index.js';
|
|
3
|
+
|
|
4
|
+
interface CommandResult {
|
|
5
|
+
handled: boolean;
|
|
6
|
+
output?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function useCommands(app: App) {
|
|
10
|
+
const handleCommand = useCallback((input: string): CommandResult => {
|
|
11
|
+
const cmd = input.toLowerCase().trim();
|
|
12
|
+
|
|
13
|
+
switch (cmd) {
|
|
14
|
+
case 'help':
|
|
15
|
+
return {
|
|
16
|
+
handled: true,
|
|
17
|
+
output: [
|
|
18
|
+
'Commands:',
|
|
19
|
+
' help Show this help',
|
|
20
|
+
' tools List available tools',
|
|
21
|
+
' model Show current model',
|
|
22
|
+
' clear Clear screen',
|
|
23
|
+
' exit Exit',
|
|
24
|
+
'',
|
|
25
|
+
'Keyboard:',
|
|
26
|
+
' ↑/↓ Browse history',
|
|
27
|
+
' Ctrl+C Exit',
|
|
28
|
+
].join('\n'),
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
case 'tools':
|
|
32
|
+
return {
|
|
33
|
+
handled: true,
|
|
34
|
+
output: app.tools.list()
|
|
35
|
+
.map(t => ` ${t.name.padEnd(16)} ${t.description}`)
|
|
36
|
+
.join('\n'),
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
case 'model': {
|
|
40
|
+
const c = app.config.get();
|
|
41
|
+
return {
|
|
42
|
+
handled: true,
|
|
43
|
+
output: [
|
|
44
|
+
` Provider: ${c.provider}`,
|
|
45
|
+
` Model: ${c.model}`,
|
|
46
|
+
` Base URL: ${c.baseUrl || 'default'}`,
|
|
47
|
+
` API Key: ${c.apiKey ? '••••' + c.apiKey.slice(-4) : 'not set'}`,
|
|
48
|
+
` Context Length: ${c.contextLength || 50} messages`,
|
|
49
|
+
].join('\n'),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
case 'clear':
|
|
54
|
+
// Handled separately in the App component
|
|
55
|
+
return { handled: true };
|
|
56
|
+
|
|
57
|
+
default:
|
|
58
|
+
return { handled: false };
|
|
59
|
+
}
|
|
60
|
+
}, [app]);
|
|
61
|
+
|
|
62
|
+
return { handleCommand };
|
|
63
|
+
}
|
package/src/tui/index.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* TUI Module - Terminal
|
|
2
|
+
* TUI Module - Ink-based Terminal UI
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
export {
|
|
6
|
-
export { REPLInput } from './input.js';
|
|
7
|
-
export { REPLOutput } from './output.js';
|
|
5
|
+
export { TuiApp } from './app.js';
|
|
8
6
|
export { WelcomeScreen } from './welcome.js';
|
package/src/tui/output.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* REPL Output -
|
|
2
|
+
* REPL Output - Clean terminal UI
|
|
3
|
+
* Inspired by opencode: simple indentation for streaming, clean boundaries
|
|
3
4
|
*/
|
|
4
5
|
|
|
5
6
|
import chalk from 'chalk';
|
|
@@ -7,14 +8,12 @@ import ora, { Ora } from 'ora';
|
|
|
7
8
|
|
|
8
9
|
export class REPLOutput {
|
|
9
10
|
private spinner: Ora | null = null;
|
|
10
|
-
private inAssistantBlock = false;
|
|
11
|
-
private currentLineHasContent = false;
|
|
12
11
|
|
|
13
12
|
// ── Banner ──────────────────────────────────────────────
|
|
14
13
|
|
|
15
14
|
printBanner(): void {
|
|
16
15
|
console.log();
|
|
17
|
-
console.log(chalk.cyan.bold(' ⚡ Thatgfsj Code') + chalk.gray(' v0.
|
|
16
|
+
console.log(chalk.cyan.bold(' ⚡ Thatgfsj Code') + chalk.gray(' v0.5.0'));
|
|
18
17
|
console.log(chalk.gray(' AI Coding Assistant'));
|
|
19
18
|
console.log(chalk.gray(' ' + '─'.repeat(52)));
|
|
20
19
|
console.log();
|
|
@@ -30,54 +29,54 @@ export class REPLOutput {
|
|
|
30
29
|
|
|
31
30
|
printUserInput(input: string): void {
|
|
32
31
|
console.log();
|
|
33
|
-
console.log(chalk.
|
|
32
|
+
console.log(chalk.bold(' You'));
|
|
33
|
+
console.log(chalk.gray(' ' + '─'.repeat(40)));
|
|
34
34
|
for (const line of input.split('\n')) {
|
|
35
|
-
console.log(
|
|
35
|
+
console.log(' ' + line);
|
|
36
36
|
}
|
|
37
|
-
console.log(
|
|
37
|
+
console.log();
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
// ── Assistant Streaming ─────────────────────────────────
|
|
41
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Begin assistant response - just print the header
|
|
44
|
+
*/
|
|
42
45
|
beginAssistant(): void {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
this.currentLineHasContent = false;
|
|
46
|
-
console.log();
|
|
47
|
-
process.stdout.write(chalk.cyan(' ┌─ ') + chalk.bold('AI') + '\n' + chalk.cyan(' │ '));
|
|
46
|
+
console.log(chalk.bold(' AI'));
|
|
47
|
+
console.log(chalk.gray(' ' + '─'.repeat(40)));
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Write streaming chunk - output as-is with indent.
|
|
52
|
+
* The chunk already contains proper newlines from the AI.
|
|
53
|
+
*/
|
|
50
54
|
writeChunk(chunk: string): void {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
for (
|
|
54
|
-
if (
|
|
55
|
-
process.stdout.write('
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
process.stdout.write(
|
|
59
|
-
this.currentLineHasContent = true;
|
|
55
|
+
// Indent each line by 2 spaces
|
|
56
|
+
const lines = chunk.split('\n');
|
|
57
|
+
for (let i = 0; i < lines.length; i++) {
|
|
58
|
+
if (lines[i]) {
|
|
59
|
+
process.stdout.write(' ' + lines[i]);
|
|
60
|
+
}
|
|
61
|
+
if (i < lines.length - 1) {
|
|
62
|
+
process.stdout.write('\n');
|
|
60
63
|
}
|
|
61
64
|
}
|
|
62
65
|
}
|
|
63
66
|
|
|
67
|
+
/**
|
|
68
|
+
* End assistant response
|
|
69
|
+
*/
|
|
64
70
|
endAssistant(): void {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
process.stdout.write('\n');
|
|
68
|
-
}
|
|
69
|
-
process.stdout.write(chalk.cyan(' └─') + '\n\n');
|
|
70
|
-
this.inAssistantBlock = false;
|
|
71
|
-
this.currentLineHasContent = false;
|
|
71
|
+
process.stdout.write('\n');
|
|
72
|
+
console.log();
|
|
72
73
|
}
|
|
73
74
|
|
|
74
75
|
// ── Tool Calls ──────────────────────────────────────────
|
|
75
76
|
|
|
76
77
|
printToolCall(name: string, args: string): void {
|
|
77
|
-
if (this.inAssistantBlock) this.endAssistant();
|
|
78
|
-
|
|
79
78
|
console.log();
|
|
80
|
-
console.log(chalk.yellow(' ⚙ ') + chalk.bold(name)
|
|
79
|
+
console.log(chalk.yellow(' ⚙ ') + chalk.bold(name));
|
|
81
80
|
|
|
82
81
|
if (args) {
|
|
83
82
|
try {
|
|
@@ -85,15 +84,15 @@ export class REPLOutput {
|
|
|
85
84
|
for (const [key, val] of Object.entries(obj)) {
|
|
86
85
|
let display: string;
|
|
87
86
|
if (typeof val === 'string') {
|
|
88
|
-
display = val.length >
|
|
87
|
+
display = val.length > 100 ? val.slice(0, 100) + '...' : val;
|
|
89
88
|
} else {
|
|
90
89
|
display = JSON.stringify(val);
|
|
91
|
-
if (display.length >
|
|
90
|
+
if (display.length > 100) display = display.slice(0, 100) + '...';
|
|
92
91
|
}
|
|
93
|
-
console.log(chalk.gray(' ' + key + ': ') +
|
|
92
|
+
console.log(chalk.gray(' ' + key + ': ') + display);
|
|
94
93
|
}
|
|
95
94
|
} catch {
|
|
96
|
-
const display = args.length >
|
|
95
|
+
const display = args.length > 120 ? args.slice(0, 120) + '...' : args;
|
|
97
96
|
console.log(chalk.gray(' ') + display);
|
|
98
97
|
}
|
|
99
98
|
}
|
|
@@ -103,7 +102,7 @@ export class REPLOutput {
|
|
|
103
102
|
if (!output) return;
|
|
104
103
|
|
|
105
104
|
const lines = output.split('\n');
|
|
106
|
-
const maxLines =
|
|
105
|
+
const maxLines = 20;
|
|
107
106
|
const display = lines.slice(0, maxLines);
|
|
108
107
|
|
|
109
108
|
for (const line of display) {
|
|
@@ -120,7 +119,7 @@ export class REPLOutput {
|
|
|
120
119
|
}
|
|
121
120
|
|
|
122
121
|
printToolEnd(): void {
|
|
123
|
-
console.log(
|
|
122
|
+
console.log();
|
|
124
123
|
}
|
|
125
124
|
|
|
126
125
|
// ── Thinking ────────────────────────────────────────────
|
|
@@ -167,11 +166,11 @@ export class REPLOutput {
|
|
|
167
166
|
|
|
168
167
|
error(msg: string): void {
|
|
169
168
|
console.log();
|
|
170
|
-
console.log(chalk.red('
|
|
169
|
+
console.log(chalk.red(' Error'));
|
|
170
|
+
console.log(chalk.red(' ' + '─'.repeat(40)));
|
|
171
171
|
for (const line of msg.split('\n')) {
|
|
172
|
-
console.log(chalk.red('
|
|
172
|
+
console.log(chalk.red(' ') + line);
|
|
173
173
|
}
|
|
174
|
-
console.log(chalk.red(' └─'));
|
|
175
174
|
console.log();
|
|
176
175
|
}
|
|
177
176
|
|
|
@@ -202,9 +201,9 @@ export class REPLOutput {
|
|
|
202
201
|
section(title: string, items: Array<{ label: string; value: string }>): void {
|
|
203
202
|
console.log();
|
|
204
203
|
console.log(chalk.bold(' ' + title));
|
|
205
|
-
|
|
204
|
+
console.log(chalk.gray(' ' + '─'.repeat(40)));
|
|
206
205
|
for (const item of items) {
|
|
207
|
-
console.log(' ' + chalk.gray(item.label.padEnd(14)) +
|
|
206
|
+
console.log(' ' + chalk.gray(item.label.padEnd(14)) + item.value);
|
|
208
207
|
}
|
|
209
208
|
console.log();
|
|
210
209
|
}
|
|
@@ -214,7 +213,7 @@ export class REPLOutput {
|
|
|
214
213
|
printHelp(): void {
|
|
215
214
|
console.log();
|
|
216
215
|
console.log(chalk.bold(' Commands'));
|
|
217
|
-
|
|
216
|
+
console.log(chalk.gray(' ' + '─'.repeat(40)));
|
|
218
217
|
const cmds: [string, string][] = [
|
|
219
218
|
['help', 'Show this help'],
|
|
220
219
|
['tools', 'List available tools'],
|
|
@@ -227,7 +226,7 @@ export class REPLOutput {
|
|
|
227
226
|
}
|
|
228
227
|
console.log();
|
|
229
228
|
console.log(chalk.bold(' Keyboard'));
|
|
230
|
-
|
|
229
|
+
console.log(chalk.gray(' ' + '─'.repeat(40)));
|
|
231
230
|
console.log(' ' + chalk.cyan('↑ / ↓'.padEnd(14)) + chalk.gray('Browse command history'));
|
|
232
231
|
console.log(' ' + chalk.cyan('Tab'.padEnd(14)) + chalk.gray('Auto-complete'));
|
|
233
232
|
console.log(' ' + chalk.cyan('Ctrl+C'.padEnd(14)) + chalk.gray('Exit'));
|
package/src/tui/repl.ts
CHANGED
|
@@ -106,44 +106,56 @@ export class REPL {
|
|
|
106
106
|
let hasStartedOutput = false;
|
|
107
107
|
|
|
108
108
|
try {
|
|
109
|
-
const stream = this.app.
|
|
109
|
+
const stream = this.app.streamResponse();
|
|
110
110
|
|
|
111
111
|
for await (const chunk of stream) {
|
|
112
112
|
this.output.stopThinking();
|
|
113
113
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if (
|
|
122
|
-
|
|
123
|
-
|
|
114
|
+
// Check for structured tool messages
|
|
115
|
+
if (chunk.includes('@@TOOL@@')) {
|
|
116
|
+
const parts = chunk.split('\n');
|
|
117
|
+
for (const part of parts) {
|
|
118
|
+
if (part.startsWith('@@TOOL@@')) {
|
|
119
|
+
try {
|
|
120
|
+
const data = JSON.parse(part.slice(8));
|
|
121
|
+
if (data.action === 'call') {
|
|
122
|
+
if (hasStartedOutput) {
|
|
123
|
+
this.output.endAssistant();
|
|
124
|
+
hasStartedOutput = false;
|
|
125
|
+
}
|
|
126
|
+
this.output.printToolCall(data.name, data.args || '');
|
|
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();
|
|
124
132
|
}
|
|
125
|
-
|
|
126
|
-
//
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
133
|
+
} catch {
|
|
134
|
+
// If parse fails, treat as regular text
|
|
135
|
+
if (part) {
|
|
136
|
+
if (!hasStartedOutput) {
|
|
137
|
+
this.output.beginAssistant();
|
|
138
|
+
hasStartedOutput = true;
|
|
139
|
+
}
|
|
140
|
+
this.output.writeChunk(part);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
} else if (part) {
|
|
144
|
+
// Regular text between tool messages
|
|
145
|
+
if (!hasStartedOutput) {
|
|
146
|
+
this.output.beginAssistant();
|
|
147
|
+
hasStartedOutput = true;
|
|
132
148
|
}
|
|
133
|
-
} catch {
|
|
134
149
|
this.output.writeChunk(part + '\n');
|
|
135
150
|
}
|
|
136
|
-
continue;
|
|
137
151
|
}
|
|
138
|
-
|
|
139
|
-
//
|
|
140
|
-
if (
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
hasStartedOutput = true;
|
|
144
|
-
}
|
|
145
|
-
this.output.writeChunk(part + '\n');
|
|
152
|
+
} else {
|
|
153
|
+
// Pure text chunk - just output it
|
|
154
|
+
if (!hasStartedOutput) {
|
|
155
|
+
this.output.beginAssistant();
|
|
156
|
+
hasStartedOutput = true;
|
|
146
157
|
}
|
|
158
|
+
this.output.writeChunk(chunk);
|
|
147
159
|
}
|
|
148
160
|
|
|
149
161
|
fullResponse += chunk;
|
|
@@ -153,7 +165,6 @@ export class REPL {
|
|
|
153
165
|
this.output.endAssistant();
|
|
154
166
|
}
|
|
155
167
|
|
|
156
|
-
// Don't save empty responses
|
|
157
168
|
if (fullResponse.trim()) {
|
|
158
169
|
this.app.session.addMessage('assistant', fullResponse);
|
|
159
170
|
this.app.session.truncate();
|
package/src/tui/welcome.ts
CHANGED
|
@@ -18,7 +18,7 @@ export class WelcomeScreen {
|
|
|
18
18
|
if (hasApiKey) return;
|
|
19
19
|
|
|
20
20
|
console.log();
|
|
21
|
-
console.log(chalk.cyan.bold(' ⚡ Thatgfsj Code') + chalk.gray(' v0.
|
|
21
|
+
console.log(chalk.cyan.bold(' ⚡ Thatgfsj Code') + chalk.gray(' v0.5.0'));
|
|
22
22
|
console.log(chalk.gray(' AI Coding Assistant'));
|
|
23
23
|
console.log(line);
|
|
24
24
|
console.log();
|
|
@@ -89,17 +89,17 @@ export class WelcomeScreen {
|
|
|
89
89
|
console.log(chalk.bold(' 3. Choose Model'));
|
|
90
90
|
console.log();
|
|
91
91
|
|
|
92
|
+
let model: string;
|
|
92
93
|
if (isCustomProvider(providerName)) {
|
|
93
94
|
console.log(chalk.gray(' Enter model name for your relay station'));
|
|
94
95
|
console.log(chalk.gray(' e.g. gpt-4o-mini, claude-sonnet-4-20250514'));
|
|
95
96
|
console.log();
|
|
96
|
-
|
|
97
|
+
model = await ask(chalk.cyan(' Model ❯ '));
|
|
97
98
|
if (!model) {
|
|
98
99
|
console.log(chalk.red(' ❌ Model name required'));
|
|
99
100
|
rl.close();
|
|
100
101
|
return;
|
|
101
102
|
}
|
|
102
|
-
this.saveConfig(providerName, model, apiKey, baseUrl);
|
|
103
103
|
} else {
|
|
104
104
|
const models = getModelsForProvider(providerName);
|
|
105
105
|
models.forEach((m, i) => {
|
|
@@ -107,12 +107,30 @@ export class WelcomeScreen {
|
|
|
107
107
|
console.log(` ${num} ${chalk.white(m.name)} ${chalk.gray(m.desc)}`);
|
|
108
108
|
});
|
|
109
109
|
console.log();
|
|
110
|
-
|
|
111
110
|
const modelChoice = await ask(chalk.cyan(' ❯ '));
|
|
112
111
|
const selectedModel = models[parseInt(modelChoice) - 1] || models[0];
|
|
113
|
-
|
|
112
|
+
model = selectedModel.id;
|
|
114
113
|
}
|
|
115
114
|
|
|
115
|
+
// ── Step 4: Context Length ───────────────────────────
|
|
116
|
+
console.log();
|
|
117
|
+
console.log(chalk.bold(' 4. Context Length'));
|
|
118
|
+
console.log();
|
|
119
|
+
console.log(chalk.gray(' Max messages kept in conversation history'));
|
|
120
|
+
console.log(chalk.gray(' Higher = more context, more tokens used'));
|
|
121
|
+
console.log();
|
|
122
|
+
console.log(chalk.cyan(' 1') + ' 20 ' + chalk.gray('(short,节省 token)'));
|
|
123
|
+
console.log(chalk.cyan(' 2') + ' 50 ' + chalk.gray('(default,推荐)'));
|
|
124
|
+
console.log(chalk.cyan(' 3') + ' 100 ' + chalk.gray('(long,长对话)'));
|
|
125
|
+
console.log(chalk.cyan(' 4') + ' 200 ' + chalk.gray('(very long,超长对话)'));
|
|
126
|
+
console.log();
|
|
127
|
+
const ctxChoice = await ask(chalk.cyan(' ❯ '));
|
|
128
|
+
const ctxMap: Record<string, number> = { '1': 20, '2': 50, '3': 100, '4': 200 };
|
|
129
|
+
const contextLength = ctxMap[ctxChoice] || 50;
|
|
130
|
+
|
|
131
|
+
// ── Save ────────────────────────────────────────────
|
|
132
|
+
this.saveConfig(providerName, model, apiKey, baseUrl, contextLength);
|
|
133
|
+
|
|
116
134
|
// ── Done ────────────────────────────────────────────
|
|
117
135
|
console.log();
|
|
118
136
|
console.log(line);
|
|
@@ -128,7 +146,7 @@ export class WelcomeScreen {
|
|
|
128
146
|
}
|
|
129
147
|
}
|
|
130
148
|
|
|
131
|
-
private static saveConfig(provider: ProviderName, model: string, apiKey: string, baseUrl?: string): void {
|
|
149
|
+
private static saveConfig(provider: ProviderName, model: string, apiKey: string, baseUrl?: string, contextLength = 50): void {
|
|
132
150
|
const configDir = join(homedir(), '.thatgfsj');
|
|
133
151
|
const configPath = join(configDir, 'config.json');
|
|
134
152
|
|
|
@@ -142,6 +160,7 @@ export class WelcomeScreen {
|
|
|
142
160
|
apiKey,
|
|
143
161
|
temperature: 0.7,
|
|
144
162
|
maxTokens: 4096,
|
|
163
|
+
contextLength,
|
|
145
164
|
};
|
|
146
165
|
|
|
147
166
|
if (baseUrl) config.baseUrl = baseUrl;
|
package/tsconfig.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"compilerOptions": {
|
|
3
3
|
"target": "ES2022",
|
|
4
|
-
"module": "
|
|
5
|
-
"moduleResolution": "
|
|
4
|
+
"module": "Node16",
|
|
5
|
+
"moduleResolution": "node16",
|
|
6
6
|
"outDir": "./dist",
|
|
7
7
|
"rootDir": "./src",
|
|
8
8
|
"strict": true,
|
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
"resolveJsonModule": true,
|
|
13
13
|
"declaration": true,
|
|
14
14
|
"declarationMap": true,
|
|
15
|
-
"sourceMap": true
|
|
15
|
+
"sourceMap": true,
|
|
16
|
+
"jsx": "react-jsx",
|
|
17
|
+
"jsxImportSource": "react"
|
|
16
18
|
},
|
|
17
19
|
"include": ["src/**/*"],
|
|
18
20
|
"exclude": ["node_modules", "dist"]
|