ianalin123 commited on
Commit
d2552c7
·
2 Parent(s): 608285d 85a3e59

Merge branch 'main' of https://huggingface.co/spaces/openenv-community/optigami

Browse files
.dockerignore ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .git
2
+ .DS_Store
3
+ __pycache__
4
+ *.pyc
5
+ *.pyo
6
+ .pytest_cache
7
+ .claude
8
+ node_modules
9
+ build
10
+ research
11
+ docs
12
+ plans
13
+ RESEARCH_NOTES.md
14
+ trainer
15
+ train.py
16
+ sim
17
+ viz
18
+ planner
.gitignore CHANGED
@@ -8,9 +8,6 @@
8
  # testing
9
  /coverage
10
 
11
- # production
12
- /build
13
-
14
  # misc
15
  .DS_Store
16
  .env.local
 
8
  # testing
9
  /coverage
10
 
 
 
 
11
  # misc
12
  .DS_Store
13
  .env.local
Dockerfile CHANGED
@@ -2,7 +2,7 @@ FROM node:20-alpine AS web-builder
2
 
3
  WORKDIR /web
4
  COPY package*.json ./
5
- RUN npm install --no-audit --no-fund
6
  COPY public ./public
7
  COPY src ./src
8
  RUN npm run build
@@ -10,12 +10,18 @@ RUN npm run build
10
  FROM ghcr.io/meta-pytorch/openenv-base:latest
11
 
12
  WORKDIR /app
13
- COPY . /app
14
- COPY --from=web-builder /web/build /app/build
15
 
 
 
16
  RUN pip install --no-cache-dir -r requirements.txt \
17
  && pip install --no-cache-dir "openenv-core[core]>=0.2.1"
18
 
19
- ENV ENABLE_WEB_INTERFACE=false
 
 
 
 
 
 
20
 
21
  CMD ["uvicorn", "openenv_server.app:app", "--host", "0.0.0.0", "--port", "8000"]
 
2
 
3
  WORKDIR /web
4
  COPY package*.json ./
5
+ RUN npm ci --no-audit --no-fund
6
  COPY public ./public
7
  COPY src ./src
8
  RUN npm run build
 
10
  FROM ghcr.io/meta-pytorch/openenv-base:latest
11
 
12
  WORKDIR /app
 
 
13
 
14
+ # Install Python deps first for better layer caching
15
+ COPY requirements.txt ./
16
  RUN pip install --no-cache-dir -r requirements.txt \
17
  && pip install --no-cache-dir "openenv-core[core]>=0.2.1"
18
 
19
+ # Copy application source
20
+ COPY . /app
21
+
22
+ # Overlay the compiled React frontend
23
+ COPY --from=web-builder /web/build /app/build
24
+
25
+ EXPOSE 8000
26
 
27
  CMD ["uvicorn", "openenv_server.app:app", "--host", "0.0.0.0", "--port", "8000"]
build/asset-manifest.json ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "files": {
3
+ "main.css": "/static/css/main.edb517bf.css",
4
+ "main.js": "/static/js/main.7e6cf91b.js",
5
+ "index.html": "/index.html",
6
+ "main.edb517bf.css.map": "/static/css/main.edb517bf.css.map",
7
+ "main.7e6cf91b.js.map": "/static/js/main.7e6cf91b.js.map"
8
+ },
9
+ "entrypoints": [
10
+ "static/css/main.edb517bf.css",
11
+ "static/js/main.7e6cf91b.js"
12
+ ]
13
+ }
build/favicon.ico ADDED
build/index.html ADDED
@@ -0,0 +1 @@
 
 
1
+ <!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>React App</title><script defer="defer" src="/static/js/main.7e6cf91b.js"></script><link href="/static/css/main.edb517bf.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
build/logo192.png ADDED
build/logo512.png ADDED
build/manifest.json ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "short_name": "React App",
3
+ "name": "Create React App Sample",
4
+ "icons": [
5
+ {
6
+ "src": "favicon.ico",
7
+ "sizes": "64x64 32x32 24x24 16x16",
8
+ "type": "image/x-icon"
9
+ },
10
+ {
11
+ "src": "logo192.png",
12
+ "type": "image/png",
13
+ "sizes": "192x192"
14
+ },
15
+ {
16
+ "src": "logo512.png",
17
+ "type": "image/png",
18
+ "sizes": "512x512"
19
+ }
20
+ ],
21
+ "start_url": ".",
22
+ "display": "standalone",
23
+ "theme_color": "#000000",
24
+ "background_color": "#ffffff"
25
+ }
build/robots.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ # https://www.robotstxt.org/robotstxt.html
2
+ User-agent: *
3
+ Disallow:
build/static/css/main.edb517bf.css ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ @import url(https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;700&family=IBM+Plex+Mono:wght@300;400;500&display=swap);*,:after,:before{box-sizing:border-box;margin:0;padding:0}body{-webkit-font-smoothing:antialiased;background:#0d0d14;color:#f8fafc;font-family:IBM Plex Mono,monospace;font-size:13px;line-height:1.5;overflow-x:hidden}::-webkit-scrollbar{height:4px;width:4px}::-webkit-scrollbar-track{background:#0d0d14}::-webkit-scrollbar-thumb{background:#2a2a3a}::-webkit-scrollbar-thumb:hover{background:#3a3a5a}:root{--bg:#0d0d14;--surface:#13131d;--surface-2:#1a1a2e;--paper-white:#fafaf5;--paper-edge:#2a2a3a;--mountain:#f59e0b;--valley:#38bdf8;--target-ghost:#7c3aed33;--target-ghost-stroke:#7c3aed73;--validity:#22d3ee;--progress:#22c55e;--economy:#a78bfa;--text-primary:#f8fafc;--text-dim:#64748b;--border:#2a2a3a;--border-bright:#3a3a5a;--font-display:"JetBrains Mono",monospace;--font-mono:"IBM Plex Mono",monospace}.app{background:#0d0d14;background:var(--bg);display:flex;flex-direction:column;height:100vh;overflow:hidden}.app-header{align-items:center;background:#13131d;background:var(--surface);border-bottom:1px solid #2a2a3a;border-bottom:1px solid var(--border);display:flex;flex-shrink:0;gap:24px;height:48px;padding:0 20px;z-index:10}.app-title{color:#f8fafc;color:var(--text-primary);font-family:JetBrains Mono,monospace;font-family:var(--font-display);font-size:14px;font-weight:700;letter-spacing:.12em;white-space:nowrap}.app-title .title-accent{color:#f59e0b;color:var(--mountain)}.header-sep{background:#2a2a3a;background:var(--border);flex-shrink:0;height:24px;width:1px}.header-right{gap:16px;margin-left:auto}.api-status,.header-right{align-items:center;display:flex}.api-status{font-family:JetBrains Mono,monospace;font-family:var(--font-display);font-size:11px;gap:6px;letter-spacing:.08em}.api-status-dot{background:#64748b;background:var(--text-dim);border-radius:50%;height:6px;width:6px}.api-status-dot.ok{background:#22c55e;background:var(--progress);box-shadow:0 0 6px #22c55e;box-shadow:0 0 6px var(--progress)}.api-status-dot.err{background:#ef4444;box-shadow:0 0 6px #ef4444}.app-body{display:grid;flex:1 1;grid-template-columns:1fr 280px;overflow:hidden}.app-left{border-right:1px solid #2a2a3a;border-right:1px solid var(--border)}.app-left,.app-right{display:flex;flex-direction:column;overflow:hidden}.app-right{background:#13131d;background:var(--surface)}.canvas-row{border-bottom:1px solid #2a2a3a;border-bottom:1px solid var(--border);display:flex;flex-shrink:0;gap:0;overflow-x:auto;padding:16px}.canvas-wrap{display:flex;flex:1 1;flex-direction:column;gap:8px;min-width:280px}.canvas-wrap+.canvas-wrap{margin-left:16px}.canvas-label{color:#64748b;color:var(--text-dim);font-family:JetBrains Mono,monospace;font-family:var(--font-display);font-size:10px;font-weight:500;letter-spacing:.14em;text-transform:uppercase}.canvas-svg{background:#fafaf5;background:var(--paper-white);display:block}.canvas-3d{background:linear-gradient(180deg,#1a1a2e,#0f101a);border:1px solid #2a2a3a;border:1px solid var(--border);display:block}.canvas-label-row{align-items:center;display:flex;gap:10px;justify-content:space-between}.fold-mode-toggle{background:#13131d;background:var(--surface);border:1px solid #2a2a3a;border:1px solid var(--border);display:inline-flex}.fold-mode-btn{background:#0000;border:none;color:#64748b;color:var(--text-dim);cursor:pointer;font-family:JetBrains Mono,monospace;font-family:var(--font-display);font-size:9px;letter-spacing:.08em;padding:3px 7px}.fold-mode-btn+.fold-mode-btn{border-left:1px solid #2a2a3a;border-left:1px solid var(--border)}.fold-mode-btn.active{background:#1f2538;color:#f8fafc;color:var(--text-primary)}.step-feed-section{display:flex;flex:1 1;flex-direction:column;overflow:hidden}.section-header{border-bottom:1px solid #2a2a3a;border-bottom:1px solid var(--border);color:#64748b;color:var(--text-dim);flex-shrink:0;font-family:JetBrains Mono,monospace;font-family:var(--font-display);font-size:10px;font-weight:500;letter-spacing:.14em;padding:8px 16px;text-transform:uppercase}.step-feed{flex:1 1;overflow-y:auto;padding:4px 0}.step-entry{border-bottom:1px solid #2a2a3a;border-bottom:1px solid var(--border);cursor:default;display:flex;flex-direction:column;gap:2px;padding:8px 16px;transition:background .1s}.step-entry:hover{background:#13131d;background:var(--surface)}.step-entry.active{background:#1a1a2e;background:var(--surface-2);border-left:2px solid #38bdf8;border-left:2px solid var(--valley);padding-left:14px}.step-entry-top{align-items:center;display:flex;gap:8px}.step-num{color:#64748b;color:var(--text-dim);flex-shrink:0;font-family:JetBrains Mono,monospace;font-family:var(--font-display);font-size:10px;font-weight:700;width:24px}.step-instruction{color:#f8fafc;color:var(--text-primary);flex:1 1;font-size:12px}.assign-badge{flex-shrink:0;font-family:JetBrains Mono,monospace;font-family:var(--font-display);font-size:10px;font-weight:700;line-height:1.4;padding:1px 5px}.assign-badge.M{background:#f59e0b;background:var(--mountain);color:#0d0d14}.assign-badge.V{background:#38bdf8;background:var(--valley);color:#0d0d14}.assign-badge.B{background:#3a3a5a;background:var(--border-bright)}.assign-badge.B,.step-reward-delta{color:#64748b;color:var(--text-dim)}.step-reward-delta{font-size:11px;padding-left:32px}.step-reward-delta .delta-positive{color:#22c55e;color:var(--progress)}.step-reward-delta .delta-negative{color:#ef4444}.reward-panel{border-bottom:1px solid #2a2a3a;border-bottom:1px solid var(--border);flex-shrink:0;padding:12px 16px}.reward-row{align-items:center;display:flex;gap:8px;margin-bottom:6px}.reward-row:last-child{margin-bottom:0}.reward-label{color:#64748b;color:var(--text-dim);flex-shrink:0;font-family:JetBrains Mono,monospace;font-family:var(--font-display);font-size:10px;font-weight:500;letter-spacing:.06em;text-transform:uppercase;width:72px}.reward-track{background:#0d0d14;background:var(--bg);border:1px solid #2a2a3a;border:1px solid var(--border);flex:1 1;height:8px;overflow:hidden}.reward-bar{height:100%;transition:width .4s ease}.reward-value{color:#f8fafc;color:var(--text-primary);flex-shrink:0;font-family:JetBrains Mono,monospace;font-family:var(--font-display);font-size:11px;font-weight:500;text-align:right;width:36px}.reward-value.dim{color:#64748b;color:var(--text-dim)}.reward-divider{background:#2a2a3a;background:var(--border);height:1px;margin:6px 0}.info-badges{display:flex;flex-direction:column;gap:8px;padding:12px 16px}.info-row{align-items:center;display:flex;gap:8px;justify-content:space-between}.info-key{color:#64748b;color:var(--text-dim);font-size:10px;font-weight:500;letter-spacing:.06em;text-transform:uppercase}.info-key,.info-val{font-family:JetBrains Mono,monospace;font-family:var(--font-display)}.info-val{color:#f8fafc;color:var(--text-primary);font-size:11px;font-weight:700}.info-val.bool-true{color:#22c55e;color:var(--progress)}.info-val.bool-false{color:#ef4444}.info-val.dim{color:#64748b;color:var(--text-dim)}.target-selector{align-items:center;display:flex;gap:8px}.target-selector-label{color:#64748b;color:var(--text-dim);font-size:10px;font-weight:500;letter-spacing:.1em;text-transform:uppercase;white-space:nowrap}.target-select,.target-selector-label{font-family:JetBrains Mono,monospace;font-family:var(--font-display)}.target-select{background:#1a1a2e;background:var(--surface-2);border:1px solid #3a3a5a;border:1px solid var(--border-bright);color:#f8fafc;color:var(--text-primary);cursor:pointer;font-size:11px;min-width:180px;outline:none;padding:4px 8px}.target-select:focus{border-color:#38bdf8;border-color:var(--valley)}optgroup{background:#13131d;background:var(--surface);color:#64748b;color:var(--text-dim);font-size:10px}optgroup,option{font-family:JetBrains Mono,monospace;font-family:var(--font-display)}option{background:#1a1a2e;background:var(--surface-2);color:#f8fafc;color:var(--text-primary)}.player-controls{align-items:center;display:flex;flex-shrink:0;gap:6px}.ctrl-btn{background:#1a1a2e;background:var(--surface-2);border:1px solid #3a3a5a;border:1px solid var(--border-bright);color:#f8fafc;color:var(--text-primary);cursor:pointer;font-family:JetBrains Mono,monospace;font-family:var(--font-display);font-size:11px;font-weight:500;letter-spacing:.04em;line-height:1.4;padding:4px 10px;transition:background .1s,border-color .1s;white-space:nowrap}.ctrl-btn:hover:not(:disabled){background:#13131d;background:var(--surface);border-color:#64748b;border-color:var(--text-dim)}.ctrl-btn:disabled{cursor:not-allowed;opacity:.35}.ctrl-btn.play{border-color:#38bdf8;border-color:var(--valley);color:#38bdf8;color:var(--valley)}.ctrl-btn.play:hover:not(:disabled){background:#38bdf81a}.ctrl-step-display{border:1px solid #2a2a3a;border:1px solid var(--border);color:#64748b;color:var(--text-dim);font-family:JetBrains Mono,monospace;font-family:var(--font-display);font-size:11px;min-width:72px;padding:4px 8px;text-align:center;white-space:nowrap}.app-overlay,.ctrl-step-display{background:#0d0d14;background:var(--bg)}.app-overlay{inset:0;justify-content:center;position:fixed;z-index:100}.app-overlay,.overlay-message{align-items:center;display:flex}.overlay-message{color:#64748b;color:var(--text-dim);font-family:JetBrains Mono,monospace;font-family:var(--font-display);font-size:13px;gap:12px;letter-spacing:.1em}.pulse-dot{animation:pulse 1.2s ease-in-out infinite;background:#38bdf8;background:var(--valley);border-radius:50%;height:8px;width:8px}@keyframes pulse{0%,to{opacity:.2;transform:scale(.8)}50%{opacity:1;transform:scale(1)}}.episode-loading{align-items:center;color:#64748b;color:var(--text-dim);display:flex;font-family:JetBrains Mono,monospace;font-family:var(--font-display);font-size:11px;gap:8px;justify-content:center;letter-spacing:.08em;padding:12px 16px}
2
+ /*# sourceMappingURL=main.edb517bf.css.map*/
build/static/css/main.edb517bf.css.map ADDED
@@ -0,0 +1 @@
 
 
1
+ {"version":3,"file":"static/css/main.edb517bf.css","mappings":"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","sources":["index.css","App.css"],"sourcesContent":["@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;700&family=IBM+Plex+Mono:wght@300;400;500&display=swap');\n\n*, *::before, *::after {\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n}\n\nbody {\n background: #0d0d14;\n color: #f8fafc;\n font-family: 'IBM Plex Mono', monospace;\n font-size: 13px;\n line-height: 1.5;\n -webkit-font-smoothing: antialiased;\n overflow-x: hidden;\n}\n\n::-webkit-scrollbar {\n width: 4px;\n height: 4px;\n}\n\n::-webkit-scrollbar-track {\n background: #0d0d14;\n}\n\n::-webkit-scrollbar-thumb {\n background: #2a2a3a;\n}\n\n::-webkit-scrollbar-thumb:hover {\n background: #3a3a5a;\n}\n",":root {\n --bg: #0d0d14;\n --surface: #13131d;\n --surface-2: #1a1a2e;\n --paper-white: #fafaf5;\n --paper-edge: #2a2a3a;\n --mountain: #f59e0b;\n --valley: #38bdf8;\n --target-ghost: rgba(124, 58, 237, 0.20);\n --target-ghost-stroke: rgba(124, 58, 237, 0.45);\n --validity: #22d3ee;\n --progress: #22c55e;\n --economy: #a78bfa;\n --text-primary: #f8fafc;\n --text-dim: #64748b;\n --border: #2a2a3a;\n --border-bright: #3a3a5a;\n --font-display: 'JetBrains Mono', monospace;\n --font-mono: 'IBM Plex Mono', monospace;\n}\n\n.app {\n display: flex;\n flex-direction: column;\n height: 100vh;\n background: var(--bg);\n overflow: hidden;\n}\n\n/* ─── HEADER ─── */\n.app-header {\n display: flex;\n align-items: center;\n gap: 24px;\n padding: 0 20px;\n height: 48px;\n border-bottom: 1px solid var(--border);\n background: var(--surface);\n flex-shrink: 0;\n z-index: 10;\n}\n\n.app-title {\n font-family: var(--font-display);\n font-size: 14px;\n font-weight: 700;\n letter-spacing: 0.12em;\n color: var(--text-primary);\n white-space: nowrap;\n}\n\n.app-title .title-accent {\n color: var(--mountain);\n}\n\n.header-sep {\n width: 1px;\n height: 24px;\n background: var(--border);\n flex-shrink: 0;\n}\n\n.header-right {\n display: flex;\n align-items: center;\n gap: 16px;\n margin-left: auto;\n}\n\n.api-status {\n font-size: 11px;\n font-family: var(--font-display);\n letter-spacing: 0.08em;\n display: flex;\n align-items: center;\n gap: 6px;\n}\n\n.api-status-dot {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: var(--text-dim);\n}\n\n.api-status-dot.ok {\n background: var(--progress);\n box-shadow: 0 0 6px var(--progress);\n}\n\n.api-status-dot.err {\n background: #ef4444;\n box-shadow: 0 0 6px #ef4444;\n}\n\n/* ─── MAIN LAYOUT ─── */\n.app-body {\n display: grid;\n grid-template-columns: 1fr 280px;\n flex: 1;\n overflow: hidden;\n}\n\n.app-left {\n display: flex;\n flex-direction: column;\n overflow: hidden;\n border-right: 1px solid var(--border);\n}\n\n.app-right {\n display: flex;\n flex-direction: column;\n overflow: hidden;\n background: var(--surface);\n}\n\n/* ─── CANVAS ROW ─── */\n.canvas-row {\n display: flex;\n gap: 0;\n padding: 16px;\n flex-shrink: 0;\n border-bottom: 1px solid var(--border);\n overflow-x: auto;\n}\n\n.canvas-wrap {\n display: flex;\n flex-direction: column;\n gap: 8px;\n flex: 1;\n min-width: 280px;\n}\n\n.canvas-wrap + .canvas-wrap {\n margin-left: 16px;\n}\n\n.canvas-label {\n font-family: var(--font-display);\n font-size: 10px;\n font-weight: 500;\n letter-spacing: 0.14em;\n color: var(--text-dim);\n text-transform: uppercase;\n}\n\n.canvas-svg {\n display: block;\n background: var(--paper-white);\n}\n\n.canvas-3d {\n display: block;\n background: linear-gradient(180deg, #1a1a2e 0%, #0f101a 100%);\n border: 1px solid var(--border);\n}\n\n.canvas-label-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 10px;\n}\n\n.fold-mode-toggle {\n display: inline-flex;\n border: 1px solid var(--border);\n background: var(--surface);\n}\n\n.fold-mode-btn {\n border: none;\n background: transparent;\n color: var(--text-dim);\n font-family: var(--font-display);\n font-size: 9px;\n letter-spacing: 0.08em;\n padding: 3px 7px;\n cursor: pointer;\n}\n\n.fold-mode-btn + .fold-mode-btn {\n border-left: 1px solid var(--border);\n}\n\n.fold-mode-btn.active {\n color: var(--text-primary);\n background: #1f2538;\n}\n\n/* ─── STEP FEED ─── */\n.step-feed-section {\n flex: 1;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n}\n\n.section-header {\n font-family: var(--font-display);\n font-size: 10px;\n font-weight: 500;\n letter-spacing: 0.14em;\n color: var(--text-dim);\n text-transform: uppercase;\n padding: 8px 16px;\n border-bottom: 1px solid var(--border);\n flex-shrink: 0;\n}\n\n.step-feed {\n overflow-y: auto;\n flex: 1;\n padding: 4px 0;\n}\n\n.step-entry {\n display: flex;\n flex-direction: column;\n gap: 2px;\n padding: 8px 16px;\n border-bottom: 1px solid var(--border);\n cursor: default;\n transition: background 0.1s;\n}\n\n.step-entry:hover {\n background: var(--surface);\n}\n\n.step-entry.active {\n background: var(--surface-2);\n border-left: 2px solid var(--valley);\n padding-left: 14px;\n}\n\n.step-entry-top {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.step-num {\n font-family: var(--font-display);\n font-size: 10px;\n font-weight: 700;\n color: var(--text-dim);\n width: 24px;\n flex-shrink: 0;\n}\n\n.step-instruction {\n font-size: 12px;\n color: var(--text-primary);\n flex: 1;\n}\n\n.assign-badge {\n font-family: var(--font-display);\n font-size: 10px;\n font-weight: 700;\n padding: 1px 5px;\n line-height: 1.4;\n flex-shrink: 0;\n}\n\n.assign-badge.M {\n background: var(--mountain);\n color: #0d0d14;\n}\n\n.assign-badge.V {\n background: var(--valley);\n color: #0d0d14;\n}\n\n.assign-badge.B {\n background: var(--border-bright);\n color: var(--text-dim);\n}\n\n.step-reward-delta {\n font-size: 11px;\n color: var(--text-dim);\n padding-left: 32px;\n}\n\n.step-reward-delta .delta-positive {\n color: var(--progress);\n}\n\n.step-reward-delta .delta-negative {\n color: #ef4444;\n}\n\n/* ─── REWARD PANEL ─── */\n.reward-panel {\n padding: 12px 16px;\n border-bottom: 1px solid var(--border);\n flex-shrink: 0;\n}\n\n.reward-row {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-bottom: 6px;\n}\n\n.reward-row:last-child {\n margin-bottom: 0;\n}\n\n.reward-label {\n font-family: var(--font-display);\n font-size: 10px;\n font-weight: 500;\n letter-spacing: 0.06em;\n color: var(--text-dim);\n width: 72px;\n flex-shrink: 0;\n text-transform: uppercase;\n}\n\n.reward-track {\n flex: 1;\n height: 8px;\n background: var(--bg);\n border: 1px solid var(--border);\n overflow: hidden;\n}\n\n.reward-bar {\n height: 100%;\n transition: width 0.4s ease;\n}\n\n.reward-value {\n font-family: var(--font-display);\n font-size: 11px;\n font-weight: 500;\n color: var(--text-primary);\n width: 36px;\n text-align: right;\n flex-shrink: 0;\n}\n\n.reward-value.dim {\n color: var(--text-dim);\n}\n\n.reward-divider {\n height: 1px;\n background: var(--border);\n margin: 6px 0;\n}\n\n/* ─── INFO BADGES ─── */\n.info-badges {\n padding: 12px 16px;\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.info-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n}\n\n.info-key {\n font-family: var(--font-display);\n font-size: 10px;\n font-weight: 500;\n letter-spacing: 0.06em;\n color: var(--text-dim);\n text-transform: uppercase;\n}\n\n.info-val {\n font-family: var(--font-display);\n font-size: 11px;\n font-weight: 700;\n color: var(--text-primary);\n}\n\n.info-val.bool-true {\n color: var(--progress);\n}\n\n.info-val.bool-false {\n color: #ef4444;\n}\n\n.info-val.dim {\n color: var(--text-dim);\n}\n\n/* ─── TARGET SELECTOR ─── */\n.target-selector {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.target-selector-label {\n font-family: var(--font-display);\n font-size: 10px;\n font-weight: 500;\n letter-spacing: 0.10em;\n color: var(--text-dim);\n text-transform: uppercase;\n white-space: nowrap;\n}\n\n.target-select {\n background: var(--surface-2);\n border: 1px solid var(--border-bright);\n color: var(--text-primary);\n font-family: var(--font-display);\n font-size: 11px;\n padding: 4px 8px;\n outline: none;\n cursor: pointer;\n min-width: 180px;\n}\n\n.target-select:focus {\n border-color: var(--valley);\n}\n\noptgroup {\n background: var(--surface);\n color: var(--text-dim);\n font-family: var(--font-display);\n font-size: 10px;\n}\n\noption {\n background: var(--surface-2);\n color: var(--text-primary);\n font-family: var(--font-display);\n}\n\n/* ─── PLAYER CONTROLS ─── */\n.player-controls {\n display: flex;\n align-items: center;\n gap: 6px;\n flex-shrink: 0;\n}\n\n.ctrl-btn {\n background: var(--surface-2);\n border: 1px solid var(--border-bright);\n color: var(--text-primary);\n font-family: var(--font-display);\n font-size: 11px;\n font-weight: 500;\n padding: 4px 10px;\n cursor: pointer;\n white-space: nowrap;\n line-height: 1.4;\n letter-spacing: 0.04em;\n transition: background 0.1s, border-color 0.1s;\n}\n\n.ctrl-btn:hover:not(:disabled) {\n background: var(--surface);\n border-color: var(--text-dim);\n}\n\n.ctrl-btn:disabled {\n opacity: 0.35;\n cursor: not-allowed;\n}\n\n.ctrl-btn.play {\n border-color: var(--valley);\n color: var(--valley);\n}\n\n.ctrl-btn.play:hover:not(:disabled) {\n background: rgba(56, 189, 248, 0.1);\n}\n\n.ctrl-step-display {\n font-family: var(--font-display);\n font-size: 11px;\n color: var(--text-dim);\n padding: 4px 8px;\n border: 1px solid var(--border);\n background: var(--bg);\n white-space: nowrap;\n min-width: 72px;\n text-align: center;\n}\n\n/* ─── LOADING / ERROR ─── */\n.app-overlay {\n position: fixed;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--bg);\n z-index: 100;\n}\n\n.overlay-message {\n font-family: var(--font-display);\n font-size: 13px;\n letter-spacing: 0.1em;\n color: var(--text-dim);\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.pulse-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: var(--valley);\n animation: pulse 1.2s ease-in-out infinite;\n}\n\n@keyframes pulse {\n 0%, 100% { opacity: 0.2; transform: scale(0.8); }\n 50% { opacity: 1; transform: scale(1); }\n}\n\n/* ─── MISC ─── */\n.episode-loading {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n padding: 12px 16px;\n font-family: var(--font-display);\n font-size: 11px;\n color: var(--text-dim);\n letter-spacing: 0.08em;\n}\n"],"names":[],"sourceRoot":""}
build/static/js/main.7e6cf91b.js ADDED
The diff for this file is too large to render. See raw diff
 
build/static/js/main.7e6cf91b.js.LICENSE.txt ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * @license React
3
+ * react-dom-client.production.js
4
+ *
5
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
6
+ *
7
+ * This source code is licensed under the MIT license found in the
8
+ * LICENSE file in the root directory of this source tree.
9
+ */
10
+
11
+ /**
12
+ * @license React
13
+ * react-dom.production.js
14
+ *
15
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
16
+ *
17
+ * This source code is licensed under the MIT license found in the
18
+ * LICENSE file in the root directory of this source tree.
19
+ */
20
+
21
+ /**
22
+ * @license React
23
+ * react-jsx-runtime.production.js
24
+ *
25
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
26
+ *
27
+ * This source code is licensed under the MIT license found in the
28
+ * LICENSE file in the root directory of this source tree.
29
+ */
30
+
31
+ /**
32
+ * @license React
33
+ * react.production.js
34
+ *
35
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
36
+ *
37
+ * This source code is licensed under the MIT license found in the
38
+ * LICENSE file in the root directory of this source tree.
39
+ */
40
+
41
+ /**
42
+ * @license React
43
+ * scheduler.production.js
44
+ *
45
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
46
+ *
47
+ * This source code is licensed under the MIT license found in the
48
+ * LICENSE file in the root directory of this source tree.
49
+ */
build/static/js/main.7e6cf91b.js.map ADDED
The diff for this file is too large to render. See raw diff
 
openenv_server/app.py CHANGED
@@ -17,10 +17,130 @@ app = create_app(
17
  env_name="optigami",
18
  )
19
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  _BUILD_DIR = Path(__file__).resolve().parent.parent / "build"
21
 
22
  if _BUILD_DIR.exists():
23
- # Serve compiled React dashboard at "/" while preserving existing OpenEnv API routes.
24
  app.mount("/", StaticFiles(directory=str(_BUILD_DIR), html=True), name="renderer")
25
  else:
26
  @app.get("/", include_in_schema=False)
 
17
  env_name="optigami",
18
  )
19
 
20
+
21
+ # ---------------------------------------------------------------------------
22
+ # Demo routes required by the React frontend.
23
+ # These must be registered BEFORE the StaticFiles catch-all mount.
24
+ # ---------------------------------------------------------------------------
25
+
26
+ DEMO_COMPLETIONS: dict[str, str] = {
27
+ "half_horizontal": '<folds>[{"instruction": "Valley fold along horizontal center line", "from": [0, 0.5], "to": [1, 0.5], "assignment": "V"}]</folds>',
28
+ "half_vertical": '<folds>[{"instruction": "Mountain fold along vertical center line", "from": [0.5, 0], "to": [0.5, 1], "assignment": "M"}]</folds>',
29
+ "diagonal_main": '<folds>[{"instruction": "Valley fold along main diagonal", "from": [0, 0], "to": [1, 1], "assignment": "V"}]</folds>',
30
+ "diagonal_anti": '<folds>[{"instruction": "Mountain fold along anti-diagonal", "from": [1, 0], "to": [0, 1], "assignment": "M"}]</folds>',
31
+ "thirds_h": '<folds>[{"instruction": "Valley fold at one-third height", "from": [0, 0.333], "to": [1, 0.333], "assignment": "V"}, {"instruction": "Valley fold at two-thirds height", "from": [0, 0.667], "to": [1, 0.667], "assignment": "V"}]</folds>',
32
+ "thirds_v": '<folds>[{"instruction": "Mountain fold at one-third width", "from": [0.333, 0], "to": [0.333, 1], "assignment": "M"}, {"instruction": "Mountain fold at two-thirds width", "from": [0.667, 0], "to": [0.667, 1], "assignment": "M"}]</folds>',
33
+ "accordion_3h": '<folds>[{"instruction": "Valley fold at quarter height", "from": [0, 0.25], "to": [1, 0.25], "assignment": "V"}, {"instruction": "Mountain fold at half height", "from": [0, 0.5], "to": [1, 0.5], "assignment": "M"}, {"instruction": "Valley fold at three-quarter height", "from": [0, 0.75], "to": [1, 0.75], "assignment": "V"}]</folds>',
34
+ "accordion_4h": '<folds>[{"instruction": "Valley fold at 0.2", "from": [0, 0.2], "to": [1, 0.2], "assignment": "V"}, {"instruction": "Mountain fold at 0.4", "from": [0, 0.4], "to": [1, 0.4], "assignment": "M"}, {"instruction": "Valley fold at 0.6", "from": [0, 0.6], "to": [1, 0.6], "assignment": "V"}, {"instruction": "Mountain fold at 0.8", "from": [0, 0.8], "to": [1, 0.8], "assignment": "M"}]</folds>',
35
+ }
36
+
37
+
38
+ @app.get("/targets", include_in_schema=True)
39
+ def get_targets() -> dict:
40
+ """Return available target names and metadata for the frontend."""
41
+ from env.environment import OrigamiEnvironment
42
+
43
+ env = OrigamiEnvironment()
44
+ result: dict[str, dict] = {}
45
+ for name in env.available_targets():
46
+ t = env._targets[name]
47
+ result[name] = {
48
+ "name": name,
49
+ "level": t.get("level", 1),
50
+ "description": t.get("description", ""),
51
+ "n_creases": sum(1 for a in t["edges_assignment"] if a in ("M", "V")),
52
+ }
53
+ return result
54
+
55
+
56
+ @app.get("/episode/run", include_in_schema=True)
57
+ def run_episode(target: str = "half_horizontal", completion: str = "") -> dict:
58
+ """Run a fold-sequence episode and return step-by-step data."""
59
+ from env.environment import OrigamiEnvironment
60
+ from env.prompts import parse_fold_list, step_level_prompt
61
+ from env.rewards import compute_reward
62
+
63
+ env = OrigamiEnvironment(mode="step")
64
+ obs = env.reset(target_name=target)
65
+
66
+ if not completion:
67
+ return {"prompt": obs["prompt"], "steps": [], "target": env.target}
68
+
69
+ try:
70
+ folds = parse_fold_list(completion)
71
+ except ValueError as exc:
72
+ return {"error": str(exc), "steps": []}
73
+
74
+ steps: list[dict] = []
75
+ for i, fold in enumerate(folds):
76
+ result = env.paper.add_crease(fold["from"], fold["to"], fold["assignment"])
77
+ reward = compute_reward(env.paper, result, env.target)
78
+
79
+ paper_state = {
80
+ "vertices": {str(k): list(v) for k, v in env.paper.graph.vertices.items()},
81
+ "edges": [
82
+ {
83
+ "id": k,
84
+ "v1": list(env.paper.graph.vertices[v[0]]),
85
+ "v2": list(env.paper.graph.vertices[v[1]]),
86
+ "assignment": v[2],
87
+ }
88
+ for k, v in env.paper.graph.edges.items()
89
+ ],
90
+ "anchor_points": [list(p) for p in env.paper.anchor_points()],
91
+ }
92
+
93
+ step_prompt = step_level_prompt(
94
+ target=env.target,
95
+ paper_state=env.paper,
96
+ step=i + 1,
97
+ max_steps=env.max_steps,
98
+ last_reward=reward,
99
+ )
100
+
101
+ steps.append(
102
+ {
103
+ "step": i + 1,
104
+ "fold": {
105
+ "from_point": fold["from"],
106
+ "to_point": fold["to"],
107
+ "assignment": fold["assignment"],
108
+ "instruction": fold.get("instruction", ""),
109
+ },
110
+ "paper_state": paper_state,
111
+ "anchor_points": [list(p) for p in env.paper.anchor_points()],
112
+ "reward": reward,
113
+ "done": reward.get("completion", 0) > 0,
114
+ "info": env._info(),
115
+ "prompt": step_prompt,
116
+ }
117
+ )
118
+
119
+ if reward.get("completion", 0) > 0:
120
+ break
121
+
122
+ return {
123
+ "target_name": target,
124
+ "target": env.target,
125
+ "steps": steps,
126
+ "final_reward": steps[-1]["reward"] if steps else {},
127
+ }
128
+
129
+
130
+ @app.get("/episode/demo", include_in_schema=True)
131
+ def demo_episode(target: str = "half_horizontal") -> dict:
132
+ """Return a pre-solved demo episode for the given target."""
133
+ completion = DEMO_COMPLETIONS.get(target, DEMO_COMPLETIONS["half_horizontal"])
134
+ return run_episode(target=target, completion=completion)
135
+
136
+
137
+ # ---------------------------------------------------------------------------
138
+ # Static file serving — must come LAST so API routes take priority.
139
+ # ---------------------------------------------------------------------------
140
+
141
  _BUILD_DIR = Path(__file__).resolve().parent.parent / "build"
142
 
143
  if _BUILD_DIR.exists():
 
144
  app.mount("/", StaticFiles(directory=str(_BUILD_DIR), html=True), name="renderer")
145
  else:
146
  @app.get("/", include_in_schema=False)
package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
requirements.txt CHANGED
@@ -1,7 +1,5 @@
1
  shapely>=2.0.0
2
  numpy>=1.24.0
3
- scipy>=1.10.0
4
- matplotlib>=3.7.0
5
- pytest>=7.0.0
6
  fastapi>=0.100.0
7
  uvicorn>=0.23.0
 
 
1
  shapely>=2.0.0
2
  numpy>=1.24.0
 
 
 
3
  fastapi>=0.100.0
4
  uvicorn>=0.23.0
5
+ pydantic>=2.0.0
src/App.js CHANGED
@@ -8,7 +8,7 @@ import TargetSelector from './components/TargetSelector';
8
  import PlayerControls from './components/PlayerControls';
9
  import Fold3DCanvas from './components/Fold3DCanvas';
10
 
11
- const API_BASE = 'http://localhost:8000';
12
 
13
  function App() {
14
  const [targets, setTargets] = useState({});
 
8
  import PlayerControls from './components/PlayerControls';
9
  import Fold3DCanvas from './components/Fold3DCanvas';
10
 
11
+ const API_BASE = '';
12
 
13
  function App() {
14
  const [targets, setTargets] = useState({});