mirror of
https://github.com/HuLaSpark/HuLa.git
synced 2026-08-28 19:11:16 +08:00
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:
Vendored
+1
-1
@@ -28,7 +28,7 @@
|
||||
}
|
||||
},
|
||||
"git": {
|
||||
"commitMessage": "chore: 发布 v${version}"
|
||||
"commitMessage": "chore: release v${version}"
|
||||
},
|
||||
"npm": {
|
||||
"publish": false
|
||||
|
||||
Vendored
+8
-8
@@ -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": {
|
||||
|
||||
+262
-264
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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%
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user