dhuser commited on
Commit
c1ce9a3
·
1 Parent(s): e46b0c7

Update automatic refresh

Browse files
Files changed (1) hide show
  1. 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.state = data;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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.state = await r.json();
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.state = await r.json();
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.state = await r.json();
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.state = await r.json();
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.state = await r0.json();
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.state = await r0.json();
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.state = await r.json();
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.state = await r.json();
487
  },
488
 
489
  async clearIcl() {
490
  const r = await fetch('/api/icl/clear', { method: 'POST' });
491
- this.state = await r.json();
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.state = await r.json();
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.state = await r.json();
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.state = await r.json();
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.state.sentences[this.editor.sidx] = sent;
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.state.sentences[sidx] = sent;
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.state = j.state;
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.state.sentences[Number(s)] = await r.json();
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();