Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import SidebarNav from '../components/SidebarNav';
import Header from '../components/Header';
import MobileDrawer from '../components/MobileDrawer';
import SearchPanel from '../features/search/SearchPanel';
import JobMetrics from '../components/JobMetrics';
import ErrorBoundary from '../components/ErrorBoundary';
import Editor from '../features/editor/Editor';
import { useMediaQuery } from '../hooks/useMediaQuery';
Expand Down Expand Up @@ -165,6 +166,8 @@ const AppContent: React.FC = () => {
/>
</div>
)}

<JobMetrics />
</div>
);
};
Expand Down
54 changes: 31 additions & 23 deletions src/components/JobMetrics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,30 +12,38 @@ const JobMetrics: React.FC = () => {
return () => clearInterval(interval);
}, []);

// Only show in development
if (!import.meta.env.DEV) {
return null;
}

return (
<div className="job-metrics-panel" style={{
position: 'fixed',
bottom: '10px',
right: '10px',
background: 'rgba(0, 0, 0, 0.8)',
color: '#fff',
padding: '10px',
borderRadius: '5px',
fontSize: '12px',
zIndex: 9999,
pointerEvents: 'none',
fontFamily: 'monospace'
}}>
<h4 style={{ margin: '0 0 5px 0', borderBottom: '1px solid #444' }}>Job Queue Metrics</h4>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '5px' }}>
<span>Queued:</span> <span>{metrics.queued}</span>
<span>Running:</span> <span>{metrics.running}</span>
<span>Completed:</span> <span>{metrics.completed}</span>
<span>Failed:</span> <span>{metrics.failed}</span>
<span>Cancelled:</span> <span>{metrics.cancelled}</span>
<span>Coalesced:</span> <span>{metrics.coalesced}</span>
<span>Avg Wait:</span> <span>{metrics.avgWaitTime.toFixed(0)}ms</span>
<span>Avg Exec:</span> <span>{metrics.avgExecutionTime.toFixed(0)}ms</span>
<div className="job-metrics-panel">
<h4>Job Queue Metrics</h4>
<div className="metrics-grid">
<span className="metric-label">Queued:</span>
<span className="metric-value">{metrics.queued}</span>

<span className="metric-label">Running:</span>
<span className="metric-value">{metrics.running}</span>

<span className="metric-label">Completed:</span>
<span className="metric-value">{metrics.completed}</span>

<span className="metric-label">Failed:</span>
<span className="metric-value">{metrics.failed}</span>

<span className="metric-label">Cancelled:</span>
<span className="metric-value">{metrics.cancelled}</span>

<span className="metric-label">Coalesced:</span>
<span className="metric-value">{metrics.coalesced}</span>

<span className="metric-label">Avg Wait:</span>
<span className="metric-value">{metrics.avgWaitTime.toFixed(0)}ms</span>

<span className="metric-label">Avg Exec:</span>
<span className="metric-value">{metrics.avgExecutionTime.toFixed(0)}ms</span>
</div>
</div>
);
Expand Down
115 changes: 101 additions & 14 deletions src/features/ai/AIHarness.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,105 @@
import React from 'react';

const AIHarness: React.FC = () => (
<div className="chat-view">
<h2>AI Harness</h2>
<div className="messages-list">
<div className="message assistant">
AI agent ready to assist with TRIZ analysis and knowledge synthesis.
import React, { useState } from 'react';

const AIHarness: React.FC = () => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

`AIHarness` has a cyclomatic complexity of 9 with "medium" risk


A function with high cyclomatic complexity can be hard to understand and
maintain. Cyclomatic complexity is a software metric that measures the number of
independent paths through a function. A higher cyclomatic complexity indicates
that the function has more decision points and is more complex.

const [activePanel, setActivePanel] = useState<'prompt' | 'context' | 'log' | 'artifacts'>('prompt');

return (
<div className="ai-harness-view">
<div className="ai-harness-header">
<div className="header-main">
<h2>AI Harness</h2>
<span className="experimental-badge">Experimental Lab</span>
</div>
<div className="model-status-banner">
<span className="status-dot warning"></span>
<span className="status-text">Local model unavailable. Configure provider in settings.</span>
</div>
</div>

<div className="harness-layout">
<nav className="harness-tabs">
<button
className={activePanel === 'prompt' ? 'active' : ''}
onClick={() => setActivePanel('prompt')}
>
Prompt
</button>
<button
className={activePanel === 'context' ? 'active' : ''}
onClick={() => setActivePanel('context')}
>
Context
</button>
<button
className={activePanel === 'log' ? 'active' : ''}
onClick={() => setActivePanel('log')}
>
Run Log
</button>
<button
className={activePanel === 'artifacts' ? 'active' : ''}
onClick={() => setActivePanel('artifacts')}
>
Artifacts
</button>
</nav>

<div className="harness-content">
{activePanel === 'prompt' && (
<div className="panel prompt-panel">
<div className="panel-header">
<h3>System & User Prompt</h3>
</div>
<textarea
className="prompt-editor"
placeholder="Enter prompt templates..."
disabled
/>
<div className="panel-footer">
<button className="primary" disabled>
Execute Chain (Behavior Pending)
</button>
</div>
</div>
)}

{activePanel === 'context' && (
<div className="panel context-panel">
<div className="panel-header">
<h3>Retrieved Context</h3>
</div>
<div className="empty-panel-state">
No context retrieved. Run a chain to see RAG results.
</div>
</div>
)}

{activePanel === 'log' && (
<div className="panel log-panel">
<div className="panel-header">
<h3>Execution Trace</h3>
</div>
<div className="log-entries">
<div className="log-entry info">Lab environment initialized.</div>
<div className="log-entry warning">Waiting for model configuration...</div>
</div>
</div>
)}

{activePanel === 'artifacts' && (
<div className="panel artifacts-panel">
<div className="panel-header">
<h3>Generated Artifacts</h3>
</div>
<div className="empty-panel-state">
No artifacts generated in this session.
</div>
</div>
)}
</div>
</div>
</div>
<div className="chat-controls">
<input type="text" placeholder="Ask the AI agent..." />
<button className="primary">Send</button>
</div>
</div>
);
);
};

export default AIHarness;
150 changes: 140 additions & 10 deletions src/features/export/ExportPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,160 @@
import React, { useEffect } from 'react';
import React, { useEffect, useState } from 'react';
import { jobCoordinator } from '../../lib/jobs';
import { logger } from '../../lib/logger';
import { repository } from '../../db/repository';

type ExportFormat = 'markdown' | 'json' | 'sqlite' | 'graph' | 'site';
type ExportScope = 'all' | 'selected' | 'neighborhood' | 'search';

interface Stats {
entities: number;
claims: number;
links: number;
}

const ExportPanel: React.FC = () => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

`ExportPanel` has a cyclomatic complexity of 6 with "medium" risk


A function with high cyclomatic complexity can be hard to understand and
maintain. Cyclomatic complexity is a software metric that measures the number of
independent paths through a function. A higher cyclomatic complexity indicates
that the function has more decision points and is more complex.

const [selectedFormat, setSelectedFormat] = useState<ExportFormat>('markdown');
const [selectedScope, setSelectedScope] = useState<ExportScope>('all');
const [stats, setStats] = useState<Stats>({ entities: 0, claims: 0, links: 0 });
const [exportStatus, setExportStatus] = useState<'idle' | 'running' | 'completed' | 'failed'>('idle');
const [error, setError] = useState<string | null>(null);

useEffect(() => {
const fetchStats = async () => {
try {
const [entities, claims, links] = await Promise.all([
repository.getAllEntities(),
repository.getAllClaims(),
repository.getAllLinks(),
]);
setStats({
entities: entities.length,
claims: claims.length,
links: links.length,
});
} catch (err) {
logger.error('Failed to fetch stats for export', err);
}
};

fetchStats();

jobCoordinator.registerHandler('prepare-export', async (payload) => {
const { format } = payload as { format: string };
logger.info(`Preparing export for format: ${format}`);
// Simulate expensive work
await new Promise(resolve => setTimeout(resolve, 2000));
logger.info(`Export prepared: ${format}`);
});

return () => {
jobCoordinator.unregisterHandler('prepare-export');
};
}, []);

const handleExport = (format: string) => {
jobCoordinator.enqueue('prepare-export', format, { format });
const handleExport = async () => {
setExportStatus('running');
setError(null);
try {
jobCoordinator.enqueue('prepare-export', selectedFormat, {
format: selectedFormat,
scope: selectedScope,
});
// In a real app, we would listen for the specific job completion
// For this demo, we'll simulate the wait
await new Promise(resolve => setTimeout(resolve, 2500));
setExportStatus('completed');
} catch (err) {
setExportStatus('failed');
setError(err instanceof Error ? err.message : 'Export failed');
}
};

const formats: { id: ExportFormat; label: string; description: string }[] = [
{ id: 'markdown', label: 'Markdown', description: 'Interconnected .md files with wikilinks' },
{ id: 'json', label: 'JSON', description: 'Machine-readable knowledge graph' },
{ id: 'sqlite', label: 'SQLite Snapshot', description: 'Full offline storage backup' },
{ id: 'graph', label: 'Graph Data', description: 'D3/Sigma compatible JSON' },
{ id: 'site', label: 'Static Site', description: 'Self-hosted web exploration' },
];

return (
<div className="editor-container">
<h2>Export</h2>
<p>Export your knowledge base to various formats.</p>
<div className="toolbar">
<button className="primary" onClick={() => handleExport('markdown')}>Export as Markdown</button>
<button onClick={() => handleExport('json')}>Export as JSON</button>
<button onClick={() => handleExport('site')}>Export as Static Site</button>
<div className="export-view">
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JSX tree is too deeply nested. Found 5 levels of nesting


Nesting JSX elements too deeply can confuse developers reading the code. To make maintenance and refactoring easier, DeepSource recommends limiting the maximum JSX tree depth to 4.

<div className="export-header">
<h2>Export & Sync</h2>
<p className="export-intro">
Your local SQLite/OPFS storage is the canonical source of truth.
Use exports to share knowledge or create portable artifacts.
</p>
</div>

<section className="export-section">
<h3>1. Select Format</h3>
<div className="format-grid">
{formats.map((f) => (
<button
key={f.id}
className={`format-card ${selectedFormat === f.id ? 'selected' : ''}`}
onClick={() => setSelectedFormat(f.id)}
>
<span className="format-label">{f.label}</span>
<span className="format-desc">{f.description}</span>
</button>
))}
</div>
</section>

<section className="export-section">
<h3>2. Define Scope</h3>
<div className="scope-selector">
{(['all', 'selected', 'neighborhood', 'search'] as ExportScope[]).map((s) => (
<button
key={s}
className={`filter-chip ${selectedScope === s ? 'active' : ''}`}
onClick={() => setSelectedScope(s)}
>
{s.charAt(0).toUpperCase() + s.slice(1)}
</button>
))}
</div>
</section>

<div className="export-footer">
<div className="export-stats">
<div className="stat-item">
<span className="stat-value">{stats.entities}</span>
<span className="stat-label">Entities</span>
</div>
<div className="stat-item">
<span className="stat-value">{stats.claims}</span>
<span className="stat-label">Claims</span>
</div>
<div className="stat-item">
<span className="stat-value">{stats.links}</span>
<span className="stat-label">Links</span>
</div>
</div>

<div className="export-actions">
<button
className={`primary export-button ${exportStatus === 'running' ? 'loading' : ''}`}
onClick={handleExport}
disabled={exportStatus === 'running'}
>
{exportStatus === 'running' ? 'Preparing Artifacts...' : 'Generate Export'}
</button>

{exportStatus === 'completed' && (
<div className="status-message success">
Export complete! Artifacts available in Lab/Downloads.
</div>
)}
{exportStatus === 'failed' && (
<div className="status-message error">
{error || 'Export failed. Check logs for details.'}
</div>
)}
</div>
</div>
</div>
);
Expand Down
Loading
Loading