App.kt
5.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
package org.example.project
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeContentPadding
import androidx.compose.material3.Button
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.ui.tooling.preview.Preview
import kotlinproject.composeapp.generated.resources.Res
import kotlinproject.composeapp.generated.resources.compose_multiplatform
import kotlinx.datetime.Clock
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toLocalDateTime
import io.ktor.client.*
import io.ktor.client.call.body
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.coroutines.*
import kotlinx.serialization.json.Json
// 数据类定义
import kotlinx.serialization.Serializable
import kotlinx.coroutines.flow.*
@Composable
@Preview
fun App() {
MaterialTheme {
var showContent by remember { mutableStateOf(false) }
var fullResponse by remember { mutableStateOf<String?>(null) } // 完整回复
var isLoading by remember { mutableStateOf(false) }
val greeting = remember { Greeting().greet() }
Column(
modifier = Modifier
.safeContentPadding()
.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Button(onClick = { showContent = !showContent }) {
Text("Click me!")
}
AnimatedVisibility(showContent) {
Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
Image(painterResource(Res.drawable.compose_multiplatform), null)
Text("Compose: $greeting")
}
}
Button(
onClick = {
isLoading = true
fullResponse = null
CoroutineScope(Dispatchers.Main).launch {
try {
val result = ChatApi.getChatResponse()
fullResponse = result // 先设置完整文本
} finally {
isLoading = false
}
}
},
modifier = Modifier.padding(16.dp)
) {
Text("Ask AI")
}
// 显示打字机动画
if (isLoading) {
Text("Typing...")
} else if (fullResponse != null) {
TypingText(fullResponse!!)
}
}
}
}
@Composable
fun TypingText(fullText: String) {
// 使用 Flow 将字符串转为逐字发射
val typingFlow = remember(fullText) {
flow {
var current = ""
for (char in fullText) {
delay(50) // 控制打字速度
current += char
emit(current)
}
}
}
// 使用 Flow 收集到状态
val typedText by typingFlow.collectAsState(initial = "")
Text(
text = "AI Response: $typedText",
modifier = Modifier.padding(16.dp),
textAlign = TextAlign.Center
)
}
// 替换为你自己的 API Key
private const val ARK_API_KEY = "2479cc18-02a6-4e8a-8262-e468573f5345"
@Serializable
data class ChatRequest(
val model: String,
val messages: List<Message>,
)
@Serializable
data class Message(
val role: String,
val content: String,
)
@Serializable
data class ChatResponse(
val choices: List<Choice>,
)
@Serializable
data class Choice(
val message: Message,
)
object ChatApi {
private val client = HttpClient {
install(ContentNegotiation) {
json(Json {
prettyPrint = true
isLenient = true
ignoreUnknownKeys = true
})
}
}
suspend fun getChatResponse(): String = coroutineScope {
try {
val response: HttpResponse = client.post("https://ark.cn-beijing.volces.com/api/v3/chat/completions") {
headers {
contentType(ContentType.Application.Json)
bearerAuth(ARK_API_KEY)
}
setBody(Json.encodeToString(ChatRequest.serializer(), ChatRequest(
model = "doubao-seed-1.6-250615",
messages = listOf(Message("user", "你好"))
)))
}
val result = response.body<ChatResponse>()
return@coroutineScope result.choices.first().message.content
} catch (e: Exception) {
e.printStackTrace()
return@coroutineScope "Error: ${e.message}"
}
}
}
fun String.asTypingFlow(delayMillis: Long = 100): Flow<String> = flow {
var currentText = ""
for (char in this@asTypingFlow) {
currentText += char
delay(delayMillis)
emit(currentText)
}
}