Files
veridian/app/pages/agent/[id]/index.vue
T
zoeissleeping 6ee4087a29 ♻️ refactor: optimize state management, switch to @tanstack/vue-virtual, and improve performance
- 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.
2026-02-23 15:56:10 +00:00

106 lines
3.4 KiB
Vue

<script setup lang="ts">
import type { ModelWithProvider } from '~/composables/useModels';
const triplit = useTriplitClient();
const route = useRoute();
const inputValue = ref('');
const pendingMessage = ref<Message | null>(null);
const { createTopic, sendMessage, autoRename } = useChat(route.params.id as string);
const { getAgent } = useAgents();
const { providers } = useModels();
const agent = getAgent(route.params.id as string);
if (!agent.value) navigateTo('/');
const handleSubmit = async (message: string, model: ModelWithProvider | null) => {
if (!model) {
console.error('No model selected');
return;
}
const user = useAuth().user;
if (!user) {
console.error('No user');
return;
}
pendingMessage.value = {
id: '',
userId: user.value!.id,
topicId: null,
content: message,
role: 'user',
parts: [],
generation: null,
parentMessageId: null,
children: [],
generationId: null,
focusedIndex: null,
deleted: false,
createdAt: new Date(),
}
const topic = await createTopic();
if (!topic) throw new Error('Failed to create topic');
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;
}
});
await navigateTo(`/agent/${route.params.id}/topic/${topic.id}`);
return sendMessage(message, topic, [], agent.value!, model.provider, model).then(async res => {
if (res.ok === false) {
console.error('Failed to send message:', res.error);
pendingMessage.value = null;
await navigateTo(`/agent/${route.params.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();
});
}
}
});
};
</script>
<template>
<div
class="chat-scroll-container justify-center w-full h-full [scrollbar-width:thin] [scrollbar-color:#888_transparent] overflow-y-scroll overflow-x-hidden flex justify-center">
<div class="chatPane max-w-4xl w-full min-h-full flex flex-col px-4">
<div class="w-full px-px flex flex-col flex-grow gap-2"
:class="pendingMessage === null ? 'justify-end pb-28' : 'pb-9'">
<div v-if="pendingMessage === null">
<h1 v-if="agent" class="font-bold">{{ agent.name }}</h1>
<p class="text-[var(--text-secondary)]">Select a topic to continue or create a new one</p>
</div>
<div v-else class="opacity-70">
<Message :message="pendingMessage" />
</div>
</div>
<div class="sticky bottom-0 z-10 bg-[var(--bg-surface)] pb-4 w-full rounded-t-2xl">
<ChatInput v-model="inputValue" class="[view-transition-name:chat-prompt] duration-150 ease-in-out"
:agent="agent" :providers="providers?.filter(p => p.enabled)" @submit="handleSubmit" />
</div>
</div>
</div>
</template>