File size: 39,111 Bytes
c61c435
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f8c73f9
 
c61c435
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f8c73f9
 
 
 
 
c61c435
 
 
 
 
 
f8c73f9
c61c435
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f8c73f9
c61c435
 
 
f8c73f9
c61c435
 
 
 
 
f8c73f9
 
 
c61c435
 
 
 
 
 
 
 
 
 
 
 
f8c73f9
 
 
 
 
c61c435
 
 
 
 
 
f8c73f9
c61c435
f8c73f9
 
c61c435
 
 
 
f8c73f9
c61c435
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
from __future__ import annotations


def remote_dashboard_app_html() -> str:
    return """<!doctype html>

<html lang="en">

<head>

<meta charset="utf-8">

<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">

<title>ADAM Remote</title>

<style>

:root{color-scheme:dark;--bg:#050911;--panel:#111823;--panel2:#171f2b;--line:#26374b;--text:#f4f8ff;--muted:#94a2b1;--blue:#3988ff;--blue2:#74bdff;--gold:#f5c84d;--warn:#f8c35d;--bad:#ff6d86}

*{box-sizing:border-box}body{margin:0;background:radial-gradient(circle at 50% -20%,#102747 0,#07101c 34%,#02060b 100%);color:var(--text);font:15px/1.38 system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}button,input,select,textarea{font:inherit}button{border:1px solid #32475d;border-radius:8px;background:#1a2431;color:var(--text);padding:10px 12px;font-weight:750;min-height:42px}button.primary{background:linear-gradient(180deg,#438dff,#2467df);color:#fff;border-color:#4a97ff;box-shadow:0 8px 22px rgba(36,103,223,.28)}button.ghost{background:#121923;color:#c5e4ff;border-color:#2d76c9}button.warn{background:#352816;color:#ffe3a3}button.bad{background:#391d29;color:#ffd2db}button.icon{width:40px;min-width:40px;padding:0;color:var(--gold);font-size:22px;background:transparent;border:0}button:disabled{opacity:.46}input,select,textarea{width:100%;border:1px solid var(--line);border-radius:8px;background:#101722;color:var(--text);min-height:44px;padding:10px 12px}textarea{resize:vertical;min-height:86px}.app{min-height:100vh;padding:12px 14px 88px}.top,.titlebar{display:grid;grid-template-columns:42px 1fr 42px;align-items:center;margin:2px 0 14px}.brand{text-align:center}.brand b{display:block;font-size:22px}.brand span,.muted{color:var(--muted);font-size:13px}.titlebar h1{margin:0;text-align:center;font-size:22px;letter-spacing:0}.pill{border:1px solid var(--line);border-radius:999px;padding:6px 10px;color:var(--blue2);background:#0d1721;font-size:12px}.tabs{position:fixed;left:0;right:0;bottom:0;display:grid;grid-template-columns:repeat(5,1fr);background:rgba(4,8,13,.97);border-top:1px solid var(--line);z-index:20}.tabs button{border-radius:0;border:0;border-left:1px solid #111b25;background:transparent;color:var(--muted);font-size:12px;padding:9px 4px}.tabs button b{display:block;font-size:20px;line-height:1}.tabs button.active{color:var(--blue2);background:#0b1421}.view{display:none}.view.active{display:block}.grid{display:grid;gap:10px}.two{grid-template-columns:1fr 1fr}.card{background:rgba(17,24,35,.92);border:1px solid var(--line);border-radius:8px;padding:12px}.panel{background:transparent;border:0;padding:0}.section-row{display:flex;justify-content:space-between;align-items:center;margin:16px 4px 8px}.section{font-size:17px;color:var(--text);font-weight:850;margin:0}.view-all{color:var(--blue2);font-size:13px;font-weight:800}.row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.row>*{flex:1 1 auto}.status{min-height:20px;color:var(--muted);font-size:13px;white-space:pre-wrap}.progress{height:9px;border-radius:999px;background:#05080c;overflow:hidden;margin-top:10px}.bar{height:100%;width:0;background:linear-gradient(90deg,var(--blue),var(--blue2))}.dataset-list{display:grid;gap:8px}.dataset-card{display:grid;grid-template-columns:82px 1fr 36px;gap:10px;align-items:center;border:1px solid rgba(88,112,140,.35);border-radius:8px;background:linear-gradient(180deg,#171f2b,#111923);padding:8px;width:100%;box-shadow:0 10px 24px rgba(0,0,0,.18)}.dataset-card.compact{grid-template-columns:54px 1fr 22px}.dataset-card.unavailable{opacity:.62}.dataset-card img{width:82px;height:74px;object-fit:cover;border-radius:7px;background:#05080c}.dataset-card.compact img{width:54px;height:54px}.dataset-card b{font-size:16px;overflow-wrap:anywhere}.dataset-actions{grid-column:2/4;display:grid;grid-template-columns:1fr 1.15fr;gap:8px}.dataset-actions button{min-height:36px;padding:7px 10px}.meta{color:var(--muted);font-size:12px}.search-wrap{position:relative}.search-wrap span{position:absolute;left:12px;top:11px;color:var(--muted)}.search-wrap input{padding-left:38px}.chips{display:flex;gap:6px;overflow:auto;padding-bottom:2px}.chip{white-space:nowrap;border:1px solid var(--line);border-radius:999px;background:#0b1219;color:var(--muted);padding:7px 10px}.chip.active{color:#fff;background:#173b63;border-color:#3f8fff}.thumbs{display:grid;grid-template-columns:repeat(auto-fill,minmax(92px,1fr));gap:8px}.thumb{padding:0;overflow:hidden;text-align:left;background:#0d151e}.thumb img{display:block;width:100%;aspect-ratio:1;object-fit:cover;background:#05080c}.thumb div{padding:6px;color:var(--muted);font-size:12px;overflow-wrap:anywhere}.preview{display:block;width:100%;max-height:54vh;object-fit:contain;border-radius:8px;background:#05080c}.form{display:grid;gap:18px}.form-row{display:grid;grid-template-columns:112px 1fr;gap:10px;align-items:center}.form-row.stack{grid-template-columns:1fr}.form-row label,.field label{color:var(--text);font-size:15px;font-weight:780}.hint{color:var(--muted);font-size:12px;margin-top:5px}.toggle{display:grid;grid-template-columns:1fr 58px;gap:10px;align-items:center}.toggle input{width:58px;min-height:32px;accent-color:var(--blue)}.settings-grid{display:grid;gap:12px}.details summary{cursor:pointer;color:var(--blue2);font-weight:800;padding:8px 0}.queue-item{border:1px solid var(--line);border-radius:8px;background:#0d151e;padding:10px}.bad-text{color:var(--bad)}pre{overflow:auto}.big-action{width:100%;min-height:58px;font-size:22px}.selected-note{margin-top:6px;color:var(--muted);font-size:13px}

@media(min-width:760px){.app{max-width:1020px;margin:0 auto}.wide{grid-template-columns:1.05fr .95fr}.settings-grid{grid-template-columns:1fr 1fr}.tabs{left:50%;transform:translateX(-50%);max-width:1020px;border-left:1px solid var(--line);border-right:1px solid var(--line)}}

</style>

</head>

<body>

<main class="app">

  <div class="top"><button id="refresh" class="ghost">Refresh</button><div class="brand"><b>ADAM Remote</b><span id="connection">Connecting</span></div><span></span></div>



  <section id="home" class="view active">

    <div class="grid wide">

      <article class="card"><div class="section">Active Job</div><div id="activeJob">Checking ADAM...</div><div class="progress"><div id="activeBar" class="bar"></div></div><div class="row" style="margin-top:10px"><span class="pill">Time Left <b id="timeLeft">-</b></span><span class="pill">Finish <b id="finishTime">-</b></span></div><div class="row" style="margin-top:10px"><button data-action="pause">Pause</button><button data-action="resume">Resume</button><button class="bad" data-action="cancel">Cancel</button></div></article>

      <article class="card"><div class="section">Live Preview</div><img id="preview" class="preview" alt="Live preview" style="display:none"><div id="previewNote" class="status">Waiting for a preview.</div></article>

      <article class="card"><div class="section">Prompt ADAM</div><textarea id="prompt" placeholder="Ask ADAM naturally."></textarea><div class="row"><button class="primary" id="sendPrompt">Send</button><button class="ghost" id="askCreate">Create Model</button><button class="ghost" id="askDataset">Collect Dataset</button><button class="ghost" id="askQuick">Quick</button></div><div id="promptStatus" class="status"></div></article>
      <article class="card"><div class="section">Image Generation</div><div class="status">Choose the model directly instead of describing a LoRA in a prompt.</div><button id="openGeneration" class="primary" style="margin-top:10px">Generate an Image</button></article>
      <article class="card"><div class="section">Latest Generation</div><div id="latestGeneration" class="thumbs"></div></article>

    </div>

  </section>



  <section id="datasetsView" class="view">

    <div class="titlebar"><span></span><h1>Datasets</h1><button id="filterDatasets" class="icon" title="Filter">+</button></div>

    <div class="search-wrap"><span>Q</span><input id="datasetSearch" placeholder="Search datasets..."></div>

    <div id="locationChips" class="chips" style="margin-top:10px"></div>

    <div class="section-row"><div class="section">Favorites</div><div class="view-all">View All</div></div><div id="favoriteDatasets" class="dataset-list"></div>

    <div class="section-row"><div class="section">Recently Used</div><div class="view-all">View All</div></div><div id="recentDatasets" class="dataset-list"></div>

    <div class="section-row"><div class="section">Locations</div></div><div id="locationsListInline" class="dataset-list"></div>

    <div class="section-row"><div class="section">All Remembered</div></div><div id="allDatasets" class="dataset-list"></div>

    <article id="datasetDetail" class="card" style="display:none;margin-top:12px">

      <div class="section">Preview</div>

      <div id="datasetTitle"></div><div id="datasetStats" class="meta"></div>

      <div class="row" style="margin:10px 0"><button id="useDataset" class="primary">Use Dataset</button><button id="selectDatasetForCreate" class="ghost">Select For Create</button></div>

      <div id="datasetGrid" class="thumbs"></div>

      <div class="row" style="margin-top:10px"><button id="prevPage">Previous</button><span id="pageLabel" class="pill">Page 1</span><button id="nextPage">Next</button></div>

    </article>

    <article id="imageDetail" class="card" style="display:none;margin-top:12px"><img id="detailImage" class="preview" alt="Dataset image"><div id="detailName"></div><div id="detailDims" class="meta"></div><div class="field"><label>Caption</label><textarea id="captionEditor"></textarea></div><div class="row"><button id="saveCaption">Save</button><button id="keepImage">Keep</button><button class="warn" id="rejectImage">Reject</button><button id="unreviewImage">Unreview</button></div><div id="imageStatus" class="status"></div></article>

  </section>



  <section id="createView" class="view">

    <div class="titlebar"><button id="createBack" class="icon" title="Back">&lt;</button><h1>Create Model</h1><button id="createHelp" class="icon" title="Help">?</button></div>

    <article class="panel">

      <div class="form">

        <div class="form-row"><label>Preset</label><div><select id="preset"></select><div class="hint">Save time with a preset configuration.</div></div></div>

        <div class="form-row"><label>Trainer</label><select id="trainer"></select></div>

        <div class="form-row stack"><label>Model Name</label><input id="modelName" placeholder="Minecraft Oasis V3"></div>

        <div class="form-row stack"><label>Dataset</label><button id="chooseDataset" class="ghost">Choose Dataset</button><select id="trainDataset" style="display:none"></select><div id="selectedDatasetNote" class="selected-note">No dataset selected</div></div>

        <div id="baseModelWrap" class="form-row stack"><label>Base model</label><select id="baseModel"></select></div>

        <div id="basicSettings" class="settings-grid"></div>

        <details class="details"><summary>Advanced Settings</summary><div id="advancedSettings" class="settings-grid"></div></details>

        <button id="startTraining" class="primary big-action">Start Training</button>

        <button id="askInstead" class="ghost big-action">Ask ADAM Instead</button>

        <button id="reviewPlan" class="ghost">Review Plan</button>

        <pre id="trainingReview" class="status"></pre>

      </div>

    </article>

  </section>

  <section id="generationView" class="view"><div class="titlebar"><button id="generationBack" class="icon" title="Back">&lt;</button><h1>Generate Image</h1><span></span></div><article class="panel"><div class="form"><div class="form-row"><label>Provider</label><select id="generationProvider"></select></div><div class="form-row stack"><label>Model</label><select id="generationModel"></select><div id="generationModelNote" class="hint"></div></div><div id="generationBaseWrap" class="form-row stack"><label>Base model</label><select id="generationBase"></select></div><div class="form-row stack"><label>Prompt</label><textarea id="generationPrompt" placeholder="Describe the image you want."></textarea></div><div class="form-row stack"><label>Negative prompt</label><textarea id="generationNegative" placeholder="Optional"></textarea></div><div class="settings-grid"><div class="form-row"><label>Images</label><input id="generationCount" type="number" min="1" max="8" value="1"></div><div class="form-row"><label>Steps</label><input id="generationSteps" type="number" min="1" value="30"></div><div class="form-row"><label>Seed</label><input id="generationSeed" type="number" min="0" value="0"></div><div class="form-row"><label>LoRA strength</label><input id="generationStrength" type="number" min="0" max="3" step="0.05" value="1"></div></div><button id="startGeneration" class="primary big-action">Generate</button><div id="generationStatus" class="status"></div></div></article></section>

  <section id="jobsView" class="view"><article class="card"><div class="section">Jobs</div><div id="queues" class="grid"></div></article></section>
  <section id="settingsView" class="view"><div class="grid"><article class="card"><div class="section">Remote Control</div><label class="toggle"><span>Auto-approve remote training</span><input id="autoApproveTraining" type="checkbox"></label><label class="toggle"><span>Keep screen updated</span><input id="keepAwake" type="checkbox"></label><div id="settingsStatus" class="status"></div></article><article class="card"><div class="section">Remembered Locations</div><div id="locationsList" class="dataset-list"></div></article><article class="card"><div class="section">System</div><div id="system" class="grid two"></div></article></div></section>

</main>

<nav class="tabs"><button class="active" data-view="home"><b>^</b>Home</button><button data-view="datasetsView"><b>O</b>Datasets</button><button data-view="createView"><b>+</b>Create</button><button data-view="jobsView"><b>/</b>Jobs</button><button data-view="settingsView"><b>*</b>Settings</button></nav>

<script>

(function(){

var queryString=window.location.search||"";

var state={datasets:[],locations:[],trainingSchema:{trainers:[],base_models:[],presets:[]},generationSchema:{providers:[],models:[],base_models:[]},selectedDataset:"",datasetPage:1,selectedItem:null,activeJobId:"",promptJobId:"",promptLocked:false,refreshMs:3000,locationFilter:""};
var timer=null;

function $(id){return document.getElementById(id)}

function list(v){return Array.isArray(v)?v:[]}

function clear(n){if(!n)return;while(n.firstChild)n.removeChild(n.firstChild)}

function text(id,v){var n=$(id);if(n)n.textContent=v==null?"":String(v)}

function authUrl(path,extra){var token=queryString.replace(/^\\?/,"");var url=path;if(token)url+=(url.indexOf("?")>=0?"&":"?")+token;if(extra)url+=(url.indexOf("?")>=0?"&":"?")+extra;return url}

function errorMessage(e){return e&&e.message?e.message:String(e||"Remote request failed.")}

function requestJson(path,method,payload){return new Promise(function(resolve,reject){var x=new XMLHttpRequest();x.open(method||"GET",authUrl(path),true);x.timeout=25000;x.setRequestHeader("Accept","application/json");if(payload!==undefined)x.setRequestHeader("Content-Type","application/json");x.onreadystatechange=function(){if(x.readyState!==4)return;var p={};try{p=x.responseText?JSON.parse(x.responseText):{}}catch(_e){reject(new Error("ADAM returned an unreadable response."));return}if(x.status<200||x.status>=300){reject(new Error(p.error||"Remote request failed ("+x.status+")."));return}resolve(p)};x.onerror=function(){reject(new Error("Connection failed. Check that ADAM Remote is still running."))};x.ontimeout=function(){reject(new Error("Connection timed out. The PC may be busy."))};x.send(payload===undefined?null:JSON.stringify(payload))})}

function getJson(path){return requestJson(path,"GET")}function postJson(path,payload){return requestJson(path,"POST",payload||{})}

function option(select,label,value){var o=document.createElement("option");o.textContent=label||"";o.value=value||"";select.appendChild(o)}

function appendText(parent,tag,value,cls){var node=document.createElement(tag);node.textContent=value==null?"":String(value);if(cls)node.className=cls;parent.appendChild(node);return node}

function fieldId(key){return "setting_"+key.replace(/[^A-Za-z0-9_]/g,"_")}

function activeTrainer(){var id=$("trainer").value;return list(state.trainingSchema.trainers).filter(function(t){return t.id===id})[0]||{settings:{}}}

function schemaFor(key){return activeTrainer().settings[key]||{}}

function closestButton(n){while(n&&n!==document.body){if(n.tagName&&n.tagName.toLowerCase()==="button")return n;n=n.parentNode}return null}

function switchView(id){var nodes=document.querySelectorAll(".view,.tabs button");for(var i=0;i<nodes.length;i++)nodes[i].classList.remove("active");if($(id))$(id).classList.add("active");var tab=document.querySelector('[data-view="'+id+'"]');if(tab)tab.classList.add("active")}

document.body.addEventListener("click",function(ev){var b=closestButton(ev.target);if(!b)return;var view=b.getAttribute("data-view");if(view)switchView(view);var action=b.getAttribute("data-action");if(action&&state.activeJobId){postJson("/api/job",{job_id:state.activeJobId,action:action}).then(load).catch(function(e){text("promptStatus",errorMessage(e))})}});

window.addEventListener("error",function(e){text("connection","Phone app error: "+(e.message||"unknown"))});

window.addEventListener("unhandledrejection",function(e){text("connection","Remote request error: "+errorMessage(e.reason))});



function img(src,alt,note){var im=document.createElement("img");im.alt=alt||"";if(src)im.src=authUrl(src);im.onerror=function(){im.style.display="none";if(note)note.textContent="Preview unavailable"};return im}

function renderStatus(p){p=p||{};text("connection",(p.app||"ADAM")+" online - "+(p.scope||"remote"));var perms=p.permissions||{};var job=p.active_job||null;state.activeJobId=job&&job.id?job.id:"";if(state.promptLocked&&state.promptJobId&&job&&job.id===state.promptJobId){state.promptLocked=false;state.promptJobId="";$("sendPrompt").disabled=false;text("promptStatus","Job started. You can send another prompt.")}if(job){text("activeJob",(job.project||"Active job")+" - "+(job.status||"")+" - "+String(job.progress||0)+"% - Time left "+((job.timing||{}).remaining_label||""));text("timeLeft",(job.timing||{}).remaining_label||"-");text("finishTime",(job.timing||{}).finish_label||"-");$("activeBar").style.width=String(job.progress||0)+"%"}else{text("activeJob","No active job.");text("timeLeft","-");text("finishTime","-");$("activeBar").style.width="0%"}var buttons=document.querySelectorAll("[data-action]");for(var i=0;i<buttons.length;i++)buttons[i].disabled=!job||!perms.job_control;$("autoApproveTraining").checked=!!perms.auto_approve_training;$("autoApproveTraining").disabled=!perms.job_control&&!perms.auto_approve_training;renderPreview(p.preview||{});renderLatest(p.latest_generation||{});renderSystem(p.system||{});renderQueues(p,perms)}
function renderPreview(info){var p=$("preview");if(info.available){p.style.display="block";p.src=authUrl("/api/preview","t="+Date.now());text("previewNote",(info.kind||"preview")+" "+(info.current||info.epoch||"")+"/"+(info.total||""))}else{p.style.display="none";p.removeAttribute("src");text("previewNote","Waiting for a preview.")}}

function renderLatest(latest){var root=$("latestGeneration");clear(root);var images=list(latest.images).slice(0,8);if(!latest.available||!images.length){var d=document.createElement("div");d.className="status";d.textContent="Finished generated images will appear here.";root.appendChild(d);return}images.forEach(function(item){var b=document.createElement("button");b.className="thumb";b.type="button";var n=document.createElement("div");n.textContent=latest.model_name||"Generated image";b.appendChild(img(item.url,"Generated image",n));b.appendChild(n);b.onclick=function(){window.open(authUrl(item.url,"t="+Date.now()),"_blank","noopener,noreferrer")};root.appendChild(b)})}

function renderSystem(sys){var root=$("system");clear(root);[["CPU",sys.cpu_percent],["RAM",sys.memory_percent],["GPU",sys.gpu_percent],["VRAM",sys.vram_percent]].forEach(function(m){var d=document.createElement("div");d.className="card";appendText(d,"div",m[0],"meta");appendText(d,"b",m[1]==null?"-":m[1]+"%");root.appendChild(d)})}

function renderQueues(p,perms){var root=$("queues");clear(root);var jobs=list(p.queue).concat(list(p.completed_jobs),list(p.failed_jobs)).slice(0,40);if(!jobs.length){text("queues","No jobs yet.");return}jobs.forEach(function(job){var d=document.createElement("div");d.className="queue-item";appendText(d,"b",job.project||"ADAM Job");appendText(d,"div",(job.status||"")+" - "+String(job.progress||0)+"%","meta");if(job.current_step_title){var s=document.createElement("div");s.className="meta";s.textContent=job.current_step_title;d.appendChild(s)}if(job.error){var e=document.createElement("div");e.className="bad-text";e.textContent=job.error;d.appendChild(e)}if(perms&&perms.job_control&&["Queued","Scheduled","Awaiting confirmation","Running","Paused","Interrupted"].indexOf(job.status)>=0){var cancel=document.createElement("button");cancel.className="bad";cancel.textContent="Cancel job";cancel.style.marginTop="8px";cancel.onclick=function(){cancel.disabled=true;postJson("/api/job",{job_id:job.id,action:"cancel"}).then(function(){load()}).catch(function(e){text("promptStatus",errorMessage(e));cancel.disabled=false})};d.appendChild(cancel)}root.appendChild(d)})}


function load(){return getJson("/api/status").then(renderStatus).catch(function(e){text("connection","Offline: "+errorMessage(e))})}

function loadData(){return Promise.all([

  getJson("/api/v1/datasets").then(function(p){state.datasets=list(p.datasets);renderDatasets();fillDatasets()}),

  getJson("/api/v1/datasets/locations").then(function(p){state.locations=list(p.locations);renderLocations()}),

  getJson("/api/v1/training/schema").then(function(p){state.trainingSchema=p||{trainers:[]};fillCreate()}),
  getJson("/api/v1/generation/schema").then(function(p){state.generationSchema=p||{providers:[],models:[],base_models:[]};fillGeneration()})
]).catch(function(e){text("trainingReview",errorMessage(e))})}
function datasetMatches(d){var q=($("datasetSearch").value||"").toLowerCase();if(q&&(d.name||"").toLowerCase().indexOf(q)<0)return false;if(state.locationFilter&&d.location_id!==state.locationFilter)return false;return true}

function datasetImage(d){return d.thumbnail_url?img(d.thumbnail_url,"Dataset thumbnail"):document.createElement("span")}

function useDataset(id){if(!id)return;postJson("/api/v1/datasets/"+encodeURIComponent(id)+"/use",{}).then(function(p){var d=p.dataset||{};state.selectedDataset=d.id||id;fillDatasets();if(!$("modelName").value)$("modelName").value=d.name||"";updateSelectedDatasetNote();switchView("createView");loadData()}).catch(function(e){text("datasetStats",errorMessage(e))})}

function datasetCard(d,compact){var row=document.createElement("div");row.className="dataset-card"+(compact?" compact":"")+(d.available?"":" unavailable");var image=datasetImage(d);var body=document.createElement("div");var name=document.createElement("b");name.textContent=d.name||"Dataset";body.appendChild(name);var meta=document.createElement("div");meta.className="meta";meta.textContent=(d.available?String(d.image_count||d.item_count||0)+" images":"Unavailable")+" - "+(d.dataset_format||"Dataset");body.appendChild(meta);var star=document.createElement("button");star.type="button";star.className="icon";star.textContent=d.favorite?"*":"+";star.title=d.favorite?"Remove favorite":"Favorite";star.onclick=function(ev){ev.stopPropagation();postJson("/api/v1/datasets/"+encodeURIComponent(d.id)+"/favorite",{favorite:!d.favorite}).then(loadData)};row.appendChild(image);row.appendChild(body);row.appendChild(star);if(compact){row.onclick=function(){useDataset(d.id)};return row}var actions=document.createElement("div");actions.className="dataset-actions";var preview=document.createElement("button");preview.type="button";preview.className="ghost";preview.textContent="Preview";preview.onclick=function(){openDataset(d.id,1)};var use=document.createElement("button");use.type="button";use.className="primary";use.textContent="Use Dataset";use.onclick=function(){useDataset(d.id)};actions.appendChild(preview);actions.appendChild(use);row.appendChild(actions);return row}

function fillList(id,items,empty,compact){var root=$(id);clear(root);items=items.filter(datasetMatches);if(!items.length){var e=document.createElement("div");e.className="status";e.textContent=empty;root.appendChild(e);return}items.forEach(function(d){root.appendChild(datasetCard(d,compact))})}

function renderDatasets(){var all=state.datasets;fillList("favoriteDatasets",all.filter(function(d){return d.favorite}),"No favorites yet.",false);fillList("recentDatasets",all.filter(function(d){return d.last_used_at}),"Recently used datasets will appear here.",true);fillList("allDatasets",all,"No remembered datasets found. Add locations from desktop Training Studio.",false)}

function renderLocations(){var chips=$("locationChips");clear(chips);var all=document.createElement("button");all.className="chip"+(state.locationFilter?"":" active");all.textContent="All locations";all.onclick=function(){state.locationFilter="";renderLocations();renderDatasets()};chips.appendChild(all);var listRoot=$("locationsList");var inlineRoot=$("locationsListInline");clear(listRoot);clear(inlineRoot);state.locations.forEach(function(loc){var c=document.createElement("button");c.className="chip"+(state.locationFilter===loc.id?" active":"");c.textContent=loc.name+(loc.available?"":" (unavailable)");c.onclick=function(){state.locationFilter=loc.id;renderLocations();renderDatasets()};chips.appendChild(c);var item=document.createElement("div");item.className="queue-item";appendText(item,"b",loc.name);appendText(item,"div",(loc.available?"available":"unavailable")+" - "+loc.source,"meta");listRoot.appendChild(item.cloneNode(true));inlineRoot.appendChild(item)})}

function fillDatasets(){var current=$("trainDataset")?$("trainDataset").value:"";if(!$("trainDataset"))return;clear($("trainDataset"));state.datasets.filter(function(d){return d.available}).forEach(function(d){option($("trainDataset"),d.name+" ("+String(d.image_count||d.item_count||0)+")",d.id)});if(current)$("trainDataset").value=current;if(state.selectedDataset)$("trainDataset").value=state.selectedDataset;updateSelectedDatasetNote()}

function updateSelectedDatasetNote(){var id=state.selectedDataset||($("trainDataset")&&$("trainDataset").value)||"";var d=state.datasets.filter(function(item){return item.id===id})[0];text("selectedDatasetNote",d?d.name+" - "+String(d.image_count||d.item_count||0)+" images":"No dataset selected")}

function openDataset(id,page){state.selectedDataset=id;state.datasetPage=page;text("datasetStats","Loading preview...");$("datasetDetail").style.display="block";getJson("/api/v1/datasets/"+encodeURIComponent(id)+"/items?page="+encodeURIComponent(page)+"&page_size=24").then(function(p){var d=p.dataset||{};text("datasetTitle",d.name||"Dataset");text("datasetStats",String(d.image_count||d.item_count||0)+" items - "+String(d.caption_count||0)+" captions - "+String(d.missing_caption_count||0)+" missing captions - "+(d.dataset_format||"Dataset"));text("pageLabel","Page "+String((p.pagination||{}).page||page));$("prevPage").disabled=page<=1;$("nextPage").disabled=!(p.pagination||{}).has_next;var grid=$("datasetGrid");clear(grid);list(p.items).forEach(function(item){var b=document.createElement("button");b.type="button";b.className="thumb";var note=document.createElement("div");note.textContent=(item.display_name||"Image")+"\\n"+(item.decision||"unreviewed");b.appendChild(img(item.thumbnail_url,"Dataset image",note));b.appendChild(note);b.onclick=function(){showImage(item)};grid.appendChild(b)})}).catch(function(e){text("datasetStats",errorMessage(e))})}

function showImage(item){state.selectedItem=item;$("imageDetail").style.display="block";$("detailImage").src=authUrl(item.preview_url);text("detailName",item.display_name||"Image");text("detailDims",String((item.dimensions||{}).width||0)+" x "+String((item.dimensions||{}).height||0));$("captionEditor").value=item.caption||"";text("imageStatus",item.decision||"unreviewed")}



function fillCreate(){clear($("trainer"));list(state.trainingSchema.trainers).forEach(function(t){option($("trainer"),t.name,t.id)});clear($("preset"));option($("preset"),"Custom","");list(state.trainingSchema.presets).forEach(function(p){option($("preset"),p.name,p.id)});clear($("baseModel"));list(state.trainingSchema.base_models).forEach(function(m){option($("baseModel"),m.name,m.id)});fillDatasets();renderSettings();ensureEpochField()}
function activeGenerationProvider(){return list(state.generationSchema.providers).filter(function(p){return p.id===$("generationProvider").value})[0]||{model_trainers:[],options:{}}}
function fillGeneration(){var current=$("generationProvider").value;clear($("generationProvider"));list(state.generationSchema.providers).forEach(function(p){option($("generationProvider"),p.name,p.id)});if(current)$("generationProvider").value=current;fillGenerationModels()}
function fillGenerationModels(){var provider=activeGenerationProvider(),current=$("generationModel").value,trainers=list(provider.model_trainers);clear($("generationModel"));list(state.generationSchema.models).filter(function(m){return !trainers.length||trainers.indexOf(m.trainer)>=0}).forEach(function(m){option($("generationModel"),m.name+(m.trigger_word?" · trigger: "+m.trigger_word:""),m.id)});if(current)$("generationModel").value=current;clear($("generationBase"));list(state.generationSchema.base_models).forEach(function(m){option($("generationBase"),m.name,m.id)});var isLora=provider.id==="lora_generator";$("generationBaseWrap").style.display=isLora?"grid":"none";$("generationStrength").parentNode.style.display=isLora?"grid":"none";var selected=list(state.generationSchema.models).filter(function(m){return m.id===$("generationModel").value})[0];text("generationModelNote",selected&&selected.trigger_word?"LoRA trigger word: "+selected.trigger_word:"Choose a completed model.");var opts=provider.options||{};if(opts.step_default)$("generationSteps").value=String(opts.step_default)}
function generationPayload(){var provider=activeGenerationProvider();return{provider_id:$("generationProvider").value,model_id:$("generationModel").value,base_model_id:$("generationBase").value,prompt:$("generationPrompt").value,negative_prompt:$("generationNegative").value,image_count:Number($("generationCount").value||1),steps:Number($("generationSteps").value||30),seed:Number($("generationSeed").value||0),sampler:(provider.options&&list(provider.options.samplers)[0])||"DDIM",aspect_ratio:(provider.options&&list(provider.options.aspect_ratios)[0])||"1:1 (Square)",lora_strength:Number($("generationStrength").value||1),prompt_weighting:true}}
function makeSetting(key,spec,advanced){if(key==="base_model")return null;var wrap=document.createElement("div");wrap.className=spec.type==="bool"?"toggle":"form-row";var input;if(spec.type==="bool"){var label=document.createElement("div");appendText(label,"b",spec.label||key);appendText(label,"div",spec.description||"Enable this setting","hint");input=document.createElement("input");input.type="checkbox";input.checked=spec.default!==false;wrap.appendChild(label);wrap.appendChild(input)}else{var label=document.createElement("label");label.textContent=spec.label||key;wrap.appendChild(label);if(spec.type==="choice"){input=document.createElement("select");list(spec.options).forEach(function(v){option(input,String(v),String(v))})}else{input=document.createElement("input");input.type=(spec.type==="int"||spec.type==="float"||spec.type==="slider")?"number":"text";if(spec.min!==undefined)input.min=spec.min;if(spec.max!==undefined)input.max=spec.max;if(spec.step!==undefined)input.step=spec.step}input.value=spec.default!==undefined?String(spec.default):"";wrap.appendChild(input)}input.id=fieldId(key);input.setAttribute("data-setting-key",key);input.setAttribute("data-setting-type",spec.type||"text");input.setAttribute("data-advanced",advanced?"1":"0");return wrap}

function renderSettings(){var t=activeTrainer();var schema=t.settings||{};var basic=$("basicSettings"),advanced=$("advancedSettings");clear(basic);clear(advanced);var basicKeys=["resolution","batch_size","preview_enabled"];Object.keys(schema).forEach(function(key){var spec=schema[key]||{};var isAdvanced=!!spec.advanced||basicKeys.indexOf(key)<0;var node=makeSetting(key,spec,isAdvanced);if(!node)return;(isAdvanced?advanced:basic).appendChild(node)});$("baseModelWrap").style.display=schema.base_model?"grid":"none"}

function settingValue(node){var type=node.getAttribute("data-setting-type");if(type==="bool")return !!node.checked;if(type==="int"||type==="slider")return Number(node.value||0);if(type==="float")return Number(node.value||0);var spec=schemaFor(node.getAttribute("data-setting-key"));if(spec.type==="choice"){var sample=list(spec.options)[0];if(typeof sample==="number")return Number(node.value)}return node.value}

function trainingPayload(){var settings={};var nodes=document.querySelectorAll("[data-setting-key]");for(var i=0;i<nodes.length;i++){var key=nodes[i].getAttribute("data-setting-key");if(key==="epochs")continue;settings[key]=settingValue(nodes[i])}return{trainer:$("trainer").value,dataset_id:state.selectedDataset||$("trainDataset").value,base_model_id:$("baseModel").value,model_name:$("modelName").value,trigger_word:settings.trigger_word||"",epochs:Number($(fieldId("epochs"))&&$(fieldId("epochs")).value||10),settings:settings}}

function applyPreset(){var id=$("preset").value;var preset=list(state.trainingSchema.presets).filter(function(p){return p.id===id})[0];if(!preset)return;if(preset.trainer){$("trainer").value=preset.trainer;renderSettings();ensureEpochField()}if(preset.epochs&&$(fieldId("epochs")))$(fieldId("epochs")).value=preset.epochs;Object.keys(preset.settings||{}).forEach(function(key){var n=$(fieldId(key));if(!n)return;if(n.type==="checkbox")n.checked=!!preset.settings[key];else n.value=String(preset.settings[key])})}

function ensureEpochField(){if(!$(fieldId("epochs"))){var node=makeSetting("epochs",{label:"Epochs",type:"int",default:10,min:1,max:100000},false);$("basicSettings").appendChild(node)}}

$("trainer").onchange=function(){renderSettings();ensureEpochField()};$("preset").onchange=applyPreset;$("datasetSearch").oninput=renderDatasets;$("filterDatasets").onclick=function(){var chips=$("locationChips");chips.style.display=chips.style.display==="none"?"flex":"none"};$("chooseDataset").onclick=function(){switchView("datasetsView")};$("createBack").onclick=function(){switchView("home")};$("createHelp").onclick=function(){text("trainingReview","Choose a preset, trainer, model name, and dataset. Advanced settings come from ADAM's trainer registry.")};$("askInstead").onclick=function(){switchView("home");$("prompt").focus()};$("useDataset").onclick=function(){useDataset(state.selectedDataset)};$("selectDatasetForCreate").onclick=$("useDataset").onclick;$("openGeneration").onclick=function(){switchView("generationView")};$("generationBack").onclick=function(){switchView("home")};$("generationProvider").onchange=fillGenerationModels;$("generationModel").onchange=fillGenerationModels;
$("reviewPlan").onclick=function(){postJson("/api/v1/training/plan",trainingPayload()).then(function(p){$("trainingReview").textContent=JSON.stringify(p.plan||p,null,2)}).catch(function(e){text("trainingReview",errorMessage(e))})};

$("startTraining").onclick=function(){if(!state.selectedDataset&&!$("trainDataset").value){text("trainingReview","Choose a dataset first.");return}if(!confirm("Start this training job on the PC?"))return;postJson("/api/v1/training/start",trainingPayload()).then(function(p){text("trainingReview",p.message||"Training queued.");load();loadData()}).catch(function(e){text("trainingReview",errorMessage(e))})};
$("startGeneration").onclick=function(){var button=this;button.disabled=true;postJson("/api/v1/generation/start",generationPayload()).then(function(p){text("generationStatus",p.message||"Generation queued.");load()}).catch(function(e){text("generationStatus",errorMessage(e))}).then(function(){button.disabled=false})};
$("prevPage").onclick=function(){openDataset(state.selectedDataset,Math.max(1,state.datasetPage-1))};$("nextPage").onclick=function(){openDataset(state.selectedDataset,state.datasetPage+1)};

$("saveCaption").onclick=function(){if(!state.selectedDataset||!state.selectedItem)return;postJson("/api/v1/datasets/"+encodeURIComponent(state.selectedDataset)+"/items/"+encodeURIComponent(state.selectedItem.id)+"/caption",{caption:$("captionEditor").value}).then(function(p){text("imageStatus",p.message||"Saved.")}).catch(function(e){text("imageStatus",errorMessage(e))})};

function decide(decision){if(!state.selectedDataset||!state.selectedItem)return;postJson("/api/v1/datasets/"+encodeURIComponent(state.selectedDataset)+"/items/"+encodeURIComponent(state.selectedItem.id)+"/decision",{decision:decision}).then(function(p){text("imageStatus",p.message||decision);openDataset(state.selectedDataset,state.datasetPage)}).catch(function(e){text("imageStatus",errorMessage(e))})}

$("keepImage").onclick=function(){decide("keep")};$("rejectImage").onclick=function(){decide("reject")};$("unreviewImage").onclick=function(){decide("unreviewed")};

$("sendPrompt").onclick=function(){var button=this;if(state.promptLocked)return;button.disabled=true;postJson("/api/prompt",{prompt:$("prompt").value}).then(function(p){state.promptJobId=p.job_id||"";state.promptLocked=!!state.promptJobId;text("promptStatus",state.promptLocked?"Queued. Sending is available again when this job starts.":(p.message||"Sent."));$("prompt").value="";if(!state.promptLocked)button.disabled=false;load()}).catch(function(e){text("promptStatus",errorMessage(e));button.disabled=false})};$("askCreate").onclick=function(){switchView("createView")};$("askDataset").onclick=function(){$("prompt").value="Collect a new dataset for Example."};$("askQuick").onclick=function(){$("prompt").value="Generate an image of Example."};
$("refresh").onclick=function(){load();loadData()};$("autoApproveTraining").onchange=function(e){postJson("/api/remote-settings",{auto_approve_training:e.target.checked}).then(function(p){text("settingsStatus",p.message||"Saved.");load()}).catch(function(err){text("settingsStatus",errorMessage(err))})};$("keepAwake").onchange=function(e){state.refreshMs=e.target.checked?1500:5000;startTimer();text("settingsStatus",e.target.checked?"Fast refresh is on.":"Quiet refresh is on.")};

function startTimer(){if(timer)clearInterval(timer);timer=setInterval(load,state.refreshMs)}

load();loadData();startTimer();

}());

</script>

</body>

</html>"""