anuragredbus commited on
Commit
09ea129
·
1 Parent(s): 2ae4336

reduced steps to fit out free tier

Browse files
inference.py CHANGED
@@ -35,8 +35,8 @@ from viraltest.server.viraltest_environment import (
35
 
36
  DOCKER_IMAGE = os.getenv("IMAGE_NAME") or os.getenv("LOCAL_IMAGE_NAME")
37
  API_KEY = os.getenv("HF_TOKEN") or os.getenv("OPENAI_API_KEY") or os.getenv("API_KEY")
38
- API_BASE_URL = os.getenv("API_BASE_URL") or "http://127.0.0.1:1337/v1"
39
- MODEL_NAME = os.getenv("MODEL_NAME") or "gemma-4-E4B-it-IQ4_XS"
40
  BENCHMARK = os.getenv("VIRALTEST_BENCHMARK", "viraltest")
41
 
42
  TASKS = ["weekly_engage", "weekly_strategic", "weekly_competitive"]
@@ -228,7 +228,7 @@ def get_model_daily_plan(
228
 
229
  user_prompt = format_observation(obs)
230
  messages = [{"role": "system", "content": SYSTEM_PROMPT}]
231
- messages.extend(history[-12:])
232
  messages.append({"role": "user", "content": user_prompt})
233
 
234
  try:
 
35
 
36
  DOCKER_IMAGE = os.getenv("IMAGE_NAME") or os.getenv("LOCAL_IMAGE_NAME")
37
  API_KEY = os.getenv("HF_TOKEN") or os.getenv("OPENAI_API_KEY") or os.getenv("API_KEY")
38
+ API_BASE_URL = os.getenv("API_BASE_URL") or "https://router.huggingface.co/v1"
39
+ MODEL_NAME = os.getenv("MODEL_NAME") or "Qwen/Qwen2.5-7B-Instruct"
40
  BENCHMARK = os.getenv("VIRALTEST_BENCHMARK", "viraltest")
41
 
42
  TASKS = ["weekly_engage", "weekly_strategic", "weekly_competitive"]
 
228
 
229
  user_prompt = format_observation(obs)
230
  messages = [{"role": "system", "content": SYSTEM_PROMPT}]
231
+ messages.extend(history[-7:])
232
  messages.append({"role": "user", "content": user_prompt})
233
 
234
  try:
server/dashboard.html CHANGED
@@ -66,7 +66,7 @@ body{background:#0b1326;color:#dae2fd;font-family:'Inter',sans-serif}
66
  <header class="flex justify-between items-center px-6 h-14 border-b border-white/5 bg-surface/60 backdrop-blur-xl sticky top-0 z-40">
67
  <div class="flex items-center gap-5">
68
  <span id="statusDot" class="flex items-center gap-2 text-xs font-label text-secondary"><span class="w-2 h-2 rounded-full bg-secondary"></span>Ready</span>
69
- <span class="text-xs font-label text-on-surface-dim">Step <span id="stepNum" class="text-on-surface font-bold">0</span> / 168</span>
70
  </div>
71
  <div class="flex items-center gap-3">
72
  <span id="rewardBadge" class="text-xs font-label text-on-surface-dim">Last reward: —</span>
@@ -136,13 +136,13 @@ body{background:#0b1326;color:#dae2fd;font-family:'Inter',sans-serif}
136
  <div class="flex gap-3 items-start">
137
  <span class="material-symbols-outlined text-secondary text-lg shrink-0">info</span>
138
  <p class="text-[11px] font-label text-on-surface-dim leading-relaxed flex-1 min-w-0">
139
- <span class="text-on-surface font-semibold">Simulation only</span> — not live social data. Each <span class="text-on-surface">step</span> is ~1 hour; <span class="text-on-surface">Post</span> drives engagement and tags; <span class="text-on-surface">Rest</span> restores energy while rivals keep posting.
140
  </p>
141
  </div>
142
  <div class="border-t border-white/5 pt-3 space-y-2">
143
  <div class="text-[10px] font-bold text-on-surface uppercase tracking-widest">Niche saturation</div>
144
  <p class="text-[10px] font-label text-on-surface-dim leading-relaxed">
145
- Shown after each step for your <span class="text-on-surface">last post topic</span>. The sim collects competitor posts from the last <span class="text-on-surface">12 simulated hours</span>, counts how many topics overlap yours (≥50% shared words), and divides by the number of those recent competitor posts. Result is capped at 1.0. High saturation usually means more crowd overlap; the environment can lower engagement when you post into a crowded topic.
146
  </p>
147
  </div>
148
  <div class="border-t border-white/5 pt-3 space-y-2">
@@ -161,7 +161,7 @@ body{background:#0b1326;color:#dae2fd;font-family:'Inter',sans-serif}
161
  <div class="flex justify-between items-center mb-2">
162
  <div>
163
  <h3 class="text-sm font-bold">Reward history</h3>
164
- <p class="text-[10px] text-on-surface-dim mt-0.5">Per-step RL reward after each action (axes: step index × reward)</p>
165
  </div>
166
  <span class="flex items-center gap-1.5 text-[10px] font-label text-on-surface-dim"><span class="w-2 h-2 rounded-full bg-secondary"></span>Reward</span>
167
  </div>
@@ -224,52 +224,52 @@ body{background:#0b1326;color:#dae2fd;font-family:'Inter',sans-serif}
224
  </div>
225
  </div>
226
 
227
- <!-- Step & hour analytics -->
228
  <div class="space-y-3">
229
  <div class="flex items-center gap-2 px-1">
230
  <span class="material-symbols-outlined text-secondary text-lg">show_chart</span>
231
- <h2 class="text-sm font-bold">Step &amp; hour analytics</h2>
232
- <span class="text-[9px] font-label text-on-surface-dim uppercase tracking-widest">X = simulation step (~1h); posts histogram = clock hour (0–23)</span>
233
  </div>
234
  <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-3 gap-3">
235
  <div class="glass-solid p-4 rounded-xl overflow-hidden">
236
- <div class="text-[10px] font-bold text-on-surface-dim uppercase tracking-widest mb-1">Energy / step</div>
237
  <svg id="tsEnergy" class="w-full h-24" viewBox="0 0 360 112" preserveAspectRatio="xMidYMid meet"></svg>
238
  </div>
239
  <div class="glass-solid p-4 rounded-xl overflow-hidden">
240
- <div class="text-[10px] font-bold text-on-surface-dim uppercase tracking-widest mb-1">Followers / step</div>
241
  <svg id="tsFollowers" class="w-full h-24" viewBox="0 0 360 112" preserveAspectRatio="xMidYMid meet"></svg>
242
  </div>
243
  <div class="glass-solid p-4 rounded-xl overflow-hidden">
244
- <div class="text-[10px] font-bold text-on-surface-dim uppercase tracking-widest mb-1">Follower Δ / step</div>
245
  <svg id="tsFollowDelta" class="w-full h-24" viewBox="0 0 360 112" preserveAspectRatio="xMidYMid meet"></svg>
246
  </div>
247
  <div class="glass-solid p-4 rounded-xl overflow-hidden">
248
- <div class="text-[10px] font-bold text-on-surface-dim uppercase tracking-widest mb-1">Engagement rate / step</div>
249
  <svg id="tsEngagement" class="w-full h-24" viewBox="0 0 360 112" preserveAspectRatio="xMidYMid meet"></svg>
250
  </div>
251
  <div class="glass-solid p-4 rounded-xl overflow-hidden">
252
- <div class="text-[10px] font-bold text-on-surface-dim uppercase tracking-widest mb-1">Reward / step</div>
253
  <svg id="tsReward" class="w-full h-24" viewBox="0 0 360 112" preserveAspectRatio="xMidYMid meet"></svg>
254
  </div>
255
  <div class="glass-solid p-4 rounded-xl overflow-hidden">
256
- <div class="text-[10px] font-bold text-on-surface-dim uppercase tracking-widest mb-1">Niche saturation / step</div>
257
  <svg id="tsSat" class="w-full h-24" viewBox="0 0 360 112" preserveAspectRatio="xMidYMid meet"></svg>
258
  </div>
259
  <div class="glass-solid p-4 rounded-xl overflow-hidden">
260
- <div class="text-[10px] font-bold text-on-surface-dim uppercase tracking-widest mb-1">Content queue / step</div>
261
  <svg id="tsQueue" class="w-full h-24" viewBox="0 0 360 112" preserveAspectRatio="xMidYMid meet"></svg>
262
  </div>
263
  <div class="glass-solid p-4 rounded-xl overflow-hidden">
264
- <div class="text-[10px] font-bold text-on-surface-dim uppercase tracking-widest mb-1">Competitor avg engagement / step</div>
265
  <svg id="tsComp" class="w-full h-24" viewBox="0 0 360 112" preserveAspectRatio="xMidYMid meet"></svg>
266
  </div>
267
  <div class="glass-solid p-4 rounded-xl overflow-hidden">
268
- <div class="text-[10px] font-bold text-on-surface-dim uppercase tracking-widest mb-1">Sleep debt / step</div>
269
  <svg id="tsSleep" class="w-full h-24" viewBox="0 0 360 112" preserveAspectRatio="xMidYMid meet"></svg>
270
  </div>
271
  <div class="glass-solid p-4 rounded-xl overflow-hidden">
272
- <div class="text-[10px] font-bold text-on-surface-dim uppercase tracking-widest mb-1">Hours since sleep / step</div>
273
  <svg id="tsAwake" class="w-full h-24" viewBox="0 0 360 112" preserveAspectRatio="xMidYMid meet"></svg>
274
  </div>
275
  <div class="glass-solid p-4 rounded-xl overflow-hidden">
@@ -316,17 +316,17 @@ body{background:#0b1326;color:#dae2fd;font-family:'Inter',sans-serif}
316
  <div class="glass-solid p-5 rounded-xl overflow-hidden">
317
  <h3 class="text-sm font-bold mb-4 flex items-center gap-2"><span class="material-symbols-outlined text-primary text-lg">gamepad</span>Send Action</h3>
318
  <div class="grid grid-cols-3 gap-3 mb-3">
319
- <button type="button" title="Advance one hour, recover energy, reduce burnout. Competitors still simulate." onclick="doAction('rest')" class="action-btn group p-4 rounded-xl bg-gradient-to-br from-tertiary/5 to-tertiary/10 border border-tertiary/15 hover:border-tertiary/40 hover:from-tertiary/10 hover:to-tertiary/20 text-center">
320
  <span class="material-symbols-outlined text-tertiary text-3xl group-hover:scale-110 transition-transform">hotel</span>
321
  <div class="text-sm font-bold text-tertiary mt-1">Rest</div>
322
  <div class="text-[9px] text-on-surface-dim mt-0.5">+0.12 energy recovery</div>
323
  </button>
324
- <button type="button" title="Add one item to the queue. Costs a little energy; use before Post for cheaper publishes." onclick="doAction('create_content')" class="action-btn group p-4 rounded-xl bg-gradient-to-br from-secondary/5 to-secondary/10 border border-secondary/15 hover:border-secondary/40 hover:from-secondary/10 hover:to-secondary/20 text-center">
325
  <span class="material-symbols-outlined text-secondary text-3xl group-hover:scale-110 transition-transform">edit_note</span>
326
  <div class="text-sm font-bold text-secondary mt-1">Create</div>
327
  <div class="text-[9px] text-on-surface-dim mt-0.5">-0.05 energy, +1 queue</div>
328
  </button>
329
- <button type="button" title="Publish to the feed: choose format, topic, and tags. Drives engagement and tag stats." onclick="showPostForm()" id="postBtn" class="action-btn group p-4 rounded-xl bg-gradient-to-br from-primary/5 to-primary/10 border border-primary/15 hover:border-primary/40 hover:from-primary/10 hover:to-primary/20 text-center">
330
  <span class="material-symbols-outlined text-primary text-3xl group-hover:scale-110 transition-transform">send</span>
331
  <div class="text-sm font-bold text-primary mt-1">Post</div>
332
  <div class="text-[9px] text-on-surface-dim mt-0.5">type + topic + tags</div>
@@ -358,7 +358,7 @@ body{background:#0b1326;color:#dae2fd;font-family:'Inter',sans-serif}
358
  <div class="flex flex-col items-end gap-0.5">
359
  <div class="flex items-center gap-2">
360
  <span id="scenarioCount" class="text-[9px] font-label text-primary font-bold">…</span>
361
- <span class="text-[9px] font-label text-on-surface-dim">168-step episode</span>
362
  </div>
363
  <span class="text-[8px] font-label text-on-surface-dim/70 max-w-[16rem] text-right leading-tight">All strategies below — scroll the grid or search. Count updates after load.</span>
364
  </div>
@@ -391,10 +391,10 @@ body{background:#0b1326;color:#dae2fd;font-family:'Inter',sans-serif}
391
  </div>
392
  </div>
393
 
394
- <!-- Step History -->
395
  <div class="glass-solid rounded-xl overflow-hidden">
396
  <div class="p-4 border-b border-white/5 flex justify-between items-center">
397
- <h3 class="text-sm font-bold flex items-center gap-2"><span class="material-symbols-outlined text-on-surface-dim text-lg">history</span>Step History</h3>
398
  </div>
399
  <div id="historyLog" class="p-4 space-y-1.5 max-h-72 overflow-y-auto text-[11px] font-mono leading-relaxed">
400
  <div class="text-on-surface-dim italic">Reset the environment to begin...</div>
@@ -469,7 +469,7 @@ body{background:#0b1326;color:#dae2fd;font-family:'Inter',sans-serif}
469
  <th class="text-left px-4 py-2.5">Scenario</th>
470
  <th class="text-left px-4 py-2.5">Task</th>
471
  <th class="text-right px-4 py-2.5">Score</th>
472
- <th class="text-right px-4 py-2.5">Steps</th>
473
  <th class="text-right px-4 py-2.5">Posts</th>
474
  <th class="text-right px-4 py-2.5">Followers</th>
475
  <th class="text-right px-4 py-2.5">Delta</th>
@@ -489,6 +489,7 @@ body{background:#0b1326;color:#dae2fd;font-family:'Inter',sans-serif}
489
 
490
  <script>
491
  const API=window.location.origin;
 
492
  const DAYS=["Mon","Tue","Wed","Thu","Fri","Sat","Sun"];
493
  function fmtAxisNum(v){
494
  const a=Math.abs(v);
@@ -570,7 +571,7 @@ function drawStepLineChart(svgId,key,color){
570
  const W=360,H=112,pL=48,pR=10,pT=10,pB=28;
571
  const plotW=W-pL-pR,plotH=H-pT-pB;
572
  if(!data.length){
573
- svg.innerHTML=`<text x="${W/2}" y="${H/2}" text-anchor="middle" fill="#958ea0" font-size="10" font-family="Space Grotesk,sans-serif">No steps yet</text>`;
574
  return;
575
  }
576
  const vals=data.map(d=>Number(d[key]??0));
@@ -604,7 +605,7 @@ function drawStepLineChart(svgId,key,color){
604
  h+=`<text x="${pL}" y="${H-8}" fill="#958ea0" font-size="8" font-family="Space Grotesk,sans-serif">0</text>`;
605
  h+=`<text x="${pL+plotW/2}" y="${H-8}" text-anchor="middle" fill="#958ea0" font-size="8" font-family="Space Grotesk,sans-serif">${Math.floor(lastI/2)}</text>`;
606
  h+=`<text x="${W-pR}" y="${H-8}" text-anchor="end" fill="#958ea0" font-size="8" font-family="Space Grotesk,sans-serif">${lastI}</text>`;
607
- h+=`<text x="${pL+plotW/2}" y="${H-1}" text-anchor="middle" fill="#958ea0" font-size="7" font-family="Space Grotesk,sans-serif" opacity="0.75">step</text>`;
608
  svg.innerHTML=h;
609
  }
610
 
@@ -615,7 +616,7 @@ function drawFollowerDeltaChart(svgId){
615
  const W=360,H=112,pL=48,pR=10,pT=10,pB=28;
616
  const plotW=W-pL-pR,plotH=H-pT-pB;
617
  if(data.length<2){
618
- svg.innerHTML=`<text x="${W/2}" y="${H/2}" text-anchor="middle" fill="#958ea0" font-size="10" font-family="Space Grotesk,sans-serif">Need 2+ steps</text>`;
619
  return;
620
  }
621
  const dlt=data.map((d,i)=>i===0?0:d.followers-data[i-1].followers);
@@ -640,7 +641,7 @@ function drawFollowerDeltaChart(svgId){
640
  h+=`<text x="${pL}" y="${H-8}" fill="#958ea0" font-size="8" font-family="Space Grotesk,sans-serif">0</text>`;
641
  h+=`<text x="${pL+plotW/2}" y="${H-8}" text-anchor="middle" fill="#958ea0" font-size="8" font-family="Space Grotesk,sans-serif">${Math.floor(lastI/2)}</text>`;
642
  h+=`<text x="${W-pR}" y="${H-8}" text-anchor="end" fill="#958ea0" font-size="8" font-family="Space Grotesk,sans-serif">${lastI}</text>`;
643
- h+=`<text x="${pL+plotW/2}" y="${H-1}" text-anchor="middle" fill="#958ea0" font-size="7" font-family="Space Grotesk,sans-serif" opacity="0.75">step · Δ followers</text>`;
644
  svg.innerHTML=h;
645
  }
646
 
@@ -678,7 +679,7 @@ function drawActionMix(svgId){
678
  const svg=document.getElementById(svgId);
679
  if(!svg)return;
680
  if(!timelineHistory.length){
681
- svg.innerHTML='<text x="160" y="28" text-anchor="middle" fill="#958ea0" font-size="10" font-family="Space Grotesk,sans-serif">No steps yet</text>';
682
  return;
683
  }
684
  let r=0,c=0,p=0;
@@ -721,7 +722,7 @@ async function doReset(){
721
  }
722
 
723
  async function doAction(type){
724
- setStatus("Stepping...");
725
  try{
726
  const r=await fetch(API+"/dashboard/step",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({action:{action_type:type}})});
727
  const d=await r.json();
@@ -736,7 +737,7 @@ async function doPost(){
736
  const tagsRaw=document.getElementById("tagsInput").value.trim();
737
  const tags=tagsRaw?tagsRaw.split(",").map(t=>t.trim()).filter(Boolean):[];
738
  if(!topic){alert("Enter a topic");return}
739
- setStatus("Stepping...");
740
  try{
741
  const body={action:{action_type:"post",content_type:ct,topic,tags:tags.length?tags:undefined}};
742
  const r=await fetch(API+"/dashboard/step",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(body)});
@@ -899,10 +900,10 @@ function drawEngagementChart(){
899
  h+=`<defs><linearGradient id="${gid}" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#7bd0ff" stop-opacity="0.28"/><stop offset="1" stop-color="#7bd0ff" stop-opacity="0"/></linearGradient></defs>`;
900
  h+=`<path d="${areaD}" fill="url(#${gid})"/><path d="${lineD}" fill="none" stroke="#7bd0ff" stroke-width="2.5"/>`;
901
  const lastI=data.length-1;
902
- h+=`<text x="${pL}" y="${H-18}" fill="#958ea0" font-size="9" font-family="Space Grotesk,sans-serif">step 0</text>`;
903
- h+=`<text x="${pL+plotW/2}" y="${H-18}" text-anchor="middle" fill="#958ea0" font-size="9" font-family="Space Grotesk,sans-serif">step ${Math.floor(lastI/2)}</text>`;
904
- h+=`<text x="${W-pR}" y="${H-18}" text-anchor="end" fill="#958ea0" font-size="9" font-family="Space Grotesk,sans-serif">step ${lastI}</text>`;
905
- h+=`<text x="${pL+plotW/2}" y="${H-4}" text-anchor="middle" fill="#958ea0" font-size="9" font-family="Space Grotesk,sans-serif" opacity="0.85">simulation step index</text>`;
906
  h+=`<text x="12" y="${pT+plotH/2}" transform="rotate(-90 12 ${pT+plotH/2})" text-anchor="middle" fill="#958ea0" font-size="9" font-family="Space Grotesk,sans-serif" opacity="0.85">reward</text>`;
907
  svg.innerHTML=h;
908
  }
@@ -914,7 +915,7 @@ function drawBurnoutMeter(energy){
914
  document.getElementById("burnoutArc").setAttribute("stroke-dasharray",fill.toFixed(1)+" "+circ.toFixed(1));
915
  document.getElementById("burnoutPct").textContent=burnout+"%";
916
  const rec=document.getElementById("burnoutRec");
917
- if(burnout>=70)rec.textContent="Recommendation: Limit posting for 45 mins to prevent creative fatigue.";
918
  else if(burnout>=40)rec.textContent="Recommendation: Alternate between creating and resting to maintain output quality.";
919
  else rec.textContent="Recommendation: Energy levels healthy. Good window for high-effort content.";
920
  }
@@ -987,7 +988,7 @@ function addRecentAction(d){
987
  const action=d.action_type||d.observation?.last_action||"step";
988
  const icon=icons[action]||"play_arrow";
989
  const c=colors[action]||"on-surface-dim";
990
- const entry=`<div class="flex items-start gap-2.5 fade-in"><span class="material-symbols-outlined text-${c} text-lg mt-0.5 shrink-0">${icon}</span><div class="flex-1 min-w-0"><div class="text-xs font-bold text-on-surface truncate">${action.replace("_"," ")}</div><div class="text-[9px] text-on-surface-dim">${step} steps ago · r=${reward.toFixed(2)}</div></div></div>`;
991
  if(el.querySelector(".italic"))el.innerHTML="";
992
  el.innerHTML=entry+el.innerHTML;
993
  if(el.children.length>8)el.removeChild(el.lastChild);
@@ -1000,7 +1001,7 @@ function addLog(action,reward,done,error){
1000
  const errStr=error?` <span class="text-error">err=${error}</span>`:"";
1001
  const color=reward>0.5?"text-secondary":reward>0.2?"text-primary":"text-on-surface-dim";
1002
  const doneStr=done?'<span class="text-tertiary font-bold"> DONE</span>':"";
1003
- log.innerHTML+=`<div class="fade-in py-0.5"><span class="text-on-surface-dim/50">[${step}]</span> <span class="text-on-surface">${action}</span> <span class="${color}">r=${(reward??0).toFixed(2)}</span>${doneStr}${errStr}</div>`;
1004
  log.scrollTop=log.scrollHeight;
1005
  document.getElementById("rewardBadge").textContent="Last reward: "+(reward??0).toFixed(2);
1006
  }
@@ -1056,69 +1057,64 @@ async function runSim(scenario){
1056
  document.getElementById("simBar").style.width=pct+"%";
1057
  document.getElementById("simPct").textContent=pct+"%";
1058
 
1059
- // Live stat card updates every 5 steps
1060
- if(i%5===0||i===total-1){
1061
- document.getElementById("energyVal").textContent=s.energy.toFixed(2);
1062
- document.getElementById("energyBar").style.width=(s.energy*100)+"%";
1063
- document.getElementById("followersVal").textContent=s.followers.toLocaleString();
1064
- document.getElementById("engVal").textContent=s.engagement_rate.toFixed(3);
1065
- document.getElementById("stepNum").textContent=s.step;
1066
- document.getElementById("timeVal").textContent=s.hour+":00";
1067
- document.getElementById("dayVal").textContent=DAYS[s.day];
1068
- document.getElementById("postsVal").textContent=s.posts_today;
1069
- document.getElementById("queueVal").textContent=s.queue;
1070
- document.getElementById("satVal").textContent=s.niche_saturation.toFixed(2);
1071
- document.getElementById("compEng").textContent=s.competitor_avg_engagement.toFixed(3);
1072
- const diff=s.engagement_rate-s.competitor_avg_engagement;
1073
- const evc=document.getElementById("engVsComp");
1074
- evc.textContent="vs competitors: "+(diff>=0?"+":"")+diff.toFixed(3);
1075
- evc.className="mt-1.5 text-[9px] font-label "+(diff>0?"text-secondary":"text-tertiary");
1076
- const fdelta=s.followers-10000;
1077
- const fdEl=document.getElementById("followersDelta");
1078
- fdEl.textContent=(fdelta>=0?"+":"")+fdelta+" since start";
1079
- fdEl.className="mt-1.5 text-[9px] font-label "+(fdelta>0?"text-secondary":fdelta<0?"text-tertiary":"text-on-surface-dim");
1080
-
1081
- drawEngagementChart();
1082
- drawBurnoutMeter(s.energy);
1083
- drawFollowerBars();
1084
- updateBottomStats();
1085
- redrawTimelineCharts();
1086
-
1087
- // Update trending
1088
- const tt=document.getElementById("trendTopics");
1089
- tt.innerHTML=(s.trending_topics||[]).map(t=>`<span class="px-2 py-1 rounded-lg bg-secondary/10 border border-secondary/15 text-secondary text-[10px] font-label">${t}</span>`).join("");
1090
- const tg=document.getElementById("trendTags");
1091
- tg.innerHTML=(s.trending_tags||[]).map(t=>`<span class="px-2 py-1 rounded-lg bg-primary/10 border border-primary/15 text-primary text-[10px] font-label">#${t}</span>`).join("");
1092
-
1093
- // Update tag perf
1094
- const perf=s.tag_performance||{};
1095
- const entries=Object.entries(perf).sort((a,b)=>b[1]-a[1]);
1096
- const tp=document.getElementById("tagPerf");
1097
- if(entries.length){
1098
- const maxV=Math.max(...entries.map(e=>e[1]),0.01);
1099
- tp.innerHTML=entries.slice(0,6).map(([tag,val],j)=>{
1100
- const c=j%2===0?"primary":"secondary";
1101
- const w=Math.min(100,(val/maxV)*100);
1102
- return `<div><div class="flex justify-between font-label text-[10px]"><span class="text-on-surface">#${tag}</span><span class="text-${c}">${val.toFixed(3)}</span></div><div class="h-1.5 bg-surface-top rounded-full mt-1 overflow-hidden"><div class="h-full bg-gradient-to-r from-${c} to-${c}-ctr rounded-full" style="width:${w}%"></div></div></div>`;
1103
- }).join("");
1104
- }
1105
- const ttl=document.getElementById("topTagsList");
1106
- const colors=["secondary","primary","tertiary","on-surface-dim"];
1107
- if(entries.length){
1108
- ttl.innerHTML=entries.slice(0,4).map(([tag,val],j)=>{
1109
- const c=colors[j%colors.length];
1110
- const fmtVal=val>=1000?(val/1000).toFixed(1)+"k":val.toFixed(1);
1111
- return `<div class="flex items-center justify-between"><div class="flex items-center gap-2.5"><span class="w-2 h-2 rounded-full bg-${c}"></span><span class="text-sm font-label text-on-surface">#${tag}</span></div><span class="text-sm font-bold font-label text-${c}">${fmtVal}</span></div>`;
1112
- }).join("");
1113
- }
1114
-
1115
- await new Promise(r=>setTimeout(r,12));
1116
  }
 
 
 
 
 
 
 
 
 
 
 
1117
 
1118
  const color=s.reward>0.5?"text-secondary":s.reward>0.2?"text-primary":"text-on-surface-dim";
1119
  const err=s.error?` <span class="text-error">err=${s.error}</span>`:"";
1120
  const dn=s.done?'<span class="text-tertiary font-bold"> DONE</span>':"";
1121
- log.innerHTML+=`<div class="fade-in py-0.5"><span class="text-on-surface-dim/50">[${s.step}]</span> <span class="text-on-surface">${s.action}</span> <span class="${color}">r=${s.reward.toFixed(2)}</span>${dn}${err}</div>`;
1122
  log.scrollTop=log.scrollHeight;
1123
  }
1124
 
@@ -1176,7 +1172,7 @@ async function runSim(scenario){
1176
  <div class="p-4 rounded-xl border ${scoreBg} space-y-2">
1177
  <div class="flex justify-between items-center"><span class="text-[10px] font-label text-on-surface-dim uppercase tracking-widest">Grader Score</span><span class="text-3xl font-black ${scoreColor}">${sc.toFixed(4)}</span></div>
1178
  <div class="grid grid-cols-2 gap-x-6 gap-y-1 text-[10px] font-label">
1179
- <div class="flex justify-between"><span class="text-on-surface-dim">Steps</span><span>${d.total_steps}</span></div>
1180
  <div class="flex justify-between"><span class="text-on-surface-dim">Burned Out</span><span class="${f.burned_out?"text-tertiary":"text-secondary"}">${f.burned_out?"YES":"NO"}</span></div>
1181
  <div class="flex justify-between"><span class="text-on-surface-dim">Final Energy</span><span>${f.energy.toFixed(2)}</span></div>
1182
  <div class="flex justify-between"><span class="text-on-surface-dim">Followers</span><span>${f.followers.toLocaleString()}</span></div>
@@ -1216,7 +1212,7 @@ async function loadHistory(){
1216
  const deltaStr=(delta>=0?"+":"")+delta.toLocaleString();
1217
  const deltaClass=delta>0?"text-secondary":delta<0?"text-tertiary":"text-on-surface-dim";
1218
  const scoreColor=h.score>=0.7?"text-primary":h.score>=0.3?"text-secondary":"text-tertiary";
1219
- const status=f.burned_out?'<span class="text-tertiary font-bold">BURNED</span>':h.total_steps>=168?'<span class="text-secondary">DONE</span>':'<span class="text-on-surface-dim">PARTIAL</span>';
1220
  const energyColor=f.energy>=0.5?"text-secondary":f.energy>0?"text-tertiary":"text-error";
1221
  const desc=(h.description||"").trim();
1222
  return `<tr class="border-b border-white/5 hover:bg-white/[.02] transition">
@@ -1297,6 +1293,7 @@ async function loadScenarioButtons(){
1297
  }
1298
  }
1299
 
 
1300
  loadScenarioButtons();
1301
  loadHistory();
1302
  doReset();
 
66
  <header class="flex justify-between items-center px-6 h-14 border-b border-white/5 bg-surface/60 backdrop-blur-xl sticky top-0 z-40">
67
  <div class="flex items-center gap-5">
68
  <span id="statusDot" class="flex items-center gap-2 text-xs font-label text-secondary"><span class="w-2 h-2 rounded-full bg-secondary"></span>Ready</span>
69
+ <span class="text-xs font-label text-on-surface-dim">Day <span id="stepNum" class="text-on-surface font-bold">0</span> / <span id="episodeHorizon">7</span></span>
70
  </div>
71
  <div class="flex items-center gap-3">
72
  <span id="rewardBadge" class="text-xs font-label text-on-surface-dim">Last reward: —</span>
 
136
  <div class="flex gap-3 items-start">
137
  <span class="material-symbols-outlined text-secondary text-lg shrink-0">info</span>
138
  <p class="text-[11px] font-label text-on-surface-dim leading-relaxed flex-1 min-w-0">
139
+ <span class="text-on-surface font-semibold">Simulation only</span> — not live social data. Each <span class="text-on-surface">step</span> is one full simulated day (24 hours of hourly actions inside the env). You submit a daily plan; <span class="text-on-surface">Post</span> and <span class="text-on-surface">Create</span> are scheduled at hours you choose; unlisted hours are rest while rivals keep posting.
140
  </p>
141
  </div>
142
  <div class="border-t border-white/5 pt-3 space-y-2">
143
  <div class="text-[10px] font-bold text-on-surface uppercase tracking-widest">Niche saturation</div>
144
  <p class="text-[10px] font-label text-on-surface-dim leading-relaxed">
145
+ Shown after each day for your <span class="text-on-surface">last post topic</span>. The sim collects competitor posts from the last <span class="text-on-surface">12 simulated hours</span>, counts how many topics overlap yours (≥50% shared words), and divides by the number of those recent competitor posts. Result is capped at 1.0. High saturation usually means more crowd overlap; the environment can lower engagement when you post into a crowded topic.
146
  </p>
147
  </div>
148
  <div class="border-t border-white/5 pt-3 space-y-2">
 
161
  <div class="flex justify-between items-center mb-2">
162
  <div>
163
  <h3 class="text-sm font-bold">Reward history</h3>
164
+ <p class="text-[10px] text-on-surface-dim mt-0.5">Per-day RL reward after each day (axes: day index × reward)</p>
165
  </div>
166
  <span class="flex items-center gap-1.5 text-[10px] font-label text-on-surface-dim"><span class="w-2 h-2 rounded-full bg-secondary"></span>Reward</span>
167
  </div>
 
224
  </div>
225
  </div>
226
 
227
+ <!-- Day & hour analytics -->
228
  <div class="space-y-3">
229
  <div class="flex items-center gap-2 px-1">
230
  <span class="material-symbols-outlined text-secondary text-lg">show_chart</span>
231
+ <h2 class="text-sm font-bold">Day &amp; hour analytics</h2>
232
+ <span class="text-[9px] font-label text-on-surface-dim uppercase tracking-widest">X = day index (1–7); line charts = metrics per day; posts histogram = clock hour (0–23) within days</span>
233
  </div>
234
  <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-3 gap-3">
235
  <div class="glass-solid p-4 rounded-xl overflow-hidden">
236
+ <div class="text-[10px] font-bold text-on-surface-dim uppercase tracking-widest mb-1">Energy / day</div>
237
  <svg id="tsEnergy" class="w-full h-24" viewBox="0 0 360 112" preserveAspectRatio="xMidYMid meet"></svg>
238
  </div>
239
  <div class="glass-solid p-4 rounded-xl overflow-hidden">
240
+ <div class="text-[10px] font-bold text-on-surface-dim uppercase tracking-widest mb-1">Followers / day</div>
241
  <svg id="tsFollowers" class="w-full h-24" viewBox="0 0 360 112" preserveAspectRatio="xMidYMid meet"></svg>
242
  </div>
243
  <div class="glass-solid p-4 rounded-xl overflow-hidden">
244
+ <div class="text-[10px] font-bold text-on-surface-dim uppercase tracking-widest mb-1">Follower Δ / day</div>
245
  <svg id="tsFollowDelta" class="w-full h-24" viewBox="0 0 360 112" preserveAspectRatio="xMidYMid meet"></svg>
246
  </div>
247
  <div class="glass-solid p-4 rounded-xl overflow-hidden">
248
+ <div class="text-[10px] font-bold text-on-surface-dim uppercase tracking-widest mb-1">Engagement rate / day</div>
249
  <svg id="tsEngagement" class="w-full h-24" viewBox="0 0 360 112" preserveAspectRatio="xMidYMid meet"></svg>
250
  </div>
251
  <div class="glass-solid p-4 rounded-xl overflow-hidden">
252
+ <div class="text-[10px] font-bold text-on-surface-dim uppercase tracking-widest mb-1">Reward / day</div>
253
  <svg id="tsReward" class="w-full h-24" viewBox="0 0 360 112" preserveAspectRatio="xMidYMid meet"></svg>
254
  </div>
255
  <div class="glass-solid p-4 rounded-xl overflow-hidden">
256
+ <div class="text-[10px] font-bold text-on-surface-dim uppercase tracking-widest mb-1">Niche saturation / day</div>
257
  <svg id="tsSat" class="w-full h-24" viewBox="0 0 360 112" preserveAspectRatio="xMidYMid meet"></svg>
258
  </div>
259
  <div class="glass-solid p-4 rounded-xl overflow-hidden">
260
+ <div class="text-[10px] font-bold text-on-surface-dim uppercase tracking-widest mb-1">Content queue / day</div>
261
  <svg id="tsQueue" class="w-full h-24" viewBox="0 0 360 112" preserveAspectRatio="xMidYMid meet"></svg>
262
  </div>
263
  <div class="glass-solid p-4 rounded-xl overflow-hidden">
264
+ <div class="text-[10px] font-bold text-on-surface-dim uppercase tracking-widest mb-1">Competitor avg engagement / day</div>
265
  <svg id="tsComp" class="w-full h-24" viewBox="0 0 360 112" preserveAspectRatio="xMidYMid meet"></svg>
266
  </div>
267
  <div class="glass-solid p-4 rounded-xl overflow-hidden">
268
+ <div class="text-[10px] font-bold text-on-surface-dim uppercase tracking-widest mb-1">Sleep debt / day</div>
269
  <svg id="tsSleep" class="w-full h-24" viewBox="0 0 360 112" preserveAspectRatio="xMidYMid meet"></svg>
270
  </div>
271
  <div class="glass-solid p-4 rounded-xl overflow-hidden">
272
+ <div class="text-[10px] font-bold text-on-surface-dim uppercase tracking-widest mb-1">Hours since sleep / day</div>
273
  <svg id="tsAwake" class="w-full h-24" viewBox="0 0 360 112" preserveAspectRatio="xMidYMid meet"></svg>
274
  </div>
275
  <div class="glass-solid p-4 rounded-xl overflow-hidden">
 
316
  <div class="glass-solid p-5 rounded-xl overflow-hidden">
317
  <h3 class="text-sm font-bold mb-4 flex items-center gap-2"><span class="material-symbols-outlined text-primary text-lg">gamepad</span>Send Action</h3>
318
  <div class="grid grid-cols-3 gap-3 mb-3">
319
+ <button type="button" title="Submit a full rest day (empty schedule). Advances one simulated day; competitors still simulate." onclick="doAction('rest')" class="action-btn group p-4 rounded-xl bg-gradient-to-br from-tertiary/5 to-tertiary/10 border border-tertiary/15 hover:border-tertiary/40 hover:from-tertiary/10 hover:to-tertiary/20 text-center">
320
  <span class="material-symbols-outlined text-tertiary text-3xl group-hover:scale-110 transition-transform">hotel</span>
321
  <div class="text-sm font-bold text-tertiary mt-1">Rest</div>
322
  <div class="text-[9px] text-on-surface-dim mt-0.5">+0.12 energy recovery</div>
323
  </button>
324
+ <button type="button" title="Schedule create_content at a default hour for this day (daily plan). Queue lowers post energy cost." onclick="doAction('create_content')" class="action-btn group p-4 rounded-xl bg-gradient-to-br from-secondary/5 to-secondary/10 border border-secondary/15 hover:border-secondary/40 hover:from-secondary/10 hover:to-secondary/20 text-center">
325
  <span class="material-symbols-outlined text-secondary text-3xl group-hover:scale-110 transition-transform">edit_note</span>
326
  <div class="text-sm font-bold text-secondary mt-1">Create</div>
327
  <div class="text-[9px] text-on-surface-dim mt-0.5">-0.05 energy, +1 queue</div>
328
  </button>
329
+ <button type="button" title="Schedule a post at a default hour for this day (daily plan). Drives engagement and tag stats." onclick="showPostForm()" id="postBtn" class="action-btn group p-4 rounded-xl bg-gradient-to-br from-primary/5 to-primary/10 border border-primary/15 hover:border-primary/40 hover:from-primary/10 hover:to-primary/20 text-center">
330
  <span class="material-symbols-outlined text-primary text-3xl group-hover:scale-110 transition-transform">send</span>
331
  <div class="text-sm font-bold text-primary mt-1">Post</div>
332
  <div class="text-[9px] text-on-surface-dim mt-0.5">type + topic + tags</div>
 
358
  <div class="flex flex-col items-end gap-0.5">
359
  <div class="flex items-center gap-2">
360
  <span id="scenarioCount" class="text-[9px] font-label text-primary font-bold">…</span>
361
+ <span class="text-[9px] font-label text-on-surface-dim">7-day episode</span>
362
  </div>
363
  <span class="text-[8px] font-label text-on-surface-dim/70 max-w-[16rem] text-right leading-tight">All strategies below — scroll the grid or search. Count updates after load.</span>
364
  </div>
 
391
  </div>
392
  </div>
393
 
394
+ <!-- Day History -->
395
  <div class="glass-solid rounded-xl overflow-hidden">
396
  <div class="p-4 border-b border-white/5 flex justify-between items-center">
397
+ <h3 class="text-sm font-bold flex items-center gap-2"><span class="material-symbols-outlined text-on-surface-dim text-lg">history</span>Day History</h3>
398
  </div>
399
  <div id="historyLog" class="p-4 space-y-1.5 max-h-72 overflow-y-auto text-[11px] font-mono leading-relaxed">
400
  <div class="text-on-surface-dim italic">Reset the environment to begin...</div>
 
469
  <th class="text-left px-4 py-2.5">Scenario</th>
470
  <th class="text-left px-4 py-2.5">Task</th>
471
  <th class="text-right px-4 py-2.5">Score</th>
472
+ <th class="text-right px-4 py-2.5">Days</th>
473
  <th class="text-right px-4 py-2.5">Posts</th>
474
  <th class="text-right px-4 py-2.5">Followers</th>
475
  <th class="text-right px-4 py-2.5">Delta</th>
 
489
 
490
  <script>
491
  const API=window.location.origin;
492
+ const EPISODE_DAYS=7;
493
  const DAYS=["Mon","Tue","Wed","Thu","Fri","Sat","Sun"];
494
  function fmtAxisNum(v){
495
  const a=Math.abs(v);
 
571
  const W=360,H=112,pL=48,pR=10,pT=10,pB=28;
572
  const plotW=W-pL-pR,plotH=H-pT-pB;
573
  if(!data.length){
574
+ svg.innerHTML=`<text x="${W/2}" y="${H/2}" text-anchor="middle" fill="#958ea0" font-size="10" font-family="Space Grotesk,sans-serif">No days yet</text>`;
575
  return;
576
  }
577
  const vals=data.map(d=>Number(d[key]??0));
 
605
  h+=`<text x="${pL}" y="${H-8}" fill="#958ea0" font-size="8" font-family="Space Grotesk,sans-serif">0</text>`;
606
  h+=`<text x="${pL+plotW/2}" y="${H-8}" text-anchor="middle" fill="#958ea0" font-size="8" font-family="Space Grotesk,sans-serif">${Math.floor(lastI/2)}</text>`;
607
  h+=`<text x="${W-pR}" y="${H-8}" text-anchor="end" fill="#958ea0" font-size="8" font-family="Space Grotesk,sans-serif">${lastI}</text>`;
608
+ h+=`<text x="${pL+plotW/2}" y="${H-1}" text-anchor="middle" fill="#958ea0" font-size="7" font-family="Space Grotesk,sans-serif" opacity="0.75">day</text>`;
609
  svg.innerHTML=h;
610
  }
611
 
 
616
  const W=360,H=112,pL=48,pR=10,pT=10,pB=28;
617
  const plotW=W-pL-pR,plotH=H-pT-pB;
618
  if(data.length<2){
619
+ svg.innerHTML=`<text x="${W/2}" y="${H/2}" text-anchor="middle" fill="#958ea0" font-size="10" font-family="Space Grotesk,sans-serif">Need 2+ days</text>`;
620
  return;
621
  }
622
  const dlt=data.map((d,i)=>i===0?0:d.followers-data[i-1].followers);
 
641
  h+=`<text x="${pL}" y="${H-8}" fill="#958ea0" font-size="8" font-family="Space Grotesk,sans-serif">0</text>`;
642
  h+=`<text x="${pL+plotW/2}" y="${H-8}" text-anchor="middle" fill="#958ea0" font-size="8" font-family="Space Grotesk,sans-serif">${Math.floor(lastI/2)}</text>`;
643
  h+=`<text x="${W-pR}" y="${H-8}" text-anchor="end" fill="#958ea0" font-size="8" font-family="Space Grotesk,sans-serif">${lastI}</text>`;
644
+ h+=`<text x="${pL+plotW/2}" y="${H-1}" text-anchor="middle" fill="#958ea0" font-size="7" font-family="Space Grotesk,sans-serif" opacity="0.75">day · Δ followers</text>`;
645
  svg.innerHTML=h;
646
  }
647
 
 
679
  const svg=document.getElementById(svgId);
680
  if(!svg)return;
681
  if(!timelineHistory.length){
682
+ svg.innerHTML='<text x="160" y="28" text-anchor="middle" fill="#958ea0" font-size="10" font-family="Space Grotesk,sans-serif">No days yet</text>';
683
  return;
684
  }
685
  let r=0,c=0,p=0;
 
722
  }
723
 
724
  async function doAction(type){
725
+ setStatus("Running day…");
726
  try{
727
  const r=await fetch(API+"/dashboard/step",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({action:{action_type:type}})});
728
  const d=await r.json();
 
737
  const tagsRaw=document.getElementById("tagsInput").value.trim();
738
  const tags=tagsRaw?tagsRaw.split(",").map(t=>t.trim()).filter(Boolean):[];
739
  if(!topic){alert("Enter a topic");return}
740
+ setStatus("Running day…");
741
  try{
742
  const body={action:{action_type:"post",content_type:ct,topic,tags:tags.length?tags:undefined}};
743
  const r=await fetch(API+"/dashboard/step",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(body)});
 
900
  h+=`<defs><linearGradient id="${gid}" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#7bd0ff" stop-opacity="0.28"/><stop offset="1" stop-color="#7bd0ff" stop-opacity="0"/></linearGradient></defs>`;
901
  h+=`<path d="${areaD}" fill="url(#${gid})"/><path d="${lineD}" fill="none" stroke="#7bd0ff" stroke-width="2.5"/>`;
902
  const lastI=data.length-1;
903
+ h+=`<text x="${pL}" y="${H-18}" fill="#958ea0" font-size="9" font-family="Space Grotesk,sans-serif">day 0</text>`;
904
+ h+=`<text x="${pL+plotW/2}" y="${H-18}" text-anchor="middle" fill="#958ea0" font-size="9" font-family="Space Grotesk,sans-serif">day ${Math.floor(lastI/2)}</text>`;
905
+ h+=`<text x="${W-pR}" y="${H-18}" text-anchor="end" fill="#958ea0" font-size="9" font-family="Space Grotesk,sans-serif">day ${lastI}</text>`;
906
+ h+=`<text x="${pL+plotW/2}" y="${H-4}" text-anchor="middle" fill="#958ea0" font-size="9" font-family="Space Grotesk,sans-serif" opacity="0.85">day index (1–${EPISODE_DAYS})</text>`;
907
  h+=`<text x="12" y="${pT+plotH/2}" transform="rotate(-90 12 ${pT+plotH/2})" text-anchor="middle" fill="#958ea0" font-size="9" font-family="Space Grotesk,sans-serif" opacity="0.85">reward</text>`;
908
  svg.innerHTML=h;
909
  }
 
915
  document.getElementById("burnoutArc").setAttribute("stroke-dasharray",fill.toFixed(1)+" "+circ.toFixed(1));
916
  document.getElementById("burnoutPct").textContent=burnout+"%";
917
  const rec=document.getElementById("burnoutRec");
918
+ if(burnout>=70)rec.textContent="Recommendation: Ease off scheduled posts for the next day to prevent creative fatigue.";
919
  else if(burnout>=40)rec.textContent="Recommendation: Alternate between creating and resting to maintain output quality.";
920
  else rec.textContent="Recommendation: Energy levels healthy. Good window for high-effort content.";
921
  }
 
988
  const action=d.action_type||d.observation?.last_action||"step";
989
  const icon=icons[action]||"play_arrow";
990
  const c=colors[action]||"on-surface-dim";
991
+ const entry=`<div class="flex items-start gap-2.5 fade-in"><span class="material-symbols-outlined text-${c} text-lg mt-0.5 shrink-0">${icon}</span><div class="flex-1 min-w-0"><div class="text-xs font-bold text-on-surface truncate">${action.replace("_"," ")}</div><div class="text-[9px] text-on-surface-dim">day ${step} · r=${reward.toFixed(2)}</div></div></div>`;
992
  if(el.querySelector(".italic"))el.innerHTML="";
993
  el.innerHTML=entry+el.innerHTML;
994
  if(el.children.length>8)el.removeChild(el.lastChild);
 
1001
  const errStr=error?` <span class="text-error">err=${error}</span>`:"";
1002
  const color=reward>0.5?"text-secondary":reward>0.2?"text-primary":"text-on-surface-dim";
1003
  const doneStr=done?'<span class="text-tertiary font-bold"> DONE</span>':"";
1004
+ log.innerHTML+=`<div class="fade-in py-0.5"><span class="text-on-surface-dim/50">[day ${step}]</span> <span class="text-on-surface">${action}</span> <span class="${color}">r=${(reward??0).toFixed(2)}</span>${doneStr}${errStr}</div>`;
1005
  log.scrollTop=log.scrollHeight;
1006
  document.getElementById("rewardBadge").textContent="Last reward: "+(reward??0).toFixed(2);
1007
  }
 
1057
  document.getElementById("simBar").style.width=pct+"%";
1058
  document.getElementById("simPct").textContent=pct+"%";
1059
 
1060
+ document.getElementById("energyVal").textContent=s.energy.toFixed(2);
1061
+ document.getElementById("energyBar").style.width=(s.energy*100)+"%";
1062
+ document.getElementById("followersVal").textContent=s.followers.toLocaleString();
1063
+ document.getElementById("engVal").textContent=s.engagement_rate.toFixed(3);
1064
+ document.getElementById("stepNum").textContent=s.step;
1065
+ document.getElementById("timeVal").textContent=s.hour+":00";
1066
+ document.getElementById("dayVal").textContent=DAYS[s.day];
1067
+ document.getElementById("postsVal").textContent=s.posts_today;
1068
+ document.getElementById("queueVal").textContent=s.queue;
1069
+ document.getElementById("satVal").textContent=s.niche_saturation.toFixed(2);
1070
+ document.getElementById("compEng").textContent=s.competitor_avg_engagement.toFixed(3);
1071
+ const diff=s.engagement_rate-s.competitor_avg_engagement;
1072
+ const evc=document.getElementById("engVsComp");
1073
+ evc.textContent="vs competitors: "+(diff>=0?"+":"")+diff.toFixed(3);
1074
+ evc.className="mt-1.5 text-[9px] font-label "+(diff>0?"text-secondary":"text-tertiary");
1075
+ const fdelta=s.followers-10000;
1076
+ const fdEl=document.getElementById("followersDelta");
1077
+ fdEl.textContent=(fdelta>=0?"+":"")+fdelta+" since start";
1078
+ fdEl.className="mt-1.5 text-[9px] font-label "+(fdelta>0?"text-secondary":fdelta<0?"text-tertiary":"text-on-surface-dim");
1079
+
1080
+ drawEngagementChart();
1081
+ drawBurnoutMeter(s.energy);
1082
+ drawFollowerBars();
1083
+ updateBottomStats();
1084
+ redrawTimelineCharts();
1085
+
1086
+ const tt=document.getElementById("trendTopics");
1087
+ tt.innerHTML=(s.trending_topics||[]).map(t=>`<span class="px-2 py-1 rounded-lg bg-secondary/10 border border-secondary/15 text-secondary text-[10px] font-label">${t}</span>`).join("");
1088
+ const tg=document.getElementById("trendTags");
1089
+ tg.innerHTML=(s.trending_tags||[]).map(t=>`<span class="px-2 py-1 rounded-lg bg-primary/10 border border-primary/15 text-primary text-[10px] font-label">#${t}</span>`).join("");
1090
+
1091
+ const perf=s.tag_performance||{};
1092
+ const entries=Object.entries(perf).sort((a,b)=>b[1]-a[1]);
1093
+ const tp=document.getElementById("tagPerf");
1094
+ if(entries.length){
1095
+ const maxV=Math.max(...entries.map(e=>e[1]),0.01);
1096
+ tp.innerHTML=entries.slice(0,6).map(([tag,val],j)=>{
1097
+ const c=j%2===0?"primary":"secondary";
1098
+ const w=Math.min(100,(val/maxV)*100);
1099
+ return `<div><div class="flex justify-between font-label text-[10px]"><span class="text-on-surface">#${tag}</span><span class="text-${c}">${val.toFixed(3)}</span></div><div class="h-1.5 bg-surface-top rounded-full mt-1 overflow-hidden"><div class="h-full bg-gradient-to-r from-${c} to-${c}-ctr rounded-full" style="width:${w}%"></div></div></div>`;
1100
+ }).join("");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1101
  }
1102
+ const ttl=document.getElementById("topTagsList");
1103
+ const colors=["secondary","primary","tertiary","on-surface-dim"];
1104
+ if(entries.length){
1105
+ ttl.innerHTML=entries.slice(0,4).map(([tag,val],j)=>{
1106
+ const c=colors[j%colors.length];
1107
+ const fmtVal=val>=1000?(val/1000).toFixed(1)+"k":val.toFixed(1);
1108
+ return `<div class="flex items-center justify-between"><div class="flex items-center gap-2.5"><span class="w-2 h-2 rounded-full bg-${c}"></span><span class="text-sm font-label text-on-surface">#${tag}</span></div><span class="text-sm font-bold font-label text-${c}">${fmtVal}</span></div>`;
1109
+ }).join("");
1110
+ }
1111
+
1112
+ await new Promise(r=>setTimeout(r,12));
1113
 
1114
  const color=s.reward>0.5?"text-secondary":s.reward>0.2?"text-primary":"text-on-surface-dim";
1115
  const err=s.error?` <span class="text-error">err=${s.error}</span>`:"";
1116
  const dn=s.done?'<span class="text-tertiary font-bold"> DONE</span>':"";
1117
+ log.innerHTML+=`<div class="fade-in py-0.5"><span class="text-on-surface-dim/50">[day ${s.step}]</span> <span class="text-on-surface">${s.action}</span> <span class="${color}">r=${s.reward.toFixed(2)}</span>${dn}${err}</div>`;
1118
  log.scrollTop=log.scrollHeight;
1119
  }
1120
 
 
1172
  <div class="p-4 rounded-xl border ${scoreBg} space-y-2">
1173
  <div class="flex justify-between items-center"><span class="text-[10px] font-label text-on-surface-dim uppercase tracking-widest">Grader Score</span><span class="text-3xl font-black ${scoreColor}">${sc.toFixed(4)}</span></div>
1174
  <div class="grid grid-cols-2 gap-x-6 gap-y-1 text-[10px] font-label">
1175
+ <div class="flex justify-between"><span class="text-on-surface-dim">Days</span><span>${d.total_steps}</span></div>
1176
  <div class="flex justify-between"><span class="text-on-surface-dim">Burned Out</span><span class="${f.burned_out?"text-tertiary":"text-secondary"}">${f.burned_out?"YES":"NO"}</span></div>
1177
  <div class="flex justify-between"><span class="text-on-surface-dim">Final Energy</span><span>${f.energy.toFixed(2)}</span></div>
1178
  <div class="flex justify-between"><span class="text-on-surface-dim">Followers</span><span>${f.followers.toLocaleString()}</span></div>
 
1212
  const deltaStr=(delta>=0?"+":"")+delta.toLocaleString();
1213
  const deltaClass=delta>0?"text-secondary":delta<0?"text-tertiary":"text-on-surface-dim";
1214
  const scoreColor=h.score>=0.7?"text-primary":h.score>=0.3?"text-secondary":"text-tertiary";
1215
+ const status=f.burned_out?'<span class="text-tertiary font-bold">BURNED</span>':h.total_steps>=EPISODE_DAYS?'<span class="text-secondary">DONE</span>':'<span class="text-on-surface-dim">PARTIAL</span>';
1216
  const energyColor=f.energy>=0.5?"text-secondary":f.energy>0?"text-tertiary":"text-error";
1217
  const desc=(h.description||"").trim();
1218
  return `<tr class="border-b border-white/5 hover:bg-white/[.02] transition">
 
1293
  }
1294
  }
1295
 
1296
+ (function(){const h=document.getElementById("episodeHorizon");if(h)h.textContent=String(EPISODE_DAYS);})();
1297
  loadScenarioButtons();
1298
  loadHistory();
1299
  doReset();
server/viraltest_environment.py CHANGED
@@ -150,8 +150,9 @@ class ViraltestEnvironment(Environment):
150
  """
151
  Weekly creator optimization simulation.
152
 
153
- The agent manages a social media creator's posting strategy over 7 days
154
- (168 hourly steps), balancing engagement, energy, tags, and competition.
 
155
  """
156
 
157
  SUPPORTS_CONCURRENT_SESSIONS: bool = True
 
150
  """
151
  Weekly creator optimization simulation.
152
 
153
+ The agent manages a social media creator's posting strategy over 7 daily
154
+ steps (each day runs 24 simulated hours from a sparse schedule), balancing
155
+ engagement, energy, tags, and competition.
156
  """
157
 
158
  SUPPORTS_CONCURRENT_SESSIONS: bool = True