@@ -973,9 +973,14 @@ internal fun processCardHeader(
973973 else -> " "
974974 }
975975 return when {
976+ // Settled (reply text arrived, or a pure-tool/error/timeout round
977+ // ended): always the done summary.
976978 ! running -> " ✅ · $counts "
977- expanded && state.isNotEmpty() -> state
978- state.isNotEmpty() -> " $state · $counts "
979+ // Running + expanded: the steps are on screen, so the header shows
980+ // only the live state; fall back to the generic label when idle.
981+ expanded -> state.ifEmpty { " AI 过程" }
982+ // Running + collapsed: fixed label with ticking duration. The live
983+ // state stays out — that is what the expanded header is for.
979984 else -> " AI 过程 · $counts "
980985 }
981986}
@@ -999,11 +1004,13 @@ internal fun ProcessSummaryBar(
9991004 // listing every step (expanded). Replaces the centred hairline divider:
10001005 // the reference treats a process run as one tappable object sitting
10011006 // between reply paragraphs, not as a section rule.
1002- val labelColor = MaterialTheme .colorScheme.onSurface
1003- val muted = MaterialTheme .colorScheme.onSurfaceVariant
1004- val outline = MaterialTheme .colorScheme.outlineVariant
1005- val shape = RoundedCornerShape (22 .dp)
1006- val iconTint = if (hasFailure) MaterialTheme .colorScheme.error else muted
1007+ // [P-process-card] Reuse the thinking card's look so enabling folding is
1008+ // not a visual break: same blue, same 12dp radius, same 6%/15% tint and
1009+ // 0.5dp hairline, same 12×8 padding as ThinkingBlock. The old 22dp
1010+ // backgroundless capsule looked nothing like the blocks it replaced.
1011+ val thinkingBlue = Color (0xFF007AFF )
1012+ val shape = RoundedCornerShape (12 .dp)
1013+ val iconTint = if (hasFailure) MaterialTheme .colorScheme.error else thinkingBlue
10071014 // A running card ticks once a second so the duration walks. Bounded by
10081015 // the run: the moment it settles, running flips false and the loop ends.
10091016 var nowMs by remember { mutableStateOf(System .currentTimeMillis()) }
@@ -1025,16 +1032,17 @@ internal fun ProcessSummaryBar(
10251032 Column (
10261033 modifier = Modifier
10271034 .fillMaxWidth()
1028- .padding(horizontal = 4 .dp, vertical = 4 .dp)
1035+ .padding(vertical = 2.5 .dp)
1036+ .background(thinkingBlue.copy(alpha = 0.06f ), shape)
1037+ .border(0.5 .dp, thinkingBlue.copy(alpha = 0.15f ), shape)
10291038 .clip(shape)
1030- .border(1 .dp, outline, shape)
10311039 .clickable(onClick = onToggle)
1032- .padding(horizontal = 14 .dp, vertical = 11 .dp),
1040+ .padding(horizontal = 12 .dp, vertical = 8 .dp),
10331041 ) {
10341042 Row (verticalAlignment = Alignment .CenterVertically ) {
10351043 if (running) {
10361044 CircularProgressIndicator (
1037- modifier = Modifier .size(16 .dp),
1045+ modifier = Modifier .size(13 .dp),
10381046 strokeWidth = 1.5 .dp,
10391047 color = iconTint,
10401048 )
@@ -1043,32 +1051,31 @@ internal fun ProcessSummaryBar(
10431051 imageVector = if (hasFailure) Icons .Default .Error else Icons .Default .CheckCircle ,
10441052 contentDescription = null ,
10451053 tint = iconTint,
1046- modifier = Modifier .size(18 .dp),
1054+ modifier = Modifier .size(14 .dp),
10471055 )
10481056 }
1049- Spacer (modifier = Modifier .width(8 .dp))
1057+ Spacer (modifier = Modifier .width(6 .dp))
10501058 Text (
10511059 text = header,
1052- fontSize = 15 .sp,
1053- fontWeight = FontWeight .Medium ,
1054- color = labelColor ,
1060+ fontSize = 13 .sp,
1061+ fontWeight = FontWeight .SemiBold ,
1062+ color = if (hasFailure) MaterialTheme .colorScheme.error else thinkingBlue ,
10551063 modifier = Modifier .weight(1f ),
10561064 maxLines = 1 ,
10571065 overflow = TextOverflow .Ellipsis ,
10581066 )
10591067 Icon (
1060- imageVector = if (expanded) Icons .Default .KeyboardArrowDown
1061- else Icons .AutoMirrored .Filled .KeyboardArrowRight ,
1068+ imageVector = if (expanded) Icons .Default .KeyboardArrowUp else Icons .Default .KeyboardArrowDown ,
10621069 contentDescription = if (expanded) " Collapse" else " Expand" ,
1063- tint = muted ,
1064- modifier = Modifier .size(20 .dp),
1070+ tint = thinkingBlue.copy(alpha = 0.5f ) ,
1071+ modifier = Modifier .size(14 .dp),
10651072 )
10661073 }
10671074 if (expanded && steps.isNotEmpty()) {
10681075 HorizontalDivider (
1069- modifier = Modifier .padding(top = 10 .dp, bottom = 4 .dp),
1076+ modifier = Modifier .padding(top = 8 .dp, bottom = 2 .dp),
10701077 thickness = 0.5 .dp,
1071- color = outline ,
1078+ color = thinkingBlue.copy(alpha = 0.15f ) ,
10721079 )
10731080 steps.forEach { step ->
10741081 ProcessStepRow (step = step, onOpen = { onOpenStep(step.id) })
@@ -1085,6 +1092,7 @@ internal fun ProcessSummaryBar(
10851092@Composable
10861093private fun ProcessStepRow (step : ProcessStepRef , onOpen : () -> Unit ) {
10871094 val muted = MaterialTheme .colorScheme.onSurfaceVariant
1095+ val thinkingBlue = Color (0xFF007AFF )
10881096 val isThinking = step.kind == " thinking"
10891097 val duration = formatProcessDuration(step.durationMs)
10901098 val title = when {
@@ -1098,43 +1106,91 @@ private fun ProcessStepRow(step: ProcessStepRef, onOpen: () -> Unit) {
10981106 isThinking -> Icons .Default .Lightbulb
10991107 else -> Icons .Default .Search
11001108 }
1101- Row (
1102- verticalAlignment = Alignment .CenterVertically ,
1103- modifier = Modifier
1104- .fillMaxWidth()
1105- .then(if (isThinking) Modifier else Modifier .clickable(onClick = onOpen))
1106- .padding(vertical = 7 .dp),
1107- ) {
1108- Icon (icon, contentDescription = null , tint = muted, modifier = Modifier .size(16 .dp))
1109- Spacer (modifier = Modifier .width(10 .dp))
1110- Text (
1111- text = title,
1112- fontSize = 14 .sp,
1113- color = MaterialTheme .colorScheme.onSurface,
1114- modifier = Modifier .weight(1f ),
1115- maxLines = 1 ,
1116- overflow = TextOverflow .Ellipsis ,
1117- )
1118- if (! isThinking) {
1119- Box (
1120- modifier = Modifier
1121- .size(7 .dp)
1122- .background(Color (0xFF34C759 ), CircleShape ),
1109+ // [P-process-card] Thinking layer, independent of the card layer: a
1110+ // running thinking step auto-expands while its text streams, and settles
1111+ // 500ms after it finishes. A step the user collapsed by hand
1112+ // (userTouched) is never reopened by the card expanding, and never
1113+ // auto-collapsed over their choice — same rule as ThinkingBlock. State
1114+ // is keyed on step.id so one step's toggle never leaks to the next.
1115+ var stepExpanded by remember(step.id) { mutableStateOf(step.running) }
1116+ var userTouched by remember(step.id) { mutableStateOf(false ) }
1117+ LaunchedEffect (step.id, step.running) {
1118+ if (! step.running && ! userTouched) {
1119+ delay(500 )
1120+ stepExpanded = false
1121+ }
1122+ }
1123+ Column (modifier = Modifier .fillMaxWidth()) {
1124+ Row (
1125+ verticalAlignment = Alignment .CenterVertically ,
1126+ modifier = Modifier
1127+ .fillMaxWidth()
1128+ .clickable {
1129+ if (isThinking) {
1130+ userTouched = true
1131+ stepExpanded = ! stepExpanded
1132+ } else {
1133+ onOpen()
1134+ }
1135+ }
1136+ .padding(vertical = 7 .dp),
1137+ ) {
1138+ Icon (
1139+ icon,
1140+ contentDescription = null ,
1141+ tint = if (isThinking) thinkingBlue else muted,
1142+ modifier = Modifier .size(16 .dp),
11231143 )
1124- Spacer (modifier = Modifier .width(6 .dp))
1144+ Spacer (modifier = Modifier .width(10 .dp))
11251145 Text (
1126- text = stringResource(R .string.chat_process_step_view),
1127- fontSize = 13 .sp,
1128- color = Color (0xFF8E8EFF ),
1146+ text = title,
1147+ fontSize = 14 .sp,
1148+ color = MaterialTheme .colorScheme.onSurface,
1149+ modifier = Modifier .weight(1f ),
1150+ maxLines = 1 ,
1151+ overflow = TextOverflow .Ellipsis ,
1152+ )
1153+ if (! isThinking) {
1154+ Box (
1155+ modifier = Modifier
1156+ .size(7 .dp)
1157+ .background(Color (0xFF34C759 ), CircleShape ),
1158+ )
1159+ Spacer (modifier = Modifier .width(6 .dp))
1160+ Text (
1161+ text = stringResource(R .string.chat_process_step_view),
1162+ fontSize = 13 .sp,
1163+ color = Color (0xFF8E8EFF ),
1164+ )
1165+ Spacer (modifier = Modifier .width(2 .dp))
1166+ }
1167+ Icon (
1168+ imageVector = if (isThinking) {
1169+ if (stepExpanded) Icons .Default .KeyboardArrowUp else Icons .Default .KeyboardArrowDown
1170+ } else {
1171+ Icons .AutoMirrored .Filled .KeyboardArrowRight
1172+ },
1173+ contentDescription = null ,
1174+ tint = if (isThinking) thinkingBlue.copy(alpha = 0.5f ) else muted,
1175+ modifier = Modifier .size(16 .dp),
11291176 )
1130- Spacer (modifier = Modifier .width(2 .dp))
11311177 }
1132- Icon (
1133- Icons .AutoMirrored .Filled .KeyboardArrowRight ,
1134- contentDescription = null ,
1135- tint = muted,
1136- modifier = Modifier .size(16 .dp),
1137- )
1178+ if (isThinking) {
1179+ AnimatedVisibility (visible = stepExpanded && step.content.isNotEmpty()) {
1180+ val scrollState = rememberScrollState()
1181+ Text (
1182+ text = step.content,
1183+ fontSize = 12 .sp,
1184+ lineHeight = 17 .sp,
1185+ color = MaterialTheme .colorScheme.onSurfaceVariant,
1186+ modifier = Modifier
1187+ .fillMaxWidth()
1188+ .padding(start = 26 .dp, top = 2 .dp, bottom = 6 .dp)
1189+ .heightIn(max = 300 .dp)
1190+ .verticalScroll(scrollState),
1191+ )
1192+ }
1193+ }
11381194 }
11391195}
11401196
0 commit comments