Enabled edits for agent configurations

This commit is contained in:
Viswamedha Nalabotu 2026-02-27 14:56:20 +00:00
parent 1bb6075332
commit 700c7df542
2 changed files with 177 additions and 8 deletions

View file

@ -12,6 +12,7 @@ import {
message, message,
Tag, Tag,
InputNumber, InputNumber,
Select,
} from 'ant-design-vue' } from 'ant-design-vue'
import { marked } from 'marked' import { marked } from 'marked'
import DOMPurify from 'dompurify' import DOMPurify from 'dompurify'
@ -22,18 +23,32 @@ import type { AgentConfig, AgentRunResult } from '../types/agent'
const route = useRoute() const route = useRoute()
const agentStore = useAgentStore() const agentStore = useAgentStore()
const agentId = route.params.id as string const agentUuid = route.params.agentUuid as string
const agent = ref<AgentConfig>({ const agent = ref<AgentConfig>({
id: agentId,
name: 'Loading...', name: 'Loading...',
description: '', description: '',
status: 'idle', status: 'idle',
uuid: agentId, uuid: agentUuid,
agent_type: 'knowledge', agent_type: 'knowledge',
llm_config: {}, llm_config: {},
organization: '', organization: '',
}) })
const saveLoading = ref(false)
const editingConfig = ref(false)
const agentForm = ref({
name: '',
agent_type: 'knowledge',
model_id: '',
system_prompt: '',
})
const agentTypeOptions = [
{ label: 'Curriculum Agent', value: 'curriculum' },
{ label: 'Knowledge Agent', value: 'knowledge' },
{ label: 'Assessment Agent', value: 'assessment' },
{ label: 'Progress Monitor', value: 'monitor' },
]
const maxTokens = ref<number>(256) const maxTokens = ref<number>(256)
const queryInput = ref('') const queryInput = ref('')
@ -63,8 +78,14 @@ const statusColor = (status: string) => {
const fetchAgent = async () => { const fetchAgent = async () => {
try { try {
const response = await apiClient.get<AgentConfig>(API.agents.configs.byId(agentId)) const response = await apiClient.get<AgentConfig>(API.agents.configs.byId(agentUuid))
agent.value = response.data agent.value = response.data
agentForm.value = {
name: response.data.name || '',
agent_type: response.data.agent_type || 'knowledge',
model_id: String(response.data.llm_config?.model_id || ''),
system_prompt: String(response.data.system_prompt || ''),
}
} catch (error) { } catch (error) {
console.error('Failed to fetch agent:', error) console.error('Failed to fetch agent:', error)
if (isAxiosError(error)) { if (isAxiosError(error)) {
@ -78,6 +99,50 @@ const fetchAgent = async () => {
} }
} }
const resetForm = () => {
agentForm.value = {
name: agent.value.name || '',
agent_type: agent.value.agent_type || 'knowledge',
model_id: String(agent.value.llm_config?.model_id || ''),
system_prompt: String(agent.value.system_prompt || ''),
}
editingConfig.value = false
}
const saveConfig = async () => {
const payload = {
name: agentForm.value.name.trim(),
system_prompt: agentForm.value.system_prompt.trim(),
}
if (!payload.name) {
message.error('Agent name is required')
return
}
if (!payload.system_prompt) {
message.error('System prompt is required')
return
}
saveLoading.value = true
try {
const response = await apiClient.patch<AgentConfig>(API.agents.configs.byId(agentUuid), payload)
agent.value = response.data
editingConfig.value = false
message.success('Agent configuration updated')
} catch (error) {
console.error('Failed to update agent config:', error)
if (isAxiosError(error)) {
message.error(error.response?.data?.detail || 'Failed to update configuration')
} else {
message.error('Failed to update configuration')
}
} finally {
saveLoading.value = false
}
}
const renderedAgentResponse = computed(() => { const renderedAgentResponse = computed(() => {
const rawMarkdown = agentResponse.value const rawMarkdown = agentResponse.value
if (!rawMarkdown) return '' if (!rawMarkdown) return ''
@ -110,7 +175,7 @@ const stopAgent = () => {
onMounted(() => { onMounted(() => {
fetchAgent() fetchAgent()
agentStore.connect(agentId) agentStore.connect(agentUuid)
}) })
onUnmounted(() => { onUnmounted(() => {
@ -132,6 +197,52 @@ onUnmounted(() => {
{{ agent.description || 'No description available' }} {{ agent.description || 'No description available' }}
</Typography.Paragraph> </Typography.Paragraph>
<Typography.Title :level="4" class="section-title">Configuration</Typography.Title>
<div class="execution-controls">
<Space direction="vertical" style="width: 100%" :size="12">
<div>
<Typography.Text>Agent Name:</Typography.Text>
<Input v-model:value="agentForm.name" :disabled="!editingConfig" />
</div>
<div>
<Typography.Text>Agent Type:</Typography.Text>
<Select
v-model:value="agentForm.agent_type"
:options="agentTypeOptions"
disabled
style="width: 100%"
/>
</div>
<div>
<Typography.Text>Model ID:</Typography.Text>
<Input v-model:value="agentForm.model_id" disabled />
</div>
<div>
<Typography.Text>System Prompt:</Typography.Text>
<Input.TextArea
v-model:value="agentForm.system_prompt"
:rows="6"
:disabled="!editingConfig"
/>
</div>
<Space>
<Button v-if="!editingConfig" type="primary" @click="editingConfig = true">
Edit Configuration
</Button>
<template v-else>
<Button type="primary" :loading="saveLoading" @click="saveConfig">
Save Changes
</Button>
<Button @click="resetForm">Cancel</Button>
</template>
</Space>
</Space>
</div>
<div class="connection-status"> <div class="connection-status">
<span>WebSocket Status:</span> <span>WebSocket Status:</span>
<Tag :color="agentStore.isConnected ? 'green' : 'red'"> <Tag :color="agentStore.isConnected ? 'green' : 'red'">

View file

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted } from 'vue' import { ref, onMounted, computed } from 'vue'
import { List, Typography, Button, Card, Spin, message, Tag, Space } from 'ant-design-vue' import { List, Typography, Button, Card, Spin, message, Tag, Space, Select } from 'ant-design-vue'
import { apiClient, API } from '../router/api' import { apiClient, API } from '../router/api'
import type { MaybePaginated } from '../types/common' import type { MaybePaginated } from '../types/common'
import type { AgentConfig } from '../types/agent' import type { AgentConfig } from '../types/agent'
@ -8,6 +8,8 @@ import type { AgentConfig } from '../types/agent'
const agents = ref<AgentConfig[]>([]) const agents = ref<AgentConfig[]>([])
const loading = ref(false) const loading = ref(false)
const loadError = ref(false) const loadError = ref(false)
const selectedRole = ref<string | undefined>(undefined)
const selectedAgentType = ref<string | undefined>(undefined)
const fetchAgents = async () => { const fetchAgents = async () => {
loading.value = true loading.value = true
@ -36,6 +38,32 @@ const getAgentTypeLabel = (type: string) => {
return types[type] || type return types[type] || type
} }
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(() => { onMounted(() => {
fetchAgents() fetchAgents()
}) })
@ -49,6 +77,23 @@ onMounted(() => {
</Typography.Paragraph> </Typography.Paragraph>
<Card class="panel" :bordered="false"> <Card class="panel" :bordered="false">
<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..."> <Spin :spinning="loading" tip="Loading Agents...">
<div v-if="loadError" class="empty"> <div v-if="loadError" class="empty">
<Typography.Paragraph type="danger"> <Typography.Paragraph type="danger">
@ -62,12 +107,19 @@ onMounted(() => {
</Typography.Paragraph> </Typography.Paragraph>
</div> </div>
<List v-else :data-source="agents" item-layout="horizontal"> <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 }"> <template #renderItem="{ item }">
<List.Item class="item"> <List.Item class="item">
<List.Item.Meta :title="item.name"> <List.Item.Meta :title="item.name">
<template #description> <template #description>
<Space direction="vertical"> <Space direction="vertical">
<Tag color="geekblue">{{ getRoleLabel(item) }}</Tag>
<Tag color="blue"> <Tag color="blue">
{{ getAgentTypeLabel(item.agent_type) }} {{ getAgentTypeLabel(item.agent_type) }}
</Tag> </Tag>
@ -98,6 +150,12 @@ onMounted(() => {
background: #0f172a; background: #0f172a;
border: 1px solid #1f2937; border: 1px solid #1f2937;
} }
.filters {
display: flex;
gap: 0.75rem;
margin-bottom: 1rem;
flex-wrap: wrap;
}
.item :deep(.ant-list-item-meta-title) { .item :deep(.ant-list-item-meta-title) {
color: #f8fafc; color: #f8fafc;
font-weight: 600; font-weight: 600;