vue通過ollama接口調(diào)用開源模型實(shí)現(xiàn)人機(jī)對(duì)話功能
先展示下最終效果:
第一步:先安裝ollama,并配置對(duì)應(yīng)的開源大模型。
安裝步驟可以查看上一篇博客:
ollama搭建本地ai大模型并應(yīng)用調(diào)用
第二步:需要注意兩個(gè)配置,頁面才可以調(diào)用
1)OLLAMA_HOST= "0.0.0.0:11434"
2)若應(yīng)用部署服務(wù)器后想調(diào)用,需要配置:OLLAMA_ORIGINS=*
第三步:js流式調(diào)用大模型接口方法
async startStreaming(e) { if(e.ctrkey&&e.keyCode==13){ this.form.desc+='\n'; } document.getElementById("txt_suiwen").disabled="true"; // 如果已經(jīng)有一個(gè)正在進(jìn)行的流式請(qǐng)求,則中止它 if (this.controller) { this.controller.abort(); } setTimeout(()=>{ this.scrollToBottom(); },50); var mymsg=this.form.desc.trim(); if(mymsg.length>0){ this.form.desc=''; this.message.push({ user:this.username, msg:mymsg }) this.message.push({ user:'GPT', msg:'', dot:'' }); // 創(chuàng)建一個(gè)新的 AbortController 實(shí)例 this.controller = new AbortController(); const signal = this.controller.signal; this.arequestData.messages.push({role:"user",content:mymsg}); try { const response = await fetch('http://127.0.0.1:11434/api/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body:JSON.stringify(this.arequestData), signal }); if (!response.body) { this.message[this.message.length-1].msg='ReadableStream not yet supported in this browser.'; throw new Error('ReadableStream not yet supported in this browser.'); } const reader = response.body.getReader(); const decoder = new TextDecoder(); let result = ''; this.message[this.message.length-1].dot='?'; while (true) { const { done, value } = await reader.read(); if (done) { break; } result += decoder.decode(value, { stream: true }); // 處理流中的每一塊數(shù)據(jù),這里假設(shè)每塊數(shù)據(jù)都是完整的 JSON 對(duì)象 const jsonChunks = result.split('\n').filter(line => line.trim()); //console.log(result) for (const chunk of jsonChunks) { try { const data = JSON.parse(chunk); //console.log(data.message.content) this.message[this.message.length-1].msg+=data.message.content; setTimeout(()=>{ this.scrollToBottom(); },50); } catch (e) { //this.message[this.message.length-1].msg=e; // 處理 JSON 解析錯(cuò)誤 //console.error('Failed to parse JSON:', e); } } // 清空 result 以便處理下一塊數(shù)據(jù) result = ''; } } catch (error) { if (error.name === 'AbortError') { console.log('Stream aborted'); this.message[this.message.length-1].msg='Stream aborted'; } else { console.error('Streaming error:', error); this.message[this.message.length-1].msg='Stream error'+error; } } this.message[this.message.length-1].dot=''; this.arequestData.messages.push({ role: 'assistant',//this.message[this.message.length-1].user,//"GPT", content: this.message[this.message.length-1].msg }) setTimeout(()=>{ this.scrollToBottom(); },50); }else{ this.form.desc=''; } document.getElementById("txt_suiwen").disabled=""; document.getElementById("txt_suiwen").focus(); } }
vue完整代碼如下:
<template> <el-row :gutter="12" class="demo-radius"> <div class="radius" :style="{ borderRadius: 'base' }"> <div class="messge" id="messgebox" ref="scrollDiv"> <ul> <li v-for="(item, index) in message" :key="index" style="list-style-type:none;"> <div v-if="item.user == username" class="mymsginfo" style="float:right"> <div> <el-avatar style="float: right;margin-right: 30px;background: #01bd7e;"> <!-- {{ item.user.substring(0, 2) }} --> <img :alt="item.user.substring(0, 2)" :src=userphoto /> </el-avatar> </div><div style="float: right;margin-right: 10px;margin-top:10px;width:80%;text-align: right;"> {{ item.msg }} </div> </div> <div v-else class="chatmsginfo" > <div> <el-avatar style="float: left;margin-right: 10px;"> {{ item.user }} </el-avatar> </div> <div style="float: left;margin-top:10px;width:80%;"> <img alt="loading" v-if="item.msg == ''" class="loading" src="../../assets/loading.gif"/> <MdPreview style="margin-top:-20px;" :autoFoldThreshold="9999" :editorId="id" :modelValue=" item.msg + item.dot " /> <!-- {{ item.msg }} --> </div> </div> </li> </ul> </div> <div class="inputmsg"> <el-form :model="form" > <el-form-item > <el-avatar style="float: left;background: #01bd7e;margin-bottom: -44px;margin-left: 4px;z-index: 999;width: 30px;height: 30px;"> <img alt="jin" :src=userphoto /> </el-avatar> <el-input id="txt_suiwen" :prefix-icon="userphoto" resize="none" autofocus="true" :autosize="{ minRows: 1, maxRows: 2 }" v-model="form.desc" placeholder="說說你想問點(diǎn)啥....按Enter鍵可直接發(fā)送" @keydown.enter.native.prevent="startStreaming($event)" type="textarea" /> </el-form-item> </el-form> </div> </div> </el-row> </template> <script setup> import { MdPreview, MdCatalog } from 'md-editor-v3'; import 'md-editor-v3/lib/preview.css'; const id = 'preview-only'; </script> <script> export default { data() { return { form: { desc: '' }, message:[], username:sessionStorage.name, userphoto:sessionStorage.photo, loadingtype:false, controller: null, arequestData : { model: "qwen2",//"llama3.1", messages: [] } } }, mounted() { }, methods: { scrollToBottom() { let elscroll=this.$refs["scrollDiv"]; elscroll.scrollTop = elscroll.scrollHeight+30 }, clearForm(formName){ this.form.desc=''; }, async startStreaming(e) { if(e.ctrkey&&e.keyCode==13){ this.form.desc+='\n'; } document.getElementById("txt_suiwen").disabled="true"; // 如果已經(jīng)有一個(gè)正在進(jìn)行的流式請(qǐng)求,則中止它 if (this.controller) { this.controller.abort(); } setTimeout(()=>{ this.scrollToBottom(); },50); var mymsg=this.form.desc.trim(); if(mymsg.length>0){ this.form.desc=''; this.message.push({ user:this.username, msg:mymsg }) this.message.push({ user:'GPT', msg:'', dot:'' }); // 創(chuàng)建一個(gè)新的 AbortController 實(shí)例 this.controller = new AbortController(); const signal = this.controller.signal; this.arequestData.messages.push({role:"user",content:mymsg}); try { const response = await fetch('http://127.0.0.1:11434/api/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body:JSON.stringify(this.arequestData), signal }); if (!response.body) { this.message[this.message.length-1].msg='ReadableStream not yet supported in this browser.'; throw new Error('ReadableStream not yet supported in this browser.'); } const reader = response.body.getReader(); const decoder = new TextDecoder(); let result = ''; this.message[this.message.length-1].dot='?'; while (true) { const { done, value } = await reader.read(); if (done) { break; } result += decoder.decode(value, { stream: true }); // 處理流中的每一塊數(shù)據(jù),這里假設(shè)每塊數(shù)據(jù)都是完整的 JSON 對(duì)象 const jsonChunks = result.split('\n').filter(line => line.trim()); //console.log(result) for (const chunk of jsonChunks) { try { const data = JSON.parse(chunk); //console.log(data.message.content) this.message[this.message.length-1].msg+=data.message.content; setTimeout(()=>{ this.scrollToBottom(); },50); } catch (e) { //this.message[this.message.length-1].msg=e; // 處理 JSON 解析錯(cuò)誤 //console.error('Failed to parse JSON:', e); } } // 清空 result 以便處理下一塊數(shù)據(jù) result = ''; } } catch (error) { if (error.name === 'AbortError') { console.log('Stream aborted'); this.message[this.message.length-1].msg='Stream aborted'; } else { console.error('Streaming error:', error); this.message[this.message.length-1].msg='Stream error'+error; } } this.message[this.message.length-1].dot=''; this.arequestData.messages.push({ role: 'assistant',//this.message[this.message.length-1].user,//"GPT", content: this.message[this.message.length-1].msg }) setTimeout(()=>{ this.scrollToBottom(); },50); }else{ this.form.desc=''; } document.getElementById("txt_suiwen").disabled=""; document.getElementById("txt_suiwen").focus(); } }, beforeDestroy() { // 組件銷毀時(shí)中止流式請(qǐng)求 if (this.controller) { this.controller.abort(); } } } </script> <style scoped> .radius{ margin:0 auto; } .demo-radius .title { color: var(--el-text-color-regular); font-size: 18px; margin: 10px 0; } .demo-radius .value { color: var(--el-text-color-primary); font-size: 16px; margin: 10px 0; } .demo-radius .radius { min-height: 580px; height: 85vh; width: 70%; border: 1px solid var(--el-border-color); border-radius: 14px; margin-top: 10px; } .messge{ width:96%; height:84%; /* border:1px solid red; */ margin: 6px auto; overflow: hidden; overflow-y: auto; } .inputmsg{ width:96%; height:12%; /* border:1px solid blue; */ border-top:2px solid #ccc; margin: 4px auto; padding-top: 10px; } .mymsginfo{ width:100%; height:auto; min-height:50px; } ::-webkit-scrollbar {width: 6px;height: 5px; } ::-webkit-scrollbar-track {background-color: rgba(0, 0, 0, 0.2);border-radius: 10px; } ::-webkit-scrollbar-thumb {background-color: rgba(0, 0, 0, 0.5);border-radius: 10px; } ::-webkit-scrollbar-button {background-color: #7c2929;height: 0;width: 0px; } ::-webkit-scrollbar-corner {background-color: black; } </style> <style> .el-textarea__inner{ padding-left: 45px; padding-top: .75rem; padding-bottom: .75rem; } </style>
到此這篇關(guān)于vue通過ollama接口調(diào)用開源模型實(shí)現(xiàn)人機(jī)對(duì)話的文章就介紹到這了,更多相關(guān)vue ollama接口人機(jī)對(duì)話內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Vue實(shí)現(xiàn)父子組件傳值其實(shí)不難
這篇文章主要介紹了Vue實(shí)現(xiàn)父子組件傳值其實(shí)不難問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03VUE引入DataV報(bào)錯(cuò)解決實(shí)戰(zhàn)記錄
在使用vue開發(fā)大屏?xí)r,發(fā)現(xiàn)了一個(gè)很好用的可視化組件庫DataV,下面這篇文章主要給大家介紹了關(guān)于VUE引入DataV報(bào)錯(cuò)解決的實(shí)戰(zhàn)記錄,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-04-04el-table實(shí)現(xiàn)給每行添加loading效果案例
這篇文章主要介紹了el-table實(shí)現(xiàn)給每行添加loading效果案例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08vue項(xiàng)目中常用解決跨域的方法總結(jié)(CORS和Proxy)
在vue項(xiàng)目中,一般我們會(huì)遇到跨域的問題,vue項(xiàng)目中解決跨域是非常簡單的,下面這篇文章主要給大家介紹了關(guān)于vue項(xiàng)目中常用解決跨域的方法,主要解釋CROS和Proxy兩種方式,需要的朋友可以參考下2022-12-12vite(vue3)配置內(nèi)網(wǎng)ip訪問的方法步驟
Vite是一個(gè)快速的構(gòu)建工具,Vue3是一個(gè)流行的JavaScript框架,下面這篇文章主要給大家介紹了關(guān)于vite(vue3)配置內(nèi)網(wǎng)ip訪問的方法步驟,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下2023-05-05