qwen-local: the first provider that runs on our own GPU
sd-server (stable-diffusion.cpp, Vulkan) serving Qwen-Image 2.1 on this machine. It already speaks OpenAI's image API, which is the vocabulary `ImageRequest` is written in, so there is no translation layer — only the parts that are true of a local GPU and not of a hosted one. ── keyless is a category, not an exception ── `credentials.ts` decided a provider was usable by asking whether a key existed, so a service that takes none read as unconfigured forever. The fix is an adapter declaring `keyless`, not a dummy secret: a dummy would record a credential that does not exist and make the secret store lie about what is configured, and that store is the one place meant to be the truth about them. A keyless adapter never reaches `keyFor` at all. That function's own comment says why it uses `getSecret` rather than `getKey` — so a missing FOREIGN credential is never minted — and a provider with no credential by design has no business in that path. whisper, OCR and TTS already run on this machine, so this will not be the last. ── measured, not assumed ── The integration doc said "tens of seconds, not two". The server's own log says 512x512 completes in ~185s and 1024x1024 samples at ~41.4s/step over 20 steps, about 14 minutes. And it serialises strictly: a second caller waits for the first to finish entirely, so a queued 512 can wait 17 minutes before a byte comes back. So: 512 is the default and the substitute for `auto`, 1024 is offered with the cost in its label, and `timeout: false` is a requirement rather than caution — every default anyone would reach for is far under 17 minutes. The caller's `signal` still aborts, because a user cancelling is not a client giving up. ── the model id moved, because only it could ── `GET /v1/models` returns `sd-cpp-local`, hardcoded in sd-server with no override. The catalogue now agrees with the server and carries "Qwen-Image 2.1 (local)" in the label, so requests name a model the server knows while the UI still says something meaningful. ── not shipped: edit() ── Same GPU, same queue. It doubles the ways generation can be blocked while the generate path is unproven in anger, and `ImageAdapter` makes absence the way to say so — the route answers 400 instead of the queue finding out late. Verified: /v1/models answers, provider statuses report qwen-local as configured/keyless/usable, and the shipped entry survives the merge with the stored catalogue.json. NOT verified end to end — a generation is occupying the GPU and I left it alone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Officer-Session: de3ed4e6-f88a-429c-94e6-a760336ba83b
This commit is contained in:
@@ -74,7 +74,11 @@ export const ProvidersBar = ({
|
||||
{entry.label}
|
||||
{/* Said here rather than discovered at Generate: a vendor with no key is listed
|
||||
precisely so you can add one, so the state has to be visible in the list. */}
|
||||
{!entry.configured && <span className="text-xs text-destructive">no key</span>}
|
||||
{entry.keyless ? (
|
||||
<span className="text-xs text-muted-foreground">local</span>
|
||||
) : !entry.configured ? (
|
||||
<span className="text-xs text-destructive">no key</span>
|
||||
) : null}
|
||||
</span>
|
||||
</SelectItem>
|
||||
))}
|
||||
@@ -103,10 +107,16 @@ export const ProvidersBar = ({
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<Button size="sm" variant="outline" onClick={() => provider && setKeyFor(provider)} disabled={!provider}>
|
||||
<KeyRound className="mr-1 size-3.5" />
|
||||
{provider?.configured ? 'Change key' : 'Add key'}
|
||||
</Button>
|
||||
{/* Hidden rather than disabled for a keyless provider. A greyed-out "Add key" invites the
|
||||
question of what would enable it, and the answer is nothing — there is no key to add. */}
|
||||
{provider?.keyless ? (
|
||||
<span className="text-xs text-muted-foreground">Runs on this machine — no key needed</span>
|
||||
) : (
|
||||
<Button size="sm" variant="outline" onClick={() => provider && setKeyFor(provider)} disabled={!provider}>
|
||||
<KeyRound className="mr-1 size-3.5" />
|
||||
{provider?.configured ? 'Change key' : 'Add key'}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Button
|
||||
size="sm"
|
||||
|
||||
+8
-2
@@ -18,8 +18,14 @@ export type ProviderStatus = {
|
||||
name: string;
|
||||
label: string;
|
||||
configured: boolean;
|
||||
key_source: 'store' | 'environment' | null;
|
||||
env_var: string;
|
||||
/**
|
||||
* This provider authenticates with nothing — a service of ours rather than a vendor's. It is
|
||||
* `configured` by existing, so anywhere that offers to add or change a key must skip it.
|
||||
*/
|
||||
keyless: boolean;
|
||||
key_source: 'store' | 'environment' | 'keyless' | null;
|
||||
/** Null for a keyless provider: naming a variable nobody can usefully set would be a wrong hint. */
|
||||
env_var: string | null;
|
||||
/** False when the catalogue lists a vendor this build has no adapter for. */
|
||||
usable: boolean;
|
||||
supports_edit: boolean;
|
||||
|
||||
Reference in New Issue
Block a user