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.
Files changed (94) hide show
  1. package/README.md +7 -12
  2. package/dist/app/index.d.ts +6 -14
  3. package/dist/app/index.d.ts.map +1 -1
  4. package/dist/app/index.js +29 -31
  5. package/dist/app/index.js.map +1 -1
  6. package/dist/cmd/index.d.ts +1 -2
  7. package/dist/cmd/index.d.ts.map +1 -1
  8. package/dist/cmd/index.js +58 -47
  9. package/dist/cmd/index.js.map +1 -1
  10. package/dist/config/index.d.ts.map +1 -1
  11. package/dist/config/index.js +1 -0
  12. package/dist/config/index.js.map +1 -1
  13. package/dist/config/types.d.ts +1 -0
  14. package/dist/config/types.d.ts.map +1 -1
  15. package/dist/llm/index.d.ts.map +1 -1
  16. package/dist/llm/index.js +0 -1
  17. package/dist/llm/index.js.map +1 -1
  18. package/dist/tui/app.d.ts +9 -0
  19. package/dist/tui/app.d.ts.map +1 -0
  20. package/dist/tui/app.js +40 -0
  21. package/dist/tui/app.js.map +1 -0
  22. package/dist/tui/components/ChatList.d.ts +14 -0
  23. package/dist/tui/components/ChatList.d.ts.map +1 -0
  24. package/dist/tui/components/ChatList.js +11 -0
  25. package/dist/tui/components/ChatList.js.map +1 -0
  26. package/dist/tui/components/ChatMessage.d.ts +14 -0
  27. package/dist/tui/components/ChatMessage.d.ts.map +1 -0
  28. package/dist/tui/components/ChatMessage.js +17 -0
  29. package/dist/tui/components/ChatMessage.js.map +1 -0
  30. package/dist/tui/components/Header.d.ts +9 -0
  31. package/dist/tui/components/Header.d.ts.map +1 -0
  32. package/dist/tui/components/Header.js +6 -0
  33. package/dist/tui/components/Header.js.map +1 -0
  34. package/dist/tui/components/Markdown.d.ts +8 -0
  35. package/dist/tui/components/Markdown.d.ts.map +1 -0
  36. package/dist/tui/components/Markdown.js +31 -0
  37. package/dist/tui/components/Markdown.js.map +1 -0
  38. package/dist/tui/components/Thinking.d.ts +8 -0
  39. package/dist/tui/components/Thinking.d.ts.map +1 -0
  40. package/dist/tui/components/Thinking.js +7 -0
  41. package/dist/tui/components/Thinking.js.map +1 -0
  42. package/dist/tui/components/ToolCall.d.ts +14 -0
  43. package/dist/tui/components/ToolCall.d.ts.map +1 -0
  44. package/dist/tui/components/ToolCall.js +37 -0
  45. package/dist/tui/components/ToolCall.js.map +1 -0
  46. package/dist/tui/components/UserInput.d.ts +9 -0
  47. package/dist/tui/components/UserInput.d.ts.map +1 -0
  48. package/dist/tui/components/UserInput.js +82 -0
  49. package/dist/tui/components/UserInput.js.map +1 -0
  50. package/dist/tui/hooks/useChat.d.ts +11 -0
  51. package/dist/tui/hooks/useChat.d.ts.map +1 -0
  52. package/dist/tui/hooks/useChat.js +138 -0
  53. package/dist/tui/hooks/useChat.js.map +1 -0
  54. package/dist/tui/hooks/useCommands.d.ts +10 -0
  55. package/dist/tui/hooks/useCommands.d.ts.map +1 -0
  56. package/dist/tui/hooks/useCommands.js +51 -0
  57. package/dist/tui/hooks/useCommands.js.map +1 -0
  58. package/dist/tui/index.d.ts +2 -4
  59. package/dist/tui/index.d.ts.map +1 -1
  60. package/dist/tui/index.js +2 -4
  61. package/dist/tui/index.js.map +1 -1
  62. package/dist/tui/output.d.ts +12 -3
  63. package/dist/tui/output.d.ts.map +1 -1
  64. package/dist/tui/output.js +43 -47
  65. package/dist/tui/output.js.map +1 -1
  66. package/dist/tui/repl.d.ts.map +1 -1
  67. package/dist/tui/repl.js +42 -28
  68. package/dist/tui/repl.js.map +1 -1
  69. package/dist/tui/welcome.d.ts.map +1 -1
  70. package/dist/tui/welcome.js +23 -5
  71. package/dist/tui/welcome.js.map +1 -1
  72. package/install.ps1 +171 -192
  73. package/package.json +9 -1
  74. package/src/app/index.ts +29 -34
  75. package/src/cmd/{index.ts → index.tsx} +52 -47
  76. package/src/config/index.ts +1 -0
  77. package/src/config/types.ts +1 -0
  78. package/src/llm/index.ts +0 -1
  79. package/src/tui/app.tsx +60 -0
  80. package/src/tui/components/ChatList.tsx +29 -0
  81. package/src/tui/components/ChatMessage.tsx +51 -0
  82. package/src/tui/components/Header.tsx +22 -0
  83. package/src/tui/components/Markdown.tsx +35 -0
  84. package/src/tui/components/Thinking.tsx +19 -0
  85. package/src/tui/components/ToolCall.tsx +72 -0
  86. package/src/tui/components/UserInput.tsx +101 -0
  87. package/src/tui/hooks/useChat.ts +154 -0
  88. package/src/tui/hooks/useCommands.ts +63 -0
  89. package/src/tui/index.ts +2 -4
  90. package/src/tui/output.ts +43 -44
  91. package/src/tui/repl.ts +40 -29
  92. package/src/tui/welcome.ts +25 -6
  93. package/tsconfig.json +5 -3
  94. 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 User Interface
2
+ * TUI Module - Ink-based Terminal UI
3
3
  */
4
4
 
5
- export { REPL } from './repl.js';
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 - Beautiful terminal UI
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.4.1'));
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.gray(' ┌─ ') + chalk.bold('You'));
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(chalk.gray(' ') + line);
35
+ console.log(' ' + line);
36
36
  }
37
- console.log(chalk.gray(' └─'));
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
- 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(' │ '));
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
- 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;
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
- 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;
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) + chalk.gray(' ─────────────────────────'));
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 > 120 ? val.slice(0, 120) + '...' : val;
87
+ display = val.length > 100 ? val.slice(0, 100) + '...' : val;
89
88
  } else {
90
89
  display = JSON.stringify(val);
91
- if (display.length > 120) display = display.slice(0, 120) + '...';
90
+ if (display.length > 100) display = display.slice(0, 100) + '...';
92
91
  }
93
- console.log(chalk.gray(' ' + key + ': ') + chalk.white(display));
92
+ console.log(chalk.gray(' ' + key + ': ') + display);
94
93
  }
95
94
  } catch {
96
- const display = args.length > 150 ? args.slice(0, 150) + '...' : args;
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 = 25;
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(chalk.yellow(' ────────────────────────────────────'));
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(' ┌─ Error'));
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(' ') + line);
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
- this.divider();
204
+ console.log(chalk.gray(' ' + '─'.repeat(40)));
206
205
  for (const item of items) {
207
- console.log(' ' + chalk.gray(item.label.padEnd(14)) + chalk.white(item.value));
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
- this.divider();
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
- this.divider();
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.getAgent().run(this.app.session.getMessages());
109
+ const stream = this.app.streamResponse();
110
110
 
111
111
  for await (const chunk of stream) {
112
112
  this.output.stopThinking();
113
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;
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
- 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();
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
- // Regular text
140
- if (part) {
141
- if (!hasStartedOutput) {
142
- this.output.beginAssistant();
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();
@@ -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.4.1'));
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
- const model = await ask(chalk.cyan(' Model ❯ '));
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
- this.saveConfig(providerName, selectedModel.id, apiKey, baseUrl);
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": "ESNext",
5
- "moduleResolution": "node",
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"]