add prototype index html

This commit is contained in:
2025-10-09 16:07:01 +08:00
parent 19c18e61a0
commit 446b422a12
12 changed files with 2204 additions and 0 deletions

View File

@@ -0,0 +1,164 @@
:root {
--color-brand:#E20074;
--color-brand-hover:#C40062;
--color-bg:#F0F0F0;
--color-surface:#FFFFFF;
--color-text:#222;
--color-text-secondary:#555;
--color-border:#E2E2E2;
--color-border-strong:#CFCFCF;
--color-success:#1E9E59;
--color-danger:#D22C32;
--color-warning:#D89200;
--radius:10px;
--shadow:0 2px 4px rgba(0,0,0,0.08),0 4px 12px rgba(0,0,0,0.06);
font-family:"Segoe UI",system-ui,-apple-system,BlinkMacSystemFont,"Helvetica Neue",Arial,sans-serif;
}
*{box-sizing:border-box;}body{margin:0;background:var(--color-bg);color:var(--color-text);display:flex;min-height:100vh;overflow:auto;font-family:inherit;-webkit-font-smoothing:antialiased;}a{text-decoration:none;color:inherit;}
/* Layout */
.sidebar{width:240px;background:#1E1E24;color:#DDD;display:flex;flex-direction:column;padding:1rem 0;} .sidebar h1{font-size:1.05rem;font-weight:600;padding:0 1.25rem 1rem;margin:0 0 .5rem;color:#FFF;letter-spacing:.5px;}
.nav{list-style:none;margin:0;padding:0;flex:1;} .nav li a{display:flex;align-items:center;gap:.75rem;padding:.75rem 1.25rem;color:#B9B9C2;font-size:.9rem;border-left:4px solid transparent;transition:.2s;} .nav li a:hover{background:rgba(255,255,255,.05);color:#FFF;} .nav li a.active{background:rgba(226,0,116,.12);color:#FFF;border-left-color:var(--color-brand);font-weight:600;}
.icon{width:18px;height:18px;border-radius:4px;background:linear-gradient(135deg,var(--color-brand),#FF4DA8);opacity:.8;}
.footer{padding:1rem 1.25rem;font-size:.7rem;opacity:.55;}
.main-wrapper{flex:1;display:flex;flex-direction:column;min-width:0;}
.header{height:64px;background:var(--color-surface);display:flex;align-items:center;padding:0 1.5rem;gap:1rem;border-bottom:1px solid var(--color-border);} .brand-title{font-weight:600;letter-spacing:.5px;font-size:.9rem;color:var(--color-brand);}
.search-box{flex:1;position:relative;} .search-box input{width:100%;padding:.65rem .9rem .65rem 2.25rem;border:1px solid var(--color-border);border-radius:999px;background:#FFF;font-size:.85rem;outline:none;transition:.2s;} .search-box input:focus{border-color:var(--color-brand);box-shadow:0 0 0 3px rgba(226,0,116,.25);} .search-box .magnifier{position:absolute;top:50%;left:.8rem;width:14px;height:14px;background:var(--color-brand);border-radius:50%;transform:translateY(-50%);box-shadow:0 0 0 3px rgba(226,0,116,.25);}
.user-avatar{width:36px;height:36px;background:linear-gradient(135deg,var(--color-brand),#FF4DA8);border-radius:50%;box-shadow:0 0 0 3px rgba(226,0,116,.25);}
.content{flex:1;overflow:visible;padding:1.5rem clamp(1rem,2vw,2rem) 2rem;display:flex;flex-direction:column;gap:1.5rem;}
/* Cards & Utilities */
.cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:1.25rem;}
.card{background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius);padding:1.1rem 1.15rem 1.25rem;box-shadow:var(--shadow);display:flex;flex-direction:column;gap:.75rem;position:relative;}
.card h3{margin:0;font-size:.95rem;font-weight:600;letter-spacing:.3px;}
.small{font-size:.65rem;opacity:.75;}
.primary-btn{align-self:flex-start;background:var(--color-brand);color:#FFF;font-size:.8rem;font-weight:600;border:none;padding:.65rem 1.1rem;border-radius:8px;cursor:pointer;letter-spacing:.3px;display:inline-flex;gap:.4rem;box-shadow:0 4px 10px -2px rgba(226,0,116,.45);transition:.2s;} .primary-btn:hover{background:var(--color-brand-hover);transform:translateY(-2px);box-shadow:0 6px 14px -2px rgba(226,0,116,.5);} .secondary-btn{background:#FFF;color:var(--color-brand);border:1px solid var(--color-brand);padding:.55rem .95rem;font-size:.75rem;border-radius:8px;cursor:pointer;font-weight:500;transition:.2s;} .secondary-btn:hover{background:rgba(226,0,116,.08);} .danger-btn{background:var(--color-danger);color:#FFF;border:none;padding:.55rem .95rem;font-size:.75rem;border-radius:8px;cursor:pointer;}
.inline-note{font-size:.65rem;opacity:.8;margin-left:.35rem;}
.status-pill{background:rgba(226,0,116,.1);color:var(--color-brand);padding:.25rem .55rem;font-size:.65rem;border-radius:999px;font-weight:500;white-space:nowrap;}
.status-ok{background:rgba(30,158,89,.12);color:var(--color-success);} .status-warn{background:rgba(216,146,0,.15);color:var(--color-warning);} .status-bad{background:rgba(210,44,50,.15);color:var(--color-danger);}
.divider{height:1px;background:var(--color-border);margin:.75rem 0;}
/* Metrics */
.metrics{display:flex;gap:1.25rem;margin-top:.25rem;} .metric{flex:1;background:#F8F8F9;padding:.6rem .7rem;border-radius:8px;display:flex;flex-direction:column;gap:.15rem;font-size:.7rem;line-height:1.25;border:1px solid #ECECEC;} .metric .val{font-size:.95rem;font-weight:600;color:var(--color-brand);}
/* Tables */
.table-wrapper{overflow:auto;border:1px solid var(--color-border);border-radius:10px;background:#FFF;}
table{width:100%;border-collapse:collapse;font-size:.75rem;min-width:600px;}
thead{background:#FBFBFC;} th,td{text-align:left;padding:.65rem .85rem;border-bottom:1px solid #EEE;white-space:nowrap;} tbody tr:hover{background:#FFF4FA;} tbody tr:last-child td{border-bottom:none;}
.row-actions{opacity:0;transition:.15s;display:flex;gap:.3rem;} tbody tr:hover .row-actions{opacity:1;} .icon-btn{background:transparent;border:1px solid var(--color-border);width:26px;height:26px;padding:0;border-radius:6px;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:.6rem;color:var(--color-text-secondary);} .icon-btn:hover{border-color:var(--color-brand);color:var(--color-brand);} .overflow-menu{position:absolute;top:100%;right:0;background:#FFF;border:1px solid var(--color-border);border-radius:8px;min-width:160px;padding:.35rem .3rem;box-shadow:var(--shadow);display:none;z-index:10;} .overflow-menu.open{display:block;} .overflow-menu button{all:unset;display:block;width:100%;padding:.45rem .65rem;font-size:.7rem;color:var(--color-text-secondary);border-radius:6px;cursor:pointer;} .overflow-menu button:hover{background:rgba(226,0,116,.08);color:var(--color-brand);}
/* Filters */
.filters-bar{display:flex;gap:.6rem;flex-wrap:wrap;align-items:center;margin-bottom:.6rem;} .filters-bar input[type=search]{flex:1;min-width:220px;padding:.55rem .75rem;border:1px solid var(--color-border);border-radius:8px;font-size:.75rem;outline:none;} .filters-bar input[type=search]:focus{border-color:var(--color-brand);box-shadow:0 0 0 3px rgba(226,0,116,.2);} .chip{background:#FFF;border:1px solid var(--color-border);border-radius:999px;padding:.35rem .7rem;font-size:.65rem;display:inline-flex;align-items:center;gap:.35rem;cursor:pointer;} .chip.active{border-color:var(--color-brand);color:var(--color-brand);background:rgba(226,0,116,.08);}
/* Tabs */
.tabs{display:flex;gap:.75rem;margin-top:.3rem;border-bottom:1px solid var(--color-border);padding:0 .15rem;} .tab{padding:.55rem .95rem;font-size:.75rem;font-weight:500;cursor:pointer;color:var(--color-text-secondary);position:relative;border-radius:6px 6px 0 0;transition:.2s;} .tab.active{background:var(--color-surface);color:var(--color-brand);font-weight:600;box-shadow:0 -2px 6px rgba(0,0,0,.05);} .tab.active:after{content:"";position:absolute;left:0;bottom:-1px;width:100%;height:3px;background:var(--color-brand);border-radius:2px 2px 0 0;}
/* Stepper */
.stepper{display:flex;gap:1.5rem;align-items:flex-start;flex-wrap:wrap;padding:.4rem 0 .9rem;} .step{display:flex;align-items:center;gap:.6rem;position:relative;font-size:.7rem;color:var(--color-text-secondary);} .step-circle{width:28px;height:28px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:.65rem;font-weight:600;border:2px solid var(--color-border);background:#FFF;color:var(--color-text-secondary);transition:.25s;} .step.active .step-circle{border-color:var(--color-brand);background:var(--color-brand);color:#FFF;box-shadow:0 0 0 4px rgba(226,0,116,.25);} .step.completed .step-circle{border-color:var(--color-brand);background:#FFF;color:var(--color-brand);} .step-label{max-width:120px;line-height:1.2;} .step:after{content:"";position:absolute;left:14px;top:34px;width:2px;height:26px;background:var(--color-border);opacity:.5;} .step:last-child:after{display:none;} @media (min-width:760px){.stepper.horizontal{flex-wrap:nowrap;}.stepper.horizontal .step{flex-direction:column;align-items:center;}.stepper.horizontal .step:after{display:none;}.stepper.horizontal .step:not(:last-child)::before{content:"";position:absolute;top:14px;left:50%;right:-50%;height:2px;background:var(--color-border);z-index:-1;}}
/* Forms */
.form-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:1rem 1.25rem;margin-top:.75rem;} .form-group{display:flex;flex-direction:column;gap:.35rem;font-size:.7rem;} .form-group label{font-weight:600;font-size:.65rem;letter-spacing:.5px;text-transform:uppercase;color:var(--color-text-secondary);} .form-group input,.form-group select,.form-group textarea{padding:.6rem .7rem;border:1px solid var(--color-border);border-radius:8px;font-size:.75rem;background:#FFF;resize:vertical;min-height:42px;font-family:inherit;} .form-group textarea.code{font-family:ui-monospace,Consolas,"SFMono-Regular",monospace;line-height:1.35;tab-size:2;white-space:pre;} .form-group input:focus,.form-group select:focus,.form-group textarea:focus{outline:none;border-color:var(--color-brand);box-shadow:0 0 0 3px rgba(226,0,116,.25);} .mask-wrapper{position:relative;display:flex;align-items:center;} .mask-wrapper button{position:absolute;right:.45rem;top:50%;transform:translateY(-50%);border:none;background:rgba(226,0,116,.12);color:var(--color-brand);font-size:.6rem;padding:.3rem .5rem;border-radius:6px;cursor:pointer;} .mask-wrapper button:hover{background:var(--color-brand);color:#FFF;}
/* KPI Cards */
.kpi-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:1rem;} .kpi{background:var(--color-surface);border:1px solid var(--color-border);border-radius:14px;padding:.9rem 1rem;display:flex;flex-direction:column;gap:.35rem;box-shadow:var(--shadow);position:relative;overflow:hidden;} .kpi h4{margin:0;font-size:.6rem;letter-spacing:.6px;font-weight:600;text-transform:uppercase;color:var(--color-text-secondary);} .kpi .val{font-size:1.35rem;font-weight:600;color:var(--color-brand);line-height:1;} .kpi .trend{font-size:.6rem;font-weight:500;} .trend.up{color:var(--color-success);} .trend.down{color:var(--color-danger);} .sparkline{position:absolute;inset:0;opacity:.15;background:radial-gradient(circle at 80% 20%,var(--color-brand),transparent 60%);}
/* Chart Placeholder */
.chart-card{min-height:280px;} .chart-placeholder{flex:1;display:flex;align-items:center;justify-content:center;font-size:.7rem;color:var(--color-text-secondary);border:2px dashed var(--color-border);border-radius:12px;padding:1rem;margin-top:.5rem;}
/* Health Panel */
.health-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:.75rem;margin-top:.5rem;} .health-item{background:#FAFAFA;border:1px solid var(--color-border);border-radius:10px;padding:.6rem .7rem;display:flex;flex-direction:column;gap:.25rem;font-size:.65rem;} .health-item strong{font-size:.7rem;}
/* Usage Bars */
.usage-bar{height:6px;background:#ECECEC;border-radius:4px;overflow:hidden;position:relative;}
.usage-bar > span{display:block;height:100%;background:linear-gradient(90deg,var(--color-brand),#FF4DA8);}
.usage-bar.warn > span{background:linear-gradient(90deg,#D89200,#FFB347);}
.usage-bar.danger > span{background:linear-gradient(90deg,#D22C32,#FF5A5F);}
/* Toggles */
.toggle {position:relative;display:inline-flex;align-items:center;gap:.55rem;font-size:.7rem;cursor:pointer;user-select:none;}
.toggle input{appearance:none;width:40px;height:20px;background:#CFCFCF;border-radius:999px;position:relative;outline:none;transition:.25s;border:1px solid #B9B9B9;cursor:pointer;}
.toggle input:after{content:"";position:absolute;left:2px;top:2px;width:16px;height:16px;border-radius:50%;background:#FFF;box-shadow:0 1px 2px rgba(0,0,0,.25);transition:.25s;}
.toggle input:checked{background:var(--color-brand);border-color:var(--color-brand);}
.toggle input:checked:after{transform:translateX(20px);}
/* Slider (Hybrid Weights) */
.slider-row{display:flex;align-items:center;gap:.75rem;font-size:.65rem;}
.slider-row input[type=range]{flex:1;height:6px;background:linear-gradient(90deg,var(--color-brand),#FF4DA8);border-radius:4px;outline:none;-webkit-appearance:none;appearance:none;}
.slider-row input[type=range]::-webkit-slider-thumb{appearance:none;width:18px;height:18px;border-radius:50%;background:#FFF;border:2px solid var(--color-brand);box-shadow:0 0 0 3px rgba(226,0,116,.25);cursor:pointer;}
.slider-row .weight-badge{background:#FFF;border:1px solid var(--color-border);padding:.35rem .55rem;border-radius:6px;font-size:.6rem;font-weight:600;min-width:42px;text-align:center;}
/* Metadata Rule Builder */
.rule-builder{display:flex;flex-direction:column;gap:.6rem;}
.rule-row{display:flex;gap:.5rem;flex-wrap:wrap;align-items:center;background:#FAFAFA;border:1px solid var(--color-border);padding:.55rem .6rem;border-radius:8px;position:relative;}
.rule-row select,.rule-row input{padding:.45rem .55rem;font-size:.65rem;border:1px solid var(--color-border);border-radius:6px;min-width:120px;}
.rule-row select:focus,.rule-row input:focus{outline:none;border-color:var(--color-brand);box-shadow:0 0 0 2px rgba(226,0,116,.25);}
.rule-row button.remove-rule{background:transparent;border:1px solid var(--color-border);color:#666;font-size:.6rem;width:26px;height:26px;border-radius:6px;cursor:pointer;}
.rule-row button.remove-rule:hover{color:var(--color-brand);border-color:var(--color-brand);}
.add-rule-btn{background:#FFF;border:1px dashed var(--color-brand);color:var(--color-brand);padding:.5rem .75rem;font-size:.65rem;border-radius:8px;cursor:pointer;width:fit-content;}
.add-rule-btn:hover{background:rgba(226,0,116,.08);}
/* Upload Zone */
.upload-zone{border:2px dashed var(--color-border);border-radius:12px;padding:1.1rem .9rem;text-align:center;background:#FCFCFD;display:flex;flex-direction:column;gap:.55rem;font-size:.65rem;color:var(--color-text-secondary);}
.upload-zone.dragover{border-color:var(--color-brand);background:rgba(226,0,116,.04);}
.file-list{display:flex;flex-direction:column;gap:.45rem;font-size:.6rem;margin-top:.4rem;}
.file-item{display:flex;align-items:center;gap:.5rem;padding:.45rem .55rem;background:#FFF;border:1px solid var(--color-border);border-radius:8px;}
.file-icon{width:26px;height:26px;border-radius:6px;background:linear-gradient(135deg,var(--color-brand),#FF4DA8);opacity:.85;display:flex;align-items:center;justify-content:center;color:#FFF;font-size:.55rem;font-weight:600;}
/* ACL / Versions / Cost */
.acl-table th,.acl-table td{padding:.5rem .6rem;font-size:.65rem;border-bottom:1px solid #EEE;text-align:left;}
.acl-table th{background:#FBFBFC;font-weight:600;font-size:.6rem;letter-spacing:.5px;text-transform:uppercase;}
.version-list{display:flex;flex-direction:column;gap:.45rem;font-size:.6rem;}
.version-item{display:flex;justify-content:space-between;align-items:center;background:#FAFAFA;border:1px solid var(--color-border);padding:.45rem .55rem;border-radius:8px;gap:.5rem;}
.version-item .meta{display:flex;flex-direction:column;line-height:1.25;}
.version-item button{background:#FFF;border:1px solid var(--color-border);padding:.3rem .55rem;font-size:.55rem;border-radius:6px;cursor:pointer;}
.version-item button:hover{border-color:var(--color-brand);color:var(--color-brand);}
.cost-metrics{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:.75rem;font-size:.6rem;margin-top:.6rem;}
.cost-box{background:#FAFAFA;border:1px solid var(--color-border);border-radius:10px;padding:.55rem .65rem;display:flex;flex-direction:column;gap:.25rem;}
.cost-box strong{font-size:.7rem;}
.cost-val{font-size:.85rem;font-weight:600;color:var(--color-brand);}
/* Flex helpers */
.flex{display:flex;} .col{display:flex;flex-direction:column;} .gap-s{gap:.5rem;} .gap-m{gap:1rem;} .space-between{justify-content:space-between;} .align-center{align-items:center;}
/* Responsive */
@media (max-width:900px){.sidebar{width:200px;}.header{height:60px;}} @media (max-width:680px){.sidebar{display:none;} body{flex-direction:column;} .header{border-top:4px solid var(--color-brand);} }
/* ================= LOGIN PAGE ================= */
.login-shell{display:flex;flex-direction:column;min-height:100vh;width:100%;background:var(--color-bg);}
.login-topbar{height:70px;background:var(--color-brand);display:flex;align-items:center;padding:0 2rem;}
.login-topbar .brand-logo{width:44px;height:44px;background:#FFF;border-radius:8px;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:1.1rem;color:var(--color-brand);box-shadow:0 2px 6px rgba(0,0,0,.15);}
.login-main{flex:1;display:flex;align-items:flex-start;justify-content:center;padding:2.5rem 1rem 3rem;overflow:auto;}
.login-card{background:#FFF;border:1px solid var(--color-border);border-radius:14px;box-shadow:var(--shadow);width:100%;max-width:640px;padding:3.25rem 3rem 2.75rem;display:flex;flex-direction:column;gap:1.4rem;position:relative;}
.login-card h2{margin:0;font-size:2.15rem;line-height:1.15;font-weight:700;letter-spacing:.5px;text-align:center;}
.login-card h2 .sub{display:block;font-size:.6em;font-weight:500;opacity:.65;margin-top:.35rem;}
.login-service{font-weight:600;text-align:center;font-size:.8rem;letter-spacing:.6px;margin-top:-.5rem;color:var(--color-text-secondary);}
.login-form{display:flex;flex-direction:column;gap:1rem;}
.login-form .field{display:flex;flex-direction:column;gap:.4rem;}
.login-form input[type=text],.login-form input[type=password]{padding:.75rem .9rem;border:1px solid var(--color-border);border-radius:8px;font-size:.85rem;outline:none;background:#FFF;}
.login-form input[type=text]:focus,.login-form input[type=password]:focus{border-color:var(--color-brand);box-shadow:0 0 0 3px rgba(226,0,116,.25);}
.login-actions{display:flex;flex-direction:column;gap:.75rem;margin-top:.25rem;}
.login-btn-primary{background:var(--color-brand);color:#FFF;border:none;border-radius:8px;padding:.85rem 1.1rem;font-size:.85rem;font-weight:600;cursor:pointer;box-shadow:0 4px 12px -2px rgba(226,0,116,.45);transition:.2s;}
.login-btn-primary:hover{background:var(--color-brand-hover);transform:translateY(-2px);}
.login-btn-secondary{background:#FFF;border:1px solid var(--color-border);border-radius:8px;padding:.8rem 1.1rem;font-size:.8rem;font-weight:600;cursor:pointer;}
.login-btn-secondary:hover{border-color:var(--color-brand);color:var(--color-brand);}
.login-meta{display:flex;align-items:center;justify-content:space-between;font-size:.65rem;}
.login-meta a{color:var(--color-brand);text-decoration:none;font-weight:500;}
.login-meta a:hover{text-decoration:underline;}
.remember-row{display:flex;align-items:center;gap:.5rem;font-size:.65rem;color:var(--color-text-secondary);}
.remember-row input{width:16px;height:16px;cursor:pointer;}
.login-help{text-align:center;font-size:.6rem;}
.login-help a{color:var(--color-brand);}
.login-social{display:flex;justify-content:center;gap:1.1rem;margin-top:.4rem;}
.login-social a{width:36px;height:36px;border-radius:50%;display:flex;align-items:center;justify-content:center;background:#FFF;border:1px solid var(--color-border);font-size:.85rem;color:var(--color-text-secondary);transition:.2s;}
.login-social a:hover{border-color:var(--color-brand);color:var(--color-brand);}
.login-footer{border-top:1px solid var(--color-border);padding:.85rem 2rem;font-size:.55rem;color:var(--color-text-secondary);display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:.5rem;background:#FFF;}
.legal-links{display:flex;gap:1rem;}
.legal-links a{color:var(--color-text-secondary);text-decoration:none;}
.legal-links a:hover{color:var(--color-brand);}
@media (max-width:720px){
.login-card{padding:2.5rem 1.6rem 2.4rem;}
.login-card h2{font-size:1.85rem;}
.login-topbar{height:60px;padding:0 1rem;}
.login-footer{padding:.85rem 1rem;}
}

220
web_prototype/assets/app.js Normal file
View File

@@ -0,0 +1,220 @@
// Basic shared interactions for prototype
(function(){
function qs(sel, ctx=document){ return ctx.querySelector(sel); }
function qsa(sel, ctx=document){ return Array.from(ctx.querySelectorAll(sel)); }
// Overflow menus (three dots) - open/close logic
document.addEventListener('click', e => {
const btn = e.target.closest('[data-menu]');
if(btn){
const id = btn.getAttribute('data-menu');
const menu = qs('#menu-' + id);
if(menu){
menu.classList.toggle('open');
// close others
qsa('.overflow-menu').forEach(m => { if(m !== menu) m.classList.remove('open'); });
}
} else if(!e.target.closest('.overflow-menu')) {
qsa('.overflow-menu').forEach(m => m.classList.remove('open'));
}
});
// Tabs
function initTabs(container){
if(!container) return;
const tabs = qsa('.tab', container);
const panelRoot = qs('#tabPanels') || container.parentElement;
tabs.forEach(tab => {
tab.addEventListener('click', () => {
const target = tab.getAttribute('data-tab');
tabs.forEach(t => t.classList.remove('active'));
tab.classList.add('active');
qsa('[data-panel]', panelRoot).forEach(p => {
p.style.display = p.getAttribute('data-panel') === target ? '' : 'none';
});
});
});
}
initTabs(qs('#modelTabs'));
// API Key mask toggle
qsa('[data-toggle-mask]').forEach(btn => {
btn.addEventListener('click', () => {
const input = btn.parentElement.querySelector('[data-mask-target]');
if(!input) return;
if(input.type === 'password'){
input.type = 'text';
btn.textContent = 'Hide';
} else {
input.type = 'password';
btn.textContent = 'Show';
}
});
});
// Stepper logic (pipeline configuration)
const stepper = qs('#pipelineStepper');
if(stepper){
stepper.addEventListener('click', e => {
const stepEl = e.target.closest('.step');
if(!stepEl) return;
const targetStep = stepEl.getAttribute('data-step');
qsa('.step', stepper).forEach(s => {
const sStep = s.getAttribute('data-step');
s.classList.remove('active');
if(Number(sStep) < Number(targetStep)) s.classList.add('completed'); else s.classList.remove('completed');
});
stepEl.classList.add('active');
// toggle panels
qsa('[data-step-panel]').forEach(p => {
p.style.display = p.getAttribute('data-step-panel') === targetStep ? '' : 'none';
});
});
}
// Simple persistence of active nav via location
const path = location.pathname.split('/').pop();
qsa('.sidebar .nav a').forEach(a => {
if(a.getAttribute('href') === path){
a.classList.add('active');
} else {
a.classList.remove('active');
}
});
// ---- Retrieval Configuration Page Enhancements ----
(function initRetrievalConfig(){
const slider = qs('#hybridSlider');
if(!slider) return; // not on this page
const vW = qs('#vectorWeight');
const kW = qs('#keywordWeight');
const ruleBuilder = qs('#ruleBuilder');
const addRuleBtn = qs('#addRuleBtn');
const saveBtn = qs('#saveConfigBtn');
const saveStatus = qs('#saveStatus');
function markDirty(){ if(saveStatus){ saveStatus.textContent = 'Unsaved changes...'; saveStatus.style.color = 'var(--color-brand)'; } }
function markSaved(){ if(saveStatus){ saveStatus.textContent = 'Configuration saved (simulated)'; saveStatus.style.color = 'var(--color-text-secondary)'; } }
slider.addEventListener('input', () => {
const v = Number(slider.value); const k = 100 - v;
if(vW) vW.textContent = v + '%';
if(kW) kW.textContent = k + '%';
markDirty();
});
if(addRuleBtn && ruleBuilder){
addRuleBtn.addEventListener('click', () => {
const row = document.createElement('div');
row.className = 'rule-row'; row.setAttribute('data-rule','');
row.innerHTML = `\n <select data-field>\n <option value="document_type">document_type</option>\n <option value="source_type">source_type</option>\n <option value="date">date</option>\n <option value="language">language</option>\n </select>\n <select data-operator>\n <option value="==">==</option>\n <option value=">=">>=</option>\n <option value=">">></option>\n <option value="<="><=</option>\n <option value="<"><</option>\n <option value="contains">contains</option>\n </select>\n <input data-value placeholder="value" />\n <button type="button" class="remove-rule" title="Remove rule">×</button>`;
ruleBuilder.appendChild(row); markDirty();
});
ruleBuilder.addEventListener('click', e => {
if(e.target.classList.contains('remove-rule')){
const r = e.target.closest('[data-rule]');
if(r){ r.remove(); markDirty(); }
}
});
['change','input'].forEach(evt => ruleBuilder.addEventListener(evt, markDirty));
}
['mmToggle','rerankToggle','postRerankN','rerankerModel'].forEach(id => {
const el = qs('#'+id); if(el){ el.addEventListener('change', markDirty); el.addEventListener('input', markDirty); }
});
if(saveBtn){
saveBtn.addEventListener('click', () => {
saveBtn.disabled = true; const orig = saveBtn.textContent; saveBtn.textContent = 'Saving...';
setTimeout(()=>{ saveBtn.textContent = orig; saveBtn.disabled = false; markSaved(); }, 900);
});
}
})();
// ---- Knowledge Base Detail Page Enhancements ----
(function initKbDetail(){
const uploadZone = qs('#uploadZone');
if(!uploadZone) return; // not on detail page
const fileInput = qs('#fileInput');
const browseTrigger = qs('#browseTrigger');
const ingestQueue = qs('#ingestQueue');
const queueCount = qs('#queueCount');
const docTable = qs('#docTable');
const reindexBtn = qs('#reindexBtn');
const syncBtn = qs('#syncNowBtn');
const syncStatus = qs('#syncStatus');
const estimateBtn = qs('#estimateBtn');
const versionList = qs('#versionList');
const versionStatus = qs('#versionStatus');
const aclTable = qs('#aclTable');
const addAclBtn = qs('#addAclBtn');
const aclPrincipal = qs('#aclPrincipal');
const aclRole = qs('#aclRole');
const aclScope = qs('#aclScope');
function addDocs(files){
if(!files.length) return;
if(ingestQueue) ingestQueue.style.display = 'block';
if(queueCount){ queueCount.textContent = (Number(queueCount.textContent) + files.length).toString(); }
Array.from(files).forEach(file => {
const row = document.createElement('tr');
const ext = (file.name.split('.').pop()||'txt');
row.innerHTML = `<td>${file.name}</td><td>${ext}</td><td>—</td><td><span class="status-pill">pending</span></td>`;
if(docTable) docTable.appendChild(row);
setTimeout(()=>{
const status = row.querySelector('.status-pill');
if(status){ status.textContent='ready'; status.classList.add('status-success'); }
row.children[2].textContent = Math.floor(Math.random()*120)+10;
}, 1300 + Math.random()*1200);
});
}
if(browseTrigger && fileInput){ browseTrigger.addEventListener('click', () => fileInput.click()); }
if(fileInput){ fileInput.addEventListener('change', e => addDocs(e.target.files)); }
if(uploadZone){
uploadZone.addEventListener('dragover', e => { e.preventDefault(); uploadZone.classList.add('drag-over'); });
uploadZone.addEventListener('dragleave', () => uploadZone.classList.remove('drag-over'));
uploadZone.addEventListener('drop', e => { e.preventDefault(); uploadZone.classList.remove('drag-over'); addDocs(e.dataTransfer.files); });
}
if(reindexBtn){
reindexBtn.addEventListener('click', () => {
reindexBtn.disabled = true; const orig = reindexBtn.textContent; reindexBtn.textContent = 'Reindexing...';
setTimeout(()=>{ reindexBtn.textContent = orig; reindexBtn.disabled = false; alert('Reindex simulation completed.'); }, 1800);
});
}
if(syncBtn && syncStatus){
syncBtn.addEventListener('click', () => {
syncStatus.textContent = 'Sync in progress...'; syncStatus.style.color = 'var(--color-brand)';
setTimeout(()=>{ syncStatus.textContent = 'Last sync: just now (simulated)'; syncStatus.style.color = 'var(--color-text-secondary)'; }, 1400);
});
}
if(estimateBtn){
estimateBtn.addEventListener('click', () => {
estimateBtn.disabled = true; const orig = estimateBtn.textContent; estimateBtn.textContent='Calculating...';
setTimeout(()=>{ const cost = 150 + Math.floor(Math.random()*60); const mc = qs('#monthlyCost'); if(mc) mc.textContent = '$'+cost; estimateBtn.textContent=orig; estimateBtn.disabled=false; }, 1000);
});
}
if(versionList){
versionList.addEventListener('click', e => {
const btn = e.target.closest('.rollback-btn'); if(!btn) return;
const v = btn.getAttribute('data-target');
if(confirm('Simulate rollback to version '+v+'?')){
if(versionStatus){ versionStatus.textContent = 'Rolling back to v'+v+' ...'; versionStatus.style.color = 'var(--color-brand)'; }
setTimeout(()=>{ if(versionStatus){ versionStatus.textContent = 'Current active version: v'+v+' (simulated rollback)'; versionStatus.style.color = 'var(--color-text-secondary)'; } }, 1300);
}
});
}
if(addAclBtn && aclTable){
addAclBtn.addEventListener('click', () => {
const p = (aclPrincipal && aclPrincipal.value.trim()) || ''; if(!p) return;
const role = (aclRole && aclRole.value) || 'reader'; const scope = (aclScope && aclScope.value.trim()) || 'all';
const row = document.createElement('tr');
row.innerHTML = `<td>${p}</td><td>${role}</td><td>${scope}</td><td><button class="remove-acl" title="Remove">×</button></td>`;
const body = aclTable.querySelector('tbody'); if(body) body.appendChild(row);
if(aclPrincipal) aclPrincipal.value=''; if(aclScope) aclScope.value='';
});
aclTable.addEventListener('click', e => { if(e.target.classList.contains('remove-acl')) e.target.closest('tr').remove(); });
}
})();
})();

View File

@@ -0,0 +1,287 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Create Knowledge Base - RAG Dashboard</title>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link rel="stylesheet" href="assets/app.css" />
</head>
<body>
<aside class="sidebar">
<h1>RAGflow Prototype</h1>
<ul class="nav">
<li><a href="index.html"><span class="icon"></span>Overview</a></li>
<li><a class="active" href="kb_list.html"><span class="icon"></span>Knowledge Bases</a></li>
<li><a href="pipeline_config.html"><span class="icon"></span>RAG Pipeline</a></li>
<li><a href="dashboard.html"><span class="icon"></span>Operations</a></li>
<li><a href="models_resources.html"><span class="icon"></span>Models & Resources</a></li>
<li><a href="mcp.html"><span class="icon"></span>MCP</a></li>
</ul>
<div class="footer">© 2025 RAG Demo</div>
</aside>
<div class="main-wrapper">
<header class="header">
<div class="brand-title">Create Knowledge Base</div>
<div class="search-box">
<span class="magnifier"></span>
<input placeholder="Search help..." />
</div>
<div class="user-avatar" title="User Profile"></div>
</header>
<main class="content" style="gap:1.25rem;">
<div class="card" style="gap:1rem;">
<h3 style="margin:0;">New Knowledge Base Wizard</h3>
<div class="stepper horizontal" id="kbWizardStepper">
<div class="step active" data-step="1"><div class="step-circle">1</div><div class="step-label">Basic Info</div></div>
<div class="step" data-step="2"><div class="step-circle">2</div><div class="step-label">Sources</div></div>
<div class="step" data-step="3"><div class="step-circle">3</div><div class="step-label">Processing</div></div>
<div class="step" data-step="4"><div class="step-circle">4</div><div class="step-label">Access & Tags</div></div>
<div class="step" data-step="5"><div class="step-circle">5</div><div class="step-label">Review</div></div>
</div>
<div class="divider"></div>
<form id="kbWizardForm" class="col" style="gap:1.2rem;">
<!-- Step 1 -->
<div class="wizard-panel" data-wizard-panel="1">
<div class="form-grid">
<div class="form-group">
<label>Knowledge Base Name</label>
<input placeholder="e.g. Product Manuals v3" />
</div>
<div class="form-group">
<label>Owner Team</label>
<select>
<option>Engineering</option>
<option>Support</option>
<option>Operations</option>
<option>Product</option>
</select>
</div>
<div class="form-group">
<label>Visibility</label>
<select>
<option>Internal</option>
<option>Private</option>
<option>Public (Restricted)</option>
</select>
</div>
<div class="form-group">
<label>Default Language</label>
<select>
<option>English</option>
<option>Chinese</option>
<option>German</option>
</select>
</div>
</div>
<div class="form-group">
<label>Description</label>
<textarea rows="4" placeholder="Short description of what this knowledge base covers."></textarea>
</div>
</div>
<!-- Step 2 -->
<div class="wizard-panel" data-wizard-panel="2" style="display:none;">
<div class="small" style="margin-top:-.3rem;">Define ingestion sources to pull content into this knowledge base.</div>
<div class="form-grid">
<div class="form-group">
<label>Source Type</label>
<select>
<option>Cloud Storage Bucket</option>
<option>Git Repository</option>
<option>Web Crawl</option>
<option>API Endpoint</option>
<option>Manual Upload</option>
</select>
</div>
<div class="form-group">
<label>Source Identifier / Path</label>
<input placeholder="e.g. s3://docs-bucket/manuals" />
</div>
<div class="form-group">
<label>Ingestion Schedule (cron)</label>
<input value="0 */4 * * *" />
</div>
<div class="form-group">
<label>File Types</label>
<input value="pdf, md, html" />
</div>
</div>
<div class="form-group">
<label>Inclusion / Exclusion Rules</label>
<textarea rows="4" placeholder="Include: docs/manuals/**\nExclude: drafts/**"></textarea>
</div>
<div class="secondary-btn" style="width:fit-content;">+ Add Another Source</div>
</div>
<!-- Step 3 -->
<div class="wizard-panel" data-wizard-panel="3" style="display:none;">
<div class="small" style="margin-top:-.3rem;">Configure how documents are chunked, embedded, and stored for retrieval.</div>
<div class="form-grid">
<div class="form-group">
<label>Chunk Size (tokens)</label>
<input value="512" />
</div>
<div class="form-group">
<label>Chunk Overlap</label>
<input value="40" />
</div>
<div class="form-group">
<label>Embedding Model</label>
<select>
<option>text-embedding-3-large</option>
<option>e5-large-v2</option>
<option>gte-large</option>
</select>
</div>
<div class="form-group">
<label>Vector Store</label>
<select>
<option>pgvector-prod</option>
<option>milvus-cluster</option>
<option>chroma-dev</option>
</select>
</div>
</div>
<div class="form-grid">
<div class="form-group">
<label>Enable Reranker</label>
<select>
<option>Yes</option>
<option>No</option>
</select>
</div>
<div class="form-group">
<label>Reranker Model</label>
<select>
<option>cross-encoder-v2</option>
<option>mono-t5-large</option>
</select>
</div>
<div class="form-group">
<label>Metadata Extraction</label>
<select>
<option>Auto (LLM)</option>
<option>Regex Rules</option>
<option>None</option>
</select>
</div>
<div class="form-group">
<label>Quality Gate Threshold</label>
<input value="0.35" />
</div>
</div>
</div>
<!-- Step 4 -->
<div class="wizard-panel" data-wizard-panel="4" style="display:none;">
<div class="small" style="margin-top:-.3rem;">Assign access scope, tags and optional retention rules.</div>
<div class="form-grid">
<div class="form-group">
<label>Access Policy</label>
<select>
<option>Role-Based</option>
<option>Team-Based</option>
<option>Open (Internal)</option>
</select>
</div>
<div class="form-group">
<label>Allowed Roles (comma)</label>
<input value="support_agent, engineer" />
</div>
<div class="form-group">
<label>Tags</label>
<input value="manuals, devices" />
</div>
<div class="form-group">
<label>Data Retention (days)</label>
<input value="365" />
</div>
</div>
<div class="form-group">
<label>Notes</label>
<textarea rows="3" placeholder="Any compliance or access caveats..."></textarea>
</div>
</div>
<!-- Step 5 -->
<div class="wizard-panel" data-wizard-panel="5" style="display:none;">
<div class="small" style="margin-top:-.3rem;">Review configuration before creating the knowledge base.</div>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:1rem;font-size:.65rem;">
<div style="background:#FAFAFA;border:1px solid var(--color-border);border-radius:8px;padding:.6rem .7rem;">
<strong style="font-size:.7rem;">Basic</strong><br/>Name: Product Manuals v3<br/>Owner: Engineering<br/>Visibility: Internal
</div>
<div style="background:#FAFAFA;border:1px solid var(--color-border);border-radius:8px;padding:.6rem .7rem;">
<strong style="font-size:.7rem;">Sources</strong><br/>Type: Cloud Storage<br/>Path: s3://docs-bucket/manuals<br/>Schedule: 0 */4 * * *
</div>
<div style="background:#FAFAFA;border:1px solid var(--color-border);border-radius:8px;padding:.6rem .7rem;">
<strong style="font-size:.7rem;">Processing</strong><br/>Chunk: 512/40<br/>Embed: text-embedding-3-large<br/>Vector: pgvector-prod
</div>
<div style="background:#FAFAFA;border:1px solid var(--color-border);border-radius:8px;padding:.6rem .7rem;">
<strong style="font-size:.7rem;">Access</strong><br/>Policy: Role-Based<br/>Roles: support_agent, engineer<br/>Retention: 365d
</div>
</div>
<div class="divider"></div>
<div class="small" style="color:var(--color-text-secondary);">On creation: ingestion job will be queued, initial sync ETA ~5 minutes.</div>
</div>
<div class="flex space-between align-center" style="margin-top:.25rem; flex-wrap:wrap; gap:.75rem;">
<div class="flex gap-s">
<button type="button" class="secondary-btn" id="kbPrevBtn" disabled>← Previous</button>
<button type="button" class="primary-btn" id="kbNextBtn">Next →</button>
</div>
<div class="small" id="kbProgressNote">Step 1 of 5</div>
</div>
</form>
</div>
</main>
</div>
<script src="assets/app.js"></script>
<script>
(function(){
const stepper = document.getElementById('kbWizardStepper');
const panels = Array.from(document.querySelectorAll('[data-wizard-panel]'));
const prevBtn = document.getElementById('kbPrevBtn');
const nextBtn = document.getElementById('kbNextBtn');
const progress = document.getElementById('kbProgressNote');
let current = 1; const total = panels.length;
function update(){
panels.forEach(p=>{p.style.display = Number(p.getAttribute('data-wizard-panel'))===current ? '' : 'none';});
Array.from(stepper.querySelectorAll('.step')).forEach(s=>{
const n = Number(s.getAttribute('data-step'));
s.classList.toggle('active', n===current);
s.classList.toggle('completed', n<current);
});
prevBtn.disabled = current===1;
if(current===total){
nextBtn.textContent = 'Create Knowledge Base';
} else {
nextBtn.textContent = 'Next →';
}
progress.textContent = 'Step ' + current + ' of ' + total;
}
nextBtn.addEventListener('click', ()=>{
if(current < total){
current++; update();
} else {
// Simulate create action
nextBtn.disabled = true; prevBtn.disabled = true;
nextBtn.textContent = 'Creating...';
setTimeout(()=>{
nextBtn.textContent = 'Done';
nextBtn.classList.remove('primary-btn');
nextBtn.classList.add('secondary-btn');
progress.textContent = 'Knowledge Base created (simulated)';
}, 1400);
}
});
prevBtn.addEventListener('click', ()=>{ if(current>1){ current--; update(); } });
stepper.addEventListener('click', e=>{
const st = e.target.closest('.step'); if(!st) return; const to = Number(st.getAttribute('data-step')); if(to && to<=current){ current = to; update(); }
});
update();
})();
</script>
</body>
</html>

View File

@@ -0,0 +1,148 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Operational Dashboard - RAG Dashboard</title>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link rel="stylesheet" href="assets/app.css" />
</head>
<body>
<aside class="sidebar">
<h1>RAGflow Prototype</h1>
<ul class="nav">
<li><a href="index.html"><span class="icon"></span>Overview</a></li>
<li><a href="kb_list.html"><span class="icon"></span>Knowledge Bases</a></li>
<li><a href="pipeline_config.html"><span class="icon"></span>RAG Pipeline</a></li>
<li><a class="active" href="dashboard.html"><span class="icon"></span>Operations</a></li>
<li><a href="models_resources.html"><span class="icon"></span>Models & Resources</a></li>
<li><a href="mcp.html"><span class="icon"></span>MCP</a></li>
</ul>
<div class="footer">© 2025 RAG Demo</div>
</aside>
<div class="main-wrapper">
<header class="header">
<div class="brand-title">System Operations</div>
<div class="search-box">
<span class="magnifier"></span>
<input placeholder="Search metrics..." />
</div>
<div class="user-avatar" title="User Profile"></div>
</header>
<main class="content" style="gap:1.25rem;">
<div class="kpi-grid">
<div class="kpi">
<div class="sparkline"></div>
<h4>Total Queries</h4>
<div class="val">182K</div>
<div class="trend up">+4.2% vs prev</div>
</div>
<div class="kpi">
<div class="sparkline"></div>
<h4>Avg Latency</h4>
<div class="val">742ms</div>
<div class="trend down">-3.1%</div>
</div>
<div class="kpi">
<div class="sparkline"></div>
<h4>Error Rate</h4>
<div class="val">0.84%</div>
<div class="trend up" style="color:var(--color-danger);">+0.12%</div>
</div>
<div class="kpi">
<div class="sparkline"></div>
<h4>Active KBs</h4>
<div class="val">37</div>
<div class="trend up">+2 new</div>
</div>
</div>
<div class="cards" style="grid-template-columns:2.2fr 1fr;">
<div class="card chart-card">
<h3 style="margin-top:0;">Query Volume Over Time</h3>
<div class="chart-placeholder">
<svg width="100%" height="180" viewBox="0 0 600 180" preserveAspectRatio="none" style="max-width:100%;">
<polyline fill="none" stroke="#E20074" stroke-width="3" points="0,120 40,115 80,130 120,90 160,95 200,70 240,75 280,60 320,68 360,50 400,62 440,55 480,45 520,58 560,52 600,40" />
<line x1="0" y1="140" x2="600" y2="140" stroke="#ddd" stroke-width="1" />
<line x1="0" y1="100" x2="600" y2="100" stroke="#eee" stroke-width="1" />
<line x1="0" y1="60" x2="600" y2="60" stroke="#eee" stroke-width="1" />
</svg>
</div>
<div class="small" style="margin-top:.4rem;">Last 24h aggregated by 1h intervals.</div>
</div>
<div class="card" style="display:flex;flex-direction:column;">
<h3 style="margin-top:0;">System Health</h3>
<div class="health-grid">
<div class="health-item"><strong>API Gateway</strong><span class="status-pill status-ok">Healthy</span><span>p95 810ms</span></div>
<div class="health-item"><strong>Vector Store</strong><span class="status-pill status-ok">Healthy</span><span>Shard 12/12</span></div>
<div class="health-item"><strong>Workers</strong><span class="status-pill status-warn">High Load</span><span>76% util</span></div>
<div class="health-item"><strong>Indexer</strong><span class="status-pill status-ok">Idle</span><span>Queue 0</span></div>
<div class="health-item"><strong>Embeddings</strong><span class="status-pill status-ok">Normal</span><span>Batch 42</span></div>
<div class="health-item"><strong>Cache</strong><span class="status-pill status-ok">Hot</span><span>Hit 94%</span></div>
</div>
<div class="divider"></div>
<div class="small">Updated 30s ago</div>
</div>
<div class="card" style="grid-column:1 / -1;">
<h3 style="margin-top:0;">Top 5 Slowest Pipelines</h3>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th style="min-width:200px;">Pipeline</th>
<th>Avg Latency</th>
<th>p95</th>
<th>Throughput</th>
<th>Error Rate</th>
<th>Last Run</th>
</tr>
</thead>
<tbody>
<tr>
<td>Support-KB-v2</td>
<td>1.42s</td>
<td>2.85s</td>
<td>19 q/min</td>
<td><span class="status-pill status-ok">0.6%</span></td>
<td>09:21</td>
</tr>
<tr>
<td>Hardware-Manuals</td>
<td>1.31s</td>
<td>2.40s</td>
<td>23 q/min</td>
<td><span class="status-pill status-ok">0.9%</span></td>
<td>09:20</td>
</tr>
<tr>
<td>Auth-Guides</td>
<td>1.18s</td>
<td>2.10s</td>
<td>27 q/min</td>
<td><span class="status-pill status-ok">0.7%</span></td>
<td>09:19</td>
</tr>
<tr>
<td>Legacy-Archive</td>
<td>1.11s</td>
<td>2.00s</td>
<td>14 q/min</td>
<td><span class="status-pill status-warn">1.3%</span></td>
<td>09:18</td>
</tr>
<tr>
<td>Design-Docs</td>
<td>1.05s</td>
<td>1.92s</td>
<td>32 q/min</td>
<td><span class="status-pill status-ok">0.5%</span></td>
<td>09:17</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</main>
</div>
<script src="assets/app.js"></script>
</body>
</html>

138
web_prototype/index.html Normal file
View File

@@ -0,0 +1,138 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>RAG Dashboard Prototype</title>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link rel="stylesheet" href="assets/app.css" />
</head>
<body>
<aside class="sidebar">
<h1>RAGflow Prototype</h1>
<ul class="nav">
<li><a class="active" href="index.html"><span class="icon"></span>Overview</a></li>
<li><a href="kb_list.html"><span class="icon"></span>Knowledge Bases</a></li>
<li><a href="pipeline_config.html"><span class="icon"></span>RAG Pipeline</a></li>
<li><a href="dashboard.html"><span class="icon"></span>Operations</a></li>
<li><a href="models_resources.html"><span class="icon"></span>Models & Resources</a></li>
<li><a href="mcp.html"><span class="icon"></span>MCP</a></li>
</ul>
<div class="footer">© 2025 RAG Demo</div>
</aside>
<div class="main-wrapper">
<header class="header">
<div class="brand-title">RAG Dashboard</div>
<div class="search-box">
<span class="magnifier"></span>
<input placeholder="Search queries, KB names..." />
</div>
<div class="user-avatar" title="User Profile"></div>
</header>
<main class="content">
<div class="cards">
<div class="card">
<h3>Knowledge Base Status</h3>
<div class="metrics">
<div class="metric">
<span>Documents</span>
<span class="val">4,218</span>
</div>
<div class="metric">
<span>Sources</span>
<span class="val">17</span>
</div>
<div class="metric">
<span>Vectors</span>
<span class="val">1.2M</span>
</div>
</div>
<div class="progress-wrap">
<div style="display:flex; justify-content:space-between; font-size:.65rem; margin-bottom:.25rem;">
<span>Sync Progress</span>
<span>62%</span>
</div>
<div class="progress-bar"><span></span></div>
</div>
<button class="primary-btn">Create New Knowledge Base</button>
</div>
<div class="card">
<h3>Recent Activity <span class="inline-note">(latest 24h)</span></h3>
<div style="font-size:.7rem; line-height:1.3;">
152 user queries processed<br/>
87 new documents ingested<br/>
4 pipeline adjustments
</div>
<div style="margin-top:auto; font-size:.65rem; opacity:.75;">Latency stable at p95 820ms</div>
</div>
<div class="card">
<h3>Model Overview</h3>
<div style="display:flex; flex-direction:column; gap:.4rem; font-size:.7rem;">
<div>Embedding Model: <strong>text-embedding-3-large</strong></div>
<div>Generator: <strong>gpt-4o-mini</strong></div>
<div>Reranker: <strong>cross-encoder-v2</strong></div>
<div>Chunking: 512 tokens</div>
<div>Retriever Top-K: 8</div>
</div>
<span class="status-pill" style="margin-top:.5rem;">Healthy</span>
</div>
<div class="card table-card">
<h3>Recent RAG Queries <span class="inline-note">(latest 5)</span></h3>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Query</th>
<th>Latency (ms)</th>
<th>Source</th>
<th>Time</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>How to reset device firmware?</td>
<td>732</td>
<td>manual.pdf</td>
<td>09:21</td>
<td><span class="status-pill status-ok">OK</span></td>
</tr>
<tr>
<td>List authentication failure codes</td>
<td>801</td>
<td>auth_guide.html</td>
<td>09:18</td>
<td><span class="status-pill status-ok">OK</span></td>
</tr>
<tr>
<td>Can we purge stale vectors?</td>
<td>915</td>
<td>system_kb</td>
<td>09:10</td>
<td><span class="status-pill status-ok">OK</span></td>
</tr>
<tr>
<td>Explain retrieval scoring logic</td>
<td>845</td>
<td>design_notes</td>
<td>08:57</td>
<td><span class="status-pill status-ok">OK</span></td>
</tr>
<tr>
<td>Pipeline concurrency limits?</td>
<td>1042</td>
<td>ops_doc</td>
<td>08:43</td>
<td><span class="status-pill status-ok">OK</span></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</main>
</div>
</body>
</html>

View File

@@ -0,0 +1,250 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Knowledge Base Detail - RAG Dashboard</title>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link rel="stylesheet" href="assets/app.css" />
</head>
<body>
<aside class="sidebar">
<h1>RAGflow Prototype</h1>
<ul class="nav">
<li><a href="index.html"><span class="icon"></span>Overview</a></li>
<li><a class="active" href="kb_list.html"><span class="icon"></span>Knowledge Bases</a></li>
<li><a href="pipeline_config.html"><span class="icon"></span>RAG Pipeline</a></li>
<li><a href="dashboard.html"><span class="icon"></span>Operations</a></li>
<li><a href="models_resources.html"><span class="icon"></span>Models & Resources</a></li>
<li><a href="mcp.html"><span class="icon"></span>MCP</a></li>
</ul>
<div class="footer">© 2025 RAG Demo</div>
</aside>
<div class="main-wrapper">
<header class="header">
<div class="brand-title">Knowledge Base Detail</div>
<div class="search-box">
<span class="magnifier"></span>
<input placeholder="Search documents..." />
</div>
<div class="user-avatar" title="User Profile"></div>
</header>
<main class="content" style="gap:1.25rem;">
<div class="cards" style="grid-template-columns:2.2fr 1fr; align-items:start;">
<!-- Ingestion & Upload -->
<div class="card" style="grid-column:1 / 2; gap:1rem;">
<div class="flex space-between align-center" style="flex-wrap:wrap; gap:.75rem;">
<h3 style="margin:0;">Multi-Modal Ingestion</h3>
<div class="flex" style="gap:.5rem;">
<button class="secondary-btn" id="reindexBtn">Full Reindex</button>
<a class="secondary-btn" href="kb_retrieval_config.html">Retrieval Config →</a>
</div>
</div>
<div class="upload-zone" id="uploadZone">
<div class="placeholder-icon"></div>
<div>Drag & Drop files (PDF, DOCX, TXT, MD, PNG, JPG, MP4, WAV)</div>
<div class="sub">or <span class="linkish" id="browseTrigger">browse</span> &nbsp;&nbsp; <label class="linkish" style="cursor:pointer;">remote URL<input type="text" id="remoteUrlInput" placeholder="https://..." style="display:none;" /></label></div>
<input type="file" id="fileInput" multiple style="display:none;" />
</div>
<div id="ingestQueue" class="small" style="display:none;">Queued: <span id="queueCount">0</span> files...</div>
<div class="divider"></div>
<div class="form-grid">
<div class="form-group">
<label>Text Chunk Size</label>
<input type="number" value="1024" />
</div>
<div class="form-group">
<label>Chunk Overlap</label>
<input type="number" value="120" />
</div>
<div class="form-group">
<label>Enable OCR</label>
<select>
<option>auto</option>
<option>force</option>
<option>disable</option>
</select>
</div>
<div class="form-group">
<label>Image Embedding Model</label>
<select>
<option>clip-vit-large-p14</option>
<option>siglip-so400m</option>
</select>
</div>
<div class="form-group">
<label>ASR (Audio → Text)</label>
<select>
<option>whisper-large-v3</option>
<option>conformer-transcribe</option>
</select>
</div>
<div class="form-group">
<label>Video Frame Interval (sec)</label>
<input type="number" value="4" />
</div>
</div>
<div class="small" style="opacity:.75;">Settings apply to new ingestions. Existing vectors remain until reindexed.</div>
</div>
<!-- Source Preview / Documents -->
<div class="card" style="grid-column:2 / 3; gap:.85rem; max-height:520px; overflow:auto;">
<h3 style="margin:0;">Recent Documents</h3>
<table class="simple">
<thead><tr><th>Name</th><th>Type</th><th>Chunks</th><th>Status</th></tr></thead>
<tbody id="docTable">
<tr><td>product_guide.pdf</td><td>pdf</td><td>184</td><td><span class="status-pill status-success">ready</span></td></tr>
<tr><td>pricing_sheet.xlsx</td><td>xlsx</td><td>42</td><td><span class="status-pill status-success">ready</span></td></tr>
<tr><td>brand_assets.zip</td><td>zip</td><td></td><td><span class="status-pill">pending</span></td></tr>
</tbody>
</table>
</div>
<!-- Access Control -->
<div class="card" style="grid-column:1 / 2; gap:.9rem;">
<h3 style="margin:0;">Access Control (ACL)</h3>
<table class="acl-table" id="aclTable">
<thead><tr><th>Principal</th><th>Role</th><th>Scope</th><th></th></tr></thead>
<tbody>
<tr><td>analyst-team</td><td>reader</td><td>all</td><td><button class="remove-acl" title="Remove">×</button></td></tr>
<tr><td>ml-admins</td><td>admin</td><td>all</td><td><button class="remove-acl" title="Remove">×</button></td></tr>
</tbody>
</table>
<div class="flex" style="gap:.5rem; flex-wrap:wrap;">
<input id="aclPrincipal" placeholder="principal" style="flex:1 1 140px;" />
<select id="aclRole">
<option value="reader">reader</option>
<option value="writer">writer</option>
<option value="admin">admin</option>
</select>
<input id="aclScope" placeholder="scope (optional)" style="flex:1 1 140px;" />
<button class="secondary-btn" id="addAclBtn">Add</button>
</div>
</div>
<!-- Version History -->
<div class="card" style="grid-column:2 / 3; gap:.85rem;">
<h3 style="margin:0;">Version History</h3>
<ul class="version-list" id="versionList">
<li data-version="1.3"><div class="meta">v1.3 • today • added 24 docs</div><button class="rollback-btn" data-target="1.3">Rollback</button></li>
<li data-version="1.2"><div class="meta">v1.2 • 2d ago • reindexed (chunk params)</div><button class="rollback-btn" data-target="1.2">Rollback</button></li>
<li data-version="1.1"><div class="meta">v1.1 • 6d ago • initial image embeddings</div><button class="rollback-btn" data-target="1.1">Rollback</button></li>
</ul>
<div class="small" id="versionStatus" style="opacity:.75;">Current active version: v1.3</div>
</div>
<!-- Sync Monitor & Cost -->
<div class="card" style="grid-column:1 / -1; gap:1rem;">
<h3 style="margin:0;">Sync Monitor & Cost Metrics</h3>
<div class="cost-metrics">
<div class="metric-box"><div class="label">Vector Count</div><div class="value" id="vectorCount">128,450</div></div>
<div class="metric-box"><div class="label">Storage (GB)</div><div class="value" id="storageGB">6.9</div></div>
<div class="metric-box"><div class="label">Monthly Cost (est)</div><div class="value" id="monthlyCost">$174</div></div>
<div class="metric-box"><div class="label">Pending Jobs</div><div class="value" id="pendingJobs">3</div></div>
<div class="metric-box"><div class="label">Avg Embedding Latency</div><div class="value" id="embedLatency">420ms</div></div>
</div>
<div class="usage-bars" style="margin-top:.25rem;">
<div class="bar"><div class="fill" style="width:62%;"></div></div>
<div class="small" style="opacity:.7;">Compute Quota 62% used</div>
</div>
<div class="flex" style="gap:.75rem; flex-wrap:wrap;">
<button class="secondary-btn" id="syncNowBtn">Trigger Sync</button>
<button class="secondary-btn" id="estimateBtn">Recalculate Cost Estimate</button>
</div>
<div class="small" id="syncStatus" style="opacity:.75;">Idle.</div>
</div>
</div>
</main>
</div>
<script src="assets/app.js"></script>
<script>
(function(){
const uploadZone = document.getElementById('uploadZone');
const fileInput = document.getElementById('fileInput');
const browseTrigger = document.getElementById('browseTrigger');
const ingestQueue = document.getElementById('ingestQueue');
const queueCount = document.getElementById('queueCount');
const docTable = document.getElementById('docTable');
const reindexBtn = document.getElementById('reindexBtn');
const syncBtn = document.getElementById('syncNowBtn');
const syncStatus = document.getElementById('syncStatus');
const estimateBtn = document.getElementById('estimateBtn');
const versionList = document.getElementById('versionList');
const versionStatus = document.getElementById('versionStatus');
const aclTable = document.getElementById('aclTable');
const addAclBtn = document.getElementById('addAclBtn');
const aclPrincipal = document.getElementById('aclPrincipal');
const aclRole = document.getElementById('aclRole');
const aclScope = document.getElementById('aclScope');
function addDocs(files){
ingestQueue.style.display = 'block';
let current = Number(queueCount.textContent);
current += files.length; queueCount.textContent = current;
// Simulate processing
Array.from(files).forEach(file => {
const row = document.createElement('tr');
row.innerHTML = `<td>${file.name}</td><td>${(file.name.split('.').pop()||'txt')}</td><td>—</td><td><span class="status-pill">pending</span></td>`;
docTable.appendChild(row);
setTimeout(()=>{
const statusCell = row.querySelector('.status-pill');
statusCell.textContent = 'ready';
statusCell.classList.add('status-success');
row.children[2].textContent = Math.floor(Math.random()*120)+10; // chunks
}, 1300 + Math.random()*1200);
});
}
browseTrigger.addEventListener('click', ()=> fileInput.click());
fileInput.addEventListener('change', e=> addDocs(e.target.files));
uploadZone.addEventListener('dragover', e=>{ e.preventDefault(); uploadZone.classList.add('drag-over'); });
uploadZone.addEventListener('dragleave', ()=> uploadZone.classList.remove('drag-over'));
uploadZone.addEventListener('drop', e=>{ e.preventDefault(); uploadZone.classList.remove('drag-over'); if(e.dataTransfer.files.length) addDocs(e.dataTransfer.files); });
reindexBtn.addEventListener('click', ()=>{
reindexBtn.disabled = true; reindexBtn.textContent = 'Reindexing...';
setTimeout(()=>{ reindexBtn.textContent = 'Full Reindex'; reindexBtn.disabled = false; alert('Reindex simulation completed.'); }, 1800);
});
syncBtn.addEventListener('click', ()=>{
syncStatus.textContent = 'Sync in progress...'; syncStatus.style.color = 'var(--color-brand)';
setTimeout(()=>{ syncStatus.textContent = 'Last sync: just now (simulated)'; syncStatus.style.color = 'var(--color-text-secondary)'; }, 1400);
});
estimateBtn.addEventListener('click', ()=>{
estimateBtn.disabled = true; estimateBtn.textContent = 'Calculating...';
setTimeout(()=>{ document.getElementById('monthlyCost').textContent = '$' + (150 + Math.floor(Math.random()*60)); estimateBtn.textContent = 'Recalculate Cost Estimate'; estimateBtn.disabled = false; }, 1000);
});
versionList.addEventListener('click', e=>{
if(e.target.classList.contains('rollback-btn')){
const v = e.target.getAttribute('data-target');
if(confirm('Simulate rollback to version '+v+'?')){
versionStatus.textContent = 'Rolling back to v'+v+' ...';
versionStatus.style.color = 'var(--color-brand)';
setTimeout(()=>{ versionStatus.textContent = 'Current active version: v'+v+' (simulated rollback)'; versionStatus.style.color = 'var(--color-text-secondary)'; }, 1300);
}
}
});
addAclBtn.addEventListener('click', ()=>{
const p = aclPrincipal.value.trim(); if(!p) return;
const role = aclRole.value; const scope = aclScope.value.trim() || 'all';
const row = document.createElement('tr');
row.innerHTML = `<td>${p}</td><td>${role}</td><td>${scope}</td><td><button class="remove-acl" title="Remove">×</button></td>`;
aclTable.querySelector('tbody').appendChild(row);
aclPrincipal.value=''; aclScope.value='';
});
aclTable.addEventListener('click', e=>{
if(e.target.classList.contains('remove-acl')){
e.target.closest('tr').remove();
}
});
})();
</script>
</body>
</html>

162
web_prototype/kb_list.html Normal file
View File

@@ -0,0 +1,162 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Knowledge Bases - RAG Dashboard</title>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link rel="stylesheet" href="assets/app.css" />
</head>
<body>
<aside class="sidebar">
<h1>RAGflow Prototype</h1>
<ul class="nav">
<li><a href="index.html"><span class="icon"></span>Overview</a></li>
<li><a class="active" href="kb_list.html"><span class="icon"></span>Knowledge Bases</a></li>
<li><a href="pipeline_config.html"><span class="icon"></span>RAG Pipeline</a></li>
<li><a href="dashboard.html"><span class="icon"></span>Operations</a></li>
<li><a href="models_resources.html"><span class="icon"></span>Models & Resources</a></li>
<li><a href="mcp.html"><span class="icon"></span>MCP</a></li>
</ul>
<div class="footer">© 2025 RAG Demo</div>
</aside>
<div class="main-wrapper">
<header class="header">
<div class="brand-title">Knowledge Base Management</div>
<div class="search-box">
<span class="magnifier"></span>
<input placeholder="Search knowledge bases..." />
</div>
<div class="user-avatar" title="User Profile"></div>
</header>
<main class="content">
<div class="card" style="gap:1rem;">
<div class="flex space-between align-center" style="flex-wrap:wrap; gap:.75rem;">
<h3 style="margin:0;">Knowledge Bases</h3>
<a class="primary-btn" style="text-decoration:none;" href="create_kb.html">+ Create New Knowledge Base</a>
</div>
<div class="filters-bar">
<input type="search" placeholder="Filter by name or tag..." />
<div class="chip active">All</div>
<div class="chip">Active</div>
<div class="chip">Paused</div>
<div class="chip">Sync Error</div>
</div>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th style="min-width:220px;">Name</th>
<th>Documents</th>
<th>Last Sync</th>
<th>Creation Date</th>
<th style="text-align:right;">Actions</th>
</tr>
</thead>
<tbody>
<tr>
<td>Device Manuals v2</td>
<td>4,218</td>
<td><span class="status-pill status-ok">Success 09:20</span></td>
<td>2025-08-11</td>
<td style="text-align:right; position:relative;">
<div class="row-actions">
<button class="icon-btn" data-menu="kb1"></button>
</div>
<div class="overflow-menu" id="menu-kb1">
<button onclick="location.href='kb_detail.html'">Open</button>
<button onclick="location.href='kb_retrieval_config.html'">Retrieval Config</button>
<button>Sync Now</button>
<button>Edit Settings</button>
<button class="danger">Delete</button>
</div>
</td>
</tr>
<tr>
<td>Authentication Guides</td>
<td>1,104</td>
<td><span class="status-pill status-ok">Success 09:05</span></td>
<td>2025-08-15</td>
<td style="text-align:right; position:relative;">
<div class="row-actions">
<button class="icon-btn" data-menu="kb2"></button>
</div>
<div class="overflow-menu" id="menu-kb2">
<button onclick="location.href='kb_detail.html'">Open</button>
<button onclick="location.href='kb_retrieval_config.html'">Retrieval Config</button>
<button>Sync Now</button>
<button>Edit Settings</button>
<button class="danger">Delete</button>
</div>
</td>
</tr>
<tr>
<td>System Design Notes</td>
<td>842</td>
<td><span class="status-pill status-warn">Partial 08:58</span></td>
<td>2025-08-19</td>
<td style="text-align:right; position:relative;">
<div class="row-actions">
<button class="icon-btn" data-menu="kb3"></button>
</div>
<div class="overflow-menu" id="menu-kb3">
<button onclick="location.href='kb_detail.html'">Open</button>
<button onclick="location.href='kb_retrieval_config.html'">Retrieval Config</button>
<button>Retry Sync</button>
<button>Edit Settings</button>
<button class="danger">Delete</button>
</div>
</td>
</tr>
<tr>
<td>Operations Runbooks</td>
<td>2,309</td>
<td><span class="status-pill status-bad">Failed 08:41</span></td>
<td>2025-08-23</td>
<td style="text-align:right; position:relative;">
<div class="row-actions">
<button class="icon-btn" data-menu="kb4"></button>
</div>
<div class="overflow-menu" id="menu-kb4">
<button onclick="location.href='kb_detail.html'">Open</button>
<button onclick="location.href='kb_retrieval_config.html'">Retrieval Config</button>
<button>Retry Sync</button>
<button>Edit Settings</button>
<button class="danger">Delete</button>
</div>
</td>
</tr>
<tr>
<td>Legacy Archive</td>
<td>18,044</td>
<td><span class="status-pill status-ok">Success 02:14</span></td>
<td>2025-06-02</td>
<td style="text-align:right; position:relative;">
<div class="row-actions">
<button class="icon-btn" data-menu="kb5"></button>
</div>
<div class="overflow-menu" id="menu-kb5">
<button onclick="location.href='kb_detail.html'">Open</button>
<button onclick="location.href='kb_retrieval_config.html'">Retrieval Config</button>
<button>Sync Now</button>
<button>Edit Settings</button>
<button class="danger">Delete</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div class="flex space-between align-center" style="font-size:.6rem;opacity:.6;">
<div>Showing 15 of 42 knowledge bases</div>
<div class="flex gap-s" style="align-items:center;">
<button class="icon-btn"></button>
<span style="font-size:.65rem;">1 / 9</span>
<button class="icon-btn"></button>
</div>
</div>
</div>
</main>
</div>
<script src="assets/app.js"></script>
</body>
</html>

View File

@@ -0,0 +1,195 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>KB Retrieval Configuration - RAG Dashboard</title>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link rel="stylesheet" href="assets/app.css" />
</head>
<body>
<aside class="sidebar">
<h1>RAGflow Prototype</h1>
<ul class="nav">
<li><a href="index.html"><span class="icon"></span>Overview</a></li>
<li><a class="active" href="kb_list.html"><span class="icon"></span>Knowledge Bases</a></li>
<li><a href="pipeline_config.html"><span class="icon"></span>RAG Pipeline</a></li>
<li><a href="dashboard.html"><span class="icon"></span>Operations</a></li>
<li><a href="models_resources.html"><span class="icon"></span>Models & Resources</a></li>
<li><a href="mcp.html"><span class="icon"></span>MCP</a></li>
</ul>
<div class="footer">© 2025 RAG Demo</div>
</aside>
<div class="main-wrapper">
<header class="header">
<div class="brand-title">Knowledge Base Retrieval Configuration</div>
<div class="search-box">
<span class="magnifier"></span>
<input placeholder="Search settings..." />
</div>
<div class="user-avatar" title="User Profile"></div>
</header>
<main class="content" style="gap:1.25rem;">
<div class="cards" style="grid-template-columns:2fr 1fr; align-items:start;">
<!-- Multi-Modal Retrieval Settings -->
<div class="card" style="grid-column:1 / 2; gap:1rem;">
<h3 style="margin:0;">Multi-Modal Retrieval Settings</h3>
<label class="toggle" title="Enable multi-modal search across text, image, audio and video derived embeddings.">
<input type="checkbox" id="mmToggle" checked />
<span>Enable Multi-Modal Search</span>
</label>
<div class="small" style="margin-top:-.3rem;">When enabled: image & audio derived vectors included in top-K candidate gathering.</div>
<div class="divider"></div>
<div class="form-grid">
<div class="form-group">
<label>Image Vector Model</label>
<select>
<option>clip-vit-large-p14</option>
<option>siglip-so400m</option>
<option>blip2-feature-extractor</option>
</select>
</div>
<div class="form-group">
<label>Audio Transcription Model</label>
<select>
<option>whisper-large-v3</option>
<option>conformer-transcribe</option>
</select>
</div>
<div class="form-group">
<label>Video Frame Sample (sec)</label>
<input type="number" value="4" />
</div>
<div class="form-group">
<label>Max Frames per Asset</label>
<input type="number" value="64" />
</div>
</div>
</div>
<!-- Reranking Optimization -->
<div class="card" style="grid-column:2 / 3; gap:.85rem;">
<h3 style="margin:0;">Reranking Optimization</h3>
<label class="toggle"><input type="checkbox" id="rerankToggle" checked /><span>Enable Reranker</span></label>
<div class="form-grid" style="margin-top:.25rem;">
<div class="form-group">
<label>Reranker Model</label>
<select id="rerankerModel">
<option>cross-encoder-v2</option>
<option>mono-t5-large</option>
<option>colbert-re-rank</option>
</select>
</div>
<div class="form-group">
<label>Post-Rerank Top-N</label>
<input type="number" id="postRerankN" value="8" />
</div>
</div>
<div class="small" style="margin-top:-.4rem;">Reranking applies cross-encoder scoring; disable if latency sensitive.</div>
</div>
<!-- Hybrid Search Weights -->
<div class="card" style="grid-column:1 / -1; gap:.9rem;">
<h3 style="margin:0;">Hybrid Search Weights</h3>
<div class="slider-row" title="Adjust balance between vector semantic search and keyword lexical search.">
<span style="font-weight:600;font-size:.6rem;">Vector</span>
<input type="range" id="hybridSlider" min="0" max="100" value="70" />
<span style="font-weight:600;font-size:.6rem;">Keyword</span>
<div class="weight-badge" id="vectorWeight">70%</div>
<div class="weight-badge" id="keywordWeight">30%</div>
</div>
<div class="small">Weight influences blended score: final_score = V * (vector_w/100) + K * (keyword_w/100).</div>
</div>
<!-- Metadata Filters -->
<div class="card" style="grid-column:1 / -1; gap:.9rem;">
<h3 style="margin:0;">Metadata Filters (Rule Builder)</h3>
<div class="small">Define structured constraints applied before scoring (pre-filter stage).</div>
<div class="rule-builder" id="ruleBuilder">
<div class="rule-row" data-rule>
<select data-field>
<option value="document_type">document_type</option>
<option value="source_type">source_type</option>
<option value="date">date</option>
<option value="language">language</option>
</select>
<select data-operator>
<option value="==">==</option>
<option value=">=">>=</option>
<option value=">">></option>
<option value="<="><=</option>
<option value="<"><</option>
<option value="contains">contains</option>
</select>
<input data-value placeholder="e.g. pdf or 2024-01-01" />
<button type="button" class="remove-rule" title="Remove rule">×</button>
</div>
</div>
<button type="button" class="add-rule-btn" id="addRuleBtn">+ Add Rule</button>
<div class="small" style="opacity:.7;">Rules combined with AND. Future: OR groups, parentheses.</div>
<div class="form-group" style="margin-top:.5rem;">
<label>Raw JSON Schema (optional)</label>
<textarea rows="5" class="code" placeholder='{"filters":[{"field":"document_type","op":"==","value":"pdf"}]}'></textarea>
</div>
</div>
<!-- Save Configuration -->
<div class="card" style="grid-column:1 / -1; gap:.9rem;">
<div class="flex space-between align-center" style="flex-wrap:wrap; gap:.75rem;">
<h3 style="margin:0;">Persist Configuration</h3>
<button class="primary-btn" id="saveConfigBtn">Save Configuration</button>
</div>
<div class="small" id="saveStatus" style="opacity:.7;">No unsaved changes.</div>
</div>
</div>
</main>
</div>
<script src="assets/app.js"></script>
<script>
(function(){
const slider = document.getElementById('hybridSlider');
const vW = document.getElementById('vectorWeight');
const kW = document.getElementById('keywordWeight');
const ruleBuilder = document.getElementById('ruleBuilder');
const addRuleBtn = document.getElementById('addRuleBtn');
const saveBtn = document.getElementById('saveConfigBtn');
const saveStatus = document.getElementById('saveStatus');
function markDirty(){ saveStatus.textContent = 'Unsaved changes...'; saveStatus.style.color = 'var(--color-brand)'; }
function markSaved(){ saveStatus.textContent = 'Configuration saved (simulated)'; saveStatus.style.color = 'var(--color-text-secondary)'; }
slider.addEventListener('input', ()=>{
const v = Number(slider.value); const k = 100 - v;
vW.textContent = v + '%'; kW.textContent = k + '%';
markDirty();
});
// Add/remove rules
addRuleBtn.addEventListener('click', ()=>{
const row = document.createElement('div');
row.className = 'rule-row'; row.setAttribute('data-rule','');
row.innerHTML = `\n <select data-field>\n <option value="document_type">document_type</option>\n <option value="source_type">source_type</option>\n <option value="date">date</option>\n <option value="language">language</option>\n </select>\n <select data-operator>\n <option value="==">==</option>\n <option value=">=">>=</option>\n <option value=">">></option>\n <option value="<="><=</option>\n <option value="<"><</option>\n <option value="contains">contains</option>\n </select>\n <input data-value placeholder="value" />\n <button type="button" class="remove-rule" title="Remove rule">×</button>`;
ruleBuilder.appendChild(row); markDirty();
});
ruleBuilder.addEventListener('click', e=>{
if(e.target.classList.contains('remove-rule')){
const r = e.target.closest('[data-rule]');
if(r){ r.remove(); markDirty(); }
}
});
ruleBuilder.addEventListener('change', markDirty);
ruleBuilder.addEventListener('input', markDirty);
document.getElementById('mmToggle').addEventListener('change', markDirty);
document.getElementById('rerankToggle').addEventListener('change', markDirty);
document.getElementById('postRerankN').addEventListener('input', markDirty);
document.getElementById('rerankerModel').addEventListener('change', markDirty);
saveBtn.addEventListener('click', ()=>{
saveBtn.disabled = true; saveBtn.textContent = 'Saving...';
setTimeout(()=>{ saveBtn.textContent = 'Save Configuration'; saveBtn.disabled = false; markSaved(); }, 900);
});
})();
</script>
</body>
</html>

70
web_prototype/login.html Normal file
View File

@@ -0,0 +1,70 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Login - RAG Dashboard</title>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link rel="stylesheet" href="assets/app.css" />
</head>
<body class="login-shell">
<div class="login-topbar">
<div class="brand-logo" aria-label="Brand">T</div>
</div>
<main class="login-main">
<section class="login-card" role="form" aria-labelledby="loginTitle">
<div class="login-service">Servicename</div>
<h2 id="loginTitle">Enter Login <br/> Username</h2>
<form class="login-form" id="loginForm" novalidate>
<div class="field">
<input type="text" id="username" name="username" placeholder="Username" autocomplete="username" required />
</div>
<div class="remember-row">
<label style="display:flex;align-items:center;gap:.4rem;cursor:pointer;">
<input type="checkbox" id="rememberUser" />
<span>Remember username</span>
</label>
<a href="#" style="margin-left:auto;font-size:.6rem;">Forgot your username or password?</a>
</div>
<div class="login-actions">
<button type="submit" class="login-btn-primary" id="nextBtn">Next</button>
<button type="button" class="login-btn-secondary" id="cancelBtn">Cancel</button>
</div>
</form>
<div class="login-help">
<a href="#">Do you need help?</a>
</div>
<div class="login-help" style="margin-top:.25rem;">
No account? <a href="#">Sign up</a> or log in with your social network account.
</div>
<div class="login-social">
<a href="#" aria-label="Login with Facebook">f</a>
<a href="#" aria-label="Login with Twitter">t</a>
</div>
</section>
</main>
<footer class="login-footer">
<div>© Deutsche Telekom AG</div>
<div class="legal-links"><a href="#">Imprint</a><a href="#">Data privacy</a></div>
</footer>
<script>
(function(){
const form = document.getElementById('loginForm');
const nextBtn = document.getElementById('nextBtn');
const cancelBtn = document.getElementById('cancelBtn');
form.addEventListener('submit', e => {
e.preventDefault();
const username = form.username.value.trim();
if(!username){
form.username.focus();
form.username.style.borderColor='var(--color-danger)';
form.username.style.boxShadow='0 0 0 3px rgba(210,44,50,.3)';
return;
}
nextBtn.disabled = true; nextBtn.textContent='Processing...';
setTimeout(()=>{ window.location.href = 'index.html'; }, 800); // simulate
});
cancelBtn.addEventListener('click', ()=>{ window.location.href = 'index.html'; });
})();
</script>
</body>
</html>

258
web_prototype/mcp.html Normal file
View File

@@ -0,0 +1,258 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>MCP Server Management - RAG Dashboard</title>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link rel="stylesheet" href="assets/app.css" />
</head>
<body>
<aside class="sidebar">
<h1>RAGflow Prototype</h1>
<ul class="nav">
<li><a href="index.html"><span class="icon"></span>Overview</a></li>
<li><a href="kb_list.html"><span class="icon"></span>Knowledge Bases</a></li>
<li><a href="pipeline_config.html"><span class="icon"></span>RAG Pipeline</a></li>
<li><a href="dashboard.html"><span class="icon"></span>Operations</a></li>
<li><a href="models_resources.html"><span class="icon"></span>Models & Resources</a></li>
<li><a class="active" href="mcp.html"><span class="icon"></span>MCP</a></li>
</ul>
<div class="footer">© 2025 RAG Demo</div>
</aside>
<div class="main-wrapper">
<header class="header">
<div class="brand-title">MCP Server Registry</div>
<div class="search-box">
<span class="magnifier"></span>
<input placeholder="Search providers, models..." />
</div>
<div class="user-avatar" title="User Profile"></div>
</header>
<main class="content" style="gap:1.25rem;">
<div class="cards" style="grid-template-columns:2.1fr 1fr; align-items:start;">
<!-- Registered MCP Servers -->
<div class="card" style="grid-column:1 / 2; gap:.9rem;">
<div class="flex space-between align-center" style="flex-wrap:wrap; gap:.75rem;">
<h3 style="margin:0;">Registered MCP Servers</h3>
<button class="primary-btn">+ Register New MCP Server</button>
</div>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th style="min-width:180px;">Server URL</th>
<th>Tooling Namespace</th>
<th>Status</th>
<th>Owning Team</th>
<th>Last Check</th>
<th style="text-align:right;">Actions</th>
</tr>
</thead>
<tbody>
<tr>
<td>https://mcp-hr.internal/api</td>
<td>hr.tools</td>
<td><span class="status-pill status-ok">Online</span></td>
<td>PeopleOps</td>
<td>09:21</td>
<td style="text-align:right; position:relative;">
<div class="row-actions"><button class="icon-btn" data-menu="s1"></button></div>
<div class="overflow-menu" id="menu-s1">
<button>View Detail</button>
<button>Run Health Check</button>
<button>Reload Schema</button>
<button class="danger">Deregister</button>
</div>
</td>
</tr>
<tr>
<td>https://mcp-finance.internal/v1</td>
<td>finance.calc</td>
<td><span class="status-pill status-warn">Degraded</span></td>
<td>Finance</td>
<td>09:17</td>
<td style="text-align:right; position:relative;">
<div class="row-actions"><button class="icon-btn" data-menu="s2"></button></div>
<div class="overflow-menu" id="menu-s2">
<button>View Detail</button>
<button>Run Health Check</button>
<button>Reload Schema</button>
<button class="danger">Deregister</button>
</div>
</td>
</tr>
<tr>
<td>https://mcp-legal.internal</td>
<td>legal.search</td>
<td><span class="status-pill status-ok">Online</span></td>
<td>Legal</td>
<td>09:09</td>
<td style="text-align:right; position:relative;">
<div class="row-actions"><button class="icon-btn" data-menu="s3"></button></div>
<div class="overflow-menu" id="menu-s3">
<button>View Detail</button>
<button>Run Health Check</button>
<button>Reload Schema</button>
<button class="danger">Deregister</button>
</div>
</td>
</tr>
<tr>
<td>grpc://mcp-ai-lab.internal:7443</td>
<td>ailab.exp</td>
<td><span class="status-pill status-ok">Online</span></td>
<td>AI Lab</td>
<td>09:02</td>
<td style="text-align:right; position:relative;">
<div class="row-actions"><button class="icon-btn" data-menu="s4"></button></div>
<div class="overflow-menu" id="menu-s4">
<button>View Detail</button>
<button>Run Health Check</button>
<button>Reload Schema</button>
<button class="danger">Deregister</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div class="flex space-between align-center" style="font-size:.6rem;opacity:.6;">
<div>Showing 14 of 4 MCP servers</div>
<div class="flex gap-s" style="align-items:center;">
<button class="icon-btn" disabled></button>
<span style="font-size:.65rem;">1 / 1</span>
<button class="icon-btn" disabled></button>
</div>
</div>
</div>
<!-- API Gateway Keys Panel -->
<div class="card" style="grid-column:2 / 3; gap:.85rem;">
<h3 style="margin:0;">MCP API Gateway Keys</h3>
<div class="small">Keys used by internal apps to call unified MCP gateway. Masked by default.</div>
<div class="form-group" style="margin-top:.35rem;">
<label>Gateway Key (Primary)</label>
<div class="mask-wrapper">
<input type="password" value="gw-primary-******************4e2" data-mask-target />
<button type="button" data-toggle-mask>Show</button>
</div>
</div>
<div class="form-group">
<label>Gateway Key (Backup)</label>
<div class="mask-wrapper">
<input type="password" value="gw-backup-******************91b" data-mask-target />
<button type="button" data-toggle-mask>Show</button>
</div>
</div>
<div class="small" style="opacity:.7;">Last rotation: 5 days ago</div>
<div class="flex gap-s" style="flex-wrap:wrap;">
<button class="secondary-btn">Rotate Primary</button>
<button class="secondary-btn">Rotate Backup</button>
</div>
<div class="divider"></div>
<div class="small" style="font-weight:600;">Usage Limits</div>
<div style="display:flex;flex-direction:column;gap:.4rem;font-size:.6rem;">
<div>Requests Today: 12,480 / 50,000</div>
<div style="height:6px;background:#ECECEC;border-radius:4px;overflow:hidden;">
<div style="width:25%;height:100%;background:linear-gradient(90deg,var(--color-brand),#FF4DA8);"></div>
</div>
<div>Error Rate (24h): 0.42%</div>
<div style="height:6px;background:#ECECEC;border-radius:4px;overflow:hidden;">
<div style="width:0.42%;min-width:4px;height:100%;background:var(--color-brand);"></div>
</div>
</div>
</div>
<!-- Context Function Mapping -->
<div class="card" style="grid-column:1 / -1; gap:.9rem;">
<h3 style="margin:0;">Context Function Mapping</h3>
<div class="small">Internal simple tool identifiers mapped to remote MCP server exposed function signatures.</div>
<div class="table-wrapper" style="margin-top:.5rem;">
<table>
<thead>
<tr>
<th style="min-width:160px;">Internal Tool Name</th>
<th>MCP Server URL</th>
<th>Remote Function</th>
<th>Auth Mode</th>
<th>Last Used</th>
<th style="text-align:right;">Actions</th>
</tr>
</thead>
<tbody>
<tr>
<td>HR_policy_lookup</td>
<td>https://mcp-hr.internal/api</td>
<td>policies.lookup(v1)</td>
<td>gateway-key</td>
<td>09:20</td>
<td style="text-align:right; position:relative;">
<div class="row-actions"><button class="icon-btn" data-menu="f1"></button></div>
<div class="overflow-menu" id="menu-f1">
<button>Edit Mapping</button>
<button>Test Invoke</button>
<button class="danger">Remove</button>
</div>
</td>
</tr>
<tr>
<td>Finance_cost_summary</td>
<td>https://mcp-finance.internal/v1</td>
<td>costs.aggregate(month)</td>
<td>gateway-key</td>
<td>09:18</td>
<td style="text-align:right; position:relative;">
<div class="row-actions"><button class="icon-btn" data-menu="f2"></button></div>
<div class="overflow-menu" id="menu-f2">
<button>Edit Mapping</button>
<button>Test Invoke</button>
<button class="danger">Remove</button>
</div>
</td>
</tr>
<tr>
<td>Legal_doc_search</td>
<td>https://mcp-legal.internal</td>
<td>documents.search(text)</td>
<td>gateway-key</td>
<td>09:11</td>
<td style="text-align:right; position:relative;">
<div class="row-actions"><button class="icon-btn" data-menu="f3"></button></div>
<div class="overflow-menu" id="menu-f3">
<button>Edit Mapping</button>
<button>Test Invoke</button>
<button class="danger">Remove</button>
</div>
</td>
</tr>
<tr>
<td>AI_experiment_trigger</td>
<td>grpc://mcp-ai-lab.internal:7443</td>
<td>exp.runVariant(set)</td>
<td>mTLS</td>
<td>08:59</td>
<td style="text-align:right; position:relative;">
<div class="row-actions"><button class="icon-btn" data-menu="f4"></button></div>
<div class="overflow-menu" id="menu-f4">
<button>Edit Mapping</button>
<button>Test Invoke</button>
<button class="danger">Remove</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div class="flex gap-s" style="flex-wrap:wrap;">
<button class="secondary-btn">Add Function Mapping</button>
<button class="secondary-btn">Bulk Import</button>
<button class="secondary-btn">Export JSON</button>
</div>
</div>
</div>
</div>
</main>
</div>
<script src="assets/app.js"></script>
</body>
</html>

View File

@@ -0,0 +1,132 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Models & Resources - RAG Dashboard</title>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link rel="stylesheet" href="assets/app.css" />
</head>
<body>
<aside class="sidebar">
<h1>RAGflow Prototype</h1>
<ul class="nav">
<li><a href="index.html"><span class="icon"></span>Overview</a></li>
<li><a href="kb_list.html"><span class="icon"></span>Knowledge Bases</a></li>
<li><a href="pipeline_config.html"><span class="icon"></span>RAG Pipeline</a></li>
<li><a href="dashboard.html"><span class="icon"></span>Operations</a></li>
<li><a class="active" href="models_resources.html"><span class="icon"></span>Models & Resources</a></li>
<li><a href="mcp.html"><span class="icon"></span>MCP</a></li>
</ul>
<div class="footer">© 2025 RAG Demo</div>
</aside>
<div class="main-wrapper">
<header class="header">
<div class="brand-title">Model & Resource Management</div>
<div class="search-box">
<span class="magnifier"></span>
<input placeholder="Search models, keys..." />
</div>
<div class="user-avatar" title="User Profile"></div>
</header>
<main class="content" style="gap:1.25rem;">
<div class="card" style="gap:.4rem;">
<div class="tabs" id="modelTabs">
<div class="tab active" data-tab="llm">LLM Models</div>
<div class="tab" data-tab="embedding">Embedding Models</div>
<div class="tab" data-tab="vector">Vector Stores</div>
</div>
<div id="tabPanels" style="margin-top:.6rem;">
<div data-panel="llm">
<h3 style="margin:.2rem 0 .8rem;">Configured LLM Providers</h3>
<div class="form-grid">
<div class="form-group">
<label>Provider Name</label>
<input value="OpenAI GPT-4" />
</div>
<div class="form-group">
<label>Endpoint URL</label>
<input value="https://api.openai.com/v1/chat/completions" />
</div>
<div class="form-group">
<label>Rate Limit (req/min)</label>
<input type="number" value="120" />
</div>
<div class="form-group">
<label>API Key</label>
<div class="mask-wrapper">
<input type="password" value="sk-live-*****************123" data-mask-target />
<button type="button" data-toggle-mask>Show</button>
</div>
</div>
<div class="form-group">
<label>Connection Status</label>
<input value="Healthy" disabled style="color:var(--color-success); font-weight:600;" />
</div>
</div>
<div class="divider"></div>
<button class="primary-btn">Save LLM Config</button>
</div>
<div data-panel="embedding" style="display:none;">
<h3 style="margin:.2rem 0 .8rem;">Embedding Model Settings</h3>
<div class="form-grid">
<div class="form-group">
<label>Model Name</label>
<input value="text-embedding-3-large" />
</div>
<div class="form-group">
<label>Endpoint URL</label>
<input value="https://api.example.com/embeddings" />
</div>
<div class="form-group">
<label>Batch Size</label>
<input type="number" value="64" />
</div>
<div class="form-group">
<label>API Key</label>
<div class="mask-wrapper">
<input type="password" value="emb-live-***********xyz" data-mask-target />
<button type="button" data-toggle-mask>Show</button>
</div>
</div>
<div class="form-group">
<label>Connection Status</label>
<input value="Healthy" disabled style="color:var(--color-success); font-weight:600;" />
</div>
</div>
<div class="divider"></div>
<button class="primary-btn">Save Embedding Config</button>
</div>
<div data-panel="vector" style="display:none;">
<h3 style="margin:.2rem 0 .8rem;">Vector Store Configuration</h3>
<div class="form-grid">
<div class="form-group">
<label>Store Name</label>
<input value="pgvector-prod" />
</div>
<div class="form-group">
<label>Host</label>
<input value="10.14.3.27" />
</div>
<div class="form-group">
<label>Port</label>
<input value="5432" />
</div>
<div class="form-group">
<label>Namespace</label>
<input value="prod_docs" />
</div>
<div class="form-group">
<label>Connection Status</label>
<input value="Healthy" disabled style="color:var(--color-success); font-weight:600;" />
</div>
</div>
<div class="divider"></div>
<button class="primary-btn">Save Vector Store</button>
</div>
</div>
</div>
</main>
</div>
<script src="assets/app.js"></script>
</body>
</html>

View File

@@ -0,0 +1,180 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Pipeline Configuration - RAG Dashboard</title>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link rel="stylesheet" href="assets/app.css" />
</head>
<body>
<aside class="sidebar">
<h1>RAGflow Prototype</h1>
<ul class="nav">
<li><a href="index.html"><span class="icon"></span>Overview</a></li>
<li><a href="kb_list.html"><span class="icon"></span>Knowledge Bases</a></li>
<li><a class="active" href="pipeline_config.html"><span class="icon"></span>RAG Pipeline</a></li>
<li><a href="dashboard.html"><span class="icon"></span>Operations</a></li>
<li><a href="models_resources.html"><span class="icon"></span>Models & Resources</a></li>
<li><a href="mcp.html"><span class="icon"></span>MCP</a></li>
</ul>
<div class="footer">© 2025 RAG Demo</div>
</aside>
<div class="main-wrapper">
<header class="header">
<div class="brand-title">Pipeline Configuration</div>
<div class="search-box">
<span class="magnifier"></span>
<input placeholder="Search settings, steps..." />
</div>
<div class="user-avatar" title="User Profile"></div>
</header>
<main class="content" style="gap:1.25rem;">
<div class="card" style="gap:.6rem;">
<h3 style="margin:0;">Pipeline Steps</h3>
<div class="stepper horizontal" id="pipelineStepper">
<div class="step active" data-step="1"><div class="step-circle">1</div><div class="step-label">Data Ingestion</div></div>
<div class="step" data-step="2"><div class="step-circle">2</div><div class="step-label">Chunking</div></div>
<div class="step" data-step="3"><div class="step-circle">3</div><div class="step-label">Retrieval</div></div>
<div class="step" data-step="4"><div class="step-circle">4</div><div class="step-label">Reranking</div></div>
<div class="step" data-step="5"><div class="step-circle">5</div><div class="step-label">Generation</div></div>
</div>
<div class="divider"></div>
<div class="flex gap-m" style="align-items:flex-start; flex-wrap:wrap;">
<div class="col" style="flex:2; min-width:360px; gap:.9rem;">
<div class="form-group">
<label for="promptTemplate">Prompt Template (Generation)</label>
<textarea id="promptTemplate" class="code" rows="12" placeholder="You are an assistant that answers with grounded information...">System: You are a helpful assistant. Use ONLY the provided context.\n\nUser Query: {{query}}\n\nRelevant Chunks:\n{{context_blocks}}\n\nAnswer concisely and cite sources.</textarea>
</div>
<div class="form-grid" style="margin-top:.25rem;">
<div class="form-group">
<label>LLM Model</label>
<select>
<option>gpt-4o-mini</option>
<option>gpt-4o</option>
<option>llama-3-70b</option>
</select>
</div>
<div class="form-group">
<label>Embedding Model</label>
<select>
<option>text-embedding-3-large</option>
<option>e5-large-v2</option>
<option>gte-large</option>
</select>
</div>
<div class="form-group">
<label>Reranker</label>
<select>
<option>cross-encoder-v2</option>
<option>mono-t5-large</option>
</select>
</div>
<div class="form-group">
<label>Retriever Top-K</label>
<input type="number" value="8" />
</div>
</div>
<div class="form-grid">
<div class="form-group">
<label>Temperature</label>
<input type="number" step="0.1" value="0.4" />
</div>
<div class="form-group">
<label>Max Tokens</label>
<input type="number" value="512" />
</div>
<div class="form-group">
<label>Presence Penalty</label>
<input type="number" step="0.1" value="0" />
</div>
<div class="form-group">
<label>Frequency Penalty</label>
<input type="number" step="0.1" value="0" />
</div>
</div>
</div>
<div class="col" style="flex:1.2; min-width:300px; gap:.9rem;">
<div class="card" style="padding:1rem; gap:.85rem;">
<h3 style="margin:0;">Step Settings</h3>
<div class="small">Configuration depends on current active step in pipeline.</div>
<div id="stepSettings">
<div data-step-panel="1">
<div class="form-group">
<label>Ingestion Source</label>
<select><option>Cloud Storage</option><option>Git Repo</option><option>Web Crawl</option></select>
</div>
<div class="form-group">
<label>File Types</label>
<input value="pdf, md, html" />
</div>
<div class="form-group">
<label>Schedule (cron)</label>
<input value="0 */2 * * *" />
</div>
</div>
<div data-step-panel="2" style="display:none;">
<div class="form-group">
<label>Chunk Size (tokens)</label>
<input value="512" />
</div>
<div class="form-group">
<label>Chunk Overlap</label>
<input value="40" />
</div>
<div class="form-group">
<label>Splitter Strategy</label>
<select><option>Recursive</option><option>Naive</option><option>Markdown Headers</option></select>
</div>
</div>
<div data-step-panel="3" style="display:none;">
<div class="form-group">
<label>Vector Store</label>
<select><option>pgvector-prod</option><option>milvus-cluster</option></select>
</div>
<div class="form-group">
<label>Distance Metric</label>
<select><option>cosine</option><option>l2</option><option>dot</option></select>
</div>
<div class="form-group">
<label>Min Score Threshold</label>
<input value="0.35" />
</div>
</div>
<div data-step-panel="4" style="display:none;">
<div class="form-group">
<label>Rerank Top-N</label>
<input value="25" />
</div>
<div class="form-group">
<label>Score Normalization</label>
<select><option>minmax</option><option>z-score</option></select>
</div>
</div>
<div data-step-panel="5" style="display:none;">
<div class="form-group">
<label>Answer Style Hint</label>
<input value="Concise with references" />
</div>
<div class="form-group">
<label>Include Source Citations</label>
<select><option>Yes</option><option>No</option></select>
</div>
</div>
</div>
<button class="primary-btn" style="margin-top:.5rem;">Save Pipeline</button>
</div>
<div class="card" style="padding:1rem; gap:.75rem;">
<h3 style="margin:0;">Preview Output (Sample)</h3>
<div class="small">Updated after generation step evaluation.</div>
<div style="background:#FAFAFA;border:1px solid var(--color-border);border-radius:8px;padding:.65rem .7rem;font-size:.65rem;line-height:1.4;max-height:160px;overflow:auto;">
This device requires a factory reset performed via the embedded maintenance console. Refer to Section 4.2 of manual.pdf for command sequence. Sources: manual.pdf (p. 18)
</div>
</div>
</div>
</div>
</div>
</main>
</div>
<script src="assets/app.js"></script>
</body>
</html>