fix(chatBot): 🐛 fix package startup error due to md stream rendering component

fix code block color issue after switching AI sessions
This commit is contained in:
Dawn
2025-11-04 15:23:54 +08:00
parent c04adb1bae
commit 712c1df912
6 changed files with 423 additions and 302 deletions
+1 -1
View File
@@ -28,7 +28,7 @@
}
},
"git": {
"commitMessage": "chore: 发布 v${version}"
"commitMessage": "chore: release v${version}"
},
"npm": {
"publish": false
+8 -8
View File
@@ -81,7 +81,7 @@
"dependencies": {
"@actions/github": "^6.0.1",
"@breezystack/lamejs": "^1.2.7",
"@fingerprintjs/fingerprintjs": "^4.6.2",
"@fingerprintjs/fingerprintjs": "^5.0.1",
"@tauri-apps/api": "2.9.0",
"@tauri-apps/plugin-autostart": "2.5.0",
"@tauri-apps/plugin-barcode-scanner": "^2.4.0",
@@ -116,7 +116,9 @@
"grapheme-splitter": "^1.0.4",
"hula-emojis": "^1.2.30",
"internal-ip": "^8.0.1",
"mermaid": "^11.12.1",
"mitt": "^3.0.1",
"monaco-editor": "0.52.2",
"naive-ui": "^2.43.1",
"p-limit": "^7.2.0",
"pinia": "^3.0.3",
@@ -124,9 +126,10 @@
"pinia-shared-state": "^1.0.1",
"seemly": "^0.3.10",
"stream-markdown": "^0.0.8",
"stream-monaco": "^0.0.2",
"tauri-plugin-mic-recorder-api": "^2.0.0",
"tauri-plugin-safe-area-insets": "^0.1.0",
"three": "^0.180.0",
"three": "^0.181.0",
"tlbs-map-vue": "^1.3.1",
"vant": "^4.9.21",
"vue": "^3.5.22",
@@ -148,7 +151,7 @@
"@tauri-apps/cli": "2.9.1",
"@types/crypto-js": "^4.2.2",
"@types/node": "^24.6.2",
"@types/three": "^0.180.0",
"@types/three": "^0.181.0",
"@unocss/preset-wind3": "^66.5.4",
"@unocss/reset": "^66.5.4",
"@unocss/transformer-directives": "^66.5.4",
@@ -166,20 +169,17 @@
"happy-dom": "^20.0.2",
"husky": "^9.1.7",
"lint-staged": "^16.2.6",
"mermaid": "^11.12.1",
"monaco-editor": "^0.52.2",
"postcss-pxtorem": "^6.1.0",
"prettier": "^3.6.2",
"release-it": "^17.11.0",
"sass": "1.93.2",
"stream-monaco": "^0.0.2",
"typescript": "^5.9.3",
"unplugin-auto-import": "^20.2.0",
"unplugin-vue-components": "^30.0.0",
"vite": "7.1.11",
"vite": "7.1.12",
"vite-plugin-vue-setup-extend": "^0.4.0",
"vitest": "^3.2.4",
"vue-tsc": "^3.1.1",
"vue-tsc": "^3.1.3",
"web-vitals": "^5.1.0"
},
"config": {
Generated Vendored
+262 -264
View File
File diff suppressed because it is too large Load Diff
+139 -14
View File
@@ -128,8 +128,14 @@
<!-- 聊天信息框 -->
<div :class="{ 'shadow-inner': page.shadow }" class="chat-messages-container w-full box-border flex flex-col">
<n-scrollbar ref="scrollbarRef" class="chat-scrollbar flex-1 min-h-0" trigger="none">
<div class="p-[16px_16px] box-border">
<div
ref="scrollContainerRef"
class="chat-scrollbar flex-1 min-h-0 scrollbar-container"
:class="{ 'hide-scrollbar': !showScrollbar }"
@scroll="handleScroll"
@mouseenter="showScrollbar = true"
@mouseleave="showScrollbar = false">
<div ref="messageContentRef" class="p-[16px_16px] box-border">
<!-- 欢迎消息 -->
<div class="flex gap-12px mb-12px">
<n-avatar
@@ -216,12 +222,11 @@
{{ message.content }}
</template>
<template v-else>
<div
class="code-block-wrapper"
:class="themes.content === 'dark' ? 'code-block-dark' : 'code-block-light'">
<div class="code-block-wrapper" :class="isDarkTheme ? 'code-block-dark' : 'code-block-light'">
<MarkdownRender
:content="message.content"
:is-dark="themes.content === 'dark'"
:is-dark="isDarkTheme"
:viewportPriority="false"
:code-block-light-theme="'vitesse-light'"
:code-block-dark-theme="'vitesse-dark'"
:themes="['vitesse-light', 'vitesse-dark']"
@@ -229,7 +234,10 @@
showPreviewButton: false,
showFontSizeButtons: false,
enableFontSizeControl: false,
showExpandButton: false
showExpandButton: false,
monacoOptions: {
MAX_HEIGHT: Number.MAX_SAFE_INTEGER
}
}" />
</div>
</template>
@@ -238,7 +246,7 @@
</div>
</div>
</div>
</n-scrollbar>
</div>
</div>
<div class="h-1px bg-[--line-color]"></div>
<!-- 下半部分输入框以及功能栏 -->
@@ -439,13 +447,15 @@
</main>
</template>
<script setup lang="ts">
import { type InputInst, type ScrollbarInst, NIcon, NPagination, NTag, NEmpty, NSpin, NAvatar } from 'naive-ui'
import { type InputInst, NIcon, NPagination, NTag, NEmpty, NSpin, NAvatar } from 'naive-ui'
import { Icon } from '@iconify/vue'
import MsgInput from '@/components/rightBox/MsgInput.vue'
import { useMitt } from '@/hooks/useMitt.ts'
import { useSettingStore } from '@/stores/setting.ts'
import { useUserStore } from '@/stores/user.ts'
import MarkdownRender from 'vue-renderer-markdown'
import { useResizeObserver } from '@vueuse/core'
import { ThemeEnum } from '@/enums'
import 'vue-renderer-markdown/index.css'
import {
modelPage,
@@ -464,6 +474,7 @@ import router from '@/router'
const settingStore = useSettingStore()
const userStore = useUserStore()
const { page, themes } = storeToRefs(settingStore)
const MsgInputRef = ref()
/** 是否是编辑模式 */
const isEdit = ref(false)
@@ -477,6 +488,34 @@ const currentChat = ref({
messageCount: 0
})
// 计算是否是暗色主题,处理空值和未初始化的情况
const isDarkTheme = computed(() => {
const content = themes.value.content
// 如果 content 为空,尝试从 document.documentElement.dataset.theme 获取
if (!content) {
const datasetTheme = document.documentElement.dataset.theme
return datasetTheme === ThemeEnum.DARK
}
return content === ThemeEnum.DARK
})
// 同时监听 isDarkTheme 的变化,确保在主题切换时组件能正确更新
watch(
isDarkTheme,
(newVal) => {
// 主题切换时,确保 CSS 类正确应用
nextTick(() => {
// 查找所有 code-block-wrapper 元素,确保它们有正确的类
const wrappers = document.querySelectorAll('.code-block-wrapper')
wrappers.forEach((wrapper) => {
wrapper.classList.remove('code-block-dark', 'code-block-light')
wrapper.classList.add(newVal ? 'code-block-dark' : 'code-block-light')
})
})
},
{ immediate: true }
)
// 消息列表
interface Message {
type: 'user' | 'assistant'
@@ -489,7 +528,10 @@ interface Message {
}
const messageList = ref<Message[]>([])
const scrollbarRef = ref<ScrollbarInst | null>(null)
const scrollContainerRef = ref<HTMLElement | null>(null)
const messageContentRef = ref<HTMLElement | null>(null)
const shouldAutoStickBottom = ref(true)
const showScrollbar = ref(true)
const loadingMessages = ref(false) // 消息加载状态
const showDeleteChatConfirm = ref(false) // 删除会话确认框显示状态
@@ -505,14 +547,56 @@ const hasAvailableRoles = computed(() => {
})
// 滚动到底部
const scrollToBottom = () => {
const getScrollContainer = () => scrollContainerRef.value
const isNearBottom = () => {
const container = getScrollContainer()
if (!container) return true
const offset = container.scrollHeight - (container.scrollTop + container.clientHeight)
return offset <= 80
}
const scrollToBottom = (retryCount = 2) => {
shouldAutoStickBottom.value = true
const raf =
typeof window === 'undefined'
? (cb: FrameRequestCallback) => setTimeout(() => cb(0), 16)
: window.requestAnimationFrame
const scroll = () => {
const container = getScrollContainer()
if (!container) return
container.scrollTo({ top: container.scrollHeight, behavior: 'auto' })
}
const runWithRetry = (remaining: number) => {
raf(() => {
scroll()
if (remaining > 0) {
runWithRetry(remaining - 1)
}
})
}
nextTick(() => {
const scrollbar = scrollbarRef.value
if (!scrollbar) return
scrollbar.scrollTo({ top: Number.MAX_SAFE_INTEGER })
runWithRetry(retryCount)
})
}
const handleScroll = () => {
shouldAutoStickBottom.value = isNearBottom()
}
watch(scrollContainerRef, () => {
handleScroll()
})
useResizeObserver(messageContentRef, () => {
if (shouldAutoStickBottom.value) {
scrollToBottom()
}
})
// 模型选择相关状态
const showModelPopover = ref(false)
const modelLoading = ref(false)
@@ -1080,6 +1164,47 @@ onMounted(() => {
overflow: hidden;
}
/* 原生滚动条样式与交互 */
.scrollbar-container {
flex: 1;
overflow-y: auto;
overflow-x: hidden;
-webkit-overflow-scrolling: touch;
will-change: scroll-position;
transform: translateZ(0);
&::-webkit-scrollbar {
width: 6px;
transition: opacity 0.3s ease;
}
&::-webkit-scrollbar-thumb {
background-color: rgba(144, 144, 144, 0.3);
border-radius: 3px;
transition:
opacity 0.3s ease,
background-color 0.3s ease;
}
&::-webkit-scrollbar-thumb:hover {
background-color: rgba(144, 144, 144, 0.5);
}
&::-webkit-scrollbar-track {
background: transparent;
}
&.hide-scrollbar {
&::-webkit-scrollbar,
&::-webkit-scrollbar-thumb,
&::-webkit-scrollbar-track {
background: transparent;
}
padding-right: 0.01px;
}
}
/* 输入框容器固定在底部 */
.chat-input-container {
flex-shrink: 0;
+13 -3
View File
@@ -56,7 +56,12 @@
--code-block-toolbar-hover-bg: rgba(148, 163, 184, 0.15);
--code-block-toolbar-active-bg: rgba(148, 163, 184, 0.25);
--code-block-toolbar-border-color: rgba(148, 163, 184, 0.35);
--code-block-skeleton-line: linear-gradient(90deg, rgba(148, 163, 184, 0.08) 25%, rgba(148, 163, 184, 0.16) 37%, rgba(148, 163, 184, 0.08) 63%);
--code-block-skeleton-line: linear-gradient(
90deg,
rgba(148, 163, 184, 0.08) 25%,
rgba(148, 163, 184, 0.16) 37%,
rgba(148, 163, 184, 0.08) 63%
);
}
.code-block-light {
@@ -67,5 +72,10 @@
--code-block-toolbar-hover-bg: rgba(19, 152, 127, 0.08);
--code-block-toolbar-active-bg: rgba(19, 152, 127, 0.14);
--code-block-toolbar-border-color: rgba(19, 152, 127, 0.25);
--code-block-skeleton-line: linear-gradient(90deg, rgba(148, 163, 184, 0.12) 25%, rgba(148, 163, 184, 0.22) 37%, rgba(148, 163, 184, 0.12) 63%);
}
--code-block-skeleton-line: linear-gradient(
90deg,
rgba(148, 163, 184, 0.12) 25%,
rgba(148, 163, 184, 0.22) 37%,
rgba(148, 163, 184, 0.12) 63%
);
}
-12
View File
@@ -171,18 +171,6 @@ export default defineConfig(({ mode }: ConfigEnv) => {
}
}
},
// 优化依赖配置
optimizeDeps: {
include: [
'vue-renderer-markdown',
'shiki',
'shiki/themes/vitesse-dark.mjs',
'shiki/themes/vitesse-light.mjs',
'vue-renderer-markdown/index.css'
]
},
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
//
// 1. prevent vite from obscuring rust errors