-
- Region:
- setSelectedRegion(e.target.value)}
- buttonStyle="solid"
- >
- {availableRegions.map(region => (
-
- {region}
-
- ))}
-
-
- {availableTypes.length > 2 && (
-
-
Type:
-
setSelectedType(e.target.value)}
- buttonStyle="solid"
- >
- {availableTypes.map(type => (
-
- {type}
-
- ))}
-
+
+ {/* Left sidebar - tag filters */}
+ {tagCounts.length > 0 && (
+
+
+
+
+ Categories
+
+ {selectedTags.size > 0 && (
+
+ Clear all
+
+ )}
+
+
+ {tagCounts.map(([tag, count]) => (
+
+
+ handleTagToggle(tag)}
+ />
+ {tag}
+
+
+ {count}
+
+
+ ))}
+
+
)}
-
-
- {filteredTemplates.map((template, index) => (
-
onUseTemplate(template)}
- />
- ))}
+ {/* Right content - template cards */}
+
+ {selectedTags.size > 0 && (
+
+ Showing {filteredTemplates.length} of {templates.length} templates
+
+ )}
+
+ {filteredTemplates.map((template, index) => (
+
onUseTemplate(template)}
+ />
+ ))}
+
+
+ {filteredTemplates.length === 0 && (
+
+
No templates match the selected filters.
+
+ Clear all filters
+
+
+ )}
+
);
diff --git a/ui/litellm-dashboard/src/components/policies/template_parameter_modal.tsx b/ui/litellm-dashboard/src/components/policies/template_parameter_modal.tsx
new file mode 100644
index 00000000000..ce1e5bc22e9
--- /dev/null
+++ b/ui/litellm-dashboard/src/components/policies/template_parameter_modal.tsx
@@ -0,0 +1,127 @@
+import React, { useState, useEffect } from "react";
+import { Modal, Spin } from "antd";
+import { Button, TextInput } from "@tremor/react";
+
+interface TemplateParameter {
+ name: string;
+ label: string;
+ type: string;
+ required: boolean;
+ placeholder?: string;
+}
+
+interface TemplateParameterModalProps {
+ visible: boolean;
+ template: any;
+ onConfirm: (parameters: Record
) => void;
+ onCancel: () => void;
+ isLoading?: boolean;
+}
+
+const TemplateParameterModal: React.FC = ({
+ visible,
+ template,
+ onConfirm,
+ onCancel,
+ isLoading = false,
+}) => {
+ const [parameterValues, setParameterValues] = useState>({});
+
+ const parameters: TemplateParameter[] = template?.parameters || [];
+
+ useEffect(() => {
+ if (visible && template) {
+ const initial: Record = {};
+ parameters.forEach((p) => {
+ initial[p.name] = "";
+ });
+ setParameterValues(initial);
+ }
+ }, [visible, template]);
+
+ const allRequiredFilled = parameters
+ .filter((p) => p.required)
+ .every((p) => (parameterValues[p.name] || "").trim().length > 0);
+
+ const handleConfirm = () => {
+ onConfirm(parameterValues);
+ };
+
+ return (
+
+ {template?.title}
+
+ {template?.llm_enrichment
+ ? "Enter your brand name to auto-discover competitors and configure guardrails"
+ : "Configure template parameters"}
+
+
+ }
+ open={visible}
+ onCancel={onCancel}
+ width={500}
+ footer={[
+
+ {parameters.map((param) => (
+
+
+ {param.label}
+ {param.required && * }
+
+
+ setParameterValues((prev) => ({
+ ...prev,
+ [param.name]: e.target.value,
+ }))
+ }
+ />
+
+ ))}
+
+ {template?.llm_enrichment && (
+
+
+ This template uses AI to automatically discover your competitors and configure
+ guardrails. An onboarded LLM will be called to identify competitor names.
+
+
+ )}
+
+ {isLoading && (
+
+
+
+ {template?.llm_enrichment
+ ? "Using AI to discover competitors..."
+ : "Processing template..."}
+
+
+ )}
+
+
+ );
+};
+
+export default TemplateParameterModal;
diff --git a/ui/litellm-dashboard/src/components/templates/key_edit_view.test.tsx b/ui/litellm-dashboard/src/components/templates/key_edit_view.test.tsx
index bbe3c206cee..49a98b699e8 100644
--- a/ui/litellm-dashboard/src/components/templates/key_edit_view.test.tsx
+++ b/ui/litellm-dashboard/src/components/templates/key_edit_view.test.tsx
@@ -494,7 +494,50 @@ describe("KeyEditView", () => {
});
});
- it("should disable cancel button during submission", async () => {
+ it("should display 'AI APIs' label for the llm_api key type option", async () => {
+ const keyDataWithLlmApiRoutes = {
+ ...MOCK_KEY_DATA,
+ allowed_routes: ["llm_api_routes"],
+ };
+
+ renderWithProviders(
+