@chai-ui/react 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.
Files changed (54) hide show
  1. package/DESIGN.md +6 -1
  2. package/README.md +79 -17
  3. package/dist/ChaiProvider.d.ts +3 -42
  4. package/dist/ChaiProvider.d.ts.map +1 -1
  5. package/dist/ChaiProvider.js +5 -43
  6. package/dist/ChaiProvider.js.map +1 -1
  7. package/dist/Composer.d.ts +10 -60
  8. package/dist/Composer.d.ts.map +1 -1
  9. package/dist/Composer.js +26 -24
  10. package/dist/Composer.js.map +1 -1
  11. package/dist/EditCard.d.ts +2 -16
  12. package/dist/EditCard.d.ts.map +1 -1
  13. package/dist/EditCard.js +7 -2
  14. package/dist/EditCard.js.map +1 -1
  15. package/dist/MediaAnalyzer.d.ts +14 -18
  16. package/dist/MediaAnalyzer.d.ts.map +1 -1
  17. package/dist/MediaAnalyzer.js +26 -8
  18. package/dist/MediaAnalyzer.js.map +1 -1
  19. package/dist/ResultCard.d.ts.map +1 -1
  20. package/dist/ResultCard.js +10 -6
  21. package/dist/ResultCard.js.map +1 -1
  22. package/dist/icons.d.ts +1 -0
  23. package/dist/icons.d.ts.map +1 -1
  24. package/dist/icons.js +9 -0
  25. package/dist/icons.js.map +1 -1
  26. package/dist/index.d.ts +2 -2
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +1 -1
  29. package/dist/index.js.map +1 -1
  30. package/dist/media-actions.d.ts +5 -2
  31. package/dist/media-actions.d.ts.map +1 -1
  32. package/dist/media-actions.js +12 -3
  33. package/dist/media-actions.js.map +1 -1
  34. package/dist/style.css +15 -0
  35. package/dist/useComposer.d.ts +11 -86
  36. package/dist/useComposer.d.ts.map +1 -1
  37. package/dist/useComposer.js +26 -291
  38. package/dist/useComposer.js.map +1 -1
  39. package/dist/useMediaAnalyzer.d.ts +8 -28
  40. package/dist/useMediaAnalyzer.d.ts.map +1 -1
  41. package/dist/useMediaAnalyzer.js +9 -106
  42. package/dist/useMediaAnalyzer.js.map +1 -1
  43. package/package.json +3 -3
  44. package/src/ChaiProvider.tsx +12 -70
  45. package/src/Composer.tsx +42 -90
  46. package/src/EditCard.tsx +18 -20
  47. package/src/MediaAnalyzer.tsx +51 -30
  48. package/src/ResultCard.tsx +12 -4
  49. package/src/icons.tsx +14 -0
  50. package/src/index.ts +10 -0
  51. package/src/media-actions.ts +13 -4
  52. package/src/style.css +15 -0
  53. package/src/useComposer.ts +33 -411
  54. package/src/useMediaAnalyzer.ts +15 -146
package/DESIGN.md CHANGED
@@ -50,7 +50,12 @@ use these names: `Results`, `Compare`, `ModelPicker`,
50
50
  Claude Opus 5 for text and images, Gemini 3.8 Flash for video and
51
51
  audio, on OpenRouter. They're suggestions; respect a builder's own
52
52
  `reasoningModel`, `reasoningModelByKind` and `reasoningEngine`.
53
- 7. **Only show what's real.** Show cost only when the engine reports it.
53
+ 7. **Suggest models, never require them.** With no `models`, Composer and
54
+ MediaAnalyzer use the suggested lists (`suggestedModels`,
55
+ `SUGGESTED_ANALYSIS_MODELS`), each model carrying its verified aspect
56
+ ratios and Fal input format. A builder's own list always wins. Don't
57
+ make a builder write a model list to get a working Model menu.
58
+ 8. **Only show what's real.** Show cost only when the engine reports it.
54
59
  Don't show model rankings or leaderboard positions you can't keep up to
55
60
  date. Don't show a control the selected model can't honor.
56
61
 
package/README.md CHANGED
@@ -80,7 +80,9 @@ capability beyond the default file picker. Every other control is additive:
80
80
  onAttachMenuSelect={(action) => {
81
81
  if (action === "create-image") setUseCase({ kind: "image", label: "Image" });
82
82
  }}
83
- models={models}
83
+ // The Model menu lists Chai's suggested models for the use case, with the
84
+ // first picked, unless you pass your own `models` (see "Suggested models").
85
+ // It shows only with `onModelChange`; without it the first model still runs.
84
86
  modelId={modelId}
85
87
  onModelChange={setModelId}
86
88
  // An "Auto-select model" switch appears at the top of the model menu so
@@ -89,9 +91,10 @@ capability beyond the default file picker. Every other control is additive:
89
91
  autoSelectModel={autoSelectModel}
90
92
  showAutoSelectToggle
91
93
  onAutoSelectModelChange={setAutoSelectModel}
92
- // The Aspect ratio menu offers what the selected models take: give each
93
- // model its `aspectRatios`, e.g. ["1:1", "16:9", "9:16"]. A model without
94
- // them (image-to-video follows the input image) shows no menu.
94
+ // The Aspect ratio menu offers what the selected models take. Suggested
95
+ // models carry their `aspectRatios`; give your own models theirs, e.g.
96
+ // ["1:1", "16:9", "9:16"]. A model without them (image-to-video follows
97
+ // the input image) shows no menu.
95
98
  aspectRatio={aspectRatio}
96
99
  onAspectRatioChange={setAspectRatio}
97
100
  // Omit onEnhance entirely to hide the "optimize prompt" button — e.g.
@@ -113,9 +116,30 @@ for a live example with every opt-in feature.
113
116
  ### No use case picked: a text request
114
117
 
115
118
  A prompt typed with no use case picked is a text request
116
- (`TEXT_USE_CASE`): it gets a text reply rather than doing nothing. For an
117
- app that's mostly about images, set the default instead; it's never shown
118
- as a chip:
119
+ (`TEXT_USE_CASE`): it gets a text reply rather than doing nothing. That's
120
+ how to use the Composer as a plain chat bar for an LLM:
121
+
122
+ - **The model:** `useComposer` sends it to `textEngine` with `textModel`
123
+ when you set them, otherwise to `ChaiProvider`'s reasoning model
124
+ (default Claude Opus 5 on OpenRouter). Without a `ChaiProvider` or a
125
+ `textEngine`, a mock text engine answers, so wrap your app in
126
+ `<ChaiProvider>` for real replies.
127
+ - **No Model menu:** the suggested lists cover image and video only, so a
128
+ text request shows no Model menu. To offer a choice of text models, pass
129
+ your own `models`.
130
+ - **The reply streams** into the `ResultCard` as it's written, and
131
+ `cancel()` stops it, keeping what has arrived.
132
+
133
+ ```tsx
134
+ <ChaiProvider>
135
+ <Composer value={value} onChange={setValue} onSubmit={submit} submitting={busy} onAbort={cancel} />
136
+ {run && <ResultCard results={run.results} prompt={run.request.prompt} onAction={() => {}} />}
137
+ </ChaiProvider>
138
+ ```
139
+
140
+ For an app that's mostly about images, set the default instead; it's
141
+ never shown as a chip, and it brings the suggested image models and the
142
+ Aspect ratio menu:
119
143
 
120
144
  ```tsx
121
145
  <Composer defaultUseCase={{ kind: "image", label: "Image" }} … />
@@ -160,8 +184,8 @@ unlisted reply), the request runs on the first listed model and
160
184
  ## What else this package ships
161
185
 
162
186
  - **`MediaAnalyzer`** — drop in an image, video, or audio clip and get a
163
- generation-ready prompt back, with kind-aware model menus and a prompt
164
- length picker. Wire it with `useMediaAnalyzer()`, which sends the media
187
+ generation-ready prompt back, with kind-aware model menus (suggested
188
+ OpenRouter models unless you pass your own) and a prompt length picker. Wire it with `useMediaAnalyzer()`, which sends the media
165
189
  to `ChaiProvider`'s reasoning model and returns the prompt:
166
190
  `onSubmit={submit} submitting={analyzing} submitError={error}`.
167
191
  - **`ResultCard`** — renders one prompt's generated results (image, video,
@@ -178,6 +202,44 @@ unlisted reply), the request runs on the first listed model and
178
202
  The components are built from these; they're also exported directly if
179
203
  you're assembling your own layout.
180
204
 
205
+ ## Suggested models
206
+
207
+ You don't have to write model lists. Chai ships suggested lists, checked
208
+ against each provider's own catalog, and the components use them when you
209
+ pass none:
210
+
211
+ | Use case | Export | Provider |
212
+ |---|---|---|
213
+ | Create image | `SUGGESTED_IMAGE_MODELS` | Fal |
214
+ | Create video from a prompt | `SUGGESTED_TEXT_TO_VIDEO_MODELS` | Fal |
215
+ | Create video from an image | `SUGGESTED_IMAGE_TO_VIDEO_MODELS` | Fal |
216
+ | Edit image | `SUGGESTED_EDIT_MODELS` | Fal |
217
+ | Media analysis, per media kind | `SUGGESTED_ANALYSIS_MODELS` | OpenRouter |
218
+
219
+ - **Composer** offers the list for the current use case
220
+ (`suggestedModels(useCase)`), with its first model picked until the person
221
+ picks another. **MediaAnalyzer** offers the analysis lists.
222
+ - **Your own list wins.** Pass `models` (or `modelsByKind`) to replace a
223
+ list, or `[]` for no Model menu. Nothing is required.
224
+ - **Each suggested model knows its inputs**: its aspect ratios, and for Fal
225
+ the field its image goes in (`falInput`), so the Fal engine sends exactly
226
+ what that model expects.
227
+ - **They change only through package releases.** Providers change faster,
228
+ so check them while setting up your engine. It needs no key, runs no
229
+ model and costs nothing:
230
+
231
+ ```ts
232
+ import { checkSuggestedModels } from "@chai-ui/react";
233
+
234
+ const { ok, problems } = await checkSuggestedModels();
235
+ // problems: [{ provider: "fal", modelId, problem: "No longer takes these aspect_ratio values: 21:9." }]
236
+ ```
237
+
238
+ It confirms each model still exists and is active, still takes the
239
+ aspect ratios and image field Chai sends (Fal), and still accepts its media
240
+ kind (OpenRouter). If something changed, pick another model or update the
241
+ package once a release catches up.
242
+
181
243
  ## `useComposer` — wiring `Composer` to a real engine
182
244
 
183
245
  ```tsx
@@ -291,11 +353,12 @@ to `EDIT_IMAGE_USE_CASE`, pass the attached image to `useComposer` as
291
353
  `editImage`, and drop in the card:
292
354
 
293
355
  ```tsx
294
- import { Composer, EditCard, EDIT_IMAGE_USE_CASE, PRECISE_EDIT_MODELS, useComposer } from "@chai-ui/react";
356
+ import { Composer, EditCard, EDIT_IMAGE_USE_CASE, SUGGESTED_EDIT_MODELS, useComposer } from "@chai-ui/react";
295
357
 
296
358
  const editImage = useCase?.edit ? (attachments.find((a) => a.kind === "image") ?? null) : null;
297
359
  const { run, submit, edit } = useComposer({ engine: createFalEngine({ outputKind: "image" }), editImage });
298
- const model = PRECISE_EDIT_MODELS.find((m) => m.id === modelId);
360
+ // Unpicked, the first suggested edit model (Flux 3 Image) runs.
361
+ const model = SUGGESTED_EDIT_MODELS.find((m) => m.id === modelId) ?? SUGGESTED_EDIT_MODELS[0];
299
362
 
300
363
  {edit && <EditCard {...edit} maxRegions={model?.maxRegions} />}
301
364
  {!edit && run && <ResultCard results={run.results} prompt={run.request.prompt} onAction={…} />}
@@ -305,7 +368,6 @@ const model = PRECISE_EDIT_MODELS.find((m) => m.id === modelId);
305
368
  onAttachMenuSelect={(action) => {
306
369
  if (action === "edit-media") setUseCase(EDIT_IMAGE_USE_CASE);
307
370
  }}
308
- models={useCase?.edit ? PRECISE_EDIT_MODELS : models}
309
371
  regions={edit?.regions}
310
372
  onRegionsChange={edit?.onRegionsChange}
311
373
  onSubmit={submit}
@@ -319,11 +381,11 @@ const model = PRECISE_EDIT_MODELS.find((m) => m.id === modelId);
319
381
  - **Submitting.** An edit needs the image plus a whole-image prompt, a
320
382
  region, or both. Regions carry their own instructions, so the prompt can
321
383
  stay empty.
322
- - **Models.** `PRECISE_EDIT_MODELS` is a suggested list: Flux 3 Image on
323
- Fal, which reads each region as a box. Offer any edit model you like; one
324
- without a `regionFormat` gets its regions described in words, which is
325
- less precise. Change how the prompt is built with `useComposer`'s
326
- `editPrompt`.
384
+ - **Models.** In edit mode Composer offers `SUGGESTED_EDIT_MODELS`: Flux 3
385
+ Image first, which reads each region as a box, then Nano Banana Pro and
386
+ GPT Image 2. Offer any edit model you like; one without a `regionFormat`
387
+ gets its regions described in words, which is less precise. Change how
388
+ the prompt is built with `useComposer`'s `editPrompt`.
327
389
  - **Versions.** Each edit's result is a new version, shown in the card with
328
390
  dots to page back. The next edit applies to the version showing.
329
391
  - **Images only** for now.
@@ -1,49 +1,10 @@
1
1
  import { type ReactNode } from "react";
2
- import { type DroppedMedia, type GenerationEngine, type MediaKind } from "@chai-ui/core";
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
- export interface ChaiProviderProps {
23
- /** Text, image and routing model. Defaults to `DEFAULT_REASONING_MODEL`. */
24
- reasoningModel?: string;
25
- /**
26
- * Per-attachment-kind models, merged over `DEFAULT_REASONING_MODEL_BY_KIND`
27
- * one kind at a time, e.g. `{ video: "qwen/qwen3.8-omni-flash" }` keeps
28
- * the default for audio. Map a kind to `reasoningModel`'s value to use
29
- * one model for everything.
30
- */
31
- reasoningModelByKind?: Partial<Record<MediaKind, string>>;
32
- /**
33
- * The engine the reasoning models run on. Defaults to OpenRouter,
34
- * independent of the generation engine: Fal's own LLM access is an
35
- * OpenRouter proxy, so defaulting to it would double-proxy.
36
- */
37
- reasoningEngine?: GenerationEngine;
2
+ import { type Reasoning, type ReasoningConfig } from "@chai-ui/core";
3
+ export { DEFAULT_REASONING_MODEL, DEFAULT_REASONING_MODEL_BY_KIND, reasoningModelFor, type Reasoning, } from "@chai-ui/core";
4
+ export interface ChaiProviderProps extends ReasoningConfig {
38
5
  children?: ReactNode;
39
6
  }
40
7
  export declare function ChaiProvider({ reasoningModel, reasoningModelByKind, reasoningEngine, children }: ChaiProviderProps): import("react").JSX.Element;
41
8
  /** The nearest `ChaiProvider`'s reasoning engine and models, or `undefined` outside one. */
42
9
  export declare function useReasoning(): Reasoning | undefined;
43
- /**
44
- * The reasoning model for a call with these attachments: the first
45
- * attachment whose kind has its own model wins (a clip plus a still goes
46
- * to the model that can read the clip), else `reasoning.model`.
47
- */
48
- export declare function reasoningModelFor(reasoning: Reasoning, attachments?: DroppedMedia[]): string;
49
10
  //# sourceMappingURL=ChaiProvider.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ChaiProvider.d.ts","sourceRoot":"","sources":["../src/ChaiProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAsC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAC3E,OAAO,EAA0B,KAAK,YAAY,EAAE,KAAK,gBAAgB,EAAE,KAAK,SAAS,EAAE,MAAM,eAAe,CAAC;AAEjH;;;;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;AAID,MAAM,WAAW,iBAAiB;IAChC,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;IACnC,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED,wBAAgB,YAAY,CAAC,EAAE,cAAc,EAAE,oBAAoB,EAAE,eAAe,EAAE,QAAQ,EAAE,EAAE,iBAAiB,+BAgBlH;AAMD,4FAA4F;AAC5F,wBAAgB,YAAY,IAAI,SAAS,GAAG,SAAS,CAEpD;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,SAAS,EAAE,WAAW,GAAE,YAAY,EAAO,GAAG,MAAM,CAMhG"}
1
+ {"version":3,"file":"ChaiProvider.d.ts","sourceRoot":"","sources":["../src/ChaiProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAsC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAC3E,OAAO,EAAuC,KAAK,SAAS,EAAE,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAE1G,OAAO,EACL,uBAAuB,EACvB,+BAA+B,EAC/B,iBAAiB,EACjB,KAAK,SAAS,GACf,MAAM,eAAe,CAAC;AAIvB,MAAM,WAAW,iBAAkB,SAAQ,eAAe;IACxD,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED,wBAAgB,YAAY,CAAC,EAAE,cAAc,EAAE,oBAAoB,EAAE,eAAe,EAAE,QAAQ,EAAE,EAAE,iBAAiB,+BAQlH;AAED,4FAA4F;AAC5F,wBAAgB,YAAY,IAAI,SAAS,GAAG,SAAS,CAEpD"}
@@ -1,54 +1,16 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { createContext, useContext, useMemo } from "react";
3
- import { createOpenRouterEngine } from "@chai-ui/core";
4
- /**
5
- * The suggested reasoning model for text and images: prompt enhancement,
6
- * auto-select routing and image analysis. A default, not a requirement —
7
- * pass any model id `reasoningEngine` can run.
8
- */
9
- export const DEFAULT_REASONING_MODEL = "anthropic/claude-opus-5";
10
- /**
11
- * Suggested per-kind overrides for media Claude can't read. Gemini takes
12
- * video and audio directly. For a cheaper option, swap in
13
- * `"qwen/qwen3.8-omni-flash"`, which takes both too.
14
- */
15
- export const DEFAULT_REASONING_MODEL_BY_KIND = {
16
- video: "google/gemini-3.8-flash",
17
- audio: "google/gemini-3.8-flash",
18
- };
3
+ import { createReasoning, reasoningEngineFor } from "@chai-ui/core";
4
+ export { DEFAULT_REASONING_MODEL, DEFAULT_REASONING_MODEL_BY_KIND, reasoningModelFor, } from "@chai-ui/core";
19
5
  const ReasoningContext = createContext(undefined);
20
6
  export function ChaiProvider({ reasoningModel, reasoningModelByKind, reasoningEngine, children }) {
21
- const engine = useMemo(() => reasoningEngine ?? createOpenRouterEngine(), [reasoningEngine]);
22
- const model = reasoningModel ?? DEFAULT_REASONING_MODEL;
23
- const video = reasoningModelByKind?.video;
24
- const audio = reasoningModelByKind?.audio;
25
- const image = reasoningModelByKind?.image;
26
- const text = reasoningModelByKind?.text;
27
- const value = useMemo(() => ({
28
- engine,
29
- model,
30
- modelByKind: { ...DEFAULT_REASONING_MODEL_BY_KIND, ...dropUndefined({ video, audio, image, text }) },
31
- }), [engine, model, video, audio, image, text]);
7
+ const engine = useMemo(() => reasoningEngineFor({ reasoningEngine }), [reasoningEngine]);
8
+ const { video, audio, image, text } = reasoningModelByKind ?? {};
9
+ const value = useMemo(() => createReasoning({ reasoningModel, reasoningModelByKind: { video, audio, image, text } }, engine), [engine, reasoningModel, video, audio, image, text]);
32
10
  return _jsx(ReasoningContext.Provider, { value: value, children: children });
33
11
  }
34
- function dropUndefined(obj) {
35
- return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined));
36
- }
37
12
  /** The nearest `ChaiProvider`'s reasoning engine and models, or `undefined` outside one. */
38
13
  export function useReasoning() {
39
14
  return useContext(ReasoningContext);
40
15
  }
41
- /**
42
- * The reasoning model for a call with these attachments: the first
43
- * attachment whose kind has its own model wins (a clip plus a still goes
44
- * to the model that can read the clip), else `reasoning.model`.
45
- */
46
- export function reasoningModelFor(reasoning, attachments = []) {
47
- for (const { kind } of attachments) {
48
- const model = reasoning.modelByKind[kind];
49
- if (model)
50
- return model;
51
- }
52
- return reasoning.model;
53
- }
54
16
  //# sourceMappingURL=ChaiProvider.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ChaiProvider.js","sourceRoot":"","sources":["../src/ChaiProvider.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,OAAO,EAAkB,MAAM,OAAO,CAAC;AAC3E,OAAO,EAAE,sBAAsB,EAA4D,MAAM,eAAe,CAAC;AAEjH;;;;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;AAUF,MAAM,gBAAgB,GAAG,aAAa,CAAwB,SAAS,CAAC,CAAC;AAqBzE,MAAM,UAAU,YAAY,CAAC,EAAE,cAAc,EAAE,oBAAoB,EAAE,eAAe,EAAE,QAAQ,EAAqB;IACjH,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,eAAe,IAAI,sBAAsB,EAAE,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC;IAC7F,MAAM,KAAK,GAAG,cAAc,IAAI,uBAAuB,CAAC;IACxD,MAAM,KAAK,GAAG,oBAAoB,EAAE,KAAK,CAAC;IAC1C,MAAM,KAAK,GAAG,oBAAoB,EAAE,KAAK,CAAC;IAC1C,MAAM,KAAK,GAAG,oBAAoB,EAAE,KAAK,CAAC;IAC1C,MAAM,IAAI,GAAG,oBAAoB,EAAE,IAAI,CAAC;IACxC,MAAM,KAAK,GAAG,OAAO,CACnB,GAAG,EAAE,CAAC,CAAC;QACL,MAAM;QACN,KAAK;QACL,WAAW,EAAE,EAAE,GAAG,+BAA+B,EAAE,GAAG,aAAa,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,EAAE;KACrG,CAAC,EACF,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,CAC3C,CAAC;IACF,OAAO,KAAC,gBAAgB,CAAC,QAAQ,IAAC,KAAK,EAAE,KAAK,YAAG,QAAQ,GAA6B,CAAC;AACzF,CAAC;AAED,SAAS,aAAa,CAAmB,GAAM;IAC7C,OAAO,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC,CAAe,CAAC;AAClG,CAAC;AAED,4FAA4F;AAC5F,MAAM,UAAU,YAAY;IAC1B,OAAO,UAAU,CAAC,gBAAgB,CAAC,CAAC;AACtC,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"}
1
+ {"version":3,"file":"ChaiProvider.js","sourceRoot":"","sources":["../src/ChaiProvider.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,OAAO,EAAkB,MAAM,OAAO,CAAC;AAC3E,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAwC,MAAM,eAAe,CAAC;AAE1G,OAAO,EACL,uBAAuB,EACvB,+BAA+B,EAC/B,iBAAiB,GAElB,MAAM,eAAe,CAAC;AAEvB,MAAM,gBAAgB,GAAG,aAAa,CAAwB,SAAS,CAAC,CAAC;AAMzE,MAAM,UAAU,YAAY,CAAC,EAAE,cAAc,EAAE,oBAAoB,EAAE,eAAe,EAAE,QAAQ,EAAqB;IACjH,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,kBAAkB,CAAC,EAAE,eAAe,EAAE,CAAC,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC;IACzF,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,oBAAoB,IAAI,EAAE,CAAC;IACjE,MAAM,KAAK,GAAG,OAAO,CACnB,GAAG,EAAE,CAAC,eAAe,CAAC,EAAE,cAAc,EAAE,oBAAoB,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,CAAC,EACtG,CAAC,MAAM,EAAE,cAAc,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,CACpD,CAAC;IACF,OAAO,KAAC,gBAAgB,CAAC,QAAQ,IAAC,KAAK,EAAE,KAAK,YAAG,QAAQ,GAA6B,CAAC;AACzF,CAAC;AAED,4FAA4F;AAC5F,MAAM,UAAU,YAAY;IAC1B,OAAO,UAAU,CAAC,gBAAgB,CAAC,CAAC;AACtC,CAAC"}
@@ -1,63 +1,8 @@
1
1
  import * as React from "react";
2
- import type { DroppedMedia, EditRegion, ModelOption, ParameterOption, MediaKind } from "@chai-ui/core";
3
- export interface ComposerAttachment extends DroppedMedia {
4
- id: string;
5
- }
2
+ import type { EditRegion, ModelOption, ParameterOption, MediaKind } from "@chai-ui/core";
3
+ import type { ComposerAttachment, ComposerSubmitPayload, ComposerUseCase } from "@chai-ui/core";
4
+ export { EDIT_IMAGE_USE_CASE, TEXT_USE_CASE, type ComposerAttachment, type ComposerSelection, type ComposerSubmitPayload, type ComposerUseCase, } from "@chai-ui/core";
6
5
  export type ComposerAttachMenuAction = "add-media" | "create-image" | "create-video" | "edit-media";
7
- /**
8
- * The selected generation use case, shown as a removable chip (reference:
9
- * the mock's "Video"/"Image" chip) — deliberately independent of
10
- * `attachments`: a use case can be picked from the attach menu with nothing
11
- * attached yet (e.g. "Create video"). Composer never derives this from
12
- * `attachments` itself (that's app-level policy, not a Composer opinion —
13
- * the design). With none picked, `defaultUseCase` applies.
14
- */
15
- export interface ComposerUseCase {
16
- kind: MediaKind;
17
- label: string;
18
- /**
19
- * An edit of the attached image rather than a new one: no aspect ratio,
20
- * region chips when `regions` is set, and a submit needs only an image and
21
- * either a prompt or a region. Images only for now.
22
- */
23
- edit?: boolean;
24
- }
25
- /** A plain text request — what a submit means when no use case is picked, unless the builder sets `defaultUseCase`. */
26
- export declare const TEXT_USE_CASE: ComposerUseCase;
27
- /** Editing the attached image, the attach menu's "Edit image". Pair it with `useComposer`'s `editImage` and an `EditCard`. */
28
- export declare const EDIT_IMAGE_USE_CASE: ComposerUseCase;
29
- /** One use case to run and the models picked for it. Empty `modelIds` means the engine's default model. */
30
- export interface ComposerSelection {
31
- useCase: ComposerUseCase;
32
- modelIds: string[];
33
- /** The models offered for this use case: what auto-select routes among. */
34
- models: ModelOption[];
35
- }
36
- /**
37
- * Everything Composer itself tracks, assembled into one object at submit
38
- * time — not the the design `Request` shape (no `mode`/`params`/
39
- * `presetId`; those belong to the run store this package doesn't have yet,
40
- * see the package README), just an honest snapshot of this component's own
41
- * state. Handed to `onSubmit` so a builder doesn't have to re-derive it
42
- * from five separately-controlled props they already hold — mainly a
43
- * correctness/ergonomics win for a coding agent wiring this up from the
44
- * type signature alone.
45
- */
46
- export interface ComposerSubmitPayload {
47
- value: string;
48
- attachments: ComposerAttachment[];
49
- /** The use case to run — the picked one, or `defaultUseCase` when none is picked. The first one with `multiSelectUseCases`. */
50
- useCase: ComposerUseCase;
51
- /** The first picked model, or null for the engine's default. */
52
- modelId: string | null;
53
- aspectRatio: string | null;
54
- /** Everything to run: one entry per use case with its picked models. A single entry unless `multiSelectUseCases` is on. */
55
- selections: ComposerSelection[];
56
- /** When true, the model is chosen at submit (`useComposer` routes among each selection's `models`), not by the picks. */
57
- autoSelectModel: boolean;
58
- /** An edit's marked regions, each with its own instruction. Empty unless the use case is an edit. */
59
- regions?: EditRegion[];
60
- }
61
6
  export interface ComposerProps {
62
7
  /** The prompt text. Controlled — Composer owns no text state of its own. */
63
8
  value: string;
@@ -83,7 +28,7 @@ export interface ComposerProps {
83
28
  multiSelectUseCases?: boolean;
84
29
  useCases?: ComposerUseCase[];
85
30
  onUseCasesChange?: (next: ComposerUseCase[]) => void;
86
- /** Models per use-case kind, for `multiSelectUseCases`. */
31
+ /** Models per use-case kind, for `multiSelectUseCases`. A kind left out gets Chai's suggested models (see `models`). */
87
32
  modelsByKind?: Partial<Record<MediaKind, ModelOption[]>>;
88
33
  /**
89
34
  * Builder opt-in, off by default: the end user can pick several models,
@@ -94,7 +39,12 @@ export interface ComposerProps {
94
39
  multiSelectModels?: boolean;
95
40
  modelIds?: string[];
96
41
  onModelIdsChange?: (ids: string[]) => void;
97
- /** Models valid for the current use case. The Model menu only renders when this is non-empty. */
42
+ /**
43
+ * Models for the current use case. Leave it out and Composer offers Chai's
44
+ * suggested Fal models for the use case (`suggestedModels`), with the
45
+ * first one picked until the person picks another. Pass a list to use
46
+ * your own; pass `[]` for no Model menu.
47
+ */
98
48
  models?: ModelOption[];
99
49
  modelId?: string | null;
100
50
  /** `null` clears the pick: Composer does this when auto-select turns on, until routing picks a model. */
@@ -1 +1 @@
1
- {"version":3,"file":"Composer.d.ts","sourceRoot":"","sources":["../src/Composer.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Composer.d.ts","sourceRoot":"","sources":["../src/Composer.tsx"],"names":[],"mappings":"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"}
package/dist/Composer.js CHANGED
@@ -1,10 +1,13 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { StopIcon } from "./icons.js";
2
3
  import { useEffect, useRef, useState } from "react";
3
4
  import { createComponent } from "@lit/react";
4
5
  import * as React from "react";
5
6
  import { MdFab } from "@material/web/fab/fab.js";
7
+ import { deriveGenerationUseCase, suggestedModels, TEXT_USE_CASE } from "@chai-ui/core";
6
8
  import { SearchMenu } from "./primitives/SearchMenu.js";
7
9
  import { regionColor } from "./regions.js";
10
+ export { EDIT_IMAGE_USE_CASE, TEXT_USE_CASE, } from "@chai-ui/core";
8
11
  /** See primitives/Toggle.tsx for why `createComponent` is used instead of raw JSX on custom-element tags. */
9
12
  const MdFabElement = createComponent({
10
13
  react: React,
@@ -37,10 +40,6 @@ function nextAttachmentId() {
37
40
  ? crypto.randomUUID()
38
41
  : `attachment-${Date.now()}-${Math.random().toString(36).slice(2)}`;
39
42
  }
40
- /** A plain text request — what a submit means when no use case is picked, unless the builder sets `defaultUseCase`. */
41
- export const TEXT_USE_CASE = { kind: "text", label: "Text" };
42
- /** Editing the attached image, the attach menu's "Edit image". Pair it with `useComposer`'s `editImage` and an `EditCard`. */
43
- export const EDIT_IMAGE_USE_CASE = { kind: "image", label: "Edit image", edit: true };
44
43
  /**
45
44
  * The `bar`-layout Composer —
46
45
  * reference: the "Compose Component" design, whose four states (minimal / model+aspect select / auto-select with
@@ -77,18 +76,30 @@ export function Composer({ value, onChange, placeholder, promptLabel, attachment
77
76
  const showStop = submitting && Boolean(onAbort);
78
77
  const multiModel = multiSelectModels || multiSelectUseCases;
79
78
  const rawPickedIds = multiModel ? modelIds : modelId ? [modelId] : [];
79
+ // The app's list for a use case, or Chai's suggested models when it gave none.
80
+ const rosterFor = (u) => {
81
+ const given = multiSelectUseCases ? modelsByKind?.[u.kind] : models;
82
+ if (given)
83
+ return { options: given, suggested: false };
84
+ return { options: suggestedModels(u.edit ? "image-edit" : deriveGenerationUseCase(u, attachments)), suggested: true };
85
+ };
80
86
  // One menu section per use case with multi-use-case select; otherwise one flat roster.
81
- const modelSections = multiSelectUseCases
82
- ? activeUseCases
83
- .map((u) => ({ label: u.label, kind: u.kind, options: modelsByKind?.[u.kind] ?? [] }))
84
- .filter((s) => s.options.length > 0)
85
- : [{ label: "", kind: activeUseCases[0].kind, options: models ?? [] }].filter((s) => s.options.length > 0);
87
+ const modelSections = (multiSelectUseCases ? activeUseCases : activeUseCases.slice(0, 1))
88
+ .map((u) => ({ label: multiSelectUseCases ? u.label : "", kind: u.kind, ...rosterFor(u) }))
89
+ .filter((s) => s.options.length > 0);
86
90
  const allModels = modelSections.flatMap((s) => s.options);
87
91
  // A pick that isn't in the current list (e.g. the edit model after leaving
88
92
  // edit mode) is shown as unpicked and never submitted. With no list at
89
- // all, the app's pick is passed through as given.
90
- const pickedModelIds = allModels.length > 0 ? rawPickedIds.filter((id) => allModels.some((m) => m.id === id)) : rawPickedIds;
93
+ // all, the app's pick is passed through as given. With nothing picked
94
+ // from a suggested list (and auto-select off), its first model is the pick.
95
+ const validPicks = allModels.length > 0 ? rawPickedIds.filter((id) => allModels.some((m) => m.id === id)) : rawPickedIds;
96
+ const pickedModelIds = validPicks.length > 0 || autoSelectModel
97
+ ? validPicks
98
+ : modelSections.filter((s) => s.suggested).map((s) => s.options[0].id);
91
99
  const pickedModels = allModels.filter((m) => pickedModelIds.includes(m.id));
100
+ // No menu that can't change anything: without its handler, the Model menu
101
+ // hides (the default pick still runs).
102
+ const canPickModel = multiModel ? Boolean(onModelIdsChange) : Boolean(onModelChange);
92
103
  const modelTriggerLabel = pickedModels.length > 1
93
104
  ? `${pickedModels.length} models`
94
105
  : pickedModels[0]?.label ?? (autoSelectModel ? "Auto-select" : "Select models");
@@ -113,9 +124,9 @@ export function Composer({ value, onChange, placeholder, promptLabel, attachment
113
124
  function buildSelections() {
114
125
  return activeUseCases.map((u) => ({
115
126
  useCase: u,
116
- models: multiSelectUseCases ? (modelsByKind?.[u.kind] ?? []) : (models ?? []),
127
+ models: rosterFor(u).options,
117
128
  modelIds: multiSelectUseCases
118
- ? pickedModelIds.filter((id) => modelsByKind?.[u.kind]?.some((m) => m.id === id))
129
+ ? pickedModelIds.filter((id) => rosterFor(u).options.some((m) => m.id === id))
119
130
  : pickedModelIds,
120
131
  }));
121
132
  }
@@ -254,7 +265,7 @@ export function Composer({ value, onChange, placeholder, promptLabel, attachment
254
265
  }
255
266
  return (_jsxs("div", { className: "chai-composer", children: [attachments.length > 0 && (_jsx("div", { className: "chai-composer__thumbnails", children: attachments.map((a) => (_jsx(ComposerThumbnail, { attachment: a, onRemove: onAttachmentsChange ? () => handleRemoveAttachment(a.id) : undefined }, a.id))) })), editing && regions.length > 0 && (_jsx("ul", { className: "chai-composer__regions", "aria-label": "Edit regions", children: regions.map((r) => (_jsx("li", { children: _jsx(RegionChip, { region: r, onRemove: onRegionsChange ? () => onRegionsChange(regions.filter((x) => x.id !== r.id)) : undefined, disabled: disabled }) }, r.id))) })), _jsxs("div", { className: "chai-composer__field", children: [_jsx("textarea", { ref: textareaRef, className: `chai-composer__input chai-composer__type${animating ? " chai-composer__input--revealing" : ""}`, value: value, onChange: (e) => onChange(e.target.value), placeholder: effectivePlaceholder, "aria-label": promptLabel ?? effectivePlaceholder, rows: 1, disabled: disabled || animating }), animating && (_jsx("div", { className: "chai-composer__reveal chai-composer__type", "aria-hidden": "true", children: _jsx("span", { className: "chai-composer__reveal-text", children: animatingText.slice(0, revealedLength) }) })), onEnhance && (_jsx("button", { type: "button", className: `chai-composer__optimize${enhanceBusy ? " chai-composer__optimize--active" : ""}`, onClick: handleOptimizeClick, disabled: disabled || (isEnhanced ? false : !value.trim() || enhanceBusy), "aria-label": isEnhanced ? "Revert to original prompt" : "Optimize prompt", title: isEnhanced ? "Revert to original prompt" : "Optimize prompt", children: isEnhanced ? _jsx(UndoIcon, {}) : _jsx(OptimizeIcon, {}) }))] }), submitError && (_jsxs("p", { className: "chai-composer__error", role: "alert", children: [_jsx(ErrorIcon, {}), submitError] })), _jsxs("div", { className: "chai-composer__controls", children: [_jsxs("div", { className: "chai-composer__controls-start", children: [menuItems.length > 0 && (_jsx(ComposerAttachMenu, { items: menuItems, onSelect: onAttachMenuSelect, onAddMediaFiles: handleFilesSelected, imagesOnly: editing, disabled: disabled })), pickedUseCases.map((u) => (_jsx(UseCaseChip, { useCase: u, onRemove: multiSelectUseCases
256
267
  ? onUseCasesChange && (() => onUseCasesChange(useCases.filter((x) => x.kind !== u.kind)))
257
- : onClearUseCase, disabled: disabled }, u.kind))), modelSections.length > 0 && (_jsx(SearchMenu, { ...(multiSelectUseCases
268
+ : onClearUseCase, disabled: disabled }, u.kind))), modelSections.length > 0 && canPickModel && (_jsx(SearchMenu, { ...(multiSelectUseCases
258
269
  ? { sections: modelSections.map((s) => ({ label: s.label, options: s.options })) }
259
270
  : { options: modelSections[0].options }), value: multiModel ? pickedModelIds : modelId, multiple: multiModel, toggleHeader: showAutoSelectToggle
260
271
  ? {
@@ -262,7 +273,7 @@ export function Composer({ value, onChange, placeholder, promptLabel, attachment
262
273
  value: autoSelectModel,
263
274
  onChange: (v) => onAutoSelectModelChange?.(v),
264
275
  }
265
- : undefined, triggerLabel: modelTriggerLabel, menuLabel: multiModel ? "Select models" : "Select model", onSelect: (opt) => toggleModel(opt.id), searchable: false, triggerVariant: "text", disabled: disabled || (autoSelectModel && !showAutoSelectToggle), panelWidth: showAutoSelectToggle ? 320 : undefined })), showAspectRatio && aspectOptions.length > 0 && (_jsx(SearchMenu, { options: aspectOptions.map((o) => ({ id: o.value, label: o.label })), value: offeredAspect, triggerLabel: selectedAspect?.label ?? "Aspect ratio", menuLabel: "Aspect ratio", onSelect: (opt) => onAspectRatioChange?.(opt.id), searchable: false, triggerVariant: "text", disabled: disabled }))] }), _jsx(MdFabElement, { className: "chai-composer__submit", size: "small", variant: "secondary", label: "", "data-ready": showStop || !isSubmitDisabled || undefined, "data-error": Boolean(submitError) || undefined, "data-stop": showStop || undefined, ...{
276
+ : undefined, triggerLabel: modelTriggerLabel, menuLabel: multiModel ? "Select models" : "Select model", onSelect: (opt) => toggleModel(opt.id), searchable: false, triggerVariant: "text", disabled: disabled || (autoSelectModel && !showAutoSelectToggle), panelWidth: showAutoSelectToggle ? 320 : undefined })), showAspectRatio && aspectOptions.length > 0 && onAspectRatioChange && (_jsx(SearchMenu, { options: aspectOptions.map((o) => ({ id: o.value, label: o.label })), value: offeredAspect, triggerLabel: selectedAspect?.label ?? "Aspect ratio", menuLabel: "Aspect ratio", onSelect: (opt) => onAspectRatioChange?.(opt.id), searchable: false, triggerVariant: "text", disabled: disabled }))] }), _jsx(MdFabElement, { className: "chai-composer__submit", size: "small", variant: "secondary", label: "", "data-ready": showStop || !isSubmitDisabled || undefined, "data-error": Boolean(submitError) || undefined, "data-stop": showStop || undefined, ...{
266
277
  ariaLabel: showStop ? "Stop" : submitError ? "Retry" : "Submit",
267
278
  ariaDisabled: showStop ? "false" : submitting ? "true" : isSubmitDisabled ? "true" : "false",
268
279
  }, onClick: () => {
@@ -418,15 +429,6 @@ function OptimizeIcon() {
418
429
  function UndoIcon() {
419
430
  return (_jsx("svg", { viewBox: "0 -960 960 960", "aria-hidden": "true", children: _jsx("path", { d: "M259-200v-60h310q70 0 120.5-46.5T740-422q0-69-50.5-115.5T569-584H274l114 114-42 42-186-186 186-186 42 42-114 114h294q95 0 163.5 64T800-422q0 94-68.5 158T568-200H259Z", fill: "currentColor" }) }));
420
431
  }
421
- // Real Material Symbols "stop" glyph, the filled variant specifically
422
- // (`stop-fill`, not the base `stop` — that one traces a hollow square-
423
- // outline shape even filled with `currentColor`, which read as a thin
424
- // frame rather than a real stop icon at 24px; design review caught
425
- // this live). Viewbox `0 -960 960 960` — the submit FAB's in-flight state,
426
- // its own doc comment above where it's rendered.
427
- function StopIcon() {
428
- return (_jsx("svg", { viewBox: "0 -960 960 960", "aria-hidden": "true", children: _jsx("path", { d: "M240-240v-480h480v480H240Z", fill: "currentColor" }) }));
429
- }
430
432
  function AttachFileIcon() {
431
433
  return (_jsx("svg", { viewBox: "0 -960 960 960", "aria-hidden": "true", children: _jsx("path", { d: "M728-326q0 103-72.18 174.5-72.17 71.5-175 71.5Q378-80 305.5-151.5T233-326v-380q0-72.5 51.5-123.25T408-880q72 0 123.5 50.75T583-706v360q0 42-30 72t-72.5 30q-42.5 0-72.5-29.67-30-29.68-30-72.33v-370h60v370q0 17 12.5 29.5t30.64 12.5q18.14 0 30-12.5T523-346v-360q0-48-33.5-81t-81.71-33q-48.21 0-81.5 33.06T293-706v380q0 78 54.97 132T481-140q77.92 0 132.46-54Q668-248 668-326v-390h60v390Z", fill: "currentColor" }) }));
432
434
  }