Hide experimental gateway modes by default
This commit is contained in:
parent
a1263b9a4d
commit
4d200b6faa
@ -152,6 +152,18 @@ mobile:
|
||||
build_modes: [debug, profile, release]
|
||||
description: Mobile Vault server integration section
|
||||
ui_surface: settings_page
|
||||
gateway_self_hosted_base:
|
||||
enabled: false
|
||||
release_tier: experimental
|
||||
build_modes: [debug, profile, release]
|
||||
description: Mobile self-hosted base connection controls
|
||||
ui_surface: settings_page
|
||||
gateway_advanced_custom_mode:
|
||||
enabled: false
|
||||
release_tier: experimental
|
||||
build_modes: [debug, profile, release]
|
||||
description: Mobile advanced custom override mode
|
||||
ui_surface: settings_page
|
||||
gateway_setup_code:
|
||||
enabled: false
|
||||
release_tier: experimental
|
||||
@ -356,6 +368,18 @@ desktop:
|
||||
build_modes: [debug, profile, release]
|
||||
description: Desktop Vault server integration section
|
||||
ui_surface: settings_page
|
||||
gateway_self_hosted_base:
|
||||
enabled: false
|
||||
release_tier: experimental
|
||||
build_modes: [debug, profile, release]
|
||||
description: Desktop self-hosted base connection controls
|
||||
ui_surface: settings_page
|
||||
gateway_advanced_custom_mode:
|
||||
enabled: false
|
||||
release_tier: experimental
|
||||
build_modes: [debug, profile, release]
|
||||
description: Desktop advanced custom override mode
|
||||
ui_surface: settings_page
|
||||
gateway_setup_code:
|
||||
enabled: false
|
||||
release_tier: experimental
|
||||
@ -517,6 +541,18 @@ web:
|
||||
build_modes: []
|
||||
description: Web does not expose vault server integration
|
||||
ui_surface: web_settings_page
|
||||
gateway_self_hosted_base:
|
||||
enabled: false
|
||||
release_tier: experimental
|
||||
build_modes: []
|
||||
description: Web does not expose self-hosted base connection controls
|
||||
ui_surface: web_settings_page
|
||||
gateway_advanced_custom_mode:
|
||||
enabled: false
|
||||
release_tier: experimental
|
||||
build_modes: []
|
||||
description: Web does not expose advanced custom override mode
|
||||
ui_surface: web_settings_page
|
||||
gateway_setup_code:
|
||||
enabled: false
|
||||
release_tier: experimental
|
||||
|
||||
@ -0,0 +1,50 @@
|
||||
# Settings Integration Configuration Model
|
||||
|
||||
This document records the logical model behind the Settings -> Integrations page.
|
||||
|
||||
The page is organized into three layers:
|
||||
|
||||
- User login state
|
||||
- Base connection configuration
|
||||
- Advanced custom mode
|
||||
|
||||
The base connection layer is the default configuration surface. It represents the connection identity that can come from either `svc.plus` or a self-hosted service. Advanced custom mode does not replace the base layer; it overrides selected defaults on top of it.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Settings Integrations Page] --> B[User Login State]
|
||||
A --> C[Base Connection Configuration]
|
||||
A --> D[Advanced Custom Mode]
|
||||
|
||||
B --> B1[Signed out]
|
||||
B --> B2[Signed in]
|
||||
B --> B3[MFA pending]
|
||||
B --> B4[Signing in]
|
||||
|
||||
C --> C1[Account / Email]
|
||||
C --> C2[Password]
|
||||
C --> C3[Service URL]
|
||||
C --> C4[User]
|
||||
C --> C5[Sync]
|
||||
C --> C6[Default connection source]
|
||||
C6 --> C7[svc.plus provided]
|
||||
C6 --> C8[Self-hosted]
|
||||
|
||||
D --> D1[Override OpenClaw Gateway]
|
||||
D --> D2[Override Vault Server]
|
||||
D --> D3[Override LLM Endpoint]
|
||||
D --> D4[Override External ACP Server endpoint]
|
||||
D --> D5[Override SKILLS directories]
|
||||
|
||||
B2 --> C
|
||||
C --> D
|
||||
D --> E[Final effective configuration]
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- User login state describes authentication only.
|
||||
- Base connection configuration describes the default connection path and identity.
|
||||
- Advanced custom mode is a layered override mechanism.
|
||||
- The effective runtime configuration is computed from the base layer plus any advanced overrides.
|
||||
|
||||
@ -70,6 +70,10 @@ abstract final class UiFeatureKeys {
|
||||
static const settingsGateway = 'settings.gateway';
|
||||
static const settingsAccountAccess = 'settings.account_access';
|
||||
static const settingsVaultServer = 'settings.vault_server';
|
||||
static const settingsGatewaySelfHostedBase =
|
||||
'settings.gateway_self_hosted_base';
|
||||
static const settingsGatewayAdvancedCustomMode =
|
||||
'settings.gateway_advanced_custom_mode';
|
||||
static const settingsGatewaySetupCode = 'settings.gateway_setup_code';
|
||||
static const settingsAgents = 'settings.agents';
|
||||
static const settingsAppearance = 'settings.appearance';
|
||||
@ -494,6 +498,12 @@ class UiFeatureAccess {
|
||||
bool get supportsVaultServer =>
|
||||
isEnabledPath(UiFeatureKeys.settingsVaultServer);
|
||||
|
||||
bool get supportsGatewaySelfHostedBase =>
|
||||
isEnabledPath(UiFeatureKeys.settingsGatewaySelfHostedBase);
|
||||
|
||||
bool get supportsGatewayAdvancedCustomMode =>
|
||||
isEnabledPath(UiFeatureKeys.settingsGatewayAdvancedCustomMode);
|
||||
|
||||
List<SettingsTab> get availableSettingsTabs {
|
||||
return SettingsTab.values
|
||||
.where(
|
||||
|
||||
@ -163,6 +163,18 @@ mobile:
|
||||
build_modes: [debug, profile, release]
|
||||
description: Mobile Vault server integration section
|
||||
ui_surface: settings_page
|
||||
gateway_self_hosted_base:
|
||||
enabled: false
|
||||
release_tier: experimental
|
||||
build_modes: [debug, profile, release]
|
||||
description: Mobile self-hosted base connection controls
|
||||
ui_surface: settings_page
|
||||
gateway_advanced_custom_mode:
|
||||
enabled: false
|
||||
release_tier: experimental
|
||||
build_modes: [debug, profile, release]
|
||||
description: Mobile advanced custom override mode
|
||||
ui_surface: settings_page
|
||||
gateway_setup_code:
|
||||
enabled: true
|
||||
release_tier: experimental
|
||||
@ -367,6 +379,18 @@ desktop:
|
||||
build_modes: [debug, profile, release]
|
||||
description: Desktop Vault server integration section
|
||||
ui_surface: settings_page
|
||||
gateway_self_hosted_base:
|
||||
enabled: false
|
||||
release_tier: experimental
|
||||
build_modes: [debug, profile, release]
|
||||
description: Desktop self-hosted base connection controls
|
||||
ui_surface: settings_page
|
||||
gateway_advanced_custom_mode:
|
||||
enabled: false
|
||||
release_tier: experimental
|
||||
build_modes: [debug, profile, release]
|
||||
description: Desktop advanced custom override mode
|
||||
ui_surface: settings_page
|
||||
gateway_setup_code:
|
||||
enabled: false
|
||||
release_tier: experimental
|
||||
@ -528,6 +552,18 @@ web:
|
||||
build_modes: []
|
||||
description: Web does not expose vault server integration
|
||||
ui_surface: web_settings_page
|
||||
gateway_self_hosted_base:
|
||||
enabled: false
|
||||
release_tier: experimental
|
||||
build_modes: []
|
||||
description: Web does not expose self-hosted base connection controls
|
||||
ui_surface: web_settings_page
|
||||
gateway_advanced_custom_mode:
|
||||
enabled: false
|
||||
release_tier: experimental
|
||||
build_modes: []
|
||||
description: Web does not expose advanced custom override mode
|
||||
ui_surface: web_settings_page
|
||||
gateway_setup_code:
|
||||
enabled: false
|
||||
release_tier: experimental
|
||||
|
||||
@ -36,197 +36,210 @@ extension SettingsPageGatewayMixinInternal on SettingsPageStateInternal {
|
||||
UiFeatureAccess uiFeatures,
|
||||
) {
|
||||
if (!widget.showSectionTabs) {
|
||||
return buildUnifiedGatewaySectionsInternal(
|
||||
return [
|
||||
buildGatewayOverviewCardInternal(
|
||||
context,
|
||||
controller,
|
||||
settings,
|
||||
uiFeatures,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
buildOnlineAccountCardInternal(context, controller, settings),
|
||||
const SizedBox(height: 16),
|
||||
buildAcpBridgeServerModeCardInternal(
|
||||
context,
|
||||
controller,
|
||||
settings,
|
||||
uiFeatures: uiFeatures,
|
||||
),
|
||||
if (uiFeatures.supportsGatewayAdvancedCustomMode) ...[
|
||||
const SizedBox(height: 16),
|
||||
...buildGatewayAdvancedSectionsInternal(
|
||||
context,
|
||||
controller,
|
||||
settings,
|
||||
uiFeatures,
|
||||
),
|
||||
],
|
||||
];
|
||||
}
|
||||
final selectedSubTab =
|
||||
!uiFeatures.supportsGatewayAdvancedCustomMode &&
|
||||
integrationSubTabInternal == GatewayIntegrationSubTabInternal.advancedConfig
|
||||
? GatewayIntegrationSubTabInternal.vault
|
||||
: integrationSubTabInternal;
|
||||
final effectiveTabLabel = switch (selectedSubTab) {
|
||||
GatewayIntegrationSubTabInternal.gateway => appText(
|
||||
'用户登录状态',
|
||||
'User Login State',
|
||||
),
|
||||
GatewayIntegrationSubTabInternal.vault => appText(
|
||||
'基础连接配置',
|
||||
'Base Connection Configuration',
|
||||
),
|
||||
GatewayIntegrationSubTabInternal.llm => appText(
|
||||
'高级自定义模式',
|
||||
'Advanced Custom Mode',
|
||||
),
|
||||
GatewayIntegrationSubTabInternal.acp => appText(
|
||||
'高级自定义模式',
|
||||
'Advanced Custom Mode',
|
||||
),
|
||||
GatewayIntegrationSubTabInternal.skills => appText(
|
||||
'高级自定义模式',
|
||||
'Advanced Custom Mode',
|
||||
),
|
||||
GatewayIntegrationSubTabInternal.advancedConfig => appText(
|
||||
'高级自定义模式',
|
||||
'Advanced Custom Mode',
|
||||
),
|
||||
};
|
||||
return [
|
||||
buildGatewayOverviewCardInternal(
|
||||
context,
|
||||
controller,
|
||||
settings,
|
||||
uiFeatures,
|
||||
);
|
||||
}
|
||||
final advancedEditable =
|
||||
settings.acpBridgeServerModeConfig.mode ==
|
||||
AcpBridgeServerMode.advancedCustom;
|
||||
final tabLabel = switch (integrationSubTabInternal) {
|
||||
GatewayIntegrationSubTabInternal.gateway => 'OpenClaw Gateway',
|
||||
GatewayIntegrationSubTabInternal.vault => appText(
|
||||
'Vault Server',
|
||||
'Vault Server',
|
||||
),
|
||||
GatewayIntegrationSubTabInternal.llm => appText(
|
||||
'LLM 接入点',
|
||||
'LLM Endpoints',
|
||||
),
|
||||
GatewayIntegrationSubTabInternal.acp => appText(
|
||||
'ACP 外部接入',
|
||||
'External ACP',
|
||||
),
|
||||
GatewayIntegrationSubTabInternal.skills => appText(
|
||||
'SKILLS 目录授权',
|
||||
'SKILLS Directory Authorization',
|
||||
),
|
||||
GatewayIntegrationSubTabInternal.advancedConfig => appText(
|
||||
'高级自定义配置',
|
||||
'Advanced Custom Configuration',
|
||||
),
|
||||
};
|
||||
return [
|
||||
const SizedBox(height: 16),
|
||||
SectionTabs(
|
||||
items: <String>[
|
||||
'OpenClaw Gateway',
|
||||
appText('Vault Server', 'Vault Server'),
|
||||
appText('LLM 接入点', 'LLM Endpoints'),
|
||||
appText('ACP 外部接入', 'External ACP'),
|
||||
appText('SKILLS 目录授权', 'SKILLS Directory Authorization'),
|
||||
appText('高级自定义配置', 'Advanced Custom Configuration'),
|
||||
appText('用户登录状态', 'User Login State'),
|
||||
appText('基础连接配置', 'Base Connection Configuration'),
|
||||
if (uiFeatures.supportsGatewayAdvancedCustomMode)
|
||||
appText('高级自定义模式', 'Advanced Custom Mode'),
|
||||
],
|
||||
value: tabLabel,
|
||||
value: effectiveTabLabel,
|
||||
onChanged: (value) => setStateInternal(() {
|
||||
integrationSubTabInternal = switch (value) {
|
||||
'OpenClaw Gateway' => GatewayIntegrationSubTabInternal.gateway,
|
||||
_ when value == appText('Vault Server', 'Vault Server') =>
|
||||
GatewayIntegrationSubTabInternal.vault,
|
||||
_ when value == appText('LLM 接入点', 'LLM Endpoints') =>
|
||||
GatewayIntegrationSubTabInternal.llm,
|
||||
_ when value == appText('ACP 外部接入', 'External ACP') =>
|
||||
GatewayIntegrationSubTabInternal.acp,
|
||||
_ when value == appText('用户登录状态', 'User Login State') =>
|
||||
GatewayIntegrationSubTabInternal.gateway,
|
||||
_
|
||||
when value ==
|
||||
appText('SKILLS 目录授权', 'SKILLS Directory Authorization') =>
|
||||
GatewayIntegrationSubTabInternal.skills,
|
||||
appText(
|
||||
'基础连接配置',
|
||||
'Base Connection Configuration',
|
||||
) =>
|
||||
GatewayIntegrationSubTabInternal.vault,
|
||||
_
|
||||
when value ==
|
||||
appText('高级自定义模式', 'Advanced Custom Mode') =>
|
||||
GatewayIntegrationSubTabInternal.advancedConfig,
|
||||
_ => GatewayIntegrationSubTabInternal.advancedConfig,
|
||||
};
|
||||
}),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
...switch (integrationSubTabInternal) {
|
||||
...switch (selectedSubTab) {
|
||||
GatewayIntegrationSubTabInternal.gateway => <Widget>[
|
||||
Opacity(
|
||||
opacity: advancedEditable ? 1 : 0.72,
|
||||
child: IgnorePointer(
|
||||
ignoring: !advancedEditable,
|
||||
child: buildCollapsibleGatewaySectionInternal(
|
||||
context: context,
|
||||
title: 'OpenClaw Gateway',
|
||||
expanded: openClawGatewayExpandedInternal,
|
||||
onChanged: (value) => setStateInternal(() {
|
||||
openClawGatewayExpandedInternal = value;
|
||||
}),
|
||||
child: buildOpenClawGatewayCardInternal(
|
||||
context,
|
||||
controller,
|
||||
settings,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
buildOnlineAccountCardInternal(context, controller, settings),
|
||||
],
|
||||
GatewayIntegrationSubTabInternal.vault => <Widget>[
|
||||
if (uiFeatures.supportsVaultServer)
|
||||
Opacity(
|
||||
opacity: advancedEditable ? 1 : 0.72,
|
||||
child: IgnorePointer(
|
||||
ignoring: !advancedEditable,
|
||||
child: buildCollapsibleGatewaySectionInternal(
|
||||
context: context,
|
||||
title: appText('Vault Server', 'Vault Server'),
|
||||
expanded: vaultServerExpandedInternal,
|
||||
onChanged: (value) => setStateInternal(() {
|
||||
vaultServerExpandedInternal = value;
|
||||
}),
|
||||
child: buildVaultProviderCardInternal(
|
||||
context,
|
||||
controller,
|
||||
settings,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
else
|
||||
SurfaceCard(
|
||||
borderWidth: settingsHairlineBorderWidthInternal,
|
||||
child: Text(
|
||||
appText(
|
||||
'当前发布配置未开放 Vault Server 参数。',
|
||||
'Vault Server settings are disabled in this release configuration.',
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
GatewayIntegrationSubTabInternal.llm => <Widget>[
|
||||
Opacity(
|
||||
opacity: advancedEditable ? 1 : 0.72,
|
||||
child: IgnorePointer(
|
||||
ignoring: !advancedEditable,
|
||||
child: buildCollapsibleGatewaySectionInternal(
|
||||
context: context,
|
||||
title: appText('LLM 接入点', 'LLM Endpoints'),
|
||||
expanded: aiGatewayExpandedInternal,
|
||||
onChanged: (value) => setStateInternal(() {
|
||||
aiGatewayExpandedInternal = value;
|
||||
}),
|
||||
child: buildLlmEndpointManagerInternal(
|
||||
context,
|
||||
controller,
|
||||
settings,
|
||||
),
|
||||
),
|
||||
),
|
||||
buildAcpBridgeServerModeCardInternal(
|
||||
context,
|
||||
controller,
|
||||
settings,
|
||||
uiFeatures: uiFeatures,
|
||||
),
|
||||
],
|
||||
GatewayIntegrationSubTabInternal.acp => <Widget>[
|
||||
Opacity(
|
||||
opacity: advancedEditable ? 1 : 0.72,
|
||||
child: IgnorePointer(
|
||||
ignoring: !advancedEditable,
|
||||
child: buildCollapsibleGatewaySectionInternal(
|
||||
context: context,
|
||||
title: appText(
|
||||
'外部 ACP Server Endpoint',
|
||||
'External ACP Server Endpoints',
|
||||
),
|
||||
expanded: externalAcpExpandedInternal,
|
||||
onChanged: (value) => setStateInternal(() {
|
||||
externalAcpExpandedInternal = value;
|
||||
}),
|
||||
child: buildExternalAcpEndpointManagerInternal(
|
||||
context,
|
||||
controller,
|
||||
settings,
|
||||
),
|
||||
GatewayIntegrationSubTabInternal.llm => const <Widget>[],
|
||||
GatewayIntegrationSubTabInternal.acp => const <Widget>[],
|
||||
GatewayIntegrationSubTabInternal.skills => const <Widget>[],
|
||||
GatewayIntegrationSubTabInternal.advancedConfig =>
|
||||
uiFeatures.supportsGatewayAdvancedCustomMode
|
||||
? <Widget>[
|
||||
...buildGatewayAdvancedSectionsInternal(
|
||||
context,
|
||||
controller,
|
||||
settings,
|
||||
uiFeatures,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
GatewayIntegrationSubTabInternal.skills => <Widget>[
|
||||
Opacity(
|
||||
opacity: advancedEditable ? 1 : 0.72,
|
||||
child: IgnorePointer(
|
||||
ignoring: !advancedEditable,
|
||||
child: buildCollapsibleGatewaySectionInternal(
|
||||
context: context,
|
||||
title: appText('SKILLS 目录授权', 'SKILLS Directory Authorization'),
|
||||
expanded: skillsDirectoryAuthorizationExpandedInternal,
|
||||
onChanged: (value) => setStateInternal(() {
|
||||
skillsDirectoryAuthorizationExpandedInternal = value;
|
||||
}),
|
||||
child: SkillDirectoryAuthorizationCard(
|
||||
controller: controller,
|
||||
showHeader: false,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
GatewayIntegrationSubTabInternal.advancedConfig => <Widget>[
|
||||
buildOnlineAccountCardInternal(context, controller, settings),
|
||||
const SizedBox(height: 16),
|
||||
buildAcpBridgeServerModeCardInternal(context, controller, settings),
|
||||
],
|
||||
]
|
||||
: <Widget>[],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
List<Widget> buildUnifiedGatewaySectionsInternal(
|
||||
Widget buildGatewayOverviewCardInternal(
|
||||
BuildContext context,
|
||||
AppController controller,
|
||||
SettingsSnapshot settings,
|
||||
UiFeatureAccess uiFeatures,
|
||||
) {
|
||||
final accountController = controller.settingsController;
|
||||
final modeConfig = settings.acpBridgeServerModeConfig;
|
||||
final supportsSelfHosted = uiFeatures.supportsGatewaySelfHostedBase;
|
||||
final supportsAdvancedOverrides =
|
||||
uiFeatures.supportsGatewayAdvancedCustomMode;
|
||||
final loginStatus = accountController.accountMfaRequired
|
||||
? appText('MFA 待验证', 'MFA pending')
|
||||
: accountController.accountBusy
|
||||
? appText('登录中', 'Signing in')
|
||||
: accountController.accountSignedIn
|
||||
? appText('已登录', 'Signed in')
|
||||
: appText('未登录', 'Signed out');
|
||||
final defaultSource = supportsSelfHosted && modeConfig.usesSelfHostedBase
|
||||
? appText('自建服务', 'Self-hosted')
|
||||
: appText('svc.plus 提供', 'svc.plus provided');
|
||||
final hasAdvancedOverrides =
|
||||
supportsAdvancedOverrides &&
|
||||
modeConfig.mode == AcpBridgeServerMode.advancedCustom;
|
||||
return SurfaceCard(
|
||||
key: const ValueKey('gateway-configuration-overview-card'),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
appText('最终生效配置概览', 'Effective Configuration Overview'),
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
supportsAdvancedOverrides
|
||||
? appText(
|
||||
'先确认登录状态,再确定默认连接来源,最后按需用高级自定义模式覆盖默认配置。',
|
||||
'Confirm login state first, choose the default connection source second, then apply advanced custom overrides only where needed.',
|
||||
)
|
||||
: appText(
|
||||
'先确认登录状态,再查看当前默认连接来源。',
|
||||
'Confirm login state first, then review the current default connection source.',
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Wrap(
|
||||
spacing: 12,
|
||||
runSpacing: 12,
|
||||
children: [
|
||||
StatusChipInternal(
|
||||
key: const ValueKey('gateway-overview-login-status'),
|
||||
label: '${appText('登录状态', 'Login')}: $loginStatus',
|
||||
tone: accountController.accountSignedIn
|
||||
? StatusChipToneInternal.ready
|
||||
: StatusChipToneInternal.idle,
|
||||
),
|
||||
StatusChipInternal(
|
||||
key: const ValueKey('gateway-overview-default-source'),
|
||||
label:
|
||||
'${appText('默认连接来源', 'Default Source')}: $defaultSource',
|
||||
tone: StatusChipToneInternal.ready,
|
||||
),
|
||||
if (supportsAdvancedOverrides)
|
||||
StatusChipInternal(
|
||||
key: const ValueKey('gateway-overview-advanced-override'),
|
||||
label:
|
||||
'${appText('高级覆盖', 'Advanced Override')}: ${hasAdvancedOverrides ? appText('已启用', 'Enabled') : appText('未启用', 'Disabled')}',
|
||||
tone: hasAdvancedOverrides
|
||||
? StatusChipToneInternal.ready
|
||||
: StatusChipToneInternal.idle,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> buildGatewayAdvancedSectionsInternal(
|
||||
BuildContext context,
|
||||
AppController controller,
|
||||
SettingsSnapshot settings,
|
||||
@ -235,7 +248,38 @@ extension SettingsPageGatewayMixinInternal on SettingsPageStateInternal {
|
||||
final advancedEditable =
|
||||
settings.acpBridgeServerModeConfig.mode ==
|
||||
AcpBridgeServerMode.advancedCustom;
|
||||
return [
|
||||
final sections = <Widget>[
|
||||
SurfaceCard(
|
||||
key: const ValueKey('gateway-advanced-override-intro'),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
appText('高级自定义模式', 'Advanced Custom Mode'),
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
appText(
|
||||
'这里的配置只负责覆盖默认配置,不会把基础连接配置替换成另一套平行模式。未覆盖的字段继续继承当前默认连接来源。',
|
||||
'These settings only override the default configuration. They do not replace the base connection model with a parallel mode. Any field you do not override keeps inheriting from the current default source.',
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
FilledButton.tonal(
|
||||
key: const ValueKey('acp-bridge-advanced-reset'),
|
||||
onPressed: advancedEditable
|
||||
? () => resetAcpBridgeServerAdvancedOverridesInternal(
|
||||
controller,
|
||||
settings,
|
||||
)
|
||||
: null,
|
||||
child: Text(appText('清空高级覆盖', 'Clear Advanced Overrides')),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Opacity(
|
||||
opacity: advancedEditable ? 1 : 0.72,
|
||||
child: IgnorePointer(
|
||||
@ -349,6 +393,21 @@ extension SettingsPageGatewayMixinInternal on SettingsPageStateInternal {
|
||||
),
|
||||
),
|
||||
];
|
||||
return sections;
|
||||
}
|
||||
|
||||
List<Widget> buildUnifiedGatewaySectionsInternal(
|
||||
BuildContext context,
|
||||
AppController controller,
|
||||
SettingsSnapshot settings,
|
||||
UiFeatureAccess uiFeatures,
|
||||
) {
|
||||
return buildGatewayAdvancedSectionsInternal(
|
||||
context,
|
||||
controller,
|
||||
settings,
|
||||
uiFeatures,
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildLlmEndpointManagerInternal(
|
||||
|
||||
@ -6,6 +6,7 @@ import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../app/app_controller.dart';
|
||||
import '../../app/ui_feature_manifest.dart';
|
||||
import '../../i18n/app_language.dart';
|
||||
import '../../runtime/gateway_acp_client.dart';
|
||||
import '../../runtime/runtime_controllers.dart';
|
||||
@ -234,7 +235,7 @@ extension SettingsPageGatewayAcpMixinInternal on SettingsPageStateInternal {
|
||||
? accountSession!.email.trim()
|
||||
: accountSession?.name.trim().isNotEmpty == true
|
||||
? accountSession!.name.trim()
|
||||
: appText('在线账户', 'Online Account');
|
||||
: appText('用户登录状态', 'User Login State');
|
||||
final sessionStatusText = accountSignedIn
|
||||
? appText('已登录:$signedInLabel', 'Signed in: $signedInLabel')
|
||||
: accountMfaRequired
|
||||
@ -261,15 +262,15 @@ extension SettingsPageGatewayAcpMixinInternal on SettingsPageStateInternal {
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
appText('在线账户', 'Online Account'),
|
||||
appText('用户登录状态', 'User Login State'),
|
||||
style: theme.textTheme.headlineMedium,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
appText(
|
||||
'请先登录 ACP Bridge Server',
|
||||
'Please sign in to ACP Bridge Server',
|
||||
'先完成账户登录,再同步或校验默认连接配置。',
|
||||
'Sign in first, then sync or verify the default connection configuration.',
|
||||
),
|
||||
style: theme.textTheme.titleMedium?.copyWith(
|
||||
color: theme.textTheme.bodyMedium?.color?.withValues(
|
||||
@ -343,8 +344,8 @@ extension SettingsPageGatewayAcpMixinInternal on SettingsPageStateInternal {
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
appText(
|
||||
'这里继续只负责在线账户身份、MFA、工作区与同步摘要。ACP Bridge Server 的本地连接与高级配置在下面统一收口。',
|
||||
'This card focuses on online account identity, MFA, workspace, and sync summary. Local ACP Bridge Server connection and advanced config are unified below.',
|
||||
'这里仅描述认证状态本身:登录、MFA、同步状态与当前账户身份。默认连接来源和高级覆盖在下面分别配置。',
|
||||
'This card describes authentication only: sign-in, MFA, sync state, and current account identity. The default connection source and advanced overrides are configured below.',
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
@ -371,7 +372,7 @@ extension SettingsPageGatewayAcpMixinInternal on SettingsPageStateInternal {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
appText('在线账户同步摘要', 'Online account sync summary'),
|
||||
appText('登录状态摘要', 'Login state summary'),
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
@ -380,7 +381,7 @@ extension SettingsPageGatewayAcpMixinInternal on SettingsPageStateInternal {
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
'${appText('在线账户', 'Online Account')}: ${settings.accountUsername.trim().isEmpty ? appText('未填写', 'Not set') : settings.accountUsername}',
|
||||
'${appText('账户标识', 'Account')}: ${settings.accountUsername.trim().isEmpty ? appText('未填写', 'Not set') : settings.accountUsername}',
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
@ -446,21 +447,24 @@ extension SettingsPageGatewayAcpMixinInternal on SettingsPageStateInternal {
|
||||
Widget buildAcpBridgeServerModeCardInternal(
|
||||
BuildContext context,
|
||||
AppController controller,
|
||||
SettingsSnapshot settings,
|
||||
) {
|
||||
SettingsSnapshot settings, {
|
||||
required UiFeatureAccess uiFeatures,
|
||||
}) {
|
||||
syncAcpBridgeServerModeDraftControllersInternal(settings);
|
||||
final modeConfig = settings.acpBridgeServerModeConfig;
|
||||
final supportsSelfHosted = uiFeatures.supportsGatewaySelfHostedBase;
|
||||
final effectiveUsesSelfHosted =
|
||||
supportsSelfHosted && modeConfig.usesSelfHostedBase;
|
||||
final effectiveUsesCloudSync = !effectiveUsesSelfHosted;
|
||||
final accountController = controller.settingsController;
|
||||
final accountSyncState = accountController.accountSyncState;
|
||||
final accountSignedIn = accountController.accountSignedIn;
|
||||
final accountBusy = accountController.accountBusy;
|
||||
final cloudSync = modeConfig.cloudSynced;
|
||||
final remoteSummary = cloudSync.remoteServerSummary;
|
||||
final currentSource = switch (modeConfig.sourceTag) {
|
||||
'cloudSynced' => appText('在线账户', 'Online Account'),
|
||||
'selfHosted' => appText('本地账户', 'Local Account'),
|
||||
_ => appText('高级模式', 'Advanced Mode'),
|
||||
};
|
||||
final currentSource = effectiveUsesSelfHosted
|
||||
? appText('自建服务', 'Self-hosted')
|
||||
: appText('svc.plus 提供', 'svc.plus provided');
|
||||
final syncStatus = accountSyncState?.syncState.trim().isNotEmpty == true
|
||||
? accountSyncState!.syncState
|
||||
: appText('未同步', 'Not synced');
|
||||
@ -475,17 +479,22 @@ extension SettingsPageGatewayAcpMixinInternal on SettingsPageStateInternal {
|
||||
children: [
|
||||
Text(
|
||||
appText(
|
||||
'ACP Bridge Server 连接模式',
|
||||
'ACP Bridge Server Connection Mode',
|
||||
'基础连接配置',
|
||||
'Base Connection Configuration',
|
||||
),
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
appText(
|
||||
'在线账户负责云端同步,本地账户负责连接 ACP Bridge Server,高级模式是在本地账户基础上再叠加 advanced config 覆盖层。App 只负责配置、会话、安全存储与连接编排,不承载服务端逻辑。',
|
||||
'Online account handles cloud sync, local account connects to ACP Bridge Server, and advanced mode layers advanced config on top of the local account. The app stays a pure client for configuration, session handling, secure storage, and connection orchestration.',
|
||||
),
|
||||
supportsSelfHosted
|
||||
? appText(
|
||||
'这里维护默认连接来源与默认凭据。默认来源可以是 svc.plus 提供的托管配置,也可以是自建 ACP Bridge Server。高级自定义模式只在这层默认配置上做覆盖。',
|
||||
'This section maintains the default connection source and default credentials. The default source can come from svc.plus managed configuration or from a self-hosted ACP Bridge Server. Advanced custom mode only overrides this base layer.',
|
||||
)
|
||||
: appText(
|
||||
'这里维护默认连接来源与默认凭据。当前默认 UI 仅展示 svc.plus 提供的托管配置入口;实验性的自建与高级覆盖能力保留在代码模块中。',
|
||||
'This section maintains the default connection source and default credentials. The default UI currently exposes only the svc.plus managed entry point, while experimental self-hosted and advanced override capabilities remain in the codebase.',
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Wrap(
|
||||
@ -494,8 +503,8 @@ extension SettingsPageGatewayAcpMixinInternal on SettingsPageStateInternal {
|
||||
children: [
|
||||
ChoiceChip(
|
||||
key: const ValueKey('acp-bridge-mode-cloud'),
|
||||
label: Text(appText('在线账户', 'Online Account')),
|
||||
selected: modeConfig.mode == AcpBridgeServerMode.cloudSynced,
|
||||
label: Text(appText('svc.plus 提供', 'svc.plus provided')),
|
||||
selected: effectiveUsesCloudSync,
|
||||
onSelected: (_) => saveSettingsInternal(
|
||||
controller,
|
||||
settings.copyWith(
|
||||
@ -506,34 +515,21 @@ extension SettingsPageGatewayAcpMixinInternal on SettingsPageStateInternal {
|
||||
),
|
||||
),
|
||||
),
|
||||
ChoiceChip(
|
||||
key: const ValueKey('acp-bridge-mode-self-hosted'),
|
||||
label: Text(appText('本地账户', 'Local Account')),
|
||||
selected: modeConfig.mode == AcpBridgeServerMode.selfHosted,
|
||||
onSelected: (_) => saveSettingsInternal(
|
||||
controller,
|
||||
settings.copyWith(
|
||||
accountLocalMode: true,
|
||||
acpBridgeServerModeConfig: modeConfig.copyWith(
|
||||
mode: AcpBridgeServerMode.selfHosted,
|
||||
if (supportsSelfHosted)
|
||||
ChoiceChip(
|
||||
key: const ValueKey('acp-bridge-mode-self-hosted'),
|
||||
label: Text(appText('自建服务', 'Self-hosted')),
|
||||
selected: effectiveUsesSelfHosted,
|
||||
onSelected: (_) => saveSettingsInternal(
|
||||
controller,
|
||||
settings.copyWith(
|
||||
accountLocalMode: true,
|
||||
acpBridgeServerModeConfig: modeConfig.copyWith(
|
||||
mode: AcpBridgeServerMode.selfHosted,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
ChoiceChip(
|
||||
key: const ValueKey('acp-bridge-mode-advanced'),
|
||||
label: Text(appText('高级模式', 'Advanced Mode')),
|
||||
selected: modeConfig.mode == AcpBridgeServerMode.advancedCustom,
|
||||
onSelected: (_) => saveSettingsInternal(
|
||||
controller,
|
||||
settings.captureAcpBridgeServerAdvancedOverrides().copyWith(
|
||||
acpBridgeServerModeConfig: settings
|
||||
.captureAcpBridgeServerAdvancedOverrides()
|
||||
.acpBridgeServerModeConfig
|
||||
.copyWith(mode: AcpBridgeServerMode.advancedCustom),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
@ -542,7 +538,8 @@ extension SettingsPageGatewayAcpMixinInternal on SettingsPageStateInternal {
|
||||
runSpacing: 12,
|
||||
children: [
|
||||
StatusChipInternal(
|
||||
label: '${appText('当前模式', 'Mode')}: $currentSource',
|
||||
label:
|
||||
'${appText('默认连接来源', 'Default Source')}: $currentSource',
|
||||
tone: StatusChipToneInternal.ready,
|
||||
),
|
||||
StatusChipInternal(
|
||||
@ -551,20 +548,28 @@ extension SettingsPageGatewayAcpMixinInternal on SettingsPageStateInternal {
|
||||
? StatusChipToneInternal.ready
|
||||
: StatusChipToneInternal.idle,
|
||||
),
|
||||
if (uiFeatures.supportsGatewayAdvancedCustomMode)
|
||||
StatusChipInternal(
|
||||
label:
|
||||
'${appText('高级覆盖', 'Advanced Override')}: ${modeConfig.mode == AcpBridgeServerMode.advancedCustom ? appText('已启用', 'Enabled') : appText('未启用', 'Disabled')}',
|
||||
tone: modeConfig.mode == AcpBridgeServerMode.advancedCustom
|
||||
? StatusChipToneInternal.ready
|
||||
: StatusChipToneInternal.idle,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
...switch (modeConfig.mode) {
|
||||
AcpBridgeServerMode.cloudSynced => <Widget>[
|
||||
...switch (effectiveUsesCloudSync) {
|
||||
true => <Widget>[
|
||||
Text(
|
||||
accountSignedIn
|
||||
? appText(
|
||||
'已登录在线账户,可直接同步云端 ACP Bridge Server 默认配置。',
|
||||
'Signed in to the online account. You can sync the cloud ACP Bridge Server defaults directly.',
|
||||
'当前默认来源为 svc.plus 提供的托管配置。你可以直接同步远端默认配置。',
|
||||
'The current default source is managed by svc.plus. You can sync the remote defaults directly.',
|
||||
)
|
||||
: appText(
|
||||
'当前未登录在线账户。建议先登录,再从云端同步默认配置。',
|
||||
'No online account is signed in. Sign in first, then sync the default configuration from the cloud.',
|
||||
'当前默认来源为 svc.plus 提供的托管配置,但你还没有登录。建议先完成登录,再同步默认配置。',
|
||||
'The current default source is managed by svc.plus, but no account is signed in yet. Sign in first, then sync the default configuration.',
|
||||
),
|
||||
),
|
||||
Text(
|
||||
@ -576,9 +581,10 @@ extension SettingsPageGatewayAcpMixinInternal on SettingsPageStateInternal {
|
||||
key: const ValueKey('acp-bridge-cloud-last-sync'),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
'${appText('高级覆盖', 'Advanced Override')}: ${remoteSummary.hasAdvancedOverrides ? appText('存在', 'Present') : appText('无', 'None')}',
|
||||
),
|
||||
if (uiFeatures.supportsGatewayAdvancedCustomMode)
|
||||
Text(
|
||||
'${appText('高级覆盖', 'Advanced Override')}: ${remoteSummary.hasAdvancedOverrides ? appText('存在', 'Present') : appText('无', 'None')}',
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
Wrap(
|
||||
spacing: 10,
|
||||
@ -601,34 +607,26 @@ extension SettingsPageGatewayAcpMixinInternal on SettingsPageStateInternal {
|
||||
],
|
||||
),
|
||||
],
|
||||
AcpBridgeServerMode.selfHosted => <Widget>[],
|
||||
AcpBridgeServerMode.advancedCustom => <Widget>[
|
||||
false => <Widget>[
|
||||
Text(
|
||||
appText(
|
||||
'高级模式 = 本地账户 + advanced config。下面先保留本地 ACP Bridge Server 连接,再把 OpenClaw Gateway / Vault Server / LLM Endpoint / 外部 ACP Server endpoint / SKILLS 目录 当作覆盖层。未覆盖的值继续继承当前基础模式。',
|
||||
'Advanced mode = local account + advanced config. Keep the local ACP Bridge Server connection below, then treat the OpenClaw Gateway / Vault Server / LLM Endpoint / external ACP server endpoint / SKILLS directory as overrides. Fields you do not override keep inheriting from the current base mode.',
|
||||
'当前默认来源为自建服务。下面的 ACP Bridge Server URL、用户和密码会作为默认连接配置使用;如果启用了高级自定义模式,其它集成项只会覆盖这份默认配置。',
|
||||
'The current default source is self-hosted. The ACP Bridge Server URL, username, and password below act as the default connection configuration. If advanced custom mode is enabled, the other integrations only override this base layer.',
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
FilledButton.tonal(
|
||||
key: const ValueKey('acp-bridge-advanced-reset'),
|
||||
onPressed: () => resetAcpBridgeServerAdvancedOverridesInternal(
|
||||
controller,
|
||||
settings,
|
||||
),
|
||||
child: Text(appText('清空高级覆盖', 'Clear Advanced Overrides')),
|
||||
),
|
||||
],
|
||||
},
|
||||
const SizedBox(height: 16),
|
||||
buildAcpBridgeServerSelfHostedPanelInternal(
|
||||
context,
|
||||
controller,
|
||||
settings,
|
||||
targetMode: modeConfig.mode == AcpBridgeServerMode.advancedCustom
|
||||
? AcpBridgeServerMode.advancedCustom
|
||||
: AcpBridgeServerMode.selfHosted,
|
||||
),
|
||||
if (supportsSelfHosted) ...[
|
||||
const SizedBox(height: 16),
|
||||
buildAcpBridgeServerSelfHostedPanelInternal(
|
||||
context,
|
||||
controller,
|
||||
settings,
|
||||
targetMode: modeConfig.mode == AcpBridgeServerMode.advancedCustom
|
||||
? AcpBridgeServerMode.advancedCustom
|
||||
: AcpBridgeServerMode.selfHosted,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@ -91,8 +91,17 @@ void main() {
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.text('OpenClaw Gateway'), findsWidgets);
|
||||
expect(find.text('LLM 接入点'), findsWidgets);
|
||||
expect(find.text('用户登录状态'), findsWidgets);
|
||||
expect(find.text('基础连接配置'), findsWidgets);
|
||||
expect(find.text('高级自定义模式'), findsNothing);
|
||||
expect(
|
||||
find.byKey(const ValueKey('gateway-configuration-overview-card')),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(
|
||||
find.byKey(const ValueKey('gateway-overview-advanced-override')),
|
||||
findsNothing,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
library;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:xworkmate/features/settings/settings_page.dart';
|
||||
import 'package:xworkmate/models/app_models.dart';
|
||||
|
||||
@ -27,7 +28,16 @@ void main() {
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.text('OpenClaw Gateway'), findsWidgets);
|
||||
expect(find.text('Vault Server'), findsOneWidget);
|
||||
expect(find.text('用户登录状态'), findsWidgets);
|
||||
expect(find.text('基础连接配置'), findsWidgets);
|
||||
expect(find.text('高级自定义模式'), findsNothing);
|
||||
expect(
|
||||
find.byKey(const ValueKey('gateway-configuration-overview-card')),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(
|
||||
find.byKey(const ValueKey('gateway-overview-advanced-override')),
|
||||
findsNothing,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ library;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:xworkmate/app/ui_feature_manifest.dart';
|
||||
import 'package:xworkmate/features/settings/settings_page_core.dart';
|
||||
import 'package:xworkmate/models/app_models.dart';
|
||||
|
||||
@ -10,9 +11,19 @@ import '../test_support.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets(
|
||||
'SettingsPage shows ACP bridge server mode card in advanced custom config',
|
||||
'SettingsPage shows base connection card when self-hosted base is enabled',
|
||||
(WidgetTester tester) async {
|
||||
final controller = await createTestController(tester);
|
||||
final manifest = UiFeatureManifest.fallback().copyWithFeature(
|
||||
platform: UiFeaturePlatform.desktop,
|
||||
module: 'settings',
|
||||
feature: 'gateway_self_hosted_base',
|
||||
enabled: true,
|
||||
releaseTier: UiFeatureReleaseTier.experimental,
|
||||
);
|
||||
final controller = await createTestController(
|
||||
tester,
|
||||
uiFeatureManifest: manifest,
|
||||
);
|
||||
controller.openSettings(tab: SettingsTab.gateway);
|
||||
|
||||
await pumpPage(
|
||||
@ -22,12 +33,13 @@ void main() {
|
||||
initialTab: SettingsTab.gateway,
|
||||
showSectionTabs: true,
|
||||
),
|
||||
platform: TargetPlatform.macOS,
|
||||
);
|
||||
|
||||
await tester.tap(find.byKey(const ValueKey('section-tab-高级自定义配置')));
|
||||
await tester.tap(find.byKey(const ValueKey('section-tab-基础连接配置')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('ACP Bridge Server 连接模式'), findsOneWidget);
|
||||
expect(find.text('基础连接配置'), findsWidgets);
|
||||
expect(
|
||||
find.byKey(const ValueKey('acp-bridge-mode-cloud')),
|
||||
findsOneWidget,
|
||||
@ -38,7 +50,7 @@ void main() {
|
||||
);
|
||||
expect(
|
||||
find.byKey(const ValueKey('acp-bridge-mode-advanced')),
|
||||
findsOneWidget,
|
||||
findsNothing,
|
||||
);
|
||||
expect(
|
||||
find.byKey(const ValueKey('acp-bridge-self-hosted-url')),
|
||||
|
||||
@ -76,10 +76,12 @@ class _DesktopServiceStub implements DesktopPlatformService {
|
||||
Future<AppController> _createControllerWithSkillAccessService(
|
||||
WidgetTester tester,
|
||||
SkillDirectoryAccessService skillDirectoryAccessService,
|
||||
{UiFeatureManifest? uiFeatureManifest,}
|
||||
) async {
|
||||
final controller = AppController(
|
||||
store: createIsolatedTestStore(enableSecureStorage: false),
|
||||
skillDirectoryAccessService: skillDirectoryAccessService,
|
||||
uiFeatureManifest: uiFeatureManifest,
|
||||
);
|
||||
addTearDown(controller.dispose);
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
@ -179,6 +181,24 @@ Future<void> _ensureVisible(WidgetTester tester, Finder finder) async {
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
UiFeatureManifest _gatewayAdvancedManifestInternal() {
|
||||
return UiFeatureManifest.fallback()
|
||||
.copyWithFeature(
|
||||
platform: UiFeaturePlatform.desktop,
|
||||
module: 'settings',
|
||||
feature: 'gateway_self_hosted_base',
|
||||
enabled: true,
|
||||
releaseTier: UiFeatureReleaseTier.experimental,
|
||||
)
|
||||
.copyWithFeature(
|
||||
platform: UiFeaturePlatform.desktop,
|
||||
module: 'settings',
|
||||
feature: 'gateway_advanced_custom_mode',
|
||||
enabled: true,
|
||||
releaseTier: UiFeatureReleaseTier.experimental,
|
||||
);
|
||||
}
|
||||
|
||||
void main() {
|
||||
testWidgets('SettingsPage theme chips update controller theme mode', (
|
||||
WidgetTester tester,
|
||||
@ -197,7 +217,7 @@ void main() {
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'SettingsPage gateway advanced config tab merges online account and ACP Bridge Server',
|
||||
'SettingsPage gateway home aligns with the architecture model and hides experimental controls by default',
|
||||
(WidgetTester tester) async {
|
||||
final controller = await createTestController(tester);
|
||||
controller.setSettingsTab(SettingsTab.gateway);
|
||||
@ -213,31 +233,111 @@ void main() {
|
||||
);
|
||||
|
||||
expect(
|
||||
find.byKey(const ValueKey('account-base-url-field')),
|
||||
findsNothing,
|
||||
);
|
||||
expect(find.byKey(const ValueKey('acp-bridge-mode-cloud')), findsNothing);
|
||||
|
||||
await tester.tap(find.byKey(const ValueKey('section-tab-高级自定义配置')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('ACP Bridge Server 连接模式'), findsOneWidget);
|
||||
expect(
|
||||
find.byKey(const ValueKey('account-base-url-field')),
|
||||
find.byKey(const ValueKey('gateway-configuration-overview-card')),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(
|
||||
find.byKey(const ValueKey('gateway-overview-login-status')),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(
|
||||
find.byKey(const ValueKey('gateway-overview-default-source')),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(
|
||||
find.byKey(const ValueKey('gateway-overview-advanced-override')),
|
||||
findsNothing,
|
||||
);
|
||||
expect(
|
||||
find.byKey(const ValueKey('section-tab-用户登录状态')),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(
|
||||
find.byKey(const ValueKey('section-tab-基础连接配置')),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(
|
||||
find.byKey(const ValueKey('section-tab-高级自定义模式')),
|
||||
findsNothing,
|
||||
);
|
||||
expect(find.byKey(const ValueKey('account-base-url-field')), findsOneWidget);
|
||||
expect(find.byKey(const ValueKey('account-username-field')), findsOneWidget);
|
||||
expect(find.byKey(const ValueKey('account-password-field')), findsOneWidget);
|
||||
expect(find.byKey(const ValueKey('acp-bridge-mode-cloud')), findsNothing);
|
||||
expect(
|
||||
find.byKey(const ValueKey('acp-bridge-mode-self-hosted')),
|
||||
findsNothing,
|
||||
);
|
||||
expect(find.text('OpenClaw Gateway'), findsNothing);
|
||||
expect(find.text('Vault Server'), findsNothing);
|
||||
expect(find.text('LLM 接入点'), findsNothing);
|
||||
expect(find.text('外部 ACP Server Endpoint'), findsNothing);
|
||||
expect(find.text('SKILLS 目录授权'), findsNothing);
|
||||
|
||||
await tester.tap(find.byKey(const ValueKey('section-tab-基础连接配置')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('基础连接配置'), findsWidgets);
|
||||
expect(
|
||||
find.byKey(const ValueKey('acp-bridge-mode-cloud')),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(
|
||||
find.byKey(const ValueKey('acp-bridge-mode-self-hosted')),
|
||||
findsOneWidget,
|
||||
findsNothing,
|
||||
);
|
||||
expect(
|
||||
find.byKey(const ValueKey('acp-bridge-mode-advanced')),
|
||||
findsNothing,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'SettingsPage gateway can show self-hosted and advanced custom controls when feature flags are enabled',
|
||||
(WidgetTester tester) async {
|
||||
final manifest = _gatewayAdvancedManifestInternal();
|
||||
final controller = await createTestController(
|
||||
tester,
|
||||
uiFeatureManifest: manifest,
|
||||
);
|
||||
controller.setSettingsTab(SettingsTab.gateway);
|
||||
|
||||
await pumpPage(
|
||||
tester,
|
||||
child: SettingsPage(
|
||||
controller: controller,
|
||||
initialTab: SettingsTab.gateway,
|
||||
showSectionTabs: true,
|
||||
),
|
||||
platform: TargetPlatform.macOS,
|
||||
);
|
||||
|
||||
expect(
|
||||
find.byKey(const ValueKey('section-tab-高级自定义模式')),
|
||||
findsOneWidget,
|
||||
);
|
||||
|
||||
await tester.tap(find.byKey(const ValueKey('section-tab-基础连接配置')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
find.byKey(const ValueKey('acp-bridge-mode-self-hosted')),
|
||||
findsOneWidget,
|
||||
);
|
||||
|
||||
await tester.tap(find.byKey(const ValueKey('section-tab-高级自定义模式')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
find.byKey(const ValueKey('gateway-advanced-override-intro')),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(find.text('OpenClaw Gateway'), findsWidgets);
|
||||
expect(find.text('Vault Server'), findsAtLeastNWidgets(1));
|
||||
expect(find.text('LLM 接入点'), findsOneWidget);
|
||||
expect(find.text('外部 ACP Server Endpoint'), findsOneWidget);
|
||||
expect(find.text('SKILLS 目录授权'), findsOneWidget);
|
||||
},
|
||||
);
|
||||
|
||||
@ -309,73 +409,37 @@ void main() {
|
||||
|
||||
await _pumpSettingsPage(tester, controller, tab: SettingsTab.gateway);
|
||||
|
||||
expect(find.text('OpenClaw Gateway'), findsWidgets);
|
||||
expect(find.text('LLM 接入点'), findsOneWidget);
|
||||
expect(find.text('Vault Server'), findsAtLeastNWidgets(1));
|
||||
expect(find.byKey(const ValueKey('gateway-test-button')), findsOneWidget);
|
||||
expect(find.byKey(const ValueKey('gateway-save-button')), findsNothing);
|
||||
expect(find.byKey(const ValueKey('gateway-apply-button')), findsOneWidget);
|
||||
expect(find.text('用户登录状态'), findsWidgets);
|
||||
expect(find.text('基础连接配置'), findsWidgets);
|
||||
expect(find.text('高级自定义模式'), findsNothing);
|
||||
expect(find.byKey(const ValueKey('account-base-url-field')), findsOneWidget);
|
||||
expect(find.byKey(const ValueKey('account-username-field')), findsOneWidget);
|
||||
expect(find.byKey(const ValueKey('account-password-field')), findsOneWidget);
|
||||
expect(find.byKey(const ValueKey('acp-bridge-mode-cloud')), findsNothing);
|
||||
expect(
|
||||
find.byKey(const ValueKey('gateway-profile-chip-0')),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(
|
||||
find.byKey(const ValueKey('gateway-profile-chip-1')),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(
|
||||
find.byKey(const ValueKey('gateway-profile-chip-2')),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(
|
||||
find.byKey(const ValueKey('gateway-profile-chip-3')),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(
|
||||
find.byKey(const ValueKey('gateway-profile-chip-4')),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byKey(const ValueKey('gateway-profile-chip-2')),
|
||||
matching: find.text('连接源 1(空)'),
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(find.text('自定义连接源 1(空)'), findsNothing);
|
||||
expect(
|
||||
find.byKey(const ValueKey('gateway-device-security-card')),
|
||||
findsOneWidget,
|
||||
);
|
||||
|
||||
expect(
|
||||
find.byKey(const ValueKey('vault-server-url-field')),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(
|
||||
find.byKey(const ValueKey('vault-root-access-token-field')),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(find.byKey(const ValueKey('ai-gateway-url-field')), findsOneWidget);
|
||||
expect(find.byKey(const ValueKey('gateway-mode-field')), findsNothing);
|
||||
expect(find.text('认证诊断'), findsNothing);
|
||||
expect(
|
||||
find.byKey(const ValueKey('external-acp-provider-add-button')),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(
|
||||
find.byKey(const ValueKey('settings-global-apply-button')),
|
||||
find.byKey(const ValueKey('acp-bridge-mode-self-hosted')),
|
||||
findsNothing,
|
||||
);
|
||||
expect(find.text('OpenClaw Gateway'), findsNothing);
|
||||
expect(find.text('Vault Server'), findsNothing);
|
||||
expect(find.text('LLM 接入点'), findsNothing);
|
||||
expect(find.text('外部 ACP Server Endpoint'), findsNothing);
|
||||
expect(find.text('SKILLS 目录授权'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('SettingsPage vault card exposes concrete K/V fields', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
final controller = await createTestController(tester);
|
||||
final controller = await createTestController(
|
||||
tester,
|
||||
uiFeatureManifest: _gatewayAdvancedManifestInternal(),
|
||||
);
|
||||
|
||||
await _pumpSettingsPage(tester, controller, tab: SettingsTab.gateway);
|
||||
|
||||
await tester.tap(find.byKey(const ValueKey('section-tab-高级自定义模式')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Vault Server'), findsAtLeastNWidgets(1));
|
||||
expect(find.text('VAULT_SERVER_URL'), findsOneWidget);
|
||||
expect(
|
||||
@ -389,10 +453,16 @@ void main() {
|
||||
testWidgets('SettingsPage integration tab exposes ACP provider endpoints', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
final controller = await createTestController(tester);
|
||||
final controller = await createTestController(
|
||||
tester,
|
||||
uiFeatureManifest: _gatewayAdvancedManifestInternal(),
|
||||
);
|
||||
|
||||
await _pumpSettingsPage(tester, controller, tab: SettingsTab.gateway);
|
||||
|
||||
await tester.tap(find.byKey(const ValueKey('section-tab-高级自定义模式')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('外部 ACP Server Endpoint'), findsOneWidget);
|
||||
expect(find.textContaining('Codex'), findsWidgets);
|
||||
expect(find.textContaining('OpenCode'), findsWidgets);
|
||||
@ -419,10 +489,16 @@ void main() {
|
||||
testWidgets('SettingsPage ACP wizard adds a custom provider card', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
final controller = await createTestController(tester);
|
||||
final controller = await createTestController(
|
||||
tester,
|
||||
uiFeatureManifest: _gatewayAdvancedManifestInternal(),
|
||||
);
|
||||
|
||||
await _pumpSettingsPage(tester, controller, tab: SettingsTab.gateway);
|
||||
|
||||
await tester.tap(find.byKey(const ValueKey('section-tab-高级自定义模式')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await _ensureVisible(
|
||||
tester,
|
||||
find.byKey(const ValueKey('external-acp-provider-add-button')),
|
||||
@ -455,10 +531,14 @@ void main() {
|
||||
final controller = await _createControllerWithSkillAccessService(
|
||||
tester,
|
||||
_FakeSkillDirectoryAccessService(userHomeDirectory: '/Users/tester'),
|
||||
uiFeatureManifest: _gatewayAdvancedManifestInternal(),
|
||||
);
|
||||
|
||||
await _pumpSettingsPage(tester, controller, tab: SettingsTab.gateway);
|
||||
|
||||
await tester.tap(find.byKey(const ValueKey('section-tab-高级自定义模式')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('~/.agents/skills'), findsOneWidget);
|
||||
expect(find.text('/Users/tester/.agents/skills'), findsOneWidget);
|
||||
expect(find.text('~/.codex/skills'), findsOneWidget);
|
||||
@ -485,9 +565,12 @@ void main() {
|
||||
),
|
||||
],
|
||||
),
|
||||
uiFeatureManifest: _gatewayAdvancedManifestInternal(),
|
||||
);
|
||||
|
||||
await _pumpSettingsPage(tester, controller, tab: SettingsTab.gateway);
|
||||
await tester.tap(find.byKey(const ValueKey('section-tab-高级自定义模式')));
|
||||
await tester.pumpAndSettle();
|
||||
await _ensureVisible(
|
||||
tester,
|
||||
find.byKey(const ValueKey('skill-directory-batch-add-button')),
|
||||
@ -542,9 +625,12 @@ paths:
|
||||
),
|
||||
],
|
||||
),
|
||||
uiFeatureManifest: _gatewayAdvancedManifestInternal(),
|
||||
);
|
||||
|
||||
await _pumpSettingsPage(tester, controller, tab: SettingsTab.gateway);
|
||||
await tester.tap(find.byKey(const ValueKey('section-tab-高级自定义模式')));
|
||||
await tester.pumpAndSettle();
|
||||
await _ensureVisible(
|
||||
tester,
|
||||
find.byKey(const ValueKey('skill-directory-batch-add-button')),
|
||||
@ -582,9 +668,12 @@ paths:
|
||||
final controller = await _createControllerWithSkillAccessService(
|
||||
tester,
|
||||
_FakeSkillDirectoryAccessService(userHomeDirectory: '/Users/tester'),
|
||||
uiFeatureManifest: _gatewayAdvancedManifestInternal(),
|
||||
);
|
||||
|
||||
await _pumpSettingsPage(tester, controller, tab: SettingsTab.gateway);
|
||||
await tester.tap(find.byKey(const ValueKey('section-tab-高级自定义模式')));
|
||||
await tester.pumpAndSettle();
|
||||
await _ensureVisible(
|
||||
tester,
|
||||
find.byKey(const ValueKey('skill-directory-batch-add-button')),
|
||||
@ -651,9 +740,14 @@ paths:
|
||||
testWidgets('SettingsPage external ACP section can collapse independently', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
final controller = await createTestController(tester);
|
||||
final controller = await createTestController(
|
||||
tester,
|
||||
uiFeatureManifest: _gatewayAdvancedManifestInternal(),
|
||||
);
|
||||
|
||||
await _pumpSettingsPage(tester, controller, tab: SettingsTab.gateway);
|
||||
await tester.tap(find.byKey(const ValueKey('section-tab-高级自定义模式')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await _ensureVisible(tester, find.text('外部 ACP Server Endpoint'));
|
||||
await tester.tap(find.text('外部 ACP Server Endpoint').first);
|
||||
@ -678,7 +772,10 @@ paths:
|
||||
testWidgets('SettingsPage external ACP card supports continuous input', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
final controller = await createTestController(tester);
|
||||
final controller = await createTestController(
|
||||
tester,
|
||||
uiFeatureManifest: _gatewayAdvancedManifestInternal(),
|
||||
);
|
||||
final customProfile = buildCustomExternalAcpEndpointProfile(
|
||||
controller.settingsDraft.externalAcpEndpoints,
|
||||
label: 'Initial Name',
|
||||
@ -694,6 +791,8 @@ paths:
|
||||
);
|
||||
|
||||
await _pumpSettingsPage(tester, controller, tab: SettingsTab.gateway);
|
||||
await tester.tap(find.byKey(const ValueKey('section-tab-高级自定义模式')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final labelField = find.byKey(
|
||||
ValueKey('external-acp-label-${customProfile.providerKey}'),
|
||||
|
||||
Loading…
Reference in New Issue
Block a user