《60天AI学习计划启动 | Day 40: 前端 AI SDK 抽象(aiClient + hooks)》

news/2025/12/17 10:51:09/文章来源:https://www.cnblogs.com/aigent/p/19360909

Day 40:前端 AI SDK 抽象(aiClient + hooks)

学习目标

  • 抽象 一套通用的 aiClient 接口(不用关心具体后端实现细节)
  • 封装 常用 hooks:useChat(非流式)、useStreamingChat(流式)
  • 为后面 在任何项目中快速接 AI 打基础

核心知识点

  • aiClient 抽象

    • 核心思想:前端只依赖一个统一客户端,不直接散落 fetch('/api/xxx')
    • 可以定义接口:
      interface AIClient {chat(payload: ChatRequest): Promise<ChatResponse>streamChat(payload: ChatRequest): AsyncIterable<ChatChunk>
      }
      
    • 具体实现可以对接不同后端(自家服务 / OpenAI / 其他网关)
  • hooks 层

    • useChat(client):一次性请求,适合短回答、非流式场景
    • useStreamingChat(client):基于 AsyncIterable 或 SSE 读流,适合聊天页面

实战作业(附完整代码)

作业 1:定义 AIClient 接口 + 一个 HTTP 实现

// aiClient.ts
export interface ChatMessage {role: 'system' | 'user' | 'assistant'content: string
}export interface ChatRequest {messages: ChatMessage[]meta?: Record<string, any>
}export interface ChatResponse {answer: stringusage?: {promptTokens?: numbercompletionTokens?: numbertotalTokens?: number}
}export interface ChatChunk {type: 'delta' | 'final' | 'error'content?: stringerror?: string
}export interface AIClient {chat(req: ChatRequest): Promise<ChatResponse>streamChat(req: ChatRequest): AsyncIterable<ChatChunk>
}// 一个基于 HTTP 的简单实现(假设后端有 /api/chat 和 /api/chat/stream)
export class HttpAIClient implements AIClient {constructor(private baseUrl = '') {}async chat(req: ChatRequest): Promise<ChatResponse> {const res = await fetch(this.baseUrl + '/api/chat', {method: 'POST',headers: { 'Content-Type': 'application/json' },body: JSON.stringify(req)})if (!res.ok) throw new Error(`HTTP ${res.status}`)const data = await res.json()return {answer: data.answer ?? '',usage: data.usage}}async *streamChat(req: ChatRequest): AsyncIterable<ChatChunk> {const res = await fetch(this.baseUrl + '/api/chat/stream', {method: 'POST',headers: { 'Content-Type': 'application/json' },body: JSON.stringify(req)})if (!res.body) throw new Error('No response body')const reader = res.body.getReader()const decoder = new TextDecoder()let done = falselet buffer = ''while (!done) {const chunk = await reader.read()done = chunk.doneif (chunk.value) {buffer += decoder.decode(chunk.value, { stream: true })const parts = buffer.split('\n\n')buffer = parts.pop() || ''for (const part of parts) {const line = part.trim()if (!line.startsWith('data:')) continueconst jsonStr = line.slice(5).trim()if (!jsonStr || jsonStr === '[DONE]') continueconst data = JSON.parse(jsonStr) as ChatChunkyield data}}}}
}

作业 2:useChat(非流式)hook

// useChat.ts
import { useState, useCallback } from 'react'
import type { AIClient, ChatMessage } from './aiClient'interface UseChatOptions {client: AIClientinitialMessages?: ChatMessage[]
}export function useChat({ client, initialMessages = [] }: UseChatOptions) {const [messages, setMessages] = useState<ChatMessage[]>(initialMessages)const [loading, setLoading] = useState(false)const [error, setError] = useState<string | null>(null)const send = useCallback(async (content: string) => {const text = content.trim()if (!text || loading) returnsetError(null)const userMsg: ChatMessage = { role: 'user', content: text }const newMessages = [...messages, userMsg]setMessages(newMessages)setLoading(true)try {const res = await client.chat({ messages: newMessages })const aiMsg: ChatMessage = {role: 'assistant',content: res.answer}setMessages((prev) => [...prev, aiMsg])} catch (e: any) {setError(e?.message || '请求失败')} finally {setLoading(false)}},[client, messages, loading])return { messages, loading, error, send }
}

作业 3:useStreamingChat hook(基于 streamChat

// useStreamingChat.ts
import { useState, useCallback, useRef } from 'react'
import type { AIClient, ChatMessage, ChatChunk } from './aiClient'interface UseStreamingChatOptions {client: AIClientinitialMessages?: ChatMessage[]
}export function useStreamingChat({client,initialMessages = []
}: UseStreamingChatOptions) {const [messages, setMessages] = useState<ChatMessage[]>(initialMessages)const [loading, setLoading] = useState(false)const [error, setError] = useState<string | null>(null)const abortRef = useRef<AbortController | null>(null)const send = useCallback(async (content: string) => {const text = content.trim()if (!text || loading) returnsetError(null)const userMsg: ChatMessage = { role: 'user', content: text }const baseMessages = [...messages, userMsg]setMessages(baseMessages)const aiMsgId = Symbol('aiMsg') // 本地标记let currentAI: ChatMessage = { role: 'assistant', content: '' }setMessages((prev) => [...prev, currentAI])setLoading(true)try {// 不使用 AbortController 控制 fetch 本身,因为 AsyncIterable 内部已封装for await (const chunk of client.streamChat({ messages: baseMessages })) {if (chunk.type === 'delta' && chunk.content) {currentAI = {...currentAI,content: currentAI.content + chunk.content}setMessages((prev) => {const next = [...prev]next[next.length - 1] = currentAIreturn next})} else if (chunk.type === 'error') {throw new Error(chunk.error || '流式错误')}}} catch (e: any) {setError(e?.message || '流式请求失败')} finally {setLoading(false)abortRef.current = null}},[client, messages, loading])return { messages, loading, error, send }
}

明日学习计划预告(Day 41)

  • 主题:LangChain 复杂 Chain(Router / Parallel / Map-Reduce)
  • 方向
    • 用 Router Chain 按“问答/代码/报表”路由到不同链路
    • 前端只需要感知一个统一的 /smart-chat 接口,由后端 Chain 决定内部流程

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

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

相关文章

AI对比:传统刷题与智能生成Flutter面试准备

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 创建一个对比展示应用&#xff0c;左侧显示传统方式(静态题库)&#xff0c;右侧展示AI生成方式。实现功能&#xff1a;1)相同题目两种解答方式对比&#xff1b;2)学习时间统计对比&…

Spring新手必看:5步搞定Bean初始化失败的简单教程

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 创建一个面向新手的SpringBoot错误指导应用&#xff1a;1. 用通俗语言解释Bean初始化原理 2. 分步演示典型错误场景 3. 提供可视化修复向导 4. 包含点击修复自动修正功能 5. 内置简…

AI如何自动清理Windows系统垃圾文件

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 开发一个基于AI的Windows系统清理工具&#xff0c;能够自动扫描系统垃圾文件&#xff08;如临时文件、缓存、日志等&#xff09;&#xff0c;使用机器学习模型分析文件重要性&#…

固件升级时fd一直增加,升级十几次后crash

固件升级流程,升级软件向app进程发送升级指令,在app中使用system(“./app.sh upgrade start”)执行脚本,启动upgrade进行升级包接收,同时关闭app进程;但发现升级完成后fd增加,app进程打开的fd存在两份; 分析原因…

2025长沙美甲美睫培训学校TOP5权威推荐:速成班费用与优 - myqiye

当下美业市场蓬勃发展,据湖南省美容美发化妆品行业协会数据,2024年省内美甲美睫师岗位缺口超2万个,速成培训班需求激增,但行业乱象频发:超60%学员遭遇隐形消费,35%机构课程与市场潮流脱节,28%零基础学员因大班教…

5分钟用MySQL存储过程搭建业务逻辑原型

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 开发一个快速原型工具&#xff0c;允许用户&#xff1a;1. 通过自然语言描述业务逻辑&#xff1b;2. 自动生成对应的MySQL存储过程框架&#xff1b;3. 提供测试数据生成功能&#x…

基于CentOS 9的快速开发环境搭建指南

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 创建一个自动化脚本&#xff0c;用于在CentOS 9上快速配置Python和Node.js开发环境。包含常用开发工具安装、虚拟环境配置和示例项目模板。支持一键安装和配置&#xff0c;自动检测…

MySQL 中 COUNT (*) 与 COUNT (col) 区别

MySQL 中 COUNT (*) 与 COUNT (col) 区别一、功能本质:计数范围的 “天壤之别”COUNT(*)与COUNT(col)的核心差异,在于是否排除 NULL 值,这直接决定了计数结果的不同,也是后续性能差异的根源。1. COUNT (*):“不挑…

AI如何帮你快速掌握Java基础知识?

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 创建一个Java基础学习助手应用&#xff0c;包含以下功能&#xff1a;1) 交互式Java语法解释器&#xff0c;输入代码片段自动解析语法结构&#xff1b;2) 常见编程错误自动检测与修正…

CH585 CH584 CH592 RF PHY/Basic 2.4G 包格式

射频(Radio Frequency ,RF):以下是RF物理层信号的主要构成部分:RF使用2.4 GHz的ISM(工业、科学和医疗)频段。在2.4 GHz频段中,RF占用了40个频道,每个频道有2 MHz的带宽。 RF信号的结构通常由RF包(RF Packet)…

Fiddler抓包神器:AI如何帮你自动分析网络请求

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 开发一个基于Fiddler的AI辅助分析工具&#xff0c;能够&#xff1a;1. 自动识别和分类常见的API请求模式 2. 根据历史请求智能生成Mock响应数据 3. 检测异常请求和潜在安全问题 4. …

CentOS 9在企业级Web服务中的实战部署

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 开发一个在CentOS 9上部署高可用Web服务的自动化脚本。包含Nginx配置模板、MySQL优化参数和负载均衡设置。脚本应支持一键部署&#xff0c;自动检测系统资源并优化配置。使用Bash编…

2025年铝箔翅片定制厂家权威推荐榜单:铝防爆箔/亲水铝箔/铝翅片源头厂家精选 - 品牌推荐官

铝箔翅片作为现代高效换热器的核心传热元件,其性能直接决定了空调、制冷设备、工业冷却装置及新能源热管理系统的能效、可靠性与紧凑性。随着“双碳”目标下各行业对节能要求的提升以及设备小型化、轻量化的趋势,市场…

2025年内衬不锈钢复合管品牌推荐:新澎内衬不锈钢复合管质量 - mypinpai

在流体输送管道领域,内衬不锈钢复合管凭借防腐性能优+成本可控的核心优势,成为净水输送、石油化工、航空煤油等场景的优选方案。但市场产品质量参差不齐,如何选到靠谱品牌?本文围绕新澎内衬不锈钢复合管质量怎样新…

事倍功半是蠢蛋69 TODO

注释中的TODO 当代码写到一半或者任务没有完成时,在注释中添加TODO,可以让IDEA在TODO中显示未完成的部分,直接定位;

忘记密码?3分钟快速解锁密码锁的技巧

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 开发一个密码锁快速解锁指南应用&#xff0c;根据用户输入的密码锁类型&#xff08;数字、图案、指纹等&#xff09;&#xff0c;提供分步骤的图文/视频解锁教程。包含常见错误提示…

用Packet Tracer快速验证网络架构设计的3种方法

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 创建一个网络架构原型验证工具&#xff0c;基于Packet Tracer实现&#xff1a;1. 常见网络拓扑模板库 2. 配置导入/导出功能 3. AI辅助设计建议 4. 自动化测试脚本 5. 性能分析报告…

企业级Spring Boot项目中的AutoConfiguration.imports实战

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 为一个电商平台的Spring Boot项目创建自定义自动配置。首先定义3个自定义自动配置类&#xff1a;PaymentAutoConfiguration(支付)、InventoryAutoConfiguration(库存)和Recommendat…

JookDB在电商平台中的实战应用案例

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 开发一个电商平台演示系统&#xff0c;展示JookDB在以下场景的应用&#xff1a;1. 实时订单处理系统 2. 用户行为分析看板 3. 个性化推荐引擎。要求&#xff1a;使用JookDB作为主数…

《60天AI学习计划启动 | Day 38: 多会话 多 Tab 同步(前端层)》

Day 38:多会话 & 多 Tab 同步(前端层) 学习目标设计 多会话模型:当前会话 + 会话列表(标题/摘要/时间) 掌握 利用 localStorage + storage 事件 做多 Tab 状态同步 实现 简单的会话新建 / 重命名 / 归档逻辑…