39998-vm/frontend/src/pages/agents/agents-view.tsx
Flatlogic Bot 83cdb092cd 7
2026-05-15 19:17:21 +00:00

359 lines
15 KiB
TypeScript

import {
mdiArrowLeft,
mdiChartTimelineVariant,
mdiPencilOutline,
mdiRobotOutline,
mdiTextBoxOutline,
mdiTuneVariant,
} from '@mdi/js';
import Head from 'next/head';
import Link from 'next/link';
import React, { ReactElement, useEffect } from 'react';
import BaseIcon from '../../components/BaseIcon';
import LoadingSpinner from '../../components/LoadingSpinner';
import SectionMain from '../../components/SectionMain';
import { getPageTitle } from '../../config';
import LayoutAuthenticated from '../../layouts/Authenticated';
import { fetch } from '../../stores/agents/agentsSlice';
import { useAppDispatch, useAppSelector } from '../../stores/hooks';
import { useRouter } from 'next/router';
const actionButtonClassName =
'inline-flex items-center justify-center rounded-[8px] border px-4 py-2 text-sm font-medium';
function formatDate(value: string) {
if (!value) {
return 'No date';
}
const date = new Date(value);
if (Number.isNaN(date.getTime())) {
return value;
}
return date.toLocaleString('en-US', {
day: 'numeric',
hour: 'numeric',
minute: '2-digit',
month: 'short',
});
}
function formatTemperature(value: any) {
if (value === null || value === undefined || value === '') {
return 'Auto';
}
return value;
}
function formatTokenLimit(value: any) {
if (!value) {
return 'Default';
}
return `${value} max tokens`;
}
function parseMetadata(value: string) {
if (!value) {
return null;
}
try {
return JSON.parse(value);
} catch (error) {
return null;
}
}
function RelatedItem({
href,
meta,
title,
}: {
href: string;
meta: string;
title: string;
}) {
return (
<Link
className="flex items-start justify-between gap-4 rounded-[10px] border border-slate-200 bg-white px-4 py-3 transition-colors hover:border-slate-300"
href={href}
>
<div className="min-w-0">
<p className="truncate text-[14px] font-medium text-slate-900">{title}</p>
<p className="mt-1 text-[12px] leading-5 text-slate-500">{meta}</p>
</div>
<BaseIcon className="shrink-0 text-slate-400" path={mdiChartTimelineVariant} size={16} />
</Link>
);
}
const AgentsView = () => {
const router = useRouter();
const dispatch = useAppDispatch();
const { agents, loading } = useAppSelector((state) => state.agents);
const { id } = router.query;
useEffect(() => {
if (typeof id !== 'string') {
return;
}
dispatch(fetch({ id }));
}, [dispatch, id]);
const agent = !Array.isArray(agents) && agents && typeof agents === 'object' ? agents : null;
const metadata = parseMetadata(agent?.metadata_json || '');
return (
<>
<Head>
<title>{getPageTitle('Agent details')}</title>
</Head>
<SectionMain>
<div className="flex w-full flex-col gap-5">
<div className="rounded-[12px] border border-slate-200 bg-white px-6 py-6">
<div className="flex flex-wrap items-start justify-between gap-4">
<div>
<Link
className="inline-flex items-center gap-2 text-[12px] font-medium text-slate-500"
href="/agents/agents-list"
>
<BaseIcon path={mdiArrowLeft} size={14} />
Back to agents
</Link>
<p className="mt-4 text-[11px] font-medium uppercase tracking-[0.28em] text-slate-400">
Agent
</p>
<h1 className="mt-3 text-[2rem] font-semibold tracking-[-0.04em] text-slate-900">
{agent?.name || 'Agent details'}
</h1>
<p className="mt-3 max-w-2xl text-sm leading-6 text-slate-500">
Review the prompt, model behavior, and recent activity linked to this assistant profile.
</p>
</div>
<div className="flex flex-wrap gap-2">
<Link
className={`${actionButtonClassName} border-slate-200 bg-white text-slate-700`}
href="/agents/agents-list"
>
Close
</Link>
<Link
className={`${actionButtonClassName} border-slate-900 bg-slate-900 text-white`}
href={`/agents/agents-edit/?id=${id}`}
>
<BaseIcon className="mr-1" path={mdiPencilOutline} size={16} />
Edit agent
</Link>
</div>
</div>
</div>
{loading && !agent && (
<div className="rounded-[12px] border border-slate-200 bg-white px-6 py-10">
<LoadingSpinner />
</div>
)}
{!loading && !agent && (
<div className="rounded-[12px] border border-dashed border-slate-200 bg-slate-50 px-6 py-12 text-center">
<p className="text-[11px] font-medium uppercase tracking-[0.2em] text-slate-400">
No data
</p>
<h2 className="mt-3 text-[22px] font-semibold tracking-[-0.03em] text-slate-900">
We could not load this agent.
</h2>
<p className="mt-3 text-[14px] leading-6 text-slate-500">
Try going back to the agents list and opening it again.
</p>
</div>
)}
{agent && (
<>
<div className="grid gap-5 2xl:grid-cols-[minmax(0,1fr)_320px]">
<div className="space-y-5">
<div className="rounded-[12px] border border-slate-200 bg-white px-6 py-6">
<div className="mb-5 flex items-start gap-3">
<div className="mt-0.5 inline-flex h-10 w-10 items-center justify-center rounded-[10px] border border-slate-200 bg-slate-50 text-slate-600">
<BaseIcon path={mdiRobotOutline} size={20} />
</div>
<div>
<p className="text-[15px] font-medium text-slate-900">Identity</p>
<p className="mt-1 text-sm leading-6 text-slate-500">
Basic positioning and the model this assistant runs on.
</p>
</div>
</div>
<div className="grid gap-4 md:grid-cols-2">
<div className="rounded-[10px] border border-slate-200 px-4 py-3">
<p className="text-[11px] font-medium uppercase tracking-[0.18em] text-slate-400">
Name
</p>
<p className="mt-2 text-[16px] font-medium text-slate-900">
{agent.name || 'Untitled agent'}
</p>
</div>
<div className="rounded-[10px] border border-slate-200 px-4 py-3">
<p className="text-[11px] font-medium uppercase tracking-[0.18em] text-slate-400">
Model
</p>
<p className="mt-2 text-[16px] font-medium text-slate-900">
{agent.model || 'Default model'}
</p>
</div>
<div className="rounded-[10px] border border-slate-200 px-4 py-3 md:col-span-2">
<p className="text-[11px] font-medium uppercase tracking-[0.18em] text-slate-400">
Description
</p>
<p className="mt-2 text-[14px] leading-6 text-slate-600">
{agent.description || 'No description yet.'}
</p>
</div>
</div>
</div>
<div className="rounded-[12px] border border-slate-200 bg-white px-6 py-6">
<div className="mb-5 flex items-start gap-3">
<div className="mt-0.5 inline-flex h-10 w-10 items-center justify-center rounded-[10px] border border-slate-200 bg-slate-50 text-slate-600">
<BaseIcon path={mdiTextBoxOutline} size={20} />
</div>
<div>
<p className="text-[15px] font-medium text-slate-900">System prompt</p>
<p className="mt-1 text-sm leading-6 text-slate-500">
The standing instructions this agent follows in every conversation.
</p>
</div>
</div>
<div className="rounded-[10px] border border-slate-200 bg-slate-50 px-4 py-4">
<pre className="whitespace-pre-wrap text-[13px] leading-6 text-slate-700">
{agent.system_prompt || 'No system prompt yet.'}
</pre>
</div>
</div>
<div className="rounded-[12px] border border-slate-200 bg-white px-6 py-6">
<div className="mb-5 flex items-start gap-3">
<div className="mt-0.5 inline-flex h-10 w-10 items-center justify-center rounded-[10px] border border-slate-200 bg-slate-50 text-slate-600">
<BaseIcon path={mdiTuneVariant} size={20} />
</div>
<div>
<p className="text-[15px] font-medium text-slate-900">Behavior</p>
<p className="mt-1 text-sm leading-6 text-slate-500">
Output shaping, default availability, and any structured metadata stored with the agent.
</p>
</div>
</div>
<div className="grid gap-4 md:grid-cols-2">
<div className="rounded-[10px] border border-slate-200 px-4 py-3">
<p className="text-[11px] font-medium uppercase tracking-[0.18em] text-slate-400">
Temperature
</p>
<p className="mt-2 text-[16px] font-medium text-slate-900">
{formatTemperature(agent.temperature)}
</p>
</div>
<div className="rounded-[10px] border border-slate-200 px-4 py-3">
<p className="text-[11px] font-medium uppercase tracking-[0.18em] text-slate-400">
Output
</p>
<p className="mt-2 text-[16px] font-medium text-slate-900">
{formatTokenLimit(agent.max_output_tokens)}
</p>
</div>
<div className="rounded-[10px] border border-slate-200 px-4 py-3">
<p className="text-[11px] font-medium uppercase tracking-[0.18em] text-slate-400">
Availability
</p>
<p className="mt-2 text-[16px] font-medium text-slate-900">
{agent.is_active ? 'Active' : 'Inactive'}
</p>
</div>
<div className="rounded-[10px] border border-slate-200 px-4 py-3">
<p className="text-[11px] font-medium uppercase tracking-[0.18em] text-slate-400">
Preset type
</p>
<p className="mt-2 text-[16px] font-medium text-slate-900">
{agent.is_default ? 'Default preset' : 'Custom preset'}
</p>
</div>
<div className="rounded-[10px] border border-slate-200 px-4 py-3 md:col-span-2">
<p className="text-[11px] font-medium uppercase tracking-[0.18em] text-slate-400">
Metadata JSON
</p>
<pre className="mt-2 whitespace-pre-wrap text-[13px] leading-6 text-slate-600">
{metadata ? JSON.stringify(metadata, null, 2) : agent.metadata_json || 'No metadata.'}
</pre>
</div>
</div>
</div>
</div>
<div className="space-y-5">
<div className="rounded-[12px] border border-slate-200 bg-white px-5 py-5">
<p className="text-[11px] font-medium uppercase tracking-[0.28em] text-slate-400">
Related conversations
</p>
<div className="mt-4 space-y-3">
{Array.isArray(agent.conversations_agent) && agent.conversations_agent.length > 0 ? (
agent.conversations_agent.map((item: any) => (
<RelatedItem
key={item.id}
href={`/conversations/conversations-view/?id=${item.id}`}
meta={`${item.status || 'unknown'} · ${formatDate(item.last_message_at)}`}
title={item.title || 'Untitled conversation'}
/>
))
) : (
<p className="text-[13px] leading-6 text-slate-500">
No conversations linked to this agent yet.
</p>
)}
</div>
</div>
<div className="rounded-[12px] border border-slate-200 bg-white px-5 py-5">
<p className="text-[11px] font-medium uppercase tracking-[0.28em] text-slate-400">
Usage events
</p>
<div className="mt-4 space-y-3">
{Array.isArray(agent.usage_events_agent) && agent.usage_events_agent.length > 0 ? (
agent.usage_events_agent.map((item: any) => (
<RelatedItem
key={item.id}
href={`/usage_events/usage_events-view/?id=${item.id}`}
meta={`${item.provider || 'provider'} · ${formatDate(item.occurred_at)}`}
title={item.event_type || 'Usage event'}
/>
))
) : (
<p className="text-[13px] leading-6 text-slate-500">
No usage activity has been recorded for this agent yet.
</p>
)}
</div>
</div>
</div>
</div>
</>
)}
</div>
</SectionMain>
</>
);
};
AgentsView.getLayout = function getLayout(page: ReactElement) {
return <LayoutAuthenticated permission="READ_AGENTS">{page}</LayoutAuthenticated>;
};
export default AgentsView;