Spaces:
Running
feat(v0.2): full i18n coverage + round flag buttons + loading progress bar
Browse filesUI improvements based on self-test feedback:
- Round flag buttons (36px circular) with tooltip-on-hover label below.
Active language gets glowing border + accent background.
- Loading progress bar: shimmering indeterminate + discrete progress
(Pyodide load → 50%, TAF load → 95%, ready → hide). Visible during
the ~10-30s initial boot so users know something is happening.
- Expanded data-i18n coverage to ALL visible text:
* Mode tab buttons (Profile/Compare/Ask/Recipe)
* Profile section: preset label, HF id label, fetch button, generate button
* Compare section: recipe label, T_eval label, models title, compare button
* Ask section: title, placeholder, analyze button, example button
* Recipe section: title, default option
* Form section: input title, preset label, HF label, fetch button, run button
* Output sections: verdict title, chain title, chain desc, answer title,
share button, TAF Card title, comparison title
- New translation keys: profile.hf_placeholder, compare.hf_placeholder
(placeholders use data-i18n-placeholder attribute)
- Removed accidentally duplicated ask-section block
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- index.html +50 -44
- js/i18n.js +16 -4
- js/main.js +22 -3
- style.css +66 -8
|
@@ -10,12 +10,12 @@
|
|
| 10 |
</head>
|
| 11 |
<body>
|
| 12 |
<header>
|
| 13 |
-
<!-- Language switcher (top-right) -->
|
| 14 |
<div class="lang-switcher">
|
| 15 |
-
<button class="lang-btn" data-lang="en" title="English">🇬🇧</button>
|
| 16 |
-
<button class="lang-btn" data-lang="es" title="Español">🇪🇸</button>
|
| 17 |
-
<button class="lang-btn" data-lang="fr" title="Français">🇫🇷</button>
|
| 18 |
-
<button class="lang-btn" data-lang="zh" title="中文">🇨🇳</button>
|
| 19 |
</div>
|
| 20 |
|
| 21 |
<h1 data-i18n="hero.title">🔬 TAF Agent</h1>
|
|
@@ -141,8 +141,13 @@
|
|
| 141 |
</div>
|
| 142 |
|
| 143 |
<main>
|
| 144 |
-
<!-- Status -->
|
| 145 |
-
<section id="status-bar">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 146 |
|
| 147 |
<!-- Mode toggle -->
|
| 148 |
<section id="mode-section">
|
|
@@ -179,23 +184,24 @@
|
|
| 179 |
what's its full viability profile?" → paste id → Profile → done.
|
| 180 |
</span></span>
|
| 181 |
</h2>
|
| 182 |
-
<p class="recipe-desc">
|
| 183 |
<strong>For technicians</strong>: when you need a complete viability snapshot
|
| 184 |
of a candidate model. Outputs match paper §sec:gamma_decomposition format.
|
| 185 |
</p>
|
| 186 |
|
| 187 |
<div class="form-row">
|
| 188 |
-
<label for="profile-preset">Preset:</label>
|
| 189 |
<select id="profile-preset" disabled>
|
| 190 |
-
<option value="">— or pick from list —</option>
|
| 191 |
</select>
|
| 192 |
</div>
|
| 193 |
|
| 194 |
<div class="form-row">
|
| 195 |
-
<label for="profile-hf-id">HF model id:</label>
|
| 196 |
<input type="text" id="profile-hf-id"
|
|
|
|
| 197 |
placeholder="e.g. meta-llama/Meta-Llama-3-8B or Qwen/Qwen2.5-7B" style="flex:1;" />
|
| 198 |
-
<button id="profile-fetch-btn" type="button" class="secondary">📥 Fetch</button>
|
| 199 |
</div>
|
| 200 |
<div id="profile-hf-status" class="subtle" style="margin: -0.5rem 0 1rem; min-height:1.2em;"></div>
|
| 201 |
|
|
@@ -241,7 +247,7 @@
|
|
| 241 |
</div>
|
| 242 |
</div>
|
| 243 |
|
| 244 |
-
<button id="profile-btn" disabled>🚀 Generate full profile</button>
|
| 245 |
</section>
|
| 246 |
|
| 247 |
<!-- COMPARE mode -->
|
|
@@ -257,20 +263,20 @@
|
|
| 257 |
best: Llama-3-8B, Mistral-7B, or Qwen-7B?" → pick 3 + X-2 + 16K → see winner.
|
| 258 |
</span></span>
|
| 259 |
</h2>
|
| 260 |
-
<p class="recipe-desc">
|
| 261 |
<strong>For technicians</strong>: when choosing between 2-3 candidate models for
|
| 262 |
a specific deployment scenario. Compare their verdicts on the same recipe.
|
| 263 |
</p>
|
| 264 |
|
| 265 |
<div class="form-row">
|
| 266 |
-
<label for="compare-recipe">Recipe:</label>
|
| 267 |
<select id="compare-recipe" disabled>
|
| 268 |
-
<option value="">— pick a recipe —</option>
|
| 269 |
</select>
|
| 270 |
</div>
|
| 271 |
|
| 272 |
<div class="form-row">
|
| 273 |
-
<label for="compare-T_eval">T_eval (target context):</label>
|
| 274 |
<input type="number" id="compare-T_eval" value="16000" style="flex:1;" />
|
| 275 |
<span class="info" style="margin-top:0.5rem;"><span class="tooltip">
|
| 276 |
For X-2 / X-19 only. The context length all compared models will be
|
|
@@ -279,7 +285,7 @@
|
|
| 279 |
</div>
|
| 280 |
|
| 281 |
<div id="compare-models">
|
| 282 |
-
<h3 style="margin-top:1rem;">Models to compare (add up to 3)</h3>
|
| 283 |
<div class="compare-slot" data-slot="1">
|
| 284 |
<input type="text" class="compare-hf-id" placeholder="HF model id (e.g. meta-llama/Meta-Llama-3-8B)" />
|
| 285 |
<select class="compare-preset">
|
|
@@ -300,82 +306,82 @@
|
|
| 300 |
</div>
|
| 301 |
</div>
|
| 302 |
|
| 303 |
-
<button id="compare-btn" disabled style="margin-top:1rem;">🚀 Compare</button>
|
| 304 |
</section>
|
| 305 |
|
| 306 |
-
<!-- ASK mode -->
|
| 307 |
<section id="ask-section" style="display:none;">
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
|
| 311 |
-
|
| 312 |
-
<textarea id="question" rows="3" placeholder="e.g. Will Mistral-7B handle 16K NIAH retrieval? Or: I have $5,000, what model can I train? Or: Cheapest GPU to serve Llama-70B at 100M tokens/day?"></textarea>
|
| 313 |
<div style="display:flex; gap:0.5rem; margin-top:0.5rem; flex-wrap:wrap;">
|
| 314 |
-
<button id="ask-btn" disabled>🚀 Analyze</button>
|
| 315 |
-
<button id="example-btn" type="button" class="secondary">💡 Try an example</button>
|
| 316 |
</div>
|
| 317 |
</section>
|
| 318 |
|
| 319 |
<!-- Recipe selector (mode=recipe) -->
|
| 320 |
<section id="recipe-section" style="display:none;">
|
| 321 |
-
<h2>📋 Recipe</h2>
|
| 322 |
<select id="recipe-select" disabled>
|
| 323 |
-
<option value="">— select a recipe —</option>
|
| 324 |
</select>
|
| 325 |
<p id="recipe-desc-display" class="recipe-desc"></p>
|
| 326 |
</section>
|
| 327 |
|
| 328 |
<!-- Form (mode=recipe) -->
|
| 329 |
<section id="form-section" style="display:none;">
|
| 330 |
-
<h2>🎯 Inputs</h2>
|
| 331 |
|
| 332 |
<div class="form-row">
|
| 333 |
-
<label for="preset">Preset model:</label>
|
| 334 |
<select id="preset" disabled>
|
| 335 |
-
<option value="">— select to autofill —</option>
|
| 336 |
</select>
|
| 337 |
</div>
|
| 338 |
|
| 339 |
<div class="form-row">
|
| 340 |
-
<label for="hf-id">Or any HF model:</label>
|
| 341 |
-
<input type="text" id="hf-id"
|
| 342 |
-
|
|
|
|
|
|
|
| 343 |
</div>
|
| 344 |
<div id="hf-status" class="subtle" style="margin: -0.5rem 0 1rem; min-height:1.2em;"></div>
|
| 345 |
|
| 346 |
-
<!-- Dynamic form fields based on recipe -->
|
| 347 |
<div id="dynamic-form" class="form-grid"></div>
|
| 348 |
|
| 349 |
-
<button id="run-btn" disabled>🚀 Analyze</button>
|
| 350 |
</section>
|
| 351 |
|
| 352 |
<!-- Output (single-recipe verdict + chain) -->
|
| 353 |
<section id="output-section" style="display:none;">
|
| 354 |
-
<h2>📊 Verdict</h2>
|
| 355 |
<div id="verdict-box"></div>
|
| 356 |
|
| 357 |
<div style="margin: 0.75rem 0;">
|
| 358 |
-
<button id="share-btn" class="secondary" type="button">🔗 Copy share link</button>
|
| 359 |
<span id="share-status" class="subtle" style="margin-left:0.75rem;"></span>
|
| 360 |
</div>
|
| 361 |
|
| 362 |
-
<h2>🔍 Computation Chain</h2>
|
| 363 |
-
<p class="subtle">Every number below is deterministic Python. Click a step to expand.</p>
|
| 364 |
<div id="chain-box"></div>
|
| 365 |
|
| 366 |
-
<h2 id="answer-header" style="display:none;">💬 Plain-English Answer</h2>
|
| 367 |
<div id="answer-box" style="display:none;"></div>
|
| 368 |
</section>
|
| 369 |
|
| 370 |
<!-- Profile output -->
|
| 371 |
<section id="profile-output" style="display:none;">
|
| 372 |
-
<h2>📇 TAF Card — full model profile</h2>
|
| 373 |
<div id="profile-box"></div>
|
| 374 |
</section>
|
| 375 |
|
| 376 |
<!-- Compare output -->
|
| 377 |
<section id="compare-output" style="display:none;">
|
| 378 |
-
<h2>🆚 Comparison Table</h2>
|
| 379 |
<div id="compare-box"></div>
|
| 380 |
</section>
|
| 381 |
</main>
|
|
|
|
| 10 |
</head>
|
| 11 |
<body>
|
| 12 |
<header>
|
| 13 |
+
<!-- Language switcher (top-right, round flags) -->
|
| 14 |
<div class="lang-switcher">
|
| 15 |
+
<button class="lang-btn" data-lang="en" data-label="English" title="English">🇬🇧</button>
|
| 16 |
+
<button class="lang-btn" data-lang="es" data-label="Español" title="Español">🇪🇸</button>
|
| 17 |
+
<button class="lang-btn" data-lang="fr" data-label="Français" title="Français">🇫🇷</button>
|
| 18 |
+
<button class="lang-btn" data-lang="zh" data-label="中文" title="中文">🇨🇳</button>
|
| 19 |
</div>
|
| 20 |
|
| 21 |
<h1 data-i18n="hero.title">🔬 TAF Agent</h1>
|
|
|
|
| 141 |
</div>
|
| 142 |
|
| 143 |
<main>
|
| 144 |
+
<!-- Status with loading bar -->
|
| 145 |
+
<section id="status-bar">
|
| 146 |
+
<div id="status" data-i18n="status.loading_pyodide">⏳ Loading Python runtime...</div>
|
| 147 |
+
<div id="loading-bar-wrap" style="display:none;">
|
| 148 |
+
<div id="loading-bar"></div>
|
| 149 |
+
</div>
|
| 150 |
+
</section>
|
| 151 |
|
| 152 |
<!-- Mode toggle -->
|
| 153 |
<section id="mode-section">
|
|
|
|
| 184 |
what's its full viability profile?" → paste id → Profile → done.
|
| 185 |
</span></span>
|
| 186 |
</h2>
|
| 187 |
+
<p class="recipe-desc" data-i18n="profile.desc">
|
| 188 |
<strong>For technicians</strong>: when you need a complete viability snapshot
|
| 189 |
of a candidate model. Outputs match paper §sec:gamma_decomposition format.
|
| 190 |
</p>
|
| 191 |
|
| 192 |
<div class="form-row">
|
| 193 |
+
<label for="profile-preset" data-i18n="profile.preset_label">Preset:</label>
|
| 194 |
<select id="profile-preset" disabled>
|
| 195 |
+
<option value="" data-i18n="profile.preset_default">— or pick from list —</option>
|
| 196 |
</select>
|
| 197 |
</div>
|
| 198 |
|
| 199 |
<div class="form-row">
|
| 200 |
+
<label for="profile-hf-id" data-i18n="profile.hf_label">HF model id:</label>
|
| 201 |
<input type="text" id="profile-hf-id"
|
| 202 |
+
data-i18n-placeholder="profile.hf_placeholder"
|
| 203 |
placeholder="e.g. meta-llama/Meta-Llama-3-8B or Qwen/Qwen2.5-7B" style="flex:1;" />
|
| 204 |
+
<button id="profile-fetch-btn" type="button" class="secondary" data-i18n="profile.fetch_btn">📥 Fetch</button>
|
| 205 |
</div>
|
| 206 |
<div id="profile-hf-status" class="subtle" style="margin: -0.5rem 0 1rem; min-height:1.2em;"></div>
|
| 207 |
|
|
|
|
| 247 |
</div>
|
| 248 |
</div>
|
| 249 |
|
| 250 |
+
<button id="profile-btn" disabled data-i18n="profile.btn">🚀 Generate full profile</button>
|
| 251 |
</section>
|
| 252 |
|
| 253 |
<!-- COMPARE mode -->
|
|
|
|
| 263 |
best: Llama-3-8B, Mistral-7B, or Qwen-7B?" → pick 3 + X-2 + 16K → see winner.
|
| 264 |
</span></span>
|
| 265 |
</h2>
|
| 266 |
+
<p class="recipe-desc" data-i18n="compare.desc">
|
| 267 |
<strong>For technicians</strong>: when choosing between 2-3 candidate models for
|
| 268 |
a specific deployment scenario. Compare their verdicts on the same recipe.
|
| 269 |
</p>
|
| 270 |
|
| 271 |
<div class="form-row">
|
| 272 |
+
<label for="compare-recipe" data-i18n="compare.recipe_label">Recipe:</label>
|
| 273 |
<select id="compare-recipe" disabled>
|
| 274 |
+
<option value="" data-i18n="recipe.default">— pick a recipe —</option>
|
| 275 |
</select>
|
| 276 |
</div>
|
| 277 |
|
| 278 |
<div class="form-row">
|
| 279 |
+
<label for="compare-T_eval" data-i18n="compare.T_eval_label">T_eval (target context):</label>
|
| 280 |
<input type="number" id="compare-T_eval" value="16000" style="flex:1;" />
|
| 281 |
<span class="info" style="margin-top:0.5rem;"><span class="tooltip">
|
| 282 |
For X-2 / X-19 only. The context length all compared models will be
|
|
|
|
| 285 |
</div>
|
| 286 |
|
| 287 |
<div id="compare-models">
|
| 288 |
+
<h3 style="margin-top:1rem;" data-i18n="compare.models_title">Models to compare (add up to 3)</h3>
|
| 289 |
<div class="compare-slot" data-slot="1">
|
| 290 |
<input type="text" class="compare-hf-id" placeholder="HF model id (e.g. meta-llama/Meta-Llama-3-8B)" />
|
| 291 |
<select class="compare-preset">
|
|
|
|
| 306 |
</div>
|
| 307 |
</div>
|
| 308 |
|
| 309 |
+
<button id="compare-btn" disabled style="margin-top:1rem;" data-i18n="compare.btn">🚀 Compare</button>
|
| 310 |
</section>
|
| 311 |
|
| 312 |
+
<!-- ASK mode (free-form question) -->
|
| 313 |
<section id="ask-section" style="display:none;">
|
| 314 |
+
<h2 data-i18n="ask.title">❓ Your question</h2>
|
| 315 |
+
<textarea id="question" rows="3"
|
| 316 |
+
data-i18n-placeholder="ask.placeholder"
|
| 317 |
+
placeholder="e.g. Will Mistral-7B handle 16K NIAH retrieval? Or: I have $5,000, what model can I train? Or: Cheapest GPU to serve Llama-70B at 100M tokens/day?"></textarea>
|
|
|
|
| 318 |
<div style="display:flex; gap:0.5rem; margin-top:0.5rem; flex-wrap:wrap;">
|
| 319 |
+
<button id="ask-btn" disabled data-i18n="ask.btn">🚀 Analyze</button>
|
| 320 |
+
<button id="example-btn" type="button" class="secondary" data-i18n="ask.example_btn">💡 Try an example</button>
|
| 321 |
</div>
|
| 322 |
</section>
|
| 323 |
|
| 324 |
<!-- Recipe selector (mode=recipe) -->
|
| 325 |
<section id="recipe-section" style="display:none;">
|
| 326 |
+
<h2 data-i18n="recipe.title">📋 Recipe</h2>
|
| 327 |
<select id="recipe-select" disabled>
|
| 328 |
+
<option value="" data-i18n="recipe.default">— select a recipe —</option>
|
| 329 |
</select>
|
| 330 |
<p id="recipe-desc-display" class="recipe-desc"></p>
|
| 331 |
</section>
|
| 332 |
|
| 333 |
<!-- Form (mode=recipe) -->
|
| 334 |
<section id="form-section" style="display:none;">
|
| 335 |
+
<h2 data-i18n="recipe.input_title">🎯 Inputs</h2>
|
| 336 |
|
| 337 |
<div class="form-row">
|
| 338 |
+
<label for="preset" data-i18n="profile.preset_label">Preset model:</label>
|
| 339 |
<select id="preset" disabled>
|
| 340 |
+
<option value="" data-i18n="profile.preset_default">— select to autofill —</option>
|
| 341 |
</select>
|
| 342 |
</div>
|
| 343 |
|
| 344 |
<div class="form-row">
|
| 345 |
+
<label for="hf-id" data-i18n="profile.hf_label">Or any HF model:</label>
|
| 346 |
+
<input type="text" id="hf-id"
|
| 347 |
+
data-i18n-placeholder="profile.hf_placeholder"
|
| 348 |
+
placeholder="e.g. Qwen/Qwen2.5-32B-Instruct" style="flex:1;" />
|
| 349 |
+
<button id="hf-fetch-btn" type="button" class="secondary" data-i18n="profile.fetch_btn">📥 Fetch</button>
|
| 350 |
</div>
|
| 351 |
<div id="hf-status" class="subtle" style="margin: -0.5rem 0 1rem; min-height:1.2em;"></div>
|
| 352 |
|
|
|
|
| 353 |
<div id="dynamic-form" class="form-grid"></div>
|
| 354 |
|
| 355 |
+
<button id="run-btn" disabled data-i18n="ask.btn">🚀 Analyze</button>
|
| 356 |
</section>
|
| 357 |
|
| 358 |
<!-- Output (single-recipe verdict + chain) -->
|
| 359 |
<section id="output-section" style="display:none;">
|
| 360 |
+
<h2 data-i18n="verdict.title">📊 Verdict</h2>
|
| 361 |
<div id="verdict-box"></div>
|
| 362 |
|
| 363 |
<div style="margin: 0.75rem 0;">
|
| 364 |
+
<button id="share-btn" class="secondary" type="button" data-i18n="share.btn">🔗 Copy share link</button>
|
| 365 |
<span id="share-status" class="subtle" style="margin-left:0.75rem;"></span>
|
| 366 |
</div>
|
| 367 |
|
| 368 |
+
<h2 data-i18n="chain.title">🔍 Computation Chain</h2>
|
| 369 |
+
<p class="subtle" data-i18n="chain.desc">Every number below is deterministic Python. Click a step to expand.</p>
|
| 370 |
<div id="chain-box"></div>
|
| 371 |
|
| 372 |
+
<h2 id="answer-header" style="display:none;" data-i18n="answer.title">💬 Plain-English Answer</h2>
|
| 373 |
<div id="answer-box" style="display:none;"></div>
|
| 374 |
</section>
|
| 375 |
|
| 376 |
<!-- Profile output -->
|
| 377 |
<section id="profile-output" style="display:none;">
|
| 378 |
+
<h2 data-i18n="tafcard.title">📇 TAF Card — full model profile</h2>
|
| 379 |
<div id="profile-box"></div>
|
| 380 |
</section>
|
| 381 |
|
| 382 |
<!-- Compare output -->
|
| 383 |
<section id="compare-output" style="display:none;">
|
| 384 |
+
<h2 data-i18n="compare.title_out">🆚 Comparison Table</h2>
|
| 385 |
<div id="compare-box"></div>
|
| 386 |
</section>
|
| 387 |
</main>
|
|
@@ -64,12 +64,15 @@ export const TRANSLATIONS = {
|
|
| 64 |
|
| 65 |
"compare.title_out": "🆚 Comparison Table",
|
| 66 |
|
| 67 |
-
"status.loading_pyodide": "⏳ Loading Python runtime...",
|
| 68 |
"status.loading_taf": "⏳ Loading TAF formulas + recipes...",
|
| 69 |
"status.ready": "✅ Ready. Pick a model and click Profile to start.",
|
| 70 |
"status.computing": "🧮 Computing TAF chain...",
|
| 71 |
"status.done": "✅ Done.",
|
| 72 |
|
|
|
|
|
|
|
|
|
|
| 73 |
"footer.text": "© 2026 Carles Marin · Apache-2.0 · independent research · the tool that closes the loop of the paper.",
|
| 74 |
},
|
| 75 |
|
|
@@ -127,12 +130,15 @@ export const TRANSLATIONS = {
|
|
| 127 |
|
| 128 |
"compare.title_out": "🆚 Tabla comparativa",
|
| 129 |
|
| 130 |
-
"status.loading_pyodide": "⏳ Cargando runtime Python...",
|
| 131 |
"status.loading_taf": "⏳ Cargando fórmulas TAF + recetas...",
|
| 132 |
"status.ready": "✅ Listo. Elige un modelo y click Perfilar para empezar.",
|
| 133 |
"status.computing": "🧮 Calculando cadena TAF...",
|
| 134 |
"status.done": "✅ Hecho.",
|
| 135 |
|
|
|
|
|
|
|
|
|
|
| 136 |
"footer.text": "© 2026 Carles Marin · Apache-2.0 · investigación independiente · la herramienta que cierra el círculo del paper.",
|
| 137 |
},
|
| 138 |
|
|
@@ -190,12 +196,15 @@ export const TRANSLATIONS = {
|
|
| 190 |
|
| 191 |
"compare.title_out": "🆚 Tableau comparatif",
|
| 192 |
|
| 193 |
-
"status.loading_pyodide": "⏳ Chargement du runtime Python...",
|
| 194 |
"status.loading_taf": "⏳ Chargement des formules TAF + recettes...",
|
| 195 |
"status.ready": "✅ Prêt. Choisissez un modèle et cliquez Profiler pour commencer.",
|
| 196 |
"status.computing": "🧮 Calcul de la chaîne TAF...",
|
| 197 |
"status.done": "✅ Terminé.",
|
| 198 |
|
|
|
|
|
|
|
|
|
|
| 199 |
"footer.text": "© 2026 Carles Marin · Apache-2.0 · recherche indépendante · l'outil qui ferme la boucle du paper.",
|
| 200 |
},
|
| 201 |
|
|
@@ -253,12 +262,15 @@ export const TRANSLATIONS = {
|
|
| 253 |
|
| 254 |
"compare.title_out": "🆚 比较表",
|
| 255 |
|
| 256 |
-
"status.loading_pyodide": "⏳ 加载 Python 运行时...",
|
| 257 |
"status.loading_taf": "⏳ 加载 TAF 公式 + 配方...",
|
| 258 |
"status.ready": "✅ 就绪。选择一个模型并点击画像开始。",
|
| 259 |
"status.computing": "🧮 计算 TAF 链...",
|
| 260 |
"status.done": "✅ 完成。",
|
| 261 |
|
|
|
|
|
|
|
|
|
|
| 262 |
"footer.text": "© 2026 Carles Marin · Apache-2.0 · 独立研究 · 闭合论文回路的工具。",
|
| 263 |
},
|
| 264 |
};
|
|
|
|
| 64 |
|
| 65 |
"compare.title_out": "🆚 Comparison Table",
|
| 66 |
|
| 67 |
+
"status.loading_pyodide": "⏳ Loading Python runtime (~10MB, first time only)...",
|
| 68 |
"status.loading_taf": "⏳ Loading TAF formulas + recipes...",
|
| 69 |
"status.ready": "✅ Ready. Pick a model and click Profile to start.",
|
| 70 |
"status.computing": "🧮 Computing TAF chain...",
|
| 71 |
"status.done": "✅ Done.",
|
| 72 |
|
| 73 |
+
"profile.hf_placeholder": "e.g. meta-llama/Meta-Llama-3-8B or Qwen/Qwen2.5-7B",
|
| 74 |
+
"compare.hf_placeholder": "HF model id (e.g. meta-llama/Meta-Llama-3-8B)",
|
| 75 |
+
|
| 76 |
"footer.text": "© 2026 Carles Marin · Apache-2.0 · independent research · the tool that closes the loop of the paper.",
|
| 77 |
},
|
| 78 |
|
|
|
|
| 130 |
|
| 131 |
"compare.title_out": "🆚 Tabla comparativa",
|
| 132 |
|
| 133 |
+
"status.loading_pyodide": "⏳ Cargando runtime Python (~10MB, solo primera vez)...",
|
| 134 |
"status.loading_taf": "⏳ Cargando fórmulas TAF + recetas...",
|
| 135 |
"status.ready": "✅ Listo. Elige un modelo y click Perfilar para empezar.",
|
| 136 |
"status.computing": "🧮 Calculando cadena TAF...",
|
| 137 |
"status.done": "✅ Hecho.",
|
| 138 |
|
| 139 |
+
"profile.hf_placeholder": "ej. meta-llama/Meta-Llama-3-8B o Qwen/Qwen2.5-7B",
|
| 140 |
+
"compare.hf_placeholder": "ID modelo HF (ej. meta-llama/Meta-Llama-3-8B)",
|
| 141 |
+
|
| 142 |
"footer.text": "© 2026 Carles Marin · Apache-2.0 · investigación independiente · la herramienta que cierra el círculo del paper.",
|
| 143 |
},
|
| 144 |
|
|
|
|
| 196 |
|
| 197 |
"compare.title_out": "🆚 Tableau comparatif",
|
| 198 |
|
| 199 |
+
"status.loading_pyodide": "⏳ Chargement du runtime Python (~10MB, première fois)...",
|
| 200 |
"status.loading_taf": "⏳ Chargement des formules TAF + recettes...",
|
| 201 |
"status.ready": "✅ Prêt. Choisissez un modèle et cliquez Profiler pour commencer.",
|
| 202 |
"status.computing": "🧮 Calcul de la chaîne TAF...",
|
| 203 |
"status.done": "✅ Terminé.",
|
| 204 |
|
| 205 |
+
"profile.hf_placeholder": "ex. meta-llama/Meta-Llama-3-8B ou Qwen/Qwen2.5-7B",
|
| 206 |
+
"compare.hf_placeholder": "ID modèle HF (ex. meta-llama/Meta-Llama-3-8B)",
|
| 207 |
+
|
| 208 |
"footer.text": "© 2026 Carles Marin · Apache-2.0 · recherche indépendante · l'outil qui ferme la boucle du paper.",
|
| 209 |
},
|
| 210 |
|
|
|
|
| 262 |
|
| 263 |
"compare.title_out": "🆚 比较表",
|
| 264 |
|
| 265 |
+
"status.loading_pyodide": "⏳ 加载 Python 运行时 (~10MB,首次加载)...",
|
| 266 |
"status.loading_taf": "⏳ 加载 TAF 公式 + 配方...",
|
| 267 |
"status.ready": "✅ 就绪。选择一个模型并点击画像开始。",
|
| 268 |
"status.computing": "🧮 计算 TAF 链...",
|
| 269 |
"status.done": "✅ 完成。",
|
| 270 |
|
| 271 |
+
"profile.hf_placeholder": "例如: meta-llama/Meta-Llama-3-8B 或 Qwen/Qwen2.5-7B",
|
| 272 |
+
"compare.hf_placeholder": "HF 模型 id (例如: meta-llama/Meta-Llama-3-8B)",
|
| 273 |
+
|
| 274 |
"footer.text": "© 2026 Carles Marin · Apache-2.0 · 独立研究 · 闭合论文回路的工具。",
|
| 275 |
},
|
| 276 |
};
|
|
@@ -39,12 +39,29 @@ const EXAMPLES = [
|
|
| 39 |
// ════════════════════════════════════════════════════════════════════
|
| 40 |
// Bootstrap
|
| 41 |
// ════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
async function loadPyodideAndTaf() {
|
| 43 |
-
|
|
|
|
| 44 |
state.pyodide = await loadPyodide({
|
| 45 |
indexURL: "https://cdn.jsdelivr.net/pyodide/v0.26.4/full/",
|
| 46 |
});
|
| 47 |
-
|
|
|
|
| 48 |
const tafCode = await fetch(TAF_BROWSER_URL).then(r => r.text());
|
| 49 |
await state.pyodide.runPythonAsync(tafCode);
|
| 50 |
|
|
@@ -52,10 +69,12 @@ async function loadPyodideAndTaf() {
|
|
| 52 |
state.recipes = JSON.parse(state.pyodide.runPython("list_recipes()"));
|
| 53 |
state.recipesById = Object.fromEntries(state.recipes.map(r => [r.id, r]));
|
| 54 |
|
|
|
|
| 55 |
populatePresets();
|
| 56 |
populateRecipes();
|
| 57 |
enableUI();
|
| 58 |
-
|
|
|
|
| 59 |
}
|
| 60 |
|
| 61 |
function populatePresets() {
|
|
|
|
| 39 |
// ════════════════════════════════════════════════════════════════════
|
| 40 |
// Bootstrap
|
| 41 |
// ════════════════════════════════════════════════════════════════════
|
| 42 |
+
function showLoadingBar(show, progress=null) {
|
| 43 |
+
const wrap = $("loading-bar-wrap");
|
| 44 |
+
const bar = $("loading-bar");
|
| 45 |
+
if (!wrap || !bar) return;
|
| 46 |
+
if (!show) { wrap.style.display = "none"; return; }
|
| 47 |
+
wrap.style.display = "block";
|
| 48 |
+
if (progress === null) {
|
| 49 |
+
bar.classList.add("indeterminate");
|
| 50 |
+
bar.style.width = "100%";
|
| 51 |
+
} else {
|
| 52 |
+
bar.classList.remove("indeterminate");
|
| 53 |
+
bar.style.width = `${Math.min(100, Math.max(0, progress * 100))}%`;
|
| 54 |
+
}
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
async function loadPyodideAndTaf() {
|
| 58 |
+
showLoadingBar(true, null);
|
| 59 |
+
setStatus(t("status.loading_pyodide"));
|
| 60 |
state.pyodide = await loadPyodide({
|
| 61 |
indexURL: "https://cdn.jsdelivr.net/pyodide/v0.26.4/full/",
|
| 62 |
});
|
| 63 |
+
showLoadingBar(true, 0.5);
|
| 64 |
+
setStatus(t("status.loading_taf"));
|
| 65 |
const tafCode = await fetch(TAF_BROWSER_URL).then(r => r.text());
|
| 66 |
await state.pyodide.runPythonAsync(tafCode);
|
| 67 |
|
|
|
|
| 69 |
state.recipes = JSON.parse(state.pyodide.runPython("list_recipes()"));
|
| 70 |
state.recipesById = Object.fromEntries(state.recipes.map(r => [r.id, r]));
|
| 71 |
|
| 72 |
+
showLoadingBar(true, 0.95);
|
| 73 |
populatePresets();
|
| 74 |
populateRecipes();
|
| 75 |
enableUI();
|
| 76 |
+
showLoadingBar(false);
|
| 77 |
+
setStatus(t("status.ready"));
|
| 78 |
}
|
| 79 |
|
| 80 |
function populatePresets() {
|
|
@@ -33,29 +33,53 @@ header {
|
|
| 33 |
}
|
| 34 |
header h1 { margin: 0 0 0.5rem 0; font-size: 2rem; }
|
| 35 |
|
| 36 |
-
/* Language switcher (top-right) */
|
| 37 |
.lang-switcher {
|
| 38 |
position: absolute;
|
| 39 |
top: 1rem; right: 1rem;
|
| 40 |
-
display: flex; gap: 0.
|
| 41 |
z-index: 50;
|
| 42 |
}
|
| 43 |
.lang-btn {
|
| 44 |
background: var(--bg-input);
|
| 45 |
-
border:
|
| 46 |
color: var(--fg);
|
| 47 |
-
border-radius:
|
| 48 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
font-size: 1.1rem;
|
| 50 |
cursor: pointer;
|
| 51 |
line-height: 1;
|
| 52 |
-
transition:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
}
|
| 54 |
-
.lang-btn:hover { border-color: var(--accent); }
|
| 55 |
.lang-btn.lang-active {
|
| 56 |
border-color: var(--accent);
|
| 57 |
background: var(--accent-dim);
|
|
|
|
| 58 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
|
| 60 |
/* Quickstart banner */
|
| 61 |
.quickstart-banner {
|
|
@@ -97,7 +121,41 @@ section {
|
|
| 97 |
h2 { margin-top: 0; font-size: 1.2rem; color: var(--accent); }
|
| 98 |
|
| 99 |
#status-bar { padding: 0.75rem 1.25rem; }
|
| 100 |
-
#status { font-family: monospace; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
|
| 102 |
.recipe-desc { color: var(--fg-dim); margin: 0.5rem 0 0 0; }
|
| 103 |
|
|
|
|
| 33 |
}
|
| 34 |
header h1 { margin: 0 0 0.5rem 0; font-size: 2rem; }
|
| 35 |
|
| 36 |
+
/* Language switcher (top-right) — round flags */
|
| 37 |
.lang-switcher {
|
| 38 |
position: absolute;
|
| 39 |
top: 1rem; right: 1rem;
|
| 40 |
+
display: flex; gap: 0.4rem;
|
| 41 |
z-index: 50;
|
| 42 |
}
|
| 43 |
.lang-btn {
|
| 44 |
background: var(--bg-input);
|
| 45 |
+
border: 2px solid var(--border);
|
| 46 |
color: var(--fg);
|
| 47 |
+
border-radius: 50%;
|
| 48 |
+
width: 36px; height: 36px;
|
| 49 |
+
padding: 0;
|
| 50 |
+
display: inline-flex;
|
| 51 |
+
align-items: center;
|
| 52 |
+
justify-content: center;
|
| 53 |
font-size: 1.1rem;
|
| 54 |
cursor: pointer;
|
| 55 |
line-height: 1;
|
| 56 |
+
transition: all 0.2s ease;
|
| 57 |
+
position: relative;
|
| 58 |
+
}
|
| 59 |
+
.lang-btn:hover {
|
| 60 |
+
border-color: var(--accent);
|
| 61 |
+
transform: scale(1.1);
|
| 62 |
}
|
|
|
|
| 63 |
.lang-btn.lang-active {
|
| 64 |
border-color: var(--accent);
|
| 65 |
background: var(--accent-dim);
|
| 66 |
+
box-shadow: 0 0 12px rgba(88, 166, 255, 0.4);
|
| 67 |
}
|
| 68 |
+
.lang-btn::after {
|
| 69 |
+
content: attr(data-label);
|
| 70 |
+
position: absolute;
|
| 71 |
+
top: 100%;
|
| 72 |
+
left: 50%;
|
| 73 |
+
transform: translateX(-50%);
|
| 74 |
+
margin-top: 4px;
|
| 75 |
+
font-size: 0.65rem;
|
| 76 |
+
color: var(--fg-dim);
|
| 77 |
+
opacity: 0;
|
| 78 |
+
transition: opacity 0.15s;
|
| 79 |
+
white-space: nowrap;
|
| 80 |
+
pointer-events: none;
|
| 81 |
+
}
|
| 82 |
+
.lang-btn:hover::after { opacity: 1; }
|
| 83 |
|
| 84 |
/* Quickstart banner */
|
| 85 |
.quickstart-banner {
|
|
|
|
| 121 |
h2 { margin-top: 0; font-size: 1.2rem; color: var(--accent); }
|
| 122 |
|
| 123 |
#status-bar { padding: 0.75rem 1.25rem; }
|
| 124 |
+
#status { font-family: monospace; margin-bottom: 0.4rem; }
|
| 125 |
+
|
| 126 |
+
/* Loading progress bar */
|
| 127 |
+
#loading-bar-wrap {
|
| 128 |
+
height: 6px;
|
| 129 |
+
background: var(--bg-input);
|
| 130 |
+
border-radius: 3px;
|
| 131 |
+
overflow: hidden;
|
| 132 |
+
position: relative;
|
| 133 |
+
}
|
| 134 |
+
#loading-bar {
|
| 135 |
+
height: 100%;
|
| 136 |
+
background: linear-gradient(90deg, var(--accent-dim), var(--accent), var(--accent-dim));
|
| 137 |
+
background-size: 200% 100%;
|
| 138 |
+
width: 0%;
|
| 139 |
+
transition: width 0.3s ease-out;
|
| 140 |
+
border-radius: 3px;
|
| 141 |
+
animation: loading-shimmer 1.5s linear infinite;
|
| 142 |
+
}
|
| 143 |
+
@keyframes loading-shimmer {
|
| 144 |
+
0% { background-position: 200% 0; }
|
| 145 |
+
100% { background-position: -200% 0; }
|
| 146 |
+
}
|
| 147 |
+
#loading-bar.indeterminate {
|
| 148 |
+
width: 100%;
|
| 149 |
+
background: linear-gradient(90deg,
|
| 150 |
+
var(--bg-input) 0%, var(--accent) 50%, var(--bg-input) 100%);
|
| 151 |
+
background-size: 50% 100%;
|
| 152 |
+
background-repeat: no-repeat;
|
| 153 |
+
animation: loading-indeterminate 1.5s linear infinite;
|
| 154 |
+
}
|
| 155 |
+
@keyframes loading-indeterminate {
|
| 156 |
+
0% { background-position: -50% 0; }
|
| 157 |
+
100% { background-position: 150% 0; }
|
| 158 |
+
}
|
| 159 |
|
| 160 |
.recipe-desc { color: var(--fg-dim); margin: 0.5rem 0 0 0; }
|
| 161 |
|