当前位置: 首页 > article >正文

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。


http://www.kler.cn/a/550252.html

相关文章:

  • PosgreSQL比MySQL更优秀吗?
  • 容联云联络中心AICC:深度整合DeepSeek,业务验证结果公开
  • MDX语言的安全开发
  • 安全筑基,智能赋能:BeeWorks IM引领企业协同新纪元
  • 缺陷检测之图片标注工具--labme
  • 汇能感知摄像头模组/模块产品有哪些?
  • 【Linux】进程间通信——进程池
  • Python----数据结构(双向链表:节点,是否为空,长度,遍历,添加,删除,查找,循环链表)
  • BT401双模音频蓝牙模块如何开启ble的透传,有什么注意事项
  • XBLW/芯伯乐产品应用在数字万用表上的开发设计
  • springboot和springframework版本依赖关系
  • LeetCode 热门100题-轮转数组
  • Python函数的函数名250217
  • 机器学习数理基础:从概率到梯度下降的全面解析
  • AI与美颜SDK融合:探索直播美颜工具的智能化方案
  • 基于Spring Boot+Vue的宠物服务管理系统(源码+文档)
  • Windows环境搭建ES集群
  • # 10分钟了解DeepSeek,保姆级部署DeepSeek到WPS,实现AI赋能
  • 【Spring Cloud Alibaba】Sentinel 服务熔断与流量控制
  • Ubuntu 系统 cuda12.2 安装 MMDetection3D