@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.
- package/dist/ChaiProvider.d.ts +3 -42
- package/dist/ChaiProvider.d.ts.map +1 -1
- package/dist/ChaiProvider.js +5 -43
- package/dist/ChaiProvider.js.map +1 -1
- package/dist/Composer.d.ts +3 -58
- package/dist/Composer.d.ts.map +1 -1
- package/dist/Composer.js +2 -5
- package/dist/Composer.js.map +1 -1
- package/dist/EditCard.d.ts +2 -16
- package/dist/EditCard.d.ts.map +1 -1
- package/dist/EditCard.js.map +1 -1
- package/dist/MediaAnalyzer.d.ts +2 -13
- package/dist/MediaAnalyzer.d.ts.map +1 -1
- package/dist/MediaAnalyzer.js.map +1 -1
- package/dist/useComposer.d.ts +11 -86
- package/dist/useComposer.d.ts.map +1 -1
- package/dist/useComposer.js +26 -291
- package/dist/useComposer.js.map +1 -1
- package/dist/useMediaAnalyzer.d.ts +8 -28
- package/dist/useMediaAnalyzer.d.ts.map +1 -1
- package/dist/useMediaAnalyzer.js +9 -106
- package/dist/useMediaAnalyzer.js.map +1 -1
- package/package.json +3 -3
- package/src/ChaiProvider.tsx +12 -70
- package/src/Composer.tsx +11 -64
- package/src/EditCard.tsx +3 -17
- package/src/MediaAnalyzer.tsx +3 -15
- package/src/useComposer.ts +33 -411
- package/src/useMediaAnalyzer.ts +15 -146
package/dist/useMediaAnalyzer.js
CHANGED
|
@@ -1,114 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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,
|
|
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.
|
|
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.
|
|
52
|
-
"@chai-ui/tokens": "0.6.
|
|
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",
|
package/src/ChaiProvider.tsx
CHANGED
|
@@ -1,88 +1,30 @@
|
|
|
1
1
|
import { createContext, useContext, useMemo, type ReactNode } from "react";
|
|
2
|
-
import {
|
|
2
|
+
import { createReasoning, reasoningEngineFor, type Reasoning, type ReasoningConfig } from "@chai-ui/core";
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
|
52
|
-
const
|
|
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
|
-
|
|
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
|
|
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[];
|
package/src/MediaAnalyzer.tsx
CHANGED
|
@@ -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;
|