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:
@@ -10,6 +10,7 @@ from typing import Optional
|
|||||||
import base64
|
import base64
|
||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
from app.services.local_inpaint import (
|
from app.services.local_inpaint import (
|
||||||
lama_inpaint, opencv_inpaint, lama_available, gpu_available, rembg_available,
|
lama_inpaint, opencv_inpaint, lama_available, gpu_available, rembg_available,
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* ProviderBadge — small DOM element showing the active AI provider.
|
* ProviderBadge — compact status indicator in the left toolbar footer.
|
||||||
* Inserted into the toolbar footer on app load.
|
* Shows a dot + 3-5 char label; all details in the tooltip.
|
||||||
*
|
|
||||||
* Green = remote provider healthy (or local_gpu active)
|
|
||||||
* Yellow = provider configured but unhealthy/unreachable
|
|
||||||
* Grey = local only (LaMa + OpenCV)
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { getCapabilities } from '../../api/capabilities.js';
|
import { getCapabilities } from '../../api/capabilities.js';
|
||||||
@@ -15,84 +11,74 @@ export async function mountProviderBadge(container) {
|
|||||||
var badge = document.createElement('div');
|
var badge = document.createElement('div');
|
||||||
badge.id = 'provider-badge';
|
badge.id = 'provider-badge';
|
||||||
badge.style.cssText = [
|
badge.style.cssText = [
|
||||||
'display:inline-flex', 'align-items:center', 'gap:5px',
|
'display:flex', 'flex-direction:column', 'align-items:center', 'gap:2px',
|
||||||
'padding:3px 8px', 'border-radius:10px',
|
'padding:4px 2px 4px',
|
||||||
'font-size:11px', 'font-family:sans-serif',
|
'font-size:9px', 'font-family:sans-serif', 'line-height:1.2',
|
||||||
'cursor:default', 'user-select:none',
|
'cursor:default', 'user-select:none',
|
||||||
'margin:4px', 'opacity:0.85',
|
'width:100%', 'box-sizing:border-box',
|
||||||
|
'text-align:center', 'word-break:break-word',
|
||||||
].join(';');
|
].join(';');
|
||||||
|
|
||||||
var dot = document.createElement('span');
|
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');
|
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 remote = caps.remote || {};
|
||||||
var local = caps.local || {};
|
var local = caps.local || {};
|
||||||
|
|
||||||
if (remote.provider === 'local_gpu') {
|
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 gpuName = _shortGpuName(local.gpu_device);
|
||||||
var tier = local.gpu_tier || '';
|
var tier = local.gpu_tier || '';
|
||||||
|
|
||||||
if (remote.healthy) {
|
if (remote.healthy) {
|
||||||
dot.style.background = '#44cc44';
|
dot.style.background = '#44cc44';
|
||||||
badge.style.background = '#1a2a1a';
|
|
||||||
badge.style.color = '#aaffaa';
|
badge.style.color = '#aaffaa';
|
||||||
label.textContent = 'GPU · ' + tier + ' · ' + gpuName;
|
label.textContent = _shortTier(tier);
|
||||||
|
|
||||||
var flagList = [
|
var flagList = [
|
||||||
local.gpu_fp16 && 'fp16',
|
local.gpu_fp16 && 'fp16',
|
||||||
local.gpu_bf16 && 'bf16',
|
local.gpu_bf16 && 'bf16',
|
||||||
local.gpu_fp8 && 'fp8',
|
local.gpu_fp8 && 'fp8',
|
||||||
local.gpu_tensor_cores && 'tensor-cores',
|
local.gpu_tensor_cores && 'TC',
|
||||||
].filter(Boolean).join(' ');
|
].filter(Boolean).join(' ');
|
||||||
|
|
||||||
badge.title = [
|
badge.title = [
|
||||||
local.gpu_device || gpuName,
|
gpuName,
|
||||||
'VRAM: ' + local.gpu_vram_total + ' GB total ' + local.gpu_vram_free + ' GB free',
|
'VRAM: ' + local.gpu_vram_total + ' GB total / ' + local.gpu_vram_free + ' GB free',
|
||||||
'Compute: CC ' + local.gpu_cc + ' Eff: ' + local.gpu_eff_vram + ' GB',
|
'CC: ' + local.gpu_cc + ' Eff VRAM: ' + local.gpu_eff_vram + ' GB',
|
||||||
flagList ? 'Features: ' + flagList : '',
|
flagList ? 'Flags: ' + flagList : '',
|
||||||
'Capabilities: ' + (local.local_gpu_capabilities || []).join(', '),
|
tier ? 'Tier: ' + tier : '',
|
||||||
(local.local_gpu_warnings || []).length
|
(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');
|
].filter(Boolean).join('\n');
|
||||||
} else {
|
} else {
|
||||||
dot.style.background = '#ffaa00';
|
dot.style.background = '#ffaa00';
|
||||||
badge.style.background = '#2a2000';
|
|
||||||
badge.style.color = '#ffdd88';
|
badge.style.color = '#ffdd88';
|
||||||
label.textContent = 'Local GPU (not ready)';
|
label.textContent = 'GPU?';
|
||||||
badge.title = 'local_gpu is configured but the diffusers library may not be installed.\nCheck container logs for details.';
|
badge.title = 'local_gpu configured but diffusers may not be installed.\nCheck container logs.';
|
||||||
}
|
}
|
||||||
} else if (remote.provider && remote.healthy) {
|
} else if (remote.provider && remote.healthy) {
|
||||||
dot.style.background = '#44cc44';
|
dot.style.background = '#44cc44';
|
||||||
badge.style.background = '#1a2a1a';
|
|
||||||
badge.style.color = '#aaffaa';
|
badge.style.color = '#aaffaa';
|
||||||
|
label.textContent = _shortProvider(remote.provider);
|
||||||
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' : '');
|
|
||||||
|
|
||||||
var opLines = Object.entries(remote.operations || {})
|
var opLines = Object.entries(remote.operations || {})
|
||||||
.map(([op, s]) => op + ': ' + (s.provider || remote.provider) + ' ' + (s.healthy ? '✓' : '✗'))
|
.map(([op, s]) => op + ': ' + (s.provider || remote.provider) + ' ' + (s.healthy ? '✓' : '✗'))
|
||||||
.join('\n');
|
.join('\n');
|
||||||
badge.title = opLines || ('Provider: ' + remote.provider);
|
badge.title = ('Provider: ' + remote.provider) + (opLines ? '\n' + opLines : '');
|
||||||
} else if (remote.provider && !remote.healthy) {
|
} else if (remote.provider && !remote.healthy) {
|
||||||
dot.style.background = '#ffaa00';
|
dot.style.background = '#ffaa00';
|
||||||
badge.style.background = '#2a2000';
|
|
||||||
badge.style.color = '#ffdd88';
|
badge.style.color = '#ffdd88';
|
||||||
label.textContent = remote.provider + ' (offline)';
|
label.textContent = _shortProvider(remote.provider) + '?';
|
||||||
badge.title = remote.provider + ' is configured but not reachable. Check your .env URL.';
|
badge.title = remote.provider + ' configured but not reachable.\nCheck your .env URL.';
|
||||||
} else {
|
} else {
|
||||||
dot.style.background = '#888888';
|
dot.style.background = '#888888';
|
||||||
badge.style.background = '#1a1a1a';
|
|
||||||
badge.style.color = '#aaaaaa';
|
badge.style.color = '#aaaaaa';
|
||||||
label.textContent = 'Local' + (local.lama ? ' · LaMa' : '') + (local.gpu_detected ? ' · GPU' : '');
|
label.textContent = local.lama ? 'LaMa' : 'Local';
|
||||||
badge.title = 'Local only. Set AI_PROVIDER in .env to enable generative tools.';
|
badge.title = 'Local only (no generative AI).\nSet AI_PROVIDER in .env to enable.';
|
||||||
}
|
}
|
||||||
|
|
||||||
badge.appendChild(dot);
|
badge.appendChild(dot);
|
||||||
@@ -108,6 +94,25 @@ export async function mountProviderBadge(container) {
|
|||||||
function _shortGpuName(name) {
|
function _shortGpuName(name) {
|
||||||
return (name || 'GPU')
|
return (name || 'GPU')
|
||||||
.replace(/^NVIDIA GeForce\s+/i, '')
|
.replace(/^NVIDIA GeForce\s+/i, '')
|
||||||
|
.replace(/^NVIDIA Quadro\s+/i, '')
|
||||||
.replace(/^NVIDIA\s+/i, '')
|
.replace(/^NVIDIA\s+/i, '')
|
||||||
.replace(/^AMD Radeon\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);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user