From 790f24d0bd85860b34785c812b450f496639cf68 Mon Sep 17 00:00:00 2001 From: Olasunkanmi Oyinlola Date: Thu, 17 Jul 2025 21:19:52 +0800 Subject: [PATCH 1/4] feat(ui): Enhance webview UI with new font and improved typography - Updates the font to Inter for a cleaner and more modern look. - Implements improved typography for better readability and visual appeal. - Ensures workspace is published only once after the webview is ready. - Adds webview-ready event to ensure workspace is only published one time, once the webview is ready. --- src/commands/handler.ts | 100 +++++++++++++++++------- src/webview-providers/base.ts | 109 +++++++++------------------ webviewUi/src/components/webview.tsx | 8 ++ webviewUi/src/index.css | 85 ++++++++++----------- 4 files changed, 156 insertions(+), 146 deletions(-) diff --git a/src/commands/handler.ts b/src/commands/handler.ts index 570bd86..d815266 100644 --- a/src/commands/handler.ts +++ b/src/commands/handler.ts @@ -3,7 +3,11 @@ import Anthropic from "@anthropic-ai/sdk"; import { GenerativeModel, GoogleGenerativeAI } from "@google/generative-ai"; import Groq from "groq-sdk"; import * as vscode from "vscode"; -import { APP_CONFIG, COMMON, generativeAiModels } from "../application/constant"; +import { + APP_CONFIG, + COMMON, + generativeAiModels, +} from "../application/constant"; import { AnthropicWebViewProvider } from "../webview-providers/anthropic"; import { DeepseekWebViewProvider } from "../webview-providers/deepseek"; import { GeminiWebViewProvider } from "../webview-providers/gemini"; @@ -41,7 +45,7 @@ export abstract class CodeCommandHandler implements ICodeCommandHandler { constructor( private readonly action: string, _context: vscode.ExtensionContext, - errorMessage?: string + errorMessage?: string, ) { this.context = _context; this.error = errorMessage; @@ -84,7 +88,10 @@ export abstract class CodeCommandHandler implements ICodeCommandHandler { } { const { CODEBUDDY_ACTIONS } = require("../application/constant"); - const commandDescriptions: Record = { + const commandDescriptions: Record< + string, + { action: string; description: string } + > = { [CODEBUDDY_ACTIONS.comment]: { action: "Adding Code Comments", description: @@ -97,7 +104,8 @@ export abstract class CodeCommandHandler implements ICodeCommandHandler { }, [CODEBUDDY_ACTIONS.refactor]: { action: "Refactoring Code", - description: "CodeBuddy is applying SOLID principles and design patterns to improve code maintainability...", + description: + "CodeBuddy is applying SOLID principles and design patterns to improve code maintainability...", }, [CODEBUDDY_ACTIONS.optimize]: { action: "Optimizing Performance", @@ -106,7 +114,8 @@ export abstract class CodeCommandHandler implements ICodeCommandHandler { }, [CODEBUDDY_ACTIONS.fix]: { action: "Fixing Code Issues", - description: "CodeBuddy is diagnosing the error and implementing defensive programming solutions...", + description: + "CodeBuddy is diagnosing the error and implementing defensive programming solutions...", }, [CODEBUDDY_ACTIONS.explain]: { action: "Explaining Code Logic", @@ -115,19 +124,23 @@ export abstract class CodeCommandHandler implements ICodeCommandHandler { }, [CODEBUDDY_ACTIONS.commitMessage]: { action: "Generating Commit Message", - description: "CodeBuddy is analyzing your staged changes and crafting a professional commit message...", + description: + "CodeBuddy is analyzing your staged changes and crafting a professional commit message...", }, [CODEBUDDY_ACTIONS.interviewMe]: { action: "Preparing Interview Questions", - description: "CodeBuddy is creating comprehensive technical interview questions based on your code...", + description: + "CodeBuddy is creating comprehensive technical interview questions based on your code...", }, [CODEBUDDY_ACTIONS.generateUnitTest]: { action: "Generating Unit Tests", - description: "CodeBuddy is creating comprehensive test suites with edge cases and mocking strategies...", + description: + "CodeBuddy is creating comprehensive test suites with edge cases and mocking strategies...", }, [CODEBUDDY_ACTIONS.generateDiagram]: { action: "Creating System Diagram", - description: "CodeBuddy is visualizing your code architecture with professional Mermaid diagrams...", + description: + "CodeBuddy is visualizing your code architecture with professional Mermaid diagrams...", }, [CODEBUDDY_ACTIONS.reviewPR]: { action: "Reviewing Pull Request", @@ -136,14 +149,16 @@ export abstract class CodeCommandHandler implements ICodeCommandHandler { }, [CODEBUDDY_ACTIONS.inlineChat]: { action: "Processing Inline Request", - description: "CodeBuddy is analyzing your inline query and generating a contextual response...", + description: + "CodeBuddy is analyzing your inline query and generating a contextual response...", }, }; return ( commandDescriptions[action] || { action: "Processing Request", - description: "CodeBuddy is analyzing your code and generating a response...", + description: + "CodeBuddy is analyzing your code and generating a response...", } ); } @@ -186,13 +201,15 @@ export abstract class CodeCommandHandler implements ICodeCommandHandler { } } - protected createModel(): { generativeAi: string; model: any; modelName: string } | undefined { + protected createModel(): + | { generativeAi: string; model: any; modelName: string } + | undefined { try { let model; let modelName = ""; if (!this.generativeAi) { vscodeErrorMessage( - "Configuration not found. Go to settings, search for Your coding buddy. Fill up the model and model name" + "Configuration not found. Go to settings, search for Your coding buddy. Fill up the model and model name", ); } if (this.generativeAi === generativeAiModels.GROQ) { @@ -200,7 +217,7 @@ export abstract class CodeCommandHandler implements ICodeCommandHandler { modelName = this.groqModel; if (!apiKey || !modelName) { vscodeErrorMessage( - "Configuration not found. Go to settings, search for Your coding buddy. Fill up the model and model name" + "Configuration not found. Go to settings, search for Your coding buddy. Fill up the model and model name", ); } model = this.createGroqModel(apiKey); @@ -226,7 +243,9 @@ export abstract class CodeCommandHandler implements ICodeCommandHandler { return { generativeAi: this.generativeAi, model, modelName }; } catch (error) { console.error("Error creating model:", error); - vscode.window.showErrorMessage("An error occurred while creating the model. Please try again."); + vscode.window.showErrorMessage( + "An error occurred while creating the model. Please try again.", + ); } } @@ -259,7 +278,9 @@ export abstract class CodeCommandHandler implements ICodeCommandHandler { return new Groq({ apiKey }); } - protected async generateModelResponse(text: string): Promise { + protected async generateModelResponse( + text: string, + ): Promise { try { if (text?.length > 0) { this.orchestrator.publish("onUserPrompt", text); @@ -299,7 +320,7 @@ export abstract class CodeCommandHandler implements ICodeCommandHandler { if (!response) { throw new Error( - "Could not generate response. Check your settings, ensure the API keys and Model Name is added properly." + "Could not generate response. Check your settings, ensure the API keys and Model Name is added properly.", ); } if (this.action.includes("chart")) { @@ -310,7 +331,9 @@ export abstract class CodeCommandHandler implements ICodeCommandHandler { return response; } catch (error) { this.logger.error("Error generating response:", error); - vscode.window.showErrorMessage("An error occurred while generating the response. Please try again."); + vscode.window.showErrorMessage( + "An error occurred while generating the response. Please try again.", + ); } } @@ -321,7 +344,10 @@ export abstract class CodeCommandHandler implements ICodeCommandHandler { return inputString; } - async generateGeminiResponse(model: any, text: string): Promise { + async generateGeminiResponse( + model: any, + text: string, + ): Promise { const result = await model.generateContent(text); return result ? await result.response.text() : undefined; } @@ -329,7 +355,7 @@ export abstract class CodeCommandHandler implements ICodeCommandHandler { private async anthropicResponse( model: Anthropic, generativeAiModel: string, - userPrompt: string + userPrompt: string, ): Promise { try { const response = await model.messages.create({ @@ -353,9 +379,15 @@ export abstract class CodeCommandHandler implements ICodeCommandHandler { } } - private async groqResponse(model: Groq, prompt: string, generativeAiModel: string): Promise { + private async groqResponse( + model: Groq, + prompt: string, + generativeAiModel: string, + ): Promise { try { - const chatHistory = Memory.has(COMMON.ANTHROPIC_CHAT_HISTORY) ? Memory.get(COMMON.GROQ_CHAT_HISTORY) : []; + const chatHistory = Memory.has(COMMON.ANTHROPIC_CHAT_HISTORY) + ? Memory.get(COMMON.GROQ_CHAT_HISTORY) + : []; const params = { messages: [ ...chatHistory, @@ -367,7 +399,8 @@ export abstract class CodeCommandHandler implements ICodeCommandHandler { model: generativeAiModel, }; - const completion: Groq.Chat.ChatCompletion = await model.chat.completions.create(params); + const completion: Groq.Chat.ChatCompletion = + await model.chat.completions.create(params); return completion.choices[0]?.message?.content ?? undefined; } catch (error) { this.logger.error("Error generating response:", error); @@ -378,7 +411,9 @@ export abstract class CodeCommandHandler implements ICodeCommandHandler { abstract createPrompt(text?: string): any; - async generateResponse(message?: string): Promise { + async generateResponse( + message?: string, + ): Promise { this.logger.info(this.action); let prompt; const selectedCode = this.getSelectedWindowArea(); @@ -390,7 +425,9 @@ export abstract class CodeCommandHandler implements ICodeCommandHandler { if (message && selectedCode) { prompt = await this.createPrompt(`${message} \n ${selectedCode}`); } else { - message ? (prompt = await this.createPrompt(message)) : (prompt = await this.createPrompt(selectedCode)); + message + ? (prompt = await this.createPrompt(message)) + : (prompt = await this.createPrompt(selectedCode)); } if (!prompt) { @@ -473,7 +510,9 @@ export abstract class CodeCommandHandler implements ICodeCommandHandler { placeHolder: "Enter instructions for CodeBuddy", ignoreFocusOut: true, validateInput: (text) => { - return text === "" ? "Enter instructions for CodeBuddy or press Escape to close chat box" : null; + return text === "" + ? "Enter instructions for CodeBuddy or press Escape to close chat box" + : null; }, }); return userPrompt; @@ -488,7 +527,9 @@ export abstract class CodeCommandHandler implements ICodeCommandHandler { await this.sendCommandFeedback(action || this.action); let prompt: string | undefined; - const response = (await this.generateResponse(prompt ?? message)) as string; + const response = (await this.generateResponse( + prompt ?? message, + )) as string; if (!response) { vscode.window.showErrorMessage("model not reponding, try again later"); return; @@ -529,7 +570,10 @@ export abstract class CodeCommandHandler implements ICodeCommandHandler { break; } } catch (error) { - this.logger.error("Error while passing model response to the webview", error); + this.logger.error( + "Error while passing model response to the webview", + error, + ); } } } diff --git a/src/webview-providers/base.ts b/src/webview-providers/base.ts index a32def8..105e8b6 100644 --- a/src/webview-providers/base.ts +++ b/src/webview-providers/base.ts @@ -1,9 +1,6 @@ import * as vscode from "vscode"; import { Orchestrator } from "../agents/orchestrator"; -import { - FolderEntry, - IContextInfo, -} from "../application/interfaces/workspace.interface"; +import { FolderEntry, IContextInfo } from "../application/interfaces/workspace.interface"; import { IEventPayload } from "../emitter/interface"; import { Logger } from "../infrastructure/logger/logger"; import { AgentService } from "../services/agent-state"; @@ -29,12 +26,13 @@ export abstract class BaseWebViewProvider implements vscode.Disposable { private readonly fileManager: FileManager; private readonly agentService: AgentService; protected readonly chatHistoryManager: ChatHistoryManager; + private workspacePublished = false; // Track if workspace has been published constructor( private readonly _extensionUri: vscode.Uri, protected readonly apiKey: string, protected readonly generativeAiModel: string, - context: vscode.ExtensionContext, + context: vscode.ExtensionContext ) { this.fileManager = FileManager.initialize(context, "files"); this.fileService = FileService.getInstance(); @@ -60,23 +58,13 @@ export abstract class BaseWebViewProvider implements vscode.Disposable { this.orchestrator.onThinking(this.handleModelResponseEvent.bind(this)), this.orchestrator.onUpdate(this.handleModelResponseEvent.bind(this)), this.orchestrator.onError(this.handleModelResponseEvent.bind(this)), - this.orchestrator.onSecretChange( - this.handleModelResponseEvent.bind(this), - ), - this.orchestrator.onActiveworkspaceUpdate( - this.handleGenericEvents.bind(this), - ), + this.orchestrator.onSecretChange(this.handleModelResponseEvent.bind(this)), + this.orchestrator.onActiveworkspaceUpdate(this.handleGenericEvents.bind(this)), this.orchestrator.onFileUpload(this.handleModelResponseEvent.bind(this)), - this.orchestrator.onStrategizing( - this.handleModelResponseEvent.bind(this), - ), - this.orchestrator.onConfigurationChange( - this.handleGenericEvents.bind(this), - ), + this.orchestrator.onStrategizing(this.handleModelResponseEvent.bind(this)), + this.orchestrator.onConfigurationChange(this.handleGenericEvents.bind(this)), this.orchestrator.onUserPrompt(this.handleUserPrompt.bind(this)), - this.orchestrator.onGetUserPreferences( - this.handleUserPreferences.bind(this), - ), + this.orchestrator.onGetUserPreferences(this.handleUserPreferences.bind(this)) ); } @@ -99,9 +87,7 @@ export abstract class BaseWebViewProvider implements vscode.Disposable { webviewView.webview.options = webviewOptions; if (!this.apiKey) { - vscode.window.showErrorMessage( - "API key not configured. Check your settings.", - ); + vscode.window.showErrorMessage("API key not configured. Check your settings."); return; } @@ -109,13 +95,12 @@ export abstract class BaseWebViewProvider implements vscode.Disposable { this.setupMessageHandler(this.currentWebView); // Get the current workspace files from DB. await this.getFiles(); + // Publish workspace immediately on webview load + await this.publishWorkSpaceOnce(); } private async setWebviewHtml(view: vscode.WebviewView): Promise { - view.webview.html = getWebviewContent( - this.currentWebView?.webview!, - this._extensionUri, - ); + view.webview.html = getWebviewContent(this.currentWebView?.webview!, this._extensionUri); } private async getFiles() { @@ -152,10 +137,8 @@ export abstract class BaseWebViewProvider implements vscode.Disposable { private async publishWorkSpace(): Promise { try { - const filesAndDirs: IContextInfo = - await this.workspaceService.getContextInfo(true); - const workspaceFiles: Map | undefined = - filesAndDirs.workspaceFiles; + const filesAndDirs: IContextInfo = await this.workspaceService.getContextInfo(true); + const workspaceFiles: Map | undefined = filesAndDirs.workspaceFiles; if (!workspaceFiles) { this.logger.warn("There no files within the workspace"); return; @@ -170,6 +153,13 @@ export abstract class BaseWebViewProvider implements vscode.Disposable { } } + private async publishWorkSpaceOnce(): Promise { + if (!this.workspacePublished) { + await this.publishWorkSpace(); + this.workspacePublished = true; + } + } + private UserMessageCounter = 0; private async setupMessageHandler(_view: vscode.WebviewView): Promise { @@ -184,33 +174,24 @@ export abstract class BaseWebViewProvider implements vscode.Disposable { // Check if we should prune history for performance if (this.UserMessageCounter % 10 === 0) { const stats = await this.getChatHistoryStats("agentId"); - if ( - stats.totalMessages > 100 || - stats.estimatedTokens > 16000 - ) { + if (stats.totalMessages > 100 || stats.estimatedTokens > 16000) { this.logger.info( - `High chat history usage detected: ${stats.totalMessages} messages, ${stats.estimatedTokens} tokens`, + `High chat history usage detected: ${stats.totalMessages} messages, ${stats.estimatedTokens} tokens` ); // Optionally trigger manual pruning here // await this.pruneHistoryManually("agentId", { maxMessages: 50, maxTokens: 8000 }); } } - response = await this.generateResponse( - message.message, - message.metaData, - ); - if (this.UserMessageCounter === 1) { - await this.publishWorkSpace(); - } + response = await this.generateResponse(message.message, message.metaData); if (response) { await this.sendResponse(formatText(response), "bot"); } break; } - // case "webview-ready": - // await this.publishWorkSpace(); - // break; + case "webview-ready": + await this.publishWorkSpaceOnce(); + break; case "upload-file": await this.fileManager.uploadFileHandler(); break; @@ -232,7 +213,7 @@ export abstract class BaseWebViewProvider implements vscode.Disposable { default: throw new Error("Unknown command"); } - }), + }) ); } catch (error) { this.logger.error("Message handler failed", error); @@ -248,33 +229,21 @@ export abstract class BaseWebViewProvider implements vscode.Disposable { } public handleModelResponseEvent(event: IEventPayload) { - this.sendResponse( - formatText(event.message), - event.message === "folders" ? "bootstrap" : "bot", - ); + this.sendResponse(formatText(event.message), event.message === "folders" ? "bootstrap" : "bot"); } - abstract generateResponse( - message?: string, - metaData?: Record, - ): Promise; + abstract generateResponse(message?: string, metaData?: Record): Promise; - abstract sendResponse( - response: string, - currentChat?: string, - ): Promise; + abstract sendResponse(response: string, currentChat?: string): Promise; public dispose(): void { - this.logger.debug( - `Disposing BaseWebViewProvider with ${this.disposables.length} disposables`, - ); + this.logger.debug(`Disposing BaseWebViewProvider with ${this.disposables.length} disposables`); this.disposables.forEach((d) => d.dispose()); this.disposables.length = 0; // Clear the array } async getContext(files: string[]) { try { - const filesContent: Map | undefined = - await this.fileService.getFilesContent(files); + const filesContent: Map | undefined = await this.fileService.getFilesContent(files); if (filesContent && filesContent.size > 0) { return Array.from(filesContent.values()).join("\n"); } @@ -294,15 +263,9 @@ export abstract class BaseWebViewProvider implements vscode.Disposable { maxTokens: number; maxAgeHours: number; preserveSystemMessages: boolean; - }>, + }> ): Promise { - return this.chatHistoryManager.formatChatHistory( - role, - message, - model, - key, - pruneConfig, - ); + return this.chatHistoryManager.formatChatHistory(role, message, model, key, pruneConfig); } // Get chat history stats for monitoring @@ -322,7 +285,7 @@ export abstract class BaseWebViewProvider implements vscode.Disposable { maxMessages?: number; maxTokens?: number; maxAgeHours?: number; - }, + } ): Promise { await this.chatHistoryManager.pruneHistoryForKey(key, config); } diff --git a/webviewUi/src/components/webview.tsx b/webviewUi/src/components/webview.tsx index 6f90dae..7acd30a 100644 --- a/webviewUi/src/components/webview.tsx +++ b/webviewUi/src/components/webview.tsx @@ -64,6 +64,14 @@ export const WebviewUI = () => { const [darkMode, setDarkMode] = useState(false); const nameInputRef = useRef(null); + // Signal webview is ready on mount + useEffect(() => { + vsCode.postMessage({ + command: "webview-ready", + message: "Webview is ready", + }); + }, []); + useEffect(() => { const messageHandler = (event: any) => { const message = event.data; diff --git a/webviewUi/src/index.css b/webviewUi/src/index.css index 2c0d55d..a501424 100644 --- a/webviewUi/src/index.css +++ b/webviewUi/src/index.css @@ -1,5 +1,7 @@ +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap'); + :root { - font-family: "JetBrains Mono", SF Mono, "Geist Mono", "Fira Code", "Fira Mono", "Menlo", "Consolas", "DejaVu Sans Mono", monospace; + font-family: "Inter", -apple-system, BlinkMacSystemFont, "SF Pro Display", "Segoe UI", "Roboto", "Helvetica Neue", Arial, sans-serif; line-height: 1.5; font-weight: 400; color-scheme: light dark; @@ -11,6 +13,34 @@ background-color: #16161e; } +/* Improved typography */ +body { + font-feature-settings: "cv02", "cv03", "cv04", "cv11"; + font-variant-ligatures: common-ligatures; +} + +/* Better headings */ +h1, +h2, +h3, +h4, +h5, +h6 { + color: #7aa2f7; + margin-top: 2rem; + margin-bottom: 1rem; + font-weight: 600; + letter-spacing: -0.025em; + line-height: 1.25; +} + +/* Better paragraph text */ +p { + margin-bottom: 1.2rem; + color: #c0caf5; + line-height: 1.65; +} + .container { @@ -43,19 +73,16 @@ a { font-weight: 500; - color: #646cff; - text-decoration: inherit; + color: #7dcfff; + text-decoration: none; + transition: color 0.2s ease; } a:hover { - color: #535bf2; + color: #bb9af7; + text-decoration: underline; } -/* h1 { - font-size: 3.2em; - line-height: 1.1; -} */ - button { border-radius: 8px; border: 1px solid transparent; @@ -135,7 +162,7 @@ vscode-text-area { border: 2px solid var(--vscode-editor-background); border-radius: 6px; padding: 8px 12px; - font-family: "JetBrains Mono", SF Mono, "Geist Mono", "Fira Code", "Fira Mono", "Menlo", "Consolas", "DejaVu Sans Mono", monospace; + font-family: "Inter", -apple-system, BlinkMacSystemFont, "SF Pro Display", "Segoe UI", "Roboto", "Helvetica Neue", Arial, sans-serif; font-size: 14px; line-height: 1.6; min-height: 36px; @@ -331,7 +358,7 @@ code { } .doc-content { - font-family: "JetBrains Mono", SF Mono, "Geist Mono", "Fira Code", "Fira Mono", "Menlo", "Consolas", "DejaVu Sans Mono", monospace; + font-family: "Inter", -apple-system, BlinkMacSystemFont, "SF Pro Display", "Segoe UI", "Roboto", "Helvetica Neue", Arial, sans-serif; color: var(--vscode-editor-foreground); padding: 16px; text-align: left; @@ -413,38 +440,6 @@ code { } -/* Headings */ -h1, -h2, -h3, -h4, -h5, -h6 { - color: #7aa2f7; - margin-top: 2rem; - margin-bottom: 1rem; - font-weight: 400; -} - -/* Paragraphs and text */ -p { - margin-bottom: 1.2rem; - color: #c0caf5; -} - -/* Links */ -a { - color: #7dcfff; - text-decoration: none; - transition: color 0.2s ease; -} - -a:hover { - color: #bb9af7; - text-decoration: underline; -} - - /* Important notes and warnings */ blockquote { border-left: 4px solid #f7768e; @@ -546,7 +541,7 @@ hr { .url-link { color: #007bff; font-size: 16px; - font-family: "JetBrains Mono", SF Mono, "Geist Mono", "Fira Code", "Fira Mono", "Menlo", "Consolas", "DejaVu Sans Mono", monospace; + font-family: "Inter", -apple-system, BlinkMacSystemFont, "SF Pro Display", "Segoe UI", "Roboto", "Helvetica Neue", Arial, sans-serif; line-height: 1.5; text-decoration: none; word-wrap: break-word; @@ -585,7 +580,7 @@ hr { border: 1px solid var(--vscode-panel-border); border-radius: 6px; margin: 16px 0; - font-family: "JetBrains Mono", SF Mono, "Geist Mono", "Fira Code", "Fira Mono", "Menlo", "Consolas", "DejaVu Sans Mono", monospace; + font-family: "JetBrains Mono", "Fira Code", "Fira Mono", "Menlo", "Consolas", "DejaVu Sans Mono", monospace; } .individual-code-header { From 995763dcc8bbbfb0e65bf0173604e2cf9c8ff662 Mon Sep 17 00:00:00 2001 From: Olasunkanmi Oyinlola Date: Thu, 17 Jul 2025 21:41:01 +0800 Subject: [PATCH 2/4] feat(chat): Implement chat history trimming and optimized file storage - Limit chat history for Gemini, Groq, Anthropic and Grok models to a maximum of 20 items to prevent memory issues - Implement optimized file storage with caching, debouncing writes, and asynchronous .gitignore updates to improve performance. - Switch from FileStorage to OptimizedFileStorage for AgentService. - Add file-storage-optimized.ts and file-storage-backup.ts with optimized logic --- src/commands/handler.ts | 59 ++++-- src/services/agent-state.ts | 4 +- src/services/file-storage-backup.ts | 118 ++++++++++++ src/services/file-storage-optimized.ts | 254 +++++++++++++++++++++++++ src/services/file-storage.ts | 38 ++-- src/webview-providers/base.ts | 90 ++++++--- src/webview/chat_html.ts | 17 +- 7 files changed, 523 insertions(+), 57 deletions(-) create mode 100644 src/services/file-storage-backup.ts create mode 100644 src/services/file-storage-optimized.ts diff --git a/src/commands/handler.ts b/src/commands/handler.ts index d815266..d8bf0a9 100644 --- a/src/commands/handler.ts +++ b/src/commands/handler.ts @@ -440,10 +440,12 @@ export abstract class CodeCommandHandler implements ICodeCommandHandler { if (prompt && response) { let chatHistory; + const MAX_HISTORY_ITEMS = 20; // Limit chat history to prevent memory issues + switch (model) { - case generativeAiModels.GEMINI: + case generativeAiModels.GEMINI: { chatHistory = getLatestChatHistory(COMMON.GEMINI_CHAT_HISTORY); - Memory.set(COMMON.GEMINI_CHAT_HISTORY, [ + const newGeminiHistory = [ ...chatHistory, { role: "user", @@ -453,11 +455,20 @@ export abstract class CodeCommandHandler implements ICodeCommandHandler { role: "model", parts: [{ text: response }], }, - ]); + ]; + + // Trim history if too long (keep system messages at start) + const trimmedGeminiHistory = + newGeminiHistory.length > MAX_HISTORY_ITEMS + ? newGeminiHistory.slice(-MAX_HISTORY_ITEMS) + : newGeminiHistory; + + Memory.set(COMMON.GEMINI_CHAT_HISTORY, trimmedGeminiHistory); break; - case generativeAiModels.GROQ: + } + case generativeAiModels.GROQ: { chatHistory = getLatestChatHistory(COMMON.GROQ_CHAT_HISTORY); - Memory.set(COMMON.GROQ_CHAT_HISTORY, [ + const newGroqHistory = [ ...chatHistory, { role: "user", @@ -467,11 +478,19 @@ export abstract class CodeCommandHandler implements ICodeCommandHandler { role: "system", content: response, }, - ]); + ]; + + const trimmedGroqHistory = + newGroqHistory.length > MAX_HISTORY_ITEMS + ? newGroqHistory.slice(-MAX_HISTORY_ITEMS) + : newGroqHistory; + + Memory.set(COMMON.GROQ_CHAT_HISTORY, trimmedGroqHistory); break; - case generativeAiModels.ANTHROPIC: + } + case generativeAiModels.ANTHROPIC: { chatHistory = getLatestChatHistory(COMMON.ANTHROPIC_CHAT_HISTORY); - Memory.set(COMMON.ANTHROPIC_CHAT_HISTORY, [ + const newAnthropicHistory = [ ...chatHistory, { role: "user", @@ -481,11 +500,19 @@ export abstract class CodeCommandHandler implements ICodeCommandHandler { role: "assistant", content: response, }, - ]); + ]; + + const trimmedAnthropicHistory = + newAnthropicHistory.length > MAX_HISTORY_ITEMS + ? newAnthropicHistory.slice(-MAX_HISTORY_ITEMS) + : newAnthropicHistory; + + Memory.set(COMMON.ANTHROPIC_CHAT_HISTORY, trimmedAnthropicHistory); break; - case generativeAiModels.GROK: + } + case generativeAiModels.GROK: { chatHistory = getLatestChatHistory(COMMON.ANTHROPIC_CHAT_HISTORY); - Memory.set(COMMON.ANTHROPIC_CHAT_HISTORY, [ + const newGrokHistory = [ ...chatHistory, { role: "user", @@ -495,8 +522,16 @@ export abstract class CodeCommandHandler implements ICodeCommandHandler { role: "assistant", content: response, }, - ]); + ]; + + const trimmedGrokHistory = + newGrokHistory.length > MAX_HISTORY_ITEMS + ? newGrokHistory.slice(-MAX_HISTORY_ITEMS) + : newGrokHistory; + + Memory.set(COMMON.ANTHROPIC_CHAT_HISTORY, trimmedGrokHistory); break; + } default: throw new Error(`Generative model ${model} not available`); } diff --git a/src/services/agent-state.ts b/src/services/agent-state.ts index 6b0e3e6..4c7fecb 100644 --- a/src/services/agent-state.ts +++ b/src/services/agent-state.ts @@ -1,7 +1,7 @@ import { AgentState } from "../agents/interface"; import { COMMON } from "../application/constant"; import { GeminiLLMSnapShot } from "../llms/interface"; -import { FileStorage, IStorage } from "./file-storage"; +import { OptimizedFileStorage, IStorage } from "./file-storage-optimized"; export class AgentService { private static instance: AgentService; @@ -13,7 +13,7 @@ export class AgentService { public static getInstance(): AgentService { if (!AgentService.instance) { - AgentService.instance = new AgentService(new FileStorage()); + AgentService.instance = new AgentService(new OptimizedFileStorage()); } return AgentService.instance; } diff --git a/src/services/file-storage-backup.ts b/src/services/file-storage-backup.ts new file mode 100644 index 0000000..6bc0cb8 --- /dev/null +++ b/src/services/file-storage-backup.ts @@ -0,0 +1,118 @@ +// Create a new file: src/storage/database.ts + +import * as fs from "fs"; +import * as path from "path"; +import * as vscode from "vscode"; + +export interface IStorage { + get(key: string): Promise; + set(key: string, value: T): Promise; + delete(key: string): Promise; + has(key: string): Promise; +} + +export class FileStorage implements IStorage { + private storagePath = ""; + private initialized = false; + + constructor() { + // No async operations in constructor + } + + private async ensureInitialized(): Promise { + if (!this.initialized) { + await this.createCodeBuddyFolder(); + this.initialized = true; + } + } + + async createCodeBuddyFolder() { + const workSpaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath ?? ""; + this.storagePath = path.join(workSpaceRoot, ".codebuddy"); + if (!fs.existsSync(this.storagePath)) { + fs.mkdirSync(this.storagePath, { recursive: true }); + } + await this.updateGitIgnore(workSpaceRoot, ".codebuddy"); + } + + /** + * Updates or creates a .gitignore file with the specified pattern + * @param workspaceRoot The root folder of the current workspace + * @param pattern The pattern to add to .gitignore (e.g., '.codebuddy') + */ + async updateGitIgnore(workspaceRoot: string, pattern: string): Promise { + const gitIgnorePath = path.join(workspaceRoot, ".gitignore"); + + if (fs.existsSync(gitIgnorePath)) { + const gitIgnoreContent = fs.readFileSync(gitIgnorePath, "utf8"); + const lines = gitIgnoreContent.split(/\r?\n/); + + const patternExists = lines.some( + (line) => line.trim() === pattern || line.trim() === `/${pattern}` || line.trim() === `${pattern}/` + ); + + if (!patternExists) { + const newContent = gitIgnoreContent.endsWith("\n") + ? `${gitIgnoreContent}${pattern}\n` + : `${gitIgnoreContent}\n${pattern}\n`; + + fs.writeFileSync(gitIgnorePath, newContent, "utf8"); + console.log(`Added ${pattern} to .gitignore`); + } + } else { + fs.writeFileSync(gitIgnorePath, `# Generated by CodeBuddy Extension\n${pattern}\n`, "utf8"); + console.log(`Created new .gitignore with ${pattern}`); + } + } + private getFilePath(key: string): string { + return path.join(this.storagePath, `${key}.json`); + } + + async get(key: string): Promise { + await this.ensureInitialized(); + + try { + const filePath = this.getFilePath(key); + if (!fs.existsSync(filePath)) { + return undefined; + } + const data = await fs.promises.readFile(filePath, "utf-8"); + return JSON.parse(data) as T; + } catch (error) { + console.error(`Error reading data for key ${key}:`, error); + return undefined; + } + } + + async set(key: string, value: T): Promise { + await this.ensureInitialized(); + + try { + const filePath = this.getFilePath(key); + await fs.promises.writeFile(filePath, JSON.stringify(value, null, 2), "utf-8"); + } catch (error) { + console.error(`Error storing data for key ${key}:`, error); + throw new Error(`Failed to store data: ${error}`); + } + } + + async delete(key: string): Promise { + await this.ensureInitialized(); + + try { + const filePath = this.getFilePath(key); + if (fs.existsSync(filePath)) { + await fs.promises.unlink(filePath); + } + } catch (error) { + console.error(`Error deleting data for key ${key}:`, error); + } + } + + async has(key: string): Promise { + await this.ensureInitialized(); + + const filePath = this.getFilePath(key); + return fs.existsSync(filePath); + } +} diff --git a/src/services/file-storage-optimized.ts b/src/services/file-storage-optimized.ts new file mode 100644 index 0000000..e4fca93 --- /dev/null +++ b/src/services/file-storage-optimized.ts @@ -0,0 +1,254 @@ +import * as fs from "fs"; +import * as path from "path"; +import * as vscode from "vscode"; + +export interface IStorage { + get(key: string): Promise; + set(key: string, value: T): Promise; + delete(key: string): Promise; + has(key: string): Promise; +} + +export class OptimizedFileStorage implements IStorage { + private storagePath = ""; + private readonly cache = new Map< + string, + { data: any; timestamp: number; dirty: boolean } + >(); + private readonly CACHE_TTL = 10000; // 10 seconds cache + private readonly pendingWrites = new Map>(); + private initialized = false; + private batchWriteTimer: NodeJS.Timeout | null = null; + private readonly BATCH_WRITE_DELAY = 1000; // 1 second batching + + constructor() { + // Don't call async operations in constructor + } + + private async ensureInitialized(): Promise { + if (!this.initialized) { + await this.createCodeBuddyFolder(); + this.initialized = true; + } + } + + async createCodeBuddyFolder() { + const workSpaceRoot = + vscode.workspace.workspaceFolders?.[0]?.uri.fsPath ?? ""; + this.storagePath = path.join(workSpaceRoot, ".codebuddy"); + + if (!fs.existsSync(this.storagePath)) { + await fs.promises.mkdir(this.storagePath, { recursive: true }); + } + + // Update .gitignore asynchronously and non-blocking + this.updateGitIgnoreAsync(workSpaceRoot, ".codebuddy").catch(console.error); + } + + /** + * Non-blocking .gitignore update + */ + private async updateGitIgnoreAsync( + workspaceRoot: string, + pattern: string, + ): Promise { + try { + const gitIgnorePath = path.join(workspaceRoot, ".gitignore"); + + let gitIgnoreContent = ""; + try { + gitIgnoreContent = await fs.promises.readFile(gitIgnorePath, "utf8"); + } catch { + // File doesn't exist, create new one + } + + const lines = gitIgnoreContent.split(/\r?\n/); + const patternExists = lines.some( + (line) => + line.trim() === pattern || + line.trim() === `/${pattern}` || + line.trim() === `${pattern}/`, + ); + + if (!patternExists) { + let newContent: string; + if (gitIgnoreContent) { + newContent = gitIgnoreContent.endsWith("\n") + ? `${gitIgnoreContent}${pattern}\n` + : `${gitIgnoreContent}\n${pattern}\n`; + } else { + newContent = `# Generated by CodeBuddy Extension\n${pattern}\n`; + } + + await fs.promises.writeFile(gitIgnorePath, newContent, "utf8"); + } + } catch (error) { + // Silently fail .gitignore updates to prevent blocking + console.warn("Could not update .gitignore:", error); + } + } + + private getFilePath(key: string): string { + return path.join(this.storagePath, `${key}.json`); + } + + private isValidCache(entry: { data: any; timestamp: number }): boolean { + return Date.now() - entry.timestamp < this.CACHE_TTL; + } + + async get(key: string): Promise { + await this.ensureInitialized(); + + // Check cache first + const cached = this.cache.get(key); + if (cached && this.isValidCache(cached)) { + return cached.data as T; + } + + try { + const filePath = this.getFilePath(key); + + // Check if file exists without throwing + try { + await fs.promises.access(filePath); + } catch { + return undefined; + } + + const data = await fs.promises.readFile(filePath, "utf-8"); + const parsed = JSON.parse(data) as T; + + // Cache the result + this.cache.set(key, { + data: parsed, + timestamp: Date.now(), + dirty: false, + }); + + return parsed; + } catch (error) { + console.error(`Error reading data for key ${key}:`, error); + return undefined; + } + } + + async set(key: string, value: T): Promise { + await this.ensureInitialized(); + + // Update cache immediately + this.cache.set(key, { + data: value, + timestamp: Date.now(), + dirty: true, + }); + + // Debounce writes to prevent excessive I/O + const existingWrite = this.pendingWrites.get(key); + if (existingWrite) { + return existingWrite; + } + + const writePromise = this.debouncedWrite(key, value); + this.pendingWrites.set(key, writePromise); + + try { + await writePromise; + } finally { + this.pendingWrites.delete(key); + } + } + + private async debouncedWrite(key: string, value: T): Promise { + return new Promise((resolve, reject) => { + // Clear existing timer + if (this.batchWriteTimer) { + clearTimeout(this.batchWriteTimer); + } + + // Set new timer + this.batchWriteTimer = setTimeout(async () => { + try { + const filePath = this.getFilePath(key); + await fs.promises.writeFile( + filePath, + JSON.stringify(value, null, 2), + "utf-8", + ); + + // Mark as clean in cache + const cached = this.cache.get(key); + if (cached) { + cached.dirty = false; + } + + resolve(); + } catch (error) { + console.error(`Error storing data for key ${key}:`, error); + reject(new Error(`Failed to store data: ${error}`)); + } + }, this.BATCH_WRITE_DELAY); + }); + } + + async delete(key: string): Promise { + await this.ensureInitialized(); + + // Remove from cache + this.cache.delete(key); + + try { + const filePath = this.getFilePath(key); + await fs.promises.unlink(filePath); + } catch (error) { + // File might not exist, that's fine + if ( + error instanceof Error && + "code" in error && + error.code !== "ENOENT" + ) { + console.error(`Error deleting data for key ${key}:`, error); + } + } + } + + async has(key: string): Promise { + await this.ensureInitialized(); + + // Check cache first + const cached = this.cache.get(key); + if (cached && this.isValidCache(cached)) { + return true; + } + + try { + const filePath = this.getFilePath(key); + await fs.promises.access(filePath); + return true; + } catch { + return false; + } + } + + // Force flush all pending writes + async flushAll(): Promise { + const promises = Array.from(this.pendingWrites.values()); + await Promise.all(promises); + } + + // Clear cache + clearCache(): void { + this.cache.clear(); + } + + // Get cache stats for monitoring + getCacheStats(): { size: number; dirtyCount: number } { + let dirtyCount = 0; + for (const entry of this.cache.values()) { + if (entry.dirty) dirtyCount++; + } + return { + size: this.cache.size, + dirtyCount, + }; + } +} diff --git a/src/services/file-storage.ts b/src/services/file-storage.ts index dce3c4d..6bc0cb8 100644 --- a/src/services/file-storage.ts +++ b/src/services/file-storage.ts @@ -13,14 +13,21 @@ export interface IStorage { export class FileStorage implements IStorage { private storagePath = ""; + private initialized = false; constructor() { - this.createCodeBuddyFolder(); + // No async operations in constructor + } + + private async ensureInitialized(): Promise { + if (!this.initialized) { + await this.createCodeBuddyFolder(); + this.initialized = true; + } } async createCodeBuddyFolder() { - const workSpaceRoot = - vscode.workspace.workspaceFolders?.[0]?.uri.fsPath ?? ""; + const workSpaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath ?? ""; this.storagePath = path.join(workSpaceRoot, ".codebuddy"); if (!fs.existsSync(this.storagePath)) { fs.mkdirSync(this.storagePath, { recursive: true }); @@ -41,10 +48,7 @@ export class FileStorage implements IStorage { const lines = gitIgnoreContent.split(/\r?\n/); const patternExists = lines.some( - (line) => - line.trim() === pattern || - line.trim() === `/${pattern}` || - line.trim() === `${pattern}/`, + (line) => line.trim() === pattern || line.trim() === `/${pattern}` || line.trim() === `${pattern}/` ); if (!patternExists) { @@ -56,11 +60,7 @@ export class FileStorage implements IStorage { console.log(`Added ${pattern} to .gitignore`); } } else { - fs.writeFileSync( - gitIgnorePath, - `# Generated by CodeBuddy Extension\n${pattern}\n`, - "utf8", - ); + fs.writeFileSync(gitIgnorePath, `# Generated by CodeBuddy Extension\n${pattern}\n`, "utf8"); console.log(`Created new .gitignore with ${pattern}`); } } @@ -69,6 +69,8 @@ export class FileStorage implements IStorage { } async get(key: string): Promise { + await this.ensureInitialized(); + try { const filePath = this.getFilePath(key); if (!fs.existsSync(filePath)) { @@ -83,13 +85,11 @@ export class FileStorage implements IStorage { } async set(key: string, value: T): Promise { + await this.ensureInitialized(); + try { const filePath = this.getFilePath(key); - await fs.promises.writeFile( - filePath, - JSON.stringify(value, null, 2), - "utf-8", - ); + await fs.promises.writeFile(filePath, JSON.stringify(value, null, 2), "utf-8"); } catch (error) { console.error(`Error storing data for key ${key}:`, error); throw new Error(`Failed to store data: ${error}`); @@ -97,6 +97,8 @@ export class FileStorage implements IStorage { } async delete(key: string): Promise { + await this.ensureInitialized(); + try { const filePath = this.getFilePath(key); if (fs.existsSync(filePath)) { @@ -108,6 +110,8 @@ export class FileStorage implements IStorage { } async has(key: string): Promise { + await this.ensureInitialized(); + const filePath = this.getFilePath(key); return fs.existsSync(filePath); } diff --git a/src/webview-providers/base.ts b/src/webview-providers/base.ts index 105e8b6..9b2fcd0 100644 --- a/src/webview-providers/base.ts +++ b/src/webview-providers/base.ts @@ -1,6 +1,9 @@ import * as vscode from "vscode"; import { Orchestrator } from "../agents/orchestrator"; -import { FolderEntry, IContextInfo } from "../application/interfaces/workspace.interface"; +import { + FolderEntry, + IContextInfo, +} from "../application/interfaces/workspace.interface"; import { IEventPayload } from "../emitter/interface"; import { Logger } from "../infrastructure/logger/logger"; import { AgentService } from "../services/agent-state"; @@ -32,7 +35,7 @@ export abstract class BaseWebViewProvider implements vscode.Disposable { private readonly _extensionUri: vscode.Uri, protected readonly apiKey: string, protected readonly generativeAiModel: string, - context: vscode.ExtensionContext + context: vscode.ExtensionContext, ) { this.fileManager = FileManager.initialize(context, "files"); this.fileService = FileService.getInstance(); @@ -58,13 +61,23 @@ export abstract class BaseWebViewProvider implements vscode.Disposable { this.orchestrator.onThinking(this.handleModelResponseEvent.bind(this)), this.orchestrator.onUpdate(this.handleModelResponseEvent.bind(this)), this.orchestrator.onError(this.handleModelResponseEvent.bind(this)), - this.orchestrator.onSecretChange(this.handleModelResponseEvent.bind(this)), - this.orchestrator.onActiveworkspaceUpdate(this.handleGenericEvents.bind(this)), + this.orchestrator.onSecretChange( + this.handleModelResponseEvent.bind(this), + ), + this.orchestrator.onActiveworkspaceUpdate( + this.handleGenericEvents.bind(this), + ), this.orchestrator.onFileUpload(this.handleModelResponseEvent.bind(this)), - this.orchestrator.onStrategizing(this.handleModelResponseEvent.bind(this)), - this.orchestrator.onConfigurationChange(this.handleGenericEvents.bind(this)), + this.orchestrator.onStrategizing( + this.handleModelResponseEvent.bind(this), + ), + this.orchestrator.onConfigurationChange( + this.handleGenericEvents.bind(this), + ), this.orchestrator.onUserPrompt(this.handleUserPrompt.bind(this)), - this.orchestrator.onGetUserPreferences(this.handleUserPreferences.bind(this)) + this.orchestrator.onGetUserPreferences( + this.handleUserPreferences.bind(this), + ), ); } @@ -87,7 +100,9 @@ export abstract class BaseWebViewProvider implements vscode.Disposable { webviewView.webview.options = webviewOptions; if (!this.apiKey) { - vscode.window.showErrorMessage("API key not configured. Check your settings."); + vscode.window.showErrorMessage( + "API key not configured. Check your settings.", + ); return; } @@ -100,7 +115,10 @@ export abstract class BaseWebViewProvider implements vscode.Disposable { } private async setWebviewHtml(view: vscode.WebviewView): Promise { - view.webview.html = getWebviewContent(this.currentWebView?.webview!, this._extensionUri); + view.webview.html = getWebviewContent( + this.currentWebView?.webview!, + this._extensionUri, + ); } private async getFiles() { @@ -137,8 +155,10 @@ export abstract class BaseWebViewProvider implements vscode.Disposable { private async publishWorkSpace(): Promise { try { - const filesAndDirs: IContextInfo = await this.workspaceService.getContextInfo(true); - const workspaceFiles: Map | undefined = filesAndDirs.workspaceFiles; + const filesAndDirs: IContextInfo = + await this.workspaceService.getContextInfo(true); + const workspaceFiles: Map | undefined = + filesAndDirs.workspaceFiles; if (!workspaceFiles) { this.logger.warn("There no files within the workspace"); return; @@ -174,16 +194,22 @@ export abstract class BaseWebViewProvider implements vscode.Disposable { // Check if we should prune history for performance if (this.UserMessageCounter % 10 === 0) { const stats = await this.getChatHistoryStats("agentId"); - if (stats.totalMessages > 100 || stats.estimatedTokens > 16000) { + if ( + stats.totalMessages > 100 || + stats.estimatedTokens > 16000 + ) { this.logger.info( - `High chat history usage detected: ${stats.totalMessages} messages, ${stats.estimatedTokens} tokens` + `High chat history usage detected: ${stats.totalMessages} messages, ${stats.estimatedTokens} tokens`, ); // Optionally trigger manual pruning here // await this.pruneHistoryManually("agentId", { maxMessages: 50, maxTokens: 8000 }); } } - response = await this.generateResponse(message.message, message.metaData); + response = await this.generateResponse( + message.message, + message.metaData, + ); if (response) { await this.sendResponse(formatText(response), "bot"); } @@ -213,7 +239,7 @@ export abstract class BaseWebViewProvider implements vscode.Disposable { default: throw new Error("Unknown command"); } - }) + }), ); } catch (error) { this.logger.error("Message handler failed", error); @@ -229,21 +255,33 @@ export abstract class BaseWebViewProvider implements vscode.Disposable { } public handleModelResponseEvent(event: IEventPayload) { - this.sendResponse(formatText(event.message), event.message === "folders" ? "bootstrap" : "bot"); + this.sendResponse( + formatText(event.message), + event.message === "folders" ? "bootstrap" : "bot", + ); } - abstract generateResponse(message?: string, metaData?: Record): Promise; + abstract generateResponse( + message?: string, + metaData?: Record, + ): Promise; - abstract sendResponse(response: string, currentChat?: string): Promise; + abstract sendResponse( + response: string, + currentChat?: string, + ): Promise; public dispose(): void { - this.logger.debug(`Disposing BaseWebViewProvider with ${this.disposables.length} disposables`); + this.logger.debug( + `Disposing BaseWebViewProvider with ${this.disposables.length} disposables`, + ); this.disposables.forEach((d) => d.dispose()); this.disposables.length = 0; // Clear the array } async getContext(files: string[]) { try { - const filesContent: Map | undefined = await this.fileService.getFilesContent(files); + const filesContent: Map | undefined = + await this.fileService.getFilesContent(files); if (filesContent && filesContent.size > 0) { return Array.from(filesContent.values()).join("\n"); } @@ -263,9 +301,15 @@ export abstract class BaseWebViewProvider implements vscode.Disposable { maxTokens: number; maxAgeHours: number; preserveSystemMessages: boolean; - }> + }>, ): Promise { - return this.chatHistoryManager.formatChatHistory(role, message, model, key, pruneConfig); + return this.chatHistoryManager.formatChatHistory( + role, + message, + model, + key, + pruneConfig, + ); } // Get chat history stats for monitoring @@ -285,7 +329,7 @@ export abstract class BaseWebViewProvider implements vscode.Disposable { maxMessages?: number; maxTokens?: number; maxAgeHours?: number; - } + }, ): Promise { await this.chatHistoryManager.pruneHistoryForKey(key, config); } diff --git a/src/webview/chat_html.ts b/src/webview/chat_html.ts index 12d5d24..26e6f11 100644 --- a/src/webview/chat_html.ts +++ b/src/webview/chat_html.ts @@ -6,7 +6,8 @@ import { Uri, Webview } from "vscode"; // and ensure script integrity when using Content Security Policy (CSP) function getNonce() { let text = ""; - const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + const possible = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; for (let i = 0; i < 32; i++) { text += possible.charAt(Math.floor(Math.random() * possible.length)); } @@ -16,8 +17,18 @@ function getNonce() { const nonce = getNonce(); export const chartComponent = (webview: Webview, extensionUri: Uri) => { - const stylesUri = getUri(webview, extensionUri, ["dist", "webview", "assets", "index.css"]); - const scriptUri = getUri(webview, extensionUri, ["dist", "webview", "assets", "index.js"]); + const stylesUri = getUri(webview, extensionUri, [ + "dist", + "webview", + "assets", + "index.css", + ]); + const scriptUri = getUri(webview, extensionUri, [ + "dist", + "webview", + "assets", + "index.js", + ]); return ` From 42c4522e5212dbfa2e0753a9483f9ae784bcc796 Mon Sep 17 00:00:00 2001 From: Olasunkanmi Oyinlola Date: Thu, 17 Jul 2025 21:59:41 +0800 Subject: [PATCH 3/4] feat(ui): Implement standardized prompt for all webview providers - Introduces a utility to ensure consistent prompt formatting across all LLM interactions. - Adds to the enum in to unify chat history for all providers. - Updates Anthropic, Deepseek, Gemini and Groq webview providers to use the utility for creating prompts. - Improves prompt quality by providing consistent instructions and context to the LLMs. --- src/application/constant.ts | 1 + src/services/file-storage-backup.ts | 20 ++++++-- src/services/file-storage.ts | 20 ++++++-- src/utils/standardized-prompt.ts | 73 +++++++++++++++++++++++++++++ src/webview-providers/anthropic.ts | 6 ++- src/webview-providers/deepseek.ts | 10 +++- src/webview-providers/gemini.ts | 8 +++- src/webview-providers/groq.ts | 6 ++- 8 files changed, 131 insertions(+), 13 deletions(-) create mode 100644 src/utils/standardized-prompt.ts diff --git a/src/application/constant.ts b/src/application/constant.ts index a705ac7..6d93518 100644 --- a/src/application/constant.ts +++ b/src/application/constant.ts @@ -23,6 +23,7 @@ export enum COMMON { GEMINI_CHAT_HISTORY = "geminiChatHistory", ANTHROPIC_CHAT_HISTORY = "anthropicChatHistory", DEEPSEEK_CHAT_HISTORY = "deepseekChatHistory", + SHARED_CHAT_HISTORY = "sharedChatHistory", // Unified chat history for all providers USER_INPUT = "user-input", BOT = "bot", GEMINI_SNAPSHOT = "GeminiSnapshot", diff --git a/src/services/file-storage-backup.ts b/src/services/file-storage-backup.ts index 6bc0cb8..de44636 100644 --- a/src/services/file-storage-backup.ts +++ b/src/services/file-storage-backup.ts @@ -27,7 +27,8 @@ export class FileStorage implements IStorage { } async createCodeBuddyFolder() { - const workSpaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath ?? ""; + const workSpaceRoot = + vscode.workspace.workspaceFolders?.[0]?.uri.fsPath ?? ""; this.storagePath = path.join(workSpaceRoot, ".codebuddy"); if (!fs.existsSync(this.storagePath)) { fs.mkdirSync(this.storagePath, { recursive: true }); @@ -48,7 +49,10 @@ export class FileStorage implements IStorage { const lines = gitIgnoreContent.split(/\r?\n/); const patternExists = lines.some( - (line) => line.trim() === pattern || line.trim() === `/${pattern}` || line.trim() === `${pattern}/` + (line) => + line.trim() === pattern || + line.trim() === `/${pattern}` || + line.trim() === `${pattern}/`, ); if (!patternExists) { @@ -60,7 +64,11 @@ export class FileStorage implements IStorage { console.log(`Added ${pattern} to .gitignore`); } } else { - fs.writeFileSync(gitIgnorePath, `# Generated by CodeBuddy Extension\n${pattern}\n`, "utf8"); + fs.writeFileSync( + gitIgnorePath, + `# Generated by CodeBuddy Extension\n${pattern}\n`, + "utf8", + ); console.log(`Created new .gitignore with ${pattern}`); } } @@ -89,7 +97,11 @@ export class FileStorage implements IStorage { try { const filePath = this.getFilePath(key); - await fs.promises.writeFile(filePath, JSON.stringify(value, null, 2), "utf-8"); + await fs.promises.writeFile( + filePath, + JSON.stringify(value, null, 2), + "utf-8", + ); } catch (error) { console.error(`Error storing data for key ${key}:`, error); throw new Error(`Failed to store data: ${error}`); diff --git a/src/services/file-storage.ts b/src/services/file-storage.ts index 6bc0cb8..de44636 100644 --- a/src/services/file-storage.ts +++ b/src/services/file-storage.ts @@ -27,7 +27,8 @@ export class FileStorage implements IStorage { } async createCodeBuddyFolder() { - const workSpaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath ?? ""; + const workSpaceRoot = + vscode.workspace.workspaceFolders?.[0]?.uri.fsPath ?? ""; this.storagePath = path.join(workSpaceRoot, ".codebuddy"); if (!fs.existsSync(this.storagePath)) { fs.mkdirSync(this.storagePath, { recursive: true }); @@ -48,7 +49,10 @@ export class FileStorage implements IStorage { const lines = gitIgnoreContent.split(/\r?\n/); const patternExists = lines.some( - (line) => line.trim() === pattern || line.trim() === `/${pattern}` || line.trim() === `${pattern}/` + (line) => + line.trim() === pattern || + line.trim() === `/${pattern}` || + line.trim() === `${pattern}/`, ); if (!patternExists) { @@ -60,7 +64,11 @@ export class FileStorage implements IStorage { console.log(`Added ${pattern} to .gitignore`); } } else { - fs.writeFileSync(gitIgnorePath, `# Generated by CodeBuddy Extension\n${pattern}\n`, "utf8"); + fs.writeFileSync( + gitIgnorePath, + `# Generated by CodeBuddy Extension\n${pattern}\n`, + "utf8", + ); console.log(`Created new .gitignore with ${pattern}`); } } @@ -89,7 +97,11 @@ export class FileStorage implements IStorage { try { const filePath = this.getFilePath(key); - await fs.promises.writeFile(filePath, JSON.stringify(value, null, 2), "utf-8"); + await fs.promises.writeFile( + filePath, + JSON.stringify(value, null, 2), + "utf-8", + ); } catch (error) { console.error(`Error storing data for key ${key}:`, error); throw new Error(`Failed to store data: ${error}`); diff --git a/src/utils/standardized-prompt.ts b/src/utils/standardized-prompt.ts new file mode 100644 index 0000000..b2a9adc --- /dev/null +++ b/src/utils/standardized-prompt.ts @@ -0,0 +1,73 @@ +/** + * Utility for creating standardized, high-quality prompts for LLM interactions + * Ensures consistent, professional prompts across all webview providers + */ + +export class StandardizedPrompt { + /** + * Creates a comprehensive, professional prompt for user input + * @param userMessage The user's original message/request + * @param context Optional project context (file contents, selections, etc.) + * @returns A standardized prompt optimized for LLM performance + */ + static create(userMessage: string, context?: string): string { + const SYSTEM_PROMPT = `You are CodeBuddy, an expert AI programming assistant and code mentor. You excel at understanding developer intent and providing comprehensive, actionable solutions for all coding challenges. + +## Core Capabilities + +### 🎯 **Code Analysis & Understanding** +- **Language Detection**: Automatically identify programming languages and frameworks +- **Intent Recognition**: Understand what the developer is trying to achieve +- **Context Awareness**: Consider surrounding code, project structure, and best practices +- **Error Diagnosis**: Identify bugs, performance issues, and code smells + +### 🚀 **Code Generation & Enhancement** +- **Smart Completion**: Generate code that follows project patterns and conventions +- **Refactoring**: Improve code structure, readability, and maintainability +- **Optimization**: Enhance performance with efficient algorithms and data structures +- **Best Practices**: Apply SOLID principles, design patterns, and industry standards + +### 🔧 **Problem Solving** +- **Debugging**: Step-by-step problem identification and resolution +- **Implementation**: Convert requirements into working code solutions +- **Architecture**: Design scalable and maintainable code structures +- **Testing**: Generate comprehensive test cases and validation strategies + +### 📚 **Education & Mentoring** +- **Explanations**: Clear, educational explanations of concepts and code +- **Examples**: Practical, real-world code samples and use cases +- **Alternatives**: Multiple approaches with pros/cons analysis +- **Learning Path**: Progressive skill development recommendations + +## Response Guidelines + +### 📋 **Format Standards** +- **Code Blocks**: Use proper syntax highlighting and language tags +- **Documentation**: Include clear comments and explanations +- **Structure**: Organize responses with headers, sections, and bullet points +- **Examples**: Provide practical, runnable code examples + +### ⚡ **Quality Principles** +- **Accuracy**: Ensure all code is syntactically correct and functional +- **Completeness**: Address all aspects of the user's request +- **Clarity**: Use clear, professional language accessible to developers +- **Efficiency**: Optimize for both performance and developer productivity + +### 🎨 **Presentation** +- Use emojis strategically for visual organization +- Provide before/after comparisons when applicable +- Include error handling and edge cases +- Suggest testing and validation approaches + +## Context Integration +${context ? `\n**Project Context:**\n${context}\n` : ""} + +**Developer Request:** ${userMessage} + +--- + +**Instructions**: Analyze the request comprehensively and provide a complete, professional solution that addresses all aspects while following modern coding standards and best practices.`; + + return SYSTEM_PROMPT; + } +} diff --git a/src/webview-providers/anthropic.ts b/src/webview-providers/anthropic.ts index 0f46466..5c1b95a 100644 --- a/src/webview-providers/anthropic.ts +++ b/src/webview-providers/anthropic.ts @@ -12,6 +12,7 @@ import { getGenerativeAiModel, getXGroKBaseURL, } from "../utils/utils"; +import { StandardizedPrompt } from "../utils/standardized-prompt"; import { BaseWebViewProvider } from "./base"; export class AnthropicWebViewProvider extends BaseWebViewProvider { @@ -67,9 +68,12 @@ export class AnthropicWebViewProvider extends BaseWebViewProvider { this.baseUrl = getXGroKBaseURL(); } + // Create standardized prompt for user input + const standardizedPrompt = StandardizedPrompt.create(message, context); + let chatHistory = await this.modelChatHistory( "user", - `${message} \n context: ${context}`, + standardizedPrompt, "anthropic", "agentId", ); diff --git a/src/webview-providers/deepseek.ts b/src/webview-providers/deepseek.ts index 9609254..8066254 100644 --- a/src/webview-providers/deepseek.ts +++ b/src/webview-providers/deepseek.ts @@ -5,6 +5,7 @@ import { Memory } from "../memory/base"; import { IMessageInput, Message } from "../llms/message"; import { DeepseekLLM } from "../llms/deepseek/deepseek"; import { Logger, LogLevel } from "../infrastructure/logger/logger"; +import { StandardizedPrompt } from "../utils/standardized-prompt"; export class DeepseekWebViewProvider extends BaseWebViewProvider { public static readonly viewId = "chatView"; @@ -85,9 +86,16 @@ export class DeepseekWebViewProvider extends BaseWebViewProvider { return; } + // Create standardized prompt for user input + const context = + metaData?.context?.length > 0 + ? await this.getContext(metaData.context) + : undefined; + const standardizedPrompt = StandardizedPrompt.create(message, context); + const userMessage = Message.of({ role: "user", - content: message, + content: standardizedPrompt, }); let chatHistory = Memory.has(COMMON.DEEPSEEK_CHAT_HISTORY) diff --git a/src/webview-providers/gemini.ts b/src/webview-providers/gemini.ts index 2adfa2c..e804046 100644 --- a/src/webview-providers/gemini.ts +++ b/src/webview-providers/gemini.ts @@ -4,6 +4,7 @@ import { COMMON } from "../application/constant"; import { GeminiLLM } from "../llms/gemini/gemini"; import { IMessageInput } from "../llms/message"; import { Memory } from "../memory/base"; +import { StandardizedPrompt } from "../utils/standardized-prompt"; import { BaseWebViewProvider } from "./base"; export class GeminiWebViewProvider extends BaseWebViewProvider { @@ -67,9 +68,12 @@ export class GeminiWebViewProvider extends BaseWebViewProvider { return; } + // Create standardized prompt for user input + const enhancedPrompt = StandardizedPrompt.create(message, context); + let chatHistory = await this.modelChatHistory( "user", - `${message} \n context: ${context}`, + enhancedPrompt, "gemini", "agentId", ); @@ -77,7 +81,7 @@ export class GeminiWebViewProvider extends BaseWebViewProvider { const chat = this.model.startChat({ history: [...chatHistory], }); - const result = await chat.sendMessage(message); + const result = await chat.sendMessage(enhancedPrompt); const response = result.response; return response.text(); } catch (error) { diff --git a/src/webview-providers/groq.ts b/src/webview-providers/groq.ts index 9dc1f41..fdb5651 100644 --- a/src/webview-providers/groq.ts +++ b/src/webview-providers/groq.ts @@ -4,6 +4,7 @@ import { COMMON, GROQ_CONFIG } from "../application/constant"; import { Memory } from "../memory/base"; import { BaseWebViewProvider } from "./base"; import { IMessageInput, Message } from "../llms/message"; +import { StandardizedPrompt } from "../utils/standardized-prompt"; export class GroqWebViewProvider extends BaseWebViewProvider { chatHistory: IMessageInput[] = []; @@ -69,9 +70,12 @@ export class GroqWebViewProvider extends BaseWebViewProvider { } const { temperature, max_tokens, top_p, stop } = GROQ_CONFIG; + // Create standardized prompt for user input + const standardizedPrompt = StandardizedPrompt.create(message, context); + let chatHistory = await this.modelChatHistory( "user", - `${message} \n context: ${context}`, + standardizedPrompt, "groq", "agentId", ); From 1f1be1ab7493df6ede5511beeead535954aa4209 Mon Sep 17 00:00:00 2001 From: Olasunkanmi Oyinlola Date: Thu, 17 Jul 2025 22:28:35 +0800 Subject: [PATCH 4/4] make the history available to all webviews --- package.json | 14 +++++++- src/webview-providers/anthropic.ts | 13 ++++--- src/webview-providers/base.ts | 26 +++++++++++--- src/webview-providers/deepseek.ts | 54 +++++++++++------------------- src/webview-providers/gemini.ts | 20 ++++++++--- src/webview-providers/groq.ts | 18 +++++++--- 6 files changed, 92 insertions(+), 53 deletions(-) diff --git a/package.json b/package.json index 2b84935..e8fab0e 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ }, "publisher": "fiatinnovations", "description": "CodeBuddy is a Visual Studio Code extension that enhances developer productivity through AI-powered code assistance. It provides intelligent code review, refactoring suggestions, optimization tips, and interactive chat capabilities powered by multiple AI models including Gemini, Groq, Anthropic, and Deepseek.", - "version": "3.2.6", + "version": "3.2.9", "engines": { "vscode": "^1.78.0" }, @@ -96,6 +96,10 @@ "when": "editorHasSelection", "command": "CodeBuddy.generateMermaidDiagram", "group": "CodeBuddy" + }, + { + "command": "CodeBuddy.reviewPR", + "group": "CodeBuddy" } ] }, @@ -135,6 +139,14 @@ { "command": "CodeBuddy.generateMermaidDiagram", "title": "CodeBuddy. Generate Mermaid diagram." + }, + { + "command": "CodeBuddy.restart", + "title": "CodeBuddy: Restart Extension" + }, + { + "command": "CodeBuddy.reviewPR", + "title": "CodeBuddy: Review Pull Request" } ], "viewsContainers": { diff --git a/src/webview-providers/anthropic.ts b/src/webview-providers/anthropic.ts index 5c1b95a..16c5b60 100644 --- a/src/webview-providers/anthropic.ts +++ b/src/webview-providers/anthropic.ts @@ -40,10 +40,15 @@ export class AnthropicWebViewProvider extends BaseWebViewProvider { "assistant", response, "anthropic", - "agentId", + COMMON.SHARED_CHAT_HISTORY, ); } else { - await this.modelChatHistory("user", response, "anthropic", "agentId"); + await this.modelChatHistory( + "user", + response, + "anthropic", + COMMON.SHARED_CHAT_HISTORY, + ); } return await this.currentWebView?.webview.postMessage({ type, @@ -75,7 +80,7 @@ export class AnthropicWebViewProvider extends BaseWebViewProvider { "user", standardizedPrompt, "anthropic", - "agentId", + COMMON.SHARED_CHAT_HISTORY, ); const chatCompletion = await this.model.messages.create({ @@ -97,7 +102,7 @@ export class AnthropicWebViewProvider extends BaseWebViewProvider { return response; } catch (error) { console.error(error); - Memory.set(COMMON.ANTHROPIC_CHAT_HISTORY, []); + Memory.set(COMMON.SHARED_CHAT_HISTORY, []); vscode.window.showErrorMessage( "Model not responding, please resend your question", ); diff --git a/src/webview-providers/base.ts b/src/webview-providers/base.ts index 9b2fcd0..aa7f420 100644 --- a/src/webview-providers/base.ts +++ b/src/webview-providers/base.ts @@ -1,5 +1,6 @@ import * as vscode from "vscode"; import { Orchestrator } from "../agents/orchestrator"; +import { COMMON } from "../application/constant"; import { FolderEntry, IContextInfo, @@ -193,7 +194,9 @@ export abstract class BaseWebViewProvider implements vscode.Disposable { // Check if we should prune history for performance if (this.UserMessageCounter % 10 === 0) { - const stats = await this.getChatHistoryStats("agentId"); + const stats = await this.getChatHistoryStats( + COMMON.SHARED_CHAT_HISTORY, + ); if ( stats.totalMessages > 100 || stats.estimatedTokens > 16000 @@ -202,7 +205,7 @@ export abstract class BaseWebViewProvider implements vscode.Disposable { `High chat history usage detected: ${stats.totalMessages} messages, ${stats.estimatedTokens} tokens`, ); // Optionally trigger manual pruning here - // await this.pruneHistoryManually("agentId", { maxMessages: 50, maxTokens: 8000 }); + // await this.pruneHistoryManually(COMMON.SHARED_CHAT_HISTORY, { maxMessages: 50, maxTokens: 8000 }); } } @@ -229,7 +232,9 @@ export abstract class BaseWebViewProvider implements vscode.Disposable { // this.orchestrator.publish("onHistoryUpdated", message); // break; case "clear-history": - await this.chatHistoryManager.clearHistory("agentId"); + await this.chatHistoryManager.clearHistory( + COMMON.SHARED_CHAT_HISTORY, + ); this.orchestrator.publish("onClearHistory", message); break; case "update-user-info": @@ -274,8 +279,19 @@ export abstract class BaseWebViewProvider implements vscode.Disposable { this.logger.debug( `Disposing BaseWebViewProvider with ${this.disposables.length} disposables`, ); - this.disposables.forEach((d) => d.dispose()); - this.disposables.length = 0; // Clear the array + try { + this.disposables.forEach((d) => { + try { + d.dispose(); + } catch (err) { + this.logger.error(`Error disposing of disposable: ${err}`); + } + }); + } catch (error: any) { + this.logger.error(`Error during dispose: ${error.message}`); + } finally { + this.disposables.length = 0; // Clear the array + } } async getContext(files: string[]) { diff --git a/src/webview-providers/deepseek.ts b/src/webview-providers/deepseek.ts index 8066254..c2c2c40 100644 --- a/src/webview-providers/deepseek.ts +++ b/src/webview-providers/deepseek.ts @@ -2,7 +2,7 @@ import * as vscode from "vscode"; import { BaseWebViewProvider } from "./base"; import { COMMON } from "../application/constant"; import { Memory } from "../memory/base"; -import { IMessageInput, Message } from "../llms/message"; +import { IMessageInput } from "../llms/message"; import { DeepseekLLM } from "../llms/deepseek/deepseek"; import { Logger, LogLevel } from "../infrastructure/logger/logger"; import { StandardizedPrompt } from "../utils/standardized-prompt"; @@ -40,37 +40,27 @@ export class DeepseekWebViewProvider extends BaseWebViewProvider { try { const type = currentChat === "bot" ? "bot-response" : "user-input"; if (currentChat === "bot") { - this.chatHistory.push( - Message.of({ - role: "assistant", - content: response, - }), + await this.modelChatHistory( + "assistant", + response, + "deepseek", + COMMON.SHARED_CHAT_HISTORY, ); } else { - this.chatHistory.push( - Message.of({ - role: "user", - content: response, - }), + await this.modelChatHistory( + "user", + response, + "deepseek", + COMMON.SHARED_CHAT_HISTORY, ); } - - if (this.chatHistory.length === 2) { - const chatHistory = Memory.has(COMMON.DEEPSEEK_CHAT_HISTORY) - ? Memory.get(COMMON.DEEPSEEK_CHAT_HISTORY) - : []; - Memory.set(COMMON.DEEPSEEK_CHAT_HISTORY, [ - ...chatHistory, - ...this.chatHistory, - ]); - } return await this.currentWebView?.webview.postMessage({ type, message: response, }); } catch (error) { this.logger.error("Error sending response", error); - Memory.set(COMMON.DEEPSEEK_CHAT_HISTORY, []); + Memory.set(COMMON.SHARED_CHAT_HISTORY, []); console.error(error); } } @@ -93,23 +83,19 @@ export class DeepseekWebViewProvider extends BaseWebViewProvider { : undefined; const standardizedPrompt = StandardizedPrompt.create(message, context); - const userMessage = Message.of({ - role: "user", - content: standardizedPrompt, - }); - - let chatHistory = Memory.has(COMMON.DEEPSEEK_CHAT_HISTORY) - ? Memory.get(COMMON.DEEPSEEK_CHAT_HISTORY) - : [userMessage]; - - chatHistory = [...chatHistory, userMessage]; + // Use shared chat history like other providers + let chatHistory = await this.modelChatHistory( + "user", + standardizedPrompt, + "deepseek", + COMMON.SHARED_CHAT_HISTORY, + ); - Memory.removeItems(COMMON.DEEPSEEK_CHAT_HISTORY); const result = await this.deepseekLLM.generateText(message); return result; } catch (error) { this.logger.error("Error generating response", error); - Memory.set(COMMON.DEEPSEEK_CHAT_HISTORY, []); + Memory.set(COMMON.SHARED_CHAT_HISTORY, []); vscode.window.showErrorMessage( "Model not responding, please resend your question", ); diff --git a/src/webview-providers/gemini.ts b/src/webview-providers/gemini.ts index e804046..b22bda3 100644 --- a/src/webview-providers/gemini.ts +++ b/src/webview-providers/gemini.ts @@ -35,16 +35,26 @@ export class GeminiWebViewProvider extends BaseWebViewProvider { try { const type = currentChat === "bot" ? "bot-response" : "user-input"; if (currentChat === "bot") { - await this.modelChatHistory("model", response, "gemini", "agentId"); + await this.modelChatHistory( + "model", + response, + "gemini", + COMMON.SHARED_CHAT_HISTORY, + ); } else { - await this.modelChatHistory("user", response, "gemini", "agentId"); + await this.modelChatHistory( + "user", + response, + "gemini", + COMMON.SHARED_CHAT_HISTORY, + ); } return await this.currentWebView?.webview.postMessage({ type, message: response, }); } catch (error) { - Memory.set(COMMON.GEMINI_CHAT_HISTORY, []); + Memory.set(COMMON.SHARED_CHAT_HISTORY, []); console.error(error); } } @@ -75,7 +85,7 @@ export class GeminiWebViewProvider extends BaseWebViewProvider { "user", enhancedPrompt, "gemini", - "agentId", + COMMON.SHARED_CHAT_HISTORY, ); const chat = this.model.startChat({ @@ -85,7 +95,7 @@ export class GeminiWebViewProvider extends BaseWebViewProvider { const response = result.response; return response.text(); } catch (error) { - Memory.set(COMMON.GEMINI_CHAT_HISTORY, []); + Memory.set(COMMON.SHARED_CHAT_HISTORY, []); vscode.window.showErrorMessage( "Model not responding, please resend your question", ); diff --git a/src/webview-providers/groq.ts b/src/webview-providers/groq.ts index fdb5651..87f54ff 100644 --- a/src/webview-providers/groq.ts +++ b/src/webview-providers/groq.ts @@ -46,9 +46,19 @@ export class GroqWebViewProvider extends BaseWebViewProvider { try { const type = participant === "bot" ? "bot-response" : "user-input"; if (participant === "bot") { - await this.modelChatHistory("system", response, "groq", "agentId"); + await this.modelChatHistory( + "system", + response, + "groq", + COMMON.SHARED_CHAT_HISTORY, + ); } else { - await this.modelChatHistory("user", response, "groq", "agentId"); + await this.modelChatHistory( + "user", + response, + "groq", + COMMON.SHARED_CHAT_HISTORY, + ); } return await this.currentWebView?.webview.postMessage({ type, @@ -77,7 +87,7 @@ export class GroqWebViewProvider extends BaseWebViewProvider { "user", standardizedPrompt, "groq", - "agentId", + COMMON.SHARED_CHAT_HISTORY, ); const chatCompletion = this.model.chat.completions.create({ @@ -93,7 +103,7 @@ export class GroqWebViewProvider extends BaseWebViewProvider { return response ?? undefined; } catch (error) { console.error(error); - Memory.set(COMMON.GROQ_CHAT_HISTORY, []); + Memory.set(COMMON.SHARED_CHAT_HISTORY, []); vscode.window.showErrorMessage( "Model not responding, please resend your question", );