Merge pull request #76 from Kevinlinpr/atm2
Refactor: 群记忆重构为房间规则并新增私密群组付费功能
This commit is contained in:
@@ -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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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,
|
||||||
)
|
)
|
||||||
@@ -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
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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,12 +1136,15 @@ 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),
|
||||||
style = TextStyle(fontSize = 12.sp, color = Color(0x4D3C3C43)),
|
style = TextStyle(fontSize = 12.sp, color = Color(0x4D3C3C43)),
|
||||||
textAlign = TextAlign.Center
|
textAlign = TextAlign.Center
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
addMemorySuccess = true
|
||||||
|
Log.d("GroupChatInfoViewModel", "群记忆添加成功")
|
||||||
if (response.isSuccessful) {
|
// 刷新记忆列表和配额
|
||||||
addMemorySuccess = true
|
loadMemoryQuota()
|
||||||
Log.d("GroupChatInfoViewModel", "群记忆添加成功")
|
loadMemoryList()
|
||||||
// 刷新记忆列表和配额
|
|
||||||
loadMemoryQuota(openId)
|
|
||||||
loadMemoryList(openId)
|
|
||||||
} 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(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)
|
||||||
@@ -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()
|
||||||
// 刷新记忆列表和配额
|
loadMemoryList()
|
||||||
loadMemoryQuota(openId)
|
|
||||||
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)
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
) {
|
) {
|
||||||
// 日期文本 - 左侧
|
// 创建者信息 - 左侧:头像 + 用户名 · 时间
|
||||||
Text(
|
Row(
|
||||||
text = formattedDate,
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
style = TextStyle(
|
verticalAlignment = Alignment.CenterVertically
|
||||||
color = Color(0x993C3C43),
|
) {
|
||||||
fontSize = 11.sp
|
// 圆形头像
|
||||||
|
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 = buildString {
|
||||||
|
append(memory.creator?.nickname ?: "未知用户")
|
||||||
|
append(" · ")
|
||||||
|
append(formattedDate)
|
||||||
|
},
|
||||||
|
style = TextStyle(
|
||||||
|
color = Color(0x993C3C43),
|
||||||
|
fontSize = 11.sp
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// 编辑和删除图标 - 右侧
|
// 编辑和删除图标 - 右侧
|
||||||
Row(
|
Row(
|
||||||
|
|||||||
@@ -0,0 +1,806 @@
|
|||||||
|
package com.aiosman.ravenow.ui.profile
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Brush
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
|
import androidx.compose.ui.graphics.SolidColor
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.text.BasicTextField
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.ModalBottomSheet
|
||||||
|
import androidx.compose.material3.rememberModalBottomSheetState
|
||||||
|
import com.aiosman.ravenow.LocalAppTheme
|
||||||
|
import com.aiosman.ravenow.R
|
||||||
|
import com.aiosman.ravenow.data.AgentRuleEntity
|
||||||
|
import com.aiosman.ravenow.entity.AccountProfileEntity
|
||||||
|
import com.aiosman.ravenow.ui.composables.CustomAsyncImage
|
||||||
|
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AgentMemoryManageContent(
|
||||||
|
openId: String,
|
||||||
|
profile: AccountProfileEntity?,
|
||||||
|
viewModel: AgentMemoryManageViewModel,
|
||||||
|
onAddMemoryClick: () -> Unit = {},
|
||||||
|
onDismiss: () -> Unit = {}
|
||||||
|
) {
|
||||||
|
val AppColors = LocalAppTheme.current
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
// 编辑记忆的状态 - 存储正在编辑的记忆ID
|
||||||
|
var editingMemoryId by remember { mutableStateOf<Int?>(null) }
|
||||||
|
|
||||||
|
// 加载配额和列表数据
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
viewModel.loadMemoryQuota()
|
||||||
|
viewModel.loadMemoryList()
|
||||||
|
}
|
||||||
|
|
||||||
|
val quota = viewModel.memoryQuota
|
||||||
|
val memoryList = viewModel.memoryList
|
||||||
|
val isLoading = viewModel.isLoadingMemory
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(Color(0xFFFAF9FB))
|
||||||
|
) {
|
||||||
|
// 顶部栏:返回按钮 + 标题 + 加号按钮
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(44.dp)
|
||||||
|
.padding(horizontal = 16.dp)
|
||||||
|
) {
|
||||||
|
// 中间标题 - 绝对居中,不受其他组件影响
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.group_chat_info_memory_manage),
|
||||||
|
style = TextStyle(
|
||||||
|
color = Color.Black,
|
||||||
|
fontSize = 17.sp,
|
||||||
|
fontWeight = FontWeight.SemiBold
|
||||||
|
),
|
||||||
|
modifier = Modifier.align(Alignment.Center),
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
|
||||||
|
// 左侧返回按钮
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.CenterStart)
|
||||||
|
.clip(RoundedCornerShape(296.dp))
|
||||||
|
.background(Color.White)
|
||||||
|
.noRippleClickable { onDismiss() }
|
||||||
|
.padding(horizontal = 12.dp, vertical = 8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(R.drawable.rider_pro_back_icon),
|
||||||
|
contentDescription = "返回",
|
||||||
|
modifier = Modifier.size(16.dp),
|
||||||
|
colorFilter = ColorFilter.tint(Color.Black)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "返回",
|
||||||
|
style = TextStyle(
|
||||||
|
color = Color.Black,
|
||||||
|
fontSize = 15.sp,
|
||||||
|
fontWeight = FontWeight.Normal
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 右侧圆形加号按钮
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.CenterEnd)
|
||||||
|
.size(32.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.background(Color.White)
|
||||||
|
.noRippleClickable { onAddMemoryClick() },
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "+",
|
||||||
|
style = TextStyle(
|
||||||
|
color = Color.Black,
|
||||||
|
fontSize = 20.sp,
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 浅黄色提示栏 - 显示真实的配额数据
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(Color(0xFFFBF8EF))
|
||||||
|
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Row(horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Text("已付费:", style = TextStyle(color = Color(0x993C3C43), fontSize = 13.sp))
|
||||||
|
Spacer(Modifier.width(3.dp))
|
||||||
|
Text(
|
||||||
|
"${quota?.purchasedCount ?: 0}",
|
||||||
|
style = TextStyle(color = Color(0xFFFF8D28), fontSize = 13.sp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Text("已使用:", style = TextStyle(color = Color(0x993C3C43), fontSize = 13.sp))
|
||||||
|
Spacer(Modifier.width(3.dp))
|
||||||
|
Text(
|
||||||
|
"${quota?.currentCount ?: 0}",
|
||||||
|
style = TextStyle(color = Color(0xFFFF8D28), fontSize = 13.sp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Text("可用上限:", style = TextStyle(color = Color(0x993C3C43), fontSize = 13.sp))
|
||||||
|
Spacer(Modifier.width(3.dp))
|
||||||
|
Text(
|
||||||
|
"${quota?.totalMaxCount ?: 50}",
|
||||||
|
style = TextStyle(color = Color(0xFFFF8D28), fontSize = 13.sp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记忆列表或空状态
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.weight(1f)
|
||||||
|
) {
|
||||||
|
if (isLoading) {
|
||||||
|
// 加载中状态
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
modifier = Modifier.size(40.dp),
|
||||||
|
color = Color(0xFFFF8D28)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else if (memoryList.isNotEmpty()) {
|
||||||
|
// 显示记忆列表
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 12.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
|
items(memoryList) { memory ->
|
||||||
|
AgentMemoryItem(
|
||||||
|
memory = memory,
|
||||||
|
isEditing = editingMemoryId == memory.id,
|
||||||
|
onEdit = {
|
||||||
|
editingMemoryId = memory.id
|
||||||
|
},
|
||||||
|
onCancel = {
|
||||||
|
editingMemoryId = null
|
||||||
|
},
|
||||||
|
onSave = { newText ->
|
||||||
|
viewModel.updateMemory(memory.id, newText)
|
||||||
|
editingMemoryId = null
|
||||||
|
},
|
||||||
|
onDelete = {
|
||||||
|
viewModel.deleteMemory(memory.id)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(vertical = 60.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(id = R.mipmap.group),
|
||||||
|
contentDescription = "暂无记忆",
|
||||||
|
modifier = Modifier
|
||||||
|
.height(150.dp).width(180.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(Modifier.height(10.dp))
|
||||||
|
Text(
|
||||||
|
text = "暂无记忆",
|
||||||
|
style = TextStyle(color = Color.Black, fontSize = 16.sp, fontWeight = FontWeight.SemiBold)
|
||||||
|
)
|
||||||
|
Spacer(Modifier.height(6.dp))
|
||||||
|
Text(
|
||||||
|
text = "点击上方按钮添加 Agent 记忆",
|
||||||
|
style = TextStyle(color = Color.Black, fontSize = 14.sp, fontWeight = FontWeight.Normal)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Agent 记忆项组件(不显示创建者信息)
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun AgentMemoryItem(
|
||||||
|
memory: AgentRuleEntity,
|
||||||
|
isEditing: Boolean = false,
|
||||||
|
onEdit: () -> Unit = {},
|
||||||
|
onCancel: () -> Unit = {},
|
||||||
|
onSave: (String) -> Unit = {},
|
||||||
|
onDelete: () -> Unit
|
||||||
|
) {
|
||||||
|
val AppColors = LocalAppTheme.current
|
||||||
|
val context = LocalContext.current
|
||||||
|
var memoryText by remember { mutableStateOf(memory.rule) }
|
||||||
|
val maxLength = 500
|
||||||
|
|
||||||
|
// 渐变边框颜色
|
||||||
|
val gradientColors = listOf(
|
||||||
|
Color(0xFF7C45ED),
|
||||||
|
Color(0xFF7C57EE),
|
||||||
|
Color(0xFF7BD8F8)
|
||||||
|
)
|
||||||
|
val gradientBrush = Brush.horizontalGradient(colors = gradientColors)
|
||||||
|
|
||||||
|
// 当进入编辑模式时,重置文本
|
||||||
|
LaunchedEffect(isEditing) {
|
||||||
|
if (isEditing) {
|
||||||
|
memoryText = memory.rule
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isEditing) {
|
||||||
|
// 编辑模式:显示编辑界面
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clip(RoundedCornerShape(16.dp))
|
||||||
|
.background(Color.White)
|
||||||
|
.padding(horizontal = 12.dp, vertical = 16.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
|
// 文本输入框 - 带渐变边框
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(88.dp)
|
||||||
|
) {
|
||||||
|
// 渐变边框层
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.clip(RoundedCornerShape(16.dp))
|
||||||
|
.background(brush = gradientBrush)
|
||||||
|
)
|
||||||
|
|
||||||
|
// 内容层 - 白色背景,通过padding形成边框效果
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(1.dp)
|
||||||
|
.clip(RoundedCornerShape(15.dp))
|
||||||
|
.background(Color.White)
|
||||||
|
.padding(12.dp),
|
||||||
|
contentAlignment = Alignment.TopStart
|
||||||
|
) {
|
||||||
|
BasicTextField(
|
||||||
|
value = memoryText,
|
||||||
|
onValueChange = { newText ->
|
||||||
|
if (newText.length <= maxLength) {
|
||||||
|
memoryText = newText
|
||||||
|
}
|
||||||
|
},
|
||||||
|
cursorBrush = SolidColor(Color.Black),
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
textStyle = TextStyle(
|
||||||
|
fontSize = 13.sp,
|
||||||
|
color = Color.Black,
|
||||||
|
lineHeight = 18.sp
|
||||||
|
),
|
||||||
|
decorationBox = { innerTextField ->
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
contentAlignment = Alignment.TopStart
|
||||||
|
) {
|
||||||
|
innerTextField()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按钮行:取消和保存
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
|
// 取消按钮
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.height(34.dp)
|
||||||
|
.clip(RoundedCornerShape(653.8.dp))
|
||||||
|
.background(Color(0x147C7480))
|
||||||
|
.noRippleClickable { onCancel() },
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "取消",
|
||||||
|
style = TextStyle(
|
||||||
|
fontSize = 15.sp,
|
||||||
|
color = Color.Black
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存按钮
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.height(34.dp)
|
||||||
|
.clip(RoundedCornerShape(653.8.dp))
|
||||||
|
.background(Color(0xFF110C13))
|
||||||
|
.noRippleClickable {
|
||||||
|
if (memoryText.isNotBlank() && memoryText != memory.rule) {
|
||||||
|
onSave(memoryText)
|
||||||
|
Toast.makeText(context, "记忆更新成功", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "保存",
|
||||||
|
style = TextStyle(
|
||||||
|
fontSize = 15.sp,
|
||||||
|
color = Color.White
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 显示模式:显示记忆内容(不显示创建者信息)
|
||||||
|
// 格式化日期:从 "2025-10-20T10:30:00Z" 格式转换为 "2025年10月20日"
|
||||||
|
val formattedDate = try {
|
||||||
|
if (memory.createdAt.length >= 10) {
|
||||||
|
val dateStr = memory.createdAt.substring(0, 10)
|
||||||
|
val parts = dateStr.split("-")
|
||||||
|
if (parts.size == 3) {
|
||||||
|
"${parts[0]}年${parts[1].toInt()}月${parts[2].toInt()}日"
|
||||||
|
} else {
|
||||||
|
dateStr
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
memory.createdAt
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
if (memory.createdAt.length >= 10) memory.createdAt.substring(0, 10) else memory.createdAt
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clip(RoundedCornerShape(16.dp))
|
||||||
|
.background(Color.White)
|
||||||
|
.padding(horizontal = 12.dp, vertical = 16.dp)
|
||||||
|
) {
|
||||||
|
// 主文本 - 顶部
|
||||||
|
Text(
|
||||||
|
text = memory.rule,
|
||||||
|
style = TextStyle(
|
||||||
|
color = Color.Black,
|
||||||
|
fontSize = 13.sp,
|
||||||
|
lineHeight = 18.sp
|
||||||
|
),
|
||||||
|
maxLines = 3,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
// 底部行:时间 + 编辑删除按钮(不显示创建者信息)
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
// 时间信息 - 左侧
|
||||||
|
Text(
|
||||||
|
text = formattedDate,
|
||||||
|
style = TextStyle(
|
||||||
|
color = Color(0x993C3C43),
|
||||||
|
fontSize = 11.sp
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// 编辑和删除图标 - 右侧
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
// 编辑图标
|
||||||
|
Image(
|
||||||
|
painter = painterResource(R.mipmap.icons_infor_edit),
|
||||||
|
contentDescription = "编辑",
|
||||||
|
modifier = Modifier
|
||||||
|
.size(20.dp)
|
||||||
|
.noRippleClickable { onEdit() },
|
||||||
|
colorFilter = ColorFilter.tint(Color.Black)
|
||||||
|
)
|
||||||
|
|
||||||
|
Image(
|
||||||
|
painter = painterResource(R.mipmap.iconsdelete),
|
||||||
|
contentDescription = "删除",
|
||||||
|
modifier = Modifier
|
||||||
|
.size(20.dp)
|
||||||
|
.noRippleClickable { onDelete() },
|
||||||
|
colorFilter = ColorFilter.tint(Color(0xFFEE2A33))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加 Agent 记忆对话框
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun AddAgentMemoryDialog(
|
||||||
|
profile: AccountProfileEntity?,
|
||||||
|
viewModel: AgentMemoryManageViewModel,
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
onRequestAddMemory: (String) -> Unit // 改为请求添加,而不是直接添加
|
||||||
|
) {
|
||||||
|
val AppColors = LocalAppTheme.current
|
||||||
|
val context = LocalContext.current
|
||||||
|
var memoryText by remember { mutableStateOf("") }
|
||||||
|
val maxLength = 500
|
||||||
|
|
||||||
|
// 监听添加记忆的结果
|
||||||
|
LaunchedEffect(viewModel.addMemorySuccess) {
|
||||||
|
if (viewModel.addMemorySuccess) {
|
||||||
|
Toast.makeText(context, context.getString(R.string.group_chat_info_memory_add_success), Toast.LENGTH_SHORT).show()
|
||||||
|
memoryText = "" // 清空输入框
|
||||||
|
onDismiss()
|
||||||
|
viewModel.addMemorySuccess = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(viewModel.addMemoryError) {
|
||||||
|
viewModel.addMemoryError?.let { error ->
|
||||||
|
Toast.makeText(context, error, Toast.LENGTH_SHORT).show()
|
||||||
|
viewModel.addMemoryError = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp)
|
||||||
|
.padding(bottom = 40.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
// 顶部标题栏
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(44.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
Spacer(modifier = Modifier.width(24.dp))
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.group_chat_info_add_memory),
|
||||||
|
style = TextStyle(
|
||||||
|
fontSize = 17.sp,
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
color = Color.Black
|
||||||
|
),
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
Image(
|
||||||
|
painter = painterResource(R.drawable.rider_pro_close),
|
||||||
|
contentDescription = "关闭",
|
||||||
|
modifier = Modifier
|
||||||
|
.size(24.dp)
|
||||||
|
.noRippleClickable { onDismiss() },
|
||||||
|
colorFilter = ColorFilter.tint(Color.Black)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(10.dp))
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
|
) {
|
||||||
|
// Agent 信息卡片
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(80.dp)
|
||||||
|
.clip(RoundedCornerShape(16.dp))
|
||||||
|
.background(Color.White)
|
||||||
|
.padding(horizontal = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
|
if (profile?.avatar?.isNotEmpty() == true) {
|
||||||
|
CustomAsyncImage(
|
||||||
|
imageUrl = profile.avatar,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(48.dp)
|
||||||
|
.clip(RoundedCornerShape(12.dp)),
|
||||||
|
contentDescription = "Agent 头像",
|
||||||
|
context = LocalContext.current
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(48.dp)
|
||||||
|
.clip(RoundedCornerShape(12.dp))
|
||||||
|
.background(AppColors.decentBackground),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = profile?.nickName?.firstOrNull()?.toString() ?: "",
|
||||||
|
style = TextStyle(
|
||||||
|
fontSize = 20.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = AppColors.text
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = profile?.nickName ?: "",
|
||||||
|
style = TextStyle(
|
||||||
|
fontSize = 15.sp,
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
color = Color.Black
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if (!profile?.bio.isNullOrEmpty()) {
|
||||||
|
Text(
|
||||||
|
text = profile?.bio ?: "",
|
||||||
|
style = TextStyle(
|
||||||
|
fontSize = 12.sp,
|
||||||
|
color = Color(0xFF3C3C43).copy(alpha = 0.6f)
|
||||||
|
),
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 输入框卡片
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clip(RoundedCornerShape(16.dp))
|
||||||
|
.background(Color.White)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(12.dp)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(84.dp),
|
||||||
|
contentAlignment = Alignment.TopStart
|
||||||
|
) {
|
||||||
|
BasicTextField(
|
||||||
|
value = memoryText,
|
||||||
|
onValueChange = { newText ->
|
||||||
|
if (newText.length <= maxLength) {
|
||||||
|
memoryText = newText
|
||||||
|
}
|
||||||
|
},
|
||||||
|
cursorBrush = SolidColor(Color.Black),
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
maxLines = 6,
|
||||||
|
textStyle = TextStyle(
|
||||||
|
fontSize = 15.sp,
|
||||||
|
color = Color.Black,
|
||||||
|
lineHeight = 20.sp
|
||||||
|
),
|
||||||
|
decorationBox = { innerTextField ->
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
contentAlignment = Alignment.TopStart
|
||||||
|
) {
|
||||||
|
innerTextField()
|
||||||
|
if (memoryText.isEmpty()) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.group_chat_info_memory_input_hint),
|
||||||
|
style = TextStyle(
|
||||||
|
fontSize = 15.sp,
|
||||||
|
color = Color.Black.copy(alpha = 0.3f)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.End
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "${memoryText.length}/$maxLength",
|
||||||
|
style = TextStyle(
|
||||||
|
fontSize = 12.sp,
|
||||||
|
color = Color(0xFF3C3C43).copy(alpha = 0.3f)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提示信息卡片
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clip(RoundedCornerShape(16.dp))
|
||||||
|
.background(Color(0xFFFBF8EF))
|
||||||
|
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "⭐",
|
||||||
|
fontSize = 13.sp
|
||||||
|
)
|
||||||
|
val memoryCost = viewModel.addMemoryCost
|
||||||
|
Text(
|
||||||
|
text = if (memoryCost != null && memoryCost > 0) {
|
||||||
|
"添加记忆需消耗 ${memoryCost}派币"
|
||||||
|
} else {
|
||||||
|
stringResource(R.string.group_chat_info_memory_cost)
|
||||||
|
},
|
||||||
|
style = TextStyle(
|
||||||
|
fontSize = 13.sp,
|
||||||
|
color = Color.Black
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "🤖",
|
||||||
|
fontSize = 13.sp
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.group_chat_info_memory_optimization),
|
||||||
|
style = TextStyle(
|
||||||
|
fontSize = 13.sp,
|
||||||
|
color = Color.Black
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "✏️",
|
||||||
|
fontSize = 13.sp
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.group_chat_info_memory_editable),
|
||||||
|
style = TextStyle(
|
||||||
|
fontSize = 13.sp,
|
||||||
|
color = Color.Black
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加记忆按钮
|
||||||
|
val isButtonEnabled = memoryText.isNotEmpty() && !viewModel.isAddingMemory
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(50.dp)
|
||||||
|
.clip(RoundedCornerShape(1000.dp))
|
||||||
|
.then(
|
||||||
|
if (isButtonEnabled) {
|
||||||
|
Modifier.background(
|
||||||
|
brush = Brush.horizontalGradient(
|
||||||
|
colors = listOf(
|
||||||
|
Color(0xFF7C45ED),
|
||||||
|
Color(0x997C57EE),
|
||||||
|
Color(0x887BD8F8)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Modifier.background(
|
||||||
|
brush = Brush.horizontalGradient(
|
||||||
|
colors = listOf(
|
||||||
|
Color(0x337C45ED),
|
||||||
|
Color(0x337C57EE),
|
||||||
|
Color(0x337BD8F8)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.noRippleClickable {
|
||||||
|
if (isButtonEnabled) {
|
||||||
|
onRequestAddMemory(memoryText)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
if (viewModel.isAddingMemory) {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
modifier = Modifier.size(20.dp),
|
||||||
|
color = Color.White
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.group_chat_info_add_memory),
|
||||||
|
style = TextStyle(
|
||||||
|
fontSize = 17.sp,
|
||||||
|
color = if (isButtonEnabled) Color.White else Color.White.copy(alpha = 0.6f)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,207 @@
|
|||||||
|
package com.aiosman.ravenow.ui.profile
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.aiosman.ravenow.data.AgentRuleService
|
||||||
|
import com.aiosman.ravenow.data.AgentRuleServiceImpl
|
||||||
|
import com.aiosman.ravenow.data.AgentRuleEntity
|
||||||
|
import com.aiosman.ravenow.data.AgentRuleQuotaEntity
|
||||||
|
import com.aiosman.ravenow.data.PointService
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class AgentMemoryManageViewModel(
|
||||||
|
private val openId: String
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
// 记忆管理相关状态
|
||||||
|
var memoryQuota by mutableStateOf<AgentRuleQuotaEntity?>(null)
|
||||||
|
var memoryList by mutableStateOf<List<AgentRuleEntity>>(emptyList())
|
||||||
|
var isLoadingMemory by mutableStateOf(false)
|
||||||
|
var memoryError by mutableStateOf<String?>(null)
|
||||||
|
|
||||||
|
// Agent 规则服务
|
||||||
|
private val agentRuleService: AgentRuleService = AgentRuleServiceImpl()
|
||||||
|
|
||||||
|
// 添加记忆相关状态
|
||||||
|
var isAddingMemory by mutableStateOf(false)
|
||||||
|
var addMemoryError by mutableStateOf<String?>(null)
|
||||||
|
var addMemorySuccess by mutableStateOf(false)
|
||||||
|
var addMemoryCost by mutableStateOf<Int?>(null)
|
||||||
|
var pointsBalance by mutableStateOf<Int?>(null)
|
||||||
|
|
||||||
|
init {
|
||||||
|
loadMemoryCost()
|
||||||
|
loadPointsBalance()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载积分余额
|
||||||
|
*/
|
||||||
|
fun loadPointsBalance() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
PointService.refreshMyPointsBalance(includeStatistics = false)
|
||||||
|
val balance = PointService.pointsBalance.first()
|
||||||
|
pointsBalance = balance?.balance
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("AgentMemoryManageViewModel", "加载积分余额失败: ${e.message}", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加 Agent 记忆
|
||||||
|
* @param memoryText 记忆内容
|
||||||
|
*/
|
||||||
|
fun addAgentMemory(memoryText: String) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
isAddingMemory = true
|
||||||
|
addMemoryError = null
|
||||||
|
addMemorySuccess = false
|
||||||
|
|
||||||
|
// 使用 Agent 规则接口创建记忆
|
||||||
|
agentRuleService.createAgentRuleByOpenId(
|
||||||
|
openId = openId,
|
||||||
|
rule = memoryText
|
||||||
|
)
|
||||||
|
|
||||||
|
addMemorySuccess = true
|
||||||
|
Log.d("AgentMemoryManageViewModel", "Agent 记忆添加成功")
|
||||||
|
// 刷新记忆列表和配额
|
||||||
|
loadMemoryQuota()
|
||||||
|
loadMemoryList()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
addMemoryError = e.message ?: "添加 Agent 记忆失败"
|
||||||
|
Log.e("AgentMemoryManageViewModel", "添加 Agent 记忆失败: ${e.message}", e)
|
||||||
|
} finally {
|
||||||
|
isAddingMemory = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取记忆配额信息(Agent 规则配额)
|
||||||
|
*/
|
||||||
|
fun loadMemoryQuota() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
isLoadingMemory = true
|
||||||
|
memoryError = null
|
||||||
|
|
||||||
|
// 使用 Agent 规则接口获取配额
|
||||||
|
memoryQuota = agentRuleService.getAgentRuleQuota(openId = openId)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
memoryError = e.message ?: "获取配额信息失败"
|
||||||
|
Log.e("AgentMemoryManageViewModel", "获取配额信息失败: ${e.message}", e)
|
||||||
|
} finally {
|
||||||
|
isLoadingMemory = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取记忆列表(Agent 规则列表)
|
||||||
|
*/
|
||||||
|
fun loadMemoryList(page: Int = 1, pageSize: Int = 20) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
isLoadingMemory = true
|
||||||
|
memoryError = null
|
||||||
|
|
||||||
|
// 使用 Agent 规则接口获取列表
|
||||||
|
val result = agentRuleService.getAgentRuleList(
|
||||||
|
openId = openId,
|
||||||
|
keyword = null,
|
||||||
|
page = page,
|
||||||
|
pageSize = pageSize
|
||||||
|
)
|
||||||
|
memoryList = result.list
|
||||||
|
} catch (e: Exception) {
|
||||||
|
memoryError = e.message ?: "获取记忆列表失败"
|
||||||
|
Log.e("AgentMemoryManageViewModel", "获取记忆列表失败: ${e.message}", e)
|
||||||
|
} finally {
|
||||||
|
isLoadingMemory = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除记忆(Agent 规则)
|
||||||
|
*/
|
||||||
|
fun deleteMemory(ruleId: Int) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
isLoadingMemory = true
|
||||||
|
memoryError = null
|
||||||
|
|
||||||
|
// 使用 Agent 规则接口删除
|
||||||
|
agentRuleService.deleteAgentRule(ruleId)
|
||||||
|
|
||||||
|
// 刷新记忆列表和配额
|
||||||
|
loadMemoryQuota()
|
||||||
|
loadMemoryList()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
memoryError = e.message ?: "删除记忆失败"
|
||||||
|
Log.e("AgentMemoryManageViewModel", "删除记忆失败: ${e.message}", e)
|
||||||
|
} finally {
|
||||||
|
isLoadingMemory = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新记忆(Agent 规则)
|
||||||
|
*/
|
||||||
|
fun updateMemory(ruleId: Int, newRuleText: String) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
isLoadingMemory = true
|
||||||
|
memoryError = null
|
||||||
|
|
||||||
|
// 使用 Agent 规则接口更新
|
||||||
|
agentRuleService.updateAgentRule(
|
||||||
|
id = ruleId,
|
||||||
|
rule = newRuleText,
|
||||||
|
openId = openId
|
||||||
|
)
|
||||||
|
|
||||||
|
// 刷新记忆列表和配额
|
||||||
|
loadMemoryQuota()
|
||||||
|
loadMemoryList()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
memoryError = e.message ?: "更新记忆失败"
|
||||||
|
Log.e("AgentMemoryManageViewModel", "更新记忆失败: ${e.message}", e)
|
||||||
|
} finally {
|
||||||
|
isLoadingMemory = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载添加 Agent 记忆的价格
|
||||||
|
*/
|
||||||
|
fun loadMemoryCost() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
// 获取积分规则中的 Agent 记忆价格
|
||||||
|
PointService.refreshPointsRules()
|
||||||
|
val rules = PointService.pointsRules.first()
|
||||||
|
val agentMemoryRule = rules?.sub?.get(PointService.PointsRuleKey.ADD_AGENT_MEMORY)
|
||||||
|
addMemoryCost = when (agentMemoryRule) {
|
||||||
|
is PointService.RuleAmount.Fixed -> agentMemoryRule.value
|
||||||
|
is PointService.RuleAmount.Range -> agentMemoryRule.min
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("AgentMemoryManageViewModel", "加载记忆价格失败: ${e.message}", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -57,6 +57,7 @@ fun AiProfileV3(
|
|||||||
onFollowClick: () -> Unit = {},
|
onFollowClick: () -> Unit = {},
|
||||||
onChatClick: () -> Unit = {},
|
onChatClick: () -> Unit = {},
|
||||||
onShareClick: () -> Unit = {},
|
onShareClick: () -> Unit = {},
|
||||||
|
onMemoryManageClick: () -> Unit = {},
|
||||||
onLoadMore: () -> Unit = {},
|
onLoadMore: () -> Unit = {},
|
||||||
onComment: (MomentEntity) -> Unit = {},
|
onComment: (MomentEntity) -> Unit = {},
|
||||||
) {
|
) {
|
||||||
@@ -160,7 +161,7 @@ fun AiProfileV3(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 底部菜单
|
// 底部菜单
|
||||||
if (showMenu) {
|
if (showMenu) {
|
||||||
AiProfileMenuModal(
|
AiProfileMenuModal(
|
||||||
onDismiss = { showMenu = false },
|
onDismiss = { showMenu = false },
|
||||||
@@ -181,6 +182,10 @@ fun AiProfileV3(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onMemoryManageClick = {
|
||||||
|
showMenu = false
|
||||||
|
onMemoryManageClick()
|
||||||
|
},
|
||||||
profile = profile,
|
profile = profile,
|
||||||
showEdit = isCreator
|
showEdit = isCreator
|
||||||
)
|
)
|
||||||
@@ -525,6 +530,7 @@ private fun AiProfileMenuModal(
|
|||||||
onChatClick: () -> Unit,
|
onChatClick: () -> Unit,
|
||||||
onShareClick: () -> Unit,
|
onShareClick: () -> Unit,
|
||||||
onEditClick: () -> Unit,
|
onEditClick: () -> Unit,
|
||||||
|
onMemoryManageClick: () -> Unit,
|
||||||
showEdit: Boolean = false,
|
showEdit: Boolean = false,
|
||||||
profile: AccountProfileEntity? = null
|
profile: AccountProfileEntity? = null
|
||||||
) {
|
) {
|
||||||
@@ -541,7 +547,7 @@ private fun AiProfileMenuModal(
|
|||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(if (showEdit) 240.dp else 160.dp)
|
.height(if (showEdit) 240.dp else 240.dp)
|
||||||
.background(appColors.background)
|
.background(appColors.background)
|
||||||
.padding(vertical = 47.dp, horizontal = 20.dp)
|
.padding(vertical = 47.dp, horizontal = 20.dp)
|
||||||
) {
|
) {
|
||||||
@@ -581,7 +587,7 @@ private fun AiProfileMenuModal(
|
|||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
.padding(end = if (showEdit) 16.dp else 0.dp),
|
.padding(end = 16.dp),
|
||||||
verticalArrangement = Arrangement.Center,
|
verticalArrangement = Arrangement.Center,
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
@@ -609,6 +615,38 @@ private fun AiProfileMenuModal(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 记忆管理选项
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.padding(end = if (showEdit) 16.dp else 0.dp),
|
||||||
|
verticalArrangement = Arrangement.Center,
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(CircleShape)
|
||||||
|
.noRippleClickable {
|
||||||
|
onMemoryManageClick()
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(id = R.drawable.ic_brain_add),
|
||||||
|
contentDescription = "",
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
colorFilter = ColorFilter.tint(appColors.text)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.group_chat_info_memory_manage),
|
||||||
|
fontSize = 12.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = appColors.text
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// 编辑选项(仅创建者可见)
|
// 编辑选项(仅创建者可见)
|
||||||
if (showEdit) {
|
if (showEdit) {
|
||||||
Column(
|
Column(
|
||||||
|
|||||||
@@ -1,22 +1,44 @@
|
|||||||
package com.aiosman.ravenow.ui.profile
|
package com.aiosman.ravenow.ui.profile
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.ModalBottomSheet
|
||||||
|
import androidx.compose.material3.rememberModalBottomSheetState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.aiosman.ravenow.GuestLoginCheckOut
|
import com.aiosman.ravenow.GuestLoginCheckOut
|
||||||
import com.aiosman.ravenow.GuestLoginCheckOutScene
|
import com.aiosman.ravenow.GuestLoginCheckOutScene
|
||||||
import com.aiosman.ravenow.LocalNavController
|
import com.aiosman.ravenow.LocalNavController
|
||||||
|
import com.aiosman.ravenow.R
|
||||||
import com.aiosman.ravenow.data.UserServiceImpl
|
import com.aiosman.ravenow.data.UserServiceImpl
|
||||||
import com.aiosman.ravenow.exp.viewModelFactory
|
import com.aiosman.ravenow.exp.viewModelFactory
|
||||||
import com.aiosman.ravenow.ui.NavigationRoute
|
import com.aiosman.ravenow.ui.NavigationRoute
|
||||||
|
import com.aiosman.ravenow.ui.composables.PointsPaymentDialog
|
||||||
import com.aiosman.ravenow.ui.index.tabs.ai.tabs.mine.MineAgentViewModel
|
import com.aiosman.ravenow.ui.index.tabs.ai.tabs.mine.MineAgentViewModel
|
||||||
import com.aiosman.ravenow.ui.index.tabs.profile.MyProfileViewModel
|
import com.aiosman.ravenow.ui.index.tabs.profile.MyProfileViewModel
|
||||||
import com.aiosman.ravenow.ui.navigateToChatAi
|
import com.aiosman.ravenow.ui.navigateToChatAi
|
||||||
import com.aiosman.ravenow.ui.navigateToPost
|
import com.aiosman.ravenow.ui.navigateToPost
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun AiProfileWrap(id: String) {
|
fun AiProfileWrap(id: String) {
|
||||||
val model: AiProfileViewModel = viewModel(factory = viewModelFactory {
|
val model: AiProfileViewModel = viewModel(factory = viewModelFactory {
|
||||||
@@ -26,6 +48,26 @@ fun AiProfileWrap(id: String) {
|
|||||||
val navController = LocalNavController.current
|
val navController = LocalNavController.current
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
// 记忆管理相关状态
|
||||||
|
var showMemoryManageDialog by remember { mutableStateOf(false) }
|
||||||
|
var showAddMemoryDialog by remember { mutableStateOf(false) }
|
||||||
|
var showAddMemoryConfirmDialog by remember { mutableStateOf(false) }
|
||||||
|
var pendingMemoryText by remember { mutableStateOf("") }
|
||||||
|
val memoryManageSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
||||||
|
val addMemorySheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
||||||
|
|
||||||
|
// 获取 chatAIId (openId)
|
||||||
|
val chatAIId = model.profile?.chatAIId ?: ""
|
||||||
|
|
||||||
|
// 创建记忆管理 ViewModel
|
||||||
|
val memoryViewModel: AgentMemoryManageViewModel? = if (chatAIId.isNotEmpty()) {
|
||||||
|
viewModel(factory = viewModelFactory {
|
||||||
|
AgentMemoryManageViewModel(chatAIId)
|
||||||
|
}, key = "agentMemoryViewModel_${chatAIId}")
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
model.loadProfile(id)
|
model.loadProfile(id)
|
||||||
MyProfileViewModel.loadProfile()
|
MyProfileViewModel.loadProfile()
|
||||||
@@ -33,58 +75,174 @@ fun AiProfileWrap(id: String) {
|
|||||||
|
|
||||||
val isSelf = id == MyProfileViewModel.profile?.id.toString()
|
val isSelf = id == MyProfileViewModel.profile?.id.toString()
|
||||||
|
|
||||||
AiProfileV3(
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
profile = model.profile,
|
AiProfileV3(
|
||||||
moments = model.moments,
|
profile = model.profile,
|
||||||
postCount = model.momentLoader.total,
|
moments = model.moments,
|
||||||
isSelf = isSelf,
|
postCount = model.momentLoader.total,
|
||||||
onFollowClick = {
|
isSelf = isSelf,
|
||||||
model.profile?.let {
|
onFollowClick = {
|
||||||
if (it.isFollowing) {
|
model.profile?.let {
|
||||||
model.unFollowUser(id)
|
if (it.isFollowing) {
|
||||||
} else {
|
model.unFollowUser(id)
|
||||||
model.followUser(id)
|
} else {
|
||||||
|
model.followUser(id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
onChatClick = {
|
||||||
onChatClick = {
|
// 检查游客模式
|
||||||
// 检查游客模式
|
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.CHAT_WITH_AGENT)) {
|
||||||
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.CHAT_WITH_AGENT)) {
|
navController.navigate(NavigationRoute.Login.route)
|
||||||
navController.navigate(NavigationRoute.Login.route)
|
} else {
|
||||||
} else {
|
model.profile?.let { profile ->
|
||||||
model.profile?.let { profile ->
|
scope.launch {
|
||||||
scope.launch {
|
try {
|
||||||
try {
|
// 参考主页逻辑:使用chatAIId作为openId创建单聊并导航
|
||||||
// 参考主页逻辑:使用chatAIId作为openId创建单聊并导航
|
// 创建单聊
|
||||||
// 创建单聊
|
MineAgentViewModel.createSingleChat(profile.chatAIId)
|
||||||
MineAgentViewModel.createSingleChat(profile.chatAIId)
|
|
||||||
|
// 通过chatAIId获取完整的AI profile(类似主页的goToChatAi逻辑)
|
||||||
// 通过chatAIId获取完整的AI profile(类似主页的goToChatAi逻辑)
|
val userService = UserServiceImpl()
|
||||||
val userService = UserServiceImpl()
|
val aiProfile = userService.getUserProfileByOpenId(profile.chatAIId)
|
||||||
val aiProfile = userService.getUserProfileByOpenId(profile.chatAIId)
|
|
||||||
|
// 导航到AI聊天页面
|
||||||
// 导航到AI聊天页面
|
navController.navigateToChatAi(aiProfile.id.toString())
|
||||||
navController.navigateToChatAi(aiProfile.id.toString())
|
} catch (e: Exception) {
|
||||||
} catch (e: Exception) {
|
Log.e("AiProfileWrap", "Error navigating to AI chat", e)
|
||||||
Log.e("AiProfileWrap", "Error navigating to AI chat", e)
|
// 如果获取失败,直接使用当前profile的id
|
||||||
// 如果获取失败,直接使用当前profile的id
|
navController.navigateToChatAi(profile.id.toString())
|
||||||
navController.navigateToChatAi(profile.id.toString())
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
onShareClick = {
|
||||||
|
// TODO: 实现分享逻辑
|
||||||
|
Log.d("AiProfileWrap", "分享功能待实现")
|
||||||
|
},
|
||||||
|
onMemoryManageClick = {
|
||||||
|
if (chatAIId.isNotEmpty()) {
|
||||||
|
showMemoryManageDialog = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLoadMore = {
|
||||||
|
Log.d("AiProfileWrap", "onLoadMore被调用")
|
||||||
|
model.loadMoreMoment()
|
||||||
|
},
|
||||||
|
onComment = { moment ->
|
||||||
|
navController.navigateToPost(moment.id)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 添加记忆对话框
|
||||||
|
if (showAddMemoryDialog && memoryViewModel != null) {
|
||||||
|
ModalBottomSheet(
|
||||||
|
onDismissRequest = { showAddMemoryDialog = false },
|
||||||
|
sheetState = addMemorySheetState,
|
||||||
|
containerColor = Color(0xFFFAF9FB),
|
||||||
|
dragHandle = {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.width(36.dp)
|
||||||
|
.height(5.dp)
|
||||||
|
.padding(top = 5.dp)
|
||||||
|
.background(
|
||||||
|
Color(0xFFCCCCCC),
|
||||||
|
RoundedCornerShape(100.dp)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
shape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp)
|
||||||
|
) {
|
||||||
|
AddAgentMemoryDialog(
|
||||||
|
profile = model.profile,
|
||||||
|
viewModel = memoryViewModel,
|
||||||
|
onDismiss = { showAddMemoryDialog = false },
|
||||||
|
onRequestAddMemory = { memoryText ->
|
||||||
|
// 关闭添加对话框,显示确认框
|
||||||
|
showAddMemoryDialog = false
|
||||||
|
pendingMemoryText = memoryText
|
||||||
|
showAddMemoryConfirmDialog = true
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
},
|
|
||||||
onShareClick = {
|
|
||||||
// TODO: 实现分享逻辑
|
|
||||||
Log.d("AiProfileWrap", "分享功能待实现")
|
|
||||||
},
|
|
||||||
onLoadMore = {
|
|
||||||
Log.d("AiProfileWrap", "onLoadMore被调用")
|
|
||||||
model.loadMoreMoment()
|
|
||||||
},
|
|
||||||
onComment = { moment ->
|
|
||||||
navController.navigateToPost(moment.id)
|
|
||||||
}
|
}
|
||||||
)
|
|
||||||
|
// 记忆管理弹窗
|
||||||
|
if (showMemoryManageDialog && memoryViewModel != null && chatAIId.isNotEmpty()) {
|
||||||
|
ModalBottomSheet(
|
||||||
|
onDismissRequest = { showMemoryManageDialog = false },
|
||||||
|
sheetState = memoryManageSheetState,
|
||||||
|
containerColor = Color(0xFFFAF9FB),
|
||||||
|
dragHandle = {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.width(36.dp)
|
||||||
|
.height(5.dp)
|
||||||
|
.padding(top = 5.dp)
|
||||||
|
.background(
|
||||||
|
Color(0xFFCCCCCC),
|
||||||
|
RoundedCornerShape(100.dp)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
shape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp)
|
||||||
|
) {
|
||||||
|
// 立即展开到全屏,避免逐渐变高的动画
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
memoryManageSheetState.expand()
|
||||||
|
}
|
||||||
|
AgentMemoryManageContent(
|
||||||
|
openId = chatAIId,
|
||||||
|
profile = model.profile,
|
||||||
|
viewModel = memoryViewModel,
|
||||||
|
onAddMemoryClick = {
|
||||||
|
showMemoryManageDialog = false
|
||||||
|
showAddMemoryDialog = true
|
||||||
|
},
|
||||||
|
onDismiss = {
|
||||||
|
showMemoryManageDialog = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加记忆确认对话框
|
||||||
|
if (showAddMemoryConfirmDialog && memoryViewModel != null) {
|
||||||
|
val cost = memoryViewModel.addMemoryCost ?: 0
|
||||||
|
val currentBalance = memoryViewModel.pointsBalance ?: 0
|
||||||
|
val balanceAfterCost = (currentBalance - cost).coerceAtLeast(0)
|
||||||
|
val isBalanceSufficient = currentBalance >= cost
|
||||||
|
|
||||||
|
// 监听添加成功,关闭确认对话框并刷新积分余额
|
||||||
|
LaunchedEffect(memoryViewModel.addMemorySuccess) {
|
||||||
|
if (memoryViewModel.addMemorySuccess) {
|
||||||
|
showAddMemoryConfirmDialog = false
|
||||||
|
pendingMemoryText = ""
|
||||||
|
memoryViewModel.addMemorySuccess = false
|
||||||
|
memoryViewModel.loadPointsBalance() // 刷新积分余额
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PointsPaymentDialog(
|
||||||
|
cost = cost,
|
||||||
|
currentBalance = currentBalance,
|
||||||
|
balanceAfterCost = balanceAfterCost,
|
||||||
|
isBalanceSufficient = isBalanceSufficient,
|
||||||
|
onConfirm = {
|
||||||
|
// 确认支付,添加记忆
|
||||||
|
memoryViewModel.addAgentMemory(pendingMemoryText)
|
||||||
|
},
|
||||||
|
onCancel = {
|
||||||
|
showAddMemoryConfirmDialog = false
|
||||||
|
pendingMemoryText = ""
|
||||||
|
},
|
||||||
|
title = stringResource(R.string.group_chat_info_add_memory),
|
||||||
|
description = stringResource(R.string.group_chat_info_memory_description),
|
||||||
|
isProcessing = memoryViewModel.isAddingMemory
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user