vue通过ollama接口调用开源模型实现人机对话

先展示下最终效果:

vue通过ollama接口调用开源模型实现人机对话

 第一步:先安装ollama,并配置对应的开源大模型。

安装步骤可以查看上一篇博客:

ollama搭建本地ai大模型并应用调用

 第二步:需要注意两个配置,页面才可以调用
1)OLLAMA_HOST= "0.0.0.0:11434"
2)若应用部署服务器后想调用,需要配置:OLLAMA_ORIGINS=*
 
第三步:js流式调用大模型接口方法
async startStreaming(e) {       if(e.ctrkey&&e.keyCode==13){         this.form.desc+='n';       }       document.getElementById("txt_suiwen").disabled="true";       // 如果已经有一个正在进行的流式请求,则中止它       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:''                });                       // 创建一个新的 AbortController 实例              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 });                           // 处理流中的每一块数据,这里假设每块数据都是完整的 JSON 对象                  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 解析错误                      //console.error('Failed to parse JSON:', e);                    }                  }                          // 清空 result 以便处理下一块数据                  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="说说你想问点啥....按Enter键可直接发送" @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";       // 如果已经有一个正在进行的流式请求,则中止它       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:''                });                       // 创建一个新的 AbortController 实例              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 });                  // 处理流中的每一块数据,这里假设每块数据都是完整的 JSON 对象                  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 解析错误                      //console.error('Failed to parse JSON:', e);                    }                  }                  // 清空 result 以便处理下一块数据                  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() {     // 组件销毁时中止流式请求     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>

 

 
 

 

发表评论

相关文章