From a944bd0fa3e5ba19db0eb5c77a18d6abc3115a19 Mon Sep 17 00:00:00 2001 From: weber Date: Wed, 6 Aug 2025 15:58:17 +0800 Subject: [PATCH] =?UTF-8?q?=E6=99=BA=E8=83=BD=E4=BD=93=E5=88=97=E8=A1=A8?= =?UTF-8?q?=EF=BC=8C=E5=85=A8=E9=83=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/aiosman/ravenow/data/AgentService.kt | 8 +- .../aiosman/ravenow/data/api/RiderProAPI.kt | 22 ++- .../java/com/aiosman/ravenow/entity/Agent.kt | 25 ++- .../com/aiosman/ravenow/ui/agent/AddAgent.kt | 2 + .../aiosman/ravenow/ui/composables/Agent.kt | 3 +- .../aiosman/ravenow/ui/index/tabs/ai/Agent.kt | 11 +- .../ui/index/tabs/ai/tabs/hot/HotAgent.kt | 149 ++++++++++++++++++ .../tabs/ai/tabs/hot/HotAgentViewModel.kt | 90 +++++++++++ .../tabs/ai/tabs/mine/MineAgentViewModel.kt | 26 +++ 9 files changed, 318 insertions(+), 18 deletions(-) create mode 100644 app/src/main/java/com/aiosman/ravenow/ui/index/tabs/ai/tabs/hot/HotAgent.kt create mode 100644 app/src/main/java/com/aiosman/ravenow/ui/index/tabs/ai/tabs/hot/HotAgentViewModel.kt diff --git a/app/src/main/java/com/aiosman/ravenow/data/AgentService.kt b/app/src/main/java/com/aiosman/ravenow/data/AgentService.kt index f1fbf62..1c70212 100644 --- a/app/src/main/java/com/aiosman/ravenow/data/AgentService.kt +++ b/app/src/main/java/com/aiosman/ravenow/data/AgentService.kt @@ -1,5 +1,6 @@ package com.aiosman.ravenow.data +import com.aiosman.ravenow.AppStore import com.aiosman.ravenow.data.api.ApiClient import com.aiosman.ravenow.entity.AgentEntity import com.aiosman.ravenow.entity.ProfileEntity @@ -22,8 +23,6 @@ data class Agent( val isPublic: Boolean, @SerializedName("openId") val openId: String, - @SerializedName("profile") - val profile: Profile, @SerializedName("title") val title: String, @SerializedName("updatedAt") @@ -38,13 +37,12 @@ data class Agent( desc = desc, createdAt = createdAt, updatedAt = updatedAt, - avatar = "${ApiClient.BASE_SERVER}$avatar", - author = author, + avatar = "${ApiClient.BASE_API_URL+"/outside"}$avatar"+"?token="+"Bearer ${AppStore.token}", + //author = author, isPublic = isPublic, openId = openId, breakMode = breakMode, useCount = useCount, - profile = profile.toProfileEntity(), ) } } diff --git a/app/src/main/java/com/aiosman/ravenow/data/api/RiderProAPI.kt b/app/src/main/java/com/aiosman/ravenow/data/api/RiderProAPI.kt index 2d8be6a..8c8c760 100644 --- a/app/src/main/java/com/aiosman/ravenow/data/api/RiderProAPI.kt +++ b/app/src/main/java/com/aiosman/ravenow/data/api/RiderProAPI.kt @@ -38,6 +38,11 @@ data class AgentMomentRequestBody( val sessionId: String ) +data class SingleChatRequestBody( + @SerializedName("agentOpenId") + val generateText: String, +) + data class LoginUserRequestBody( @SerializedName("username") val username: String? = null, @@ -470,12 +475,14 @@ interface RaveNowAPI { suspend fun getAgent( @Query("page") page: Int = 1, @Query("pageSize") pageSize: Int = 20, - ): Response> + @Query("withWorkflow") withWorkflow: Int = 1, + ): Response>> @GET("outside/my/prompts") suspend fun getMyAgent( @Query("page") page: Int = 1, @Query("pageSize") pageSize: Int = 20, + @Query("withWorkflow") withWorkflow: Int = 1, ): Response> @Multipart @@ -483,12 +490,25 @@ interface RaveNowAPI { suspend fun createAgent( @Part avatar: MultipartBody.Part?, @Part("title") title: RequestBody?, + @Part("value") value: RequestBody?, @Part("desc") desc: RequestBody?, + @Part("workflowId") workflowId: RequestBody?, + @Part("public") isPublic: RequestBody?, + @Part("breakMode") breakMode: RequestBody?, + @Part("useWorkflow") useWorkflow: RequestBody?, + @Part("workflowInputs") workflowInputs: RequestBody?, ): Response> @POST("generate/postText") suspend fun agentMoment(@Body body: AgentMomentRequestBody): Response> + @POST("outside/rooms/create-single-chat") + suspend fun createSingleChat(@Body body: SingleChatRequestBody): Response> + + + + + } diff --git a/app/src/main/java/com/aiosman/ravenow/entity/Agent.kt b/app/src/main/java/com/aiosman/ravenow/entity/Agent.kt index 65e48d7..1d26e76 100644 --- a/app/src/main/java/com/aiosman/ravenow/entity/Agent.kt +++ b/app/src/main/java/com/aiosman/ravenow/entity/Agent.kt @@ -7,35 +7,48 @@ import com.aiosman.ravenow.data.AgentService import com.aiosman.ravenow.data.ServiceException import com.aiosman.ravenow.data.UploadImage import com.aiosman.ravenow.data.api.ApiClient +import okhttp3.MediaType import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MultipartBody import okhttp3.RequestBody import okhttp3.RequestBody.Companion.asRequestBody import okhttp3.RequestBody.Companion.toRequestBody +import retrofit2.http.Part import java.io.File import java.io.IOException /** * 智能体 */ - suspend fun createAgent( title: String, desc: String, avatar: UploadImage? = null, + 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 isPublicRequestBody = isPublic.toString().toRequestBody("text/plain".toMediaTypeOrNull()) + val breakModeRequestBody = breakMode.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 avatarField: MultipartBody.Part? = avatar?.let { createMultipartBody(it.file, it.filename, "avatar") } - val response = ApiClient.api.createAgent(avatarField, textTitle ,textDesc) + 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() } + data class AgentEntity( - val author: String, + //val author: String, val avatar: String, val breakMode: Boolean, val createdAt: String, @@ -43,7 +56,7 @@ data class AgentEntity( val id: Int, val isPublic: Boolean, val openId: String, - val profile: ProfileEntity, + //val profile: ProfileEntity, val title: String, val updatedAt: String, val useCount: Int @@ -80,8 +93,8 @@ class AgentLoader : DataLoader() { ) val data = result.body()?.let { ListContainer( - list = it.list.map { it.toAgentEntity()}, - total = it.total, + list = it.data.list.map { it.toAgentEntity()}, + total = it.data.total, page = page, pageSize = pageSize ) diff --git a/app/src/main/java/com/aiosman/ravenow/ui/agent/AddAgent.kt b/app/src/main/java/com/aiosman/ravenow/ui/agent/AddAgent.kt index 732a59b..d8eba6a 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/agent/AddAgent.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/agent/AddAgent.kt @@ -230,6 +230,8 @@ fun AddAgentScreen() { if (result != null) { println("AddAgent: Agent created successfully, closing page") // 创建成功,关闭页面 + model.name = "" + model.desc = "" navController.popBackStack() } } catch (e: Exception) { diff --git a/app/src/main/java/com/aiosman/ravenow/ui/composables/Agent.kt b/app/src/main/java/com/aiosman/ravenow/ui/composables/Agent.kt index f963cf6..d927032 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/composables/Agent.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/composables/Agent.kt @@ -80,7 +80,7 @@ fun AgentCard( text = agentEntity.title, color = AppColors.text, fontSize = 16.sp, - style = TextStyle(fontWeight = FontWeight.Bold) + style = TextStyle(fontWeight = FontWeight.W700) ) } Row( @@ -93,6 +93,7 @@ fun AgentCard( modifier = Modifier, text = agentEntity.desc, color = AppColors.text, + maxLines = 1, fontSize = 12.sp ) Spacer(modifier = Modifier.width(8.dp)) diff --git a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/ai/Agent.kt b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/ai/Agent.kt index 38e529c..afd3256 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/ai/Agent.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/ai/Agent.kt @@ -37,6 +37,7 @@ import com.aiosman.ravenow.LocalNavController import com.aiosman.ravenow.R import com.aiosman.ravenow.ui.NavigationRoute import com.aiosman.ravenow.ui.index.tabs.ai.tabs.mine.MineAgent +import com.aiosman.ravenow.ui.index.tabs.ai.tabs.hot.HotAgent import com.aiosman.ravenow.ui.modifiers.noRippleClickable import kotlinx.coroutines.launch @@ -134,7 +135,7 @@ fun Agent() { Text( text = stringResource(R.string.agent_mine), fontSize = 14.sp, - color = if (pagerState.currentPage == 0) AppColors.checkedText else AppColors.text, + color = if (pagerState.currentPage == 0) AppColors.mainText else AppColors.checkedBackground, modifier = Modifier .clip(RoundedCornerShape(8.dp)) .background(if (pagerState.currentPage == 0) AppColors.checkedBackground else AppColors.unCheckedBackground) @@ -158,7 +159,7 @@ fun Agent() { Text( text = stringResource(R.string.agent_hot), fontSize = 14.sp, - color = if (pagerState.currentPage == 1) AppColors.checkedText else AppColors.text, + color = if (pagerState.currentPage == 1) AppColors.mainText else AppColors.checkedBackground, modifier = Modifier .clip(RoundedCornerShape(8.dp)) .background(if (pagerState.currentPage == 1) AppColors.checkedBackground else AppColors.unCheckedBackground) @@ -182,7 +183,7 @@ fun Agent() { Text( text = stringResource(R.string.agent_recommend), fontSize = 14.sp, - color = if (pagerState.currentPage == 2) AppColors.checkedText else AppColors.text, + color = if (pagerState.currentPage == 2) AppColors.mainText else AppColors.checkedBackground, modifier = Modifier .clip(RoundedCornerShape(8.dp)) .background(if (pagerState.currentPage == 2) AppColors.checkedBackground else AppColors.unCheckedBackground) @@ -206,7 +207,7 @@ fun Agent() { Text( text = stringResource(R.string.agent_other), fontSize = 14.sp, - color = if (pagerState.currentPage == 3) AppColors.checkedText else AppColors.text, + color = if (pagerState.currentPage == 3) AppColors.mainText else AppColors.checkedBackground, modifier = Modifier .clip(RoundedCornerShape(8.dp)) .background(if (pagerState.currentPage == 3) AppColors.checkedBackground else AppColors.unCheckedBackground) @@ -231,7 +232,7 @@ fun Agent() { } 1 -> { - + HotAgent() } 2 -> { diff --git a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/ai/tabs/hot/HotAgent.kt b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/ai/tabs/hot/HotAgent.kt new file mode 100644 index 0000000..2d58eee --- /dev/null +++ b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/ai/tabs/hot/HotAgent.kt @@ -0,0 +1,149 @@ +package com.aiosman.ravenow.ui.index.tabs.ai.tabs.hot + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.pullrefresh.PullRefreshIndicator +import androidx.compose.material.pullrefresh.pullRefresh +import androidx.compose.material.pullrefresh.rememberPullRefreshState +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.aiosman.ravenow.LocalAppTheme +import com.aiosman.ravenow.R +import com.aiosman.ravenow.ui.composables.AgentCard + +@OptIn(ExperimentalMaterialApi::class) +@Composable +fun HotAgent() { + val AppColors = LocalAppTheme.current + val model = HotAgentViewModel + var agentList = model.agentList + val scope = rememberCoroutineScope() + val state = rememberPullRefreshState(model.refreshing, onRefresh = { + model.refreshPager(pullRefresh = true) + }) + val listState = rememberLazyListState() + + // observe list scrolling + val reachedBottom by remember { + derivedStateOf { + val lastVisibleItem = listState.layoutInfo.visibleItemsInfo.lastOrNull() + lastVisibleItem?.index != 0 && lastVisibleItem?.index == listState.layoutInfo.totalItemsCount - 2 + } + } + + // load more if scrolled to bottom + LaunchedEffect(reachedBottom) { + if (reachedBottom && !model.isLoading && model.hasNext) { + model.loadMore() + } + } + + LaunchedEffect(Unit) { + model.refreshPager() + } + + Column( + modifier = Modifier + .fillMaxSize() + ) { + if(agentList.isEmpty() && !model.isLoading) { + Box( + modifier = Modifier + .fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.fillMaxWidth() + ) { + Image( + painter = painterResource(id = R.mipmap.rider_pro_following_empty), + contentDescription = null, + modifier = Modifier.size(140.dp) + ) + Spacer(modifier = Modifier.size(32.dp)) + Text( + text = "您还没有创建任何智能体", + color = AppColors.text, + fontSize = 16.sp, + fontWeight = FontWeight.W600 + ) + Spacer(modifier = Modifier.size(16.dp)) + Text( + text = "点击开始创建您的第一个智能体", + color = AppColors.text, + fontSize = 16.sp, + fontWeight = FontWeight.W400 + ) + } + } + } else { + Box(Modifier.pullRefresh(state)) { + LazyColumn( + modifier = Modifier.fillMaxSize(), + state = listState + ) { + items( + agentList.size, + key = { idx -> idx } + ) { idx -> + val agentItem = agentList[idx] + AgentCard(agentEntity = agentItem) + } + + // 加载更多指示器 + if (model.isLoading && agentList.isNotEmpty()) { + item { + Box( + modifier = Modifier + .fillMaxWidth(), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator( + modifier = Modifier.size(24.dp), + color = AppColors.main + ) + } + } + } + } + PullRefreshIndicator(model.refreshing, state, Modifier.align(Alignment.TopCenter)) + } + } + + // 错误信息显示 + model.error?.let { error -> + Box( + modifier = Modifier + .fillMaxWidth(), + contentAlignment = Alignment.Center + ) { + Text( + text = error, + color = AppColors.error, + fontSize = 14.sp + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/ai/tabs/hot/HotAgentViewModel.kt b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/ai/tabs/hot/HotAgentViewModel.kt new file mode 100644 index 0000000..36c167b --- /dev/null +++ b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/ai/tabs/hot/HotAgentViewModel.kt @@ -0,0 +1,90 @@ +package com.aiosman.ravenow.ui.index.tabs.ai.tabs.hot + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.aiosman.ravenow.data.api.ApiClient +import com.aiosman.ravenow.data.ServiceException +import com.aiosman.ravenow.entity.AgentEntity +import kotlinx.coroutines.launch + +object HotAgentViewModel : ViewModel() { + var agentList by mutableStateOf>(emptyList()) + var refreshing by mutableStateOf(false) + var isLoading by mutableStateOf(false) + var hasNext by mutableStateOf(true) + var currentPage by mutableStateOf(1) + var error by mutableStateOf(null) + + private val pageSize = 20 + + init { + refreshPager() + } + + fun refreshPager(pullRefresh: Boolean = false) { + if (isLoading && !pullRefresh) return + + viewModelScope.launch { + try { + isLoading = true + refreshing = pullRefresh + error = null + + val response = ApiClient.api.getAgent( + page = 1, + pageSize = pageSize + ) + + val body = response.body() + if (body != null) { + val newAgents = body.data.list.map { it.toAgentEntity() } + agentList = newAgents + currentPage = 1 + hasNext = newAgents.size == pageSize + } else { + throw ServiceException("Failed to load agents") + } + } catch (e: Exception) { + error = e.message ?: "加载失败" + e.printStackTrace() + } finally { + isLoading = false + refreshing = false + } + } + } + + fun loadMore() { + if (isLoading || !hasNext) return + + viewModelScope.launch { + try { + isLoading = true + error = null + + val response = ApiClient.api.getAgent( + page = currentPage + 1, + pageSize = pageSize + ) + + val body = response.body() + if (body != null) { + val newAgents = body.data.list.map { it.toAgentEntity() } + agentList = agentList + newAgents + currentPage += 1 + hasNext = newAgents.size == pageSize + } else { + throw ServiceException("Failed to load more agents") + } + } catch (e: Exception) { + error = e.message ?: "加载更多失败" + e.printStackTrace() + } finally { + isLoading = false + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/ai/tabs/mine/MineAgentViewModel.kt b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/ai/tabs/mine/MineAgentViewModel.kt index e5b0831..c2a663f 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/ai/tabs/mine/MineAgentViewModel.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/ai/tabs/mine/MineAgentViewModel.kt @@ -5,10 +5,18 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import androidx.navigation.NavHostController import com.aiosman.ravenow.data.api.ApiClient import com.aiosman.ravenow.data.ServiceException +import com.aiosman.ravenow.data.api.AgentMomentRequestBody +import com.aiosman.ravenow.data.api.SingleChatRequestBody import com.aiosman.ravenow.entity.AgentEntity +import com.aiosman.ravenow.ui.index.tabs.message.Conversation +import com.aiosman.ravenow.ui.index.tabs.message.MessageListViewModel.userService +import com.aiosman.ravenow.ui.navigateToChat import kotlinx.coroutines.launch +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.RequestBody.Companion.toRequestBody object MineAgentViewModel : ViewModel() { var agentList by mutableStateOf>(emptyList()) @@ -87,4 +95,22 @@ object MineAgentViewModel : ViewModel() { } } } + + suspend fun createSingleChat( + openId: String, + ): String { + val response = ApiClient.api.createSingleChat(SingleChatRequestBody(generateText = openId)) + val body = response.body()?.data ?: throw ServiceException("Failed to create single chat") + return body.toString() + + } + fun goToChat( + conversation: Conversation, + navController: NavHostController + ) { + viewModelScope.launch { + val profile = userService.getUserProfileByTrtcUserId(conversation.trtcUserId) + navController.navigateToChat(profile.id.toString()) + } + } } \ No newline at end of file