讯飞星火大模型V3.0 WebApi使用 文档说明:星火认知大模型Web文档 | 讯飞开放平台文档中心 (xfyun.cn)
实现效果
初始化 首先构建一个基础脚手架项目
用到如下依赖
1 2 3 4 5 6 7 8 9 "dependencies": { "crypto-js": "^4.2.0", "highlight.js": "^11.9.0", "marked": "^9.1.3", "pinia": "^2.1.7", "pinia-plugin-persistedstate": "^3.2.0", "vue": "^3.3.4", "vue-router": "^4.2.5" }
修改 main.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import './assets/main.css' import { createApp } from 'vue' import { createPinia } from 'pinia' import PiniaPluginPersistedstate from "pinia-plugin-persistedstate" import App from './App.vue' import router from './router' import highlight from 'highlight.js' import "highlight.js/styles/atom-one-dark.css" const app = createApp(App)const pinia = createPinia()pinia.use(PiniaPluginPersistedstate) app.use(pinia) app.use(router) app.directive("highlight" ,function (el ) { let blocks = el.querySelectorAll('pre code' ); blocks.forEach((block )=> { highlight.highlightBlock(block); }) }) app.mount('#app' )
TTSRecorder 新建 utils/TTSRecorder.js
这个文件封装了发送消息并相应消息的核心功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 import CryptoJS from "crypto-js" const APPID = '' const API_SECRET = '' const API_KEY = '' let total_res = "" ;function getWebsocketUrl ( ) { return new Promise ((resolve, reject ) => { var apiKey = API_KEY var apiSecret = API_SECRET var url = 'ws://spark-api.xf-yun.com/v3.1/chat' var host = location.host var date = new Date ().toGMTString() var algorithm = 'hmac-sha256' var headers = 'host date request-line' var signatureOrigin = `host: ${host} \ndate: ${date} \nGET /v3.1/chat HTTP/1.1` var signatureSha = CryptoJS.HmacSHA256(signatureOrigin, apiSecret) var signature = CryptoJS.enc.Base64.stringify(signatureSha) var authorizationOrigin = `api_key="${apiKey} ", algorithm="${algorithm} ", headers="${headers} ", signature="${signature} "` var authorization = btoa(authorizationOrigin) url = `${url} ?authorization=${authorization} &date=${date} &host=${host} ` resolve(url) }) } export default class TTSRecorder { constructor ({appId = APPID} = {} ) { this .appId = appId this .msgStore = null this .msgDom = null } connectWebSocket ( ) { return getWebsocketUrl().then(url => { let ttsWS if ('WebSocket' in window ) { ttsWS = new WebSocket(url) } else if ('MozWebSocket' in window ) { ttsWS = new MozWebSocket(url) } else { alert('浏览器不支持WebSocket' ) return } this .ttsWS = ttsWS ttsWS.onopen = e => { this .webSocketSend() } ttsWS.onmessage = e => { this .result(e.data) } ttsWS.onerror = e => { alert('WebSocket报错,请f12查看详情' ) console .error(`详情查看:${encodeURI (url.replace('wss:' , 'https:' ))} ` ) } ttsWS.onclose = e => { console .log(e) } }) } webSocketSend ( ) { var params = { "header" : { "app_id" : this .appId, }, "parameter" : { "chat" : { "domain" : "generalv3" , "temperature" : 0.5 , "max_tokens" : 1024 } }, "payload" : { "message" : { "text" : this .msgStore.list } } } console .log(params,'请求的参数' ) this .ttsWS.send(JSON .stringify(params)) } start (msgStore,msgDom ) { this .msgStore = msgStore this .msgDom = msgDom.value total_res = "" ; this .connectWebSocket().then(r => {}) } result (resultData ) { let jsonData = JSON .parse(resultData) jsonData.payload.choices.text.forEach(res => { this .msgStore.aiAddMsg(res.content,jsonData.header.status) this .msgDom.scrollTop = this .msgDom.scrollHeight + 500 }) if (jsonData.header.code !== 0 ) { alert(`提问失败: ${jsonData.header.code} :${jsonData.header.message} ` ) console .error(`${jsonData.header.code} :${jsonData.header.message} ` ) return } if (jsonData.header.code === 0 && jsonData.header.status === 2 ) { this .ttsWS.close() } } }
msgStore 新建 stores/msgStore.js
用于存放历史问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 import { defineStore } from 'pinia' import { marked } from 'marked' export const userMsgStore = defineStore("userMsgStore" ,{ persist: true , state: () => { return { list:[] } }, actions: { userAddMsg (msg ) { this .list.push({ role:"user" , content:marked(msg), status:2 }) }, aiAddMsg (content,status ) { let runMsg = this .list.find(i => i.status !== 2 ) if (!runMsg){ this .list.push({ role:"assistant" , content:content, status:status }) }else { runMsg.content += content runMsg.status = status if (status === 2 ){ runMsg.content = marked(runMsg.content) } } } }, })
编写界面代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 <template > <div class ="content" > <div class ="message" id ='message-box' > <div v-for ="(msg,index) in msgList" :key ="index" :class ="{ 'user':msg.role === 'user', 'assistant':msg.role === 'assistant' }" > <div > <div > <img class ='role-img' :src ="userImg" v-if ="msg.role === 'user'" /> </div > <div class ='imgbox' v-if ="msg.role === 'assistant'" > <img class ='role-img' :src ="aiImg" /> <div class ='name' > 讯飞AI</div > </div > </div > <div v-highlight v-html ='msg.content' > </div > </div > </div > <div class ="footer" > <textarea rows ="5" placeholder ="请输入问题" class ="text" v-model ="msgValue" > </textarea > <button class ="btn" @click ="submitMsg" > 发送</button > </div > </div > </template > <script setup > import userImg from "@/assets/user.png" import aiImg from "@/assets/ai.png" import { nextTick, onMounted, ref } from 'vue' import TTSRecorder from "@/utils/TTSRecorder" import { userMsgStore } from '@/stores/msgStore' const msgStore = userMsgStore()const msgValue = ref("" )let ttsRecorder = new TTSRecorder()const msgList = ref([])let msgDom = ref(null )onMounted(()=> { msgDom.value = document .getElementById("message-box" ) msgList.value = msgStore.list scroll() }) const scroll = () => { nextTick(()=> { msgDom.value.scrollTop = msgDom.value.scrollHeight }) } const submitMsg = async () => { msgStore.userAddMsg(msgValue.value) msgValue.value = "" ttsRecorder.start(msgStore,msgDom) scroll() } </script > <style scoped lang ="less" > .content{ height : 100% ; position : relative; .message{ position : absolute; top : 0 ; left : 20% ; right : 20% ; bottom : 150px ; display : flex; overflow : auto; flex-direction : column; .user{ background-color : #ebf7f8 ; padding : 15px ; box-sizing : border-box; display : flex; flex-direction : column; align-items : flex-end; border-bottom : 1px solid #dfdfdf ; } .assistant{ background-color : #f7f7f7 ; padding : 15px ; box-sizing : border-box; border-bottom : 1px solid #dfdfdf ; } } .footer{ position : absolute; bottom : 50px ; left : 20% ; right : 20% ; display : flex; align-items : flex-end; gap: 15px ; .text{ width : 100% ; } .btn{ width : 100px ; height : 40px ; background-color : #1a60ea ; color : white; border : none; } } @media screen and (max-width : 768px ) { .message,.footer { left : 0 ; right : 0 ; } .message{ bottom : 100px ; } .footer{ bottom : 10px ; } } } .imgbox{ display : flex; align-items : center; gap: 10px ; margin-bottom : 10px ; .name{ font-size : 13px ; color : #fd919e ; font-weight : 400 ; } } .role-img{ width : 40px ; height : 40px ; border-radius : 50% ; overflow : hidden; } </style >