feat: release 1.1.0 with responsive task control
This commit is contained in:
1 parent
057f0c3050
commit
bfcd828fa8
37 files changed
+1601
-151
No files matched your search
@@ -331,7 +331,7 @@ limitations under the License.
|
||||
</header>
|
||||
|
||||
<!-- Floating Top-Right Badges & Toolbar (Pinned overlay, hidden in empty/initial state) -->
|
||||
@if (consolidatedBlocks().length > 0 || showPlanningLoader()) {
|
||||
@if (consolidatedBlocks().length > 0 || showPlanningLoader() || agentService.currentSession()) {
|
||||
<div class="stream-floating-toolbar">
|
||||
<!-- Active Architecture Chip: click opens the run info popover -->
|
||||
@if (agentService.viewedModel(); as model) {
|
||||
@@ -492,6 +492,7 @@ limitations under the License.
|
||||
</span>
|
||||
}
|
||||
</button>
|
||||
<ng-content select="[task-guidance]"></ng-content>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -568,6 +569,13 @@ limitations under the License.
|
||||
>
|
||||
<!-- Unified Step / Stream Body -->
|
||||
<div class="step-body">
|
||||
@if (block.data?.extra_metadata?.injected_instruction; as guidance) {
|
||||
<div class="guidance-step-note">
|
||||
<div class="guidance-step-title"><span class="material-symbols-outlined">forum</span>用户指导 · 已进入第 {{block.data.step_number}} 步</div>
|
||||
<p>{{ guidance }}</p>
|
||||
<small>{{ hasRecordedDeviceAction(block.data.action_taken) ? '本步设备动作见下方;是否完成目标需以实际屏幕为准。' : '本步没有记录设备动作。' }}</small>
|
||||
</div>
|
||||
}
|
||||
<!-- Check block (verifier lane): header, then the declared check items
|
||||
and how each is judged; the chronological stream follows, and the
|
||||
result rows close the block (see check-result below). -->
|
||||
@@ -746,7 +754,7 @@ limitations under the License.
|
||||
@if (getReportStatusExplanation(item.data); as expText) {
|
||||
<div class="grid-item full-width">
|
||||
<span class="grid-label">报告与发现</span>
|
||||
<span class="grid-value report-markdown-body" [innerHTML]="renderMarkdown(expText, 'report-' + block.id + '-' + itemIdx)"></span>
|
||||
<div class="grid-value report-markdown-body" [innerHTML]="renderMarkdown(expText, 'report-' + block.id + '-' + itemIdx)"></div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -1283,6 +1291,23 @@ limitations under the License.
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@if (awaitingGuidance().length) {
|
||||
<div class="guidance-queue" aria-label="待处理用户指导">
|
||||
@for (item of awaitingGuidance(); track item.id) {
|
||||
<div class="guidance-queue-item">
|
||||
<strong>用户指导 · {{ guidanceStatusLabel(item) }}</strong>
|
||||
<p>{{ item.instruction }}</p>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@if (terminalFailureReason(); as reason) {
|
||||
<div class="session-failure-summary" role="status">
|
||||
<strong>任务执行失败 · 原因</strong>
|
||||
<p>{{ reason }}</p>
|
||||
<button type="button" (click)="isAutomationOpen.set(true)">查看完整报告</button>
|
||||
</div>
|
||||
}
|
||||
<!-- Outputter Post-Execution Synthesis Card (Rendered ONCE at the end of stream) -->
|
||||
@if (outputterReport(); as outReport) {
|
||||
<div class="outputter-synthesis-stream-card">
|
||||
|
||||
@@ -1921,6 +1921,8 @@ $stream-row-height: 25px;
|
||||
}
|
||||
|
||||
&.task-report-card {
|
||||
width: min(680px, 100%);
|
||||
box-sizing: border-box;
|
||||
background: #f0fdf4;
|
||||
border: 1px solid #bbf7d0;
|
||||
|
||||
@@ -1967,12 +1969,13 @@ $stream-row-height: 25px;
|
||||
}
|
||||
|
||||
.report-markdown-body {
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
font-size: 14px;
|
||||
line-height: 1.7;
|
||||
color: #1f2937;
|
||||
font-weight: 400;
|
||||
user-select: text;
|
||||
word-break: break-word;
|
||||
word-break: normal;
|
||||
overflow-wrap: anywhere;
|
||||
|
||||
p {
|
||||
margin: 3px 0;
|
||||
@@ -2227,11 +2230,19 @@ $stream-row-height: 25px;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.grid-value {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
word-break: break-all;
|
||||
.grid-value {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
word-break: break-all;
|
||||
|
||||
&.report-markdown-body {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 1.7;
|
||||
word-break: normal;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
&.code-pill {
|
||||
font-family: 'Roboto Mono', 'Fira Code', Consolas, monospace;
|
||||
@@ -3971,3 +3982,48 @@ $stream-row-height: 25px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.guidance-step-note, .guidance-queue-item {
|
||||
padding: 10px 13px;
|
||||
border: 1px solid #bfdbfe;
|
||||
border-radius: 10px;
|
||||
background: #eff6ff;
|
||||
color: #1e3a8a;
|
||||
line-height: 1.6;
|
||||
overflow-wrap: anywhere;
|
||||
.guidance-step-title { display: flex; align-items: center; gap: 6px; font-weight: 650; }
|
||||
.material-symbols-outlined { font-size: 17px; }
|
||||
p { margin: 5px 0; white-space: pre-wrap; color: #1e293b; }
|
||||
small { color: #475569; }
|
||||
}
|
||||
.guidance-queue { display: grid; gap: 8px; padding: 12px 16px; }
|
||||
.guidance-queue-item strong { font-size: 12px; }
|
||||
.session-failure-summary {
|
||||
margin: 12px 16px 28px;
|
||||
padding: 12px 14px;
|
||||
border: 1px solid #fecdd3;
|
||||
border-radius: 10px;
|
||||
background: #fff7f8;
|
||||
color: #881337;
|
||||
strong { font-size: 13px; }
|
||||
p { margin: 6px 0 10px; line-height: 1.6; white-space: pre-wrap; overflow-wrap: anywhere; }
|
||||
button { border: 0; background: transparent; padding: 0; color: #be123c; font: inherit; font-weight: 600; cursor: pointer; }
|
||||
}
|
||||
:host ::ng-deep .report-markdown-body {
|
||||
display: block;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
line-height: 1.7;
|
||||
overflow-wrap: anywhere;
|
||||
word-break: normal;
|
||||
> div { margin: 3px 0; }
|
||||
h1, h2, h3 { margin: 12px 0 6px; font-size: 14px; color: #0f172a; }
|
||||
ul, ol { padding-left: 22px; margin: 8px 0; }
|
||||
li { margin: 4px 0; }
|
||||
pre { overflow: auto; white-space: pre-wrap; padding: 10px; border-radius: 7px; background: #e2e8f0; }
|
||||
table { width: 100%; border-collapse: collapse; }
|
||||
th, td { padding: 7px 9px; border: 1px solid #cbd5e1; text-align: left; vertical-align: top; }
|
||||
th { background: #dbeafe; }
|
||||
.md-para-gap { height: 8px; }
|
||||
}
|
||||
@@ -23,7 +23,7 @@ import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { RouterLink } from '@angular/router';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { AgentService, StartupProgressEvent } from '../../services/agent.service';
|
||||
import { AgentService, GuidanceEvent, StartupProgressEvent } from '../../services/agent.service';
|
||||
import { Session, ModelInfo, SessionUsage } from '../../core/models/session.model';
|
||||
import { MarkdownSegment, MarkdownLine, NoteMilestone, ParsedNote } from '../../core/models/markdown.model';
|
||||
import { OverlayModule, ConnectedPosition } from '@angular/cdk/overlay';
|
||||
@@ -247,6 +247,11 @@ export class AgentStreamComponent implements AfterViewInit {
|
||||
private http = inject(HttpClient);
|
||||
private destroyRef = inject(DestroyRef);
|
||||
private zone = inject(NgZone);
|
||||
public terminalFailureReason = signal('');
|
||||
private selectedTerminalStatus = computed(() => {
|
||||
const id = this.agentService.currentSessionId();
|
||||
return this.agentService.sessions().find(session => session.session_id === id)?.status || '';
|
||||
});
|
||||
|
||||
// Auto-scroll state tracking
|
||||
public isUserAtBottom = true;
|
||||
@@ -474,6 +479,23 @@ export class AgentStreamComponent implements AfterViewInit {
|
||||
return consolidateLogsToBlocks(this.filteredLogs());
|
||||
});
|
||||
|
||||
public awaitingGuidance = computed<GuidanceEvent[]>(() => {
|
||||
const steps = this.consolidatedBlocks().filter(block => block.type === 'step');
|
||||
return (this.agentService.guidanceEvents?.() || []).filter(event => !steps.some(block =>
|
||||
new Date(block.timestamp).getTime() >= event.timestamp * 1000
|
||||
&& String(block.data?.extra_metadata?.injected_instruction || '').includes(event.instruction)
|
||||
));
|
||||
});
|
||||
|
||||
public guidanceStatusLabel(event: GuidanceEvent): string {
|
||||
const terminal = ['completed', 'failed', 'cancelled', 'interrupted']
|
||||
.includes(this.agentService.currentSession()?.status || '');
|
||||
if (event.status === 'pending') {
|
||||
return terminal ? '任务结束前未进入模型' : '已接收,等待下一步';
|
||||
}
|
||||
return terminal ? '模型已读取,未留下后续动作记录' : '模型已读取,等待动作记录';
|
||||
}
|
||||
|
||||
public phases = computed<PhaseBlock[]>(() => {
|
||||
const currentSession = this.agentService.sessions().find(s => s.session_id === this.agentService.currentSessionId());
|
||||
return groupBlocksToPhases(this.consolidatedBlocks(), currentSession?.start_time || 0);
|
||||
@@ -541,6 +563,17 @@ export class AgentStreamComponent implements AfterViewInit {
|
||||
}
|
||||
|
||||
constructor() {
|
||||
effect((onCleanup) => {
|
||||
const id = this.agentService.currentSessionId();
|
||||
const status = this.selectedTerminalStatus();
|
||||
this.terminalFailureReason.set('');
|
||||
if (!id || !['failed', 'interrupted'].includes(status || '')) return;
|
||||
const request = this.http.get<{error?: string}>(`/api/automation/reports/${encodeURIComponent(id)}`).subscribe({
|
||||
next: report => this.terminalFailureReason.set(report.error || '任务未完成;请查看已记录的执行步骤。'),
|
||||
error: () => this.terminalFailureReason.set('任务未完成,暂时无法读取异常详情。')
|
||||
});
|
||||
onCleanup(() => request.unsubscribe());
|
||||
});
|
||||
// Run info popover: fetch usage when opened or when the viewed session
|
||||
// changes, and keep refreshing while that session is still running.
|
||||
effect((onCleanup) => {
|
||||
@@ -1327,6 +1360,11 @@ export class AgentStreamComponent implements AfterViewInit {
|
||||
return isAndroidAction(action);
|
||||
}
|
||||
|
||||
public hasRecordedDeviceAction(action: any): boolean {
|
||||
const actions = Array.isArray(action) ? action : action ? [action] : [];
|
||||
return actions.some(item => this.isAndroidAction(item) && !this.isReportStatusAction(item));
|
||||
}
|
||||
|
||||
public getActionIcon(action: any): string {
|
||||
return getActionIcon(action);
|
||||
}
|
||||
@@ -1813,6 +1851,7 @@ export class AgentStreamComponent implements AfterViewInit {
|
||||
public hasVisibleContent(block: any): boolean {
|
||||
if (!block) return false;
|
||||
if (block.type === 'checker') return true;
|
||||
if (block.data?.extra_metadata?.injected_instruction) return true;
|
||||
const hasNative = Boolean(this.getNativeThinking(block));
|
||||
const hasRaw = Boolean(this.getRawThinking(block));
|
||||
const hasReset = this.hasBlockResets(block, false) || this.hasBlockResets(block, true);
|
||||
|
||||
Reference in new issue
Block a user