Clean up bridge provider dialog copy

This commit is contained in:
Haitao Pan 2026-04-10 14:59:39 +08:00
parent ee452fc7ea
commit 9e407bf090
12 changed files with 119 additions and 51 deletions

View File

@ -218,6 +218,26 @@ extension AppControllerDesktopRuntimeHelpers on AppController {
if (lowered.contains('gateway not connected') ||
lowered.contains('code: offline') ||
lowered.contains('offlin') && lowered.contains('gateway')) {
if (target == AssistantExecutionTarget.singleAgent) {
final selection = singleAgentProviderForSession(
sessionsControllerInternal.currentSessionKey,
);
final provider =
resolvedSingleAgentProviderInternal(selection) ?? selection;
final providerLabel = provider == SingleAgentProvider.auto
? appText('Bridge Provider', 'Bridge Provider')
: provider.label;
final address = _extractGatewayAddressFromErrorInternal(raw);
return address.isEmpty
? appText(
'当前线程的 Bridge Provider$providerLabel)未连接。请先在设置里连接并同步后再重试。',
'The Bridge Provider for this thread ($providerLabel) is not connected. Connect and sync it from Settings, then try again.',
)
: appText(
'当前线程的 Bridge Provider$providerLabel)未连接:$address。请先在设置里连接并同步后再重试。',
'The Bridge Provider for this thread ($providerLabel) is not connected: $address. Connect and sync it from Settings, then try again.',
);
}
final profile = gatewayProfileForAssistantExecutionTargetInternal(target);
final address = gatewayAddressLabelInternal(profile);
final targetLabel = target.label;
@ -234,6 +254,13 @@ extension AppControllerDesktopRuntimeHelpers on AppController {
return raw;
}
String _extractGatewayAddressFromErrorInternal(String raw) {
final match = RegExp(
r'((?:\d{1,3}\.){3}\d{1,3}:\d+|localhost:\d+|[a-zA-Z0-9.-]+:\d+)',
).firstMatch(raw);
return match?.group(1)?.trim() ?? '';
}
String formatAiGatewayHttpErrorInternal(int statusCode, String detail) {
final base = switch (statusCode) {
400 => appText(

View File

@ -83,12 +83,12 @@ String singleAgentUnavailableLabelDesktopInternal(
)) {
return detail.isEmpty
? appText(
'当前线程固定为 ${selection.label},但它在这台设备上不可用。检测到其他外部 Agent ACP 端点时不会自动改线,请切到可用的 ACP Server。',
'This thread is pinned to ${selection.label}, but it is unavailable on this device. XWorkmate will not reroute to another external Agent ACP endpoint automatically. Switch to an available ACP Server.',
'当前线程固定为 ${selection.label},但它在这台设备上不可用。检测到其他 Bridge Provider 时不会自动改线,请手动切到可用 Provider。',
'This thread is pinned to ${selection.label}, but it is unavailable on this device. XWorkmate will not reroute to another bridge provider automatically. Switch to an available provider manually.',
)
: appText(
'当前线程固定为 ${selection.label}$detail 检测到其他外部 Agent ACP 端点时不会自动改线,请切到可用的 ACP Server。',
'This thread is pinned to ${selection.label}: $detail XWorkmate will not reroute to another external Agent ACP endpoint automatically. Switch to an available ACP Server.',
'当前线程固定为 ${selection.label}$detail 检测到其他 Bridge Provider 时不会自动改线,请手动切到可用 Provider。',
'This thread is pinned to ${selection.label}: $detail XWorkmate will not reroute to another bridge provider automatically. Switch to an available provider manually.',
);
}
if (controller.singleAgentNeedsAiGatewayConfigurationForSession(
@ -96,21 +96,21 @@ String singleAgentUnavailableLabelDesktopInternal(
)) {
return detail.isEmpty
? appText(
'当前没有可用的外部 Agent ACP 端点。请先配置外部 Agent 连接。',
'No external Agent ACP endpoint is available. Configure an external Agent connection first.',
'当前没有可用的 Bridge Provider。请先在设置里配置并同步外部 Agent 连接。',
'No bridge provider is available. Configure and sync an external agent connection in Settings first.',
)
: appText(
'$detail 当前没有可用的外部 Agent ACP 端点。请先配置外部 Agent 连接。',
'$detail No external Agent ACP endpoint is available. Configure an external Agent connection first.',
'$detail 当前没有可用的 Bridge Provider。请先在设置里配置并同步外部 Agent 连接。',
'$detail No bridge provider is available. Configure and sync an external agent connection in Settings first.',
);
}
return detail.isEmpty
? appText(
'当前线程的外部 Agent ACP 连接尚未就绪。',
'The external Agent ACP connection for this thread is not ready yet.',
'当前线程的 Bridge Provider 尚未就绪。',
'The bridge provider for this thread is not ready yet.',
)
: appText(
'当前线程的外部 Agent ACP 连接尚未就绪:$detail',
'The external Agent ACP connection for this thread is not ready yet: $detail',
'当前线程的 Bridge Provider 尚未就绪:$detail',
'The bridge provider for this thread is not ready yet: $detail',
);
}

View File

@ -351,7 +351,7 @@ extension AppControllerDesktopThreadSessions on AppController {
);
final target = assistantExecutionTargetForSession(normalizedSessionKey);
if (target == AssistantExecutionTarget.singleAgent) {
final primaryLabel = appText('ACP Server Local', 'ACP Server Local');
final primaryLabel = appText('Bridge', 'Bridge');
final provider = singleAgentProviderForSession(normalizedSessionKey);
final resolvedProvider = singleAgentResolvedProviderForSession(
normalizedSessionKey,
@ -362,19 +362,19 @@ extension AppControllerDesktopThreadSessions on AppController {
? joinConnectionPartsInternal(<String>[resolvedProvider.label, model])
: singleAgentShouldSuggestAcpSwitchForSession(normalizedSessionKey)
? appText(
'${provider.label} 不可用,请切到可用的 ACP Server。',
'${provider.label} is unavailable. Switch to an available ACP Server.',
'${provider.label} 当前不可用,请改成 Bridge 当前可用的 Provider。',
'${provider.label} is unavailable. Switch to a provider currently advertised by the bridge.',
)
: singleAgentNeedsAiGatewayConfigurationForSession(
normalizedSessionKey,
)
? appText(
'没有可用的外部 Agent ACP 端点,请先配置可用的 ACP Server',
'No external Agent ACP endpoint is available. Configure an ACP Server first.',
'当前没有可用的 Bridge Provider。请先在设置里配置并同步可用连接',
'No bridge provider is currently available. Configure and sync an available upstream connection in Settings first.',
)
: appText(
'当前线程的外部 Agent ACP 连接尚未就绪。',
'The external Agent ACP connection for this thread is not ready yet.',
'当前线程的 Bridge Provider 尚未就绪。',
'The bridge provider for this thread is not ready yet.',
);
return AssistantThreadConnectionState(
executionTarget: target,

View File

@ -511,10 +511,16 @@ class AssistantEmptyStateInternal extends StatelessWidget {
final reconnectAvailable = controller.canQuickConnectGateway;
final title = singleAgent
? connected
? appText('开始 ACP Server 任务', 'Start an ACP Server task')
? appText('开始智能体任务', 'Start an agent task')
: singleAgentNeedsAiGateway
? appText('先配置 ACP Server', 'Configure ACP Server first')
: appText('先准备 ACP Server', 'Prepare the ACP Server first')
? appText(
'先配置 Bridge Provider',
'Configure a bridge provider first',
)
: appText(
'先准备 Bridge Provider',
'Prepare the bridge provider first',
)
: connected
? appText('开始对话或运行任务', 'Start a chat or run a task')
: connectionState.status == RuntimeConnectionStatus.error
@ -523,22 +529,22 @@ class AssistantEmptyStateInternal extends StatelessWidget {
final description = singleAgent
? connected
? appText(
'当前线程通过 ACP Server 处理任务,不会建立 OpenClaw Gateway 会话。',
'This thread runs through the ACP Server path and does not open an OpenClaw Gateway session.',
'当前线程会通过 Bridge 当前广告的 Provider 处理任务,不会建立 OpenClaw Gateway 会话。',
'This thread runs through the provider currently advertised by the bridge and does not open an OpenClaw Gateway session.',
)
: singleAgentSuggestsAcpSwitch
? appText(
'当前线程固定为 $providerLabel,但它在这台设备上不可用。请改成可用的 ACP Server。',
'This thread is pinned to $providerLabel, but it is unavailable on this device. Switch to an available ACP Server.',
'当前线程固定为 $providerLabel,但它在这台设备上不可用。请改成 Bridge 当前可用的 Provider。',
'This thread is pinned to $providerLabel, but it is unavailable on this device. Switch to a provider currently advertised by the bridge.',
)
: singleAgentNeedsAiGateway
? appText(
'请先在 设置 -> 集成 中配置可用的外部 Agent ACP 端点,然后以 ACP Server 模式继续当前任务。',
'Configure an external Agent ACP endpoint in Settings -> Integrations, then continue this task in ACP Server mode.',
'请先在 设置 -> 集成 中配置并同步可用的外部 Agent 连接,然后再继续当前任务。',
'Configure and sync an available external agent connection in Settings -> Integrations before continuing this task.',
)
: appText(
'当前线程的外部 Agent ACP 连接尚未就绪。请先配置 $providerLabel 对应端点',
'The external Agent ACP connection for this thread is not ready yet. Configure the endpoint for $providerLabel first.',
'当前线程的 Bridge Provider 尚未就绪。请先检查 $providerLabel 对应连接',
'The bridge provider for this thread is not ready yet. Check the connection mapped to $providerLabel first.',
)
: connected
? appText(

View File

@ -37,5 +37,5 @@ import 'assistant_page_composer_skill_models.dart';
import 'assistant_page_composer_skill_picker.dart';
import 'assistant_page_composer_clipboard.dart';
// Lightweight compatibility anchor. The Composer and assistant widgets now
// live in focused part files under the same library.
// Composer and assistant widgets live in focused part files under the same
// library to keep UI ownership local to the assistant feature.

View File

@ -474,7 +474,7 @@ class ComposerBarStateInternal extends State<ComposerBarInternal> {
if (singleAgent) ...[
PopupMenuButton<SingleAgentProvider>(
key: const Key('assistant-single-agent-provider-button'),
tooltip: appText('单机智能体执行器', 'Single Agent Provider'),
tooltip: appText('Bridge Provider', 'Bridge Provider'),
onSelected: (value) {
unawaited(controller.setSingleAgentProvider(value));
},

View File

@ -45,8 +45,8 @@ String executionTargetTooltipInternal(AssistantExecutionTarget target) =>
String singleAgentProviderTooltipInternal(SingleAgentProvider provider) =>
appText(
'单机智能体执行器: ${provider.label}',
'Single-agent provider: ${provider.label}',
'Bridge Provider: ${provider.label}',
'Bridge Provider: ${provider.label}',
);
String modelTooltipInternal(String modelLabel) =>

View File

@ -116,8 +116,8 @@ class SkillsFocusPreviewInternal extends StatelessWidget {
message: typedController.isSingleAgentMode
? (typedController.currentSingleAgentNeedsAiGatewayConfiguration
? appText(
'当前没有可用的外部 Agent ACP 端点,请先配置 ACP Server',
'No external Agent ACP endpoint is available. Configure an ACP server first.',
'当前没有可用的 Bridge Provider请先在设置里配置并同步连接',
'No bridge provider is available. Configure and sync a connection in Settings first.',
)
: appText(
'当前线程还没有已加载技能。切换 provider 后会读取该线程自己的 skills 列表。',

View File

@ -620,7 +620,7 @@ void registerAppControllerAiGatewayChatSuiteSingleAgentTestsInternal() {
controller.chatMessages.any(
(message) =>
message.role == 'assistant' &&
message.text.contains('当前没有可用的外部 Agent ACP 端点'),
message.text.contains('当前没有可用的 Bridge Provider'),
),
isTrue,
);
@ -684,7 +684,7 @@ void registerAppControllerAiGatewayChatSuiteSingleAgentTestsInternal() {
controller.chatMessages.any(
(message) =>
message.role == 'assistant' &&
message.text.contains('当前没有可用的外部 Agent ACP 端点'),
message.text.contains('当前没有可用的 Bridge Provider'),
),
isTrue,
);

View File

@ -35,8 +35,11 @@ void main() {
controller.currentAssistantExecutionTarget,
AssistantExecutionTarget.singleAgent,
);
expect(controller.currentAssistantConnectionState.isSingleAgent, isTrue);
expect(controller.assistantConnectionStatusLabel, 'ACP Server Local');
expect(
controller.currentAssistantConnectionState.isSingleAgent,
isTrue,
);
expect(controller.assistantConnectionStatusLabel, 'Bridge');
},
);
@ -63,7 +66,10 @@ void main() {
controller.currentAssistantExecutionTarget,
AssistantExecutionTarget.local,
);
expect(controller.currentAssistantConnectionState.isSingleAgent, isFalse);
expect(
controller.currentAssistantConnectionState.isSingleAgent,
isFalse,
);
expect(controller.assistantConnectionStatusLabel, '已连接');
expect(controller.assistantConnectionTargetLabel, '127.0.0.1:4317');
},
@ -92,7 +98,10 @@ void main() {
controller.currentAssistantExecutionTarget,
AssistantExecutionTarget.remote,
);
expect(controller.currentAssistantConnectionState.isSingleAgent, isFalse);
expect(
controller.currentAssistantConnectionState.isSingleAgent,
isFalse,
);
expect(controller.assistantConnectionStatusLabel, '已连接');
expect(
controller.assistantConnectionTargetLabel,
@ -100,6 +109,32 @@ void main() {
);
},
);
test(
'core flow 04 formats single-agent offline errors with bridge provider wording',
() async {
final controller = await createCoreFlowControllerInternal();
addTearDown(controller.dispose);
final sessionKey = buildDraftSessionKeyInternal();
controller.initializeAssistantThreadContext(
sessionKey,
title: '新对话',
executionTarget: AssistantExecutionTarget.singleAgent,
);
await controller.switchSession(sessionKey);
await controller.setSingleAgentProvider(SingleAgentProvider.opencode);
expect(
controller.gatewayExecutionErrorLabelInternal(
'gateway not connected: 127.0.0.1:18789',
target: AssistantExecutionTarget.singleAgent,
),
'当前线程的 Bridge ProviderOpenCode未连接127.0.0.1:18789。请先在设置里连接并同步后再重试。',
);
},
);
});
}

View File

@ -144,10 +144,10 @@ void registerExecutionTargetSwitchConnectionTests() {
RuntimeConnectionMode.remote,
);
expect(gateway.disconnectCount, 1);
expect(controller.assistantConnectionStatusLabel, 'ACP Server Local');
expect(controller.assistantConnectionStatusLabel, 'Bridge');
expect(
controller.assistantConnectionTargetLabel,
'没有可用的外部 Agent ACP 端点,请先配置可用的 ACP Server',
'当前没有可用的 Bridge Provider。请先在设置里配置并同步可用连接',
);
expect(
gateway.connectedProfiles,
@ -337,7 +337,7 @@ void registerExecutionTargetSwitchConnectionTests() {
controller.settings.assistantExecutionTarget,
AssistantExecutionTarget.remote,
);
expect(controller.assistantConnectionStatusLabel, 'ACP Server Local');
expect(controller.assistantConnectionStatusLabel, 'Bridge');
},
);
@ -479,7 +479,7 @@ void registerExecutionTargetSwitchConnectionTests() {
controller.assistantExecutionTarget,
AssistantExecutionTarget.singleAgent,
);
expect(controller.assistantConnectionStatusLabel, 'ACP Server Local');
expect(controller.assistantConnectionStatusLabel, 'Bridge');
expect(completed, isFalse);
} finally {
if (!disconnectGate.isCompleted) {
@ -497,7 +497,7 @@ void registerExecutionTargetSwitchConnectionTests() {
controller.assistantExecutionTarget,
AssistantExecutionTarget.singleAgent,
);
expect(controller.assistantConnectionStatusLabel, 'ACP Server Local');
expect(controller.assistantConnectionStatusLabel, 'Bridge');
},
);
});

View File

@ -98,7 +98,7 @@ void registerExecutionTargetSwitchThreadTests() {
AssistantExecutionTarget.singleAgent,
);
expect(gateway.disconnectCount, 1);
expect(controller.assistantConnectionStatusLabel, 'ACP Server Local');
expect(controller.assistantConnectionStatusLabel, 'Bridge');
expect(
controller.settings.assistantExecutionTarget,
AssistantExecutionTarget.local,
@ -195,10 +195,10 @@ void registerExecutionTargetSwitchThreadTests() {
);
await controller.switchSession('main');
expect(controller.assistantConnectionStatusLabel, 'ACP Server Local');
expect(controller.assistantConnectionStatusLabel, 'Bridge');
expect(
controller.assistantConnectionTargetLabel,
'没有可用的外部 Agent ACP 端点,请先配置可用的 ACP Server',
'当前没有可用的 Bridge Provider。请先在设置里配置并同步可用连接',
);
},
);