Files
zoeissleeping 03a0c3587b fix: improve chat flow and cleanup
Awaits sendMessage before navigating to prevent race conditions. Passes
agentId to startGeneration for correct agent resolution. Adds topic
deletion via Ctrl+Alt+Backspace shortcut. Adds date/time prefix to user
messages in LLM context. Fixes reasoning-container typo.
2026-04-27 12:06:46 -05:00

171 lines
5.5 KiB
Vue

<script setup lang="ts">
import type { BaseMessage } from '~/composables/useChat';
const { open: sidebarOpen, openSidebar } = useSidebar();
const { agents, createAgent, createTopic } = await useAgents();
const { providers } = await useModels();
const inputValue = ref<BaseMessage>({ content: '', fileIds: [] });
const taglines = {
morning: [
'crush your goals before breakfast',
'turn your productivity to 11',
'start your day like a boss',
'make it happen before noon',
'rise and grind, repeat',
'morning MVP, all day',
'fuel your fire early',
'own the first half of your day',
],
afternoon: [
'afternoon focus session',
'making afternoon moves',
'steady progress continues',
'keeping the flow going',
'afternoon productivity boost',
'momentum is building',
'making it happen today',
'afternoon hustle mode',
],
evening: [
'finish strong today',
'end on a high note',
'wrap it up like a pro',
'leave it all on the field',
"tomorrow's success starts tonight",
'last call for wins',
'close it out like a champion',
'seal the deal before bed',
],
};
const animatedText = ref('');
const currentTaglineIndex = ref(0);
const isDeleting = ref(false);
let typeWriterInterval: NodeJS.Timeout | null = null;
const typeWriter = (time: 'morning' | 'afternoon' | 'evening') => {
const currentTaglines = taglines[time];
const currentText = currentTaglines[currentTaglineIndex.value]!;
if (!isDeleting.value) {
animatedText.value = currentText.substring(0, animatedText.value.length + 1);
if (animatedText.value === currentText) {
isDeleting.value = true;
typeWriterInterval = setTimeout(() => typeWriter(time), 2000);
return;
}
} else {
animatedText.value = currentText.substring(0, animatedText.value.length - 1);
if (animatedText.value === '') {
isDeleting.value = false;
currentTaglineIndex.value = (currentTaglineIndex.value + 1) % currentTaglines.length;
}
}
const baseDeleteSpeed = 80;
const speed = isDeleting.value
? baseDeleteSpeed * (0.5 + 0.25 * (animatedText.value.length / currentText.length))
: 100;
typeWriterInterval = setTimeout(() => typeWriter(time), speed);
};
const agent = computed(() => {
return agents.value?.[0] ?? null;
});
const { autoRename: autoRenameTopic } = useTopic();
const handleChatSubmit = async (message: BaseMessage, model: ModelWithProvider | null) => {
if (!model) {
console.error('No model selected');
return;
}
if (!message.content) return;
let targetAgent = agents.value?.[0] ?? null;
if (!targetAgent) {
targetAgent = await createAgent(false);
if (!targetAgent) {
console.error('Failed to create agent');
return;
}
}
const topic = await createTopic(targetAgent.id);
if (!topic) {
console.error('Failed to create topic');
return;
}
const { sendMessage, startGeneration } = await useChat(topic.id, false);
await sendMessage(message).then(async res => {
if (res.ok === false) {
console.error('Failed to send message:', res.error);
await navigateTo('/');
nextTick(() => {
inputValue.value = message;
});
}
});
await navigateTo(`/agent/${targetAgent.id}/topic/${topic.id}`);
autoRenameTopic(topic.id);
return startGeneration(model, undefined, targetAgent.id);
};
onMounted(() => {
let time: 'morning' | 'afternoon' | 'evening' = 'morning';
const now = new Date();
const hours = now.getHours();
if (hours < 12) {
time = 'morning';
} else if (hours < 22) {
time = 'afternoon';
} else {
time = 'evening';
}
// randomly select a tagline
currentTaglineIndex.value = Math.floor(Math.random() * taglines[time].length);
typeWriter(time);
});
onUnmounted(() => {
if (typeWriterInterval !== null) {
clearTimeout(typeWriterInterval);
}
})
</script>
<template>
<div class="h-full w-full flex flex-col">
<div class="h-14 flex items-center justify-between px-4">
<div class="flex items-center gap-2">
<button v-if="!sidebarOpen" @click="openSidebar"
class="flex p-2 rounded-lg text-[var(--text-primary)] bg-transparent @hover:bg-[var(--color-hover)] transition-colors">
<span class="i-mynaui-panel-left-open text-5"></span>
</button>
</div>
</div>
<div class="flex flex-col items-center pt-12 px-4 h-full gap-12">
<h1 class="text-center text-3xl font-semibold">{{ animatedText }}<span
class="animate-blink inline-block w-4 h-[0.9em] bg-current align-middle ml-0.5 select-none">&nbsp;</span>
</h1>
<div class="max-w-4xl h-full w-full">
<!-- TODO: view transitions have caused me issues with the page flashing with no content (so just a black or white screen depending on the theme) so I have disabled them for now. -->
<ChatInput v-model="inputValue" class="[view-transition-name:chat-prompt] duration-150 ease-in-out"
:agent="agent" :providers="providers" @submit="handleChatSubmit" />
</div>
</div>
</div>
</template>