fix(chat): repair Arco migration fallout in the chat page
The Phase 006 walkthrough surfaced four issues the automated checks could not see. Dead naive-ui class names left behind by the migration stopped matching, tooltips that carried their label as trailing default-slot text leaked that text into the flex row, the workspace panel squeezed the chat column until the input toolbar wrapped, and the Escape fallback still looked for naive-ui's modal mask. Tooltips now pass their label through the content prop, the tool panel reserves a minimum width for the chat column, and Escape finds the visible Arco modal and clicks its close button.
This commit is contained in:
@@ -1048,11 +1048,10 @@ function isImage(type: string): boolean {
|
||||
<div v-if="showContextUsage" class="context-usage-row">
|
||||
<span class="context-info" :class="{ 'context-warning': usagePercent > 80 }">
|
||||
{{ formatTokens(totalTokens) }} /
|
||||
<a-tooltip trigger="hover" :disabled="isMobileViewport">
|
||||
<a-tooltip trigger="hover" :disabled="isMobileViewport" :content="t('chat.contextClickToEdit')">
|
||||
<span class="context-limit-editable" @click="handleEditContextLimit">
|
||||
{{ formatTokens(contextLength) }}
|
||||
</span>
|
||||
<span>{{ t('chat.contextClickToEdit') }}</span>
|
||||
</a-tooltip>
|
||||
· {{ t('chat.contextRemaining') }} {{ formatTokens(remainingTokens) }}
|
||||
</span>
|
||||
@@ -1083,13 +1082,16 @@ function isImage(type: string): boolean {
|
||||
<div class="input-toolbar">
|
||||
<!-- Bottom bar: attach + input settings + actions -->
|
||||
<div class="input-top-bar">
|
||||
<a-tooltip trigger="hover" :disabled="isMobileViewport">
|
||||
<a-tooltip
|
||||
trigger="hover"
|
||||
:disabled="isMobileViewport"
|
||||
:content="t('chat.attachFiles')"
|
||||
>
|
||||
<a-button type="secondary" size="mini" shape="circle" @click="handleAttachClick" class="toolbar-icon-button">
|
||||
<template #icon>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><path d="M12 5v14"/><path d="M5 12h14"/></svg>
|
||||
</template>
|
||||
</a-button>
|
||||
{{ t('chat.attachFiles') }}
|
||||
</a-tooltip>
|
||||
|
||||
<a-dropdown
|
||||
@@ -1159,7 +1161,11 @@ function isImage(type: string): boolean {
|
||||
</template>
|
||||
</a-dropdown>
|
||||
|
||||
<a-tooltip trigger="hover" :disabled="isMobileViewport">
|
||||
<a-tooltip
|
||||
trigger="hover"
|
||||
:disabled="isMobileViewport"
|
||||
:content="props.modelLabel || t('models.selectModel')"
|
||||
>
|
||||
<a-button
|
||||
type="secondary"
|
||||
size="mini"
|
||||
@@ -1193,7 +1199,6 @@ function isImage(type: string): boolean {
|
||||
<span class="input-model-label">{{ compactModelLabel }}</span>
|
||||
<svg class="toolbar-chevron" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m6 9 6 6 6-6"/></svg>
|
||||
</a-button>
|
||||
{{ props.modelLabel || t('models.selectModel') }}
|
||||
</a-tooltip>
|
||||
|
||||
</div>
|
||||
@@ -1386,8 +1391,8 @@ function isImage(type: string): boolean {
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.n-switch),
|
||||
:deep(.n-switch__rail) {
|
||||
:deep(.arco-switch),
|
||||
:deep(.arco-switch-track) {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
@@ -1405,12 +1410,6 @@ function isImage(type: string): boolean {
|
||||
background: transparent !important;
|
||||
opacity: 1;
|
||||
|
||||
:deep(.n-button__state-border),
|
||||
:deep(.n-button__border),
|
||||
:deep(.n-button__ripple) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tool-trace-icon {
|
||||
display: block;
|
||||
flex: 0 0 16px;
|
||||
@@ -1433,16 +1432,8 @@ function isImage(type: string): boolean {
|
||||
color: $text-secondary;
|
||||
border-radius: 999px;
|
||||
padding: 0 7px 0 6px;
|
||||
|
||||
:deep(.n-button__content) {
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
:deep(.n-button__state-border),
|
||||
:deep(.n-button__border),
|
||||
:deep(.n-button__ripple) {
|
||||
display: none;
|
||||
}
|
||||
gap: 4px;
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.input-model-button {
|
||||
@@ -1450,17 +1441,8 @@ function isImage(type: string): boolean {
|
||||
border-radius: 999px;
|
||||
max-width: 190px;
|
||||
padding: 0 4px 0 6px;
|
||||
|
||||
:deep(.n-button__content) {
|
||||
gap: 4px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
:deep(.n-button__state-border),
|
||||
:deep(.n-button__border),
|
||||
:deep(.n-button__ripple) {
|
||||
display: none;
|
||||
}
|
||||
gap: 4px;
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.input-model-label {
|
||||
@@ -1476,15 +1458,12 @@ function isImage(type: string): boolean {
|
||||
color: $text-secondary;
|
||||
border-radius: 999px;
|
||||
padding: 0 4px 0 6px;
|
||||
gap: 4px;
|
||||
border-color: transparent;
|
||||
|
||||
&.active {
|
||||
color: #4caf50;
|
||||
}
|
||||
|
||||
:deep(.n-button__content) {
|
||||
gap: 4px;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.reasoning-effort-label {
|
||||
@@ -1625,14 +1604,7 @@ function isImage(type: string): boolean {
|
||||
.input-settings-button {
|
||||
min-width: 36px;
|
||||
padding: 0 4px 0 6px;
|
||||
|
||||
:deep(.n-button__content) {
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
:deep(.n-button__icon) {
|
||||
margin: 0;
|
||||
}
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.input-settings-label {
|
||||
@@ -1820,6 +1792,7 @@ function isImage(type: string): boolean {
|
||||
gap: 12px;
|
||||
margin-top: auto;
|
||||
min-height: 32px;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.toolbar-icon-button {
|
||||
@@ -1833,22 +1806,12 @@ function isImage(type: string): boolean {
|
||||
padding: 0 !important;
|
||||
border: 0 !important;
|
||||
box-shadow: none !important;
|
||||
gap: 0;
|
||||
|
||||
:deep(.n-button__icon) {
|
||||
:deep(.arco-btn-icon) {
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
:deep(.n-button__content) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:deep(.n-button__border),
|
||||
:deep(.n-button__state-border),
|
||||
:deep(.n-button__ripple),
|
||||
:deep(.n-base-wave) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
color: var(--text-on-overlay);
|
||||
background-color: #9f9f9f;
|
||||
|
||||
@@ -44,6 +44,9 @@ const activeWorkspaceSessionId = computed(() => chatStore.activeSession?.workspa
|
||||
const activeWorkspacePath = computed(() => chatStore.activeSession?.workspace && !chatStore.activeSession.isLocalOnly ? chatStore.activeSession.workspace : null);
|
||||
const TOOL_PANEL_MIN_WIDTH = 360;
|
||||
const TOOL_PANEL_DEFAULT_WIDTH = 560;
|
||||
// 工具栏要单行放下:附件 + 推理强度 + 设置 + 模型下拉(最长 190px)+ 语音 + 发送,
|
||||
// 再加输入框左右内边距。窄于此值 Arco 按钮会被挤成竖排。
|
||||
const CHAT_MAIN_MIN_WIDTH = 620;
|
||||
const TOOL_PANEL_STORAGE_KEY = "hermes.chat.toolPanelWidth";
|
||||
const toolPanelWidth = ref(loadToolPanelWidth());
|
||||
const toolResizeStart = ref<{ x: number; width: number } | null>(null);
|
||||
@@ -102,7 +105,9 @@ function toolPanelMaxWidth() {
|
||||
if (typeof window === "undefined") return 1180;
|
||||
if (isMobile.value) return window.innerWidth;
|
||||
const available = chatContentWrapperRef.value?.clientWidth || window.innerWidth;
|
||||
return Math.max(320, Math.min(Math.floor(available * 0.88), available - 120));
|
||||
// 主区要放得下输入工具栏(附件 + 推理强度 + 设置 + 模型 + 语音 + 发送),
|
||||
// 否则面板一展开工具栏就被压成竖排。
|
||||
return Math.max(320, Math.min(Math.floor(available * 0.88), available - CHAT_MAIN_MIN_WIDTH));
|
||||
}
|
||||
|
||||
function clampToolPanelWidth(width: number) {
|
||||
@@ -2068,7 +2073,7 @@ async function handleSessionModelCustomSubmit() {
|
||||
gap: 4px;
|
||||
height: 22px;
|
||||
|
||||
.n-button {
|
||||
.arco-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -2150,24 +2155,20 @@ async function handleSessionModelCustomSubmit() {
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
:deep(.new-chat-drawer .n-drawer-content) {
|
||||
:deep(.new-chat-drawer .arco-drawer) {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
:deep(.new-chat-drawer .n-drawer-header),
|
||||
:deep(.new-chat-drawer .n-drawer-footer) {
|
||||
:deep(.new-chat-drawer .arco-drawer-header),
|
||||
:deep(.new-chat-drawer .arco-drawer-footer) {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
:deep(.new-chat-drawer .n-drawer-body) {
|
||||
:deep(.new-chat-drawer .arco-drawer-body) {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:deep(.new-chat-drawer .n-drawer-body-content-wrapper) {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
@@ -2181,7 +2182,7 @@ async function handleSessionModelCustomSubmit() {
|
||||
}
|
||||
|
||||
@media (max-width: $breakpoint-mobile) {
|
||||
:deep(.new-chat-drawer .n-drawer-body-content-wrapper) {
|
||||
:deep(.new-chat-drawer .arco-drawer-body) {
|
||||
padding-top: 12px;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
@@ -2112,7 +2112,7 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.n-drawer-body-content-wrapper) {
|
||||
:deep(.arco-drawer-body) {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
||||
@@ -1017,12 +1017,12 @@ defineExpose({
|
||||
padding: 10px 4px 0;
|
||||
border-top: 1px solid $border-color;
|
||||
|
||||
:deep(.n-input) {
|
||||
:deep(.arco-input) {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
:deep(.n-button) {
|
||||
:deep(.arco-btn) {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
}
|
||||
@@ -1200,7 +1200,7 @@ defineExpose({
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
|
||||
:deep(.n-button) {
|
||||
:deep(.arco-btn) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
@@ -1208,7 +1208,7 @@ defineExpose({
|
||||
.clarify-float-input-row {
|
||||
flex-direction: column;
|
||||
|
||||
:deep(.n-button) {
|
||||
:deep(.arco-btn) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -445,7 +445,7 @@ onUnmounted(() => {
|
||||
}
|
||||
|
||||
@media (max-width: $breakpoint-mobile) {
|
||||
:deep(.n-modal-body-wrapper) {
|
||||
:deep(.arco-modal) {
|
||||
width: calc(100vw - 24px);
|
||||
}
|
||||
|
||||
|
||||
@@ -473,7 +473,7 @@ onUnmounted(() => {
|
||||
>
|
||||
<div class="sidebar-header">
|
||||
<span class="sidebar-title">{{ t("terminal.sessions") }}</span>
|
||||
<a-tooltip trigger="hover">
|
||||
<a-tooltip trigger="hover" :content="t('terminal.newTab')">
|
||||
<a-button type="secondary" size="mini" @click="createSession" shape="circle">
|
||||
<template #icon>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
@@ -482,7 +482,6 @@ onUnmounted(() => {
|
||||
</svg>
|
||||
</template>
|
||||
</a-button>
|
||||
{{ t("terminal.newTab") }}
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<div class="session-list">
|
||||
|
||||
@@ -36,12 +36,14 @@ export function useKeyboard() {
|
||||
closeSessionSearch()
|
||||
return
|
||||
}
|
||||
// Close any open modals — naive-ui handles this internally
|
||||
const modal = document.querySelector('.n-modal-mask')
|
||||
if (modal) {
|
||||
const closeBtn = modal.querySelector('.n-base-close') as HTMLElement
|
||||
closeBtn?.click()
|
||||
}
|
||||
// 聊天页的弹窗已全部迁到 Arco(且都带 unmount-on-close),Esc 兜底要查
|
||||
// Arco 的类名,否则模型选择、会话搜索这些弹窗在键盘下关不掉。
|
||||
const openModal = Array.from(
|
||||
document.querySelectorAll<HTMLElement>('.arco-modal'),
|
||||
).find((el) => el.offsetParent !== null)
|
||||
openModal
|
||||
?.querySelector<HTMLElement>('.arco-modal-close-btn')
|
||||
?.click()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user