self modifying website

feat: add tool calls

dunkirk.sh 99b77929 3cfc384e

verified
+126 -15
+126 -15
index.html
··· 300 300 </div> 301 301 302 302 <script> 303 + // Tool call system for AI to interact with the page 304 + window.toolCallbacks = { 305 + replaceElement: function(selector, newHTML) { 306 + const element = document.querySelector(selector); 307 + if (element) { 308 + element.outerHTML = newHTML; 309 + return { success: true, message: `Replaced element: ${selector}` }; 310 + } 311 + return { success: false, message: `Element not found: ${selector}` }; 312 + }, 313 + 314 + updateElement: function(selector, newContent) { 315 + const element = document.querySelector(selector); 316 + if (element) { 317 + element.innerHTML = newContent; 318 + return { success: true, message: `Updated element: ${selector}` }; 319 + } 320 + return { success: false, message: `Element not found: ${selector}` }; 321 + }, 322 + 323 + addElement: function(parentSelector, newHTML, position = 'beforeend') { 324 + const parent = document.querySelector(parentSelector); 325 + if (parent) { 326 + parent.insertAdjacentHTML(position, newHTML); 327 + return { success: true, message: `Added element to: ${parentSelector}` }; 328 + } 329 + return { success: false, message: `Parent not found: ${parentSelector}` }; 330 + }, 331 + 332 + removeElement: function(selector) { 333 + const element = document.querySelector(selector); 334 + if (element) { 335 + element.remove(); 336 + return { success: true, message: `Removed element: ${selector}` }; 337 + } 338 + return { success: false, message: `Element not found: ${selector}` }; 339 + }, 340 + 341 + updateStyle: function(selector, styleObj) { 342 + const element = document.querySelector(selector); 343 + if (element) { 344 + Object.assign(element.style, styleObj); 345 + return { success: true, message: `Updated styles for: ${selector}` }; 346 + } 347 + return { success: false, message: `Element not found: ${selector}` }; 348 + }, 349 + 350 + executeJS: function(code) { 351 + try { 352 + const result = eval(code); 353 + return { success: true, message: 'JavaScript executed', result: result }; 354 + } catch (error) { 355 + return { success: false, message: `JS Error: ${error.message}` }; 356 + } 357 + } 358 + }; 359 + 360 + function executeToolCall(toolCall) { 361 + const { function: func, arguments: args } = toolCall; 362 + console.log('Executing tool call:', func, args); 363 + 364 + if (window.toolCallbacks[func]) { 365 + // Handle parameter name variations 366 + if (func === 'updateStyle' && args.styleObj) { 367 + // Convert styleObj to the expected parameter name 368 + return window.toolCallbacks[func](args.selector, args.styleObj); 369 + } else { 370 + // Use all argument values in order 371 + return window.toolCallbacks[func](...Object.values(args)); 372 + } 373 + } 374 + return { success: false, message: `Unknown tool: ${func}` }; 375 + } 376 + 303 377 async function generateAndExecute() { 304 378 const userPrompt = document.getElementById("codeEditor").value; 305 379 const statusDiv = document.getElementById("statusDisplay"); ··· 325 399 messages: [ 326 400 { 327 401 role: "user", 328 - content: `Here is the current HTML page:\n\n${currentPageHTML}\n\nUser request: "${userPrompt}"\n\nGenerate HTML code that fits the DOS/retro terminal aesthetic for this request. Use flat colors like #00ff41 (green), #ffff00 (yellow), #00ffff (cyan), #ffffff (white), #c0c0c0 (gray), #000080 (blue), and #ff0000 (red). Use monospace fonts and simple borders. Make it look like it belongs in a 1990s DOS program. Only return the HTML code to add, no explanations.`, 402 + 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:\n1. replaceElement(selector, newHTML) - Replace an element\n2. updateElement(selector, newContent) - Update element content \n3. addElement(parentSelector, newHTML, position) - Add element\n4. removeElement(selector) - Remove an element\n5. updateStyle(selector, styleObj) - Update CSS styles\n6. executeJS(code) - Run JavaScript code\n\nUse DOS/retro aesthetic with flat colors: #000000 (black), #ffffff (white), #c0c0c0 (gray), #000080 (blue), #ff0000 (red). Use monospace fonts.`, 329 403 }, 330 404 ], 331 405 }), ··· 341 415 statusDiv.textContent = "AI PROCESSING..."; 342 416 343 417 const data = await response.json(); 344 - console.log("API Response:", data); // Debug log 418 + console.log("API Response:", data); 345 419 346 - let generatedCode; 420 + let generatedContent; 347 421 if ( 348 422 data.choices && 349 423 data.choices[0] && 350 424 data.choices[0].message 351 425 ) { 352 - generatedCode = data.choices[0].message.content; 426 + generatedContent = data.choices[0].message.content; 353 427 } else if (data.content) { 354 - generatedCode = data.content; 428 + generatedContent = data.content; 355 429 } else if (data.response) { 356 - generatedCode = data.response; 430 + generatedContent = data.response; 357 431 } else if (typeof data === "string") { 358 - generatedCode = data; 432 + generatedContent = data; 359 433 } else { 360 434 throw new Error("Unexpected API response format"); 361 435 } 362 436 363 - // Clean up the code (remove markdown formatting if present) 364 - let cleanCode = generatedCode 365 - .replace(/```html\n?/g, "") 437 + statusDiv.textContent = "EXECUTING COMMANDS..."; 438 + 439 + // Clean up response and extract JSON if present 440 + let cleanResponse = generatedContent.trim(); 441 + 442 + // Remove markdown formatting 443 + cleanResponse = cleanResponse 444 + .replace(/```json\n?/g, "") 366 445 .replace(/```\n?/g, ""); 367 446 368 - statusDiv.textContent = "INJECTING CODE..."; 447 + // Try to extract JSON from mixed content 448 + const jsonMatch = cleanResponse.match(/\{[\s\S]*"tool_calls"[\s\S]*\}/); 449 + if (jsonMatch) { 450 + cleanResponse = jsonMatch[0]; 451 + } 369 452 370 - // Insert the generated code 371 - document.querySelector(".content").innerHTML += cleanCode; 372 - document.getElementById("codeEditor").value = ""; 453 + console.log('Cleaned response:', cleanResponse); 454 + 455 + // Check if response contains tool calls 456 + try { 457 + const toolResponse = JSON.parse(cleanResponse); 458 + if (toolResponse.tool_calls && Array.isArray(toolResponse.tool_calls)) { 459 + // Execute tool calls 460 + const results = []; 461 + for (const toolCall of toolResponse.tool_calls) { 462 + const result = executeToolCall(toolCall); 463 + results.push(result); 464 + console.log('Tool call result:', result); 465 + } 466 + statusDiv.textContent = `EXECUTED ${results.length} COMMANDS`; 467 + } else { 468 + throw new Error("Invalid tool call format"); 469 + } 470 + } catch (jsonError) { 471 + console.log('JSON parse error:', jsonError); 472 + console.log('Raw response:', generatedContent); 473 + console.log('Attempting to parse as HTML...'); 474 + 475 + // Not JSON, treat as HTML code 476 + let cleanCode = generatedContent 477 + .replace(/```html\n?/g, "") 478 + .replace(/```\n?/g, ""); 479 + 480 + statusDiv.textContent = "INJECTING CODE..."; 481 + document.querySelector(".content").innerHTML += cleanCode; 482 + statusDiv.textContent = "CODE EXECUTION SUCCESSFUL"; 483 + } 373 484 374 - statusDiv.textContent = "CODE EXECUTION SUCCESSFUL"; 485 + document.getElementById("codeEditor").value = ""; 375 486 376 487 // Clear status after 3 seconds 377 488 setTimeout(() => {