新增智能体和房间规则管理功能

- 新增智能体(Agent)规则的增删改查(CRUD)和服务实现。
- 新增房间(Room)规则的增删改查(CRUD)和服务实现。
- 将 `PromptRule` 相关命名重构为 `AgentRule`,以提高代码清晰度。
- 将相关数据实体中表示总数的字段类型从 `Int` 修改为 `Long`。
This commit is contained in:
2025-11-05 22:24:03 +08:00
parent 1de8bb825c
commit 9c9eb66b71
9 changed files with 870 additions and 39 deletions

View File

@@ -2,8 +2,16 @@ package com.aiosman.ravenow.data
import com.aiosman.ravenow.AppStore import com.aiosman.ravenow.AppStore
import com.aiosman.ravenow.data.api.ApiClient import com.aiosman.ravenow.data.api.ApiClient
import com.aiosman.ravenow.data.api.AgentRule
import com.aiosman.ravenow.data.api.AgentRuleListResponse
import com.aiosman.ravenow.data.api.AgentRuleQuota
import com.aiosman.ravenow.data.api.AgentRuleAgent
import com.aiosman.ravenow.data.api.CreateAgentRuleRequestBody
import com.aiosman.ravenow.data.api.InsufficientBalanceError
import com.aiosman.ravenow.data.api.UpdateAgentRuleRequestBody
import com.aiosman.ravenow.entity.AgentEntity import com.aiosman.ravenow.entity.AgentEntity
import com.aiosman.ravenow.entity.ProfileEntity import com.aiosman.ravenow.entity.ProfileEntity
import com.google.gson.Gson
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
data class Agent( data class Agent(
@@ -83,9 +91,16 @@ data class Profile(
) )
} }
} }
/**
* 智能体服务
*/
interface AgentService { interface AgentService {
/** /**
* 获取智能体列表 * 获取智能体列表
* @param pageNumber 页码
* @param pageSize 每页数量,默认 20
* @param authorId 作者ID可选参数用于筛选特定作者的智能体
* @return 智能体列表容器,包含分页信息和智能体列表,失败时返回 null
*/ */
suspend fun getAgent( suspend fun getAgent(
pageNumber: Int, pageNumber: Int,
@@ -96,3 +111,206 @@ interface AgentService {
} }
// ========== Agent 规则 - 领域实体 ==========
data class AgentRuleAgentInfo(
val id: Int,
val title: String,
val avatar: String,
)
data class AgentRuleEntity(
val id: Int,
val rule: String,
val creator: String,
val creatorType: String,
val scope: String,
val agent: AgentRuleAgentInfo,
val createdAt: String,
val updatedAt: String,
)
data class AgentRuleListResult(
val page: Int,
val pageSize: Int,
val total: Int,
val list: List<AgentRuleEntity>,
)
data class AgentRuleQuotaEntity(
val agentId: Int,
val agentTitle: String,
val baseMaxCount: Int,
val purchasedCount: Int,
val totalMaxCount: Int,
val currentCount: Int,
val remainingCount: Int,
val usagePercent: Double,
)
// ========== Agent 规则 - Service 接口 ==========
/**
* Agent 规则服务
*/
interface AgentRuleService {
/**
* 根据 OpenId 创建 Agent 规则
* @param openId Agent 的 OpenId
* @param rule 规则内容,不能为空
* @throws ServiceException 创建失败时抛出异常(包括余额不足等情况)
*/
suspend fun createAgentRuleByOpenId(openId: String, rule: String)
/**
* 修改 Agent 规则
* @param id 规则ID
* @param rule 新的规则内容,不能为空
* @param openId Agent 的 OpenId可选参数
* @throws ServiceException 修改失败时抛出异常(包括余额不足等情况)
*/
suspend fun updateAgentRule(id: Int, rule: String, openId: String? = null)
/**
* 删除 Agent 规则
* @param id 规则ID
* @throws ServiceException 删除失败时抛出异常
*/
suspend fun deleteAgentRule(id: Int)
/**
* 查询 Agent 规则列表
* @param openId Agent 的 OpenId
* @param keyword 关键词搜索,可选参数
* @param page 页码,默认 1
* @param pageSize 每页数量,默认 10
* @return 规则列表响应,包含分页信息和规则列表
* @throws ServiceException 查询失败时抛出异常
*/
suspend fun getAgentRuleList(
openId: String,
keyword: String? = null,
page: Int = 1,
pageSize: Int = 10
): AgentRuleListResult
/**
* 查询 Agent 规则配额使用情况
* @param openId Agent 的 OpenId
* @return 规则配额信息,包含基础配额、已购买配额、当前使用量等
* @throws ServiceException 查询失败时抛出异常
*/
suspend fun getAgentRuleQuota(openId: String): AgentRuleQuotaEntity
}
class AgentRuleServiceImpl : AgentRuleService {
private val gson = Gson()
override suspend fun createAgentRuleByOpenId(openId: String, rule: String) {
val body = CreateAgentRuleRequestBody(
rule = rule,
promptId = null,
openId = openId
)
val resp = ApiClient.api.createAgentRule(body)
if (!resp.isSuccessful) {
val errorText = resp.errorBody()?.string()
try {
val err = gson.fromJson(errorText, InsufficientBalanceError::class.java)
if (err != null && err.code == 35600) {
throw ServiceException(err.message)
}
} catch (_: Exception) {
// ignore parse error
}
throw ServiceException("创建 Agent 规则失败: HTTP ${resp.code()}")
}
}
override suspend fun updateAgentRule(id: Int, rule: String, openId: String?) {
val body = UpdateAgentRuleRequestBody(
id = id,
rule = rule,
promptId = null,
openId = openId
)
val resp = ApiClient.api.updateAgentRule(body)
if (!resp.isSuccessful) {
val errorText = resp.errorBody()?.string()
try {
val err = gson.fromJson(errorText, InsufficientBalanceError::class.java)
if (err != null && err.code == 35600) {
throw ServiceException(err.message)
}
} catch (_: Exception) {
// ignore parse error
}
throw ServiceException("更新 Agent 规则失败: HTTP ${resp.code()}")
}
}
override suspend fun deleteAgentRule(id: Int) {
val resp = ApiClient.api.deleteAgentRule(id)
if (!resp.isSuccessful) {
throw ServiceException("删除 Agent 规则失败: HTTP ${resp.code()}")
}
}
override suspend fun getAgentRuleList(
openId: String,
keyword: String?,
page: Int,
pageSize: Int
): AgentRuleListResult {
val resp = ApiClient.api.getAgentRuleList(
promptId = openId,
rule = keyword,
page = page,
pageSize = pageSize
)
val data = resp.body()?.data ?: throw ServiceException("获取 Agent 规则列表失败")
return data.toEntity()
}
override suspend fun getAgentRuleQuota(openId: String): AgentRuleQuotaEntity {
val resp = ApiClient.api.getAgentRuleQuota(openId)
val data = resp.body()?.data ?: throw ServiceException("获取 Agent 规则配额失败")
return data.toEntity()
}
private fun AgentRuleListResponse.toEntity(): AgentRuleListResult = AgentRuleListResult(
page = page,
pageSize = pageSize,
total = total,
list = list.map { it.toEntity() }
)
private fun AgentRule.toEntity(): AgentRuleEntity = AgentRuleEntity(
id = id,
rule = rule,
creator = creator,
creatorType = creatorType,
scope = scope,
agent = prompt.toEntity(),
createdAt = createdAt,
updatedAt = updatedAt,
)
private fun AgentRuleAgent.toEntity(): AgentRuleAgentInfo = AgentRuleAgentInfo(
id = id,
title = title,
avatar = avatar,
)
private fun AgentRuleQuota.toEntity(): AgentRuleQuotaEntity = AgentRuleQuotaEntity(
agentId = promptId,
agentTitle = promptTitle,
baseMaxCount = baseMaxCount,
purchasedCount = purchasedCount,
totalMaxCount = totalMaxCount,
currentCount = currentCount,
remainingCount = remainingCount,
usagePercent = usagePercent,
)
}

View File

@@ -9,7 +9,7 @@ import com.google.gson.annotations.SerializedName
data class ListContainer<T>( data class ListContainer<T>(
// 总数 // 总数
@SerializedName("total") @SerializedName("total")
val total: Int, val total: Long,
// 当前页 // 当前页
@SerializedName("page") @SerializedName("page")
val page: Int, val page: Int,

View File

@@ -1,11 +1,16 @@
package com.aiosman.ravenow.data package com.aiosman.ravenow.data
import com.aiosman.ravenow.AppStore
import com.aiosman.ravenow.data.api.ApiClient import com.aiosman.ravenow.data.api.ApiClient
import com.aiosman.ravenow.entity.AgentEntity import com.aiosman.ravenow.data.api.CreateRoomRuleRequestBody
import com.aiosman.ravenow.data.api.UpdateRoomRuleRequestBody
import com.aiosman.ravenow.data.api.RoomRuleQuota
import com.aiosman.ravenow.data.api.RoomRule
import com.aiosman.ravenow.data.api.RoomRuleCreator
import com.aiosman.ravenow.entity.CreatorEntity import com.aiosman.ravenow.entity.CreatorEntity
import com.aiosman.ravenow.entity.ProfileEntity
import com.aiosman.ravenow.entity.RoomEntity import com.aiosman.ravenow.entity.RoomEntity
import com.aiosman.ravenow.entity.RoomRuleEntity
import com.aiosman.ravenow.entity.RoomRuleCreatorEntity
import com.aiosman.ravenow.entity.RoomRuleQuotaEntity
import com.aiosman.ravenow.entity.UsersEntity import com.aiosman.ravenow.entity.UsersEntity
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
@@ -106,6 +111,190 @@ data class Users(
} }
} }
/**
* 房间规则相关服务
*/
interface RoomService {
/**
* 创建房间规则
* @param rule 规则内容,不能为空
* @param roomId 房间ID与 trtcId 二选一
* @param trtcId TRTC房间ID与 roomId 二选一
* @throws ServiceException 创建失败时抛出异常
*/
suspend fun createRoomRule(
rule: String,
roomId: Int? = null,
trtcId: String? = null
)
/**
* 修改房间规则
* @param id 规则ID
* @param rule 新的规则内容,不能为空
* @throws ServiceException 修改失败时抛出异常
*/
suspend fun updateRoomRule(
id: Int,
rule: String
)
/**
* 删除房间规则
* @param id 规则ID
* @throws ServiceException 删除失败时抛出异常
*/
suspend fun deleteRoomRule(id: Int)
/**
* 查询房间规则列表
* @param roomId 房间ID与 trtcId 二选一
* @param trtcId TRTC房间ID与 roomId 二选一
* @param page 页码,默认 1
* @param pageSize 每页数量,默认 10
* @return 规则列表响应,包含分页信息和规则列表
* @throws ServiceException 查询失败时抛出异常
*/
suspend fun getRoomRuleList(
roomId: Int? = null,
trtcId: String? = null,
page: Int = 1,
pageSize: Int = 10
): ListContainer<RoomRuleEntity>
/**
* 查询规则配额使用情况
* @param roomId 房间ID与 trtcId 二选一
* @param trtcId TRTC房间ID与 roomId 二选一
* @return 规则配额信息
* @throws ServiceException 查询失败时抛出异常
*/
suspend fun getRoomRuleQuota(
roomId: Int? = null,
trtcId: String? = null
): RoomRuleQuotaEntity
}
/**
* 房间规则服务实现类
*/
class RoomServiceImpl : RoomService {
override suspend fun createRoomRule(
rule: String,
roomId: Int?,
trtcId: String?
) {
val resp = ApiClient.api.createRoomRule(
CreateRoomRuleRequestBody(
rule = rule,
roomId = roomId,
trtcId = trtcId
)
)
if (!resp.isSuccessful) {
throw ServiceException("创建房间规则失败")
}
}
override suspend fun updateRoomRule(
id: Int,
rule: String
) {
val resp = ApiClient.api.updateRoomRule(
UpdateRoomRuleRequestBody(
id = id,
rule = rule
)
)
if (!resp.isSuccessful) {
throw ServiceException("修改房间规则失败")
}
}
override suspend fun deleteRoomRule(id: Int) {
val resp = ApiClient.api.deleteRoomRule(id)
if (!resp.isSuccessful) {
throw ServiceException("删除房间规则失败")
}
}
override suspend fun getRoomRuleList(
roomId: Int?,
trtcId: String?,
page: Int,
pageSize: Int
): ListContainer<RoomRuleEntity> {
val resp = ApiClient.api.getRoomRuleList(
roomId = roomId,
trtcId = trtcId,
page = page,
pageSize = pageSize
)
val body = resp.body() ?: throw ServiceException("获取房间规则列表失败")
return ListContainer(
list = body.list.map { it.toRoomRuleEntity() },
page = body.page,
total = body.total,
pageSize = body.pageSize
)
}
override suspend fun getRoomRuleQuota(
roomId: Int?,
trtcId: String?
): RoomRuleQuotaEntity {
val resp = ApiClient.api.getRoomRuleQuota(
roomId = roomId,
trtcId = trtcId
)
val body = resp.body() ?: throw ServiceException("获取规则配额信息失败")
val data = body.data ?: throw ServiceException("规则配额数据为空")
return data.toRoomRuleQuotaEntity()
}
}
/**
* RoomRule 扩展函数,转换为 RoomRuleEntity
*/
fun RoomRule.toRoomRuleEntity(): RoomRuleEntity {
return RoomRuleEntity(
id = id,
rule = rule,
creator = creator?.toRoomRuleCreatorEntity(),
creatorType = creatorType,
roomId = roomId,
createdAt = createdAt,
updatedAt = updatedAt
)
}
/**
* RoomRuleCreator 扩展函数,转换为 RoomRuleCreatorEntity
*/
fun RoomRuleCreator.toRoomRuleCreatorEntity(): RoomRuleCreatorEntity {
return RoomRuleCreatorEntity(
id = id,
nickname = nickname,
avatar = avatar
)
}
/**
* RoomRuleQuota 扩展函数,转换为 RoomRuleQuotaEntity
*/
fun RoomRuleQuota.toRoomRuleQuotaEntity(): RoomRuleQuotaEntity {
return RoomRuleQuotaEntity(
baseMaxCount = baseMaxCount,
purchasedCount = purchasedCount,
totalMaxCount = totalMaxCount,
currentCount = currentCount,
remainingCount = remainingCount,
usagePercent = usagePercent
)
}

View File

@@ -489,7 +489,7 @@ data class CategoryListResponse(
val list: List<CategoryTemplate> val list: List<CategoryTemplate>
) )
// ========== Prompt Rule 相关数据类 ========== // ========== Agent Rule 相关数据类 ==========
/** /**
* 创建规则请求体 * 创建规则请求体
@@ -497,7 +497,7 @@ data class CategoryListResponse(
* @param promptId 智能体ID与 openId 二选一promptId 优先 * @param promptId 智能体ID与 openId 二选一promptId 优先
* @param openId 智能体的 OpenIDUUID格式与 promptId 二选一 * @param openId 智能体的 OpenIDUUID格式与 promptId 二选一
*/ */
data class CreatePromptRuleRequestBody( data class CreateAgentRuleRequestBody(
@SerializedName("rule") @SerializedName("rule")
val rule: String, val rule: String,
@SerializedName("promptId") @SerializedName("promptId")
@@ -513,7 +513,7 @@ data class CreatePromptRuleRequestBody(
* @param promptId 要更改关联的智能体ID可选 * @param promptId 要更改关联的智能体ID可选
* @param openId 要更改关联的智能体 OpenID可选 * @param openId 要更改关联的智能体 OpenID可选
*/ */
data class UpdatePromptRuleRequestBody( data class UpdateAgentRuleRequestBody(
@SerializedName("id") @SerializedName("id")
val id: Int, val id: Int,
@SerializedName("rule") @SerializedName("rule")
@@ -530,7 +530,7 @@ data class UpdatePromptRuleRequestBody(
* @param title 智能体标题 * @param title 智能体标题
* @param avatar 智能体头像URL * @param avatar 智能体头像URL
*/ */
data class PromptRuleAgent( data class AgentRuleAgent(
@SerializedName("id") @SerializedName("id")
val id: Int, val id: Int,
@SerializedName("title") @SerializedName("title")
@@ -550,7 +550,7 @@ data class PromptRuleAgent(
* @param createdAt 创建时间ISO 8601 格式) * @param createdAt 创建时间ISO 8601 格式)
* @param updatedAt 更新时间ISO 8601 格式) * @param updatedAt 更新时间ISO 8601 格式)
*/ */
data class PromptRule( data class AgentRule(
@SerializedName("id") @SerializedName("id")
val id: Int, val id: Int,
@SerializedName("rule") @SerializedName("rule")
@@ -562,7 +562,7 @@ data class PromptRule(
@SerializedName("scope") @SerializedName("scope")
val scope: String, val scope: String,
@SerializedName("prompt") @SerializedName("prompt")
val prompt: PromptRuleAgent, val prompt: AgentRuleAgent,
@SerializedName("created_at") @SerializedName("created_at")
val createdAt: String, val createdAt: String,
@SerializedName("updated_at") @SerializedName("updated_at")
@@ -576,7 +576,7 @@ data class PromptRule(
* @param total 总记录数 * @param total 总记录数
* @param list 规则列表 * @param list 规则列表
*/ */
data class PromptRuleListResponse( data class AgentRuleListResponse(
@SerializedName("page") @SerializedName("page")
val page: Int, val page: Int,
@SerializedName("pageSize") @SerializedName("pageSize")
@@ -584,7 +584,7 @@ data class PromptRuleListResponse(
@SerializedName("total") @SerializedName("total")
val total: Int, val total: Int,
@SerializedName("list") @SerializedName("list")
val list: List<PromptRule> val list: List<AgentRule>
) )
/** /**
@@ -598,7 +598,7 @@ data class PromptRuleListResponse(
* @param remainingCount 剩余可用条数 * @param remainingCount 剩余可用条数
* @param usagePercent 使用百分比0-100 * @param usagePercent 使用百分比0-100
*/ */
data class PromptRuleQuota( data class AgentRuleQuota(
@SerializedName("promptId") @SerializedName("promptId")
val promptId: Int, val promptId: Int,
@SerializedName("promptTitle") @SerializedName("promptTitle")
@@ -617,6 +617,150 @@ data class PromptRuleQuota(
val usagePercent: Double val usagePercent: Double
) )
// ========== Room Rule 相关数据类 ==========
/**
* 创建房间规则请求体
* @param rule 规则内容,不能为空
* @param roomId 房间ID与 trtcId 二选一
* @param trtcId TRTC房间ID字符串格式与 roomId 二选一
*/
data class CreateRoomRuleRequestBody(
@SerializedName("rule")
val rule: String,
@SerializedName("roomId")
val roomId: Int? = null,
@SerializedName("trtcId")
val trtcId: String? = null
)
/**
* 修改房间规则请求体
* @param id 规则ID必填
* @param rule 新的规则内容,不能为空
*/
data class UpdateRoomRuleRequestBody(
@SerializedName("id")
val id: Int,
@SerializedName("rule")
val rule: String
)
/**
* 房间规则创建者信息
* @param id 创建者ID
* @param nickname 创建者昵称
* @param avatar 创建者头像文件名
*/
data class RoomRuleCreator(
@SerializedName("id")
val id: Int,
@SerializedName("nickname")
val nickname: String,
@SerializedName("avatar")
val avatar: String
)
/**
* 房间规则详情
* @param id 规则ID
* @param rule 规则内容
* @param creator 创建者信息(可能为 null
* @param creatorType 创建者类型(如 "user"
* @param roomId 所属房间ID
* @param createdAt 创建时间ISO 8601 格式)
* @param updatedAt 更新时间ISO 8601 格式)
*/
data class RoomRule(
@SerializedName("id")
val id: Int,
@SerializedName("rule")
val rule: String,
@SerializedName("creator")
val creator: RoomRuleCreator?,
@SerializedName("creatorType")
val creatorType: String,
@SerializedName("roomId")
val roomId: Int,
@SerializedName("createdAt")
val createdAt: String,
@SerializedName("updatedAt")
val updatedAt: String
)
/**
* 房间规则列表响应
* @param page 当前页码
* @param pageSize 每页数量
* @param total 总记录数
* @param list 规则列表
*/
data class RoomRuleListResponse(
@SerializedName("page")
val page: Int,
@SerializedName("pageSize")
val pageSize: Int,
@SerializedName("total")
val total: Long,
@SerializedName("list")
val list: List<RoomRule>
)
/**
* 房间规则配额信息
* @param baseMaxCount 基础条数限制(系统配置的免费配额)
* @param purchasedCount 用户购买的额外条数(通过自动扩容机制)
* @param totalMaxCount 总可用条数baseMaxCount + purchasedCount
* @param currentCount 当前已创建的规则条数(该用户在该房间)
* @param remainingCount 剩余可用条数totalMaxCount - currentCount
* @param usagePercent 使用百分比0-100
*/
data class RoomRuleQuota(
@SerializedName("baseMaxCount")
val baseMaxCount: Int,
@SerializedName("purchasedCount")
val purchasedCount: Int,
@SerializedName("totalMaxCount")
val totalMaxCount: Int,
@SerializedName("currentCount")
val currentCount: Int,
@SerializedName("remainingCount")
val remainingCount: Int,
@SerializedName("usagePercent")
val usagePercent: Double
)
/**
* 积分不足错误响应
*
* 当创建房间规则时积分不足,服务器会返回此特殊错误格式。
* 包含当前余额和所需金额信息,便于客户端提示用户。
*
* @param success 是否成功,固定为 false
* @param code 错误码,固定为 35600积分不足
* @param message 错误消息,包含所需积分和当前余额的详细说明
* @param err 错误类型,固定为 "InsufficientBalance"
* @param currentBalance 当前积分余额
* @param requiredAmount 所需积分数量
* @param traceId 追踪ID可选
*/
data class InsufficientBalanceError(
@SerializedName("success")
val success: Boolean,
@SerializedName("code")
val code: Int,
@SerializedName("message")
val message: String,
@SerializedName("err")
val err: String,
@SerializedName("currentBalance")
val currentBalance: Int,
@SerializedName("requiredAmount")
val requiredAmount: Int,
@SerializedName("traceId")
val traceId: String?
)
interface RaveNowAPI { interface RaveNowAPI {
@GET("membership/config") @GET("membership/config")
@retrofit2.http.Headers("X-Requires-Auth: true") @retrofit2.http.Headers("X-Requires-Auth: true")
@@ -992,7 +1136,7 @@ interface RaveNowAPI {
@Query("pageSize") pageSize: Int? = null @Query("pageSize") pageSize: Int? = null
): Response<ListContainer<Agent>> ): Response<ListContainer<Agent>>
// ========== Prompt Rule API ========== // ========== Agent Rule API ==========
/** /**
* 创建智能体规则 * 创建智能体规则
@@ -1020,8 +1164,8 @@ interface RaveNowAPI {
* ``` * ```
*/ */
@POST("outside/prompt/rule") @POST("outside/prompt/rule")
suspend fun createPromptRule( suspend fun createAgentRule(
@Body body: CreatePromptRuleRequestBody @Body body: CreateAgentRuleRequestBody
): Response<Unit> ): Response<Unit>
/** /**
@@ -1054,8 +1198,8 @@ interface RaveNowAPI {
* ``` * ```
*/ */
@retrofit2.http.PUT("outside/prompt/rule") @retrofit2.http.PUT("outside/prompt/rule")
suspend fun updatePromptRule( suspend fun updateAgentRule(
@Body body: UpdatePromptRuleRequestBody @Body body: UpdateAgentRuleRequestBody
): Response<Unit> ): Response<Unit>
/** /**
@@ -1079,7 +1223,7 @@ interface RaveNowAPI {
* ``` * ```
*/ */
@DELETE("outside/prompt/rule/{id}") @DELETE("outside/prompt/rule/{id}")
suspend fun deletePromptRule( suspend fun deleteAgentRule(
@Path("id") id: Int @Path("id") id: Int
): Response<Unit> ): Response<Unit>
@@ -1134,12 +1278,12 @@ interface RaveNowAPI {
* ``` * ```
*/ */
@GET("outside/prompt/{promptId}/rule/list") @GET("outside/prompt/{promptId}/rule/list")
suspend fun getPromptRuleList( suspend fun getAgentRuleList(
@Path("promptId") promptId: String, @Path("promptId") promptId: String,
@Query("rule") rule: String? = null, @Query("rule") rule: String? = null,
@Query("page") page: Int = 1, @Query("page") page: Int = 1,
@Query("pageSize") pageSize: Int = 10 @Query("pageSize") pageSize: Int = 10
): Response<DataContainer<PromptRuleListResponse>> ): Response<DataContainer<AgentRuleListResponse>>
/** /**
* 查询智能体规则配额信息 * 查询智能体规则配额信息
@@ -1189,9 +1333,264 @@ interface RaveNowAPI {
* ``` * ```
*/ */
@GET("outside/prompt/{promptId}/rule/count") @GET("outside/prompt/{promptId}/rule/count")
suspend fun getPromptRuleQuota( suspend fun getAgentRuleQuota(
@Path("promptId") promptId: String @Path("promptId") promptId: String
): Response<DataContainer<PromptRuleQuota>> ): Response<DataContainer<AgentRuleQuota>>
// ========== Room Rule API ==========
/**
* 创建房间规则
*
* 功能说明:
* - 为指定的房间创建一条新规则
* - 规则会被添加到AI对话上下文中用于约束房间内的行为和交互方式
* - 必须是房间成员或房间创建者才能创建规则
* - 每个用户在每个房间有基础条数限制,达到限制后会自动扣费扩容
* - 如果积分不足,会返回特殊的错误响应(错误码 35600
*
* @param body 创建规则请求体
* - rule: 规则内容,不能为空
* - roomId: 房间ID与 trtcId 二选一)
* - trtcId: TRTC房间ID与 roomId 二选一)
*
* @return 成功时返回空 Unit失败时返回错误信息
*
* 错误响应说明:
* - 400 (40001): 缺少房间标识(必须提供 roomId 或 trtcId
* - 400 (40002): 规则内容为空
* - 400 (40003): 规则内容超出字数限制
* - 400 (35600): 积分不足(自动扩容失败),返回 InsufficientBalanceError 格式
* - 401 (40101): 未登录
* - 403 (40301): 无权限为该房间创建规则
* - 400 (40005): 房间不存在
*
* 示例:
* ```kotlin
* // 使用 roomId 创建规则
* val request1 = CreateRoomRuleRequestBody(
* rule = "禁止在房间内讨论政治话题",
* roomId = 123
* )
* val response1 = api.createRoomRule(request1)
*
* // 使用 trtcId 创建规则
* val request2 = CreateRoomRuleRequestBody(
* rule = "请使用文明用语",
* trtcId = "room_trtc_123"
* )
* val response2 = api.createRoomRule(request2)
*
* // 处理积分不足错误
* if (!response1.isSuccessful && response1.code() == 400) {
* val errorBody = response1.errorBody()?.string()
* // 解析为 InsufficientBalanceError 获取余额信息
* }
* ```
*/
@POST("outside/room/rule")
suspend fun createRoomRule(
@Body body: CreateRoomRuleRequestBody
): Response<Unit>
/**
* 修改房间规则
*
* 功能说明:
* - 修改指定的房间规则内容
* - 只能修改自己创建的规则creator_type 为 "user"
* - 修改不会增加规则条数,因此不会触发扣费
*
* @param body 修改规则请求体
* - id: 规则ID必填
* - rule: 新的规则内容,不能为空
*
* @return 成功时返回空 Unit失败时返回错误信息
*
* 权限要求:
* - 必须是规则的创建者
* - 规则必须是用户类型creator_type = "user"
*
* 错误响应说明:
* - 400 (40002): 规则内容为空
* - 400 (40003): 规则内容超出字数限制
* - 401 (40101): 未登录
* - 403 (40301): 无权限修改该规则
* - 404 (40401): 规则不存在
*
* 示例:
* ```kotlin
* val request = UpdateRoomRuleRequestBody(
* id = 456,
* rule = "请保持友善交流"
* )
* val response = api.updateRoomRule(request)
* ```
*/
@retrofit2.http.PUT("outside/room/rule")
suspend fun updateRoomRule(
@Body body: UpdateRoomRuleRequestBody
): Response<Unit>
/**
* 删除房间规则
*
* 功能说明:
* - 删除指定的房间规则
* - 只能删除自己创建的规则creator_type 为 "user"
* - 删除操作不可恢复,请谨慎操作
* - 删除规则不会退还已扣除的积分
*
* @param id 规则ID
*
* @return 成功时返回空 Unit失败时返回错误信息
*
* 权限要求:
* - 必须是规则的创建者
* - 规则必须是用户类型creator_type = "user"
*
* 错误响应说明:
* - 400 (40006): 无效的规则ID
* - 401 (40101): 未登录
* - 403 (40301): 无权限删除该规则
*
* 示例:
* ```kotlin
* val response = api.deleteRoomRule(456)
* ```
*/
@DELETE("outside/room/rule/{id}")
suspend fun deleteRoomRule(
@Path("id") id: Int
): Response<Unit>
/**
* 查询房间规则列表
*
* 功能说明:
* - 查询指定房间的规则列表,支持分页
* - 返回该房间所有用户创建的规则,包含创建者信息
* - 必须是房间成员或房间创建者才能查询
*
* @param roomId 房间ID与 trtcId 二选一
* @param trtcId TRTC房间ID与 roomId 二选一
* @param page 页码,默认 1
* @param pageSize 每页数量,默认 10
*
* @return 返回分页的规则列表,包含规则详情和创建者信息
*
* 响应数据说明:
* - page: 当前页码
* - pageSize: 每页数量
* - total: 总记录数
* - list: 规则列表
* - id: 规则ID
* - rule: 规则内容
* - creator: 创建者信息id, nickname, avatar
* - creatorType: 创建者类型("user" 等)
* - roomId: 所属房间ID
* - createdAt: 创建时间ISO 8601格式
* - updatedAt: 更新时间ISO 8601格式
*
* 错误响应说明:
* - 400 (40001): 缺少房间标识(必须提供 roomId 或 trtcId
* - 401 (40101): 未登录
* - 403 (40301): 无权限查看该房间的规则
*
* 示例:
* ```kotlin
* // 使用 roomId 查询
* val response1 = api.getRoomRuleList(
* roomId = 123,
* page = 1,
* pageSize = 10
* )
*
* // 使用 trtcId 查询
* val response2 = api.getRoomRuleList(
* trtcId = "room_trtc_123",
* page = 1,
* pageSize = 10
* )
*
* // 处理响应
* response1.body()?.let { result ->
* println("共 ${result.total} 条规则,当前第 ${result.page} 页")
* result.list.forEach { rule ->
* println("规则: ${rule.rule}, 创建者: ${rule.creator?.nickname}")
* }
* }
* ```
*/
@GET("outside/room/rule")
suspend fun getRoomRuleList(
@Query("roomId") roomId: Int? = null,
@Query("trtcId") trtcId: String? = null,
@Query("page") page: Int = 1,
@Query("pageSize") pageSize: Int = 10
): Response<RoomRuleListResponse>
/**
* 查询规则额度使用情况
*
* 功能说明:
* - 查询当前用户在指定房间的规则额度使用情况
* - 包括基础条数、已购买条数、当前使用数等完整信息
* - 用于判断用户是否还能创建新规则
* - 必须是房间成员或房间创建者才能查询
*
* @param roomId 房间ID与 trtcId 二选一
* @param trtcId TRTC房间ID与 roomId 二选一
*
* @return 返回配额详细信息
*
* 响应数据说明:
* - baseMaxCount: 基础条数限制(系统配置的免费配额)
* - purchasedCount: 用户购买的额外条数(通过自动扩容机制)
* - totalMaxCount: 总可用条数baseMaxCount + purchasedCount
* - currentCount: 当前已创建的规则条数(该用户在该房间)
* - remainingCount: 剩余可用条数totalMaxCount - currentCount
* - usagePercent: 使用百分比0-100currentCount / totalMaxCount * 100
*
* 使用场景:
* 1. 创建规则前检查是否有足够配额
* 2. 展示规则使用情况统计
* 3. 提示用户即将达到配额上限
*
* 错误响应说明:
* - 400 (40001): 缺少房间标识(必须提供 roomId 或 trtcId
* - 401 (40101): 未登录
* - 403 (40301): 无权限查看该房间的规则条数信息
*
* 示例:
* ```kotlin
* // 使用 roomId 查询
* val response1 = api.getRoomRuleQuota(roomId = 123)
*
* // 使用 trtcId 查询
* val response2 = api.getRoomRuleQuota(trtcId = "room_trtc_123")
*
* // 处理响应
* response1.body()?.data?.let { quota ->
* if (quota.remainingCount > 0) {
* // 可以创建新规则
* println("还可以创建 ${quota.remainingCount} 条规则")
* println("使用率: ${quota.usagePercent}%")
* } else {
* // 配额已用完,需要扩容
* println("规则配额已用完,已使用 ${quota.currentCount}/${quota.totalMaxCount}")
* println("创建新规则将自动扣除积分进行扩容")
* }
* }
* ```
*/
@GET("outside/room/rule/length-info")
suspend fun getRoomRuleQuota(
@Query("roomId") roomId: Int? = null,
@Query("trtcId") trtcId: String? = null
): Response<DataContainer<RoomRuleQuota>>
} }

View File

@@ -7,18 +7,13 @@ import com.aiosman.ravenow.data.Agent
import com.aiosman.ravenow.data.ListContainer import com.aiosman.ravenow.data.ListContainer
import com.aiosman.ravenow.data.AgentService import com.aiosman.ravenow.data.AgentService
import com.aiosman.ravenow.data.DataContainer import com.aiosman.ravenow.data.DataContainer
import com.aiosman.ravenow.data.MomentService
import com.aiosman.ravenow.data.ServiceException import com.aiosman.ravenow.data.ServiceException
import com.aiosman.ravenow.data.UploadImage import com.aiosman.ravenow.data.UploadImage
import com.aiosman.ravenow.data.UserService
import com.aiosman.ravenow.data.api.ApiClient import com.aiosman.ravenow.data.api.ApiClient
import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody import okhttp3.MultipartBody
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.asRequestBody import okhttp3.RequestBody.Companion.asRequestBody
import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.RequestBody.Companion.toRequestBody
import retrofit2.http.Part
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
@@ -160,7 +155,7 @@ class AgentBackend {
return if (authorId != null) { return if (authorId != null) {
// getAgent 返回 DataContainer<ListContainer<Agent>> // getAgent 返回 DataContainer<ListContainer<Agent>>
val dataContainer = val dataContainer =
body as com.aiosman.ravenow.data.DataContainer<com.aiosman.ravenow.data.ListContainer<com.aiosman.ravenow.data.Agent>> body as DataContainer<ListContainer<Agent>>
val listContainer = dataContainer.data val listContainer = dataContainer.data
ListContainer( ListContainer(
total = listContainer.total, total = listContainer.total,
@@ -171,7 +166,7 @@ class AgentBackend {
} else { } else {
// getMyAgent 返回 ListContainer<Agent> // getMyAgent 返回 ListContainer<Agent>
val listContainer = val listContainer =
body as com.aiosman.ravenow.data.ListContainer<com.aiosman.ravenow.data.Agent> body as ListContainer<Agent>
ListContainer( ListContainer(
total = listContainer.total, total = listContainer.total,
page = pageNumber, page = pageNumber,
@@ -251,7 +246,7 @@ class AgentLoader : DataLoader<AgentEntity, AgentLoaderExtraArgs>() {
} else { } else {
// getMyAgent 返回 ListContainer<Agent> // getMyAgent 返回 ListContainer<Agent>
val listContainer = val listContainer =
body as com.aiosman.ravenow.data.ListContainer<com.aiosman.ravenow.data.Agent> body as ListContainer<Agent>
ListContainer( ListContainer(
list = listContainer.list.map { it.toAgentEntity() }, list = listContainer.list.map { it.toAgentEntity() },
total = listContainer.total, total = listContainer.total,

View File

@@ -9,7 +9,7 @@ import com.aiosman.ravenow.data.ListContainer
abstract class DataLoader<T,ET> { abstract class DataLoader<T,ET> {
var list: MutableList<T> = mutableListOf() var list: MutableList<T> = mutableListOf()
var page by mutableStateOf(1) var page by mutableStateOf(1)
var total by mutableStateOf(0) var total by mutableStateOf(0L)
var pageSize by mutableStateOf(10) var pageSize by mutableStateOf(10)
var hasNext by mutableStateOf(true) var hasNext by mutableStateOf(true)
var isLoading by mutableStateOf(false) var isLoading by mutableStateOf(false)

View File

@@ -3,10 +3,6 @@ package com.aiosman.ravenow.entity
import com.aiosman.ravenow.data.ListContainer import com.aiosman.ravenow.data.ListContainer
import com.aiosman.ravenow.data.ServiceException import com.aiosman.ravenow.data.ServiceException
import com.aiosman.ravenow.data.api.ApiClient import com.aiosman.ravenow.data.api.ApiClient
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
import okhttp3.RequestBody.Companion.asRequestBody
import java.io.File
/** /**
* 群聊房间 * 群聊房间
@@ -56,6 +52,40 @@ data class ProfileEntity(
val aiAccount: Boolean, val aiAccount: Boolean,
) )
/**
* 房间规则创建者信息
*/
data class RoomRuleCreatorEntity(
val id: Int,
val nickname: String,
val avatar: String
)
/**
* 房间规则详情
*/
data class RoomRuleEntity(
val id: Int,
val rule: String,
val creator: RoomRuleCreatorEntity?,
val creatorType: String,
val roomId: Int,
val createdAt: String,
val updatedAt: String
)
/**
* 房间规则配额信息
*/
data class RoomRuleQuotaEntity(
val baseMaxCount: Int,
val purchasedCount: Int,
val totalMaxCount: Int,
val currentCount: Int,
val remainingCount: Int,
val usagePercent: Double
)
class RoomLoader : DataLoader<AgentEntity,AgentLoaderExtraArgs>() { class RoomLoader : DataLoader<AgentEntity,AgentLoaderExtraArgs>() {
override suspend fun fetchData( override suspend fun fetchData(
page: Int, page: Int,

View File

@@ -118,7 +118,7 @@ fun ProfileV3(
onLike: (MomentEntity) -> Unit = {}, onLike: (MomentEntity) -> Unit = {},
onComment: (MomentEntity) -> Unit = {}, onComment: (MomentEntity) -> Unit = {},
onAgentClick: (AgentEntity) -> Unit = {}, onAgentClick: (AgentEntity) -> Unit = {},
postCount: Int? = null, // 新增参数用于传递帖子总数 postCount: Long? = null, // 新增参数用于传递帖子总数
) { ) {
val model = MyProfileViewModel val model = MyProfileViewModel
val pagerState = rememberPagerState(pageCount = { if (isAiAccount) 1 else 2 }) val pagerState = rememberPagerState(pageCount = { if (isAiAccount) 1 else 2 })
@@ -357,7 +357,7 @@ fun ProfileV3(
profile?.let { profile?.let {
UserItem( UserItem(
accountProfileEntity = it, accountProfileEntity = it,
postCount = postCount ?: if (isSelf) MyProfileViewModel.momentLoader.total else moments.size postCount = postCount ?: if (isSelf) MyProfileViewModel.momentLoader.total else moments.size.toLong()
) )
} }
} }

View File

@@ -33,7 +33,7 @@ import com.aiosman.ravenow.ui.modifiers.noRippleClickable
@Composable @Composable
fun UserItem( fun UserItem(
accountProfileEntity: AccountProfileEntity, accountProfileEntity: AccountProfileEntity,
postCount: Int = 0 postCount: Long = 0
) { ) {
val navController = LocalNavController.current val navController = LocalNavController.current
val AppColors = LocalAppTheme.current val AppColors = LocalAppTheme.current