feat: 扩展ViewModel支持新功能

- 扩展CreateMemoryViewModel创建回忆功能
- 扩展MyMemoirViewModel支持回忆录状态和任务状态
- 优化数据同步和错误处理
This commit is contained in:
iammm0
2026-01-22 17:58:50 +08:00
parent a3764030a1
commit 0f7a7cc7f2
2 changed files with 217 additions and 10 deletions

View File

@@ -9,9 +9,13 @@ import com.huaga.life_echo.data.repository.ChapterRepository
import com.huaga.life_echo.network.WebSocketClient
import com.huaga.life_echo.network.WebSocketMessage
import com.huaga.life_echo.network.MessageType
import com.huaga.life_echo.network.ApiService
import com.huaga.life_echo.network.AuthService
import com.huaga.life_echo.data.database.Chapter
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.delay
class CreateMemoryViewModel(
private val conversationRepository: ConversationRepository,
@@ -20,6 +24,7 @@ class CreateMemoryViewModel(
) : ViewModel() {
private val webSocketClient = WebSocketClient()
private val apiService = ApiService(TokenManager, AuthService())
val isRecording = MutableStateFlow(false)
val transcript = MutableStateFlow("")
@@ -33,6 +38,10 @@ class CreateMemoryViewModel(
val streamingText = MutableStateFlow("") // 流式文本内容
val isTyping = MutableStateFlow(false) // AI是否正在输入
// 后台处理状态
val isProcessing = MutableStateFlow(false) // 是否正在处理回忆录
val processingStatus = MutableStateFlow("") // 处理状态文本
init {
TokenManager.initialize(context)
}
@@ -45,6 +54,9 @@ class CreateMemoryViewModel(
connectionStatus.value = "连接中..."
try {
// 清除旧的任务记录(与测试脚本一致)
apiService.clearTasks()
// 获取访问令牌
val token = TokenManager.getAccessToken()
webSocketClient.connect(convId, token) { message ->
@@ -102,13 +114,28 @@ class CreateMemoryViewModel(
private fun handleWebSocketMessage(message: WebSocketMessage) {
when (message.type) {
MessageType.transcript -> {
transcript.value = message.data["text"] ?: ""
transcript.value = message.getString("text") ?: ""
}
MessageType.agent_response -> {
// 完整回复
agentResponse.value = message.data["text"] ?: ""
isStreaming.value = false
streamingText.value = ""
// 处理Agent回复可能有多条消息
val text = message.getString("text") ?: ""
val index = message.getInt("index") ?: 0
val total = message.getInt("total") ?: 1
// 如果是第一条消息,重置内容
if (index == 0) {
agentResponse.value = text
} else {
// 追加后续消息
agentResponse.value += "\n\n$text"
}
// 如果是最后一条消息,结束流式状态
if (index >= total - 1) {
isStreaming.value = false
streamingText.value = ""
webSocketClient.setGenerating(false)
}
}
MessageType.agent_response_start -> {
// 流式回复开始
@@ -119,7 +146,7 @@ class CreateMemoryViewModel(
}
MessageType.agent_response_chunk -> {
// 流式回复片段
val chunk = message.data["text"] ?: ""
val chunk = message.getString("text") ?: ""
streamingText.value += chunk
}
MessageType.agent_response_end -> {
@@ -144,9 +171,11 @@ class CreateMemoryViewModel(
isRecording.value = false
isStreaming.value = false
webSocketClient.setGenerating(false)
// 触发对话结束后的处理
handleConversationEnded()
}
MessageType.error -> {
connectionStatus.value = "错误: ${message.data["message"]}"
connectionStatus.value = "错误: ${message.getString("message")}"
isStreaming.value = false
webSocketClient.setGenerating(false)
}
@@ -168,6 +197,109 @@ class CreateMemoryViewModel(
}
}
/**
* 处理对话结束后的逻辑
* 等待后台任务完成,然后刷新章节列表
*/
private fun handleConversationEnded() {
viewModelScope.launch {
isProcessing.value = true
processingStatus.value = "正在处理回忆录..."
// 等待后台处理完成最多等待3分钟
val maxWaitSeconds = 180
val checkInterval = 5 // 每5秒检查一次
var elapsed = 0
while (elapsed < maxWaitSeconds) {
delay(checkInterval * 1000L)
elapsed += checkInterval
// 检查任务状态
val tasksStatus = apiService.getTasksStatus()
tasksStatus.fold(
onSuccess = { status ->
val total = status.total
val pending = status.pending
val running = status.running
val success = status.success
val failure = status.failure
val allCompleted = status.all_completed
processingStatus.value = "处理中: 总任务$total, 等待$pending, 运行$running, 成功$success, 失败$failure"
// 如果所有任务完成,刷新章节列表
if (total > 0 && allCompleted) {
processingStatus.value = "处理完成!"
refreshChapters()
isProcessing.value = false
return@launch
}
// 如果没有任务但有章节内容,也认为完成
if (total == 0 && elapsed > 30) {
// 检查是否有章节
val chaptersResult = apiService.getChapters()
chaptersResult.fold(
onSuccess = { chapters ->
if (chapters.isNotEmpty()) {
processingStatus.value = "处理完成!"
refreshChapters()
isProcessing.value = false
return@launch
}
},
onFailure = { }
)
}
},
onFailure = { exception ->
processingStatus.value = "检查任务状态失败: ${exception.message}"
}
)
}
// 超时后也刷新章节列表
processingStatus.value = "处理超时,正在刷新章节..."
refreshChapters()
isProcessing.value = false
}
}
/**
* 刷新章节列表
*/
private suspend fun refreshChapters() {
val chaptersResult = apiService.getChapters()
chaptersResult.fold(
onSuccess = { chapterDtos ->
// 转换为本地Chapter实体并保存
val chapters = chapterDtos.map { dto ->
Chapter(
id = dto.id,
title = dto.title,
content = dto.content,
orderIndex = dto.order_index,
status = dto.status,
category = dto.category,
updatedAt = dto.updated_at?.let {
try {
java.time.Instant.parse(it).toEpochMilli()
} catch (e: Exception) {
System.currentTimeMillis()
}
} ?: System.currentTimeMillis(),
isNew = dto.is_new
)
}
chapterRepository.insertChapters(chapters)
},
onFailure = { exception ->
processingStatus.value = "刷新章节失败: ${exception.message}"
}
)
}
override fun onCleared() {
super.onCleared()
viewModelScope.launch {

View File

@@ -5,6 +5,8 @@ import androidx.lifecycle.viewModelScope
import com.huaga.life_echo.data.database.Chapter
import com.huaga.life_echo.data.repository.ChapterRepository
import com.huaga.life_echo.network.ApiService
import com.huaga.life_echo.network.models.MemoirStateDto
import com.huaga.life_echo.network.models.TasksStatusDto
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -21,6 +23,8 @@ class MyMemoirViewModel(
val error = MutableStateFlow<String?>(null)
val bookInfo = MutableStateFlow<com.huaga.life_echo.network.models.BookDto?>(null)
val showFullTextReading = MutableStateFlow(false)
val memoirState = MutableStateFlow<MemoirStateDto?>(null)
val tasksStatus = MutableStateFlow<TasksStatusDto?>(null)
fun selectChapter(chapter: Chapter) {
selectedChapter.value = chapter
@@ -52,11 +56,36 @@ class MyMemoirViewModel(
fun refreshChapters() {
viewModelScope.launch {
isLoading.value = true
error.value = null
try {
val result = apiService.getChapters()
result.getOrNull()?.let { chapters ->
// TODO: 更新本地数据库
}
result.fold(
onSuccess = { chapterDtos ->
// 转换为本地Chapter实体并保存
val chapters = chapterDtos.map { dto ->
Chapter(
id = dto.id,
title = dto.title,
content = dto.content,
orderIndex = dto.order_index,
status = dto.status,
category = dto.category,
updatedAt = dto.updated_at?.let {
try {
java.time.Instant.parse(it).toEpochMilli()
} catch (e: Exception) {
System.currentTimeMillis()
}
} ?: System.currentTimeMillis(),
isNew = dto.is_new
)
}
chapterRepository.insertChapters(chapters)
},
onFailure = { exception ->
error.value = "同步失败: ${exception.message}"
}
)
} catch (e: Exception) {
error.value = "同步失败: ${e.message}"
} finally {
@@ -65,6 +94,52 @@ class MyMemoirViewModel(
}
}
/**
* 加载回忆录状态
*/
fun loadMemoirState() {
viewModelScope.launch {
isLoading.value = true
error.value = null
try {
val result = apiService.getMemoirState()
result.fold(
onSuccess = { state ->
memoirState.value = state
},
onFailure = { exception ->
error.value = "获取回忆录状态失败: ${exception.message}"
}
)
} catch (e: Exception) {
error.value = "获取回忆录状态失败: ${e.message}"
} finally {
isLoading.value = false
}
}
}
/**
* 检查任务状态
*/
fun checkTasksStatus() {
viewModelScope.launch {
try {
val result = apiService.getTasksStatus()
result.fold(
onSuccess = { status ->
tasksStatus.value = status
},
onFailure = { exception ->
error.value = "获取任务状态失败: ${exception.message}"
}
)
} catch (e: Exception) {
error.value = "获取任务状态失败: ${e.message}"
}
}
}
fun loadBookInfo() {
viewModelScope.launch {
apiService.getBookInfo().fold(