# 天工智能体 Android 客户端 — 架构上下文文档 **文档版本**: 2.0 | **生成日期**: 2026-06-27 | **分析源码文件数**: 14 --- ## 1. 技术栈全景 | 类别 | 技术 | 版本 | 证据来源 | |------|------|------|----------| | **语言** | Kotlin | 2.0.10 | `gradle/libs.versions.toml:3` | | **编译 SDK** | Android API | 34 | `app/build.gradle.kts:9` | | **最低 SDK** | Android API | 26 | `app/build.gradle.kts:13` | | **Java 目标** | JVM 17 | - | `app/build.gradle.kts:34-35` | | **UI 框架** | Jetpack Compose (BOM) | 2024.06.00 | `gradle/libs.versions.toml:4` | | **Material Design** | Material3 | BOM 托管 | `gradle/libs.versions.toml:26` | | **导航** | Navigation Compose | 2.7.7 | `gradle/libs.versions.toml:6` | | **DI** | Hilt (Dagger) | 2.51.1 | `gradle/libs.versions.toml:11` | | **网络层** | Retrofit + OkHttp | 2.11.0 / 4.12.0 | `gradle/libs.versions.toml:8-9` | | **JSON** | Gson | 2.11.0 | `gradle/libs.versions.toml:14` | | **SSE 流式** | OkHttp 底层流式读取 | - | `SseClient.kt:95-120` | | **本地数据库** | Room | 2.6.1 | `gradle/libs.versions.toml:10` | | **键值存储** | DataStore Preferences | 1.1.1 | `gradle/libs.versions.toml:10` | | **图片加载** | Coil | 2.6.0 | `gradle/libs.versions.toml:14` | | **音频播放** | Media3 ExoPlayer | 1.3.1 | `gradle/libs.versions.toml:14` | | **Markdown 渲染** | Markwon | 4.6.2 | `gradle/libs.versions.toml:15` | | **安全加密** | Security Crypto | 1.1.0-alpha06 | `gradle/libs.versions.toml:15` | | **启动画面** | SplashScreen API | 1.0.1 | `app/build.gradle.kts:75` | | **构建工具** | AGP | 8.5.2 | `gradle/libs.versions.toml:2` | | **架构模式** | MVVM + Repository + UDF (单向数据流) | - | 所有 ViewModel 均采用 `StateFlow` | --- ## 2. 目录结构与模块组织 ``` app/src/main/java/com/tiangong/aiagent/ ├── MainActivity.kt # 入口 Activity ├── TiangongApplication.kt # Hilt Application │ ├── navigation/ # 导航图 │ ├── di/ # Hilt DI 模块 │ ├── data/ │ ├── local/ │ │ ├── TokenDataStore.kt # DataStore 键值存储(Token/偏好) │ │ ├── CredentialStore.kt # 加密凭据存储 │ │ ├── AppDatabase.kt # Room 数据库定义 │ │ ├── ConversationEntity.kt # 对话表实体 │ │ ├── MessageEntity.kt # 消息表实体 │ │ ├── ConversationDao.kt # 对话 DAO │ │ └── MessageDao.kt # 消息 DAO │ ├── remote/ │ │ ├── ApiService.kt # Retrofit API 接口 │ │ ├── SseClient.kt # SSE 流式客户端(OkHttp) │ │ ├── AuthInterceptor.kt # Token 注入拦截器 │ │ └── dto/ # 请求/响应 DTO │ └── repository/ │ ├── AuthRepository.kt # 认证仓库 │ ├── ChatRepository.kt # 对话仓库 │ ├── AgentRepository.kt # 智能体仓库 │ └── FeedbackRepository.kt # 反馈仓库 │ ├── domain/ │ └── model/ │ ├── Agent.kt # 智能体模型 │ ├── Message.kt # 消息模型 + Role 枚举 │ ├── Conversation.kt # 对话模型 │ ├── SseEvent.kt # SSE 事件 sealed class │ └── TokenUsage.kt # Token 用量模型 │ ├── ui/ │ ├── login/ LoginScreen + LoginViewModel │ ├── register/ RegisterScreen + RegisterViewModel │ ├── chat/ │ │ ├── ChatScreen.kt # 主聊天界面 │ │ ├── ChatViewModel.kt # 聊天 ViewModel(核心) │ │ └── components/ # MessageBubble, StreamingText, ToolCallCard, VoiceInputButton │ ├── agents/ AgentListScreen + AgentListViewModel │ ├── history/ ConversationListScreen + ConversationListViewModel │ ├── settings/ SettingsScreen │ └── common/ SkeletonAgentList, SkeletonChat, SkeletonConversationList │ └── util/ ├── AudioPlayer.kt # TTS 音频播放器(Media3) └── AudioRecorder.kt # 语音录制器 ``` --- ## 3. 核心模块分析 ### 3.1 认证模块 (Auth) | 文件 | 职责 | |------|------| | `LoginViewModel.kt` | 处理登录表单状态,调用 `AuthRepository.login()` | | `RegisterViewModel.kt` | 客户端字段验证 + 调用 `AuthRepository.register()` | | `AuthRepository.kt` | 封装 API 调用,管理 Token 持久化和凭据存储 | | `ApiService.kt:15-19` | Retrofit `@FormUrlEncoded @POST login` / `@POST register` | | `TokenDataStore.kt` | DataStore 持久化 `access_token`、`server_url` 等 | | `CredentialStore.kt` | 加密存储用户名/密码 | | `AuthInterceptor.kt` | OkHttp 拦截器,自动注入 `Authorization: Bearer $token` | **认证流程**: ``` LoginScreen → LoginViewModel.login() → AuthRepository.login(username, password) → ApiService.login() → 返回 LoginResponse(accessToken) → tokenDataStore.saveToken() // 持久化 Token → credentialStore.saveCredentials() // 加密保存凭据 → LoginViewModel._uiState.copy(isLoggedIn = true) → LoginScreen LaunchedEffect 跳转主页 ``` **⚠️ 风险点**: - `LoginViewModel.kt:2-3` — 默认用户名 `"admin"`、密码 `"123456"` 硬编码在 `LoginUiState` 中,这是明显的安全漏洞,可能是测试遗留代码 - `AuthRepository.kt:22` — `login()` 的 `catch` 捕获所有异常不区分类型,网络异常和业务异常混杂 - `TokenDataStore.kt:86-88` — `clearAll()` 注释说 "Keep server URL and theme preferences across logouts",但实际执行的是 `prefs.clear()` 然后什么也不保留——**注释与实现矛盾** ### 3.2 聊天/对话模块 (Chat) — 核心模块 | 文件 | 行数(估) | 职责 | |------|----------|------| | `ChatViewModel.kt` | ~300+ | 管理聊天状态、SSE 流式消费、语音/图片、反馈 | | `ChatScreen.kt` | ~400+ | Compose UI:消息列表、输入框、语音按钮、反馈操作 | | `ChatRepository.kt` | 186 | 对话 API + Room 本地存储 | | `SseClient.kt` | 203 | SSE 长连接 + 指数退避自动重连 | **ChatUiState 状态字段** — `ChatViewModel.kt`: ``` messages: List # 当前对话消息列表 currentAgent: Agent? # 当前选中的智能体 availableAgents: List # 可用智能体列表 isLoading/isStreaming # 加载/流式状态 streamingContent: String # 流式接收中的文本 currentToolCall: Pair? # 进行中的工具调用 (name, input) sessionId: String? # 当前会话 ID isRecording/isTranscribing # 语音录制/识别状态 reconnectionState # SSE 重连状态(Idle/Reconnecting/Failed) feedbackSubmittingMsgId # 提交反馈中的消息 ID ``` **ChatViewModel 依赖注入** — `ChatViewModel.kt`(10 个依赖): ``` ChatRepository, AgentRepository, FeedbackRepository, AudioRecorder, AudioPlayer, ApiService, TokenDataStore, SavedStateHandle, Gson ``` **⚠️ 高依赖耦合**: ViewModel 直接依赖 `ApiService`(绕过 Repository 层),对于 `speakText()`(TTS)、`transcribeVoice()`、`uploadImage()` 这三个方法是直接在 ViewModel 中调用 `ApiService` + `TokenDataStore`,破坏了 Repository 模式的封装。 **SSE 流式数据路径**: ``` ChatScreen 输入文字 → ChatViewModel.sendMessage() → ChatRepository.chatStream(agentId, message, sessionId) → SseClient.connect(agentId, token, request) → OkHttp POST 到 /api/v1/agent-chat/{agentId}/stream → 通过 callbackFlow 逐行解析 "event:" / "data:" 行 → parseEvent() 转为 SseEvent sealed class: Plan / Think / ToolCall / ToolResult / Final / Message / Error / ConnectionError → 返回 Flow → ChatViewModel 收集 Flow: on Plan → 不处理 on Think → 不处理 on ToolCall → 更新 currentToolCall on ToolResult → 清除 currentToolCall on Final → 追加 AI 消息到 messages,停止 streaming on Message → 累积 streamingContent on ConnectionError → 更新 reconnectionState on Done → 停止 streaming on Error → 设置 error → 同时 ChatRepository.saveMessage() 写入 Room 数据库 ``` **SSE 重连策略** — `SseClient.kt:28-32`: ``` 初始延迟: 1s → 退避因子: 2.0 → 最大延迟: 30s → 最大重试: 3 次 401: 不重试(认证过期) 5xx/IOException: 触发退避重试 ``` ### 3.3 数据持久化层 **Room 数据库** — 包含两张表: - `ConversationEntity` — 字段: `sessionId, agentId, agentName, title, lastMessage, lastMessageAt, messageCount` - `MessageEntity` — 字段: `id, conversationId, agentId, role, content, toolName, toolInput, toolOutput, tokenUsageJson, createdAt` **DataStore (Preferences)** — `TokenDataStore.kt` — 8 个键: ``` access_token # JWT Token server_url # 服务器地址(默认 BuildConfig.BASE_URL = http://192.168.31.150:8037/) current_agent_id # 当前选中智能体 current_agent_name # 当前智能体名称 tts_enabled # TTS 开关 tts_voice # TTS 语音 (默认 "alloy") theme_mode # 主题模式 (默认 "system") last_session_id # 上次会话 ID(用于恢复) ``` **消息持久化时机** — `ChatRepository.kt`: - `chat()` (非流式): 调用 `saveMessagesFromResponse()` 批量保存 steps - `chatStream()` (流式): 不自动保存,由 ViewModel 在收到 `SseEvent.Final` 后调用 `saveMessage()` 逐条保存 - `saveMessage()` 同时 upsert `ConversationEntity` **⚠️ 风险**: 流式消息在 `SseEvent.Final` 之前如果 App 崩溃或网络断开,消息会丢失(未持久化到 Room) ### 3.4 UI 层 **骨架屏 (Skeleton Screens)**: - `SkeletonAgentList` — 智能体列表加载中(6 个占位项) - `SkeletonChat` — 聊天界面加载中 - `SkeletonConversationList` — 对话历史加载中(5 个占位项) **ChatScreen 组件树** — `ChatScreen.kt`: ``` Scaffold ├── TopAppBar (标题 = currentAgent.name, 操作按钮: 历史/通知/设置) ├── LazyColumn (消息列表, reversed) │ ├── SkeletonChat (首次加载时) │ ├── MessageBubble (逐条消息气泡) │ │ ├── Markdown 渲染正文 │ │ ├── ToolCallCard (工具调用卡片) │ │ └── 操作栏: 复制 / 朗读 / 点赞 / 点踩 │ ├── ToolCallCard (进行中的工具调用) │ └── StreamingText (流式输出文本) └── BottomBar ├── IconButton (图片上传) ├── OutlinedTextField (文字输入) └── VoiceInputButton (语音输入) ``` **⚠️ 注意**: 预扫描中 ChatScreen.kt 被截断(8KB 限制),部分实现细节未获取。`MessageBubble`, `StreamingText`, `ToolCallCard`, `VoiceInputButton` 的具体实现在 `ui/chat/components/` 目录下的独立文件中,未读取。 ### 3.5 反馈模块 `ChatViewModel.kt` 中: - `submitFeedback(msgId, rating)` → `FeedbackRepository` → `ApiService.submitFeedback()` - 支持 LIKE / DISLIKE 评分 + 可选评论 (`submitFeedbackComment`) - 状态通过 `feedbackSubmittingMsgId` 跟踪提交中状态 --- ## 4. 数据流路径汇总 ### 4.1 登录流程 ``` LoginScreen → onUsernameChange / onPasswordChange → LoginViewModel._uiState.update → Button onClick → LoginViewModel.login() → AuthRepository.login(username, password) → ApiService.login(@Field username, @Field password) → 返回 LoginResponse(accessToken) → TokenDataStore.saveToken(accessToken) → CredentialStore.saveCredentials(username, password) → _uiState.copy(isLoggedIn = true) → LaunchedEffect(isLoggedIn) → onLoginSuccess() 导航 ``` ### 4.2 注册流程 ``` RegisterScreen (5 个字段: username/displayName/email/password/confirmPassword) → 每个输入框 onValueChange → RegisterViewModel.onXxxChanged() → 实时客户端验证 → Button onClick → RegisterViewModel.register() → 再次验证所有字段 → AuthRepository.register(RegisterRequest) → ApiService.register(@Body) → 成功: isSuccess = true → HTTP 409: 解析 errorBody JSON 中的 detail 字段 → HTTP 422: 同上 → IOException: "网络连接失败" → RegisterScreen LaunchedEffect(isSuccess) → Snackbar + 跳转登录 ``` ### 4.3 SSE 流式对话 ``` ChatScreen 输入 "你好" → ChatViewModel.sendMessage("你好") → 生成用户 UiMessage,追加到 messages → ChatRepository.chatStream(agentId, "你好", sessionId) → TokenDataStore.getToken() → SseClient.connect(agentId, token, ChatRequest) → runBlocking { TokenDataStore.serverUrl.first() } // ⚠️ 阻塞 OkHttp 线程 → POST {baseUrl}api/v1/agent-chat/{agentId}/stream → callbackFlow 主循环: source.readUtf8Line() 逐行解析 SSE event: → 设置事件类型 data: → 设置数据 空行 → parseEvent(type, data) → trySend(event) 返回 Flow → ChatViewModel.collect: is ToolCall → currentToolCall = (name, input) is ToolResult → currentToolCall = null is Message → streamingContent += content is Final → 生成 AI UiMessage, isStreaming = false, 保存到 Room is ConnectionError → reconnectionState = Reconnecting(...) is Done → isStreaming = false is Error → error = ... → ChatScreen 自动滚动到最新消息 ``` ### 4.4 TTS 语音合成 ``` ChatScreen 点击朗读按钮 → ChatViewModel.speakText(text) → TokenDataStore.ttsEnabled.first() // 检查 TTS 开关 → TokenDataStore.ttsVoice.first() // 获取语音类型 → ApiService.synthesizeSpeech(TtsRequest) → 拼接 audioUrl (相对路径补全 BaseURL) → AudioPlayer.play(audioUrl) // Media3 ExoPlayer ``` ### 4.5 语音识别 (ASR) ``` VoiceInputButton 录制完成 → AudioRecorder 输出 File (AAC 格式) → ChatViewModel.transcribeVoice(file) → file.asRequestBody("audio/aac") → ApiService.transcribeAudio(MultipartBody.Part) → 返回 transcribedText → file.delete() // finally 中删除临时文件 ``` ### 4.6 图片上传 ``` ChatScreen 图片选择器 (ActivityResultContracts.GetContent) → ContentResolver 读取 Uri → 写入 cacheDir 临时文件 → ChatViewModel.uploadImage(tempFile) → file.asRequestBody("image/*") → ApiService.uploadFile(MultipartBody.Part) → 拼接预览 URL: {baseUrl}api/v1/uploads/preview/file?file_path={relativePath} → 设置 transcribedText = "[图片](url)" // Markdown 格式 → file.delete() ``` --- ## 5. 关键类的职责与依赖关系 ``` ┌───────────────────────┐ │ TiangongApplication │ (Hilt Entry Point) └───────────┬───────────┘ │ @HiltAndroidApp ┌───────────▼───────────┐ │ MainActivity │ (Single Activity) └───────────┬───────────┘ │ setContent { NavHost } ┌───────────────────────────┼───────────────────────────┐ │ │ │ LoginScreen ChatScreen AgentListScreen RegisterScreen ConversationListScreen SettingsScreen │ │ │ LoginViewModel ChatViewModel AgentListViewModel RegisterViewModel ConversationListVM │ │ │ AuthRepository ChatRepository ◄──┐ AgentRepository │ │ │ │ │ │ ┌────────┼────┐ │ │ │ │ │ │ │ │ │ │ ApiService SseClient AppDatabase │ │ ApiService │ │ │ │ │ │ TokenDataStore OkHttpClient │ │ │ CredentialStore Room DAOs │ │ │ │ │ MessageDao ConversationDao ``` **依赖注入关系说明**: - 所有 ViewModel 通过 `@HiltViewModel` + `@Inject constructor` 获取依赖 - 所有 Repository 通过 `@Singleton` + `@Inject constructor` 由 Hilt 管理 - `ApiService` 是 Retrofit 接口,由 Hilt Module 提供实例 - `SseClient` 直接注入 `OkHttpClient` + `Gson` + `TokenDataStore` - `TokenDataStore` 通过 `@ApplicationContext` 获取 Context 初始化 DataStore --- ## 6. 从源码中发现的实际代码质量问题 ### 🔴 严重问题 | ID | 问题 | 位置 | 影响 | |----|------|------|------| | **B1** | `runBlocking` 在 OkHttp 回调线程调用 | `SseClient.kt:66` — `val baseUrl = runBlocking { tokenDataStore.serverUrl.first() }` | **阻塞 OkHttp 网络线程**,可能导致 ANR 或连接超时。`runBlocking` 在 `callbackFlow {}` builder 内运行在 OkHttp 的分发线程上 | | **B2** | OkHttp Callback 中 `Thread.sleep()` | `SseClient.kt:90,108,140` — 重连等待使用 `Thread.sleep(delay)` | **阻塞 OkHttp 线程池**,多连接下可能耗尽 OkHttp dispatcher 线程 | | **B3** | 硬编码默认凭据 | `LoginViewModel.kt:3` — `val username: String = "admin"`, `val password: String = "123456"` | **安全漏洞**,发布到生产环境会让攻击者轻松猜测默认凭据 | | **B4** | `clearAll()` 实现与注释矛盾 | `TokenDataStore.kt:85-88` — 注释说保留 server_url 和 theme,但实际执行 `prefs.clear()` | 用户退出登录后可能丢失服务器地址配置 | ### 🟡 中等问题 | ID | 问题 | 位置 | 影响 | |----|------|------|------| | **M1** | ViewModel 绕过 Repository 直接调用 ApiService | `ChatViewModel.kt` — `speakText()`, `transcribeVoice()`, `uploadImage()` 方法内直接使用 `apiService` 和 `tokenDataStore` | 破坏架构分层,测试困难,无法 mock | | **M2** | 流式消息在 Final 事件前不持久化 | `ChatViewModel.kt` — 仅在 `SseEvent.Final` 后调用 `ChatRepository.saveMessage()` | App 崩溃或断网时,已流式接收的消息全部丢失 | | **M3** | 缺少 Token 过期自动刷新机制 | `SseClient.kt:95` — 401 直接 `close()`,`AuthRepository` 无 `refreshToken` 方法 | Token 过期后用户必须手动重新登录 | | **M4** | `parseEvent()` 对未知 type 默认创建 `SseEvent.Unknown` | `SseClient.kt:178-181` — `else → SseEvent.Unknown` | 服务端新增事件类型可能导致前端默默忽略而非报错 | | **M5** | ChatViewModel 依赖过多(10 个注入参数) | `ChatViewModel.kt` 构造函数 | 违反单一职责原则,建议拆分为 AudioHandler、ImageHandler 等 | ### 🟢 轻微问题 | ID | 问题 | 位置 | 影响 | |----|------|------|------| | **N1** | 搜索输入每次变化都触发网络请求 | `AgentListViewModel.kt:42-44` — `onSearchQueryChange` 直接调用 `loadAgents()` | 快速输入时产生大量冗余 API 请求,无防抖(debounce) | | **N2** | BaseURL 硬编码局域网 IP | `app/build.gradle.kts:23` — `http://192.168.31.150:8037/` | 只能在该局域网使用,无法切换环境 | | **N3** | 异常吞没 | `ChatViewModel.kt` — `speakText()` 中 `catch (_: Exception) { }` 完全忽略异常 | 用户点击朗读无反应时无任何错误提示 | | **N4** | `ConversationListViewModel.loadConversations()` 每次加载取消上一个 Job | `ConversationListScreen.kt:51` — `loadJob?.cancel()` | 频繁切换筛选时可能丢失中间状态 | | **N5** | Room role 字段使用 String 值枚举 | `ChatRepository.kt:147-151` — `Message.Role.valueOf(entity.role.uppercase())` 失败时 default=`SYSTEM` | 类型不安全,数据库中存在非预期值时静默降级 | | **N6** | 临时文件未统一管理 | ChatScreen/ChatViewModel — 图片和音频临时文件写入 `cacheDir`,依赖 `file.delete()` | 异常时可能产生孤儿文件 | | **N7** | ProGuard 代码混淆未启用 | `app/build.gradle.kts:27` — `isMinifyEnabled = false` | Release 构建无混淆保护 | | **N8** | RegisterScreen 密码明文传输 | `RegisterRequest` DTO — 密码字段未被加密 | 即使走 HTTPS,也应在客户端做一次哈希后再发送 | --- ## 7. 架构模式总结 ``` ┌─────────────────────────────────────────────────────┐ │ 架构模式: MVVM + UDF │ │ │ │ UI Layer (Compose) ViewModel Layer Data Layer│ │ ┌──────────┐ ┌──────────────┐ ┌─────────┐ │ │ │ Screen │──State─│ ViewModel │──▶│Repository│ │ │ │ │◀─Flow──│ (StateFlow) │◀──│ │ │ │ │ Events │───────▶│ │ │ ┌─────┐│ │ │ │ (click) │ │ viewModelScope│ │ │ApiSvc││ │ │ └──────────┘ └──────────────┘ │ ├─────┤│ │ │ │ │Room ││ │ │ │ ├─────┤│ │ │ │ │DataSt││ │ │ │ └─────┘│ │ │ └─────────┘ │ └─────────────────────────────────────────────────────┘ ``` **数据流向特点**: - **单向数据流 (UDF)**: UI → Events → ViewModel → Repository → DataSource → State → UI - **StateFlow 驱动**: 所有 ViewModel 通过 `MutableStateFlow` + `asStateFlow()` 暴露只读状态 - **viewModelScope 管理协程**: 所有异步操作绑定到 ViewModel 生命周期 - **Flow 用于响应式数据**: Room DAO 返回 `Flow>`,Repository 用 `map` 转换为 Domain Model - **callbackFlow 用于 SSE**: 将 OkHttp 回调风格的事件源桥接到 Kotlin Flow --- ## 8. 附录:分析引用的源文件清单 | # | 文件路径 | 读取状态 | 用途 | |---|----------|----------|------| | 1 | `app/build.gradle.kts` | 完整 | 构建配置、SDK 版本、依赖声明 | | 2 | `gradle/libs.versions.toml` | 完整 | 全量版本号目录 | | 3 | `ui/agents/AgentListViewModel.kt` | 完整 | 智能体列表 ViewModel | | 4 | `ui/chat/ChatViewModel.kt` | 截断(8KB) | 聊天核心 ViewModel,SSE 消费,TTS/ASR/图片 | | 5 | `ui/login/LoginViewModel.kt` | 完整 | 登录 ViewModel | | 6 | `ui/register/RegisterViewModel.kt` | 完整 | 注册 ViewModel + 客户端验证 | | 7 | `ui/agents/AgentListScreen.kt` | 完整 | 智能体列表 Compose UI | | 8 | `ui/chat/ChatScreen.kt` | 截断(8KB) | 聊天 Compose UI | | 9 | `ui/history/ConversationListScreen.kt` | 截断(8KB) | 对话历史 Compose UI + ViewModel | | 10 | `ui/login/LoginScreen.kt` | 完整 | 登录 Compose UI | | 11 | `ui/register/RegisterScreen.kt` | 截断(8KB) | 注册 Compose UI | | 12 | `data/repository/AuthRepository.kt` | 完整 | 认证仓库 | | 13 | `data/remote/ApiService.kt` | 完整 | Retrofit API 接口定义 | | 14 | `data/repository/ChatRepository.kt` | 完整 | 聊天仓库 + Room 持久化 | | 15 | `data/local/TokenDataStore.kt` | 完整 | DataStore 键值存储 | | 16 | `domain/model/Message.kt` | 完整 | 消息领域模型 | | 17 | `data/remote/SseClient.kt` | 完整 | SSE 流式客户端 | **未读取但推测存在的文件**: `AppDatabase.kt`, `ConversationEntity.kt`, `MessageEntity.kt`, `ConversationDao.kt`, `MessageDao.kt`, `CredentialStore.kt`, `AuthInterceptor.kt`, `AgentRepository.kt`, `FeedbackRepository.kt`, `Agent.kt`, `Conversation.kt`, `SseEvent.kt`, `MainActivity.kt`, `TiangongApplication.kt`, 各 DTO 文件, DI 模块, 导航图, 各 UI 组件 (`MessageBubble`, `StreamingText`, `ToolCallCard`, `VoiceInputButton`, `Skeleton*`)