merge main

This commit is contained in:
Pulkit Sharma
2025-04-09 01:23:42 +05:30
6 changed files with 450 additions and 433 deletions

View File

@@ -3,14 +3,16 @@ import { Button } from "@/components/ui/button";
const PingTab = ({ onPingClick }: { onPingClick: () => void }) => { const PingTab = ({ onPingClick }: { onPingClick: () => void }) => {
return ( return (
<TabsContent value="ping" className="grid grid-cols-2 gap-4"> <TabsContent value="ping">
<div className="col-span-2 flex justify-center items-center"> <div className="grid grid-cols-2 gap-4">
<Button <div className="col-span-2 flex justify-center items-center">
onClick={onPingClick} <Button
className="font-bold py-6 px-12 rounded-full" onClick={onPingClick}
> className="font-bold py-6 px-12 rounded-full"
Ping Server >
</Button> Ping Server
</Button>
</div>
</div> </div>
</TabsContent> </TabsContent>
); );

View File

@@ -84,84 +84,88 @@ const PromptsTab = ({
}; };
return ( return (
<TabsContent value="prompts" className="grid grid-cols-2 gap-4"> <TabsContent value="prompts">
<ListPane <div className="grid grid-cols-2 gap-4">
items={prompts} <ListPane
listItems={listPrompts} items={prompts}
clearItems={clearPrompts} listItems={listPrompts}
setSelectedItem={(prompt) => { clearItems={clearPrompts}
setSelectedPrompt(prompt); setSelectedItem={(prompt) => {
setPromptArgs({}); setSelectedPrompt(prompt);
}} setPromptArgs({});
renderItem={(prompt) => ( }}
<> renderItem={(prompt) => (
<span className="flex-1">{prompt.name}</span> <>
<span className="text-sm text-gray-500">{prompt.description}</span> <span className="flex-1">{prompt.name}</span>
</> <span className="text-sm text-gray-500">
)} {prompt.description}
title="Prompts" </span>
buttonText={nextCursor ? "List More Prompts" : "List Prompts"} </>
isButtonDisabled={!nextCursor && prompts.length > 0}
/>
<div className="bg-card rounded-lg shadow">
<div className="p-4 border-b border-gray-200">
<h3 className="font-semibold">
{selectedPrompt ? selectedPrompt.name : "Select a prompt"}
</h3>
</div>
<div className="p-4">
{error ? (
<Alert variant="destructive">
<AlertCircle className="h-4 w-4" />
<AlertTitle>Error</AlertTitle>
<AlertDescription>{error}</AlertDescription>
</Alert>
) : selectedPrompt ? (
<div className="space-y-4">
{selectedPrompt.description && (
<p className="text-sm text-gray-600">
{selectedPrompt.description}
</p>
)}
{selectedPrompt.arguments?.map((arg) => (
<div key={arg.name}>
<Label htmlFor={arg.name}>{arg.name}</Label>
<Combobox
id={arg.name}
placeholder={`Enter ${arg.name}`}
value={promptArgs[arg.name] || ""}
onChange={(value) => handleInputChange(arg.name, value)}
onInputChange={(value) =>
handleInputChange(arg.name, value)
}
options={completions[arg.name] || []}
/>
{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 && (
<JsonView data={promptContent} withCopyButton={false} />
)}
</div>
) : (
<Alert>
<AlertDescription>
Select a prompt from the list to view and use it
</AlertDescription>
</Alert>
)} )}
title="Prompts"
buttonText={nextCursor ? "List More Prompts" : "List Prompts"}
isButtonDisabled={!nextCursor && prompts.length > 0}
/>
<div className="bg-card rounded-lg shadow">
<div className="p-4 border-b border-gray-200">
<h3 className="font-semibold">
{selectedPrompt ? selectedPrompt.name : "Select a prompt"}
</h3>
</div>
<div className="p-4">
{error ? (
<Alert variant="destructive">
<AlertCircle className="h-4 w-4" />
<AlertTitle>Error</AlertTitle>
<AlertDescription>{error}</AlertDescription>
</Alert>
) : selectedPrompt ? (
<div className="space-y-4">
{selectedPrompt.description && (
<p className="text-sm text-gray-600">
{selectedPrompt.description}
</p>
)}
{selectedPrompt.arguments?.map((arg) => (
<div key={arg.name}>
<Label htmlFor={arg.name}>{arg.name}</Label>
<Combobox
id={arg.name}
placeholder={`Enter ${arg.name}`}
value={promptArgs[arg.name] || ""}
onChange={(value) => handleInputChange(arg.name, value)}
onInputChange={(value) =>
handleInputChange(arg.name, value)
}
options={completions[arg.name] || []}
/>
{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 && (
<JsonView data={promptContent} withCopyButton={false} />
)}
</div>
) : (
<Alert>
<AlertDescription>
Select a prompt from the list to view and use it
</AlertDescription>
</Alert>
)}
</div>
</div> </div>
</div> </div>
</TabsContent> </TabsContent>

View File

@@ -111,155 +111,158 @@ const ResourcesTab = ({
}; };
return ( return (
<TabsContent value="resources" className="grid grid-cols-3 gap-4"> <TabsContent value="resources">
<ListPane <div className="grid grid-cols-3 gap-4">
items={resources} <ListPane
listItems={listResources} items={resources}
clearItems={clearResources} listItems={listResources}
setSelectedItem={(resource) => { clearItems={clearResources}
setSelectedResource(resource); setSelectedItem={(resource) => {
readResource(resource.uri); setSelectedResource(resource);
setSelectedTemplate(null); readResource(resource.uri);
}} setSelectedTemplate(null);
renderItem={(resource) => ( }}
<div className="flex items-center w-full"> renderItem={(resource) => (
<FileText className="w-4 h-4 mr-2 flex-shrink-0 text-gray-500" /> <div className="flex items-center w-full">
<span className="flex-1 truncate" title={resource.uri.toString()}> <FileText className="w-4 h-4 mr-2 flex-shrink-0 text-gray-500" />
{resource.name} <span className="flex-1 truncate" title={resource.uri.toString()}>
</span> {resource.name}
<ChevronRight className="w-4 h-4 flex-shrink-0 text-gray-400" /> </span>
</div> <ChevronRight className="w-4 h-4 flex-shrink-0 text-gray-400" />
)}
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 && (
<div className="flex row-auto gap-1 justify-end w-2/5">
{resourceSubscriptionsSupported &&
!resourceSubscriptions.has(selectedResource.uri) && (
<Button
variant="outline"
size="sm"
onClick={() => subscribeToResource(selectedResource.uri)}
>
Subscribe
</Button>
)}
{resourceSubscriptionsSupported &&
resourceSubscriptions.has(selectedResource.uri) && (
<Button
variant="outline"
size="sm"
onClick={() =>
unsubscribeFromResource(selectedResource.uri)
}
>
Unsubscribe
</Button>
)}
<Button
variant="outline"
size="sm"
onClick={() => readResource(selectedResource.uri)}
>
<RefreshCw className="w-4 h-4 mr-2" />
Refresh
</Button>
</div> </div>
)} )}
</div> title="Resources"
<div className="p-4"> buttonText={nextCursor ? "List More Resources" : "List Resources"}
{error ? ( isButtonDisabled={!nextCursor && resources.length > 0}
<Alert variant="destructive"> />
<AlertCircle className="h-4 w-4" />
<AlertTitle>Error</AlertTitle> <ListPane
<AlertDescription>{error}</AlertDescription> items={resourceTemplates}
</Alert> listItems={listResourceTemplates}
) : selectedResource ? ( clearItems={clearResourceTemplates}
<JsonView setSelectedItem={(template) => {
data={resourceContent} setSelectedTemplate(template);
className="bg-gray-50 dark:bg-gray-800 p-4 rounded text-sm overflow-auto max-h-96 text-gray-900 dark:text-gray-100" setSelectedResource(null);
/> setTemplateValues({});
) : selectedTemplate ? ( }}
<div className="space-y-4"> renderItem={(template) => (
<p className="text-sm text-gray-600"> <div className="flex items-center w-full">
{selectedTemplate.description} <FileText className="w-4 h-4 mr-2 flex-shrink-0 text-gray-500" />
</p> <span className="flex-1 truncate" title={template.uriTemplate}>
{selectedTemplate.uriTemplate {template.name}
.match(/{([^}]+)}/g) </span>
?.map((param) => { <ChevronRight className="w-4 h-4 flex-shrink-0 text-gray-400" />
const key = param.slice(1, -1);
return (
<div key={key}>
<Label htmlFor={key}>{key}</Label>
<Combobox
id={key}
placeholder={`Enter ${key}`}
value={templateValues[key] || ""}
onChange={(value) =>
handleTemplateValueChange(key, value)
}
onInputChange={(value) =>
handleTemplateValueChange(key, value)
}
options={completions[key] || []}
/>
</div>
);
})}
<Button
onClick={handleReadTemplateResource}
disabled={Object.keys(templateValues).length === 0}
>
Read Resource
</Button>
</div> </div>
) : (
<Alert>
<AlertDescription>
Select a resource or template from the list to view its contents
</AlertDescription>
</Alert>
)} )}
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 && (
<div className="flex row-auto gap-1 justify-end w-2/5">
{resourceSubscriptionsSupported &&
!resourceSubscriptions.has(selectedResource.uri) && (
<Button
variant="outline"
size="sm"
onClick={() => subscribeToResource(selectedResource.uri)}
>
Subscribe
</Button>
)}
{resourceSubscriptionsSupported &&
resourceSubscriptions.has(selectedResource.uri) && (
<Button
variant="outline"
size="sm"
onClick={() =>
unsubscribeFromResource(selectedResource.uri)
}
>
Unsubscribe
</Button>
)}
<Button
variant="outline"
size="sm"
onClick={() => readResource(selectedResource.uri)}
>
<RefreshCw className="w-4 h-4 mr-2" />
Refresh
</Button>
</div>
)}
</div>
<div className="p-4">
{error ? (
<Alert variant="destructive">
<AlertCircle className="h-4 w-4" />
<AlertTitle>Error</AlertTitle>
<AlertDescription>{error}</AlertDescription>
</Alert>
) : selectedResource ? (
<JsonView
data={resourceContent}
className="bg-gray-50 dark:bg-gray-800 p-4 rounded text-sm overflow-auto max-h-96 text-gray-900 dark:text-gray-100"
/>
) : 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}>{key}</Label>
<Combobox
id={key}
placeholder={`Enter ${key}`}
value={templateValues[key] || ""}
onChange={(value) =>
handleTemplateValueChange(key, value)
}
onInputChange={(value) =>
handleTemplateValueChange(key, value)
}
options={completions[key] || []}
/>
</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>
</div> </div>
</TabsContent> </TabsContent>

View File

@@ -35,40 +35,42 @@ const RootsTab = ({
}; };
return ( return (
<TabsContent value="roots" className="space-y-4"> <TabsContent value="roots">
<Alert> <div className="space-y-4">
<AlertDescription> <Alert>
Configure the root directories that the server can access <AlertDescription>
</AlertDescription> Configure the root directories that the server can access
</Alert> </AlertDescription>
</Alert>
{roots.map((root, index) => ( {roots.map((root, index) => (
<div key={index} className="flex gap-2 items-center"> <div key={index} className="flex gap-2 items-center">
<Input <Input
placeholder="file:// URI" placeholder="file:// URI"
value={root.uri} value={root.uri}
onChange={(e) => updateRoot(index, "uri", e.target.value)} onChange={(e) => updateRoot(index, "uri", e.target.value)}
className="flex-1" className="flex-1"
/> />
<Button <Button
variant="destructive" variant="destructive"
size="sm" size="sm"
onClick={() => removeRoot(index)} onClick={() => removeRoot(index)}
> >
<Minus className="h-4 w-4" /> <Minus className="h-4 w-4" />
</Button>
</div>
))}
<div className="flex gap-2">
<Button variant="outline" onClick={addRoot}>
<Plus className="h-4 w-4 mr-2" />
Add Root
</Button>
<Button onClick={handleSave}>
<Save className="h-4 w-4 mr-2" />
Save Changes
</Button> </Button>
</div> </div>
))}
<div className="flex gap-2">
<Button variant="outline" onClick={addRoot}>
<Plus className="h-4 w-4 mr-2" />
Add Root
</Button>
<Button onClick={handleSave}>
<Save className="h-4 w-4 mr-2" />
Save Changes
</Button>
</div> </div>
</TabsContent> </TabsContent>
); );

View File

@@ -33,33 +33,37 @@ const SamplingTab = ({ pendingRequests, onApprove, onReject }: Props) => {
}; };
return ( return (
<TabsContent value="sampling" className="h-96"> <TabsContent value="sampling">
<Alert> <div className="h-96">
<AlertDescription> <Alert>
When the server requests LLM sampling, requests will appear here for <AlertDescription>
approval. When the server requests LLM sampling, requests will appear here for
</AlertDescription> approval.
</Alert> </AlertDescription>
<div className="mt-4 space-y-4"> </Alert>
<h3 className="text-lg font-semibold">Recent Requests</h3> <div className="mt-4 space-y-4">
{pendingRequests.map((request) => ( <h3 className="text-lg font-semibold">Recent Requests</h3>
<div key={request.id} className="p-4 border rounded-lg space-y-4"> {pendingRequests.map((request) => (
<JsonView <div key={request.id} className="p-4 border rounded-lg space-y-4">
className="bg-gray-50 dark:bg-gray-800 dark:text-gray-100 rounded" <JsonView
data={JSON.stringify(request.request)} className="bg-gray-50 dark:bg-gray-800 dark:text-gray-100 rounded"
/> data={JSON.stringify(request.request)}
/>
<div className="flex space-x-2"> <div className="flex space-x-2">
<Button onClick={() => handleApprove(request.id)}>Approve</Button> <Button onClick={() => handleApprove(request.id)}>
<Button variant="outline" onClick={() => onReject(request.id)}> Approve
Reject </Button>
</Button> <Button variant="outline" onClick={() => onReject(request.id)}>
Reject
</Button>
</div>
</div> </div>
</div> ))}
))} {pendingRequests.length === 0 && (
{pendingRequests.length === 0 && ( <p className="text-gray-500">No pending requests</p>
<p className="text-gray-500">No pending requests</p> )}
)} </div>
</div> </div>
</TabsContent> </TabsContent>
); );

View File

@@ -108,166 +108,168 @@ const ToolsTab = ({
}; };
return ( return (
<TabsContent value="tools" className="grid grid-cols-2 gap-4"> <TabsContent value="tools">
<ListPane <div className="grid grid-cols-2 gap-4">
items={tools} <ListPane
listItems={listTools} items={tools}
clearItems={() => { listItems={listTools}
clearTools(); clearItems={() => {
setSelectedTool(null); clearTools();
}} setSelectedTool(null);
setSelectedItem={setSelectedTool} }}
renderItem={(tool) => ( setSelectedItem={setSelectedTool}
<> renderItem={(tool) => (
<span className="flex-1">{tool.name}</span> <>
<span className="text-sm text-gray-500 text-right"> <span className="flex-1">{tool.name}</span>
{tool.description} <span className="text-sm text-gray-500 text-right">
</span> {tool.description}
</> </span>
)} </>
title="Tools" )}
buttonText={nextCursor ? "List More Tools" : "List Tools"} title="Tools"
isButtonDisabled={!nextCursor && tools.length > 0} 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">
{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]) => {
const prop = value as JsonSchemaType; const prop = value as JsonSchemaType;
return ( return (
<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>
{prop.type === "boolean" ? ( {prop.type === "boolean" ? (
<div className="flex items-center space-x-2 mt-2"> <div className="flex items-center space-x-2 mt-2">
<Checkbox <Checkbox
id={key}
name={key}
checked={!!params[key]}
onCheckedChange={(checked: boolean) =>
setParams({
...params,
[key]: checked,
})
}
/>
<label
htmlFor={key}
className="text-sm font-medium text-gray-700 dark:text-gray-300"
>
{prop.description || "Toggle this option"}
</label>
</div>
) : prop.type === "string" ? (
<Textarea
id={key} id={key}
name={key} name={key}
checked={!!params[key]} placeholder={prop.description}
onCheckedChange={(checked: boolean) => value={(params[key] as string) ?? ""}
onChange={(e) =>
setParams({ setParams({
...params, ...params,
[key]: checked, [key]: e.target.value,
}) })
} }
className="mt-1"
/> />
<label ) : prop.type === "object" || prop.type === "array" ? (
htmlFor={key} <div className="mt-1">
className="text-sm font-medium text-gray-700 dark:text-gray-300" <DynamicJsonForm
> schema={{
{prop.description || "Toggle this option"} type: prop.type,
</label> properties: prop.properties,
</div> description: prop.description,
) : prop.type === "string" ? ( items: prop.items,
<Textarea }}
id={key} value={
name={key} (params[key] as JsonValue) ??
placeholder={prop.description} generateDefaultValue(prop)
value={(params[key] as string) ?? ""} }
onChange={(e) => onChange={(newValue: JsonValue) => {
setParams({ setParams({
...params, ...params,
[key]: e.target.value, [key]: newValue,
}) });
} }}
className="mt-1" />
/> </div>
) : prop.type === "object" || prop.type === "array" ? ( ) : (
<div className="mt-1"> <Input
<DynamicJsonForm type={
schema={{ prop.type === "number" || prop.type === "integer"
type: prop.type, ? "number"
properties: prop.properties, : "text"
description: prop.description,
items: prop.items,
}}
value={
(params[key] as JsonValue) ??
generateDefaultValue(prop)
} }
onChange={(newValue: JsonValue) => { id={key}
name={key}
placeholder={prop.description}
value={(params[key] as string) ?? ""}
onChange={(e) =>
setParams({ setParams({
...params, ...params,
[key]: newValue, [key]:
}); prop.type === "number" ||
}} prop.type === "integer"
? Number(e.target.value)
: e.target.value,
})
}
className="mt-1"
/> />
</div> )}
) : ( </div>
<Input );
type={ },
prop.type === "number" || prop.type === "integer"
? "number"
: "text"
}
id={key}
name={key}
placeholder={prop.description}
value={(params[key] as string) ?? ""}
onChange={(e) =>
setParams({
...params,
[key]:
prop.type === "number" ||
prop.type === "integer"
? Number(e.target.value)
: e.target.value,
})
}
className="mt-1"
/>
)}
</div>
);
},
)}
<Button
onClick={async () => {
try {
setIsToolRunning(true);
await callTool(selectedTool.name, params);
} finally {
setIsToolRunning(false);
}
}}
disabled={isToolRunning}
>
{isToolRunning ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
Running...
</>
) : (
<>
<Send className="w-4 h-4 mr-2" />
Run Tool
</>
)} )}
</Button> <Button
{toolResult && renderToolResult()} onClick={async () => {
</div> try {
) : ( setIsToolRunning(true);
<Alert> await callTool(selectedTool.name, params);
<AlertDescription> } finally {
Select a tool from the list to view its details and run it setIsToolRunning(false);
</AlertDescription> }
</Alert> }}
)} disabled={isToolRunning}
>
{isToolRunning ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
Running...
</>
) : (
<>
<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>
)}
</div>
</div> </div>
</div> </div>
</TabsContent> </TabsContent>