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:
parent
14bfce710d
commit
45f183df37
3 changed files with 114 additions and 5 deletions
77
app.py
77
app.py
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue