@wendongfly/myhi 1.3.182 → 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 +81 -14
- package/dist/index.html +19 -0
- 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; } }
|
|
@@ -1104,6 +1107,8 @@
|
|
|
1104
1107
|
ansi.use_classes = false;
|
|
1105
1108
|
|
|
1106
1109
|
fetch('/api/me').then(r => r.json()).then(data => {
|
|
1110
|
+
// 服务端没有桌面(headless 部署):「打开当前文件夹」必然无效,直接藏掉,只留文件面板
|
|
1111
|
+
if (data?.desktop === false) document.body.classList.add('no-desktop');
|
|
1107
1112
|
// 打包默认快捷栏预设:用户没自定义过勾选(skVisible 为 null)时采用服务端 config.json 的 skMode
|
|
1108
1113
|
if (data?.skMode && settings.skVisible == null) {
|
|
1109
1114
|
settings.skVisible = data.skMode === 'normal' ? skPresetNormal() : skPresetAll();
|
|
@@ -3202,7 +3207,7 @@
|
|
|
3202
3207
|
// 输入框里打出 @词(@ 后无空白)时浮出文件搜索层:递归搜会话目录、
|
|
3203
3208
|
// 最近修改优先;选中把 @词 替换成真实相对路径。「让 AI 读某个文件」
|
|
3204
3209
|
// 不再需要手打文件名。也可在 📄 文件面板点「引用」达到同样效果。
|
|
3205
|
-
const _mention = { open: false, start: -1, items: [], idx: 0 };
|
|
3210
|
+
const _mention = { open: false, start: -1, items: [], idx: 0, browse: null, seq: 0 };
|
|
3206
3211
|
const _mentionBox = document.createElement('div');
|
|
3207
3212
|
_mentionBox.id = 'mention-box';
|
|
3208
3213
|
document.body.appendChild(_mentionBox);
|
|
@@ -3221,24 +3226,38 @@
|
|
|
3221
3226
|
|
|
3222
3227
|
function mentionClose() {
|
|
3223
3228
|
_mention.open = false;
|
|
3229
|
+
_mention.browse = null;
|
|
3224
3230
|
_mention.items = [];
|
|
3225
3231
|
_mentionBox.style.display = 'none';
|
|
3226
3232
|
}
|
|
3227
3233
|
|
|
3234
|
+
// 两种模式:只打 @ = 分层浏览(_mention.browse 记当前层,点文件夹进入下一层);
|
|
3235
|
+
// @ 后打了字 = 全局递归搜索(服务端已排好:本项目在前、项目外在后,匹配度优先)
|
|
3236
|
+
const MENTION_GROUP = { own: '本项目', ext: '项目外' };
|
|
3228
3237
|
function mentionRowHTML(f, i) {
|
|
3229
|
-
const
|
|
3230
|
-
|
|
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('/');
|
|
3231
3243
|
const dir = cut >= 0 ? path.slice(0, cut + 1) : '';
|
|
3232
3244
|
const name = cut >= 0 ? path.slice(cut + 1) : path;
|
|
3233
3245
|
const ico = f.dir ? '📁' : (isEditableFile(name) ? '📝' : '📄');
|
|
3234
|
-
const hint = f.dir ? (f.abs ? '项目外目录' : '文件夹') : (f.abs ? '项目外' : '引用');
|
|
3235
|
-
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>`;
|
|
3236
3248
|
}
|
|
3237
3249
|
|
|
3238
3250
|
function mentionRender() {
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
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;
|
|
3242
3261
|
const r = cmdInput.getBoundingClientRect();
|
|
3243
3262
|
_mentionBox.style.left = Math.max(8, r.left) + 'px';
|
|
3244
3263
|
_mentionBox.style.width = Math.max(r.width, 240) + 'px';
|
|
@@ -3254,20 +3273,68 @@
|
|
|
3254
3273
|
try {
|
|
3255
3274
|
const r = await fetch(`/api/files/${SESSION_ID}/search?q=${encodeURIComponent(token)}`);
|
|
3256
3275
|
const d = await r.json();
|
|
3257
|
-
if (!_mention.open || !d.ok) return;
|
|
3258
|
-
_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' }));
|
|
3259
3278
|
_mention.idx = 0;
|
|
3260
3279
|
mentionRender();
|
|
3261
3280
|
} catch { mentionClose(); }
|
|
3262
3281
|
}, 140);
|
|
3263
3282
|
}
|
|
3264
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
|
+
|
|
3265
3310
|
function mentionUpdate() {
|
|
3266
3311
|
const t = mentionToken();
|
|
3267
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;
|
|
3268
3315
|
_mention.open = true;
|
|
3269
3316
|
_mention.start = t.start;
|
|
3270
|
-
|
|
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);
|
|
3271
3338
|
}
|
|
3272
3339
|
|
|
3273
3340
|
function mentionPick(f) {
|
|
@@ -3301,7 +3368,7 @@
|
|
|
3301
3368
|
if (!d) return;
|
|
3302
3369
|
if (Math.abs(e.clientX - d.x) >= 8 || Math.abs(e.clientY - d.y) >= 8) return; // 滚动手势
|
|
3303
3370
|
const row = e.pointerType === 'touch' ? d.row : e.target.closest('.mi[data-i]');
|
|
3304
|
-
if (row)
|
|
3371
|
+
if (row) mentionActivate(_mention.items[Number(row.dataset.i)]);
|
|
3305
3372
|
});
|
|
3306
3373
|
_mentionBox.addEventListener('pointercancel', () => { _mentionDown = null; });
|
|
3307
3374
|
window.addEventListener('resize', () => { if (_mention.open) mentionClose(); });
|
|
@@ -3315,7 +3382,7 @@
|
|
|
3315
3382
|
e.preventDefault(); _mention.idx = (_mention.idx - 1 + _mention.items.length) % _mention.items.length; mentionRender(); return;
|
|
3316
3383
|
}
|
|
3317
3384
|
if ((e.key === 'Enter' || e.key === 'Tab') && _mention.items.length && !e.isComposing && e.keyCode !== 229) {
|
|
3318
|
-
e.preventDefault();
|
|
3385
|
+
e.preventDefault(); mentionActivate(_mention.items[_mention.idx], e.key === 'Tab'); return; // 补全层开着时回车=选中/进入,不是发送
|
|
3319
3386
|
}
|
|
3320
3387
|
if (e.key === 'Escape') { e.preventDefault(); mentionClose(); return; } // 先关补全,别误触中断 AI
|
|
3321
3388
|
}
|
|
@@ -3434,7 +3501,7 @@
|
|
|
3434
3501
|
const d = await r.json();
|
|
3435
3502
|
if (d.ok) {
|
|
3436
3503
|
closeFilePanel();
|
|
3437
|
-
addStatusMessage('📂 已在本机打开文件夹 ' + (d.cwd || ''));
|
|
3504
|
+
addStatusMessage((d.via === 'task' ? '📂 已在本机桌面打开文件夹(myhi 在后台会话运行,经计划任务打开)' : '📂 已在本机打开文件夹 ') + (d.cwd || ''));
|
|
3438
3505
|
} else {
|
|
3439
3506
|
addStatusMessage('打开失败: ' + (d.error || '') + '(若你是远程访问本面板,此按钮本就不生效)');
|
|
3440
3507
|
}
|
package/dist/index.html
CHANGED
|
@@ -99,6 +99,11 @@
|
|
|
99
99
|
}
|
|
100
100
|
.hd-btn:hover { color: var(--text); border-color: var(--border2); }
|
|
101
101
|
#user-name { max-width: 7rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
102
|
+
/* 顶栏放不下一行时逐级收紧(fitHeader 按实际宽度判断,大字号/窄窗口都适用):先藏用户名,再只留图标 */
|
|
103
|
+
#header.hd-fit1 #user-name { display: none; }
|
|
104
|
+
#header.hd-fit2 .hd-btn .hd-txt { display: none; }
|
|
105
|
+
#header.hd-fit2 .hd-actions { gap: 0.4rem; }
|
|
106
|
+
#header.hd-fit2 .hd-btn { padding: 0.32rem 0.48rem; }
|
|
102
107
|
|
|
103
108
|
/* ── 设置 sheet(全局设置收口:外观 / 本机名称 / @ 引用范围 / 语音 / 账户 / 服务)── */
|
|
104
109
|
#set-backdrop { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.55); z-index: 30; }
|
|
@@ -851,6 +856,7 @@
|
|
|
851
856
|
if ((data.exclusive || data.hasUsers) && data.name) {
|
|
852
857
|
document.getElementById('user-name').textContent = data.name;
|
|
853
858
|
document.getElementById('set-account-name').textContent = data.name;
|
|
859
|
+
fitHeader();
|
|
854
860
|
document.getElementById('set-account').style.display = '';
|
|
855
861
|
document.getElementById('logout-btn').style.display = '';
|
|
856
862
|
}
|
|
@@ -980,7 +986,20 @@
|
|
|
980
986
|
r.setAttribute('data-font', f);
|
|
981
987
|
const meta = document.querySelector('meta[name="theme-color"]');
|
|
982
988
|
if (meta) meta.setAttribute('content', THEME_META[t] || '#0d1117');
|
|
989
|
+
fitHeader();
|
|
983
990
|
}
|
|
991
|
+
// 顶栏按钮组折行了就逐级收紧:字号「大」时按钮跟着 rem 变宽,固定断点兜不住
|
|
992
|
+
function fitHeader() {
|
|
993
|
+
const h = document.getElementById('header');
|
|
994
|
+
const acts = h.querySelector('.hd-actions');
|
|
995
|
+
const wraps = () => acts.offsetHeight > document.getElementById('settings-btn').offsetHeight * 1.5;
|
|
996
|
+
h.classList.remove('hd-fit1', 'hd-fit2');
|
|
997
|
+
if (wraps()) h.classList.add('hd-fit1');
|
|
998
|
+
if (wraps()) h.classList.add('hd-fit2');
|
|
999
|
+
}
|
|
1000
|
+
window.addEventListener('resize', fitHeader);
|
|
1001
|
+
document.fonts?.ready.then(fitHeader);
|
|
1002
|
+
fitHeader();
|
|
984
1003
|
// ── 设置 sheet ─────────────────────────────────────────
|
|
985
1004
|
const THEMES = [['system','跟随系统'],['dark','深色'],['light','浅色'],['black','纯黑']];
|
|
986
1005
|
const FONTS = [['small','小'],['medium','中'],['large','大']];
|