feat(openclaw): P1-6 config.schema RPC 写入校验 - 防小白改坏配置

OpenClaw 内核 config.set/patch 写入时已经会校验,但用户要等点保存才看到错误。
本 PR 把校验提前到前端,让用户改完字段立刻看到红字提示。

## 新增工具 src/lib/config-schema.js
- getFieldSchema(path) — 调内核 config.schema.lookup,带 5 分钟缓存
- validateField(path, value) — 拿 schema 后做基础约束校验
- validateFieldSync(schema, value, path) — 已有 schema 时同步校验
- clearSchemaCache(path?) — 清缓存(极少需要)

## 校验维度(不引入 ajv,保持 vanilla JS 体积)
- required — 必填空值检查
- type — string / number / integer / boolean / array / object / null
        (含「数字字符串当数字看」的容错)
- enum — 枚举白名单
- minimum / maximum — 数值范围
- pattern — 字符串正则

## 友好错误文案(i18n)
- common.error.schemaRequired / schemaType / schemaEnum / schemaMin / schemaMax / schemaPattern
- 6 个键 × 11 语言全覆盖
- 替换 schema 给的英文 path 后用户看到的是:
  「Gateway 端口不能小于 1024」「gateway.port 应该是「integer」类型」

## 集成示范:gateway.js
- saveConfig 开头先 validateField('gateway.port', port)
- 失败 → toast 红字 + focus 回 port 输入框 + early return
- 通过 → 走原流程
- 内核无该 schema 时降级放行(不阻断保存)

## 设计哲学
- 内核仍是最终守门人(config.set/patch 会再校验)
- 本模块的价值是「立即反馈」+「未来动态 UI 渲染」基础
- 容错优先:schema.lookup 失败时静默放行,不影响老内核兼容性

## 累计
- 1 新文件(config-schema.js)
- 2 修改(gateway.js / common.js)
- 6 个新 i18n 键 × 11 语言
- 后续可在 memory/dreaming/security/cron 等页面同样接入
This commit is contained in:
晴天
2026-05-14 04:30:23 +08:00
parent e717a7a098
commit c00b2dbf64
3 changed files with 200 additions and 0 deletions

View File

@@ -96,6 +96,13 @@ export default {
auth: _('请检查 API key/账号信息是否正确,或重新登录', 'Check that the API key / account info is correct, or sign in again', '請檢查 API key/帳號資訊是否正確,或重新登入', 'API キー/アカウント情報が正しいか確認するか、再度ログインしてください', 'API 키/계정 정보가 올바른지 확인하거나 다시 로그인하세요', 'Hãy kiểm tra API key/thông tin tài khoản, hoặc đăng nhập lại', 'Verifica que la API key / cuenta sea correcta, o vuelve a iniciar sesión', 'Verifique se a API key / conta está correta, ou faça login novamente', 'Проверьте API-ключ / данные учётной записи или войдите снова', 'Vérifiez que la clé API / les identifiants sont corrects ou reconnectez-vous', 'Prüfen Sie API-Key / Anmeldedaten oder melden Sie sich erneut an'),
rateLimit: _('已触发限流,请稍后重试,或在模型/渠道配置中放宽频率', 'Rate limit reached. Retry later, or raise the limit in the model/channel settings', '已觸發限流,請稍後重試,或在模型/頻道設定中放寬頻率', 'レート制限に達しました。後で再試行するか、モデル/チャンネル設定で上限を緩めてください', '속도 제한에 도달했습니다. 잠시 후 다시 시도하거나 모델/채널 설정에서 한도를 완화하세요', 'Đã đạt giới hạn tốc độ, thử lại sau hoặc nới lỏng giới hạn trong cài đặt mô hình/kênh', 'Se alcanzó el límite de tasa, reintenta luego o aumenta el límite en la configuración', 'Limite de taxa atingido, tente mais tarde ou aumente o limite nas configurações', 'Достигнут лимит запросов, повторите позже или увеличьте лимит в настройках', 'Limite de débit atteinte, réessayez plus tard ou augmentez la limite dans les paramètres', 'Ratenlimit erreicht, später erneut versuchen oder das Limit in den Einstellungen erhöhen'),
generic: _('请稍后重试;如问题持续,请到日志页查看详情', 'Try again later; if the issue persists, check the Logs page for details', '請稍後重試;如問題持續,請到日誌頁查看詳情', '後で再試行してください。問題が続く場合はログページで詳細を確認してください', '잠시 후 다시 시도하세요; 문제가 계속되면 로그 페이지에서 자세히 확인하세요', 'Vui lòng thử lại sau; nếu lỗi vẫn xảy ra, hãy kiểm tra trang Nhật ký', 'Reintenta más tarde; si persiste, revisa la página de Registros', 'Tente novamente mais tarde; se persistir, verifique a página de Logs', 'Повторите позже; если ошибка не уходит — посмотрите страницу Логи', 'Réessayez plus tard ; si le problème persiste, consultez la page Journaux', 'Versuchen Sie es später erneut; bei anhaltendem Problem die Protokoll-Seite prüfen'),
// schema 校验P1-6— 写入前的字段级即时校验
schemaRequired: _('{path} 是必填字段', '{path} is required', '{path} 是必填欄位', '{path} は必須項目です', '{path}은(는) 필수입니다', '{path} là bắt buộc', '{path} es obligatorio', '{path} é obrigatório', '{path} обязательно', '{path} est requis', '{path} ist erforderlich'),
schemaType: _('{path} 应该是「{expected}」类型,当前是「{actual}」', '{path} should be of type "{expected}", got "{actual}"', '{path} 應該是「{expected}」型別,目前是「{actual}」', '{path} は「{expected}」型である必要があります(現在は「{actual}」)', '{path}은(는) "{expected}" 타입이어야 합니다 (현재: {actual})', '{path} phải là kiểu "{expected}", hiện đang là "{actual}"', '{path} debe ser del tipo "{expected}", actual: "{actual}"', '{path} deve ser do tipo "{expected}", atual: "{actual}"', '{path} должно быть типа "{expected}", сейчас: "{actual}"', '{path} doit être de type « {expected} », actuel : « {actual} »', '{path} muss vom Typ „{expected}" sein, ist aktuell „{actual}"'),
schemaEnum: _('{path} 取值必须在 {allowed} 之中', '{path} must be one of: {allowed}', '{path} 取值必須在 {allowed} 之中', '{path} は {allowed} のいずれかである必要があります', '{path}은(는) 다음 중 하나여야 합니다: {allowed}', '{path} phải là một trong: {allowed}', '{path} debe ser uno de: {allowed}', '{path} deve ser um de: {allowed}', '{path} должно быть одним из: {allowed}', '{path} doit être l\'une de : {allowed}', '{path} muss eines von: {allowed} sein'),
schemaMin: _('{path} 不能小于 {min}', '{path} must be ≥ {min}', '{path} 不能小於 {min}', '{path} は {min} 以上である必要があります', '{path}은(는) {min} 이상이어야 합니다', '{path} không được nhỏ hơn {min}', '{path} no puede ser menor que {min}', '{path} não pode ser menor que {min}', '{path} не может быть меньше {min}', '{path} ne peut pas être inférieur à {min}', '{path} darf nicht kleiner als {min} sein'),
schemaMax: _('{path} 不能大于 {max}', '{path} must be ≤ {max}', '{path} 不能大於 {max}', '{path} は {max} 以下である必要があります', '{path}은(는) {max} 이하여야 합니다', '{path} không được lớn hơn {max}', '{path} no puede ser mayor que {max}', '{path} não pode ser maior que {max}', '{path} не может быть больше {max}', '{path} ne peut pas être supérieur à {max}', '{path} darf nicht größer als {max} sein'),
schemaPattern: _('{path} 格式不正确', '{path} format is invalid', '{path} 格式不正確', '{path} の形式が正しくありません', '{path} 형식이 올바르지 않습니다', '{path} sai định dạng', '{path} tiene formato inválido', '{path} com formato inválido', '{path} имеет неверный формат', '{path} a un format invalide', '{path}-Format ist ungültig'),
},
errorRawLabel: _('技术详情', 'Technical details', '技術詳情', '技術的詳細', '기술 정보', 'Chi tiết kỹ thuật', 'Detalles técnicos', 'Detalhes técnicos', 'Технические подробности', 'Détails techniques', 'Technische Details'),
// 智能行动按钮toast 副标题旁的快捷跳转)