Files

200 lines
6.1 KiB
TypeScript

"use client";
import { useState, useEffect } from "react";
interface ChangeLogEntry {
id: string;
date: string;
agent: string;
type: string;
description: string;
version?: string;
}
const TYPE_ICONS: Record<string, string> = {
skill_update: "🧠",
template_change: "📝",
new_feature: "✨",
bug_fix: "🐛"
};
const TYPE_COLORS: Record<string, string> = {
skill_update: "#8b5cf6",
template_change: "#ec4899",
new_feature: "#10b981",
bug_fix: "#ef4444"
};
const AGENT_NAMES: Record<string, string> = {
thoth: "Thoth",
"thoth-trading": "Thoth Trading",
ptah: "Ptah",
seshat: "Seshat",
anubis: "Anubis",
sekhmet: "Sekhmet",
horus: "Horus"
};
export default function ChangeLogPanel() {
const [entries, setEntries] = useState<ChangeLogEntry[]>([]);
const [loading, setLoading] = useState(true);
const [showAdd, setShowAdd] = useState(false);
const [newEntry, setNewEntry] = useState({
agent: "horus",
type: "new_feature",
description: "",
version: ""
});
useEffect(() => {
fetchChangelog();
}, []);
const fetchChangelog = async () => {
const res = await fetch("/api/changelog?limit=50");
const data = await res.json();
setEntries(data);
setLoading(false);
};
const addEntry = async () => {
if (!newEntry.description.trim()) return;
await fetch("/api/changelog", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
action: "add",
entry: newEntry
})
});
setNewEntry({ agent: "horus", type: "new_feature", description: "", version: "" });
setShowAdd(false);
fetchChangelog();
};
const deleteEntry = async (entryId: string) => {
if (!confirm("Delete this entry?")) return;
await fetch("/api/changelog", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ action: "delete", entryId })
});
fetchChangelog();
};
if (loading) {
return <div className="p-4 text-white/50">Loading changelog...</div>;
}
return (
<div className="space-y-4">
<div className="flex justify-between items-center">
<h3 className="text-lg font-semibold">📝 Change Log</h3>
<button
onClick={() => setShowAdd(!showAdd)}
className="px-3 py-1 bg-brand-pink hover:bg-[#ff7bc0] rounded text-sm font-medium"
>
{showAdd ? "✕ Cancel" : "+ Add Entry"}
</button>
</div>
{/* Add Entry Form */}
{showAdd && (
<div className="p-4 bg-white/10 rounded-lg space-y-3">
<div className="grid grid-cols-2 gap-3">
<select
value={newEntry.agent}
onChange={(e) => setNewEntry({ ...newEntry, agent: e.target.value })}
className="bg-black/30 border border-white/20 rounded px-3 py-2 text-white"
>
{Object.entries(AGENT_NAMES).map(([id, name]) => (
<option key={id} value={id}>{name}</option>
))}
</select>
<select
value={newEntry.type}
onChange={(e) => setNewEntry({ ...newEntry, type: e.target.value })}
className="bg-black/30 border border-white/20 rounded px-3 py-2 text-white"
>
<option value="new_feature"> New Feature</option>
<option value="skill_update">🧠 Skill Update</option>
<option value="template_change">📝 Template Change</option>
<option value="bug_fix">🐛 Bug Fix</option>
</select>
</div>
<input
type="text"
value={newEntry.version}
onChange={(e) => setNewEntry({ ...newEntry, version: e.target.value })}
placeholder="Version (optional, e.g., v2.0)"
className="w-full bg-black/30 border border-white/20 rounded px-3 py-2 text-white placeholder-white/50"
/>
<textarea
value={newEntry.description}
onChange={(e) => setNewEntry({ ...newEntry, description: e.target.value })}
placeholder="What changed?"
rows={3}
className="w-full bg-black/30 border border-white/20 rounded px-3 py-2 text-white placeholder-white/50"
/>
<button
onClick={addEntry}
className="w-full py-2 bg-green-600 hover:bg-green-700 rounded font-medium"
>
Save Entry
</button>
</div>
)}
{/* Entries List */}
<div className="space-y-3 max-h-[500px] overflow-y-auto">
{entries.map((entry) => (
<div
key={entry.id}
className="p-3 bg-white/5 rounded-lg border border-white/10"
>
<div className="flex justify-between items-start">
<div className="flex items-center gap-2">
<span
className="px-2 py-0.5 rounded text-xs"
style={{ backgroundColor: TYPE_COLORS[entry.type] || "#666" }}
>
{TYPE_ICONS[entry.type] || "📌"} {entry.type.replace("_", " ")}
</span>
<span className="text-white/50 text-xs">
{new Date(entry.date).toLocaleDateString()}
</span>
</div>
<button
onClick={() => deleteEntry(entry.id)}
className="text-white/30 hover:text-red-400 text-xs"
>
</button>
</div>
<p className="mt-2 text-white/90">{entry.description}</p>
<div className="flex gap-3 mt-2 text-xs text-white/50">
<span>{AGENT_NAMES[entry.agent] || entry.agent}</span>
{entry.version && <span>v{entry.version}</span>}
</div>
</div>
))}
{entries.length === 0 && (
<div className="text-center py-8 text-white/50">
No changes recorded yet. Add an entry to track agent improvements.
</div>
)}
</div>
</div>
);
}