Update automatic refresh
Browse files- static/app.js +34 -17
static/app.js
CHANGED
|
@@ -238,7 +238,24 @@ function annotator() {
|
|
| 238 |
async refresh() {
|
| 239 |
const r = await fetch('/api/state');
|
| 240 |
const data = await r.json();
|
| 241 |
-
this.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 242 |
},
|
| 243 |
|
| 244 |
// ----------- helpers -----------
|
|
@@ -292,7 +309,7 @@ function annotator() {
|
|
| 292 |
// ----------- mutations: task / settings / models -----------
|
| 293 |
async setPreset(key) {
|
| 294 |
const r = await fetch('/api/task/preset', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ key }) });
|
| 295 |
-
this.
|
| 296 |
this.taskEditor.json = JSON.stringify(this.state.schema, null, 2);
|
| 297 |
this.toast('Task: ' + this.state.schema.task_name, 'ok');
|
| 298 |
},
|
|
@@ -302,7 +319,7 @@ function annotator() {
|
|
| 302 |
const annotation_schema = JSON.parse(this.taskEditor.json);
|
| 303 |
const r = await fetch('/api/task/schema', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ annotation_schema }) });
|
| 304 |
if (!r.ok) throw new Error((await r.json()).detail);
|
| 305 |
-
this.
|
| 306 |
this.toast('Custom schema applied.', 'ok');
|
| 307 |
} catch (e) {
|
| 308 |
this.toast('Invalid schema JSON: ' + e.message, 'error');
|
|
@@ -311,7 +328,7 @@ function annotator() {
|
|
| 311 |
|
| 312 |
async saveSettings(partial) {
|
| 313 |
const r = await fetch('/api/settings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(partial) });
|
| 314 |
-
this.
|
| 315 |
},
|
| 316 |
|
| 317 |
saveKey() {
|
|
@@ -396,7 +413,7 @@ function annotator() {
|
|
| 396 |
this.loading = true;
|
| 397 |
try {
|
| 398 |
const r = await fetch('/api/corpus/exercise', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ idx }) });
|
| 399 |
-
this.
|
| 400 |
this.taskEditor.json = JSON.stringify(this.state.schema, null, 2);
|
| 401 |
this.toast(`Loaded: ${this.state.exercises[idx].title}`, 'ok');
|
| 402 |
} finally { this.loading = false; }
|
|
@@ -463,10 +480,10 @@ function annotator() {
|
|
| 463 |
const annotation_schema = this.buildCustomSchema();
|
| 464 |
const r0 = await fetch('/api/task/schema', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ annotation_schema }) });
|
| 465 |
if (!r0.ok) { this.toast('Schema rejected: ' + (await r0.json()).detail, 'error'); return; }
|
| 466 |
-
this.
|
| 467 |
} else if (this.pasteEditor.presetKey) {
|
| 468 |
const r0 = await fetch('/api/task/preset', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ key: this.pasteEditor.presetKey }) });
|
| 469 |
-
if (r0.ok) this.
|
| 470 |
}
|
| 471 |
|
| 472 |
// 2) Load the text
|
|
@@ -475,7 +492,7 @@ function annotator() {
|
|
| 475 |
tokenizer: this.pasteEditor.tokenizer,
|
| 476 |
language: this.pasteEditor.language,
|
| 477 |
}) });
|
| 478 |
-
this.
|
| 479 |
this.closeModal();
|
| 480 |
this.toast(`Loaded ${this.state.sentences.length} sentence(s). Task: ${this.state.schema?.task_name}.`, 'ok');
|
| 481 |
} finally { this.loading = false; }
|
|
@@ -483,12 +500,12 @@ function annotator() {
|
|
| 483 |
|
| 484 |
async clearCorpus() {
|
| 485 |
const r = await fetch('/api/corpus/clear', { method: 'POST' });
|
| 486 |
-
this.
|
| 487 |
},
|
| 488 |
|
| 489 |
async clearIcl() {
|
| 490 |
const r = await fetch('/api/icl/clear', { method: 'POST' });
|
| 491 |
-
this.
|
| 492 |
},
|
| 493 |
|
| 494 |
// ----------- annotation -----------
|
|
@@ -501,7 +518,7 @@ function annotator() {
|
|
| 501 |
try {
|
| 502 |
const r = await fetch('/api/annotate', { method: 'POST', headers: { 'Content-Type': 'application/json', ...this.keyHeaders() }, body: JSON.stringify({}) });
|
| 503 |
if (!r.ok) throw new Error((await r.json()).detail);
|
| 504 |
-
this.
|
| 505 |
const dis = this.totalDisagreements;
|
| 506 |
this.toast(`Done. ${dis} disagreement${dis !== 1 ? 's' : ''} to review.`, dis > 0 ? 'warn' : 'ok');
|
| 507 |
} catch (e) {
|
|
@@ -516,7 +533,7 @@ function annotator() {
|
|
| 516 |
try {
|
| 517 |
const r = await fetch('/api/annotate', { method: 'POST', headers: { 'Content-Type': 'application/json', ...this.keyHeaders() }, body: JSON.stringify({ sentence_idxs: [sidx] }) });
|
| 518 |
if (!r.ok) throw new Error((await r.json()).detail);
|
| 519 |
-
this.
|
| 520 |
const s = this.state.sentences[sidx];
|
| 521 |
this.toast(`Sentence ${s.id}: ${s.n_disagreements} disagreement(s).`, s.n_disagreements > 0 ? 'warn' : 'ok');
|
| 522 |
} catch (e) { this.toast(e.message, 'error'); }
|
|
@@ -525,7 +542,7 @@ function annotator() {
|
|
| 525 |
|
| 526 |
async addSentenceToIcl(sidx) {
|
| 527 |
const r = await fetch(`/api/sentence/${sidx}/add_to_icl`, { method: 'POST' });
|
| 528 |
-
this.
|
| 529 |
this.toast(`Added to ICL pool (v${this.state.icl_pool.version}, ${this.state.icl_pool.size} entries).`, 'ok');
|
| 530 |
},
|
| 531 |
|
|
@@ -609,7 +626,7 @@ function annotator() {
|
|
| 609 |
}) });
|
| 610 |
if (!r.ok) throw new Error((await r.json()).detail);
|
| 611 |
const sent = await r.json();
|
| 612 |
-
this.
|
| 613 |
// re-open with the new token
|
| 614 |
this.openTokenEditor(this.editor.sidx, this.editor.tidx);
|
| 615 |
this.toast(`Re-asked ${this.modelShort(model)}.`, 'ok');
|
|
@@ -631,7 +648,7 @@ function annotator() {
|
|
| 631 |
const r = await fetch(`/api/sentence/${sidx}/token/${tidx}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ token: this.editor.tok }) });
|
| 632 |
if (!r.ok) { this.toast('Save failed.', 'error'); return; }
|
| 633 |
const sent = await r.json();
|
| 634 |
-
this.
|
| 635 |
|
| 636 |
let propagatedCount = 0;
|
| 637 |
if (wantPropagate) {
|
|
@@ -647,7 +664,7 @@ function annotator() {
|
|
| 647 |
});
|
| 648 |
if (r2.ok) {
|
| 649 |
const j = await r2.json();
|
| 650 |
-
this.
|
| 651 |
propagatedCount = (j.affected || []).length;
|
| 652 |
}
|
| 653 |
} catch (e) {
|
|
@@ -718,7 +735,7 @@ function annotator() {
|
|
| 718 |
const r = await fetch(`/api/sentence/${s}/bulk`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({
|
| 719 |
token_idxs: idxs, field: this.bulkEditor.field, value: this.bulkEditor.value,
|
| 720 |
}) });
|
| 721 |
-
if (r.ok) this.
|
| 722 |
}
|
| 723 |
this.clearSelection();
|
| 724 |
this.closeModal();
|
|
|
|
| 238 |
async refresh() {
|
| 239 |
const r = await fetch('/api/state');
|
| 240 |
const data = await r.json();
|
| 241 |
+
this.applyState(data);
|
| 242 |
+
},
|
| 243 |
+
|
| 244 |
+
// Mutate state property-by-property and replace nested arrays with fresh references,
|
| 245 |
+
// so Alpine reactivity detects every change (replacing `state` wholesale can silently
|
| 246 |
+
// miss deep updates in x-for / :class bindings).
|
| 247 |
+
applyState(newState) {
|
| 248 |
+
if (!newState) return;
|
| 249 |
+
for (const k of Object.keys(newState)) {
|
| 250 |
+
const v = newState[k];
|
| 251 |
+
this.state[k] = Array.isArray(v) ? v.slice() : v;
|
| 252 |
+
}
|
| 253 |
+
},
|
| 254 |
+
|
| 255 |
+
replaceSentence(sidx, sent) {
|
| 256 |
+
const arr = this.state.sentences.slice();
|
| 257 |
+
arr[sidx] = sent;
|
| 258 |
+
this.state.sentences = arr;
|
| 259 |
},
|
| 260 |
|
| 261 |
// ----------- helpers -----------
|
|
|
|
| 309 |
// ----------- mutations: task / settings / models -----------
|
| 310 |
async setPreset(key) {
|
| 311 |
const r = await fetch('/api/task/preset', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ key }) });
|
| 312 |
+
this.applyState(await r.json());
|
| 313 |
this.taskEditor.json = JSON.stringify(this.state.schema, null, 2);
|
| 314 |
this.toast('Task: ' + this.state.schema.task_name, 'ok');
|
| 315 |
},
|
|
|
|
| 319 |
const annotation_schema = JSON.parse(this.taskEditor.json);
|
| 320 |
const r = await fetch('/api/task/schema', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ annotation_schema }) });
|
| 321 |
if (!r.ok) throw new Error((await r.json()).detail);
|
| 322 |
+
this.applyState(await r.json());
|
| 323 |
this.toast('Custom schema applied.', 'ok');
|
| 324 |
} catch (e) {
|
| 325 |
this.toast('Invalid schema JSON: ' + e.message, 'error');
|
|
|
|
| 328 |
|
| 329 |
async saveSettings(partial) {
|
| 330 |
const r = await fetch('/api/settings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(partial) });
|
| 331 |
+
this.applyState(await r.json());
|
| 332 |
},
|
| 333 |
|
| 334 |
saveKey() {
|
|
|
|
| 413 |
this.loading = true;
|
| 414 |
try {
|
| 415 |
const r = await fetch('/api/corpus/exercise', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ idx }) });
|
| 416 |
+
this.applyState(await r.json());
|
| 417 |
this.taskEditor.json = JSON.stringify(this.state.schema, null, 2);
|
| 418 |
this.toast(`Loaded: ${this.state.exercises[idx].title}`, 'ok');
|
| 419 |
} finally { this.loading = false; }
|
|
|
|
| 480 |
const annotation_schema = this.buildCustomSchema();
|
| 481 |
const r0 = await fetch('/api/task/schema', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ annotation_schema }) });
|
| 482 |
if (!r0.ok) { this.toast('Schema rejected: ' + (await r0.json()).detail, 'error'); return; }
|
| 483 |
+
this.applyState(await r0.json());
|
| 484 |
} else if (this.pasteEditor.presetKey) {
|
| 485 |
const r0 = await fetch('/api/task/preset', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ key: this.pasteEditor.presetKey }) });
|
| 486 |
+
if (r0.ok) this.applyState(await r0.json());
|
| 487 |
}
|
| 488 |
|
| 489 |
// 2) Load the text
|
|
|
|
| 492 |
tokenizer: this.pasteEditor.tokenizer,
|
| 493 |
language: this.pasteEditor.language,
|
| 494 |
}) });
|
| 495 |
+
this.applyState(await r.json());
|
| 496 |
this.closeModal();
|
| 497 |
this.toast(`Loaded ${this.state.sentences.length} sentence(s). Task: ${this.state.schema?.task_name}.`, 'ok');
|
| 498 |
} finally { this.loading = false; }
|
|
|
|
| 500 |
|
| 501 |
async clearCorpus() {
|
| 502 |
const r = await fetch('/api/corpus/clear', { method: 'POST' });
|
| 503 |
+
this.applyState(await r.json());
|
| 504 |
},
|
| 505 |
|
| 506 |
async clearIcl() {
|
| 507 |
const r = await fetch('/api/icl/clear', { method: 'POST' });
|
| 508 |
+
this.applyState(await r.json());
|
| 509 |
},
|
| 510 |
|
| 511 |
// ----------- annotation -----------
|
|
|
|
| 518 |
try {
|
| 519 |
const r = await fetch('/api/annotate', { method: 'POST', headers: { 'Content-Type': 'application/json', ...this.keyHeaders() }, body: JSON.stringify({}) });
|
| 520 |
if (!r.ok) throw new Error((await r.json()).detail);
|
| 521 |
+
this.applyState(await r.json());
|
| 522 |
const dis = this.totalDisagreements;
|
| 523 |
this.toast(`Done. ${dis} disagreement${dis !== 1 ? 's' : ''} to review.`, dis > 0 ? 'warn' : 'ok');
|
| 524 |
} catch (e) {
|
|
|
|
| 533 |
try {
|
| 534 |
const r = await fetch('/api/annotate', { method: 'POST', headers: { 'Content-Type': 'application/json', ...this.keyHeaders() }, body: JSON.stringify({ sentence_idxs: [sidx] }) });
|
| 535 |
if (!r.ok) throw new Error((await r.json()).detail);
|
| 536 |
+
this.applyState(await r.json());
|
| 537 |
const s = this.state.sentences[sidx];
|
| 538 |
this.toast(`Sentence ${s.id}: ${s.n_disagreements} disagreement(s).`, s.n_disagreements > 0 ? 'warn' : 'ok');
|
| 539 |
} catch (e) { this.toast(e.message, 'error'); }
|
|
|
|
| 542 |
|
| 543 |
async addSentenceToIcl(sidx) {
|
| 544 |
const r = await fetch(`/api/sentence/${sidx}/add_to_icl`, { method: 'POST' });
|
| 545 |
+
this.applyState(await r.json());
|
| 546 |
this.toast(`Added to ICL pool (v${this.state.icl_pool.version}, ${this.state.icl_pool.size} entries).`, 'ok');
|
| 547 |
},
|
| 548 |
|
|
|
|
| 626 |
}) });
|
| 627 |
if (!r.ok) throw new Error((await r.json()).detail);
|
| 628 |
const sent = await r.json();
|
| 629 |
+
this.replaceSentence(this.editor.sidx, sent);
|
| 630 |
// re-open with the new token
|
| 631 |
this.openTokenEditor(this.editor.sidx, this.editor.tidx);
|
| 632 |
this.toast(`Re-asked ${this.modelShort(model)}.`, 'ok');
|
|
|
|
| 648 |
const r = await fetch(`/api/sentence/${sidx}/token/${tidx}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ token: this.editor.tok }) });
|
| 649 |
if (!r.ok) { this.toast('Save failed.', 'error'); return; }
|
| 650 |
const sent = await r.json();
|
| 651 |
+
this.replaceSentence(sidx, sent);
|
| 652 |
|
| 653 |
let propagatedCount = 0;
|
| 654 |
if (wantPropagate) {
|
|
|
|
| 664 |
});
|
| 665 |
if (r2.ok) {
|
| 666 |
const j = await r2.json();
|
| 667 |
+
this.applyState(j.state);
|
| 668 |
propagatedCount = (j.affected || []).length;
|
| 669 |
}
|
| 670 |
} catch (e) {
|
|
|
|
| 735 |
const r = await fetch(`/api/sentence/${s}/bulk`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({
|
| 736 |
token_idxs: idxs, field: this.bulkEditor.field, value: this.bulkEditor.value,
|
| 737 |
}) });
|
| 738 |
+
if (r.ok) this.replaceSentence(Number(s), await r.json());
|
| 739 |
}
|
| 740 |
this.clearSelection();
|
| 741 |
this.closeModal();
|