feat(proxy): 设置页新增 Trae/WorkBuddy 流式探针
- 用默认账号直接请求 llm_utils_chat/v2 chat,抓取上游原始 SSE 帧 - 调试输出显示 HTTP 状态、Content-Type 与最多 80 行原始帧 - 为 StreamNormalizer 提供真实协议样本
This commit is contained in:
parent
67e1f060c5
commit
a4d854271f
@ -93,6 +93,7 @@ fun SettingsScreen(
|
|||||||
val proxyRunning by viewModel.proxyRunning.collectAsStateWithLifecycle()
|
val proxyRunning by viewModel.proxyRunning.collectAsStateWithLifecycle()
|
||||||
val proxyApiKey by viewModel.proxyApiKey.collectAsStateWithLifecycle()
|
val proxyApiKey by viewModel.proxyApiKey.collectAsStateWithLifecycle()
|
||||||
val proxyError by viewModel.proxyError.collectAsStateWithLifecycle()
|
val proxyError by viewModel.proxyError.collectAsStateWithLifecycle()
|
||||||
|
val proxyDebug by viewModel.proxyDebug.collectAsStateWithLifecycle()
|
||||||
var showLanguageDialog by remember { mutableStateOf(false) }
|
var showLanguageDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
@ -168,7 +169,10 @@ fun SettingsScreen(
|
|||||||
running = proxyRunning,
|
running = proxyRunning,
|
||||||
apiKey = proxyApiKey,
|
apiKey = proxyApiKey,
|
||||||
error = proxyError,
|
error = proxyError,
|
||||||
onToggle = viewModel::setProxyEnabled
|
debug = proxyDebug,
|
||||||
|
onToggle = viewModel::setProxyEnabled,
|
||||||
|
onTestTrae = viewModel::testTraeStream,
|
||||||
|
onTestWorkBuddy = viewModel::testWorkBuddyStream
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
@ -198,7 +202,10 @@ private fun ProxyGatewayCard(
|
|||||||
running: Boolean,
|
running: Boolean,
|
||||||
apiKey: String,
|
apiKey: String,
|
||||||
error: String?,
|
error: String?,
|
||||||
onToggle: (Boolean) -> Unit
|
debug: String?,
|
||||||
|
onToggle: (Boolean) -> Unit,
|
||||||
|
onTestTrae: () -> Unit,
|
||||||
|
onTestWorkBuddy: () -> Unit
|
||||||
) {
|
) {
|
||||||
Card(
|
Card(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
@ -241,6 +248,22 @@ private fun ProxyGatewayCard(
|
|||||||
modifier = Modifier.padding(top = 2.dp)
|
modifier = Modifier.padding(top = 2.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Row(modifier = Modifier.padding(top = 4.dp)) {
|
||||||
|
TextButton(onClick = onTestTrae) {
|
||||||
|
Text("测试 Trae 流")
|
||||||
|
}
|
||||||
|
TextButton(onClick = onTestWorkBuddy) {
|
||||||
|
Text("测试 WorkBuddy 流")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (debug != null) {
|
||||||
|
Text(
|
||||||
|
text = debug,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = InkMuted,
|
||||||
|
modifier = Modifier.padding(top = 4.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Switch(
|
Switch(
|
||||||
checked = running,
|
checked = running,
|
||||||
|
|||||||
@ -3,7 +3,11 @@ package com.rainy.token.ui.settings
|
|||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.rainy.token.data.proxy.LocalProxyServer
|
import com.rainy.token.data.proxy.LocalProxyServer
|
||||||
|
import com.rainy.token.data.proxy.ProviderRegion
|
||||||
|
import com.rainy.token.data.proxy.ProxyUpstreamStream
|
||||||
import com.rainy.token.data.proxy.ProxyServerConfig
|
import com.rainy.token.data.proxy.ProxyServerConfig
|
||||||
|
import com.rainy.token.data.proxy.TraeChatProxy
|
||||||
|
import com.rainy.token.data.proxy.WorkBuddyChatProxy
|
||||||
import com.rainy.token.data.repository.CredentialRepository
|
import com.rainy.token.data.repository.CredentialRepository
|
||||||
import com.rainy.token.domain.model.CredentialStatus
|
import com.rainy.token.domain.model.CredentialStatus
|
||||||
import com.rainy.token.domain.service.ServiceType
|
import com.rainy.token.domain.service.ServiceType
|
||||||
@ -12,7 +16,9 @@ import java.util.UUID
|
|||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ -23,7 +29,9 @@ import javax.inject.Inject
|
|||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class SettingsViewModel @Inject constructor(
|
class SettingsViewModel @Inject constructor(
|
||||||
private val credentialRepository: CredentialRepository,
|
private val credentialRepository: CredentialRepository,
|
||||||
private val localProxyServer: LocalProxyServer
|
private val localProxyServer: LocalProxyServer,
|
||||||
|
private val traeChatProxy: TraeChatProxy,
|
||||||
|
private val workBuddyChatProxy: WorkBuddyChatProxy
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
private val _uiState = MutableStateFlow(SettingsUiState())
|
private val _uiState = MutableStateFlow(SettingsUiState())
|
||||||
@ -51,6 +59,72 @@ class SettingsViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 代理调试输出(原始 SSE 帧)。 */
|
||||||
|
private val _proxyDebug = MutableStateFlow<String?>(null)
|
||||||
|
val proxyDebug: StateFlow<String?> = _proxyDebug.asStateFlow()
|
||||||
|
|
||||||
|
/** 用 Trae CN 默认账号发一条 stream=true 测试,抓上游原始 SSE。 */
|
||||||
|
fun testTraeStream() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
_proxyDebug.value = "正在请求 Trae CN 流..."
|
||||||
|
val body = """{"model":"glm-5.2","stream":true,"messages":[{"role":"user","content":"只回复OK两个字"}],"max_tokens":100}"""
|
||||||
|
try {
|
||||||
|
val stream = traeChatProxy.openStreamingChat(body, null, ProviderRegion.CN)
|
||||||
|
if (stream == null) {
|
||||||
|
_proxyDebug.value = "Trae CN 未配置或未登录"
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
_proxyDebug.value = readStreamSample(stream, "Trae CN")
|
||||||
|
} catch (e: kotlinx.coroutines.CancellationException) {
|
||||||
|
throw e
|
||||||
|
} catch (e: Exception) {
|
||||||
|
_proxyDebug.value = "Trae CN 异常:" + (e.message ?: "未知错误")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 用 WorkBuddy CN 默认账号发一条 stream=true 测试,抓上游原始 SSE。 */
|
||||||
|
fun testWorkBuddyStream() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
_proxyDebug.value = "正在请求 WorkBuddy CN 流..."
|
||||||
|
val body = """{"model":"glm-5.2","stream":true,"messages":[{"role":"user","content":"只回复OK两个字"}],"max_tokens":100}"""
|
||||||
|
try {
|
||||||
|
val stream = workBuddyChatProxy.openStreamingChat(body, null, ProviderRegion.CN)
|
||||||
|
if (stream == null) {
|
||||||
|
_proxyDebug.value = "WorkBuddy CN 未配置或未登录"
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
_proxyDebug.value = readStreamSample(stream, "WorkBuddy CN")
|
||||||
|
} catch (e: kotlinx.coroutines.CancellationException) {
|
||||||
|
throw e
|
||||||
|
} catch (e: Exception) {
|
||||||
|
_proxyDebug.value = "WorkBuddy CN 异常:" + (e.message ?: "未知错误")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun readStreamSample(
|
||||||
|
stream: ProxyUpstreamStream,
|
||||||
|
label: String,
|
||||||
|
): String = withContext(Dispatchers.IO) {
|
||||||
|
val sb = StringBuilder()
|
||||||
|
sb.append(label).append(" HTTP ").append(stream.status)
|
||||||
|
sb.append(" Content-Type=").append(stream.contentType).append("\n")
|
||||||
|
try {
|
||||||
|
val reader = stream.input.bufferedReader()
|
||||||
|
repeat(80) {
|
||||||
|
val line = reader.readLine() ?: return@repeat
|
||||||
|
sb.append(line).append("\n")
|
||||||
|
if (line.trim() == "data: [DONE]") return@repeat
|
||||||
|
if (sb.length > 6000) return@repeat
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
stream.close()
|
||||||
|
}
|
||||||
|
sb.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun generateProxyKey(): String =
|
private fun generateProxyKey(): String =
|
||||||
"rainy-" + UUID.randomUUID().toString().replace("-", "").take(16)
|
"rainy-" + UUID.randomUUID().toString().replace("-", "").take(16)
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user