= {
+ documentation: "Documentation",
+ error_messages: "Error Messages",
+ performance_optimization: "Performance",
+ additional_features: "Additional Features",
+ nothing: "Nothing - It's great!",
+ };
+
+ const readableFeatures = data.features.map((f) => {
+ if (f === "other") return "Other";
+ return featureLabels[f] || f;
+ });
+
+ const readableImprovements = data.improvements.map((i) => {
+ if (i === "other" && data.improvementOther) {
+ return `Other: ${data.improvementOther}`;
+ }
+ return improvementLabels[i] || i;
+ });
+
+ await fetch("https://hooks.zapier.com/hooks/catch/16331268/ugms6w0/", {
+ method: "POST",
+ mode: "no-cors",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ surveyType: "Claude Code Feedback",
+ satisfaction: data.satisfaction,
+ features: readableFeatures.join(", "),
+ improvements: readableImprovements.join(", "),
+ improvementOther: data.improvementOther || null,
+ email: data.email || null,
+ submittedAt: new Date().toISOString(),
+ }),
+ });
+ } catch (error) {
+ // Silently fail - don't block the user experience
+ console.error("Failed to submit Claude Code feedback:", error);
+ }
+ setIsSubmitting(false);
+ onComplete();
+ };
+
+ const updateData = (key: keyof SurveyData, value: string | string[]) => {
+ setData((prev) => ({
+ ...prev,
+ [key]: value,
+ }));
+ };
+
+ const toggleFeature = (featureId: string) => {
+ setData((prev) => ({
+ ...prev,
+ features: prev.features.includes(featureId)
+ ? prev.features.filter((f) => f !== featureId)
+ : [...prev.features, featureId],
+ }));
+ };
+
+ const toggleImprovement = (improvementId: string) => {
+ setData((prev) => ({
+ ...prev,
+ improvements: prev.improvements.includes(improvementId)
+ ? prev.improvements.filter((i) => i !== improvementId)
+ : [...prev.improvements, improvementId],
+ }));
+ };
+
+ const isStepValid = () => {
+ if (step === 1) return data.satisfaction !== "";
+ if (step === 2) return data.features.length > 0;
+ if (step === 3) {
+ // If "other" is selected, require the text field
+ if (data.improvements.includes("other")) {
+ return data.improvements.length > 0 && data.improvementOther.trim().length > 0;
+ }
+ return data.improvements.length > 0;
+ }
+ if (step === 4) return true; // Email is optional
+ return false;
+ };
+
+ const renderStepContent = () => {
+ // Step 1: Satisfaction rating
+ if (step === 1) {
+ return (
+
+
How satisfied are you with using LiteLLM in Claude Code?
+ updateData("satisfaction", e.target.value)}
+ className="w-full"
+ >
+
+ {[
+ "Very Satisfied",
+ "Satisfied",
+ "Neutral",
+ "Dissatisfied",
+ "Very Dissatisfied",
+ ].map((option) => (
+
+ ))}
+
+
+
+ );
+ }
+
+ // Step 2: What features do you value?
+ if (step === 2) {
+ return (
+
+
What do you value most about using LiteLLM with Claude Code?
+
Select all that apply.
+
+ {FEATURE_OPTIONS.map((option) => {
+ const isSelected = data.features.includes(option.id);
+ return (
+
toggleFeature(option.id)}
+ onKeyDown={(e) => {
+ if (e.key === "Enter" || e.key === " ") {
+ e.preventDefault();
+ toggleFeature(option.id);
+ }
+ }}
+ className={`flex items-start p-4 rounded-lg border cursor-pointer transition-all ${
+ isSelected
+ ? "border-purple-600 bg-purple-50 ring-1 ring-purple-600"
+ : "border-gray-200 hover:bg-gray-50"
+ }`}
+ >
+
+
+ {option.label}
+ {option.description}
+
+
+ );
+ })}
+
+
+ );
+ }
+
+ // Step 3: What could be improved?
+ if (step === 3) {
+ return (
+
+
What could we improve for Claude Code users?
+
Select all that apply.
+
+ {IMPROVEMENT_OPTIONS.map((option) => {
+ const isSelected = data.improvements.includes(option.id);
+ return (
+
+
toggleImprovement(option.id)}
+ onKeyDown={(e) => {
+ if (e.key === "Enter" || e.key === " ") {
+ e.preventDefault();
+ toggleImprovement(option.id);
+ }
+ }}
+ className={`flex items-start p-4 rounded-lg border cursor-pointer transition-all ${
+ isSelected
+ ? "border-purple-600 bg-purple-50 ring-1 ring-purple-600"
+ : "border-gray-200 hover:bg-gray-50"
+ }`}
+ >
+
+
+ {option.label}
+ {option.description}
+
+
+ {/* Show text input if "Other" is selected */}
+ {option.id === "other" && isSelected && (
+
updateData("improvementOther", e.target.value)}
+ onClick={(e) => e.stopPropagation()}
+ autoFocus
+ />
+ )}
+
+ );
+ })}
+
+
+ );
+ }
+
+ // Step 4: Email (optional)
+ if (step === 4) {
+ return (
+
+
Want to discuss further?
+
+ Leave your email and we may reach out to learn more about your Claude Code experience. This is completely optional.
+
+
updateData("email", e.target.value)}
+ autoFocus
+ />
+
+ We will only use this to follow up on your feedback. No spam, ever.
+
+
+ );
+ }
+
+ return null;
+ };
+
+ const isLastStep = step === 4;
+
+ return (
+
+ {/* Backdrop */}
+
+
+ {/* Modal */}
+
+ {/* Header */}
+
+
+
+ Claude Code Feedback
+
+
+
+
+ {/* Progress Bar */}
+
+
+ {/* Content */}
+
{renderStepContent()}
+
+ {/* Footer */}
+
+
+ Step {step} of {totalSteps}
+
+
+ {step > 1 && (
+
}>
+ Back
+
+ )}
+
+
+
+
+
+ );
+}
+
diff --git a/ui/litellm-dashboard/src/components/survey/ClaudeCodePrompt.tsx b/ui/litellm-dashboard/src/components/survey/ClaudeCodePrompt.tsx
new file mode 100644
index 00000000000..9006575ce0a
--- /dev/null
+++ b/ui/litellm-dashboard/src/components/survey/ClaudeCodePrompt.tsx
@@ -0,0 +1,26 @@
+import React from "react";
+import { Code } from "lucide-react";
+import { NudgePrompt } from "./NudgePrompt";
+
+interface ClaudeCodePromptProps {
+ onOpen: () => void;
+ onDismiss: () => void;
+ isVisible: boolean;
+}
+
+export function ClaudeCodePrompt({ onOpen, onDismiss, isVisible }: ClaudeCodePromptProps) {
+ return (
+
+ );
+}
+
diff --git a/ui/litellm-dashboard/src/components/survey/NudgePrompt.tsx b/ui/litellm-dashboard/src/components/survey/NudgePrompt.tsx
new file mode 100644
index 00000000000..9095c6c21c5
--- /dev/null
+++ b/ui/litellm-dashboard/src/components/survey/NudgePrompt.tsx
@@ -0,0 +1,91 @@
+import React, { useEffect, useState } from "react";
+import { X, LucideIcon } from "lucide-react";
+import { Button } from "antd";
+
+interface NudgePromptProps {
+ onOpen: () => void;
+ onDismiss: () => void;
+ isVisible: boolean;
+ title: string;
+ description: string;
+ buttonText: string;
+ icon: LucideIcon;
+ accentColor: string;
+ buttonStyle?: React.CSSProperties;
+}
+
+const DISMISS_DURATION = 15000; // 15 seconds
+
+export function NudgePrompt({
+ onOpen,
+ onDismiss,
+ isVisible,
+ title,
+ description,
+ buttonText,
+ icon: Icon,
+ accentColor,
+ buttonStyle,
+}: NudgePromptProps) {
+ const [progress, setProgress] = useState(100);
+
+ useEffect(() => {
+ if (!isVisible) {
+ setProgress(100);
+ return;
+ }
+
+ const startTime = Date.now();
+ const interval = setInterval(() => {
+ const elapsed = Date.now() - startTime;
+ const remaining = Math.max(0, 100 - (elapsed / DISMISS_DURATION) * 100);
+ setProgress(remaining);
+
+ if (remaining <= 0) {
+ clearInterval(interval);
+ }
+ }, 50);
+
+ return () => clearInterval(interval);
+ }, [isVisible]);
+
+ if (!isVisible) return null;
+
+ return (
+
+ {/* Progress bar at top showing time remaining */}
+
+
+
+
+
+
{description}
+
+
+
+
+ );
+}
+
diff --git a/ui/litellm-dashboard/src/components/survey/SurveyPrompt.tsx b/ui/litellm-dashboard/src/components/survey/SurveyPrompt.tsx
index 69a886bddeb..a41acb265a7 100644
--- a/ui/litellm-dashboard/src/components/survey/SurveyPrompt.tsx
+++ b/ui/litellm-dashboard/src/components/survey/SurveyPrompt.tsx
@@ -1,6 +1,6 @@
-import React, { useEffect, useState } from "react";
-import { MessageSquare, X } from "lucide-react";
-import { Button } from "antd";
+import React from "react";
+import { MessageSquare } from "lucide-react";
+import { NudgePrompt } from "./NudgePrompt";
interface SurveyPromptProps {
onOpen: () => void;
@@ -8,70 +8,18 @@ interface SurveyPromptProps {
isVisible: boolean;
}
-const DISMISS_DURATION = 15000; // 15 seconds
-
export function SurveyPrompt({ onOpen, onDismiss, isVisible }: SurveyPromptProps) {
- const [progress, setProgress] = useState(100);
-
- useEffect(() => {
- if (!isVisible) {
- setProgress(100);
- return;
- }
-
- const startTime = Date.now();
- const interval = setInterval(() => {
- const elapsed = Date.now() - startTime;
- const remaining = Math.max(0, 100 - (elapsed / DISMISS_DURATION) * 100);
- setProgress(remaining);
-
- if (remaining <= 0) {
- clearInterval(interval);
- }
- }, 50);
-
- return () => clearInterval(interval);
- }, [isVisible]);
-
- if (!isVisible) return null;
-
return (
-
- {/* Progress bar at top showing time remaining */}
-
-
-
-
-
-
- Quick feedback
-
-
-
-
-
- Help us improve LiteLLM! Share your experience in 5 quick questions.
-
-
-
-
-
+
);
}
diff --git a/ui/litellm-dashboard/src/components/survey/index.tsx b/ui/litellm-dashboard/src/components/survey/index.tsx
index 7c36419980c..fde05084b7e 100644
--- a/ui/litellm-dashboard/src/components/survey/index.tsx
+++ b/ui/litellm-dashboard/src/components/survey/index.tsx
@@ -1,3 +1,6 @@
export { SurveyPrompt } from "./SurveyPrompt";
export { SurveyModal } from "./SurveyModal";
+export { ClaudeCodePrompt } from "./ClaudeCodePrompt";
+export { ClaudeCodeModal } from "./ClaudeCodeModal";
+export { NudgePrompt } from "./NudgePrompt";