@chai-ui/react 0.6.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.
@@ -1,114 +1,17 @@
1
- import { useCallback, useRef, useState } from "react";
2
- import { createMockEngine, DEFAULT_MEDIA_ANALYSIS_PROMPT_LENGTH, isAbortError, MEDIA_ANALYSIS_PROMPTS, normalizeGenerationError, routeModel, } from "@chai-ui/core";
3
- import { reasoningModelFor, useReasoning } from "./ChaiProvider.js";
1
+ import { useState, useSyncExternalStore } from "react";
2
+ import { createMediaAnalyzerStore } from "@chai-ui/core";
3
+ import { useReasoning } from "./ChaiProvider.js";
4
4
  import { useLatest } from "./use-latest.js";
5
- const mockTextEngine = createMockEngine({ outputKind: "text" });
6
5
  /**
7
- * The binding behind `MediaAnalyzer`'s `onSubmit`: sends the attached
8
- * media and the instruction for its kind and prompt length
9
- * (`MEDIA_ANALYSIS_PROMPTS`) to the reasoning engine, and returns the
10
- * generated prompt. `prompt` fills in as the text streams.
6
+ * The React binding for `createMediaAnalyzerStore` in `@chai-ui/core`, behind
7
+ * `MediaAnalyzer`'s `onSubmit`: returns the generated prompt, which fills in
8
+ * as the text streams.
11
9
  */
12
10
  export function useMediaAnalyzer(options = {}) {
13
- const [analyzing, setAnalyzing] = useState(false);
14
- const [error, setError] = useState(null);
15
- const [prompt, setPrompt] = useState(null);
16
11
  const provided = useReasoning();
17
12
  const latest = useLatest({ options, provided });
18
- // Each submit gets an id; a late reply from an earlier submit is dropped,
19
- // and its call aborted.
20
- const callId = useRef(0);
21
- const controller = useRef(null);
22
- const submit = useCallback((payload) => {
23
- const { options: opts, provided: reasoning } = latest.current;
24
- const id = ++callId.current;
25
- controller.current?.abort();
26
- const { signal } = (controller.current = new AbortController());
27
- setError(null);
28
- setPrompt(null);
29
- const kind = payload.attachments[0]?.kind;
30
- if (kind !== "image" && kind !== "video" && kind !== "audio") {
31
- const message = "Attach an image, video, or audio file to analyze.";
32
- setError(message);
33
- opts.onError?.(message);
34
- return;
35
- }
36
- const engine = opts.engine ?? reasoning?.engine ?? mockTextEngine;
37
- const defaultModel = opts.model ?? (reasoning ? reasoningModelFor(reasoning, payload.attachments) : undefined);
38
- const instruction = MEDIA_ANALYSIS_PROMPTS[kind][payload.promptLength ?? DEFAULT_MEDIA_ANALYSIS_PROMPT_LENGTH];
39
- const current = () => id === callId.current;
40
- const autoSelect = payload.autoSelectModel && payload.models.length > 0;
41
- const analyze = (modelId) => engine
42
- .generate({
43
- modelId,
44
- prompt: instruction,
45
- attachments: payload.attachments,
46
- onText: (text) => current() && setPrompt(text),
47
- signal,
48
- })
49
- .then(({ src }) => {
50
- if (!current())
51
- return;
52
- setPrompt(src);
53
- latest.current.options.onResult?.(src);
54
- }, (err) => {
55
- if (!current() || isAbortError(err))
56
- return;
57
- const { message } = normalizeGenerationError(err);
58
- setError(message);
59
- latest.current.options.onError?.(message);
60
- })
61
- .finally(() => current() && setAnalyzing(false));
62
- setAnalyzing(true);
63
- if (!autoSelect) {
64
- void analyze(payload.modelId ?? defaultModel);
65
- return;
66
- }
67
- // Auto-select: the reasoning model looks at the media and picks the
68
- // analysis model first. A routing problem falls back, never blocks.
69
- void routeModel({
70
- prompt: "",
71
- outputKind: "text",
72
- attachmentKinds: payload.attachments.map((a) => a.kind),
73
- attachments: payload.attachments,
74
- models: payload.models,
75
- engine: opts.engine ?? reasoning?.engine,
76
- model: defaultModel,
77
- routingPrompt: opts.routingPrompt,
78
- signal,
79
- }).then((routed) => {
80
- if (!current() || signal.aborted)
81
- return;
82
- const latestOpts = latest.current.options;
83
- if (routed.status === "fallback") {
84
- if (routed.reason === "no-reasoning")
85
- warnNoReasoningOnce();
86
- latestOpts.onRoutingFallback?.(routed);
87
- }
88
- latestOpts.onModelChange?.(routed.modelId);
89
- return analyze(routed.modelId);
90
- });
91
- }, [latest]);
92
- /** Stops the analysis in flight (cancelling at the provider where it can), keeping any text that already streamed in. */
93
- const cancel = useCallback(() => {
94
- callId.current += 1;
95
- controller.current?.abort();
96
- controller.current = null;
97
- setAnalyzing(false);
98
- }, []);
99
- /** Stops anything in flight and clears the prompt and error, e.g. when the person changes their media. */
100
- const reset = useCallback(() => {
101
- cancel();
102
- setError(null);
103
- setPrompt(null);
104
- }, [cancel]);
105
- return { submit, analyzing, error, prompt, cancel, reset };
106
- }
107
- let warnedNoReasoning = false;
108
- function warnNoReasoningOnce() {
109
- if (warnedNoReasoning)
110
- return;
111
- warnedNoReasoning = true;
112
- console.warn("[chai-ui] Auto-select model is on, but there's no reasoning model, so analysis runs on the first listed model. Wrap your app in <ChaiProvider> to route with one.");
13
+ const [store] = useState(() => createMediaAnalyzerStore(() => latest.current.options, () => latest.current.provided));
14
+ const state = useSyncExternalStore(store.subscribe, store.getState, store.getState);
15
+ return { ...state, submit: store.submit, cancel: store.cancel, reset: store.reset };
113
16
  }
114
17
  //# sourceMappingURL=useMediaAnalyzer.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useMediaAnalyzer.js","sourceRoot":"","sources":["../src/useMediaAnalyzer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACtD,OAAO,EACL,gBAAgB,EAChB,oCAAoC,EACpC,YAAY,EACZ,sBAAsB,EACtB,wBAAwB,EACxB,UAAU,GAIX,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACpE,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,MAAM,cAAc,GAAG,gBAAgB,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC,CAAC;AAoBhE;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,UAAmC,EAAE;IACpE,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAClD,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAC;IACxD,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAC;IAE1D,MAAM,QAAQ,GAAG,YAAY,EAAE,CAAC;IAChC,MAAM,MAAM,GAAG,SAAS,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC;IAChD,0EAA0E;IAC1E,wBAAwB;IACxB,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IACzB,MAAM,UAAU,GAAG,MAAM,CAAyB,IAAI,CAAC,CAAC;IAExD,MAAM,MAAM,GAAG,WAAW,CAAC,CAAC,OAAmC,EAAE,EAAE;QACjE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC;QAC9D,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,OAAO,CAAC;QAC5B,UAAU,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC;QAC5B,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC,UAAU,CAAC,OAAO,GAAG,IAAI,eAAe,EAAE,CAAC,CAAC;QAChE,QAAQ,CAAC,IAAI,CAAC,CAAC;QACf,SAAS,CAAC,IAAI,CAAC,CAAC;QAEhB,MAAM,IAAI,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC;QAC1C,IAAI,IAAI,KAAK,OAAO,IAAI,IAAI,KAAK,OAAO,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;YAC7D,MAAM,OAAO,GAAG,mDAAmD,CAAC;YACpE,QAAQ,CAAC,OAAO,CAAC,CAAC;YAClB,IAAI,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC;YACxB,OAAO;QACT,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,SAAS,EAAE,MAAM,IAAI,cAAc,CAAC;QAClE,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,iBAAiB,CAAC,SAAS,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QAC/G,MAAM,WAAW,GAAG,sBAAsB,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,YAAY,IAAI,oCAAoC,CAAC,CAAC;QAC/G,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC,EAAE,KAAK,MAAM,CAAC,OAAO,CAAC;QAC5C,MAAM,UAAU,GAAG,OAAO,CAAC,eAAe,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;QAExE,MAAM,OAAO,GAAG,CAAC,OAA2B,EAAE,EAAE,CAC9C,MAAM;aACH,QAAQ,CAAC;YACR,OAAO;YACP,MAAM,EAAE,WAAW;YACnB,WAAW,EAAE,OAAO,CAAC,WAAW;YAChC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,EAAE,IAAI,SAAS,CAAC,IAAI,CAAC;YAC9C,MAAM;SACP,CAAC;aACD,IAAI,CACH,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE;YACV,IAAI,CAAC,OAAO,EAAE;gBAAE,OAAO;YACvB,SAAS,CAAC,GAAG,CAAC,CAAC;YACf,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,CAAC;QACzC,CAAC,EACD,CAAC,GAAG,EAAE,EAAE;YACN,IAAI,CAAC,OAAO,EAAE,IAAI,YAAY,CAAC,GAAG,CAAC;gBAAE,OAAO;YAC5C,MAAM,EAAE,OAAO,EAAE,GAAG,wBAAwB,CAAC,GAAG,CAAC,CAAC;YAClD,QAAQ,CAAC,OAAO,CAAC,CAAC;YAClB,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC;QAC5C,CAAC,CACF;aACA,OAAO,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,IAAI,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC;QAErD,YAAY,CAAC,IAAI,CAAC,CAAC;QACnB,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,KAAK,OAAO,CAAC,OAAO,CAAC,OAAO,IAAI,YAAY,CAAC,CAAC;YAC9C,OAAO;QACT,CAAC;QACD,oEAAoE;QACpE,oEAAoE;QACpE,KAAK,UAAU,CAAC;YACd,MAAM,EAAE,EAAE;YACV,UAAU,EAAE,MAAM;YAClB,eAAe,EAAE,OAAO,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;YACvD,WAAW,EAAE,OAAO,CAAC,WAAW;YAChC,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,SAAS,EAAE,MAAM;YACxC,KAAK,EAAE,YAAY;YACnB,aAAa,EAAE,IAAI,CAAC,aAAa;YACjC,MAAM;SACP,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE;YACjB,IAAI,CAAC,OAAO,EAAE,IAAI,MAAM,CAAC,OAAO;gBAAE,OAAO;YACzC,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC;YAC1C,IAAI,MAAM,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;gBACjC,IAAI,MAAM,CAAC,MAAM,KAAK,cAAc;oBAAE,mBAAmB,EAAE,CAAC;gBAC5D,UAAU,CAAC,iBAAiB,EAAE,CAAC,MAAM,CAAC,CAAC;YACzC,CAAC;YACD,UAAU,CAAC,aAAa,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YAC3C,OAAO,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACjC,CAAC,CAAC,CAAC;IACL,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;IAEb,yHAAyH;IACzH,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,EAAE;QAC9B,MAAM,CAAC,OAAO,IAAI,CAAC,CAAC;QACpB,UAAU,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC;QAC5B,UAAU,CAAC,OAAO,GAAG,IAAI,CAAC;QAC1B,YAAY,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,0GAA0G;IAC1G,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;QAC7B,MAAM,EAAE,CAAC;QACT,QAAQ,CAAC,IAAI,CAAC,CAAC;QACf,SAAS,CAAC,IAAI,CAAC,CAAC;IAClB,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;IAEb,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;AAC7D,CAAC;AAID,IAAI,iBAAiB,GAAG,KAAK,CAAC;AAC9B,SAAS,mBAAmB;IAC1B,IAAI,iBAAiB;QAAE,OAAO;IAC9B,iBAAiB,GAAG,IAAI,CAAC;IACzB,OAAO,CAAC,IAAI,CACV,mKAAmK,CACpK,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"useMediaAnalyzer.js","sourceRoot":"","sources":["../src/useMediaAnalyzer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC;AACvD,OAAO,EAAE,wBAAwB,EAA6B,MAAM,eAAe,CAAC;AACpF,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAI5C;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,UAAmC,EAAE;IACpE,MAAM,QAAQ,GAAG,YAAY,EAAE,CAAC;IAChC,MAAM,MAAM,GAAG,SAAS,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC;IAChD,MAAM,CAAC,KAAK,CAAC,GAAG,QAAQ,CAAC,GAAG,EAAE,CAC5B,wBAAwB,CACtB,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,EAC5B,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAC9B,CACF,CAAC;IACF,MAAM,KAAK,GAAG,oBAAoB,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;IACpF,OAAO,EAAE,GAAG,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC;AACtF,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chai-ui/react",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "CHAI UI React components — smart, drop-in UI for AI apps: Composer, Media Analyzer, Result Card, and the useComposer hook.",
5
5
  "license": "MIT",
6
6
  "author": "kazarazat",
@@ -48,8 +48,8 @@
48
48
  "@fontsource/roboto": "^5.3.0",
49
49
  "@lit/react": "^1.0.8",
50
50
  "@material/web": "^2.5.0",
51
- "@chai-ui/core": "0.6.0",
52
- "@chai-ui/tokens": "0.6.0"
51
+ "@chai-ui/core": "0.6.1",
52
+ "@chai-ui/tokens": "0.6.1"
53
53
  },
54
54
  "devDependencies": {
55
55
  "@material-symbols/svg-400": "^0.47.5",
@@ -1,88 +1,30 @@
1
1
  import { createContext, useContext, useMemo, type ReactNode } from "react";
2
- import { createOpenRouterEngine, type DroppedMedia, type GenerationEngine, type MediaKind } from "@chai-ui/core";
2
+ import { createReasoning, reasoningEngineFor, type Reasoning, type ReasoningConfig } from "@chai-ui/core";
3
3
 
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
- /**
12
- * Suggested per-kind overrides for media Claude can't read. Gemini takes
13
- * video and audio directly. For a cheaper option, swap in
14
- * `"qwen/qwen3.8-omni-flash"`, which takes both too.
15
- */
16
- export const DEFAULT_REASONING_MODEL_BY_KIND: Partial<Record<MediaKind, string>> = {
17
- video: "google/gemini-3.8-flash",
18
- audio: "google/gemini-3.8-flash",
19
- };
20
-
21
- /** The reasoning engine and models every CHAI component shares. */
22
- export interface Reasoning {
23
- engine: GenerationEngine;
24
- /** The model for text and any attachment kind without its own entry in `modelByKind`. */
25
- model: string;
26
- modelByKind: Partial<Record<MediaKind, string>>;
27
- }
4
+ export {
5
+ DEFAULT_REASONING_MODEL,
6
+ DEFAULT_REASONING_MODEL_BY_KIND,
7
+ reasoningModelFor,
8
+ type Reasoning,
9
+ } from "@chai-ui/core";
28
10
 
29
11
  const ReasoningContext = createContext<Reasoning | undefined>(undefined);
30
12
 
31
- export interface ChaiProviderProps {
32
- /** Text, image and routing model. Defaults to `DEFAULT_REASONING_MODEL`. */
33
- reasoningModel?: string;
34
- /**
35
- * Per-attachment-kind models, merged over `DEFAULT_REASONING_MODEL_BY_KIND`
36
- * one kind at a time, e.g. `{ video: "qwen/qwen3.8-omni-flash" }` keeps
37
- * the default for audio. Map a kind to `reasoningModel`'s value to use
38
- * one model for everything.
39
- */
40
- reasoningModelByKind?: Partial<Record<MediaKind, string>>;
41
- /**
42
- * The engine the reasoning models run on. Defaults to OpenRouter,
43
- * independent of the generation engine: Fal's own LLM access is an
44
- * OpenRouter proxy, so defaulting to it would double-proxy.
45
- */
46
- reasoningEngine?: GenerationEngine;
13
+ export interface ChaiProviderProps extends ReasoningConfig {
47
14
  children?: ReactNode;
48
15
  }
49
16
 
50
17
  export function ChaiProvider({ reasoningModel, reasoningModelByKind, reasoningEngine, children }: ChaiProviderProps) {
51
- const engine = useMemo(() => reasoningEngine ?? createOpenRouterEngine(), [reasoningEngine]);
52
- const model = reasoningModel ?? DEFAULT_REASONING_MODEL;
53
- const video = reasoningModelByKind?.video;
54
- const audio = reasoningModelByKind?.audio;
55
- const image = reasoningModelByKind?.image;
56
- const text = reasoningModelByKind?.text;
18
+ const engine = useMemo(() => reasoningEngineFor({ reasoningEngine }), [reasoningEngine]);
19
+ const { video, audio, image, text } = reasoningModelByKind ?? {};
57
20
  const value = useMemo(
58
- () => ({
59
- engine,
60
- model,
61
- modelByKind: { ...DEFAULT_REASONING_MODEL_BY_KIND, ...dropUndefined({ video, audio, image, text }) },
62
- }),
63
- [engine, model, video, audio, image, text]
21
+ () => createReasoning({ reasoningModel, reasoningModelByKind: { video, audio, image, text } }, engine),
22
+ [engine, reasoningModel, video, audio, image, text]
64
23
  );
65
24
  return <ReasoningContext.Provider value={value}>{children}</ReasoningContext.Provider>;
66
25
  }
67
26
 
68
- function dropUndefined<T extends object>(obj: T): Partial<T> {
69
- return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined)) as Partial<T>;
70
- }
71
-
72
27
  /** The nearest `ChaiProvider`'s reasoning engine and models, or `undefined` outside one. */
73
28
  export function useReasoning(): Reasoning | undefined {
74
29
  return useContext(ReasoningContext);
75
30
  }
76
-
77
- /**
78
- * The reasoning model for a call with these attachments: the first
79
- * attachment whose kind has its own model wins (a clip plus a still goes
80
- * to the model that can read the clip), else `reasoning.model`.
81
- */
82
- export function reasoningModelFor(reasoning: Reasoning, attachments: DroppedMedia[] = []): string {
83
- for (const { kind } of attachments) {
84
- const model = reasoning.modelByKind[kind];
85
- if (model) return model;
86
- }
87
- return reasoning.model;
88
- }
package/src/Composer.tsx CHANGED
@@ -3,10 +3,20 @@ import { useEffect, useRef, useState, type ChangeEvent } from "react";
3
3
  import { createComponent } from "@lit/react";
4
4
  import * as React from "react";
5
5
  import { MdFab } from "@material/web/fab/fab.js";
6
- import { deriveGenerationUseCase, suggestedModels } from "@chai-ui/core";
6
+ import { deriveGenerationUseCase, suggestedModels, TEXT_USE_CASE } from "@chai-ui/core";
7
7
  import type { DroppedMedia, EditRegion, ModelOption, ParameterOption, MediaKind } from "@chai-ui/core";
8
8
  import { SearchMenu } from "./primitives/SearchMenu.js";
9
9
  import { regionColor } from "./regions.js";
10
+ import type { ComposerAttachment, ComposerSelection, ComposerSubmitPayload, ComposerUseCase } from "@chai-ui/core";
11
+
12
+ export {
13
+ EDIT_IMAGE_USE_CASE,
14
+ TEXT_USE_CASE,
15
+ type ComposerAttachment,
16
+ type ComposerSelection,
17
+ type ComposerSubmitPayload,
18
+ type ComposerUseCase,
19
+ } from "@chai-ui/core";
10
20
 
11
21
  /** See primitives/Toggle.tsx for why `createComponent` is used instead of raw JSX on custom-element tags. */
12
22
  const MdFabElement = createComponent({
@@ -46,71 +56,8 @@ function nextAttachmentId(): string {
46
56
  : `attachment-${Date.now()}-${Math.random().toString(36).slice(2)}`;
47
57
  }
48
58
 
49
- export interface ComposerAttachment extends DroppedMedia {
50
- id: string;
51
- }
52
-
53
59
  export type ComposerAttachMenuAction = "add-media" | "create-image" | "create-video" | "edit-media";
54
60
 
55
- /**
56
- * The selected generation use case, shown as a removable chip (reference:
57
- * the mock's "Video"/"Image" chip) — deliberately independent of
58
- * `attachments`: a use case can be picked from the attach menu with nothing
59
- * attached yet (e.g. "Create video"). Composer never derives this from
60
- * `attachments` itself (that's app-level policy, not a Composer opinion —
61
- * the design). With none picked, `defaultUseCase` applies.
62
- */
63
- export interface ComposerUseCase {
64
- kind: MediaKind;
65
- label: string;
66
- /**
67
- * An edit of the attached image rather than a new one: no aspect ratio,
68
- * region chips when `regions` is set, and a submit needs only an image and
69
- * either a prompt or a region. Images only for now.
70
- */
71
- edit?: boolean;
72
- }
73
-
74
- /** A plain text request — what a submit means when no use case is picked, unless the builder sets `defaultUseCase`. */
75
- export const TEXT_USE_CASE: ComposerUseCase = { kind: "text", label: "Text" };
76
-
77
- /** Editing the attached image, the attach menu's "Edit image". Pair it with `useComposer`'s `editImage` and an `EditCard`. */
78
- export const EDIT_IMAGE_USE_CASE: ComposerUseCase = { kind: "image", label: "Edit image", edit: true };
79
-
80
- /** One use case to run and the models picked for it. Empty `modelIds` means the engine's default model. */
81
- export interface ComposerSelection {
82
- useCase: ComposerUseCase;
83
- modelIds: string[];
84
- /** The models offered for this use case: what auto-select routes among. */
85
- models: ModelOption[];
86
- }
87
-
88
- /**
89
- * Everything Composer itself tracks, assembled into one object at submit
90
- * time — not the the design `Request` shape (no `mode`/`params`/
91
- * `presetId`; those belong to the run store this package doesn't have yet,
92
- * see the package README), just an honest snapshot of this component's own
93
- * state. Handed to `onSubmit` so a builder doesn't have to re-derive it
94
- * from five separately-controlled props they already hold — mainly a
95
- * correctness/ergonomics win for a coding agent wiring this up from the
96
- * type signature alone.
97
- */
98
- export interface ComposerSubmitPayload {
99
- value: string;
100
- attachments: ComposerAttachment[];
101
- /** The use case to run — the picked one, or `defaultUseCase` when none is picked. The first one with `multiSelectUseCases`. */
102
- useCase: ComposerUseCase;
103
- /** The first picked model, or null for the engine's default. */
104
- modelId: string | null;
105
- aspectRatio: string | null;
106
- /** Everything to run: one entry per use case with its picked models. A single entry unless `multiSelectUseCases` is on. */
107
- selections: ComposerSelection[];
108
- /** When true, the model is chosen at submit (`useComposer` routes among each selection's `models`), not by the picks. */
109
- autoSelectModel: boolean;
110
- /** An edit's marked regions, each with its own instruction. Empty unless the use case is an edit. */
111
- regions?: EditRegion[];
112
- }
113
-
114
61
  export interface ComposerProps {
115
62
  /** The prompt text. Controlled — Composer owns no text state of its own. */
116
63
  value: string;
package/src/EditCard.tsx CHANGED
@@ -1,5 +1,7 @@
1
1
  import { useEffect, useRef, useState, type CSSProperties, type KeyboardEvent, type PointerEvent } from "react";
2
- import { DEFAULT_MAX_REGIONS, type EditRegion, type RegionBox, type ResultStatus } from "@chai-ui/core";
2
+ import { DEFAULT_MAX_REGIONS, type EditRegion, type EditVersion, type RegionBox } from "@chai-ui/core";
3
+
4
+ export type { EditVersion } from "@chai-ui/core";
3
5
  import { Pagination } from "./primitives/Pagination.js";
4
6
  import {
5
7
  AddIcon,
@@ -18,22 +20,6 @@ import { downloadFilename, downloadMedia, shareMedia } from "./media-actions.js"
18
20
  import { PageSlide, usePageSlide } from "./page-slide.js";
19
21
  import { regionColor } from "./regions.js";
20
22
 
21
- /**
22
- * One version of the image being edited: the original, or the result of an
23
- * edit. `useComposer`'s `edit.versions` builds these from its runs.
24
- */
25
- export interface EditVersion {
26
- id: string;
27
- /** The image, once there is one. */
28
- src?: string;
29
- /** The image this version was edited from, shown under the progress spinner until `src` arrives. */
30
- from?: string;
31
- status: ResultStatus;
32
- /** The regions this version was made with (none for the original). Shown faintly, for reference. */
33
- regions: EditRegion[];
34
- error?: string;
35
- }
36
-
37
23
  export interface EditCardProps {
38
24
  /** The original image first, then each edit. Paged with the dots in the card's bottom row. */
39
25
  versions: EditVersion[];
@@ -7,6 +7,9 @@ import type { DroppedMedia, ModelOption, MediaKind } from "@chai-ui/core";
7
7
  import type { MediaAnalysisPromptLength } from "@chai-ui/core";
8
8
  import { SearchMenu, type SearchMenuSection } from "./primitives/SearchMenu.js";
9
9
  import { StopIcon } from "./icons.js";
10
+ import type { MediaAnalyzerAttachment, MediaAnalyzerSubmitPayload } from "@chai-ui/core";
11
+
12
+ export type { MediaAnalyzerAttachment, MediaAnalyzerSubmitPayload } from "@chai-ui/core";
10
13
 
11
14
  /** See primitives/Toggle.tsx for why `createComponent` is used instead of raw JSX on custom-element tags. */
12
15
  const MdFabElement = createComponent({
@@ -29,10 +32,6 @@ function nextAttachmentId(): string {
29
32
  : `attachment-${Date.now()}-${Math.random().toString(36).slice(2)}`;
30
33
  }
31
34
 
32
- export interface MediaAnalyzerAttachment extends DroppedMedia {
33
- id: string;
34
- }
35
-
36
35
  /**
37
36
  * How many attachments of each kind an analysis can hold at once. Image
38
37
  * defaults to 5 (a sequence/series reads as one analysis); video and audio
@@ -99,17 +98,6 @@ export function isMediaAnalyzerAtCap(
99
98
  return current.length >= (maxByKind.image ?? DEFAULT_MAX_ATTACHMENTS_BY_KIND.image);
100
99
  }
101
100
 
102
- export interface MediaAnalyzerSubmitPayload {
103
- attachments: MediaAnalyzerAttachment[];
104
- modelId: string | null;
105
- /** When true, `useMediaAnalyzer` routes among `models` at submit instead of using `modelId`. */
106
- autoSelectModel: boolean;
107
- /** The models offered for the attached media's kind. */
108
- models: ModelOption[];
109
- /** `null` until a person actually picks one — see the `promptLength` prop's own doc comment for why this doesn't default to "concise". */
110
- promptLength: MediaAnalysisPromptLength | null;
111
- }
112
-
113
101
  export interface MediaAnalyzerProps {
114
102
  attachments: MediaAnalyzerAttachment[];
115
103
  onAttachmentsChange: (next: MediaAnalyzerAttachment[]) => void;