From 8f6ec43d373e0198d91cd47fc8d7354859b899e4 Mon Sep 17 00:00:00 2001 From: Hongyu Shi Date: Mon, 27 Oct 2025 15:02:55 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20LLM=20=E9=80=89?= =?UTF-8?q?=E6=8B=A9=20TUI=20=E5=B4=A9=E6=BA=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Hongyu Shi --- src/app/dialogs/user.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/app/dialogs/user.py b/src/app/dialogs/user.py index fcda486..5d314a2 100644 --- a/src/app/dialogs/user.py +++ b/src/app/dialogs/user.py @@ -228,6 +228,10 @@ class UserConfigDialog(ModalScreen): else: # 渲染模型列表 model_list_container = Container(id=f"{tab_id}-list", classes="llm-model-list") + + # 将容器挂载到父容器 + content_container.mount(model_list_container) + # 向容器添加子组件 for i, model in enumerate(models): is_saved = model.llm_id == self.saved_chat_model # 已保存的 is_activated = model.llm_id == activated_llm_id # 已激活的(用空格确认) @@ -245,13 +249,12 @@ class UserConfigDialog(ModalScreen): model_item = Static(model.model_name, classes=classes) model_list_container.mount(model_item) - content_container.mount(model_list_container) - # 显示当前光标所指模型的详细信息 if 0 <= cursor_index < len(models): current_model = models[cursor_index] - detail_container = self._create_model_detail(current_model) + detail_container = Container(classes="llm-model-detail") content_container.mount(detail_container) + self._populate_model_detail(detail_container, current_model) # 添加帮助文本 tab_pane.mount( @@ -261,19 +264,15 @@ class UserConfigDialog(ModalScreen): ), ) - def _create_model_detail(self, model: ModelInfo) -> Container: + def _populate_model_detail(self, detail_container: Container, model: ModelInfo) -> None: """ - 创建模型详情容器 + 填充模型详情到已挂载的容器中 Args: + detail_container: 已挂载的详情容器 model: 模型信息 - Returns: - 包含模型详情的容器 - """ - detail_container = Container(classes="llm-model-detail") - # 模型描述 if model.llm_description: detail_container.mount( @@ -305,8 +304,6 @@ class UserConfigDialog(ModalScreen): ), ) - return detail_container - def _update_cursor_positions(self) -> None: """根据已保存的配置更新光标位置""" # 基础模型光标 -- Gitee From 067be7de117c8261759137273ab59d1cb0352373 Mon Sep 17 00:00:00 2001 From: Hongyu Shi Date: Mon, 27 Oct 2025 20:07:03 +0800 Subject: [PATCH 2/2] fix: llm config keyboard actions Signed-off-by: Hongyu Shi --- src/app/css/styles.tcss | 14 ++++- src/app/dialogs/user.py | 81 ++++++++++++++-------------- src/backend/hermes/services/model.py | 9 ++-- src/backend/models.py | 13 +++-- 4 files changed, 64 insertions(+), 53 deletions(-) diff --git a/src/app/css/styles.tcss b/src/app/css/styles.tcss index 5c85105..934659d 100644 --- a/src/app/css/styles.tcss +++ b/src/app/css/styles.tcss @@ -62,6 +62,8 @@ SettingsScreen { align: center middle; width: 85%; height: 95%; + max-width: 120; + max-height: 50; color: #ffffff; } @@ -122,7 +124,9 @@ SettingsScreen { width: 20%; height: 3; text-style: bold; + text-align: right; margin-top: 1; + margin-right: 1; padding: 0 1; dock: right; } @@ -531,6 +535,8 @@ UserConfigDialog { align: center middle; width: 80%; height: 80%; + max-width: 100; + max-height: 40; } #user-dialog { @@ -620,6 +626,10 @@ UserConfigDialog { margin: 1 0; background: rgba(73, 99, 177, 0.1); height: auto; + max-height: 15; + overflow-y: auto; + scrollbar-size: 1 1; + dock: bottom; } /* 详情行 */ @@ -634,6 +644,8 @@ UserConfigDialog { color: #888888; width: auto; padding-right: 1; + margin-top: 1; + text-align: right; } /* 详情值 */ @@ -659,7 +671,7 @@ UserConfigDialog { } /* 大模型设置帮助文本 */ -#llm-dialog-help { +.llm-dialog-help { text-align: center; color: #888888; padding: 0 1; diff --git a/src/app/dialogs/user.py b/src/app/dialogs/user.py index 5d314a2..f62d041 100644 --- a/src/app/dialogs/user.py +++ b/src/app/dialogs/user.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, ClassVar +from typing import TYPE_CHECKING from textual import on from textual.containers import Container, Horizontal @@ -15,6 +15,7 @@ from i18n.manager import _ if TYPE_CHECKING: from textual.app import ComposeResult + from textual.events import Key from backend import LLMClientBase from config.manager import ConfigManager @@ -23,14 +24,6 @@ if TYPE_CHECKING: class UserConfigDialog(ModalScreen): """用户配置对话框""" - BINDINGS: ClassVar[list[tuple[str, str, str]]] = [ - ("up", "previous_model", _("上一个模型")), - ("down", "next_model", _("下一个模型")), - ("space", "activate_model", _("激活模型")), - ("enter", "save_llm_settings", _("保存大模型设置")), - ("escape", "cancel", _("取消")), - ] - def __init__( self, config_manager: ConfigManager, @@ -213,7 +206,7 @@ class UserConfigDialog(ModalScreen): tab_pane.remove_children() # 创建内容容器 - content_container = Container(id=f"{tab_id}-content", classes="llm-tab-content") + content_container = Container(classes="llm-tab-content") tab_pane.mount(content_container) if not self.models_loaded: @@ -227,7 +220,7 @@ class UserConfigDialog(ModalScreen): else: # 渲染模型列表 - model_list_container = Container(id=f"{tab_id}-list", classes="llm-model-list") + model_list_container = Container(classes="llm-model-list") # 将容器挂载到父容器 content_container.mount(model_list_container) @@ -246,7 +239,7 @@ class UserConfigDialog(ModalScreen): if is_cursor: classes += " llm-model-cursor" - model_item = Static(model.model_name, classes=classes) + model_item = Static(model.llm_id or "", classes=classes) model_list_container.mount(model_item) # 显示当前光标所指模型的详细信息 @@ -260,7 +253,7 @@ class UserConfigDialog(ModalScreen): tab_pane.mount( Static( _("↑↓: 选择模型 空格: 激活 回车: 保存 ESC: 取消"), - id="llm-dialog-help", + classes="llm-dialog-help", ), ) @@ -313,13 +306,41 @@ class UserConfigDialog(ModalScreen): self.chat_cursor = i break + def on_key(self, event: Key) -> None: + """处理按键事件""" + # ESC 键在任何情况下都可以取消并关闭对话框 + if event.key == "escape": + self.action_cancel() + event.prevent_default() + event.stop() + return + + # 以下按键仅在大模型设置标签页且有可用模型时生效 + if self.current_tab == "llm" and self.chat_models: + if event.key == "up": + self.action_previous_model() + event.prevent_default() + event.stop() + elif event.key == "down": + self.action_next_model() + event.prevent_default() + event.stop() + elif event.key == "space": + self.action_activate_model() + event.prevent_default() + event.stop() + elif event.key == "enter": + self.action_save_llm_settings() + event.prevent_default() + event.stop() + @on(TabbedContent.TabActivated) def on_tab_changed(self, event: TabbedContent.TabActivated) -> None: """标签页切换事件""" - tab_id = event.tab.id - if tab_id == "general-tab": + pane_id = event.pane.id if event.pane else None + if pane_id == "general-tab": self.current_tab = "general" - elif tab_id == "llm-tab": + elif pane_id == "llm-tab": self.current_tab = "llm" @on(Button.Pressed, "#save-user-settings-btn") @@ -353,47 +374,23 @@ class UserConfigDialog(ModalScreen): mcp_btn.label = _("自动执行") if self.auto_execute_status else _("手动确认") def action_previous_model(self) -> None: - """选择上一个模型(仅在大模型设置标签页生效)""" - if not self.models_loaded or self.current_tab != "llm": - return - - if not self.chat_models: - return - + """选择上一个模型""" self.chat_cursor = max(0, self.chat_cursor - 1) self._render_chat_llm_tab() def action_next_model(self) -> None: - """选择下一个模型(仅在大模型设置标签页生效)""" - if not self.models_loaded or self.current_tab != "llm": - return - - if not self.chat_models: - return - + """选择下一个模型""" self.chat_cursor = min(len(self.chat_models) - 1, self.chat_cursor + 1) self._render_chat_llm_tab() def action_activate_model(self) -> None: """激活当前光标所在的模型(用空格键),等待保存""" - if not self.models_loaded or self.current_tab != "llm": - return - - if not self.chat_models: - return - if 0 <= self.chat_cursor < len(self.chat_models): self.activated_chat_model = self.chat_models[self.chat_cursor].llm_id or "" self._render_chat_llm_tab() def action_save_llm_settings(self) -> None: """保存大模型设置(用回车键)""" - if not self.models_loaded or self.current_tab != "llm": - return - - if not self.chat_models: - return - # 保存已激活的模型到配置 self.config_manager.set_llm_chat_model(self.activated_chat_model) self.saved_chat_model = self.activated_chat_model diff --git a/src/backend/hermes/services/model.py b/src/backend/hermes/services/model.py index 2133360..62d79cd 100644 --- a/src/backend/hermes/services/model.py +++ b/src/backend/hermes/services/model.py @@ -98,9 +98,8 @@ class HermesModelManager: if not isinstance(llm_info, dict): continue - # modelName 是前端显示所必需的字段 - model_name = llm_info.get("modelName") - if not model_name: + llm_id = llm_info.get("llmId") + if not llm_id: continue # 解析并验证 llmType 字段 @@ -108,8 +107,8 @@ class HermesModelManager: # 构建 ModelInfo 对象 model_info = ModelInfo( - model_name=model_name, - llm_id=llm_info.get("llmId"), + model_name=llm_info.get("modelName") or llm_id, + llm_id=llm_id, llm_description=llm_info.get("llmDescription"), llm_type=llm_types, max_tokens=llm_info.get("maxTokens"), diff --git a/src/backend/models.py b/src/backend/models.py index 8799269..f8c8eb0 100644 --- a/src/backend/models.py +++ b/src/backend/models.py @@ -35,16 +35,19 @@ class ModelInfo: 模型信息数据类 该类用于统一表示不同后端(OpenAI、Hermes)返回的模型信息。 - 所有后端都需要提供 modelName 字段,其他字段为可选。 + + 注意: + - model_name: 仅用于后端调用大模型 API 时使用,CLI 前端不需要关心 + - llm_id: CLI 前端使用的模型标识符,用于显示和配置保存 """ # 通用字段(所有后端都支持) model_name: str - """模型名称,用于标识和选择模型""" + """模型名称,仅用于后端调用大模型 API""" # Hermes 特有字段 llm_id: str | None = None - """LLM ID,Hermes 后端的模型唯一标识""" + """LLM ID,CLI 前端使用的模型唯一标识符(用于显示和配置)""" llm_description: str | None = None """LLM 描述,Hermes 后端的模型说明""" @@ -56,8 +59,8 @@ class ModelInfo: """模型支持的最大 token 数,Hermes 后端提供""" def __str__(self) -> str: - """返回模型的字符串表示""" - return self.model_name + """返回模型的字符串表示(优先使用 llm_id)""" + return self.llm_id or self.model_name def __repr__(self) -> str: """返回模型的详细表示""" -- Gitee