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

news2025/1/17 0:03:16

前言

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

大模型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 addMsgToTextarea = (text: string) => {
  //   respondHoodle(text);
  // };

  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: [
              { role: 'user', content: '你是谁' }, //# 用户的历史问题
              { role: 'assistant', content: '我是AI助手' }, //# AI的历史回答结果
              // ....... 省略的历史对话
              { 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) => {
      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/1197398.html

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

相关文章

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.下列关…

【PyQt】(自制类)处理鼠标点击逻辑

写了个自认为还算不错的类&#xff0c;用于简化mousePressEvent、mouseMoveEvent和mouseReleaseEvent中的鼠标信息。 功能有以下几点&#xff1a; 鼠标当前状态&#xff0c;包括鼠标左/中/右键和单击/双击/抬起鼠标防抖(仅超出一定程度时才判断鼠标发生了移动)&#xff0c;灵…

Java面向对象(进阶)-- 面向对象特征之三:多态性

文章目录 一、多态的形式和体现&#xff08;1&#xff09;为什么需要多态性(polymorphism)&#xff1f;&#xff08;2&#xff09; 对象的多态性 二、 多态的理解&#xff08;1&#xff09;如何理解多态性&#xff08;2&#xff09;Java中多态性的体现&#xff08;3&#xff09…

SpringMvc 常见面试题

1、SpringMvc概述 1.1、什么是Spring MVC &#xff1f;简单介绍下你对springMVC的理解? Spring MVC是一个基于Java的实现了MVC设计模式的请求驱动类型的轻量级Web框架&#xff0c;通过把Model&#xff0c;View&#xff0c;Controller分离&#xff0c;将web层进行职责解耦&am…

CentOs7 NAT模式连接网络

1.配置动态网络 1.1 检查主机网卡配置 检查主机的网络设置 进入控制面板&#xff0c;找到网络共享中心 查看适配器是否都已经开启 1.2 设置虚拟机的网络配置 打开虚拟机网络配置设置&#xff0c;对网卡VMnet8 进行设置 记住网关 全部选择应用&#xff0c;确定 1.3 设置…

操作系统(一)基础知识及操作系统启动

文章目录 前言前置基础知识计算机组成CPU磁盘内核中断、异常、系统调用局部性原理 启动操作系统计算机加电是如何正常执行服务的&#xff1f;开机自检BIOS&#xff08;Basic Input/Output System&#xff09;BootLoader 小结 前言 本文主要涉及操作系统的简介、硬件结构、内存…

从Hadoop到对象存储,抛弃Hadoop,数据湖才能重获新生?

Hadoop与数据湖的关系 1、Hadoop时代的落幕2、Databricks和Snowflake做对了什么3、Hadoop与对象存储&#xff08;OSD&#xff09;4、Databricks与Snowflake为什么选择对象存储5、对象存储面临的挑战 1、Hadoop时代的落幕 十几年前&#xff0c;Hadoop是解决大规模数据分析的“白…

C++17中std::optional的使用

模版类std::optional管理一个可选的(optional)存储值(contained value)&#xff0c;即可能存在也可能不存在的值。std::optional的一个常见用例是存储可能失败的函数的返回值。与其它方法相反(例如std::pair<T, bool>),std::optional可以很好地处理构造成本高昂的对象&am…

ETW HOOK原理探析

ETW HOOK研究 文章目录 ETW HOOK研究前言原理探究内核开启ETW日志HOOK ETW修改ETW日志上下文代理GetCpuClock函数寻找SSDT和SSDT Shadow 总结参考 前言 关于ETW是什么我就不多说了&#xff0c;可以通过微软的相关文档了解到。据网上得知这项技术最早被披露于2345的驱动中&…

Netty--ByteBuffer

2. ByteBuffer 有一普通文本文件 data.txt&#xff0c;内容为 1234567890abcd 使用 FileChannel 来读取文件内容 Slf4j public class ChannelDemo1 {public static void main(String[] args) {// FileChannel// 1. 输入输出流&#xff0c; 2. RandomAccessFile// try (F…