Fix artifact API - use correct parameter and field names
Correct the artifact implementation to match Unsandbox API: - Request parameter: return_artifact (boolean, singular) - Response field: artifacts (array, plural) - Data field: content_base64 (not data/content) Changes: - Backend: Changed artifacts to return_artifact in request body - Frontend: Use return_artifact in execute request - Frontend: Look for artifacts array in response (not artifact singleton) - Frontend: Decode content_base64 field (not data/content) - Frontend: Use filename field (primary) over name - Remove debug console.log statements - Update CLAUDE.md with correct API contract The API parameter is singular but response is plural - this is intentional design by Unsandbox API. Request enables artifact collection, response returns array of generated artifacts.
This commit is contained in:
parent
b53ee2168b
commit
c6e6e81c3c
3 changed files with 31 additions and 34 deletions
31
CLAUDE.md
31
CLAUDE.md
|
|
@ -232,9 +232,10 @@ lang = un.detect_language("script.py") # Returns "python"
|
|||
OpenCompletion supports artifacts generated during code execution (compiled binaries, images, videos, etc.).
|
||||
|
||||
**How Artifacts Work**:
|
||||
- Pass `artifacts: true` in execution requests to enable artifact collection
|
||||
- Artifacts are returned as **base64-encoded data** directly in the job response payload
|
||||
- Pass `return_artifact: true` (boolean) in execution requests to enable artifact collection
|
||||
- Response includes `artifacts` array with base64-encoded data
|
||||
- No separate download endpoint needed - artifacts are embedded in the response
|
||||
- Note: Parameter is singular `return_artifact` but response field is plural `artifacts`
|
||||
|
||||
**Artifact Response Format**:
|
||||
```json
|
||||
|
|
@ -246,15 +247,21 @@ OpenCompletion supports artifacts generated during code execution (compiled bina
|
|||
"exit_code": 0,
|
||||
"artifacts": [
|
||||
{
|
||||
"name": "output.png",
|
||||
"type": "image/png",
|
||||
"data": "base64string...",
|
||||
"filename": "output.png",
|
||||
"mime_type": "image/png",
|
||||
"content_base64": "iVBORw0KGgoAAAANS...",
|
||||
"size": 12345
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Artifact Fields**:
|
||||
- `filename`: Original filename
|
||||
- `mime_type`: MIME type (e.g., "image/png", "application/octet-stream")
|
||||
- `content_base64`: Base64-encoded file content
|
||||
- `size`: File size in bytes
|
||||
|
||||
**Artifact Types**:
|
||||
- **Binaries**: Compiled executables (C, C++, Rust, Go, etc.)
|
||||
- **Images**: PNG, JPG, GIF, SVG generated by code
|
||||
|
|
@ -262,22 +269,22 @@ OpenCompletion supports artifacts generated during code execution (compiled bina
|
|||
- **Text/Data**: JSON, CSV, TXT output files
|
||||
|
||||
**Frontend Features**:
|
||||
- Download button decodes base64 and triggers browser download
|
||||
- View button decodes base64 and displays inline:
|
||||
- **Images**: Rendered as data URLs
|
||||
- Download button decodes `content_base64` and triggers browser download
|
||||
- View button decodes `content_base64` and displays inline:
|
||||
- **Images**: Rendered as data URLs (`data:image/png;base64,...`)
|
||||
- **Videos**: Rendered as blob URLs with controls
|
||||
- **Text**: Decoded and displayed in formatted `<pre>` blocks
|
||||
- View button disabled for binary executables
|
||||
- File size and type information displayed
|
||||
- File size and MIME type information displayed
|
||||
|
||||
#### Frontend Integration
|
||||
|
||||
- Add play button (▶) next to copy button on code blocks
|
||||
- Execute code when user clicks play button with `artifacts: true` parameter
|
||||
- Execute code when user clicks play button with `return_artifact: true` parameter
|
||||
- Display execution results inline below code block
|
||||
- Show stdout, stderr, and exit_code separately
|
||||
- Display artifacts section with download/view buttons
|
||||
- Decode base64 artifacts for inline viewing and downloads
|
||||
- Display artifact section with download/view buttons
|
||||
- Decode `content_base64` field for inline viewing and downloads
|
||||
- Images displayed as data URLs, videos as blob URLs
|
||||
- Use syntax highlighting for output
|
||||
- Handle timeouts gracefully (60s default)
|
||||
|
|
|
|||
2
app.py
2
app.py
|
|
@ -1276,7 +1276,7 @@ def proxy_code_execute():
|
|||
request_body = {
|
||||
"language": language,
|
||||
"code": code,
|
||||
"return_artifacts": True,
|
||||
"return_artifact": True,
|
||||
}
|
||||
|
||||
# Use SDK's internal _make_request for full parameter support
|
||||
|
|
|
|||
|
|
@ -1708,7 +1708,7 @@ async function executeCodeBlock(code, blockElement, playButton) {
|
|||
body: JSON.stringify({
|
||||
language: language,
|
||||
code: code,
|
||||
artifacts: true // Request artifacts (binaries, images, videos, etc)
|
||||
return_artifact: true // Request artifacts (binaries, images, videos, etc)
|
||||
})
|
||||
});
|
||||
|
||||
|
|
@ -1946,10 +1946,6 @@ function displayExecutionResults(result, resultsContainer, language, code) {
|
|||
|
||||
// Handle artifacts (binaries, images, videos, etc.)
|
||||
if (artifacts && artifacts.length > 0) {
|
||||
// Debug: log artifact structure
|
||||
console.log('Artifacts received:', artifacts);
|
||||
console.log('First artifact:', artifacts[0]);
|
||||
|
||||
const artifactsDiv = document.createElement('div');
|
||||
artifactsDiv.style.marginTop = '12px';
|
||||
artifactsDiv.style.borderTop = '1px solid var(--border-color)';
|
||||
|
|
@ -1963,9 +1959,6 @@ function displayExecutionResults(result, resultsContainer, language, code) {
|
|||
artifactsDiv.appendChild(artifactsTitle);
|
||||
|
||||
artifacts.forEach((artifact, index) => {
|
||||
// Debug: log each artifact
|
||||
console.log(`Artifact ${index}:`, artifact);
|
||||
console.log(`Artifact ${index} keys:`, Object.keys(artifact));
|
||||
const artifactItem = document.createElement('div');
|
||||
artifactItem.style.marginBottom = '8px';
|
||||
artifactItem.style.padding = '8px';
|
||||
|
|
@ -1976,17 +1969,18 @@ function displayExecutionResults(result, resultsContainer, language, code) {
|
|||
const artifactName = document.createElement('div');
|
||||
artifactName.style.fontWeight = 'bold';
|
||||
artifactName.style.marginBottom = '4px';
|
||||
artifactName.textContent = artifact.name || artifact.filename || `Artifact ${index + 1}`;
|
||||
artifactName.textContent = artifact.filename || artifact.name || `Artifact ${index + 1}`;
|
||||
artifactItem.appendChild(artifactName);
|
||||
|
||||
// Artifact type/size info
|
||||
if (artifact.type || artifact.size) {
|
||||
const mimeType = artifact.mime_type || artifact.type || '';
|
||||
if (mimeType || artifact.size) {
|
||||
const artifactInfo = document.createElement('div');
|
||||
artifactInfo.style.fontSize = '12px';
|
||||
artifactInfo.style.color = 'var(--text-muted)';
|
||||
artifactInfo.style.marginBottom = '8px';
|
||||
let infoText = '';
|
||||
if (artifact.type) infoText += `Type: ${artifact.type}`;
|
||||
if (mimeType) infoText += `Type: ${mimeType}`;
|
||||
if (artifact.size) infoText += ` | Size: ${formatFileSize(artifact.size)}`;
|
||||
artifactInfo.textContent = infoText;
|
||||
artifactItem.appendChild(artifactInfo);
|
||||
|
|
@ -1998,7 +1992,6 @@ function displayExecutionResults(result, resultsContainer, language, code) {
|
|||
buttonsDiv.style.gap = '8px';
|
||||
|
||||
// Determine if artifact is viewable (images, videos, text)
|
||||
const mimeType = artifact.mime_type || artifact.type || '';
|
||||
const isImage = mimeType.startsWith('image/');
|
||||
const isVideo = mimeType.startsWith('video/');
|
||||
const isBinary = mimeType.includes('octet-stream') || mimeType.includes('executable');
|
||||
|
|
@ -2126,12 +2119,8 @@ function formatFileSize(bytes) {
|
|||
// Helper function to download artifact
|
||||
async function downloadArtifact(artifact) {
|
||||
try {
|
||||
console.log('Download artifact called with:', artifact);
|
||||
console.log('Artifact keys:', Object.keys(artifact));
|
||||
|
||||
// Artifacts come as base64 in the response
|
||||
const base64Data = artifact.data || artifact.content;
|
||||
console.log('Base64 data found:', base64Data ? `yes (${base64Data.length} chars)` : 'no');
|
||||
// Artifacts come as base64 in the response (field: content_base64)
|
||||
const base64Data = artifact.content_base64 || artifact.data || artifact.content;
|
||||
|
||||
if (!base64Data) {
|
||||
console.error('No base64 data in artifact:', artifact);
|
||||
|
|
@ -2154,7 +2143,7 @@ async function downloadArtifact(artifact) {
|
|||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = artifact.name || artifact.filename || 'download';
|
||||
a.download = artifact.filename || artifact.name || 'download';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
|
|
@ -2168,9 +2157,10 @@ async function downloadArtifact(artifact) {
|
|||
// Helper function to view artifact inline
|
||||
async function viewArtifact(artifact, artifactItem, isImage, isVideo) {
|
||||
try {
|
||||
// Artifacts come as base64 in the response
|
||||
const base64Data = artifact.data || artifact.content;
|
||||
// Artifacts come as base64 in the response (field: content_base64)
|
||||
const base64Data = artifact.content_base64 || artifact.data || artifact.content;
|
||||
if (!base64Data) {
|
||||
console.error('No base64 data in artifact:', artifact);
|
||||
alert('Artifact data not available');
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue