Fix scale-selection 500 error (missing BytesIO import) and compact GPU badge

ai_tools.py: add 'from io import BytesIO' — scale_selection and paste
endpoints used BytesIO directly but it was only imported locally in one
unrelated function, causing NameError on every scale call.

provider-badge.js: redesign badge to fit the narrow (~40px) left toolbar.
Was rendering 'GPU · sdxl_offload · Quadro RTX 3000' inline which wrapped
into multiple lines covering tool icons. Now shows a status dot + short
label (SDXL / OAI / Rep…) with all details moved to the hover tooltip.

https://claude.ai/code/session_01WVDg7amsy1TTtxvpku7bcM
This commit is contained in:
Claude
2026-06-13 23:33:07 +00:00
parent 34d9f5aafc
commit 80830beae2
2 changed files with 45 additions and 39 deletions
+1
View File
@@ -10,6 +10,7 @@ from typing import Optional
import base64
import asyncio
import json
from io import BytesIO
from app.services.local_inpaint import (
lama_inpaint, opencv_inpaint, lama_available, gpu_available, rembg_available,
@@ -1,10 +1,6 @@
/**
* ProviderBadge — small DOM element showing the active AI provider.
* Inserted into the toolbar footer on app load.
*
* Green = remote provider healthy (or local_gpu active)
* Yellow = provider configured but unhealthy/unreachable
* Grey = local only (LaMa + OpenCV)
* ProviderBadge — compact status indicator in the left toolbar footer.
* Shows a dot + 3-5 char label; all details in the tooltip.
*/
import { getCapabilities } from '../../api/capabilities.js';
@@ -15,84 +11,74 @@ export async function mountProviderBadge(container) {
var badge = document.createElement('div');
badge.id = 'provider-badge';
badge.style.cssText = [
'display:inline-flex', 'align-items:center', 'gap:5px',
'padding:3px 8px', 'border-radius:10px',
'font-size:11px', 'font-family:sans-serif',
'display:flex', 'flex-direction:column', 'align-items:center', 'gap:2px',
'padding:4px 2px 4px',
'font-size:9px', 'font-family:sans-serif', 'line-height:1.2',
'cursor:default', 'user-select:none',
'margin:4px', 'opacity:0.85',
'width:100%', 'box-sizing:border-box',
'text-align:center', 'word-break:break-word',
].join(';');
var dot = document.createElement('span');
dot.style.cssText = 'width:7px;height:7px;border-radius:50%;display:inline-block;';
dot.style.cssText = 'width:8px;height:8px;border-radius:50%;display:block;flex-shrink:0;';
var label = document.createElement('span');
label.style.cssText = 'color:inherit;max-width:36px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:block;';
var remote = caps.remote || {};
var local = caps.local || {};
if (remote.provider === 'local_gpu') {
// Local GPU provider — show GPU name and tier from /api/config local fields
var gpuName = _shortGpuName(local.gpu_device);
var tier = local.gpu_tier || '';
if (remote.healthy) {
dot.style.background = '#44cc44';
badge.style.background = '#1a2a1a';
badge.style.color = '#aaffaa';
label.textContent = 'GPU · ' + tier + ' · ' + gpuName;
label.textContent = _shortTier(tier);
var flagList = [
local.gpu_fp16 && 'fp16',
local.gpu_bf16 && 'bf16',
local.gpu_fp8 && 'fp8',
local.gpu_tensor_cores && 'tensor-cores',
local.gpu_tensor_cores && 'TC',
].filter(Boolean).join(' ');
badge.title = [
local.gpu_device || gpuName,
'VRAM: ' + local.gpu_vram_total + ' GB total ' + local.gpu_vram_free + ' GB free',
'Compute: CC ' + local.gpu_cc + ' Eff: ' + local.gpu_eff_vram + ' GB',
flagList ? 'Features: ' + flagList : '',
'Capabilities: ' + (local.local_gpu_capabilities || []).join(', '),
gpuName,
'VRAM: ' + local.gpu_vram_total + ' GB total / ' + local.gpu_vram_free + ' GB free',
'CC: ' + local.gpu_cc + ' Eff VRAM: ' + local.gpu_eff_vram + ' GB',
flagList ? 'Flags: ' + flagList : '',
tier ? 'Tier: ' + tier : '',
(local.local_gpu_warnings || []).length
? '\nWarnings:\n' + local.local_gpu_warnings.join('\n')
? 'Warnings:\n' + local.local_gpu_warnings.join('\n')
: '',
].filter(Boolean).join('\n');
} else {
dot.style.background = '#ffaa00';
badge.style.background = '#2a2000';
badge.style.color = '#ffdd88';
label.textContent = 'Local GPU (not ready)';
badge.title = 'local_gpu is configured but the diffusers library may not be installed.\nCheck container logs for details.';
label.textContent = 'GPU?';
badge.title = 'local_gpu configured but diffusers may not be installed.\nCheck container logs.';
}
} else if (remote.provider && remote.healthy) {
dot.style.background = '#44cc44';
badge.style.background = '#1a2a1a';
badge.style.color = '#aaffaa';
var overrides = remote.overrides || {};
var overrideEntries = Object.entries(overrides).filter(([, v]) => v);
var overrideStr = overrideEntries.length
? ' · ' + overrideEntries.map(([k, v]) => k + '→' + v).join(', ')
: '';
label.textContent = remote.provider + overrideStr + (local.gpu_detected ? ' · GPU' : '');
label.textContent = _shortProvider(remote.provider);
var opLines = Object.entries(remote.operations || {})
.map(([op, s]) => op + ': ' + (s.provider || remote.provider) + ' ' + (s.healthy ? '✓' : '✗'))
.join('\n');
badge.title = opLines || ('Provider: ' + remote.provider);
badge.title = ('Provider: ' + remote.provider) + (opLines ? '\n' + opLines : '');
} else if (remote.provider && !remote.healthy) {
dot.style.background = '#ffaa00';
badge.style.background = '#2a2000';
badge.style.color = '#ffdd88';
label.textContent = remote.provider + ' (offline)';
badge.title = remote.provider + ' is configured but not reachable. Check your .env URL.';
label.textContent = _shortProvider(remote.provider) + '?';
badge.title = remote.provider + ' configured but not reachable.\nCheck your .env URL.';
} else {
dot.style.background = '#888888';
badge.style.background = '#1a1a1a';
badge.style.color = '#aaaaaa';
label.textContent = 'Local' + (local.lama ? ' · LaMa' : '') + (local.gpu_detected ? ' · GPU' : '');
badge.title = 'Local only. Set AI_PROVIDER in .env to enable generative tools.';
label.textContent = local.lama ? 'LaMa' : 'Local';
badge.title = 'Local only (no generative AI).\nSet AI_PROVIDER in .env to enable.';
}
badge.appendChild(dot);
@@ -108,6 +94,25 @@ export async function mountProviderBadge(container) {
function _shortGpuName(name) {
return (name || 'GPU')
.replace(/^NVIDIA GeForce\s+/i, '')
.replace(/^NVIDIA Quadro\s+/i, '')
.replace(/^NVIDIA\s+/i, '')
.replace(/^AMD Radeon\s+/i, '');
}
function _shortTier(tier) {
if (!tier) return 'GPU';
// sdxl_offload → SDXL, flux → FLUX, sd15 → SD15
return tier
.replace(/_offload$/, '')
.replace(/_cpu$/, '')
.toUpperCase()
.slice(0, 6);
}
function _shortProvider(p) {
var map = {
openai: 'OAI', replicate: 'Rep', stability: 'Stab',
invokeai: 'Inv', comfyui: 'CUI', local_gpu: 'GPU',
};
return map[p] || (p || 'AI').slice(0, 4);
}