@eldrex/dashboard
Advanced tools
| @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
| > @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... | ||
| [2mdist/[22m[32mindex.html [39m[1m[2m 0.57 kB[22m[1m[22m[2m │ gzip: 0.36 kB[22m | ||
| [2mdist/[22m[35massets/index-DXjn3ft7.css [39m[1m[2m 3.78 kB[22m[1m[22m[2m │ gzip: 1.40 kB[22m | ||
| [2mdist/[22m[36massets/index-B_i_36NW.js [39m[1m[2m152.12 kB[22m[1m[22m[2m │ gzip: 48.62 kB[22m | ||
| [32m✓ built in 1.12s[39m | ||
| [2mdist/[22m[32mindex.html [39m[1m[2m 0.60 kB[22m[1m[22m[2m │ gzip: 0.37 kB[22m | ||
| [2mdist/[22m[35massets/index-3hymSP_O.css [39m[1m[2m 3.80 kB[22m[1m[22m[2m │ gzip: 1.41 kB[22m | ||
| [2mdist/[22m[36massets/index-DAFVNwFY.js [39m[1m[2m152.12 kB[22m[1m[22m[2m │ gzip: 48.62 kB[22m | ||
| [32m✓ built in 1.11s[39m |
+7
-4
@@ -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> |
+5
-2
@@ -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> |
+1
-1
| { | ||
| "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; |
+34
-15
@@ -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); |
+6
-6
@@ -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>, | ||
| ) | ||
| ); |
+4
-3
@@ -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} |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
180859
1.1%1350
14.21%8
14.29%