Dynavera/site/src/views/AgentsView.vue

181 lines
6.5 KiB
Vue
Raw Normal View History

<script setup lang="ts">
2026-02-27 14:56:20 +00:00
import { ref, onMounted, computed } from 'vue'
import { List, Typography, Button, Card, Spin, message, Tag, Space, Select, Tooltip } from 'ant-design-vue'
import { apiClient, API } from '../router/api'
import type { MaybePaginated } from '../types/common'
import type { AgentConfig } from '../types/agent'
const agents = ref<AgentConfig[]>([])
const loading = ref(false)
const loadError = ref(false)
2026-02-27 14:56:20 +00:00
const selectedRole = ref<string | undefined>(undefined)
const selectedAgentType = ref<string | undefined>(undefined)
const fetchAgents = async () => {
loading.value = true
loadError.value = false
try {
const response = await apiClient.get<MaybePaginated<AgentConfig>>(API.agents.configs.list())
const data = response.data
agents.value = Array.isArray(data) ? data : data.results || []
} catch (error) {
console.error('Failed to fetch agents:', error)
message.error('Failed to load agent configurations')
loadError.value = true
} finally {
loading.value = false
}
}
const agentTypeDescriptions: Record<string, string> = {
curriculum: 'Guides new hires through a structured onboarding path in sequence.',
knowledge: 'Answers ad-hoc questions by searching uploaded training documents.',
assessment: 'Tests understanding through role-specific questions and reports results.',
monitor: 'Tracks session progress and surfaces completions or blockers.',
}
const getAgentTypeLabel = (type: string) => {
const types: Record<string, string> = {
curriculum: 'Curriculum Agent',
knowledge: 'Knowledge Agent',
assessment: 'Assessment Agent',
monitor: 'Progress Monitor',
}
return types[type] || type
}
2026-02-27 14:56:20 +00:00
const getRoleLabel = (agent: AgentConfig) => {
const knownSuffixes = ['Curriculum Agent', 'Knowledge Agent', 'Assessment Agent', 'Progress Monitor']
const name = (agent.name || '').trim()
const suffix = knownSuffixes.find((value) => name.endsWith(value))
if (!suffix) return name
return name.slice(0, name.length - suffix.length).trim()
}
const roleOptions = computed(() => {
const values = Array.from(new Set(agents.value.map((agent) => getRoleLabel(agent)).filter(Boolean)))
return values.map((value) => ({ label: value, value }))
})
const agentTypeOptions = computed(() => {
const values = Array.from(new Set(agents.value.map((agent) => agent.agent_type).filter(Boolean)))
return values.map((value) => ({ label: getAgentTypeLabel(value), value }))
})
const filteredAgents = computed(() =>
agents.value.filter((agent) => {
const roleMatches = !selectedRole.value || getRoleLabel(agent) === selectedRole.value
const typeMatches = !selectedAgentType.value || agent.agent_type === selectedAgentType.value
return roleMatches && typeMatches
}),
)
onMounted(() => {
fetchAgents()
})
</script>
<template>
<div class="page">
<Typography.Title :level="2">Agent Configurations</Typography.Title>
<Typography.Paragraph type="secondary">
Manage your AI personas and their specific tool permissions.
</Typography.Paragraph>
<Card class="panel" :bordered="false">
2026-02-27 14:56:20 +00:00
<div class="filters">
<Select
v-model:value="selectedRole"
allow-clear
placeholder="Filter by role"
:options="roleOptions"
style="min-width: 240px"
/>
<Select
v-model:value="selectedAgentType"
allow-clear
placeholder="Filter by agent type"
:options="agentTypeOptions"
style="min-width: 240px"
/>
</div>
<Spin :spinning="loading" tip="Loading Agents...">
<div v-if="loadError" class="empty">
<Typography.Paragraph type="danger">
Failed to load agents.
</Typography.Paragraph>
</div>
<div v-else-if="!loading && agents.length === 0" class="empty">
<Typography.Paragraph type="secondary">
No agent configurations found.
</Typography.Paragraph>
</div>
2026-02-27 14:56:20 +00:00
<div v-else-if="!loading && filteredAgents.length === 0" class="empty">
<Typography.Paragraph type="secondary">
No agents match the selected filters.
</Typography.Paragraph>
</div>
<List v-else :data-source="filteredAgents" item-layout="horizontal">
<template #renderItem="{ item }">
<List.Item class="item">
<List.Item.Meta :title="item.name">
<template #description>
<Space direction="vertical">
2026-02-27 14:56:20 +00:00
<Tag color="geekblue">{{ getRoleLabel(item) }}</Tag>
<Tooltip :title="agentTypeDescriptions[item.agent_type]">
<Tag color="blue">
{{ getAgentTypeLabel(item.agent_type) }}
</Tag>
</Tooltip>
<span class="config-summary">
Model: {{ item.llm_config?.model_id || 'Default' }}
</span>
</Space>
</template>
</List.Item.Meta>
<RouterLink :to="`/agents/${item.uuid}`">
<Button type="primary">Manage & Run</Button>
</RouterLink>
</List.Item>
</template>
</List>
</Spin>
</Card>
</div>
</template>
<style scoped>
.page {
max-width: 900px;
margin: 0 auto;
padding: 2rem 1rem;
}
.panel {
background: #ffffff;
border: 1px solid #dbe3ec;
}
2026-02-27 14:56:20 +00:00
.filters {
display: flex;
gap: 0.75rem;
margin-bottom: 1rem;
flex-wrap: wrap;
}
.item :deep(.ant-list-item-meta-title) {
color: #1f2937;
font-weight: 600;
}
.config-summary {
color: #6b7280;
font-size: 0.85rem;
}
.empty {
padding: 3rem;
text-align: center;
}
</style>