@wendongfly/myhi 1.3.185 → 1.3.187

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 CHANGED
@@ -1112,11 +1112,9 @@
1112
1112
  const ansi = new AnsiUp();
1113
1113
  ansi.use_classes = false;
1114
1114
 
1115
- let _myRole = 'admin'; // 成员(operator)@ 只能浏览本项目/绑定目录,不提示项目外用法
1116
1115
  fetch('/api/me').then(r => r.json()).then(data => {
1117
1116
  // 服务端没有桌面(headless 部署):「打开当前文件夹」必然无效,直接藏掉,只留文件面板
1118
1117
  if (data?.desktop === false) document.body.classList.add('no-desktop');
1119
- _myRole = data?.role || 'admin';
1120
1118
  // 打包默认快捷栏预设:用户没自定义过勾选(skVisible 为 null)时采用服务端 config.json 的 skMode
1121
1119
  if (data?.skMode && settings.skVisible == null) {
1122
1120
  settings.skVisible = data.skMode === 'normal' ? skPresetNormal() : skPresetAll();
@@ -3324,8 +3322,7 @@
3324
3322
  if (b && !b.err) {
3325
3323
  const tips = [];
3326
3324
  if (_mention.items.some(f => f.nav)) tips.push('回车进入文件夹 · Tab 直接引用文件夹');
3327
- if (!b.dir && _myRole !== 'admin') tips.push('打字搜索本项目');
3328
- else if (!b.dir) tips.push('打字搜索本项目 · 项目外打 ' + (b.win ? '@D: 或 @\\' : '@/') + '、@~/ 逐层浏览');
3325
+ if (!b.dir) tips.push('打字搜索本项目 · 项目外打 ' + (b.win ? '@D: 或 @\\' : '@/') + '、@~/ 逐层浏览');
3329
3326
  if (tips.length) html += `<div class="mi-tip">${escHtml(tips.join(' · '))}</div>`;
3330
3327
  }
3331
3328
  _mentionBox.innerHTML = html;
@@ -3589,9 +3586,10 @@
3589
3586
  const d = await r.json();
3590
3587
  if (d.ok) {
3591
3588
  closeFilePanel();
3592
- addStatusMessage((d.via === 'task' ? '📂 已在本机桌面打开文件夹(myhi 在后台会话运行,经计划任务打开)' : '📂 已在本机打开文件夹 ') + (d.cwd || ''));
3589
+ addStatusMessage('📂 已在本机打开文件夹 ' + (d.cwd || ''));
3593
3590
  } else {
3594
- addStatusMessage('打开失败: ' + (d.error || '') + '(若你是远程访问本面板,此按钮本就不生效)');
3591
+ if (d.error?.includes('后台服务')) document.body.classList.add('no-desktop'); // 页面开着时服务换成了后台模式:顺手把按钮藏掉
3592
+ addStatusMessage('打开失败:' + (d.error || '') + (d.error?.includes('后台服务') ? '' : '(若你是远程访问本面板,此按钮本就不生效)'));
3595
3593
  }
3596
3594
  } catch (e) { addStatusMessage('打开出错: ' + e.message); }
3597
3595
  };
package/dist/index.html CHANGED
@@ -98,7 +98,15 @@
98
98
  width: auto; white-space: nowrap; flex-shrink: 0;
99
99
  }
100
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; }
101
+ #user-name { display: inline-block; max-width: 7rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; vertical-align: bottom; margin-left: 0.2rem; }
102
+ /* 用户菜单:顶栏 👤 点开,修改密码 / 退出登录 */
103
+ #user-menu { display: none; position: fixed; z-index: 40; min-width: 11rem; background: var(--surface); border: 1px solid var(--border); border-radius: 10px; box-shadow: 0 8px 28px rgba(0,0,0,0.4); padding: 0.3rem; }
104
+ #user-menu.open { display: block; }
105
+ #user-menu .um-who { padding: 0.45rem 0.6rem 0.5rem; font-size: 0.72rem; color: var(--muted); border-bottom: 1px solid var(--border); margin-bottom: 0.25rem; }
106
+ #user-menu .um-who b { display: block; color: var(--text); font-size: 0.85rem; margin-top: 0.1rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
107
+ #user-menu button { display: block; width: 100%; text-align: left; background: none; border: none; color: var(--text); font-size: 0.82rem; padding: 0.5rem 0.6rem; border-radius: 7px; cursor: pointer; }
108
+ #user-menu button:hover { background: var(--bg); }
109
+ #user-menu button.danger { color: var(--red); }
102
110
  /* 顶栏放不下一行时逐级收紧(fitHeader 按实际宽度判断,大字号/窄窗口都适用):先藏用户名,再只留图标 */
103
111
  #header.hd-fit1 #user-name { display: none; }
104
112
  #header.hd-fit2 .hd-btn .hd-txt { display: none; }
@@ -110,39 +118,48 @@
110
118
  #set-sheet { display: none; position: fixed; left: 0; right: 0; bottom: 0; z-index: 31; max-width: 640px; margin: 0 auto;
111
119
  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; }
112
120
  #set-sheet.open, #set-backdrop.open { display: block; }
113
- /* 分组卡片(仿系统设置):组标题说明作用范围,卡片内一行一项,说明文字收成一行小字 */
114
- .set-top { position: sticky; top: 0; z-index: 1; background: var(--surface); padding-bottom: 0.35rem; }
121
+ /* 分组卡片(仿系统设置):一行一项,左名称右控件;说明只留一行小字 */
122
+ .set-top { position: sticky; top: 0; z-index: 1; background: var(--surface); padding-bottom: 0.2rem; }
115
123
  .set-top .sheet-handle { margin: 0.6rem auto 0.5rem; }
116
124
  .set-head { display: flex; align-items: center; justify-content: space-between; }
117
125
  .set-head b { font-size: 1.05rem; }
118
126
  .set-x { background: none; border: none; color: var(--muted); font-size: 1.1rem; cursor: pointer; padding: 0.2rem 0.4rem; border-radius: 6px; }
119
127
  .set-x:hover { color: var(--text); background: var(--bg); }
120
- .set-group-t { font-size: 0.72rem; color: var(--muted); margin: 1rem 0.2rem 0.35rem; }
121
- .set-group-t small { font-size: 0.68rem; opacity: 0.8; margin-left: 0.3rem; }
128
+ .set-group-t { font-size: 0.72rem; color: var(--muted); margin: 1.1rem 0.25rem 0.4rem; }
122
129
  .set-card { background: var(--bg); border: 1px solid var(--border); border-radius: 12px; overflow: hidden; }
123
- .set-item { padding: 0.7rem 0.8rem; display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; }
124
- .set-item + .set-item, .set-card > * + .set-link, .set-item + .set-link { border-top: 1px solid var(--border); }
125
- .set-lbl { font-size: 0.84rem; color: var(--text); min-width: 4.5rem; }
130
+ .set-item { padding: 0.65rem 0.8rem; display: flex; align-items: center; gap: 0.75rem; }
131
+ .set-card > :not(:first-child) { border-top: 1px solid var(--border); }
132
+ .set-lbl { flex: 1; min-width: 0; font-size: 0.84rem; color: var(--text); }
126
133
  .set-lbl small { display: block; font-size: 0.68rem; color: var(--muted); margin-top: 0.1rem; }
127
- .set-item.stack { flex-direction: column; align-items: stretch; gap: 0.45rem; }
128
- .set-val { margin-left: auto; font-size: 0.8rem; color: var(--muted); }
129
- .set-status { font-size: 0.72rem; color: var(--muted); }
134
+ .set-ctl { flex: 0 1 15rem; min-width: 0; display: flex; gap: 0.35rem; }
135
+ .set-status { font-size: 0.7rem; color: var(--muted); padding: 0 0.8rem 0.55rem; margin-top: -0.3rem; }
130
136
  .set-status:empty { display: none; }
131
- .set-row { display: flex; gap: 0.4rem; }
132
- .set-inp { flex: 1; min-width: 0; background: var(--surface); border: 1px solid var(--border); border-radius: 8px; color: var(--text); font-size: 0.82rem; padding: 0.45rem 0.6rem; outline: none; }
137
+ .set-card > .set-status { border-top: none; }
138
+ .set-lbl.nw { flex: none; white-space: nowrap; }
139
+ .set-lbl.nw + .set-seg { margin-left: auto; }
140
+ /* 手机:带输入框的项改成名称在上、输入框整行;分段条占满剩余宽度 */
141
+ @media (max-width: 540px) {
142
+ .set-item.inp { flex-direction: column; align-items: stretch; gap: 0.4rem; }
143
+ .set-item.inp .set-ctl { flex: none; }
144
+ .set-lbl small { display: inline; margin-left: 0.4rem; }
145
+ .set-seg { flex: 1; }
146
+ }
147
+ .set-inp { flex: 1; min-width: 0; background: var(--surface); border: 1px solid var(--border); border-radius: 8px; color: var(--text); font-size: 0.8rem; padding: 0.4rem 0.55rem; outline: none; }
133
148
  .set-inp:focus { border-color: var(--accent); }
134
149
  .set-inp:disabled { opacity: 0.6; }
135
- /* 保存按钮平时是淡的,改了内容才亮起来(回车也能保存) */
136
- .set-btn { flex-shrink: 0; white-space: nowrap; background: var(--surface); color: var(--muted); border: 1px solid var(--border); border-radius: 8px; padding: 0.45rem 0.85rem; font-size: 0.78rem; cursor: pointer; }
137
- .set-btn.dirty { background: var(--accent); border-color: var(--accent); color: #fff; }
138
- .set-seg { flex: 1; display: flex; gap: 0.3rem; min-width: 12rem; }
139
- .set-seg button { flex: 1; background: var(--surface); color: var(--muted); border: 1px solid var(--border); border-radius: 8px; padding: 0.4rem 0.3rem; font-size: 0.78rem; cursor: pointer; white-space: nowrap; }
140
- .set-seg button.on { background: var(--accent); border-color: var(--accent); color: #fff; }
141
- .set-link { display: flex; align-items: center; width: 100%; background: none; border: none; color: var(--text); font-size: 0.84rem; padding: 0.75rem 0.8rem; cursor: pointer; text-align: left; text-decoration: none; }
150
+ /* 保存按钮只在改了内容后出现(回车也能保存) */
151
+ .set-btn { display: none; flex-shrink: 0; white-space: nowrap; background: var(--accent); color: #fff; border: none; border-radius: 8px; padding: 0.4rem 0.7rem; font-size: 0.76rem; cursor: pointer; }
152
+ .set-btn.dirty { display: block; }
153
+ /* 一体式分段选择条 */
154
+ .set-seg { flex: 0 1 17rem; display: flex; background: var(--surface); border: 1px solid var(--border); border-radius: 9px; padding: 2px; }
155
+ .set-seg button { flex: 1; background: none; color: var(--muted); border: none; border-radius: 7px; padding: 0.35rem 0.2rem; font-size: 0.76rem; cursor: pointer; white-space: nowrap; }
156
+ .set-seg button:hover { color: var(--text); }
157
+ .set-seg button.on { background: var(--accent); color: #fff; }
158
+ .set-link { display: flex; align-items: center; gap: 0.5rem; width: 100%; background: none; border: none; color: var(--text); font-size: 0.84rem; padding: 0.72rem 0.8rem; cursor: pointer; text-align: left; text-decoration: none; }
142
159
  .set-link:hover { background: var(--surface); }
160
+ .set-link small { color: var(--muted); font-size: 0.7rem; }
143
161
  .set-link::after { content: '›'; margin-left: auto; color: var(--muted); font-size: 1rem; }
144
- .set-link.danger { color: var(--red); }
145
- .set-foot { font-size: 0.7rem; color: var(--muted); line-height: 1.5; margin: 0.9rem 0.2rem 0.2rem; }
162
+ .set-foot { font-size: 0.7rem; color: var(--muted); line-height: 1.5; margin: 0.9rem 0.25rem 0.2rem; text-align: center; }
146
163
  /* 手机(≤540px):藏用户名、文字标签只留图标,一行放下且不憋屈 */
147
164
  @media (max-width: 540px) {
148
165
  #user-name { display: none; }
@@ -568,12 +585,12 @@
568
585
  <div id="header">
569
586
  <div class="logo">my<span>hi</span></div>
570
587
  <div class="hd-actions">
571
- <span id="user-name" style="font-size:0.8rem;color:var(--muted)"></span>
572
588
  <button id="grid-btn" class="hd-btn" onclick="location.href='/grid'" title="分屏工作台(多任务并排)">▦<span class="hd-txt"> 分屏</span></button>
573
589
  <button id="doctor-btn" class="hd-btn" onclick="openDoctor()" title="会话体检">🩺<span class="hd-txt"> 体检</span></button>
574
590
  <button id="usage-btn" class="hd-btn" onclick="showUsage()" title="用量">📊<span class="hd-txt"> 用量</span></button>
575
591
  <button id="help-btn" class="hd-btn" onclick="openHelpSheet()" title="使用说明 / 快速上手">❓<span class="hd-txt"> 帮助</span></button>
576
- <button id="settings-btn" class="hd-btn" onclick="openSettings()" title="设置(外观 / 本机名称 / 语音 / 账户)">⚙<span class="hd-txt"> 设置</span></button>
592
+ <button id="settings-btn" class="hd-btn" onclick="openSettings()" title="设置(外观 / 本机名称 / 语音 / 服务)">⚙<span class="hd-txt"> 设置</span></button>
593
+ <button id="user-btn" class="hd-btn" onclick="toggleUserMenu(event)" title="账户:修改密码 / 退出登录" style="display:none">👤<span id="user-name" class="hd-txt"></span></button>
577
594
  <div id="conn-dot"></div>
578
595
  </div>
579
596
  </div>
@@ -671,55 +688,51 @@
671
688
  </div>
672
689
 
673
690
  <!-- 设置 sheet:首页是全局设置的唯一入口(会话页 ⚙ 只管本设备的聊天显示/快捷栏) -->
691
+ <div id="user-menu">
692
+ <div class="um-who">已登录<b id="um-name"></b></div>
693
+ <button id="passwd-btn" onclick="closeUserMenu();openPasswdSheet()" style="display:none">🔑 修改密码</button>
694
+ <button id="logout-btn" class="danger" onclick="closeUserMenu();doLogout()">↩ 退出登录</button>
695
+ </div>
674
696
  <div id="set-backdrop" onclick="closeSettings()"></div>
675
697
  <div id="set-sheet">
676
698
  <div class="set-top">
677
699
  <div class="sheet-handle"></div>
678
- <div class="set-head"><b>⚙ 设置</b><button class="set-x" onclick="closeSettings()" title="关闭 (Esc)">✕</button></div>
700
+ <div class="set-head"><b>设置</b><button class="set-x" onclick="closeSettings()" title="关闭 (Esc)">✕</button></div>
679
701
  </div>
680
702
 
681
- <div class="set-group-t">本设备<small>只影响这台手机 / 电脑的浏览器</small></div>
703
+ <div class="set-group-t">本设备</div>
682
704
  <div class="set-card">
683
- <div class="set-item"><div class="set-lbl">主题</div><div class="set-seg" id="set-theme"></div></div>
684
- <div class="set-item"><div class="set-lbl">字号</div><div class="set-seg" id="set-font"></div></div>
705
+ <div class="set-item"><div class="set-lbl nw">主题</div><div class="set-seg" id="set-theme"></div></div>
706
+ <div class="set-item"><div class="set-lbl nw">字号</div><div class="set-seg" id="set-font"></div></div>
685
707
  </div>
686
708
 
687
- <div class="set-group-t">这台电脑<small>运行 myhi 的电脑,所有访问设备都生效</small></div>
709
+ <div class="set-group-t">这台电脑 · 所有设备生效</div>
688
710
  <div class="set-card">
689
- <div class="set-item stack">
690
- <div class="set-lbl">本机名称<small>显示在浏览器标签上,好区分多台电脑;留空用主机名</small></div>
691
- <div class="set-row">
711
+ <div class="set-item inp">
712
+ <div class="set-lbl">本机名称<small>显示在浏览器标签上</small></div>
713
+ <div class="set-ctl">
692
714
  <input id="set-machine-name" class="set-inp" maxlength="32" autocomplete="off" data-save="saveMachineName">
693
715
  <button class="set-btn" onclick="saveMachineName()">保存</button>
694
716
  </div>
695
- <div id="set-machine-status" class="set-status"></div>
696
717
  </div>
697
- <div class="set-item stack">
698
- <div class="set-lbl">语音识别服务<small>SenseVoice 等服务地址;留空 = 不用语音输入</small></div>
699
- <div class="set-row">
700
- <input id="set-asr-url" class="set-inp" placeholder="http://192.168.x.x:8765" autocomplete="off" data-save="saveAsrUrl">
718
+ <div id="set-machine-status" class="set-status"></div>
719
+ <div class="set-item inp">
720
+ <div class="set-lbl">语音识别<small>留空 = 不用语音输入</small></div>
721
+ <div class="set-ctl">
722
+ <input id="set-asr-url" class="set-inp" placeholder="http://IP:8765" autocomplete="off" data-save="saveAsrUrl">
701
723
  <button class="set-btn" onclick="saveAsrUrl()">保存</button>
702
724
  </div>
703
- <div id="set-asr-status" class="set-status"></div>
704
- </div>
705
- </div>
706
-
707
- <div id="set-account" style="display:none">
708
- <div class="set-group-t">账户</div>
709
- <div class="set-card">
710
- <div class="set-item"><div class="set-lbl">当前用户</div><span id="set-account-name" class="set-val"></span></div>
711
- <button id="passwd-btn" class="set-link" onclick="closeSettings();openPasswdSheet()" style="display:none">🔑 修改密码</button>
712
- <button id="logout-btn" class="set-link danger" onclick="doLogout()" style="display:none">退出登录</button>
713
725
  </div>
726
+ <div id="set-asr-status" class="set-status"></div>
714
727
  </div>
715
728
 
716
729
  <div class="set-group-t">服务</div>
717
730
  <div class="set-card">
718
- <a id="set-admin-link" class="set-link" href="/admin" style="display:none">🛠 管理后台(用户 / 大模型网关 / 密钥库 / 升级)</a>
719
- <button class="set-link danger" onclick="restartMyhi()">🔄 重启 myhi</button>
731
+ <a id="set-admin-link" class="set-link" href="/admin" style="display:none">🛠 管理后台 <small>用户 · 网关 · 密钥 · 升级</small></a>
732
+ <button class="set-link" onclick="restartMyhi()">🔄 重启 myhi</button>
720
733
  </div>
721
- <div id="set-restart-status" class="set-status" style="margin:0.4rem 0.2rem 0"></div>
722
- <div class="set-foot">聊天显示、回车键、快捷栏按钮在会话页右上角 ⚙ 里设置。</div>
734
+ <div id="set-restart-status" class="set-status" style="padding:0.45rem 0.25rem 0;margin:0"></div>
735
+ <div class="set-foot">聊天显示、回车键、快捷栏在会话页右上角 ⚙ 设置</div>
723
736
  </div>
724
737
 
725
738
  <!-- 修改密码 sheet(成员用户自助改密;管理员改密码在 /admin) -->
@@ -848,17 +861,14 @@
848
861
  fetch('/api/me').then(r => r.json()).then(data => {
849
862
  if ((data.exclusive || data.hasUsers) && data.name) {
850
863
  document.getElementById('user-name').textContent = data.name;
851
- document.getElementById('set-account-name').textContent = data.name;
864
+ document.getElementById('um-name').textContent = data.name;
865
+ document.getElementById('user-btn').style.display = '';
852
866
  fitHeader();
853
- document.getElementById('set-account').style.display = '';
854
- document.getElementById('logout-btn').style.display = '';
855
867
  }
856
868
  // 成员用户(users.json 登录)才显示自助改密入口;管理员改密码在 /admin
857
869
  if (data.role === 'admin') document.getElementById('set-admin-link').style.display = '';
858
- if (data.hasUsers && data.role === 'operator') {
859
- document.getElementById('set-account').style.display = '';
860
- document.getElementById('passwd-btn').style.display = '';
861
- }
870
+ else document.getElementById('set-admin-link').remove(); // 别留个隐藏首项,免得下一项顶部多一道分隔线
871
+ if (data.hasUsers && data.role === 'operator') document.getElementById('passwd-btn').style.display = '';
862
872
  if (data.dir) {
863
873
  _userDir = data.dir;
864
874
  // 默认填入绑定目录,隐藏盘符栏,保留最近目录
@@ -962,6 +972,21 @@
962
972
  } catch { msg.textContent = '连接失败'; }
963
973
  };
964
974
 
975
+ // 顶栏 👤 用户菜单:贴在按钮下方右对齐,点外面/Esc 关闭
976
+ window.toggleUserMenu = function(e) {
977
+ e.stopPropagation();
978
+ const m = document.getElementById('user-menu');
979
+ if (m.classList.contains('open')) return closeUserMenu();
980
+ const r = document.getElementById('user-btn').getBoundingClientRect();
981
+ m.style.top = (r.bottom + 6) + 'px';
982
+ m.style.right = Math.max(8, window.innerWidth - r.right) + 'px';
983
+ m.classList.add('open');
984
+ };
985
+ window.closeUserMenu = function() { document.getElementById('user-menu').classList.remove('open'); };
986
+ document.addEventListener('click', (e) => { if (!e.target.closest('#user-menu')) closeUserMenu(); });
987
+ document.addEventListener('keydown', (e) => { if (e.key === 'Escape') closeUserMenu(); });
988
+ window.addEventListener('resize', closeUserMenu);
989
+
965
990
  function doLogout() {
966
991
  if (!confirm('确定退出登录?')) return;
967
992
  fetch('/logout', { method: 'POST' }).finally(() => {