@chai-ui/react 0.1.0
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 +207 -0
- package/LICENSE +21 -0
- package/README.md +265 -0
- package/dist/ChaiProvider.d.ts +49 -0
- package/dist/ChaiProvider.d.ts.map +1 -0
- package/dist/ChaiProvider.js +54 -0
- package/dist/ChaiProvider.js.map +1 -0
- package/dist/Composer.d.ts +169 -0
- package/dist/Composer.d.ts.map +1 -0
- package/dist/Composer.js +389 -0
- package/dist/Composer.js.map +1 -0
- package/dist/MediaAnalyzer.d.ts +102 -0
- package/dist/MediaAnalyzer.d.ts.map +1 -0
- package/dist/MediaAnalyzer.js +250 -0
- package/dist/MediaAnalyzer.js.map +1 -0
- package/dist/ResultCard.d.ts +55 -0
- package/dist/ResultCard.d.ts.map +1 -0
- package/dist/ResultCard.js +409 -0
- package/dist/ResultCard.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/primitives/Pagination.d.ts +19 -0
- package/dist/primitives/Pagination.d.ts.map +1 -0
- package/dist/primitives/Pagination.js +13 -0
- package/dist/primitives/Pagination.js.map +1 -0
- package/dist/primitives/SearchMenu.d.ts +69 -0
- package/dist/primitives/SearchMenu.d.ts.map +1 -0
- package/dist/primitives/SearchMenu.js +88 -0
- package/dist/primitives/SearchMenu.js.map +1 -0
- package/dist/primitives/Toggle.d.ts +9 -0
- package/dist/primitives/Toggle.d.ts.map +1 -0
- package/dist/primitives/Toggle.js +28 -0
- package/dist/primitives/Toggle.js.map +1 -0
- package/dist/primitives/index.d.ts +4 -0
- package/dist/primitives/index.d.ts.map +1 -0
- package/dist/primitives/index.js +4 -0
- package/dist/primitives/index.js.map +1 -0
- package/dist/style.css +1860 -0
- package/dist/use-latest.d.ts +7 -0
- package/dist/use-latest.d.ts.map +1 -0
- package/dist/use-latest.js +17 -0
- package/dist/use-latest.js.map +1 -0
- package/dist/useComposer.d.ts +75 -0
- package/dist/useComposer.d.ts.map +1 -0
- package/dist/useComposer.js +191 -0
- package/dist/useComposer.js.map +1 -0
- package/dist/useMediaAnalyzer.d.ts +37 -0
- package/dist/useMediaAnalyzer.d.ts.map +1 -0
- package/dist/useMediaAnalyzer.js +114 -0
- package/dist/useMediaAnalyzer.js.map +1 -0
- package/package.json +88 -0
- package/src/ChaiProvider.tsx +88 -0
- package/src/Composer.tsx +961 -0
- package/src/MediaAnalyzer.tsx +527 -0
- package/src/ResultCard.tsx +980 -0
- package/src/index.ts +36 -0
- package/src/primitives/Pagination.tsx +41 -0
- package/src/primitives/SearchMenu.tsx +284 -0
- package/src/primitives/Toggle.tsx +43 -0
- package/src/primitives/index.ts +3 -0
- package/src/style.css +1860 -0
- package/src/use-latest.ts +18 -0
- package/src/useComposer.ts +289 -0
- package/src/useMediaAnalyzer.ts +157 -0
package/DESIGN.md
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# CHAI UI — DESIGN.md
|
|
2
|
+
|
|
3
|
+
The rules for building with `@chai-ui/react`, written for the people and
|
|
4
|
+
the coding agents who integrate it. The API reference is the JSDoc on each
|
|
5
|
+
component's props and the docs site's `llms.txt`. This file covers how to
|
|
6
|
+
use the pieces correctly: which component to pick, which tokens to use,
|
|
7
|
+
which states you get for free, and what not to build.
|
|
8
|
+
|
|
9
|
+
If this file and the code disagree, the code wins. Please report the
|
|
10
|
+
mismatch.
|
|
11
|
+
|
|
12
|
+
## 1. Pick the component, don't rebuild it
|
|
13
|
+
|
|
14
|
+
| You need | Use | Not |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| A prompt box with attach, model and aspect-ratio pickers, submit | `Composer` | A hand-built textarea + button row |
|
|
17
|
+
| Media (image, video, audio) in, a generation-ready prompt out | `MediaAnalyzer` + `useMediaAnalyzer` | A file input plus your own model call |
|
|
18
|
+
| One prompt's generated output (image, video, audio, text) | `ResultCard` | A custom media card |
|
|
19
|
+
| Submit → engine → result state, wired together | `useComposer` | Your own fetch/loading/error state |
|
|
20
|
+
| One reasoning model shared by every component | `ChaiProvider`, `useReasoning` | A text model picked per component |
|
|
21
|
+
| A switch, an option menu, a pager | `Toggle`, `SearchMenu`, `Pagination` | Another UI kit's versions inside a CHAI surface |
|
|
22
|
+
|
|
23
|
+
These don't exist yet. Don't import them, and don't invent stand-ins that
|
|
24
|
+
use these names: `Results`, `Compare`, `ModelPicker`,
|
|
25
|
+
`ParamControls`, `Presets`, `RunSummary`, `MediaViewer`, `createHttpEngine`.
|
|
26
|
+
|
|
27
|
+
## 2. Integration rules
|
|
28
|
+
|
|
29
|
+
1. **Load the CSS in order:** `@chai-ui/tokens/css` first, then
|
|
30
|
+
`@chai-ui/react/style.css`. Component styles read the token variables.
|
|
31
|
+
2. **Everything is controlled.** Every value comes in as a prop and every
|
|
32
|
+
change comes out as a callback (`value` / `onChange`). Keep the state in
|
|
33
|
+
your app.
|
|
34
|
+
3. **Advanced selection is the builder's opt-in.** `multiSelectModels`
|
|
35
|
+
and `multiSelectUseCases` are off by default. Turn them on only where
|
|
36
|
+
comparing models or making several media types at once is the point.
|
|
37
|
+
4. **A callback's presence is the opt-in.** Omit `onEnhance` and there's
|
|
38
|
+
no optimize button. Omit `onAbort` and there's no stop button. Omit
|
|
39
|
+
`moreActions` and there's no ⋯ menu. Don't pass no-op handlers just to
|
|
40
|
+
make a control appear.
|
|
41
|
+
5. **API keys never reach the browser.** Engines call one same-origin
|
|
42
|
+
route, `/api/chai/*`, served by `createChaiHandler` from
|
|
43
|
+
`@chai-ui/core/server`, which adds the key. Never pass a key to
|
|
44
|
+
`createFalEngine` or `createOpenRouterEngine`. In production, set the
|
|
45
|
+
handler's `authorize` and `allowedModels`.
|
|
46
|
+
6. **Media generation and reasoning are separate choices.** `engine`
|
|
47
|
+
generates media and defaults to Fal.ai. `ChaiProvider`'s
|
|
48
|
+
reasoning models power Enhance, routing and text requests. Defaults:
|
|
49
|
+
Claude Opus 5 for text and images, Gemini 3.8 Flash for video and
|
|
50
|
+
audio, on OpenRouter. They're suggestions; respect a builder's own
|
|
51
|
+
`reasoningModel`, `reasoningModelByKind` and `reasoningEngine`.
|
|
52
|
+
7. **Only show what's real.** Show cost only when the engine reports it.
|
|
53
|
+
Don't show model rankings or leaderboard positions you can't keep up to
|
|
54
|
+
date. Don't show a control the selected model can't honor.
|
|
55
|
+
|
|
56
|
+
## 3. Material components, and where Chai uses its own
|
|
57
|
+
|
|
58
|
+
Chai builds on Material Design 3's web components where they fit, and
|
|
59
|
+
uses its own component where they don't. Both kinds take every color from
|
|
60
|
+
Chai tokens (§4), so they look like one system.
|
|
61
|
+
|
|
62
|
+
| Native Material component | Where Chai uses it |
|
|
63
|
+
|---|---|
|
|
64
|
+
| `<md-fab>` | The submit, retry and stop button |
|
|
65
|
+
| `<md-switch>` (`Toggle`) | Auto-select model, and any switch you add |
|
|
66
|
+
| `<md-checkbox>` | The checkboxes on rows in a multi-select model menu |
|
|
67
|
+
| `<md-outlined-text-field>` | The search field in searchable menus |
|
|
68
|
+
| `<md-focus-ring>` | The focus ring on all of the above |
|
|
69
|
+
|
|
70
|
+
Chai's own components are the menus (`SearchMenu` and the attach menu),
|
|
71
|
+
the use-case chips, `Pagination`, and the cards. The menus are the main
|
|
72
|
+
difference, because Material has an `<md-menu>`:
|
|
73
|
+
|
|
74
|
+
- **Placement.** `<md-menu>` opens as a floating popover anchored to a
|
|
75
|
+
corner of its trigger. Chai's menus open as a panel directly under their
|
|
76
|
+
trigger, inside the component, as the Figma designs show.
|
|
77
|
+
- **Features `<md-menu>` doesn't have.** Chai's model menus need a search
|
|
78
|
+
field, grouped sections with headings (one per use case), a switch
|
|
79
|
+
pinned at the top (auto-select), and multi-select. `<md-menu>` closes
|
|
80
|
+
when an item is picked and has none of these.
|
|
81
|
+
- **Portability.** Chai isn't tied to Material. Its own components restyle
|
|
82
|
+
entirely through tokens, which keeps the door open to other design
|
|
83
|
+
systems later.
|
|
84
|
+
|
|
85
|
+
They still follow Material's conventions where they apply: a trailing
|
|
86
|
+
check mark for single-select, MD3's own checkbox for multi-select,
|
|
87
|
+
and Material's type scale. Each option is a plain button: reach it with
|
|
88
|
+
Tab, pick it with Enter or Space; `aria-pressed` marks what's picked.
|
|
89
|
+
|
|
90
|
+
## 4. Tokens
|
|
91
|
+
|
|
92
|
+
All visual values come from `@chai-ui/tokens`, which builds from one JSON
|
|
93
|
+
source into CSS variables, JS/TS and a Figma token set. **It is the only
|
|
94
|
+
source of color:** no component contains a hard-coded color, and Material's
|
|
95
|
+
colors are driven by Chai tokens, not the other way around.
|
|
96
|
+
|
|
97
|
+
| Layer | Pattern | Use it for |
|
|
98
|
+
|---|---|---|
|
|
99
|
+
| Root palette | `--chai-color-root-{gray, green, amber, red}-*` | Nothing directly. It only feeds the layers below. `green-50` (`#009747`) is the brand green. |
|
|
100
|
+
| Brand and state | `--chai-color-semantic-{accent, accent-hover, accent-subtle, accent-soft, accent-text, text-on-accent, warning, warning-subtle, danger, danger-subtle}` | The brand green and state colors. Use `accent` for fills, icons and focus rings, and `accent-text` for green text (`accent` is only 3.8:1 on white). |
|
|
101
|
+
| Controls | `--chai-color-semantic-control-{surface, surface-hover, surface-raised, track, divider, border, border-soft, border-faint, text-strong, text, text-muted}` | The pure grays the components are built from. |
|
|
102
|
+
| Overlays and media | `--chai-color-semantic-{overlay, on-overlay, scrim, media-backdrop}` | Buttons and backdrops on top of images and video |
|
|
103
|
+
| Space | `--chai-space-0` … `--chai-space-8` (0, 4, 8, 12, 16, 24, 32, 48, 64px) | Padding, gaps, margins |
|
|
104
|
+
| Radius | `--chai-radius-{sm, md, lg, full}` | Corners |
|
|
105
|
+
| Type | `--chai-type-{family, size, weight, leading, tracking}-*` | Text |
|
|
106
|
+
| Motion | `--chai-motion-duration-{fast, base, slow, steep}`, `--chai-motion-easing-{standard, growth}` | Transitions |
|
|
107
|
+
| Effects | `--chai-shadow-menu`, `--chai-gradient-ai` | Floating menus, and the AI shimmer after prompt optimization |
|
|
108
|
+
| Component | `--chai-composer-{bg, border, hover, placeholder, text}`, `--chai-media-analyzer-{bg, border, hover, text}` | Restyling that one component. Each defaults to a control role. |
|
|
109
|
+
| Material | `--md-sys-color-*` | Nothing in your code. They exist so the Material switch, button and text field follow Chai tokens. |
|
|
110
|
+
|
|
111
|
+
Rules:
|
|
112
|
+
|
|
113
|
+
- **Never invent a token name.** If it isn't listed above or in
|
|
114
|
+
`@chai-ui/tokens/css`, it doesn't exist, and `var()` on an unknown name
|
|
115
|
+
silently falls back to nothing.
|
|
116
|
+
- **Never put a literal color on a component.** Use a semantic role. If a
|
|
117
|
+
design needs a color that has no role yet, add the role to
|
|
118
|
+
`packages/tokens/src/tokens.json` instead of writing the hex.
|
|
119
|
+
- **Restyle by overriding variables, not selectors.** Override the
|
|
120
|
+
semantic roles on `:root` to restyle everything, including the Material
|
|
121
|
+
components, or set a component variable like `--chai-composer-bg` on
|
|
122
|
+
`.chai-composer` to restyle one component. Don't target internal
|
|
123
|
+
`.chai-*__*` class names. Those are implementation details and can
|
|
124
|
+
change without notice.
|
|
125
|
+
|
|
126
|
+
## 5. AI states you get for free — use them, don't replace them
|
|
127
|
+
|
|
128
|
+
| Situation | Built-in behavior | Your part |
|
|
129
|
+
|---|---|---|
|
|
130
|
+
| Generation failed | `Composer`'s submit button becomes a retry action and an inline message appears (`submitError`) | Set `submitError`, and clear it on success or when the prompt changes. **No error dialogs or toasts for a failed generation.** |
|
|
131
|
+
| Request in flight | Submit becomes a stop button (`submitting` + `onAbort`) | Pass `useComposer`'s `cancel` as `onAbort`; results show "Stopped" |
|
|
132
|
+
| Prompt too thin | Optimize button rewrites the prompt, then offers undo (`onEnhance`) | Run the rewrite and call `onChange` with the new text |
|
|
133
|
+
| No use case picked | The prompt is a text request and gets a text reply | Set `defaultUseCase` if your app's default should be image or video |
|
|
134
|
+
| Result pending | `ResultCard` shows a spinner for `queued` / `running` | Nothing. `useComposer` sets the status. |
|
|
135
|
+
| Long text answer | `ResultCard` keeps line breaks, scrolls past 400px with a visible scrollbar, and follows streaming text unless the person scrolls up | Change the height with `textMaxHeight` if needed |
|
|
136
|
+
| Text streaming in | `ResultCard` renders partial text with a caret, marked `aria-busy` | Use an engine that calls `onText` (OpenRouter does, and so does the mock text engine). `useComposer` wires it. |
|
|
137
|
+
| Result failed | `ResultCard` shows the error message in place | Nothing |
|
|
138
|
+
| Try again | `ResultCard`'s retry fires `onAction("retry", result)` | Re-run the **same** model call, not a different one |
|
|
139
|
+
| Feedback | Like/dislike fire `onAction`, and the displayed vote comes from `getVote` | Store votes yourself |
|
|
140
|
+
| Details | The flip side shows prompt, model, tokens, cost and duration | Supply `models` so ids resolve to labels |
|
|
141
|
+
| Quality checks | Each result's `evaluations` render on the flip side: green with a check if it passed, red with a cross if it failed | Run your checks in your backend proxy and add a top-level `evaluations: [{ id, label, passed }]` field to the JSON it returns (for a stream, send it as its own `data:` chunk). The built-in Fal and OpenRouter engines read that field, a custom engine returns it from `generate`, and `useComposer` puts it on the matching `Result`. CHAI never runs or scores anything itself, and there are no confidence scores. |
|
|
142
|
+
|
|
143
|
+
`ResultCard` is where a run ends. Don't add a "use as input" action that
|
|
144
|
+
feeds a result back into a `Composer`. Chaining steps like that is
|
|
145
|
+
deliberately out of scope.
|
|
146
|
+
|
|
147
|
+
## 6. Accessibility
|
|
148
|
+
|
|
149
|
+
- Every icon-only button in CHAI has an accessible name. In `moreActions`,
|
|
150
|
+
`label` is the item's visible and announced text, so pass an `icon` that
|
|
151
|
+
is `aria-hidden`.
|
|
152
|
+
- Animations (the optimize reveal, the streaming caret) are disabled under
|
|
153
|
+
`prefers-reduced-motion`. Your own motion around CHAI should do the same.
|
|
154
|
+
- **Focus rings are on by default, as WCAG 2.2 AA requires** (2.4.7 Focus
|
|
155
|
+
Visible). Every control shows a ring when it
|
|
156
|
+
has keyboard focus: buttons and menu items use the browser's own ring,
|
|
157
|
+
MD3 elements use `<md-focus-ring>`, and the Composer rings its whole pill
|
|
158
|
+
while the prompt field is focused. Hover fills don't replace these,
|
|
159
|
+
because hover never appears for someone navigating with a keyboard.
|
|
160
|
+
- **To hide them**, set `data-chai-focus-ring="hidden"` on any ancestor.
|
|
161
|
+
Every CHAI component inside it stops drawing focus rings, and your own
|
|
162
|
+
controls under the same ancestor are unaffected. Remove the attribute to
|
|
163
|
+
turn them back on:
|
|
164
|
+
|
|
165
|
+
```tsx
|
|
166
|
+
<div data-chai-focus-ring={showFocusRings ? undefined : "hidden"}>…</div>
|
|
167
|
+
// or page-wide:
|
|
168
|
+
document.documentElement.dataset.chaiFocusRing = "hidden";
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**Hiding focus rings breaks WCAG 2.2 AA (and 2.1 AA) compliance.** Keyboard users
|
|
172
|
+
lose any indication of where they are. Only hide them on showcase or
|
|
173
|
+
preview surfaces, like the docs site's component previews, not in a
|
|
174
|
+
production app. Don't set `tabIndex={-1}` on CHAI controls either.
|
|
175
|
+
- Streaming text is `aria-busy` until it settles, so screen readers read
|
|
176
|
+
the finished text once instead of every chunk.
|
|
177
|
+
- **Supports WCAG 2.2 AA.** Chai's components are built to support WCAG 2.2 AA and are tested
|
|
178
|
+
against it with automated checks: axe (WCAG 2.0, 2.1 and 2.2, levels A
|
|
179
|
+
and AA) on every component state in the test suite, plus keyboard and
|
|
180
|
+
focus tests in a browser. They haven't been tested by hand with a
|
|
181
|
+
screen reader, so this is support, not a certification. Compliance is
|
|
182
|
+
judged for a whole app, so test yours, including with a screen reader.
|
|
183
|
+
|
|
184
|
+
What that covers: contrast, focus rings, a name for every control
|
|
185
|
+
(`Composer`'s `promptLabel`, `Toggle`'s `ariaLabel`), alt text for
|
|
186
|
+
generated images (`ResultCard`'s `altText`, default the prompt), 24px
|
|
187
|
+
targets, and keyboard use: video play/pause (Tab to the video, Space)
|
|
188
|
+
and seeking (a slider: arrow keys, Home, End), the turned-away side of a
|
|
189
|
+
flipped card is `inert` and focus follows the flip, and the expanded
|
|
190
|
+
view is a dialog (focus inside, Escape closes, focus returns to Expand).
|
|
191
|
+
|
|
192
|
+
## 7. What's enforced
|
|
193
|
+
|
|
194
|
+
Inside this repo, `pnpm lint` runs `scripts/lint-tokens.mjs` over the
|
|
195
|
+
packages' CSS:
|
|
196
|
+
|
|
197
|
+
- It fails on `var(--…)` names that don't exist (invented tokens).
|
|
198
|
+
- It fails on any hard-coded color, custom properties included.
|
|
199
|
+
- It fails on direct use of Material's `--md-sys-color-*` roles, which
|
|
200
|
+
must go through the Chai token they map to.
|
|
201
|
+
|
|
202
|
+
TypeScript enforces the component APIs: controlled props, typed submit
|
|
203
|
+
payloads, and typed engine and result shapes. `ResultCard`'s `onAction`
|
|
204
|
+
is a plain `string` so it can carry your own `moreActions` ids, which
|
|
205
|
+
means a misspelled action name isn't caught. Nothing checks your
|
|
206
|
+
app's own CSS or markup yet, so sections 1–6 above are guidance there, not
|
|
207
|
+
guarantees.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 kazarazat
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
# @chai-ui/react
|
|
2
|
+
|
|
3
|
+
React components for the input and output surfaces of generative-media
|
|
4
|
+
apps. Everything here is controlled — you own the state, these components
|
|
5
|
+
render it and tell you when it changes.
|
|
6
|
+
|
|
7
|
+
Building with it, or pointing a coding agent at it? Read
|
|
8
|
+
[`DESIGN.md`](./DESIGN.md) (shipped in the package) for the usage rules:
|
|
9
|
+
which component to pick, which tokens exist, the built-in AI states, and
|
|
10
|
+
what not to build.
|
|
11
|
+
|
|
12
|
+
Chai's components support WCAG 2.2 AA: tested with axe and automated
|
|
13
|
+
keyboard and focus checks, not by hand with a screen reader. DESIGN.md §6
|
|
14
|
+
has the details. Focus rings are visible by default, as WCAG requires.
|
|
15
|
+
`data-chai-focus-ring="hidden"` on an ancestor hides them, for preview or
|
|
16
|
+
showcase surfaces only. `Composer`'s `promptLabel` names the prompt field
|
|
17
|
+
(default: the placeholder), and `ResultCard`'s `altText` sets image alt
|
|
18
|
+
text (default: the prompt).
|
|
19
|
+
|
|
20
|
+
## Quick start: `Composer`
|
|
21
|
+
|
|
22
|
+
`Composer` is the `bar`-layout input surface: a prompt field, an attach
|
|
23
|
+
menu, a use-case chip, model/aspect-ratio pickers, and submit — all driven
|
|
24
|
+
by which props you supply, not by four separate components.
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
import { useState } from "react";
|
|
28
|
+
import "@chai-ui/tokens/css";
|
|
29
|
+
import "@chai-ui/react/style.css";
|
|
30
|
+
import { Composer, type ComposerAttachment, type ComposerSubmitPayload } from "@chai-ui/react";
|
|
31
|
+
|
|
32
|
+
function App() {
|
|
33
|
+
const [value, setValue] = useState("");
|
|
34
|
+
const [attachments, setAttachments] = useState<ComposerAttachment[]>([]);
|
|
35
|
+
|
|
36
|
+
function handleSubmit(payload: ComposerSubmitPayload) {
|
|
37
|
+
// payload = { value, attachments, useCase, modelId, aspectRatio } —
|
|
38
|
+
// everything Composer tracks, assembled for you. Send it to whichever
|
|
39
|
+
// model/provider you've set up; Composer never calls one itself.
|
|
40
|
+
console.log(payload);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<Composer
|
|
45
|
+
value={value}
|
|
46
|
+
onChange={setValue}
|
|
47
|
+
attachments={attachments}
|
|
48
|
+
onAttachmentsChange={setAttachments}
|
|
49
|
+
onSubmit={handleSubmit}
|
|
50
|
+
/>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
That's the minimal case — no model picker, no aspect ratio, no attach
|
|
56
|
+
capability beyond the default file picker. Every other control is additive:
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
<Composer
|
|
60
|
+
value={value}
|
|
61
|
+
onChange={setValue}
|
|
62
|
+
attachments={attachments}
|
|
63
|
+
onAttachmentsChange={setAttachments}
|
|
64
|
+
// A use-case chip + model/aspect-ratio pickers only appear once `useCase`
|
|
65
|
+
// is set — wire it from the attach menu's own picks, or set it yourself.
|
|
66
|
+
useCase={useCase}
|
|
67
|
+
onClearUseCase={() => setUseCase(null)}
|
|
68
|
+
onAttachMenuSelect={(action) => {
|
|
69
|
+
if (action === "create-image") setUseCase({ kind: "image", label: "Image" });
|
|
70
|
+
}}
|
|
71
|
+
models={models}
|
|
72
|
+
modelId={modelId}
|
|
73
|
+
onModelChange={setModelId}
|
|
74
|
+
// An "Auto-select model" switch appears at the top of the model menu so
|
|
75
|
+
// the end user can flip it — omit `showAutoSelectToggle` to keep it a
|
|
76
|
+
// fixed, builder-only setting with no visible control instead.
|
|
77
|
+
autoSelectModel={autoSelectModel}
|
|
78
|
+
showAutoSelectToggle
|
|
79
|
+
onAutoSelectModelChange={setAutoSelectModel}
|
|
80
|
+
aspectRatios={aspectRatios}
|
|
81
|
+
aspectRatio={aspectRatio}
|
|
82
|
+
onAspectRatioChange={setAspectRatio}
|
|
83
|
+
// Omit onEnhance entirely to hide the "optimize prompt" button — e.g.
|
|
84
|
+
// before you've wired an engine that can actually run the rewrite.
|
|
85
|
+
onEnhance={(currentValue) => runEnhance(currentValue).then(setValue)}
|
|
86
|
+
onSubmit={handleSubmit}
|
|
87
|
+
/>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The full prop list — including `attachments`' file-reading behavior,
|
|
91
|
+
`onUnsupportedFile`, and `disabled`/`submitDisabled` — is documented as
|
|
92
|
+
JSDoc directly on `ComposerProps` in `src/Composer.tsx`; every prop and
|
|
93
|
+
event follows the same `value`/`onChange` shape used everywhere else in
|
|
94
|
+
this package.
|
|
95
|
+
|
|
96
|
+
See the [Composer page on the docs site](https://chai-ui.com/docs/components/composer-bar)
|
|
97
|
+
for a live example with every opt-in feature.
|
|
98
|
+
|
|
99
|
+
### No use case picked: a text request
|
|
100
|
+
|
|
101
|
+
A prompt typed with no use case picked is a text request
|
|
102
|
+
(`TEXT_USE_CASE`): it gets a text reply rather than doing nothing. For an
|
|
103
|
+
app that's mostly about images, set the default instead; it's never shown
|
|
104
|
+
as a chip:
|
|
105
|
+
|
|
106
|
+
```tsx
|
|
107
|
+
<Composer defaultUseCase={{ kind: "image", label: "Image" }} … />
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Several models, several use cases (builder opt-ins, off by default)
|
|
111
|
+
|
|
112
|
+
```tsx
|
|
113
|
+
// Several models: each produces its own result, paged inside one ResultCard.
|
|
114
|
+
<Composer multiSelectModels modelIds={modelIds} onModelIdsChange={setModelIds} models={models} … />
|
|
115
|
+
|
|
116
|
+
// Several use cases at once (at most one per kind): the Model menu shows a
|
|
117
|
+
// section per picked use case, and onSubmit gets one selection per use case.
|
|
118
|
+
<Composer
|
|
119
|
+
multiSelectUseCases
|
|
120
|
+
useCases={useCases}
|
|
121
|
+
onUseCasesChange={setUseCases}
|
|
122
|
+
modelsByKind={{ image: imageModels, video: videoModels }}
|
|
123
|
+
modelIds={modelIds}
|
|
124
|
+
onModelIdsChange={setModelIds}
|
|
125
|
+
…
|
|
126
|
+
/>
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Either way `onSubmit` receives `selections`: one `{ useCase, modelIds, models }`
|
|
130
|
+
entry per use case.
|
|
131
|
+
|
|
132
|
+
With `autoSelectModel` on, the trigger reads "Auto-select" and
|
|
133
|
+
`useComposer` picks the model at submit: the reasoning model reads the
|
|
134
|
+
prompt and media and chooses from that use case's `models`. Pass
|
|
135
|
+
`onModelChange` (or `onModelIdsChange`) to the hook too, so the trigger
|
|
136
|
+
shows the pick:
|
|
137
|
+
|
|
138
|
+
```tsx
|
|
139
|
+
const { submit, routing } = useComposer({ engine, onModelChange: setModelId });
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
If routing can't run (no `ChaiProvider`, a failed reasoning call, an
|
|
143
|
+
unlisted reply), the request runs on the first listed model and
|
|
144
|
+
`onRoutingFallback` says why.
|
|
145
|
+
|
|
146
|
+
## What else this package ships
|
|
147
|
+
|
|
148
|
+
- **`MediaAnalyzer`** — drop in an image, video, or audio clip and get a
|
|
149
|
+
generation-ready prompt back, with kind-aware model menus and a prompt
|
|
150
|
+
length picker. Wire it with `useMediaAnalyzer()`, which sends the media
|
|
151
|
+
to `ChaiProvider`'s reasoning model and returns the prompt:
|
|
152
|
+
`onSubmit={submit} submitting={analyzing} submitError={error}`.
|
|
153
|
+
- **`ResultCard`** — renders one prompt's generated results (image, video,
|
|
154
|
+
audio, or text) with like/dislike/retry/download/share, a configurable
|
|
155
|
+
"more" menu, pagination across multiple results, and an info flip showing
|
|
156
|
+
the prompt and model metadata. Live example:
|
|
157
|
+
[chai-ui.com/docs/components/result-card](https://chai-ui.com/docs/components/result-card).
|
|
158
|
+
- **Primitives** (`src/primitives/`) — `Toggle` (an MD3 switch),
|
|
159
|
+
`SearchMenu` (the model/option menu behind every picker — Chai's own rather than Material's `<md-menu>`; DESIGN.md §3 says why), and
|
|
160
|
+
`Pagination` (a progress-style pager any paginated surface can reuse).
|
|
161
|
+
The components are built from these; they're also exported directly if
|
|
162
|
+
you're assembling your own layout.
|
|
163
|
+
|
|
164
|
+
## `useComposer` — wiring `Composer` to a real engine
|
|
165
|
+
|
|
166
|
+
```tsx
|
|
167
|
+
import { ChaiProvider, useComposer, createFalEngine, Composer, ResultCard } from "@chai-ui/react";
|
|
168
|
+
|
|
169
|
+
// The reasoning models write text (Enhance, routing, text requests) and
|
|
170
|
+
// are separate from the media engine. With no props,
|
|
171
|
+
// Claude Opus 5 handles text and images, Gemini 3.8 Flash handles video
|
|
172
|
+
// and audio, both on `createOpenRouterEngine()`. Without a provider,
|
|
173
|
+
// Composer's Enhance button stays hidden.
|
|
174
|
+
function Root() {
|
|
175
|
+
return (
|
|
176
|
+
<ChaiProvider>
|
|
177
|
+
<App />
|
|
178
|
+
</ChaiProvider>
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function App() {
|
|
183
|
+
const { run, submit, cancel, enhance, enhancing } = useComposer({
|
|
184
|
+
engine: createFalEngine({ outputKind: "image" }),
|
|
185
|
+
});
|
|
186
|
+
const busy = run?.results.some((r) => r.status === "queued" || r.status === "running") ?? false;
|
|
187
|
+
|
|
188
|
+
return (
|
|
189
|
+
<>
|
|
190
|
+
<Composer
|
|
191
|
+
value={value}
|
|
192
|
+
onChange={setValue}
|
|
193
|
+
onSubmit={submit}
|
|
194
|
+
submitting={busy}
|
|
195
|
+
onAbort={cancel}
|
|
196
|
+
onEnhance={enhance ? (v) => enhance(v).then(setValue) : undefined}
|
|
197
|
+
enhancing={enhancing}
|
|
198
|
+
// ...the rest of Composer's own props, as above
|
|
199
|
+
/>
|
|
200
|
+
{run && (
|
|
201
|
+
<ResultCard
|
|
202
|
+
results={run.results}
|
|
203
|
+
prompt={run.request.prompt}
|
|
204
|
+
onAction={(action, result) => {
|
|
205
|
+
// like / dislike / retry / download / share / your "more" actions
|
|
206
|
+
}}
|
|
207
|
+
/>
|
|
208
|
+
)}
|
|
209
|
+
</>
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
`submit` takes `Composer`'s `ComposerSubmitPayload` directly — pass it
|
|
215
|
+
straight to `onSubmit`. It turns each selection into a real the design
|
|
216
|
+
`Request`/`Run`/`Result`: creates a `"queued"` `Result` per model up
|
|
217
|
+
front, then folds each one to `"running"` → `"done"`/`"error"` as the
|
|
218
|
+
engine settles it. `run` is the first `Run` (or `null` before the first
|
|
219
|
+
submit); `runs` holds one per use case when `multiSelectUseCases` is on.
|
|
220
|
+
`ResultCard` renders `run.results` directly, paginating when there's more
|
|
221
|
+
than one.
|
|
222
|
+
|
|
223
|
+
**The server route.** `createFalEngine()` and the OpenRouter reasoning
|
|
224
|
+
engine call `/api/chai/*` on your server, which adds the API keys. Add it
|
|
225
|
+
with one file, then set `FAL_KEY` and `OPENROUTER_API_KEY`:
|
|
226
|
+
|
|
227
|
+
```ts
|
|
228
|
+
// Next.js: app/api/chai/[...path]/route.ts
|
|
229
|
+
import { createChaiHandler } from "@chai-ui/core/server";
|
|
230
|
+
export const { GET, POST, PUT } = createChaiHandler();
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Other frameworks, and the `authorize` / `allowedModels` settings to add
|
|
234
|
+
before going live, are in the `@chai-ui/core` README.
|
|
235
|
+
|
|
236
|
+
Every default is a suggestion. Swap any of them:
|
|
237
|
+
|
|
238
|
+
```tsx
|
|
239
|
+
<ChaiProvider
|
|
240
|
+
reasoningModel="your/text-and-image-model"
|
|
241
|
+
// Cheaper video and audio analysis; kinds you leave out keep their default.
|
|
242
|
+
reasoningModelByKind={{ video: "qwen/qwen3.8-omni-flash", audio: "qwen/qwen3.8-omni-flash" }}
|
|
243
|
+
reasoningEngine={yourEngine}
|
|
244
|
+
/>
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
The defaults are exported as `DEFAULT_REASONING_MODEL` and
|
|
248
|
+
`DEFAULT_REASONING_MODEL_BY_KIND`.
|
|
249
|
+
|
|
250
|
+
`useComposer`'s own `reasoningEngine` + `reasoningModel` override the
|
|
251
|
+
provider's, but only when both are set. Text requests go to `textEngine`
|
|
252
|
+
(default: the reasoning engine, then a mock text engine), using
|
|
253
|
+
`textModel` (default: the reasoning model) when no model is picked. Other kinds go to `engineByKind[kind]`, then `engine`. With no
|
|
254
|
+
model picked, the engine's own default model runs.
|
|
255
|
+
|
|
256
|
+
**Stop.** `cancel()` stops the current submit: wire it to Composer's
|
|
257
|
+
`onAbort`, with `submitting` true while results are queued or running.
|
|
258
|
+
Unfinished results turn `"cancelled"` (ResultCard shows "Stopped"; a
|
|
259
|
+
streamed text result keeps what arrived), and a new submit cancels the
|
|
260
|
+
previous one. Cancellation is best effort at the provider: Fal removes a
|
|
261
|
+
queued request and signals a running one, which may still finish;
|
|
262
|
+
OpenRouter stops a *streaming* request, and its billing, only for
|
|
263
|
+
providers that support it (not Google); a non-streaming request keeps
|
|
264
|
+
running. A custom engine receives `signal` in `generate()`: cancel at the
|
|
265
|
+
provider if it can, then reject with an `AbortError` (`abortError()`).
|
|
@@ -0,0 +1,49 @@
|
|
|
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;
|
|
38
|
+
children?: ReactNode;
|
|
39
|
+
}
|
|
40
|
+
export declare function ChaiProvider({ reasoningModel, reasoningModelByKind, reasoningEngine, children }: ChaiProviderProps): import("react").JSX.Element;
|
|
41
|
+
/** The nearest `ChaiProvider`'s reasoning engine and models, or `undefined` outside one. */
|
|
42
|
+
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
|
+
//# sourceMappingURL=ChaiProvider.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
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
|
+
};
|
|
19
|
+
const ReasoningContext = createContext(undefined);
|
|
20
|
+
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]);
|
|
32
|
+
return _jsx(ReasoningContext.Provider, { value: value, children: children });
|
|
33
|
+
}
|
|
34
|
+
function dropUndefined(obj) {
|
|
35
|
+
return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined));
|
|
36
|
+
}
|
|
37
|
+
/** The nearest `ChaiProvider`'s reasoning engine and models, or `undefined` outside one. */
|
|
38
|
+
export function useReasoning() {
|
|
39
|
+
return useContext(ReasoningContext);
|
|
40
|
+
}
|
|
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
|
+
//# sourceMappingURL=ChaiProvider.js.map
|
|
@@ -0,0 +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"}
|