Merge origin/main into feat/pr-20251104-154907 (prefer main); resolve gradlew via theirs

This commit is contained in:
2025-11-04 15:58:50 +08:00
15 changed files with 511 additions and 24 deletions

3
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

1
.idea/.name generated Normal file
View File

@@ -0,0 +1 @@
RaveNow

6
.idea/compiler.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="21" />
</component>
</project>

18
.idea/deploymentTargetSelector.xml generated Normal file
View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetSelector">
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2025-09-17T06:25:35.585100400Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="Default" identifier="serial=192.168.0.216:5555;connection=698a7727" />
</handle>
</Target>
</DropdownSelection>
<DialogSelection />
</SelectionState>
</selectionStates>
</component>
</project>

20
.idea/gradle.xml generated Normal file
View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="CHOOSE_PER_TEST" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
<option name="resolveExternalAnnotations" value="false" />
</GradleProjectSettings>
</option>
</component>
</project>

View File

@@ -0,0 +1,77 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="ComposePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewApiLevelMustBeValid" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewDeviceShouldUseNewSpec" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewFontScaleMustBeGreaterThanZero" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewParameterProviderOnFirstParameter" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
</profile>
</component>

6
.idea/kotlinc.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="1.9.10" />
</component>
</project>

10
.idea/migrations.xml generated Normal file
View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectMigrations">
<option name="MigrateToGradleLocalJavaHome">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
</component>
</project>

9
.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,9 @@
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

17
.idea/runConfigurations.xml generated Normal file
View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
</set>
</option>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@@ -489,6 +489,134 @@ data class CategoryListResponse(
val list: List<CategoryTemplate> val list: List<CategoryTemplate>
) )
// ========== Prompt Rule 相关数据类 ==========
/**
* 创建规则请求体
* @param rule 规则内容,不能为空
* @param promptId 智能体ID与 openId 二选一promptId 优先
* @param openId 智能体的 OpenIDUUID格式与 promptId 二选一
*/
data class CreatePromptRuleRequestBody(
@SerializedName("rule")
val rule: String,
@SerializedName("promptId")
val promptId: Int? = null,
@SerializedName("openId")
val openId: String? = null
)
/**
* 修改规则请求体
* @param id 规则ID必填
* @param rule 新的规则内容,不能为空
* @param promptId 要更改关联的智能体ID可选
* @param openId 要更改关联的智能体 OpenID可选
*/
data class UpdatePromptRuleRequestBody(
@SerializedName("id")
val id: Int,
@SerializedName("rule")
val rule: String,
@SerializedName("promptId")
val promptId: Int? = null,
@SerializedName("openId")
val openId: String? = null
)
/**
* 规则关联的智能体信息
* @param id 智能体ID
* @param title 智能体标题
* @param avatar 智能体头像URL
*/
data class PromptRuleAgent(
@SerializedName("id")
val id: Int,
@SerializedName("title")
val title: String,
@SerializedName("avatar")
val avatar: String
)
/**
* 规则详情
* @param id 规则ID
* @param rule 规则内容
* @param creator 创建者名称
* @param creatorType 创建者类型(如 "user"
* @param scope 作用域(如 "personal"
* @param prompt 关联的智能体信息
* @param createdAt 创建时间ISO 8601 格式)
* @param updatedAt 更新时间ISO 8601 格式)
*/
data class PromptRule(
@SerializedName("id")
val id: Int,
@SerializedName("rule")
val rule: String,
@SerializedName("creator")
val creator: String,
@SerializedName("creator_type")
val creatorType: String,
@SerializedName("scope")
val scope: String,
@SerializedName("prompt")
val prompt: PromptRuleAgent,
@SerializedName("created_at")
val createdAt: String,
@SerializedName("updated_at")
val updatedAt: String
)
/**
* 规则列表响应
* @param page 当前页码
* @param pageSize 每页数量
* @param total 总记录数
* @param list 规则列表
*/
data class PromptRuleListResponse(
@SerializedName("page")
val page: Int,
@SerializedName("pageSize")
val pageSize: Int,
@SerializedName("total")
val total: Int,
@SerializedName("list")
val list: List<PromptRule>
)
/**
* 规则配额信息
* @param promptId 智能体ID
* @param promptTitle 智能体标题
* @param baseMaxCount 基础条数限制(免费配额)
* @param purchasedCount 用户购买的额外条数
* @param totalMaxCount 总可用条数(基础+购买)
* @param currentCount 当前已创建的规则条数
* @param remainingCount 剩余可用条数
* @param usagePercent 使用百分比0-100
*/
data class PromptRuleQuota(
@SerializedName("promptId")
val promptId: Int,
@SerializedName("promptTitle")
val promptTitle: String,
@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
)
interface RaveNowAPI { interface RaveNowAPI {
@GET("membership/config") @GET("membership/config")
@retrofit2.http.Headers("X-Requires-Auth: true") @retrofit2.http.Headers("X-Requires-Auth: true")
@@ -864,5 +992,206 @@ interface RaveNowAPI {
@Query("pageSize") pageSize: Int? = null @Query("pageSize") pageSize: Int? = null
): Response<ListContainer<Agent>> ): Response<ListContainer<Agent>>
// ========== Prompt Rule API ==========
/**
* 创建智能体规则
*
* 功能说明:
* - 为指定的智能体创建一条新规则
* - 规则必须关联到存在的智能体(通过 promptId 或 openId 指定)
* - 创建前会自动检查配额限制,如果超出会尝试自动扩容
* - 只有创建者可以修改和删除该规则
*
* @param body 创建规则请求体
* - rule: 规则内容,不能为空
* - promptId: 智能体ID与 openId 二选一promptId 优先)
* - openId: 智能体的 OpenIDUUID格式与 promptId 二选一)
*
* @return 成功时返回空 Unit失败时返回错误信息
*
* 示例:
* ```kotlin
* val request = CreatePromptRuleRequestBody(
* rule = "禁止讨论政治话题",
* promptId = 123
* )
* val response = api.createPromptRule(request)
* ```
*/
@POST("outside/prompt/rule")
suspend fun createPromptRule(
@Body body: CreatePromptRuleRequestBody
): Response<Unit>
/**
* 修改智能体规则
*
* 功能说明:
* - 修改已存在的规则内容或关联的智能体
* - 只有规则的创建者可以修改
* - 可以同时修改规则内容和关联的智能体
* - 修改关联智能体时会重新验证配额限制
*
* @param body 修改规则请求体
* - id: 规则ID必填
* - rule: 新的规则内容,不能为空
* - promptId: 要更改关联的智能体ID可选
* - openId: 要更改关联的智能体 OpenID可选
*
* @return 成功时返回空 Unit失败时返回错误信息
*
* 权限要求:
* - 必须是规则的创建者creator_type 为 "user" 且 create_id 匹配)
*
* 示例:
* ```kotlin
* val request = UpdatePromptRuleRequestBody(
* id = 456,
* rule = "禁止讨论政治和敏感话题"
* )
* val response = api.updatePromptRule(request)
* ```
*/
@retrofit2.http.PUT("outside/prompt/rule")
suspend fun updatePromptRule(
@Body body: UpdatePromptRuleRequestBody
): Response<Unit>
/**
* 删除智能体规则
*
* 功能说明:
* - 删除指定的规则
* - 只有规则的创建者可以删除
* - 删除操作不可恢复,请谨慎操作
*
* @param id 规则ID
*
* @return 成功时返回空 Unit失败时返回错误信息
*
* 权限要求:
* - 必须是规则的创建者creator_type 为 "user" 且 create_id 匹配)
*
* 示例:
* ```kotlin
* val response = api.deletePromptRule(456)
* ```
*/
@DELETE("outside/prompt/rule/{id}")
suspend fun deletePromptRule(
@Path("id") id: Int
): Response<Unit>
/**
* 查询智能体规则列表
*
* 功能说明:
* - 查询指定智能体下当前用户创建的规则列表
* - 支持分页和关键词模糊搜索
* - 只返回当前用户创建的规则,不会返回其他用户的规则
*
* @param promptId 智能体ID支持数字ID或UUID格式的 openId
* @param rule 规则内容关键词(模糊搜索),可选
* @param page 页码,默认 1
* @param pageSize 每页数量,默认 10
*
* @return 返回分页的规则列表,包含规则详情和关联的智能体信息
*
* 响应数据说明:
* - page: 当前页码
* - pageSize: 每页数量
* - total: 总记录数
* - list: 规则列表
* - id: 规则ID
* - rule: 规则内容
* - creator: 创建者名称
* - creator_type: 创建者类型
* - scope: 作用域
* - prompt: 关联的智能体信息id, title, avatar
* - created_at: 创建时间
* - updated_at: 更新时间
*
* 示例:
* ```kotlin
* // 使用数字ID查询
* val response1 = api.getPromptRuleList("123", page = 1, pageSize = 10)
*
* // 使用 OpenID 查询
* val response2 = api.getPromptRuleList(
* "550e8400-e29b-41d4-a716-446655440000",
* page = 1,
* pageSize = 10
* )
*
* // 带关键词搜索
* val response3 = api.getPromptRuleList(
* "123",
* rule = "政治",
* page = 1,
* pageSize = 10
* )
* ```
*/
@GET("outside/prompt/{promptId}/rule/list")
suspend fun getPromptRuleList(
@Path("promptId") promptId: String,
@Query("rule") rule: String? = null,
@Query("page") page: Int = 1,
@Query("pageSize") pageSize: Int = 10
): Response<DataContainer<PromptRuleListResponse>>
/**
* 查询智能体规则配额信息
*
* 功能说明:
* - 查询指定智能体的规则条数使用情况
* - 包括基础配额、已购买配额、已使用数量等完整信息
* - 用于判断是否还能创建新规则
*
* @param promptId 智能体ID支持数字ID或UUID格式的 openId
*
* @return 返回配额详细信息
*
* 响应数据说明:
* - promptId: 智能体ID
* - promptTitle: 智能体标题
* - baseMaxCount: 基础条数限制(免费配额,由智能体等级决定)
* - purchasedCount: 用户购买的额外条数
* - totalMaxCount: 总可用条数baseMaxCount + purchasedCount
* - currentCount: 当前已创建的规则条数(只统计当前用户创建的)
* - remainingCount: 剩余可用条数totalMaxCount - currentCount
* - usagePercent: 使用百分比0-100currentCount / totalMaxCount * 100
*
* 使用场景:
* 1. 创建规则前检查是否有足够配额
* 2. 展示规则使用情况统计
* 3. 提示用户购买额外配额
*
* 示例:
* ```kotlin
* // 使用数字ID查询
* val response1 = api.getPromptRuleQuota("123")
*
* // 使用 OpenID 查询
* val response2 = api.getPromptRuleQuota("550e8400-e29b-41d4-a716-446655440000")
*
* // 处理响应
* response1.body()?.data?.let { quota ->
* if (quota.remainingCount > 0) {
* // 可以创建新规则
* println("还可以创建 ${quota.remainingCount} 条规则")
* } else {
* // 配额已用完
* println("规则配额已用完,已使用 ${quota.currentCount}/${quota.totalMaxCount}")
* }
* }
* ```
*/
@GET("outside/prompt/{promptId}/rule/count")
suspend fun getPromptRuleQuota(
@Path("promptId") promptId: String
): Response<DataContainer<PromptRuleQuota>>
} }

View File

@@ -233,18 +233,7 @@ fun IndexScreen() {
.background(AppColors.divider) .background(AppColors.divider)
) )
} }
Box(
modifier = Modifier
.fillMaxWidth()
.padding(top = 16.dp, bottom = 16.dp, start = 16.dp, end = 16.dp)
) {
Box(
modifier = Modifier
.fillMaxWidth()
.height(1.dp)
.background(AppColors.divider)
)
}
// NavItem( // NavItem(
// iconRes = R.drawable.rave_now_nav_switch, // iconRes = R.drawable.rave_now_nav_switch,
// label = "Switch Account" // label = "Switch Account"
@@ -291,8 +280,6 @@ fun IndexScreen() {
) { ) {
item.forEachIndexed { idx, it -> item.forEachIndexed { idx, it ->
val isSelected = model.tabIndex == idx val isSelected = model.tabIndex == idx
val selectedColor = Color(0xFF7C45ED)
val iconTint by animateColorAsState( val iconTint by animateColorAsState(
targetValue = if (isSelected) AppColors.brandColorsColor else AppColors.text, targetValue = if (isSelected) AppColors.brandColorsColor else AppColors.text,
animationSpec = tween(durationMillis = 250), label = "" animationSpec = tween(durationMillis = 250), label = ""
@@ -356,7 +343,7 @@ fun IndexScreen() {
.width(48.dp) .width(48.dp)
.height(32.dp) .height(32.dp)
.background( .background(
color = if (isSelected) selectedColor.copy(alpha = 0.15f) else Color.Transparent, color = if (isSelected) AppColors.brandColorsColor.copy(alpha = 0.15f) else Color.Transparent,
shape = RoundedCornerShape(12.dp) shape = RoundedCornerShape(12.dp)
), ),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
@@ -375,7 +362,7 @@ fun IndexScreen() {
Text( Text(
text = it.label(), text = it.label(),
fontSize = 10.sp, fontSize = 10.sp,
color = if (isSelected) selectedColor else AppColors.text, color = if (isSelected) Color.Blue else AppColors.text,
fontWeight = if (isSelected) FontWeight.W600 else FontWeight.Normal fontWeight = if (isSelected) FontWeight.W600 else FontWeight.Normal
) )
} }

View File

@@ -320,15 +320,12 @@ fun LoginPage() {
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()
) )
} }
// 减少下方间距,使文本上移
Spacer(modifier = Modifier.height(16.dp)) // 原来可能是24dp或32dp
Text( Text(
text = stringResource(R.string.join_party_carnival), text = stringResource(R.string.join_party_carnival),
fontSize = 17.sp, fontSize = 17.sp,
fontWeight = FontWeight.Bold, // 加粗字体 fontWeight = FontWeight.W600,
color = AppColors.text color = AppColors.text
) )
Spacer(modifier = Modifier.height(24.dp)) // 调整后续间距
// Image( // Image(
// painter = painterResource(id = R.mipmap.invalid_name), // painter = painterResource(id = R.mipmap.invalid_name),
// contentDescription = "Rave Now", // contentDescription = "Rave Now",
@@ -351,11 +348,12 @@ fun LoginPage() {
// color = AppColors.text // color = AppColors.text
// ) // )
//注册tab //注册tab
Spacer(modifier = Modifier.height(48.dp))
ActionButton( ActionButton(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
text = stringResource(R.string.sign_up_upper), text = stringResource(R.string.sign_up_upper),
color = Color.White, // 文字颜色保持白色 color = if (AppState.darkMode) Color.Black else Color.White,
backgroundColor = Color(0xFF4169E1) // 改为皇家蓝Royal Blue backgroundColor = if (AppState.darkMode) Color.White else Color.Black
) { ) {
navController.navigate( navController.navigate(
NavigationRoute.EmailSignUp.route, NavigationRoute.EmailSignUp.route,
@@ -423,7 +421,7 @@ fun MovingImageWall(resources: Resources) {
val AppColors = LocalAppTheme.current val AppColors = LocalAppTheme.current
val imageList1 = remember { val imageList1 = remember {
mutableStateListOf( mutableStateListOf(
R.drawable.wall_1_2, R.drawable.wall_1_1,
R.drawable.wall_1_2, R.drawable.wall_1_2,
R.drawable.wall_1_3, R.drawable.wall_1_3,
R.drawable.wall_1_1, R.drawable.wall_1_1,

View File

@@ -58,7 +58,7 @@
<string name="current_password_tip4">密码必须至少包含一个大写字母</string> <string name="current_password_tip4">密码必须至少包含一个大写字母</string>
<string name="change_password_tip1">密码不匹配</string> <string name="change_password_tip1">密码不匹配</string>
<string name="current_password">当前密码</string> <string name="current_password">当前密码</string>
<string name="current_password_tip5">请输入密码</string> <string name="current_password_tip5">请输入当前密码</string>
<string name="new_password">新密码</string> <string name="new_password">新密码</string>
<string name="new_password_tip1">请输入新密码</string> <string name="new_password_tip1">请输入新密码</string>
<string name="confirm_new_password">确认新密码</string> <string name="confirm_new_password">确认新密码</string>