sam-coder-cli 1.0.0 → 1.0.3
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/bin/agi-cli.js +2 -1
- package/package.json +1 -1
- package/ai-assistant-0.0.1.vsix +0 -0
- package/bin/agi-cli.js.bak +0 -352
- package/dist/agentUtils.js +0 -539
- package/dist/agentUtils.js.map +0 -1
- package/dist/aiAssistantViewProvider.js +0 -2098
- package/dist/aiAssistantViewProvider.js.map +0 -1
- package/dist/extension.js +0 -117
- package/dist/extension.js.map +0 -1
- package/dist/fetch-polyfill.js +0 -9
- package/dist/fetch-polyfill.js.map +0 -1
- package/out/agentUtils.d.ts +0 -28
- package/out/agentUtils.d.ts.map +0 -1
- package/out/agentUtils.js +0 -539
- package/out/agentUtils.js.map +0 -1
- package/out/aiAssistantViewProvider.d.ts +0 -58
- package/out/aiAssistantViewProvider.d.ts.map +0 -1
- package/out/aiAssistantViewProvider.js +0 -2098
- package/out/aiAssistantViewProvider.js.map +0 -1
- package/out/extension.d.ts +0 -4
- package/out/extension.d.ts.map +0 -1
- package/out/extension.js +0 -117
- package/out/extension.js.map +0 -1
- package/out/fetch-polyfill.d.ts +0 -11
- package/out/fetch-polyfill.d.ts.map +0 -1
- package/out/fetch-polyfill.js +0 -9
- package/out/fetch-polyfill.js.map +0 -1
- package/src/agentUtils.ts +0 -583
- package/src/aiAssistantViewProvider.ts +0 -2264
- package/src/cliAgentUtils.js +0 -73
- package/src/extension.ts +0 -112
- package/src/fetch-polyfill.ts +0 -11
- package/tsconfig.json +0 -24
- package/webpack.config.js +0 -45
|
@@ -1,2098 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
-
if (mod && mod.__esModule) return mod;
|
|
20
|
-
var result = {};
|
|
21
|
-
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
-
__setModuleDefault(result, mod);
|
|
23
|
-
return result;
|
|
24
|
-
};
|
|
25
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
-
exports.AIAssistantViewProvider = void 0;
|
|
27
|
-
const vscode = __importStar(require("vscode"));
|
|
28
|
-
const fetch_polyfill_1 = require("./fetch-polyfill");
|
|
29
|
-
const agentUtils_1 = require("./agentUtils");
|
|
30
|
-
// Default API key to use if none is provided in settings
|
|
31
|
-
const DEFAULT_API_KEY = 'fw_3ZM5QnSBpeAvHmRG6qB1FWCm';
|
|
32
|
-
const translations = {
|
|
33
|
-
'en': {
|
|
34
|
-
welcomeMessage: "Hello! I'm Samantha Coder, your AI assistant. Ask me anything or request help with your code. I can analyze and modify files in your workspace.",
|
|
35
|
-
workingMessage: "🤖 I'm working on this task...",
|
|
36
|
-
taskCompletedMessage: "✅ Task completed.",
|
|
37
|
-
errorMessage: "Sorry, I encountered an error. Please check your API key and connection.",
|
|
38
|
-
inputPlaceholder: "Ask something...",
|
|
39
|
-
sendButton: "Send",
|
|
40
|
-
clearButton: "Clear",
|
|
41
|
-
changeLanguageButton: "PT-BR",
|
|
42
|
-
loadingText: "Working on it..."
|
|
43
|
-
},
|
|
44
|
-
'pt-br': {
|
|
45
|
-
welcomeMessage: "Olá! Eu sou Samantha Coder, sua assistente de IA. Pergunte qualquer coisa ou peça ajuda com seu código. Posso analisar e modificar arquivos no seu espaço de trabalho.",
|
|
46
|
-
workingMessage: "🤖 Estou trabalhando nesta tarefa...",
|
|
47
|
-
taskCompletedMessage: "✅ Tarefa concluída.",
|
|
48
|
-
errorMessage: "Desculpe, encontrei um erro. Por favor, verifique sua chave de API e conexão.",
|
|
49
|
-
inputPlaceholder: "Pergunte algo...",
|
|
50
|
-
sendButton: "Enviar",
|
|
51
|
-
clearButton: "Limpar",
|
|
52
|
-
changeLanguageButton: "EN",
|
|
53
|
-
loadingText: "Trabalhando nisso..."
|
|
54
|
-
}
|
|
55
|
-
};
|
|
56
|
-
class AIAssistantViewProvider {
|
|
57
|
-
constructor(_extensionUri) {
|
|
58
|
-
this._extensionUri = _extensionUri;
|
|
59
|
-
this._chats = [];
|
|
60
|
-
this._currentChatId = null;
|
|
61
|
-
this._isProcessingAgentActions = false;
|
|
62
|
-
this._lastEditedFile = null;
|
|
63
|
-
this._lastEditTime = 0;
|
|
64
|
-
this._language = 'en';
|
|
65
|
-
this._conversationHistory = {}; // Store state snapshots
|
|
66
|
-
this._trackedFiles = new Set();
|
|
67
|
-
this._abortController = null; // For canceling fetch requests
|
|
68
|
-
this._isGeneratingResponse = false; // Flag to track if a response is being generated
|
|
69
|
-
this._agentUtils = new agentUtils_1.AgentUtils();
|
|
70
|
-
// Load language setting
|
|
71
|
-
const config = vscode.workspace.getConfiguration('aiAssistant');
|
|
72
|
-
this._language = config.get('language') || 'en';
|
|
73
|
-
// Track language setting changes
|
|
74
|
-
vscode.workspace.onDidChangeConfiguration(e => {
|
|
75
|
-
if (e.affectsConfiguration('aiAssistant.language')) {
|
|
76
|
-
const config = vscode.workspace.getConfiguration('aiAssistant');
|
|
77
|
-
this._language = config.get('language') || 'en';
|
|
78
|
-
this._updateWebviewContent();
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
// Track file edits
|
|
82
|
-
vscode.workspace.onDidChangeTextDocument(e => {
|
|
83
|
-
this._lastEditedFile = e.document.uri.fsPath;
|
|
84
|
-
this._lastEditTime = Date.now();
|
|
85
|
-
this._trackedFiles.add(e.document.uri.fsPath);
|
|
86
|
-
});
|
|
87
|
-
// Track file system changes
|
|
88
|
-
vscode.workspace.onDidCreateFiles(e => {
|
|
89
|
-
e.files.forEach(uri => {
|
|
90
|
-
this._trackedFiles.add(uri.fsPath);
|
|
91
|
-
});
|
|
92
|
-
});
|
|
93
|
-
// Initialize with default chat
|
|
94
|
-
this._ensureDefaultChat();
|
|
95
|
-
}
|
|
96
|
-
// Ensure there's at least one chat
|
|
97
|
-
_ensureDefaultChat() {
|
|
98
|
-
if (this._chats.length === 0) {
|
|
99
|
-
this._createNewChat();
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
// Create a new chat
|
|
103
|
-
_createNewChat(title) {
|
|
104
|
-
const id = Date.now().toString();
|
|
105
|
-
const now = new Date().toISOString();
|
|
106
|
-
const newChat = {
|
|
107
|
-
id,
|
|
108
|
-
title: title || `Chat ${this._chats.length + 1}`,
|
|
109
|
-
messages: [],
|
|
110
|
-
createdAt: now,
|
|
111
|
-
updatedAt: now
|
|
112
|
-
};
|
|
113
|
-
this._chats.push(newChat);
|
|
114
|
-
this._currentChatId = id;
|
|
115
|
-
// Update the webview if it exists
|
|
116
|
-
if (this._view) {
|
|
117
|
-
this._postMessageToWebview({
|
|
118
|
-
type: 'updateChats',
|
|
119
|
-
chats: this._chats,
|
|
120
|
-
currentChatId: this._currentChatId
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
return id;
|
|
124
|
-
}
|
|
125
|
-
// Get the current chat
|
|
126
|
-
_getCurrentChat() {
|
|
127
|
-
// Ensure we have at least one chat
|
|
128
|
-
this._ensureDefaultChat();
|
|
129
|
-
if (!this._currentChatId && this._chats.length > 0) {
|
|
130
|
-
this._currentChatId = this._chats[0].id;
|
|
131
|
-
}
|
|
132
|
-
return this._chats.find(chat => chat.id === this._currentChatId);
|
|
133
|
-
}
|
|
134
|
-
// Filter out system messages for display
|
|
135
|
-
_getMessagesForDisplay(messages) {
|
|
136
|
-
return messages.filter(msg => msg.role !== 'system');
|
|
137
|
-
}
|
|
138
|
-
// Switch to a different chat
|
|
139
|
-
_switchChat(chatId) {
|
|
140
|
-
const chat = this._chats.find(c => c.id === chatId);
|
|
141
|
-
if (chat) {
|
|
142
|
-
this._currentChatId = chatId;
|
|
143
|
-
// Update the webview with visible messages only
|
|
144
|
-
this._postMessageToWebview({
|
|
145
|
-
type: 'switchChat',
|
|
146
|
-
chatId,
|
|
147
|
-
messages: this._getMessagesForDisplay(chat.messages)
|
|
148
|
-
});
|
|
149
|
-
// Update chat list to reflect new active chat
|
|
150
|
-
this._postMessageToWebview({
|
|
151
|
-
type: 'updateChats',
|
|
152
|
-
chats: this._chats,
|
|
153
|
-
currentChatId: this._currentChatId
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
// Rename a chat
|
|
158
|
-
_renameChat(chatId, newTitle) {
|
|
159
|
-
const chat = this._chats.find(c => c.id === chatId);
|
|
160
|
-
if (chat) {
|
|
161
|
-
chat.title = newTitle;
|
|
162
|
-
chat.updatedAt = new Date().toISOString();
|
|
163
|
-
// Update the webview
|
|
164
|
-
this._postMessageToWebview({
|
|
165
|
-
type: 'updateChats',
|
|
166
|
-
chats: this._chats,
|
|
167
|
-
currentChatId: this._currentChatId
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
// Delete a chat
|
|
172
|
-
_deleteChat(chatId) {
|
|
173
|
-
const index = this._chats.findIndex(c => c.id === chatId);
|
|
174
|
-
if (index !== -1) {
|
|
175
|
-
// Store reference to the chat we're deleting
|
|
176
|
-
const deletedChat = this._chats[index];
|
|
177
|
-
// Remove the chat
|
|
178
|
-
this._chats.splice(index, 1);
|
|
179
|
-
// Remove all state snapshots associated with this chat's messages
|
|
180
|
-
if (deletedChat && deletedChat.messages) {
|
|
181
|
-
deletedChat.messages.forEach(msg => {
|
|
182
|
-
if (msg.timestamp && this._conversationHistory[msg.timestamp]) {
|
|
183
|
-
delete this._conversationHistory[msg.timestamp];
|
|
184
|
-
}
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
// If we deleted the current chat, switch to another one or create a new one
|
|
188
|
-
if (this._currentChatId === chatId) {
|
|
189
|
-
if (this._chats.length > 0) {
|
|
190
|
-
this._currentChatId = this._chats[0].id;
|
|
191
|
-
// Get the new current chat
|
|
192
|
-
const newCurrentChat = this._getCurrentChat();
|
|
193
|
-
// Update the webview with the new chat's messages
|
|
194
|
-
if (newCurrentChat && this._view) {
|
|
195
|
-
this._postMessageToWebview({
|
|
196
|
-
type: 'switchChat',
|
|
197
|
-
chatId: this._currentChatId,
|
|
198
|
-
messages: this._getMessagesForDisplay(newCurrentChat.messages)
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
else {
|
|
203
|
-
// Create a new chat if no chats remain
|
|
204
|
-
this._createNewChat();
|
|
205
|
-
// Get the new chat
|
|
206
|
-
const newChat = this._getCurrentChat();
|
|
207
|
-
// Update the webview with the new chat
|
|
208
|
-
if (newChat && this._view) {
|
|
209
|
-
this._postMessageToWebview({
|
|
210
|
-
type: 'switchChat',
|
|
211
|
-
chatId: this._currentChatId,
|
|
212
|
-
messages: this._getMessagesForDisplay(newChat.messages)
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
// Update the webview's chat list
|
|
218
|
-
this._postMessageToWebview({
|
|
219
|
-
type: 'updateChats',
|
|
220
|
-
chats: this._chats,
|
|
221
|
-
currentChatId: this._currentChatId
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
_updateSystemMessage(chat) {
|
|
226
|
-
// Remove existing system message
|
|
227
|
-
chat.messages = chat.messages.filter(msg => msg.role !== 'system');
|
|
228
|
-
// Get workspace info
|
|
229
|
-
const workspaceFolders = vscode.workspace.workspaceFolders || [];
|
|
230
|
-
const workspaceInfo = workspaceFolders.map(folder => folder.uri.fsPath).join(', ');
|
|
231
|
-
// Add updated system message
|
|
232
|
-
chat.messages.push({
|
|
233
|
-
role: 'system',
|
|
234
|
-
content: `You are a VS Code AI Assistant with agency capabilities. You can perform actions on the user's workspace.
|
|
235
|
-
|
|
236
|
-
ENVIRONMENT CONTEXT:
|
|
237
|
-
- OS: ${process.platform}
|
|
238
|
-
- Workspace: ${workspaceInfo || 'No workspace open'}
|
|
239
|
-
- Last edited file: ${this._lastEditedFile || 'None'}
|
|
240
|
-
${this._lastEditedFile ? `- Last edit: ${new Date(this._lastEditTime).toLocaleTimeString()}` : ''}
|
|
241
|
-
|
|
242
|
-
When you need to perform actions, respond with JSON in the following format:
|
|
243
|
-
\`\`\`json
|
|
244
|
-
{
|
|
245
|
-
"thoughts": "Your reasoning about what needs to be done",
|
|
246
|
-
"actions": [
|
|
247
|
-
{
|
|
248
|
-
"type": "read|write|search|command|analyze|execute|stop",
|
|
249
|
-
"data": { ... action specific data ... }
|
|
250
|
-
}
|
|
251
|
-
]
|
|
252
|
-
}
|
|
253
|
-
\`\`\`
|
|
254
|
-
|
|
255
|
-
Action types and their data:
|
|
256
|
-
- read: { "path": "relative/or/absolute/path" }
|
|
257
|
-
- write: { "path": "relative/or/absolute/path", "content": "file content" }
|
|
258
|
-
- search: { "type": "files", "pattern": "glob pattern" } or { "type": "text", "text": "search text" }
|
|
259
|
-
- command: { "command": "command string to execute in terminal" }
|
|
260
|
-
- execute: { "language": "js|python|bash|...", "code": "code to execute" }
|
|
261
|
-
- analyze: { "code": "code to analyze", "question": "what you want to analyze" }
|
|
262
|
-
- browse: { "query": "search query", "numResults": 5 } (free web search using DuckDuckGo, optional numResults)
|
|
263
|
-
- edit: {
|
|
264
|
-
"path": "relative/or/absolute/path",
|
|
265
|
-
"edits": {
|
|
266
|
-
"operations": [
|
|
267
|
-
{ "type": "replace", "startLine": 10, "endLine": 15, "newText": "new code here" },
|
|
268
|
-
{ "type": "replace", "pattern": "oldFunction\\(\\)", "replacement": "newFunction()", "flags": "g" },
|
|
269
|
-
{ "type": "insert", "line": 20, "text": "new line of code here" },
|
|
270
|
-
{ "type": "insert", "position": "start", "text": "// Header comment" },
|
|
271
|
-
{ "type": "insert", "position": "end", "text": "// Footer comment" },
|
|
272
|
-
{ "type": "delete", "startLine": 25, "endLine": 30 }
|
|
273
|
-
]
|
|
274
|
-
}
|
|
275
|
-
} (edit specific parts of an existing file)
|
|
276
|
-
- stop: {} (use this to indicate you're done with the task and no more actions are needed)
|
|
277
|
-
|
|
278
|
-
CONTEXTUAL UNDERSTANDING:
|
|
279
|
-
Before every response, I will automatically gather information about the user's current context, including:
|
|
280
|
-
- Currently open files
|
|
281
|
-
- Current editor selection
|
|
282
|
-
- Recent edits
|
|
283
|
-
- Terminal output (if available)
|
|
284
|
-
|
|
285
|
-
By default, you will continue to take actions in a loop until you decide to stop with the 'stop' action type.
|
|
286
|
-
Always wrap your JSON in markdown code blocks with the json language specifier.
|
|
287
|
-
When executing code or commands that might be potentially harmful, explain what the code does before executing it.
|
|
288
|
-
`,
|
|
289
|
-
timestamp: new Date().toISOString()
|
|
290
|
-
});
|
|
291
|
-
}
|
|
292
|
-
async gatherUserContext() {
|
|
293
|
-
try {
|
|
294
|
-
let context = "";
|
|
295
|
-
// Get active editor and document info
|
|
296
|
-
const editor = vscode.window.activeTextEditor;
|
|
297
|
-
if (editor) {
|
|
298
|
-
const document = editor.document;
|
|
299
|
-
const selection = editor.selection;
|
|
300
|
-
const selectedText = document.getText(selection);
|
|
301
|
-
context += `ACTIVE EDITOR:\n`;
|
|
302
|
-
context += `- File: ${document.uri.fsPath}\n`;
|
|
303
|
-
context += `- Language: ${document.languageId}\n`;
|
|
304
|
-
if (!selection.isEmpty) {
|
|
305
|
-
context += `- Selection (Lines ${selection.start.line + 1}-${selection.end.line + 1}):\n`;
|
|
306
|
-
context += "```\n" + selectedText + "\n```\n";
|
|
307
|
-
}
|
|
308
|
-
else {
|
|
309
|
-
const lineCount = document.lineCount;
|
|
310
|
-
const visibleRange = editor.visibleRanges[0];
|
|
311
|
-
context += `- Visible Range: Lines ${visibleRange.start.line + 1}-${visibleRange.end.line + 1} of ${lineCount}\n`;
|
|
312
|
-
// Get a few lines around the cursor to provide context
|
|
313
|
-
const cursorPos = selection.active;
|
|
314
|
-
const startLine = Math.max(0, cursorPos.line - 3);
|
|
315
|
-
const endLine = Math.min(lineCount - 1, cursorPos.line + 3);
|
|
316
|
-
const contextLines = [];
|
|
317
|
-
for (let i = startLine; i <= endLine; i++) {
|
|
318
|
-
const line = document.lineAt(i);
|
|
319
|
-
contextLines.push(`${i === cursorPos.line ? '> ' : ' '}${line.text}`);
|
|
320
|
-
}
|
|
321
|
-
context += `- Code around cursor (Line ${cursorPos.line + 1}):\n`;
|
|
322
|
-
context += "```\n" + contextLines.join('\n') + "\n```\n";
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
else {
|
|
326
|
-
context += "No active editor\n";
|
|
327
|
-
}
|
|
328
|
-
// Get information about open editors
|
|
329
|
-
const openEditors = vscode.window.visibleTextEditors.map(editor => {
|
|
330
|
-
return {
|
|
331
|
-
path: editor.document.uri.fsPath,
|
|
332
|
-
viewColumn: editor.viewColumn
|
|
333
|
-
};
|
|
334
|
-
});
|
|
335
|
-
if (openEditors.length > 0) {
|
|
336
|
-
context += `\nOPEN EDITORS:\n`;
|
|
337
|
-
openEditors.forEach(editor => {
|
|
338
|
-
context += `- ${editor.path}\n`;
|
|
339
|
-
});
|
|
340
|
-
}
|
|
341
|
-
// Recent edits
|
|
342
|
-
if (this._lastEditedFile) {
|
|
343
|
-
context += `\nRECENT EDITS:\n`;
|
|
344
|
-
context += `- Last modified: ${this._lastEditedFile} at ${new Date(this._lastEditTime).toLocaleTimeString()}\n`;
|
|
345
|
-
}
|
|
346
|
-
// Terminal output
|
|
347
|
-
try {
|
|
348
|
-
const terminalOutput = await this._agentUtils.getTerminalOutput();
|
|
349
|
-
if (terminalOutput) {
|
|
350
|
-
context += `\nTERMINAL:\n${terminalOutput}\n`;
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
catch (error) {
|
|
354
|
-
console.error('Error getting terminal output:', error);
|
|
355
|
-
}
|
|
356
|
-
// Current extensions
|
|
357
|
-
try {
|
|
358
|
-
const extensions = vscode.extensions.all
|
|
359
|
-
.filter(ext => ext.isActive && !ext.packageJSON.isBuiltin)
|
|
360
|
-
.map(ext => `${ext.packageJSON.displayName || ext.packageJSON.name} (${ext.packageJSON.version})`);
|
|
361
|
-
if (extensions.length > 0) {
|
|
362
|
-
context += `\nACTIVE EXTENSIONS (top 5):\n`;
|
|
363
|
-
extensions.slice(0, 5).forEach(ext => {
|
|
364
|
-
context += `- ${ext}\n`;
|
|
365
|
-
});
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
catch (error) {
|
|
369
|
-
console.error('Error getting extensions:', error);
|
|
370
|
-
}
|
|
371
|
-
return context;
|
|
372
|
-
}
|
|
373
|
-
catch (error) {
|
|
374
|
-
console.error('Error gathering context:', error);
|
|
375
|
-
return "Error gathering context";
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
ensureWebviewIsVisible() {
|
|
379
|
-
try {
|
|
380
|
-
if (!this._view) {
|
|
381
|
-
// Try to show the view if not already visible
|
|
382
|
-
vscode.commands.executeCommand('workbench.view.extension.ai-assistant-view');
|
|
383
|
-
vscode.commands.executeCommand('aiAssistantView.focus');
|
|
384
|
-
}
|
|
385
|
-
else {
|
|
386
|
-
// If view exists but might not be visible, try to show it
|
|
387
|
-
this._view.show(true);
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
catch (error) {
|
|
391
|
-
console.error('Error ensuring webview is visible:', error);
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
resolveWebviewView(webviewView, _context, _token) {
|
|
395
|
-
this._view = webviewView;
|
|
396
|
-
if (!this._extensionUri) {
|
|
397
|
-
console.error('Extension URI is undefined in resolveWebviewView');
|
|
398
|
-
return;
|
|
399
|
-
}
|
|
400
|
-
// Ensure default chat exists
|
|
401
|
-
this._ensureDefaultChat();
|
|
402
|
-
webviewView.webview.options = {
|
|
403
|
-
enableScripts: true,
|
|
404
|
-
localResourceRoots: [
|
|
405
|
-
vscode.Uri.joinPath(this._extensionUri, 'media')
|
|
406
|
-
]
|
|
407
|
-
};
|
|
408
|
-
webviewView.webview.html = this._getHtmlForWebview(webviewView.webview);
|
|
409
|
-
webviewView.webview.onDidReceiveMessage(async (data) => {
|
|
410
|
-
switch (data.type) {
|
|
411
|
-
case 'sendQuery':
|
|
412
|
-
await this.sendQueryToAI(data.value);
|
|
413
|
-
break;
|
|
414
|
-
case 'clearConversation':
|
|
415
|
-
this.clearConversation();
|
|
416
|
-
break;
|
|
417
|
-
case 'toggleLanguage':
|
|
418
|
-
this.toggleLanguage();
|
|
419
|
-
break;
|
|
420
|
-
case 'restoreState':
|
|
421
|
-
await this.restoreToState(data.messageId);
|
|
422
|
-
break;
|
|
423
|
-
case 'createNewChat':
|
|
424
|
-
this._createNewChat(data.title);
|
|
425
|
-
break;
|
|
426
|
-
case 'switchChat':
|
|
427
|
-
this._switchChat(data.chatId);
|
|
428
|
-
break;
|
|
429
|
-
case 'renameChat':
|
|
430
|
-
this._renameChat(data.chatId, data.newTitle);
|
|
431
|
-
break;
|
|
432
|
-
case 'deleteChat':
|
|
433
|
-
this._deleteChat(data.chatId);
|
|
434
|
-
break;
|
|
435
|
-
case 'getConfiguration':
|
|
436
|
-
this._getConfiguration();
|
|
437
|
-
break;
|
|
438
|
-
case 'saveConfiguration':
|
|
439
|
-
this._saveConfiguration(data.modelName, data.apiBaseUrl, data.apiKey, data.maxTokens);
|
|
440
|
-
break;
|
|
441
|
-
}
|
|
442
|
-
});
|
|
443
|
-
// Ensure a default chat exists
|
|
444
|
-
this._ensureDefaultChat();
|
|
445
|
-
// Send initial chat data to the webview
|
|
446
|
-
this._postMessageToWebview({
|
|
447
|
-
type: 'updateChats',
|
|
448
|
-
chats: this._chats,
|
|
449
|
-
currentChatId: this._currentChatId
|
|
450
|
-
});
|
|
451
|
-
// Get current chat messages (if any)
|
|
452
|
-
const currentChat = this._getCurrentChat();
|
|
453
|
-
if (currentChat) {
|
|
454
|
-
this._postMessageToWebview({
|
|
455
|
-
type: 'restoreConversation',
|
|
456
|
-
messages: this._getMessagesForDisplay(currentChat.messages)
|
|
457
|
-
});
|
|
458
|
-
}
|
|
459
|
-
// Show the view by focusing on it
|
|
460
|
-
if (!webviewView.visible) {
|
|
461
|
-
webviewView.show();
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
async sendQueryToAI(query) {
|
|
465
|
-
if (!this._view) {
|
|
466
|
-
this.ensureWebviewIsVisible();
|
|
467
|
-
return;
|
|
468
|
-
}
|
|
469
|
-
// Don't start a new query if we're already generating a response
|
|
470
|
-
if (this._isGeneratingResponse) {
|
|
471
|
-
vscode.window.showInformationMessage('Already processing a request. Please wait or clear the conversation.');
|
|
472
|
-
return;
|
|
473
|
-
}
|
|
474
|
-
// Get the current chat or create a new one if none exists
|
|
475
|
-
let currentChat = this._getCurrentChat();
|
|
476
|
-
if (!currentChat) {
|
|
477
|
-
this._createNewChat();
|
|
478
|
-
currentChat = this._getCurrentChat();
|
|
479
|
-
}
|
|
480
|
-
if (!currentChat) {
|
|
481
|
-
console.error('Failed to get or create a chat');
|
|
482
|
-
return;
|
|
483
|
-
}
|
|
484
|
-
// Add user message to conversation
|
|
485
|
-
const userMessage = {
|
|
486
|
-
role: 'user',
|
|
487
|
-
content: query,
|
|
488
|
-
timestamp: new Date().toISOString()
|
|
489
|
-
};
|
|
490
|
-
currentChat.messages.push(userMessage);
|
|
491
|
-
currentChat.updatedAt = new Date().toISOString();
|
|
492
|
-
// Take a snapshot of the current state before processing
|
|
493
|
-
await this._takeStateSnapshot(userMessage.timestamp);
|
|
494
|
-
// Update the webview with the new message
|
|
495
|
-
await this._postMessageToWebview({
|
|
496
|
-
type: 'addMessage',
|
|
497
|
-
message: userMessage
|
|
498
|
-
});
|
|
499
|
-
try {
|
|
500
|
-
// Gather current context before sending query
|
|
501
|
-
const userContext = await this.gatherUserContext();
|
|
502
|
-
// Update the system message with fresh context
|
|
503
|
-
this._updateSystemMessage(currentChat);
|
|
504
|
-
// Update the system message to include a timestamp
|
|
505
|
-
currentChat.messages.push({
|
|
506
|
-
role: 'system',
|
|
507
|
-
content: `Current user context:\n${userContext}`,
|
|
508
|
-
timestamp: new Date().toISOString()
|
|
509
|
-
});
|
|
510
|
-
// Show loading indicator
|
|
511
|
-
await this._postMessageToWebview({ type: 'setLoading', isLoading: true });
|
|
512
|
-
// Get AI response
|
|
513
|
-
await this._getAIResponse(currentChat);
|
|
514
|
-
}
|
|
515
|
-
catch (error) {
|
|
516
|
-
console.error('Error in sendQueryToAI:', error);
|
|
517
|
-
vscode.window.showErrorMessage(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
518
|
-
await this._postMessageToWebview({
|
|
519
|
-
type: 'addMessage',
|
|
520
|
-
message: {
|
|
521
|
-
role: 'assistant',
|
|
522
|
-
content: translations[this._language].errorMessage,
|
|
523
|
-
timestamp: new Date().toISOString()
|
|
524
|
-
}
|
|
525
|
-
});
|
|
526
|
-
}
|
|
527
|
-
finally {
|
|
528
|
-
// Hide loading indicator
|
|
529
|
-
await this._postMessageToWebview({ type: 'setLoading', isLoading: false });
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
// Helper method to safely post messages to the webview
|
|
533
|
-
async _postMessageToWebview(message) {
|
|
534
|
-
if (this._view?.webview) {
|
|
535
|
-
try {
|
|
536
|
-
await this._view.webview.postMessage(message);
|
|
537
|
-
}
|
|
538
|
-
catch (error) {
|
|
539
|
-
console.error('Error posting message to webview:', error);
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
async _getAIResponse(chat) {
|
|
544
|
-
try {
|
|
545
|
-
if (!this._view) {
|
|
546
|
-
return;
|
|
547
|
-
}
|
|
548
|
-
// Set the generating flag
|
|
549
|
-
this._isGeneratingResponse = true;
|
|
550
|
-
// Create a new AbortController for this request
|
|
551
|
-
this._abortController = new AbortController();
|
|
552
|
-
// Get API key from settings or use default
|
|
553
|
-
const config = vscode.workspace.getConfiguration('aiAssistant');
|
|
554
|
-
let apiKey = config.get('apiKey');
|
|
555
|
-
// If no API key in settings, use the default one
|
|
556
|
-
if (!apiKey || apiKey.trim() === '') {
|
|
557
|
-
apiKey = DEFAULT_API_KEY;
|
|
558
|
-
}
|
|
559
|
-
const model = config.get('model') || "accounts/fireworks/models/deepseek-v3-0324";
|
|
560
|
-
const apiBaseUrl = config.get('apiBaseUrl') || "https://api.fireworks.ai/inference/v1";
|
|
561
|
-
const maxTokens = config.get('maxTokens') || 120000;
|
|
562
|
-
// Strip out timestamp field from messages before sending to API
|
|
563
|
-
const apiMessages = chat.messages.map(msg => ({
|
|
564
|
-
role: msg.role,
|
|
565
|
-
content: msg.content
|
|
566
|
-
}));
|
|
567
|
-
// Create request options
|
|
568
|
-
const options = {
|
|
569
|
-
method: "POST",
|
|
570
|
-
headers: {
|
|
571
|
-
"Accept": "application/json",
|
|
572
|
-
"Content-Type": "application/json",
|
|
573
|
-
"Authorization": `Bearer ${apiKey}`
|
|
574
|
-
},
|
|
575
|
-
body: JSON.stringify({
|
|
576
|
-
model: model,
|
|
577
|
-
max_tokens: maxTokens,
|
|
578
|
-
top_p: 1,
|
|
579
|
-
top_k: 40,
|
|
580
|
-
presence_penalty: 0,
|
|
581
|
-
frequency_penalty: 0,
|
|
582
|
-
temperature: 0.6,
|
|
583
|
-
messages: apiMessages
|
|
584
|
-
})
|
|
585
|
-
};
|
|
586
|
-
// Add abort signal if available
|
|
587
|
-
if (this._abortController) {
|
|
588
|
-
options.signal = this._abortController.signal;
|
|
589
|
-
}
|
|
590
|
-
const response = await (0, fetch_polyfill_1.fetch)(`${apiBaseUrl}/chat/completions`, options);
|
|
591
|
-
// Check if the request was aborted
|
|
592
|
-
if (this._abortController && this._abortController.signal.aborted) {
|
|
593
|
-
this._isGeneratingResponse = false;
|
|
594
|
-
return;
|
|
595
|
-
}
|
|
596
|
-
if (!response.ok) {
|
|
597
|
-
const errorData = await response.json().catch(() => null);
|
|
598
|
-
let errorMessage = `API request failed: ${response.statusText}`;
|
|
599
|
-
if (errorData?.error?.message) {
|
|
600
|
-
errorMessage = `API Error: ${errorData.error.message}`;
|
|
601
|
-
}
|
|
602
|
-
else if (response.status === 401) {
|
|
603
|
-
errorMessage = 'Invalid API key. Please check your API key in settings.';
|
|
604
|
-
}
|
|
605
|
-
else if (response.status === 429) {
|
|
606
|
-
errorMessage = 'Rate limit exceeded. Please try again later.';
|
|
607
|
-
}
|
|
608
|
-
else if (response.status >= 500) {
|
|
609
|
-
errorMessage = 'Server error. Please try again later.';
|
|
610
|
-
}
|
|
611
|
-
throw new Error(errorMessage);
|
|
612
|
-
}
|
|
613
|
-
const data = await response.json();
|
|
614
|
-
if (!data?.choices?.[0]?.message?.content) {
|
|
615
|
-
throw new Error('Invalid response format from API');
|
|
616
|
-
}
|
|
617
|
-
const assistantMessage = data.choices[0].message.content;
|
|
618
|
-
// Update the agent working message to include a timestamp
|
|
619
|
-
const timestamp = new Date().toISOString();
|
|
620
|
-
await this._postMessageToWebview({
|
|
621
|
-
type: 'addMessage',
|
|
622
|
-
message: {
|
|
623
|
-
role: 'assistant',
|
|
624
|
-
content: 'Thinking...',
|
|
625
|
-
timestamp
|
|
626
|
-
}
|
|
627
|
-
});
|
|
628
|
-
// Update the agent response message to include a timestamp
|
|
629
|
-
chat.messages.push({
|
|
630
|
-
role: 'assistant',
|
|
631
|
-
content: assistantMessage,
|
|
632
|
-
timestamp
|
|
633
|
-
});
|
|
634
|
-
// Update UI with agent response
|
|
635
|
-
await this._postMessageToWebview({
|
|
636
|
-
type: 'addMessage',
|
|
637
|
-
message: {
|
|
638
|
-
role: 'assistant',
|
|
639
|
-
content: assistantMessage,
|
|
640
|
-
timestamp
|
|
641
|
-
}
|
|
642
|
-
});
|
|
643
|
-
// Check if the message contains agent actions
|
|
644
|
-
await this._processAgentActions(assistantMessage, chat);
|
|
645
|
-
}
|
|
646
|
-
catch (error) {
|
|
647
|
-
console.error('Error calling Fireworks API:', error);
|
|
648
|
-
// Show a more specific error message in the chat
|
|
649
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
650
|
-
await this._postMessageToWebview({
|
|
651
|
-
type: 'addMessage',
|
|
652
|
-
message: {
|
|
653
|
-
role: 'assistant',
|
|
654
|
-
content: `❌ ${errorMessage}`,
|
|
655
|
-
timestamp: new Date().toISOString()
|
|
656
|
-
}
|
|
657
|
-
});
|
|
658
|
-
throw error; // Rethrow to be handled by caller
|
|
659
|
-
}
|
|
660
|
-
finally {
|
|
661
|
-
this._isGeneratingResponse = false;
|
|
662
|
-
if (this._view) {
|
|
663
|
-
this._view.webview.postMessage({ type: 'setLoading', isLoading: false });
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
_updateWebviewContent() {
|
|
668
|
-
if (this._view) {
|
|
669
|
-
this._view.webview.html = this._getHtmlForWebview(this._view.webview);
|
|
670
|
-
// Update welcome message
|
|
671
|
-
this._postMessageToWebview({
|
|
672
|
-
type: 'updateLanguage',
|
|
673
|
-
translations: translations[this._language]
|
|
674
|
-
});
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
async _processAgentActions(message, chat) {
|
|
678
|
-
// If already processing actions, don't start another process
|
|
679
|
-
if (this._isProcessingAgentActions) {
|
|
680
|
-
return;
|
|
681
|
-
}
|
|
682
|
-
try {
|
|
683
|
-
this._isProcessingAgentActions = true;
|
|
684
|
-
// First try to extract JSON from markdown code blocks
|
|
685
|
-
const jsonMatch = message.match(/```json\s*([\s\S]*?)\s*```/);
|
|
686
|
-
if (jsonMatch) {
|
|
687
|
-
// Process JSON from code block
|
|
688
|
-
const jsonContent = jsonMatch[1].trim();
|
|
689
|
-
this._agentUtils.log(`Extracted JSON from markdown: ${jsonContent ? jsonContent.substring(0, 100) + '...' : 'empty content'}`);
|
|
690
|
-
try {
|
|
691
|
-
const actionData = JSON.parse(jsonContent);
|
|
692
|
-
this._agentUtils.log(`Successfully parsed JSON from code block`);
|
|
693
|
-
// Check if processing was cancelled
|
|
694
|
-
if (!this._isProcessingAgentActions) {
|
|
695
|
-
return;
|
|
696
|
-
}
|
|
697
|
-
await this._processJsonActions(actionData, chat);
|
|
698
|
-
}
|
|
699
|
-
catch (parseError) {
|
|
700
|
-
console.error('Error parsing JSON from code block:', parseError);
|
|
701
|
-
this._agentUtils.log(`Error parsing JSON from code block: ${parseError instanceof Error ? parseError.message : String(parseError)}`);
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
else {
|
|
705
|
-
// Try extracting without code block markers
|
|
706
|
-
try {
|
|
707
|
-
const jsonData = JSON.parse(message);
|
|
708
|
-
if (jsonData && jsonData.actions && Array.isArray(jsonData.actions)) {
|
|
709
|
-
this._agentUtils.log(`Found JSON without code block markers`);
|
|
710
|
-
// Check if processing was cancelled
|
|
711
|
-
if (!this._isProcessingAgentActions) {
|
|
712
|
-
return;
|
|
713
|
-
}
|
|
714
|
-
await this._processJsonActions(jsonData, chat);
|
|
715
|
-
}
|
|
716
|
-
else {
|
|
717
|
-
this._agentUtils.log(`Parsed JSON but no valid actions array found`);
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
catch (e) {
|
|
721
|
-
// Not valid JSON
|
|
722
|
-
this._agentUtils.log(`No JSON code blocks found and content is not valid JSON`);
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
catch (error) {
|
|
727
|
-
console.error('Error processing agent actions:', error);
|
|
728
|
-
this._agentUtils.log(`Error processing agent actions: ${error instanceof Error ? error.message : String(error)}`);
|
|
729
|
-
if (this._view) {
|
|
730
|
-
this._view.webview.postMessage({
|
|
731
|
-
type: 'addMessage',
|
|
732
|
-
message: {
|
|
733
|
-
role: 'assistant',
|
|
734
|
-
content: `❌ ${error instanceof Error ? error.message : String(error)}`,
|
|
735
|
-
timestamp: new Date().toISOString()
|
|
736
|
-
}
|
|
737
|
-
});
|
|
738
|
-
}
|
|
739
|
-
}
|
|
740
|
-
finally {
|
|
741
|
-
this._isProcessingAgentActions = false;
|
|
742
|
-
if (this._view) {
|
|
743
|
-
this._view.webview.postMessage({ type: 'setLoading', isLoading: false });
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
async _processJsonActions(actionData, chat) {
|
|
748
|
-
if (!actionData.actions || !Array.isArray(actionData.actions)) {
|
|
749
|
-
this._agentUtils.log(`No valid actions array found in JSON`);
|
|
750
|
-
return; // No valid actions array
|
|
751
|
-
}
|
|
752
|
-
// Check if there's a "stop" action which should halt the agent after executing all actions
|
|
753
|
-
const hasStopAction = actionData.actions.some((action) => action.type === 'stop');
|
|
754
|
-
// Show that the agent is working
|
|
755
|
-
this._agentUtils.showOutputChannel();
|
|
756
|
-
this._agentUtils.log(`Processing ${actionData.actions.length} agent actions...`);
|
|
757
|
-
this._agentUtils.log(`Thoughts: ${actionData.thoughts || 'No thoughts provided'}`);
|
|
758
|
-
// Log each action for debugging
|
|
759
|
-
actionData.actions.forEach((action, index) => {
|
|
760
|
-
const actionDataStr = action.data ?
|
|
761
|
-
(typeof action.data === 'string' ?
|
|
762
|
-
action.data.substring(0, 150) :
|
|
763
|
-
JSON.stringify(action.data || {}).substring(0, 150)) :
|
|
764
|
-
'undefined';
|
|
765
|
-
this._agentUtils.log(`Action ${index + 1}: type=${action.type}, data=${actionDataStr}${actionDataStr && actionDataStr.length > 149 ? '...' : ''}`);
|
|
766
|
-
});
|
|
767
|
-
if (this._view) {
|
|
768
|
-
this._view.webview.postMessage({
|
|
769
|
-
type: 'addMessage',
|
|
770
|
-
message: {
|
|
771
|
-
role: 'assistant',
|
|
772
|
-
content: translations[this._language].workingMessage,
|
|
773
|
-
timestamp: new Date().toISOString()
|
|
774
|
-
}
|
|
775
|
-
});
|
|
776
|
-
this._view.webview.postMessage({ type: 'setLoading', isLoading: true });
|
|
777
|
-
}
|
|
778
|
-
// Execute actions
|
|
779
|
-
try {
|
|
780
|
-
// Check if processing was cancelled before executing actions
|
|
781
|
-
if (!this._isProcessingAgentActions) {
|
|
782
|
-
return;
|
|
783
|
-
}
|
|
784
|
-
const actionResults = await this._agentUtils.executeActions(actionData.actions);
|
|
785
|
-
// Check again if processing was cancelled after executing actions
|
|
786
|
-
if (!this._isProcessingAgentActions) {
|
|
787
|
-
return;
|
|
788
|
-
}
|
|
789
|
-
this._agentUtils.log(`Actions executed successfully`);
|
|
790
|
-
// Log results for debugging
|
|
791
|
-
actionResults.forEach((result, index) => {
|
|
792
|
-
let resultStr = 'undefined';
|
|
793
|
-
if (result.result) {
|
|
794
|
-
if (typeof result.result === 'string') {
|
|
795
|
-
resultStr = result.result.substring(0, 100);
|
|
796
|
-
}
|
|
797
|
-
else {
|
|
798
|
-
try {
|
|
799
|
-
resultStr = JSON.stringify(result.result).substring(0, 100);
|
|
800
|
-
}
|
|
801
|
-
catch (e) {
|
|
802
|
-
resultStr = '[Object cannot be stringified]';
|
|
803
|
-
}
|
|
804
|
-
}
|
|
805
|
-
}
|
|
806
|
-
this._agentUtils.log(`Result ${index + 1}: ${resultStr}${resultStr.length >= 100 ? '...' : ''}`);
|
|
807
|
-
});
|
|
808
|
-
// If this batch included a stop action, stop after all actions complete
|
|
809
|
-
if (hasStopAction) {
|
|
810
|
-
this._agentUtils.log(`Stop action detected, halting after execution`);
|
|
811
|
-
if (this._view) {
|
|
812
|
-
this._view.webview.postMessage({
|
|
813
|
-
type: 'addMessage',
|
|
814
|
-
message: {
|
|
815
|
-
role: 'assistant',
|
|
816
|
-
content: translations[this._language].taskCompletedMessage,
|
|
817
|
-
timestamp: new Date().toISOString()
|
|
818
|
-
}
|
|
819
|
-
});
|
|
820
|
-
}
|
|
821
|
-
return;
|
|
822
|
-
}
|
|
823
|
-
// Check if processing was cancelled before continuing
|
|
824
|
-
if (!this._isProcessingAgentActions) {
|
|
825
|
-
return;
|
|
826
|
-
}
|
|
827
|
-
// Format results to send back to AI
|
|
828
|
-
const resultsForAI = JSON.stringify(actionResults, null, 2);
|
|
829
|
-
// Update context after actions completed
|
|
830
|
-
const updatedContext = await this.gatherUserContext();
|
|
831
|
-
// First add context as a system message
|
|
832
|
-
chat.messages.push({
|
|
833
|
-
role: 'system',
|
|
834
|
-
content: `Updated context after actions:\n${updatedContext}`,
|
|
835
|
-
timestamp: new Date().toISOString()
|
|
836
|
-
});
|
|
837
|
-
// Add action results as a system message to the conversation (not shown in UI)
|
|
838
|
-
chat.messages.push({
|
|
839
|
-
role: 'system',
|
|
840
|
-
content: `The assistant has completed the actions. Here are the results:\n\`\`\`json\n${resultsForAI}\n\`\`\`\n
|
|
841
|
-
Based on these results, determine what to do next. You can:
|
|
842
|
-
1. Continue with more actions by returning a new JSON with "actions" array
|
|
843
|
-
2. Stop the iteration by including an action with "type": "stop" if the task is completed
|
|
844
|
-
3. Provide a final response to the user with your findings
|
|
845
|
-
|
|
846
|
-
Please analyze these results and respond appropriately.`,
|
|
847
|
-
timestamp: new Date().toISOString()
|
|
848
|
-
});
|
|
849
|
-
// Check if processing was cancelled before calling API again
|
|
850
|
-
if (!this._isProcessingAgentActions) {
|
|
851
|
-
return;
|
|
852
|
-
}
|
|
853
|
-
// Call API again with updated conversation
|
|
854
|
-
await this._getAIResponse(chat);
|
|
855
|
-
}
|
|
856
|
-
catch (error) {
|
|
857
|
-
console.error('Error executing actions:', error);
|
|
858
|
-
this._agentUtils.log(`Error executing actions: ${error instanceof Error ? error.message : String(error)}`);
|
|
859
|
-
throw error;
|
|
860
|
-
}
|
|
861
|
-
}
|
|
862
|
-
clearConversation() {
|
|
863
|
-
// Abort any ongoing API request
|
|
864
|
-
if (this._abortController && this._isGeneratingResponse) {
|
|
865
|
-
this._abortController.abort();
|
|
866
|
-
this._abortController = null;
|
|
867
|
-
this._isGeneratingResponse = false;
|
|
868
|
-
}
|
|
869
|
-
// Stop any agent actions processing
|
|
870
|
-
this._isProcessingAgentActions = false;
|
|
871
|
-
// Get the current chat
|
|
872
|
-
const currentChat = this._getCurrentChat();
|
|
873
|
-
if (!currentChat) {
|
|
874
|
-
return;
|
|
875
|
-
}
|
|
876
|
-
// Keep system message but clear the rest
|
|
877
|
-
currentChat.messages = currentChat.messages.filter(msg => msg.role === 'system');
|
|
878
|
-
// Update the webview
|
|
879
|
-
this._postMessageToWebview({ type: 'clearConversation' });
|
|
880
|
-
// Hide loading indicator if it's showing
|
|
881
|
-
this._postMessageToWebview({ type: 'setLoading', isLoading: false });
|
|
882
|
-
}
|
|
883
|
-
_getHtmlForWebview(webview) {
|
|
884
|
-
// Create URLs for images
|
|
885
|
-
let mediaPath = '';
|
|
886
|
-
let infinityIconPath = '';
|
|
887
|
-
if (this._extensionUri) {
|
|
888
|
-
try {
|
|
889
|
-
mediaPath = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, 'media'));
|
|
890
|
-
infinityIconPath = `${mediaPath}/infinity-icon.svg`;
|
|
891
|
-
}
|
|
892
|
-
catch (error) {
|
|
893
|
-
console.error('Error creating webview URIs:', error);
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
else {
|
|
897
|
-
console.error('Extension URI is undefined');
|
|
898
|
-
}
|
|
899
|
-
return `<!DOCTYPE html>
|
|
900
|
-
<html lang="${this._language}">
|
|
901
|
-
<head>
|
|
902
|
-
<meta charset="UTF-8">
|
|
903
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
904
|
-
<title>AI Assistant</title>
|
|
905
|
-
<style>
|
|
906
|
-
:root {
|
|
907
|
-
--primary-color: #ff3333;
|
|
908
|
-
--primary-gradient: linear-gradient(135deg, #ff3333, #cc0000);
|
|
909
|
-
--secondary-gradient: linear-gradient(135deg, #ff6666, #ff3333);
|
|
910
|
-
--accent-color: #ff6666;
|
|
911
|
-
--text-on-primary: white;
|
|
912
|
-
--message-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
913
|
-
--hover-transform: translateY(-2px);
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
body {
|
|
917
|
-
font-family: var(--vscode-font-family);
|
|
918
|
-
padding: 0;
|
|
919
|
-
margin: 0;
|
|
920
|
-
color: var(--vscode-foreground);
|
|
921
|
-
background-color: var(--vscode-editor-background);
|
|
922
|
-
background-image:
|
|
923
|
-
radial-gradient(circle at 0% 0%, rgba(255, 51, 51, 0.03) 0%, transparent 50%),
|
|
924
|
-
radial-gradient(circle at 100% 100%, rgba(255, 102, 102, 0.03) 0%, transparent 50%);
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
.container {
|
|
928
|
-
display: flex;
|
|
929
|
-
flex-direction: column;
|
|
930
|
-
height: 100vh;
|
|
931
|
-
max-width: 100%;
|
|
932
|
-
box-sizing: border-box;
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
.header {
|
|
936
|
-
background: var(--primary-gradient);
|
|
937
|
-
color: var(--text-on-primary);
|
|
938
|
-
padding: 12px 16px;
|
|
939
|
-
display: flex;
|
|
940
|
-
justify-content: space-between;
|
|
941
|
-
align-items: center;
|
|
942
|
-
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
943
|
-
position: relative;
|
|
944
|
-
overflow: hidden;
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
.header::before {
|
|
948
|
-
content: '';
|
|
949
|
-
position: absolute;
|
|
950
|
-
top: 0;
|
|
951
|
-
left: 0;
|
|
952
|
-
right: 0;
|
|
953
|
-
bottom: 0;
|
|
954
|
-
background: linear-gradient(45deg, transparent 0%, rgba(255, 255, 255, 0.1) 50%, transparent 100%);
|
|
955
|
-
animation: shimmer 3s infinite;
|
|
956
|
-
}
|
|
957
|
-
|
|
958
|
-
@keyframes shimmer {
|
|
959
|
-
0% { transform: translateX(-100%); }
|
|
960
|
-
100% { transform: translateX(100%); }
|
|
961
|
-
}
|
|
962
|
-
|
|
963
|
-
.header-title {
|
|
964
|
-
display: flex;
|
|
965
|
-
align-items: center;
|
|
966
|
-
font-size: 16px;
|
|
967
|
-
font-weight: bold;
|
|
968
|
-
position: relative;
|
|
969
|
-
z-index: 1;
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
.header-actions {
|
|
973
|
-
display: flex;
|
|
974
|
-
gap: 8px;
|
|
975
|
-
}
|
|
976
|
-
|
|
977
|
-
.profile-icon {
|
|
978
|
-
width: 32px;
|
|
979
|
-
height: 32px;
|
|
980
|
-
border-radius: 50%;
|
|
981
|
-
background: var(--secondary-gradient);
|
|
982
|
-
margin-right: 12px;
|
|
983
|
-
padding: 4px;
|
|
984
|
-
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.2);
|
|
985
|
-
display: flex;
|
|
986
|
-
align-items: center;
|
|
987
|
-
justify-content: center;
|
|
988
|
-
transition: transform 0.3s ease;
|
|
989
|
-
}
|
|
990
|
-
|
|
991
|
-
.profile-icon:hover {
|
|
992
|
-
transform: scale(1.1);
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
.profile-icon img {
|
|
996
|
-
width: 24px;
|
|
997
|
-
height: 24px;
|
|
998
|
-
filter: brightness(0) invert(1);
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
|
-
.conversation {
|
|
1002
|
-
flex: 1;
|
|
1003
|
-
overflow-y: auto;
|
|
1004
|
-
padding: 20px;
|
|
1005
|
-
background-color: var(--vscode-editor-background);
|
|
1006
|
-
background-image:
|
|
1007
|
-
radial-gradient(circle at 10% 20%, rgba(255, 51, 51, 0.03) 0%, transparent 20%),
|
|
1008
|
-
radial-gradient(circle at 90% 80%, rgba(255, 102, 102, 0.03) 0%, transparent 20%);
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
|
-
.message {
|
|
1012
|
-
margin-bottom: 20px;
|
|
1013
|
-
padding: 14px 18px;
|
|
1014
|
-
border-radius: 12px;
|
|
1015
|
-
max-width: 85%;
|
|
1016
|
-
word-wrap: break-word;
|
|
1017
|
-
animation: fadeIn 0.4s ease;
|
|
1018
|
-
box-shadow: var(--message-shadow);
|
|
1019
|
-
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
|
1020
|
-
}
|
|
1021
|
-
|
|
1022
|
-
.message:hover {
|
|
1023
|
-
transform: var(--hover-transform);
|
|
1024
|
-
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
|
|
1025
|
-
}
|
|
1026
|
-
|
|
1027
|
-
@keyframes fadeIn {
|
|
1028
|
-
from {
|
|
1029
|
-
opacity: 0;
|
|
1030
|
-
transform: translateY(20px);
|
|
1031
|
-
}
|
|
1032
|
-
to {
|
|
1033
|
-
opacity: 1;
|
|
1034
|
-
transform: translateY(0);
|
|
1035
|
-
}
|
|
1036
|
-
}
|
|
1037
|
-
|
|
1038
|
-
.user {
|
|
1039
|
-
background: var(--primary-gradient);
|
|
1040
|
-
color: var(--text-on-primary);
|
|
1041
|
-
align-self: flex-end;
|
|
1042
|
-
margin-left: auto;
|
|
1043
|
-
border-top-right-radius: 4px;
|
|
1044
|
-
}
|
|
1045
|
-
|
|
1046
|
-
.assistant {
|
|
1047
|
-
background-color: var(--vscode-editor-inactiveSelectionBackground);
|
|
1048
|
-
color: var(--vscode-foreground);
|
|
1049
|
-
align-self: flex-start;
|
|
1050
|
-
margin-right: auto;
|
|
1051
|
-
border-top-left-radius: 4px;
|
|
1052
|
-
position: relative;
|
|
1053
|
-
padding-left: 24px;
|
|
1054
|
-
}
|
|
1055
|
-
|
|
1056
|
-
.assistant::before {
|
|
1057
|
-
content: '';
|
|
1058
|
-
display: block;
|
|
1059
|
-
position: absolute;
|
|
1060
|
-
left: 0;
|
|
1061
|
-
top: 0;
|
|
1062
|
-
bottom: 0;
|
|
1063
|
-
width: 4px;
|
|
1064
|
-
background: var(--primary-gradient);
|
|
1065
|
-
border-radius: 4px 0 0 4px;
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
.input-area {
|
|
1069
|
-
display: flex;
|
|
1070
|
-
padding: 16px;
|
|
1071
|
-
border-top: 1px solid var(--vscode-panel-border);
|
|
1072
|
-
background-color: var(--vscode-editor-background);
|
|
1073
|
-
position: relative;
|
|
1074
|
-
}
|
|
1075
|
-
|
|
1076
|
-
#query-input {
|
|
1077
|
-
flex: 1;
|
|
1078
|
-
padding: 14px;
|
|
1079
|
-
border: 2px solid var(--vscode-input-border);
|
|
1080
|
-
background-color: var(--vscode-input-background);
|
|
1081
|
-
color: var(--vscode-input-foreground);
|
|
1082
|
-
border-radius: 12px;
|
|
1083
|
-
outline: none;
|
|
1084
|
-
transition: all 0.3s ease;
|
|
1085
|
-
font-size: 14px;
|
|
1086
|
-
}
|
|
1087
|
-
|
|
1088
|
-
#query-input:focus {
|
|
1089
|
-
border-color: var(--primary-color);
|
|
1090
|
-
box-shadow: 0 0 0 3px rgba(255, 51, 51, 0.1);
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
|
-
.send-button {
|
|
1094
|
-
margin-left: 10px;
|
|
1095
|
-
padding: 10px 16px;
|
|
1096
|
-
background: var(--primary-gradient);
|
|
1097
|
-
color: var(--text-on-primary);
|
|
1098
|
-
border: none;
|
|
1099
|
-
border-radius: 12px;
|
|
1100
|
-
cursor: pointer;
|
|
1101
|
-
font-weight: bold;
|
|
1102
|
-
transition: all 0.3s ease;
|
|
1103
|
-
box-shadow: 0 2px 6px rgba(255, 51, 51, 0.3);
|
|
1104
|
-
font-size: 13px;
|
|
1105
|
-
position: relative;
|
|
1106
|
-
overflow: hidden;
|
|
1107
|
-
}
|
|
1108
|
-
|
|
1109
|
-
.send-button::before {
|
|
1110
|
-
content: '';
|
|
1111
|
-
position: absolute;
|
|
1112
|
-
top: 0;
|
|
1113
|
-
left: 0;
|
|
1114
|
-
width: 100%;
|
|
1115
|
-
height: 100%;
|
|
1116
|
-
background: linear-gradient(45deg, transparent 0%, rgba(255, 255, 255, 0.2) 50%, transparent 100%);
|
|
1117
|
-
transform: translateX(-100%);
|
|
1118
|
-
transition: transform 0.6s ease;
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
.send-button:hover {
|
|
1122
|
-
transform: var(--hover-transform);
|
|
1123
|
-
box-shadow: 0 4px 12px rgba(255, 51, 51, 0.4);
|
|
1124
|
-
}
|
|
1125
|
-
|
|
1126
|
-
.send-button:hover::before {
|
|
1127
|
-
transform: translateX(100%);
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
|
-
.clear-button {
|
|
1131
|
-
margin-left: 8px;
|
|
1132
|
-
padding: 10px 16px;
|
|
1133
|
-
background-color: transparent;
|
|
1134
|
-
color: var(--vscode-errorForeground);
|
|
1135
|
-
border: 2px solid var(--vscode-errorForeground);
|
|
1136
|
-
border-radius: 12px;
|
|
1137
|
-
cursor: pointer;
|
|
1138
|
-
transition: all 0.3s ease;
|
|
1139
|
-
font-size: 13px;
|
|
1140
|
-
font-weight: 500;
|
|
1141
|
-
}
|
|
1142
|
-
|
|
1143
|
-
.clear-button:hover {
|
|
1144
|
-
background-color: var(--vscode-errorForeground);
|
|
1145
|
-
color: white;
|
|
1146
|
-
transform: var(--hover-transform);
|
|
1147
|
-
}
|
|
1148
|
-
|
|
1149
|
-
.lang-button {
|
|
1150
|
-
background-color: rgba(255, 255, 255, 0.1);
|
|
1151
|
-
color: var(--text-on-primary);
|
|
1152
|
-
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
1153
|
-
border-radius: 6px;
|
|
1154
|
-
cursor: pointer;
|
|
1155
|
-
padding: 6px 12px;
|
|
1156
|
-
font-size: 12px;
|
|
1157
|
-
transition: all 0.3s ease;
|
|
1158
|
-
backdrop-filter: blur(4px);
|
|
1159
|
-
}
|
|
1160
|
-
|
|
1161
|
-
.config-button {
|
|
1162
|
-
background-color: rgba(255, 255, 255, 0.1);
|
|
1163
|
-
color: var(--text-on-primary);
|
|
1164
|
-
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
1165
|
-
border-radius: 6px;
|
|
1166
|
-
cursor: pointer;
|
|
1167
|
-
padding: 6px 12px;
|
|
1168
|
-
font-size: 12px;
|
|
1169
|
-
transition: all 0.3s ease;
|
|
1170
|
-
backdrop-filter: blur(4px);
|
|
1171
|
-
}
|
|
1172
|
-
|
|
1173
|
-
.lang-button:hover, .config-button:hover {
|
|
1174
|
-
background-color: rgba(255, 255, 255, 0.2);
|
|
1175
|
-
transform: var(--hover-transform);
|
|
1176
|
-
}
|
|
1177
|
-
|
|
1178
|
-
.config-modal {
|
|
1179
|
-
display: none;
|
|
1180
|
-
position: fixed;
|
|
1181
|
-
top: 0;
|
|
1182
|
-
left: 0;
|
|
1183
|
-
width: 100%;
|
|
1184
|
-
height: 100%;
|
|
1185
|
-
background-color: rgba(0, 0, 0, 0.5);
|
|
1186
|
-
z-index: 1000;
|
|
1187
|
-
justify-content: center;
|
|
1188
|
-
align-items: center;
|
|
1189
|
-
}
|
|
1190
|
-
|
|
1191
|
-
.config-modal-content {
|
|
1192
|
-
background-color: var(--vscode-editor-background);
|
|
1193
|
-
border-radius: 8px;
|
|
1194
|
-
padding: 24px;
|
|
1195
|
-
width: 90%;
|
|
1196
|
-
max-width: 450px;
|
|
1197
|
-
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
|
|
1198
|
-
margin: 0 auto;
|
|
1199
|
-
}
|
|
1200
|
-
|
|
1201
|
-
.config-form {
|
|
1202
|
-
display: flex;
|
|
1203
|
-
flex-direction: column;
|
|
1204
|
-
align-items: stretch;
|
|
1205
|
-
width: 100%;
|
|
1206
|
-
}
|
|
1207
|
-
|
|
1208
|
-
.config-modal-header {
|
|
1209
|
-
display: flex;
|
|
1210
|
-
justify-content: space-between;
|
|
1211
|
-
align-items: center;
|
|
1212
|
-
margin-bottom: 20px;
|
|
1213
|
-
}
|
|
1214
|
-
|
|
1215
|
-
.config-modal-title {
|
|
1216
|
-
font-size: 18px;
|
|
1217
|
-
font-weight: bold;
|
|
1218
|
-
}
|
|
1219
|
-
|
|
1220
|
-
.config-modal-close {
|
|
1221
|
-
background: none;
|
|
1222
|
-
border: none;
|
|
1223
|
-
font-size: 20px;
|
|
1224
|
-
cursor: pointer;
|
|
1225
|
-
color: var(--vscode-foreground);
|
|
1226
|
-
}
|
|
1227
|
-
|
|
1228
|
-
.config-form-group {
|
|
1229
|
-
margin-bottom: 18px;
|
|
1230
|
-
width: 100%;
|
|
1231
|
-
}
|
|
1232
|
-
|
|
1233
|
-
.config-label {
|
|
1234
|
-
display: block;
|
|
1235
|
-
margin-bottom: 8px;
|
|
1236
|
-
font-weight: 500;
|
|
1237
|
-
}
|
|
1238
|
-
|
|
1239
|
-
.config-input {
|
|
1240
|
-
width: 100%;
|
|
1241
|
-
padding: 10px 12px;
|
|
1242
|
-
border-radius: 6px;
|
|
1243
|
-
border: 1px solid var(--vscode-input-border);
|
|
1244
|
-
background-color: var(--vscode-input-background);
|
|
1245
|
-
color: var(--vscode-input-foreground);
|
|
1246
|
-
box-sizing: border-box;
|
|
1247
|
-
}
|
|
1248
|
-
|
|
1249
|
-
.config-save-button {
|
|
1250
|
-
background: var(--primary-gradient);
|
|
1251
|
-
color: var(--text-on-primary);
|
|
1252
|
-
border: none;
|
|
1253
|
-
border-radius: 6px;
|
|
1254
|
-
padding: 10px 16px;
|
|
1255
|
-
font-weight: bold;
|
|
1256
|
-
cursor: pointer;
|
|
1257
|
-
margin-top: 12px;
|
|
1258
|
-
align-self: center;
|
|
1259
|
-
width: 80%;
|
|
1260
|
-
}
|
|
1261
|
-
|
|
1262
|
-
.loading {
|
|
1263
|
-
display: none;
|
|
1264
|
-
text-align: center;
|
|
1265
|
-
margin: 24px 0;
|
|
1266
|
-
padding: 24px;
|
|
1267
|
-
border-radius: 12px;
|
|
1268
|
-
background-color: rgba(0, 0, 0, 0.03);
|
|
1269
|
-
animation: pulse 2s infinite;
|
|
1270
|
-
}
|
|
1271
|
-
|
|
1272
|
-
@keyframes pulse {
|
|
1273
|
-
0% { opacity: 0.6; }
|
|
1274
|
-
50% { opacity: 1; }
|
|
1275
|
-
100% { opacity: 0.6; }
|
|
1276
|
-
}
|
|
1277
|
-
|
|
1278
|
-
.spinner {
|
|
1279
|
-
display: inline-block;
|
|
1280
|
-
position: relative;
|
|
1281
|
-
width: 48px;
|
|
1282
|
-
height: 48px;
|
|
1283
|
-
}
|
|
1284
|
-
|
|
1285
|
-
.spinner::before {
|
|
1286
|
-
content: "";
|
|
1287
|
-
display: block;
|
|
1288
|
-
position: absolute;
|
|
1289
|
-
top: 0;
|
|
1290
|
-
left: 0;
|
|
1291
|
-
width: 100%;
|
|
1292
|
-
height: 100%;
|
|
1293
|
-
border-radius: 50%;
|
|
1294
|
-
border: 4px solid rgba(255, 51, 51, 0.1);
|
|
1295
|
-
border-top-color: var(--primary-color);
|
|
1296
|
-
animation: spin 1s cubic-bezier(0.68, -0.55, 0.265, 1.55) infinite;
|
|
1297
|
-
}
|
|
1298
|
-
|
|
1299
|
-
@keyframes spin {
|
|
1300
|
-
to { transform: rotate(360deg); }
|
|
1301
|
-
}
|
|
1302
|
-
|
|
1303
|
-
.loading-text {
|
|
1304
|
-
margin-top: 12px;
|
|
1305
|
-
color: var(--primary-color);
|
|
1306
|
-
font-weight: 500;
|
|
1307
|
-
font-size: 14px;
|
|
1308
|
-
}
|
|
1309
|
-
|
|
1310
|
-
pre {
|
|
1311
|
-
background-color: var(--vscode-textCodeBlock-background);
|
|
1312
|
-
padding: 16px;
|
|
1313
|
-
border-radius: 12px;
|
|
1314
|
-
overflow-x: auto;
|
|
1315
|
-
border-left: 4px solid var(--primary-color);
|
|
1316
|
-
margin: 12px 0;
|
|
1317
|
-
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
1318
|
-
}
|
|
1319
|
-
|
|
1320
|
-
code {
|
|
1321
|
-
font-family: var(--vscode-editor-font-family);
|
|
1322
|
-
font-size: 13px;
|
|
1323
|
-
}
|
|
1324
|
-
|
|
1325
|
-
.agent-working {
|
|
1326
|
-
font-style: italic;
|
|
1327
|
-
opacity: 0.9;
|
|
1328
|
-
background-color: rgba(255, 51, 51, 0.05);
|
|
1329
|
-
border-left: 4px solid var(--primary-color);
|
|
1330
|
-
padding: 16px;
|
|
1331
|
-
border-radius: 12px;
|
|
1332
|
-
margin: 12px 0;
|
|
1333
|
-
animation: pulse 2s infinite;
|
|
1334
|
-
}
|
|
1335
|
-
|
|
1336
|
-
.message-actions {
|
|
1337
|
-
display: flex;
|
|
1338
|
-
justify-content: flex-end;
|
|
1339
|
-
margin-top: 5px;
|
|
1340
|
-
}
|
|
1341
|
-
|
|
1342
|
-
.restore-button {
|
|
1343
|
-
background-color: var(--vscode-button-secondaryBackground);
|
|
1344
|
-
color: var(--vscode-button-secondaryForeground);
|
|
1345
|
-
border: none;
|
|
1346
|
-
border-radius: 3px;
|
|
1347
|
-
padding: 2px 8px;
|
|
1348
|
-
font-size: 11px;
|
|
1349
|
-
cursor: pointer;
|
|
1350
|
-
margin-left: 5px;
|
|
1351
|
-
display: flex;
|
|
1352
|
-
align-items: center;
|
|
1353
|
-
transition: all 0.2s ease;
|
|
1354
|
-
}
|
|
1355
|
-
|
|
1356
|
-
.restore-button:hover {
|
|
1357
|
-
background-color: var(--vscode-button-secondaryHoverBackground);
|
|
1358
|
-
transform: translateY(-1px);
|
|
1359
|
-
}
|
|
1360
|
-
|
|
1361
|
-
.restore-button::before {
|
|
1362
|
-
content: "↺";
|
|
1363
|
-
margin-right: 4px;
|
|
1364
|
-
font-size: 12px;
|
|
1365
|
-
}
|
|
1366
|
-
|
|
1367
|
-
/* Chat sidebar styles */
|
|
1368
|
-
.app-container {
|
|
1369
|
-
display: flex;
|
|
1370
|
-
height: 100vh;
|
|
1371
|
-
width: 100%;
|
|
1372
|
-
}
|
|
1373
|
-
|
|
1374
|
-
.chat-sidebar {
|
|
1375
|
-
width: 250px;
|
|
1376
|
-
background-color: var(--vscode-sideBar-background);
|
|
1377
|
-
border-right: 1px solid var(--vscode-sideBar-border);
|
|
1378
|
-
display: flex;
|
|
1379
|
-
flex-direction: column;
|
|
1380
|
-
overflow: hidden;
|
|
1381
|
-
}
|
|
1382
|
-
|
|
1383
|
-
.chat-sidebar-header {
|
|
1384
|
-
padding: 10px;
|
|
1385
|
-
display: flex;
|
|
1386
|
-
justify-content: space-between;
|
|
1387
|
-
align-items: center;
|
|
1388
|
-
border-bottom: 1px solid var(--vscode-sideBar-border);
|
|
1389
|
-
}
|
|
1390
|
-
|
|
1391
|
-
.new-chat-button {
|
|
1392
|
-
background-color: var(--primary-color);
|
|
1393
|
-
color: var(--text-on-primary);
|
|
1394
|
-
border: none;
|
|
1395
|
-
border-radius: 3px;
|
|
1396
|
-
padding: 5px 10px;
|
|
1397
|
-
cursor: pointer;
|
|
1398
|
-
font-size: 12px;
|
|
1399
|
-
display: flex;
|
|
1400
|
-
align-items: center;
|
|
1401
|
-
}
|
|
1402
|
-
|
|
1403
|
-
.new-chat-button:hover {
|
|
1404
|
-
background-color: #cc0000;
|
|
1405
|
-
}
|
|
1406
|
-
|
|
1407
|
-
.new-chat-button svg {
|
|
1408
|
-
margin-right: 5px;
|
|
1409
|
-
width: 14px;
|
|
1410
|
-
height: 14px;
|
|
1411
|
-
}
|
|
1412
|
-
|
|
1413
|
-
.chat-list {
|
|
1414
|
-
flex: 1;
|
|
1415
|
-
overflow-y: auto;
|
|
1416
|
-
padding: 5px;
|
|
1417
|
-
}
|
|
1418
|
-
|
|
1419
|
-
.chat-item {
|
|
1420
|
-
padding: 8px 10px;
|
|
1421
|
-
margin-bottom: 2px;
|
|
1422
|
-
border-radius: 4px;
|
|
1423
|
-
cursor: pointer;
|
|
1424
|
-
display: flex;
|
|
1425
|
-
align-items: center;
|
|
1426
|
-
justify-content: space-between;
|
|
1427
|
-
transition: background-color 0.2s;
|
|
1428
|
-
}
|
|
1429
|
-
|
|
1430
|
-
.chat-item:hover {
|
|
1431
|
-
background-color: var(--vscode-list-hoverBackground);
|
|
1432
|
-
}
|
|
1433
|
-
|
|
1434
|
-
.chat-item.active {
|
|
1435
|
-
background-color: var(--primary-color);
|
|
1436
|
-
color: var(--text-on-primary);
|
|
1437
|
-
}
|
|
1438
|
-
|
|
1439
|
-
.chat-title {
|
|
1440
|
-
flex: 1;
|
|
1441
|
-
white-space: nowrap;
|
|
1442
|
-
overflow: hidden;
|
|
1443
|
-
text-overflow: ellipsis;
|
|
1444
|
-
font-size: 13px;
|
|
1445
|
-
}
|
|
1446
|
-
|
|
1447
|
-
.chat-actions {
|
|
1448
|
-
display: none;
|
|
1449
|
-
margin-left: 5px;
|
|
1450
|
-
}
|
|
1451
|
-
|
|
1452
|
-
.chat-item:hover .chat-actions {
|
|
1453
|
-
display: flex;
|
|
1454
|
-
}
|
|
1455
|
-
|
|
1456
|
-
.chat-action-button {
|
|
1457
|
-
background: none;
|
|
1458
|
-
border: none;
|
|
1459
|
-
color: var(--vscode-foreground);
|
|
1460
|
-
cursor: pointer;
|
|
1461
|
-
padding: 2px 5px;
|
|
1462
|
-
font-size: 12px;
|
|
1463
|
-
opacity: 0.7;
|
|
1464
|
-
}
|
|
1465
|
-
|
|
1466
|
-
.chat-action-button:hover {
|
|
1467
|
-
opacity: 1;
|
|
1468
|
-
}
|
|
1469
|
-
|
|
1470
|
-
.main-content {
|
|
1471
|
-
flex: 1;
|
|
1472
|
-
display: flex;
|
|
1473
|
-
flex-direction: column;
|
|
1474
|
-
overflow: hidden;
|
|
1475
|
-
}
|
|
1476
|
-
</style>
|
|
1477
|
-
</head>
|
|
1478
|
-
<body>
|
|
1479
|
-
<div class="app-container">
|
|
1480
|
-
<div class="chat-sidebar">
|
|
1481
|
-
<div class="chat-sidebar-header">
|
|
1482
|
-
<h3>Chats</h3>
|
|
1483
|
-
<button class="new-chat-button" id="new-chat-button">
|
|
1484
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1485
|
-
<line x1="12" y1="5" x2="12" y2="19"></line>
|
|
1486
|
-
<line x1="5" y1="12" x2="19" y2="12"></line>
|
|
1487
|
-
</svg>
|
|
1488
|
-
New Chat
|
|
1489
|
-
</button>
|
|
1490
|
-
</div>
|
|
1491
|
-
<div class="chat-list" id="chat-list">
|
|
1492
|
-
<!-- Chat items will be added here dynamically -->
|
|
1493
|
-
</div>
|
|
1494
|
-
</div>
|
|
1495
|
-
|
|
1496
|
-
<div class="main-content">
|
|
1497
|
-
<div class="header">
|
|
1498
|
-
<div class="header-title">
|
|
1499
|
-
<div class="profile-icon">
|
|
1500
|
-
<img src="${infinityIconPath}" alt="AI" />
|
|
1501
|
-
</div>
|
|
1502
|
-
<span>Samantha Coder</span>
|
|
1503
|
-
</div>
|
|
1504
|
-
<div class="header-actions">
|
|
1505
|
-
<button class="config-button" id="config-button">⚙️ Config</button>
|
|
1506
|
-
<button class="lang-button" id="lang-button">${translations[this._language].changeLanguageButton}</button>
|
|
1507
|
-
</div>
|
|
1508
|
-
</div>
|
|
1509
|
-
|
|
1510
|
-
<div class="config-modal" id="config-modal">
|
|
1511
|
-
<div class="config-modal-content">
|
|
1512
|
-
<div class="config-modal-header">
|
|
1513
|
-
<div class="config-modal-title">Configuration</div>
|
|
1514
|
-
<button class="config-modal-close" id="config-modal-close">×</button>
|
|
1515
|
-
</div>
|
|
1516
|
-
<div class="config-form">
|
|
1517
|
-
<div class="config-form-group">
|
|
1518
|
-
<label class="config-label" for="model-input">Model Name</label>
|
|
1519
|
-
<input type="text" id="model-input" class="config-input" placeholder="Enter model name">
|
|
1520
|
-
</div>
|
|
1521
|
-
<div class="config-form-group">
|
|
1522
|
-
<label class="config-label" for="api-base-url-input">API Base URL</label>
|
|
1523
|
-
<input type="text" id="api-base-url-input" class="config-input" placeholder="Enter API base URL">
|
|
1524
|
-
</div>
|
|
1525
|
-
<div class="config-form-group">
|
|
1526
|
-
<label class="config-label" for="api-key-input">API Key (optional)</label>
|
|
1527
|
-
<input type="password" id="api-key-input" class="config-input" placeholder="Enter API key">
|
|
1528
|
-
</div>
|
|
1529
|
-
<div class="config-form-group">
|
|
1530
|
-
<label class="config-label" for="max-tokens-input">Max Tokens</label>
|
|
1531
|
-
<input type="number" id="max-tokens-input" class="config-input" placeholder="Enter max tokens">
|
|
1532
|
-
</div>
|
|
1533
|
-
<button class="config-save-button" id="config-save-button">Save Configuration</button>
|
|
1534
|
-
</div>
|
|
1535
|
-
</div>
|
|
1536
|
-
</div>
|
|
1537
|
-
|
|
1538
|
-
<div class="conversation" id="conversation">
|
|
1539
|
-
<div class="message assistant">
|
|
1540
|
-
${translations[this._language].welcomeMessage}
|
|
1541
|
-
</div>
|
|
1542
|
-
</div>
|
|
1543
|
-
|
|
1544
|
-
<div class="loading" id="loading">
|
|
1545
|
-
<div class="spinner"></div>
|
|
1546
|
-
<div class="loading-text">${translations[this._language].loadingText}</div>
|
|
1547
|
-
</div>
|
|
1548
|
-
|
|
1549
|
-
<div class="input-area">
|
|
1550
|
-
<input type="text" id="query-input" placeholder="${translations[this._language].inputPlaceholder}" />
|
|
1551
|
-
<button class="send-button" id="send-button">${translations[this._language].sendButton}</button>
|
|
1552
|
-
<button class="clear-button" id="clear-button">${translations[this._language].clearButton}</button>
|
|
1553
|
-
</div>
|
|
1554
|
-
</div>
|
|
1555
|
-
</div>
|
|
1556
|
-
|
|
1557
|
-
<script>
|
|
1558
|
-
(function() {
|
|
1559
|
-
const vscode = acquireVsCodeApi();
|
|
1560
|
-
const conversationEl = document.getElementById('conversation');
|
|
1561
|
-
const queryInput = document.getElementById('query-input');
|
|
1562
|
-
const sendButton = document.getElementById('send-button');
|
|
1563
|
-
const clearButton = document.getElementById('clear-button');
|
|
1564
|
-
const langButton = document.getElementById('lang-button');
|
|
1565
|
-
const loadingEl = document.getElementById('loading');
|
|
1566
|
-
const chatListEl = document.getElementById('chat-list');
|
|
1567
|
-
const newChatButton = document.getElementById('new-chat-button');
|
|
1568
|
-
const configButton = document.getElementById('config-button');
|
|
1569
|
-
const configModal = document.getElementById('config-modal');
|
|
1570
|
-
const configModalClose = document.getElementById('config-modal-close');
|
|
1571
|
-
const configSaveButton = document.getElementById('config-save-button');
|
|
1572
|
-
const modelInput = document.getElementById('model-input');
|
|
1573
|
-
const apiBaseUrlInput = document.getElementById('api-base-url-input');
|
|
1574
|
-
const apiKeyInput = document.getElementById('api-key-input');
|
|
1575
|
-
const maxTokensInput = document.getElementById('max-tokens-input');
|
|
1576
|
-
|
|
1577
|
-
// Current translations
|
|
1578
|
-
let currentTranslations = ${JSON.stringify(translations[this._language])};
|
|
1579
|
-
|
|
1580
|
-
// Current chat ID
|
|
1581
|
-
let currentChatId = null;
|
|
1582
|
-
|
|
1583
|
-
// Format messages with markdown-like syntax
|
|
1584
|
-
function formatMessage(text) {
|
|
1585
|
-
// Handle code blocks
|
|
1586
|
-
text = text.replace(/\`\`\`([^\`]+)\`\`\`/g, '<pre><code>$1</code></pre>');
|
|
1587
|
-
|
|
1588
|
-
// Handle inline code
|
|
1589
|
-
text = text.replace(/\`([^\`]+)\`/g, '<code>$1</code>');
|
|
1590
|
-
|
|
1591
|
-
// Handle line breaks
|
|
1592
|
-
text = text.replace(/\\n/g, '<br>');
|
|
1593
|
-
|
|
1594
|
-
return text;
|
|
1595
|
-
}
|
|
1596
|
-
|
|
1597
|
-
function addMessageToUI(message) {
|
|
1598
|
-
const messageEl = document.createElement('div');
|
|
1599
|
-
messageEl.className = \`message \${message.role}\`;
|
|
1600
|
-
messageEl.dataset.timestamp = message.timestamp;
|
|
1601
|
-
|
|
1602
|
-
// Add agent-working class if it's the agent working message
|
|
1603
|
-
if (message.role === 'assistant' && message.content.includes(currentTranslations.workingMessage)) {
|
|
1604
|
-
messageEl.classList.add('agent-working');
|
|
1605
|
-
}
|
|
1606
|
-
|
|
1607
|
-
const contentDiv = document.createElement('div');
|
|
1608
|
-
contentDiv.className = 'message-content';
|
|
1609
|
-
contentDiv.innerHTML = formatMessage(message.content);
|
|
1610
|
-
|
|
1611
|
-
// Simply add content to the message for all message types
|
|
1612
|
-
messageEl.appendChild(contentDiv);
|
|
1613
|
-
|
|
1614
|
-
conversationEl.appendChild(messageEl);
|
|
1615
|
-
conversationEl.scrollTop = conversationEl.scrollHeight;
|
|
1616
|
-
}
|
|
1617
|
-
|
|
1618
|
-
// Update UI text elements based on language
|
|
1619
|
-
function updateUILanguage(trans) {
|
|
1620
|
-
currentTranslations = trans;
|
|
1621
|
-
document.documentElement.lang = currentTranslations.changeLanguageButton === 'EN' ? 'pt-br' : 'en';
|
|
1622
|
-
|
|
1623
|
-
// Update placeholders and button texts
|
|
1624
|
-
queryInput.placeholder = currentTranslations.inputPlaceholder;
|
|
1625
|
-
sendButton.textContent = currentTranslations.sendButton;
|
|
1626
|
-
clearButton.textContent = currentTranslations.clearButton;
|
|
1627
|
-
langButton.textContent = currentTranslations.changeLanguageButton;
|
|
1628
|
-
|
|
1629
|
-
// Update loading text
|
|
1630
|
-
document.querySelector('.loading-text').textContent = currentTranslations.loadingText;
|
|
1631
|
-
}
|
|
1632
|
-
|
|
1633
|
-
// Handle sending query
|
|
1634
|
-
function sendQuery() {
|
|
1635
|
-
const query = queryInput.value.trim();
|
|
1636
|
-
if (query) {
|
|
1637
|
-
vscode.postMessage({
|
|
1638
|
-
type: 'sendQuery',
|
|
1639
|
-
value: query
|
|
1640
|
-
});
|
|
1641
|
-
queryInput.value = '';
|
|
1642
|
-
}
|
|
1643
|
-
}
|
|
1644
|
-
|
|
1645
|
-
// Create a chat item element
|
|
1646
|
-
function createChatItem(chat) {
|
|
1647
|
-
const chatItem = document.createElement('div');
|
|
1648
|
-
chatItem.className = 'chat-item';
|
|
1649
|
-
if (chat.id === currentChatId) {
|
|
1650
|
-
chatItem.classList.add('active');
|
|
1651
|
-
}
|
|
1652
|
-
chatItem.dataset.chatId = chat.id;
|
|
1653
|
-
|
|
1654
|
-
const titleSpan = document.createElement('span');
|
|
1655
|
-
titleSpan.className = 'chat-title';
|
|
1656
|
-
titleSpan.textContent = chat.title;
|
|
1657
|
-
|
|
1658
|
-
const actionsDiv = document.createElement('div');
|
|
1659
|
-
actionsDiv.className = 'chat-actions';
|
|
1660
|
-
|
|
1661
|
-
const renameButton = document.createElement('button');
|
|
1662
|
-
renameButton.className = 'chat-action-button';
|
|
1663
|
-
renameButton.innerHTML = '✏️';
|
|
1664
|
-
renameButton.title = 'Rename';
|
|
1665
|
-
renameButton.onclick = (e) => {
|
|
1666
|
-
e.stopPropagation();
|
|
1667
|
-
const newTitle = prompt('Enter new chat title:', chat.title);
|
|
1668
|
-
if (newTitle && newTitle.trim() !== '') {
|
|
1669
|
-
vscode.postMessage({
|
|
1670
|
-
type: 'renameChat',
|
|
1671
|
-
chatId: chat.id,
|
|
1672
|
-
newTitle: newTitle.trim()
|
|
1673
|
-
});
|
|
1674
|
-
}
|
|
1675
|
-
};
|
|
1676
|
-
|
|
1677
|
-
const deleteButton = document.createElement('button');
|
|
1678
|
-
deleteButton.className = 'chat-action-button';
|
|
1679
|
-
deleteButton.innerHTML = '🗑️';
|
|
1680
|
-
deleteButton.title = 'Delete';
|
|
1681
|
-
deleteButton.onclick = (e) => {
|
|
1682
|
-
e.stopPropagation();
|
|
1683
|
-
// Prevent deleting the only chat
|
|
1684
|
-
const chatCount = document.querySelectorAll('.chat-item').length;
|
|
1685
|
-
if (chatCount <= 1) {
|
|
1686
|
-
alert('Cannot delete the only chat. Create a new chat first.');
|
|
1687
|
-
return;
|
|
1688
|
-
}
|
|
1689
|
-
|
|
1690
|
-
if (confirm('Are you sure you want to delete this chat?')) {
|
|
1691
|
-
vscode.postMessage({
|
|
1692
|
-
type: 'deleteChat',
|
|
1693
|
-
chatId: chat.id
|
|
1694
|
-
});
|
|
1695
|
-
}
|
|
1696
|
-
};
|
|
1697
|
-
|
|
1698
|
-
actionsDiv.appendChild(renameButton);
|
|
1699
|
-
actionsDiv.appendChild(deleteButton);
|
|
1700
|
-
|
|
1701
|
-
chatItem.appendChild(titleSpan);
|
|
1702
|
-
chatItem.appendChild(actionsDiv);
|
|
1703
|
-
|
|
1704
|
-
chatItem.onclick = () => {
|
|
1705
|
-
// Don't switch if already active
|
|
1706
|
-
if (chat.id !== currentChatId) {
|
|
1707
|
-
vscode.postMessage({
|
|
1708
|
-
type: 'switchChat',
|
|
1709
|
-
chatId: chat.id
|
|
1710
|
-
});
|
|
1711
|
-
}
|
|
1712
|
-
};
|
|
1713
|
-
|
|
1714
|
-
return chatItem;
|
|
1715
|
-
}
|
|
1716
|
-
|
|
1717
|
-
// Update the chat list
|
|
1718
|
-
function updateChatList(chats, activeChatId) {
|
|
1719
|
-
chatListEl.innerHTML = '';
|
|
1720
|
-
currentChatId = activeChatId;
|
|
1721
|
-
|
|
1722
|
-
chats.forEach(chat => {
|
|
1723
|
-
chatListEl.appendChild(createChatItem(chat));
|
|
1724
|
-
});
|
|
1725
|
-
}
|
|
1726
|
-
|
|
1727
|
-
// Event listeners
|
|
1728
|
-
sendButton.addEventListener('click', sendQuery);
|
|
1729
|
-
|
|
1730
|
-
queryInput.addEventListener('keydown', (e) => {
|
|
1731
|
-
if (e.key === 'Enter') {
|
|
1732
|
-
sendQuery();
|
|
1733
|
-
}
|
|
1734
|
-
});
|
|
1735
|
-
|
|
1736
|
-
clearButton.addEventListener('click', () => {
|
|
1737
|
-
vscode.postMessage({
|
|
1738
|
-
type: 'clearConversation'
|
|
1739
|
-
});
|
|
1740
|
-
});
|
|
1741
|
-
|
|
1742
|
-
langButton.addEventListener('click', () => {
|
|
1743
|
-
vscode.postMessage({
|
|
1744
|
-
type: 'toggleLanguage'
|
|
1745
|
-
});
|
|
1746
|
-
});
|
|
1747
|
-
|
|
1748
|
-
newChatButton.addEventListener('click', () => {
|
|
1749
|
-
const title = prompt('Enter chat title:', 'New Chat');
|
|
1750
|
-
if (title && title.trim() !== '') {
|
|
1751
|
-
vscode.postMessage({
|
|
1752
|
-
type: 'createNewChat',
|
|
1753
|
-
title: title.trim()
|
|
1754
|
-
});
|
|
1755
|
-
} else {
|
|
1756
|
-
vscode.postMessage({
|
|
1757
|
-
type: 'createNewChat'
|
|
1758
|
-
});
|
|
1759
|
-
}
|
|
1760
|
-
});
|
|
1761
|
-
|
|
1762
|
-
// Config button event
|
|
1763
|
-
configButton.addEventListener('click', () => {
|
|
1764
|
-
// Request current configuration values
|
|
1765
|
-
vscode.postMessage({
|
|
1766
|
-
type: 'getConfiguration'
|
|
1767
|
-
});
|
|
1768
|
-
|
|
1769
|
-
// Show modal
|
|
1770
|
-
configModal.style.display = 'flex';
|
|
1771
|
-
});
|
|
1772
|
-
|
|
1773
|
-
// Close modal when clicking close button
|
|
1774
|
-
configModalClose.addEventListener('click', () => {
|
|
1775
|
-
configModal.style.display = 'none';
|
|
1776
|
-
});
|
|
1777
|
-
|
|
1778
|
-
// Close modal when clicking outside the modal content
|
|
1779
|
-
configModal.addEventListener('click', (e) => {
|
|
1780
|
-
if (e.target === configModal) {
|
|
1781
|
-
configModal.style.display = 'none';
|
|
1782
|
-
}
|
|
1783
|
-
});
|
|
1784
|
-
|
|
1785
|
-
// Save configuration
|
|
1786
|
-
configSaveButton.addEventListener('click', () => {
|
|
1787
|
-
const modelName = modelInput.value.trim();
|
|
1788
|
-
const apiBaseUrl = apiBaseUrlInput.value.trim();
|
|
1789
|
-
const apiKey = apiKeyInput.value.trim();
|
|
1790
|
-
const maxTokens = maxTokensInput.value.trim();
|
|
1791
|
-
|
|
1792
|
-
vscode.postMessage({
|
|
1793
|
-
type: 'saveConfiguration',
|
|
1794
|
-
modelName: modelName,
|
|
1795
|
-
apiBaseUrl: apiBaseUrl,
|
|
1796
|
-
apiKey: apiKey,
|
|
1797
|
-
maxTokens: maxTokens
|
|
1798
|
-
});
|
|
1799
|
-
|
|
1800
|
-
configModal.style.display = 'none';
|
|
1801
|
-
});
|
|
1802
|
-
|
|
1803
|
-
// Handle messages from the extension
|
|
1804
|
-
window.addEventListener('message', event => {
|
|
1805
|
-
const message = event.data;
|
|
1806
|
-
switch (message.type) {
|
|
1807
|
-
case 'addMessage':
|
|
1808
|
-
addMessageToUI(message.message);
|
|
1809
|
-
break;
|
|
1810
|
-
case 'clearConversation':
|
|
1811
|
-
conversationEl.innerHTML = '';
|
|
1812
|
-
addMessageToUI({
|
|
1813
|
-
role: 'assistant',
|
|
1814
|
-
content: currentTranslations.welcomeMessage,
|
|
1815
|
-
timestamp: new Date().toISOString()
|
|
1816
|
-
});
|
|
1817
|
-
break;
|
|
1818
|
-
case 'setLoading':
|
|
1819
|
-
loadingEl.style.display = message.isLoading ? 'block' : 'none';
|
|
1820
|
-
break;
|
|
1821
|
-
case 'updateLanguage':
|
|
1822
|
-
updateUILanguage(message.translations);
|
|
1823
|
-
break;
|
|
1824
|
-
case 'restoreConversation':
|
|
1825
|
-
conversationEl.innerHTML = '';
|
|
1826
|
-
if (Array.isArray(message.messages)) {
|
|
1827
|
-
message.messages.forEach(msg => {
|
|
1828
|
-
addMessageToUI(msg);
|
|
1829
|
-
});
|
|
1830
|
-
}
|
|
1831
|
-
break;
|
|
1832
|
-
case 'updateChats':
|
|
1833
|
-
updateChatList(message.chats, message.currentChatId);
|
|
1834
|
-
break;
|
|
1835
|
-
case 'switchChat':
|
|
1836
|
-
currentChatId = message.chatId;
|
|
1837
|
-
conversationEl.innerHTML = '';
|
|
1838
|
-
if (Array.isArray(message.messages)) {
|
|
1839
|
-
message.messages.forEach(msg => {
|
|
1840
|
-
addMessageToUI(msg);
|
|
1841
|
-
});
|
|
1842
|
-
}
|
|
1843
|
-
break;
|
|
1844
|
-
case 'configuration':
|
|
1845
|
-
// Update configuration form with values from extension
|
|
1846
|
-
modelInput.value = message.model || '';
|
|
1847
|
-
apiBaseUrlInput.value = message.apiBaseUrl || '';
|
|
1848
|
-
apiKeyInput.value = message.apiKey || '';
|
|
1849
|
-
maxTokensInput.value = message.maxTokens || '';
|
|
1850
|
-
break;
|
|
1851
|
-
}
|
|
1852
|
-
});
|
|
1853
|
-
})();
|
|
1854
|
-
</script>
|
|
1855
|
-
</body>
|
|
1856
|
-
</html>`;
|
|
1857
|
-
}
|
|
1858
|
-
getAgentUtils() {
|
|
1859
|
-
return this._agentUtils;
|
|
1860
|
-
}
|
|
1861
|
-
toggleLanguage() {
|
|
1862
|
-
// Toggle between English and Portuguese
|
|
1863
|
-
this._language = this._language === 'en' ? 'pt-br' : 'en';
|
|
1864
|
-
// Update the configuration
|
|
1865
|
-
const config = vscode.workspace.getConfiguration('aiAssistant');
|
|
1866
|
-
config.update('language', this._language, true);
|
|
1867
|
-
// Update the webview content while preserving chat history
|
|
1868
|
-
if (this._view) {
|
|
1869
|
-
this._view.webview.html = this._getHtmlForWebview(this._view.webview);
|
|
1870
|
-
// Send current chat state to webview
|
|
1871
|
-
this._postMessageToWebview({
|
|
1872
|
-
type: 'updateLanguage',
|
|
1873
|
-
translations: translations[this._language]
|
|
1874
|
-
});
|
|
1875
|
-
// Restore current chat messages
|
|
1876
|
-
const currentChat = this._getCurrentChat();
|
|
1877
|
-
if (currentChat) {
|
|
1878
|
-
this._postMessageToWebview({
|
|
1879
|
-
type: 'restoreConversation',
|
|
1880
|
-
messages: this._getMessagesForDisplay(currentChat.messages)
|
|
1881
|
-
});
|
|
1882
|
-
}
|
|
1883
|
-
// Update chat list
|
|
1884
|
-
this._postMessageToWebview({
|
|
1885
|
-
type: 'updateChats',
|
|
1886
|
-
chats: this._chats,
|
|
1887
|
-
currentChatId: this._currentChatId
|
|
1888
|
-
});
|
|
1889
|
-
}
|
|
1890
|
-
}
|
|
1891
|
-
// Take a snapshot of the current state
|
|
1892
|
-
async _takeStateSnapshot(messageId) {
|
|
1893
|
-
try {
|
|
1894
|
-
// Create a snapshot even if no editor is active
|
|
1895
|
-
const snapshot = {
|
|
1896
|
-
files: {},
|
|
1897
|
-
timestamp: new Date().toISOString(),
|
|
1898
|
-
chatId: this._currentChatId,
|
|
1899
|
-
trackedFiles: [...this._trackedFiles] // Store the set of tracked files at this point
|
|
1900
|
-
};
|
|
1901
|
-
// Track open editors and their content
|
|
1902
|
-
for (const editor of vscode.window.visibleTextEditors) {
|
|
1903
|
-
const document = editor.document;
|
|
1904
|
-
snapshot.files[document.uri.fsPath] = {
|
|
1905
|
-
exists: true,
|
|
1906
|
-
content: document.getText(),
|
|
1907
|
-
version: document.version
|
|
1908
|
-
};
|
|
1909
|
-
}
|
|
1910
|
-
// Store the snapshot
|
|
1911
|
-
this._conversationHistory[messageId] = snapshot;
|
|
1912
|
-
// If there's an active editor, mark its file as the primary focus
|
|
1913
|
-
const activeEditor = vscode.window.activeTextEditor;
|
|
1914
|
-
if (activeEditor) {
|
|
1915
|
-
const document = activeEditor.document;
|
|
1916
|
-
if (snapshot.files[document.uri.fsPath]) {
|
|
1917
|
-
snapshot.primaryFile = document.uri.fsPath;
|
|
1918
|
-
}
|
|
1919
|
-
}
|
|
1920
|
-
// Reset the tracked files set after taking a snapshot
|
|
1921
|
-
this._trackedFiles.clear();
|
|
1922
|
-
// Re-add the currently open files to the tracked set
|
|
1923
|
-
vscode.window.visibleTextEditors.forEach(editor => {
|
|
1924
|
-
this._trackedFiles.add(editor.document.uri.fsPath);
|
|
1925
|
-
});
|
|
1926
|
-
}
|
|
1927
|
-
catch (error) {
|
|
1928
|
-
console.error('Error taking state snapshot:', error);
|
|
1929
|
-
}
|
|
1930
|
-
}
|
|
1931
|
-
// Restore to the state before a specific message
|
|
1932
|
-
async restoreToState(messageId) {
|
|
1933
|
-
try {
|
|
1934
|
-
const snapshot = this._conversationHistory[messageId];
|
|
1935
|
-
if (!snapshot) {
|
|
1936
|
-
vscode.window.showErrorMessage('No previous state found for this message');
|
|
1937
|
-
return;
|
|
1938
|
-
}
|
|
1939
|
-
// Get the current chat
|
|
1940
|
-
const currentChat = this._getCurrentChat();
|
|
1941
|
-
if (!currentChat) {
|
|
1942
|
-
return;
|
|
1943
|
-
}
|
|
1944
|
-
// Find the message in the conversation
|
|
1945
|
-
const messageIndex = currentChat.messages.findIndex(msg => msg.timestamp === messageId);
|
|
1946
|
-
if (messageIndex === -1) {
|
|
1947
|
-
vscode.window.showErrorMessage('Message not found in conversation');
|
|
1948
|
-
return;
|
|
1949
|
-
}
|
|
1950
|
-
// Get a list of files that were created or modified after this snapshot
|
|
1951
|
-
const filesCreatedAfter = new Set(this._trackedFiles);
|
|
1952
|
-
// If the snapshot has trackedFiles, remove them from the current set
|
|
1953
|
-
if (snapshot.trackedFiles) {
|
|
1954
|
-
snapshot.trackedFiles.forEach(file => {
|
|
1955
|
-
filesCreatedAfter.delete(file);
|
|
1956
|
-
});
|
|
1957
|
-
}
|
|
1958
|
-
// Check if there are files that were created after this message
|
|
1959
|
-
if (filesCreatedAfter.size > 0) {
|
|
1960
|
-
// Ask user if they want to delete newly created files
|
|
1961
|
-
const deleteNewFiles = await vscode.window.showInformationMessage(`${filesCreatedAfter.size} file(s) were created after this message. Delete them?`, 'Yes', 'No');
|
|
1962
|
-
if (deleteNewFiles === 'Yes') {
|
|
1963
|
-
// Delete files created after the snapshot
|
|
1964
|
-
const deletedCount = await this._deleteFiles(filesCreatedAfter);
|
|
1965
|
-
if (deletedCount > 0) {
|
|
1966
|
-
vscode.window.showInformationMessage(`Deleted ${deletedCount} file(s) created after the message`);
|
|
1967
|
-
}
|
|
1968
|
-
}
|
|
1969
|
-
}
|
|
1970
|
-
// Implement restoration logic
|
|
1971
|
-
if (snapshot.files) {
|
|
1972
|
-
// Keep track of successful restorations
|
|
1973
|
-
let restoredCount = 0;
|
|
1974
|
-
let primaryFileOpened = false;
|
|
1975
|
-
// Process each file in the snapshot
|
|
1976
|
-
for (const [filePath, fileData] of Object.entries(snapshot.files)) {
|
|
1977
|
-
try {
|
|
1978
|
-
// Restore file content if it existed at the time of snapshot
|
|
1979
|
-
if (fileData.exists && fileData.content) {
|
|
1980
|
-
await this._agentUtils.writeFile(filePath, fileData.content);
|
|
1981
|
-
restoredCount++;
|
|
1982
|
-
// Open the primary file (the one that was active when snapshot was taken)
|
|
1983
|
-
if (snapshot.primaryFile === filePath && !primaryFileOpened) {
|
|
1984
|
-
const uri = vscode.Uri.file(filePath);
|
|
1985
|
-
const document = await vscode.workspace.openTextDocument(uri);
|
|
1986
|
-
await vscode.window.showTextDocument(document);
|
|
1987
|
-
primaryFileOpened = true;
|
|
1988
|
-
}
|
|
1989
|
-
}
|
|
1990
|
-
}
|
|
1991
|
-
catch (fileError) {
|
|
1992
|
-
console.error(`Error restoring file ${filePath}:`, fileError);
|
|
1993
|
-
}
|
|
1994
|
-
}
|
|
1995
|
-
// If we have a primary file but couldn't open it, try opening the first restored file
|
|
1996
|
-
if (!primaryFileOpened && restoredCount > 0 && snapshot.primaryFile) {
|
|
1997
|
-
try {
|
|
1998
|
-
const uri = vscode.Uri.file(snapshot.primaryFile);
|
|
1999
|
-
const document = await vscode.workspace.openTextDocument(uri);
|
|
2000
|
-
await vscode.window.showTextDocument(document);
|
|
2001
|
-
}
|
|
2002
|
-
catch (error) {
|
|
2003
|
-
console.error('Error opening primary file:', error);
|
|
2004
|
-
}
|
|
2005
|
-
}
|
|
2006
|
-
// Show restoration success message
|
|
2007
|
-
if (restoredCount > 0) {
|
|
2008
|
-
vscode.window.showInformationMessage(`Restored ${restoredCount} file(s) to state before message`);
|
|
2009
|
-
}
|
|
2010
|
-
else {
|
|
2011
|
-
// If no files were in the snapshot, this was likely the start of the conversation
|
|
2012
|
-
vscode.window.showInformationMessage('Restored to initial state before any changes');
|
|
2013
|
-
}
|
|
2014
|
-
}
|
|
2015
|
-
// Remove all messages after this one from the conversation
|
|
2016
|
-
currentChat.messages = currentChat.messages.slice(0, messageIndex + 1);
|
|
2017
|
-
currentChat.updatedAt = new Date().toISOString();
|
|
2018
|
-
// Update the webview to reflect the changes
|
|
2019
|
-
await this._postMessageToWebview({
|
|
2020
|
-
type: 'restoreConversation',
|
|
2021
|
-
messages: this._getMessagesForDisplay(currentChat.messages)
|
|
2022
|
-
});
|
|
2023
|
-
// Reset the tracked files to the state at the time of the snapshot
|
|
2024
|
-
this._trackedFiles.clear();
|
|
2025
|
-
if (snapshot.trackedFiles) {
|
|
2026
|
-
snapshot.trackedFiles.forEach(file => {
|
|
2027
|
-
this._trackedFiles.add(file);
|
|
2028
|
-
});
|
|
2029
|
-
}
|
|
2030
|
-
}
|
|
2031
|
-
catch (error) {
|
|
2032
|
-
console.error('Error restoring state:', error);
|
|
2033
|
-
vscode.window.showErrorMessage(`Failed to restore state: ${error instanceof Error ? error.message : String(error)}`);
|
|
2034
|
-
}
|
|
2035
|
-
}
|
|
2036
|
-
// Helper method to delete multiple files
|
|
2037
|
-
async _deleteFiles(filesToDelete) {
|
|
2038
|
-
let deletedCount = 0;
|
|
2039
|
-
for (const filePath of filesToDelete) {
|
|
2040
|
-
try {
|
|
2041
|
-
const uri = vscode.Uri.file(filePath);
|
|
2042
|
-
// Check if the file exists before attempting to delete
|
|
2043
|
-
try {
|
|
2044
|
-
await vscode.workspace.fs.stat(uri);
|
|
2045
|
-
// File exists, delete it
|
|
2046
|
-
await vscode.workspace.fs.delete(uri, { useTrash: false });
|
|
2047
|
-
deletedCount++;
|
|
2048
|
-
}
|
|
2049
|
-
catch (statError) {
|
|
2050
|
-
// File doesn't exist, skip it
|
|
2051
|
-
console.log(`File ${filePath} doesn't exist, skipping deletion`);
|
|
2052
|
-
}
|
|
2053
|
-
}
|
|
2054
|
-
catch (error) {
|
|
2055
|
-
console.error(`Error deleting file ${filePath}:`, error);
|
|
2056
|
-
}
|
|
2057
|
-
}
|
|
2058
|
-
return deletedCount;
|
|
2059
|
-
}
|
|
2060
|
-
async _getConfiguration() {
|
|
2061
|
-
if (!this._view) {
|
|
2062
|
-
return;
|
|
2063
|
-
}
|
|
2064
|
-
const config = vscode.workspace.getConfiguration('aiAssistant');
|
|
2065
|
-
const model = config.get('model');
|
|
2066
|
-
const apiKey = config.get('apiKey');
|
|
2067
|
-
const apiBaseUrl = config.get('apiBaseUrl');
|
|
2068
|
-
const maxTokens = config.get('maxTokens');
|
|
2069
|
-
this._postMessageToWebview({
|
|
2070
|
-
type: 'configuration',
|
|
2071
|
-
model,
|
|
2072
|
-
apiKey,
|
|
2073
|
-
apiBaseUrl,
|
|
2074
|
-
maxTokens
|
|
2075
|
-
});
|
|
2076
|
-
}
|
|
2077
|
-
async _saveConfiguration(modelName, apiBaseUrl, apiKey, maxTokens) {
|
|
2078
|
-
const config = vscode.workspace.getConfiguration('aiAssistant');
|
|
2079
|
-
if (modelName) {
|
|
2080
|
-
await config.update('model', modelName, vscode.ConfigurationTarget.Global);
|
|
2081
|
-
}
|
|
2082
|
-
if (apiBaseUrl) {
|
|
2083
|
-
await config.update('apiBaseUrl', apiBaseUrl, vscode.ConfigurationTarget.Global);
|
|
2084
|
-
}
|
|
2085
|
-
if (apiKey) {
|
|
2086
|
-
await config.update('apiKey', apiKey, vscode.ConfigurationTarget.Global);
|
|
2087
|
-
}
|
|
2088
|
-
if (maxTokens) {
|
|
2089
|
-
const maxTokensNum = parseInt(maxTokens, 10);
|
|
2090
|
-
if (!isNaN(maxTokensNum) && maxTokensNum > 0) {
|
|
2091
|
-
await config.update('maxTokens', maxTokensNum, vscode.ConfigurationTarget.Global);
|
|
2092
|
-
}
|
|
2093
|
-
}
|
|
2094
|
-
vscode.window.showInformationMessage('AI Assistant configuration updated');
|
|
2095
|
-
}
|
|
2096
|
-
}
|
|
2097
|
-
exports.AIAssistantViewProvider = AIAssistantViewProvider;
|
|
2098
|
-
//# sourceMappingURL=aiAssistantViewProvider.js.map
|