3 Commits

Author SHA1 Message Date
85141fde1b A
Revert "lottie依赖"

This reverts commit 89227edccf.
2025-10-20 11:04:41 +08:00
234c07142c 首页UI调整 2025-10-17 18:54:55 +08:00
89227edccf lottie依赖 2025-10-17 15:07:46 +08:00
9 changed files with 1065 additions and 437 deletions

View File

@@ -81,7 +81,9 @@ data class CreateGroupChatRequestBody(
data class JoinGroupChatRequestBody( data class JoinGroupChatRequestBody(
@SerializedName("trtcId") @SerializedName("trtcId")
val trtcId: String, val trtcId: String? = null,
@SerializedName("roomId")
val roomId: Int? = null,
) )
data class LoginUserRequestBody( data class LoginUserRequestBody(
@@ -271,6 +273,132 @@ data class RemoveAccountRequestBody(
val password: String, val password: String,
) )
// API 错误响应(用于加入房间等接口的错误处理)
data class ApiErrorResponse(
@SerializedName("err")
val error: String,
@SerializedName("success")
val success: Boolean
)
// 群聊中的用户信息
data class GroupChatUser(
@SerializedName("ID")
val id: Int,
@SerializedName("CreatedAt")
val createdAt: String,
@SerializedName("UpdatedAt")
val updatedAt: String,
@SerializedName("DeletedAt")
val deletedAt: String?,
@SerializedName("userSessionId")
val userSessionId: String,
@SerializedName("sessions")
val sessions: Any?, // 根据实际需要可以定义具体类型
@SerializedName("prompts")
val prompts: Any?, // 根据实际需要可以定义具体类型
@SerializedName("isAgent")
val isAgent: Boolean
)
// 智能体角色信息
data class GroupChatPrompt(
@SerializedName("ID")
val id: Int,
@SerializedName("CreatedAt")
val createdAt: String,
@SerializedName("UpdatedAt")
val updatedAt: String,
@SerializedName("DeletedAt")
val deletedAt: String?,
@SerializedName("Title")
val title: String,
@SerializedName("Desc")
val desc: String,
@SerializedName("Value")
val value: String,
@SerializedName("Enable")
val enable: Boolean,
@SerializedName("UserSessions")
val userSessions: Any?, // 根据实际需要可以定义具体类型
@SerializedName("Avatar")
val avatar: String,
@SerializedName("AuthorId")
val authorId: Int?,
@SerializedName("Author")
val author: Any?, // 根据实际需要可以定义具体类型
@SerializedName("TokenCount")
val tokenCount: Int,
@SerializedName("OpenId")
val openId: String,
@SerializedName("Public")
val public: Boolean,
@SerializedName("BreakMode")
val breakMode: Boolean,
@SerializedName("DocNamespace")
val docNamespace: String,
@SerializedName("UseRag")
val useRag: Boolean,
@SerializedName("RagThreshold")
val ragThreshold: Double,
@SerializedName("WorkflowId")
val workflowId: Int?,
@SerializedName("Workflow")
val workflow: Any?, // 根据实际需要可以定义具体类型
@SerializedName("WorkflowInputs")
val workflowInputs: Any?, // 根据实际需要可以定义具体类型
@SerializedName("Source")
val source: String,
@SerializedName("categories")
val categories: Any? // 根据实际需要可以定义具体类型
)
// 群聊详细信息响应
data class GroupChatResponse(
@SerializedName("ID")
val id: Int,
@SerializedName("CreatedAt")
val createdAt: String,
@SerializedName("UpdatedAt")
val updatedAt: String,
@SerializedName("DeletedAt")
val deletedAt: String?,
@SerializedName("name")
val name: String,
@SerializedName("description")
val description: String,
@SerializedName("creatorId")
val creatorId: Int,
@SerializedName("creator")
val creator: Any?, // 根据实际需要可以定义具体类型
@SerializedName("trtcRoomId")
val trtcRoomId: String,
@SerializedName("trtcType")
val trtcType: String,
@SerializedName("cover")
val cover: String,
@SerializedName("avatar")
val avatar: String,
@SerializedName("recommendBanner")
val recommendBanner: String,
@SerializedName("isRecommended")
val isRecommended: Boolean,
@SerializedName("allowInHot")
val allowInHot: Boolean,
@SerializedName("users")
val users: List<GroupChatUser>,
@SerializedName("prompts")
val prompts: List<GroupChatPrompt>,
@SerializedName("source")
val source: String
)
class CategoryTemplateTranslation(
@SerializedName("name")
val name: String,
@SerializedName("description")
val description: String,
)
data class CategoryTemplate( data class CategoryTemplate(
@SerializedName("id") @SerializedName("id")
val id: Int, val id: Int,
@@ -295,19 +423,19 @@ data class CategoryTemplate(
@SerializedName("createdAt") @SerializedName("createdAt")
val createdAt: String, val createdAt: String,
@SerializedName("updatedAt") @SerializedName("updatedAt")
val updatedAt: String val updatedAt: String,
) @SerializedName("translations")
val translations: Map<String, CategoryTemplateTranslation>?
data class CategoryListResponse( ) {
@SerializedName("page") /**
val page: Int, * 获取本地化名称,优先使用当前语言的翻译,如果没有则使用默认名称
@SerializedName("pageSize") */
val pageSize: Int, fun getLocalizedName(): String {
@SerializedName("total") // 这里可以根据需要添加国际化逻辑
val total: Int, // 目前直接返回默认名称
@SerializedName("list") return name
val list: List<CategoryTemplate> }
) }
interface RaveNowAPI { interface RaveNowAPI {
@GET("membership/config") @GET("membership/config")
@@ -588,9 +716,26 @@ interface RaveNowAPI {
suspend fun getAgent( suspend fun getAgent(
@Query("page") page: Int = 1, @Query("page") page: Int = 1,
@Query("pageSize") pageSize: Int = 20, @Query("pageSize") pageSize: Int = 20,
@Query("withWorkflow") withWorkflow: Int = 1, @Query("order") order: String? = null,
@Query("orderKey") orderKey: String? = null,
@Query("createdAt") createdAt: String? = null,
@Query("updatedAt") updatedAt: String? = null,
@Query("createdStart") createdStart: String? = null,
@Query("createdEnd") createdEnd: String? = null,
@Query("updatedStart") updatedStart: String? = null,
@Query("updatedEnd") updatedEnd: String? = null,
@Query("title") title: String? = null,
@Query("authorId") authorId: Int? = null, @Query("authorId") authorId: Int? = null,
@Query("authorOpenId") authorOpenId: String? = null,
@Query("showPrivate") showPrivate: String? = null,
@Query("explore") explore: String? = null,
@Query("desc") desc: String? = null,
@Query("withWorkflow") withWorkflow: String? = null,
@Query("hasAvatar") hasAvatar: String? = null,
@Query("random") random: String? = null,
@Query("categoryName") categoryName: String? = null,
@Query("categoryIds") categoryIds: List<Int>? = null, @Query("categoryIds") categoryIds: List<Int>? = null,
@Query("uncategorized") uncategorized: String? = null,
): Response<DataContainer<ListContainer<Agent>>> ): Response<DataContainer<ListContainer<Agent>>>
@GET("outside/my/prompts") @GET("outside/my/prompts")
@@ -619,7 +764,10 @@ interface RaveNowAPI {
suspend fun agentMoment(@Body body: AgentMomentRequestBody): Response<DataContainer<String>> suspend fun agentMoment(@Body body: AgentMomentRequestBody): Response<DataContainer<String>>
@GET("outside/rooms/open") @GET("outside/rooms/open")
suspend fun createGroupChatAi(@Query("trtcGroupId") trtcGroupId: String): Response<DataContainer<Unit>> suspend fun createGroupChatAi(
@Query("trtcGroupId") trtcGroupId: String? = null,
@Query("roomId") roomId: Int? = null
): Response<DataContainer<GroupChatResponse>>
@POST("outside/rooms/create-single-chat") @POST("outside/rooms/create-single-chat")
suspend fun createSingleChat(@Body body: SingleChatRequestBody): Response<DataContainer<Unit>> suspend fun createSingleChat(@Body body: SingleChatRequestBody): Response<DataContainer<Unit>>
@@ -634,6 +782,7 @@ interface RaveNowAPI {
suspend fun getRooms(@Query("page") page: Int = 1, suspend fun getRooms(@Query("page") page: Int = 1,
@Query("pageSize") pageSize: Int = 20, @Query("pageSize") pageSize: Int = 20,
@Query("isRecommended") isRecommended: Int = 1, @Query("isRecommended") isRecommended: Int = 1,
@Query("random") random: Int? = null,
): Response<ListContainer<Room>> ): Response<ListContainer<Room>>
@GET("outside/rooms/detail") @GET("outside/rooms/detail")
@@ -655,7 +804,7 @@ interface RaveNowAPI {
@Query("withParent") withParent: Boolean? = null, @Query("withParent") withParent: Boolean? = null,
@Query("withCount") withCount: Boolean? = null, @Query("withCount") withCount: Boolean? = null,
@Query("hideEmpty") hideEmpty: Boolean? = null @Query("hideEmpty") hideEmpty: Boolean? = null
): Response<DataContainer<CategoryListResponse>> ): Response<ListContainer<CategoryTemplate>>
@GET("outside/categories/tree") @GET("outside/categories/tree")
suspend fun getCategoryTree( suspend fun getCategoryTree(
@@ -668,14 +817,6 @@ interface RaveNowAPI {
@Path("id") id: Int @Path("id") id: Int
): Response<DataContainer<CategoryTemplate>> ): Response<DataContainer<CategoryTemplate>>
@GET("outside/prompts")
suspend fun getPromptsByCategory(
@Query("categoryIds") categoryIds: List<Int>? = null,
@Query("categoryName") categoryName: String? = null,
@Query("uncategorized") uncategorized: String? = null,
@Query("page") page: Int? = null,
@Query("pageSize") pageSize: Int? = null
): Response<ListContainer<Agent>>
} }

View File

@@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
@@ -28,19 +29,15 @@ import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Icon import androidx.compose.material.Icon
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Scaffold
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
@@ -52,6 +49,10 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.compose.material.CircularProgressIndicator
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import com.aiosman.ravenow.AppStore import com.aiosman.ravenow.AppStore
@@ -78,9 +79,7 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.ui.zIndex
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items as gridItems
// 检测是否接近列表底部的扩展函数 // 检测是否接近列表底部的扩展函数
fun LazyListState.isScrolledToEnd(buffer: Int = 3): Boolean { fun LazyListState.isScrolledToEnd(buffer: Int = 3): Boolean {
@@ -91,7 +90,7 @@ fun LazyListState.isScrolledToEnd(buffer: Int = 3): Boolean {
return lastVisibleItemIndex >= (totalItemsCount - buffer) return lastVisibleItemIndex >= (totalItemsCount - buffer)
} }
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
fun Agent() { fun Agent() {
val AppColors = LocalAppTheme.current val AppColors = LocalAppTheme.current
@@ -111,6 +110,19 @@ fun Agent() {
viewModel.ensureDataLoaded() viewModel.ensureDataLoaded()
} }
// 列表滚动状态(用于吸顶与加载更多)
val listState = rememberLazyListState()
// 监听滚动到底部,加载更多网格数据
LaunchedEffect(listState) {
snapshotFlow { listState.isScrolledToEnd() }
.collect { atEnd ->
if (atEnd && !viewModel.isLoading) {
viewModel.loadMoreGridAgentData()
}
}
}
// 防抖状态 // 防抖状态
var lastClickTime by remember { mutableStateOf(0L) } var lastClickTime by remember { mutableStateOf(0L) }
@@ -122,38 +134,35 @@ fun Agent() {
} }
} }
val agentItems = viewModel.agentItems Box(
var selectedTabIndex by remember { mutableStateOf(0) } modifier = Modifier.fillMaxSize()
) {
// 无限滚动状态 // 固定顶部搜索条
val listState = rememberLazyListState() Box(
modifier = Modifier
// 创建一个可观察的滚动到底部状态 .fillMaxWidth()
val isScrolledToEnd by remember { .background(AppColors.background)
derivedStateOf { .zIndex(999.0f)
listState.isScrolledToEnd() .height(44.dp + statusBarPaddingValues.calculateTopPadding())
} ) {
} Row(
modifier = Modifier
// 检测滚动到底部并加载更多数据 .fillMaxWidth()
LaunchedEffect(isScrolledToEnd) { .fillMaxHeight().padding(top = 32.dp, start = 16.dp, end = 16.dp),
if (isScrolledToEnd && !viewModel.isLoadingMore && agentItems.isNotEmpty() && viewModel.hasMoreData) { horizontalArrangement = Arrangement.Start,
viewModel.loadMoreAgents() verticalAlignment = Alignment.CenterVertically
} ) {
} Text(
Scaffold(
topBar = {
TopAppBar(
title = {
androidx.compose.material3.Text(
text = "Rave AI", text = "Rave AI",
fontSize = 20.sp, fontSize = 20.sp,
fontWeight = FontWeight.W900, fontWeight = FontWeight.W900,
color = AppColors.text color = AppColors.text,
modifier = Modifier
.align(Alignment.CenterVertically)
) )
},
actions = { Spacer(modifier = Modifier.weight(1f))
Image( Image(
painter = painterResource(id = R.drawable.rider_pro_nav_search), painter = painterResource(id = R.drawable.rider_pro_nav_search),
contentDescription = "search", contentDescription = "search",
@@ -164,28 +173,22 @@ fun Agent() {
}, },
colorFilter = ColorFilter.tint(AppColors.text) colorFilter = ColorFilter.tint(AppColors.text)
) )
}, }
colors = TopAppBarDefaults.topAppBarColors( }
containerColor = AppColors.background
)
)
},
containerColor = AppColors.background,
contentWindowInsets = WindowInsets(0, 0, 0, 0)
) { paddingValues ->
LazyColumn( LazyColumn(
state = listState, state = listState,
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.padding(paddingValues)
.padding( .padding(
top = 44.dp + statusBarPaddingValues.calculateTopPadding() + 15.dp,
bottom = navigationBarPaddings, bottom = navigationBarPaddings,
start = 16.dp, start = 16.dp,
end = 16.dp end = 16.dp
) )
) { ) {
// 类别标签页 - 吸顶 // 动态标签页
stickyHeader(key = "category_tabs") { stickyHeader(key = "category_tabs") {
Column( Column(
modifier = Modifier modifier = Modifier
@@ -193,101 +196,33 @@ fun Agent() {
.background(AppColors.background) .background(AppColors.background)
.padding(top = 4.dp, bottom = 8.dp) .padding(top = 4.dp, bottom = 8.dp)
) { ) {
val selectedTabIndex = viewModel.selectedCategoryIndex
if (viewModel.categories.isNotEmpty()) {
LazyRow( LazyRow(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.wrapContentHeight() .wrapContentHeight()
.padding(bottom = 8.dp), .padding(bottom = 16.dp),
horizontalArrangement = Arrangement.Start, horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
viewModel.categories.forEachIndexed { index, category ->
item { item {
CustomTabItem( CustomTabItem(
text = stringResource(R.string.agent_recommend), text = category.getLocalizedName(),
isSelected = selectedTabIndex == 0, isSelected = selectedTabIndex == index,
onClick = { onClick = { viewModel.selectCategory(index) }
selectedTabIndex = 0
viewModel.loadAllAgents()
}
) )
} }
if (index < viewModel.categories.size - 1) {
item { item { TabSpacer() }
TabSpacer()
}
// 动态添加分类标签
viewModel.categories.take(4).forEachIndexed { index, category ->
item {
CustomTabItem(
text = category.name,
isSelected = selectedTabIndex == index + 1,
onClick = {
selectedTabIndex = index + 1
viewModel.loadAgentsByCategory(category.id)
}
)
}
item {
TabSpacer()
} }
} }
item {
CustomTabItem(
text = "scenes",
isSelected = selectedTabIndex == 1,
onClick = {
selectedTabIndex = 1
}
)
}
item {
TabSpacer()
}
item {
CustomTabItem(
text = "voices",
isSelected = selectedTabIndex == 6,
onClick = {
selectedTabIndex = 6
}
)
}
item {
TabSpacer()
}
item {
CustomTabItem(
text = "anime",
isSelected = selectedTabIndex == 7,
onClick = {
selectedTabIndex = 7
}
)
}
item {
TabSpacer()
}
item {
CustomTabItem(
text = "assist",
isSelected = selectedTabIndex == 8,
onClick = {
selectedTabIndex = 8
}
)
} }
} }
} }
} }
// 推荐内容区域 // 推荐内容区域
item { item {
Column( Column(
@@ -295,23 +230,59 @@ fun Agent() {
.fillMaxWidth() .fillMaxWidth()
.padding(vertical = 8.dp) .padding(vertical = 8.dp)
) { ) {
when { AgentViewPagerSection(agentItems = viewModel.topAgentItems, viewModel)
selectedTabIndex == 0 -> {
AgentViewPagerSection(agentItems = viewModel.agentItems.take(15), viewModel)
} }
selectedTabIndex in 1..viewModel.categories.size -> {
AgentViewPagerSection(agentItems = viewModel.agentItems.take(15), viewModel)
} }
else -> {
val shuffledAgents = viewModel.agentItems.shuffled().take(15) // 热门聊天室
AgentViewPagerSection(agentItems = shuffledAgents, viewModel) stickyHeader(key = "hot_rooms_header") {
Row(
modifier = Modifier
.fillMaxWidth()
.background(AppColors.background)
.padding(top = 8.dp, bottom = 12.dp),
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(R.mipmap.rider_pro_hot_room),
contentDescription = "chat room",
modifier = Modifier.size(28.dp)
)
Spacer(modifier = Modifier.width(4.dp))
androidx.compose.material3.Text(
text = stringResource(R.string.hot_rooms),
fontSize = 16.sp,
fontWeight = androidx.compose.ui.text.font.FontWeight.W900,
color = AppColors.text
)
} }
} }
// 热门聊天室网格
items(viewModel.chatRooms.chunked(2)) { rowRooms ->
Row(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 12.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
rowRooms.forEach { chatRoom ->
ChatRoomCard(
chatRoom = chatRoom,
navController = LocalNavController.current,
modifier = Modifier.weight(1f)
)
}
if (rowRooms.size == 1) {
Spacer(modifier = Modifier.weight(1f))
}
} }
} }
item { Spacer(modifier = Modifier.height(20.dp)) }
// "发现更多" 标题 - 吸顶 // 发现区域
stickyHeader(key = "discover_more") { stickyHeader(key = "discover_more") {
Row( Row(
modifier = Modifier modifier = Modifier
@@ -322,9 +293,9 @@ fun Agent() {
verticalAlignment = Alignment.Bottom verticalAlignment = Alignment.Bottom
) { ) {
Image( Image(
painter = painterResource(R.mipmap.rider_pro_agent2), painter = painterResource(R.mipmap.bars_x_buttons_home_n_copy_2),
contentDescription = "agent", contentDescription = "agent",
modifier = Modifier.size(28.dp), modifier = Modifier.size(28.dp)
) )
Spacer(modifier = Modifier.width(4.dp)) Spacer(modifier = Modifier.width(4.dp))
androidx.compose.material3.Text( androidx.compose.material3.Text(
@@ -335,38 +306,42 @@ fun Agent() {
) )
} }
} }
item { Spacer(modifier = Modifier.height(20.dp)) }
// Agent网格 - 使用行式布局 // Agent 两列网格行
items( items(viewModel.gridAgentItems.chunked(2)) { rowItems ->
items = agentItems.chunked(2),
key = { row -> row.firstOrNull()?.openId ?: "" }
) { rowItems ->
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(vertical = 16.dp), .padding(
top = 20.dp,
bottom = 20.dp
),
horizontalArrangement = Arrangement.spacedBy(16.dp) horizontalArrangement = Arrangement.spacedBy(16.dp)
) { ) {
rowItems.forEach { agentItem -> Box(modifier = Modifier.weight(1f)) {
Box(
modifier = Modifier.weight(1f)
) {
AgentCardSquare( AgentCardSquare(
agentItem = agentItem, agentItem = rowItems[0],
viewModel = viewModel, viewModel = viewModel,
navController = LocalNavController.current navController = LocalNavController.current
) )
} }
if (rowItems.size > 1) {
Box(modifier = Modifier.weight(1f)) {
AgentCardSquare(
agentItem = rowItems[1],
viewModel = viewModel,
navController = LocalNavController.current
)
} }
// 如果这一行只有一个item添加一个空的占位符 } else {
if (rowItems.size == 1) {
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
} }
} }
} }
// 加载更多指示器 // 加载更多指示器
if (viewModel.isLoadingMore) { if (viewModel.isLoading) {
item { item {
Row( Row(
modifier = Modifier modifier = Modifier
@@ -381,7 +356,7 @@ fun Agent() {
) )
Spacer(modifier = Modifier.width(12.dp)) Spacer(modifier = Modifier.width(12.dp))
androidx.compose.material3.Text( androidx.compose.material3.Text(
text = "加载中...", text = stringResource(R.string.agent_chat_loading),
color = AppColors.secondaryText, color = AppColors.secondaryText,
fontSize = 14.sp fontSize = 14.sp
) )
@@ -391,11 +366,67 @@ fun Agent() {
} }
} }
} }
@Composable
fun AgentGridLayout(
agentItems: List<AgentItem>,
viewModel: AgentViewModel,
navController: NavHostController
) {
Column(
modifier = Modifier.fillMaxWidth()
) {
// 将agentItems按两列分组
agentItems.chunked(2).forEachIndexed { rowIndex, rowItems ->
Row(
modifier = Modifier
.fillMaxWidth()
.padding(
top = if (rowIndex == 0) 30.dp else 20.dp, // 第一行添加更多顶部间距
bottom = 20.dp
),
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
// 第一列
Box(
modifier = Modifier.weight(1f)
) {
AgentCardSquare(
agentItem = rowItems[0],
viewModel = viewModel,
navController = navController
)
}
// 第二列(如果存在)
if (rowItems.size > 1) {
Box(
modifier = Modifier.weight(1f)
) {
AgentCardSquare(
agentItem = rowItems[1],
viewModel = viewModel,
navController = navController
)
}
} else {
// 如果只有一列,添加空白占位
Spacer(modifier = Modifier.weight(1f))
}
}
}
}
}
@SuppressLint("SuspiciousIndentation") @SuppressLint("SuspiciousIndentation")
@Composable @Composable
fun AgentCardSquare(agentItem: AgentItem, viewModel: AgentViewModel, navController: NavHostController) { fun AgentCardSquare(
agentItem: AgentItem,
viewModel: AgentViewModel,
navController: NavHostController
) {
val AppColors = LocalAppTheme.current val AppColors = LocalAppTheme.current
val cardHeight = 200.dp val cardHeight = 180.dp
val avatarSize = cardHeight / 3 // 头像大小为方块高度的三分之一 val avatarSize = cardHeight / 3 // 头像大小为方块高度的三分之一
// 防抖状态 // 防抖状态
@@ -404,9 +435,8 @@ fun AgentCardSquare(agentItem: AgentItem, viewModel: AgentViewModel, navControll
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(top = avatarSize / 2)
.height(cardHeight) .height(cardHeight)
.background(AppColors.nonActive, RoundedCornerShape(12.dp)) // 修改背景颜色 .background(AppColors.secondaryBackground, RoundedCornerShape(12.dp))
.clickable { .clickable {
if (DebounceUtils.simpleDebounceClick(lastClickTime, 500L) { if (DebounceUtils.simpleDebounceClick(lastClickTime, 500L) {
viewModel.goToProfile(agentItem.openId, navController) viewModel.goToProfile(agentItem.openId, navController)
@@ -416,12 +446,11 @@ fun AgentCardSquare(agentItem: AgentItem, viewModel: AgentViewModel, navControll
}, },
contentAlignment = Alignment.TopCenter contentAlignment = Alignment.TopCenter
) { ) {
// 头像,位于方块上方居中,部分悬于方块外部
Box( Box(
modifier = Modifier modifier = Modifier
.offset(y = -avatarSize / 2) .offset(y = 4.dp)
.size(avatarSize) .size(avatarSize)
.background(Color.White, RoundedCornerShape(avatarSize / 2)) .background(AppColors.background, RoundedCornerShape(avatarSize / 2))
.clip(RoundedCornerShape(avatarSize / 2)), .clip(RoundedCornerShape(avatarSize / 2)),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
@@ -429,9 +458,7 @@ fun AgentCardSquare(agentItem: AgentItem, viewModel: AgentViewModel, navControll
painter = painterResource(R.mipmap.group_copy), painter = painterResource(R.mipmap.group_copy),
contentDescription = "默认头像", contentDescription = "默认头像",
modifier = Modifier.size(avatarSize), modifier = Modifier.size(avatarSize),
contentScale = androidx.compose.ui.layout.ContentScale.Crop
) )
if (agentItem.avatar.isNotEmpty()) { if (agentItem.avatar.isNotEmpty()) {
CustomAsyncImage( CustomAsyncImage(
imageUrl = agentItem.avatar, imageUrl = agentItem.avatar,
@@ -448,7 +475,7 @@ fun AgentCardSquare(agentItem: AgentItem, viewModel: AgentViewModel, navControll
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(top = avatarSize / 2 + 8.dp, start = 8.dp, end = 8.dp, bottom = 8.dp), .padding(top = 4.dp + avatarSize + 8.dp, start = 8.dp, end = 8.dp, bottom = 48.dp), // 为底部聊天按钮留出空间
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
androidx.compose.material3.Text( androidx.compose.material3.Text(
@@ -462,30 +489,31 @@ fun AgentCardSquare(agentItem: AgentItem, viewModel: AgentViewModel, navControll
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
Box(
modifier = Modifier
.height(85.dp)
.fillMaxWidth()
) {
androidx.compose.material3.Text( androidx.compose.material3.Text(
text = agentItem.desc, text = agentItem.desc,
fontSize = 12.sp, fontSize = 12.sp,
color = AppColors.secondaryText, color = AppColors.secondaryText,
maxLines = 5, maxLines = 2,
overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis, overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis,
modifier = Modifier.weight(1f, fill = false)
) )
} }
Spacer(modifier = Modifier.height(8.dp)) // 聊天按钮,固定在底部居中,距离底部有一定边距
// 聊天按钮,位于底部居中
Box( Box(
modifier = Modifier modifier = Modifier
.align(Alignment.BottomCenter)
.padding(bottom = 12.dp) // 距离底部的边距
.width(60.dp) .width(60.dp)
.height(32.dp) .height(32.dp)
.background( .background(
color = Color(0X147c7480), color = AppColors.text,
shape = RoundedCornerShape(8.dp) shape = RoundedCornerShape(
topStart = 10.dp,
topEnd = 10.dp,
bottomStart = 0.dp,
bottomEnd = 10.dp
)
) )
.clickable { .clickable {
if (DebounceUtils.simpleDebounceClick(lastClickTime, 500L) { if (DebounceUtils.simpleDebounceClick(lastClickTime, 500L) {
@@ -507,14 +535,14 @@ fun AgentCardSquare(agentItem: AgentItem, viewModel: AgentViewModel, navControll
) { ) {
androidx.compose.material3.Text( androidx.compose.material3.Text(
text = stringResource(R.string.chat), text = stringResource(R.string.chat),
fontSize = 12.sp, fontSize = 15.sp,
color = AppColors.text, color = AppColors.background,
fontWeight = androidx.compose.ui.text.font.FontWeight.W500 fontWeight = androidx.compose.ui.text.font.FontWeight.W500
) )
} }
} }
} }
}
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
fun AgentViewPagerSection(agentItems: List<AgentItem>, viewModel: AgentViewModel) { fun AgentViewPagerSection(agentItems: List<AgentItem>, viewModel: AgentViewModel) {
@@ -531,7 +559,7 @@ fun AgentViewPagerSection(agentItems: List<AgentItem>,viewModel: AgentViewModel)
// Agent内容 // Agent内容
Box( Box(
modifier = Modifier modifier = Modifier
.height(310.dp) .height(300.dp)
) { ) {
HorizontalPager( HorizontalPager(
state = pagerState, state = pagerState,
@@ -553,7 +581,7 @@ fun AgentViewPagerSection(agentItems: List<AgentItem>,viewModel: AgentViewModel)
agentItems = agentItems.drop(page * itemsPerPage).take(itemsPerPage), agentItems = agentItems.drop(page * itemsPerPage).take(itemsPerPage),
page = page, page = page,
modifier = Modifier modifier = Modifier
.height(310.dp) .height(300.dp)
.graphicsLayer { .graphicsLayer {
scaleX = scale scaleX = scale
scaleY = scale scaleY = scale
@@ -590,7 +618,13 @@ fun AgentViewPagerSection(agentItems: List<AgentItem>,viewModel: AgentViewModel)
} }
@Composable @Composable
fun AgentPage(viewModel: AgentViewModel,agentItems: List<AgentItem>, page: Int, modifier: Modifier = Modifier,navController: NavHostController) { fun AgentPage(
viewModel: AgentViewModel,
agentItems: List<AgentItem>,
page: Int,
modifier: Modifier = Modifier,
navController: NavHostController
) {
Column( Column(
modifier = modifier modifier = modifier
.fillMaxSize() .fillMaxSize()
@@ -598,7 +632,11 @@ fun AgentPage(viewModel: AgentViewModel,agentItems: List<AgentItem>, page: Int,
) { ) {
// 显示3个agent // 显示3个agent
agentItems.forEachIndexed { index, agentItem -> agentItems.forEachIndexed { index, agentItem ->
AgentCard2(agentItem = agentItem, viewModel = viewModel, navController = LocalNavController.current) AgentCard2(
agentItem = agentItem,
viewModel = viewModel,
navController = LocalNavController.current
)
if (index < agentItems.size - 1) { if (index < agentItems.size - 1) {
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
} }
@@ -624,7 +662,7 @@ fun AgentCard2(viewModel: AgentViewModel,agentItem: AgentItem,navController: Nav
Box( Box(
modifier = Modifier modifier = Modifier
.size(48.dp) .size(48.dp)
.background(Color(0x00F5F5F5), RoundedCornerShape(24.dp)) .background(AppColors.secondaryBackground, RoundedCornerShape(24.dp))
.clickable { .clickable {
if (DebounceUtils.simpleDebounceClick(lastClickTime, 500L) { if (DebounceUtils.simpleDebounceClick(lastClickTime, 500L) {
viewModel.goToProfile(agentItem.openId, navController) viewModel.goToProfile(agentItem.openId, navController)
@@ -634,12 +672,6 @@ fun AgentCard2(viewModel: AgentViewModel,agentItem: AgentItem,navController: Nav
}, },
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
Image(
painter = painterResource(R.mipmap.group_copy),
contentDescription = "默认头像",
modifier = Modifier.size(48.dp),
)
if (agentItem.avatar.isNotEmpty()) { if (agentItem.avatar.isNotEmpty()) {
CustomAsyncImage( CustomAsyncImage(
imageUrl = agentItem.avatar, imageUrl = agentItem.avatar,
@@ -649,6 +681,13 @@ fun AgentCard2(viewModel: AgentViewModel,agentItem: AgentItem,navController: Nav
.clip(RoundedCornerShape(24.dp)), .clip(RoundedCornerShape(24.dp)),
contentScale = androidx.compose.ui.layout.ContentScale.Crop contentScale = androidx.compose.ui.layout.ContentScale.Crop
) )
} else {
Image(
painter = painterResource(R.mipmap.rider_pro_agent),
contentDescription = "默认头像",
modifier = Modifier.size(24.dp),
colorFilter = ColorFilter.tint(AppColors.secondaryText)
)
} }
} }
@@ -687,7 +726,7 @@ fun AgentCard2(viewModel: AgentViewModel,agentItem: AgentItem,navController: Nav
modifier = Modifier modifier = Modifier
.size(width = 60.dp, height = 32.dp) .size(width = 60.dp, height = 32.dp)
.background( .background(
color = Color(0X147c7480), color = AppColors.inputBackground,
shape = RoundedCornerShape(8.dp) shape = RoundedCornerShape(8.dp)
) )
.clickable { .clickable {
@@ -717,3 +756,180 @@ fun AgentCard2(viewModel: AgentViewModel,agentItem: AgentItem,navController: Nav
} }
} }
} }
@Composable
fun ChatRoomsSection(
chatRooms: List<ChatRoom>,
navController: NavHostController
) {
val AppColors = LocalAppTheme.current
Column(
modifier = Modifier.fillMaxWidth()
) {
// 标题
Row(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(R.mipmap.rider_pro_hot_room),
contentDescription = "chat room",
modifier = Modifier.size(28.dp)
)
Spacer(modifier = Modifier.width(4.dp))
androidx.compose.material3.Text(
text = stringResource(R.string.hot_rooms),
fontSize = 16.sp,
fontWeight = androidx.compose.ui.text.font.FontWeight.W900,
color = AppColors.text
)
}
Column(
modifier = Modifier.fillMaxWidth()
) {
chatRooms.chunked(2).forEach { rowRooms ->
Row(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 12.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
rowRooms.forEach { chatRoom ->
ChatRoomCard(
chatRoom = chatRoom,
navController = navController,
modifier = Modifier.weight(1f)
)
}
}
}
}
}
}
@Composable
fun ChatRoomCard(
chatRoom: ChatRoom,
navController: NavHostController,
modifier: Modifier = Modifier
) {
val AppColors = LocalAppTheme.current
val cardSize = 160.dp
val viewModel: AgentViewModel = viewModel()
val context = LocalContext.current
// 防抖状态
var lastClickTime by remember { mutableStateOf(0L) }
// Loading 对话框
if (viewModel.isJoiningRoom) {
Dialog(
onDismissRequest = { /* 阻止用户关闭对话框 */ },
properties = DialogProperties(
dismissOnBackPress = false,
dismissOnClickOutside = false
)
) {
Box(
modifier = Modifier
.size(120.dp)
.background(
color = AppColors.background,
shape = RoundedCornerShape(12.dp)
),
contentAlignment = Alignment.Center
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
CircularProgressIndicator(
modifier = Modifier.size(32.dp),
color = AppColors.main
)
Spacer(modifier = Modifier.height(12.dp))
androidx.compose.material3.Text(
text = "加入中...",
fontSize = 14.sp,
color = AppColors.text
)
}
}
}
}
// 正方形卡片,文字重叠在底部
Box(
modifier = modifier
.size(cardSize)
.background(AppColors.secondaryBackground, RoundedCornerShape(12.dp))
.clickable(enabled = !viewModel.isJoiningRoom) {
if (!viewModel.isJoiningRoom && DebounceUtils.simpleDebounceClick(lastClickTime, 500L) {
// 加入群聊房间
viewModel.joinRoom(
id = chatRoom.id,
name = chatRoom.name,
avatar = chatRoom.avatar,
context = context,
navController = navController,
onSuccess = {
// 成功加入房间
},
onError = { errorMsg ->
// 处理错误可以显示Toast或其他提示
}
)
}) {
lastClickTime = System.currentTimeMillis()
}
}
) {
// 优先显示banner如果没有banner则显示头像
val imageUrl = if (chatRoom.banner.isNotEmpty()) chatRoom.banner else chatRoom.avatar
if (imageUrl.isNotEmpty()) {
CustomAsyncImage(
imageUrl = imageUrl,
contentDescription = if (chatRoom.banner.isNotEmpty()) "房间banner" else "房间头像",
modifier = Modifier
.size(cardSize)
.clip(RoundedCornerShape(12.dp)),
contentScale = androidx.compose.ui.layout.ContentScale.Crop
)
} else {
// 默认房间图标
Image(
painter = painterResource(R.mipmap.rider_pro_agent),
contentDescription = "默认房间图标",
modifier = Modifier.size(cardSize * 0.4f),
colorFilter = ColorFilter.tint(AppColors.secondaryText)
)
}
// 房间名称,重叠在底部
Box(
modifier = Modifier
.align(Alignment.BottomCenter)
.fillMaxWidth()
.background(
color = Color.Black.copy(alpha = 0.6f),
shape = RoundedCornerShape(bottomStart = 12.dp, bottomEnd = 12.dp)
)
.padding(horizontal = 8.dp, vertical = 6.dp)
) {
androidx.compose.material3.Text(
text = chatRoom.name,
fontSize = 12.sp,
color = Color.White,
maxLines = 1,
overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis,
modifier = Modifier.fillMaxWidth(),
textAlign = androidx.compose.ui.text.style.TextAlign.Center
)
}
}
}

View File

@@ -1,185 +1,351 @@
package com.aiosman.ravenow.ui.index.tabs.ai package com.aiosman.ravenow.ui.index.tabs.ai
import android.util.Log
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import com.aiosman.ravenow.data.Agent import com.aiosman.ravenow.AppState
import com.aiosman.ravenow.data.ListContainer import com.aiosman.ravenow.AppStore
import com.aiosman.ravenow.ConstVars
import com.aiosman.ravenow.data.Room
import com.aiosman.ravenow.data.api.ApiClient import com.aiosman.ravenow.data.api.ApiClient
import com.aiosman.ravenow.data.api.CategoryTemplate import com.aiosman.ravenow.data.api.CategoryTemplate
import com.aiosman.ravenow.data.api.CreateGroupChatRequestBody
import com.aiosman.ravenow.data.api.RaveNowAPI import com.aiosman.ravenow.data.api.RaveNowAPI
import com.aiosman.ravenow.data.api.SingleChatRequestBody import com.aiosman.ravenow.data.api.SingleChatRequestBody
import com.aiosman.ravenow.data.api.JoinGroupChatRequestBody
import com.aiosman.ravenow.ui.index.tabs.ai.tabs.mine.MineAgentViewModel.createGroup2ChatAi import com.aiosman.ravenow.ui.index.tabs.ai.tabs.mine.MineAgentViewModel.createGroup2ChatAi
import com.aiosman.ravenow.ui.NavigationRoute import com.aiosman.ravenow.ui.NavigationRoute
import com.aiosman.ravenow.ui.index.tabs.message.MessageListViewModel.userService import com.aiosman.ravenow.ui.index.tabs.message.MessageListViewModel.userService
import com.aiosman.ravenow.ui.index.tabs.message.tab.GroupChatListViewModel.createGroupChat
import com.aiosman.ravenow.ui.index.tabs.moment.tabs.expolre.AgentItem import com.aiosman.ravenow.ui.index.tabs.moment.tabs.expolre.AgentItem
import com.aiosman.ravenow.ui.navigateToGroupChat
import com.aiosman.ravenow.data.api.ApiErrorResponse
import com.google.gson.Gson
import android.content.Context
import android.widget.Toast
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
data class ChatRoom(
val id: Int,
val name: String,
val avatar: String = "",
val banner: String = "",
val memberCount: Int = 0
)
object AgentViewModel : ViewModel() { object AgentViewModel : ViewModel() {
private val apiClient: RaveNowAPI = ApiClient.api private val apiClient: RaveNowAPI = ApiClient.api
var agentItems by mutableStateOf<List<AgentItem>>(emptyList()) // 顶部Agent列表数据用于ViewPager
var topAgentItems by mutableStateOf<List<AgentItem>>(emptyList())
private set private set
var categories by mutableStateOf<List<CategoryItem>>(emptyList()) // 底部网格布局Agent数据
var gridAgentItems by mutableStateOf<List<AgentItem>>(emptyList())
private set
var chatRooms by mutableStateOf<List<ChatRoom>>(emptyList())
private set
var rooms by mutableStateOf<List<Room>>(emptyList())
private set
var categories by mutableStateOf<List<CategoryTemplate>>(emptyList())
private set
var selectedCategoryIndex by mutableStateOf(0)
private set private set
var errorMessage by mutableStateOf<String?>(null) var errorMessage by mutableStateOf<String?>(null)
private set private set
var isRefreshing by mutableStateOf(false) var isRefreshing by mutableStateOf(false)
private set private set
var isLoading by mutableStateOf(false) var isLoading by mutableStateOf(false)
private set private set
// 分页相关状态 var isJoiningRoom by mutableStateOf(false)
var isLoadingMore by mutableStateOf(false)
private set private set
var currentPage by mutableStateOf(1) private var topCurrentPage = 1
private set private var topHasMoreData = true
var hasMoreData by mutableStateOf(true) private var gridCurrentPage = 1
private set private var gridHasMoreData = true
private val pageSize = 20
private var currentCategoryId: Int? = null
init { init {
loadAgentData() loadTopAgentData()
loadGridAgentData()
loadChatRooms()
loadCategories() loadCategories()
} }
private fun loadAgentData(categoryId: Int? = null, page: Int = 1, isLoadMore: Boolean = false) { /**
* 加载顶部Agent列表数据用于ViewPager
*/
private fun loadTopAgentData(categoryIndex: Int = 0) {
viewModelScope.launch { viewModelScope.launch {
if (isLoadMore) {
isLoadingMore = true
} else {
isLoading = true isLoading = true
// 重置分页状态
currentPage = 1
hasMoreData = true
currentCategoryId = categoryId
}
errorMessage = null errorMessage = null
topCurrentPage = 1
topHasMoreData = true
try { try {
val response = if (categoryId != null) { val selectedCategory =
// 根据分类ID获取智能体 if (categoryIndex < categories.size) categories[categoryIndex] else null
val response = if (categoryIndex == 0 || selectedCategory == null) {
// 推荐分类或无效分类,加载所有 Agent
apiClient.getAgent( apiClient.getAgent(
page = page, page = topCurrentPage,
pageSize = pageSize, pageSize = 15,
withWorkflow = 1, withWorkflow = "1",
categoryIds = listOf(categoryId) random = "1"
) )
} else { } else {
// 获取所有智能体 // 特定分类,使用 categoryName 参数
apiClient.getAgent(page = page, pageSize = pageSize, withWorkflow = 1) apiClient.getAgent(
page = topCurrentPage,
pageSize = 15,
withWorkflow = "1",
categoryName = selectedCategory.name,
random = "1"
)
} }
if (response.isSuccessful) { if (response.isSuccessful) {
val responseData = response.body()?.data val agents = response.body()?.data?.list ?: emptyList()
val agents = responseData?.list ?: emptyList<Agent>() topAgentItems = agents.map { agent ->
val newAgentItems = agents.map { agent ->
AgentItem.fromAgent(agent) AgentItem.fromAgent(agent)
} }
topHasMoreData = agents.size >= 15
if (isLoadMore) {
// 加载更多:追加到现有列表
agentItems = agentItems + newAgentItems
currentPage = page
} else { } else {
// 首次加载或刷新:替换整个列表 errorMessage = "获取顶部Agent数据失败: ${response.code()}"
agentItems = newAgentItems
currentPage = 1
}
// 检查是否还有更多数据
hasMoreData = agents.size >= pageSize
} else {
errorMessage = "获取Agent数据失败: ${response.code()}"
} }
} catch (e: Exception) { } catch (e: Exception) {
errorMessage = "网络请求失败: ${e.message}" errorMessage = "网络请求失败: ${e.message}"
} finally { } finally {
if (isLoadMore) {
isLoadingMore = false
} else {
isLoading = false isLoading = false
} }
} }
} }
/**
* 加载底部网格布局Agent数据
*/
private fun loadGridAgentData(categoryIndex: Int = 0) {
viewModelScope.launch {
isLoading = true
errorMessage = null
gridCurrentPage = 1
gridHasMoreData = true
try {
val selectedCategory =
if (categoryIndex < categories.size) categories[categoryIndex] else null
val response = if (categoryIndex == 0 || selectedCategory == null) {
// 推荐分类或无效分类,加载所有 Agent
apiClient.getAgent(
page = gridCurrentPage,
pageSize = 20,
withWorkflow = "1",
random = "true"
)
} else {
// 特定分类,使用 categoryName 参数
apiClient.getAgent(
page = gridCurrentPage,
pageSize = 20,
withWorkflow = "1",
categoryName = selectedCategory.name,
random = "true"
)
}
if (response.isSuccessful) {
val agents = response.body()?.data?.list ?: emptyList()
gridAgentItems = agents.map { agent ->
AgentItem.fromAgent(agent)
}
gridHasMoreData = agents.size >= 20
} else {
errorMessage = "获取网格Agent数据失败: ${response.code()}"
}
} catch (e: Exception) {
errorMessage = "网络请求失败: ${e.message}"
} finally {
isLoading = false
}
}
}
private fun loadChatRooms() {
viewModelScope.launch {
try {
val response = apiClient.getRooms(
page = 1,
pageSize = 21,
isRecommended = 1,
random = 1
) // 请求21个确保是3的倍数
if (response.isSuccessful) {
val allRooms = response.body()?.list ?: emptyList()
// 确保房间数量是3的倍数如果不足则截取如果超出则取前几个
val targetCount = (allRooms.size / 3) * 3 // 向下取整到最近的3的倍数
rooms = allRooms.take(targetCount)
// 转换为ChatRoom格式用于兼容现有UI
chatRooms = rooms.map { room ->
ChatRoom(
id = room.id,
name = room.name,
avatar = room.avatar,
banner = ConstVars.BASE_SERVER + "/api/v1/outside/" + room.recommendBanner + "?token=${AppStore.token}",
memberCount = room.userCount
)
}
} else {
}
} catch (e: Exception) {
// 如果网络请求失败,使用默认数据
}
}
} }
private fun loadCategories() { private fun loadCategories() {
viewModelScope.launch { viewModelScope.launch {
try { try {
val response = apiClient.getCategories( val categoriesResponse = apiClient.getCategories(
pageSize = 20, page = 1,
withChildren = false, pageSize = 100,
withParent = false,
withCount = true,
hideEmpty = true
) )
println("分类数据请求完成,响应成功: ${response.isSuccessful}") if (categoriesResponse.isSuccessful && categoriesResponse.body() != null) {
if (response.isSuccessful) { // 添加一个默认的"推荐"分类在第一位
val categoryList = response.body()?.data?.list ?: emptyList() val recommendCategory = createRecommendCategory()
println("获取到 ${categoryList.size} 个分类") val categoriesList = categoriesResponse.body()?.list ?: emptyList()
categories = categoryList.map { category -> categories = listOf(recommendCategory) + categoriesList
CategoryItem.fromCategoryTemplate(category)
// 分类加载完成后,重新加载当前选中分类的 Agent 数据
if (topAgentItems.isEmpty()) {
loadTopAgentData(selectedCategoryIndex)
}
if (gridAgentItems.isEmpty()) {
loadGridAgentData(selectedCategoryIndex)
} }
println("成功处理并映射了 ${categories.size} 个分类")
} else { } else {
errorMessage = "获取分类数据失败: ${response.code()}" // 如果请求失败,使用默认分类
println("获取分类数据失败: ${response.code()}") categories = listOf(createRecommendCategory())
errorMessage = "获取分类失败: ${categoriesResponse.code()}"
} }
} catch (e: Exception) { } catch (e: Exception) {
errorMessage = "获取分类数据失败: ${e.message}" // 如果网络请求失败,使用默认分类
println("获取分类数据异常: ${e.message}") categories = listOf(createRecommendCategory())
e.printStackTrace() errorMessage = "网络请求失败: ${e.message}"
} }
} }
} }
fun loadAgentsByCategory(categoryId: Int) {
loadAgentData(categoryId)
}
fun loadAllAgents() {
loadAgentData()
}
/** /**
* 加载更多Agent数据 * 创建推荐分类
*/ */
fun loadMoreAgents() { private fun createRecommendCategory(): CategoryTemplate {
// 检查是否正在加载或没有更多数据 return CategoryTemplate(
if (isLoadingMore || !hasMoreData) { id = 0,
return name = "推荐",
} description = "推荐内容",
avatar = "",
val nextPage = currentPage + 1 parentId = null,
loadAgentData( parent = null,
categoryId = currentCategoryId, children = null,
page = nextPage, sort = 0,
isLoadMore = true isActive = true,
promptCount = 0,
createdAt = "",
updatedAt = "",
translations = null
) )
} }
/**
* 加载更多网格Agent数据
*/
fun loadMoreGridAgentData() {
if (!gridHasMoreData || isLoading) return
viewModelScope.launch {
isLoading = true
try {
val nextPage = gridCurrentPage + 1
val selectedCategory =
if (selectedCategoryIndex < categories.size) categories[selectedCategoryIndex] else null
val response = if (selectedCategoryIndex == 0 || selectedCategory == null) {
apiClient.getAgent(
page = nextPage,
pageSize = 20,
withWorkflow = "1",
random = "true"
)
} else {
apiClient.getAgent(
page = nextPage,
pageSize = 20,
withWorkflow = "1",
categoryName = selectedCategory.name,
random = "true"
)
}
if (response.isSuccessful) {
val agents = response.body()?.data?.list ?: emptyList()
val newAgentItems = agents.map { agent ->
AgentItem.fromAgent(agent)
}
gridAgentItems = gridAgentItems + newAgentItems
gridCurrentPage = nextPage
gridHasMoreData = agents.size >= 20
} else {
errorMessage = "加载更多网格Agent数据失败: ${response.code()}"
}
} catch (e: Exception) {
errorMessage = "网络请求失败: ${e.message}"
} finally {
isLoading = false
}
}
}
/**
* 选择分类并加载对应的 Agent 数据
*/
fun selectCategory(categoryIndex: Int) {
if (categoryIndex != selectedCategoryIndex && categoryIndex >= 0 && categoryIndex < categories.size) {
selectedCategoryIndex = categoryIndex
// 同时加载顶部和网格的数据
loadTopAgentData(categoryIndex)
loadGridAgentData(categoryIndex)
}
}
fun createSingleChat( fun createSingleChat(
openId: String, openId: String,
) { ) {
viewModelScope.launch { viewModelScope.launch {
val response = ApiClient.api.createSingleChat(SingleChatRequestBody(agentOpenId = openId)) val response =
ApiClient.api.createSingleChat(SingleChatRequestBody(agentOpenId = openId))
Log.d("debug", response.toString())
} }
} }
fun goToChatAi( fun goToChatAi(
openId: String, openId: String,
navController: NavHostController navController: NavHostController
@@ -213,15 +379,98 @@ object AgentViewModel: ViewModel() {
* 刷新推荐Agent数据 * 刷新推荐Agent数据
*/ */
fun refreshAgentData() { fun refreshAgentData() {
loadAgentData() loadTopAgentData()
loadGridAgentData()
} }
/** /**
* 检查数据是否为空,如果为空则重新加载 * 检查数据是否为空,如果为空则重新加载
*/ */
fun ensureDataLoaded() { fun ensureDataLoaded() {
if (agentItems.isEmpty() && !isLoading) { if (topAgentItems.isEmpty() && !isLoading) {
loadAgentData() loadTopAgentData()
}
if (gridAgentItems.isEmpty() && !isLoading) {
loadGridAgentData()
}
// 同时确保分类数据已加载
if (categories.isEmpty() && !isLoading) {
loadCategories()
}
}
/**
* 加入房间
*/
fun joinRoom(
id: Int,
name: String,
avatar: String,
context: Context,
navController: NavHostController,
onSuccess: () -> Unit,
onError: (String) -> Unit
) {
// 防止重复点击
if (isJoiningRoom) return
viewModelScope.launch {
try {
isJoiningRoom = true
val response = apiClient.joinRoom(JoinGroupChatRequestBody(roomId = id))
if (response.isSuccessful) {
// 打开房间
val openRoomResponse = apiClient.createGroupChatAi(
roomId = id
)
if (openRoomResponse.isSuccessful){
val respData = openRoomResponse.body()
respData?.let {
viewModelScope.launch {
try {
// 群聊直接使用群ID进行导航
navController.navigateToGroupChat(
id = respData.data.trtcRoomId,
name = name,
avatar = avatar
)
} catch (e: Exception) {
onError("加入房间失败")
e.printStackTrace()
}
}
}
}
onSuccess()
} else {
// 处理错误响应
try {
val errorBody = response.errorBody()?.string()
if (errorBody != null) {
val gson = Gson()
val errorResponse = gson.fromJson(errorBody, ApiErrorResponse::class.java)
// 在主线程显示 Toast
Toast.makeText(context, errorResponse.error, Toast.LENGTH_LONG).show()
onError(errorResponse.error)
} else {
Toast.makeText(context, "加入房间失败", Toast.LENGTH_SHORT).show()
onError("加入房间失败")
}
} catch (parseException: Exception) {
// 如果解析错误响应失败,显示默认错误信息
Toast.makeText(context, "加入房间失败", Toast.LENGTH_SHORT).show()
onError("加入房间失败")
}
}
} catch (e: Exception) {
Toast.makeText(context, "网络请求失败:${e.message}", Toast.LENGTH_SHORT).show()
onError("网络请求失败:${e.message}")
} finally {
isJoiningRoom = false
}
} }
} }
@@ -229,33 +478,18 @@ object AgentViewModel: ViewModel() {
* 重置ViewModel状态用于登出或切换账号时清理数据 * 重置ViewModel状态用于登出或切换账号时清理数据
*/ */
fun ResetModel() { fun ResetModel() {
agentItems = emptyList() topAgentItems = emptyList()
gridAgentItems = emptyList()
categories = emptyList()
selectedCategoryIndex = 0
errorMessage = null errorMessage = null
isRefreshing = false isRefreshing = false
isLoading = false isLoading = false
isLoadingMore = false isJoiningRoom = false
currentPage = 1 topCurrentPage = 1
hasMoreData = true topHasMoreData = true
currentCategoryId = null gridCurrentPage = 1
gridHasMoreData = true
} }
} }
data class CategoryItem(
val id: Int,
val name: String,
val description: String,
val avatar: String,
val promptCount: Int?
) {
companion object {
fun fromCategoryTemplate(template: CategoryTemplate): CategoryItem {
return CategoryItem(
id = template.id,
name = template.name,
description = template.description,
avatar = "${ApiClient.BASE_API_URL}${template.avatar}",
promptCount = template.promptCount
)
}
}
}

View File

@@ -2,6 +2,7 @@ package com.aiosman.ravenow.ui.index.tabs.message.tab
import android.content.Context import android.content.Context
import android.icu.util.Calendar import android.icu.util.Calendar
import android.util.Log
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
@@ -167,10 +168,12 @@ object GroupChatListViewModel : ViewModel() {
} }
fun createGroupChat( fun createGroupChat(
trtcGroupId: String, trtcGroupId: String? = null,
roomId: Int? = null
) { ) {
viewModelScope.launch { viewModelScope.launch {
val response = ApiClient.api.createGroupChatAi(trtcGroupId = trtcGroupId) val response = ApiClient.api.createGroupChatAi(trtcGroupId = trtcGroupId,roomId = roomId)
Log.d("debug",response.toString())
} }
} }

View File

@@ -369,6 +369,7 @@ fun Explore() {
trtcId = roomItem.trtcId.toString(), trtcId = roomItem.trtcId.toString(),
name = roomItem.title, name = roomItem.title,
avatar = roomItem.avatar, avatar = roomItem.avatar,
context = context,
navController = navController, navController = navController,
onSuccess = { onSuccess = {
Toast.makeText(context, enterSuccessText, Toast.LENGTH_SHORT).show() Toast.makeText(context, enterSuccessText, Toast.LENGTH_SHORT).show()
@@ -636,6 +637,7 @@ fun Explore() {
trtcId = bannerItem.trtcId.toString(), trtcId = bannerItem.trtcId.toString(),
name = bannerItem.title, name = bannerItem.title,
avatar = bannerItem.avatar, avatar = bannerItem.avatar,
context = context,
navController = navController, navController = navController,
onSuccess = { onSuccess = {
Toast.makeText(context, enterSuccessText, Toast.LENGTH_SHORT).show() Toast.makeText(context, enterSuccessText, Toast.LENGTH_SHORT).show()

View File

@@ -17,6 +17,10 @@ import com.aiosman.ravenow.ui.index.tabs.message.MessageListViewModel.userServic
import com.aiosman.ravenow.ui.index.tabs.message.tab.GroupChatListViewModel import com.aiosman.ravenow.ui.index.tabs.message.tab.GroupChatListViewModel
import com.aiosman.ravenow.ui.index.tabs.message.tab.GroupChatListViewModel.createGroupChat import com.aiosman.ravenow.ui.index.tabs.message.tab.GroupChatListViewModel.createGroupChat
import com.aiosman.ravenow.ui.navigateToGroupChat import com.aiosman.ravenow.ui.navigateToGroupChat
import com.aiosman.ravenow.data.api.ApiErrorResponse
import com.google.gson.Gson
import android.content.Context
import android.widget.Toast
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class ExploreViewModel : ViewModel() { class ExploreViewModel : ViewModel() {
@@ -70,7 +74,7 @@ class ExploreViewModel : ViewModel() {
isRefreshing = true isRefreshing = true
errorMessage = null errorMessage = null
try { try {
val response = apiClient.getAgent(page = 1, pageSize = 20, withWorkflow = 1) val response = apiClient.getAgent(page = 1, pageSize = 20, withWorkflow = "1")
if (response.isSuccessful) { if (response.isSuccessful) {
val agents = response.body()?.data?.list ?: emptyList() val agents = response.body()?.data?.list ?: emptyList()
agentItems = agents.map { agent -> agentItems = agents.map { agent ->
@@ -114,7 +118,7 @@ class ExploreViewModel : ViewModel() {
isLoading = true isLoading = true
errorMessage = null errorMessage = null
try { try {
val response = apiClient.getAgent(page = 1, pageSize = 20, withWorkflow = 1) val response = apiClient.getAgent(page = 1, pageSize = 20, withWorkflow = "1")
if (response.isSuccessful) { if (response.isSuccessful) {
val agents = response.body()?.data?.list ?: emptyList() val agents = response.body()?.data?.list ?: emptyList()
agentItems = agents.map { agent -> agentItems = agents.map { agent ->
@@ -130,14 +134,17 @@ class ExploreViewModel : ViewModel() {
} }
} }
} }
fun createSingleChat( fun createSingleChat(
openId: String, openId: String,
) { ) {
viewModelScope.launch { viewModelScope.launch {
val response = ApiClient.api.createSingleChat(SingleChatRequestBody(agentOpenId = openId)) val response =
ApiClient.api.createSingleChat(SingleChatRequestBody(agentOpenId = openId))
} }
} }
fun goToChatAi( fun goToChatAi(
openId: String, openId: String,
navController: NavHostController navController: NavHostController
@@ -152,6 +159,7 @@ class ExploreViewModel : ViewModel() {
trtcId: String, trtcId: String,
name: String, name: String,
avatar: String, avatar: String,
context: Context,
navController: NavHostController, navController: NavHostController,
onSuccess: () -> Unit, onSuccess: () -> Unit,
onError: (String) -> Unit onError: (String) -> Unit
@@ -164,9 +172,11 @@ class ExploreViewModel : ViewModel() {
try { try {
createGroupChat(trtcGroupId = trtcId) createGroupChat(trtcGroupId = trtcId)
// 群聊直接使用群ID进行导航 // 群聊直接使用群ID进行导航
navController.navigateToGroupChat( id = trtcId, navController.navigateToGroupChat(
id = trtcId,
name = name, name = name,
avatar = avatar) avatar = avatar
)
} catch (e: Exception) { } catch (e: Exception) {
onError("加入房间失败") onError("加入房间失败")
e.printStackTrace() e.printStackTrace()
@@ -175,9 +185,28 @@ class ExploreViewModel : ViewModel() {
onSuccess() onSuccess()
} else { } else {
// 处理错误响应
try {
val errorBody = response.errorBody()?.string()
if (errorBody != null) {
val gson = Gson()
val errorResponse = gson.fromJson(errorBody, ApiErrorResponse::class.java)
// 在主线程显示 Toast
Toast.makeText(context, errorResponse.error, Toast.LENGTH_LONG).show()
onError(errorResponse.error)
} else {
Toast.makeText(context, "加入房间失败", Toast.LENGTH_SHORT).show()
onError("加入房间失败") onError("加入房间失败")
} }
} catch (parseException: Exception) {
// 如果解析错误响应失败,显示默认错误信息
Toast.makeText(context, "加入房间失败", Toast.LENGTH_SHORT).show()
onError("加入房间失败")
}
}
} catch (e: Exception) { } catch (e: Exception) {
Toast.makeText(context, "网络请求失败:${e.message}", Toast.LENGTH_SHORT).show()
onError("网络请求失败:${e.message}") onError("网络请求失败:${e.message}")
} }
} }

View File

@@ -157,6 +157,7 @@
<string name="agent_chat_file">[ファイル]</string> <string name="agent_chat_file">[ファイル]</string>
<string name="agent_chat_message">[メッセージ]</string> <string name="agent_chat_message">[メッセージ]</string>
<string name="agent_chat_load_failed">読み込みに失敗しました</string> <string name="agent_chat_load_failed">読み込みに失敗しました</string>
<string name="agent_chat_loading">読み込み中</string>
<string name="agent_chat_load_more_failed">さらに読み込むのに失敗しました</string> <string name="agent_chat_load_more_failed">さらに読み込むのに失敗しました</string>
<string name="agent_chat_user_info_failed">ユーザー情報の取得に失敗しました: %s</string> <string name="agent_chat_user_info_failed">ユーザー情報の取得に失敗しました: %s</string>
<string name="group_chat_empty">グループチャットがありません</string> <string name="group_chat_empty">グループチャットがありません</string>

View File

@@ -161,14 +161,15 @@
<string name="agent_chat_file">[文件]</string> <string name="agent_chat_file">[文件]</string>
<string name="agent_chat_message">[消息]</string> <string name="agent_chat_message">[消息]</string>
<string name="agent_chat_load_failed">加载失败</string> <string name="agent_chat_load_failed">加载失败</string>
<string name="agent_chat_loading">加载中</string>
<string name="agent_chat_load_more_failed">加载更多失败</string> <string name="agent_chat_load_more_failed">加载更多失败</string>
<string name="agent_chat_user_info_failed">获取用户信息失败: %s</string> <string name="agent_chat_user_info_failed">获取用户信息失败: %s</string>
<string name="group_chat_empty">没有群聊,宇宙好安静</string> <string name="group_chat_empty">没有群聊,宇宙好安静</string>
<string name="group_chat_empty_title">没有群聊消息的宇宙太安静了</string> <string name="group_chat_empty_title">没有群聊消息的宇宙太安静了</string>
<string name="group_chat_empty_subtitle">在首页探索感兴趣的主题房间</string> <string name="group_chat_empty_subtitle">在首页探索感兴趣的主题房间</string>
<string name="group_chat_empty_join">去首页探索感兴趣的高能对话</string> <string name="group_chat_empty_join">去首页探索感兴趣的高能对话</string>
<string name="friend_chat_empty_title">和朋友,还没有对话哦~</string> <string name="friend_chat_empty_title">和朋友,还没说第一句话呢</string>
<string name="friend_chat_empty_subtitle">点击好友头像,即刻发起聊天</string> <string name="friend_chat_empty_subtitle">一段崭新的友谊 等待被唤醒</string>
<string name="friend_chat_me_prefix">我: </string> <string name="friend_chat_me_prefix">我: </string>
<string name="friend_chat_load_failed">加载失败</string> <string name="friend_chat_load_failed">加载失败</string>
<string name="create_group_chat">创建群聊</string> <string name="create_group_chat">创建群聊</string>

View File

@@ -147,6 +147,7 @@
<string name="chat_friend">Friends</string> <string name="chat_friend">Friends</string>
<string name="chat_all">All</string> <string name="chat_all">All</string>
<string name="agent_chat_list_title">Agent Chat</string> <string name="agent_chat_list_title">Agent Chat</string>
<string name="agent_chat_loading">Loading</string>
<string name="agent_chat_empty_title">No Agent Chat</string> <string name="agent_chat_empty_title">No Agent Chat</string>
<string name="agent_chat_empty_subtitle">Start chatting with agents</string> <string name="agent_chat_empty_subtitle">Start chatting with agents</string>
<string name="agent_chat_me_prefix">Me: </string> <string name="agent_chat_me_prefix">Me: </string>