pageparse.ai / frontend /src /components /CodeEditor.tsx
Varun2007's picture
initial clean deployment commit with compilers
8c3e275
Raw
History Blame Contribute Delete
11.1 kB
import { useState, useEffect, type KeyboardEvent } from 'react';
import { PageParseAPI } from '../services/api';
import { Code, Sparkles, Copy, Check, FileCode, Terminal, Braces, Globe, Database, Cpu, Coffee, Wifi, WifiOff, Settings, RefreshCw } from 'lucide-react';
const LANGUAGES = [
{ value: 'auto', label: 'Auto-Detect' },
{ value: 'python', label: 'Python' },
{ value: 'javascript', label: 'JavaScript' },
{ value: 'typescript', label: 'TypeScript' },
{ value: 'java', label: 'Java' },
{ value: 'cpp', label: 'C/C++' },
{ value: 'rust', label: 'Rust' },
{ value: 'go', label: 'Go' },
{ value: 'ruby', label: 'Ruby' },
{ value: 'bash', label: 'Bash/Shell' },
{ value: 'html', label: 'HTML' },
{ value: 'css', label: 'CSS' },
{ value: 'sql', label: 'SQL' },
];
const LANG_ICONS: Record<string, typeof Braces> = {
python: Terminal,
javascript: Braces,
typescript: Braces,
java: Coffee,
cpp: FileCode,
rust: Cpu,
go: Globe,
html: Globe,
css: FileCode,
sql: Database,
bash: Terminal,
};
interface CodeAnalysisResult {
language: string;
explanation: string;
summary: string;
}
export const CodeEditor = () => {
const [code, setCode] = useState('');
const [selectedLanguage, setSelectedLanguage] = useState('auto');
const [result, setResult] = useState<CodeAnalysisResult | null>(null);
const [isAnalyzing, setIsAnalyzing] = useState(false);
const [error, setError] = useState<string | null>(null);
const [copied, setCopied] = useState(false);
const [ollamaConnected, setOllamaConnected] = useState(false);
const [ollamaModel, setOllamaModel] = useState('');
const [ollamaUrl, setOllamaUrl] = useState('http://localhost:11434');
const [ollamaVersion, setOllamaVersion] = useState('');
const [modelAvailable, setModelAvailable] = useState(false);
const [checkingStatus, setCheckingStatus] = useState(true);
const [showConfig, setShowConfig] = useState(false);
const [configUrl, setConfigUrl] = useState('');
const [configModel, setConfigModel] = useState('');
const [availableModels, setAvailableModels] = useState<string[]>([]);
const [saving, setSaving] = useState(false);
const [configSaved, setConfigSaved] = useState(false);
const checkStatus = async () => {
setCheckingStatus(true);
try {
const status = await PageParseAPI.checkOllamaStatus();
setOllamaConnected(status.connected);
setOllamaModel(status.model);
setOllamaUrl(status.url);
setOllamaVersion(status.version);
setModelAvailable(status.model_available);
setAvailableModels(status.models || []);
setConfigUrl(status.url);
setConfigModel(status.model);
} catch {
setOllamaConnected(false);
}
setCheckingStatus(false);
};
useEffect(() => { checkStatus(); }, []);
const handleSaveConfig = async () => {
setSaving(true);
try {
const res = await PageParseAPI.configureOllama(configUrl, configModel);
setOllamaUrl(res.url);
setOllamaModel(res.model);
setConfigSaved(true);
setTimeout(() => setConfigSaved(false), 2000);
setTimeout(() => checkStatus(), 1000);
} catch (err: any) {
setError(err.message || 'Failed to save config');
}
setSaving(false);
};
const handleAnalyze = async () => {
if (!code.trim()) return;
setIsAnalyzing(true);
setError(null);
setResult(null);
try {
const res = await PageParseAPI.analyzeCode(code, selectedLanguage);
setResult(res);
} catch (err: any) {
setError(err.message || 'Analysis failed');
}
setIsAnalyzing(false);
};
const handleCopy = async () => {
if (!result) return;
const text = `Language: ${result.language}\n\nExplanation:\n${result.explanation}\n\nSummary:\n${result.summary}`;
await navigator.clipboard.writeText(text);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
const handleKeyDown = (e: KeyboardEvent<HTMLTextAreaElement>) => {
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
e.preventDefault();
handleAnalyze();
}
};
const snippetCount = code.trim() ? code.trim().split(/\s+/).length : 0;
const lineCount = code ? code.split('\n').length : 0;
const IconComponent = result && LANG_ICONS[result.language] ? LANG_ICONS[result.language] : null;
return (
<div className="section">
<div className="section-header">
<h3><Code size={16} className="text-secondary" /> Code Paste & Analyze</h3>
<div className="flex gap-3 items-center">
<select value={selectedLanguage} onChange={e => setSelectedLanguage(e.target.value)} className="select">
{LANGUAGES.map(opt => <option key={opt.value} value={opt.value}>{opt.label}</option>)}
</select>
</div>
</div>
<div className="section-body">
<div className="ollama-status-bar">
<div className="flex items-center gap-2">
{checkingStatus ? (
<span className="text-xs text-tertiary flex items-center gap-1">
<span className="animate-pulse">Checking Ollama connection...</span>
</span>
) : ollamaConnected ? (
<span className="text-xs flex items-center gap-1" style={{ color: 'var(--success)' }}>
<Wifi size={12} />
<span>
Ollama {ollamaVersion && `v${ollamaVersion}`} connected
{modelAvailable
? ` (${ollamaModel})`
: ` — model "${ollamaModel}" not pulled yet`}
</span>
{!modelAvailable && (
<span className="text-xs" style={{ color: 'var(--warning)' }}>
Run: ollama pull {ollamaModel}
</span>
)}
</span>
) : (
<span className="text-xs flex items-center gap-1" style={{ color: 'var(--danger)' }}>
<WifiOff size={12} />
<span>Ollama not reachable at {ollamaUrl} — using local fallback</span>
</span>
)}
</div>
<div className="flex items-center gap-2">
<button className="btn btn-sm" onClick={checkStatus} title="Refresh connection status">
<RefreshCw size={12} />
</button>
<button className="btn btn-sm" onClick={() => setShowConfig(!showConfig)} title="Configure Ollama">
<Settings size={12} />
</button>
</div>
</div>
{showConfig && (
<div className="ollama-config-panel">
<div className="flex flex-col gap-3">
<div>
<label className="text-xs font-semibold text-tertiary" style={{ textTransform: 'uppercase', letterSpacing: '0.5px' }}>Ollama URL</label>
<input className="input" value={configUrl} onChange={e => setConfigUrl(e.target.value)} placeholder="http://localhost:11434" style={{ width: '100%', marginTop: 4 }} />
</div>
<div>
<label className="text-xs font-semibold text-tertiary" style={{ textTransform: 'uppercase', letterSpacing: '0.5px' }}>Model Name</label>
<div className="flex gap-2 items-center" style={{ marginTop: 4 }}>
<input className="input" value={configModel} onChange={e => setConfigModel(e.target.value)} placeholder="llama3.2:1b" style={{ flex: 1 }} list="ollama-models" />
<datalist id="ollama-models">
{availableModels.map(m => <option key={m} value={m} />)}
</datalist>
<button className="btn btn-primary btn-sm" onClick={handleSaveConfig} disabled={saving}>
{saving ? 'Saving...' : configSaved ? <><Check size={12} /> Saved</> : 'Save'}
</button>
</div>
</div>
</div>
</div>
)}
<div className="code-editor-container" style={{ marginTop: 8 }}>
<div className="code-editor-header">
<span className="text-xs text-tertiary">
{lineCount} lines &middot; {snippetCount} tokens
</span>
<span className="text-xs text-tertiary">
{code.length > 0 && `${code.length} chars`}
</span>
</div>
<textarea
className="code-textarea"
value={code}
onChange={e => setCode(e.target.value)}
onKeyDown={handleKeyDown}
placeholder={`Paste or type code here...\n\nThe SLM will explain what it does and summarize its purpose.\n\nSupports: Python, JavaScript, TypeScript, Java, C/C++, Rust, Go, Ruby, Bash, HTML, CSS, SQL, and more.`}
spellCheck={false}
rows={14}
/>
<div className="code-editor-actions">
<button
className="btn btn-primary"
onClick={handleAnalyze}
disabled={!code.trim() || isAnalyzing}
>
{isAnalyzing ? (
<span className="flex items-center gap-2">
<span className="animate-pulse">Analyzing...</span>
</span>
) : (
<span className="flex items-center gap-2">
<Sparkles size={14} />
Explain & Summarize
</span>
)}
</button>
<span className="text-xs text-tertiary">Ctrl+Enter to run</span>
</div>
</div>
{error && (
<div className="alert alert-error" style={{ marginTop: 16 }}>
<span>{error}</span>
</div>
)}
{result && (
<div className="code-analysis-result">
<div className="result-header">
<div className="result-language-badge">
{IconComponent ? (
<span className="flex items-center gap-1">
<IconComponent size={14} />
<span>{result.language.charAt(0).toUpperCase() + result.language.slice(1)}</span>
</span>
) : (
<span className="flex items-center gap-1">
<FileCode size={14} />
<span>{result.language.charAt(0).toUpperCase() + result.language.slice(1)}</span>
</span>
)}
</div>
<button className="btn btn-sm" onClick={handleCopy} title="Copy analysis">
{copied ? <Check size={14} /> : <Copy size={14} />}
</button>
</div>
<div className="result-summary">
<div className="result-label">Summary</div>
<p className="result-text">{result.summary}</p>
</div>
<div className="result-explanation">
<div className="result-label">Explanation</div>
<p className="result-text">{result.explanation}</p>
</div>
</div>
)}
</div>
</div>
);
};