fix(shell): 去掉所有窗口级玻璃(白框/黑块根因);feat: 多会话切换 + 文件桥(桌面壳 HTTP 网关 403 的解法)
白框根因:SetWindowCompositionAttribute(Win10 亚克力) / DwmExtendFrameIntoClientArea(客户区变玻璃) / WebView2 Transparent 背景 三者在真机上都会让 8px 边环露白、WebView2 变黑。现在窗口永远由 应用自绘深色,背景效果只作为页面侧半透明(不再碰 DWM 与窗口 alpha)。 新增: - 会话切换:标题栏 ⇄ 按钮 + 列表(会话标题/轮/步/消息数,可'跟随最新');宿主侧 pinnedSessionId, 被选中的会话不再被其它会话的事件抢走;snapshot 里带 sessions[] - 文件桥:桌面壳对未认证请求一律 403(实测 6 种请求形态),浮窗改走文件——宿主每秒写 snapshot.json, 浮窗渲染;浮窗的 chat/watch/newSession 写 outbox.json,宿主轮询执行。端到端实测通过 - 编码修复:快照按显式 UTF-8 读(PS 5.1 Get-Content -Raw 会按 GBK 读坏中文 JSON); outbox 写 UTF-8 无 BOM
This commit is contained in:
+114
@@ -357,6 +357,64 @@
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* ---------------- 会话切换器 ---------------- */
|
||||
#sessList {
|
||||
position: absolute;
|
||||
left: 8px;
|
||||
right: 8px;
|
||||
top: 36px;
|
||||
max-height: 60%;
|
||||
overflow-y: auto;
|
||||
background: #171b22;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 10px;
|
||||
padding: 6px;
|
||||
box-shadow: 0 12px 32px rgba(0, 0, 0, .5);
|
||||
display: none;
|
||||
z-index: 25;
|
||||
}
|
||||
|
||||
#sessList.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#sessList .srow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 6px 8px;
|
||||
border-radius: 7px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
#sessList .srow:hover {
|
||||
background: var(--bg-chip);
|
||||
}
|
||||
|
||||
#sessList .srow.act {
|
||||
background: var(--accent-soft);
|
||||
}
|
||||
|
||||
#sessList .srow .st {
|
||||
flex: 1 1 auto;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#sessList .srow .sm {
|
||||
flex: 0 0 auto;
|
||||
color: var(--fg-dim);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
#sessList .head {
|
||||
color: var(--fg-dim);
|
||||
font-size: 11px;
|
||||
padding: 2px 8px 6px;
|
||||
}
|
||||
|
||||
#settings h4 {
|
||||
margin: 0 0 8px;
|
||||
font-size: 12px;
|
||||
@@ -482,6 +540,11 @@
|
||||
<div id="bar">
|
||||
<div class="dot wait" id="dot"></div>
|
||||
<div id="title"><span id="titleText">DSH 悬浮窗</span><small id="sub">未连接</small></div>
|
||||
<button class="tbtn" id="bSess" title="切换要镜像的会话">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round">
|
||||
<path d="M4 8h13l-3-3M20 16H7l3 3" />
|
||||
</svg>
|
||||
</button>
|
||||
<button class="tbtn" id="bPin" title="固定位置">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round">
|
||||
<path d="M9 4h6l-1 6 3 3v1H7v-1l3-3-1-6z" />
|
||||
@@ -554,6 +617,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="sessList"></div>
|
||||
|
||||
<div id="settings">
|
||||
<h4>悬浮窗设置</h4>
|
||||
<div class="srow"><span>背景效果</span>
|
||||
@@ -620,6 +685,16 @@
|
||||
|
||||
let es = null
|
||||
let ENDPOINT = null
|
||||
/** 文件桥(桌面壳 HTTP 网关不可用时的通道)也叫"连上了" */
|
||||
function markLive(label) {
|
||||
$('dot').className = 'dot live'
|
||||
if (label) $('sub').textContent = label
|
||||
$('input').disabled = false
|
||||
$('send').disabled = false
|
||||
document.body.classList.add('linked')
|
||||
const hint = $('hint')
|
||||
if (hint) hint.style.display = 'none'
|
||||
}
|
||||
function setEndpoint(ep) {
|
||||
const live = !!(ep && ep.baseUrl)
|
||||
ENDPOINT = live ? ep.baseUrl : null
|
||||
@@ -652,6 +727,11 @@
|
||||
function handleCmd(c) {
|
||||
if (c === 'hide') document.body.style.visibility = 'hidden'
|
||||
if (c === 'show') document.body.style.visibility = 'visible'
|
||||
if (c === 'backdropFallback') { // 系统玻璃不可用,宿主已回退成不透明
|
||||
S.backdrop = 'opaque'
|
||||
paint()
|
||||
toast('这台机器上系统毛玻璃不可用,已回退为不透明')
|
||||
}
|
||||
}
|
||||
|
||||
let settingsApplied = false
|
||||
@@ -672,15 +752,48 @@
|
||||
function render(msg) {
|
||||
if (!msg) return
|
||||
if (msg.type === 'cmd') return handleCmd(msg.cmd)
|
||||
if (msg.type === 'snapshot') { // 文件桥来的快照
|
||||
markLive('本地桥')
|
||||
return render(msg.data)
|
||||
}
|
||||
if (msg.settings && !settingsApplied) { settingsApplied = true; applyDshSettings(msg.settings) }
|
||||
if (msg.metrics) metrics(msg.metrics)
|
||||
if (msg.messages) messages(msg.messages)
|
||||
if (Array.isArray(msg.sessions)) { SESSIONS = msg.sessions; PINNED = msg.pinned || null; if ($('sessList').classList.contains('show')) renderSessions() }
|
||||
if (msg.title) {
|
||||
const t = String(msg.title).slice(0, 42)
|
||||
$('titleText').textContent = 'DSH · ' + t
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------- 会话切换 ---------------- */
|
||||
let SESSIONS = []
|
||||
let PINNED = null
|
||||
function renderSessions() {
|
||||
const box = $('sessList')
|
||||
box.innerHTML = ''
|
||||
const head = document.createElement('div')
|
||||
head.className = 'head'
|
||||
head.textContent = SESSIONS.length ? `共 ${SESSIONS.length} 个会话 · 点一个跟随它` : '还没有会话'
|
||||
box.appendChild(head)
|
||||
|
||||
const mk = (label, sub, active, sid) => {
|
||||
const row = document.createElement('div')
|
||||
row.className = 'srow' + (active ? ' act' : '')
|
||||
const st = document.createElement('div'); st.className = 'st'; st.textContent = label
|
||||
const sm = document.createElement('div'); sm.className = 'sm'; sm.textContent = sub
|
||||
row.append(st, sm)
|
||||
row.onclick = () => { send({ cmd: 'watch', sessionId: sid }); box.classList.remove('show'); toast(sid ? '已切换到:' + label : '已改为跟随最新') }
|
||||
box.appendChild(row)
|
||||
}
|
||||
mk('跟随最新', '', !PINNED, '')
|
||||
for (const s of SESSIONS) {
|
||||
const label = s.title || (s.id || '').slice(0, 8) + '…'
|
||||
const sub = `${s.turns || 0}轮/${s.steps || 0}步`
|
||||
mk(label, sub, PINNED ? s.id === PINNED : !!s.active, s.id)
|
||||
}
|
||||
}
|
||||
|
||||
function metrics(m) {
|
||||
$('m-turn').textContent = m.turns ?? '—'
|
||||
$('m-step').textContent = m.steps ?? '—'
|
||||
@@ -723,6 +836,7 @@
|
||||
$('bFull').onclick = () => cmd('fullscreen')
|
||||
$('bClose').onclick = () => cmd('close')
|
||||
$('bSet').onclick = () => $('settings').classList.toggle('show')
|
||||
$('bSess').onclick = () => { const el = $('sessList'); renderSessions(); el.classList.toggle('show') }
|
||||
$('sTop').onclick = () => cmd('topmost')
|
||||
$('sPin').onclick = () => cmd('posLock')
|
||||
$('sLock').onclick = () => cmd('sizeLock')
|
||||
|
||||
Reference in New Issue
Block a user