187 lines
25 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use strict';
const $ = s => document.querySelector(s);
const $$ = s => Array.from(document.querySelectorAll(s));
const token = $('meta[name="workbench-token"]').content;
let overview = null, settings = {}, currentRun = null, currentPlan = null, fileData = null, currentCard = null, poll = null;
const signals = {repeat:'反复提问',risk:'风险 / 事故线索',rework:'重复踩坑线索',future:'后续复用线索'};
const fields = {source_id:'消息 ID', conversation:'会话名称', conversation_id:'会话 ID', kind:'会话类型', sender:'发送人名称', sender_id:'发送人 ID', timestamp:'时间', content:'内容', message_type:'消息类型', quote:'引用'};
const titles = {connect:'连接 Mac 微信',overview:'看见对话中的知识需求',import:'导入你的聊天记录',analyze:'发现值得沉淀的知识',settings:'模型接口与隐私设置'};
function el(tag, text, className) { const n = document.createElement(tag); if(text !== undefined) n.textContent = text; if(className) n.className=className; return n; }
function notify(text, error=false, level='success') { const n=$('#notice'); n.textContent=text; n.hidden=false; n.classList.toggle('error',error); n.classList.toggle('warning',!error&&level==='warning'); }
async function api(path, body) {
const opts=body===undefined?{}:{method:'POST',headers:{'Content-Type':'application/json','X-Workbench-Token':token},body:JSON.stringify(body)};
const response=await fetch(path,opts); const data=await response.json(); if(!response.ok)throw Error(data.error||'请求失败'); return data;
}
async function busy(button, work) {button.disabled=true;try{await work();}catch(e){notify(e.message,true);}finally{button.disabled=button.id.startsWith('mac-')&&button.id!=='mac-refresh'&&(macRunning||!macAvailable);if(button.id==='mac-create-copy')button.disabled=button.disabled||!macRecoveryPlan||!$('#mac-copy-consent').checked;if(button.id==='mac-launch-copy')button.disabled=button.disabled||!macCopyReady;}}
function page(name) {if(!titles[name])name='overview';$$('.page').forEach(n=>n.hidden=n.id!=='page-'+name);$$('nav button').forEach(n=>n.classList.toggle('active',n.dataset.page===name));$('#page-title').textContent=titles[name];location.hash=name;}
function displayTime(time) {
if(!time)return '—'; const offset=settings.utc_offset||'+08:00'; const sign=offset[0]==='-'?-1:1;
const minutes=sign*(Number(offset.slice(1,3))*60+Number(offset.slice(4,6)));
const dt=new Date(new Date(time).getTime()+minutes*60000);
return dt.toISOString().slice(0,19).replace('T',' ');
}
function selected(select) {return Array.from(select.selectedOptions).map(n=>n.value);}
function filters() {
const start=$('#start').value,end=$('#end').value;
if(start&&end&&start>end)throw Error('开始时间不能晚于结束时间');
return {start,end,kind:$('#filter-kind').value,conversations:selected($('#include-conversations')),exclude:selected($('#exclude-conversations')),search:$('#filter-search').value.trim(),utc_offset:settings.utc_offset||'+08:00'};
}
function invalidatePlan() {currentPlan=null;$('#preview-panel').hidden=true;}
async function refresh() {
overview=await api('/api/overview');
const stats=$('#stats');stats.replaceChildren();
const metrics=[['已导入消息',overview.count,'可重复导入,自动去重'],['会话',overview.conversations.length,'群聊与私聊'],['群聊',overview.conversations.filter(c=>c.kind==='group').length,'可跨群汇总相似问题'],['分析记录',overview.runs.length,'保留审核与修正']];
for(const [name,num,note] of metrics){const node=el('div',undefined,'stat');node.append(el('small',name),el('strong',num.toLocaleString()),el('span',note));stats.append(node);}
$('#conversations').replaceChildren();$('#empty').hidden=overview.count>0;
for(const c of overview.conversations){const tr=el('tr');for(const text of [c.conversation,c.kind==='group'?'群聊':'私聊',c.count,c.media,displayTime(c.first),displayTime(c.last)])tr.append(el('td',text));$('#conversations').append(tr);}
for(const selector of ['#include-conversations','#exclude-conversations']){const select=$(selector),old=new Set(selected(select));select.replaceChildren();for(const c of overview.conversations){const opt=el('option',`${c.conversation} · ${c.kind==='group'?'群':'私聊'} · ${c.count} 条`);opt.value=c.conversation_id;opt.selected=old.has(opt.value);select.append(opt);}}
const history=$('#history');history.replaceChildren();
for(const r of overview.runs){const b=el('button');b.append(el('span',`${displayTime(r.created)} · ${r.mode==='api'?'模型分析':'本地规则'}`),el('small',`${r.coverage.messages} 条记录 · ${r.coverage.conversations} 个会话 · ${{complete:'已完成',running:'进行中',failed:'失败'}[r.status]}`));b.addEventListener('click',()=>loadRun(r.id).catch(e=>notify(e.message,true)));history.append(b);}
if(!overview.runs.length)history.append(el('p','还没有分析记录。','muted'));
}
async function loadSettings() {
settings=await api('/api/settings');$('#api-endpoint').value=settings.api.endpoint||'';$('#api-model').value=settings.api.model||'';$('#custom-words').value=(settings.custom||[]).join('\n');$('#utc-offset').value=settings.utc_offset||'+08:00';$('#key-state').textContent=settings.has_key?'当前运行进程中已配置密钥;留空保存会保留它。':'当前没有内存密钥;本地免密接口可留空。';$('#timezone-label').textContent='筛选与显示时区:UTC'+settings.utc_offset+'。不填时间代表全部已导入记录。';
}
async function loadFile(file) {
fileData=null;$('#import-button').disabled=true;$('#sqlite-options').hidden=true;
if(!file)return;
if(file.size>45*1024*1024)throw Error('单个文件不能超过 45 MB,请拆分后导入');
const bytes=new Uint8Array(await file.arrayBuffer());let binary='';for(let i=0;i<bytes.length;i+=16384)binary+=String.fromCharCode(...bytes.subarray(i,i+16384));
fileData={filename:file.name,data:btoa(binary)};$('#file-info').textContent=`${file.name} · ${(file.size/1024).toFixed(1)} KB`;
if(/\.(db|sqlite|sqlite3)$/i.test(file.name)){
const schema=await api('/api/schema',fileData);$('#sqlite-table').replaceChildren();
for(const table of schema){const opt=el('option',table.table+' · '+table.columns.join(', '));opt.value=table.table;$('#sqlite-table').append(opt);}
if(!schema.length)throw Error('数据库中没有可导入的表');$('#sqlite-options').hidden=false;
}
$('#import-button').disabled=false;
}
async function loadRun(id) {
clearTimeout(poll); currentRun=await api('/api/run?id='+encodeURIComponent(id));page('analyze');$('#results').hidden=false;
const r=currentRun,c=r.coverage;$('#result-meta').textContent=`${r.mode==='api'?'模型提炼':'本地规则'} · ${c.messages} 条记录 · ${c.conversations} 个会话 · ${c.events} 个讨论候选 · ${displayTime(c.first)} 至 ${displayTime(c.last)}`;
$('#export-csv').href='/api/export?id='+r.id+'&format=csv';$('#export-md').href='/api/export?id='+r.id+'&format=md';
$('#export-csv').hidden=$('#export-md').hidden=r.status!=='complete';
const status=$('#run-status');status.hidden=r.status==='complete';status.textContent=r.status==='running'?'模型正在分析。分批串行发送;任务失败不会自动重试。可以留在页面等待。':r.error;
renderCards();if(r.status==='running')poll=setTimeout(()=>loadRun(id).catch(e=>notify(e.message,true)),2500);
}
function renderCards() {
const container=$('#cards');container.replaceChildren();if(!currentRun)return;
const search=$('#card-search').value.toLowerCase(),signal=$('#card-signal').value;
const cards=currentRun.cards.filter(c=>(!search||(c.title+' '+c.summary).toLowerCase().includes(search))&&(!signal||c.signals.includes(signal)));
for(const c of cards){
const n=el('article',undefined,'card'),top=el('div',undefined,'card-top'),label=el('label'),check=el('input');check.type='checkbox';check.value=c.id;check.className='card-select';check.setAttribute('aria-label','选择主题:'+c.title);label.append(check,el('h3',c.title));top.append(label,el('span','优先级 '+c.priority,'score'));n.append(top);
const tags=el('div',undefined,'tags');for(const s of c.signals)tags.append(el('span',signals[s],'tag '+s));tags.append(el('span',c.strategy,'tag'));n.append(tags,el('p',c.summary),el('div',`${c.event_count} 个独立讨论候选 · ${c.conversations.length} 个会话 · ${c.participants.length} 位参与人`,'card-meta'));
if(c.responders.length)n.append(el('div','经验提供者候选:'+c.responders.join('、'),'card-meta'));
const bottom=el('div',undefined,'card-bottom'),b=el('button','查看证据与审核 ↗');b.addEventListener('click',()=>openCard(c.id).catch(e=>notify(e.message,true)));bottom.append(el('small',(c.reviewed?'已人工审核':'待人工审核')+' · '+c.status),b);n.append(bottom);container.append(n);
}
if(!cards.length)container.append(el('div',currentRun.status==='complete'?'没有匹配的候选主题。当前规则可能未识别出问题,也可以扩大时间范围。':'任务尚未生成完整结果。','empty'));
}
async function openCard(id) {
currentCard=currentRun.cards.find(c=>c.id===id);if(!currentCard)return;
const card=currentCard;$('#detail-title').textContent=card.title;const content=$('#detail-content');content.replaceChildren();
const grid=el('div',undefined,'review-grid');
for(const [field,name] of [['title','主题'],['summary','摘要'],['answer','经验候选(尚需验证)'],['boundaries','适用边界与证据缺口'],['notes','人工备注']]){const label=el('label',name),input=el(field==='title'?'input':'textarea');input.id='edit-'+field;input.value=card[field]||'';if(input.tagName==='TEXTAREA')input.rows=3;label.append(input);grid.append(label);}
const strategyLabel=el('label','处理策略'),strategy=el('select');strategy.id='edit-strategy';for(const v of ['正式资产','轻量记录','专家带教','暂不投入']){const op=el('option',v);op.value=v;op.selected=v===card.strategy;strategy.append(op);}strategyLabel.append(strategy);grid.append(strategyLabel);
const label=el('label',undefined,'check'),reviewed=el('input');reviewed.type='checkbox';reviewed.id='edit-reviewed';reviewed.checked=card.reviewed;label.append(reviewed,document.createTextNode('已人工审核(不代表经过实践验证)'));grid.append(label);
const save=el('button','保存修正','primary');save.addEventListener('click',()=>busy(save,async()=>{const body={run_id:currentRun.id,ids:[id],action:'edit',strategy:strategy.value,reviewed:reviewed.checked};for(const field of ['title','summary','answer','boundaries','notes'])body[field]=$('#edit-'+field).value;await api('/api/card',body);await loadRun(currentRun.id);$('#detail-dialog').close();notify('已保存人工修正。');}));grid.append(save);content.append(grid);
content.append(el('h3','原始讨论与证据','detail-evidence-title'),el('p','下方展示候选事件的全部已收集上下文。★ 为该卡引用的消息。事件窗口可能遗漏延迟回复或混入其他话题。经验提供者仅按他人的较长非问句回复识别,不代表专家认定。','muted'));
const loading=el('p','正在读取本地原文…','muted');content.append(loading);$('#detail-dialog').showModal();
const events=await api('/api/context?run_id='+currentRun.id+'&card_id='+id);loading.remove();
const splitButton=el('button','将勾选的讨论拆成新主题');splitButton.disabled=events.length<2;const splitChecks=[];
for(const event of events){const box=el('div',undefined,'event'),l=el('label'),cb=el('input');cb.type='checkbox';cb.value=event.id;splitChecks.push(cb);l.append(cb,document.createTextNode(`${event.id} · ${event.conversation} · ${displayTime(event.messages[0].timestamp)}`));box.append(l);
for(const m of event.messages){const msg=el('div',undefined,'message');msg.append(el('div',`${card.evidence_ids.includes(m.id)?'★ ':''}#${m.id} · ${m.sender} · ${displayTime(m.timestamp)} · ${m.message_type}`,'msg-meta'),el('p',m.content||'[非文字消息,无文字内容]'));if(m.quote)msg.append(el('blockquote',m.quote));box.append(msg);}content.append(box);
}
splitButton.addEventListener('click',()=>busy(splitButton,async()=>{const ids=splitChecks.filter(c=>c.checked).map(c=>c.value);await api('/api/card',{run_id:currentRun.id,ids:[id],action:'split',event_ids:ids});await loadRun(currentRun.id);$('#detail-dialog').close();notify('已拆分主题,请重新审核。');}));content.append(splitButton);
}
for(const [field,label] of Object.entries(fields)){const l=el('label',label),input=el('input');input.dataset.mapping=field;input.placeholder='自动匹配';l.append(input);$('#mapping-fields').append(l);}
$$('[data-page]').forEach(n=>n.addEventListener('click',()=>page(n.dataset.page)));
$$('[data-go]').forEach(n=>n.addEventListener('click',()=>page(n.dataset.go)));
window.addEventListener('hashchange',()=>page(location.hash.slice(1)));
$$('[data-action="refresh"]').forEach(n=>n.addEventListener('click',()=>busy(n,refresh)));
$$('[data-action="demo"]').forEach(n=>n.addEventListener('click',()=>busy(n,async()=>{const r=await api('/api/demo',{});await refresh();notify(`已导入虚构样例 ${r.added} 条,重复 ${r.duplicates} 条。可直接进入“发现知识需求”。`);})));
$('#import-file').addEventListener('change',e=>loadFile(e.target.files[0]).catch(error=>{fileData=null;$('#import-button').disabled=true;notify(error.message,true);}));
$('#import-button').addEventListener('click',e=>busy(e.target,async()=>{if(!fileData)throw Error('请先选择文件');const mapping={};$$('[data-mapping]').forEach(n=>{if(n.value.trim())mapping[n.dataset.mapping]=n.value.trim();});const options={conversation:$('#default-conversation').value.trim(),kind:$('#default-kind').value,utc_offset:settings.utc_offset||'+08:00',mapping,table:$('#sqlite-table').value};const result=await api('/api/import',{...fileData,options});$('#import-result').hidden=false;$('#import-result').textContent=JSON.stringify(result,null,2);invalidatePlan();await refresh();notify(`导入完成:新增 ${result.added},重复 ${result.duplicates},拒绝 ${result.rejected}。${result.rejected?'请查看下方具体行号和原因。':''}`);}));
$('#save-settings').addEventListener('click',e=>busy(e.target,async()=>{await api('/api/settings',{api:{endpoint:$('#api-endpoint').value.trim(),model:$('#api-model').value.trim()},key:$('#api-key').value,clear_key:$('#clear-key').checked,utc_offset:$('#utc-offset').value.trim(),custom:$('#custom-words').value.split('\n').map(x=>x.trim()).filter(Boolean)});$('#api-key').value='';$('#clear-key').checked=false;invalidatePlan();await loadSettings();await refresh();notify('配置已保存,密钥未写入磁盘。');}));
$('#analyze-local').addEventListener('click',e=>busy(e.target,async()=>{const r=await api('/api/analyze',{filters:filters(),cross:$('#cross').checked});await loadRun(r.id);await refresh();notify('本地分析完成。请查看原文,核对主题与适用条件。');}));
$('#preview-api').addEventListener('click',e=>busy(e.target,async()=>{invalidatePlan();const p=await api('/api/preview',{filters:filters(),cross:$('#cross').checked});currentPlan=p.plan_id;$('#preview-summary').textContent=`目标:${p.endpoint} · ${p.batches} 批 · 约 ${p.characters.toLocaleString()} 字符 · ${p.coverage.messages} 条原始记录 / ${p.coverage.events} 个讨论候选`;$('#preview-text').value=JSON.stringify(p.requests,null,2);$('#preview-panel').hidden=false;$('#preview-panel').scrollIntoView({behavior:'smooth',block:'start'});}));
$('#send-api').addEventListener('click',e=>busy(e.target,async()=>{if(!currentPlan)throw Error('请重新生成预览');const id=currentPlan;invalidatePlan();const r=await api('/api/send',{plan_id:id});await loadRun(r.id);await refresh();notify('已开始发送预览中的脱敏请求。请保持本地服务运行。');}));
$('#close-preview').addEventListener('click',invalidatePlan);
['start','end','filter-kind','include-conversations','exclude-conversations','filter-search','cross'].forEach(id=>$('#'+id).addEventListener('change',invalidatePlan));
$('#card-search').addEventListener('input',renderCards);$('#card-signal').addEventListener('change',renderCards);
$('#close-detail').addEventListener('click',()=>$('#detail-dialog').close());
$('#merge-cards').addEventListener('click',()=>{const ids=$$('.card-select:checked').map(n=>n.value);if(ids.length<2)return notify('请至少勾选两张主题卡片。',true);$('#merge-title').value=currentRun.cards.find(c=>c.id===ids[0]).title;$('#merge-dialog').showModal();});
$('#cancel-merge').addEventListener('click',()=>$('#merge-dialog').close());
$('#confirm-merge').addEventListener('click',e=>busy(e.target,async()=>{await api('/api/card',{run_id:currentRun.id,ids:$$('.card-select:checked').map(n=>n.value),action:'merge',title:$('#merge-title').value.trim()});await loadRun(currentRun.id);$('#merge-dialog').close();notify('已合并主题,原摘要保留在备注中,请重新审核。');}));
let macTimer = null, lastMacJob = null, macRunning = false, macAvailable = false, macRecoveryPlan = null, macCopyReady = false, macNextAction = null;
async function macStatus(){
clearTimeout(macTimer);
const state=await api('/api/mac/status');
$('#mac-platform').textContent=state.available?`本地服务运行于 Mac · ${state.architecture}。扫描和读取只操作这台 Mac。`:'当前服务运行于非 Mac 环境。请把工具复制到安装微信的 Mac 上启动,再在那台 Mac 的浏览器打开此页面。';
const running=state.job?.status==='running';
macRunning=running;
macAvailable=state.available;macCopyReady=state.debug_copy_ready;
for(const id of ['mac-scan','mac-folder','mac-capture','mac-capture-admin','mac-dependencies','mac-import','mac-permissions','mac-preview-copy'])$('#'+id).disabled=!state.available||running;
$('#mac-launch-copy').disabled=!state.available||running||!state.debug_copy_ready;
$('#mac-diagnostic-next').disabled=!state.available||running;
$('#mac-create-copy').disabled=!state.available||running||!macRecoveryPlan||!$('#mac-copy-consent').checked;
$('#mac-key-file').disabled=running;
const select=$('#mac-account'),previous=select.value;select.replaceChildren();$('#mac-accounts').replaceChildren();
for(const account of state.accounts){
const option=el('option',account.name);option.value=account.id;option.selected=account.id===previous;select.append(option);
const node=el('div',undefined,'mac-account-info');node.append(el('strong',account.name),el('p',`${account.databases} 个核心数据库 · ${(account.bytes/1024**2).toFixed(1)} MiB · 已可读或密钥匹配 ${account.readable}/${account.databases}`),el('p',account.path,'muted'));$('#mac-accounts').append(node);
}
if(!state.accounts.length){const option=el('option','请先扫描或选择数据文件夹');option.value='';select.append(option);}
$('#mac-key-state').textContent=state.keys_ready?'候选密钥已在运行内存。账号卡上的匹配数量决定实际可读范围。重启知微后需重新读取。':'尚未读取或导入密钥;未加密的可读数据库可直接进入第 3 步。';
const job=state.job;$('#mac-job-panel').hidden=!job;
const diagnostic=state.diagnostic;$('#mac-diagnostic-panel').hidden=!diagnostic;
const guidance=state.next_step;macNextAction=guidance?.action||null;
if(guidance){$('#mac-next-title').textContent=guidance.title;$('#mac-next-description').textContent=guidance.description;$('#mac-diagnostic-next').textContent=guidance.button;}
if(diagnostic){$('#mac-diagnostic-summary').textContent=diagnostic.summary;const yes=value=>value===true?'是':value===false?'否':'未知';$('#mac-diagnostic-details').textContent=[`诊断类型:${diagnostic.code}`,`强化运行时:${yes(diagnostic.hardened_runtime)}`,`允许调试:${yes(diagnostic.get_task_allow)}`,`管理员进程:${yes(diagnostic.administrator)}`,`进程读取返回码:${diagnostic.task_for_pid_code??'尚未尝试'}`].join('\n');}
if(job){$('#mac-job-stage').textContent=job.stage;$('#mac-job-error').hidden=job.status!=='failed';$('#mac-job-error').textContent=job.error||'';$('#mac-job-result').hidden=!job.result;$('#mac-job-result').textContent=job.result?.message||'';
const info=$('#mac-job-details');info.replaceChildren();const labels={parsed:'已解析消息',added:'新增消息',duplicates:'重复消息',backup:'本地备份位置',copy:'临时副本位置'};for(const [field,label] of Object.entries(labels)){if(job.result?.[field]!==undefined){info.append(el('dt',label),el('dd',String(job.result[field])));}}info.hidden=!info.childElementCount;
if(!running&&lastMacJob!==job.id){lastMacJob=job.id;if(job.status==='complete'){if(job.action==='import')await refresh();notify(job.result?.message||'接入操作已完成',false,job.result?.level||'success');}else{notify(guidance?.title||job.error||'接入未完成',true);}if(location.hash==='#connect'&&diagnostic)$('#mac-diagnostic-panel').scrollIntoView({behavior:'smooth',block:'start'});}
if(running)macTimer=setTimeout(()=>macStatus().catch(e=>notify(e.message,true)),1800);
}
return state;
}
async function macStart(action,extra={}){await api('/api/mac/start',{action,...extra});const state=await macStatus();const target=state.diagnostic&&state.job?.status!=='running'?'#mac-diagnostic-panel':'#mac-job-panel';$(target).scrollIntoView({behavior:'smooth',block:'start'});}
async function previewMacCopy(){
if(!$('#mac-account').value){$('#mac-account').scrollIntoView({behavior:'smooth',block:'center'});throw Error('请先扫描并选择账号,再预览完整备份与临时副本方案');}
const plan=await api('/api/mac/recovery-plan',{account:$('#mac-account').value});
macRecoveryPlan=plan.plan_id;$('#mac-copy-consent').checked=false;$('#mac-create-copy').disabled=true;$('#mac-copy-plan').hidden=false;
$('#mac-copy-paths').textContent=`原版:${plan.source}\n临时副本:${plan.copy}\n完整备份:${plan.backup}\n预计至少需要 ${(plan.estimated_bytes/1024**3).toFixed(2)} GiB 可用空间`;
$('#mac-copy-effects').replaceChildren(...plan.effects.map(effect=>el('li',effect)));
$('#mac-copy-plan').scrollIntoView({behavior:'smooth',block:'start'});
}
$('#mac-diagnostic-next').addEventListener('click',e=>busy(e.target,async()=>{
if(macNextAction==='preview_copy')return previewMacCopy();
if(macNextAction==='capture_admin')return macStart('capture',{administrator:true});
if(macNextAction==='key_file'){$('#mac-key-options').open=true;$('#mac-key-options').scrollIntoView({behavior:'smooth',block:'center'});$('#mac-key-file').focus();return;}
if(['capture','launch_copy','permissions'].includes(macNextAction))return macStart(macNextAction);
throw Error('请先重新检查权限,再选择下一步');
}));
$('#mac-show-recovery').addEventListener('click',()=>$('#mac-recovery-section').scrollIntoView({behavior:'smooth',block:'start'}));
$('#mac-scan').addEventListener('click',e=>busy(e.target,async()=>{await api('/api/mac/scan',{});await macStatus();}));
$('#mac-folder').addEventListener('click',e=>busy(e.target,async()=>{await api('/api/mac/scan',{choose_folder:true});await macStatus();}));
$('#mac-refresh').addEventListener('click',e=>busy(e.target,macStatus));
$('#mac-capture').addEventListener('click',e=>busy(e.target,()=>macStart('capture')));
$('#mac-capture-admin').addEventListener('click',e=>busy(e.target,()=>macStart('capture',{administrator:true})));
$('#mac-permissions').addEventListener('click',e=>busy(e.target,()=>macStart('permissions')));
$('#mac-preview-copy').addEventListener('click',e=>busy(e.target,previewMacCopy));
$('#mac-copy-consent').addEventListener('change',()=>{$('#mac-create-copy').disabled=!macAvailable||macRunning||!macRecoveryPlan||!$('#mac-copy-consent').checked;});
$('#mac-account').addEventListener('change',()=>{macRecoveryPlan=null;$('#mac-copy-plan').hidden=true;$('#mac-copy-consent').checked=false;$('#mac-create-copy').disabled=true;});
$('#mac-create-copy').addEventListener('click',e=>busy(e.target,async()=>{if(!macRecoveryPlan||!$('#mac-copy-consent').checked)throw Error('请先预览并明确同意操作影响');const plan=macRecoveryPlan;macRecoveryPlan=null;$('#mac-copy-plan').hidden=true;await macStart('debug_copy',{account:$('#mac-account').value,plan_id:plan,consent:true});}));
$('#mac-launch-copy').addEventListener('click',e=>busy(e.target,()=>macStart('launch_copy')));
$('#mac-dependencies').addEventListener('click',e=>busy(e.target,()=>macStart('dependencies')));
$('#mac-import').addEventListener('click',e=>busy(e.target,()=>macStart('import',{account:$('#mac-account').value,include_biz:$('#mac-include-biz').checked})));
$('#mac-key-file').addEventListener('change',async e=>{try{const file=e.target.files[0];if(!file)return;if(file.size>1024**2)throw Error('密钥文件最多 1 MB');await api('/api/mac/keys',{keys:JSON.parse(await file.text())});await macStatus();notify('密钥文件已在本机内存处理,不会发送给模型。');}catch(error){notify(error.message,true);}finally{e.target.value='';}});
async function init(){await loadSettings();await refresh();page(location.hash.slice(1)||'overview');await macStatus();}
init().catch(e=>notify(e.message,true));