From 7a6de62c82a8502fb7c170d08b4577b400af7c5a Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 30 May 2026 16:59:10 +0000 Subject: [PATCH] feat(ui): show coding-agent prompt after creating a virtual key After a key is created, the dialog now mirrors the Browser Use flow: title 'API Key Created', a yellow callout warning that the key is only shown once, the key itself with a copy icon, and a 'Prompt for coding agents' card containing a ready-to-paste prompt that bundles the proxy base URL, the new key, and links to the LiteLLM llms.txt docs. The primary 'Copy Virtual Key' button is preserved. The base URL is taken from proxyBaseUrl, falling back to window.location.origin when the UI is co-hosted with the proxy. e2e tests and the create-key-button mock are updated for the new heading and the WarningOutlined/CopyOutlined icons used by the revamped CreatedKeyDisplay. Co-authored-by: ishaan-berri --- .../e2e_tests/tests/proxy-admin/keys.spec.ts | 8 +- .../tests/team-admin/teamAdmin.spec.ts | 2 +- .../organisms/create_key_button.test.tsx | 2 + .../organisms/create_key_button.tsx | 17 +- .../shared/CreatedKeyDisplay.test.tsx | 51 ++++-- .../components/shared/CreatedKeyDisplay.tsx | 166 ++++++++++++++---- 6 files changed, 183 insertions(+), 63 deletions(-) diff --git a/ui/litellm-dashboard/e2e_tests/tests/proxy-admin/keys.spec.ts b/ui/litellm-dashboard/e2e_tests/tests/proxy-admin/keys.spec.ts index 1e44d9a25a0..d0f81eac7b3 100644 --- a/ui/litellm-dashboard/e2e_tests/tests/proxy-admin/keys.spec.ts +++ b/ui/litellm-dashboard/e2e_tests/tests/proxy-admin/keys.spec.ts @@ -41,8 +41,8 @@ test.describe("Proxy Admin - Keys", () => { // Submit await page.getByRole("button", { name: "Create Key", exact: true }).click(); - // Success shows "Save your Key" in a second dialog - await expect(page.getByText("Save your Key")).toBeVisible({ timeout: 10_000 }); + // Success shows "API Key Created" in a second dialog + await expect(page.getByText("API Key Created")).toBeVisible({ timeout: 10_000 }); await page.keyboard.press("Escape"); // Verify the new key appears in the table @@ -150,7 +150,7 @@ test.describe("Proxy Admin - Keys", () => { await page.getByRole("button", { name: "Create Key", exact: true }).click(); - await expect(page.getByText("Save your Key")).toBeVisible({ timeout: 10_000 }); + await expect(page.getByText("API Key Created")).toBeVisible({ timeout: 10_000 }); await page.keyboard.press("Escape"); await expect(page.getByText(keyName)).toBeVisible({ timeout: 10_000 }); @@ -182,7 +182,7 @@ test.describe("Proxy Admin - Keys", () => { await page.getByRole("button", { name: "Create Key", exact: true }).click(); - await expect(page.getByText("Save your Key")).toBeVisible({ timeout: 10_000 }); + await expect(page.getByText("API Key Created")).toBeVisible({ timeout: 10_000 }); // Grab the new key from the success modal (rendered inside a
) and
     // verify it can call /chat/completions for the model it was scoped to.
diff --git a/ui/litellm-dashboard/e2e_tests/tests/team-admin/teamAdmin.spec.ts b/ui/litellm-dashboard/e2e_tests/tests/team-admin/teamAdmin.spec.ts
index 1612e6929bd..aa33ce75654 100644
--- a/ui/litellm-dashboard/e2e_tests/tests/team-admin/teamAdmin.spec.ts
+++ b/ui/litellm-dashboard/e2e_tests/tests/team-admin/teamAdmin.spec.ts
@@ -109,7 +109,7 @@ test.describe("Team Admin", () => {
 
     await page.getByRole("button", { name: "Create Key", exact: true }).click();
 
-    await expect(page.getByText("Save your Key")).toBeVisible({ timeout: 10_000 });
+    await expect(page.getByText("API Key Created")).toBeVisible({ timeout: 10_000 });
     await page.keyboard.press("Escape");
 
     await expect(page.getByText(keyName)).toBeVisible({ timeout: 10_000 });
diff --git a/ui/litellm-dashboard/src/components/organisms/create_key_button.test.tsx b/ui/litellm-dashboard/src/components/organisms/create_key_button.test.tsx
index bd139df4833..27436a27b67 100644
--- a/ui/litellm-dashboard/src/components/organisms/create_key_button.test.tsx
+++ b/ui/litellm-dashboard/src/components/organisms/create_key_button.test.tsx
@@ -52,6 +52,8 @@ vi.mock("@/app/(dashboard)/hooks/keys/useKeys", () => ({
 
 vi.mock("@ant-design/icons", () => ({
   InfoCircleOutlined: () => null,
+  CopyOutlined: () => null,
+  WarningOutlined: () => null,
 }));
 
 vi.mock("react-copy-to-clipboard", () => ({
diff --git a/ui/litellm-dashboard/src/components/organisms/create_key_button.tsx b/ui/litellm-dashboard/src/components/organisms/create_key_button.tsx
index b590a7dc043..97b2f7e8ed9 100644
--- a/ui/litellm-dashboard/src/components/organisms/create_key_button.tsx
+++ b/ui/litellm-dashboard/src/components/organisms/create_key_button.tsx
@@ -1657,17 +1657,12 @@ const CreateKey: React.FC = ({ team, teams, data, addKey, autoOp
       )}
 
       {apiKey && (
-        
-          
-            Save your Key
-            
-              {apiKey != null ? (
-                
-              ) : (
-                Key being created, this might take 30s
-              )}
-            
-          
+        
+          {apiKey != null ? (
+            
+          ) : (
+            Key being created, this might take 30s
+          )}
         
       )}
     
diff --git a/ui/litellm-dashboard/src/components/shared/CreatedKeyDisplay.test.tsx b/ui/litellm-dashboard/src/components/shared/CreatedKeyDisplay.test.tsx
index eb6fe5f50cd..ed418221bfd 100644
--- a/ui/litellm-dashboard/src/components/shared/CreatedKeyDisplay.test.tsx
+++ b/ui/litellm-dashboard/src/components/shared/CreatedKeyDisplay.test.tsx
@@ -7,51 +7,74 @@ vi.mock("@/components/molecules/message_manager", () => ({
   default: { success: vi.fn(), error: vi.fn(), warning: vi.fn(), info: vi.fn(), loading: vi.fn(), destroy: vi.fn() },
 }));
 
+vi.mock("@/components/networking", () => ({
+  proxyBaseUrl: "https://litellm.example.com",
+}));
+
 import MessageManager from "@/components/molecules/message_manager";
 
 describe("CreatedKeyDisplay", () => {
   beforeEach(() => {
     vi.useFakeTimers({ shouldAdvanceTime: true });
+    vi.mocked(MessageManager.success).mockClear();
   });
 
   afterEach(() => {
     vi.useRealTimers();
   });
 
-  it("should render", () => {
+  it("renders the API key value", () => {
     render();
-    expect(screen.getByText("sk-test-123")).toBeInTheDocument();
+    expect(screen.getByTestId("created-key-value")).toHaveTextContent("sk-test-123");
   });
 
-  it("should display the security warning", () => {
+  it("displays the security warning and modal heading", () => {
     render();
-    expect(screen.getByText(/you will not be able to view it again/i)).toBeInTheDocument();
+    expect(screen.getByRole("heading", { name: /api key created/i })).toBeInTheDocument();
+    expect(
+      screen.getByText(/make sure to copy your api key now/i),
+    ).toBeInTheDocument();
+    expect(screen.getByText(/you won't be able to see it again/i)).toBeInTheDocument();
   });
 
-  it("should show the copy button with initial label", () => {
+  it("builds a coding agent prompt that embeds the key, base URL, and docs links", () => {
     render();
-    expect(screen.getByRole("button", { name: /copy virtual key/i })).toBeInTheDocument();
+    const prompt = screen.getByTestId("coding-agent-prompt").textContent ?? "";
+
+    expect(prompt).toMatch(/Base URL: https:\/\/litellm\.example\.com/);
+    expect(prompt).toMatch(/API key: sk-test-123/);
+    expect(prompt).toContain("Authorization: Bearer sk-test-123");
+    expect(prompt).toContain("https://litellm.example.com/v1/models");
+    expect(prompt).toContain("https://docs.litellm.ai/llms.txt");
+    expect(prompt).toContain("https://docs.litellm.ai/llms-full.txt");
   });
 
-  it("should change button text to Copied after clicking copy", async () => {
-    const user = userEvent.setup({ advanceTimers: vi.advanceTimersByTime });
+  it("does not produce a trailing slash in the base URL when proxyBaseUrl is provided", () => {
     render();
-
-    await user.click(screen.getByRole("button", { name: /copy virtual key/i }));
-
-    expect(screen.getByRole("button", { name: /copied/i })).toBeInTheDocument();
+    const prompt = screen.getByTestId("coding-agent-prompt").textContent ?? "";
+    expect(prompt).not.toMatch(/Base URL: https:\/\/litellm\.example\.com\//);
   });
 
-  it("should show a success message when the key is copied", async () => {
+  it("copies the API key to clipboard when the primary button is clicked", async () => {
     const user = userEvent.setup({ advanceTimers: vi.advanceTimersByTime });
     render();
 
     await user.click(screen.getByRole("button", { name: /copy virtual key/i }));
 
     expect(MessageManager.success).toHaveBeenCalledWith("Key copied to clipboard");
+    expect(screen.getByRole("button", { name: /copied/i })).toBeInTheDocument();
   });
 
-  it("should revert button text back after 2 seconds", async () => {
+  it("copies the coding agent prompt to clipboard when its copy button is clicked", async () => {
+    const user = userEvent.setup({ advanceTimers: vi.advanceTimersByTime });
+    render();
+
+    await user.click(screen.getByRole("button", { name: /copy prompt for coding agents/i }));
+
+    expect(MessageManager.success).toHaveBeenCalledWith("Prompt copied to clipboard");
+  });
+
+  it("reverts the primary button label after 2 seconds", async () => {
     const user = userEvent.setup({ advanceTimers: vi.advanceTimersByTime });
     render();
 
diff --git a/ui/litellm-dashboard/src/components/shared/CreatedKeyDisplay.tsx b/ui/litellm-dashboard/src/components/shared/CreatedKeyDisplay.tsx
index 00ffb8c6e20..6defe40d959 100644
--- a/ui/litellm-dashboard/src/components/shared/CreatedKeyDisplay.tsx
+++ b/ui/litellm-dashboard/src/components/shared/CreatedKeyDisplay.tsx
@@ -1,50 +1,150 @@
-import React, { useState } from "react";
+import React, { useMemo, useState } from "react";
 import { CopyToClipboard } from "react-copy-to-clipboard";
-import { Button } from "antd";
+import { Button, Tooltip } from "antd";
+import { CopyOutlined, WarningOutlined } from "@ant-design/icons";
 import MessageManager from "@/components/molecules/message_manager";
+import { proxyBaseUrl } from "@/components/networking";
 
 interface CreatedKeyDisplayProps {
   apiKey: string;
 }
 
-/**
- * Shared component for displaying a newly-created virtual key.
- * Used on the Virtual Keys page and in the Add Agent wizard.
- */
-const CreatedKeyDisplay: React.FC = ({ apiKey }) => {
-  const [copied, setCopied] = useState(false);
+const buildCodingAgentPrompt = (apiKey: string, baseUrl: string): string => {
+  return `You have access to LiteLLM, an OpenAI-compatible AI gateway that lets you call 100+ LLMs (OpenAI, Anthropic, Gemini, Bedrock, etc.) through a single API.
 
-  const handleCopy = () => {
-    setCopied(true);
+Use these credentials:
+- Base URL: ${baseUrl}
+- API key: ${apiKey}
+
+LiteLLM is a drop-in replacement for the OpenAI SDK. Point the SDK's \`base_url\` at the URL above and use the key as your \`OPENAI_API_KEY\`. To list the models available to this key, GET ${baseUrl}/v1/models with header \`Authorization: Bearer ${apiKey}\`.
+
+LiteLLM docs:
+- llms.txt (overview + all doc links): https://docs.litellm.ai/llms.txt
+- llms-full.txt (complete reference with inline code examples): https://docs.litellm.ai/llms-full.txt`;
+};
+
+const resolveProxyBaseUrl = (): string => {
+  if (proxyBaseUrl) return proxyBaseUrl.replace(/\/$/, "");
+  if (typeof window !== "undefined" && window.location?.origin) {
+    return window.location.origin;
+  }
+  return "https://your-litellm-proxy";
+};
+
+const CreatedKeyDisplay: React.FC = ({ apiKey }) => {
+  const [copiedKey, setCopiedKey] = useState(false);
+  const [copiedPrompt, setCopiedPrompt] = useState(false);
+
+  const codingAgentPrompt = useMemo(
+    () => buildCodingAgentPrompt(apiKey, resolveProxyBaseUrl()),
+    [apiKey],
+  );
+
+  const handleCopyKey = () => {
+    setCopiedKey(true);
     MessageManager.success("Key copied to clipboard");
-    setTimeout(() => setCopied(false), 2000);
+    setTimeout(() => setCopiedKey(false), 2000);
+  };
+
+  const handleCopyPrompt = () => {
+    setCopiedPrompt(true);
+    MessageManager.success("Prompt copied to clipboard");
+    setTimeout(() => setCopiedPrompt(false), 2000);
   };
 
   return (
-    
-

- Please save this secret key somewhere safe and accessible. For security reasons,{" "} - you will not be able to view it again through your LiteLLM account. If you - lose this secret key, you will need to generate a new one. -

- -

Virtual Key:

-
-
-          {apiKey}
-        
+
+
+

API Key Created

+

+ Paste this prompt into any coding agent to start using LiteLLM. +

- -
+
+
+            {apiKey}
+          
+
+
+ +
+
+ + Prompt for coding agents + + + +
+
+
+            {codingAgentPrompt}
+          
+
+
+ + +