diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..79a46d15 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +ide/ diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..6c2ff60b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "githubPullRequests.ignoredPullRequestBranches": [ + "master" + ] +} \ No newline at end of file diff --git a/css/ide.css b/css/ide.css index f6432a7b..c2006812 100755 --- a/css/ide.css +++ b/css/ide.css @@ -1,50 +1,103 @@ -.judge0-file-menu { - min-width: 15rem !important; +.ai-chat-container { + background: var(--bg-color); + border-left: 1px solid #ddd; + height: 100vh; + width: 350px; + display: flex; + flex-direction: column; } -#judge0-chat-container { - display: flex; - flex-direction: column; - height: 100%; - padding: 0; +.chat-messages { + flex: 1; + overflow-y: auto; + padding: 1rem; } -#judge0-chat-container > .ui.menu { - border-bottom: none; +.chat-input { + padding: 1rem; + border-top: 1px solid #eee; + display: flex; + gap: 0.5rem; } -#judge0-chat-messages { - flex-grow: 1; - overflow-y: auto; +.ai-message, .user-message { + padding: 0.8rem; + margin: 0.5rem 0; + border-radius: 8px; + max-width: 80%; } -#judge0-chat-messages pre { - overflow-x: auto; +.user-message { + background: #007bff; + color: white; + margin-left: auto; } -.judge0-user-message { - margin-left: auto !important; - margin-right: 1em !important; - margin-top: 1em !important; - max-width: 66%; - overflow-wrap: break-word; - width: fit-content; +.ai-message { + background: #f0f0f0; + color: #333; } -#judge0-chat-input-container { - margin-top: 0; +.loading-dots::after { + content: '...'; + animation: dots 1s infinite; } -#judge0-status-line:empty { - display: none; +@keyframes dots { + 0%, 20% { content: '.'; } + 40% { content: '..'; } + 60% { content: '...'; } } -.judge0-hidden, .judge0-style-hidden { - display: none !important; -} -@media (display-mode: standalone) { - .judge0-standalone-hidden { - display: none !important; - } -} + +.ai-chat-container { + background: var(--bg-color); + border-left: 1px solid #ddd; + height: 100vh; + width: 350px; + display: flex; + flex-direction: column; + } + + .chat-messages { + flex: 1; + overflow-y: auto; + padding: 1rem; + } + + .chat-input { + padding: 1rem; + border-top: 1px solid #eee; + display: flex; + gap: 0.5rem; + } + + .ai-message, .user-message { + padding: 0.8rem; + margin: 0.5rem 0; + border-radius: 8px; + max-width: 80%; + } + + .user-message { + background: #007bff; + color: white; + margin-left: auto; + } + + .ai-message { + background: #f0f0f0; + color: #333; + } + + .loading-dots::after { + content: '...'; + animation: dots 1s infinite; + } + + @keyframes dots { + 0%, 20% { content: '.'; } + 40% { content: '..'; } + 60% { content: '...'; } + } \ No newline at end of file diff --git a/css/site.css b/css/site.css index 8e94d43f..7477f8fe 100644 --- a/css/site.css +++ b/css/site.css @@ -5,3 +5,55 @@ html, body { padding: 0; overflow: hidden; } + + +#chat-window { + position: fixed; + bottom: 20px; + right: 20px; + width: 300px; + background: #f1f1f1; + border: 1px solid #ccc; + padding: 10px; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); +} + +#chat-messages { + height: 200px; + overflow-y: auto; + margin-bottom: 10px; +} + +#chat-input { + width: 100%; + padding: 5px; + box-sizing: border-box; +} + + +/* Light Theme */ +#chat-window { + background: #f1f1f1; + border: 1px solid #ccc; + color: #000; +} + +/* Dark Theme */ +.dark-theme #chat-window { + background: #333; + border: 1px solid #555; + color: #fff; +} + +/* Inline Chat Window */ +#inline-chat-window { + background: #fff; + border: 1px solid #ccc; + color: #000; +} + +.dark-theme #inline-chat-window { + background: #444; + border: 1px solid #666; + color: #fff; +} \ No newline at end of file diff --git a/index.html b/index.html index faf35a96..349d0f30 100644 --- a/index.html +++ b/index.html @@ -21,6 +21,8 @@ } }; </script> + <script src="https://cdn.jsdelivr.net/npm/openai@4.0.0/browser/openai.min.js"></script> + <script src="js/ai.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/golden-layout/1.5.9/goldenlayout.min.js" integrity="sha256-NhJAZDfGgv4PiB+GVlSrPdh3uc75XXYSM4su8hgTchI=" crossorigin="anonymous"></script> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/golden-layout/1.5.9/css/goldenlayout-base.css" integrity="sha512-rGtzJyDE8egZ/OScuBNLW1EIhRydcfrZo14oTvZwRLzEZu+s8foCtwi0XhXsKnhuoQAwJ7POFARWdU4fBL7FbQ==" crossorigin="anonymous" referrerpolicy="no-referrer"> @@ -228,5 +230,46 @@ navigator.serviceWorker.register("sw.js").then(() => console.log("Service Worker Registered")); } </script> + + +// Here I added the chat interface +<div id="chat-window"> + <div id="chat-messages"></div> + <input id="chat-input" type="text" placeholder="Ask a question..." /> +</div> </body> </html> +<button id="optimize-btn" class="ui primary labeled icon button"> + <i class="magic icon"></i> + Optimize Code +</button> + +<div id="editor-container"> + <div id="editor"></div> + </div> + <div id="judge0-chat-container"> + <div id="judge0-chat-messages"></div> + <form id="judge0-chat-form"> + <input id="judge0-chat-user-input" type="text"> + </form> + </div> + <div id="analysis-results"></div> + + + <div class="ai-chat-container"> + <div class="chat-header"> + <h3>AI Assistant</h3> + <select id="ai-mode-select" class="ui dropdown"> + <option value="debug">Debugging Assistant</option> + <option value="code">Code Completion</option> + <option value="explain">Explanation</option> + </select> + </div> + <div class="chat-messages" id="chat-messages"></div> + <div class="chat-input"> + <textarea id="user-input" placeholder="Ask about your code..."></textarea> + <button id="send-btn" class="ui primary button"> + <i class="paper plane icon"></i> + </button> + </div> + </div> \ No newline at end of file diff --git a/js/ai.js b/js/ai.js index 7e9fe58f..ae1860f8 100644 --- a/js/ai.js +++ b/js/ai.js @@ -1,136 +1,233 @@ -"use strict"; -import theme from "./theme.js"; -import configuration from "./configuration.js"; -import { sourceEditor } from "./ide.js"; - -const THREAD = [ - { - role: "system", - content: ` -You are an AI assistant integrated into an online code editor. -Your main job is to help users with their code, but you should also be able to engage in casual conversation. - -The following are your guidelines: -1. **If the user asks for coding help**: - - Always consider the user's provided code. - - Analyze the code and provide relevant help (debugging, optimization, explanation, etc.). - - Make sure to be specific and clear when explaining things about their code. - -2. **If the user asks a casual question or makes a casual statement**: - - Engage in friendly, natural conversation. - - Do not reference the user's code unless they bring it up or ask for help. - - Be conversational and polite. - -3. **If the user's message is ambiguous or unclear**: - - Politely ask for clarification or more details to better understand the user's needs. - - If the user seems confused about something, help guide them toward what they need. - -4. **General Behavior**: - - Always respond in a helpful, friendly, and professional tone. - - Never assume the user's intent. If unsure, ask clarifying questions. - - Keep the conversation flowing naturally, even if the user hasn't directly asked about their code. - -You will always have access to the user's latest code. -Use this context only when relevant to the user's message. -If their message is unrelated to the code, focus solely on their conversational intent. - `.trim() +// Chat interface component +class AIChat { + constructor() { + this.setupEventListeners(); } -]; -document.addEventListener("DOMContentLoaded", function () { - document.getElementById("judge0-chat-form").addEventListener("submit", async function (event) { - event.preventDefault(); - - const userInput = document.getElementById("judge0-chat-user-input"); - const userInputValue = userInput.value.trim(); - if (userInputValue === "") { - return; - } - - const sendButton = document.getElementById("judge0-chat-send-button"); - - sendButton.classList.add("loading"); - userInput.disabled = true; - - const userMessage = document.createElement("div"); - userMessage.innerText = userInputValue; - userMessage.classList.add("ui", "message", "judge0-message", "judge0-user-message"); - if (!theme.isLight()) { - userMessage.classList.add("inverted"); - } - - const messages = document.getElementById("judge0-chat-messages"); - messages.appendChild(userMessage); - - userInput.value = ""; - messages.scrollTop = messages.scrollHeight; - - THREAD.push({ - role: "user", - content: ` -User's code: -${sourceEditor.getValue()} + setupEventListeners() { + document.getElementById("judge0-chat-form").addEventListener("submit", async (event) => { + event.preventDefault(); + + const userInput = document.getElementById("judge0-chat-user-input"); + const userInputValue = userInput.value.trim(); + if (userInputValue === "") { + return; + } + + const sendButton = document.getElementById("judge0-chat-send-button"); + const messages = document.getElementById("judge0-chat-messages"); + + // Disable input and show loading state + sendButton.classList.add("loading"); + userInput.disabled = true; + + // Display user message + const userMessage = document.createElement("div"); + userMessage.innerText = userInputValue; + userMessage.classList.add("ui", "message", "judge0-message", "judge0-user-message"); + if (!theme.isLight()) { + userMessage.classList.add("inverted"); + } + messages.appendChild(userMessage); + + // Clear input and scroll + userInput.value = ""; + messages.scrollTop = messages.scrollHeight; + + // Create AI message placeholder + const aiMessage = document.createElement("div"); + aiMessage.classList.add("ui", "basic", "segment", "judge0-message", "loading"); + if (!theme.isLight()) { + aiMessage.classList.add("inverted"); + } + messages.appendChild(aiMessage); + messages.scrollTop = messages.scrollHeight; + + try { + // Get current code context + const currentCode = sourceEditor.getValue(); + + // Create context-aware prompt + const prompt = `Current code:\n${currentCode}\n\nUser message: ${userInputValue}`; + + // Use Puter AI API + const aiResponse = await puter.ai.chat([{ + role: "user", + content: prompt + }], { + model: document.getElementById("judge0-chat-model-select").value, + }); + + // Process response + let aiResponseValue = typeof aiResponse === "string" ? aiResponse : aiResponse.map(v => v.text).join("\n"); + + // Display response with markdown formatting + aiMessage.innerHTML = DOMPurify.sanitize(marked.parse(aiResponseValue)); + + // Render any math expressions + renderMathInElement(aiMessage, { + delimiters: [ + { left: "\\(", right: "\\)", display: false }, + { left: "\\[", right: "\\]", display: true } + ] + }); + } catch (error) { + console.error('Chat error:', error); + aiMessage.innerText = "I encountered an error processing your request. Please try again."; + } finally { + // Reset UI state + aiMessage.classList.remove("loading"); + messages.scrollTop = messages.scrollHeight; + userInput.disabled = false; + sendButton.classList.remove("loading"); + userInput.focus(); + } + }); + } -User's message: -${userInputValue} -`.trim() + // Add keyboard shortcut for chat focus + setupKeyboardShortcuts() { + document.addEventListener("keydown", function(e) { + if ((e.metaKey || e.ctrlKey) && e.key === "p") { + if (configuration.get("appOptions.showAIAssistant")) { + e.preventDefault(); + document.getElementById("judge0-chat-user-input").focus(); + } + } }); + } +} +// Initialize chat when document is ready +document.addEventListener("DOMContentLoaded", () => { + const chat = new AIChat(); + chat.setupKeyboardShortcuts(); +}); - const aiMessage = document.createElement("div"); - aiMessage.classList.add("ui", "basic", "segment", "judge0-message", "loading"); - if (!theme.isLight()) { - aiMessage.classList.add("inverted"); - } - messages.appendChild(aiMessage); - messages.scrollTop = messages.scrollHeight; - const aiResponse = await puter.ai.chat(THREAD, { - model: document.getElementById("judge0-chat-model-select").value, - }); - let aiResponseValue = aiResponse.toString(); - if (typeof aiResponseValue !== "string") { - aiResponseValue = aiResponseValue.map(v => v.text).join("\n"); - } - - THREAD.push({ - role: "assistant", - content: aiResponseValue - }); - - aiMessage.innerHTML = DOMPurify.sanitize(aiResponseValue); - renderMathInElement(aiMessage, { - delimiters: [ - { left: "\\(", right: "\\)", display: false }, - { left: "\\[", right: "\\]", display: true } - ] - }); - aiMessage.innerHTML = marked.parse(aiMessage.innerHTML); +// Configure Monaco Editor with inline suggestions +require(["vs/editor/editor.main"], function() { + // Register inline suggestions provider for all languages + monaco.languages.registerInlineCompletionsProvider('*', { + provideInlineCompletions: async (model, position, context) => { + if (!puter.auth.isSignedIn() || + !document.getElementById("judge0-inline-suggestions").checked || + !configuration.get("appOptions.showAIAssistant")) { + return; + } + + // Get text before and after cursor for context + const textBeforeCursor = model.getValueInRange({ + startLineNumber: 1, + startColumn: 1, + endLineNumber: position.lineNumber, + endColumn: position.column + }); + + const textAfterCursor = model.getValueInRange({ + startLineNumber: position.lineNumber, + startColumn: position.column, + endLineNumber: model.getLineCount(), + endColumn: model.getLineMaxColumn(model.getLineCount()) + }); + + try { + // Get AI suggestion using Puter API + const aiResponse = await puter.ai.chat([{ + role: "user", + content: `You are a code completion assistant. Given the following context, generate the most likely code completion. + + ### Code Before Cursor: + ${textBeforeCursor} + + ### Code After Cursor: + ${textAfterCursor} + + ### Instructions: + - Predict the next logical code segment + - Ensure the suggestion is syntactically and contextually correct + - Keep the completion concise and relevant + - Do not repeat existing code + - Provide only the missing code + - **Respond with only the code, without markdown formatting** + - **Do not include triple backticks (\`\`\`) or additional explanations** + + ### Completion:`.trim() + }], { + model: document.getElementById("judge0-chat-model-select").value, + }); + + // Process the response + let aiResponseValue = aiResponse?.toString().trim() || ""; + if (Array.isArray(aiResponseValue)) { + aiResponseValue = aiResponseValue.map(v => v.text).join("\n").trim(); + } - aiMessage.classList.remove("loading"); - messages.scrollTop = messages.scrollHeight; + if (!aiResponseValue || aiResponseValue.length === 0) { + return; + } - userInput.disabled = false; - sendButton.classList.remove("loading"); - userInput.focus(); + // Return the suggestion + return { + items: [{ + insertText: aiResponseValue, + range: { + startLineNumber: position.lineNumber, + startColumn: position.column, + endLineNumber: position.lineNumber, + endColumn: position.column + } + }] + }; + } catch (error) { + console.error('Inline suggestion error:', error); + return null; + } + }, + + // Required but can be empty for basic implementation + handleItemDidShow: () => {}, + handleItemDidHide: () => {}, + freeInlineCompletions: () => {} }); - document.getElementById("judge0-chat-model-select").addEventListener("change", function () { - const userInput = document.getElementById("judge0-chat-user-input"); - userInput.placeholder = `Message ${this.value}`; - }); + // Add UI toggle for inline suggestions + const settingsContainer = document.querySelector('.settings-container'); + if (settingsContainer) { + const toggleDiv = document.createElement('div'); + toggleDiv.className = 'ui toggle checkbox'; + toggleDiv.innerHTML = ` + <input type="checkbox" id="judge0-inline-suggestions" checked> + <label>Enable AI inline suggestions</label> + `; + settingsContainer.appendChild(toggleDiv); + } }); -document.addEventListener("keydown", function (e) { - if (e.metaKey || e.ctrlKey) { - switch (e.key) { - case "p": - if (!configuration.get("appOptions.showAIAssistant")) { - break; - } - e.preventDefault(); - document.getElementById("judge0-chat-user-input").focus(); - break; - } +// Update the editor configuration with inline suggestions enabled +const editorConfig = { + value: '// Start coding here...', + language: 'javascript', + theme: 'vs-dark', + automaticLayout: true, + inlineSuggest: { + enabled: true, + mode: 'subword' + }, + // Other existing editor options... + minimap: { + enabled: true } -}); +}; + +// Create editor with updated configuration +sourceEditor = monaco.editor.create(container.getElement()[0], editorConfig); + +// Add keyboard shortcut for accepting suggestions +sourceEditor.addCommand(monaco.KeyMod.Alt | monaco.KeyCode.Tab, () => { + // Accept the current inline suggestion if present + const controller = sourceEditor.getContribution('editor.contrib.inlineSuggestionController'); + if (controller) { + controller.accept(); + } +}); \ No newline at end of file diff --git a/js/ide.js b/js/ide.js index 448f9def..8044dc89 100755 --- a/js/ide.js +++ b/js/ide.js @@ -181,6 +181,17 @@ async function getSelectedLanguage() { return getLanguage(getSelectedLanguageFlavor(), getSelectedLanguageId()) } +// addded error handling here: + +async function handleCompilationError(errorMessage) { + const prompt = `The following code has an error: ${errorMessage}. Suggest a fix.`; + const fix = await sendChatMessage(prompt); + document.getElementById('chat-messages').innerHTML += `<div><strong>AI:</strong> ${fix}</div>`; +} + + + + function getSelectedLanguageId() { return parseInt($selectLanguage.val()); } @@ -941,3 +952,34 @@ const EXTENSIONS_TABLE = { function getLanguageForExtension(extension) { return EXTENSIONS_TABLE[extension] || { "flavor": CE, "language_id": 43 }; // Plain Text (https://ce.judge0.com/languages/43) } + + + +require.config({ paths: { 'vs': 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.44.0/min/vs' }}); +require(['vs/editor/editor.main'], function() { + const editor = monaco.editor.create(document.getElementById('editor'), { + value: '// Start coding here...', + language: 'javascript', + theme: 'vs-dark', + automaticLayout: true, + suggest: { + showWords: true, + showSnippets: true, + showClasses: true, + showFunctions: true + } + }); + + // Example: Add AI-driven autocomplete + editor.addAction({ + id: 'ai-autocomplete', + label: 'AI Autocomplete', + keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.Space], + run: async function(editor) { + const code = editor.getValue(); + const prompt = `Complete the following code:\n${code}`; + const completion = await sendChatMessage(prompt); + editor.setValue(code + '\n' + completion); + } + }); +}); \ No newline at end of file