Quiet
  • 主页
  • 归档
  • 分类
  • 标签
  • 链接
  • 关于我

bajiu

  • 主页
  • 归档
  • 分类
  • 标签
  • 链接
  • 关于我
Quiet主题
  • React

react实现类似GPT的对话应用

bajiu
前端

2024-11-09 23:29:00

最近AI类应用好像比较火,想起了之前在公司做过IM相关的实时通信和对话,感觉有很多类似的地方,主要实现一下类似GPT一样的前端功能,就当作代码练习了

前端脚手架用的vite、框架用的``、UI直接用Antd比较快。

启动项目

# 启动项目、选react和ts,swc反正几年前有坑现在不知道了别选就是了
yarn create vite
# 装相关
yarn add antd react-router-dom

创建聊天界面

界面分为三部分,两个组件一个界面,界面负责处理逻辑,两个组件分别展示数据列表以及输入数据。

主界面:

import React from "react";
import {Layout} from "antd";
import {Content, Header, Footer} from "antd/es/layout/layout";
import {IMessage} from "../types/IChat.ts";
import MessageList from "../component/MessageList.tsx";
import MessageInput from "../component/MessageInput.tsx";

const Chat: React.FC = () => {

    const [messages, setMessages] = React.useState<IMessage[]>([]);
    const [writing, setWriting] = React.useState<boolean>(false);

    const handleSendMessage = (text: string) => {
        const id = Date.now().toString();
        const newMessage:IMessage = { id, text, from: 'user' };
        setMessages((prev) => [...prev, newMessage]);


        // 模拟响应数据
        setTimeout(() => {
            setWriting(true);
            const botMessage: IMessage = { id: Date.now().toString(), text: ``, from: 'bot', typing: true  };
            setMessages((prev) => [...prev, botMessage]);

            let index = 0;
            const response = `我只会鹦鹉学舌,${text}`;
            // 模拟逐字回复
            const interval = setInterval(() =>{
                if(index < response.length) {
                    setMessages((prev:IMessage[]) => {
                        return prev.map((msg:IMessage) =>
                            msg.id == botMessage.id ? {
                                ...msg,
                                text: msg.text + response[index++]
                            }: msg
                        )
                    })
                } else {
                    // 回复完成,移除 typing 状态
                    setMessages((prev) =>
                        prev.map((msg) =>
                            msg.id === botMessage.id ? { ...msg, typing: false } : msg
                        )
                    );
                    setWriting(false);
                    clearInterval(interval);
                }
            }, 50)
        },1000)
    }



    return (
        <Layout style={{ height: `50vh`, width: '50vw',borderRadius: 10, overflow: 'hidden'}}>
            <Header style={{ color: 'white', textAlign: 'center' }}>GPT对话模拟</Header>
            <Content style={{ padding: '16px', overflow: 'auto' }}>
                <MessageList messages={messages} />
            </Content>
            <Footer style={{padding: '16px'}}>
                <MessageInput onSend={handleSendMessage} isWriting={writing}></MessageInput>
            </Footer>
        </Layout>
    )
}


export default Chat;

消息列表

import {IMessageListProps} from "../types/IChat.ts";
import {useEffect, useRef} from "react";
import {List, Typography} from "antd";

const MessageList:React.FC<IMessageListProps> = ({messages}) => {
    const listRef = useRef<HTMLDivElement>(null);

    // 自动滚到底部
    useEffect(() => {
        if(listRef.current){
            console.log("run this", listRef.current.scrollHeight)
            requestAnimationFrame(() => {
                listRef.current!.scrollTo({
                    top: listRef.current!.scrollHeight,
                    behavior: 'smooth',
                });
            });
            // listRef.current.scrollTo({
            //     top: listRef.current.scrollHeight,
            //     behavior: 'smooth' // 平滑滚动
            // })
        }
    }, [messages]);
    
    return (
        <div ref={listRef} style={{ maxHeight: 'calc(50vh - 200px)', overflowY: 'auto' }}>
            <List
                dataSource={messages}
                renderItem={(message) => (
                    <List.Item style={{ textAlign: message.from === 'user' ? 'right' : 'left', display: 'block' }}>
                        <Typography.Text
                            style={{
                                textAlign: 'left',
                                display: 'inline-block',
                                padding: '10px',
                                borderRadius: '12px',
                                background: message.from === 'user' ? '#1890ff' : '#f0f0f0',
                                color: message.from === 'user' ? '#fff' : '#000',
                            }}
                        >
                            {message.from === 'bot' &&
                                <strong>机器人:&nbsp;&nbsp;&nbsp;</strong>
                            }
                            {message.text}
                            {message.typing && (
                                <span
                                    style={{
                                        color: '#dadada',
                                        marginLeft: '6px',
                                        display: 'inline-block',
                                        animation: 'blink 1s step-start infinite',
                                    }}
                                >
                  |
                </span>
                            )}
                        </Typography.Text>
                    </List.Item>
                )}
            >
            </List>
        </div>
    )
}

export default MessageList;

消息输入

import {Button, Input, Space} from "antd";
import {useState} from "react";
import {IMessageInputProps} from "../types/IChat.ts";

const MessageInput:React.FC<IMessageInputProps> = ({onSend, isWriting}) => {
    const [text, setText] = useState<string>('');

    const handleSend = () => {
        if (text.trim()) {
            onSend(text.trim());
            setText('');
        }
    }
    
    return (
        <Space.Compact  style={{ width: 'calc(100% - 80px)'}}>
            <Input
                disabled={isWriting}
                placeholder="请输入内容..."
                value={text}
                onChange={(e) => setText(e.target.value)}
                onPressEnter={handleSend}
            ></Input>
            <Button disabled={isWriting} type="primary" onClick={handleSend} style={{fontSize: 20}}>
                <b>&rarr;</b>
            </Button>
        </Space.Compact>
    )
}

export default MessageInput;

react_chat_demo_0

随着文字的变多实时到最下面

react_chat_demo_1

大概就是这样一个demo,实现的功能就是输入之后持续拉到底部,然后输入完了之后才能继续说,还有一些比如说滚动效果啊,暂停啊啥的就先意念实现了,但有一个点的着重交代一下,就是listRef.current.scrollTo的失效问题,数据变换的时候还没有撑开格子,所以我们放到了下一帧的时候再监听变化,就能正常处理了,就这。

上一篇

微信小程序生成二维码

下一篇

自定义hooks之拖拽功能

©2024 By bajiu.