Add exploration field rendering helpers for dynamic content display

- Implemented `renderExpField` to handle various data types for exploration fields.
- Created `renderExpArray` to format arrays, including support for objects with specific properties.
- Developed `renderExpObject` for recursive rendering of object values, filtering out private keys.
- Introduced HTML escaping for safe rendering of user-generated content.
This commit is contained in:
catlog22
2025-12-07 18:07:28 +08:00
parent 26a325efff
commit a6f9701679
6 changed files with 201 additions and 4549 deletions

View File

@@ -0,0 +1,52 @@
// Helper: Render exploration field with smart type detection
function renderExpField(label, value) {
if (value === null || value === undefined) return '';
let rendered;
if (typeof value === 'string') {
rendered = `<p>${escapeHtml(value)}</p>`;
} else if (Array.isArray(value)) {
rendered = renderExpArray(value);
} else if (typeof value === 'object') {
rendered = renderExpObject(value);
} else {
rendered = `<p>${escapeHtml(String(value))}</p>`;
}
return `<div class="exp-field"><label>${escapeHtml(label)}</label>${rendered}</div>`;
}
// Helper: Render array values
function renderExpArray(arr) {
if (!arr.length) return '<p>-</p>';
if (typeof arr[0] === 'object' && arr[0] !== null) {
return `<div class="exp-array-objects">${arr.map(item => {
if (item.question) {
return `<div class="clarification-item">
<div class="clarification-question">${escapeHtml(item.question)}</div>
${item.impact ? `<div class="clarification-impact">Impact: ${escapeHtml(item.impact)}</div>` : ''}
${item.priority ? `<span class="priority-badge priority-${item.priority}">${item.priority}</span>` : ''}
</div>`;
}
return `<div class="exp-object-item">${renderExpObject(item)}</div>`;
}).join('')}</div>`;
}
return `<ul class="exp-list">${arr.map(item => `<li>${escapeHtml(String(item))}</li>`).join('')}</ul>`;
}
// Helper: Render object values recursively
function renderExpObject(obj) {
if (!obj || typeof obj !== 'object') return '';
const entries = Object.entries(obj).filter(([k]) => !k.startsWith('_'));
if (!entries.length) return '<p>-</p>';
return `<div class="exp-object">${entries.map(([key, val]) => {
const label = key.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
if (val === null || val === undefined) return '';
if (typeof val === 'string') {
return `<div class="exp-obj-field"><span class="exp-obj-key">${escapeHtml(label)}:</span> <span class="exp-obj-val">${escapeHtml(val)}</span></div>`;
} else if (Array.isArray(val)) {
return `<div class="exp-obj-field"><span class="exp-obj-key">${escapeHtml(label)}:</span>${renderExpArray(val)}</div>`;
} else if (typeof val === 'object') {
return `<div class="exp-obj-nested"><span class="exp-obj-key">${escapeHtml(label)}</span>${renderExpObject(val)}</div>`;
}
return `<div class="exp-obj-field"><span class="exp-obj-key">${escapeHtml(label)}:</span> <span class="exp-obj-val">${escapeHtml(String(val))}</span></div>`;
}).join('')}</div>`;
}

View File

@@ -986,25 +986,25 @@ function renderSessionContextContent(context, explorations, conflictResolution)
window._currentContextJson = contextJson;
// Use existing renderContextContent for detailed rendering
sections.push(\`
sections.push(`
<div class="session-context-section">
\${renderContextContent(context)}
${renderContextContent(context)}
</div>
\`);
`);
}
// If we have any sections, wrap them
if (sections.length > 0) {
return \`<div class="context-tab-content session-context-combined">\${sections.join('')}</div>\`;
return `<div class="context-tab-content session-context-combined">${sections.join('')}</div>`;
}
return \`
return `
<div class="tab-empty-state">
<div class="empty-icon">📦</div>
<div class="empty-title">No Context Data</div>
<div class="empty-text">No context-package.json, exploration files, or conflict resolution data found for this session.</div>
</div>
\`;
`;
}
// ==========================================
@@ -1019,79 +1019,79 @@ function renderConflictResolutionContext(conflictResolution) {
let sections = [];
// Header
sections.push(\`
sections.push(`
<div class="conflict-resolution-header">
<h4>⚖️ Conflict Resolution Decisions</h4>
<div class="conflict-meta">
<span class="meta-item">Session: <strong>\${escapeHtml(conflictResolution.session_id || 'N/A')}</strong></span>
\${conflictResolution.resolved_at ? \`<span class="meta-item">Resolved: <strong>\${formatDate(conflictResolution.resolved_at)}</strong></span>\` : ''}
<span class="meta-item">Session: <strong>${escapeHtml(conflictResolution.session_id || 'N/A')}</strong></span>
${conflictResolution.resolved_at ? `<span class="meta-item">Resolved: <strong>${formatDate(conflictResolution.resolved_at)}</strong></span>` : ''}
</div>
</div>
\`);
`);
// User decisions
if (conflictResolution.user_decisions && Object.keys(conflictResolution.user_decisions).length > 0) {
const decisions = Object.entries(conflictResolution.user_decisions);
sections.push(\`
sections.push(`
<div class="conflict-decisions-section collapsible-section">
<div class="collapsible-header">
<span class="collapse-icon">▶</span>
<span class="section-label">🎯 User Decisions (\${decisions.length})</span>
<span class="section-label">🎯 User Decisions (${decisions.length})</span>
</div>
<div class="collapsible-content collapsed">
<div class="decisions-list">
\${decisions.map(([key, decision]) => \`
${decisions.map(([key, decision]) => `
<div class="decision-item">
<div class="decision-header">
<span class="decision-key">\${escapeHtml(key.replace(/_/g, ' '))}</span>
<span class="decision-choice">\${escapeHtml(decision.choice || 'N/A')}</span>
<span class="decision-key">${escapeHtml(key.replace(/_/g, ' '))}</span>
<span class="decision-choice">${escapeHtml(decision.choice || 'N/A')}</span>
</div>
\${decision.description ? \`<p class="decision-description">\${escapeHtml(decision.description)}</p>\` : ''}
\${decision.implications && decision.implications.length > 0 ? \`
${decision.description ? `<p class="decision-description">${escapeHtml(decision.description)}</p>` : ''}
${decision.implications && decision.implications.length > 0 ? `
<div class="decision-implications">
<span class="implications-label">Implications:</span>
<ul class="implications-list">
\${decision.implications.map(impl => \`<li>\${escapeHtml(impl)}</li>\`).join('')}
${decision.implications.map(impl => `<li>${escapeHtml(impl)}</li>`).join('')}
</ul>
</div>
\` : ''}
` : ''}
</div>
\`).join('')}
`).join('')}
</div>
</div>
</div>
\`);
`);
}
// Resolved conflicts
if (conflictResolution.resolved_conflicts && conflictResolution.resolved_conflicts.length > 0) {
sections.push(\`
sections.push(`
<div class="resolved-conflicts-section collapsible-section">
<div class="collapsible-header">
<span class="collapse-icon">▶</span>
<span class="section-label">✅ Resolved Conflicts (\${conflictResolution.resolved_conflicts.length})</span>
<span class="section-label">✅ Resolved Conflicts (${conflictResolution.resolved_conflicts.length})</span>
</div>
<div class="collapsible-content collapsed">
<div class="conflicts-list">
\${conflictResolution.resolved_conflicts.map(conflict => \`
${conflictResolution.resolved_conflicts.map(conflict => `
<div class="resolved-conflict-item">
<div class="conflict-row">
<span class="conflict-id">\${escapeHtml(conflict.id || 'N/A')}</span>
<span class="conflict-category-badge">\${escapeHtml(conflict.category || 'General')}</span>
<span class="conflict-id">${escapeHtml(conflict.id || 'N/A')}</span>
<span class="conflict-category-badge">${escapeHtml(conflict.category || 'General')}</span>
</div>
<div class="conflict-brief">\${escapeHtml(conflict.brief || '')}</div>
<div class="conflict-brief">${escapeHtml(conflict.brief || '')}</div>
<div class="conflict-strategy">
<span class="strategy-label">Strategy:</span>
<span class="strategy-value">\${escapeHtml(conflict.strategy || 'N/A')}</span>
<span class="strategy-value">${escapeHtml(conflict.strategy || 'N/A')}</span>
</div>
</div>
\`).join('')}
`).join('')}
</div>
</div>
</div>
\`);
`);
}
return \`<div class="conflict-resolution-context">\${sections.join('')}</div>\`;
return `<div class="conflict-resolution-context">${sections.join('')}</div>`;
}

View File

@@ -281,14 +281,9 @@ function renderExplorationContext(explorations) {
function renderExplorationAngle(angle, data) {
let content = [];
// Project structure (architecture)
// Project structure - handle string or object
if (data.project_structure) {
content.push(`
<div class="exp-field">
<label>Project Structure</label>
<p>${escapeHtml(data.project_structure)}</p>
</div>
`);
content.push(renderExpField('Project Structure', data.project_structure));
}
// Relevant files
@@ -310,69 +305,30 @@ function renderExplorationAngle(angle, data) {
`);
}
// Patterns
// Patterns - handle string or object
if (data.patterns) {
content.push(`
<div class="exp-field">
<label>Patterns</label>
<p class="patterns-text">${escapeHtml(data.patterns)}</p>
</div>
`);
content.push(renderExpField('Patterns', data.patterns));
}
// Dependencies
// Dependencies - handle string or object
if (data.dependencies) {
content.push(`
<div class="exp-field">
<label>Dependencies</label>
<p>${escapeHtml(data.dependencies)}</p>
</div>
`);
content.push(renderExpField('Dependencies', data.dependencies));
}
// Integration points
// Integration points - handle string or object
if (data.integration_points) {
content.push(`
<div class="exp-field">
<label>Integration Points</label>
<p>${escapeHtml(data.integration_points)}</p>
</div>
`);
content.push(renderExpField('Integration Points', data.integration_points));
}
// Constraints
// Constraints - handle string or object
if (data.constraints) {
content.push(`
<div class="exp-field">
<label>Constraints</label>
<p>${escapeHtml(data.constraints)}</p>
</div>
`);
content.push(renderExpField('Constraints', data.constraints));
}
// Clarification needs
if (data.clarification_needs && data.clarification_needs.length) {
content.push(`
<div class="exp-field">
<label>Clarification Needs</label>
<div class="clarification-list">
${data.clarification_needs.map(c => `
<div class="clarification-item">
<div class="clarification-question">${escapeHtml(c.question)}</div>
${c.options && c.options.length ? `
<div class="clarification-options">
${c.options.map((opt, i) => `
<span class="option-badge ${i === c.recommended ? 'recommended' : ''}">${escapeHtml(opt)}</span>
`).join('')}
</div>
` : ''}
</div>
`).join('')}
</div>
</div>
`);
// Clarification needs - handle array or object
if (data.clarification_needs) {
content.push(renderExpField('Clarification Needs', data.clarification_needs));
}
const result = content.join('') || '<p>No data available</p>';
return result;
return content.join('') || '<p>No data available</p>';
}