fix: unify collapse animation and scroll lock reset
This commit is contained in:
parent
95285747c0
commit
d34fbe963a
@ -57,6 +57,7 @@ import {
|
|||||||
scrollToBottom as scrollToBottomHelper,
|
scrollToBottom as scrollToBottomHelper,
|
||||||
conditionalScrollToBottom as conditionalScrollToBottomHelper,
|
conditionalScrollToBottom as conditionalScrollToBottomHelper,
|
||||||
toggleScrollLock as toggleScrollLockHelper,
|
toggleScrollLock as toggleScrollLockHelper,
|
||||||
|
normalizeScrollLock,
|
||||||
scrollThinkingToBottom as scrollThinkingToBottomHelper
|
scrollThinkingToBottom as scrollThinkingToBottomHelper
|
||||||
} from './composables/useScrollControl';
|
} from './composables/useScrollControl';
|
||||||
import {
|
import {
|
||||||
@ -308,6 +309,8 @@ const appOptions = {
|
|||||||
await socketPromise;
|
await socketPromise;
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.ensureScrollListener();
|
this.ensureScrollListener();
|
||||||
|
// 刷新后若无输出,自动解锁滚动锁定
|
||||||
|
normalizeScrollLock(this);
|
||||||
});
|
});
|
||||||
setupShowImageObserver();
|
setupShowImageObserver();
|
||||||
|
|
||||||
|
|||||||
@ -79,7 +79,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<span class="status-text">{{ group.action.streaming ? '正在思考...' : '思考过程' }}</span>
|
<span class="status-text">{{ group.action.streaming ? '正在思考...' : '思考过程' }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="collapsible-content">
|
<div
|
||||||
|
class="collapsible-content"
|
||||||
|
:ref="el => registerCollapseContent(group.action.blockId || `${index}-thinking-${group.actionIndex}`, el)"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
class="content-inner thinking-content"
|
class="content-inner thinking-content"
|
||||||
:ref="el => registerThinkingRef(group.action.blockId || `${index}-thinking-${group.actionIndex}`, el)"
|
:ref="el => registerThinkingRef(group.action.blockId || `${index}-thinking-${group.actionIndex}`, el)"
|
||||||
@ -208,7 +211,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<span class="status-text">{{ action.streaming ? '正在思考...' : '思考过程' }}</span>
|
<span class="status-text">{{ action.streaming ? '正在思考...' : '思考过程' }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="collapsible-content">
|
<div
|
||||||
|
class="collapsible-content"
|
||||||
|
:ref="el => registerCollapseContent(action.blockId || `${index}-thinking-${actionIndex}`, el)"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
class="content-inner thinking-content"
|
class="content-inner thinking-content"
|
||||||
:ref="el => registerThinkingRef(action.blockId || `${index}-thinking-${actionIndex}`, el)"
|
:ref="el => registerThinkingRef(action.blockId || `${index}-thinking-${actionIndex}`, el)"
|
||||||
@ -319,7 +325,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<span class="status-text">系统消息</span>
|
<span class="status-text">系统消息</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="collapsible-content">
|
<div
|
||||||
|
class="collapsible-content"
|
||||||
|
:ref="el => registerCollapseContent(`system-${index}`, el)"
|
||||||
|
>
|
||||||
<div class="content-inner">
|
<div class="content-inner">
|
||||||
{{ msg.content }}
|
{{ msg.content }}
|
||||||
</div>
|
</div>
|
||||||
@ -359,6 +368,17 @@ const stackedBlocksEnabled = computed(() => personalization.experiments.stackedB
|
|||||||
const DEFAULT_GENERATING_TEXT = '生成中…';
|
const DEFAULT_GENERATING_TEXT = '生成中…';
|
||||||
const rootEl = ref<HTMLElement | null>(null);
|
const rootEl = ref<HTMLElement | null>(null);
|
||||||
const thinkingRefs = new Map<string, HTMLElement | null>();
|
const thinkingRefs = new Map<string, HTMLElement | null>();
|
||||||
|
const registerCollapseContent = (key: string, el: Element | null) => {
|
||||||
|
if (!(el instanceof HTMLElement)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
const h = el.scrollHeight || el.offsetHeight || 0;
|
||||||
|
if (h > 0) {
|
||||||
|
el.style.setProperty('--collapse-max', `${h}px`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
function registerThinkingRef(key: string, el: Element | null) {
|
function registerThinkingRef(key: string, el: Element | null) {
|
||||||
if (el instanceof HTMLElement) {
|
if (el instanceof HTMLElement) {
|
||||||
|
|||||||
@ -20,7 +20,10 @@
|
|||||||
<span class="status-text">{{ getToolStatusText(action.tool) }}</span>
|
<span class="status-text">{{ getToolStatusText(action.tool) }}</span>
|
||||||
<span class="tool-desc">{{ getToolDescription(action.tool) }}</span>
|
<span class="tool-desc">{{ getToolDescription(action.tool) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="collapsible-content">
|
<div
|
||||||
|
class="collapsible-content"
|
||||||
|
:ref="el => registerCollapseContent && registerCollapseContent(collapseKey || action.tool.id || action.id || 'tool', el)"
|
||||||
|
>
|
||||||
<div class="content-inner">
|
<div class="content-inner">
|
||||||
<div v-if="action.tool.name === 'web_search' && action.tool.result">
|
<div v-if="action.tool.name === 'web_search' && action.tool.result">
|
||||||
<div class="search-meta">
|
<div class="search-meta">
|
||||||
@ -75,6 +78,8 @@ 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;
|
||||||
streamingMessage: boolean;
|
streamingMessage: boolean;
|
||||||
|
registerCollapseContent?: (key: string, el: Element | null) => void;
|
||||||
|
collapseKey?: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
defineEmits<{ (event: 'toggle'): void }>();
|
defineEmits<{ (event: 'toggle'): void }>();
|
||||||
|
|||||||
@ -10,6 +10,8 @@ type ScrollContext = {
|
|||||||
autoScrollEnabled?: boolean;
|
autoScrollEnabled?: boolean;
|
||||||
userScrolling?: boolean;
|
userScrolling?: boolean;
|
||||||
isOutputActive?: () => boolean;
|
isOutputActive?: () => boolean;
|
||||||
|
streamingMessage?: boolean;
|
||||||
|
hasPendingToolActions?: () => boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ScrollOptions = {
|
type ScrollOptions = {
|
||||||
@ -106,6 +108,21 @@ export function toggleScrollLock(ctx: ScrollContext) {
|
|||||||
return nextState;
|
return nextState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在页面初始化/刷新后同步滚动锁定的默认状态:
|
||||||
|
* 仅当存在输出(流式中或有未完成工具)时才保持锁定,否则自动解锁。
|
||||||
|
*/
|
||||||
|
export function normalizeScrollLock(ctx: ScrollContext) {
|
||||||
|
const active =
|
||||||
|
(typeof ctx.isOutputActive === 'function' ? ctx.isOutputActive() : false) ||
|
||||||
|
!!ctx.streamingMessage ||
|
||||||
|
(typeof ctx.hasPendingToolActions === 'function' ? ctx.hasPendingToolActions() : false);
|
||||||
|
|
||||||
|
if (!active && ctx.autoScrollEnabled) {
|
||||||
|
ctx.chatSetScrollState?.({ autoScrollEnabled: false, userScrolling: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function scrollThinkingToBottom(ctx: ScrollContext, blockId: string) {
|
export function scrollThinkingToBottom(ctx: ScrollContext, blockId: string) {
|
||||||
if (!blockId) {
|
if (!blockId) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -266,11 +266,12 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition:
|
transition:
|
||||||
max-height 0.26s cubic-bezier(0.4, 0, 0.2, 1),
|
max-height 260ms cubic-bezier(0.4, 0, 0.2, 1),
|
||||||
opacity 0.26s cubic-bezier(0.4, 0, 0.2, 1);
|
opacity 220ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
/* 始终隐藏滚动条,避免堆叠模式展开时闪现 */
|
/* 始终隐藏滚动条,避免堆叠模式展开时闪现 */
|
||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
-ms-overflow-style: none;
|
-ms-overflow-style: none;
|
||||||
|
will-change: max-height, opacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
.collapsible-content::-webkit-scrollbar {
|
.collapsible-content::-webkit-scrollbar {
|
||||||
@ -278,7 +279,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.collapsible-block.expanded .collapsible-content {
|
.collapsible-block.expanded .collapsible-content {
|
||||||
max-height: 600px;
|
max-height: var(--collapse-max, 600px);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
@ -439,8 +440,8 @@
|
|||||||
|
|
||||||
.stacked-block .collapsible-content {
|
.stacked-block .collapsible-content {
|
||||||
transition:
|
transition:
|
||||||
max-height 280ms cubic-bezier(0.25, 0.9, 0.3, 1),
|
max-height 260ms cubic-bezier(0.4, 0, 0.2, 1),
|
||||||
opacity 220ms ease;
|
opacity 220ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress-indicator {
|
.progress-indicator {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user