From 045be7943d6f6cc9e0bcde8379cdcb0d7512b39f Mon Sep 17 00:00:00 2001 From: Soulter <37870767+Soulter@users.noreply.github.com> Date: Fri, 27 Mar 2026 01:58:04 +0800 Subject: [PATCH 1/8] =?UTF-8?q?revert:=20"fix(provider):=20restore=20param?= =?UTF-8?q?eter=20transparency=20in=20core=20LLM=20provider=20ad=E2=80=A6"?= =?UTF-8?q?=20(#7023)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 1ad7e10c0fe86624f32b6661dd914373204f8e39. --- astrbot/core/provider/sources/anthropic_source.py | 4 ++-- astrbot/core/provider/sources/gemini_source.py | 4 ++-- astrbot/core/provider/sources/openai_source.py | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/astrbot/core/provider/sources/anthropic_source.py b/astrbot/core/provider/sources/anthropic_source.py index b6d6ebeaf..203d0610f 100644 --- a/astrbot/core/provider/sources/anthropic_source.py +++ b/astrbot/core/provider/sources/anthropic_source.py @@ -515,7 +515,7 @@ class ProviderAnthropic(Provider): model = model or self.get_model() - payloads = {**kwargs, "messages": new_messages, "model": model} + payloads = {"messages": new_messages, "model": model} # Anthropic has a different way of handling system prompts if system_prompt: @@ -571,7 +571,7 @@ class ProviderAnthropic(Provider): model = model or self.get_model() - payloads = {**kwargs, "messages": new_messages, "model": model} + payloads = {"messages": new_messages, "model": model} # Anthropic has a different way of handling system prompts if system_prompt: diff --git a/astrbot/core/provider/sources/gemini_source.py b/astrbot/core/provider/sources/gemini_source.py index c66a9a7e9..9557f3dbc 100644 --- a/astrbot/core/provider/sources/gemini_source.py +++ b/astrbot/core/provider/sources/gemini_source.py @@ -757,7 +757,7 @@ class ProviderGoogleGenAI(Provider): model = model or self.get_model() - payloads = {**kwargs, "messages": context_query, "model": model} + payloads = {"messages": context_query, "model": model} retry = 10 keys = self.api_keys.copy() @@ -812,7 +812,7 @@ class ProviderGoogleGenAI(Provider): model = model or self.get_model() - payloads = {**kwargs, "messages": context_query, "model": model} + payloads = {"messages": context_query, "model": model} retry = 10 keys = self.api_keys.copy() diff --git a/astrbot/core/provider/sources/openai_source.py b/astrbot/core/provider/sources/openai_source.py index 7e09d1615..cdad66a22 100644 --- a/astrbot/core/provider/sources/openai_source.py +++ b/astrbot/core/provider/sources/openai_source.py @@ -812,7 +812,8 @@ class ProviderOpenAIOfficial(Provider): context_query = await self._materialize_context_image_parts(context_query) model = model or self.get_model() - payloads = {**kwargs, "messages": context_query, "model": model} + + payloads = {"messages": context_query, "model": model} self._finally_convert_payload(payloads) From 8986d05309d384f0d4e798d5d52c44be39183213 Mon Sep 17 00:00:00 2001 From: SJ Date: Fri, 27 Mar 2026 11:16:21 +0800 Subject: [PATCH 2/8] fix(dashboard): update aiocqhttp tutorial links (#7038) Co-authored-by: idiotsj --- dashboard/src/components/platform/AddNewPlatform.vue | 2 +- dashboard/src/utils/platformUtils.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dashboard/src/components/platform/AddNewPlatform.vue b/dashboard/src/components/platform/AddNewPlatform.vue index e771a56e5..147732517 100644 --- a/dashboard/src/components/platform/AddNewPlatform.vue +++ b/dashboard/src/components/platform/AddNewPlatform.vue @@ -259,7 +259,7 @@

{{ tm('dialog.securityWarning.aiocqhttpTokenMissing') }}

{{ tm('dialog.securityWarning.learnMore') }}
diff --git a/dashboard/src/utils/platformUtils.js b/dashboard/src/utils/platformUtils.js index 04cb226fa..384b204aa 100644 --- a/dashboard/src/utils/platformUtils.js +++ b/dashboard/src/utils/platformUtils.js @@ -50,7 +50,7 @@ export function getTutorialLink(platformType) { const tutorialMap = { "qq_official_webhook": "https://docs.astrbot.app/platform/qqofficial/webhook.html", "qq_official": "https://docs.astrbot.app/platform/qqofficial/websockets.html", - "aiocqhttp": "https://docs.astrbot.app/platform/aiocqhttp/napcat.html", + "aiocqhttp": "https://docs.astrbot.app/platform/aiocqhttp.html", "wecom": "https://docs.astrbot.app/platform/wecom.html", "weixin_oc": "https://docs.astrbot.app/platform/weixin_oc.html", "wecom_ai_bot": "https://docs.astrbot.app/platform/wecom_ai_bot.html", From af6f9cfc5e2b964d14b187df72c445b54d40510c Mon Sep 17 00:00:00 2001 From: Izayoi9 <105905446+Izayoi9@users.noreply.github.com> Date: Fri, 27 Mar 2026 11:24:06 +0800 Subject: [PATCH 3/8] =?UTF-8?q?fix:=20=E4=BD=BF=E7=94=A8=20removesuffix=20?= =?UTF-8?q?=E6=9B=BF=E4=BB=A3=20rstrip=20=E4=BF=AE=E5=A4=8D=20URL=20?= =?UTF-8?q?=E5=AD=97=E7=AC=A6=E8=AF=AF=E5=88=A0=E9=97=AE=E9=A2=98=20(#7026?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 之前在 #6863 中我提交的修复使用了 rstrip() 来移除末尾的 /embeddings, 但 rstrip() 是字符集操作,会误删 URL 末尾属于该字符集的字符。 例如 siliconflow.cn 的末尾 n 会被误删,导致 URL 变成 siliconflow.c 改用 removesuffix() 可以正确处理这种情况,只在字符串以指定后缀结尾时才移除。 closes #7025 --- astrbot/core/provider/sources/openai_embedding_source.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/astrbot/core/provider/sources/openai_embedding_source.py b/astrbot/core/provider/sources/openai_embedding_source.py index 9bf127766..ae531996a 100644 --- a/astrbot/core/provider/sources/openai_embedding_source.py +++ b/astrbot/core/provider/sources/openai_embedding_source.py @@ -27,8 +27,8 @@ class OpenAIEmbeddingProvider(EmbeddingProvider): api_base = ( provider_config.get("embedding_api_base", "https://api.openai.com/v1") .strip() - .rstrip("/") - .rstrip("/embeddings") + .removesuffix("/") + .removesuffix("/embeddings") ) if api_base and not api_base.endswith("/v1") and not api_base.endswith("/v4"): # /v4 see #5699 From a5e86c8b942d6ff6cddb6d4cbad4b843bad0f33f Mon Sep 17 00:00:00 2001 From: sanyekana Date: Fri, 27 Mar 2026 11:29:03 +0800 Subject: [PATCH 4/8] fix(telegram): preserve attachment captions (#7020) * fix(telegram): preserve attachment captions * Update astrbot/core/platform/sources/telegram/tg_adapter.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --------- Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../platform/sources/telegram/tg_adapter.py | 25 ++-- tests/fixtures/mocks/telegram.py | 3 +- tests/test_telegram_adapter.py | 108 ++++++++++++++++++ 3 files changed, 125 insertions(+), 11 deletions(-) create mode 100644 tests/test_telegram_adapter.py diff --git a/astrbot/core/platform/sources/telegram/tg_adapter.py b/astrbot/core/platform/sources/telegram/tg_adapter.py index 5f4491357..6c22a1aa5 100644 --- a/astrbot/core/platform/sources/telegram/tg_adapter.py +++ b/astrbot/core/platform/sources/telegram/tg_adapter.py @@ -335,6 +335,18 @@ class TelegramPlatformAdapter(Platform): logger.warning("Received an update without a message.") return None + def _apply_caption() -> None: + if update.message.caption: + message.message_str = update.message.caption + message.message.append(Comp.Plain(message.message_str)) + if update.message.caption and update.message.caption_entities: + for entity in update.message.caption_entities: + if entity.type == "mention": + name = update.message.caption[ + entity.offset + 1 : entity.offset + entity.length + ] + message.message.append(Comp.At(qq=name, name=name)) + message = AstrBotMessage() message.session_id = str(update.message.chat.id) @@ -454,16 +466,7 @@ class TelegramPlatformAdapter(Platform): photo = update.message.photo[-1] # get the largest photo file = await photo.get_file() message.message.append(Comp.Image(file=file.file_path, url=file.file_path)) - if update.message.caption: - message.message_str = update.message.caption - message.message.append(Comp.Plain(message.message_str)) - if update.message.caption_entities: - for entity in update.message.caption_entities: - if entity.type == "mention": - name = message.message_str[ - entity.offset + 1 : entity.offset + entity.length - ] - message.message.append(Comp.At(qq=name, name=name)) + _apply_caption() elif update.message.sticker: # 将sticker当作图片处理 @@ -486,6 +489,7 @@ class TelegramPlatformAdapter(Platform): message.message.append( Comp.File(file=file_path, name=file_name, url=file_path) ) + _apply_caption() elif update.message.video: file = await update.message.video.get_file() @@ -497,6 +501,7 @@ class TelegramPlatformAdapter(Platform): ) else: message.message.append(Comp.Video(file=file_path, path=file.file_path)) + _apply_caption() return message diff --git a/tests/fixtures/mocks/telegram.py b/tests/fixtures/mocks/telegram.py index 904ec4d09..079292951 100644 --- a/tests/fixtures/mocks/telegram.py +++ b/tests/fixtures/mocks/telegram.py @@ -33,7 +33,8 @@ def create_mock_telegram_modules(): mock_telegram_ext = MagicMock() mock_telegram_ext.ApplicationBuilder = MagicMock - mock_telegram_ext.ContextTypes = MagicMock + mock_telegram_ext.ContextTypes = MagicMock() + mock_telegram_ext.ContextTypes.DEFAULT_TYPE = MagicMock mock_telegram_ext.ExtBot = MagicMock mock_telegram_ext.filters = MagicMock() mock_telegram_ext.filters.ALL = MagicMock() diff --git a/tests/test_telegram_adapter.py b/tests/test_telegram_adapter.py new file mode 100644 index 000000000..f7ae4d4be --- /dev/null +++ b/tests/test_telegram_adapter.py @@ -0,0 +1,108 @@ +import asyncio +import importlib +import sys +from unittest.mock import MagicMock, patch + +import pytest + +import astrbot.api.message_components as Comp +from tests.fixtures.helpers import ( + create_mock_file, + create_mock_update, + make_platform_config, +) +from tests.fixtures.mocks.telegram import create_mock_telegram_modules + +_TELEGRAM_PLATFORM_ADAPTER = None + + +def _load_telegram_adapter(): + global _TELEGRAM_PLATFORM_ADAPTER + if _TELEGRAM_PLATFORM_ADAPTER is not None: + return _TELEGRAM_PLATFORM_ADAPTER + + mocks = create_mock_telegram_modules() + patched_modules = { + "telegram": mocks["telegram"], + "telegram.constants": mocks["telegram"].constants, + "telegram.error": mocks["telegram"].error, + "telegram.ext": mocks["telegram.ext"], + "telegramify_markdown": mocks["telegramify_markdown"], + "apscheduler": mocks["apscheduler"], + "apscheduler.schedulers": mocks["apscheduler"].schedulers, + "apscheduler.schedulers.asyncio": mocks["apscheduler"].schedulers.asyncio, + "apscheduler.schedulers.background": mocks["apscheduler"].schedulers.background, + } + with patch.dict(sys.modules, patched_modules): + sys.modules.pop("astrbot.core.platform.sources.telegram.tg_adapter", None) + module = importlib.import_module("astrbot.core.platform.sources.telegram.tg_adapter") + _TELEGRAM_PLATFORM_ADAPTER = module.TelegramPlatformAdapter + return _TELEGRAM_PLATFORM_ADAPTER + + +def _build_context() -> MagicMock: + context = MagicMock() + context.bot.username = "test_bot" + context.bot.id = 12345678 + return context + + +@pytest.mark.asyncio +async def test_telegram_document_caption_populates_message_text_and_plain(): + TelegramPlatformAdapter = _load_telegram_adapter() + adapter = TelegramPlatformAdapter( + make_platform_config("telegram"), + {}, + asyncio.Queue(), + ) + document = create_mock_file("https://api.telegram.org/file/test/report.md") + document.file_name = "report.md" + mention = MagicMock(type="mention", offset=0, length=6) + update = create_mock_update( + message_text=None, + document=document, + caption="@alice 请总结这份文档", + caption_entities=[mention], + ) + + result = await adapter.convert_message(update, _build_context()) + + assert result is not None + assert result.message_str == "@alice 请总结这份文档" + assert any(isinstance(component, Comp.File) for component in result.message) + assert any( + isinstance(component, Comp.Plain) + and component.text == "@alice 请总结这份文档" + for component in result.message + ) + assert any( + isinstance(component, Comp.At) and component.qq == "alice" + for component in result.message + ) + + +@pytest.mark.asyncio +async def test_telegram_video_caption_populates_message_text_and_plain(): + TelegramPlatformAdapter = _load_telegram_adapter() + adapter = TelegramPlatformAdapter( + make_platform_config("telegram"), + {}, + asyncio.Queue(), + ) + video = create_mock_file("https://api.telegram.org/file/test/lesson.mp4") + video.file_name = "lesson.mp4" + update = create_mock_update( + message_text=None, + video=video, + caption="这段视频讲了什么", + ) + + result = await adapter.convert_message(update, _build_context()) + + assert result is not None + assert result.message_str == "这段视频讲了什么" + assert any(isinstance(component, Comp.Video) for component in result.message) + assert any( + isinstance(component, Comp.Plain) and component.text == "这段视频讲了什么" + for component in result.message + ) From 26627887d172208a02e234bc3967d2778e6b8858 Mon Sep 17 00:00:00 2001 From: silwings1986 <7500493@qq.com> Date: Fri, 27 Mar 2026 11:34:18 +0800 Subject: [PATCH 5/8] fix(wecom): fallback to message API when kf returns 40096 (#7012) * fix(wecom): fallback to regular message API when kf API returns 40096 When sending WeCom messages via kf/send_msg, if the API returns error 40096 (invalid external userid), fall back to the regular message/send API. This handles internal employees who don't have external userids. Fixes the issue where internal WeCom users (e.g. WangCong) would cause kf API to fail with 'invalid external userid' error. * fix(wecom): improve error handling for kf API fallback to regular message API --------- Co-authored-by: Soulter <905617992@qq.com> --- .../core/platform/sources/wecom/wecom_event.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/astrbot/core/platform/sources/wecom/wecom_event.py b/astrbot/core/platform/sources/wecom/wecom_event.py index 7aee26e47..265b41014 100644 --- a/astrbot/core/platform/sources/wecom/wecom_event.py +++ b/astrbot/core/platform/sources/wecom/wecom_event.py @@ -2,6 +2,7 @@ import asyncio import os from wechatpy.enterprise import WeChatClient +from wechatpy.exceptions import WeChatClientException from astrbot.api import logger from astrbot.api.event import AstrMessageEvent, MessageChain @@ -95,7 +96,19 @@ class WecomPlatformEvent(AstrMessageEvent): # Split long text messages if needed plain_chunks = await self.split_plain(comp.text) for chunk in plain_chunks: - kf_message_api.send_text(user_id, self.get_self_id(), chunk) + try: + kf_message_api.send_text(user_id, self.get_self_id(), chunk) + except WeChatClientException as e: + if getattr(e, "errcode", None) == 40096: + # 40096: invalid external userid, fallback to regular message API + logger.warning( + f"kf API error 40096 for user {user_id}, falling back to regular message API" + ) + self.client.message.send_text( + self.get_self_id(), user_id, chunk + ) + else: + raise await asyncio.sleep(0.5) # Avoid sending too fast elif isinstance(comp, Image): img_path = await comp.convert_to_file_path() From 383df74e34a5987a478dd9598295c116b3dc5d70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=80=E8=A2=8B=E7=B1=B3=E8=A6=81=E6=89=9B=E5=B9=BE?= =?UTF-8?q?=E6=A8=93?= Date: Fri, 27 Mar 2026 14:01:18 +0800 Subject: [PATCH 6/8] fix(chatui): refactor routing and layout to drive UI mode from URL and scope state to sessionStorage (#6535) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 移除所有使用 localStorage 的路由,改为使用 sessionStorage * 增加修正 * 增加修正 * 增加修正 * 增加修正 * 增加修正 * 回退修正 就這樣吧 * 小修正 --- dashboard/src/composables/useSessions.ts | 24 +-- dashboard/src/layouts/full/FullLayout.vue | 28 +-- .../full/vertical-header/VerticalHeader.vue | 191 ++++++++++++------ dashboard/src/stores/customizer.ts | 6 +- 4 files changed, 145 insertions(+), 104 deletions(-) diff --git a/dashboard/src/composables/useSessions.ts b/dashboard/src/composables/useSessions.ts index a38fc0362..382ddbc56 100644 --- a/dashboard/src/composables/useSessions.ts +++ b/dashboard/src/composables/useSessions.ts @@ -19,7 +19,6 @@ export function useSessions(chatboxMode: boolean = false) { const selectedSessions = ref([]); const currSessionId = ref(''); const pendingSessionId = ref(null); - // 编辑标题相关 const editTitleDialog = ref(false); const editingTitle = ref(''); @@ -30,29 +29,16 @@ export function useSessions(chatboxMode: boolean = false) { return sessions.value.find(s => s.session_id === currSessionId.value); }); + + async function getSessions() { try { const response = await axios.get('/api/chat/sessions'); sessions.value = response.data.data; - // 处理待加载的会话 - if (pendingSessionId.value) { - const session = sessions.value.find(s => s.session_id === pendingSessionId.value); - if (session) { - selectedSessions.value = [pendingSessionId.value]; - pendingSessionId.value = null; - } - } else if (currSessionId.value) { - // 如果当前有选中的会话,确保它在列表中并被选中 - const session = sessions.value.find(s => s.session_id === currSessionId.value); - if (session) { - selectedSessions.value = [currSessionId.value]; - } - } else if (sessions.value.length > 0) { - // 默认选择第一个会话 - const firstSession = sessions.value[0]; - selectedSessions.value = [firstSession.session_id]; - } + + + } catch (err: any) { if (err.response?.status === 401) { router.push('/auth/login?redirect=/chatbox'); diff --git a/dashboard/src/layouts/full/FullLayout.vue b/dashboard/src/layouts/full/FullLayout.vue index af7011d12..408f1db6b 100644 --- a/dashboard/src/layouts/full/FullLayout.vue +++ b/dashboard/src/layouts/full/FullLayout.vue @@ -17,18 +17,10 @@ const customizer = useCustomizerStore(); const { locale } = useI18n(); const route = useRoute(); const routerLoadingStore = useRouterLoadingStore(); +const isCurrentChatRoute = computed(() => route.path === '/chat' || route.path.startsWith('/chat/')); -const isChatPage = computed(() => { - return route.path.startsWith('/chat'); -}); -const showSidebar = computed(() => { - return customizer.viewMode === 'bot'; -}); - -const showChatPage = computed(() => { - return customizer.viewMode === 'chat'; -}); +const showSidebar = computed(() => !isCurrentChatRoute.value) const migrationDialog = ref | null>(null); const showFirstNoticeDialog = ref(false); @@ -111,20 +103,20 @@ onMounted(() => { -
-
+
+
diff --git a/dashboard/src/layouts/full/vertical-header/VerticalHeader.vue b/dashboard/src/layouts/full/vertical-header/VerticalHeader.vue index aa627baea..1fb32b48a 100644 --- a/dashboard/src/layouts/full/vertical-header/VerticalHeader.vue +++ b/dashboard/src/layouts/full/vertical-header/VerticalHeader.vue @@ -28,6 +28,7 @@ const theme = useTheme(); const { t } = useI18n(); const route = useRoute(); const LAST_BOT_ROUTE_KEY = 'astrbot:last_bot_route'; +const LAST_CHAT_ROUTE_KEY = 'astrbot:last_chat_route'; let dialog = ref(false); let accountWarning = ref(false) let updateStatusDialog = ref(false); @@ -58,7 +59,9 @@ const desktopUpdateHasNewVersion = ref(false); const desktopUpdateCurrentVersion = ref('-'); const desktopUpdateLatestVersion = ref('-'); const desktopUpdateStatus = ref(''); - +const isChatPath = computed(() => + route.path === '/chat' || route.path.startsWith('/chat/') +); const getAppUpdaterBridge = (): AstrBotAppUpdaterBridge | null => { if (typeof window === 'undefined') { return null; @@ -380,7 +383,7 @@ function openReleaseNotesDialog(body: string, tag: string) { } function handleLogoClick() { - if (customizer.viewMode === 'chat') { + if (isChatPath.value) { aboutDialog.value = true; } else { router.push('/about'); @@ -395,10 +398,22 @@ commonStore.createEventSource(); // log commonStore.getStartTime(); // 视图模式切换 -const viewMode = computed({ - get: () => customizer.viewMode, - set: (value: 'bot' | 'chat') => { - customizer.SET_VIEW_MODE(value); +onMounted(() => { + // 初次加載時保存當前路由 + if (typeof window !== 'undefined') { + if (isChatPath.value) { + // 保存 chat ID + const parts = route.fullPath.split('/'); + const sessionId = parts[2]; + if (sessionId) { + sessionStorage.setItem(LAST_CHAT_ROUTE_KEY, sessionId); + console.log('Initial save chat ID:', sessionId); + } + } else { + // 保存 bot 路由(非 chat 頁面) + sessionStorage.setItem(LAST_BOT_ROUTE_KEY, route.fullPath); + console.log('Initial save bot route:', route.fullPath); + } } }); @@ -406,26 +421,61 @@ const viewMode = computed({ // 保存 bot 模式的最後路由 // 監聽 route 變化,保存最後一次 bot 路由 watch(() => route.fullPath, (newPath) => { - if (customizer.viewMode === 'bot' && typeof window !== 'undefined') { - try { - localStorage.setItem(LAST_BOT_ROUTE_KEY, newPath); - } catch (e) { - console.error('Failed to save last bot route to localStorage:', e); + if (typeof window === 'undefined') return; + console.log('Route changed:', { + newPath, + isChat: isChatPath.value, + currentChatId: route.params.id + }); + try { + // 使用現有的 isChatPath 計算屬性來避免名稱衝突 + const isChat = isChatPath.value; // 這裡使用已經計算好的 isChatPath + + // ✅ bot:只存「非 chat 頁」 + if (!isChat) { + sessionStorage.setItem(LAST_BOT_ROUTE_KEY, newPath); } + + // ✅ chat:只存 sessionId + if (isChat) { + const parts = newPath.split('/'); + const sessionId = parts[2]; + + if (sessionId) { + sessionStorage.setItem(LAST_CHAT_ROUTE_KEY, sessionId); + } + } + + } catch (e) { + console.error('Failed to save route:', e); } }); -// 監聽 viewMode 切換 -watch(() => customizer.viewMode, (newMode, oldMode) => { - if (newMode === 'bot' && oldMode === 'chat' && typeof window !== 'undefined') { - // 從 chat 切換回 bot,跳轉到最後一次的 bot 路由 - let lastBotRoute = '/'; +const currentMode = computed({ + get: () => (isChatPath.value ? 'chat' : 'bot'), + set: (val: 'chat' | 'bot') => { try { - lastBotRoute = localStorage.getItem(LAST_BOT_ROUTE_KEY) || '/'; + // 檢查 window 和 sessionStorage 是否存在 + if (typeof window === 'undefined' || typeof sessionStorage === 'undefined') { + // 如果在非瀏覽器環境中,不做任何 sessionStorage 操作 + console.warn('sessionStorage is not available in this environment'); + return; + } + + if (val === 'chat') { + const lastSessionId = sessionStorage.getItem(LAST_CHAT_ROUTE_KEY); + router.push(lastSessionId ? `/chat/${lastSessionId}` : '/chat'); + } else { + let lastBotRoute = sessionStorage.getItem(LAST_BOT_ROUTE_KEY) || '/'; + if (lastBotRoute.startsWith('/chat')) { + lastBotRoute = '/'; + } + router.push(lastBotRoute); + } } catch (e) { - console.error('Failed to read last bot route from localStorage:', e); + // 在受限隱私模式等環境中,sessionStorage 操作可能會拋出 SecurityError + console.warn('Failed to access sessionStorage in currentMode setter:', e); } - router.push(lastBotRoute); } }); @@ -465,29 +515,46 @@ onMounted(async () => { - - mdi-menu - - - - mdi-menu - + + mdi-menu + - - - mdi-menu - + + + mdi-menu + -
+ + mdi-menu + + +
AstrBot Christmas hat - ChatUI + ChatUI
@@ -504,23 +571,23 @@ onMounted(async () => {
- + @@ -542,14 +609,14 @@ onMounted(async () => {