從零實現一個GPT 【React + Express】--- 【2】實現對話流和停止生成

摘要

這是本系列文章的第二篇,開始之前我們先回顧一下上一篇文章的內容:

從零實現一個GPT 【React + Express】— 【1】初始化前后端項目,實現模型接入+SSE

在這一篇中,我們主要創建了前端工程和后端工程,這里貼一下我的github地址:

https://github.com/TeacherXin/gpt-xin
https://github.com/TeacherXin/gpt-xin-server

最后我們實現了前端和后端部分的SSE內容,可以通過前端發送query,后端調用gpt模型通過流試返回內容。

而在這一篇中,我們主要把對話部分給實現出來,就是通過后端返回的內容來渲染對話流。

對話流的數據結構

首先我們來到前端項目,肯定是在components下創建一個DialogCardList組件,用來展示對話。

讀者可以先在豆包上發送個對話試一下,可以看到對話區域主要是通過問答對的結構展示的。就是一問一答。

所以我們很容易就能設計出來,這個對話列表的數據結構應該是一個List,List下的每一個對象包含著,id,answer,question三個屬性。

所以我們可以設計一下DialogCardList組件的store:

import { create } from 'zustand';interface DialogCard {question: string;answer: string;cardId: string;
}interface DialogCardListStore {sessionId: string;setSessionId: (id: string) => void;dialogCardList: DialogCard[];addDialogCard: (card: DialogCard) => void;changeLastAnswer: (question: string) => void;changeLastId: (id: string) => void;
}export const useDialogCardListStore = create<DialogCardListStore>((set) => ({sessionId: '',setSessionId: (id: string) => set(() => ({ sessionId: id })),dialogCardList: [],addDialogCard: (card: DialogCard) => set((state) => ({dialogCardList: [...state.dialogCardList, card],})),changeLastAnswer: (answer: string) => set((state) => {const dialogCard = state.dialogCardList[state.dialogCardList.length - 1];if (dialogCard) {dialogCard.answer += answer;}return { dialogCardList: [...state.dialogCardList] };}),changeLastId: (id: string) => set((state) => {const dialogCard = state.dialogCardList[state.dialogCardList.length - 1];if (dialogCard) {dialogCard.cardId = id;}return { dialogCardList: [...state.dialogCardList] };}),}));

dialogCardList就是代表每個問答對組成的列表;

changeLastAnswer方法主要是用來修改最后一個card的answer,這里是因為sse返回內容是流試的。所以我們要不停的更新最后一個節點的回答。

后端添加major事件

剛才我們說到,每個對話的card都有三個屬性,id,question,answer,那id是從哪里來的呢,肯定是后端返回的。

后端可以在每次返回模型輸出內容之前,先返回一個id。但是這個id肯定不能是message類型的,所以,我們可以在major事件里返回對應的id。

在getChat方法中,在for循環之前先發送一個major消息:

const eventName = 'major';
res.write(`event: ${eventName}\n`);
res.write(`data: ${JSON.stringify({id: Date.now()})}\n\n`);

這樣我們再看一下接口的返回:

在這里插入圖片描述

可以看到在SSE中會先返回一個major類型的消息。

本篇章里server端的內容就三行代碼的修改,
具體的提交可以查看:

https://github.com/TeacherXin/gpt-xin-server/commit/1ecc36ceb29acec888df48102ec64edf0c3c676f

實現前端對話流

現在我們已經有了對話流的數據結構,現在我們來想一下流程應該是什么樣子的。

最開始肯定是在輸入框里面輸入內容然后發送調用chat接口了,然后服務端通過SSE返回消息內容。

我們現在有三個回調,major,message,close。這三個函數調用的時機是什么,函數需要做什么呢。我們就來模擬整個流程來講解。

【第一步】發送消息

給dialogCardList添加一個問答對,不過這個時候只有一個question,接口還沒有返回。所以answer和cardId應該為空

const data = {message: inputStore.inputValue,
};dialogCardListStore.addDialogCard({question: inputStore.inputValue,answer: '',cardId: '',
});inputStore.setInputValue('');
inputStore.setInputLoading(true);

【第二步】設置三種事件類型的回調

const url = 'http://localhost:3002/chat';const messageCallback = (message: Message) => {dialogCardListStore.changeLastAnswer(message.content);
};const closeCallback = () => {inputStore.setInputLoading(false);
};const majorCallback = (major: Major) => {dialogCardListStore.changeLastId(major.id);
};connectSSE(url, data, {message: messageCallback,major: majorCallback,close: closeCallback,
});

我們需要在messageCallback,不停的更新dialogCardList中,最后一個card的answer。
在majorCallback中,更新最后一個card的id
在closeCallback中,更新一下輸入框的loading狀態。
然后傳給connectSSE方法即可。

【第三步】實現DialogCardList組件

有了數據結構以及更新流程之后,我們就可以實現DialogCardList組件了:

const DialogCardList: React.FunctionComponent = () => {const dialogCardListStore = useDialogCardListStore();return (<div className={styles.scrollContainer}><div className={styles.dialogCardList}>{dialogCardListStore.dialogCardList.map((item) => {return (<div className={styles.dialogCard} key={item.cardId}><div className={styles.question}><p>{item.question}</p></div><div className={styles.answer}>{item.answer}</div></div>);})}</div></div>);
};

只需要遍歷dialogCardList把對應的問答對展示出來即可,CSS的樣式這里我就不寫了,可以直接看我的提交記錄(貼在后面了)。

最終我們就可以通過發送query實現對話功能了,這里展示一下效果:

請添加圖片描述

停止生成

現在我們發送完對話,如何停止生成,讓這個對話結束呢。

其實我們只需要把SSE的請求取消即可,回到我們的sse.ts中,在最外層定義個abortController

let abortController = new AbortController();

然后修改connectSSE方法,把abortController傳給fetch請求:

const res = await fetch(url, {headers: {'Content-Type': 'application/json', // 必須設置Accept: 'text/event-stream','Cache-Control': 'no-cache',},method: 'POST',body: JSON.stringify(params),signal: abortController.signal, // 用于取消請求});

最后再實現一個stopSSE方法,這里注意一下,每次停止生成都要生成一個新的AbortController,因為下次發送fetch請求不能用之前的AbortController,不然所有的請求都發不出去了:

const stopSSE = () => {abortController.abort(); // 取消 fetch 請求abortController = new AbortController();
}

當inputLoading為true的時候,點擊按鈕就停止生成。

前端部分在這一篇的內容也就實現完了,具體的代碼變更可以看下面的提交記錄:
https://github.com/TeacherXin/gpt-xin/commit/6cb2c719cce51ae9cd6af92cad1283de41c485c9

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/88470.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/88470.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/88470.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

SEQUENCE在RAC多實例開啟CACHE的NEXTVAL數值亂序問題

問題說明 在多實例環境中可能會出現從Sequence所取出來的nextval是亂序的&#xff0c;比如第二次比第一次所取的數要小但這并不是我們所希望的。當程序邏輯Base on sequence.nextval數值所謂填充字段的大小來排序時&#xff0c;就會產生問題。 實際上就是由于多實例這一特性造成…

后臺管理系統-權限管理

在后臺管理系統當中&#xff0c;權限管理占著非常重要的位置&#xff0c;權限管理&#xff0c;顧名思義&#xff0c;就是用來管理用戶登錄后臺的權限。 在權限管理中有三個重要的名詞&#xff1a;賬號&#xff0c;角色&#xff0c;權限 賬號&#xff1a;通過賬號進入平臺&…

MySQL表的約束(5)

文章目錄前言一、空屬性二、默認值三、列描述四、zerofill五、主鍵六、自增長七、唯一鍵八、外鍵總結前言 真正約束字段的是數據類型&#xff0c;但是數據類型約束很單一&#xff0c;需要有一些額外的約束&#xff0c;更好的保證數據的合法性&#xff0c;從業務邏輯角度保證數據…

MyBatis:SQL與Java的智能橋梁

MyBatis&#xff1a;SQL 與 Java 的「智能翻譯官」 —— 用 極簡的方式 連接數據庫和 Java 對象&#xff0c;告別 JDBC 的繁瑣操作&#xff01;核心定位&#xff1a;半自動化 ORM 框架對比項JDBC 原生操作MyBatis 解決方案SQL 編寫拼字符串&#xff08;易出錯、難維護&#xff…

自動駕駛控制系統

目錄 控制系統概述 無人車控制架構設計 自動駕駛控制核心技術 車輛縱向控制 車輛橫向控制 自動駕駛控制方法 自動駕駛控制技術方案 人機交互系統 控制系統概述 控制技術是智能駕駛的關鍵,旨在環境感知技術的基礎之上,根據決策規劃出目標軌跡,通過縱向和橫向控制系統…

網絡安全基石:從弱口令治理到動態防御體系的構建

引言&#xff1a;數字時代的防御困局 在5G與物聯網技術全面落地的數字新基建時代&#xff0c;企業網絡資產規模呈現指數級增長。Verizon《2023年數據泄露調查報告》顯示&#xff0c;61%的安全事件直接源于憑證失竊&#xff0c;而其中81%的攻擊成功案例可溯源至初始口令強度的不…

Error: fatal: detected dubious ownership in repository at

這個錯誤是 Git 在新版中引入的一種 安全檢查機制&#xff0c;目的是防止不同用戶訪問同一個 Git 倉庫目錄&#xff0c;避免潛在的權限或安全問題。你的情況是&#xff1a;倉庫目錄是屬于另一個用戶。當前以管理員用戶 OVERSPREAD/Administrator 運行 Git。Git 為了安全起見&am…

嵌入式 數據結構學習 (六) 樹、哈希表與內核鏈表

一、樹(Tree)結構詳解1. 樹的基本概念樹的核心特性非線性結構&#xff1a;數據元素之間存在一對多的層次關系遞歸定義&#xff1a;樹的子樹仍然是樹專業術語&#xff1a;度(Degree)&#xff1a;結點擁有的子樹數葉子結點&#xff1a;度為0的結點層次(Level)&#xff1a;根為第1…

封裝WebSocket

一個基于原生 WebSocket 的封裝庫&#xff0c;實現了自動重連、心跳檢測等功能&#xff0c;用于在前端應用中穩定地與后端 WebSocket 服務通信。下面從設計思路、關鍵功能等方面進行詳細分析&#xff1a;設計思路 這個封裝庫采用單例模式設計&#xff0c;全局維護一個 WebSocke…

SLICEGPT: COMPRESS LARGE LANGUAGE MODELSBY DELETING ROWS AND COLUMNS

發表&#xff1a;ICLR24 機構&#xff1a;ETH Zurich 連接&#xff1a;https://arxiv.org/pdf/2401.15024 ABSTRACT 大型語言模型&#xff08;Large Language Models, LLMs&#xff09;已成為自然語言處理的基石&#xff0c;但其使用伴隨著在計算和內存資源方面的高昂代價。…

Python 【技術面試題和HR面試題】? 循環結構、控制語句及綜合應用問答

1.技術面試題 &#xff08;1&#xff09;詳細描述單調棧的工作原理和應用場景 答&#xff1a; 原理 維護棧內元素單調遞增 / 遞減&#xff0c;新元素入棧前&#xff0c;彈出破壞單調性的棧頂&#xff0c;保持單調。 應用場景 排隊比身高&#xff0c;搭積木找最大的空地 &#x…

100G系列光模塊產品與應用場景介紹

在當今數字化時代&#xff0c;網絡流量呈爆炸式增長&#xff0c;對數據傳輸速度和帶寬的要求也越來越高。100G 光模塊作為高速數據傳輸的關鍵組件&#xff0c;因其卓越的高速傳輸能力&#xff0c;已成為數據中心、云計算、企業網絡以及 5G 通信網絡等領域的重要組成部分。接下來…

運籌說 第140期 | 從直覺到算法:這些奠基人如何塑造了啟發式方法的科學根基?

運籌說建構知識體系&#xff0c;解析學習要點運 籌 優 化 領 域 教 學 媒 體視頻課程已上線&#xff01;&#xff01;!歡迎大家關注同名抖音和嗶哩嗶哩賬號&#xff01;在人工智能與優化科學的浩瀚星空中&#xff0c;啟發式算法如同一把鑰匙&#xff0c;為人類打開了處…

Flutter編譯安卓應用時遇到的compileDebugJavaWithJavac和compileDebugKotlin版本不匹配的問題

記一次flutter應用&#xff0c;編譯安卓時&#xff0c;報的一個compileDebugJavaWithJavac和compileDebugKotlin版本本匹配的問題。 最終定位的原因是項目一來了audioplayers組件。 audioplayers組件有依賴了audioplayers_android&#xff0c; 它使用1.8編譯的。 版本過低。后來…

linux-權限管理

linux-權限管理一、權限的基本類型二、權限的表示方式1. 字符形式&#xff08;rwx&#xff09;2. 數字形式三、權限管理常用命令1. chmod2. chown3. chgrp四、隱藏權限1. lsattr2. chattr五、權限掩碼六、特別權限位1. suid2. sgid3. Sticky Bit七、權限委托1. 授權用戶2. 授權…

從FCOS3D到PGD:看深度估計如何快速搭建你的3D檢測項目

【導讀】 還記得那個曾經在單目3D目標檢測領域掀起熱潮的 FCOS3D 嗎&#xff1f;在后續更新中他們又推出了全新升級版——PGD&#xff08;Probabilistic and Geometric Depth&#xff09;最有意思的是&#xff0c;這次他們徹底換了路線&#xff1a;從原先的“直接回歸深度”&a…

Apache Cloudberry 向量化實踐(三)重塑表達式構建路徑:Gandiva 優化實戰

在向量化執行系統中&#xff0c;表達式構建是不可或缺的基礎環節。無論是 SQL 中的投影、篩選&#xff0c;還是分區、聚合、排序&#xff0c;最終都需轉化為底層執行引擎能識別和執行的表達式樹。而在 Apache Cloudberry 向量化執行框架中&#xff0c;這一過程由 Gandiva 表達式…

Windows刪除文件或者拔出U盤顯示正在使用/占用解決辦法

1、復制文件地址2、打開任務管理器&#xff0c;選擇左側【性能】3、打開資源監視器4、選擇資源監視器中的CPU5、粘貼你復制的占用文件地址6、除了explore.exe以外&#xff0c;其他的關聯的句柄都選中&#xff0c;然后右鍵結束

自由學習記錄(68)

&#x1f9e0; blender為什么不用 M 或 T&#xff1f; 鍵位含義為什么沒選MMove&#xff1f;其實被用作「Move to Collection」等功能不符合歷史定義&#xff0c;而且功能太多了TTransform&#xff1f; 但 transform 是一個總稱&#xff08;含移動、旋轉、縮放&#xff09;T 被…

ReactNative【實戰系列教程】我的小紅書 8 -- 我(含左側彈窗菜單,右下角圖標等)

最終效果點左上角菜單按鈕&#xff0c;彈出左側菜單后代碼實現app/(tabs)/mine.tsx import icon_add from "/assets/icons/icon_add.png"; import mine_bg from "/assets/images/mine_bg.png"; import Heart from "/components/Heart"; import a…