From d821266e8cd29160fb226331171f5511be405927 Mon Sep 17 00:00:00 2001 From: Hongyu Shi Date: Mon, 1 Sep 2025 16:02:39 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat(settings):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E9=AA=8C=E8=AF=81=E4=BB=BB=E5=8A=A1=E8=B0=83=E5=BA=A6=E4=B8=8E?= =?UTF-8?q?=E7=AE=A1=E7=90=86=EF=BC=8C=E9=98=B2=E6=AD=A2=E9=A2=91=E7=B9=81?= =?UTF-8?q?=E8=A7=A6=E5=8F=91=E9=AA=8C=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Hongyu Shi --- src/app/settings.py | 73 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 57 insertions(+), 16 deletions(-) diff --git a/src/app/settings.py b/src/app/settings.py index d62a9b97..d8653230 100644 --- a/src/app/settings.py +++ b/src/app/settings.py @@ -7,7 +7,7 @@ from typing import TYPE_CHECKING from textual import on from textual.containers import Container, Horizontal -from textual.screen import Screen +from textual.screen import ModalScreen from textual.widgets import Button, Input, Label, Static from backend.hermes import HermesChatClient @@ -22,7 +22,7 @@ if TYPE_CHECKING: from backend.base import LLMClientBase -class SettingsScreen(Screen): +class SettingsScreen(ModalScreen): """设置页面""" CSS_PATH = "css/styles.tcss" @@ -43,6 +43,10 @@ class SettingsScreen(Screen): self.validation_message = "" self.validator = APIValidator() + # 防抖和验证任务管理 + self._validation_task: asyncio.Task | None = None + self._debounce_timer: asyncio.Task | None = None + def compose(self) -> ComposeResult: """构建设置页面""" yield Container( @@ -116,9 +120,7 @@ class SettingsScreen(Screen): task.add_done_callback(self.background_tasks.discard) # 启动配置验证 - validation_task = asyncio.create_task(self._validate_configuration()) - self.background_tasks.add(validation_task) - validation_task.add_done_callback(self.background_tasks.discard) + self._schedule_validation() # 确保操作按钮始终可见 self._ensure_buttons_visible() @@ -161,9 +163,7 @@ class SettingsScreen(Screen): task.add_done_callback(self.background_tasks.discard) # 重新验证配置 - validation_task = asyncio.create_task(self._validate_configuration()) - self.background_tasks.add(validation_task) - validation_task.add_done_callback(self.background_tasks.discard) + self._schedule_validation() @on(Button.Pressed, "#backend-btn") def toggle_backend(self) -> None: @@ -225,9 +225,7 @@ class SettingsScreen(Screen): self._ensure_buttons_visible() # 切换后端后重新验证配置 - validation_task = asyncio.create_task(self._validate_configuration()) - self.background_tasks.add(validation_task) - validation_task.add_done_callback(self.background_tasks.discard) + self._schedule_validation() @on(Button.Pressed, "#model-btn") def toggle_model(self) -> None: @@ -250,9 +248,7 @@ class SettingsScreen(Screen): model_btn.label = self.selected_model # 模型改变时重新验证配置 - validation_task = asyncio.create_task(self._validate_configuration()) - self.background_tasks.add(validation_task) - validation_task.add_done_callback(self.background_tasks.discard) + self._schedule_validation() except (IndexError, ValueError): # 处理任何可能的异常 self.selected_model = self.models[0] if self.models else "默认模型" @@ -262,6 +258,12 @@ class SettingsScreen(Screen): @on(Button.Pressed, "#save-btn") def save_settings(self) -> None: """保存设置""" + # 取消所有后台任务 + for task in self.background_tasks: + if not task.done(): + task.cancel() + self.background_tasks.clear() + # 检查验证状态 if not self.is_validated: return @@ -289,14 +291,48 @@ class SettingsScreen(Screen): @on(Button.Pressed, "#cancel-btn") def cancel_settings(self) -> None: """取消设置""" + # 取消所有后台任务 + for task in self.background_tasks: + if not task.done(): + task.cancel() + self.background_tasks.clear() + self.app.pop_screen() def on_key(self, event: Key) -> None: """处理键盘事件""" if event.key == "escape": + # 取消所有后台任务 + for task in self.background_tasks: + if not task.done(): + task.cancel() + self.background_tasks.clear() # ESC 键退出设置页面,等效于取消 self.app.pop_screen() + def _schedule_validation(self) -> None: + """调度验证任务,带防抖机制""" + # 取消之前的定时器 + if self._debounce_timer and not self._debounce_timer.done(): + self._debounce_timer.cancel() + + # 取消之前的验证任务 + if self._validation_task and not self._validation_task.done(): + self._validation_task.cancel() + + # 创建新的定时器,1秒后启动验证 + async def debounce_and_validate() -> None: + await asyncio.sleep(1.0) # 防抖延迟 + if self._validation_task and not self._validation_task.done(): + return # 如果已经有验证任务在运行,跳过 + self._validation_task = asyncio.create_task(self._validate_configuration()) + self.background_tasks.add(self._validation_task) + self._validation_task.add_done_callback(self.background_tasks.discard) + + self._debounce_timer = asyncio.create_task(debounce_and_validate()) + self.background_tasks.add(self._debounce_timer) + self._debounce_timer.add_done_callback(self.background_tasks.discard) + def _ensure_buttons_visible(self) -> None: """确保操作按钮始终可见""" @@ -325,12 +361,16 @@ class SettingsScreen(Screen): try: if self.backend == Backend.OPENAI: + # 检查是否有有效的模型选择 + if not self.selected_model or not self.models: + # 如果没有模型,跳过验证,不更改验证状态 + return + # 验证 OpenAI 配置 - model = self.selected_model if self.selected_model else "gpt-3.5-turbo" valid, message, _ = await self.validator.validate_llm_config( endpoint=base_url, api_key=api_key, - model=model, + model=self.selected_model, timeout=10, ) self.is_validated = valid @@ -345,6 +385,7 @@ class SettingsScreen(Screen): self.is_validated = False self.validation_message = f"验证过程中发生错误: {e!s}" + self.notify(self.validation_message, severity="error" if not self.is_validated else "information") self._update_save_button_state() def _update_save_button_state(self) -> None: -- Gitee From d72416bf1e468a5443f6f749ca3c684d63fbd4de Mon Sep 17 00:00:00 2001 From: Hongyu Shi Date: Mon, 1 Sep 2025 16:02:52 +0800 Subject: [PATCH 2/2] =?UTF-8?q?feat(styles):=20=E4=BC=98=E5=8C=96=E5=AF=B9?= =?UTF-8?q?=E8=AF=9D=E6=A1=86=E6=A0=B7=E5=BC=8F=EF=BC=8C=E8=B0=83=E6=95=B4?= =?UTF-8?q?=E5=AF=B9=E9=BD=90=E6=96=B9=E5=BC=8F=E5=92=8C=E5=B0=BA=E5=AF=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Hongyu Shi --- src/app/css/styles.tcss | 45 ++++++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/src/app/css/styles.tcss b/src/app/css/styles.tcss index fbe381f9..19578663 100644 --- a/src/app/css/styles.tcss +++ b/src/app/css/styles.tcss @@ -55,17 +55,20 @@ Static { } /* 设置界面样式 */ +SettingsScreen { + align: center middle; +} + #settings-screen { align: center middle; + width: 80%; + height: 95%; color: #ffffff; - padding: 1; } /* 设置容器样式 */ #settings-container { align: center top; - width: 80%; - height: 100%; border: solid #4963b1; padding: 1 2; } @@ -124,14 +127,11 @@ Static { } /* 退出对话框屏幕样式 */ -#exit-dialog-screen { +ExitDialog { align: center middle; - width: 100%; - height: 100%; } -/* 退出对话框样式 */ -#exit-dialog { +#exit-dialog-screen { align: center middle; width: 50%; height: 12; @@ -141,6 +141,11 @@ Static { padding: 1; } +/* 退出对话框样式 */ +#exit-dialog { + align: center middle; +} + /* 对话框文本样式 */ #dialog-text { text-align: center; @@ -157,14 +162,11 @@ Static { } /* 智能体选择对话框屏幕样式 */ -#agent-dialog-screen { +AgentSelectionDialog { align: center middle; - width: 100%; - height: 100%; } -/* 智能体选择对话框样式 */ -#agent-dialog { +#agent-dialog-screen { align: center middle; width: 60%; height: 70%; @@ -174,6 +176,11 @@ Static { padding: 1; } +/* 智能体选择对话框样式 */ +#agent-dialog { + align: center middle; +} + /* 智能体对话框标题样式 */ #agent-dialog-title { text-align: center; @@ -206,14 +213,11 @@ Static { } /* 后端要求对话框屏幕样式 */ -#backend-dialog-screen { +BackendRequiredDialog { align: center middle; - width: 100%; - height: 100%; } -/* 后端要求对话框样式 */ -#backend-dialog { +#backend-dialog-screen { align: center middle; width: 50%; height: 15; @@ -223,6 +227,11 @@ Static { padding: 1; } +/* 后端要求对话框样式 */ +#backend-dialog { + align: center middle; +} + /* 后端对话框标题样式 */ #backend-dialog-title { text-align: center; -- Gitee