diff --git a/app/src/main/kotlin/top/yeij/cyrene/app/AppContainer.kt b/app/src/main/kotlin/top/yeij/cyrene/app/AppContainer.kt index 73e694f..78d2b94 100644 --- a/app/src/main/kotlin/top/yeij/cyrene/app/AppContainer.kt +++ b/app/src/main/kotlin/top/yeij/cyrene/app/AppContainer.kt @@ -15,6 +15,7 @@ import top.yeij.cyrene.data.api.AdminApi import top.yeij.cyrene.data.api.ApiClientFactory import top.yeij.cyrene.data.api.ChatApi import top.yeij.cyrene.data.api.DocumentsApi +import top.yeij.cyrene.data.api.MediaApi import top.yeij.cyrene.data.api.MetaApi import top.yeij.cyrene.data.api.PersonaApi import top.yeij.cyrene.data.api.SessionsApi @@ -23,6 +24,7 @@ import top.yeij.cyrene.data.api.dto.SessionSummaryDto import top.yeij.cyrene.data.repository.AdminRepository import top.yeij.cyrene.data.repository.ChatRepository import top.yeij.cyrene.data.repository.DocumentRepository +import top.yeij.cyrene.data.repository.MediaRepository import top.yeij.cyrene.data.repository.MessageRepository import top.yeij.cyrene.data.repository.MetaRepository import top.yeij.cyrene.data.repository.PersonaRepository @@ -186,6 +188,34 @@ class AppContainer(context: Context) { val documentRepository: DocumentRepository get() = DocumentRepository(documentsApi) + private var cachedMediaApi: MediaApi? = null + private var cachedMediaBaseUrl: String? = null + + /** + * 语音副本端点(`GET /api/v1/media/{name}`,契约 §1.2 **U19**);与 [documentsApi] **同一套门**: + * `owner|admin` **必须 Bearer**(与图片读侧「名字即凭据」刻意相反 —— 语音是私密内容)。 + * + * 同样与 [chatApi] 同源(同一 baseUrl、同一 token/401 处理):`voice.url` 是**服务端相对路径**, + * 只有落在**同一台后端**上才取得到那份副本。 + */ + val mediaApi: MediaApi + get() { + val baseUrl = authManager.currentBaseUrl + val cached = cachedMediaApi + if (cached != null && cachedMediaBaseUrl == baseUrl) return cached + val created = ApiClientFactory.create( + baseUrl = baseUrl, + service = MediaApi::class.java, + tokenProvider = authManager::currentToken, + unauthorizedPolicy = authManager.unauthorizedPolicy, + ) + cachedMediaBaseUrl = baseUrl + cachedMediaApi = created + return created + } + + val mediaRepository: MediaRepository get() = MediaRepository(mediaApi) + private var cachedAdminApi: AdminApi? = null private var cachedAdminBaseUrl: String? = null diff --git a/app/src/main/kotlin/top/yeij/cyrene/data/api/MediaApi.kt b/app/src/main/kotlin/top/yeij/cyrene/data/api/MediaApi.kt new file mode 100644 index 0000000..cc14ad3 --- /dev/null +++ b/app/src/main/kotlin/top/yeij/cyrene/data/api/MediaApi.kt @@ -0,0 +1,41 @@ +package top.yeij.cyrene.data.api + +import okhttp3.ResponseBody +import retrofit2.Response +import retrofit2.http.GET +import retrofit2.http.Streaming +import retrofit2.http.Url + +/** + * 语音副本的字节读取(契约 **v0.79 已实现**,api-client.md §1.2 **U19** + * `GET /api/v1/media/{name}`)。 + * + * ## 鉴权:**必须** Bearer `owner|admin` + * 与 `GET /api/v1/documents/{name}` 同一套门,与图片读侧「128-bit 名即凭据」**刻意相反** + * ——语音是私密内容,那个 URL 不能直接贴进 `