added easyOCR

This commit is contained in:
Boki 2026-02-12 01:04:19 -05:00
parent 37d6678577
commit 9f208b0606
27 changed files with 1780 additions and 112 deletions

View file

@ -129,6 +129,34 @@
.link-item button { padding: 4px 12px; font-size: 12px; }
.link-item.inactive { opacity: 0.5; }
.mode-badge {
display: inline-block;
font-size: 10px;
padding: 2px 8px;
border-radius: 4px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
cursor: pointer;
user-select: none;
transition: background 0.15s;
}
.mode-badge.live { background: #1f6feb; color: #fff; }
.mode-badge.live:hover { background: #388bfd; }
.mode-badge.scrap { background: #9e6a03; color: #fff; }
.mode-badge.scrap:hover { background: #d29922; }
.mode-select {
padding: 6px 10px;
background: #0d1117;
border: 1px solid #30363d;
border-radius: 6px;
color: #e6edf3;
font-size: 13px;
outline: none;
}
.mode-select:focus { border-color: #58a6ff; }
/* Toggle switch */
.toggle { position: relative; width: 36px; height: 20px; cursor: pointer; flex-shrink: 0; }
.toggle input { opacity: 0; width: 0; height: 0; }
@ -316,6 +344,41 @@
}
.detect-badge.ok { background: #238636; color: #fff; }
.detect-badge.fallback { background: #9e6a03; color: #fff; }
/* Inventory grid */
.inv-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
}
.inv-free {
font-size: 12px;
color: #8b949e;
font-weight: 600;
}
.inventory-grid {
display: grid;
grid-template-columns: repeat(12, 1fr);
gap: 2px;
background: #161b22;
border: 1px solid #30363d;
border-radius: 8px;
padding: 10px;
}
.inv-cell {
aspect-ratio: 1;
border-radius: 3px;
background: #0d1117;
min-width: 0;
}
.inv-cell.occupied {
background: #238636;
}
.inv-cell.item-top { border-top: 2px solid #3fb950; }
.inv-cell.item-bottom { border-bottom: 2px solid #3fb950; }
.inv-cell.item-left { border-left: 2px solid #3fb950; }
.inv-cell.item-right { border-right: 2px solid #3fb950; }
</style>
</head>
<body>
@ -359,11 +422,25 @@
<button class="warning" id="pauseBtn" onclick="togglePause()">Pause</button>
</div>
<div class="section">
<div class="inv-header">
<div class="section-title" style="margin-bottom:0">Inventory</div>
<span class="inv-free" id="invFreeCount"></span>
</div>
<div class="inventory-grid" id="inventoryGrid">
<div class="empty-state" style="grid-column:1/-1">No active scrap session</div>
</div>
</div>
<div class="section">
<div class="section-title">Trade Links</div>
<div class="add-link">
<input type="text" id="nameInput" placeholder="Name (optional)" style="max-width:180px" />
<input type="text" id="urlInput" placeholder="Paste trade URL..." />
<select id="modeInput" class="mode-select" style="width:90px">
<option value="live">Live</option>
<option value="scrap">Scrap</option>
</select>
<button class="primary" onclick="addLink()">Add</button>
</div>
<div class="links-list" id="linksList">
@ -375,6 +452,10 @@
<div class="section-title">Debug Tools</div>
<div class="debug-panel">
<div class="debug-row">
<select id="ocrEngineSelect" onchange="setOcrEngine(this.value)" style="padding:6px 10px;background:#0d1117;border:1px solid #30363d;border-radius:6px;color:#e6edf3;font-size:13px">
<option value="tesseract">Tesseract</option>
<option value="easyocr">EasyOCR</option>
</select>
<button onclick="debugScreenshot()">Screenshot</button>
<button onclick="debugOcr()">OCR Screen</button>
<button onclick="debugHideout()">Go Hideout</button>
@ -382,6 +463,7 @@
<div class="debug-row">
<button onclick="debugFindAndClick('ANGE')">ANGE</button>
<button onclick="debugFindAndClick('STASH')">STASH</button>
<button onclick="debugFindAndClick('SALVAGE BENCH', true)">SALVAGE</button>
</div>
<div class="debug-row">
<button onclick="debugAngeOption('Currency')">Currency Exchange</button>
@ -512,6 +594,9 @@
// Settings (populate once on first status)
if (status.settings) populateSettings(status.settings);
// Inventory grid
renderInventory();
// Active links count
document.getElementById('linksValue').textContent = status.links.filter(l => l.active).length;
@ -527,6 +612,7 @@
<input type="checkbox" ${link.active ? 'checked' : ''} onchange="toggleLink('${esc(link.id)}', this.checked)" />
<span class="slider"></span>
</label>
<span class="mode-badge ${link.mode || 'live'}" onclick="cycleMode('${esc(link.id)}', '${link.mode || 'live'}')" title="Click to change mode">${esc(link.mode || 'live')}</span>
<div class="link-info">
<div class="link-name" contenteditable="true" spellcheck="false"
onblur="renameLink('${esc(link.id)}', this.textContent)"
@ -542,6 +628,41 @@
}
}
function renderInventory() {
const container = document.getElementById('inventoryGrid');
const freeLabel = document.getElementById('invFreeCount');
if (!status.inventory) {
container.innerHTML = '<div class="empty-state" style="grid-column:1/-1">No active scrap session</div>';
freeLabel.textContent = '';
return;
}
const { grid, items, free } = status.inventory;
freeLabel.textContent = `${free}/60 free`;
let html = '';
for (let r = 0; r < 5; r++) {
for (let c = 0; c < 12; c++) {
const occupied = grid[r] && grid[r][c] ? 'occupied' : '';
html += `<div class="inv-cell ${occupied}" data-r="${r}" data-c="${c}"></div>`;
}
}
container.innerHTML = html;
for (const item of items) {
for (let r = item.row; r < item.row + item.h; r++) {
for (let c = item.col; c < item.col + item.w; c++) {
const cell = container.querySelector(`[data-r="${r}"][data-c="${c}"]`);
if (cell) {
if (r === item.row) cell.classList.add('item-top');
if (r === item.row + item.h - 1) cell.classList.add('item-bottom');
if (c === item.col) cell.classList.add('item-left');
if (c === item.col + item.w - 1) cell.classList.add('item-right');
}
}
}
}
}
function addLog(data) {
const panel = document.getElementById('logPanel');
const line = document.createElement('div');
@ -561,12 +682,13 @@
async function addLink() {
const urlEl = document.getElementById('urlInput');
const nameEl = document.getElementById('nameInput');
const modeEl = document.getElementById('modeInput');
const url = urlEl.value.trim();
if (!url) return;
await fetch('/api/links', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url, name: nameEl.value.trim() }),
body: JSON.stringify({ url, name: nameEl.value.trim(), mode: modeEl.value }),
});
urlEl.value = '';
nameEl.value = '';
@ -596,6 +718,15 @@
}, 300);
}
async function cycleMode(id, currentMode) {
const newMode = currentMode === 'live' ? 'scrap' : 'live';
await fetch('/api/links/' + encodeURIComponent(id) + '/mode', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ mode: newMode }),
});
}
function esc(s) {
const d = document.createElement('div');
d.textContent = s;
@ -795,14 +926,14 @@
}
}
async function debugFindAndClick(directText) {
async function debugFindAndClick(directText, fuzzy) {
const text = directText || document.getElementById('debugTextInput').value.trim();
if (!text) return;
showDebugResult(`Finding and clicking "${text}"...`);
showDebugResult(`Finding and clicking "${text}"${fuzzy ? ' (fuzzy)' : ''}...`);
const res = await fetch('/api/debug/find-and-click', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text }),
body: JSON.stringify({ text, fuzzy: !!fuzzy }),
});
const data = await res.json();
if (data.found) {
@ -855,7 +986,26 @@
if (e.key === 'Enter') addLink();
});
async function setOcrEngine(engine) {
await fetch('/api/debug/ocr-engine', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ engine }),
});
}
async function loadOcrEngine() {
try {
const res = await fetch('/api/debug/ocr-engine');
const data = await res.json();
if (data.ok && data.engine) {
document.getElementById('ocrEngineSelect').value = data.engine;
}
} catch {}
}
connect();
loadOcrEngine();
</script>
</body>
</html>