基于React开发的聊天窗口,集成了deepseek和gpt-4o的api

本文将介绍如何基于 React / Nextjs框架开发一个功能强大的聊天窗口,并集成 deepseek 和 gpt-4o 的 API,实现智能对话功能。免登录。

源码地址:https://github.com/geeeeeeeek/ai-starter

一、技术栈

  • 前端框架: React / nextjs
  • UI 库: tailwindcss/shadcn
  • 状态管理: Redux
  • HTTP 请求: fetch
  • API 集成: deepseek API, gpt-4o API

二、功能实现

  1. 聊天窗口界面

使用 React 组件构建聊天窗口界面,包括消息列表、输入框、发送按钮等。
使用 UI 库美化界面,例如 Material-UI 的卡片、按钮等组件。
实现消息的发送和接收功能,并将消息实时显示在消息列表中。
将聊天窗口封装到chatDialog.jsx中(具体代码可参考github里)

    return (
        <div className="flex-1 flex flex-col h-screen bg-white p-0">
            <div className="relative bg-white h-12 shadow">

                <SidebarTrigger className="absolute h-12 w-12" />
                <div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 ">AI Chat</div>
            </div>
            <div className="flex-1 overflow-y-auto mt-4 mb-4 space-y-4 ">
                {messages.map((msg, index) => (
                    <div
                        key={index} className="w-full max-w-4xl m-auto"
                    >
                        {
                            msg.role === 'user' ? (
                                <div className="flex justify-end">
                                    <div className="bg-blue-200 p-3 rounded inline-block">{msg.content}</div>
                                </div>
                            ) : (
                                <div className="flex gap-4">
                                    <div>
                                        <svg t="1735719680744" className="icon" viewBox="0 0 1024 1024" version="1.1"
                                            xmlns="http://www.w3.org/2000/svg" p-id="20857" width="32" height="32">
                                            <path
                                                d="M509.44 403.2c-26.88 0-48.64 21.76-48.64 48.64s21.76 48.64 48.64 48.64 48.64-21.76 48.64-48.64c1.28-26.88-20.48-48.64-48.64-48.64z m104.96 53.76c0 26.88 21.76 48.64 48.64 48.64s48.64-21.76 48.64-48.64-21.76-48.64-48.64-48.64c-26.88-1.28-48.64 20.48-48.64 48.64zM512 0C229.12 0 0 229.12 0 512s229.12 512 512 512 512-229.12 512-512S794.88 0 512 0z m243.2 509.44c-14.08 117.76-138.24 200.96-267.52 192-14.08-1.28-29.44 1.28-42.24 7.68l-87.04 47.36c-12.8 6.4-23.04 1.28-26.88-1.28s-12.8-10.24-12.8-24.32l2.56-70.4c1.28-19.2 3.84-46.08-12.8-58.88-56.32-44.8-57.6-97.28-51.2-152.32 12.8-111.36 115.2-195.84 234.24-195.84 10.24 0 21.76 1.28 32 2.56 65.28 7.68 128 34.56 167.68 83.2 44.8 46.08 70.4 111.36 64 170.24zM353.28 403.2c-26.88 0-48.64 21.76-48.64 48.64s21.76 48.64 48.64 48.64 48.64-21.76 48.64-48.64-21.76-48.64-48.64-48.64z"
                                                p-id="20858" fill="#1296db"></path>
                                        </svg>
                                    </div>
                                    <div className="pt-1">
                                        <ReactMarkdown components={{
                                            code({ node, inline, className, children, ...props }) {
                                                const match = /language-(\w+)/.exec(className || '');
                                                return !inline && match ? (
                                                    <SyntaxHighlighter
                                                        style={materialDark}
                                                        language={match[1]}
                                                        PreTag="div"
                                                        {...props}
                                                    >
                                                        {String(children).replace(/\n$/, '')}
                                                    </SyntaxHighlighter>
                                                ) : (
                                                    <code className={className} {...props}>
                                                        {children}
                                                    </code>
                                                );
                                            },
                                        }}>{msg.content}</ReactMarkdown>
                                    </div>
                                </div>
                            )
                        }
                    </div>
                ))}
                {/* 用于自动滚动的空 div */}
                <div ref={messagesEndRef} />
            </div>
            <div className="flex w-full max-w-4xl mb-4 m-auto " >
                <input
                    type="text"
                    value={input}
                    onChange={(e) => setInput(e.target.value)}
                    onKeyPress={(e) => e.key === 'Enter' && handleSend()}
                    className="flex-1 h-12 p-4 border border-gray-300 rounded-l-lg focus:outline-none"
                    placeholder="Send a message to AI...."
                />

                <Button onClick={handleSend} disabled={isFetching} className="bg-blue-500 text-white h-12 py-4 px-4 rounded-l-none rounded-r-lg hover:bg-blue-600">
                    Send
                </Button>
            </div>
        </div>
    );
  1. API 集成

使用了nextjs的api路由功能,提供的服务渲染,创建route.js文件编写api请求方法,并调用openai的sdk。实现了流式获取。

// app/api/chat/route.js
import OpenAI from 'openai';

export async function POST(request) {
  const { messages, model } = await request.json();

  let baseURL = 'https://api.deepseek.com';
  let apiKey = process.env.OPENAI_API_KEY;

  if (model === 'gpt-4o-mini' || model === 'gpt-4o') {
    baseURL = 'https://models.inference.ai.azure.com';
    apiKey = process.env.OPENAI_API_KEY_GPT;
  } else {
    baseURL = 'https://api.deepseek.com';
    apiKey = process.env.OPENAI_API_KEY;
  }

  const openai = new OpenAI({
    baseURL: baseURL,
    apiKey: apiKey, // 从环境变量中获取 API Key
  });

  try {
    const response = await openai.chat.completions.create({
      model: model,
      messages,
      stream: true, // 启用流式响应
    });

    // 创建可读流
    const stream = new ReadableStream({
      async start(controller) {
        for await (const chunk of response) {
          const content = chunk.choices[0]?.delta?.content || '';
          controller.enqueue(`data: ${JSON.stringify({ content })}\n\n`);
        }
        controller.close();
      },
    });

    // 返回流式响应
    return new Response(stream, {
      headers: {
        'Content-Type': 'text/event-stream',
        'Cache-Control': 'no-cache',
        Connection: 'keep-alive',
      },
    });
  } catch (error) {
    console.error('Error calling OpenAI API:', error);
    return new Response(JSON.stringify({ error: 'Internal server error' }), {
      status: 500,
      headers: { 'Content-Type': 'application/json' },
    });
  }
}

注意:需要开发者去deepseek申请api_key,另外还需要去github申请gpt-4o的api_key

预览结果如下


©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,384评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,845评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,148评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,640评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,731评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,712评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,703评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,473评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,915评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,227评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,384评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,063评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,706评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,302评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,531评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,321评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,248评论 2 352

推荐阅读更多精彩内容