thatgfsj-code 0.9.0 → 0.9.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 +1 -1
- package/dist/tui/components/ChatMessage.js +7 -7
- package/dist/tui/components/ChatMessage.js.map +1 -1
- package/dist/tui/components/Header.js +1 -1
- package/dist/tui/components/ToolCall.d.ts +1 -1
- package/dist/tui/components/ToolCall.d.ts.map +1 -1
- package/dist/tui/components/ToolCall.js +10 -18
- package/dist/tui/components/ToolCall.js.map +1 -1
- package/dist/tui/hooks/useChat.d.ts.map +1 -1
- package/dist/tui/hooks/useChat.js +12 -18
- package/dist/tui/hooks/useChat.js.map +1 -1
- package/package.json +2 -1
- package/src/cmd/index.tsx +1 -1
- package/src/tui/components/ChatMessage.tsx +12 -12
- package/src/tui/components/Header.tsx +1 -1
- package/src/tui/components/ToolCall.tsx +18 -21
- package/src/tui/hooks/useChat.ts +14 -19
package/dist/cmd/index.js
CHANGED
|
@@ -24,7 +24,7 @@ process.on('unhandledRejection', (reason) => {
|
|
|
24
24
|
program
|
|
25
25
|
.name('gfcode')
|
|
26
26
|
.description('Thatgfsj Code - AI Coding Assistant')
|
|
27
|
-
.version('0.9.
|
|
27
|
+
.version('0.9.1')
|
|
28
28
|
.argument('[prompt]', 'Task to execute (omit to start interactive mode)')
|
|
29
29
|
.option('-m, --model <model>', 'Specify model')
|
|
30
30
|
.option('-i, --interactive', 'Force interactive mode')
|
|
@@ -4,16 +4,16 @@ import React from 'react';
|
|
|
4
4
|
import { Box, Text } from 'ink';
|
|
5
5
|
import { Markdown } from './Markdown.js';
|
|
6
6
|
import { ToolCall } from './ToolCall.js';
|
|
7
|
-
function UserMessage({ content
|
|
8
|
-
return (
|
|
7
|
+
function UserMessage({ content }) {
|
|
8
|
+
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, paddingLeft: 1, children: [_jsx(Text, { bold: true, color: "#06B6D4", children: "You" }), _jsx(Box, { paddingLeft: 2, children: _jsx(Text, { children: content }) })] }));
|
|
9
9
|
}
|
|
10
|
-
function AssistantMessage({ content, toolCalls
|
|
11
|
-
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [toolCalls && toolCalls.map((tc, i) => (_jsx(ToolCall, { tool: tc
|
|
10
|
+
function AssistantMessage({ content, toolCalls }) {
|
|
11
|
+
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [toolCalls && toolCalls.map((tc, i) => (_jsx(ToolCall, { tool: tc }, i))), content && (_jsxs(Box, { flexDirection: "column", paddingLeft: 1, children: [_jsx(Text, { bold: true, color: "#22D3EE", children: "AI" }), _jsx(Box, { paddingLeft: 2, children: _jsx(Markdown, { content: content }) })] }))] }));
|
|
12
12
|
}
|
|
13
|
-
export const ChatMessage = React.memo(function ChatMessage({ message
|
|
13
|
+
export const ChatMessage = React.memo(function ChatMessage({ message }) {
|
|
14
14
|
if (message.role === 'user') {
|
|
15
|
-
return _jsx(UserMessage, { content: message.content
|
|
15
|
+
return _jsx(UserMessage, { content: message.content });
|
|
16
16
|
}
|
|
17
|
-
return _jsx(AssistantMessage, { content: message.content, toolCalls: message.toolCalls
|
|
17
|
+
return _jsx(AssistantMessage, { content: message.content, toolCalls: message.toolCalls });
|
|
18
18
|
});
|
|
19
19
|
//# sourceMappingURL=ChatMessage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChatMessage.js","sourceRoot":"","sources":["../../../src/tui/components/ChatMessage.tsx"],"names":[],"mappings":";AAAA,6BAA6B;AAC7B,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAqB,MAAM,eAAe,CAAC;AAa5D,SAAS,WAAW,CAAC,EAAE,OAAO,
|
|
1
|
+
{"version":3,"file":"ChatMessage.js","sourceRoot":"","sources":["../../../src/tui/components/ChatMessage.tsx"],"names":[],"mappings":";AAAA,6BAA6B;AAC7B,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAqB,MAAM,eAAe,CAAC;AAa5D,SAAS,WAAW,CAAC,EAAE,OAAO,EAAuB;IACnD,OAAO,CACL,MAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,EAAC,YAAY,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,aACzD,KAAC,IAAI,IAAC,IAAI,QAAC,KAAK,EAAC,SAAS,oBAAW,EACrC,KAAC,GAAG,IAAC,WAAW,EAAE,CAAC,YACjB,KAAC,IAAI,cAAE,OAAO,GAAQ,GAClB,IACF,CACP,CAAC;AACJ,CAAC;AAED,SAAS,gBAAgB,CAAC,EAAE,OAAO,EAAE,SAAS,EAAmD;IAC/F,OAAO,CACL,MAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,EAAC,YAAY,EAAE,CAAC,aACxC,SAAS,IAAI,SAAS,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,CACrC,KAAC,QAAQ,IAAS,IAAI,EAAE,EAAE,IAAX,CAAC,CAAc,CAC/B,CAAC,EACD,OAAO,IAAI,CACV,MAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,EAAC,WAAW,EAAE,CAAC,aACxC,KAAC,IAAI,IAAC,IAAI,QAAC,KAAK,EAAC,SAAS,mBAAU,EACpC,KAAC,GAAG,IAAC,WAAW,EAAE,CAAC,YACjB,KAAC,QAAQ,IAAC,OAAO,EAAE,OAAO,GAAI,GAC1B,IACF,CACP,IACG,CACP,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,WAAW,GAAG,KAAK,CAAC,IAAI,CAAC,SAAS,WAAW,CAAC,EAAE,OAAO,EAAS;IAC3E,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QAC5B,OAAO,KAAC,WAAW,IAAC,OAAO,EAAE,OAAO,CAAC,OAAO,GAAI,CAAC;IACnD,CAAC;IACD,OAAO,KAAC,gBAAgB,IAAC,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,GAAI,CAAC;AACtF,CAAC,CAAC,CAAC"}
|
|
@@ -3,6 +3,6 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import React from 'react';
|
|
4
4
|
import { Box, Text } from 'ink';
|
|
5
5
|
export const Header = React.memo(function Header({ provider, model }) {
|
|
6
|
-
return (_jsxs(Box, { flexDirection: "column", marginBottom: 0, children: [_jsxs(Box, { justifyContent: "space-between", width: "100%", children: [_jsxs(Box, { children: [_jsx(Text, { color: "#06B6D4", bold: true, children: " \u26A1 " }), _jsx(Text, { color: "#22D3EE", bold: true, children: "THATGFSJ CODE" }), _jsx(Text, { dimColor: true, children: " v0.9.
|
|
6
|
+
return (_jsxs(Box, { flexDirection: "column", marginBottom: 0, children: [_jsxs(Box, { justifyContent: "space-between", width: "100%", children: [_jsxs(Box, { children: [_jsx(Text, { color: "#06B6D4", bold: true, children: " \u26A1 " }), _jsx(Text, { color: "#22D3EE", bold: true, children: "THATGFSJ CODE" }), _jsx(Text, { dimColor: true, children: " v0.9.1" })] }), _jsxs(Box, { children: [_jsxs(Text, { color: "#06B6D4", bold: true, children: [" ", provider, " "] }), _jsx(Text, { dimColor: true, children: "/" }), _jsxs(Text, { color: "#22D3EE", children: [" ", model, " "] })] })] }), _jsx(Text, { color: "#374151", children: '─'.repeat(80) })] }));
|
|
7
7
|
});
|
|
8
8
|
//# sourceMappingURL=Header.js.map
|
|
@@ -10,6 +10,6 @@ interface Props {
|
|
|
10
10
|
tool: ToolCallData;
|
|
11
11
|
width?: number;
|
|
12
12
|
}
|
|
13
|
-
export declare function ToolCall({ tool
|
|
13
|
+
export declare function ToolCall({ tool }: Props): React.JSX.Element;
|
|
14
14
|
export type { ToolCallData };
|
|
15
15
|
//# sourceMappingURL=ToolCall.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ToolCall.d.ts","sourceRoot":"","sources":["../../../src/tui/components/ToolCall.tsx"],"names":[],"mappings":"AAAA,6BAA6B;AAC7B,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,UAAU,YAAY;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,UAAU,KAAK;IACb,IAAI,EAAE,YAAY,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;
|
|
1
|
+
{"version":3,"file":"ToolCall.d.ts","sourceRoot":"","sources":["../../../src/tui/components/ToolCall.tsx"],"names":[],"mappings":"AAAA,6BAA6B;AAC7B,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,UAAU,YAAY;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,UAAU,KAAK;IACb,IAAI,EAAE,YAAY,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA4BD,wBAAgB,QAAQ,CAAC,EAAE,IAAI,EAAE,EAAE,KAAK,qBA0BvC;AAED,YAAY,EAAE,YAAY,EAAE,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
|
-
function
|
|
3
|
+
function formatToolName(name, args) {
|
|
4
4
|
try {
|
|
5
5
|
const obj = JSON.parse(args);
|
|
6
6
|
switch (name) {
|
|
@@ -9,34 +9,26 @@ function formatToolParams(name, args) {
|
|
|
9
9
|
const path = (obj.path || '').replace(/.*[/\\]/, '');
|
|
10
10
|
return `${action} ${path || obj.path || ''}`;
|
|
11
11
|
}
|
|
12
|
-
case 'shell': return obj.command || '';
|
|
12
|
+
case 'shell': return obj.command?.slice(0, 60) || '';
|
|
13
13
|
case 'git': return obj.command || '';
|
|
14
14
|
case 'search': return obj.query || obj.pattern || '';
|
|
15
15
|
case 'nwt': return obj.action || '';
|
|
16
|
-
default:
|
|
17
|
-
const first = Object.entries(obj)[0];
|
|
18
|
-
if (!first)
|
|
19
|
-
return '';
|
|
20
|
-
const val = typeof first[1] === 'string' && first[1].length > 40
|
|
21
|
-
? first[1].slice(0, 40) + '...'
|
|
22
|
-
: JSON.stringify(first[1]);
|
|
23
|
-
return `${first[0]}=${val}`;
|
|
24
|
-
}
|
|
16
|
+
default: return Object.keys(obj).slice(0, 2).join(', ');
|
|
25
17
|
}
|
|
26
18
|
}
|
|
27
19
|
catch {
|
|
28
|
-
return
|
|
20
|
+
return '';
|
|
29
21
|
}
|
|
30
22
|
}
|
|
31
|
-
function
|
|
23
|
+
function truncateOutput(output, maxLines = 8) {
|
|
32
24
|
const lines = output.split('\n');
|
|
33
25
|
if (lines.length <= maxLines)
|
|
34
26
|
return { text: output, truncated: false };
|
|
35
27
|
return { text: lines.slice(0, maxLines).join('\n'), truncated: true };
|
|
36
28
|
}
|
|
37
|
-
export function ToolCall({ tool
|
|
38
|
-
const
|
|
39
|
-
const result = tool.result ?
|
|
40
|
-
return (_jsxs(Box, {
|
|
29
|
+
export function ToolCall({ tool }) {
|
|
30
|
+
const label = formatToolName(tool.name, tool.args);
|
|
31
|
+
const result = tool.result ? truncateOutput(tool.result) : null;
|
|
32
|
+
return (_jsxs(Box, { flexDirection: "column", marginBottom: 0, paddingLeft: 1, children: [_jsxs(Box, { children: [_jsx(Text, { color: "#06B6D4", bold: true, children: "\u2699 " }), _jsx(Text, { color: "#06B6D4", children: tool.name }), label && _jsxs(Text, { color: "#64748B", children: [" ", label] })] }), result && (_jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [result.text.split('\n').map((line, i) => (_jsx(Text, { color: tool.isError ? '#EF4444' : '#64748B', wrap: "truncate", children: line }, i))), result.truncated && _jsx(Text, { dimColor: true, children: " ..." })] }))] }));
|
|
41
33
|
}
|
|
42
34
|
//# sourceMappingURL=ToolCall.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ToolCall.js","sourceRoot":"","sources":["../../../src/tui/components/ToolCall.tsx"],"names":[],"mappings":";AAEA,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAchC,SAAS,
|
|
1
|
+
{"version":3,"file":"ToolCall.js","sourceRoot":"","sources":["../../../src/tui/components/ToolCall.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useChat.d.ts","sourceRoot":"","sources":["../../../src/tui/hooks/useChat.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,oBAAoB,CAAC;AAS9C,wBAAgB,OAAO,CAAC,GAAG,EAAE,GAAG;
|
|
1
|
+
{"version":3,"file":"useChat.d.ts","sourceRoot":"","sources":["../../../src/tui/hooks/useChat.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,oBAAoB,CAAC;AAS9C,wBAAgB,OAAO,CAAC,GAAG,EAAE,GAAG;yBASU,MAAM;cAfpC,WAAW,EAAE;gBACX,OAAO;eACR,MAAM;wBACG,YAAY,EAAE;EA2InC"}
|
|
@@ -7,13 +7,10 @@ export function useChat(app) {
|
|
|
7
7
|
streamingToolCalls: [],
|
|
8
8
|
});
|
|
9
9
|
const processingRef = useRef(false);
|
|
10
|
-
const abortRef = useRef(false);
|
|
11
10
|
const sendMessage = useCallback((input) => {
|
|
12
11
|
if (processingRef.current)
|
|
13
12
|
return;
|
|
14
13
|
processingRef.current = true;
|
|
15
|
-
abortRef.current = false;
|
|
16
|
-
// Add user message
|
|
17
14
|
setState(prev => ({
|
|
18
15
|
...prev,
|
|
19
16
|
messages: [...prev.messages, { role: 'user', content: input }],
|
|
@@ -22,15 +19,14 @@ export function useChat(app) {
|
|
|
22
19
|
streamingToolCalls: [],
|
|
23
20
|
}));
|
|
24
21
|
app.session.addMessage('user', input);
|
|
25
|
-
// Process stream in background (non-blocking)
|
|
26
22
|
const stream = app.streamResponse();
|
|
27
23
|
let fullContent = '';
|
|
28
24
|
let currentToolCalls = [];
|
|
25
|
+
let lastUpdateTime = 0;
|
|
26
|
+
const THROTTLE_MS = 50; // Update UI at most every 50ms
|
|
29
27
|
const processStream = async () => {
|
|
30
28
|
try {
|
|
31
29
|
for await (const chunk of stream) {
|
|
32
|
-
if (abortRef.current)
|
|
33
|
-
break;
|
|
34
30
|
if (chunk.includes('@@TOOL@@')) {
|
|
35
31
|
const parts = chunk.split('\n');
|
|
36
32
|
for (const part of parts) {
|
|
@@ -63,21 +59,24 @@ export function useChat(app) {
|
|
|
63
59
|
}
|
|
64
60
|
catch {
|
|
65
61
|
fullContent += part;
|
|
66
|
-
setState(prev => ({ ...prev, isThinking: false, streaming: fullContent }));
|
|
67
62
|
}
|
|
68
63
|
}
|
|
69
64
|
else if (part) {
|
|
70
65
|
fullContent += part;
|
|
71
|
-
setState(prev => ({ ...prev, isThinking: false, streaming: fullContent }));
|
|
72
66
|
}
|
|
73
67
|
}
|
|
74
68
|
}
|
|
75
69
|
else {
|
|
76
70
|
fullContent += chunk;
|
|
71
|
+
}
|
|
72
|
+
// Throttle UI updates
|
|
73
|
+
const now = Date.now();
|
|
74
|
+
if (now - lastUpdateTime >= THROTTLE_MS) {
|
|
75
|
+
lastUpdateTime = now;
|
|
77
76
|
setState(prev => ({ ...prev, isThinking: false, streaming: fullContent }));
|
|
78
77
|
}
|
|
79
78
|
}
|
|
80
|
-
//
|
|
79
|
+
// Final update
|
|
81
80
|
if (fullContent.trim() || currentToolCalls.length > 0) {
|
|
82
81
|
app.session.addMessage('assistant', fullContent);
|
|
83
82
|
}
|
|
@@ -102,15 +101,14 @@ export function useChat(app) {
|
|
|
102
101
|
catch (error) {
|
|
103
102
|
const msg = error.message || String(error);
|
|
104
103
|
let errorMsg = `Error: ${msg}`;
|
|
105
|
-
// Detect API auth/quota errors
|
|
106
104
|
if (msg.includes('401') || msg.includes('403') || msg.includes('Unauthorized')) {
|
|
107
|
-
errorMsg = `❌ API key invalid
|
|
105
|
+
errorMsg = `❌ API key invalid. Run \`gfcode init\` to reconfigure.`;
|
|
108
106
|
}
|
|
109
107
|
else if (msg.includes('429') || msg.includes('rate limit') || msg.includes('quota')) {
|
|
110
|
-
errorMsg = `❌ Rate limit
|
|
108
|
+
errorMsg = `❌ Rate limit exceeded. Wait or run \`gfcode init\` to switch provider.`;
|
|
111
109
|
}
|
|
112
110
|
else if (msg.includes('ECONNREFUSED') || msg.includes('ENOTFOUND')) {
|
|
113
|
-
errorMsg = `❌ Cannot connect
|
|
111
|
+
errorMsg = `❌ Cannot connect. Check network or run \`gfcode init\`.`;
|
|
114
112
|
}
|
|
115
113
|
setState(prev => ({
|
|
116
114
|
...prev,
|
|
@@ -128,12 +126,8 @@ export function useChat(app) {
|
|
|
128
126
|
}
|
|
129
127
|
processingRef.current = false;
|
|
130
128
|
};
|
|
131
|
-
// Start processing without blocking
|
|
132
129
|
processStream();
|
|
133
130
|
}, [app]);
|
|
134
|
-
return {
|
|
135
|
-
...state,
|
|
136
|
-
sendMessage,
|
|
137
|
-
};
|
|
131
|
+
return { ...state, sendMessage };
|
|
138
132
|
}
|
|
139
133
|
//# sourceMappingURL=useChat.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useChat.js","sourceRoot":"","sources":["../../../src/tui/hooks/useChat.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"useChat.js","sourceRoot":"","sources":["../../../src/tui/hooks/useChat.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAYtD,MAAM,UAAU,OAAO,CAAC,GAAQ;IAC9B,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAY;QAC5C,QAAQ,EAAE,EAAE;QACZ,UAAU,EAAE,KAAK;QACjB,SAAS,EAAE,EAAE;QACb,kBAAkB,EAAE,EAAE;KACvB,CAAC,CAAC;IACH,MAAM,aAAa,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAEpC,MAAM,WAAW,GAAG,WAAW,CAAC,CAAC,KAAa,EAAE,EAAE;QAChD,IAAI,aAAa,CAAC,OAAO;YAAE,OAAO;QAClC,aAAa,CAAC,OAAO,GAAG,IAAI,CAAC;QAE7B,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAChB,GAAG,IAAI;YACP,QAAQ,EAAE,CAAC,GAAG,IAAI,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;YAC9D,UAAU,EAAE,IAAI;YAChB,SAAS,EAAE,EAAE;YACb,kBAAkB,EAAE,EAAE;SACvB,CAAC,CAAC,CAAC;QAEJ,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QAEtC,MAAM,MAAM,GAAG,GAAG,CAAC,cAAc,EAAE,CAAC;QACpC,IAAI,WAAW,GAAG,EAAE,CAAC;QACrB,IAAI,gBAAgB,GAAmB,EAAE,CAAC;QAC1C,IAAI,cAAc,GAAG,CAAC,CAAC;QACvB,MAAM,WAAW,GAAG,EAAE,CAAC,CAAC,+BAA+B;QAEvD,MAAM,aAAa,GAAG,KAAK,IAAI,EAAE;YAC/B,IAAI,CAAC;gBACH,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;oBACjC,IAAI,KAAK,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;wBAC/B,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;wBAChC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;4BACzB,IAAI,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;gCAChC,IAAI,CAAC;oCACH,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;oCACvC,IAAI,IAAI,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;wCAC3B,gBAAgB,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC,CAAC;wCAClE,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;4CAChB,GAAG,IAAI;4CACP,UAAU,EAAE,KAAK;4CACjB,kBAAkB,EAAE,CAAC,GAAG,gBAAgB,CAAC;yCAC1C,CAAC,CAAC,CAAC;oCACN,CAAC;yCAAM,IAAI,IAAI,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;wCACpC,MAAM,OAAO,GAAG,gBAAgB,CAAC,MAAM,GAAG,CAAC,CAAC;wCAC5C,IAAI,OAAO,IAAI,CAAC,EAAE,CAAC;4CACjB,gBAAgB,CAAC,OAAO,CAAC,GAAG;gDAC1B,GAAG,gBAAgB,CAAC,OAAO,CAAC;gDAC5B,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,KAAK,IAAI,EAAE;gDACvC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK;6CACtB,CAAC;wCACJ,CAAC;wCACD,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;4CAChB,GAAG,IAAI;4CACP,kBAAkB,EAAE,CAAC,GAAG,gBAAgB,CAAC;4CACzC,UAAU,EAAE,IAAI;yCACjB,CAAC,CAAC,CAAC;oCACN,CAAC;gCACH,CAAC;gCAAC,MAAM,CAAC;oCACP,WAAW,IAAI,IAAI,CAAC;gCACtB,CAAC;4BACH,CAAC;iCAAM,IAAI,IAAI,EAAE,CAAC;gCAChB,WAAW,IAAI,IAAI,CAAC;4BACtB,CAAC;wBACH,CAAC;oBACH,CAAC;yBAAM,CAAC;wBACN,WAAW,IAAI,KAAK,CAAC;oBACvB,CAAC;oBAED,sBAAsB;oBACtB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;oBACvB,IAAI,GAAG,GAAG,cAAc,IAAI,WAAW,EAAE,CAAC;wBACxC,cAAc,GAAG,GAAG,CAAC;wBACrB,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC;oBAC7E,CAAC;gBACH,CAAC;gBAED,eAAe;gBACf,IAAI,WAAW,CAAC,IAAI,EAAE,IAAI,gBAAgB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBACtD,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;gBACnD,CAAC;gBAED,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;oBAChB,GAAG,IAAI;oBACP,QAAQ,EAAE;wBACR,GAAG,IAAI,CAAC,QAAQ;wBAChB,GAAG,CAAC,WAAW,CAAC,IAAI,EAAE,IAAI,gBAAgB,CAAC,MAAM,GAAG,CAAC;4BACnD,CAAC,CAAC,CAAC;oCACC,IAAI,EAAE,WAAoB;oCAC1B,OAAO,EAAE,WAAW;oCACpB,SAAS,EAAE,gBAAgB,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,SAAS;iCACtE,CAAC;4BACJ,CAAC,CAAC,EAAE,CAAC;qBACR;oBACD,SAAS,EAAE,EAAE;oBACb,kBAAkB,EAAE,EAAE;oBACtB,UAAU,EAAE,KAAK;iBAClB,CAAC,CAAC,CAAC;gBAEJ,GAAG,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;YACzB,CAAC;YAAC,OAAO,KAAU,EAAE,CAAC;gBACpB,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC;gBAC3C,IAAI,QAAQ,GAAG,UAAU,GAAG,EAAE,CAAC;gBAE/B,IAAI,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;oBAC/E,QAAQ,GAAG,wDAAwD,CAAC;gBACtE,CAAC;qBAAM,IAAI,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;oBACtF,QAAQ,GAAG,wEAAwE,CAAC;gBACtF,CAAC;qBAAM,IAAI,GAAG,CAAC,QAAQ,CAAC,cAAc,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;oBACrE,QAAQ,GAAG,yDAAyD,CAAC;gBACvE,CAAC;gBAED,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;oBAChB,GAAG,IAAI;oBACP,QAAQ,EAAE;wBACR,GAAG,IAAI,CAAC,QAAQ;wBAChB,GAAG,CAAC,WAAW,CAAC,IAAI,EAAE;4BACpB,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,WAAoB,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;4BACxD,CAAC,CAAC,EAAE,CAAC;wBACP,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE;qBACzC;oBACD,SAAS,EAAE,EAAE;oBACb,kBAAkB,EAAE,EAAE;oBACtB,UAAU,EAAE,KAAK;iBAClB,CAAC,CAAC,CAAC;YACN,CAAC;YAED,aAAa,CAAC,OAAO,GAAG,KAAK,CAAC;QAChC,CAAC,CAAC;QAEF,aAAa,EAAE,CAAC;IAClB,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAEV,OAAO,EAAE,GAAG,KAAK,EAAE,WAAW,EAAE,CAAC;AACnC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thatgfsj-code",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "Thatgfsj Code - AI Coding Assistant",
|
|
5
5
|
"main": "dist/cmd/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"marked": "^15.0.12",
|
|
34
34
|
"marked-terminal": "^7.3.0",
|
|
35
35
|
"ora": "^7.0.1",
|
|
36
|
+
"playwright": "^1.61.1",
|
|
36
37
|
"react": "^19.2.7",
|
|
37
38
|
"readline": "^1.3.0"
|
|
38
39
|
},
|
package/src/cmd/index.tsx
CHANGED
|
@@ -28,7 +28,7 @@ process.on('unhandledRejection', (reason) => {
|
|
|
28
28
|
program
|
|
29
29
|
.name('gfcode')
|
|
30
30
|
.description('Thatgfsj Code - AI Coding Assistant')
|
|
31
|
-
.version('0.9.
|
|
31
|
+
.version('0.9.1')
|
|
32
32
|
.argument('[prompt]', 'Task to execute (omit to start interactive mode)')
|
|
33
33
|
.option('-m, --model <model>', 'Specify model')
|
|
34
34
|
.option('-i, --interactive', 'Force interactive mode')
|
|
@@ -15,27 +15,27 @@ interface Props {
|
|
|
15
15
|
width?: number;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
function UserMessage({ content
|
|
18
|
+
function UserMessage({ content }: { content: string }) {
|
|
19
19
|
return (
|
|
20
|
-
<Box
|
|
21
|
-
<
|
|
22
|
-
|
|
20
|
+
<Box flexDirection="column" marginBottom={1} paddingLeft={1}>
|
|
21
|
+
<Text bold color="#06B6D4">You</Text>
|
|
22
|
+
<Box paddingLeft={2}>
|
|
23
23
|
<Text>{content}</Text>
|
|
24
24
|
</Box>
|
|
25
25
|
</Box>
|
|
26
26
|
);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
function AssistantMessage({ content, toolCalls
|
|
29
|
+
function AssistantMessage({ content, toolCalls }: { content: string; toolCalls?: ToolCallData[] }) {
|
|
30
30
|
return (
|
|
31
31
|
<Box flexDirection="column" marginBottom={1}>
|
|
32
32
|
{toolCalls && toolCalls.map((tc, i) => (
|
|
33
|
-
<ToolCall key={i} tool={tc}
|
|
33
|
+
<ToolCall key={i} tool={tc} />
|
|
34
34
|
))}
|
|
35
35
|
{content && (
|
|
36
|
-
<Box
|
|
37
|
-
<
|
|
38
|
-
|
|
36
|
+
<Box flexDirection="column" paddingLeft={1}>
|
|
37
|
+
<Text bold color="#22D3EE">AI</Text>
|
|
38
|
+
<Box paddingLeft={2}>
|
|
39
39
|
<Markdown content={content} />
|
|
40
40
|
</Box>
|
|
41
41
|
</Box>
|
|
@@ -44,11 +44,11 @@ function AssistantMessage({ content, toolCalls, width }: { content: string; tool
|
|
|
44
44
|
);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
export const ChatMessage = React.memo(function ChatMessage({ message
|
|
47
|
+
export const ChatMessage = React.memo(function ChatMessage({ message }: Props) {
|
|
48
48
|
if (message.role === 'user') {
|
|
49
|
-
return <UserMessage content={message.content}
|
|
49
|
+
return <UserMessage content={message.content} />;
|
|
50
50
|
}
|
|
51
|
-
return <AssistantMessage content={message.content} toolCalls={message.toolCalls}
|
|
51
|
+
return <AssistantMessage content={message.content} toolCalls={message.toolCalls} />;
|
|
52
52
|
});
|
|
53
53
|
|
|
54
54
|
export type { MessageData };
|
|
@@ -14,7 +14,7 @@ export const Header = React.memo(function Header({ provider, model }: Props) {
|
|
|
14
14
|
<Box>
|
|
15
15
|
<Text color="#06B6D4" bold> ⚡ </Text>
|
|
16
16
|
<Text color="#22D3EE" bold>THATGFSJ CODE</Text>
|
|
17
|
-
<Text dimColor> v0.9.
|
|
17
|
+
<Text dimColor> v0.9.1</Text>
|
|
18
18
|
</Box>
|
|
19
19
|
<Box>
|
|
20
20
|
<Text color="#06B6D4" bold> {provider} </Text>
|
|
@@ -14,7 +14,7 @@ interface Props {
|
|
|
14
14
|
width?: number;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
function
|
|
17
|
+
function formatToolName(name: string, args: string): string {
|
|
18
18
|
try {
|
|
19
19
|
const obj = JSON.parse(args);
|
|
20
20
|
switch (name) {
|
|
@@ -23,48 +23,45 @@ function formatToolParams(name: string, args: string): string {
|
|
|
23
23
|
const path = (obj.path || '').replace(/.*[/\\]/, '');
|
|
24
24
|
return `${action} ${path || obj.path || ''}`;
|
|
25
25
|
}
|
|
26
|
-
case 'shell': return obj.command || '';
|
|
26
|
+
case 'shell': return obj.command?.slice(0, 60) || '';
|
|
27
27
|
case 'git': return obj.command || '';
|
|
28
28
|
case 'search': return obj.query || obj.pattern || '';
|
|
29
29
|
case 'nwt': return obj.action || '';
|
|
30
|
-
default:
|
|
31
|
-
const first = Object.entries(obj)[0];
|
|
32
|
-
if (!first) return '';
|
|
33
|
-
const val = typeof first[1] === 'string' && first[1].length > 40
|
|
34
|
-
? first[1].slice(0, 40) + '...'
|
|
35
|
-
: JSON.stringify(first[1]);
|
|
36
|
-
return `${first[0]}=${val}`;
|
|
37
|
-
}
|
|
30
|
+
default: return Object.keys(obj).slice(0, 2).join(', ');
|
|
38
31
|
}
|
|
39
32
|
} catch {
|
|
40
|
-
return
|
|
33
|
+
return '';
|
|
41
34
|
}
|
|
42
35
|
}
|
|
43
36
|
|
|
44
|
-
function
|
|
37
|
+
function truncateOutput(output: string, maxLines = 8): { text: string; truncated: boolean } {
|
|
45
38
|
const lines = output.split('\n');
|
|
46
39
|
if (lines.length <= maxLines) return { text: output, truncated: false };
|
|
47
40
|
return { text: lines.slice(0, maxLines).join('\n'), truncated: true };
|
|
48
41
|
}
|
|
49
42
|
|
|
50
|
-
export function ToolCall({ tool
|
|
51
|
-
const
|
|
52
|
-
const result = tool.result ?
|
|
43
|
+
export function ToolCall({ tool }: Props) {
|
|
44
|
+
const label = formatToolName(tool.name, tool.args);
|
|
45
|
+
const result = tool.result ? truncateOutput(tool.result) : null;
|
|
53
46
|
|
|
54
47
|
return (
|
|
55
|
-
<Box
|
|
48
|
+
<Box flexDirection="column" marginBottom={0} paddingLeft={1}>
|
|
49
|
+
{/* Tool header */}
|
|
56
50
|
<Box>
|
|
57
|
-
<Text color="#06B6D4" bold>⚙
|
|
58
|
-
<Text color="#
|
|
51
|
+
<Text color="#06B6D4" bold>⚙ </Text>
|
|
52
|
+
<Text color="#06B6D4">{tool.name}</Text>
|
|
53
|
+
{label && <Text color="#64748B"> {label}</Text>}
|
|
59
54
|
</Box>
|
|
55
|
+
|
|
56
|
+
{/* Result */}
|
|
60
57
|
{result && (
|
|
61
|
-
<Box paddingLeft={
|
|
58
|
+
<Box paddingLeft={2} flexDirection="column">
|
|
62
59
|
{result.text.split('\n').map((line, i) => (
|
|
63
|
-
<Text key={i} color={tool.isError ? '#EF4444' : '#64748B'}>
|
|
60
|
+
<Text key={i} color={tool.isError ? '#EF4444' : '#64748B'} wrap="truncate">
|
|
64
61
|
{line}
|
|
65
62
|
</Text>
|
|
66
63
|
))}
|
|
67
|
-
{result.truncated && <Text dimColor
|
|
64
|
+
{result.truncated && <Text dimColor> ...</Text>}
|
|
68
65
|
</Box>
|
|
69
66
|
)}
|
|
70
67
|
</Box>
|
package/src/tui/hooks/useChat.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState, useCallback, useRef
|
|
1
|
+
import { useState, useCallback, useRef } from 'react';
|
|
2
2
|
import type { MessageData } from '../components/ChatMessage.js';
|
|
3
3
|
import type { ToolCallData } from '../components/ToolCall.js';
|
|
4
4
|
import type { App } from '../../app/index.js';
|
|
@@ -18,14 +18,11 @@ export function useChat(app: App) {
|
|
|
18
18
|
streamingToolCalls: [],
|
|
19
19
|
});
|
|
20
20
|
const processingRef = useRef(false);
|
|
21
|
-
const abortRef = useRef(false);
|
|
22
21
|
|
|
23
22
|
const sendMessage = useCallback((input: string) => {
|
|
24
23
|
if (processingRef.current) return;
|
|
25
24
|
processingRef.current = true;
|
|
26
|
-
abortRef.current = false;
|
|
27
25
|
|
|
28
|
-
// Add user message
|
|
29
26
|
setState(prev => ({
|
|
30
27
|
...prev,
|
|
31
28
|
messages: [...prev.messages, { role: 'user', content: input }],
|
|
@@ -36,16 +33,15 @@ export function useChat(app: App) {
|
|
|
36
33
|
|
|
37
34
|
app.session.addMessage('user', input);
|
|
38
35
|
|
|
39
|
-
// Process stream in background (non-blocking)
|
|
40
36
|
const stream = app.streamResponse();
|
|
41
37
|
let fullContent = '';
|
|
42
38
|
let currentToolCalls: ToolCallData[] = [];
|
|
39
|
+
let lastUpdateTime = 0;
|
|
40
|
+
const THROTTLE_MS = 50; // Update UI at most every 50ms
|
|
43
41
|
|
|
44
42
|
const processStream = async () => {
|
|
45
43
|
try {
|
|
46
44
|
for await (const chunk of stream) {
|
|
47
|
-
if (abortRef.current) break;
|
|
48
|
-
|
|
49
45
|
if (chunk.includes('@@TOOL@@')) {
|
|
50
46
|
const parts = chunk.split('\n');
|
|
51
47
|
for (const part of parts) {
|
|
@@ -76,20 +72,24 @@ export function useChat(app: App) {
|
|
|
76
72
|
}
|
|
77
73
|
} catch {
|
|
78
74
|
fullContent += part;
|
|
79
|
-
setState(prev => ({ ...prev, isThinking: false, streaming: fullContent }));
|
|
80
75
|
}
|
|
81
76
|
} else if (part) {
|
|
82
77
|
fullContent += part;
|
|
83
|
-
setState(prev => ({ ...prev, isThinking: false, streaming: fullContent }));
|
|
84
78
|
}
|
|
85
79
|
}
|
|
86
80
|
} else {
|
|
87
81
|
fullContent += chunk;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Throttle UI updates
|
|
85
|
+
const now = Date.now();
|
|
86
|
+
if (now - lastUpdateTime >= THROTTLE_MS) {
|
|
87
|
+
lastUpdateTime = now;
|
|
88
88
|
setState(prev => ({ ...prev, isThinking: false, streaming: fullContent }));
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
//
|
|
92
|
+
// Final update
|
|
93
93
|
if (fullContent.trim() || currentToolCalls.length > 0) {
|
|
94
94
|
app.session.addMessage('assistant', fullContent);
|
|
95
95
|
}
|
|
@@ -116,13 +116,12 @@ export function useChat(app: App) {
|
|
|
116
116
|
const msg = error.message || String(error);
|
|
117
117
|
let errorMsg = `Error: ${msg}`;
|
|
118
118
|
|
|
119
|
-
// Detect API auth/quota errors
|
|
120
119
|
if (msg.includes('401') || msg.includes('403') || msg.includes('Unauthorized')) {
|
|
121
|
-
errorMsg = `❌ API key invalid
|
|
120
|
+
errorMsg = `❌ API key invalid. Run \`gfcode init\` to reconfigure.`;
|
|
122
121
|
} else if (msg.includes('429') || msg.includes('rate limit') || msg.includes('quota')) {
|
|
123
|
-
errorMsg = `❌ Rate limit
|
|
122
|
+
errorMsg = `❌ Rate limit exceeded. Wait or run \`gfcode init\` to switch provider.`;
|
|
124
123
|
} else if (msg.includes('ECONNREFUSED') || msg.includes('ENOTFOUND')) {
|
|
125
|
-
errorMsg = `❌ Cannot connect
|
|
124
|
+
errorMsg = `❌ Cannot connect. Check network or run \`gfcode init\`.`;
|
|
126
125
|
}
|
|
127
126
|
|
|
128
127
|
setState(prev => ({
|
|
@@ -143,12 +142,8 @@ export function useChat(app: App) {
|
|
|
143
142
|
processingRef.current = false;
|
|
144
143
|
};
|
|
145
144
|
|
|
146
|
-
// Start processing without blocking
|
|
147
145
|
processStream();
|
|
148
146
|
}, [app]);
|
|
149
147
|
|
|
150
|
-
return {
|
|
151
|
-
...state,
|
|
152
|
-
sendMessage,
|
|
153
|
-
};
|
|
148
|
+
return { ...state, sendMessage };
|
|
154
149
|
}
|