Refactor reasoning widget to an expandable header
- Convert reasoning widget to a collapsible panel with a header - Add animated expansion and theming tweaks - Use GptMarkdownTheme for reasoning content - Remove obsolete zh server_type_*_sub entries
abdulmominsakib committed
Jun 29, 2026 at 21:02 UTC
c732e7843d3d72bce8a29ecc58a11681d56633a9
2 files changed
+92
-117
lib/features/chat/views/components/reasoning_widget.dart
+92
-111
@@ -17,7 +17,8 @@ class ReasoningWidget extends StatefulWidget {
17
State<ReasoningWidget> createState() => _ReasoningWidgetState();
18
}
19
20
-class _ReasoningWidgetState extends State<ReasoningWidget> with SingleTickerProviderStateMixin {
20
+class _ReasoningWidgetState extends State<ReasoningWidget>
21
+ with SingleTickerProviderStateMixin {
22
late bool _isExpanded;
23
late AnimationController _animationController;
24
late Animation<double> _expandAnimation;
@@ -78,17 +79,23 @@ class _ReasoningWidgetState extends State<ReasoningWidget> with SingleTickerProv
79
return const SizedBox.shrink();
80
}
81
82
+ final reasoningTextColor = Color.lerp(
83
+ isDark ? AppColors.darkPrimaryText : AppColors.lightPrimaryText,
84
+ isDark ? AppColors.darkMutedText : AppColors.lightMutedText,
85
+ 0.35,
86
+ )!;
87
+
88
+ final reasoningHighlightColor = isDark
89
+ ? AppColors.darkSurface.withValues(alpha: 0.9)
90
+ : const Color(0xFFF4F4F5);
91
+
92
return Container(
93
margin: const EdgeInsets.only(bottom: 12, top: 4),
94
decoration: BoxDecoration(
84
- color: isDark
85
- ? AppColors.darkSurfaceInput
86
- : AppColors.lightSurface,
95
+ color: isDark ? AppColors.darkSurfaceInput : AppColors.lightSurface,
96
borderRadius: BorderRadius.circular(12),
97
border: Border.all(
89
- color: isDark
90
- ? AppColors.darkBorder
91
- : AppColors.lightBorder,
98
+ color: isDark ? AppColors.darkBorder : AppColors.lightBorder,
99
width: 1.0,
100
),
101
boxShadow: [
@@ -101,123 +108,97 @@ class _ReasoningWidgetState extends State<ReasoningWidget> with SingleTickerProv
108
),
109
child: ClipRRect(
110
borderRadius: BorderRadius.circular(11),
104
- child: IntrinsicHeight(
105
- child: Row(
106
- crossAxisAlignment: CrossAxisAlignment.stretch,
107
- children: [
108
- // Accent glowing line
109
- Container(
110
- width: 4,
111
- decoration: BoxDecoration(
112
- gradient: LinearGradient(
113
- colors: [
114
- theme.colorScheme.primary,
115
- theme.colorScheme.secondary,
111
+ child: Column(
112
+ mainAxisSize: MainAxisSize.min,
113
+ crossAxisAlignment: CrossAxisAlignment.start,
114
+ children: [
115
+ // Header Row
116
+ InkWell(
117
+ onTap: _toggleExpand,
118
+ borderRadius: BorderRadius.circular(11),
119
+ child: Padding(
120
+ padding: const EdgeInsets.symmetric(
121
+ horizontal: 14,
122
+ vertical: 12,
123
+ ),
124
+ child: Row(
125
+ children: [
126
+ Icon(
127
+ Icons.psychology_outlined,
128
+ size: 18,
129
+ color: isDark
130
+ ? AppColors.darkMutedText
131
+ : AppColors.lightMutedText,
132
+ ),
133
+ const SizedBox(width: 8),
134
+ Text(
135
+ l10n.thinking,
136
+ style: TextStyle(
137
+ fontSize: 13,
138
+ fontWeight: FontWeight.w600,
139
+ letterSpacing: 0.3,
140
+ color: isDark
141
+ ? AppColors.darkMutedText
142
+ : AppColors.lightMutedText,
143
+ ),
144
+ ),
145
+ const SizedBox(width: 8),
146
+ if (widget.isStreaming) ...[
147
+ SizedBox(
148
+ width: 10,
149
+ height: 10,
150
+ child: CircularProgressIndicator(
151
+ strokeWidth: 1.5,
152
+ color: theme.colorScheme.primary,
153
+ ),
154
+ ),
155
],
117
- begin: Alignment.topCenter,
118
- end: Alignment.bottomCenter,
119
- ),
156
+ const Spacer(),
157
+ RotationTransition(
158
+ turns: Tween(
159
+ begin: 0.0,
160
+ end: 0.5,
161
+ ).animate(_expandAnimation),
162
+ child: Icon(
163
+ Icons.keyboard_arrow_down_rounded,
164
+ size: 18,
165
+ color: isDark
166
+ ? AppColors.darkMutedText
167
+ : AppColors.lightMutedText,
168
+ ),
169
+ ),
170
+ ],
171
),
172
),
122
- Expanded(
173
+ ),
174
+ // Expandable content
175
+ SizeTransition(
176
+ sizeFactor: _expandAnimation,
177
+ child: Padding(
178
+ padding: const EdgeInsets.fromLTRB(14, 0, 14, 14),
179
child: Column(
124
- mainAxisSize: MainAxisSize.min,
180
crossAxisAlignment: CrossAxisAlignment.start,
181
children: [
127
- // Header Row
128
- InkWell(
129
- onTap: _toggleExpand,
130
- borderRadius: const BorderRadius.only(
131
- topRight: Radius.circular(11),
132
- bottomRight: Radius.circular(11),
182
+ GptMarkdownTheme(
183
+ gptThemeData: GptMarkdownThemeData(
184
+ brightness: isDark ? Brightness.dark : Brightness.light,
185
+ highlightColor: reasoningHighlightColor,
186
+ linkColor: theme.colorScheme.primary,
187
),
134
- child: Padding(
135
- padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
136
- child: Row(
137
- children: [
138
- // Brain / thinking icon
139
- Icon(
140
- Icons.psychology_outlined,
141
- size: 18,
142
- color: isDark
143
- ? AppColors.darkMutedText
144
- : AppColors.lightMutedText,
145
- ),
146
- const SizedBox(width: 8),
147
- Text(
148
- l10n.thinking,
149
- style: TextStyle(
150
- fontSize: 13,
151
- fontWeight: FontWeight.w600,
152
- letterSpacing: 0.3,
153
- color: isDark
154
- ? const Color(0xFFBAC2DE)
155
- : const Color(0xFF585B70),
156
- ),
157
- ),
158
- const SizedBox(width: 8),
159
- if (widget.isStreaming) ...[
160
- SizedBox(
161
- width: 10,
162
- height: 10,
163
- child: CircularProgressIndicator(
164
- strokeWidth: 1.5,
165
- color: theme.colorScheme.primary,
166
- ),
167
- ),
168
- ],
169
- const Spacer(),
170
- // Expand Icon with rotation
171
- RotationTransition(
172
- turns: Tween(begin: 0.0, end: 0.5).animate(_expandAnimation),
173
- child: Icon(
174
- Icons.keyboard_arrow_down_rounded,
175
- size: 18,
176
- color: isDark
177
- ? AppColors.darkMutedText
178
- : AppColors.lightMutedText,
179
- ),
180
- ),
181
- ],
182
- ),
183
- ),
184
- ),
185
- // Expandable content
186
- SizeTransition(
187
- sizeFactor: _expandAnimation,
188
- child: Padding(
189
- padding: const EdgeInsets.fromLTRB(14, 0, 14, 14),
190
- child: Column(
191
- crossAxisAlignment: CrossAxisAlignment.start,
192
- children: [
193
- GptMarkdownTheme(
194
- gptThemeData: GptMarkdownThemeData(
195
- brightness: isDark ? Brightness.dark : Brightness.light,
196
- highlightColor: isDark
197
- ? AppColors.darkBorder
198
- : AppColors.lightBorder,
199
- linkColor: theme.colorScheme.primary,
200
- ),
201
- child: GptMarkdown(
202
- widget.reasoningContent ?? '',
203
- style: TextStyle(
204
- fontSize: 13,
205
- color: isDark
206
- ? AppColors.darkPrimaryText
207
- : AppColors.lightPrimaryText,
208
- height: 1.5,
209
- ),
210
- ),
211
- ),
212
- ],
188
+ child: GptMarkdown(
189
+ widget.reasoningContent ?? '',
190
+ style: TextStyle(
191
+ fontSize: 13,
192
+ color: reasoningTextColor,
193
+ height: 1.5,
194
),
195
),
196
),
197
],
198
),
199
),
219
- ],
220
- ),
200
+ ),
201
+ ],
202
),
203
),
204
);
lib/l10n/app_localizations_zh.dart
-6
@@ -2455,18 +2455,12 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
2455
@override
2456
String get server_type_on_device => '裝置端';
2457
2458
- String get server_type_on_device_sub => '無需伺服器';
2459
-
2458
@override
2459
String get server_type_lm_studio => 'LM Studio';
2460
2463
- String get server_type_lm_studio_sub => '本機 API';
2464
-
2461
@override
2462
String get server_type_ollama => 'Ollama';
2463
2468
- String get server_type_ollama_sub => 'CLI 引擎';
2469
-
2464
@override
2465
String get server_type_openrouter => 'OpenRouter';
2466