Electron Forge【实战】桌面应用 —— AI聊天(下)

此为系列教程,需先完成

  • Electron Forge【实战】桌面应用 —— AI聊天(上)
  • Electron Forge【实战】桌面应用 —— AI聊天(中)

会话列表按更新时间倒序加载

在这里插入图片描述
src/db.ts

db.version(1).stores({// 主键为id,且自增// 新增updatedAt字段,用于排序conversations: "++id, updatedAt",
});

src/stores/conversation.ts

    // 从本地存储中查询出会话列表async fetchConversations() {const items = await db.conversations.orderBy('updatedAt') // 按更新日期排序.reverse() // 倒序排列.toArray(); // 转换为数组this.items = items;},

新创建的会话,在会话列表顶部

src/stores/conversation

      //   pinia 中新增会话this.items.unshift({id: newCId,...createdData,});

会话更新时,同步存储到本地

src/views/Conversation.vue

      // 本次回答结束后if (data.is_end) {// 清空流式消息的内容streamContent = "";// 更新会话时,需要移除 id 字段,否则会报错let temp_convsersation = JSON.parse(JSON.stringify(convsersation.value));delete temp_convsersation.id;await conversationStore.updateConversation(convsersation.value!.id,temp_convsersation);}

src/stores/conversation.ts

    async updateConversation(id: number, newData: Omit<ConversationProps, "id">) {// 本地存储中更新会话await db.conversations.update(id, newData);//   pinia 中更新会话const index = this.items.findIndex((item) => item.id === id);if (index > -1) {this.items[index] = { id, ...newData };}},

聊天区自动滚动到底部

src/components/MessageList.vue
需对外暴露 ref

<div class="message-list" ref="_ref">
const _ref = ref<HTMLDivElement>();defineExpose({ref: _ref,
});

src/views/Conversation.vue

<MessageList :messages="convsersation!.msgList" ref="messageListRef" />
const messageListRef = ref<{ ref: HTMLDivElement }>();const messageScrollToBottom = async (behavior?: string) => {await nextTick();if (messageListRef.value) {// 获取到自定义组件内的真实 ref 调用 scrollIntoView messageListRef.value.ref.scrollIntoView({block: "end",behavior: behavior as ScrollBehavior, // "auto" | "instant" | "smooth"});}
};

会话页初次加载时

在 onMounted 末尾添加

await messageScrollToBottom();

切换当前会话时

在这里插入图片描述

watch(() => route.params.id,async (newId: string) => {conversationId.value = parseInt(newId);// 切换当前会话时,聊天区自动滚动到底部await messageScrollToBottom();}
);

AI 流式回答问题时

onUpdateMessage 内

      // 根据消息id, 获取到 loading 状态的消息let msg = convsersation.value!.msgList[messageId];// 将 AI 回答的流式消息替换掉 loading 状态的消息msg.content = streamContent;// 根据 AI 的返回,更新消息的状态msg.status = getMessageStatus(data);// 用 dayjs 得到格式化的当前时间字符串msg.updatedAt = dayjs().format("YYYY-MM-DD HH:mm:ss");// 顺滑滚动到底部await messageScrollToBottom("smooth");

滚动性能优化 – 仅当 AI 回答超过一行时才触发滚动

let currentMessageListHeight = 0;
const checkAndScrollToBottom = async () => {if (messageListRef.value) {const newHeight = messageListRef.value.ref.clientHeight;if (newHeight > currentMessageListHeight) {currentMessageListHeight = newHeight;await messageScrollToBottom("smooth");}}
};

onUpdateMessage 内改为

      // 顺滑滚动到底部await nextTick()await checkAndScrollToBottom()

切换会话时记得重置 currentMessageListHeight

watch(() => route.params.id,async (newId: string) => {conversationId.value = parseInt(newId);// 切换当前会话时,聊天区自动滚动到底部await messageScrollToBottom();// 切换当前会话时,将当前会话的消息列表的高度重置为0currentMessageListHeight = 0;}
);

恢复默认样式

Tailwind CSS 默认移除了几乎所有的默认样式,导致无法渲染带格式的富文本,通过插件 @tailwindcss/typography 来重置一套比较合理的默认样式

npm install -D @tailwindcss/typography

src/index.css 中添加

@plugin "@tailwindcss/typography";

给目标内容加上类名 prose 即可

<div class="prose">要恢复样式的内容</div>

自定义默认样式

// 给 p 标签添加 my-1 的 Tailwind CSS 样式
prose-p:my-1

更多自定义样式的方法见
https://github.com/tailwindlabs/tailwindcss-typography?tab=readme-ov-file#element-modifiers

渲染 markdown 的内容(支持代码高亮)

AI 模型返回的都是 markdown 语法的文本,需要按 markdown 进行格式化渲染

npm install vue-markdown-render markdown-it-highlightjs --save

src/components/MessageList.vue 中使用

import VueMarkdown from "vue-markdown-render";
import markdownItHighlightjs from "markdown-it-highlightjs";const plugins = [markdownItHighlightjs];

要渲染的内容,改用 vue-markdown 组件,通过 source 传入

            <divv-elseclass="prose prose-slate prose-hr:my-0 prose-li:my-0 prose-ul:my-3 prose-p:my-1 prose-pre:p-0"><vue-markdown :source="message.content" :plugins="plugins" /></div>

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

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

相关文章

[架构之美]Ubuntu源码部署APISIX全流程详解(含避坑指南)

[架构之美]Ubuntu源码部署APISIX全流程详解(含避坑指南) 一、离线安装场景需求分析 1.1 典型应用场景 金融/政务内网环境生产环境安全合规要求边缘计算节点部署1.2 离线安装难点 #mermaid-svg-B25djI0XquaOb1HM {font-family:"trebuchet ms",verdana,arial,sans-s…

多头注意力(Multi‑Head Attention)

1. 多头注意力&#xff08;Multi‑Head Attention&#xff09;原理 设输入序列表示为矩阵 X ∈ R B L d model X\in\mathbb{R}^{B\times L\times d_{\text{model}}} X∈RBLdmodel​&#xff0c;其中 B B B&#xff1a;批大小&#xff08;batch size&#xff09;&#xff0c…

系列位置效应——AI与思维模型【80】

一、定义 系列位置效应思维模型是指在一系列事物或信息的呈现过程中&#xff0c;人们对于处于系列开头和结尾部分的项目的记忆效果优于中间部分项目的现象。具体而言&#xff0c;开头部分的记忆优势被称为首因效应&#xff0c;结尾部分的记忆优势被称为近因效应。这种效应反映…

MyBatis XML 配置完整示例(含所有核心配置项)

MyBatis XML 配置完整示例&#xff08;含所有核心配置项&#xff09; 1. 完整 mybatis-config.xml 配置文件 <?xml version"1.0" encoding"UTF-8" ?> <!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""htt…

电商数据中台架构:淘宝 API 实时采集与多源数据融合技术拆解

引言 在当今竞争激烈的电商领域&#xff0c;数据已成为企业决策和业务发展的核心驱动力。电商数据中台能够整合和管理企业内外部的各种数据&#xff0c;为业务提供有力支持。其中&#xff0c;淘宝 API 实时采集与多源数据融合技术是数据中台架构中的关键部分。本文将深入探讨这…

ubuntu22.04部署Snipe-IT

文章目录 参考链接一、写在前二、安装操作系统三、安装 PHP四、下载 Snipe-IT五、安装依赖六、安装数据库并创建用户七、安装 Snipe-IT八、安装 Nginx九、Web 继续安装 Snipe-IT补充&#xff1a;20250427补充&#xff1a; 最后 参考链接 How to Install Snipe-IT on Ubuntu 22…

图论---Bellman-Ford算法

适用场景&#xff1a;有边数限制 ->&#xff08;有负环也就没影响了&#xff09;&#xff0c;存在负权边&#xff0c;O( n * m )&#xff1b; 有负权回路时有的点距离会是负无穷&#xff0c;因此最短路存在的话就说明没有负权回路。 从1号点经过不超过k条边到每个点的距离…

A. Ideal Generator

time limit per test 1 second memory limit per test 256 megabytes We call an array aa, consisting of kk positive integers, palindromic if [a1,a2,…,ak][ak,ak−1,…,a1][a1,a2,…,ak][ak,ak−1,…,a1]. For example, the arrays [1,2,1][1,2,1] and [5,1,1,5][5,…

[详细无套路]MDI Jade6.5安装包下载安装教程

目录 1. 软件包获取 2. 下载安装 3. 启动 4. 问题记录 写在前面: 垂死病中惊坐起,JAVA博主居然开始更博客了~ 最近忙项目了, 没啥更新的动力,见谅~见谅~. 这次博主的化工友友突然让帮安装JADE6.5软件,本来以为不就一个软件,直接拿捏. 不料竟然翻了个小车, 反被拿捏了. 既…

Serverless 在云原生后端的实践与演化:从函数到平台的革新

📝个人主页🌹:慌ZHANG-CSDN博客 🌹🌹期待您的关注 🌹🌹 一、引言:从服务器到“无服务器”的后端演变 在传统后端开发中,我们需要为服务配置并维护服务器资源,无论是物理机、虚拟机还是容器化服务,都需要: 管理系统运行环境 监控负载与扩缩容 保证高可用与安…

【专题三】二分查找(2)

&#x1f4dd;前言说明&#xff1a; 本专栏主要记录本人的基础算法学习以及LeetCode刷题记录&#xff0c;按专题划分每题主要记录&#xff1a;&#xff08;1&#xff09;本人解法 本人屎山代码&#xff1b;&#xff08;2&#xff09;优质解法 优质代码&#xff1b;&#xff…

MySQL 详解之函数:数据处理与计算的利器

在 MySQL 中,函数可以接受零个或多个输入参数,并返回一个值。这些函数可以在 SELECT 语句的字段列表、WHERE 子句、HAVING 子句、ORDER BY 子句以及 UPDATE 和 INSERT 语句中使用。合理利用函数,可以简化 SQL 语句,提高开发效率。 MySQL 提供了大量的内置函数 (Built-in F…

探索具身智能协作机器人:技术、应用与未来

具身智能协作机器人&#xff1a;概念与特点 具身智能协作机器人&#xff0c;简单来说&#xff0c;就是将人工智能技术与机器人实体相结合&#xff0c;使其能够在与人类共享的空间中进行安全、高效协作的智能设备。它打破了传统机器人只能在预设环境中执行固定任务的局限&#…

基于物联网的园林防火监测系统

标题:基于物联网的园林防火监测系统 内容:1.摘要 随着全球气候变化和人类活动影响&#xff0c;园林火灾发生频率呈上升趋势&#xff0c;给生态环境和人类生命财产造成巨大损失。为有效预防和应对园林火灾&#xff0c;本文提出基于物联网的园林防火监测系统。该系统综合运用传感…

JAVA多线程(8.0)

目录 线程池 为什么使用线程池 线程池的使用 工厂类Executors&#xff08;工厂模式&#xff09; submit 实现一个线程池 线程池 为什么使用线程池 在前面我们都是通过new Thread() 来创建线程的&#xff0c;虽然在java中对线程的创建、中断、销毁、等值等功能提供了支持…

用go从零构建写一个RPC(仿gRPC,tRPC)--- 版本1

希望借助手写这个go的中间件项目&#xff0c;能够理解go语言的特性以及用go写中间件的优势之处&#xff0c;同时也是为了更好的使用和优化公司用到的trpc&#xff0c;并且作者之前也使用过grpc并有一定的兴趣&#xff0c;所以打算从0构建一个rpc系统&#xff0c;对于生产环境已…

【学习笔记】Stata

一、Stata简介 Stata 是一种用于数据分析、数据管理和图形生成的统计软件包&#xff0c;广泛应用于经济学、社会学、政治科学等社会科学领域。 二、Stata基础语法 2.1 数据管理 Stata 支持多种数据格式的导入&#xff0c;包括 Excel、CSV、文本文件等。 从 Excel 文件导入…

Redis数据结构SDS,IntSet,Dict

目录 1.字符串&#xff1a;SDS 1.1.为什么叫做动态字符串 2.IntSet 2.1.inset如何保存大于当前编码的最大数字&#xff1f; 3.Dict 3.1Dict的扩容 3.2Dict的收缩 3.3.rehash 1.字符串&#xff1a;SDS SDS的底层是C语言编写的构建的一种简单动态字符串 简称SDS&#xff…

Maven的聚合工程与继承

目录 一、为什么需要使用Maven工程 二、聚合工程的结构 三、聚合工程实现步骤 四、父工程统一管理版本 五、编译打包 大家好&#xff0c;我是jstart千语。想着平时开发项目似乎都是用maven来管理的&#xff0c;并且大多都是聚合工程。而且在maven的聚合工程中&#xff0c…

前端职业发展:如何规划前端工程师的成长路径?

前端职业发展:如何规划前端工程师的成长路径? 大家好,我是全栈老李。今天咱们聊聊前端工程师的职业发展路径,这个话题看似简单,实则暗藏玄机。就像打游戏升级一样,你得知道下一关是什么,才能提前准备装备和技能点。 前端之路 一般我们从一个新手到大神,普遍需要经过…