Skip to content

Commit 3e7c87a

Browse files
create sampling response form
1 parent f9cb2c1 commit 3e7c87a

File tree

4 files changed

+426
-27
lines changed

4 files changed

+426
-27
lines changed
+211
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
import { Button } from "@/components/ui/button";
2+
import JsonView from "./JsonView";
3+
import { Textarea } from "@/components/ui/textarea";
4+
import {
5+
Select,
6+
SelectContent,
7+
SelectItem,
8+
SelectTrigger,
9+
SelectValue,
10+
} from "@/components/ui/select";
11+
import { useState } from "react";
12+
import { Combobox } from "./ui/combobox";
13+
import { CreateMessageResult } from "@modelcontextprotocol/sdk/types.js";
14+
import { PendingRequest } from "./SamplingTab";
15+
import { Label } from "@/components/ui/label";
16+
17+
export type SamplingRequestProps = {
18+
request: PendingRequest;
19+
onApprove: (id: number, result: CreateMessageResult) => void;
20+
onReject: (id: number) => void;
21+
};
22+
23+
const SamplingRequest = ({
24+
onApprove,
25+
request,
26+
onReject,
27+
}: SamplingRequestProps) => {
28+
const [messageResult, setMessageResult] = useState<CreateMessageResult>({
29+
model: "GPT-4o",
30+
stopReason: "endTurn",
31+
role: "assistant",
32+
content: {
33+
type: "text",
34+
text: "",
35+
},
36+
});
37+
38+
const handleApprove = (id: number) => {
39+
onApprove(id, messageResult);
40+
};
41+
42+
return (
43+
<div
44+
data-testid="sampling-request"
45+
className="flex gap-4 p-4 border rounded-lg space-y-4"
46+
>
47+
<div className="flex-1 bg-gray-50 dark:bg-gray-800 dark:text-gray-100 p-2 rounded">
48+
<JsonView data={JSON.stringify(request.request)} />
49+
</div>
50+
<form className="flex-1 space-y-4">
51+
<div className="space-y-2">
52+
<Label htmlFor="model">Model</Label>
53+
<Combobox
54+
id="model"
55+
placeholder="Enter model"
56+
value={(messageResult.model as string) ?? ""}
57+
onChange={(value) =>
58+
setMessageResult({ ...messageResult, model: value })
59+
}
60+
onInputChange={(value) =>
61+
setMessageResult({ ...messageResult, model: value })
62+
}
63+
options={[
64+
"Claude 3.5 Sonnet (Preview)",
65+
"Claude 3.7 Sonnet (Preview)",
66+
"Claude 3.7 Sonnet Thinking (Preview)",
67+
"Gemini 2.0 Flash (Preview)",
68+
"GPT-4o",
69+
"o1 (Preview)",
70+
"o3-mini (Preview)",
71+
]}
72+
/>
73+
</div>
74+
<div className="space-y-2">
75+
<Label htmlFor="stopReason">Stop reason</Label>
76+
<Combobox
77+
id="stopReason"
78+
placeholder="Enter stop reason"
79+
value={(messageResult.stopReason as string) ?? ""}
80+
onChange={(value) =>
81+
setMessageResult({ ...messageResult, stopReason: value })
82+
}
83+
onInputChange={(value) =>
84+
setMessageResult({ ...messageResult, stopReason: value })
85+
}
86+
options={["endTurn", "stopSequence", "maxTokens"]}
87+
/>
88+
</div>
89+
<div className="space-y-2">
90+
<Label htmlFor="role">Role</Label>
91+
<Select
92+
value={(messageResult.role as string) ?? ""}
93+
onValueChange={(value: "assistant" | "user") =>
94+
setMessageResult({ ...messageResult, role: value })
95+
}
96+
>
97+
<SelectTrigger>
98+
<SelectValue placeholder="Select role" />
99+
</SelectTrigger>
100+
<SelectContent>
101+
<SelectItem value="assistant">Assistant</SelectItem>
102+
<SelectItem value="user">User</SelectItem>
103+
</SelectContent>
104+
</Select>
105+
</div>
106+
<div className="space-y-2">
107+
<Label htmlFor="type">Type</Label>
108+
<Select
109+
value={(messageResult.content.type as string) ?? ""}
110+
onValueChange={(value: string) => {
111+
setMessageResult({
112+
...messageResult,
113+
content:
114+
value === "text"
115+
? { type: "text", text: "" }
116+
: { type: "image", data: "", mimeType: "" },
117+
});
118+
}}
119+
>
120+
<SelectTrigger id="type">
121+
<SelectValue placeholder="Select role" />
122+
</SelectTrigger>
123+
<SelectContent>
124+
<SelectItem value="text">Text</SelectItem>
125+
<SelectItem value="image">Image</SelectItem>
126+
</SelectContent>
127+
</Select>
128+
</div>
129+
130+
{messageResult.content.type === "text" && (
131+
<div className="space-y-2">
132+
<Label htmlFor="text">Text</Label>
133+
134+
<Textarea
135+
id="text"
136+
name="text"
137+
placeholder="Context text"
138+
value={(messageResult.content.text as string) ?? ""}
139+
onChange={(e) =>
140+
setMessageResult({
141+
...messageResult,
142+
content: {
143+
...messageResult.content,
144+
text: e.target.value,
145+
},
146+
})
147+
}
148+
/>
149+
</div>
150+
)}
151+
{messageResult.content.type === "image" && (
152+
<>
153+
<div className="space-y-2">
154+
<Label htmlFor="mimeType">Mime type</Label>
155+
<Combobox
156+
id="mimeType"
157+
placeholder="Enter Mime Type"
158+
value={(messageResult.content.mimeType as string) ?? ""}
159+
onChange={(value) =>
160+
setMessageResult({
161+
...messageResult,
162+
content: { ...messageResult.content, mimeType: value },
163+
})
164+
}
165+
onInputChange={(value) =>
166+
setMessageResult({
167+
...messageResult,
168+
content: { ...messageResult.content, mimeType: value },
169+
})
170+
}
171+
options={["image/jpeg", "image/png", "image/webp"]}
172+
/>
173+
</div>
174+
<div className="space-y-2">
175+
<Label htmlFor="data">Data (base64)</Label>
176+
<Textarea
177+
id="data"
178+
name="data"
179+
placeholder="Data (base64)"
180+
value={(messageResult.content.data as string) ?? ""}
181+
onChange={(e) =>
182+
setMessageResult({
183+
...messageResult,
184+
content: {
185+
...messageResult.content,
186+
data: e.target.value,
187+
},
188+
})
189+
}
190+
/>
191+
</div>
192+
</>
193+
)}
194+
<div className="flex space-x-2 mt-1">
195+
<Button type="button" onClick={() => handleApprove(request.id)}>
196+
Approve
197+
</Button>
198+
<Button
199+
type="button"
200+
variant="outline"
201+
onClick={() => onReject(request.id)}
202+
>
203+
Reject
204+
</Button>
205+
</div>
206+
</form>
207+
</div>
208+
);
209+
};
210+
211+
export default SamplingRequest;

client/src/components/SamplingTab.tsx

+8-27
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import { Alert, AlertDescription } from "@/components/ui/alert";
2-
import { Button } from "@/components/ui/button";
32
import { TabsContent } from "@/components/ui/tabs";
43
import {
54
CreateMessageRequest,
65
CreateMessageResult,
76
} from "@modelcontextprotocol/sdk/types.js";
8-
import JsonView from "./JsonView";
7+
import SamplingRequest from "./SamplingRequest";
98

109
export type PendingRequest = {
1110
id: number;
@@ -19,21 +18,8 @@ export type Props = {
1918
};
2019

2120
const SamplingTab = ({ pendingRequests, onApprove, onReject }: Props) => {
22-
const handleApprove = (id: number) => {
23-
// For now, just return a stub response
24-
onApprove(id, {
25-
model: "stub-model",
26-
stopReason: "endTurn",
27-
role: "assistant",
28-
content: {
29-
type: "text",
30-
text: "This is a stub response.",
31-
},
32-
});
33-
};
34-
3521
return (
36-
<TabsContent value="sampling" className="h-96">
22+
<TabsContent value="sampling" className="mh-96">
3723
<Alert>
3824
<AlertDescription>
3925
When the server requests LLM sampling, requests will appear here for
@@ -43,17 +29,12 @@ const SamplingTab = ({ pendingRequests, onApprove, onReject }: Props) => {
4329
<div className="mt-4 space-y-4">
4430
<h3 className="text-lg font-semibold">Recent Requests</h3>
4531
{pendingRequests.map((request) => (
46-
<div key={request.id} className="p-4 border rounded-lg space-y-4">
47-
<div className="bg-gray-50 dark:bg-gray-800 dark:text-gray-100 p-2 rounded">
48-
<JsonView data={JSON.stringify(request.request)} />
49-
</div>
50-
<div className="flex space-x-2">
51-
<Button onClick={() => handleApprove(request.id)}>Approve</Button>
52-
<Button variant="outline" onClick={() => onReject(request.id)}>
53-
Reject
54-
</Button>
55-
</div>
56-
</div>
32+
<SamplingRequest
33+
key={request.id}
34+
request={request}
35+
onApprove={onApprove}
36+
onReject={onReject}
37+
/>
5738
))}
5839
{pendingRequests.length === 0 && (
5940
<p className="text-gray-500">No pending requests</p>

0 commit comments

Comments
 (0)