59bb7fbc12
This is once again a huge commit, but its mostly performance improvements along with some bug fixes and refactoring. It also includes changes to the theming systems. I'm still not 100% happy with the theming system, but its better than before. Model fetching has been dramatically improved! Nearly all the important computation and pre-processing has been moved to the server. This has also somehow fixed the way model details are loaded, which was causing many models to be missing their details despite models.dev having them. The markdown renderer has once again been changed, but I'm mostly certain that this is the last time major changes will be made to it. The renderer is not spamming components, bloating memory usage, and its not using a bug prone custom written chunking system. There's also a lot more that I haven't mentioned and honestly forgot. I need to get better commit hygiene tbh.
182 lines
5.7 KiB
Vue
182 lines
5.7 KiB
Vue
<script setup lang="ts">
|
|
import type schema from '#triplit/schema';
|
|
import type { Entity } from '@triplit/client';
|
|
import { assert } from '~~/utils/assert';
|
|
|
|
const triplit = useTriplitClient();
|
|
|
|
const { agents, unsubscribe: unsubscribeAgents, createAgent } = await useAgents();
|
|
const { providers, unsubscribe: unsubscribeModels, getFirstAvailableModel, allModels } = await 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];
|
|
});
|
|
|
|
const handleChatSubmit = async (message: string, model: ModelWithProvider | null) => {
|
|
console.log('Message submitted:', message, agents);
|
|
let agent: Entity<typeof schema, 'agents'> | 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);
|
|
}
|
|
unsubscribeAgents?.();
|
|
unsubscribeModels?.();
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<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>
|
|
</template>
|