self modifying website
1<!doctype html> 2<html lang="en"> 3 <head> 4 <meta charset="UTF-8" /> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 <title>C:\PLASTIC.EXE - Self-Modifying System</title> 7 <style> 8 @import url("https://fonts.googleapis.com/css2?family=Courier+Prime:wght@400;700&display=swap"); 9 10 body { 11 background: #f0f0f0; 12 font-family: "Courier Prime", "Courier New", monospace; 13 color: #000000; 14 margin: 0; 15 padding: 20px; 16 line-height: 1.2; 17 font-size: 14px; 18 } 19 20 .terminal { 21 background: #ffffff; 22 border: 2px solid #808080; 23 box-shadow: 24 inset -2px -2px #c0c0c0, 25 inset 2px 2px #404040; 26 max-width: 800px; 27 margin: 0 auto; 28 padding: 0; 29 } 30 31 .title-bar { 32 background: #c0c0c0; 33 color: #000000; 34 padding: 4px 8px; 35 font-weight: bold; 36 border-bottom: 1px solid #808080; 37 font-size: 12px; 38 } 39 40 .content { 41 padding: 10px; 42 background: #ffffff; 43 } 44 45 .prompt { 46 color: #000000; 47 margin: 0; 48 } 49 50 .cursor { 51 background: #000000; 52 color: #ffffff; 53 animation: blink 1s infinite; 54 } 55 56 @keyframes blink { 57 0%, 58 50% { 59 opacity: 1; 60 } 61 51%, 62 100% { 63 opacity: 0; 64 } 65 } 66 67 h1 { 68 color: #000000; 69 font-size: 24px; 70 margin: 10px 0; 71 text-transform: uppercase; 72 } 73 74 h2 { 75 color: #000000; 76 font-size: 16px; 77 margin: 15px 0 5px 0; 78 text-transform: uppercase; 79 } 80 81 .command { 82 color: #ffffff; 83 background: #808080; 84 padding: 2px 4px; 85 display: inline-block; 86 margin: 2px; 87 } 88 89 .button { 90 background: #c0c0c0; 91 color: #000000; 92 border: 2px outset #c0c0c0; 93 padding: 4px 8px; 94 font-family: inherit; 95 font-size: 12px; 96 cursor: pointer; 97 margin: 5px 2px; 98 display: inline-block; 99 text-decoration: none; 100 text-transform: uppercase; 101 } 102 103 .button:active { 104 border: 2px inset #c0c0c0; 105 } 106 107 .button:hover { 108 background: #d0d0d0; 109 } 110 111 .editor { 112 width: calc(100% - 20px); 113 height: 150px; 114 background: #0000ff; 115 color: #ffffff; 116 font-family: inherit; 117 margin: 10px 0; 118 padding: 10px; 119 border: 2px inset #c0c0c0; 120 font-size: 12px; 121 resize: none; 122 } 123 124 .status-bar { 125 background: #c0c0c0; 126 color: #000000; 127 padding: 2px 8px; 128 border-top: 1px solid #808080; 129 font-size: 11px; 130 display: flex; 131 justify-content: space-between; 132 } 133 134 .file-listing { 135 margin: 10px 0; 136 } 137 138 .file-line { 139 margin: 2px 0; 140 font-family: inherit; 141 } 142 143 .dir { 144 color: #000080; 145 } 146 147 .exe { 148 color: #000000; 149 } 150 151 .txt { 152 color: #000000; 153 } 154 155 .error { 156 color: #ffffff; 157 background: #ff0000; 158 padding: 2px 4px; 159 } 160 161 ul { 162 margin: 5px 0; 163 padding-left: 20px; 164 } 165 166 li { 167 margin: 2px 0; 168 } 169 170 .separator { 171 color: #808080; 172 margin: 10px 0; 173 } 174 175 .ascii-art { 176 color: #000000; 177 font-size: 10px; 178 line-height: 1; 179 white-space: pre; 180 margin: 10px 0; 181 } 182 183 /* Modal styles */ 184 .modal { 185 display: none; 186 position: fixed; 187 z-index: 9999; 188 left: 0; 189 top: 0; 190 width: 100%; 191 height: 100%; 192 background-color: rgba(0, 0, 0, 0.7); 193 overflow: auto; 194 opacity: 1; 195 transition: opacity 0.2s ease-in-out; 196 } 197 198 .modal-content { 199 background-color: #ffffff; 200 border: 2px solid #808080; 201 box-shadow: 202 inset -2px -2px #c0c0c0, 203 inset 2px 2px #404040, 204 0 0 10px rgba(0, 0, 0, 0.5); 205 margin: 10% auto; 206 width: 80%; 207 max-width: 600px; 208 animation: fadeIn 0.2s ease-in-out; 209 position: relative; /* Ensure proper positioning of close button */ 210 } 211 212 @keyframes fadeIn { 213 from { 214 opacity: 0; 215 transform: translateY(-20px); 216 } 217 to { 218 opacity: 1; 219 transform: translateY(0); 220 } 221 } 222 223 .modal-body { 224 padding: 15px; 225 max-height: 70vh; 226 overflow-y: auto; 227 } 228 229 .input-group { 230 margin: 10px 0; 231 } 232 233 .input-group label { 234 display: inline-block; 235 width: 80px; 236 } 237 238 .input-group input { 239 font-family: "Courier Prime", "Courier New", monospace; 240 padding: 5px; 241 background: #ffffff; 242 border: 2px inset #c0c0c0; 243 } 244 245 .save-list { 246 max-height: 200px; 247 overflow-y: auto; 248 border: 1px solid #c0c0c0; 249 margin: 10px 0; 250 padding: 5px; 251 } 252 253 .save-item { 254 display: flex; 255 justify-content: space-between; 256 margin: 5px 0; 257 padding: 8px; 258 border-bottom: 1px solid #c0c0c0; 259 background-color: #f0f0f0; 260 } 261 262 .hotkey { 263 cursor: pointer; 264 padding: 2px 5px; 265 margin: 0 2px; 266 border: 1px solid #c0c0c0; 267 border-radius: 3px; 268 } 269 270 .hotkey:hover { 271 background-color: #c0c0c0; 272 } 273 274 .modal-close { 275 position: absolute; 276 right: 10px; 277 top: 2px; 278 cursor: pointer; 279 font-size: 16px; 280 font-weight: bold; 281 color: #000000; 282 background: #d0d0d0; 283 border: 1px solid #808080; 284 border-radius: 3px; 285 width: 20px; 286 height: 20px; 287 text-align: center; 288 line-height: 18px; 289 box-shadow: 290 inset -1px -1px #404040, 291 inset 1px 1px #ffffff; 292 } 293 294 .modal-close:hover { 295 background-color: #c0c0c0; 296 color: #ff0000; 297 } 298 299 .modal-close:active { 300 box-shadow: 301 inset 1px 1px #404040, 302 inset -1px -1px #ffffff; 303 } 304 </style> 305 </head> 306 <body> 307 <!-- Help Modal --> 308 <div id="help-modal" class="modal"> 309 <div class="modal-content"> 310 <div class="title-bar"> 311 PLASTIC.EXE - Help 312 <span 313 class="modal-close" 314 onclick="hideModal('help-modal')" 315 tabindex="0" 316 role="button" 317 aria-label="Close help modal" 318 ></span 319 > 320 </div> 321 <div class="modal-body"> 322 <h2>Keyboard Shortcuts:</h2> 323 <ul> 324 <li><strong>F1</strong>: Show this help screen</li> 325 <li> 326 <strong>F2</strong>: Save the current page state 327 </li> 328 <li><strong>F3</strong>: Load a saved page state</li> 329 <li><strong>Esc</strong>: Close modals</li> 330 <li> 331 <strong>Ctrl+Enter</strong>: Execute code in the 332 editor 333 </li> 334 </ul> 335 336 <h2>How to Use PLASTIC:</h2> 337 <ol> 338 <li> 339 Type a description of what you want to change in the 340 editor 341 </li> 342 <li> 343 Press Ctrl+Enter or click the "GENERATE & EXECUTE" 344 button 345 </li> 346 <li> 347 PLASTIC will modify itself according to your 348 description 349 </li> 350 <li>Use F2 to save your modified system</li> 351 <li>Use F3 to load a previously saved state</li> 352 </ol> 353 354 <h2>About PLASTIC:</h2> 355 <p> 356 PLASTIC.EXE is a self-modifying system that lets you 357 transform its interface and functionality through 358 natural language commands. 359 </p> 360 </div> 361 <div style="text-align: center; margin-top: 15px"> 362 <div class="button" onclick="hideModal('help-modal')"> 363 CLOSE 364 </div> 365 </div> 366 </div> 367 </div> 368 369 <!-- Save Modal --> 370 <div id="save-modal" class="modal"> 371 <div class="modal-content"> 372 <div class="title-bar"> 373 PLASTIC.EXE - Save 374 <span 375 class="modal-close" 376 onclick="hideModal('save-modal')" 377 tabindex="0" 378 role="button" 379 aria-label="Close save modal" 380 ></span 381 > 382 </div> 383 <div class="modal-body"> 384 <h2>Save Current State</h2> 385 <div class="input-group"> 386 <label for="save-name">Name:</label> 387 <input 388 type="text" 389 id="save-name" 390 placeholder="Enter save name" 391 /> 392 </div> 393 <div 394 style=" 395 display: flex; 396 justify-content: center; 397 gap: 10px; 398 margin-top: 15px; 399 " 400 > 401 <div class="button" onclick="saveCurrentHTML()"> 402 SAVE 403 </div> 404 <div class="button" onclick="hideModal('save-modal')"> 405 CANCEL 406 </div> 407 </div> 408 <div id="save-message"></div> 409 410 <h2>Saved States</h2> 411 <div id="saved-list" class="save-list"></div> 412 </div> 413 </div> 414 </div> 415 416 <!-- Load Modal --> 417 <div id="load-modal" class="modal"> 418 <div class="modal-content"> 419 <div class="title-bar"> 420 PLASTIC.EXE - Load 421 <span 422 class="modal-close" 423 onclick="hideModal('load-modal')" 424 tabindex="0" 425 role="button" 426 aria-label="Close load modal" 427 ></span 428 > 429 </div> 430 <div class="modal-body"> 431 <h2>Load Saved State</h2> 432 <div id="load-list" class="save-list"></div> 433 <div id="load-message"></div> 434 <div style="text-align: center; margin-top: 15px"> 435 <div class="button" onclick="hideModal('load-modal')"> 436 CANCEL 437 </div> 438 </div> 439 </div> 440 </div> 441 </div> 442 <div class="terminal"> 443 <div class="title-bar"> 444 C:\PLASTIC.EXE - [Self-Modifying System v3.0] 445 </div> 446 447 <div class="content"> 448 <p class="prompt">C:\PLASTIC> PLASTIC.EXE</p> 449 450 <pre class="ascii-art"> 451██████╗ ██╗ █████╗ ███████╗████████╗██╗ ██████╗ 452██╔══██╗██║ ██╔══██╗██╔════╝╚══██╔══╝██║██╔════╝ 453██████╔╝██║ ███████║███████╗ ██║ ██║██║ 454██╔═══╝ ██║ ██╔══██║╚════██║ ██║ ██║██║ 455██║ ███████╗██║ ██║███████║ ██║ ██║╚██████╗ 456╚═╝ ╚══════╝╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═════╝</pre 457 > 458 459 <h2>System Information:</h2> 460 <p>PLASTIC v3.0 - Self-Modifying Code System</p> 461 <p>Copyright (C) 1995 DUNKIRK Corp.</p> 462 <p> 463 All rights reserved. Licensed to: REGISTERED USER under MIT 464 </p> 465 466 <div class="separator"> 467 ════════════════════════════════════════════════════════════ 468 </div> 469 470 <h2>About This System:</h2> 471 <p> 472 PLASTIC.EXE is an experimental self-modifying executable 473 that allows real-time code injection and system 474 modification. WARNING: Use at your own risk. System may 475 become unstable. 476 </p> 477 478 <h2>Experimental Code Generator:</h2> 479 <p>Describe changes to make to the page:</p> 480 <textarea 481 class="editor" 482 id="codeEditor" 483 placeholder="e.g., 'Add a retro calculator widget', 'Change theme to green terminal', or 'Completely redesign as a DOS file manager'" 484 ></textarea> 485 <div class="button" onclick="generateAndExecute()"> 486 GENERATE & EXECUTE 487 </div> 488 <div class="button" onclick="clearEditor()">CLEAR</div> 489 <div 490 id="statusDisplay" 491 style="color: #000080; margin-top: 10px" 492 ></div> 493 494 <h2>System Features:</h2> 495 <ul> 496 <li> 497 Real-time <span class="command">EXEC</span> code 498 modification 499 </li> 500 <li>Compatible with DOS 6.22 and Windows 95</li> 501 <li>640K memory optimized</li> 502 <li>No TSR conflicts detected</li> 503 <li>Supports EGA/VGA graphics modes</li> 504 </ul> 505 506 <div class="separator"> 507 ════════════════════════════════════════════════════════════ 508 </div> 509 510 <p> 511 Runtime: <span id="runtime">00:13:37</span> | Memory: 512 <span id="memory">589K free</span> | CPU: 80486DX-33 513 </p> 514 </div> 515 516 <div class="status-bar"> 517 <span 518 ><span class="hotkey" onclick="showHelp()">F1=Help</span> 519 <span class="hotkey" onclick="showSaveModal()" 520 >F2=Save</span 521 > 522 <span class="hotkey" onclick="showLoadModal()" 523 >F3=Load</span 524 > 525 F10=Menu</span 526 > 527 <span id="time">12:00</span> 528 </div> 529 </div> 530 531 <script> 532 // Tool call system for AI to interact with the page 533 window.toolCallbacks = { 534 replaceElement: function (selector, newHTML) { 535 const element = document.querySelector(selector); 536 if (element) { 537 element.outerHTML = newHTML; 538 return { 539 success: true, 540 message: `Replaced element: ${selector}`, 541 }; 542 } 543 return { 544 success: false, 545 message: `Element not found: ${selector}`, 546 }; 547 }, 548 549 updateElement: function (selector, newContent) { 550 const element = document.querySelector(selector); 551 if (element) { 552 element.innerHTML = newContent; 553 return { 554 success: true, 555 message: `Updated element: ${selector}`, 556 }; 557 } 558 return { 559 success: false, 560 message: `Element not found: ${selector}`, 561 }; 562 }, 563 564 addElement: function ( 565 parentSelector, 566 newHTML, 567 position = "beforeend", 568 ) { 569 const parent = document.querySelector(parentSelector); 570 if (parent) { 571 parent.insertAdjacentHTML(position, newHTML); 572 return { 573 success: true, 574 message: `Added element to: ${parentSelector}`, 575 }; 576 } 577 return { 578 success: false, 579 message: `Parent not found: ${parentSelector}`, 580 }; 581 }, 582 583 removeElement: function (selector) { 584 const element = document.querySelector(selector); 585 if (element) { 586 element.remove(); 587 return { 588 success: true, 589 message: `Removed element: ${selector}`, 590 }; 591 } 592 return { 593 success: false, 594 message: `Element not found: ${selector}`, 595 }; 596 }, 597 598 updateStyle: function (selector, styleObj) { 599 const element = document.querySelector(selector); 600 if (element) { 601 Object.assign(element.style, styleObj); 602 return { 603 success: true, 604 message: `Updated styles for: ${selector}`, 605 }; 606 } 607 return { 608 success: false, 609 message: `Element not found: ${selector}`, 610 }; 611 }, 612 613 executeJS: function (code) { 614 try { 615 const result = eval(code); 616 return { 617 success: true, 618 message: "JavaScript executed", 619 result: result, 620 }; 621 } catch (error) { 622 return { 623 success: false, 624 message: `JS Error: ${error.message}`, 625 }; 626 } 627 }, 628 }; 629 630 function executeToolCall(toolCall) { 631 const { function: func, arguments: args } = toolCall; 632 console.log("Executing tool call:", func, args); 633 634 if (window.toolCallbacks[func]) { 635 try { 636 // Handle different function signatures 637 if (func === "removeElement") { 638 // removeElement expects just a selector 639 const selector = 640 args.selector || 641 Object.keys(args)[0] || 642 Object.values(args)[0]; 643 return window.toolCallbacks[func](selector); 644 } else if (func === "updateStyle" && args.styleObj) { 645 return window.toolCallbacks[func]( 646 args.selector, 647 args.styleObj, 648 ); 649 } else if (func === "executeJS") { 650 return window.toolCallbacks[func](args.code); 651 } else if (func === "updateElement") { 652 return window.toolCallbacks[func]( 653 args.selector, 654 args.newContent, 655 ); 656 } else if (func === "replaceElement") { 657 return window.toolCallbacks[func]( 658 args.selector, 659 args.newHTML, 660 ); 661 } else if (func === "addElement") { 662 return window.toolCallbacks[func]( 663 args.parentSelector, 664 args.newHTML, 665 args.position, 666 ); 667 } else { 668 return window.toolCallbacks[func]( 669 ...Object.values(args), 670 ); 671 } 672 } catch (error) { 673 return { 674 success: false, 675 message: `Error executing ${func}: ${error.message}`, 676 }; 677 } 678 } 679 return { success: false, message: `Unknown tool: ${func}` }; 680 } 681 682 async function generateAndExecute() { 683 const userPrompt = document.getElementById("codeEditor").value; 684 const statusDiv = document.getElementById("statusDisplay"); 685 686 if (userPrompt.trim() === "") { 687 statusDiv.textContent = "ERROR: No description provided"; 688 return; 689 } 690 691 statusDiv.textContent = "CONNECTING TO AI SYSTEM..."; 692 693 try { 694 const currentPageHTML = document.documentElement.outerHTML; 695 696 const response = await fetch( 697 "https://ai.hackclub.com/chat/completions", 698 { 699 method: "POST", 700 headers: { 701 "Content-Type": "application/json", 702 }, 703 body: JSON.stringify({ 704 messages: [ 705 { 706 role: "user", 707 content: `Here is the current HTML page:\n\n${currentPageHTML}\n\nUser request: "${userPrompt}"\n\nIMPORTANT: You must respond with ONLY one of these two formats:\n\nFORMAT 1 - Tool calls (for precise modifications):\n{"tool_calls": [{"function": "functionName", "arguments": {"param": "value"}}]}\n\nFORMAT 2 - Raw HTML (to append to page):\n<div>Your HTML content here</div>\n\nDO NOT include any explanatory text, markdown formatting, or additional commentary. Respond with ONLY the JSON or HTML.\n\nAvailable tools with correct argument formats:\n1. removeElement: {"function": "removeElement", "arguments": {"selector": ".class-name"}}\n2. updateElement: {"function": "updateElement", "arguments": {"selector": ".class-name", "newContent": "new content"}}\n3. replaceElement: {"function": "replaceElement", "arguments": {"selector": ".class-name", "newHTML": "<div>new html</div>"}}\n4. addElement: {"function": "addElement", "arguments": {"parentSelector": ".parent", "newHTML": "<div>content</div>", "position": "beforeend"}}\n5. updateStyle: {"function": "updateStyle", "arguments": {"selector": ".class-name", "styleObj": {"color": "#000000"}}}\n6. executeJS: {"function": "executeJS", "arguments": {"code": "console.log('hello');"}}\n\nUse DOS/retro aesthetic with flat colors: #000000 (black), #ffffff (white), #c0c0c0 (gray), #000080 (blue), #ff0000 (red). Use monospace fonts.`, 708 }, 709 ], 710 }), 711 }, 712 ); 713 714 if (!response.ok) { 715 throw new Error( 716 `HTTP ${response.status}: ${response.statusText}`, 717 ); 718 } 719 720 statusDiv.textContent = "AI PROCESSING..."; 721 722 const data = await response.json(); 723 console.log("API Response:", data); 724 725 let generatedContent; 726 if ( 727 data.choices && 728 data.choices[0] && 729 data.choices[0].message 730 ) { 731 generatedContent = data.choices[0].message.content; 732 } else if (data.content) { 733 generatedContent = data.content; 734 } else if (data.response) { 735 generatedContent = data.response; 736 } else if (typeof data === "string") { 737 generatedContent = data; 738 } else { 739 throw new Error("Unexpected API response format"); 740 } 741 742 statusDiv.textContent = "EXECUTING COMMANDS..."; 743 744 // Clean up response and extract JSON if present 745 let cleanResponse = generatedContent.trim(); 746 747 // Remove markdown formatting 748 cleanResponse = cleanResponse 749 .replace(/```json\n?/g, "") 750 .replace(/```\n?/g, ""); 751 752 // Try to extract JSON from mixed content 753 const jsonMatch = cleanResponse.match( 754 /\{[\s\S]*"tool_calls"[\s\S]*\}/, 755 ); 756 if (jsonMatch) { 757 cleanResponse = jsonMatch[0]; 758 } 759 760 console.log("Cleaned response:", cleanResponse); 761 762 // For safety, preprocess JavaScript code in JSON to escape problematic characters 763 cleanResponse = cleanResponse.replace( 764 /"code"\s*:\s*(`|")([^`"]*?)(`|")/g, 765 function (match, q1, code, q3) { 766 // Replace all literal backslashes with double backslashes in the code string 767 const escapedCode = code.replace(/\\/g, "\\\\"); 768 return `"code":${q1}${escapedCode}${q3}`; 769 }, 770 ); 771 772 // Check if response contains tool calls 773 try { 774 const toolResponse = safeJsonParse(cleanResponse); 775 if ( 776 toolResponse.tool_calls && 777 Array.isArray(toolResponse.tool_calls) 778 ) { 779 // Execute tool calls 780 const results = []; 781 for (const toolCall of toolResponse.tool_calls) { 782 const result = executeToolCall(toolCall); 783 results.push(result); 784 console.log("Tool call result:", result); 785 } 786 statusDiv.textContent = `EXECUTED ${results.length} COMMANDS`; 787 788 // Send feedback to AI about tool results 789 setTimeout( 790 () => sendToolFeedback(userPrompt, results), 791 100, 792 ); 793 } else { 794 throw new Error("Invalid tool call format"); 795 } 796 } catch (jsonError) { 797 console.log("JSON parse error:", jsonError); 798 console.log("Raw response:", generatedContent); 799 console.log("Attempting to parse as HTML..."); 800 801 // Not JSON, treat as HTML code 802 let cleanCode = generatedContent 803 .replace(/```html\n?/g, "") 804 .replace(/```\n?/g, ""); 805 806 statusDiv.textContent = "INJECTING CODE..."; 807 document.querySelector(".content").innerHTML += 808 cleanCode; 809 statusDiv.textContent = "CODE EXECUTION SUCCESSFUL"; 810 } 811 812 document.getElementById("codeEditor").value = ""; 813 814 // Clear status after 3 seconds 815 setTimeout(() => { 816 statusDiv.textContent = ""; 817 }, 3000); 818 } catch (error) { 819 statusDiv.textContent = `SYSTEM ERROR: ${error.message}`; 820 console.error("Error:", error); 821 } 822 } 823 824 function clearEditor() { 825 document.getElementById("codeEditor").value = ""; 826 document.getElementById("statusDisplay").textContent = ""; 827 } 828 829 async function sendToolFeedback(originalPrompt, toolResults) { 830 const statusDiv = document.getElementById("statusDisplay"); 831 832 try { 833 statusDiv.textContent = "SENDING FEEDBACK TO AI..."; 834 835 const currentPageHTML = document.documentElement.outerHTML; 836 const resultsText = toolResults 837 .map( 838 (r) => 839 `${r.success ? "✓" : "✗"} ${r.message}${r.result ? ` (result: ${r.result})` : ""}`, 840 ) 841 .join("\n"); 842 843 const response = await fetch( 844 "https://ai.hackclub.com/chat/completions", 845 { 846 method: "POST", 847 headers: { 848 "Content-Type": "application/json", 849 }, 850 body: JSON.stringify({ 851 messages: [ 852 { 853 role: "user", 854 content: `Previous request: "${originalPrompt}"\n\nTool execution results:\n${resultsText}\n\nCurrent page state:\n${currentPageHTML}\n\nBased on the tool results, do you need to make any follow-up modifications? If everything looks good, respond with "COMPLETE". If you need to make adjustments, respond with tool calls or HTML.\n\nIMPORTANT: Respond with ONLY one of these formats:\n- "COMPLETE" (if satisfied)\n- {"tool_calls": [...]} (for modifications)\n- Raw HTML (to append content)\n\nDO NOT include explanatory text.`, 855 }, 856 ], 857 }), 858 }, 859 ); 860 861 if (!response.ok) { 862 throw new Error( 863 `HTTP ${response.status}: ${response.statusText}`, 864 ); 865 } 866 867 const data = await response.json(); 868 let followUpContent; 869 870 if ( 871 data.choices && 872 data.choices[0] && 873 data.choices[0].message 874 ) { 875 followUpContent = data.choices[0].message.content; 876 } else if (data.content) { 877 followUpContent = data.content; 878 } else if (data.response) { 879 followUpContent = data.response; 880 } else { 881 throw new Error("Unexpected API response format"); 882 } 883 884 followUpContent = followUpContent.trim(); 885 console.log("Follow-up response:", followUpContent); 886 887 if ( 888 followUpContent === "COMPLETE" || 889 followUpContent === '"COMPLETE"' 890 ) { 891 statusDiv.textContent = "AI SATISFIED - TASK COMPLETE"; 892 setTimeout(() => (statusDiv.textContent = ""), 3000); 893 return; 894 } 895 896 // Process follow-up commands 897 statusDiv.textContent = "AI MAKING ADJUSTMENTS..."; 898 899 // Try to parse as tool calls 900 try { 901 const jsonMatch = followUpContent.match( 902 /\{[\s\S]*"tool_calls"[\s\S]*\}/, 903 ); 904 if (jsonMatch) { 905 // Preprocess JavaScript code in JSON to escape problematic characters 906 let cleanJson = jsonMatch[0].replace( 907 /"code"\s*:\s*(`|")([^`"]*?)(`|")/g, 908 function (match, q1, code, q3) { 909 // Replace all literal backslashes with double backslashes in the code string 910 const escapedCode = code.replace( 911 /\\/g, 912 "\\\\", 913 ); 914 return `"code":${q1}${escapedCode}${q3}`; 915 }, 916 ); 917 918 const toolResponse = safeJsonParse(cleanJson); 919 if ( 920 toolResponse.tool_calls && 921 Array.isArray(toolResponse.tool_calls) 922 ) { 923 const followUpResults = []; 924 for (const toolCall of toolResponse.tool_calls) { 925 const result = executeToolCall(toolCall); 926 followUpResults.push(result); 927 console.log( 928 "Follow-up tool result:", 929 result, 930 ); 931 } 932 statusDiv.textContent = `AI EXECUTED ${followUpResults.length} ADJUSTMENTS`; 933 } 934 } else { 935 // Treat as HTML 936 let cleanCode = followUpContent 937 .replace(/```html\n?/g, "") 938 .replace(/```\n?/g, ""); 939 document.querySelector(".content").innerHTML += 940 cleanCode; 941 statusDiv.textContent = 942 "AI ADDED FOLLOW-UP CONTENT"; 943 } 944 } catch (error) { 945 console.log("Follow-up parsing error:", error); 946 statusDiv.textContent = "AI FEEDBACK ERROR"; 947 } 948 949 setTimeout(() => (statusDiv.textContent = ""), 4000); 950 } catch (error) { 951 statusDiv.textContent = `FEEDBACK ERROR: ${error.message}`; 952 console.error("Feedback error:", error); 953 setTimeout(() => (statusDiv.textContent = ""), 3000); 954 } 955 } 956 957 // Debug helper for JSON parsing issues 958 function safeJsonParse(jsonString) { 959 try { 960 return JSON.parse(jsonString); 961 } catch (error) { 962 console.error("JSON parse error:", error); 963 console.error("Problem JSON:", jsonString); 964 // Try to escape any unescaped control characters 965 const escapedJson = jsonString.replace( 966 /[\u0000-\u001F]/g, 967 (match) => { 968 return ( 969 "\\u" + 970 ( 971 "0000" + match.charCodeAt(0).toString(16) 972 ).slice(-4) 973 ); 974 }, 975 ); 976 try { 977 return JSON.parse(escapedJson); 978 } catch (secondError) { 979 console.error( 980 "Second parse attempt failed:", 981 secondError, 982 ); 983 throw error; // Throw the original error 984 } 985 } 986 } 987 988 function deleteSave(key) { 989 if (confirm("Are you sure you want to delete this save?")) { 990 localStorage.removeItem(key); 991 // Refresh the load modal to show updated list 992 showLoadModal(); 993 } 994 } 995 996 // Handle keyboard shortcuts 997 function setupKeyboardHandlers() { 998 const codeEditor = document.getElementById("codeEditor"); 999 if (codeEditor) { 1000 // Remove any previous event listeners first (to avoid duplicates) 1001 codeEditor.removeEventListener("keydown", editorKeyHandler); 1002 // Add a new event listener 1003 codeEditor.addEventListener("keydown", editorKeyHandler); 1004 } else { 1005 console.error("codeEditor element not found, will retry"); 1006 // Retry after a short delay 1007 setTimeout(setupKeyboardHandlers, 100); 1008 } 1009 1010 // Global keyboard shortcuts 1011 document.addEventListener("keydown", function (e) { 1012 // F1 key - Help 1013 if (e.key === "F1") { 1014 e.preventDefault(); 1015 showHelp(); 1016 } 1017 // F2 key - Save 1018 else if (e.key === "F2") { 1019 e.preventDefault(); 1020 showSaveModal(); 1021 } 1022 // F3 key - Load 1023 else if (e.key === "F3") { 1024 e.preventDefault(); 1025 showLoadModal(); 1026 } 1027 // Escape key - Close any open modal 1028 else if (e.key === "Escape") { 1029 document.querySelectorAll(".modal").forEach((modal) => { 1030 if (modal.style.display === "block") { 1031 hideModal(modal.id); 1032 // Return focus to the editor after closing modal 1033 const editor = 1034 document.getElementById("codeEditor"); 1035 if (editor) { 1036 editor.focus(); 1037 } 1038 } 1039 }); 1040 } 1041 }); 1042 } 1043 1044 // Define the editor key handler function 1045 function editorKeyHandler(e) { 1046 if (e.ctrlKey && e.key === "Enter") { 1047 e.preventDefault(); 1048 generateAndExecute(); 1049 } 1050 } 1051 1052 // Set up the handlers immediately 1053 setupKeyboardHandlers(); 1054 1055 // Also ensure they're set up when the DOM is fully loaded 1056 // Modal functions 1057 function showModal(modalId) { 1058 const modal = document.getElementById(modalId); 1059 if (modal) { 1060 modal.style.display = "block"; 1061 // Prevent body scrolling when modal is open 1062 document.body.style.overflow = "hidden"; 1063 1064 // Make the modal appear with a nice fade-in effect 1065 modal.style.opacity = "0"; 1066 setTimeout(() => { 1067 modal.style.opacity = "1"; 1068 }, 10); 1069 1070 // Add/refresh click handler for closing when clicking outside 1071 const outsideClickHandler = function (e) { 1072 if (e.target === modal) { 1073 hideModal(modalId); 1074 } 1075 }; 1076 // Remove any existing handlers to avoid duplicates 1077 modal.removeEventListener("click", outsideClickHandler); 1078 modal.addEventListener("click", outsideClickHandler); 1079 1080 // Trap focus within the modal 1081 const focusableElements = modal.querySelectorAll( 1082 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"]), .modal-close, .button', 1083 ); 1084 if (focusableElements.length > 0) { 1085 const firstElement = focusableElements[0]; 1086 const lastElement = 1087 focusableElements[focusableElements.length - 1]; 1088 1089 // Focus the first element 1090 setTimeout(() => { 1091 firstElement.focus(); 1092 }, 100); 1093 1094 // Add key event handler for tab key to trap focus 1095 const handleTabKey = function (e) { 1096 if (e.key === "Tab") { 1097 if ( 1098 e.shiftKey && 1099 document.activeElement === firstElement 1100 ) { 1101 e.preventDefault(); 1102 lastElement.focus(); 1103 } else if ( 1104 !e.shiftKey && 1105 document.activeElement === lastElement 1106 ) { 1107 e.preventDefault(); 1108 firstElement.focus(); 1109 } 1110 } 1111 }; 1112 1113 // Remove any existing handler first 1114 modal.removeEventListener("keydown", handleTabKey); 1115 modal.addEventListener("keydown", handleTabKey); 1116 1117 // Store the handler on the modal object for later cleanup 1118 modal._tabHandler = handleTabKey; 1119 } 1120 } 1121 } 1122 1123 function hideModal(modalId) { 1124 const modal = document.getElementById(modalId); 1125 if (modal) { 1126 // Fade out effect 1127 modal.style.opacity = "0"; 1128 1129 // Hide the modal after animation 1130 setTimeout(() => { 1131 modal.style.display = "none"; 1132 // Restore body scrolling when modal is closed 1133 document.body.style.overflow = "auto"; 1134 }, 200); 1135 1136 // Return focus to editor when modal is closed 1137 const editor = document.getElementById("codeEditor"); 1138 if (editor) { 1139 editor.focus(); 1140 } 1141 1142 // Clear any error messages 1143 const messageElements = document.querySelectorAll( 1144 "#save-message, #load-message", 1145 ); 1146 messageElements.forEach((el) => { 1147 if (el) el.textContent = ""; 1148 }); 1149 } 1150 } 1151 1152 function showHelp() { 1153 showModal("help-modal"); 1154 } 1155 1156 function showSaveModal() { 1157 document.getElementById("save-name").value = ""; 1158 showModal("save-modal"); 1159 // Focus the input field and select any existing text 1160 setTimeout(() => { 1161 const saveNameInput = document.getElementById("save-name"); 1162 saveNameInput.focus(); 1163 saveNameInput.select(); 1164 }, 100); 1165 } 1166 1167 function showLoadModal() { 1168 // Populate the load list 1169 const loadList = document.getElementById("load-list"); 1170 loadList.innerHTML = ""; 1171 1172 // Get all keys in localStorage that start with 'plastic-save-' 1173 const saves = []; 1174 for (let i = 0; i < localStorage.length; i++) { 1175 const key = localStorage.key(i); 1176 if (key && key.startsWith("plastic-save-")) { 1177 const saveName = key.replace("plastic-save-", ""); 1178 let timestamp = null; 1179 1180 // Try to extract timestamp if it's a JSON object 1181 try { 1182 const saveData = JSON.parse( 1183 localStorage.getItem(key), 1184 ); 1185 timestamp = saveData.timestamp; 1186 } catch (e) { 1187 // Old format, no timestamp available 1188 } 1189 1190 saves.push({ 1191 key, 1192 name: saveName, 1193 timestamp: timestamp, 1194 }); 1195 } 1196 } 1197 1198 if (saves.length === 0) { 1199 loadList.innerHTML = 1200 '<div class="save-item">No saves found</div>'; 1201 } else { 1202 // Sort saves by timestamp (newest first) or alphabetically if no timestamp 1203 saves.sort((a, b) => { 1204 if (a.timestamp && b.timestamp) { 1205 return b.timestamp - a.timestamp; // Newest first 1206 } else if (a.timestamp) { 1207 return -1; // a comes first 1208 } else if (b.timestamp) { 1209 return 1; // b comes first 1210 } else { 1211 return a.name.localeCompare(b.name); // Alphabetically 1212 } 1213 }); 1214 1215 saves.forEach((save) => { 1216 const saveItem = document.createElement("div"); 1217 saveItem.className = "save-item"; 1218 1219 // Format timestamp if available 1220 let dateStr = ""; 1221 if (save.timestamp) { 1222 const date = new Date(save.timestamp); 1223 dateStr = ` <span style="font-size: 12px; color: #808080;">(${date.toLocaleString()})</span>`; 1224 } 1225 1226 saveItem.innerHTML = ` 1227 <span>${save.name}${dateStr}</span> 1228 <div> 1229 <span class="hotkey" onclick="loadSave('${save.key}')" tabindex="0" role="button" aria-label="Load save ${save.name}">Load</span> 1230 <span class="separator">|</span> 1231 <span class="hotkey" onclick="deleteSave('${save.key}')" tabindex="0" role="button" aria-label="Delete save ${save.name}">Delete</span> 1232 </div> 1233 `; 1234 // Add double-click support to load the save directly 1235 saveItem.addEventListener("dblclick", () => 1236 loadSave(save.key), 1237 ); 1238 loadList.appendChild(saveItem); 1239 }); 1240 } 1241 1242 showModal("load-modal"); 1243 } 1244 1245 function saveCurrentHTML() { 1246 const saveName = document 1247 .getElementById("save-name") 1248 .value.trim(); 1249 if (!saveName) { 1250 const saveMessage = document.getElementById("save-message"); 1251 saveMessage.textContent = 1252 "Please enter a name for your save"; 1253 saveMessage.style.color = "#ff0000"; 1254 setTimeout(() => { 1255 saveMessage.textContent = ""; 1256 }, 3000); 1257 return; 1258 } 1259 1260 try { 1261 // Ensure all modals are closed in the saved HTML 1262 // First make a clone of the current document state 1263 const tempDiv = document.createElement("div"); 1264 tempDiv.innerHTML = document.documentElement.outerHTML; 1265 1266 // Find and hide all modals in the clone 1267 const modalElements = tempDiv.querySelectorAll(".modal"); 1268 modalElements.forEach((modal) => { 1269 modal.style.display = "none"; 1270 }); 1271 1272 // Save the current state 1273 const saveData = { 1274 html: tempDiv.innerHTML, 1275 timestamp: new Date().getTime(), 1276 name: saveName, 1277 }; 1278 1279 localStorage.setItem( 1280 `plastic-save-${saveName}`, 1281 JSON.stringify(saveData), 1282 ); 1283 1284 const saveMessage = document.getElementById("save-message"); 1285 saveMessage.textContent = `Saved as "${saveName}"`; 1286 saveMessage.style.color = "#008000"; 1287 setTimeout(() => { 1288 saveMessage.textContent = ""; 1289 hideModal("save-modal"); 1290 }, 1500); 1291 } catch (e) { 1292 const saveMessage = document.getElementById("save-message"); 1293 saveMessage.textContent = `Error saving: ${e.message}`; 1294 saveMessage.style.color = "#ff0000"; 1295 setTimeout(() => { 1296 saveMessage.textContent = ""; 1297 }, 3000); 1298 } 1299 } 1300 1301 // Add keyboard support for modals 1302 document.addEventListener("DOMContentLoaded", function () { 1303 const saveNameInput = document.getElementById("save-name"); 1304 if (saveNameInput) { 1305 // Remove any existing listeners to prevent duplicates 1306 const saveInputHandler = function (e) { 1307 if (e.key === "Enter") { 1308 e.preventDefault(); 1309 saveCurrentHTML(); 1310 } 1311 }; 1312 1313 saveNameInput.removeEventListener( 1314 "keydown", 1315 saveInputHandler, 1316 ); 1317 saveNameInput.addEventListener("keydown", saveInputHandler); 1318 } 1319 1320 document 1321 .querySelectorAll(".modal-close") 1322 .forEach((closeBtn) => { 1323 closeBtn.addEventListener("keydown", function (e) { 1324 if (e.key === "Enter" || e.key === " ") { 1325 e.preventDefault(); 1326 const modalId = this.closest(".modal").id; 1327 hideModal(modalId); 1328 } 1329 }); 1330 }); 1331 }); 1332 1333 function loadSave(key) { 1334 try { 1335 const savedData = localStorage.getItem(key); 1336 if (savedData) { 1337 let saveObj; 1338 1339 try { 1340 saveObj = JSON.parse(savedData); 1341 } catch (e) { 1342 saveObj = { html: savedData }; 1343 } 1344 1345 if ( 1346 confirm( 1347 "Loading will replace the current page. Continue?", 1348 ) 1349 ) { 1350 hideModal("load-modal"); 1351 1352 const scriptToAdd = ` 1353 <script> 1354 document.addEventListener('DOMContentLoaded', function() { 1355 // Ensure all modals are hidden 1356 document.querySelectorAll('.modal').forEach(modal => { 1357 modal.style.display = "none"; 1358 }); 1359 1360 // Re-setup keyboard handlers 1361 if (typeof setupKeyboardHandlers === 'function') { 1362 setupKeyboardHandlers(); 1363 } 1364 }); 1365 <\/script> 1366 `; 1367 1368 // Load the saved HTML with the added script 1369 document.open(); 1370 document.write(saveObj.html + scriptToAdd); 1371 document.close(); 1372 } 1373 } else { 1374 const loadMessage = 1375 document.getElementById("load-message"); 1376 loadMessage.textContent = "Save not found"; 1377 loadMessage.style.color = "#ff0000"; 1378 setTimeout(() => { 1379 loadMessage.textContent = ""; 1380 }, 3000); 1381 } 1382 } catch (e) { 1383 const loadMessage = document.getElementById("load-message"); 1384 loadMessage.textContent = `Error loading: ${e.message}`; 1385 loadMessage.style.color = "#ff0000"; 1386 setTimeout(() => { 1387 loadMessage.textContent = ""; 1388 }, 3000); 1389 } 1390 } 1391 </script> 1392 </body> 1393</html>