Skip to content

Commit 90daad1

Browse files
committed
files committed
1 parent 51e79cb commit 90daad1

2 files changed

Lines changed: 252 additions & 258 deletions

File tree

app/src/main/java/com/xjyzs/aiapi/MainActivity.kt

Lines changed: 7 additions & 258 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package com.xjyzs.aiapi
22

33
import android.annotation.SuppressLint
4-
import android.content.ClipboardManager
54
import android.content.Context
65
import android.content.Intent
76
import android.content.SharedPreferences
@@ -33,7 +32,6 @@ import androidx.compose.foundation.lazy.LazyColumn
3332
import androidx.compose.foundation.lazy.itemsIndexed
3433
import androidx.compose.foundation.rememberScrollState
3534
import androidx.compose.foundation.shape.CircleShape
36-
import androidx.compose.foundation.shape.RoundedCornerShape
3735
import androidx.compose.foundation.text.selection.SelectionContainer
3836
import androidx.compose.foundation.verticalScroll
3937
import androidx.compose.material.icons.Icons
@@ -86,17 +84,10 @@ import androidx.compose.ui.layout.onSizeChanged
8684
import androidx.compose.ui.platform.LocalContext
8785
import androidx.compose.ui.res.stringResource
8886
import androidx.compose.ui.res.vectorResource
89-
import androidx.compose.ui.text.AnnotatedString
90-
import androidx.compose.ui.text.SpanStyle
9187
import androidx.compose.ui.text.TextRange
9288
import androidx.compose.ui.text.TextStyle
93-
import androidx.compose.ui.text.buildAnnotatedString
94-
import androidx.compose.ui.text.font.FontFamily
95-
import androidx.compose.ui.text.font.FontStyle
9689
import androidx.compose.ui.text.font.FontWeight
9790
import androidx.compose.ui.text.input.TextFieldValue
98-
import androidx.compose.ui.text.style.TextDecoration
99-
import androidx.compose.ui.unit.Dp
10091
import androidx.compose.ui.unit.dp
10192
import androidx.compose.ui.unit.sp
10293
import androidx.lifecycle.Lifecycle
@@ -109,7 +100,9 @@ import com.google.gson.Gson
109100
import com.google.gson.JsonParser
110101
import com.google.gson.reflect.TypeToken
111102
import com.xjyzs.aiapi.ui.theme.AIAPITheme
103+
import com.xjyzs.aiapi.utils.InlineMarkdown
112104
import com.xjyzs.aiapi.utils.clickVibrate
105+
import com.xjyzs.aiapi.utils.hideKeyboard
113106
import kotlinx.coroutines.Dispatchers
114107
import kotlinx.coroutines.delay
115108
import kotlinx.coroutines.launch
@@ -121,11 +114,6 @@ import okhttp3.RequestBody.Companion.toRequestBody
121114
import java.io.BufferedReader
122115
import java.io.InputStreamReader
123116
import java.util.concurrent.TimeUnit
124-
import androidx.compose.foundation.text.KeyboardActions
125-
import androidx.compose.foundation.text.KeyboardOptions
126-
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
127-
import androidx.compose.ui.platform.SoftwareKeyboardController
128-
import androidx.compose.ui.text.input.ImeAction
129117

130118
@Keep
131119
data class Message(val role: String, val content: String)
@@ -278,7 +266,7 @@ fun MainUI(viewModel: ChatViewModel) {
278266
viewModel.addSystemMessage(systemPrompt)
279267
}
280268
delay(100)
281-
scrollState.animateScrollTo(contentHeight,tween(500))
269+
scrollState.animateScrollTo(contentHeight,tween(300))
282270
}
283271
LaunchedEffect(if (viewModel.msgs.isNotEmpty())viewModel.msgs.last().content.length else 0) {
284272
if (contentHeight - scrollState.value - containerHeight < 10) {
@@ -341,7 +329,7 @@ fun MainUI(viewModel: ChatViewModel) {
341329
}
342330
viewModel.currentSession=newName
343331
}
344-
LocalSoftwareKeyboardController.current!!.hide()
332+
context.hideKeyboard()
345333
viewModel.addUserMessage(it)
346334
send(context, viewModel)
347335
viewModel.inputMsg = ""
@@ -723,7 +711,7 @@ private fun HistoryDrawer(viewModel: ChatViewModel, context: Context, sessionsPr
723711
scope.launch {
724712
scrollState.scrollTo(0)
725713
delay(100)
726-
scrollState.animateScrollTo(contentHeight, tween(500))
714+
scrollState.animateScrollTo(contentHeight, tween(300))
727715
}
728716
}
729717
) { Text("删除") }
@@ -793,7 +781,7 @@ private fun HistoryDrawer(viewModel: ChatViewModel, context: Context, sessionsPr
793781
scope.launch {
794782
scrollState.scrollTo(0)
795783
delay(100)
796-
scrollState.animateScrollTo(contentHeight, tween(500))
784+
scrollState.animateScrollTo(contentHeight, tween(300))
797785
}
798786
}
799787
},
@@ -905,243 +893,4 @@ private fun createNewSession(viewModel: ChatViewModel,context: Context) {
905893
viewModel.currentSession = "新对话" + System.currentTimeMillis().toString()
906894
viewModel.sessions.add(viewModel.currentSession)
907895
}
908-
}
909-
910-
class MarkdownParser {
911-
// 代码块
912-
private val codeBlockRegex = """```(.*?)\n([\s\S]*?)\s*```""".toRegex()
913-
// 块级元素正则表达式
914-
private val headerRegex = """^(#{1,6})\s*(.*)""".toRegex()
915-
private val unorderedListRegex = """^[*+-]\s+(.*)""".toRegex()
916-
private val orderedListRegex = """^(\d+)\.\s+(.*)""".toRegex()
917-
918-
// 行内样式正则表达式
919-
private val boldRegex = """\*\*(.*?)\*\*""".toRegex()
920-
private val italicRegex = """\*(.*?)\*""".toRegex()
921-
private val codeRegex = """`(.*?)`""".toRegex()
922-
private val deleteRegex = """~~(.*?)~~""".toRegex()
923-
924-
fun parse(content: String,context: Context): List<@Composable () -> Unit> {
925-
val blocks = mutableListOf<@Composable () -> Unit>()
926-
927-
var remainingText = content
928-
929-
while (true) {
930-
val codeBlockMatch = codeBlockRegex.find(remainingText)
931-
932-
val precedingText = if (codeBlockMatch != null) {
933-
remainingText.substring(0, codeBlockMatch.range.first)
934-
} else {
935-
remainingText
936-
}
937-
parseRegularText(precedingText, blocks)
938-
if (codeBlockMatch == null) break
939-
val (language, code) = codeBlockMatch.destructured
940-
blocks.add { CodeBlock(code, language,context=context) }
941-
942-
remainingText = remainingText.substring(codeBlockMatch.range.last + 1)
943-
}
944-
945-
return blocks
946-
}
947-
948-
private fun parseRegularText(text: String, blocks: MutableList<@Composable () -> Unit>) {
949-
text.split("\n").forEach { line ->
950-
when {
951-
line.matches(headerRegex) -> parseHeader(line)?.let { blocks.add(it) }
952-
line.matches(unorderedListRegex) -> parseListItem(line)?.let { blocks.add(it) }
953-
line.matches(orderedListRegex) -> parseOrderedListItem(line)?.let {
954-
blocks.add(it)
955-
}
956-
957-
else -> blocks.add { ParseInlineText(line) }
958-
}
959-
}
960-
}
961-
962-
@Composable
963-
fun CodeBlock(
964-
code: String,
965-
language: String = "",
966-
modifier: Modifier = Modifier,
967-
backgroundColor: Color = MaterialTheme.colorScheme.surfaceContainerHighest,
968-
cornerRadius: Dp = 8.dp,
969-
context: Context
970-
) {
971-
val typography = MaterialTheme.typography
972-
973-
Surface(
974-
modifier = modifier
975-
.fillMaxWidth()
976-
.padding(vertical = 8.dp)
977-
.clickable {
978-
(context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager).setText(
979-
AnnotatedString(code)
980-
)
981-
},
982-
shape = RoundedCornerShape(cornerRadius),
983-
color = backgroundColor
984-
) {
985-
Column {
986-
if (language.isNotBlank()) {
987-
Text(
988-
text = language,
989-
style = typography.labelSmall,
990-
modifier = Modifier
991-
.padding(start = 12.dp, top = 8.dp, end = 12.dp)
992-
.align(Alignment.Start),
993-
color = MaterialTheme.colorScheme.onSurfaceVariant
994-
)
995-
}
996-
Text(
997-
text = highlightSyntax(code, language),
998-
style = typography.bodyMedium.copy(
999-
fontFamily = FontFamily.Monospace,
1000-
lineHeight = 20.sp
1001-
),
1002-
modifier = Modifier
1003-
.padding(12.dp),
1004-
color = MaterialTheme.colorScheme.onSurface
1005-
)
1006-
}
1007-
}
1008-
}
1009-
1010-
fun highlightSyntax(code: String, language: String): AnnotatedString {
1011-
return buildAnnotatedString {
1012-
append(code)
1013-
1014-
val lc = language.lowercase()
1015-
if (lc == "kotlin" || lc == "java") {
1016-
val rules = listOf(
1017-
Rule("""val|var|class""".toRegex(), SpanStyle(color = Color(0xFFFF9800))),
1018-
)
1019-
rules.forEach { rule ->
1020-
rule.regex.findAll(code).forEach { match ->
1021-
addStyle(rule.style, match.range.first, match.range.last + 1)
1022-
}
1023-
}
1024-
}
1025-
else if (lc == "python") {
1026-
val rules = listOf(
1027-
Rule("""print""".toRegex(), SpanStyle(color = Color(0xFF16659B)))
1028-
)
1029-
rules.forEach { rule ->
1030-
rule.regex.findAll(code).forEach { match ->
1031-
addStyle(rule.style, match.range.first, match.range.last + 1)
1032-
}
1033-
}
1034-
}
1035-
val rules = listOf(
1036-
Rule("""\b\d+(\.\d+)?\b""".toRegex(), SpanStyle(color = Color(0xFF16659B))), // 数字
1037-
Rule(
1038-
"""return |def |fun |try|except |except:|finally|with | as |if |else |import """.toRegex(),
1039-
SpanStyle(color = Color(0xFFFF9800))
1040-
),
1041-
Rule("""@.*?\n""".toRegex(), SpanStyle(color = Color(0xFFFFEB3B))),
1042-
Rule("""".*?"|'.*?'""".toRegex(), SpanStyle(color = Color(0xFF067D17))), // 字符串
1043-
Rule("""//.*|#.*""".toRegex(), SpanStyle(color = Color(0xFF9E9E9E))), // 注释
1044-
Rule("""/\*.*?\*/""".toRegex(RegexOption.DOT_MATCHES_ALL),SpanStyle(color = Color(0xFF9E9E9E))), // 注释
1045-
)
1046-
rules.forEach { rule ->
1047-
rule.regex.findAll(code).forEach { match ->
1048-
addStyle(rule.style, match.range.first, match.range.last + 1)
1049-
}
1050-
}
1051-
}
1052-
}
1053-
1054-
private data class Rule(
1055-
val regex: Regex,
1056-
val style: SpanStyle
1057-
)
1058-
1059-
private fun parseHeader(line: String): (@Composable () -> Unit)? {
1060-
val (hashes, text) = headerRegex.find(line)?.destructured ?: return null
1061-
val level = hashes.length.coerceIn(1, 6)
1062-
return {
1063-
Text(
1064-
text = parseInlineStyles(text),
1065-
fontSize = when (level) {
1066-
1 -> 24.sp
1067-
else -> (24-level * 1).sp
1068-
},
1069-
fontWeight = FontWeight.Bold
1070-
)
1071-
}
1072-
}
1073-
1074-
private fun parseListItem(line: String): (@Composable () -> Unit)? {
1075-
val (content) = unorderedListRegex.find(line)?.destructured ?: return null
1076-
return {
1077-
Row(verticalAlignment = Alignment.Top) {
1078-
Text("", fontSize = 16.sp)
1079-
Text(parseInlineStyles(content))
1080-
}
1081-
}
1082-
}
1083-
1084-
private fun parseOrderedListItem(line: String): (@Composable () -> Unit)? {
1085-
val (num, content) = orderedListRegex.find(line)?.destructured ?: return null
1086-
return {
1087-
Row(verticalAlignment = Alignment.Top) {
1088-
Text("$num. ", fontSize = 16.sp)
1089-
Text(parseInlineStyles(content))
1090-
}
1091-
}
1092-
}
1093-
1094-
@Composable
1095-
private fun ParseInlineText(text: String) {
1096-
Text(parseInlineStyles(text))
1097-
}
1098-
1099-
@Composable
1100-
private fun parseInlineStyles(text: String): AnnotatedString {
1101-
val annotatedString = buildAnnotatedString {
1102-
append(text)
1103-
1104-
// 处理粗体
1105-
boldRegex.findAll(text).forEach { result ->
1106-
addStyle(SpanStyle(fontWeight = FontWeight.Bold), result.range.first+1,result.range.last)
1107-
}
1108-
1109-
// 处理斜体
1110-
italicRegex.findAll(text).forEach { result ->
1111-
addStyle(SpanStyle(fontStyle = FontStyle.Italic), result.range.first+1,result.range.last)
1112-
}
1113-
1114-
// 处理删除线
1115-
deleteRegex.findAll(text).forEach { result ->
1116-
addStyle(SpanStyle(textDecoration = TextDecoration.LineThrough), result.range.first+1,result.range.last)
1117-
}
1118-
1119-
// 处理代码样式
1120-
codeRegex.findAll(text).forEach { result ->
1121-
addStyle(
1122-
SpanStyle(
1123-
background = MaterialTheme.colorScheme.surfaceContainerHighest,
1124-
fontFamily = FontFamily.Monospace
1125-
),
1126-
result.range.first+1,result.range.last
1127-
)
1128-
}
1129-
}
1130-
return annotatedString
1131-
}
1132-
}
1133-
1134-
@Composable
1135-
fun InlineMarkdown(content: String, modifier: Modifier = Modifier,context: Context) {
1136-
val parser = remember { MarkdownParser() }
1137-
val blocks = remember(content) { parser.parse(content, context) }
1138-
1139-
Column(modifier) {
1140-
blocks.forEach { block ->
1141-
Row {
1142-
block()
1143-
}
1144-
Text("\n", lineHeight = 0.sp)
1145-
}
1146-
}
1147-
}
896+
}

0 commit comments

Comments
 (0)