Merge pull request #22 from Zhong202501/main

添加Category接口
This commit is contained in:
2025-09-17 10:39:22 +08:00
committed by GitHub
5 changed files with 288 additions and 130 deletions

View File

@@ -271,6 +271,44 @@ data class RemoveAccountRequestBody(
val password: String,
)
data class CategoryTemplate(
@SerializedName("id")
val id: Int,
@SerializedName("name")
val name: String,
@SerializedName("description")
val description: String,
@SerializedName("avatar")
val avatar: String,
@SerializedName("parentId")
val parentId: Int?,
@SerializedName("parent")
val parent: CategoryTemplate?,
@SerializedName("children")
val children: List<CategoryTemplate>?,
@SerializedName("sort")
val sort: Int,
@SerializedName("isActive")
val isActive: Boolean,
@SerializedName("promptCount")
val promptCount: Int?,
@SerializedName("createdAt")
val createdAt: String,
@SerializedName("updatedAt")
val updatedAt: String
)
data class CategoryListResponse(
@SerializedName("page")
val page: Int,
@SerializedName("pageSize")
val pageSize: Int,
@SerializedName("total")
val total: Int,
@SerializedName("list")
val list: List<CategoryTemplate>
)
interface RaveNowAPI {
@GET("membership/config")
@retrofit2.http.Headers("X-Requires-Auth: true")
@@ -552,6 +590,7 @@ interface RaveNowAPI {
@Query("pageSize") pageSize: Int = 20,
@Query("withWorkflow") withWorkflow: Int = 1,
@Query("authorId") authorId: Int? = null,
@Query("categoryIds") categoryIds: List<Int>? = null,
): Response<DataContainer<ListContainer<Agent>>>
@GET("outside/my/prompts")
@@ -605,7 +644,38 @@ interface RaveNowAPI {
suspend fun joinRoom(@Body body: JoinGroupChatRequestBody,
): Response<DataContainer<Room>>
@GET("outside/categories")
suspend fun getCategories(
@Query("page") page: Int? = null,
@Query("pageSize") pageSize: Int? = null,
@Query("parentId") parentId: Int? = null,
@Query("isActive") isActive: Boolean? = null,
@Query("name") name: String? = null,
@Query("withChildren") withChildren: Boolean? = null,
@Query("withParent") withParent: Boolean? = null,
@Query("withCount") withCount: Boolean? = null,
@Query("hideEmpty") hideEmpty: Boolean? = null
): Response<DataContainer<CategoryListResponse>>
@GET("outside/categories/tree")
suspend fun getCategoryTree(
@Query("withCount") withCount: Boolean? = null,
@Query("hideEmpty") hideEmpty: Boolean? = null
): Response<DataContainer<List<CategoryTemplate>>>
@GET("outside/categories/{id}")
suspend fun getCategoryById(
@Path("id") id: Int
): 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

@@ -283,7 +283,7 @@ fun ChatAiScreen(userId: String) {
Box(
modifier = Modifier
.fillMaxSize()
.background(AppColors.decentBackground)
.background(Color.White)
.padding(paddingValues)
) {
LazyColumn(

View File

@@ -246,6 +246,7 @@ fun Agent() {
isSelected = selectedTabIndex == 0,
onClick = {
selectedTabIndex = 0
viewModel.loadAllAgents()
}
)
}
@@ -254,6 +255,24 @@ fun Agent() {
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",
@@ -271,9 +290,9 @@ fun Agent() {
item {
CustomTabItem(
text = "voices",
isSelected = selectedTabIndex == 2,
isSelected = selectedTabIndex == 6,
onClick = {
selectedTabIndex = 2
selectedTabIndex = 6
}
)
}
@@ -285,9 +304,9 @@ fun Agent() {
item {
CustomTabItem(
text = "anime",
isSelected = selectedTabIndex == 3,
isSelected = selectedTabIndex == 7,
onClick = {
selectedTabIndex = 3
selectedTabIndex = 7
}
)
}
@@ -299,16 +318,19 @@ fun Agent() {
item {
CustomTabItem(
text = "assist",
isSelected = selectedTabIndex == 4,
isSelected = selectedTabIndex == 8,
onClick = {
selectedTabIndex = 4
selectedTabIndex = 8
}
)
}
}
when (selectedTabIndex) {
0 -> {
when {
selectedTabIndex == 0 -> {
AgentViewPagerSection(agentItems = viewModel.agentItems.take(15), viewModel)
}
selectedTabIndex in 1..viewModel.categories.size -> {
AgentViewPagerSection(agentItems = viewModel.agentItems.take(15), viewModel)
}
else -> {
@@ -341,18 +363,18 @@ fun Agent() {
)
}
Spacer(modifier = Modifier.height(50.dp))
Spacer(modifier = Modifier.height(16.dp))
Column(
modifier = Modifier
.fillMaxWidth()
.weight(1f)
) {
val agentItems = viewModel.agentItems.take(15)
val agentItems = viewModel.agentItems
LazyVerticalGrid(
columns = GridCells.Fixed(2),
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalArrangement = Arrangement.spacedBy(50.dp)
verticalArrangement = Arrangement.spacedBy(32.dp)
) {
items(agentItems) { agentItem ->
AgentCardSquare(
@@ -369,7 +391,7 @@ fun Agent() {
@Composable
fun AgentCardSquare(agentItem: AgentItem, viewModel: AgentViewModel, navController: NavHostController) {
val AppColors = LocalAppTheme.current
val cardHeight = 180.dp
val cardHeight = 200.dp
val avatarSize = cardHeight / 3 // 头像大小为方块高度的三分之一
// 防抖状态
@@ -378,8 +400,9 @@ fun AgentCardSquare(agentItem: AgentItem, viewModel: AgentViewModel, navControll
Box(
modifier = Modifier
.fillMaxWidth()
.padding(top = avatarSize / 2)
.height(cardHeight)
.background(Color(0xFFE0E0E0), RoundedCornerShape(12.dp)) // 灰色背景
.background(AppColors.nonActive, RoundedCornerShape(12.dp)) // 修改背景颜色
.clickable {
if (DebounceUtils.simpleDebounceClick(lastClickTime, 500L) {
viewModel.goToProfile(agentItem.openId, navController)
@@ -426,7 +449,7 @@ fun AgentCardSquare(agentItem: AgentItem, viewModel: AgentViewModel, navControll
) {
androidx.compose.material3.Text(
text = agentItem.title,
fontSize = 16.sp,
fontSize = 14.sp,
fontWeight = androidx.compose.ui.text.font.FontWeight.W600,
color = AppColors.text,
maxLines = 1,
@@ -435,21 +458,26 @@ fun AgentCardSquare(agentItem: AgentItem, viewModel: AgentViewModel, navControll
Spacer(modifier = Modifier.height(8.dp))
androidx.compose.material3.Text(
text = agentItem.desc,
fontSize = 14.sp,
color = AppColors.secondaryText,
maxLines = 2,
overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis,
modifier = Modifier.weight(1f, fill = false)
)
Box(
modifier = Modifier
.height(85.dp)
.fillMaxWidth()
) {
androidx.compose.material3.Text(
text = agentItem.desc,
fontSize = 12.sp,
color = AppColors.secondaryText,
maxLines = 5,
overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis,
)
}
Spacer(modifier = Modifier.height(8.dp))
// 聊天按钮,位于底部居中
Box(
modifier = Modifier
.width(80.dp)
.width(60.dp)
.height(32.dp)
.background(
color = Color(0X147c7480),

View File

@@ -6,7 +6,10 @@ import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.navigation.NavHostController
import com.aiosman.ravenow.data.Agent
import com.aiosman.ravenow.data.ListContainer
import com.aiosman.ravenow.data.api.ApiClient
import com.aiosman.ravenow.data.api.CategoryTemplate
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
@@ -22,6 +25,8 @@ object AgentViewModel: ViewModel() {
var agentItems by mutableStateOf<List<AgentItem>>(emptyList())
private set
var categories by mutableStateOf<List<CategoryItem>>(emptyList())
private set
var errorMessage by mutableStateOf<String?>(null)
private set
@@ -35,16 +40,30 @@ object AgentViewModel: ViewModel() {
init {
loadAgentData()
loadCategories()
}
private fun loadAgentData() {
private fun loadAgentData(categoryId: Int? = null) {
viewModelScope.launch {
isLoading = true
errorMessage = null
try {
val response = apiClient.getAgent(page = 1, pageSize = 20, withWorkflow = 1)
val response = if (categoryId != null) {
// 根据分类ID获取智能体
apiClient.getAgent(
page = 1,
pageSize = 20,
withWorkflow = 1,
categoryIds = listOf(categoryId)
)
} else {
// 获取所有智能体
apiClient.getAgent(page = 1, pageSize = 20, withWorkflow = 1)
}
if (response.isSuccessful) {
val agents = response.body()?.data?.list ?: emptyList()
val agents = response.body()?.data?.list ?: emptyList<Agent>()
agentItems = agents.map { agent ->
AgentItem.fromAgent(agent)
}
@@ -58,6 +77,44 @@ object AgentViewModel: ViewModel() {
}
}
}
private fun loadCategories() {
viewModelScope.launch {
try {
val response = apiClient.getCategories(
pageSize = 20,
withChildren = false,
withParent = false,
withCount = true,
hideEmpty = true
)
println("分类数据请求完成,响应成功: ${response.isSuccessful}")
if (response.isSuccessful) {
val categoryList = response.body()?.data?.list ?: emptyList()
println("获取到 ${categoryList.size} 个分类")
categories = categoryList.map { category ->
CategoryItem.fromCategoryTemplate(category)
}
println("成功处理并映射了 ${categories.size} 个分类")
} else {
errorMessage = "获取分类数据失败: ${response.code()}"
println("获取分类数据失败: ${response.code()}")
}
} catch (e: Exception) {
errorMessage = "获取分类数据失败: ${e.message}"
println("获取分类数据异常: ${e.message}")
e.printStackTrace()
}
}
}
fun loadAgentsByCategory(categoryId: Int) {
loadAgentData(categoryId)
}
fun loadAllAgents() {
loadAgentData()
}
fun createSingleChat(
openId: String,
) {
@@ -122,3 +179,22 @@ object AgentViewModel: ViewModel() {
}
}
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

@@ -50,10 +50,12 @@ 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.draw.drawWithContent
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.nativeCanvas
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalContext
@@ -369,39 +371,6 @@ fun ProfileV3(
)
}
}
if (isSelf&&isMain) {
Box(
modifier = Modifier
.align(Alignment.TopEnd)
.padding(
top = statusBarPaddingValues.calculateTopPadding(),
start = 8.dp,
end = 8.dp
)
.noRippleClickable {
IndexViewModel.openDrawer = true
}
) {
Box(
modifier = Modifier
.padding(16.dp)
.clip(RoundedCornerShape(8.dp))
.background(
AppColors.background.copy(
alpha = 0.7f
)
)
) {
Icon(
painter = painterResource(id = R.drawable.rider_pro_more_horizon),
contentDescription = "",
tint = AppColors.text
)
}
}
}
}
Box(
@@ -509,84 +478,99 @@ fun ProfileV3(
}
Column(
modifier = Modifier
.fillMaxWidth()
.graphicsLayer {
alpha = 1 - state.toolbarState.progress
}
.background(AppColors.profileBackground)
.onGloballyPositioned {
miniToolbarHeight = with(density) {
it.size.height.toDp().value.toInt()
Box(modifier = Modifier.fillMaxWidth()) {
Column(
modifier = Modifier
.fillMaxWidth()
.graphicsLayer {
alpha = 1 - state.toolbarState.progress
}
.background(AppColors.profileBackground)
.onGloballyPositioned {
miniToolbarHeight = with(density) {
it.size.height.toDp().value.toInt()
}
}
}
) {
StatusBarSpacer()
Row(
modifier = Modifier.padding(
horizontal = 16.dp,
vertical = 8.dp,
).noRippleClickable {
},
verticalAlignment = Alignment.CenterVertically
) {
if (!isMain) {
Image(
painter = painterResource(id = R.drawable.rider_pro_back_icon), // Replace with your image resource
contentDescription = "Back",
modifier = Modifier
.noRippleClickable {
navController.navigateUp()
}
.size(24.dp),
colorFilter = ColorFilter.tint(AppColors.text)
)
Spacer(modifier = Modifier.width(8.dp))
CustomAsyncImage(
LocalContext.current,
profile?.avatar,
modifier = Modifier
.size(32.dp)
.clip(CircleShape),
contentDescription = "",
contentScale = ContentScale.Crop
)
Spacer(modifier = Modifier.width(16.dp))
Text(
text = profile?.nickName ?: "",
fontSize = 16.sp,
fontWeight = FontWeight.W600,
color = AppColors.text
)
StatusBarSpacer()
Row(
modifier = Modifier.padding(
horizontal = 16.dp,
vertical = 8.dp,
).noRippleClickable {
}
},
verticalAlignment = Alignment.CenterVertically
) {
if (!isMain) {
Image(
painter = painterResource(id = R.drawable.rider_pro_back_icon), // Replace with your image resource
contentDescription = "Back",
modifier = Modifier
.noRippleClickable {
navController.navigateUp()
}
.size(24.dp),
colorFilter = ColorFilter.tint(AppColors.text)
)
Spacer(modifier = Modifier.width(8.dp))
CustomAsyncImage(
LocalContext.current,
profile?.avatar,
modifier = Modifier
.size(32.dp)
.clip(CircleShape),
contentDescription = "",
contentScale = ContentScale.Crop
)
Spacer(modifier = Modifier.width(16.dp))
Text(
text = profile?.nickName ?: "",
fontSize = 16.sp,
fontWeight = FontWeight.W600,
color = AppColors.text
)
}
Spacer(modifier = Modifier.weight(1f))
if (isSelf&&isMain) {
Box(
modifier = Modifier.noRippleClickable {
IndexViewModel.openDrawer = true
}
) {
Box(
modifier = Modifier
.padding(16.dp)
modifier = Modifier
.size(24.dp)
.padding(16.dp)
)
}
}
) {
Icon(
painter = painterResource(id = R.drawable.rider_pro_more_horizon),
contentDescription = "",
tint = AppColors.text
)
}
Spacer(modifier = Modifier.height(8.dp))
}
if (isSelf&&isMain) {
Box(
modifier = Modifier
.align(Alignment.TopEnd)
.padding(
top = 32.dp ,
end = 16.dp
)
.noRippleClickable {
IndexViewModel.openDrawer = true
}
) {
Box(
modifier = Modifier
.padding(16.dp)
}
) {
Icon(
painter = painterResource(id = R.drawable.rider_pro_more_horizon),
contentDescription = "",
tint = AppColors.text
)
}
}
}
Spacer(modifier = Modifier.height(8.dp))
}
}
) {