Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
import { Alert, AlertDescription } from "@/components/ui/alert";
import { Button } from "@/components/ui/button";
import { useGetCoreConfigQuery } from "@/lib/store";
import { useCopyToClipboard } from "@/hooks/useCopyToClipboard";
import { Copy, InfoIcon, KeyRound } from "lucide-react";
import Link from "next/link";
import { useMemo } from "react";
import { toast } from "sonner";
import ContactUsView from "../views/contactUsView";

export default function APIKeysView() {
Expand All @@ -31,10 +31,7 @@ curl --location 'http://localhost:8080/v1/chat/completions'
]
}'`;

const copyToClipboard = (text: string) => {
navigator.clipboard.writeText(text);
toast.success("Copied to clipboard");
};
const { copy: copyToClipboard } = useCopyToClipboard();

if (isLoading) {
return <div>Loading...</div>;
Expand Down
29 changes: 10 additions & 19 deletions ui/app/workspace/logs/sheets/logDetailsSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
StatusColors,
} from "@/lib/constants/logs";
import { LogEntry } from "@/lib/types/logs";
import { useCopyToClipboard } from "@/hooks/useCopyToClipboard";
import { ChevronDown, ChevronUp, Clipboard, Loader2, MoreVertical, Trash2 } from "lucide-react";
import moment from "moment";
import { toast } from "sonner";
Expand Down Expand Up @@ -82,6 +83,8 @@ const isContainerOperation = (object: string) => {
};

export function LogDetailSheet({ log, open, onOpenChange, handleDelete, onNavigate, hasPrev = false, hasNext = false }: LogDetailSheetProps) {
const { copy: copyRequestId } = useCopyToClipboard({ successMessage: "Request ID copied" });
const { copy: copyBody } = useCopyToClipboard({ successMessage: "Request body copied to clipboard", errorMessage: "Failed to copy request body" });
const [fetchLog, { data: fullLog, isFetching }] = useLazyGetLogByIdQuery();

useEffect(() => {
Expand Down Expand Up @@ -147,12 +150,7 @@ export function LogDetailSheet({ log, open, onOpenChange, handleDelete, onNaviga
Request ID:{" "}
<code
className="text-normal cursor-pointer"
onClick={() => {
navigator.clipboard
.writeText(displayLog.id)
.then(() => toast.success("Request ID copied"))
.catch(() => toast.error("Failed to copy"));
}}
onClick={() => copyRequestId(displayLog.id)}
>
Comment thread
impoiler marked this conversation as resolved.
{displayLog.id}
</code>
Expand All @@ -177,22 +175,22 @@ export function LogDetailSheet({ log, open, onOpenChange, handleDelete, onNaviga
</SheetTitle>
</div>
<div className="flex items-center">
<Button variant="ghost" className="size-8" disabled={!hasPrev} onClick={() => onNavigate?.("prev")} aria-label="Previous log" data-testid="logdetails-prev-button">
<Button variant="ghost" className="size-8" disabled={!hasPrev} onClick={() => onNavigate?.("prev")} aria-label="Previous log" data-testid="logdetails-prev-button" type="button">
<ChevronUp className="size-4" />
</Button>
<Button variant="ghost" className="size-8" disabled={!hasNext} onClick={() => onNavigate?.("next")} aria-label="Next log" data-testid="logdetails-next-button">
<Button variant="ghost" className="size-8" disabled={!hasNext} onClick={() => onNavigate?.("next")} aria-label="Next log" data-testid="logdetails-next-button" type="button">
<ChevronDown className="size-4" />
</Button>
</div>
<AlertDialog>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="size-8">
<Button variant="ghost" className="size-8" type="button">
<MoreVertical className="h-3 w-3" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => copyRequestBody(displayLog)} data-testid="logdetails-copy-request-body-button">
<DropdownMenuItem onClick={() => copyRequestBody(displayLog, copyBody)} data-testid="logdetails-copy-request-body-button">
<Clipboard className="h-4 w-4" />
Copy request body
</DropdownMenuItem>
Expand Down Expand Up @@ -898,7 +896,7 @@ const normalizeObjectForCopy = (object: string | undefined): string => {
return mapping[normalized] ?? normalized;
};

const copyRequestBody = async (log: LogEntry) => {
const copyRequestBody = async (log: LogEntry, copy: (text: string) => Promise<void>) => {
try {
// Check if request is for responses, chat, speech, text completion, or embedding (exclude transcriptions)
const object = normalizeObjectForCopy(log.object);
Expand Down Expand Up @@ -1007,14 +1005,7 @@ const copyRequestBody = async (log: LogEntry) => {
}

const requestBodyJson = JSON.stringify(requestBody, null, 2);
navigator.clipboard
.writeText(requestBodyJson)
.then(() => {
toast.success("Request body copied to clipboard");
})
.catch((error) => {
toast.error("Failed to copy request body");
});
await copy(requestBodyJson);
} catch (error) {
toast.error("Failed to copy request body");
}
Expand Down
13 changes: 3 additions & 10 deletions ui/app/workspace/logs/views/collapsibleBox.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Button } from "@/components/ui/button";
import { useCopyToClipboard } from "@/hooks/useCopyToClipboard";
import { ChevronDown, ChevronUp, Copy } from "lucide-react";
import { useEffect, useRef, useState } from "react";
import { toast } from "sonner";

interface CollapsibleBoxProps {
title: string;
Expand All @@ -15,6 +15,7 @@ export default function CollapsibleBox({ title, children, collapsedHeight = 60,
const [isExpanded, setIsExpanded] = useState(false);
const [needsExpansion, setNeedsExpansion] = useState(false);
const innerContentRef = useRef<HTMLDivElement>(null);
const { copy } = useCopyToClipboard();

useEffect(() => {
if (!innerContentRef.current) return;
Expand All @@ -39,15 +40,7 @@ export default function CollapsibleBox({ title, children, collapsedHeight = 60,

const handleCopy = () => {
if (!onCopy) return;

navigator.clipboard
.writeText(onCopy())
.then(() => {
toast.success("Copied to clipboard");
})
.catch(() => {
toast.error("Failed to copy");
});
copy(onCopy());
};

return (
Expand Down
9 changes: 3 additions & 6 deletions ui/app/workspace/logs/views/emptyState.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ 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";
import { toast } from "sonner";

type Provider = "openai" | "anthropic" | "genai" | "litellm" | "langchain";
type Language = "python" | "typescript";
Expand Down Expand Up @@ -42,10 +42,7 @@ interface CodeBlockProps {
}

function CodeBlock({ code, language, onLanguageChange, showLanguageSelect = false, readonly = true }: CodeBlockProps) {
const copyToClipboard = () => {
navigator.clipboard.writeText(code);
toast.success("Copied to clipboard");
};
const { copy: copyToClipboard } = useCopyToClipboard();

return (
<div className="relative">
Expand All @@ -65,7 +62,7 @@ function CodeBlock({ code, language, onLanguageChange, showLanguageSelect = fals
</SelectContent>
</Select>
)}
<Button variant="ghost" size="icon" onClick={copyToClipboard}>
<Button variant="ghost" size="icon" onClick={() => copyToClipboard(code)}>
<Copy className="size-4" />
</Button>
</div>
Expand Down
9 changes: 3 additions & 6 deletions ui/app/workspace/mcp-logs/views/emptyState.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ 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";
import { toast } from "sonner";

type Language = "python" | "typescript";

Expand Down Expand Up @@ -41,10 +41,7 @@ interface CodeBlockProps {
}

function CodeBlock({ code, language, onLanguageChange, showLanguageSelect = false, readonly = true }: CodeBlockProps) {
const copyToClipboard = () => {
navigator.clipboard.writeText(code);
toast.success("Copied to clipboard");
};
const { copy: copyToClipboard } = useCopyToClipboard();

return (
<div className="relative">
Expand All @@ -64,7 +61,7 @@ function CodeBlock({ code, language, onLanguageChange, showLanguageSelect = fals
</SelectContent>
</Select>
)}
<Button variant="ghost" size="icon" onClick={copyToClipboard} aria-label="Copy to clipboard">
<Button variant="ghost" size="icon" onClick={() => copyToClipboard(code)} aria-label="Copy to clipboard">
<Copy className="size-4" />
</Button>
</div>
Expand Down
6 changes: 3 additions & 3 deletions ui/app/workspace/mcp-logs/views/mcpLogDetailsSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,17 +83,17 @@ export function MCPLogDetailSheet({ log, open, onOpenChange, handleDelete, onNav
</SheetTitle>
</div>
<div className="flex items-center">
<Button variant="ghost" className="size-8" disabled={!hasPrev} onClick={() => onNavigate?.("prev")} aria-label="Previous log" data-testid="mcp-log-nav-prev">
<Button variant="ghost" className="size-8" disabled={!hasPrev} onClick={() => onNavigate?.("prev")} aria-label="Previous log" data-testid="mcp-log-nav-prev" type="button">
<ChevronUp className="size-4" />
</Button>
<Button variant="ghost" className="size-8" disabled={!hasNext} onClick={() => onNavigate?.("next")} aria-label="Next log" data-testid="mcp-log-nav-next">
<Button variant="ghost" className="size-8" disabled={!hasNext} onClick={() => onNavigate?.("next")} aria-label="Next log" data-testid="mcp-log-nav-next" type="button">
<ChevronDown className="size-4" />
</Button>
</div>
<AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="size-8">
<Button variant="ghost" className="size-8" type="button">
<MoreVertical className="h-3 w-3" />
</Button>
</DropdownMenuTrigger>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { prometheusFormSchema, type PrometheusFormSchema } from "@/lib/types/sch
import { RbacOperation, RbacResource, useRbac } from "@enterprise/lib";
import { zodResolver } from "@hookform/resolvers/zod";
import { Switch } from "@/components/ui/switch";
import { useCopyToClipboard } from "@/hooks/useCopyToClipboard";
import { AlertTriangle, Copy, Eye, EyeOff, Info, Plus, Trash, Trash2 } from "lucide-react";
import { useEffect, useState } from "react";
import { useForm, type Resolver } from "react-hook-form";
Expand Down Expand Up @@ -44,7 +45,7 @@ export function PrometheusFormFragment({
const hasPrometheusAccess = useRbac(RbacResource.Observability, RbacOperation.Update);
const [showPassword, setShowPassword] = useState(false);
const [isSaving, setIsSaving] = useState(false);
const [copied, setCopied] = useState(false);
const { copy, copied } = useCopyToClipboard();
const [showBasicAuth, setShowBasicAuth] = useState(!!(initialConfig?.basic_auth?.username || initialConfig?.basic_auth?.password));

const form = useForm<PrometheusFormSchema, any, PrometheusFormSchema>({
Expand Down Expand Up @@ -86,9 +87,7 @@ export function PrometheusFormFragment({

const handleCopyEndpoint = () => {
if (metricsEndpoint) {
navigator.clipboard.writeText(metricsEndpoint);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
copy(metricsEndpoint);
}
Comment thread
impoiler marked this conversation as resolved.
};

Expand Down
2 changes: 1 addition & 1 deletion ui/app/workspace/plugins/sheets/pluginSequenceSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ export default function PluginSequenceSheet({ open, onClose, plugins }: PluginSe
<Button type="button" variant="outline" onClick={onClose} disabled={isLoading} data-testid="plugin-sequence-cancel-button">
Cancel
</Button>
<Button onClick={handleSave} disabled={isLoading} isLoading={isLoading} data-testid="plugin-sequence-save-button">
<Button onClick={handleSave} disabled={isLoading} isLoading={isLoading} data-testid="plugin-sequence-save-button" type="button">
Save Sequence
</Button>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { Textarea } from "@/components/ui/textarea";
import { getRoutingFields } from "@/lib/config/celFieldsRouting";
import { celOperatorsRouting } from "@/lib/config/celOperatorsRouting";
import { convertRuleGroupToCEL } from "@/lib/utils/celConverterRouting";
import { useCopyToClipboard } from "@/hooks/useCopyToClipboard";
import { Check, Copy, Loader2 } from "lucide-react";
import { useEffect, useMemo, useRef, useState } from "react";
import { Field, QueryBuilder, RuleGroupType } from "react-querybuilder";
Expand Down Expand Up @@ -46,7 +47,7 @@ export function CELRuleBuilder({
}: CELRuleBuilderProps) {
const [query, setQuery] = useState<RuleGroupType>(initialQuery || defaultQuery);
const [celExpression, setCelExpression] = useState("");
const [copied, setCopied] = useState(false);
const { copy, copied } = useCopyToClipboard();
const onChangeRef = useRef(onChange);

// Keep ref updated so the query effect always invokes the latest callback
Expand All @@ -69,11 +70,7 @@ export function CELRuleBuilder({
onChangeRef.current?.(expression, query);
}, [query]);

const handleCopy = async () => {
await navigator.clipboard.writeText(celExpression);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
const handleCopy = () => copy(celExpression);

// Show loading state
if (isLoading) {
Expand Down Expand Up @@ -122,7 +119,7 @@ export function CELRuleBuilder({
<div className="space-y-2">
<div className="flex items-center justify-between">
<Label>CEL Expression Preview</Label>
<Button variant="outline" size="sm" onClick={handleCopy} disabled={!celExpression} className="gap-2">
<Button variant="outline" size="sm" onClick={handleCopy} disabled={!celExpression} className="gap-2" type="button">
{copied ? (
<>
<Check className="h-4 w-4" />
Expand Down
6 changes: 2 additions & 4 deletions ui/app/workspace/virtual-keys/views/virtualKeysTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { resetDurationLabels } from "@/lib/constants/governance"
import { cn } from "@/lib/utils"
import { formatCurrency } from "@/lib/utils/governance"
import { RbacOperation, RbacResource, useRbac } from "@enterprise/lib"
import { useCopyToClipboard } from "@/hooks/useCopyToClipboard"
import { ChevronLeft, ChevronRight, Copy, Edit, Eye, EyeOff, Plus, Search, Trash2 } from "lucide-react"
import { useMemo, useState } from "react"
import { toast } from "sonner"
Expand Down Expand Up @@ -136,10 +137,7 @@ export default function VirtualKeysTable({
return key.substring(0, 8) + "•".repeat(Math.max(0, key.length - 8));
};

const copyToClipboard = (key: string) => {
navigator.clipboard.writeText(key);
toast.success("Copied to clipboard");
};
const { copy: copyToClipboard } = useCopyToClipboard();

const hasActiveFilters = debouncedSearch || customerFilter || teamFilter;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,10 @@ export function AssistantMessageView({
) : (
<div
className={!disabled && !isStreaming ? "cursor-text" : undefined}
onClick={() => {
if (!disabled && !isStreaming) setEditMode(true);
onClick={(e) => {
if (disabled || isStreaming || editMode) return;
if ((e.target as HTMLElement).closest("button, a, [role='button']")) return;
setEditMode(true);
}}
>
<Markdown content={content} isStreaming={isStreaming} className="text-muted-foreground" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ export function SystemMessageView({
</div>
</div>

<div onClick={() => { if (!disabled && !editMode) setEditMode(true); }} className={!disabled && !editMode ? "cursor-text" : ""}>
<div onClick={(e) => { if (!disabled && !editMode && !(e.target as HTMLElement).closest("button, a, [role='button']")) setEditMode(true); }} className={!disabled && !editMode ? "cursor-text" : ""}>
{editMode ? (
<RichTextarea
autoFocus
Expand Down Expand Up @@ -168,8 +168,10 @@ export function SystemMessageView({
) : (
<div
className={!disabled ? "cursor-text" : undefined}
onClick={() => {
if (!disabled) setEditMode(true);
onClick={(e) => {
if (disabled || editMode) return;
if ((e.target as HTMLElement).closest("button, a, [role='button']")) return;
setEditMode(true);
}}
>
<Markdown content={content} className="text-muted-foreground" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -286,8 +286,10 @@ export function UserMessageView({
) : (
<div
className={!disabled ? "cursor-text" : undefined}
onClick={() => {
if (!disabled) setEditMode(true);
onClick={(e) => {
if (disabled || editMode) return;
if ((e.target as HTMLElement).closest("button, a, [role='button']")) return;
setEditMode(true);
}}
>
<Markdown content={content} className="text-muted-foreground" />
Expand Down
Loading
Loading