fix(chat): place agent activity before response
This commit is contained in:
@@ -35,7 +35,7 @@ const JSON_MAX_KEYS_PER_OBJECT = 50;
|
||||
const JSON_MAX_ITEMS_PER_ARRAY = 50;
|
||||
const JSON_TRUNCATED_KEY = "__truncated__";
|
||||
|
||||
const props = defineProps<{ message: Message; highlight?: boolean; headingIdPrefix?: string; showForkAction?: boolean }>();
|
||||
const props = defineProps<{ message: Message; highlight?: boolean; headingIdPrefix?: string; showForkAction?: boolean; activityOnly?: boolean }>();
|
||||
const { t } = useI18n();
|
||||
const toast = useMessage();
|
||||
|
||||
@@ -1021,7 +1021,8 @@ onBeforeUnmount(() => {
|
||||
/>
|
||||
<span class="msg-profile-name" :title="assistantProfileName">{{ assistantProfileName }}</span>
|
||||
</div>
|
||||
<div class="msg-content" :class="message.role">
|
||||
<slot name="agent-activity" />
|
||||
<div v-if="!activityOnly" class="msg-content" :class="message.role">
|
||||
<div
|
||||
class="message-bubble"
|
||||
:class="{
|
||||
|
||||
@@ -120,7 +120,22 @@ const displayMessages = computed(() => {
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return groupRunMessages(messages, chatStore.isRunActive || !!chatStore.abortState);
|
||||
const grouped = groupRunMessages(messages, chatStore.isRunActive || !!chatStore.abortState);
|
||||
if (isThinkingIndicatorVisible.value) {
|
||||
const lastInput = grouped.findLastIndex(m => m.role === 'user' || m.role === 'command');
|
||||
const currentAssistant = grouped.slice(lastInput + 1).findLast(m => m.role === 'assistant');
|
||||
if (!currentAssistant) {
|
||||
grouped.push({ id: `run-activity-${chatStore.activeSessionId}`, role: 'assistant', content: '', timestamp: 0 });
|
||||
}
|
||||
}
|
||||
return grouped;
|
||||
});
|
||||
|
||||
const activeAssistantId = computed(() => {
|
||||
if (!isThinkingIndicatorVisible.value) return null;
|
||||
const messages = displayMessages.value;
|
||||
const lastInput = messages.findLastIndex(m => m.role === 'user' || m.role === 'command');
|
||||
return messages.slice(lastInput + 1).findLast(m => m.role === 'assistant')?.id ?? null;
|
||||
});
|
||||
|
||||
function forkDividerId(sessionId: string): string {
|
||||
@@ -519,18 +534,15 @@ defineExpose({
|
||||
:message="msg"
|
||||
:highlight="chatStore.focusMessageId === msg.id"
|
||||
:show-fork-action="canForkActiveSession && msg.id === lastForkActionMessageId"
|
||||
/>
|
||||
</template>
|
||||
<template #after>
|
||||
<Transition name="fade">
|
||||
<div v-if="isThinkingIndicatorVisible" class="streaming-indicator">
|
||||
:activity-only="msg.id === `run-activity-${chatStore.activeSessionId}`"
|
||||
>
|
||||
<template #agent-activity>
|
||||
<details v-if="msg.toolMessages?.length" class="tool-group">
|
||||
<summary>{{ t('chat.toolCallsGroup', { count: msg.toolMessages.length }) }}</summary>
|
||||
<MessageItem v-for="tool in msg.toolMessages" :key="tool.id" :message="tool" />
|
||||
</details>
|
||||
<div v-if="msg.id === activeAssistantId" class="streaming-indicator">
|
||||
<div class="thinking-status">
|
||||
<img
|
||||
src="/app-logo.png"
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
class="thinking-avatar"
|
||||
>
|
||||
<div class="thinking-status-copy">
|
||||
<span class="thinking-status-label">{{ t("chat.thinkingInProgress") }}</span>
|
||||
<span class="thinking-status-time">{{ formattedThinkingElapsed }}</span>
|
||||
@@ -696,7 +708,8 @@ defineExpose({
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</template>
|
||||
</MessageItem>
|
||||
</template>
|
||||
</VirtualMessageList>
|
||||
<button
|
||||
@@ -1475,22 +1488,8 @@ defineExpose({
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
.thinking-avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
flex-shrink: 0;
|
||||
animation: thinking-avatar-breathe 2.4s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes thinking-avatar-breathe {
|
||||
0%, 100% { transform: translateY(0) scale(1); filter: drop-shadow(0 0 2px rgba(var(--accent-primary-rgb), 0.12)); }
|
||||
50% { transform: translateY(-2px) scale(1.06); filter: drop-shadow(0 0 7px rgba(var(--accent-primary-rgb), 0.4)); }
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.thinking-status .thinking-avatar, .thinking-status .thinking-status-label { animation: none; }
|
||||
.thinking-status .thinking-status-label { animation: none; }
|
||||
}
|
||||
|
||||
.thinking-status-copy {
|
||||
|
||||
@@ -13,6 +13,7 @@ export function groupRunMessages(messages: Message[], runActive: boolean): Messa
|
||||
const first = assistants[0]
|
||||
result.push({
|
||||
...first,
|
||||
toolMessages: tools.length ? tools : undefined,
|
||||
id: assistants[assistants.length - 1].id,
|
||||
content: assistants.map(m => m.content).filter(text => text.trim()).join('\n\n'),
|
||||
reasoning: assistants.map(m => m.reasoning).filter(Boolean).join('\n\n') || undefined,
|
||||
@@ -20,7 +21,7 @@ export function groupRunMessages(messages: Message[], runActive: boolean): Messa
|
||||
isStreaming: assistants.some(m => m.isStreaming),
|
||||
})
|
||||
}
|
||||
if (tools.length) {
|
||||
if (tools.length && !assistants.length) {
|
||||
result.push({ id: `tool-group-${tools[0].id}`, role: 'system', content: '', timestamp: tools[0].timestamp, systemType: 'tool-group', toolMessages: tools })
|
||||
}
|
||||
if (completed && assistants.length) {
|
||||
|
||||
Reference in New Issue
Block a user