diff --git a/src/renderer/src/components/ChatView.vue b/src/renderer/src/components/ChatView.vue
index 428a75923..e527be4c5 100644
--- a/src/renderer/src/components/ChatView.vue
+++ b/src/renderer/src/components/ChatView.vue
@@ -112,4 +112,8 @@ onUnmounted(async () => {
window.electron.ipcRenderer.removeAllListeners(STREAM_EVENTS.END)
window.electron.ipcRenderer.removeAllListeners(STREAM_EVENTS.ERROR)
})
+
+defineExpose({
+ messageList
+})
diff --git a/src/renderer/src/components/MessageNavigationSidebar.vue b/src/renderer/src/components/MessageNavigationSidebar.vue
new file mode 100644
index 000000000..b76ad6732
--- /dev/null
+++ b/src/renderer/src/components/MessageNavigationSidebar.vue
@@ -0,0 +1,253 @@
+
+
+
+
+
+
+
diff --git a/src/renderer/src/components/ThreadsView.vue b/src/renderer/src/components/ThreadsView.vue
index c3a885be0..6bf3cabbb 100644
--- a/src/renderer/src/components/ThreadsView.vue
+++ b/src/renderer/src/components/ThreadsView.vue
@@ -215,6 +215,7 @@ const createNewThread = async () => {
try {
await chatStore.createNewEmptyThread()
chatStore.isSidebarOpen = false
+ chatStore.isMessageNavigationOpen = false
} catch (error) {
console.error(t('common.error.createChatFailed'), error)
}
@@ -226,6 +227,7 @@ const handleThreadSelect = async (thread: CONVERSATION) => {
await chatStore.setActiveThread(thread.id)
if (windowSize.width.value < 1024) {
chatStore.isSidebarOpen = false
+ chatStore.isMessageNavigationOpen = false
}
} catch (error) {
console.error(t('common.error.selectChatFailed'), error)
diff --git a/src/renderer/src/components/TitleView.vue b/src/renderer/src/components/TitleView.vue
index 7faf7fb6e..62ce911f0 100644
--- a/src/renderer/src/components/TitleView.vue
+++ b/src/renderer/src/components/TitleView.vue
@@ -43,6 +43,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -59,6 +99,7 @@ import { useSettingsStore } from '@/stores/settings'
import { RENDERER_MODEL_META } from '@shared/presenter'
import { useArtifactStore } from '@/stores/artifact'
import ArtifactDialog from '@/components/artifacts/ArtifactDialog.vue'
+import MessageNavigationSidebar from '@/components/MessageNavigationSidebar.vue'
import { useRoute } from 'vue-router'
import { useLanguageStore } from '@/stores/language'
const ThreadsView = defineAsyncComponent(() => import('@/components/ThreadsView.vue'))
@@ -71,6 +112,7 @@ const route = useRoute()
const chatStore = useChatStore()
const title = useTitle()
const langStore = useLanguageStore()
+const chatViewRef = ref()
// 添加标题更新逻辑
const updateTitle = () => {
const activeThread = chatStore.activeThread
@@ -103,12 +145,18 @@ watch(
// 点击外部区域关闭侧边栏
const sidebarRef = ref()
+const messageNavigationRef = ref()
const isLargeScreen = useMediaQuery('(min-width: 1024px)')
-onClickOutside(sidebarRef, () => {
+onClickOutside(sidebarRef, (event) => {
+ const isClickInMessageNavigation = messageNavigationRef.value?.contains(event.target as Node)
+
if (chatStore.isSidebarOpen && !isLargeScreen.value) {
chatStore.isSidebarOpen = false
}
+ if (chatStore.isMessageNavigationOpen && !isLargeScreen.value && !isClickInMessageNavigation) {
+ chatStore.isMessageNavigationOpen = false
+ }
})
const activeModel = computed(() => {
@@ -161,6 +209,22 @@ const activeModel = computed(() => {
tags: []
}
})
+
+/**
+ * 处理滚动到指定消息
+ */
+const handleScrollToMessage = (messageId: string) => {
+ if (chatViewRef.value && chatViewRef.value.messageList) {
+ chatViewRef.value.messageList.scrollToMessage(messageId)
+
+ // 在小屏幕模式下,滚动完成后延迟关闭导航
+ if (!isLargeScreen.value && chatStore.isMessageNavigationOpen) {
+ setTimeout(() => {
+ chatStore.isMessageNavigationOpen = false
+ }, 1000) // 延迟1秒关闭,让用户看到滚动效果
+ }
+ }
+}