Skip to content

Commit 052a042

Browse files
committed
feat: implement summarization
1 parent 41f6b0e commit 052a042

File tree

8 files changed

+80
-38
lines changed

8 files changed

+80
-38
lines changed

examples/react-example/src/Root.tsx

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
1-
import type {
2-
ChannelFilters,
3-
ChannelOptions,
4-
ChannelSort,
5-
} from 'stream-chat';
1+
import type { ChannelFilters, ChannelOptions, ChannelSort } from 'stream-chat';
62
import { AIChatApp } from './components/AIChatApp';
73
import { ThemeProvider } from './contexts/ThemeContext';
84

@@ -28,7 +24,7 @@ const filters: ChannelFilters = {
2824
type: 'messaging',
2925
archived: false,
3026
};
31-
const options: ChannelOptions = { limit: 5, presence: true, state: true };
27+
const options: ChannelOptions = { limit: 15, presence: true, state: true };
3228
const sort: ChannelSort = { pinned_at: 1, last_message_at: -1, updated_at: -1 };
3329

3430
const App = () => {

examples/react-example/src/api.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import type { Channel } from 'stream-chat';
2+
3+
const baseApiUrl = 'https://ai-sdk-server-0f347d455e2e.herokuapp.com';
4+
5+
export const startAiAgent = async (
6+
channel: Channel,
7+
model: string | File | null,
8+
platform: 'openai' | 'anthropic' | 'gemini' | 'xai' = 'openai',
9+
) => {
10+
return await fetch(`${baseApiUrl}/start-ai-agent`, {
11+
method: 'POST',
12+
headers: { 'Content-Type': 'application/json' },
13+
body: JSON.stringify({
14+
channel_id: channel.id,
15+
channel_type: channel.type,
16+
platform,
17+
model,
18+
}),
19+
});
20+
};
21+
22+
export const summarizeConversation = async (text: string): Promise<string> => {
23+
return fetch(`${baseApiUrl}/summarize`, {
24+
method: 'POST',
25+
headers: { 'Content-Type': 'application/json' },
26+
body: JSON.stringify({ text, platform: 'openai' }),
27+
})
28+
.then((res) => res.json())
29+
.then((json) => json.summary);
30+
};

examples/react-example/src/components/AIChatApp/AIChatApp.tsx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type {
88
import { Chat, useCreateChatClient, useChatContext } from 'stream-chat-react';
99
import { Sidebar } from '../Sidebar';
1010
import { ChatContainer } from '../ChatContainer';
11+
import { summarizeConversation } from '../../api';
1112
import './AIChatApp.scss';
1213

1314
interface AIChatAppProps {
@@ -81,6 +82,42 @@ const ChatContent = ({
8182
return () => window.removeEventListener('popstate', handlePopState);
8283
}, [client, channel?.id, setActiveChannel]);
8384

85+
// Watch for new messages and trigger summarization
86+
useEffect(() => {
87+
if (!channel) return;
88+
89+
const handleNewMessage = async () => {
90+
try {
91+
// Get channel state to check message count
92+
const state = channel.state;
93+
const messages = state.messages || [];
94+
const messageCount = messages.length;
95+
96+
// Only run if there are 5 or fewer messages
97+
if (messageCount > 5) return;
98+
99+
// Extract text from the last 5 messages
100+
const messageTexts = messages
101+
.slice(-5)
102+
.map((msg) => msg.text || '')
103+
.filter((text) => text.trim() !== '')
104+
.join('\n');
105+
106+
if (messageTexts.length === 0) return;
107+
108+
const summary = await summarizeConversation(messageTexts);
109+
await channel.update({ summary });
110+
} catch (error) {
111+
console.error('Failed to summarize conversation:', error);
112+
}
113+
};
114+
115+
const subscription = channel.on('message.new', handleNewMessage);
116+
return () => {
117+
subscription.unsubscribe();
118+
};
119+
}, [channel]);
120+
84121
const toggleSidebar = () => setIsSidebarOpen((prev) => !prev);
85122
const closeSidebar = () => setIsSidebarOpen(false);
86123

examples/react-example/src/components/ChatContainer/ChatContainer.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,7 @@ export const ChatContainer = ({ onToggleSidebar }: ChatContainerProps) => {
1717
initializeOnMount={false}
1818
EmptyPlaceholder={<EmptyState />}
1919
Message={MessageBubble}
20-
/* @ts-expect-error: `null` isn't in the types yet */
2120
UnreadMessagesNotification={null}
22-
/* @ts-expect-error: `null` isn't in the types yet */
2321
UnreadMessagesSeparator={null}
2422
>
2523
<TopNavBar onToggleSidebar={onToggleSidebar} />

examples/react-example/src/components/MessageInputBar/MessageInputBar.tsx

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,12 @@
1-
import { type Channel } from 'stream-chat';
21
import { AIMessageComposer } from '@stream-io/chat-react-ai';
32
import {
43
useChannelActionContext,
54
useChannelStateContext,
65
useMessageComposer,
76
} from 'stream-chat-react';
7+
import { startAiAgent } from '../../api.ts';
88
import './MessageInputBar.scss';
99

10-
const startAiAgent = async (channel: Channel, model: string | File | null) => {
11-
await fetch(
12-
'https://ai-sdk-server-0f347d455e2e.herokuapp.com/start-ai-agent',
13-
{
14-
method: 'POST',
15-
headers: {
16-
'Content-Type': 'application/json',
17-
},
18-
body: JSON.stringify({
19-
channel_id: channel.id,
20-
channel_type: channel.type,
21-
platform: 'openai',
22-
model,
23-
}),
24-
},
25-
);
26-
};
27-
2810
export const MessageInputBar = () => {
2911
const { updateMessage, sendMessage } = useChannelActionContext();
3012
const { channel } = useChannelStateContext();

examples/react-example/src/components/Sidebar/ChannelPreviewItem.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,17 @@ import { useChatContext } from 'stream-chat-react';
33
import './ChannelPreviewItem.scss';
44

55
export const ChannelPreviewItem = (props: ChannelPreviewProps) => {
6+
const { id, data } = props.channel;
67
const { setActiveChannel, channel: activeChannel } = useChatContext();
7-
const isActive = activeChannel?.id === props.channel.id;
8+
const isActive = activeChannel?.id === id;
89

910
return (
1011
<div
1112
className={`ai-demo-channel-preview ${isActive ? 'ai-demo-channel-preview--active' : ''}`}
1213
onClick={() => setActiveChannel(props.channel)}
1314
>
1415
<div className="ai-demo-channel-preview__text">
15-
{props.channel.data?.summary ?? props.channel.id}
16+
{data?.summary ?? 'New Chat'}
1617
</div>
1718
</div>
1819
);

examples/react-example/src/components/Sidebar/Sidebar.tsx

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { ChannelFilters, ChannelOptions, ChannelSort } from 'stream-chat';
2-
import { ChannelList, ChannelPreview } from 'stream-chat-react';
2+
import { ChannelList } from 'stream-chat-react';
33
import { SidebarHeader } from './SidebarHeader';
44
import { SidebarFooter } from './SidebarFooter';
55
import { ChannelPreviewItem } from './ChannelPreviewItem';
@@ -23,18 +23,16 @@ export const Sidebar = ({
2323
return (
2424
<>
2525
{/* Backdrop for mobile */}
26-
{isOpen && (
27-
<div className="ai-demo-sidebar-backdrop" onClick={onClose} />
28-
)}
26+
{isOpen && <div className="ai-demo-sidebar-backdrop" onClick={onClose} />}
2927

30-
<div className={`ai-demo-sidebar ${isOpen ? 'ai-demo-sidebar--open' : ''}`}>
28+
<div
29+
className={`ai-demo-sidebar ${isOpen ? 'ai-demo-sidebar--open' : ''}`}
30+
>
3131
<SidebarHeader />
3232
<div className="ai-demo-sidebar__list">
3333
<ChannelList
3434
setActiveChannelOnMount={false}
35-
Preview={(props) => (
36-
<ChannelPreview {...props} Preview={ChannelPreviewItem} />
37-
)}
35+
Preview={ChannelPreviewItem}
3836
filters={filters}
3937
options={options}
4038
sort={sort}

examples/react-example/src/components/TopNavBar/TopNavBar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export const TopNavBar = ({ onToggleSidebar }: TopNavBarProps) => {
1010
const { channel } = useChannelStateContext();
1111
const { theme, toggleTheme } = useTheme();
1212

13-
const conversationTitle = channel?.data?.summary ?? channel?.id ?? 'New Chat';
13+
const conversationTitle = channel?.data?.summary ?? 'New Chat';
1414

1515
return (
1616
<div className="ai-demo-top-nav">

0 commit comments

Comments
 (0)