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-topic="formatSearchTopic"
|
||||||
:format-search-time="formatSearchTime"
|
:format-search-time="formatSearchTime"
|
||||||
:format-search-domains="formatSearchDomains"
|
:format-search-domains="formatSearchDomains"
|
||||||
|
:history-loading="historyLoading"
|
||||||
@stick-state-change="handleStickStateChange"
|
@stick-state-change="handleStickStateChange"
|
||||||
@user-scroll-intent="handleUserScrollIntent"
|
@user-scroll-intent="handleUserScrollIntent"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -199,7 +199,7 @@ export const historyMethods = {
|
|||||||
|
|
||||||
// 滚动到底部
|
// 滚动到底部
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.scrollToBottom();
|
this.scrollHistoryToBottomInstant();
|
||||||
});
|
});
|
||||||
|
|
||||||
debugLog('历史对话内容显示完成');
|
debugLog('历史对话内容显示完成');
|
||||||
@ -532,7 +532,7 @@ export const historyMethods = {
|
|||||||
|
|
||||||
// 确保滚动到底部
|
// 确保滚动到底部
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.scrollToBottom();
|
this.scrollHistoryToBottomInstant();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const blockCount =
|
const blockCount =
|
||||||
this.$el && this.$el.querySelectorAll
|
this.$el && this.$el.querySelectorAll
|
||||||
|
|||||||
@ -148,6 +148,26 @@ export const scrollMethods = {
|
|||||||
80
|
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() {
|
scrollToBottom() {
|
||||||
uiBounceTrace(
|
uiBounceTrace(
|
||||||
'ui.scrollToBottom:called',
|
'ui.scrollToBottom:called',
|
||||||
|
|||||||
@ -234,7 +234,6 @@ export const socketMethods = {
|
|||||||
throw new Error(`状态接口请求失败: ${statusResponse.status}`);
|
throw new Error(`状态接口请求失败: ${statusResponse.status}`);
|
||||||
}
|
}
|
||||||
const statusData = await statusResponse.json();
|
const statusData = await statusResponse.json();
|
||||||
this.isConnected = true;
|
|
||||||
this.socket = null;
|
this.socket = null;
|
||||||
this.projectPath = statusData.project_path || '';
|
this.projectPath = statusData.project_path || '';
|
||||||
this.agentVersion = statusData.version || this.agentVersion;
|
this.agentVersion = statusData.version || this.agentVersion;
|
||||||
@ -296,6 +295,7 @@ export const socketMethods = {
|
|||||||
this.thinkingMode = false;
|
this.thinkingMode = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.isConnected = true;
|
||||||
|
|
||||||
const focusPromise = this.focusFetchFiles();
|
const focusPromise = this.focusFetchFiles();
|
||||||
let treePromise: Promise<any> | null = null;
|
let treePromise: Promise<any> | null = null;
|
||||||
|
|||||||
@ -24,7 +24,8 @@
|
|||||||
class="message-text user-bubble-text"
|
class="message-text user-bubble-text"
|
||||||
:class="{
|
:class="{
|
||||||
'is-collapsed': isUserBubbleCollapsed(msg, index),
|
'is-collapsed': isUserBubbleCollapsed(msg, index),
|
||||||
'is-expandable': isUserBubbleExpandable(msg, index)
|
'is-expandable': isUserBubbleExpandable(msg, index),
|
||||||
|
'is-history-loading': props.historyLoading || userBubbleTransitionSuppressed
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@ -666,6 +667,7 @@ const props = defineProps<{
|
|||||||
formatSearchTopic: (filters: Record<string, any>) => string;
|
formatSearchTopic: (filters: Record<string, any>) => string;
|
||||||
formatSearchTime: (filters: Record<string, any>) => string;
|
formatSearchTime: (filters: Record<string, any>) => string;
|
||||||
formatSearchDomains: (filters: Record<string, any>) => string;
|
formatSearchDomains: (filters: Record<string, any>) => string;
|
||||||
|
historyLoading?: boolean;
|
||||||
}>();
|
}>();
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(
|
(
|
||||||
@ -715,8 +717,10 @@ const USER_BUBBLE_FOLD_LINES = 10;
|
|||||||
const userBubbleRefs = new Map<string, HTMLElement>();
|
const userBubbleRefs = new Map<string, HTMLElement>();
|
||||||
const userBubbleFoldStates = ref<Record<string, UserBubbleFoldState>>({});
|
const userBubbleFoldStates = ref<Record<string, UserBubbleFoldState>>({});
|
||||||
const copiedBubbleKeys = ref<Set<string>>(new Set());
|
const copiedBubbleKeys = ref<Set<string>>(new Set());
|
||||||
|
const userBubbleTransitionSuppressed = ref(false);
|
||||||
let userBubbleResizeObserver: ResizeObserver | null = null;
|
let userBubbleResizeObserver: ResizeObserver | null = null;
|
||||||
let copiedBubbleTimeouts = new Map<string, number>();
|
let copiedBubbleTimeouts = new Map<string, number>();
|
||||||
|
let userBubbleTransitionSeq = 0;
|
||||||
|
|
||||||
const getUserBubbleKey = (msg: any, index: number) => msg?.id || `user-bubble-${index}`;
|
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) => {
|
const isSystemAutoUserMessage = (message: any) => {
|
||||||
if (!message || message.role !== 'user') {
|
if (!message || message.role !== 'user') {
|
||||||
return false;
|
return false;
|
||||||
@ -1807,12 +1822,14 @@ function compactBriefLabel(msg: any): string {
|
|||||||
watch(
|
watch(
|
||||||
() => filteredMessages.value.map((m) => m?.id ?? m?.content?.slice(0, 80)),
|
() => filteredMessages.value.map((m) => m?.id ?? m?.content?.slice(0, 80)),
|
||||||
() => {
|
() => {
|
||||||
|
userBubbleTransitionSuppressed.value = true;
|
||||||
// 先同步估算折叠状态,再异步精确测量,避免长气泡先展开后收起
|
// 先同步估算折叠状态,再异步精确测量,避免长气泡先展开后收起
|
||||||
preMeasureUserBubbles();
|
preMeasureUserBubbles();
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
measureUserBubbles();
|
measureUserBubbles();
|
||||||
observeUserBubbles();
|
observeUserBubbles();
|
||||||
|
releaseUserBubbleTransitionAfterLayout();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@ -98,6 +98,7 @@ const DEFAULT_DEEP_COMPRESS_TRIGGER_TOKENS = 150000;
|
|||||||
const RUN_MODE_OPTIONS: RunMode[] = ['fast', 'thinking', 'deep'];
|
const RUN_MODE_OPTIONS: RunMode[] = ['fast', 'thinking', 'deep'];
|
||||||
const EXPERIMENT_STORAGE_KEY = 'agents_personalization_experiments';
|
const EXPERIMENT_STORAGE_KEY = 'agents_personalization_experiments';
|
||||||
const THEME_STORAGE_KEY = 'agents_ui_theme';
|
const THEME_STORAGE_KEY = 'agents_ui_theme';
|
||||||
|
const STACKED_HIDE_BORDERS_STORAGE_KEY = 'agents_stacked_hide_borders';
|
||||||
|
|
||||||
const loadCachedTheme = (): PersonalForm['theme'] => {
|
const loadCachedTheme = (): PersonalForm['theme'] => {
|
||||||
if (typeof window === 'undefined' || !window.localStorage) {
|
if (typeof window === 'undefined' || !window.localStorage) {
|
||||||
@ -107,6 +108,24 @@ const loadCachedTheme = (): PersonalForm['theme'] => {
|
|||||||
return saved === 'light' || saved === 'dark' || saved === 'classic' ? saved : 'classic';
|
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 => ({
|
const defaultForm = (): PersonalForm => ({
|
||||||
enabled: false,
|
enabled: false,
|
||||||
communication_style: 'default',
|
communication_style: 'default',
|
||||||
@ -125,7 +144,7 @@ const defaultForm = (): PersonalForm => ({
|
|||||||
compact_message_display: 'full',
|
compact_message_display: 'full',
|
||||||
show_git_status_bar: true,
|
show_git_status_bar: true,
|
||||||
auto_open_terminal_panel: true,
|
auto_open_terminal_panel: true,
|
||||||
stacked_hide_borders: false,
|
stacked_hide_borders: loadCachedStackedHideBorders(),
|
||||||
enhanced_tool_display_categories: [],
|
enhanced_tool_display_categories: [],
|
||||||
enabled_skills: [],
|
enabled_skills: [],
|
||||||
self_identify: '',
|
self_identify: '',
|
||||||
@ -398,6 +417,7 @@ export const usePersonalizationStore = defineStore('personalization', {
|
|||||||
this.applyTheme(this.form.theme);
|
this.applyTheme(this.form.theme);
|
||||||
}
|
}
|
||||||
persistDefaultHideWorkspace(this.form.default_hide_workspace);
|
persistDefaultHideWorkspace(this.form.default_hide_workspace);
|
||||||
|
persistStackedHideBorders(this.form.stacked_hide_borders);
|
||||||
if (this.form.default_hide_workspace) {
|
if (this.form.default_hide_workspace) {
|
||||||
useUiStore().setWorkspaceCollapsed(true);
|
useUiStore().setWorkspaceCollapsed(true);
|
||||||
}
|
}
|
||||||
@ -593,6 +613,9 @@ export const usePersonalizationStore = defineStore('personalization', {
|
|||||||
...this.form,
|
...this.form,
|
||||||
[payload.key]: payload.value
|
[payload.key]: payload.value
|
||||||
};
|
};
|
||||||
|
if (payload.key === 'stacked_hide_borders') {
|
||||||
|
persistStackedHideBorders(!!payload.value);
|
||||||
|
}
|
||||||
this.clearFeedback();
|
this.clearFeedback();
|
||||||
this.scheduleAutoSave();
|
this.scheduleAutoSave();
|
||||||
},
|
},
|
||||||
|
|||||||
@ -585,6 +585,10 @@
|
|||||||
word-break: break-all;
|
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) {
|
.user-message .message-text.user-bubble-text.is-expandable .bubble-text:not(.is-expanded) {
|
||||||
max-height: var(--bubble-fold-height, 240px);
|
max-height: var(--bubble-fold-height, 240px);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user