mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-23 19:05:16 +02:00
add community prompts tab and public toggle in prompt form
This commit is contained in:
parent
16884963a4
commit
1238efaf99
3 changed files with 154 additions and 15 deletions
|
|
@ -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<PublicPromptRead[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [copyingId, setCopyingId] = useState<number | null>(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 (
|
||||
<div className="flex items-center justify-center py-12">
|
||||
<Spinner className="size-6" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6 min-w-0 overflow-hidden">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Prompts shared by other users. Add any to your collection with one click.
|
||||
</p>
|
||||
|
||||
{prompts.length === 0 && (
|
||||
<div className="rounded-lg border border-dashed border-border/60 p-8 text-center">
|
||||
<Globe className="mx-auto size-8 text-muted-foreground/40" />
|
||||
<p className="mt-2 text-sm text-muted-foreground">No community prompts yet</p>
|
||||
<p className="text-xs text-muted-foreground/60">
|
||||
Share your own prompts from the My Prompts tab
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{prompts.length > 0 && (
|
||||
<div className="space-y-2">
|
||||
{prompts.map((prompt) => (
|
||||
<div
|
||||
key={prompt.id}
|
||||
className="group flex items-start gap-3 rounded-lg border border-border/60 bg-card p-4"
|
||||
>
|
||||
<div className="mt-0.5 shrink-0 text-muted-foreground">
|
||||
<Sparkles className="size-4" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-medium">{prompt.name}</span>
|
||||
<span className="rounded-full border px-2 py-0.5 text-[10px] text-muted-foreground">
|
||||
{prompt.mode}
|
||||
</span>
|
||||
</div>
|
||||
<p className="mt-1 text-xs text-muted-foreground line-clamp-2">{prompt.prompt}</p>
|
||||
{prompt.author_name && (
|
||||
<p className="mt-1.5 text-[11px] text-muted-foreground/60">
|
||||
by {prompt.author_name}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="shrink-0 gap-1.5"
|
||||
disabled={copyingId === prompt.id}
|
||||
onClick={() => handleCopy(prompt.id)}
|
||||
>
|
||||
{copyingId === prompt.id ? (
|
||||
<Spinner className="size-3" />
|
||||
) : (
|
||||
<Copy className="size-3" />
|
||||
)}
|
||||
Add to mine
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -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<PromptRead[]>([]);
|
||||
|
|
@ -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() {
|
|||
<div className="space-y-6 min-w-0 overflow-hidden">
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Create prompt templates triggered with <kbd className="rounded border bg-muted px-1.5 py-0.5 text-xs font-mono">/</kbd> in the chat composer.
|
||||
Create prompt templates triggered with{" "}
|
||||
<kbd className="rounded border bg-muted px-1.5 py-0.5 text-xs font-mono">/</kbd> in the
|
||||
chat composer.
|
||||
</p>
|
||||
{!showForm && (
|
||||
<Button
|
||||
|
|
@ -144,7 +149,11 @@ export function PromptsContent() {
|
|||
className="w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm outline-none resize-none focus:ring-1 focus:ring-ring"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Use <code className="rounded bg-muted px-1 py-0.5 font-mono text-[11px]">{"{selection}"}</code> to insert the input text. If omitted, the text is appended automatically.
|
||||
Use{" "}
|
||||
<code className="rounded bg-muted px-1 py-0.5 font-mono text-[11px]">
|
||||
{"{selection}"}
|
||||
</code>{" "}
|
||||
to insert the input text. If omitted, the text is appended automatically.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
|
@ -153,7 +162,9 @@ export function PromptsContent() {
|
|||
<select
|
||||
id="prompt-mode"
|
||||
value={formData.mode}
|
||||
onChange={(e) => setFormData((p) => ({ ...p, mode: e.target.value as "transform" | "explore" }))}
|
||||
onChange={(e) =>
|
||||
setFormData((p) => ({ ...p, mode: e.target.value as "transform" | "explore" }))
|
||||
}
|
||||
className="w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm outline-none focus:ring-1 focus:ring-ring"
|
||||
>
|
||||
<option value="transform">Transform — rewrites or modifies your text</option>
|
||||
|
|
@ -161,14 +172,25 @@ export function PromptsContent() {
|
|||
</select>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-end gap-2 pt-2">
|
||||
<Button variant="ghost" size="sm" onClick={handleCancel}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button size="sm" onClick={handleSave} disabled={isSaving}>
|
||||
{isSaving ? <Spinner className="size-3.5" /> : editingId ? "Update" : "Create"}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Switch
|
||||
id="prompt-public"
|
||||
checked={formData.is_public}
|
||||
onCheckedChange={(checked) => setFormData((p) => ({ ...p, is_public: checked }))}
|
||||
/>
|
||||
<Label htmlFor="prompt-public" className="text-sm font-normal">
|
||||
Share with community
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-end gap-2 pt-2">
|
||||
<Button variant="ghost" size="sm" onClick={handleCancel}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button size="sm" onClick={handleSave} disabled={isSaving}>
|
||||
{isSaving ? <Spinner className="size-3.5" /> : editingId ? "Update" : "Create"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
@ -198,6 +220,12 @@ export function PromptsContent() {
|
|||
<span className="rounded-full border px-2 py-0.5 text-[10px] text-muted-foreground">
|
||||
{prompt.mode}
|
||||
</span>
|
||||
{prompt.is_public && (
|
||||
<span className="flex items-center gap-1 rounded-full border border-primary/20 bg-primary/5 px-2 py-0.5 text-[10px] text-primary">
|
||||
<Globe className="size-2.5" />
|
||||
Public
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="mt-1 text-xs text-muted-foreground line-clamp-2">{prompt.prompt}</p>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue