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

@@ -7,6 +7,7 @@ import com.aiosman.ravenow.data.api.AppConfig
import com.aiosman.ravenow.data.api.CaptchaInfo
import com.aiosman.ravenow.data.api.ChangePasswordRequestBody
import com.aiosman.ravenow.data.api.GoogleRegisterRequestBody
import com.aiosman.ravenow.data.api.GuestLoginRequestBody
import com.aiosman.ravenow.data.api.LoginUserRequestBody
import com.aiosman.ravenow.data.api.RegisterMessageChannelRequestBody
import com.aiosman.ravenow.data.api.RegisterRequestBody
@@ -300,6 +301,13 @@ interface AccountService {
*/
suspend fun loginUserWithGoogle(googleId: String): UserAuth
/**
* 游客登录
* @param deviceId 设备ID
* @param deviceInfo 设备信息
*/
suspend fun guestLogin(deviceId: String, deviceInfo: String? = null): UserAuth
/**
* 退出登录
*/
@@ -456,6 +464,21 @@ class AccountServiceImpl : AccountService {
return UserAuth(0, body.token)
}
override suspend fun guestLogin(deviceId: String, deviceInfo: String?): UserAuth {
val resp = ApiClient.api.guestLogin(GuestLoginRequestBody(
deviceId = deviceId,
deviceInfo = deviceInfo
))
if (!resp.isSuccessful) {
parseErrorResponse(resp.errorBody())?.let {
throw it.toServiceException()
}
throw ServiceException("Failed to guest login")
}
val body = resp.body() ?: throw ServiceException("Failed to guest login")
return UserAuth(0, body.token, isGuest = true)
}
override suspend fun regiterUserWithGoogleAccount(idToken: String) {
val resp = ApiClient.api.registerWithGoogle(GoogleRegisterRequestBody(idToken))
if (!resp.isSuccessful) {

View File

@@ -5,7 +5,8 @@ import com.aiosman.ravenow.entity.AccountProfileEntity
data class UserAuth(
val id: Int,
val token: String? = null
val token: String? = null,
val isGuest: Boolean = false
)
/**

View File

@@ -23,7 +23,29 @@ fun getUnsafeOkHttpClient(
): OkHttpClient {
return try {
// Create a trust manager that does not validate certificate chains
val trustAllCerts = arrayOf<TrustManager>(object : X509TrustManager {
@Throws(CertificateException::class)
override fun checkClientTrusted(chain: Array<java.security.cert.X509Certificate>, authType: String) {
}
@Throws(CertificateException::class)
override fun checkServerTrusted(chain: Array<java.security.cert.X509Certificate>, authType: String) {
}
override fun getAcceptedIssuers(): Array<java.security.cert.X509Certificate> {
return arrayOf()
}
})
// Install the all-trusting trust manager
val sslContext = SSLContext.getInstance("SSL")
sslContext.init(null, trustAllCerts, java.security.SecureRandom())
// Create an ssl socket factory with our all-trusting manager
val sslSocketFactory = sslContext.socketFactory
OkHttpClient.Builder()
.sslSocketFactory(sslSocketFactory, trustAllCerts[0] as X509TrustManager)
.hostnameVerifier { _, _ -> true }
.apply {
authInterceptor?.let {

View File

@@ -95,6 +95,19 @@ data class LoginUserRequestBody(
val captcha: CaptchaInfo? = null,
)
data class GuestLoginRequestBody(
@SerializedName("deviceID")
val deviceId: String,
@SerializedName("platform")
val platform: String = "android",
@SerializedName("deviceInfo")
val deviceInfo: String? = null,
@SerializedName("userAgent")
val userAgent: String? = null,
@SerializedName("ipAddress")
val ipAddress: String? = null
)
data class GoogleRegisterRequestBody(
@SerializedName("idToken")
val idToken: String
@@ -274,6 +287,9 @@ interface RaveNowAPI {
@POST("login")
suspend fun login(@Body body: LoginUserRequestBody): Response<AuthResult>
@POST("guest/login")
suspend fun guestLogin(@Body body: GuestLoginRequestBody): Response<AuthResult>
@GET("auth/token")
suspend fun checkToken(): Response<ValidateTokenResult>