当前位置: 首页 > 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。

相关文章:

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

文章目录 前言一、demo演示二、node.js 使用步骤1.引入库2.引入包 前端HTML调用接口和UI所有文件总结 前言 关注博主&#xff0c;学习每天一个小demo 今天是Ai对话网站 又到了每天一个小demo的时候咯&#xff0c;前面我写了多人实时对话demo、和视频转换demo&#xff0c;今天…...

14.Python生成器、迭代器、闭包、装饰器、元类、垃圾回收、内建函数

在 Python 中&#xff0c;生成器、迭代器、闭包、装饰器、元类、垃圾回收和内建函数是一些重要的概念和功能&#xff0c;它们对于编写高效、灵活的代码非常重要。下面我们逐一详细介绍这些概念及其用法。 1. 生成器&#xff08;Generator&#xff09; 生成器是一个函数&#…...

STM32+Proteus+DS18B20数码管仿真实验

1. 实验准备 硬件方面&#xff1a; 了解 STM32 单片机的基本原理和使用方法&#xff0c;本实验可选用常见的 STM32F103 系列。熟悉 DS18B20 温度传感器的工作原理和通信协议&#xff08;单总线协议&#xff09;。数码管可选用共阴极或共阳极数码管&#xff0c;用于显示温度值。…...

Vulhub靶机 ActiveMQ 反序列化漏洞(CVE-2015-5254)(渗透测试详解)

一、开启vulhub环境 docker-compose up -d 启动 docker ps 查看开放的端口 漏洞版本&#xff1a;Apache ActiveMQ 5.x ~ Apache ActiveMQ 5.13.0 二、访问靶机IP 8161端口 默认账户密码 admin/admin&#xff0c;登录 此时qucues事件为空 1、使用jmet-0.1.0-all.jar工具将…...

ConcurrentHashMap扩容

目录 一、tryPreSize方法-初始化数组 二、tryPreSize方法-扩容标识戳 三、transfer方法-构建新数组 四、transfer方法-迁移数据 五、transfer方法-lastRun机制 六、helpTransfer方法-协助扩容 三种触发方式 达到了扩容的阈值 一、tryPreSize方法-初始化数组 // 扩容前…...

2025年二级建造师报名流程图解

2025年二级建造师报名时间&#xff01;附报名流程&#xff01; ⏰️已公布25年二建考试时间的省份如下&#xff1a; ️4月19日、20日考试的城市有&#xff1a;贵州 ️5月10日、11日考试的城市有&#xff1a;湖北、陕西、宁夏、甘肃、福建、浙江、江西、黑龙江、河南、湖南、…...

【自学笔记】人工智能基础知识点总览-持续更新

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 人工智能重点知识点总览一、基础概念与原理1.1 人工智能定义与发展1.2 算法与数据结构1.3 数学基础 二、机器学习2.1 监督学习2.2 无监督学习2.3 强化学习 三、深度…...

hexo 魔改 | 修改卡片透明度

hexo 魔改 | 修改卡片透明度 ** 博客食物用更佳 博客地址 ** 这是笔者自己瞎倒腾的。作为前端菜鸡一枚&#xff0c;大佬们随便看看就好~ 我用的主题是 butterfly 4.12.0 分析 通过开发者工具可以看出来卡片的背景和 --card-bg 变量有关 再在 sources 下的 css 文件夹下的…...

Golang的并发编程案例详解

Golang的并发编程案例详解 一、并发编程概述 并发编程是指程序中有多个独立的执行线索&#xff0c;并且这些线索在时间上是重叠的。在 Golang 中&#xff0c;并发是其核心特性之一&#xff0c;通过 goroutine 和 channel 来支持并发编程&#xff0c;使得程序可以更高效地利用计…...

【升级】阿里云对象存储 HTTPS 根证书升级公告

--时间打败一切...

贪心算法与动态规划的区别

贪心算法&#xff1a;每一步都选择当前最优解&#xff0c;期望通过局部最优达到全局最优。 动态规划&#xff1a;通过分解问题为子问题&#xff0c;存储并重用子问题的解&#xff0c;避免重复计算。 最简单的JS ACM代码举例 贪心算法&#xff1a;找零问题 function greed…...

策略模式-小结

总结一下看到的策略模式&#xff1a; A:一个含有一个方法的接口 B:具体的实行方式行为1,2,3&#xff0c;实现上面的接口。 C:一个环境类&#xff08;或者上下文类&#xff09;&#xff0c;形式可以是&#xff1a;工厂模式&#xff0c;构造器注入模式&#xff0c;枚举模式。 …...

TDengine 性能测试工具 taosBenchmark

简介工具获取运行 无参数模式命令行模式配置文件模式 命令行参数配置文件参数 通用配置参数写入配置参数 数据库相关超级表相关标签列与数据列写入行为相关 查询配置参数 执行指定查询语句查询超级表 订阅配置参数数据类型对照表 配置文件示例 写入 JSON 示例查询 JSON 示例订阅…...

硬件学习笔记--41 电磁兼容试验-5 射频场感应的传导干扰试验介绍

目录 电磁兼容试验-射频场感应的传导干扰试验介绍 1.试验目的 2.试验方法 3.判定依据及意义 电磁兼容试验-射频场感应的传导干扰试验介绍 驻留时间是在规定频率下影响量施加的持续时间。被试设备&#xff08;EUT&#xff09;在经受扫频频带的电磁影响量或电磁干扰的情况下&a…...

泛型 类 接口 方法 通配符

泛型 泛型类 what: 类型参数化 why use&#xff1a; 1. 输出时候是object类型 而不是真正类型转化麻烦 import java.util.ArrayList; import java.util.List;public class ObjectExample {public static void main(String[] args) {List<Object> list new ArrayLi…...

文字转语音(三)FreeTTS实现

项目中有相关的功能&#xff0c;就简单研究了一下。 说明 FreeTTS 是一个基于 Java 的开源文本转语音&#xff08;TTS&#xff09;引擎&#xff0c;旨在将文字内容转换为自然语音输出。 FreeTTS 适合对 英文语音质量要求低、预算有限且需要离线运行 的场景&#xff0c;但若需…...

STM32 RTC 实时时钟说明

目录 背景 RTC(实时时钟)和后备寄存器 32.768HZ 如何产生1S定时 RTC配置程序 第一次上电RTC配置 第1步、启用备用寄存器外设时钟和PWR外设时钟 第2步、使能RTC和备份寄存器访问 第3步、备份寄存器初始化 第4步、开启LSE 第5步、等待LSE启动后稳定状态 第6步、配置LSE为…...

Open-R1 项目代码文件的详细剖析

目录 1. configs.py 功能概述 关键代码与细节 2. evaluate.py 功能概述 关键代码与细节 3. generate.py 功能概述 关键代码与细节 4. grpo.py 功能概述 关键代码与细节 5. rewards.py 功能概述 关键代码与细节 6. sft.py 功能概述 关键代码与细节 安装 训练…...

Android RenderEffect对Bitmap高斯模糊(毛玻璃),Kotlin(1)

Android RenderEffect对Bitmap高斯模糊(毛玻璃)&#xff0c;Kotlin&#xff08;1&#xff09; import android.graphics.Bitmap import android.graphics.BitmapFactory import android.graphics.HardwareRenderer import android.graphics.PixelFormat import android.graphic…...

【DuodooBMS】基于Odoo的开源制造执行系统——以开源之力,驱动智能制造

以用户为中心的开放式智造平台 DuodooMES的设计始终围绕“用户可编程、生态可生长”的核心思想&#xff0c;打破传统工业软件的封闭性&#xff0c;让制造企业真正成为系统的“主人”&#xff1a; 1. 用户可编程&#xff1a;生产流程由你定义 界面可配置&#xff1a;无需代码即…...

机器视觉深度学习,工业缺陷检测中数据标注需要注意那些问题

在工业缺陷检测中,数据标注是构建高质量模型的关键步骤,需注意以下问题: 标注准确性 精确标注缺陷位置:确保标注框或掩码准确覆盖缺陷区域,避免过大或过小。 区分缺陷类型:不同缺陷应有明确分类,避免混淆。标注一致性 统一标注标准:制定并遵循统一的标注规范,确保不同…...

数据结构:图论入门

图论起源于欧拉对哥尼斯堡七桥问题的解决. 他构建的图模型将陆地用点来表示, 桥梁则用线表示, 如此一来, 该问题便转化为在图中能否不重复地遍历每条边的问题. 图论的应用 地图着色 在地图着色问题中, 我们用顶点代表国家, 将相邻国家之间用边相连. 这样, 问题就转化为用最少…...

【R语言】方差分析

一、基本术语 在R语言以及更广泛的统计学领域中&#xff0c;方差分析&#xff08;ANOVA&#xff0c;即Analysis of Variance&#xff09;是一种用于比较两个或更多组数据的均值是否存在显著差异的统计方法。可以使用aov()函数或其他相关函数&#xff08;如anova()&#xff09;…...

区块链+隐私计算:长安链多方计算合约标准协议(CMMPC-1)发布

建设背景 长安链与隐私计算的深度融合是构建分布式数据与价值流通网络的关键基石&#xff0c;可以在有效连接多元参与主体的同时确保数据的分布式、可追溯、可计算&#xff0c;以及隐私性与安全性。在长安链与隐私计算的融合实践中&#xff0c;开源社区提炼并抽象出多方计算场…...

#渗透测试#批量漏洞挖掘#Crocus系统—Download 文件读取

免责声明 本教程仅为合法的教学目的而准备&#xff0c;严禁用于任何形式的违法犯罪活动及其他商业行为&#xff0c;在使用本教程前&#xff0c;您应确保该行为符合当地的法律法规&#xff0c;继续阅读即表示您需自行承担所有操作的后果&#xff0c;如有异议&#xff0c;请立即停…...

HAL库USART中断接收的相关问题

文章目录 一、使用中断的步骤二、相关函数分析1、HAL_UART_IRQHandler2、UART_Receive_IT3、HAL_UART_Receive_IT4、UART_Start_Receive_IT5、总结 三、HAL库使用心得 一、使用中断的步骤 1、配置GPIO 2、配置USART1 3、设置UART1中断优先级&#xff08;不开启手动中断&#x…...

顺序表(C)

1.顺序表的概念 顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构&#xff0c;通常借助数组来实现。它的特点是逻辑上相邻的元素在物理存储位置上也相邻&#xff0c;支持随机访问&#xff0c;可通过下标直接访问任意位置的元素。不过&#xff0c;顺序表在插入和…...

Polkadot-API (PAPI) 简介与使用指南

在 Polkadot 生态系统中&#xff0c;去中心化应用&#xff08;dApp&#xff09;、网页和钱包的开发者通常使用 JavaScript 和 TypeScript 进行开发。与基于 Polkadot SDK 的区块链进行交互&#xff0c;传统上主要依赖于 Polkadot JS 库。然而&#xff0c;最近波卡生态中出现了一…...

LabVIEW用户界面设计原则

在LabVIEW开发中&#xff0c;用户界面&#xff08;UI&#xff09;设计不仅仅是为了美观&#xff0c;它直接关系到用户的操作效率和体验。一个直观、简洁、易于使用的界面能够大大提升软件的可用性&#xff0c;尤其是在复杂的实验或工业应用中。设计良好的UI能够减少操作错误&am…...

Java中的synchronized:使用与锁升级机制

在Java并发编程中&#xff0c;synchronized关键字是实现线程同步的重要工具。它能够确保多个线程在访问共享资源时的线程安全性。随着Java版本的更新&#xff0c;synchronized的底层实现也在不断优化&#xff0c;尤其是引入了锁升级机制&#xff0c;显著提高了性能。本文将详细…...