feat: add Chinese UI, Responses models, and mirrored Windows setup
Localize the web console and persist custom model and capability settings. Use OpenAI Responses exclusively and move runtime limits into environment configuration. Bootstrap uv and the virtual environment on Windows, support optional China mirrors, and package source distributions without environments, build output, or credentials. Update regression tests and repository quality checks for the new behavior.
This commit is contained in:
1 parent
371aa6df56
commit
2ddec53954
183 files changed
+3427
-11857
No files matched your search
@@ -1,3 +1,4 @@
|
||||
import { UiLabelPipe } from '../../utils/ui-label.pipe';
|
||||
/**
|
||||
* Copyright 2026 Google LLC
|
||||
*
|
||||
@@ -34,22 +35,22 @@ import {
|
||||
} from '../../utils/run-info.util';
|
||||
|
||||
export const PLANNING_LOADER_PHRASES: string[] = [
|
||||
'Planning next step...',
|
||||
'Analyzing screen coordinates...',
|
||||
'Consulting neural network...',
|
||||
'Formulating tactical action plan...',
|
||||
'Deciphering UI state & elements...',
|
||||
'Synthesizing decision pathways...',
|
||||
'Calibrating next move...',
|
||||
'Aligning logical vectors...',
|
||||
'Evaluating optimal sub-goals...',
|
||||
'Gathering sensory inputs...',
|
||||
'Computing next interaction...',
|
||||
'Optimizing execution strategy...',
|
||||
'Simulating probable outcomes...',
|
||||
'Summoning AI intuition...',
|
||||
'Brewing the next command...',
|
||||
'Strategizing tactical moves...'
|
||||
"正在规划下一步…",
|
||||
"正在分析屏幕坐标…",
|
||||
"正在请求模型…",
|
||||
"正在制定操作计划…",
|
||||
"正在识别界面状态与元素…",
|
||||
"正在评估执行路径…",
|
||||
"正在确认下一步操作…",
|
||||
"正在整理推理结果…",
|
||||
"正在评估子目标…",
|
||||
"正在收集屏幕信息…",
|
||||
"正在分析下一步交互…",
|
||||
"正在调整执行策略…",
|
||||
"正在预测操作结果…",
|
||||
"正在分析任务…",
|
||||
"正在准备下一条指令…",
|
||||
"正在规划操作步骤…"
|
||||
];
|
||||
|
||||
export interface StartupWorkItem extends StartupProgressEvent {
|
||||
@@ -71,12 +72,12 @@ const STARTUP_WORK_STAGES: StartupWorkStage[] = [
|
||||
{
|
||||
started: 'device_check',
|
||||
completed: 'device_ready',
|
||||
completedMessage: 'Android device connected'
|
||||
completedMessage: "Android 设备已连接"
|
||||
},
|
||||
{
|
||||
started: 'uiautomator',
|
||||
completed: 'uiautomator_ready',
|
||||
completedMessage: 'UI hierarchy service is ready',
|
||||
completedMessage: "界面层级服务已就绪",
|
||||
// First task on a device installs / upgrades the accessibility helper
|
||||
// (a few seconds): say so instead of a generic "connecting".
|
||||
liveDetailStages: ['helper_install', 'helper_upgrade'],
|
||||
@@ -86,7 +87,7 @@ const STARTUP_WORK_STAGES: StartupWorkStage[] = [
|
||||
{
|
||||
started: 'environment',
|
||||
completed: 'environment_ready',
|
||||
completedMessage: 'Device environment is ready'
|
||||
completedMessage: "设备环境已就绪"
|
||||
}
|
||||
];
|
||||
|
||||
@@ -233,7 +234,7 @@ export type { MarkdownSegment, MarkdownLine, NoteMilestone, ParsedNote, StreamRe
|
||||
@Component({
|
||||
selector: 'app-agent-stream',
|
||||
standalone: true,
|
||||
imports: [CommonModule, FormsModule, OverlayModule],
|
||||
imports: [UiLabelPipe, CommonModule, FormsModule, OverlayModule],
|
||||
templateUrl: './agent-stream.component.html',
|
||||
styleUrl: './agent-stream.component.scss',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
@@ -558,7 +559,7 @@ export class AgentStreamComponent implements AfterViewInit {
|
||||
onCleanup(() => clearInterval(clockInterval));
|
||||
});
|
||||
|
||||
// The retry clock only needs to tick while something on screen is timing
|
||||
// The retry clock only needs to tick while something 在屏幕上 is timing
|
||||
// (startup progress, LLM retry countdowns). An idle page stays quiet.
|
||||
this.zone.runOutsideAngular(() => {
|
||||
const retryClockInterval = setInterval(() => {
|
||||
@@ -712,7 +713,7 @@ export class AgentStreamComponent implements AfterViewInit {
|
||||
this.scheduledCollapses.clear();
|
||||
this.collapsedStreams.set(new Set<string>());
|
||||
this.isUserAtBottom = true;
|
||||
|
||||
|
||||
setTimeout(() => {
|
||||
const container = document.querySelector('.stream-logs-content');
|
||||
if (container) {
|
||||
@@ -929,12 +930,12 @@ export class AgentStreamComponent implements AfterViewInit {
|
||||
|
||||
public deleteTask(sessionId: string, event?: Event): void {
|
||||
if (event) event.stopPropagation();
|
||||
if (!confirm(`Are you sure you want to delete this task? This cannot be undone.`)) {
|
||||
if (!confirm("确定删除此任务?此操作无法撤销。")) {
|
||||
return;
|
||||
}
|
||||
this.agentService.deleteSession(sessionId).subscribe({
|
||||
error: (err) => {
|
||||
console.error(`Failed to delete session ${sessionId}:`, err);
|
||||
console.error(`删除会话失败:${sessionId}:`, err);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1516,12 +1517,12 @@ export class AgentStreamComponent implements AfterViewInit {
|
||||
}
|
||||
|
||||
public getVideoAnalysisLabel(tool: any): string {
|
||||
return getVideoAnalysisView(tool)?.title || 'Analyzing screen recording';
|
||||
return getVideoAnalysisView(tool)?.title || "正在分析屏幕录像";
|
||||
}
|
||||
|
||||
public getVideoAnalysisRangeLabel(tool: any): string {
|
||||
const range = getVideoAnalysisView(tool)?.requestedRange;
|
||||
if (!range) return 'Screen recording';
|
||||
if (!range) return "屏幕录像";
|
||||
return `${formatVideoTime(range.start)}–${formatVideoTime(range.end)}`;
|
||||
}
|
||||
|
||||
@@ -1530,11 +1531,11 @@ export class AgentStreamComponent implements AfterViewInit {
|
||||
if (!view || view.totalCount <= 1) return '';
|
||||
if (view.outcome === 'running' || view.outcome === 'recovering') {
|
||||
return view.completedCount > 0
|
||||
? `${view.completedCount}/${view.totalCount} segments saved`
|
||||
? `${view.completedCount}/${view.totalCount} 段已保存`
|
||||
: '';
|
||||
}
|
||||
if (view.outcome === 'partial') {
|
||||
return `${view.completedCount}/${view.totalCount} segments saved`;
|
||||
return `${view.completedCount}/${view.totalCount} 段已保存`;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
@@ -1554,23 +1555,23 @@ export class AgentStreamComponent implements AfterViewInit {
|
||||
|
||||
public getScreenRecordingButtonTitle(): string {
|
||||
if (this.agentService.isCurrentSessionRunning()) {
|
||||
return 'Task is currently running (Recording screen)';
|
||||
return "任务正在运行(录屏中)";
|
||||
}
|
||||
if (this.agentService.currentSessionRecordingStatus() === 'processing') {
|
||||
return 'Preparing screen recording...';
|
||||
return "正在准备屏幕录像…";
|
||||
}
|
||||
if (this.agentService.currentSessionVideoUrl()) {
|
||||
return 'Play Screen Recording Video';
|
||||
return "播放屏幕录像";
|
||||
}
|
||||
if (this.agentService.hasCurrentSessionStepFrames()) {
|
||||
return this.agentService.currentSessionRecordingStatus() === 'failed'
|
||||
? 'Video recording failed — Click to replay step-by-step screenshots'
|
||||
: 'Play step-by-step screenshots replay';
|
||||
? "录屏失败,点击逐步回放截图"
|
||||
: "逐步回放截图";
|
||||
}
|
||||
if (this.agentService.currentSessionRecordingStatus() === 'failed') {
|
||||
return 'Screen recording generation failed';
|
||||
return "屏幕录像生成失败";
|
||||
}
|
||||
return 'Screen Recording';
|
||||
return "屏幕录像";
|
||||
}
|
||||
|
||||
public isDeviceActionTool(tool: any): boolean {
|
||||
@@ -1639,11 +1640,11 @@ export class AgentStreamComponent implements AfterViewInit {
|
||||
public getCheckerPhaseLabel(block: any): string {
|
||||
switch (block?.data?.phase) {
|
||||
case 'final':
|
||||
return 'Final check';
|
||||
return "最终检查";
|
||||
case 'outcome':
|
||||
return 'Result';
|
||||
return "结果";
|
||||
default:
|
||||
return 'Check';
|
||||
return "勾选";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1660,8 +1661,8 @@ export class AgentStreamComponent implements AfterViewInit {
|
||||
|
||||
/** How the item is judged, in plain words (verify = must pass, assert = recorded test result). */
|
||||
public getCheckMethodLabel(item: any, block: any): string {
|
||||
const parts: string[] = [item?.kind === 'assert' ? 'Test assertion' : 'Must pass'];
|
||||
if (item?.when === 'at_end' && block?.data?.phase !== 'final') parts.push('at the end');
|
||||
const parts: string[] = [item?.kind === 'assert' ? "测试断言" : "必须通过"];
|
||||
if (item?.when === 'at_end' && block?.data?.phase !== 'final') parts.push("结束时");
|
||||
return parts.join(' · ');
|
||||
}
|
||||
|
||||
@@ -1680,15 +1681,15 @@ export class AgentStreamComponent implements AfterViewInit {
|
||||
public getVerdictStatusText(status: string): string {
|
||||
switch (status) {
|
||||
case 'passed':
|
||||
return 'Passed';
|
||||
return "已通过";
|
||||
case 'failed':
|
||||
return 'Failed';
|
||||
return "失败";
|
||||
case 'inconclusive':
|
||||
return 'Could not be confirmed';
|
||||
return "无法确认";
|
||||
case 'superseded':
|
||||
return 'Superseded by a newer check';
|
||||
return "已被较新的检查替代";
|
||||
case 'unchecked':
|
||||
return 'Not checked';
|
||||
return "未检查";
|
||||
default:
|
||||
return status || '';
|
||||
}
|
||||
@@ -1706,9 +1707,9 @@ export class AgentStreamComponent implements AfterViewInit {
|
||||
if (d.phase === 'outcome') return Array.isArray(d.last_findings) ? d.last_findings : [];
|
||||
const notes: string[] = [];
|
||||
if (Array.isArray(d.unmet_subgoals)) {
|
||||
for (const text of d.unmet_subgoals) notes.push(`Not finished yet: ${text}`);
|
||||
for (const text of d.unmet_subgoals) notes.push(`尚未完成:${text}`);
|
||||
}
|
||||
if (d.reverted) notes.push('The step was sent back to be redone.');
|
||||
if (d.reverted) notes.push("此步骤已退回重新执行。");
|
||||
if (d.error) notes.push(String(d.error));
|
||||
return notes;
|
||||
}
|
||||
@@ -1760,32 +1761,32 @@ export class AgentStreamComponent implements AfterViewInit {
|
||||
const d = block?.data || {};
|
||||
if (d.phase === 'outcome') {
|
||||
const t = d.tests || {};
|
||||
const label = d.task_status === 'completed' ? 'Goal completed'
|
||||
: (d.task_status === 'blocked' ? 'Blocked' : 'Partially completed');
|
||||
const label = d.task_status === 'completed' ? "目标已完成"
|
||||
: (d.task_status === 'blocked' ? "受阻" : "部分完成");
|
||||
const counts = [
|
||||
[t.passed, 'passed'],
|
||||
[t.failed, 'failed'],
|
||||
[t.inconclusive, 'inconclusive'],
|
||||
[t.unchecked, 'not checked']
|
||||
[t.unchecked, "未检查"]
|
||||
]
|
||||
.filter(([n]) => Number(n) > 0)
|
||||
.map(([n, word]) => `${n} ${word}`);
|
||||
return counts.length > 0 ? `${label} · ${counts.join(' · ')}` : label;
|
||||
}
|
||||
if (d.isCompleted === false) return 'Checking…';
|
||||
if (d.isCompleted === false) return "正在检查…";
|
||||
switch (d.status) {
|
||||
case 'superseded':
|
||||
return 'Superseded';
|
||||
return "已替代";
|
||||
case 'unchecked':
|
||||
return 'Not checked';
|
||||
return "未检查";
|
||||
case 'error':
|
||||
return 'No verdict';
|
||||
return "暂无结论";
|
||||
}
|
||||
const verdicts = this.getCheckerVerdicts(block);
|
||||
const failed = verdicts.filter((v) => v.status === 'failed').length;
|
||||
if (failed > 0) return `${failed} failed`;
|
||||
if (verdicts.some((v) => v.status === 'inconclusive')) return 'Inconclusive';
|
||||
return verdicts.length > 0 ? 'Passed' : 'Done';
|
||||
if (verdicts.some((v) => v.status === 'inconclusive')) return "无法确定";
|
||||
return verdicts.length > 0 ? "已通过" : "完成";
|
||||
}
|
||||
|
||||
public getVerdictIcon(status: string): string {
|
||||
@@ -1854,7 +1855,7 @@ export class AgentStreamComponent implements AfterViewInit {
|
||||
}
|
||||
|
||||
public getBackendSwitchMessage(tool: any): string {
|
||||
return String(tool?.payload?.message || 'UI hierarchy source changed');
|
||||
return String(tool?.payload?.message || "界面层级来源已变更");
|
||||
}
|
||||
|
||||
public resumePausedTask(event: Event): void {
|
||||
@@ -1863,12 +1864,12 @@ export class AgentStreamComponent implements AfterViewInit {
|
||||
}
|
||||
|
||||
public getLLMErrorText(tool: any): string {
|
||||
const noDetails = 'The AI provider did not return error details after the request failed.';
|
||||
const noDetails = "模型请求失败,接口未返回错误详情。";
|
||||
if (!tool) return noDetails;
|
||||
const rawError = tool.payload?.error || tool.error;
|
||||
if (!rawError) return noDetails;
|
||||
const cleaned = this.cleanErrorMessage(rawError);
|
||||
return cleaned === 'Unknown error' ? noDetails : cleaned;
|
||||
return cleaned === "未知错误" ? noDetails : cleaned;
|
||||
}
|
||||
|
||||
public isLLMRetry(tool: any): boolean {
|
||||
@@ -1945,7 +1946,7 @@ export class AgentStreamComponent implements AfterViewInit {
|
||||
|
||||
public getToolCallName(action: any): string {
|
||||
const act = this.getActionObject(action);
|
||||
if (!act) return 'Tool';
|
||||
if (!act) return "工具";
|
||||
const label = this.getToolDisplayLabel(act);
|
||||
const key = this.getToolKey(act);
|
||||
return key ? `${label}: ${key}` : label;
|
||||
@@ -2051,13 +2052,13 @@ export class AgentStreamComponent implements AfterViewInit {
|
||||
public getStatusLabel(status: string): string {
|
||||
switch (status) {
|
||||
case 'idle':
|
||||
return 'Idle';
|
||||
return "空闲";
|
||||
case 'running':
|
||||
return 'Running';
|
||||
return "执行中";
|
||||
case 'completed':
|
||||
return 'Completed';
|
||||
return "已完成";
|
||||
case 'offline':
|
||||
return 'Offline';
|
||||
return "离线";
|
||||
default:
|
||||
return status.toUpperCase();
|
||||
}
|
||||
@@ -2085,7 +2086,7 @@ export class AgentStreamComponent implements AfterViewInit {
|
||||
public onToolKeyClick(key: string): void {
|
||||
this.agentService.selectedNoteKey.set(key);
|
||||
this.agentService.activeTab.set('notes');
|
||||
|
||||
|
||||
const sessionId = this.agentService.currentSessionId();
|
||||
if (sessionId) {
|
||||
this.agentService.fetchNotes(sessionId);
|
||||
@@ -2162,16 +2163,16 @@ export class AgentStreamComponent implements AfterViewInit {
|
||||
}
|
||||
|
||||
public getArchitectureTooltip(model?: ModelInfo | null): string {
|
||||
if (!model) return 'Agent Architecture: ARTEMIS Flash (Reactive Fast Loop)';
|
||||
if (!model) return "执行模式:ARTEMIS Flash(快速响应)";
|
||||
const name = this.getModelDisplayName(model.name);
|
||||
const isPro = name.toLowerCase().includes('pro');
|
||||
const archDesc = isPro
|
||||
? 'ARTEMIS Pro (Multi-Agent Cognitive State Graph)'
|
||||
: 'ARTEMIS Flash (Reactive Fast Loop)';
|
||||
? "ARTEMIS Pro(多智能体规划)"
|
||||
: "ARTEMIS Flash(快速响应)";
|
||||
if (model.id) {
|
||||
return `Agent Architecture: ${archDesc} · LLM: ${model.id} (${model.provider || 'google'})`;
|
||||
return `智能体架构:${archDesc} · LLM: ${model.id} (${model.provider || 'google'})`;
|
||||
}
|
||||
return `Agent Architecture: ${archDesc}`;
|
||||
return `智能体架构:${archDesc}`;
|
||||
}
|
||||
|
||||
public formatTokenCount(tokens?: number): string {
|
||||
@@ -2181,9 +2182,9 @@ export class AgentStreamComponent implements AfterViewInit {
|
||||
public getTokenTooltip(phase: PhaseBlock): string {
|
||||
if (!phase.tokens) return '';
|
||||
if (phase.promptTokens && phase.completionTokens) {
|
||||
return `Consumed: ${phase.tokens.toLocaleString()} tokens (${phase.promptTokens.toLocaleString()} in / ${phase.completionTokens.toLocaleString()} out)`;
|
||||
return `已使用:${phase.tokens.toLocaleString()} tokens (${phase.promptTokens.toLocaleString()} 输入 / ${phase.completionTokens.toLocaleString()} 输出)`;
|
||||
}
|
||||
return `Consumed: ${phase.tokens.toLocaleString()} tokens`;
|
||||
return `已使用:${phase.tokens.toLocaleString()} tokens`;
|
||||
}
|
||||
|
||||
public isCurrentSessionActive(): boolean {
|
||||
|
||||
Reference in new issue
Block a user