无锡网站推广排名/网络推广软文范文

无锡网站推广排名,网络推广软文范文,网上订货发货网站建设,河北沧州网站建设目录 引言组件概述核心组件与功能实现1. 消息显示组件(Message.vue)2. 输入组件(Input.vue)3. 流式请求处理(useDeepseek.ts)4. 语音处理模块(Voice.vue) 总结Demo Github 地址 引言…

目录

    • 引言
    • 组件概述
    • 核心组件与功能实现
      • 1. 消息显示组件(Message.vue)
      • 2. 输入组件(Input.vue)
      • 3. 流式请求处理(useDeepseek.ts)
      • 4. 语音处理模块(Voice.vue)
    • 总结
    • Demo Github 地址

引言

在当今数字化时代,智能对话系统的应用越来越广泛,如客服聊天机器人、智能助手等。本文将详细介绍一个基于 Vue 框架开发的智能对话系统的实现过程,该系统支持文本输入、语音输入、流式响应等功能,让我们一步步揭开它的神秘面纱。

在这里插入图片描述

组件概述

整个组件主要由以下几个核心部分组成:

  1. 用户界面组件:负责与用户进行交互,包括文本输入框、语音输入按钮、消息显示区域等。
  2. 消息处理组件:将用户输入的消息进行处理,并显示在界面上。
  3. 流式请求处理:与后端进行流式通信,实时获取响应内容。
  4. 语音处理模块:支持语音输入功能,将语音转换为文本。

核心组件与功能实现

1. 消息显示组件(Message.vue)

该组件用于显示用户和系统的消息。在这个组件中,我们使用了 Markdown 解析器来处理消息内容,支持代码高亮和自定义标签。

<template><divclass="msg-item":class="{'msg-item-system': role === 'system'}"><divclass="msg-content":class="{'msg-content-user': role === 'user'}"><span class="msg-pop-container"><spanclass="msg-pop-default"v-html="mkHtml"ref="popRef":class="{'msg-pop-primary': role === 'user'}"></span></span></div></div>
</template><script lang="ts" setup>
import MarkdownIt from "markdown-it";
import mk from "markdown-it-katex";
import hljs from "highlight.js";
import "highlight.js/styles/atom-one-dark-reasonable.css";
import { computed, nextTick, ref } from "vue";interface Props {role: string;content: string;streaming?: boolean;
}const props = withDefaults(defineProps<Props>(), {content: "",streaming: false
});const md: MarkdownIt = MarkdownIt({highlight: function (str: string, lang: string) {if (lang && hljs.getLanguage(lang)) {try {return `<div class="hl-code"><div class="hl-code-header"><span>${lang}</span></div><div class="hljs"><code>${hljs.highlight(str, { language: lang, ignoreIllegals: true }).value}</code></div></div>`;} catch (__) {console.log(__, "error");}}return `<div class="hl-code"><div class="hl-code-header"><span>${lang}</span></div><div class="hljs"><code>${md.utils.escapeHtml(str)}</code></div></div>`;}
});md.use(mk, {throwOnError: false,errorColor: " #cc0000"
});// 自定义规则函数,用于解析 <think> 标签
function thinkTagRule(state, startLine, endLine, silent) {let pos,max,nextLine,token,autoClosed = false;let start = state.bMarks[startLine] + state.tShift[startLine];let end = state.eMarks[startLine];// 检查是否以 <think> 开头if (start + 7 > end || state.src.slice(start, start + 7) !== "<think>") {return false;}// 跳过 <think>pos = start + 7;// 查找 </think> 结束标签for (nextLine = startLine; ; nextLine++) {if (nextLine >= endLine) {// 未找到结束标签break;}max = state.bMarks[nextLine] + state.tShift[nextLine];if (max < state.eMarks[nextLine] && state.src.slice(max, max + 8) === "</think>") {// 找到结束标签autoClosed = true;break;}}// 如果处于静默模式,只验证标签,不生成 tokenif (silent) {return autoClosed;}// 创建一个新的 token 表示开始标签token = state.push("think_tag_open", "think", 1);token.markup = "<think>";token.map = [startLine, nextLine];// 处理标签内部的内容state.md.block.tokenize(state, startLine + 1, nextLine);// 创建结束 tokentoken = state.push("think_tag_close", "think", -1);token.markup = "</think>";// 更新状态,跳过已处理的行state.line = nextLine + 1;return true;
}// 将自定义规则添加到 MarkdownIt 实例中
md.block.ruler.before("paragraph", "think_tag", thinkTagRule);// 自定义渲染规则,将 <think> 标签渲染为 <span class="think">
md.renderer.rules.think_tag_open = function () {return '<span class="think">';
};md.renderer.rules.think_tag_close = function () {return "</span>";
};function findLastElement(element: HTMLElement): HTMLElement {if (!element.children.length) {return element;}const lastChild = element.children[element.children.length - 1];if (lastChild.nodeType === Node.ELEMENT_NODE) {return findLastElement(lastChild as HTMLElement);}return element;
}const popRef = ref();
const mkHtml = computed(() => {if (props.role === "user") {return props.content;}let html = md.render(props.content);console.log(html); // 调试信息nextTick(() => {if (props.streaming) {const parent = popRef.value;if (!parent) return;let lastChild = parent.lastElementChild || parent;console.log(lastChild.tagName);if (lastChild.tagName === "PRE") {lastChild = lastChild.getElementsByClassName("hljs")[0] || lastChild;}if (lastChild.tagName === "OL") {lastChild = findLastElement(lastChild as HTMLElement);}lastChild?.insertAdjacentHTML("beforeend", '<span class="input-cursor"></span>');}});return html;
});
</script><style lang="scss" scoped>
.msg-item {width: 100%;display: flex;margin-bottom: 10px;padding: 0 10px;border-radius: 4px;.msg-content {position: relative;width: 100%;flex: 1 1 auto;.msg-pop-container {position: relative;display: inline-block;max-width: 95%;.msg-pop-default {width: 100%;display: inline-block;padding: 8px;background: #f5f5f5;border-radius: 4px;color: #252724;:deep(p) {margin-bottom: 0;white-space: pre-line;}}.msg-pop-primary {background: #95ec69;// white-space: pre-line;}}}
}.msg-content-user {text-align: end;
}
.msg-item-system {justify-content: flex-end;
}
</style><style lang="scss">
.think {color: blue;font-style: italic;
}
.hl-code {margin-top: 1em;
}.hl-code-header {padding: 0 10px;color: #abb2bf;background: #1d2635;border-radius: 4px 4px 0 0;display: flex;justify-content: space-between;align-items: center;
}.hljs {padding: 10px;overflow-x: auto;border-radius: 0 0 4px 4px;.input-cursor {background: #fff;/* fallback for old browsers */}
}.input-cursor {position: relative;display: inline-flex;align-items: center;width: 1px;height: 1em;background: #3b414b;/* fallback for old browsers */padding-left: 0.05em;top: 0.1em;animation: blink 1s steps(1) infinite;
}@keyframes blink {0% {visibility: visible;}50% {visibility: hidden;}100% {visibility: visible;}
}
</style>

2. 输入组件(Input.vue)

该组件提供了文本输入框和语音输入按钮,支持用户输入问题并发送,同时可以开启新的对话。

<template><div class="msg-editor-container"><!-- 文本输入框 --><div class="flex items-center"><el-inputclass="flex-1"ref="inputDiv"v-model="inputValue"type="textarea":autosize="{ minRows: 2, maxRows: 4 }"placeholder="请输入你的问题"@keydown.enter.exact="handleKeydown"></el-input><!-- 语音 --><DeepseekVoiceVue @voiceTextChange="voiceTextChange"></DeepseekVoiceVue><!-- 新对话 --><el-tooltip :z-index="100000" effect="dark" content="新对话" placement="top"><el-icon class="mr-1 focus:border-blue-400 hover:bg-[#f5f5f5] bg-white cursor-pointer" size="22px" @click="newSessionBtn"><Plus /></el-icon></el-tooltip><!-- 操作按钮 --><el-tooltip :z-index="100000" effect="dark" content="发送" placement="top"><el-button type="primary" icon="Top" circle @click="handleKeydown"></el-button></el-tooltip></div></div>
</template><script lang="ts" setup>
import { ref } from "vue";
import DeepseekVoiceVue from "./Voice.vue";const emits = defineEmits(["submit", "newSession"]);
const inputValue = ref("");const voiceTextChange = (text: string) => {console.log(text);inputValue.value = inputValue.value + text;
};const handleKeydown = (e: Event) => {e.stopPropagation();e.returnValue = false;if (inputValue.value === "") return;emits("submit", inputValue.value);inputValue.value = "";
};const newSessionBtn = () => {emits("newSession");
};
</script><style lang="scss" scoped>
.msg-editor-container {border: 1px solid #dee0e3;border-radius: 4px;padding: 5px;
}.operationBtn {display: flex;
}
</style>

3. 流式请求处理(useDeepseek.ts)

通过useGpt钩子函数处理 GPT 流式请求,使用Typewriter类模拟打字效果,让响应内容逐字显示。

import { ref } from "vue";
import { StreamGpt, Typewriter, GptMsgs, RequestData } from "./fetchApi";// useGpt 钩子函数,用于处理 GPT 流式请求
export const useGpt = (key: string) => {const streamingText = ref("");const streaming = ref(false);const msgList = ref<GptMsgs>([]);const sessionId = ref("");// 初始化 Typewriter 实例const typewriter = new Typewriter((str: string) => {streamingText.value += str || "";// console.log("str", str);});// 初始化 StreamGpt 实例const gpt = new StreamGpt(key, {onStart: (prompt: string) => {streaming.value = true;msgList.value.push({role: "user",content: prompt});},onPatch: (text: string) => {// console.log("onPatch", text);typewriter.add(text);},onCreated: () => {typewriter.start();},onDone: () => {typewriter.done();streaming.value = false;msgList.value.push({role: "system",content: streamingText.value});streamingText.value = "";}});// 发送流式请求const stream = (requestData: RequestData) => {if (sessionId.value === "") {sessionId.value = generateUUID();}gpt.stream({ ...requestData, sessionId: sessionId.value });};// 新会话const newSession = () => {msgList.value = [];streamingText.value = "";sessionId.value = generateUUID();};// 生成 UUIDconst generateUUID = () => {let uuid = "";for (let i = 0; i < 32; i++) {const random = (Math.random() * 16) | 0;if (i === 8 || i === 12 || i === 16 || i === 20) uuid += "-";uuid += (i === 12 ? 4 : i === 16 ? (random & 3) | 8 : random).toString(16);}return uuid;};return {streamingText,streaming,msgList,stream,newSession};
};

4. 语音处理模块(Voice.vue)

该模块支持语音录制和识别,将录制的语音转换为 WAV 文件或Base64,并发送到后端进行识别。

<template><!-- 语音输入按钮 --><button @click="toggleRecording">{{ isRecording ? '停止录音' : '开始录音' }}</button>
</template><script lang="ts" setup>
import { ref } from "vue";const isRecording = ref(false);
const mediaRecorder: any = ref(null);
const chunks: any = ref([]);const emits = defineEmits(["voiceTextChange"]);// 开始录音
const startRecording = async () => {try {const stream = await navigator.mediaDevices.getUserMedia({ audio: true });mediaRecorder.value = new MediaRecorder(stream);mediaRecorder.value.ondataavailable = (event: any) => {if (event.data.size > 0) {chunks.value.push(event.data);}};mediaRecorder.value.onstop = () => {const wavBlob = encodeWAV(chunks.value);wavTransformBase64(wavBlob);chunks.value = [];};mediaRecorder.value.start();isRecording.value = true;} catch (error) {console.error("录音失败:", error);}
};// 停止录音
const stopRecording = () => {if (mediaRecorder.value) {mediaRecorder.value.stop();isRecording.value = false;}
};// 生成WAV文件
function encodeWAV(chunks) {const totalLength = chunks.reduce((acc, chunk) => acc + chunk.byteLength, 0);const header = generateWavHeader(totalLength);const wavBuffer = new Uint8Array(header.byteLength + totalLength);// 合并头和数据wavBuffer.set(new Uint8Array(header.buffer), 0);let offset = header.byteLength;chunks.forEach(chunk => {wavBuffer.set(new Uint8Array(chunk.buffer), offset);offset += chunk.byteLength;});return new Blob([wavBuffer], { type: "audio/wav" });
}// 生成WAV文件头
function generateWavHeader(dataLength) {const header = new ArrayBuffer(44);const view = new DataView(header);// RIFF标识writeString(view, 0, "RIFF");// 文件长度(数据长度 + 36)view.setUint32(4, dataLength + 36, true);// WAVE格式writeString(view, 8, "WAVE");// fmt子块writeString(view, 12, "fmt ");// fmt块长度(16字节)view.setUint32(16, 16, true);// 格式类型(1=PCM)view.setUint16(20, 1, true);// 声道数view.setUint16(22, WAV_CONFIG.channelCount, true);// 采样率view.setUint32(24, WAV_CONFIG.sampleRate, true);// 字节率view.setUint32(28, WAV_CONFIG.sampleRate * WAV_CONFIG.channelCount * (WAV_CONFIG.bitDepth / 8), true);// 块对齐view.setUint16(32, WAV_CONFIG.channelCount * (WAV_CONFIG.bitDepth / 8), true);// 位深度view.setUint16(34, WAV_CONFIG.bitDepth, true);// data标识writeString(view, 36, "data");// 数据长度view.setUint32(40, dataLength, true);return view;
}// 写入字符串到DataView
function writeString(view, offset, string) {for (let i = 0; i < string.length; i++) {view.setUint8(offset + i, string.charCodeAt(i));}
}// 将WAV文件转换为Base64编码
function wavTransformBase64(wavBlob) {const reader = new FileReader();reader.onload = function (e) {console.log("wav base64:", e.target.result);getRecognition(e.target.result);};reader.readAsDataURL(wavBlob);
}// 切换录音状态
const toggleRecording = () => {if (isRecording.value) {stopRecording();} else {startRecording();}
};const getRecognition = base64Str => {isLoading.value = true;Recognition(base64Str).then(res => {emits("voiceTextChange", res.data);}).finally(() => {console.log("voiceTextChange");isLoading.value = false;});
};
</script>

总结

通过以上步骤,我们实现了一个基于 Vue 的智能对话系统,支持文本输入、语音输入和流式响应。在开发过程中,我们使用了 Vue 的响应式原理和组件化开发思想,结合 Markdown 解析器、语音处理 API 和流式请求技术,为用户提供了一个流畅、智能的对话体验。未来,我们可以进一步优化系统性能,增加更多的功能,如多语言支持、情感分析等。

Demo Github 地址

stream-deepseek

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/72091.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

WPS二次开发系列:Android 第三方应用如何获取WPS端内文档

1.需求场景 在项目开发中碰到这种情况&#xff0c;我们需要利用WPS的文档管理能力&#xff0c;比如需要调用WPS的文件选择器&#xff0c;来选择文档&#xff0c;同时需要得到WPS选择的文档结果返回给我们的应用。之前在网上找到了很久都没有找到WPS移动端有相关的API接口文档和…

Flutter 基础组件 Text 详解

目录 1. 引言 2. 基本使用 3. 自定义样式 4. 文本对齐与溢出控制 5. 外边距 5.1 使用 Container 包裹 5.2 使用 Padding 组件 5.3 在 Row/Column 中使用 5.4 动态边距调整 5.5 关键区别说明 5.6 设置 margin 无效 6. 结论 相关推荐 1. 引言 Text 组件是 Flutter 中…

Acknowledgment.nack方法重试消费kafka消息异常

文章目录 问题示例异常 原因nack方法Acknowledgment接口实现类&#xff1a;ConsumerAcknowledgment实现类&#xff1a;ConsumerBatchAcknowledgment 解决方案1 批量消费指定index示例 2 单条消费示例 问题 使用BatchAcknowledgingMessageListener 批量消费Kafka消息&#xff0…

Java 反序列化 - commons collection 之困(一)

#01多余的碎碎念 说到 java 反序列化&#xff0c;去搜索的话能看到网上有很多分析关于 commons collection 利用链的文章&#xff0c;emm 我一开始看不懂&#xff0c;看到很多代码的图头晕。 这篇文章的话其实是我跟着 p 神的文章一路走下来的&#xff0c;所以整个逻辑会按照…

Linux账号和权限管理

用户账户管理 理论 /etc/passwd 该目录用于保存用户名&#xff0c;宿主目录&#xff0c;登录shel等基本信息 /etc/shadow 该目录用于保存 用户密码&#xff0c;账户有效期等信息 图上每一行中都有用“&#xff1a;”隔断的字段 字段含义&#xff1a; 第1字段:用户账号的名…

晋升系列4:学习方法

每一个成功的人&#xff0c;都是从底层开始打怪&#xff0c;不断的总结经验&#xff0c;一步一步打上来的。在这个过程中需要坚持、总结方法论。 对一件事情长久坚持的人其实比较少&#xff0c;在坚持的人中&#xff0c;不断的总结优化的更少&#xff0c;所以最终达到高级别的…

win32汇编环境,对话框中使用树形视图示例四

;运行效果,当点击张辽时,展示张辽的图像 ;当点击曹仁时,展示曹仁的图像 ;win32汇编环境,对话框中使用树形视图示例四 ;当点击树形视图treeview控件中的某项时,展示某些功能。这里展示的是当点到某个将领时,显示某个将领的图像 ;直接抄进RadAsm可编译运行。重要部分加备注。…

Windows server网络安全

摘要 安全策略 IP安全策略&#xff0c;简单的来说就是可以通过做相应的策略来达到放行、阻止相关的端口&#xff1b;放行、阻止相关的IP&#xff0c;如何做安全策略&#xff0c;小编为大家详细的写了相关的步骤&#xff1a; 解说步骤&#xff1a; 阻止所有&#xff1a; 打…

充电桩快速搭建springcloud(微服务)+前后端分离(vue),客户端实现微信小程序+ios+app使用uniapp(一处编写,处处编译)

充电桩管理系统是专为中小型充电桩运营商、企业和个人开发者设计的一套高效、灵活的管理平台。系统基于Spring Cloud微服务架构开发&#xff0c;采用模块化设计&#xff0c;支持单机部署与集群部署&#xff0c;能够根据业务需求动态扩展。系统前端使用uniapp框架&#xff0c;可…

小肥柴慢慢手写数据结构(C篇)(4-3 关于栈和队列的讨论)

小肥柴慢慢学习数据结构笔记&#xff08;C篇&#xff09;&#xff08;4-3 关于栈和队列的讨论&#xff09; 目录1 双端栈/队列2 栈与队列的相互转化2-1 栈转化成队列2-2 队列转化成栈 3 经典工程案例3-1 生产者和消费者模型&#xff08;再次重温环形缓冲区&#xff09;3-2 MapR…

labview实现大小端交换移位

在解码时遇到了大小端交换的问题&#xff0c;需要把高低字节的16进制值进行互换&#xff0c;这里一时间不知道怎么操作&#xff0c;本来打算先把16进制转字节数组&#xff0c;算出字节数组的大小&#xff0c;然后通过模2得到0&#xff0c;1&#xff0c;来判断是否为奇数位和偶数…

在Windows系统上安装和配置Redis服务

&#x1f31f; 在Windows系统上安装和配置Redis服务 Redis是一个高性能的键值存储数据库&#xff0c;广泛用于缓存、消息队列和实时分析等场景。虽然Redis最初是为Linux设计的&#xff0c;但也有Windows版本可供使用。今天&#xff0c;我将详细介绍如何在Windows系统上安装Red…

Ateme在云端构建可扩展视频流播平台

Akamai Connected Cloud帮助Ateme客户向全球观众分发最高质量视频内容。 “付费电视运营商和内容提供商现在可以在Akamai Connected Cloud上通过高质量视频吸引观众&#xff0c;并轻松扩展。”── Ateme首席战略官Rmi Beaudouin ​ Ateme是全球领先的视频压缩和传输解决方案提…

DeepSeek进阶应用(一):结合Mermaid绘图(流程图、时序图、类图、状态图、甘特图、饼图)

&#x1f31f;前言: 在软件开发、项目管理和系统设计等领域&#xff0c;图表是表达复杂信息的有效工具。随着AI助手如DeepSeek的普及&#xff0c;我们现在可以更轻松地创建各种专业图表。 名人说&#xff1a;博观而约取&#xff0c;厚积而薄发。——苏轼《稼说送张琥》 创作者&…

时序数据库TimescaleDB基本操作示例

好的&#xff01;以下是使用 TimescaleDB 的 Java 示例&#xff08;基于 JDBC&#xff0c;因为 TimescaleDB 是 PostgreSQL 的扩展&#xff0c;官方未提供独立的 Java SDK&#xff09;&#xff1a; 1. 添加依赖&#xff08;Maven&#xff09; <dependency><groupId&g…

C/C++中使用CopyFile、CopyFileEx原理、用法、区别及分别在哪些场景使用

文章目录 1. CopyFile原理函数原型返回值用法示例适用场景 2. CopyFileEx原理函数原型返回值用法示例适用场景 3. 核心区别4. 选择建议5. 常见问题6.区别 在Windows系统编程中&#xff0c;CopyFile和CopyFileEx是用于文件复制的两个API函数。它们的核心区别在于功能扩展性和控制…

浙江大学:DeepSeek行业应用案例集(153页)(文末可下载PDF)

浙江大学&#xff1a;DeepSeek行业应用案例集&#xff08;153页&#xff09;&#xff08;文末可下载PDF&#xff09; 全文链接&#xff1a;浙江大学&#xff1a;DeepSeek行业应用案例集&#xff08;153页&#xff09;&#xff08;文末可下载PDF&#xff09; | AI探金 全文链接&…

入门到入土,Java学习 day16(算法1)

利用循环遍历来判断是否相等 二分查找/折半查找 前提条件&#xff1a;数组中的数据有序 每次排除一般的查找范围 用min,max,mid来处理&#xff0c;最大加最小除2&#xff0c;比较&#xff0c;然后得到在中间左边还是右边然后更新最大最小 public class Two {// 二分查找方法…

mysql-8.0.41-winx64 手动安装详细教程(2025版)

mysql-8.0.41-winx64 手动安装详细教程&#xff08;2025版&#xff09; 一、下载安装包二、配置环境变量三、安装配置四、启动 MySQL 服务&#xff0c;修改密码 一、下载安装包 安装地址如下&#xff1a; https://dev.mysql.com/downloads/mysql/使用7-zip或其他解压软件&…

Flink深入浅出之03:状态、窗口、checkpoint、两阶段提交

Flink是一个有状态的流&#xff0c;&#x1f445;一起深入了解这个有状态的流 3️⃣ 目标 掌握State知识掌握Flink三种State Backend掌握Flink checkpoint和savepoint原理了解Flink的重启策略checkpointtwo phase commit保证E-O语义 4️⃣ 要点 &#x1f4d6; 1. Flink的St…