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

前言

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

大模型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工具,其实还能产生不少其他的扩展,接下来着重研究下!加油加油!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java,多线程,线程安全的懒汉式、死锁、ReentrantLock的使用以及一些知识点补充

关于线程安全地懒汉式有以下几种方式&#xff1a; /*** 实现线程安全的懒汉式*/ public class BankTest {Bank b1 null;Bank b2 null;public static void main(String[] args){BankTest bb new BankTest();Thread t1 new Thread(){Overridepublic void run(){bb.b1 Bank.…

蓝桥杯算法双周赛心得——被替换的身份证(分类讨论)

大家好&#xff0c;我是晴天学长&#xff0c;分类讨论一定要细节啊&#xff0c;需要的小伙伴可以关注支持一下哦&#xff01;后续会继续更新的。&#x1f4aa;&#x1f4aa;&#x1f4aa; 1) .被替换的身份证 2) .算法思路 假设一方获胜 1.接受数据 2.假设潜梦醒 无非就是&am…

基于安卓android微信小程序的四六级助手系统

项目介绍 随着我国教育需求不断增加&#xff0c;高校教育资源有限&#xff0c;教育经费相对不足的情况下&#xff0c;利用现代信息技术发展高等教育&#xff0c;不仅充分利用了优秀的教育资源&#xff0c;而且为更多的人提供接受高等教育的机会&#xff0c;同时这也是极大促进…

【代码随想录】算法训练计划18

1、513. 找树左下角的值 题目&#xff1a; 给定一个二叉树的 根节点 root&#xff0c;请找出该二叉树的 最底层 最左边 节点的值。 假设二叉树中至少有一个节点。 思路&#xff1a; 递归&#xff0c;规则&#xff0c;基本可以自己写出来 var maxDepth int var res int fun…

ObjectArx动态加载及卸载自定义菜单

上节中我们介绍了如何制作自定义菜单即cuix文件&#xff1a;给CAD中添加自定义菜单CUIX-CSDN博客https://blog.csdn.net/qianlixiaomage/article/details/134349794在此基础上&#xff0c;我们开发时通常需要在ObjectArx程序中进行动态的添加或者删除cuix菜单。 创建ObjectArx…

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

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

jmeter性能测试常见的一些问题

一、request 请求超时设置 timeout 超时时间是可以手动设置的&#xff0c;新建一个 http 请求&#xff0c;在“高级”设置中找到“超时”设置&#xff0c;设置连接、响应时间为2000ms。 1. 请求连接超时&#xff0c;连不上服务器。 现象&#xff1a; Jmeter表现形式为&#xff…

【从0到1设计一个网关】上岸大厂的秘诀之一

文章目录 前言【从0到1设计一个网关】什么是网关&#xff1f;以及为什么需要自研网关&#xff1f;【从0到1设计一个网关】自研网关的设计要点以及架构设计【从0到1设计一个网关】自研网关的架构搭建【从0到1设计一个网关】网络通信框架Netty的设计【从0到1设计一个网关】整合Na…

Zephyr-7B论文解析及全量训练、Lora训练

文章目录 一、Zephyr&#xff1a;Direct Distillation of LM Alignment1.1 开发经过1.1.1 Zephyr-7B-alpha1.1.2 Zephyr-7B-beta 1.2 摘要1.3 相关工作1.4 算法1.4.1 蒸馏监督微调&#xff08;dSFT&#xff09;1.4.2 基于偏好的AI反馈 (AIF&#xff09;1.4.3 直接蒸馏偏好优化&…

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

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

Transforme原理--全局解读

文章目录 作用全局解读 作用 Transformer最初设计用于处理序列数据&#xff0c;特别在NLP(自然语言处理)领域取得了巨大成功 全局解读 Transformer来源于谷歌的一篇经典论文Attention is All you Need 在此使用Transformer在机器翻译中的运用来讲解Transformer。 其中Tran…

Windows11跳过联网激活 跳过登陆操作

1 背景 笔者使用VirtualBox时安装Win11&#xff0c;初始化的配置提示需要注册账户才能进行下一步操作&#xff0c;于是去查了一下发现有办法绕过&#xff0c;方法就是断网oobe\ByPassNRO.cmd&#xff0c;试了一下发现可以&#xff0c;便有了这篇文章。 2 流程 开机之前&…

【完美世界】石昊负伤遭囚禁,无始种惊现,二秃子用柳枝力保石昊

Hello,小伙伴们&#xff0c;我是小郑继续为大家深度解析国漫资讯。 深度爆料完美世界最新预告资讯&#xff0c;《完美世界》第137集预告片已经更新了&#xff0c;这一集的预告片充满了紧张的气氛和精彩的情节。从预告中我们可以看到&#xff0c;石昊的真实身份被天人族知晓&…

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

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

ZYNQ_project:IP_ram_pll_test

例化MMCM ip核&#xff0c;产生100Mhz&#xff0c;100Mhz并相位偏移180&#xff0c;50Mhz&#xff0c;25Mhz的时钟信号。 例化单口ram&#xff0c;并编写读写控制器&#xff0c;实现32个数据的写入与读出。 模块框图&#xff1a; 代码&#xff1a; module ip_top(input …

SpringBoot_01

Spring https://spring.io/ SpringBoot可以帮助我们非常快速的构建应用程序、简化开发、提高效率。 SpringBootWeb入门 需求&#xff1a;使用SpringBoot开发一个web应用&#xff0c;浏览器发起请求/hello后&#xff0c;给浏览器返回字符串"Hello World~~~"。 步骤…

测试人员如何通过AI提高工作效率!

随着AI技术的兴起&#xff0c;像OpenAI推出的ChatGPT、Microsoft发布的Microsoft 365 Copilot、阿里的通义千问、百度的文心一言、华为的盘古大模型等。很多测试人员开始担心&#xff0c;岗位是否会被AI取代&#xff1f;其实取代你的不是AI&#xff0c;而是会使用AI的测试人&am…

基于springboot+vue的校园闲置物品交易系统

运行环境 开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven 项目介绍 本文从管…

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

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

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

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