539 lines
16 KiB
Dart
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? ?? '',
|
|
);
|
|
}
|
|
}
|