[ZSHX]双窗口并行对话:让思路分叉而不被阻断
当你正在对话,一个“不该出现在这里”的想法冒出来时,如何让它被立即接住,而不打断主线?
一、核心场景
你正在与AI深入讨论一个话题。突然,一个新想法涌现——它有价值,但属于另一个方向。此时有三个选择:
| 选择 | 后果 |
|---|---|
| 当场展开 | 主线被打断,节奏崩塌 |
| 忽略它 | 大概率再也回不来 |
| 记下来,等主线结束再处理 | 状态已凉,重新预热成本极高 |
真实需求是:让想法在另一个窗口中立即被“行走”,而不是被“记录”。
二、技术实现(localStorage方案,可直接运行)
将以下代码保存为branch.html,用浏览器打开两次(窗口A带?window=A,窗口B带?window=B):
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>双窗口并行对话</title>
<style>
body { font-family: system-ui; max-width: 800px; margin: 2rem auto; padding: 0 1.5rem; }
.panel { background: #f8f9fa; border-radius: 12px; padding: 1.5rem; margin: 1rem 0; border-left: 4px solid #2563eb; }
.panel-b { border-left-color: #059669; }
.window-id { font-size: 14px; color: #6b7280; }
textarea { width: 100%; padding: 0.75rem; border-radius: 8px; border: 1px solid #d1d5db; box-sizing: border-box; }
.btn { padding: 0.5rem 1.5rem; border-radius: 8px; border: none; background: #2563eb; color: white; cursor: pointer; }
.btn-b { background: #059669; }
.log { background: #1e293b; color: #e2e8f0; padding: 0.75rem 1rem; border-radius: 8px; max-height: 150px; overflow-y: auto; font-size: 13px; }
.branch-display { background: #f1f5f9; padding: 0.75rem 1rem; border-radius: 8px; margin: 0.5rem 0; }
.status { display: inline-block; padding: 0.2rem 0.8rem; border-radius: 20px; font-size: 12px; }
.status-pending { background: #fef3c7; color: #92400e; }
.status-processing { background: #dbeafe; color: #1e40af; }
.status-done { background: #d1fae5; color: #065f46; }
.row { display: flex; gap: 1rem; flex-wrap: wrap; }
.row > * { flex: 1; min-width: 250px; }
</style>
</head>
<body>
<h1>双窗口并行对话</h1>
<div class="row">
<div class="panel">
<div class="window-id">窗口A(发送端)</div>
<textarea id="branchInput" rows="2" placeholder="输入你想分叉的想法..."></textarea>
<button class="btn" onclick="sendBranch()">发送到窗口B</button>
<div style="margin-top:0.75rem;font-size:14px;color:#6b7280;">状态:<span id="aStatus">就绪</span></div>
<div class="log" id="aLog">等待操作...</div>
</div>
<div class="panel panel-b">
<div class="window-id">窗口B(接收端)</div>
<div id="currentBranchDisplay" class="branch-display" style="color:#6b7280;font-style:italic;">等待窗口A发送...</div>
<div style="margin-top:0.75rem;font-size:14px;color:#6b7280;">状态:<span id="bStatus" class="status status-pending">等待中</span></div>
<div class="log" id="bLog">等待接收...</div>
</div>
</div>
<script>
const STORAGE_KEY = 'branch_queue';
const WINDOW_ID = location.search.includes('window=B') ? 'B' : 'A';
function getBranches() { try { return JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]'); } catch { return []; } }
function saveBranches(b) { localStorage.setItem(STORAGE_KEY, JSON.stringify(b)); }
function genId() { return Date.now().toString(36) + Math.random().toString(36).slice(2,6); }
function log(id, msg) {
const el = document.getElementById(id);
el.textContent = `[${new Date().toLocaleTimeString()}] ${msg}`;
el.scrollTop = el.scrollHeight;
}
function sendBranch() {
const input = document.getElementById('branchInput');
const content = input.value.trim();
if (!content) return alert('请输入想法');
const branch = { id: genId(), content, status: 'pending', createdAt: new Date().toISOString() };
const branches = getBranches();
branches.push(branch);
saveBranches(branches);
log('aLog', `已发送:「${content}」`);
document.getElementById('aStatus').textContent = '已发送,等待窗口B承接...';
input.value = '';
localStorage.setItem('__branch_notify', Date.now().toString());
}
function processBranches() {
const branches = getBranches();
const pending = branches.find(b => b.status === 'pending');
if (!pending) {
document.getElementById('currentBranchDisplay').textContent = '暂无待处理的想法';
document.getElementById('bStatus').textContent = '等待中';
document.getElementById('bStatus').className = 'status status-pending';
return;
}
document.getElementById('currentBranchDisplay').textContent = pending.content;
document.getElementById('bStatus').textContent = '分析中...';
document.getElementById('bStatus').className = 'status status-processing';
log('bLog', `接收到:「${pending.content}」`);
let progress = 0;
const interval = setInterval(() => {
progress += 10;
if (progress <= 100) log('bLog', `分析中... ${progress}%`);
if (progress >= 100) {
clearInterval(interval);
const updated = getBranches();
const target = updated.find(b => b.id === pending.id);
if (target) { target.status = 'done'; saveBranches(updated); }
document.getElementById('bStatus').textContent = '分析完成,等待你切换过来';
document.getElementById('bStatus').className = 'status status-done';
log('bLog', '分析完成!切换到此窗口继续深入。');
}
}, 400);
}
window.addEventListener('storage', function(e) {
if (e.key === STORAGE_KEY || e.key === '__branch_notify') {
if (WINDOW_ID === 'B') processBranches();
else if (WINDOW_ID === 'A') {
const branches = getBranches();
const pending = branches.find(b => b.status === 'pending');
if (pending) document.getElementById('aStatus').textContent = '想法已发送,窗口B正在处理...';
else if (branches.find(b => b.status === 'processing')) document.getElementById('aStatus').textContent = '窗口B正在分析中...';
else if (branches.find(b => b.status === 'done')) document.getElementById('aStatus').textContent = '想法已在窗口B中完成分析,可切换查看';
}
}
});
if (WINDOW_ID === 'A') {
document.querySelector('.panel .window-id').textContent = '窗口A(发送端)— 当前';
log('aLog', '窗口A已就绪');
const done = getBranches().find(b => b.status === 'done');
if (done) document.getElementById('aStatus').textContent = '想法已在窗口B中完成分析,可切换查看';
}
if (WINDOW_ID === 'B') {
document.querySelector('.panel-b .window-id').textContent = '窗口B(接收端)— 当前';
log('bLog', '窗口B已就绪,监听中...');
processBranches();
setInterval(processBranches, 3000);
}
</script>
</body>
</html>
三、工作机制
| 步骤 | 窗口A | 窗口B |
|---|---|---|
| 1 | 输入想法,点击发送 | — |
| 2 | 写入localStorage,触发storage事件 |
监听到事件,自动接收 |
| 3 | 继续主线对话(不被打断) | 自动开始“分析” |
| 4 | 对话完成 | 分析完成,等待用户切换 |
四、方案对比
| 方案 | 适用场景 | 优缺点 |
|---|---|---|
| localStorage + storage事件 | 快速验证、日常使用 | ✅ 无外部依赖,保存HTML即可运行 ❌ 秒级延迟,依赖页面焦点 |
| SharedWorker | 高频交互、稳定通信 | ✅ 性能更好,不依赖焦点 ❌ 浏览器支持有限 |
| WebSocket + 服务端 | 跨设备/跨浏览器 | ✅ 真正跨终端 ❌ 需要自己搭服务端 |
五、能力边界
能做的:
- 在对话中分叉思路,不打断主线
- 想法被另一个窗口立即接住并开始处理
- 两个窗口独立运行,互不干扰
- 完全在浏览器本地完成,无服务端依赖
不能做的:
- 不能“传送”AI的对话状态(上下文、记忆、会话状态无法跨窗口复制)
- 不能在窗口B中自动继续窗口A的对话
- 不能实现真正的实时同步(存在秒级延迟)
六、总结
双窗口并行对话的工程本质是:让思路在对话过程中分叉,在另一个空间中并行行走,不打断主线。
它解决的不是“跨窗口通信”,而是——让想法被立即接住并开始行走,而不是被记录后等待。
下一步:用上述代码本地打开两个标签页测试,然后将窗口B中的模拟分析替换为真实AI接口调用。
Top comments (1)
@hadil 是这种双窗并行会话,状态同步的效果吗?