@chai-ui/core 0.5.0 → 0.6.1
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/README.md +33 -3
- package/dist/check-suggested-models.d.ts +31 -0
- package/dist/check-suggested-models.d.ts.map +1 -0
- package/dist/check-suggested-models.js +123 -0
- package/dist/check-suggested-models.js.map +1 -0
- package/dist/composer.d.ts +188 -0
- package/dist/composer.d.ts.map +1 -0
- package/dist/composer.js +326 -0
- package/dist/composer.js.map +1 -0
- package/dist/engines/fal.d.ts +6 -4
- package/dist/engines/fal.d.ts.map +1 -1
- package/dist/engines/fal.js +20 -19
- package/dist/engines/fal.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/media-analyzer.d.ts +55 -0
- package/dist/media-analyzer.d.ts.map +1 -0
- package/dist/media-analyzer.js +124 -0
- package/dist/media-analyzer.js.map +1 -0
- package/dist/reasoning.d.ts +59 -0
- package/dist/reasoning.d.ts.map +1 -0
- package/dist/reasoning.js +55 -0
- package/dist/reasoning.js.map +1 -0
- package/dist/suggested-models.d.ts +23 -0
- package/dist/suggested-models.d.ts.map +1 -0
- package/dist/suggested-models.js +110 -0
- package/dist/suggested-models.js.map +1 -0
- package/dist/types.d.ts +12 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/check-suggested-models.ts +147 -0
- package/src/composer.ts +525 -0
- package/src/engines/fal.ts +20 -19
- package/src/index.ts +5 -0
- package/src/media-analyzer.ts +182 -0
- package/src/reasoning.ts +90 -0
- package/src/suggested-models.ts +121 -0
- package/src/types.ts +13 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Media Analyzer's behavior with no framework: sends the attached media
|
|
3
|
+
* and the instruction for its kind and prompt length
|
|
4
|
+
* (`MEDIA_ANALYSIS_PROMPTS`) to the reasoning engine, and keeps the
|
|
5
|
+
* generated prompt, which fills in as the text streams. A small store
|
|
6
|
+
* (`getState` + `subscribe`) that `useMediaAnalyzer` (React) and the Vue
|
|
7
|
+
* composable wrap.
|
|
8
|
+
*/
|
|
9
|
+
import { warnNoReasoningOnce } from "./composer.js";
|
|
10
|
+
import { createMockEngine, isAbortError, normalizeGenerationError } from "./engine.js";
|
|
11
|
+
import { DEFAULT_MEDIA_ANALYSIS_PROMPT_LENGTH, MEDIA_ANALYSIS_PROMPTS, } from "./media-analysis-prompts.js";
|
|
12
|
+
import { routeModel } from "./model-routing.js";
|
|
13
|
+
import { reasoningModelFor } from "./reasoning.js";
|
|
14
|
+
const mockTextEngine = createMockEngine({ outputKind: "text" });
|
|
15
|
+
/**
|
|
16
|
+
* `getOptions` is read on every call, so options can change between
|
|
17
|
+
* renders; `getProvided` is the nearest provider's reasoning, if any.
|
|
18
|
+
*/
|
|
19
|
+
export function createMediaAnalyzerStore(getOptions, getProvided = () => undefined) {
|
|
20
|
+
let state = { analyzing: false, error: null, prompt: null };
|
|
21
|
+
const listeners = new Set();
|
|
22
|
+
const set = (next) => {
|
|
23
|
+
state = { ...state, ...next };
|
|
24
|
+
for (const l of listeners)
|
|
25
|
+
l();
|
|
26
|
+
};
|
|
27
|
+
// Each submit gets an id; a late reply from an earlier submit is dropped,
|
|
28
|
+
// and its call aborted.
|
|
29
|
+
let callId = 0;
|
|
30
|
+
let controller = null;
|
|
31
|
+
function submit(payload) {
|
|
32
|
+
const opts = getOptions();
|
|
33
|
+
const reasoning = getProvided();
|
|
34
|
+
const id = ++callId;
|
|
35
|
+
controller?.abort();
|
|
36
|
+
const { signal } = (controller = new AbortController());
|
|
37
|
+
set({ error: null, prompt: null });
|
|
38
|
+
const kind = payload.attachments[0]?.kind;
|
|
39
|
+
if (kind !== "image" && kind !== "video" && kind !== "audio") {
|
|
40
|
+
const message = "Attach an image, video, or audio file to analyze.";
|
|
41
|
+
set({ error: message });
|
|
42
|
+
opts.onError?.(message);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const engine = opts.engine ?? reasoning?.engine ?? mockTextEngine;
|
|
46
|
+
const defaultModel = opts.model ?? (reasoning ? reasoningModelFor(reasoning, payload.attachments) : undefined);
|
|
47
|
+
const instruction = MEDIA_ANALYSIS_PROMPTS[kind][payload.promptLength ?? DEFAULT_MEDIA_ANALYSIS_PROMPT_LENGTH];
|
|
48
|
+
const current = () => id === callId;
|
|
49
|
+
const autoSelect = payload.autoSelectModel && payload.models.length > 0;
|
|
50
|
+
const analyze = (modelId) => engine
|
|
51
|
+
.generate({
|
|
52
|
+
modelId,
|
|
53
|
+
prompt: instruction,
|
|
54
|
+
attachments: payload.attachments,
|
|
55
|
+
onText: (text) => current() && set({ prompt: text }),
|
|
56
|
+
signal,
|
|
57
|
+
})
|
|
58
|
+
.then(({ src }) => {
|
|
59
|
+
if (!current())
|
|
60
|
+
return;
|
|
61
|
+
set({ prompt: src });
|
|
62
|
+
getOptions().onResult?.(src);
|
|
63
|
+
}, (err) => {
|
|
64
|
+
if (!current() || isAbortError(err))
|
|
65
|
+
return;
|
|
66
|
+
const { message } = normalizeGenerationError(err);
|
|
67
|
+
set({ error: message });
|
|
68
|
+
getOptions().onError?.(message);
|
|
69
|
+
})
|
|
70
|
+
.finally(() => current() && set({ analyzing: false }));
|
|
71
|
+
set({ analyzing: true });
|
|
72
|
+
if (!autoSelect) {
|
|
73
|
+
void analyze(payload.modelId ?? defaultModel);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
// Auto-select: the reasoning model looks at the media and picks the
|
|
77
|
+
// analysis model first. A routing problem falls back, never blocks.
|
|
78
|
+
void routeModel({
|
|
79
|
+
prompt: "",
|
|
80
|
+
outputKind: "text",
|
|
81
|
+
attachmentKinds: payload.attachments.map((a) => a.kind),
|
|
82
|
+
attachments: payload.attachments,
|
|
83
|
+
models: payload.models,
|
|
84
|
+
engine: opts.engine ?? reasoning?.engine,
|
|
85
|
+
model: defaultModel,
|
|
86
|
+
routingPrompt: opts.routingPrompt,
|
|
87
|
+
signal,
|
|
88
|
+
}).then((routed) => {
|
|
89
|
+
if (!current() || signal.aborted)
|
|
90
|
+
return;
|
|
91
|
+
const latest = getOptions();
|
|
92
|
+
if (routed.status === "fallback") {
|
|
93
|
+
if (routed.reason === "no-reasoning")
|
|
94
|
+
warnNoReasoningOnce("analysis runs on the first listed model");
|
|
95
|
+
latest.onRoutingFallback?.(routed);
|
|
96
|
+
}
|
|
97
|
+
latest.onModelChange?.(routed.modelId);
|
|
98
|
+
return analyze(routed.modelId);
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
/** Stops the analysis in flight (cancelling at the provider where it can), keeping any text that already streamed in. */
|
|
102
|
+
function cancel() {
|
|
103
|
+
callId += 1;
|
|
104
|
+
controller?.abort();
|
|
105
|
+
controller = null;
|
|
106
|
+
set({ analyzing: false });
|
|
107
|
+
}
|
|
108
|
+
/** Stops anything in flight and clears the prompt and error, e.g. when the person changes their media. */
|
|
109
|
+
function reset() {
|
|
110
|
+
cancel();
|
|
111
|
+
set({ error: null, prompt: null });
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
getState: () => state,
|
|
115
|
+
subscribe(listener) {
|
|
116
|
+
listeners.add(listener);
|
|
117
|
+
return () => void listeners.delete(listener);
|
|
118
|
+
},
|
|
119
|
+
submit,
|
|
120
|
+
cancel,
|
|
121
|
+
reset,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=media-analyzer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-analyzer.js","sourceRoot":"","sources":["../src/media-analyzer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { GenerationEngine } from "./engine.js";
|
|
2
|
+
import type { DroppedMedia, MediaKind } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* The suggested reasoning model for text and images: prompt enhancement,
|
|
5
|
+
* auto-select routing and image analysis. A default, not a requirement —
|
|
6
|
+
* pass any model id `reasoningEngine` can run.
|
|
7
|
+
*/
|
|
8
|
+
export declare const DEFAULT_REASONING_MODEL = "anthropic/claude-opus-5";
|
|
9
|
+
/**
|
|
10
|
+
* Suggested per-kind overrides for media Claude can't read. Gemini takes
|
|
11
|
+
* video and audio directly. For a cheaper option, swap in
|
|
12
|
+
* `"qwen/qwen3.8-omni-flash"`, which takes both too.
|
|
13
|
+
*/
|
|
14
|
+
export declare const DEFAULT_REASONING_MODEL_BY_KIND: Partial<Record<MediaKind, string>>;
|
|
15
|
+
/** The reasoning engine and models every CHAI component shares. */
|
|
16
|
+
export interface Reasoning {
|
|
17
|
+
engine: GenerationEngine;
|
|
18
|
+
/** The model for text and any attachment kind without its own entry in `modelByKind`. */
|
|
19
|
+
model: string;
|
|
20
|
+
modelByKind: Partial<Record<MediaKind, string>>;
|
|
21
|
+
}
|
|
22
|
+
/** What a `ChaiProvider` takes, in any framework. */
|
|
23
|
+
export interface ReasoningConfig {
|
|
24
|
+
/** Text, image and routing model. Defaults to `DEFAULT_REASONING_MODEL`. */
|
|
25
|
+
reasoningModel?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Per-attachment-kind models, merged over `DEFAULT_REASONING_MODEL_BY_KIND`
|
|
28
|
+
* one kind at a time, e.g. `{ video: "qwen/qwen3.8-omni-flash" }` keeps
|
|
29
|
+
* the default for audio. Map a kind to `reasoningModel`'s value to use
|
|
30
|
+
* one model for everything.
|
|
31
|
+
*/
|
|
32
|
+
reasoningModelByKind?: Partial<Record<MediaKind, string>>;
|
|
33
|
+
/**
|
|
34
|
+
* The engine the reasoning models run on. Defaults to OpenRouter,
|
|
35
|
+
* independent of the generation engine: Fal's own LLM access is an
|
|
36
|
+
* OpenRouter proxy, so defaulting to it would double-proxy.
|
|
37
|
+
*/
|
|
38
|
+
reasoningEngine?: GenerationEngine;
|
|
39
|
+
}
|
|
40
|
+
/** A provider's `Reasoning`, from its config and the engine it holds (`config.reasoningEngine`, else one OpenRouter engine it created once). */
|
|
41
|
+
export declare function createReasoning(config: ReasoningConfig, engine: GenerationEngine): Reasoning;
|
|
42
|
+
/** The reasoning engine a provider uses: the one passed, else OpenRouter's. */
|
|
43
|
+
export declare function reasoningEngineFor(config: ReasoningConfig): GenerationEngine;
|
|
44
|
+
/**
|
|
45
|
+
* A hook's or composable's own pair wins, but only as a pair: half of one
|
|
46
|
+
* never mixes with half of the provider's (e.g. a hook engine with the
|
|
47
|
+
* provider's model). Half a pair means no reasoning.
|
|
48
|
+
*/
|
|
49
|
+
export declare function pickReasoning(own: {
|
|
50
|
+
reasoningEngine?: GenerationEngine;
|
|
51
|
+
reasoningModel?: string;
|
|
52
|
+
}, provided: Reasoning | undefined): Reasoning | undefined;
|
|
53
|
+
/**
|
|
54
|
+
* The reasoning model for a call with these attachments: the first
|
|
55
|
+
* attachment whose kind has its own model wins (a clip plus a still goes
|
|
56
|
+
* to the model that can read the clip), else `reasoning.model`.
|
|
57
|
+
*/
|
|
58
|
+
export declare function reasoningModelFor(reasoning: Reasoning, attachments?: DroppedMedia[]): string;
|
|
59
|
+
//# sourceMappingURL=reasoning.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reasoning.d.ts","sourceRoot":"","sources":["../src/reasoning.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE1D;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,4BAA4B,CAAC;AAEjE;;;;GAIG;AACH,eAAO,MAAM,+BAA+B,EAAE,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAG9E,CAAC;AAEF,mEAAmE;AACnE,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,gBAAgB,CAAC;IACzB,yFAAyF;IACzF,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC;CACjD;AAED,qDAAqD;AACrD,MAAM,WAAW,eAAe;IAC9B,4EAA4E;IAC5E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC;IAC1D;;;;OAIG;IACH,eAAe,CAAC,EAAE,gBAAgB,CAAC;CACpC;AAED,gJAAgJ;AAChJ,wBAAgB,eAAe,CAAC,MAAM,EAAE,eAAe,EAAE,MAAM,EAAE,gBAAgB,GAAG,SAAS,CAQ5F;AAED,+EAA+E;AAC/E,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,eAAe,GAAG,gBAAgB,CAE5E;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,GAAG,EAAE;IAAE,eAAe,CAAC,EAAE,gBAAgB,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CAAE,EACpE,QAAQ,EAAE,SAAS,GAAG,SAAS,GAC9B,SAAS,GAAG,SAAS,CAIvB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,SAAS,EAAE,WAAW,GAAE,YAAY,EAAO,GAAG,MAAM,CAMhG"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { createOpenRouterEngine } from "./engines/openrouter.js";
|
|
2
|
+
/**
|
|
3
|
+
* The suggested reasoning model for text and images: prompt enhancement,
|
|
4
|
+
* auto-select routing and image analysis. A default, not a requirement —
|
|
5
|
+
* pass any model id `reasoningEngine` can run.
|
|
6
|
+
*/
|
|
7
|
+
export const DEFAULT_REASONING_MODEL = "anthropic/claude-opus-5";
|
|
8
|
+
/**
|
|
9
|
+
* Suggested per-kind overrides for media Claude can't read. Gemini takes
|
|
10
|
+
* video and audio directly. For a cheaper option, swap in
|
|
11
|
+
* `"qwen/qwen3.8-omni-flash"`, which takes both too.
|
|
12
|
+
*/
|
|
13
|
+
export const DEFAULT_REASONING_MODEL_BY_KIND = {
|
|
14
|
+
video: "google/gemini-3.8-flash",
|
|
15
|
+
audio: "google/gemini-3.8-flash",
|
|
16
|
+
};
|
|
17
|
+
/** A provider's `Reasoning`, from its config and the engine it holds (`config.reasoningEngine`, else one OpenRouter engine it created once). */
|
|
18
|
+
export function createReasoning(config, engine) {
|
|
19
|
+
const byKind = config.reasoningModelByKind ?? {};
|
|
20
|
+
const overrides = Object.fromEntries(Object.entries(byKind).filter(([, v]) => v !== undefined));
|
|
21
|
+
return {
|
|
22
|
+
engine,
|
|
23
|
+
model: config.reasoningModel ?? DEFAULT_REASONING_MODEL,
|
|
24
|
+
modelByKind: { ...DEFAULT_REASONING_MODEL_BY_KIND, ...overrides },
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/** The reasoning engine a provider uses: the one passed, else OpenRouter's. */
|
|
28
|
+
export function reasoningEngineFor(config) {
|
|
29
|
+
return config.reasoningEngine ?? createOpenRouterEngine();
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* A hook's or composable's own pair wins, but only as a pair: half of one
|
|
33
|
+
* never mixes with half of the provider's (e.g. a hook engine with the
|
|
34
|
+
* provider's model). Half a pair means no reasoning.
|
|
35
|
+
*/
|
|
36
|
+
export function pickReasoning(own, provided) {
|
|
37
|
+
const { reasoningEngine, reasoningModel } = own;
|
|
38
|
+
if (!reasoningEngine && !reasoningModel)
|
|
39
|
+
return provided;
|
|
40
|
+
return reasoningEngine && reasoningModel ? { engine: reasoningEngine, model: reasoningModel, modelByKind: {} } : undefined;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The reasoning model for a call with these attachments: the first
|
|
44
|
+
* attachment whose kind has its own model wins (a clip plus a still goes
|
|
45
|
+
* to the model that can read the clip), else `reasoning.model`.
|
|
46
|
+
*/
|
|
47
|
+
export function reasoningModelFor(reasoning, attachments = []) {
|
|
48
|
+
for (const { kind } of attachments) {
|
|
49
|
+
const model = reasoning.modelByKind[kind];
|
|
50
|
+
if (model)
|
|
51
|
+
return model;
|
|
52
|
+
}
|
|
53
|
+
return reasoning.model;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=reasoning.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reasoning.js","sourceRoot":"","sources":["../src/reasoning.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAIjE;;;;GAIG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,yBAAyB,CAAC;AAEjE;;;;GAIG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAAuC;IACjF,KAAK,EAAE,yBAAyB;IAChC,KAAK,EAAE,yBAAyB;CACjC,CAAC;AA6BF,gJAAgJ;AAChJ,MAAM,UAAU,eAAe,CAAC,MAAuB,EAAE,MAAwB;IAC/E,MAAM,MAAM,GAAG,MAAM,CAAC,oBAAoB,IAAI,EAAE,CAAC;IACjD,MAAM,SAAS,GAAG,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC;IAChG,OAAO;QACL,MAAM;QACN,KAAK,EAAE,MAAM,CAAC,cAAc,IAAI,uBAAuB;QACvD,WAAW,EAAE,EAAE,GAAG,+BAA+B,EAAE,GAAG,SAAS,EAAE;KAClE,CAAC;AACJ,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,kBAAkB,CAAC,MAAuB;IACxD,OAAO,MAAM,CAAC,eAAe,IAAI,sBAAsB,EAAE,CAAC;AAC5D,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAC3B,GAAoE,EACpE,QAA+B;IAE/B,MAAM,EAAE,eAAe,EAAE,cAAc,EAAE,GAAG,GAAG,CAAC;IAChD,IAAI,CAAC,eAAe,IAAI,CAAC,cAAc;QAAE,OAAO,QAAQ,CAAC;IACzD,OAAO,eAAe,IAAI,cAAc,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,eAAe,EAAE,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;AAC7H,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,SAAoB,EAAE,cAA8B,EAAE;IACtF,KAAK,MAAM,EAAE,IAAI,EAAE,IAAI,WAAW,EAAE,CAAC;QACnC,MAAM,KAAK,GAAG,SAAS,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;QAC1C,IAAI,KAAK;YAAE,OAAO,KAAK,CAAC;IAC1B,CAAC;IACD,OAAO,SAAS,CAAC,KAAK,CAAC;AACzB,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { GenerationUseCase, MediaKind, ModelOption } from "./types.js";
|
|
2
|
+
/** Create image, from a prompt. */
|
|
3
|
+
export declare const SUGGESTED_IMAGE_MODELS: ModelOption[];
|
|
4
|
+
/** Create video, from a prompt alone. */
|
|
5
|
+
export declare const SUGGESTED_TEXT_TO_VIDEO_MODELS: ModelOption[];
|
|
6
|
+
/** Create video, starting from an attached image. The video follows the image's shape, so none lists aspect ratios. */
|
|
7
|
+
export declare const SUGGESTED_IMAGE_TO_VIDEO_MODELS: ModelOption[];
|
|
8
|
+
/**
|
|
9
|
+
* Edit an image. Flux 3 Image reads marked regions as boxes; the others get
|
|
10
|
+
* them described in words. An edit keeps the image's shape, so no aspect
|
|
11
|
+
* ratios.
|
|
12
|
+
*/
|
|
13
|
+
export declare const SUGGESTED_EDIT_MODELS: ModelOption[];
|
|
14
|
+
/**
|
|
15
|
+
* MediaAnalyzer's models per media kind, on OpenRouter. The first in each
|
|
16
|
+
* is `ChaiProvider`'s default reasoning model for that kind.
|
|
17
|
+
*/
|
|
18
|
+
export declare const SUGGESTED_ANALYSIS_MODELS: Partial<Record<MediaKind, ModelOption[]>>;
|
|
19
|
+
/** The suggested Fal models for a generation use case, or none (text requests use the reasoning model). */
|
|
20
|
+
export declare function suggestedModels(useCase: GenerationUseCase | undefined): ModelOption[];
|
|
21
|
+
/** Every suggested Fal model, by id: how the Fal engine knows each one's inputs. */
|
|
22
|
+
export declare const SUGGESTED_FAL_MODELS: ReadonlyMap<string, ModelOption>;
|
|
23
|
+
//# sourceMappingURL=suggested-models.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"suggested-models.d.ts","sourceRoot":"","sources":["../src/suggested-models.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAuB5E,mCAAmC;AACnC,eAAO,MAAM,sBAAsB,EAAE,WAAW,EAa/C,CAAC;AAEF,yCAAyC;AACzC,eAAO,MAAM,8BAA8B,EAAE,WAAW,EAKvD,CAAC;AAEF,uHAAuH;AACvH,eAAO,MAAM,+BAA+B,EAAE,WAAW,EAKxD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,EAAE,WAAW,EAY9C,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,yBAAyB,EAAE,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,WAAW,EAAE,CAAC,CAqB/E,CAAC;AAEF,2GAA2G;AAC3G,wBAAgB,eAAe,CAAC,OAAO,EAAE,iBAAiB,GAAG,SAAS,GAAG,WAAW,EAAE,CAarF;AAED,oFAAoF;AACpF,eAAO,MAAM,oBAAoB,EAAE,WAAW,CAAC,MAAM,EAAE,WAAW,CAEjE,CAAC"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { DEFAULT_MAX_REGIONS } from "./region-edit.js";
|
|
2
|
+
/**
|
|
3
|
+
* Suggested models, so builders don't have to write model lists. Composer
|
|
4
|
+
* and MediaAnalyzer use them when no `models` is passed; passing `models`
|
|
5
|
+
* still replaces them. The first model in each list is the default pick.
|
|
6
|
+
*
|
|
7
|
+
* Generation runs on Fal (the default engine), analysis on OpenRouter (the
|
|
8
|
+
* reasoning engine). Every entry was checked against the provider's own
|
|
9
|
+
* catalog: Fal's model schemas (`api.fal.ai/v1/models?expand=openapi-3.0`)
|
|
10
|
+
* for the aspect ratios and the image field, OpenRouter's model list for
|
|
11
|
+
* the input kinds. They change only through package releases; run
|
|
12
|
+
* `checkSuggestedModels()` to see whether a provider has changed since.
|
|
13
|
+
*
|
|
14
|
+
* Checked 2026-10-06.
|
|
15
|
+
*/
|
|
16
|
+
const NANO_BANANA_RATIOS = ["21:9", "16:9", "3:2", "4:3", "5:4", "1:1", "4:5", "3:4", "2:3", "9:16"];
|
|
17
|
+
const FLUX_3_RATIOS = ["21:9", "2:1", "16:9", "3:2", "7:5", "4:3", "5:4", "1:1", "4:5", "3:4", "5:7", "2:3", "9:16", "1:2"];
|
|
18
|
+
/** The ratios a named `image_size` covers (FLUX.1, GPT Image 2). */
|
|
19
|
+
const IMAGE_SIZE_RATIOS = ["1:1", "4:3", "16:9", "3:4", "9:16"];
|
|
20
|
+
/** Create image, from a prompt. */
|
|
21
|
+
export const SUGGESTED_IMAGE_MODELS = [
|
|
22
|
+
{ id: "fal-ai/nano-banana-pro", label: "Nano Banana Pro", provider: "Google", speed: "standard", aspectRatios: NANO_BANANA_RATIOS, falInput: { aspectRatio: "aspect_ratio" } },
|
|
23
|
+
{
|
|
24
|
+
id: "fal-ai/nano-banana-2",
|
|
25
|
+
label: "Nano Banana 2",
|
|
26
|
+
provider: "Google",
|
|
27
|
+
speed: "fast",
|
|
28
|
+
aspectRatios: [...NANO_BANANA_RATIOS, "4:1", "1:4", "8:1", "1:8"],
|
|
29
|
+
falInput: { aspectRatio: "aspect_ratio" },
|
|
30
|
+
},
|
|
31
|
+
{ id: "blackforestlabs/flux-3/text-to-image", label: "Flux 3 Image", provider: "Black Forest Labs", speed: "standard", aspectRatios: FLUX_3_RATIOS, falInput: { aspectRatio: "aspect_ratio" } },
|
|
32
|
+
{ id: "openai/gpt-image-2", label: "GPT Image 2", provider: "OpenAI", speed: "slow", aspectRatios: IMAGE_SIZE_RATIOS, falInput: { aspectRatio: "image_size" } },
|
|
33
|
+
{ id: "fal-ai/flux/schnell", label: "FLUX.1 schnell", provider: "Black Forest Labs", speed: "fast", aspectRatios: IMAGE_SIZE_RATIOS, falInput: { aspectRatio: "image_size" } },
|
|
34
|
+
];
|
|
35
|
+
/** Create video, from a prompt alone. */
|
|
36
|
+
export const SUGGESTED_TEXT_TO_VIDEO_MODELS = [
|
|
37
|
+
{ id: "minimax/h3-max/text-to-video", label: "MiniMax H3 Max", provider: "MiniMax", speed: "slow", aspectRatios: ["21:9", "16:9", "4:3", "1:1", "3:4", "9:16"], falInput: { aspectRatio: "aspect_ratio" } },
|
|
38
|
+
{ id: "bytedance/seedance-2.5/text-to-video", label: "Seedance 2.5", provider: "ByteDance", speed: "slow", aspectRatios: ["21:9", "16:9", "4:3", "1:1", "3:4", "9:16"], falInput: { aspectRatio: "aspect_ratio" } },
|
|
39
|
+
{ id: "fal-ai/kling-video/v3/pro/text-to-video", label: "Kling v3 Pro", provider: "Kling", speed: "slow", aspectRatios: ["16:9", "9:16", "1:1"], falInput: { aspectRatio: "aspect_ratio" } },
|
|
40
|
+
{ id: "fal-ai/veo3.1/fast", label: "Veo 3.1 Fast", provider: "Google", speed: "standard", aspectRatios: ["16:9", "9:16"], falInput: { aspectRatio: "aspect_ratio" } },
|
|
41
|
+
];
|
|
42
|
+
/** Create video, starting from an attached image. The video follows the image's shape, so none lists aspect ratios. */
|
|
43
|
+
export const SUGGESTED_IMAGE_TO_VIDEO_MODELS = [
|
|
44
|
+
{ id: "fal-ai/kling-video/v3/pro/image-to-video", label: "Kling v3 Pro", provider: "Kling", speed: "slow", falInput: { image: "start_image_url" } },
|
|
45
|
+
{ id: "minimax/h3-max/image-to-video", label: "MiniMax H3 Max", provider: "MiniMax", speed: "slow", falInput: { image: "image_url" } },
|
|
46
|
+
{ id: "bytedance/seedance-2.5/image-to-video", label: "Seedance 2.5", provider: "ByteDance", speed: "slow", falInput: { image: "image_url" } },
|
|
47
|
+
{ id: "fal-ai/veo3.1/fast/image-to-video", label: "Veo 3.1 Fast", provider: "Google", speed: "standard", falInput: { image: "image_url" } },
|
|
48
|
+
];
|
|
49
|
+
/**
|
|
50
|
+
* Edit an image. Flux 3 Image reads marked regions as boxes; the others get
|
|
51
|
+
* them described in words. An edit keeps the image's shape, so no aspect
|
|
52
|
+
* ratios.
|
|
53
|
+
*/
|
|
54
|
+
export const SUGGESTED_EDIT_MODELS = [
|
|
55
|
+
{
|
|
56
|
+
id: "blackforestlabs/flux-3/edit-image",
|
|
57
|
+
label: "Flux 3 Image",
|
|
58
|
+
provider: "Black Forest Labs",
|
|
59
|
+
speed: "standard",
|
|
60
|
+
regionFormat: "flux-3-boxes",
|
|
61
|
+
maxRegions: DEFAULT_MAX_REGIONS,
|
|
62
|
+
falInput: { image: "image_urls" },
|
|
63
|
+
},
|
|
64
|
+
{ id: "fal-ai/nano-banana-pro/edit", label: "Nano Banana Pro", provider: "Google", speed: "standard", falInput: { image: "image_urls" } },
|
|
65
|
+
{ id: "openai/gpt-image-2/edit", label: "GPT Image 2", provider: "OpenAI", speed: "slow", falInput: { image: "image_urls" } },
|
|
66
|
+
];
|
|
67
|
+
/**
|
|
68
|
+
* MediaAnalyzer's models per media kind, on OpenRouter. The first in each
|
|
69
|
+
* is `ChaiProvider`'s default reasoning model for that kind.
|
|
70
|
+
*/
|
|
71
|
+
export const SUGGESTED_ANALYSIS_MODELS = {
|
|
72
|
+
image: [
|
|
73
|
+
{ id: "anthropic/claude-opus-5", label: "Claude Opus 5", provider: "Anthropic", speed: "standard" },
|
|
74
|
+
{ id: "openai/gpt-5.2", label: "GPT-5.2", provider: "OpenAI", speed: "standard" },
|
|
75
|
+
{ id: "google/gemini-3.5-flash", label: "Gemini 3.5 Flash", provider: "Google", speed: "fast" },
|
|
76
|
+
{ id: "meta-llama/llama-4-maverick", label: "Llama 4 Maverick", provider: "Meta", speed: "standard" },
|
|
77
|
+
{ id: "x-ai/grok-4.5", label: "Grok 4.5", provider: "xAI", speed: "standard" },
|
|
78
|
+
],
|
|
79
|
+
video: [
|
|
80
|
+
{ id: "google/gemini-3.8-flash", label: "Gemini 3.8 Flash", provider: "Google", speed: "fast" },
|
|
81
|
+
{ id: "google/gemini-3.5-flash", label: "Gemini 3.5 Flash", provider: "Google", speed: "fast" },
|
|
82
|
+
{ id: "amazon/nova-2-lite-v1", label: "Nova 2 Lite", provider: "Amazon", speed: "fast" },
|
|
83
|
+
{ id: "bytedance-seed/seed-2.0-lite", label: "Seed 2.0 Lite", provider: "ByteDance", speed: "fast" },
|
|
84
|
+
{ id: "moonshotai/kimi-k3", label: "Kimi K3", provider: "Moonshot", speed: "standard" },
|
|
85
|
+
],
|
|
86
|
+
audio: [
|
|
87
|
+
{ id: "google/gemini-3.8-flash", label: "Gemini 3.8 Flash", provider: "Google", speed: "fast" },
|
|
88
|
+
{ id: "openai/gpt-audio", label: "GPT Audio", provider: "OpenAI", speed: "standard" },
|
|
89
|
+
{ id: "mistralai/voxtral-small-24b-2507", label: "Voxtral Small 24B", provider: "Mistral", speed: "standard" },
|
|
90
|
+
{ id: "xiaomi/mimo-v2.5", label: "MiMo V2.5", provider: "Xiaomi", speed: "standard" },
|
|
91
|
+
],
|
|
92
|
+
};
|
|
93
|
+
/** The suggested Fal models for a generation use case, or none (text requests use the reasoning model). */
|
|
94
|
+
export function suggestedModels(useCase) {
|
|
95
|
+
switch (useCase) {
|
|
96
|
+
case "text-to-image":
|
|
97
|
+
return SUGGESTED_IMAGE_MODELS;
|
|
98
|
+
case "text-to-video":
|
|
99
|
+
return SUGGESTED_TEXT_TO_VIDEO_MODELS;
|
|
100
|
+
case "image-to-video":
|
|
101
|
+
return SUGGESTED_IMAGE_TO_VIDEO_MODELS;
|
|
102
|
+
case "image-edit":
|
|
103
|
+
return SUGGESTED_EDIT_MODELS;
|
|
104
|
+
default:
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/** Every suggested Fal model, by id: how the Fal engine knows each one's inputs. */
|
|
109
|
+
export const SUGGESTED_FAL_MODELS = new Map([...SUGGESTED_IMAGE_MODELS, ...SUGGESTED_TEXT_TO_VIDEO_MODELS, ...SUGGESTED_IMAGE_TO_VIDEO_MODELS, ...SUGGESTED_EDIT_MODELS].map((m) => [m.id, m]));
|
|
110
|
+
//# sourceMappingURL=suggested-models.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"suggested-models.js","sourceRoot":"","sources":["../src/suggested-models.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -87,6 +87,13 @@ export interface ModelOption {
|
|
|
87
87
|
regionFormat?: RegionFormat;
|
|
88
88
|
/** For image-edit models: the most regions one edit takes. `EditCard` disables "New region" at this many. */
|
|
89
89
|
maxRegions?: number;
|
|
90
|
+
/**
|
|
91
|
+
* How a Fal model takes its inputs, from its Fal schema: which field the
|
|
92
|
+
* attached image goes in, and whether the aspect ratio is sent as
|
|
93
|
+
* `aspect_ratio` or as a named `image_size`. The suggested lists carry it;
|
|
94
|
+
* without it the Fal engine guesses from the id.
|
|
95
|
+
*/
|
|
96
|
+
falInput?: FalInputFormat;
|
|
90
97
|
/**
|
|
91
98
|
* The output aspect ratios the model takes, e.g. `["1:1", "16:9", "9:16"]`.
|
|
92
99
|
* Composer's Aspect ratio menu offers only ratios the selected models
|
|
@@ -96,6 +103,11 @@ export interface ModelOption {
|
|
|
96
103
|
*/
|
|
97
104
|
aspectRatios?: string[];
|
|
98
105
|
}
|
|
106
|
+
/** See `ModelOption.falInput`. */
|
|
107
|
+
export interface FalInputFormat {
|
|
108
|
+
image?: "image_url" | "image_urls" | "start_image_url";
|
|
109
|
+
aspectRatio?: "aspect_ratio" | "image_size";
|
|
110
|
+
}
|
|
99
111
|
/** How an edit's regions reach the model — see `ModelOption.regionFormat`. */
|
|
100
112
|
export type RegionFormat = "flux-3-boxes" | "text";
|
|
101
113
|
/**
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,6DAA6D;AAC7D,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,CAAC;AAE7D;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,SAAS,CAAC;IAChB,kFAAkF;IAClF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,8DAA8D;IAC9D,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;GAMG;AACH,MAAM,WAAW,KAAK;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;CACjB;AAED;;;;GAIG;AACH,MAAM,WAAW,cAAc,CAAC,CAAC,GAAG,OAAO;IACzC,KAAK,EAAE,CAAC,CAAC;IACT,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,CAAC;IAC5B,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,oFAAoF;AACpF,MAAM,WAAW,eAAe,CAAC,CAAC;IAChC,KAAK,EAAE,CAAC,CAAC;IACT,KAAK,EAAE,MAAM,CAAC;CACf;AAED,iDAAiD;AACjD,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX;;;;;OAKG;IACH,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,wEAAwE;IACxE,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,CAAC;IACpC;;;;;;OAMG;IACH,qBAAqB,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1D;;;;;OAKG;IACH,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,6GAA6G;IAC7G,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAED,8EAA8E;AAC9E,MAAM,MAAM,YAAY,GAAG,cAAc,GAAG,MAAM,CAAC;AAEnD;;;;;;;GAOG;AACH,MAAM,MAAM,iBAAiB,GACzB,eAAe,GACf,YAAY,GACZ,eAAe,GACf,gBAAgB,GAChB,YAAY,GACZ,QAAQ,GACR,cAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,6DAA6D;AAC7D,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,CAAC;AAE7D;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,SAAS,CAAC;IAChB,kFAAkF;IAClF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,8DAA8D;IAC9D,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;GAMG;AACH,MAAM,WAAW,KAAK;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;CACjB;AAED;;;;GAIG;AACH,MAAM,WAAW,cAAc,CAAC,CAAC,GAAG,OAAO;IACzC,KAAK,EAAE,CAAC,CAAC;IACT,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,CAAC;IAC5B,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,oFAAoF;AACpF,MAAM,WAAW,eAAe,CAAC,CAAC;IAChC,KAAK,EAAE,CAAC,CAAC;IACT,KAAK,EAAE,MAAM,CAAC;CACf;AAED,iDAAiD;AACjD,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX;;;;;OAKG;IACH,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,wEAAwE;IACxE,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,CAAC;IACpC;;;;;;OAMG;IACH,qBAAqB,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1D;;;;;OAKG;IACH,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,6GAA6G;IAC7G,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAED,kCAAkC;AAClC,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,EAAE,WAAW,GAAG,YAAY,GAAG,iBAAiB,CAAC;IACvD,WAAW,CAAC,EAAE,cAAc,GAAG,YAAY,CAAC;CAC7C;AAED,8EAA8E;AAC9E,MAAM,MAAM,YAAY,GAAG,cAAc,GAAG,MAAM,CAAC;AAEnD;;;;;;;GAOG;AACH,MAAM,MAAM,iBAAiB,GACzB,eAAe,GACf,YAAY,GACZ,eAAe,GACf,gBAAgB,GAChB,YAAY,GACZ,QAAQ,GACR,cAAc,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chai-ui/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "CHAI UI headless core — the Request/Run/Result data model and pluggable generation engines (Fal.ai, OpenRouter, mock). No framework, no rendering.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "kazarazat",
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import type { MediaKind, ModelOption } from "./types.js";
|
|
2
|
+
import { SUGGESTED_ANALYSIS_MODELS, SUGGESTED_FAL_MODELS } from "./suggested-models.js";
|
|
3
|
+
import { FAL_NAMED_IMAGE_SIZES } from "./engines/fal.js";
|
|
4
|
+
|
|
5
|
+
/** One way a provider no longer matches a suggested model. */
|
|
6
|
+
export interface SuggestedModelProblem {
|
|
7
|
+
provider: "fal" | "openrouter";
|
|
8
|
+
/** The model's id, or `"*"` when the provider couldn't be reached at all. */
|
|
9
|
+
modelId: string;
|
|
10
|
+
problem: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface SuggestedModelsCheck {
|
|
14
|
+
/** True when every suggested model still matches its provider. */
|
|
15
|
+
ok: boolean;
|
|
16
|
+
/** How many models were checked. */
|
|
17
|
+
checked: number;
|
|
18
|
+
problems: SuggestedModelProblem[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Checks the suggested model lists against Fal and OpenRouter as they are
|
|
23
|
+
* today: each model still exists and is active; for Fal, it still takes
|
|
24
|
+
* the aspect ratios and image field Chai sends, and needs no other field
|
|
25
|
+
* Chai doesn't send; for OpenRouter, it still accepts the media kind it's
|
|
26
|
+
* listed for. Run it while setting up your engine. It needs no API key,
|
|
27
|
+
* runs no model and costs nothing; both catalogs are public and allow
|
|
28
|
+
* calls from a browser.
|
|
29
|
+
*
|
|
30
|
+
* The lists themselves only change through package releases. A problem
|
|
31
|
+
* here means a provider changed since: pick another model, or update the
|
|
32
|
+
* package once a release catches up.
|
|
33
|
+
*/
|
|
34
|
+
export async function checkSuggestedModels(options: { fetchImpl?: typeof fetch } = {}): Promise<SuggestedModelsCheck> {
|
|
35
|
+
const fetchImpl: typeof fetch = (input, init) => (options.fetchImpl ?? fetch)(input, init);
|
|
36
|
+
const fal = [...SUGGESTED_FAL_MODELS.values()];
|
|
37
|
+
const analysis = Object.entries(SUGGESTED_ANALYSIS_MODELS).flatMap(([kind, models]) =>
|
|
38
|
+
(models ?? []).map((model) => ({ kind: kind as MediaKind, model }))
|
|
39
|
+
);
|
|
40
|
+
const problems = [...(await checkFal(fal, fetchImpl)), ...(await checkOpenRouter(analysis, fetchImpl))];
|
|
41
|
+
return { ok: problems.length === 0, checked: fal.length + analysis.length, problems };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
type Schema = { properties?: Record<string, Schema>; required?: string[]; enum?: unknown[]; const?: unknown; default?: unknown; anyOf?: Schema[]; allOf?: Schema[]; $ref?: string };
|
|
45
|
+
|
|
46
|
+
async function checkFal(models: ModelOption[], fetchImpl: typeof fetch): Promise<SuggestedModelProblem[]> {
|
|
47
|
+
const query = models.map((m) => `endpoint_id=${encodeURIComponent(m.id)}`).join("&");
|
|
48
|
+
let found: { endpoint_id: string; metadata?: { status?: string }; openapi?: OpenApi }[];
|
|
49
|
+
try {
|
|
50
|
+
// Fal answers 10 models a page, with a cursor for the next.
|
|
51
|
+
found = [];
|
|
52
|
+
let cursor: string | undefined;
|
|
53
|
+
do {
|
|
54
|
+
const res = await fetchImpl(
|
|
55
|
+
`https://api.fal.ai/v1/models?expand=openapi-3.0&${query}${cursor ? `&cursor=${encodeURIComponent(cursor)}` : ""}`
|
|
56
|
+
);
|
|
57
|
+
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
|
|
58
|
+
const page = (await res.json()) as { models?: typeof found; has_more?: boolean; next_cursor?: string };
|
|
59
|
+
found.push(...(page.models ?? []));
|
|
60
|
+
cursor = page.has_more ? page.next_cursor : undefined;
|
|
61
|
+
} while (cursor);
|
|
62
|
+
} catch (err) {
|
|
63
|
+
return [{ provider: "fal", modelId: "*", problem: `Couldn't reach Fal's model catalog: ${(err as Error).message}` }];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const problems: SuggestedModelProblem[] = [];
|
|
67
|
+
const add = (modelId: string, problem: string) => problems.push({ provider: "fal", modelId, problem });
|
|
68
|
+
for (const model of models) {
|
|
69
|
+
const entry = found.find((f) => f.endpoint_id === model.id);
|
|
70
|
+
if (!entry) {
|
|
71
|
+
add(model.id, "Not in Fal's catalog any more.");
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (entry.metadata?.status && entry.metadata.status !== "active") add(model.id, `Fal lists it as "${entry.metadata.status}".`);
|
|
75
|
+
const input = inputSchema(entry.openapi);
|
|
76
|
+
if (!input) continue;
|
|
77
|
+
const props = input.schema.properties ?? {};
|
|
78
|
+
|
|
79
|
+
const imageField = model.falInput?.image;
|
|
80
|
+
if (imageField && !props[imageField]) add(model.id, `No longer takes "${imageField}" for the image.`);
|
|
81
|
+
|
|
82
|
+
const ratioField = model.falInput?.aspectRatio;
|
|
83
|
+
if (model.aspectRatios && ratioField) {
|
|
84
|
+
const accepted = enumOf(props[ratioField], input.resolve);
|
|
85
|
+
if (!props[ratioField]) add(model.id, `No longer takes "${ratioField}".`);
|
|
86
|
+
else if (accepted) {
|
|
87
|
+
const wanted = ratioField === "image_size" ? model.aspectRatios.map((r) => FAL_NAMED_IMAGE_SIZES[r] ?? r) : model.aspectRatios;
|
|
88
|
+
const missing = wanted.filter((v) => !accepted.includes(v));
|
|
89
|
+
if (missing.length > 0) add(model.id, `No longer takes these ${ratioField} values: ${missing.join(", ")}.`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const sent = new Set(["prompt", imageField ?? "", ratioField ?? ""]);
|
|
94
|
+
for (const name of input.schema.required ?? []) {
|
|
95
|
+
if (!sent.has(name) && props[name]?.default === undefined) add(model.id, `Now requires "${name}", which Chai doesn't send.`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return problems;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function checkOpenRouter(
|
|
102
|
+
models: { kind: MediaKind; model: ModelOption }[],
|
|
103
|
+
fetchImpl: typeof fetch
|
|
104
|
+
): Promise<SuggestedModelProblem[]> {
|
|
105
|
+
let catalog: { id: string; architecture?: { input_modalities?: string[] } }[];
|
|
106
|
+
try {
|
|
107
|
+
const res = await fetchImpl("https://openrouter.ai/api/v1/models");
|
|
108
|
+
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
|
|
109
|
+
catalog = ((await res.json()) as { data?: typeof catalog }).data ?? [];
|
|
110
|
+
} catch (err) {
|
|
111
|
+
return [{ provider: "openrouter", modelId: "*", problem: `Couldn't reach OpenRouter's model list: ${(err as Error).message}` }];
|
|
112
|
+
}
|
|
113
|
+
const problems: SuggestedModelProblem[] = [];
|
|
114
|
+
for (const { kind, model } of models) {
|
|
115
|
+
const entry = catalog.find((c) => c.id === model.id);
|
|
116
|
+
if (!entry) problems.push({ provider: "openrouter", modelId: model.id, problem: "Not in OpenRouter's catalog any more." });
|
|
117
|
+
else if (!entry.architecture?.input_modalities?.includes(kind))
|
|
118
|
+
problems.push({ provider: "openrouter", modelId: model.id, problem: `No longer accepts ${kind}.` });
|
|
119
|
+
}
|
|
120
|
+
return problems;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
type OpenApi = {
|
|
124
|
+
paths?: Record<string, { post?: { requestBody?: { content?: Record<string, { schema?: Schema }> } } }>;
|
|
125
|
+
components?: { schemas?: Record<string, Schema> };
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
/** The model's request schema, from its OpenAPI document, with a resolver for `$ref`s. */
|
|
129
|
+
function inputSchema(openapi: OpenApi | undefined): { schema: Schema; resolve: (s: Schema) => Schema } | null {
|
|
130
|
+
const schemas = openapi?.components?.schemas ?? {};
|
|
131
|
+
const resolve = (s: Schema): Schema => (s.$ref ? (schemas[s.$ref.split("/").pop()!] ?? s) : s);
|
|
132
|
+
for (const path of Object.values(openapi?.paths ?? {})) {
|
|
133
|
+
const body = path.post?.requestBody?.content?.["application/json"]?.schema;
|
|
134
|
+
if (body) return { schema: resolve(body), resolve };
|
|
135
|
+
}
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** The allowed values of a field, wherever its schema keeps them. */
|
|
140
|
+
function enumOf(field: Schema | undefined, resolve: (s: Schema) => Schema): unknown[] | null {
|
|
141
|
+
if (!field) return null;
|
|
142
|
+
for (const s of [field, ...(field.anyOf ?? []), ...(field.allOf ?? [])].map(resolve)) {
|
|
143
|
+
if (s.enum) return s.enum;
|
|
144
|
+
if (s.const !== undefined) return [s.const];
|
|
145
|
+
}
|
|
146
|
+
return null;
|
|
147
|
+
}
|