fix(frontend): smooth initial conversation rendering

This commit is contained in:
JOJO 2026-06-28 10:57:54 +08:00
parent 2b2ff44fcd
commit dd5e94b294
7 changed files with 70 additions and 5 deletions

View File

@ -235,6 +235,7 @@
:format-search-topic="formatSearchTopic"
:format-search-time="formatSearchTime"
:format-search-domains="formatSearchDomains"
:history-loading="historyLoading"
@stick-state-change="handleStickStateChange"
@user-scroll-intent="handleUserScrollIntent"
/>

View File

@ -199,7 +199,7 @@ export const historyMethods = {
// 滚动到底部
this.$nextTick(() => {
this.scrollToBottom();
this.scrollHistoryToBottomInstant();
});
debugLog('历史对话内容显示完成');
@ -532,7 +532,7 @@ export const historyMethods = {
// 确保滚动到底部
this.$nextTick(() => {
this.scrollToBottom();
this.scrollHistoryToBottomInstant();
setTimeout(() => {
const blockCount =
this.$el && this.$el.querySelectorAll

View File

@ -148,6 +148,26 @@ export const scrollMethods = {
80
);
},
scrollHistoryToBottomInstant() {
this._escapedByUserScroll = false;
const chatArea = this.getChatAreaController();
if (chatArea && typeof chatArea.stopStickScroll === 'function') {
chatArea.stopStickScroll();
}
const area = this.getMessagesAreaElement();
if (!area) {
return;
}
const jump = () => {
area.scrollTop = area.scrollHeight;
};
jump();
requestAnimationFrame(() => {
jump();
requestAnimationFrame(jump);
});
this.chatSetScrollState({ userScrolling: false });
},
scrollToBottom() {
uiBounceTrace(
'ui.scrollToBottom:called',

View File

@ -234,7 +234,6 @@ export const socketMethods = {
throw new Error(`状态接口请求失败: ${statusResponse.status}`);
}
const statusData = await statusResponse.json();
this.isConnected = true;
this.socket = null;
this.projectPath = statusData.project_path || '';
this.agentVersion = statusData.version || this.agentVersion;
@ -296,6 +295,7 @@ export const socketMethods = {
this.thinkingMode = false;
}
}
this.isConnected = true;
const focusPromise = this.focusFetchFiles();
let treePromise: Promise<any> | null = null;

View File

@ -24,7 +24,8 @@
class="message-text user-bubble-text"
:class="{
'is-collapsed': isUserBubbleCollapsed(msg, index),
'is-expandable': isUserBubbleExpandable(msg, index)
'is-expandable': isUserBubbleExpandable(msg, index),
'is-history-loading': props.historyLoading || userBubbleTransitionSuppressed
}"
>
<div
@ -666,6 +667,7 @@ const props = defineProps<{
formatSearchTopic: (filters: Record<string, any>) => string;
formatSearchTime: (filters: Record<string, any>) => string;
formatSearchDomains: (filters: Record<string, any>) => string;
historyLoading?: boolean;
}>();
const emit = defineEmits<{
(
@ -715,8 +717,10 @@ const USER_BUBBLE_FOLD_LINES = 10;
const userBubbleRefs = new Map<string, HTMLElement>();
const userBubbleFoldStates = ref<Record<string, UserBubbleFoldState>>({});
const copiedBubbleKeys = ref<Set<string>>(new Set());
const userBubbleTransitionSuppressed = ref(false);
let userBubbleResizeObserver: ResizeObserver | null = null;
let copiedBubbleTimeouts = new Map<string, number>();
let userBubbleTransitionSeq = 0;
const getUserBubbleKey = (msg: any, index: number) => msg?.id || `user-bubble-${index}`;
@ -867,6 +871,17 @@ const observeUserBubbles = () => {
});
};
const releaseUserBubbleTransitionAfterLayout = () => {
const seq = ++userBubbleTransitionSeq;
requestAnimationFrame(() => {
requestAnimationFrame(() => {
if (seq === userBubbleTransitionSeq) {
userBubbleTransitionSuppressed.value = false;
}
});
});
};
const isSystemAutoUserMessage = (message: any) => {
if (!message || message.role !== 'user') {
return false;
@ -1807,12 +1822,14 @@ function compactBriefLabel(msg: any): string {
watch(
() => filteredMessages.value.map((m) => m?.id ?? m?.content?.slice(0, 80)),
() => {
userBubbleTransitionSuppressed.value = true;
//
preMeasureUserBubbles();
nextTick(() => {
requestAnimationFrame(() => {
measureUserBubbles();
observeUserBubbles();
releaseUserBubbleTransitionAfterLayout();
});
});
},

View File

@ -98,6 +98,7 @@ const DEFAULT_DEEP_COMPRESS_TRIGGER_TOKENS = 150000;
const RUN_MODE_OPTIONS: RunMode[] = ['fast', 'thinking', 'deep'];
const EXPERIMENT_STORAGE_KEY = 'agents_personalization_experiments';
const THEME_STORAGE_KEY = 'agents_ui_theme';
const STACKED_HIDE_BORDERS_STORAGE_KEY = 'agents_stacked_hide_borders';
const loadCachedTheme = (): PersonalForm['theme'] => {
if (typeof window === 'undefined' || !window.localStorage) {
@ -107,6 +108,24 @@ const loadCachedTheme = (): PersonalForm['theme'] => {
return saved === 'light' || saved === 'dark' || saved === 'classic' ? saved : 'classic';
};
const loadCachedStackedHideBorders = (): boolean => {
if (typeof window === 'undefined' || !window.localStorage) {
return false;
}
return window.localStorage.getItem(STACKED_HIDE_BORDERS_STORAGE_KEY) === 'true';
};
const persistStackedHideBorders = (value: boolean) => {
if (typeof window === 'undefined' || !window.localStorage) {
return;
}
try {
window.localStorage.setItem(STACKED_HIDE_BORDERS_STORAGE_KEY, value ? 'true' : 'false');
} catch (error) {
console.warn('写入堆叠块边线设置失败:', error);
}
};
const defaultForm = (): PersonalForm => ({
enabled: false,
communication_style: 'default',
@ -125,7 +144,7 @@ const defaultForm = (): PersonalForm => ({
compact_message_display: 'full',
show_git_status_bar: true,
auto_open_terminal_panel: true,
stacked_hide_borders: false,
stacked_hide_borders: loadCachedStackedHideBorders(),
enhanced_tool_display_categories: [],
enabled_skills: [],
self_identify: '',
@ -398,6 +417,7 @@ export const usePersonalizationStore = defineStore('personalization', {
this.applyTheme(this.form.theme);
}
persistDefaultHideWorkspace(this.form.default_hide_workspace);
persistStackedHideBorders(this.form.stacked_hide_borders);
if (this.form.default_hide_workspace) {
useUiStore().setWorkspaceCollapsed(true);
}
@ -593,6 +613,9 @@ export const usePersonalizationStore = defineStore('personalization', {
...this.form,
[payload.key]: payload.value
};
if (payload.key === 'stacked_hide_borders') {
persistStackedHideBorders(!!payload.value);
}
this.clearFeedback();
this.scheduleAutoSave();
},

View File

@ -585,6 +585,10 @@
word-break: break-all;
}
.user-message .message-text.user-bubble-text.is-history-loading .bubble-text {
transition: none !important;
}
.user-message .message-text.user-bubble-text.is-expandable .bubble-text:not(.is-expanded) {
max-height: var(--bubble-fold-height, 240px);
}