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})
|
||||
|
||||
|
||||
@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>")
|
||||
def chat(room_name):
|
||||
# Query all rooms so that newest is first.
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue