Files
bifrost/ui/app/workspace/mcp-logs/views/emptyState.tsx
Beyhan Oğur 880f412e2c first commit
2026-04-26 21:52:23 +03:00

321 lines
10 KiB
TypeScript

import { Alert, AlertDescription } from "@/components/ui/alert";
import { Button } from "@/components/ui/button";
import { CodeEditor } from "@/components/ui/codeEditor";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { getExampleBaseUrl } from "@/lib/utils/port";
import { useCopyToClipboard } from "@/hooks/useCopyToClipboard";
import { AlertTriangle, Copy } from "lucide-react";
import { useMemo, useState } from "react";
type Language = "python" | "typescript";
type Examples = {
manual: {
[L in Language]: string;
};
agentMode: {
[L in Language]: string;
};
};
// Common editor options to reduce duplication
const EditorOptions = {
scrollBeyondLastLine: false,
minimap: { enabled: false },
lineNumbers: "off",
folding: false,
lineDecorationsWidth: 0,
lineNumbersMinChars: 0,
glyphMargin: false,
} as const;
interface CodeBlockProps {
code: string;
language: string;
onLanguageChange?: (language: string) => void;
showLanguageSelect?: boolean;
readonly?: boolean;
}
function CodeBlock({ code, language, onLanguageChange, showLanguageSelect = false, readonly = true }: CodeBlockProps) {
const { copy: copyToClipboard } = useCopyToClipboard();
return (
<div className="relative">
<div className="absolute top-4 right-4 z-10 flex items-center gap-2">
{showLanguageSelect && onLanguageChange && (
<Select value={language} onValueChange={onLanguageChange}>
<SelectTrigger className="h-8 w-fit text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem className="text-xs" value="python">
Python
</SelectItem>
<SelectItem className="text-xs" value="typescript">
TypeScript
</SelectItem>
</SelectContent>
</Select>
)}
<Button variant="ghost" size="icon" onClick={() => copyToClipboard(code)} aria-label="Copy to clipboard">
<Copy className="size-4" />
</Button>
</div>
<CodeEditor className="w-full" code={code} lang={language} readonly={readonly} height={300} fontSize={14} options={EditorOptions} />
</div>
);
}
interface MCPEmptyStateProps {
error?: string | null;
statusIndicator?: React.ReactNode;
}
export function MCPEmptyState({ error, statusIndicator }: MCPEmptyStateProps) {
const [language, setLanguage] = useState<Language>("python");
// Generate examples dynamically using the port utility
const examples: Examples = useMemo(() => {
const baseUrl = getExampleBaseUrl();
return {
manual: {
python: `import openai
import requests
# Step 1: Initialize OpenAI client with Bifrost
client = openai.OpenAI(
base_url="${baseUrl}/openai",
api_key="dummy-api-key" # Handled by Bifrost
)
# Step 2: Send chat request
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "List files in current directory"}]
)
# Step 3: Check for tool calls
message = response.choices[0].message
if message.tool_calls:
for tool_call in message.tool_calls:
# Step 4: Execute tool via Bifrost
tool_result = requests.post(
"${baseUrl}/v1/mcp/tool/execute",
json={
"id": tool_call.id,
"type": "function",
"function": {
"name": tool_call.function.name,
"arguments": tool_call.function.arguments
}
}
).json()
# Step 5: Continue conversation with results
final_response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "user", "content": "List files in current directory"},
message,
tool_result
]
)
print(final_response.choices[0].message.content)`,
typescript: `import OpenAI from "openai";
// Step 1: Initialize OpenAI client with Bifrost
const openai = new OpenAI({
baseURL: "${baseUrl}/openai",
apiKey: "dummy-api-key", // Handled by Bifrost
});
// Step 2: Send chat request
const response = await openai.chat.completions.create({
model: "gpt-4o",
messages: [{ role: "user", content: "List files in current directory" }],
});
const message = response.choices[0].message;
// Step 3: Check for tool calls
if (message.tool_calls) {
for (const toolCall of message.tool_calls) {
// Step 4: Execute tool via Bifrost
const toolResult = await fetch("${baseUrl}/v1/mcp/tool/execute", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
id: toolCall.id,
type: "function",
function: {
name: toolCall.function.name,
arguments: toolCall.function.arguments,
},
}),
}).then(res => res.json());
// Step 5: Continue conversation with results
const finalResponse = await openai.chat.completions.create({
model: "gpt-4o",
messages: [
{ role: "user", content: "List files in current directory" },
message,
toolResult,
],
});
console.log(finalResponse.choices[0].message.content);
}
}`,
},
agentMode: {
python: `import openai
# Agent Mode enables autonomous tool execution
# Configure auto-executable tools in MCP Gateway settings
client = openai.OpenAI(
base_url="${baseUrl}/openai",
api_key="dummy-api-key"
)
# With agent mode enabled, Bifrost automatically:
# 1. Receives tool calls from LLM
# 2. Executes auto-approved tools (e.g., read_file, list_directory)
# 3. Feeds results back to LLM
# 4. Returns final response after all iterations
response = client.chat.completions.create(
model="gpt-4o",
messages=[{
"role": "user",
"content": "List all Python files and summarize their purpose"
}]
)
# The response includes results from all auto-executed tools
# Non-auto-executable tools (e.g., write_file) are returned for manual approval
print(response.choices[0].message.content)
# If there are pending non-auto-executable tools:
if response.choices[0].message.tool_calls:
print("Pending tools requiring approval:",
[tc.function.name for tc in response.choices[0].message.tool_calls])`,
typescript: `import OpenAI from "openai";
// Agent Mode enables autonomous tool execution
// Configure auto-executable tools in MCP Gateway settings
const openai = new OpenAI({
baseURL: "${baseUrl}/openai",
apiKey: "dummy-api-key",
});
// With agent mode enabled, Bifrost automatically:
// 1. Receives tool calls from LLM
// 2. Executes auto-approved tools (e.g., read_file, list_directory)
// 3. Feeds results back to LLM
// 4. Returns final response after all iterations
const response = await openai.chat.completions.create({
model: "gpt-4o",
messages: [{
role: "user",
content: "List all Python files and summarize their purpose"
}],
});
// The response includes results from all auto-executed tools
// Non-auto-executable tools (e.g., write_file) are returned for manual approval
console.log(response.choices[0].message.content);
// If there are pending non-auto-executable tools:
if (response.choices[0].message.tool_calls) {
console.log("Pending tools requiring approval:",
response.choices[0].message.tool_calls.map(tc => tc.function.name)
);
}`,
},
};
}, []);
const isUnexpectedError = error && error.includes("An unexpected error occurred");
return (
<div className="dark:bg-card flex w-full flex-col items-center justify-center space-y-8 bg-white">
{error && (
<Alert>
<AlertTriangle className="h-4 w-4" />
<AlertDescription>
{isUnexpectedError ? "Looks like you haven't configured the log store in your config file." : error}
</AlertDescription>
</Alert>
)}
<div className="w-full space-y-6">
<div className="flex flex-row items-center gap-2">
<div>
<h3 className="text-lg font-semibold">Get Started with MCP Tool Execution</h3>
<p className="text-muted-foreground text-sm">Execute your first MCP tool call to see logs appear</p>
</div>
<div className="ml-auto">{statusIndicator}</div>
</div>
<Tabs defaultValue="manual" className="w-full rounded-lg border">
<TabsList className="grid h-10 w-full grid-cols-2 rounded-t-lg rounded-b-none">
<TabsTrigger value="manual">Manual Tool Execution</TabsTrigger>
<TabsTrigger value="agent">Agent Mode (Auto-Execute)</TabsTrigger>
</TabsList>
<TabsContent value="manual" className="px-4">
<div className="text-muted-foreground mb-3 text-sm">
<p>Full control over tool approval. You explicitly execute each tool call via the API.</p>
</div>
<CodeBlock
code={examples.manual[language]}
language={language}
onLanguageChange={(newLang) => setLanguage(newLang as Language)}
showLanguageSelect
/>
</TabsContent>
<TabsContent value="agent" className="px-4">
<div className="text-muted-foreground mb-3 text-sm">
<p>Autonomous execution for pre-approved tools. Configure auto-executable tools in MCP Gateway settings.</p>
</div>
<CodeBlock
code={examples.agentMode[language]}
language={language}
onLanguageChange={(newLang) => setLanguage(newLang as Language)}
showLanguageSelect
/>
</TabsContent>
</Tabs>
<div className="bg-muted/50 rounded-lg border p-4">
<h4 className="mb-2 text-sm font-semibold">Prerequisites</h4>
<ul className="text-muted-foreground space-y-1 text-sm">
<li className="flex items-start gap-2">
<span className="text-primary">1.</span>
<span>Configure MCP servers in the MCP Gateway (e.g., filesystem, web_search)</span>
</li>
<li className="flex items-start gap-2">
<span className="text-primary">2.</span>
<span>
Set <code className="bg-muted rounded px-1">tools_to_execute</code> to whitelist available tools
</span>
</li>
<li className="flex items-start gap-2">
<span className="text-primary">3.</span>
<span>
For Agent Mode: Configure <code className="bg-muted rounded px-1">tools_to_auto_execute</code> for autonomous execution
</span>
</li>
</ul>
</div>
</div>
</div>
);
}