@wendongfly/myhi 1.3.181 → 1.3.184
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/bin/myhi.js +873 -873
- package/dist/chat.html +85 -201
- package/dist/index.html +296 -30
- package/dist/index.js +4 -4
- package/dist/index.min.js +2 -2
- package/package.json +1 -1
package/dist/chat.html
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
}catch(e){}})();
|
|
23
23
|
</script>
|
|
24
24
|
<style>
|
|
25
|
+
body.no-desktop [onclick="fpOpenFolder()"] { display: none !important; }
|
|
25
26
|
/* ── 主题 & 字号(默认深色,值=原配色,深色零变化)───────── */
|
|
26
27
|
:root {
|
|
27
28
|
--bg:#0d1117; --surface:#161b22; --surface2:#21262d; --surface3:#1c2128; --border:#30363d;
|
|
@@ -267,6 +268,8 @@
|
|
|
267
268
|
#mention-box .mi .m-name { color: var(--text); white-space: nowrap; }
|
|
268
269
|
#mention-box .mi .m-hint { color: var(--muted); font-size: 0.66rem; margin-left: auto; padding-left: 0.5rem; flex-shrink: 0; }
|
|
269
270
|
#mention-box .mi-empty { padding: 0.55rem 0.7rem; color: var(--muted); font-size: 0.75rem; }
|
|
271
|
+
#mention-box .mi-head { padding: 0.4rem 0.6rem 0.15rem; color: var(--muted); font-size: 0.66rem; font-weight: 600; letter-spacing: 0.04em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
272
|
+
#mention-box .mi-tip { padding: 0.35rem 0.6rem 0.2rem; color: var(--dim, var(--muted)); font-size: 0.64rem; border-top: 1px solid var(--border); margin-top: 0.2rem; }
|
|
270
273
|
#cmd-input { display: block; width: 100%; background: transparent; color: var(--text); border: none; padding: 0.6rem 0.75rem 0.3rem; font-family: 'SF Mono', 'Cascadia Code', 'Consolas', monospace; font-size: 0.85rem; outline: none; resize: none; line-height: 1.5; max-height: 140px; overflow-y: auto; }
|
|
271
274
|
#cmd-input::placeholder { color: var(--dimmer); }
|
|
272
275
|
@media (pointer: coarse) { #cmd-input { font-size: 1rem; padding: 0.75rem 0.75rem 0.35rem; line-height: 1.55; } }
|
|
@@ -410,8 +413,6 @@
|
|
|
410
413
|
.dr-text { color: var(--text); font-size: 0.82rem; line-height: 1.5; word-break: break-word; white-space: pre-wrap; }
|
|
411
414
|
.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
415
|
.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
416
|
.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
417
|
.dr-btn.take { background: var(--green); color: #fff; font-weight: 600; }
|
|
417
418
|
.dr-btn:active { opacity: 0.75; }
|
|
@@ -665,48 +666,9 @@
|
|
|
665
666
|
<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
667
|
</div>
|
|
667
668
|
<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>
|
|
669
|
+
<h4>全局设置</h4>
|
|
670
|
+
<div style="font-size:0.72rem;color:var(--muted);margin-bottom:0.45rem;line-height:1.5">本机名称、@ 引用范围、语音识别、账户等对所有任务生效的设置,统一放在首页的 ⚙ 设置里。这里的「外观 / 聊天 / 快捷栏」只影响当前这台设备。</div>
|
|
671
|
+
<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
672
|
</div>
|
|
711
673
|
<div class="setting-group" data-stab="more" style="display:none">
|
|
712
674
|
<h4>服务管理</h4>
|
|
@@ -1145,6 +1107,8 @@
|
|
|
1145
1107
|
ansi.use_classes = false;
|
|
1146
1108
|
|
|
1147
1109
|
fetch('/api/me').then(r => r.json()).then(data => {
|
|
1110
|
+
// 服务端没有桌面(headless 部署):「打开当前文件夹」必然无效,直接藏掉,只留文件面板
|
|
1111
|
+
if (data?.desktop === false) document.body.classList.add('no-desktop');
|
|
1148
1112
|
// 打包默认快捷栏预设:用户没自定义过勾选(skVisible 为 null)时采用服务端 config.json 的 skMode
|
|
1149
1113
|
if (data?.skMode && settings.skVisible == null) {
|
|
1150
1114
|
settings.skVisible = data.skMode === 'normal' ? skPresetNormal() : skPresetAll();
|
|
@@ -1334,93 +1298,6 @@
|
|
|
1334
1298
|
updateEnterHint();
|
|
1335
1299
|
syncSkModeUI();
|
|
1336
1300
|
|
|
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
1301
|
// 输出累积
|
|
1425
1302
|
let outputBuffer = '';
|
|
1426
1303
|
let outputTimer = null;
|
|
@@ -3330,7 +3207,7 @@
|
|
|
3330
3207
|
// 输入框里打出 @词(@ 后无空白)时浮出文件搜索层:递归搜会话目录、
|
|
3331
3208
|
// 最近修改优先;选中把 @词 替换成真实相对路径。「让 AI 读某个文件」
|
|
3332
3209
|
// 不再需要手打文件名。也可在 📄 文件面板点「引用」达到同样效果。
|
|
3333
|
-
const _mention = { open: false, start: -1, items: [], idx: 0 };
|
|
3210
|
+
const _mention = { open: false, start: -1, items: [], idx: 0, browse: null, seq: 0 };
|
|
3334
3211
|
const _mentionBox = document.createElement('div');
|
|
3335
3212
|
_mentionBox.id = 'mention-box';
|
|
3336
3213
|
document.body.appendChild(_mentionBox);
|
|
@@ -3349,24 +3226,38 @@
|
|
|
3349
3226
|
|
|
3350
3227
|
function mentionClose() {
|
|
3351
3228
|
_mention.open = false;
|
|
3229
|
+
_mention.browse = null;
|
|
3352
3230
|
_mention.items = [];
|
|
3353
3231
|
_mentionBox.style.display = 'none';
|
|
3354
3232
|
}
|
|
3355
3233
|
|
|
3234
|
+
// 两种模式:只打 @ = 分层浏览(_mention.browse 记当前层,点文件夹进入下一层);
|
|
3235
|
+
// @ 后打了字 = 全局递归搜索(服务端已排好:本项目在前、项目外在后,匹配度优先)
|
|
3236
|
+
const MENTION_GROUP = { own: '本项目', ext: '项目外' };
|
|
3356
3237
|
function mentionRowHTML(f, i) {
|
|
3357
|
-
const
|
|
3358
|
-
|
|
3238
|
+
const on = i === _mention.idx ? 'on' : '';
|
|
3239
|
+
if (f.act === 'up') return `<div class="mi ${on}" data-i="${i}"><span class="m-ico">⬆</span><span class="m-name">上一级</span></div>`;
|
|
3240
|
+
if (f.act === 'self') return `<div class="mi ${on}" data-i="${i}"><span class="m-ico">✓</span><span class="m-name">引用这个文件夹</span><span class="m-hint">${f.abs ? '项目外目录' : '文件夹'}</span></div>`;
|
|
3241
|
+
const path = f.label || f.name;
|
|
3242
|
+
const cut = f.label ? -1 : path.lastIndexOf('/');
|
|
3359
3243
|
const dir = cut >= 0 ? path.slice(0, cut + 1) : '';
|
|
3360
3244
|
const name = cut >= 0 ? path.slice(cut + 1) : path;
|
|
3361
3245
|
const ico = f.dir ? '📁' : (isEditableFile(name) ? '📝' : '📄');
|
|
3362
|
-
const hint = f.dir ? (f.abs ? '项目外目录' : '文件夹') : (f.abs ? '项目外' : '引用');
|
|
3363
|
-
return `<div class="mi ${
|
|
3246
|
+
const hint = f.nav ? '进入 ›' : f.dir ? (f.abs ? '项目外目录' : '文件夹') : (f.abs ? '项目外' : '引用');
|
|
3247
|
+
return `<div class="mi ${on}" data-i="${i}"><span class="m-ico">${ico}</span><span class="m-dir">${escHtml(dir)}</span><span class="m-name">${escHtml(name)}</span><span class="m-hint">${hint}</span></div>`;
|
|
3364
3248
|
}
|
|
3365
3249
|
|
|
3366
3250
|
function mentionRender() {
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3251
|
+
const b = _mention.browse;
|
|
3252
|
+
let html = b && b.crumb ? `<div class="mi-head">📂 ${escHtml(b.crumb)}</div>` : '';
|
|
3253
|
+
let group = null;
|
|
3254
|
+
_mention.items.forEach((f, i) => {
|
|
3255
|
+
if (f.group && f.group !== group && !(b && b.crumb)) { group = f.group; html += `<div class="mi-head">${MENTION_GROUP[group]}</div>`; }
|
|
3256
|
+
html += mentionRowHTML(f, i);
|
|
3257
|
+
});
|
|
3258
|
+
if (!_mention.items.some(f => !f.act)) html += `<div class="mi-empty">${b ? '这个文件夹是空的' : '没有匹配的文件'}</div>`;
|
|
3259
|
+
if (b && _mention.items.some(f => f.nav)) html += '<div class="mi-tip">回车/点击进入文件夹 · Tab 直接引用文件夹 · 打字可全局搜索</div>';
|
|
3260
|
+
_mentionBox.innerHTML = html;
|
|
3370
3261
|
const r = cmdInput.getBoundingClientRect();
|
|
3371
3262
|
_mentionBox.style.left = Math.max(8, r.left) + 'px';
|
|
3372
3263
|
_mentionBox.style.width = Math.max(r.width, 240) + 'px';
|
|
@@ -3382,20 +3273,68 @@
|
|
|
3382
3273
|
try {
|
|
3383
3274
|
const r = await fetch(`/api/files/${SESSION_ID}/search?q=${encodeURIComponent(token)}`);
|
|
3384
3275
|
const d = await r.json();
|
|
3385
|
-
if (!_mention.open || !d.ok) return;
|
|
3386
|
-
_mention.items = d.files || [];
|
|
3276
|
+
if (!_mention.open || _mention.browse || !d.ok) return;
|
|
3277
|
+
_mention.items = (d.files || []).map(f => ({ ...f, group: f.abs ? 'ext' : 'own' }));
|
|
3387
3278
|
_mention.idx = 0;
|
|
3388
3279
|
mentionRender();
|
|
3389
3280
|
} catch { mentionClose(); }
|
|
3390
3281
|
}, 140);
|
|
3391
3282
|
}
|
|
3392
3283
|
|
|
3284
|
+
// 分层浏览:root=null 顶层(本项目第一层 + 项目外根目录),-1 本项目内,i 第 i 个项目外根目录内
|
|
3285
|
+
async function mentionBrowse(root, path) {
|
|
3286
|
+
const seq = ++_mention.seq;
|
|
3287
|
+
try {
|
|
3288
|
+
const qs = root === null ? '' : `?root=${root}&path=${encodeURIComponent(path)}`;
|
|
3289
|
+
const r = await fetch(`/api/files/${SESSION_ID}/at-browse${qs}`);
|
|
3290
|
+
const d = await r.json();
|
|
3291
|
+
if (seq !== _mention.seq || !_mention.open || !d.ok) return;
|
|
3292
|
+
const items = [];
|
|
3293
|
+
let crumb = '';
|
|
3294
|
+
if (root !== null) {
|
|
3295
|
+
const rootFull = path ? d.self.slice(0, -(path.length + 1)) : d.self;
|
|
3296
|
+
const rootLabel = root === -1 ? '本项目' : '项目外 · ' + (rootFull.split('/').pop() || rootFull);
|
|
3297
|
+
crumb = rootLabel + (path ? ' / ' + path.split('/').join(' / ') : '');
|
|
3298
|
+
items.push({ act: 'up' });
|
|
3299
|
+
if (path || root !== -1) items.push({ act: 'self', name: d.self, dir: true, abs: root !== -1 });
|
|
3300
|
+
}
|
|
3301
|
+
d.entries.forEach(e => items.push({ ...e, root: root === null ? -1 : root, nav: e.dir, group: root === null ? 'own' : null }));
|
|
3302
|
+
(d.roots || []).forEach(x => items.push({ name: x.name, dir: true, abs: true, root: x.i, rel: '', nav: true, group: 'ext' }));
|
|
3303
|
+
_mention.browse = { root, path, crumb };
|
|
3304
|
+
_mention.items = items;
|
|
3305
|
+
_mention.idx = Math.max(0, items.findIndex(f => !f.act)); // 默认落在第一个条目上,不是「上一级」
|
|
3306
|
+
mentionRender();
|
|
3307
|
+
} catch { mentionClose(); }
|
|
3308
|
+
}
|
|
3309
|
+
|
|
3393
3310
|
function mentionUpdate() {
|
|
3394
3311
|
const t = mentionToken();
|
|
3395
3312
|
if (!t) return mentionClose();
|
|
3313
|
+
const token = cmdInput.value.slice(t.start + 1, cmdInput.selectionStart ?? cmdInput.value.length);
|
|
3314
|
+
const wasOpen = _mention.open && _mention.start === t.start;
|
|
3396
3315
|
_mention.open = true;
|
|
3397
3316
|
_mention.start = t.start;
|
|
3398
|
-
|
|
3317
|
+
if (!token) {
|
|
3318
|
+
// 已在浏览某一层就别重置(keyup 等事件会反复进来);新打开或从搜索删回空才回顶层
|
|
3319
|
+
if (!(wasOpen && _mention.browse)) { _mention.browse = { root: null, path: '', crumb: '' }; mentionBrowse(null, ''); }
|
|
3320
|
+
return;
|
|
3321
|
+
}
|
|
3322
|
+
_mention.browse = null;
|
|
3323
|
+
_mention.seq++;
|
|
3324
|
+
mentionSearch(token);
|
|
3325
|
+
}
|
|
3326
|
+
|
|
3327
|
+
// 回车/点击:文件夹(浏览模式)进入、上一级返回、其余引用;Tab = 文件夹也直接引用
|
|
3328
|
+
function mentionActivate(f, pickDir) {
|
|
3329
|
+
if (!f) return;
|
|
3330
|
+
if (f.act === 'up') {
|
|
3331
|
+
const b = _mention.browse;
|
|
3332
|
+
if (b.root !== null && b.path) mentionBrowse(b.root, b.path.includes('/') ? b.path.slice(0, b.path.lastIndexOf('/')) : '');
|
|
3333
|
+
else mentionBrowse(null, '');
|
|
3334
|
+
return;
|
|
3335
|
+
}
|
|
3336
|
+
if (f.nav && !pickDir) return mentionBrowse(f.root, f.rel);
|
|
3337
|
+
mentionPick(f);
|
|
3399
3338
|
}
|
|
3400
3339
|
|
|
3401
3340
|
function mentionPick(f) {
|
|
@@ -3429,7 +3368,7 @@
|
|
|
3429
3368
|
if (!d) return;
|
|
3430
3369
|
if (Math.abs(e.clientX - d.x) >= 8 || Math.abs(e.clientY - d.y) >= 8) return; // 滚动手势
|
|
3431
3370
|
const row = e.pointerType === 'touch' ? d.row : e.target.closest('.mi[data-i]');
|
|
3432
|
-
if (row)
|
|
3371
|
+
if (row) mentionActivate(_mention.items[Number(row.dataset.i)]);
|
|
3433
3372
|
});
|
|
3434
3373
|
_mentionBox.addEventListener('pointercancel', () => { _mentionDown = null; });
|
|
3435
3374
|
window.addEventListener('resize', () => { if (_mention.open) mentionClose(); });
|
|
@@ -3443,7 +3382,7 @@
|
|
|
3443
3382
|
e.preventDefault(); _mention.idx = (_mention.idx - 1 + _mention.items.length) % _mention.items.length; mentionRender(); return;
|
|
3444
3383
|
}
|
|
3445
3384
|
if ((e.key === 'Enter' || e.key === 'Tab') && _mention.items.length && !e.isComposing && e.keyCode !== 229) {
|
|
3446
|
-
e.preventDefault();
|
|
3385
|
+
e.preventDefault(); mentionActivate(_mention.items[_mention.idx], e.key === 'Tab'); return; // 补全层开着时回车=选中/进入,不是发送
|
|
3447
3386
|
}
|
|
3448
3387
|
if (e.key === 'Escape') { e.preventDefault(); mentionClose(); return; } // 先关补全,别误触中断 AI
|
|
3449
3388
|
}
|
|
@@ -3535,7 +3474,7 @@
|
|
|
3535
3474
|
overlay.appendChild(wrap);
|
|
3536
3475
|
};
|
|
3537
3476
|
|
|
3538
|
-
window.openSettings = function() { document.getElementById('settings-backdrop').classList.add('open'); document.getElementById('settings-panel').classList.add('open'); syncSettingsUI();
|
|
3477
|
+
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
3478
|
// 设置抽屉分类页签(1.3.174:一屏只看一类,不再长条堆叠)
|
|
3540
3479
|
function applySettingsTab(tab) {
|
|
3541
3480
|
document.querySelectorAll('#settings-tabs .stab').forEach(b => b.classList.toggle('active', b.dataset.tab === tab));
|
|
@@ -3562,7 +3501,7 @@
|
|
|
3562
3501
|
const d = await r.json();
|
|
3563
3502
|
if (d.ok) {
|
|
3564
3503
|
closeFilePanel();
|
|
3565
|
-
addStatusMessage('📂 已在本机打开文件夹 ' + (d.cwd || ''));
|
|
3504
|
+
addStatusMessage((d.via === 'task' ? '📂 已在本机桌面打开文件夹(myhi 在后台会话运行,经计划任务打开)' : '📂 已在本机打开文件夹 ') + (d.cwd || ''));
|
|
3566
3505
|
} else {
|
|
3567
3506
|
addStatusMessage('打开失败: ' + (d.error || '') + '(若你是远程访问本面板,此按钮本就不生效)');
|
|
3568
3507
|
}
|
|
@@ -4077,17 +4016,6 @@
|
|
|
4077
4016
|
document.getElementById('set-rawclip').checked = settings.rawClip !== false;
|
|
4078
4017
|
syncEnterModeUI();
|
|
4079
4018
|
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
4019
|
}
|
|
4092
4020
|
document.getElementById('set-parse').addEventListener('change', (e) => { settings.parseOutput = e.target.checked; saveSettings(); });
|
|
4093
4021
|
document.getElementById('set-thinking').addEventListener('change', (e) => { settings.showThinking = e.target.checked; saveSettings(); });
|
|
@@ -4099,50 +4027,6 @@
|
|
|
4099
4027
|
chatArea.querySelectorAll('.msg-raw').forEach(m => { delete m.dataset.open; rawClipSync(m); });
|
|
4100
4028
|
});
|
|
4101
4029
|
|
|
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
4030
|
window.restartMyhi = async function() {
|
|
4147
4031
|
if (!confirm('确认重启 myhi 服务?\n重启期间连接会短暂中断,页面将自动重连。')) return;
|
|
4148
4032
|
try {
|