From 45f183df37c228498329084571a08e963f82d84d Mon Sep 17 00:00:00 2001 From: Russell Date: Tue, 11 Nov 2025 17:43:52 -0500 Subject: [PATCH] Add AI-powered artifact filename generation (#39) * Add AI-powered artifact naming for compiled binaries Implements intelligent filename generation for downloaded binaries using Hermes AI to analyze code and generate meaningful 1-3 word filenames. Changes: - Add /api/generate-artifact-name endpoint that uses MODEL_1 (Hermes) - Modify frontend to call naming API before download - Add ENABLE_AI_ARTIFACT_NAMING environment variable (enabled by default) - Filenames are descriptive (e.g., "fizzbuzz", "hello-world", "prime-checker") - Graceful fallback to "compiled_binary" if naming fails or is disabled The feature can be disabled by setting ENABLE_AI_ARTIFACT_NAMING="false" in environment variables. * Rename env var to ENABLE_CODE_GEN_FILENAMES --------- Co-authored-by: Claude --- app.py | 77 +++++++++++++++++++++++++++++++++++++++++++++ templates/chat.html | 38 +++++++++++++++++++--- vars.sh.sample | 4 +++ 3 files changed, 114 insertions(+), 5 deletions(-) diff --git a/app.py b/app.py index 73d5e1a..aabea29 100644 --- a/app.py +++ b/app.py @@ -331,6 +331,83 @@ def get_activities(): return jsonify({"activities": activities}) +@app.route("/api/generate-artifact-name", methods=["POST"]) +def generate_artifact_name(): + """Generate a meaningful filename for an artifact using AI. + + Returns a 1-3 word filename with dashes based on what the code does. + Respects ENABLE_CODE_GEN_FILENAMES environment variable (enabled by default). + """ + # Check if feature is enabled (default: true) + enabled = os.environ.get("ENABLE_CODE_GEN_FILENAMES", "true").lower() == "true" + if not enabled: + return jsonify({"filename": "compiled_binary"}) + + try: + data = request.get_json() + code = data.get("code", "") + language = data.get("language", "") + + if not code: + return jsonify({"filename": "compiled_binary"}) + + # Use MODEL_1 (Hermes) to generate filename + client, model = get_openai_client_and_model("MODEL_1") + + system_prompt = """You are a filename generator. Given code, generate a SHORT, descriptive filename that represents what the code does. + +Rules: +- Output ONLY the filename, nothing else +- Use 1-3 words maximum +- Use lowercase with dashes between words (e.g., "fizzbuzz" or "hello-world" or "prime-checker") +- NO file extension +- NO explanations or commentary +- Be specific about what the code does + +Examples: +- Code that prints "Hello World" → "hello-world" +- Code that checks for prime numbers → "prime-checker" +- Code that plays FizzBuzz → "fizzbuzz" +- Code that sorts an array → "array-sort" +- Code that calculates factorial → "factorial" +""" + + user_prompt = f"Language: {language}\n\nCode:\n{code}\n\nGenerate filename:" + + response = client.chat.completions.create( + model=model, + messages=[ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": user_prompt} + ], + temperature=0.3, + max_tokens=20 + ) + + filename = response.choices[0].message.content.strip() + + # Clean up the filename (remove quotes, extensions, whitespace) + filename = filename.strip('"\'') + filename = filename.split('.')[0] # Remove any extension + filename = filename.replace(' ', '-') + filename = filename.lower() + + # Validate filename (alphanumeric and dashes only) + import re + if not re.match(r'^[a-z0-9-]+$', filename): + filename = "compiled_binary" + + # Ensure it's not too long (max 50 chars) + if len(filename) > 50: + filename = filename[:50] + + return jsonify({"filename": filename}) + + except Exception as e: + print(f"Error generating artifact name: {e}") + return jsonify({"filename": "compiled_binary"}) + + @app.route("/chat/") def chat(room_name): # Query all rooms so that newest is first. diff --git a/templates/chat.html b/templates/chat.html index 143c5af..53edce0 100644 --- a/templates/chat.html +++ b/templates/chat.html @@ -1495,7 +1495,7 @@ async function executeCodeBlock(code, blockElement, playButton) { if (job.status === 'completed') { const result = job.result; - displayExecutionResults(result, resultsContainer, language); + displayExecutionResults(result, resultsContainer, language, code); break; } @@ -1520,7 +1520,7 @@ async function executeCodeBlock(code, blockElement, playButton) { stderr: job.result.stderr || '', artifact: artifact }; - displayExecutionResults(resultForDisplay, resultsContainer, language); + displayExecutionResults(resultForDisplay, resultsContainer, language, code); // Prepend error message to the results const errorDiv = document.createElement('div'); @@ -1569,7 +1569,7 @@ function sleep(ms) { } // Helper function to display execution results -function displayExecutionResults(result, resultsContainer, language) { +function displayExecutionResults(result, resultsContainer, language, code) { // Format and display results let outputHtml = ''; @@ -1618,8 +1618,36 @@ function displayExecutionResults(result, resultsContainer, language) { // Show and populate the download button if (downloadButton) { downloadButton.style.display = 'inline-block'; - downloadButton.onclick = () => { + downloadButton.onclick = async () => { try { + // Generate AI filename if code is available + let filename = result.artifact.filename || 'compiled_binary'; + + if (code && language) { + try { + const nameResponse = await fetch('/api/generate-artifact-name', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + code: code, + language: language + }) + }); + + if (nameResponse.ok) { + const nameData = await nameResponse.json(); + if (nameData.filename) { + filename = nameData.filename; + } + } + } catch (nameError) { + // If naming fails, fall back to original filename + console.warn('Failed to generate AI filename:', nameError); + } + } + // Convert base64 to binary const binaryString = atob(result.artifact.data); const bytes = new Uint8Array(binaryString.length); @@ -1632,7 +1660,7 @@ function displayExecutionResults(result, resultsContainer, language) { const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; - a.download = result.artifact.filename || 'compiled_binary'; + a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); diff --git a/vars.sh.sample b/vars.sh.sample index 2ec365d..017d2bd 100644 --- a/vars.sh.sample +++ b/vars.sh.sample @@ -35,3 +35,7 @@ export MODEL_API_KEY_8="gone" # Anthropic Platform export MODEL_ENDPOINT_9="https://api.anthropic.com/v1" export MODEL_API_KEY_9="gone" + +# Enable code-generated filenames (enabled by default: "true", disabled: "false") +# When enabled, uses Hermes AI to generate meaningful 1-3 word filenames for downloaded binaries +export ENABLE_CODE_GEN_FILENAMES="true"