Compare commits
3 Commits
69c5953fe7
...
1ca5e15f8e
| Author | SHA1 | Date | |
|---|---|---|---|
| 1ca5e15f8e | |||
| bc8337ab09 | |||
| 2a870c5a35 |
@ -354,6 +354,18 @@ def create_multi_agent_conversation():
|
||||
default_permission_mode = (prefs or {}).get("default_permission_mode")
|
||||
if default_permission_mode not in ("readonly", "approval", "auto_approval", "unrestricted"):
|
||||
default_permission_mode = None
|
||||
# 运行模式(work_mode):沿用 terminal 当前值(与正常创建路径一致,
|
||||
# /new 页面切换后新对话继承);plan 档不变量 ⇒ 权限必须只读,
|
||||
# 同时记录进入前权限供离开 plan 恢复
|
||||
safe_work_mode = getattr(terminal, "get_work_mode", lambda: "plan")()
|
||||
if safe_work_mode not in ("plan", "ask", "execute"):
|
||||
safe_work_mode = "plan"
|
||||
safe_permission_mode = default_permission_mode or getattr(terminal, "get_permission_mode", lambda: "unrestricted")()
|
||||
safe_pre_plan_permission = None
|
||||
if safe_work_mode == "plan":
|
||||
if safe_permission_mode != "readonly":
|
||||
safe_pre_plan_permission = safe_permission_mode
|
||||
safe_permission_mode = "readonly"
|
||||
|
||||
previous_cm_current = getattr(ctx_manager, "current_conversation_id", None)
|
||||
|
||||
@ -380,8 +392,10 @@ def create_multi_agent_conversation():
|
||||
initial_messages=[],
|
||||
model_key=(prefs or {}).get("default_model") or getattr(terminal, "model_key", None),
|
||||
metadata_overrides={
|
||||
"permission_mode": default_permission_mode or getattr(terminal, "get_permission_mode", lambda: "unrestricted")(),
|
||||
"permission_mode": safe_permission_mode,
|
||||
"execution_mode": getattr(terminal, "get_execution_mode", lambda: "sandbox")(),
|
||||
"work_mode": safe_work_mode,
|
||||
"pre_plan_permission_mode": safe_pre_plan_permission,
|
||||
"multi_agent_mode": True,
|
||||
"reasoning_effort": ma_effort,
|
||||
},
|
||||
|
||||
@ -7,8 +7,12 @@
|
||||
* assistant 消息末尾。同一文件多次编辑只展示合并后的最终结果(净变化)。
|
||||
*
|
||||
* 交互:
|
||||
* - 桌面端 hover 行(150ms 意图延迟)打开居中浮窗;离开行与浮窗后自动关闭
|
||||
* - 桌面端 hover 行(600ms 意图延迟)打开居中浮窗;离开行与浮窗后自动关闭;
|
||||
* 弹窗已打开时鼠标移到其他行,瞬间切换内容(不再等延迟)
|
||||
* - 点击行「钉住」弹窗(带遮罩 + 关闭按钮);移动端无 hover,点击为唯一入口
|
||||
* - 弹窗位置/高度在展开瞬间一次性计算并钉死,之后不再重算
|
||||
* - 外部滚动 / 窗口缩放:直接关闭弹窗;钉住弹窗内部滚动放行(可滚动查看长 diff),
|
||||
* hover 弹窗内部滚动同样关闭
|
||||
* - Esc 关闭
|
||||
*/
|
||||
import { computed, onBeforeUnmount, ref, watch } from 'vue';
|
||||
@ -132,9 +136,25 @@ function computePanelStyle() {
|
||||
}
|
||||
}
|
||||
|
||||
function onWindowReposition() {
|
||||
// 弹窗打开后位置/高度钉死(只在 open 时计算一次),滚动不再重算位置:
|
||||
// - 外部滚动(对话区/页面):一律关闭弹窗
|
||||
// - 内部滚动(diff 内容):钉住弹窗放行,hover 弹窗也关闭
|
||||
// - 滚动/缩放关闭后短暂抑制重开,避免弹窗消失瞬间鼠标落在文件行上导致「关了又开」
|
||||
let suppressOpenUntil = 0;
|
||||
|
||||
function onScrollCapture(e: Event) {
|
||||
if (!activePath.value) return;
|
||||
computePanelStyle();
|
||||
const target = e.target as Element | null;
|
||||
const isInsidePanel = target instanceof Element && !!target.closest('.es-modal');
|
||||
if (isInsidePanel && pinned.value) return;
|
||||
suppressOpenUntil = Date.now() + 250;
|
||||
close();
|
||||
}
|
||||
|
||||
function onResizeClose() {
|
||||
if (!activePath.value) return;
|
||||
suppressOpenUntil = Date.now() + 250;
|
||||
close();
|
||||
}
|
||||
|
||||
function clearOpenTimer() {
|
||||
@ -160,9 +180,18 @@ function open(path: string, pin: boolean, rowEl: HTMLElement | null = null) {
|
||||
computePanelStyle();
|
||||
}
|
||||
|
||||
// hover 打开意图延迟;弹窗已打开时切换到另一行则瞬间切换,不等待此延迟
|
||||
const HOVER_OPEN_DELAY = 600;
|
||||
|
||||
function scheduleOpen(path: string, rowEl: HTMLElement | null) {
|
||||
clearOpenTimer();
|
||||
openTimer = setTimeout(() => open(path, false, rowEl), 150);
|
||||
if (Date.now() < suppressOpenUntil) return;
|
||||
if (activePath.value) {
|
||||
// 弹窗已打开:鼠标移到另一行,瞬间切换(重新定位 + 换内容)
|
||||
open(path, false, rowEl);
|
||||
return;
|
||||
}
|
||||
openTimer = setTimeout(() => open(path, false, rowEl), HOVER_OPEN_DELAY);
|
||||
}
|
||||
|
||||
function scheduleClose() {
|
||||
@ -215,13 +244,14 @@ function onKeydown(e: KeyboardEvent) {
|
||||
watch(activePath, (v) => {
|
||||
if (v) {
|
||||
window.addEventListener('keydown', onKeydown);
|
||||
window.addEventListener('resize', onWindowReposition);
|
||||
// 捕获阶段监听滚动,让弹窗在对话区滚动时始终贴着锚定条目
|
||||
window.addEventListener('scroll', onWindowReposition, true);
|
||||
// 窗口缩放:直接关闭(钉死位置可能跑出视口),不做重算
|
||||
window.addEventListener('resize', onResizeClose);
|
||||
// 捕获阶段监听滚动:外部滚动一律关闭,钉住弹窗内部滚动放行
|
||||
window.addEventListener('scroll', onScrollCapture, true);
|
||||
} else {
|
||||
window.removeEventListener('keydown', onKeydown);
|
||||
window.removeEventListener('resize', onWindowReposition);
|
||||
window.removeEventListener('scroll', onWindowReposition, true);
|
||||
window.removeEventListener('resize', onResizeClose);
|
||||
window.removeEventListener('scroll', onScrollCapture, true);
|
||||
}
|
||||
});
|
||||
|
||||
@ -229,8 +259,8 @@ onBeforeUnmount(() => {
|
||||
clearOpenTimer();
|
||||
clearCloseTimer();
|
||||
window.removeEventListener('keydown', onKeydown);
|
||||
window.removeEventListener('resize', onWindowReposition);
|
||||
window.removeEventListener('scroll', onWindowReposition, true);
|
||||
window.removeEventListener('resize', onResizeClose);
|
||||
window.removeEventListener('scroll', onScrollCapture, true);
|
||||
});
|
||||
|
||||
// ---------------- diff 行渲染辅助 ----------------
|
||||
|
||||
@ -869,6 +869,22 @@
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* 透明桥接热区:按钮被绝对定位在容器外,与气泡之间有 4px 间隙,
|
||||
鼠标从气泡滑向按钮会经过间隙导致 :hover 中断、按钮收回;
|
||||
用伪元素覆盖间隙,让 hover 从气泡平滑转移到按钮(仅命中区域,无视觉变化) */
|
||||
.message-block--last
|
||||
.user-message:not(.user-message--compact):not(.user-message--brief):not(
|
||||
.user-message--multi-agent
|
||||
)
|
||||
.user-bubble-actions::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: -12px;
|
||||
right: -12px;
|
||||
top: -4px;
|
||||
height: 4px;
|
||||
}
|
||||
|
||||
.message-block--last .assistant-message .text-output {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user