principles-disciple 1.13.0 → 1.14.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/openclaw.plugin.json +4 -4
- package/package.json +1 -1
- package/scripts/sync-plugin.mjs +1 -156
- package/src/commands/nocturnal-train.ts +12 -11
- package/src/core/evolution-reducer.ts +4 -31
- package/src/core/nocturnal-trinity.ts +4 -19
- package/src/core/principle-tree-ledger.ts +7 -27
- package/src/core/thinking-os-parser.ts +44 -36
- package/src/index.ts +3 -7
- package/src/service/nocturnal-service.ts +7 -11
- package/src/service/subagent-workflow/nocturnal-workflow-manager.ts +3 -18
- package/templates/langs/en/principles/THINKING_OS.md +0 -13
- package/templates/langs/zh/principles/THINKING_OS.md +0 -13
- package/ui/src/i18n/ui.ts +52 -0
- package/ui/src/pages/EvolutionPage.tsx +38 -57
- package/ui/src/pages/FeedbackPage.tsx +0 -2
- package/ui/src/pages/GateMonitorPage.tsx +3 -3
- package/ui/src/pages/LoginPage.tsx +2 -1
- package/ui/src/pages/OverviewPage.tsx +10 -9
- package/ui/src/pages/SamplesPage.tsx +3 -3
- package/ui/src/pages/ThinkingModelsPage.tsx +444 -95
- package/ui/src/styles.css +316 -0
- package/src/core/principle-tree-migration.ts +0 -195
package/src/index.ts
CHANGED
|
@@ -54,7 +54,6 @@ import { PDTaskService } from './core/pd-task-service.js';
|
|
|
54
54
|
import { CentralSyncService } from './service/central-sync-service.js';
|
|
55
55
|
import { ensureWorkspaceTemplates } from './core/init.js';
|
|
56
56
|
import { migrateDirectoryStructure } from './core/migration.js';
|
|
57
|
-
import { runMigrationIfNeeded } from './core/principle-tree-migration.js';
|
|
58
57
|
import { SystemLogger } from './core/system-logger.js';
|
|
59
58
|
import { createDeepReflectTool } from './tools/deep-reflect.js';
|
|
60
59
|
import { PathResolver, resolveWorkspaceDirFromApi } from './core/path-resolver.js';
|
|
@@ -126,9 +125,6 @@ const plugin = {
|
|
|
126
125
|
const workspaceDir = ctx.workspaceDir || api.resolvePath('.');
|
|
127
126
|
if (!workspaceInitialized && workspaceDir) {
|
|
128
127
|
migrateDirectoryStructure(api, workspaceDir);
|
|
129
|
-
// Phase 11: Migrate trainingStore principles to tree.principles
|
|
130
|
-
const { stateDir } = WorkspaceContext.fromHookContext({ workspaceDir });
|
|
131
|
-
runMigrationIfNeeded(stateDir, workspaceDir);
|
|
132
128
|
ensureWorkspaceTemplates(api, workspaceDir, language);
|
|
133
129
|
SystemLogger.log(workspaceDir, 'SYSTEM_BOOT', `Principles Disciple online. Language: ${language}`);
|
|
134
130
|
workspaceInitialized = true;
|
|
@@ -235,7 +231,7 @@ const plugin = {
|
|
|
235
231
|
try {
|
|
236
232
|
const workspaceDir = resolveToolHookWorkspaceDir(ctx, api, 'trajectory.after_tool_call');
|
|
237
233
|
TrajectoryCollector.handleAfterToolCall(event, { ...ctx, workspaceDir });
|
|
238
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- Reason: catch binding intentionally unused
|
|
234
|
+
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars -- Reason: catch binding intentionally unused
|
|
239
235
|
} catch (_err) {
|
|
240
236
|
// Non-critical: don't log, just skip
|
|
241
237
|
}
|
|
@@ -248,7 +244,7 @@ const plugin = {
|
|
|
248
244
|
try {
|
|
249
245
|
const workspaceDir = resolveToolHookWorkspaceDir(ctx as unknown as Record<string, unknown>, api, 'trajectory.llm_output');
|
|
250
246
|
TrajectoryCollector.handleLlmOutput(event, { ...ctx, workspaceDir });
|
|
251
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- Reason: catch binding intentionally unused
|
|
247
|
+
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars -- Reason: catch binding intentionally unused
|
|
252
248
|
} catch (_err) {
|
|
253
249
|
// Non-critical: don't log, just skip
|
|
254
250
|
}
|
|
@@ -258,7 +254,7 @@ const plugin = {
|
|
|
258
254
|
// ── Hook: Subagent Loop Closure ──
|
|
259
255
|
api.on(
|
|
260
256
|
'subagent_spawning',
|
|
261
|
-
|
|
257
|
+
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars -- Reason: ctx param required by hook callback signature but not used in this handler
|
|
262
258
|
(event: PluginHookSubagentSpawningEvent, _ctx: PluginHookSubagentContext): void | PluginHookSubagentSpawningResult => {
|
|
263
259
|
try {
|
|
264
260
|
// Resolve workspace via official API, falling back to PathResolver
|
|
@@ -1146,14 +1146,13 @@ async function executeNocturnalReflectionWithAdapter(
|
|
|
1146
1146
|
// eslint-disable-next-line no-useless-assignment -- Reason: initial value unused due to immediate reassignment in all branches
|
|
1147
1147
|
let snapshot: NocturnalSessionSnapshot | null = null;
|
|
1148
1148
|
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
const snapStats = options.snapshotOverride.stats;
|
|
1149
|
+
if (options.principleIdOverride && options.snapshotOverride) {
|
|
1150
|
+
// Skip Selector: use provided principleId and snapshot directly
|
|
1151
|
+
selectedPrincipleId = options.principleIdOverride;
|
|
1152
|
+
selectedSessionId = options.snapshotOverride.sessionId;
|
|
1153
|
+
snapshot = options.snapshotOverride;
|
|
1154
|
+
// Calculate violation density from snapshot stats for meaningful diagnostics
|
|
1155
|
+
const snapStats = options.snapshotOverride.stats;
|
|
1157
1156
|
const totalToolCalls = snapStats?.totalToolCalls ?? 0;
|
|
1158
1157
|
const failureCount = snapStats?.failureCount ?? 0;
|
|
1159
1158
|
const violationDensity = totalToolCalls > 0 ? failureCount / totalToolCalls : 0;
|
|
@@ -1178,7 +1177,6 @@ async function executeNocturnalReflectionWithAdapter(
|
|
|
1178
1177
|
diagnostics.idle = { isIdle: true, mostRecentActivityAt: 0, idleForMs: 0, userActiveSessions: 0, abandonedSessionIds: [], trajectoryGuardrailConfirmsIdle: true, reason: 'selector skipped (override provided)' };
|
|
1179
1178
|
} else {
|
|
1180
1179
|
// Normal Selector path
|
|
1181
|
-
console.log(`[nocturnal-service] Step 2/7: Target selection (normal path)`);
|
|
1182
1180
|
const extractor = createNocturnalTrajectoryExtractor(workspaceDir, stateDir);
|
|
1183
1181
|
const selector = new NocturnalTargetSelector(workspaceDir, stateDir, extractor, {
|
|
1184
1182
|
idleCheckOverride: options.idleCheckOverride,
|
|
@@ -1187,10 +1185,8 @@ async function executeNocturnalReflectionWithAdapter(
|
|
|
1187
1185
|
|
|
1188
1186
|
const selection = selector.select();
|
|
1189
1187
|
diagnostics.selection = selection;
|
|
1190
|
-
console.log(`[nocturnal-service] Selector result: decision=${selection.decision}, skipReason=${selection.skipReason ?? 'none'}`);
|
|
1191
1188
|
|
|
1192
1189
|
if (selection.decision === 'skip') {
|
|
1193
|
-
console.warn(`[nocturnal-service] Target selection skipped: ${selection.skipReason}`);
|
|
1194
1190
|
return {
|
|
1195
1191
|
success: false,
|
|
1196
1192
|
noTargetSelected: true,
|
|
@@ -176,14 +176,8 @@ export class NocturnalWorkflowManager implements WorkflowManager {
|
|
|
176
176
|
// Other workflow managers (empathy, deep-reflect) have this check
|
|
177
177
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Reason: TrinityRuntimeAdapter interface doesn't expose api.runtime.subagent, but OpenClawTrinityRuntimeAdapter has it
|
|
178
178
|
const subagent = (this.runtimeAdapter as any).api?.runtime?.subagent;
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
const subagentAvailable = isSubagentRuntimeAvailable(subagent);
|
|
182
|
-
|
|
183
|
-
this.logger.info(`[PD:NocturnalWorkflow] Subagent availability check: api=${apiAvailable}, runtime=${runtimeAvailable}, subagent=${subagentAvailable}`);
|
|
184
|
-
|
|
185
|
-
if (!subagentAvailable) {
|
|
186
|
-
this.logger.warn(`[PD:NocturnalWorkflow] Subagent runtime unavailable (api=${apiAvailable}, runtime=${runtimeAvailable}), skipping workflow`);
|
|
179
|
+
if (!isSubagentRuntimeAvailable(subagent)) {
|
|
180
|
+
this.logger.warn(`[PD:NocturnalWorkflow] Subagent runtime unavailable, skipping workflow`);
|
|
187
181
|
throw new Error(`NocturnalWorkflowManager: subagent runtime unavailable`);
|
|
188
182
|
}
|
|
189
183
|
|
|
@@ -240,15 +234,12 @@ export class NocturnalWorkflowManager implements WorkflowManager {
|
|
|
240
234
|
// When principleId is provided, we pass it as principleIdOverride to skip Selector.
|
|
241
235
|
// When principleId is missing, Selector will choose a principle from training store.
|
|
242
236
|
this.logger.info(`[PD:NocturnalWorkflow] Calling executeNocturnalReflectionAsync for full pipeline (principleId=${principleId ?? 'auto-select'})`);
|
|
243
|
-
const pipelineStart = Date.now();
|
|
244
237
|
|
|
245
238
|
// #213: Wrap fire-and-forget Promise with .catch() to prevent
|
|
246
239
|
// unhandled promise rejections if anything throws outside the try-catch
|
|
247
240
|
// (e.g., during parameter construction or environment errors).
|
|
248
241
|
Promise.resolve().then(async () => {
|
|
249
242
|
try {
|
|
250
|
-
this.logger.info(`[PD:NocturnalWorkflow] [${workflowId}] Pipeline step 1/4: Starting executeNocturnalReflectionAsync`);
|
|
251
|
-
|
|
252
243
|
const result = await executeNocturnalReflectionAsync(
|
|
253
244
|
this.workspaceDir,
|
|
254
245
|
this.stateDir,
|
|
@@ -271,26 +262,20 @@ export class NocturnalWorkflowManager implements WorkflowManager {
|
|
|
271
262
|
}
|
|
272
263
|
);
|
|
273
264
|
|
|
274
|
-
const pipelineDuration = Date.now() - pipelineStart;
|
|
275
|
-
this.logger.info(`[PD:NocturnalWorkflow] [${workflowId}] Pipeline completed in ${pipelineDuration}ms, success=${result.success}`);
|
|
276
|
-
|
|
277
265
|
if (result.success) {
|
|
278
|
-
this.logger.info(`[PD:NocturnalWorkflow] [${workflowId}] Pipeline step 4/4: Completed successfully, artifactId=${result.diagnostics?.persistedPath}`);
|
|
279
266
|
this.store.recordEvent(workflowId, 'nocturnal_completed', null, 'completed', 'Full pipeline completed via executeNocturnalReflectionAsync', {
|
|
280
267
|
artifactId: result.diagnostics?.persistedPath,
|
|
281
268
|
});
|
|
282
269
|
this.completedWorkflows.set(workflowId, Date.now());
|
|
283
270
|
} else {
|
|
284
271
|
const reason = result.noTargetSelected ? 'no_target_selected' : 'validation_failed';
|
|
285
|
-
this.logger.warn(`[PD:NocturnalWorkflow] [${workflowId}] Pipeline failed: reason=${reason}, noTargetSelected=${result.noTargetSelected}, skipReason=${result.skipReason ?? 'none'}, validationFailures=${result.validationFailures?.length ?? 0}`);
|
|
286
272
|
this.store.recordEvent(workflowId, 'nocturnal_failed', null, 'terminal_error', reason, {
|
|
287
273
|
failures: result.validationFailures,
|
|
288
274
|
skipReason: result.skipReason,
|
|
289
275
|
});
|
|
290
276
|
}
|
|
291
277
|
} catch (err) {
|
|
292
|
-
|
|
293
|
-
this.logger.error(`[PD:NocturnalWorkflow] [${workflowId}] executeNocturnalReflectionAsync threw after ${errDuration}ms: ${String(err)}`);
|
|
278
|
+
this.logger.error(`[PD:NocturnalWorkflow] executeNocturnalReflectionAsync threw: ${String(err)}`);
|
|
294
279
|
this.store.recordEvent(workflowId, 'nocturnal_failed', null, 'terminal_error', String(err), { workflowId });
|
|
295
280
|
}
|
|
296
281
|
}).catch((err) => {
|
|
@@ -61,17 +61,4 @@ LLMs are highly sensitive to XML tags; this structure is designed to boost instr
|
|
|
61
61
|
<must>Maintain extreme digital cleanliness. The project root is SACRED. Use strict `kebab-case` for all naming. Clean up all test scripts and debug artifacts after the task.</must>
|
|
62
62
|
<forbidden>Creating arbitrary temporary files (e.g., `test.txt`, `temp.md`, `debug.log`) in the project root directory.</forbidden>
|
|
63
63
|
</directive>
|
|
64
|
-
|
|
65
|
-
<!-- 复杂任务分解与记忆外化 (Complex Task Decomposition & Memory Externalization) -->
|
|
66
|
-
<directive id="T-09" name="DIVIDE_AND_CONQUER">
|
|
67
|
-
<trigger>When facing a complex task with multiple interdependent steps or large-scale refactoring.</trigger>
|
|
68
|
-
<must>Break the work into smallest meaningful units. Execute in dependency order. Validate each unit before proceeding.</must>
|
|
69
|
-
<forbidden>Tackle complex tasks as a single monolithic operation. Mix unrelated changes in one edit.</forbidden>
|
|
70
|
-
</directive>
|
|
71
|
-
|
|
72
|
-
<directive id="T-10" name="MEMORY_EXTERNALIZATION">
|
|
73
|
-
<trigger>When drawing conclusions, completing analysis, or about to switch context.</trigger>
|
|
74
|
-
<must>Write conclusions to a file (plan.md, scratchpad, memory) before proceeding. Preserve reasoning for future reference.</must>
|
|
75
|
-
<forbidden>Keep important conclusions only in conversation context. Lose state between turns.</forbidden>
|
|
76
|
-
</directive>
|
|
77
64
|
</thinking_os_core_directives>
|
|
@@ -61,17 +61,4 @@
|
|
|
61
61
|
<must>保持极致的数字洁癖。项目根目录是神圣的。所有命名必须严格使用 `kebab-case`。任务结束后清理所有的测试脚本和 Debug 遗留物。</must>
|
|
62
62
|
<forbidden>在项目根目录下随意创建临时文件(如 `test.txt`、`temp.md`、`debug.log`)。</forbidden>
|
|
63
63
|
</directive>
|
|
64
|
-
|
|
65
|
-
<!-- 复杂任务分解与记忆外化 (Complex Task Decomposition & Memory Externalization) -->
|
|
66
|
-
<directive id="T-09" name="DIVIDE_AND_CONQUER">
|
|
67
|
-
<trigger>面对包含多个相互依赖步骤的复杂任务或大规模重构时。</trigger>
|
|
68
|
-
<must>将工作拆分为最小有意义的单元。按依赖顺序执行。在继续之前验证每个单元。</must>
|
|
69
|
-
<forbidden>将复杂任务当作单一操作处理。在一次编辑中混合不相关的变更。</forbidden>
|
|
70
|
-
</directive>
|
|
71
|
-
|
|
72
|
-
<directive id="T-10" name="MEMORY_EXTERNALIZATION">
|
|
73
|
-
<trigger>得出结论、完成分析或即将切换上下文时。</trigger>
|
|
74
|
-
<must>将结论写入文件(plan.md、scratchpad、memory)后再继续。保留推理过程供未来参考。</must>
|
|
75
|
-
<forbidden>将重要结论仅保留在对话上下文中。在会话切换后丢失状态。</forbidden>
|
|
76
|
-
</directive>
|
|
77
64
|
</thinking_os_core_directives>
|
package/ui/src/i18n/ui.ts
CHANGED
|
@@ -52,6 +52,7 @@ export const i18n = {
|
|
|
52
52
|
timeRange: { zh: '时间范围', en: 'Time Range' },
|
|
53
53
|
total: { zh: '共', en: 'Total' },
|
|
54
54
|
items: { zh: '条', en: 'items' },
|
|
55
|
+
search: { zh: '搜索...', en: 'Search...' },
|
|
55
56
|
},
|
|
56
57
|
|
|
57
58
|
// ========================================================================
|
|
@@ -70,6 +71,7 @@ export const i18n = {
|
|
|
70
71
|
checking: { zh: '正在验证身份...', en: 'Verifying identity...' },
|
|
71
72
|
loginTitle: { zh: 'Principles Console', en: 'Principles Console' },
|
|
72
73
|
loginSubtitle: { zh: 'AI Agent 进化流程监控平台', en: 'AI Agent Evolution Monitoring Platform' },
|
|
74
|
+
tokenLabel: { zh: 'Gateway Token', en: 'Gateway Token' },
|
|
73
75
|
tokenPlaceholder: { zh: '请输入您的 Gateway Token', en: 'Enter your Gateway Token' },
|
|
74
76
|
tokenHint: { zh: '在服务器上运行 openclaw config get gateway.auth.token 获取 Token', en: 'Run openclaw config get gateway.auth.token on your server to get the Token' },
|
|
75
77
|
loginButton: { zh: '登 录', en: 'Sign In' },
|
|
@@ -184,6 +186,11 @@ export const i18n = {
|
|
|
184
186
|
createdAt: { zh: '创建时间', en: 'Created At' },
|
|
185
187
|
failureMode: { zh: '失败模式', en: 'Failure Mode' },
|
|
186
188
|
relatedThinking: { zh: '相关思维', en: 'Thinking Hits' },
|
|
189
|
+
|
|
190
|
+
// Missing i18n keys (previously hardcoded in components)
|
|
191
|
+
noGfiToday: { zh: '今日暂无 GFI 记录', en: 'No GFI data today' },
|
|
192
|
+
noWorkspacesFound: { zh: '未找到已启用的工作区', en: 'No enabled workspaces found' },
|
|
193
|
+
refreshing: { zh: '刷新中', en: 'Refreshing' },
|
|
187
194
|
},
|
|
188
195
|
|
|
189
196
|
// ========================================================================
|
|
@@ -213,6 +220,9 @@ export const i18n = {
|
|
|
213
220
|
// Actions
|
|
214
221
|
approve: { zh: '批准', en: 'Approve' },
|
|
215
222
|
reject: { zh: '拒绝', en: 'Reject' },
|
|
223
|
+
|
|
224
|
+
// Error messages (previously hardcoded English)
|
|
225
|
+
reviewFailed: { zh: '审核操作失败', en: 'Review operation failed' },
|
|
216
226
|
},
|
|
217
227
|
|
|
218
228
|
// ========================================================================
|
|
@@ -236,6 +246,7 @@ export const i18n = {
|
|
|
236
246
|
emptyAllActiveDesc: { zh: '没有休眠模型。', en: 'No dormant models.' },
|
|
237
247
|
noModelsYet: { zh: '暂无思维模型数据', en: 'No thinking model data yet' },
|
|
238
248
|
noModelsYetDesc: { zh: 'AI 开始使用后,这里会显示思维模型的使用情况。', en: 'Thinking model usage will appear here once AI starts working.' },
|
|
249
|
+
noMatches: { zh: '没有匹配的模型', en: 'No models match your filters.' },
|
|
239
250
|
|
|
240
251
|
// Recommendations
|
|
241
252
|
dormantModels: { zh: '休眠模型', en: 'Dormant Models' },
|
|
@@ -247,6 +258,8 @@ export const i18n = {
|
|
|
247
258
|
// Detail sections
|
|
248
259
|
outcomeStats: { zh: '效果统计', en: 'Outcome Stats' },
|
|
249
260
|
usageTrend: { zh: '使用趋势', en: 'Usage Trend' },
|
|
261
|
+
emptyUsageTrend: { zh: '暂无使用趋势记录', en: 'No usage trend data yet' },
|
|
262
|
+
emptyUsageTrendDesc: { zh: '该模型尚未被触发使用。', en: 'This model has not been triggered yet.' },
|
|
250
263
|
success: { zh: '成功', en: 'Success' },
|
|
251
264
|
failure: { zh: '失败', en: 'Failure' },
|
|
252
265
|
pain: { zh: '痛点', en: 'Pain' },
|
|
@@ -265,11 +278,32 @@ export const i18n = {
|
|
|
265
278
|
emptyDesc: { zh: '点击左侧列表中的模型,查看场景分布和最近事件', en: 'Click a model from the list to inspect scenario coverage and recent events' },
|
|
266
279
|
noDataTitle: { zh: '思维模型定义', en: 'Thinking Model Definitions' },
|
|
267
280
|
noDataDesc: { zh: '以下是 10 个思维模型的定义。当 AI 开始使用后,这里会显示每个模型的使用统计。', en: 'Below are 10 thinking model definitions. Usage statistics will appear once the AI starts working.' },
|
|
281
|
+
noUsageDataYet: { zh: '该模型暂无使用数据', en: 'No usage data for this model yet' },
|
|
282
|
+
noUsageDataDesc: { zh: '当模型被触发后,使用趋势和事件会在此显示。', en: 'Usage trends and events will appear once the model is triggered.' },
|
|
268
283
|
|
|
269
284
|
// Table
|
|
270
285
|
hits: { zh: '命中', en: 'Hits' },
|
|
271
286
|
successRate: { zh: '成功率', en: 'Success Rate' },
|
|
272
287
|
failureRate: { zh: '失败率', en: 'Failure Rate' },
|
|
288
|
+
|
|
289
|
+
// Comparison mode
|
|
290
|
+
compare: { zh: '对比', en: 'Compare' },
|
|
291
|
+
compareSelected: { zh: '对比选中', en: 'Compare Selected' },
|
|
292
|
+
exitCompare: { zh: '退出对比', en: 'Exit Compare' },
|
|
293
|
+
comparisonTitle: { zh: '模型对比', en: 'Model Comparison' },
|
|
294
|
+
comparisonEmpty: { zh: '请选择至少 2 个模型进行对比', en: 'Select at least 2 models to compare' },
|
|
295
|
+
|
|
296
|
+
// Search and sort
|
|
297
|
+
filterAll: { zh: '全部', en: 'All' },
|
|
298
|
+
searchPlaceholder: { zh: '按名称或场景搜索...', en: 'Search by name or scenario...' },
|
|
299
|
+
sortByHits: { zh: '命中数', en: 'Hits' },
|
|
300
|
+
sortBySuccessRate: { zh: '成功率', en: 'Success Rate' },
|
|
301
|
+
sortByName: { zh: '名称', en: 'Name' },
|
|
302
|
+
|
|
303
|
+
// Loading states
|
|
304
|
+
loadingDetail: { zh: '正在加载模型详情...', en: 'Loading model details...' },
|
|
305
|
+
loadingComparison: { zh: '正在加载对比数据...', en: 'Loading comparison data...' },
|
|
306
|
+
modelLoading: { zh: '加载中', en: 'Loading' },
|
|
273
307
|
},
|
|
274
308
|
|
|
275
309
|
// ========================================================================
|
|
@@ -301,6 +335,11 @@ export const i18n = {
|
|
|
301
335
|
analyzing: { zh: '分析中', en: 'Analyzing' },
|
|
302
336
|
principle_generated: { zh: '原则生成', en: 'Principle Generated' },
|
|
303
337
|
completed: { zh: '已完成', en: 'Completed' },
|
|
338
|
+
// Principle status labels
|
|
339
|
+
candidate: { zh: '候选', en: 'Candidate' },
|
|
340
|
+
probation: { zh: '试用', en: 'Probation' },
|
|
341
|
+
active: { zh: '活跃', en: 'Active' },
|
|
342
|
+
deprecated: { zh: '废弃', en: 'Deprecated' },
|
|
304
343
|
},
|
|
305
344
|
|
|
306
345
|
// Status filter
|
|
@@ -331,6 +370,19 @@ export const i18n = {
|
|
|
331
370
|
evolutionTimeline: { zh: '进化时间线', en: 'Evolution Timeline' },
|
|
332
371
|
detailedEvents: { zh: '详细事件', en: 'Detailed Events' },
|
|
333
372
|
reason: { zh: '原因', en: 'Reason' },
|
|
373
|
+
|
|
374
|
+
// EvolutionPage panel headers (previously hardcoded Chinese)
|
|
375
|
+
currentStage: { zh: '当前阶段', en: 'Current Stage' },
|
|
376
|
+
principleLifecycle: { zh: '原则生命周期', en: 'Principle Lifecycle' },
|
|
377
|
+
nocturnalTrainingStatus: { zh: '夜间训练状态', en: 'Nocturnal Training Status' },
|
|
378
|
+
trainingQueue: { zh: '训练队列', en: 'Training Queue' },
|
|
379
|
+
arbiterPassRate: { zh: 'Arbiter 通过率', en: 'Arbiter Pass Rate' },
|
|
380
|
+
orpoSampleCount: { zh: 'ORPO 样本数', en: 'ORPO Sample Count' },
|
|
381
|
+
modelDeployments: { zh: '模型部署', en: 'Model Deployments' },
|
|
382
|
+
deploymentCount: { zh: '个', en: 'deployments' },
|
|
383
|
+
pendingShort: { zh: '待', en: 'P' },
|
|
384
|
+
inProgressShort: { zh: '中', en: 'I' },
|
|
385
|
+
completedShort: { zh: '完', en: 'C' },
|
|
334
386
|
},
|
|
335
387
|
|
|
336
388
|
// ========================================================================
|
|
@@ -1,45 +1,34 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { useEffect, useMemo, useState } from 'react';
|
|
2
2
|
import { ChevronLeft, Clock, Activity, Shield, Zap, BookOpen } from 'lucide-react';
|
|
3
3
|
import { api } from '../api';
|
|
4
4
|
import type {
|
|
5
|
-
OverviewResponse,
|
|
6
|
-
SampleDetailResponse,
|
|
7
|
-
SamplesResponse,
|
|
8
|
-
ThinkingModelDetailResponse,
|
|
9
|
-
ThinkingOverviewResponse,
|
|
10
5
|
EvolutionTasksResponse,
|
|
11
6
|
EvolutionTraceResponse,
|
|
12
7
|
EvolutionStatsResponse,
|
|
13
|
-
OverviewHealthResponse,
|
|
14
8
|
EvolutionPrinciplesResponse,
|
|
15
|
-
FeedbackGfiResponse,
|
|
16
|
-
GateStatsResponse,
|
|
17
|
-
GateBlockItem,
|
|
18
|
-
EmpathyEvent,
|
|
19
|
-
FeedbackGateBlock,
|
|
20
9
|
} from '../types';
|
|
21
|
-
import {
|
|
10
|
+
import { DonutChart, GroupedBarChart, TimeRangeSelector, StatusBadge, EmptyState } from '../charts';
|
|
22
11
|
import { useI18n } from '../i18n/ui';
|
|
23
12
|
import { formatPercent, formatDate, formatDuration } from '../utils/format';
|
|
24
13
|
import { Loading, ErrorState } from '../components';
|
|
25
14
|
|
|
26
15
|
const STAGE_COLORS: Record<string, string> = {
|
|
27
|
-
pain_detected: '
|
|
28
|
-
queued: '
|
|
29
|
-
started: '
|
|
16
|
+
pain_detected: 'var(--error)',
|
|
17
|
+
queued: 'var(--warning)',
|
|
18
|
+
started: 'var(--info)',
|
|
30
19
|
analyzing: '#8b5cf6',
|
|
31
|
-
principle_generated: '
|
|
32
|
-
completed: '
|
|
20
|
+
principle_generated: 'var(--success)',
|
|
21
|
+
completed: 'var(--success)',
|
|
33
22
|
};
|
|
34
23
|
|
|
35
|
-
const
|
|
36
|
-
pain_detected
|
|
37
|
-
queued
|
|
38
|
-
started
|
|
39
|
-
analyzing
|
|
40
|
-
principle_generated
|
|
41
|
-
completed
|
|
42
|
-
|
|
24
|
+
const STAGE_LABEL_KEYS: string[] = [
|
|
25
|
+
'pain_detected',
|
|
26
|
+
'queued',
|
|
27
|
+
'started',
|
|
28
|
+
'analyzing',
|
|
29
|
+
'principle_generated',
|
|
30
|
+
'completed',
|
|
31
|
+
];
|
|
43
32
|
|
|
44
33
|
export function EvolutionPage() {
|
|
45
34
|
const { t } = useI18n();
|
|
@@ -86,10 +75,10 @@ export function EvolutionPage() {
|
|
|
86
75
|
|
|
87
76
|
// Prepare donut chart data
|
|
88
77
|
const statusSegments = [
|
|
89
|
-
{ label: t('evolution.pending'), value: stats.pending, color: '
|
|
90
|
-
{ label: t('evolution.inProgress'), value: stats.inProgress, color: '
|
|
91
|
-
{ label: t('evolution.completed'), value: stats.completed, color: '
|
|
92
|
-
{ label: t('evolution.failed'), value: stats.failed, color: '
|
|
78
|
+
{ label: t('evolution.pending'), value: stats.pending, color: 'var(--warning)' },
|
|
79
|
+
{ label: t('evolution.inProgress'), value: stats.inProgress, color: 'var(--info)' },
|
|
80
|
+
{ label: t('evolution.completed'), value: stats.completed, color: 'var(--success)' },
|
|
81
|
+
{ label: t('evolution.failed'), value: stats.failed, color: 'var(--error)' },
|
|
93
82
|
].filter(s => s.value > 0);
|
|
94
83
|
|
|
95
84
|
return (
|
|
@@ -112,23 +101,15 @@ export function EvolutionPage() {
|
|
|
112
101
|
{/* Current Stage Indicator */}
|
|
113
102
|
{evoPrinciples && (
|
|
114
103
|
<section className="panel" style={{ marginBottom: 'var(--space-5)' }}>
|
|
115
|
-
<h3
|
|
104
|
+
<h3>{t('evolution.currentStage')}</h3>
|
|
116
105
|
<div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-3)', padding: 'var(--space-3) 0' }}>
|
|
117
|
-
<span
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
background: 'var(--accent)',
|
|
123
|
-
color: '#fff',
|
|
124
|
-
border: '2px solid var(--accent)',
|
|
125
|
-
}}>
|
|
126
|
-
{evoPrinciples.activeStage === 'pending' ? <><Clock size={16} style={{marginRight: 6, verticalAlign: 'middle'}}/>{t('evolution.activeStage.pending')}</> :
|
|
127
|
-
evoPrinciples.activeStage === 'in_progress' ? <><Activity size={16} style={{marginRight: 6, verticalAlign: 'middle'}}/>{t('evolution.activeStage.in_progress')}</> :
|
|
128
|
-
evoPrinciples.activeStage === 'completed' ? <><Shield size={16} style={{marginRight: 6, verticalAlign: 'middle'}}/>{t('evolution.activeStage.completed')}</> :
|
|
129
|
-
evoPrinciples.activeStage === 'idle' ? <><Zap size={16} style={{marginRight: 6, verticalAlign: 'middle'}}/>{t('evolution.activeStage.idle')}</> : evoPrinciples.activeStage}
|
|
106
|
+
<span className="stage-badge">
|
|
107
|
+
{evoPrinciples.activeStage === 'pending' ? <><Clock size={16} /><span>{t('evolution.activeStage.pending')}</span></> :
|
|
108
|
+
evoPrinciples.activeStage === 'in_progress' ? <><Activity size={16} /><span>{t('evolution.activeStage.in_progress')}</span></> :
|
|
109
|
+
evoPrinciples.activeStage === 'completed' ? <><Shield size={16} /><span>{t('evolution.activeStage.completed')}</span></> :
|
|
110
|
+
evoPrinciples.activeStage === 'idle' ? <><Zap size={16} /><span>{t('evolution.activeStage.idle')}</span></> : evoPrinciples.activeStage}
|
|
130
111
|
</span>
|
|
131
|
-
<span style={{ color: 'var(--text-secondary)'
|
|
112
|
+
<span className="text-sm" style={{ color: 'var(--text-secondary)' }}>
|
|
132
113
|
{t('evolution.enhancementLoopStatus')}
|
|
133
114
|
</span>
|
|
134
115
|
</div>
|
|
@@ -139,12 +120,12 @@ export function EvolutionPage() {
|
|
|
139
120
|
{evoPrinciples && (
|
|
140
121
|
<div className="grid two-columns" style={{ marginBottom: 'var(--space-5)' }}>
|
|
141
122
|
<section className="panel">
|
|
142
|
-
<h3><BookOpen size={16}
|
|
123
|
+
<h3><BookOpen size={16} />{t('evolution.principleLifecycle')}</h3>
|
|
143
124
|
<div className="pill-row" style={{ marginBottom: 'var(--space-3)' }}>
|
|
144
|
-
<StatusBadge variant="warning"
|
|
145
|
-
<StatusBadge variant="info"
|
|
146
|
-
<StatusBadge variant="success"
|
|
147
|
-
<StatusBadge variant="error"
|
|
125
|
+
<StatusBadge variant="warning">{t('evolution.stageLabels.candidate')}: {evoPrinciples.principles.summary.candidate}</StatusBadge>
|
|
126
|
+
<StatusBadge variant="info">{t('evolution.stageLabels.probation')}: {evoPrinciples.principles.summary.probation}</StatusBadge>
|
|
127
|
+
<StatusBadge variant="success">{t('evolution.stageLabels.active')}: {evoPrinciples.principles.summary.active}</StatusBadge>
|
|
128
|
+
<StatusBadge variant="error">{t('evolution.stageLabels.deprecated')}: {evoPrinciples.principles.summary.deprecated}</StatusBadge>
|
|
148
129
|
</div>
|
|
149
130
|
{evoPrinciples.principles.recent.length > 0 && (
|
|
150
131
|
<div className="stack">
|
|
@@ -161,23 +142,23 @@ export function EvolutionPage() {
|
|
|
161
142
|
)}
|
|
162
143
|
</section>
|
|
163
144
|
<section className="panel">
|
|
164
|
-
<h3>💤
|
|
145
|
+
<h3>💤 {t('evolution.nocturnalTrainingStatus')}</h3>
|
|
165
146
|
<div className="stack">
|
|
166
147
|
<div className="row-card">
|
|
167
|
-
<strong
|
|
168
|
-
<span
|
|
148
|
+
<strong>{t('evolution.trainingQueue')}</strong>
|
|
149
|
+
<span>{t('evolution.pendingShort')}: {evoPrinciples.nocturnalTraining.queue.pending} | {t('evolution.inProgressShort')}: {evoPrinciples.nocturnalTraining.queue.inProgress} | {t('evolution.completedShort')}: {evoPrinciples.nocturnalTraining.queue.completed}</span>
|
|
169
150
|
</div>
|
|
170
151
|
<div className="row-card">
|
|
171
|
-
<strong>
|
|
152
|
+
<strong>{t('evolution.arbiterPassRate')}</strong>
|
|
172
153
|
<span>{(evoPrinciples.nocturnalTraining.arbiterPassRate * 100).toFixed(1)}%</span>
|
|
173
154
|
</div>
|
|
174
155
|
<div className="row-card">
|
|
175
|
-
<strong>
|
|
156
|
+
<strong>{t('evolution.orpoSampleCount')}</strong>
|
|
176
157
|
<span>{evoPrinciples.nocturnalTraining.orpoSampleCount}</span>
|
|
177
158
|
</div>
|
|
178
159
|
<div className="row-card">
|
|
179
|
-
<strong
|
|
180
|
-
<span>{evoPrinciples.nocturnalTraining.deployments.length}
|
|
160
|
+
<strong>{t('evolution.modelDeployments')}</strong>
|
|
161
|
+
<span>{evoPrinciples.nocturnalTraining.deployments.length} {t('evolution.deploymentCount')}</span>
|
|
181
162
|
</div>
|
|
182
163
|
</div>
|
|
183
164
|
</section>
|
|
@@ -80,7 +80,7 @@ export function GateMonitorPage() {
|
|
|
80
80
|
<section className="panel">
|
|
81
81
|
<h3>🔐 {t('gate.trustEngine')}</h3>
|
|
82
82
|
<div style={{ padding: 'var(--space-3) 0' }}>
|
|
83
|
-
<div style={{ fontSize: '
|
|
83
|
+
<div style={{ fontSize: '1.5rem', fontWeight: 700 }}>Stage {gateStats.trust.stage}: {gateStats.trust.status}</div>
|
|
84
84
|
<div style={{ marginTop: 'var(--space-2)', width: '100%', height: '12px', background: 'var(--bg-sunken)', borderRadius: '6px' }}>
|
|
85
85
|
<div style={{ width: `${gateStats.trust.score}%`, height: '100%', background: 'var(--info)', borderRadius: '6px' }} />
|
|
86
86
|
</div>
|
|
@@ -92,11 +92,11 @@ export function GateMonitorPage() {
|
|
|
92
92
|
<section className="panel">
|
|
93
93
|
<h3>🌱 {t('gate.evolutionEngine')}</h3>
|
|
94
94
|
<div style={{ padding: 'var(--space-3) 0' }}>
|
|
95
|
-
<div style={{ fontSize: '
|
|
95
|
+
<div style={{ fontSize: '1.5rem', fontWeight: 700 }}>{gateStats.evolution.tier} ({gateStats.evolution.status})</div>
|
|
96
96
|
<div style={{ marginTop: 'var(--space-2)', width: '100%', height: '12px', background: 'var(--bg-sunken)', borderRadius: '6px' }}>
|
|
97
97
|
<div style={{ width: `${Math.min(100, gateStats.evolution.points / 10)}%`, height: '100%', background: 'var(--success)', borderRadius: '6px' }} />
|
|
98
98
|
</div>
|
|
99
|
-
<div style={{
|
|
99
|
+
<div className="text-sm" style={{ color: 'var(--text-secondary)', marginTop: 'var(--space-1)' }}>
|
|
100
100
|
{t('gate.points')}: {gateStats.evolution.points}
|
|
101
101
|
</div>
|
|
102
102
|
</div>
|
|
@@ -44,7 +44,7 @@ export function LoginPage() {
|
|
|
44
44
|
|
|
45
45
|
<form className="login-form" onSubmit={handleSubmit}>
|
|
46
46
|
<div className="form-group">
|
|
47
|
-
<label htmlFor="token">
|
|
47
|
+
<label htmlFor="token">{t('auth.tokenLabel')}</label>
|
|
48
48
|
<input
|
|
49
49
|
id="token"
|
|
50
50
|
type="password"
|
|
@@ -52,6 +52,7 @@ export function LoginPage() {
|
|
|
52
52
|
onChange={(e) => setToken(e.target.value)}
|
|
53
53
|
placeholder={t('auth.tokenPlaceholder')}
|
|
54
54
|
autoComplete="off"
|
|
55
|
+
aria-label={t('auth.tokenLabel')}
|
|
55
56
|
/>
|
|
56
57
|
<span className="form-hint">
|
|
57
58
|
{t('auth.tokenHint')}
|
|
@@ -196,11 +196,11 @@ function WorkspaceHealthPanel({ entry }: { entry: WorkspaceHealthEntry }) {
|
|
|
196
196
|
{/* Full-width GFI trend chart */}
|
|
197
197
|
<section style={{ marginTop: 'var(--space-4)', borderTop: '1px solid var(--border)', paddingTop: 'var(--space-4)' }}>
|
|
198
198
|
<div style={{ display: 'flex', alignItems: 'baseline', gap: 8, marginBottom: 8 }}>
|
|
199
|
-
<span
|
|
200
|
-
|
|
199
|
+
<span className="text-lg text-semibold">
|
|
200
|
+
{t('overview.health.gfi')} · {t('overview.recentTrend')}
|
|
201
201
|
</span>
|
|
202
|
-
<span style={{
|
|
203
|
-
|
|
202
|
+
<span className="text-sm" style={{ color: 'var(--text-secondary)' }}>
|
|
203
|
+
{t('overview.health.peakToday')}: {h.gfi.peakToday}
|
|
204
204
|
</span>
|
|
205
205
|
</div>
|
|
206
206
|
{h.gfi.trend.length >= 2 ? (
|
|
@@ -236,10 +236,11 @@ function ThinkingModelDistribution({
|
|
|
236
236
|
modelBreakdown?: Array<{ modelId: string; hits: number }>;
|
|
237
237
|
definitions?: Array<{ modelId: string; name: string; description: string }>;
|
|
238
238
|
}) {
|
|
239
|
+
const { t } = useI18n();
|
|
239
240
|
if (!definitions || definitions.length === 0) {
|
|
240
241
|
return (
|
|
241
|
-
<div style={{
|
|
242
|
-
|
|
242
|
+
<div className="text-center" style={{ padding: 'var(--space-3)', color: 'var(--text-secondary)', fontSize: 'var(--text-lg, 0.8rem)' }}>
|
|
243
|
+
{t('overview.noDefinitions')}
|
|
243
244
|
</div>
|
|
244
245
|
);
|
|
245
246
|
}
|
|
@@ -249,8 +250,8 @@ function ThinkingModelDistribution({
|
|
|
249
250
|
|
|
250
251
|
if (!hasAnyHits) {
|
|
251
252
|
return (
|
|
252
|
-
<div style={{
|
|
253
|
-
|
|
253
|
+
<div className="text-center" style={{ padding: 'var(--space-3)', color: 'var(--text-secondary)', fontSize: 'var(--text-lg, 0.8rem)' }}>
|
|
254
|
+
{t('overview.noUsage')}
|
|
254
255
|
</div>
|
|
255
256
|
);
|
|
256
257
|
}
|
|
@@ -472,7 +473,7 @@ export function OverviewPage() {
|
|
|
472
473
|
) : (
|
|
473
474
|
<section className="panel" style={{ marginBottom: 'var(--space-4)' }}>
|
|
474
475
|
<div style={{ textAlign: 'center', padding: 'var(--space-4)', color: 'var(--text-secondary)' }}>
|
|
475
|
-
{t('overview.health.noWorkspaces') || '
|
|
476
|
+
{t('overview.health.noWorkspaces') || t('overview.noWorkspacesFound')}
|
|
476
477
|
</div>
|
|
477
478
|
</section>
|
|
478
479
|
)}
|
|
@@ -46,7 +46,7 @@ export function SamplesPage() {
|
|
|
46
46
|
setData(samples);
|
|
47
47
|
setSelected(detail);
|
|
48
48
|
} catch (err) {
|
|
49
|
-
setError(err instanceof Error ? err.message : '
|
|
49
|
+
setError(err instanceof Error ? err.message : t('samples.reviewFailed'));
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
@@ -122,11 +122,11 @@ export function SamplesPage() {
|
|
|
122
122
|
</div>
|
|
123
123
|
<article>
|
|
124
124
|
<h4>{t('samples.badAttempt')}</h4>
|
|
125
|
-
<pre>{selected.badAttempt.rawText || selected.badAttempt.sanitizedText}</pre>
|
|
125
|
+
<pre className="sample-pre">{selected.badAttempt.rawText || selected.badAttempt.sanitizedText}</pre>
|
|
126
126
|
</article>
|
|
127
127
|
<article>
|
|
128
128
|
<h4>{t('samples.userCorrection')}</h4>
|
|
129
|
-
<pre>{selected.userCorrection.rawText}</pre>
|
|
129
|
+
<pre className="sample-pre">{selected.userCorrection.rawText}</pre>
|
|
130
130
|
</article>
|
|
131
131
|
<article>
|
|
132
132
|
<h4>{t('samples.recoveryToolSpan')}</h4>
|