updates on the hook and endpoints for the ai agents

This commit is contained in:
2026-09-30 15:08:33 +05:30
parent ede1731fa5
commit d33fff01d3
5 changed files with 180 additions and 38 deletions

View File

@@ -826,6 +826,16 @@ export const updateAiAgent = async (id, patch) => {
* false when the backend is not subscribed to AI_engine telemetry.
* @param {number} days
*/
/**
* What is actually wired, for the Skills & Tools banner: whether AI_engine is
* reading the registry (it polls ~every 30 s; the backend records each read),
* telemetry, live agents, and whether the Test tab has a model.
*/
export const getAiStatus = async () => {
const response = await doormileAxios.get('/admin/ai/status');
return response.data.data;
};
export const getAiInsights = async (days = 7) => {
const response = await doormileAxios.get(`/admin/ai/insights${buildQuery({ days })}`);
return response.data.data;

View File

@@ -865,6 +865,18 @@ export const useUpdateAiAgent = () =>
});
// Read-only, and it changes as agents work: refetched every minute while open.
// The banner's source of truth; re-checked every 30 s so it turns green on its
// own once AI_engine starts reading the registry.
export const useAiStatus = (options) =>
useQuery({
queryKey: [...KEYS.aiRegistry, 'status'],
queryFn: api.getAiStatus,
staleTime: 15_000,
refetchInterval: 30_000,
retry: false,
...options,
});
export const useAiInsights = (days = 7, options) =>
useQuery({
queryKey: [...KEYS.aiRegistry, 'insights', days],

View File

@@ -5,7 +5,6 @@ import {
Wrench,
Bot,
BarChart3,
AlertTriangle,
Info,
Loader2,
} from 'lucide-react';
@@ -28,6 +27,7 @@ import SkillDetailsDrawer from './SkillDetailsDrawer';
import CreateSkillModal from './CreateSkillModal';
import ToolRegistryView from './ToolRegistryView';
import AgentPlayground from './AgentPlayground';
import WiringStatusBanner from './WiringStatusBanner';
import { AgentConfigView, AgentInsightsView } from './AgentConfigAndInsights';
const TOP_NAV_TABS = [
@@ -122,22 +122,9 @@ export default function AgentStudio() {
return (
<div className="space-y-5 pb-10">
{/* Said on screen: what this page changes today, and what it does not. */}
<div
role="note"
className="flex items-start gap-3 rounded-2xl border border-amber-200/80 bg-gradient-to-r from-amber-50 to-orange-50 px-4 py-3.5 text-xs text-amber-900 shadow-sm"
>
<AlertTriangle className="w-4 h-4 mt-0.5 shrink-0 text-amber-500" />
<p className="leading-relaxed">
<span className="font-bold text-amber-800">Half wired.</span>{' '}
Changes here are saved to the Doormile agent registry and every change is audited. The Console Ops Agent&apos;s skills — the Exceptions early warnings and the
chat&apos;s &ldquo;what needs attention&rdquo; — run on these settings now. AI_engine&apos;s Dispatch,
Exception, Express Dispatch and Customer agents pick up changes within about 30 seconds once its
registry-reading build is deployed; until then, and whenever the registry cannot be reached, they run on
their environment defaults. Hub, Fleet and Route Optimizer are simulations. The Test tab runs real AI model
calls (reads redacted, writes only proposed) once the server has a model API key; until then it says so.
</p>
</div>
{/* Said on screen: what these settings actually drive right now, from
GET /admin/ai/status — not a fixed note. */}
<WiringStatusBanner />
{!canEdit && (
<div className="flex items-center gap-2 rounded-xl border border-slate-200 bg-slate-50 px-4 py-2.5 text-xs text-slate-600">

View File

@@ -0,0 +1,108 @@
import React from 'react';
import { AlertTriangle, CheckCircle2, Info, XCircle } from 'lucide-react';
import { useAiStatus } from '@/lib/doormileHooks';
/**
* What this page's settings actually drive, from GET /admin/ai/status — not a
* fixed note. It used to say "Half wired" forever, including after AI_engine
* and the backend were deployed, because nothing checked.
*
* Green only when AI_engine has read the registry recently (the backend stamps
* every poll) and the Test tab has a model. Re-checks every 30 seconds.
*/
function ago(iso) {
if (!iso) return '';
const s = Math.max(0, Math.round((Date.now() - new Date(iso).getTime()) / 1000));
if (s < 60) return `${s}s ago`;
if (s < 3600) return `${Math.round(s / 60)} min ago`;
return new Date(iso).toLocaleString('en-IN', { dateStyle: 'medium', timeStyle: 'short' });
}
/** The banner's lines, from a status payload. Pure, so it is tested directly. */
export function wiringLines(status) {
const engine = status?.engine || {};
const playground = status?.playground || {};
return [
{ ok: true, text: 'Changes are saved to the Doormile agent registry, and every change is audited.' },
{
ok: true,
text: 'The Console Ops Agent’s skills (Exceptions early warnings, the chat’s “what needs attention”) run on these settings.',
},
engine.readingsettings
? {
ok: true,
text: `AI_engine is following these settings (last read ${ago(engine.lastreadat)}); changes reach its Dispatch, Exception, Express Dispatch and Customer agents within about 30 seconds.`,
}
: {
ok: false,
text: engine.lastreadat
? `AI_engine has not read these settings since ${ago(engine.lastreadat)}, so its agents are on their environment defaults. Check it is running and can reach this API (GO_API_BASE_URL, INTERNAL_API_KEY).`
: 'AI_engine has not read these settings yet, so its agents run on their environment defaults. It needs the registry-reading build, with GO_API_BASE_URL and INTERNAL_API_KEY pointing at this API.',
},
playground.configured
? { ok: true, text: `The Test tab is live${playground.model ? ` (model ${playground.model})` : ''}: reads are redacted, writes are only proposed.` }
: { ok: false, text: 'The Test tab is off: the server has no model API key (PLAYGROUND_LLM_API_KEY).' },
{ info: true, text: 'Hub, Fleet and Route Optimizer are simulations and are not affected by these settings.' },
];
}
export default function WiringStatusBanner() {
const { data, isLoading, isError, error } = useAiStatus();
if (isLoading) {
return (
<div role="note" className="rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3.5 text-xs text-slate-500">
Checking what is connected…
</div>
);
}
if (isError) {
const notDeployed = error?.response?.status === 404;
return (
<div
role="note"
className="flex items-start gap-3 rounded-2xl border border-amber-200/80 bg-gradient-to-r from-amber-50 to-orange-50 px-4 py-3.5 text-xs text-amber-900 shadow-sm"
>
<AlertTriangle className="w-4 h-4 mt-0.5 shrink-0 text-amber-500" />
<p className="leading-relaxed">
<span className="font-bold text-amber-800">Connection status unavailable.</span>{' '}
{notDeployed
? 'This API server does not report agent status yet — deploy the current doormile_backend build. Changes here are still saved and audited.'
: 'The status check failed; it will retry shortly. Changes here are still saved and audited.'}
</p>
</div>
);
}
const lines = wiringLines(data);
const allOk = lines.every((l) => l.ok || l.info);
return (
<div
role="note"
className={
allOk
? 'rounded-2xl border border-emerald-200/80 bg-gradient-to-r from-emerald-50 to-teal-50 px-4 py-3.5 text-xs text-emerald-900 shadow-sm'
: 'rounded-2xl border border-amber-200/80 bg-gradient-to-r from-amber-50 to-orange-50 px-4 py-3.5 text-xs text-amber-900 shadow-sm'
}
>
<p className={`font-bold mb-1.5 ${allOk ? 'text-emerald-800' : 'text-amber-800'}`}>
{allOk ? 'All connected.' : 'Partly connected.'}
</p>
<ul className="space-y-1">
{lines.map((l) => {
const Icon = l.info ? Info : l.ok ? CheckCircle2 : XCircle;
const color = l.info ? 'text-slate-400' : l.ok ? 'text-emerald-600' : 'text-amber-600';
return (
<li key={l.text} className="flex items-start gap-2 leading-relaxed">
<Icon className={`w-3.5 h-3.5 mt-0.5 shrink-0 ${color}`} />
<span>{l.text}</span>
</li>
);
})}
</ul>
</div>
);
}