Skip to content

移动端远程配置接入

本文面向前端项目,说明 App 如何读取 /public/client-config,以及如何使用端点、模型、转写模式和功能开关。后端管理入口是 /admin/app-settings,但客户端只读取公开接口,不直接访问后台接口。

拉取配置

推荐 App 启动后尽早拉取需要的公开分组:

http
GET /public/client-config?groups=endpoints,models,transcribe,flags,thresholds&platform=ios&appVersion=1.0.0

请求规则:

参数用途
groups逗号分隔的分组列表。移动端通常读取 endpoints,models,transcribe,flags,thresholds
platform客户端平台,例如 iosandroid;当前服务端仅记录语义,不按平台分流
appVersionApp 版本;当前服务端仅记录语义,不按版本分流

响应示例:

json
{
  "version": 1782480000000,
  "updatedAt": "2026-06-26T10:00:00.000Z",
  "groups": {
    "endpoints": {
      "apiBaseUrl": "https://block2-api.wainao.chat",
      "oauthBaseUrl": "https://block2-api.wainao.chat",
      "modelGatewayBaseUrl": "/api/model-gateway/v1",
      "modelManifestUrl": "https://example.com/mobile/models/manifest.json",
      "modelBaseUrl": "https://example.com/mobile/models"
    },
    "models": {
      "asr": {
        "lite": { "id": "sense-voice-small-int8", "enabled": true },
        "pro": { "id": "sense-voice-small-fp16", "enabled": true }
      },
      "refine": {
        "lite": { "id": "qwen3-0.6b-int4", "enabled": true },
        "pro": { "id": "qwen3-1.7b-int4", "enabled": true }
      },
      "cloud": {
        "asrModel": "sense-voice-small",
        "refineModel": "qwen-flash"
      }
    },
    "transcribe": {
      "defaultMode": "local_first",
      "allowedModes": ["local_first", "local_only"],
      "defaultRefineMode": "cloud_first",
      "allowLocalDownload": true
    },
    "flags": {
      "enableLocalAsr": true,
      "enableLocalRefine": true,
      "enableModelDownload": true,
      "enableTabAiFriends": true,
      "enableTabAiCircle": true,
      "enableTabMine": true,
      "enableTabSearch": true
    }
  }
}

客户端必须把 groups 当作部分配置处理:缺少某个分组或字段时,使用 App 内置默认值继续启动,不应因为远程配置为空阻塞首屏。

flags 可能携带历史安全字段。前端只应对本文列出的 boolean 功能开关做业务判断,未知字段保留但不要参与 Tab 或模型能力判断。

缓存策略

接口返回 ETagversionupdatedAt

  1. 首次启动使用内置默认值,并异步拉取远程配置。
  2. 拉取成功后持久化完整响应和 ETag
  3. 下次请求携带 If-None-Match
  4. 收到 304 Not Modified 时继续使用本地缓存。
  5. 收到新配置时以远程配置覆盖对应字段,未知字段保留但不参与业务判断。

远程配置失败时,前端应使用最近一次成功缓存;没有缓存时使用内置默认值。

端点使用

endpoints 只描述公开可用地址,不携带任何密钥。

字段前端使用方式
apiBaseUrl普通后端 API base URL;origin 由后端按请求域名动态推导,见下节
oauthBaseUrlOAuth 登录和 token 交换 base URL;为空时可回退到 apiBaseUrl;origin 同样按请求域名动态推导
modelGatewayBaseUrlOpenAI-compatible 模型网关路径,默认 /api/model-gateway/v1;若配成绝对 URL,origin 同样按请求域名动态推导
modelManifestUrl模型清单稳定 URL,优先使用;强制 https://,不受请求域名影响
modelBaseUrl模型下载 base URL,用于 manifest 内相对路径补全或无 manifest 时兜底;强制 https://,不受请求域名影响
modelDownloadBaseUrl旧字段兼容别名,新客户端优先读取 modelBaseUrl强制 https://,不受请求域名影响

modelManifestUrl 应是稳定 URL。不要把带短期签名 token 的 OSS URL 填到这里,否则客户端缓存和后端刷新都不可控。私有模型文件需要签名时,推荐 manifest 本身指向后端稳定接口,由后端在 manifest 或下载接口里生成短期下载 URL。

modelBaseUrl 可以为空。为空表示客户端不能靠 base URL 拼出模型文件地址,只能使用 modelManifestUrl 或 manifest 中的完整 url 字段。

apiBaseUrl / oauthBaseUrl / modelGatewayBaseUrl 按请求域名动态推导(issue #861 Phase 2b)

ReAI(block2-api.wainao.chat)和大陆 CloudOS(block-api.cloudos.com)两个节点共用同一个 selfhosted 数据库。为了避免两个域名的客户端都拿到同一个硬编码域名,GET /public/client-config 会按发起请求的 Host / X-Forwarded-Host / X-Forwarded-Proto 推导出请求方自己的 origin,只替换这三个字段的 origin 部分,路径后缀原样保留(例如后台配的是 .../api/model-gateway/v1,改写后还是 .../api/model-gateway/v1,只是域名换成了发请求那台节点自己的域名)。

前端不需要为这一行为做任何适配——响应里拿到的就是当前所连节点自己的真实域名。响应头会带 Vary: Host, X-Forwarded-Host, X-Forwarded-Proto

modelManifestUrl / modelBaseUrl / modelDownloadBaseUrl 强制 HTTPS

这三个字段必须是 https://(或空/站内相对路径),不允许明文 http:// 或内嵌用户名密码——iOS App Transport Security(ATS)默认会拒绝明文 HTTP 请求。管理后台写入非法值会被拒绝;公开接口读取到历史脏数据时会跳过整个 endpoints 分组(不是只跳过单个字段),前端应按「远程配置可能整体缺失」的既有容错逻辑处理,不需要额外适配。

已知限制modelManifestUrl 指向的 manifest JSON 文件本身由 ASR 本地模型上架流程在仓库之外单独生成上传,不受这里描述的校验覆盖。截至 2026-07-01,manifest 内部各模型文件的 url 仍可能是 http://,App 实际下载模型文件时仍可能被 ATS 拦截,需要另外专项修正。

模型清单

客户端优先从 modelManifestUrl 获取模型文件清单。建议 manifest 至少包含模型 ID、版本、文件 URL、大小和校验和:

json
{
  "version": "2026-06-27",
  "models": [
    {
      "id": "sense-voice-small-int8",
      "type": "asr",
      "tier": "lite",
      "version": "1.0.0",
      "url": "sense-voice-small-int8/model.bin",
      "sizeBytes": 123456789,
      "sha256": "..."
    }
  ]
}

下载规则:

  1. 先检查 flags.enableModelDownloadtranscribe.allowLocalDownload
  2. 再检查对应模型档位的 enabled 是否为 true
  3. 优先使用 manifest 中的完整 url
  4. 如果 url 是相对路径,用 modelBaseUrl 补全。
  5. 下载后必须校验 sizeBytessha256,校验失败不得启用模型。

客户端不能从远程配置中读取 OSS AccessKey、Secret、STS token、Bearer token 或任何私有凭据。

模型与转写模式

models.asrmodels.refine 描述本地模型档位:

字段说明
asr.lite.id本地轻量 ASR 模型 ID
asr.pro.id本地高质量 ASR 模型 ID
refine.lite.id本地轻量润色模型 ID
refine.pro.id本地高质量润色模型 ID
*.enabled是否允许客户端展示并使用该档位

models.cloud 描述云端模型:

字段说明
cloud.asrModel云端 ASR 默认模型 ID,用于 /services/passthrough/asr/transcriptions 未显式传 model 时;2026-07-03 起生产库该字段已指向真实上架模型 cloudos-sensevoice-small-asr(CloudOS Audio API,接入记录见仓库根 docs/features/ai-agent/providers/cloudos_audio_asr.md,未纳入 docs-site)
cloud.refineModel云端润色默认模型 ID,用于 /services/passthrough/text-refine 未显式传 model

转写模式由服务端策略和客户端运行时能力共同决定:

服务端字段含义
transcribe.allowedModes服务端允许展示的模式集合
transcribe.defaultMode服务端建议默认模式
transcribe.defaultRefineMode服务端建议默认润色模式
transcribe.allowLocalDownload是否允许端上下载本地模型

客户端最终可用模式应取交集:服务端允许、功能开关打开、模型 ID 已配置、模型文件可用或可下载、当前设备满足运行条件。服务端勾选“允许模式”只是产品策略,不代表端上一定可运行。

参考逻辑:

ts
const flags = config.groups.flags ?? {}
const transcribe = config.groups.transcribe ?? {}
const models = config.groups.models ?? {}

const allowedModes = new Set(transcribe.allowedModes ?? ['cloud_only'])
const canUseCloudAsr = Boolean(models.cloud?.asrModel) && oauthTokenReady
const canUseLocalAsr =
  flags.enableLocalAsr === true &&
  hasEnabledLocalAsrModel(models) &&
  (localAsrReady || (flags.enableModelDownload === true && transcribe.allowLocalDownload === true))

const usableModes = [
  allowedModes.has('cloud_only') && canUseCloudAsr ? 'cloud_only' : null,
  allowedModes.has('cloud_first') && canUseCloudAsr ? 'cloud_first' : null,
  allowedModes.has('local_first') && canUseLocalAsr ? 'local_first' : null,
  allowedModes.has('local_only') && canUseLocalAsr ? 'local_only' : null,
].filter(Boolean)

Tab 功能开关

后台 App 设置可以控制移动端 Tab 是否展示或启用:

字段对应功能
enableTabAiFriendsAI 好友
enableTabAiCircleAI 圈
enableTabMine我的
enableTabSearch搜索

推荐前端行为:

  1. 配置为 true 时展示并允许进入。
  2. 配置为 false 时隐藏或置灰,具体交互由产品决定。
  3. 如果当前选中的 Tab 被关闭,切到第一个仍可用的 Tab。
  4. 如果所有业务 Tab 都关闭,保留一个安全兜底页,避免底部导航为空。

与模型网关的关系

官方移动端不内置模型网关 API Key。正确链路是:

  1. App 通过 OAuth PKCE 获取 wno- access token。
  2. App 使用 Authorization: Bearer wno-... 调用 /services/passthrough/* 业务包装接口。
  3. 后端校验 mobile:full scope,并在服务端映射隐藏的系统 API Key。

云端 ASR 使用 /services/passthrough/asr/transcriptions,云端润色使用 /services/passthrough/text-refine。具体接口见 服务目录查询 API

modelGatewayBaseUrl 仍保留给 OpenAI-compatible 兼容链路;移动端业务流程优先使用包装接口,以获得稳定的业务响应结构、默认模型选择和服务端限额。

兼容字段

后台会继续写入部分旧字段,方便旧客户端过渡:

旧字段新字段
endpoints.modelDownloadBaseUrlendpoints.modelBaseUrl
models.local.asrLite.modelIdmodels.asr.lite.id
models.local.asrPro.modelIdmodels.asr.pro.id
models.local.refineLite.modelIdmodels.refine.lite.id
models.local.refinePro.modelIdmodels.refine.pro.id

新客户端应优先读取新字段;如果新字段为空,再读取旧字段作为兼容兜底。

AI Workflow Editor