Replace OpenClaw gateway provider text with lobster badge

This commit is contained in:
Haitao Pan 2026-04-12 14:55:21 +08:00
parent 248b40f8fa
commit 8cc0506122
6 changed files with 154 additions and 4 deletions

View File

@ -524,7 +524,9 @@ class ComposerBarStateInternal extends State<ComposerBarInternal> {
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<ComposerBarInternal> {
),
],
child: ComposerToolbarChipInternal(
icon: Icons.cloud_outlined,
leading: const GatewayProviderBadgeInternal(
key: Key('assistant-gateway-provider-badge'),
),
tooltip: gatewayProviderTooltipInternal(),
showChevron: true,
padding: const EdgeInsets.symmetric(

View File

@ -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),
),
);
}
}

View File

@ -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) =>

View File

@ -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<PopupMenuButton<String>>(
find.byKey(const Key('assistant-gateway-provider-button')),
);
@ -233,6 +238,12 @@ void main() {
items.whereType<PopupMenuItem<String>>().single.value,
kCanonicalGatewayProviderId,
);
final menuRow =
items.whereType<PopupMenuItem<String>>().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();

View File

@ -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 <String>[],
);
_seedBridgeProviders(controller, const <SingleAgentProvider>[
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 <String>['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 <String>[],
attachments: const <ComposerAttachmentInternal>[],
availableSkills: const <ComposerSkillOptionInternal>[],
selectedSkillKeys: const <String>[],
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(

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB