Skip to content

Commit 9c22726

Browse files
feat(react-sdk): component adjustments
1 parent d373b2f commit 9c22726

File tree

6 files changed

+39
-96
lines changed

6 files changed

+39
-96
lines changed

packages/react-sdk/src/components/ai-markdown.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ export const AIMarkdown: AIMarkdown = (props) => {
184184
const mergedToolComponents: ToolComponents = useMemo(
185185
() => ({
186186
chartjs: SuspendedChart,
187+
json: SuspendedChart,
187188
...props.toolComponents,
188189
}),
189190
[props.toolComponents],

packages/react-sdk/src/components/composer/ai-message-composer.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ import { StateStore } from '@stream-io/state-store';
1212
import { useStateStore } from '@stream-io/state-store/react-bindings';
1313

1414
import { AttachmentPreview } from './attachment-preview';
15-
import { useSpeechToText } from './use-speech-to-text';
15+
import {
16+
useSpeechToText,
17+
type UseSpeechToTextOptions,
18+
} from './use-speech-to-text';
1619
import { useStableCallback } from '../../hooks/use-stable-callback';
1720

1821
const nanoId = customAlphabet('abcdefghijklmnopqrstuvwxyz0123456789', 15);
@@ -325,7 +328,11 @@ const TextInput = (props: ComponentPropsWithoutRef<'input'>) => {
325328
);
326329
};
327330

328-
const SpeechToTextButton = (props: ComponentPropsWithoutRef<'button'>) => {
331+
const SpeechToTextButton = (
332+
props: ComponentPropsWithoutRef<'button'> & {
333+
options: UseSpeechToTextOptions;
334+
},
335+
) => {
329336
const { setText } = useText();
330337

331338
const { startListening, stopListening, isListening } = useSpeechToText({

packages/react-sdk/src/components/composer/use-speech-to-text.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ declare global {
6363
}
6464
}
6565

66-
interface UseSpeechToTextOptions {
66+
export type UseSpeechToTextOptions = {
6767
/**
6868
* Language for speech recognition (e.g., 'en-US', 'es-ES')
6969
* @default 'en-US'
@@ -92,7 +92,7 @@ interface UseSpeechToTextOptions {
9292
* Callback when an error occurs
9393
*/
9494
onError?: (error: string) => void;
95-
}
95+
};
9696

9797
export const useSpeechToText = (options: UseSpeechToTextOptions = {}) => {
9898
const {

packages/react-sdk/src/components/streaming-message.tsx

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { AIMarkdown } from './ai-markdown';
33
import { useEffect, useRef, useState } from 'react';
44

55
export type UseMessageTextStreamingProps = {
6-
streamingLetterIntervalMs?: number;
6+
letterIntervalMs?: number;
77
renderingLetterCount?: number;
88
text: string;
99
};
@@ -14,14 +14,14 @@ const DEFAULT_RENDERING_LETTER_COUNT = 2;
1414
/**
1515
* A hook that returns text in a streamed, typewriter fashion. The speed of streaming is
1616
* configurable.
17-
* @param {number} [streamingLetterIntervalMs=30] - The timeout between each typing animation in milliseconds.
17+
* @param {number} [letterIntervalMs=30] - The timeout between each typing animation in milliseconds.
1818
* @param {number} [renderingLetterCount=2] - The number of letters to be rendered each time we update.
1919
* @param {string} text - The text that we want to render in a typewriter fashion.
2020
* @returns {{ streamedMessageText: string }} - A substring of the text property, up until we've finished rendering the typewriter animation.
2121
*/
2222
export const useMessageTextStreaming = ({
2323
renderingLetterCount = DEFAULT_RENDERING_LETTER_COUNT,
24-
streamingLetterIntervalMs = DEFAULT_LETTER_INTERVAL,
24+
letterIntervalMs = DEFAULT_LETTER_INTERVAL,
2525
text,
2626
}: UseMessageTextStreamingProps): { streamedMessageText: string } => {
2727
const [streamedMessageText, setStreamedMessageText] = useState<string>(text);
@@ -37,21 +37,25 @@ export const useMessageTextStreaming = ({
3737
const newText = text.substring(0, newCursorValue);
3838
textCursor.current += newText.length - textCursor.current;
3939
setStreamedMessageText(newText);
40-
}, streamingLetterIntervalMs);
40+
}, letterIntervalMs);
4141

4242
return () => {
4343
clearInterval(interval);
4444
};
45-
}, [streamingLetterIntervalMs, renderingLetterCount, text]);
45+
}, [letterIntervalMs, renderingLetterCount, text]);
4646

4747
return { streamedMessageText };
4848
};
4949

50-
export const StreamingMessage = ({ text }: { text: string }) => {
50+
export const StreamingMessage = ({
51+
text,
52+
renderingLetterCount = DEFAULT_RENDERING_LETTER_COUNT,
53+
letterIntervalMs = DEFAULT_LETTER_INTERVAL,
54+
}: { text: string } & UseMessageTextStreamingProps) => {
5155
const streamedText = useMessageTextStreaming({
5256
text,
53-
renderingLetterCount: 3,
54-
streamingLetterIntervalMs: 20,
57+
renderingLetterCount,
58+
letterIntervalMs,
5559
}).streamedMessageText;
5660

5761
return (

packages/react-sdk/src/components/tools/charts/chartJsSchema.ts

Lines changed: 3 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ export const chartJsSchema = zod.object({
1212
'scatter',
1313
]),
1414
data: zod.object({
15-
labels: zod.array(zod.string()).optional(),
15+
labels: zod.array(zod.union([zod.string(), zod.number()])).optional(),
16+
xLabels: zod.array(zod.union([zod.string(), zod.number()])).optional(),
17+
yLabels: zod.array(zod.union([zod.string(), zod.number()])).optional(),
1618
datasets: zod.array(
1719
zod.object({
1820
label: zod.string().optional(),
@@ -48,77 +50,4 @@ export const chartJsSchema = zod.object({
4850
}),
4951
),
5052
}),
51-
options: zod
52-
.object({
53-
responsive: zod.boolean().optional(),
54-
maintainAspectRatio: zod.boolean().optional(),
55-
aspectRatio: zod.number().optional(),
56-
plugins: zod
57-
.object({
58-
title: zod
59-
.object({
60-
display: zod.boolean().optional(),
61-
text: zod.string().optional(),
62-
align: zod.enum(['start', 'center', 'end']).optional(),
63-
font: zod
64-
.object({
65-
size: zod.number().optional(),
66-
weight: zod.union([zod.string(), zod.number()]).optional(),
67-
family: zod.string().optional(),
68-
})
69-
.optional(),
70-
})
71-
.optional(),
72-
legend: zod
73-
.object({
74-
display: zod.boolean().optional(),
75-
position: zod.enum(['top', 'left', 'bottom', 'right']).optional(),
76-
})
77-
.optional(),
78-
tooltip: zod
79-
.object({
80-
enabled: zod.boolean().optional(),
81-
})
82-
.optional(),
83-
})
84-
.optional(),
85-
scales: zod
86-
.object({
87-
x: zod
88-
.object({
89-
display: zod.boolean().optional(),
90-
title: zod
91-
.object({
92-
display: zod.boolean().optional(),
93-
text: zod.string().optional(),
94-
})
95-
.optional(),
96-
grid: zod
97-
.object({
98-
display: zod.boolean().optional(),
99-
})
100-
.optional(),
101-
})
102-
.optional(),
103-
y: zod
104-
.object({
105-
display: zod.boolean().optional(),
106-
title: zod
107-
.object({
108-
display: zod.boolean().optional(),
109-
text: zod.string().optional(),
110-
})
111-
.optional(),
112-
grid: zod
113-
.object({
114-
display: zod.boolean().optional(),
115-
})
116-
.optional(),
117-
beginAtZero: zod.boolean().optional(),
118-
})
119-
.optional(),
120-
})
121-
.optional(),
122-
})
123-
.optional(),
12453
});

packages/react-sdk/src/components/tools/charts/charts.tsx

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
Scatter,
2323
} from 'react-chartjs-2';
2424
import type { ToolComponentProps } from '../../ai-markdown';
25+
import { chartJsSchema } from './chartJsSchema';
2526

2627
ChartJS.register(
2728
ArcElement,
@@ -48,28 +49,29 @@ const components = {
4849
} as const;
4950

5051
const Chart = ({ data, fallback }: ToolComponentProps) => {
51-
const parsedData = useMemo(() => {
52+
const parsedDataOrError = useMemo(() => {
5253
try {
53-
return JSON.parse(data);
54-
} catch {
55-
return new Error('Invalid JSON data for Chart.js');
54+
return chartJsSchema.parse(JSON.parse(data));
55+
} catch (error) {
56+
if (error instanceof Error) {
57+
error.message = `Error occured while parsing Chart.js data: ${error.message}`;
58+
return error;
59+
}
60+
return new Error('Unknown error occured while parsing Chart.js data');
5661
}
5762
}, [data]);
5863

59-
if (parsedData instanceof Error) {
64+
if (parsedDataOrError instanceof Error) {
6065
return fallback;
6166
}
6267

6368
const Component =
64-
components[parsedData.type as keyof typeof components] ??
69+
components[parsedDataOrError.type as keyof typeof components] ??
6570
components.unknown;
6671

6772
return (
6873
<div className="aicr__chart">
69-
<Component
70-
data={parsedData.data}
71-
options={{ ...parsedData.options, responsive: true }}
72-
/>
74+
<Component data={parsedDataOrError.data} options={{ responsive: true }} />
7375
</div>
7476
);
7577
};

0 commit comments

Comments
 (0)