Explorar o código

feat: 增加模型思考强度配置,使用openai标准low / medium / high / xhigh / max 五个挡位,deepseek等模型做了向此挡位配置的映射

duanhl hai 3 meses
pai
achega
2d3c33de66

+ 19 - 6
app/src/main/java/io/legado/app/data/repository/AiProfileRepository.kt

@@ -15,6 +15,7 @@ import io.legado.app.domain.model.AiPromptTemplate
 import io.legado.app.domain.model.AiProtocol
 import io.legado.app.domain.model.AiProtocol
 import io.legado.app.domain.model.AiProviderConfig
 import io.legado.app.domain.model.AiProviderConfig
 import io.legado.app.domain.model.AiProviderDraft
 import io.legado.app.domain.model.AiProviderDraft
+import io.legado.app.domain.model.AiReasoningLevel
 import io.legado.app.domain.model.AiTaskRuntimeOptions
 import io.legado.app.domain.model.AiTaskRuntimeOptions
 import io.legado.app.domain.model.AiTaskPresetConfig
 import io.legado.app.domain.model.AiTaskPresetConfig
 import io.legado.app.domain.model.AiTaskType
 import io.legado.app.domain.model.AiTaskType
@@ -97,7 +98,10 @@ class AiProfileRepository(
         )
         )
         val existingModel = aiProfileDao.getModel(modelProfileId)
         val existingModel = aiProfileDao.getModel(modelProfileId)
         val now = System.currentTimeMillis()
         val now = System.currentTimeMillis()
-        val params = AiGenerationParams(temperature = draft.temperature)
+        val params = AiGenerationParams(
+            temperature = draft.temperature,
+            reasoningLevel = draft.reasoningLevel
+        )
         val model = AiModelProfile(
         val model = AiModelProfile(
             id = modelProfileId,
             id = modelProfileId,
             providerId = draft.providerId,
             providerId = draft.providerId,
@@ -105,7 +109,7 @@ class AiProfileRepository(
             modelId = draft.modelId,
             modelId = draft.modelId,
             contextWindow = draft.contextWindow,
             contextWindow = draft.contextWindow,
             maxOutputTokens = draft.maxOutputTokens,
             maxOutputTokens = draft.maxOutputTokens,
-            capabilities = existingModel?.capabilities.orEmpty(),
+            capabilities = mergeCapabilities(existingModel?.capabilities, draft.modelId),
             defaultParamsJson = GSON.toJson(params),
             defaultParamsJson = GSON.toJson(params),
             enabled = existingModel?.enabled ?: true,
             enabled = existingModel?.enabled ?: true,
             sortNumber = existingModel?.sortNumber ?: 0,
             sortNumber = existingModel?.sortNumber ?: 0,
@@ -132,9 +136,10 @@ class AiProfileRepository(
                 modelId = availableModel.id,
                 modelId = availableModel.id,
                 contextWindow = availableModel.contextWindow.takeIf { it > 0 } ?: existingModel?.contextWindow ?: 0,
                 contextWindow = availableModel.contextWindow.takeIf { it > 0 } ?: existingModel?.contextWindow ?: 0,
                 maxOutputTokens = availableModel.maxOutputTokens.takeIf { it > 0 } ?: existingModel?.maxOutputTokens ?: 0,
                 maxOutputTokens = availableModel.maxOutputTokens.takeIf { it > 0 } ?: existingModel?.maxOutputTokens ?: 0,
-                capabilities = existingModel?.capabilities?.takeIf { it.isNotBlank() }
-                    ?: AiModelRegistry.inferCapabilities(availableModel.id).joinToString(","),
-                defaultParamsJson = existingModel?.defaultParamsJson ?: GSON.toJson(AiGenerationParams()),
+                capabilities = mergeCapabilities(existingModel?.capabilities, availableModel.id),
+                defaultParamsJson = existingModel?.defaultParamsJson ?: GSON.toJson(
+                    AiGenerationParams(reasoningLevel = AiReasoningLevel.MEDIUM)
+                ),
                 enabled = existingModel?.enabled ?: true,
                 enabled = existingModel?.enabled ?: true,
                 sortNumber = existingModel?.sortNumber ?: 0,
                 sortNumber = existingModel?.sortNumber ?: 0,
                 createdAt = existingModel?.createdAt ?: now,
                 createdAt = existingModel?.createdAt ?: now,
@@ -266,7 +271,7 @@ class AiProfileRepository(
                 modelId = draft.modelId,
                 modelId = draft.modelId,
                 contextWindow = draft.contextWindow,
                 contextWindow = draft.contextWindow,
                 maxOutputTokens = draft.maxOutputTokens,
                 maxOutputTokens = draft.maxOutputTokens,
-                capabilities = existingModel?.capabilities.orEmpty(),
+                capabilities = mergeCapabilities(existingModel?.capabilities, draft.modelId),
                 defaultParamsJson = GSON.toJson(params),
                 defaultParamsJson = GSON.toJson(params),
                 createdAt = existingModel?.createdAt ?: now,
                 createdAt = existingModel?.createdAt ?: now,
                 updatedAt = now
                 updatedAt = now
@@ -407,6 +412,14 @@ class AiProfileRepository(
         }.getOrDefault(emptyMap())
         }.getOrDefault(emptyMap())
     }
     }
 
 
+    private fun mergeCapabilities(existing: String?, modelId: String): String {
+        return (existing.orEmpty().split(',') + AiModelRegistry.inferCapabilities(modelId))
+            .map { it.trim() }
+            .filter { it.isNotEmpty() }
+            .distinct()
+            .joinToString(",")
+    }
+
     private companion object {
     private companion object {
         const val DEFAULT_TRANSLATE_PRESET_ID = "default_translate_chapter"
         const val DEFAULT_TRANSLATE_PRESET_ID = "default_translate_chapter"
         const val DEFAULT_SUMMARY_PRESET_ID = "default_summarize_chapter"
         const val DEFAULT_SUMMARY_PRESET_ID = "default_summarize_chapter"

+ 0 - 9
app/src/main/java/io/legado/app/data/repository/ai/AiSseUtils.kt

@@ -3,7 +3,6 @@ package io.legado.app.data.repository.ai
 import com.google.gson.JsonElement
 import com.google.gson.JsonElement
 import com.google.gson.JsonObject
 import com.google.gson.JsonObject
 import io.legado.app.domain.model.AiCapability
 import io.legado.app.domain.model.AiCapability
-import io.legado.app.domain.model.AiReasoningLevel
 import io.legado.app.utils.GSON
 import io.legado.app.utils.GSON
 import okhttp3.Response
 import okhttp3.Response
 
 
@@ -66,11 +65,3 @@ internal fun JsonElement.asJsonArrayOrNull() = if (isJsonArray) asJsonArray else
 internal fun hasReasoningCapability(capabilities: Set<String>): Boolean {
 internal fun hasReasoningCapability(capabilities: Set<String>): Boolean {
     return AiCapability.REASONING in capabilities
     return AiCapability.REASONING in capabilities
 }
 }
-
-/**
- * Map AiReasoningLevel to OpenAI-compatible reasoning_effort value.
- * OpenAI doesn't accept "none" — remap to "low".
- */
-internal fun AiReasoningLevel.toOpenAiEffort(): String {
-    return if (effort == "none") "low" else effort
-}

+ 4 - 4
app/src/main/java/io/legado/app/data/repository/ai/AnthropicHandler.kt

@@ -75,8 +75,8 @@ class AnthropicHandler : AiProtocolHandler {
                 }
                 }
             }
             }
             body["thinking"] = thinking
             body["thinking"] = thinking
-            if (reasoningLevel != AiReasoningLevel.OFF && reasoningLevel != AiReasoningLevel.AUTO) {
-                body["output_config"] = mapOf("effort" to reasoningLevel.effort)
+            reasoningLevel.effortFor(provider)?.let {
+                body["output_config"] = mapOf("effort" to it)
             }
             }
         } else {
         } else {
             request.params.temperature?.let { body["temperature"] = it }
             request.params.temperature?.let { body["temperature"] = it }
@@ -143,8 +143,8 @@ class AnthropicHandler : AiProtocolHandler {
                 }
                 }
             }
             }
             body["thinking"] = thinking
             body["thinking"] = thinking
-            if (reasoningLevel != AiReasoningLevel.OFF && reasoningLevel != AiReasoningLevel.AUTO) {
-                body["output_config"] = mapOf("effort" to reasoningLevel.effort)
+            reasoningLevel.effortFor(provider)?.let {
+                body["output_config"] = mapOf("effort" to it)
             }
             }
         } else {
         } else {
             request.params.temperature?.let { body["temperature"] = it }
             request.params.temperature?.let { body["temperature"] = it }

+ 8 - 9
app/src/main/java/io/legado/app/data/repository/ai/OpenAiChatHandler.kt

@@ -3,18 +3,14 @@ package io.legado.app.data.repository.ai
 import androidx.annotation.Keep
 import androidx.annotation.Keep
 import io.legado.app.domain.gateway.AiStreamEvent
 import io.legado.app.domain.gateway.AiStreamEvent
 import io.legado.app.domain.model.AiAvailableModel
 import io.legado.app.domain.model.AiAvailableModel
-import io.legado.app.domain.model.AiCapability
 import io.legado.app.domain.model.AiGenerateRequest
 import io.legado.app.domain.model.AiGenerateRequest
 import io.legado.app.domain.model.AiGenerateResponse
 import io.legado.app.domain.model.AiGenerateResponse
 import io.legado.app.domain.model.AiMessage
 import io.legado.app.domain.model.AiMessage
 import io.legado.app.domain.model.AiMessageRole
 import io.legado.app.domain.model.AiMessageRole
 import io.legado.app.domain.model.AiProtocol
 import io.legado.app.domain.model.AiProtocol
 import io.legado.app.domain.model.AiProviderConfig
 import io.legado.app.domain.model.AiProviderConfig
-import io.legado.app.domain.model.AiReasoningLevel
-import io.legado.app.domain.model.AiToolCall
 import io.legado.app.domain.model.AiToolDefinition
 import io.legado.app.domain.model.AiToolDefinition
 import io.legado.app.help.http.addHeaders
 import io.legado.app.help.http.addHeaders
-import io.legado.app.help.http.await
 import io.legado.app.help.http.newCallResponse
 import io.legado.app.help.http.newCallResponse
 import io.legado.app.help.http.newCallStrResponse
 import io.legado.app.help.http.newCallStrResponse
 import io.legado.app.help.http.okHttpClient
 import io.legado.app.help.http.okHttpClient
@@ -22,7 +18,6 @@ import io.legado.app.help.http.postJson
 import io.legado.app.utils.GSON
 import io.legado.app.utils.GSON
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.withContext
 import kotlinx.coroutines.withContext
-import okhttp3.Response
 
 
 class OpenAiChatHandler : AiProtocolHandler {
 class OpenAiChatHandler : AiProtocolHandler {
 
 
@@ -60,8 +55,10 @@ class OpenAiChatHandler : AiProtocolHandler {
         params.temperature?.let { body["temperature"] = it }
         params.temperature?.let { body["temperature"] = it }
         params.maxOutputTokens?.let { body["max_tokens"] = it }
         params.maxOutputTokens?.let { body["max_tokens"] = it }
         params.topP?.let { body["top_p"] = it }
         params.topP?.let { body["top_p"] = it }
-        if (hasReasoningCapability(request.model.capabilities) && params.reasoningLevel != AiReasoningLevel.AUTO) {
-            body["reasoning_effort"] = params.reasoningLevel.toOpenAiEffort()
+        if (hasReasoningCapability(request.model.capabilities)) {
+            params.reasoningLevel.effortFor(provider)?.let {
+                body["reasoning_effort"] = it
+            }
         }
         }
 
 
         return retryWithBackoff(maxAttempts = 3, keyRotator = keyRotator) {
         return retryWithBackoff(maxAttempts = 3, keyRotator = keyRotator) {
@@ -107,8 +104,10 @@ class OpenAiChatHandler : AiProtocolHandler {
         params.temperature?.let { body["temperature"] = it }
         params.temperature?.let { body["temperature"] = it }
         params.maxOutputTokens?.let { body["max_tokens"] = it }
         params.maxOutputTokens?.let { body["max_tokens"] = it }
         params.topP?.let { body["top_p"] = it }
         params.topP?.let { body["top_p"] = it }
-        if (hasReasoningCapability(request.model.capabilities) && params.reasoningLevel != AiReasoningLevel.AUTO) {
-            body["reasoning_effort"] = params.reasoningLevel.toOpenAiEffort()
+        if (hasReasoningCapability(request.model.capabilities)) {
+            params.reasoningLevel.effortFor(provider)?.let {
+                body["reasoning_effort"] = it
+            }
         }
         }
 
 
         // For streaming, we retry before establishing the SSE connection.
         // For streaming, we retry before establishing the SSE connection.

+ 4 - 5
app/src/main/java/io/legado/app/data/repository/ai/OpenAiResponsesHandler.kt

@@ -10,7 +10,6 @@ import io.legado.app.domain.model.AiMessage
 import io.legado.app.domain.model.AiMessageRole
 import io.legado.app.domain.model.AiMessageRole
 import io.legado.app.domain.model.AiProtocol
 import io.legado.app.domain.model.AiProtocol
 import io.legado.app.domain.model.AiProviderConfig
 import io.legado.app.domain.model.AiProviderConfig
-import io.legado.app.domain.model.AiReasoningLevel
 import io.legado.app.domain.model.AiToolCall
 import io.legado.app.domain.model.AiToolCall
 import io.legado.app.domain.model.AiToolDefinition
 import io.legado.app.domain.model.AiToolDefinition
 import io.legado.app.help.http.addHeaders
 import io.legado.app.help.http.addHeaders
@@ -62,8 +61,8 @@ class OpenAiResponsesHandler : AiProtocolHandler {
         if (hasReasoningCapability(request.model.capabilities)) {
         if (hasReasoningCapability(request.model.capabilities)) {
             body["reasoning"] = buildMap<String, Any> {
             body["reasoning"] = buildMap<String, Any> {
                 put("summary", "auto")
                 put("summary", "auto")
-                if (params.reasoningLevel != AiReasoningLevel.AUTO) {
-                    put("effort", params.reasoningLevel.effort)
+                params.reasoningLevel.effortFor(provider)?.let {
+                    put("effort", it)
                 }
                 }
             }
             }
         }
         }
@@ -114,8 +113,8 @@ class OpenAiResponsesHandler : AiProtocolHandler {
         if (hasReasoningCapability(request.model.capabilities)) {
         if (hasReasoningCapability(request.model.capabilities)) {
             body["reasoning"] = buildMap<String, Any> {
             body["reasoning"] = buildMap<String, Any> {
                 put("summary", "auto")
                 put("summary", "auto")
-                if (params.reasoningLevel != AiReasoningLevel.AUTO) {
-                    put("effort", params.reasoningLevel.effort)
+                params.reasoningLevel.effortFor(provider)?.let {
+                    put("effort", it)
                 }
                 }
             }
             }
         }
         }

+ 8 - 2
app/src/main/java/io/legado/app/domain/model/AiModelRegistry.kt

@@ -17,9 +17,15 @@ object AiModelRegistry {
     )
     )
 
 
     private val patterns = listOf(
     private val patterns = listOf(
-        // OpenAI GPT-4o / GPT-4.1 / GPT-5
+        // OpenAI GPT-5 reasoning models
+        ModelPattern(
+            tokens = listOf("gpt-5"),
+            capabilities = setOf(AiCapability.TOOLS, AiCapability.REASONING, AiCapability.VISION, AiCapability.STREAMING)
+        ),
+        // OpenAI non-reasoning GPT models
         ModelPattern(
         ModelPattern(
             tokens = listOf("gpt"),
             tokens = listOf("gpt"),
+            notTokens = listOf("gpt-5"),
             capabilities = setOf(AiCapability.TOOLS, AiCapability.VISION, AiCapability.STREAMING)
             capabilities = setOf(AiCapability.TOOLS, AiCapability.VISION, AiCapability.STREAMING)
         ),
         ),
         // OpenAI o-series (reasoning models)
         // OpenAI o-series (reasoning models)
@@ -50,7 +56,7 @@ object AiModelRegistry {
         ModelPattern(
         ModelPattern(
             tokens = listOf("deepseek"),
             tokens = listOf("deepseek"),
             notTokens = listOf("reasoner"),
             notTokens = listOf("reasoner"),
-            capabilities = setOf(AiCapability.TOOLS, AiCapability.STREAMING)
+            capabilities = setOf(AiCapability.TOOLS, AiCapability.REASONING, AiCapability.STREAMING)
         ),
         ),
         // Gemini
         // Gemini
         ModelPattern(
         ModelPattern(

+ 23 - 2
app/src/main/java/io/legado/app/domain/model/AiModels.kt

@@ -202,7 +202,8 @@ data class AiModelDraft(
     val modelId: String,
     val modelId: String,
     val contextWindow: Int = 0,
     val contextWindow: Int = 0,
     val maxOutputTokens: Int = 0,
     val maxOutputTokens: Int = 0,
-    val temperature: Float = TranslationConstants.DEFAULT_TEMPERATURE
+    val temperature: Float = TranslationConstants.DEFAULT_TEMPERATURE,
+    val reasoningLevel: AiReasoningLevel = AiReasoningLevel.MEDIUM
 )
 )
 
 
 /**
 /**
@@ -219,11 +220,31 @@ enum class AiReasoningLevel(val effort: String, val budgetTokens: Int) {
     LOW("low", 1_000),
     LOW("low", 1_000),
     MEDIUM("medium", 2_000),
     MEDIUM("medium", 2_000),
     HIGH("high", 8_000),
     HIGH("high", 8_000),
-    XHIGH("xhigh", 16_000);
+    XHIGH("xhigh", 16_000),
+    MAX("max", 32_000);
 
 
     val isEnabled: Boolean get() = this != OFF
     val isEnabled: Boolean get() = this != OFF
 
 
+    fun effortFor(provider: AiProviderConfig): String? {
+        val identity = "${provider.id} ${provider.name} ${provider.baseUrl}".lowercase()
+        return when {
+            "mimo" in identity || "xiaomi" in identity -> null
+            "deepseek" in identity && this == MEDIUM -> LOW.effort
+            "deepseek" in identity ||
+                "openai" in identity ||
+                "anthropic" in identity ||
+                "claude" in identity -> standardEffort()
+            else -> null
+        }
+    }
+
+    private fun standardEffort(): String? {
+        return takeIf { it in modelConfigEntries }?.effort
+    }
+
     companion object {
     companion object {
+        val modelConfigEntries = listOf(LOW, MEDIUM, HIGH, XHIGH, MAX)
+
         fun fromEffort(effort: String): AiReasoningLevel =
         fun fromEffort(effort: String): AiReasoningLevel =
             entries.firstOrNull { it.effort == effort } ?: AUTO
             entries.firstOrNull { it.effort == effort } ?: AUTO
 
 

+ 10 - 2
app/src/main/java/io/legado/app/domain/usecase/AiChatGenerationUseCase.kt

@@ -44,7 +44,9 @@ class AiChatGenerationUseCase(
         return AiGenerateRequest(
         return AiGenerateRequest(
             model = preset.model,
             model = preset.model,
             messages = buildRequestMessages(userContent, history, conversationId),
             messages = buildRequestMessages(userContent, history, conversationId),
-            params = preset.params.copy(reasoningLevel = reasoningLevel),
+            params = preset.params.copy(
+                reasoningLevel = reasoningLevel.resolveModelDefault(preset.params.reasoningLevel)
+            ),
             tools = aiToolGateway.availableTools()
             tools = aiToolGateway.availableTools()
         )
         )
     }
     }
@@ -133,7 +135,9 @@ class AiChatGenerationUseCase(
         val request = AiGenerateRequest(
         val request = AiGenerateRequest(
             model = preset.model,
             model = preset.model,
             messages = listOf(AiMessage(AiMessageRole.USER, prompt)),
             messages = listOf(AiMessage(AiMessageRole.USER, prompt)),
-            params = preset.params.copy(reasoningLevel = reasoningLevel)
+            params = preset.params.copy(
+                reasoningLevel = reasoningLevel.resolveModelDefault(preset.params.reasoningLevel)
+            )
         )
         )
         val result = aiTextGateway.generate(request)
         val result = aiTextGateway.generate(request)
         return result.getOrNull()?.text?.trim()?.take(30) ?: userContent.take(20)
         return result.getOrNull()?.text?.trim()?.take(30) ?: userContent.take(20)
@@ -218,6 +222,10 @@ class AiChatGenerationUseCase(
         return "$base\n\n## User Memory\nThe following facts about the user have been remembered from prior conversations:\n$memoryBlock"
         return "$base\n\n## User Memory\nThe following facts about the user have been remembered from prior conversations:\n$memoryBlock"
     }
     }
 
 
+    private fun AiReasoningLevel.resolveModelDefault(default: AiReasoningLevel): AiReasoningLevel {
+        return if (this == AiReasoningLevel.AUTO) default else this
+    }
+
     companion object {
     companion object {
         const val MAX_HISTORY_MESSAGES = 12
         const val MAX_HISTORY_MESSAGES = 12
         const val MAX_TOOL_OUTPUT_CHARS = 8_000
         const val MAX_TOOL_OUTPUT_CHARS = 8_000

+ 15 - 16
app/src/main/java/io/legado/app/ui/ai/chat/AiChatScreen.kt

@@ -852,14 +852,7 @@ private fun ChatInputBar(
     onUpdateReasoningLevel: (AiReasoningLevel) -> Unit
     onUpdateReasoningLevel: (AiReasoningLevel) -> Unit
 ) {
 ) {
     var showThinkingSheet by rememberSaveable { mutableStateOf(false) }
     var showThinkingSheet by rememberSaveable { mutableStateOf(false) }
-    val thinkingLabel = when (reasoningLevel) {
-        AiReasoningLevel.OFF -> stringResource(R.string.ai_thinking_off)
-        AiReasoningLevel.AUTO -> "Auto"
-        AiReasoningLevel.LOW -> "Low"
-        AiReasoningLevel.MEDIUM -> "Med"
-        AiReasoningLevel.HIGH -> "High"
-        AiReasoningLevel.XHIGH -> "Max"
-    }
+    val thinkingLabel = reasoningLevel.label()
     val isThinkingOn = reasoningLevel != AiReasoningLevel.OFF
     val isThinkingOn = reasoningLevel != AiReasoningLevel.OFF
     val isKeyboardVisible =
     val isKeyboardVisible =
         WindowInsets.ime.asPaddingValues().calculateBottomPadding() > 0.dp
         WindowInsets.ime.asPaddingValues().calculateBottomPadding() > 0.dp
@@ -882,14 +875,7 @@ private fun ChatInputBar(
     ) {
     ) {
         AiReasoningLevel.entries.forEach { level ->
         AiReasoningLevel.entries.forEach { level ->
             val isSelected = level == reasoningLevel
             val isSelected = level == reasoningLevel
-            val label = when (level) {
-                AiReasoningLevel.OFF -> stringResource(R.string.ai_thinking_off)
-                AiReasoningLevel.AUTO -> "Auto"
-                AiReasoningLevel.LOW -> "Low"
-                AiReasoningLevel.MEDIUM -> "Med"
-                AiReasoningLevel.HIGH -> "High"
-                AiReasoningLevel.XHIGH -> "Max"
-            }
+            val label = level.label()
 
 
             Row(
             Row(
                 modifier = Modifier
                 modifier = Modifier
@@ -1007,6 +993,19 @@ private fun ChatInputBar(
     }
     }
 }
 }
 
 
+@Composable
+private fun AiReasoningLevel.label(): String {
+    return when (this) {
+        AiReasoningLevel.OFF -> stringResource(R.string.ai_thinking_off)
+        AiReasoningLevel.AUTO -> stringResource(R.string.ai_thinking_auto)
+        AiReasoningLevel.LOW -> stringResource(R.string.ai_reasoning_level_low)
+        AiReasoningLevel.MEDIUM -> stringResource(R.string.ai_reasoning_level_medium)
+        AiReasoningLevel.HIGH -> stringResource(R.string.ai_reasoning_level_high)
+        AiReasoningLevel.XHIGH -> stringResource(R.string.ai_reasoning_level_xhigh)
+        AiReasoningLevel.MAX -> stringResource(R.string.ai_reasoning_level_max)
+    }
+}
+
 @Composable
 @Composable
 private fun EmptyChatHint() {
 private fun EmptyChatHint() {
     Box(
     Box(

+ 3 - 0
app/src/main/java/io/legado/app/ui/config/ai/AiModelEditContract.kt

@@ -1,6 +1,7 @@
 package io.legado.app.ui.config.ai
 package io.legado.app.ui.config.ai
 
 
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.Stable
+import io.legado.app.domain.model.AiReasoningLevel
 import io.legado.app.domain.model.TranslationConstants
 import io.legado.app.domain.model.TranslationConstants
 import kotlinx.collections.immutable.ImmutableList
 import kotlinx.collections.immutable.ImmutableList
 import kotlinx.collections.immutable.persistentListOf
 import kotlinx.collections.immutable.persistentListOf
@@ -15,6 +16,7 @@ data class AiModelEditUiState(
     val contextWindow: Int = 0,
     val contextWindow: Int = 0,
     val maxOutputTokens: Int = 0,
     val maxOutputTokens: Int = 0,
     val temperature: Float = TranslationConstants.DEFAULT_TEMPERATURE,
     val temperature: Float = TranslationConstants.DEFAULT_TEMPERATURE,
+    val reasoningLevel: AiReasoningLevel = AiReasoningLevel.MEDIUM,
     val isSaving: Boolean = false,
     val isSaving: Boolean = false,
     val isTesting: Boolean = false,
     val isTesting: Boolean = false,
     val initialized: Boolean = false
     val initialized: Boolean = false
@@ -35,6 +37,7 @@ sealed interface AiModelEditIntent {
     data class UpdateContextWindow(val value: Int) : AiModelEditIntent
     data class UpdateContextWindow(val value: Int) : AiModelEditIntent
     data class UpdateMaxOutputTokens(val value: Int) : AiModelEditIntent
     data class UpdateMaxOutputTokens(val value: Int) : AiModelEditIntent
     data class UpdateTemperature(val value: Float) : AiModelEditIntent
     data class UpdateTemperature(val value: Float) : AiModelEditIntent
+    data class UpdateReasoningLevel(val value: AiReasoningLevel) : AiModelEditIntent
     data object Save : AiModelEditIntent
     data object Save : AiModelEditIntent
     data object TestConnection : AiModelEditIntent
     data object TestConnection : AiModelEditIntent
 }
 }

+ 18 - 0
app/src/main/java/io/legado/app/ui/config/ai/AiModelEditScreen.kt

@@ -16,6 +16,7 @@ import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import io.legado.app.R
 import io.legado.app.R
+import io.legado.app.domain.model.AiReasoningLevel
 import io.legado.app.domain.model.TranslationConstants
 import io.legado.app.domain.model.TranslationConstants
 import io.legado.app.ui.theme.adaptiveContentPadding
 import io.legado.app.ui.theme.adaptiveContentPadding
 import io.legado.app.ui.widget.components.AppFloatingActionButton
 import io.legado.app.ui.widget.components.AppFloatingActionButton
@@ -173,6 +174,23 @@ fun AiModelEditScreen(
                         entryValues = maxOutputTokenOptions.entryValues,
                         entryValues = maxOutputTokenOptions.entryValues,
                         onValueChange = { onIntent(AiModelEditIntent.UpdateMaxOutputTokens(it.toIntOrNull() ?: 0)) }
                         onValueChange = { onIntent(AiModelEditIntent.UpdateMaxOutputTokens(it.toIntOrNull() ?: 0)) }
                     )
                     )
+                    DropdownListSettingItem(
+                        title = stringResource(R.string.ai_thinking_strength),
+                        selectedValue = state.reasoningLevel.effort,
+                        displayEntries = arrayOf(
+                            stringResource(R.string.ai_reasoning_level_low),
+                            stringResource(R.string.ai_reasoning_level_medium),
+                            stringResource(R.string.ai_reasoning_level_high),
+                            stringResource(R.string.ai_reasoning_level_xhigh),
+                            stringResource(R.string.ai_reasoning_level_max)
+                        ),
+                        entryValues = AiReasoningLevel.modelConfigEntries
+                            .map { it.effort }
+                            .toTypedArray(),
+                        onValueChange = {
+                            onIntent(AiModelEditIntent.UpdateReasoningLevel(AiReasoningLevel.fromEffort(it)))
+                        }
+                    )
                     SliderSettingItem(
                     SliderSettingItem(
                         title = stringResource(R.string.ai_temperature),
                         title = stringResource(R.string.ai_temperature),
                         value = state.temperature,
                         value = state.temperature,

+ 9 - 1
app/src/main/java/io/legado/app/ui/config/ai/AiModelEditViewModel.kt

@@ -9,6 +9,7 @@ import io.legado.app.domain.model.AiGenerationParams
 import io.legado.app.domain.model.AiMessage
 import io.legado.app.domain.model.AiMessage
 import io.legado.app.domain.model.AiMessageRole
 import io.legado.app.domain.model.AiMessageRole
 import io.legado.app.domain.model.AiModelDraft
 import io.legado.app.domain.model.AiModelDraft
+import io.legado.app.domain.model.AiReasoningLevel
 import io.legado.app.utils.GSON
 import io.legado.app.utils.GSON
 import kotlinx.collections.immutable.toImmutableList
 import kotlinx.collections.immutable.toImmutableList
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableSharedFlow
@@ -72,6 +73,7 @@ class AiModelEditViewModel(
                             contextWindow = model?.contextWindow ?: 0,
                             contextWindow = model?.contextWindow ?: 0,
                             maxOutputTokens = model?.maxOutputTokens ?: 0,
                             maxOutputTokens = model?.maxOutputTokens ?: 0,
                             temperature = params.temperature ?: current.temperature,
                             temperature = params.temperature ?: current.temperature,
+                            reasoningLevel = params.reasoningLevel.toModelConfigLevel(),
                             initialized = true
                             initialized = true
                         )
                         )
                     }
                     }
@@ -88,6 +90,7 @@ class AiModelEditViewModel(
             is AiModelEditIntent.UpdateContextWindow -> _uiState.update { it.copy(contextWindow = intent.value) }
             is AiModelEditIntent.UpdateContextWindow -> _uiState.update { it.copy(contextWindow = intent.value) }
             is AiModelEditIntent.UpdateMaxOutputTokens -> _uiState.update { it.copy(maxOutputTokens = intent.value) }
             is AiModelEditIntent.UpdateMaxOutputTokens -> _uiState.update { it.copy(maxOutputTokens = intent.value) }
             is AiModelEditIntent.UpdateTemperature -> _uiState.update { it.copy(temperature = intent.value) }
             is AiModelEditIntent.UpdateTemperature -> _uiState.update { it.copy(temperature = intent.value) }
+            is AiModelEditIntent.UpdateReasoningLevel -> _uiState.update { it.copy(reasoningLevel = intent.value) }
             AiModelEditIntent.Save -> save(navigateBack = true)
             AiModelEditIntent.Save -> save(navigateBack = true)
             AiModelEditIntent.TestConnection -> testConnection()
             AiModelEditIntent.TestConnection -> testConnection()
         }
         }
@@ -146,10 +149,15 @@ class AiModelEditViewModel(
             modelId = modelId.trim(),
             modelId = modelId.trim(),
             contextWindow = contextWindow,
             contextWindow = contextWindow,
             maxOutputTokens = maxOutputTokens,
             maxOutputTokens = maxOutputTokens,
-            temperature = temperature
+            temperature = temperature,
+            reasoningLevel = reasoningLevel
         )
         )
     }
     }
 
 
+    private fun AiReasoningLevel.toModelConfigLevel(): AiReasoningLevel {
+        return takeIf { it in AiReasoningLevel.modelConfigEntries } ?: AiReasoningLevel.MEDIUM
+    }
+
     private fun parseParams(json: String?): AiGenerationParams {
     private fun parseParams(json: String?): AiGenerationParams {
         if (json.isNullOrBlank()) return AiGenerationParams()
         if (json.isNullOrBlank()) return AiGenerationParams()
         return runCatching {
         return runCatching {

+ 6 - 2
app/src/main/java/io/legado/app/ui/config/ai/AiProviderEditContract.kt

@@ -2,6 +2,7 @@ package io.legado.app.ui.config.ai
 
 
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.Stable
 import io.legado.app.domain.model.AiProtocol
 import io.legado.app.domain.model.AiProtocol
+import io.legado.app.domain.model.AiReasoningLevel
 import kotlinx.collections.immutable.ImmutableList
 import kotlinx.collections.immutable.ImmutableList
 import kotlinx.collections.immutable.persistentListOf
 import kotlinx.collections.immutable.persistentListOf
 
 
@@ -51,7 +52,8 @@ data class AiProviderModelUi(
     val modelId: String,
     val modelId: String,
     val contextWindow: Int = 0,
     val contextWindow: Int = 0,
     val maxOutputTokens: Int = 0,
     val maxOutputTokens: Int = 0,
-    val temperature: Float = 0.3f
+    val temperature: Float = 0.3f,
+    val reasoningLevel: AiReasoningLevel = AiReasoningLevel.MEDIUM
 )
 )
 
 
 @Stable
 @Stable
@@ -61,7 +63,8 @@ data class AiProviderModelEditorUi(
     val modelId: String = "",
     val modelId: String = "",
     val contextWindow: String = "",
     val contextWindow: String = "",
     val maxOutputTokens: String = "",
     val maxOutputTokens: String = "",
-    val temperature: String = "0.3"
+    val temperature: String = "0.3",
+    val reasoningLevel: AiReasoningLevel = AiReasoningLevel.MEDIUM
 )
 )
 
 
 sealed interface AiProviderEditIntent {
 sealed interface AiProviderEditIntent {
@@ -79,6 +82,7 @@ sealed interface AiProviderEditIntent {
     data class UpdateEditingContextWindow(val value: String) : AiProviderEditIntent
     data class UpdateEditingContextWindow(val value: String) : AiProviderEditIntent
     data class UpdateEditingMaxOutputTokens(val value: String) : AiProviderEditIntent
     data class UpdateEditingMaxOutputTokens(val value: String) : AiProviderEditIntent
     data class UpdateEditingTemperature(val value: String) : AiProviderEditIntent
     data class UpdateEditingTemperature(val value: String) : AiProviderEditIntent
+    data class UpdateEditingReasoningLevel(val value: AiReasoningLevel) : AiProviderEditIntent
     data object SaveEditingModel : AiProviderEditIntent
     data object SaveEditingModel : AiProviderEditIntent
     data object TestConnection : AiProviderEditIntent
     data object TestConnection : AiProviderEditIntent
     data object SaveProvider : AiProviderEditIntent
     data object SaveProvider : AiProviderEditIntent

+ 19 - 0
app/src/main/java/io/legado/app/ui/config/ai/AiProviderEditScreen.kt

@@ -32,6 +32,7 @@ import androidx.compose.ui.unit.dp
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import io.legado.app.R
 import io.legado.app.R
 import io.legado.app.domain.model.AiProtocol
 import io.legado.app.domain.model.AiProtocol
+import io.legado.app.domain.model.AiReasoningLevel
 import io.legado.app.ui.theme.LegadoTheme
 import io.legado.app.ui.theme.LegadoTheme
 import io.legado.app.ui.theme.adaptiveContentPadding
 import io.legado.app.ui.theme.adaptiveContentPadding
 import io.legado.app.ui.widget.components.AppFloatingActionButton
 import io.legado.app.ui.widget.components.AppFloatingActionButton
@@ -309,6 +310,24 @@ fun AiProviderEditScreen(
                     label = stringResource(R.string.ai_temperature),
                     label = stringResource(R.string.ai_temperature),
                     keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal)
                     keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal)
                 )
                 )
+                Spacer(modifier = Modifier.height(8.dp))
+                DropdownListSettingItem(
+                    title = stringResource(R.string.ai_thinking_strength),
+                    selectedValue = model.reasoningLevel.effort,
+                    displayEntries = arrayOf(
+                        stringResource(R.string.ai_reasoning_level_low),
+                        stringResource(R.string.ai_reasoning_level_medium),
+                        stringResource(R.string.ai_reasoning_level_high),
+                        stringResource(R.string.ai_reasoning_level_xhigh),
+                        stringResource(R.string.ai_reasoning_level_max)
+                    ),
+                    entryValues = AiReasoningLevel.modelConfigEntries
+                        .map { it.effort }
+                        .toTypedArray(),
+                    onValueChange = {
+                        onIntent(AiProviderEditIntent.UpdateEditingReasoningLevel(AiReasoningLevel.fromEffort(it)))
+                    }
+                )
                 if (model.modelProfileId != null) {
                 if (model.modelProfileId != null) {
                     Spacer(modifier = Modifier.height(16.dp))
                     Spacer(modifier = Modifier.height(16.dp))
                     ClickableSettingItem(
                     ClickableSettingItem(

+ 12 - 3
app/src/main/java/io/legado/app/ui/config/ai/AiProviderEditViewModel.kt

@@ -11,6 +11,7 @@ import io.legado.app.domain.model.AiModelDraft
 import io.legado.app.domain.model.AiProviderConfig
 import io.legado.app.domain.model.AiProviderConfig
 import io.legado.app.domain.model.AiProviderDraft
 import io.legado.app.domain.model.AiProviderDraft
 import io.legado.app.domain.model.AiProviderPresets
 import io.legado.app.domain.model.AiProviderPresets
+import io.legado.app.domain.model.AiReasoningLevel
 import io.legado.app.domain.model.TranslationConstants
 import io.legado.app.domain.model.TranslationConstants
 import io.legado.app.utils.GSON
 import io.legado.app.utils.GSON
 import kotlinx.collections.immutable.toImmutableList
 import kotlinx.collections.immutable.toImmutableList
@@ -73,7 +74,8 @@ class AiProviderEditViewModel(
                                 modelId = model.modelId,
                                 modelId = model.modelId,
                                 contextWindow = model.contextWindow,
                                 contextWindow = model.contextWindow,
                                 maxOutputTokens = model.maxOutputTokens,
                                 maxOutputTokens = model.maxOutputTokens,
-                                temperature = params.temperature ?: TranslationConstants.DEFAULT_TEMPERATURE
+                                temperature = params.temperature ?: TranslationConstants.DEFAULT_TEMPERATURE,
+                                reasoningLevel = params.reasoningLevel.toModelConfigLevel()
                             )
                             )
                         }
                         }
                         .toImmutableList()
                         .toImmutableList()
@@ -116,6 +118,7 @@ class AiProviderEditViewModel(
             is AiProviderEditIntent.UpdateEditingContextWindow -> updateEditingModel { copy(contextWindow = intent.value) }
             is AiProviderEditIntent.UpdateEditingContextWindow -> updateEditingModel { copy(contextWindow = intent.value) }
             is AiProviderEditIntent.UpdateEditingMaxOutputTokens -> updateEditingModel { copy(maxOutputTokens = intent.value) }
             is AiProviderEditIntent.UpdateEditingMaxOutputTokens -> updateEditingModel { copy(maxOutputTokens = intent.value) }
             is AiProviderEditIntent.UpdateEditingTemperature -> updateEditingModel { copy(temperature = intent.value) }
             is AiProviderEditIntent.UpdateEditingTemperature -> updateEditingModel { copy(temperature = intent.value) }
+            is AiProviderEditIntent.UpdateEditingReasoningLevel -> updateEditingModel { copy(reasoningLevel = intent.value) }
             AiProviderEditIntent.SaveEditingModel -> saveEditingModel()
             AiProviderEditIntent.SaveEditingModel -> saveEditingModel()
             AiProviderEditIntent.TestConnection -> testConnection()
             AiProviderEditIntent.TestConnection -> testConnection()
             AiProviderEditIntent.SaveProvider -> saveProvider()
             AiProviderEditIntent.SaveProvider -> saveProvider()
@@ -153,7 +156,8 @@ class AiProviderEditViewModel(
                     modelId = model.modelId,
                     modelId = model.modelId,
                     contextWindow = model.contextWindow.takeIf { value -> value > 0 }?.toString().orEmpty(),
                     contextWindow = model.contextWindow.takeIf { value -> value > 0 }?.toString().orEmpty(),
                     maxOutputTokens = model.maxOutputTokens.takeIf { value -> value > 0 }?.toString().orEmpty(),
                     maxOutputTokens = model.maxOutputTokens.takeIf { value -> value > 0 }?.toString().orEmpty(),
-                    temperature = model.temperature.toString()
+                    temperature = model.temperature.toString(),
+                    reasoningLevel = model.reasoningLevel
                 )
                 )
             )
             )
         }
         }
@@ -181,7 +185,8 @@ class AiProviderEditViewModel(
                         contextWindow = editor.contextWindow.toIntOrNull() ?: 0,
                         contextWindow = editor.contextWindow.toIntOrNull() ?: 0,
                         maxOutputTokens = editor.maxOutputTokens.toIntOrNull() ?: 0,
                         maxOutputTokens = editor.maxOutputTokens.toIntOrNull() ?: 0,
                         temperature = editor.temperature.toFloatOrNull()
                         temperature = editor.temperature.toFloatOrNull()
-                            ?: TranslationConstants.DEFAULT_TEMPERATURE
+                            ?: TranslationConstants.DEFAULT_TEMPERATURE,
+                        reasoningLevel = editor.reasoningLevel
                     )
                     )
                 )
                 )
             }.onSuccess { model ->
             }.onSuccess { model ->
@@ -340,4 +345,8 @@ class AiProviderEditViewModel(
             GSON.fromJson(json, AiGenerationParams::class.java)
             GSON.fromJson(json, AiGenerationParams::class.java)
         }.getOrDefault(AiGenerationParams())
         }.getOrDefault(AiGenerationParams())
     }
     }
+
+    private fun AiReasoningLevel.toModelConfigLevel(): AiReasoningLevel {
+        return takeIf { it in AiReasoningLevel.modelConfigEntries } ?: AiReasoningLevel.MEDIUM
+    }
 }
 }

+ 5 - 0
app/src/main/res/values-zh-rCN/strings.xml

@@ -2092,6 +2092,11 @@
     <string name="ai_regenerate">重新生成</string>
     <string name="ai_regenerate">重新生成</string>
     <string name="ai_thinking_mode">思考模式</string>
     <string name="ai_thinking_mode">思考模式</string>
     <string name="ai_thinking_strength">强度</string>
     <string name="ai_thinking_strength">强度</string>
+    <string name="ai_reasoning_level_low">轻度</string>
+    <string name="ai_reasoning_level_medium">中</string>
+    <string name="ai_reasoning_level_high">高</string>
+    <string name="ai_reasoning_level_xhigh">极高</string>
+    <string name="ai_reasoning_level_max">最高</string>
     <string name="ai_thinking_off">关闭</string>
     <string name="ai_thinking_off">关闭</string>
     <string name="ai_thinking_auto">自动</string>
     <string name="ai_thinking_auto">自动</string>
     <string name="ai_thinking_deep">深度</string>
     <string name="ai_thinking_deep">深度</string>

+ 5 - 0
app/src/main/res/values-zh-rHK/strings.xml

@@ -1556,6 +1556,11 @@
     <string name="ai_thinking_seconds">思考 %.1f 秒</string>
     <string name="ai_thinking_seconds">思考 %.1f 秒</string>
     <string name="ai_thinking_show_more">顯示多 %d 個步驟</string>
     <string name="ai_thinking_show_more">顯示多 %d 個步驟</string>
     <string name="ai_thinking_strength">強度</string>
     <string name="ai_thinking_strength">強度</string>
+    <string name="ai_reasoning_level_low">輕度</string>
+    <string name="ai_reasoning_level_medium">中</string>
+    <string name="ai_reasoning_level_high">高</string>
+    <string name="ai_reasoning_level_xhigh">極高</string>
+    <string name="ai_reasoning_level_max">最高</string>
     <string name="ai_tool_confirmation">確認工具操作</string>
     <string name="ai_tool_confirmation">確認工具操作</string>
     <string name="ai_tool_done">完成</string>
     <string name="ai_tool_done">完成</string>
     <string name="ai_tool_input">輸入:</string>
     <string name="ai_tool_input">輸入:</string>

+ 5 - 0
app/src/main/res/values-zh-rTW/strings.xml

@@ -2025,6 +2025,11 @@
     <string name="ai_regenerate">重新生成</string>
     <string name="ai_regenerate">重新生成</string>
     <string name="ai_thinking_mode">思考模式</string>
     <string name="ai_thinking_mode">思考模式</string>
     <string name="ai_thinking_strength">強度</string>
     <string name="ai_thinking_strength">強度</string>
+    <string name="ai_reasoning_level_low">輕度</string>
+    <string name="ai_reasoning_level_medium">中</string>
+    <string name="ai_reasoning_level_high">高</string>
+    <string name="ai_reasoning_level_xhigh">極高</string>
+    <string name="ai_reasoning_level_max">最高</string>
     <string name="ai_thinking_off">關閉</string>
     <string name="ai_thinking_off">關閉</string>
     <string name="ai_thinking_auto">自動</string>
     <string name="ai_thinking_auto">自動</string>
     <string name="ai_thinking_deep">深度</string>
     <string name="ai_thinking_deep">深度</string>

+ 5 - 0
app/src/main/res/values/strings.xml

@@ -2037,6 +2037,11 @@
     <string name="ai_regenerate">Regenerate</string>
     <string name="ai_regenerate">Regenerate</string>
     <string name="ai_thinking_mode">Thinking</string>
     <string name="ai_thinking_mode">Thinking</string>
     <string name="ai_thinking_strength">Strength</string>
     <string name="ai_thinking_strength">Strength</string>
+    <string name="ai_reasoning_level_low">Low</string>
+    <string name="ai_reasoning_level_medium">Medium</string>
+    <string name="ai_reasoning_level_high">High</string>
+    <string name="ai_reasoning_level_xhigh">Very high</string>
+    <string name="ai_reasoning_level_max">Maximum</string>
     <string name="ai_thinking_off">Off</string>
     <string name="ai_thinking_off">Off</string>
     <string name="ai_thinking_auto">Auto</string>
     <string name="ai_thinking_auto">Auto</string>
     <string name="ai_thinking_deep">Deep</string>
     <string name="ai_thinking_deep">Deep</string>