feat(enums): 接入 /meta/enums 权威枚举表,删掉客户端硬编码映射

客户端此前自带 platform / mood / mode 三张硬编码标签表,会与服务端漂移——
实测已经漂了:客户端把 `web` 显示成「Web」,权威表里是「网页」。

- data:MetaApi(GET /api/v1/meta/enums)+ MetaDto + MetaRepository,
  经 ApiClientFactory 创建以复用既有 token / 401 处理
- domain/enums/EnumCatalog.kt:标签查找器,**不含任何硬编码兜底表**;
  不在表内的取值一律原样返回(api-client §1.1 的未知值回落红线);
  platformLabelOrNull 供 deviceId 前缀这类「认识/不认识要走不同回落」的调用方用;
  空 id 或空 label 一律不入表(否则角标会显示空白,或查空串凭空借到别人的标签)
- core/prefs/EnumPreferences.kt:last-good 快照,baseUrl+username 指纹闸门,
  换服务器/账号即作废,登出清空
- EnumCatalogViewModel + LocalEnumCatalog:在 CyreneRoot 注入(与 LocalAppPrefs 同套路),
  聊天角标与管理页共读同一份
- 删除 platformLabels / personaModeLabels / moodLabels;MessageSource 与 AdminFormat
  改为查表,并保留各自原有的原样回落

为什么不留硬编码兜底表:没有登录就看不到任何消息,而权威表在登录前后即会拉取,
所以 last-good 快照几乎总是存在;留着硬编码表等于把「漂移」重新引进来,而且是无声的——
旧标签和新鲜标签长得一模一样。

不转换的:trace kind/status、memory scope、thinking.mode、WS session_busy 的 mode 集合——
它们都不在 /meta/enums 内,且原本就已按原样回落。

测试:115 全绿(88 → +27:MetaDtoRealPayload 8 / EnumCatalog 7 / EnumPrefs 3 /
MessageSource 8,AdminFormatTest 改写并 +1),其中 /meta/enums 与登录响应一样
用真实抓取的响应体做夹具。
This commit is contained in:
AskaEth committed 2026-10-04 13:19:34 +08:00
1 parent 9f054aad28
commit 09a3222b7b
17 files changed
+693 -48

No files matched your search

@@ -0,0 +1,16 @@
package top.yeij.cyrene.data.api
import top.yeij.cyrene.data.api.dto.EnumTableDto
import retrofit2.Response
import retrofit2.http.GET
/**
* 共享枚举权威表端点(api-client §1.1 / api-reference §3.7)。
* 任意已认证角色可读(与 `GET /sessions` 同级),无 token 回 `401`;
* 纯内存投影,没有其它错误码。按本工程惯例单独成接口。
*/
interface MetaApi {
@GET("api/v1/meta/enums")
suspend fun enums(): Response<ApiEnvelope<EnumTableDto>>
}
@@ -0,0 +1,29 @@
package top.yeij.cyrene.data.api.dto
import kotlinx.serialization.Serializable
/**
* `GET /api/v1/meta/enums` 表内一项(api-client §1.1)。
* [id] 是**唯一可比对**的值(契约 id 为小写、大小写敏感);[label] 只是显示默认值,
* 自带 i18n 的客户端可以覆盖。
*/
@Serializable
data class EnumItemDto(
val id: String = "",
val label: String = "",
)
/**
* `platform` / `mood` / `mode` 的共享枚举权威表——客户端**不必硬编码**即可拿到稳定映射。
*
* 字段全部带默认值:只下发部分表(或 `null`)时仍能解码,缺失的那一类按「表内无此项」
* 走原样回落,而不是让整次解码失败。
* [unknown] 是机器可读的未知值回落约定,当前恒为 `"display-raw"`(原样显示,见 §1.1 红线)。
*/
@Serializable
data class EnumTableDto(
val platforms: List<EnumItemDto> = emptyList(),
val moods: List<EnumItemDto> = emptyList(),
val modes: List<EnumItemDto> = emptyList(),
val unknown: String = "",
)
@@ -0,0 +1,22 @@
package top.yeij.cyrene.data.repository
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import top.yeij.cyrene.data.api.MetaApi
import top.yeij.cyrene.data.api.apiCall
import top.yeij.cyrene.data.api.dto.EnumTableDto
/**
* 枚举权威表取数层(api-client §1.1)。
* 仓库不持有持久化状态:调用方失败时保留上次快照(见 [top.yeij.cyrene.core.prefs.EnumPreferences])。
*/
class MetaRepository(
private val api: MetaApi,
private val dispatcher: CoroutineDispatcher = Dispatchers.IO,
) {
suspend fun enums(): Result<EnumTableDto> = withContext(dispatcher) {
apiCall { api.enums() }
}
}