2026最新款Tauri2.10+Vite7.3+DeepSeek桌面版AI系统Exe

0 阅读3分钟

经过两周爆肝迭代研发tauri2.10+vue3.5+openai深度接入deepseek-v3.2搭建客户端流式ai智能小助手。新增深度思考复制代码katex公式mermaid图渲染等功能。

360截图20260306233825251.png

p3.gif

项目支持性

  1. 支持深度思考链 ✨
  2. 支持KaTex数学公式 ✨
  3. 支持Mermaid图表渲染(放大/缩小/下载svg/下载png)✨
  4. 支持代码块滚动粘性、横向滚动、行号、复制代码、下载代码 ✨
  5. 支持表格、链接跳转、图片预览 ✨

p3-1-1.gif

p4.gif

使用技术

  • 编辑器:VScode
  • 跨平台框架:Tauri^2.10
  • 前端技术框架:vite^7.3.1+vue^3.5.29+vue-router^5.0.3
  • 通用大模型:deepseek-v3.2 + openai
  • 组件库:arco-design^2.57.0
  • 状态管理:pinia^3.0.4
  • 本地存储:pinia-plugin-persistedstate^4.7.1
  • markdown解析:markdown-it^14.1.0
  • katex公式渲染:@mdit/plugin-katex^0.25.0

360截图20260306233825268.png

p1.gif

项目框架结构

使用vite7.3+tauri2.10搭建项目框架,集成deepseek-v3.2智能模型,采用vue3 setup语法糖编码开发页面。

360截图20260306233825246.png

项目入口文件

import { createApp } from "vue"
import App from "./App.vue"
import './style.scss'

// 引入插件配置
import Plugins from './plugins'

// 引入路由/状态管理
import Router from './router'
import Pinia from './pinia'

createApp(App)
.use(Plugins)
.use(Router)
.use(Pinia)
.mount("#app")

环境变量配置.env

360截图20260306233825249.png

如上图:替换下自己申请的deepseek apikey即可体验流式对话功能。

360截图20260306233825252.png

852f0926a10631fddf629bb482f6e6bc_1289798-20260308115350283-1898628840.png

6c0e0559efb1a84cb66a5ce5bca561dc_1289798-20260308115401597-1719322161.png

项目通用模板

63e9717c5117838f934be656598aca52_1289798-20260308115444173-170163762.png

9f267371b511b646995062f70a729799_1289798-20260308115455749-576460461.png

<script setup>
  import { appState } from '@/pinia/modules/app'

  import Titlebar from '@/layouts/components/titlebar/index.vue'
  import Sidebar from '@/layouts/components/sidebar/index.vue'

  const appstate = appState()
</script>

<template>
  <div class="vu__chatbot">
    <div class="vu__container" :style="{'--themeSkin': appstate.config.skin}">
      <div class="vu__layout flexbox flex-col">
        <!-- 导航栏 -->
        <Titlebar />

        <div class="vu__layout-body flex1 flexbox">
          <!-- 侧边栏 -->
          <Sidebar />

          <!-- 主面板 -->
          <div class="vu__layout-main flex1">
            <router-view v-slot="{ Component, route }">
              <keep-alive>
                <component :is="Component" :key="route.path" />
              </keep-alive>
            </router-view>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

47b060f236e59d79baf07b103e1448e0_1289798-20260308115610148-1924439510.png

cc5dc6425e4bec0dd49541170d6c6a76_1289798-20260308115625183-642973311.png

0bef9f6f596e5ba4917cf9161ba68186_1289798-20260308115648059-277981813.png

自定义集成katex公式+mermaid渲染

import { imgSize } from '@mdit/plugin-img-size' // 支持带尺寸图片
import { katex } from "@mdit/plugin-katex"; // 支持数学公式
import 'katex/dist/katex.min.css'
// 渲染mermaid图表
import { markdownItMermaidPlugin } from '@/components/markdown/plugins/mermaidPlugin'

渲染markdown结构。

<Markdown
  :source="item.content"
  :html="true"
  :linkify="true"
  :typographer="true"
  :plugins="[
    [katex, {delimiters: 'all'}],
    [markdownItMermaidPlugin]
  ]"
  @copy="onCopy"
  @preview="onPreview"
/>

封装mermaid渲染插件。

export const markdownItMermaidPlugin = (md, options) => {
  const defaultFence = md.renderer.rules.fence
  md.renderer.rules.fence = (...args) => {
    const [tokens, idx] = args
    const token = tokens[idx]
    const lang = token.info.replace(/\[.*\]/, '').trim() || ''

    if(lang === 'mermaid') {
      const code = token.content.trim()
      const hash = generateHash(code)
      const uuid = `${hash}-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`

      // 如果有缓存,加载缓存图表
      if(renderCache.has(hash)) {
        // console.log('加载缓存mermaid图表')
        return `
          ${ defaultFence(...args) }
          <div class="mermaid-container">${renderCache.get(hash)}</div>
        `
      }

      nextTickRender(uuid)

      return `
        ${ defaultFence(...args) }
        <div class="mermaid-container" id="${uuid}" data-mermaid-hash="${hash}" data-mermaid-code="${encodeURIComponent(code)}">
          <div class="mermaid-loading">📊Mermaid 图表加载中...</div>
        </div>
      `
    }

    return defaultFence(...args)
  }

  function nextTickRender(containerId) {
    // 如果容器存在,直接渲染
    if(document.getElementById(containerId)) {
      renderMermaidDiagram(containerId)
      return
    }
    // 使用MutationObserver监听DOM更新
    const observer = new MutationObserver((mutations, ob) => {
      const container = document.getElementById(containerId)
      if(container) {
        ob.disconnect()
        renderMermaidDiagram(containerId)
      }
    })
    observer.observe(document.body, {
      childList: true,
      subtree: true
    })
  }
  
  async function renderMermaidDiagram(containerId) {
    const container = document.getElementById(containerId)
    if (!container) {
      console.warn(`Mermaid container #${containerId} not found`)
      return
    }
  
    const code = decodeURIComponent(container.dataset.mermaidCode)
    const hash = container.dataset.mermaidHash

    if (!code) {
      return
    }

    // 检查 mermaid 是否可用
    if (typeof window.mermaid === 'undefined') {
      showError(container, 'Mermaid 库未加载!')
      return
    }
  
    try {
      // 配置 mermaid(如果还未配置)
      if (!window.mermaid.initialized) {
        window.mermaid.initialize({
          startOnLoad: false,
          theme: 'default',
          securityLevel: 'loose',
        })
        window.mermaid.initialized = true
      }
  
      let svg
  
      // 检查缓存
      if(renderCache.has(hash)) {
        svg = renderCache.get(hash)
      }else {
        const { isValid } = await verifyMermaid(code)

        if(!isValid) {
          showError(container, `<pre>渲染语法错误:\n${ code }\n</pre>`)
          return
        }

        // 使用唯一ID渲染(避免图表冲突)
        const {svg: renderedSvg} = await window.mermaid.render(`mermaid-${containerId}`, code)
        svg = renderedSvg
        renderCache.set(hash, svg)
      }

      // 更新容器内容
      container.innerHTML = svg
      container.removeAttribute('data-mermaid-hash')
      container.removeAttribute('data-mermaid-code')
    } catch (error) {
      console.error('Mermaid 渲染失败:', error)
      showError(container, `<pre>渲染图表时出错: \n ${error.message}\n</pre>`)
    }
  }
}

858109568ada9501f953a70ffcfd3290_1289798-20260308120125776-1975440424.png

cf3d5b7ee596dd8e987a09d2472486ec_1289798-20260308120150048-294736202.png

696bb7aed29fd13364efbec6ba15e481_1289798-20260308120208586-1972843948.png

f5db3c39e693d9af63d77bbf0935ef67_1289798-20260308120228038-1847243833.png

45b3ee004d01ecdcba513bda1e56325b_1289798-20260308120246435-404555262.png

47660254559a24993f75a67e03d2a080_1289798-20260308120311260-483468567.png

72727983a45a069e210c4b2aac37e6c9_1289798-20260308120335562-1676524146.png

979d58193d5cbfb8c366b9fdc72841e8_1289798-20260308120346799-956730863.png

249971017f30cad404859d8f3d586e47_1289798-20260308120401351-1791683147.png

dc16c7b61ca03496aef0879752f032bc_1289798-20260308120416723-1170386023.png

1b99e80b87a6a2304933b22125a4a5b5_1289798-20260308120454812-431094628.gif

自定义ai对话框

e24bec6a5c229e538ad0911317550028_1289798-20260308120530987-938069197.png

e9eb26cbdbbb25ef3f3791dfca93eefd_1289798-20260308120539536-3250889.png

d2a3e37d386e968ea646c68bc57c883e_1289798-20260308120549891-33348309.png

<template>
  <div class="v3ai__footbar flexbox flex-col">
    <!-- 技能栏 -->
    <div v-if="skillbar" class="v3ai__skills flexbox flex-alignc">
      <div class="item" v-for="(item, index) in skills" :key="index" @click="handleSkill(item)">
        <i class="iconfont" :class="item.icon"></i><span class="text">{{item.text}}</span>
      </div>
    </div>
    <!-- 编辑栏 -->
    <div class="v3ai__inputbox flexbox flex-col">
      <div class="v3ai__editor flexbox">
        <a-textarea v-model="editorText" :auto-size="autoSize" placeholder="想问点什么..." spellcheck="false" @input="handleInput" />
      </div>
      <!-- 操作栏 -->
      <div class="v3ai__tools flexbox flex-alignc">
        <div class="option flex1 flexbox">
          <div class="btn" @click="isDeep =! isDeep"><i class="iconfont ai-deepthink"></i> 深度思考 <span class="fs-12">(R1)</span></div>
          <div class="btn" @click="isNetwork =! isNetwork"><i class="iconfont ai-network"></i> 联网</div>
        </div>
        <a-dropdown trigger="hover" :show-arrow="false" position="lb" :content-style="{'min-width': '250px'}">
          <a-button shape="circle"><icon-attachment size="18" /></a-button>
          <template #content>
            <a-dgroup>
              <template #title><div style="margin-bottom: 5px;">从网盘添加</div></template>
              <a-doption value="wx"><icon-more /> 选择网盘文件</a-doption>
            </a-dgroup>
            <a-dgroup>
              <template #title><div style="margin-bottom: 5px;">从本地添加</div></template>
              <a-doption value="wx"><icon-apps /> 上传文件</a-doption>
              <a-dsubmenu trigger="hover" position="rb" :popup-translate="[8, 8]" value="option-1">
                <template #default><icon-apps /> 上传代码</template>
                <template #content>
                  <a-doption value="pyq"><icon-apps /> 代码文件</a-doption>
                  <a-doption value="qq"><icon-apps /> 代码文件夹</a-doption>
                  <a-doption value="qq"><icon-apps /> GitHub仓库</a-doption>
                </template>
              </a-dsubmenu>
            </a-dgroup>
          </template>
        </a-dropdown>
        <a-dropdown :show-arrow="false" position="top" :popup-translate="[-5, -5]" :content-style="{'min-width': '150px'}">
          <a-button shape="circle"><icon-plus size="18" /></a-button>
          <template #content>
            <a-doption value="image"><icon-file-image /> 图片</a-doption>
            <a-doption value="file"><icon-file /> 本地文件</a-doption>
            <a-doption value="pdf"><icon-file-pdf /> PDF文档分析</a-doption>
            <a-doption value="page"><icon-cloud /> 网页总结</a-doption>
          </template>
        </a-dropdown>
        <a-divider direction="vertical" style="margin: 0 7px;" />
        <a-button class="submit" type="primary" shape="circle" @click="handleSubmit">
          <icon-send v-if="!sessionstate.loading" size="20" />
          <icon-loading v-else size="18" />
        </a-button>
      </div>
    </div>
  </div>
</template>

未标题-4.png

接入deepseek实现多轮会话

// 调用deepseek接口
const completion = await openai.chat.completions.create({
  // 单一会话
  // messages: [{role: 'user', content: editorValue}],
  // 多轮会话
  messages: props.multiConversation ? historySession.value : [{role: 'user', content: editorValue}],
  // deepseek-chat对话模型 deepseek-reasoner推理模型
  model: sessionstate.thinkingEnabled ? 'deepseek-reasoner' : 'deepseek-chat',
  stream: true, // 流式输出
  max_tokens: 8192,
  temperature: 0.4
})

// 处理流式返回结果
for await (const chunk of completion) {
  // 检查是否已终止
  if(sessionstate.aborted) break

  const content = chunk.choices[0]?.delta?.content || ''
  // 获取推理内容
  const reasoningContent = chunk.choices[0]?.delta?.reasoning_content || ''
  
  if(content || reasoningContent) {
    // ...
  }
  if(chunk.choices[0]?.finish_reason === 'stop') {
    // ...
  }
}

推荐热文

2026实战uniapp+vue3+mphtml调用deepseek【小程序+安卓+H5】流式输出ai

2026最新款Vue3+DeepSeek-V3.2+Arco+Markdown网页端流式生成AI Chat

2026原创Electron39.2+Vue3+DeepSeek从0-1手搓AI模板桌面应用Exe

2026最新款Vite7+Vue3+DeepSeek-V3.2+Markdown移动端流式输出AI会话

基于electron38+vite7+vue3 setup+elementPlus电脑端仿微信/QQ聊天软件

electron38.2-vue3os系统|Vite7+Electron38+Pinia3+ArcoDesign桌面版OS后台管理

2025最新款Electron38+Vite7+Vue3+ElementPlus电脑端后台系统Exe

基于uni-app+vue3+uvui跨三端仿微信app聊天模板【h5+小程序+app】

基于uniapp+vue3+uvue短视频+聊天+直播app系统

自研2025版flutter3.38实战抖音app短视频+聊天+直播商城系统

基于flutter3.32+window_manager仿macOS/Wins风格桌面os系统

flutter3.27+bitsdojo_window电脑端仿微信Exe应用

自研tauri2.0+vite6.x+vue3+rust+arco-design桌面版os管理系统Tauri2-ViteOS