import { CapabilitySettingsComponent } from '../../components/model-settings/capability-settings.component'; import { ModelSettingsComponent } from '../../components/model-settings/model-settings.component'; /** * Copyright 2026 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { Component, signal, computed, inject, OnInit, OnDestroy, ChangeDetectionStrategy } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { Router } from '@angular/router'; import { AgentService } from '../../services/agent.service'; import { SystemService } from '../../services/system.service'; import { AdbServerConnectionResult, AdbServerDevice, DeviceInfo, ProbeResult } from '../../core/models/system.model'; import { AppReference, SmartSuggestion, SuggestionCategory } from '../../core/data/smart-tasks.data'; import { TaskRecommendationService } from '../../core/services/task-recommendation.service'; import { DEFAULT_EXPLORER_MODE, DEFAULT_VERIFICATION_LEVEL, EXPLORER_MODES, ExplorerModeId, TuningLevel, VERIFICATION_LEVELS, VerificationLevelId, levelIndex, notchPercent } from '../../core/models/pro-tuning.model'; export type TuningKind = 'verify' | 'explore'; /** One 2x2 px square of the "maxed out" dither texture drawn over a slider rail. */ export interface DitherPixel { /** Horizontal position as a percentage of the rail width. */ x: number; /** Row offset in px (rail is 6 px tall, three 2 px rows). */ y: number; /** Resting opacity; squares near the thumb are stronger. */ opacity: number; /** Animation delay in ms so the texture spreads leftwards from the thumb. */ delay: number; } /** * Seeded pixels keep the slider texture stable across renders. * Delays increase with distance from the thumb to animate from right to left. */ function buildDitherPixels(count = 260, seed = 7): DitherPixel[] { let state = seed >>> 0; const rand = (): number => { state = (Math.imul(state, 1664525) + 1013904223) >>> 0; return state / 0x100000000; }; const pixels: DitherPixel[] = []; for (let i = 0; i < count; i++) { const x = rand() * 100; const y = Math.floor(rand() * 3) * 2; pixels.push({ x: Math.round(x * 10) / 10, y, opacity: Math.round((0.35 + 0.4 * rand()) * 100) / 100, // 8 ms per percent: the front takes ~0.8 s to reach the left end. delay: Math.round((100 - x) * 8) }); } return pixels; } /** Shared view model for the verification and screen-reading sliders. */ export interface TuningSliderVm { kind: TuningKind; name: string; /** One-word meaning of each end of the track, e.g. ["Off", "Strict"]. */ ends: readonly [string, string]; ladder: readonly TuningLevel[]; index: number; level: TuningLevel; /** 0..1 position of the thumb along the track. */ fraction: number; } export type { AppReference, SmartSuggestion, SuggestionCategory }; type AdbGuideTab = 'emulator' | 'usb' | 'wifi' | 'remote'; @Component({ selector: 'app-home', standalone: true, imports: [FormsModule, ModelSettingsComponent, CapabilitySettingsComponent], templateUrl: './home.component.html', changeDetection: ChangeDetectionStrategy.Eager, styleUrl: './home.component.scss' }) export class HomeComponent implements OnInit, OnDestroy { public agentService = inject(AgentService); public systemService = inject(SystemService); public taskRecService = inject(TaskRecommendationService); private router = inject(Router); // High-level navigation mode: 'diagnostics' (System Setup Guide) vs 'launcher' (Task Execution) public activeTab = signal<'diagnostics' | 'launcher'>('launcher'); // Interactive guide sub-tab inside the ADB section public activeAdbGuideTab = signal('emulator'); public emulatorSetupMode = signal<'studio' | 'cli'>('studio'); // Clipboard copy state tracker for interactive feedback public copiedId = signal(null); // Diagnostic re-check state public isRefreshingDiagnostics = signal(false); // Wireless ADB Interactive connection signals public wifiHost = signal('192.168.1.100'); public wifiPort = signal('5555'); public isConnectingWifi = signal(false); public wifiConnectMessage = signal(null); public wifiConnectError = signal(null); public adbRestartFeedback = signal(null); public showConnectionMethods = signal(false); // ADB server endpoint connection state public remoteAdbHost = signal('127.0.0.1'); public remoteAdbPort = signal('5038'); public rememberRemoteAdb = signal(true); public isConnectingRemoteAdb = signal(false); public isActivatingRemoteAdb = signal(false); public isSwitchingToLocalAdb = signal(false); public remoteAdbMessage = signal(null); public remoteAdbError = signal(null); public remoteAdbDevices = signal([]); public remoteAdbProbeResult = signal(null); public adbServerStatus = computed(() => this.systemService.adbServerStatus()); public isRemoteAdbServer = computed(() => this.systemService.isRemoteAdbServer()); public remoteAdbHasReadyDevice = computed(() => this.remoteAdbDevices().some(device => device.state === 'device') ); public isProbedRemoteAdbActive = computed(() => { const tested = this.remoteAdbProbeResult()?.endpoint; const active = this.adbServerStatus()?.endpoint; return !!tested && !!active && tested.identity === active.identity; }); public wifiCommand = computed(() => { const h = this.wifiHost().trim() || ''; const p = this.wifiPort().trim() || '5555'; return `adb connect ${h}:${p}`; }); // Task execution parameters public selectedProfile = signal<'flash' | 'pro'>('flash'); public taskGoal = signal(''); public isSubmitting = signal(false); public errorMessage = signal(null); // Pro Mode Outputter & Structured Output Configuration public expectedOutput = signal(''); public showOutputterDrawer = signal(false); // Slider indexes map to the API ids in VERIFICATION_LEVELS / EXPLORER_MODES. public readonly verificationLevels = VERIFICATION_LEVELS; public readonly explorerModes = EXPLORER_MODES; public verificationIndex = signal( levelIndex(VERIFICATION_LEVELS, DEFAULT_VERIFICATION_LEVEL, DEFAULT_VERIFICATION_LEVEL) ); public explorerIndex = signal( levelIndex(EXPLORER_MODES, DEFAULT_EXPLORER_MODE, DEFAULT_EXPLORER_MODE) ); /** Effective defaults from the backend config (`GET /api/run/defaults`). */ private tuningDefaults = signal<{ verification: VerificationLevelId; explorer: ExplorerModeId }>({ verification: DEFAULT_VERIFICATION_LEVEL, explorer: DEFAULT_EXPLORER_MODE }); /** True once the user moved a slider; backend defaults then stop overriding it. */ private tuningTouched = signal(false); /** Which slider's hover card is open (while hovering, dragging, or focused). */ public activeTuningTip = signal(null); /** Pixel cloud drawn over a rail once its slider reaches the last notch. */ public readonly ditherPixels: readonly DitherPixel[] = buildDitherPixels(); public verificationLevel = computed>( () => VERIFICATION_LEVELS[this.verificationIndex()] ); public explorerMode = computed>( () => EXPLORER_MODES[this.explorerIndex()] ); public isTuningDefault = computed(() => { const d = this.tuningDefaults(); return this.verificationLevel().id === d.verification && this.explorerMode().id === d.explorer; }); public tuningSliders = computed(() => { const vi = this.verificationIndex(); const ei = this.explorerIndex(); return [ { kind: 'verify', name: "结果检查", ends: ["关闭", "严格检查"], ladder: VERIFICATION_LEVELS, index: vi, level: VERIFICATION_LEVELS[vi], fraction: notchPercent(vi, VERIFICATION_LEVELS.length) / 100 }, { kind: 'explore', name: "屏幕识别", ends: ["更快", "更精细"], ladder: EXPLORER_MODES, index: ei, level: EXPLORER_MODES[ei], fraction: notchPercent(ei, EXPLORER_MODES.length) / 100 } ]; }); public notchFraction(index: number, count: number): number { return notchPercent(index, count) / 100; } public setTuningIndex(kind: TuningKind, raw: number | string): void { const idx = Math.round(Number(raw)); if (!Number.isFinite(idx)) return; const ladder = kind === 'verify' ? VERIFICATION_LEVELS : EXPLORER_MODES; const clamped = Math.min(Math.max(idx, 0), ladder.length - 1); this.tuningTouched.set(true); if (kind === 'verify') { this.verificationIndex.set(clamped); } else { this.explorerIndex.set(clamped); } // Keyboard nudges and drags should keep the explanation visible. this.activeTuningTip.set(kind); } public showTuningTip(kind: TuningKind): void { this.activeTuningTip.set(kind); } public hideTuningTip(kind: TuningKind): void { if (this.activeTuningTip() === kind) { this.activeTuningTip.set(null); } } public resetTuning(): void { const d = this.tuningDefaults(); this.verificationIndex.set(levelIndex(VERIFICATION_LEVELS, d.verification, DEFAULT_VERIFICATION_LEVEL)); this.explorerIndex.set(levelIndex(EXPLORER_MODES, d.explorer, DEFAULT_EXPLORER_MODE)); this.tuningTouched.set(false); } /** Pull the effective config defaults so the sliders start where artemis.jsonc is. */ private loadProTuningDefaults(): void { this.agentService.getProTuningDefaults().subscribe({ next: (res) => { const vIdx = levelIndex(VERIFICATION_LEVELS, res?.verification_level, DEFAULT_VERIFICATION_LEVEL); const eIdx = levelIndex(EXPLORER_MODES, res?.explorer_mode, DEFAULT_EXPLORER_MODE); this.tuningDefaults.set({ verification: VERIFICATION_LEVELS[vIdx].id, explorer: EXPLORER_MODES[eIdx].id }); if (!this.tuningTouched()) { this.verificationIndex.set(vIdx); this.explorerIndex.set(eIdx); } }, // Defaults are a convenience; the built-in ladder defaults already apply. error: () => undefined }); } public toggleOutputterDrawer(): void { this.showOutputterDrawer.update((v) => !v); } public applyOutputPreset(preset: string): void { if (this.expectedOutput() === preset) { this.expectedOutput.set(''); } else { this.expectedOutput.set(preset); this.showOutputterDrawer.set(true); } } // Smart intent detection for model recommendation public isIntentSuggestingPro = computed(() => { const text = this.taskGoal().toLowerCase(); if (!text.trim()) return false; const keywords = [ 'monitor', 'polling', 'poll', 'wait until', 'loop', 'keep watching', 'crash', 'logcat', 'troubleshoot', 'diagnose', 'debug', 'investigate', 'compare', 'extract', 'summarize', 'report', '监控', '轮询', '等待', '一直', '直到', '崩溃', '闪退', '排查', '分析日志', '对比', '总结' ]; return keywords.some(k => text.includes(k)); }); public showIntentSuggestion = computed(() => { return this.isIntentSuggestingPro() && this.selectedProfile() === 'flash'; }); // Computed helper states delegating to SystemService public isReady = computed(() => this.systemService.isReady()); public hasReadinessReport = computed(() => this.systemService.hasReadinessReport()); public isLoading = computed(() => this.systemService.isLoading()); public isRestartingAdb = computed(() => this.systemService.isRestartingAdb()); public launchingAvd = computed(() => this.systemService.launchingAvd()); public emulatorLaunchState = computed(() => this.systemService.emulatorLaunchState()); public isEmulatorLaunching = computed(() => this.systemService.isEmulatorLaunching()); public showLaunchLogs = signal(false); // Probes public pythonProbe = computed(() => this.systemService.pythonProbe()); public configProbe = computed(() => this.systemService.configProbe()); public adbProbe = computed(() => this.systemService.adbProbe()); public llmProbe = computed(() => this.systemService.llmProbe()); public openaiProbe = computed(() => this.systemService.openaiProbe()); public ocrProbe = computed(() => this.systemService.ocrProbe()); public toolchainProbe = computed(() => this.systemService.toolchainProbe()); // Step-level readiness public isEnvironmentReady = computed(() => this.systemService.isEnvironmentReady()); public isCredentialsReady = computed(() => this.systemService.isCredentialsReady()); public isSkipCredentialsCheck = computed(() => this.systemService.isSkipCredentialsCheck()); public isDeviceReady = computed(() => this.systemService.isDeviceReady()); // Device information public activeDevice = computed(() => this.systemService.activeDevice()); public connectedDevices = computed(() => this.systemService.connectedDevices()); public installedAvds = computed(() => this.systemService.installedAvds()); public emulatorPath = computed(() => this.systemService.emulatorPath()); public isEmulatorInPath = computed(() => this.systemService.isEmulatorInPath()); public totalStepCount = computed(() => this.systemService.totalStepCount()); public passedStepCount = computed(() => this.systemService.passedStepCount()); public blockerCount = computed(() => this.systemService.blockerCount()); public passedBlockerCount = computed(() => this.systemService.passedBlockerCount()); // Configured LLM providers from probe metadata public configuredLlmProviders = computed(() => { const meta = this.llmProbe()?.metadata; if (meta && Array.isArray(meta['providers'])) { return meta['providers']; } return []; }); // Multi-OS detection & active OS selection public selectedOs = signal<'linux' | 'darwin' | 'windows' | null>(null); public effectiveOs = computed<'linux' | 'darwin' | 'windows'>(() => { return this.selectedOs() || this.systemService.osType(); }); public oneClickSetupCmd = computed(() => { const os = this.effectiveOs(); if (os === 'windows') { return 'powershell -ExecutionPolicy Bypass -File scripts/install_deps.ps1'; } return 'bash scripts/install_deps.sh'; }); public adbInstallCmd = computed(() => { const os = this.effectiveOs(); if (os === 'windows') { return 'winget install Google.PlatformTools'; } if (os === 'darwin') { return 'brew install android-platform-tools'; } return 'sudo apt-get install -y adb'; }); public toolchainInstallCmd = computed(() => { const os = this.effectiveOs(); if (os === 'windows') { return 'winget install Gyan.FFmpeg Genymobile.scrcpy'; } if (os === 'darwin') { return 'brew install ffmpeg scrcpy'; } return 'sudo apt-get install -y ffmpeg scrcpy'; }); public emuHypervisorTitle = computed(() => { const os = this.effectiveOs(); if (os === 'windows') return "启用 Windows 虚拟机监控程序平台(WHPX)"; if (os === 'darwin') return "检查 macOS 虚拟化并安装工具"; return "启用 KVM 硬件加速(Linux)"; }); public emuHypervisorDesc = computed(() => { const os = this.effectiveOs(); if (os === 'windows') return "以管理员身份打开 PowerShell,启用 Windows 虚拟机监控程序平台:"; if (os === 'darwin') return "macOS 使用系统 Hypervisor.framework。通过 Homebrew 或 Android Studio 安装 SDK 工具:"; return "确保当前用户拥有虚拟化权限:"; }); public emuHypervisorCmd = computed(() => { const os = this.effectiveOs(); if (os === 'windows') return 'Enable-WindowsOptionalFeature -Online -FeatureName HypervisorPlatform'; if (os === 'darwin') return 'brew install --cask android-commandlinetools'; return 'sudo apt-get install -y qemu-kvm libvirt-daemon-system && sudo adduser $USER kvm'; }); public emuSdkInstallCmd = computed(() => { const os = this.effectiveOs(); if (os === 'darwin') { return 'sdkmanager --install "system-images;android-34;google_apis;arm64-v8a" "emulator" "platform-tools"'; } return 'sdkmanager --install "system-images;android-34;google_apis;x86_64" "emulator" "platform-tools"'; }); public emuCreateAvdCmd = computed(() => { const os = this.effectiveOs(); if (os === 'darwin') { return 'avdmanager create avd -n Pixel_8_API_34 -k "system-images;android-34;google_apis;arm64-v8a" --device "pixel_8"'; } return 'avdmanager create avd -n Pixel_8_API_34 -k "system-images;android-34;google_apis;x86_64" --device "pixel_8"'; }); // Flag indicating whether Google Cloud Vision OCR is configured public isOcrConfigured = computed(() => { const meta = this.ocrProbe()?.metadata; return meta?.['configured'] === true; }); // Rich Smart Suggestions Library (Device-Aware, Flash vs Pro Tailored) public readonly allSuggestions = this.taskRecService.allTasks; // Suggestion category filter & shuffle state public selectedCategory = signal('all'); public shuffleOffset = signal(0); // Set of installed package strings from active device public installedPackages = computed>(() => { const pkgs = this.activeDevice()?.installed_packages; if (pkgs && Array.isArray(pkgs)) { return new Set(pkgs); } return new Set(); }); public filteredSuggestions = computed(() => { return this.taskRecService.filterAndRankTasks( this.installedPackages(), this.selectedCategory(), this.shuffleOffset() ); }); private focusListener = () => { // Silently re-check environment when user returns to the browser tab this.systemService.fetchReadiness().subscribe(); }; ngOnInit(): void { this.loadProTuningDefaults(); // Initial fetch of system readiness & model configuration this.systemService.fetchReadiness().subscribe(); this.systemService.fetchModelConfigEnv().subscribe(); this.systemService.fetchAdbServerStatus().subscribe({ next: status => { if (status.endpoint.mode === 'remote') { this.remoteAdbHost.set(status.endpoint.host); this.remoteAdbPort.set(String(status.endpoint.port)); this.activeAdbGuideTab.set('remote'); } }, error: () => {} }); window.addEventListener('focus', this.focusListener); } ngOnDestroy(): void { window.removeEventListener('focus', this.focusListener); } public setTab(tab: 'diagnostics' | 'launcher'): void { this.activeTab.set(tab); if (tab === 'diagnostics') { this.systemService.fetchReadiness().subscribe(); this.systemService.fetchModelConfigEnv().subscribe(); } } public setSelectedOs(os: 'linux' | 'darwin' | 'windows'): void { this.selectedOs.set(os); } public setAdbGuideTab(tab: AdbGuideTab): void { this.activeAdbGuideTab.set(tab); this.remoteAdbError.set(null); this.remoteAdbMessage.set(null); } public setEmulatorSetupMode(mode: 'studio' | 'cli'): void { this.emulatorSetupMode.set(mode); } public skipCredentialsCheck(): void { this.systemService.skipCredentialsCheck(); } public refreshReadiness(): void { this.isRefreshingDiagnostics.set(true); this.systemService.fetchReadiness(false, true).subscribe({ next: () => { this.systemService.fetchModelConfigEnv().subscribe({ next: () => { setTimeout(() => this.isRefreshingDiagnostics.set(false), 450); }, error: () => this.isRefreshingDiagnostics.set(false) }); }, error: () => this.isRefreshingDiagnostics.set(false) }); } public restartAdbServer(): void { this.adbRestartFeedback.set(null); this.systemService.restartAdb().subscribe({ next: (res) => { this.adbRestartFeedback.set( res?.restart_result?.skipped ? "设备已刷新 ✓" : "ADB 已刷新 ✓" ); setTimeout(() => this.adbRestartFeedback.set(null), 2500); }, error: () => { this.adbRestartFeedback.set("重启失败"); setTimeout(() => this.adbRestartFeedback.set(null), 3000); } }); } public connectWifiDevice(): void { if (this.isRemoteAdbServer()) { this.wifiConnectError.set("连接无线设备前,请先切换至本地 ADB。"); return; } const host = this.wifiHost().trim(); const portStr = this.wifiPort().trim() || '5555'; const port = parseInt(portStr, 10) || 5555; if (!host) { this.wifiConnectError.set("请输入有效的 IP 地址。"); return; } this.isConnectingWifi.set(true); this.wifiConnectError.set(null); this.wifiConnectMessage.set(null); this.systemService.connectWirelessAdb(host, port).subscribe({ next: (res) => { this.isConnectingWifi.set(false); const cr = res?.connect_result; if (cr?.success) { this.wifiConnectMessage.set(`已连接至 ${host}:${port}!`); setTimeout(() => this.wifiConnectMessage.set(null), 4000); } else { this.wifiConnectError.set(cr?.message || "连接失败,请检查手机 IP 地址和 Wi-Fi。"); } }, error: (err) => { this.isConnectingWifi.set(false); this.wifiConnectError.set(err?.error?.detail || "连接失败,请检查 ADB 连接。"); } }); } public connectRemoteAdbServer(): void { const host = this.remoteAdbHost().trim(); const port = Number(this.remoteAdbPort().trim()); if (!host) { this.remoteAdbError.set("请输入 ADB 服务器的主机名或 IP 地址。"); return; } if (!Number.isInteger(port) || port < 1 || port > 65535) { this.remoteAdbError.set("请输入 1–65535 之间的端口。"); return; } this.isConnectingRemoteAdb.set(true); this.remoteAdbError.set(null); this.remoteAdbMessage.set(null); this.remoteAdbDevices.set([]); this.remoteAdbProbeResult.set(null); this.systemService.probeAdbServer(host, port).subscribe({ next: response => { this.isConnectingRemoteAdb.set(false); const result = response.connection_result; if (result.success) { this.remoteAdbProbeResult.set(result); this.remoteAdbDevices.set(result.devices || []); this.remoteAdbMessage.set(result.message); } else { this.remoteAdbError.set(result.message); } }, error: error => { this.isConnectingRemoteAdb.set(false); this.remoteAdbError.set( error?.error?.detail || "无法测试 ADB 服务器连接。" ); } }); } public activateRemoteAdbServer(): void { const tested = this.remoteAdbProbeResult(); if (!tested?.success) { this.remoteAdbError.set("请先测试服务器连接。"); return; } this.isActivatingRemoteAdb.set(true); this.remoteAdbError.set(null); this.systemService.connectAdbServer( tested.endpoint.host, tested.endpoint.port, this.rememberRemoteAdb() ).subscribe({ next: response => { this.isActivatingRemoteAdb.set(false); const result = response.connection_result; if (result.success) { this.remoteAdbProbeResult.set(result); this.remoteAdbDevices.set(result.devices || []); this.remoteAdbMessage.set(result.message); } else { this.remoteAdbError.set(result.message); } }, error: error => { this.isActivatingRemoteAdb.set(false); this.remoteAdbError.set( error?.error?.detail || "无法使用此 ADB 服务器。" ); } }); } public updateRemoteAdbHost(value: string): void { this.remoteAdbHost.set(value); this.clearRemoteAdbProbe(); } public updateRemoteAdbPort(value: string): void { this.remoteAdbPort.set(value); this.clearRemoteAdbProbe(); } private clearRemoteAdbProbe(): void { this.remoteAdbProbeResult.set(null); this.remoteAdbDevices.set([]); this.remoteAdbMessage.set(null); this.remoteAdbError.set(null); } public switchToLocalAdbServer(): void { this.isSwitchingToLocalAdb.set(true); this.remoteAdbError.set(null); this.systemService.useLocalAdbServer(true).subscribe({ next: response => { this.isSwitchingToLocalAdb.set(false); this.remoteAdbDevices.set([]); this.remoteAdbMessage.set(response.connection_result.message); }, error: error => { this.isSwitchingToLocalAdb.set(false); this.remoteAdbError.set( error?.error?.detail || "无法切换回本地 ADB 服务器。" ); } }); } public toggleConnectionMethods(): void { this.showConnectionMethods.update(value => !value); } public launchAvdEmulator(avdName: string): void { if (this.isRemoteAdbServer()) { return; } this.systemService.launchEmulator(avdName).subscribe(); } public toggleLaunchLogs(): void { this.showLaunchLogs.update(v => !v); } public stopEmulator(): void { this.systemService.stopEmulator().subscribe(); } public dismissEmulatorStatus(): void { this.systemService.dismissEmulatorStatus().subscribe(); } public selectTargetDevice(serial: string): void { this.systemService.selectDevice(serial).subscribe(); } public getEmulatorCommand(avdName: string): string { const p = this.emulatorPath(); const cmd = this.isEmulatorInPath() ? 'emulator' : (p || '~/Android/Sdk/emulator/emulator'); return `${cmd} -avd ${avdName}`; } public copyToClipboard(text: string, id: string): void { if (navigator.clipboard) { navigator.clipboard.writeText(text).then(() => { this.copiedId.set(id); setTimeout(() => { if (this.copiedId() === id) { this.copiedId.set(null); } }, 2000); }); } } public setProfile(profile: 'flash' | 'pro'): void { this.selectedProfile.set(profile); } public setCategory(cat: SuggestionCategory): void { this.selectedCategory.set(cat); } public shuffleSuggestions(): void { this.shuffleOffset.update(v => v + 3); } public getAppNamesDisplay(apps: AppReference[]): string { return apps.map(a => a.name).join(' + '); } public applySuggestion(item: SmartSuggestion): void { this.taskGoal.set(item.goal); this.selectedProfile.set(item.profile); this.errorMessage.set(null); } public applyQuickPrompt(promptGoal: string, profile?: 'flash' | 'pro'): void { this.taskGoal.set(promptGoal); if (profile) { this.selectedProfile.set(profile); } this.errorMessage.set(null); } public proceedToLauncher(): void { this.activeTab.set('launcher'); } public runTask(): void { const goal = this.taskGoal().trim(); if (!goal) { this.errorMessage.set("请先输入任务目标。"); return; } if (!this.isReady()) { this.errorMessage.set("运行条件尚未满足,请先检查系统设置。"); this.activeTab.set('diagnostics'); return; } this.isSubmitting.set(true); this.errorMessage.set(null); this.agentService .runTask( goal, this.selectedProfile(), this.selectedProfile() === 'pro' && this.expectedOutput().trim() ? this.expectedOutput().trim() : undefined, undefined, this.selectedProfile() === 'pro' ? { verificationLevel: this.verificationLevel().id, explorerMode: this.explorerMode().id } : undefined ) .subscribe({ next: () => { this.isSubmitting.set(false); this.router.navigate(['/workspace']); }, error: (err) => { console.error('Failed to submit task from home page:', err); this.isSubmitting.set(false); this.errorMessage.set( err?.error?.detail || "提交任务失败,请检查服务器连接。" ); } }); } }