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 887e644..2df28ed 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 @@ -271,6 +271,13 @@ data class RemoveAccountRequestBody( val password: String, ) +data class CategoryTranslation( + @SerializedName("name") + val name: String?, + @SerializedName("description") + val description: String? +) + data class CategoryTemplate( @SerializedName("id") val id: Int, @@ -295,8 +302,58 @@ data class CategoryTemplate( @SerializedName("createdAt") val createdAt: String, @SerializedName("updatedAt") - val updatedAt: String -) + val updatedAt: String, + @SerializedName("translations") + val translations: Map? +) { + /** + * 根据语言代码获取翻译后的名称,如果没有翻译则返回默认名称 + */ + fun getLocalizedName(lang: String): String { + // 尝试获取完整的语言标记(如 "zh-CN") + val translation = translations?.get(lang) + if (translation?.name != null && translation.name.isNotEmpty()) { + return translation.name + } + + // 如果没有找到,尝试语言代码的前缀(如 "zh") + val langPrefix = lang.split("-", "_").firstOrNull() + if (langPrefix != null) { + translations?.entries?.forEach { (key, value) -> + if (key.startsWith(langPrefix) && value.name != null && value.name.isNotEmpty()) { + return value.name + } + } + } + + // 如果没有翻译,返回默认名称 + return name + } + + /** + * 根据语言代码获取翻译后的描述,如果没有翻译则返回默认描述 + */ + fun getLocalizedDescription(lang: String): String { + // 尝试获取完整的语言标记(如 "zh-CN") + val translation = translations?.get(lang) + if (translation?.description != null && translation.description.isNotEmpty()) { + return translation.description + } + + // 如果没有找到,尝试语言代码的前缀(如 "zh") + val langPrefix = lang.split("-", "_").firstOrNull() + if (langPrefix != null) { + translations?.entries?.forEach { (key, value) -> + if (key.startsWith(langPrefix) && value.description != null && value.description.isNotEmpty()) { + return value.description + } + } + } + + // 如果没有翻译,返回默认描述 + return description + } +} data class CategoryListResponse( @SerializedName("page") @@ -591,6 +648,7 @@ interface RaveNowAPI { @Query("withWorkflow") withWorkflow: Int = 1, @Query("authorId") authorId: Int? = null, @Query("categoryIds") categoryIds: List? = null, + @Query("random") random: Int? = null, ): Response>> @GET("outside/my/prompts") diff --git a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/ai/AgentViewModel.kt b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/ai/AgentViewModel.kt index 855e097..bff7c12 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/ai/AgentViewModel.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/ai/AgentViewModel.kt @@ -16,9 +16,17 @@ import com.aiosman.ravenow.ui.index.tabs.ai.tabs.mine.MineAgentViewModel.createG import com.aiosman.ravenow.ui.NavigationRoute import com.aiosman.ravenow.ui.index.tabs.message.MessageListViewModel.userService import com.aiosman.ravenow.ui.index.tabs.moment.tabs.expolre.AgentItem -import com.aiosman.ravenow.utils.Utils import kotlinx.coroutines.launch +/** + * 缓存数据结构,用于存储每个分类的Agent列表 + */ +data class AgentCacheData( + val items: List, + val currentPage: Int, + val hasMoreData: Boolean +) + object AgentViewModel: ViewModel() { private val apiClient: RaveNowAPI = ApiClient.api @@ -48,17 +56,34 @@ object AgentViewModel: ViewModel() { var hasMoreData by mutableStateOf(true) private set - + private val pageSize = 20 private var currentCategoryId: Int? = null + + // 缓存:使用分类ID作为key,null表示推荐列表 + private val agentCache = mutableMapOf() init { loadAgentData() loadCategories() } - private fun loadAgentData(categoryId: Int? = null, page: Int = 1, isLoadMore: Boolean = false) { + private fun loadAgentData(categoryId: Int? = null, page: Int = 1, isLoadMore: Boolean = false, forceRefresh: Boolean = false) { viewModelScope.launch { + // 如果不是强制刷新且不是加载更多,检查缓存 + if (!forceRefresh && !isLoadMore) { + val cached = agentCache[categoryId] + if (cached != null && cached.items.isNotEmpty()) { + // 使用缓存数据 + agentItems = cached.items + currentPage = cached.currentPage + hasMoreData = cached.hasMoreData + currentCategoryId = categoryId + println("使用缓存数据,分类ID: $categoryId, 数据数量: ${cached.items.size}") + return@launch + } + } + if (isLoadMore) { isLoadingMore = true } else { @@ -77,11 +102,18 @@ object AgentViewModel: ViewModel() { page = page, pageSize = pageSize, withWorkflow = 1, - categoryIds = listOf(categoryId) + categoryIds = listOf(categoryId), + random = 1 ) } else { - // 获取所有智能体 - apiClient.getAgent(page = page, pageSize = pageSize, withWorkflow = 1) + // 获取推荐智能体,使用random=1 + apiClient.getAgent( + page = page, + pageSize = pageSize, + withWorkflow = 1, + categoryIds = null, + random = 1 + ) } if (response.isSuccessful) { @@ -104,6 +136,14 @@ object AgentViewModel: ViewModel() { // 检查是否还有更多数据 hasMoreData = agents.size >= pageSize + // 更新缓存 + agentCache[categoryId] = AgentCacheData( + items = agentItems, + currentPage = currentPage, + hasMoreData = hasMoreData + ) + println("更新缓存,分类ID: $categoryId, 数据数量: ${agentItems.size}") + } else { errorMessage = "获取Agent数据失败: ${response.code()}" } @@ -121,7 +161,15 @@ object AgentViewModel: ViewModel() { private fun loadCategories() { viewModelScope.launch { + // 如果分类已经加载,不重复请求 + if (categories.isNotEmpty()) { + println("使用已缓存的分类数据,数量: ${categories.size}") + return@launch + } + try { + // 获取完整的语言标记(如 "zh-CN") + val sysLang = com.aiosman.ravenow.utils.Utils.getPreferredLanguageTag() val response = apiClient.getCategories( page = 1, pageSize = 100, @@ -130,14 +178,15 @@ object AgentViewModel: ViewModel() { withParent = false, withCount = true, hideEmpty = true, - lang = Utils.getCurrentLanguage() + lang = sysLang ) - println("分类数据请求完成,响应成功: ${response.isSuccessful}") + println("分类数据请求完成,响应成功: ${response.isSuccessful}, 语言标记: $sysLang") if (response.isSuccessful) { val categoryList = response.body()?.list ?: emptyList() println("获取到 ${categoryList.size} 个分类") + // 使用当前语言获取翻译后的分类名称 categories = categoryList.map { category -> - CategoryItem.fromCategoryTemplate(category) + CategoryItem.fromCategoryTemplate(category, sysLang) } println("成功处理并映射了 ${categories.size} 个分类") } else { @@ -214,10 +263,12 @@ object AgentViewModel: ViewModel() { } /** - * 刷新推荐Agent数据 + * 刷新当前分类的Agent数据(强制刷新,清除缓存) */ fun refreshAgentData() { - loadAgentData() + // 清除当前分类的缓存 + agentCache.remove(currentCategoryId) + loadAgentData(categoryId = currentCategoryId, forceRefresh = true) } /** @@ -234,6 +285,7 @@ object AgentViewModel: ViewModel() { */ fun ResetModel() { agentItems = emptyList() + categories = emptyList() errorMessage = null isRefreshing = false isLoading = false @@ -241,6 +293,8 @@ object AgentViewModel: ViewModel() { currentPage = 1 hasMoreData = true currentCategoryId = null + // 清空缓存 + agentCache.clear() } } @@ -252,11 +306,11 @@ data class CategoryItem( val promptCount: Int? ) { companion object { - fun fromCategoryTemplate(template: CategoryTemplate): CategoryItem { + fun fromCategoryTemplate(template: CategoryTemplate, lang: String): CategoryItem { return CategoryItem( id = template.id, - name = template.name, - description = template.description, + name = template.getLocalizedName(lang), + description = template.getLocalizedDescription(lang), avatar = "${ApiClient.BASE_API_URL}${template.avatar}", promptCount = template.promptCount ) diff --git a/app/src/main/java/com/aiosman/ravenow/utils/Utils.kt b/app/src/main/java/com/aiosman/ravenow/utils/Utils.kt index 2b38c24..7ed2cce 100644 --- a/app/src/main/java/com/aiosman/ravenow/utils/Utils.kt +++ b/app/src/main/java/com/aiosman/ravenow/utils/Utils.kt @@ -63,6 +63,23 @@ object Utils { return Locale.getDefault().language } + /** + * 获取完整的语言标记,如 "zh-CN", "en-US" + * 优先使用完整的 BCP-47 语言标记,提升与后端 translations 键的匹配率 + */ + fun getPreferredLanguageTag(): String { + val locale = Locale.getDefault() + val language = locale.language + val country = locale.country + + // 如果有国家/地区代码,返回完整的语言标记 + return if (country.isNotEmpty()) { + "$language-$country" + } else { + language + } + } + fun compressImage(context: Context, uri: Uri, maxSize: Int = 512, quality: Int = 85): File { val inputStream = context.contentResolver.openInputStream(uri) val originalBitmap = BitmapFactory.decodeStream(inputStream)