feat: Agent System Prompts MVP (B) - database, backend API, and frontend integration
This commit is contained in:
@@ -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?.();
|
||||
|
||||
|
||||
38
apps/web/src/hooks/useAgentPrompts.ts
Normal file
38
apps/web/src/hooks/useAgentPrompts.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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<{
|
||||
|
||||
Reference in New Issue
Block a user