fix: remove processing indicators, unblock input during agent activity
This commit is contained in:
@@ -380,26 +380,34 @@ defineExpose({ selectMode, toggleSelectMode, allCollapsed, collapseAllExceptLast
|
||||
// These skip the bounce animation and get a smooth transition instead
|
||||
const resolvedUuids = ref(new Set<string>())
|
||||
|
||||
// Force scroll to the absolute bottom of the container
|
||||
function scrollToBottom() {
|
||||
if (!scrollContainer.value) return
|
||||
const el = scrollContainer.value
|
||||
el.scrollTop = el.scrollHeight
|
||||
// Second pass: content-visibility: auto can cause reflow after initial paint,
|
||||
// so re-scroll after the browser finishes layout
|
||||
requestAnimationFrame(() => {
|
||||
el.scrollTop = el.scrollHeight
|
||||
})
|
||||
}
|
||||
|
||||
// Scroll to bottom when a new transcript is loaded
|
||||
watch(
|
||||
() => props.conversation.sessionId,
|
||||
async () => {
|
||||
await nextTick()
|
||||
if (scrollContainer.value) {
|
||||
scrollContainer.value.scrollTop = scrollContainer.value.scrollHeight
|
||||
}
|
||||
scrollToBottom()
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
// Auto-scroll to bottom when messages change
|
||||
// Auto-scroll to bottom when messages change (every WS update)
|
||||
watch(
|
||||
() => props.conversation.messages.length,
|
||||
async () => {
|
||||
await nextTick()
|
||||
if (scrollContainer.value) {
|
||||
scrollContainer.value.scrollTop = scrollContainer.value.scrollHeight
|
||||
}
|
||||
scrollToBottom()
|
||||
}
|
||||
)
|
||||
|
||||
@@ -674,7 +682,6 @@ function formatDuration(start: string, end: string): string {
|
||||
/>
|
||||
|
||||
<UserInput
|
||||
:processing="props.processing"
|
||||
:terminal-ready="props.terminalReady"
|
||||
:voice-transcript="voiceTranscript"
|
||||
:is-recording="isRecording"
|
||||
@@ -999,6 +1006,8 @@ function formatDuration(start: string, end: string): string {
|
||||
align-items: flex-start;
|
||||
gap: 0.4rem;
|
||||
position: relative;
|
||||
content-visibility: auto;
|
||||
contain-intrinsic-size: auto 120px;
|
||||
}
|
||||
|
||||
.message-wrapper.selected {
|
||||
|
||||
@@ -3,7 +3,6 @@ import { ref, computed, watch, nextTick } from 'vue'
|
||||
import VoiceMicButton from './VoiceMicButton.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
processing?: boolean
|
||||
terminalReady?: boolean | null // null = no terminal, false = starting, true = ready
|
||||
voiceTranscript?: string
|
||||
isRecording?: boolean
|
||||
@@ -25,10 +24,9 @@ const emit = defineEmits<{
|
||||
|
||||
const input = ref('')
|
||||
|
||||
// terminalReady: null = no terminal (show "no terminal"), false = starting, true = ready
|
||||
const terminalStarting = computed(() => props.terminalReady === false) // explicitly false, not null
|
||||
// terminalReady: null = no terminal, false = starting, true = ready
|
||||
const noTerminal = computed(() => props.terminalReady === null)
|
||||
const canSend = computed(() => props.terminalReady === true && !props.processing)
|
||||
const canSend = computed(() => props.terminalReady === true)
|
||||
const isDisabled = computed(() => !input.value.trim() || !canSend.value)
|
||||
|
||||
function handleSend() {
|
||||
@@ -66,24 +64,12 @@ watch(() => props.voiceTranscript, (newText) => {
|
||||
|
||||
<template>
|
||||
<div class="user-input">
|
||||
<div v-if="terminalStarting" class="processing-bar starting">
|
||||
<span class="processing-dots">
|
||||
<span class="dot"></span><span class="dot"></span><span class="dot"></span>
|
||||
</span>
|
||||
<span>Starting terminal...</span>
|
||||
</div>
|
||||
<div v-else-if="processing" class="processing-bar">
|
||||
<span class="processing-dots">
|
||||
<span class="dot"></span><span class="dot"></span><span class="dot"></span>
|
||||
</span>
|
||||
<span>Agent is processing...</span>
|
||||
</div>
|
||||
<div class="input-container" :class="{ disabled: !canSend }">
|
||||
<textarea
|
||||
v-model="input"
|
||||
class="input-field"
|
||||
:style="{ maxHeight: maxH }"
|
||||
:placeholder="terminalStarting ? 'Starting terminal...' : noTerminal ? 'No terminal — use + to create session' : processing ? 'Wait for agent to finish...' : 'Continue this conversation...'"
|
||||
:placeholder="noTerminal ? 'No terminal — use + to create session' : 'Continue this conversation...'"
|
||||
rows="1"
|
||||
:disabled="!canSend"
|
||||
@keydown="handleKeydown"
|
||||
@@ -120,40 +106,6 @@ watch(() => props.voiceTranscript, (newText) => {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.processing-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
padding: 0 0.25rem 0.35rem;
|
||||
font-size: 10px;
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.processing-bar.starting {
|
||||
color: var(--text-muted, #888);
|
||||
}
|
||||
|
||||
.processing-dots {
|
||||
display: flex;
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
.processing-dots .dot {
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
border-radius: 50%;
|
||||
background: var(--accent);
|
||||
animation: pulse-dot 1.2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.processing-dots .dot:nth-child(2) { animation-delay: 0.15s; }
|
||||
.processing-dots .dot:nth-child(3) { animation-delay: 0.3s; }
|
||||
|
||||
@keyframes pulse-dot {
|
||||
0%, 80%, 100% { opacity: 0.3; transform: scale(0.8); }
|
||||
40% { opacity: 1; transform: scale(1); }
|
||||
}
|
||||
|
||||
.input-container {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
|
||||
Reference in New Issue
Block a user