Oliver-ai-bot_2.0/frontend/components/chat/code-execution-result.tsx
Vadym Samoilenko f2b5dce63a feat: code interpreter, agent analytics/execute APIs, usage sync, RAG scoping fixes
**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>
2026-03-30 20:13:27 +01:00

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>
);
}