fix(frontend): smooth initial conversation rendering
This commit is contained in:
parent
2b2ff44fcd
commit
dd5e94b294
@ -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"
|
||||
/>
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
@ -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();
|
||||
},
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user