From 0cde93ef07c9aeb2f01e57dc5679e5d4ac38f1aa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 8 Nov 2025 20:56:24 +0000 Subject: [PATCH 01/24] Initial plan From 38013a8923ec1d3ce639ea5748f79da47a1ad184 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 8 Nov 2025 21:11:44 +0000 Subject: [PATCH 02/24] Add AI Coding Agent integration to Editor - Added IPC channels for AI coding agent operations (generate, edit, explain, fix) - Created AI coding agent handlers using existing LLM infrastructure - Integrated AI Coding Agent panel as a new tab in Build Output area - Added e2e tests for AI Coding Agent functionality - Updated preload to expose aiCodingAgent API to renderer Co-authored-by: anikitenko <12380460+anikitenko@users.noreply.github.com> --- playwright.config.js | 2 +- src/components/editor/AiCodingAgentPanel.jsx | 292 ++++++++++++++++++ .../editor/AiCodingAgentPanel.module.css | 79 +++++ .../editor/BuildOutputTerminalComponent.js | 9 +- src/components/editor/EditorPage.jsx | 8 +- src/ipc/ai_coding_agent.js | 268 ++++++++++++++++ src/ipc/channels.js | 12 + src/main.js | 2 + src/preload.js | 18 +- test-results/.last-run.json | 7 +- tests/e2e/ai-coding-agent.spec.js | 170 ++++++++++ 11 files changed, 859 insertions(+), 8 deletions(-) create mode 100644 src/components/editor/AiCodingAgentPanel.jsx create mode 100644 src/components/editor/AiCodingAgentPanel.module.css create mode 100644 src/ipc/ai_coding_agent.js create mode 100644 tests/e2e/ai-coding-agent.spec.js diff --git a/playwright.config.js b/playwright.config.js index 053f526..b7fa3e5 100644 --- a/playwright.config.js +++ b/playwright.config.js @@ -3,7 +3,7 @@ const config = { testDir: 'tests/e2e', workers: 1, // Run tests sequentially - testMatch: ['tests/e2e/snapshots.e2e.spec.js'], + testMatch: ['tests/e2e/snapshots.e2e.spec.js', 'tests/e2e/ai-coding-agent.spec.js'], }; module.exports = config; diff --git a/src/components/editor/AiCodingAgentPanel.jsx b/src/components/editor/AiCodingAgentPanel.jsx new file mode 100644 index 0000000..155164f --- /dev/null +++ b/src/components/editor/AiCodingAgentPanel.jsx @@ -0,0 +1,292 @@ +import React, { useState, useEffect, useRef } from "react"; +import { + Button, + Card, + TextArea, + FormGroup, + HTMLSelect, + Tag, + Spinner, + NonIdealState, +} from "@blueprintjs/core"; +import * as styles from "./AiCodingAgentPanel.module.css"; +import Markdown from "markdown-to-jsx"; + +const AI_ACTIONS = [ + { label: "Generate Code", value: "generate" }, + { label: "Edit Code", value: "edit" }, + { label: "Explain Code", value: "explain" }, + { label: "Fix Code", value: "fix" }, +]; + +export default function AiCodingAgentPanel({ codeEditor, editorModelPath }) { + const [action, setAction] = useState("generate"); + const [prompt, setPrompt] = useState(""); + const [isLoading, setIsLoading] = useState(false); + const [response, setResponse] = useState(""); + const [error, setError] = useState(null); + const [streamingRequestId, setStreamingRequestId] = useState(null); + const responseRef = useRef(""); + + useEffect(() => { + const handleStreamDelta = (data) => { + if (data.requestId === streamingRequestId && data.type === "content") { + responseRef.current += data.content; + setResponse(responseRef.current); + } + }; + + const handleStreamDone = (data) => { + if (data.requestId === streamingRequestId) { + setIsLoading(false); + setStreamingRequestId(null); + } + }; + + const handleStreamError = (data) => { + if (data.requestId === streamingRequestId) { + setError(data.error); + setIsLoading(false); + setStreamingRequestId(null); + } + }; + + window.electron.aiCodingAgent.on.streamDelta(handleStreamDelta); + window.electron.aiCodingAgent.on.streamDone(handleStreamDone); + window.electron.aiCodingAgent.on.streamError(handleStreamError); + + return () => { + window.electron.aiCodingAgent.off.streamDelta(handleStreamDelta); + window.electron.aiCodingAgent.off.streamDone(handleStreamDone); + window.electron.aiCodingAgent.off.streamError(handleStreamError); + }; + }, [streamingRequestId]); + + const getSelectedCode = () => { + if (!codeEditor) return ""; + const selection = codeEditor.getSelection(); + const model = codeEditor.getModel(); + if (!model) return ""; + return model.getValueInRange(selection); + }; + + const getLanguage = () => { + if (!codeEditor) return ""; + const model = codeEditor.getModel(); + if (!model) return ""; + return model.getLanguageId(); + }; + + const getContext = () => { + if (!codeEditor) return ""; + const model = codeEditor.getModel(); + if (!model) return ""; + return model.getValue(); + }; + + const handleSubmit = async () => { + if (!prompt.trim()) return; + + setIsLoading(true); + setError(null); + setResponse(""); + responseRef.current = ""; + + try { + const selectedCode = getSelectedCode(); + const language = getLanguage(); + const context = action === "generate" ? getContext() : ""; + + let result; + switch (action) { + case "generate": + result = await window.electron.aiCodingAgent.generateCode({ + prompt, + language, + context, + }); + break; + case "edit": + if (!selectedCode) { + setError("Please select code to edit"); + setIsLoading(false); + return; + } + result = await window.electron.aiCodingAgent.editCode({ + code: selectedCode, + instruction: prompt, + language, + }); + break; + case "explain": + if (!selectedCode) { + setError("Please select code to explain"); + setIsLoading(false); + return; + } + result = await window.electron.aiCodingAgent.explainCode({ + code: selectedCode, + language, + }); + break; + case "fix": + if (!selectedCode) { + setError("Please select code to fix"); + setIsLoading(false); + return; + } + result = await window.electron.aiCodingAgent.fixCode({ + code: selectedCode, + error: prompt, + language, + }); + break; + default: + break; + } + + if (result && result.requestId) { + setStreamingRequestId(result.requestId); + } else if (result && result.error) { + setError(result.error); + setIsLoading(false); + } + } catch (err) { + setError(err.message || "An error occurred"); + setIsLoading(false); + } + }; + + const insertCodeIntoEditor = () => { + if (!codeEditor || !response) return; + + const selection = codeEditor.getSelection(); + const model = codeEditor.getModel(); + if (!model) return; + + // Extract code from markdown code blocks if present + const codeBlockMatch = response.match(/```[\w]*\n([\s\S]*?)\n```/); + const codeToInsert = codeBlockMatch ? codeBlockMatch[1] : response; + + const edit = { + range: selection, + text: codeToInsert, + forceMoveMarkers: true, + }; + + model.pushEditOperations([], [edit], () => null); + codeEditor.focus(); + }; + + const clearResponse = () => { + setResponse(""); + responseRef.current = ""; + setError(null); + }; + + return ( +
+
+

AI Coding Agent

+ + Beta + +
+ +
+ + setAction(e.target.value)} + options={AI_ACTIONS} + fill + /> + + + +