@miphamai/cli 0.47.0 → 0.48.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/agent/agent-context.ts +3 -1
- package/src/agent/sub-agent.ts +31 -28
- package/src/agent/types.ts +0 -1
- package/src/core/engine.ts +1 -1
- package/src/daemon/server.ts +21 -4
- package/src/i18n-core/locales/en-US.json +73 -0
- package/src/i18n-core/locales/zh-CN.json +73 -0
- package/src/shared/package-info.ts +1 -1
- package/src/ui/config-wizard.tsx +77 -47
- package/src/ui/error-boundary.tsx +18 -15
- package/src/ui/goal-progress.tsx +13 -7
- package/src/ui/workflow-progress.tsx +18 -9
package/package.json
CHANGED
|
@@ -102,10 +102,12 @@ export function createAgentContext(
|
|
|
102
102
|
contextWindow?: number,
|
|
103
103
|
crsiConfig?: Partial<CrsiConfig>,
|
|
104
104
|
): AgentContextResult {
|
|
105
|
-
// Create isolated context
|
|
105
|
+
// Create isolated context, sized to the resolved model's window when known.
|
|
106
|
+
// Passing contextWindow (not just maxTokens) enables adaptive thresholds.
|
|
106
107
|
const context = new ContextManager({
|
|
107
108
|
maxTokens: contextWindow || 100_000,
|
|
108
109
|
compactionThreshold: 0.85,
|
|
110
|
+
contextWindow,
|
|
109
111
|
})
|
|
110
112
|
|
|
111
113
|
// Build system prompt with optional agent memory
|
package/src/agent/sub-agent.ts
CHANGED
|
@@ -254,7 +254,35 @@ export class SubAgent {
|
|
|
254
254
|
const systemPrompt =
|
|
255
255
|
agentDef?.systemPrompt || options.systemPrompt || TYPE_SYSTEM_PROMPTS[agentType]
|
|
256
256
|
|
|
257
|
-
//
|
|
257
|
+
// Resolve the sub-agent's model first, so its context window is known
|
|
258
|
+
// before the isolated context is created (model-aware sizing).
|
|
259
|
+
const modelToUse = options.modelOverride || agentDef?.model || model
|
|
260
|
+
// 'inherit' means use parent model
|
|
261
|
+
const resolvedModel = modelToUse === 'inherit' ? model : modelToUse
|
|
262
|
+
|
|
263
|
+
// Validate resolved model exists in registry; fall back to parent model with warning
|
|
264
|
+
let finalModel = resolvedModel
|
|
265
|
+
if (resolvedModel !== model) {
|
|
266
|
+
const modelExists = this.registry.findModel(resolvedModel) !== undefined
|
|
267
|
+
if (!modelExists) {
|
|
268
|
+
const warnMsg = `Warning: model "${resolvedModel}" not found in provider registry. Falling back to "${model}".`
|
|
269
|
+
console.warn(warnMsg)
|
|
270
|
+
|
|
271
|
+
// Post warning to message bus for UI display
|
|
272
|
+
const bus = getMessageBus()
|
|
273
|
+
bus.post(
|
|
274
|
+
'system',
|
|
275
|
+
'main',
|
|
276
|
+
`Sub-agent model fallback: ${resolvedModel} → ${model}`,
|
|
277
|
+
warnMsg,
|
|
278
|
+
'warning',
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
finalModel = model
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Create isolated context with tool scoping, sized to the resolved model.
|
|
258
286
|
const resolvedDef: AgentDefinition = agentDef || {
|
|
259
287
|
name: agentType,
|
|
260
288
|
description: '',
|
|
@@ -264,10 +292,11 @@ export class SubAgent {
|
|
|
264
292
|
background: false,
|
|
265
293
|
source: 'builtin',
|
|
266
294
|
}
|
|
295
|
+
const contextWindow = this.registry.findModel(finalModel)?.contextWindow
|
|
267
296
|
const { context, allowedTools } = createAgentContext(
|
|
268
297
|
resolvedDef,
|
|
269
298
|
this.toolRegistry,
|
|
270
|
-
|
|
299
|
+
contextWindow,
|
|
271
300
|
)
|
|
272
301
|
|
|
273
302
|
context.setSystemPrompt(systemPrompt)
|
|
@@ -291,32 +320,6 @@ export class SubAgent {
|
|
|
291
320
|
}))
|
|
292
321
|
: undefined
|
|
293
322
|
|
|
294
|
-
const modelToUse = options.modelOverride || agentDef?.model || model
|
|
295
|
-
// 'inherit' means use parent model
|
|
296
|
-
const resolvedModel = modelToUse === 'inherit' ? model : modelToUse
|
|
297
|
-
|
|
298
|
-
// Validate resolved model exists in registry; fall back to parent model with warning
|
|
299
|
-
let finalModel = resolvedModel
|
|
300
|
-
if (resolvedModel !== model) {
|
|
301
|
-
const modelExists = this.registry.findModel(resolvedModel) !== undefined
|
|
302
|
-
if (!modelExists) {
|
|
303
|
-
const warnMsg = `Warning: model "${resolvedModel}" not found in provider registry. Falling back to "${model}".`
|
|
304
|
-
console.warn(warnMsg)
|
|
305
|
-
|
|
306
|
-
// Post warning to message bus for UI display
|
|
307
|
-
const bus = getMessageBus()
|
|
308
|
-
bus.post(
|
|
309
|
-
'system',
|
|
310
|
-
'main',
|
|
311
|
-
`Sub-agent model fallback: ${resolvedModel} → ${model}`,
|
|
312
|
-
warnMsg,
|
|
313
|
-
'warning',
|
|
314
|
-
)
|
|
315
|
-
|
|
316
|
-
finalModel = model
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
|
|
320
323
|
const chunks: string[] = []
|
|
321
324
|
const MAX_TOOL_TURNS = options.maxTurns || 5
|
|
322
325
|
|
package/src/agent/types.ts
CHANGED
|
@@ -39,7 +39,6 @@ export interface SubAgentOptions {
|
|
|
39
39
|
type?: SubAgentType
|
|
40
40
|
agentDef?: AgentDefinition
|
|
41
41
|
systemPrompt?: string
|
|
42
|
-
maxContextMessages?: number
|
|
43
42
|
allowedTools?: string[]
|
|
44
43
|
modelOverride?: string
|
|
45
44
|
/** Maximum tool-calling turns (default: 5) to prevent infinite loops. */
|
package/src/core/engine.ts
CHANGED
|
@@ -1431,7 +1431,7 @@ export class QueryEngine {
|
|
|
1431
1431
|
const DISABLE_1M = process.env.MIPHAM_DISABLE_1M_CONTEXT === '1'
|
|
1432
1432
|
const maxTokens =
|
|
1433
1433
|
DISABLE_1M && model.contextWindow > 200_000 ? 200_000 : model.contextWindow
|
|
1434
|
-
this.context.updateMaxTokens(maxTokens)
|
|
1434
|
+
this.context.updateMaxTokens(maxTokens, model.contextWindow)
|
|
1435
1435
|
}
|
|
1436
1436
|
}
|
|
1437
1437
|
}
|
package/src/daemon/server.ts
CHANGED
|
@@ -67,6 +67,22 @@ function resolveDaemonPermission(): PermissionMode {
|
|
|
67
67
|
: 'default'
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
const DAEMON_DEFAULT_CONTEXT_WINDOW = 200_000
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Resolve the effective context window (in tokens) for the daemon session's
|
|
74
|
+
* model. Mirrors the CLI's model-aware context sizing so a 1M-context model
|
|
75
|
+
* isn't silently hard-capped at 200K; falls back when the model isn't in the
|
|
76
|
+
* registry.
|
|
77
|
+
*/
|
|
78
|
+
export function resolveContextWindow(
|
|
79
|
+
registry: ProviderRegistry,
|
|
80
|
+
modelId: string,
|
|
81
|
+
fallback = DAEMON_DEFAULT_CONTEXT_WINDOW,
|
|
82
|
+
): number {
|
|
83
|
+
return registry.findModel(modelId)?.contextWindow ?? fallback
|
|
84
|
+
}
|
|
85
|
+
|
|
70
86
|
export function createServer(config: ServerConfig): Server<WsData> {
|
|
71
87
|
const {
|
|
72
88
|
db,
|
|
@@ -120,7 +136,6 @@ export function createServer(config: ServerConfig): Server<WsData> {
|
|
|
120
136
|
const engineCache = new Map<string, QueryEngine>()
|
|
121
137
|
let sharedRegistry: ProviderRegistry | null = null
|
|
122
138
|
let sharedTools: Map<string, ToolDefinition> | null = null
|
|
123
|
-
const DEFAULT_MAX_TOKENS = 200_000
|
|
124
139
|
|
|
125
140
|
function getOrCreateEngine(
|
|
126
141
|
sessionId: string,
|
|
@@ -148,11 +163,13 @@ export function createServer(config: ServerConfig): Server<WsData> {
|
|
|
148
163
|
sharedTools = createToolRegistry()
|
|
149
164
|
}
|
|
150
165
|
|
|
151
|
-
// Create per-session context, restoring previous messages from DB
|
|
166
|
+
// Create per-session context, restoring previous messages from DB.
|
|
167
|
+
// Size it to the active model's context window (1M-capable models get 1M).
|
|
168
|
+
const contextWindow = resolveContextWindow(sharedRegistry!, model)
|
|
152
169
|
const context = new ContextManager({
|
|
153
|
-
maxTokens:
|
|
170
|
+
maxTokens: contextWindow,
|
|
154
171
|
compactionThreshold: 0.9,
|
|
155
|
-
contextWindow
|
|
172
|
+
contextWindow,
|
|
156
173
|
})
|
|
157
174
|
|
|
158
175
|
const dbMessages = db.getMessages(sessionId, 10000)
|
|
@@ -718,6 +718,79 @@
|
|
|
718
718
|
"shift_tab_cycle": "shift+tab",
|
|
719
719
|
"left_for_agents": "ctrl+g agents to manage",
|
|
720
720
|
"mipham_modes": "Mipham"
|
|
721
|
+
},
|
|
722
|
+
"wizard": {
|
|
723
|
+
"header_subtitle": " — Configuration Wizard",
|
|
724
|
+
"welcome_title": "Welcome to Mipham Code!",
|
|
725
|
+
"welcome_intro": "Before you start, you need to configure your AI model connection.",
|
|
726
|
+
"welcome_time": "This wizard takes about 1 minute — please have your API key ready.",
|
|
727
|
+
"welcome_start": "Press Enter to start →",
|
|
728
|
+
"welcome_skip": "Esc to skip",
|
|
729
|
+
"mode_title": "Choose connection method:",
|
|
730
|
+
"mode_cloud": "🌐 Cloud AI models (recommended)",
|
|
731
|
+
"mode_local": "🖥️ Local models (Ollama)",
|
|
732
|
+
"mode_local_hint": " Requires Ollama installed with models downloaded",
|
|
733
|
+
"nav_hint": "↑↓ select · Enter confirm · Esc back",
|
|
734
|
+
"provider_title": "Choose AI model provider:",
|
|
735
|
+
"provider_count": " ({count} models)",
|
|
736
|
+
"model_title": "Choose model ({provider}):",
|
|
737
|
+
"apikey_title": "Paste your API key:",
|
|
738
|
+
"apikey_provider": "Provider: {provider}",
|
|
739
|
+
"apikey_model": "Model: {model}",
|
|
740
|
+
"apikey_placeholder": "Paste API key then press Enter...",
|
|
741
|
+
"apikey_continue": "Enter to continue · Esc to go back to model",
|
|
742
|
+
"ollama_title": "Ollama local model configuration:",
|
|
743
|
+
"ollama_status": "Status: ",
|
|
744
|
+
"ollama_installed": "✅ installed",
|
|
745
|
+
"ollama_not_installed": "❌ not installed",
|
|
746
|
+
"ollama_running": " · running",
|
|
747
|
+
"ollama_not_running": " · not running (run ollama serve)",
|
|
748
|
+
"ollama_detecting": "Detecting Ollama...",
|
|
749
|
+
"ollama_downloaded": "Downloaded models (↑↓ select · Enter confirm):",
|
|
750
|
+
"ollama_source_hot": "Popular",
|
|
751
|
+
"ollama_no_models": "No downloaded models found. Run ollama pull <model> to download one.",
|
|
752
|
+
"ollama_preset_hint": "Below are preset models — switch anytime via /models.",
|
|
753
|
+
"confirm_title": "Confirm configuration:",
|
|
754
|
+
"confirm_connection": "Connection: ",
|
|
755
|
+
"confirm_cloud": "🌐 Cloud",
|
|
756
|
+
"confirm_local": "🖥️ Local",
|
|
757
|
+
"confirm_provider": "Provider: {provider}",
|
|
758
|
+
"confirm_model": "Model: {model}",
|
|
759
|
+
"confirm_apikey": "API key: {masked}",
|
|
760
|
+
"confirm_ollama_model": "Ollama model: {model}",
|
|
761
|
+
"confirm_save_path": "Config will be saved to ~/.mipham/config.yml",
|
|
762
|
+
"confirm_save": "Press Enter to save and launch →",
|
|
763
|
+
"confirm_back": "Esc to go back",
|
|
764
|
+
"done_title": "✅ Configuration complete!",
|
|
765
|
+
"done_launching": "Mipham Code is starting...",
|
|
766
|
+
"error_apikey_required": "Please enter your API key",
|
|
767
|
+
"error_write_failed": "Failed to write config: {err}"
|
|
768
|
+
},
|
|
769
|
+
"goal_progress": {
|
|
770
|
+
"title": "🎯 Goal: {goal}",
|
|
771
|
+
"no_subtasks": " (no subtasks yet)",
|
|
772
|
+
"summary": "═══ {done}/{total} done",
|
|
773
|
+
"in_progress": " · {n} in progress",
|
|
774
|
+
"failed": " · {n} failed",
|
|
775
|
+
"tokens": " · {n} tokens",
|
|
776
|
+
"border_end": " ═══"
|
|
777
|
+
},
|
|
778
|
+
"workflow": {
|
|
779
|
+
"title": "═══ Workflow: {runId} ═══",
|
|
780
|
+
"phase": "Phase: {name}",
|
|
781
|
+
"done_count": " [{done}/{total} done]",
|
|
782
|
+
"footer": "═══ Elapsed: {time} | Agents: {done}/{total}",
|
|
783
|
+
"running": " ({n} running)",
|
|
784
|
+
"failed": " ({n} failed)",
|
|
785
|
+
"cache_hits": " | Cache hits: {n}",
|
|
786
|
+
"done_marker": " | DONE ═══",
|
|
787
|
+
"running_marker": " | Status: running ═══"
|
|
788
|
+
},
|
|
789
|
+
"error_boundary": {
|
|
790
|
+
"title": "⚠️ UI Render Error",
|
|
791
|
+
"unknown": "Unknown rendering error",
|
|
792
|
+
"recovery": "The terminal interface encountered a rendering issue. Auto-recovery in 5 seconds... Press Enter to attempt immediate recovery.",
|
|
793
|
+
"preserved": "Your session data and conversation history are preserved."
|
|
721
794
|
}
|
|
722
795
|
},
|
|
723
796
|
"tools": {
|
|
@@ -718,6 +718,79 @@
|
|
|
718
718
|
"shift_tab_cycle": "Shift+Tab",
|
|
719
719
|
"left_for_agents": "ctrl+g 管理代理",
|
|
720
720
|
"mipham_modes": "Mipham独有"
|
|
721
|
+
},
|
|
722
|
+
"wizard": {
|
|
723
|
+
"header_subtitle": " — 配置向导",
|
|
724
|
+
"welcome_title": "欢迎使用 Mipham Code!",
|
|
725
|
+
"welcome_intro": "在开始之前,需要先配置 AI 模型连接。",
|
|
726
|
+
"welcome_time": "此向导只需 1 分钟,需要您准备好 API Key。",
|
|
727
|
+
"welcome_start": "按 Enter 开始 →",
|
|
728
|
+
"welcome_skip": "Esc 跳过配置",
|
|
729
|
+
"mode_title": "选择模型连接方式:",
|
|
730
|
+
"mode_cloud": "🌐 云端 AI 模型(推荐)",
|
|
731
|
+
"mode_local": "🖥️ 本地模型(Ollama)",
|
|
732
|
+
"mode_local_hint": " 需要提前安装 Ollama 并下载模型",
|
|
733
|
+
"nav_hint": "↑↓ 选择 · Enter 确认 · Esc 返回",
|
|
734
|
+
"provider_title": "选择 AI 模型提供商:",
|
|
735
|
+
"provider_count": " ({count} 个模型)",
|
|
736
|
+
"model_title": "选择模型({provider}):",
|
|
737
|
+
"apikey_title": "粘贴 API Key:",
|
|
738
|
+
"apikey_provider": "提供商:{provider}",
|
|
739
|
+
"apikey_model": "模型:{model}",
|
|
740
|
+
"apikey_placeholder": "粘贴 API Key 后按 Enter...",
|
|
741
|
+
"apikey_continue": "Enter 继续 · Esc 返回选模型",
|
|
742
|
+
"ollama_title": "Ollama 本地模型配置:",
|
|
743
|
+
"ollama_status": "状态:",
|
|
744
|
+
"ollama_installed": "✅ 已安装",
|
|
745
|
+
"ollama_not_installed": "❌ 未安装",
|
|
746
|
+
"ollama_running": " · 运行中",
|
|
747
|
+
"ollama_not_running": " · 未运行(请执行 ollama serve)",
|
|
748
|
+
"ollama_detecting": "正在检测 Ollama...",
|
|
749
|
+
"ollama_downloaded": "已下载模型(↑↓ 选择 · Enter 确认):",
|
|
750
|
+
"ollama_source_hot": "热门",
|
|
751
|
+
"ollama_no_models": "未检测到已下载模型。请先运行 ollama pull <model> 下载模型。",
|
|
752
|
+
"ollama_preset_hint": "以下为预置模型列表,选择后可在对话中通过 /models 切换。",
|
|
753
|
+
"confirm_title": "配置确认:",
|
|
754
|
+
"confirm_connection": "连接方式:",
|
|
755
|
+
"confirm_cloud": "🌐 云端",
|
|
756
|
+
"confirm_local": "🖥️ 本地",
|
|
757
|
+
"confirm_provider": "提供商:{provider}",
|
|
758
|
+
"confirm_model": "模型:{model}",
|
|
759
|
+
"confirm_apikey": "API Key:{masked}",
|
|
760
|
+
"confirm_ollama_model": "Ollama 模型:{model}",
|
|
761
|
+
"confirm_save_path": "配置文件将保存到 ~/.mipham/config.yml",
|
|
762
|
+
"confirm_save": "按 Enter 保存并启动 →",
|
|
763
|
+
"confirm_back": "Esc 返回修改",
|
|
764
|
+
"done_title": "✅ 配置完成!",
|
|
765
|
+
"done_launching": "Mipham Code 正在启动...",
|
|
766
|
+
"error_apikey_required": "请填写 API Key",
|
|
767
|
+
"error_write_failed": "写入配置失败: {err}"
|
|
768
|
+
},
|
|
769
|
+
"goal_progress": {
|
|
770
|
+
"title": "🎯 目标:{goal}",
|
|
771
|
+
"no_subtasks": " (暂无子任务)",
|
|
772
|
+
"summary": "═══ {done}/{total} 完成",
|
|
773
|
+
"in_progress": " · {n} 进行中",
|
|
774
|
+
"failed": " · {n} 失败",
|
|
775
|
+
"tokens": " · {n} tokens",
|
|
776
|
+
"border_end": " ═══"
|
|
777
|
+
},
|
|
778
|
+
"workflow": {
|
|
779
|
+
"title": "═══ 工作流:{runId} ═══",
|
|
780
|
+
"phase": "阶段:{name}",
|
|
781
|
+
"done_count": " [{done}/{total} 完成]",
|
|
782
|
+
"footer": "═══ 耗时:{time} | Agent:{done}/{total}",
|
|
783
|
+
"running": " ({n} 运行中)",
|
|
784
|
+
"failed": " ({n} 失败)",
|
|
785
|
+
"cache_hits": " | 缓存命中:{n}",
|
|
786
|
+
"done_marker": " | 完成 ═══",
|
|
787
|
+
"running_marker": " | 状态:运行中 ═══"
|
|
788
|
+
},
|
|
789
|
+
"error_boundary": {
|
|
790
|
+
"title": "⚠️ UI 渲染错误",
|
|
791
|
+
"unknown": "未知渲染错误",
|
|
792
|
+
"recovery": "终端界面遇到渲染问题,5 秒后自动恢复... 按 Enter 尝试立即恢复。",
|
|
793
|
+
"preserved": "你的会话数据和对话历史已保留。"
|
|
721
794
|
}
|
|
722
795
|
},
|
|
723
796
|
"tools": {
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
export const PACKAGE_NAME = '@miphamai/cli' as const
|
|
10
10
|
|
|
11
11
|
/** 当前发布版本 */
|
|
12
|
-
export const PACKAGE_VERSION = '0.
|
|
12
|
+
export const PACKAGE_VERSION = '0.48.0' as const
|
|
13
13
|
|
|
14
14
|
/** npm install 全局安装命令 */
|
|
15
15
|
export const NPM_INSTALL_COMMAND = `npm install -g ${PACKAGE_NAME}` as const
|
package/src/ui/config-wizard.tsx
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import React, { useState, useEffect } from 'react'
|
|
12
12
|
import { Box, Text, useInput } from 'ink'
|
|
13
|
+
import { useI18n } from '../i18n-context'
|
|
13
14
|
import TextInput from 'ink-text-input'
|
|
14
15
|
import { DEFAULT_PROVIDERS, OLLAMA_PRESET_MODELS } from '../shared/constants'
|
|
15
16
|
import type { ProviderConfig, ModelInfo } from '../shared/types'
|
|
@@ -167,6 +168,7 @@ function getOllamaModelList(installedModels: string[]): OllamaModelItem[] {
|
|
|
167
168
|
// ── Component ──
|
|
168
169
|
|
|
169
170
|
export function ConfigWizard({ onComplete, onSkip }: Props) {
|
|
171
|
+
const { t } = useI18n()
|
|
170
172
|
const [step, setStep] = useState<Step>('welcome')
|
|
171
173
|
|
|
172
174
|
// Selections
|
|
@@ -202,14 +204,14 @@ export function ConfigWizard({ onComplete, onSkip }: Props) {
|
|
|
202
204
|
|
|
203
205
|
const finishCloud = () => {
|
|
204
206
|
if (!providerId || !modelId || !apiKey.trim()) {
|
|
205
|
-
setError('
|
|
207
|
+
setError(t('ui.wizard.error_apikey_required'))
|
|
206
208
|
return
|
|
207
209
|
}
|
|
208
210
|
try {
|
|
209
211
|
writeConfigFile(providerId, modelId, apiKey.trim())
|
|
210
212
|
onComplete({ providerId, modelId, apiKey: apiKey.trim() })
|
|
211
213
|
} catch (err) {
|
|
212
|
-
setError(
|
|
214
|
+
setError(t('ui.wizard.error_write_failed', { err: String(err) }))
|
|
213
215
|
}
|
|
214
216
|
}
|
|
215
217
|
|
|
@@ -220,7 +222,7 @@ export function ConfigWizard({ onComplete, onSkip }: Props) {
|
|
|
220
222
|
writeConfigFile('ollama', model, 'ollama-local')
|
|
221
223
|
onComplete({ providerId: 'ollama', modelId: model, apiKey: 'ollama-local' })
|
|
222
224
|
} catch (err) {
|
|
223
|
-
setError(
|
|
225
|
+
setError(t('ui.wizard.error_write_failed', { err: String(err) }))
|
|
224
226
|
}
|
|
225
227
|
}
|
|
226
228
|
|
|
@@ -366,7 +368,7 @@ export function ConfigWizard({ onComplete, onSkip }: Props) {
|
|
|
366
368
|
<Text color="#FFD700" bold>
|
|
367
369
|
Mipham Code
|
|
368
370
|
</Text>
|
|
369
|
-
<Text dimColor>
|
|
371
|
+
<Text dimColor>{t('ui.wizard.header_subtitle')}</Text>
|
|
370
372
|
</Box>
|
|
371
373
|
<Box marginBottom={1}>
|
|
372
374
|
<Text dimColor>━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━</Text>
|
|
@@ -377,16 +379,16 @@ export function ConfigWizard({ onComplete, onSkip }: Props) {
|
|
|
377
379
|
<Box flexDirection="column">
|
|
378
380
|
<Box marginBottom={1}>
|
|
379
381
|
<Text bold color="cyan">
|
|
380
|
-
|
|
382
|
+
{t('ui.wizard.welcome_title')}
|
|
381
383
|
</Text>
|
|
382
384
|
</Box>
|
|
383
|
-
<Text
|
|
384
|
-
<Text
|
|
385
|
+
<Text>{t('ui.wizard.welcome_intro')}</Text>
|
|
386
|
+
<Text>{t('ui.wizard.welcome_time')}</Text>
|
|
385
387
|
<Box marginTop={1}>
|
|
386
|
-
<Text color="yellow"
|
|
388
|
+
<Text color="yellow">{t('ui.wizard.welcome_start')}</Text>
|
|
387
389
|
</Box>
|
|
388
390
|
<Box marginTop={1}>
|
|
389
|
-
<Text dimColor>
|
|
391
|
+
<Text dimColor>{t('ui.wizard.welcome_skip')}</Text>
|
|
390
392
|
</Box>
|
|
391
393
|
</Box>
|
|
392
394
|
)}
|
|
@@ -395,25 +397,25 @@ export function ConfigWizard({ onComplete, onSkip }: Props) {
|
|
|
395
397
|
{step === 'mode' && (
|
|
396
398
|
<Box flexDirection="column">
|
|
397
399
|
<Box marginBottom={1}>
|
|
398
|
-
<Text bold
|
|
400
|
+
<Text bold>{t('ui.wizard.mode_title')}</Text>
|
|
399
401
|
</Box>
|
|
400
402
|
|
|
401
403
|
<Box marginBottom={1}>
|
|
402
404
|
<Text color={cursor === 0 ? SELECTED_COLOR : undefined}>
|
|
403
|
-
{cursor === 0 ? '▶' : ' '}
|
|
405
|
+
{cursor === 0 ? '▶' : ' '} {t('ui.wizard.mode_cloud')}
|
|
404
406
|
</Text>
|
|
405
407
|
</Box>
|
|
406
408
|
<Text dimColor> Claude · DeepSeek · Gemini · 豆包 · Qwen · 混元 · OpenAI</Text>
|
|
407
409
|
|
|
408
410
|
<Box marginTop={2} marginBottom={1}>
|
|
409
411
|
<Text color={cursor === 1 ? SELECTED_COLOR : undefined}>
|
|
410
|
-
{cursor === 1 ? '▶' : ' '}
|
|
412
|
+
{cursor === 1 ? '▶' : ' '} {t('ui.wizard.mode_local')}
|
|
411
413
|
</Text>
|
|
412
414
|
</Box>
|
|
413
|
-
<Text dimColor>
|
|
415
|
+
<Text dimColor>{t('ui.wizard.mode_local_hint')}</Text>
|
|
414
416
|
|
|
415
417
|
<Box marginTop={2}>
|
|
416
|
-
<Text dimColor
|
|
418
|
+
<Text dimColor>{t('ui.wizard.nav_hint')}</Text>
|
|
417
419
|
</Box>
|
|
418
420
|
</Box>
|
|
419
421
|
)}
|
|
@@ -422,18 +424,22 @@ export function ConfigWizard({ onComplete, onSkip }: Props) {
|
|
|
422
424
|
{step === 'provider' && (
|
|
423
425
|
<Box flexDirection="column">
|
|
424
426
|
<Box marginBottom={1}>
|
|
425
|
-
<Text bold
|
|
427
|
+
<Text bold>{t('ui.wizard.provider_title')}</Text>
|
|
426
428
|
</Box>
|
|
427
429
|
{providerList.map((p, i) => (
|
|
428
430
|
<Box key={p.id} marginBottom={1}>
|
|
429
431
|
<Text color={i === cursor ? SELECTED_COLOR : undefined}>
|
|
430
432
|
{i === cursor ? '▶' : ' '} {p.name}
|
|
431
433
|
</Text>
|
|
432
|
-
<Text dimColor>
|
|
434
|
+
<Text dimColor>
|
|
435
|
+
{t('ui.wizard.provider_count', {
|
|
436
|
+
count: String(p.models.filter((m) => m.status === 'active').length),
|
|
437
|
+
})}
|
|
438
|
+
</Text>
|
|
433
439
|
</Box>
|
|
434
440
|
))}
|
|
435
441
|
<Box marginTop={1}>
|
|
436
|
-
<Text dimColor
|
|
442
|
+
<Text dimColor>{t('ui.wizard.nav_hint')}</Text>
|
|
437
443
|
</Box>
|
|
438
444
|
</Box>
|
|
439
445
|
)}
|
|
@@ -443,7 +449,10 @@ export function ConfigWizard({ onComplete, onSkip }: Props) {
|
|
|
443
449
|
<Box flexDirection="column">
|
|
444
450
|
<Box marginBottom={1}>
|
|
445
451
|
<Text bold>
|
|
446
|
-
|
|
452
|
+
{t('ui.wizard.model_title', {
|
|
453
|
+
provider:
|
|
454
|
+
DEFAULT_PROVIDERS.find((p) => p.id === providerId)?.name || providerId || '',
|
|
455
|
+
})}
|
|
447
456
|
</Text>
|
|
448
457
|
</Box>
|
|
449
458
|
{modelList.map((m, i) => (
|
|
@@ -455,7 +464,7 @@ export function ConfigWizard({ onComplete, onSkip }: Props) {
|
|
|
455
464
|
</Box>
|
|
456
465
|
))}
|
|
457
466
|
<Box marginTop={1}>
|
|
458
|
-
<Text dimColor
|
|
467
|
+
<Text dimColor>{t('ui.wizard.nav_hint')}</Text>
|
|
459
468
|
</Box>
|
|
460
469
|
</Box>
|
|
461
470
|
)}
|
|
@@ -464,11 +473,17 @@ export function ConfigWizard({ onComplete, onSkip }: Props) {
|
|
|
464
473
|
{step === 'apikey' && (
|
|
465
474
|
<Box flexDirection="column">
|
|
466
475
|
<Box marginBottom={1}>
|
|
467
|
-
<Text bold
|
|
476
|
+
<Text bold>{t('ui.wizard.apikey_title')}</Text>
|
|
468
477
|
</Box>
|
|
469
|
-
<Text dimColor>提供商:{DEFAULT_PROVIDERS.find((p) => p.id === providerId)?.name}</Text>
|
|
470
478
|
<Text dimColor>
|
|
471
|
-
|
|
479
|
+
{t('ui.wizard.apikey_provider', {
|
|
480
|
+
provider: DEFAULT_PROVIDERS.find((p) => p.id === providerId)?.name || '',
|
|
481
|
+
})}
|
|
482
|
+
</Text>
|
|
483
|
+
<Text dimColor>
|
|
484
|
+
{t('ui.wizard.apikey_model', {
|
|
485
|
+
model: getActiveModels(providerId!).find((m) => m.id === modelId)?.name || '',
|
|
486
|
+
})}
|
|
472
487
|
</Text>
|
|
473
488
|
{error && (
|
|
474
489
|
<Box marginBottom={1}>
|
|
@@ -481,11 +496,11 @@ export function ConfigWizard({ onComplete, onSkip }: Props) {
|
|
|
481
496
|
value={apiKey}
|
|
482
497
|
onChange={setApiKey}
|
|
483
498
|
onSubmit={() => goStep('confirm')}
|
|
484
|
-
placeholder=
|
|
499
|
+
placeholder={t('ui.wizard.apikey_placeholder')}
|
|
485
500
|
/>
|
|
486
501
|
</Box>
|
|
487
502
|
<Box marginTop={1}>
|
|
488
|
-
<Text dimColor>
|
|
503
|
+
<Text dimColor>{t('ui.wizard.apikey_continue')}</Text>
|
|
489
504
|
</Box>
|
|
490
505
|
</Box>
|
|
491
506
|
)}
|
|
@@ -494,26 +509,29 @@ export function ConfigWizard({ onComplete, onSkip }: Props) {
|
|
|
494
509
|
{step === 'ollama' && (
|
|
495
510
|
<Box flexDirection="column">
|
|
496
511
|
<Box marginBottom={1}>
|
|
497
|
-
<Text bold>
|
|
512
|
+
<Text bold>{t('ui.wizard.ollama_title')}</Text>
|
|
498
513
|
</Box>
|
|
499
514
|
{ollamaStatus ? (
|
|
500
515
|
<Box flexDirection="column" marginBottom={1}>
|
|
501
516
|
<Text>
|
|
502
|
-
|
|
517
|
+
{t('ui.wizard.ollama_status')}
|
|
518
|
+
{ollamaStatus.installed
|
|
519
|
+
? t('ui.wizard.ollama_installed')
|
|
520
|
+
: t('ui.wizard.ollama_not_installed')}
|
|
503
521
|
{ollamaStatus.running
|
|
504
|
-
? '
|
|
522
|
+
? t('ui.wizard.ollama_running')
|
|
505
523
|
: ollamaStatus.installed
|
|
506
|
-
? '
|
|
524
|
+
? t('ui.wizard.ollama_not_running')
|
|
507
525
|
: ''}
|
|
508
526
|
</Text>
|
|
509
527
|
</Box>
|
|
510
528
|
) : (
|
|
511
|
-
<Text dimColor
|
|
529
|
+
<Text dimColor>{t('ui.wizard.ollama_detecting')}</Text>
|
|
512
530
|
)}
|
|
513
531
|
{ollamaModelList.length > 0 && (
|
|
514
532
|
<Box flexDirection="column" marginBottom={1}>
|
|
515
533
|
<Box marginBottom={1}>
|
|
516
|
-
<Text dimColor
|
|
534
|
+
<Text dimColor>{t('ui.wizard.ollama_downloaded')}</Text>
|
|
517
535
|
</Box>
|
|
518
536
|
{ollamaModelList.map((m, i) => (
|
|
519
537
|
<Box key={m.id}>
|
|
@@ -521,7 +539,10 @@ export function ConfigWizard({ onComplete, onSkip }: Props) {
|
|
|
521
539
|
{i === ollamaCursor ? '▶' : ' '} {m.id}
|
|
522
540
|
</Text>
|
|
523
541
|
{m.source !== 'local' && (
|
|
524
|
-
<Text dimColor>
|
|
542
|
+
<Text dimColor>
|
|
543
|
+
{' '}
|
|
544
|
+
[{m.source === 'MiphamAI' ? 'MiphamAI' : t('ui.wizard.ollama_source_hot')}]
|
|
545
|
+
</Text>
|
|
525
546
|
)}
|
|
526
547
|
</Box>
|
|
527
548
|
))}
|
|
@@ -529,14 +550,12 @@ export function ConfigWizard({ onComplete, onSkip }: Props) {
|
|
|
529
550
|
)}
|
|
530
551
|
{ollamaStatus && ollamaModelList.length === 0 && (
|
|
531
552
|
<Box marginBottom={1}>
|
|
532
|
-
<Text dimColor>
|
|
533
|
-
|
|
534
|
-
</Text>
|
|
535
|
-
<Text dimColor>以下为预置模型列表,选择后可在对话中通过 /models 切换。</Text>
|
|
553
|
+
<Text dimColor>{t('ui.wizard.ollama_no_models')}</Text>
|
|
554
|
+
<Text dimColor>{t('ui.wizard.ollama_preset_hint')}</Text>
|
|
536
555
|
</Box>
|
|
537
556
|
)}
|
|
538
557
|
<Box marginTop={1}>
|
|
539
|
-
<Text dimColor
|
|
558
|
+
<Text dimColor>{t('ui.wizard.nav_hint')}</Text>
|
|
540
559
|
</Box>
|
|
541
560
|
</Box>
|
|
542
561
|
)}
|
|
@@ -546,26 +565,37 @@ export function ConfigWizard({ onComplete, onSkip }: Props) {
|
|
|
546
565
|
<Box flexDirection="column">
|
|
547
566
|
<Box marginBottom={1}>
|
|
548
567
|
<Text bold color="cyan">
|
|
549
|
-
|
|
568
|
+
{t('ui.wizard.confirm_title')}
|
|
550
569
|
</Text>
|
|
551
570
|
</Box>
|
|
552
|
-
<Text
|
|
571
|
+
<Text>
|
|
572
|
+
{t('ui.wizard.confirm_connection')}
|
|
573
|
+
{mode === 'cloud' ? t('ui.wizard.confirm_cloud') : t('ui.wizard.confirm_local')}
|
|
574
|
+
</Text>
|
|
553
575
|
{mode === 'cloud' && (
|
|
554
576
|
<>
|
|
555
|
-
<Text>提供商:{DEFAULT_PROVIDERS.find((p) => p.id === providerId)?.name}</Text>
|
|
556
|
-
<Text>模型:{modelId}</Text>
|
|
557
577
|
<Text>
|
|
558
|
-
|
|
578
|
+
{t('ui.wizard.confirm_provider', {
|
|
579
|
+
provider: DEFAULT_PROVIDERS.find((p) => p.id === providerId)?.name || '',
|
|
580
|
+
})}
|
|
581
|
+
</Text>
|
|
582
|
+
<Text>{t('ui.wizard.confirm_model', { model: modelId || '' })}</Text>
|
|
583
|
+
<Text>
|
|
584
|
+
{t('ui.wizard.confirm_apikey', {
|
|
585
|
+
masked: `${apiKey.slice(0, 8)}...${apiKey.slice(-4)}`,
|
|
586
|
+
})}
|
|
559
587
|
</Text>
|
|
560
588
|
</>
|
|
561
589
|
)}
|
|
562
590
|
{mode === 'local' && (
|
|
563
591
|
<Text>
|
|
564
|
-
|
|
592
|
+
{t('ui.wizard.confirm_ollama_model', {
|
|
593
|
+
model: ollamaModelList[ollamaCursor]?.id || ollamaModel.trim() || 'llama3.2',
|
|
594
|
+
})}
|
|
565
595
|
</Text>
|
|
566
596
|
)}
|
|
567
597
|
<Box marginTop={1}>
|
|
568
|
-
<Text dimColor
|
|
598
|
+
<Text dimColor>{t('ui.wizard.confirm_save_path')}</Text>
|
|
569
599
|
</Box>
|
|
570
600
|
{error && (
|
|
571
601
|
<Box marginBottom={1}>
|
|
@@ -573,10 +603,10 @@ export function ConfigWizard({ onComplete, onSkip }: Props) {
|
|
|
573
603
|
</Box>
|
|
574
604
|
)}
|
|
575
605
|
<Box marginTop={1}>
|
|
576
|
-
<Text color="yellow"
|
|
606
|
+
<Text color="yellow">{t('ui.wizard.confirm_save')}</Text>
|
|
577
607
|
</Box>
|
|
578
608
|
<Box marginTop={1}>
|
|
579
|
-
<Text dimColor>
|
|
609
|
+
<Text dimColor>{t('ui.wizard.confirm_back')}</Text>
|
|
580
610
|
</Box>
|
|
581
611
|
</Box>
|
|
582
612
|
)}
|
|
@@ -586,10 +616,10 @@ export function ConfigWizard({ onComplete, onSkip }: Props) {
|
|
|
586
616
|
<Box flexDirection="column">
|
|
587
617
|
<Box marginBottom={1}>
|
|
588
618
|
<Text color="green" bold>
|
|
589
|
-
|
|
619
|
+
{t('ui.wizard.done_title')}
|
|
590
620
|
</Text>
|
|
591
621
|
</Box>
|
|
592
|
-
<Text>
|
|
622
|
+
<Text>{t('ui.wizard.done_launching')}</Text>
|
|
593
623
|
</Box>
|
|
594
624
|
)}
|
|
595
625
|
</Box>
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
import React, { Component } from 'react'
|
|
13
13
|
import { Text, Box } from 'ink'
|
|
14
|
+
import { useI18n } from '../i18n-context'
|
|
14
15
|
|
|
15
16
|
interface Props {
|
|
16
17
|
children: React.ReactNode
|
|
@@ -63,23 +64,25 @@ export class ErrorBoundary extends Component<Props, State> {
|
|
|
63
64
|
|
|
64
65
|
override render(): React.ReactNode {
|
|
65
66
|
if (this.state.hasError) {
|
|
66
|
-
return
|
|
67
|
-
<Box flexDirection="column" padding={1}>
|
|
68
|
-
<Text color="red" bold>
|
|
69
|
-
⚠️ UI Render Error
|
|
70
|
-
</Text>
|
|
71
|
-
<Text dimColor>{this.state.error?.message || 'Unknown rendering error'}</Text>
|
|
72
|
-
<Text> </Text>
|
|
73
|
-
<Text dimColor>
|
|
74
|
-
The terminal interface encountered a rendering issue. Auto-recovery in 5 seconds...
|
|
75
|
-
Press Enter to attempt immediate recovery.
|
|
76
|
-
</Text>
|
|
77
|
-
<Text> </Text>
|
|
78
|
-
<Text color="yellow">Your session data and conversation history are preserved.</Text>
|
|
79
|
-
</Box>
|
|
80
|
-
)
|
|
67
|
+
return <ErrorFallback error={this.state.error} />
|
|
81
68
|
}
|
|
82
69
|
|
|
83
70
|
return this.props.children
|
|
84
71
|
}
|
|
85
72
|
}
|
|
73
|
+
|
|
74
|
+
function ErrorFallback({ error }: { error: Error | null }): React.ReactNode {
|
|
75
|
+
const { t } = useI18n()
|
|
76
|
+
return (
|
|
77
|
+
<Box flexDirection="column" padding={1}>
|
|
78
|
+
<Text color="red" bold>
|
|
79
|
+
{t('ui.error_boundary.title')}
|
|
80
|
+
</Text>
|
|
81
|
+
<Text dimColor>{error?.message || t('ui.error_boundary.unknown')}</Text>
|
|
82
|
+
<Text> </Text>
|
|
83
|
+
<Text dimColor>{t('ui.error_boundary.recovery')}</Text>
|
|
84
|
+
<Text> </Text>
|
|
85
|
+
<Text color="yellow">{t('ui.error_boundary.preserved')}</Text>
|
|
86
|
+
</Box>
|
|
87
|
+
)
|
|
88
|
+
}
|
package/src/ui/goal-progress.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useState, useEffect, useRef } from 'react'
|
|
2
2
|
import { Box, Text } from 'ink'
|
|
3
|
+
import { useI18n } from '../i18n-context'
|
|
3
4
|
import { getTasks } from '../tools/exec/task.js'
|
|
4
5
|
import type { Task } from '../tools/exec/task.js'
|
|
5
6
|
|
|
@@ -61,6 +62,7 @@ function taskIcon(status: Task['status']): { icon: string; color?: string; dim?:
|
|
|
61
62
|
|
|
62
63
|
/** Live goal progress panel — mirrors ZCode's goal view: title + subtasks + done/total · elapsed · tokens. */
|
|
63
64
|
export function GoalProgress({ goal, getTokens, listTasks }: GoalProgressProps) {
|
|
65
|
+
const { t } = useI18n()
|
|
64
66
|
const [tasks, setTasks] = useState<Task[]>([])
|
|
65
67
|
const [tokens, setTokens] = useState(0)
|
|
66
68
|
const [elapsedMs, setElapsedMs] = useState(0)
|
|
@@ -103,11 +105,11 @@ export function GoalProgress({ goal, getTokens, listTasks }: GoalProgressProps)
|
|
|
103
105
|
marginBottom={1}
|
|
104
106
|
>
|
|
105
107
|
<Text bold color="green">
|
|
106
|
-
|
|
108
|
+
{t('ui.goal_progress.title', { goal })}
|
|
107
109
|
</Text>
|
|
108
110
|
|
|
109
111
|
{visibleTasks.length === 0 ? (
|
|
110
|
-
<Text dimColor>
|
|
112
|
+
<Text dimColor>{t('ui.goal_progress.no_subtasks')}</Text>
|
|
111
113
|
) : (
|
|
112
114
|
visibleTasks.map((t) => {
|
|
113
115
|
const meta = taskIcon(t.status)
|
|
@@ -123,13 +125,17 @@ export function GoalProgress({ goal, getTokens, listTasks }: GoalProgressProps)
|
|
|
123
125
|
|
|
124
126
|
<Box marginTop={1}>
|
|
125
127
|
<Text dimColor>
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
128
|
+
{t('ui.goal_progress.summary', {
|
|
129
|
+
done: String(summary.done),
|
|
130
|
+
total: String(summary.total),
|
|
131
|
+
})}
|
|
132
|
+
{summary.inProgress > 0 &&
|
|
133
|
+
t('ui.goal_progress.in_progress', { n: String(summary.inProgress) })}
|
|
134
|
+
{summary.failed > 0 && t('ui.goal_progress.failed', { n: String(summary.failed) })}
|
|
129
135
|
{' · '}
|
|
130
136
|
{formatElapsed(elapsedMs)}
|
|
131
|
-
{tokens > 0 &&
|
|
132
|
-
{'
|
|
137
|
+
{tokens > 0 && t('ui.goal_progress.tokens', { n: formatTokens(tokens) })}
|
|
138
|
+
{t('ui.goal_progress.border_end')}
|
|
133
139
|
</Text>
|
|
134
140
|
</Box>
|
|
135
141
|
</Box>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { useState, useEffect, useMemo } from 'react'
|
|
2
2
|
import { Box, Text } from 'ink'
|
|
3
|
+
import { useI18n } from '../i18n-context'
|
|
3
4
|
import { getEventBus } from '../workflow/event-bus.js'
|
|
4
5
|
import type { WorkflowEvent } from '../workflow/event-bus.js'
|
|
5
6
|
|
|
@@ -16,6 +17,7 @@ interface WorkflowProgressProps {
|
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
export function WorkflowProgress({ runId }: WorkflowProgressProps) {
|
|
20
|
+
const { t } = useI18n()
|
|
19
21
|
const [activeRunId, setActiveRunId] = useState<string | null>(null)
|
|
20
22
|
const [phases, setPhases] = useState<Array<{ name: string; done: boolean }>>([])
|
|
21
23
|
const [agents, setAgents] = useState<Map<string, AgentState>>(new Map())
|
|
@@ -129,7 +131,7 @@ export function WorkflowProgress({ runId }: WorkflowProgressProps) {
|
|
|
129
131
|
<Box flexDirection="column" borderStyle="round" borderColor="cyan" paddingX={1}>
|
|
130
132
|
<Box>
|
|
131
133
|
<Text bold color="cyan">
|
|
132
|
-
|
|
134
|
+
{t('ui.workflow.title', { runId: activeRunId ?? runId ?? '' })}
|
|
133
135
|
</Text>
|
|
134
136
|
</Box>
|
|
135
137
|
|
|
@@ -139,9 +141,12 @@ export function WorkflowProgress({ runId }: WorkflowProgressProps) {
|
|
|
139
141
|
return (
|
|
140
142
|
<Box key={i} flexDirection="column" marginTop={1}>
|
|
141
143
|
<Text>
|
|
142
|
-
{phase.done ? '●' : '◌'}
|
|
144
|
+
{phase.done ? '●' : '◌'} {t('ui.workflow.phase', { name: phase.name })}
|
|
143
145
|
{phaseAgents.length > 0 &&
|
|
144
|
-
|
|
146
|
+
t('ui.workflow.done_count', {
|
|
147
|
+
done: String(phaseAgents.filter((a) => a.status === 'done').length),
|
|
148
|
+
total: String(phaseAgents.length),
|
|
149
|
+
})}
|
|
145
150
|
</Text>
|
|
146
151
|
{phaseAgents.map((agent, j) => (
|
|
147
152
|
<Box key={j} marginLeft={2}>
|
|
@@ -163,12 +168,16 @@ export function WorkflowProgress({ runId }: WorkflowProgressProps) {
|
|
|
163
168
|
{/* Summary footer */}
|
|
164
169
|
<Box marginTop={1}>
|
|
165
170
|
<Text dimColor>
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
{
|
|
171
|
+
{t('ui.workflow.footer', {
|
|
172
|
+
time: elapsedStr,
|
|
173
|
+
done: String(doneCount),
|
|
174
|
+
total: String(agentList.length),
|
|
175
|
+
})}
|
|
176
|
+
{runningCount > 0 && t('ui.workflow.running', { n: String(runningCount) })}
|
|
177
|
+
{failedCount > 0 && t('ui.workflow.failed', { n: String(failedCount) })}
|
|
178
|
+
{done && t('ui.workflow.cache_hits', { n: String(cacheHits) })}
|
|
179
|
+
{done && t('ui.workflow.done_marker')}
|
|
180
|
+
{!done && t('ui.workflow.running_marker')}
|
|
172
181
|
</Text>
|
|
173
182
|
</Box>
|
|
174
183
|
</Box>
|