-
Notifications
You must be signed in to change notification settings - Fork 0
Modernize Export and AI Lab Interfaces #94
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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 = () => { | ||
| 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; | ||
| 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 = () => { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| 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"> | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| <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> | ||
| ); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.