diff --git a/src/web-ui/src/component-library/components/ConfirmDialog/ConfirmDialog.scss b/src/web-ui/src/component-library/components/ConfirmDialog/ConfirmDialog.scss index e87a5281d..485287063 100644 --- a/src/web-ui/src/component-library/components/ConfirmDialog/ConfirmDialog.scss +++ b/src/web-ui/src/component-library/components/ConfirmDialog/ConfirmDialog.scss @@ -1,18 +1,23 @@ /** * ConfirmDialog styles + * Icon top-centered; content uses full width of a wide (landscape) modal. */ @use '../../styles/tokens' as *; .confirm-dialog { - padding: $size-gap-4 $size-gap-5; - text-align: center; - min-width: 320px; - max-width: 480px; + display: flex; + flex-direction: column; + align-items: stretch; + width: 100%; + max-width: 560px; + box-sizing: border-box; + padding-top: $size-gap-16; &__icon { + flex-shrink: 0; width: 48px; height: 48px; - margin: 0 auto $size-gap-3; + margin: 0 auto $size-gap-4; display: flex; align-items: center; justify-content: center; @@ -44,14 +49,20 @@ } &__content { - margin-bottom: $size-gap-4; + flex: 1; + min-width: 0; + margin: 0; + padding: 0 $size-gap-6 $size-gap-4; } &__title { font-size: $font-size-lg; font-weight: $font-weight-semibold; + line-height: 1.35; + letter-spacing: -0.01em; color: var(--color-text-primary); - margin: 0 0 $size-gap-2 0; + margin: 0 0 $size-gap-3; + text-align: center; } &__title--compact { @@ -60,13 +71,65 @@ &__message { font-size: $font-size-sm; + line-height: 1.55; color: var(--color-text-secondary); + text-align: left; + + > *:first-child { + margin-top: 0; + } + + > *:last-child { + margin-bottom: 0; + } + } + + /** Lead line before bullet list (e.g. “This will:”) */ + &__message-intro { + margin: 0 0 $size-gap-3; + font-size: $font-size-sm; + font-weight: $font-weight-medium; line-height: 1.5; + color: var(--color-text-primary); + } + + /** Structured bullet list inside confirm body */ + &__bullet-list { + list-style: none; + margin: 0; + padding: $size-gap-3 $size-gap-5; + border-radius: $size-radius-base; + background: var(--color-bg-subtle); + border: 1px solid var(--color-border); + } + + &__bullet-list li { + position: relative; + margin: 0; + padding-left: $size-gap-5; + font-size: $font-size-sm; + line-height: 1.55; + color: var(--color-text-secondary); + + &::before { + content: ''; + position: absolute; + left: 2px; + top: 0.55em; + width: 6px; + height: 6px; + border-radius: 50%; + background: color-mix(in srgb, var(--color-text-secondary) 45%, transparent); + } + } + + &__bullet-list li + li { + margin-top: $size-gap-2; } &__preview { margin-top: $size-gap-3; - padding: $size-gap-2 $size-gap-3; + padding: $size-gap-3 $size-gap-4; background: var(--color-bg-subtle); border: 1px solid var(--color-border); border-radius: $size-radius-base; @@ -91,12 +154,11 @@ gap: $size-gap-2; justify-content: flex-end; align-items: center; - width: 100%; - box-sizing: border-box; - padding-inline: $size-gap-3; + padding: $size-gap-4 $size-gap-6 $size-gap-5; + border-top: 1px solid var(--color-border); .btn { - min-width: 80px; + min-width: 88px; } } } diff --git a/src/web-ui/src/component-library/components/ConfirmDialog/ConfirmDialog.tsx b/src/web-ui/src/component-library/components/ConfirmDialog/ConfirmDialog.tsx index 5c965293d..3192eb18f 100644 --- a/src/web-ui/src/component-library/components/ConfirmDialog/ConfirmDialog.tsx +++ b/src/web-ui/src/component-library/components/ConfirmDialog/ConfirmDialog.tsx @@ -3,7 +3,7 @@ * Supports both controlled usage and imperative calls */ -import React, { useEffect, useRef } from 'react'; +import React, { useEffect, useId, useRef } from 'react'; import { useI18n } from '@/infrastructure/i18n'; import { Modal } from '../Modal/Modal'; import { Button } from '../Button/Button'; @@ -64,6 +64,7 @@ export const ConfirmDialog: React.FC = ({ previewMaxHeight = 200, }) => { const { t } = useI18n('components'); + const titleId = useId(); const hasMessage = message !== null && message !== undefined && message !== ''; // Resolve i18n default values @@ -94,22 +95,29 @@ export const ConfirmDialog: React.FC = ({
-
+
{iconMap[type]}
- +
-

{title}

+

+ {title} +

{hasMessage ? ( -
{message}
+
+ {message} +
) : null} - + {preview && ( -
diff --git a/src/web-ui/src/features/ssh-remote/ConfirmDialog.scss b/src/web-ui/src/features/ssh-remote/ConfirmDialog.scss index e2df491fa..dd175397f 100644 --- a/src/web-ui/src/features/ssh-remote/ConfirmDialog.scss +++ b/src/web-ui/src/features/ssh-remote/ConfirmDialog.scss @@ -1,8 +1,8 @@ /** - * Confirm Dialog Styles + * SSH remote confirmation modal (scoped block — avoid clashing with component-library .confirm-dialog) */ -.confirm-dialog { +.ssh-remote-confirm-dialog { padding: 8px 0; &__warning { diff --git a/src/web-ui/src/features/ssh-remote/ConfirmDialog.tsx b/src/web-ui/src/features/ssh-remote/ConfirmDialog.tsx index 0d3193959..e4755e219 100644 --- a/src/web-ui/src/features/ssh-remote/ConfirmDialog.tsx +++ b/src/web-ui/src/features/ssh-remote/ConfirmDialog.tsx @@ -46,15 +46,15 @@ export const ConfirmDialog: React.FC = ({ size="small" showCloseButton > -
+
{destructive && ( -
+
{title}
)} -

{message}

-
+

{message}

+
diff --git a/src/web-ui/src/flow_chat/components/TurnRollbackButton.tsx b/src/web-ui/src/flow_chat/components/TurnRollbackButton.tsx index 41a87ffde..9abcc416a 100644 --- a/src/web-ui/src/flow_chat/components/TurnRollbackButton.tsx +++ b/src/web-ui/src/flow_chat/components/TurnRollbackButton.tsx @@ -1,6 +1,8 @@ import React, { useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { snapshotAPI } from '@/infrastructure/api'; import { notificationService } from '@/shared/notification-system'; +import { confirmDanger } from '@/component-library'; import { createLogger } from '@/shared/utils/logger'; import './TurnRollbackButton.scss'; @@ -19,20 +21,27 @@ export const TurnRollbackButton: React.FC = ({ isCurrent, onRollbackComplete, }) => { + const { t } = useTranslation('flow-chat'); const [loading, setLoading] = useState(false); const handleRollback = async () => { if (isCurrent || loading) return; - - // In Tauri, window.confirm is async and must be awaited. - const confirmed = await window.confirm( - `Roll back to before turn ${turnIndex + 1}?\n\n` + - `This will:\n` + - `• Restore files to the state before turn ${turnIndex + 1}\n` + - `• Undo all file changes from turn ${turnIndex + 1} onward\n` + - `• Keep the session history (you can roll forward again anytime)` + + const index = turnIndex + 1; + const confirmed = await confirmDanger( + t('message.rollbackPanelDialogTitle', { index }), + ( + <> +

{t('message.rollbackPanelDialogIntro')}

+
    +
  • {t('message.rollbackPanelBulletRestore', { index })}
  • +
  • {t('message.rollbackPanelBulletUndo', { index })}
  • +
  • {t('message.rollbackPanelBulletHistory')}
  • +
+ + ) ); - + if (!confirmed) return; setLoading(true); @@ -66,7 +75,9 @@ export const TurnRollbackButton: React.FC = ({ } catch (error) { log.error('Rollback failed', { sessionId, turnIndex, error }); - notificationService.error(`Rollback failed: ${error}`); + notificationService.error( + `${t('message.rollbackFailed')}: ${error instanceof Error ? error.message : String(error)}` + ); } finally { setLoading(false); } diff --git a/src/web-ui/src/flow_chat/components/modern/UserMessageItem.scss b/src/web-ui/src/flow_chat/components/modern/UserMessageItem.scss index 7947761a3..aacce0bb2 100644 --- a/src/web-ui/src/flow_chat/components/modern/UserMessageItem.scss +++ b/src/web-ui/src/flow_chat/components/modern/UserMessageItem.scss @@ -70,17 +70,10 @@ } -// Expanded state. +// Expanded state (outer scroll area). .user-message-item--expanded { max-height: 60vh; overflow-y: auto; - - .user-message-item__content { - white-space: pre-wrap; - overflow: visible; - text-overflow: unset; - max-height: none; - } } // Failed state. @@ -101,6 +94,15 @@ } } +// Expanded message body: after --failed so multiline / whitespace always apply when open. +.user-message-item--expanded .user-message-item__content { + white-space: pre-wrap; + overflow: visible; + text-overflow: unset; + max-height: none; + overflow-wrap: break-word; +} + .user-message-item__copy-btn { // Align with the IconButton ghost variant. display: inline-flex; diff --git a/src/web-ui/src/flow_chat/components/modern/UserMessageItem.tsx b/src/web-ui/src/flow_chat/components/modern/UserMessageItem.tsx index 7cf7d75f7..46a003f30 100644 --- a/src/web-ui/src/flow_chat/components/modern/UserMessageItem.tsx +++ b/src/web-ui/src/flow_chat/components/modern/UserMessageItem.tsx @@ -13,7 +13,7 @@ import { flowChatStore } from '../../store/FlowChatStore'; import { snapshotAPI } from '@/infrastructure/api'; import { notificationService } from '@/shared/notification-system'; import { globalEventBus } from '@/infrastructure/event-bus'; -import { ReproductionStepsBlock, Tooltip } from '@/component-library'; +import { ReproductionStepsBlock, Tooltip, confirmDanger } from '@/component-library'; import { createLogger } from '@/shared/utils/logger'; import './UserMessageItem.scss'; @@ -101,7 +101,19 @@ export const UserMessageItem = React.memo( e.stopPropagation(); if (!canRollback || !sessionId) return; - const confirmed = await window.confirm(t('message.rollbackConfirm', { index: turnIndex + 1 })); + const index = turnIndex + 1; + const confirmed = await confirmDanger( + t('message.rollbackDialogTitle', { index }), + ( + <> +

{t('message.rollbackDialogIntro')}

+
    +
  • {t('message.rollbackDialogBulletFiles')}
  • +
  • {t('message.rollbackDialogBulletHistory')}
  • +
+ + ) + ); if (!confirmed) return; setIsRollingBack(true); @@ -181,7 +193,11 @@ export const UserMessageItem = React.memo( className="user-message-item__content" onClick={handleToggleExpand} title={(hasOverflow || expanded) ? (expanded ? t('message.clickToCollapse') : t('message.clickToExpand')) : undefined} - style={{ cursor: (hasOverflow || expanded) ? 'pointer' : 'text' }} + style={{ + cursor: (hasOverflow || expanded) ? 'pointer' : 'text', + // Inline so newline preservation wins over any global/cascade overrides. + ...(expanded ? { whiteSpace: 'pre-wrap' as const } : {}), + }} > {displayText}
diff --git a/src/web-ui/src/infrastructure/config/components/AIModelConfig.tsx b/src/web-ui/src/infrastructure/config/components/AIModelConfig.tsx index b9f2f47c6..eef89f19f 100644 --- a/src/web-ui/src/infrastructure/config/components/AIModelConfig.tsx +++ b/src/web-ui/src/infrastructure/config/components/AIModelConfig.tsx @@ -1750,31 +1750,31 @@ const AIModelConfig: React.FC = () => { }} size="small" /> - - - + ); diff --git a/src/web-ui/src/locales/en-US/common.json b/src/web-ui/src/locales/en-US/common.json index 758525bc2..15bf39784 100644 --- a/src/web-ui/src/locales/en-US/common.json +++ b/src/web-ui/src/locales/en-US/common.json @@ -51,7 +51,7 @@ "chatPanel": "Chat", "showChatPanel": "Show Chat Panel", "hideChatPanel": "Hide Chat Panel", - "switchToToolbar": "Switch to Toolbar Mode", + "switchToToolbar": "Floating window mode", "remoteConnect": "Remote Control (Beta)", "modeSwitchAriaLabel": "View mode switch", "modeCowork": "Cowork", diff --git a/src/web-ui/src/locales/en-US/flow-chat.json b/src/web-ui/src/locales/en-US/flow-chat.json index 2fc565979..acd4349f1 100644 --- a/src/web-ui/src/locales/en-US/flow-chat.json +++ b/src/web-ui/src/locales/en-US/flow-chat.json @@ -87,7 +87,15 @@ "fillToInput": "Fill to input", "cannotRollback": "Cannot rollback", "rollbackTo": "Rollback to before turn {{index}} (delete this and following turns)", - "rollbackConfirm": "Are you sure you want to rollback to before turn {{index}}?\n\nThis will:\n• Undo all file modifications from that turn onwards\n• Delete that turn and all following dialog records (irreversible)", + "rollbackDialogTitle": "Rollback to before turn {{index}}?", + "rollbackDialogIntro": "This will:", + "rollbackDialogBulletFiles": "Undo all file modifications from that turn onwards", + "rollbackDialogBulletHistory": "Delete that turn and all following dialog records (irreversible)", + "rollbackPanelDialogTitle": "Roll back to before turn {{index}}?", + "rollbackPanelDialogIntro": "This will:", + "rollbackPanelBulletRestore": "Restore files to the state before turn {{index}}", + "rollbackPanelBulletUndo": "Undo all file changes from that turn onward", + "rollbackPanelBulletHistory": "Keep the session history (you can roll forward again anytime)", "rollbackSuccess": "Rolled back and deleted subsequent dialog records", "rollbackFailed": "Rollback failed" }, diff --git a/src/web-ui/src/locales/zh-CN/common.json b/src/web-ui/src/locales/zh-CN/common.json index 8d26fb9a8..6d11380b4 100644 --- a/src/web-ui/src/locales/zh-CN/common.json +++ b/src/web-ui/src/locales/zh-CN/common.json @@ -51,7 +51,7 @@ "chatPanel": "聊天", "showChatPanel": "显示聊天面板", "hideChatPanel": "隐藏聊天面板", - "switchToToolbar": "切换到工具栏模式", + "switchToToolbar": "悬浮窗模式", "remoteConnect": "远程控制 (Beta)", "modeSwitchAriaLabel": "视图模式切换", "modeCowork": "Cowork", diff --git a/src/web-ui/src/locales/zh-CN/flow-chat.json b/src/web-ui/src/locales/zh-CN/flow-chat.json index e7cf1b5ae..454ee1f36 100644 --- a/src/web-ui/src/locales/zh-CN/flow-chat.json +++ b/src/web-ui/src/locales/zh-CN/flow-chat.json @@ -87,7 +87,15 @@ "fillToInput": "填充到输入框", "cannotRollback": "无法回滚", "rollbackTo": "回滚到第 {{index}} 轮之前(删除该轮及之后)", - "rollbackConfirm": "确定要回滚到第 {{index}} 轮之前吗?\n\n这将:\n• 撤销从该轮开始的所有文件修改\n• 删除该轮及之后的对话记录(不可恢复)", + "rollbackDialogTitle": "确定回滚到第 {{index}} 轮之前?", + "rollbackDialogIntro": "将执行:", + "rollbackDialogBulletFiles": "撤销从该轮开始的所有文件修改", + "rollbackDialogBulletHistory": "删除该轮及之后的对话记录(不可恢复)", + "rollbackPanelDialogTitle": "回滚到第 {{index}} 轮之前?", + "rollbackPanelDialogIntro": "将执行:", + "rollbackPanelBulletRestore": "将文件恢复到第 {{index}} 轮之前的状态", + "rollbackPanelBulletUndo": "撤销从该轮开始的所有文件变更", + "rollbackPanelBulletHistory": "保留会话历史(之后仍可继续对话)", "rollbackSuccess": "已回滚并删除后续对话记录", "rollbackFailed": "回滚失败" },