1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
|
// pages/index.tsx
import { useState, useCallback, useRef } from 'react';
import dynamic from 'next/dynamic';
import axios from 'axios';
import debounce from 'lodash.debounce';
// 动态导入 Monaco Editor,使其仅在客户端加载。
const MonacoEditor = dynamic(
() => import('@monaco-editor/react').then(mod => mod.default),
{ ssr: false }
);
type CursorPosition = {
column: number;
lineNumber: number;
};
const Home = () => {
// 存储编辑器当前代码的状态。
const [code, setCode] = useState<string>(`// Start coding here...
function helloWorld() {
console.log("Hello, world!");
}
// Write a comment below to get a suggestion
//`);
// 存储从 Goose AI 获取的建议的状态。
const [suggestion, setSuggestion] = useState<string>('');
// 处理加载指示器的状态。
const [loading, setLoading] = useState<boolean>(false);
// 处理错误的状态。
const [error, setError] = useState<string>('');
// Ref 用于存储 Monaco Editor 实例,以访问如 getPosition 等方法。
const editorRef = useRef<any>(null);
/**
* 如果最后一行以 // 开头,则从中提取提示。
*
* @param codeText - 编辑器中的完整文本。
* @returns 修剪后的注释文本,如果未找到则返回 null。
*/
const extractCommentPrompt = (codeText: string): string | null => {
const lines = codeText.split('\n');
const lastLine = lines[lines.length - 1].trim();
if (lastLine.startsWith('//')) {
// 移除注释标记并返回文本。
return lastLine.slice(2).trim();
}
return null;
};
/**
* 防抖函数用于调用 Goose AI API。
* 这可以防止用户在输入时进行过多的 API 调用。
*/
const debouncedFetchSuggestion = useCallback(
debounce((prompt: string, currentCode: string, cursorPosition: CursorPosition) => {
fetchSuggestion(prompt, currentCode, cursorPosition);
}, 500),
[]
);
/**
* 使用提供的提示、代码上下文和光标位置调用 Goose AI 的 API。
*
* @param prompt - 从代码中提取的注释提示。
* @param currentCode - 编辑器的当前内容。
* @param cursorPosition - 编辑器中当前的光标位置。
*/
const fetchSuggestion = async (
prompt: string,
currentCode: string,
cursorPosition: CursorPosition
) => {
setLoading(true);
setError('');
try {
// 向 Goose AI 的建议端点发送 POST 请求。
const response = await axios.post(
'https://api.goose.ai/v1/suggestions',
{
prompt,
codeContext: currentCode,
cursorPosition,
language: 'javascript'
},
{
headers: {
'Authorization': `Bearer ${process.env.NEXT_PUBLIC_GOOSE_AI_API_KEY}`,
'Content-Type': 'application/json'
}
}
);
// 使用返回的建议更新建议状态。
setSuggestion(response.data.suggestion);
} catch (err) {
console.error('Error fetching suggestion:', err);
setError('Error fetching suggestion. Please try again.');
} finally {
setLoading(false);
}
};
/**
* 处理编辑器中的更改。更新代码状态,
* 提取提示(如果有),并触发防抖的 API 调用。
*
* @param newValue - 编辑器中的新代码。
*/
const handleEditorChange = (newValue: string) => {
setCode(newValue);
const prompt = extractCommentPrompt(newValue);
if (prompt) {
// 从编辑器实例检索当前光标位置。
const position = editorRef.current?.getPosition();
if (position) {
// 触发防抖的 API 调用。
debouncedFetchSuggestion(prompt, newValue, position);
}
}
};
/**
* 当 Monaco Editor 挂载时调用。
* 存储对编辑器实例的引用以供以后使用。
*
* @param editor - Monaco Editor 实例。
*/
const editorDidMount = (editor: any) => {
editorRef.current = editor;
};
/**
* 在当前光标位置将获取的建议插入编辑器。
*/
const acceptSuggestion = () => {
if (editorRef.current && suggestion) {
const position = editorRef.current.getPosition();
// 创建插入建议的编辑操作。
const id = { major: 1, minor: 1 }; // 编辑标识符。
const op = {
identifier: id,
// 在当前光标位置定义插入范围。
range: new editorRef.current.constructor.Range(
position.lineNumber,
position.column,
position.lineNumber,
position.column
),
text: suggestion,
forceMoveMarkers: true
};
// 在编辑器中执行编辑操作。
editorRef.current.executeEdits('insert-suggestion', [op]);
// 插入后可选地清除建议。
setSuggestion('');
}
};
return (
<div className="flex h-screen">
{/* 主代码编辑器部分 */}
<div className="flex-1">
<MonacoEditor
height="100%"
language="javascript"
theme="vs-dark"
value={code}
onChange={handleEditorChange}
editorDidMount={editorDidMount}
options={{
automaticLayout: true,
fontSize: 14,
}}
/>
</div>
{/* 建议侧边栏 */}
<div className="w-80 p-4 bg-gray-800 text-white overflow-y-auto">
<h3 className="text-lg font-bold mb-2">Suggestions</h3>
{loading && <p>Loading suggestion...</p>}
{error && <p className="text-red-500">{error}</p>}
{suggestion && (
<div>
<pre className="whitespace-pre-wrap bg-gray-700 p-2 rounded">
{suggestion}
</pre>
<button
onClick={acceptSuggestion}
className="mt-2 bg-blue-500 hover:bg-blue-600 text-white py-1 px-2 rounded"
>
Accept Suggestion
</button>
</div>
)}
{!loading && !suggestion && !error && (
<p className="text-gray-400">Type a comment for a suggestion.</p>
)}
</div>
</div>
);
};
export default Home;
|