Files

303 lines
11 KiB
Python

from typing import Any, Literal
from pydantic import BaseModel, Field
FieldKind = Literal["text", "password", "url", "select", "combobox", "toggle", "number", "status"]
class SettingOption(BaseModel):
value: str
label: str
help: str
class SettingField(BaseModel):
key: str
label: str
description: str
kind: FieldKind
required: bool = False
secret: bool = False
default: Any = None
placeholder: str = ""
options: list[SettingOption] = Field(default_factory=list)
generator: Literal["hex24", "hex32", "base64_32"] | None = None
advanced: bool = False
visible_when: dict[str, Any] | None = None
class SettingCategory(BaseModel):
id: str
label: str
description: str
test_targets: list[str] = Field(default_factory=list)
required_for_workflow: bool = False
fields: list[SettingField]
def option(value: str, label: str, help_text: str) -> SettingOption:
return SettingOption(value=value, label=label, help=help_text)
CATEGORIES = [
SettingCategory(
id="deployment",
label="基础运行",
description="由安装器自动管理,通常不需要手动填写。修改数据库密码需要重启服务。",
test_targets=["infrastructure"],
required_for_workflow=True,
fields=[
SettingField(
key="database_status",
label="PostgreSQL",
description="保存项目、版本、任务和结构化设计状态。密码由启动脚本自动生成。",
kind="status",
required=True,
),
SettingField(
key="redis_status",
label="Redis",
description="用于任务队列、缓存和并发锁。密码由启动脚本自动生成。",
kind="status",
required=True,
),
SettingField(
key="encryption_status",
label="配置加密",
description="模型 API Key 会加密保存;主密钥首次启动时自动生成,不显示在页面中。",
kind="status",
required=True,
),
],
),
SettingCategory(
id="storage",
label="文件存储",
description="保存原始户型、清洗图、3D 白模和效果图。你的 NAS MinIO 可以直接使用。",
test_targets=["storage"],
required_for_workflow=True,
fields=[
SettingField(
key="s3_endpoint",
label="MinIO 内部地址",
description="后端访问 NAS 的 S3 API 地址,不是 MinIO 控制台地址。",
kind="url",
required=True,
placeholder="http://192.168.200.36:9000",
),
SettingField(
key="s3_public_endpoint",
label="浏览器可访问地址",
description="上传和预览文件时使用;纯内网部署通常与内部地址相同。",
kind="url",
required=True,
placeholder="http://192.168.200.36:9000",
),
SettingField(
key="s3_access_key_id",
label="服务账号 Access Key",
description="建议在 MinIO 中单独创建服务账号,不要使用 Root 账号。",
kind="password",
required=True,
secret=True,
),
SettingField(
key="s3_secret_access_key",
label="服务账号 Secret Key",
description="仅加密保存于后端,不会返回到浏览器。",
kind="password",
required=True,
secret=True,
),
SettingField(
key="s3_bucket_inputs",
label="原始文件空间",
description="保存用户上传的 PDF、DXF 和图片;测试会提示是否缺失。",
kind="text",
required=True,
default="renovation-inputs",
),
SettingField(
key="s3_bucket_derived",
label="中间结果空间",
description="保存 SVG、控制图、蒙版和 GLB 白模。",
kind="text",
required=True,
default="renovation-derived",
),
SettingField(
key="s3_bucket_renders",
label="效果图空间",
description="保存方向图、效果图和局部修改版本。",
kind="text",
required=True,
default="renovation-renders",
),
SettingField(
key="s3_force_path_style",
label="MinIO 兼容模式",
description="MinIO 通常保持开启;AWS S3 可以关闭。",
kind="toggle",
default=True,
advanced=True,
),
],
),
SettingCategory(
id="document",
label="图纸识别",
description="矢量 PDF 优先直接解析;只有扫描图或文字层损坏时才调用 OCR。",
test_targets=["baidu_ocr"],
fields=[
SettingField(
key="baidu_ocr_enabled",
label="启用百度 OCR",
description="开启后,扫描户型图会使用百度 OCR 识别尺寸和文字。",
kind="toggle",
default=False,
),
SettingField(
key="baidu_ocr_api_key",
label="百度 OCR API Key",
description="来自百度智能云 OCR 应用。",
kind="password",
required=True,
secret=True,
visible_when={"baidu_ocr_enabled": True},
),
SettingField(
key="baidu_ocr_secret_key",
label="百度 OCR Secret Key",
description="只用于换取访问令牌,不会返回前端。",
kind="password",
required=True,
secret=True,
visible_when={"baidu_ocr_enabled": True},
),
],
),
SettingCategory(
id="models",
label="模型池",
description="分别添加大语言模型与多模态模型。每个模型独立配置、独立测试,再用于总调度、空间理解或生图路由。",
required_for_workflow=True,
fields=[],
),
SettingCategory(
id="gpu",
label="本地算力",
description="GPU 主要用于户型分割、深度估计和本地视觉模型,不是调用云端生图 API 的必需项。",
test_targets=["gpu"],
fields=[
SettingField(
key="gpu_mode",
label="运行方式",
description="同机 Worker 会自动使用本机地址;只有独立 GPU 服务器才需要手填地址。",
kind="select",
default="disabled",
options=[
option("disabled", "暂不使用本地 GPU", "全部使用云端 API,最容易部署。"),
option("local", "本机 GPU Worker", "API 通过本机 Worker 调用显卡,页面无需填写地址。"),
option("remote", "局域网 GPU 服务器", "GPU 在另一台机器时填写内网地址。"),
],
),
SettingField(
key="gpu_service_url",
label="GPU 服务地址",
description="仅远程模式需要,例如 http://192.168.200.50:8100。",
kind="url",
required=True,
placeholder="http://192.168.200.50:8100",
visible_when={"gpu_mode": "remote"},
),
SettingField(
key="gpu_service_token",
label="内部访问令牌",
description="用于阻止其他内网设备随意调用 GPU;可以一键生成。",
kind="password",
required=True,
secret=True,
generator="hex32",
visible_when={"gpu_mode__not": "disabled"},
),
],
),
SettingCategory(
id="observability",
label="监控与追踪",
description="均为可选项。Langfuse 用于评估模型链路,Sentry 用于发现程序异常。",
test_targets=["langfuse", "sentry"],
fields=[
SettingField(
key="langfuse_enabled",
label="启用 Langfuse",
description="记录每次 Agent 调用、模型耗时、费用与评测结果。",
kind="toggle",
default=False,
),
SettingField(
key="langfuse_host",
label="Langfuse 地址",
description="支持自部署地址,例如 http://192.168.200.20:3001。",
kind="url",
required=True,
visible_when={"langfuse_enabled": True},
),
SettingField(
key="langfuse_public_key",
label="Langfuse Public Key",
description="从 Langfuse 项目设置中复制。",
kind="password",
required=True,
secret=True,
visible_when={"langfuse_enabled": True},
),
SettingField(
key="langfuse_secret_key",
label="Langfuse Secret Key",
description="只保存在后端。",
kind="password",
required=True,
secret=True,
visible_when={"langfuse_enabled": True},
),
SettingField(
key="sentry_enabled",
label="启用 Sentry",
description="收集异常和性能问题,支持自部署。",
kind="toggle",
default=False,
),
SettingField(
key="sentry_dsn",
label="Sentry DSN",
description="从 Sentry 项目的 Client Keys 页面复制。",
kind="password",
required=True,
secret=True,
visible_when={"sentry_enabled": True},
),
SettingField(
key="sentry_traces_sample_rate",
label="性能采样率",
description="0 表示关闭,0.1 表示记录约 10% 的请求。",
kind="select",
default="0",
visible_when={"sentry_enabled": True},
options=[
option("0", "仅错误,不采集性能", "最省资源,适合初期。"),
option("0.05", "采样 5%", "适合请求量较大的生产环境。"),
option("0.1", "采样 10%", "排障信息和资源消耗较均衡。"),
option("1", "全部采样", "仅建议短期排障使用。"),
],
),
],
),
]
FIELDS = {field.key: field for category in CATEGORIES for field in category.fields}
SECRET_FIELDS = {key for key, field in FIELDS.items() if field.secret}
EDITABLE_FIELDS = {key for key, field in FIELDS.items() if field.kind != "status"}