feat: add favorite task templates and optimize evidence exports
This commit is contained in:
1 parent
e1ea099592
commit
a4a5430922
46 files changed
+1714
-188
No files matched your search
@@ -0,0 +1,91 @@
|
||||
import { Component, ChangeDetectionStrategy, computed, inject, signal } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { Router } from '@angular/router';
|
||||
import { FavoritesService, Favorite } from '../../services/favorites.service';
|
||||
import { AgentService } from '../../services/agent.service';
|
||||
import { SystemService } from '../../services/system.service';
|
||||
import { SettingSliderComponent, REASONING_VALUES, REASONING_LABELS } from '../setting-slider/setting-slider.component';
|
||||
|
||||
interface ModelDraft {profile: string; model: string; reasoning_effort: string; device_serial: string;}
|
||||
interface Draft extends ModelDraft {name: string; uniform: boolean; cases: ModelDraft[];}
|
||||
|
||||
@Component({
|
||||
selector: 'app-featured-tasks', standalone: true, imports: [FormsModule, SettingSliderComponent],
|
||||
templateUrl: './featured-tasks.component.html', styleUrl: './featured-tasks.component.scss',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class FeaturedTasksComponent {
|
||||
api = inject(FavoritesService); system = inject(SystemService); private agent = inject(AgentService); private router = inject(Router);
|
||||
query = signal(''); filter = signal('all'); expanded = signal<string | null>(null);
|
||||
busy = signal<string | null>(null); error = signal(''); notice = signal('');
|
||||
drafts = new Map<string, Draft>();
|
||||
readonly efforts = REASONING_VALUES; readonly effortLabels = REASONING_LABELS;
|
||||
readonly models = ['gpt-6.1-sol','gpt-6-astra','gpt-6-luna','mimo-v2.6-pro','mimo-v2.6-flash'];
|
||||
readonly filters = [{id:'all',label:'全部'},{id:'flash',label:'Flash'},{id:'pro',label:'Pro'},{id:'exploration',label:'探索'},{id:'batch',label:'批量'}];
|
||||
filtered = computed(() => this.api.items().filter(item => {
|
||||
const filter = this.filter(), query = this.query().trim().toLowerCase();
|
||||
return (filter === 'all' || (filter === 'batch' ? item.kind === 'batch' : filter === 'exploration' ? ['exploration','exploration_case'].includes(item.kind) : item.kind !== 'batch' && this.options(item)['profile'] === filter)) && (!query || `${item.name} ${item.snapshot.goal || ''}`.toLowerCase().includes(query));
|
||||
}));
|
||||
constructor() { void this.api.load(); }
|
||||
options(item: Favorite): Record<string, any> { return item.kind === 'exploration' ? {...item.snapshot.task_config,...item.snapshot.config,profile:'exploration'} : item.snapshot.runtime_overrides || {}; }
|
||||
mode(item: Favorite) { return item.kind === 'batch' ? '批量' : item.kind === 'exploration' ? '重新探索' : (this.options(item)['profile'] === 'pro' ? 'Pro' : 'Flash') + (item.kind === 'exploration_case' ? ' · 用例复跑' : ''); }
|
||||
summary(item: Favorite) { return item.kind === 'batch' ? `${item.snapshot.cases?.length || 0} 个用例 · ${item.snapshot.repeat_count || 1} 轮 · 保留各项配置` : `${this.options(item)['model'] || '当前默认'} / ${this.options(item)['reasoning_effort'] || '当前默认'}`; }
|
||||
computer(options: Record<string, any>) { return options['execution_target'] === 'computer'; }
|
||||
modelDraft(options: Record<string, any>): ModelDraft { return {profile:options['profile'] || 'flash', model:options['model'] || '', reasoning_effort:options['reasoning_effort'] || 'default', device_serial:this.computer(options) ? '' : options['device_serial'] || ''}; }
|
||||
draft(item: Favorite): Draft {
|
||||
let draft = this.drafts.get(item.id);
|
||||
if (!draft) { draft = {...this.modelDraft(item.kind === 'batch' ? item.snapshot.cases?.[0]?.task_config || {} : this.options(item)), name:item.name, uniform:false, cases:(item.snapshot.cases || []).map(c => this.modelDraft(c.task_config))}; this.drafts.set(item.id,draft); }
|
||||
return draft;
|
||||
}
|
||||
toggle(item: Favorite) { this.expanded.set(this.expanded() === item.id ? null : item.id); }
|
||||
private changes(current: ModelDraft, original: Record<string, any>) {
|
||||
const baseline = this.modelDraft(original), result: Record<string, unknown> = {};
|
||||
for (const key of ['profile','model','reasoning_effort','device_serial'] as const) {
|
||||
if (current[key] !== baseline[key] && current[key]) result[key] = current[key];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
payload(item: Favorite): Record<string, unknown> {
|
||||
const draft = this.drafts.get(item.id); if (!draft) return {};
|
||||
if (item.kind === 'batch') {
|
||||
const case_configs: Record<number, unknown> = {};
|
||||
draft.cases.forEach((c,i) => { const changes = this.changes(c,item.snapshot.cases![i].task_config); if(Object.keys(changes).length)case_configs[i]=changes; });
|
||||
return {case_configs, ...(draft.uniform ? {task_config:{model:draft.model,reasoning_effort:draft.reasoning_effort}} : {})};
|
||||
}
|
||||
return {task_config:this.changes(draft,this.options(item))};
|
||||
}
|
||||
private deviceMissing(item: Favorite) {
|
||||
const devices = this.system.connectedDevices();
|
||||
const cases = item.kind === 'batch' ? item.snapshot.cases!.map((c,i) => ({...c.task_config,...(this.payload(item)['case_configs'] as any)?.[i]})) : [{...this.options(item),...(this.payload(item)['task_config'] as any)}];
|
||||
return cases.some(options => !this.computer(options) && (!options['device_serial'] || !devices.some(d => d.serial === options['device_serial'] && d.state === 'device')));
|
||||
}
|
||||
async save(item: Favorite) {
|
||||
if(!this.validModels(item)){this.error.set('模型名称不能为空');return;}
|
||||
if(this.busy())return;this.busy.set(item.id);this.error.set('');this.notice.set('');
|
||||
try { const saved = await this.api.update(item.id,{...this.payload(item),name:this.draft(item).name});this.drafts.delete(item.id);this.draft(saved);this.notice.set('已保存收藏的默认配置'); }
|
||||
catch(e){this.error.set((e as any)?.error?.detail || '保存失败,请重试');}finally{this.busy.set(null);}
|
||||
}
|
||||
async remove(item: Favorite) {
|
||||
if(this.busy())return;this.busy.set(item.id);this.error.set('');
|
||||
try{await this.api.remove(item.id);this.drafts.delete(item.id);}catch(e){this.error.set((e as any)?.error?.detail || '取消收藏失败');}finally{this.busy.set(null);}
|
||||
}
|
||||
async run(item: Favorite) {
|
||||
if(this.busy())return;this.error.set('');this.notice.set('');
|
||||
if(!this.validModels(item)){this.error.set('模型名称不能为空');return;}
|
||||
if(this.deviceMissing(item)){this.expanded.set(item.id);this.error.set('原设备不在线,请在配置中选择可用设备后运行。');return;}
|
||||
const draft=this.drafts.get(item.id);if(draft?.uniform && !draft.model.trim()){this.error.set('请填写统一使用的模型');return;}
|
||||
this.busy.set(item.id);
|
||||
try{
|
||||
const result=await this.api.run(item.id,this.payload(item));
|
||||
this.agent.fetchSessions();
|
||||
if(result.batch_id){this.agent.batchFilter.set(result.batch_id);this.agent.activeTab.set('tasks');}
|
||||
else{const id=result.session_id || result.admission?.run?.id;if(id)this.agent.selectSession(id,true);}
|
||||
await this.router.navigate(['/workspace']);
|
||||
}catch(e){this.error.set((e as any)?.error?.detail || '运行请求失败;重试将沿用相同请求编号,避免重复执行。');}
|
||||
finally{this.busy.set(null);}
|
||||
}
|
||||
private validModels(item: Favorite) {
|
||||
const draft=this.drafts.get(item.id);if(!draft)return true;
|
||||
return item.kind === 'batch' ? draft.uniform ? !!draft.model.trim() : draft.cases.every(c => !!c.model.trim()) : !!draft.model.trim();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user