@convai/web-sdk 1.8.0-beta.8 → 1.8.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 +45 -213
- package/dist/core/ConvaiClient.d.ts +1 -190
- package/dist/core/ConvaiClient.d.ts.map +1 -1
- package/dist/core/ConvaiClient.js +286 -1306
- package/dist/core/ConvaiClient.js.map +1 -1
- package/dist/core/MessageHandler.d.ts +1 -19
- package/dist/core/MessageHandler.d.ts.map +1 -1
- package/dist/core/MessageHandler.js +27 -378
- package/dist/core/MessageHandler.js.map +1 -1
- package/dist/core/connectRequest.d.ts +4 -17
- package/dist/core/connectRequest.d.ts.map +1 -1
- package/dist/core/connectRequest.js +17 -28
- package/dist/core/connectRequest.js.map +1 -1
- package/dist/core/index.d.ts +1 -8
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -13
- package/dist/core/index.js.map +1 -1
- package/dist/core/logicalTurn.d.ts +6 -4
- package/dist/core/logicalTurn.d.ts.map +1 -1
- package/dist/core/logicalTurn.js +4 -29
- package/dist/core/logicalTurn.js.map +1 -1
- package/dist/core/modelOutput.d.ts.map +1 -1
- package/dist/core/modelOutput.js +2 -2
- package/dist/core/modelOutput.js.map +1 -1
- package/dist/core/types.d.ts +22 -860
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/embed/ConvaiChatElement.d.ts +1 -77
- package/dist/embed/ConvaiChatElement.d.ts.map +1 -1
- package/dist/embed/ConvaiChatElement.js +54 -433
- package/dist/embed/ConvaiChatElement.js.map +1 -1
- package/dist/embed/chat-embed-v1.js +35 -136
- package/dist/embed/chat-embed-v1.js.map +4 -4
- package/dist/react/components/ConvaiWidget.d.ts.map +1 -1
- package/dist/react/components/ConvaiWidget.js +1 -6
- package/dist/react/components/ConvaiWidget.js.map +1 -1
- package/dist/react/components/rtc-widget/components/AudioVisualizer.d.ts.map +1 -1
- package/dist/react/components/rtc-widget/components/AudioVisualizer.js +1 -28
- package/dist/react/components/rtc-widget/components/AudioVisualizer.js.map +1 -1
- package/dist/react/components/rtc-widget/components/MarkdownRenderer.d.ts +0 -7
- package/dist/react/components/rtc-widget/components/MarkdownRenderer.d.ts.map +1 -1
- package/dist/react/components/rtc-widget/components/MarkdownRenderer.js +46 -135
- package/dist/react/components/rtc-widget/components/MarkdownRenderer.js.map +1 -1
- package/dist/react/components/rtc-widget/components/UserMessage.js +1 -1
- package/dist/react/components/rtc-widget/components/UserMessage.js.map +1 -1
- package/dist/react/components/rtc-widget/components/conviComponents/VoiceModeOverlay.d.ts.map +1 -1
- package/dist/react/components/rtc-widget/components/conviComponents/VoiceModeOverlay.js +49 -93
- package/dist/react/components/rtc-widget/components/conviComponents/VoiceModeOverlay.js.map +1 -1
- package/dist/react/hooks/useConvaiClient.d.ts.map +1 -1
- package/dist/react/hooks/useConvaiClient.js +2 -17
- package/dist/react/hooks/useConvaiClient.js.map +1 -1
- package/dist/react/index.d.ts +1 -4
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +1 -8
- package/dist/react/index.js.map +1 -1
- package/dist/vanilla/AudioRenderer.d.ts +1 -3
- package/dist/vanilla/AudioRenderer.d.ts.map +1 -1
- package/dist/vanilla/AudioRenderer.js +2 -8
- package/dist/vanilla/AudioRenderer.js.map +1 -1
- package/dist/vanilla/ConvaiWidget.d.ts.map +1 -1
- package/dist/vanilla/ConvaiWidget.js +302 -1122
- package/dist/vanilla/ConvaiWidget.js.map +1 -1
- package/dist/vanilla/WidgetEventSubscriptions.d.ts +1 -1
- package/dist/vanilla/WidgetEventSubscriptions.d.ts.map +1 -1
- package/dist/vanilla/WidgetEventSubscriptions.js +0 -5
- package/dist/vanilla/WidgetEventSubscriptions.js.map +1 -1
- package/dist/vanilla/icons.d.ts.map +1 -1
- package/dist/vanilla/icons.js +15 -62
- package/dist/vanilla/icons.js.map +1 -1
- package/dist/vanilla/index.d.ts +2 -6
- package/dist/vanilla/index.d.ts.map +1 -1
- package/dist/vanilla/index.js +1 -5
- package/dist/vanilla/index.js.map +1 -1
- package/dist/vanilla/styles.d.ts +1 -18
- package/dist/vanilla/styles.d.ts.map +1 -1
- package/dist/vanilla/styles.js +29 -141
- package/dist/vanilla/styles.js.map +1 -1
- package/dist/vanilla/types.d.ts +0 -74
- package/dist/vanilla/types.d.ts.map +1 -1
- package/dist/vanilla/types.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +17 -44
- package/dist/core/CharacterRoster.d.ts +0 -84
- package/dist/core/CharacterRoster.d.ts.map +0 -1
- package/dist/core/CharacterRoster.js +0 -348
- package/dist/core/CharacterRoster.js.map +0 -1
- package/dist/core/CharacterVersionManager.d.ts +0 -100
- package/dist/core/CharacterVersionManager.d.ts.map +0 -1
- package/dist/core/CharacterVersionManager.js +0 -227
- package/dist/core/CharacterVersionManager.js.map +0 -1
- package/dist/core/ConvaiRoomError.d.ts +0 -48
- package/dist/core/ConvaiRoomError.d.ts.map +0 -1
- package/dist/core/ConvaiRoomError.js +0 -111
- package/dist/core/ConvaiRoomError.js.map +0 -1
- package/dist/core/SSESession.d.ts +0 -59
- package/dist/core/SSESession.d.ts.map +0 -1
- package/dist/core/SSESession.js +0 -289
- package/dist/core/SSESession.js.map +0 -1
- package/dist/core/characterReference.d.ts +0 -40
- package/dist/core/characterReference.d.ts.map +0 -1
- package/dist/core/characterReference.js +0 -72
- package/dist/core/characterReference.js.map +0 -1
- package/dist/core/rosterRequest.d.ts +0 -57
- package/dist/core/rosterRequest.d.ts.map +0 -1
- package/dist/core/rosterRequest.js +0 -210
- package/dist/core/rosterRequest.js.map +0 -1
- package/dist/utils/inlineMarkdown.d.ts +0 -60
- package/dist/utils/inlineMarkdown.d.ts.map +0 -1
- package/dist/utils/inlineMarkdown.js +0 -183
- package/dist/utils/inlineMarkdown.js.map +0 -1
|
@@ -1,137 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import React from "react";
|
|
3
|
-
|
|
4
|
-
const MEDIA_RADIUS = 8;
|
|
5
|
-
const SHIMMER = "convai-preview-shimmer";
|
|
6
|
-
/**
|
|
7
|
-
* The renderer styles inline, but a keyframe cannot be expressed that way, so
|
|
8
|
-
* the one animation it needs is injected once per document. Guarded for SSR and
|
|
9
|
-
* for the tests, which render to a string with no document.
|
|
10
|
-
*/
|
|
11
|
-
function ensureShimmerKeyframes() {
|
|
12
|
-
if (typeof document === "undefined")
|
|
13
|
-
return;
|
|
14
|
-
if (document.getElementById(SHIMMER))
|
|
15
|
-
return;
|
|
16
|
-
const style = document.createElement("style");
|
|
17
|
-
style.id = SHIMMER;
|
|
18
|
-
style.textContent = `
|
|
19
|
-
@keyframes ${SHIMMER} { from { background-position: 200% 0 } to { background-position: -200% 0 } }
|
|
20
|
-
@media (prefers-reduced-motion: reduce) {
|
|
21
|
-
[data-convai-skeleton] { animation: none !important }
|
|
22
|
-
}`;
|
|
23
|
-
document.head.appendChild(style);
|
|
24
|
-
}
|
|
25
|
-
const linkStyle = {
|
|
26
|
-
color: "#10b981",
|
|
27
|
-
textDecoration: "underline",
|
|
28
|
-
wordBreak: "break-all",
|
|
29
|
-
};
|
|
30
|
-
const mediaStyle = {
|
|
31
|
-
display: "block",
|
|
32
|
-
width: "100%",
|
|
33
|
-
height: "100%",
|
|
34
|
-
objectFit: "cover",
|
|
35
|
-
borderRadius: MEDIA_RADIUS,
|
|
36
|
-
};
|
|
37
|
-
/**
|
|
38
|
-
* A preview card sizes itself before the media arrives, so the bubble does not
|
|
39
|
-
* jump when an image decodes. 16/10 is a compromise: tall enough to read, short
|
|
40
|
-
* enough that several in a row still scroll.
|
|
41
|
-
*/
|
|
42
|
-
const CARD_ASPECT = "16 / 10";
|
|
43
|
-
const cardStyle = {
|
|
44
|
-
// A span by default is inline, where aspect-ratio does nothing.
|
|
45
|
-
display: "block",
|
|
46
|
-
position: "relative",
|
|
47
|
-
marginTop: 6,
|
|
48
|
-
maxWidth: 320,
|
|
49
|
-
aspectRatio: CARD_ASPECT,
|
|
50
|
-
borderRadius: MEDIA_RADIUS,
|
|
51
|
-
overflow: "hidden",
|
|
52
|
-
// The widget is embedded in someone else's page on an unknown background, so
|
|
53
|
-
// chrome is derived from the inherited text colour rather than a fixed grey.
|
|
54
|
-
background: "color-mix(in srgb, currentColor 6%, transparent)",
|
|
55
|
-
boxShadow: "inset 0 0 0 1px color-mix(in srgb, currentColor 10%, transparent)",
|
|
56
|
-
};
|
|
57
|
-
const skeletonStyle = {
|
|
58
|
-
position: "absolute",
|
|
59
|
-
inset: 0,
|
|
60
|
-
borderRadius: MEDIA_RADIUS,
|
|
61
|
-
background: "color-mix(in srgb, currentColor 8%, transparent)",
|
|
62
|
-
backgroundImage: "linear-gradient(90deg, transparent, color-mix(in srgb, currentColor 12%, transparent), transparent)",
|
|
63
|
-
backgroundSize: "200% 100%",
|
|
64
|
-
animation: `${SHIMMER} 1.4s ease-in-out infinite`,
|
|
65
|
-
};
|
|
66
|
-
const captionStyle = {
|
|
67
|
-
display: "block",
|
|
68
|
-
marginTop: 4,
|
|
69
|
-
fontSize: 11,
|
|
70
|
-
opacity: 0.7,
|
|
71
|
-
whiteSpace: "nowrap",
|
|
72
|
-
overflow: "hidden",
|
|
73
|
-
textOverflow: "ellipsis",
|
|
74
|
-
};
|
|
75
|
-
const unavailableStyle = {
|
|
76
|
-
...captionStyle,
|
|
77
|
-
marginTop: 6,
|
|
78
|
-
opacity: 0.55,
|
|
79
|
-
fontStyle: "italic",
|
|
80
|
-
};
|
|
81
|
-
/** An unsafe target: the words the character wrote, with no href behind them. */
|
|
82
|
-
const blockedStyle = {
|
|
83
|
-
opacity: 0.75,
|
|
84
|
-
textDecoration: "underline",
|
|
85
|
-
textDecorationStyle: "dotted",
|
|
86
|
-
cursor: "help",
|
|
87
|
-
};
|
|
88
|
-
/** `cdn.example.com/a/b/c.png` → `cdn.example.com · c.png` */
|
|
89
|
-
function describeUrl(url) {
|
|
90
|
-
try {
|
|
91
|
-
const { host, pathname } = new URL(url);
|
|
92
|
-
const file = pathname.split("/").filter(Boolean).pop();
|
|
93
|
-
return file ? `${host} · ${file}` : host;
|
|
94
|
-
}
|
|
95
|
-
catch {
|
|
96
|
-
return compactUrl(url);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
/**
|
|
100
|
-
* Inline preview for an image or video URL, shown under its link.
|
|
101
|
-
*
|
|
102
|
-
* The media element is always in the tree so the browser starts fetching it
|
|
103
|
-
* immediately; the skeleton sits on top until it decodes. A URL that does not
|
|
104
|
-
* resolve — which a character can easily invent — collapses to one muted line
|
|
105
|
-
* instead of leaving an empty player in the transcript.
|
|
106
|
-
*/
|
|
107
|
-
const MediaPreview = ({ src, alt, kind }) => {
|
|
108
|
-
const [state, setState] = React.useState("loading");
|
|
109
|
-
React.useEffect(ensureShimmerKeyframes, []);
|
|
110
|
-
if (state === "error") {
|
|
111
|
-
return _jsxs("span", { style: unavailableStyle, children: ["Preview unavailable \u2014 ", describeUrl(src)] });
|
|
112
|
-
}
|
|
113
|
-
const ready = state === "ready";
|
|
114
|
-
return (_jsxs(_Fragment, { children: [_jsxs("span", { style: cardStyle, children: [kind === "image" ? (_jsx("img", { src: src, alt: alt, loading: "lazy", onLoad: () => setState("ready"), onError: () => setState("error"), style: { ...mediaStyle, opacity: ready ? 1 : 0 } })) : (_jsx("video", { src: src, controls: true, preload: "metadata", onLoadedMetadata: () => setState("ready"), onError: () => setState("error"), style: { ...mediaStyle, objectFit: "contain", opacity: ready ? 1 : 0 } })), !ready && _jsx("span", { "data-convai-skeleton": "", style: skeletonStyle, "aria-hidden": "true" })] }), _jsx("span", { style: captionStyle, children: describeUrl(src) })] }));
|
|
115
|
-
};
|
|
116
|
-
const renderNode = (node, key, media) => {
|
|
117
|
-
switch (node.kind) {
|
|
118
|
-
case "strong":
|
|
119
|
-
return _jsx("strong", { children: node.text }, key);
|
|
120
|
-
case "em":
|
|
121
|
-
return _jsx("em", { children: node.text }, key);
|
|
122
|
-
case "link":
|
|
123
|
-
return (_jsxs(React.Fragment, { children: [_jsx("a", { href: node.href, target: "_blank", rel: "noopener noreferrer", style: linkStyle, children: node.label === node.href ? compactUrl(node.label) : node.label }), media && node.media && (_jsx(MediaPreview, { src: node.href, alt: node.label, kind: node.media }))] }, key));
|
|
124
|
-
case "blocked":
|
|
125
|
-
return (_jsx("span", { style: blockedStyle, title: `Link blocked: ${node.href}`, children: node.label }, key));
|
|
126
|
-
case "image":
|
|
127
|
-
// With previews off, an image still has to say what it pointed at, so it
|
|
128
|
-
// degrades to the same anchor a plain link would render.
|
|
129
|
-
return media ? (_jsx(MediaPreview, { src: node.src, alt: node.alt, kind: "image" }, key)) : (_jsx("a", { href: node.src, target: "_blank", rel: "noopener noreferrer", style: linkStyle, children: node.alt || node.src }, key));
|
|
130
|
-
default:
|
|
131
|
-
return _jsx(React.Fragment, { children: node.text }, key);
|
|
132
|
-
}
|
|
133
|
-
};
|
|
134
|
-
export const MarkdownRenderer = ({ text, media = true }) => {
|
|
3
|
+
export const MarkdownRenderer = ({ text }) => {
|
|
135
4
|
// Convert markdown to JSX elements
|
|
136
5
|
const renderMarkdown = (input) => {
|
|
137
6
|
if (!input)
|
|
@@ -142,8 +11,50 @@ export const MarkdownRenderer = ({ text, media = true }) => {
|
|
|
142
11
|
// Split by actual newlines
|
|
143
12
|
const lines = normalizedText.split('\n');
|
|
144
13
|
return lines.map((line, lineIndex) => {
|
|
145
|
-
//
|
|
146
|
-
const
|
|
14
|
+
// Process markdown within each line
|
|
15
|
+
const processLine = (text) => {
|
|
16
|
+
const parts = [];
|
|
17
|
+
let remaining = text;
|
|
18
|
+
let keyCounter = 0;
|
|
19
|
+
// Process bold and italic markdown
|
|
20
|
+
// Bold: **text** or __text__
|
|
21
|
+
// Italic: *text* or _text_
|
|
22
|
+
const markdownRegex = /(\*\*|__)(.*?)\1|\*([^*]+)\*|_([^_]+)_/g;
|
|
23
|
+
let lastIndex = 0;
|
|
24
|
+
let match;
|
|
25
|
+
while ((match = markdownRegex.exec(remaining)) !== null) {
|
|
26
|
+
// Add text before the match
|
|
27
|
+
if (match.index > lastIndex) {
|
|
28
|
+
const textBefore = remaining.substring(lastIndex, match.index);
|
|
29
|
+
if (textBefore) {
|
|
30
|
+
parts.push(_jsx(React.Fragment, { children: textBefore }, `text-${lineIndex}-${keyCounter++}`));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// Determine if it's bold or italic
|
|
34
|
+
if (match[1] && match[2]) {
|
|
35
|
+
// Bold text (** or __)
|
|
36
|
+
parts.push(_jsx("strong", { children: match[2] }, `bold-${lineIndex}-${keyCounter++}`));
|
|
37
|
+
}
|
|
38
|
+
else if (match[3]) {
|
|
39
|
+
// Italic text (*)
|
|
40
|
+
parts.push(_jsx("em", { children: match[3] }, `italic-${lineIndex}-${keyCounter++}`));
|
|
41
|
+
}
|
|
42
|
+
else if (match[4]) {
|
|
43
|
+
// Italic text (_)
|
|
44
|
+
parts.push(_jsx("em", { children: match[4] }, `italic-${lineIndex}-${keyCounter++}`));
|
|
45
|
+
}
|
|
46
|
+
lastIndex = match.index + match[0].length;
|
|
47
|
+
}
|
|
48
|
+
// Add remaining text
|
|
49
|
+
if (lastIndex < remaining.length) {
|
|
50
|
+
const textAfter = remaining.substring(lastIndex);
|
|
51
|
+
if (textAfter) {
|
|
52
|
+
parts.push(_jsx(React.Fragment, { children: textAfter }, `text-${lineIndex}-${keyCounter++}`));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return parts;
|
|
56
|
+
};
|
|
57
|
+
const processedLine = processLine(line);
|
|
147
58
|
// Handle empty lines (creates paragraph spacing)
|
|
148
59
|
if (line.trim() === "" && lineIndex > 0) {
|
|
149
60
|
return _jsx("br", {}, `br-${lineIndex}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MarkdownRenderer.js","sourceRoot":"","sources":["../../../../../src/react/components/rtc-widget/components/MarkdownRenderer.tsx"],"names":[],"mappings":"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","sourcesContent":["import React from \"react\";\nimport {\n parseInlineMarkdown,\n compactUrl,\n type InlineNode,\n type MediaKind,\n} from \"../../../../utils/inlineMarkdown\";\n\nconst MEDIA_RADIUS = 8;\nconst SHIMMER = \"convai-preview-shimmer\";\n\n/**\n * The renderer styles inline, but a keyframe cannot be expressed that way, so\n * the one animation it needs is injected once per document. Guarded for SSR and\n * for the tests, which render to a string with no document.\n */\nfunction ensureShimmerKeyframes(): void {\n if (typeof document === \"undefined\") return;\n if (document.getElementById(SHIMMER)) return;\n const style = document.createElement(\"style\");\n style.id = SHIMMER;\n style.textContent = `\n@keyframes ${SHIMMER} { from { background-position: 200% 0 } to { background-position: -200% 0 } }\n@media (prefers-reduced-motion: reduce) {\n [data-convai-skeleton] { animation: none !important }\n}`;\n document.head.appendChild(style);\n}\n\ninterface MarkdownRendererProps {\n text: string;\n /**\n * Render inline image/video previews under a media link. Bot messages want\n * them; the user's own bubble does not — you already know what you typed, and\n * a preview there would let a URL a user pasted embed remote media into the\n * transcript. Links stay clickable either way.\n */\n media?: boolean;\n}\n\nconst linkStyle: React.CSSProperties = {\n color: \"#10b981\",\n textDecoration: \"underline\",\n wordBreak: \"break-all\",\n};\n\nconst mediaStyle: React.CSSProperties = {\n display: \"block\",\n width: \"100%\",\n height: \"100%\",\n objectFit: \"cover\",\n borderRadius: MEDIA_RADIUS,\n};\n\n/**\n * A preview card sizes itself before the media arrives, so the bubble does not\n * jump when an image decodes. 16/10 is a compromise: tall enough to read, short\n * enough that several in a row still scroll.\n */\nconst CARD_ASPECT = \"16 / 10\";\n\nconst cardStyle: React.CSSProperties = {\n // A span by default is inline, where aspect-ratio does nothing.\n display: \"block\",\n position: \"relative\",\n marginTop: 6,\n maxWidth: 320,\n aspectRatio: CARD_ASPECT,\n borderRadius: MEDIA_RADIUS,\n overflow: \"hidden\",\n // The widget is embedded in someone else's page on an unknown background, so\n // chrome is derived from the inherited text colour rather than a fixed grey.\n background: \"color-mix(in srgb, currentColor 6%, transparent)\",\n boxShadow: \"inset 0 0 0 1px color-mix(in srgb, currentColor 10%, transparent)\",\n};\n\nconst skeletonStyle: React.CSSProperties = {\n position: \"absolute\",\n inset: 0,\n borderRadius: MEDIA_RADIUS,\n background: \"color-mix(in srgb, currentColor 8%, transparent)\",\n backgroundImage:\n \"linear-gradient(90deg, transparent, color-mix(in srgb, currentColor 12%, transparent), transparent)\",\n backgroundSize: \"200% 100%\",\n animation: `${SHIMMER} 1.4s ease-in-out infinite`,\n};\n\nconst captionStyle: React.CSSProperties = {\n display: \"block\",\n marginTop: 4,\n fontSize: 11,\n opacity: 0.7,\n whiteSpace: \"nowrap\",\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n};\n\nconst unavailableStyle: React.CSSProperties = {\n ...captionStyle,\n marginTop: 6,\n opacity: 0.55,\n fontStyle: \"italic\",\n};\n\n/** An unsafe target: the words the character wrote, with no href behind them. */\nconst blockedStyle: React.CSSProperties = {\n opacity: 0.75,\n textDecoration: \"underline\",\n textDecorationStyle: \"dotted\",\n cursor: \"help\",\n};\n\n/** `cdn.example.com/a/b/c.png` → `cdn.example.com · c.png` */\nfunction describeUrl(url: string): string {\n try {\n const { host, pathname } = new URL(url);\n const file = pathname.split(\"/\").filter(Boolean).pop();\n return file ? `${host} · ${file}` : host;\n } catch {\n return compactUrl(url);\n }\n}\n\ntype LoadState = \"loading\" | \"ready\" | \"error\";\n\n/**\n * Inline preview for an image or video URL, shown under its link.\n *\n * The media element is always in the tree so the browser starts fetching it\n * immediately; the skeleton sits on top until it decodes. A URL that does not\n * resolve — which a character can easily invent — collapses to one muted line\n * instead of leaving an empty player in the transcript.\n */\nconst MediaPreview = ({ src, alt, kind }: { src: string; alt: string; kind: MediaKind }) => {\n const [state, setState] = React.useState<LoadState>(\"loading\");\n React.useEffect(ensureShimmerKeyframes, []);\n\n if (state === \"error\") {\n return <span style={unavailableStyle}>Preview unavailable — {describeUrl(src)}</span>;\n }\n\n const ready = state === \"ready\";\n return (\n <>\n <span style={cardStyle}>\n {kind === \"image\" ? (\n <img\n src={src}\n alt={alt}\n loading=\"lazy\"\n onLoad={() => setState(\"ready\")}\n onError={() => setState(\"error\")}\n style={{ ...mediaStyle, opacity: ready ? 1 : 0 }}\n />\n ) : (\n <video\n src={src}\n controls\n preload=\"metadata\"\n onLoadedMetadata={() => setState(\"ready\")}\n onError={() => setState(\"error\")}\n style={{ ...mediaStyle, objectFit: \"contain\", opacity: ready ? 1 : 0 }}\n />\n )}\n {!ready && <span data-convai-skeleton=\"\" style={skeletonStyle} aria-hidden=\"true\" />}\n </span>\n <span style={captionStyle}>{describeUrl(src)}</span>\n </>\n );\n};\n\nconst renderNode = (node: InlineNode, key: string, media: boolean): React.ReactNode => {\n switch (node.kind) {\n case \"strong\":\n return <strong key={key}>{node.text}</strong>;\n case \"em\":\n return <em key={key}>{node.text}</em>;\n case \"link\":\n return (\n <React.Fragment key={key}>\n <a href={node.href} target=\"_blank\" rel=\"noopener noreferrer\" style={linkStyle}>\n {node.label === node.href ? compactUrl(node.label) : node.label}\n </a>\n {media && node.media && (\n <MediaPreview src={node.href} alt={node.label} kind={node.media} />\n )}\n </React.Fragment>\n );\n case \"blocked\":\n return (\n <span key={key} style={blockedStyle} title={`Link blocked: ${node.href}`}>\n {node.label}\n </span>\n );\n case \"image\":\n // With previews off, an image still has to say what it pointed at, so it\n // degrades to the same anchor a plain link would render.\n return media ? (\n <MediaPreview key={key} src={node.src} alt={node.alt} kind=\"image\" />\n ) : (\n <a key={key} href={node.src} target=\"_blank\" rel=\"noopener noreferrer\" style={linkStyle}>\n {node.alt || node.src}\n </a>\n );\n default:\n return <React.Fragment key={key}>{node.text}</React.Fragment>;\n }\n};\n\nexport const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({ text, media = true }) => {\n // Convert markdown to JSX elements\n const renderMarkdown = (input: string): React.ReactNode => {\n if (!input) return null;\n\n // Handle both actual newlines and \\n escape sequences\n // Replace literal \\n strings with actual newlines\n const normalizedText = input.replace(/\\\\n/g, '\\n');\n\n // Split by actual newlines\n const lines = normalizedText.split('\\n');\n\n return lines.map((line, lineIndex) => {\n // Bold, italic, links, images and bare URLs — see utils/inlineMarkdown.\n const processedLine = parseInlineMarkdown(line).map((node, i) =>\n renderNode(node, `${node.kind}-${lineIndex}-${i}`, media),\n );\n\n // Handle empty lines (creates paragraph spacing)\n if (line.trim() === \"\" && lineIndex > 0) {\n return <br key={`br-${lineIndex}`} />;\n }\n\n // Return the line with line break\n return (\n <React.Fragment key={`line-${lineIndex}`}>\n {processedLine.length > 0 ? processedLine : line}\n {lineIndex < lines.length - 1 && <br />}\n </React.Fragment>\n );\n });\n };\n\n return <div style={{ whiteSpace: 'pre-wrap', wordWrap: 'break-word' }}>{renderMarkdown(text)}</div>;\n};\n"]}
|
|
1
|
+
{"version":3,"file":"MarkdownRenderer.js","sourceRoot":"","sources":["../../../../../src/react/components/rtc-widget/components/MarkdownRenderer.tsx"],"names":[],"mappings":"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","sourcesContent":["import React from \"react\";\n\ninterface MarkdownRendererProps {\n text: string;\n}\n\nexport const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({ text }) => {\n // Convert markdown to JSX elements\n const renderMarkdown = (input: string): React.ReactNode => {\n if (!input) return null;\n\n // Handle both actual newlines and \\n escape sequences\n // Replace literal \\n strings with actual newlines\n const normalizedText = input.replace(/\\\\n/g, '\\n');\n \n // Split by actual newlines\n const lines = normalizedText.split('\\n');\n \n return lines.map((line, lineIndex) => {\n // Process markdown within each line\n const processLine = (text: string): React.ReactNode[] => {\n const parts: React.ReactNode[] = [];\n let remaining = text;\n let keyCounter = 0;\n\n // Process bold and italic markdown\n // Bold: **text** or __text__\n // Italic: *text* or _text_\n const markdownRegex = /(\\*\\*|__)(.*?)\\1|\\*([^*]+)\\*|_([^_]+)_/g;\n let lastIndex = 0;\n let match;\n\n while ((match = markdownRegex.exec(remaining)) !== null) {\n // Add text before the match\n if (match.index > lastIndex) {\n const textBefore = remaining.substring(lastIndex, match.index);\n if (textBefore) {\n parts.push(\n <React.Fragment key={`text-${lineIndex}-${keyCounter++}`}>\n {textBefore}\n </React.Fragment>\n );\n }\n }\n\n // Determine if it's bold or italic\n if (match[1] && match[2]) {\n // Bold text (** or __)\n parts.push(\n <strong key={`bold-${lineIndex}-${keyCounter++}`}>\n {match[2]}\n </strong>\n );\n } else if (match[3]) {\n // Italic text (*)\n parts.push(\n <em key={`italic-${lineIndex}-${keyCounter++}`}>\n {match[3]}\n </em>\n );\n } else if (match[4]) {\n // Italic text (_)\n parts.push(\n <em key={`italic-${lineIndex}-${keyCounter++}`}>\n {match[4]}\n </em>\n );\n }\n\n lastIndex = match.index + match[0].length;\n }\n\n // Add remaining text\n if (lastIndex < remaining.length) {\n const textAfter = remaining.substring(lastIndex);\n if (textAfter) {\n parts.push(\n <React.Fragment key={`text-${lineIndex}-${keyCounter++}`}>\n {textAfter}\n </React.Fragment>\n );\n }\n }\n\n return parts;\n };\n\n const processedLine = processLine(line);\n\n // Handle empty lines (creates paragraph spacing)\n if (line.trim() === \"\" && lineIndex > 0) {\n return <br key={`br-${lineIndex}`} />;\n }\n\n // Return the line with line break\n return (\n <React.Fragment key={`line-${lineIndex}`}>\n {processedLine.length > 0 ? processedLine : line}\n {lineIndex < lines.length - 1 && <br />}\n </React.Fragment>\n );\n });\n };\n\n return <div style={{ whiteSpace: 'pre-wrap', wordWrap: 'break-word' }}>{renderMarkdown(text)}</div>;\n};\n\n"]}
|
|
@@ -4,7 +4,7 @@ import { UserBubble, UserMessageContainer, MessageHeader, UserLabel, LogoWrapper
|
|
|
4
4
|
import { FaUser } from "react-icons/fa";
|
|
5
5
|
import { MarkdownRenderer } from "./MarkdownRenderer.js";
|
|
6
6
|
export const UserMessage = ({ message, timestamp, className, sender, }) => {
|
|
7
|
-
return (_jsxs(UserMessageContainer, { className: className, children: [_jsxs(UserBubble, { children: [sender && (_jsxs(MessageHeader, { children: [_jsx(LogoWrapper, { children: _jsx(FaUser, { size: 16, color: aeroTheme.colors.convai.dark }) }), _jsx(UserLabel, { children: sender === "User" ? "You" : sender })] })), _jsx(MarkdownRenderer, { text: message
|
|
7
|
+
return (_jsxs(UserMessageContainer, { className: className, children: [_jsxs(UserBubble, { children: [sender && (_jsxs(MessageHeader, { children: [_jsx(LogoWrapper, { children: _jsx(FaUser, { size: 16, color: aeroTheme.colors.convai.dark }) }), _jsx(UserLabel, { children: sender === "User" ? "You" : sender })] })), _jsx(MarkdownRenderer, { text: message })] }), timestamp && (_jsx("span", { style: {
|
|
8
8
|
fontSize: aeroTheme.typography.sizes.caption,
|
|
9
9
|
color: aeroTheme.colors.text.secondary,
|
|
10
10
|
marginTop: 4,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UserMessage.js","sourceRoot":"","sources":["../../../../../src/react/components/rtc-widget/components/UserMessage.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EACL,UAAU,EACV,oBAAoB,EACpB,aAAa,EACb,SAAS,EACT,WAAW,GACZ,MAAM,WAAW,CAAC;AAEnB,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AACxC,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAStD,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,EAC1B,OAAO,EACP,SAAS,EACT,SAAS,EACT,MAAM,GACW,EAAE,EAAE;IACrB,OAAO,CACL,MAAC,oBAAoB,IAAC,SAAS,EAAE,SAAS,aACxC,MAAC,UAAU,eACR,MAAM,IAAI,CACT,MAAC,aAAa,eACZ,KAAC,WAAW,cACV,KAAC,MAAM,IAAC,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,GAAI,GAC7C,EACd,KAAC,SAAS,cAAE,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,GAAa,IAC7C,CACjB,EACD,KAAC,gBAAgB,IAAC,IAAI,EAAE,OAAO,
|
|
1
|
+
{"version":3,"file":"UserMessage.js","sourceRoot":"","sources":["../../../../../src/react/components/rtc-widget/components/UserMessage.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EACL,UAAU,EACV,oBAAoB,EACpB,aAAa,EACb,SAAS,EACT,WAAW,GACZ,MAAM,WAAW,CAAC;AAEnB,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AACxC,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAStD,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,EAC1B,OAAO,EACP,SAAS,EACT,SAAS,EACT,MAAM,GACW,EAAE,EAAE;IACrB,OAAO,CACL,MAAC,oBAAoB,IAAC,SAAS,EAAE,SAAS,aACxC,MAAC,UAAU,eACR,MAAM,IAAI,CACT,MAAC,aAAa,eACZ,KAAC,WAAW,cACV,KAAC,MAAM,IAAC,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,GAAI,GAC7C,EACd,KAAC,SAAS,cAAE,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,GAAa,IAC7C,CACjB,EACD,KAAC,gBAAgB,IAAC,IAAI,EAAE,OAAO,GAAI,IACxB,EACZ,SAAS,IAAI,CACZ,eACE,KAAK,EAAE;oBACL,QAAQ,EAAE,SAAS,CAAC,UAAU,CAAC,KAAK,CAAC,OAAO;oBAC5C,KAAK,EAAE,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS;oBACtC,SAAS,EAAE,CAAC;oBACZ,SAAS,EAAE,UAAU;oBACrB,UAAU,EAAE,SAAS,CAAC,UAAU,CAAC,UAAU,CAAC,IAAI;iBACjD,YAEA,SAAS,GACL,CACR,IACoB,CACxB,CAAC;AACJ,CAAC,CAAC","sourcesContent":["import React from \"react\";\nimport { aeroTheme } from \"../styles\";\nimport {\n UserBubble,\n UserMessageContainer,\n MessageHeader,\n UserLabel,\n LogoWrapper,\n} from \"../styles\";\n\nimport { FaUser } from \"react-icons/fa\";\nimport { MarkdownRenderer } from \"./MarkdownRenderer\";\n\ninterface UserMessageProps {\n message: string;\n timestamp?: string;\n className?: string;\n sender?: string;\n}\n\nexport const UserMessage = ({\n message,\n timestamp,\n className,\n sender,\n}: UserMessageProps) => {\n return (\n <UserMessageContainer className={className}>\n <UserBubble>\n {sender && (\n <MessageHeader>\n <LogoWrapper>\n <FaUser size={16} color={aeroTheme.colors.convai.dark} />\n </LogoWrapper>\n <UserLabel>{sender === \"User\" ? \"You\" : sender}</UserLabel>\n </MessageHeader>\n )}\n <MarkdownRenderer text={message} />\n </UserBubble>\n {timestamp && (\n <span\n style={{\n fontSize: aeroTheme.typography.sizes.caption,\n color: aeroTheme.colors.text.secondary,\n marginTop: 4,\n alignSelf: \"flex-end\",\n fontFamily: aeroTheme.typography.fontFamily.body,\n }}\n >\n {timestamp}\n </span>\n )}\n </UserMessageContainer>\n );\n};\n\n"]}
|
package/dist/react/components/rtc-widget/components/conviComponents/VoiceModeOverlay.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VoiceModeOverlay.d.ts","sourceRoot":"","sources":["../../../../../../src/react/components/rtc-widget/components/conviComponents/VoiceModeOverlay.tsx"],"names":[],"mappings":"AAKA,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAEtC,UAAU,qBAAqB;IAC7B,QAAQ,EAAE,OAAO,CAAC;IAClB,SAAS,EAAE,OAAO,CAAC;IACnB,2EAA2E;IAC3E,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,GAAG,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;CACpB;AAED,eAAO,MAAM,gBAAgB,GAAI,0FAQ9B,qBAAqB,
|
|
1
|
+
{"version":3,"file":"VoiceModeOverlay.d.ts","sourceRoot":"","sources":["../../../../../../src/react/components/rtc-widget/components/conviComponents/VoiceModeOverlay.tsx"],"names":[],"mappings":"AAKA,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAEtC,UAAU,qBAAqB;IAC7B,QAAQ,EAAE,OAAO,CAAC;IAClB,SAAS,EAAE,OAAO,CAAC;IACnB,2EAA2E;IAC3E,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,GAAG,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;CACpB;AAED,eAAO,MAAM,gBAAgB,GAAI,0FAQ9B,qBAAqB,4CA8YvB,CAAC"}
|
|
@@ -6,91 +6,10 @@ import { iosTransitions } from "../../styles/framerConfig.js";
|
|
|
6
6
|
import { AudioVisualizer } from "../AudioVisualizer.js";
|
|
7
7
|
export const VoiceModeOverlay = ({ isActive, isTalking, isListening = false, convaiClient, characterImage, characterName = "Character", room, }) => {
|
|
8
8
|
const barsCount = 40; // Number of bars for user microphone waveform
|
|
9
|
-
|
|
10
|
-
// frame re-rendered the whole overlay (reconcile + style recalc +
|
|
11
|
-
// framer-motion filter retarget) ~60x/s — ~30ms main-thread tasks that
|
|
12
|
-
// visibly juddered the host page's 3D scene whenever voice mode was
|
|
13
|
-
// open. Levels live in a ref and the SVG paths are updated directly.
|
|
14
|
-
const audioLevelsRef = useRef(Array(barsCount).fill(0));
|
|
15
|
-
const waveCanvasRef = useRef(null);
|
|
9
|
+
const [audioLevels, setAudioLevels] = useState(Array(barsCount).fill(0));
|
|
16
10
|
const analyzerRef = useRef(null);
|
|
17
11
|
const animationFrameRef = useRef(undefined);
|
|
18
12
|
const audioContextRef = useRef(null);
|
|
19
|
-
// Paint the three waves onto a small canvas — no React, no SVG filters.
|
|
20
|
-
// (The previous SVG version re-rasterized a 35px drop-shadow blur every
|
|
21
|
-
// frame; that GPU paint alone pushed the host page to ~30fps.) Glow is
|
|
22
|
-
// approximated with a wide low-alpha understroke, which is ~free.
|
|
23
|
-
// Painting is THROTTLED to 30fps and skipped entirely while silent:
|
|
24
|
-
// every canvas update forces the compositor to re-blend the whole
|
|
25
|
-
// panel stack over the host WebGL canvas, so idle repaints are pure
|
|
26
|
-
// waste that judders the 3D scene.
|
|
27
|
-
const waveFrameParityRef = useRef(0);
|
|
28
|
-
const waveWasSilentRef = useRef(false);
|
|
29
|
-
const drawWaves = () => {
|
|
30
|
-
waveFrameParityRef.current ^= 1;
|
|
31
|
-
if (waveFrameParityRef.current)
|
|
32
|
-
return; // 30fps is plenty for a waveform
|
|
33
|
-
const canvas = waveCanvasRef.current;
|
|
34
|
-
if (!canvas)
|
|
35
|
-
return;
|
|
36
|
-
{
|
|
37
|
-
const lv = audioLevelsRef.current;
|
|
38
|
-
let sum = 0;
|
|
39
|
-
for (let i = 0; i < lv.length; i++)
|
|
40
|
-
sum += lv[i];
|
|
41
|
-
const silent = sum / lv.length < 0.012;
|
|
42
|
-
if (silent && waveWasSilentRef.current)
|
|
43
|
-
return; // static line already drawn
|
|
44
|
-
waveWasSilentRef.current = silent;
|
|
45
|
-
}
|
|
46
|
-
const dpr = Math.min(window.devicePixelRatio || 1, 2);
|
|
47
|
-
if (canvas.width !== 250 * dpr) {
|
|
48
|
-
canvas.width = 250 * dpr;
|
|
49
|
-
canvas.height = 80 * dpr;
|
|
50
|
-
}
|
|
51
|
-
const ctx = canvas.getContext("2d");
|
|
52
|
-
if (!ctx)
|
|
53
|
-
return;
|
|
54
|
-
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
55
|
-
ctx.clearRect(0, 0, 250, 80);
|
|
56
|
-
const audioLevels = audioLevelsRef.current;
|
|
57
|
-
const centerY = 40;
|
|
58
|
-
const waveWidth = 250;
|
|
59
|
-
const segments = barsCount;
|
|
60
|
-
const avgLevel = audioLevels.reduce((sum, level) => sum + level, 0) / audioLevels.length;
|
|
61
|
-
const waveColors = [
|
|
62
|
-
aeroTheme.colors.convai.light,
|
|
63
|
-
aeroTheme.colors.convai.light,
|
|
64
|
-
"#34d399",
|
|
65
|
-
];
|
|
66
|
-
ctx.lineCap = "round";
|
|
67
|
-
ctx.lineJoin = "round";
|
|
68
|
-
for (let waveIndex = 2; waveIndex >= 0; waveIndex--) {
|
|
69
|
-
ctx.beginPath();
|
|
70
|
-
for (let i = 0; i <= segments; i++) {
|
|
71
|
-
const x = (i / segments) * waveWidth;
|
|
72
|
-
const levelIndex = Math.floor((i / segments) * (audioLevels.length - 1));
|
|
73
|
-
const level = audioLevels[levelIndex] || 0;
|
|
74
|
-
const phase = (Date.now() / 500 + i * 0.2 + waveIndex * 1.5) % (Math.PI * 2);
|
|
75
|
-
const baseAmplitude = avgLevel > 0.05 ? 2 + waveIndex * 1.5 : 0;
|
|
76
|
-
const amplitude = baseAmplitude + level * (25 - baseAmplitude);
|
|
77
|
-
const y = centerY + Math.sin(phase) * amplitude;
|
|
78
|
-
if (i === 0)
|
|
79
|
-
ctx.moveTo(x, y);
|
|
80
|
-
else
|
|
81
|
-
ctx.lineTo(x, y);
|
|
82
|
-
}
|
|
83
|
-
// glow understroke scaled by level, then the crisp line on top
|
|
84
|
-
ctx.strokeStyle = waveColors[waveIndex];
|
|
85
|
-
ctx.globalAlpha = 0.18 * (0.3 + avgLevel);
|
|
86
|
-
ctx.lineWidth = (2.5 - waveIndex * 0.4) + 8 * avgLevel + 3;
|
|
87
|
-
ctx.stroke();
|
|
88
|
-
ctx.globalAlpha = 0.95 - waveIndex * 0.15;
|
|
89
|
-
ctx.lineWidth = 2.5 - waveIndex * 0.4;
|
|
90
|
-
ctx.stroke();
|
|
91
|
-
}
|
|
92
|
-
ctx.globalAlpha = 1;
|
|
93
|
-
};
|
|
94
13
|
// Setup audio analysis for USER microphone input ONLY
|
|
95
14
|
useEffect(() => {
|
|
96
15
|
if (!isActive) {
|
|
@@ -98,8 +17,7 @@ export const VoiceModeOverlay = ({ isActive, isTalking, isListening = false, con
|
|
|
98
17
|
if (animationFrameRef.current) {
|
|
99
18
|
cancelAnimationFrame(animationFrameRef.current);
|
|
100
19
|
}
|
|
101
|
-
|
|
102
|
-
drawWaves();
|
|
20
|
+
setAudioLevels(Array(barsCount).fill(0));
|
|
103
21
|
return;
|
|
104
22
|
}
|
|
105
23
|
let stream = null;
|
|
@@ -132,17 +50,17 @@ export const VoiceModeOverlay = ({ isActive, isTalking, isListening = false, con
|
|
|
132
50
|
const rms = Math.sqrt(sum / dataArray.length);
|
|
133
51
|
// Apply some scaling and clamping
|
|
134
52
|
const volume = Math.min(1, rms * 3); // Boost sensitivity
|
|
135
|
-
// Create left-to-right wave effect
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
53
|
+
// Create left-to-right wave effect
|
|
54
|
+
const levels = Array(barsCount)
|
|
55
|
+
.fill(0)
|
|
56
|
+
.map((_, i) => {
|
|
57
|
+
// Progressive wave from left to right
|
|
140
58
|
const position = i / barsCount; // 0 to 1 from left to right
|
|
141
59
|
const wavePhase = Date.now() / 300 + position * Math.PI * 2;
|
|
142
60
|
const waveVariation = Math.sin(wavePhase) * 0.15 + 0.85; // 0.7 to 1.0
|
|
143
|
-
|
|
144
|
-
}
|
|
145
|
-
|
|
61
|
+
return volume * waveVariation;
|
|
62
|
+
});
|
|
63
|
+
setAudioLevels(levels);
|
|
146
64
|
animationFrameRef.current = requestAnimationFrame(updateLevels);
|
|
147
65
|
};
|
|
148
66
|
updateLevels();
|
|
@@ -282,7 +200,45 @@ export const VoiceModeOverlay = ({ isActive, isTalking, isListening = false, con
|
|
|
282
200
|
width: "250px",
|
|
283
201
|
height: "80px",
|
|
284
202
|
position: "relative",
|
|
285
|
-
}, children: _jsx("
|
|
203
|
+
}, children: _jsx("svg", { width: "250", height: "80", style: {
|
|
204
|
+
overflow: "visible",
|
|
205
|
+
}, children: [0, 1, 2].map((waveIndex) => {
|
|
206
|
+
// Calculate wave path based on audio levels
|
|
207
|
+
const points = [];
|
|
208
|
+
const centerY = 40;
|
|
209
|
+
const waveWidth = 250;
|
|
210
|
+
const segments = barsCount;
|
|
211
|
+
// Calculate average audio level for glow intensity
|
|
212
|
+
const avgLevel = audioLevels.reduce((sum, level) => sum + level, 0) / audioLevels.length;
|
|
213
|
+
const glowIntensity = avgLevel * 35; // 0-35px glow (increased from 20)
|
|
214
|
+
// Green color variants for each wave
|
|
215
|
+
const waveColors = [
|
|
216
|
+
aeroTheme.colors.convai.light, // Bright green - front wave
|
|
217
|
+
aeroTheme.colors.convai.light, // Medium green - mid wave
|
|
218
|
+
"#34d399", // Medium-light green - back wave (lighter than dark)
|
|
219
|
+
];
|
|
220
|
+
for (let i = 0; i <= segments; i++) {
|
|
221
|
+
const x = (i / segments) * waveWidth;
|
|
222
|
+
const levelIndex = Math.floor((i / segments) * (audioLevels.length - 1));
|
|
223
|
+
const level = audioLevels[levelIndex] || 0;
|
|
224
|
+
// When idle (low audio), show straight line
|
|
225
|
+
// When active, create waves with increased amplitude
|
|
226
|
+
const phase = (Date.now() / 500 + i * 0.2 + waveIndex * 1.5) % (Math.PI * 2);
|
|
227
|
+
// Only add wave motion when there's audio
|
|
228
|
+
// Idle: amplitude ~0 (straight line)
|
|
229
|
+
// Active: amplitude up to 25px (increased from 15)
|
|
230
|
+
const baseAmplitude = avgLevel > 0.05 ? (2 + waveIndex * 1.5) : 0;
|
|
231
|
+
const amplitude = baseAmplitude + level * (25 - baseAmplitude);
|
|
232
|
+
const y = centerY + Math.sin(phase) * amplitude;
|
|
233
|
+
points.push(`${x},${y}`);
|
|
234
|
+
}
|
|
235
|
+
return (_jsx(motion.path, { d: `M ${points.join(" L ")}`, fill: "none", stroke: waveColors[waveIndex], strokeWidth: 2.5 - waveIndex * 0.4, strokeLinecap: "round", strokeLinejoin: "round", opacity: 0.95 - waveIndex * 0.15, animate: {
|
|
236
|
+
filter: `drop-shadow(0 0 ${glowIntensity}px ${waveColors[waveIndex]}) blur(${waveIndex * 0.3}px)`,
|
|
237
|
+
}, transition: {
|
|
238
|
+
duration: 0.1,
|
|
239
|
+
ease: "easeOut",
|
|
240
|
+
} }, waveIndex));
|
|
241
|
+
}) }) })) }), _jsxs(motion.div, { initial: { opacity: 0, y: 10 }, animate: { opacity: 1, y: 0 }, transition: { delay: 0.2, ...iosTransitions.fadePop }, children: [_jsx("div", { style: {
|
|
286
242
|
fontSize: "16px",
|
|
287
243
|
fontWeight: 600,
|
|
288
244
|
color: aeroTheme.colors.text.primary,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VoiceModeOverlay.js","sourceRoot":"","sources":["../../../../../../src/react/components/rtc-widget/components/conviComponents/VoiceModeOverlay.tsx"],"names":[],"mappings":"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","sourcesContent":["import React, { useEffect, useRef, useState } from \"react\";\nimport { motion, AnimatePresence } from \"framer-motion\";\nimport { aeroTheme } from \"../../styles/theme\";\nimport { iosTransitions } from \"../../styles/framerConfig\";\nimport { AudioVisualizer } from \"../AudioVisualizer\";\nimport { Room } from \"livekit-client\";\n\ninterface VoiceModeOverlayProps {\n isActive: boolean;\n isTalking: boolean;\n /** When true (user is speaking, from server), show \"Listening...\" label */\n isListening?: boolean;\n convaiClient?: any; // Reference to convai client for audio analysis\n characterImage?: string;\n characterName?: string;\n room?: Room | null;\n}\n\nexport const VoiceModeOverlay = ({\n isActive,\n isTalking,\n isListening = false,\n convaiClient,\n characterImage,\n characterName = \"Character\",\n room,\n}: VoiceModeOverlayProps) => {\n const barsCount = 40; // Number of bars for user microphone waveform\n // PERF: the waveform is driven OUTSIDE React. A setState per animation\n // frame re-rendered the whole overlay (reconcile + style recalc +\n // framer-motion filter retarget) ~60x/s — ~30ms main-thread tasks that\n // visibly juddered the host page's 3D scene whenever voice mode was\n // open. Levels live in a ref and the SVG paths are updated directly.\n const audioLevelsRef = useRef<number[]>(Array(barsCount).fill(0));\n const waveCanvasRef = useRef<HTMLCanvasElement | null>(null);\n const analyzerRef = useRef<AnalyserNode | null>(null);\n const animationFrameRef = useRef<number | undefined>(undefined);\n const audioContextRef = useRef<AudioContext | null>(null);\n\n // Paint the three waves onto a small canvas — no React, no SVG filters.\n // (The previous SVG version re-rasterized a 35px drop-shadow blur every\n // frame; that GPU paint alone pushed the host page to ~30fps.) Glow is\n // approximated with a wide low-alpha understroke, which is ~free.\n // Painting is THROTTLED to 30fps and skipped entirely while silent:\n // every canvas update forces the compositor to re-blend the whole\n // panel stack over the host WebGL canvas, so idle repaints are pure\n // waste that judders the 3D scene.\n const waveFrameParityRef = useRef(0);\n const waveWasSilentRef = useRef(false);\n const drawWaves = () => {\n waveFrameParityRef.current ^= 1;\n if (waveFrameParityRef.current) return; // 30fps is plenty for a waveform\n const canvas = waveCanvasRef.current;\n if (!canvas) return;\n {\n const lv = audioLevelsRef.current;\n let sum = 0;\n for (let i = 0; i < lv.length; i++) sum += lv[i];\n const silent = sum / lv.length < 0.012;\n if (silent && waveWasSilentRef.current) return; // static line already drawn\n waveWasSilentRef.current = silent;\n }\n const dpr = Math.min(window.devicePixelRatio || 1, 2);\n if (canvas.width !== 250 * dpr) {\n canvas.width = 250 * dpr;\n canvas.height = 80 * dpr;\n }\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) return;\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n ctx.clearRect(0, 0, 250, 80);\n const audioLevels = audioLevelsRef.current;\n const centerY = 40;\n const waveWidth = 250;\n const segments = barsCount;\n const avgLevel =\n audioLevels.reduce((sum, level) => sum + level, 0) / audioLevels.length;\n const waveColors = [\n aeroTheme.colors.convai.light,\n aeroTheme.colors.convai.light,\n \"#34d399\",\n ];\n ctx.lineCap = \"round\";\n ctx.lineJoin = \"round\";\n for (let waveIndex = 2; waveIndex >= 0; waveIndex--) {\n ctx.beginPath();\n for (let i = 0; i <= segments; i++) {\n const x = (i / segments) * waveWidth;\n const levelIndex = Math.floor((i / segments) * (audioLevels.length - 1));\n const level = audioLevels[levelIndex] || 0;\n const phase = (Date.now() / 500 + i * 0.2 + waveIndex * 1.5) % (Math.PI * 2);\n const baseAmplitude = avgLevel > 0.05 ? 2 + waveIndex * 1.5 : 0;\n const amplitude = baseAmplitude + level * (25 - baseAmplitude);\n const y = centerY + Math.sin(phase) * amplitude;\n if (i === 0) ctx.moveTo(x, y);\n else ctx.lineTo(x, y);\n }\n // glow understroke scaled by level, then the crisp line on top\n ctx.strokeStyle = waveColors[waveIndex];\n ctx.globalAlpha = 0.18 * (0.3 + avgLevel);\n ctx.lineWidth = (2.5 - waveIndex * 0.4) + 8 * avgLevel + 3;\n ctx.stroke();\n ctx.globalAlpha = 0.95 - waveIndex * 0.15;\n ctx.lineWidth = 2.5 - waveIndex * 0.4;\n ctx.stroke();\n }\n ctx.globalAlpha = 1;\n };\n\n // Setup audio analysis for USER microphone input ONLY\n useEffect(() => {\n if (!isActive) {\n // Clean up when user is not speaking\n if (animationFrameRef.current) {\n cancelAnimationFrame(animationFrameRef.current);\n }\n audioLevelsRef.current = Array(barsCount).fill(0);\n drawWaves();\n return;\n }\n\n let stream: MediaStream | null = null;\n\n const setupAudioAnalysis = async () => {\n try {\n // Get microphone stream\n stream = await navigator.mediaDevices.getUserMedia({ audio: true });\n\n // Create audio context and analyzer\n const audioContext = new (window.AudioContext ||\n (window as any).webkitAudioContext)();\n audioContextRef.current = audioContext;\n\n const analyzer = audioContext.createAnalyser();\n analyzer.fftSize = 256; // Better resolution for time domain\n analyzer.smoothingTimeConstant = 0.7;\n analyzerRef.current = analyzer;\n\n const source = audioContext.createMediaStreamSource(stream);\n source.connect(analyzer);\n\n const dataArray = new Uint8Array(analyzer.fftSize);\n\n const updateLevels = () => {\n if (!analyzerRef.current) return;\n\n // Use time domain data (waveform) instead of frequency\n analyzer.getByteTimeDomainData(dataArray);\n\n // Calculate RMS (Root Mean Square) for volume\n let sum = 0;\n for (let i = 0; i < dataArray.length; i++) {\n const normalized = (dataArray[i] - 128) / 128; // Center around 0\n sum += normalized * normalized;\n }\n const rms = Math.sqrt(sum / dataArray.length);\n\n // Apply some scaling and clamping\n const volume = Math.min(1, rms * 3); // Boost sensitivity\n\n // Create left-to-right wave effect — write into the ref and paint\n // the SVG directly (no setState: a per-frame React render here\n // visibly juddered the host page).\n const levels = audioLevelsRef.current;\n for (let i = 0; i < barsCount; i++) {\n const position = i / barsCount; // 0 to 1 from left to right\n const wavePhase = Date.now() / 300 + position * Math.PI * 2;\n const waveVariation = Math.sin(wavePhase) * 0.15 + 0.85; // 0.7 to 1.0\n levels[i] = volume * waveVariation;\n }\n drawWaves();\n animationFrameRef.current = requestAnimationFrame(updateLevels);\n };\n\n updateLevels();\n } catch (error) {\n console.error(\"Error setting up audio analysis:\", error);\n }\n };\n\n setupAudioAnalysis();\n\n return () => {\n if (animationFrameRef.current) {\n cancelAnimationFrame(animationFrameRef.current);\n }\n if (stream) {\n stream.getTracks().forEach((track) => track.stop());\n }\n if (audioContextRef.current) {\n audioContextRef.current.close();\n }\n };\n }, [isActive, barsCount]);\n\n const [isBotSpeaking, setIsBotSpeaking] = useState(false);\n\n return (\n <div\n style={{\n position: \"absolute\",\n top: \"50%\",\n left: \"50%\",\n transform: \"translate(-50%, -50%)\",\n textAlign: \"center\",\n padding: \"1rem\",\n zIndex: 10,\n pointerEvents: \"none\",\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n gap: \"2rem\",\n }}\n >\n {/* Character Avatar with Audio Visualizer */}\n <div\n style={{\n position: \"relative\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n isolation: \"isolate\", // Create stacking context\n }}\n >\n {/* Audio Visualizer - positioned absolutely behind the character */}\n {room && (\n <motion.div\n initial={{ opacity: 0, scale: 0.8 }}\n animate={{\n opacity: isBotSpeaking ? 1 : 0,\n scale: isBotSpeaking ? 1 : 0.8,\n }}\n transition={iosTransitions.fadePop}\n style={{\n position: \"absolute\",\n width: \"220px\",\n height: \"220px\",\n zIndex: -1, // Negative z-index to ensure it's behind\n pointerEvents: \"none\",\n }}\n >\n <AudioVisualizer\n room={room}\n size={220}\n color={aeroTheme.colors.convai.light}\n opacity={0.6}\n avatarRadius={110}\n barCount={48}\n onSpeakingChange={setIsBotSpeaking}\n />\n </motion.div>\n )}\n\n {/* Character Image - pops from header to center */}\n <motion.div\n key=\"center-avatar\" // Use key instead of layoutId for independent animation\n initial={{ scale: 0.8, opacity: 0 }}\n animate={{ scale: 1, opacity: 1 }}\n exit={{ scale: 0.8, opacity: 0 }}\n transition={iosTransitions.fadePop}\n style={{\n position: \"relative\",\n zIndex: 10, // Higher z-index to be clearly on top\n borderRadius: \"50%\",\n overflow: \"visible\", // Changed to visible to show glow\n boxShadow: \"0 8px 32px rgba(0, 0, 0, 0.3)\",\n background: \"white\",\n }}\n >\n {/* Animated liquid gradient - Layer 1 */}\n <motion.div\n animate={{\n rotate: [0, 360],\n scale: isBotSpeaking ? [1, 1.05, 1.02, 1.05, 1] : 1,\n }}\n transition={{\n rotate: { duration: 20, repeat: Infinity, ease: \"linear\" },\n scale: { duration: 3, repeat: Infinity, ease: \"easeInOut\" },\n }}\n style={{\n position: \"absolute\",\n width: \"126px\",\n height: \"126px\",\n borderRadius: \"50%\",\n background: `\n conic-gradient(\n from 0deg,\n ${aeroTheme.colors.convai.light}00 0%,\n ${aeroTheme.colors.convai.light}40 25%,\n ${aeroTheme.colors.convai.dark}60 50%,\n ${aeroTheme.colors.convai.light}40 75%,\n ${aeroTheme.colors.convai.light}00 100%\n )\n `,\n opacity: 0.8,\n filter: \"blur(8px)\",\n zIndex: -2,\n }}\n />\n\n {/* Animated liquid gradient - Layer 2 (counter-rotating) */}\n <motion.div\n animate={{\n rotate: [360, 0],\n scale: isBotSpeaking ? [1, 1.03, 1.06, 1.03, 1] : 1,\n }}\n transition={{\n rotate: { duration: 15, repeat: Infinity, ease: \"linear\" },\n scale: { duration: 2.5, repeat: Infinity, ease: \"easeInOut\" },\n }}\n style={{\n position: \"absolute\",\n width: \"126px\",\n height: \"126px\",\n borderRadius: \"50%\",\n background: `\n radial-gradient(\n ellipse 80% 100% at 30% 40%,\n ${aeroTheme.colors.convai.light}50,\n ${aeroTheme.colors.convai.light}30 40%,\n transparent 70%\n ),\n radial-gradient(\n ellipse 80% 100% at 70% 60%,\n ${aeroTheme.colors.convai.dark}50,\n ${aeroTheme.colors.convai.dark}30 40%,\n transparent 70%\n )\n `,\n opacity: 0.9,\n filter: \"blur(6px)\",\n zIndex: -1,\n }}\n />\n\n {/* Character container with border */}\n <div\n style={{\n position: \"relative\",\n borderRadius: \"50%\",\n overflow: \"hidden\",\n border: `3px solid rgba(255, 255, 255, 0.2)`,\n }}\n >\n {characterImage ? (\n <motion.img\n src={characterImage}\n alt={characterName}\n animate={{\n scale: isBotSpeaking ? 1.05 : 1,\n }}\n transition={{\n duration: 0.3,\n ease: \"easeInOut\",\n }}\n style={{\n width: \"120px\",\n height: \"120px\",\n objectFit: \"cover\",\n display: \"block\",\n }}\n />\n ) : (\n <div\n style={{\n width: \"120px\",\n height: \"120px\",\n backgroundColor: aeroTheme.colors.convai.light + \"20\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n fontSize: \"48px\",\n color: aeroTheme.colors.convai.light,\n fontWeight: 600,\n }}\n >\n {characterName[0]?.toUpperCase() || \"C\"}\n </div>\n )}\n </div>\n </motion.div>\n </div>\n\n {/* Sound Wave Lines - USER microphone ONLY */}\n <AnimatePresence>\n {isActive && (\n <motion.div\n initial={{ opacity: 0, y: 20 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: -20 }}\n transition={iosTransitions.fadePop}\n style={{\n width: \"250px\",\n height: \"80px\",\n position: \"relative\",\n }}\n >\n {/* Wave lines painted by drawWaves() (rAF-driven canvas) — the\n previous SVG + animated drop-shadow filter re-rasterized a\n blurred layer every frame and juddered the host page. */}\n <canvas\n ref={waveCanvasRef}\n style={{ width: \"250px\", height: \"80px\" }}\n />\n </motion.div>\n )}\n </AnimatePresence>\n\n {/* Status Text */}\n <motion.div\n initial={{ opacity: 0, y: 10 }}\n animate={{ opacity: 1, y: 0 }}\n transition={{ delay: 0.2, ...iosTransitions.fadePop }}\n >\n <div\n style={{\n fontSize: \"16px\",\n fontWeight: 600,\n color: aeroTheme.colors.text.primary,\n marginBottom: \"0.5rem\",\n }}\n >\n {isTalking\n ? `${characterName} Speaking...`\n : isListening || isActive\n ? \"Listening...\"\n : \"Voice Mode\"}\n </div>\n <div\n style={{\n fontSize: \"13px\",\n color: aeroTheme.colors.text.secondary,\n }}\n >\n {isListening || isActive || isTalking ? \"Audio active\" : \"Tap microphone to talk\"}\n </div>\n </motion.div>\n </div>\n );\n};\n"]}
|
|
1
|
+
{"version":3,"file":"VoiceModeOverlay.js","sourceRoot":"","sources":["../../../../../../src/react/components/rtc-widget/components/conviComponents/VoiceModeOverlay.tsx"],"names":[],"mappings":"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","sourcesContent":["import React, { useEffect, useRef, useState } from \"react\";\nimport { motion, AnimatePresence } from \"framer-motion\";\nimport { aeroTheme } from \"../../styles/theme\";\nimport { iosTransitions } from \"../../styles/framerConfig\";\nimport { AudioVisualizer } from \"../AudioVisualizer\";\nimport { Room } from \"livekit-client\";\n\ninterface VoiceModeOverlayProps {\n isActive: boolean;\n isTalking: boolean;\n /** When true (user is speaking, from server), show \"Listening...\" label */\n isListening?: boolean;\n convaiClient?: any; // Reference to convai client for audio analysis\n characterImage?: string;\n characterName?: string;\n room?: Room | null;\n}\n\nexport const VoiceModeOverlay = ({\n isActive,\n isTalking,\n isListening = false,\n convaiClient,\n characterImage,\n characterName = \"Character\",\n room,\n}: VoiceModeOverlayProps) => {\n const barsCount = 40; // Number of bars for user microphone waveform\n const [audioLevels, setAudioLevels] = useState<number[]>(\n Array(barsCount).fill(0) as number[],\n );\n const analyzerRef = useRef<AnalyserNode | null>(null);\n const animationFrameRef = useRef<number | undefined>(undefined);\n const audioContextRef = useRef<AudioContext | null>(null);\n\n // Setup audio analysis for USER microphone input ONLY\n useEffect(() => {\n if (!isActive) {\n // Clean up when user is not speaking\n if (animationFrameRef.current) {\n cancelAnimationFrame(animationFrameRef.current);\n }\n setAudioLevels(Array(barsCount).fill(0));\n return;\n }\n\n let stream: MediaStream | null = null;\n\n const setupAudioAnalysis = async () => {\n try {\n // Get microphone stream\n stream = await navigator.mediaDevices.getUserMedia({ audio: true });\n\n // Create audio context and analyzer\n const audioContext = new (window.AudioContext ||\n (window as any).webkitAudioContext)();\n audioContextRef.current = audioContext;\n\n const analyzer = audioContext.createAnalyser();\n analyzer.fftSize = 256; // Better resolution for time domain\n analyzer.smoothingTimeConstant = 0.7;\n analyzerRef.current = analyzer;\n\n const source = audioContext.createMediaStreamSource(stream);\n source.connect(analyzer);\n\n const dataArray = new Uint8Array(analyzer.fftSize);\n\n const updateLevels = () => {\n if (!analyzerRef.current) return;\n\n // Use time domain data (waveform) instead of frequency\n analyzer.getByteTimeDomainData(dataArray);\n\n // Calculate RMS (Root Mean Square) for volume\n let sum = 0;\n for (let i = 0; i < dataArray.length; i++) {\n const normalized = (dataArray[i] - 128) / 128; // Center around 0\n sum += normalized * normalized;\n }\n const rms = Math.sqrt(sum / dataArray.length);\n\n // Apply some scaling and clamping\n const volume = Math.min(1, rms * 3); // Boost sensitivity\n\n // Create left-to-right wave effect\n const levels = Array(barsCount)\n .fill(0)\n .map((_, i) => {\n // Progressive wave from left to right\n const position = i / barsCount; // 0 to 1 from left to right\n const wavePhase = Date.now() / 300 + position * Math.PI * 2;\n const waveVariation = Math.sin(wavePhase) * 0.15 + 0.85; // 0.7 to 1.0\n return volume * waveVariation;\n });\n\n setAudioLevels(levels);\n animationFrameRef.current = requestAnimationFrame(updateLevels);\n };\n\n updateLevels();\n } catch (error) {\n console.error(\"Error setting up audio analysis:\", error);\n }\n };\n\n setupAudioAnalysis();\n\n return () => {\n if (animationFrameRef.current) {\n cancelAnimationFrame(animationFrameRef.current);\n }\n if (stream) {\n stream.getTracks().forEach((track) => track.stop());\n }\n if (audioContextRef.current) {\n audioContextRef.current.close();\n }\n };\n }, [isActive, barsCount]);\n\n const [isBotSpeaking, setIsBotSpeaking] = useState(false);\n\n return (\n <div\n style={{\n position: \"absolute\",\n top: \"50%\",\n left: \"50%\",\n transform: \"translate(-50%, -50%)\",\n textAlign: \"center\",\n padding: \"1rem\",\n zIndex: 10,\n pointerEvents: \"none\",\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n gap: \"2rem\",\n }}\n >\n {/* Character Avatar with Audio Visualizer */}\n <div\n style={{\n position: \"relative\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n isolation: \"isolate\", // Create stacking context\n }}\n >\n {/* Audio Visualizer - positioned absolutely behind the character */}\n {room && (\n <motion.div\n initial={{ opacity: 0, scale: 0.8 }}\n animate={{\n opacity: isBotSpeaking ? 1 : 0,\n scale: isBotSpeaking ? 1 : 0.8,\n }}\n transition={iosTransitions.fadePop}\n style={{\n position: \"absolute\",\n width: \"220px\",\n height: \"220px\",\n zIndex: -1, // Negative z-index to ensure it's behind\n pointerEvents: \"none\",\n }}\n >\n <AudioVisualizer\n room={room}\n size={220}\n color={aeroTheme.colors.convai.light}\n opacity={0.6}\n avatarRadius={110}\n barCount={48}\n onSpeakingChange={setIsBotSpeaking}\n />\n </motion.div>\n )}\n\n {/* Character Image - pops from header to center */}\n <motion.div\n key=\"center-avatar\" // Use key instead of layoutId for independent animation\n initial={{ scale: 0.8, opacity: 0 }}\n animate={{ scale: 1, opacity: 1 }}\n exit={{ scale: 0.8, opacity: 0 }}\n transition={iosTransitions.fadePop}\n style={{\n position: \"relative\",\n zIndex: 10, // Higher z-index to be clearly on top\n borderRadius: \"50%\",\n overflow: \"visible\", // Changed to visible to show glow\n boxShadow: \"0 8px 32px rgba(0, 0, 0, 0.3)\",\n background: \"white\",\n }}\n >\n {/* Animated liquid gradient - Layer 1 */}\n <motion.div\n animate={{\n rotate: [0, 360],\n scale: isBotSpeaking ? [1, 1.05, 1.02, 1.05, 1] : 1,\n }}\n transition={{\n rotate: { duration: 20, repeat: Infinity, ease: \"linear\" },\n scale: { duration: 3, repeat: Infinity, ease: \"easeInOut\" },\n }}\n style={{\n position: \"absolute\",\n width: \"126px\",\n height: \"126px\",\n borderRadius: \"50%\",\n background: `\n conic-gradient(\n from 0deg,\n ${aeroTheme.colors.convai.light}00 0%,\n ${aeroTheme.colors.convai.light}40 25%,\n ${aeroTheme.colors.convai.dark}60 50%,\n ${aeroTheme.colors.convai.light}40 75%,\n ${aeroTheme.colors.convai.light}00 100%\n )\n `,\n opacity: 0.8,\n filter: \"blur(8px)\",\n zIndex: -2,\n }}\n />\n\n {/* Animated liquid gradient - Layer 2 (counter-rotating) */}\n <motion.div\n animate={{\n rotate: [360, 0],\n scale: isBotSpeaking ? [1, 1.03, 1.06, 1.03, 1] : 1,\n }}\n transition={{\n rotate: { duration: 15, repeat: Infinity, ease: \"linear\" },\n scale: { duration: 2.5, repeat: Infinity, ease: \"easeInOut\" },\n }}\n style={{\n position: \"absolute\",\n width: \"126px\",\n height: \"126px\",\n borderRadius: \"50%\",\n background: `\n radial-gradient(\n ellipse 80% 100% at 30% 40%,\n ${aeroTheme.colors.convai.light}50,\n ${aeroTheme.colors.convai.light}30 40%,\n transparent 70%\n ),\n radial-gradient(\n ellipse 80% 100% at 70% 60%,\n ${aeroTheme.colors.convai.dark}50,\n ${aeroTheme.colors.convai.dark}30 40%,\n transparent 70%\n )\n `,\n opacity: 0.9,\n filter: \"blur(6px)\",\n zIndex: -1,\n }}\n />\n\n {/* Character container with border */}\n <div\n style={{\n position: \"relative\",\n borderRadius: \"50%\",\n overflow: \"hidden\",\n border: `3px solid rgba(255, 255, 255, 0.2)`,\n }}\n >\n {characterImage ? (\n <motion.img\n src={characterImage}\n alt={characterName}\n animate={{\n scale: isBotSpeaking ? 1.05 : 1,\n }}\n transition={{\n duration: 0.3,\n ease: \"easeInOut\",\n }}\n style={{\n width: \"120px\",\n height: \"120px\",\n objectFit: \"cover\",\n display: \"block\",\n }}\n />\n ) : (\n <div\n style={{\n width: \"120px\",\n height: \"120px\",\n backgroundColor: aeroTheme.colors.convai.light + \"20\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n fontSize: \"48px\",\n color: aeroTheme.colors.convai.light,\n fontWeight: 600,\n }}\n >\n {characterName[0]?.toUpperCase() || \"C\"}\n </div>\n )}\n </div>\n </motion.div>\n </div>\n\n {/* Sound Wave Lines - USER microphone ONLY */}\n <AnimatePresence>\n {isActive && (\n <motion.div\n initial={{ opacity: 0, y: 20 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: -20 }}\n transition={iosTransitions.fadePop}\n style={{\n width: \"250px\",\n height: \"80px\",\n position: \"relative\",\n }}\n >\n <svg\n width=\"250\"\n height=\"80\"\n style={{\n overflow: \"visible\",\n }}\n >\n {/* Generate 3 wave lines with green shades */}\n {[0, 1, 2].map((waveIndex) => {\n // Calculate wave path based on audio levels\n const points: string[] = [];\n const centerY = 40;\n const waveWidth = 250;\n const segments = barsCount;\n \n // Calculate average audio level for glow intensity\n const avgLevel = audioLevels.reduce((sum, level) => sum + level, 0) / audioLevels.length;\n const glowIntensity = avgLevel * 35; // 0-35px glow (increased from 20)\n \n // Green color variants for each wave\n const waveColors = [\n aeroTheme.colors.convai.light, // Bright green - front wave\n aeroTheme.colors.convai.light, // Medium green - mid wave\n \"#34d399\", // Medium-light green - back wave (lighter than dark)\n ];\n \n for (let i = 0; i <= segments; i++) {\n const x = (i / segments) * waveWidth;\n const levelIndex = Math.floor((i / segments) * (audioLevels.length - 1));\n const level = audioLevels[levelIndex] || 0;\n \n // When idle (low audio), show straight line\n // When active, create waves with increased amplitude\n const phase = (Date.now() / 500 + i * 0.2 + waveIndex * 1.5) % (Math.PI * 2);\n \n // Only add wave motion when there's audio\n // Idle: amplitude ~0 (straight line)\n // Active: amplitude up to 25px (increased from 15)\n const baseAmplitude = avgLevel > 0.05 ? (2 + waveIndex * 1.5) : 0;\n const amplitude = baseAmplitude + level * (25 - baseAmplitude);\n const y = centerY + Math.sin(phase) * amplitude;\n \n points.push(`${x},${y}`);\n }\n \n return (\n <motion.path\n key={waveIndex}\n d={`M ${points.join(\" L \")}`}\n fill=\"none\"\n stroke={waveColors[waveIndex]}\n strokeWidth={2.5 - waveIndex * 0.4}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n opacity={0.95 - waveIndex * 0.15}\n animate={{\n filter: `drop-shadow(0 0 ${glowIntensity}px ${waveColors[waveIndex]}) blur(${waveIndex * 0.3}px)`,\n }}\n transition={{\n duration: 0.1,\n ease: \"easeOut\",\n }}\n />\n );\n })}\n </svg>\n </motion.div>\n )}\n </AnimatePresence>\n\n {/* Status Text */}\n <motion.div\n initial={{ opacity: 0, y: 10 }}\n animate={{ opacity: 1, y: 0 }}\n transition={{ delay: 0.2, ...iosTransitions.fadePop }}\n >\n <div\n style={{\n fontSize: \"16px\",\n fontWeight: 600,\n color: aeroTheme.colors.text.primary,\n marginBottom: \"0.5rem\",\n }}\n >\n {isTalking\n ? `${characterName} Speaking...`\n : isListening || isActive\n ? \"Listening...\"\n : \"Voice Mode\"}\n </div>\n <div\n style={{\n fontSize: \"13px\",\n color: aeroTheme.colors.text.secondary,\n }}\n >\n {isListening || isActive || isTalking ? \"Audio active\" : \"Tap microphone to talk\"}\n </div>\n </motion.div>\n </div>\n );\n};\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useConvaiClient.d.ts","sourceRoot":"","sources":["../../../src/react/hooks/useConvaiClient.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,YAAY,EACZ,aAAa,EACb,WAAW,EAEZ,MAAM,kBAAkB,CAAC;AAE1B;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,eAAe,GAC1B,SAAS,YAAY,KACpB,aAAa,GAAG;IACjB,oFAAoF;IACpF,2BAA2B,EAAE,WAAW,CACtC,aAAa,CAAC,6BAA6B,CAAC,CAC7C,CAAC;IACF,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,WAAW,EAAE,CAAC;IAC5B,YAAY,EAAE,OAAO,CAAC;IACtB,cAAc,EAAE,OAAO,CAAC;IACxB,mBAAmB,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"useConvaiClient.d.ts","sourceRoot":"","sources":["../../../src/react/hooks/useConvaiClient.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,YAAY,EACZ,aAAa,EACb,WAAW,EAEZ,MAAM,kBAAkB,CAAC;AAE1B;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,eAAe,GAC1B,SAAS,YAAY,KACpB,aAAa,GAAG;IACjB,oFAAoF;IACpF,2BAA2B,EAAE,WAAW,CACtC,aAAa,CAAC,6BAA6B,CAAC,CAC7C,CAAC;IACF,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,WAAW,EAAE,CAAC;IAC5B,YAAY,EAAE,OAAO,CAAC;IACtB,cAAc,EAAE,OAAO,CAAC;IACxB,mBAAmB,EAAE,OAAO,CAAC;CAwO9B,CAAC"}
|