From 278a084c23fa0461ab86bab73fad9ee525337592 Mon Sep 17 00:00:00 2001 From: DrSmoothl <1787882683@qq.com> Date: Tue, 17 Feb 2026 17:05:25 +0800 Subject: [PATCH] feat(webui): enhance ConfigSchemaGenerator with field_docs and UI metadata - Add AttrDocBase.get_class_field_docs() classmethod for class-level field docs extraction - Merge json_schema_extra (x-widget, x-icon, step) into schema output - Map Pydantic constraints (ge/le) to minValue/maxValue for frontend compatibility - Add ge=0, le=1 constraints to ChatConfig.talk_value for validation Completes Task 1 (including subtasks 1a, 1b, 1c, 1d) of webui-config-visualization-refactor plan. --- src/config/config_base.py | 14 ++++++++++++-- src/config/official_configs.py | 2 ++ src/webui/config_schema.py | 12 ++++++++++++ 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/config/config_base.py b/src/config/config_base.py index 7baf1bd6..5e2d1827 100644 --- a/src/config/config_base.py +++ b/src/config/config_base.py @@ -5,7 +5,7 @@ import types from dataclasses import dataclass, field from pathlib import Path from pydantic import BaseModel, ConfigDict, Field -from typing import Union, get_args, get_origin, Tuple, Any, List, Dict, Set, Literal +from typing import Any, Dict, List, Literal, Set, Tuple, Union, cast, get_args, get_origin __all__ = ["ConfigBase", "Field", "AttributeData"] @@ -44,6 +44,16 @@ class AttrDocBase: # 从类定义节点中提取字段文档 return self._extract_field_docs(class_node, allow_extra_methods) + @classmethod + def get_class_field_docs(cls) -> dict[str, str]: + class_source = cls._get_class_source() + class_node = cls._find_class_node(class_source) + return AttrDocBase._extract_field_docs( + cast(AttrDocBase, cast(Any, cls)), + class_node, + allow_extra_methods=False, + ) + @classmethod def _get_class_source(cls) -> str: """获取类定义所在文件的完整源代码""" @@ -265,7 +275,7 @@ class ConfigBase(BaseModel, AttrDocBase): if origin_type in (int, float, str, bool, complex, bytes, Any): continue # 允许嵌套的ConfigBase自定义类 - if inspect.isclass(origin_type) and issubclass(origin_type, ConfigBase): # type: ignore + if isinstance(origin_type, type) and issubclass(cast(type, origin_type), ConfigBase): continue # 只允许 list, set, dict 三类泛型 if origin_type not in (list, set, dict, List, Set, Dict, Literal): diff --git a/src/config/official_configs.py b/src/config/official_configs.py index 3d9468c9..868ae4c2 100644 --- a/src/config/official_configs.py +++ b/src/config/official_configs.py @@ -104,6 +104,8 @@ class ChatConfig(ConfigBase): talk_value: float = Field( default=1, + ge=0, + le=1, json_schema_extra={ "x-widget": "slider", "x-icon": "message-circle", diff --git a/src/webui/config_schema.py b/src/webui/config_schema.py index 58f22876..711b18a8 100644 --- a/src/webui/config_schema.py +++ b/src/webui/config_schema.py @@ -77,6 +77,18 @@ class ConfigSchemaGenerator: if options: schema["options"] = options + # Task 1c: Merge json_schema_extra (x-widget, x-icon, step, etc.) + if hasattr(field_info, "json_schema_extra") and field_info.json_schema_extra: + schema.update(field_info.json_schema_extra) + + # Task 1d: Map Pydantic constraints to minValue/maxValue (frontend naming convention) + if hasattr(field_info, "metadata") and field_info.metadata: + for constraint in field_info.metadata: + if hasattr(constraint, "ge"): + schema["minValue"] = constraint.ge + if hasattr(constraint, "le"): + schema["maxValue"] = constraint.le + return schema @staticmethod