Skip to content

Commit 65bb739

Browse files
feat(react-sdk): move AI state indicator to the SDK
1 parent d43d41a commit 65bb739

File tree

8 files changed

+226
-204
lines changed

8 files changed

+226
-204
lines changed
Lines changed: 6 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,90 +1,7 @@
1-
.ai-demo-state-indicator {
2-
display: flex;
3-
align-items: center;
4-
justify-content: center;
5-
padding: 1rem 0;
6-
background-color: var(--ai-demo-bg-primary);
7-
8-
&__content {
9-
display: flex;
10-
align-items: center;
11-
gap: 0.75rem;
12-
padding: 0.5rem 0.75rem;
13-
background-color: var(--ai-demo-bg-tertiary);
14-
border-radius: 18px;
15-
max-width: 80%;
16-
}
17-
18-
&__dots {
19-
display: flex;
20-
align-items: center;
21-
gap: 0.375rem;
22-
}
23-
24-
&__dot {
25-
width: 6px;
26-
height: 6px;
27-
background-color: var(--ai-demo-text-secondary);
28-
border-radius: 50%;
29-
animation: ai-demo-thinking 1.4s ease-in-out infinite;
30-
31-
&:nth-child(1) {
32-
animation-delay: 0s;
33-
}
34-
35-
&:nth-child(2) {
36-
animation-delay: 0.2s;
37-
}
38-
39-
&:nth-child(3) {
40-
animation-delay: 0.4s;
41-
}
42-
}
43-
44-
&__text {
45-
color: var(--ai-demo-text-secondary);
46-
font-size: 0.875rem;
47-
font-weight: 500;
48-
animation: ai-demo-text-fade 2s ease-in-out infinite;
49-
}
50-
}
51-
52-
@keyframes ai-demo-thinking {
53-
0%,
54-
60%,
55-
100% {
56-
transform: scale(1);
57-
opacity: 0.4;
58-
}
59-
30% {
60-
transform: scale(1.4);
61-
opacity: 1;
62-
}
63-
}
64-
65-
@keyframes ai-demo-text-fade {
66-
0%,
67-
100% {
68-
opacity: 1;
69-
}
70-
45%,
71-
55% {
72-
opacity: 0.5;
73-
}
74-
}
75-
76-
/* Mobile responsiveness */
77-
@media (max-width: 768px) {
78-
.ai-demo-state-indicator {
79-
padding: 0.75rem 0;
80-
81-
&__content {
82-
gap: 0.5rem;
83-
padding: 0.375rem 0.625rem;
84-
}
85-
86-
&__text {
87-
font-size: 0.8125rem;
88-
}
89-
}
1+
:root {
2+
--aicr__bg-primary: var(--ai-demo-bg-primary);
3+
--aicr__bg-secondary: var(--ai-demo-bg-secondary);
4+
--aicr__bg-tertiary: var(--ai-demo-bg-tertiary);
5+
--aicr__text-primary: var(--ai-demo-text-primary);
6+
--aicr__text-secondary: var(--ai-demo-text-secondary);
907
}
Lines changed: 2 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,18 @@
11
'use client';
22

3-
import { useMemo } from 'react';
43
import {
54
AIStates,
65
useAIState,
76
useChannelStateContext,
87
} from 'stream-chat-react';
8+
import { AIStateIndicator as StateIndicator } from '@stream-io/chat-react-ai';
99
import './AIStateIndicator.scss';
1010

11-
const MESSAGES = [
12-
'Thinking really hard',
13-
'Putting on my thinking cap',
14-
'Consulting the AI gods',
15-
'Brewing up an answer',
16-
'Crunching the numbers',
17-
'Reading the digital tea leaves',
18-
'Firing up the neurons',
19-
'Summoning my inner genius',
20-
'Connecting the dots',
21-
'Working my magic',
22-
'Channeling my inner Einstein',
23-
'Cooking up something good',
24-
];
25-
2611
export const AIStateIndicator = () => {
2712
const { channel } = useChannelStateContext();
2813
const { aiState } = useAIState(channel);
29-
const messageIndex = useMemo(
30-
() => Math.floor(Math.random() * MESSAGES.length),
31-
// reset the thinking message everytime a new chat message arrives
32-
// eslint-disable-next-line
33-
[channel.state.last_message_at],
34-
);
3514

3615
if (![AIStates.Generating, AIStates.Thinking].includes(aiState)) return null;
3716

38-
return (
39-
<div className="ai-demo-state-indicator">
40-
<div className="ai-demo-state-indicator__content">
41-
<div className="ai-demo-state-indicator__dots">
42-
<span className="ai-demo-state-indicator__dot"></span>
43-
<span className="ai-demo-state-indicator__dot"></span>
44-
<span className="ai-demo-state-indicator__dot"></span>
45-
</div>
46-
<span className="ai-demo-state-indicator__text">
47-
{MESSAGES[messageIndex]}
48-
</span>
49-
</div>
50-
</div>
51-
);
17+
return <StateIndicator key={channel.state.last_message_at?.toString()} />;
5218
};

examples/react-chatbot/components/index.scss

Lines changed: 24 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -83,20 +83,18 @@
8383
--ai-demo-accent: #005fff;
8484
--ai-demo-accent-hover: #0d8968;
8585

86-
.aicr__syntax-highlighter-pre {
87-
--syntax-text: #e5e7eb;
88-
--syntax-selection-bg: #4a4a4a;
89-
--syntax-comment: #9ca3af;
90-
--syntax-punctuation: #d1d5db;
91-
--syntax-number: #fbbf24;
92-
--syntax-keyword: #a78bfa;
93-
--syntax-tag: #f87171;
94-
--syntax-string: #34d399;
95-
--syntax-function: #60a5fa;
96-
--syntax-cyan: #22d3ee;
97-
--syntax-error: #f87171;
98-
--syntax-whitespace: hsla(0, 0%, 100%, 0.1);
99-
}
86+
--aicr__syntax-text: #e5e7eb;
87+
--aicr__syntax-selection-bg: #4a4a4a;
88+
--aicr__syntax-comment: #9ca3af;
89+
--aicr__syntax-punctuation: #d1d5db;
90+
--aicr__syntax-number: #fbbf24;
91+
--aicr__syntax-keyword: #a78bfa;
92+
--aicr__syntax-tag: #f87171;
93+
--aicr__syntax-string: #34d399;
94+
--aicr__syntax-function: #60a5fa;
95+
--aicr__syntax-cyan: #22d3ee;
96+
--aicr__syntax-error: #f87171;
97+
--aicr__syntax-whitespace: hsla(0, 0%, 100%, 0.1);
10098

10199
color-scheme: dark;
102100
color: var(--ai-demo-text-primary);
@@ -124,20 +122,18 @@
124122
--ai-demo-accent: #005fff;
125123
--ai-demo-accent-hover: #0d8968;
126124

127-
.aicr__syntax-highlighter-pre {
128-
--syntax-text: #2e3440;
129-
--syntax-selection-bg: #d8dee9;
130-
--syntax-comment: #6b7280;
131-
--syntax-punctuation: #4b5563;
132-
--syntax-number: #d97706;
133-
--syntax-keyword: #8b5cf6;
134-
--syntax-tag: #dc2626;
135-
--syntax-string: #059669;
136-
--syntax-function: #2563eb;
137-
--syntax-cyan: #0891b2;
138-
--syntax-error: #ef4444;
139-
--syntax-whitespace: hsla(220, 14%, 30%, 0.15);
140-
}
125+
--aicr__syntax-text: #2e3440;
126+
--aicr__syntax-selection-bg: #d8dee9;
127+
--aicr__syntax-comment: #6b7280;
128+
--aicr__syntax-punctuation: #4b5563;
129+
--aicr__syntax-number: #d97706;
130+
--aicr__syntax-keyword: #8b5cf6;
131+
--aicr__syntax-tag: #dc2626;
132+
--aicr__syntax-string: #059669;
133+
--aicr__syntax-function: #2563eb;
134+
--aicr__syntax-cyan: #0891b2;
135+
--aicr__syntax-error: #ef4444;
136+
--aicr__syntax-whitespace: hsla(220, 14%, 30%, 0.15);
141137

142138
color-scheme: light;
143139
color: var(--ai-demo-text-primary);
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
'use client';
2+
3+
import { useMemo } from 'react';
4+
5+
const MESSAGES = [
6+
'Thinking really hard',
7+
'Putting on my thinking cap',
8+
'Consulting the AI gods',
9+
'Brewing up an answer',
10+
'Crunching the numbers',
11+
'Reading the digital tea leaves',
12+
'Firing up the neurons',
13+
'Summoning my inner genius',
14+
'Connecting the dots',
15+
'Working my magic',
16+
'Channeling my inner Einstein',
17+
'Cooking up something good',
18+
];
19+
20+
export const AIStateIndicator = () => {
21+
const messageIndex = useMemo(
22+
() => Math.floor(Math.random() * MESSAGES.length),
23+
[],
24+
);
25+
26+
return (
27+
<div className="aicr__state-indicator">
28+
<div className="aicr__state-indicator__content">
29+
<div className="aicr__state-indicator__dots">
30+
<span className="aicr__state-indicator__dot" />
31+
<span className="aicr__state-indicator__dot" />
32+
<span className="aicr__state-indicator__dot" />
33+
</div>
34+
<span className="aicr__state-indicator__text">
35+
{MESSAGES[messageIndex]}
36+
</span>
37+
</div>
38+
</div>
39+
);
40+
};

packages/react-sdk/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from './components/ai-markdown';
22
export * from './components/composer/ai-message-composer';
33
export * from './components/streaming-message';
4+
export * from './components/ai-state-indicator';
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
.aicr__state-indicator {
2+
display: flex;
3+
align-items: center;
4+
justify-content: center;
5+
padding: 1rem 0;
6+
background-color: var(--aicr__bg-primary);
7+
8+
&__content {
9+
display: flex;
10+
align-items: center;
11+
gap: 0.75rem;
12+
padding: 0.5rem 0.75rem;
13+
background-color: var(--aicr__bg-tertiary);
14+
border-radius: 18px;
15+
max-width: 80%;
16+
}
17+
18+
&__dots {
19+
display: flex;
20+
align-items: center;
21+
gap: 0.375rem;
22+
}
23+
24+
&__dot {
25+
width: 6px;
26+
height: 6px;
27+
background-color: var(--aicr__text-secondary);
28+
border-radius: 50%;
29+
animation: aicr__thinking 1.4s ease-in-out infinite;
30+
31+
&:nth-child(1) {
32+
animation-delay: 0s;
33+
}
34+
35+
&:nth-child(2) {
36+
animation-delay: 0.2s;
37+
}
38+
39+
&:nth-child(3) {
40+
animation-delay: 0.4s;
41+
}
42+
}
43+
44+
&__text {
45+
color: var(--aicr__text-secondary);
46+
font-size: 0.875rem;
47+
font-weight: 500;
48+
animation: aicr__text-fade 2s ease-in-out infinite;
49+
}
50+
}
51+
52+
@keyframes aicr__thinking {
53+
0%,
54+
60%,
55+
100% {
56+
transform: scale(1);
57+
opacity: 0.4;
58+
}
59+
30% {
60+
transform: scale(1.4);
61+
opacity: 1;
62+
}
63+
}
64+
65+
@keyframes aicr__text-fade {
66+
0%,
67+
100% {
68+
opacity: 1;
69+
}
70+
45%,
71+
55% {
72+
opacity: 0.5;
73+
}
74+
}
75+
76+
/* Mobile responsiveness */
77+
@media (max-width: 768px) {
78+
.aicr__state-indicator {
79+
padding: 0.75rem 0;
80+
81+
&__content {
82+
gap: 0.5rem;
83+
padding: 0.375rem 0.625rem;
84+
}
85+
86+
&__text {
87+
font-size: 0.8125rem;
88+
}
89+
}
90+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
11
@use './attachment-preview.scss';
22
@use './streaming-message.scss';
33
@use './ai-message-composer.scss';
4+
@use './ai-state-indicator.scss';
5+
6+
:root {
7+
--aicr__bg-primary: #121212;
8+
--aicr__bg-secondary: #1e1e1e;
9+
--aicr__bg-tertiary: #2c2c2c;
10+
--aicr__text-primary: #ffffff;
11+
--aicr__text-secondary: #aaaaaa;
12+
}

0 commit comments

Comments
 (0)