Compare commits
10 Commits
revert-48-
...
atm
| Author | SHA1 | Date | |
|---|---|---|---|
| 2ba4c2ec02 | |||
| 28061617da | |||
| c9e411c4ad | |||
| 995e061b6f | |||
| 9c9eb66b71 | |||
| f90bfbfa0f | |||
| 9ea03cee34 | |||
| 1de8bb825c | |||
| 4a1c15747c | |||
| cff6b78c30 |
4
.idea/deploymentTargetSelector.xml
generated
@@ -4,10 +4,10 @@
|
||||
<selectionStates>
|
||||
<SelectionState runConfigName="app">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
<DropdownSelection timestamp="2025-09-17T06:25:35.585100400Z">
|
||||
<DropdownSelection timestamp="2025-11-05T12:24:27.034893100Z">
|
||||
<Target type="DEFAULT_BOOT">
|
||||
<handle>
|
||||
<DeviceId pluginId="Default" identifier="serial=192.168.0.216:5555;connection=698a7727" />
|
||||
<DeviceId pluginId="PhysicalDevice" identifier="serial=c328a150" />
|
||||
</handle>
|
||||
</Target>
|
||||
</DropdownSelection>
|
||||
|
||||
@@ -2,8 +2,16 @@ package com.aiosman.ravenow.data
|
||||
|
||||
import com.aiosman.ravenow.AppStore
|
||||
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.ProfileEntity
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class Agent(
|
||||
@@ -83,9 +91,16 @@ data class Profile(
|
||||
)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 智能体服务
|
||||
*/
|
||||
interface AgentService {
|
||||
/**
|
||||
* 获取智能体列表
|
||||
* @param pageNumber 页码
|
||||
* @param pageSize 每页数量,默认 20
|
||||
* @param authorId 作者ID,可选参数,用于筛选特定作者的智能体
|
||||
* @return 智能体列表容器,包含分页信息和智能体列表,失败时返回 null
|
||||
*/
|
||||
suspend fun getAgent(
|
||||
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,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import com.google.gson.annotations.SerializedName
|
||||
data class ListContainer<T>(
|
||||
// 总数
|
||||
@SerializedName("total")
|
||||
val total: Int,
|
||||
val total: Long,
|
||||
// 当前页
|
||||
@SerializedName("page")
|
||||
val page: Int,
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
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.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.ProfileEntity
|
||||
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.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
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -489,7 +489,7 @@ data class CategoryListResponse(
|
||||
val list: List<CategoryTemplate>
|
||||
)
|
||||
|
||||
// ========== Prompt Rule 相关数据类 ==========
|
||||
// ========== Agent Rule 相关数据类 ==========
|
||||
|
||||
/**
|
||||
* 创建规则请求体
|
||||
@@ -497,7 +497,7 @@ data class CategoryListResponse(
|
||||
* @param promptId 智能体ID,与 openId 二选一,promptId 优先
|
||||
* @param openId 智能体的 OpenID(UUID格式),与 promptId 二选一
|
||||
*/
|
||||
data class CreatePromptRuleRequestBody(
|
||||
data class CreateAgentRuleRequestBody(
|
||||
@SerializedName("rule")
|
||||
val rule: String,
|
||||
@SerializedName("promptId")
|
||||
@@ -513,7 +513,7 @@ data class CreatePromptRuleRequestBody(
|
||||
* @param promptId 要更改关联的智能体ID(可选)
|
||||
* @param openId 要更改关联的智能体 OpenID(可选)
|
||||
*/
|
||||
data class UpdatePromptRuleRequestBody(
|
||||
data class UpdateAgentRuleRequestBody(
|
||||
@SerializedName("id")
|
||||
val id: Int,
|
||||
@SerializedName("rule")
|
||||
@@ -530,7 +530,7 @@ data class UpdatePromptRuleRequestBody(
|
||||
* @param title 智能体标题
|
||||
* @param avatar 智能体头像URL
|
||||
*/
|
||||
data class PromptRuleAgent(
|
||||
data class AgentRuleAgent(
|
||||
@SerializedName("id")
|
||||
val id: Int,
|
||||
@SerializedName("title")
|
||||
@@ -550,7 +550,7 @@ data class PromptRuleAgent(
|
||||
* @param createdAt 创建时间(ISO 8601 格式)
|
||||
* @param updatedAt 更新时间(ISO 8601 格式)
|
||||
*/
|
||||
data class PromptRule(
|
||||
data class AgentRule(
|
||||
@SerializedName("id")
|
||||
val id: Int,
|
||||
@SerializedName("rule")
|
||||
@@ -562,7 +562,7 @@ data class PromptRule(
|
||||
@SerializedName("scope")
|
||||
val scope: String,
|
||||
@SerializedName("prompt")
|
||||
val prompt: PromptRuleAgent,
|
||||
val prompt: AgentRuleAgent,
|
||||
@SerializedName("created_at")
|
||||
val createdAt: String,
|
||||
@SerializedName("updated_at")
|
||||
@@ -576,7 +576,7 @@ data class PromptRule(
|
||||
* @param total 总记录数
|
||||
* @param list 规则列表
|
||||
*/
|
||||
data class PromptRuleListResponse(
|
||||
data class AgentRuleListResponse(
|
||||
@SerializedName("page")
|
||||
val page: Int,
|
||||
@SerializedName("pageSize")
|
||||
@@ -584,7 +584,7 @@ data class PromptRuleListResponse(
|
||||
@SerializedName("total")
|
||||
val total: Int,
|
||||
@SerializedName("list")
|
||||
val list: List<PromptRule>
|
||||
val list: List<AgentRule>
|
||||
)
|
||||
|
||||
/**
|
||||
@@ -598,7 +598,7 @@ data class PromptRuleListResponse(
|
||||
* @param remainingCount 剩余可用条数
|
||||
* @param usagePercent 使用百分比(0-100)
|
||||
*/
|
||||
data class PromptRuleQuota(
|
||||
data class AgentRuleQuota(
|
||||
@SerializedName("promptId")
|
||||
val promptId: Int,
|
||||
@SerializedName("promptTitle")
|
||||
@@ -617,6 +617,150 @@ data class PromptRuleQuota(
|
||||
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 {
|
||||
@GET("membership/config")
|
||||
@retrofit2.http.Headers("X-Requires-Auth: true")
|
||||
@@ -992,7 +1136,7 @@ interface RaveNowAPI {
|
||||
@Query("pageSize") pageSize: Int? = null
|
||||
): Response<ListContainer<Agent>>
|
||||
|
||||
// ========== Prompt Rule API ==========
|
||||
// ========== Agent Rule API ==========
|
||||
|
||||
/**
|
||||
* 创建智能体规则
|
||||
@@ -1020,8 +1164,8 @@ interface RaveNowAPI {
|
||||
* ```
|
||||
*/
|
||||
@POST("outside/prompt/rule")
|
||||
suspend fun createPromptRule(
|
||||
@Body body: CreatePromptRuleRequestBody
|
||||
suspend fun createAgentRule(
|
||||
@Body body: CreateAgentRuleRequestBody
|
||||
): Response<Unit>
|
||||
|
||||
/**
|
||||
@@ -1054,8 +1198,8 @@ interface RaveNowAPI {
|
||||
* ```
|
||||
*/
|
||||
@retrofit2.http.PUT("outside/prompt/rule")
|
||||
suspend fun updatePromptRule(
|
||||
@Body body: UpdatePromptRuleRequestBody
|
||||
suspend fun updateAgentRule(
|
||||
@Body body: UpdateAgentRuleRequestBody
|
||||
): Response<Unit>
|
||||
|
||||
/**
|
||||
@@ -1079,7 +1223,7 @@ interface RaveNowAPI {
|
||||
* ```
|
||||
*/
|
||||
@DELETE("outside/prompt/rule/{id}")
|
||||
suspend fun deletePromptRule(
|
||||
suspend fun deleteAgentRule(
|
||||
@Path("id") id: Int
|
||||
): Response<Unit>
|
||||
|
||||
@@ -1134,12 +1278,12 @@ interface RaveNowAPI {
|
||||
* ```
|
||||
*/
|
||||
@GET("outside/prompt/{promptId}/rule/list")
|
||||
suspend fun getPromptRuleList(
|
||||
suspend fun getAgentRuleList(
|
||||
@Path("promptId") promptId: String,
|
||||
@Query("rule") rule: String? = null,
|
||||
@Query("page") page: Int = 1,
|
||||
@Query("pageSize") pageSize: Int = 10
|
||||
): Response<DataContainer<PromptRuleListResponse>>
|
||||
): Response<DataContainer<AgentRuleListResponse>>
|
||||
|
||||
/**
|
||||
* 查询智能体规则配额信息
|
||||
@@ -1189,9 +1333,264 @@ interface RaveNowAPI {
|
||||
* ```
|
||||
*/
|
||||
@GET("outside/prompt/{promptId}/rule/count")
|
||||
suspend fun getPromptRuleQuota(
|
||||
suspend fun getAgentRuleQuota(
|
||||
@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-100(currentCount / 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>>
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -7,18 +7,13 @@ import com.aiosman.ravenow.data.Agent
|
||||
import com.aiosman.ravenow.data.ListContainer
|
||||
import com.aiosman.ravenow.data.AgentService
|
||||
import com.aiosman.ravenow.data.DataContainer
|
||||
import com.aiosman.ravenow.data.MomentService
|
||||
import com.aiosman.ravenow.data.ServiceException
|
||||
import com.aiosman.ravenow.data.UploadImage
|
||||
import com.aiosman.ravenow.data.UserService
|
||||
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
|
||||
|
||||
@@ -160,7 +155,7 @@ class AgentBackend {
|
||||
return if (authorId != null) {
|
||||
// getAgent 返回 DataContainer<ListContainer<Agent>>
|
||||
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
|
||||
ListContainer(
|
||||
total = listContainer.total,
|
||||
@@ -171,7 +166,7 @@ class AgentBackend {
|
||||
} else {
|
||||
// getMyAgent 返回 ListContainer<Agent>
|
||||
val listContainer =
|
||||
body as com.aiosman.ravenow.data.ListContainer<com.aiosman.ravenow.data.Agent>
|
||||
body as ListContainer<Agent>
|
||||
ListContainer(
|
||||
total = listContainer.total,
|
||||
page = pageNumber,
|
||||
@@ -251,7 +246,7 @@ class AgentLoader : DataLoader<AgentEntity, AgentLoaderExtraArgs>() {
|
||||
} else {
|
||||
// getMyAgent 返回 ListContainer<Agent>
|
||||
val listContainer =
|
||||
body as com.aiosman.ravenow.data.ListContainer<com.aiosman.ravenow.data.Agent>
|
||||
body as ListContainer<Agent>
|
||||
ListContainer(
|
||||
list = listContainer.list.map { it.toAgentEntity() },
|
||||
total = listContainer.total,
|
||||
|
||||
@@ -9,7 +9,7 @@ import com.aiosman.ravenow.data.ListContainer
|
||||
abstract class DataLoader<T,ET> {
|
||||
var list: MutableList<T> = mutableListOf()
|
||||
var page by mutableStateOf(1)
|
||||
var total by mutableStateOf(0)
|
||||
var total by mutableStateOf(0L)
|
||||
var pageSize by mutableStateOf(10)
|
||||
var hasNext by mutableStateOf(true)
|
||||
var isLoading by mutableStateOf(false)
|
||||
|
||||
@@ -3,10 +3,6 @@ package com.aiosman.ravenow.entity
|
||||
import com.aiosman.ravenow.data.ListContainer
|
||||
import com.aiosman.ravenow.data.ServiceException
|
||||
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,
|
||||
)
|
||||
|
||||
/**
|
||||
* 房间规则创建者信息
|
||||
*/
|
||||
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>() {
|
||||
override suspend fun fetchData(
|
||||
page: Int,
|
||||
|
||||
@@ -71,4 +71,29 @@ object AppStore {
|
||||
AppState.chatBackgroundUrl = url
|
||||
}
|
||||
|
||||
// ===================== 用户本地扩展信息 =====================
|
||||
// 后端暂未提供 MBTI 与星座字段,使用本地持久化按用户维度进行存储
|
||||
private fun mbtiKey(userId: Int) = "mbti_user_$userId"
|
||||
private fun zodiacKey(userId: Int) = "zodiac_user_$userId"
|
||||
|
||||
fun getUserMbti(userId: Int): String? {
|
||||
return sharedPreferences.getString(mbtiKey(userId), null)
|
||||
}
|
||||
|
||||
fun setUserMbti(userId: Int, mbti: String?) {
|
||||
sharedPreferences.edit().apply {
|
||||
if (mbti.isNullOrEmpty()) remove(mbtiKey(userId)) else putString(mbtiKey(userId), mbti)
|
||||
}.apply()
|
||||
}
|
||||
|
||||
fun getUserZodiac(userId: Int): String? {
|
||||
return sharedPreferences.getString(zodiacKey(userId), null)
|
||||
}
|
||||
|
||||
fun setUserZodiac(userId: Int, zodiac: String?) {
|
||||
sharedPreferences.edit().apply {
|
||||
if (zodiac.isNullOrEmpty()) remove(zodiacKey(userId)) else putString(zodiacKey(userId), zodiac)
|
||||
}.apply()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -35,8 +35,10 @@ import com.aiosman.ravenow.LocalSharedTransitionScope
|
||||
import com.aiosman.ravenow.ui.about.AboutScreen
|
||||
import com.aiosman.ravenow.ui.account.AccountEditScreen2
|
||||
import com.aiosman.ravenow.ui.account.AccountSetting
|
||||
import com.aiosman.ravenow.ui.account.MbtiSelectScreen
|
||||
import com.aiosman.ravenow.ui.account.RemoveAccountScreen
|
||||
import com.aiosman.ravenow.ui.account.ResetPasswordScreen
|
||||
import com.aiosman.ravenow.ui.account.ZodiacSelectScreen
|
||||
import com.aiosman.ravenow.ui.agent.AddAgentScreen
|
||||
import com.aiosman.ravenow.ui.agent.AgentImageCropScreen
|
||||
import com.aiosman.ravenow.ui.group.CreateGroupChatScreen
|
||||
@@ -120,6 +122,8 @@ sealed class NavigationRoute(
|
||||
data object VipSelPage : NavigationRoute("VipSelPage")
|
||||
data object RemoveAccountScreen: NavigationRoute("RemoveAccount")
|
||||
data object NotificationScreen : NavigationRoute("NotificationScreen")
|
||||
data object MbtiSelect : NavigationRoute("MbtiSelect")
|
||||
data object ZodiacSelect : NavigationRoute("ZodiacSelect")
|
||||
}
|
||||
|
||||
|
||||
@@ -421,6 +425,12 @@ fun NavigationController(
|
||||
composable(route = NavigationRoute.RemoveAccountScreen.route) {
|
||||
RemoveAccountScreen()
|
||||
}
|
||||
composable(route = NavigationRoute.MbtiSelect.route) {
|
||||
MbtiSelectScreen()
|
||||
}
|
||||
composable(route = NavigationRoute.ZodiacSelect.route) {
|
||||
ZodiacSelectScreen()
|
||||
}
|
||||
composable(route = NavigationRoute.VipSelPage.route) {
|
||||
VipSelPage()
|
||||
}
|
||||
|
||||
@@ -26,6 +26,9 @@ object AccountEditViewModel : ViewModel() {
|
||||
var croppedBitmap by mutableStateOf<Bitmap?>(null)
|
||||
var isUpdating by mutableStateOf(false)
|
||||
var isLoading by mutableStateOf(false)
|
||||
// 本地扩展字段
|
||||
var mbti by mutableStateOf<String?>(null)
|
||||
var zodiac by mutableStateOf<String?>(null)
|
||||
suspend fun reloadProfile(updateTrtcProfile:Boolean = false) {
|
||||
Log.d("AccountEditViewModel", "reloadProfile: 开始加载用户资料")
|
||||
isLoading = true
|
||||
@@ -38,6 +41,12 @@ object AccountEditViewModel : ViewModel() {
|
||||
bio = it.bio
|
||||
// 清除之前裁剪的图片
|
||||
croppedBitmap = null
|
||||
// 读取本地扩展字段
|
||||
try {
|
||||
val uid = it.id // 使用 profile 的 id,确保非空
|
||||
mbti = com.aiosman.ravenow.AppStore.getUserMbti(uid)
|
||||
zodiac = com.aiosman.ravenow.AppStore.getUserZodiac(uid)
|
||||
} catch (_: Exception) { }
|
||||
if (updateTrtcProfile) {
|
||||
TrtcHelper.updateTrtcProfile(
|
||||
it.nickName,
|
||||
@@ -84,6 +93,13 @@ object AccountEditViewModel : ViewModel() {
|
||||
nickName = newName,
|
||||
bio = cleanBio
|
||||
)
|
||||
// 保存本地扩展字段
|
||||
try {
|
||||
profile?.id?.let { uid ->
|
||||
com.aiosman.ravenow.AppStore.setUserMbti(uid, mbti)
|
||||
com.aiosman.ravenow.AppStore.setUserZodiac(uid, zodiac)
|
||||
}
|
||||
} catch (_: Exception) { }
|
||||
// 刷新用户资料
|
||||
reloadProfile()
|
||||
// 刷新个人资料页面的用户资料
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
package com.aiosman.ravenow.ui.account
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Check
|
||||
import com.aiosman.ravenow.LocalAppTheme
|
||||
import com.aiosman.ravenow.LocalNavController
|
||||
import com.aiosman.ravenow.R
|
||||
import com.aiosman.ravenow.ui.comment.NoticeScreenHeader
|
||||
|
||||
// MBTI类型列表
|
||||
val MBTI_TYPES = listOf(
|
||||
"INTJ", "INTP", "ENTJ", "ENTP",
|
||||
"INFJ", "INFP", "ENFJ", "ENFP",
|
||||
"ISTJ", "ISFJ", "ESTJ", "ESFJ",
|
||||
"ISTP", "ISFP", "ESTP", "ESFP"
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun MbtiSelectScreen() {
|
||||
val navController = LocalNavController.current
|
||||
val appColors = LocalAppTheme.current
|
||||
val model = AccountEditViewModel
|
||||
val currentMbti = model.mbti
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(appColors.profileBackground)
|
||||
) {
|
||||
// 头部
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 16.dp)
|
||||
) {
|
||||
NoticeScreenHeader(
|
||||
title = stringResource(R.string.choose_mbti),
|
||||
moreIcon = false
|
||||
)
|
||||
}
|
||||
|
||||
// 列表
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentPadding = androidx.compose.foundation.layout.PaddingValues(horizontal = 16.dp, vertical = 8.dp)
|
||||
) {
|
||||
items(MBTI_TYPES) { mbti ->
|
||||
MBTIItem(
|
||||
mbti = mbti,
|
||||
isSelected = mbti == currentMbti,
|
||||
onClick = {
|
||||
model.mbti = mbti
|
||||
navController.navigateUp()
|
||||
}
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MBTIItem(
|
||||
mbti: String,
|
||||
isSelected: Boolean,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
val appColors = LocalAppTheme.current
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.background(if (isSelected) appColors.main.copy(alpha = 0.1f) else Color.White)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
onClick()
|
||||
}
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = mbti,
|
||||
fontSize = 17.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = if (isSelected) appColors.main else appColors.text,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
|
||||
if (isSelected) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Check,
|
||||
contentDescription = "Selected",
|
||||
modifier = Modifier.size(20.dp),
|
||||
tint = appColors.main
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
package com.aiosman.ravenow.ui.account
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Check
|
||||
import com.aiosman.ravenow.LocalAppTheme
|
||||
import com.aiosman.ravenow.LocalNavController
|
||||
import com.aiosman.ravenow.R
|
||||
import com.aiosman.ravenow.ui.comment.NoticeScreenHeader
|
||||
|
||||
// 星座列表
|
||||
val ZODIAC_SIGNS = listOf(
|
||||
"白羊座", "金牛座", "双子座", "巨蟹座",
|
||||
"狮子座", "处女座", "天秤座", "天蝎座",
|
||||
"射手座", "摩羯座", "水瓶座", "双鱼座"
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun ZodiacSelectScreen() {
|
||||
val navController = LocalNavController.current
|
||||
val appColors = LocalAppTheme.current
|
||||
val model = AccountEditViewModel
|
||||
val currentZodiac = model.zodiac
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(appColors.profileBackground)
|
||||
) {
|
||||
// 头部
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 16.dp)
|
||||
) {
|
||||
NoticeScreenHeader(
|
||||
title = stringResource(R.string.choose_zodiac),
|
||||
moreIcon = false
|
||||
)
|
||||
}
|
||||
|
||||
// 列表
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentPadding = androidx.compose.foundation.layout.PaddingValues(horizontal = 16.dp, vertical = 8.dp)
|
||||
) {
|
||||
items(ZODIAC_SIGNS) { zodiac ->
|
||||
ZodiacItem(
|
||||
zodiac = zodiac,
|
||||
isSelected = zodiac == currentZodiac,
|
||||
onClick = {
|
||||
model.zodiac = zodiac
|
||||
navController.navigateUp()
|
||||
}
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ZodiacItem(
|
||||
zodiac: String,
|
||||
isSelected: Boolean,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
val appColors = LocalAppTheme.current
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.background(if (isSelected) appColors.main.copy(alpha = 0.1f) else Color.White)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
onClick()
|
||||
}
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = zodiac,
|
||||
fontSize = 17.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = if (isSelected) appColors.main else appColors.text,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
|
||||
if (isSelected) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Check,
|
||||
contentDescription = "Selected",
|
||||
modifier = Modifier.size(20.dp),
|
||||
tint = appColors.main
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,12 +125,13 @@ fun CommentNoticeScreen() {
|
||||
) {
|
||||
androidx.compose.foundation.Image(
|
||||
painter = painterResource(
|
||||
id =if(AppState.darkMode) R.mipmap.qst_pl_qs_as_img
|
||||
id = if(AppState.darkMode) R.mipmap.tietie_dark
|
||||
else R.mipmap.invalid_name_11),
|
||||
contentDescription = "No Comment",
|
||||
modifier = Modifier.size(181.dp)
|
||||
modifier = Modifier
|
||||
.size(width = 181.dp, height = 153.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.size(24.dp))
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Text(
|
||||
text = "等一位旅人~",
|
||||
color = AppColors.text,
|
||||
|
||||
@@ -137,9 +137,9 @@ fun FavouriteListPage() {
|
||||
id = if (com.aiosman.ravenow.AppState.darkMode) R.mipmap.invalid_dark
|
||||
else R.mipmap.invalid_name_1),
|
||||
contentDescription = "No favourites",
|
||||
modifier = Modifier.size(110.dp)
|
||||
modifier = Modifier.size(181.dp, 153.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.size(24.dp))
|
||||
Spacer(modifier = Modifier.size(9.dp)) // 调整间距为9dp
|
||||
Text(
|
||||
text = stringResource(R.string.favourites_null),
|
||||
color = AppColors.text,
|
||||
|
||||
@@ -124,21 +124,21 @@ fun FollowerListScreen(userId: Int) {
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(
|
||||
id =if(AppState.darkMode) R.mipmap.qst_fs_qs_as_img
|
||||
id = if(AppState.darkMode) R.mipmap.frame_4
|
||||
else R.mipmap.invalid_name_8),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(181.dp)
|
||||
modifier = Modifier.size(181.dp, 153.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.size(24.dp))
|
||||
Spacer(modifier = Modifier.size(9.dp)) // 调整间距为9dp
|
||||
androidx.compose.material.Text(
|
||||
text = "还没有人关注哦",
|
||||
text = "还没有人关注你呢",
|
||||
color = appColors.text,
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.W600
|
||||
)
|
||||
Spacer(modifier = Modifier.size(8.dp))
|
||||
androidx.compose.material.Text(
|
||||
text = "去发布动态,吸引更多粉丝~",
|
||||
text = "试着发信号出来,某人就会被吸引啦~",
|
||||
color = appColors.text,
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.W400
|
||||
|
||||
@@ -114,21 +114,22 @@ fun FollowerNoticeScreen() {
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(
|
||||
id =if(AppState.darkMode) R.mipmap.qst_fs_qs_as_img
|
||||
id = if(AppState.darkMode) R.mipmap.frame_4
|
||||
else R.mipmap.invalid_name_8),
|
||||
contentDescription = "No Followers",
|
||||
modifier = Modifier.size(181.dp)
|
||||
modifier = Modifier
|
||||
.size(width = 181.dp, height = 153.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.size(24.dp))
|
||||
Spacer(modifier = Modifier.height(if (AppState.darkMode) 9.dp else 24.dp))
|
||||
androidx.compose.material.Text(
|
||||
text = "还没有人关注哦",
|
||||
text = "还没有人关注你呢",
|
||||
color = AppColors.text,
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.W600
|
||||
)
|
||||
Spacer(modifier = Modifier.size(8.dp))
|
||||
androidx.compose.material.Text(
|
||||
text = "去发布动态,吸引更多粉丝~",
|
||||
text = "试着发信号出来,某人就会被吸引啦~",
|
||||
color = AppColors.text,
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.W400
|
||||
|
||||
@@ -126,14 +126,14 @@ fun FollowingListScreen(userId: Int) {
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(
|
||||
id =if(AppState.darkMode) R.mipmap.qst_gz_qs_as_img_my
|
||||
id = if(AppState.darkMode) R.mipmap.frame_3
|
||||
else R.mipmap.invalid_name_9),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(181.dp)
|
||||
modifier = Modifier.size(181.dp, 153.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.size(24.dp))
|
||||
Spacer(modifier = Modifier.size(9.dp)) // 调整间距为9dp
|
||||
androidx.compose.material.Text(
|
||||
text = "没有关注任何灵魂",
|
||||
text = "还没有关注任何灵魂",
|
||||
color = appColors.text,
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.W600
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.aiosman.ravenow.ui.group
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
@@ -8,6 +9,10 @@ import androidx.lifecycle.viewModelScope
|
||||
import com.aiosman.ravenow.AppStore
|
||||
import com.aiosman.ravenow.ChatState
|
||||
import com.aiosman.ravenow.data.api.ApiClient
|
||||
import com.aiosman.ravenow.data.api.CreatePromptRuleRequestBody
|
||||
import com.aiosman.ravenow.data.api.PromptRule
|
||||
import com.aiosman.ravenow.data.api.PromptRuleQuota
|
||||
import com.aiosman.ravenow.data.parseErrorResponse
|
||||
import com.aiosman.ravenow.entity.ChatNotification
|
||||
import com.aiosman.ravenow.entity.GroupInfo
|
||||
import com.aiosman.ravenow.entity.GroupMember
|
||||
@@ -22,9 +27,36 @@ class GroupChatInfoViewModel(
|
||||
var isLoading by mutableStateOf(false)
|
||||
var error by mutableStateOf<String?>(null)
|
||||
var chatNotification by mutableStateOf<ChatNotification?>(null)
|
||||
var isAddingMemory by mutableStateOf(false)
|
||||
var addMemoryError by mutableStateOf<String?>(null)
|
||||
var addMemorySuccess by mutableStateOf(false)
|
||||
val notificationStrategy get() = chatNotification?.strategy ?: "default"
|
||||
|
||||
// 记忆管理相关状态
|
||||
var memoryQuota by mutableStateOf<PromptRuleQuota?>(null)
|
||||
var memoryList by mutableStateOf<List<PromptRule>>(emptyList())
|
||||
var isLoadingMemory by mutableStateOf(false)
|
||||
var memoryError by mutableStateOf<String?>(null)
|
||||
var promptOpenId by mutableStateOf<String?>(null)
|
||||
init {
|
||||
loadGroupInfo()
|
||||
loadPromptOpenId()
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取群聊中智能体的 OpenID
|
||||
*/
|
||||
private fun loadPromptOpenId() {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val response = ApiClient.api.createGroupChatAi(trtcGroupId = groupId)
|
||||
val groupChatResponse = response.body()?.data
|
||||
val prompts = groupChatResponse?.prompts
|
||||
promptOpenId = prompts?.firstOrNull()?.openId
|
||||
} catch (e: Exception) {
|
||||
Log.e("GroupChatInfoViewModel", "获取智能体OpenID失败: ${e.message}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
suspend fun updateNotificationStrategy(strategy: String) {
|
||||
val result = ChatState.updateChatNotification(groupId.hashCode(), strategy)
|
||||
@@ -71,4 +103,218 @@ class GroupChatInfoViewModel(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加群记忆
|
||||
* @param memoryText 记忆内容
|
||||
* @param promptOpenId 智能体的 OpenID(可选),如果不提供则从群聊信息中获取
|
||||
*/
|
||||
fun addGroupMemory(memoryText: String, promptOpenId: String? = null) {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
isAddingMemory = true
|
||||
addMemoryError = null
|
||||
addMemorySuccess = false
|
||||
|
||||
// 如果没有提供 promptOpenId,需要先获取群聊的智能体信息
|
||||
val openId = promptOpenId ?: run {
|
||||
// 通过 createGroupChatAi 接口获取群聊详细信息(包含 prompts)
|
||||
val response = ApiClient.api.createGroupChatAi(trtcGroupId = groupId)
|
||||
val groupChatResponse = response.body()?.data
|
||||
val prompts = groupChatResponse?.prompts
|
||||
|
||||
if (prompts.isNullOrEmpty()) {
|
||||
throw Exception("群聊中没有找到智能体,无法添加记忆")
|
||||
}
|
||||
|
||||
// 使用第一个智能体的 openId
|
||||
prompts.firstOrNull()?.openId
|
||||
?: throw Exception("无法获取智能体信息")
|
||||
}
|
||||
|
||||
if (openId.isBlank()) {
|
||||
throw Exception("智能体ID不能为空")
|
||||
}
|
||||
|
||||
// 创建智能体规则(群记忆)
|
||||
val requestBody = CreatePromptRuleRequestBody(
|
||||
rule = memoryText,
|
||||
openId = openId
|
||||
)
|
||||
|
||||
val response = ApiClient.api.createPromptRule(requestBody)
|
||||
|
||||
if (response.isSuccessful) {
|
||||
addMemorySuccess = true
|
||||
Log.d("GroupChatInfoViewModel", "群记忆添加成功")
|
||||
// 刷新记忆列表和配额
|
||||
loadMemoryQuota(openId)
|
||||
loadMemoryList(openId)
|
||||
} else {
|
||||
val errorResponse = parseErrorResponse(response.errorBody())
|
||||
val errorMessage = errorResponse?.toServiceException()?.message
|
||||
?: "添加群记忆失败: ${response.code()}"
|
||||
throw Exception(errorMessage)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
addMemoryError = e.message ?: "添加群记忆失败"
|
||||
Log.e("GroupChatInfoViewModel", "添加群记忆失败: ${e.message}", e)
|
||||
} finally {
|
||||
isAddingMemory = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取记忆配额信息
|
||||
*/
|
||||
fun loadMemoryQuota(openId: String? = null) {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
isLoadingMemory = true
|
||||
memoryError = null
|
||||
|
||||
val targetOpenId = openId ?: promptOpenId
|
||||
if (targetOpenId.isNullOrBlank()) {
|
||||
// 如果还没有获取到 openId,先获取
|
||||
val response = ApiClient.api.createGroupChatAi(trtcGroupId = groupId)
|
||||
val groupChatResponse = response.body()?.data
|
||||
val prompts = groupChatResponse?.prompts
|
||||
val fetchedOpenId = prompts?.firstOrNull()?.openId
|
||||
?: throw Exception("无法获取智能体信息")
|
||||
|
||||
promptOpenId = fetchedOpenId
|
||||
val quotaResponse = ApiClient.api.getPromptRuleQuota(fetchedOpenId)
|
||||
if (quotaResponse.isSuccessful) {
|
||||
memoryQuota = quotaResponse.body()?.data
|
||||
} else {
|
||||
throw Exception("获取配额信息失败: ${quotaResponse.code()}")
|
||||
}
|
||||
} else {
|
||||
val quotaResponse = ApiClient.api.getPromptRuleQuota(targetOpenId)
|
||||
if (quotaResponse.isSuccessful) {
|
||||
memoryQuota = quotaResponse.body()?.data
|
||||
} else {
|
||||
throw Exception("获取配额信息失败: ${quotaResponse.code()}")
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
memoryError = e.message ?: "获取配额信息失败"
|
||||
Log.e("GroupChatInfoViewModel", "获取配额信息失败: ${e.message}", e)
|
||||
} finally {
|
||||
isLoadingMemory = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取记忆列表
|
||||
*/
|
||||
fun loadMemoryList(openId: String? = null, page: Int = 1, pageSize: Int = 20) {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
isLoadingMemory = true
|
||||
memoryError = null
|
||||
|
||||
val targetOpenId = openId ?: promptOpenId
|
||||
if (targetOpenId.isNullOrBlank()) {
|
||||
// 如果还没有获取到 openId,先获取
|
||||
val response = ApiClient.api.createGroupChatAi(trtcGroupId = groupId)
|
||||
val groupChatResponse = response.body()?.data
|
||||
val prompts = groupChatResponse?.prompts
|
||||
val fetchedOpenId = prompts?.firstOrNull()?.openId
|
||||
?: throw Exception("无法获取智能体信息")
|
||||
|
||||
promptOpenId = fetchedOpenId
|
||||
val listResponse = ApiClient.api.getPromptRuleList(fetchedOpenId, page = page, pageSize = pageSize)
|
||||
if (listResponse.isSuccessful) {
|
||||
memoryList = listResponse.body()?.data?.list ?: emptyList()
|
||||
} else {
|
||||
throw Exception("获取记忆列表失败: ${listResponse.code()}")
|
||||
}
|
||||
} else {
|
||||
val listResponse = ApiClient.api.getPromptRuleList(targetOpenId, page = page, pageSize = pageSize)
|
||||
if (listResponse.isSuccessful) {
|
||||
memoryList = listResponse.body()?.data?.list ?: emptyList()
|
||||
} else {
|
||||
throw Exception("获取记忆列表失败: ${listResponse.code()}")
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
memoryError = e.message ?: "获取记忆列表失败"
|
||||
Log.e("GroupChatInfoViewModel", "获取记忆列表失败: ${e.message}", e)
|
||||
} finally {
|
||||
isLoadingMemory = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除记忆
|
||||
*/
|
||||
fun deleteMemory(ruleId: Int) {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
isLoadingMemory = true
|
||||
memoryError = null
|
||||
|
||||
val response = ApiClient.api.deletePromptRule(ruleId)
|
||||
if (response.isSuccessful) {
|
||||
// 刷新记忆列表和配额
|
||||
promptOpenId?.let { openId ->
|
||||
loadMemoryQuota(openId)
|
||||
loadMemoryList(openId)
|
||||
}
|
||||
} else {
|
||||
val errorResponse = parseErrorResponse(response.errorBody())
|
||||
val errorMessage = errorResponse?.toServiceException()?.message
|
||||
?: "删除记忆失败: ${response.code()}"
|
||||
throw Exception(errorMessage)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
memoryError = e.message ?: "删除记忆失败"
|
||||
Log.e("GroupChatInfoViewModel", "删除记忆失败: ${e.message}", e)
|
||||
} finally {
|
||||
isLoadingMemory = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新记忆
|
||||
*/
|
||||
fun updateMemory(ruleId: Int, newRuleText: String, targetOpenId: String? = null) {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
isLoadingMemory = true
|
||||
memoryError = null
|
||||
|
||||
val openId = targetOpenId ?: promptOpenId
|
||||
?: throw Exception("无法获取智能体ID")
|
||||
|
||||
val requestBody = com.aiosman.ravenow.data.api.UpdatePromptRuleRequestBody(
|
||||
id = ruleId,
|
||||
rule = newRuleText,
|
||||
openId = openId
|
||||
)
|
||||
|
||||
val response = ApiClient.api.updatePromptRule(requestBody)
|
||||
if (response.isSuccessful) {
|
||||
// 刷新记忆列表和配额
|
||||
loadMemoryQuota(openId)
|
||||
loadMemoryList(openId)
|
||||
} else {
|
||||
val errorResponse = parseErrorResponse(response.errorBody())
|
||||
val errorMessage = errorResponse?.toServiceException()?.message
|
||||
?: "更新记忆失败: ${response.code()}"
|
||||
throw Exception(errorMessage)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
memoryError = e.message ?: "更新记忆失败"
|
||||
Log.e("GroupChatInfoViewModel", "更新记忆失败: ${e.message}", e)
|
||||
} finally {
|
||||
isLoadingMemory = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,625 @@
|
||||
package com.aiosman.ravenow.ui.group
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
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.modifiers.noRippleClickable
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import android.widget.Toast
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
|
||||
@Composable
|
||||
fun GroupMemoryManageContent(
|
||||
groupId: String,
|
||||
viewModel: GroupChatInfoViewModel,
|
||||
onAddMemoryClick: () -> Unit = {},
|
||||
onDismiss: () -> Unit = {}
|
||||
) {
|
||||
val AppColors = LocalAppTheme.current
|
||||
val configuration = LocalConfiguration.current
|
||||
val screenHeight = configuration.screenHeightDp.dp
|
||||
val sheetHeight = screenHeight * 0.95f
|
||||
val context = LocalContext.current
|
||||
|
||||
// 编辑记忆的状态 - 存储正在编辑的记忆ID
|
||||
var editingMemoryId by remember { mutableStateOf<Int?>(null) }
|
||||
|
||||
// 加载配额和列表数据
|
||||
LaunchedEffect(Unit) {
|
||||
viewModel.loadMemoryQuota()
|
||||
viewModel.loadMemoryList()
|
||||
}
|
||||
|
||||
val quota = viewModel.memoryQuota
|
||||
val memoryList = viewModel.memoryList
|
||||
val isLoading = viewModel.isLoadingMemory
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(sheetHeight)
|
||||
.background(Color(0xFFFAF9FB))
|
||||
) {
|
||||
// 顶部栏:返回按钮 + 标题 + 加号按钮
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(44.dp)
|
||||
.padding(horizontal = 16.dp)
|
||||
) {
|
||||
// 中间标题 - 绝对居中,不受其他组件影响
|
||||
Text(
|
||||
text = "记忆管理",
|
||||
style = TextStyle(
|
||||
color = Color.Black,
|
||||
fontSize = 17.sp,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
),
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
|
||||
// 左侧返回按钮
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterStart)
|
||||
.clip(RoundedCornerShape(296.dp))
|
||||
.background(Color.White) // 浅灰色背景
|
||||
.noRippleClickable { onDismiss() }
|
||||
.padding(horizontal = 12.dp, vertical = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(R.drawable.rider_pro_back_icon),
|
||||
contentDescription = "返回",
|
||||
modifier = Modifier.size(16.dp),
|
||||
colorFilter = ColorFilter.tint(Color.Black)
|
||||
)
|
||||
Text(
|
||||
text = "返回",
|
||||
style = TextStyle(
|
||||
color = Color.Black,
|
||||
fontSize = 15.sp,
|
||||
fontWeight = FontWeight.Normal
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// 右侧圆形加号按钮
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterEnd)
|
||||
.size(32.dp)
|
||||
.clip(CircleShape)
|
||||
.background(Color.White)
|
||||
.noRippleClickable { onAddMemoryClick() },
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = "+",
|
||||
style = TextStyle(
|
||||
color = Color.Black,
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 浅黄色提示栏 - 显示真实的配额数据
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(Color(0xFFFBF8EF))
|
||||
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text("已付费:", style = TextStyle(color = Color(0x993C3C43), fontSize = 13.sp))
|
||||
Spacer(Modifier.width(3.dp))
|
||||
Text(
|
||||
"${quota?.purchasedCount ?: 0}",
|
||||
style = TextStyle(color = Color(0xFFFF8D28), fontSize = 13.sp)
|
||||
)
|
||||
}
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text("已使用:", style = TextStyle(color = Color(0x993C3C43), fontSize = 13.sp))
|
||||
Spacer(Modifier.width(3.dp))
|
||||
Text(
|
||||
"${quota?.currentCount ?: 0}",
|
||||
style = TextStyle(color = Color(0xFFFF8D28), fontSize = 13.sp)
|
||||
)
|
||||
}
|
||||
}
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text("可用上限:", style = TextStyle(color = Color(0x993C3C43), fontSize = 13.sp))
|
||||
Spacer(Modifier.width(3.dp))
|
||||
Text(
|
||||
"50",
|
||||
style = TextStyle(color = Color(0xFFFF8D28), fontSize = 13.sp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 记忆列表或空状态
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f)
|
||||
) {
|
||||
if (isLoading) {
|
||||
// 加载中状态
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(40.dp),
|
||||
color = Color(0xFFFF8D28)
|
||||
)
|
||||
}
|
||||
} else if (memoryList.isNotEmpty()) {
|
||||
// 显示记忆列表
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 12.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
items(memoryList) { memory ->
|
||||
MemoryItem(
|
||||
memory = memory,
|
||||
isEditing = editingMemoryId == memory.id,
|
||||
onEdit = {
|
||||
editingMemoryId = memory.id
|
||||
},
|
||||
onCancel = {
|
||||
editingMemoryId = null
|
||||
},
|
||||
onSave = { newText ->
|
||||
viewModel.updateMemory(memory.id, newText)
|
||||
editingMemoryId = null
|
||||
},
|
||||
onDelete = {
|
||||
viewModel.deleteMemory(memory.id)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(vertical = 60.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.group),
|
||||
contentDescription = "暂无记忆",
|
||||
modifier = Modifier
|
||||
.height(150.dp).width(180.dp)
|
||||
)
|
||||
|
||||
Spacer(Modifier.height(10.dp))
|
||||
Text(
|
||||
text = "暂无记忆",
|
||||
style = TextStyle(color = Color.Black, fontSize = 16.sp, fontWeight = FontWeight.SemiBold)
|
||||
)
|
||||
Spacer(Modifier.height(6.dp))
|
||||
Text(
|
||||
text = "点击上方按钮添加群记忆",
|
||||
style = TextStyle(color = Color.Black, fontSize = 14.sp, fontWeight = FontWeight.Normal)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑记忆对话框
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun EditGroupMemoryDialog(
|
||||
memory: com.aiosman.ravenow.data.api.PromptRule,
|
||||
viewModel: GroupChatInfoViewModel,
|
||||
onDismiss: () -> Unit,
|
||||
onUpdateMemory: (String) -> Unit
|
||||
) {
|
||||
val AppColors = LocalAppTheme.current
|
||||
val context = LocalContext.current
|
||||
var memoryText by remember { mutableStateOf(memory.rule) }
|
||||
val maxLength = 500
|
||||
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
||||
|
||||
val gradientColors = listOf(
|
||||
Color(0xFF7C45ED),
|
||||
Color(0xFF7C57EE),
|
||||
Color(0xFF7BD8F8)
|
||||
)
|
||||
val gradientBrush = Brush.horizontalGradient(colors = gradientColors)
|
||||
|
||||
ModalBottomSheet(
|
||||
onDismissRequest = onDismiss,
|
||||
sheetState = sheetState,
|
||||
containerColor = Color(0xFFFAF9FB),
|
||||
dragHandle = {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(36.dp)
|
||||
.height(5.dp)
|
||||
.padding(top = 5.dp)
|
||||
.background(
|
||||
Color(0xFFCCCCCC),
|
||||
RoundedCornerShape(100.dp)
|
||||
)
|
||||
)
|
||||
},
|
||||
shape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 12.dp, vertical = 16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(88.dp)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.background(brush = gradientBrush)
|
||||
)
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(1.dp)
|
||||
.clip(RoundedCornerShape(15.dp))
|
||||
.background(Color.White)
|
||||
.padding(12.dp),
|
||||
contentAlignment = Alignment.TopStart
|
||||
) {
|
||||
BasicTextField(
|
||||
value = memoryText,
|
||||
onValueChange = { newText ->
|
||||
if (newText.length <= maxLength) {
|
||||
memoryText = newText
|
||||
}
|
||||
},
|
||||
cursorBrush = SolidColor(Color.Black),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
textStyle = TextStyle(
|
||||
fontSize = 13.sp,
|
||||
color = Color.Black,
|
||||
lineHeight = 18.sp
|
||||
),
|
||||
decorationBox = { innerTextField ->
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
contentAlignment = Alignment.TopStart
|
||||
) {
|
||||
innerTextField()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
// 取消按钮
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.height(34.dp)
|
||||
.clip(RoundedCornerShape(653.8.dp))
|
||||
.background(Color(0x147C7480))
|
||||
.noRippleClickable { onDismiss() },
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = "取消",
|
||||
style = TextStyle(
|
||||
fontSize = 15.sp,
|
||||
color = Color.Black
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// 保存按钮
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.height(34.dp)
|
||||
.clip(RoundedCornerShape(653.8.dp))
|
||||
.background(Color(0xFF110C13))
|
||||
.noRippleClickable {
|
||||
if (memoryText.isNotBlank() && memoryText != memory.rule) {
|
||||
onUpdateMemory(memoryText)
|
||||
Toast.makeText(context, "记忆更新成功", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
},
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = "保存",
|
||||
style = TextStyle(
|
||||
fontSize = 15.sp,
|
||||
color = Color.White
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记忆项组件
|
||||
*/
|
||||
@Composable
|
||||
fun MemoryItem(
|
||||
memory: com.aiosman.ravenow.data.api.PromptRule,
|
||||
isEditing: Boolean = false,
|
||||
onEdit: () -> Unit = {},
|
||||
onCancel: () -> Unit = {},
|
||||
onSave: (String) -> Unit = {},
|
||||
onDelete: () -> Unit
|
||||
) {
|
||||
val AppColors = LocalAppTheme.current
|
||||
val context = LocalContext.current
|
||||
var memoryText by remember { mutableStateOf(memory.rule) }
|
||||
val maxLength = 500
|
||||
|
||||
// 渐变边框颜色
|
||||
val gradientColors = listOf(
|
||||
Color(0xFF7C45ED),
|
||||
Color(0xFF7C57EE),
|
||||
Color(0xFF7BD8F8)
|
||||
)
|
||||
val gradientBrush = Brush.horizontalGradient(colors = gradientColors)
|
||||
|
||||
// 当进入编辑模式时,重置文本
|
||||
LaunchedEffect(isEditing) {
|
||||
if (isEditing) {
|
||||
memoryText = memory.rule
|
||||
}
|
||||
}
|
||||
|
||||
if (isEditing) {
|
||||
// 编辑模式:显示编辑界面
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.background(Color.White)
|
||||
.padding(horizontal = 12.dp, vertical = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
// 文本输入框 - 带渐变边框
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(88.dp)
|
||||
) {
|
||||
// 渐变边框层
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.background(brush = gradientBrush)
|
||||
)
|
||||
|
||||
// 内容层 - 白色背景,通过padding形成边框效果
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(1.dp)
|
||||
.clip(RoundedCornerShape(15.dp))
|
||||
.background(Color.White)
|
||||
.padding(12.dp),
|
||||
contentAlignment = Alignment.TopStart
|
||||
) {
|
||||
BasicTextField(
|
||||
value = memoryText,
|
||||
onValueChange = { newText ->
|
||||
if (newText.length <= maxLength) {
|
||||
memoryText = newText
|
||||
}
|
||||
},
|
||||
cursorBrush = SolidColor(Color.Black),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
textStyle = TextStyle(
|
||||
fontSize = 13.sp,
|
||||
color = Color.Black,
|
||||
lineHeight = 18.sp
|
||||
),
|
||||
decorationBox = { innerTextField ->
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
contentAlignment = Alignment.TopStart
|
||||
) {
|
||||
innerTextField()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 按钮行:取消和保存
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
// 取消按钮
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.height(34.dp)
|
||||
.clip(RoundedCornerShape(653.8.dp))
|
||||
.background(Color(0x147C7480))
|
||||
.noRippleClickable { onCancel() },
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = "取消",
|
||||
style = TextStyle(
|
||||
fontSize = 15.sp,
|
||||
color = Color.Black
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// 保存按钮
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.height(34.dp)
|
||||
.clip(RoundedCornerShape(653.8.dp))
|
||||
.background(Color(0xFF110C13))
|
||||
.noRippleClickable {
|
||||
if (memoryText.isNotBlank() && memoryText != memory.rule) {
|
||||
onSave(memoryText)
|
||||
Toast.makeText(context, "记忆更新成功", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
},
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = "保存",
|
||||
style = TextStyle(
|
||||
fontSize = 15.sp,
|
||||
color = Color.White
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 显示模式:显示记忆内容
|
||||
// 格式化日期:从 "2025-10-20T10:30:00Z" 格式转换为 "2025年10月20日"
|
||||
val formattedDate = try {
|
||||
if (memory.createdAt.length >= 10) {
|
||||
val dateStr = memory.createdAt.substring(0, 10)
|
||||
val parts = dateStr.split("-")
|
||||
if (parts.size == 3) {
|
||||
"${parts[0]}年${parts[1].toInt()}月${parts[2].toInt()}日"
|
||||
} else {
|
||||
dateStr
|
||||
}
|
||||
} else {
|
||||
memory.createdAt
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
if (memory.createdAt.length >= 10) memory.createdAt.substring(0, 10) else memory.createdAt
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.background(Color.White)
|
||||
.padding(horizontal = 12.dp, vertical = 16.dp)
|
||||
) {
|
||||
// 主文本 - 顶部
|
||||
Text(
|
||||
text = memory.rule,
|
||||
style = TextStyle(
|
||||
color = Color.Black,
|
||||
fontSize = 13.sp,
|
||||
lineHeight = 18.sp
|
||||
),
|
||||
maxLines = 3,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
// 底部行:日期 + 编辑删除按钮
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.Bottom
|
||||
) {
|
||||
// 日期文本 - 左侧
|
||||
Text(
|
||||
text = formattedDate,
|
||||
style = TextStyle(
|
||||
color = Color(0x993C3C43),
|
||||
fontSize = 11.sp
|
||||
)
|
||||
)
|
||||
|
||||
// 编辑和删除图标 - 右侧
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
// 编辑图标
|
||||
Image(
|
||||
painter = painterResource(R.mipmap.icons_infor_edit),
|
||||
contentDescription = "编辑",
|
||||
modifier = Modifier
|
||||
.size(20.dp)
|
||||
.noRippleClickable { onEdit() },
|
||||
colorFilter = ColorFilter.tint(Color.Black)
|
||||
)
|
||||
|
||||
Image(
|
||||
painter = painterResource(R.mipmap.iconsdelete),
|
||||
contentDescription = "删除",
|
||||
modifier = Modifier
|
||||
.size(20.dp)
|
||||
.noRippleClickable { onDelete() },
|
||||
colorFilter = ColorFilter.tint(Color(0xFFEE2A33))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,13 +97,13 @@ fun AgentChatListScreen() {
|
||||
Spacer(modifier = Modifier.height(39.dp))
|
||||
Image(
|
||||
painter = painterResource(
|
||||
id = if(AppState.darkMode) R.mipmap.qs_znt_qs_as_img
|
||||
id = if(AppState.darkMode) R.mipmap.juhao_dark
|
||||
else R.mipmap.invalid_name_5),
|
||||
contentDescription = "null data",
|
||||
modifier = Modifier
|
||||
.size(181.dp)
|
||||
.size(width = 181.dp, height = 153.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Spacer(modifier = Modifier.height(if (AppState.darkMode) 9.dp else 24.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.agent_chat_empty_title),
|
||||
color = AppColors.text,
|
||||
|
||||
@@ -175,13 +175,13 @@ fun AllChatListScreen() {
|
||||
Spacer(modifier = Modifier.height(39.dp))
|
||||
Image(
|
||||
painter = painterResource(
|
||||
id = if(AppState.darkMode) R.mipmap.qs_py_qs_as_img
|
||||
id = if(AppState.darkMode) R.mipmap.piao_dark
|
||||
else R.mipmap.invalid_name_2),
|
||||
contentDescription = "null data",
|
||||
modifier = Modifier
|
||||
.size(181.dp)
|
||||
.size(width = 181.dp, height = 153.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Spacer(modifier = Modifier.height(if (AppState.darkMode) 9.dp else 24.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.friend_chat_empty_title),
|
||||
color = AppColors.text,
|
||||
|
||||
@@ -85,13 +85,13 @@ fun FriendChatListScreen() {
|
||||
Spacer(modifier = Modifier.height(39.dp))
|
||||
Image(
|
||||
painter = painterResource(
|
||||
id = if(AppState.darkMode) R.mipmap.qs_py_qs_as_img
|
||||
id = if(AppState.darkMode) R.mipmap.piao_dark
|
||||
else R.mipmap.invalid_name_2),
|
||||
contentDescription = "null data",
|
||||
modifier = Modifier
|
||||
.size(181.dp)
|
||||
.size(width = 181.dp, height = 153.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Spacer(modifier = Modifier.height(if (AppState.darkMode) 9.dp else 24.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.friend_chat_empty_title),
|
||||
color = AppColors.text,
|
||||
|
||||
@@ -77,13 +77,13 @@ fun GroupChatListScreen() {
|
||||
Spacer(modifier = Modifier.height(39.dp))
|
||||
Image(
|
||||
painter = painterResource(
|
||||
id = if(AppState.darkMode) R.mipmap.qs_ql_qs_as_img
|
||||
id = if(AppState.darkMode) R.mipmap.fei_dark
|
||||
else R.mipmap.invalid_name_12),
|
||||
contentDescription = "null data",
|
||||
modifier = Modifier
|
||||
.size(181.dp)
|
||||
.size(width = 181.dp, height = 153.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Spacer(modifier = Modifier.height(if (AppState.darkMode) 9.dp else 24.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.group_chat_empty),
|
||||
color = AppColors.text,
|
||||
|
||||
@@ -118,7 +118,7 @@ fun ProfileV3(
|
||||
onLike: (MomentEntity) -> Unit = {},
|
||||
onComment: (MomentEntity) -> Unit = {},
|
||||
onAgentClick: (AgentEntity) -> Unit = {},
|
||||
postCount: Int? = null, // 新增参数用于传递帖子总数
|
||||
postCount: Long? = null, // 新增参数用于传递帖子总数
|
||||
) {
|
||||
val model = MyProfileViewModel
|
||||
val pagerState = rememberPagerState(pageCount = { if (isAiAccount) 1 else 2 })
|
||||
@@ -357,7 +357,7 @@ fun ProfileV3(
|
||||
profile?.let {
|
||||
UserItem(
|
||||
accountProfileEntity = it,
|
||||
postCount = postCount ?: if (isSelf) MyProfileViewModel.momentLoader.total else moments.size
|
||||
postCount = postCount ?: if (isSelf) MyProfileViewModel.momentLoader.total else moments.size.toLong()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,16 +189,17 @@ fun GalleryGrid(
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(
|
||||
id = if(AppState.darkMode) R.mipmap.qs_dt_qs_as_img
|
||||
id = if(AppState.darkMode) R.mipmap.shuihu_dark
|
||||
else R.mipmap.invalid_name_7),
|
||||
contentDescription = "暂无图片",
|
||||
modifier = Modifier.size(181.dp),
|
||||
modifier = Modifier
|
||||
.size(width = 181.dp, height = 153.dp),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Spacer(modifier = Modifier.height(if(AppState.darkMode) 9.dp else 24.dp))
|
||||
|
||||
Text(
|
||||
text = "故事还没开始",
|
||||
text = "你的故事还没开始",
|
||||
fontSize = 16.sp,
|
||||
color = AppColors.text,
|
||||
fontWeight = FontWeight.W600
|
||||
|
||||
@@ -220,7 +220,8 @@ fun EmptyAgentsView() {
|
||||
.align(Alignment.CenterHorizontally),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
// 根据是否为深色模式调整间距
|
||||
Spacer(modifier = Modifier.height(if(AppState.darkMode) 9.dp else 24.dp))
|
||||
|
||||
Text(
|
||||
text = "专属AI等你召唤",
|
||||
|
||||
@@ -33,7 +33,7 @@ import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
||||
@Composable
|
||||
fun UserItem(
|
||||
accountProfileEntity: AccountProfileEntity,
|
||||
postCount: Int = 0
|
||||
postCount: Long = 0
|
||||
) {
|
||||
val navController = LocalNavController.current
|
||||
val AppColors = LocalAppTheme.current
|
||||
|
||||
@@ -123,14 +123,15 @@ fun LikeNoticeScreen() {
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(
|
||||
id =if(AppState.darkMode) R.mipmap.qst_z_qs_as_img
|
||||
id = if(AppState.darkMode) R.mipmap.sanqiu_dark
|
||||
else R.mipmap.invalid_name_6),
|
||||
contentDescription = "No Notice",
|
||||
modifier = Modifier.size(181.dp)
|
||||
modifier = Modifier
|
||||
.size(width = 181.dp, height = 153.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Spacer(modifier = Modifier.height(if (AppState.darkMode) 9.dp else 24.dp))
|
||||
Text(
|
||||
text = "你的赞在路上~",
|
||||
text = "你的赞在赶来的路上",
|
||||
color = AppColors.text,
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.W600,
|
||||
|
||||
@@ -26,7 +26,7 @@ fun SplashScreen() {
|
||||
) {
|
||||
// 居中的图标
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.invalid_name),
|
||||
painter = painterResource(id = R.mipmap.kp_logo_img),
|
||||
contentDescription = "App Logo",
|
||||
modifier = Modifier
|
||||
.align(Alignment.Center)
|
||||
|
||||
BIN
app/src/main/res/mipmap-hdpi/ai_dark.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
app/src/main/res/mipmap-hdpi/fei_dark.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
app/src/main/res/mipmap-hdpi/frame_3.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
app/src/main/res/mipmap-hdpi/frame_4.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
app/src/main/res/mipmap-hdpi/icons_brain.png
Normal file
|
After Width: | Height: | Size: 551 B |
BIN
app/src/main/res/mipmap-hdpi/invalid_dark.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
app/src/main/res/mipmap-hdpi/juhao_dark.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
app/src/main/res/mipmap-hdpi/piao_dark.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
app/src/main/res/mipmap-hdpi/sanqiu_dark.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
app/src/main/res/mipmap-hdpi/shuihu_dark.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
app/src/main/res/mipmap-hdpi/tietie_dark.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
app/src/main/res/mipmap-mdpi/ai_dark.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
app/src/main/res/mipmap-mdpi/fei_dark.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
app/src/main/res/mipmap-mdpi/frame_3.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
app/src/main/res/mipmap-mdpi/frame_4.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
app/src/main/res/mipmap-mdpi/icons_brain.png
Normal file
|
After Width: | Height: | Size: 419 B |
BIN
app/src/main/res/mipmap-mdpi/invalid_dark.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
app/src/main/res/mipmap-mdpi/juhao_dark.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
app/src/main/res/mipmap-mdpi/piao_dark.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
app/src/main/res/mipmap-mdpi/sanqiu_dark.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
app/src/main/res/mipmap-mdpi/shuihu_dark.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
app/src/main/res/mipmap-mdpi/tietie_dark.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 2.9 KiB |
BIN
app/src/main/res/mipmap-xhdpi/fei_dark.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
app/src/main/res/mipmap-xhdpi/frame_3.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
app/src/main/res/mipmap-xhdpi/frame_4.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
app/src/main/res/mipmap-xhdpi/group.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
app/src/main/res/mipmap-xhdpi/icons_brain.png
Normal file
|
After Width: | Height: | Size: 718 B |
BIN
app/src/main/res/mipmap-xhdpi/iconsdelete.png
Normal file
|
After Width: | Height: | Size: 312 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 2.1 KiB |
BIN
app/src/main/res/mipmap-xhdpi/juhao_dark.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
app/src/main/res/mipmap-xhdpi/piao_dark.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
app/src/main/res/mipmap-xhdpi/sanqiu_dark.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
app/src/main/res/mipmap-xhdpi/shuihu_dark.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
app/src/main/res/mipmap-xhdpi/tietie_dark.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 4.3 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/fei_dark.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/frame_3.png
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/frame_4.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/group.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/icons_brain.png
Normal file
|
After Width: | Height: | Size: 980 B |
BIN
app/src/main/res/mipmap-xxhdpi/iconsdelete.png
Normal file
|
After Width: | Height: | Size: 438 B |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 3.0 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/juhao_dark.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/piao_dark.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/sanqiu_dark.png
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/shuihu_dark.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/tietie_dark.png
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 5.4 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/fei_dark.png
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/frame_3.png
Normal file
|
After Width: | Height: | Size: 9.5 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/frame_4.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/group.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/icons_brain.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/iconsdelete.png
Normal file
|
After Width: | Height: | Size: 575 B |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 3.7 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/juhao_dark.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/piao_dark.png
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/sanqiu_dark.png
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/shuihu_dark.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/tietie_dark.png
Normal file
|
After Width: | Height: | Size: 9.6 KiB |
@@ -248,5 +248,38 @@
|
||||
<string name="select_apply_to_use_theme">「適用」を選択してこのテーマを使用</string>
|
||||
<string name="tap_cancel_to_preview_other_themes">「キャンセル」をタップして他のテーマをプレビュー</string>
|
||||
|
||||
<!-- Group Chat Info -->
|
||||
<string name="group_chat_info_title">グループチャット情報</string>
|
||||
<string name="group_chat_info_add_member">メンバーを追加</string>
|
||||
<string name="group_chat_info_notification">通知</string>
|
||||
<string name="group_chat_info_share">共有</string>
|
||||
<string name="group_chat_info_unlock_extension">グループ拡張機能を解除</string>
|
||||
<string name="group_chat_info_group_memory">グループメモリ</string>
|
||||
<string name="group_chat_info_memory_description">AIは記憶に基づいてグループチャットであなたをより理解します</string>
|
||||
<string name="group_chat_info_add_memory">メモリを追加</string>
|
||||
<string name="group_chat_info_memory_manage">メモリ管理</string>
|
||||
<string name="group_chat_info_group_settings">グループ設定</string>
|
||||
<string name="group_chat_info_group_visibility">グループの可視性</string>
|
||||
<string name="group_chat_info_locked">ロック中</string>
|
||||
<string name="group_chat_info_member_manage">メンバー管理</string>
|
||||
<string name="group_chat_info_wallpaper">グループチャット壁紙</string>
|
||||
<string name="group_chat_info_dissolve">グループチャットを解散</string>
|
||||
<string name="group_chat_info_add_group_memory">グループメモリを追加</string>
|
||||
<string name="group_chat_info_member_count">%d人のメンバー</string>
|
||||
<string name="group_chat_info_memory_input_hint">グループの記憶内容を入力、例えば、グループのメンバーは科学技術と設計を議論するのが好きである....</string>
|
||||
<string name="group_chat_info_memory_cost">メモリを追加すると20コインを消費します</string>
|
||||
<string name="group_chat_info_memory_optimization">AIは記憶に基づいて返信を最適化します</string>
|
||||
<string name="group_chat_info_memory_editable">いつでも編集または削除できます</string>
|
||||
<string name="group_chat_info_memory_add_success">グループメモリが正常に追加されました</string>
|
||||
<string name="group_chat_info_permission_settings">グループ権限設定</string>
|
||||
<string name="group_chat_info_public_group">公開グループ</string>
|
||||
<string name="group_chat_info_public_group_desc">誰でも検索して参加できます</string>
|
||||
<string name="group_chat_info_private_group">プライベートグループ</string>
|
||||
<string name="group_chat_info_private_group_desc">招待のみ</string>
|
||||
<string name="group_chat_info_private_group_cost">50コイン</string>
|
||||
<string name="group_chat_info_balance">残高: %1$dコイン</string>
|
||||
<string name="group_chat_info_unlock_cost">アンロック費用: %1$dコイン</string>
|
||||
<string name="group_chat_info_done">完了</string>
|
||||
<string name="group_chat_info_recharge_hint">チャージしてより多くのコインを獲得できます</string>
|
||||
</resources>
|
||||
|
||||
|
||||
@@ -152,8 +152,8 @@
|
||||
<string name="favourites_null">暂无数据</string>
|
||||
|
||||
<string name="agent_chat_list_title">智能体聊天</string>
|
||||
<string name="agent_chat_empty_title">AI 在等你的开场白</string>
|
||||
<string name="agent_chat_empty_subtitle">去首页探索一下,主动发起对话!</string>
|
||||
<string name="agent_chat_empty_title">AI们在等你开启第一句对话</string>
|
||||
<string name="agent_chat_empty_subtitle">去首页探索一下,主动发起一场对话!</string>
|
||||
<string name="agent_chat_me_prefix">我: </string>
|
||||
<string name="agent_chat_image">[图片]</string>
|
||||
<string name="agent_chat_voice">[语音]</string>
|
||||
@@ -163,12 +163,12 @@
|
||||
<string name="agent_chat_load_failed">加载失败</string>
|
||||
<string name="agent_chat_load_more_failed">加载更多失败</string>
|
||||
<string name="agent_chat_user_info_failed">获取用户信息失败: %s</string>
|
||||
<string name="group_chat_empty">没有群聊,宇宙好安静</string>
|
||||
<string name="group_chat_empty">没有群聊消息的宇宙太安静了</string>
|
||||
<string name="group_chat_empty_title">没有群聊消息的宇宙太安静了</string>
|
||||
<string name="group_chat_empty_subtitle">在首页探索感兴趣的主题房间</string>
|
||||
<string name="group_chat_empty_join">去首页探索感兴趣的高能对话</string>
|
||||
<string name="friend_chat_empty_title">和朋友,还没有对话哦~</string>
|
||||
<string name="friend_chat_empty_subtitle">点击好友头像,即刻发起聊天</string>
|
||||
<string name="group_chat_empty_join">去首页探索感兴趣的主题房间</string>
|
||||
<string name="friend_chat_empty_title">你和朋友,还没说第一句话呢</string>
|
||||
<string name="friend_chat_empty_subtitle">一段崭新的友谊 等待被唤醒</string>
|
||||
<string name="friend_chat_me_prefix">我: </string>
|
||||
<string name="friend_chat_load_failed">加载失败</string>
|
||||
<string name="create_group_chat">创建群聊</string>
|
||||
@@ -250,4 +250,45 @@
|
||||
<string name="each_theme_unique_experience">每个主题都有自己独特的体验</string>
|
||||
<string name="select_apply_to_use_theme">选择"应用"可选中这个主题</string>
|
||||
<string name="tap_cancel_to_preview_other_themes">轻触"取消"可预览其他主题</string>
|
||||
|
||||
<!-- Group Chat Info -->
|
||||
<string name="group_chat_info_title">群聊信息</string>
|
||||
<string name="group_chat_info_add_member">添加成员</string>
|
||||
<string name="group_chat_info_notification">通知</string>
|
||||
<string name="group_chat_info_share">分享</string>
|
||||
<string name="group_chat_info_unlock_extension">解锁群扩展</string>
|
||||
<string name="group_chat_info_group_memory">群记忆</string>
|
||||
<string name="group_chat_info_memory_description">AI 会根据记忆在群聊里更懂你</string>
|
||||
<string name="group_chat_info_add_memory">添加记忆</string>
|
||||
<string name="group_chat_info_memory_manage">记忆管理</string>
|
||||
<string name="group_chat_info_group_settings">群资料设置</string>
|
||||
<string name="group_chat_info_group_visibility">群可见性</string>
|
||||
<string name="group_chat_info_locked">待解锁</string>
|
||||
<string name="group_chat_info_member_manage">成员管理</string>
|
||||
<string name="group_chat_info_wallpaper">群聊壁纸</string>
|
||||
<string name="group_chat_info_dissolve">解散群聊</string>
|
||||
<string name="group_chat_info_add_group_memory">添加群记忆</string>
|
||||
<string name="group_chat_info_member_count">%d 位成员</string>
|
||||
<string name="group_chat_info_memory_input_hint">输入群记忆内容,例如:群成员喜欢讨论科技和设计....</string>
|
||||
<string name="group_chat_info_memory_cost">添加记忆需消耗 20 派币</string>
|
||||
<string name="group_chat_info_memory_optimization">AI 将基于记忆优化回复</string>
|
||||
<string name="group_chat_info_memory_editable">可随时编辑或删除</string>
|
||||
<string name="group_chat_info_memory_add_success">群记忆添加成功</string>
|
||||
<string name="group_chat_info_permission_settings">群权限设置</string>
|
||||
<string name="group_chat_info_public_group">公开群组</string>
|
||||
<string name="group_chat_info_public_group_desc">任何人都可搜索并加入</string>
|
||||
<string name="group_chat_info_private_group">私密群组</string>
|
||||
<string name="group_chat_info_private_group_desc">仅限邀请加入</string>
|
||||
<string name="group_chat_info_private_group_cost">50 派币</string>
|
||||
<string name="group_chat_info_balance">余额: %1$d 派币</string>
|
||||
<string name="group_chat_info_unlock_cost">解锁费用: %1$d 派币</string>
|
||||
<string name="group_chat_info_done">完成</string>
|
||||
<string name="group_chat_info_recharge_hint">可通过充值获得更多派币</string>
|
||||
|
||||
<!-- Edit Profile Extras -->
|
||||
<string name="mbti_type">MBTI 类型</string>
|
||||
<string name="zodiac">星座</string>
|
||||
<string name="save">保存</string>
|
||||
<string name="choose_mbti">选择 MBTI</string>
|
||||
<string name="choose_zodiac">选择星座</string>
|
||||
</resources>
|
||||
@@ -246,5 +246,43 @@
|
||||
<string name="select_apply_to_use_theme">Select "Apply" to use this theme</string>
|
||||
<string name="tap_cancel_to_preview_other_themes">Tap "Cancel" to preview other themes</string>
|
||||
|
||||
|
||||
<!-- Group Chat Info -->
|
||||
<string name="group_chat_info_title">Group Chat Info</string>
|
||||
<string name="group_chat_info_add_member">Add Member</string>
|
||||
<string name="group_chat_info_notification">Notification</string>
|
||||
<string name="group_chat_info_share">Share</string>
|
||||
<string name="group_chat_info_unlock_extension">Unlock Group Extension</string>
|
||||
<string name="group_chat_info_group_memory">Group Memory</string>
|
||||
<string name="group_chat_info_memory_description">AI will understand you better in group chat based on memory</string>
|
||||
<string name="group_chat_info_add_memory">Add Memory</string>
|
||||
<string name="group_chat_info_memory_manage">Memory Management</string>
|
||||
<string name="group_chat_info_group_settings">Group Settings</string>
|
||||
<string name="group_chat_info_group_visibility">Group Visibility</string>
|
||||
<string name="group_chat_info_locked">Locked</string>
|
||||
<string name="group_chat_info_member_manage">Member Management</string>
|
||||
<string name="group_chat_info_wallpaper">Group Chat Wallpaper</string>
|
||||
<string name="group_chat_info_dissolve">Dissolve Group Chat</string>
|
||||
<string name="group_chat_info_add_group_memory">Add Group Memory</string>
|
||||
<string name="group_chat_info_member_count">%d members</string>
|
||||
<string name="group_chat_info_memory_input_hint">Input group memory content, for example: Group members enjoy discussing technology and design …</string>
|
||||
<string name="group_chat_info_memory_cost">Adding memory consumes 20 coins</string>
|
||||
<string name="group_chat_info_memory_optimization">AI will optimize replies based on memory</string>
|
||||
<string name="group_chat_info_memory_editable">Can be edited or deleted at any time</string>
|
||||
<string name="group_chat_info_memory_add_success">Group memory added successfully</string>
|
||||
<string name="group_chat_info_permission_settings">Group Permission Settings</string>
|
||||
<string name="group_chat_info_public_group">Public Group</string>
|
||||
<string name="group_chat_info_public_group_desc">Anyone can search and join</string>
|
||||
<string name="group_chat_info_private_group">Private Group</string>
|
||||
<string name="group_chat_info_private_group_desc">Invitation only</string>
|
||||
<string name="group_chat_info_private_group_cost">50 coins</string>
|
||||
<string name="group_chat_info_balance">Balance: %1$d coins</string>
|
||||
<string name="group_chat_info_unlock_cost">Unlock cost: %1$d coins</string>
|
||||
<string name="group_chat_info_done">Done</string>
|
||||
<string name="group_chat_info_recharge_hint">You can recharge to get more coins</string>
|
||||
<!-- Edit Profile Extras -->
|
||||
<string name="mbti_type">MBTI</string>
|
||||
<string name="zodiac">Zodiac</string>
|
||||
<string name="save">Save</string>
|
||||
<string name="choose_mbti">Choose MBTI</string>
|
||||
<string name="choose_zodiac">Choose Zodiac</string>
|
||||
</resources>
|
||||