From 8cc050612296b3a62e4e833ebeef44b0fe4f8be2 Mon Sep 17 00:00:00 2001 From: Haitao Pan Date: Sun, 12 Apr 2026 14:55:21 +0800 Subject: [PATCH] Replace OpenClaw gateway provider text with lobster badge --- .../assistant_page_composer_bar.dart | 8 +- .../assistant_page_composer_support.dart | 32 ++++++ .../assistant_page_tooltip_labels.dart | 4 +- ...t_execution_target_picker_widget_test.dart | 11 ++ .../assistant_page_composer_golden_test.dart | 103 ++++++++++++++++++ ...sistant_page_composer_gateway_provider.png | Bin 0 -> 7087 bytes 6 files changed, 154 insertions(+), 4 deletions(-) create mode 100644 test/features/assistant/goldens/assistant_page_composer_gateway_provider.png diff --git a/lib/features/assistant/assistant_page_composer_bar.dart b/lib/features/assistant/assistant_page_composer_bar.dart index 5aa4e37f..62d5d745 100644 --- a/lib/features/assistant/assistant_page_composer_bar.dart +++ b/lib/features/assistant/assistant_page_composer_bar.dart @@ -524,7 +524,9 @@ class ComposerBarStateInternal extends State { key: Key('assistant-gateway-provider-menu-item-openclaw'), child: Row( children: [ - Icon(Icons.cloud_outlined, size: 18), + GatewayProviderBadgeInternal( + key: Key('assistant-gateway-provider-menu-badge'), + ), SizedBox(width: 10), Expanded(child: Text(kCanonicalGatewayProviderLabel)), Icon(Icons.check_rounded, size: 18), @@ -533,7 +535,9 @@ class ComposerBarStateInternal extends State { ), ], child: ComposerToolbarChipInternal( - icon: Icons.cloud_outlined, + leading: const GatewayProviderBadgeInternal( + key: Key('assistant-gateway-provider-badge'), + ), tooltip: gatewayProviderTooltipInternal(), showChevron: true, padding: const EdgeInsets.symmetric( diff --git a/lib/features/assistant/assistant_page_composer_support.dart b/lib/features/assistant/assistant_page_composer_support.dart index 6fed462e..611d8834 100644 --- a/lib/features/assistant/assistant_page_composer_support.dart +++ b/lib/features/assistant/assistant_page_composer_support.dart @@ -246,3 +246,35 @@ class SingleAgentProviderBadgeInternal extends StatelessWidget { ); } } + +class GatewayProviderBadgeInternal extends StatelessWidget { + const GatewayProviderBadgeInternal({ + super.key, + this.size = 18, + this.fontSize = 11, + }); + + final double size; + final double fontSize; + + @override + Widget build(BuildContext context) { + final palette = context.palette; + return Container( + width: size, + height: size, + alignment: Alignment.center, + decoration: BoxDecoration( + color: palette.surfaceSecondary, + borderRadius: BorderRadius.circular(999), + border: Border.all(color: palette.strokeSoft), + ), + child: Text( + '🦞', + maxLines: 1, + overflow: TextOverflow.clip, + style: TextStyle(fontSize: fontSize, height: 1), + ), + ); + } +} diff --git a/lib/features/assistant/assistant_page_tooltip_labels.dart b/lib/features/assistant/assistant_page_tooltip_labels.dart index e6f81839..6a55633e 100644 --- a/lib/features/assistant/assistant_page_tooltip_labels.dart +++ b/lib/features/assistant/assistant_page_tooltip_labels.dart @@ -51,8 +51,8 @@ String singleAgentProviderTooltipInternal( ); String gatewayProviderTooltipInternal() => appText( - 'Gateway Provider: $kCanonicalGatewayProviderLabel', - 'Gateway Provider: $kCanonicalGatewayProviderLabel', + 'Gateway Provider: 🦞 $kCanonicalGatewayProviderLabel', + 'Gateway Provider: 🦞 $kCanonicalGatewayProviderLabel', ); String modelTooltipInternal(String modelLabel) => diff --git a/test/assistant_execution_target_picker_widget_test.dart b/test/assistant_execution_target_picker_widget_test.dart index 27f0a783..19d82a95 100644 --- a/test/assistant_execution_target_picker_widget_test.dart +++ b/test/assistant_execution_target_picker_widget_test.dart @@ -220,6 +220,11 @@ void main() { find.byKey(const Key('assistant-gateway-provider-button')), findsOneWidget, ); + expect( + find.byKey(const Key('assistant-gateway-provider-badge')), + findsOneWidget, + ); + expect(find.text('🦞'), findsOneWidget); final gatewayButton = tester.widget>( find.byKey(const Key('assistant-gateway-provider-button')), ); @@ -233,6 +238,12 @@ void main() { items.whereType>().single.value, kCanonicalGatewayProviderId, ); + final menuRow = + items.whereType>().single.child as Row; + expect( + menuRow.children.first.key, + const Key('assistant-gateway-provider-menu-badge'), + ); await tester.pumpWidget(const SizedBox.shrink()); await tester.pump(); diff --git a/test/features/assistant/assistant_page_composer_golden_test.dart b/test/features/assistant/assistant_page_composer_golden_test.dart index 5744323b..7b34e41a 100644 --- a/test/features/assistant/assistant_page_composer_golden_test.dart +++ b/test/features/assistant/assistant_page_composer_golden_test.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:xworkmate/app/app_controller_desktop_core.dart'; +import 'package:xworkmate/app/app_controller_desktop_workspace_execution.dart'; import 'package:xworkmate/features/assistant/assistant_page_composer_bar.dart'; import 'package:xworkmate/features/assistant/assistant_page_composer_clipboard.dart'; import 'package:xworkmate/features/assistant/assistant_page_composer_skill_models.dart'; @@ -110,6 +111,108 @@ void main() { ), ); }); + + testWidgets('renders composer with gateway provider lobster badge', ( + tester, + ) async { + await tester.binding.setSurfaceSize(const Size(1400, 320)); + addTearDown(() async => tester.binding.setSurfaceSize(null)); + + final root = Directory.systemTemp.createTempSync( + 'xworkmate-composer-golden-gateway-', + ); + final store = SecureConfigStore( + enableSecureStorage: false, + appDataRootPathResolver: () async => '${root.path}/settings.sqlite3', + secretRootPathResolver: () async => root.path, + supportRootPathResolver: () async => root.path, + ); + final controller = AppController( + store: store, + desktopPlatformService: UnsupportedDesktopPlatformService(), + skillDirectoryAccessService: _GoldenSkillDirectoryAccessService( + root.path, + ), + goTaskServiceClient: const _GoldenGoTaskServiceClient(), + singleAgentSharedSkillScanRootOverrides: const [], + ); + _seedBridgeProviders(controller, const [ + SingleAgentProvider.codex, + ]); + final inputController = TextEditingController(text: 'θ―·ζ•΄η†δ»Šε€©ηš„δ»»εŠ‘θΏ›ε±•'); + final focusNode = FocusNode(); + + addTearDown(() async { + controller.dispose(); + inputController.dispose(); + focusNode.dispose(); + if (root.existsSync()) { + await root.delete(recursive: true); + } + }); + + controller.appUiStateInternal = controller.appUiState.copyWith( + savedGatewayTargets: const ['gateway'], + ); + controller.lastObservedSettingsSnapshotInternal = + controller.settingsController.snapshotInternal; + controller.initializeAssistantThreadContext( + controller.currentSessionKey, + executionTarget: AssistantExecutionTarget.gateway, + ); + + await tester.pumpWidget( + MaterialApp( + theme: AppTheme.light(platform: TargetPlatform.macOS), + home: Scaffold( + body: Center( + child: RepaintBoundary( + key: const ValueKey('assistant-gateway-composer-boundary'), + child: SizedBox( + width: 1280, + child: ComposerBarInternal( + controller: controller, + inputController: inputController, + focusNode: focusNode, + thinkingLabel: 'Normal', + showModelControl: false, + modelLabel: '', + modelOptions: const [], + attachments: const [], + availableSkills: const [], + selectedSkillKeys: const [], + onRemoveAttachment: (_) {}, + onToggleSkill: (_) {}, + onThinkingChanged: (_) {}, + onModelChanged: (_) async {}, + onOpenGateway: () {}, + onOpenAiGatewaySettings: () {}, + onReconnectGateway: () async {}, + onPickAttachments: () {}, + onAddAttachment: (_) {}, + onPasteImageAttachment: () async => null, + onContentHeightChanged: (_) {}, + onInputHeightChanged: (_) {}, + onSend: () async {}, + ), + ), + ), + ), + ), + ), + ); + await tester.pump(const Duration(milliseconds: 300)); + + expect( + find.byKey(const Key('assistant-gateway-provider-badge')), + findsOneWidget, + ); + + await expectLater( + find.byKey(const ValueKey('assistant-gateway-composer-boundary')), + matchesGoldenFile('goldens/assistant_page_composer_gateway_provider.png'), + ); + }); } void _seedBridgeProviders( diff --git a/test/features/assistant/goldens/assistant_page_composer_gateway_provider.png b/test/features/assistant/goldens/assistant_page_composer_gateway_provider.png new file mode 100644 index 0000000000000000000000000000000000000000..051c9a24159fe12b694557b62105cd79e834e2e3 GIT binary patch literal 7087 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 E54t0l+5i9m literal 0 HcmV?d00001