🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@eldrex/dashboard

Package Overview
Dependencies
Maintainers
1
Versions
6
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@eldrex/dashboard - npm Package Compare versions

Comparing version
1.0.0
to
1.0.2
+1
dist/assets/index-3hymSP_O.css
@import"https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;800&family=Plus+Jakarta+Sans:wght@300;400;500;700&display=swap";:root{font-family:Plus Jakarta Sans,system-ui,sans-serif;line-height:1.5;font-weight:400;color-scheme:dark;--bg: #040408;--panel: rgba(10, 10, 20, .45);--border: rgba(255, 255, 255, .06);--text: #f8fafc;--text-muted: #94a3b8;--primary: #0066ff;--primary-glow: rgba(0, 102, 255, .15);--accent: #7c3aed;--accent-glow: rgba(124, 58, 237, .15);--cyan: #00e5ff;--cyan-glow: rgba(0, 229, 255, .15);background-color:var(--bg);color:var(--text)}body{margin:0;min-height:100vh;background-image:radial-gradient(circle at 0% 0%,rgba(124,58,237,.06) 0%,transparent 45%),radial-gradient(circle at 100% 100%,rgba(0,102,255,.06) 0%,transparent 45%);background-attachment:fixed}.app-container{max-width:1200px;margin:0 auto;padding:3rem 1.5rem}header{display:flex;justify-content:space-between;align-items:center;margin-bottom:3.5rem;border-bottom:1px solid var(--border);padding-bottom:1.5rem}.logo-container{display:flex;align-items:center;gap:.75rem}.logo-svg{width:40px;height:40px}.logo-text{font-family:Outfit,sans-serif;font-size:2rem;font-weight:800;letter-spacing:-.05em;color:var(--text)}.logo-text span{background:linear-gradient(135deg,var(--primary),var(--cyan));-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}.status-badge{display:inline-flex;align-items:center;gap:.5rem;padding:.5rem 1rem;background:#00e5ff14;border:1px solid rgba(0,229,255,.2);color:var(--cyan);border-radius:99px;font-size:.85rem;font-weight:600}.status-dot{width:8px;height:8px;background:var(--cyan);border-radius:50%;box-shadow:0 0 10px var(--cyan);animation:pulse 2s infinite}@keyframes pulse{0%{transform:scale(.95);box-shadow:0 0 #00e5ffb3}70%{transform:scale(1);box-shadow:0 0 0 8px #00e5ff00}to{transform:scale(.95);box-shadow:0 0 #00e5ff00}}.grid{display:grid;grid-template-columns:1fr 2fr;gap:2.5rem}.sidebar{display:flex;flex-direction:column;gap:1.5rem}.card{background:var(--panel);-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);border:1px solid var(--border);border-radius:20px;padding:2rem;box-shadow:0 12px 35px #00000040;transition:border-color .3s}.card:hover{border-color:#0066ff26}h2{font-family:Outfit,sans-serif;font-size:1.35rem;margin-top:0;margin-bottom:1.5rem;color:var(--text);font-weight:600;display:flex;align-items:center;gap:.5rem}h2 svg{color:var(--cyan);width:20px;height:20px}.list{display:flex;flex-direction:column;gap:.75rem}.list-item{padding:1rem;background:#ffffff03;border:1px solid var(--border);border-radius:14px;cursor:pointer;transition:all .25s cubic-bezier(.4,0,.2,1);display:flex;justify-content:space-between;align-items:center}.list-item:hover,.list-item.active{background:#0066ff14;border-color:#0066ff40;transform:translateY(-2px)}.list-item.active{box-shadow:inset 0 0 12px #0066ff1a}.list-item-title{font-weight:600;font-size:.95rem;color:var(--text);margin-bottom:.25rem}.list-item-date{font-size:.8rem;color:var(--text-muted)}.main-content{display:flex;flex-direction:column;gap:2rem}.badge{font-size:.75rem;font-weight:600;padding:.25rem .6rem;border-radius:8px;text-transform:uppercase;letter-spacing:.02em}.badge-breaking{background:#ef44441a;color:#ef4444;border:1px solid rgba(239,68,68,.2)}.badge-minor{background:#0066ff1a;color:#60a5fa;border:1px solid rgba(0,102,255,.2)}.file-row{display:flex;flex-direction:column;gap:.5rem;padding:1.25rem 0;border-bottom:1px solid var(--border)}.file-row:last-child{border-bottom:none}.file-header{display:flex;align-items:center;gap:.5rem}.file-header svg{color:var(--primary);width:16px;height:16px}.file-name{font-family:monospace;font-weight:600;color:var(--cyan)}.file-desc{font-size:.92rem;color:var(--text-muted);line-height:1.6}

Sorry, the diff of this file is too big to display

+5
-5
> @eldrex/dashboard@1.0.0 build C:\Users\Eldrex\Downloads\classhost\DevDiff\packages\web-dashboard
> @eldrex/dashboard@1.0.2 build C:\Users\Eldrex\Downloads\classhost\DevDiff\packages\web-dashboard
> tsc && vite build

@@ -10,5 +10,5 @@

computing gzip size...
dist/index.html  0.57 kB │ gzip: 0.36 kB
dist/assets/index-DXjn3ft7.css  3.78 kB │ gzip: 1.40 kB
dist/assets/index-B_i_36NW.js 152.12 kB │ gzip: 48.62 kB
✓ built in 1.12s
dist/index.html  0.60 kB │ gzip: 0.37 kB
dist/assets/index-3hymSP_O.css  3.80 kB │ gzip: 1.41 kB
dist/assets/index-DAFVNwFY.js 152.12 kB │ gzip: 48.62 kB
✓ built in 1.11s

@@ -1,2 +0,2 @@

<!DOCTYPE html>
<!doctype html>
<html lang="en">

@@ -8,5 +8,8 @@ <head>

<title>DevDiff - Dashboard</title>
<meta name="description" content="Visual changelog intelligence for your local development" />
<script type="module" crossorigin src="/assets/index-B_i_36NW.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DXjn3ft7.css">
<meta
name="description"
content="Visual changelog intelligence for your local development"
/>
<script type="module" crossorigin src="/demo/assets/index-DAFVNwFY.js"></script>
<link rel="stylesheet" crossorigin href="/demo/assets/index-3hymSP_O.css">
</head>

@@ -13,0 +16,0 @@ <body>

@@ -1,2 +0,2 @@

<!DOCTYPE html>
<!doctype html>
<html lang="en">

@@ -8,3 +8,6 @@ <head>

<title>DevDiff - Dashboard</title>
<meta name="description" content="Visual changelog intelligence for your local development" />
<meta
name="description"
content="Visual changelog intelligence for your local development"
/>
</head>

@@ -11,0 +14,0 @@ <body>

{
"name": "@eldrex/dashboard",
"version": "1.0.0",
"version": "1.0.2",
"description": "Web dashboard for DevDiff — visualize changes and changelogs",

@@ -5,0 +5,0 @@ "type": "module",

+302
-154

@@ -1,15 +0,15 @@

import { useState, useEffect } from 'react'
import { useState, useEffect } from "react";
interface ChangelogEntry {
hash: string
timestamp: string
provider: string
model: string
hash: string;
timestamp: string;
provider: string;
model: string;
result: {
summary: string
impact: 'minor' | 'major' | 'breaking' | 'none'
breaking: boolean
files: { path: string; explanation: string }[]
relatedIssues: string[]
}
summary: string;
impact: "minor" | "major" | "breaking" | "none";
breaking: boolean;
files: { path: string; explanation: string }[];
relatedIssues: string[];
};
}

@@ -20,84 +20,158 @@

routing: {
strategy: string
localOnly: boolean
}
providers: { name: string; url: string }[]
}
strategy: string;
localOnly: boolean;
};
providers: { name: string; url: string }[];
};
}
const API_BASE = window.location.origin.includes('5173')
? 'http://localhost:4200'
: window.location.origin
const API_BASE = window.location.origin.includes("5173")
? "http://localhost:4200"
: window.location.origin;
// Clean SVG Icons
const SparklesIcon = () => (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="icon-sparkles">
<path d="m12 3-1.912 5.813a2 2 0 0 1-1.275 1.275L3 12l5.813 1.912a2 2 0 0 1 1.275 1.275L12 21l1.912-5.813a2 2 0 0 1 1.275-1.275L21 12l-5.813-1.912a2 2 0 0 1-1.275-1.275L12 3Z"/>
<path d="m5 3 1 2.5L8.5 6 6 7 5 9.5 4 7 1.5 6 4 5 5 3Z" opacity="0.6"/>
<path d="m19 17 1 2.5 2.5.5-2.5 1-1 2.5-1-2.5-2.5-1 2.5-1 1-2.5Z" opacity="0.6"/>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="icon-sparkles"
>
<path d="m12 3-1.912 5.813a2 2 0 0 1-1.275 1.275L3 12l5.813 1.912a2 2 0 0 1 1.275 1.275L12 21l1.912-5.813a2 2 0 0 1 1.275-1.275L21 12l-5.813-1.912a2 2 0 0 1-1.275-1.275L12 3Z" />
<path d="m5 3 1 2.5L8.5 6 6 7 5 9.5 4 7 1.5 6 4 5 5 3Z" opacity="0.6" />
<path
d="m19 17 1 2.5 2.5.5-2.5 1-1 2.5-1-2.5-2.5-1 2.5-1 1-2.5Z"
opacity="0.6"
/>
</svg>
)
);
const FileIcon = () => (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="icon-file">
<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/>
<path d="M14 2v4a2 2 0 0 0 2 2h4"/>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="icon-file"
>
<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" />
<path d="M14 2v4a2 2 0 0 0 2 2h4" />
</svg>
)
);
const HistoryIcon = () => (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="icon-history">
<path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/>
<path d="M3 3v5h5"/>
<path d="M12 7v5l4 2"/>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="icon-history"
>
<path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" />
<path d="M3 3v5h5" />
<path d="M12 7v5l4 2" />
</svg>
)
);
const SettingsIcon = () => (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="icon-settings">
<path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.1a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/>
<circle cx="12" cy="12" r="3"/>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="icon-settings"
>
<path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.1a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z" />
<circle cx="12" cy="12" r="3" />
</svg>
)
);
const GenerateIcon = () => (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" style={{ width: '16px', height: '16px', marginRight: '8px' }}>
<path d="M5 12h14"/>
<path d="M12 5v14"/>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2.5"
strokeLinecap="round"
strokeLinejoin="round"
style={{ width: "16px", height: "16px", marginRight: "8px" }}
>
<path d="M5 12h14" />
<path d="M12 5v14" />
</svg>
)
);
// Library Logo Component matching C:\Users\Eldrex\Downloads\classhost\DevDiff\asset\devdiff.svg
const Logo = () => (
<svg className="logo-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500">
<svg
className="logo-svg"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 500 500"
>
<defs>
<linearGradient id="logoRightGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style={{ stopColor: '#0033aa', stopOpacity: 1 }} />
<stop offset="50%" style={{ stopColor: '#0066ff', stopOpacity: 1 }} />
<stop offset="100%" style={{ stopColor: '#00bfff', stopOpacity: 1 }} />
<stop offset="0%" style={{ stopColor: "#0033aa", stopOpacity: 1 }} />
<stop offset="50%" style={{ stopColor: "#0066ff", stopOpacity: 1 }} />
<stop offset="100%" style={{ stopColor: "#00bfff", stopOpacity: 1 }} />
</linearGradient>
<linearGradient id="logoLeftGrad" x1="100%" y1="100%" x2="0%" y2="0%">
<stop offset="0%" style={{ stopColor: '#6d28d9', stopOpacity: 1 }} />
<stop offset="40%" style={{ stopColor: '#7c3aed', stopOpacity: 0.8 }} />
<stop offset="80%" style={{ stopColor: '#a78bfa', stopOpacity: 0.1 }} />
<stop offset="100%" style={{ stopColor: '#ffffff', stopOpacity: 0 }} />
<stop offset="0%" style={{ stopColor: "#6d28d9", stopOpacity: 1 }} />
<stop offset="40%" style={{ stopColor: "#7c3aed", stopOpacity: 0.8 }} />
<stop offset="80%" style={{ stopColor: "#a78bfa", stopOpacity: 0.1 }} />
<stop offset="100%" style={{ stopColor: "#ffffff", stopOpacity: 0 }} />
</linearGradient>
<linearGradient id="logoIrisGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style={{ stopColor: '#00e5ff', stopOpacity: 1 }} />
<stop offset="50%" style={{ stopColor: '#0099ff', stopOpacity: 1 }} />
<stop offset="100%" style={{ stopColor: '#0033cc', stopOpacity: 1 }} />
<stop offset="0%" style={{ stopColor: "#00e5ff", stopOpacity: 1 }} />
<stop offset="50%" style={{ stopColor: "#0099ff", stopOpacity: 1 }} />
<stop offset="100%" style={{ stopColor: "#0033cc", stopOpacity: 1 }} />
</linearGradient>
<radialGradient id="logoPupilHighlight" cx="35%" cy="35%" r="70%">
<stop offset="0%" style={{ stopColor: '#ffffff', stopOpacity: 0.8 }} />
<stop offset="50%" style={{ stopColor: '#ffffff', stopOpacity: 0.1 }} />
<stop offset="100%" style={{ stopColor: '#ffffff', stopOpacity: 0 }} />
<stop offset="0%" style={{ stopColor: "#ffffff", stopOpacity: 0.8 }} />
<stop offset="50%" style={{ stopColor: "#ffffff", stopOpacity: 0.1 }} />
<stop offset="100%" style={{ stopColor: "#ffffff", stopOpacity: 0 }} />
</radialGradient>
</defs>
<rect x="100" y="150" width="200" height="200" rx="55" transform="rotate(45 200 250)" fill="url(#logoLeftGrad)" />
<rect x="200" y="150" width="200" height="200" rx="55" transform="rotate(45 300 250)" fill="url(#logoRightGrad)" />
<path d="M 160 250 Q 250 160 340 250 Q 250 340 160 250 Z" fill="none" stroke="#ffffff" strokeWidth="12" strokeLinejoin="round" />
<rect
x="100"
y="150"
width="200"
height="200"
rx="55"
transform="rotate(45 200 250)"
fill="url(#logoLeftGrad)"
/>
<rect
x="200"
y="150"
width="200"
height="200"
rx="55"
transform="rotate(45 300 250)"
fill="url(#logoRightGrad)"
/>
<path
d="M 160 250 Q 250 160 340 250 Q 250 340 160 250 Z"
fill="none"
stroke="#ffffff"
strokeWidth="12"
strokeLinejoin="round"
/>
<circle cx="250" cy="250" r="46" fill="url(#logoIrisGrad)" />

@@ -108,64 +182,64 @@ <circle cx="250" cy="250" r="46" fill="url(#logoPupilHighlight)" />

</svg>
)
);
function App() {
const [entries, setEntries] = useState<ChangelogEntry[]>([])
const [config, setConfig] = useState<ConfigData | null>(null)
const [selectedHash, setSelectedHash] = useState<string | null>(null)
const [loading, setLoading] = useState(true)
const [generating, setGenerating] = useState(false)
const [error, setError] = useState<string | null>(null)
const [entries, setEntries] = useState<ChangelogEntry[]>([]);
const [config, setConfig] = useState<ConfigData | null>(null);
const [selectedHash, setSelectedHash] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
const [generating, setGenerating] = useState(false);
const [error, setError] = useState<string | null>(null);
const fetchChangelogs = async () => {
try {
const res = await fetch(`${API_BASE}/api/changelogs`)
const data = await res.json()
const res = await fetch(`${API_BASE}/api/changelogs`);
const data = await res.json();
if (data.success) {
setEntries(data.data)
setEntries(data.data);
if (data.data.length > 0 && !selectedHash) {
setSelectedHash(data.data[0].hash)
setSelectedHash(data.data[0].hash);
}
}
} catch {
setError('Could not connect to DevDiff API server.')
setError("Could not connect to DevDiff API server.");
} finally {
setLoading(false)
setLoading(false);
}
}
};
const fetchConfig = async () => {
try {
const res = await fetch(`${API_BASE}/api/config`)
const data = await res.json()
const res = await fetch(`${API_BASE}/api/config`);
const data = await res.json();
if (data.success) {
setConfig(data.data)
setConfig(data.data);
}
} catch {}
}
};
const triggerGenerate = async () => {
setGenerating(true)
setGenerating(true);
try {
const res = await fetch(`${API_BASE}/api/generate`, {
method: 'POST',
})
const data = await res.json()
method: "POST",
});
const data = await res.json();
if (data.success) {
await fetchChangelogs()
await fetchChangelogs();
} else {
alert(data.message || 'No staged changes detected.')
alert(data.message || "No staged changes detected.");
}
} catch {
alert('Failed to connect to generator API.')
alert("Failed to connect to generator API.");
} finally {
setGenerating(false)
setGenerating(false);
}
}
};
useEffect(() => {
fetchChangelogs()
fetchConfig()
}, [])
fetchChangelogs();
fetchConfig();
}, []);
const selectedEntry = entries.find(c => c.hash === selectedHash)
const selectedEntry = entries.find((c) => c.hash === selectedHash);

@@ -181,12 +255,18 @@ return (

</div>
<div className="status-badge" style={{
background: error ? 'rgba(239, 68, 68, 0.08)' : undefined,
borderColor: error ? 'rgba(239, 68, 68, 0.2)' : undefined,
color: error ? '#ef4444' : undefined
}}>
<span className="status-dot" style={{
background: error ? '#ef4444' : undefined,
boxShadow: error ? '0 0 10px #ef4444' : undefined
}}></span>
{error ? 'API Disconnected' : 'Workspace Connected'}
<div
className="status-badge"
style={{
background: error ? "rgba(239, 68, 68, 0.08)" : undefined,
borderColor: error ? "rgba(239, 68, 68, 0.2)" : undefined,
color: error ? "#ef4444" : undefined,
}}
>
<span
className="status-dot"
style={{
background: error ? "#ef4444" : undefined,
boxShadow: error ? "0 0 10px #ef4444" : undefined,
}}
></span>
{error ? "API Disconnected" : "Workspace Connected"}
</div>

@@ -196,3 +276,9 @@ </header>

{loading ? (
<div style={{ textAlign: 'center', padding: '3rem', color: 'var(--text-muted)' }}>
<div
style={{
textAlign: "center",
padding: "3rem",
color: "var(--text-muted)",
}}
>
Loading changelogs...

@@ -203,23 +289,27 @@ </div>

<section className="sidebar">
<div className="card" style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
<button
onClick={triggerGenerate}
<div
className="card"
style={{ display: "flex", flexDirection: "column", gap: "1rem" }}
>
<button
onClick={triggerGenerate}
disabled={generating}
style={{
background: 'linear-gradient(135deg, var(--primary), var(--accent))',
border: 'none',
borderRadius: '12px',
color: 'white',
padding: '1rem',
fontWeight: 'bold',
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
background:
"linear-gradient(135deg, var(--primary), var(--accent))",
border: "none",
borderRadius: "12px",
color: "white",
padding: "1rem",
fontWeight: "bold",
cursor: "pointer",
display: "flex",
alignItems: "center",
justifyContent: "center",
opacity: generating ? 0.7 : 1,
transition: 'opacity 0.2s',
transition: "opacity 0.2s",
}}
>
<GenerateIcon />
{generating ? 'Explaining...' : 'Generate Staged Changelog'}
{generating ? "Explaining..." : "Generate Staged Changelog"}
</button>

@@ -234,14 +324,15 @@ </div>

{entries.length === 0 ? (
<div style={{ color: 'var(--text-muted)', fontSize: '0.9rem' }}>
No changelogs recorded yet. Run git commits or use the trigger button above.
<div style={{ color: "var(--text-muted)", fontSize: "0.9rem" }}>
No changelogs recorded yet. Run git commits or use the trigger
button above.
</div>
) : (
<div className="list">
{entries.map(item => (
<div
{entries.map((item) => (
<div
key={item.hash}
className={`list-item ${selectedHash === item.hash ? 'active' : ''}`}
className={`list-item ${selectedHash === item.hash ? "active" : ""}`}
onClick={() => setSelectedHash(item.hash)}
>
<div style={{ flex: 1, marginRight: '10px' }}>
<div style={{ flex: 1, marginRight: "10px" }}>
<div className="list-item-title">

@@ -255,3 +346,5 @@ {item.result.summary.substring(0, 40)}...

<div>
<span className={`badge ${item.result.breaking ? 'badge-breaking' : 'badge-minor'}`}>
<span
className={`badge ${item.result.breaking ? "badge-breaking" : "badge-minor"}`}
>
{item.result.impact}

@@ -267,3 +360,3 @@ </span>

{config && (
<div className="card" style={{ padding: '1.5rem' }}>
<div className="card" style={{ padding: "1.5rem" }}>
<h2>

@@ -273,7 +366,24 @@ <SettingsIcon />

</h2>
<div style={{ fontSize: '0.9rem', color: 'var(--text-muted)', display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
<div><strong>Routing strategy:</strong> {config.ai.routing.strategy}</div>
<div><strong>Local Only:</strong> {config.ai.routing.localOnly ? 'yes' : 'no'}</div>
<div
style={{
fontSize: "0.9rem",
color: "var(--text-muted)",
display: "flex",
flexDirection: "column",
gap: "0.5rem",
}}
>
<div>
<strong>Routing strategy:</strong>{" "}
{config.ai.routing.strategy}
</div>
<div>
<strong>Local Only:</strong>{" "}
{config.ai.routing.localOnly ? "yes" : "no"}
</div>
{config.ai.providers.length > 0 && (
<div><strong>Active Provider:</strong> {config.ai.providers[0].name}</div>
<div>
<strong>Active Provider:</strong>{" "}
{config.ai.providers[0].name}
</div>
)}

@@ -292,39 +402,77 @@ </div>

</h2>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '1rem', marginBottom: '1.5rem', alignItems: 'center' }}>
<span className={`badge ${selectedEntry.result.breaking ? 'badge-breaking' : 'badge-minor'}`}>
<div
style={{
display: "flex",
flexWrap: "wrap",
gap: "1rem",
marginBottom: "1.5rem",
alignItems: "center",
}}
>
<span
className={`badge ${selectedEntry.result.breaking ? "badge-breaking" : "badge-minor"}`}
>
{selectedEntry.result.impact} Impact
</span>
<span style={{ fontSize: '0.9rem', color: 'var(--text-muted)' }}>
Generated: {new Date(selectedEntry.timestamp).toLocaleString()}
<span
style={{ fontSize: "0.9rem", color: "var(--text-muted)" }}
>
Generated:{" "}
{new Date(selectedEntry.timestamp).toLocaleString()}
</span>
<span style={{ fontSize: '0.9rem', color: 'var(--text-muted)' }}>
<span
style={{ fontSize: "0.9rem", color: "var(--text-muted)" }}
>
Provider: {selectedEntry.provider} ({selectedEntry.model})
</span>
</div>
<p style={{ fontSize: '1.1rem', lineHeight: '1.7', marginBottom: '2.5rem', color: '#f1f5f9' }}>
<p
style={{
fontSize: "1.1rem",
lineHeight: "1.7",
marginBottom: "2.5rem",
color: "#f1f5f9",
}}
>
{selectedEntry.result.summary}
</p>
{selectedEntry.result.files && selectedEntry.result.files.length > 0 && (
<>
<h3 style={{ fontFamily: 'Outfit', fontSize: '1.25rem', marginBottom: '1rem', borderBottom: '1px solid var(--border)', paddingBottom: '0.5rem' }}>
File Breakdown
</h3>
<div style={{ display: 'flex', flexDirection: 'column' }}>
{selectedEntry.result.files.map((file, idx) => (
<div key={idx} className="file-row">
<div className="file-header">
<FileIcon />
<div className="file-name">{file.path}</div>
{selectedEntry.result.files &&
selectedEntry.result.files.length > 0 && (
<>
<h3
style={{
fontFamily: "Outfit",
fontSize: "1.25rem",
marginBottom: "1rem",
borderBottom: "1px solid var(--border)",
paddingBottom: "0.5rem",
}}
>
File Breakdown
</h3>
<div style={{ display: "flex", flexDirection: "column" }}>
{selectedEntry.result.files.map((file, idx) => (
<div key={idx} className="file-row">
<div className="file-header">
<FileIcon />
<div className="file-name">{file.path}</div>
</div>
<div className="file-desc">{file.explanation}</div>
</div>
<div className="file-desc">{file.explanation}</div>
</div>
))}
</div>
</>
)}
))}
</div>
</>
)}
</div>
) : (
<div className="card" style={{ textAlign: 'center', padding: '5rem', color: 'var(--text-muted)' }}>
<div
className="card"
style={{
textAlign: "center",
padding: "5rem",
color: "var(--text-muted)",
}}
>
Select a changelog from the audit log to view detailed analysis.

@@ -337,5 +485,5 @@ </div>

</div>
)
);
}
export default App
export default App;

@@ -1,5 +0,5 @@

@import url('https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;800&family=Plus+Jakarta+Sans:wght@300;400;500;700&display=swap');
@import url("https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;800&family=Plus+Jakarta+Sans:wght@300;400;500;700&display=swap");
:root {
font-family: 'Plus Jakarta Sans', system-ui, sans-serif;
font-family: "Plus Jakarta Sans", system-ui, sans-serif;
line-height: 1.5;

@@ -14,9 +14,9 @@ font-weight: 400;

--text-muted: #94a3b8;
/* Logo color palette */
--primary: #0066ff; /* Deep Blue */
--primary: #0066ff; /* Deep Blue */
--primary-glow: rgba(0, 102, 255, 0.15);
--accent: #7c3aed; /* Violet */
--accent: #7c3aed; /* Violet */
--accent-glow: rgba(124, 58, 237, 0.15);
--cyan: #00e5ff; /* Iris Cyan */
--cyan: #00e5ff; /* Iris Cyan */
--cyan-glow: rgba(0, 229, 255, 0.15);

@@ -31,5 +31,13 @@

min-height: 100vh;
background-image:
radial-gradient(circle at 0% 0%, rgba(124, 58, 237, 0.06) 0%, transparent 45%),
radial-gradient(circle at 100% 100%, rgba(0, 102, 255, 0.06) 0%, transparent 45%);
background-image:
radial-gradient(
circle at 0% 0%,
rgba(124, 58, 237, 0.06) 0%,
transparent 45%
),
radial-gradient(
circle at 100% 100%,
rgba(0, 102, 255, 0.06) 0%,
transparent 45%
);
background-attachment: fixed;

@@ -65,3 +73,3 @@ }

.logo-text {
font-family: 'Outfit', sans-serif;
font-family: "Outfit", sans-serif;
font-size: 2rem;

@@ -76,2 +84,3 @@ font-weight: 800;

-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;

@@ -103,5 +112,14 @@ }

@keyframes pulse {
0% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(0, 229, 255, 0.7); }
70% { transform: scale(1); box-shadow: 0 0 0 8px rgba(0, 229, 255, 0); }
100% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(0, 229, 255, 0); }
0% {
transform: scale(0.95);
box-shadow: 0 0 0 0 rgba(0, 229, 255, 0.7);
}
70% {
transform: scale(1);
box-shadow: 0 0 0 8px rgba(0, 229, 255, 0);
}
100% {
transform: scale(0.95);
box-shadow: 0 0 0 0 rgba(0, 229, 255, 0);
}
}

@@ -136,3 +154,3 @@

h2 {
font-family: 'Outfit', sans-serif;
font-family: "Outfit", sans-serif;
font-size: 1.35rem;

@@ -172,3 +190,4 @@ margin-top: 0;

.list-item:hover, .list-item.active {
.list-item:hover,
.list-item.active {
background: rgba(0, 102, 255, 0.08);

@@ -175,0 +194,0 @@ border-color: rgba(0, 102, 255, 0.25);

@@ -1,10 +0,10 @@

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.tsx";
import "./index.css";
ReactDOM.createRoot(document.getElementById('root')!).render(
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)
);

@@ -1,3 +0,3 @@

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";

@@ -7,2 +7,3 @@ // https://vitejs.dev/config/

plugins: [react()],
})
base: "/demo/",
});

Sorry, the diff of this file is too big to display

@import"https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;800&family=Plus+Jakarta+Sans:wght@300;400;500;700&display=swap";:root{font-family:Plus Jakarta Sans,system-ui,sans-serif;line-height:1.5;font-weight:400;color-scheme:dark;--bg: #040408;--panel: rgba(10, 10, 20, .45);--border: rgba(255, 255, 255, .06);--text: #f8fafc;--text-muted: #94a3b8;--primary: #0066ff;--primary-glow: rgba(0, 102, 255, .15);--accent: #7c3aed;--accent-glow: rgba(124, 58, 237, .15);--cyan: #00e5ff;--cyan-glow: rgba(0, 229, 255, .15);background-color:var(--bg);color:var(--text)}body{margin:0;min-height:100vh;background-image:radial-gradient(circle at 0% 0%,rgba(124,58,237,.06) 0%,transparent 45%),radial-gradient(circle at 100% 100%,rgba(0,102,255,.06) 0%,transparent 45%);background-attachment:fixed}.app-container{max-width:1200px;margin:0 auto;padding:3rem 1.5rem}header{display:flex;justify-content:space-between;align-items:center;margin-bottom:3.5rem;border-bottom:1px solid var(--border);padding-bottom:1.5rem}.logo-container{display:flex;align-items:center;gap:.75rem}.logo-svg{width:40px;height:40px}.logo-text{font-family:Outfit,sans-serif;font-size:2rem;font-weight:800;letter-spacing:-.05em;color:var(--text)}.logo-text span{background:linear-gradient(135deg,var(--primary),var(--cyan));-webkit-background-clip:text;-webkit-text-fill-color:transparent}.status-badge{display:inline-flex;align-items:center;gap:.5rem;padding:.5rem 1rem;background:#00e5ff14;border:1px solid rgba(0,229,255,.2);color:var(--cyan);border-radius:99px;font-size:.85rem;font-weight:600}.status-dot{width:8px;height:8px;background:var(--cyan);border-radius:50%;box-shadow:0 0 10px var(--cyan);animation:pulse 2s infinite}@keyframes pulse{0%{transform:scale(.95);box-shadow:0 0 #00e5ffb3}70%{transform:scale(1);box-shadow:0 0 0 8px #00e5ff00}to{transform:scale(.95);box-shadow:0 0 #00e5ff00}}.grid{display:grid;grid-template-columns:1fr 2fr;gap:2.5rem}.sidebar{display:flex;flex-direction:column;gap:1.5rem}.card{background:var(--panel);-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);border:1px solid var(--border);border-radius:20px;padding:2rem;box-shadow:0 12px 35px #00000040;transition:border-color .3s}.card:hover{border-color:#0066ff26}h2{font-family:Outfit,sans-serif;font-size:1.35rem;margin-top:0;margin-bottom:1.5rem;color:var(--text);font-weight:600;display:flex;align-items:center;gap:.5rem}h2 svg{color:var(--cyan);width:20px;height:20px}.list{display:flex;flex-direction:column;gap:.75rem}.list-item{padding:1rem;background:#ffffff03;border:1px solid var(--border);border-radius:14px;cursor:pointer;transition:all .25s cubic-bezier(.4,0,.2,1);display:flex;justify-content:space-between;align-items:center}.list-item:hover,.list-item.active{background:#0066ff14;border-color:#0066ff40;transform:translateY(-2px)}.list-item.active{box-shadow:inset 0 0 12px #0066ff1a}.list-item-title{font-weight:600;font-size:.95rem;color:var(--text);margin-bottom:.25rem}.list-item-date{font-size:.8rem;color:var(--text-muted)}.main-content{display:flex;flex-direction:column;gap:2rem}.badge{font-size:.75rem;font-weight:600;padding:.25rem .6rem;border-radius:8px;text-transform:uppercase;letter-spacing:.02em}.badge-breaking{background:#ef44441a;color:#ef4444;border:1px solid rgba(239,68,68,.2)}.badge-minor{background:#0066ff1a;color:#60a5fa;border:1px solid rgba(0,102,255,.2)}.file-row{display:flex;flex-direction:column;gap:.5rem;padding:1.25rem 0;border-bottom:1px solid var(--border)}.file-row:last-child{border-bottom:none}.file-header{display:flex;align-items:center;gap:.5rem}.file-header svg{color:var(--primary);width:16px;height:16px}.file-name{font-family:monospace;font-weight:600;color:var(--cyan)}.file-desc{font-size:.92rem;color:var(--text-muted);line-height:1.6}