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 <noreply@anthropic.com>
This commit is contained in:
Russell 2025-11-11 17:43:52 -05:00 committed by GitHub
parent 14bfce710d
commit 45f183df37
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 114 additions and 5 deletions

77
app.py
View file

@ -331,6 +331,83 @@ def get_activities():
return jsonify({"activities": 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/<room_name>") @app.route("/chat/<room_name>")
def chat(room_name): def chat(room_name):
# Query all rooms so that newest is first. # Query all rooms so that newest is first.

View file

@ -1495,7 +1495,7 @@ async function executeCodeBlock(code, blockElement, playButton) {
if (job.status === 'completed') { if (job.status === 'completed') {
const result = job.result; const result = job.result;
displayExecutionResults(result, resultsContainer, language); displayExecutionResults(result, resultsContainer, language, code);
break; break;
} }
@ -1520,7 +1520,7 @@ async function executeCodeBlock(code, blockElement, playButton) {
stderr: job.result.stderr || '', stderr: job.result.stderr || '',
artifact: artifact artifact: artifact
}; };
displayExecutionResults(resultForDisplay, resultsContainer, language); displayExecutionResults(resultForDisplay, resultsContainer, language, code);
// Prepend error message to the results // Prepend error message to the results
const errorDiv = document.createElement('div'); const errorDiv = document.createElement('div');
@ -1569,7 +1569,7 @@ function sleep(ms) {
} }
// Helper function to display execution results // Helper function to display execution results
function displayExecutionResults(result, resultsContainer, language) { function displayExecutionResults(result, resultsContainer, language, code) {
// Format and display results // Format and display results
let outputHtml = ''; let outputHtml = '';
@ -1618,8 +1618,36 @@ function displayExecutionResults(result, resultsContainer, language) {
// Show and populate the download button // Show and populate the download button
if (downloadButton) { if (downloadButton) {
downloadButton.style.display = 'inline-block'; downloadButton.style.display = 'inline-block';
downloadButton.onclick = () => { downloadButton.onclick = async () => {
try { 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 // Convert base64 to binary
const binaryString = atob(result.artifact.data); const binaryString = atob(result.artifact.data);
const bytes = new Uint8Array(binaryString.length); const bytes = new Uint8Array(binaryString.length);
@ -1632,7 +1660,7 @@ function displayExecutionResults(result, resultsContainer, language) {
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
const a = document.createElement('a'); const a = document.createElement('a');
a.href = url; a.href = url;
a.download = result.artifact.filename || 'compiled_binary'; a.download = filename;
document.body.appendChild(a); document.body.appendChild(a);
a.click(); a.click();
document.body.removeChild(a); document.body.removeChild(a);

View file

@ -35,3 +35,7 @@ export MODEL_API_KEY_8="gone"
# Anthropic Platform # Anthropic Platform
export MODEL_ENDPOINT_9="https://api.anthropic.com/v1" export MODEL_ENDPOINT_9="https://api.anthropic.com/v1"
export MODEL_API_KEY_9="gone" 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"