@tduncan / Cortana / commits / c732e78

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