diff --git a/lib/features/assistant/assistant_page.dart b/lib/features/assistant/assistant_page.dart index d5762438..4901b408 100644 --- a/lib/features/assistant/assistant_page.dart +++ b/lib/features/assistant/assistant_page.dart @@ -28,7 +28,7 @@ import '../../widgets/surface_card.dart'; const double _assistantComposerDefaultInputHeight = 78; const double _assistantWorkspaceMinConversationHeight = 180; -const double _assistantWorkspaceMinLowerPaneHeight = 176; +const double _assistantWorkspaceMinLowerPaneHeight = 160; const double _assistantHorizontalResizeHandleWidth = 6; const double _assistantHorizontalPaneGap = 2; const double _assistantVerticalResizeHandleHeight = 10; @@ -36,8 +36,8 @@ const double _assistantArtifactPaneMinWidth = 280; const double _assistantArtifactPaneDefaultWidth = 360; const double _assistantCollapsedArtifactToggleClearance = 56; const double _assistantComposerSafeAreaGap = 8; -const double _assistantComposerBaseHeightCompact = 192; -const double _assistantComposerBaseHeightTall = 216; +const double _assistantComposerBaseHeightCompact = 168; +const double _assistantComposerBaseHeightTall = 188; const int _assistantTaskActionMaxRetryCount = 5; typedef AssistantClipboardImageReader = Future Function(); @@ -89,6 +89,7 @@ class _AssistantPageState extends State { String? _lastAutoAgentLabel; String _lastConversationScrollSignature = ''; double _composerInputHeight = _assistantComposerDefaultInputHeight; + double _composerMeasuredContentHeight = 0; double _workspaceLowerPaneHeightAdjustment = 0; bool _artifactPaneCollapsed = true; double _artifactPaneWidth = _assistantArtifactPaneDefaultWidth; @@ -112,6 +113,18 @@ class _AssistantPageState extends State { } } + void _handleComposerContentHeightChanged(double value) { + if (!mounted || !value.isFinite || value <= 0) { + return; + } + if ((_composerMeasuredContentHeight - value).abs() < 0.5) { + return; + } + setState(() { + _composerMeasuredContentHeight = value; + }); + } + @override void dispose() { _inputController.dispose(); @@ -418,16 +431,20 @@ class _AssistantPageState extends State { availableWidth: composerContentWidth, averageChipWidth: 132, ); + final fallbackComposerContentHeight = + baseComposerHeight + + math.max( + 0.0, + _composerInputHeight - _assistantComposerDefaultInputHeight, + ) + + attachmentExtraHeight + + selectedSkillExtraHeight; + final composerContentHeight = _composerMeasuredContentHeight > 0 + ? _composerMeasuredContentHeight + : fallbackComposerContentHeight; final defaultComposerHeight = math.min( availableWorkspaceHeight, - baseComposerHeight + - math.max( - 0.0, - _composerInputHeight - _assistantComposerDefaultInputHeight, - ) + - attachmentExtraHeight + - selectedSkillExtraHeight + - composerBottomSpacing, + composerContentHeight + composerBottomSpacing, ); final composerHeightUpperBound = math.min( availableWorkspaceHeight, @@ -552,6 +569,8 @@ class _AssistantPageState extends State { }, onPasteImageAttachment: widget.clipboardImageReader ?? _readClipboardImageAsXFile, + onComposerContentHeightChanged: + _handleComposerContentHeightChanged, onComposerInputHeightChanged: _handleComposerInputHeightChanged, onSend: _submitPrompt, @@ -1789,6 +1808,7 @@ class _AssistantLowerPane extends StatelessWidget { required this.onPickAttachments, required this.onAddAttachment, required this.onPasteImageAttachment, + required this.onComposerContentHeightChanged, required this.onComposerInputHeightChanged, required this.onSend, }); @@ -1814,6 +1834,7 @@ class _AssistantLowerPane extends StatelessWidget { final VoidCallback onPickAttachments; final ValueChanged<_ComposerAttachment> onAddAttachment; final AssistantClipboardImageReader onPasteImageAttachment; + final ValueChanged onComposerContentHeightChanged; final ValueChanged onComposerInputHeightChanged; final Future Function() onSend; @@ -1847,6 +1868,7 @@ class _AssistantLowerPane extends StatelessWidget { onPickAttachments: onPickAttachments, onAddAttachment: onAddAttachment, onPasteImageAttachment: onPasteImageAttachment, + onContentHeightChanged: onComposerContentHeightChanged, onInputHeightChanged: onComposerInputHeightChanged, onSend: onSend, ), @@ -2632,6 +2654,7 @@ class _ComposerBar extends StatefulWidget { required this.onPickAttachments, required this.onAddAttachment, required this.onPasteImageAttachment, + required this.onContentHeightChanged, required this.onInputHeightChanged, required this.onSend, }); @@ -2656,6 +2679,7 @@ class _ComposerBar extends StatefulWidget { final VoidCallback onPickAttachments; final ValueChanged<_ComposerAttachment> onAddAttachment; final AssistantClipboardImageReader onPasteImageAttachment; + final ValueChanged onContentHeightChanged; final ValueChanged onInputHeightChanged; final Future Function() onSend; @@ -2683,6 +2707,7 @@ class _ComposerBarState extends State<_ComposerBar> { final GlobalKey _skillPickerTargetKey = GlobalKey( debugLabel: 'assistant-skill-picker-target', ); + final GlobalKey _contentKey = GlobalKey(debugLabel: 'assistant-composer-bar'); final LayerLink _skillPickerLayerLink = LayerLink(); final OverlayPortalController _skillPickerPortalController = OverlayPortalController(debugLabel: 'assistant-skill-picker'); @@ -2704,6 +2729,7 @@ class _ComposerBarState extends State<_ComposerBar> { return; } widget.onInputHeightChanged(_inputHeight); + _reportContentHeight(); }); } @@ -2714,6 +2740,7 @@ class _ComposerBarState extends State<_ComposerBar> { oldWidget.controller.removeListener(_handleControllerChanged); widget.controller.addListener(_handleControllerChanged); } + _reportContentHeight(); } @override @@ -2734,6 +2761,19 @@ class _ComposerBarState extends State<_ComposerBar> { setState(() {}); } + void _reportContentHeight() { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (!mounted) { + return; + } + final height = _contentKey.currentContext?.size?.height; + if (height == null || !height.isFinite || height <= 0) { + return; + } + widget.onContentHeightChanged(height); + }); + } + void _resizeInput(double delta) { final nextHeight = (_inputHeight + delta).clamp( _minInputHeight, @@ -2979,7 +3019,10 @@ class _ComposerBarState extends State<_ComposerBar> { ? appText('重连', 'Reconnect') : appText('连接', 'Connect'); + _reportContentHeight(); + return Padding( + key: _contentKey, padding: const EdgeInsets.fromLTRB(10, 8, 10, 0), child: Column( mainAxisAlignment: MainAxisAlignment.end, diff --git a/test/features/assistant_page_suite.dart b/test/features/assistant_page_suite.dart index 63a5f3fd..ab866099 100644 --- a/test/features/assistant_page_suite.dart +++ b/test/features/assistant_page_suite.dart @@ -555,6 +555,22 @@ void main() { }, ); + testWidgets('AssistantPage keeps the default composer footprint compact', ( + WidgetTester tester, + ) async { + final controller = await createTestController(tester); + + await pumpPage( + tester, + child: AssistantPage(controller: controller, onOpenDetail: (_) {}), + ); + + final composerShell = find.byKey(const Key('assistant-composer-shell')); + + expect(composerShell, findsOneWidget); + expect(tester.getRect(composerShell).height, lessThan(210)); + }); + testWidgets('AssistantPage keeps a minimal composer action menu', ( WidgetTester tester, ) async {