| | <!DOCTYPE html> |
| | <html lang="es"> |
| | <head> |
| | <meta charset="UTF-8"> |
| | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| | <title>DeepSeek IDE - Live Code Editor</title> |
| | <script src="https://cdn.tailwindcss.com"></script> |
| | <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/codemirror.min.css"> |
| | <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/theme/dracula.min.css"> |
| | <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
| | <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/codemirror.min.js"></script> |
| | |
| | <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/javascript/javascript.min.js"></script> |
| | <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/python/python.min.js"></script> |
| | <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/htmlmixed/htmlmixed.min.js"></script> |
| | <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/css/css.min.js"></script> |
| | <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/xml/xml.min.js"></script> |
| | <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/clike/clike.min.js"></script> |
| | <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/php/php.min.js"></script> |
| | <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/ruby/ruby.min.js"></script> |
| | <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/shell/shell.min.js"></script> |
| | <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/sql/sql.min.js"></script> |
| | <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/swift/swift.min.js"></script> |
| | <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/go/go.min.js"></script> |
| | <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/rust/rust.min.js"></script> |
| | <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/addon/edit/matchbrackets.min.js"></script> |
| | <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/addon/edit/closebrackets.min.js"></script> |
| | <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/addon/comment/comment.min.js"></script> |
| | <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/addon/display/placeholder.min.js"></script> |
| | <script src="https://cdnjs.cloudflare.com/ajax/libs/jsdiff/5.0.0/diff.min.js"></script> |
| | <style> |
| | .cm-s-dracula .CodeMirror-activeline-background { background: rgba(255, 255, 255, 0.1) !important; } |
| | .diff-added { background-color: rgba(40, 167, 69, 0.2); } |
| | .diff-removed { background-color: rgba(220, 53, 69, 0.2); } |
| | .diff-unchanged { opacity: 0.7; } |
| | .CodeMirror { height: 100% !important; font-size: 14px; } |
| | .resize-handle { width: 4px; background: rgba(156, 163, 175, 0.3); cursor: col-resize; } |
| | .resize-handle:hover { background: rgba(156, 163, 175, 0.6); } |
| | #output-container { scrollbar-width: thin; } |
| | #output-container::-webkit-scrollbar { width: 6px; } |
| | #output-container::-webkit-scrollbar-track { background: rgba(0, 0, 0, 0.1); } |
| | #output-container::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.2); } |
| | .tab-active { border-bottom: 2px solid #3b82f6; color: white !important; } |
| | .CodeMirror pre { padding: 0 8px; } |
| | .file-tab { position: relative; } |
| | .file-tab.active { background-color: rgba(59, 130, 246, 0.2); } |
| | .file-tab-close { visibility: hidden; } |
| | .file-tab:hover .file-tab-close { visibility: visible; } |
| | .history-item:hover { background-color: rgba(55, 65, 81, 0.5); } |
| | .prompt-container { transition: all 0.3s ease; } |
| | .prompt-container.collapsed { height: 40px; overflow: hidden; } |
| | .prompt-toggle { transform: rotate(0deg); transition: transform 0.3s ease; } |
| | .prompt-toggle.collapsed { transform: rotate(180deg); } |
| | .apply-diff-btn { transition: all 0.2s ease; } |
| | .apply-diff-btn:hover { transform: scale(1.05); } |
| | .code-block { position: relative; } |
| | .code-block-copy { position: absolute; top: 0.5rem; right: 0.5rem; opacity: 0; transition: opacity 0.2s ease; } |
| | .code-block:hover .code-block-copy { opacity: 1; } |
| | .ai-cursor { position: absolute; background-color: rgba(59, 130, 246, 0.3); border-left: 2px solid #3b82f6; } |
| | .ai-cursor-label { position: absolute; background-color: #3b82f6; color: white; font-size: 0.75rem; padding: 0.1rem 0.3rem; border-radius: 0.25rem; top: -1.25rem; left: -2px; white-space: nowrap; } |
| | .typewriter-cursor { position: absolute; width: 2px; background-color: #3b82f6; animation: blink 1s infinite; } |
| | @keyframes blink { |
| | 0%, 100% { opacity: 1; } |
| | 50% { opacity: 0; } |
| | } |
| | </style> |
| | </head> |
| | <body class="bg-gray-900 text-gray-100 h-screen flex flex-col overflow-hidden"> |
| | |
| | <header class="bg-gray-800 px-4 py-3 flex items-center justify-between border-b border-gray-700"> |
| | <div class="flex items-center space-x-2"> |
| | <i class="fas fa-code text-blue-400 text-xl"></i> |
| | <h1 class="text-xl font-bold">DeepSeek IDE</h1> |
| | </div> |
| | <div class="flex items-center space-x-4"> |
| | <div class="relative"> |
| | <input type="password" id="api-key" placeholder="DeepSeek API Key" |
| | class="bg-gray-700 px-3 py-1 rounded text-sm w-64 focus:outline-none focus:ring-1 focus:ring-blue-500"> |
| | <button id="save-key-btn" class="absolute right-2 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-blue-400"> |
| | <i class="fas fa-save"></i> |
| | </button> |
| | </div> |
| | <button id="new-file-btn" class="text-gray-400 hover:text-blue-400 px-2 py-1 rounded text-sm flex items-center"> |
| | <i class="fas fa-plus mr-1"></i> |
| | <span>New</span> |
| | </button> |
| | <button id="analyze-btn" class="bg-blue-600 hover:bg-blue-700 px-3 py-1 rounded text-sm flex items-center space-x-1"> |
| | <i class="fas fa-play"></i> |
| | <span>Analyze</span> |
| | </button> |
| | </div> |
| | </header> |
| |
|
| | |
| | <div id="prompt-container" class="prompt-container bg-gray-800 border-b border-gray-700 px-4 py-2"> |
| | <div class="flex justify-between items-center cursor-pointer" id="prompt-toggle"> |
| | <h3 class="text-sm font-medium flex items-center"> |
| | <i class="fas fa-comment-dots mr-2 text-blue-400"></i> |
| | Custom Prompt |
| | </h3> |
| | <i class="fas fa-chevron-up prompt-toggle text-gray-400"></i> |
| | </div> |
| | <div class="mt-2"> |
| | <textarea id="custom-prompt" placeholder="Describe what changes you want to make to the code (e.g. 'Optimize this function', 'Fix bugs', 'Explain how it works')..." |
| | class="w-full bg-gray-700 rounded px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-blue-500"></textarea> |
| | </div> |
| | </div> |
| |
|
| | |
| | <div class="flex flex-1 overflow-hidden"> |
| | |
| | <div class="flex-1 flex flex-col border-r border-gray-700 relative"> |
| | <div class="bg-gray-800 px-3 py-2 flex items-center justify-between"> |
| | <div class="flex items-center space-x-2 overflow-x-auto"> |
| | |
| | </div> |
| | <div class="flex items-center space-x-2"> |
| | <select id="language-selector" class="bg-gray-700 text-sm rounded px-2 py-1"> |
| | <option value="javascript">JavaScript</option> |
| | <option value="python">Python</option> |
| | <option value="htmlmixed">HTML</option> |
| | <option value="css">CSS</option> |
| | <option value="text/x-java">Java</option> |
| | <option value="text/x-csrc">C</option> |
| | <option value="text/x-c++src">C++</option> |
| | <option value="text/x-csharp">C#</option> |
| | <option value="text/x-go">Go</option> |
| | <option value="text/x-rustsrc">Rust</option> |
| | <option value="text/x-swift">Swift</option> |
| | <option value="text/x-php">PHP</option> |
| | <option value="text/x-ruby">Ruby</option> |
| | <option value="text/x-sql">SQL</option> |
| | <option value="text/x-sh">Shell</option> |
| | </select> |
| | <button id="format-btn" class="text-gray-400 hover:text-blue-400 text-sm"> |
| | <i class="fas fa-align-left mr-1"></i>Format |
| | </button> |
| | <button id="clear-btn" class="text-gray-400 hover:text-red-400 text-sm"> |
| | <i class="fas fa-trash-alt mr-1"></i>Clear |
| | </button> |
| | </div> |
| | </div> |
| | <div id="editor" class="flex-1"></div> |
| | <div id="ai-cursor-container"></div> |
| | </div> |
| |
|
| | |
| | <div class="resize-handle"></div> |
| |
|
| | |
| | <div class="w-1/3 flex flex-col bg-gray-800"> |
| | <div class="bg-gray-800 px-3 py-2 flex border-b border-gray-700"> |
| | <button data-tab="diffs" class="tab-active px-3 py-1 text-sm font-medium"> |
| | <i class="fas fa-code-compare mr-1"></i>Diffs |
| | </button> |
| | <button data-tab="response" class="px-3 py-1 text-sm font-medium text-gray-400 hover:text-white"> |
| | <i class="fas fa-robot mr-1"></i>AI Response |
| | </button> |
| | <button data-tab="history" class="px-3 py-1 text-sm font-medium text-gray-400 hover:text-white"> |
| | <i class="fas fa-history mr-1"></i>History |
| | </button> |
| | <button data-tab="console" class="px-3 py-1 text-sm font-medium text-gray-400 hover:text-white"> |
| | <i class="fas fa-terminal mr-1"></i>Console |
| | </button> |
| | </div> |
| | <div id="output-container" class="flex-1 overflow-auto p-4"> |
| | <div id="diff-container" class="tab-content active"> |
| | <div class="text-center text-gray-500 py-8"> |
| | <i class="fas fa-code-compare text-3xl mb-2"></i> |
| | <p>Make changes to your code to see the differences here</p> |
| | </div> |
| | </div> |
| | <div id="response-container" class="tab-content hidden"> |
| | <div class="text-center text-gray-500 py-8"> |
| | <i class="fas fa-robot text-3xl mb-2"></i> |
| | <p>Click "Analyze" to get AI feedback on your code</p> |
| | </div> |
| | </div> |
| | <div id="history-container" class="tab-content hidden"> |
| | <div class="text-center text-gray-500 py-8"> |
| | <i class="fas fa-history text-3xl mb-2"></i> |
| | <p>Your analysis history will appear here</p> |
| | </div> |
| | </div> |
| | <div id="console-output" class="tab-content hidden"> |
| | <div class="text-center text-gray-500 py-8"> |
| | <i class="fas fa-terminal text-3xl mb-2"></i> |
| | <p>Console output will appear here</p> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| |
|
| | <script> |
| | // File system and state management |
| | const state = { |
| | files: { |
| | 'script.js': { |
| | content: `// Ejemplo de función en JavaScript |
| | function saludar(nombre) { |
| | return \`Hola, \${nombre}!\`; |
| | } |
| | |
| | console.log(saludar("Mundo"));`, |
| | language: 'javascript', |
| | lastContent: '' |
| | } |
| | }, |
| | currentFile: 'script.js', |
| | history: [], |
| | apiKey: localStorage.getItem('deepseek-api-key') || '', |
| | promptCollapsed: localStorage.getItem('prompt-collapsed') === 'true', |
| | proposedChanges: null, |
| | isApplyingChanges: false, |
| | typewriterInterval: null |
| | }; |
| | |
| | // Language mode mapping |
| | const languageModes = { |
| | 'javascript': 'javascript', |
| | 'python': 'python', |
| | 'html': 'htmlmixed', |
| | 'css': 'css', |
| | 'java': 'text/x-java', |
| | 'c': 'text/x-csrc', |
| | 'cpp': 'text/x-c++src', |
| | 'csharp': 'text/x-csharp', |
| | 'go': 'text/x-go', |
| | 'rust': 'text/x-rustsrc', |
| | 'swift': 'text/x-swift', |
| | 'php': 'text/x-php', |
| | 'ruby': 'text/x-ruby', |
| | 'sql': 'text/x-sql', |
| | 'sh': 'text/x-sh', |
| | 'htmlmixed': 'htmlmixed' |
| | }; |
| | |
| | // Initialize CodeMirror editor |
| | const editor = CodeMirror(document.getElementById('editor'), { |
| | mode: 'javascript', |
| | theme: 'dracula', |
| | lineNumbers: true, |
| | indentUnit: 4, |
| | tabSize: 4, |
| | lineWrapping: true, |
| | autoCloseBrackets: true, |
| | matchBrackets: true, |
| | extraKeys: { |
| | 'Ctrl-Enter': analyzeCode, |
| | 'Cmd-Enter': analyzeCode, |
| | 'Ctrl-/': 'toggleComment', |
| | 'Cmd-/': 'toggleComment', |
| | 'Shift-Ctrl-F': formatCode, |
| | 'Shift-Cmd-F': formatCode |
| | }, |
| | placeholder: '// Escribe tu código aquí...\n// Presiona Ctrl+Enter o Cmd+Enter para analizar' |
| | }); |
| | |
| | // Initialize the UI |
| | function initUI() { |
| | // Load API key if exists |
| | document.getElementById('api-key').value = state.apiKey; |
| | |
| | // Set up file tabs |
| | renderFileTabs(); |
| | loadFile(state.currentFile); |
| | |
| | // Set up language selector |
| | document.getElementById('language-selector').value = state.files[state.currentFile].language; |
| | |
| | // Set up initial diff |
| | updateDiff(); |
| | |
| | // Set up prompt toggle |
| | setupPromptToggle(); |
| | |
| | // Load custom prompt if exists |
| | const savedPrompt = localStorage.getItem('custom-prompt'); |
| | if (savedPrompt) { |
| | document.getElementById('custom-prompt').value = savedPrompt; |
| | } |
| | } |
| | |
| | // File management functions |
| | function renderFileTabs() { |
| | const tabsContainer = document.querySelector('.bg-gray-800 > div:first-child'); |
| | tabsContainer.innerHTML = ''; |
| | |
| | Object.keys(state.files).forEach(filename => { |
| | const isActive = filename === state.currentFile; |
| | const tab = document.createElement('div'); |
| | tab.className = `file-tab flex items-center px-3 py-1 rounded-t text-sm cursor-pointer mr-1 ${isActive ? 'active' : ''}`; |
| | tab.innerHTML = ` |
| | <span>${filename}</span> |
| | <button class="file-tab-close ml-2 text-gray-400 hover:text-red-400" data-file="${filename}"> |
| | <i class="fas fa-times text-xs"></i> |
| | </button> |
| | `; |
| | |
| | tab.addEventListener('click', () => switchFile(filename)); |
| | |
| | const closeBtn = tab.querySelector('.file-tab-close'); |
| | closeBtn.addEventListener('click', (e) => { |
| | e.stopPropagation(); |
| | closeFile(filename); |
| | }); |
| | |
| | tabsContainer.appendChild(tab); |
| | }); |
| | } |
| | |
| | function switchFile(filename) { |
| | if (filename === state.currentFile) return; |
| | |
| | // Save current file content |
| | state.files[state.currentFile].content = editor.getValue(); |
| | |
| | // Switch to new file |
| | state.currentFile = filename; |
| | loadFile(filename); |
| | |
| | // Update UI |
| | renderFileTabs(); |
| | document.getElementById('language-selector').value = state.files[filename].language; |
| | } |
| | |
| | function loadFile(filename) { |
| | const file = state.files[filename]; |
| | editor.setValue(file.content); |
| | |
| | // Set the editor mode based on file extension |
| | const mode = languageModes[file.language] || 'javascript'; |
| | editor.setOption('mode', mode); |
| | |
| | // Store the original content for diff |
| | file.lastContent = file.content; |
| | } |
| | |
| | function createNewFile() { |
| | const filename = prompt('Enter new file name (include extension):', 'newfile.js'); |
| | if (!filename) return; |
| | |
| | // Determine language from extension |
| | const extension = filename.split('.').pop().toLowerCase(); |
| | let language = 'javascript'; |
| | |
| | if (extension === 'py') language = 'python'; |
| | else if (extension === 'html') language = 'htmlmixed'; |
| | else if (extension === 'css') language = 'css'; |
| | else if (extension === 'java') language = 'text/x-java'; |
| | else if (extension === 'c') language = 'text/x-csrc'; |
| | else if (extension === 'cpp' || extension === 'c++') language = 'text/x-c++src'; |
| | else if (extension === 'cs') language = 'text/x-csharp'; |
| | else if (extension === 'go') language = 'text/x-go'; |
| | else if (extension === 'rs') language = 'text/x-rustsrc'; |
| | else if (extension === 'swift') language = 'text/x-swift'; |
| | else if (extension === 'php') language = 'text/x-php'; |
| | else if (extension === 'rb') language = 'text/x-ruby'; |
| | else if (extension === 'sql') language = 'text/x-sql'; |
| | else if (extension === 'sh') language = 'text/x-sh'; |
| | |
| | state.files[filename] = { |
| | content: '', |
| | language: language, |
| | lastContent: '' |
| | }; |
| | |
| | state.currentFile = filename; |
| | renderFileTabs(); |
| | loadFile(filename); |
| | document.getElementById('language-selector').value = language; |
| | } |
| | |
| | function closeFile(filename) { |
| | if (Object.keys(state.files).length <= 1) { |
| | alert('You must have at least one file open'); |
| | return; |
| | } |
| | |
| | delete state.files[filename]; |
| | |
| | // Switch to another file |
| | const remainingFiles = Object.keys(state.files); |
| | state.currentFile = remainingFiles[0]; |
| | |
| | renderFileTabs(); |
| | loadFile(state.currentFile); |
| | } |
| | |
| | // Language selector |
| | document.getElementById('language-selector').addEventListener('change', (e) => { |
| | const language = e.target.value; |
| | state.files[state.currentFile].language = language; |
| | |
| | // Set the editor mode |
| | const mode = languageModes[language] || 'javascript'; |
| | editor.setOption('mode', mode); |
| | }); |
| | |
| | // Prompt toggle functionality |
| | function setupPromptToggle() { |
| | const promptContainer = document.getElementById('prompt-container'); |
| | const promptToggle = document.getElementById('prompt-toggle'); |
| | const toggleIcon = promptToggle.querySelector('.prompt-toggle'); |
| | |
| | if (state.promptCollapsed) { |
| | promptContainer.classList.add('collapsed'); |
| | toggleIcon.classList.add('collapsed'); |
| | } |
| | |
| | promptToggle.addEventListener('click', () => { |
| | promptContainer.classList.toggle('collapsed'); |
| | toggleIcon.classList.toggle('collapsed'); |
| | state.promptCollapsed = !state.promptCollapsed; |
| | localStorage.setItem('prompt-collapsed', state.promptCollapsed); |
| | }); |
| | } |
| | |
| | // Tab switching functionality |
| | document.querySelectorAll('[data-tab]').forEach(tab => { |
| | tab.addEventListener('click', () => { |
| | // Update active tab |
| | document.querySelectorAll('[data-tab]').forEach(t => { |
| | t.classList.remove('tab-active'); |
| | t.classList.add('text-gray-400'); |
| | }); |
| | tab.classList.add('tab-active'); |
| | tab.classList.remove('text-gray-400'); |
| | |
| | // Show corresponding content |
| | const tabId = tab.getAttribute('data-tab'); |
| | document.querySelectorAll('.tab-content').forEach(content => { |
| | content.classList.add('hidden'); |
| | content.classList.remove('active'); |
| | }); |
| | |
| | const contentId = `${tabId}-container`; |
| | document.getElementById(contentId).classList.remove('hidden'); |
| | document.getElementById(contentId).classList.add('active'); |
| | }); |
| | }); |
| | |
| | // Button functionality |
| | document.getElementById('clear-btn').addEventListener('click', () => { |
| | if (confirm('¿Estás seguro de que quieres borrar todo el código?')) { |
| | editor.setValue(''); |
| | state.files[state.currentFile].content = ''; |
| | updateDiff(); |
| | } |
| | }); |
| | |
| | document.getElementById('format-btn').addEventListener('click', formatCode); |
| | document.getElementById('new-file-btn').addEventListener('click', createNewFile); |
| | document.getElementById('analyze-btn').addEventListener('click', analyzeCode); |
| | document.getElementById('save-key-btn').addEventListener('click', () => { |
| | const apiKey = document.getElementById('api-key').value; |
| | state.apiKey = apiKey; |
| | localStorage.setItem('deepseek-api-key', apiKey); |
| | showToast('API Key guardada localmente'); |
| | }); |
| | |
| | // Save custom prompt when changed |
| | document.getElementById('custom-prompt').addEventListener('change', (e) => { |
| | localStorage.setItem('custom-prompt', e.target.value); |
| | }); |
| | |
| | // Resize functionality |
| | const resizeHandle = document.querySelector('.resize-handle'); |
| | const editorPanel = document.querySelector('.flex-1.flex-col'); |
| | const outputPanel = document.querySelector('.w-1\\/3'); |
| | |
| | let isResizing = false; |
| | |
| | resizeHandle.addEventListener('mousedown', (e) => { |
| | isResizing = true; |
| | document.body.style.cursor = 'col-resize'; |
| | e.preventDefault(); |
| | }); |
| | |
| | document.addEventListener('mousemove', (e) => { |
| | if (!isResizing) return; |
| | |
| | const containerWidth = document.querySelector('.flex-1.overflow-hidden').clientWidth; |
| | const newEditorWidth = e.clientX; |
| | const newOutputWidth = containerWidth - e.clientX - 4; // 4px for the resize handle |
| | |
| | editorPanel.style.width = `${newEditorWidth}px`; |
| | outputPanel.style.width = `${newOutputWidth}px`; |
| | |
| | editor.refresh(); |
| | }); |
| | |
| | document.addEventListener('mouseup', () => { |
| | isResizing = false; |
| | document.body.style.cursor = ''; |
| | }); |
| | |
| | // Code formatting |
| | function formatCode() { |
| | const code = editor.getValue(); |
| | const language = state.files[state.currentFile].language; |
| | |
| | // Basic formatting for different languages |
| | let formattedCode = code; |
| | |
| | if (language === 'javascript' || language === 'text/x-java' || language === 'text/x-csharp') { |
| | formattedCode = formatCurlyBraceLanguage(code); |
| | } else if (language === 'python') { |
| | formattedCode = formatPython(code); |
| | } else if (language === 'htmlmixed') { |
| | formattedCode = formatHtml(code); |
| | } else if (language === 'css') { |
| | formattedCode = formatCss(code); |
| | } |
| | |
| | editor.setValue(formattedCode); |
| | showToast('Formato básico aplicado'); |
| | } |
| | |
| | function formatCurlyBraceLanguage(code) { |
| | const lines = code.split('\n'); |
| | let formattedCode = ''; |
| | let indentLevel = 0; |
| | |
| | for (const line of lines) { |
| | const trimmedLine = line.trim(); |
| | |
| | // Decrease indent if line closes a block |
| | if (trimmedLine.endsWith('}') || trimmedLine.endsWith(');')) { |
| | indentLevel = Math.max(0, indentLevel - 1); |
| | } |
| | |
| | // Add current indentation |
| | formattedCode += ' '.repeat(indentLevel) + trimmedLine + '\n'; |
| | |
| | // Increase indent if line opens a block |
| | if (trimmedLine.endsWith('{') || (trimmedLine.endsWith('(') && !trimmedLine.endsWith(');'))) { |
| | indentLevel += 1; |
| | } |
| | } |
| | |
| | return formattedCode.trim(); |
| | } |
| | |
| | function formatPython(code) { |
| | // Very basic Python formatting |
| | const lines = code.split('\n'); |
| | let formattedCode = ''; |
| | let indentLevel = 0; |
| | |
| | for (const line of lines) { |
| | const trimmedLine = line.trim(); |
| | |
| | // Decrease indent if line starts with dedent keywords |
| | if (trimmedLine.startsWith('return') || trimmedLine.startsWith('pass') || |
| | trimmedLine.startsWith('break') || trimmedLine.startsWith('continue') || |
| | trimmedLine.startsWith('raise')) { |
| | indentLevel = Math.max(0, indentLevel - 1); |
| | } |
| | |
| | // Add current indentation |
| | formattedCode += ' '.repeat(indentLevel) + trimmedLine + '\n'; |
| | |
| | // Increase indent if line ends with colon |
| | if (trimmedLine.endsWith(':')) { |
| | indentLevel += 1; |
| | } |
| | } |
| | |
| | return formattedCode.trim(); |
| | } |
| | |
| | function formatHtml(code) { |
| | // Basic HTML formatting |
| | const lines = code.split('\n'); |
| | let formattedCode = ''; |
| | let indentLevel = 0; |
| | |
| | for (const line of lines) { |
| | const trimmedLine = line.trim(); |
| | |
| | // Decrease indent for closing tags |
| | if (trimmedLine.startsWith('</')) { |
| | indentLevel = Math.max(0, indentLevel - 1); |
| | } |
| | |
| | // Add current indentation |
| | formattedCode += ' '.repeat(indentLevel) + trimmedLine + '\n'; |
| | |
| | // Increase indent for opening tags (unless they're self-closing) |
| | if (trimmedLine.startsWith('<') && !trimmedLine.startsWith('<!') && |
| | !trimmedLine.endsWith('/>') && !trimmedLine.includes('</')) { |
| | indentLevel += 1; |
| | } |
| | } |
| | |
| | return formattedCode.trim(); |
| | } |
| | |
| | function formatCss(code) { |
| | // Basic CSS formatting |
| | const lines = code.split('\n'); |
| | let formattedCode = ''; |
| | let indentLevel = 0; |
| | |
| | for (const line of lines) { |
| | const trimmedLine = line.trim(); |
| | |
| | // Decrease indent after closing brace |
| | if (trimmedLine.endsWith('}')) { |
| | indentLevel = Math.max(0, indentLevel - 1); |
| | } |
| | |
| | // Add current indentation |
| | formattedCode += ' '.repeat(indentLevel) + trimmedLine + '\n'; |
| | |
| | // Increase indent after opening brace |
| | if (trimmedLine.endsWith('{')) { |
| | indentLevel += 1; |
| | } |
| | } |
| | |
| | return formattedCode.trim(); |
| | } |
| | |
| | // Diff functionality |
| | function updateDiff() { |
| | const currentFile = state.files[state.currentFile]; |
| | const oldContent = currentFile.lastContent; |
| | const newContent = editor.getValue(); |
| | |
| | if (oldContent === newContent) return; |
| | |
| | const diff = Diff.diffLines(oldContent, newContent); |
| | const diffContainer = document.getElementById('diff-container'); |
| | |
| | let html = '<div class="font-mono text-sm">'; |
| | |
| | diff.forEach((part) => { |
| | const lines = part.value.split('\n'); |
| | if (lines[lines.length - 1] === '') lines.pop(); |
| | |
| | const className = part.added ? 'diff-added' : part.removed ? 'diff-removed' : 'diff-unchanged'; |
| | |
| | lines.forEach(line => { |
| | if (line.trim() === '') return; |
| | html += `<div class="${className} px-2 py-1 my-1 rounded">${escapeHtml(line)}</div>`; |
| | }); |
| | }); |
| | |
| | html += '</div>'; |
| | diffContainer.innerHTML = html; |
| | |
| | // Update last content if not empty |
| | if (newContent.trim() !== '') { |
| | currentFile.lastContent = newContent; |
| | } |
| | } |
| | |
| | // Show AI cursor in the editor |
| | function showAICursor(line, ch, length, label = 'AI') { |
| | const cursorContainer = document.getElementById('ai-cursor-container'); |
| | cursorContainer.innerHTML = ''; |
| | |
| | const lineHeight = editor.defaultTextHeight(); |
| | const charWidth = editor.defaultCharWidth(); |
| | |
| | const cursor = document.createElement('div'); |
| | cursor.className = 'ai-cursor'; |
| | cursor.style.top = `${line * lineHeight}px`; |
| | cursor.style.left = `${ch * charWidth}px`; |
| | cursor.style.height = `${lineHeight}px`; |
| | cursor.style.width = `${length * charWidth}px`; |
| | |
| | const cursorLabel = document.createElement('div'); |
| | cursorLabel.className = 'ai-cursor-label'; |
| | cursorLabel.textContent = label; |
| | |
| | cursor.appendChild(cursorLabel); |
| | cursorContainer.appendChild(cursor); |
| | } |
| | |
| | // Typewriter effect for applying changes |
| | function applyChangesWithTypewriter(newCode, callback) { |
| | const currentCode = editor.getValue(); |
| | const cursor = editor.getCursor(); |
| | |
| | // Clear any existing interval |
| | if (state.typewriterInterval) { |
| | clearInterval(state.typewriterInterval); |
| | } |
| | |
| | // Set the new code immediately but keep track for animation |
| | editor.setValue(newCode); |
| | |
| | // Move cursor to the first change position |
| | const diff = Diff.diffLines(currentCode, newCode); |
| | let firstChangePos = {line: 0, ch: 0}; |
| | |
| | for (let i = 0; i < diff.length; i++) { |
| | if (diff[i].added) { |
| | const lines = newCode.split('\n'); |
| | for (let j = 0; j < lines.length; j++) { |
| | if (lines[j].includes(diff[i].value.trim())) { |
| | firstChangePos = {line: j, ch: 0}; |
| | break; |
| | } |
| | } |
| | break; |
| | } |
| | } |
| | |
| | editor.setCursor(firstChangePos); |
| | |
| | // Add typewriter cursor effect |
| | const typewriterCursor = document.createElement('div'); |
| | typewriterCursor.className = 'typewriter-cursor'; |
| | typewriterCursor.style.top = `${firstChangePos.line * editor.defaultTextHeight()}px`; |
| | typewriterCursor.style.left = `${firstChangePos.ch * editor.defaultCharWidth()}px`; |
| | typewriterCursor.style.height = `${editor.defaultTextHeight()}px`; |
| | document.getElementById('ai-cursor-container').appendChild(typewriterCursor); |
| | |
| | // Remove cursor after animation |
| | setTimeout(() => { |
| | typewriterCursor.remove(); |
| | if (callback) callback(); |
| | }, 1000); |
| | } |
| | |
| | // Analysis function with real API integration |
| | async function analyzeCode() { |
| | const code = editor.getValue(); |
| | const apiKey = state.apiKey; |
| | const customPrompt = document.getElementById('custom-prompt').value; |
| | |
| | if (!code.trim()) { |
| | showToast('Please enter some code to analyze', 'error'); |
| | return; |
| | } |
| | |
| | if (!apiKey) { |
| | showToast('Please enter your DeepSeek API key', 'error'); |
| | return; |
| | } |
| | |
| | // Save current content |
| | state.files[state.currentFile].content = code; |
| | |
| | // Show loading state |
| | const responseContainer = document.getElementById('response-container'); |
| | responseContainer.innerHTML = ` |
| | <div class="flex flex-col items-center justify-center py-8"> |
| | <div class="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500 mb-4"></div> |
| | <p>Analizando tu código...</p> |
| | </div> |
| | `; |
| | |
| | // Switch to response tab |
| | document.querySelector('[data-tab="response"]').click(); |
| | |
| | try { |
| | const messages = [ |
| | { |
| | role: "system", |
| | content: `You are a helpful code assistant. Analyze the provided code and respond with: |
| | 1. An explanation of what the code does |
| | 2. Suggestions for improvement |
| | 3. A modified version of the code with improvements |
| | 4. A clear markdown formatted response with code blocks |
| | |
| | Important: When providing the modified code, include the ENTIRE code file with all changes applied, not just the modified parts. This is crucial for the diff tool to work properly. |
| | |
| | Format your response like this: |
| | ### Analysis |
| | [Explanation of the code] |
| | |
| | ### Suggestions |
| | [Bullet points with improvement suggestions] |
| | |
| | ### Improved Code |
| | \`\`\`[language] |
| | [Complete modified code here] |
| | \`\`\` |
| | |
| | Additional notes: |
| | - Respond in the same language as the code comments |
| | - Preserve all original functionality |
| | - Include any necessary imports or boilerplate` |
| | } |
| | ]; |
| | |
| | // Add custom prompt if provided |
| | if (customPrompt.trim()) { |
| | messages.push({ |
| | role: "user", |
| | content: customPrompt |
| | }); |
| | } |
| | |
| | // Add the code to analyze |
| | messages.push({ |
| | role: "user", |
| | content: `Here is my ${state.files[state.currentFile].language} code:\n\n${code}` |
| | }); |
| | |
| | const response = await fetch('https://api.deepseek.com/v1/chat/completions', { |
| | method: 'POST', |
| | headers: { |
| | 'Content-Type': 'application/json', |
| | 'Authorization': `Bearer ${apiKey}` |
| | }, |
| | body: JSON.stringify({ |
| | model: "deepseek-coder", |
| | messages: messages, |
| | temperature: 0.7, |
| | max_tokens: 2000 |
| | }) |
| | }); |
| | |
| | if (!response.ok) { |
| | throw new Error(`API request failed with status ${response.status}`); |
| | } |
| | |
| | const data = await response.json(); |
| | const aiResponse = data.choices[0].message.content; |
| | |
| | // Extract the improved code from the response |
| | const improvedCodeMatch = aiResponse.match(/```(?:[a-zA-Z]*)\n([\s\S]*?)```/); |
| | let improvedCode = improvedCodeMatch ? improvedCodeMatch[1] : null; |
| | |
| | // Add to history |
| | state.history.unshift({ |
| | timestamp: new Date().toISOString(), |
| | code: code, |
| | response: aiResponse, |
| | prompt: customPrompt, |
| | improvedCode: improvedCode |
| | }); |
| | |
| | // Update history tab |
| | renderHistory(); |
| | |
| | // Format the response as HTML |
| | responseContainer.innerHTML = ` |
| | <div class="prose prose-invert max-w-none"> |
| | ${formatAIResponse(aiResponse)} |
| | ${improvedCode ? ` |
| | <div class="mt-6 flex space-x-3"> |
| | <button id="show-diff-btn" class="bg-blue-600 hover:bg-blue-700 px-3 py-2 rounded text-sm flex items-center space-x-1"> |
| | <i class="fas fa-code-compare mr-1"></i> |
| | <span>Show Changes</span> |
| | </button> |
| | <button id="apply-changes-btn" class="bg-green-600 hover:bg-green-700 px-3 py-2 rounded text-sm flex items-center space-x-1"> |
| | <i class="fas fa-magic mr-1"></i> |
| | <span>Apply Changes</span> |
| | </button> |
| | </div> |
| | ` : ''} |
| | </div> |
| | `; |
| | |
| | // Add event listeners for buttons |
| | if (improvedCode) { |
| | document.getElementById('show-diff-btn').addEventListener('click', () => { |
| | showProposedDiff(code, improvedCode); |
| | }); |
| | |
| | document.getElementById('apply-changes-btn').addEventListener('click', () => { |
| | applyChangesWithTypewriter(improvedCode, () => { |
| | state.files[state.currentFile].content = improvedCode; |
| | state.files[state.currentFile].lastContent = improvedCode; |
| | showToast('Changes applied successfully'); |
| | }); |
| | }); |
| | } |
| | |
| | showToast('Analysis completed successfully'); |
| | } catch (error) { |
| | console.error('API Error:', error); |
| | responseContainer.innerHTML = ` |
| | <div class="bg-red-900/30 p-4 rounded border border-red-700"> |
| | <h3 class="text-red-400 font-bold">Error</h3> |
| | <p>${escapeHtml(error.message)}</p> |
| | <p class="mt-2 text-sm">Please check your API key and network connection.</p> |
| | </div> |
| | `; |
| | showToast('Analysis failed', 'error'); |
| | } |
| | } |
| | |
| | function formatAIResponse(text) { |
| | // Improved formatting with copy buttons for code blocks |
| | let html = escapeHtml(text) |
| | .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>') |
| | .replace(/\*(.*?)\*/g, '<em>$1</em>') |
| | .replace(/```(\w*)\n([\s\S]*?)```/g, (match, language, code) => { |
| | return ` |
| | <div class="code-block bg-gray-700 rounded my-3 relative"> |
| | <button class="code-block-copy bg-gray-600 hover:bg-gray-500 text-white text-xs px-2 py-1 rounded" |
| | onclick="navigator.clipboard.writeText(\`${escapeHtml(code).replace(/`/g, '\\`')}\`)"> |
| | <i class="fas fa-copy mr-1"></i>Copy |
| | </button> |
| | <pre class="p-3 overflow-x-auto"><code>${escapeHtml(code)}</code></pre> |
| | </div> |
| | `; |
| | }) |
| | .replace(/`(.*?)`/g, '<code class="bg-gray-700 px-1 rounded">$1</code>') |
| | .replace(/\n/g, '<br>'); |
| | |
| | return html; |
| | } |
| | |
| | function renderHistory() { |
| | const historyContainer = document.getElementById('history-container'); |
| | |
| | if (state.history.length === 0) { |
| | historyContainer.innerHTML = ` |
| | <div class="text-center text-gray-500 py-8"> |
| | <i class="fas fa-history text-3xl mb-2"></i> |
| | <p>No analysis history yet</p> |
| | </div> |
| | `; |
| | return; |
| | } |
| | |
| | let html = '<div class="space-y-4">'; |
| | |
| | state.history.forEach((item, index) => { |
| | const date = new Date(item.timestamp); |
| | const formattedDate = date.toLocaleString(); |
| | |
| | html += ` |
| | <div class="history-item bg-gray-700/50 p-3 rounded-lg cursor-pointer" data-index="${index}"> |
| | <div class="flex justify-between items-center mb-2"> |
| | <span class="text-sm font-medium">Analysis #${state.history.length - index}</span> |
| | <span class="text-xs text-gray-400">${formattedDate}</span> |
| | </div> |
| | ${item.prompt ? `<div class="text-xs text-blue-400 mb-1">Prompt: "${escapeHtml(item.prompt.substring(0, 50))}${item.prompt.length > 50 ? '...' : ''}"</div>` : ''} |
| | <div class="text-sm text-gray-300 truncate">${escapeHtml(item.response.substring(0, 100))}...</div> |
| | ${item.improvedCode ? `<div class="mt-2 text-xs text-green-400">Contains code modifications</div>` : ''} |
| | </div> |
| | `; |
| | }); |
| | |
| | html += '</div>'; |
| | historyContainer.innerHTML = html; |
| | |
| | // Add click handlers to history items |
| | document.querySelectorAll('.history-item').forEach(item => { |
| | item.addEventListener('click', () => { |
| | const index = item.getAttribute('data-index'); |
| | const historyItem = state.history[index]; |
| | |
| | document.getElementById('response-container').innerHTML = ` |
| | <div class="prose prose-invert max-w-none"> |
| | <div class="flex justify-between items-center mb-4"> |
| | <h3>Analysis from ${new Date(historyItem.timestamp).toLocaleString()}</h3> |
| | <div class="flex space-x-2"> |
| | <button id="load-code-btn" class="bg-blue-600 hover:bg-blue-700 px-2 py-1 rounded text-xs"> |
| | <i class="fas fa-code mr-1"></i>Load Code |
| | </button> |
| | ${historyItem.improvedCode ? ` |
| | <button id="apply-history-changes-btn" class="bg-green-600 hover:bg-green-700 px-2 py-1 rounded text-xs"> |
| | <i class="fas fa-magic mr-1"></i>Apply Changes |
| | </button> |
| | ` : ''} |
| | </div> |
| | </div> |
| | ${historyItem.prompt ? `<div class="bg-gray-700/50 p-3 rounded mb-4"> |
| | <h4 class="text-blue-400 font-medium mb-1">Prompt:</h4> |
| | <p>${escapeHtml(historyItem.prompt)}</p> |
| | </div>` : ''} |
| | ${formatAIResponse(historyItem.response)} |
| | </div> |
| | `; |
| | |
| | // Add handler for load code button |
| | document.getElementById('load-code-btn').addEventListener('click', () => { |
| | editor.setValue(historyItem.code); |
| | if (historyItem.prompt) { |
| | document.getElementById('custom-prompt').value = historyItem.prompt; |
| | localStorage.setItem('custom-prompt', historyItem.prompt); |
| | } |
| | showToast('Code loaded from history'); |
| | }); |
| | |
| | // Add handler for apply changes button |
| | if (historyItem.improvedCode) { |
| | document.getElementById('apply-history-changes-btn').addEventListener('click', () => { |
| | applyChangesWithTypewriter(historyItem.improvedCode, () => { |
| | state.files[state.currentFile].content = historyItem.improvedCode; |
| | state.files[state.currentFile].lastContent = historyItem.improvedCode; |
| | showToast('Changes applied successfully'); |
| | }); |
| | }); |
| | } |
| | |
| | document.querySelector('[data-tab="response"]').click(); |
| | }); |
| | }); |
| | } |
| | |
| | // Helper functions |
| | function escapeHtml(unsafe) { |
| | return unsafe |
| | .replace(/&/g, "&") |
| | .replace(/</g, "<") |
| | .replace(/>/g, ">") |
| | .replace(/"/g, """) |
| | .replace(/'/g, "'"); |
| | } |
| | |
| | function showToast(message, type = 'success') { |
| | const toast = document.createElement('div'); |
| | toast.className = `fixed bottom-4 right-4 px-4 py-2 rounded-md shadow-lg ${ |
| | type === 'success' ? 'bg-green-600' : 'bg-red-600' |
| | } text-white animate-fade-in`; |
| | toast.textContent = message; |
| | document.body.appendChild(toast); |
| | |
| | setTimeout(() => { |
| | toast.classList.remove('animate-fade-in'); |
| | toast.classList.add('animate-fade-out'); |
| | setTimeout(() => toast.remove(), 300); |
| | }, 3000); |
| | } |
| | |
| | // Auto-save and diff updates |
| | editor.on('change', () => { |
| | updateDiff(); |
| | |
| | // Auto-save every 5 seconds |
| | clearTimeout(window.autoSaveTimer); |
| | window.autoSaveTimer = setTimeout(() => { |
| | state.files[state.currentFile].content = editor.getValue(); |
| | }, 5000); |
| | }); |
| | |
| | // Initialize the app |
| | initUI(); |
| | |
| | // Add some CSS animations |
| | const style = document.createElement('style'); |
| | style.textContent = ` |
| | @keyframes fadeIn { |
| | from { opacity: 0; transform: translateY(10px); } |
| | to { opacity: 1; transform: translateY(0); } |
| | } |
| | @keyframes fadeOut { |
| | from { opacity: 1; transform: translateY(0); } |
| | to { opacity: 0; transform: translateY(10px); } |
| | } |
| | .animate-fade-in { |
| | animation: fadeIn 0.3s ease-out forwards; |
| | } |
| | .animate-fade-out { |
| | animation: fadeOut 0.3s ease-out forwards; |
| | } |
| | `; |
| | document.head.appendChild(style); |
| | </script> |
| | </body> |
| | </html> |
| |
|