feat: release 1.4.0 with isolated upgrades and device discovery

This commit is contained in:
chenmingxuan committed 2026-10-09 15:46:28 +08:00
1 parent a6a3ee2eb0
commit df66897ff4
41 files changed
+1564 -126

No files matched your search

+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "artemis-showcase-ui",
"version": "1.3.3",
"version": "1.4.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "artemis-showcase-ui",
"version": "1.3.3",
"version": "1.4.0",
"dependencies": {
"@angular/cdk": "^22.1.5",
"@angular/common": "^22.1.4",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "artemis-showcase-ui",
"version": "1.3.3",
"version": "1.4.0",
"scripts": {
"ng": "ng",
"start": "ng serve --proxy-config proxy.conf.json",
@@ -4,6 +4,9 @@ import { HttpClient } from '@angular/common/http';
import { finalize } from 'rxjs';
interface Capabilities {
adb_auto_connect: boolean;
adb_discovery_interval_seconds: number;
adb_discovery_timeout_seconds: number;
video_enabled: boolean;
recording_enabled: boolean;
outputter_enabled: boolean;
@@ -24,6 +27,8 @@ interface Capabilities {
<section aria-label="功能开关">
<h3>功能开关</h3>
<p>按需启用,保存后对新任务生效。</p>
<label><input type="checkbox" role="switch" [(ngModel)]="config.adb_auto_connect" [disabled]="busy() || loading() || !loaded()" />
<span>自动连接设备<small>刷新设备时发现本机常见模拟器,并重连手动连接过的 TCP 设备;重启 ADB 后也会重连。不扫描局域网,不自动选择任务设备。保存后立即生效。</small></span></label>
<label><input type="checkbox" role="switch" [(ngModel)]="config.video_enabled" [disabled]="busy() || loading() || !loaded()" />
<span>视频分析<small>允许智能体分析任务画面。运行期间会临时录制,完成后不保留录像(除非开启录屏)。</small></span></label>
<label><input type="checkbox" role="switch" [(ngModel)]="config.recording_enabled" [disabled]="busy() || loading() || !loaded()" />
@@ -44,6 +49,8 @@ interface Capabilities {
<label>ADB 命令超时(秒)<input type="number" min="1" max="60" [(ngModel)]="config.device_command_timeout_seconds" [disabled]="busy() || !loaded()" /></label>
<label>连续动作失败次数<input type="number" min="1" max="10" step="1" [(ngModel)]="config.action_failure_attempts" [disabled]="busy() || !loaded()" /><small>先重新观察,再让兜底模型处理当前步骤。</small></label>
<label>任务收尾超时(秒)<input type="number" min="1" max="300" [(ngModel)]="config.worker_cleanup_timeout_seconds" [disabled]="busy() || !loaded()" /><small>已有最终结果但进程未退出时,回收进程以释放队列。</small></label>
<label>设备自动发现间隔(秒)<input type="number" min="1" max="300" [(ngModel)]="config.adb_discovery_interval_seconds" [disabled]="busy() || !loaded()" /></label>
<label>每轮自动连接预算(秒)<input type="number" min="1" max="30" [(ngModel)]="config.adb_discovery_timeout_seconds" [disabled]="busy() || !loaded()" /></label>
</div>
</details>
<button type="button" (click)="save()" [disabled]="busy() || loading() || !loaded() || !valid()">{{ busy() ? '正在保存…' : '保存功能设置' }}</button>
@@ -67,7 +74,7 @@ interface Capabilities {
export class CapabilitySettingsComponent implements OnInit {
changed = output<Capabilities>();
private http = inject(HttpClient);
config: Capabilities = { video_enabled: false, recording_enabled: false, outputter_enabled: true, step_summarizer_enabled: true, screenshot_limit_enabled: true, screenshot_max_long_edge: 2048, task_timeout_seconds: 1800, observation_timeout_seconds: 60, observation_max_attempts: 3, device_command_timeout_seconds: 10, action_failure_attempts: 3, worker_cleanup_timeout_seconds: 30 };
config: Capabilities = { adb_auto_connect: true, adb_discovery_interval_seconds: 10, adb_discovery_timeout_seconds: 5, video_enabled: false, recording_enabled: false, outputter_enabled: true, step_summarizer_enabled: true, screenshot_limit_enabled: true, screenshot_max_long_edge: 2048, task_timeout_seconds: 1800, observation_timeout_seconds: 60, observation_max_attempts: 3, device_command_timeout_seconds: 10, action_failure_attempts: 3, worker_cleanup_timeout_seconds: 30 };
busy = signal(false); loading = signal(true); loaded = signal(false); message = signal(''); error = signal('');
ngOnInit(): void {
this.http.get<Capabilities>('/api/system/capability-settings').pipe(finalize(() => this.loading.set(false))).subscribe({
@@ -89,6 +96,8 @@ export class CapabilitySettingsComponent implements OnInit {
&& Number.isInteger(c.observation_max_attempts) && c.observation_max_attempts >= 1 && c.observation_max_attempts <= 5
&& c.device_command_timeout_seconds > 0 && c.device_command_timeout_seconds <= 60
&& Number.isInteger(c.action_failure_attempts) && c.action_failure_attempts >= 1 && c.action_failure_attempts <= 10
&& c.worker_cleanup_timeout_seconds > 0 && c.worker_cleanup_timeout_seconds <= 300;
&& c.worker_cleanup_timeout_seconds > 0 && c.worker_cleanup_timeout_seconds <= 300
&& c.adb_discovery_interval_seconds >= 1 && c.adb_discovery_interval_seconds <= 300
&& c.adb_discovery_timeout_seconds > 0 && c.adb_discovery_timeout_seconds <= 30;
}
}
@@ -138,7 +138,7 @@ describe('持久化模型与功能设置', () => {
});
it('录屏与视频分析独立保存,重新打开后保留各自状态', () => {
const capabilities = { video_enabled: true, recording_enabled: false, outputter_enabled: true, step_summarizer_enabled: false, screenshot_limit_enabled: true, screenshot_max_long_edge: 2048, task_timeout_seconds: 1800, observation_timeout_seconds: 60, observation_max_attempts: 3, device_command_timeout_seconds: 10, action_failure_attempts: 3, worker_cleanup_timeout_seconds: 30 };
const capabilities = { adb_auto_connect: true, adb_discovery_interval_seconds: 10, adb_discovery_timeout_seconds: 5, video_enabled: true, recording_enabled: false, outputter_enabled: true, step_summarizer_enabled: false, screenshot_limit_enabled: true, screenshot_max_long_edge: 2048, task_timeout_seconds: 1800, observation_timeout_seconds: 60, observation_max_attempts: 3, device_command_timeout_seconds: 10, action_failure_attempts: 3, worker_cleanup_timeout_seconds: 30 };
const fixture = TestBed.createComponent(CapabilitySettingsComponent);
fixture.detectChanges();
http.expectOne('/api/system/capability-settings').flush(capabilities);
@@ -162,7 +162,7 @@ describe('持久化模型与功能设置', () => {
});
it('执行保护默认折叠,修改时限可保存且拒绝无效数值', async () => {
const capabilities = { video_enabled: false, recording_enabled: false, outputter_enabled: true, step_summarizer_enabled: true, screenshot_limit_enabled: true, screenshot_max_long_edge: 2048, task_timeout_seconds: 1800, observation_timeout_seconds: 60, observation_max_attempts: 3, device_command_timeout_seconds: 10, action_failure_attempts: 3, worker_cleanup_timeout_seconds: 30 };
const capabilities = { adb_auto_connect: true, adb_discovery_interval_seconds: 10, adb_discovery_timeout_seconds: 5, video_enabled: false, recording_enabled: false, outputter_enabled: true, step_summarizer_enabled: true, screenshot_limit_enabled: true, screenshot_max_long_edge: 2048, task_timeout_seconds: 1800, observation_timeout_seconds: 60, observation_max_attempts: 3, device_command_timeout_seconds: 10, action_failure_attempts: 3, worker_cleanup_timeout_seconds: 30 };
const fixture = TestBed.createComponent(CapabilitySettingsComponent);
fixture.detectChanges();
http.expectOne('/api/system/capability-settings').flush(capabilities);
@@ -190,4 +190,26 @@ describe('持久化模型与功能设置', () => {
fixture.componentInstance.save();
http.expectNone('/api/system/capability-settings');
});
it('自动连接默认开启,可独立关闭并保存发现预算', () => {
const fixture = TestBed.createComponent(CapabilitySettingsComponent);
const capabilities = { ...fixture.componentInstance.config };
fixture.detectChanges();
http.expectOne('/api/system/capability-settings').flush(capabilities);
fixture.detectChanges();
const root: HTMLElement = fixture.nativeElement;
expect(root.textContent).toContain('自动连接设备');
expect(fixture.componentInstance.config.adb_auto_connect).toBeTrue();
fixture.componentInstance.config.adb_auto_connect = false;
fixture.componentInstance.config.adb_discovery_timeout_seconds = 8;
fixture.componentInstance.save();
const request = http.expectOne('/api/system/capability-settings');
expect(request.request.body.adb_auto_connect).toBeFalse();
expect(request.request.body.adb_discovery_timeout_seconds).toBe(8);
expect(request.request.body.recording_enabled).toBeFalse();
request.flush(request.request.body);
fixture.componentInstance.config.adb_discovery_timeout_seconds = 0;
fixture.componentInstance.save();
http.expectNone('/api/system/capability-settings');
});
});
@@ -20,7 +20,7 @@ import { Component, signal, computed, inject, OnInit, OnDestroy, ChangeDetection
import { FormsModule } from '@angular/forms';
import { Router } from '@angular/router';
import { AgentService } from '../../services/agent.service';
import { SystemService } from '../../services/system.service';
import { SystemService, formatAdbRestartFeedback } from '../../services/system.service';
import {
AdbServerConnectionResult,
AdbServerDevice,
@@ -548,10 +548,8 @@ export class HomeComponent implements OnInit, OnDestroy {
this.adbRestartFeedback.set(null);
this.systemService.restartAdb().subscribe({
next: (res) => {
this.adbRestartFeedback.set(
res?.restart_result?.skipped ? "设备已刷新 ✓" : "ADB 已刷新 ✓"
);
setTimeout(() => this.adbRestartFeedback.set(null), 2500);
this.adbRestartFeedback.set(formatAdbRestartFeedback(res?.restart_result));
setTimeout(() => this.adbRestartFeedback.set(null), 6000);
},
error: () => {
this.adbRestartFeedback.set("重启失败");
@@ -3,7 +3,23 @@ import { HttpTestingController, provideHttpClientTesting } from '@angular/common
import { TestBed } from '@angular/core/testing';
import { SystemReadinessReport } from '../core/models/system.model';
import { SystemService } from './system.service';
import { SystemService, formatAdbRestartFeedback } from './system.service';
describe('ADB restart feedback', () => {
it('区分已连接与正在连接,不把等待就绪显示为失败', () => {
const message = formatAdbRestartFeedback({ success: true, automatic_connections: [{ success: true }, { success: false, pending: true }] });
expect(message).toContain('已连接 1 台设备');
expect(message).toContain('1 台设备连接中');
expect(message).not.toContain('失败');
});
it('显示连接失败数量,提示查看诊断', () => {
expect(formatAdbRestartFeedback({ success: true, automatic_connections: [{ success: false }] })).toContain('1 项连接失败,请查看诊断');
});
it('服务器重启失败不会显示成功,远程端点明确显示仅刷新', () => {
expect(formatAdbRestartFeedback({ success: false, message: '端口被占用' })).toBe('端口被占用');
expect(formatAdbRestartFeedback({ success: true, skipped: true })).toBe('设备已刷新 ✓');
});
});
describe('SystemService readiness polling', () => {
let service: SystemService;
@@ -27,6 +27,19 @@ import {
EmulatorLaunchStage
} from '../core/models/system.model';
export function formatAdbRestartFeedback(result?: {
success?: boolean; skipped?: boolean; message?: string;
automatic_connections?: { success?: boolean; pending?: boolean }[];
}): string {
if (!result?.success) return result?.message || '重启失败';
if (result.skipped) return '设备已刷新 ✓';
const connections = result.automatic_connections ?? [];
const connected = connections.filter(item => item.success).length;
const pending = connections.filter(item => item.pending).length;
const failed = connections.filter(item => !item.success && !item.pending).length;
return `ADB 已重启${connected ? `,已连接 ${connected} 台设备` : ''}${pending ? `,${pending} 台设备连接中,将自动重试` : ''}${failed ? `,${failed} 项连接失败,请查看诊断` : ' ✓'}`;
}
@Injectable({
providedIn: 'root'
})