ai应用一篇通【react环境】
首先进行技术选型。1 Deepseek文本大模型其官方具备面向nodejs环境的openai调用模型的sdk。2 环境直接起一个react项目直接在前端项目中调用Deepseek仅学习用生产还是要起nodejs轻服务。一 流式多轮对话1.1 基础请求先发出一个简单的文本请求。下面的apiKey需要替换为自己的key在deepseek开发平台中获取。下面的代码包含了OpenAI的简单用法importOpenAIfromopenai;import{useState,useEffect}fromreact;constopenainewOpenAI({baseURL:https://api.deepseek.com,apiKey:xxxxxxxxxxxxxxxxxxxxx,dangerouslyAllowBrowser:true,});exportdefaultfunctionDeepseekPage(){const[chatList,setChatList]useState([])useEffect((){openai.chat.completions.create({messages:[{role:system,content:You are a helpful assistant.},{role:user,content:hello.},],model:deepseek-v4-flash,thinking:{type:disabled},stream:false,}).then(res{console.log(res.choices[0].message.content);});},[])return(div{chatList.map((msg,index)(div key{index}strong{msg.role}:/strong{msg.content}/div))}/div)}注意下面代码中打印了一个res.choices[0].message.content内容如下{role:assistant,content:Hi! How can I help you today?}其中content是内容role是角色。现在页面上已经展示了一行返回内容了。1.2 多轮对话多轮对话的核心是将第一轮中模型的输出添加到 messages 末尾。上面代码中第一次请求的参数是这样的[{role:system,content:You are a helpful assistant.},{role:user,content:hello.},]我们将返回体和用户的写入内容追加到后面使参数变为[{role:system,content:You are a helpful assistant.},{role:user,content:hello.},{role:assistant,content:Hi! How can I help you today?}{role:user,content:xxxxxx},]同时在用户点击发送后触发接口请求。现在我们要做两件事1 上面代码中的useEffect要重复触发触发条件是有新的用户信息产生时所以书写一个变量记录该信息列表chatList并将其加入到useEffect依赖项中。2 状态的触发需要入口所以添加input和submit入口。exportdefaultfunctionDeepseekPage(){const[chatList,setChatList]useState([{role:system,content:You are a helpful assistant.},{role:user,content:hello.},])const[input,setInput]useState()useEffect((){if(chatList[chatList.length-1].role!user)returnopenai.chat.completions.create({messages:chatList,model:deepseek-v4-flash,thinking:{type:disabled},stream:false,}).then(res{console.log(res.choices[0].message.content);setChatList([...chatList,res.choices[0].message])});},[chatList])return(div{chatList.map((msg,index)(div key{index}strong{msg.role}:/strong{msg.content}/div))}Space vertical sizemediumSpace.Compact style{{width:100%}}Input defaultValueCombine input and buttonvalue{input}onChange{(e)setInput(e.target.value)}/Button typeprimaryonClick{(){setChatList([...chatList,{role:user,content:input}])}}Submit/Button/Space.Compact/Space/div)}现在输入文字点击按钮就可以实现多轮对话了。1.3 流式传输现在ai返回的语句是一次性全部返回的等待时候较长。我们使用ai对话的时候可以让它陆续返回实现打字机效果就是流式传输了。首先将stream设置为true让其开启流式传输。其次要在js中补充承接逻辑。1.3.1 fetch如果使用fetch来处理返回值会返回两个重要的数据value和doneReading。doneReading是流式数据是否已全部返回。value是返回的内容使用new TextDecoder().decode(value)进行解析读取。返回的内容解析后可能出现的情况示例如下第一轮返回data:{choices:[{delta:{content:你第二轮返回}}]}\n\n可以看到解析的时候一个有效json会分成多次返回一个有效json由data开启由\n结束。整个代码逻辑如下1 判断是否已经全部返回否则循环获取数据。2 将本次数据与上次数据相结合形成本次校验是否形成有效json?数据chunkValue。3 将有效数据以’data: ‘为开头’\n’为结尾取出4 解析json串后赋值1.3.2 openai我们这里使用openai来处理。首先将openai.chat.completions.create中的stream设置为true让其开启流式传输。先把useEffect中的代码提取出来成为方法使用。因为这个方法是独立性比较强的所以不需要封装为hook。const getCharDateasync(dataList, callback, streamfalse){letresawait openai.chat.completions.create({messages: dataList, model:deepseek-v4-flash, thinking:{type:disabled}, stream: stream,})callback([...dataList, res.choices[0].message])}现在添加流式处理逻辑。openai已对其做了封装实现流式就比较容易。其会返回异步迭代器Async Iterator直接使用 for await…of 循环来逐块获取数据即可。forawait(const chunk of res){console.log(chunk)}上面打印出的chunk示例如下{id:5a3ce01f-1353-47ed-b355-0337dc890ff6, // 对话id 多组流式对话时靠它拼接object:chat.completion.chunk,created:1784792183,model:deepseek-v4-flash,system_fingerprint:fp_8b330d02d0_prod0820_fp8_kvcache_20260402,choices:[{index:0,delta:{role:assistant,content:// 数据放在这里},logprobs:null,finish_reason:null}]}现在根据chunk内容list有值则拼接无值则添加对话项将id记录在项中用来寻找对应项。forawait(constchunkofres){callback(prevList{constchartIndexItemprevList.findIndex(item{returnitem.charIdchunk.id})if(chartIndexItem-1){return[...prevList,{...chunk.choices[0].delta,charId:chunk.id}]}else{returnprevList.map(item{if(item.charIdchunk.id){return{...item,content:item.contentchunk.choices[0].delta.content}}else{returnitem}})}})}如此就实现了流式效果。1.3.3 设置node中间层上面是在前端项目中直接请求的openai接口实际项目中需要使用node中间层做中转以保护key。中转层在操作时需要注意一下几点:1 发出请求时注意请求头的正确性{Content-Type:text/event-stream,Cache-Control:no-cache,Connection:keep-alive,}返回数据时使用res.write陆续返回。数据返回结束后使用res.end();结束接口。其他逻辑和前端项目中的内容等同都是通过openai获取chund后对数据进行处理。前端可以使用fetch读取流式数据axios不支持。fetch读取流式数据的方式上面有介绍。使用response.body.getReader().read()读取内容new TextDecoder().decode(value)进行解码。response.body.getReader().read()反复读取直到返回的done为true时break。这里也可以使用EventSource但是EventSource仅支持get接口不支持太长的上下文传递。const eventSourcenew EventSource(url);eventSource.addEventListener(message, function(e: any){content.valuee.data;});eventSource.addEventListener(end,(){eventSource.close();});个人更推荐仍然使用openai来在前端读取只是不写秘钥把url改成node中间层的地址。二 图像生成我们仍然选用国内场景大模型可灵ai来做示范。可灵ai需要比文本请求多做一件事通过jsonwebtoken包换取token。当然这一步在生产上肯定是后端做的这里我们就放在前端了。npm i jsonwebtoken安装依赖。2.1 发布任务可灵ai首页左侧有开发者平台按钮开发者平台右上角能进入控制台买个试用资源包然后创建key。和文本一样在header中写Authorization “Bearer XXX”其中 XXX 填写第一步获取的 API Key。现在页面中准备好相关的内容。divstyle{{margin:10px}}Imagewidth{200}srchttps://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png//divSpace verticalsizemediumSpace.Compactstyle{{width:100%}}InputdefaultValueCombine input and buttonvalue{pInput}onChange{(e)setPInput(e.target.value)}/ButtontypeprimaryonClick{()getImage(pInput)}图片生成/Button/Space.Compact/Space然后在getImage事件中根据pInput的值获取图片把图片url给image组件即可。api调用文档https://klingai.com/document-api/api/image/2-1/image-generationconst getImageasync(imageInput){const endPointhttps://api-beijing.klingai.com/v1/images/generationsletresfetch(endPoint,{headers:{Content-Type:application/json,Authorization:Bearer xxxx,}, method:POST, body: JSON.stringify({prompt: imageInput, aspect_ratio:1:1,})})if(res.status400){throw new Error(Non-200 response: ${await res.text()});}const resJsonawait res.json();constidresJson.data.task_id;const resultUrl${endpoint}/${id};console.log(id, resultUrl)// while(true){// 循环获取图片内容直到生成成功 // 返回task_status标志位为succeed即成功 // 成功后task_result中有生成图片的url //}}可灵ai是不支持跨域的所以咋package.json中配置proxy来进行跨域。在文本框中输入文字后点击图片生成id。909536646579253261/v1/images/generations/909536646579253261根据这个id就可以来获取图片内容了。2.2 获取图片现在将while中的内容补全。while(true){// console.log(resJson,id)// 循环获取图片内容直到生成成功// 返回task_status标志位为succeed即成功// 成功后task_result中有生成图片的urlawaitnewPromise((resolve)setTimeout(resolve,100));// 预防堵塞 让出主线程constresultawaitfetch(resultUrl,{headers:{Content-Type:application/json,Authorization:Bearer xxxx,}});constresultJsonawaitresult.json();console.log(resultJson)consttaskStatusresultJson.data.task_status;if(taskStatussucceed){console.log(resultJson.data?.task_result.images[0].url)setImageUrl(resultJson.data?.task_result.images[0].url)returnresultJson.data?.task_result.images[0].url}else{continue}}setImageUrl是这个方法的第二个参数用来赋值图片地址。现在点击生成图片页面上就出现了图片。三 AI应用智能体-------------------------------------------------------------------------------------------------------------未完待续 持续更新