@pi-unipi/image 2.2.4 → 2.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/generate.ts +36 -0
- package/src/models.ts +17 -0
- package/src/tui/model-selector.ts +12 -1
- package/src/tui/settings-dialog.ts +15 -1
package/package.json
CHANGED
package/src/generate.ts
CHANGED
|
@@ -103,6 +103,26 @@ export function saveImage(
|
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
+
/** Providers pi-ai's images collection can actually generate with. */
|
|
107
|
+
function supportedProviders(imagesApi: ImagesModelsLike): string[] {
|
|
108
|
+
try {
|
|
109
|
+
return [...new Set(imagesApi.getModels().map((m) => m.provider))];
|
|
110
|
+
} catch {
|
|
111
|
+
return [];
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Whether generation can route to a provider. Unknown/empty catalogs are
|
|
117
|
+
* treated as capable so a stubbed or future pi-ai is never blocked by this
|
|
118
|
+
* check — the real call still reports its own error.
|
|
119
|
+
*/
|
|
120
|
+
function providerCanGenerate(imagesApi: ImagesModelsLike, provider: string): boolean {
|
|
121
|
+
const providers = supportedProviders(imagesApi);
|
|
122
|
+
if (providers.length === 0) return true;
|
|
123
|
+
return providers.includes(provider);
|
|
124
|
+
}
|
|
125
|
+
|
|
106
126
|
export interface GenerateOptions {
|
|
107
127
|
prompt: string;
|
|
108
128
|
model: ImageGenModel;
|
|
@@ -134,6 +154,22 @@ export async function generateImage(options: GenerateOptions): Promise<GenerateR
|
|
|
134
154
|
);
|
|
135
155
|
}
|
|
136
156
|
|
|
157
|
+
// pi-ai's images collection carries its own provider set (currently only
|
|
158
|
+
// `openrouter`) and is entirely separate from pi's chat model registry.
|
|
159
|
+
// A chat provider registered by another extension can therefore list image
|
|
160
|
+
// models that generation cannot actually drive — pi-ai answers with a bare
|
|
161
|
+
// "Unknown provider: x". Detect that here and say something useful.
|
|
162
|
+
if (!providerCanGenerate(imagesApi, model.provider)) {
|
|
163
|
+
const supported = supportedProviders(imagesApi);
|
|
164
|
+
throw new Error(
|
|
165
|
+
`Provider "${model.provider}" cannot generate images.\n` +
|
|
166
|
+
`→ Image generation is served by: ${supported.join(", ") || "openrouter"}.\n` +
|
|
167
|
+
`→ "${model.provider}" is a chat provider; its image models are listed for ` +
|
|
168
|
+
`recognition and reference, but generation must go through a supported provider.\n` +
|
|
169
|
+
"→ Pick one with /unipi:image-settings.",
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
137
173
|
// Prefer pi-ai's own credential store, then the caller-supplied fallback so
|
|
138
174
|
// a bare OPENROUTER_API_KEY still works.
|
|
139
175
|
let apiKey: string | undefined;
|
package/src/models.ts
CHANGED
|
@@ -105,6 +105,23 @@ export async function getImagesModels(): Promise<ImagesModelsLike | null> {
|
|
|
105
105
|
return cachedImagesModels;
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
/**
|
|
109
|
+
* Providers pi-ai's images collection can actually generate with.
|
|
110
|
+
*
|
|
111
|
+
* This is NOT the same set as pi's chat model registry: a chat provider
|
|
112
|
+
* registered by another extension may list image models that image generation
|
|
113
|
+
* cannot drive. Empty means "unknown", which callers treat as permissive.
|
|
114
|
+
*/
|
|
115
|
+
export async function getGeneratingProviders(): Promise<string[]> {
|
|
116
|
+
const images = await getImagesModels();
|
|
117
|
+
if (!images) return [];
|
|
118
|
+
try {
|
|
119
|
+
return [...new Set(images.getModels().map((m) => m.provider))];
|
|
120
|
+
} catch {
|
|
121
|
+
return [];
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
108
125
|
/** List available image-generation models. Empty when unavailable. */
|
|
109
126
|
export async function listImageGenModels(): Promise<ImageGenModel[]> {
|
|
110
127
|
const images = await getImagesModels();
|
|
@@ -14,6 +14,8 @@ export interface SelectableModel {
|
|
|
14
14
|
provider: string;
|
|
15
15
|
id: string;
|
|
16
16
|
name?: string;
|
|
17
|
+
/** Set when the model is listed but not usable, with the reason. */
|
|
18
|
+
unavailable?: string;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
export type ModelSelectorKind = "generate" | "recognize";
|
|
@@ -212,6 +214,12 @@ export class ImageModelSelectorOverlay implements Component {
|
|
|
212
214
|
this.error = "No model selected";
|
|
213
215
|
return;
|
|
214
216
|
}
|
|
217
|
+
// Require a deliberate second Enter on a model that cannot be used, rather
|
|
218
|
+
// than silently saving a choice that will fail at call time.
|
|
219
|
+
if (model.unavailable && this.error === null) {
|
|
220
|
+
this.error = `${model.id} ${model.unavailable} — press Enter again to select anyway`;
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
215
223
|
|
|
216
224
|
this.onSelect?.(`${model.provider}/${model.id}`);
|
|
217
225
|
this.saved = true;
|
|
@@ -335,9 +343,12 @@ export class ImageModelSelectorOverlay implements Component {
|
|
|
335
343
|
const marker = isSelected ? this.fg("accent", "▸") : " ";
|
|
336
344
|
const label = model.name || model.id;
|
|
337
345
|
const providerTag = this.fg("dim", `[${model.provider}]`);
|
|
338
|
-
const
|
|
346
|
+
const base = isSelected
|
|
339
347
|
? `${providerTag} ${this.bold(label)}`
|
|
340
348
|
: `${providerTag} ${this.fg("dim", label)}`;
|
|
349
|
+
const display = model.unavailable
|
|
350
|
+
? `${base} ${this.fg("warning", `(${model.unavailable})`)}`
|
|
351
|
+
: base;
|
|
341
352
|
lines.push(this.frameLine(` ${marker} ${display}`, innerWidth));
|
|
342
353
|
}
|
|
343
354
|
}
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
} from "../settings.js";
|
|
18
18
|
import {
|
|
19
19
|
formatModelRef,
|
|
20
|
+
getGeneratingProviders,
|
|
20
21
|
listAllImageGenModels,
|
|
21
22
|
listVisionModels,
|
|
22
23
|
type ChatModelRegistry,
|
|
@@ -241,7 +242,20 @@ async function collectModels(
|
|
|
241
242
|
// Include models from providers registered by other extensions, not just
|
|
242
243
|
// pi-ai's built-in OpenRouter catalog.
|
|
243
244
|
const models = await listAllImageGenModels(registry);
|
|
244
|
-
|
|
245
|
+
const generating = await getGeneratingProviders();
|
|
246
|
+
|
|
247
|
+
return models.map((m) => ({
|
|
248
|
+
provider: m.provider,
|
|
249
|
+
id: m.id,
|
|
250
|
+
name: m.name,
|
|
251
|
+
// pi-ai's images collection has its own provider set. A chat provider's
|
|
252
|
+
// image models are listed for reference but cannot actually generate,
|
|
253
|
+
// so flag them rather than letting the user pick a dead option.
|
|
254
|
+
unavailable:
|
|
255
|
+
generating.length > 0 && !generating.includes(m.provider)
|
|
256
|
+
? "cannot generate"
|
|
257
|
+
: undefined,
|
|
258
|
+
}));
|
|
245
259
|
}
|
|
246
260
|
|
|
247
261
|
if (!registry) return [];
|