Spaces:
Sleeping
Sleeping
Commit ·
09ea129
1
Parent(s): 2ae4336
reduced steps to fit out free tier
Browse files- inference.py +3 -3
- server/dashboard.html +95 -98
- server/viraltest_environment.py +3 -2
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 "
|
| 39 |
-
MODEL_NAME = os.getenv("MODEL_NAME") or "
|
| 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[-
|
| 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">
|
| 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
|
| 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
|
| 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-
|
| 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 |
-
<!--
|
| 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">
|
| 232 |
-
<span class="text-[9px] font-label text-on-surface-dim uppercase tracking-widest">X =
|
| 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 /
|
| 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 /
|
| 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 Δ /
|
| 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 /
|
| 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 /
|
| 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 /
|
| 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 /
|
| 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 /
|
| 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 /
|
| 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 /
|
| 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="
|
| 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="
|
| 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="
|
| 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">
|
| 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 |
-
<!--
|
| 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>
|
| 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">
|
| 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
|
| 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">
|
| 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+
|
| 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">
|
| 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
|
| 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("
|
| 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("
|
| 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">
|
| 903 |
-
h+=`<text x="${pL+plotW/2}" y="${H-18}" text-anchor="middle" fill="#958ea0" font-size="9" font-family="Space Grotesk,sans-serif">
|
| 904 |
-
h+=`<text x="${W-pR}" y="${H-18}" text-anchor="end" fill="#958ea0" font-size="9" font-family="Space Grotesk,sans-serif">
|
| 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">
|
| 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:
|
| 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}
|
| 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 |
-
|
| 1060 |
-
|
| 1061 |
-
|
| 1062 |
-
|
| 1063 |
-
|
| 1064 |
-
|
| 1065 |
-
|
| 1066 |
-
|
| 1067 |
-
|
| 1068 |
-
|
| 1069 |
-
|
| 1070 |
-
|
| 1071 |
-
|
| 1072 |
-
|
| 1073 |
-
|
| 1074 |
-
|
| 1075 |
-
|
| 1076 |
-
|
| 1077 |
-
|
| 1078 |
-
|
| 1079 |
-
|
| 1080 |
-
|
| 1081 |
-
|
| 1082 |
-
|
| 1083 |
-
|
| 1084 |
-
|
| 1085 |
-
|
| 1086 |
-
|
| 1087 |
-
|
| 1088 |
-
|
| 1089 |
-
|
| 1090 |
-
|
| 1091 |
-
|
| 1092 |
-
|
| 1093 |
-
|
| 1094 |
-
const
|
| 1095 |
-
|
| 1096 |
-
|
| 1097 |
-
|
| 1098 |
-
|
| 1099 |
-
|
| 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">
|
| 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>=
|
| 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 & 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
|
| 154 |
-
(
|
|
|
|
| 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
|