Refactor: 群记忆重构为房间规则并新增私密群组付费功能

- **群记忆重构:**
    - 将群记忆(Group Memory)的底层实现从 `AgentRule`(智能体规则)重构为 `RoomRule`(房间规则)。
    - ViewModel 中相关的数据和服务调用已全部更新,以使用 `RoomService` 和 `RoomRuleEntity`。
    - 移除了获取智能体 `openId` 的相关逻辑。

- **私密群组付费:**
    - 新增设置群组为私密时的派币付费流程。
    - 用户设置私密群组时,若未支付过费用,将弹出付费确认对话框,显示所需费用和账户余额。
    - `GroupInfo` 实体新增 `trtcType` 和 `privateFeePaid` 字段,用于判断群组可见性状态和付费状态。
    - UI 逻辑更新,根据付费状态显示不同的提示信息(如 "待解锁"、派币费用)。

- **UI 优化:**
    - 移除群信息页中已废弃的 "解锁群扩展" 横幅。
    - 记忆管理弹窗现在会立即展开到全屏,优化了显示动画。
    - 动态显示添加群记忆所需的派币消耗。
This commit is contained in:
2025-11-12 22:45:41 +08:00
parent 464d0adb19
commit 2f08a7b2b6
7 changed files with 278 additions and 199 deletions

View File

@@ -468,7 +468,12 @@ fun RoomRuleCreator.toRoomRuleCreatorEntity(): RoomRuleCreatorEntity {
return RoomRuleCreatorEntity( return RoomRuleCreatorEntity(
id = id, id = id,
nickname = nickname, nickname = nickname,
avatar = avatar avatar = avatar,
avatarMedium = avatarMedium,
avatarLarge = avatarLarge,
avatarDirectUrl = avatarDirectUrl,
avatarMediumDirectUrl = avatarMediumDirectUrl,
avatarLargeDirectUrl = avatarLargeDirectUrl
) )
} }

View File

@@ -913,6 +913,11 @@ data class UpdateRoomRuleRequestBody(
* @param id 创建者ID * @param id 创建者ID
* @param nickname 创建者昵称 * @param nickname 创建者昵称
* @param avatar 创建者头像文件名 * @param avatar 创建者头像文件名
* @param avatarMedium 中等头像文件名
* @param avatarLarge 大头像文件名
* @param avatarDirectUrl 小头像直接访问URL
* @param avatarMediumDirectUrl 中等头像直接访问URL
* @param avatarLargeDirectUrl 大头像直接访问URL
*/ */
data class RoomRuleCreator( data class RoomRuleCreator(
@SerializedName("id") @SerializedName("id")
@@ -920,7 +925,17 @@ data class RoomRuleCreator(
@SerializedName("nickname") @SerializedName("nickname")
val nickname: String, val nickname: String,
@SerializedName("avatar") @SerializedName("avatar")
val avatar: String val avatar: String,
@SerializedName("avatarMedium")
val avatarMedium: String? = null,
@SerializedName("avatarLarge")
val avatarLarge: String? = null,
@SerializedName("avatarDirectUrl")
val avatarDirectUrl: String? = null,
@SerializedName("avatarMediumDirectUrl")
val avatarMediumDirectUrl: String? = null,
@SerializedName("avatarLargeDirectUrl")
val avatarLargeDirectUrl: String? = null
) )
/** /**

View File

@@ -13,4 +13,6 @@ data class GroupInfo(
val groupAvatar: String, val groupAvatar: String,
val memberCount: Int, val memberCount: Int,
val isCreator: Boolean = false, val isCreator: Boolean = false,
val trtcType: String = "Public",
val privateFeePaid: Boolean = false,
) )

View File

@@ -80,7 +80,12 @@ data class ProfileEntity(
data class RoomRuleCreatorEntity( data class RoomRuleCreatorEntity(
val id: Int, val id: Int,
val nickname: String, val nickname: String,
val avatar: String val avatar: String,
val avatarMedium: String? = null,
val avatarLarge: String? = null,
val avatarDirectUrl: String? = null,
val avatarMediumDirectUrl: String? = null,
val avatarLargeDirectUrl: String? = null
) )
/** /**

View File

@@ -40,6 +40,7 @@ import com.aiosman.ravenow.LocalAppTheme
import com.aiosman.ravenow.LocalNavController import com.aiosman.ravenow.LocalNavController
import com.aiosman.ravenow.R import com.aiosman.ravenow.R
import com.aiosman.ravenow.ui.composables.CustomAsyncImage import com.aiosman.ravenow.ui.composables.CustomAsyncImage
import com.aiosman.ravenow.ui.composables.PointsPaymentDialog
import com.aiosman.ravenow.ui.composables.StatusBarSpacer import com.aiosman.ravenow.ui.composables.StatusBarSpacer
import com.aiosman.ravenow.ui.index.NavItem import com.aiosman.ravenow.ui.index.NavItem
import com.aiosman.ravenow.ui.modifiers.noRippleClickable import com.aiosman.ravenow.ui.modifiers.noRippleClickable
@@ -66,6 +67,8 @@ fun GroupChatInfoScreen(groupId: String) {
var showAddMemoryDialog by remember { mutableStateOf(false) } var showAddMemoryDialog by remember { mutableStateOf(false) }
var showMemoryManageDialog by remember { mutableStateOf(false) } var showMemoryManageDialog by remember { mutableStateOf(false) }
var showVisibilityDialog by remember { mutableStateOf(false) } var showVisibilityDialog by remember { mutableStateOf(false) }
var showVisibilityPaymentDialog by remember { mutableStateOf(false) }
var pendingIsPrivate by remember { mutableStateOf(false) }
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
val memoryManageSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) val memoryManageSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
@@ -287,27 +290,6 @@ fun GroupChatInfoScreen(groupId: String) {
} }
} }
// 解锁群扩展 横幅
item {
Spacer(modifier = Modifier.height(12.dp))
Box(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(10.dp))
.background(AppColors.decentBackground.copy(alpha = 0.35f))
.padding(horizontal = 10.dp, vertical = 10.dp),
contentAlignment = Alignment.CenterStart
) {
Text(
text = stringResource(R.string.group_chat_info_unlock_extension),
style = androidx.compose.ui.text.TextStyle(
color = AppColors.main,
fontSize = 12.sp,
fontWeight = FontWeight.Medium
)
)
}
}
// 群记忆 卡片 // 群记忆 卡片
item { item {
@@ -457,6 +439,8 @@ fun GroupChatInfoScreen(groupId: String) {
), ),
modifier = Modifier.weight(1f) modifier = Modifier.weight(1f)
) )
// 未解锁时才显示"待解锁"
if (viewModel.groupInfo?.privateFeePaid != true) {
Text( Text(
text = stringResource(R.string.group_chat_info_locked), text = stringResource(R.string.group_chat_info_locked),
style = androidx.compose.ui.text.TextStyle( style = androidx.compose.ui.text.TextStyle(
@@ -464,6 +448,7 @@ fun GroupChatInfoScreen(groupId: String) {
fontSize = 11.sp fontSize = 11.sp
) )
) )
}
Image( Image(
painter = painterResource(R.drawable.rave_now_nav_right), painter = painterResource(R.drawable.rave_now_nav_right),
modifier = Modifier.size(16.dp), modifier = Modifier.size(16.dp),
@@ -592,11 +577,50 @@ fun GroupChatInfoScreen(groupId: String) {
shape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp) shape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp)
) { ) {
GroupVisibilityDialog( GroupVisibilityDialog(
onDismiss = { showVisibilityDialog = false } viewModel = viewModel,
onDismiss = { showVisibilityDialog = false },
onConfirmPrivate = { isPrivate ->
// 如果选择私密群组且未解锁,显示付费确认弹框
if (isPrivate && (viewModel.groupInfo?.privateFeePaid != true)) {
pendingIsPrivate = true
showVisibilityDialog = false
showVisibilityPaymentDialog = true
} else {
// 直接更新可见性
viewModel.updateVisibility(isPrivate)
showVisibilityDialog = false
}
}
) )
} }
} }
// 付费确认弹框
if (showVisibilityPaymentDialog) {
val cost = viewModel.privateGroupCost ?: 0
val currentBalance = viewModel.pointsBalance ?: 0
val balanceAfterCost = (currentBalance - cost).coerceAtLeast(0)
val isBalanceSufficient = currentBalance >= cost
PointsPaymentDialog(
cost = cost,
currentBalance = currentBalance,
balanceAfterCost = balanceAfterCost,
isBalanceSufficient = isBalanceSufficient,
onConfirm = {
// 确认支付,更新可见性
viewModel.updateVisibility(pendingIsPrivate)
showVisibilityPaymentDialog = false
},
onCancel = {
showVisibilityPaymentDialog = false
},
title = stringResource(R.string.group_chat_info_private_group),
description = stringResource(R.string.group_chat_info_private_group_desc),
isProcessing = viewModel.isUpdatingVisibility
)
}
// 添加群记忆弹窗 // 添加群记忆弹窗
if (showAddMemoryDialog) { if (showAddMemoryDialog) {
ModalBottomSheet( ModalBottomSheet(
@@ -648,6 +672,10 @@ fun GroupChatInfoScreen(groupId: String) {
}, },
shape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp) shape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp)
) { ) {
// 立即展开到全屏,避免逐渐变高的动画
LaunchedEffect(Unit) {
memoryManageSheetState.expand()
}
GroupMemoryManageContent( GroupMemoryManageContent(
groupId = groupId, groupId = groupId,
viewModel = viewModel, viewModel = viewModel,
@@ -880,8 +908,13 @@ fun AddGroupMemoryDialog(
text = "", text = "",
fontSize = 13.sp fontSize = 13.sp
) )
val memoryCost = viewModel.addMemoryCost
Text( Text(
text = stringResource(R.string.group_chat_info_memory_cost), text = if (memoryCost != null && memoryCost > 0) {
"添加记忆需消耗 ${memoryCost}派币"
} else {
stringResource(R.string.group_chat_info_memory_cost)
},
style = TextStyle( style = TextStyle(
fontSize = 13.sp, fontSize = 13.sp,
color = Color.Black color = Color.Black
@@ -980,12 +1013,16 @@ fun AddGroupMemoryDialog(
@Composable @Composable
fun GroupVisibilityDialog( fun GroupVisibilityDialog(
onDismiss: () -> Unit viewModel: GroupChatInfoViewModel,
onDismiss: () -> Unit,
onConfirmPrivate: (Boolean) -> Unit
) { ) {
val AppColors = LocalAppTheme.current val AppColors = LocalAppTheme.current
var isPrivate by remember { mutableStateOf(false) } val currentTrtcType = viewModel.groupInfo?.trtcType ?: "Public"
val balance = 482 val isPrivateFeePaid = viewModel.groupInfo?.privateFeePaid == true
val unlockCost = 500 var isPrivate by remember { mutableStateOf(currentTrtcType == "Private") }
val balance = viewModel.pointsBalance ?: 0
val unlockCost = viewModel.privateGroupCost ?: 0
Column( Column(
modifier = Modifier modifier = Modifier
@@ -1040,14 +1077,15 @@ fun GroupVisibilityDialog(
VisibilityOptionItem( VisibilityOptionItem(
title = stringResource(R.string.group_chat_info_private_group), title = stringResource(R.string.group_chat_info_private_group),
desc = stringResource(R.string.group_chat_info_private_group_desc), desc = stringResource(R.string.group_chat_info_private_group_desc),
badge = stringResource(R.string.group_chat_info_private_group_cost), badge = if (!isPrivateFeePaid && unlockCost > 0) "${unlockCost}派币" else null,
selected = isPrivate, selected = isPrivate,
onClick = { isPrivate = true } onClick = { isPrivate = true }
) )
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
// 余额与费用 // 余额与费用(仅未解锁时显示)
if (!isPrivateFeePaid) {
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
@@ -1070,6 +1108,7 @@ fun GroupVisibilityDialog(
} }
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
}
// 完成按钮 // 完成按钮
Box( Box(
@@ -1086,7 +1125,9 @@ fun GroupVisibilityDialog(
) )
) )
) )
.noRippleClickable { onDismiss() }, .noRippleClickable {
onConfirmPrivate(isPrivate)
},
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
Text( Text(
@@ -1095,6 +1136,8 @@ fun GroupVisibilityDialog(
) )
} }
// 仅未解锁时显示充值提示
if (!isPrivateFeePaid) {
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
Text( Text(
text = stringResource(R.string.group_chat_info_recharge_hint), text = stringResource(R.string.group_chat_info_recharge_hint),
@@ -1102,6 +1145,7 @@ fun GroupVisibilityDialog(
textAlign = TextAlign.Center textAlign = TextAlign.Center
) )
} }
}
} }
@Composable @Composable

View File

@@ -9,15 +9,17 @@ import androidx.lifecycle.viewModelScope
import com.aiosman.ravenow.AppStore import com.aiosman.ravenow.AppStore
import com.aiosman.ravenow.ChatState import com.aiosman.ravenow.ChatState
import com.aiosman.ravenow.data.api.ApiClient import com.aiosman.ravenow.data.api.ApiClient
import com.aiosman.ravenow.data.api.AgentRule import com.aiosman.ravenow.data.RoomService
import com.aiosman.ravenow.data.api.AgentRuleQuota import com.aiosman.ravenow.data.RoomServiceImpl
import com.aiosman.ravenow.data.api.CreateAgentRuleRequestBody
import com.aiosman.ravenow.data.api.UpdateAgentRuleRequestBody
import com.aiosman.ravenow.data.parseErrorResponse import com.aiosman.ravenow.data.parseErrorResponse
import com.aiosman.ravenow.data.PointService
import com.aiosman.ravenow.entity.RoomRuleEntity
import com.aiosman.ravenow.entity.RoomRuleQuotaEntity
import com.aiosman.ravenow.entity.ChatNotification import com.aiosman.ravenow.entity.ChatNotification
import com.aiosman.ravenow.entity.GroupInfo import com.aiosman.ravenow.entity.GroupInfo
import com.aiosman.ravenow.entity.GroupMember import com.aiosman.ravenow.entity.GroupMember
import com.aiosman.ravenow.ui.index.tabs.profile.MyProfileViewModel import com.aiosman.ravenow.ui.index.tabs.profile.MyProfileViewModel
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class GroupChatInfoViewModel( class GroupChatInfoViewModel(
@@ -34,30 +36,26 @@ class GroupChatInfoViewModel(
val notificationStrategy get() = chatNotification?.strategy ?: "default" val notificationStrategy get() = chatNotification?.strategy ?: "default"
// 记忆管理相关状态 // 记忆管理相关状态
var memoryQuota by mutableStateOf<AgentRuleQuota?>(null) var memoryQuota by mutableStateOf<RoomRuleQuotaEntity?>(null)
var memoryList by mutableStateOf<List<AgentRule>>(emptyList()) var memoryList by mutableStateOf<List<RoomRuleEntity>>(emptyList())
var isLoadingMemory by mutableStateOf(false) var isLoadingMemory by mutableStateOf(false)
var memoryError by mutableStateOf<String?>(null) var memoryError by mutableStateOf<String?>(null)
var promptOpenId by mutableStateOf<String?>(null)
// 房间规则服务
private val roomService: RoomService = RoomServiceImpl()
// 群可见性相关状态
var privateGroupCost by mutableStateOf<Int?>(null)
var pointsBalance by mutableStateOf<Int?>(null)
var isLoadingVisibility by mutableStateOf(false)
var isUpdatingVisibility by mutableStateOf(false)
// 群记忆相关状态
var addMemoryCost by mutableStateOf<Int?>(null)
init { init {
loadGroupInfo() loadGroupInfo()
loadPromptOpenId() loadVisibilityInfo()
} loadMemoryCost()
/**
* 获取群聊中智能体的 OpenID
*/
private fun loadPromptOpenId() {
viewModelScope.launch {
try {
val response = ApiClient.api.createGroupChatAi(trtcGroupId = groupId)
val groupChatResponse = response.body()?.data
val prompts = groupChatResponse?.prompts
promptOpenId = prompts?.firstOrNull()?.openId
} catch (e: Exception) {
Log.e("GroupChatInfoViewModel", "获取智能体OpenID失败: ${e.message}", e)
}
}
} }
suspend fun updateNotificationStrategy(strategy: String) { suspend fun updateNotificationStrategy(strategy: String) {
val result = ChatState.updateChatNotification(groupId.hashCode(), strategy) val result = ChatState.updateChatNotification(groupId.hashCode(), strategy)
@@ -93,7 +91,9 @@ class GroupChatInfoViewModel(
"${ApiClient.BASE_API_URL+"/outside"}${it.avatar}"+"?token="+"${AppStore.token}" "${ApiClient.BASE_API_URL+"/outside"}${it.avatar}"+"?token="+"${AppStore.token}"
}, },
memberCount = room.userCount, memberCount = room.userCount,
isCreator = room.creator.userId == MyProfileViewModel.profile?.id.toString() isCreator = room.creator.userId == MyProfileViewModel.profile?.id.toString(),
trtcType = it.trtcType ?: "Public",
privateFeePaid = it.privateFeePaid ?: false
) )
} }
@@ -106,57 +106,27 @@ class GroupChatInfoViewModel(
} }
/** /**
* 添加群记忆 * 添加群记忆(房间规则)
* @param memoryText 记忆内容 * @param memoryText 记忆内容
* @param promptOpenId 智能体的 OpenID可选如果不提供则从群聊信息中获取
*/ */
fun addGroupMemory(memoryText: String, promptOpenId: String? = null) { fun addGroupMemory(memoryText: String) {
viewModelScope.launch { viewModelScope.launch {
try { try {
isAddingMemory = true isAddingMemory = true
addMemoryError = null addMemoryError = null
addMemorySuccess = false addMemorySuccess = false
// 如果没有提供 promptOpenId需要先获取群聊的智能体信息 // 使用房间规则接口创建群记忆
val openId = promptOpenId ?: run { roomService.createRoomRule(
// 通过 createGroupChatAi 接口获取群聊详细信息(包含 prompts
val response = ApiClient.api.createGroupChatAi(trtcGroupId = groupId)
val groupChatResponse = response.body()?.data
val prompts = groupChatResponse?.prompts
if (prompts.isNullOrEmpty()) {
throw Exception("群聊中没有找到智能体,无法添加记忆")
}
// 使用第一个智能体的 openId
prompts.firstOrNull()?.openId
?: throw Exception("无法获取智能体信息")
}
if (openId.isBlank()) {
throw Exception("智能体ID不能为空")
}
// 创建智能体规则(群记忆)
val requestBody = CreateAgentRuleRequestBody(
rule = memoryText, rule = memoryText,
openId = openId trtcId = groupId
) )
val response = ApiClient.api.createAgentRule(requestBody)
if (response.isSuccessful) {
addMemorySuccess = true addMemorySuccess = true
Log.d("GroupChatInfoViewModel", "群记忆添加成功") Log.d("GroupChatInfoViewModel", "群记忆添加成功")
// 刷新记忆列表和配额 // 刷新记忆列表和配额
loadMemoryQuota(openId) loadMemoryQuota()
loadMemoryList(openId) loadMemoryList()
} else {
val errorResponse = parseErrorResponse(response.errorBody())
val errorMessage = errorResponse?.toServiceException()?.message
?: "添加群记忆失败: ${response.code()}"
throw Exception(errorMessage)
}
} catch (e: Exception) { } catch (e: Exception) {
addMemoryError = e.message ?: "添加群记忆失败" addMemoryError = e.message ?: "添加群记忆失败"
Log.e("GroupChatInfoViewModel", "添加群记忆失败: ${e.message}", e) Log.e("GroupChatInfoViewModel", "添加群记忆失败: ${e.message}", e)
@@ -167,38 +137,16 @@ class GroupChatInfoViewModel(
} }
/** /**
* 获取记忆配额信息 * 获取记忆配额信息(房间规则配额)
*/ */
fun loadMemoryQuota(openId: String? = null) { fun loadMemoryQuota() {
viewModelScope.launch { viewModelScope.launch {
try { try {
isLoadingMemory = true isLoadingMemory = true
memoryError = null memoryError = null
val targetOpenId = openId ?: promptOpenId // 使用房间规则接口获取配额
if (targetOpenId.isNullOrBlank()) { memoryQuota = roomService.getRoomRuleQuota(trtcId = groupId)
// 如果还没有获取到 openId先获取
val response = ApiClient.api.createGroupChatAi(trtcGroupId = groupId)
val groupChatResponse = response.body()?.data
val prompts = groupChatResponse?.prompts
val fetchedOpenId = prompts?.firstOrNull()?.openId
?: throw Exception("无法获取智能体信息")
promptOpenId = fetchedOpenId
val quotaResponse = ApiClient.api.getAgentRuleQuota(fetchedOpenId)
if (quotaResponse.isSuccessful) {
memoryQuota = quotaResponse.body()?.data
} else {
throw Exception("获取配额信息失败: ${quotaResponse.code()}")
}
} else {
val quotaResponse = ApiClient.api.getAgentRuleQuota(targetOpenId)
if (quotaResponse.isSuccessful) {
memoryQuota = quotaResponse.body()?.data
} else {
throw Exception("获取配额信息失败: ${quotaResponse.code()}")
}
}
} catch (e: Exception) { } catch (e: Exception) {
memoryError = e.message ?: "获取配额信息失败" memoryError = e.message ?: "获取配额信息失败"
Log.e("GroupChatInfoViewModel", "获取配额信息失败: ${e.message}", e) Log.e("GroupChatInfoViewModel", "获取配额信息失败: ${e.message}", e)
@@ -209,38 +157,21 @@ class GroupChatInfoViewModel(
} }
/** /**
* 获取记忆列表 * 获取记忆列表(房间规则列表)
*/ */
fun loadMemoryList(openId: String? = null, page: Int = 1, pageSize: Int = 20) { fun loadMemoryList(page: Int = 1, pageSize: Int = 20) {
viewModelScope.launch { viewModelScope.launch {
try { try {
isLoadingMemory = true isLoadingMemory = true
memoryError = null memoryError = null
val targetOpenId = openId ?: promptOpenId // 使用房间规则接口获取列表
if (targetOpenId.isNullOrBlank()) { val result = roomService.getRoomRuleList(
// 如果还没有获取到 openId先获取 trtcId = groupId,
val response = ApiClient.api.createGroupChatAi(trtcGroupId = groupId) page = page,
val groupChatResponse = response.body()?.data pageSize = pageSize
val prompts = groupChatResponse?.prompts )
val fetchedOpenId = prompts?.firstOrNull()?.openId memoryList = result.list
?: throw Exception("无法获取智能体信息")
promptOpenId = fetchedOpenId
val listResponse = ApiClient.api.getAgentRuleList(fetchedOpenId, page = page, pageSize = pageSize)
if (listResponse.isSuccessful) {
memoryList = listResponse.body()?.data?.list ?: emptyList()
} else {
throw Exception("获取记忆列表失败: ${listResponse.code()}")
}
} else {
val listResponse = ApiClient.api.getAgentRuleList(targetOpenId, page = page, pageSize = pageSize)
if (listResponse.isSuccessful) {
memoryList = listResponse.body()?.data?.list ?: emptyList()
} else {
throw Exception("获取记忆列表失败: ${listResponse.code()}")
}
}
} catch (e: Exception) { } catch (e: Exception) {
memoryError = e.message ?: "获取记忆列表失败" memoryError = e.message ?: "获取记忆列表失败"
Log.e("GroupChatInfoViewModel", "获取记忆列表失败: ${e.message}", e) Log.e("GroupChatInfoViewModel", "获取记忆列表失败: ${e.message}", e)
@@ -251,7 +182,7 @@ class GroupChatInfoViewModel(
} }
/** /**
* 删除记忆 * 删除记忆(房间规则)
*/ */
fun deleteMemory(ruleId: Int) { fun deleteMemory(ruleId: Int) {
viewModelScope.launch { viewModelScope.launch {
@@ -259,19 +190,12 @@ class GroupChatInfoViewModel(
isLoadingMemory = true isLoadingMemory = true
memoryError = null memoryError = null
val response = ApiClient.api.deleteAgentRule(ruleId) // 使用房间规则接口删除
if (response.isSuccessful) { roomService.deleteRoomRule(ruleId)
// 刷新记忆列表和配额 // 刷新记忆列表和配额
promptOpenId?.let { openId -> loadMemoryQuota()
loadMemoryQuota(openId) loadMemoryList()
loadMemoryList(openId)
}
} else {
val errorResponse = parseErrorResponse(response.errorBody())
val errorMessage = errorResponse?.toServiceException()?.message
?: "删除记忆失败: ${response.code()}"
throw Exception(errorMessage)
}
} catch (e: Exception) { } catch (e: Exception) {
memoryError = e.message ?: "删除记忆失败" memoryError = e.message ?: "删除记忆失败"
Log.e("GroupChatInfoViewModel", "删除记忆失败: ${e.message}", e) Log.e("GroupChatInfoViewModel", "删除记忆失败: ${e.message}", e)
@@ -282,34 +206,23 @@ class GroupChatInfoViewModel(
} }
/** /**
* 更新记忆 * 更新记忆(房间规则)
*/ */
fun updateMemory(ruleId: Int, newRuleText: String, targetOpenId: String? = null) { fun updateMemory(ruleId: Int, newRuleText: String) {
viewModelScope.launch { viewModelScope.launch {
try { try {
isLoadingMemory = true isLoadingMemory = true
memoryError = null memoryError = null
val openId = targetOpenId ?: promptOpenId // 使用房间规则接口更新
?: throw Exception("无法获取智能体ID") roomService.updateRoomRule(
val requestBody = UpdateAgentRuleRequestBody(
id = ruleId, id = ruleId,
rule = newRuleText, rule = newRuleText
openId = openId
) )
val response = ApiClient.api.updateAgentRule(requestBody)
if (response.isSuccessful) {
// 刷新记忆列表和配额 // 刷新记忆列表和配额
loadMemoryQuota(openId) loadMemoryQuota()
loadMemoryList(openId) loadMemoryList()
} else {
val errorResponse = parseErrorResponse(response.errorBody())
val errorMessage = errorResponse?.toServiceException()?.message
?: "更新记忆失败: ${response.code()}"
throw Exception(errorMessage)
}
} catch (e: Exception) { } catch (e: Exception) {
memoryError = e.message ?: "更新记忆失败" memoryError = e.message ?: "更新记忆失败"
Log.e("GroupChatInfoViewModel", "更新记忆失败: ${e.message}", e) Log.e("GroupChatInfoViewModel", "更新记忆失败: ${e.message}", e)
@@ -318,4 +231,77 @@ class GroupChatInfoViewModel(
} }
} }
} }
/**
* 加载群可见性相关信息(价格和积分余额)
*/
fun loadVisibilityInfo() {
viewModelScope.launch {
try {
isLoadingVisibility = true
// 获取积分规则中的私密群组价格
PointService.refreshPointsRules()
val rules = PointService.pointsRules.first()
val roomPrivateRule = rules?.sub?.get(PointService.PointsRuleKey.ROOM_PRIVATE)
privateGroupCost = when (roomPrivateRule) {
is PointService.RuleAmount.Fixed -> roomPrivateRule.value
is PointService.RuleAmount.Range -> roomPrivateRule.min
null -> null
}
// 获取积分余额
PointService.refreshMyPointsBalance(includeStatistics = false)
val balance = PointService.pointsBalance.first()
pointsBalance = balance?.balance
} catch (e: Exception) {
Log.e("GroupChatInfoViewModel", "加载可见性信息失败: ${e.message}", e)
} finally {
isLoadingVisibility = false
}
}
}
/**
* 更新群可见性
* @param isPrivate 是否设置为私密群组
*/
fun updateVisibility(isPrivate: Boolean) {
viewModelScope.launch {
try {
isUpdatingVisibility = true
// TODO: 实现更新房间可见性的接口调用
// 暂时留空
// 更新成功后刷新群信息和积分余额
loadGroupInfo()
loadVisibilityInfo()
} catch (e: Exception) {
Log.e("GroupChatInfoViewModel", "更新可见性失败: ${e.message}", e)
} finally {
isUpdatingVisibility = false
}
}
}
/**
* 加载添加群记忆的价格
*/
fun loadMemoryCost() {
viewModelScope.launch {
try {
// 获取积分规则中的房间记忆价格(群聊记忆,区别于 Agent 记忆)
PointService.refreshPointsRules()
val rules = PointService.pointsRules.first()
val roomMemoryRule = rules?.sub?.get(PointService.PointsRuleKey.SPEND_ROOM_MEMORY)
addMemoryCost = when (roomMemoryRule) {
is PointService.RuleAmount.Fixed -> roomMemoryRule.value
is PointService.RuleAmount.Range -> roomMemoryRule.min
else -> null
}
} catch (e: Exception) {
Log.e("GroupChatInfoViewModel", "加载记忆价格失败: ${e.message}", e)
}
}
}
} }

View File

@@ -39,6 +39,8 @@ import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import android.widget.Toast import android.widget.Toast
import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Brush
import com.aiosman.ravenow.ui.composables.CustomAsyncImage
import androidx.compose.ui.layout.ContentScale
@Composable @Composable
fun GroupMemoryManageContent( fun GroupMemoryManageContent(
@@ -48,9 +50,6 @@ fun GroupMemoryManageContent(
onDismiss: () -> Unit = {} onDismiss: () -> Unit = {}
) { ) {
val AppColors = LocalAppTheme.current val AppColors = LocalAppTheme.current
val configuration = LocalConfiguration.current
val screenHeight = configuration.screenHeightDp.dp
val sheetHeight = screenHeight * 0.95f
val context = LocalContext.current val context = LocalContext.current
// 编辑记忆的状态 - 存储正在编辑的记忆ID // 编辑记忆的状态 - 存储正在编辑的记忆ID
@@ -68,8 +67,7 @@ fun GroupMemoryManageContent(
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxSize()
.height(sheetHeight)
.background(Color(0xFFFAF9FB)) .background(Color(0xFFFAF9FB))
) { ) {
// 顶部栏:返回按钮 + 标题 + 加号按钮 // 顶部栏:返回按钮 + 标题 + 加号按钮
@@ -257,7 +255,7 @@ fun GroupMemoryManageContent(
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun EditGroupMemoryDialog( fun EditGroupMemoryDialog(
memory: com.aiosman.ravenow.data.api.AgentRule, memory: com.aiosman.ravenow.entity.RoomRuleEntity,
viewModel: GroupChatInfoViewModel, viewModel: GroupChatInfoViewModel,
onDismiss: () -> Unit, onDismiss: () -> Unit,
onUpdateMemory: (String) -> Unit onUpdateMemory: (String) -> Unit
@@ -403,7 +401,7 @@ fun EditGroupMemoryDialog(
*/ */
@Composable @Composable
fun MemoryItem( fun MemoryItem(
memory: com.aiosman.ravenow.data.api.AgentRule, memory: com.aiosman.ravenow.entity.RoomRuleEntity,
isEditing: Boolean = false, isEditing: Boolean = false,
onEdit: () -> Unit = {}, onEdit: () -> Unit = {},
onCancel: () -> Unit = {}, onCancel: () -> Unit = {},
@@ -579,20 +577,44 @@ fun MemoryItem(
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
// 底部行:日期 + 编辑删除按钮 // 底部行:创建者信息 + 编辑删除按钮
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween, horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.Bottom verticalAlignment = Alignment.CenterVertically
) { ) {
// 日期文本 - 左侧 // 创建者信息 - 左侧:头像 + 用户名 · 时间
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
// 圆形头像
val avatarUrl = memory.creator?.avatarDirectUrl?.takeIf { it.isNotBlank() }
CustomAsyncImage(
imageUrl = avatarUrl,
contentDescription = "创建者头像",
modifier = Modifier
.size(20.dp)
.clip(CircleShape),
defaultRes = R.drawable.default_avatar,
placeholderRes = R.drawable.default_avatar,
errorRes = R.drawable.default_avatar,
contentScale = ContentScale.Crop
)
// 用户名 · 时间
Text( Text(
text = formattedDate, text = buildString {
append(memory.creator?.nickname ?: "未知用户")
append(" · ")
append(formattedDate)
},
style = TextStyle( style = TextStyle(
color = Color(0x993C3C43), color = Color(0x993C3C43),
fontSize = 11.sp fontSize = 11.sp
) )
) )
}
// 编辑和删除图标 - 右侧 // 编辑和删除图标 - 右侧
Row( Row(