feat: reorganize settings system configuration (#8777)

* feat: reorganize settings system configuration

* fix: float system config restart notice

* fix: blur system config restart notice

* fix: tint restart notice blur background

* fix: allow totp setup without body

* fix: filter public openapi docs

* fix: handle settings autosave cleanup

* chore: ui
This commit is contained in:
Weilong Liao
2026-06-16 11:03:42 +08:00
committed by GitHub
parent dd46cce09e
commit 5566bd621c
36 changed files with 1869 additions and 4351 deletions

View File

@@ -11,6 +11,29 @@ import yaml
REPO_ROOT = Path(__file__).resolve().parents[2]
DEFAULT_SPEC = REPO_ROOT / "openspec" / "openapi-v1.yaml"
DEFAULT_OUTPUT = REPO_ROOT / "docs" / "public" / "openapi.json"
PUBLIC_OPEN_API_TAGS = {
"System Config",
"Config Profiles",
"Bot Config Routes",
"Bots",
"Provider Sources",
"Providers",
"Chat",
"IM",
"Plugins",
"Plugin Sources",
"Plugin Pages",
"MCP",
"Skills",
"Personas",
"T2I",
"Subagents",
}
PUBLIC_OPEN_API_EXCLUDED_PATHS = {
"/api/v1/live-chat/ws",
"/api/v1/unified-chat/ws",
}
COMPONENT_REF_PREFIX = "#/components/"
def parse_args() -> argparse.Namespace:
@@ -39,12 +62,111 @@ def load_yaml(path: Path) -> dict[str, Any]:
return data
def iter_refs(value: Any):
"""Yield local component refs from an OpenAPI value.
Args:
value: Arbitrary OpenAPI object value.
Yields:
Local component reference strings.
"""
if isinstance(value, dict):
ref = value.get("$ref")
if isinstance(ref, str) and ref.startswith(COMPONENT_REF_PREFIX):
yield ref
for child in value.values():
yield from iter_refs(child)
elif isinstance(value, list):
for child in value:
yield from iter_refs(child)
def parse_component_ref(ref: str) -> tuple[str, str] | None:
"""Parse a local component ref into its section and name.
Args:
ref: OpenAPI local component reference.
Returns:
The component section and name, or None if the ref is not a component ref.
"""
if not ref.startswith(COMPONENT_REF_PREFIX):
return None
rest = ref.removeprefix(COMPONENT_REF_PREFIX)
if "/" not in rest:
return None
section, name = rest.split("/", 1)
return section, name
def filter_public_openapi(spec: dict[str, Any]) -> dict[str, Any]:
"""Filter the full v1 spec down to developer API key endpoints.
Args:
spec: Full OpenAPI spec loaded from the YAML source.
Returns:
A filtered OpenAPI spec for the public docs site.
"""
output = dict(spec)
output["tags"] = [
tag
for tag in spec.get("tags", [])
if isinstance(tag, dict) and tag.get("name") in PUBLIC_OPEN_API_TAGS
]
paths = {}
for path, methods in spec.get("paths", {}).items():
if path in PUBLIC_OPEN_API_EXCLUDED_PATHS:
continue
kept_methods = {
method: operation
for method, operation in methods.items()
if any(tag in PUBLIC_OPEN_API_TAGS for tag in operation.get("tags", []))
}
if kept_methods:
paths[path] = kept_methods
output["paths"] = paths
used_refs: dict[str, set[str]] = {}
pending = list(iter_refs(paths))
components = output.get("components", {})
while pending:
parsed = parse_component_ref(pending.pop())
if parsed is None:
continue
section, name = parsed
used_names = used_refs.setdefault(section, set())
if name in used_names:
continue
used_names.add(name)
component = components.get(section, {}).get(name)
pending.extend(iter_refs(component))
pruned_components = {}
for section, values in components.items():
if section == "securitySchemes":
pruned_components[section] = values
continue
if not isinstance(values, dict):
pruned_components[section] = values
continue
names = used_refs.get(section, set())
kept_values = {name: values[name] for name in values if name in names}
if kept_values:
pruned_components[section] = kept_values
output["components"] = pruned_components
return output
def main() -> int:
args = parse_args()
spec_path = args.spec.resolve()
output_path = args.output.resolve()
spec = load_yaml(spec_path)
spec = filter_public_openapi(spec)
output_path.parent.mkdir(parents=True, exist_ok=True)
output_path.write_text(
json.dumps(spec, ensure_ascii=False, indent=2) + "\n",