Add YouTube transcripts tab to Mission Control

This commit is contained in:
Horus
2026-02-27 15:00:38 +01:00
parent 0a4853bcda
commit d7cd81b293
18 changed files with 3517 additions and 12 deletions
+440
View File
@@ -0,0 +1,440 @@
"use client";
import { useState, useEffect, useCallback } from "react";
import Card from "@/components/ui/Card";
const today = () => new Date().toISOString().split("T")[0];
type Severity = "low" | "medium" | "high" | "critical";
type BugStatus = "open" | "closed";
interface Bug {
description: string;
severity: Severity;
status: BugStatus;
}
interface FormState {
date: string;
testsRun: string[];
bugsFound: Bug[];
codeQualityScore: number;
recommendations: string[];
}
const defaultForm: FormState = {
date: today(),
testsRun: [""],
bugsFound: [],
codeQualityScore: 0,
recommendations: [""],
};
const severityColors: Record<Severity, string> = {
low: "text-emerald-400 bg-emerald-500/10 border-emerald-500/30",
medium: "text-amber-400 bg-amber-500/10 border-amber-500/30",
high: "text-orange-400 bg-orange-500/10 border-orange-500/30",
critical: "text-red-400 bg-red-500/10 border-red-500/30",
};
export default function AmunPage() {
const [form, setForm] = useState<FormState>(defaultForm);
const [saving, setSaving] = useState(false);
const [saved, setSaved] = useState(false);
const [errors, setErrors] = useState<Record<string, string>>({});
const [loadingDate, setLoadingDate] = useState(false);
const [history, setHistory] = useState<FormState[]>([]);
const loadSessionForDate = useCallback(async (date: string) => {
setLoadingDate(true);
try {
const res = await fetch(`/api/amun?date=${date}`);
const data = await res.json();
if (data) {
setForm({
date: data.date,
testsRun: typeof data.testsRun === "string" ? JSON.parse(data.testsRun) : (data.testsRun || [""]),
bugsFound: typeof data.bugsFound === "string" ? JSON.parse(data.bugsFound) : (data.bugsFound || []),
codeQualityScore: data.codeQualityScore || 0,
recommendations: typeof data.recommendations === "string" ? JSON.parse(data.recommendations) : (data.recommendations || [""]),
});
} else {
setForm({ ...defaultForm, date });
}
} catch {
setForm({ ...defaultForm, date });
} finally {
setLoadingDate(false);
}
}, []);
const loadHistory = useCallback(async () => {
try {
const res = await fetch("/api/amun?limit=10");
const data = await res.json();
if (Array.isArray(data)) {
setHistory(
data.map((s) => ({
date: s.date,
testsRun: typeof s.testsRun === "string" ? JSON.parse(s.testsRun) : (s.testsRun || []),
bugsFound: typeof s.bugsFound === "string" ? JSON.parse(s.bugsFound) : (s.bugsFound || []),
codeQualityScore: s.codeQualityScore || 0,
recommendations: typeof s.recommendations === "string" ? JSON.parse(s.recommendations) : (s.recommendations || []),
}))
);
}
} catch {
// ignore
}
}, []);
useEffect(() => {
loadSessionForDate(today());
loadHistory();
}, [loadSessionForDate, loadHistory]);
const validate = (): boolean => {
const errs: Record<string, string> = {};
if (form.codeQualityScore < 0 || form.codeQualityScore > 100) {
errs.codeQualityScore = "Score must be between 0 and 100";
}
setErrors(errs);
return Object.keys(errs).length === 0;
};
const handleSave = async () => {
if (!validate()) return;
setSaving(true);
try {
const res = await fetch("/api/amun", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(form),
});
if (!res.ok) {
const err = await res.json();
setErrors({ submit: err.error || "Failed to save" });
} else {
setSaved(true);
setTimeout(() => setSaved(false), 3000);
loadHistory();
}
} catch {
setErrors({ submit: "Network error. Please try again." });
} finally {
setSaving(false);
}
};
const updateArrayItem = (field: "testsRun" | "recommendations", index: number, value: string) => {
setForm((prev) => {
const arr = [...prev[field]];
arr[index] = value;
return { ...prev, [field]: arr };
});
};
const addArrayItem = (field: "testsRun" | "recommendations") => {
setForm((prev) => ({ ...prev, [field]: [...prev[field], ""] }));
};
const removeArrayItem = (field: "testsRun" | "recommendations", index: number) => {
setForm((prev) => ({ ...prev, [field]: prev[field].filter((_, i) => i !== index) }));
};
const addBug = () => {
setForm((prev) => ({
...prev,
bugsFound: [...prev.bugsFound, { description: "", severity: "medium", status: "open" }],
}));
};
const updateBug = (index: number, field: keyof Bug, value: string) => {
setForm((prev) => {
const bugs = [...prev.bugsFound];
bugs[index] = { ...bugs[index], [field]: value as never };
return { ...prev, bugsFound: bugs };
});
};
const removeBug = (index: number) => {
setForm((prev) => ({ ...prev, bugsFound: prev.bugsFound.filter((_, i) => i !== index) }));
};
const scoreColor = (score: number) => {
if (score >= 80) return "text-emerald-400";
if (score >= 60) return "text-amber-400";
if (score >= 40) return "text-orange-400";
return "text-red-400";
};
const scoreBarColor = (score: number) => {
if (score >= 80) return "bg-emerald-500";
if (score >= 60) return "bg-amber-500";
if (score >= 40) return "bg-orange-500";
return "bg-red-500";
};
const openBugs = form.bugsFound.filter((b) => b.status === "open");
const closedBugs = form.bugsFound.filter((b) => b.status === "closed");
return (
<div className="p-6 max-w-4xl mx-auto">
{/* Header */}
<div className="flex items-center justify-between mb-6">
<div>
<h1 className="text-xl font-bold text-white flex items-center gap-2">
<span className="text-purple-400"></span> Amun Dev Agent
</h1>
<p className="text-sm text-slate-500 mt-0.5">Development testing & code quality dashboard</p>
</div>
<div className="flex items-center gap-3">
<input
type="date"
value={form.date}
onChange={(e) => {
setForm((prev) => ({ ...prev, date: e.target.value }));
loadSessionForDate(e.target.value);
}}
className="bg-[#1a1d27] border border-[#2a2d3a] rounded-lg px-3 py-2 text-sm text-slate-300 focus:outline-none focus:border-purple-500/50"
/>
<button
onClick={handleSave}
disabled={saving || loadingDate}
className="px-4 py-2 bg-purple-600 hover:bg-purple-500 disabled:opacity-50 text-white font-semibold text-sm rounded-lg transition-colors"
>
{saving ? "Saving…" : saved ? "✓ Saved" : "Save Session"}
</button>
</div>
</div>
{errors.submit && (
<div className="mb-4 p-3 bg-red-500/10 border border-red-500/30 rounded-lg text-red-400 text-sm">
{errors.submit}
</div>
)}
{/* Stats Row */}
<div className="grid grid-cols-3 gap-4 mb-5">
<div className="bg-[#13151f] border border-[#1e2130] rounded-xl p-4">
<p className="text-xs text-slate-500 mb-1">Tests Run</p>
<p className="text-2xl font-bold text-white">{form.testsRun.filter((t) => t.trim()).length}</p>
</div>
<div className="bg-[#13151f] border border-[#1e2130] rounded-xl p-4">
<p className="text-xs text-slate-500 mb-1">Open Bugs</p>
<p className={`text-2xl font-bold ${openBugs.length > 0 ? "text-red-400" : "text-emerald-400"}`}>
{openBugs.length}
</p>
</div>
<div className="bg-[#13151f] border border-[#1e2130] rounded-xl p-4">
<p className="text-xs text-slate-500 mb-1">Code Quality</p>
<p className={`text-2xl font-bold ${scoreColor(form.codeQualityScore)}`}>
{form.codeQualityScore}%
</p>
</div>
</div>
<div className="space-y-5">
{/* Code Quality Score */}
<Card title="📊 Code Quality Score" accent="purple">
{errors.codeQualityScore && (
<p className="text-xs text-red-400 mb-2">{errors.codeQualityScore}</p>
)}
<div className="flex items-center gap-4">
<input
type="range"
min="0"
max="100"
value={form.codeQualityScore}
onChange={(e) => setForm((p) => ({ ...p, codeQualityScore: parseInt(e.target.value) }))}
className="flex-1 accent-purple-500"
/>
<div className="flex items-center gap-2">
<input
type="number"
min="0"
max="100"
value={form.codeQualityScore}
onChange={(e) =>
setForm((p) => ({
...p,
codeQualityScore: Math.min(100, Math.max(0, parseInt(e.target.value) || 0)),
}))
}
className="w-16 bg-[#0f1117] border border-[#2a2d3a] rounded-lg px-2 py-1.5 text-sm text-slate-200 focus:outline-none focus:border-purple-500/50 text-center"
/>
<span className="text-sm text-slate-500">%</span>
</div>
</div>
<div className="mt-3 h-2 bg-[#0f1117] rounded-full overflow-hidden border border-[#2a2d3a]">
<div
className={`h-full rounded-full transition-all duration-300 ${scoreBarColor(form.codeQualityScore)}`}
style={{ width: `${form.codeQualityScore}%` }}
/>
</div>
</Card>
{/* Tests Run */}
<Card title="🧪 Tests Run" accent="blue">
<div className="space-y-2">
{form.testsRun.map((t, i) => (
<div key={i} className="flex gap-2">
<span className="text-xs text-blue-500/70 font-mono mt-2.5 w-4"></span>
<input
type="text"
value={t}
onChange={(e) => updateArrayItem("testsRun", i, e.target.value)}
placeholder={`Test ${i + 1} (e.g. unit:auth, e2e:checkout)`}
className="flex-1 bg-[#0f1117] border border-[#2a2d3a] rounded-lg px-3 py-2 text-sm text-slate-200 focus:outline-none focus:border-blue-500/50 font-mono"
/>
{form.testsRun.length > 1 && (
<button
onClick={() => removeArrayItem("testsRun", i)}
className="text-slate-600 hover:text-red-400 transition-colors text-lg leading-none mt-1"
>
×
</button>
)}
</div>
))}
<button
onClick={() => addArrayItem("testsRun")}
className="text-xs text-blue-400 hover:text-blue-300 transition-colors mt-1"
>
+ Add test
</button>
</div>
</Card>
{/* Bugs Found */}
<Card title="🐛 Bugs Found" subtitle={`${openBugs.length} open · ${closedBugs.length} closed`} accent="red">
<div className="space-y-3">
{form.bugsFound.length === 0 && (
<p className="text-sm text-slate-600 italic">No bugs logged yet.</p>
)}
{form.bugsFound.map((bug, i) => (
<div key={i} className="p-3 bg-[#0f1117] rounded-lg border border-[#2a2d3a]">
<div className="flex gap-2 mb-2">
<input
type="text"
value={bug.description}
onChange={(e) => updateBug(i, "description", e.target.value)}
placeholder="Bug description"
className="flex-1 bg-[#13151f] border border-[#2a2d3a] rounded-lg px-3 py-2 text-sm text-slate-200 focus:outline-none focus:border-red-500/50"
/>
<button
onClick={() => removeBug(i)}
className="text-slate-600 hover:text-red-400 transition-colors text-lg leading-none px-1"
>
×
</button>
</div>
<div className="flex gap-2">
<select
value={bug.severity}
onChange={(e) => updateBug(i, "severity", e.target.value)}
className={`flex-1 bg-[#13151f] border rounded-lg px-3 py-1.5 text-xs focus:outline-none ${severityColors[bug.severity]}`}
>
<option value="low">Low</option>
<option value="medium">Medium</option>
<option value="high">High</option>
<option value="critical">Critical</option>
</select>
<button
onClick={() => updateBug(i, "status", bug.status === "open" ? "closed" : "open")}
className={`px-3 py-1.5 rounded-lg text-xs font-medium border transition-colors ${
bug.status === "open"
? "bg-red-500/10 border-red-500/30 text-red-400 hover:bg-red-500/20"
: "bg-emerald-500/10 border-emerald-500/30 text-emerald-400 hover:bg-emerald-500/20"
}`}
>
{bug.status === "open" ? "● Open" : "✓ Closed"}
</button>
</div>
</div>
))}
<button
onClick={addBug}
className="text-xs text-red-400 hover:text-red-300 transition-colors mt-1"
>
+ Log bug
</button>
</div>
</Card>
{/* Recommendations */}
<Card title="💡 Recommendations" accent="amber">
<div className="space-y-2">
{form.recommendations.map((r, i) => (
<div key={i} className="flex gap-2">
<span className="text-xs text-amber-500/70 font-mono mt-2.5 w-4"></span>
<input
type="text"
value={r}
onChange={(e) => updateArrayItem("recommendations", i, e.target.value)}
placeholder={`Recommendation ${i + 1}`}
className="flex-1 bg-[#0f1117] border border-[#2a2d3a] rounded-lg px-3 py-2 text-sm text-slate-200 focus:outline-none focus:border-amber-500/50"
/>
{form.recommendations.length > 1 && (
<button
onClick={() => removeArrayItem("recommendations", i)}
className="text-slate-600 hover:text-red-400 transition-colors text-lg leading-none mt-1"
>
×
</button>
)}
</div>
))}
<button
onClick={() => addArrayItem("recommendations")}
className="text-xs text-amber-400 hover:text-amber-300 transition-colors mt-1"
>
+ Add recommendation
</button>
</div>
</Card>
{/* Session History */}
{history.length > 0 && (
<Card title="📅 Recent Sessions" subtitle="Last 10 Amun sessions">
<div className="space-y-2">
{history.map((s) => (
<button
key={s.date}
onClick={() => loadSessionForDate(s.date)}
className="w-full flex items-center justify-between p-3 bg-[#0f1117] rounded-lg border border-[#2a2d3a] hover:border-purple-500/30 transition-colors text-left"
>
<div className="flex items-center gap-3">
<span className="text-xs text-slate-500 font-mono">{s.date}</span>
<span className="text-xs text-slate-400">
{s.testsRun.filter((t) => t.trim()).length} tests
</span>
{s.bugsFound.filter((b) => b.status === "open").length > 0 && (
<span className="text-xs text-red-400">
{s.bugsFound.filter((b) => b.status === "open").length} open bugs
</span>
)}
</div>
<span className={`text-sm font-bold ${scoreColor(s.codeQualityScore)}`}>
{s.codeQualityScore}%
</span>
</button>
))}
</div>
</Card>
)}
{/* Save Button */}
<div className="flex justify-end pb-6">
<button
onClick={handleSave}
disabled={saving}
className="px-6 py-2.5 bg-purple-600 hover:bg-purple-500 disabled:opacity-50 text-white font-semibold text-sm rounded-lg transition-colors"
>
{saving ? "Saving…" : saved ? "✓ Session Saved!" : "Save Amun Session"}
</button>
</div>
</div>
</div>
);
}