用Stream和OpenAI快速构建AI聊天助手!前端Vite+React集成stream-chat
SDK,后端Node.js用OpenAI API驱动。通过Stream Chat UI创建访客用户和临时频道,useWatchers
Hook监控AI机器人加入。轻松定制AI助手,解决用户疑问!
译自:Build an AI Chat Assistant With Stream and OpenAI
作者:Danny Adams
您是否曾经访问过某个网站,发现自己在与一个感觉几乎像人类的 AI 助手 聊天?您可以构建一个 AI 聊天助手,它驻留在您的网站上,了解业务,并实时帮助用户解决他们的疑问。
它将集成 Stream 用于 聊天基础设施 和 UI,以及 OpenAI API 用于 AI 驱动的对话。这个功能齐全的 AI 助手将根据公司的知识库进行定制,配有时尚的 UI 和浮动聊天小部件。
整个代码仓库可以在 这里 找到。
使用 Vite 快速搭建一个 React + TypeScript 应用程序。
yarn create vite frontend --template react-ts
cd frontend
这些包设置了聊天功能并处理聊天会话的唯一 ID。
yarn add stream-chat stream-chat-react uuid
-
stream-chat
: 核心 Stream Chat SDK。 -
stream-chat-react
: 用于聊天 UI 的预构建 React 组件。 -
uuid
: 用于生成唯一的频道名称或用户 ID。
启动开发服务器:
yarn dev
在 Stream 创建一个免费帐户,并在仪表板中设置一个新的应用程序。
图片 1
图片 2
从 聊天消息 > 概述,复制您的 应用程序访问密钥。
图片 3
在您的 frontend
文件夹中创建一个 .env
文件,并添加密钥:
VITE_STREAM_API_KEY=<your_key>
📌 注意:Vite 要求前端可访问的环境变量以 VITE_
开头。
在设置好前端项目并准备好 API 密钥后,项目就可以初始化 Stream Chat 客户端并渲染 聊天 UI。
首先,将 App.tsx
中的默认内容替换为基本布局和聊天组件的占位符:
import "stream-chat-react/dist/css/v2/index.css";
import "./App.css";
import AIChat from "./components/AIChat/AIChat";
const App = () => {
return (
<>
<section className="section">
<div className="container">
<h1>Welcome to StreamIO Chat</h1>
<p>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Sint omnis
ipsum, incidunt at quas dolorum a earum aspernatur quaerat amet
impedit vero rerum corrupti autem natus dolor sapiente modi nemo.
</p>
</div>
</section>
<AIChat />
</>
);
};
export default App;
在 src/components/AIChat/AIChat.tsx
创建一个新文件。这将使用 API 密钥初始化 Stream 客户端,并将其连接到 React SDK。
import { StreamChat } from "stream-chat";
import { Chat } from "stream-chat-react";
const apiKey = import.meta.env.VITE_STREAM_API_KEY;
if (!apiKey) {
throw new Error("Missing Stream API key");
}
const client = new StreamChat(apiKey);
const AIChat = () => {
if (!client) return <div>Setting up client & connection...</div>;
return <Chat client={client} />;
};
export default AIChat;
以下是正在发生的事情:
- 它使用 API 密钥初始化一个 StreamChat 实例。
- 如果客户端未准备好,它会显示一条加载消息。
- 客户端设置好后,它会从
stream-chat-react
传递到 Chat 组件。
此设置准备应用程序连接到聊天后端,但尚未创建用户和频道。接下来,构建聊天界面并将访客用户连接到临时频道以与助手交谈。
使用 Stream 的预构建 React 组件 构建聊天界面。
将 AIChat.tsx
的内容替换为以下内容。这将处理设置访客用户、创建临时频道和渲染完整的聊天界面。
import { StreamChat } from 'stream-chat';
import {
Chat,
Channel,
ChannelHeader,
MessageInput,
MessageList,
Thread,
Window,
} from 'stream-chat-react';
import 'stream-chat-react/dist/css/v2/index.css';
import { v4 as uuidv4 } from 'uuid';
import { useEffect, useState } from 'react';
const apiKey = import.meta.env.VITE_STREAM_API_KEY;
if (!apiKey) {
throw new Error('Missing Stream API key');
}
const client = new StreamChat(apiKey);
const AIChat = () => {
const [channel, setChannel] = useState(null);
const [thread, setThread] = useState(null);
const [isOpen, setIsOpen] = useState(false);
useEffect(() => {
const initChat = async () => {
const userId = uuidv4();
await client.setUser(
{
id: userId,
name: 'Guest User',
},
client.devToken(userId)
);
const channelId = 'ai-assistant';
const channel = client.channel('livestream', channelId, {
name: 'AI Assistant',
});
await channel.create();
setChannel(channel);
};
initChat();
}, []);
if (!channel) return <div>Setting up client & connection...</div>;
return (
<div className="AIChat">
<Chat client={client} theme="messaging light">
<Channel channel={channel}>
<Window>
<ChannelHeader />
<MessageList onThreadSelect={(thread) => setThread(thread)} />
<MessageInput />
</Window>
<Thread thread={thread} />
</Channel>
</Chat>
</div>
);
};
这里发生了什么?
- 创建了一个访客用户,因此访问者无需登录即可使用聊天。
- 使用
uuidv4()
创建唯一的聊天频道以避免冲突。 - Stream React 组件呈现完整的聊天界面:
-
ChannelHeader
– 聊天窗口的标题区域。 -
MessageList
– 显示对话。 -
MessageInput
– 用于发送新消息的输入字段。 -
AIStateIndicator
– 显示 AI 活动,例如“正在思考…”动画。 -
Thread
– 支持线程对话。
-
此时,UI 已经就位,但可能会出现一条错误消息:
Error: StreamChat 错误代码 17:GetOrCreateChannel failed with error: “User ‘guest-586486fd-d52e-4626-af0e-a480c83f95c6-guest_user’ with role ‘guest’ is not allowed to perform action CreateChannel in scope ‘messaging'”
这意味着不允许访客用户创建消息频道(或“聊天室”)。因此,在 Stream 仪表板中,转到“角色和权限”:
图片 4
对于“guest”角色和“messaging”范围,添加以下权限:
- Read Channel(读取频道)
- Create Message(创建消息)
- Create Channel(创建频道)
更新权限后,聊天 UI 现在应该可以成功加载,并且访客将拥有一个可用的聊天界面:
图片 5
要将聊天前端连接到 AI 模型(如 OpenAI),需要一个后端来处理请求、安全地通过第三方 API 进行身份验证并触发 AI 代理加入聊天。
Stream 为此提供了一个即用型的 Node.js 后端。
从项目根目录:
git clone https://github.com/GetStream/ai-assistant-nodejs
cd ai-assistant-nodejs
yarn install
此后端已预先配置为与 Stream 配合使用,并支持 OpenAI 和 Anthropic。要使用 OpenAI:
- 前往 https://platform.openai.com/.
- 登录或创建一个帐户。
- 前往 API 密钥: https://platform.openai.com/api-keys - 单击“+ Create new secret key”。
- 立即复制密钥。(您将无法再次看到它)。
在 ai-assistant-nodejs
文件夹中,创建一个 .env
文件并添加以下内容:
ANTHROPIC_API_KEY=not_needed
STREAM_API_KEY=insert_your_key
STREAM_API_SECRET=insert_your_secret
OPENAI_API_KEY=insert_your_key
OPENWEATHER_API_KEY=not_needed
默认情况下,后端使用 Anthropic。要切换到 OpenAI,请在 index.ts
或 app.ts
中找到此路由:
app.post('/start-ai-agent', async (req, res) => {
const {
channel_id,
channel_type = 'messaging',
platform = 'openai',
} = req.body;
确保 platform = 'openai'
。
现在后端可以在本地运行:
yarn dev
默认情况下,这将在 http://localhost:3000
上启动服务器。前端需要将 AI 机器人添加到聊天频道时,将调用此服务器。
接下来,在前端中连接它,以便 AI 在新用户打开聊天时自动加入。
为了确保 AI 代理在需要时自动加入聊天,请创建一个自定义 React Hook 来监视频道的观察者,并检查 AI 机器人是否存在。如果不存在,则触发后端以添加它。
在 frontend/src/custom-hooks/useWatchers.ts
创建一个新文件:
// Content of useWatchers.ts
import { useCallback, useEffect, useState } from "react";
import { Channel } from "stream-chat";
export const useWatchers = ({ channel }: { channel: Channel }) => {
const [watchers, setWatchers] = useState<string[] | undefined>(undefined);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<Error | null>(null);
const queryWatchers = useCallback(async () => {
setLoading(true);
setError(null);
try {
const result = await channel.query({ watchers: { limit: 5, offset: 0 } });
setWatchers(result?.watchers?.map((watcher) => watcher.id));
setLoading(false);
return;
} catch (err) {
console.error("An error has occurred while querying watchers: ", err);
setError(err as Error);
}
}, [channel]);
useEffect(() => {
queryWatchers();
}, [queryWatchers]);
useEffect(() => {
const watchingStartListener = channel.on("user.watching.start", (event) => {
const userId = event?.user?.id;
if (userId && userId.startsWith("ai-bot")) {
setWatchers((prevWatchers) => [
userId,
...(prevWatchers || []).filter((watcherId) => watcherId !== userId),
]);
}
});
const watchingStopListener = channel.on("user.watching.stop", (event) => {
const userId = event?.user?.id;
if (userId && userId.startsWith("ai-bot")) {
setWatchers((prevWatchers) => (prevWatchers || []).filter((watcherId) => watcherId !== userId));
}
});
return () => {
watchingStartListener.unsubscribe();
watchingStopListener.unsubscribe();
};
}, [channel]);
return { watchers, loading, error };
};
现在创建一个新的频道头部,用于检查 AI 是否在频道中,如果不在,则调用后端以添加它。
import { useChannelStateContext } from "stream-chat-react";
import { useWatchers } from "../../custom-hooks/useWatchers";
import { useEffect } from "react";
export default function MyChannelHeader() {
const { channel } = useChannelStateContext();
const { watchers } = useWatchers({ channel });
const aiInChannel = (watchers ?? []).filter((watcher) => watcher.includes("ai-bot")).length > 0;
useEffect(() => {
const addAIAgent = async () => {
if (!channel || aiInChannel) return;
const endpoint = "start-ai-agent";
await fetch(`http://127.0.0.1:3000/${endpoint}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ channel_id: channel.id }),
});
};
addAIAgent();
}, [aiInChannel, channel]);
return (
<div className="my-channel-header">
<h2>AI Assistant</h2>
{aiInChannel ? (
<span style={{ fontSize: 12, color: "gray" }}> I'm Stream's AI helper! </span>
) : (
<span style={{ fontSize: 14, color: "red" }}>Not connected to AI</span>
)}
</div>
);
}
在 AIChat.tsx
中,将默认的 ChannelHeader
替换为新的:
<Window>
<MyChannelHeader /> // Add this
<MessageList />
<AIStateIndicator />
<MessageInput />
</Window>
现在,每当用户打开聊天时,应用程序都会检查 AI 代理是否存在。如果不存在,它会调用后端并自动邀请 AI 进入聊天频道。 让我们测试一下:
太棒了——一个 AI 聊天!
但是,这并不是最终目标。 还需要做:
- 使其看起来像一个 AI 助手。
- 教会 AI 助手关于我们公司的信息,并指示它如何回复用户的消息,以便它实际解决用户的问题,以便它实际解决用户的问题。
首先,解决样式问题...
网站每个页面右下角的按钮将在单击时打开 AI 聊天。
首先,创建将切换聊天显示的按钮:
import classes from "./AIChat.module.css";
interface Props {
onClick: () => void;
showAIChat: boolean;
}
const ToggleAIChatButton = ({ onClick, showAIChat }: Props) => {
return (
<button onClick={onClick} className={classes.toggleAIChatButton}>
{showAIChat ? "⏷" : "💬"}
</button>
);
};
export default ToggleAIChatButton;
将 css 添加到 AIChat/AIChat.module.css
:
.toggleAIChatButton {
position: absolute;
bottom: 20px;
right: 20px;
width: 60px;
height: 60px;
background: black;
color: white;
border-radius: 50%;
font-size: 20px;
cursor: pointer;
border: none;
display: flex;
align-items: center;
justify-content: center;
transition: background 0.3s;
}
.toggleAIChatButton:hover {
background: #333;
}
现在创建一个新组件,位于 src/components/AIChat/AIChatWidget.tsx
,以有条件地显示 AI 聊天:
import { useState } from "react";
import AIChat from "./AIChat";
import ToggleAIChatButton from "./ToggleAIChatButton";
import classes from "./AIChat.module.css";
const AIChatWidget = () => {
const [showAIChat, setShowAIChat] = useState(false);
const toggleAIChat = () => {
setShowAIChat((prev) => !prev);
};
return (
<div>
<div
className={`${classes.chatPanel} ${!showAIChat ? classes.hidden : ""}`}
>
<AIChat />
</div>
<ToggleAIChatButton onClick={toggleAIChat} showAIChat={showAIChat} />
</div>
);
};
export default AIChatWidget;
将以下类添加到 AIChat.module.css
:
.chatPanel {
position: absolute !important;
bottom: 60px !important;
right: 0 !important;
height: 400px !important;
min-width: 320px !important;
max-width: 500px !important;
}
.hidden {
display: none;
}
图片 7
现在,每个页面底部都有一个按钮。
单击时,它会切换 AI 聊天。
太棒了!情况看起来好多了。但这仍然只是一些通用的 AI 聊天,比如 ChatGPT。我们希望它像 Stream 专家一样做出回应。
转到您的在后端,更改助理的姓名并指示其行为:OpenAIAgent.ts
this.assistant = await this.openai.beta.assistants.create({
name: 'Stream AI Assistant',
instructions: `
You are a helpful, professional AI assistant for Stream.io, a company providing scalable APIs and SDKs for building in-app chat, video, and activity feeds.
Assist users by answering their questions clearly and accurately.
If users ask about any of the following, respond with the corresponding information:
Support or contacting a human: Direct them to https://getstream.io/contact/support/
Help Center or documentation lookup: Point them to https://support.getstream.io/hc/en-us
Pricing details: Guide them to https://getstream.io/chat/pricing/
Company/team info: Refer them to https://getstream.io/team/
Documentation-specific queries:
Chat API/docs: https://getstream.io/chat/docs/
Video & audio docs: https://getstream.io/video/docs
Activity Feed docs: https://getstream.io/activity-feeds/docs
Moderation docs: https://getstream.io/moderation/docs
For anything related to configuring Stream, accessing API keys, managing users, teams, or billing, direct users to https://dashboard.getstream.io/
If you're unsure or the topic is not covered, say: "I'm not certain about that, but I recommend reaching out to our support team."
Keep your responses friendly, accurate, and focused on helping users efficiently navigate GetStream.io's tools and resources.
`
});
使用 yarn dev
重新启动服务器,让我们看看会发生什么:
图片 8
就这样,Stream 就有了一个 AI 助手。
您现在有了一个可用的示例,可以处理访客用户、管理聊天频道并连接到 AI 后端。
有了这个基础,您可以将自己的 AI 助手变为现实,并将智能对话直接嵌入到您的网站中。