@elabs-ai/components-ai 4.0.0 → 4.2.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/README.md +62 -12
- package/dist/{_audio-player-media-chrome-KA5DY54G.js → _audio-player-media-chrome-T3XVXWRZ.js} +8 -4
- package/dist/_audio-player-media-chrome-T3XVXWRZ.js.map +1 -0
- package/dist/{_flow-boundary-D63PJ65S.js → _flow-boundary-SHNWLQG5.js} +32 -43
- package/dist/_flow-boundary-SHNWLQG5.js.map +1 -0
- package/dist/{_persona-rive-RFR2EUWP.js → _persona-rive-JEG44YHX.js} +9 -5
- package/dist/_persona-rive-JEG44YHX.js.map +1 -0
- package/dist/index.d.ts +1152 -218
- package/dist/index.js +5601 -3241
- package/dist/index.js.map +1 -1
- package/package.json +30 -15
- package/src/__contract__/audio-visualizer.contract.test.tsx +49 -0
- package/src/__contract__/chat-shell.contract.test.tsx +49 -0
- package/src/__contract__/grouped-parts.contract.test.tsx +49 -0
- package/src/__contract__/image.contract.test.tsx +49 -0
- package/src/__contract__/markdown-view.contract.test.tsx +49 -0
- package/src/__contract__/message-feedback.contract.test.tsx +49 -0
- package/src/__contract__/message-form.contract.test.tsx +49 -0
- package/src/__contract__/message-table.contract.test.tsx +49 -0
- package/src/__contract__/model-provider-logo.contract.test.tsx +49 -0
- package/src/__contract__/persona.contract.test.tsx +49 -0
- package/src/__contract__/prompt-input-effort.contract.test.tsx +49 -0
- package/src/__contract__/prompt-input-mode.contract.test.tsx +49 -0
- package/src/_audio-player-media-chrome.tsx +102 -15
- package/src/_chat-shell-rail.tsx +2 -2
- package/src/_flow-boundary.tsx +68 -49
- package/src/_lazy-boundary-conformance.ts +38 -0
- package/src/_lazy-cjk.test.ts +43 -0
- package/src/_lazy-cjk.ts +73 -0
- package/src/_lazy-engine-boundary.tsx +61 -0
- package/src/_lazy-math.test.ts +63 -0
- package/src/_lazy-math.ts +90 -0
- package/src/_lazy-mermaid-absent.test.ts +53 -0
- package/src/_lazy-mermaid.test.ts +15 -0
- package/src/_lazy-mermaid.ts +24 -1
- package/src/_mermaid-error-panel.test.tsx +50 -0
- package/src/_mermaid-error-panel.tsx +66 -0
- package/src/_persona-rive.tsx +62 -10
- package/src/_streamdown-i18n.ts +94 -22
- package/src/_streamdown-safety.ts +170 -0
- package/src/_theme-scope-store.test.ts +83 -0
- package/src/_theme-scope-store.ts +103 -0
- package/src/agent-event.stories.tsx +97 -0
- package/src/agent-event.test.tsx +145 -0
- package/src/agent-event.tsx +187 -0
- package/src/agent-timeline.stories.tsx +9 -1
- package/src/agent.test.tsx +47 -0
- package/src/agent.tsx +43 -24
- package/src/agentic-workspace.stories.tsx +27 -20
- package/src/artifact.tsx +17 -14
- package/src/asset-preview.test.tsx +40 -0
- package/src/asset-preview.tsx +83 -12
- package/src/attachments.tsx +7 -4
- package/src/audio-player.test.tsx +78 -0
- package/src/audio-player.tsx +243 -56
- package/src/audio-visualizer.stories.tsx +126 -0
- package/src/audio-visualizer.test.tsx +438 -0
- package/src/audio-visualizer.tsx +367 -0
- package/src/canvas.stories.tsx +150 -1
- package/src/chat-shell.stories.tsx +18 -3
- package/src/chat.stories.tsx +16 -2
- package/src/code-block.stories.tsx +9 -1
- package/src/code-block.test.tsx +100 -1
- package/src/code-block.tsx +166 -103
- package/src/commit.tsx +30 -41
- package/src/composer.stories.tsx +372 -9
- package/src/composer.test.tsx +357 -6
- package/src/composer.tsx +217 -35
- package/src/confirmation.stories.tsx +72 -1
- package/src/confirmation.test.tsx +216 -2
- package/src/confirmation.tsx +264 -4
- package/src/context-panel.stories.tsx +9 -1
- package/src/context-panel.tsx +3 -2
- package/src/conversation.stories.tsx +86 -2
- package/src/conversation.test.tsx +66 -0
- package/src/conversation.tsx +52 -9
- package/src/diff-view.stories.tsx +196 -0
- package/src/diff-view.test.tsx +238 -0
- package/src/diff-view.tsx +697 -0
- package/src/environment-variables.tsx +20 -37
- package/src/file-tree.test.tsx +21 -0
- package/src/file-tree.tsx +12 -2
- package/src/gallery.tsx +4 -2
- package/src/index.ts +14 -4
- package/src/inline-citation.tsx +5 -5
- package/src/jsx-preview.stories.tsx +2 -2
- package/src/jsx-preview.tsx +151 -43
- package/src/markdown-view.stories.tsx +92 -1
- package/src/markdown-view.test.tsx +237 -2
- package/src/markdown-view.tsx +157 -6
- package/src/message-compare.stories.tsx +175 -0
- package/src/message-compare.test.tsx +207 -0
- package/src/message-compare.tsx +453 -0
- package/src/message-form.stories.tsx +65 -4
- package/src/message-form.test.tsx +8 -2
- package/src/message-form.tsx +15 -7
- package/src/message-table.stories.tsx +2 -2
- package/src/message-table.test.tsx +7 -0
- package/src/message-table.tsx +8 -4
- package/src/message.stories.tsx +9 -1
- package/src/message.test.tsx +247 -1
- package/src/message.tsx +125 -15
- package/src/microcopy.test.tsx +40 -0
- package/src/{model-selector.stories.tsx → model-provider-logo.stories.tsx} +17 -8
- package/src/model-provider-logo.test.tsx +109 -0
- package/src/model-provider-logo.tsx +183 -0
- package/src/open-in-chat.tsx +50 -29
- package/src/package-info.tsx +12 -12
- package/src/permission-mode-select.stories.tsx +82 -0
- package/src/permission-mode-select.test.tsx +100 -0
- package/src/permission-mode-select.tsx +137 -0
- package/src/persona-missing-peer.test.tsx +54 -0
- package/src/persona.tsx +68 -22
- package/src/plan.stories.tsx +166 -0
- package/src/plan.test.tsx +267 -0
- package/src/plan.tsx +182 -20
- package/src/prompt-input-effort.stories.tsx +123 -0
- package/src/prompt-input-effort.test.tsx +83 -0
- package/src/prompt-input-effort.tsx +136 -0
- package/src/prompt-input-mode.stories.tsx +108 -0
- package/src/prompt-input-mode.test.tsx +99 -0
- package/src/prompt-input-mode.tsx +169 -0
- package/src/prompt-input-slash.stories.tsx +211 -0
- package/src/prompt-input-slash.test.tsx +262 -0
- package/src/prompt-input-slash.tsx +541 -0
- package/src/prompt-input.stories.tsx +2 -2
- package/src/prompt-input.test.tsx +67 -1
- package/src/prompt-input.tsx +42 -4
- package/src/queue.tsx +4 -4
- package/src/reasoning.tsx +42 -17
- package/src/sandbox.stories.tsx +9 -1
- package/src/sandbox.tsx +3 -3
- package/src/schema-display.test.tsx +56 -0
- package/src/schema-display.tsx +71 -37
- package/src/selection-toolbar.stories.tsx +9 -1
- package/src/session-header.stories.tsx +128 -0
- package/src/session-header.test.tsx +138 -0
- package/src/session-header.tsx +243 -0
- package/src/session-status-bar.stories.tsx +73 -0
- package/src/session-status-bar.test.tsx +94 -0
- package/src/session-status-bar.tsx +165 -0
- package/src/snippet.stories.tsx +9 -1
- package/src/snippet.test.tsx +6 -2
- package/src/snippet.tsx +14 -34
- package/src/speech-input.test.tsx +109 -0
- package/src/speech-input.tsx +31 -3
- package/src/stack-trace.tsx +24 -36
- package/src/streamdown-i18n.test.tsx +24 -1
- package/src/task.stories.tsx +13 -3
- package/src/templates-ai-assistant.stories.tsx +21 -1
- package/src/test-results.tsx +47 -27
- package/src/token-usage.stories.tsx +24 -0
- package/src/token-usage.test.tsx +92 -0
- package/src/{context.tsx → token-usage.tsx} +73 -59
- package/src/tool-result-card.stories.tsx +9 -1
- package/src/tool.stories.tsx +13 -3
- package/src/tool.test.tsx +65 -0
- package/src/tool.tsx +73 -30
- package/src/transcription.tsx +1 -1
- package/src/turn-status.stories.tsx +124 -0
- package/src/turn-status.test.tsx +74 -0
- package/src/turn-status.tsx +174 -0
- package/src/use-audio-level.ts +104 -0
- package/src/voice-selector.tsx +5 -5
- package/src/web-preview.test.tsx +51 -1
- package/src/web-preview.tsx +46 -10
- package/dist/_audio-player-media-chrome-KA5DY54G.js.map +0 -1
- package/dist/_flow-boundary-D63PJ65S.js.map +0 -1
- package/dist/_interactive-terminal-xterm-ATJ5EW3G.js +0 -11
- package/dist/_interactive-terminal-xterm-ATJ5EW3G.js.map +0 -1
- package/dist/_persona-rive-RFR2EUWP.js.map +0 -1
- package/src/_interactive-terminal-xterm.ts +0 -32
- package/src/blocks-ai-composer.stories.tsx +0 -83
- package/src/context.stories.tsx +0 -16
- package/src/interactive-terminal.stories.tsx +0 -165
- package/src/interactive-terminal.test.tsx +0 -448
- package/src/interactive-terminal.tsx +0 -444
- package/src/model-selector.test.tsx +0 -55
- package/src/model-selector.tsx +0 -225
- package/src/terminal.tsx +0 -244
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The OPT-IN analyser-plumbing hook for `AudioVisualizer` (issue #21). It is a
|
|
7
|
+
* separate export a consumer may ignore entirely — `AudioVisualizer` itself
|
|
8
|
+
* never imports or calls this. brand-ui is a presentation layer (D5): this
|
|
9
|
+
* hook never calls `getUserMedia` and never creates an `AudioContext` until a
|
|
10
|
+
* `MediaStream` the CONSUMER already obtained is actually passed in.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export interface UseAudioLevelOptions {
|
|
14
|
+
/** `AnalyserNode.fftSize` — controls how many frequency bins `levels` has
|
|
15
|
+
* (`fftSize / 2`). Must be a power of two in [32, 32768]. */
|
|
16
|
+
fftSize?: number;
|
|
17
|
+
/** `AnalyserNode.smoothingTimeConstant` (0–1) — the analyser's OWN frame-to-frame
|
|
18
|
+
* smoothing, independent of `AudioVisualizer`'s reduced-motion smoothing. */
|
|
19
|
+
smoothingTimeConstant?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface UseAudioLevelResult {
|
|
23
|
+
/** Normalized 0–1 frequency-bin samples for the current frame — feed this
|
|
24
|
+
* directly to `<AudioVisualizer levels={levels} />`. Empty while `stream`
|
|
25
|
+
* is `null`/`undefined` or the Web Audio API is unavailable. */
|
|
26
|
+
levels: number[];
|
|
27
|
+
/** The mean of `levels` — a single 0–1 scalar for a simple level meter. */
|
|
28
|
+
level: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const DEFAULT_FFT_SIZE = 64;
|
|
32
|
+
const DEFAULT_SMOOTHING_TIME_CONSTANT = 0.6;
|
|
33
|
+
|
|
34
|
+
type AudioContextCtor = new () => AudioContext;
|
|
35
|
+
|
|
36
|
+
/** Feature-detects the Web Audio API — absent under SSR, in a test runner
|
|
37
|
+
* with no Web Audio shim, and in older Safari (`webkitAudioContext`). */
|
|
38
|
+
function resolveAudioContextConstructor(): AudioContextCtor | null {
|
|
39
|
+
if (typeof window === "undefined") return null;
|
|
40
|
+
const w = window as typeof window & { webkitAudioContext?: AudioContextCtor };
|
|
41
|
+
return w.AudioContext ?? w.webkitAudioContext ?? null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Polls a `MediaStream` the caller already obtained (their own `getUserMedia`
|
|
46
|
+
* call — this hook never requests one) via a Web Audio `AnalyserNode`, on a
|
|
47
|
+
* `requestAnimationFrame` loop, and returns normalized levels.
|
|
48
|
+
*
|
|
49
|
+
* Pass `null`/`undefined` for `stream` (e.g. before the user grants mic
|
|
50
|
+
* permission) and the hook is a no-op returning empty levels — safe to call
|
|
51
|
+
* unconditionally, including with no stream ever provided at all.
|
|
52
|
+
*/
|
|
53
|
+
export function useAudioLevel(
|
|
54
|
+
stream: MediaStream | null | undefined,
|
|
55
|
+
options: UseAudioLevelOptions = {},
|
|
56
|
+
): UseAudioLevelResult {
|
|
57
|
+
const { fftSize = DEFAULT_FFT_SIZE, smoothingTimeConstant = DEFAULT_SMOOTHING_TIME_CONSTANT } =
|
|
58
|
+
options;
|
|
59
|
+
const [levels, setLevels] = useState<number[]>([]);
|
|
60
|
+
const [level, setLevel] = useState(0);
|
|
61
|
+
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
if (!stream) {
|
|
64
|
+
setLevels([]);
|
|
65
|
+
setLevel(0);
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const AudioContextConstructor = resolveAudioContextConstructor();
|
|
70
|
+
if (!AudioContextConstructor) {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const audioContext = new AudioContextConstructor();
|
|
75
|
+
const source = audioContext.createMediaStreamSource(stream);
|
|
76
|
+
const analyser = audioContext.createAnalyser();
|
|
77
|
+
analyser.fftSize = fftSize;
|
|
78
|
+
analyser.smoothingTimeConstant = smoothingTimeConstant;
|
|
79
|
+
source.connect(analyser);
|
|
80
|
+
|
|
81
|
+
const buffer = new Uint8Array(analyser.frequencyBinCount);
|
|
82
|
+
let rafId = 0;
|
|
83
|
+
|
|
84
|
+
const tick = () => {
|
|
85
|
+
analyser.getByteFrequencyData(buffer);
|
|
86
|
+
const normalized = Array.from(buffer, (value) => value / 255);
|
|
87
|
+
setLevels(normalized);
|
|
88
|
+
setLevel(normalized.reduce((sum, value) => sum + value, 0) / (normalized.length || 1));
|
|
89
|
+
rafId = requestAnimationFrame(tick);
|
|
90
|
+
};
|
|
91
|
+
rafId = requestAnimationFrame(tick);
|
|
92
|
+
|
|
93
|
+
return () => {
|
|
94
|
+
cancelAnimationFrame(rafId);
|
|
95
|
+
source.disconnect();
|
|
96
|
+
analyser.disconnect();
|
|
97
|
+
if (audioContext.state !== "closed") {
|
|
98
|
+
void audioContext.close();
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
}, [stream, fftSize, smoothingTimeConstant]);
|
|
102
|
+
|
|
103
|
+
return { level, levels };
|
|
104
|
+
}
|
package/src/voice-selector.tsx
CHANGED
|
@@ -197,7 +197,7 @@ export const VoiceSelectorGender = ({
|
|
|
197
197
|
}
|
|
198
198
|
|
|
199
199
|
return (
|
|
200
|
-
<span className={cn("text-muted-foreground text-
|
|
200
|
+
<span className={cn("text-muted-foreground text-meta", className)} {...props}>
|
|
201
201
|
{children ?? icon}
|
|
202
202
|
</span>
|
|
203
203
|
);
|
|
@@ -373,7 +373,7 @@ export const VoiceSelectorAccent = ({
|
|
|
373
373
|
}
|
|
374
374
|
|
|
375
375
|
return (
|
|
376
|
-
<span className={cn("text-muted-foreground text-
|
|
376
|
+
<span className={cn("text-muted-foreground text-meta", className)} {...props}>
|
|
377
377
|
{children ?? emoji}
|
|
378
378
|
</span>
|
|
379
379
|
);
|
|
@@ -382,7 +382,7 @@ export const VoiceSelectorAccent = ({
|
|
|
382
382
|
export type VoiceSelectorAgeProps = ComponentProps<"span">;
|
|
383
383
|
|
|
384
384
|
export const VoiceSelectorAge = ({ className, ...props }: VoiceSelectorAgeProps) => (
|
|
385
|
-
<span className={cn("text-muted-foreground text-
|
|
385
|
+
<span className={cn("text-muted-foreground text-meta tabular-nums", className)} {...props} />
|
|
386
386
|
);
|
|
387
387
|
|
|
388
388
|
export type VoiceSelectorNameProps = ComponentProps<"span">;
|
|
@@ -397,7 +397,7 @@ export const VoiceSelectorDescription = ({
|
|
|
397
397
|
className,
|
|
398
398
|
...props
|
|
399
399
|
}: VoiceSelectorDescriptionProps) => (
|
|
400
|
-
<span className={cn("text-muted-foreground text-
|
|
400
|
+
<span className={cn("text-muted-foreground text-meta", className)} {...props} />
|
|
401
401
|
);
|
|
402
402
|
|
|
403
403
|
export type VoiceSelectorAttributesProps = ComponentProps<"div">;
|
|
@@ -407,7 +407,7 @@ export const VoiceSelectorAttributes = ({
|
|
|
407
407
|
children,
|
|
408
408
|
...props
|
|
409
409
|
}: VoiceSelectorAttributesProps) => (
|
|
410
|
-
<div className={cn("flex items-center text-
|
|
410
|
+
<div className={cn("flex items-center text-meta", className)} {...props}>
|
|
411
411
|
{children}
|
|
412
412
|
</div>
|
|
413
413
|
);
|
package/src/web-preview.test.tsx
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { render, screen } from "@testing-library/react";
|
|
2
|
+
import { render, screen, fireEvent } from "@testing-library/react";
|
|
3
|
+
import { LocaleProvider } from "@elabs-ai/components-ui";
|
|
3
4
|
import {
|
|
4
5
|
WebPreview,
|
|
5
6
|
WebPreviewBody,
|
|
7
|
+
WebPreviewConsole,
|
|
6
8
|
WebPreviewNavigation,
|
|
7
9
|
WebPreviewNavigationButton,
|
|
8
10
|
WebPreviewUrl,
|
|
@@ -115,3 +117,51 @@ describe("WebPreviewNavigationButton accessible name (#386)", () => {
|
|
|
115
117
|
expect(screen.getByRole("button", { name: "Back" })).toBeInTheDocument();
|
|
116
118
|
});
|
|
117
119
|
});
|
|
120
|
+
|
|
121
|
+
// 1.3 — `allow-same-origin` combined with `allow-scripts` lets a same-origin
|
|
122
|
+
// previewed page break out of the sandbox entirely. It must be opt-in.
|
|
123
|
+
describe("WebPreviewBody sandbox (#1.3)", () => {
|
|
124
|
+
it("never includes allow-same-origin by default", () => {
|
|
125
|
+
const { container } = render(
|
|
126
|
+
<WebPreview>
|
|
127
|
+
<WebPreviewBody />
|
|
128
|
+
</WebPreview>,
|
|
129
|
+
);
|
|
130
|
+
const iframe = container.querySelector("iframe");
|
|
131
|
+
expect(iframe).not.toBeNull();
|
|
132
|
+
expect(iframe?.getAttribute("sandbox")).not.toContain("allow-same-origin");
|
|
133
|
+
// The other, non-origin-escaping permissions still apply.
|
|
134
|
+
expect(iframe?.getAttribute("sandbox")).toContain("allow-scripts");
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("adds allow-same-origin only when explicitly opted in", () => {
|
|
138
|
+
const { container } = render(
|
|
139
|
+
<WebPreview>
|
|
140
|
+
<WebPreviewBody allowSameOrigin />
|
|
141
|
+
</WebPreview>,
|
|
142
|
+
);
|
|
143
|
+
const iframe = container.querySelector("iframe");
|
|
144
|
+
expect(iframe?.getAttribute("sandbox")).toContain("allow-same-origin");
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
describe("WebPreviewConsole timestamp formatting (#1.3)", () => {
|
|
149
|
+
it("formats the log timestamp through the active locale, not the JS-engine default", () => {
|
|
150
|
+
const timestamp = new Date("2024-03-04T15:30:00Z");
|
|
151
|
+
render(
|
|
152
|
+
<LocaleProvider locale="de-DE">
|
|
153
|
+
<WebPreview>
|
|
154
|
+
<WebPreviewConsole logs={[{ level: "log", message: "started", timestamp }]} />
|
|
155
|
+
</WebPreview>
|
|
156
|
+
</LocaleProvider>,
|
|
157
|
+
);
|
|
158
|
+
// The console body is collapsed by default — open it before asserting.
|
|
159
|
+
fireEvent.click(screen.getByRole("button", { name: "Console" }));
|
|
160
|
+
const expected = new Intl.DateTimeFormat("de-DE", {
|
|
161
|
+
hour: "2-digit",
|
|
162
|
+
minute: "2-digit",
|
|
163
|
+
second: "2-digit",
|
|
164
|
+
}).format(timestamp);
|
|
165
|
+
expect(screen.getByText(expected)).toBeInTheDocument();
|
|
166
|
+
});
|
|
167
|
+
});
|
package/src/web-preview.tsx
CHANGED
|
@@ -158,7 +158,7 @@ export const WebPreviewUrl = ({ value, onChange, onKeyDown, ...props }: WebPrevi
|
|
|
158
158
|
|
|
159
159
|
return (
|
|
160
160
|
<Input
|
|
161
|
-
className="h-8 flex-1 text-
|
|
161
|
+
className="h-8 flex-1 text-body"
|
|
162
162
|
onChange={onChange ?? handleChange}
|
|
163
163
|
onKeyDown={handleKeyDown}
|
|
164
164
|
placeholder={t("ai.webPreview.urlPlaceholder")}
|
|
@@ -180,20 +180,45 @@ export type WebPreviewBodyProps = Omit<ComponentProps<"iframe">, "loading"> & {
|
|
|
180
180
|
* @default false
|
|
181
181
|
*/
|
|
182
182
|
loading?: boolean | ReactNode;
|
|
183
|
+
/**
|
|
184
|
+
* Adds `allow-same-origin` to the iframe sandbox. **Opt-in, off by
|
|
185
|
+
* default**: combined with `allow-scripts`, a same-origin previewed page
|
|
186
|
+
* can script its way out of the sandbox restrictions entirely (its own
|
|
187
|
+
* `<iframe>` reads as unsandboxed once it shares the origin). Only enable
|
|
188
|
+
* this for content you trust to be same-origin-safe.
|
|
189
|
+
* @default false
|
|
190
|
+
*/
|
|
191
|
+
allowSameOrigin?: boolean;
|
|
183
192
|
};
|
|
184
193
|
|
|
185
|
-
export const WebPreviewBody = ({
|
|
194
|
+
export const WebPreviewBody = ({
|
|
195
|
+
className,
|
|
196
|
+
loading,
|
|
197
|
+
src,
|
|
198
|
+
allowSameOrigin = false,
|
|
199
|
+
...props
|
|
200
|
+
}: WebPreviewBodyProps) => {
|
|
186
201
|
const { url } = useWebPreview();
|
|
187
202
|
const { t } = useLocale();
|
|
188
203
|
|
|
204
|
+
const sandbox = [
|
|
205
|
+
"allow-scripts",
|
|
206
|
+
"allow-forms",
|
|
207
|
+
"allow-popups",
|
|
208
|
+
"allow-presentation",
|
|
209
|
+
allowSameOrigin && "allow-same-origin",
|
|
210
|
+
]
|
|
211
|
+
.filter(Boolean)
|
|
212
|
+
.join(" ");
|
|
213
|
+
|
|
189
214
|
return (
|
|
190
215
|
<div className="relative flex-1">
|
|
191
216
|
<iframe
|
|
192
217
|
className={cn("size-full", className)}
|
|
193
218
|
// oxlint-disable-next-line eslint-plugin-react(iframe-missing-sandbox)
|
|
194
|
-
sandbox=
|
|
219
|
+
sandbox={sandbox}
|
|
195
220
|
src={(src ?? url) || undefined}
|
|
196
|
-
title="
|
|
221
|
+
title={t("ai.webPreview.previewTitle")}
|
|
197
222
|
{...props}
|
|
198
223
|
/>
|
|
199
224
|
{loading === true ? (
|
|
@@ -228,10 +253,11 @@ export const WebPreviewConsole = ({
|
|
|
228
253
|
...props
|
|
229
254
|
}: WebPreviewConsoleProps) => {
|
|
230
255
|
const { consoleOpen, setConsoleOpen } = useWebPreview();
|
|
256
|
+
const { t, formatDate } = useLocale();
|
|
231
257
|
|
|
232
258
|
return (
|
|
233
259
|
<Collapsible
|
|
234
|
-
className={cn("border-t bg-muted/50 font-mono text-
|
|
260
|
+
className={cn("border-t bg-muted/50 font-mono text-body", className)}
|
|
235
261
|
onOpenChange={setConsoleOpen}
|
|
236
262
|
open={consoleOpen}
|
|
237
263
|
{...props}
|
|
@@ -258,19 +284,29 @@ export const WebPreviewConsole = ({
|
|
|
258
284
|
>
|
|
259
285
|
<div className="max-h-48 space-y-1 overflow-y-auto">
|
|
260
286
|
{logs.length === 0 ? (
|
|
261
|
-
<p className="text-muted-foreground">
|
|
287
|
+
<p className="text-muted-foreground">{t("ai.webPreview.noConsoleOutput")}</p>
|
|
262
288
|
) : (
|
|
263
289
|
logs.map((log) => (
|
|
264
290
|
<div
|
|
265
291
|
className={cn(
|
|
266
|
-
"text-
|
|
267
|
-
log
|
|
268
|
-
|
|
292
|
+
"text-meta",
|
|
293
|
+
// #124: console log lines are running text — ink rung, not
|
|
294
|
+
// the mark rung. `text-yellow-600` was also a raw, untokened
|
|
295
|
+
// Tailwind palette colour (styling-and-tokens.md); replaced
|
|
296
|
+
// with the warning tone's own ink rung.
|
|
297
|
+
log.level === "error" && "text-destructive-text",
|
|
298
|
+
log.level === "warn" && "text-warning-text",
|
|
269
299
|
log.level === "log" && "text-foreground",
|
|
270
300
|
)}
|
|
271
301
|
key={`${log.timestamp.getTime()}-${log.level}-${log.message}`}
|
|
272
302
|
>
|
|
273
|
-
<span className="text-muted-foreground">
|
|
303
|
+
<span className="text-muted-foreground">
|
|
304
|
+
{formatDate(log.timestamp, {
|
|
305
|
+
hour: "2-digit",
|
|
306
|
+
minute: "2-digit",
|
|
307
|
+
second: "2-digit",
|
|
308
|
+
})}
|
|
309
|
+
</span>{" "}
|
|
274
310
|
{log.message}
|
|
275
311
|
</div>
|
|
276
312
|
))
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/_audio-player-media-chrome.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * The media-chrome half of `AudioPlayer`, split out so it can be `lazy()`-loaded.\n *\n * `media-chrome` declares no `sideEffects`, so a static import from\n * `audio-player.tsx` put the whole custom-element library (and the\n * `media-chrome/react` wrappers that register it) into the entry chunk of every\n * `@elabs-ai/components-ai` consumer — including the vast majority that\n * never render an `AudioPlayer`. Keeping every media-chrome *value* import in\n * this module, reached only through\n * `lazy(() => import(\"./_audio-player-media-chrome\"))`, confines it to its own\n * chunk. `audio-player.tsx` still owns the public prop types and imports the\n * media-chrome element types with `import type` (types erase).\n *\n * See ADR 0019 and `pnpm heavy-deps:check`.\n *\n * @lazy-boundary This module must only ever be reached via `import()`. The gate\n * fails if anything imports it statically, which would put media-chrome back in\n * the entry chunk and make the `lazy()` pointless.\n */\nimport { Button, ButtonGroup, ButtonGroupText } from \"@elabs-ai/components-ui\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport {\n MediaControlBar,\n MediaController,\n MediaDurationDisplay,\n MediaMuteButton,\n MediaPlayButton,\n MediaSeekBackwardButton,\n MediaSeekForwardButton,\n MediaTimeDisplay,\n MediaTimeRange,\n MediaVolumeRange,\n} from \"media-chrome/react\";\nimport type { CSSProperties } from \"react\";\n\nimport type {\n AudioPlayerControlBarProps,\n AudioPlayerDurationDisplayProps,\n AudioPlayerMuteButtonProps,\n AudioPlayerPlayButtonProps,\n AudioPlayerProps,\n AudioPlayerSeekBackwardButtonProps,\n AudioPlayerSeekForwardButtonProps,\n AudioPlayerTimeDisplayProps,\n AudioPlayerTimeRangeProps,\n AudioPlayerVolumeRangeProps,\n} from \"./audio-player\";\n\nexport const AudioPlayer = ({ children, style, ...props }: AudioPlayerProps) => (\n <MediaController\n audio\n style={\n {\n \"--media-background-color\": \"transparent\",\n \"--media-button-icon-height\": \"1rem\",\n \"--media-button-icon-width\": \"1rem\",\n \"--media-control-background\": \"transparent\",\n \"--media-control-hover-background\": \"var(--color-accent)\",\n \"--media-control-padding\": \"0\",\n \"--media-font\": \"var(--font-sans)\",\n \"--media-font-size\": \"10px\",\n \"--media-icon-color\": \"currentColor\",\n \"--media-preview-time-background\": \"var(--color-background)\",\n \"--media-preview-time-border-radius\": \"var(--radius-md)\",\n \"--media-preview-time-text-shadow\": \"none\",\n \"--media-primary-color\": \"var(--color-primary)\",\n \"--media-range-bar-color\": \"var(--color-primary)\",\n \"--media-range-track-background\": \"var(--color-secondary)\",\n \"--media-secondary-color\": \"var(--color-secondary)\",\n \"--media-text-color\": \"var(--color-foreground)\",\n \"--media-tooltip-arrow-display\": \"none\",\n \"--media-tooltip-background\": \"var(--color-background)\",\n \"--media-tooltip-border-radius\": \"var(--radius-md)\",\n ...style,\n } as CSSProperties\n }\n {...props}\n >\n {children}\n </MediaController>\n);\n\nexport const AudioPlayerControlBar = ({ children, ...props }: AudioPlayerControlBarProps) => (\n <MediaControlBar {...props}>\n <ButtonGroup orientation=\"horizontal\">{children}</ButtonGroup>\n </MediaControlBar>\n);\n\nexport const AudioPlayerPlayButton = ({ className, ...props }: AudioPlayerPlayButtonProps) => (\n <Button asChild size=\"icon-sm\" variant=\"outline\">\n <MediaPlayButton className={cn(\"bg-transparent\", className)} {...props} />\n </Button>\n);\n\nexport const AudioPlayerSeekBackwardButton = ({\n seekOffset = 10,\n ...props\n}: AudioPlayerSeekBackwardButtonProps) => (\n <Button asChild size=\"icon-sm\" variant=\"outline\">\n <MediaSeekBackwardButton seekOffset={seekOffset} {...props} />\n </Button>\n);\n\nexport const AudioPlayerSeekForwardButton = ({\n seekOffset = 10,\n ...props\n}: AudioPlayerSeekForwardButtonProps) => (\n <Button asChild size=\"icon-sm\" variant=\"outline\">\n <MediaSeekForwardButton seekOffset={seekOffset} {...props} />\n </Button>\n);\n\nexport const AudioPlayerTimeDisplay = ({ className, ...props }: AudioPlayerTimeDisplayProps) => (\n <ButtonGroupText asChild className=\"bg-transparent\">\n <MediaTimeDisplay className={cn(\"tabular-nums\", className)} {...props} />\n </ButtonGroupText>\n);\n\nexport const AudioPlayerTimeRange = ({ className, ...props }: AudioPlayerTimeRangeProps) => (\n <ButtonGroupText asChild className=\"bg-transparent\">\n <MediaTimeRange className={cn(\"\", className)} {...props} />\n </ButtonGroupText>\n);\n\nexport const AudioPlayerDurationDisplay = ({\n className,\n ...props\n}: AudioPlayerDurationDisplayProps) => (\n <ButtonGroupText asChild className=\"bg-transparent\">\n <MediaDurationDisplay className={cn(\"tabular-nums\", className)} {...props} />\n </ButtonGroupText>\n);\n\nexport const AudioPlayerMuteButton = ({ className, ...props }: AudioPlayerMuteButtonProps) => (\n <ButtonGroupText asChild className=\"bg-transparent\">\n <MediaMuteButton className={cn(\"\", className)} {...props} />\n </ButtonGroupText>\n);\n\nexport const AudioPlayerVolumeRange = ({ className, ...props }: AudioPlayerVolumeRangeProps) => (\n <ButtonGroupText asChild className=\"bg-transparent\">\n <MediaVolumeRange className={cn(\"\", className)} {...props} />\n </ButtonGroupText>\n);\n"],"mappings":";;;;AAqBA,SAAS,QAAQ,aAAa,uBAAuB;AACrD,SAAS,UAAU;AACnB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAiBL;AADK,IAAM,cAAc,CAAC,EAAE,UAAU,OAAO,GAAG,MAAM,MACtD;AAAA,EAAC;AAAA;AAAA,IACC,OAAK;AAAA,IACL,OACE;AAAA,MACE,4BAA4B;AAAA,MAC5B,8BAA8B;AAAA,MAC9B,6BAA6B;AAAA,MAC7B,8BAA8B;AAAA,MAC9B,oCAAoC;AAAA,MACpC,2BAA2B;AAAA,MAC3B,gBAAgB;AAAA,MAChB,qBAAqB;AAAA,MACrB,sBAAsB;AAAA,MACtB,mCAAmC;AAAA,MACnC,sCAAsC;AAAA,MACtC,oCAAoC;AAAA,MACpC,yBAAyB;AAAA,MACzB,2BAA2B;AAAA,MAC3B,kCAAkC;AAAA,MAClC,2BAA2B;AAAA,MAC3B,sBAAsB;AAAA,MACtB,iCAAiC;AAAA,MACjC,8BAA8B;AAAA,MAC9B,iCAAiC;AAAA,MACjC,GAAG;AAAA,IACL;AAAA,IAED,GAAG;AAAA,IAEH;AAAA;AACH;AAGK,IAAM,wBAAwB,CAAC,EAAE,UAAU,GAAG,MAAM,MACzD,oBAAC,mBAAiB,GAAG,OACnB,8BAAC,eAAY,aAAY,cAAc,UAAS,GAClD;AAGK,IAAM,wBAAwB,CAAC,EAAE,WAAW,GAAG,MAAM,MAC1D,oBAAC,UAAO,SAAO,MAAC,MAAK,WAAU,SAAQ,WACrC,8BAAC,mBAAgB,WAAW,GAAG,kBAAkB,SAAS,GAAI,GAAG,OAAO,GAC1E;AAGK,IAAM,gCAAgC,CAAC;AAAA,EAC5C,aAAa;AAAA,EACb,GAAG;AACL,MACE,oBAAC,UAAO,SAAO,MAAC,MAAK,WAAU,SAAQ,WACrC,8BAAC,2BAAwB,YAAyB,GAAG,OAAO,GAC9D;AAGK,IAAM,+BAA+B,CAAC;AAAA,EAC3C,aAAa;AAAA,EACb,GAAG;AACL,MACE,oBAAC,UAAO,SAAO,MAAC,MAAK,WAAU,SAAQ,WACrC,8BAAC,0BAAuB,YAAyB,GAAG,OAAO,GAC7D;AAGK,IAAM,yBAAyB,CAAC,EAAE,WAAW,GAAG,MAAM,MAC3D,oBAAC,mBAAgB,SAAO,MAAC,WAAU,kBACjC,8BAAC,oBAAiB,WAAW,GAAG,gBAAgB,SAAS,GAAI,GAAG,OAAO,GACzE;AAGK,IAAM,uBAAuB,CAAC,EAAE,WAAW,GAAG,MAAM,MACzD,oBAAC,mBAAgB,SAAO,MAAC,WAAU,kBACjC,8BAAC,kBAAe,WAAW,GAAG,IAAI,SAAS,GAAI,GAAG,OAAO,GAC3D;AAGK,IAAM,6BAA6B,CAAC;AAAA,EACzC;AAAA,EACA,GAAG;AACL,MACE,oBAAC,mBAAgB,SAAO,MAAC,WAAU,kBACjC,8BAAC,wBAAqB,WAAW,GAAG,gBAAgB,SAAS,GAAI,GAAG,OAAO,GAC7E;AAGK,IAAM,wBAAwB,CAAC,EAAE,WAAW,GAAG,MAAM,MAC1D,oBAAC,mBAAgB,SAAO,MAAC,WAAU,kBACjC,8BAAC,mBAAgB,WAAW,GAAG,IAAI,SAAS,GAAI,GAAG,OAAO,GAC5D;AAGK,IAAM,yBAAyB,CAAC,EAAE,WAAW,GAAG,MAAM,MAC3D,oBAAC,mBAAgB,SAAO,MAAC,WAAU,kBACjC,8BAAC,oBAAiB,WAAW,GAAG,IAAI,SAAS,GAAI,GAAG,OAAO,GAC7D;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/_flow-boundary.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * The React Flow half of `@elabs-ai/components-ai`'s in-chat agent\n * workspace graph — `Canvas`, `Controls`, `Edge`, `Node`, `Panel`, `Toolbar` —\n * split out so the engine can be `lazy()`-loaded.\n *\n * `@xyflow/react` declares no `sideEffects`, so the six static imports it used to\n * have (one per public module, plus `@xyflow/react/dist/style.css`) put the whole\n * engine into the entry chunk of every consumer, canvas rendered or not. **The\n * stylesheet import has to live here too**: a bare\n * `import \"@xyflow/react/dist/style.css\"` in `canvas.tsx` keeps the edge alive on\n * its own.\n *\n * This is ONE boundary for all six parts on purpose (see `_flow-lazy.ts`): six\n * boundaries would be six chunks all pulling the same engine. The public modules\n * keep their `import type { … } from \"@xyflow/react\"` lines (types erase) and\n * their prop types, and render a `lazy()` wrapper over the implementations here.\n * `connection.tsx` is type-only already and needs no boundary.\n *\n * See ADR 0019 and `pnpm heavy-deps:check`.\n *\n * @lazy-boundary This module must only ever be reached via `import()`. The gate\n * fails if anything imports it statically, which would put React Flow back in\n * the entry chunk and make the `lazy()` pointless.\n */\nimport { Card } from \"@elabs-ai/components-ui\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport type { EdgeProps, InternalNode, Node as FlowNode } from \"@xyflow/react\";\nimport {\n Background,\n BaseEdge,\n Controls as ControlsPrimitive,\n getBezierPath,\n getSimpleBezierPath,\n Handle,\n NodeToolbar,\n Panel as PanelPrimitive,\n Position,\n ReactFlow,\n useInternalNode,\n} from \"@xyflow/react\";\n\nimport \"@xyflow/react/dist/style.css\";\n\nimport type { CanvasProps } from \"./canvas\";\nimport type { ControlsProps } from \"./controls\";\nimport type { NodeProps } from \"./node\";\nimport type { PanelProps } from \"./panel\";\nimport type { ToolbarProps } from \"./toolbar\";\n\nconst deleteKeyCode = [\"Backspace\", \"Delete\"];\n\n/**\n * The React Flow attribution badge is hidden on both canvas surfaces (here and\n * `@elabs-ai/components-flow`'s `CanvasShell`). `@xyflow/react` is\n * MIT — the licence requires the notice in source copies, not a rendered badge —\n * and xyflow asks that the badge only be hidden under a React Flow Pro\n * subscription. Keeping it is therefore a product/commercial decision, and this\n * repo's is to hide it. A consumer can restore it per-canvas with\n * `proOptions={{ hideAttribution: false }}`, which still wins via `...props`.\n */\nconst proOptions = { hideAttribution: true };\n\nexport const Canvas = ({ children, ...props }: CanvasProps) => (\n <ReactFlow\n deleteKeyCode={deleteKeyCode}\n fitView\n panOnDrag={false}\n panOnScroll\n proOptions={proOptions}\n selectionOnDrag={true}\n zoomOnDoubleClick={false}\n {...props}\n >\n <Background bgColor=\"var(--sidebar)\" />\n {children}\n </ReactFlow>\n);\n\nexport const Controls = ({ className, ...props }: ControlsProps) => (\n <ControlsPrimitive\n className={cn(\n \"gap-px overflow-hidden rounded-md border bg-card p-1 shadow-none!\",\n \"[&>button]:rounded-md [&>button]:border-none! [&>button]:bg-transparent! [&>button]:hover:bg-secondary!\",\n className,\n )}\n {...props}\n />\n);\n\nexport const EdgeTemporary = ({\n id,\n sourceX,\n sourceY,\n targetX,\n targetY,\n sourcePosition,\n targetPosition,\n}: EdgeProps) => {\n const [edgePath] = getSimpleBezierPath({\n sourcePosition,\n sourceX,\n sourceY,\n targetPosition,\n targetX,\n targetY,\n });\n\n return (\n <BaseEdge\n className=\"stroke-1 stroke-ring\"\n id={id}\n path={edgePath}\n style={{\n strokeDasharray: \"5, 5\",\n }}\n />\n );\n};\n\nconst getHandleCoordsByPosition = (node: InternalNode<FlowNode>, handlePosition: Position) => {\n // Choose the handle type based on position - Left is for target, Right is for source\n const handleType = handlePosition === Position.Left ? \"target\" : \"source\";\n\n const handle = node.internals.handleBounds?.[handleType]?.find(\n (h) => h.position === handlePosition,\n );\n\n if (!handle) {\n return [0, 0] as const;\n }\n\n let offsetX = handle.width / 2;\n let offsetY = handle.height / 2;\n\n // this is a tiny detail to make the markerEnd of an edge visible.\n // The handle position that gets calculated has the origin top-left, so depending which side we are using, we add a little offset\n // when the handlePosition is Position.Right for example, we need to add an offset as big as the handle itself in order to get the correct position\n switch (handlePosition) {\n case Position.Left: {\n offsetX = 0;\n break;\n }\n case Position.Right: {\n offsetX = handle.width;\n break;\n }\n case Position.Top: {\n offsetY = 0;\n break;\n }\n case Position.Bottom: {\n offsetY = handle.height;\n break;\n }\n default: {\n throw new Error(`Invalid handle position: ${handlePosition}`);\n }\n }\n\n const x = node.internals.positionAbsolute.x + handle.x + offsetX;\n const y = node.internals.positionAbsolute.y + handle.y + offsetY;\n\n return [x, y] as const;\n};\n\nconst getEdgeParams = (source: InternalNode<FlowNode>, target: InternalNode<FlowNode>) => {\n const sourcePos = Position.Right;\n const [sx, sy] = getHandleCoordsByPosition(source, sourcePos);\n const targetPos = Position.Left;\n const [tx, ty] = getHandleCoordsByPosition(target, targetPos);\n\n return {\n sourcePos,\n sx,\n sy,\n targetPos,\n tx,\n ty,\n };\n};\n\nexport const EdgeAnimated = ({ id, source, target, markerEnd, style }: EdgeProps) => {\n const sourceNode = useInternalNode(source);\n const targetNode = useInternalNode(target);\n\n if (!(sourceNode && targetNode)) {\n return null;\n }\n\n const { sx, sy, tx, ty, sourcePos, targetPos } = getEdgeParams(sourceNode, targetNode);\n\n const [edgePath] = getBezierPath({\n sourcePosition: sourcePos,\n sourceX: sx,\n sourceY: sy,\n targetPosition: targetPos,\n targetX: tx,\n targetY: ty,\n });\n\n return (\n <>\n <BaseEdge id={id} markerEnd={markerEnd} path={edgePath} style={style} />\n <circle fill=\"var(--primary)\" r=\"4\">\n <animateMotion dur=\"2s\" path={edgePath} repeatCount=\"indefinite\" />\n </circle>\n </>\n );\n};\n\nexport const Node = ({ handles, className, ...props }: NodeProps) => (\n <Card\n className={cn(\"node-container relative size-full h-auto w-sm gap-0 rounded-md p-0\", className)}\n {...props}\n >\n {handles.target && <Handle position={Position.Left} type=\"target\" />}\n {handles.source && <Handle position={Position.Right} type=\"source\" />}\n {props.children}\n </Card>\n);\n\nexport const Panel = ({ className, ...props }: PanelProps) => (\n <PanelPrimitive\n className={cn(\"m-4 overflow-hidden rounded-md border bg-card p-1\", className)}\n {...props}\n />\n);\n\nexport const Toolbar = ({ className, ...props }: ToolbarProps) => (\n <NodeToolbar\n className={cn(\"flex items-center gap-1 rounded-sm border bg-background p-1.5\", className)}\n position={Position.Bottom}\n {...props}\n />\n);\n"],"mappings":"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","names":[]}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
// src/_interactive-terminal-xterm.ts
|
|
4
|
-
import "@xterm/xterm/css/xterm.css";
|
|
5
|
-
import { FitAddon } from "@xterm/addon-fit";
|
|
6
|
-
import { Terminal } from "@xterm/xterm";
|
|
7
|
-
export {
|
|
8
|
-
FitAddon,
|
|
9
|
-
Terminal as XTerm
|
|
10
|
-
};
|
|
11
|
-
//# sourceMappingURL=_interactive-terminal-xterm-ATJ5EW3G.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/_interactive-terminal-xterm.ts"],"sourcesContent":["/**\n * The xterm.js half of `InteractiveTerminal`, split out so it can be reached\n * through a dynamic `import()`.\n *\n * `@xterm/xterm` declares no `sideEffects`, so a static import from\n * `interactive-terminal.tsx` put the whole emulator — plus its stylesheet — into\n * the entry chunk of every `@elabs-ai/components-ai` consumer, including\n * the vast majority that never render a terminal. The **stylesheet import has to\n * live here too**: a bare `import \"@xterm/xterm/css/xterm.css\"` in the public\n * module keeps the edge alive on its own.\n *\n * Unlike `_persona-rive.tsx` (a `lazy()` component boundary), this one follows\n * `_lazy-mermaid.ts`: the engine is reached through a **late-called interface**.\n * `InteractiveTerminal` only ever constructs xterm inside its mount effect —\n * never during render — so the public component keeps its `forwardRef`, its\n * imperative handle and its container `<div>` unchanged, and awaits this module\n * inside that effect. No `Suspense` seam, no skeleton swap, no change to the box\n * the terminal paints into (the container is sized by the consumer either way).\n *\n * `ITheme` and the two constructor types stay `import type` in the public module\n * (types erase), which is what keeps `buildInteractiveTerminalTheme` public.\n *\n * See ADR 0019 and `pnpm heavy-deps:check`.\n *\n * @lazy-boundary This module must only ever be reached via `import()`. The gate\n * fails if anything imports it statically, which would put xterm back in the\n * entry chunk and make the dynamic import pointless.\n */\nimport \"@xterm/xterm/css/xterm.css\";\n\nexport { FitAddon } from \"@xterm/addon-fit\";\nexport { Terminal as XTerm } from \"@xterm/xterm\";\n"],"mappings":";;;AA4BA,OAAO;AAEP,SAAS,gBAAgB;AACzB,SAAqB,gBAAa;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/_persona-rive.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * The Rive half of `Persona`, split out so it can be `lazy()`-loaded.\n *\n * `@rive-app/react-webgl2` declares no `sideEffects`, so a static import from\n * `persona.tsx` put the whole WebGL2 runtime (and its `rive.wasm`) into the entry\n * chunk of every `@elabs-ai/components-ai` consumer — including the vast majority that never\n * render a `Persona`. Keeping every Rive *value* import in this module, reached\n * only through `lazy(() => import(\"./_persona-rive\"))`, confines it to its own\n * chunk. `persona.tsx` may still `import type` from Rive (types erase).\n *\n * See ADR 0019 and `pnpm heavy-deps:check`.\n *\n * @lazy-boundary This module must only ever be reached via `import()`. The gate\n * fails if anything imports it statically, which would put Rive back in the\n * entry chunk and make the `lazy()` pointless.\n */\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport { useReducedMotion } from \"@elabs-ai/components-tokens\";\nimport type { RiveParameters } from \"@rive-app/react-webgl2\";\nimport {\n useRive,\n useStateMachineInput,\n useViewModel,\n useViewModelInstance,\n useViewModelInstanceColor,\n} from \"@rive-app/react-webgl2\";\nimport type { ReactNode } from \"react\";\nimport { memo, useEffect, useMemo, useRef, useState } from \"react\";\n\nimport type { PersonaSource, PersonaState } from \"./persona-sources\";\n\n// Delays Rive initialization by one frame so that React Strict Mode's\n// immediate unmount cycle never creates a WebGL2 context. Only the\n// second (real) mount will initialise, avoiding context exhaustion.\nconst useStrictModeSafeInit = () => {\n const [ready, setReady] = useState(false);\n\n useEffect(() => {\n const id = requestAnimationFrame(() => setReady(true));\n return () => {\n cancelAnimationFrame(id);\n setReady(false);\n };\n }, []);\n\n return ready;\n};\n\n// The state machine name is always 'default' for Elements AI visuals\nconst stateMachine = \"default\";\n\nconst getCurrentTheme = (): \"light\" | \"dark\" => {\n if (typeof window !== \"undefined\") {\n if (document.documentElement.classList.contains(\"dark\")) {\n return \"dark\";\n }\n if (window.matchMedia?.(\"(prefers-color-scheme: dark)\").matches) {\n return \"dark\";\n }\n }\n return \"light\";\n};\n\nconst useTheme = (enabled: boolean) => {\n const [theme, setTheme] = useState<\"light\" | \"dark\">(getCurrentTheme);\n\n useEffect(() => {\n // Skip if not enabled (avoids unnecessary observers for non-dynamic-color variants)\n if (!enabled) {\n return;\n }\n\n // Watch for classList changes\n const observer = new MutationObserver(() => {\n setTheme(getCurrentTheme());\n });\n\n observer.observe(document.documentElement, {\n attributeFilter: [\"class\"],\n attributes: true,\n });\n\n // Watch for OS-level theme changes\n let mql: MediaQueryList | null = null;\n const handleMediaChange = () => {\n setTheme(getCurrentTheme());\n };\n\n if (window.matchMedia) {\n mql = window.matchMedia(\"(prefers-color-scheme: dark)\");\n mql.addEventListener(\"change\", handleMediaChange);\n }\n\n return () => {\n observer.disconnect();\n if (mql) {\n mql.removeEventListener(\"change\", handleMediaChange);\n }\n };\n }, [enabled]);\n\n return theme;\n};\n\ninterface PersonaWithModelProps {\n rive: ReturnType<typeof useRive>[\"rive\"];\n source: PersonaSource;\n children: ReactNode;\n}\n\nconst PersonaWithModel = memo(({ rive, source, children }: PersonaWithModelProps) => {\n const theme = useTheme(source.dynamicColor);\n const viewModel = useViewModel(rive, { useDefault: true });\n const viewModelInstance = useViewModelInstance(viewModel, {\n rive,\n useDefault: true,\n });\n const viewModelInstanceColor = useViewModelInstanceColor(\"color\", viewModelInstance);\n\n useEffect(() => {\n if (!(viewModelInstanceColor && source.dynamicColor)) {\n return;\n }\n\n const [r, g, b] = theme === \"dark\" ? [255, 255, 255] : [0, 0, 0];\n viewModelInstanceColor.setRgb(r, g, b);\n }, [viewModelInstanceColor, theme, source.dynamicColor]);\n\n return children;\n});\n\nPersonaWithModel.displayName = \"PersonaWithModel\";\n\nconst PersonaWithoutModel = memo(({ children }: { children: ReactNode }) => children);\n\nPersonaWithoutModel.displayName = \"PersonaWithoutModel\";\n\nexport interface PersonaRiveProps {\n className?: string;\n onLoad?: RiveParameters[\"onLoad\"];\n onLoadError?: RiveParameters[\"onLoadError\"];\n onPause?: RiveParameters[\"onPause\"];\n onPlay?: RiveParameters[\"onPlay\"];\n onReady?: () => void;\n onStop?: RiveParameters[\"onStop\"];\n source: PersonaSource;\n /** Resolved `.riv` URL — `Persona`'s `src` override, or the variant default. */\n src: string;\n state: PersonaState;\n}\n\nconst PersonaRive = ({\n className,\n onLoad,\n onLoadError,\n onPause,\n onPlay,\n onReady,\n onStop,\n source,\n src,\n state,\n}: PersonaRiveProps) => {\n // Stabilize callbacks to prevent useRive from reinitializing\n const callbacksRef = useRef({ onLoad, onLoadError, onPause, onPlay, onReady, onStop });\n\n useEffect(() => {\n callbacksRef.current = { onLoad, onLoadError, onPause, onPlay, onReady, onStop };\n }, [onLoad, onLoadError, onPause, onPlay, onReady, onStop]);\n\n const stableCallbacks = useMemo(\n () => ({\n onLoad: ((loadedRive) =>\n callbacksRef.current.onLoad?.(loadedRive)) as RiveParameters[\"onLoad\"],\n onLoadError: ((err) =>\n callbacksRef.current.onLoadError?.(err)) as RiveParameters[\"onLoadError\"],\n onPause: ((event) => callbacksRef.current.onPause?.(event)) as RiveParameters[\"onPause\"],\n onPlay: ((event) => callbacksRef.current.onPlay?.(event)) as RiveParameters[\"onPlay\"],\n onReady: () => callbacksRef.current.onReady?.(),\n onStop: ((event) => callbacksRef.current.onStop?.(event)) as RiveParameters[\"onStop\"],\n }),\n [],\n );\n\n // Delay initialisation by one frame to avoid creating (and leaking)\n // a WebGL2 context during React Strict Mode's first throw-away mount.\n const ready = useStrictModeSafeInit();\n const prefersReducedMotion = useReducedMotion();\n\n const { rive, RiveComponent } = useRive(\n ready\n ? {\n autoplay: !prefersReducedMotion,\n onLoad: stableCallbacks.onLoad,\n onLoadError: stableCallbacks.onLoadError,\n onPause: stableCallbacks.onPause,\n onPlay: stableCallbacks.onPlay,\n onRiveReady: stableCallbacks.onReady,\n onStop: stableCallbacks.onStop,\n src,\n stateMachines: stateMachine,\n }\n : null,\n );\n\n // `autoplay` only governs the INITIAL mount — honor a mid-session\n // reduced-motion preference change by pausing/resuming the running\n // instance. The state-machine inputs below keep updating either way, so\n // the artwork still shows the correct resting pose while paused.\n useEffect(() => {\n if (!rive) return;\n if (prefersReducedMotion) {\n rive.pause();\n } else {\n rive.play();\n }\n }, [rive, prefersReducedMotion]);\n\n const listeningInput = useStateMachineInput(rive, stateMachine, \"listening\");\n const thinkingInput = useStateMachineInput(rive, stateMachine, \"thinking\");\n const speakingInput = useStateMachineInput(rive, stateMachine, \"speaking\");\n const asleepInput = useStateMachineInput(rive, stateMachine, \"asleep\");\n\n // Rive state machine inputs are mutable objects that must be set via direct\n // property assignment — this is the intended Rive API, not a React anti-pattern.\n useEffect(() => {\n if (listeningInput) {\n listeningInput.value = state === \"listening\";\n }\n if (thinkingInput) {\n thinkingInput.value = state === \"thinking\";\n }\n if (speakingInput) {\n speakingInput.value = state === \"speaking\";\n }\n if (asleepInput) {\n asleepInput.value = state === \"asleep\";\n }\n }, [state, listeningInput, thinkingInput, speakingInput, asleepInput]);\n\n const Wrapper = source.hasModel ? PersonaWithModel : PersonaWithoutModel;\n\n return (\n <Wrapper rive={rive} source={source}>\n <RiveComponent className={cn(\"size-16 shrink-0\", className)} />\n </Wrapper>\n );\n};\n\nexport default PersonaRive;\n"],"mappings":"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","names":[]}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The xterm.js half of `InteractiveTerminal`, split out so it can be reached
|
|
3
|
-
* through a dynamic `import()`.
|
|
4
|
-
*
|
|
5
|
-
* `@xterm/xterm` declares no `sideEffects`, so a static import from
|
|
6
|
-
* `interactive-terminal.tsx` put the whole emulator — plus its stylesheet — into
|
|
7
|
-
* the entry chunk of every `@elabs-ai/components-ai` consumer, including
|
|
8
|
-
* the vast majority that never render a terminal. The **stylesheet import has to
|
|
9
|
-
* live here too**: a bare `import "@xterm/xterm/css/xterm.css"` in the public
|
|
10
|
-
* module keeps the edge alive on its own.
|
|
11
|
-
*
|
|
12
|
-
* Unlike `_persona-rive.tsx` (a `lazy()` component boundary), this one follows
|
|
13
|
-
* `_lazy-mermaid.ts`: the engine is reached through a **late-called interface**.
|
|
14
|
-
* `InteractiveTerminal` only ever constructs xterm inside its mount effect —
|
|
15
|
-
* never during render — so the public component keeps its `forwardRef`, its
|
|
16
|
-
* imperative handle and its container `<div>` unchanged, and awaits this module
|
|
17
|
-
* inside that effect. No `Suspense` seam, no skeleton swap, no change to the box
|
|
18
|
-
* the terminal paints into (the container is sized by the consumer either way).
|
|
19
|
-
*
|
|
20
|
-
* `ITheme` and the two constructor types stay `import type` in the public module
|
|
21
|
-
* (types erase), which is what keeps `buildInteractiveTerminalTheme` public.
|
|
22
|
-
*
|
|
23
|
-
* See ADR 0019 and `pnpm heavy-deps:check`.
|
|
24
|
-
*
|
|
25
|
-
* @lazy-boundary This module must only ever be reached via `import()`. The gate
|
|
26
|
-
* fails if anything imports it statically, which would put xterm back in the
|
|
27
|
-
* entry chunk and make the dynamic import pointless.
|
|
28
|
-
*/
|
|
29
|
-
import "@xterm/xterm/css/xterm.css";
|
|
30
|
-
|
|
31
|
-
export { FitAddon } from "@xterm/addon-fit";
|
|
32
|
-
export { Terminal as XTerm } from "@xterm/xterm";
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AI Composer — the standard chat-input block.
|
|
3
|
-
*
|
|
4
|
-
* This block is now a thin showcase of the real `<Composer />` component (the
|
|
5
|
-
* canonical brand-ui AI chat input). The double-card chrome — a status strip
|
|
6
|
-
* wrapping a recessed `PromptInput` well (sharp top, theme-rounded bottom),
|
|
7
|
-
* model pill, voice, circular send, suggestion chips — lives in `composer.tsx`,
|
|
8
|
-
* not here, so every surface gets the same input by importing it. `Composer`'s
|
|
9
|
-
* `tone` prop (#254) picks the outer/inner arrangement: `"surface"` (default)
|
|
10
|
-
* is the outer `bg-card` frame this block ships everywhere else; `"card"` is
|
|
11
|
-
* the tinted-outer/distinct-inner arrangement the reference exemplar calls
|
|
12
|
-
* for — see `DoubleCardToned` below. Semantic tokens only; theme-aware radii;
|
|
13
|
-
* reads in all themes.
|
|
14
|
-
*/
|
|
15
|
-
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
16
|
-
|
|
17
|
-
import { ChatGreeting } from "./chat-greeting";
|
|
18
|
-
import { Composer } from "./composer";
|
|
19
|
-
|
|
20
|
-
const SUGGESTIONS = ["Summary", "Code", "Design", "Research"];
|
|
21
|
-
|
|
22
|
-
/** Empty/first-run chat state: greeting + composer + suggestion chips. */
|
|
23
|
-
function AiComposerScene() {
|
|
24
|
-
return (
|
|
25
|
-
<div className="grid min-h-[28rem] place-items-center bg-background px-4 py-10 text-foreground">
|
|
26
|
-
<div className="w-full max-w-2xl">
|
|
27
|
-
<ChatGreeting title="Good morning, Avery" subtitle="How can I" accent="assist you today?" />
|
|
28
|
-
|
|
29
|
-
<Composer onSubmit={() => undefined} suggestions={SUGGESTIONS} />
|
|
30
|
-
</div>
|
|
31
|
-
</div>
|
|
32
|
-
);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const meta = {
|
|
36
|
-
title: "Patterns/Blocks/AI Composer",
|
|
37
|
-
parameters: {
|
|
38
|
-
layout: "fullscreen",
|
|
39
|
-
docs: {
|
|
40
|
-
description: {
|
|
41
|
-
component:
|
|
42
|
-
"The standard AI chat-input block — a thin showcase of the real <Composer /> component: a rounded two-tone double card (a status strip around a recessed PromptInput well with a sharp top and theme-rounded bottom), a model pill, a circular send, and suggestion chips under a centered greeting. Composer's `tone` prop (#254) picks the arrangement — `surface` (default, this block's Default/ComposerOnly) is the outer bg-card frame; `card` (see DoubleCardToned) is the tinted-outer/distinct-inner frame the reference exemplar calls for. Built on the real @elabs-ai/components-ai PromptInput; semantic tokens only; reads in all themes. Reach for <Composer /> instead of hand-rolling this.",
|
|
43
|
-
},
|
|
44
|
-
},
|
|
45
|
-
},
|
|
46
|
-
tags: ["autodocs"],
|
|
47
|
-
} satisfies Meta;
|
|
48
|
-
|
|
49
|
-
export default meta;
|
|
50
|
-
type Story = StoryObj<typeof meta>;
|
|
51
|
-
|
|
52
|
-
/** The full empty-state scene (greeting + composer + chips). */
|
|
53
|
-
export const Default: Story = { render: () => <AiComposerScene /> };
|
|
54
|
-
|
|
55
|
-
/** Just the double-card composer, to drop into a ChatShell footer. */
|
|
56
|
-
export const ComposerOnly: Story = {
|
|
57
|
-
render: () => (
|
|
58
|
-
<div className="bg-background p-6">
|
|
59
|
-
<div className="mx-auto max-w-2xl">
|
|
60
|
-
<Composer onSubmit={() => undefined} />
|
|
61
|
-
</div>
|
|
62
|
-
</div>
|
|
63
|
-
),
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* The tinted-outer/distinct-inner "double card" (#254) — the look the
|
|
68
|
-
* shadcnuikit ai-chat-v2 exemplar this block was named after actually calls
|
|
69
|
-
* for: `<Composer tone="card" />` swaps the outer frame to `bg-surface-muted`
|
|
70
|
-
* around a `tone="card"` well, instead of the default outer `bg-card` frame
|
|
71
|
-
* `ComposerOnly`/`Default` above ship. Check both themes: the well is
|
|
72
|
-
* raised (lighter than the frame) on light themes, recessed (darker) on
|
|
73
|
-
* dark.
|
|
74
|
-
*/
|
|
75
|
-
export const DoubleCardToned: Story = {
|
|
76
|
-
render: () => (
|
|
77
|
-
<div className="bg-background p-6">
|
|
78
|
-
<div className="mx-auto max-w-2xl">
|
|
79
|
-
<Composer tone="card" onSubmit={() => undefined} suggestions={SUGGESTIONS} />
|
|
80
|
-
</div>
|
|
81
|
-
</div>
|
|
82
|
-
),
|
|
83
|
-
};
|
package/src/context.stories.tsx
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
-
import { Context, ContextTrigger } from "./context";
|
|
3
|
-
const meta = {
|
|
4
|
-
title: "AI/Context",
|
|
5
|
-
component: Context,
|
|
6
|
-
parameters: { layout: "padded" },
|
|
7
|
-
} satisfies Meta<typeof Context>;
|
|
8
|
-
export default meta;
|
|
9
|
-
type Story = StoryObj<typeof meta>;
|
|
10
|
-
export const Default: Story = {
|
|
11
|
-
render: () => (
|
|
12
|
-
<Context usedTokens={2400} maxTokens={8000}>
|
|
13
|
-
<ContextTrigger />
|
|
14
|
-
</Context>
|
|
15
|
-
),
|
|
16
|
-
};
|