react+星火大模型,构建上下文ai问答页面(可扩展)
前言
最近写的开源项目核心功能跑通了,前两天突发奇想。关于项目可否介入大模型来辅助用户使用平台,就跑去研究了最近比较活火的国内大模型–讯飞星火大模型。
大模型api获取
控制台登录
地址:https://console.xfyun.cn/app/myapp
新建应用后点进去:

获取api地址和其key

左侧选用大模型版本,右侧圈起来的地方就是咱api要调用的数据了
如果没有正常的token或key可能没有实名认证,需要先实名认证下!
下面有关于web的调用接口,这是咱们后面要调用的api接口地址:

技术栈
react hooks + TypeScript + semi-ui(组件库,可选)
下载工具包:
npm i crypto-js base-64 -d
实现
api和key都获取到了,咱就直接开始上代码操作吧!
目录

utils(工具类)
以下工具类getWebsocketUrl方法,负责构建api的URL地址,具体原因可以查阅对应的官方文档说明
import * as base64 from 'base-64';
import CryptoJs from 'crypto-js';
import { requestObj } from '../config';
export const getWebsocketUrl = () => {return new Promise<string>((resovle, reject) => {let url = 'ws://spark-api.xf-yun.com/v1.1/chat';let host = 'spark-api.xf-yun.com';let apiKeyName = 'api_key';// let date = new Date().toGMTString();let date = new Date().toUTCString();let algorithm = 'hmac-sha256';let headers = 'host date request-line';let signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v1.1/chat HTTP/1.1`;let signatureSha = CryptoJs.HmacSHA256(signatureOrigin, requestObj.APISecret);let signature = CryptoJs.enc.Base64.stringify(signatureSha);let authorizationOrigin = `${apiKeyName}="${requestObj.APIKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`;let authorization = base64.encode(authorizationOrigin);// 将空格编码url = `${url}?authorization=${authorization}&date=${encodeURI(date)}&host=${host}`;resovle(url);});
};
config(配置类)
前言说的 key 在此处分别填入即可,Uid无关紧要

server/AiTool(ws服务工具)
该工具类负责接收父类组件传来的问题,并对相关数据信息进行返回。
- **forwardRef,useImperativeHandle,props **实现父子通信
- 通过 **getWebsocketUrl **返回url地址构建ws通信
- **websocket **返回相关ai回应数据,并对数据加载状态进行实时通信
import { FC, forwardRef, useImperativeHandle, useState } from 'react';
import { requestObj } from '../config';
import { getWebsocketUrl } from '../utils';
interface AiToolProps {isText?: boolean;respondHoodle: (result: string) => void; //关联数据loadHoodle?: (isLoading: boolean) => void; //加载状态errorHoodle?: (isLoading: boolean) => void; //失败调用
}interface CropperRef {submitHoodle: (v: any) => void; //父类调用
}const AiTool = forwardRef<CropperRef, AiToolProps>(function AiTool({ isText, respondHoodle, loadHoodle, errorHoodle },ref
) {let result: string = '';const [historyMessage, setHistoryMessage] = useState<any[]>([{ role: 'user', content: '你是谁' }, //# 用户的历史问题{ role: 'assistant', content: '我是AI助手' }]);useImperativeHandle(ref, () => ({submitHoodle: sendMsg}));const sendMsg = async (questionText: string) => {result = ' ';// 获取请求地址let myUrl = await getWebsocketUrl();// 获取输入框中的内容// 每次发送问题 都是一个新的websocket请求let socket = new WebSocket(myUrl);// 监听websocket的各阶段事件 并做相应处理socket.addEventListener('open', (event) => {if (loadHoodle) loadHoodle(true);// 发送消息let params = {header: {app_id: requestObj.APPID,uid: 'wzz'},parameter: {chat: {domain: 'general',temperature: 0.5,max_tokens: 1024}},payload: {message: {// 如果想获取结合上下文的回答,需要开发者每次将历史问答信息一起传给服务端,如下示例// 注意:text里面的所有content内容加一起的tokens需要控制在8192以内,开发者如有较长对话需求,需要适当裁剪历史信息text: [...historyMessage,// ....... 省略的历史对话{ role: 'user', content: questionText } //# 最新的一条问题,如无需上下文,可只传最新一条问题]}}};socket.send(JSON.stringify(params));});socket.addEventListener('message', (event) => {let data = JSON.parse(event.data);if (!data.payload) {socket.close();return;}result += data.payload.choices.text[0].content;respondHoodle(result);if (data.header.code !== 0) {console.log('出错了', data.header.code, ':', data.header.message);// 出错了"手动关闭连接"socket.close();}if (data.header.code === 0) {// 对话已经完成if (data.payload.choices.text && data.header.status === 2) {setTimeout(() => {// "对话完成,手动关闭连接"socket.close();}, 1000);}}});socket.addEventListener('close', (event) => {setHistoryMessage([...historyMessage,{ role: 'user', content: questionText },{ role: 'assistant', content: result }]);if (loadHoodle) loadHoodle(false);// 对话完成后socket会关闭,将聊天记录换行处理});socket.addEventListener('error', (event) => {if (errorHoodle) errorHoodle(true);console.log('连接发送错误!!', event);});};// return result;return '';
});
export default AiTool;
Chat.tsx(具体组件)
chat组件的具体实现
- messageList 记录信息数据
- submit 发送问题函数
- overRespond 介绍信息函数
- **moveY **返回底部
import { memo, useRef, useState } from 'react';
//type
import type { FC } from 'react';
import styles from './index.module.scss';
import { Button, Spin } from '@douyinfe/semi-ui';
import AiTool from '@/ai/server/AiTool';
interface IProps {datas?: any[];
}
//user:true代表用户信息,反之ai
interface messageInfo {text: string;user: boolean;
}const Chat: FC<IProps> = () => {const [question, setQuestion] = useState<string>('');// const [result, setResult] = useState<string>('');let result = '';const [isLoading, setIsLoading] = useState<boolean>(false);const [messageList, setMessageList] = useState<messageInfo[]>([]);const ref = useRef<any>(null);const messageContainerRef = useRef<any>(null);const loadingRef = useRef<any>(null);const submit = () => {setQuestion('');console.log(messageList);if (!messageList.length) {setMessageList([{user: true,text: question}]);console.log(messageList);} else {setMessageList([...messageList,{user: true,text: question}]);console.log(messageList);}moveY();if (ref.current) {ref.current.submitHoodle(question);}};const respondHoodle = (respond: string) => {result = respond;loadingRef.current.innerText = result;moveY();// loadingRef.current};const overRespond = (v: boolean) => {if (!v) {setMessageList((prevList) => [...prevList, { user: false, text: result }]);console.log(messageList);moveY();}setIsLoading(v);};const handleSendMessage = (e: any) => {e.preventDefault();};const handleKeyPress = (e: any) => {if (e.keyCode === 13) {submit();}};//返回底部const moveY = () => {const h = messageContainerRef.current.scrollHeight;messageContainerRef.current.scrollTop = h + 20;};return (<div className={styles.chat}><div className={styles.chat__main}><header className={styles.chat__mainHeader}><p>欢迎使用青邮AI助手!</p><div><Button onClick={moveY} style={{ marginRight: 4 }}>返回底部</Button><Button type="danger" theme="solid" onClick={() => setMessageList([])}>清除聊天记录</Button></div></header>{/* 显示你发送消息的内容 */}<div className={styles.message__container} ref={messageContainerRef}>{messageList.map((item, index) => {return item.user ? (<div key={item.user.toString() + index} className={styles.message__chats}><p className={styles.sender__name}>You</p><div className={styles.message__sender}><p>{item.text}</p></div></div>) : (<div className={styles.message__chats}><p>Ai</p><div className={styles.message__recipient}><p>{item.text}</p></div></div>);})}{isLoading ? (<div className={styles.message__chats}><p>Ai</p><div className={styles.message__recipient}><p ref={loadingRef}>{result}</p><Spin /></div></div>) : ('')}</div><div className={styles.chat__footer}><form className="form" onSubmit={handleSendMessage}><inputtype="text"placeholder="编写消息"className={styles.message}value={question}onChange={(e) => setQuestion(e.target.value)}onKeyUp={handleKeyPress}/><Button onClick={submit} type="primary" theme="solid" className={styles.sendBtn}>发送</Button></form></div><AiTool loadHoodle={overRespond} respondHoodle={respondHoodle} ref={ref} /></div></div>);
};export default memo(Chat);
scss就不v了,如需要可以在评论区喊我
结果
效果图

后言
关于使用server的AiTool.tsx工具,其实还能产生不少其他的扩展,接下来着重研究下!加油加油!
相关文章:
react+星火大模型,构建上下文ai问答页面(可扩展)
前言 最近写的开源项目核心功能跑通了,前两天突发奇想。关于项目可否介入大模型来辅助用户使用平台,就跑去研究了最近比较活火的国内大模型–讯飞星火大模型。 大模型api获取 控制台登录 地址:https://console.xfyun.cn/app/myapp 新建应…...
python---设计模式
python中设计模式-单例模式 基于__new__方法实现 第一个设计: class MySingleton:def __init__(self):passdef __new__(cls, *args, **kwargs):passmysingleton1 MySingleton() mysingleton2 MySingleton() print(mysingleton1) print(mysingleton2) print(id(…...
Java编写xml文件时,文件中特殊字符如何解决?
有一个使用Java创建XML文件的需求,但标签里面有以下特殊字符<、>、&等 在未解决之前,创建出的XML是这样的 <?xml version"1.0" encoding"UTF-8"?><actionlist><update><jobno>1111</jobno&…...
vue3 ts pinia openapi vue-query pnpm docker前端架构小记
1.引言 开发中,我们是否经常遇到以下痛点: 项目越大,启动和热更新越来越慢,启动都要花个3-5分钟以上没有类型保障,接口返回的Object不拿到真实数据都不知道有哪些字段,接手别人js项目(无类型)很痛苦需要手…...
ARM day4
LED灯亮灭控制 .text .global _start _start: 1ldr r0,0x50000a28ldr r1,[r0]orr r1,r1,#(0x3<<4)str r1,[r0] 2ldr r0,0x50006000ldr r1,[r0]bic r1,r1,#(0x3<<20)orr r1,r1,#(0x1<<20)bic r1,r1,#(0x3<<16)orr r1,r1,#(0x1<<16)str r1,[r0]…...
3.30每日一题(多元函数微分学)
1、判断连续:再分界点的极限值等于该点的函数值; 如何求极限值: 初步判断:分母都为二次幂开根号,所以分母为一次幂;分子为二次,一般来说整体为0; 如何说明极限为零(常用…...
《OSTEP》条件变量(chap30)
〇、前言 本文是对《OSTEP》第三十章的实践与总结。 一、条件变量 #include <pthread.h> #include <stdio.h> #include <assert.h>int buffer; int count 0; // 资源为空// 生产,在 buffer 中放入一个值 void put(int value) {assert(count 0);count 1…...
MySQL的索引和复合索引
由于MySQL自动将主键加入到二级索引(自行建立的index)里,所以当select的是主键或二级索引就会很快,select *就会慢。因为有些列是没在索引里的 假设CA有1kw人咋整,那我这个索引只起了前一半作用。 所以用复合索引&am…...
关于mac下pycharm旧版本没删除的情况下新版本2023安装之后闪退
先说结论,我用的app cleaner 重新删除的pycharm ,再重新安装即可。在此记录一下 之前安装的旧版的2020的pycharm,因为装不了新的插件,没办法就升级了。新装2023打开之后闪退,重启系统也不行,怀疑是一起破解…...
Django中如何让DRF的接口针对前后台返回不同的字段
在Django中,使用Django Rest Framework(DRF)时,可以通过序列化器(Serializer)和视图(View)的组合来实现前后台返回不同的字段。这通常是因为前后台对数据的需求不同,或者…...
【机器学习】Kmeans聚类算法
一、聚类简介 Clustering (聚类)是常见的unsupervised learning (无监督学习)方法,简单地说就是把相似的数据样本分到一组(簇),聚类的过程,我们并不清楚某一类是什么(通常无标签信息)࿰…...
getid3 获取视频时长
1、首先,我们需要先下载一份PHP类—getid3https://codeload.github.com/JamesHeinrich/getID3/zip/master 2.我在laravel6.0 中使用 需要在composer.json 自动加载 否则系统访问不到 在命令行 执行 composer dump-autoload $getID3 new \getID3();//视频文件需要放…...
如何知道一个程序为哪些信号注册了哪些信号处理函数?
https://unix.stackexchange.com/questions/379694/is-there-a-way-to-know-if-signals-are-present-in-your-application-and-which-sign 使用 strace...
34 mysql limit 的实现
前言 这里来看一下 我们常见的 mysql 分页的 limit 的相的处理 这个问题的主要是来自于 之前有一个需要处理 大数据量的数据表的信息, 将数据转移到 es 中 然后就是用了最简单的 “select * from tz_test limit $pageOffset, $pageSize ” 来分页处理 但是由于 数据表的数…...
jbase实现申明式事务
对有反射的语言,申明式事务肯定不可少。没必要没个人都try,catch写事务,写的不好的话还经常容易锁表,为此给框架引入申明式事务。申明式既字面意思,在需要事务的方法前面加一个申明,那么框架保证事务。 首…...
如何在在线Excel文档中规范单元格输入
在日常的工作中,我们常常需要处理大量的数据。为了确保数据的准确性和可靠性。我们需要对输入的数据进行规范化和验证。其中一个重要的方面是规范单元格输入。而数据验证作为Excel中一种非常实用的功能,它可以帮助用户规范单元格的输入,从而提…...
力扣138:随机链表的复制
力扣138:随机链表的复制 题目描述: 给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。 构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成ÿ…...
C语言左移与右移学习
在学习左移与右移之前,我们首先要学习两种移位运算:逻辑移位和算数移位。 逻辑位移:移出去的位丢弃,空缺位用0补充。 算数位移:移出去的位丢弃,空缺位用符号位补充。 左移 左移是高位溢出,低…...
asp.net core mvc之 视图
一、在控制器中找到匹配视图,然后渲染成 HTML 代码返回给用户 public class HomeController : Controller {public IActionResult Index(){return View(); //默认找 Views/Home/Index.cshtml ,呈现给用户} } 二、指定视图 1、控制器 publ…...
ChatGLM3 tool_registry.py 代码解析
ChatGLM3 tool_registry.py 代码解析 0. 背景1. tool_registry.py 0. 背景 学习 ChatGLM3 的项目内容,过程中使用 AI 代码工具,对代码进行解释,帮助自己快速理解代码。这篇文章记录 ChatGLM3 tool_registry.py 的代码解析内容。 1. tool_re…...
UE5 学习系列(二)用户操作界面及介绍
这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…...
conda相比python好处
Conda 作为 Python 的环境和包管理工具,相比原生 Python 生态(如 pip 虚拟环境)有许多独特优势,尤其在多项目管理、依赖处理和跨平台兼容性等方面表现更优。以下是 Conda 的核心好处: 一、一站式环境管理:…...
脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)
一、数据处理与分析实战 (一)实时滤波与参数调整 基础滤波操作 60Hz 工频滤波:勾选界面右侧 “60Hz” 复选框,可有效抑制电网干扰(适用于北美地区,欧洲用户可调整为 50Hz)。 平滑处理&…...
AI Agent与Agentic AI:原理、应用、挑战与未来展望
文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例:使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例:使用OpenAI GPT-3进…...
关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案
问题描述:iview使用table 中type: "index",分页之后 ,索引还是从1开始,试过绑定后台返回数据的id, 这种方法可行,就是后台返回数据的每个页面id都不完全是按照从1开始的升序,因此百度了下,找到了…...
Opencv中的addweighted函数
一.addweighted函数作用 addweighted()是OpenCV库中用于图像处理的函数,主要功能是将两个输入图像(尺寸和类型相同)按照指定的权重进行加权叠加(图像融合),并添加一个标量值&#x…...
ESP32 I2S音频总线学习笔记(四): INMP441采集音频并实时播放
简介 前面两期文章我们介绍了I2S的读取和写入,一个是通过INMP441麦克风模块采集音频,一个是通过PCM5102A模块播放音频,那如果我们将两者结合起来,将麦克风采集到的音频通过PCM5102A播放,是不是就可以做一个扩音器了呢…...
【论文笔记】若干矿井粉尘检测算法概述
总的来说,传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度,通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...
【android bluetooth 框架分析 04】【bt-framework 层详解 1】【BluetoothProperties介绍】
1. BluetoothProperties介绍 libsysprop/srcs/android/sysprop/BluetoothProperties.sysprop BluetoothProperties.sysprop 是 Android AOSP 中的一种 系统属性定义文件(System Property Definition File),用于声明和管理 Bluetooth 模块相…...
TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案
一、TRS收益互换的本质与业务逻辑 (一)概念解析 TRS(Total Return Swap)收益互换是一种金融衍生工具,指交易双方约定在未来一定期限内,基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...
