Enhance AI Agent Profile Interaction
This commit introduces several enhancements to how AI agent profiles are displayed and interacted with:
**Profile Display:**
- **AI Account Distinction:** Profile pages now differentiate between regular user accounts and AI agent accounts.
- AI agent profiles will not display the "Agents" tab in their profile.
- The profile header height is adjusted for AI accounts.
- **Navigation Parameter:** An `isAiAccount` boolean parameter is added to the `AccountProfile` navigation route to indicate if the profile being viewed belongs to an AI.
**Interaction & Navigation:**
- **Avatar Click Navigation:**
- Clicking an AI agent's avatar in various lists (Hot Agents, My Agents, User Agents Row, User Agents List) now navigates to the agent's dedicated profile page.
- When navigating to an agent's profile from an agent list, `isAiAccount` is set to `true`.
- **Chat Initiation:** Clicking the chat button on AI agent cards in the "Agent" tab (both Hot and My Agents) now correctly initiates a chat with the respective AI.
- **ViewModel Updates:**
- `AgentViewModel`, `MineAgentViewModel`, and `HotAgentViewModel` now include a `goToProfile` function to handle navigation to agent profiles, correctly passing the `isAiAccount` flag.
**Code Refinements:**
- Click handlers for agent avatars and chat buttons are now wrapped with `DebounceUtils.simpleDebounceClick` to prevent multiple rapid clicks.
- The `UserContentPageIndicator` now conditionally hides the "Agent" tab based on the `isAiAccount` status.
- `UserAgentsRow` and `UserAgentsList` now accept an `onAvatarClick` callback for navigating to agent profiles.
- `AgentItem` (used in `UserAgentsRow`) and `UserAgentCard` (used in `UserAgentsList`) now handle avatar clicks.
- The general `Agent` composable (used in `AiPostComposable`) now also supports an `onAvatarClick` callback.
This commit is contained in:
@@ -85,7 +85,7 @@ sealed class NavigationRoute(
|
||||
data object NewPost : NavigationRoute("NewPost")
|
||||
data object EditModification : NavigationRoute("EditModification")
|
||||
data object Login : NavigationRoute("Login")
|
||||
data object AccountProfile : NavigationRoute("AccountProfile/{id}")
|
||||
data object AccountProfile : NavigationRoute("AccountProfile/{id}?isAiAccount={isAiAccount}")
|
||||
data object SignUp : NavigationRoute("SignUp")
|
||||
data object UserAuth : NavigationRoute("UserAuth")
|
||||
data object EmailSignUp : NavigationRoute("EmailSignUp")
|
||||
@@ -284,7 +284,13 @@ fun NavigationController(
|
||||
}
|
||||
composable(
|
||||
route = NavigationRoute.AccountProfile.route,
|
||||
arguments = listOf(navArgument("id") { type = NavType.StringType }),
|
||||
arguments = listOf(
|
||||
navArgument("id") { type = NavType.StringType },
|
||||
navArgument("isAiAccount") {
|
||||
type = NavType.BoolType
|
||||
defaultValue = false
|
||||
}
|
||||
),
|
||||
enterTransition = {
|
||||
// iOS push: new screen slides in from the right
|
||||
slideInHorizontally(
|
||||
@@ -317,7 +323,9 @@ fun NavigationController(
|
||||
CompositionLocalProvider(
|
||||
LocalAnimatedContentScope provides this,
|
||||
) {
|
||||
AccountProfileV2(it.arguments?.getString("id")!!)
|
||||
val id = it.arguments?.getString("id")!!
|
||||
val isAiAccount = it.arguments?.getBoolean("isAiAccount") ?: false
|
||||
AccountProfileV2(id, isAiAccount)
|
||||
}
|
||||
}
|
||||
composable(
|
||||
|
||||
@@ -42,6 +42,7 @@ fun AgentCard(
|
||||
modifier: Modifier = Modifier,
|
||||
agentEntity: AgentEntity,
|
||||
onClick: () -> Unit = {},
|
||||
onAvatarClick: () -> Unit = {},
|
||||
) {
|
||||
val AppColors = LocalAppTheme.current
|
||||
val context = LocalContext.current
|
||||
@@ -63,6 +64,9 @@ fun AgentCard(
|
||||
modifier = Modifier
|
||||
.size(40.dp)
|
||||
.clip(RoundedCornerShape(40.dp))
|
||||
.noRippleClickable {
|
||||
onAvatarClick()
|
||||
}
|
||||
) {
|
||||
CustomAsyncImage(
|
||||
context,
|
||||
|
||||
@@ -389,7 +389,14 @@ fun AgentCard2(viewModel: AgentViewModel,agentItem: AgentItem,navController: Nav
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(48.dp)
|
||||
.background(Color(0xFFF5F5F5), RoundedCornerShape(24.dp)),
|
||||
.background(Color(0xFFF5F5F5), RoundedCornerShape(24.dp))
|
||||
.clickable {
|
||||
if (DebounceUtils.simpleDebounceClick(lastClickTime, 500L) {
|
||||
viewModel.goToProfile(agentItem.openId, navController)
|
||||
}) {
|
||||
lastClickTime = System.currentTimeMillis()
|
||||
}
|
||||
},
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
if (agentItem.avatar.isNotEmpty()) {
|
||||
@@ -451,8 +458,8 @@ fun AgentCard2(viewModel: AgentViewModel,agentItem: AgentItem,navController: Nav
|
||||
)
|
||||
.clickable {
|
||||
if (DebounceUtils.simpleDebounceClick(lastClickTime, 500L) {
|
||||
/* viewModel.createSingleChat(agentItem.trtcId)
|
||||
viewModel.goToChatAi(agentItem.trtcId, navController = navController)*/
|
||||
viewModel.createSingleChat(agentItem.openId)
|
||||
viewModel.goToChatAi(agentItem.openId, navController = navController)
|
||||
}) {
|
||||
lastClickTime = System.currentTimeMillis()
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import com.aiosman.ravenow.data.api.ApiClient
|
||||
import com.aiosman.ravenow.data.api.RaveNowAPI
|
||||
import com.aiosman.ravenow.data.api.SingleChatRequestBody
|
||||
import com.aiosman.ravenow.ui.index.tabs.ai.tabs.mine.MineAgentViewModel.createGroup2ChatAi
|
||||
import com.aiosman.ravenow.ui.NavigationRoute
|
||||
import com.aiosman.ravenow.ui.index.tabs.message.MessageListViewModel.userService
|
||||
import com.aiosman.ravenow.ui.index.tabs.moment.tabs.expolre.AgentItem
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -75,4 +76,23 @@ object AgentViewModel: ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
fun goToProfile(
|
||||
openId: String,
|
||||
navController: NavHostController
|
||||
) {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val profile = userService.getUserProfileByOpenId(openId)
|
||||
// 从Agent列表点击进去的一定是智能体,直接传递isAiAccount = true
|
||||
navController.navigate(
|
||||
NavigationRoute.AccountProfile.route
|
||||
.replace("{id}", profile.id.toString())
|
||||
.replace("{isAiAccount}", "true")
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
// swallow error to avoid crash on navigation attempt failures
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -120,6 +120,9 @@ fun HotAgent() {
|
||||
model.createSingleChat(agentItem.openId)
|
||||
model.goToChatAi(agentItem.openId,navController)
|
||||
},
|
||||
onAvatarClick = {
|
||||
model.goToProfile(agentItem.openId, navController)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import com.aiosman.ravenow.data.api.SingleChatRequestBody
|
||||
import com.aiosman.ravenow.entity.AgentEntity
|
||||
import com.aiosman.ravenow.ui.index.tabs.ai.tabs.mine.MineAgentViewModel.createGroup2ChatAi
|
||||
import com.aiosman.ravenow.ui.index.tabs.message.MessageListViewModel.userService
|
||||
import com.aiosman.ravenow.ui.NavigationRoute
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
object HotAgentViewModel : ViewModel() {
|
||||
@@ -128,6 +129,25 @@ object HotAgentViewModel : ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
fun goToProfile(
|
||||
openId: String,
|
||||
navController: NavHostController
|
||||
) {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val profile = userService.getUserProfileByOpenId(openId)
|
||||
// 从Agent列表点击进去的一定是智能体,直接传递isAiAccount = true
|
||||
navController.navigate(
|
||||
NavigationRoute.AccountProfile.route
|
||||
.replace("{id}", profile.id.toString())
|
||||
.replace("{isAiAccount}", "true")
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
// swallow error to avoid crash on navigation attempt failures
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 预加载图片,避免滑动时重复加载
|
||||
fun preloadImages(context: android.content.Context) {
|
||||
viewModelScope.launch {
|
||||
|
||||
@@ -111,6 +111,9 @@ fun MineAgent() {
|
||||
model.createSingleChat(agent.openId)
|
||||
model.goToChatAi(agent.openId,navController)
|
||||
},
|
||||
onAvatarClick = {
|
||||
model.goToProfile(agent.openId, navController)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import com.aiosman.ravenow.entity.MomentRemoteDataSource
|
||||
import com.aiosman.ravenow.entity.MomentServiceImpl
|
||||
import com.aiosman.ravenow.ui.index.tabs.message.MessageListViewModel.userService
|
||||
import com.aiosman.ravenow.ui.navigateToChatAi
|
||||
import com.aiosman.ravenow.ui.NavigationRoute
|
||||
import com.tencent.imsdk.v2.V2TIMConversationOperationResult
|
||||
import com.tencent.imsdk.v2.V2TIMManager
|
||||
import com.tencent.imsdk.v2.V2TIMValueCallback
|
||||
@@ -119,5 +120,24 @@ object MineAgentViewModel : ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
fun goToProfile(
|
||||
openId: String,
|
||||
navController: NavHostController
|
||||
) {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val profile = userService.getUserProfileByOpenId(openId)
|
||||
// 从Agent列表点击进去的一定是智能体,直接传递isAiAccount = true
|
||||
navController.navigate(
|
||||
NavigationRoute.AccountProfile.route
|
||||
.replace("{id}", profile.id.toString())
|
||||
.replace("{isAiAccount}", "true")
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
// swallow error to avoid crash on navigation attempt failures
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -101,6 +101,7 @@ fun ProfileV3(
|
||||
agents: List<AgentEntity> = emptyList(),
|
||||
isSelf: Boolean = true,
|
||||
isMain:Boolean = false,
|
||||
isAiAccount: Boolean = false, // 新增参数判断是否为AI账户
|
||||
onLoadMore: () -> Unit = {},
|
||||
onLike: (MomentEntity) -> Unit = {},
|
||||
onComment: (MomentEntity) -> Unit = {},
|
||||
@@ -109,7 +110,7 @@ fun ProfileV3(
|
||||
) {
|
||||
val model = MyProfileViewModel
|
||||
val state = rememberCollapsingToolbarScaffoldState()
|
||||
val pagerState = rememberPagerState(pageCount = { 2 })
|
||||
val pagerState = rememberPagerState(pageCount = { if (isAiAccount) 1 else 2 })
|
||||
val enabled by remember { mutableStateOf(true) }
|
||||
val statusBarPaddingValues = WindowInsets.systemBars.asPaddingValues()
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
@@ -227,7 +228,7 @@ fun ProfileV3(
|
||||
modifier = Modifier
|
||||
.parallax(0.5f)
|
||||
.fillMaxWidth()
|
||||
.height(700.dp)
|
||||
.height(if (isAiAccount) 600.dp else 700.dp)
|
||||
.background(AppColors.profileBackground)
|
||||
.verticalScroll(toolbarScrollState)
|
||||
) {
|
||||
@@ -387,7 +388,8 @@ fun ProfileV3(
|
||||
}
|
||||
}
|
||||
|
||||
// 添加用户智能体行
|
||||
// 添加用户智能体行(智能体用户不显示)
|
||||
if (!isAiAccount) {
|
||||
UserAgentsRow(
|
||||
userId = if (isSelf) null else profile?.id,
|
||||
modifier = Modifier.padding(top = 16.dp),
|
||||
@@ -398,9 +400,26 @@ fun ProfileV3(
|
||||
onAgentClick = { agent ->
|
||||
// 导航到智能体详情页面
|
||||
// TODO: 实现导航逻辑
|
||||
},
|
||||
onAvatarClick = { agent ->
|
||||
// 导航到智能体个人主页
|
||||
scope.launch {
|
||||
try {
|
||||
val userService = com.aiosman.ravenow.data.UserServiceImpl()
|
||||
val profile = userService.getUserProfileByOpenId(agent.openId)
|
||||
navController.navigate(
|
||||
NavigationRoute.AccountProfile.route
|
||||
.replace("{id}", profile.id.toString())
|
||||
.replace("{isAiAccount}", "true")
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
// 处理错误
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -497,6 +516,7 @@ fun ProfileV3(
|
||||
) {
|
||||
UserContentPageIndicator(
|
||||
pagerState = pagerState,
|
||||
showAgentTab = !isAiAccount
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
HorizontalPager(
|
||||
@@ -555,6 +575,22 @@ fun ProfileV3(
|
||||
UserAgentsList(
|
||||
agents = agents,
|
||||
onAgentClick = onAgentClick,
|
||||
onAvatarClick = { agent ->
|
||||
// 导航到智能体个人主页,需要通过openId获取用户ID
|
||||
scope.launch {
|
||||
try {
|
||||
val userService = com.aiosman.ravenow.data.UserServiceImpl()
|
||||
val profile = userService.getUserProfileByOpenId(agent.openId)
|
||||
navController.navigate(
|
||||
NavigationRoute.AccountProfile.route
|
||||
.replace("{id}", profile.id.toString())
|
||||
.replace("{isAiAccount}", "true")
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
// 处理错误
|
||||
}
|
||||
}
|
||||
},
|
||||
modifier = Modifier.fillMaxSize()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ fun ProfileWrap(
|
||||
}
|
||||
ProfileV3(
|
||||
isMain = true,
|
||||
isAiAccount = MyProfileViewModel.profile?.aiAccount == true, // 传入AI账户判断
|
||||
postCount = MyProfileViewModel.momentLoader.total,
|
||||
onUpdateBanner = { uri, file, context ->
|
||||
MyProfileViewModel.updateUserProfileBanner(uri, file, context)
|
||||
|
||||
@@ -45,6 +45,7 @@ import com.aiosman.ravenow.utils.DebounceUtils
|
||||
fun UserAgentsList(
|
||||
agents: List<AgentEntity>,
|
||||
onAgentClick: (AgentEntity) -> Unit = {},
|
||||
onAvatarClick: (AgentEntity) -> Unit = {},
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val AppColors = LocalAppTheme.current
|
||||
@@ -61,7 +62,8 @@ fun UserAgentsList(
|
||||
items(agents) { agent ->
|
||||
UserAgentCard(
|
||||
agent = agent,
|
||||
onAgentClick = onAgentClick
|
||||
onAgentClick = onAgentClick,
|
||||
onAvatarClick = onAvatarClick
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -76,7 +78,8 @@ fun UserAgentsList(
|
||||
@Composable
|
||||
fun UserAgentCard(
|
||||
agent: AgentEntity,
|
||||
onAgentClick: (AgentEntity) -> Unit
|
||||
onAgentClick: (AgentEntity) -> Unit,
|
||||
onAvatarClick: (AgentEntity) -> Unit = {}
|
||||
) {
|
||||
val AppColors = LocalAppTheme.current
|
||||
|
||||
@@ -93,7 +96,14 @@ fun UserAgentCard(
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(48.dp)
|
||||
.background(AppColors.nonActive, RoundedCornerShape(24.dp)),
|
||||
.background(AppColors.nonActive, RoundedCornerShape(24.dp))
|
||||
.clickable {
|
||||
if (DebounceUtils.simpleDebounceClick(lastClickTime, 500L) {
|
||||
onAvatarClick(agent)
|
||||
}) {
|
||||
lastClickTime = System.currentTimeMillis()
|
||||
}
|
||||
},
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
if (agent.avatar.isNotEmpty()) {
|
||||
|
||||
@@ -45,7 +45,8 @@ fun UserAgentsRow(
|
||||
userId: Int?,
|
||||
modifier: Modifier = Modifier,
|
||||
onMoreClick: () -> Unit = {},
|
||||
onAgentClick: (AgentEntity) -> Unit = {}
|
||||
onAgentClick: (AgentEntity) -> Unit = {},
|
||||
onAvatarClick: (AgentEntity) -> Unit = {}
|
||||
) {
|
||||
val AppColors = LocalAppTheme.current
|
||||
val viewModel: UserAgentsViewModel = viewModel(key = "UserAgentsViewModel_${userId ?: "self"}")
|
||||
@@ -128,7 +129,8 @@ fun UserAgentsRow(
|
||||
items(viewModel.agents) { agent ->
|
||||
AgentItem(
|
||||
agent = agent,
|
||||
onClick = { onAgentClick(agent) }
|
||||
onClick = { onAgentClick(agent) },
|
||||
onAvatarClick = { onAvatarClick(agent) }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -150,19 +152,21 @@ fun UserAgentsRow(
|
||||
private fun AgentItem(
|
||||
agent: AgentEntity,
|
||||
modifier: Modifier = Modifier,
|
||||
onClick: () -> Unit = {}
|
||||
onClick: () -> Unit = {},
|
||||
onAvatarClick: () -> Unit = {}
|
||||
) {
|
||||
val AppColors = LocalAppTheme.current
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = modifier.noRippleClickable { onClick() }
|
||||
modifier = modifier
|
||||
) {
|
||||
// 头像
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(48.dp)
|
||||
.clip(CircleShape)
|
||||
.noRippleClickable { onAvatarClick() }
|
||||
) {
|
||||
CustomAsyncImage(
|
||||
context = LocalContext.current,
|
||||
@@ -185,7 +189,9 @@ private fun AgentItem(
|
||||
textAlign = TextAlign.Center,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.width(48.dp)
|
||||
modifier = Modifier
|
||||
.width(48.dp)
|
||||
.noRippleClickable { onClick() }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,8 @@ import kotlinx.coroutines.launch
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun UserContentPageIndicator(
|
||||
pagerState: PagerState
|
||||
pagerState: PagerState,
|
||||
showAgentTab: Boolean = true
|
||||
){
|
||||
val scope = rememberCoroutineScope()
|
||||
val AppColors = LocalAppTheme.current
|
||||
@@ -71,7 +72,8 @@ fun UserContentPageIndicator(
|
||||
)
|
||||
}
|
||||
|
||||
// Agent Tab
|
||||
// Agent Tab (只在非智能体用户时显示)
|
||||
if (showAgentTab) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier
|
||||
@@ -91,6 +93,7 @@ fun UserContentPageIndicator(
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 下划线指示器
|
||||
Row(
|
||||
@@ -106,6 +109,7 @@ fun UserContentPageIndicator(
|
||||
if (pagerState.currentPage == 0) AppColors.text else Color.Transparent
|
||||
)
|
||||
)
|
||||
if (showAgentTab) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
@@ -117,3 +121,4 @@ fun UserContentPageIndicator(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import com.aiosman.ravenow.ui.navigateToChat
|
||||
import com.aiosman.ravenow.ui.navigateToPost
|
||||
|
||||
@Composable
|
||||
fun AccountProfileV2(id: String){
|
||||
fun AccountProfileV2(id: String, isAiAccount: Boolean = false){
|
||||
val model: AccountProfileViewModel = viewModel(factory = viewModelFactory {
|
||||
AccountProfileViewModel()
|
||||
}, key = "viewModel_${id}")
|
||||
@@ -30,6 +30,7 @@ fun AccountProfileV2(id: String){
|
||||
agents = model.agents,
|
||||
profile = model.profile,
|
||||
isSelf = isSelf,
|
||||
isAiAccount = isAiAccount, // 从参数传入AI账户判断
|
||||
postCount = model.momentLoader.total,
|
||||
onLoadMore = {
|
||||
Log.d("AccountProfileV2", "onLoadMore被调用")
|
||||
|
||||
Reference in New Issue
Block a user