Add assistant access controls and maximize desktop windows

This commit is contained in:
Haitao Pan 2026-03-11 14:04:29 +08:00
parent de068f6544
commit ce21360a59
7 changed files with 1603 additions and 484 deletions

View File

@ -71,6 +71,10 @@ class AppController extends ChangeNotifier {
String get activeAgentName => _agentsController.activeAgentName;
String get currentSessionKey => _sessionsController.currentSessionKey;
String? get activeRunId => _chatController.activeRunId;
AssistantExecutionTarget get assistantExecutionTarget =>
settings.assistantExecutionTarget;
AssistantPermissionLevel get assistantPermissionLevel =>
settings.assistantPermissionLevel;
List<SecretReferenceEntry> get secretReferences =>
_settingsController.buildSecretReferences();
List<SecretAuditEntry> get secretAuditTrail => _settingsController.auditTrail;
@ -174,10 +178,13 @@ class AppController extends ChangeNotifier {
token: token.trim(),
password: password.trim(),
);
final resolvedHost = host.trim().isEmpty && mode == RuntimeConnectionMode.local
final resolvedHost =
host.trim().isEmpty && mode == RuntimeConnectionMode.local
? '127.0.0.1'
: host.trim();
final resolvedPort = mode == RuntimeConnectionMode.local && port <= 0 ? 18789 : port;
final resolvedPort = mode == RuntimeConnectionMode.local && port <= 0
? 18789
: port;
final nextProfile = settings.gateway.copyWith(
mode: mode,
useSetupCode: false,
@ -282,6 +289,30 @@ class AppController extends ChangeNotifier {
await _chatController.abortRun();
}
Future<void> setAssistantExecutionTarget(
AssistantExecutionTarget target,
) async {
if (settings.assistantExecutionTarget == target) {
return;
}
await saveSettings(
settings.copyWith(assistantExecutionTarget: target),
refreshAfterSave: false,
);
}
Future<void> setAssistantPermissionLevel(
AssistantPermissionLevel level,
) async {
if (settings.assistantPermissionLevel == level) {
return;
}
await saveSettings(
settings.copyWith(assistantPermissionLevel: level),
refreshAfterSave: false,
);
}
Future<void> saveSettings(
SettingsSnapshot snapshot, {
bool refreshAfterSave = true,
@ -341,7 +372,8 @@ class AppController extends ChangeNotifier {
);
_runtimeEventsSubscription = _runtime.events.listen(_handleRuntimeEvent);
final shouldAutoConnect =
settings.gateway.useSetupCode && settings.gateway.setupCode.trim().isNotEmpty;
settings.gateway.useSetupCode &&
settings.gateway.setupCode.trim().isNotEmpty;
if (shouldAutoConnect) {
try {
await _connectProfile(settings.gateway);

File diff suppressed because it is too large Load Diff

View File

@ -28,6 +28,48 @@ extension RuntimeConnectionStatusCopy on RuntimeConnectionStatus {
};
}
enum AssistantExecutionTarget { local, remote }
extension AssistantExecutionTargetCopy on AssistantExecutionTarget {
String get label => switch (this) {
AssistantExecutionTarget.local => '本地',
AssistantExecutionTarget.remote => '远程',
};
String get promptValue => switch (this) {
AssistantExecutionTarget.local => 'local',
AssistantExecutionTarget.remote => 'remote',
};
static AssistantExecutionTarget fromJsonValue(String? value) {
return AssistantExecutionTarget.values.firstWhere(
(item) => item.name == value,
orElse: () => AssistantExecutionTarget.local,
);
}
}
enum AssistantPermissionLevel { defaultAccess, fullAccess }
extension AssistantPermissionLevelCopy on AssistantPermissionLevel {
String get label => switch (this) {
AssistantPermissionLevel.defaultAccess => '默认权限',
AssistantPermissionLevel.fullAccess => '完全访问权限',
};
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,
);
}
}
class GatewayConnectionProfile {
const GatewayConnectionProfile({
required this.mode,
@ -145,7 +187,8 @@ class OllamaLocalConfig {
factory OllamaLocalConfig.fromJson(Map<String, dynamic> json) {
return OllamaLocalConfig(
endpoint: json['endpoint'] as String? ?? OllamaLocalConfig.defaults().endpoint,
endpoint:
json['endpoint'] as String? ?? OllamaLocalConfig.defaults().endpoint,
defaultModel:
json['defaultModel'] as String? ??
OllamaLocalConfig.defaults().defaultModel,
@ -207,14 +250,16 @@ class OllamaCloudConfig {
factory OllamaCloudConfig.fromJson(Map<String, dynamic> json) {
return OllamaCloudConfig(
baseUrl: json['baseUrl'] as String? ?? OllamaCloudConfig.defaults().baseUrl,
baseUrl:
json['baseUrl'] as String? ?? OllamaCloudConfig.defaults().baseUrl,
organization: json['organization'] as String? ?? '',
workspace: json['workspace'] as String? ?? '',
defaultModel:
json['defaultModel'] as String? ??
OllamaCloudConfig.defaults().defaultModel,
apiKeyRef:
json['apiKeyRef'] as String? ?? OllamaCloudConfig.defaults().apiKeyRef,
json['apiKeyRef'] as String? ??
OllamaCloudConfig.defaults().apiKeyRef,
);
}
}
@ -267,7 +312,8 @@ class VaultConfig {
factory VaultConfig.fromJson(Map<String, dynamic> json) {
return VaultConfig(
address: json['address'] as String? ?? VaultConfig.defaults().address,
namespace: json['namespace'] as String? ?? VaultConfig.defaults().namespace,
namespace:
json['namespace'] as String? ?? VaultConfig.defaults().namespace,
authMode: json['authMode'] as String? ?? VaultConfig.defaults().authMode,
tokenRef: json['tokenRef'] as String? ?? VaultConfig.defaults().tokenRef,
);
@ -335,7 +381,8 @@ class ApisixYamlProfile {
return ApisixYamlProfile(
name: json['name'] as String? ?? ApisixYamlProfile.defaults().name,
sourceType:
json['sourceType'] as String? ?? ApisixYamlProfile.defaults().sourceType,
json['sourceType'] as String? ??
ApisixYamlProfile.defaults().sourceType,
filePath:
json['filePath'] as String? ?? ApisixYamlProfile.defaults().filePath,
inlineYaml: json['inlineYaml'] as String? ?? '',
@ -371,6 +418,8 @@ class SettingsSnapshot {
required this.accountUsername,
required this.accountWorkspace,
required this.accountLocalMode,
required this.assistantExecutionTarget,
required this.assistantPermissionLevel,
});
final bool appActive;
@ -393,6 +442,8 @@ class SettingsSnapshot {
final String accountUsername;
final String accountWorkspace;
final bool accountLocalMode;
final AssistantExecutionTarget assistantExecutionTarget;
final AssistantPermissionLevel assistantPermissionLevel;
factory SettingsSnapshot.defaults() {
return SettingsSnapshot(
@ -416,6 +467,8 @@ class SettingsSnapshot {
accountUsername: '',
accountWorkspace: 'Default Workspace',
accountLocalMode: true,
assistantExecutionTarget: AssistantExecutionTarget.local,
assistantPermissionLevel: AssistantPermissionLevel.defaultAccess,
);
}
@ -440,6 +493,8 @@ class SettingsSnapshot {
String? accountUsername,
String? accountWorkspace,
bool? accountLocalMode,
AssistantExecutionTarget? assistantExecutionTarget,
AssistantPermissionLevel? assistantPermissionLevel,
}) {
return SettingsSnapshot(
appActive: appActive ?? this.appActive,
@ -462,6 +517,10 @@ class SettingsSnapshot {
accountUsername: accountUsername ?? this.accountUsername,
accountWorkspace: accountWorkspace ?? this.accountWorkspace,
accountLocalMode: accountLocalMode ?? this.accountLocalMode,
assistantExecutionTarget:
assistantExecutionTarget ?? this.assistantExecutionTarget,
assistantPermissionLevel:
assistantPermissionLevel ?? this.assistantPermissionLevel,
);
}
@ -487,6 +546,8 @@ class SettingsSnapshot {
'accountUsername': accountUsername,
'accountWorkspace': accountWorkspace,
'accountLocalMode': accountLocalMode,
'assistantExecutionTarget': assistantExecutionTarget.name,
'assistantPermissionLevel': assistantPermissionLevel.name,
};
}
@ -496,13 +557,16 @@ class SettingsSnapshot {
launchAtLogin: json['launchAtLogin'] as bool? ?? false,
showDockIcon: json['showDockIcon'] as bool? ?? true,
workspacePath:
json['workspacePath'] as String? ?? SettingsSnapshot.defaults().workspacePath,
json['workspacePath'] as String? ??
SettingsSnapshot.defaults().workspacePath,
remoteProjectRoot:
json['remoteProjectRoot'] as String? ??
SettingsSnapshot.defaults().remoteProjectRoot,
cliPath: json['cliPath'] as String? ?? SettingsSnapshot.defaults().cliPath,
cliPath:
json['cliPath'] as String? ?? SettingsSnapshot.defaults().cliPath,
defaultModel:
json['defaultModel'] as String? ?? SettingsSnapshot.defaults().defaultModel,
json['defaultModel'] as String? ??
SettingsSnapshot.defaults().defaultModel,
defaultProvider:
json['defaultProvider'] as String? ??
SettingsSnapshot.defaults().defaultProvider,
@ -532,6 +596,12 @@ class SettingsSnapshot {
json['accountWorkspace'] as String? ??
SettingsSnapshot.defaults().accountWorkspace,
accountLocalMode: json['accountLocalMode'] as bool? ?? true,
assistantExecutionTarget: AssistantExecutionTargetCopy.fromJsonValue(
json['assistantExecutionTarget'] as String?,
),
assistantPermissionLevel: AssistantPermissionLevelCopy.fromJsonValue(
json['assistantPermissionLevel'] as String?,
),
);
}
@ -733,14 +803,7 @@ class GatewaySessionSummary {
final String? lastMessagePreview;
String get label {
final candidates = [
derivedTitle,
displayName,
subject,
room,
space,
key,
];
final candidates = [derivedTitle, displayName, subject, room, space, key];
return candidates.firstWhere(
(item) => item != null && item.trim().isNotEmpty,
orElse: () => key,

View File

@ -53,6 +53,7 @@ static void my_application_activate(GApplication* application) {
}
gtk_window_set_default_size(window, 1280, 720);
gtk_window_maximize(window);
g_autoptr(FlDartProject) project = fl_dart_project_new();
fl_dart_project_set_dart_entrypoint_arguments(

View File

@ -11,5 +11,11 @@ class MainFlutterWindow: NSWindow {
RegisterGeneratedPlugins(registry: flutterViewController)
super.awakeFromNib()
DispatchQueue.main.async {
if !self.isZoomed {
self.zoom(nil)
}
}
}
}

View File

@ -6,7 +6,10 @@ void main() {
testWidgets('renders XWorkmate shell', (WidgetTester tester) async {
await tester.pumpWidget(const XWorkmateApp());
expect(find.text('XWorkmate'), findsWidgets);
expect(find.text('Assistant'), findsWidgets);
expect(
find.text('Connect a gateway to start chatting and running tasks.'),
findsOneWidget,
);
});
}

View File

@ -150,7 +150,7 @@ bool Win32Window::Create(const std::wstring& title,
}
bool Win32Window::Show() {
return ShowWindow(window_handle_, SW_SHOWNORMAL);
return ShowWindow(window_handle_, SW_MAXIMIZE);
}
// static