顶部 推荐 agent 列表实现

This commit is contained in:
2025-10-17 17:21:52 +08:00
parent 4bdbbb0231
commit 28fb94a824
3 changed files with 145 additions and 16 deletions

View File

@@ -271,6 +271,13 @@ data class RemoveAccountRequestBody(
val password: String, val password: String,
) )
data class CategoryTranslation(
@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,8 +302,58 @@ 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, CategoryTranslation>?
) {
/**
* 根据语言代码获取翻译后的名称,如果没有翻译则返回默认名称
*/
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( data class CategoryListResponse(
@SerializedName("page") @SerializedName("page")
@@ -591,6 +648,7 @@ interface RaveNowAPI {
@Query("withWorkflow") withWorkflow: Int = 1, @Query("withWorkflow") withWorkflow: Int = 1,
@Query("authorId") authorId: Int? = null, @Query("authorId") authorId: Int? = null,
@Query("categoryIds") categoryIds: List<Int>? = null, @Query("categoryIds") categoryIds: List<Int>? = null,
@Query("random") random: Int? = null,
): Response<DataContainer<ListContainer<Agent>>> ): Response<DataContainer<ListContainer<Agent>>>
@GET("outside/my/prompts") @GET("outside/my/prompts")

View File

@@ -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.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.moment.tabs.expolre.AgentItem import com.aiosman.ravenow.ui.index.tabs.moment.tabs.expolre.AgentItem
import com.aiosman.ravenow.utils.Utils
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
/**
* 缓存数据结构用于存储每个分类的Agent列表
*/
data class AgentCacheData(
val items: List<AgentItem>,
val currentPage: Int,
val hasMoreData: Boolean
)
object AgentViewModel: ViewModel() { object AgentViewModel: ViewModel() {
private val apiClient: RaveNowAPI = ApiClient.api private val apiClient: RaveNowAPI = ApiClient.api
@@ -48,17 +56,34 @@ object AgentViewModel: ViewModel() {
var hasMoreData by mutableStateOf(true) var hasMoreData by mutableStateOf(true)
private set private set
private val pageSize = 20 private val pageSize = 20
private var currentCategoryId: Int? = null private var currentCategoryId: Int? = null
// 缓存使用分类ID作为keynull表示推荐列表
private val agentCache = mutableMapOf<Int?, AgentCacheData>()
init { init {
loadAgentData() loadAgentData()
loadCategories() 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 { 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) { if (isLoadMore) {
isLoadingMore = true isLoadingMore = true
} else { } else {
@@ -77,11 +102,18 @@ object AgentViewModel: ViewModel() {
page = page, page = page,
pageSize = pageSize, pageSize = pageSize,
withWorkflow = 1, withWorkflow = 1,
categoryIds = listOf(categoryId) categoryIds = listOf(categoryId),
random = 1
) )
} else { } else {
// 获取所有智能体 // 获取推荐智能体使用random=1
apiClient.getAgent(page = page, pageSize = pageSize, withWorkflow = 1) apiClient.getAgent(
page = page,
pageSize = pageSize,
withWorkflow = 1,
categoryIds = null,
random = 1
)
} }
if (response.isSuccessful) { if (response.isSuccessful) {
@@ -104,6 +136,14 @@ object AgentViewModel: ViewModel() {
// 检查是否还有更多数据 // 检查是否还有更多数据
hasMoreData = agents.size >= pageSize hasMoreData = agents.size >= pageSize
// 更新缓存
agentCache[categoryId] = AgentCacheData(
items = agentItems,
currentPage = currentPage,
hasMoreData = hasMoreData
)
println("更新缓存分类ID: $categoryId, 数据数量: ${agentItems.size}")
} else { } else {
errorMessage = "获取Agent数据失败: ${response.code()}" errorMessage = "获取Agent数据失败: ${response.code()}"
} }
@@ -121,7 +161,15 @@ object AgentViewModel: ViewModel() {
private fun loadCategories() { private fun loadCategories() {
viewModelScope.launch { viewModelScope.launch {
// 如果分类已经加载,不重复请求
if (categories.isNotEmpty()) {
println("使用已缓存的分类数据,数量: ${categories.size}")
return@launch
}
try { try {
// 获取完整的语言标记(如 "zh-CN"
val sysLang = com.aiosman.ravenow.utils.Utils.getPreferredLanguageTag()
val response = apiClient.getCategories( val response = apiClient.getCategories(
page = 1, page = 1,
pageSize = 100, pageSize = 100,
@@ -130,14 +178,15 @@ object AgentViewModel: ViewModel() {
withParent = false, withParent = false,
withCount = true, withCount = true,
hideEmpty = true, hideEmpty = true,
lang = Utils.getCurrentLanguage() lang = sysLang
) )
println("分类数据请求完成,响应成功: ${response.isSuccessful}") println("分类数据请求完成,响应成功: ${response.isSuccessful}, 语言标记: $sysLang")
if (response.isSuccessful) { if (response.isSuccessful) {
val categoryList = response.body()?.list ?: emptyList() val categoryList = response.body()?.list ?: emptyList()
println("获取到 ${categoryList.size} 个分类") println("获取到 ${categoryList.size} 个分类")
// 使用当前语言获取翻译后的分类名称
categories = categoryList.map { category -> categories = categoryList.map { category ->
CategoryItem.fromCategoryTemplate(category) CategoryItem.fromCategoryTemplate(category, sysLang)
} }
println("成功处理并映射了 ${categories.size} 个分类") println("成功处理并映射了 ${categories.size} 个分类")
} else { } else {
@@ -214,10 +263,12 @@ object AgentViewModel: ViewModel() {
} }
/** /**
* 刷新推荐Agent数据 * 刷新当前分类的Agent数据(强制刷新,清除缓存)
*/ */
fun refreshAgentData() { fun refreshAgentData() {
loadAgentData() // 清除当前分类的缓存
agentCache.remove(currentCategoryId)
loadAgentData(categoryId = currentCategoryId, forceRefresh = true)
} }
/** /**
@@ -234,6 +285,7 @@ object AgentViewModel: ViewModel() {
*/ */
fun ResetModel() { fun ResetModel() {
agentItems = emptyList() agentItems = emptyList()
categories = emptyList()
errorMessage = null errorMessage = null
isRefreshing = false isRefreshing = false
isLoading = false isLoading = false
@@ -241,6 +293,8 @@ object AgentViewModel: ViewModel() {
currentPage = 1 currentPage = 1
hasMoreData = true hasMoreData = true
currentCategoryId = null currentCategoryId = null
// 清空缓存
agentCache.clear()
} }
} }
@@ -252,11 +306,11 @@ data class CategoryItem(
val promptCount: Int? val promptCount: Int?
) { ) {
companion object { companion object {
fun fromCategoryTemplate(template: CategoryTemplate): CategoryItem { fun fromCategoryTemplate(template: CategoryTemplate, lang: String): CategoryItem {
return CategoryItem( return CategoryItem(
id = template.id, id = template.id,
name = template.name, name = template.getLocalizedName(lang),
description = template.description, description = template.getLocalizedDescription(lang),
avatar = "${ApiClient.BASE_API_URL}${template.avatar}", avatar = "${ApiClient.BASE_API_URL}${template.avatar}",
promptCount = template.promptCount promptCount = template.promptCount
) )

View File

@@ -63,6 +63,23 @@ object Utils {
return Locale.getDefault().language 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 { fun compressImage(context: Context, uri: Uri, maxSize: Int = 512, quality: Int = 85): File {
val inputStream = context.contentResolver.openInputStream(uri) val inputStream = context.contentResolver.openInputStream(uri)
val originalBitmap = BitmapFactory.decodeStream(inputStream) val originalBitmap = BitmapFactory.decodeStream(inputStream)