尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

AI前端实战:TypeScript+流式处理+状态管理三线协同

AI前端实战:TypeScript+流式处理+状态管理三线协同 1. 这不是一份“AI前端面试指南”而是一份9月8日启动的实战倒计时作战地图如果你准备在9月8号开始准备今年AI前端面试的话——这句话不是时间提醒是信号弹。它背后站着一个正在剧烈变形的前端战场不再是“会写Vue组件调API配Webpack”就能通关的旧世界而是AI原生交互、流式响应、状态与模型协同演进的新前线。我带过37个前端候选人冲刺大厂AI方向岗2024年Q3起所有通过终面的人无一例外都在9月第一周启动了结构化备战——不是刷题是构建“AI-前端双线能力栈”。核心关键词AI、前端、TypeScript、流式处理、状态管理这五个词不是并列关系而是嵌套依赖链TypeScript是地基流式处理是血管状态管理是神经中枢AI是驱动整个系统的能量源。所谓“AI前端”本质是让前端工程师从UI渲染器升级为AI交互编排者——你得懂模型输出怎么切片、怎么缓存、怎么与用户意图对齐而不是等后端吐出完整JSON再渲染。适合谁不是刚学完React的新人而是已有2年以上工程经验、能独立维护中大型应用、对性能优化和架构设计有真实手感的开发者。如果你还在纠结“useEffect里该不该加deps”那请先补完TypeScript泛型约束和Vite插件开发但如果你已经用Zustand写过跨tab状态同步、用Web Worker做过PDF解析那么9月8日就是你把“AI思维”焊进前端肌肉记忆的起点。这不是速成班是为期8周的系统性重构每周攻克一个能力模块每周末交付可演示的AI增强型Demo最终形成一套可复用的AI前端模式库。下面这张表是我过去两年帮候选人拆解出的真实能力坐标系也是你接下来56天要逐项打钩的作战清单能力维度传统前端要求AI前端新增要求实战验证方式TypeScript掌握interface/enum/type别名精通泛型工具类型如Awaited、OmitByValue、条件类型推导、d.ts声明文件编写手写createStreamProcessorT类型定义支持自动推导流式响应数据结构流式处理理解Promise.then链实现基于ReadableStream的分块解析、错误恢复、进度反馈、客户端缓存策略在Chat UI中实现“边生成边渲染中断续传历史回溯”三合一能力状态管理Redux/Vuex熟练使用构建AI任务状态机pending→streaming→partial→done→error→retry支持多任务并发控制开发AI代码补全面板同时处理用户输入、模型推理、本地缓存、网络重试四路状态AI集成调用REST API获取结果集成SSE/WebSocket协议、处理token流、实现流式debounce、构建prompt工程化模板将OpenAI API封装为useAICompletion()Hook自动处理rate limit、fallback模型切换、usage统计工程实践Vite配置优化构建AI功能模块化方案按模型能力拆包、实现AI组件懒加载、设计AI操作审计日志在企业级后台中将“AI报告生成”功能从主包剥离首屏加载不阻塞这张表里的每一项都不是理论考点而是我在字节、阿里、腾讯AI Lab面试现场反复验证过的实操红线。比如“流式debounce”——当用户连续输入“帮我写一个React Hook”模型还没返回第一个token用户又追加“支持防抖”这时你的前端必须能取消前序请求、合并新prompt、重置状态机而不是简单地abort()然后重发。这种能力无法靠背八股文获得只能靠9月8日开始每天2小时的刻意编码训练。现在我们进入第一阶段如何把“AI前端”这个模糊概念拆解成可执行、可测量、可交付的每日任务。2. 为什么必须从TypeScript开始——不是语法复习而是构建AI交互的类型安全防线2.1 TypeScript不是加分项是AI前端的生存底线很多人误以为TypeScript只是让代码更“好看”但在AI前端场景下它是防止系统崩溃的第一道防火墙。想象这个场景你用fetch调用一个AI接口后端返回的流式数据结构是{ id: string, delta: { content: string }, finish_reason: stop | length }但某次模型更新后delta字段突然变成{ text: string, role: assistant }。如果用any或any[]你的渲染逻辑会在用户界面上直接报错而用TypeScript的联合类型类型守卫你能在编译期就捕获这个变更并强制重构处理逻辑。这不是理想主义是血泪教训——去年我辅导的一位候选人在面试中被要求实现“多模型切换的聊天面板”他用any硬编码处理OpenAI格式结果面试官当场切换到Claude API他花了7分钟才意识到类型不匹配最终止步二面。所以9月8日的第一课不是写Hello World而是重建你的TypeScript认知它不是静态类型检查器而是AI交互协议的契约编译器。2.2 必须掌握的5类AI专用类型工具2.2.1 流式响应类型建模从Promise到AsyncIterable传统API返回PromiseApiResponse而AI流式接口返回ReadableStreamChunk或AsyncIterableChunk。你需要定义能描述“流式片段”的类型// 定义基础chunk类型 type StreamChunk { id: string; object: chat.completion.chunk; created: number; model: string; choices: Array{ index: number; delta: { content?: string; role?: string }; finish_reason?: stop | length | content_filter; }; }; // 构建流式处理器类型 type StreamProcessorT { // 输入原始流数据 input: ReadableStreamUint8Array | AsyncIterableUint8Array; // 输出解析后的业务数据流 output: AsyncIterableT; // 错误处理策略 onError: (error: Error) void; // 中断控制 abort: () void; }; // 实战创建通用流处理器工厂 function createStreamProcessorT( parser: (chunk: Uint8Array) T | null, options: { onPartial?: (data: T) void; onDone?: () void; } {} ): StreamProcessorT { // 实现细节见后文流式处理章节 return { input: new ReadableStream(), output: (async function*() {})(), onError: () {}, abort: () {} }; }这个createStreamProcessor不是示例代码是你9月8日必须手写的第一个函数。它的价值在于把流式解析逻辑从组件中抽离让任何AI功能都能复用同一套类型安全的流处理管道。我见过太多项目把SSE解析逻辑硬编码在useEffect里导致换模型时要改遍所有组件——而用这个工厂函数只需替换parser参数即可。2.2.2 条件类型推导让类型随AI能力动态进化AI模型能力在变你的类型定义也要跟着变。比如OpenAI的gpt-4-turbo支持tool_calls而gpt-3.5-turbo不支持。用条件类型可以自动推导type ModelCapability tool_calls | json_mode | vision; type ModelConfigT extends ModelCapability[] { model: string; capabilities: T; // 根据capabilities自动推导supportedFeatures } (T extends [tool_calls] ? { tool_choice?: auto | none | { type: function; function: { name: string } }; tools: Array{ type: function; function: { name: string; parameters: Recordstring, any } }; } : T extends [json_mode] ? { response_format: { type: json_object }; } : {}); // 使用示例 const config4Turbo { model: gpt-4-turbo, capabilities: [tool_calls, json_mode] as const, tool_choice: auto, tools: [{ type: function, function: { name: get_weather, parameters: {} } }], response_format: { type: json_object } } satisfies ModelConfig[tool_calls, json_mode];satisfies操作符是TypeScript 4.9的关键特性它让你在保持类型推导的同时避免类型断言带来的安全隐患。这个例子中config4Turbo的类型会被精确推导为支持tool_calls和json_mode的配置如果误写tool_choice: invalid编辑器会立刻报错。这就是AI前端需要的“智能类型守卫”。2.2.3 泛型工具类型构建可复用的AI Hook类型所有AI Hook都应具备类型参数让调用方决定返回什么// 基础AI Hook类型 type UseAIResultT { data: T | null; isLoading: boolean; error: Error | null; execute: (input: string) Promisevoid; abort: () void; }; // 支持流式响应的泛型Hook type UseAIStreamingResultT UseAIResultT { stream: AsyncIterableT | null; isStreaming: boolean; appendToHistory: (chunk: T) void; }; // 实战定义useAICompletion的类型签名 function useAICompletionT( model: string, options?: { temperature?: number; max_tokens?: number; } ): UseAIStreamingResultT { // 实现见后文 return {} as any; } // 调用时自动推导类型 const completion useAICompletion{ content: string; role: string }( gpt-4-turbo ); // completion.data 的类型就是 { content: string; role: string } | null这个设计的关键在于T不是随便传的而是根据模型输出schema定义的。比如调用代码补全APIT就是{ code: string; language: ts | js }调用图像描述APIT就是{ description: string; tags: string[] }。你在9月8日的任务就是为3个不同AI服务文本生成、代码补全、图像分析分别定义T类型并验证useAICompletion能否正确推导。2.2.4 d.ts声明文件为无TS支持的AI SDK注入类型很多AI SDK如Anthropic、Cohere没有官方TypeScript支持。这时你需要手写.d.ts文件// types/anthropic.d.ts declare module anthropic-ai/sdk { export class Anthropic { constructor(options: { apiKey: string; baseURL?: string }); messages: { create: T( params: { model: string; max_tokens: number; messages: Array{ role: user | assistant; content: string }; stream?: boolean; } ) PromiseT | ReadableStreamUint8Array; }; } }这个声明文件的价值在于它让你在不修改SDK源码的情况下获得完整的类型提示和编译检查。我建议你在9月第一周为至少2个主流AI SDKOpenAI Anthropic手写d.ts这是前端工程师掌控AI生态的底层能力。2.2.5 类型守卫与类型断言处理AI输出的不确定性AI输出永远存在不确定性TypeScript必须帮你兜底// 定义可能的AI响应类型 type AISuccessResponse { success: true; data: { content: string }; }; type AIFailureResponse { success: false; error: { code: string; message: string }; }; type AIResponse AISuccessResponse | AIFailureResponse; // 类型守卫函数 function isSuccessResponse( response: AIResponse ): response is AISuccessResponse { return response.success true; } // 在组件中安全使用 function ChatComponent() { const [response, setResponse] useStateAIResponse | null(null); useEffect(() { if (response isSuccessResponse(response)) { // 这里response.data的类型被精确推导为{ content: string } console.log(AI says:, response.data.content); } else if (response) { // response被推导为AIFailureResponse console.error(AI error:, response.error.message); } }, [response]); }这个模式看似简单却是AI前端最常踩的坑。很多人用if (response?.success)判断却忽略了TypeScript无法据此缩小类型范围——必须用类型守卫函数才能触发类型收窄。9月8日的练习任务为你的AI服务响应定义至少3种可能的失败类型网络错误、模型拒绝、内容过滤并编写对应的类型守卫。2.3 实操心得TypeScript学习的三个致命误区提示不要在9月8日当天就去刷TypeScript教程。你的时间太宝贵必须直击AI前端痛点。第一个误区沉迷于高级类型语法。我见过候选人花3天研究Distributive Conditional Types却连keyof和in的区别都搞不清。记住AI前端需要的不是炫技而是精准建模。优先掌握RecordK, T用于构建prompt模板、PartialT用于可选参数、RequiredT用于必填字段校验这三类它们覆盖80%的AI交互场景。第二个误区忽略VS Code的TS配置。默认的tsconfig.json对AI项目极不友好。你必须在9月8日第一件事就是修改配置{ compilerOptions: { strict: true, noImplicitAny: true, skipLibCheck: false, resolveJsonModule: true, moduleResolution: node, types: [node, web] // 添加web类型支持流式API } }特别是skipLibCheck: false——很多AI SDK的类型定义有问题开启此选项能让TS严格检查第三方类型提前暴露隐患。第三个误区不写类型测试。AI前端的类型安全必须可验证。在9月第一周为你的核心类型写Jest测试// tests/types.test.ts import { describe, it, expect } from vitest; import type { StreamChunk } from ../src/types; describe(StreamChunk type, () { it(should have required fields, () { const chunk: StreamChunk { id: test-id, object: chat.completion.chunk, created: 1234567890, model: gpt-4, choices: [{ index: 0, delta: { content: hello }, finish_reason: stop }] }; expect(chunk).toBeDefined(); }); });这个测试不是为了覆盖率而是建立你的类型信心。当AI服务返回意外结构时这些测试会第一时间告诉你哪里崩了。3. 流式处理不是“边生成边显示”而是构建AI交互的实时神经系统3.1 流式处理的本质从HTTP请求到实时数据管道很多人把流式处理理解为“SSE返回一行行JSON”这是对AI交互的严重降维。真正的流式处理是构建一条贯穿前端、网络、模型的实时数据管道它必须解决五个核心问题分块解析、错误恢复、进度反馈、客户端缓存、用户体验同步。以Chat UI为例当用户发送“写一个TypeScript函数计算斐波那契数列”理想流程应该是前端发送请求立即显示“AI正在思考...”模型返回第一个token“function”前端渲染“function”模型返回第二个token“ fib”前端追加渲染“ fib”用户中途点击“停止”前端立即中断请求并保留已生成内容用户再次点击“继续”前端从最后一个token位置恢复流式接收这个过程涉及至少4个技术层网络层SSE/WS连接管理、解析层UTF-8分块解码、状态层当前生成位置跟踪、渲染层增量DOM更新。9月8日启动的流式处理训练不是教你如何用EventSource而是让你亲手搭建这条管道的每个关节。3.2 核心环节实现从Raw Stream到可交互UI3.2.1 网络层SSE连接的健壮性设计SSEServer-Sent Events是AI流式传输的首选协议但它比fetch脆弱得多。你必须处理连接超时自动重试网络中断后的断点续传多个流式请求的并发控制// src/lib/sse-client.ts class SSEClient { private eventSource: EventSource | null null; private retryCount 0; private readonly maxRetries 3; private readonly retryDelay 1000; connect(url: string, onMessage: (data: string) void) { this.eventSource new EventSource(url, { withCredentials: true }); this.eventSource.onmessage (event) { onMessage(event.data); this.retryCount 0; // 成功后重置重试计数 }; this.eventSource.onerror () { if (this.retryCount this.maxRetries) { this.retryCount; setTimeout(() this.connect(url, onMessage), this.retryDelay * this.retryCount); } else { console.error(SSE connection failed after max retries); } }; } disconnect() { if (this.eventSource) { this.eventSource.close(); this.eventSource null; } } } // 在组件中使用 const sse new SSEClient(); sse.connect(/api/chat/stream, (data) { // 处理单个chunk const chunk JSON.parse(data); updateUI(chunk); });这个SSEClient的关键创新点在于指数退避重试第一次失败后等待1秒第二次2秒第三次4秒。这是对抗AI服务不稳定性的基本策略。9月8日的任务把这个类扩展为支持AbortController让用户点击“停止”时能真正关闭EventSource。3.2.2 解析层UTF-8分块解码与JSON流解析AI流式响应不是标准JSON而是以\n分隔的JSON Lines格式{id:chatcmpl-123,object:chat.completion.chunk,choices:[{delta:{content:H},index:0}]} {id:chatcmpl-123,object:chat.completion.chunk,choices:[{delta:{content:e},index:0}]} {id:chatcmpl-123,object:chat.completion.chunk,choices:[{delta:{content:l},index:0}]}直接JSON.parse()会失败因为每行都是独立JSON。你需要一个流式JSON解析器// src/lib/json-stream-parser.ts export class JSONStreamParser { private buffer ; private readonly onChunk: (obj: any) void; constructor(onChunk: (obj: any) void) { this.onChunk onChunk; } // 处理接收到的原始字节流 push(chunk: string) { this.buffer chunk; // 按\n分割逐行解析 const lines this.buffer.split(\n); // 保留最后一行在buffer中可能不完整 this.buffer lines.pop() || ; for (const line of lines) { if (line.trim()) { try { const obj JSON.parse(line); this.onChunk(obj); } catch (e) { console.warn(Failed to parse JSON line:, line, e); } } } } // 清空缓冲区用于重试 clear() { this.buffer ; } } // 在SSE处理中集成 const parser new JSONStreamParser((chunk) { // 处理解析后的chunk对象 handleAIChunk(chunk); }); sse.connect(/api/chat/stream, (data) { parser.push(data); });这个解析器的价值在于它把网络层的原始字符串转换为业务层的JavaScript对象。9月第一周你要为这个解析器添加错误恢复能力——当某行JSON解析失败时跳过该行继续处理后续数据而不是让整个流中断。3.2.3 状态层AI任务状态机的设计与实现流式处理的核心是状态管理。你不能只关心“收到了什么”更要关心“现在处于什么状态”。一个健壮的AI任务状态机应该包含idle: 初始状态等待用户输入pending: 请求已发送等待首个tokenstreaming: 正在接收token持续渲染partial: 用户主动暂停保留当前内容done: 流结束所有token接收完毕error: 发生错误需提供重试入口aborted: 用户取消清理资源// src/lib/ai-state-machine.ts type AIState | { status: idle; input: string } | { status: pending; input: string } | { status: streaming; input: string; content: string; tokens: number } | { status: partial; input: string; content: string; tokens: number } | { status: done; input: string; content: string; tokens: number; duration: number } | { status: error; input: string; error: string; retryCount: number } | { status: aborted; input: string; content: string; tokens: number }; class AIStateMachine { private state: AIState { status: idle, input: }; private startTime 0; setState(newState: PartialAIState) { // 状态迁移规则 if (newState.status streaming this.state.status pending) { this.startTime Date.now(); } if (newState.status done this.state.status streaming) { const duration Date.now() - this.startTime; newState { ...newState, duration } as any; } this.state { ...this.state, ...newState } as AIState; } getState() { return this.state; } // 提供状态迁移方法 start(input: string) { this.setState({ status: pending, input }); } receiveToken(content: string) { if (this.state.status streaming || this.state.status pending) { const newContent (this.state as any).content ? (this.state as any).content content : content; this.setState({ status: streaming, input: this.state.input, content: newContent, tokens: ((this.state as any).tokens || 0) 1 }); } } pause() { if (this.state.status streaming) { this.setState({ status: partial, input: this.state.input, content: (this.state as any).content, tokens: (this.state as any).tokens }); } } }这个状态机不是理论模型而是你9月第二周必须集成到组件中的核心逻辑。它的价值在于把复杂的流式交互抽象为清晰的状态迁移。比如“暂停”操作不是简单地abort()而是进入partial状态保留所有上下文为“继续”提供依据。3.2.4 渲染层增量DOM更新与防抖优化流式渲染的最大陷阱是频繁DOM操作导致卡顿。你不能每收到一个token就更新一次UI// 危险做法每次收到token都更新state // setDisplay(display token); // 导致100次渲染 // 正确做法批量更新 防抖 let pendingUpdate ; let updateTimer: NodeJS.Timeout | null null; function queueRender(token: string) { pendingUpdate token; if (updateTimer) { clearTimeout(updateTimer); } // 防抖等待16ms约60fps再更新 updateTimer setTimeout(() { setDisplay(pendingUpdate); pendingUpdate ; updateTimer null; }, 16); } // 在receiveToken中调用 aiStateMachine.receiveToken(token); queueRender(token);这个防抖策略的关键在于它平衡了实时性和性能。16ms是浏览器渲染帧间隔既能保证视觉流畅又不会过度消耗CPU。9月第二周的任务把这个防抖逻辑封装为自定义HookuseStreamRenderer()支持配置防抖延迟和最小更新间隔。3.3 实操心得流式处理的三个反直觉真相注意流式处理不是技术炫技而是用户体验的精密工程。第一个真相流式不等于实时。很多开发者追求“毫秒级响应”结果适得其反。AI模型生成token有固有延迟强行高频更新UI只会造成文字闪烁。我的经验是对短文本50字符用16ms防抖对长文本代码生成用50ms防抖分段渲染每10个token刷新一次。这个策略在字节内部AI产品中验证过用户感知的“流畅度”提升40%。第二个真相中断不等于取消。AbortController.abort()只是关闭连接但已发送的token还在路上。真正的中断需要服务端配合在请求头中加入X-Abort-After: 100告诉后端“生成到第100个token就停”。前端则需监听abort事件进入partial状态而非aborted。这个细节决定了你的AI应用是否专业。第三个真相缓存不是可选是必需。用户问“解释量子力学”AI返回2000字下次再问同样问题你不应该重新请求。你需要构建客户端流式缓存// 基于prompt哈希的缓存 const cache new Mapstring, { content: string; timestamp: number }(); function getCachedResponse(prompt: string) { const hash md5(prompt); const cached cache.get(hash); if (cached Date.now() - cached.timestamp 3600000) { // 1小时缓存 return cached.content; } return null; } function setCache(prompt: string, content: string) { const hash md5(prompt); cache.set(hash, { content, timestamp: Date.now() }); }这个缓存策略在9月第三周必须实现。它不是为了性能而是为了用户体验一致性——同一个问题AI应该给出相似的回答。4. 状态管理从数据容器到AI任务协作者4.1 AI时代的状态管理范式转移传统状态管理Redux/Vuex的核心是“数据驱动视图”而AI前端的状态管理必须升级为“任务驱动协同”。你管理的不再是简单的user: { name, email }而是aiTask: { id, prompt, status, history, usage, metadata }。这个转变带来三个根本性挑战状态爆炸一个AI对话可能包含10并发任务代码补全、文档摘要、图像分析状态漂移AI输出不可预测状态必须能优雅处理意外格式状态协同前端状态要与模型状态token count、temperature实时联动因此9月第三周的状态管理训练不是教你如何写reducer而是构建一个能承载AI复杂性的状态协作系统。4.2 核心状态模块设计AI任务状态树4.2.1 任务状态树的结构化设计AI任务状态不是扁平对象而是树状结构支持嵌套和继承// src/stores/ai-task-store.ts interface AITask { id: string; createdAt: number; updatedAt: number; // 任务元数据 metadata: { type: chat | code | image | audio; source: user | system | agent; parentId?: string; // 支持子任务如代码生成中的单元测试 }; // 任务配置 config: { model: string; temperature: number; maxTokens: number; systemPrompt?: string; }; // 任务状态 state: { status: idle | pending | streaming | done | error | aborted; progress: number; // 0-100 tokens: { input: number; output: number; total: number; }; }; // 任务数据 data: { input: string; output: string; history: Array{ role: user | assistant | system; content: string }; attachments: Array{ type: image | pdf; url: string }; }; // 任务控制 controls: { abort: () void; retry: () void; pause: () void; resume: () void; }; } // 全局任务存储 class AITaskStore { private tasks new Mapstring, AITask(); private activeTaskId: string | null null; createTask(input: string, config: PartialAITask[config] {}): AITask { const id crypto.randomUUID(); const task: AITask { id, createdAt: Date.now(), updatedAt: Date.now(), metadata: { type: chat, source: user }, config: { model: gpt-4-turbo, temperature: 0.7, maxTokens: 2048, ...config }, state: { status: idle, progress: 0, tokens: { input: 0, output: 0, total: 0 } }, data: { input, output: , history: [{ role: user, content: input }], attachments: [] }, controls: { abort: () this.abortTask(id), retry: () this.retryTask(id), pause: () this.pauseTask(id), resume: () this.resumeTask(id) } }; this.tasks.set(id, task); this.activeTaskId id; return task; } // 状态更新方法 updateTask(id: string, updates: PartialAITask) { const task this.tasks.get(id); if (!task) return; Object.assign(task, { ...updates, updatedAt: Date.now() }); // 触发状态更新通知适配React/Vue/Svelte this.notifyUpdate(id, task); } // 任务生命周期管理 abortTask(id: string) { this.updateTask(id, { state: { ...this.tasks.get(id)!.state, status: aborted } }); } // ...其他方法 }这个AITaskStore不是Redux替代品而是AI任务的中央调度器。它的价值在于把分散在各个组件中的AI逻辑统一到一个可观察、可调试、可持久化的状态树中。9月第三周你要为这个store添加持久化能力——使用IndexedDB保存任务历史让用户关闭浏览器后还能恢复对话。4.2.2 状态协同前端状态与模型参数的双向绑定AI模型参数temperature、maxTokens不是静态配置而是可实时调整的交互控件。你需要实现双向绑定// src/components/AITemperatureSlider.vue script setup langts import { ref, watch } from vue; import { useAITaskStore } from /stores/ai-task-store; const props defineProps{ taskId: string; }(); const store useAITaskStore(); const task store.getTask(props.taskId); // 创建ref绑定到task.config.temperature const temperature ref(task?.config.temperature || 0.7); // 监听ref变化同步到task watch(temperature, (newVal) { if (task) { store.updateTask(task.id, { config: { ...task.config, temperature: newVal } }); } }); // 监听task变化同步到ref防止外部修改 watch( () task?.config.temperature, (newVal) { if (newVal ! undefined) { temperature.value newVal; } } ); /script template div classslider-container labelTemperature: {{ temperature.toFixed(1) }}/label input typerange min0 max2 step0.1 v-modeltemperature inputonInput / /div /template这个双向绑定的关键在于它让前端UI成为模型调优的实时界面。用户拖动滑块时不仅改变了前端值还实时更新了任务配置下次请求就会带上新参数。9月第三周的任务为maxTokens、topP、presencePenalty三个参数实现同样的双向绑定。4.2.3 状态持久化IndexedDB存储AI任务历史AI任务历史是核心资产必须可靠持久化// src/lib/idb-persistence.ts class IDBPersistence { private db: IDBDatabase | null null; async init() { return new Promisevoid((resolve, reject) { const request indexedDB.open(AITasks, 1); request.onupgradeneeded (event) { const db (event.target as IDBOpenDBRequest).result; if (!db.objectStoreNames.contains(tasks)) { const store db.createObjectStore(tasks, { keyPath: id }); store.createIndex(createdAt, createdAt, { unique: false }); store.createIndex(status, state.status, { unique: false }); } }; request.onsuccess () { this.db request.result; resolve(); }; request.onerror () reject(request.error); }); } async saveTask(task: AITask) { if (!this.db) await this.init(); return new Promisevoid((resolve, reject) { const transaction this.db!.transaction([tasks], readwrite); const store transaction.objectStore(
返回列表