@wendongfly/myhi 1.3.192 → 1.3.194
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 +964 -118
- package/dist/index.html +164 -28
- package/dist/index.js +4 -4
- package/dist/index.min.js +167 -149
- package/dist/login.html +5 -0
- package/package.json +1 -1
package/dist/index.html
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
2
|
<html lang="zh-CN">
|
|
3
3
|
<head>
|
|
4
|
+
<script>
|
|
5
|
+
// 首页 / 登录页绝不嵌在别的页面里显示(例如工作台右侧的会话页因会话不存在跳了过来):换到顶层窗口打开
|
|
6
|
+
(function () { try { if (window.top !== window.self && window.top.location.origin === location.origin) window.top.location.replace(location.href); } catch (e) {} })();
|
|
7
|
+
</script>
|
|
8
|
+
|
|
4
9
|
<meta charset="UTF-8">
|
|
5
10
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, viewport-fit=cover">
|
|
6
11
|
<meta name="theme-color" content="#0d1117">
|
|
@@ -249,6 +254,36 @@
|
|
|
249
254
|
/* 窄栏悬停提示(原生 title 有延迟,这里即时显示) */
|
|
250
255
|
#ws-tip { position: fixed; z-index: 60; left: 62px; background: var(--text); color: var(--bg); font-size: 0.75rem; padding: 0.3rem 0.55rem; border-radius: 6px;
|
|
251
256
|
white-space: nowrap; pointer-events: none; display: none; max-width: 320px; overflow: hidden; text-overflow: ellipsis; }
|
|
257
|
+
/* 任务操作:⋯ 按钮(电脑上悬停才出现)+ 小菜单 / 原地改名 / 标签编辑 */
|
|
258
|
+
.card-more { display: none; background: none; border: none; color: var(--muted); font-size: 1.05rem; line-height: 1; padding: 0.15rem 0.35rem; border-radius: 6px; cursor: pointer; }
|
|
259
|
+
.card-more:hover, .card-more.on { background: var(--border); color: var(--text); }
|
|
260
|
+
@media (hover: hover) and (pointer: fine) { .card:hover .card-more, .card-more.on { display: block; } }
|
|
261
|
+
#card-menu:not(.sheet-mode), #tag-pop:not(.sheet-mode) { position: fixed; z-index: 85; background: var(--surface); border: 1px solid var(--border); border-radius: 10px; box-shadow: 0 10px 30px rgba(0,0,0,0.35); padding: 0.3rem; }
|
|
262
|
+
#card-menu:not(.sheet-mode) { min-width: 10.5rem; }
|
|
263
|
+
#card-menu button { display: block; width: 100%; text-align: left; background: none; border: none; color: var(--text); font-size: 0.84rem; padding: 0.5rem 0.65rem; border-radius: 7px; cursor: pointer; }
|
|
264
|
+
#card-menu button:hover { background: var(--bg); }
|
|
265
|
+
#card-menu button.danger { color: var(--red); }
|
|
266
|
+
#card-menu.sheet-mode, #tag-pop.sheet-mode { position: fixed; inset: auto 0 0 0; z-index: 85; }
|
|
267
|
+
#card-menu.sheet-mode { inset: 0; background: rgba(0,0,0,0.5); display: flex; align-items: flex-end; justify-content: center; }
|
|
268
|
+
#card-menu .cm-box { background: var(--surface); border-radius: 18px 18px 0 0; width: 100%; max-width: 640px; padding: 0.5rem 0 max(1rem, env(safe-area-inset-bottom)); }
|
|
269
|
+
#card-menu .cm-title { padding: 0.6rem 1.25rem 0.4rem; color: var(--muted); font-size: 0.8rem; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
270
|
+
#card-menu.sheet-mode button { font-size: 0.95rem; padding: 0.9rem 1.25rem; border-radius: 0; border-top: 1px solid var(--border); }
|
|
271
|
+
#card-menu .cm-cancel { color: var(--muted) !important; text-align: center !important; border-top: 6px solid var(--bg) !important; }
|
|
272
|
+
.rename-inp { width: 100%; font-size: 0.9rem; font-weight: 600; padding: 0.15rem 0.35rem; border: 1px solid var(--accent); border-radius: 6px; background: var(--bg); color: var(--text); outline: none; }
|
|
273
|
+
#tag-pop:not(.sheet-mode) { width: 17rem; padding: 0.7rem; }
|
|
274
|
+
#tag-pop.sheet-mode { background: var(--surface); border-radius: 18px 18px 0 0; padding: 1rem 1rem max(1rem, env(safe-area-inset-bottom)); box-shadow: 0 -8px 30px rgba(0,0,0,0.35); max-width: 640px; margin: 0 auto; }
|
|
275
|
+
#tag-pop .tp-title { font-size: 0.8rem; font-weight: 600; color: var(--text2); margin-bottom: 0.5rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
276
|
+
#tag-pop .tp-cur, #tag-pop .tp-all { display: flex; flex-wrap: wrap; gap: 0.3rem; }
|
|
277
|
+
#tag-pop .tp-chip { display: inline-flex; align-items: center; gap: 0.25rem; font-size: 0.75rem; background: rgba(124,58,237,0.14); border: 1px solid rgba(124,58,237,0.35); border-radius: 999px; padding: 0.1rem 0.25rem 0.1rem 0.55rem; }
|
|
278
|
+
#tag-pop .tp-chip b { cursor: pointer; color: var(--muted); font-weight: 400; padding: 0 0.2rem; }
|
|
279
|
+
#tag-pop .tp-chip b:hover { color: var(--red); }
|
|
280
|
+
#tag-pop .tp-none { font-size: 0.74rem; color: var(--muted); }
|
|
281
|
+
#tag-pop .tp-inp { width: 100%; margin: 0.6rem 0 0.2rem; font-size: 0.82rem; padding: 0.4rem 0.55rem; border: 1px solid var(--border); border-radius: 8px; background: var(--bg); color: var(--text); outline: none; }
|
|
282
|
+
#tag-pop .tp-inp:focus { border-color: var(--accent); }
|
|
283
|
+
#tag-pop .tp-sub { font-size: 0.7rem; color: var(--muted); margin: 0.45rem 0 0.3rem; }
|
|
284
|
+
#tag-pop .tp-sug { font-size: 0.74rem; color: var(--text2); border: 1px dashed var(--border); border-radius: 999px; padding: 0.1rem 0.5rem; cursor: pointer; }
|
|
285
|
+
#tag-pop .tp-sug:hover { border-color: var(--accent); color: var(--accent); }
|
|
286
|
+
#tag-pop .tp-done { display: block; margin: 0.65rem 0 0 auto; background: var(--accent); color: #fff; border: none; border-radius: 7px; padding: 0.35rem 0.9rem; font-size: 0.78rem; cursor: pointer; }
|
|
252
287
|
/* 当前使用模式(单用户 / 多用户·管理员 / 多用户·成员),让成员知道为什么有些项是只读 */
|
|
253
288
|
.set-mode { font-size: 0.72rem; color: var(--muted); margin: 0.35rem 0.25rem 0; }
|
|
254
289
|
.set-mode a { color: var(--accent); text-decoration: none; }
|
|
@@ -808,7 +843,10 @@
|
|
|
808
843
|
<div class="hs-tile"><span>🔄</span><div><b>恢复</b><p>翻历史对话继续聊</p></div></div>
|
|
809
844
|
<div class="hs-tile"><span>🛡</span><div><b>护栏 / 密钥</b><p>敏感信息不外泄</p></div></div>
|
|
810
845
|
<div class="hs-tile"><span>⚙️</span><div><b>会话设置</b><p>快捷栏、折叠、主题字号</p></div></div>
|
|
846
|
+
<div class="hs-tile"><span>💡</span><div><b>看不懂就问</b><p>选中 AI 说的词,旁边小窗解释</p></div></div>
|
|
847
|
+
<div class="hs-tile"><span>📘</span><div><b>项目说明</b><p>让 AI 记住项目规矩,不用反复交代</p></div></div>
|
|
811
848
|
</div>
|
|
849
|
+
<div class="hs-tip" style="margin-bottom:0.45rem">💡 进入任务后点顶栏 <kbd>?</kbd> 可看每个按钮是做什么的(模型、计划、压缩、清除…);电脑上鼠标停在按钮上也有提示。</div>
|
|
812
850
|
<div class="hs-tip">📱 手机浏览器打开同一地址即可(局域网 / Tailscale),建议「添加到主屏幕」当 App 用。</div>
|
|
813
851
|
<div class="hs-actions">
|
|
814
852
|
<button class="hs-ghost" onclick="closeHelpSheet()">我知道了</button>
|
|
@@ -1477,6 +1515,7 @@
|
|
|
1477
1515
|
|
|
1478
1516
|
// 视图状态:分组方式 + 标签筛选
|
|
1479
1517
|
let _allSessions = [];
|
|
1518
|
+
let _renaming = null, _renderPending = false; // 原地改名中的任务 id / 期间被推迟的刷新
|
|
1480
1519
|
// 电脑端工作台状态(声明放这里:列表渲染会用到,必须先于任何一次渲染)
|
|
1481
1520
|
let _wsCur = null; // 右侧当前打开的任务
|
|
1482
1521
|
let _wsPending = new URLSearchParams(location.search).get('s'); // 刷新后回到上次的任务
|
|
@@ -1539,6 +1578,7 @@
|
|
|
1539
1578
|
${draftChip}
|
|
1540
1579
|
</div>
|
|
1541
1580
|
<div class="card-right">
|
|
1581
|
+
<button class="card-more" title="更多操作(重命名 / 标签 / 终止)">⋯</button>
|
|
1542
1582
|
${!s.alive ? '<div class="st st-dead" title="会话进程已退出,点进去会自动恢复">已停止</div>'
|
|
1543
1583
|
: s.busy ? '<div class="st st-busy" title="AI 正在执行">执行中</div>' : ''}
|
|
1544
1584
|
${s.viewers > 1 ? `<div class="viewers">👁 ${s.viewers}</div>` : ''}
|
|
@@ -1570,6 +1610,8 @@
|
|
|
1570
1610
|
|
|
1571
1611
|
function renderSessions(sessions) {
|
|
1572
1612
|
if (Array.isArray(sessions)) _allSessions = sessions;
|
|
1613
|
+
// 正在原地改名:列表每 5 秒刷新一次会把输入框冲掉,先攒着,改完再刷
|
|
1614
|
+
if (_renaming) { _renderPending = true; return; }
|
|
1573
1615
|
renderTagFilter();
|
|
1574
1616
|
updateGroupSeg();
|
|
1575
1617
|
|
|
@@ -1648,8 +1690,9 @@
|
|
|
1648
1690
|
list.querySelectorAll('.card').forEach(card => {
|
|
1649
1691
|
const id = card.dataset.id;
|
|
1650
1692
|
const title = card.dataset.title;
|
|
1651
|
-
card.addEventListener('click', () => openSession(id));
|
|
1693
|
+
card.addEventListener('click', (e) => { if (!e.target.closest('.card-more, .rename-inp')) openSession(id); });
|
|
1652
1694
|
card.addEventListener('contextmenu', (e) => showActions(e, id, title));
|
|
1695
|
+
card.querySelector('.card-more')?.addEventListener('click', (e) => { e.stopPropagation(); showActions(e, id, title, e.currentTarget); });
|
|
1653
1696
|
// 移动端长按
|
|
1654
1697
|
let lpTimer = null;
|
|
1655
1698
|
card.addEventListener('touchstart', (e) => { lpTimer = setTimeout(() => showActions(e, id, title), 550); }, { passive: true });
|
|
@@ -2305,38 +2348,131 @@
|
|
|
2305
2348
|
// ── Kill ─────────────────────────────────────────────────────
|
|
2306
2349
|
let _killId = null;
|
|
2307
2350
|
// 会话操作菜单:打开 / 重命名 / 设置标签 / 终止
|
|
2308
|
-
|
|
2351
|
+
// 任务操作菜单:电脑上(细指针)在 ⋯ 按钮或鼠标旁弹小菜单;手机上长按弹底部菜单。
|
|
2352
|
+
// 原来电脑右键也弹手机式底部菜单(离鼠标很远、整屏变暗),重命名 / 标签用浏览器自带 prompt。
|
|
2353
|
+
function showActions(e, id, title, anchor) {
|
|
2309
2354
|
e.preventDefault();
|
|
2310
|
-
|
|
2311
|
-
const
|
|
2355
|
+
closeCardMenu();
|
|
2356
|
+
const items = [
|
|
2357
|
+
['✏️ 重命名', () => inlineRename(id)],
|
|
2358
|
+
['🏷 设置标签', () => openTagEditor(id, anchor || { x: e.clientX, y: e.clientY })],
|
|
2359
|
+
['↗ 在新窗口打开', () => window.open('/terminal/' + encodeURIComponent(id), '_blank')],
|
|
2360
|
+
['🗑 终止会话', () => showKill(e, id, title), true],
|
|
2361
|
+
];
|
|
2362
|
+
if (matchMedia('(pointer: fine)').matches) {
|
|
2363
|
+
const m = document.createElement('div');
|
|
2364
|
+
m.id = 'card-menu';
|
|
2365
|
+
m.innerHTML = items.map(([l, , danger], i) => `<button data-i="${i}" class="${danger ? 'danger' : ''}">${l}</button>`).join('');
|
|
2366
|
+
document.body.appendChild(m);
|
|
2367
|
+
m.querySelectorAll('button').forEach(b => b.onclick = (ev) => { ev.stopPropagation(); closeCardMenu(); items[b.dataset.i][1](); });
|
|
2368
|
+
placePopover(m, anchor || { x: e.clientX, y: e.clientY });
|
|
2369
|
+
if (anchor) anchor.classList.add('on');
|
|
2370
|
+
m._anchor = anchor;
|
|
2371
|
+
return;
|
|
2372
|
+
}
|
|
2373
|
+
// 手机:底部菜单
|
|
2312
2374
|
const overlay = document.createElement('div');
|
|
2313
|
-
overlay.
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
overlay.
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
const
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2375
|
+
overlay.id = 'card-menu';
|
|
2376
|
+
overlay.className = 'sheet-mode';
|
|
2377
|
+
overlay.innerHTML = `<div class="cm-box"><div class="cm-title">${esc(title)}</div>` +
|
|
2378
|
+
items.map(([l, , danger], i) => `<button data-i="${i}" class="${danger ? 'danger' : ''}">${l}</button>`).join('') +
|
|
2379
|
+
`<button class="cm-cancel">取消</button></div>`;
|
|
2380
|
+
document.body.appendChild(overlay);
|
|
2381
|
+
overlay.addEventListener('click', (ev) => { if (ev.target === overlay) closeCardMenu(); });
|
|
2382
|
+
overlay.querySelector('.cm-cancel').onclick = closeCardMenu;
|
|
2383
|
+
overlay.querySelectorAll('button[data-i]').forEach(b => b.onclick = () => { closeCardMenu(); items[b.dataset.i][1](); });
|
|
2384
|
+
}
|
|
2385
|
+
function closeCardMenu() {
|
|
2386
|
+
const m = document.getElementById('card-menu');
|
|
2387
|
+
if (m) { m._anchor?.classList.remove('on'); m.remove(); }
|
|
2388
|
+
}
|
|
2389
|
+
// 弹层贴着锚点(按钮或鼠标位置)放,超出视口就往回收
|
|
2390
|
+
function placePopover(el, anchor) {
|
|
2391
|
+
const r = anchor.getBoundingClientRect ? anchor.getBoundingClientRect() : { left: anchor.x, right: anchor.x, top: anchor.y, bottom: anchor.y };
|
|
2392
|
+
const w = el.offsetWidth, h = el.offsetHeight;
|
|
2393
|
+
let x = r.left, y = r.bottom + 4;
|
|
2394
|
+
if (x + w > innerWidth - 8) x = Math.max(8, r.right - w);
|
|
2395
|
+
if (y + h > innerHeight - 8) y = Math.max(8, r.top - h - 4);
|
|
2396
|
+
el.style.left = x + 'px'; el.style.top = y + 'px';
|
|
2397
|
+
}
|
|
2398
|
+
document.addEventListener('mousedown', (e) => {
|
|
2399
|
+
if (!e.target.closest('#card-menu') && !e.target.closest('.card-more')) closeCardMenu();
|
|
2400
|
+
if (!e.target.closest('#tag-pop') && !e.target.closest('#card-menu')) closeTagEditor();
|
|
2401
|
+
});
|
|
2402
|
+
document.addEventListener('keydown', (e) => { if (e.key === 'Escape') { closeCardMenu(); closeTagEditor(); } });
|
|
2403
|
+
|
|
2404
|
+
// 原地改名:标题变输入框,回车 / 失焦保存,Esc 取消
|
|
2405
|
+
function inlineRename(id) {
|
|
2406
|
+
const card = document.querySelector(`#list .card[data-id="${CSS.escape(id)}"]`);
|
|
2407
|
+
const t = card?.querySelector('.card-title');
|
|
2408
|
+
if (!t) return;
|
|
2409
|
+
const old = _allSessions.find(x => x.id === id)?.title || t.textContent;
|
|
2410
|
+
_renaming = id;
|
|
2411
|
+
const inp = document.createElement('input');
|
|
2412
|
+
inp.className = 'rename-inp';
|
|
2413
|
+
inp.value = old;
|
|
2414
|
+
inp.maxLength = 80;
|
|
2415
|
+
t.replaceWith(inp);
|
|
2416
|
+
inp.focus(); inp.select();
|
|
2417
|
+
let done = false;
|
|
2418
|
+
const finish = (save) => {
|
|
2419
|
+
if (done) return; done = true;
|
|
2420
|
+
const v = inp.value.trim();
|
|
2421
|
+
if (save && v && v !== old) {
|
|
2422
|
+
socket.emit('rename', { sessionId: id, title: v });
|
|
2423
|
+
const s = _allSessions.find(x => x.id === id); if (s) s.title = v;
|
|
2424
|
+
}
|
|
2425
|
+
_renaming = null;
|
|
2426
|
+
_renderPending = false;
|
|
2427
|
+
renderSessions();
|
|
2330
2428
|
};
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2429
|
+
inp.addEventListener('keydown', (ev) => {
|
|
2430
|
+
if (ev.key === 'Enter' && !ev.isComposing) { ev.preventDefault(); finish(true); }
|
|
2431
|
+
else if (ev.key === 'Escape') { ev.stopPropagation(); finish(false); }
|
|
2432
|
+
});
|
|
2433
|
+
inp.addEventListener('blur', () => finish(true));
|
|
2434
|
+
inp.addEventListener('click', (ev) => ev.stopPropagation());
|
|
2435
|
+
}
|
|
2436
|
+
|
|
2437
|
+
// 标签编辑:当前标签(点 × 去掉)+ 其他任务用过的标签(点一下加上)+ 输入新标签回车;每次改动立即保存
|
|
2438
|
+
function openTagEditor(id, anchor) {
|
|
2439
|
+
closeTagEditor();
|
|
2440
|
+
const s = _allSessions.find(x => x.id === id);
|
|
2441
|
+
if (!s) return;
|
|
2442
|
+
const pop = document.createElement('div');
|
|
2443
|
+
pop.id = 'tag-pop';
|
|
2444
|
+
if (!matchMedia('(pointer: fine)').matches) pop.classList.add('sheet-mode');
|
|
2445
|
+
document.body.appendChild(pop);
|
|
2446
|
+
const save = (tags) => {
|
|
2447
|
+
s.tags = tags;
|
|
2448
|
+
socket.emit('set-tags', { sessionId: id, tags });
|
|
2449
|
+
draw();
|
|
2335
2450
|
};
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2451
|
+
function draw() {
|
|
2452
|
+
const cur = s.tags || [];
|
|
2453
|
+
const all = [...new Set(_allSessions.flatMap(x => x.tags || []))].filter(t => !cur.includes(t)).sort((a, b) => a.localeCompare(b, 'zh'));
|
|
2454
|
+
pop.innerHTML = `<div class="tp-title">🏷 「${esc(s.title || '')}」的标签</div>` +
|
|
2455
|
+
`<div class="tp-cur">${cur.length ? cur.map(t => `<span class="tp-chip">${esc(t)}<b data-del="${esc(t)}">×</b></span>`).join('') : '<span class="tp-none">还没有标签</span>'}</div>` +
|
|
2456
|
+
`<input class="tp-inp" placeholder="输入新标签,回车添加" maxlength="20">` +
|
|
2457
|
+
(all.length ? `<div class="tp-sub">点一下添加:</div><div class="tp-all">${all.map(t => `<span class="tp-sug" data-add="${esc(t)}">+ ${esc(t)}</span>`).join('')}</div>` : '') +
|
|
2458
|
+
`<button class="tp-done">完成</button>`;
|
|
2459
|
+
pop.querySelectorAll('[data-del]').forEach(b => b.onclick = () => save(cur.filter(t => t !== b.dataset.del)));
|
|
2460
|
+
pop.querySelectorAll('[data-add]').forEach(b => b.onclick = () => save([...cur, b.dataset.add]));
|
|
2461
|
+
const inp = pop.querySelector('.tp-inp');
|
|
2462
|
+
inp.addEventListener('keydown', (ev) => {
|
|
2463
|
+
if (ev.key === 'Enter' && !ev.isComposing) {
|
|
2464
|
+
ev.preventDefault();
|
|
2465
|
+
const v = inp.value.trim().replace(/[,,]/g, '');
|
|
2466
|
+
if (v && !cur.includes(v)) save([...cur, v]); else inp.value = '';
|
|
2467
|
+
}
|
|
2468
|
+
});
|
|
2469
|
+
pop.querySelector('.tp-done').onclick = closeTagEditor;
|
|
2470
|
+
if (!pop.classList.contains('sheet-mode')) placePopover(pop, anchor);
|
|
2471
|
+
setTimeout(() => pop.querySelector('.tp-inp')?.focus(), 0);
|
|
2472
|
+
}
|
|
2473
|
+
draw();
|
|
2339
2474
|
}
|
|
2475
|
+
function closeTagEditor() { document.getElementById('tag-pop')?.remove(); }
|
|
2340
2476
|
|
|
2341
2477
|
function showKill(e, id, title) {
|
|
2342
2478
|
if (e && e.preventDefault) e.preventDefault();
|