ChatAI展示

Next.js是什么
是什么不重要,重要的是它能干什么!
- 包含前后端(这不是就全栈吗,听起来可怕,不用担心,就算是小白,只要会CV大法就行)
- 急速开发(会点开发的同学,在敲代码提示到时蛮多的,用习惯了之后开发效率起飞)
为什么要选next.js开发AI
因为在众多 Web 框架中,Next.js 是最接近 AI "原生友好"的。
CV三部曲
1. 安装环境
- 安装nodejs环境,版本去官网上去安装最新的稳定版本。

- 安装nextjs脚手架,创建文件夹,
npx create-next-app@latest,配置全部选择默认。

- 去DeepSeek买API或者OpenAI买API额度,下图是DeepSeek,支持下国产。创建一个API keys,记得保存key,因为只能创建的时候复制Key。

2. 创建文件,复制代码
- 在
app目录下创建api文件夹,在api目录下创建chat目录,在chat目录创建两个文件,key.ts和route.ts

key.ts
js
// 替换为你在DeepSeek申请的key
export const DEEPSEEK_API_KEY = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
route.ts
js
import { NextRequest } from "next/server";
import { streamText,convertToModelMessages } from 'ai'
import { createDeepSeek } from "@ai-sdk/deepseek";
import { DEEPSEEK_API_KEY } from "./key"
const deepSeek = createDeepSeek({
apiKey: DEEPSEEK_API_KEY, //设置API密钥
});
export async function POST(req: NextRequest) {
const { messages } = await req.json(); //获取请求体
//这里为什么接受messages 因为我们使用前端的useChat 他会自动注入这个参数,所有可以直接读取
const result = streamText({
model: deepSeek('deepseek-chat'), //使用deepseek-chat模型
messages:convertToModelMessages(messages), //转换为模型消息
//前端传过来的额messages不符合sdk格式所以需要convertToModelMessages转换一下
//转换之后的格式:
system: '新手小白', //系统提示词
});
return result.toUIMessageStreamResponse() //返回流式响应
}
page.tsx
js
'use client';
import { useState, useRef, useEffect } from 'react';
import { Button } from '@/components/ui/button';
import { Textarea } from '@/components/ui/textarea';
import { useChat } from '@ai-sdk/react';
export default function HomePage() {
const [input, setInput] = useState(''); //输入框的值
const messagesEndRef = useRef<HTMLDivElement>(null); //获取消息结束的ref
//useChat 内部封装了流式响应 默认会向/api/chat 发送请求
const { messages, sendMessage } = useChat({
onFinish: () => {
setInput('');
}
});
// 自动滚动到底部
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messages]);
//回车发送消息
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
if (input.trim()) {
sendMessage({ text: input });
}
}
};
return (
<div className='flex flex-col h-screen bg-linear-to-br from-blue-50 via-white to-purple-50'>
{/* 头部标题 */}
<div className='bg-white/80 backdrop-blur-sm shadow-sm border-b border-gray-200'>
<div className='max-w-4xl mx-auto px-6 py-4'>
<h1 className='text-2xl font-bold bg-linear-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent'>
AI 智能助手
</h1>
<p className='text-sm text-gray-500 mt-1'>随时为您解答问题</p>
</div>
</div>
{/* 消息区域 */}
<div className='flex-1 overflow-y-auto px-4 py-6'>
<div className='max-w-4xl mx-auto space-y-4'>
{messages.length === 0 ? (
<div className='flex flex-col items-center justify-center h-full text-center py-20'>
<div className='bg-linear-to-br from-blue-100 to-purple-100 rounded-full p-6 mb-4'>
<svg className='w-12 h-12 text-blue-600' fill='none' stroke='currentColor' viewBox='0 0 24 24'>
<path strokeLinecap='round' strokeLinejoin='round' strokeWidth={2} d='M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z' />
</svg>
</div>
<h2 className='text-xl font-semibold text-gray-700 mb-2'>开始对话</h2>
<p className='text-gray-500'>输入您的问题,我会尽力帮助您</p>
</div>
) : (
messages.map((message) => (
<div
key={message.id}
className={`flex ${message.role === 'user' ? 'justify-end' : 'justify-start'} animate-in fade-in slide-in-from-bottom-4 duration-500`}
>
<div className={`flex gap-3 max-w-[80%] ${message.role === 'user' ? 'flex-row-reverse' : 'flex-row'}`}>
{/* 头像 */}
<div className={`shrink-0 w-8 h-8 rounded-full flex items-center justify-center text-white font-semibold ${
message.role === 'user'
? 'bg-linear-to-br from-blue-500 to-blue-600'
: 'bg-linear-to-br from-purple-500 to-purple-600'
}`}>
{message.role === 'user' ? '你' : 'AI'}
</div>
{/* 消息内容 */}
<div className={`flex flex-col ${message.role === 'user' ? 'items-end' : 'items-start'}`}>
<div className={`rounded-2xl px-4 py-3 shadow-sm ${
message.role === 'user'
? 'bg-linear-to-br from-blue-500 to-blue-600 text-white'
: 'bg-white border border-gray-200 text-gray-800'
}`}>
{message.parts.map((part, index) => {
switch (part.type) {
case 'text':
return (
<div key={message.id + index} className='whitespace-pre-wrap wrap-break-word'>
{part.text}
</div>
);
}
})}
</div>
</div>
</div>
</div>
))
)}
<div ref={messagesEndRef} />
</div>
</div>
{/* 输入区域 */}
<div className='bg-white/80 backdrop-blur-sm border-t border-gray-200 shadow-lg'>
<div className='max-w-4xl mx-auto px-4 py-4'>
<div className='flex gap-3 items-end'>
<div className='flex-1 relative'>
<Textarea
value={input}
onChange={(e:any) => setInput(e.target.value)}
onKeyDown={handleKeyDown}
placeholder='请输入你的问题... (按 Enter 发送,Shift + Enter 换行)'
className='min-h-[60px] max-h-[200px] resize-none rounded-xl border-gray-300 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 transition-all shadow-sm'
/>
</div>
<Button
onClick={() => {
if (input.trim()) {
sendMessage({ text: input });
}
}}
disabled={!input.trim()}
className='h-[60px] px-6 rounded-xl bg-linear-to-r from-blue-500 to-purple-600 hover:from-blue-600 hover:to-purple-700 transition-all shadow-md hover:shadow-lg disabled:opacity-50 disabled:cursor-not-allowed'
>
<svg className='w-5 h-5' fill='none' stroke='currentColor' viewBox='0 0 24 24'>
<path strokeLinecap='round' strokeLinejoin='round' strokeWidth={2} d='M12 19l9 2-9-18-9 18 9-2zm0 0v-8' />
</svg>
</Button>
</div>
</div>
</div>
</div>
);
}
- 安装UI组件库,会生成components.json文件 和 components文件夹
npx shadcn@latestnpx shadcn@latest add buttonnpx shadcn@latest add textarea

3. 运行项目
在当前项目运行npm run dev,然后用浏览器开打http://localhost:3000/ ,就可以正常测试了。

总结
此Demo可以快速嵌套很多小型项目,譬如公司AI智能聊天软件等,其本质是调用大模型厂商提供的模型,对于要求性价比高且无保密性要求的个人和公司很实用。如果你想做出更酷炫的产品,可以进一步了解nextjs。
