131 lines
5.6 KiB
HTML
131 lines
5.6 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title data-i18n="setup.title">GenericAgent</title>
|
|
<style>
|
|
:root{--bg:#fff;--text:#0a0a0a;--muted:#6b6b6b;--border:#e8e8e8;--accent:#0a0a0a;--accent-text:#fff;--radius:10px;--font:-apple-system,BlinkMacSystemFont,"SF Pro Text","Helvetica Neue",Helvetica,Arial,sans-serif}
|
|
@media(prefers-color-scheme:dark){:root{--bg:#1a1a1a;--text:#ededed;--muted:#a0a0a0;--border:#2a2a2a;--accent:#ededed;--accent-text:#0a0a0a}}
|
|
*{box-sizing:border-box}
|
|
html,body{margin:0;height:100%;font-family:var(--font);background:var(--bg);color:var(--text);display:flex;align-items:center;justify-content:center}
|
|
.card{max-width:480px;width:90%;padding:2.5rem;border:1px solid var(--border);border-radius:var(--radius);text-align:center}
|
|
h1{font-size:1.4rem;margin:0 0 .5rem}
|
|
p{color:var(--muted);font-size:.9rem;margin:.4rem 0;line-height:1.5}
|
|
.input-row{display:flex;gap:8px;margin-top:1.2rem}
|
|
input[type=text]{flex:1;padding:10px 12px;border:1px solid var(--border);border-radius:6px;font-size:.9rem;background:var(--bg);color:var(--text);outline:none}
|
|
input[type=text]:focus{border-color:var(--accent)}
|
|
button{padding:10px 18px;border:none;border-radius:6px;font-size:.9rem;cursor:pointer;background:var(--accent);color:var(--accent-text);font-weight:500}
|
|
button:hover{opacity:.85}
|
|
button:disabled{opacity:.4;cursor:not-allowed}
|
|
.field-label{display:block;text-align:left;font-size:.8rem;font-weight:500;margin-top:1rem;margin-bottom:.3rem;color:var(--text)}
|
|
#status{margin-top:1rem;font-size:.85rem;min-height:1.2em}
|
|
.err{color:#dc2626}.ok{color:#16a34a}
|
|
</style>
|
|
<script src="i18n.js?v=1"></script>
|
|
</head>
|
|
<body>
|
|
<div class="card">
|
|
<h1><span aria-hidden="true">⚙️</span> <span data-i18n="setup.heading"></span></h1>
|
|
<p data-i18n="setup.backend_failed"></p>
|
|
|
|
<label class="field-label" data-i18n="setup.py_label"></label>
|
|
<div class="input-row">
|
|
<input id="pypath" type="text" data-i18n-placeholder="setup.py_placeholder" spellcheck="false">
|
|
</div>
|
|
<p style="color:var(--muted);font-size:.75rem;margin-top:.3rem" data-i18n="setup.py_hint"></p>
|
|
|
|
<label class="field-label" data-i18n="setup.project_label"></label>
|
|
<div class="input-row">
|
|
<input id="projdir" type="text" data-i18n-placeholder="setup.project_placeholder" spellcheck="false">
|
|
</div>
|
|
<p style="color:var(--muted);font-size:.75rem;margin-top:.3rem" data-i18n="setup.project_hint"></p>
|
|
|
|
<button id="start-btn" onclick="doStart()" style="margin-top:1.2rem;width:100%" data-i18n="setup.start"></button>
|
|
<div id="status"></div>
|
|
</div>
|
|
<script>
|
|
const bootT = window.GA_BOOT_I18N?.t || ((key, vars) => vars?.error ? `${key}: ${vars.error}` : key);
|
|
window.GA_BOOT_I18N?.apply(document);
|
|
const {invoke} = window.__TAURI__.core;
|
|
const {getCurrentWindow, Window} = window.__TAURI__.window;
|
|
const statusEl = document.getElementById('status');
|
|
const btn = document.getElementById('start-btn');
|
|
const pyInput = document.getElementById('pypath');
|
|
const dirInput = document.getElementById('projdir');
|
|
|
|
// Pre-fill from Rust config discovery
|
|
invoke('get_config').then(([py, dir]) => {
|
|
if (py) pyInput.value = py;
|
|
if (dir) dirInput.value = dir;
|
|
}).catch(()=>{});
|
|
|
|
async function doStart() {
|
|
const pythonPath = pyInput.value.trim();
|
|
const projectDir = dirInput.value.trim();
|
|
if (!pythonPath) { statusEl.className='err'; statusEl.textContent=bootT('setup.err_py'); return; }
|
|
if (!projectDir) { statusEl.className='err'; statusEl.textContent=bootT('setup.err_project'); return; }
|
|
btn.disabled = true;
|
|
statusEl.className=''; statusEl.textContent=bootT('setup.starting_bridge');
|
|
try {
|
|
await invoke('start_bridge_with_config', {pythonPath, projectDir});
|
|
statusEl.className='ok'; statusEl.textContent=bootT('setup.connected');
|
|
// Show main window and hide setup
|
|
const mainWin = await Window.getByLabel('main');
|
|
if (mainWin) {
|
|
await mainWin.show();
|
|
await mainWin.setFocus();
|
|
}
|
|
const setupWin = getCurrentWindow();
|
|
await setupWin.hide();
|
|
} catch(e) {
|
|
statusEl.className='err'; statusEl.textContent=bootT('setup.failed', { error: e });
|
|
btn.disabled = false;
|
|
}
|
|
}
|
|
pyInput.addEventListener('keydown', e=>{ if(e.key==='Enter') dirInput.focus(); });
|
|
dirInput.addEventListener('keydown', e=>{ if(e.key==='Enter') doStart(); });
|
|
</script>
|
|
|
|
<script>
|
|
// Input length limit (300 chars) — in separate script to work even without Tauri
|
|
const MAX_LEN = 300;
|
|
const _pyInput = document.getElementById('pypath');
|
|
const _dirInput = document.getElementById('projdir');
|
|
[_pyInput, _dirInput].forEach(el => {
|
|
if (!el) return;
|
|
const hint = document.createElement('span');
|
|
hint.style.cssText = 'color:#dc2626;font-size:.75rem;display:none;margin-top:2px';
|
|
el.parentNode.appendChild(hint);
|
|
|
|
function checkLimit() {
|
|
if (el.value.length > MAX_LEN) {
|
|
el.value = el.value.slice(0, MAX_LEN);
|
|
}
|
|
if (el.value.length >= MAX_LEN) {
|
|
hint.textContent = bootT('setup.limit', { n: MAX_LEN });
|
|
hint.style.display = 'block';
|
|
} else {
|
|
hint.style.display = 'none';
|
|
}
|
|
}
|
|
|
|
// Intercept paste: only take first MAX_LEN chars from clipboard
|
|
el.addEventListener('paste', e => {
|
|
e.preventDefault();
|
|
const text = (e.clipboardData || window.clipboardData).getData('text') || '';
|
|
const start = el.selectionStart;
|
|
const end = el.selectionEnd;
|
|
const remaining = MAX_LEN - el.value.length + (end - start);
|
|
const insert = text.slice(0, Math.max(0, remaining));
|
|
el.value = el.value.slice(0, start) + insert + el.value.slice(end);
|
|
el.setSelectionRange(start + insert.length, start + insert.length);
|
|
checkLimit();
|
|
});
|
|
|
|
// Fallback: keyboard input beyond limit
|
|
el.addEventListener('input', checkLimit);
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|