refactor: revert tab files to main and restore tab disabling

This commit is contained in:
Devin AI
2024-12-09 10:36:56 +00:00
parent d857e1462b
commit b39c96de7c
4 changed files with 312 additions and 353 deletions

View File

@@ -509,15 +509,15 @@ const App = () => {
className="w-full p-4" className="w-full p-4"
> >
<TabsList className="mb-4 p-0"> <TabsList className="mb-4 p-0">
<TabsTrigger value="resources"> <TabsTrigger value="resources" disabled={!serverCapabilities?.resources}>
<Files className="w-4 h-4 mr-2" /> <Files className="w-4 h-4 mr-2" />
Resources Resources
</TabsTrigger> </TabsTrigger>
<TabsTrigger value="prompts"> <TabsTrigger value="prompts" disabled={!serverCapabilities?.prompts}>
<MessageSquare className="w-4 h-4 mr-2" /> <MessageSquare className="w-4 h-4 mr-2" />
Prompts Prompts
</TabsTrigger> </TabsTrigger>
<TabsTrigger value="tools"> <TabsTrigger value="tools" disabled={!serverCapabilities?.tools}>
<Hammer className="w-4 h-4 mr-2" /> <Hammer className="w-4 h-4 mr-2" />
Tools Tools
</TabsTrigger> </TabsTrigger>
@@ -525,7 +525,7 @@ const App = () => {
<Bell className="w-4 h-4 mr-2" /> <Bell className="w-4 h-4 mr-2" />
Ping Ping
</TabsTrigger> </TabsTrigger>
<TabsTrigger value="sampling" className="relative"> <TabsTrigger value="sampling" className="relative" disabled={!serverCapabilities?.sampling}>
<Hash className="w-4 h-4 mr-2" /> <Hash className="w-4 h-4 mr-2" />
Sampling Sampling
{pendingSampleRequests.length > 0 && ( {pendingSampleRequests.length > 0 && (
@@ -534,7 +534,7 @@ const App = () => {
</span> </span>
)} )}
</TabsTrigger> </TabsTrigger>
<TabsTrigger value="roots"> <TabsTrigger value="roots" disabled={!serverCapabilities?.roots}>
<FolderTree className="w-4 h-4 mr-2" /> <FolderTree className="w-4 h-4 mr-2" />
Roots Roots
</TabsTrigger> </TabsTrigger>

View File

@@ -6,9 +6,8 @@ import { TabsContent } from "@/components/ui/tabs";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { ListPromptsResult } from "@modelcontextprotocol/sdk/types.js"; import { ListPromptsResult } from "@modelcontextprotocol/sdk/types.js";
import { AlertCircle } from "lucide-react"; import { AlertCircle } from "lucide-react";
import { useState, useContext } from "react"; import { useState } from "react";
import ListPane from "./ListPane"; import ListPane from "./ListPane";
import { CapabilityContext } from "@/lib/contexts";
export type Prompt = { export type Prompt = {
name: string; name: string;
@@ -41,7 +40,6 @@ const PromptsTab = ({
nextCursor: ListPromptsResult["nextCursor"]; nextCursor: ListPromptsResult["nextCursor"];
error: string | null; error: string | null;
}) => { }) => {
const capabilities = useContext(CapabilityContext);
const [promptArgs, setPromptArgs] = useState<Record<string, string>>({}); const [promptArgs, setPromptArgs] = useState<Record<string, string>>({});
const handleInputChange = (argName: string, value: string) => { const handleInputChange = (argName: string, value: string) => {
@@ -56,98 +54,86 @@ const PromptsTab = ({
return ( return (
<TabsContent value="prompts" className="grid grid-cols-2 gap-4"> <TabsContent value="prompts" className="grid grid-cols-2 gap-4">
{!capabilities?.prompts ? ( <ListPane
<Alert variant="destructive" className="col-span-2"> items={prompts}
<AlertCircle className="h-4 w-4" /> listItems={listPrompts}
<AlertTitle>Feature Not Available</AlertTitle> clearItems={clearPrompts}
<AlertDescription> setSelectedItem={(prompt) => {
The connected server does not support prompts. setSelectedPrompt(prompt);
</AlertDescription> setPromptArgs({});
</Alert> }}
) : ( renderItem={(prompt) => (
<> <>
<ListPane <span className="flex-1">{prompt.name}</span>
items={prompts} <span className="text-sm text-gray-500">{prompt.description}</span>
listItems={listPrompts} </>
clearItems={clearPrompts} )}
setSelectedItem={(prompt) => { title="Prompts"
setSelectedPrompt(prompt); buttonText={nextCursor ? "List More Prompts" : "List Prompts"}
setPromptArgs({}); isButtonDisabled={!nextCursor && prompts.length > 0}
}} />
renderItem={(prompt) => (
<>
<span className="flex-1">{prompt.name}</span>
<span className="text-sm text-gray-500">{prompt.description}</span>
</>
)}
title="Prompts"
buttonText={nextCursor ? "List More Prompts" : "List Prompts"}
isButtonDisabled={!nextCursor && prompts.length > 0}
/>
<div className="bg-card rounded-lg shadow"> <div className="bg-card rounded-lg shadow">
<div className="p-4 border-b border-gray-200"> <div className="p-4 border-b border-gray-200">
<h3 className="font-semibold"> <h3 className="font-semibold">
{selectedPrompt ? selectedPrompt.name : "Select a prompt"} {selectedPrompt ? selectedPrompt.name : "Select a prompt"}
</h3> </h3>
</div> </div>
<div className="p-4"> <div className="p-4">
{error ? ( {error ? (
<Alert variant="destructive"> <Alert variant="destructive">
<AlertCircle className="h-4 w-4" /> <AlertCircle className="h-4 w-4" />
<AlertTitle>Error</AlertTitle> <AlertTitle>Error</AlertTitle>
<AlertDescription>{error}</AlertDescription> <AlertDescription>{error}</AlertDescription>
</Alert> </Alert>
) : selectedPrompt ? ( ) : selectedPrompt ? (
<div className="space-y-4"> <div className="space-y-4">
{selectedPrompt.description && ( {selectedPrompt.description && (
<p className="text-sm text-gray-600"> <p className="text-sm text-gray-600">
{selectedPrompt.description} {selectedPrompt.description}
</p>
)}
{selectedPrompt.arguments?.map((arg) => (
<div key={arg.name}>
<Label htmlFor={arg.name}>{arg.name}</Label>
<Input
id={arg.name}
placeholder={`Enter ${arg.name}`}
value={promptArgs[arg.name] || ""}
onChange={(e) =>
handleInputChange(arg.name, e.target.value)
}
/>
{arg.description && (
<p className="text-xs text-gray-500 mt-1">
{arg.description}
{arg.required && (
<span className="text-xs mt-1 ml-1">(Required)</span>
)}
</p> </p>
)} )}
{selectedPrompt.arguments?.map((arg) => (
<div key={arg.name}>
<Label htmlFor={arg.name}>{arg.name}</Label>
<Input
id={arg.name}
placeholder={`Enter ${arg.name}`}
value={promptArgs[arg.name] || ""}
onChange={(e) =>
handleInputChange(arg.name, e.target.value)
}
/>
{arg.description && (
<p className="text-xs text-gray-500 mt-1">
{arg.description}
{arg.required && (
<span className="text-xs mt-1 ml-1">(Required)</span>
)}
</p>
)}
</div>
))}
<Button onClick={handleGetPrompt} className="w-full">
Get Prompt
</Button>
{promptContent && (
<Textarea
value={promptContent}
readOnly
className="h-64 font-mono"
/>
)}
</div> </div>
) : ( ))}
<Alert> <Button onClick={handleGetPrompt} className="w-full">
<AlertDescription> Get Prompt
Select a prompt from the list to view and use it </Button>
</AlertDescription> {promptContent && (
</Alert> <Textarea
value={promptContent}
readOnly
className="h-64 font-mono"
/>
)} )}
</div> </div>
</div> ) : (
</> <Alert>
)} <AlertDescription>
Select a prompt from the list to view and use it
</AlertDescription>
</Alert>
)}
</div>
</div>
</TabsContent> </TabsContent>
); );
}; };

View File

@@ -10,8 +10,7 @@ import {
} from "@modelcontextprotocol/sdk/types.js"; } from "@modelcontextprotocol/sdk/types.js";
import { AlertCircle, ChevronRight, FileText, RefreshCw } from "lucide-react"; import { AlertCircle, ChevronRight, FileText, RefreshCw } from "lucide-react";
import ListPane from "./ListPane"; import ListPane from "./ListPane";
import { useState, useContext } from "react"; import { useState } from "react";
import { CapabilityContext } from "@/lib/contexts";
const ResourcesTab = ({ const ResourcesTab = ({
resources, resources,
@@ -42,7 +41,6 @@ const ResourcesTab = ({
nextTemplateCursor: ListResourceTemplatesResult["nextCursor"]; nextTemplateCursor: ListResourceTemplatesResult["nextCursor"];
error: string | null; error: string | null;
}) => { }) => {
const capabilities = useContext(CapabilityContext);
const [selectedTemplate, setSelectedTemplate] = const [selectedTemplate, setSelectedTemplate] =
useState<ResourceTemplate | null>(null); useState<ResourceTemplate | null>(null);
const [templateValues, setTemplateValues] = useState<Record<string, string>>( const [templateValues, setTemplateValues] = useState<Record<string, string>>(
@@ -64,153 +62,142 @@ const ResourcesTab = ({
const uri = fillTemplate(selectedTemplate.uriTemplate, templateValues); const uri = fillTemplate(selectedTemplate.uriTemplate, templateValues);
readResource(uri); readResource(uri);
setSelectedTemplate(null); setSelectedTemplate(null);
// We don't have the full Resource object here, so we create a partial one
setSelectedResource({ uri, name: uri } as Resource); setSelectedResource({ uri, name: uri } as Resource);
} }
}; };
return ( return (
<TabsContent value="resources" className="grid grid-cols-3 gap-4"> <TabsContent value="resources" className="grid grid-cols-3 gap-4">
{!capabilities?.resources ? ( <ListPane
<Alert variant="destructive" className="col-span-3"> items={resources}
<AlertCircle className="h-4 w-4" /> listItems={listResources}
<AlertTitle>Feature Not Available</AlertTitle> clearItems={clearResources}
<AlertDescription> setSelectedItem={(resource) => {
The connected server does not support resources. setSelectedResource(resource);
</AlertDescription> readResource(resource.uri);
</Alert> setSelectedTemplate(null);
) : ( }}
<> renderItem={(resource) => (
<ListPane <div className="flex items-center w-full">
items={resources} <FileText className="w-4 h-4 mr-2 flex-shrink-0 text-gray-500" />
listItems={listResources} <span className="flex-1 truncate" title={resource.uri.toString()}>
clearItems={clearResources} {resource.name}
setSelectedItem={(resource) => { </span>
setSelectedResource(resource); <ChevronRight className="w-4 h-4 flex-shrink-0 text-gray-400" />
readResource(resource.uri);
setSelectedTemplate(null);
}}
renderItem={(resource) => (
<div className="flex items-center w-full">
<FileText className="w-4 h-4 mr-2 flex-shrink-0 text-gray-500" />
<span className="flex-1 truncate" title={resource.uri.toString()}>
{resource.name}
</span>
<ChevronRight className="w-4 h-4 flex-shrink-0 text-gray-400" />
</div>
)}
title="Resources"
buttonText={nextCursor ? "List More Resources" : "List Resources"}
isButtonDisabled={!nextCursor && resources.length > 0}
/>
<ListPane
items={resourceTemplates}
listItems={listResourceTemplates}
clearItems={clearResourceTemplates}
setSelectedItem={(template) => {
setSelectedTemplate(template);
setSelectedResource(null);
setTemplateValues({});
}}
renderItem={(template) => (
<div className="flex items-center w-full">
<FileText className="w-4 h-4 mr-2 flex-shrink-0 text-gray-500" />
<span className="flex-1 truncate" title={template.uriTemplate}>
{template.name}
</span>
<ChevronRight className="w-4 h-4 flex-shrink-0 text-gray-400" />
</div>
)}
title="Resource Templates"
buttonText={
nextTemplateCursor ? "List More Templates" : "List Templates"
}
isButtonDisabled={!nextTemplateCursor && resourceTemplates.length > 0}
/>
<div className="bg-card rounded-lg shadow">
<div className="p-4 border-b border-gray-200 flex justify-between items-center">
<h3
className="font-semibold truncate"
title={selectedResource?.name || selectedTemplate?.name}
>
{selectedResource
? selectedResource.name
: selectedTemplate
? selectedTemplate.name
: "Select a resource or template"}
</h3>
{selectedResource && (
<Button
variant="outline"
size="sm"
onClick={() => readResource(selectedResource.uri)}
>
<RefreshCw className="w-4 h-4 mr-2" />
Refresh
</Button>
)}
</div>
<div className="p-4">
{error ? (
<Alert variant="destructive">
<AlertCircle className="h-4 w-4" />
<AlertTitle>Error</AlertTitle>
<AlertDescription>{error}</AlertDescription>
</Alert>
) : selectedResource ? (
<pre className="bg-gray-50 dark:bg-gray-800 p-4 rounded text-sm overflow-auto max-h-96 whitespace-pre-wrap break-words text-gray-900 dark:text-gray-100">
{resourceContent}
</pre>
) : selectedTemplate ? (
<div className="space-y-4">
<p className="text-sm text-gray-600">
{selectedTemplate.description}
</p>
{selectedTemplate.uriTemplate
.match(/{([^}]+)}/g)
?.map((param) => {
const key = param.slice(1, -1);
return (
<div key={key}>
<label
htmlFor={key}
className="block text-sm font-medium text-gray-700"
>
{key}
</label>
<Input
id={key}
value={templateValues[key] || ""}
onChange={(e) =>
setTemplateValues({
...templateValues,
[key]: e.target.value,
})
}
className="mt-1"
/>
</div>
);
})}
<Button
onClick={handleReadTemplateResource}
disabled={Object.keys(templateValues).length === 0}
>
Read Resource
</Button>
</div>
) : (
<Alert>
<AlertDescription>
Select a resource or template from the list to view its contents
</AlertDescription>
</Alert>
)}
</div>
</div> </div>
</> )}
)} title="Resources"
buttonText={nextCursor ? "List More Resources" : "List Resources"}
isButtonDisabled={!nextCursor && resources.length > 0}
/>
<ListPane
items={resourceTemplates}
listItems={listResourceTemplates}
clearItems={clearResourceTemplates}
setSelectedItem={(template) => {
setSelectedTemplate(template);
setSelectedResource(null);
setTemplateValues({});
}}
renderItem={(template) => (
<div className="flex items-center w-full">
<FileText className="w-4 h-4 mr-2 flex-shrink-0 text-gray-500" />
<span className="flex-1 truncate" title={template.uriTemplate}>
{template.name}
</span>
<ChevronRight className="w-4 h-4 flex-shrink-0 text-gray-400" />
</div>
)}
title="Resource Templates"
buttonText={
nextTemplateCursor ? "List More Templates" : "List Templates"
}
isButtonDisabled={!nextTemplateCursor && resourceTemplates.length > 0}
/>
<div className="bg-card rounded-lg shadow">
<div className="p-4 border-b border-gray-200 flex justify-between items-center">
<h3
className="font-semibold truncate"
title={selectedResource?.name || selectedTemplate?.name}
>
{selectedResource
? selectedResource.name
: selectedTemplate
? selectedTemplate.name
: "Select a resource or template"}
</h3>
{selectedResource && (
<Button
variant="outline"
size="sm"
onClick={() => readResource(selectedResource.uri)}
>
<RefreshCw className="w-4 h-4 mr-2" />
Refresh
</Button>
)}
</div>
<div className="p-4">
{error ? (
<Alert variant="destructive">
<AlertCircle className="h-4 w-4" />
<AlertTitle>Error</AlertTitle>
<AlertDescription>{error}</AlertDescription>
</Alert>
) : selectedResource ? (
<pre className="bg-gray-50 dark:bg-gray-800 p-4 rounded text-sm overflow-auto max-h-96 whitespace-pre-wrap break-words text-gray-900 dark:text-gray-100">
{resourceContent}
</pre>
) : selectedTemplate ? (
<div className="space-y-4">
<p className="text-sm text-gray-600">
{selectedTemplate.description}
</p>
{selectedTemplate.uriTemplate
.match(/{([^}]+)}/g)
?.map((param) => {
const key = param.slice(1, -1);
return (
<div key={key}>
<label
htmlFor={key}
className="block text-sm font-medium text-gray-700"
>
{key}
</label>
<Input
id={key}
value={templateValues[key] || ""}
onChange={(e) =>
setTemplateValues({
...templateValues,
[key]: e.target.value,
})
}
className="mt-1"
/>
</div>
);
})}
<Button
onClick={handleReadTemplateResource}
disabled={Object.keys(templateValues).length === 0}
>
Read Resource
</Button>
</div>
) : (
<Alert>
<AlertDescription>
Select a resource or template from the list to view its contents
</AlertDescription>
</Alert>
)}
</div>
</div>
</TabsContent> </TabsContent>
); );
}; };

View File

@@ -10,9 +10,8 @@ import {
CallToolResultSchema, CallToolResultSchema,
} from "@modelcontextprotocol/sdk/types.js"; } from "@modelcontextprotocol/sdk/types.js";
import { AlertCircle, Send } from "lucide-react"; import { AlertCircle, Send } from "lucide-react";
import { useEffect, useState, useContext } from "react"; import { useEffect, useState } from "react";
import ListPane from "./ListPane"; import ListPane from "./ListPane";
import { CapabilityContext } from "@/lib/contexts";
import { CompatibilityCallToolResult } from "@modelcontextprotocol/sdk/types.js"; import { CompatibilityCallToolResult } from "@modelcontextprotocol/sdk/types.js";
@@ -37,7 +36,6 @@ const ToolsTab = ({
nextCursor: ListToolsResult["nextCursor"]; nextCursor: ListToolsResult["nextCursor"];
error: string | null; error: string | null;
}) => { }) => {
const capabilities = useContext(CapabilityContext);
const [params, setParams] = useState<Record<string, unknown>>({}); const [params, setParams] = useState<Record<string, unknown>>({});
useEffect(() => { useEffect(() => {
setParams({}); setParams({});
@@ -112,122 +110,110 @@ const ToolsTab = ({
return ( return (
<TabsContent value="tools" className="grid grid-cols-2 gap-4"> <TabsContent value="tools" className="grid grid-cols-2 gap-4">
{!capabilities?.tools ? ( <ListPane
<Alert variant="destructive" className="col-span-2"> items={tools}
<AlertCircle className="h-4 w-4" /> listItems={listTools}
<AlertTitle>Feature Not Available</AlertTitle> clearItems={() => {
<AlertDescription> clearTools();
The connected server does not support tools. setSelectedTool(null);
</AlertDescription> }}
</Alert> setSelectedItem={setSelectedTool}
) : ( renderItem={(tool) => (
<> <>
<ListPane <span className="flex-1">{tool.name}</span>
items={tools} <span className="text-sm text-gray-500 text-right">
listItems={listTools} {tool.description}
clearItems={() => { </span>
clearTools(); </>
setSelectedTool(null); )}
}} title="Tools"
setSelectedItem={setSelectedTool} buttonText={nextCursor ? "List More Tools" : "List Tools"}
renderItem={(tool) => ( isButtonDisabled={!nextCursor && tools.length > 0}
<> />
<span className="flex-1">{tool.name}</span>
<span className="text-sm text-gray-500 text-right">
{tool.description}
</span>
</>
)}
title="Tools"
buttonText={nextCursor ? "List More Tools" : "List Tools"}
isButtonDisabled={!nextCursor && tools.length > 0}
/>
<div className="bg-card rounded-lg shadow"> <div className="bg-card rounded-lg shadow">
<div className="p-4 border-b border-gray-200"> <div className="p-4 border-b border-gray-200">
<h3 className="font-semibold"> <h3 className="font-semibold">
{selectedTool ? selectedTool.name : "Select a tool"} {selectedTool ? selectedTool.name : "Select a tool"}
</h3> </h3>
</div> </div>
<div className="p-4"> <div className="p-4">
{error ? ( {error ? (
<Alert variant="destructive"> <Alert variant="destructive">
<AlertCircle className="h-4 w-4" /> <AlertCircle className="h-4 w-4" />
<AlertTitle>Error</AlertTitle> <AlertTitle>Error</AlertTitle>
<AlertDescription>{error}</AlertDescription> <AlertDescription>{error}</AlertDescription>
</Alert> </Alert>
) : selectedTool ? ( ) : selectedTool ? (
<div className="space-y-4"> <div className="space-y-4">
<p className="text-sm text-gray-600"> <p className="text-sm text-gray-600">
{selectedTool.description} {selectedTool.description}
</p> </p>
{Object.entries(selectedTool.inputSchema.properties ?? []).map( {Object.entries(selectedTool.inputSchema.properties ?? []).map(
([key, value]) => ( ([key, value]) => (
<div key={key}> <div key={key}>
<Label <Label
htmlFor={key} htmlFor={key}
className="block text-sm font-medium text-gray-700" className="block text-sm font-medium text-gray-700"
> >
{key} {key}
</Label> </Label>
{ {
/* @ts-expect-error value type is currently unknown */ /* @ts-expect-error value type is currently unknown */
value.type === "string" ? ( value.type === "string" ? (
<Textarea <Textarea
id={key} id={key}
name={key} name={key}
// @ts-expect-error value type is currently unknown // @ts-expect-error value type is currently unknown
placeholder={value.description} placeholder={value.description}
onChange={(e) => onChange={(e) =>
setParams({ setParams({
...params, ...params,
[key]: e.target.value, [key]: e.target.value,
}) })
} }
className="mt-1" className="mt-1"
/> />
) : ( ) : (
<Input <Input
// @ts-expect-error value type is currently unknown // @ts-expect-error value type is currently unknown
type={value.type === "number" ? "number" : "text"} type={value.type === "number" ? "number" : "text"}
id={key} id={key}
name={key} name={key}
// @ts-expect-error value type is currently unknown // @ts-expect-error value type is currently unknown
placeholder={value.description} placeholder={value.description}
onChange={(e) => onChange={(e) =>
setParams({ setParams({
...params, ...params,
[key]: [key]:
// @ts-expect-error value type is currently unknown // @ts-expect-error value type is currently unknown
value.type === "number" value.type === "number"
? Number(e.target.value) ? Number(e.target.value)
: e.target.value, : e.target.value,
}) })
} }
className="mt-1" className="mt-1"
/> />
) )
} }
</div> </div>
), ),
)}
<Button onClick={() => callTool(selectedTool.name, params)}>
<Send className="w-4 h-4 mr-2" />
Run Tool
</Button>
{toolResult && renderToolResult()}
</div>
) : (
<Alert>
<AlertDescription>
Select a tool from the list to view its details and run it
</AlertDescription>
</Alert>
)} )}
<Button onClick={() => callTool(selectedTool.name, params)}>
<Send className="w-4 h-4 mr-2" />
Run Tool
</Button>
{toolResult && renderToolResult()}
</div> </div>
</div> ) : (
</> <Alert>
)} <AlertDescription>
Select a tool from the list to view its details and run it
</AlertDescription>
</Alert>
)}
</div>
</div>
</TabsContent> </TabsContent>
); );
}; };