Files
GenericAgent/frontends/desktop/dist/fallback.html
T
yiqi-017 bb4b2ca974 feat(desktop): refresh compiled React Desktop 2.0 distribution
Rebuild the compiled renderer from abraxas914/GenericAgent source
commit 9fe5d69b85cfad8778ace84b6f69bc75f9e0d392, refreshing:

- New-chat default model tag: the selected-model badge in Settings
  and the composer now read '新会话默认' / 'New chat default'.
- Model selectors display the configured profile name (备注名) so the
  same model served by different providers is distinguishable.
- desktop_bridge: syncing mixin llm_nos references when a profile is
  renamed, preventing orphan references in the mixin config.
- Release publisher sets GH_REPO so 'gh release create' resolves the
  target repository without checking out source, satisfying the
  publisher security contract while fixing the prerelease step.

Provenance and verifier updated to the new source commit and the
LF-normalized content manifest (98 generated assets).
2026-08-25 18:00:43 +08:00

81 lines
18 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Startup recovery</title>
<style>
:root{color-scheme:light dark;--ga-bg:#fff;--ga-text-0:#1c1f23;--ga-text-1:#5f6673;--ga-fill:#f5f6f7;--ga-border:#dcdee3;--ga-primary:#0064fa;--ga-primary-hover:#0052d9;--ga-danger:#d54941;--ga-danger-bg:#fff2f1;--ga-focus:#0064fa33;--ga-radius:6px;font-family:-apple-system,BlinkMacSystemFont,"SF Pro Text","Segoe UI","Microsoft YaHei UI",sans-serif}
@media(prefers-color-scheme:dark){:root{--ga-bg:#16161a;--ga-text-0:#f2f3f5;--ga-text-1:#a9abb3;--ga-fill:#25262b;--ga-border:#3a3b41;--ga-primary:#54a9ff;--ga-primary-hover:#78b9ff;--ga-danger:#ff7770;--ga-danger-bg:#351f21;--ga-focus:#54a9ff40}}
*{box-sizing:border-box}html,body{min-height:100%;margin:0;background:var(--ga-bg);color:var(--ga-text-0)}body{display:grid;place-items:center;padding:32px 40px}main{width:min(100%,500px)}h1{margin:0 0 8px;font-size:22px;line-height:1.3}.ga-intro{margin:0 0 20px;color:var(--ga-text-1);font-size:14px;line-height:1.6}.ga-failure{display:none;margin-bottom:18px;padding:12px 14px;border:1px solid color-mix(in srgb,var(--ga-danger) 28%,transparent);border-radius:var(--ga-radius);background:var(--ga-danger-bg)}.ga-failure.ga-visible{display:block}.ga-failure strong{display:block;margin-bottom:4px;color:var(--ga-danger);font-size:14px}.ga-failure span{font-size:13px;line-height:1.5}.ga-location{margin:0 0 18px}.ga-location-header{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:7px}.ga-field-label{font-size:13px;font-weight:600}.ga-path{width:100%;min-height:42px;padding:10px 12px;border:1px solid var(--ga-border);border-radius:var(--ga-radius);outline:0;background:var(--ga-fill);font:13px/1.55 ui-monospace,SFMono-Regular,Consolas,monospace;white-space:normal;overflow-wrap:anywhere;word-break:break-word;user-select:text}.ga-path:focus{border-color:var(--ga-primary);box-shadow:0 0 0 2px var(--ga-focus)}.ga-path.ga-empty{color:var(--ga-text-1);font-family:inherit}.ga-hint{display:block;margin-top:5px;color:var(--ga-text-1);font-size:12px;line-height:1.45}.ga-picker-status{min-height:18px;margin:-5px 0 8px;color:var(--ga-danger);font-size:12px}button{border:0;border-radius:var(--ga-radius);font:inherit;cursor:pointer}.ga-secondary{height:30px;padding:0 10px;border:1px solid var(--ga-border);background:var(--ga-fill);color:var(--ga-text-0);font-size:12px}.ga-primary{width:100%;height:36px;margin-top:3px;background:var(--ga-primary);color:#fff;font-size:14px;font-weight:600}.ga-primary:hover{background:var(--ga-primary-hover)}.ga-primary:focus-visible,.ga-secondary:focus-visible,.ga-copy:focus-visible,summary:focus-visible{outline:2px solid var(--ga-primary);outline-offset:2px}.ga-primary:disabled,.ga-secondary:disabled{opacity:.6;cursor:wait}details{margin-top:20px;padding-top:14px;border-top:1px solid var(--ga-border)}summary{width:max-content;color:var(--ga-text-1);font-size:13px;cursor:pointer;user-select:none}.ga-diagnostics-actions{display:flex;align-items:center;gap:10px;margin:12px 0 8px}.ga-copy{height:30px;padding:0 10px;border:1px solid var(--ga-border);background:var(--ga-fill);color:var(--ga-text-0);font-size:12px}.ga-copy-status{color:var(--ga-text-1);font-size:12px}pre{max-height:190px;margin:0;padding:12px;overflow:auto;border:1px solid var(--ga-border);border-radius:var(--ga-radius);background:var(--ga-fill);color:var(--ga-text-0);font:11px/1.55 ui-monospace,SFMono-Regular,Consolas,monospace;white-space:pre-wrap;overflow-wrap:anywhere;user-select:text}.ga-privacy{margin:9px 0 0;color:var(--ga-text-1);font-size:11px;line-height:1.45}.ga-rescue-note{margin:0 0 18px;color:var(--ga-text-1);font-size:12px}.ga-contact-copy{margin:12px 0 8px;color:var(--ga-text-1);font-size:12px;line-height:1.5}.ga-contact-ids{display:flex;flex-wrap:wrap;gap:8px}.ga-contact-ids code{padding:6px 9px;border-radius:var(--ga-radius);background:var(--ga-fill);font:12px/1.4 ui-monospace,SFMono-Regular,Consolas,monospace;overflow-wrap:anywhere;user-select:text}
</style>
</head>
<body>
<main>
<h1 id="page-title">完成启动设置</h1>
<p class="ga-intro" id="intro">恢复界面未能加载。你仍可选择应用运行所需的位置并继续启动。</p>
<p class="ga-rescue-note" id="rescue-note">此页面不依赖应用界面组件,可在前端资源损坏时独立运行。</p>
<section class="ga-failure" id="failure" role="alert"><strong id="failure-title"></strong><span id="failure-description"></span></section>
<section class="ga-location" aria-labelledby="project-label"><div class="ga-location-header"><span class="ga-field-label" id="project-label">应用文件夹</span><button class="ga-secondary" id="choose-project" type="button">选择文件夹</button></div><div class="ga-path ga-empty" id="project-dir" role="textbox" aria-readonly="true" tabindex="0">尚未选择应用文件夹</div><span class="ga-hint" id="project-hint">选择此前用于运行桌面版的应用文件夹。</span></section>
<section class="ga-location" aria-labelledby="python-label"><div class="ga-location-header"><span class="ga-field-label" id="python-label">Python 环境</span><button class="ga-secondary" id="choose-python" type="button">选择环境</button></div><div class="ga-path ga-empty" id="python-path" role="textbox" aria-readonly="true" tabindex="0">选择应用文件夹后自动识别</div><span class="ga-hint" id="python-hint">应用会自动选择可用环境;如有需要,可以重新选择。</span></section>
<p class="ga-picker-status" id="picker-status" role="status" aria-live="polite"></p>
<button class="ga-primary" id="retry" type="button">继续启动</button>
<details><summary id="diagnostics-label">诊断信息</summary><div class="ga-diagnostics-actions"><button class="ga-copy" id="copy" type="button">复制诊断信息</button><span class="ga-copy-status" id="copy-status" aria-live="polite"></span></div><pre id="diagnostics" tabindex="0">GenericAgent Desktop startup diagnostics</pre><p class="ga-privacy" id="privacy">诊断信息由启动服务脱敏后提供,包含本机路径和错误日志,不包含 API Key、会话或记忆内容。</p></details>
<details><summary id="help-feedback-label">帮助与反馈</summary><p class="ga-contact-copy" id="contact-copy">如果仍无法完成启动,可添加微信联系:</p><div class="ga-contact-ids"><code>RoundSquisheen</code><code>persist0612</code></div></details>
</main>
<script>
(function () {
'use strict';
var ZH=(navigator.language||'').toLowerCase().startsWith('zh');
var tauri=window.__TAURI__||{};
var invoke=tauri.core&&tauri.core.invoke;
var listen=tauri.event&&tauri.event.listen;
var projectPath=document.getElementById('project-dir');
var pythonPath=document.getElementById('python-path');
var projectButton=document.getElementById('choose-project');
var pythonButton=document.getElementById('choose-python');
var retryButton=document.getElementById('retry');
var failureBox=document.getElementById('failure');
var failureTitle=document.getElementById('failure-title');
var failureDescription=document.getElementById('failure-description');
var diagnostics=document.getElementById('diagnostics');
var copyStatus=document.getElementById('copy-status');
var pickerStatus=document.getElementById('picker-status');
var projectDir='';
var pythonEnvironment='';
var latestSnapshot=null;
var latestSeq=-1;
var legacyApi=false;
var failures={
config_unresolved:{zh:['未找到应用文件','请选择应用文件夹后继续。'],en:['Application files not found','Choose the application folder to continue.']},
prepare_failed:{zh:['运行环境尚未准备完成','请检查所选应用文件夹和 Python 环境,然后继续启动。'],en:['Environment setup is incomplete','Check the application folder and Python environment, then continue startup.']},
spawn_failed:{zh:['本地连接未能启动','请选择可用的应用文件夹和 Python 环境后继续。'],en:['Local connection could not start','Choose a usable application folder and Python environment to continue.']},
port_conflict:{zh:['本地连接被占用','另一个应用正在使用本地连接。关闭相关应用后继续启动。'],en:['Local connection is in use','Another app is using the local connection. Close it, then continue startup.']},
service_timeout:{zh:['本地连接启动时间过长','请检查所选位置;诊断信息中包含最近的启动记录。'],en:['Local connection is taking too long','Check the selected locations. Diagnostics contain the latest startup records.']},
service_exited:{zh:['本地连接意外停止','诊断信息中包含停止状态和最近的启动记录。'],en:['Local connection stopped unexpectedly','Diagnostics contain the stop status and latest startup records.']},
ui_navigation_failed:{zh:['主界面未能打开','本地连接已经就绪,但界面加载失败。诊断信息中包含详细原因。'],en:['Main window could not open','The local connection is ready, but the interface failed to load. Diagnostics contain details.']},
unknown:{zh:['启动尚未完成','请检查所选位置;需要协助时可复制诊断信息。'],en:['Startup is not complete','Check the selected locations. Copy diagnostics if you need support.']}
};
if(!ZH){document.documentElement.lang='en';document.getElementById('page-title').textContent='Complete startup setup';document.getElementById('intro').textContent='The normal recovery interface could not load. You can still choose the locations the app needs and continue startup.';document.getElementById('rescue-note').textContent='This standalone page does not depend on the application UI bundle.';document.getElementById('project-label').textContent='Application folder';document.getElementById('project-hint').textContent='Choose the application folder previously used to run the desktop app.';projectPath.textContent='No application folder selected';projectButton.textContent='Choose folder';document.getElementById('python-label').textContent='Python environment';document.getElementById('python-hint').textContent='The app selects an available environment automatically. You can choose another if needed.';pythonPath.textContent='Detected after you choose the application folder';pythonButton.textContent='Choose environment';retryButton.textContent='Continue startup';document.getElementById('diagnostics-label').textContent='Diagnostic details';document.getElementById('copy').textContent='Copy diagnostics';document.getElementById('privacy').textContent='Diagnostics are redacted by the startup service. They contain local paths and error logs, but no API keys, sessions, or memory content.';document.getElementById('help-feedback-label').textContent='Help & Feedback';document.getElementById('contact-copy').textContent='If you still cannot complete startup, contact us on WeChat:'}
var guardReason='';try{guardReason=sessionStorage.getItem('ga-setup-fallback-reason')||'';sessionStorage.removeItem('ga-setup-fallback-reason')}catch(_){}
if(guardReason){copyStatus.textContent=(ZH?'恢复界面失败原因:':'Recovery UI failure: ')+guardReason}
function showFailure(failure){var code=failure&&failures[failure.code]?failure.code:'unknown';var copy=failures[code][ZH?'zh':'en'];failureTitle.textContent=copy[0];failureDescription.textContent=copy[1];failureBox.classList.add('ga-visible')}
function diagnosticsText(snapshot){var d=snapshot&&snapshot.diagnostics||{};var logs=Array.isArray(d.recentLogs)?d.recentLogs:[];return['GenericAgent Desktop startup diagnostics','time: '+new Date().toISOString(),'build_id: '+(d.buildId||''),'platform: '+(d.platform||''),'mode: '+(snapshot&&snapshot.mode||''),'phase: '+(snapshot&&snapshot.phase||''),'failure_code: '+(snapshot&&snapshot.failure&&snapshot.failure.code||''),'project_dir: '+(d.projectDir||''),'python_path: '+(d.pythonPath||''),'port_state: '+(d.portState||'unknown'),'bridge_identity: '+(d.bridgeIdentity||''),'error: '+(snapshot&&snapshot.failure&&snapshot.failure.detail||''),'recent_logs:'].concat(logs).join('\n')}
function setProject(value){projectDir=(value||'').trim();projectPath.textContent=projectDir||(ZH?'尚未选择应用文件夹':'No application folder selected');projectPath.classList.toggle('ga-empty',!projectDir);projectButton.textContent=projectDir?(ZH?'重新选择':'Choose again'):(ZH?'选择文件夹':'Choose folder')}
function setPython(value){pythonEnvironment=(value||'').trim();pythonPath.textContent=pythonEnvironment||(ZH?'选择应用文件夹后自动识别':'Detected after you choose the application folder');pythonPath.classList.toggle('ga-empty',!pythonEnvironment);pythonButton.textContent=pythonEnvironment?(ZH?'重新选择':'Choose again'):(ZH?'选择环境':'Choose environment')}
function renderSnapshot(snapshot){var seq=Number.isFinite(snapshot&&snapshot.seq)?snapshot.seq:latestSeq+1;if(seq<=latestSeq)return;latestSeq=seq;latestSnapshot=snapshot;if(snapshot&&snapshot.diagnostics&&snapshot.diagnostics.projectDir&&!projectDir)setProject(snapshot.diagnostics.projectDir);if(snapshot&&snapshot.diagnostics&&snapshot.diagnostics.pythonPath&&!pythonEnvironment)setPython(snapshot.diagnostics.pythonPath);if(snapshot&&snapshot.failure)showFailure(snapshot.failure);diagnostics.textContent=diagnosticsText(snapshot)}
function missingCommand(error,command){var message=String(error&&error.message||error||'').toLowerCase();return message.indexOf(command.toLowerCase())>=0&&['not found','unknown command','does not exist',"doesn't exist",'not registered'].some(function(marker){return message.indexOf(marker)>=0})}
function legacyFailure(error,config,failureCode){var detail=error==null?'':String(error&&error.message||error);return{seq:latestSeq+1,mode:'cold_start',phase:'failed',stage:null,progress:0,failure:{code:detail?(failureCode||'prepare_failed'):'unknown',detail:detail},diagnostics:{buildId:'',platform:navigator.platform||'',projectDir:config&&config[1]||projectDir,pythonPath:config&&config[0]||pythonEnvironment,recentLogs:[],portState:'unknown',bridgeIdentity:null}}}
async function refresh(){if(typeof invoke!=='function')throw new Error('Tauri bootstrap API is unavailable');var config=await invoke('get_config').catch(function(){return['','']});if(config&&config[0])setPython(config[0]);if(config&&config[1])setProject(config[1]);try{renderSnapshot(await invoke('get_bootstrap_snapshot'))}catch(error){if(!missingCommand(error,'get_bootstrap_snapshot'))throw error;legacyApi=true;var prepareError=await invoke('get_prepare_error').catch(function(){return null});renderSnapshot(legacyFailure(prepareError,config))}}
async function chooseProject(){if(typeof invoke!=='function'){pickerStatus.textContent=ZH?'无法打开或读取所选位置,请查看诊断信息。':'The selected location could not be opened or read. Check diagnostics for details.';return}projectButton.disabled=true;pickerStatus.textContent='';try{var selected=await invoke('pick_directory',{title:ZH?'选择应用文件夹':'Choose the application folder'});if(selected){setProject(selected);var detected=await invoke('discover_python_for_project',{projectDir:selected,currentPython:pythonEnvironment||null}).catch(function(){return pythonEnvironment});if(detected)setPython(detected)}}catch(error){pickerStatus.textContent=ZH?'无法打开或读取所选位置,请查看诊断信息。':'The selected location could not be opened or read. Check diagnostics for details.'}finally{projectButton.disabled=false}}
async function choosePython(){if(typeof invoke!=='function'){pickerStatus.textContent=ZH?'无法打开或读取所选位置,请查看诊断信息。':'The selected location could not be opened or read. Check diagnostics for details.';return}pythonButton.disabled=true;pickerStatus.textContent='';try{var selected=await invoke('pick_python_interpreter',{title:ZH?'选择 Python 环境':'Choose a Python environment'});if(selected)setPython(selected)}catch(error){pickerStatus.textContent=ZH?'无法打开或读取所选位置,请查看诊断信息。':'The selected location could not be opened or read. Check diagnostics for details.'}finally{pythonButton.disabled=false}}
async function retry(){if(!projectDir){showFailure({code:'config_unresolved'});return}if(typeof invoke!=='function'){showFailure({code:'unknown'});return}retryButton.disabled=true;retryButton.textContent=ZH?'正在启动…':'Starting…';copyStatus.textContent='';var args={pythonPath:pythonEnvironment,projectDir:projectDir};var retryError=null;if(legacyApi){try{await invoke('start_bridge_with_config',args)}catch(error){retryError=error}}else{try{await invoke('retry_bootstrap',args)}catch(error){if(missingCommand(error,'retry_bootstrap')){legacyApi=true;try{await invoke('start_bridge_with_config',args)}catch(legacyError){retryError=legacyError}}else{retryError=error}}}if(retryError){if(legacyApi){renderSnapshot(legacyFailure(retryError,[pythonEnvironment,projectDir],'spawn_failed'))}else{var snapshot=await invoke('get_bootstrap_snapshot').catch(function(){return latestSnapshot});renderSnapshot(snapshot)}retryButton.disabled=false;retryButton.textContent=ZH?'继续启动':'Continue startup'}}
async function copyDiagnostics(){var text=diagnostics.textContent||'';if(navigator.clipboard&&navigator.clipboard.writeText){try{await navigator.clipboard.writeText(text);copyStatus.textContent=ZH?'已复制,可发送给技术支持。':'Copied. You can send it to technical support.';return}catch(_){}}var selection=window.getSelection();var range=document.createRange();range.selectNodeContents(diagnostics);selection.removeAllRanges();selection.addRange(range);diagnostics.focus();copyStatus.textContent=ZH?'请手动复制已选中的诊断信息。':'Copy the selected diagnostics manually.'}
projectButton.addEventListener('click',chooseProject);pythonButton.addEventListener('click',choosePython);retryButton.addEventListener('click',retry);document.getElementById('copy').addEventListener('click',copyDiagnostics);
async function initialize(){if(typeof listen==='function'){try{await listen('bootstrap',function(event){renderSnapshot(event.payload)})}catch(_){}}await refresh()}
initialize().catch(function(error){renderSnapshot({seq:latestSeq+1,mode:'cold_start',phase:'failed',stage:null,progress:0,failure:{code:'unknown',detail:String(error)},diagnostics:{buildId:'',platform:'',projectDir:'',pythonPath:'',recentLogs:[],portState:'unknown',bridgeIdentity:null}})});
}());
</script>
</body>
</html>