Files
GenericAgent/frontends/desktop/static/fallback.html
T

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>