feat: add guided exploration and share user-started MCP backend

This commit is contained in:
chenmingxuan committed 2026-10-16 18:32:46 +08:00
1 parent f86db1ced6
commit ee256ad7ce
144 files changed
+8625 -2075

No files matched your search

@@ -1,3 +1,5 @@
import { ExplorationMessage, Demonstration } from '../../services/exploration.service';
import { mergeConversation } from '../../utils/exploration-timeline.util';
import { TaskIdCopyComponent } from '../task-id-copy/task-id-copy.component';
import { TaskHistoryComponent } from '../task-history/task-history.component';
import { AutomationPanelComponent } from '../automation-panel/automation-panel.component';
@@ -18,7 +20,7 @@ import { UiLabelPipe } from '../../utils/ui-label.pipe';
* limitations under the License.
*/
import { Component, ChangeDetectionStrategy, NgZone, signal, computed, effect, inject, untracked, DestroyRef, AfterViewInit } from '@angular/core';
import { Component, ChangeDetectionStrategy, NgZone, signal, computed, effect, inject, untracked, DestroyRef, AfterViewInit, input, output } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { RouterLink } from '@angular/router';
@@ -248,6 +250,13 @@ export type { MarkdownSegment, MarkdownLine, NoteMilestone, ParsedNote, StreamRe
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AgentStreamComponent implements AfterViewInit {
conversationMessages = input<ExplorationMessage[]>([]);
demonstrations = input<Demonstration[]>([]);
pendingQuestion = input('');
demoMediaUrl(runId: string, path: string) { return '/videos/' + [runId, ...path.split('/')].map(encodeURIComponent).join('/'); }
demoLabel(status: string) { return ({requested:'准备录制',recording:'录制中',analyzing:'分析中',completed:'已分析',failed:'分析失败',cancelled:'已取消',timed_out:'到时已保存'} as Record<string,string>)[status] || status; }
useRecommendation = output<string>();
public agentService = inject(AgentService);
private http = inject(HttpClient);
private destroyRef = inject(DestroyRef);
@@ -393,7 +402,7 @@ export class AgentStreamComponent implements AfterViewInit {
public activeQueue = computed(() => {
const list = this.agentService.sessions().filter((s) => {
const status = this.getTaskStatus(s);
return ['running', 'paused', 'task_paused', 'resuming', 'pending', 'queued'].includes(status) && this.agentService.matchesBatchFilter(s);
return ['running', 'paused', 'task_paused', 'resuming', 'pending', 'queued'].includes(status) && this.agentService.matchesBatchFilter(s) && this.agentService.isQueueRepresentative(s);
});
return list.sort((a, b) => {
const statusA = this.getTaskStatus(a);
@@ -501,9 +510,9 @@ export class AgentStreamComponent implements AfterViewInit {
return terminal ? '模型已读取,未留下后续动作记录' : '模型已读取,等待动作记录';
}
public phases = computed<PhaseBlock[]>(() => {
public phases = computed(() => {
const currentSession = this.agentService.sessions().find(s => s.session_id === this.agentService.currentSessionId());
return groupBlocksToPhases(this.consolidatedBlocks(), currentSession?.start_time || 0);
return mergeConversation(groupBlocksToPhases(this.consolidatedBlocks(), currentSession?.start_time || 0), this.conversationMessages(), this.demonstrations());
});
public startupWorkItems = computed(() => {
@@ -568,6 +577,12 @@ export class AgentStreamComponent implements AfterViewInit {
}
constructor() {
const conversationRevision = computed(() => JSON.stringify([
this.conversationMessages().map(message => message.id),
this.demonstrations().map(demo => [demo.id,demo.status])
]));
effect(() => { conversationRevision(); untracked(() => this.scheduleAutoScroll(false)); });
effect((onCleanup) => {
const id = this.agentService.currentSessionId();
const status = this.selectedTerminalStatus();
@@ -924,6 +939,9 @@ export class AgentStreamComponent implements AfterViewInit {
}
public getTaskStatus(session: Session): string {
const exploration = this.agentService.explorationQueueStatus(session);
if (exploration) return exploration as 'running' | 'paused' | 'completed' | 'pending' | 'failed' | 'cancelled';
if (session.status) {
const s = session.status.toLowerCase();
if (['completed', 'success', 'failed', 'cancelled', 'interrupted', 'skipped'].includes(s)) {
@@ -2233,6 +2251,7 @@ export class AgentStreamComponent implements AfterViewInit {
public getModelIcon(name: string | undefined | null): string {
if (!name) return 'bolt';
const lower = name.toLowerCase();
if (lower === '探索') return 'explore';
if (lower.includes('pro')) return 'diamond';
if (lower.includes('flash')) return 'bolt';
return 'smart_toy';
@@ -2248,6 +2267,7 @@ export class AgentStreamComponent implements AfterViewInit {
public getArchitectureTooltip(model?: ModelInfo | null): string {
if (!model) return "执行模式:ARTEMIS Flash(快速响应)";
if (model.architecture === 'explore') return `探索模式(可交流与人工示范) · LLM: ${model.id}`;
const name = this.getModelDisplayName(model.name);
const isPro = name.toLowerCase().includes('pro');
const archDesc = isPro