@wendongfly/myhi 1.3.105 → 1.3.106
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 -0
- package/dist/grid.html +189 -0
- package/dist/index.html +1 -0
- package/dist/index.js +1 -1
- package/dist/index.min.js +1 -1
- package/package.json +1 -1
package/dist/chat.html
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
var r=document.documentElement;
|
|
18
18
|
if(t&&t!=='dark') r.setAttribute('data-theme',t);
|
|
19
19
|
r.setAttribute('data-font',f);
|
|
20
|
+
if(new URLSearchParams(location.search).get('embed')==='1') r.classList.add('embed');
|
|
20
21
|
}catch(e){}})();
|
|
21
22
|
</script>
|
|
22
23
|
<style>
|
|
@@ -49,6 +50,8 @@
|
|
|
49
50
|
html[data-font="small"] { font-size: 15px; }
|
|
50
51
|
html[data-font="medium"] { font-size: 17px; }
|
|
51
52
|
html[data-font="large"] { font-size: 19.5px; }
|
|
53
|
+
/* 分屏内嵌模式(?embed=1):隐藏本页顶栏,让每个窗格更紧凑(外层 grid.html 提供窗格头) */
|
|
54
|
+
html.embed #top-bar { display: none; }
|
|
52
55
|
|
|
53
56
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
54
57
|
html, body { height: 100%; background: var(--bg); color: var(--text); font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; overflow: hidden; }
|
|
@@ -324,6 +327,7 @@
|
|
|
324
327
|
<button class="top-btn" onclick="showUsage()" title="用量" style="font-size:0.7rem;color:var(--muted)">用量</button>
|
|
325
328
|
<button class="top-btn" onclick="openFilePanel()" title="文件" style="font-size:0.7rem;color:var(--muted)">文件</button>
|
|
326
329
|
<button class="top-btn" onclick="openSwitchSheet()" title="切换会话" style="font-size:0.7rem;color:var(--muted)">切换</button>
|
|
330
|
+
<button class="top-btn" onclick="location.href='/grid'" title="分屏工作台(多任务并排)" style="font-size:0.7rem;color:var(--muted)">▦ 分屏</button>
|
|
327
331
|
<button class="top-btn" onclick="goBack()" title="返回">
|
|
328
332
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18l6-6-6-6"/></svg>
|
|
329
333
|
</button>
|
package/dist/grid.html
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
|
6
|
+
<meta name="theme-color" content="#0d1117">
|
|
7
|
+
<title>myhi 分屏</title>
|
|
8
|
+
<script>
|
|
9
|
+
(function(){try{
|
|
10
|
+
var t=localStorage.getItem('myhi_theme')||'dark';
|
|
11
|
+
var f=localStorage.getItem('myhi_font')||'medium';
|
|
12
|
+
var r=document.documentElement;
|
|
13
|
+
if(t&&t!=='dark') r.setAttribute('data-theme',t);
|
|
14
|
+
r.setAttribute('data-font',f);
|
|
15
|
+
}catch(e){}})();
|
|
16
|
+
</script>
|
|
17
|
+
<style>
|
|
18
|
+
:root {
|
|
19
|
+
--bg:#0d1117; --surface:#161b22; --surface2:#21262d; --border:#30363d;
|
|
20
|
+
--text:#e6edf3; --text2:#c9d1d9; --muted:#8b949e; --dim:#6e7681;
|
|
21
|
+
--blue:#58a6ff; --blue2:#1f6feb; --green:#3fb950; --red:#f85149; --purple:#7c3aed;
|
|
22
|
+
}
|
|
23
|
+
:root[data-theme="light"] { --bg:#ffffff; --surface:#f6f8fa; --surface2:#eaeef2; --border:#d0d7de;
|
|
24
|
+
--text:#1f2328; --text2:#32383f; --muted:#656d76; --dim:#818b98;
|
|
25
|
+
--blue:#0969da; --blue2:#0969da; --green:#1a7f37; --red:#cf222e; --purple:#8250df; }
|
|
26
|
+
:root[data-theme="black"] { --bg:#000000; --surface:#0a0a0a; --surface2:#161616; --border:#262626; }
|
|
27
|
+
@media (prefers-color-scheme: light) { :root[data-theme="system"] { --bg:#ffffff; --surface:#f6f8fa; --surface2:#eaeef2; --border:#d0d7de;
|
|
28
|
+
--text:#1f2328; --text2:#32383f; --muted:#656d76; --dim:#818b98;
|
|
29
|
+
--blue:#0969da; --blue2:#0969da; --green:#1a7f37; --red:#cf222e; --purple:#8250df; } }
|
|
30
|
+
html { font-size:16px; } html[data-font="small"]{font-size:15px;} html[data-font="large"]{font-size:17px;}
|
|
31
|
+
* { box-sizing:border-box; }
|
|
32
|
+
html,body { margin:0; height:100%; background:var(--bg); color:var(--text); font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif; }
|
|
33
|
+
body { display:flex; flex-direction:column; overflow:hidden; }
|
|
34
|
+
#bar { flex-shrink:0; display:flex; align-items:center; gap:0.5rem; padding:0.4rem 0.6rem; background:var(--surface); border-bottom:1px solid var(--border); flex-wrap:wrap; }
|
|
35
|
+
.logo { font-weight:700; font-size:0.95rem; color:var(--text); } .logo span{ color:var(--blue); }
|
|
36
|
+
.bar-btn { background:var(--bg); border:1px solid var(--border); color:var(--text2); font-size:0.78rem; padding:0.32rem 0.6rem; border-radius:7px; cursor:pointer; white-space:nowrap; }
|
|
37
|
+
.bar-btn:hover { border-color:var(--blue2); }
|
|
38
|
+
.seg { display:inline-flex; border:1px solid var(--border); border-radius:8px; overflow:hidden; }
|
|
39
|
+
.seg button { background:var(--bg); border:none; border-left:1px solid var(--border); color:var(--muted); font-size:0.76rem; padding:0.32rem 0.6rem; cursor:pointer; white-space:nowrap; }
|
|
40
|
+
.seg button:first-child { border-left:none; }
|
|
41
|
+
.seg button.active { background:var(--purple); color:#fff; }
|
|
42
|
+
.spacer { flex:1; }
|
|
43
|
+
#grid { flex:1; min-height:0; display:grid; gap:6px; padding:6px; overflow:auto; }
|
|
44
|
+
.pane { display:flex; flex-direction:column; min-height:260px; min-width:0; background:var(--surface); border:1px solid var(--border); border-radius:10px; overflow:hidden; }
|
|
45
|
+
.pane-head { flex-shrink:0; display:flex; align-items:center; gap:0.4rem; padding:0.3rem 0.4rem; background:var(--surface2); border-bottom:1px solid var(--border); }
|
|
46
|
+
.pane-head select { flex:1; min-width:0; background:var(--bg); color:var(--text); border:1px solid var(--border); border-radius:6px; padding:0.28rem 0.4rem; font-size:0.78rem; }
|
|
47
|
+
.pane-head button { background:var(--bg); border:1px solid var(--border); color:var(--muted); border-radius:6px; padding:0.2rem 0.45rem; font-size:0.8rem; cursor:pointer; line-height:1; }
|
|
48
|
+
.pane-head button:hover { color:var(--text); border-color:var(--blue2); }
|
|
49
|
+
.pane-body { flex:1; min-height:0; position:relative; }
|
|
50
|
+
.pane-body iframe { width:100%; height:100%; border:none; display:block; background:var(--bg); }
|
|
51
|
+
.pane-empty { position:absolute; inset:0; display:flex; align-items:center; justify-content:center; color:var(--dim); font-size:0.82rem; text-align:center; padding:1rem; }
|
|
52
|
+
#empty-hint { flex:1; display:flex; align-items:center; justify-content:center; color:var(--dim); font-size:0.9rem; text-align:center; padding:2rem; }
|
|
53
|
+
</style>
|
|
54
|
+
</head>
|
|
55
|
+
<body>
|
|
56
|
+
<div id="bar">
|
|
57
|
+
<div class="logo">my<span>hi</span> 分屏</div>
|
|
58
|
+
<button class="bar-btn" onclick="location.href='/'">← 首页</button>
|
|
59
|
+
<div class="seg" id="seg-layout">
|
|
60
|
+
<button data-lo="auto">自动</button>
|
|
61
|
+
<button data-lo="1">单屏</button>
|
|
62
|
+
<button data-lo="2">左右2</button>
|
|
63
|
+
<button data-lo="4">田字4</button>
|
|
64
|
+
<button data-lo="3">3列</button>
|
|
65
|
+
</div>
|
|
66
|
+
<span class="spacer"></span>
|
|
67
|
+
<button class="bar-btn" onclick="refreshSessions(true)" title="刷新任务列表">↻</button>
|
|
68
|
+
<button class="bar-btn" style="border-color:var(--blue2);color:var(--blue)" onclick="addPane('')">+ 添加任务</button>
|
|
69
|
+
</div>
|
|
70
|
+
<div id="grid"></div>
|
|
71
|
+
<div id="empty-hint" style="display:none">点「+ 添加任务」把多个任务并排看。<br>拉宽窗口自动多几格,或用上方按钮锁定布局。</div>
|
|
72
|
+
|
|
73
|
+
<script>
|
|
74
|
+
const LS_KEY = 'myhi_grid_v1';
|
|
75
|
+
const gridEl = document.getElementById('grid');
|
|
76
|
+
const emptyHint = document.getElementById('empty-hint');
|
|
77
|
+
let sessions = []; // [{id,title,cwd}]
|
|
78
|
+
let state = loadState(); // { layout, panes:[sessionId,...] }
|
|
79
|
+
const paneEls = []; // 与可见窗格一一对应的 DOM(保序,保留 iframe 不重载)
|
|
80
|
+
|
|
81
|
+
function loadState() {
|
|
82
|
+
try { const s = JSON.parse(localStorage.getItem(LS_KEY)); if (s && Array.isArray(s.panes)) return { layout: s.layout || 'auto', panes: s.panes }; } catch {}
|
|
83
|
+
return { layout: 'auto', panes: [] };
|
|
84
|
+
}
|
|
85
|
+
function saveState() {
|
|
86
|
+
state.panes = paneEls.map(p => p._sid || '');
|
|
87
|
+
localStorage.setItem(LS_KEY, JSON.stringify(state));
|
|
88
|
+
}
|
|
89
|
+
function esc(s){ return String(s==null?'':s).replace(/[&<>"]/g,c=>({'&':'&','<':'<','>':'>','"':'"'}[c])); }
|
|
90
|
+
function sName(s){ return s.title || s.name || s.summary || (s.id||s.sessionId||'').slice(0,12) || '未命名'; }
|
|
91
|
+
function sId(s){ return s.id || s.sessionId; }
|
|
92
|
+
|
|
93
|
+
// ── 布局 ──
|
|
94
|
+
function applyLayout() {
|
|
95
|
+
const lo = state.layout;
|
|
96
|
+
let cols;
|
|
97
|
+
if (lo === 'auto') cols = 'repeat(auto-fit, minmax(min(100%, 460px), 1fr))';
|
|
98
|
+
else if (lo === '1') cols = '1fr';
|
|
99
|
+
else if (lo === '3') cols = 'repeat(3, 1fr)';
|
|
100
|
+
else cols = 'repeat(2, 1fr)'; // '2' 左右 / '4' 田字 都是 2 列,行数由窗格数决定
|
|
101
|
+
gridEl.style.gridTemplateColumns = cols;
|
|
102
|
+
document.querySelectorAll('#seg-layout button').forEach(b => b.classList.toggle('active', b.dataset.lo === lo));
|
|
103
|
+
emptyHint.style.display = paneEls.length ? 'none' : '';
|
|
104
|
+
gridEl.style.display = paneEls.length ? 'grid' : 'none';
|
|
105
|
+
}
|
|
106
|
+
document.querySelectorAll('#seg-layout button').forEach(b => b.addEventListener('click', () => {
|
|
107
|
+
state.layout = b.dataset.lo;
|
|
108
|
+
// 田字:不足 4 格时自动补足空格,凑成 2×2 观感
|
|
109
|
+
if (b.dataset.lo === '4') { while (paneEls.length < 4) addPane('', true); }
|
|
110
|
+
saveState(); applyLayout();
|
|
111
|
+
}));
|
|
112
|
+
|
|
113
|
+
// ── 窗格 ──
|
|
114
|
+
function buildSelect(sid) {
|
|
115
|
+
const sel = document.createElement('select');
|
|
116
|
+
const opt0 = document.createElement('option'); opt0.value=''; opt0.textContent='— 选择任务 —'; sel.appendChild(opt0);
|
|
117
|
+
for (const s of sessions) { const o = document.createElement('option'); o.value = sId(s); o.textContent = sName(s); sel.appendChild(o); }
|
|
118
|
+
sel.value = sid || '';
|
|
119
|
+
return sel;
|
|
120
|
+
}
|
|
121
|
+
function addPane(sid, skipSave) {
|
|
122
|
+
const pane = document.createElement('div'); pane.className = 'pane'; pane._sid = sid || '';
|
|
123
|
+
const head = document.createElement('div'); head.className = 'pane-head';
|
|
124
|
+
const sel = buildSelect(sid);
|
|
125
|
+
const full = document.createElement('button'); full.textContent='↗'; full.title='在新标签页打开';
|
|
126
|
+
const close = document.createElement('button'); close.textContent='✕'; close.title='关闭此格'; close.style.color='var(--red)';
|
|
127
|
+
head.appendChild(sel); head.appendChild(full); head.appendChild(close);
|
|
128
|
+
const body = document.createElement('div'); body.className = 'pane-body';
|
|
129
|
+
const empty = document.createElement('div'); empty.className='pane-empty'; empty.textContent='从上方下拉选一个任务'; body.appendChild(empty);
|
|
130
|
+
pane.appendChild(head); pane.appendChild(body);
|
|
131
|
+
pane._sel = sel; pane._body = body; pane._empty = empty; pane._iframe = null;
|
|
132
|
+
|
|
133
|
+
sel.addEventListener('change', () => setPaneSession(pane, sel.value));
|
|
134
|
+
full.addEventListener('click', () => { if (pane._sid) window.open('/terminal/' + pane._sid, '_blank'); });
|
|
135
|
+
close.addEventListener('click', () => removePane(pane));
|
|
136
|
+
|
|
137
|
+
gridEl.appendChild(pane);
|
|
138
|
+
paneEls.push(pane);
|
|
139
|
+
if (sid) setPaneSession(pane, sid, true);
|
|
140
|
+
if (!skipSave) { saveState(); }
|
|
141
|
+
applyLayout();
|
|
142
|
+
}
|
|
143
|
+
function setPaneSession(pane, sid, keepSelect) {
|
|
144
|
+
pane._sid = sid || '';
|
|
145
|
+
if (!keepSelect) pane._sel.value = sid || '';
|
|
146
|
+
// 只重建这一格的 iframe,其它格不受影响
|
|
147
|
+
if (pane._iframe) { pane._iframe.remove(); pane._iframe = null; }
|
|
148
|
+
if (sid) {
|
|
149
|
+
pane._empty.style.display = 'none';
|
|
150
|
+
const ifr = document.createElement('iframe');
|
|
151
|
+
ifr.src = '/terminal/' + encodeURIComponent(sid) + '?embed=1';
|
|
152
|
+
pane._body.appendChild(ifr); pane._iframe = ifr;
|
|
153
|
+
} else {
|
|
154
|
+
pane._empty.style.display = '';
|
|
155
|
+
}
|
|
156
|
+
saveState();
|
|
157
|
+
}
|
|
158
|
+
function removePane(pane) {
|
|
159
|
+
const i = paneEls.indexOf(pane); if (i >= 0) paneEls.splice(i, 1);
|
|
160
|
+
pane.remove(); saveState(); applyLayout();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ── 任务列表 ──
|
|
164
|
+
async function refreshSessions(rebuildSelects) {
|
|
165
|
+
try {
|
|
166
|
+
const r = await fetch('/api/sessions'); if (!r.ok) return;
|
|
167
|
+
const data = await r.json();
|
|
168
|
+
sessions = (Array.isArray(data) ? data : (data.sessions || [])).filter(s => sId(s));
|
|
169
|
+
} catch {}
|
|
170
|
+
if (rebuildSelects) {
|
|
171
|
+
for (const pane of paneEls) {
|
|
172
|
+
const cur = pane._sid;
|
|
173
|
+
const fresh = buildSelect(cur);
|
|
174
|
+
fresh.addEventListener('change', () => setPaneSession(pane, fresh.value));
|
|
175
|
+
pane._sel.replaceWith(fresh); pane._sel = fresh;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ── 初始化 ──
|
|
181
|
+
(async function init() {
|
|
182
|
+
await refreshSessions(false);
|
|
183
|
+
const initial = (state.panes && state.panes.length) ? state.panes : [];
|
|
184
|
+
for (const sid of initial) addPane(sid, true);
|
|
185
|
+
applyLayout();
|
|
186
|
+
})();
|
|
187
|
+
</script>
|
|
188
|
+
</body>
|
|
189
|
+
</html>
|
package/dist/index.html
CHANGED
|
@@ -436,6 +436,7 @@
|
|
|
436
436
|
<div class="logo">my<span>hi</span></div>
|
|
437
437
|
<div style="display:flex;align-items:center;gap:0.6rem">
|
|
438
438
|
<span id="user-name" style="font-size:0.8rem;color:var(--muted)"></span>
|
|
439
|
+
<button id="grid-btn" onclick="location.href='/grid'" title="分屏工作台(多任务并排)" style="background:none;border:1px solid var(--border);color:var(--muted);font-size:0.75rem;padding:0.3rem 0.6rem;border-radius:6px;cursor:pointer;width:auto">▦ 分屏</button>
|
|
439
440
|
<button id="appearance-btn" onclick="openAppearance()" title="外观" style="background:none;border:1px solid var(--border);color:var(--muted);font-size:0.75rem;padding:0.3rem 0.55rem;border-radius:6px;cursor:pointer;width:auto">🎨</button>
|
|
440
441
|
<button id="doctor-btn" onclick="openDoctor()" style="background:none;border:1px solid var(--border);color:var(--muted);font-size:0.75rem;padding:0.3rem 0.6rem;border-radius:6px;cursor:pointer;width:auto">🩺 体检</button>
|
|
441
442
|
<button id="usage-btn" onclick="showUsage()" style="background:none;border:1px solid var(--border);color:var(--muted);font-size:0.75rem;padding:0.3rem 0.6rem;border-radius:6px;cursor:pointer;width:auto">用量</button>
|