fix: 语音消息暂存本地 修复显示异常
This commit is contained in:
@@ -44,9 +44,17 @@ class ConversationAgent:
|
||||
messages.append(AIMessage(content=msg["content"]))
|
||||
return messages
|
||||
|
||||
async def _save_message(self, conversation_id: str, role: str, content: str):
|
||||
async def _save_message(
|
||||
self,
|
||||
conversation_id: str,
|
||||
role: str,
|
||||
content: str,
|
||||
message_type: str = "text",
|
||||
):
|
||||
"""保存消息到 Redis"""
|
||||
await redis_service.add_message(conversation_id, role, content)
|
||||
await redis_service.add_message(
|
||||
conversation_id, role, content, message_type=message_type
|
||||
)
|
||||
|
||||
def _format_history_string(self, messages: List[Any]) -> str:
|
||||
"""将消息列表格式化为字符串(用于 prompt)"""
|
||||
@@ -236,6 +244,7 @@ class ConversationAgent:
|
||||
missing_fields: List[str],
|
||||
filled_fields: Dict[str, str],
|
||||
nickname: str = "",
|
||||
is_from_voice: bool = False,
|
||||
) -> List[str]:
|
||||
"""在资料收集过程中生成跟进回复"""
|
||||
if not self.llm:
|
||||
@@ -250,7 +259,8 @@ class ConversationAgent:
|
||||
response = await self.llm.ainvoke(full_prompt)
|
||||
response_text = response.content if hasattr(response, 'content') else str(response)
|
||||
|
||||
await self._save_message(conversation_id, "human", user_message)
|
||||
human_msg_type = "audio" if is_from_voice else "text"
|
||||
await self._save_message(conversation_id, "human", user_message, message_type=human_msg_type)
|
||||
await self._save_message(conversation_id, "ai", response_text)
|
||||
|
||||
messages = [msg.strip() for msg in response_text.split("[SPLIT]") if msg.strip()]
|
||||
@@ -285,6 +295,7 @@ class ConversationAgent:
|
||||
user_message: str,
|
||||
memoir_state: MemoirStateSchema,
|
||||
user_profile_context: str = "",
|
||||
is_from_voice: bool = False,
|
||||
) -> List[str]:
|
||||
"""
|
||||
基于共享状态异步生成引导式回复
|
||||
@@ -294,6 +305,7 @@ class ConversationAgent:
|
||||
user_message: 用户消息
|
||||
memoir_state: 共享状态
|
||||
user_profile_context: 用户基础资料上下文
|
||||
is_from_voice: 用户消息是否来自语音转写(用于保存正确的 messageType)
|
||||
|
||||
Returns:
|
||||
Agent 回应文本列表(支持多条消息)
|
||||
@@ -333,13 +345,14 @@ class ConversationAgent:
|
||||
|
||||
response = await self.llm.ainvoke(full_prompt)
|
||||
response_text = response.content if hasattr(response, 'content') else str(response)
|
||||
|
||||
await self._save_message(conversation_id, "human", user_message)
|
||||
|
||||
human_msg_type = "audio" if is_from_voice else "text"
|
||||
await self._save_message(conversation_id, "human", user_message, message_type=human_msg_type)
|
||||
await self._save_message(conversation_id, "ai", response_text)
|
||||
|
||||
|
||||
messages = [msg.strip() for msg in response_text.split("[SPLIT]") if msg.strip()]
|
||||
return messages[:3] if messages else [response_text]
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"生成回应失败: {e}")
|
||||
return [f"抱歉,生成回应时出现错误: {str(e)}"]
|
||||
|
||||
@@ -195,7 +195,7 @@ async def get_messages(
|
||||
"content": msg.get("content", ""),
|
||||
"senderType": "user" if msg.get("role") == "human" else "assistant",
|
||||
"timestamp": int(datetime.now(timezone.utc).timestamp() * 1000), # Redis中没有时间戳,使用当前时间
|
||||
"messageType": "text"
|
||||
"messageType": msg.get("messageType", "text"), # 保留语音消息类型,使重新进入时仍显示为语音条
|
||||
})
|
||||
return messages
|
||||
except Exception as e:
|
||||
|
||||
@@ -928,12 +928,14 @@ async def process_user_message(
|
||||
|
||||
remaining = _get_missing_profile_fields(user)
|
||||
filled = _get_filled_profile_fields(user)
|
||||
is_from_voice = bool(segment.audio_url)
|
||||
responses = await agent.generate_profile_followup(
|
||||
conversation_id=conversation_id,
|
||||
user_message=user_message,
|
||||
missing_fields=remaining,
|
||||
filled_fields=filled,
|
||||
nickname=user.nickname or "",
|
||||
is_from_voice=is_from_voice,
|
||||
)
|
||||
|
||||
segment.agent_response = "\n\n".join(responses)
|
||||
@@ -978,11 +980,13 @@ async def process_user_message(
|
||||
)
|
||||
|
||||
try:
|
||||
is_from_voice = bool(segment.audio_url)
|
||||
responses = await agent.generate_response_with_state(
|
||||
conversation_id=conversation_id,
|
||||
user_message=user_message,
|
||||
memoir_state=state,
|
||||
user_profile_context=user_profile_context,
|
||||
is_from_voice=is_from_voice,
|
||||
)
|
||||
|
||||
segment.agent_response = "\n\n".join(responses)
|
||||
|
||||
@@ -72,31 +72,33 @@ class RedisService:
|
||||
return []
|
||||
|
||||
async def add_message(
|
||||
self,
|
||||
conversation_id: str,
|
||||
role: str,
|
||||
content: str
|
||||
self,
|
||||
conversation_id: str,
|
||||
role: str,
|
||||
content: str,
|
||||
message_type: str = "text",
|
||||
) -> bool:
|
||||
"""
|
||||
添加消息到对话历史
|
||||
|
||||
|
||||
Args:
|
||||
conversation_id: 对话 ID
|
||||
role: 角色 ("human" 或 "ai")
|
||||
content: 消息内容
|
||||
|
||||
message_type: 消息类型 ("text" 或 "audio"),用于区分文本消息与语音消息
|
||||
|
||||
Returns:
|
||||
是否成功
|
||||
"""
|
||||
try:
|
||||
client = await self.get_client()
|
||||
key = self._conversation_key(conversation_id)
|
||||
|
||||
|
||||
# 获取现有历史
|
||||
history = await self.get_conversation_history(conversation_id)
|
||||
|
||||
|
||||
# 添加新消息
|
||||
history.append({"role": role, "content": content})
|
||||
history.append({"role": role, "content": content, "messageType": message_type})
|
||||
|
||||
# 保存回 Redis(带过期时间)
|
||||
await client.setex(key, self.session_ttl, json.dumps(history, ensure_ascii=False))
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.huaga.life_echo.data.database.AppDatabase
|
||||
import com.huaga.life_echo.data.repository.ChapterRepository
|
||||
import com.huaga.life_echo.data.repository.ConversationRepository
|
||||
import com.huaga.life_echo.data.repository.MessageRepository
|
||||
import com.huaga.life_echo.data.repository.VoiceAttachmentRepository
|
||||
import com.huaga.life_echo.data.repository.PaymentRepository
|
||||
import com.huaga.life_echo.data.repository.ProfileRepository
|
||||
import com.huaga.life_echo.feature.conversation.adapters.ConversationApiAdapter
|
||||
@@ -113,6 +114,10 @@ class AppContainer(private val context: Context) {
|
||||
)
|
||||
}
|
||||
|
||||
val voiceAttachmentRepository by lazy {
|
||||
VoiceAttachmentRepository(voiceAttachmentDao = database.voiceAttachmentDao())
|
||||
}
|
||||
|
||||
val paymentRepository by lazy {
|
||||
PaymentRepository(
|
||||
paymentApi = paymentApi,
|
||||
|
||||
@@ -4,6 +4,8 @@ import android.content.Context
|
||||
import androidx.room.Database
|
||||
import androidx.room.Room
|
||||
import androidx.room.RoomDatabase
|
||||
import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
|
||||
@Database(
|
||||
entities = [
|
||||
@@ -12,9 +14,10 @@ import androidx.room.RoomDatabase
|
||||
ConversationSegment::class,
|
||||
Chapter::class,
|
||||
Book::class,
|
||||
Message::class
|
||||
Message::class,
|
||||
VoiceAttachment::class,
|
||||
],
|
||||
version = 2,
|
||||
version = 3,
|
||||
exportSchema = false
|
||||
)
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
@@ -22,11 +25,26 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
abstract fun conversationSegmentDao(): ConversationSegmentDao
|
||||
abstract fun chapterDao(): ChapterDao
|
||||
abstract fun messageDao(): MessageDao
|
||||
abstract fun voiceAttachmentDao(): VoiceAttachmentDao
|
||||
|
||||
companion object {
|
||||
private val MIGRATION_2_3 = object : Migration(2, 3) {
|
||||
override fun migrate(db: SupportSQLiteDatabase) {
|
||||
db.execSQL("""
|
||||
CREATE TABLE IF NOT EXISTS voice_attachments (
|
||||
conversationId TEXT NOT NULL,
|
||||
userMessageIndex INTEGER NOT NULL,
|
||||
filePath TEXT NOT NULL,
|
||||
durationSeconds INTEGER NOT NULL,
|
||||
PRIMARY KEY(conversationId, userMessageIndex)
|
||||
)
|
||||
""".trimIndent())
|
||||
}
|
||||
}
|
||||
|
||||
@Volatile
|
||||
private var INSTANCE: AppDatabase? = null
|
||||
|
||||
|
||||
fun getDatabase(context: Context): AppDatabase {
|
||||
return INSTANCE ?: synchronized(this) {
|
||||
val instance = Room.databaseBuilder(
|
||||
@@ -34,6 +52,7 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
AppDatabase::class.java,
|
||||
"life_echo_database"
|
||||
)
|
||||
.addMigrations(MIGRATION_2_3)
|
||||
.fallbackToDestructiveMigration()
|
||||
.build()
|
||||
INSTANCE = instance
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.huaga.life_echo.data.database
|
||||
|
||||
import androidx.room.Entity
|
||||
|
||||
/**
|
||||
* 本地持久化的语音附件元数据
|
||||
* 用于在重新进入对话时恢复语音条的显示(时长、播放)
|
||||
*
|
||||
* @param conversationId 对话 ID
|
||||
* @param userMessageIndex 用户消息在对话中的索引(0-based,仅计 user 消息)
|
||||
* @param filePath 本地持久化文件路径
|
||||
* @param durationSeconds 录音时长(秒)
|
||||
*/
|
||||
@Entity(tableName = "voice_attachments", primaryKeys = ["conversationId", "userMessageIndex"])
|
||||
data class VoiceAttachment(
|
||||
val conversationId: String,
|
||||
val userMessageIndex: Int,
|
||||
val filePath: String,
|
||||
val durationSeconds: Int,
|
||||
)
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.huaga.life_echo.data.database
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
|
||||
@Dao
|
||||
interface VoiceAttachmentDao {
|
||||
@Query("SELECT * FROM voice_attachments WHERE conversationId = :conversationId ORDER BY userMessageIndex ASC")
|
||||
suspend fun getByConversationId(conversationId: String): List<VoiceAttachment>
|
||||
|
||||
@Query("SELECT * FROM voice_attachments WHERE conversationId = :conversationId AND userMessageIndex = :index")
|
||||
suspend fun getByConversationAndIndex(conversationId: String, index: Int): VoiceAttachment?
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun insert(attachment: VoiceAttachment)
|
||||
|
||||
@Query("DELETE FROM voice_attachments WHERE conversationId = :conversationId")
|
||||
suspend fun deleteByConversationId(conversationId: String)
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.huaga.life_echo.data.repository
|
||||
|
||||
import com.huaga.life_echo.data.database.VoiceAttachment
|
||||
import com.huaga.life_echo.data.database.VoiceAttachmentDao
|
||||
|
||||
/**
|
||||
* 语音附件本地持久化仓库
|
||||
* 用于保存和恢复用户发送的语音消息的本地文件路径和时长
|
||||
*/
|
||||
class VoiceAttachmentRepository(
|
||||
private val voiceAttachmentDao: VoiceAttachmentDao,
|
||||
) {
|
||||
suspend fun getByConversationId(conversationId: String): List<VoiceAttachment> {
|
||||
return voiceAttachmentDao.getByConversationId(conversationId)
|
||||
}
|
||||
|
||||
suspend fun insert(attachment: VoiceAttachment) {
|
||||
voiceAttachmentDao.insert(attachment)
|
||||
}
|
||||
|
||||
suspend fun deleteByConversationId(conversationId: String) {
|
||||
voiceAttachmentDao.deleteByConversationId(conversationId)
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import com.huaga.life_echo.data.auth.TokenManager
|
||||
import com.huaga.life_echo.data.repository.ConversationRepository
|
||||
import com.huaga.life_echo.data.repository.ChapterRepository
|
||||
import com.huaga.life_echo.data.repository.MessageRepository
|
||||
import com.huaga.life_echo.data.repository.VoiceAttachmentRepository
|
||||
import com.huaga.life_echo.feature.conversation.ports.ConversationApiPort
|
||||
import com.huaga.life_echo.feature.conversation.ports.ConversationRealtimePort
|
||||
import com.huaga.life_echo.feature.conversation.ports.AudioSegmentRequest
|
||||
@@ -22,6 +23,7 @@ import com.huaga.life_echo.network.WebSocketMessage
|
||||
import com.huaga.life_echo.network.MessageType
|
||||
import com.huaga.life_echo.model.MessageDto
|
||||
import com.huaga.life_echo.data.database.Chapter
|
||||
import com.huaga.life_echo.data.database.VoiceAttachment
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
@@ -40,6 +42,7 @@ class CreateMemoryViewModel(
|
||||
private val conversationRepository: ConversationRepository,
|
||||
private val chapterRepository: ChapterRepository,
|
||||
private val messageRepository: MessageRepository,
|
||||
private val voiceAttachmentRepository: VoiceAttachmentRepository,
|
||||
private val context: Context,
|
||||
private val conversationApi: ConversationApiPort,
|
||||
private val conversationRealtime: ConversationRealtimePort,
|
||||
@@ -51,6 +54,25 @@ class CreateMemoryViewModel(
|
||||
private const val TAG = "CreateMemoryViewModel"
|
||||
private const val MIN_RECORDING_DURATION = 1
|
||||
private const val SEGMENT_DURATION_SECONDS = 60
|
||||
private const val VOICE_RECORDINGS_DIR = "voice_recordings"
|
||||
}
|
||||
|
||||
/**
|
||||
* 将录音文件复制到持久化目录,避免 cache 被清理后丢失
|
||||
* @return 持久化路径,失败时返回 null
|
||||
*/
|
||||
private fun persistVoiceFile(conversationId: String, userMessageIndex: Int, sourcePath: String): String? {
|
||||
return try {
|
||||
val baseDir = File(context.filesDir, VOICE_RECORDINGS_DIR)
|
||||
val convDir = File(baseDir, conversationId)
|
||||
convDir.mkdirs()
|
||||
val destFile = File(convDir, "${userMessageIndex}.m4a")
|
||||
File(sourcePath).copyTo(destFile, overwrite = true)
|
||||
destFile.absolutePath
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "持久化语音文件失败: ${e.message}", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private val audioPlayer = AudioPlayer(context)
|
||||
@@ -165,12 +187,52 @@ class CreateMemoryViewModel(
|
||||
onSuccess = { messages ->
|
||||
historyMessages.value = messages
|
||||
messageRepository.syncMessages(convId)
|
||||
_audioFilePaths.value = emptyMap()
|
||||
_audioDurations.value = emptyMap()
|
||||
mergeLocalVoiceAttachments(convId, messages)
|
||||
},
|
||||
onFailure = { exception ->
|
||||
connectionStatus.value = "加载历史消息失败: ${exception.message}"
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 将本地持久化的语音附件合并到 audioFilePaths 和 audioDurations,
|
||||
* 使重新进入时语音条显示与首次一致(含时长、可播放)
|
||||
*/
|
||||
private suspend fun mergeLocalVoiceAttachments(convId: String, messages: List<MessageDto>) {
|
||||
val attachments = voiceAttachmentRepository.getByConversationId(convId)
|
||||
if (attachments.isEmpty()) return
|
||||
|
||||
val attachmentMap = attachments.associateBy { it.userMessageIndex }
|
||||
var userMsgIndex = 0
|
||||
val newPaths = mutableMapOf<String, String>()
|
||||
val newDurations = mutableMapOf<String, Int>()
|
||||
|
||||
for (msg in messages) {
|
||||
if (msg.senderType != "user") continue
|
||||
if (msg.messageType != "audio") {
|
||||
userMsgIndex++
|
||||
continue
|
||||
}
|
||||
val att = attachmentMap[userMsgIndex]
|
||||
if (att == null) {
|
||||
userMsgIndex++
|
||||
continue
|
||||
}
|
||||
if (File(att.filePath).exists()) {
|
||||
newPaths[msg.id] = att.filePath
|
||||
newDurations[msg.id] = att.durationSeconds
|
||||
}
|
||||
userMsgIndex++
|
||||
}
|
||||
|
||||
if (newPaths.isNotEmpty() || newDurations.isNotEmpty()) {
|
||||
_audioFilePaths.value = _audioFilePaths.value + newPaths
|
||||
_audioDurations.value = _audioDurations.value + newDurations
|
||||
}
|
||||
}
|
||||
|
||||
fun startConversation() {
|
||||
viewModelScope.launch {
|
||||
@@ -373,6 +435,19 @@ class CreateMemoryViewModel(
|
||||
|
||||
val id = conversationId.value ?: return
|
||||
|
||||
val userMessageIndex = historyMessages.value.count { it.senderType == "user" }
|
||||
val persistentPath = persistVoiceFile(id, userMessageIndex, filePath)
|
||||
if (persistentPath != null) {
|
||||
voiceAttachmentRepository.insert(
|
||||
VoiceAttachment(
|
||||
conversationId = id,
|
||||
userMessageIndex = userMessageIndex,
|
||||
filePath = persistentPath,
|
||||
durationSeconds = durationSeconds,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val tempMessageId = "audio_user_${System.currentTimeMillis()}"
|
||||
val tempMessage = MessageDto(
|
||||
id = tempMessageId,
|
||||
@@ -383,7 +458,8 @@ class CreateMemoryViewModel(
|
||||
messageType = "audio"
|
||||
)
|
||||
historyMessages.value = historyMessages.value + tempMessage
|
||||
_audioFilePaths.value = _audioFilePaths.value + (tempMessageId to filePath)
|
||||
val displayPath = persistentPath ?: filePath
|
||||
_audioFilePaths.value = _audioFilePaths.value + (tempMessageId to displayPath)
|
||||
_audioDurations.value = _audioDurations.value + (tempMessageId to durationSeconds)
|
||||
|
||||
val segmentFiles = try {
|
||||
@@ -442,6 +518,10 @@ class CreateMemoryViewModel(
|
||||
// ==================== 音频播放功能 ====================
|
||||
|
||||
fun toggleAudioPlayback(messageId: String, filePath: String) {
|
||||
if (filePath.isBlank()) {
|
||||
Log.d(TAG, "无本地音频文件,无法播放历史语音消息: $messageId")
|
||||
return
|
||||
}
|
||||
Log.d(TAG, "切换音频播放状态: $messageId, $filePath")
|
||||
audioPlayer.play(messageId, filePath)
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ class ViewModelFactory(private val context: Context) : ViewModelProvider.Factory
|
||||
conversationRepository = container.conversationRepository,
|
||||
chapterRepository = container.chapterRepository,
|
||||
messageRepository = container.messageRepository,
|
||||
voiceAttachmentRepository = container.voiceAttachmentRepository,
|
||||
context = context,
|
||||
conversationApi = container.conversationApi,
|
||||
conversationRealtime = container.createConversationRealtime(),
|
||||
|
||||
@@ -9,9 +9,12 @@ import com.huaga.life_echo.data.database.ConversationSegment
|
||||
import com.huaga.life_echo.data.database.ConversationSegmentDao
|
||||
import com.huaga.life_echo.data.database.Message
|
||||
import com.huaga.life_echo.data.database.MessageDao
|
||||
import com.huaga.life_echo.data.database.VoiceAttachment
|
||||
import com.huaga.life_echo.data.database.VoiceAttachmentDao
|
||||
import com.huaga.life_echo.data.repository.ChapterRepository
|
||||
import com.huaga.life_echo.data.repository.ConversationRepository
|
||||
import com.huaga.life_echo.data.repository.MessageRepository
|
||||
import com.huaga.life_echo.data.repository.VoiceAttachmentRepository
|
||||
import com.huaga.life_echo.feature.conversation.ports.AudioSegmentRequest
|
||||
import com.huaga.life_echo.feature.conversation.ports.ConversationApiPort
|
||||
import com.huaga.life_echo.feature.conversation.ports.ConversationRealtimePort
|
||||
@@ -396,6 +399,7 @@ class CreateMemoryViewModelRecordingCoordinatorTest {
|
||||
messageDao = FakeMessageDao(),
|
||||
conversationApi = conversationApi,
|
||||
),
|
||||
voiceAttachmentRepository = VoiceAttachmentRepository(voiceAttachmentDao = FakeVoiceAttachmentDao()),
|
||||
context = context,
|
||||
conversationApi = conversationApi,
|
||||
conversationRealtime = realtime,
|
||||
@@ -521,6 +525,13 @@ class CreateMemoryViewModelRecordingCoordinatorTest {
|
||||
override suspend fun deleteMessagesByConversationId(conversationId: String) = Unit
|
||||
}
|
||||
|
||||
private class FakeVoiceAttachmentDao : VoiceAttachmentDao {
|
||||
override suspend fun getByConversationId(conversationId: String) = emptyList()
|
||||
override suspend fun getByConversationAndIndex(conversationId: String, index: Int) = null
|
||||
override suspend fun insert(attachment: VoiceAttachment) = Unit
|
||||
override suspend fun deleteByConversationId(conversationId: String) = Unit
|
||||
}
|
||||
|
||||
private class NoOpConversationApiPort : ConversationApiPort {
|
||||
override suspend fun createConversation(): Result<CreateConversationResponse> =
|
||||
Result.failure(Exception("no-op"))
|
||||
|
||||
@@ -9,9 +9,12 @@ import com.huaga.life_echo.data.database.ConversationSegment
|
||||
import com.huaga.life_echo.data.database.ConversationSegmentDao
|
||||
import com.huaga.life_echo.data.database.Message
|
||||
import com.huaga.life_echo.data.database.MessageDao
|
||||
import com.huaga.life_echo.data.database.VoiceAttachment
|
||||
import com.huaga.life_echo.data.database.VoiceAttachmentDao
|
||||
import com.huaga.life_echo.data.repository.ChapterRepository
|
||||
import com.huaga.life_echo.data.repository.ConversationRepository
|
||||
import com.huaga.life_echo.data.repository.MessageRepository
|
||||
import com.huaga.life_echo.data.repository.VoiceAttachmentRepository
|
||||
import com.huaga.life_echo.feature.conversation.ports.AudioSegmentRequest
|
||||
import com.huaga.life_echo.feature.conversation.ports.ConversationApiPort
|
||||
import com.huaga.life_echo.feature.conversation.ports.ConversationRealtimePort
|
||||
@@ -169,6 +172,7 @@ class CreateMemoryViewModelWarmupTest {
|
||||
messageDao = FakeMessageDao(),
|
||||
conversationApi = conversationApi,
|
||||
),
|
||||
voiceAttachmentRepository = VoiceAttachmentRepository(voiceAttachmentDao = FakeVoiceAttachmentDao()),
|
||||
context = context,
|
||||
conversationApi = conversationApi,
|
||||
conversationRealtime = realtime,
|
||||
@@ -318,4 +322,11 @@ class CreateMemoryViewModelWarmupTest {
|
||||
override suspend fun deleteMessage(message: Message) = Unit
|
||||
override suspend fun deleteMessagesByConversationId(conversationId: String) = Unit
|
||||
}
|
||||
|
||||
private class FakeVoiceAttachmentDao : VoiceAttachmentDao {
|
||||
override suspend fun getByConversationId(conversationId: String) = emptyList()
|
||||
override suspend fun getByConversationAndIndex(conversationId: String, index: Int) = null
|
||||
override suspend fun insert(attachment: VoiceAttachment) = Unit
|
||||
override suspend fun deleteByConversationId(conversationId: String) = Unit
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user