Files
atelier/sidecar/catalogue/refresh.ts
T
pastilhas 3c59bf4d0c feat(catalogue): refresh is a button, not a schedule
The daily job is gone. A refresh only matters at one moment — you are picking a
provider and want to know its models are current — and at that moment a button
answers the question, where a nightly pass can only have already answered it,
invisibly, or not. It also bought a standing obligation: something hitting a
vendor from every install forever whether anyone opens Atelier or not.

So the add dialog is now the curated file made visible. Provider comes from the
catalogue, models come from that provider's entry in it, and two buttons re-read
it from the vendor: one for the provider in front of you, one for all of them.
`POST /providers/catalogue/refresh?provider=` is the only trigger there is.

The dialog also says how old the answer is — "as shipped" until a refresh has
ever run, which is worth showing, because it means the models listed are exactly
what the plugin was released with.

A provider with no refresher answers 400 rather than a silent success: from a
button, "nothing happened" and "nothing could happen" look identical.
2026-08-20 22:21:15 +01:00

71 lines
3.1 KiB
TypeScript

import { refreshOpenAI } from './openai-refresh';
import { SHIPPED_CATALOGUE } from './shipped';
import { getCatalogue, mergeCatalogue, writeCatalogue } from './store';
import type { Catalogue } from './types';
/**
* Bringing the catalogue up to date — on demand, never on a timer.
*
* There was a daily schedule here and it is gone. A refresh only matters at one moment — you are
* choosing a provider and want to know its models are current — and at that moment a button answers
* the question while a nightly job can only have already answered it, or not, invisibly. A timer also
* buys a standing obligation: something that hits a vendor from every install forever, whether anyone
* is looking at Atelier or not.
*
* One refresher per provider, each returning what it could establish. Adding a second provider is a
* second function and a line in `REFRESHERS` — the same restraint hono.ts keeps on its mounts.
*/
const REFRESHERS: Record<string, () => Promise<{ catalogue: Catalogue | null; listedIds: string[] | null }>> = {
openai: refreshOpenAI,
};
export const refreshableProviders = () => Object.keys(REFRESHERS);
/**
* Refresh one provider, or every provider we have a refresher for.
*
* Retiring happens HERE rather than in a refresher, because it is the one judgement that needs the
* whole picture: a model we ship and the vendor no longer lists is deprecated, but only when the
* listing actually succeeded. "The endpoint returned three models" and "the request failed after
* three" are indistinguishable inside the refresher and completely different here.
*
* The merge starts from the catalogue as it stands, not from the shipped file, so refreshing OpenAI
* cannot discard what a previous refresh established about anything else.
*/
export async function refreshCatalogue(providerName?: string): Promise<Catalogue> {
const names = providerName ? [providerName] : Object.keys(REFRESHERS);
const unknown = names.filter((name) => !REFRESHERS[name]);
if (unknown.length > 0) throw new Error(`No refresher for provider "${unknown.join(', ')}".`);
let merged = mergeCatalogue(SHIPPED_CATALOGUE, await getCatalogue());
for (const name of names) {
const { catalogue, listedIds } = await REFRESHERS[name]!();
if (!catalogue) continue;
merged = mergeCatalogue(merged, catalogue);
if (listedIds) {
const listed = new Set(listedIds);
for (const provider of merged.providers) {
if (provider.name !== name) continue;
for (const model of provider.models) {
// Deprecated, not deleted: the gallery labels historical rows from this list, and 88 images
// name the model that made them.
model.status = listed.has(model.id) ? 'current' : 'deprecated';
}
}
}
}
const written = { ...merged, updatedAt: new Date().toISOString() };
await writeCatalogue(written);
const count = written.providers.reduce((sum, provider) => sum + provider.models.length, 0);
console.log(`[atelier:catalogue] refreshed ${names.join(', ')} — ${count} model(s) across ${written.providers.length} provider(s)`);
return written;
}
export { getCatalogue };