react+星火大模型,构建上下文ai问答页面(可扩展)

news2025/1/17 0:03:12

前言

最近写的开源项目核心功能跑通了,前两天突发奇想。关于项目可否介入大模型来辅助用户使用平台,就跑去研究了最近比较活火的国内大模型–讯飞星火大模型。

大模型api获取

控制台登录

地址:https://console.xfyun.cn/app/myapp
新建应用后点进去:
image.png

获取api地址和其key

image.png
左侧选用大模型版本,右侧圈起来的地方就是咱api要调用的数据了

如果没有正常的token或key可能没有实名认证,需要先实名认证下!

下面有关于web的调用接口,这是咱们后面要调用的api接口地址:
image.png

技术栈

react hooks + TypeScript + semi-ui(组件库,可选)

下载工具包:

npm i crypto-js base-64 -d

实现

api和key都获取到了,咱就直接开始上代码操作吧!

目录

image.png

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无关紧要
image.png

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}>
            <input
              type="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了,如需要可以在评论区喊我

结果

效果图

QQ截图20231111211013.jpg

后言

关于使用server的AiTool.tsx工具,其实还能产生不少其他的扩展,接下来着重研究下!加油加油!

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

NtripShare Mos地铁自动化监测终端盒子硬件设计

自动化监测产品到目前为止做了接近一年&#xff0c;在软件层面上&#xff0c;控制终端软件、平台软件、网平差算法都已解决&#xff0c;硬件盒子始终是心里过不去的坎&#xff0c;最终还是没有耐住性子自己做了一把。 选型如下&#xff1a; 1、主板:瑞芯微RK3568主板。 2、外…

自动驾驶学习笔记(七)——感知融合

#Apollo开发者# 学习课程的传送门如下&#xff0c;当您也准备学习自动驾驶时&#xff0c;可以和我一同前往&#xff1a; 《自动驾驶新人之旅》免费课程—> 传送门 《Apollo Beta宣讲和线下沙龙》免费报名—>传送门 文章目录 前言 感知融合 卡尔曼滤波 融合策略 实…

C详细的字符串函数

但行前路&#xff0c;莫问归期 要注意的是&#xff0c;要使用下边所讲的函数要包含头文件<string.h> 文章目录 strlenstrcpystrncpy strcatstrncat strcmpstrncmp strstrstrtokstrerror字符串大小写转换struprstrlwr memcpymemmovememcmp strlen 求字符串的长度 函数参…

语音识别与自然语言处理(NLP):技术前沿与未来趋势

语音识别与自然语言处理&#xff08;NLP&#xff09;&#xff1a;技术前沿与未来趋势 随着科技的快速发展&#xff0c;语音识别与自然语言处理&#xff08;NLP&#xff09;技术逐渐成为人工智能领域的研究热点。这两项技术的结合&#xff0c;使得机器能够更好地理解和处理人类语…

【Java0基础学Java第八颗】 -- 继承与多态 -- 多态

8.继承与多态 8.2 多态8.2.1 多态的概念8.2.2 多态实现条件8.2.3 重写8.2.4 向上转型和向下转型8.2.5 向下转型8.2.6 多态的优缺点8.2.7 避免在构造方法中调用重写的方法 8.2 多态 8.2.1 多态的概念 通俗来说就是多种形态&#xff0c;具体点就是去完成某个行为&#xff0c;当…

阿里云服务器搭建sql 服务

阿里云搭建mysql服务 环境准备 系统镜像 ubuntu 如果买点的实例不是ubuntu 系统镜像&#xff0c;需要停止服务之后&#xff0c;更改镜像 更新 apt-get &#xff1a; 更新apt-get: sudo apt-get update 如果没有出现&#xff1a;apt-get 找不到此命令的错误&#xff0c;可能是…

解决《荒野大镖客》提示emp.dll文件丢失问题,总结5个修复方法

在当今数字时代&#xff0c;游戏已经成为人们休闲娱乐的重要方式。作为一名游戏爱好者&#xff0c;笔者在近期体验《荒野大镖客》这款游戏时&#xff0c;遇到了一个令人苦恼的问题——emp.dll文件丢失。这个问题让游戏的无法启动进行。本文将围绕这一问题&#xff0c;探讨其原因…

一个整数,它加上100后是一个完全平方数,再加上168又是一个完全平方数,请问该数是多少?

目录 1解题思路&#xff1a; 2代码如下&#xff1a; 3运行结果&#xff1a; 4总结&#xff1a; 5介绍&#xff1a; 1解题思路&#xff1a; 利用循环&#xff08;穷举法&#xff09;来 对 所 需要的数 进行确定 2代码如下&#xff1a; #include <stdio.h>int main() …

react+星火大模型,构建ai问答页面(可扩展)

前言 最近写的开源项目核心功能跑通了&#xff0c;前两天突发奇想。关于项目可否介入大模型来辅助用户使用平台&#xff0c;就跑去研究了最近比较活火的国内大模型–讯飞星火大模型。 大模型api获取 控制台登录 地址&#xff1a;https://console.xfyun.cn/app/myapp 新建应…

CL-MVSNet论文精读

本文是对CL-MVSNet: Unsupervised Multi-View Stereo with Dual-Level Contrastive Learning Kaiqiang Xiong, Rui Peng, Zhe Zhang, Tianxing Feng, Jianbo Jiao, Feng Gao, Ronggang Wang的阅读记录 Proceedings of the IEEE/CVF International Conference on Computer Visio…

数据分析实战 | K-means算法——蛋白质消费特征分析

目录 一、数据及分析对象 二、目的及分析任务 三、方法及工具 四、数据读入 五、数据理解 六、数据准备 七、模型训练 ​编辑 八、模型评价 九、模型调参与预测 一、数据及分析对象 txt文件——“protein.txt”&#xff0c;主要记录了25个国家的9个属性&#xff0c;主…

3.30每日一题(多元函数微分学)

1、判断连续&#xff1a;再分界点的极限值等于该点的函数值&#xff1b; 如何求极限值&#xff1a; 初步判断&#xff1a;分母都为二次幂开根号&#xff0c;所以分母为一次幂&#xff1b;分子为二次&#xff0c;一般来说整体为0&#xff1b; 如何说明极限为零&#xff08;常用…

Git 命令详解

系列文章目录 C高性能优化编程系列 深入理解软件架构设计系列 高级C并发线程编程 C技能系列 期待你的关注哦&#xff01;&#xff01;&#xff01; 现在的一切都是为将来的梦想编织翅膀&#xff0c;让梦想在现实中展翅高飞。 Now everything is for the future of dream we…

【教3妹学编程-算法题】2923. 找到冠军 I

3妹&#xff1a;2哥2哥&#xff0c;你看到新闻了吗&#xff1f;襄阳健桥医院院长 公然“贩卖出生证明”&#xff0c; 真是太胆大包天了吧。 2哥 : 我也看到新闻了&#xff0c;7人被采取刑事强制措施。 就应该好好查查他们&#xff0c; 一查到底&#xff01; 3妹&#xff1a;真的…

华为ensp:为vlan配置ip

配置对应vlan的ip vlan1 interface Vlanif 1 进入vlan1 ip address 192.168.1.254 24配置IP为192.168.1.254 子网掩码为24位 这样就配置上ip了 vlan2 interface Vlanif 2 ip address 192.168.2.254 24 vlan3 interface Vlanif 3 ip address 192.168.3.254 24 查看结果 …

【C++入门】构造函数析构函数

目录 前言 1. 类的默认成员函数 2. 构造函数 2.1 什么是构造函数 2.2 构造函数的特性 3. 析构函数 3.1 什么是析构函数 3.2 析构函数的特性 前言 前边我们已经了解了类和对像的基本概念&#xff0c;今天我们将继续深入了解类。类有6个默认成员函数&#xff0c;即使类中什么都…

赛氪助力全国大学生数学竞赛山东赛区圆满举办

近日&#xff0c;全国大学生数学竞赛山东赛区比赛有序进行&#xff0c;赛氪已连续6年助力本项赛事蓬勃发展。在中国高等教育学会高校竞赛评估与管理体系研究专家工作组发布的《2022全国普通高校大学生竞赛分析报告》中&#xff0c;本赛事荣登观察目录。 全国大学生数学竞赛旨在…

Leetcode2834. 找出美丽数组的最小和

Every day a Leetcode 题目来源&#xff1a;2834. 找出美丽数组的最小和 解法1&#xff1a;贪心 从最小正整数 1 开始枚举&#xff0c;设当前数为 num&#xff0c;如果 nums 里没有 target - num&#xff0c;就说明可以添加 num&#xff0c;依次填满直到有 n 个数即可。 用…

2023年【危险化学品经营单位主要负责人】免费试题及危险化学品经营单位主要负责人证考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2023年危险化学品经营单位主要负责人免费试题为正在备考危险化学品经营单位主要负责人操作证的学员准备的理论考试专题&#xff0c;每个月更新的危险化学品经营单位主要负责人证考试祝您顺利通过危险化学品经营单位主…

【2024提前批/秋招笔试汇总2】——大疆-嵌入式软件-2023.08.06

一、 单选题&#xff08;40分&#xff09; 1. 以下关于GPU的特点描述不准确的是&#xff1a; A.GPU无法使用共享内存结构&#xff0c;提高通信速度 B.GPU的并行数据处理可以大幅度提高运算能力 C.GPU使用高速全局内存可以进一步提升运算速度 D.GPU的计算能力比CPU强 2.下列关…