mirror of
https://github.com/ggml-org/llama.vscode.git
synced 2026-05-07 01:15:23 +00:00
Compare commits
1 commit
master
...
show_error
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a65ad28171 |
11 changed files with 115 additions and 67 deletions
|
|
@ -51,36 +51,13 @@ export class Architect {
|
|||
|
||||
setOnSaveDeleteFileForDb = (context: vscode.ExtensionContext) => {
|
||||
const saveListener = vscode.workspace.onDidSaveTextDocument(async (document) => {
|
||||
try {
|
||||
if (!this.app.configuration.rag_enabled || this.app.configuration.rag_max_files <= 0) return;
|
||||
if (!this.app.chatContext.isImageOrVideoFile(document.uri.toString())){
|
||||
// Update after a delay and only if the file is not changed in the meantime to avoid too often updates
|
||||
let updateTime = Date.now()
|
||||
let fileProperties = this.app.chatContext.getFileProperties(document.uri.toString())
|
||||
if (fileProperties) fileProperties.updated = updateTime;
|
||||
setTimeout(async () => {
|
||||
if (fileProperties && fileProperties.updated > updateTime ) {
|
||||
return;
|
||||
}
|
||||
this.app.chatContext.addDocument(document.uri.toString(), document.getText());
|
||||
}, 5000);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to add document to RAG:', error);
|
||||
}
|
||||
this.app.chatContext.udpateFileIndexing(document.uri.fsPath, document.getText());
|
||||
});
|
||||
context.subscriptions.push(saveListener);
|
||||
|
||||
// Add file delete listener for RAG
|
||||
const deleteListener = vscode.workspace.onDidDeleteFiles(async (event) => {
|
||||
if (!this.app.configuration.rag_enabled || this.app.configuration.rag_max_files <= 0) return;
|
||||
for (const file of event.files) {
|
||||
try {
|
||||
await this.app.chatContext.removeDocument(file.toString());
|
||||
} catch (error) {
|
||||
console.error('Failed to remove document from RAG:', error);
|
||||
}
|
||||
}
|
||||
await this.app.chatContext.removeFileIndexing(event);
|
||||
});
|
||||
context.subscriptions.push(deleteListener);
|
||||
}
|
||||
|
|
@ -461,6 +438,10 @@ export class Architect {
|
|||
context.subscriptions.push(postMessageCommand);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private async installUpgradeLlamaCpp(isFirstStart: any) {
|
||||
if (!this.app.configuration.ask_install_llamacpp) return;
|
||||
let result = await Utils.executeTerminalCommand("llama-server --version");
|
||||
|
|
|
|||
|
|
@ -275,9 +275,9 @@ export class ChatContext {
|
|||
}
|
||||
}
|
||||
|
||||
async removeDocument(uri: string) {
|
||||
this.removeChunkEntries(uri);
|
||||
this.filesProperties.delete(uri);
|
||||
async removeDocument(filePath: string) {
|
||||
this.removeChunkEntries(filePath);
|
||||
this.filesProperties.delete(filePath);
|
||||
}
|
||||
|
||||
async indexWorkspaceFiles() {
|
||||
|
|
@ -408,4 +408,37 @@ export class ChatContext {
|
|||
const regex = /@([a-zA-Z0-9_.-]+)(?=[,.?!\s]|$)/g;
|
||||
return [...text.matchAll(regex)].map(match => match[1]);
|
||||
}
|
||||
|
||||
public udpateFileIndexing(filePath: string, fileContent: string) {
|
||||
try {
|
||||
if (this.app.configuration.rag_enabled && this.app.configuration.rag_max_files > 0) {
|
||||
if (!this.isImageOrVideoFile(filePath)) {
|
||||
// Update after a delay and only if the file is not changed in the meantime to avoid too often updates
|
||||
let updateTime = Date.now();
|
||||
let fileProperties = this.getFileProperties(filePath);
|
||||
if (fileProperties) fileProperties.updated = updateTime;
|
||||
setTimeout(async () => {
|
||||
if (fileProperties && fileProperties.updated > updateTime) {
|
||||
return;
|
||||
}
|
||||
this.addDocument(filePath, fileContent);
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to add/update document to RAG:', error);
|
||||
}
|
||||
}
|
||||
|
||||
public async removeFileIndexing(event: vscode.FileDeleteEvent) {
|
||||
if (this.app.configuration.rag_enabled && this.app.configuration.rag_max_files > 0) {
|
||||
for (const file of event.files) {
|
||||
try {
|
||||
await this.removeDocument(file.fsPath);
|
||||
} catch (error) {
|
||||
console.error('Failed to remove document from RAG:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -145,6 +145,8 @@ export const UI_TEXT_KEYS = {
|
|||
showSelectedModels: "Show selected models",
|
||||
showSelectedModelsDescription: "Displays a list of currently selected models",
|
||||
useAsLocalAIRunner: "Use as local AI runner",
|
||||
editMultipleFilesWithAi: "Edit multiple files with AI",
|
||||
editMultipleFilesWithAiDescription: "Asks for glob pattern and prompt and edits with AI the files, which match the glob pattern with the provided prompt.",
|
||||
localAIRunnerDescription: "Download models automatically from Huggingface and chat with them (as LM Studio, Ollama, etc.)",
|
||||
editSettings: "Edit Settings...",
|
||||
apiKeys: "API keys...",
|
||||
|
|
@ -242,6 +244,9 @@ export const UI_TEXT_KEYS = {
|
|||
deleteToolsModel: "Delete tools model...",
|
||||
exportToolsModel: "Export tools model...",
|
||||
importToolsModel: "Import tools model...",
|
||||
|
||||
// Other
|
||||
fileUpdated: "The file is updated"
|
||||
} as const;
|
||||
|
||||
export const PERSISTENCE_KEYS = {
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ export class FileEditor {
|
|||
|
||||
const prompt = await vscode.window.showInputBox({
|
||||
placeHolder: 'Enter instructions for editing files...',
|
||||
prompt: 'How would you like to modify the files?',
|
||||
prompt: 'How would you like to modify the files? (the instructions will be applied to each file separately)',
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
if (!prompt) return;
|
||||
|
|
@ -43,6 +43,12 @@ export class FileEditor {
|
|||
ignoreFocusOut: true
|
||||
});
|
||||
if (!glob) return;
|
||||
let shouldContinue = Utils.showYesNoDialog(
|
||||
"You requested an edit of multiple files with AI. " +
|
||||
"\n\nGlob pattern (what files to edit): " + glob +
|
||||
"\nPrompt: " + prompt +
|
||||
"\n\nDo you want to continue?")
|
||||
if (!shouldContinue) return;
|
||||
|
||||
const files = await vscode.workspace.findFiles(glob);
|
||||
if (!files || files.length === 0) {
|
||||
|
|
@ -64,6 +70,7 @@ export class FileEditor {
|
|||
vscode.window.showInformationMessage(`File editing cancelled after ${processed} of ${total} files.`);
|
||||
break;
|
||||
}
|
||||
if (this.app.chatContext.isImageOrVideoFile(file.fsPath)) continue
|
||||
progress.report({ message: `Editing ${file.fsPath}`, increment: (1 / total) * 100 });
|
||||
|
||||
try {
|
||||
|
|
@ -80,8 +87,12 @@ export class FileEditor {
|
|||
|
||||
if (completion?.choices?.[0]?.message?.content) {
|
||||
var edited = completion.choices[0].message.content.trim();
|
||||
edited = this.removeFirstAndLastLinesIfBackticks(edited);
|
||||
edited = Utils.removeFirstAndLastLinesIfBackticks(edited);
|
||||
await vscode.workspace.fs.writeFile(file, Buffer.from(edited, 'utf8'));
|
||||
if (this.app.configuration.rag_enabled) {
|
||||
const document = await vscode.workspace.openTextDocument(file);
|
||||
this.app.chatContext.udpateFileIndexing(document.uri.fsPath, document.getText())
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`Failed to edit ${file.fsPath}:`, err);
|
||||
|
|
@ -94,20 +105,4 @@ export class FileEditor {
|
|||
}
|
||||
);
|
||||
}
|
||||
|
||||
private removeFirstAndLastLinesIfBackticks(input: string): string {
|
||||
const lines = input.split('\n'); // Split the string into lines
|
||||
|
||||
// Remove the first line if it starts with ```
|
||||
if (lines[0]?.trim().startsWith('```')) {
|
||||
lines.shift(); // Remove the first line
|
||||
}
|
||||
|
||||
// Remove the last line if it starts with ```
|
||||
if (lines[lines.length - 1]?.trim().startsWith('```')) {
|
||||
lines.pop(); // Remove the last line
|
||||
}
|
||||
|
||||
return lines.join('\n'); // Join the remaining lines back into a string
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { Utils } from "./utils"
|
|||
import { Chat } from "./types"
|
||||
import { Plugin } from './plugin';
|
||||
import * as fs from 'fs';
|
||||
import { UI_TEXT_KEYS } from "./constants";
|
||||
|
||||
|
||||
interface Step {
|
||||
|
|
@ -263,7 +264,13 @@ export class LlamaAgent {
|
|||
const toolFunc = this.app.tools.toolsFunc.get(oneToolCall.function.name);
|
||||
if (toolFunc) {
|
||||
commandOutput = await toolFunc(oneToolCall.function.arguments);
|
||||
if (oneToolCall.function.name == "edit_file" && commandOutput != Utils.MSG_NO_UESR_PERMISSION) changedFiles.add(commandDescription);
|
||||
if (oneToolCall.function.name == "edit_file" && commandOutput != Utils.MSG_NO_UESR_PERMISSION) {
|
||||
changedFiles.add(commandDescription);
|
||||
if (commandOutput != UI_TEXT_KEYS.fileUpdated){
|
||||
this.logText += commandOutput + "\n\n"
|
||||
this.app.llamaWebviewProvider.logInUi(this.logText);
|
||||
}
|
||||
}
|
||||
if (oneToolCall.function.name == "delete_file" && commandOutput != Utils.MSG_NO_UESR_PERMISSION) deletedFiles.add(commandDescription);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,6 +79,10 @@ export class Menu {
|
|||
{
|
||||
label: this.app.configuration.getUiText(UI_TEXT_KEYS.useAsLocalAIRunner)??"",
|
||||
description: this.app.configuration.getUiText(UI_TEXT_KEYS.localAIRunnerDescription)
|
||||
},
|
||||
{
|
||||
label: this.app.configuration.getUiText(UI_TEXT_KEYS.editMultipleFilesWithAi)??"",
|
||||
description: this.app.configuration.getUiText(UI_TEXT_KEYS.editMultipleFilesWithAiDescription)
|
||||
}
|
||||
]
|
||||
return menuItems;
|
||||
|
|
@ -230,6 +234,9 @@ export class Menu {
|
|||
vscode.commands.executeCommand('extension.showLlamaWebview');
|
||||
this.app.llamaWebviewProvider.setView(UiView.AiRunner);
|
||||
break;
|
||||
case this.app.configuration.getUiText(UI_TEXT_KEYS.editMultipleFilesWithAi):
|
||||
vscode.commands.executeCommand('extension.editAllSearchFiles');
|
||||
break;
|
||||
default:
|
||||
isHandled = false;
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ export class TextEditor {
|
|||
vscode.window.showInformationMessage('No suggestions available');
|
||||
return;
|
||||
}
|
||||
this.currentSuggestion = this.removeFirstAndLastLinesIfBackticks(data.choices[0].message.content.trim());
|
||||
this.currentSuggestion = Utils.removeFirstAndLastLinesIfBackticks(data.choices[0].message.content.trim());
|
||||
this.currentSuggestion = Utils.addLeadingSpaces(this.currentSuggestion, this.removedSpaces)
|
||||
// Show the suggestion in a diff view
|
||||
await this.showDiffView(editor, this.currentSuggestion);
|
||||
|
|
@ -111,21 +111,7 @@ export class TextEditor {
|
|||
}
|
||||
}
|
||||
|
||||
private removeFirstAndLastLinesIfBackticks(input: string): string {
|
||||
const lines = input.split('\n'); // Split the string into lines
|
||||
|
||||
// Remove the first line if it starts with ```
|
||||
if (lines[0]?.trim().startsWith('```')) {
|
||||
lines.shift(); // Remove the first line
|
||||
}
|
||||
|
||||
// Remove the last line if it starts with ```
|
||||
if (lines[lines.length - 1]?.trim().startsWith('```')) {
|
||||
lines.pop(); // Remove the last line
|
||||
}
|
||||
|
||||
return lines.join('\n'); // Join the remaining lines back into a string
|
||||
}
|
||||
|
||||
|
||||
private async showDiffView(editor: vscode.TextEditor, suggestion: string) {
|
||||
// Get context before and after the selection
|
||||
|
|
|
|||
19
src/tools.ts
19
src/tools.ts
|
|
@ -4,6 +4,7 @@ import {Utils} from "./utils";
|
|||
import path from "path";
|
||||
import fs from 'fs';
|
||||
import { Plugin } from './plugin';
|
||||
import { UI_TEXT_KEYS } from "./constants";
|
||||
|
||||
type ToolsMap = Map<string, (...args: any[]) => any>;
|
||||
|
||||
|
|
@ -215,6 +216,7 @@ export class Tools {
|
|||
return `File not found at ${filePath}`;
|
||||
}
|
||||
fs.unlinkSync(absolutePath);
|
||||
this.app.chatContext.removeDocument(absolutePath)
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
return `Failed to delete file at ${filePath}: ${error.message}`;
|
||||
|
|
@ -252,8 +254,11 @@ export class Tools {
|
|||
let changes = params.input;
|
||||
|
||||
if (params.input == undefined) return "The input is not provided."
|
||||
|
||||
let filePath = this.getFilePath(params.input);
|
||||
if (!filePath) return "The file is not provided.";
|
||||
|
||||
|
||||
try {
|
||||
if (!this.app.configuration.tool_permit_file_changes){
|
||||
let [yesApply, yesDontAsk] = await Utils.showYesYesdontaskNoDialog("Do you permit file " + filePath + " to be changed?")
|
||||
|
|
@ -264,6 +269,9 @@ export class Tools {
|
|||
if (!yesApply) return Utils.MSG_NO_UESR_PERMISSION;
|
||||
}
|
||||
let resultEdit = await Utils.applyEdits(changes)
|
||||
if (resultEdit == UI_TEXT_KEYS.fileUpdated && this.app.configuration.rag_enabled && fs.existsSync(filePath)) {
|
||||
this.app.chatContext.udpateFileIndexing(filePath, fs.readFileSync(filePath, 'utf-8'))
|
||||
}
|
||||
return resultEdit;
|
||||
} catch (error) {
|
||||
console.error('Error changes since last commit:', error);
|
||||
|
|
@ -274,7 +282,7 @@ export class Tools {
|
|||
public editFileDesc = async (args: string) => {
|
||||
let params = JSON.parse(args);
|
||||
let diffText = params.input;
|
||||
if (!diffText) return "EditFile Desc - parameter input not found."
|
||||
if (!diffText) return "Parameter input not found."
|
||||
|
||||
let filePath = this.getFilePath(diffText);
|
||||
|
||||
|
|
@ -751,10 +759,17 @@ export class Tools {
|
|||
let blockParts = Utils.extractConflictParts("```diff" + blocks.slice(1)[0]);
|
||||
filePath = blockParts[0].trim();
|
||||
} else {
|
||||
if (diffText.length > 0) filePath = Utils.extractConflictParts("```diff\n" + diffText)[0].trim()
|
||||
if (diffText.length > 0){
|
||||
if (diffText.startsWith("```\n")) diffText = diffText.slice(5)
|
||||
filePath = Utils.extractConflictParts("```diff\n" + diffText)[0].trim()
|
||||
}
|
||||
else return "";
|
||||
}
|
||||
|
||||
// Workaround for ClaudCode project file format - get only the relative path to the file
|
||||
if (filePath.includes(" ## ")) filePath = filePath.split(" ## ")[1];
|
||||
if (filePath.startsWith("## ")) filePath = filePath.slice(3);
|
||||
|
||||
let absolutePath = filePath;
|
||||
if (!path.isAbsolute(filePath)) {
|
||||
if (!vscode.workspace.workspaceFolders || vscode.workspace.workspaceFolders.length === 0) {
|
||||
|
|
|
|||
|
|
@ -195,4 +195,6 @@ export const translations: string[][] = [
|
|||
["Add tools model from OpenAI compatible provider...", "Добавяне на модел за инструменти от съвместим с OpenAI доставчик...", "Werkzeugmodell von einem mit OpenAI kompatiblen Anbieter hinzufügen...", "Добавить модель инструментов от совместимого с OpenAI поставщика...", "Añadir modelo de herramientas de un proveedor compatible con OpenAI...", "添加来自与 OpenAI 兼容提供程序的工具模型...", "Ajouter un modèle d'outils d'un fournisseur compatible OpenAI..."],
|
||||
["Edit agent...", "Редактиране на агент...", "Agent bearbeiten...", "Редактировать агента...", "Editar agente...", "编辑代理...", "Modifier l'agent..."],
|
||||
["Copy agent...", "Копиране на агент...", "Agent kopieren...", "Копировать агента...", "Copiar agente...", "复制代理...", "Copier l'agent..."],
|
||||
["Edit multiple files with AI", "Редактиране на множество файлове с изкуствен интелект", "Mehrere Dateien mit KI bearbeiten", "Редактировать несколько файлов с ИИ", "Editar múltiples archivos con IA", "使用人工智能编辑多个文件", "Modifier plusieurs fichiers avec IA"],
|
||||
["Asks for glob pattern and prompt and edits with AI the files, which match the glob pattern with the provided prompt.", "Пита за шаблон за обхват и инструкция, след което редактира с изкуствен интелект файловете, които отговарят на шаблона, със зададената инструкция.", "Fragt nach einem Glob-Muster und einer Eingabeaufforderung und bearbeitet mit KI die Dateien, die dem Glob-Muster mit der bereitgestellten Eingabeaufforderung entsprechen.", "Запрашивает шаблон glob и подсказку, а затем редактирует с помощью ИИ файлы, соответствующие шаблону, с предоставленной подсказкой.", "Pide un patrón glob y un mensaje, y edita con IA los archivos que coinciden con el patrón glob con el mensaje proporcionado.", "请求输入通配符模式和提示词,然后使用人工智能编辑符合该通配符模式的文件,并根据提供的提示词进行修改。", "Demande un motif glob et une invite, puis modifie avec IA les fichiers correspondant au motif glob avec l'invite fournie."],
|
||||
];
|
||||
|
|
|
|||
19
src/utils.ts
19
src/utils.ts
|
|
@ -8,6 +8,7 @@ import * as https from 'https';
|
|||
import * as http from 'http';
|
||||
import { URL } from 'url';
|
||||
import { Application } from "./application";
|
||||
import { UI_TEXT_KEYS } from "./constants";
|
||||
|
||||
|
||||
interface BM25Stats {
|
||||
|
|
@ -558,7 +559,7 @@ export class Utils {
|
|||
|
||||
static applyEdits = async (diffText: string): Promise<string> => {
|
||||
// Extract edit blocks from the diff-fenced format
|
||||
let ret = "The file is updated";
|
||||
let ret = UI_TEXT_KEYS.fileUpdated as string;
|
||||
let editBlocks: string[][] = [];
|
||||
if (!diffText) return "Edit file: The input parameter is missing!";
|
||||
const blocks = diffText.split("```diff")
|
||||
|
|
@ -888,4 +889,20 @@ export class Utils {
|
|||
vscode.window.showErrorMessage(noMsg);
|
||||
}
|
||||
}
|
||||
|
||||
static removeFirstAndLastLinesIfBackticks = (input: string): string => {
|
||||
const lines = input.split('\n'); // Split the string into lines
|
||||
|
||||
// Remove the first line if it starts with ```
|
||||
if (lines[0]?.trim().startsWith('```')) {
|
||||
lines.shift(); // Remove the first line
|
||||
}
|
||||
|
||||
// Remove the last line if it starts with ```
|
||||
if (lines[lines.length - 1]?.trim().startsWith('```')) {
|
||||
lines.pop(); // Remove the last line
|
||||
}
|
||||
|
||||
return lines.join('\n'); // Join the remaining lines back into a string
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -408,7 +408,7 @@ const AgentView: React.FC<AgentViewProps> = ({
|
|||
ref={textareaRef}
|
||||
value={inputText}
|
||||
onChange={(e) => setInputText(e.target.value)}
|
||||
placeholder="Ask me anything about your code... Press @ to select a file / for a command."
|
||||
placeholder="Ask me anything about your code... Press @ to select a file, / for a command."
|
||||
className="modern-textarea"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue