Compare commits

...

4 Commits

Author SHA1 Message Date
fe061e6168 fix(ui): 顶栏模糊改三级渐进条带,根治新版内核 mask+backdrop-filter 同元素失效
- mask-image 与 backdrop-filter 同元素时模糊被新版浏览器内核静默丢弃(实测确认)
- 桌面 conversation-ribbon / 移动 mobile-panel-topbar 各加 __blur 元素:
  本体 blur(14px) + ::before blur(5px) + ::after blur(1.2px) 三级递减,
  模拟原 mask 渐隐模糊,无可见下边界
- 原 ::after 颜色渐隐层保持不变,盖在最上溶掉条带阶梯感
2026-08-30 01:32:28 +08:00
d08cc693b3 fix(ui): MarkdownRenderer 内建列表样式,根治非聊天容器列表偏左
- 删除 _resource-panel.scss 中仅绑定 .text-content 容器的列表样式补丁
- 列表样式(padding-left 缩进/list-style)迁移至 MarkdownRenderer 自身,任何容器(聊天/批准弹窗/预览等)渲染一致
- 解决计划批准弹窗等非 .text-content 容器内列表 marker 贴左、文字无缩进的问题
2026-08-30 00:33:54 +08:00
a30b03fb93 fix(i18n): 未登录 401 返回双语文案「登录状态已过期,请重新登录」
- auth_helpers.py / app_legacy.py 的 api_login_required 401 响应改用 tr('auth.session_expired')
- 新增 i18n key:zh 登录状态已过期,请重新登录 / en Your login session has expired. Please log in again.
2026-08-30 00:33:43 +08:00
4c3d8cedc7 feat(ui): 计划批准弹窗支持最小化关闭,状态栏可恢复处理
- PlanApprovalDialog windowbar 新增关闭(X)按钮,点击后弹窗收起不打扰
- InputComposer 底部状态栏新增「有待批准的计划」按钮(对齐等待回答问题交互),点击恢复弹窗
- floatingStatusVisible 可见性判断纳入计划批准最小化状态,修复弹窗收起后浮栏不渲染的问题
- 批准解决后自动复位最小化状态,避免按钮残留
2026-08-30 00:33:34 +08:00
17 changed files with 190 additions and 29 deletions

View File

@ -37,6 +37,10 @@ MESSAGES = {
"zh-CN": "未登录", "zh-CN": "未登录",
"en-US": "Not logged in", "en-US": "Not logged in",
}, },
"auth.session_expired": {
"zh-CN": "登录状态已过期,请重新登录",
"en-US": "Your login session has expired. Please log in again.",
},
"auth.host_mode_tutorial_not_applicable": { "auth.host_mode_tutorial_not_applicable": {
"zh-CN": "宿主机模式无需设置新手教程状态", "zh-CN": "宿主机模式无需设置新手教程状态",
"en-US": "Tutorial status does not apply in host mode", "en-US": "Tutorial status does not apply in host mode",

View File

@ -821,7 +821,7 @@ def api_login_required(view_func):
@wraps(view_func) @wraps(view_func)
def wrapped(*args, **kwargs): def wrapped(*args, **kwargs):
if not is_logged_in(): if not is_logged_in():
return jsonify({"error": "Unauthorized"}), 401 return jsonify({"error": tr("auth.session_expired")}), 401
return view_func(*args, **kwargs) return view_func(*args, **kwargs)
return wrapped return wrapped

View File

@ -34,7 +34,7 @@ def api_login_required(view_func):
@wraps(view_func) @wraps(view_func)
def wrapped(*args, **kwargs): def wrapped(*args, **kwargs):
if not is_logged_in(): if not is_logged_in():
return jsonify({"error": "Unauthorized"}), 401 return jsonify({"error": tr("auth.session_expired")}), 401
return view_func(*args, **kwargs) return view_func(*args, **kwargs)
return wrapped return wrapped

View File

@ -128,6 +128,7 @@
/> />
<div v-if="titleRibbonVisible" class="conversation-ribbon" ref="titleRibbon"> <div v-if="titleRibbonVisible" class="conversation-ribbon" ref="titleRibbon">
<div class="conversation-ribbon__blur" aria-hidden="true"></div>
<button <button
type="button" type="button"
class="conversation-ribbon__selector" class="conversation-ribbon__selector"
@ -302,6 +303,8 @@
:project-git-summary="projectGitSummary" :project-git-summary="projectGitSummary"
:user-question-minimized="userQuestionMinimized" :user-question-minimized="userQuestionMinimized"
:pending-user-question-count="pendingUserQuestions.length" :pending-user-question-count="pendingUserQuestions.length"
:plan-approval-minimized="planApprovalMinimized"
:pending-plan-approval-count="pendingPlanApprovals.length"
:goal-mode-armed="goalModeArmed" :goal-mode-armed="goalModeArmed"
:goal-running="goalRunning" :goal-running="goalRunning"
:goal-progress="goalProgress" :goal-progress="goalProgress"
@ -319,6 +322,7 @@
:active-sub-agent-count="activeSubAgentCount" :active-sub-agent-count="activeSubAgentCount"
:avatar-status="showStatusAvatar && messages.length > 0 ? avatarStatus : null" :avatar-status="showStatusAvatar && messages.length > 0 ? avatarStatus : null"
@restore-user-question="restoreUserQuestionDialog" @restore-user-question="restoreUserQuestionDialog"
@restore-plan-approval="restorePlanApprovalDialog"
@stop-all-sub-agents="handleStopAllSubAgents" @stop-all-sub-agents="handleStopAllSubAgents"
@update:input-message="inputSetMessage" @update:input-message="inputSetMessage"
@input-change="handleInputChange" @input-change="handleInputChange"
@ -544,10 +548,11 @@
@dismiss="dismissUserQuestions" @dismiss="dismissUserQuestions"
/> />
<PlanApprovalDialog <PlanApprovalDialog
:visible="pendingPlanApprovals.length > 0" :visible="pendingPlanApprovals.length > 0 && !planApprovalMinimized"
:approvals="pendingPlanApprovals" :approvals="pendingPlanApprovals"
:submitting-ids="answeringPlanApprovalIds" :submitting-ids="answeringPlanApprovalIds"
@submit="submitPlanApproval" @submit="submitPlanApproval"
@minimize="minimizePlanApprovalDialog"
/> />
<transition name="overlay-fade"> <transition name="overlay-fade">
<VersioningDialog <VersioningDialog
@ -588,6 +593,7 @@
ref="mobilePanelTrigger" ref="mobilePanelTrigger"
> >
<div class="mobile-panel-topbar"> <div class="mobile-panel-topbar">
<div class="mobile-panel-topbar__blur" aria-hidden="true"></div>
<button <button
type="button" type="button"
class="mobile-panel-fab" class="mobile-panel-fab"

View File

@ -446,6 +446,10 @@ export const toolMethods = {
this.pendingPlanApprovals = this.pendingPlanApprovals.filter( this.pendingPlanApprovals = this.pendingPlanApprovals.filter(
(item: any) => item && String(item.approval_id || '') !== id (item: any) => item && String(item.approval_id || '') !== id
); );
// 弹窗被关闭(最小化)期间批准已解决:无待批事项时复位,避免状态栏按钮残留
if (!this.pendingPlanApprovals.length) {
this.planApprovalMinimized = false;
}
// 批准后后端已切换运行模式/恢复权限与执行环境,刷新显示(多标签页同步场景) // 批准后后端已切换运行模式/恢复权限与执行环境,刷新显示(多标签页同步场景)
if (String(data?.decision || '') === 'approved') { if (String(data?.decision || '') === 'approved') {
this.fetchWorkMode(); this.fetchWorkMode();

View File

@ -95,6 +95,18 @@ export const dialogMethods = {
// ignore // ignore
} }
}, },
minimizePlanApprovalDialog() {
if (!Array.isArray(this.pendingPlanApprovals) || !this.pendingPlanApprovals.length) {
return;
}
this.planApprovalMinimized = true;
},
restorePlanApprovalDialog() {
if (!Array.isArray(this.pendingPlanApprovals) || !this.pendingPlanApprovals.length) {
return;
}
this.planApprovalMinimized = false;
},
async submitPlanApproval(payload) { async submitPlanApproval(payload) {
const approvalId = String(payload?.approval_id || '').trim(); const approvalId = String(payload?.approval_id || '').trim();
if (!approvalId) { if (!approvalId) {

View File

@ -292,6 +292,7 @@ export function dataState() {
pendingUserQuestions: [], pendingUserQuestions: [],
pendingPlanApprovals: [], pendingPlanApprovals: [],
answeringPlanApprovalIds: [], answeringPlanApprovalIds: [],
planApprovalMinimized: false,
userQuestionDialogVisible: false, userQuestionDialogVisible: false,
userQuestionMinimized: false, userQuestionMinimized: false,
userQuestionActiveIndex: 0, userQuestionActiveIndex: 0,

View File

@ -107,4 +107,32 @@ watch(() => props.content, renderMath, { immediate: true });
.markdown-text-segment > *:last-child { .markdown-text-segment > *:last-child {
margin-bottom: 0; margin-bottom: 0;
} }
/*
全局 reset 清掉了 ul/ol 默认 padding此处置回标准缩进
保证 MarkdownRenderer 在任何容器内聊天/批准弹窗/预览等表现一致
标记在左文字缩进靠右 */
.markdown-text-segment :deep(ul),
.markdown-text-segment :deep(ol) {
padding-left: 24px;
margin-bottom: 12px;
}
.markdown-text-segment :deep(ul) {
list-style-type: disc;
}
.markdown-text-segment :deep(ul ul) {
list-style-type: circle;
margin-top: 6px;
}
.markdown-text-segment :deep(ol) {
list-style-type: decimal;
}
.markdown-text-segment :deep(li) {
margin-bottom: 6px;
line-height: 1.6;
}
</style> </style>

View File

@ -179,6 +179,14 @@
> >
{{ $t('input.waitingForQuestion') }} {{ $t('input.waitingForQuestion') }}
</button> </button>
<button
v-if="planApprovalMinimized && (pendingPlanApprovalCount || 0) > 0"
type="button"
class="floating-project-status__notice floating-project-status__notice--button"
@click.stop="$emit('restore-plan-approval')"
>
{{ $t('input.planApprovalPending') }}
</button>
<button <button
v-if="(activeSubAgentCount || 0) > 0" v-if="(activeSubAgentCount || 0) > 0"
type="button" type="button"
@ -698,6 +706,7 @@ const emit = defineEmits([
'delete-runtime-message', 'delete-runtime-message',
'composer-height-change', 'composer-height-change',
'restore-user-question', 'restore-user-question',
'restore-plan-approval',
'toggle-goal-mode', 'toggle-goal-mode',
'open-goal-dialog', 'open-goal-dialog',
'workflow-activated', 'workflow-activated',
@ -776,6 +785,8 @@ const props = defineProps<{
} | null; } | null;
userQuestionMinimized?: boolean; userQuestionMinimized?: boolean;
pendingUserQuestionCount?: number; pendingUserQuestionCount?: number;
planApprovalMinimized?: boolean;
pendingPlanApprovalCount?: number;
goalModeArmed?: boolean; goalModeArmed?: boolean;
goalRunning?: boolean; goalRunning?: boolean;
goalProgress?: Record<string, any> | null; goalProgress?: Record<string, any> | null;
@ -1066,6 +1077,7 @@ const floatingStatusVisible = computed(() => {
!!props.goalRunning || !!props.goalRunning ||
!!props.goalModeArmed || !!props.goalModeArmed ||
(Number(props.pendingUserQuestionCount || 0) > 0) || (Number(props.pendingUserQuestionCount || 0) > 0) ||
(!!props.planApprovalMinimized && Number(props.pendingPlanApprovalCount || 0) > 0) ||
(Number(props.activeSubAgentCount || 0) > 0); (Number(props.activeSubAgentCount || 0) > 0);
return visible; return visible;
}); });

View File

@ -4,6 +4,17 @@
<section class="plan-approval-card" role="dialog" aria-modal="true" :aria-label="$t('overlay.planApprovalAriaLabel')"> <section class="plan-approval-card" role="dialog" aria-modal="true" :aria-label="$t('overlay.planApprovalAriaLabel')">
<header class="plan-approval-windowbar"> <header class="plan-approval-windowbar">
<div class="plan-approval-window-title">{{ $t('overlay.planApprovalTitle') }}</div> <div class="plan-approval-window-title">{{ $t('overlay.planApprovalTitle') }}</div>
<button
type="button"
class="plan-approval-close"
:title="$t('overlay.planApprovalMinimize')"
:aria-label="$t('overlay.planApprovalMinimize')"
@click="emit('minimize')"
>
<svg viewBox="0 0 20 20" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M5 5l10 10M15 5L5 15" />
</svg>
</button>
</header> </header>
<header class="plan-approval-header"> <header class="plan-approval-header">
@ -75,6 +86,7 @@ const props = defineProps<{
const emit = defineEmits<{ const emit = defineEmits<{
(event: 'submit', payload: { approval_id: string; approved: boolean; comment: string }): void; (event: 'submit', payload: { approval_id: string; approved: boolean; comment: string }): void;
(event: 'minimize'): void;
}>(); }>();
// plan // plan
@ -139,6 +151,7 @@ function reject() {
} }
.plan-approval-windowbar { .plan-approval-windowbar {
position: relative;
height: 40px; height: 40px;
flex: 0 0 40px; flex: 0 0 40px;
display: flex; display: flex;
@ -147,6 +160,29 @@ function reject() {
border-bottom: 1px solid var(--border-default); border-bottom: 1px solid var(--border-default);
} }
.plan-approval-close {
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
width: 26px;
height: 26px;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
border: none;
border-radius: 7px;
background: transparent;
color: var(--text-secondary);
cursor: pointer;
}
.plan-approval-close:hover {
background: var(--hover-bg);
color: var(--text-primary);
}
.plan-approval-window-title { .plan-approval-window-title {
font-size: 13px; font-size: 13px;
font-weight: 600; font-weight: 600;

View File

@ -9,6 +9,7 @@ export default {
goalModeRunning: 'Goal mode running', goalModeRunning: 'Goal mode running',
goalModeReady: 'Goal mode ready', goalModeReady: 'Goal mode ready',
waitingForQuestion: 'Waiting for your answer', waitingForQuestion: 'Waiting for your answer',
planApprovalPending: 'Plan awaiting approval',
subAgentsRunning: '{n} sub-agents running', subAgentsRunning: '{n} sub-agents running',
terminalsRunning: '{n} terminals', terminalsRunning: '{n} terminals',
quickMenuAriaLabel: 'Quick menu', quickMenuAriaLabel: 'Quick menu',

View File

@ -88,6 +88,7 @@ export default {
// ── PlanApprovalDialog ── // ── PlanApprovalDialog ──
planApprovalAriaLabel: 'Plan awaiting approval', planApprovalAriaLabel: 'Plan awaiting approval',
planApprovalTitle: 'Plan awaiting approval', planApprovalTitle: 'Plan awaiting approval',
planApprovalMinimize: 'Close the dialog; restore it later from the status bar',
planTruncatedNote: '(Content too long; preview truncated. See the file for the full content)', planTruncatedNote: '(Content too long; preview truncated. See the file for the full content)',
planCommentPlaceholder: planCommentPlaceholder:
'Feedback (optional): added as extra requirements when approving, or describe what to adjust when rejecting...', 'Feedback (optional): added as extra requirements when approving, or describe what to adjust when rejecting...',

View File

@ -9,6 +9,7 @@ export default {
goalModeRunning: '目标模式运行中', goalModeRunning: '目标模式运行中',
goalModeReady: '目标模式就绪', goalModeReady: '目标模式就绪',
waitingForQuestion: '等待回答问题', waitingForQuestion: '等待回答问题',
planApprovalPending: '有待批准的计划',
subAgentsRunning: '{n} 个子智能体运行中', subAgentsRunning: '{n} 个子智能体运行中',
terminalsRunning: '{n} 个终端', terminalsRunning: '{n} 个终端',
quickMenuAriaLabel: '快捷菜单', quickMenuAriaLabel: '快捷菜单',

View File

@ -87,6 +87,7 @@ export default {
// ── PlanApprovalDialog计划待批准 ── // ── PlanApprovalDialog计划待批准 ──
planApprovalAriaLabel: '计划待批准', planApprovalAriaLabel: '计划待批准',
planApprovalTitle: '计划待批准', planApprovalTitle: '计划待批准',
planApprovalMinimize: '关闭弹窗,稍后从状态栏恢复',
planTruncatedNote: '(内容过长,已截断预览,完整内容见文件)', planTruncatedNote: '(内容过长,已截断预览,完整内容见文件)',
planCommentPlaceholder: '意见(可选):批准时作为补充要求,拒绝时说明需要调整的方向…', planCommentPlaceholder: '意见(可选):批准时作为补充要求,拒绝时说明需要调整的方向…',
planRejectTitle: '拒绝这份计划AI 将根据你的意见修订后重新提交', planRejectTitle: '拒绝这份计划AI 将根据你的意见修订后重新提交',

View File

@ -44,6 +44,46 @@
gap: 12px; gap: 12px;
} }
/* 顶栏下方渐进模糊带mask-image backdrop-filter 写在同一元素上时
新版浏览器内核中模糊会静默失效2026-08 实测单元素 mask 渐隐模糊不可用
改用三级强度递减的条带元素本体 + ::before + ::after模拟渐进模糊
上方再由 ::after 颜色渐隐层溶掉阶梯感 */
.conversation-ribbon__blur {
position: absolute;
left: 0;
right: 0;
bottom: 2px; /* 带 1最强ribbon 底上 8px ~ 底上 2px */
height: 6px;
backdrop-filter: blur(14px);
-webkit-backdrop-filter: blur(14px);
pointer-events: none;
}
.conversation-ribbon__blur::before,
.conversation-ribbon__blur::after {
content: '';
position: absolute;
left: 0;
right: 0;
pointer-events: none;
}
/* 带 2中等与带 1 重叠 1px 避免接缝 */
.conversation-ribbon__blur::before {
top: 5px;
height: 7px;
backdrop-filter: blur(5px);
-webkit-backdrop-filter: blur(5px);
}
/* 带 3极轻溶掉模糊下边界 */
.conversation-ribbon__blur::after {
top: 11px;
height: 6px;
backdrop-filter: blur(1.2px);
-webkit-backdrop-filter: blur(1.2px);
}
.conversation-ribbon::after { .conversation-ribbon::after {
content: ''; content: '';
position: absolute; position: absolute;

View File

@ -1440,6 +1440,46 @@
height: 44px; height: 44px;
} }
/* 顶栏下方渐进模糊带mask-image backdrop-filter 写在同一元素上时
新版浏览器内核中模糊会静默失效2026-08 实测单元素 mask 渐隐模糊不可用
改用三级强度递减的条带元素本体 + ::before + ::after模拟渐进模糊
上方再由 ::after 颜色渐隐层溶掉阶梯感 */
.mobile-panel-topbar__blur {
position: absolute;
left: 0;
right: 0;
bottom: 2px; /* 带 1最强顶栏底上 8px ~ 底上 2px */
height: 6px;
backdrop-filter: blur(14px);
-webkit-backdrop-filter: blur(14px);
pointer-events: none;
}
.mobile-panel-topbar__blur::before,
.mobile-panel-topbar__blur::after {
content: '';
position: absolute;
left: 0;
right: 0;
pointer-events: none;
}
/* 带 2中等与带 1 重叠 1px 避免接缝 */
.mobile-panel-topbar__blur::before {
top: 5px;
height: 7px;
backdrop-filter: blur(5px);
-webkit-backdrop-filter: blur(5px);
}
/* 带 3极轻溶掉模糊下边界 */
.mobile-panel-topbar__blur::after {
top: 11px;
height: 6px;
backdrop-filter: blur(1.2px);
-webkit-backdrop-filter: blur(1.2px);
}
.mobile-panel-topbar::after { .mobile-panel-topbar::after {
content: ''; content: '';
position: absolute; position: absolute;

View File

@ -549,32 +549,6 @@ body[data-theme='dark'] {
} }
} }
/* Markdown列表样式 - 修复偏左问题 */
.text-content ul,
.text-content ol {
margin-left: 24px; /* 增加左边距 */
padding-left: 0;
margin-bottom: 12px;
}
.text-content ul {
list-style-type: disc; /* 实心圆点 */
}
.text-content ul ul {
list-style-type: circle; /* 空心圆点 */
margin-top: 6px;
}
.text-content ol {
list-style-type: decimal; /* 数字列表 */
}
.text-content li {
margin-bottom: 6px;
line-height: 1.6;
}
/* 搜索结果展示 */ /* 搜索结果展示 */
.search-meta { .search-meta {
font-size: 14px; font-size: 14px;