esfiles / frontend /src /App.tsx
Besjon Cifliku
feat: implement anomaly detection to filter suspicious word relations
9c3ade2
Raw
History Blame Contribute Delete
6.9 kB
import { useState, useEffect, Fragment } from "react";
import type { CorpusStats } from "./types";
import { api, checkConnection } from "./api";
import TrainingPanel from "./components/TrainingPanel";
import EngineSetup from "./components/EngineSetup";
import SemanticSearch from "./components/SemanticSearch";
import TextCompare from "./components/TextCompare";
import SimilarWords from "./components/SimilarWords";
import ContextAnalysis from "./components/ContextAnalysis";
import AnomalyPanel from "./components/AnomalyPanel";
import Word2VecPanel from "./components/Word2VecPanel";
import DatasetPanel from "./components/DatasetPanel";
import MetricCard from "./components/MetricCard";
import "./styles.css";
type NavGroup = "data" | "training";
type TrainingTab = "model" | "w2v";
type AnalysisTab = "context" | "anomalies";
const STEPS: { id: NavGroup; label: string }[] = [
{ id: "data", label: "Data & Setup" },
{ id: "training", label: "Training" },
];
const TRAINING_TABS: { id: TrainingTab; label: string }[] = [
{ id: "model", label: "Fine-tune Model" },
{ id: "w2v", label: "Word2Vec Baseline" },
];
const ANALYSIS_TABS: { id: AnalysisTab; label: string }[] = [
{ id: "context", label: "Context" },
{ id: "anomalies", label: "Anomalies" },
];
export default function App() {
const [group, setGroup] = useState<NavGroup>("data");
const [trainingTab, setTrainingTab] = useState<TrainingTab>("w2v");
const [analysisTab, setAnalysisTab] = useState<AnalysisTab>("context");
const [stats, setStats] = useState<CorpusStats | null>(null);
const [showManualSetup, setShowManualSetup] = useState(false);
const [serverError, setServerError] = useState<string | null>(null);
const [w2vReady, setW2vReady] = useState(false);
const [w2vInfo, setW2vInfo] = useState<{ vocab_size: number; sentences: number; vector_size: number } | null>(null);
useEffect(() => {
checkConnection().then((err) => {
setServerError(err);
if (!err) {
api.getStats().then(setStats).catch(() => {});
api.w2vStatus().then(res => {
if (res.ready) {
setW2vReady(true);
setW2vInfo({ vocab_size: res.vocab_size!, sentences: res.sentences!, vector_size: res.vector_size! });
setGroup("training");
}
}).catch(() => {});
}
});
const interval = setInterval(() => {
checkConnection().then(setServerError);
}, 15000);
return () => clearInterval(interval);
}, []);
function handleW2vReady(ready: boolean, info?: { vocab_size: number; sentences: number; vector_size: number }) {
setW2vReady(ready);
setW2vInfo(ready && info ? info : null);
}
return (
<div className="app">
<header className="app-header">
<h1>Contextual Similarity Engine</h1>
{stats && (
<div className="header-stats">
<span className="badge">{stats.model_name}</span>
<span className="badge">{stats.total_documents} docs</span>
<span className="badge">{stats.total_chunks} chunks</span>
</div>
)}
</header>
{serverError && (
<div className="server-error-banner">
<strong>Server unavailable:</strong> {serverError}
</div>
)}
{/* Progress Stepper — hidden once training is complete */}
{!w2vReady && (
<nav className="stepper">
{STEPS.map((step, i) => {
const active = group === step.id;
const done = (step.id === "data" && stats !== null && stats.index_built)
|| (step.id === "training" && w2vReady);
return (
<Fragment key={step.id}>
{i > 0 && <div className="stepper-line stepper-line-active" />}
<div className="stepper-item">
<button
className={`stepper-circle ${active ? "stepper-active" : ""} ${done && !active ? "stepper-done" : ""}`}
onClick={() => setGroup(step.id)}
>
{done && !active ? "✓" : i + 1}
</button>
<span className={`stepper-label ${active ? "stepper-label-active" : ""}`}>
{step.label}
</span>
</div>
</Fragment>
);
})}
</nav>
)}
{/* Content */}
<main className="content">
{group === "data" && (
<>
<DatasetPanel onStatsUpdate={setStats} onLoaded={() => setGroup("training")} />
<button
className="collapsible-toggle"
onClick={() => setShowManualSetup(!showManualSetup)}
>
<span className="collapsible-arrow">{showManualSetup ? "▾" : "▸"}</span>
Or add documents manually
</button>
{showManualSetup && <EngineSetup onStatsUpdate={setStats} />}
</>
)}
{group === "training" && !w2vReady && (
<>
<nav className="subtabs">
{TRAINING_TABS.map((t) => (
<button
key={t.id}
className={`subtab ${trainingTab === t.id ? "subtab-active" : ""}`}
onClick={() => setTrainingTab(t.id)}
>
{t.label}
</button>
))}
</nav>
{trainingTab === "model" && <TrainingPanel />}
{trainingTab === "w2v" && <Word2VecPanel onReady={handleW2vReady} />}
</>
)}
{group === "training" && w2vReady && w2vInfo && (
<>
<div className="panel">
<h2 style={{ marginTop: 0 }}>Trained Corpus</h2>
<p className="panel-desc">
Word2Vec model is trained and persisted. Use the tools below to explore similarity.
</p>
<div className="metric-grid">
<MetricCard value={w2vInfo.vocab_size} label="Vocabulary" />
<MetricCard value={w2vInfo.sentences} label="Sentences" />
<MetricCard value={w2vInfo.vector_size} label="Dimensions" />
</div>
</div>
<nav className="subtabs">
{ANALYSIS_TABS.map((t) => (
<button
key={t.id}
className={`subtab ${analysisTab === t.id ? "subtab-active" : ""}`}
onClick={() => setAnalysisTab(t.id)}
>
{t.label}
</button>
))}
</nav>
{analysisTab === "context" && (
<>
<SimilarWords />
<TextCompare />
<SemanticSearch />
<ContextAnalysis />
</>
)}
{analysisTab === "anomalies" && <AnomalyPanel />}
</>
)}
</main>
</div>
);
}