diff --git a/package-lock.json b/package-lock.json index 2640ae0..c2a7fe8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "wing-man", - "version": "0.7.0", + "version": "0.7.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "wing-man", - "version": "0.7.0", + "version": "0.7.4", "license": "MIT", "workspaces": [ "docs-site", @@ -23,6 +23,7 @@ "@langchain/openai": "0.3.0", "ignore": "5.2.0", "langchain": "0.3.2", + "node-cache": "^5.1.2", "tree-sitter": "0.21.1", "vectra": "0.9.0", "vscode-languageclient": "9.0.1", @@ -14352,6 +14353,25 @@ "node": "^18 || ^20 || >= 21" } }, + "node_modules/node-cache": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz", + "integrity": "sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==", + "dependencies": { + "clone": "2.x" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/node-cache/node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "engines": { + "node": ">=0.8" + } + }, "node_modules/node-domexception": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", diff --git a/package.json b/package.json index 7d01ec3..b54a3b0 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "wing-man", "displayName": "Wingman-AI", "description": "Wingman - AI powered assistant to help you write your best code, we won't leave you hanging.", - "version": "0.7.4", + "version": "0.7.5", "publisher": "WingMan", "license": "MIT", "workspaces": [ @@ -176,39 +176,40 @@ "docs": "cd docs-site && npm run dev" }, "dependencies": { - "@langchain/langgraph": "0.2.8", - "@langchain/community": "0.3.1", - "@langchain/openai": "0.3.0", + "@ast-grep/napi": "0.27.1", "@langchain/anthropic": "0.3.1", - "@langchain/ollama": "0.1.0", + "@langchain/community": "0.3.1", "@langchain/core": "0.3.3", + "@langchain/langgraph": "0.2.8", + "@langchain/ollama": "0.1.0", + "@langchain/openai": "0.3.0", + "ignore": "5.2.0", + "langchain": "0.3.2", + "node-cache": "^5.1.2", + "tree-sitter": "0.21.1", + "vectra": "0.9.0", "vscode-languageclient": "9.0.1", "vscode-languageserver": "9.0.1", "vscode-languageserver-textdocument": "1.0.12", "vscode-uri": "3.0.8", - "@ast-grep/napi": "0.27.1", - "langchain": "0.3.2", - "vectra": "0.9.0", - "zod-to-json-schema": "3.23.3", - "ignore": "5.2.0", "zod": "3.23.8", - "tree-sitter": "0.21.1" + "zod-to-json-schema": "3.23.3" }, "devDependencies": { + "@ast-grep/cli": "0.27.1", + "@rsbuild/core": "1.0.6", + "@rsbuild/plugin-react": "1.0.2", "@types/mocha": "10.0.8", "@types/node": "22.5.5", "@types/vscode": "1.93.0", "@types/vscode-webview": "1.57.5", "@vscode/test-cli": "0.0.10", "@vscode/test-electron": "2.4.1", + "adm-zip": "0.5.16", "generate-license-file": "3.5.1", - "rimraf": "^6.0.1", - "typescript": "^5.6.2", - "@rsbuild/core": "1.0.6", - "@rsbuild/plugin-react": "1.0.2", - "@ast-grep/cli": "0.27.1", "node-fetch": "3.3.2", - "adm-zip": "0.5.16", - "tar": "7.4.3" + "rimraf": "^6.0.1", + "tar": "7.4.3", + "typescript": "^5.6.2" } } diff --git a/src/providers/codeSuggestionProvider.ts b/src/providers/codeSuggestionProvider.ts index 3ff3d52..69f864d 100644 --- a/src/providers/codeSuggestionProvider.ts +++ b/src/providers/codeSuggestionProvider.ts @@ -1,4 +1,3 @@ -import * as vscode from "vscode"; import { CancellationToken, InlineCompletionContext, @@ -14,14 +13,23 @@ import { getContentWindow } from "../service/utils/contentWindow"; import { InteractionSettings } from "@shared/types/Settings"; import { getSymbolsFromOpenFiles, supportedLanguages } from "./utilities"; import { getClipboardHistory } from "./clipboardTracker"; +import NodeCache from "node-cache"; +import { loggingProvider } from "./loggingProvider"; export class CodeSuggestionProvider implements InlineCompletionItemProvider { public static readonly selector = supportedLanguages; + private cache: NodeCache; constructor( private readonly _aiProvider: AIProvider | AIStreamProvicer, private readonly _interactionSettings: InteractionSettings - ) {} + ) { + this.cache = new NodeCache({ + stdTTL: 300, + maxKeys: 100, + checkperiod: 120, + }); + } async provideInlineCompletionItems( document: TextDocument, @@ -73,6 +81,10 @@ export class CodeSuggestionProvider implements InlineCompletionItemProvider { } } + private generateCacheKey(prefix: string, suffix: string): string { + return `${prefix.slice(-100)}:${suffix.slice(0, 100)}`; + } + async bouncedRequest( prefix: string, signal: AbortSignal, @@ -81,27 +93,42 @@ export class CodeSuggestionProvider implements InlineCompletionItemProvider { additionalContext?: string ): Promise { try { - console.log("Clipboard", getClipboardHistory().join("\n\n")); eventEmitter._onQueryStart.fire(); + const cacheKey = this.generateCacheKey( + prefix.trim(), + suffix.trim() + ); + const cachedResult = this.cache.get(cacheKey); + + if (cachedResult) { + loggingProvider.logInfo( + "Code complete - Serving from query cache" + ); + return [new InlineCompletionItem(cachedResult)]; + } + + let result: string; + if ("codeCompleteStream" in this._aiProvider && streaming) { - const codeStream = await this._aiProvider.codeCompleteStream( + result = await this._aiProvider.codeCompleteStream( prefix, suffix, signal, additionalContext, getClipboardHistory().join("\n\n") ); - return [new InlineCompletionItem(codeStream)]; } else { - const codeResponse = await this._aiProvider.codeComplete( + result = await this._aiProvider.codeComplete( prefix, suffix, signal, additionalContext, getClipboardHistory().join("\n\n") ); - return [new InlineCompletionItem(codeResponse)]; } + + this.cache.set(cacheKey, result); + return [new InlineCompletionItem(result)]; } catch (error) { return []; } finally { diff --git a/views-ui/src/Chat/SkeletonLoader.tsx b/views-ui/src/Chat/SkeletonLoader.tsx new file mode 100644 index 0000000..a53321b --- /dev/null +++ b/views-ui/src/Chat/SkeletonLoader.tsx @@ -0,0 +1,7 @@ +export const SkeletonLoader = () => { + return ( +
+
+
+ ); +}; diff --git a/views-ui/src/Chat/features/Chat/ChatEntry.tsx b/views-ui/src/Chat/features/Chat/ChatEntry.tsx index 4ff26b0..8b782cf 100644 --- a/views-ui/src/Chat/features/Chat/ChatEntry.tsx +++ b/views-ui/src/Chat/features/Chat/ChatEntry.tsx @@ -11,6 +11,7 @@ import { ChatMessage } from "@shared/types/Message"; import { vscode } from "../../utilities/vscode"; import { useAppContext } from "../../context"; import React from "react"; +import { SkeletonLoader } from "../../SkeletonLoader"; type MarkDownObject = { props: { @@ -202,12 +203,4 @@ const ChatEntry = ({ ); }; -const SkeletonLoader = () => { - return ( -
-
-
- ); -}; - export default ChatEntry; diff --git a/views-ui/src/Chat/features/Chat/ChatInput.tsx b/views-ui/src/Chat/features/Chat/ChatInput.tsx index 0777a57..a0f2dff 100644 --- a/views-ui/src/Chat/features/Chat/ChatInput.tsx +++ b/views-ui/src/Chat/features/Chat/ChatInput.tsx @@ -2,7 +2,7 @@ import { FaPlay, FaStopCircle } from "react-icons/fa"; import { useAppContext } from "../../context"; import { useAutoFocus } from "../../hooks/useAutoFocus"; import { useOnScreen } from "../../hooks/useOnScreen"; -import { useEffect } from "react"; +import { useEffect, useState } from "react"; interface ChatInputProps { onChatSubmitted: (input: string) => void; @@ -17,6 +17,7 @@ const ChatInput = ({ }: ChatInputProps) => { const [ref, isVisible] = useOnScreen(); const { isLightTheme } = useAppContext(); + const [inputValue, setInputValue] = useState(""); const chatInputBox = useAutoFocus(); useEffect(() => { @@ -30,23 +31,13 @@ const ChatInput = ({ : "bg-stone-800 text-white border-stone-700"; const handleUserInput = (e: React.KeyboardEvent) => { - if (e.key === "Enter") { - if (e.shiftKey) { - return; - } - - const element = e.target as HTMLInputElement; - const message = element.value; - - if (!message) { - return; - } + if (!inputValue.trim()) return; + if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); - onChatSubmitted(message); - - element.value = ""; + onChatSubmitted(inputValue); + chatInputBox.current!.value = ""; } }; @@ -65,7 +56,7 @@ const ChatInput = ({