Enabled edits for agent configurations
This commit is contained in:
parent
1bb6075332
commit
700c7df542
2 changed files with 177 additions and 8 deletions
|
|
@ -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'">
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue