ab57af1f23
This commit moves the actions bar out of the default template and into the individual pages so that they can display their titles at the top. this commit also has pages set the meta title so tabs are labeled nicely.
191 lines
6.2 KiB
Vue
191 lines
6.2 KiB
Vue
<script setup lang="ts">
|
|
import { assert } from '~~/utils/assert';
|
|
import type { Agent } from '~/composables/useAgents';
|
|
|
|
const triplit = useTriplitClient();
|
|
|
|
const { open: sidebarOpen, openSidebar } = useSidebar();
|
|
const { agents, createAgent } = useAgents();
|
|
const { providers, getFirstAvailableModel, allModels } = useModels();
|
|
|
|
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 handleChatSubmit = async (message: string, model: ModelWithProvider | null) => {
|
|
console.log('Message submitted:', message, agents);
|
|
let agent: Agent | null = agents.value?.[0] ?? null;
|
|
if (!agent) {
|
|
const triplit = useTriplitClient();
|
|
assert('flush' in triplit);
|
|
|
|
agent = await createAgent();
|
|
await triplit.flush();
|
|
}
|
|
|
|
if (!agent) throw new Error('Failed to find agent');
|
|
|
|
if (!model) {
|
|
if (agent.defaultModelId) {
|
|
model = allModels.value.find(m => m.id === agent.defaultModelId) ?? null;
|
|
} else {
|
|
model = getFirstAvailableModel();
|
|
}
|
|
}
|
|
|
|
if (!model) {
|
|
console.error('No model selected');
|
|
return;
|
|
}
|
|
|
|
const { createTopic, autoRename, sendMessage } = useChat(agent.id);
|
|
|
|
const topic = await createTopic();
|
|
if (!topic) throw new Error('Failed to create topic');
|
|
|
|
await navigateTo(`/agent/${agent.id}/topic/${topic.id}`);
|
|
|
|
autoRename(topic.id, message).then(async res => {
|
|
if (res.ok === false) {
|
|
console.error('Failed to auto-rename:', res.error);
|
|
await triplit.update('topics', topic.id, {
|
|
renaming: false,
|
|
});
|
|
|
|
return;
|
|
}
|
|
});
|
|
|
|
return sendMessage(message, topic, [], agent, model.provider, model).then(async res => {
|
|
if (res.ok === false) {
|
|
console.error('Failed to send message:', res.error);
|
|
await navigateTo(`/agent/${agent.id}`);
|
|
await triplit.delete('topics', topic.id);
|
|
|
|
const chatInput = document.getElementById('chat') as HTMLInputElement;
|
|
|
|
if (chatInput) {
|
|
chatInput.value = message;
|
|
chatInput.dispatchEvent(new Event('input'));
|
|
nextTick(() => {
|
|
chatInput.focus();
|
|
});
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
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">
|
|
<Icon class="text-5" name="mynaui:panel-left-open" />
|
|
</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"> </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 class="[view-transition-name:chat-prompt] duration-150 ease-in-out" :agent="agent"
|
|
:providers="providers" @submit="handleChatSubmit" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|