xworkmate-app/lib/runtime/runtime_models_profiles.dart
2026-04-13 11:34:31 +08:00

539 lines
16 KiB
Dart

// ignore_for_file: unused_import, unnecessary_import
import 'dart:convert';
import '../i18n/app_language.dart';
import '../models/app_models.dart';
import 'runtime_models_connection.dart';
import 'runtime_models_configs.dart';
import 'runtime_models_settings_snapshot.dart';
import 'runtime_models_runtime_payloads.dart';
import 'runtime_models_gateway_entities.dart';
import 'runtime_models_multi_agent.dart';
String normalizeAuthorizedSkillDirectoryPath(String path) {
var trimmed = path.trim();
if (trimmed.isEmpty) {
return trimmed;
}
trimmed = trimmed.replaceFirst(RegExp(r'[\\/]+$'), '');
trimmed = trimmed.replaceFirst(
RegExp(r'([\\/])SKILL\.md$', caseSensitive: false),
'',
);
if (trimmed.length <= 1) {
return trimmed;
}
return trimmed.replaceFirst(RegExp(r'[\\/]+$'), '');
}
class AuthorizedSkillDirectory {
const AuthorizedSkillDirectory({required this.path, this.bookmark = ''});
final String path;
final String bookmark;
AuthorizedSkillDirectory copyWith({String? path, String? bookmark}) {
return AuthorizedSkillDirectory(
path: normalizeAuthorizedSkillDirectoryPath(path ?? this.path),
bookmark: bookmark ?? this.bookmark,
);
}
Map<String, dynamic> toJson() {
return <String, dynamic>{
'path': path,
if (bookmark.trim().isNotEmpty) 'bookmark': bookmark,
};
}
factory AuthorizedSkillDirectory.fromJson(Map<String, dynamic> json) {
return AuthorizedSkillDirectory(
path: normalizeAuthorizedSkillDirectoryPath(
json['path']?.toString() ?? '',
),
bookmark: json['bookmark']?.toString().trim() ?? '',
);
}
}
List<AuthorizedSkillDirectory> normalizeAuthorizedSkillDirectories({
Iterable<AuthorizedSkillDirectory>? directories,
}) {
final incoming =
directories?.toList(growable: false) ??
const <AuthorizedSkillDirectory>[];
final normalized = <AuthorizedSkillDirectory>[];
final seen = <String>{};
for (final item in incoming) {
final path = normalizeAuthorizedSkillDirectoryPath(item.path);
if (path.isEmpty || !seen.add(path)) {
continue;
}
normalized.add(
AuthorizedSkillDirectory(path: path, bookmark: item.bookmark.trim()),
);
}
normalized.sort((left, right) => left.path.compareTo(right.path));
return List<AuthorizedSkillDirectory>.unmodifiable(normalized);
}
class AssistantThreadConnectionState {
const AssistantThreadConnectionState({
required this.executionTarget,
required this.status,
required this.primaryLabel,
required this.detailLabel,
required this.ready,
required this.gatewayTokenMissing,
required this.lastError,
});
final AssistantExecutionTarget executionTarget;
final RuntimeConnectionStatus status;
final String primaryLabel;
final String detailLabel;
final bool ready;
final bool gatewayTokenMissing;
final String? lastError;
bool get isSingleAgent => false;
bool get connected => ready;
bool get connecting =>
!isSingleAgent && status == RuntimeConnectionStatus.connecting;
}
enum AssistantMessageViewMode { rendered, raw }
extension AssistantMessageViewModeCopy on AssistantMessageViewMode {
String get label => switch (this) {
AssistantMessageViewMode.rendered => appText('渲染', 'Rendered'),
AssistantMessageViewMode.raw => 'RAW',
};
static AssistantMessageViewMode fromJsonValue(String? value) {
return AssistantMessageViewMode.values.firstWhere(
(item) => item.name == value,
orElse: () => AssistantMessageViewMode.rendered,
);
}
}
enum WorkspaceRefKind { localPath, remotePath, objectStore }
extension WorkspaceRefKindCopy on WorkspaceRefKind {
static WorkspaceRefKind fromJsonValue(String? value) {
return WorkspaceRefKind.values.firstWhere(
(item) => item.name == value,
orElse: () => WorkspaceRefKind.localPath,
);
}
}
enum AssistantPermissionLevel { defaultAccess, fullAccess }
extension AssistantPermissionLevelCopy on AssistantPermissionLevel {
String get label => switch (this) {
AssistantPermissionLevel.defaultAccess => appText('默认权限', 'Default Access'),
AssistantPermissionLevel.fullAccess => appText('完全访问权限', 'Full Access'),
};
String get promptValue => switch (this) {
AssistantPermissionLevel.defaultAccess => 'default',
AssistantPermissionLevel.fullAccess => 'full-access',
};
static AssistantPermissionLevel fromJsonValue(String? value) {
return AssistantPermissionLevel.values.firstWhere(
(item) => item.name == value,
orElse: () => AssistantPermissionLevel.defaultAccess,
);
}
}
enum CodeAgentRuntimeMode { externalCli }
extension CodeAgentRuntimeModeCopy on CodeAgentRuntimeMode {
String get label => switch (this) {
CodeAgentRuntimeMode.externalCli => appText(
'外部 Codex CLI',
'External Codex CLI',
),
};
static CodeAgentRuntimeMode fromJsonValue(String? value) {
return CodeAgentRuntimeMode.values.firstWhere(
(item) => item.name == value,
orElse: () => CodeAgentRuntimeMode.externalCli,
);
}
}
enum VpnMode { tunnel, proxy }
extension VpnModeCopy on VpnMode {
String get label => switch (this) {
VpnMode.tunnel => appText('隧道', 'Tunnel'),
VpnMode.proxy => appText('代理', 'Proxy'),
};
static VpnMode fromJsonValue(String? value) {
return VpnMode.values.firstWhere(
(item) => item.name == value,
orElse: () => VpnMode.proxy,
);
}
}
enum DesktopEnvironment { unknown, gnome, kde }
extension DesktopEnvironmentCopy on DesktopEnvironment {
String get label => switch (this) {
DesktopEnvironment.unknown => appText('未知桌面', 'Unknown Desktop'),
DesktopEnvironment.gnome => 'GNOME',
DesktopEnvironment.kde => 'KDE Plasma',
};
static DesktopEnvironment fromJsonValue(String? value) {
return DesktopEnvironment.values.firstWhere(
(item) => item.name == value,
orElse: () => DesktopEnvironment.unknown,
);
}
}
class LinuxDesktopConfig {
const LinuxDesktopConfig({
required this.preferredMode,
required this.vpnConnectionName,
required this.proxyHost,
required this.proxyPort,
required this.trayEnabled,
});
final VpnMode preferredMode;
final String vpnConnectionName;
final String proxyHost;
final int proxyPort;
final bool trayEnabled;
factory LinuxDesktopConfig.defaults() {
return const LinuxDesktopConfig(
preferredMode: VpnMode.proxy,
vpnConnectionName: 'XWorkmate Tunnel',
proxyHost: '127.0.0.1',
proxyPort: 7890,
trayEnabled: true,
);
}
LinuxDesktopConfig copyWith({
VpnMode? preferredMode,
String? vpnConnectionName,
String? proxyHost,
int? proxyPort,
bool? trayEnabled,
}) {
return LinuxDesktopConfig(
preferredMode: preferredMode ?? this.preferredMode,
vpnConnectionName: vpnConnectionName ?? this.vpnConnectionName,
proxyHost: proxyHost ?? this.proxyHost,
proxyPort: proxyPort ?? this.proxyPort,
trayEnabled: trayEnabled ?? this.trayEnabled,
);
}
Map<String, dynamic> toJson() {
return {
'preferredMode': preferredMode.name,
'vpnConnectionName': vpnConnectionName,
'proxyHost': proxyHost,
'proxyPort': proxyPort,
'trayEnabled': trayEnabled,
};
}
factory LinuxDesktopConfig.fromJson(Map<String, dynamic> json) {
final defaults = LinuxDesktopConfig.defaults();
return LinuxDesktopConfig(
preferredMode: VpnModeCopy.fromJsonValue(
json['preferredMode'] as String?,
),
vpnConnectionName:
json['vpnConnectionName'] as String? ?? defaults.vpnConnectionName,
proxyHost: json['proxyHost'] as String? ?? defaults.proxyHost,
proxyPort: json['proxyPort'] as int? ?? defaults.proxyPort,
trayEnabled: json['trayEnabled'] as bool? ?? defaults.trayEnabled,
);
}
}
class SystemProxyState {
const SystemProxyState({
required this.enabled,
required this.host,
required this.port,
required this.backend,
required this.lastAppliedMode,
});
final bool enabled;
final String host;
final int port;
final String backend;
final VpnMode lastAppliedMode;
factory SystemProxyState.defaults({LinuxDesktopConfig? config}) {
final resolvedConfig = config ?? LinuxDesktopConfig.defaults();
return SystemProxyState(
enabled: resolvedConfig.preferredMode == VpnMode.proxy,
host: resolvedConfig.proxyHost,
port: resolvedConfig.proxyPort,
backend: '',
lastAppliedMode: resolvedConfig.preferredMode,
);
}
SystemProxyState copyWith({
bool? enabled,
String? host,
int? port,
String? backend,
VpnMode? lastAppliedMode,
}) {
return SystemProxyState(
enabled: enabled ?? this.enabled,
host: host ?? this.host,
port: port ?? this.port,
backend: backend ?? this.backend,
lastAppliedMode: lastAppliedMode ?? this.lastAppliedMode,
);
}
Map<String, dynamic> toJson() {
return {
'enabled': enabled,
'host': host,
'port': port,
'backend': backend,
'lastAppliedMode': lastAppliedMode.name,
};
}
factory SystemProxyState.fromJson(
Map<String, dynamic> json, {
LinuxDesktopConfig? config,
}) {
final defaults = SystemProxyState.defaults(config: config);
return SystemProxyState(
enabled: json['enabled'] as bool? ?? defaults.enabled,
host: json['host'] as String? ?? defaults.host,
port: json['port'] as int? ?? defaults.port,
backend: json['backend'] as String? ?? defaults.backend,
lastAppliedMode: VpnModeCopy.fromJsonValue(
json['lastAppliedMode'] as String?,
),
);
}
}
class TunnelSessionState {
const TunnelSessionState({
required this.available,
required this.connected,
required this.connectionName,
required this.backend,
required this.lastError,
});
final bool available;
final bool connected;
final String connectionName;
final String backend;
final String lastError;
factory TunnelSessionState.defaults({LinuxDesktopConfig? config}) {
final resolvedConfig = config ?? LinuxDesktopConfig.defaults();
return TunnelSessionState(
available: false,
connected: false,
connectionName: resolvedConfig.vpnConnectionName,
backend: '',
lastError: '',
);
}
TunnelSessionState copyWith({
bool? available,
bool? connected,
String? connectionName,
String? backend,
String? lastError,
}) {
return TunnelSessionState(
available: available ?? this.available,
connected: connected ?? this.connected,
connectionName: connectionName ?? this.connectionName,
backend: backend ?? this.backend,
lastError: lastError ?? this.lastError,
);
}
Map<String, dynamic> toJson() {
return {
'available': available,
'connected': connected,
'connectionName': connectionName,
'backend': backend,
'lastError': lastError,
};
}
factory TunnelSessionState.fromJson(
Map<String, dynamic> json, {
LinuxDesktopConfig? config,
}) {
final defaults = TunnelSessionState.defaults(config: config);
return TunnelSessionState(
available: json['available'] as bool? ?? defaults.available,
connected: json['connected'] as bool? ?? defaults.connected,
connectionName:
json['connectionName'] as String? ?? defaults.connectionName,
backend: json['backend'] as String? ?? defaults.backend,
lastError: json['lastError'] as String? ?? defaults.lastError,
);
}
}
class DesktopIntegrationState {
const DesktopIntegrationState({
required this.isSupported,
required this.environment,
required this.mode,
required this.trayAvailable,
required this.trayEnabled,
required this.autostartEnabled,
required this.networkManagerAvailable,
required this.systemProxy,
required this.tunnel,
required this.statusMessage,
});
final bool isSupported;
final DesktopEnvironment environment;
final VpnMode mode;
final bool trayAvailable;
final bool trayEnabled;
final bool autostartEnabled;
final bool networkManagerAvailable;
final SystemProxyState systemProxy;
final TunnelSessionState tunnel;
final String statusMessage;
factory DesktopIntegrationState.loading() {
final config = LinuxDesktopConfig.defaults();
return DesktopIntegrationState(
isSupported: true,
environment: DesktopEnvironment.unknown,
mode: config.preferredMode,
trayAvailable: false,
trayEnabled: config.trayEnabled,
autostartEnabled: false,
networkManagerAvailable: false,
systemProxy: SystemProxyState.defaults(config: config),
tunnel: TunnelSessionState.defaults(config: config),
statusMessage: '',
);
}
factory DesktopIntegrationState.unsupported({
LinuxDesktopConfig? config,
String message = '',
}) {
final resolvedConfig = config ?? LinuxDesktopConfig.defaults();
return DesktopIntegrationState(
isSupported: false,
environment: DesktopEnvironment.unknown,
mode: resolvedConfig.preferredMode,
trayAvailable: false,
trayEnabled: false,
autostartEnabled: false,
networkManagerAvailable: false,
systemProxy: SystemProxyState.defaults(config: resolvedConfig),
tunnel: TunnelSessionState.defaults(config: resolvedConfig),
statusMessage: message,
);
}
DesktopIntegrationState copyWith({
bool? isSupported,
DesktopEnvironment? environment,
VpnMode? mode,
bool? trayAvailable,
bool? trayEnabled,
bool? autostartEnabled,
bool? networkManagerAvailable,
SystemProxyState? systemProxy,
TunnelSessionState? tunnel,
String? statusMessage,
}) {
return DesktopIntegrationState(
isSupported: isSupported ?? this.isSupported,
environment: environment ?? this.environment,
mode: mode ?? this.mode,
trayAvailable: trayAvailable ?? this.trayAvailable,
trayEnabled: trayEnabled ?? this.trayEnabled,
autostartEnabled: autostartEnabled ?? this.autostartEnabled,
networkManagerAvailable:
networkManagerAvailable ?? this.networkManagerAvailable,
systemProxy: systemProxy ?? this.systemProxy,
tunnel: tunnel ?? this.tunnel,
statusMessage: statusMessage ?? this.statusMessage,
);
}
Map<String, dynamic> toJson() {
return {
'isSupported': isSupported,
'environment': environment.name,
'mode': mode.name,
'trayAvailable': trayAvailable,
'trayEnabled': trayEnabled,
'autostartEnabled': autostartEnabled,
'networkManagerAvailable': networkManagerAvailable,
'systemProxy': systemProxy.toJson(),
'tunnel': tunnel.toJson(),
'statusMessage': statusMessage,
};
}
factory DesktopIntegrationState.fromJson(
Map<String, dynamic> json, {
LinuxDesktopConfig? fallbackConfig,
}) {
final config = fallbackConfig ?? LinuxDesktopConfig.defaults();
return DesktopIntegrationState(
isSupported: json['isSupported'] as bool? ?? true,
environment: DesktopEnvironmentCopy.fromJsonValue(
json['environment'] as String?,
),
mode: VpnModeCopy.fromJsonValue(json['mode'] as String?),
trayAvailable: json['trayAvailable'] as bool? ?? false,
trayEnabled: json['trayEnabled'] as bool? ?? config.trayEnabled,
autostartEnabled: json['autostartEnabled'] as bool? ?? false,
networkManagerAvailable:
json['networkManagerAvailable'] as bool? ?? false,
systemProxy: SystemProxyState.fromJson(
(json['systemProxy'] as Map?)?.cast<String, dynamic>() ?? const {},
config: config,
),
tunnel: TunnelSessionState.fromJson(
(json['tunnel'] as Map?)?.cast<String, dynamic>() ?? const {},
config: config,
),
statusMessage: json['statusMessage'] as String? ?? '',
);
}
}