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 {
// 发送POST请求到Goose AI的建议端点
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;
|