1. 安装AI工具包

    https://ai-sdk.dev/

  2. 安装依赖项

    npm install ai @ai-sdk/react zod

    https://ai-sdk.dev/docs/getting-started/tanstack-start

  3. 配置.env

  4. app/api/chat/route.ts

    import { convertToModelMessages, streamText, UIMessage } from "ai";
    import { createDeepSeek } from "@ai-sdk/deepseek";
    
    // Allow streaming responses up to 30 seconds
    export const maxDuration = 30;
    const deepseek = createDeepSeek({
      apiKey: process.env.DEEPSEEK_API_KEY ?? "",
      baseURL: process.env.DEEPSEEK_BASE_URL ?? "",
    });
    export async function POST(req: Request) {
      const { messages }: { messages: UIMessage[] } = await req.json();
    
      const result = streamText({
        model: deepseek("deepseek-chat"),
        system: "You are a helpful assistant.",
        messages: await convertToModelMessages(messages),
      });
    
      return result.toUIMessageStreamResponse();
    }
    

    https://ai-sdk.dev/docs/ai-sdk-ui/chatbot

  5. 页面 page.tsx

    "use client";
    
    import { useChat } from "@ai-sdk/react";
    import { useEffect, useRef, useState } from "react";
    import EastIcon from "@mui/icons-material/East";
    export default function Page() {
      const [model, setModel] = useState("deepseek-v3");
      const [input, setInput] = useState("");
    
      const { messages, sendMessage } = useChat({});
    
      const handleChangeModel = () => {
        setModel(model === "deepseek-v3" ? "deepseek-r1" : "deepseek-v3");
      };
      const handleSubmit = () => {
        if (input.trim()) {
          sendMessage({ text: input });
          setInput("");
        }
      };
      const endRef = useRef<HTMLDivElement>(null);
      useEffect(() => {
        if (endRef.current) {
          endRef?.current?.scrollIntoView({ behavior: "smooth" });
        }
      }, [messages]);
    
      return (
        <div className="flex flex-col h-screen justify-between items-center">
          <div className="flex flex-col w-2/3 gap-8 overflow-y-auto justify-between flex-1">
            <div className="h-4"></div>
            <div className="flex flex-col gap-8 flex-1">
              {messages?.map((message) => (
                <div
                  key={message.id}
                  className={`rounded-lg flex flex-row ${message?.role === "assistant" ? "justify-start mr-18" : "justify-end ml-10"}`}
                >
                  <p
                    className={`inline-block p-2 rounded-lg ${message?.role === "assistant" ? "bg-blue-300" : "bg-slate-100"}`}
                  >
                    {message.parts.map((part, index) =>
                      part.type === "text" ? (
                        <span key={index}>{part.text}</span>
                      ) : null,
                    )}
                  </p>
                </div>
              ))}
            </div>
            <div className="h-4" ref={endRef}></div>
          </div>
    
          {/* 输入框 */}
          <div className="flex flex-col items-center justify-center mt-4 shadow-lg border-[1px] border-gray-300 h-32 rounded-lg w-2/3">
            <textarea
              className="w-full rounded-lg p-3 h-30 focus:outline-none"
              value={input}
              onChange={(e) => setInput(e.target.value)}
            ></textarea>
            <div className="flex flex-row items-center justify-between w-full h-12 mb-2">
              <div>
                <div
                  className={`flex flex-row items-center justify-center rounded-lg border-[1px] px-2 py-1 ml-2 cursor-pointer ${model === "deepseek-r1" ? "border-blue-300 bg-blue-200" : "border-gray-300"}`}
                  onClick={handleChangeModel}
                >
                  <p className="text-sm">深度思考(R1)</p>
                </div>
              </div>
              <div
                className="flex items-center justify-center border-2 mr-4 border-black p-1 rounded-full"
                onClick={handleSubmit}
              >
                <EastIcon></EastIcon>
              </div>
            </div>
          </div>
        </div>
      );
    }