vue通過(guò)ollama接口調(diào)用開(kāi)源模型實(shí)現(xiàn)人機(jī)對(duì)話功能
先展示下最終效果:

第一步:先安裝ollama,并配置對(duì)應(yīng)的開(kāi)源大模型。
安裝步驟可以查看上一篇博客:
ollama搭建本地ai大模型并應(yīng)用調(diào)用
第二步:需要注意兩個(gè)配置,頁(yè)面才可以調(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="說(shuō)說(shuō)你想問(wèn)點(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() {
// 組件銷(xiāo)毀時(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通過(guò)ollama接口調(diào)用開(kāi)源模型實(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í)不難問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03
VUE引入DataV報(bào)錯(cuò)解決實(shí)戰(zhàn)記錄
在使用vue開(kāi)發(fā)大屏?xí)r,發(fā)現(xiàn)了一個(gè)很好用的可視化組件庫(kù)DataV,下面這篇文章主要給大家介紹了關(guān)于VUE引入DataV報(bào)錯(cuò)解決的實(shí)戰(zhàn)記錄,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-04-04
el-table實(shí)現(xiàn)給每行添加loading效果案例
這篇文章主要介紹了el-table實(shí)現(xiàn)給每行添加loading效果案例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08
vue項(xiàng)目中常用解決跨域的方法總結(jié)(CORS和Proxy)
在vue項(xiàng)目中,一般我們會(huì)遇到跨域的問(wèn)題,vue項(xiàng)目中解決跨域是非常簡(jiǎn)單的,下面這篇文章主要給大家介紹了關(guān)于vue項(xiàng)目中常用解決跨域的方法,主要解釋CROS和Proxy兩種方式,需要的朋友可以參考下2022-12-12
基于Vuejs和Element的注冊(cè)插件的編寫(xiě)方法
這篇文章主要介紹了基于Vuejs和Element的注冊(cè)插件的編寫(xiě)方法,需要的朋友可以參考下2017-07-07
vite(vue3)配置內(nèi)網(wǎng)ip訪問(wèn)的方法步驟
Vite是一個(gè)快速的構(gòu)建工具,Vue3是一個(gè)流行的JavaScript框架,下面這篇文章主要給大家介紹了關(guān)于vite(vue3)配置內(nèi)網(wǎng)ip訪問(wèn)的方法步驟,文中通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下2023-05-05
詳解如何寫(xiě)出一個(gè)利于擴(kuò)展的vue路由配置
這篇文章主要介紹了詳解如何寫(xiě)出一個(gè)利于擴(kuò)展的vue路由配置,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-05-05

