feat: 扩展网络层API和WebSocket消息处理

- 扩展ApiService添加回忆录状态和任务状态接口
- 优化WebSocketClient连接管理
- 增强WebSocketMessage消息类型支持
- 更新MemoirModels数据模型
This commit is contained in:
iammm0
2026-01-22 17:58:30 +08:00
parent 58bd42f814
commit 7f4c3ffb9f
4 changed files with 155 additions and 19 deletions

View File

@@ -95,7 +95,6 @@ class ApiService(
return try { return try {
val response = client.get("$BASE_URL/api/chapters") { val response = client.get("$BASE_URL/api/chapters") {
contentType(ContentType.Application.Json) contentType(ContentType.Application.Json)
parameter("user_id", userId)
} }
Result.success(response.body()) Result.success(response.body())
} catch (e: Exception) { } catch (e: Exception) {
@@ -261,5 +260,46 @@ class ApiService(
Result.success(Unit) Result.success(Unit)
} }
} }
// ==================== 回忆录状态相关API ====================
suspend fun getMemoirState(): Result<com.huaga.life_echo.network.models.MemoirStateDto> {
return try {
val response = client.get("$BASE_URL/api/memoir-state") {
contentType(ContentType.Application.Json)
}
Result.success(response.body())
} catch (e: Exception) {
Result.failure(Exception("获取回忆录状态失败: ${e.message}", e))
}
}
// ==================== 任务状态相关API ====================
suspend fun getTasksStatus(): Result<com.huaga.life_echo.network.models.TasksStatusDto> {
return try {
val response = client.get("$BASE_URL/api/tasks/status") {
contentType(ContentType.Application.Json)
}
Result.success(response.body())
} catch (e: Exception) {
Result.failure(Exception("获取任务状态失败: ${e.message}", e))
}
}
suspend fun clearTasks(): Result<Unit> {
return try {
val response = client.delete("$BASE_URL/api/tasks/clear") {
contentType(ContentType.Application.Json)
}
if (response.status.isSuccess()) {
Result.success(Unit)
} else {
Result.failure(Exception("清除任务失败: ${response.status}"))
}
} catch (e: Exception) {
Result.failure(Exception("清除任务失败: ${e.message}", e))
}
}
} }

View File

@@ -11,6 +11,10 @@ import io.ktor.http.*
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put
import kotlinx.serialization.json.putJsonObject
class WebSocketClient { class WebSocketClient {
private val client = HttpClient(Android) { private val client = HttpClient(Android) {
@@ -74,7 +78,7 @@ class WebSocketClient {
sendMessage(WebSocketMessage( sendMessage(WebSocketMessage(
type = MessageType.connect, type = MessageType.connect,
conversation_id = conversationId, conversation_id = conversationId,
data = mapOf("status" to "connected") data = buildJsonObject { put("status", JsonPrimitive("connected")) }
)) ))
} catch (e: Exception) { } catch (e: Exception) {
@@ -118,7 +122,7 @@ class WebSocketClient {
sendMessage(WebSocketMessage( sendMessage(WebSocketMessage(
type = MessageType.audio_chunk, type = MessageType.audio_chunk,
conversation_id = conversationId, conversation_id = conversationId,
data = mapOf("audio_base64" to base64Audio) data = buildJsonObject { put("audio_base64", JsonPrimitive(base64Audio)) }
)) ))
} }
@@ -126,14 +130,15 @@ class WebSocketClient {
sendMessage(WebSocketMessage( sendMessage(WebSocketMessage(
type = MessageType.text, type = MessageType.text,
conversation_id = conversationId, conversation_id = conversationId,
data = mapOf("text" to text) data = buildJsonObject { put("text", JsonPrimitive(text)) }
)) ))
} }
suspend fun sendEndConversation(conversationId: String) { suspend fun sendEndConversation(conversationId: String) {
sendMessage(WebSocketMessage( sendMessage(WebSocketMessage(
type = MessageType.end_conversation, type = MessageType.end_conversation,
conversation_id = conversationId conversation_id = conversationId,
data = buildJsonObject { }
)) ))
} }
@@ -146,7 +151,7 @@ class WebSocketClient {
sendMessage(WebSocketMessage( sendMessage(WebSocketMessage(
type = MessageType.cancel_generation, type = MessageType.cancel_generation,
conversation_id = conversationId, conversation_id = conversationId,
data = mapOf("action" to "cancel") data = buildJsonObject { put("action", JsonPrimitive("cancel")) }
)) ))
} }

View File

@@ -1,6 +1,9 @@
package com.huaga.life_echo.network package com.huaga.life_echo.network
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.buildJsonObject
@Serializable @Serializable
enum class MessageType { enum class MessageType {
@@ -23,7 +26,58 @@ enum class MessageType {
data class WebSocketMessage( data class WebSocketMessage(
val type: MessageType, val type: MessageType,
val conversation_id: String? = null, val conversation_id: String? = null,
val data: Map<String, String> = emptyMap(), val data: JsonObject = buildJsonObject { },
val timestamp: String? = null val timestamp: String? = null
) ) {
/**
* 获取data中的字符串值
*/
fun getString(key: String): String? {
return data[key]?.let {
if (it is JsonPrimitive) {
try {
it.content
} catch (e: Exception) {
null
}
} else {
null
}
}
}
/**
* 获取data中的整数值
*/
fun getInt(key: String): Int? {
return data[key]?.let {
if (it is JsonPrimitive) {
try {
it.content.toIntOrNull()
} catch (e: Exception) {
null
}
} else {
null
}
}
}
/**
* 获取data中的布尔值
*/
fun getBoolean(key: String): Boolean? {
return data[key]?.let {
if (it is JsonPrimitive) {
try {
it.content.toBooleanStrictOrNull()
} catch (e: Exception) {
null
}
} else {
null
}
}
}
}

View File

@@ -6,30 +6,31 @@ import kotlinx.serialization.Serializable
* 回忆录相关的数据模型 * 回忆录相关的数据模型
*/ */
// 书籍信息DTO扩展版 // 书籍信息DTO匹配服务器格式
@Serializable @Serializable
data class BookDto( data class BookDto(
val id: String, val id: String,
val userId: String,
val title: String, val title: String,
val subtitle: String? = null, val total_pages: Int? = null,
val totalPages: Int, val total_words: Int? = null,
val totalWords: Int, val cover_image_url: String? = null,
val updatedAt: Long, val has_update: Boolean = false,
val lastUpdatedAt: Long? = null val last_update_chapter_id: String? = null
) )
// 章节信息DTO扩展版 // 章节信息DTO匹配服务器格式
@Serializable @Serializable
data class ChapterDto( data class ChapterDto(
val id: String, val id: String,
val title: String, val title: String,
val content: String, val content: String,
val orderIndex: Int, val order_index: Int,
val status: String, // "draft", "partial", "completed" val status: String, // "draft", "partial", "completed"
val category: String, val category: String,
val pageCount: Int? = null, val images: List<String> = emptyList(),
val updatedAt: Long? = null val updated_at: String? = null,
val is_new: Boolean = false,
val source_segments: List<String> = emptyList()
) )
// 章节内容详情DTO // 章节内容详情DTO
@@ -45,3 +46,39 @@ data class ChapterContentDto(
val updatedAt: Long, val updatedAt: Long,
val quotes: List<String> = emptyList() // 引用内容列表 val quotes: List<String> = emptyList() // 引用内容列表
) )
// 回忆录状态DTO
@Serializable
data class MemoirStateDto(
val current_stage: String,
val covered_stages: List<String> = emptyList(),
val slots: Map<String, Map<String, SlotInfo>> = emptyMap()
)
@Serializable
data class SlotInfo(
val snippet: String? = null,
val filled: Boolean = false
)
// 任务状态DTO
@Serializable
data class TasksStatusDto(
val total: Int,
val pending: Int,
val running: Int,
val success: Int,
val failure: Int,
val all_completed: Boolean,
val tasks: List<TaskInfoDto> = emptyList()
)
@Serializable
data class TaskInfoDto(
val task_id: String,
val task_type: String = "memoir",
val status: String, // "pending", "running", "success", "failure"
val created_at: String? = null,
val updated_at: String? = null,
val result: Map<String, String>? = null
)