前端：

import React, { useState, useRef, useEffect } from 'react';

interface Message {
  role: 'user' | 'assistant' | 'system';
  content: string;
}

const App: React.FC = () => {
  const [messages, setMessages] = useState<Message[]>(() => {
    const savedMessages = localStorage.getItem('chatMessages');
    return savedMessages ? JSON.parse(savedMessages) : [];
  });
  const [input, setInput] = useState<string>('');
  const [isLoading, setIsLoading] = useState<boolean>(false);
  const [typingText, setTypingText] = useState<string>('');
  const messagesEndRef = useRef<HTMLDivElement | null>(null);
  const [isGenerating, setIsGenerating] = useState<boolean>(false);
  const websocketRef = useRef<WebSocket | null>(null);

  const connectWebSocket = () => {
    websocketRef.current = new WebSocket('wss://4f10p6gw-o6hawtcd-02gk4443zpkw.vcc2.mcprev.cn/ws');
    
    websocketRef.current.onopen = () => {
      console.log('WebSocket connected');
    };

    websocketRef.current.onclose = () => {
      console.log('WebSocket disconnected');
      // 尝试重新连接
      setTimeout(connectWebSocket, 3000);
    };

    websocketRef.current.onerror = (error) => {
      console.error('WebSocket error:', error);
    };
  };

  useEffect(() => {
    connectWebSocket();

    return () => {
      if (websocketRef.current) {
        websocketRef.current.close();
      }
    };
  }, []);

  const scrollToBottom = () => {
    messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
  };

  useEffect(scrollToBottom, [messages, typingText]);

  useEffect(() => {
    localStorage.setItem('chatMessages', JSON.stringify(messages));
  }, [messages]);

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    if (input.trim() && !isLoading && websocketRef.current) {
      const newMessage: Message = { role: "user", content: input.trim() };
      setMessages(prevMessages => [...prevMessages, newMessage]);
      setInput('');
      setIsLoading(true);
      setIsGenerating(true);

      try {
        websocketRef.current.send(JSON.stringify([...messages, newMessage]));

        let accumulatedContent = '';
        websocketRef.current.onmessage = (event: MessageEvent) => {
          const data = JSON.parse(event.data);
          if (data.content === '[DONE]') {
            setMessages(prevMessages => [...prevMessages, { role: "assistant", content: data.full_response }]);
            setIsGenerating(false);
            setIsLoading(false);
            setTypingText('');
          } else {
            accumulatedContent += data.content;
            setTypingText(accumulatedContent);
          }
        };
      } catch (error) {
        console.error('Error:', error);
        setMessages(prevMessages => [...prevMessages, { role: "system", content: "抱歉,发生了错误。请稍后再试。" }]);
        setIsGenerating(false);
        setIsLoading(false);
      }
    }
  };

  const handleStopGeneration = () => {
    if (websocketRef.current) {
      websocketRef.current.close();
      setIsGenerating(false);
      setIsLoading(false);
      setTypingText('');
      // 不需要在这里重新连接，onclose 事件会处理重连
    }
  };

  const clearHistory = () => {
    setMessages([]);
    setTypingText('');
    localStorage.removeItem('chatMessages');
  };

  return (
    <div className="flex flex-col h-screen bg-gray-100">
      <div className="flex justify-between items-center p-4 bg-white">
        <h1 className="text-xl font-bold">AI Chat</h1>
        <button 
          onClick={clearHistory}
          className="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600 transition-colors"
        >
          清除历史
        </button>
      </div>
      <div className="flex-1 overflow-y-auto p-4 space-y-4">
        {messages.map((message, index) => (
          <div key={index} className={`flex ${message.role === "user" ? 'justify-end' : 'justify-start'}`}>
            <div className={`max-w-xs md:max-w-md lg:max-w-lg xl:max-w-xl rounded-lg p-3 ${
              message.role === "user" ? 'bg-blue-500 text-white' : 
              message.role === "assistant" ? 'bg-white' : 'bg-red-100'
            }`}>
              {message.content}
            </div>
          </div>
        ))}
        {typingText && (
          <div className="flex justify-start">
            <div className="bg-white rounded-lg p-3">
              {typingText}
              <span className="animate-pulse">|</span>
            </div>
          </div>
        )}
        {isLoading && !typingText && (
          <div className="flex justify-start">
            <div className="bg-gray-200 rounded-lg p-3">正在输入...</div>
          </div>
        )}
        <div ref={messagesEndRef} />
      </div>
      <form onSubmit={handleSubmit} className="p-4 bg-white">
        <div className="flex rounded-lg border border-gray-300 overflow-hidden">
          <input
            type="text"
            value={input}
            onChange={(e) => setInput(e.target.value)}
            className="flex-1 px-4 py-2 focus:outline-none"
            placeholder="输入消息..."
            disabled={isLoading}
          />
          {isGenerating ? (
            <button 
              type="button" 
              onClick={handleStopGeneration}
              className="px-4 py-2 bg-red-500 text-white hover:bg-red-600 transition-colors"
            >
              停止生成
            </button>
          ) : (
            <button 
              type="submit" 
              className={`px-4 py-2 text-white transition-colors ${
                isLoading || !input.trim() 
                  ? 'bg-gray-400 cursor-not-allowed' 
                  : 'bg-blue-500 hover:bg-blue-600'
              }`}
              disabled={isLoading || !input.trim()}
            >
              发送
            </button>
          )}
        </div>
      </form>
    </div>
  );
};

export default App;


后端：


from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi.middleware.cors import CORSMiddleware
from openai import OpenAI
from pydantic import BaseModel
from typing import List
import json
import os
from dotenv import load_dotenv

load_dotenv()

client = OpenAI(api_key=os.getenv("api_key"), base_url=os.getenv("base_url"))
app = FastAPI()

# 配置CORS
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],  # 允许所有源,生产环境中应该限制
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

class Message(BaseModel):
    role: str
    content: str

class ChatRequest(BaseModel):
    messages: List[Message]

@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
    await websocket.accept()
    try:
        while True:
            data = await websocket.receive_text()
            messages = json.loads(data)
            
            response = client.chat.completions.create(
                model="deepseek-chat",
                messages=messages,
                stream=True
            )
            
            full_response = ""
            for chunk in response:
                content = chunk.choices[0].delta.content
                if content:
                    full_response += content
                    await websocket.send_text(json.dumps({"content": content}))
            
            await websocket.send_text(json.dumps({"content": "[DONE]", "full_response": full_response}))
    except WebSocketDisconnect:
        print("WebSocket disconnected")

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)




出现以下问题，该如何解决：


停止生成后，继续发新的还是显示p.tsx:68 WebSocket is already in CLOSING or CLOSED state.
h