react hooks 实现一个简单的GPT 对话功能

某司面试题,用指定技术栈 实现一个GPT 对话功能

指定技术栈react hook、typescript、 material-ui 、 openrouter

material-ui 第一次用 不同版本方式不同,遇到问题也不少基本都是导入问题

1、消息组件

// Message.tsx


import React from 'react';  
import {useStyles}   from './styles';  
import RenderContent from './content';
interface ChatMessageProps {  
  sender: string;  
  content: string;  
  isSender?: boolean;  
}  
const ChatMessage: React.FC<ChatMessageProps> = ({ sender, content='', isSender = false }) => {  
  const coImg = require('./assets/co.png')
  const gptImg = require('./assets/gpt.png')
  const {classes} = useStyles()
  
  return (  
    <div className={classes.message}>  
    <div className={classes.item}>
        <div className={classes.userIcon}>
            <img src={isSender ?coImg:gptImg} className={classes.Icon}></img>
            <span className={classes.userName}>{ isSender ? 'Me' :sender }</span>
        </div>
        <div>
            {/* <p className={classes.content}>{content}</p>   */}
            <RenderContent message={content}/>
        </div>    
    </div>
    </div>  
  );  
};  
  
export default ChatMessage;

content.tsx

// conyent.tsx
import React from 'react'
import {useStyles}   from './styles';
interface propsType{
    message:string
}

const RenderContent:React.FC<propsType>=({message})=>{
    const {classes} =useStyles()
    const hasTags = /<[^>]+>/.test(message);  
    if( hasTags){
        return (<div className={classes.content} dangerouslySetInnerHTML={{ __html: message }} />)
    }else {
        return (<p className={classes.content}>{message}</p>)
    }
}
export default RenderContent

3、聊天窗口

// ChatWindow.tsx  
import React, { useState,useRef,useEffect,useCallback } from 'react';  
import Message from './Message';  
import InputBase from '@mui/material/InputBase';
import { Input } from '@mui/material';
import IconButton from '@mui/material/IconButton';
import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward';
import Paper from '@mui/material/Paper';
import Icon from '@mui/material/Icon';
import axios from 'axios';
import {useStyles} from './styles'

interface ChatMessageProps {  
    sender: string;  
    content: string;  
    isSender?: boolean;  
  }  

const ChatBox:React.FC= () => {  
  const [messages, setMessages] = useState('');  
  const [allMsg,setAllmsg] = useState<ChatMessageProps []> ([])
  const containerScroll = useRef<HTMLDivElement>(null)
  const {classes} = useStyles()

const scrollToBottom = () => {  
  if (containerScroll.current) {  
    containerScroll.current.scrollTop = containerScroll.current.scrollHeight;  
  }  
}; 
const addMessage = useCallback(
  (info:ChatMessageProps) => { 
    setAllmsg((data)=>{
      return [...data, info];
    }) 
    scrollToBottom();  
  },[allMsg]
)
  // 渲染后滚动到底部  
  useEffect(() => {  
    scrollToBottom();  
  }, [allMsg]); 
  const handleSendMessage =  () => {  
    const currMsg = {sender:'Me',content:messages,isSender:true}
    addMessage(currMsg)
    setMessages('');  
           axios.post(  
                'https://openrouter.ai/api/v1/chat/completions',  
                {
                    "model": "mistralai/mistral-7b-instruct:free",
                    "messages": [
                      {"role": "user", "content": messages},
                    ],
                  },  
                {  
                  headers: {  
                    "Authorization": `openrouter key`,
                    "Content-Type": "application/json"
                  },  
                }  
              ).then(res=>{
                const result = res.data.choices[0].message;
                const aiResData= {sender:'GPT',content:result.content,isSender:false}
                addMessage(aiResData)
              })
  }
   
  const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {  
    setMessages(event.target.value);  
  };  
  return (  
    <div className={classes.root}>  
      <div className={classes.messageList}  ref={containerScroll}>  
        {allMsg.map((message, index) => (  
          <Message key={index} {...message} />  
        ))}
      </div>
          <Paper
          component="form"
          sx={{ p: '2px 4px', display: 'flex', alignItems: 'center', width: 600 }}
        >
          <InputBase
            sx={{ ml: 1, flex: 1 }}
            value={messages}  
            onChange={handleInputChange} 
            placeholder="Message ChatGPT"
            inputProps={{ 'aria-label': 'search google maps' }}
          />
          <IconButton color="primary" sx={{ p: '10px' }} onClick={handleSendMessage} aria-label="directions">
          <ArrowUpwardIcon></ArrowUpwardIcon>
          </IconButton>
        </Paper>
   
    </div>)
}

export default ChatBox

补充:可用 useCallback 钩子优化一下消息队列

4、样式

// import {makeStyles} from '@mui/material/styles';
import { makeStyles } from 'tss-react/mui'
export const useStyles=makeStyles()((theme) => {
    return {
        root:{
            height:'100%',
            width:'100vh',
            display:'flex',
            flexDirection: 'column',  
            alignItems: 'center',  
            justifyContent: 'center', 
            backgroundColor:'#fff',
            padding:'20px'
        },
        message:{
            display:'flex',
            flexDirection: 'column',  
            alignItems: 'center',  
            justifyContent: 'center',
            fontSize:'14px',
            color:'#2e3b4c'
        },
        messageList:{
            height: 'calc(100vh - 120px)',
            overflowY: 'auto',  
            padding: '20px', 
            width:'100%',
            bosSizing:'border-box'
        },
        input:{
            display:'flex',
            padding:'5px',
            border: '1px solid',
            borderRadius:'4px',
            fontSize:'14px',
            marginBottom:'10px'
        },
        userIcon:{
            display:'flex',
            height: '40px',
            alignItems: 'center'
        },
        userName:{
            textAlign:'left',
            fontSize:'16px',
            marginLeft:'10px'
        },
        content:{
            margin:'0',
            textAlign:'left',
            padding:'0 40px'
        },
        inputBox:{
            display:'flex'
        },
        item:{
            display: 'flex',
            width: '100%',
            margin: '10px',
            flexDirection: 'column',
          },
        Icon:{
            width: '28px',
            height:'28px',
            display: 'inline-block',
            backgroundColor: 'skyblue'
          },
        inputField:{
            width: '100%',
            padding: '5px',
            border: '1px solid',
            borderRadius: '4px',
            fontSize: '14px',
            marginBottom: '10px',
            outline: 'none'
          }


    };
  });

样式引用版本不同 方式不同, 这里话的时间还不少

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值