FocusTube / index.html
naveenus's picture
Update index.html
cf94c68 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title>Video Fit Flow</title>
<!-- Dagre (layout) & jsPlumb (connectors) via CDN -->
<script src="https://unpkg.com/[email protected]/dist/dagre.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jsPlumb/2.15.5/js/jsplumb.min.js"></script>
<style>
html, body { margin:0; padding:1rem; font-family:sans-serif; height:100%; }
#wrapper { display:flex; flex-direction:column; height:100vh; overflow:hidden; }
#chartContainer { flex:1; position:relative; background:#f5f5f5; overflow:auto; }
.node {
position:absolute; width:240px; padding:1rem;
border:2px solid #888; border-radius:6px; background:#fff;
text-align:center; transition:background .3s, border-color .3s;
}
.completed { border-color:#28a745; background:#e6ffed; }
.title-label { background:#e0f7fa; padding:.2rem .5rem; border-radius:4px; margin-top:.5rem; }
.score-box { margin-top:.5rem; font-size:1.2rem; }
#descContainer {
flex:none; border-top:2px solid #888; background:#fff;
padding:1rem; max-height:200px; overflow-y:auto;
}
.controls input,
.controls select,
.controls button {
width:90%; margin:6px auto; display:block;
}
</style>
</head>
<body>
<div id="wrapper">
<!-- Flowchart area -->
<div id="chartContainer"></div>
<!-- Always-visible description panel -->
<div id="descContainer">
<strong>Description:</strong>
<div id="videoDesc"></div>
</div>
</div>
<script>
// 1) Node + edge definitions
const nodes = [
{ id:'url', label:'Enter URL & Goal', controls:true },
{ id:'meta', label:'YouTube API', showTitle:true }
];
const models = [
"all-MiniLM-L6-v2","multi-qa-MiniLM-L6-cos-v1",
"paraphrase-MiniLM-L3-v2","all-mpnet-base-v2",
"distilbert-base-nli-mean-tokens"
];
models.forEach(m=>{
nodes.push({ id:`mod-${m}`, label:m });
nodes.push({ id:`scr-${m}`, label:'Score', hasScore:true });
});
const edges = [
{ v:'url', w:'meta' },
...models.flatMap(m=>[
{ v:'meta', w:`mod-${m}` },
{ v:`mod-${m}`, w:`scr-${m}` }
])
];
// 2) Dagre layout
const g = new dagre.graphlib.Graph();
g.setGraph({ rankdir:'TB', marginx:20, marginy:20 });
g.setDefaultEdgeLabel(()=>({}));
nodes.forEach(n=>{
const h = n.controls ? 160 : (n.showTitle ? 100 : 50);
g.setNode(n.id, { label:n.label, width:240, height:h });
});
edges.forEach(e=>g.setEdge(e.v,e.w));
dagre.layout(g);
// Render nodes
const chart = document.getElementById('chartContainer');
nodes.forEach(n=>{
const { x,y,width,height } = g.node(n.id);
const d = document.createElement('div');
d.id = `node-${n.id}`; d.className='node';
d.style.left = `${x-width/2}px`;
d.style.top = `${y-height/2}px`;
if (n.controls) {
d.innerHTML = `
<strong>${n.label}</strong>
<div class="controls">
<input id="urlInput" placeholder="YouTube URL" />
<input id="goalInput" placeholder="Your Goal" />
<button id="btnFetchMeta">Fetch Meta</button>
<button id="btnScore" disabled>Generate Score</button>
</div>`;
}
else if (n.showTitle) {
d.innerHTML = `
<strong>YouTube API</strong><br/>
<div class="title-label">Title:</div>
<div id="videoTitle"></div>`;
}
else {
d.innerHTML = `<strong>${n.label}</strong>` +
(n.hasScore ? `<div id="score-${n.id}" class="score-box">–</div>` : '');
}
chart.appendChild(d);
});
// 3) jsPlumb connections + prevent duplicates
jsPlumb.ready(()=>{
const inst = jsPlumb.getInstance({
Connector:['Flowchart',{ cornerRadius:5 }],
Anchors:['Bottom','Top'],
Endpoint:'Dot',
PaintStyle:{ stroke:'#888', strokeWidth:2 },
EndpointStyle:{ fill:'#888', radius:3 }
});
// draw edges
edges.forEach(e=>{
inst.connect({ source:`node-${e.v}`, target:`node-${e.w}` });
});
// block duplicate connections
inst.bind('beforeDrop', info=>
inst.select({ source: info.sourceId, target: info.targetId }).length === 0
);
// repaint on resize
new ResizeObserver(()=>inst.repaintEverything())
.observe(document.getElementById('chartContainer'));
});
// 4) Fetch meta vs score logic
let cached = null;
document.getElementById('chartContainer').addEventListener('click', async e=>{
if (e.target.id === 'btnFetchMeta') {
// Fetch metadata
const url = document.getElementById('urlInput').value;
const res = await fetch('/api/meta', {
method:'POST', headers:{'Content-Type':'application/json'},
body: JSON.stringify({url})
});
const data = await res.json();
cached = data;
// Display title & description
document.getElementById('videoTitle').textContent = data.title;
document.getElementById('videoDesc').textContent = data.description;
document.getElementById('node-meta').classList.add('completed');
// Enable scoring
document.getElementById('btnScore').disabled = false;
document.getElementById('node-url').classList.add('completed');
}
if (e.target.id === 'btnScore' && cached) {
const goal = document.getElementById('goalInput').value;
const res = await fetch('/api/score', {
method:'POST', headers:{'Content-Type':'application/json'},
body: JSON.stringify({
title: cached.title,
description: cached.description,
goal
})
});
const out = await res.json();
models.forEach(m=>{
const sc = out.scores[m];
const sd = document.getElementById(`score-scr-${m}`);
sd.textContent = sc;
const nd = document.getElementById(`node-scr-${m}`);
nd.style.background = sc < 70 ? '#ff6347' : '#90ee90';
nd.classList.add('completed');
});
}
});
</script>
</body>
</html>