claude-code-workflow 6.3.26 → 6.3.28
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/.claude/CLAUDE.md +7 -1
- package/.claude/agents/action-planning-agent.md +1 -0
- package/.claude/agents/cli-discuss-agent.md +391 -0
- package/.claude/agents/cli-execution-agent.md +2 -0
- package/.claude/agents/cli-explore-agent.md +2 -1
- package/.claude/agents/cli-lite-planning-agent.md +1 -0
- package/.claude/agents/cli-planning-agent.md +1 -0
- package/.claude/agents/code-developer.md +1 -0
- package/.claude/agents/conceptual-planning-agent.md +2 -0
- package/.claude/agents/context-search-agent.md +1 -0
- package/.claude/agents/debug-explore-agent.md +2 -0
- package/.claude/agents/doc-generator.md +1 -0
- package/.claude/agents/issue-plan-agent.md +2 -1
- package/.claude/agents/issue-queue-agent.md +2 -1
- package/.claude/agents/memory-bridge.md +2 -0
- package/.claude/agents/test-context-search-agent.md +2 -0
- package/.claude/agents/test-fix-agent.md +1 -0
- package/.claude/agents/ui-design-agent.md +2 -0
- package/.claude/agents/universal-executor.md +1 -0
- package/.claude/commands/issue/execute.md +141 -163
- package/.claude/commands/workflow/lite-lite-lite.md +798 -0
- package/.claude/commands/workflow/multi-cli-plan.md +510 -0
- package/.claude/skills/ccw/SKILL.md +262 -372
- package/.claude/skills/ccw/command.json +547 -0
- package/.claude/skills/ccw-help/SKILL.md +46 -107
- package/.claude/skills/ccw-help/command.json +511 -0
- package/.claude/skills/skill-tuning/SKILL.md +303 -0
- package/.claude/skills/skill-tuning/phases/actions/action-abort.md +164 -0
- package/.claude/skills/skill-tuning/phases/actions/action-analyze-requirements.md +406 -0
- package/.claude/skills/skill-tuning/phases/actions/action-apply-fix.md +206 -0
- package/.claude/skills/skill-tuning/phases/actions/action-complete.md +195 -0
- package/.claude/skills/skill-tuning/phases/actions/action-diagnose-agent.md +317 -0
- package/.claude/skills/skill-tuning/phases/actions/action-diagnose-context.md +243 -0
- package/.claude/skills/skill-tuning/phases/actions/action-diagnose-dataflow.md +318 -0
- package/.claude/skills/skill-tuning/phases/actions/action-diagnose-docs.md +299 -0
- package/.claude/skills/skill-tuning/phases/actions/action-diagnose-memory.md +269 -0
- package/.claude/skills/skill-tuning/phases/actions/action-diagnose-token-consumption.md +200 -0
- package/.claude/skills/skill-tuning/phases/actions/action-gemini-analysis.md +322 -0
- package/.claude/skills/skill-tuning/phases/actions/action-generate-report.md +228 -0
- package/.claude/skills/skill-tuning/phases/actions/action-init.md +149 -0
- package/.claude/skills/skill-tuning/phases/actions/action-propose-fixes.md +317 -0
- package/.claude/skills/skill-tuning/phases/actions/action-verify.md +222 -0
- package/.claude/skills/skill-tuning/phases/orchestrator.md +377 -0
- package/.claude/skills/skill-tuning/phases/state-schema.md +378 -0
- package/.claude/skills/skill-tuning/specs/category-mappings.json +284 -0
- package/.claude/skills/skill-tuning/specs/dimension-mapping.md +212 -0
- package/.claude/skills/skill-tuning/specs/problem-taxonomy.md +318 -0
- package/.claude/skills/skill-tuning/specs/quality-gates.md +263 -0
- package/.claude/skills/skill-tuning/specs/skill-authoring-principles.md +189 -0
- package/.claude/skills/skill-tuning/specs/tuning-strategies.md +1537 -0
- package/.claude/skills/skill-tuning/templates/diagnosis-report.md +153 -0
- package/.claude/skills/skill-tuning/templates/fix-proposal.md +204 -0
- package/.claude/workflows/cli-templates/schemas/multi-cli-discussion-schema.json +421 -0
- package/.claude/workflows/cli-tools-usage.md +0 -41
- package/ccw/dist/core/auth/csrf-middleware.d.ts.map +1 -1
- package/ccw/dist/core/auth/csrf-middleware.js +3 -1
- package/ccw/dist/core/auth/csrf-middleware.js.map +1 -1
- package/ccw/dist/core/data-aggregator.d.ts +2 -0
- package/ccw/dist/core/data-aggregator.d.ts.map +1 -1
- package/ccw/dist/core/data-aggregator.js +5 -2
- package/ccw/dist/core/data-aggregator.js.map +1 -1
- package/ccw/dist/core/lite-scanner.d.ts +2 -1
- package/ccw/dist/core/lite-scanner.d.ts.map +1 -1
- package/ccw/dist/core/lite-scanner.js +295 -6
- package/ccw/dist/core/lite-scanner.js.map +1 -1
- package/ccw/dist/core/routes/codexlens/config-handlers.d.ts.map +1 -1
- package/ccw/dist/core/routes/codexlens/config-handlers.js +5 -5
- package/ccw/dist/core/routes/codexlens/config-handlers.js.map +1 -1
- package/ccw/dist/core/routes/session-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/session-routes.js +166 -48
- package/ccw/dist/core/routes/session-routes.js.map +1 -1
- package/ccw/dist/core/routes/system-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/system-routes.js +87 -0
- package/ccw/dist/core/routes/system-routes.js.map +1 -1
- package/ccw/dist/core/server.js +2 -2
- package/ccw/dist/core/server.js.map +1 -1
- package/ccw/scripts/IMPLEMENTATION-SUMMARY.md +226 -0
- package/ccw/scripts/QUICK-REFERENCE.md +135 -0
- package/ccw/scripts/README-memory-embedder.md +157 -0
- package/ccw/scripts/__pycache__/memory_embedder.cpython-313.pyc +0 -0
- package/ccw/scripts/__pycache__/test_memory_embedder.cpython-313-pytest-8.4.2.pyc +0 -0
- package/ccw/scripts/memory-embedder-example.ts +184 -0
- package/ccw/scripts/memory_embedder.py +428 -0
- package/ccw/scripts/test_memory_embedder.py +245 -0
- package/ccw/src/core/auth/csrf-middleware.ts +3 -1
- package/ccw/src/core/data-aggregator.ts +7 -2
- package/ccw/src/core/lite-scanner.ts +440 -6
- package/ccw/src/core/routes/codexlens/config-handlers.ts +12 -9
- package/ccw/src/core/routes/session-routes.ts +201 -48
- package/ccw/src/core/routes/system-routes.ts +102 -0
- package/ccw/src/core/server.ts +2 -2
- package/ccw/src/templates/dashboard-css/01-base.css +8 -0
- package/ccw/src/templates/dashboard-css/02-session.css +81 -0
- package/ccw/src/templates/dashboard-css/04-lite-tasks.css +2442 -0
- package/ccw/src/templates/dashboard-css/21-cli-toolmgmt.css +157 -0
- package/ccw/src/templates/dashboard-css/32-issue-manager.css +23 -0
- package/ccw/src/templates/dashboard-js/components/cli-stream-viewer.js +38 -4
- package/ccw/src/templates/dashboard-js/components/hook-manager.js +38 -13
- package/ccw/src/templates/dashboard-js/components/navigation.js +24 -4
- package/ccw/src/templates/dashboard-js/i18n.js +194 -6
- package/ccw/src/templates/dashboard-js/views/api-settings.js +32 -0
- package/ccw/src/templates/dashboard-js/views/claude-manager.js +44 -3
- package/ccw/src/templates/dashboard-js/views/cli-manager.js +303 -31
- package/ccw/src/templates/dashboard-js/views/history.js +44 -6
- package/ccw/src/templates/dashboard-js/views/home.js +1 -0
- package/ccw/src/templates/dashboard-js/views/issue-manager.js +54 -7
- package/ccw/src/templates/dashboard-js/views/lite-tasks.js +1817 -4
- package/ccw/src/templates/dashboard.html +5 -0
- package/package.json +2 -1
- package/.claude/skills/ccw/index/command-capabilities.json +0 -127
- package/.claude/skills/ccw/index/intent-rules.json +0 -136
- package/.claude/skills/ccw/index/workflow-chains.json +0 -451
- package/.claude/skills/ccw/phases/actions/bugfix.md +0 -218
- package/.claude/skills/ccw/phases/actions/coupled.md +0 -194
- package/.claude/skills/ccw/phases/actions/docs.md +0 -93
- package/.claude/skills/ccw/phases/actions/full.md +0 -154
- package/.claude/skills/ccw/phases/actions/issue.md +0 -201
- package/.claude/skills/ccw/phases/actions/rapid.md +0 -104
- package/.claude/skills/ccw/phases/actions/review-fix.md +0 -84
- package/.claude/skills/ccw/phases/actions/tdd.md +0 -66
- package/.claude/skills/ccw/phases/actions/ui.md +0 -79
- package/.claude/skills/ccw/phases/orchestrator.md +0 -435
- package/.claude/skills/ccw/specs/intent-classification.md +0 -336
- package/.claude/skills/ccw-help/index/all-agents.json +0 -82
- package/.claude/skills/ccw-help/index/all-commands.json +0 -882
- package/.claude/skills/ccw-help/index/by-category.json +0 -914
- package/.claude/skills/ccw-help/index/by-use-case.json +0 -896
- package/.claude/skills/ccw-help/index/command-relationships.json +0 -160
- package/.claude/skills/ccw-help/index/essential-commands.json +0 -112
|
@@ -7,9 +7,17 @@ function renderLiteTasks() {
|
|
|
7
7
|
const container = document.getElementById('mainContent');
|
|
8
8
|
|
|
9
9
|
const liteTasks = workflowData.liteTasks || {};
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
let sessions;
|
|
11
|
+
|
|
12
|
+
if (currentLiteType === 'lite-plan') {
|
|
13
|
+
sessions = liteTasks.litePlan || [];
|
|
14
|
+
} else if (currentLiteType === 'lite-fix') {
|
|
15
|
+
sessions = liteTasks.liteFix || [];
|
|
16
|
+
} else if (currentLiteType === 'multi-cli-plan') {
|
|
17
|
+
sessions = liteTasks.multiCliPlan || [];
|
|
18
|
+
} else {
|
|
19
|
+
sessions = [];
|
|
20
|
+
}
|
|
13
21
|
|
|
14
22
|
if (sessions.length === 0) {
|
|
15
23
|
container.innerHTML = `
|
|
@@ -23,7 +31,12 @@ function renderLiteTasks() {
|
|
|
23
31
|
return;
|
|
24
32
|
}
|
|
25
33
|
|
|
26
|
-
|
|
34
|
+
// Render based on type
|
|
35
|
+
if (currentLiteType === 'multi-cli-plan') {
|
|
36
|
+
container.innerHTML = `<div class="sessions-grid">${sessions.map(session => renderMultiCliCard(session)).join('')}</div>`;
|
|
37
|
+
} else {
|
|
38
|
+
container.innerHTML = `<div class="sessions-grid">${sessions.map(session => renderLiteTaskCard(session)).join('')}</div>`;
|
|
39
|
+
}
|
|
27
40
|
|
|
28
41
|
// Initialize Lucide icons
|
|
29
42
|
if (typeof lucide !== 'undefined') lucide.createIcons();
|
|
@@ -68,6 +81,1806 @@ function renderLiteTaskCard(session) {
|
|
|
68
81
|
`;
|
|
69
82
|
}
|
|
70
83
|
|
|
84
|
+
// ============================================
|
|
85
|
+
// MULTI-CLI PLAN VIEW
|
|
86
|
+
// ============================================
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Render a card for multi-cli-plan session
|
|
90
|
+
* Shows: Session ID, round count, topic title, status, created date
|
|
91
|
+
*/
|
|
92
|
+
function renderMultiCliCard(session) {
|
|
93
|
+
const sessionKey = `multi-cli-${session.id}`.replace(/[^a-zA-Z0-9-]/g, '-');
|
|
94
|
+
liteTaskDataStore[sessionKey] = session;
|
|
95
|
+
|
|
96
|
+
// Extract info from latest synthesis or metadata
|
|
97
|
+
const metadata = session.metadata || {};
|
|
98
|
+
const latestSynthesis = session.latestSynthesis || session.discussionTopic || {};
|
|
99
|
+
const roundCount = metadata.roundId || session.roundCount || 1;
|
|
100
|
+
const topicTitle = getI18nText(latestSynthesis.title) || session.topicTitle || 'Discussion Topic';
|
|
101
|
+
const status = latestSynthesis.status || session.status || 'analyzing';
|
|
102
|
+
const createdAt = metadata.timestamp || session.createdAt || '';
|
|
103
|
+
|
|
104
|
+
// Status badge color mapping
|
|
105
|
+
const statusColors = {
|
|
106
|
+
'decided': 'success',
|
|
107
|
+
'converged': 'success',
|
|
108
|
+
'plan_generated': 'success',
|
|
109
|
+
'completed': 'success',
|
|
110
|
+
'exploring': 'info',
|
|
111
|
+
'initialized': 'info',
|
|
112
|
+
'analyzing': 'warning',
|
|
113
|
+
'debating': 'warning',
|
|
114
|
+
'blocked': 'error',
|
|
115
|
+
'conflict': 'error'
|
|
116
|
+
};
|
|
117
|
+
const statusColor = statusColors[status] || 'default';
|
|
118
|
+
|
|
119
|
+
return `
|
|
120
|
+
<div class="session-card multi-cli-card" onclick="showMultiCliDetailPage('${sessionKey}')" style="cursor: pointer;">
|
|
121
|
+
<div class="session-header">
|
|
122
|
+
<div class="session-title">${escapeHtml(session.id)}</div>
|
|
123
|
+
<span class="session-status multi-cli-plan">
|
|
124
|
+
<i data-lucide="messages-square" class="w-3 h-3 inline"></i> ${t('lite.multiCli') || 'Multi-CLI'}
|
|
125
|
+
</span>
|
|
126
|
+
</div>
|
|
127
|
+
<div class="session-body">
|
|
128
|
+
<div class="multi-cli-topic">
|
|
129
|
+
<i data-lucide="message-circle" class="w-4 h-4 inline mr-1"></i>
|
|
130
|
+
<span class="topic-title">${escapeHtml(topicTitle)}</span>
|
|
131
|
+
</div>
|
|
132
|
+
<div class="session-meta">
|
|
133
|
+
<span class="session-meta-item"><i data-lucide="calendar" class="w-3.5 h-3.5 inline mr-1"></i>${formatDate(createdAt)}</span>
|
|
134
|
+
<span class="session-meta-item"><i data-lucide="repeat" class="w-3.5 h-3.5 inline mr-1"></i>${roundCount} ${t('multiCli.rounds') || 'rounds'}</span>
|
|
135
|
+
<span class="session-meta-item status-badge ${statusColor}"><i data-lucide="activity" class="w-3.5 h-3.5 inline mr-1"></i>${escapeHtml(status)}</span>
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
</div>
|
|
139
|
+
`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Get text from i18n label object (supports {en, zh} format)
|
|
144
|
+
*/
|
|
145
|
+
function getI18nText(label) {
|
|
146
|
+
if (!label) return '';
|
|
147
|
+
if (typeof label === 'string') return label;
|
|
148
|
+
// Return based on current language or default to English
|
|
149
|
+
const lang = window.currentLanguage || 'en';
|
|
150
|
+
return label[lang] || label.en || label.zh || '';
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ============================================
|
|
154
|
+
// SYNTHESIS DATA TRANSFORMATION HELPERS
|
|
155
|
+
// ============================================
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Extract files from synthesis solutions[].implementation_plan.tasks[].files
|
|
159
|
+
* Returns object with fileTree and impactSummary arrays
|
|
160
|
+
*/
|
|
161
|
+
function extractFilesFromSynthesis(synthesis) {
|
|
162
|
+
if (!synthesis || !synthesis.solutions) {
|
|
163
|
+
return { fileTree: [], impactSummary: [], dependencyGraph: [] };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const fileSet = new Set();
|
|
167
|
+
const impactMap = new Map();
|
|
168
|
+
|
|
169
|
+
synthesis.solutions.forEach(solution => {
|
|
170
|
+
const tasks = solution.implementation_plan?.tasks || [];
|
|
171
|
+
tasks.forEach(task => {
|
|
172
|
+
const files = task.files || [];
|
|
173
|
+
files.forEach(filePath => {
|
|
174
|
+
fileSet.add(filePath);
|
|
175
|
+
// Build impact summary based on task context
|
|
176
|
+
if (!impactMap.has(filePath)) {
|
|
177
|
+
impactMap.set(filePath, {
|
|
178
|
+
filePath: filePath,
|
|
179
|
+
score: 'medium',
|
|
180
|
+
reasoning: { en: `Part of ${solution.title?.en || solution.id} implementation`, zh: `${solution.title?.zh || solution.id} 实现的一部分` }
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// Convert to fileTree format (flat list with file type)
|
|
188
|
+
const fileTree = Array.from(fileSet).map(path => ({
|
|
189
|
+
path: path,
|
|
190
|
+
type: 'file',
|
|
191
|
+
modificationStatus: 'modified',
|
|
192
|
+
impactScore: 'medium'
|
|
193
|
+
}));
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
fileTree: fileTree,
|
|
197
|
+
impactSummary: Array.from(impactMap.values()),
|
|
198
|
+
dependencyGraph: []
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Extract planning data from synthesis solutions[].implementation_plan
|
|
204
|
+
* Builds planning object with functional requirements format
|
|
205
|
+
*/
|
|
206
|
+
function extractPlanningFromSynthesis(synthesis) {
|
|
207
|
+
if (!synthesis || !synthesis.solutions) {
|
|
208
|
+
return { functional: [], nonFunctional: [], acceptanceCriteria: [] };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const functional = [];
|
|
212
|
+
const acceptanceCriteria = [];
|
|
213
|
+
let reqCounter = 1;
|
|
214
|
+
let acCounter = 1;
|
|
215
|
+
|
|
216
|
+
synthesis.solutions.forEach(solution => {
|
|
217
|
+
const plan = solution.implementation_plan;
|
|
218
|
+
if (!plan) return;
|
|
219
|
+
|
|
220
|
+
// Extract approach as functional requirement
|
|
221
|
+
if (plan.approach) {
|
|
222
|
+
functional.push({
|
|
223
|
+
id: `FR-${String(reqCounter++).padStart(3, '0')}`,
|
|
224
|
+
description: plan.approach,
|
|
225
|
+
priority: solution.feasibility?.score >= 0.8 ? 'high' : 'medium',
|
|
226
|
+
source: solution.id
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Extract tasks as acceptance criteria
|
|
231
|
+
const tasks = plan.tasks || [];
|
|
232
|
+
tasks.forEach(task => {
|
|
233
|
+
acceptanceCriteria.push({
|
|
234
|
+
id: `AC-${String(acCounter++).padStart(3, '0')}`,
|
|
235
|
+
description: task.title || { en: task.id, zh: task.id },
|
|
236
|
+
isMet: false
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
functional: functional,
|
|
243
|
+
nonFunctional: [],
|
|
244
|
+
acceptanceCriteria: acceptanceCriteria
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Extract decision data from synthesis solutions
|
|
250
|
+
* Sorts by feasibility score, returns highest as selected, rest as rejected
|
|
251
|
+
*/
|
|
252
|
+
function extractDecisionFromSynthesis(synthesis) {
|
|
253
|
+
if (!synthesis || !synthesis.solutions || synthesis.solutions.length === 0) {
|
|
254
|
+
return {};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Sort solutions by feasibility score (highest first)
|
|
258
|
+
const sortedSolutions = [...synthesis.solutions].sort((a, b) => {
|
|
259
|
+
const scoreA = a.feasibility?.score || 0;
|
|
260
|
+
const scoreB = b.feasibility?.score || 0;
|
|
261
|
+
return scoreB - scoreA;
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
const selectedSolution = sortedSolutions[0];
|
|
265
|
+
const rejectedAlternatives = sortedSolutions.slice(1).map(sol => ({
|
|
266
|
+
...sol,
|
|
267
|
+
rejectionReason: sol.cons?.length > 0 ? sol.cons[0] : { en: 'Lower feasibility score', zh: '可行性评分较低' }
|
|
268
|
+
}));
|
|
269
|
+
|
|
270
|
+
// Calculate confidence from convergence level
|
|
271
|
+
let confidenceScore = 0.5;
|
|
272
|
+
if (synthesis.convergence) {
|
|
273
|
+
const level = synthesis.convergence.level;
|
|
274
|
+
if (level === 'high' || level === 'converged') confidenceScore = 0.9;
|
|
275
|
+
else if (level === 'medium') confidenceScore = 0.7;
|
|
276
|
+
else if (level === 'low') confidenceScore = 0.4;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return {
|
|
280
|
+
status: synthesis.convergence?.recommendation || 'pending',
|
|
281
|
+
summary: synthesis.convergence?.summary || {},
|
|
282
|
+
selectedSolution: selectedSolution,
|
|
283
|
+
rejectedAlternatives: rejectedAlternatives,
|
|
284
|
+
confidenceScore: confidenceScore
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Extract timeline data from synthesis convergence and cross_verification
|
|
290
|
+
* Builds timeline array with events from discussion process
|
|
291
|
+
*/
|
|
292
|
+
function extractTimelineFromSynthesis(synthesis) {
|
|
293
|
+
if (!synthesis) {
|
|
294
|
+
return [];
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const timeline = [];
|
|
298
|
+
const now = new Date().toISOString();
|
|
299
|
+
|
|
300
|
+
// Add convergence summary as decision event
|
|
301
|
+
if (synthesis.convergence?.summary) {
|
|
302
|
+
timeline.push({
|
|
303
|
+
type: 'decision',
|
|
304
|
+
timestamp: now,
|
|
305
|
+
summary: synthesis.convergence.summary,
|
|
306
|
+
contributor: { name: 'Synthesis', id: 'synthesis' },
|
|
307
|
+
reversibility: synthesis.convergence.recommendation === 'proceed' ? 'irreversible' : 'reversible'
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Add cross-verification agreements as agreement events
|
|
312
|
+
const agreements = synthesis.cross_verification?.agreements || [];
|
|
313
|
+
agreements.forEach((agreement, idx) => {
|
|
314
|
+
timeline.push({
|
|
315
|
+
type: 'agreement',
|
|
316
|
+
timestamp: now,
|
|
317
|
+
summary: typeof agreement === 'string' ? { en: agreement, zh: agreement } : agreement,
|
|
318
|
+
contributor: { name: 'Cross-Verification', id: 'cross-verify' },
|
|
319
|
+
evidence: []
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
// Add cross-verification disagreements as disagreement events
|
|
324
|
+
const disagreements = synthesis.cross_verification?.disagreements || [];
|
|
325
|
+
disagreements.forEach((disagreement, idx) => {
|
|
326
|
+
timeline.push({
|
|
327
|
+
type: 'disagreement',
|
|
328
|
+
timestamp: now,
|
|
329
|
+
summary: typeof disagreement === 'string' ? { en: disagreement, zh: disagreement } : disagreement,
|
|
330
|
+
contributor: { name: 'Cross-Verification', id: 'cross-verify' },
|
|
331
|
+
evidence: []
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
// Add solutions as proposal events
|
|
336
|
+
const solutions = synthesis.solutions || [];
|
|
337
|
+
solutions.forEach(solution => {
|
|
338
|
+
timeline.push({
|
|
339
|
+
type: 'proposal',
|
|
340
|
+
timestamp: now,
|
|
341
|
+
summary: solution.description || solution.title || {},
|
|
342
|
+
contributor: { name: solution.id, id: solution.id },
|
|
343
|
+
evidence: solution.pros?.map(p => ({ type: 'pro', description: p })) || []
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
return timeline;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Track multi-cli toolbar state
|
|
351
|
+
let isMultiCliToolbarExpanded = false;
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Show multi-cli detail page with tabs
|
|
355
|
+
*/
|
|
356
|
+
function showMultiCliDetailPage(sessionKey) {
|
|
357
|
+
const session = liteTaskDataStore[sessionKey];
|
|
358
|
+
if (!session) return;
|
|
359
|
+
|
|
360
|
+
currentView = 'multiCliDetail';
|
|
361
|
+
currentSessionDetailKey = sessionKey;
|
|
362
|
+
|
|
363
|
+
hideStatsAndCarousel();
|
|
364
|
+
|
|
365
|
+
const container = document.getElementById('mainContent');
|
|
366
|
+
const metadata = session.metadata || {};
|
|
367
|
+
const discussionTopic = session.discussionTopic || {};
|
|
368
|
+
const latestSynthesis = session.latestSynthesis || discussionTopic;
|
|
369
|
+
const roundCount = metadata.roundId || session.roundCount || 1;
|
|
370
|
+
const topicTitle = getI18nText(latestSynthesis.title) || session.topicTitle || 'Discussion Topic';
|
|
371
|
+
const status = latestSynthesis.status || session.status || 'analyzing';
|
|
372
|
+
|
|
373
|
+
container.innerHTML = `
|
|
374
|
+
<div class="session-detail-page multi-cli-detail-page multi-cli-detail-with-toolbar">
|
|
375
|
+
<!-- Main Content Area -->
|
|
376
|
+
<div class="multi-cli-main-content">
|
|
377
|
+
<!-- Header -->
|
|
378
|
+
<div class="detail-header">
|
|
379
|
+
<button class="btn-back" onclick="goBackToLiteTasks()">
|
|
380
|
+
<span class="back-icon">←</span>
|
|
381
|
+
<span>${t('multiCli.backToList') || 'Back to Multi-CLI Plan'}</span>
|
|
382
|
+
</button>
|
|
383
|
+
<div class="detail-title-row">
|
|
384
|
+
<h2 class="detail-session-id"><i data-lucide="messages-square" class="w-5 h-5 inline mr-2"></i> ${escapeHtml(session.id)}</h2>
|
|
385
|
+
<div class="detail-badges">
|
|
386
|
+
<span class="session-type-badge multi-cli-plan">multi-cli-plan</span>
|
|
387
|
+
<span class="session-status-badge ${status}">${escapeHtml(status)}</span>
|
|
388
|
+
</div>
|
|
389
|
+
</div>
|
|
390
|
+
</div>
|
|
391
|
+
|
|
392
|
+
<!-- Session Info Bar -->
|
|
393
|
+
<div class="detail-info-bar">
|
|
394
|
+
<div class="info-item">
|
|
395
|
+
<span class="info-label">${t('detail.created') || 'Created'}</span>
|
|
396
|
+
<span class="info-value">${formatDate(metadata.timestamp || session.createdAt)}</span>
|
|
397
|
+
</div>
|
|
398
|
+
<div class="info-item">
|
|
399
|
+
<span class="info-label">${t('multiCli.roundCount') || 'Rounds'}</span>
|
|
400
|
+
<span class="info-value">${roundCount}</span>
|
|
401
|
+
</div>
|
|
402
|
+
<div class="info-item">
|
|
403
|
+
<span class="info-label">${t('multiCli.topic') || 'Topic'}</span>
|
|
404
|
+
<span class="info-value">${escapeHtml(topicTitle)}</span>
|
|
405
|
+
</div>
|
|
406
|
+
</div>
|
|
407
|
+
|
|
408
|
+
<!-- Tab Navigation -->
|
|
409
|
+
<div class="detail-tabs">
|
|
410
|
+
<button class="detail-tab active" data-tab="planning" onclick="switchMultiCliDetailTab('planning')">
|
|
411
|
+
<span class="tab-icon"><i data-lucide="list-checks" class="w-4 h-4"></i></span>
|
|
412
|
+
<span class="tab-text">${t('multiCli.tab.planning') || 'Planning'}</span>
|
|
413
|
+
</button>
|
|
414
|
+
<button class="detail-tab" data-tab="discussion" onclick="switchMultiCliDetailTab('discussion')">
|
|
415
|
+
<span class="tab-icon"><i data-lucide="messages-square" class="w-4 h-4"></i></span>
|
|
416
|
+
<span class="tab-text">${t('multiCli.tab.discussion') || 'Discussion'}</span>
|
|
417
|
+
<span class="tab-count">${roundCount}</span>
|
|
418
|
+
</button>
|
|
419
|
+
<button class="detail-tab" data-tab="association" onclick="switchMultiCliDetailTab('association')">
|
|
420
|
+
<span class="tab-icon"><i data-lucide="link-2" class="w-4 h-4"></i></span>
|
|
421
|
+
<span class="tab-text">${t('multiCli.tab.association') || 'Association'}</span>
|
|
422
|
+
</button>
|
|
423
|
+
</div>
|
|
424
|
+
|
|
425
|
+
<!-- Tab Content -->
|
|
426
|
+
<div class="detail-tab-content" id="multiCliDetailTabContent">
|
|
427
|
+
${renderMultiCliPlanningTab(session)}
|
|
428
|
+
</div>
|
|
429
|
+
</div>
|
|
430
|
+
|
|
431
|
+
<!-- Right Toolbar -->
|
|
432
|
+
<div class="multi-cli-toolbar ${isMultiCliToolbarExpanded ? 'expanded' : 'collapsed'}" id="multiCliToolbar">
|
|
433
|
+
<button class="toolbar-toggle-btn" onclick="toggleMultiCliToolbar()" title="${t('multiCli.toolbar.toggle') || 'Toggle Task Panel'}">
|
|
434
|
+
<i data-lucide="${isMultiCliToolbarExpanded ? 'panel-right-close' : 'panel-right-open'}" class="w-5 h-5"></i>
|
|
435
|
+
</button>
|
|
436
|
+
<div class="toolbar-content">
|
|
437
|
+
${renderMultiCliToolbar(session)}
|
|
438
|
+
</div>
|
|
439
|
+
</div>
|
|
440
|
+
</div>
|
|
441
|
+
`;
|
|
442
|
+
|
|
443
|
+
// Initialize icons and collapsible sections
|
|
444
|
+
setTimeout(() => {
|
|
445
|
+
if (typeof lucide !== 'undefined') lucide.createIcons();
|
|
446
|
+
initCollapsibleSections(container);
|
|
447
|
+
}, 50);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Toggle multi-cli toolbar expanded/collapsed state
|
|
452
|
+
*/
|
|
453
|
+
function toggleMultiCliToolbar() {
|
|
454
|
+
isMultiCliToolbarExpanded = !isMultiCliToolbarExpanded;
|
|
455
|
+
const toolbar = document.getElementById('multiCliToolbar');
|
|
456
|
+
const toggleBtn = toolbar?.querySelector('.toolbar-toggle-btn i');
|
|
457
|
+
|
|
458
|
+
if (toolbar) {
|
|
459
|
+
toolbar.classList.toggle('expanded', isMultiCliToolbarExpanded);
|
|
460
|
+
toolbar.classList.toggle('collapsed', !isMultiCliToolbarExpanded);
|
|
461
|
+
|
|
462
|
+
// Update icon
|
|
463
|
+
if (toggleBtn) {
|
|
464
|
+
toggleBtn.setAttribute('data-lucide', isMultiCliToolbarExpanded ? 'panel-right-close' : 'panel-right-open');
|
|
465
|
+
if (typeof lucide !== 'undefined') lucide.createIcons();
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Render the multi-cli toolbar content
|
|
472
|
+
*/
|
|
473
|
+
function renderMultiCliToolbar(session) {
|
|
474
|
+
const plan = session.plan;
|
|
475
|
+
const tasks = plan?.tasks || [];
|
|
476
|
+
const taskCount = tasks.length;
|
|
477
|
+
|
|
478
|
+
let toolbarHtml = `
|
|
479
|
+
<div class="toolbar-header">
|
|
480
|
+
<h4 class="toolbar-title">
|
|
481
|
+
<i data-lucide="list-checks" class="w-4 h-4"></i>
|
|
482
|
+
<span>${t('multiCli.toolbar.tasks') || 'Tasks'}</span>
|
|
483
|
+
<span class="toolbar-count">${taskCount}</span>
|
|
484
|
+
</h4>
|
|
485
|
+
</div>
|
|
486
|
+
`;
|
|
487
|
+
|
|
488
|
+
// Quick Actions
|
|
489
|
+
toolbarHtml += `
|
|
490
|
+
<div class="toolbar-actions">
|
|
491
|
+
<button class="toolbar-action-btn" onclick="refreshMultiCliToolbar()" title="${t('multiCli.toolbar.refresh') || 'Refresh'}">
|
|
492
|
+
<i data-lucide="refresh-cw" class="w-4 h-4"></i>
|
|
493
|
+
</button>
|
|
494
|
+
<button class="toolbar-action-btn" onclick="exportMultiCliPlanJson()" title="${t('multiCli.toolbar.export') || 'Export JSON'}">
|
|
495
|
+
<i data-lucide="download" class="w-4 h-4"></i>
|
|
496
|
+
</button>
|
|
497
|
+
<button class="toolbar-action-btn" onclick="viewMultiCliRawJson()" title="${t('multiCli.toolbar.viewRaw') || 'View Raw Data'}">
|
|
498
|
+
<i data-lucide="code" class="w-4 h-4"></i>
|
|
499
|
+
</button>
|
|
500
|
+
</div>
|
|
501
|
+
`;
|
|
502
|
+
|
|
503
|
+
// Task List
|
|
504
|
+
if (tasks.length > 0) {
|
|
505
|
+
toolbarHtml += `
|
|
506
|
+
<div class="toolbar-task-list">
|
|
507
|
+
${tasks.map((task, idx) => {
|
|
508
|
+
const taskTitle = task.title || task.summary || `Task ${idx + 1}`;
|
|
509
|
+
const taskScope = task.scope || '';
|
|
510
|
+
const taskId = `task-${idx}`;
|
|
511
|
+
|
|
512
|
+
return `
|
|
513
|
+
<div class="toolbar-task-item" onclick="scrollToMultiCliTask(${idx})" data-task-idx="${idx}">
|
|
514
|
+
<span class="toolbar-task-num">#${idx + 1}</span>
|
|
515
|
+
<div class="toolbar-task-info">
|
|
516
|
+
<span class="toolbar-task-title" title="${escapeHtml(taskTitle)}">${escapeHtml(taskTitle)}</span>
|
|
517
|
+
${taskScope ? `<span class="toolbar-task-scope">${escapeHtml(taskScope)}</span>` : ''}
|
|
518
|
+
</div>
|
|
519
|
+
</div>
|
|
520
|
+
`;
|
|
521
|
+
}).join('')}
|
|
522
|
+
</div>
|
|
523
|
+
`;
|
|
524
|
+
} else {
|
|
525
|
+
toolbarHtml += `
|
|
526
|
+
<div class="toolbar-empty">
|
|
527
|
+
<i data-lucide="inbox" class="w-8 h-8"></i>
|
|
528
|
+
<span>${t('multiCli.toolbar.noTasks') || 'No tasks available'}</span>
|
|
529
|
+
</div>
|
|
530
|
+
`;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// Session Info
|
|
534
|
+
toolbarHtml += `
|
|
535
|
+
<div class="toolbar-session-info">
|
|
536
|
+
<div class="toolbar-info-item">
|
|
537
|
+
<span class="toolbar-info-label">${t('multiCli.toolbar.sessionId') || 'Session'}</span>
|
|
538
|
+
<span class="toolbar-info-value" title="${escapeHtml(session.id)}">${escapeHtml(session.id)}</span>
|
|
539
|
+
</div>
|
|
540
|
+
${plan?.summary ? `
|
|
541
|
+
<div class="toolbar-info-item">
|
|
542
|
+
<span class="toolbar-info-label">${t('multiCli.toolbar.summary') || 'Summary'}</span>
|
|
543
|
+
<span class="toolbar-info-value toolbar-summary" title="${escapeHtml(plan.summary)}">${escapeHtml(plan.summary)}</span>
|
|
544
|
+
</div>
|
|
545
|
+
` : ''}
|
|
546
|
+
</div>
|
|
547
|
+
`;
|
|
548
|
+
|
|
549
|
+
return toolbarHtml;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* Scroll to a specific task in the planning tab
|
|
554
|
+
*/
|
|
555
|
+
function scrollToMultiCliTask(taskIdx) {
|
|
556
|
+
// Switch to planning tab if not active
|
|
557
|
+
const planningTab = document.querySelector('.detail-tab[data-tab="planning"]');
|
|
558
|
+
if (planningTab && !planningTab.classList.contains('active')) {
|
|
559
|
+
switchMultiCliDetailTab('planning');
|
|
560
|
+
// Wait for tab content to render
|
|
561
|
+
setTimeout(() => scrollToTaskElement(taskIdx), 100);
|
|
562
|
+
} else {
|
|
563
|
+
scrollToTaskElement(taskIdx);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Scroll to task element in the DOM
|
|
569
|
+
*/
|
|
570
|
+
function scrollToTaskElement(taskIdx) {
|
|
571
|
+
const taskItems = document.querySelectorAll('.fix-task-summary-item');
|
|
572
|
+
if (taskItems[taskIdx]) {
|
|
573
|
+
taskItems[taskIdx].scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
574
|
+
// Highlight the task briefly
|
|
575
|
+
taskItems[taskIdx].classList.add('toolbar-highlight');
|
|
576
|
+
setTimeout(() => {
|
|
577
|
+
taskItems[taskIdx].classList.remove('toolbar-highlight');
|
|
578
|
+
}, 2000);
|
|
579
|
+
// Expand the collapsible if collapsed
|
|
580
|
+
const header = taskItems[taskIdx].querySelector('.collapsible-header');
|
|
581
|
+
const content = taskItems[taskIdx].querySelector('.collapsible-content');
|
|
582
|
+
if (header && content && content.classList.contains('collapsed')) {
|
|
583
|
+
header.click();
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* Refresh the toolbar content
|
|
590
|
+
*/
|
|
591
|
+
function refreshMultiCliToolbar() {
|
|
592
|
+
const session = liteTaskDataStore[currentSessionDetailKey];
|
|
593
|
+
if (!session) return;
|
|
594
|
+
|
|
595
|
+
const toolbarContent = document.querySelector('.toolbar-content');
|
|
596
|
+
if (toolbarContent) {
|
|
597
|
+
toolbarContent.innerHTML = renderMultiCliToolbar(session);
|
|
598
|
+
if (typeof lucide !== 'undefined') lucide.createIcons();
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Export plan.json content
|
|
604
|
+
*/
|
|
605
|
+
function exportMultiCliPlanJson() {
|
|
606
|
+
const session = liteTaskDataStore[currentSessionDetailKey];
|
|
607
|
+
if (!session || !session.plan) {
|
|
608
|
+
if (typeof showRefreshToast === 'function') {
|
|
609
|
+
showRefreshToast(t('multiCli.toolbar.noPlan') || 'No plan data available', 'warning');
|
|
610
|
+
}
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
const jsonStr = JSON.stringify(session.plan, null, 2);
|
|
615
|
+
const blob = new Blob([jsonStr], { type: 'application/json' });
|
|
616
|
+
const url = URL.createObjectURL(blob);
|
|
617
|
+
const a = document.createElement('a');
|
|
618
|
+
a.href = url;
|
|
619
|
+
a.download = `plan-${session.id}.json`;
|
|
620
|
+
document.body.appendChild(a);
|
|
621
|
+
a.click();
|
|
622
|
+
document.body.removeChild(a);
|
|
623
|
+
URL.revokeObjectURL(url);
|
|
624
|
+
|
|
625
|
+
if (typeof showRefreshToast === 'function') {
|
|
626
|
+
showRefreshToast(t('multiCli.toolbar.exported') || 'Plan exported successfully', 'success');
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/**
|
|
631
|
+
* View raw session JSON in modal
|
|
632
|
+
*/
|
|
633
|
+
function viewMultiCliRawJson() {
|
|
634
|
+
const session = liteTaskDataStore[currentSessionDetailKey];
|
|
635
|
+
if (!session) return;
|
|
636
|
+
|
|
637
|
+
// Reuse existing JSON modal pattern
|
|
638
|
+
const overlay = document.createElement('div');
|
|
639
|
+
overlay.className = 'json-modal-overlay active';
|
|
640
|
+
overlay.innerHTML = `
|
|
641
|
+
<div class="json-modal">
|
|
642
|
+
<div class="json-modal-header">
|
|
643
|
+
<div class="json-modal-title">
|
|
644
|
+
<span class="session-id-badge">${escapeHtml(session.id)}</span>
|
|
645
|
+
<span>${t('multiCli.toolbar.rawData') || 'Raw Session Data'}</span>
|
|
646
|
+
</div>
|
|
647
|
+
<button class="json-modal-close" onclick="closeJsonModal(this)">×</button>
|
|
648
|
+
</div>
|
|
649
|
+
<div class="json-modal-body">
|
|
650
|
+
<pre class="json-modal-content">${escapeHtml(JSON.stringify(session, null, 2))}</pre>
|
|
651
|
+
</div>
|
|
652
|
+
<div class="json-modal-footer">
|
|
653
|
+
<button class="btn-copy-json" onclick="copyJsonToClipboard(this)">${t('action.copy') || 'Copy to Clipboard'}</button>
|
|
654
|
+
</div>
|
|
655
|
+
</div>
|
|
656
|
+
`;
|
|
657
|
+
document.body.appendChild(overlay);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* Switch between multi-cli detail tabs
|
|
662
|
+
*/
|
|
663
|
+
function switchMultiCliDetailTab(tabName) {
|
|
664
|
+
// Update active tab
|
|
665
|
+
document.querySelectorAll('.detail-tab').forEach(tab => {
|
|
666
|
+
tab.classList.toggle('active', tab.dataset.tab === tabName);
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
const session = liteTaskDataStore[currentSessionDetailKey];
|
|
670
|
+
if (!session) return;
|
|
671
|
+
|
|
672
|
+
const contentArea = document.getElementById('multiCliDetailTabContent');
|
|
673
|
+
|
|
674
|
+
switch (tabName) {
|
|
675
|
+
case 'planning':
|
|
676
|
+
contentArea.innerHTML = renderMultiCliPlanningTab(session);
|
|
677
|
+
break;
|
|
678
|
+
case 'discussion':
|
|
679
|
+
contentArea.innerHTML = renderMultiCliDiscussionSection(session);
|
|
680
|
+
break;
|
|
681
|
+
case 'association':
|
|
682
|
+
contentArea.innerHTML = renderMultiCliAssociationSection(session);
|
|
683
|
+
break;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// Re-initialize after tab switch
|
|
687
|
+
setTimeout(() => {
|
|
688
|
+
if (typeof lucide !== 'undefined') lucide.createIcons();
|
|
689
|
+
initCollapsibleSections(contentArea);
|
|
690
|
+
}, 50);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// ============================================
|
|
694
|
+
// MULTI-CLI TAB RENDERERS
|
|
695
|
+
// ============================================
|
|
696
|
+
|
|
697
|
+
/**
|
|
698
|
+
* Render Discussion Topic tab
|
|
699
|
+
* Shows: title, description, scope, keyQuestions, status, tags
|
|
700
|
+
*/
|
|
701
|
+
function renderMultiCliTopicTab(session) {
|
|
702
|
+
const topic = session.discussionTopic || session.latestSynthesis?.discussionTopic || {};
|
|
703
|
+
|
|
704
|
+
if (!topic || Object.keys(topic).length === 0) {
|
|
705
|
+
return `
|
|
706
|
+
<div class="tab-empty-state">
|
|
707
|
+
<div class="empty-icon"><i data-lucide="message-circle" class="w-12 h-12"></i></div>
|
|
708
|
+
<div class="empty-title">${t('multiCli.empty.topic') || 'No Discussion Topic'}</div>
|
|
709
|
+
<div class="empty-text">${t('multiCli.empty.topicText') || 'No discussion topic data available for this session.'}</div>
|
|
710
|
+
</div>
|
|
711
|
+
`;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
const title = getI18nText(topic.title) || 'Untitled';
|
|
715
|
+
const description = getI18nText(topic.description) || '';
|
|
716
|
+
const scope = topic.scope || {};
|
|
717
|
+
const keyQuestions = topic.keyQuestions || [];
|
|
718
|
+
const status = topic.status || 'unknown';
|
|
719
|
+
const tags = topic.tags || [];
|
|
720
|
+
|
|
721
|
+
let sections = [];
|
|
722
|
+
|
|
723
|
+
// Title and Description
|
|
724
|
+
sections.push(`
|
|
725
|
+
<div class="multi-cli-topic-section">
|
|
726
|
+
<h3 class="multi-cli-topic-title">${escapeHtml(title)}</h3>
|
|
727
|
+
${description ? `<p class="multi-cli-topic-description">${escapeHtml(description)}</p>` : ''}
|
|
728
|
+
<div class="topic-meta">
|
|
729
|
+
<span class="multi-cli-status ${status}">${escapeHtml(status)}</span>
|
|
730
|
+
${tags.length ? tags.map(tag => `<span class="tag-badge">${escapeHtml(tag)}</span>`).join('') : ''}
|
|
731
|
+
</div>
|
|
732
|
+
</div>
|
|
733
|
+
`);
|
|
734
|
+
|
|
735
|
+
// Scope (included/excluded)
|
|
736
|
+
if (scope.included?.length || scope.excluded?.length) {
|
|
737
|
+
sections.push(`
|
|
738
|
+
<div class="multi-cli-section scope-section">
|
|
739
|
+
<h4 class="section-title"><i data-lucide="target" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.scope') || 'Scope'}</h4>
|
|
740
|
+
${scope.included?.length ? `
|
|
741
|
+
<div class="scope-included">
|
|
742
|
+
<strong>${t('multiCli.scope.included') || 'Included'}:</strong>
|
|
743
|
+
<ul class="scope-list">
|
|
744
|
+
${scope.included.map(item => `<li>${escapeHtml(getI18nText(item))}</li>`).join('')}
|
|
745
|
+
</ul>
|
|
746
|
+
</div>
|
|
747
|
+
` : ''}
|
|
748
|
+
${scope.excluded?.length ? `
|
|
749
|
+
<div class="scope-excluded">
|
|
750
|
+
<strong>${t('multiCli.scope.excluded') || 'Excluded'}:</strong>
|
|
751
|
+
<ul class="scope-list excluded">
|
|
752
|
+
${scope.excluded.map(item => `<li>${escapeHtml(getI18nText(item))}</li>`).join('')}
|
|
753
|
+
</ul>
|
|
754
|
+
</div>
|
|
755
|
+
` : ''}
|
|
756
|
+
</div>
|
|
757
|
+
`);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// Key Questions
|
|
761
|
+
if (keyQuestions.length) {
|
|
762
|
+
sections.push(`
|
|
763
|
+
<div class="multi-cli-section questions-section">
|
|
764
|
+
<h4 class="section-title"><i data-lucide="help-circle" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.keyQuestions') || 'Key Questions'}</h4>
|
|
765
|
+
<ol class="key-questions-list">
|
|
766
|
+
${keyQuestions.map(q => `<li>${escapeHtml(getI18nText(q))}</li>`).join('')}
|
|
767
|
+
</ol>
|
|
768
|
+
</div>
|
|
769
|
+
`);
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
return `<div class="multi-cli-topic-tab">${sections.join('')}</div>`;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* Render Related Files tab
|
|
777
|
+
* Shows: fileTree, impactSummary
|
|
778
|
+
*/
|
|
779
|
+
function renderMultiCliFilesTab(session) {
|
|
780
|
+
// Use helper to extract files from synthesis data structure
|
|
781
|
+
const relatedFiles = extractFilesFromSynthesis(session.latestSynthesis);
|
|
782
|
+
|
|
783
|
+
if (!relatedFiles || (!relatedFiles.fileTree?.length && !relatedFiles.impactSummary?.length)) {
|
|
784
|
+
return `
|
|
785
|
+
<div class="tab-empty-state">
|
|
786
|
+
<div class="empty-icon"><i data-lucide="folder-tree" class="w-12 h-12"></i></div>
|
|
787
|
+
<div class="empty-title">${t('multiCli.empty.files') || 'No Related Files'}</div>
|
|
788
|
+
<div class="empty-text">${t('multiCli.empty.filesText') || 'No file analysis data available for this session.'}</div>
|
|
789
|
+
</div>
|
|
790
|
+
`;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
const fileTree = relatedFiles.fileTree || [];
|
|
794
|
+
const impactSummary = relatedFiles.impactSummary || [];
|
|
795
|
+
const dependencyGraph = relatedFiles.dependencyGraph || [];
|
|
796
|
+
|
|
797
|
+
let sections = [];
|
|
798
|
+
|
|
799
|
+
// File Tree
|
|
800
|
+
if (fileTree.length) {
|
|
801
|
+
sections.push(`
|
|
802
|
+
<div class="multi-cli-section file-tree-section collapsible-section">
|
|
803
|
+
<div class="collapsible-header">
|
|
804
|
+
<span class="collapse-icon">►</span>
|
|
805
|
+
<span class="section-label"><i data-lucide="folder-tree" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.fileTree') || 'File Tree'} (${fileTree.length})</span>
|
|
806
|
+
</div>
|
|
807
|
+
<div class="collapsible-content collapsed">
|
|
808
|
+
<div class="file-tree-list">
|
|
809
|
+
${renderFileTreeNodes(fileTree)}
|
|
810
|
+
</div>
|
|
811
|
+
</div>
|
|
812
|
+
</div>
|
|
813
|
+
`);
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
// Impact Summary
|
|
817
|
+
if (impactSummary.length) {
|
|
818
|
+
sections.push(`
|
|
819
|
+
<div class="multi-cli-section impact-section collapsible-section">
|
|
820
|
+
<div class="collapsible-header">
|
|
821
|
+
<span class="collapse-icon">►</span>
|
|
822
|
+
<span class="section-label"><i data-lucide="alert-triangle" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.impactSummary') || 'Impact Summary'} (${impactSummary.length})</span>
|
|
823
|
+
</div>
|
|
824
|
+
<div class="collapsible-content collapsed">
|
|
825
|
+
<div class="impact-list">
|
|
826
|
+
${impactSummary.map(impact => `
|
|
827
|
+
<div class="impact-item impact-${impact.score || 'medium'}">
|
|
828
|
+
<div class="impact-header">
|
|
829
|
+
<code class="impact-file">${escapeHtml(impact.filePath || '')}</code>
|
|
830
|
+
${impact.line ? `<span class="impact-line">:${impact.line}</span>` : ''}
|
|
831
|
+
<span class="impact-score ${impact.score || 'medium'}">${escapeHtml(impact.score || 'medium')}</span>
|
|
832
|
+
</div>
|
|
833
|
+
${impact.reasoning ? `<div class="impact-reason">${escapeHtml(getI18nText(impact.reasoning))}</div>` : ''}
|
|
834
|
+
</div>
|
|
835
|
+
`).join('')}
|
|
836
|
+
</div>
|
|
837
|
+
</div>
|
|
838
|
+
</div>
|
|
839
|
+
`);
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
// Dependency Graph
|
|
843
|
+
if (dependencyGraph.length) {
|
|
844
|
+
sections.push(`
|
|
845
|
+
<div class="multi-cli-section deps-section collapsible-section">
|
|
846
|
+
<div class="collapsible-header">
|
|
847
|
+
<span class="collapse-icon">►</span>
|
|
848
|
+
<span class="section-label"><i data-lucide="git-branch" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.dependencies') || 'Dependencies'} (${dependencyGraph.length})</span>
|
|
849
|
+
</div>
|
|
850
|
+
<div class="collapsible-content collapsed">
|
|
851
|
+
<div class="deps-list">
|
|
852
|
+
${dependencyGraph.map(edge => `
|
|
853
|
+
<div class="dep-edge">
|
|
854
|
+
<code>${escapeHtml(edge.source || '')}</code>
|
|
855
|
+
<span class="dep-arrow">→</span>
|
|
856
|
+
<code>${escapeHtml(edge.target || '')}</code>
|
|
857
|
+
<span class="dep-relationship">(${escapeHtml(edge.relationship || 'depends')})</span>
|
|
858
|
+
</div>
|
|
859
|
+
`).join('')}
|
|
860
|
+
</div>
|
|
861
|
+
</div>
|
|
862
|
+
</div>
|
|
863
|
+
`);
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
return sections.length ? `<div class="multi-cli-files-tab">${sections.join('')}</div>` : `
|
|
867
|
+
<div class="tab-empty-state">
|
|
868
|
+
<div class="empty-icon"><i data-lucide="folder-tree" class="w-12 h-12"></i></div>
|
|
869
|
+
<div class="empty-title">${t('multiCli.empty.files') || 'No Related Files'}</div>
|
|
870
|
+
</div>
|
|
871
|
+
`;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
/**
|
|
875
|
+
* Render file tree nodes recursively
|
|
876
|
+
*/
|
|
877
|
+
function renderFileTreeNodes(nodes, depth = 0) {
|
|
878
|
+
return nodes.map(node => {
|
|
879
|
+
const indent = depth * 16;
|
|
880
|
+
const isDir = node.type === 'directory';
|
|
881
|
+
const icon = isDir ? 'folder' : 'file';
|
|
882
|
+
const modStatus = node.modificationStatus || 'unchanged';
|
|
883
|
+
const impactScore = node.impactScore || '';
|
|
884
|
+
|
|
885
|
+
let html = `
|
|
886
|
+
<div class="file-tree-node" style="margin-left: ${indent}px;">
|
|
887
|
+
<i data-lucide="${icon}" class="w-4 h-4 inline mr-1 file-icon ${modStatus}"></i>
|
|
888
|
+
<span class="file-path ${modStatus}">${escapeHtml(node.path || '')}</span>
|
|
889
|
+
${modStatus !== 'unchanged' ? `<span class="mod-status ${modStatus}">${modStatus}</span>` : ''}
|
|
890
|
+
${impactScore ? `<span class="impact-badge ${impactScore}">${impactScore}</span>` : ''}
|
|
891
|
+
</div>
|
|
892
|
+
`;
|
|
893
|
+
|
|
894
|
+
if (node.children?.length) {
|
|
895
|
+
html += renderFileTreeNodes(node.children, depth + 1);
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
return html;
|
|
899
|
+
}).join('');
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
/**
|
|
903
|
+
* Render Planning tab - displays session.plan (plan.json content)
|
|
904
|
+
* Reuses renderLitePlanTab style with Summary, Approach, Focus Paths, Metadata, and Tasks
|
|
905
|
+
*/
|
|
906
|
+
function renderMultiCliPlanningTab(session) {
|
|
907
|
+
const plan = session.plan;
|
|
908
|
+
|
|
909
|
+
if (!plan) {
|
|
910
|
+
return `
|
|
911
|
+
<div class="tab-empty-state">
|
|
912
|
+
<div class="empty-icon"><i data-lucide="ruler" class="w-12 h-12"></i></div>
|
|
913
|
+
<div class="empty-title">${t('multiCli.empty.planning') || 'No Planning Data'}</div>
|
|
914
|
+
<div class="empty-text">${t('multiCli.empty.planningText') || 'No plan.json found for this session.'}</div>
|
|
915
|
+
</div>
|
|
916
|
+
`;
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
return `
|
|
920
|
+
<div class="plan-tab-content">
|
|
921
|
+
<!-- Summary -->
|
|
922
|
+
${plan.summary ? `
|
|
923
|
+
<div class="plan-section">
|
|
924
|
+
<h4 class="plan-section-title"><i data-lucide="clipboard-list" class="w-4 h-4 inline mr-1"></i> Summary</h4>
|
|
925
|
+
<p class="plan-summary-text">${escapeHtml(plan.summary)}</p>
|
|
926
|
+
</div>
|
|
927
|
+
` : ''}
|
|
928
|
+
|
|
929
|
+
<!-- Root Cause (fix-plan specific) -->
|
|
930
|
+
${plan.root_cause ? `
|
|
931
|
+
<div class="plan-section">
|
|
932
|
+
<h4 class="plan-section-title"><i data-lucide="search" class="w-4 h-4 inline mr-1"></i> Root Cause</h4>
|
|
933
|
+
<p class="plan-root-cause-text">${escapeHtml(plan.root_cause)}</p>
|
|
934
|
+
</div>
|
|
935
|
+
` : ''}
|
|
936
|
+
|
|
937
|
+
<!-- Strategy (fix-plan specific) -->
|
|
938
|
+
${plan.strategy ? `
|
|
939
|
+
<div class="plan-section">
|
|
940
|
+
<h4 class="plan-section-title"><i data-lucide="route" class="w-4 h-4 inline mr-1"></i> Fix Strategy</h4>
|
|
941
|
+
<p class="plan-strategy-text">${escapeHtml(plan.strategy)}</p>
|
|
942
|
+
</div>
|
|
943
|
+
` : ''}
|
|
944
|
+
|
|
945
|
+
<!-- Approach -->
|
|
946
|
+
${plan.approach ? `
|
|
947
|
+
<div class="plan-section">
|
|
948
|
+
<h4 class="plan-section-title"><i data-lucide="target" class="w-4 h-4 inline mr-1"></i> Approach</h4>
|
|
949
|
+
<p class="plan-approach-text">${escapeHtml(plan.approach)}</p>
|
|
950
|
+
</div>
|
|
951
|
+
` : ''}
|
|
952
|
+
|
|
953
|
+
<!-- User Requirements -->
|
|
954
|
+
${plan.user_requirements ? `
|
|
955
|
+
<div class="plan-section">
|
|
956
|
+
<h4 class="plan-section-title"><i data-lucide="user" class="w-4 h-4 inline mr-1"></i> User Requirements</h4>
|
|
957
|
+
<p class="plan-requirements-text">${escapeHtml(plan.user_requirements)}</p>
|
|
958
|
+
</div>
|
|
959
|
+
` : ''}
|
|
960
|
+
|
|
961
|
+
<!-- Focus Paths -->
|
|
962
|
+
${plan.focus_paths?.length ? `
|
|
963
|
+
<div class="plan-section">
|
|
964
|
+
<h4 class="plan-section-title"><i data-lucide="folder" class="w-4 h-4 inline mr-1"></i> Focus Paths</h4>
|
|
965
|
+
<div class="path-tags">
|
|
966
|
+
${plan.focus_paths.map(p => `<span class="path-tag">${escapeHtml(p)}</span>`).join('')}
|
|
967
|
+
</div>
|
|
968
|
+
</div>
|
|
969
|
+
` : ''}
|
|
970
|
+
|
|
971
|
+
<!-- Metadata -->
|
|
972
|
+
<div class="plan-section">
|
|
973
|
+
<h4 class="plan-section-title"><i data-lucide="info" class="w-4 h-4 inline mr-1"></i> Metadata</h4>
|
|
974
|
+
<div class="plan-meta-grid">
|
|
975
|
+
${plan.severity ? `<div class="meta-item"><span class="meta-label">Severity:</span> <span class="severity-badge ${escapeHtml(plan.severity)}">${escapeHtml(plan.severity)}</span></div>` : ''}
|
|
976
|
+
${plan.risk_level ? `<div class="meta-item"><span class="meta-label">Risk Level:</span> <span class="risk-badge ${escapeHtml(plan.risk_level)}">${escapeHtml(plan.risk_level)}</span></div>` : ''}
|
|
977
|
+
${plan.estimated_time ? `<div class="meta-item"><span class="meta-label">Estimated Time:</span> ${escapeHtml(plan.estimated_time)}</div>` : ''}
|
|
978
|
+
${plan.complexity ? `<div class="meta-item"><span class="meta-label">Complexity:</span> ${escapeHtml(plan.complexity)}</div>` : ''}
|
|
979
|
+
${plan.recommended_execution ? `<div class="meta-item"><span class="meta-label">Execution:</span> ${escapeHtml(plan.recommended_execution)}</div>` : ''}
|
|
980
|
+
</div>
|
|
981
|
+
</div>
|
|
982
|
+
|
|
983
|
+
<!-- Tasks -->
|
|
984
|
+
${plan.tasks?.length ? `
|
|
985
|
+
<div class="plan-section">
|
|
986
|
+
<h4 class="plan-section-title"><i data-lucide="list-checks" class="w-4 h-4 inline mr-1"></i> Tasks (${plan.tasks.length})</h4>
|
|
987
|
+
<div class="fix-tasks-summary">
|
|
988
|
+
${plan.tasks.map((task, idx) => `
|
|
989
|
+
<div class="fix-task-summary-item collapsible-section">
|
|
990
|
+
<div class="collapsible-header">
|
|
991
|
+
<span class="collapse-icon">▶</span>
|
|
992
|
+
<span class="task-num">#${idx + 1}</span>
|
|
993
|
+
<span class="task-title-brief">${escapeHtml(task.title || task.summary || 'Untitled')}</span>
|
|
994
|
+
${task.scope ? `<span class="task-scope-badge">${escapeHtml(task.scope)}</span>` : ''}
|
|
995
|
+
</div>
|
|
996
|
+
<div class="collapsible-content collapsed">
|
|
997
|
+
${task.modification_points?.length ? `
|
|
998
|
+
<div class="task-detail-section">
|
|
999
|
+
<strong>Modification Points:</strong>
|
|
1000
|
+
<ul class="mod-points-list">
|
|
1001
|
+
${task.modification_points.map(mp => `
|
|
1002
|
+
<li>
|
|
1003
|
+
<code>${escapeHtml(mp.file || '')}</code>
|
|
1004
|
+
${mp.function_name ? `<span class="func-name">-> ${escapeHtml(mp.function_name)}</span>` : ''}
|
|
1005
|
+
${mp.change_type ? `<span class="change-type">(${escapeHtml(mp.change_type)})</span>` : ''}
|
|
1006
|
+
</li>
|
|
1007
|
+
`).join('')}
|
|
1008
|
+
</ul>
|
|
1009
|
+
</div>
|
|
1010
|
+
` : ''}
|
|
1011
|
+
${task.implementation?.length ? `
|
|
1012
|
+
<div class="task-detail-section">
|
|
1013
|
+
<strong>Implementation Steps:</strong>
|
|
1014
|
+
<ol class="impl-steps-list">
|
|
1015
|
+
${task.implementation.map(step => `<li>${escapeHtml(step)}</li>`).join('')}
|
|
1016
|
+
</ol>
|
|
1017
|
+
</div>
|
|
1018
|
+
` : ''}
|
|
1019
|
+
${task.verification?.length ? `
|
|
1020
|
+
<div class="task-detail-section">
|
|
1021
|
+
<strong>Verification:</strong>
|
|
1022
|
+
<ul class="verify-list">
|
|
1023
|
+
${task.verification.map(v => `<li>${escapeHtml(v)}</li>`).join('')}
|
|
1024
|
+
</ul>
|
|
1025
|
+
</div>
|
|
1026
|
+
` : ''}
|
|
1027
|
+
</div>
|
|
1028
|
+
</div>
|
|
1029
|
+
`).join('')}
|
|
1030
|
+
</div>
|
|
1031
|
+
</div>
|
|
1032
|
+
` : ''}
|
|
1033
|
+
|
|
1034
|
+
<!-- Raw JSON -->
|
|
1035
|
+
<div class="plan-section collapsible-section">
|
|
1036
|
+
<div class="collapsible-header">
|
|
1037
|
+
<span class="collapse-icon">▶</span>
|
|
1038
|
+
<span class="section-label">{ } Raw JSON</span>
|
|
1039
|
+
</div>
|
|
1040
|
+
<div class="collapsible-content collapsed">
|
|
1041
|
+
<pre class="json-content">${escapeHtml(JSON.stringify(plan, null, 2))}</pre>
|
|
1042
|
+
</div>
|
|
1043
|
+
</div>
|
|
1044
|
+
</div>
|
|
1045
|
+
`;
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
/**
|
|
1049
|
+
* Render a single requirement item
|
|
1050
|
+
*/
|
|
1051
|
+
function renderRequirementItem(req) {
|
|
1052
|
+
const priorityColors = {
|
|
1053
|
+
'critical': 'error',
|
|
1054
|
+
'high': 'warning',
|
|
1055
|
+
'medium': 'info',
|
|
1056
|
+
'low': 'default'
|
|
1057
|
+
};
|
|
1058
|
+
const priority = req.priority || 'medium';
|
|
1059
|
+
const colorClass = priorityColors[priority] || 'default';
|
|
1060
|
+
|
|
1061
|
+
return `
|
|
1062
|
+
<div class="requirement-item">
|
|
1063
|
+
<div class="requirement-header">
|
|
1064
|
+
<span class="requirement-id">${escapeHtml(req.id || '')}</span>
|
|
1065
|
+
<span class="priority-badge ${colorClass}">${escapeHtml(priority)}</span>
|
|
1066
|
+
</div>
|
|
1067
|
+
<div class="requirement-desc">${escapeHtml(getI18nText(req.description))}</div>
|
|
1068
|
+
${req.source ? `<div class="requirement-source">${t('multiCli.source') || 'Source'}: ${escapeHtml(req.source)}</div>` : ''}
|
|
1069
|
+
</div>
|
|
1070
|
+
`;
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
/**
|
|
1074
|
+
* Render Decision tab
|
|
1075
|
+
* Shows: selectedSolution, rejectedAlternatives, confidenceScore
|
|
1076
|
+
*/
|
|
1077
|
+
function renderMultiCliDecisionTab(session) {
|
|
1078
|
+
// Use helper to extract decision from synthesis data structure
|
|
1079
|
+
const decision = extractDecisionFromSynthesis(session.latestSynthesis);
|
|
1080
|
+
|
|
1081
|
+
if (!decision || !decision.selectedSolution) {
|
|
1082
|
+
return `
|
|
1083
|
+
<div class="tab-empty-state">
|
|
1084
|
+
<div class="empty-icon"><i data-lucide="check-circle" class="w-12 h-12"></i></div>
|
|
1085
|
+
<div class="empty-title">${t('multiCli.empty.decision') || 'No Decision Yet'}</div>
|
|
1086
|
+
<div class="empty-text">${t('multiCli.empty.decisionText') || 'No decision has been made for this discussion yet.'}</div>
|
|
1087
|
+
</div>
|
|
1088
|
+
`;
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
const status = decision.status || 'pending';
|
|
1092
|
+
const summary = getI18nText(decision.summary) || '';
|
|
1093
|
+
const selectedSolution = decision.selectedSolution || null;
|
|
1094
|
+
const rejectedAlternatives = decision.rejectedAlternatives || [];
|
|
1095
|
+
const confidenceScore = decision.confidenceScore || 0;
|
|
1096
|
+
|
|
1097
|
+
let sections = [];
|
|
1098
|
+
|
|
1099
|
+
// Decision Status and Summary
|
|
1100
|
+
sections.push(`
|
|
1101
|
+
<div class="multi-cli-section decision-header-section">
|
|
1102
|
+
<div class="decision-status-bar ${confidenceScore >= 0.7 ? 'converged' : 'divergent'}">
|
|
1103
|
+
<div class="decision-status-wrapper">
|
|
1104
|
+
<span class="decision-status ${status}">${escapeHtml(status)}</span>
|
|
1105
|
+
</div>
|
|
1106
|
+
<div class="decision-confidence">
|
|
1107
|
+
<span class="decision-confidence-label">${t('multiCli.confidence') || 'Confidence'}:</span>
|
|
1108
|
+
<div class="decision-confidence-bar">
|
|
1109
|
+
<div class="decision-confidence-fill" style="width: ${(confidenceScore * 100).toFixed(0)}%"></div>
|
|
1110
|
+
</div>
|
|
1111
|
+
<span class="decision-confidence-value">${(confidenceScore * 100).toFixed(0)}%</span>
|
|
1112
|
+
</div>
|
|
1113
|
+
</div>
|
|
1114
|
+
${summary ? `<p class="decision-summary-text">${escapeHtml(summary)}</p>` : ''}
|
|
1115
|
+
</div>
|
|
1116
|
+
`);
|
|
1117
|
+
|
|
1118
|
+
// Selected Solution
|
|
1119
|
+
if (selectedSolution) {
|
|
1120
|
+
sections.push(`
|
|
1121
|
+
<div class="multi-cli-section selected-solution-section">
|
|
1122
|
+
<h4 class="section-title"><i data-lucide="check-circle" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.selectedSolution') || 'Selected Solution'}</h4>
|
|
1123
|
+
${renderSolutionCard(selectedSolution, true)}
|
|
1124
|
+
</div>
|
|
1125
|
+
`);
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
// Rejected Alternatives
|
|
1129
|
+
if (rejectedAlternatives.length) {
|
|
1130
|
+
sections.push(`
|
|
1131
|
+
<div class="multi-cli-section rejected-section collapsible-section">
|
|
1132
|
+
<div class="collapsible-header">
|
|
1133
|
+
<span class="collapse-icon">►</span>
|
|
1134
|
+
<span class="section-label"><i data-lucide="x-circle" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.rejectedAlternatives') || 'Rejected Alternatives'} (${rejectedAlternatives.length})</span>
|
|
1135
|
+
</div>
|
|
1136
|
+
<div class="collapsible-content collapsed">
|
|
1137
|
+
${rejectedAlternatives.map(alt => renderSolutionCard(alt, false)).join('')}
|
|
1138
|
+
</div>
|
|
1139
|
+
</div>
|
|
1140
|
+
`);
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
return `<div class="multi-cli-decision-tab">${sections.join('')}</div>`;
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
/**
|
|
1147
|
+
* Render a solution card
|
|
1148
|
+
*/
|
|
1149
|
+
function renderSolutionCard(solution, isSelected) {
|
|
1150
|
+
const title = getI18nText(solution.title) || 'Untitled Solution';
|
|
1151
|
+
const description = getI18nText(solution.description) || '';
|
|
1152
|
+
const pros = solution.pros || [];
|
|
1153
|
+
const cons = solution.cons || [];
|
|
1154
|
+
const risk = solution.risk || 'medium';
|
|
1155
|
+
const effort = getI18nText(solution.estimatedEffort) || '';
|
|
1156
|
+
const rejectionReason = solution.rejectionReason ? getI18nText(solution.rejectionReason) : '';
|
|
1157
|
+
const sourceCLIs = solution.sourceCLIs || [];
|
|
1158
|
+
|
|
1159
|
+
return `
|
|
1160
|
+
<div class="solution-card ${isSelected ? 'selected' : 'rejected'}">
|
|
1161
|
+
<div class="solution-header">
|
|
1162
|
+
<span class="solution-id">${escapeHtml(solution.id || '')}</span>
|
|
1163
|
+
<span class="solution-title">${escapeHtml(title)}</span>
|
|
1164
|
+
<span class="risk-badge ${risk}">${escapeHtml(risk)}</span>
|
|
1165
|
+
</div>
|
|
1166
|
+
${description ? `<p class="solution-desc">${escapeHtml(description)}</p>` : ''}
|
|
1167
|
+
${rejectionReason ? `<div class="rejection-reason"><strong>${t('multiCli.rejectionReason') || 'Reason'}:</strong> ${escapeHtml(rejectionReason)}</div>` : ''}
|
|
1168
|
+
<div class="solution-details">
|
|
1169
|
+
${pros.length ? `
|
|
1170
|
+
<div class="pros-section">
|
|
1171
|
+
<strong>${t('multiCli.pros') || 'Pros'}:</strong>
|
|
1172
|
+
<ul class="pros-list">${pros.map(p => `<li class="pro-item">${escapeHtml(getI18nText(p))}</li>`).join('')}</ul>
|
|
1173
|
+
</div>
|
|
1174
|
+
` : ''}
|
|
1175
|
+
${cons.length ? `
|
|
1176
|
+
<div class="cons-section">
|
|
1177
|
+
<strong>${t('multiCli.cons') || 'Cons'}:</strong>
|
|
1178
|
+
<ul class="cons-list">${cons.map(c => `<li class="con-item">${escapeHtml(getI18nText(c))}</li>`).join('')}</ul>
|
|
1179
|
+
</div>
|
|
1180
|
+
` : ''}
|
|
1181
|
+
${effort ? `<div class="effort-estimate"><strong>${t('multiCli.effort') || 'Effort'}:</strong> ${escapeHtml(effort)}</div>` : ''}
|
|
1182
|
+
${sourceCLIs.length ? `<div class="source-clis"><strong>${t('multiCli.sources') || 'Sources'}:</strong> ${sourceCLIs.map(s => `<span class="cli-badge">${escapeHtml(s)}</span>`).join('')}</div>` : ''}
|
|
1183
|
+
</div>
|
|
1184
|
+
</div>
|
|
1185
|
+
`;
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
/**
|
|
1189
|
+
* Render Timeline tab
|
|
1190
|
+
* Shows: decisionRecords.timeline
|
|
1191
|
+
*/
|
|
1192
|
+
function renderMultiCliTimelineTab(session) {
|
|
1193
|
+
// Use helper to extract timeline from synthesis data structure
|
|
1194
|
+
const timeline = extractTimelineFromSynthesis(session.latestSynthesis);
|
|
1195
|
+
|
|
1196
|
+
if (!timeline || !timeline.length) {
|
|
1197
|
+
return `
|
|
1198
|
+
<div class="tab-empty-state">
|
|
1199
|
+
<div class="empty-icon"><i data-lucide="git-commit" class="w-12 h-12"></i></div>
|
|
1200
|
+
<div class="empty-title">${t('multiCli.empty.timeline') || 'No Timeline Events'}</div>
|
|
1201
|
+
<div class="empty-text">${t('multiCli.empty.timelineText') || 'No decision timeline available for this session.'}</div>
|
|
1202
|
+
</div>
|
|
1203
|
+
`;
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
const eventTypeIcons = {
|
|
1207
|
+
'proposal': 'lightbulb',
|
|
1208
|
+
'argument': 'message-square',
|
|
1209
|
+
'agreement': 'thumbs-up',
|
|
1210
|
+
'disagreement': 'thumbs-down',
|
|
1211
|
+
'decision': 'check-circle',
|
|
1212
|
+
'reversal': 'rotate-ccw'
|
|
1213
|
+
};
|
|
1214
|
+
|
|
1215
|
+
return `
|
|
1216
|
+
<div class="multi-cli-timeline-tab">
|
|
1217
|
+
<div class="timeline-container">
|
|
1218
|
+
${timeline.map(event => {
|
|
1219
|
+
const icon = eventTypeIcons[event.type] || 'circle';
|
|
1220
|
+
const contributor = event.contributor || {};
|
|
1221
|
+
const summary = getI18nText(event.summary) || '';
|
|
1222
|
+
const evidence = event.evidence || [];
|
|
1223
|
+
|
|
1224
|
+
return `
|
|
1225
|
+
<div class="timeline-event event-${event.type || 'default'}">
|
|
1226
|
+
<div class="timeline-marker">
|
|
1227
|
+
<i data-lucide="${icon}" class="w-4 h-4"></i>
|
|
1228
|
+
</div>
|
|
1229
|
+
<div class="timeline-content">
|
|
1230
|
+
<div class="event-header">
|
|
1231
|
+
<span class="event-type ${event.type || ''}">${escapeHtml(event.type || 'event')}</span>
|
|
1232
|
+
<span class="event-contributor"><i data-lucide="user" class="w-3.5 h-3.5"></i>${escapeHtml(contributor.name || 'Unknown')}</span>
|
|
1233
|
+
<span class="event-time">${formatDate(event.timestamp)}</span>
|
|
1234
|
+
</div>
|
|
1235
|
+
<div class="event-summary">${escapeHtml(summary)}</div>
|
|
1236
|
+
${event.reversibility ? `<span class="reversibility-badge ${event.reversibility}">${escapeHtml(event.reversibility)}</span>` : ''}
|
|
1237
|
+
${evidence.length ? `
|
|
1238
|
+
<div class="event-evidence">
|
|
1239
|
+
${evidence.map(ev => `
|
|
1240
|
+
<div class="evidence-item evidence-${ev.type || 'reference'}">
|
|
1241
|
+
<span class="evidence-type">${escapeHtml(ev.type || 'reference')}</span>
|
|
1242
|
+
<span class="evidence-desc">${escapeHtml(getI18nText(ev.description))}</span>
|
|
1243
|
+
</div>
|
|
1244
|
+
`).join('')}
|
|
1245
|
+
</div>
|
|
1246
|
+
` : ''}
|
|
1247
|
+
</div>
|
|
1248
|
+
</div>
|
|
1249
|
+
`;
|
|
1250
|
+
}).join('')}
|
|
1251
|
+
</div>
|
|
1252
|
+
</div>
|
|
1253
|
+
`;
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
/**
|
|
1257
|
+
* Render Rounds tab
|
|
1258
|
+
* Shows: navigation between round synthesis files
|
|
1259
|
+
*/
|
|
1260
|
+
function renderMultiCliRoundsTab(session) {
|
|
1261
|
+
const rounds = session.rounds || [];
|
|
1262
|
+
const metadata = session.metadata || {};
|
|
1263
|
+
const totalRounds = metadata.roundId || rounds.length || 1;
|
|
1264
|
+
|
|
1265
|
+
if (!rounds.length && totalRounds <= 1) {
|
|
1266
|
+
// Show current synthesis as single round
|
|
1267
|
+
return `
|
|
1268
|
+
<div class="multi-cli-rounds-tab">
|
|
1269
|
+
<div class="rounds-nav">
|
|
1270
|
+
<div class="round-item active" data-round="1">
|
|
1271
|
+
<span class="round-number">Round 1</span>
|
|
1272
|
+
<span class="round-status">${t('multiCli.currentRound') || 'Current'}</span>
|
|
1273
|
+
</div>
|
|
1274
|
+
</div>
|
|
1275
|
+
<div class="round-content">
|
|
1276
|
+
<div class="round-info">
|
|
1277
|
+
<p>${t('multiCli.singleRoundInfo') || 'This is a single-round discussion. View other tabs for details.'}</p>
|
|
1278
|
+
</div>
|
|
1279
|
+
</div>
|
|
1280
|
+
</div>
|
|
1281
|
+
`;
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
// Render round navigation and content
|
|
1285
|
+
return `
|
|
1286
|
+
<div class="multi-cli-rounds-tab">
|
|
1287
|
+
<div class="rounds-nav">
|
|
1288
|
+
${rounds.map((round, idx) => {
|
|
1289
|
+
const roundNum = idx + 1;
|
|
1290
|
+
const isActive = roundNum === totalRounds;
|
|
1291
|
+
const roundStatus = round.convergence?.recommendation || 'continue';
|
|
1292
|
+
|
|
1293
|
+
return `
|
|
1294
|
+
<div class="round-item ${isActive ? 'active' : ''}" data-round="${roundNum}" onclick="loadMultiCliRound('${currentSessionDetailKey}', ${roundNum})">
|
|
1295
|
+
<span class="round-number">Round ${roundNum}</span>
|
|
1296
|
+
<span class="round-status ${roundStatus}">${escapeHtml(roundStatus)}</span>
|
|
1297
|
+
</div>
|
|
1298
|
+
`;
|
|
1299
|
+
}).join('')}
|
|
1300
|
+
</div>
|
|
1301
|
+
<div class="round-content" id="multiCliRoundContent">
|
|
1302
|
+
${renderRoundContent(rounds[totalRounds - 1] || rounds[0] || session)}
|
|
1303
|
+
</div>
|
|
1304
|
+
</div>
|
|
1305
|
+
`;
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
/**
|
|
1309
|
+
* Render content for a specific round
|
|
1310
|
+
*/
|
|
1311
|
+
function renderRoundContent(round) {
|
|
1312
|
+
if (!round) {
|
|
1313
|
+
return `<div class="round-empty">${t('multiCli.noRoundData') || 'No data for this round.'}</div>`;
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
const metadata = round.metadata || {};
|
|
1317
|
+
const agents = metadata.contributingAgents || [];
|
|
1318
|
+
const convergence = round._internal?.convergence || {};
|
|
1319
|
+
const crossVerification = round._internal?.cross_verification || {};
|
|
1320
|
+
|
|
1321
|
+
let sections = [];
|
|
1322
|
+
|
|
1323
|
+
// Round metadata
|
|
1324
|
+
sections.push(`
|
|
1325
|
+
<div class="round-metadata">
|
|
1326
|
+
<div class="meta-row">
|
|
1327
|
+
<span class="meta-label">${t('multiCli.roundId') || 'Round'}:</span>
|
|
1328
|
+
<span class="meta-value">${metadata.roundId || 1}</span>
|
|
1329
|
+
</div>
|
|
1330
|
+
<div class="meta-row">
|
|
1331
|
+
<span class="meta-label">${t('multiCli.timestamp') || 'Time'}:</span>
|
|
1332
|
+
<span class="meta-value">${formatDate(metadata.timestamp)}</span>
|
|
1333
|
+
</div>
|
|
1334
|
+
${metadata.durationSeconds ? `
|
|
1335
|
+
<div class="meta-row">
|
|
1336
|
+
<span class="meta-label">${t('multiCli.duration') || 'Duration'}:</span>
|
|
1337
|
+
<span class="meta-value">${metadata.durationSeconds}s</span>
|
|
1338
|
+
</div>
|
|
1339
|
+
` : ''}
|
|
1340
|
+
</div>
|
|
1341
|
+
`);
|
|
1342
|
+
|
|
1343
|
+
// Contributing agents
|
|
1344
|
+
if (agents.length) {
|
|
1345
|
+
sections.push(`
|
|
1346
|
+
<div class="round-agents">
|
|
1347
|
+
<strong>${t('multiCli.contributors') || 'Contributors'}:</strong>
|
|
1348
|
+
${agents.map(agent => `<span class="agent-badge">${escapeHtml(agent.name || agent.id)}</span>`).join('')}
|
|
1349
|
+
</div>
|
|
1350
|
+
`);
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
// Convergence metrics
|
|
1354
|
+
if (convergence.score !== undefined) {
|
|
1355
|
+
sections.push(`
|
|
1356
|
+
<div class="round-convergence">
|
|
1357
|
+
<strong>${t('multiCli.convergence') || 'Convergence'}:</strong>
|
|
1358
|
+
<span class="convergence-score">${(convergence.score * 100).toFixed(0)}%</span>
|
|
1359
|
+
<span class="convergence-rec ${convergence.recommendation || ''}">${escapeHtml(convergence.recommendation || '')}</span>
|
|
1360
|
+
${convergence.new_insights ? `<span class="new-insights-badge">${t('multiCli.newInsights') || 'New Insights'}</span>` : ''}
|
|
1361
|
+
</div>
|
|
1362
|
+
`);
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
// Cross-verification
|
|
1366
|
+
if (crossVerification.agreements?.length || crossVerification.disagreements?.length) {
|
|
1367
|
+
sections.push(`
|
|
1368
|
+
<div class="round-verification collapsible-section">
|
|
1369
|
+
<div class="collapsible-header">
|
|
1370
|
+
<span class="collapse-icon">►</span>
|
|
1371
|
+
<span class="section-label">${t('multiCli.crossVerification') || 'Cross-Verification'}</span>
|
|
1372
|
+
</div>
|
|
1373
|
+
<div class="collapsible-content collapsed">
|
|
1374
|
+
${crossVerification.agreements?.length ? `
|
|
1375
|
+
<div class="agreements">
|
|
1376
|
+
<strong>${t('multiCli.agreements') || 'Agreements'}:</strong>
|
|
1377
|
+
<ul>${crossVerification.agreements.map(a => `<li class="agreement">${escapeHtml(a)}</li>`).join('')}</ul>
|
|
1378
|
+
</div>
|
|
1379
|
+
` : ''}
|
|
1380
|
+
${crossVerification.disagreements?.length ? `
|
|
1381
|
+
<div class="disagreements">
|
|
1382
|
+
<strong>${t('multiCli.disagreements') || 'Disagreements'}:</strong>
|
|
1383
|
+
<ul>${crossVerification.disagreements.map(d => `<li class="disagreement">${escapeHtml(d)}</li>`).join('')}</ul>
|
|
1384
|
+
</div>
|
|
1385
|
+
` : ''}
|
|
1386
|
+
${crossVerification.resolution ? `
|
|
1387
|
+
<div class="resolution">
|
|
1388
|
+
<strong>${t('multiCli.resolution') || 'Resolution'}:</strong>
|
|
1389
|
+
<p>${escapeHtml(crossVerification.resolution)}</p>
|
|
1390
|
+
</div>
|
|
1391
|
+
` : ''}
|
|
1392
|
+
</div>
|
|
1393
|
+
</div>
|
|
1394
|
+
`);
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
// Round solutions
|
|
1398
|
+
if (round.solutions && round.solutions.length > 0) {
|
|
1399
|
+
sections.push(`
|
|
1400
|
+
<div class="round-solutions">
|
|
1401
|
+
<strong>${t('multiCli.solutions') || 'Solutions'} (${round.solutions.length}):</strong>
|
|
1402
|
+
<div class="solutions-list">
|
|
1403
|
+
${round.solutions.map((solution, idx) => `
|
|
1404
|
+
<div class="solution-card">
|
|
1405
|
+
<div class="solution-header">
|
|
1406
|
+
<div class="solution-title">
|
|
1407
|
+
<span class="solution-number">${idx + 1}</span>
|
|
1408
|
+
<span class="solution-name">${escapeHtml(solution.name || `Solution ${idx + 1}`)}</span>
|
|
1409
|
+
</div>
|
|
1410
|
+
<div class="solution-meta">
|
|
1411
|
+
${solution.source_cli?.length ? `
|
|
1412
|
+
<div class="source-clis">
|
|
1413
|
+
${solution.source_cli.map(cli => `<span class="cli-badge">${escapeHtml(cli)}</span>`).join('')}
|
|
1414
|
+
</div>
|
|
1415
|
+
` : ''}
|
|
1416
|
+
<div class="solution-scores">
|
|
1417
|
+
<span class="score-badge feasibility" title="Feasibility">
|
|
1418
|
+
${Math.round((solution.feasibility || 0) * 100)}%
|
|
1419
|
+
</span>
|
|
1420
|
+
<span class="score-badge effort-${solution.effort || 'medium'}" title="Effort">
|
|
1421
|
+
${escapeHtml(solution.effort || 'medium')}
|
|
1422
|
+
</span>
|
|
1423
|
+
<span class="score-badge risk-${solution.risk || 'medium'}" title="Risk">
|
|
1424
|
+
${escapeHtml(solution.risk || 'medium')}
|
|
1425
|
+
</span>
|
|
1426
|
+
</div>
|
|
1427
|
+
</div>
|
|
1428
|
+
</div>
|
|
1429
|
+
|
|
1430
|
+
${solution.summary ? `
|
|
1431
|
+
<div class="solution-summary">
|
|
1432
|
+
${escapeHtml(getI18nText(solution.summary))}
|
|
1433
|
+
</div>
|
|
1434
|
+
` : ''}
|
|
1435
|
+
|
|
1436
|
+
${solution.implementation_plan?.approach ? `
|
|
1437
|
+
<div class="solution-approach collapsible-section">
|
|
1438
|
+
<div class="collapsible-header">
|
|
1439
|
+
<span class="collapse-icon">►</span>
|
|
1440
|
+
<span class="section-label">${t('multiCli.implementation') || 'Implementation Approach'}</span>
|
|
1441
|
+
</div>
|
|
1442
|
+
<div class="collapsible-content collapsed">
|
|
1443
|
+
<p>${escapeHtml(getI18nText(solution.implementation_plan.approach))}</p>
|
|
1444
|
+
|
|
1445
|
+
${solution.implementation_plan.tasks?.length ? `
|
|
1446
|
+
<div class="solution-tasks">
|
|
1447
|
+
<strong>${t('multiCli.tasks') || 'Tasks'}:</strong>
|
|
1448
|
+
<ul class="task-list">
|
|
1449
|
+
${solution.implementation_plan.tasks.map(task => `
|
|
1450
|
+
<li class="task-item">
|
|
1451
|
+
<span class="task-id">${escapeHtml(task.id || '')}</span>
|
|
1452
|
+
<span class="task-name">${escapeHtml(getI18nText(task.name))}</span>
|
|
1453
|
+
${task.key_point ? `<span class="task-key-point">${escapeHtml(getI18nText(task.key_point))}</span>` : ''}
|
|
1454
|
+
</li>
|
|
1455
|
+
`).join('')}
|
|
1456
|
+
</ul>
|
|
1457
|
+
</div>
|
|
1458
|
+
` : ''}
|
|
1459
|
+
|
|
1460
|
+
${solution.implementation_plan.execution_flow ? `
|
|
1461
|
+
<div class="execution-flow">
|
|
1462
|
+
<strong>${t('multiCli.executionFlow') || 'Execution Flow'}:</strong>
|
|
1463
|
+
<code class="flow-code">${escapeHtml(solution.implementation_plan.execution_flow)}</code>
|
|
1464
|
+
</div>
|
|
1465
|
+
` : ''}
|
|
1466
|
+
|
|
1467
|
+
${solution.implementation_plan.milestones?.length ? `
|
|
1468
|
+
<div class="solution-milestones">
|
|
1469
|
+
<strong>${t('multiCli.milestones') || 'Milestones'}:</strong>
|
|
1470
|
+
<ul class="milestone-list">
|
|
1471
|
+
${solution.implementation_plan.milestones.map(milestone => `
|
|
1472
|
+
<li class="milestone-item">${escapeHtml(getI18nText(milestone))}</li>
|
|
1473
|
+
`).join('')}
|
|
1474
|
+
</ul>
|
|
1475
|
+
</div>
|
|
1476
|
+
` : ''}
|
|
1477
|
+
</div>
|
|
1478
|
+
</div>
|
|
1479
|
+
` : ''}
|
|
1480
|
+
|
|
1481
|
+
${(solution.dependencies?.internal?.length || solution.dependencies?.external?.length) ? `
|
|
1482
|
+
<div class="solution-dependencies collapsible-section">
|
|
1483
|
+
<div class="collapsible-header">
|
|
1484
|
+
<span class="collapse-icon">►</span>
|
|
1485
|
+
<span class="section-label">${t('multiCli.dependencies') || 'Dependencies'}</span>
|
|
1486
|
+
</div>
|
|
1487
|
+
<div class="collapsible-content collapsed">
|
|
1488
|
+
${solution.dependencies.internal?.length ? `
|
|
1489
|
+
<div class="internal-deps">
|
|
1490
|
+
<strong>${t('multiCli.internalDeps') || 'Internal'}:</strong>
|
|
1491
|
+
<ul class="dep-list">
|
|
1492
|
+
${solution.dependencies.internal.map(dep => `
|
|
1493
|
+
<li class="dep-item">${escapeHtml(getI18nText(dep))}</li>
|
|
1494
|
+
`).join('')}
|
|
1495
|
+
</ul>
|
|
1496
|
+
</div>
|
|
1497
|
+
` : ''}
|
|
1498
|
+
${solution.dependencies.external?.length ? `
|
|
1499
|
+
<div class="external-deps">
|
|
1500
|
+
<strong>${t('multiCli.externalDeps') || 'External'}:</strong>
|
|
1501
|
+
<ul class="dep-list">
|
|
1502
|
+
${solution.dependencies.external.map(dep => `
|
|
1503
|
+
<li class="dep-item">${escapeHtml(getI18nText(dep))}</li>
|
|
1504
|
+
`).join('')}
|
|
1505
|
+
</ul>
|
|
1506
|
+
</div>
|
|
1507
|
+
` : ''}
|
|
1508
|
+
</div>
|
|
1509
|
+
</div>
|
|
1510
|
+
` : ''}
|
|
1511
|
+
|
|
1512
|
+
${solution.technical_concerns?.length ? `
|
|
1513
|
+
<div class="solution-concerns collapsible-section">
|
|
1514
|
+
<div class="collapsible-header">
|
|
1515
|
+
<span class="collapse-icon">►</span>
|
|
1516
|
+
<span class="section-label">${t('multiCli.technicalConcerns') || 'Technical Concerns'}</span>
|
|
1517
|
+
</div>
|
|
1518
|
+
<div class="collapsible-content collapsed">
|
|
1519
|
+
<ul class="concern-list">
|
|
1520
|
+
${solution.technical_concerns.map(concern => `
|
|
1521
|
+
<li class="concern-item">${escapeHtml(getI18nText(concern))}</li>
|
|
1522
|
+
`).join('')}
|
|
1523
|
+
</ul>
|
|
1524
|
+
</div>
|
|
1525
|
+
</div>
|
|
1526
|
+
` : ''}
|
|
1527
|
+
</div>
|
|
1528
|
+
`).join('')}
|
|
1529
|
+
</div>
|
|
1530
|
+
</div>
|
|
1531
|
+
`);
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
return sections.join('');
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
/**
|
|
1538
|
+
* Load a specific round's data (async, may fetch from server)
|
|
1539
|
+
*/
|
|
1540
|
+
async function loadMultiCliRound(sessionKey, roundNum) {
|
|
1541
|
+
const session = liteTaskDataStore[sessionKey];
|
|
1542
|
+
if (!session) return;
|
|
1543
|
+
|
|
1544
|
+
// Update active state in nav
|
|
1545
|
+
document.querySelectorAll('.round-item').forEach(item => {
|
|
1546
|
+
item.classList.toggle('active', parseInt(item.dataset.round) === roundNum);
|
|
1547
|
+
});
|
|
1548
|
+
|
|
1549
|
+
const contentArea = document.getElementById('multiCliRoundContent');
|
|
1550
|
+
|
|
1551
|
+
// If we have rounds array, use it
|
|
1552
|
+
if (session.rounds && session.rounds[roundNum - 1]) {
|
|
1553
|
+
contentArea.innerHTML = renderRoundContent(session.rounds[roundNum - 1]);
|
|
1554
|
+
initCollapsibleSections(contentArea);
|
|
1555
|
+
return;
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
// Otherwise try to fetch from server
|
|
1559
|
+
if (window.SERVER_MODE && session.path) {
|
|
1560
|
+
contentArea.innerHTML = `<div class="tab-loading">${t('common.loading') || 'Loading...'}</div>`;
|
|
1561
|
+
try {
|
|
1562
|
+
const response = await fetch(`/api/session-detail?path=${encodeURIComponent(session.path)}&type=round&round=${roundNum}`);
|
|
1563
|
+
if (response.ok) {
|
|
1564
|
+
const data = await response.json();
|
|
1565
|
+
contentArea.innerHTML = renderRoundContent(data.round || {});
|
|
1566
|
+
initCollapsibleSections(contentArea);
|
|
1567
|
+
return;
|
|
1568
|
+
}
|
|
1569
|
+
} catch (err) {
|
|
1570
|
+
console.error('Failed to load round:', err);
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
// Fallback
|
|
1575
|
+
contentArea.innerHTML = `<div class="round-empty">${t('multiCli.noRoundData') || 'No data for this round.'}</div>`;
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
/**
|
|
1579
|
+
* Render Discussion Section (combines Topic, Rounds, Decision)
|
|
1580
|
+
* Uses accordion layout to display discussion rounds
|
|
1581
|
+
*/
|
|
1582
|
+
function renderMultiCliDiscussionSection(session) {
|
|
1583
|
+
const rounds = session.rounds || [];
|
|
1584
|
+
const metadata = session.metadata || {};
|
|
1585
|
+
const totalRounds = metadata.roundId || rounds.length || 1;
|
|
1586
|
+
const topic = session.discussionTopic || session.latestSynthesis?.discussionTopic || {};
|
|
1587
|
+
|
|
1588
|
+
// If no rounds, show topic summary and current synthesis
|
|
1589
|
+
if (!rounds.length) {
|
|
1590
|
+
const title = getI18nText(topic.title) || 'Discussion Topic';
|
|
1591
|
+
const description = getI18nText(topic.description) || '';
|
|
1592
|
+
const status = topic.status || session.status || 'analyzing';
|
|
1593
|
+
|
|
1594
|
+
return `
|
|
1595
|
+
<div class="multi-cli-discussion-section">
|
|
1596
|
+
<div class="discussion-header">
|
|
1597
|
+
<h3 class="discussion-title">${escapeHtml(title)}</h3>
|
|
1598
|
+
<span class="discussion-status ${status}">${escapeHtml(status)}</span>
|
|
1599
|
+
</div>
|
|
1600
|
+
${description ? `<p class="discussion-description">${escapeHtml(description)}</p>` : ''}
|
|
1601
|
+
<div class="discussion-empty-state">
|
|
1602
|
+
<i data-lucide="message-circle" class="w-8 h-8"></i>
|
|
1603
|
+
<p>${t('multiCli.singleRoundInfo') || 'This is a single-round discussion. View Planning tab for execution details.'}</p>
|
|
1604
|
+
</div>
|
|
1605
|
+
</div>
|
|
1606
|
+
`;
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
// Render accordion for multiple rounds
|
|
1610
|
+
const accordionItems = rounds.map((round, idx) => {
|
|
1611
|
+
const roundNum = idx + 1;
|
|
1612
|
+
const isLatest = roundNum === totalRounds;
|
|
1613
|
+
const roundMeta = round.metadata || {};
|
|
1614
|
+
const convergence = round._internal?.convergence || round.convergence || {};
|
|
1615
|
+
const recommendation = convergence.recommendation || 'continue';
|
|
1616
|
+
const score = convergence.score !== undefined ? Math.round(convergence.score * 100) : null;
|
|
1617
|
+
const solutions = round.solutions || [];
|
|
1618
|
+
const agents = roundMeta.contributingAgents || [];
|
|
1619
|
+
|
|
1620
|
+
return `
|
|
1621
|
+
<div class="discussion-round collapsible-section ${isLatest ? 'expanded' : ''}">
|
|
1622
|
+
<div class="collapsible-header discussion-round-header">
|
|
1623
|
+
<span class="collapse-icon">${isLatest ? '▼' : '►'}</span>
|
|
1624
|
+
<div class="round-title-group">
|
|
1625
|
+
<span class="round-badge">${t('multiCli.round') || 'Round'} ${roundNum}</span>
|
|
1626
|
+
<span class="round-timestamp">${formatDate(roundMeta.timestamp)}</span>
|
|
1627
|
+
</div>
|
|
1628
|
+
<div class="round-indicators">
|
|
1629
|
+
${score !== null ? `<span class="convergence-badge" title="${t('multiCli.convergence') || 'Convergence'}">${score}%</span>` : ''}
|
|
1630
|
+
<span class="recommendation-badge ${recommendation}">${escapeHtml(recommendation)}</span>
|
|
1631
|
+
</div>
|
|
1632
|
+
</div>
|
|
1633
|
+
<div class="collapsible-content ${isLatest ? '' : 'collapsed'}">
|
|
1634
|
+
<!-- Discussion Topic for this round -->
|
|
1635
|
+
${round.discussionTopic ? `
|
|
1636
|
+
<div class="round-topic">
|
|
1637
|
+
<h4 class="round-section-title"><i data-lucide="message-circle" class="w-4 h-4 inline"></i> ${t('multiCli.topic') || 'Topic'}</h4>
|
|
1638
|
+
<p>${escapeHtml(getI18nText(round.discussionTopic.title || round.discussionTopic))}</p>
|
|
1639
|
+
</div>
|
|
1640
|
+
` : ''}
|
|
1641
|
+
|
|
1642
|
+
<!-- Contributing Agents -->
|
|
1643
|
+
${agents.length ? `
|
|
1644
|
+
<div class="round-agents">
|
|
1645
|
+
<h4 class="round-section-title"><i data-lucide="users" class="w-4 h-4 inline"></i> ${t('multiCli.contributors') || 'Contributors'}</h4>
|
|
1646
|
+
<div class="agent-badges">
|
|
1647
|
+
${agents.map(agent => `<span class="agent-badge">${escapeHtml(agent.name || agent.id || agent)}</span>`).join('')}
|
|
1648
|
+
</div>
|
|
1649
|
+
</div>
|
|
1650
|
+
` : ''}
|
|
1651
|
+
|
|
1652
|
+
<!-- Solutions -->
|
|
1653
|
+
${solutions.length ? `
|
|
1654
|
+
<div class="round-solutions-summary">
|
|
1655
|
+
<h4 class="round-section-title"><i data-lucide="lightbulb" class="w-4 h-4 inline"></i> ${t('multiCli.solutions') || 'Solutions'} (${solutions.length})</h4>
|
|
1656
|
+
<div class="solution-cards-grid">
|
|
1657
|
+
${solutions.map((sol, sidx) => `
|
|
1658
|
+
<div class="solution-mini-card">
|
|
1659
|
+
<div class="solution-mini-header">
|
|
1660
|
+
<span class="solution-number">${sidx + 1}</span>
|
|
1661
|
+
<span class="solution-name">${escapeHtml(sol.name || 'Solution ' + (sidx + 1))}</span>
|
|
1662
|
+
</div>
|
|
1663
|
+
<div class="solution-mini-scores">
|
|
1664
|
+
<span class="score-pill feasibility" title="Feasibility">${Math.round((sol.feasibility || 0) * 100)}%</span>
|
|
1665
|
+
<span class="score-pill effort-${sol.effort || 'medium'}">${escapeHtml(sol.effort || 'M')}</span>
|
|
1666
|
+
<span class="score-pill risk-${sol.risk || 'medium'}">${escapeHtml(sol.risk || 'M')}</span>
|
|
1667
|
+
</div>
|
|
1668
|
+
${sol.summary ? `<p class="solution-mini-summary">${escapeHtml(getI18nText(sol.summary).substring(0, 100))}${getI18nText(sol.summary).length > 100 ? '...' : ''}</p>` : ''}
|
|
1669
|
+
</div>
|
|
1670
|
+
`).join('')}
|
|
1671
|
+
</div>
|
|
1672
|
+
</div>
|
|
1673
|
+
` : ''}
|
|
1674
|
+
|
|
1675
|
+
<!-- Decision/Recommendation -->
|
|
1676
|
+
${convergence.reasoning || round.decision ? `
|
|
1677
|
+
<div class="round-decision">
|
|
1678
|
+
<h4 class="round-section-title"><i data-lucide="check-circle" class="w-4 h-4 inline"></i> ${t('multiCli.decision') || 'Decision'}</h4>
|
|
1679
|
+
<p class="decision-text">${escapeHtml(convergence.reasoning || round.decision || '')}</p>
|
|
1680
|
+
</div>
|
|
1681
|
+
` : ''}
|
|
1682
|
+
</div>
|
|
1683
|
+
</div>
|
|
1684
|
+
`;
|
|
1685
|
+
}).join('');
|
|
1686
|
+
|
|
1687
|
+
return `
|
|
1688
|
+
<div class="multi-cli-discussion-section">
|
|
1689
|
+
<div class="discussion-header">
|
|
1690
|
+
<h3 class="discussion-title">${escapeHtml(getI18nText(topic.title) || 'Discussion')}</h3>
|
|
1691
|
+
<span class="rounds-count">${totalRounds} ${t('multiCli.tab.rounds') || 'Rounds'}</span>
|
|
1692
|
+
</div>
|
|
1693
|
+
<div class="discussion-accordion">
|
|
1694
|
+
${accordionItems}
|
|
1695
|
+
</div>
|
|
1696
|
+
</div>
|
|
1697
|
+
`;
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
/**
|
|
1701
|
+
* Render Association Section (context-package key fields)
|
|
1702
|
+
* Shows solution summary, dependencies, consensus
|
|
1703
|
+
*/
|
|
1704
|
+
function renderMultiCliAssociationSection(session) {
|
|
1705
|
+
const contextPkg = session.contextPackage || session.context_package || session.latestSynthesis?.context_package || {};
|
|
1706
|
+
const solutions = contextPkg.solutions || session.latestSynthesis?.solutions || [];
|
|
1707
|
+
const dependencies = contextPkg.dependencies || {};
|
|
1708
|
+
const consensus = contextPkg.consensus || session.latestSynthesis?._internal?.cross_verification || {};
|
|
1709
|
+
const relatedFiles = extractFilesFromSynthesis(session.latestSynthesis);
|
|
1710
|
+
|
|
1711
|
+
// Check if we have any content to display
|
|
1712
|
+
const hasSolutions = solutions.length > 0;
|
|
1713
|
+
const hasDependencies = dependencies.internal?.length || dependencies.external?.length;
|
|
1714
|
+
const hasConsensus = consensus.agreements?.length || consensus.resolved_conflicts?.length || consensus.disagreements?.length;
|
|
1715
|
+
const hasFiles = relatedFiles?.fileTree?.length || relatedFiles?.impactSummary?.length;
|
|
1716
|
+
|
|
1717
|
+
if (!hasSolutions && !hasDependencies && !hasConsensus && !hasFiles) {
|
|
1718
|
+
return `
|
|
1719
|
+
<div class="tab-empty-state">
|
|
1720
|
+
<div class="empty-icon"><i data-lucide="link-2" class="w-12 h-12"></i></div>
|
|
1721
|
+
<div class="empty-title">${t('multiCli.empty.association') || 'No Association Data'}</div>
|
|
1722
|
+
<div class="empty-text">${t('multiCli.empty.associationText') || 'No context package or related files available for this session.'}</div>
|
|
1723
|
+
</div>
|
|
1724
|
+
`;
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
let sections = [];
|
|
1728
|
+
|
|
1729
|
+
// Solution Summary Cards
|
|
1730
|
+
if (hasSolutions) {
|
|
1731
|
+
sections.push(`
|
|
1732
|
+
<div class="association-section solutions-section">
|
|
1733
|
+
<h4 class="association-section-title">
|
|
1734
|
+
<i data-lucide="lightbulb" class="w-4 h-4 inline"></i>
|
|
1735
|
+
${t('multiCli.solutionSummary') || 'Solution Summary'}
|
|
1736
|
+
</h4>
|
|
1737
|
+
<div class="association-cards-grid">
|
|
1738
|
+
${solutions.map((sol, idx) => `
|
|
1739
|
+
<div class="association-card solution-card">
|
|
1740
|
+
<div class="card-header">
|
|
1741
|
+
<span class="card-number">${idx + 1}</span>
|
|
1742
|
+
<span class="card-title">${escapeHtml(sol.name || 'Solution ' + (idx + 1))}</span>
|
|
1743
|
+
</div>
|
|
1744
|
+
<div class="card-metrics">
|
|
1745
|
+
<div class="metric">
|
|
1746
|
+
<span class="metric-label">${t('multiCli.feasibility') || 'Feasibility'}</span>
|
|
1747
|
+
<span class="metric-value">${Math.round((sol.feasibility || 0) * 100)}%</span>
|
|
1748
|
+
</div>
|
|
1749
|
+
<div class="metric">
|
|
1750
|
+
<span class="metric-label">${t('multiCli.effort') || 'Effort'}</span>
|
|
1751
|
+
<span class="metric-value effort-${sol.effort || 'medium'}">${escapeHtml(sol.effort || 'medium')}</span>
|
|
1752
|
+
</div>
|
|
1753
|
+
<div class="metric">
|
|
1754
|
+
<span class="metric-label">${t('multiCli.risk') || 'Risk'}</span>
|
|
1755
|
+
<span class="metric-value risk-${sol.risk || 'medium'}">${escapeHtml(sol.risk || 'medium')}</span>
|
|
1756
|
+
</div>
|
|
1757
|
+
</div>
|
|
1758
|
+
${sol.summary ? `<p class="card-description">${escapeHtml(getI18nText(sol.summary))}</p>` : ''}
|
|
1759
|
+
</div>
|
|
1760
|
+
`).join('')}
|
|
1761
|
+
</div>
|
|
1762
|
+
</div>
|
|
1763
|
+
`);
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
// Dependencies Card
|
|
1767
|
+
if (hasDependencies) {
|
|
1768
|
+
sections.push(`
|
|
1769
|
+
<div class="association-section dependencies-section">
|
|
1770
|
+
<h4 class="association-section-title">
|
|
1771
|
+
<i data-lucide="git-branch" class="w-4 h-4 inline"></i>
|
|
1772
|
+
${t('multiCli.dependencies') || 'Dependencies'}
|
|
1773
|
+
</h4>
|
|
1774
|
+
<div class="dependencies-grid">
|
|
1775
|
+
${dependencies.internal?.length ? `
|
|
1776
|
+
<div class="association-card dependency-card">
|
|
1777
|
+
<div class="card-header">
|
|
1778
|
+
<i data-lucide="folder" class="w-4 h-4"></i>
|
|
1779
|
+
<span class="card-title">${t('multiCli.internalDeps') || 'Internal'}</span>
|
|
1780
|
+
<span class="card-count">${dependencies.internal.length}</span>
|
|
1781
|
+
</div>
|
|
1782
|
+
<ul class="dependency-list">
|
|
1783
|
+
${dependencies.internal.map(dep => `<li>${escapeHtml(getI18nText(dep))}</li>`).join('')}
|
|
1784
|
+
</ul>
|
|
1785
|
+
</div>
|
|
1786
|
+
` : ''}
|
|
1787
|
+
${dependencies.external?.length ? `
|
|
1788
|
+
<div class="association-card dependency-card">
|
|
1789
|
+
<div class="card-header">
|
|
1790
|
+
<i data-lucide="package" class="w-4 h-4"></i>
|
|
1791
|
+
<span class="card-title">${t('multiCli.externalDeps') || 'External'}</span>
|
|
1792
|
+
<span class="card-count">${dependencies.external.length}</span>
|
|
1793
|
+
</div>
|
|
1794
|
+
<ul class="dependency-list">
|
|
1795
|
+
${dependencies.external.map(dep => `<li>${escapeHtml(getI18nText(dep))}</li>`).join('')}
|
|
1796
|
+
</ul>
|
|
1797
|
+
</div>
|
|
1798
|
+
` : ''}
|
|
1799
|
+
</div>
|
|
1800
|
+
</div>
|
|
1801
|
+
`);
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
// Consensus Card
|
|
1805
|
+
if (hasConsensus) {
|
|
1806
|
+
sections.push(`
|
|
1807
|
+
<div class="association-section consensus-section">
|
|
1808
|
+
<h4 class="association-section-title">
|
|
1809
|
+
<i data-lucide="check-check" class="w-4 h-4 inline"></i>
|
|
1810
|
+
${t('multiCli.consensus') || 'Consensus'}
|
|
1811
|
+
</h4>
|
|
1812
|
+
<div class="consensus-grid">
|
|
1813
|
+
${consensus.agreements?.length ? `
|
|
1814
|
+
<div class="association-card consensus-card agreements">
|
|
1815
|
+
<div class="card-header">
|
|
1816
|
+
<i data-lucide="thumbs-up" class="w-4 h-4"></i>
|
|
1817
|
+
<span class="card-title">${t('multiCli.agreements') || 'Agreements'}</span>
|
|
1818
|
+
<span class="card-count">${consensus.agreements.length}</span>
|
|
1819
|
+
</div>
|
|
1820
|
+
<ul class="consensus-list">
|
|
1821
|
+
${consensus.agreements.map(item => `<li class="agreement-item">${escapeHtml(item)}</li>`).join('')}
|
|
1822
|
+
</ul>
|
|
1823
|
+
</div>
|
|
1824
|
+
` : ''}
|
|
1825
|
+
${(consensus.resolved_conflicts?.length || consensus.disagreements?.length) ? `
|
|
1826
|
+
<div class="association-card consensus-card conflicts">
|
|
1827
|
+
<div class="card-header">
|
|
1828
|
+
<i data-lucide="git-merge" class="w-4 h-4"></i>
|
|
1829
|
+
<span class="card-title">${t('multiCli.resolvedConflicts') || 'Resolved Conflicts'}</span>
|
|
1830
|
+
<span class="card-count">${(consensus.resolved_conflicts || consensus.disagreements || []).length}</span>
|
|
1831
|
+
</div>
|
|
1832
|
+
<ul class="consensus-list">
|
|
1833
|
+
${(consensus.resolved_conflicts || consensus.disagreements || []).map(item => `<li class="conflict-item">${escapeHtml(item)}</li>`).join('')}
|
|
1834
|
+
</ul>
|
|
1835
|
+
</div>
|
|
1836
|
+
` : ''}
|
|
1837
|
+
</div>
|
|
1838
|
+
</div>
|
|
1839
|
+
`);
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
// Related Files (from existing Files tab logic)
|
|
1843
|
+
if (hasFiles) {
|
|
1844
|
+
sections.push(`
|
|
1845
|
+
<div class="association-section files-section">
|
|
1846
|
+
<h4 class="association-section-title">
|
|
1847
|
+
<i data-lucide="folder-tree" class="w-4 h-4 inline"></i>
|
|
1848
|
+
${t('multiCli.tab.files') || 'Related Files'}
|
|
1849
|
+
</h4>
|
|
1850
|
+
<div class="files-summary">
|
|
1851
|
+
${relatedFiles.fileTree?.length ? `
|
|
1852
|
+
<div class="files-list">
|
|
1853
|
+
${relatedFiles.fileTree.slice(0, 10).map(file => `
|
|
1854
|
+
<div class="file-item">
|
|
1855
|
+
<i data-lucide="file" class="w-3 h-3"></i>
|
|
1856
|
+
<span class="file-path">${escapeHtml(typeof file === 'string' ? file : file.path || file.name)}</span>
|
|
1857
|
+
</div>
|
|
1858
|
+
`).join('')}
|
|
1859
|
+
${relatedFiles.fileTree.length > 10 ? `
|
|
1860
|
+
<div class="files-more">+${relatedFiles.fileTree.length - 10} ${t('common.more') || 'more'}</div>
|
|
1861
|
+
` : ''}
|
|
1862
|
+
</div>
|
|
1863
|
+
` : ''}
|
|
1864
|
+
${relatedFiles.impactSummary?.length ? `
|
|
1865
|
+
<div class="impact-summary">
|
|
1866
|
+
<h5>${t('multiCli.impactSummary') || 'Impact Summary'}</h5>
|
|
1867
|
+
<ul>
|
|
1868
|
+
${relatedFiles.impactSummary.slice(0, 5).map(item => `<li>${escapeHtml(getI18nText(item))}</li>`).join('')}
|
|
1869
|
+
</ul>
|
|
1870
|
+
</div>
|
|
1871
|
+
` : ''}
|
|
1872
|
+
</div>
|
|
1873
|
+
</div>
|
|
1874
|
+
`);
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
return `
|
|
1878
|
+
<div class="multi-cli-association-section">
|
|
1879
|
+
${sections.join('')}
|
|
1880
|
+
</div>
|
|
1881
|
+
`;
|
|
1882
|
+
}
|
|
1883
|
+
|
|
71
1884
|
// Lite Task Detail Page
|
|
72
1885
|
function showLiteTaskDetailPage(sessionKey) {
|
|
73
1886
|
const session = liteTaskDataStore[sessionKey];
|