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.
pull/1496/head
DrSmoothl 2026-02-17 17:05:25 +08:00
parent 19c9c5a39a
commit 278a084c23
No known key found for this signature in database
3 changed files with 26 additions and 2 deletions

View File

@ -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):

View File

@ -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",

View File

@ -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