6ee4087a29
- Centralize `useAgents` and `useModels` state within the Nuxt app context to prevent data leaks and improve initialization. - Migrate virtualization from `vue-virtual-scroller` to `@tanstack/vue-virtual` with new `RowVirtualizerFixed` and `RowVirtualizerDynamic` components. - Upgrade Nuxt to v4.3.1 and remove `@vue-macros/nuxt`. - Replace `big.js` with an optimized custom `lshDecimal` string manipulation logic for pricing calculations in the provider API. - Implement automatic focus redirection in `ChatInput` to capture standard keyboard input. - Refactor Sidenav and Settings components to utilize virtualization for long lists (topics, agents, models). - Enhance theme colors and mobile experience. More work to come on both of these.
179 lines
5.6 KiB
Vue
179 lines
5.6 KiB
Vue
<script setup lang="ts">
|
|
import { assert } from '~~/utils/assert';
|
|
import type { Agent } from '~/composables/useAgents';
|
|
|
|
const triplit = useTriplitClient();
|
|
|
|
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="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>
|