diff --git a/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/CommunityPromptsContent.tsx b/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/CommunityPromptsContent.tsx new file mode 100644 index 000000000..4ae47dbfc --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/CommunityPromptsContent.tsx @@ -0,0 +1,104 @@ +"use client"; + +import { Copy, Globe, Sparkles } from "lucide-react"; +import { useCallback, useEffect, useState } from "react"; +import { toast } from "sonner"; +import { Button } from "@/components/ui/button"; +import { Spinner } from "@/components/ui/spinner"; +import type { PublicPromptRead } from "@/contracts/types/prompts.types"; +import { promptsApiService } from "@/lib/apis/prompts-api.service"; + +export function CommunityPromptsContent() { + const [prompts, setPrompts] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [copyingId, setCopyingId] = useState(null); + + useEffect(() => { + promptsApiService + .listPublic() + .then(setPrompts) + .catch(() => toast.error("Failed to load community prompts")) + .finally(() => setIsLoading(false)); + }, []); + + const handleCopy = useCallback(async (id: number) => { + setCopyingId(id); + try { + await promptsApiService.copy(id); + toast.success("Prompt added to your collection"); + } catch { + toast.error("Failed to copy prompt"); + } finally { + setCopyingId(null); + } + }, []); + + if (isLoading) { + return ( +
+ +
+ ); + } + + return ( +
+

+ Prompts shared by other users. Add any to your collection with one click. +

+ + {prompts.length === 0 && ( +
+ +

No community prompts yet

+

+ Share your own prompts from the My Prompts tab +

+
+ )} + + {prompts.length > 0 && ( +
+ {prompts.map((prompt) => ( +
+
+ +
+
+
+ {prompt.name} + + {prompt.mode} + +
+

{prompt.prompt}

+ {prompt.author_name && ( +

+ by {prompt.author_name} +

+ )} +
+ +
+ ))} +
+ )} +
+ ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/PromptsContent.tsx b/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/PromptsContent.tsx index 38ccafa94..c91c19f2c 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/PromptsContent.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/PromptsContent.tsx @@ -1,22 +1,24 @@ "use client"; -import { PenLine, Plus, Sparkles, Trash2 } from "lucide-react"; +import { Globe, PenLine, Plus, Sparkles, Trash2 } from "lucide-react"; import { useCallback, useEffect, useState } from "react"; import { toast } from "sonner"; -import type { PromptRead } from "@/contracts/types/prompts.types"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Spinner } from "@/components/ui/spinner"; +import { Switch } from "@/components/ui/switch"; +import type { PromptRead } from "@/contracts/types/prompts.types"; import { promptsApiService } from "@/lib/apis/prompts-api.service"; interface PromptFormData { name: string; prompt: string; mode: "transform" | "explore"; + is_public: boolean; } -const EMPTY_FORM: PromptFormData = { name: "", prompt: "", mode: "transform" }; +const EMPTY_FORM: PromptFormData = { name: "", prompt: "", mode: "transform", is_public: false }; export function PromptsContent() { const [prompts, setPrompts] = useState([]); @@ -66,6 +68,7 @@ export function PromptsContent() { name: prompt.name, prompt: prompt.prompt, mode: prompt.mode as "transform" | "explore", + is_public: prompt.is_public, }); setEditingId(prompt.id); setShowForm(true); @@ -99,7 +102,9 @@ export function PromptsContent() {

- Create prompt templates triggered with / in the chat composer. + Create prompt templates triggered with{" "} + / in the + chat composer.

{!showForm && (
@@ -153,7 +162,9 @@ export function PromptsContent() {
-
- - -
+
+ setFormData((p) => ({ ...p, is_public: checked }))} + /> + +
+ +
+ + +
)} @@ -198,6 +220,12 @@ export function PromptsContent() { {prompt.mode} + {prompt.is_public && ( + + + Public + + )}

{prompt.prompt}

diff --git a/surfsense_web/components/settings/user-settings-dialog.tsx b/surfsense_web/components/settings/user-settings-dialog.tsx index 88fc729de..3a66c54de 100644 --- a/surfsense_web/components/settings/user-settings-dialog.tsx +++ b/surfsense_web/components/settings/user-settings-dialog.tsx @@ -1,9 +1,10 @@ "use client"; import { useAtom } from "jotai"; -import { KeyRound, Sparkles, User } from "lucide-react"; +import { Globe, KeyRound, Sparkles, User } from "lucide-react"; import { useTranslations } from "next-intl"; import { ApiKeyContent } from "@/app/dashboard/[search_space_id]/user-settings/components/ApiKeyContent"; +import { CommunityPromptsContent } from "@/app/dashboard/[search_space_id]/user-settings/components/CommunityPromptsContent"; import { ProfileContent } from "@/app/dashboard/[search_space_id]/user-settings/components/ProfileContent"; import { PromptsContent } from "@/app/dashboard/[search_space_id]/user-settings/components/PromptsContent"; import { userSettingsDialogAtom } from "@/atoms/settings/settings-dialog.atoms"; @@ -25,6 +26,11 @@ export function UserSettingsDialog() { label: "My Prompts", icon: , }, + { + value: "community-prompts", + label: "Community Prompts", + icon: , + }, ]; return ( @@ -40,6 +46,7 @@ export function UserSettingsDialog() { {state.initialTab === "profile" && } {state.initialTab === "api-key" && } {state.initialTab === "prompts" && } + {state.initialTab === "community-prompts" && } );