优化: 子代理体验与纪律注入
- 工具纪律段抽共享同源注入 - 子代理轨迹浮层与完成卡身份 - 分离窗会话切换修复 - 960s 统一出口报告不再蒸发
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
//! 窗口分离模式 — detach/reattach/resumeInDetached/closeDetachedWindow/dock/syncToMain/startFollowMain/stopFollowMain
|
||||
//! + 跨窗切会话(emitDetachedOpenConversation/registerDetachedOpenConvListener)
|
||||
//!
|
||||
//! 模块级私有(不进 reactive):
|
||||
//! - _unlistenMove / _unlistenResize: 主窗口 move/resize 跟随的 unlistener
|
||||
//! - _openConvListenerRegistered: 跨窗切会话监听注册标记(幂等)
|
||||
//!
|
||||
//! 耦合:
|
||||
//! - resumeInDetached / restoreGeneratingState 调 aiShared.nextMsgId 占位 ai 气泡 +
|
||||
@@ -35,6 +37,54 @@ const TEXT_KEY_PREFIX = 'df-ai-text-'
|
||||
function detachedLabel(convId: string): string {
|
||||
return DETACHED_LABEL_PREFIX + convId
|
||||
}
|
||||
/** 分离窗口 label 判定(含旧版单 label 'ai-detached' 兼容) */
|
||||
function isDetachedLabel(label: string): boolean {
|
||||
return label === 'ai-detached' || label.startsWith(DETACHED_LABEL_PREFIX)
|
||||
}
|
||||
|
||||
// ── 跨窗切会话(UX走查 2026-10-03 P1-1)──
|
||||
// 主/分离窗口是独立 JS context(state 互不同步),主窗口点「关联会话」跳转时分离窗不跟随。
|
||||
// 约定:发起方 emit 全局事件广播到全部 webview,仅分离窗(按 label 判定)注册消费,
|
||||
// 主窗口 label 'main' 不注册,不会自发自收。
|
||||
const OPEN_CONV_EVENT = 'ai-open-conversation'
|
||||
|
||||
/** 发起方(主窗口)侧:广播跨窗切会话事件;无分离窗存活时事件落空 */
|
||||
export async function emitDetachedOpenConversation(convId: string): Promise<void> {
|
||||
try {
|
||||
const { emit } = await import('@tauri-apps/api/event')
|
||||
await emit(OPEN_CONV_EVENT, { convId })
|
||||
} catch (e) {
|
||||
console.error('[AI] 跨窗切会话事件发送失败:', e)
|
||||
}
|
||||
}
|
||||
|
||||
/** 承接方(分离窗)侧:注册跨窗切会话监听,切换完成后聚焦本窗口(点击发生在主窗口,
|
||||
* 焦点须跟到承接会话的窗口,否则用户感知「点了没反应」)。
|
||||
* handler 由 useAiConversations 注入:切会话逻辑归会话域,避免本模块反向依赖造成模块环。 */
|
||||
let _openConvListenerRegistered = false
|
||||
export function registerDetachedOpenConvListener(handler: (convId: string) => Promise<void>): void {
|
||||
if (_openConvListenerRegistered) return
|
||||
_openConvListenerRegistered = true
|
||||
void (async () => {
|
||||
try {
|
||||
const [{ listen }, { getCurrentWebviewWindow }] = await Promise.all([
|
||||
import('@tauri-apps/api/event'),
|
||||
import('@tauri-apps/api/webviewWindow'),
|
||||
])
|
||||
if (!isDetachedLabel(getCurrentWebviewWindow().label)) return
|
||||
await listen<{ convId?: string }>(OPEN_CONV_EVENT, async (ev) => {
|
||||
const convId = ev.payload?.convId
|
||||
if (!convId) return
|
||||
await handler(convId)
|
||||
try { await getCurrentWebviewWindow().setFocus() } catch { /* 窗口关闭竞态忽略 */ }
|
||||
})
|
||||
} catch (e) {
|
||||
_openConvListenerRegistered = false
|
||||
console.error('[AI] 注册跨窗切会话监听失败:', e)
|
||||
}
|
||||
})()
|
||||
}
|
||||
|
||||
function genKey(convId: string): string {
|
||||
return GEN_KEY_PREFIX + convId
|
||||
}
|
||||
@@ -125,7 +175,7 @@ async function reattachPanel() {
|
||||
const all = await WebviewWindow.getAll()
|
||||
for (const w of all) {
|
||||
const lbl = w.label
|
||||
if (lbl === 'ai-detached' || lbl.startsWith(DETACHED_LABEL_PREFIX)) {
|
||||
if (isDetachedLabel(lbl)) {
|
||||
try { await w.close() } catch { /* 忽略单个窗口关闭失败 */ }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user