@wendongfly/myhi 1.3.181 → 1.3.182
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chat.html +4 -187
- package/dist/index.html +277 -30
- package/package.json +1 -1
package/dist/chat.html
CHANGED
|
@@ -410,8 +410,6 @@
|
|
|
410
410
|
.dr-text { color: var(--text); font-size: 0.82rem; line-height: 1.5; word-break: break-word; white-space: pre-wrap; }
|
|
411
411
|
.dr-meta { display: flex; align-items: center; flex-wrap: wrap; gap: 0.4rem 0.6rem; margin-top: 0.35rem; font-size: 0.66rem; color: var(--muted); }
|
|
412
412
|
.dr-acts { display: flex; gap: 0.4rem; margin-left: auto; flex-shrink: 0; }
|
|
413
|
-
.atpick-row { padding: 0.42rem 0.7rem; cursor: pointer; color: var(--text2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
414
|
-
.atpick-row:hover { background: var(--surface2); color: var(--text); }
|
|
415
413
|
.dr-btn { flex-shrink: 0; white-space: nowrap; padding: 0.28rem 0.7rem; font-size: 0.72rem; border: none; border-radius: 6px; background: var(--surface2); color: var(--text2); cursor: pointer; }
|
|
416
414
|
.dr-btn.take { background: var(--green); color: #fff; font-weight: 600; }
|
|
417
415
|
.dr-btn:active { opacity: 0.75; }
|
|
@@ -665,48 +663,9 @@
|
|
|
665
663
|
<div id="sk-visible-list" style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:0.15rem 0.6rem;margin-top:0.5rem"></div>
|
|
666
664
|
</div>
|
|
667
665
|
<div class="setting-group" data-stab="more" style="display:none">
|
|
668
|
-
<h4
|
|
669
|
-
<div style="font-size:0.72rem;color:var(--muted);margin-bottom:0.
|
|
670
|
-
<
|
|
671
|
-
<input id="set-machine-name" class="slash-inp" maxlength="32" autocomplete="off" style="flex:1;font-size:0.82rem">
|
|
672
|
-
<button onclick="saveMachineName()" style="background:var(--blue2);color:#fff;border:none;border-radius:8px;padding:0.4rem 0.8rem;font-size:0.78rem;cursor:pointer;white-space:nowrap">保存</button>
|
|
673
|
-
</div>
|
|
674
|
-
<div id="set-machine-status" style="font-size:0.72rem;min-height:1.2em;margin-top:0.25rem;color:var(--muted)"></div>
|
|
675
|
-
</div>
|
|
676
|
-
<div class="setting-group" data-stab="more" style="display:none">
|
|
677
|
-
<h4>@ 引用范围(项目外)</h4>
|
|
678
|
-
<div style="font-size:0.72rem;color:var(--muted);margin-bottom:0.35rem;line-height:1.5">除了任务目录,再把常用的资料文件夹(如公共资料库)加进来,打 @ 时也能搜到里面的文件。</div>
|
|
679
|
-
<div id="atroots-list"></div>
|
|
680
|
-
<button id="atpick-open" onclick="openAtPicker()" style="width:100%;margin-top:0.4rem;background:var(--blue2);color:#fff;border:none;border-radius:8px;padding:0.5rem;font-size:0.8rem;cursor:pointer">📁 选择文件夹添加…</button>
|
|
681
|
-
<div id="atpick" style="display:none;margin-top:0.4rem;border:1px solid var(--border);border-radius:10px;overflow:hidden">
|
|
682
|
-
<div id="atpick-drives" style="display:none;gap:0.3rem;padding:0.4rem 0.5rem 0;flex-wrap:wrap"></div>
|
|
683
|
-
<div style="display:flex;align-items:center;gap:0.35rem;padding:0.4rem 0.5rem;border-bottom:1px solid var(--border)">
|
|
684
|
-
<button id="atpick-up" class="dr-btn" onclick="atPickUp()" title="上一级">⬆ 上一级</button>
|
|
685
|
-
<span id="atpick-cur" style="flex:1;min-width:0;font-size:0.72rem;color:var(--text2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;direction:rtl;text-align:left"></span>
|
|
686
|
-
</div>
|
|
687
|
-
<div id="atpick-list" style="max-height:200px;overflow-y:auto;scrollbar-width:thin;font-size:0.8rem"></div>
|
|
688
|
-
<div style="display:flex;gap:0.4rem;padding:0.45rem 0.5rem;border-top:1px solid var(--border)">
|
|
689
|
-
<button class="dr-btn" onclick="closeAtPicker()" style="flex:1;padding:0.45rem">取消</button>
|
|
690
|
-
<button onclick="addAtPicked()" style="flex:2;background:var(--blue2);color:#fff;border:none;border-radius:6px;padding:0.45rem;font-size:0.78rem;cursor:pointer">✓ 添加当前文件夹</button>
|
|
691
|
-
</div>
|
|
692
|
-
</div>
|
|
693
|
-
<details style="margin-top:0.35rem;font-size:0.72rem;color:var(--muted)">
|
|
694
|
-
<summary style="cursor:pointer">手动输入路径</summary>
|
|
695
|
-
<div style="display:flex;gap:0.4rem;margin-top:0.35rem">
|
|
696
|
-
<input id="atroot-new" class="slash-inp" placeholder="例如 D:\资料库" autocomplete="off" style="flex:1;font-size:0.82rem">
|
|
697
|
-
<button onclick="addAtRoot()" style="background:var(--blue2);color:#fff;border:none;border-radius:8px;padding:0.4rem 0.8rem;font-size:0.78rem;cursor:pointer;white-space:nowrap">添加</button>
|
|
698
|
-
</div>
|
|
699
|
-
</details>
|
|
700
|
-
<div id="atroot-status" style="font-size:0.72rem;min-height:1.2em;margin-top:0.25rem;color:var(--muted)"></div>
|
|
701
|
-
</div>
|
|
702
|
-
<div class="setting-group" data-stab="more" style="display:none">
|
|
703
|
-
<h4>语音识别 (ASR)</h4>
|
|
704
|
-
<div style="font-size:0.72rem;color:var(--muted);margin-bottom:0.35rem">SenseVoice 或兼容服务的地址,留空表示禁用语音</div>
|
|
705
|
-
<div style="display:flex;gap:0.4rem">
|
|
706
|
-
<input id="set-asr-url" class="slash-inp" placeholder="http://192.168.x.x:8765" autocomplete="off" style="flex:1;font-size:0.82rem">
|
|
707
|
-
<button onclick="saveAsrUrl()" style="background:var(--blue2);color:#fff;border:none;border-radius:8px;padding:0.4rem 0.8rem;font-size:0.78rem;cursor:pointer;white-space:nowrap">保存</button>
|
|
708
|
-
</div>
|
|
709
|
-
<div id="set-asr-status" style="font-size:0.72rem;min-height:1.2em;margin-top:0.25rem;color:var(--muted)"></div>
|
|
666
|
+
<h4>全局设置</h4>
|
|
667
|
+
<div style="font-size:0.72rem;color:var(--muted);margin-bottom:0.45rem;line-height:1.5">本机名称、@ 引用范围、语音识别、账户等对所有任务生效的设置,统一放在首页的 ⚙ 设置里。这里的「外观 / 聊天 / 快捷栏」只影响当前这台设备。</div>
|
|
668
|
+
<button onclick="location.href='/?settings=1'" style="width:100%;background:var(--blue2);color:#fff;border:none;border-radius:8px;padding:0.55rem;font-size:0.82rem;cursor:pointer">前往首页 ⚙ 设置 →</button>
|
|
710
669
|
</div>
|
|
711
670
|
<div class="setting-group" data-stab="more" style="display:none">
|
|
712
671
|
<h4>服务管理</h4>
|
|
@@ -1334,93 +1293,6 @@
|
|
|
1334
1293
|
updateEnterHint();
|
|
1335
1294
|
syncSkModeUI();
|
|
1336
1295
|
|
|
1337
|
-
// ── @ 引用范围(项目外目录):客户自助配置,@ 补全可引用这些目录 ──
|
|
1338
|
-
let _atRoots = null;
|
|
1339
|
-
function renderAtRoots() {
|
|
1340
|
-
const el = document.getElementById('atroots-list');
|
|
1341
|
-
if (!el) return;
|
|
1342
|
-
el.innerHTML = (_atRoots || []).length
|
|
1343
|
-
? _atRoots.map((r, i) => `<div style="display:flex;align-items:center;gap:0.4rem;padding:0.25rem 0;font-size:0.78rem"><span style="flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="${escHtml(r)}">📁 ${escHtml(r)}</span><button class="dr-btn" onclick="delAtRoot(${i})">删</button></div>`).join('')
|
|
1344
|
-
: '<div style="font-size:0.72rem;color:var(--muted)">(未配置——@ 只搜任务目录)</div>';
|
|
1345
|
-
}
|
|
1346
|
-
function loadAtRoots() {
|
|
1347
|
-
return fetch('/api/atroots').then(r => r.json()).then(d => { _atRoots = d.roots || []; renderAtRoots(); }).catch(() => {});
|
|
1348
|
-
}
|
|
1349
|
-
async function saveAtRoots(roots) {
|
|
1350
|
-
const st = document.getElementById('atroot-status');
|
|
1351
|
-
try {
|
|
1352
|
-
const r = await fetch('/api/atroots', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ roots }) });
|
|
1353
|
-
const d = await r.json();
|
|
1354
|
-
if (!d.ok) { st.textContent = '保存失败:' + (d.error || ''); return false; }
|
|
1355
|
-
_atRoots = d.roots; renderAtRoots(); st.textContent = '已保存';
|
|
1356
|
-
return true;
|
|
1357
|
-
} catch (e) { st.textContent = '保存出错:' + e.message; return false; }
|
|
1358
|
-
}
|
|
1359
|
-
window.addAtRoot = function() {
|
|
1360
|
-
const inp = document.getElementById('atroot-new');
|
|
1361
|
-
const v = inp.value.trim();
|
|
1362
|
-
if (!v) return;
|
|
1363
|
-
saveAtRoots([...(_atRoots || []), v]).then(ok => { if (ok) inp.value = ''; });
|
|
1364
|
-
};
|
|
1365
|
-
window.delAtRoot = function(i) { saveAtRoots((_atRoots || []).filter((_, j) => j !== i)); };
|
|
1366
|
-
|
|
1367
|
-
// 文件夹点选(复用 /api/dirs 目录浏览):普通用户不会敲绝对路径,点进去选中即可
|
|
1368
|
-
let _atPick = { current: '', parent: null };
|
|
1369
|
-
window.openAtPicker = function() {
|
|
1370
|
-
document.getElementById('atpick').style.display = '';
|
|
1371
|
-
document.getElementById('atpick-open').style.display = 'none';
|
|
1372
|
-
atPickGo('');
|
|
1373
|
-
};
|
|
1374
|
-
window.closeAtPicker = function() {
|
|
1375
|
-
document.getElementById('atpick').style.display = 'none';
|
|
1376
|
-
document.getElementById('atpick-open').style.display = '';
|
|
1377
|
-
};
|
|
1378
|
-
window.atPickGo = async function(path) {
|
|
1379
|
-
const list = document.getElementById('atpick-list');
|
|
1380
|
-
list.innerHTML = '<div style="padding:0.6rem;color:var(--muted);font-size:0.75rem">读取中…</div>';
|
|
1381
|
-
try {
|
|
1382
|
-
const r = await fetch('/api/dirs' + (path ? '?path=' + encodeURIComponent(path) : ''));
|
|
1383
|
-
const d = await readApiJson(r);
|
|
1384
|
-
if (!r.ok) throw new Error(d.error || r.status);
|
|
1385
|
-
_atPick = d;
|
|
1386
|
-
const cur = document.getElementById('atpick-cur');
|
|
1387
|
-
cur.textContent = '\u200e' + d.current; // rtl 截断时保留路径尾部;LRM 防标点跑位
|
|
1388
|
-
cur.title = d.current;
|
|
1389
|
-
document.getElementById('atpick-up').disabled = !d.parent;
|
|
1390
|
-
document.getElementById('atpick-up').style.opacity = d.parent ? '' : '0.4';
|
|
1391
|
-
const dv = document.getElementById('atpick-drives');
|
|
1392
|
-
dv.style.display = (d.drives || []).length ? 'flex' : 'none';
|
|
1393
|
-
dv.innerHTML = '';
|
|
1394
|
-
(d.drives || []).forEach(dr => {
|
|
1395
|
-
const b = document.createElement('button');
|
|
1396
|
-
b.className = 'dr-btn';
|
|
1397
|
-
b.textContent = '💽 ' + dr.replace(/\\$/, '');
|
|
1398
|
-
if (d.current.toUpperCase().startsWith(dr.toUpperCase())) b.style.cssText = 'background:var(--blue2);color:#fff';
|
|
1399
|
-
b.onclick = () => atPickGo(dr);
|
|
1400
|
-
dv.appendChild(b);
|
|
1401
|
-
});
|
|
1402
|
-
list.innerHTML = '';
|
|
1403
|
-
if (!d.dirs.length) list.innerHTML = '<div style="padding:0.6rem;color:var(--muted);font-size:0.75rem">(没有子文件夹)</div>';
|
|
1404
|
-
d.dirs.forEach(x => {
|
|
1405
|
-
const row = document.createElement('div');
|
|
1406
|
-
row.className = 'atpick-row';
|
|
1407
|
-
row.textContent = '📁 ' + x.name;
|
|
1408
|
-
row.onclick = () => atPickGo(x.path);
|
|
1409
|
-
list.appendChild(row);
|
|
1410
|
-
});
|
|
1411
|
-
} catch (e) {
|
|
1412
|
-
list.innerHTML = '<div style="padding:0.6rem;color:var(--red);font-size:0.75rem">读取失败:' + escHtml(String(e.message || e)) +
|
|
1413
|
-
(String(e.message).includes('权限') ? '(仅管理员可浏览目录,可展开下方「手动输入路径」)' : '') + '</div>';
|
|
1414
|
-
}
|
|
1415
|
-
};
|
|
1416
|
-
window.atPickUp = function() { if (_atPick.parent) atPickGo(_atPick.parent); };
|
|
1417
|
-
window.addAtPicked = async function() {
|
|
1418
|
-
const p = _atPick.current;
|
|
1419
|
-
if (!p) return;
|
|
1420
|
-
if ((_atRoots || []).includes(p)) { document.getElementById('atroot-status').textContent = '这个文件夹已经在列表里了'; return; }
|
|
1421
|
-
if (await saveAtRoots([...(_atRoots || []), p])) closeAtPicker();
|
|
1422
|
-
};
|
|
1423
|
-
|
|
1424
1296
|
// 输出累积
|
|
1425
1297
|
let outputBuffer = '';
|
|
1426
1298
|
let outputTimer = null;
|
|
@@ -3535,7 +3407,7 @@
|
|
|
3535
3407
|
overlay.appendChild(wrap);
|
|
3536
3408
|
};
|
|
3537
3409
|
|
|
3538
|
-
window.openSettings = function() { document.getElementById('settings-backdrop').classList.add('open'); document.getElementById('settings-panel').classList.add('open'); syncSettingsUI();
|
|
3410
|
+
window.openSettings = function() { document.getElementById('settings-backdrop').classList.add('open'); document.getElementById('settings-panel').classList.add('open'); syncSettingsUI(); applySettingsTab(localStorage.getItem('myhi_settings_tab') || 'look'); };
|
|
3539
3411
|
// 设置抽屉分类页签(1.3.174:一屏只看一类,不再长条堆叠)
|
|
3540
3412
|
function applySettingsTab(tab) {
|
|
3541
3413
|
document.querySelectorAll('#settings-tabs .stab').forEach(b => b.classList.toggle('active', b.dataset.tab === tab));
|
|
@@ -4077,17 +3949,6 @@
|
|
|
4077
3949
|
document.getElementById('set-rawclip').checked = settings.rawClip !== false;
|
|
4078
3950
|
syncEnterModeUI();
|
|
4079
3951
|
syncSkModeUI();
|
|
4080
|
-
// 从服务端加载 ASR URL
|
|
4081
|
-
fetch('/api/server-config').then(r => r.json()).then(d => {
|
|
4082
|
-
document.getElementById('set-asr-url').value = d.asrUrl || '';
|
|
4083
|
-
}).catch(() => {});
|
|
4084
|
-
fetch('/api/machine-name').then(r => r.json()).then(d => {
|
|
4085
|
-
const inp = document.getElementById('set-machine-name');
|
|
4086
|
-
inp.placeholder = d.hostname || '';
|
|
4087
|
-
inp.value = d.name === d.hostname ? '' : (d.name || '');
|
|
4088
|
-
inp.disabled = !!d.fromEnv;
|
|
4089
|
-
document.getElementById('set-machine-status').textContent = d.fromEnv ? '已由环境变量 MYHI_NAME 指定,此处不可改' : '';
|
|
4090
|
-
}).catch(() => {});
|
|
4091
3952
|
}
|
|
4092
3953
|
document.getElementById('set-parse').addEventListener('change', (e) => { settings.parseOutput = e.target.checked; saveSettings(); });
|
|
4093
3954
|
document.getElementById('set-thinking').addEventListener('change', (e) => { settings.showThinking = e.target.checked; saveSettings(); });
|
|
@@ -4099,50 +3960,6 @@
|
|
|
4099
3960
|
chatArea.querySelectorAll('.msg-raw').forEach(m => { delete m.dataset.open; rawClipSync(m); });
|
|
4100
3961
|
});
|
|
4101
3962
|
|
|
4102
|
-
window.saveMachineName = async function() {
|
|
4103
|
-
const statusEl = document.getElementById('set-machine-status');
|
|
4104
|
-
try {
|
|
4105
|
-
const resp = await fetch('/api/machine-name', {
|
|
4106
|
-
method: 'POST',
|
|
4107
|
-
headers: { 'Content-Type': 'application/json' },
|
|
4108
|
-
body: JSON.stringify({ name: document.getElementById('set-machine-name').value.trim() }),
|
|
4109
|
-
});
|
|
4110
|
-
const data = await readApiJson(resp);
|
|
4111
|
-
if (!data.ok) throw new Error(data.error || '未知错误');
|
|
4112
|
-
window.myhiSetMachineName?.(data.name);
|
|
4113
|
-
statusEl.style.color = 'var(--green)';
|
|
4114
|
-
statusEl.textContent = `已保存:${data.name}(其他已打开的页面刷新后生效)`;
|
|
4115
|
-
} catch (e) {
|
|
4116
|
-
statusEl.style.color = 'var(--red)';
|
|
4117
|
-
statusEl.textContent = '保存失败: ' + e.message;
|
|
4118
|
-
}
|
|
4119
|
-
};
|
|
4120
|
-
|
|
4121
|
-
window.saveAsrUrl = async function() {
|
|
4122
|
-
const url = document.getElementById('set-asr-url').value.trim();
|
|
4123
|
-
const statusEl = document.getElementById('set-asr-status');
|
|
4124
|
-
statusEl.style.color = 'var(--muted)';
|
|
4125
|
-
statusEl.textContent = '保存中...';
|
|
4126
|
-
try {
|
|
4127
|
-
const resp = await fetch('/api/server-config', {
|
|
4128
|
-
method: 'POST',
|
|
4129
|
-
headers: { 'Content-Type': 'application/json' },
|
|
4130
|
-
body: JSON.stringify({ asrUrl: url }),
|
|
4131
|
-
});
|
|
4132
|
-
const data = await resp.json();
|
|
4133
|
-
if (data.ok) {
|
|
4134
|
-
statusEl.style.color = 'var(--green)';
|
|
4135
|
-
statusEl.textContent = url ? '已保存,重启后生效(如已设置 ASR_URL 环境变量则以环境变量为准)' : '已清空';
|
|
4136
|
-
} else {
|
|
4137
|
-
statusEl.style.color = 'var(--red)';
|
|
4138
|
-
statusEl.textContent = '保存失败: ' + (data.error || '未知错误');
|
|
4139
|
-
}
|
|
4140
|
-
} catch (e) {
|
|
4141
|
-
statusEl.style.color = 'var(--red)';
|
|
4142
|
-
statusEl.textContent = '请求失败: ' + e.message;
|
|
4143
|
-
}
|
|
4144
|
-
};
|
|
4145
|
-
|
|
4146
3963
|
window.restartMyhi = async function() {
|
|
4147
3964
|
if (!confirm('确认重启 myhi 服务?\n重启期间连接会短暂中断,页面将自动重连。')) return;
|
|
4148
3965
|
try {
|
package/dist/index.html
CHANGED
|
@@ -57,7 +57,8 @@
|
|
|
57
57
|
display: flex;
|
|
58
58
|
align-items: center;
|
|
59
59
|
justify-content: space-between;
|
|
60
|
-
|
|
60
|
+
gap: 0.75rem;
|
|
61
|
+
padding: 0 0.75rem;
|
|
61
62
|
padding-top: max(1rem, env(safe-area-inset-top));
|
|
62
63
|
padding-bottom: 0.75rem;
|
|
63
64
|
background: var(--bg);
|
|
@@ -96,6 +97,35 @@
|
|
|
96
97
|
font-size: 0.75rem; padding: 0.3rem 0.6rem; border-radius: 6px; cursor: pointer;
|
|
97
98
|
width: auto; white-space: nowrap; flex-shrink: 0;
|
|
98
99
|
}
|
|
100
|
+
.hd-btn:hover { color: var(--text); border-color: var(--border2); }
|
|
101
|
+
#user-name { max-width: 7rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
102
|
+
|
|
103
|
+
/* ── 设置 sheet(全局设置收口:外观 / 本机名称 / @ 引用范围 / 语音 / 账户 / 服务)── */
|
|
104
|
+
#set-backdrop { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.55); z-index: 30; }
|
|
105
|
+
#set-sheet { display: none; position: fixed; left: 0; right: 0; bottom: 0; z-index: 31; max-width: 640px; margin: 0 auto;
|
|
106
|
+
background: var(--surface); border-radius: 16px 16px 0 0; padding: 0 1rem calc(1rem + env(safe-area-inset-bottom)); max-height: 88vh; overflow-y: auto; }
|
|
107
|
+
#set-sheet.open, #set-backdrop.open { display: block; }
|
|
108
|
+
.set-sec { padding: 0.75rem 0; border-top: 1px solid var(--border); }
|
|
109
|
+
.sheet-title + .set-sec { border-top: none; padding-top: 0; }
|
|
110
|
+
.set-sec h4 { font-size: 0.85rem; font-weight: 600; margin-bottom: 0.2rem; display: flex; align-items: center; gap: 0.4rem; }
|
|
111
|
+
.set-scope { font-size: 0.66rem; font-weight: 400; color: var(--muted); border: 1px solid var(--border2); border-radius: 4px; padding: 0 0.3rem; }
|
|
112
|
+
.set-hint { font-size: 0.72rem; color: var(--muted); line-height: 1.5; margin-bottom: 0.4rem; }
|
|
113
|
+
.set-status { font-size: 0.72rem; min-height: 1.1em; margin-top: 0.25rem; color: var(--muted); }
|
|
114
|
+
.set-row { display: flex; gap: 0.4rem; }
|
|
115
|
+
.set-inp { flex: 1; min-width: 0; background: var(--bg); border: 1px solid var(--border); border-radius: 8px; color: var(--text); font-size: 0.82rem; padding: 0.45rem 0.6rem; outline: none; }
|
|
116
|
+
.set-inp:focus { border-color: var(--accent); }
|
|
117
|
+
.set-btn { flex-shrink: 0; white-space: nowrap; background: var(--accent); color: #fff; border: none; border-radius: 8px; padding: 0.45rem 0.85rem; font-size: 0.78rem; cursor: pointer; }
|
|
118
|
+
.set-btn.ghost { background: var(--bg); color: var(--text2); border: 1px solid var(--border); }
|
|
119
|
+
.set-btn.danger { background: transparent; color: var(--red); border: 1px solid var(--red); }
|
|
120
|
+
.set-seg { display: flex; gap: 0.35rem; flex-wrap: wrap; }
|
|
121
|
+
.set-seg button { flex: 1; min-width: 3rem; background: var(--bg); color: var(--muted); border: 1px solid var(--border); border-radius: 8px; padding: 0.45rem 0.3rem; font-size: 0.8rem; cursor: pointer; white-space: nowrap; }
|
|
122
|
+
.set-seg button.on { background: var(--accent); border-color: var(--accent); color: #fff; }
|
|
123
|
+
.set-sub { font-size: 0.75rem; color: var(--text2); margin: 0.5rem 0 0.3rem; }
|
|
124
|
+
.atr-row { display: flex; align-items: center; gap: 0.4rem; padding: 0.2rem 0; font-size: 0.78rem; }
|
|
125
|
+
.atr-row span { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
126
|
+
.atpick-row { padding: 0.42rem 0.7rem; cursor: pointer; color: var(--text2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 0.8rem; }
|
|
127
|
+
.atpick-row:hover { background: var(--bg); color: var(--text); }
|
|
128
|
+
.dr-btn { flex-shrink: 0; white-space: nowrap; padding: 0.28rem 0.7rem; font-size: 0.72rem; border: 1px solid var(--border); border-radius: 6px; background: var(--bg); color: var(--text2); cursor: pointer; }
|
|
99
129
|
/* 手机(≤540px):藏用户名、文字标签只留图标,一行放下且不憋屈 */
|
|
100
130
|
@media (max-width: 540px) {
|
|
101
131
|
#user-name { display: none; }
|
|
@@ -522,20 +552,18 @@
|
|
|
522
552
|
<div class="logo">my<span>hi</span></div>
|
|
523
553
|
<div class="hd-actions">
|
|
524
554
|
<span id="user-name" style="font-size:0.8rem;color:var(--muted)"></span>
|
|
525
|
-
<button id="grid-btn" class="hd-btn" onclick="location.href='/grid'" title="分屏工作台(多任务并排)"
|
|
526
|
-
<button id="appearance-btn" class="hd-btn" onclick="openAppearance()" title="外观">🎨</button>
|
|
527
|
-
<button id="help-btn" class="hd-btn" onclick="openHelpSheet()" title="使用说明 / 快速上手">❓<span class="hd-txt"> 帮助</span></button>
|
|
555
|
+
<button id="grid-btn" class="hd-btn" onclick="location.href='/grid'" title="分屏工作台(多任务并排)">▦<span class="hd-txt"> 分屏</span></button>
|
|
528
556
|
<button id="doctor-btn" class="hd-btn" onclick="openDoctor()" title="会话体检">🩺<span class="hd-txt"> 体检</span></button>
|
|
529
557
|
<button id="usage-btn" class="hd-btn" onclick="showUsage()" title="用量">📊<span class="hd-txt"> 用量</span></button>
|
|
530
|
-
<button id="
|
|
531
|
-
<button id="
|
|
558
|
+
<button id="help-btn" class="hd-btn" onclick="openHelpSheet()" title="使用说明 / 快速上手">❓<span class="hd-txt"> 帮助</span></button>
|
|
559
|
+
<button id="settings-btn" class="hd-btn" onclick="openSettings()" title="设置(外观 / 本机名称 / @ 引用范围 / 语音 / 账户)">⚙<span class="hd-txt"> 设置</span></button>
|
|
532
560
|
<div id="conn-dot"></div>
|
|
533
561
|
</div>
|
|
534
562
|
</div>
|
|
535
563
|
|
|
536
564
|
<!-- 升级管理已移至 /admin -->
|
|
537
565
|
|
|
538
|
-
<div id="view-controls" style="display:flex;flex-direction:column;gap:0.5rem;
|
|
566
|
+
<div id="view-controls" style="display:flex;flex-direction:column;gap:0.5rem;padding:0.75rem 0.75rem 0">
|
|
539
567
|
<!-- 会话搜索:任务多了光看标题认不出,连首条消息/目录/标签一起搜 -->
|
|
540
568
|
<div style="display:flex;align-items:center;gap:0.4rem">
|
|
541
569
|
<input id="sess-search" type="search" placeholder="搜任务:标题 / 首条需求 / 目录 / 标签"
|
|
@@ -625,6 +653,77 @@
|
|
|
625
653
|
<button onclick="closeNoteSheet()" style="width:100%;background:var(--bg,var(--bg));color:var(--muted,var(--muted));border:1px solid var(--border,var(--border));border-radius:10px;padding:0.65rem;font-size:0.85rem;cursor:pointer;margin-top:0.5rem">取消</button>
|
|
626
654
|
</div>
|
|
627
655
|
|
|
656
|
+
<!-- 设置 sheet:首页是全局设置的唯一入口(会话页 ⚙ 只管本设备的聊天显示/快捷栏) -->
|
|
657
|
+
<div id="set-backdrop" onclick="closeSettings()"></div>
|
|
658
|
+
<div id="set-sheet">
|
|
659
|
+
<div class="sheet-handle"></div>
|
|
660
|
+
<div class="sheet-title">⚙ 设置</div>
|
|
661
|
+
<div class="set-sec">
|
|
662
|
+
<h4>外观 <span class="set-scope">本设备</span></h4>
|
|
663
|
+
<div class="set-sub">主题</div>
|
|
664
|
+
<div class="set-seg" id="set-theme"></div>
|
|
665
|
+
<div class="set-sub">字号</div>
|
|
666
|
+
<div class="set-seg" id="set-font"></div>
|
|
667
|
+
</div>
|
|
668
|
+
<div class="set-sec">
|
|
669
|
+
<h4>本机名称 <span class="set-scope">全局</span></h4>
|
|
670
|
+
<div class="set-hint">多台电脑都跑 myhi 时用来区分:浏览器标题以它开头,标签图标按名字自动配色。留空 = 用电脑主机名。</div>
|
|
671
|
+
<div class="set-row">
|
|
672
|
+
<input id="set-machine-name" class="set-inp" maxlength="32" autocomplete="off">
|
|
673
|
+
<button class="set-btn" onclick="saveMachineName()">保存</button>
|
|
674
|
+
</div>
|
|
675
|
+
<div id="set-machine-status" class="set-status"></div>
|
|
676
|
+
</div>
|
|
677
|
+
<div class="set-sec">
|
|
678
|
+
<h4>@ 引用范围 <span class="set-scope">全局</span></h4>
|
|
679
|
+
<div class="set-hint">除了任务目录,再把常用的资料文件夹(如公共资料库)加进来,打 @ 时也能搜到里面的文件。</div>
|
|
680
|
+
<div id="atroots-list"></div>
|
|
681
|
+
<button id="atpick-open" class="set-btn" onclick="openAtPicker()" style="width:100%;margin-top:0.4rem">📁 选择文件夹添加…</button>
|
|
682
|
+
<div id="atpick" style="display:none;margin-top:0.4rem;border:1px solid var(--border);border-radius:10px;overflow:hidden">
|
|
683
|
+
<div id="atpick-drives" style="display:none;gap:0.3rem;padding:0.4rem 0.5rem 0;flex-wrap:wrap"></div>
|
|
684
|
+
<div style="display:flex;align-items:center;gap:0.35rem;padding:0.4rem 0.5rem;border-bottom:1px solid var(--border)">
|
|
685
|
+
<button id="atpick-up" class="dr-btn" onclick="atPickUp()" title="上一级">⬆ 上一级</button>
|
|
686
|
+
<span id="atpick-cur" style="flex:1;min-width:0;font-size:0.72rem;color:var(--text2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;direction:rtl;text-align:left"></span>
|
|
687
|
+
</div>
|
|
688
|
+
<div id="atpick-list" style="max-height:200px;overflow-y:auto;scrollbar-width:thin"></div>
|
|
689
|
+
<div style="display:flex;gap:0.4rem;padding:0.45rem 0.5rem;border-top:1px solid var(--border)">
|
|
690
|
+
<button class="set-btn ghost" onclick="closeAtPicker()" style="flex:1">取消</button>
|
|
691
|
+
<button class="set-btn" onclick="addAtPicked()" style="flex:2">✓ 添加当前文件夹</button>
|
|
692
|
+
</div>
|
|
693
|
+
</div>
|
|
694
|
+
<details style="margin-top:0.35rem;font-size:0.72rem;color:var(--muted)">
|
|
695
|
+
<summary style="cursor:pointer">手动输入路径</summary>
|
|
696
|
+
<div class="set-row" style="margin-top:0.35rem">
|
|
697
|
+
<input id="atroot-new" class="set-inp" placeholder="例如 D:\资料库" autocomplete="off">
|
|
698
|
+
<button class="set-btn" onclick="addAtRoot()">添加</button>
|
|
699
|
+
</div>
|
|
700
|
+
</details>
|
|
701
|
+
<div id="atroot-status" class="set-status"></div>
|
|
702
|
+
</div>
|
|
703
|
+
<div class="set-sec">
|
|
704
|
+
<h4>语音识别 (ASR) <span class="set-scope">全局</span></h4>
|
|
705
|
+
<div class="set-hint">SenseVoice 或兼容服务的地址,留空表示禁用语音。</div>
|
|
706
|
+
<div class="set-row">
|
|
707
|
+
<input id="set-asr-url" class="set-inp" placeholder="http://192.168.x.x:8765" autocomplete="off">
|
|
708
|
+
<button class="set-btn" onclick="saveAsrUrl()">保存</button>
|
|
709
|
+
</div>
|
|
710
|
+
<div id="set-asr-status" class="set-status"></div>
|
|
711
|
+
</div>
|
|
712
|
+
<div class="set-sec" id="set-account" style="display:none">
|
|
713
|
+
<h4>账户 <span id="set-account-name" class="set-scope"></span></h4>
|
|
714
|
+
<div class="set-row" style="margin-top:0.4rem">
|
|
715
|
+
<button id="passwd-btn" class="set-btn ghost" onclick="closeSettings();openPasswdSheet()" style="flex:1;display:none">🔑 修改密码</button>
|
|
716
|
+
<button id="logout-btn" class="set-btn danger" onclick="doLogout()" style="flex:1;display:none">退出登录</button>
|
|
717
|
+
</div>
|
|
718
|
+
</div>
|
|
719
|
+
<div class="set-sec">
|
|
720
|
+
<h4>服务</h4>
|
|
721
|
+
<div class="set-hint">聊天显示、回车键、快捷栏按钮在会话页的 ⚙ 设置里调(只影响本设备)。</div>
|
|
722
|
+
<button class="set-btn danger" onclick="restartMyhi()" style="width:100%">重启 myhi</button>
|
|
723
|
+
<div id="set-restart-status" class="set-status"></div>
|
|
724
|
+
</div>
|
|
725
|
+
</div>
|
|
726
|
+
|
|
628
727
|
<!-- 修改密码 sheet(成员用户自助改密;管理员改密码在 /admin) -->
|
|
629
728
|
<div id="passwd-backdrop" onclick="closePasswdSheet()" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,0.55);z-index:30"></div>
|
|
630
729
|
<div id="passwd-sheet" style="display:none;position:fixed;left:0;right:0;bottom:0;z-index:31;background:var(--surface);border-radius:16px 16px 0 0;padding:1rem 1rem calc(1rem + env(safe-area-inset-bottom));max-height:88vh;overflow-y:auto">
|
|
@@ -751,10 +850,13 @@
|
|
|
751
850
|
fetch('/api/me').then(r => r.json()).then(data => {
|
|
752
851
|
if ((data.exclusive || data.hasUsers) && data.name) {
|
|
753
852
|
document.getElementById('user-name').textContent = data.name;
|
|
853
|
+
document.getElementById('set-account-name').textContent = data.name;
|
|
854
|
+
document.getElementById('set-account').style.display = '';
|
|
754
855
|
document.getElementById('logout-btn').style.display = '';
|
|
755
856
|
}
|
|
756
857
|
// 成员用户(users.json 登录)才显示自助改密入口;管理员改密码在 /admin
|
|
757
858
|
if (data.hasUsers && data.role === 'operator') {
|
|
859
|
+
document.getElementById('set-account').style.display = '';
|
|
758
860
|
document.getElementById('passwd-btn').style.display = '';
|
|
759
861
|
}
|
|
760
862
|
if (data.dir) {
|
|
@@ -879,32 +981,177 @@
|
|
|
879
981
|
const meta = document.querySelector('meta[name="theme-color"]');
|
|
880
982
|
if (meta) meta.setAttribute('content', THEME_META[t] || '#0d1117');
|
|
881
983
|
}
|
|
882
|
-
|
|
984
|
+
// ── 设置 sheet ─────────────────────────────────────────
|
|
985
|
+
const THEMES = [['system','跟随系统'],['dark','深色'],['light','浅色'],['black','纯黑']];
|
|
986
|
+
const FONTS = [['small','小'],['medium','中'],['large','大']];
|
|
987
|
+
function renderAppearance() {
|
|
883
988
|
const t = localStorage.getItem('myhi_theme') || 'dark';
|
|
884
989
|
const f = localStorage.getItem('myhi_font') || 'medium';
|
|
885
|
-
const
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
990
|
+
const seg = (key, opts, cur) => opts.map(([v, l]) => `<button data-k="${key}" data-v="${v}" class="${v === cur ? 'on' : ''}">${l}</button>`).join('');
|
|
991
|
+
document.getElementById('set-theme').innerHTML = seg('myhi_theme', THEMES, t);
|
|
992
|
+
document.getElementById('set-font').innerHTML = seg('myhi_font', FONTS, f);
|
|
993
|
+
}
|
|
994
|
+
document.getElementById('set-sheet').addEventListener('click', (e) => {
|
|
995
|
+
const b = e.target.closest('button[data-k]');
|
|
996
|
+
if (!b) return;
|
|
997
|
+
localStorage.setItem(b.dataset.k, b.dataset.v);
|
|
998
|
+
applyAppearance(); renderAppearance();
|
|
999
|
+
});
|
|
1000
|
+
function setStatus(id, text, color) {
|
|
1001
|
+
const el = document.getElementById(id);
|
|
1002
|
+
el.style.color = color ? `var(--${color})` : 'var(--muted)';
|
|
1003
|
+
el.textContent = text;
|
|
1004
|
+
}
|
|
1005
|
+
async function readJson(r) {
|
|
1006
|
+
// 旧 server 不认新接口时回的是 404 HTML,别让 JSON.parse 报一串天书
|
|
1007
|
+
const txt = await r.text();
|
|
1008
|
+
try { return JSON.parse(txt); } catch { throw new Error(r.status === 404 ? '服务端版本较旧,请重启 myhi 后再试' : `HTTP ${r.status}`); }
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
window.openSettings = function() {
|
|
1012
|
+
renderAppearance();
|
|
1013
|
+
['set-machine-status', 'atroot-status', 'set-asr-status', 'set-restart-status'].forEach(id => setStatus(id, ''));
|
|
1014
|
+
closeAtPicker();
|
|
1015
|
+
document.getElementById('set-backdrop').classList.add('open');
|
|
1016
|
+
document.getElementById('set-sheet').classList.add('open');
|
|
1017
|
+
fetch('/api/machine-name').then(readJson).then(d => {
|
|
1018
|
+
const inp = document.getElementById('set-machine-name');
|
|
1019
|
+
inp.placeholder = d.hostname || '';
|
|
1020
|
+
inp.value = d.name === d.hostname ? '' : (d.name || '');
|
|
1021
|
+
inp.disabled = !!d.fromEnv;
|
|
1022
|
+
if (d.fromEnv) setStatus('set-machine-status', '已由环境变量 MYHI_NAME 指定,此处不可改');
|
|
1023
|
+
}).catch(() => {});
|
|
1024
|
+
fetch('/api/server-config').then(readJson).then(d => {
|
|
1025
|
+
document.getElementById('set-asr-url').value = d.asrUrl || '';
|
|
1026
|
+
}).catch(() => {});
|
|
1027
|
+
loadAtRoots();
|
|
907
1028
|
};
|
|
1029
|
+
window.closeSettings = function() {
|
|
1030
|
+
document.getElementById('set-backdrop').classList.remove('open');
|
|
1031
|
+
document.getElementById('set-sheet').classList.remove('open');
|
|
1032
|
+
};
|
|
1033
|
+
document.addEventListener('keydown', (e) => {
|
|
1034
|
+
if (e.key === 'Escape' && document.getElementById('set-sheet').classList.contains('open')) closeSettings();
|
|
1035
|
+
});
|
|
1036
|
+
|
|
1037
|
+
window.saveMachineName = async function() {
|
|
1038
|
+
try {
|
|
1039
|
+
const r = await fetch('/api/machine-name', {
|
|
1040
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
1041
|
+
body: JSON.stringify({ name: document.getElementById('set-machine-name').value.trim() }),
|
|
1042
|
+
});
|
|
1043
|
+
const d = await readJson(r);
|
|
1044
|
+
if (!d.ok) throw new Error(d.error || '未知错误');
|
|
1045
|
+
window.myhiSetMachineName?.(d.name);
|
|
1046
|
+
setStatus('set-machine-status', `已保存:${d.name}(其他已打开的页面刷新后生效)`, 'green');
|
|
1047
|
+
} catch (e) { setStatus('set-machine-status', '保存失败:' + e.message, 'red'); }
|
|
1048
|
+
};
|
|
1049
|
+
|
|
1050
|
+
window.saveAsrUrl = async function() {
|
|
1051
|
+
const url = document.getElementById('set-asr-url').value.trim();
|
|
1052
|
+
setStatus('set-asr-status', '保存中…');
|
|
1053
|
+
try {
|
|
1054
|
+
const r = await fetch('/api/server-config', {
|
|
1055
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ asrUrl: url }),
|
|
1056
|
+
});
|
|
1057
|
+
const d = await readJson(r);
|
|
1058
|
+
if (!d.ok) throw new Error(d.error || '未知错误');
|
|
1059
|
+
setStatus('set-asr-status', url ? '已保存,重启后生效(如已设置 ASR_URL 环境变量则以环境变量为准)' : '已清空', 'green');
|
|
1060
|
+
} catch (e) { setStatus('set-asr-status', '保存失败:' + e.message, 'red'); }
|
|
1061
|
+
};
|
|
1062
|
+
|
|
1063
|
+
window.restartMyhi = async function() {
|
|
1064
|
+
if (!confirm('确认重启 myhi 服务?\n重启期间连接会短暂中断,页面将自动重连。')) return;
|
|
1065
|
+
try { await fetch('/api/restart', { method: 'POST' }); } catch {}
|
|
1066
|
+
setStatus('set-restart-status', '服务重启中,请稍候…');
|
|
1067
|
+
};
|
|
1068
|
+
|
|
1069
|
+
// @ 引用范围(项目外目录):@ 补全可引用这些目录
|
|
1070
|
+
let _atRoots = [];
|
|
1071
|
+
function renderAtRoots() {
|
|
1072
|
+
document.getElementById('atroots-list').innerHTML = _atRoots.length
|
|
1073
|
+
? _atRoots.map((r, i) => `<div class="atr-row"><span title="${esc(r)}">📁 ${esc(r)}</span><button class="dr-btn" onclick="delAtRoot(${i})">删</button></div>`).join('')
|
|
1074
|
+
: '<div class="set-hint" style="margin:0">(未配置——@ 只搜任务目录)</div>';
|
|
1075
|
+
}
|
|
1076
|
+
function loadAtRoots() {
|
|
1077
|
+
fetch('/api/atroots').then(readJson).then(d => { _atRoots = d.roots || []; renderAtRoots(); }).catch(() => {});
|
|
1078
|
+
}
|
|
1079
|
+
async function saveAtRoots(roots) {
|
|
1080
|
+
try {
|
|
1081
|
+
const r = await fetch('/api/atroots', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ roots }) });
|
|
1082
|
+
const d = await readJson(r);
|
|
1083
|
+
if (!d.ok) throw new Error(d.error || '未知错误');
|
|
1084
|
+
_atRoots = d.roots; renderAtRoots(); setStatus('atroot-status', '已保存', 'green');
|
|
1085
|
+
return true;
|
|
1086
|
+
} catch (e) { setStatus('atroot-status', '保存失败:' + e.message, 'red'); return false; }
|
|
1087
|
+
}
|
|
1088
|
+
window.addAtRoot = function() {
|
|
1089
|
+
const inp = document.getElementById('atroot-new');
|
|
1090
|
+
const v = inp.value.trim();
|
|
1091
|
+
if (v) saveAtRoots([..._atRoots, v]).then(ok => { if (ok) inp.value = ''; });
|
|
1092
|
+
};
|
|
1093
|
+
window.delAtRoot = function(i) { saveAtRoots(_atRoots.filter((_, j) => j !== i)); };
|
|
1094
|
+
|
|
1095
|
+
// 文件夹点选(复用 /api/dirs 目录浏览):普通用户不会敲绝对路径,点进去选中即可
|
|
1096
|
+
let _atPick = { current: '', parent: null };
|
|
1097
|
+
window.openAtPicker = function() {
|
|
1098
|
+
document.getElementById('atpick').style.display = '';
|
|
1099
|
+
document.getElementById('atpick-open').style.display = 'none';
|
|
1100
|
+
atPickGo('');
|
|
1101
|
+
};
|
|
1102
|
+
window.closeAtPicker = function() {
|
|
1103
|
+
document.getElementById('atpick').style.display = 'none';
|
|
1104
|
+
document.getElementById('atpick-open').style.display = '';
|
|
1105
|
+
};
|
|
1106
|
+
window.atPickGo = async function(path) {
|
|
1107
|
+
const list = document.getElementById('atpick-list');
|
|
1108
|
+
list.innerHTML = '<div class="set-hint" style="padding:0.6rem;margin:0">读取中…</div>';
|
|
1109
|
+
try {
|
|
1110
|
+
const r = await fetch('/api/dirs' + (path ? '?path=' + encodeURIComponent(path) : ''));
|
|
1111
|
+
const d = await readJson(r);
|
|
1112
|
+
if (!r.ok) throw new Error(d.error || r.status);
|
|
1113
|
+
_atPick = d;
|
|
1114
|
+
const cur = document.getElementById('atpick-cur');
|
|
1115
|
+
cur.textContent = '\u200e' + d.current; // rtl 截断时保留路径尾部;LRM 防标点跑位
|
|
1116
|
+
cur.title = d.current;
|
|
1117
|
+
const up = document.getElementById('atpick-up');
|
|
1118
|
+
up.disabled = !d.parent; up.style.opacity = d.parent ? '' : '0.4';
|
|
1119
|
+
const dv = document.getElementById('atpick-drives');
|
|
1120
|
+
dv.style.display = (d.drives || []).length ? 'flex' : 'none';
|
|
1121
|
+
dv.innerHTML = '';
|
|
1122
|
+
(d.drives || []).forEach(dr => {
|
|
1123
|
+
const b = document.createElement('button');
|
|
1124
|
+
b.className = 'dr-btn';
|
|
1125
|
+
b.textContent = '💽 ' + dr.replace(/\\$/, '');
|
|
1126
|
+
if (d.current.toUpperCase().startsWith(dr.toUpperCase())) b.style.cssText = 'background:var(--accent);color:#fff;border-color:var(--accent)';
|
|
1127
|
+
b.onclick = () => atPickGo(dr);
|
|
1128
|
+
dv.appendChild(b);
|
|
1129
|
+
});
|
|
1130
|
+
list.innerHTML = d.dirs.length ? '' : '<div class="set-hint" style="padding:0.6rem;margin:0">(没有子文件夹)</div>';
|
|
1131
|
+
d.dirs.forEach(x => {
|
|
1132
|
+
const row = document.createElement('div');
|
|
1133
|
+
row.className = 'atpick-row';
|
|
1134
|
+
row.textContent = '📁 ' + x.name;
|
|
1135
|
+
row.onclick = () => atPickGo(x.path);
|
|
1136
|
+
list.appendChild(row);
|
|
1137
|
+
});
|
|
1138
|
+
} catch (e) {
|
|
1139
|
+
list.innerHTML = '<div style="padding:0.6rem;color:var(--red);font-size:0.75rem">读取失败:' + esc(String(e.message || e)) +
|
|
1140
|
+
(String(e.message).includes('权限') ? '(仅管理员可浏览目录,可展开下方「手动输入路径」)' : '') + '</div>';
|
|
1141
|
+
}
|
|
1142
|
+
};
|
|
1143
|
+
window.atPickUp = function() { if (_atPick.parent) atPickGo(_atPick.parent); };
|
|
1144
|
+
window.addAtPicked = async function() {
|
|
1145
|
+
const p = _atPick.current;
|
|
1146
|
+
if (!p) return;
|
|
1147
|
+
if (_atRoots.includes(p)) { setStatus('atroot-status', '这个文件夹已经在列表里了'); return; }
|
|
1148
|
+
if (await saveAtRoots([..._atRoots, p])) closeAtPicker();
|
|
1149
|
+
};
|
|
1150
|
+
// 会话页「更多 → 全局设置」跳过来:/?settings=1 直接打开
|
|
1151
|
+
if (new URLSearchParams(location.search).get('settings')) {
|
|
1152
|
+
history.replaceState(null, '', location.pathname);
|
|
1153
|
+
setTimeout(openSettings, 0);
|
|
1154
|
+
}
|
|
908
1155
|
|
|
909
1156
|
// HTML 转义,防止 XSS
|
|
910
1157
|
function esc(s) {
|
package/package.json
CHANGED