多用户系统 + Admin 角色权限 + Web/TUI 同步 + 构建集成

本轮实现功能:
1. 多用户隔离:每个用户独立 config.json + graph.db
2. TUI 登录页 + 旧版自动迁移(core/migrate.py)
3. Admin/User 角色体系(core/embedded_db.py)
4. Web 后台管理 API(userinfo + admin CRUD)
5. Web 设置页用户管理区(仅 admin 可见)
6. TUI 侧栏配置区权限同步(非 admin 隐藏 Web 服务设置)
7. Web 服务打包为独立二进制(trulymem-web)
8. 双入口 PyInstaller 构建脚本(TUI + Web)
9. 活动记录器(core/activity_recorder.py)
10. 静态页面模板(登录/设置/首次引导)
This commit is contained in:
root
2026-04-28 10:37:57 +08:00
parent 46008c14c5
commit b4456a9c5b
22 changed files with 5488 additions and 183 deletions

View File

@ -15,9 +15,10 @@ class ConfigSection(Vertical):
self.is_tool_limits = is_tool_limits
super().__init__()
def __init__(self, config: AppConfig | None = None, **kwargs):
def __init__(self, config: AppConfig | None = None, is_admin: bool = True, **kwargs):
super().__init__(**kwargs)
self._config = config or AppConfig()
self._is_admin = is_admin
def compose(self) -> ComposeResult:
title = Static("━━ 配置 ━━", classes="config-title")
@ -83,26 +84,104 @@ class ConfigSection(Vertical):
yield l6
yield Input(value=str(self._config.memory_update_max), placeholder="10", id="memory-update-max")
hint = Static("按Enter保存配置", classes="config-hint")
hint.can_focus = False
yield hint
sep2 = Static("", classes="config-sep")
sep2.can_focus = False
yield sep2
from textual.containers import Vertical
# Web 登录 — 仅 admin 可见
with Vertical(id="admin-web-login-section"):
web_title = Static("━━ Web 登录 ━━", classes="config-title")
web_title.can_focus = False
yield web_title
label_web_user = Static("用户名:", classes="config-label")
label_web_user.can_focus = False
yield label_web_user
yield Input(
value=self._config.web_username,
placeholder="admin",
id="web-username-input"
)
label_web_pwd = Static("密码:", classes="config-label")
label_web_pwd.can_focus = False
yield label_web_pwd
yield Input(
value=self._config.web_password,
placeholder="修改密码",
id="web-password-input",
password=True
)
hint = Static("按Enter保存配置", classes="config-hint")
hint.can_focus = False
yield hint
sep3 = Static("", classes="config-sep")
sep3.can_focus = False
yield sep3
# Web 服务 — 仅 admin 可见
with Vertical(id="admin-web-service-section"):
ws_title = Static("━━ Web 服务 ━━", classes="config-title")
ws_title.can_focus = False
yield ws_title
ws_hint = Static("在侧边栏启用后将自动启动 Web 管理界面", classes="config-hint")
ws_hint.can_focus = False
yield ws_hint
from textual.widgets import Checkbox
yield Checkbox(
"启用 Web 服务",
value=self._config.enable_web,
id="enable-web-checkbox"
)
label_web_port = Static("端口:", classes="config-label")
label_web_port.can_focus = False
yield label_web_port
yield Input(
value=str(self._config.web_port),
placeholder="4096",
id="web-port-input",
type="integer"
)
# 默认隐藏 admin 区域,等 login 后决定是否显示
self._apply_admin_visibility()
def on_mount(self) -> None:
try:
api_key = self.query_one("#api-key-input", Input)
model = self.query_one("#model-input", Input)
base_url = self.query_one("#base-url-input", Input)
api_key.tab_index = 0
model.tab_index = 1
base_url.tab_index = 2
if self._config.api_key:
api_key.value = self._config.api_key
if self._config.model:
model.value = self._config.model
if self._config.base_url:
base_url.value = self._config.base_url
web_user = self.query_one("#web-username-input", Input)
web_pwd = self.query_one("#web-password-input", Input)
web_user.tab_index = 7
web_pwd.tab_index = 8
from textual.widgets import Checkbox
web_port = self.query_one("#web-port-input", Input)
try:
web_checkbox = self.query_one("#enable-web-checkbox", Checkbox)
except:
pass
web_port.tab_index = 9
except Exception:
pass
@ -111,12 +190,19 @@ class ConfigSection(Vertical):
api_key_input = self.query_one("#api-key-input", Input)
model_input = self.query_one("#model-input", Input)
base_url_input = self.query_one("#base-url-input", Input)
persona_update = self.query_one("#persona-update-max", Input)
task_update = self.query_one("#task-update-max", Input)
memory_query = self.query_one("#memory-query-max", Input)
memory_update = self.query_one("#memory-update-max", Input)
web_username = self.query_one("#web-username-input", Input)
web_password = self.query_one("#web-password-input", Input)
from textual.widgets import Checkbox
web_checkbox = self.query_one("#enable-web-checkbox", Checkbox)
web_port_input = self.query_one("#web-port-input", Input)
self._config = AppConfig(
api_key=api_key_input.value,
model=model_input.value,
@ -125,6 +211,10 @@ class ConfigSection(Vertical):
task_update_max=int(task_update.value or 5),
memory_query_max=int(memory_query.value or 20),
memory_update_max=int(memory_update.value or 10),
web_username=web_username.value,
web_password=web_password.value,
enable_web=web_checkbox.value,
web_port=int(web_port_input.value) if web_port_input.value else 4096,
)
# 先发送 API 配置更新is_tool_limits=False
@ -134,6 +224,24 @@ class ConfigSection(Vertical):
except Exception:
pass
def set_admin(self, is_admin: bool) -> None:
"""设置是否 admin 模式,动态显示/隐藏 admin 区域"""
self._is_admin = is_admin
self._apply_admin_visibility()
def _apply_admin_visibility(self) -> None:
"""根据 _is_admin 显示/隐藏 admin 专用区域"""
try:
login_section = self.query_one("#admin-web-login-section")
login_section.styles.display = "block" if self._is_admin else "none"
except Exception:
pass
try:
service_section = self.query_one("#admin-web-service-section")
service_section.styles.display = "block" if self._is_admin else "none"
except Exception:
pass
def get_config(self) -> AppConfig:
return self._config
@ -147,10 +255,20 @@ class ConfigSection(Vertical):
api_key_input.value = config.api_key
model_input.value = config.model
base_url_input.value = config.base_url
self.query_one("#persona-update-max", Input).value = str(config.persona_update_max)
self.query_one("#task-update-max", Input).value = str(config.task_update_max)
self.query_one("#memory-query-max", Input).value = str(config.memory_query_max)
self.query_one("#memory-update-max", Input).value = str(config.memory_update_max)
self.query_one("#web-username-input", Input).value = config.web_username
self.query_one("#web-password-input", Input).value = config.web_password
from textual.widgets import Checkbox
try:
self.query_one("#enable-web-checkbox", Checkbox).value = config.enable_web
except:
pass
self.query_one("#web-port-input", Input).value = str(config.web_port)
except Exception:
pass