feat: responsive side-by-side input/output layout
- Desktop: input left, output right - Mobile (<600px): stacked vertically 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
123
server.py
123
server.py
@@ -406,9 +406,16 @@ UI_HTML = """
|
||||
.status.running { background: #4d4d1a; color: #facc15; }
|
||||
.status.failed { background: #4d1a1a; color: #f87171; }
|
||||
.status.pending { background: #333; color: #888; }
|
||||
.media-container { margin-top: 12px; }
|
||||
.media-row { display: flex; gap: 16px; margin-top: 12px; flex-wrap: wrap; }
|
||||
.media-box { flex: 1; min-width: 200px; }
|
||||
.media-box label { font-size: 11px; color: #666; display: block; margin-bottom: 4px; }
|
||||
.media-container { }
|
||||
.media-container img, .media-container video {
|
||||
max-width: 100%; max-height: 400px; border-radius: 4px;
|
||||
max-width: 100%; max-height: 300px; border-radius: 4px;
|
||||
}
|
||||
@media (max-width: 600px) {
|
||||
.media-row { flex-direction: column; }
|
||||
.media-box { min-width: 100%; }
|
||||
}
|
||||
.hash { font-family: monospace; font-size: 11px; color: #666; }
|
||||
.info { font-size: 13px; color: #aaa; }
|
||||
@@ -466,32 +473,44 @@ async def ui_runs():
|
||||
</div>
|
||||
''')
|
||||
|
||||
# Show input
|
||||
if run.inputs:
|
||||
input_hash = run.inputs[0]
|
||||
html_parts.append(f'<div class="hash">Input: {input_hash[:32]}...</div>')
|
||||
input_cache_path = CACHE_DIR / input_hash
|
||||
if input_cache_path.exists():
|
||||
input_media_type = detect_media_type(input_cache_path)
|
||||
html_parts.append('<div class="media-container">')
|
||||
if input_media_type == "video":
|
||||
html_parts.append(f'<video src="/cache/{input_hash}" controls muted loop style="max-height:200px;"></video>')
|
||||
elif input_media_type == "image":
|
||||
html_parts.append(f'<img src="/cache/{input_hash}" alt="input" style="max-height:200px;">')
|
||||
html_parts.append('</div>')
|
||||
# Show input and output side by side
|
||||
has_input = run.inputs and (CACHE_DIR / run.inputs[0]).exists()
|
||||
has_output = run.status == "completed" and run.output_hash and (CACHE_DIR / run.output_hash).exists()
|
||||
|
||||
# Show output if completed
|
||||
if run.status == "completed" and run.output_hash:
|
||||
cache_path = CACHE_DIR / run.output_hash
|
||||
if cache_path.exists():
|
||||
media_type = detect_media_type(cache_path)
|
||||
html_parts.append(f'<div class="hash">Output: {run.output_hash[:32]}...</div>')
|
||||
html_parts.append('<div class="media-container">')
|
||||
if media_type == "video":
|
||||
html_parts.append(f'<video src="/cache/{run.output_hash}" controls autoplay muted loop></video>')
|
||||
elif media_type == "image":
|
||||
html_parts.append(f'<img src="/cache/{run.output_hash}" alt="{run.output_name}">')
|
||||
html_parts.append('</div>')
|
||||
if has_input or has_output:
|
||||
html_parts.append('<div class="media-row">')
|
||||
|
||||
# Input box
|
||||
if has_input:
|
||||
input_hash = run.inputs[0]
|
||||
input_media_type = detect_media_type(CACHE_DIR / input_hash)
|
||||
html_parts.append(f'''
|
||||
<div class="media-box">
|
||||
<label>Input: {input_hash[:24]}...</label>
|
||||
<div class="media-container">
|
||||
''')
|
||||
if input_media_type == "video":
|
||||
html_parts.append(f'<video src="/cache/{input_hash}" controls muted loop></video>')
|
||||
elif input_media_type == "image":
|
||||
html_parts.append(f'<img src="/cache/{input_hash}" alt="input">')
|
||||
html_parts.append('</div></div>')
|
||||
|
||||
# Output box
|
||||
if has_output:
|
||||
output_hash = run.output_hash
|
||||
output_media_type = detect_media_type(CACHE_DIR / output_hash)
|
||||
html_parts.append(f'''
|
||||
<div class="media-box">
|
||||
<label>Output: {output_hash[:24]}...</label>
|
||||
<div class="media-container">
|
||||
''')
|
||||
if output_media_type == "video":
|
||||
html_parts.append(f'<video src="/cache/{output_hash}" controls autoplay muted loop></video>')
|
||||
elif output_media_type == "image":
|
||||
html_parts.append(f'<img src="/cache/{output_hash}" alt="output">')
|
||||
html_parts.append('</div></div>')
|
||||
|
||||
html_parts.append('</div>')
|
||||
|
||||
# Show error if failed
|
||||
if run.status == "failed" and run.error:
|
||||
@@ -544,30 +563,34 @@ async def ui_run_detail(run_id: str):
|
||||
</div>
|
||||
'''
|
||||
|
||||
if run.inputs:
|
||||
input_hash = run.inputs[0]
|
||||
html += f'<div class="hash">Input: {input_hash[:32]}...</div>'
|
||||
input_cache_path = CACHE_DIR / input_hash
|
||||
if input_cache_path.exists():
|
||||
input_media_type = detect_media_type(input_cache_path)
|
||||
html += '<div class="media-container">'
|
||||
if input_media_type == "video":
|
||||
html += f'<video src="/cache/{input_hash}" controls muted loop style="max-height:200px;"></video>'
|
||||
elif input_media_type == "image":
|
||||
html += f'<img src="/cache/{input_hash}" alt="input" style="max-height:200px;">'
|
||||
html += '</div>'
|
||||
# Show input and output side by side
|
||||
has_input = run.inputs and (CACHE_DIR / run.inputs[0]).exists()
|
||||
has_output = run.status == "completed" and run.output_hash and (CACHE_DIR / run.output_hash).exists()
|
||||
|
||||
if run.status == "completed" and run.output_hash:
|
||||
cache_path = CACHE_DIR / run.output_hash
|
||||
if cache_path.exists():
|
||||
media_type = detect_media_type(cache_path)
|
||||
html += f'<div class="hash">Output: {run.output_hash[:32]}...</div>'
|
||||
html += '<div class="media-container">'
|
||||
if media_type == "video":
|
||||
html += f'<video src="/cache/{run.output_hash}" controls autoplay muted loop></video>'
|
||||
elif media_type == "image":
|
||||
html += f'<img src="/cache/{run.output_hash}" alt="{run.output_name}">'
|
||||
html += '</div>'
|
||||
if has_input or has_output:
|
||||
html += '<div class="media-row">'
|
||||
|
||||
if has_input:
|
||||
input_hash = run.inputs[0]
|
||||
input_media_type = detect_media_type(CACHE_DIR / input_hash)
|
||||
html += f'<div class="media-box"><label>Input: {input_hash[:24]}...</label><div class="media-container">'
|
||||
if input_media_type == "video":
|
||||
html += f'<video src="/cache/{input_hash}" controls muted loop></video>'
|
||||
elif input_media_type == "image":
|
||||
html += f'<img src="/cache/{input_hash}" alt="input">'
|
||||
html += '</div></div>'
|
||||
|
||||
if has_output:
|
||||
output_hash = run.output_hash
|
||||
output_media_type = detect_media_type(CACHE_DIR / output_hash)
|
||||
html += f'<div class="media-box"><label>Output: {output_hash[:24]}...</label><div class="media-container">'
|
||||
if output_media_type == "video":
|
||||
html += f'<video src="/cache/{output_hash}" controls autoplay muted loop></video>'
|
||||
elif output_media_type == "image":
|
||||
html += f'<img src="/cache/{output_hash}" alt="output">'
|
||||
html += '</div></div>'
|
||||
|
||||
html += '</div>'
|
||||
|
||||
if run.status == "failed" and run.error:
|
||||
html += f'<div class="info" style="color: #f87171;">Error: {run.error}</div>'
|
||||
|
||||
Reference in New Issue
Block a user