**Phase 1 — Agent Usage Sync to AgentHub Collector**
- Add agent_usage service: per-agent stats (messages, tokens, conversations, unique users, first/last used)
- Collector sync now includes usage data in payload; sync_agent accepts optional db session
- Celery beat task runs every 6h to sync all active agents with fresh usage stats
**Phase 2 — LibreCodeInterpreter Integration**
- Add code-interpreter, redis, minio services to docker-compose.prod.yml
- CodeInterpreterTool (BaseTool): sandboxed execution via /exec, 13 languages, Python session persistence via conversation_id
- ToolContext extended with conversation_id and agent_slug
- enable_code_interpreter boolean on Agent model (migration 027), tool seeded in tool_definitions (migration 026)
- Code interpreter auto-injected into agent tools when enabled
- Frontend: CodeExecutionResult component with terminal-style stdout/stderr/files rendering
**Phase 3 — Agent API Endpoints**
- GET /api/v1/agents/{slug}/analytics — per-agent usage stats + daily time series
- POST /api/v1/agents/{slug}/execute — synchronous programmatic agent execution (non-SSE)
- Sub-routes registered before /{slug} to avoid FastAPI route conflict
**Phase 4 — Fix Department & Region RAG Scoping**
- Department filter now OR-includes global (null department) docs, matching region filter behaviour
- retriever.search_documents/retrieve_and_prepare/query accept department_ids/region_codes lists
- MatchAny used for multi-value Qdrant filters; chat.py passes full arrays from knowledge_scope
- Admin PATCH /users/{id} now validates region_code against the regions table
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
113 lines
4 KiB
TypeScript
113 lines
4 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import { ChevronDown, ChevronUp, Download, Terminal, AlertTriangle, CheckCircle2 } from 'lucide-react';
|
|
import { cn } from '@/lib/utils';
|
|
import type { CodeExecutionData } from '@/types';
|
|
|
|
interface CodeExecutionResultProps {
|
|
data: CodeExecutionData;
|
|
duration_ms?: number;
|
|
}
|
|
|
|
export function CodeExecutionResult({ data, duration_ms }: CodeExecutionResultProps) {
|
|
const [outputOpen, setOutputOpen] = useState(true);
|
|
|
|
const hasOutput = Boolean(data.stdout || data.stderr);
|
|
const hasFiles = data.files && data.files.length > 0;
|
|
const hasErrors = Boolean(data.stderr);
|
|
|
|
return (
|
|
<div className="mt-2 rounded-lg border border-border/60 overflow-hidden text-xs">
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between bg-muted/40 px-3 py-2 border-b border-border/40">
|
|
<div className="flex items-center gap-2">
|
|
<Terminal className="h-3.5 w-3.5 text-muted-foreground" />
|
|
<span className="font-medium text-foreground/80">
|
|
{data.language} execution
|
|
</span>
|
|
{hasErrors ? (
|
|
<AlertTriangle className="h-3.5 w-3.5 text-amber-500" />
|
|
) : (
|
|
<CheckCircle2 className="h-3.5 w-3.5 text-green-500" />
|
|
)}
|
|
</div>
|
|
<div className="flex items-center gap-2 text-muted-foreground">
|
|
{duration_ms != null && <span>{duration_ms}ms</span>}
|
|
{hasOutput && (
|
|
<button
|
|
onClick={() => setOutputOpen((o) => !o)}
|
|
className="hover:text-foreground transition-colors"
|
|
>
|
|
{outputOpen ? <ChevronUp className="h-3.5 w-3.5" /> : <ChevronDown className="h-3.5 w-3.5" />}
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Output */}
|
|
{hasOutput && outputOpen && (
|
|
<div className="divide-y divide-border/30">
|
|
{data.stdout && (
|
|
<div className="px-3 py-2">
|
|
<div className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground/60 mb-1">
|
|
stdout
|
|
</div>
|
|
<pre className={cn(
|
|
'whitespace-pre-wrap break-all font-mono text-[11px] leading-relaxed',
|
|
'text-foreground/80 max-h-64 overflow-y-auto'
|
|
)}>
|
|
{data.stdout}
|
|
</pre>
|
|
</div>
|
|
)}
|
|
{data.stderr && (
|
|
<div className="px-3 py-2">
|
|
<div className="text-[10px] font-semibold uppercase tracking-wider text-amber-600/70 mb-1">
|
|
stderr
|
|
</div>
|
|
<pre className={cn(
|
|
'whitespace-pre-wrap break-all font-mono text-[11px] leading-relaxed',
|
|
'text-amber-700 dark:text-amber-400 max-h-32 overflow-y-auto'
|
|
)}>
|
|
{data.stderr}
|
|
</pre>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{/* Files */}
|
|
{hasFiles && (
|
|
<div className={cn('px-3 py-2', hasOutput && outputOpen && 'border-t border-border/30')}>
|
|
<div className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground/60 mb-1.5">
|
|
Generated files
|
|
</div>
|
|
<div className="flex flex-wrap gap-2">
|
|
{data.files.map((file) => (
|
|
<a
|
|
key={file.id}
|
|
href={`/api/v1/code-interpreter/files/${file.session_id}/${file.id}`}
|
|
download={file.name}
|
|
className={cn(
|
|
'flex items-center gap-1.5 rounded-md border border-border/50 px-2.5 py-1',
|
|
'text-[11px] font-medium text-foreground/70 hover:text-foreground hover:bg-muted/50',
|
|
'transition-colors'
|
|
)}
|
|
>
|
|
<Download className="h-3 w-3" />
|
|
{file.name}
|
|
</a>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{!hasOutput && !hasFiles && (
|
|
<div className="px-3 py-2 text-muted-foreground italic">
|
|
Code executed successfully (no output)
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|