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>