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:
2025-09-01 14:17:44 +08:00
parent 484d641554
commit 83cff9d56c
23 changed files with 780 additions and 254 deletions

View File

@@ -2,6 +2,7 @@ package com.aiosman.ravenow.entity
import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.aiosman.ravenow.AppStore
import com.aiosman.ravenow.data.Agent
import com.aiosman.ravenow.data.ListContainer
import com.aiosman.ravenow.data.AgentService
@@ -28,24 +29,37 @@ suspend fun createAgent(
title: String,
desc: String,
avatar: UploadImage? = null,
workflowId:Int = 1,
isPublic:Boolean = true,
breakMode:Boolean = false,
useWorkflow:Boolean = true,
workflowId: Int = 1,
isPublic: Boolean = true,
breakMode: Boolean = false,
useWorkflow: Boolean = true,
): AgentEntity {
val textTitle = title.toRequestBody("text/plain".toMediaTypeOrNull())
val textDesc = desc.toRequestBody("text/plain".toMediaTypeOrNull())
val workflowIdRequestBody = workflowId.toString().toRequestBody("text/plain".toMediaTypeOrNull())
val workflowIdRequestBody =
workflowId.toString().toRequestBody("text/plain".toMediaTypeOrNull())
val isPublicRequestBody = isPublic.toString().toRequestBody("text/plain".toMediaTypeOrNull())
val breakModeRequestBody = breakMode.toString().toRequestBody("text/plain".toMediaTypeOrNull())
val useWorkflowRequestBody = useWorkflow.toString().toRequestBody("text/plain".toMediaTypeOrNull())
val useWorkflowRequestBody =
useWorkflow.toString().toRequestBody("text/plain".toMediaTypeOrNull())
val workflowInputsValue = "{\"si\":\"$desc\"}"
val workflowInputsRequestBody = workflowInputsValue.toRequestBody("text/plain".toMediaTypeOrNull())
val workflowInputsRequestBody =
workflowInputsValue.toRequestBody("text/plain".toMediaTypeOrNull())
val avatarField: MultipartBody.Part? = avatar?.let {
createMultipartBody(it.file, it.filename, "avatar")
}
val response = ApiClient.api.createAgent(avatarField, textTitle ,textDesc,textDesc,workflowIdRequestBody,isPublicRequestBody,breakModeRequestBody,useWorkflowRequestBody,workflowInputsRequestBody)
val response = ApiClient.api.createAgent(
avatarField,
textTitle,
textDesc,
textDesc,
workflowIdRequestBody,
isPublicRequestBody,
breakModeRequestBody,
useWorkflowRequestBody,
workflowInputsRequestBody
)
val body = response.body()?.data ?: throw ServiceException("Failed to create agent")
return body.toAgentEntity()
@@ -99,7 +113,11 @@ class AgentRemoteDataSource(
class AgentServiceImpl() : AgentService {
val agentBackend = AgentBackend()
override suspend fun getAgent(pageNumber: Int, pageSize: Int, authorId: Int?): ListContainer<AgentEntity> {
override suspend fun getAgent(
pageNumber: Int,
pageSize: Int,
authorId: Int?
): ListContainer<AgentEntity> {
return agentBackend.getAgent(
pageNumber = pageNumber,
authorId = authorId
@@ -107,50 +125,62 @@ class AgentServiceImpl() : AgentService {
}
}
class AgentBackend {
val DataBatchSize = 20
suspend fun getAgent(
pageNumber: Int,
authorId: Int? = null
): ListContainer<AgentEntity> {
val resp = if (authorId != null) {
ApiClient.api.getAgent(
page = pageNumber,
pageSize = DataBatchSize,
authorId = authorId
)
} else {
ApiClient.api.getMyAgent(
page = pageNumber,
pageSize = DataBatchSize
)
}
val body = resp.body() ?: throw ServiceException("Failed to get agents")
// 处理不同的返回类型
return if (authorId != null) {
// getAgent 返回 DataContainer<ListContainer<Agent>>
val dataContainer = body as com.aiosman.ravenow.data.DataContainer<com.aiosman.ravenow.data.ListContainer<com.aiosman.ravenow.data.Agent>>
val listContainer = dataContainer.data
ListContainer(
total = listContainer.total,
page = pageNumber,
pageSize = DataBatchSize,
list = listContainer.list.map { it.toAgentEntity() }
)
} else {
// getMyAgent 返回 ListContainer<Agent>
val listContainer = body as com.aiosman.ravenow.data.ListContainer<com.aiosman.ravenow.data.Agent>
ListContainer(
total = listContainer.total,
page = pageNumber,
pageSize = DataBatchSize,
list = listContainer.list.map { it.toAgentEntity() }
)
}
class AgentBackend {
val DataBatchSize = 20
suspend fun getAgent(
pageNumber: Int,
authorId: Int? = null
): ListContainer<AgentEntity> {
// 如果是游客模式且获取我的AgentauthorId为null返回空列表
if (authorId == null && AppStore.isGuest) {
return ListContainer(
total = 0,
page = pageNumber,
pageSize = DataBatchSize,
list = emptyList()
)
}
val resp = if (authorId != null) {
ApiClient.api.getAgent(
page = pageNumber,
pageSize = DataBatchSize,
authorId = authorId
)
} else {
ApiClient.api.getMyAgent(
page = pageNumber,
pageSize = DataBatchSize
)
}
val body = resp.body() ?: throw ServiceException("Failed to get agents")
// 处理不同的返回类型
return if (authorId != null) {
// getAgent 返回 DataContainer<ListContainer<Agent>>
val dataContainer =
body as com.aiosman.ravenow.data.DataContainer<com.aiosman.ravenow.data.ListContainer<com.aiosman.ravenow.data.Agent>>
val listContainer = dataContainer.data
ListContainer(
total = listContainer.total,
page = pageNumber,
pageSize = DataBatchSize,
list = listContainer.list.map { it.toAgentEntity() }
)
} else {
// getMyAgent 返回 ListContainer<Agent>
val listContainer =
body as com.aiosman.ravenow.data.ListContainer<com.aiosman.ravenow.data.Agent>
ListContainer(
total = listContainer.total,
page = pageNumber,
pageSize = DataBatchSize,
list = listContainer.list.map { it.toAgentEntity() }
)
}
}
}
data class AgentEntity(
//val author: String,
@@ -172,15 +202,27 @@ fun createMultipartBody(file: File, filename: String, name: String): MultipartBo
val requestFile = file.asRequestBody("image/*".toMediaTypeOrNull())
return MultipartBody.Part.createFormData(name, filename, requestFile)
}
class AgentLoaderExtraArgs(
val authorId: Int? = null
)
class AgentLoader : DataLoader<AgentEntity,AgentLoaderExtraArgs>() {
class AgentLoader : DataLoader<AgentEntity, AgentLoaderExtraArgs>() {
override suspend fun fetchData(
page: Int,
pageSize: Int,
extra: AgentLoaderExtraArgs
): ListContainer<AgentEntity> {
// 如果是游客模式且获取我的AgentauthorId为null返回空列表
if (extra.authorId == null && AppStore.isGuest) {
return ListContainer(
total = 0,
page = page,
pageSize = pageSize,
list = emptyList()
)
}
val result = if (extra.authorId != null) {
ApiClient.api.getAgent(
page = page,
@@ -193,24 +235,25 @@ class AgentLoader : DataLoader<AgentEntity,AgentLoaderExtraArgs>() {
pageSize = pageSize
)
}
val body = result.body() ?: throw ServiceException("Failed to get agent")
return if (extra.authorId != null) {
// getAgent 返回 DataContainer<ListContainer<Agent>>
val dataContainer = body as DataContainer<ListContainer<Agent>>
val listContainer = dataContainer.data
ListContainer(
list = listContainer.list.map { it.toAgentEntity()},
list = listContainer.list.map { it.toAgentEntity() },
total = listContainer.total,
page = page,
pageSize = pageSize
)
} else {
// getMyAgent 返回 ListContainer<Agent>
val listContainer = body as com.aiosman.ravenow.data.ListContainer<com.aiosman.ravenow.data.Agent>
val listContainer =
body as com.aiosman.ravenow.data.ListContainer<com.aiosman.ravenow.data.Agent>
ListContainer(
list = listContainer.list.map { it.toAgentEntity()},
list = listContainer.list.map { it.toAgentEntity() },
total = listContainer.total,
page = page,
pageSize = pageSize