node.js + html调用ChatGPTApi实现Ai网站demo(带源码)

文章目录

  • 前言
  • 一、demo演示
  • 二、node.js 使用步骤
    • 1.引入库
    • 2.引入包
  • 前端HTML调用接口和UI
  • 所有文件
  • 总结


前言

关注博主,学习每天一个小demo 今天是Ai对话网站

又到了每天一个小demo的时候咯,前面我写了多人实时对话demo、和视频转换demo,今天我来使用 node.js + html 调用chatGpt Api实现一个Ai 流式对话小demo,当然现在主流的各种APi调用方式一致,你也可以接入deepseek,或者第三方的接口,效果一样


一、demo演示

下面是一个简单的demo演示,并且是支持流式的
在这里插入图片描述

二、node.js 使用步骤

1.引入库

代码如下:

先初始化一个package.js 文件。

npm init -y

我们需要安装 express、cors、openAi、dotenv三方库

{
  "name": "web",
  "version": "1.0.0",
  "main": "server.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node server.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": "",
  "dependencies": {
    "axios": "^1.7.9",
    "cors": "^2.8.5",
    "dotenv": "^16.0.3",
    "express": "^4.18.2",
    "openai": "^4.0.0"
  }
}

2.引入包

创建server.js 引入我们的第三方包 编写接口逻辑

**特别关注:

  1. baseURL: 这里大家可以替换其他的APi 比如deepseek(好像目前不能充值了),或者一些第三方的API地址。
  2. apiKey: 这里大家把key可以直接填写上,我这里为了学习知识,新建了一个.env 文件。**
const express = require('express');
const cors = require('cors');
const OpenAI = require('openai');
require('dotenv').config();

const app = express();
app.use(express.json());
app.use(cors());

// 初始化OpenAI客户端
const openai = new OpenAI({
    apiKey: process.env.DEEPSEEK_API_KEY, 
    baseURL: "https://api.openai.com/v1"  // 使用OpenAI官方API地址  这里可以可以使用别的Api地址
    // 比如 deepseek 或者其他的第三方的一些
    // baseURL: "https://api.deepseek.com/v1"
});

let conversationHistory = [];

app.post('/chat', async (req, res) => {
    try {
        const { message } = req.body;
        
        // 设置响应头,支持流式输出
        res.setHeader('Content-Type', 'text/event-stream');
        res.setHeader('Cache-Control', 'no-cache');
        res.setHeader('Connection', 'keep-alive');
        res.setHeader('Access-Control-Allow-Origin', '*');  // 添加CORS支持
        
        // 添加用户消息到历史记录
        conversationHistory.push({ role: "user", content: message });
        
        const stream = await openai.chat.completions.create({
            model: "gpt-3.5-turbo",
            messages: [
                { role: "system", content: "You are a helpful assistant." },
                ...conversationHistory
            ],
            temperature: 0.7,
            max_tokens: 1000,
            stream: true,
        });

        let fullResponse = '';
        
        // 确保每次写入后立即刷新缓冲区
        for await (const chunk of stream) {
            const content = chunk.choices[0]?.delta?.content || '';
            if (content) {
                fullResponse += content;
                const dataToSend = JSON.stringify({ content });
                console.log('Sending to client:', dataToSend);
                res.write(`data: ${dataToSend}\n\n`);
                // 强制刷新缓冲区
                if (res.flush) {
                    res.flush();
                }
            }
        }

        // 将完整回复添加到对话历史
        conversationHistory.push({ role: "assistant", content: fullResponse });
        
        if (conversationHistory.length > 10) {
            conversationHistory = conversationHistory.slice(-10);
        }

        res.write('data: [DONE]\n\n');
        if (res.flush) {
            res.flush();
        }
        res.end();

    } catch (error) {
        console.error('Error:', error);
        res.write(`data: ${JSON.stringify({ error: '服务器错误' })}\n\n`);
        res.end();
    }
});

const PORT = 3000;
app.listen(PORT, () => {
    console.log(`服务器运行在端口 ${PORT}`);
}); 

在根目录执行 node server.js 启动服务

前端HTML调用接口和UI

不墨迹哈,直接把所有代码贴过来 大家好直接研究代码,我就不用一行一行解读了,没啥东西,难处就是对流式数据的一个处理

特别注意, 不要直接点击html打开页面,在vscode里面安装扩展Live Server,然后点击右下角 Go live启动一个微服务。
在这里插入图片描述
在这里插入图片描述

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ChatGPT 聊天</title>
    <style>
        #chat-container {
            width: 80%;
            max-width: 800px;
            margin: 20px auto;
            padding: 20px;
            border: 1px solid #ccc;
            border-radius: 5px;
        }
        #chat-messages {
            height: 400px;
            overflow-y: auto;
            margin-bottom: 20px;
            padding: 10px;
            border: 1px solid #eee;
        }
        .message {
            margin: 10px 0;
            padding: 10px;
            border-radius: 5px;
        }
        .user-message {
            background-color: #e3f2fd;
            margin-left: 20%;
        }
        .bot-message {
            background-color: #f5f5f5;
            margin-right: 20%;
        }
        #message-form {
            display: flex;
            gap: 10px;
        }
        #message-input {
            flex: 1;
            padding: 8px;
        }
        .typing {
            opacity: 0.5;
        }
        .cursor {
            display: inline-block;
            width: 2px;
            height: 15px;
            background: #000;
            margin-left: 2px;
            animation: blink 1s infinite;
        }
        @keyframes blink {
            0%, 100% { opacity: 1; }
            50% { opacity: 0; }
        }
    </style>
</head>
<body>
    <div id="chat-container">
        <div id="chat-messages"></div>
        <form id="message-form">
            <input type="text" id="message-input" placeholder="输入消息..." required>
            <button type="submit" id="submit-btn">发送</button>
        </form>
    </div>

    <script>
        const messageForm = document.getElementById('message-form');
        const messageInput = document.getElementById('message-input');
        const chatMessages = document.getElementById('chat-messages');
        const submitBtn = document.getElementById('submit-btn');

        messageForm.addEventListener('submit', async (e) => {
            e.preventDefault();
            const message = messageInput.value.trim();
            if (!message) return;

            // 禁用输入和发送按钮
            messageInput.disabled = true;
            submitBtn.disabled = true;

            // 显示用户消息
            addMessage(message, 'user');
            messageInput.value = '';

            // 创建机器人回复的消息框
            const botMessageDiv = document.createElement('div');
            botMessageDiv.className = 'message bot-message typing';
            chatMessages.appendChild(botMessageDiv);
            
            // 添加光标
            const cursor = document.createElement('span');
            cursor.className = 'cursor';
            botMessageDiv.appendChild(cursor);

            try {
                const response = await fetch('http://localhost:3000/chat', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                        'Accept': 'text/event-stream'
                    },
                    body: JSON.stringify({ message })
                });

                if (!response.ok) {
                    throw new Error(`HTTP error! status: ${response.status}`);
                }

                const reader = response.body.getReader();
                const decoder = new TextDecoder();
                let botResponse = '';
                let buffer = '';

                try {
                    while (true) {
                        const { value, done } = await reader.read();
                        // 如果流结束了,就退出循环
                        if (done) {
                            console.log('Stream complete');
                            break;
                        }

                        // 确保有值才处理
                        if (value) {
                            buffer += decoder.decode(value, { stream: true });
                            const lines = buffer.split('\n');
                            
                            // 保留最后一个不完整的行
                            buffer = lines.pop() || '';

                            for (const line of lines) {
                                if (line.trim() === '') continue;
                                if (!line.startsWith('data: ')) continue;

                                const data = line.slice(6).trim();

                                if (data === '[DONE]') {
                                    botMessageDiv.classList.remove('typing');
                                    cursor.remove();
                                    continue;
                                }

                                try {
                                    const parsedData = JSON.parse(data);
                                    if (parsedData.content) {
                                        botResponse += parsedData.content;
                                        botMessageDiv.textContent = botResponse;
                                        botMessageDiv.appendChild(cursor);
                                        chatMessages.scrollTop = chatMessages.scrollHeight;
                                    }
                                } catch (e) {
                                    console.error('JSON解析错误:', e, 'Raw data:', data);
                                }
                            }
                        }
                    }

                    // 处理最后可能残留的数据
                    if (buffer.trim()) {
                        const finalText = decoder.decode(); // 完成流的解码
                        if (finalText) {
                            buffer += finalText;
                            const lines = buffer.split('\n');
                            
                            for (const line of lines) {
                                if (line.trim() === '' || !line.startsWith('data: ')) continue;
                                
                                const data = line.slice(6).trim();
                                if (data === '[DONE]') continue;
                                
                                try {
                                    const parsedData = JSON.parse(data);
                                    if (parsedData.content) {
                                        botResponse += parsedData.content;
                                        botMessageDiv.textContent = botResponse;
                                    }
                                } catch (e) {
                                    console.error('最终解析错误:', e, 'Raw data:', data);
                                }
                            }
                        }
                    }
                } catch (streamError) {
                    console.error('Stream processing error:', streamError);
                    throw streamError;
                }
            } catch (error) {
                console.error('Error:', error);
                botMessageDiv.textContent = '抱歉,发生错误。';
                botMessageDiv.classList.remove('typing');
                cursor.remove();
            } finally {
                messageInput.disabled = false;
                submitBtn.disabled = false;
                messageInput.focus();
            }
        });

        function addMessage(text, sender) {
            const messageDiv = document.createElement('div');
            messageDiv.className = `message ${sender}-message`;
            messageDiv.textContent = text;
            chatMessages.appendChild(messageDiv);
            chatMessages.scrollTop = chatMessages.scrollHeight;
        }
    </script>
</body>
</html> 

所有文件

  1. .env 存储了APi-key
  2. index.html 前端代码
  3. server.js 后段代码
    在这里插入图片描述
    在这里插入图片描述

总结

尽可能的多学习一些知识,或许以后用不到,关注我每天练习一个小demo。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/971141.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

类和对象(5)——抽象类和接口

目录 1. 抽象类 1.1 抽象类的概念 1.2 抽象类语法&#xff1a;abstract关键字 1.3 抽象类的特性 1.4 抽象类的作用 2. 接口 2.1 接口的概念 2.2 接口语法&#xff1a;interface关键字 2.3 接口的实现&#xff1a;implements关键字 2.4 接口的特性 2.5 实现多个接口 …

kubectl exec 实现的原理

kubectl exec 是 Kubernetes 提供的一个命令&#xff0c;它允许你在指定的 Pod 中执行命令&#xff0c;类似于在容器中打开一个终端会话。这个功能对于调试、监控和管理容器化应用非常有用。kubectl exec 的实现涉及到多个 Kubernetes 组件和机制&#xff0c;包括 API Server、…

【ubuntu24.04】 强制重启导致大模型的磁盘挂载出错

挂载NTFS文件系统出错 各种模型放在了这个机械硬盘上&#xff0c;虽然速度慢&#xff0c;但是好在容量大。大模型在工作&#xff0c;但是程序看起来有问题&#xff0c;导致系统卡死了&#xff0c;然后我重启了&#xff0c;然后报错&#xff1a;wrong fs type bad option &…

【数据结构】 栈和队列

在计算机科学的世界里&#xff0c;数据结构是构建高效算法的基础。栈&#xff08;Stack&#xff09;和队列&#xff08;Queue&#xff09;作为两种基本且重要的数据结构&#xff0c;在软件开发、算法设计等众多领域都有着广泛的应用。今天&#xff0c;我们就来深入探讨一下栈和…

移动端测试的挑战与解决方案:兼容性、网络问题及实战策略

引言 移动应用已成为用户触达服务的核心入口,但移动端测试面临设备多样性、网络波动、用户场景复杂等多重挑战。据Statista统计,2023年全球活跃移动设备超180亿台,操作系统(Android/iOS)版本碎片化率超30%,这对测试工程师提出了极高要求。本文深度解析移动端测试的核心痛…

【设计模式】03-理解常见设计模式-行为型模式(专栏完结)

前言 前面我们介绍完创建型模式和创建型模式&#xff0c;这篇介绍最后的行为型模式&#xff0c;也是【设计模式】专栏的最后一篇。 一、概述 行为型模式主要用于处理对象之间的交互和职责分配&#xff0c;以实现更灵活的行为和更好的协作。 二、常见的行为型模式 1、观察者模…

matlab欠驱动船舶模型预测控制

1、内容简介 matlab135-欠驱动船舶模型预测控制 可以交流、咨询、答疑 2、内容说明 略 针对在风 、 浪 、 流时变干扰下欠驱动水面船舶的轨迹跟踪控制问题 &#xff0c; 设计了一种基于模型 预测控制的轨迹跟踪控制器 &#xff0e; 考虑到欠驱动船舶在没有横向驱动力情况下…

计算机性能与网络体系结构探讨 —— 基于《计算机网络》谢希仁第八版

(꒪ꇴ꒪ )&#xff0c;Hello我是祐言QAQ我的博客主页&#xff1a;C/C语言&#xff0c;数据结构&#xff0c;Linux基础&#xff0c;ARM开发板&#xff0c;网络编程等领域UP&#x1f30d;快上&#x1f698;&#xff0c;一起学习&#xff0c;让我们成为一个强大的攻城狮&#xff0…

Linux上安装jdk1.8和配置环境变量

步骤一&#xff1a;&#xff1a;创建jdk安装目录(该/usr/local/ ,最好把我们自己下载的放到这,容易区分) 可以省略 步骤二&#xff1a;查看安装程序 [rootVM_0_4_centos src]# rpm -qa | grep -i jdk 若之前安装过jdk&#xff0c;下次安装一定把之前的删除干净 下载地址链接…

【Spring+MyBatis】留言墙的实现

目录 1. 添加依赖 2. 配置数据库 2.1 创建数据库与数据表 2.2 创建与数据库对应的实体类 3. 后端代码 3.1 目录结构 3.2 MessageController类 3.3 MessageService类 3.4 MessageMapper接口 4. 前端代码 5. 单元测试 5.1 后端接口测试 5.2 使用前端页面测试 在Spri…

Windows环境安装部署minimind步骤

Windows环境安装部署minimind步骤 必要的软件环境 git git&#xff0c;可下载安装版&#xff0c;本机中下载绿色版&#xff0c;解压到本地目录下&#xff08;如&#xff1a;c:\soft\git.win64&#xff09;&#xff0c;可将此路径添加到PATH环境变量中&#xff0c;供其他程序…

RocketMQ与kafka如何解决消息丢失问题?

0 前言 消息丢失基本是分布式MQ中需要解决问题&#xff0c;消息丢失时保证数据可靠性的范畴。如何保证消息不丢失程序员面试中几乎不可避免的问题。本文主要说明RocketMQ和Kafka在解决消息丢失问题时&#xff0c;在生产者、Broker和消费者之间如何解决消息丢失问题。 1.Rocket…

基于AIOHTTP、Websocket和Vue3一步步实现web部署平台,无延迟控制台输出,接近原生SSH连接

背景&#xff1a;笔者是一名Javaer&#xff0c;但是最近因为某些原因迷上了Python和它的Asyncio&#xff0c;至于什么原因&#xff1f;请往下看。在着迷”犯浑“的过程中&#xff0c;也接触到了一些高并发高性能的组件&#xff0c;通过简单的学习和了解&#xff0c;aiohttp这个…

Golang的代码结构规划

Golang的代码结构规划 是一种具有高效性能的开发语言&#xff0c;其代码结构规划对于项目的可维护性和可扩展性至关重要。在Golang中&#xff0c;合理的代码结构可以使代码更加清晰易懂&#xff0c;方便团队协作和项目维护。本文将介绍Golang代码结构规划的最佳实践&#xff0c…

【算法与数据结构】并查集详解+题目

目录 一&#xff0c;什么是并查集 二&#xff0c;并查集的结构 三&#xff0c;并查集的代码实现 1&#xff0c;并查集的大致结构和初始化 2&#xff0c;find操作 3&#xff0c;Union操作 4&#xff0c;优化 小结&#xff1a; 四&#xff0c;并查集的应用场景 省份…

服务器部署DeepSeek,通过Ollama+open-webui部署

1. 安装ollama 1.1. linux 安装 Ollama是目前常用的AI模式部署的第三方工具&#xff0c;能一键部署deepSeek Ollama官方网址https://ollama.com/ 选择Download下载对应的服务版本 服务器选择Linux&#xff0c;下面是下载代码 curl -fsSL https://ollama.com/install.…

(三)Axure制作转动的唱片

效果图 属性&#xff1a; 图标库&#xff1a;iconfont-阿里巴巴矢量图标库 方形图片转为圆角图片&#xff0c;裁剪&#xff0c;然后加圆角&#xff0c; 唱片和底图是两个图片&#xff0c;点击播放&#xff0c;唱片在旋转。 主要是播放按钮和停止按钮&#xff0c;两个动态面板…

5G时代的运维变革与美信监控易的深度剖析

一、5G普及后的网络运维新变化&#xff1a;数据驱动的挑战与机遇 &#xff08;一&#xff09;数据流量的爆炸式增长 在2025年&#xff0c;5G技术已经如同汹涌的浪潮席卷全球。据相关科技数据显示&#xff0c;5G网络的普及使得数据流量呈现出令人咋舌的增长态势。 这种海量的数…

BGP配置华为——RR反射器配置

实验拓扑 与之前实验同理将loop0作为routerID使用&#xff0c;且R1和R2上用loop1接口用于模拟用户其他网段 实验要求 1&#xff0c;在AS100内运行OSPF协议 2.配置路由反射器&#xff0c;使得从R1进入的数据能够反射到全局网络 3.在R1和R2上分别宣告自己的loop1口网段用于观…

记录第一次在windows环境编译libuvc库 踩的坑

最近遇到windows下编译libuvc库,实现经usb连接的摄像头拍摄采集。绕了一大圈&#xff0c;记录一下。 首先&#xff0c;作为新手&#xff0c;肯定需要参考大神资料&#xff0c;但是还是踩了坑。 要在windows 环境下安装libuvc的驱动并确保可用&#xff0c;需要经过一系列流程&a…