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.
153 lines
6.2 KiB
Vue
153 lines
6.2 KiB
Vue
<script setup lang="ts">
|
|
const { agents, createAgent } = useAgents();
|
|
const agentsOpen = ref(true);
|
|
const creatingAgent = ref(false);
|
|
|
|
const { openDropdown, dropdownState, closeDropdown } = useDropdown();
|
|
|
|
const triplit = useTriplitClient();
|
|
|
|
const navRef = ref<HTMLElement | null>(null);
|
|
const toggleAgentsList = () => {
|
|
agentsOpen.value = !agentsOpen.value;
|
|
};
|
|
|
|
const newAgent = async () => {
|
|
creatingAgent.value = true;
|
|
try {
|
|
const agent = await createAgent();
|
|
return navigateTo(`/agent/${agent.id}`);
|
|
} finally {
|
|
creatingAgent.value = false;
|
|
}
|
|
};
|
|
|
|
const renameAgentId = ref<string | null>(null);
|
|
const newAgentName = ref('');
|
|
|
|
const startRename = (agentId: string, currentName: string) => {
|
|
renameAgentId.value = agentId;
|
|
newAgentName.value = currentName;
|
|
nextTick(() => {
|
|
const input = document.getElementById('agent-rename-input') as HTMLInputElement | null;
|
|
if (input) {
|
|
input.focus();
|
|
}
|
|
})
|
|
};
|
|
|
|
const saveRename = async () => {
|
|
if (renameAgentId.value && newAgentName.value.trim()) {
|
|
await triplit.update('agents', renameAgentId.value, {
|
|
name: newAgentName.value.trim()
|
|
});
|
|
}
|
|
cancelRename();
|
|
};
|
|
|
|
const cancelRename = () => {
|
|
renameAgentId.value = null;
|
|
newAgentName.value = '';
|
|
};
|
|
|
|
const deleteAgent = async (agentId: string) => {
|
|
await triplit.delete('agents', agentId);
|
|
}
|
|
|
|
const handleNavClick = (e: MouseEvent) => {
|
|
const trigger = (e.target as HTMLElement).closest('[data-action]') as HTMLElement | null;
|
|
if (!trigger) return;
|
|
|
|
const agentId = trigger.dataset.agentId || (trigger.closest('[data-agent-id]') as HTMLElement | null)?.dataset.agentId;
|
|
if (!agentId) return;
|
|
|
|
const action = trigger.dataset.action;
|
|
switch (action) {
|
|
case 'navigate':
|
|
if (e.metaKey || e.ctrlKey || e.shiftKey) return;
|
|
e.preventDefault();
|
|
return navigateTo(`/agent/${agentId}`);
|
|
|
|
case 'toggle-dropdown':
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
if (dropdownState.open) {
|
|
closeDropdown();
|
|
return;
|
|
}
|
|
|
|
openDropdown(e, () => {
|
|
const agent = agents.value.find(a => a.id === agentId);
|
|
if (agent) {
|
|
return [
|
|
{ label: 'Rename', onClick: () => startRename(agentId, agent?.name || '') },
|
|
{ label: 'Delete', danger: true, onClick: () => deleteAgent(agentId) }
|
|
];
|
|
}
|
|
|
|
return [];
|
|
}, { minWidth: '120px', placement: 'right' });
|
|
break;
|
|
}
|
|
}
|
|
|
|
onMounted(() => {
|
|
// simply preload the topic page
|
|
preloadRouteComponents(`/agent/42`);
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<nav ref="navRef" class="flex flex-col gap-1 overflow-auto">
|
|
<SidenavItem name="Search" icon="mynaui:search" />
|
|
<SidenavItem to="/" name="Home" icon="mynaui:home" />
|
|
|
|
<!-- Header Toggle -->
|
|
<button
|
|
class="flex items-center justify-between gap-2 px-2 py-2 rounded-lg hover:bg-[var(--color-hover)] transition-colors w-full"
|
|
@click="toggleAgentsList">
|
|
<span class="text-sm">Agents</span>
|
|
<Icon name="mynaui:chevron-down" class="w-4 h-4 transition-transform duration-200"
|
|
:class="{ '-rotate-90': !agentsOpen }" />
|
|
</button>
|
|
|
|
<!-- Animated Section -->
|
|
<Collapsible :is-open="agentsOpen">
|
|
<div class="mt-1 gap-1 flex flex-col transform-origin-top pt-1 pb-1 px-1">
|
|
<button @click="newAgent" :disabled="creatingAgent"
|
|
class="flex items-center gap-2 px-1 h-9 shrink-0 rounded-lg text-sm text-[var(--text-secondary)] hover:bg-[var(--color-hover)] transition-colors disabled:opacity-50 w-full">
|
|
<div class="h-7 w-7 flex items-center justify-center">
|
|
<Icon v-if="creatingAgent" class="text-4.5" name="svg-spinners:ring-resize" />
|
|
<Icon v-else class="text-4.5" name="mynaui:plus" />
|
|
</div>
|
|
<span>New Agent</span>
|
|
</button>
|
|
|
|
<div @click="handleNavClick" class="flex flex-col">
|
|
<RowVirtualizerFixed :scroll-element="navRef" key-field="id" :items="agents" :prerender="50"
|
|
:item-size="40" :overscan="20">
|
|
<template v-slot="{ item: agent }">
|
|
<a data-action="navigate" :data-agent-id="agent.id" :href="`/agent/${agent.id}`"
|
|
:aria-label="agent.name"
|
|
class="mt-1 group px-2 decoration-none flex justify-between items-center shrink-0 rounded-lg transition-colors cursor-pointer h-9 text-[var(--text-secondary)] hover:bg-[var(--color-hover)] focus-visible:bg-[var(--color-hover)] focus:text-[var(--text-primary)]">
|
|
<input v-if="renameAgentId === agent.id" id="agent-rename-input" v-model="newAgentName"
|
|
@keydown.stop.enter="saveRename" @keydown.escape="cancelRename" @blur="saveRename"
|
|
class="flex-1 bg-transparent border-none outline-none text-sm font-medium text-[var(--text-primary)] px-0 min-w-0" />
|
|
<span v-else
|
|
class="text-sm font-medium overflow-hidden text-ellipsis whitespace-nowrap">
|
|
{{ agent.name }}
|
|
</span>
|
|
|
|
<div data-action="toggle-dropdown"
|
|
class="text-[var(--text-secondary)] shrink-0 opacity-0 group-hover:opacity-100 p-1 flex items-center justify-center rounded-md hover:bg-[var(--color-hover)] focus-visible:bg-[var(--color-hover)] transition-opacity duration-200 ease-[cubic-bezier(0.5,_1,_0.89,_1)]">
|
|
<span class="h-4.5 w-4.5 i-tabler:dots"></span>
|
|
</div>
|
|
</a>
|
|
</template>
|
|
</RowVirtualizerFixed>
|
|
</div>
|
|
</div>
|
|
</Collapsible>
|
|
</nav>
|
|
</template>
|