80 lines
7.2 KiB
HTML
80 lines
7.2 KiB
HTML
<nav class="tabs" aria-label="自动化工具">
|
||
<button [class.selected]="tab() === 'reports'" (click)="tab.set('reports')">结论报告</button>
|
||
<button [class.selected]="tab() === 'batches'" (click)="tab.set('batches')">批量任务</button>
|
||
</nav>
|
||
@if (error()) { <p class="error" role="alert">{{error()}}</p> }
|
||
@if (notice()) { <p class="notice" role="status">{{notice()}}</p> }
|
||
@if (tab() === 'reports') {
|
||
<section><h3>默认报告格式</h3><p>任务结束后自动生成。批次报告包同时包含汇总和每个任务的报告。</p>
|
||
<div class="format-options"><label><input type="checkbox" [(ngModel)]="word"> Word 文档</label>
|
||
<label><input type="checkbox" [(ngModel)]="excel"> Excel 表格</label></div>
|
||
<button [disabled]="busy()" (click)="savePreferences()">保存默认设置</button>
|
||
</section>
|
||
<section><h3>当前任务报告</h3>
|
||
@if (report(); as r) {
|
||
<div class="report-overview">
|
||
<strong class="report-title">{{r.name}}</strong>
|
||
<div class="report-badges"><span [class.failed]="r.status === 'failed'">{{label(r.status)}}</span><span>{{r.verdict}}</span></div>
|
||
@if (r.error) { <div class="report-error" role="status"><strong>异常原因</strong><p>{{r.error}}</p></div> }
|
||
</div>
|
||
<div class="report-meta"><div><strong>任务目标</strong><p>{{r.goal || '未记录'}}</p></div><div><strong>预期结果</strong><p>{{r.expected || '未指定'}}</p></div></div>
|
||
<div class="actions"><a [href]="'/api/automation/reports/'+r.id+'/docx'">下载 Word</a><a [href]="'/api/automation/reports/'+r.id+'/xlsx'">下载 Excel</a>
|
||
<a [href]="'/api/automation/reports/'+r.id+'/screenshots'">步骤截图 ZIP</a>
|
||
<a [href]="'/api/automation/reports/'+r.id+'/video'">分段录屏 ZIP</a>
|
||
<a [href]="'/api/automation/reports/'+r.id+'/all'">下载全部资料</a></div>
|
||
<p class="muted">报告包含目标、预期、实际结论、步骤与截图证据。执行中的下载为当前快照。</p>
|
||
<div class="report-content"><h3>结论与发现</h3><div class="report-body" [innerHTML]="renderReport(r.content)"></div></div>
|
||
} @else { <p>从任务队列或历史记录中选择任务,即可下载报告。</p> }
|
||
@if (liveTask() && agent.currentSessionId()) {
|
||
<div class="task-guidance">
|
||
<h3>任务指导 · {{label(taskStatus())}}</h3>
|
||
<p>{{ taskStatus() === 'paused' ? '模型请求已暂停;提交指导后请在执行日志中恢复模型调用。' : '运行中提交将在下一步执行前生效;暂停后会释放设备,提交指导即可重新排队恢复。' }}</p>
|
||
<label for="task-guidance-text">补充信息或调整方向</label>
|
||
<textarea id="task-guidance-text" [(ngModel)]="guidance" maxlength="10000" rows="3" placeholder="描述已观察到的情况和下一步方向"></textarea>
|
||
<div class="actions">
|
||
@if(taskStatus() === 'running'){<button [disabled]="busy()" (click)="pauseTask(agent.currentSessionId()!)">暂停当前任务</button>}
|
||
<button [disabled]="busy() || !guidance.trim()" (click)="guideTask(agent.currentSessionId()!)">提交指导{{taskStatus() === 'task_paused' ? '并恢复' : ''}}</button>
|
||
@if(taskStatus() === 'task_paused'){<button [disabled]="busy()" (click)="continueTask(agent.currentSessionId()!)">直接继续</button>}
|
||
</div>
|
||
</div>
|
||
}
|
||
</section>
|
||
}
|
||
@if (tab() !== 'reports') {
|
||
<section class="device"><label>执行设备<select [(ngModel)]="device"><option value="">请选择设备</option>@for(d of system.connectedDevices(); track d.serial){<option [value]="d.serial">{{d.serial}}</option>}</select></label></section>
|
||
}
|
||
@if (tab() === 'batches') {
|
||
<section><h3>导入任务</h3><p>每行一个用例,仅「Artemis提示词」作为任务正文;「任务配置(JSON)」可设置每项模型、模式、超时和录屏。「前置约束提取」仅供查看与搜索。示例在模板的「填写说明」页。</p>
|
||
<div class="actions"><a href="/api/automation/template">下载 Excel 模板</a><label class="upload">导入 Excel<input type="file" accept=".xlsx" [disabled]="busy()" (change)="upload($event)"></label></div>
|
||
@if(preview(); as p) {
|
||
<p>有效任务 {{p.tasks.length}} 条 · 错误 {{p.errors.length}} 条</p>
|
||
@for(e of p.errors; track e.row){<p class="error">第 {{e.row}} 行:{{e.message}}</p>}
|
||
<div class="table-wrap"><table><thead><tr><th>行</th><th>用例</th><th>提示词</th><th>任务配置</th></tr></thead><tbody>@for(t of p.tasks; track t.row){<tr><td>{{t.row}}</td><td>{{t.name}}</td><td>{{t.goal}}</td><td>{{t.task_config | json}}</td></tr>}</tbody></table></div>
|
||
<label>批次名称<input [(ngModel)]="name" maxlength="200"></label>
|
||
<div class="fields"><label>执行模式<select [(ngModel)]="profile"><option value="flash">Flash</option><option value="pro">Pro</option></select></label><label>单任务时限(秒)<input type="number" min="30" max="86400" [(ngModel)]="timeout"></label></div>
|
||
<label><input type="checkbox" [(ngModel)]="stopOnFailure"> 失败即停止后续任务(默认继续)</label>
|
||
<label><input type="checkbox" [(ngModel)]="word"> Word</label><label><input type="checkbox" [(ngModel)]="excel"> Excel</label>
|
||
<button class="primary" [disabled]="busy() || !p.valid || !device || !name.trim() || !formats().length" (click)="startBatch()">确认导入并开始执行</button>
|
||
}
|
||
</section>
|
||
<section><h3>批次记录</h3>
|
||
@for(b of batches(); track b.id){
|
||
<div class="batch-record">
|
||
<button class="record" [class.selected]="batch()?.id === b.id" (click)="selectBatch(b.id)"><strong>{{b.name}}</strong><span>{{label(b.status)}} · {{b.created_at*1000 | date:'MM-dd HH:mm'}}</span></button>
|
||
<button class="delete-batch" type="button" [disabled]="busy() || b.status === 'running'" (click)="deleteBatch(b)" [attr.aria-label]="'删除批次:' + b.name" [title]="b.status === 'running' ? '执行中的批次不能删除' : '删除批次记录(保留已执行任务与报告)'"><span class="material-symbols-outlined">delete_outline</span></button>
|
||
</div>
|
||
}
|
||
@if(!batches().length){<p>暂无批次。</p>}
|
||
</section>
|
||
@if(batch(); as b){<section><h3>{{b.name}}</h3><p>{{label(b.status)}} · {{b.reason}}</p>
|
||
<div class="actions">
|
||
@if(b.status === 'running'){<button [disabled]="busy()" (click)="batchAction('pause')">暂停后续任务</button>}
|
||
@if(b.status === 'draft' || b.status === 'paused'){<button [disabled]="busy()" (click)="batchAction('resume')">{{b.status === 'draft' ? '开始执行' : '继续未开始任务'}}</button>}
|
||
@if(b.status !== 'running'){<button [disabled]="busy()" (click)="batchAction('retry_failed')">复制失败项为新批次</button>}
|
||
<a [href]="'/api/automation/batches/'+b.id+'/archive'">下载批次报告 ZIP</a>
|
||
</div><p class="muted">服务重启后等待手动继续。失败项不会自动重试;暂停不打断当前任务。</p>
|
||
@if(b.report_error){<p class="error">报告生成失败:{{b.report_error}}</p>}
|
||
@for(j of b.jobs; track j.session_id){<div class="job"><button (click)="agent.selectSession(j.session_id,true); tab.set('reports')">{{j.position}}. {{j.case.name}}</button><span>{{label(j.status)}}</span>@if(j.error){<small>{{j.error}}</small>}</div>}
|
||
</section>}
|
||
}
|