@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.
- package/DESIGN.md +6 -1
- package/README.md +79 -17
- 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 +10 -60
- package/dist/Composer.d.ts.map +1 -1
- package/dist/Composer.js +26 -24
- 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 +7 -2
- package/dist/EditCard.js.map +1 -1
- package/dist/MediaAnalyzer.d.ts +14 -18
- package/dist/MediaAnalyzer.d.ts.map +1 -1
- package/dist/MediaAnalyzer.js +26 -8
- package/dist/MediaAnalyzer.js.map +1 -1
- package/dist/ResultCard.d.ts.map +1 -1
- package/dist/ResultCard.js +10 -6
- package/dist/ResultCard.js.map +1 -1
- package/dist/icons.d.ts +1 -0
- package/dist/icons.d.ts.map +1 -1
- package/dist/icons.js +9 -0
- package/dist/icons.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/media-actions.d.ts +5 -2
- package/dist/media-actions.d.ts.map +1 -1
- package/dist/media-actions.js +12 -3
- package/dist/media-actions.js.map +1 -1
- package/dist/style.css +15 -0
- 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 +42 -90
- package/src/EditCard.tsx +18 -20
- package/src/MediaAnalyzer.tsx +51 -30
- package/src/ResultCard.tsx +12 -4
- package/src/icons.tsx +14 -0
- package/src/index.ts +10 -0
- package/src/media-actions.ts +13 -4
- package/src/style.css +15 -0
- package/src/useComposer.ts +33 -411
- package/src/useMediaAnalyzer.ts +15 -146
package/src/useMediaAnalyzer.ts
CHANGED
|
@@ -1,157 +1,26 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
DEFAULT_MEDIA_ANALYSIS_PROMPT_LENGTH,
|
|
5
|
-
isAbortError,
|
|
6
|
-
MEDIA_ANALYSIS_PROMPTS,
|
|
7
|
-
normalizeGenerationError,
|
|
8
|
-
routeModel,
|
|
9
|
-
type GenerationEngine,
|
|
10
|
-
type ModelRoutingInput,
|
|
11
|
-
type ModelRoutingResult,
|
|
12
|
-
} from "@chai-ui/core";
|
|
13
|
-
import type { MediaAnalyzerSubmitPayload } from "./MediaAnalyzer.js";
|
|
14
|
-
import { reasoningModelFor, useReasoning } from "./ChaiProvider.js";
|
|
1
|
+
import { useState, useSyncExternalStore } from "react";
|
|
2
|
+
import { createMediaAnalyzerStore, type MediaAnalyzerOptions } from "@chai-ui/core";
|
|
3
|
+
import { useReasoning } from "./ChaiProvider.js";
|
|
15
4
|
import { useLatest } from "./use-latest.js";
|
|
16
5
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
export interface UseMediaAnalyzerOptions {
|
|
20
|
-
/**
|
|
21
|
-
* Overrides the nearest `ChaiProvider`'s reasoning engine. With neither,
|
|
22
|
-
* a mock text engine answers, so a first render works with no key.
|
|
23
|
-
*/
|
|
24
|
-
engine?: GenerationEngine;
|
|
25
|
-
/** The model when the person hasn't picked one. Defaults to the provider's reasoning model for the media's kind. */
|
|
26
|
-
model?: string;
|
|
27
|
-
onResult?: (prompt: string) => void;
|
|
28
|
-
onError?: (message: string) => void;
|
|
29
|
-
/** Auto-select: fires with the routed model so the trigger shows it. Wire it to `MediaAnalyzer`'s `onModelChange` state. */
|
|
30
|
-
onModelChange?: (modelId: string) => void;
|
|
31
|
-
/** Replaces the routing instruction (`buildModelRoutingPrompt` in `@chai-ui/core`). */
|
|
32
|
-
routingPrompt?: (input: ModelRoutingInput) => string;
|
|
33
|
-
/** Fires when auto-select fell back to the first listed model (no reasoning model, a failed call, or an unlisted reply). Analysis still runs. */
|
|
34
|
-
onRoutingFallback?: (result: Extract<ModelRoutingResult, { status: "fallback" }>) => void;
|
|
35
|
-
}
|
|
6
|
+
export type UseMediaAnalyzerOptions = MediaAnalyzerOptions;
|
|
36
7
|
|
|
37
8
|
/**
|
|
38
|
-
* The binding
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* generated prompt. `prompt` fills in as the text streams.
|
|
9
|
+
* The React binding for `createMediaAnalyzerStore` in `@chai-ui/core`, behind
|
|
10
|
+
* `MediaAnalyzer`'s `onSubmit`: returns the generated prompt, which fills in
|
|
11
|
+
* as the text streams.
|
|
42
12
|
*/
|
|
43
13
|
export function useMediaAnalyzer(options: UseMediaAnalyzerOptions = {}) {
|
|
44
|
-
const [analyzing, setAnalyzing] = useState(false);
|
|
45
|
-
const [error, setError] = useState<string | null>(null);
|
|
46
|
-
const [prompt, setPrompt] = useState<string | null>(null);
|
|
47
|
-
|
|
48
14
|
const provided = useReasoning();
|
|
49
15
|
const latest = useLatest({ options, provided });
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
controller.current?.abort();
|
|
59
|
-
const { signal } = (controller.current = new AbortController());
|
|
60
|
-
setError(null);
|
|
61
|
-
setPrompt(null);
|
|
62
|
-
|
|
63
|
-
const kind = payload.attachments[0]?.kind;
|
|
64
|
-
if (kind !== "image" && kind !== "video" && kind !== "audio") {
|
|
65
|
-
const message = "Attach an image, video, or audio file to analyze.";
|
|
66
|
-
setError(message);
|
|
67
|
-
opts.onError?.(message);
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const engine = opts.engine ?? reasoning?.engine ?? mockTextEngine;
|
|
72
|
-
const defaultModel = opts.model ?? (reasoning ? reasoningModelFor(reasoning, payload.attachments) : undefined);
|
|
73
|
-
const instruction = MEDIA_ANALYSIS_PROMPTS[kind][payload.promptLength ?? DEFAULT_MEDIA_ANALYSIS_PROMPT_LENGTH];
|
|
74
|
-
const current = () => id === callId.current;
|
|
75
|
-
const autoSelect = payload.autoSelectModel && payload.models.length > 0;
|
|
76
|
-
|
|
77
|
-
const analyze = (modelId: string | undefined) =>
|
|
78
|
-
engine
|
|
79
|
-
.generate({
|
|
80
|
-
modelId,
|
|
81
|
-
prompt: instruction,
|
|
82
|
-
attachments: payload.attachments,
|
|
83
|
-
onText: (text) => current() && setPrompt(text),
|
|
84
|
-
signal,
|
|
85
|
-
})
|
|
86
|
-
.then(
|
|
87
|
-
({ src }) => {
|
|
88
|
-
if (!current()) return;
|
|
89
|
-
setPrompt(src);
|
|
90
|
-
latest.current.options.onResult?.(src);
|
|
91
|
-
},
|
|
92
|
-
(err) => {
|
|
93
|
-
if (!current() || isAbortError(err)) return;
|
|
94
|
-
const { message } = normalizeGenerationError(err);
|
|
95
|
-
setError(message);
|
|
96
|
-
latest.current.options.onError?.(message);
|
|
97
|
-
}
|
|
98
|
-
)
|
|
99
|
-
.finally(() => current() && setAnalyzing(false));
|
|
100
|
-
|
|
101
|
-
setAnalyzing(true);
|
|
102
|
-
if (!autoSelect) {
|
|
103
|
-
void analyze(payload.modelId ?? defaultModel);
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
// Auto-select: the reasoning model looks at the media and picks the
|
|
107
|
-
// analysis model first. A routing problem falls back, never blocks.
|
|
108
|
-
void routeModel({
|
|
109
|
-
prompt: "",
|
|
110
|
-
outputKind: "text",
|
|
111
|
-
attachmentKinds: payload.attachments.map((a) => a.kind),
|
|
112
|
-
attachments: payload.attachments,
|
|
113
|
-
models: payload.models,
|
|
114
|
-
engine: opts.engine ?? reasoning?.engine,
|
|
115
|
-
model: defaultModel,
|
|
116
|
-
routingPrompt: opts.routingPrompt,
|
|
117
|
-
signal,
|
|
118
|
-
}).then((routed) => {
|
|
119
|
-
if (!current() || signal.aborted) return;
|
|
120
|
-
const latestOpts = latest.current.options;
|
|
121
|
-
if (routed.status === "fallback") {
|
|
122
|
-
if (routed.reason === "no-reasoning") warnNoReasoningOnce();
|
|
123
|
-
latestOpts.onRoutingFallback?.(routed);
|
|
124
|
-
}
|
|
125
|
-
latestOpts.onModelChange?.(routed.modelId);
|
|
126
|
-
return analyze(routed.modelId);
|
|
127
|
-
});
|
|
128
|
-
}, [latest]);
|
|
129
|
-
|
|
130
|
-
/** Stops the analysis in flight (cancelling at the provider where it can), keeping any text that already streamed in. */
|
|
131
|
-
const cancel = useCallback(() => {
|
|
132
|
-
callId.current += 1;
|
|
133
|
-
controller.current?.abort();
|
|
134
|
-
controller.current = null;
|
|
135
|
-
setAnalyzing(false);
|
|
136
|
-
}, []);
|
|
137
|
-
|
|
138
|
-
/** Stops anything in flight and clears the prompt and error, e.g. when the person changes their media. */
|
|
139
|
-
const reset = useCallback(() => {
|
|
140
|
-
cancel();
|
|
141
|
-
setError(null);
|
|
142
|
-
setPrompt(null);
|
|
143
|
-
}, [cancel]);
|
|
144
|
-
|
|
145
|
-
return { submit, analyzing, error, prompt, cancel, reset };
|
|
16
|
+
const [store] = useState(() =>
|
|
17
|
+
createMediaAnalyzerStore(
|
|
18
|
+
() => latest.current.options,
|
|
19
|
+
() => latest.current.provided
|
|
20
|
+
)
|
|
21
|
+
);
|
|
22
|
+
const state = useSyncExternalStore(store.subscribe, store.getState, store.getState);
|
|
23
|
+
return { ...state, submit: store.submit, cancel: store.cancel, reset: store.reset };
|
|
146
24
|
}
|
|
147
25
|
|
|
148
26
|
export type UseMediaAnalyzerReturn = ReturnType<typeof useMediaAnalyzer>;
|
|
149
|
-
|
|
150
|
-
let warnedNoReasoning = false;
|
|
151
|
-
function warnNoReasoningOnce() {
|
|
152
|
-
if (warnedNoReasoning) return;
|
|
153
|
-
warnedNoReasoning = true;
|
|
154
|
-
console.warn(
|
|
155
|
-
"[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."
|
|
156
|
-
);
|
|
157
|
-
}
|