File size: 2,924 Bytes
cd470c1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
import { LabAPI } from './modules/api.js';
import { LatticeRenderer } from './modules/renderer.js';
import { TelemetryUI } from './modules/ui.js';

class DiscoveryLab {
  constructor() {
    this.api = new LabAPI(window.location.host);
    this.ui  = new TelemetryUI();
    this.renderer = new LatticeRenderer(
      document.getElementById('lattice-canvas'),
      { 'A': '#ef4444', 'B': '#3b82f6', 'C': '#f59e0b', 'P': '#8b5cf6' }
    );

    this.state = {
      selectedAtom: 'A',
      bestReward: 0
    };

    this.init();
  }

  async init() {
    this.bindEvents();
    try {
      await this.api.connect('/ws');
      this.ui.log('Lab connection established', 'ok');
      this.reset();
    } catch (e) {
      this.ui.log('Failed to connect to lab engine', 'err');
    }

    this.api.onMessage((msg) => this.handleMessage(msg));
  }

  bindEvents() {
    document.getElementById('btn-reset').onclick = () => this.reset();
    
    // Canvas interaction
    this.renderer.canvas.onclick = (e) => this.handleCanvasClick(e);
    this.renderer.canvas.oncontextmenu = (e) => {
      e.preventDefault();
      this.handleCanvasClick(e, true);
    };

    // Atom selection
    document.querySelectorAll('.atom-btn').forEach(btn => {
      btn.onclick = () => {
        document.querySelectorAll('.atom-btn').forEach(b => b.classList.remove('selected'));
        btn.classList.add('selected');
        this.state.selectedAtom = btn.dataset.atom;
      };
    });
  }

  handleMessage(msg) {
    if (msg.type === 'observation') {
      const { observation, reward } = msg.data;
      if (reward > this.state.bestReward) this.state.bestReward = reward;
      
      this.renderer.draw(observation.grid);
      this.ui.updateKPIs(observation, reward, this.state.bestReward);
      this.ui.updatePropertyBars(observation);
    }
  }

  reset() {
    const scenario = document.getElementById('sel-scenario').value;
    const difficulty = document.getElementById('sel-difficulty').value;
    this.api.send('reset', { scenario_name: scenario, difficulty });
    this.state.bestReward = 0;
    this.ui.log(`Workspace re-initialized: ${scenario || 'random'}`, 'info');
  }

  handleCanvasClick(e, isRightClick = false) {
    const rect = this.renderer.canvas.getBoundingClientRect();
    const x = e.clientX - rect.left;
    const y = e.clientY - rect.top;
    const cellSize = this.renderer.canvas.width / 8;
    const col = Math.floor(x / cellSize);
    const row = Math.floor(y / cellSize);

    const action = isRightClick ? 'remove' : 'place';
    this.api.send('step', {
      action_type: action,
      row, col,
      atom: this.state.selectedAtom
    });
  }
}

// Start lab
window.addEventListener('load', () => {
  window.lab = new DiscoveryLab();
  document.getElementById('loading-overlay').classList.add('hidden');
});