@@ -37,6 +37,7 @@ import androidx.compose.foundation.rememberScrollState
3737import androidx.compose.foundation.shape.CircleShape
3838import androidx.compose.foundation.shape.RoundedCornerShape
3939import androidx.compose.foundation.text.BasicTextField
40+ import androidx.compose.foundation.text.KeyboardOptions
4041import androidx.compose.foundation.text.selection.SelectionContainer
4142import androidx.compose.foundation.verticalScroll
4243import androidx.compose.material.icons.Icons
@@ -45,6 +46,7 @@ import androidx.compose.material.icons.filled.AddCircle
4546import androidx.compose.material.icons.filled.Api
4647import androidx.compose.material.icons.filled.ArrowUpward
4748import androidx.compose.material.icons.filled.Edit
49+ import androidx.compose.material.icons.filled.ExpandMore
4850import androidx.compose.material.icons.filled.MoreVert
4951import androidx.compose.material.icons.filled.Refresh
5052import androidx.compose.material.icons.filled.Settings
@@ -82,6 +84,7 @@ import androidx.compose.runtime.setValue
8284import androidx.compose.ui.Alignment
8385import androidx.compose.ui.Modifier
8486import androidx.compose.ui.draw.clip
87+ import androidx.compose.ui.draw.rotate
8588import androidx.compose.ui.focus.FocusRequester
8689import androidx.compose.ui.focus.focusRequester
8790import androidx.compose.ui.graphics.Color
@@ -95,7 +98,9 @@ import androidx.compose.ui.res.vectorResource
9598import androidx.compose.ui.text.TextRange
9699import androidx.compose.ui.text.TextStyle
97100import androidx.compose.ui.text.font.FontWeight
101+ import androidx.compose.ui.text.input.KeyboardType
98102import androidx.compose.ui.text.input.TextFieldValue
103+ import androidx.compose.ui.unit.DpOffset
99104import androidx.compose.ui.unit.dp
100105import androidx.compose.ui.unit.sp
101106import androidx.core.content.edit
@@ -124,7 +129,6 @@ import kotlin.math.round
124129
125130@Keep
126131data class Message (val role : String , val content : String )
127- @SuppressLint(" MutableCollectionMutableState" )
128132class ChatViewModel : ViewModel () {
129133 var msgs = mutableStateListOf<Message >()
130134 var sessions = mutableStateListOf<String >()
@@ -152,26 +156,28 @@ class ChatViewModel : ViewModel() {
152156
153157
154158 fun updateAIMessage (content : String ) {
159+ val newContent= content.replaceFirst(Regex (" ^\n {0,2}" ), " " )
155160 viewModelScope.launch(Dispatchers .Main ) {
156161 if (msgs.isEmpty() || msgs.last().role != " assistant" ) {
157- msgs.add(Message (" assistant" , content ))
162+ msgs.add(Message (" assistant" , newContent ))
158163 } else {
159164 val lastMsg = msgs.last()
160- msgs[msgs.lastIndex] = lastMsg.copy(content = lastMsg.content + content )
165+ msgs[msgs.lastIndex] = lastMsg.copy(content = lastMsg.content + newContent )
161166 }
162167 }
163168 }
164169
165170 fun updateAIReasoningMessage (content : String ) {
171+ val newContent= content.replaceFirst(Regex (" ^\n {0,2}" ), " " )
166172 viewModelScope.launch(Dispatchers .Main ) {
167173 if (msgs.last().role == " assistant" && msgs.last().content == " " ) {
168174 msgs.removeAt(msgs.size - 1 )
169175 }
170176 if (msgs.isEmpty() || msgs.last().role != " assistant_reasoning" ) {
171- msgs.add(Message (" assistant_reasoning" , content ))
177+ msgs.add(Message (" assistant_reasoning" , newContent ))
172178 } else {
173179 val lastMsg = msgs.last()
174- msgs[msgs.lastIndex] = lastMsg.copy(content = lastMsg.content + content )
180+ msgs[msgs.lastIndex] = lastMsg.copy(content = lastMsg.content + newContent )
175181 }
176182 }
177183 }
@@ -194,7 +200,7 @@ class ChatViewModel : ViewModel() {
194200
195201 fun maxTokensIsNumber (): Boolean {
196202 try {
197- maxTokens.toInt ()
203+ maxTokens.toLong ()
198204 return true
199205 } catch (_: Exception ) {
200206 return false
@@ -232,6 +238,8 @@ fun MainUI(viewModel: ChatViewModel) {
232238 var currentConfig = currentConfigPref.getString(" currentConfig" , " " )
233239 val vibrator = context.getSystemService(Context .VIBRATOR_SERVICE ) as Vibrator
234240 val scope = rememberCoroutineScope()
241+ val assistantThinkingClosed = remember {mutableStateListOf<Int >()}
242+ val settingsPref = context.getSharedPreferences(" settings" , Context .MODE_PRIVATE )
235243
236244 fun save (){
237245 if (viewModel.msgs.size > if (systemPrompt.isNotEmpty()){1 }else {0 }) {
@@ -245,23 +253,25 @@ fun MainUI(viewModel: ChatViewModel) {
245253 }
246254
247255 LaunchedEffect (Unit ) {
248- viewModel.sessions.clear()
256+ viewModel.temperature= settingsPref.getInt(" temperature" ,- 1 )
257+ viewModel.maxTokens= settingsPref.getString(" maxTokens" ," " )!!
258+ viewModel.parseMd= settingsPref.getBoolean(" parseMd" ,true )
249259 viewModel.sessions.addAll(Gson ().fromJson(sessionsPref.getString(" sessions" , " []" )!! ,
250260 object : TypeToken <List <String >>() {}.type))
251- val settingsPref = context.getSharedPreferences(" settings " , Context .MODE_PRIVATE )
261+ val assistantsPref = context.getSharedPreferences(" assistants " , Context .MODE_PRIVATE )
252262 val configsList = mutableListOf<String >()
253263 currentConfig = currentConfigPref.getString(" currentConfig" , " " )!!
254- for (i in settingsPref .all) {
264+ for (i in assistantsPref .all) {
255265 configsList.add(i.key)
256266 }
257- val settings = JsonParser .parseString(
258- settingsPref .getString(
267+ val assistants = JsonParser .parseString(
268+ assistantsPref .getString(
259269 currentConfig,
260270 " {'apiUrl':'','apiKey':'','model':'','systemPrompt':''}"
261271 )
262272 ).asJsonObject
263- api_url = settings .get(" apiUrl" ).asString;api_key = settings .get(" apiKey" ).asString;model =
264- settings .get(" model" ).asString;systemPrompt = settings .get(" systemPrompt" ).asString
273+ api_url = assistants .get(" apiUrl" ).asString;api_key = assistants .get(" apiKey" ).asString;model =
274+ assistants .get(" model" ).asString;systemPrompt = assistants .get(" systemPrompt" ).asString
265275 if (viewModel.sessions.isEmpty()) {
266276 viewModel.sessions.add(" 新对话" + System .currentTimeMillis().toString())
267277 }
@@ -326,7 +336,7 @@ fun MainUI(viewModel: ChatViewModel) {
326336 currentConfig
327337 } else {
328338 " 请先配置 AI API"
329- }, vibrator
339+ }, vibrator,settingsPref
330340 )
331341 },
332342 bottomBar = {
@@ -401,7 +411,7 @@ fun MainUI(viewModel: ChatViewModel) {
401411 } else {
402412 ImageVector .vectorResource(R .drawable.ic_rectangle)
403413 },
404- viewModel, vibrator
414+ viewModel, vibrator,settingsPref
405415 )
406416 }
407417 ) { innerPadding ->
@@ -470,11 +480,34 @@ fun MainUI(viewModel: ChatViewModel) {
470480 }
471481 }
472482
473- " assistant_reasoning" -> Row {
474- Icon (
475- Icons .Default .Api ,
476- " "
477- );Text (msg.content, color = Color .Gray )
483+ " assistant_reasoning" -> {
484+ var expanded = false
485+ Row (verticalAlignment = Alignment .CenterVertically ) {
486+ Icon (
487+ ImageVector .vectorResource(R .drawable.ic_deep_think),
488+ null
489+ )
490+ if (i in assistantThinkingClosed) {
491+ IconButton ({ assistantThinkingClosed.remove(i) }) {
492+ Icon (Icons .Default .ExpandMore , null )
493+ }
494+ } else {
495+ expanded = true
496+ IconButton ({ assistantThinkingClosed.add(i) }) {
497+ Icon (
498+ Icons .Default .ExpandMore ,
499+ null ,
500+ Modifier .rotate(180f )
501+ )
502+ }
503+ }
504+ }
505+ if (expanded) {
506+ Row {
507+ Spacer (Modifier .size(22 .dp))
508+ Text (msg.content, color = Color .Gray )
509+ }
510+ }
478511 }
479512
480513 " user" -> {
@@ -522,8 +555,8 @@ fun MainUI(viewModel: ChatViewModel) {
522555 else -> Row {
523556 Icon (
524557 Icons .Default .Settings ,
525- " "
526- );Text (msg.content + " \n " )
558+ null
559+ );Text (msg.content + " \n " ,color = MaterialTheme .colorScheme.primary )
527560 }
528561 }
529562 }
@@ -541,7 +574,8 @@ fun MessageInputBar(
541574 onSend : (String ) -> Unit ,
542575 sendImg : ImageVector ,
543576 viewModel : ChatViewModel ,
544- vibrator : Vibrator
577+ vibrator : Vibrator ,
578+ settingsPref : SharedPreferences
545579) {
546580 var temperatureExpanded by remember { mutableStateOf(false ) }
547581 var maxTokensExpanded by remember { mutableStateOf(false ) }
@@ -641,19 +675,17 @@ fun MessageInputBar(
641675 )
642676 }
643677 }
644- if (maxTokensExpanded) {
645- OutlinedTextField (value = viewModel.maxTokens, onValueChange = {
646- try {
647- viewModel.maxTokens = it
648- } catch (_: Exception ) {
649- }
650- })
651- }
652678 }
653679 DropdownMenu ( // 温度
654680 expanded = temperatureExpanded,
655- onDismissRequest = { temperatureExpanded = false },
656- Modifier .width(200 .dp)
681+ onDismissRequest = {
682+ temperatureExpanded = false
683+ settingsPref.edit {
684+ putInt(" temperature" ,viewModel.temperature)
685+ }
686+ },
687+ Modifier .width(200 .dp),
688+ offset = DpOffset (10 .dp,0 .dp)
657689 ) {
658690 Slider (value = viewModel.temperature.toFloat(), onValueChange = {
659691 val tmp = round(it).toInt()
@@ -663,6 +695,29 @@ fun MessageInputBar(
663695 }
664696 }, valueRange = - 1f .. 20f , steps = 20 )
665697 }
698+ DropdownMenu ( // 最大 token 数
699+ expanded = maxTokensExpanded,
700+ onDismissRequest = {
701+ maxTokensExpanded = false
702+ settingsPref.edit {
703+ putString(" maxTokens" ,viewModel.maxTokens)
704+ }
705+ },
706+ Modifier .width(150 .dp),
707+ offset = DpOffset (100 .dp,0 .dp)
708+ ) {
709+ OutlinedTextField (
710+ value = viewModel.maxTokens, onValueChange = {
711+ try {
712+ viewModel.maxTokens = it
713+ } catch (_: Exception ) {
714+ }
715+ },
716+ keyboardOptions = KeyboardOptions (keyboardType = KeyboardType .Number ),
717+ isError = ! (viewModel.maxTokensIsNumber() || viewModel.maxTokens.isEmpty()),
718+ singleLine = true
719+ )
720+ }
666721 }
667722}
668723
@@ -966,7 +1021,7 @@ private fun HistoryDrawer(viewModel: ChatViewModel, context: Context, sessionsPr
9661021
9671022@OptIn(ExperimentalMaterial3Api ::class )
9681023@Composable
969- private fun TopBar (viewModel : ChatViewModel ,context : Context ,drawerState : DrawerState ,currentConfig : String ,vibrator : Vibrator ) {
1024+ private fun TopBar (viewModel : ChatViewModel ,context : Context ,drawerState : DrawerState ,currentConfig : String ,vibrator : Vibrator , settingsPref : SharedPreferences ) {
9701025 val scope = rememberCoroutineScope()
9711026 var showMenu by remember { mutableStateOf(false ) }
9721027 TopAppBar (
@@ -1023,12 +1078,18 @@ private fun TopBar(viewModel: ChatViewModel,context: Context,drawerState: Drawer
10231078 Checkbox (viewModel.parseMd, onCheckedChange = {
10241079 clickVibrate(vibrator)
10251080 viewModel.parseMd = ! viewModel.parseMd
1081+ settingsPref.edit {
1082+ putBoolean(" parseMd" ,viewModel.parseMd)
1083+ }
10261084 })
10271085 }
10281086 },
10291087 onClick = {
10301088 clickVibrate(vibrator)
10311089 viewModel.parseMd = ! viewModel.parseMd
1090+ settingsPref.edit {
1091+ putBoolean(" parseMd" ,viewModel.parseMd)
1092+ }
10321093 })
10331094 }
10341095 }
0 commit comments