feat: Agent System Prompts MVP (B) - database, backend API, and frontend integration

This commit is contained in:
Apple
2025-11-30 14:04:48 -08:00
parent bca81dc719
commit 1830109a95
10 changed files with 624 additions and 173 deletions

View File

@@ -1,15 +1,16 @@
'use client';
import { useState } from 'react';
import { useState, useMemo, useEffect } from 'react';
import {
AgentSystemPrompts,
PromptKind,
updateAgentPrompt
} from '@/lib/agent-dashboard';
import { useAgentPrompts } from '@/hooks/useAgentPrompts';
interface AgentSystemPromptsCardProps {
agentId: string;
systemPrompts?: AgentSystemPrompts;
systemPrompts?: AgentSystemPrompts; // Legacy/Initial data
canEdit?: boolean;
onUpdated?: () => void;
}
@@ -43,17 +44,56 @@ const PROMPT_KINDS: { id: PromptKind; label: string; icon: string; description:
export function AgentSystemPromptsCard({
agentId,
systemPrompts,
systemPrompts: initialPrompts,
canEdit = false,
onUpdated
}: AgentSystemPromptsCardProps) {
const { prompts: fetchedPromptsList, mutate } = useAgentPrompts(agentId);
// Transform list to dict structure
const systemPrompts = useMemo(() => {
if (!fetchedPromptsList || fetchedPromptsList.length === 0) {
return initialPrompts || {};
}
const dict: AgentSystemPrompts = {
core: null,
safety: null,
governance: null,
tools: null
};
fetchedPromptsList.forEach(p => {
if (p.kind in dict) {
dict[p.kind] = {
content: p.content,
version: p.version,
updated_at: p.updated_at || new Date().toISOString(),
updated_by: 'system' // Not returned by all endpoints yet
};
}
});
return dict;
}, [fetchedPromptsList, initialPrompts]);
const [activeTab, setActiveTab] = useState<PromptKind>('core');
const [editedContent, setEditedContent] = useState<Record<PromptKind, string>>({
core: systemPrompts?.core?.content || '',
safety: systemPrompts?.safety?.content || '',
governance: systemPrompts?.governance?.content || '',
tools: systemPrompts?.tools?.content || ''
core: '',
safety: '',
governance: '',
tools: ''
});
// Sync edited content when active tab or prompts change
useEffect(() => {
const current = systemPrompts?.[activeTab];
setEditedContent(prev => ({
...prev,
[activeTab]: current?.content || ''
}));
}, [activeTab, systemPrompts]);
const [saving, setSaving] = useState(false);
const [saveStatus, setSaveStatus] = useState<'idle' | 'success' | 'error'>('idle');
const [error, setError] = useState<string | null>(null);
@@ -71,6 +111,7 @@ export function AgentSystemPromptsCard({
try {
await updateAgentPrompt(agentId, activeTab, currentContent);
await mutate(); // Refresh data
setSaveStatus('success');
onUpdated?.();

View File

@@ -0,0 +1,38 @@
import useSWR from 'swr';
export type PromptKind = 'core' | 'safety' | 'governance' | 'tools';
export interface AgentPrompt {
id?: string;
kind: PromptKind;
content: string;
version: number;
updated_at?: string;
note?: string;
}
export interface AgentPromptList {
agent_id: string;
prompts: AgentPrompt[];
}
const fetcher = (url: string) => fetch(url).then((res) => {
if (!res.ok) throw new Error('Failed to fetch prompts');
return res.json();
});
export function useAgentPrompts(agentId?: string) {
const { data, error, isLoading, mutate } = useSWR<AgentPromptList>(
agentId ? `/api/v1/agents/${agentId}/prompts` : null,
fetcher
);
return {
prompts: data?.prompts || [],
agentId: data?.agent_id,
isLoading,
error,
mutate,
};
}

View File

@@ -265,12 +265,17 @@ export async function updateAgentPrompt(
content: string,
note?: string
): Promise<UpdatePromptResult> {
// Use new bulk upsert endpoint
const response = await fetch(
`/api/agents/${encodeURIComponent(agentId)}/prompts/${encodeURIComponent(kind)}`,
`/api/v1/agents/${encodeURIComponent(agentId)}/prompts`,
{
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ content, note })
body: JSON.stringify({
prompts: [
{ kind, content, note }
]
})
}
);
@@ -279,7 +284,21 @@ export async function updateAgentPrompt(
throw new Error(body?.error || 'Failed to update prompt');
}
return response.json();
// Map response (list) to singular result for compatibility
const data = await response.json(); // AgentPromptList
const updated = data.prompts.find((p: any) => p.kind === kind);
if (!updated) {
throw new Error('Updated prompt not returned');
}
return {
agent_id: data.agent_id,
kind: updated.kind,
version: updated.version,
updated_at: updated.updated_at,
updated_by: updated.created_by || 'unknown'
};
}
export async function getPromptHistory(agentId: string, kind: PromptKind): Promise<{