Skip to content

Commit a06098b

Browse files
feat: display variations in the dashboard (#581)
We now indicate which fields have been changed on a tool (if any) and show their original values <img width="850" height="300" alt="CleanShot 2025-10-16 at 13 09 18@2x" src="https://github.com/user-attachments/assets/ef3d9560-c007-4838-96f7-6d9a189ae84d" /> <img width="1242" height="696" alt="CleanShot 2025-10-16 at 13 09 07@2x" src="https://github.com/user-attachments/assets/5a89a369-15dd-497a-bfe7-c53dedd4151a" /> <img width="1224" height="1038" alt="CleanShot 2025-10-16 at 13 09 28@2x" src="https://github.com/user-attachments/assets/5c631c7a-9a58-45f3-97de-e7fb44096956" />
1 parent 660c110 commit a06098b

File tree

2 files changed

+106
-25
lines changed

2 files changed

+106
-25
lines changed

client/dashboard/src/components/tool-list/ToolList.tsx

Lines changed: 68 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
1-
import { TOOL_NAME_REGEX } from "@/lib/constants";
2-
import { cn } from "@/lib/utils";
3-
import { Tool, isHttpTool } from "@/lib/toolTypes";
4-
import { ChevronDown, FileCode, Layers, SquareFunction } from "lucide-react";
5-
import { MethodBadge } from "./MethodBadge";
6-
import { useState, useEffect, useMemo } from "react";
7-
import { MoreActions } from "@/components/ui/more-actions";
8-
import { Input } from "@/components/ui/input";
9-
import { TextArea } from "@/components/ui/textarea";
10-
import { Dialog } from "@/components/ui/dialog";
111
import { Button } from "@/components/ui/button";
122
import { Checkbox } from "@/components/ui/checkbox";
3+
import { Dialog } from "@/components/ui/dialog";
4+
import { Input } from "@/components/ui/input";
5+
import { MoreActions } from "@/components/ui/more-actions";
6+
import { TextArea } from "@/components/ui/textarea";
137
import { useCommandPalette } from "@/contexts/CommandPalette";
8+
import { TOOL_NAME_REGEX } from "@/lib/constants";
9+
import { Tool, isHttpTool } from "@/lib/toolTypes";
10+
import { cn } from "@/lib/utils";
1411
import { useLatestDeployment } from "@gram/client/react-query/index.js";
12+
import { Icon, Stack } from "@speakeasy-api/moonshine";
13+
import { ChevronDown, FileCode, Layers, SquareFunction } from "lucide-react";
14+
import { useEffect, useMemo, useState } from "react";
15+
import { ToolVariationBadge } from "../tool-variation-badge";
16+
import { Type } from "../ui/type";
17+
import { MethodBadge } from "./MethodBadge";
1518

1619
interface ToolListProps {
1720
tools: Tool[];
@@ -307,9 +310,12 @@ function ToolRow({
307310
)}
308311
/>
309312
<div className="flex flex-col min-w-0 flex-1">
310-
<p className="text-sm leading-6 text-foreground truncate">
311-
{tool.name}
312-
</p>
313+
<Stack direction="horizontal" gap={2} align="center">
314+
<p className="text-sm leading-6 text-foreground truncate">
315+
{tool.name}
316+
</p>
317+
<ToolVariationBadge tool={tool} />
318+
</Stack>
313319
<p className="text-sm leading-6 text-muted-foreground truncate">
314320
{tool.description || "No description"}
315321
</p>
@@ -337,23 +343,60 @@ function ToolRow({
337343
</Dialog.Header>
338344
<div className="space-y-4 py-4">
339345
{editType === "name" ? (
340-
<Input
341-
value={editValue}
342-
onChange={setEditValue}
343-
placeholder="Tool name"
344-
/>
346+
<Stack gap={2}>
347+
<Input
348+
value={editValue}
349+
onChange={setEditValue}
350+
placeholder="Tool name"
351+
/>
352+
{tool.variation?.name &&
353+
tool.variation?.name !== tool.canonical?.name && (
354+
<Stack direction="horizontal" gap={2} align="center">
355+
<Icon
356+
name="layers-2"
357+
size="small"
358+
className="text-muted-foreground/70"
359+
/>
360+
<Type small muted>
361+
Original name:
362+
</Type>
363+
<Type small muted>
364+
{tool.canonical?.name}
365+
</Type>
366+
</Stack>
367+
)}
368+
</Stack>
345369
) : (
346-
<TextArea
347-
value={editValue}
348-
onChange={setEditValue}
349-
placeholder="Tool description"
350-
rows={3}
351-
/>
370+
<Stack gap={2}>
371+
<TextArea
372+
value={editValue}
373+
onChange={setEditValue}
374+
placeholder="Tool description"
375+
rows={3}
376+
/>
377+
{tool.variation?.description &&
378+
tool.variation?.description !==
379+
tool.canonical?.description && (
380+
<Stack className="p-2 border rounded-md border-border/70">
381+
<Type small muted className="inline font-medium">
382+
<Icon
383+
name="layers-2"
384+
size="small"
385+
className="text-muted-foreground/70 inline align-text-bottom"
386+
/>{" "}
387+
Original Description
388+
</Type>
389+
<Type small muted>
390+
{tool.canonical?.description}
391+
</Type>
392+
</Stack>
393+
)}
394+
</Stack>
352395
)}
353396
{error && <p className="text-sm text-destructive">{error}</p>}
354397
</div>
355398
<Dialog.Footer>
356-
<Button variant="outline" onClick={() => setEditDialogOpen(false)}>
399+
<Button variant="ghost" onClick={() => setEditDialogOpen(false)}>
357400
Cancel
358401
</Button>
359402
<Button onClick={handleSave}>Save</Button>
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { Icon } from "@speakeasy-api/moonshine";
2+
import { SimpleTooltip } from "./ui/tooltip";
3+
import { Tool } from "@/lib/toolTypes";
4+
import { CanonicalToolAttributes } from "@gram/client/models/components";
5+
6+
export const ToolVariationBadge = ({ tool }: { tool: Tool }) => {
7+
if (!tool.variation) {
8+
return null;
9+
}
10+
11+
const excludedFields = [
12+
"createdAt",
13+
"updatedAt",
14+
"groupId",
15+
"id",
16+
"srcToolName",
17+
"srcToolUrn",
18+
];
19+
const fieldsChanged = Object.entries(tool.variation)
20+
.filter(
21+
([key, value]) =>
22+
!excludedFields.includes(key) &&
23+
value !== tool.canonical?.[key as keyof CanonicalToolAttributes],
24+
)
25+
.map(([key]) => key);
26+
27+
if (fieldsChanged.length === 0) {
28+
return null;
29+
}
30+
31+
return (
32+
<SimpleTooltip
33+
tooltip={`This tool has been modified. Fields changed: ${fieldsChanged.join(", ")}`}
34+
>
35+
<Icon name="layers-2" size="small" className="text-muted-foreground/70" />
36+
</SimpleTooltip>
37+
);
38+
};

0 commit comments

Comments
 (0)