mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-04-25 00:36:31 +02:00
Fix create_prompt is_public bug, conditional version bump, and add Jotai prompts atoms
- Pass is_public from request body in create_prompt route - Only bump version on content field changes (name, prompt, mode), not on is_public toggle - Add prompts query and mutation atoms (atomWithQuery/atomWithMutation) with TanStack Query caching, replacing manual useEffect fetches - Update PromptPicker, PromptsContent, and CommunityPromptsContent to consume shared atoms instead of local state
This commit is contained in:
parent
95620a4331
commit
5f4f7780d1
7 changed files with 184 additions and 88 deletions
|
|
@ -41,6 +41,7 @@ async def create_prompt(
|
||||||
name=body.name,
|
name=body.name,
|
||||||
prompt=body.prompt,
|
prompt=body.prompt,
|
||||||
mode=body.mode,
|
mode=body.mode,
|
||||||
|
is_public=body.is_public,
|
||||||
)
|
)
|
||||||
session.add(prompt)
|
session.add(prompt)
|
||||||
await session.commit()
|
await session.commit()
|
||||||
|
|
@ -65,10 +66,15 @@ async def update_prompt(
|
||||||
if not prompt:
|
if not prompt:
|
||||||
raise HTTPException(status_code=404, detail="Prompt not found")
|
raise HTTPException(status_code=404, detail="Prompt not found")
|
||||||
|
|
||||||
for field, value in body.model_dump(exclude_unset=True).items():
|
updates = body.model_dump(exclude_unset=True)
|
||||||
|
content_fields = {"name", "prompt", "mode"}
|
||||||
|
has_content_change = bool(updates.keys() & content_fields)
|
||||||
|
|
||||||
|
for field, value in updates.items():
|
||||||
setattr(prompt, field, value)
|
setattr(prompt, field, value)
|
||||||
|
|
||||||
prompt.version = (prompt.version or 0) + 1
|
if has_content_change:
|
||||||
|
prompt.version = (prompt.version or 0) + 1
|
||||||
|
|
||||||
session.add(prompt)
|
session.add(prompt)
|
||||||
await session.commit()
|
await session.commit()
|
||||||
|
|
|
||||||
|
|
@ -1,38 +1,34 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { useAtomValue } from "jotai";
|
||||||
import { Copy, Globe, Sparkles } from "lucide-react";
|
import { Copy, Globe, Sparkles } from "lucide-react";
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
import { toast } from "sonner";
|
import { copyPromptMutationAtom } from "@/atoms/prompts/prompts-mutation.atoms";
|
||||||
|
import { publicPromptsAtom } from "@/atoms/prompts/prompts-query.atoms";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Spinner } from "@/components/ui/spinner";
|
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() {
|
export function CommunityPromptsContent() {
|
||||||
const [prompts, setPrompts] = useState<PublicPromptRead[]>([]);
|
const { data: prompts, isLoading } = useAtomValue(publicPromptsAtom);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const { mutateAsync: copyPrompt, isPending: isCopying } = useAtomValue(copyPromptMutationAtom);
|
||||||
const [copyingId, setCopyingId] = useState<number | null>(null);
|
const [copyingId, setCopyingId] = useState<number | null>(null);
|
||||||
const [expandedId, setExpandedId] = useState<number | null>(null);
|
const [expandedId, setExpandedId] = useState<number | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
const handleCopy = useCallback(
|
||||||
promptsApiService
|
async (id: number) => {
|
||||||
.listPublic()
|
setCopyingId(id);
|
||||||
.then(setPrompts)
|
try {
|
||||||
.catch(() => toast.error("Failed to load community prompts"))
|
await copyPrompt(id);
|
||||||
.finally(() => setIsLoading(false));
|
} catch {
|
||||||
}, []);
|
// toast handled by mutation atom
|
||||||
|
} finally {
|
||||||
|
setCopyingId(null);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[copyPrompt]
|
||||||
|
);
|
||||||
|
|
||||||
const handleCopy = useCallback(async (id: number) => {
|
const list = prompts ?? [];
|
||||||
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) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
|
|
@ -48,7 +44,7 @@ export function CommunityPromptsContent() {
|
||||||
Prompts shared by other users. Add any to your collection with one click.
|
Prompts shared by other users. Add any to your collection with one click.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{prompts.length === 0 && (
|
{list.length === 0 && (
|
||||||
<div className="rounded-lg border border-dashed border-border/60 p-8 text-center">
|
<div className="rounded-lg border border-dashed border-border/60 p-8 text-center">
|
||||||
<Globe className="mx-auto size-8 text-muted-foreground/40" />
|
<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="mt-2 text-sm text-muted-foreground">No community prompts yet</p>
|
||||||
|
|
@ -58,9 +54,9 @@ export function CommunityPromptsContent() {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{prompts.length > 0 && (
|
{list.length > 0 && (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{prompts.map((prompt) => (
|
{list.map((prompt) => (
|
||||||
<div
|
<div
|
||||||
key={prompt.id}
|
key={prompt.id}
|
||||||
className="group flex items-start gap-3 rounded-lg border border-border/60 bg-card p-4"
|
className="group flex items-start gap-3 rounded-lg border border-border/60 bg-card p-4"
|
||||||
|
|
@ -80,7 +76,9 @@ export function CommunityPromptsContent() {
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<p className={`mt-1 text-xs text-muted-foreground ${expandedId === prompt.id ? "whitespace-pre-wrap" : "line-clamp-2"}`}>
|
<p
|
||||||
|
className={`mt-1 text-xs text-muted-foreground ${expandedId === prompt.id ? "whitespace-pre-wrap" : "line-clamp-2"}`}
|
||||||
|
>
|
||||||
{prompt.prompt}
|
{prompt.prompt}
|
||||||
</p>
|
</p>
|
||||||
{prompt.prompt.length > 100 && (
|
{prompt.prompt.length > 100 && (
|
||||||
|
|
@ -97,10 +95,10 @@ export function CommunityPromptsContent() {
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="shrink-0 gap-1.5"
|
className="shrink-0 gap-1.5"
|
||||||
disabled={copyingId === prompt.id}
|
disabled={copyingId === prompt.id && isCopying}
|
||||||
onClick={() => handleCopy(prompt.id)}
|
onClick={() => handleCopy(prompt.id)}
|
||||||
>
|
>
|
||||||
{copyingId === prompt.id ? (
|
{copyingId === prompt.id && isCopying ? (
|
||||||
<Spinner className="size-3" />
|
<Spinner className="size-3" />
|
||||||
) : (
|
) : (
|
||||||
<Copy className="size-3" />
|
<Copy className="size-3" />
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,21 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { useAtomValue } from "jotai";
|
||||||
import { Globe, Lock, PenLine, Plus, Sparkles, Trash2 } from "lucide-react";
|
import { Globe, Lock, PenLine, Plus, Sparkles, Trash2 } from "lucide-react";
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
import {
|
||||||
|
createPromptMutationAtom,
|
||||||
|
deletePromptMutationAtom,
|
||||||
|
updatePromptMutationAtom,
|
||||||
|
} from "@/atoms/prompts/prompts-mutation.atoms";
|
||||||
|
import { promptsAtom } from "@/atoms/prompts/prompts-query.atoms";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { Spinner } from "@/components/ui/spinner";
|
import { Spinner } from "@/components/ui/spinner";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import type { PromptRead } from "@/contracts/types/prompts.types";
|
import type { PromptRead } from "@/contracts/types/prompts.types";
|
||||||
import { promptsApiService } from "@/lib/apis/prompts-api.service";
|
|
||||||
|
|
||||||
interface PromptFormData {
|
interface PromptFormData {
|
||||||
name: string;
|
name: string;
|
||||||
|
|
@ -21,22 +27,17 @@ interface PromptFormData {
|
||||||
const EMPTY_FORM: PromptFormData = { name: "", prompt: "", mode: "transform", is_public: false };
|
const EMPTY_FORM: PromptFormData = { name: "", prompt: "", mode: "transform", is_public: false };
|
||||||
|
|
||||||
export function PromptsContent() {
|
export function PromptsContent() {
|
||||||
const [prompts, setPrompts] = useState<PromptRead[]>([]);
|
const { data: prompts, isLoading } = useAtomValue(promptsAtom);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const { mutateAsync: createPrompt } = useAtomValue(createPromptMutationAtom);
|
||||||
|
const { mutateAsync: updatePrompt } = useAtomValue(updatePromptMutationAtom);
|
||||||
|
const { mutateAsync: deletePrompt } = useAtomValue(deletePromptMutationAtom);
|
||||||
|
|
||||||
const [showForm, setShowForm] = useState(false);
|
const [showForm, setShowForm] = useState(false);
|
||||||
const [editingId, setEditingId] = useState<number | null>(null);
|
const [editingId, setEditingId] = useState<number | null>(null);
|
||||||
const [formData, setFormData] = useState<PromptFormData>(EMPTY_FORM);
|
const [formData, setFormData] = useState<PromptFormData>(EMPTY_FORM);
|
||||||
const [isSaving, setIsSaving] = useState(false);
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
const [expandedId, setExpandedId] = useState<number | null>(null);
|
const [expandedId, setExpandedId] = useState<number | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
promptsApiService
|
|
||||||
.list()
|
|
||||||
.then(setPrompts)
|
|
||||||
.catch(() => toast.error("Failed to load prompts"))
|
|
||||||
.finally(() => setIsLoading(false));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleSave = useCallback(async () => {
|
const handleSave = useCallback(async () => {
|
||||||
if (!formData.name.trim() || !formData.prompt.trim()) {
|
if (!formData.name.trim() || !formData.prompt.trim()) {
|
||||||
toast.error("Name and prompt are required");
|
toast.error("Name and prompt are required");
|
||||||
|
|
@ -46,23 +47,19 @@ export function PromptsContent() {
|
||||||
setIsSaving(true);
|
setIsSaving(true);
|
||||||
try {
|
try {
|
||||||
if (editingId) {
|
if (editingId) {
|
||||||
const updated = await promptsApiService.update(editingId, formData);
|
await updatePrompt({ id: editingId, ...formData });
|
||||||
setPrompts((prev) => prev.map((p) => (p.id === editingId ? updated : p)));
|
|
||||||
toast.success("Prompt updated");
|
|
||||||
} else {
|
} else {
|
||||||
const created = await promptsApiService.create(formData);
|
await createPrompt(formData);
|
||||||
setPrompts((prev) => [created, ...prev]);
|
|
||||||
toast.success("Prompt created");
|
|
||||||
}
|
}
|
||||||
setShowForm(false);
|
setShowForm(false);
|
||||||
setFormData(EMPTY_FORM);
|
setFormData(EMPTY_FORM);
|
||||||
setEditingId(null);
|
setEditingId(null);
|
||||||
} catch {
|
} catch {
|
||||||
toast.error("Failed to save prompt");
|
// toast handled by mutation atoms
|
||||||
} finally {
|
} finally {
|
||||||
setIsSaving(false);
|
setIsSaving(false);
|
||||||
}
|
}
|
||||||
}, [formData, editingId]);
|
}, [formData, editingId, createPrompt, updatePrompt]);
|
||||||
|
|
||||||
const handleEdit = useCallback((prompt: PromptRead) => {
|
const handleEdit = useCallback((prompt: PromptRead) => {
|
||||||
setFormData({
|
setFormData({
|
||||||
|
|
@ -75,15 +72,27 @@ export function PromptsContent() {
|
||||||
setShowForm(true);
|
setShowForm(true);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleDelete = useCallback(async (id: number) => {
|
const handleDelete = useCallback(
|
||||||
try {
|
async (id: number) => {
|
||||||
await promptsApiService.delete(id);
|
try {
|
||||||
setPrompts((prev) => prev.filter((p) => p.id !== id));
|
await deletePrompt(id);
|
||||||
toast.success("Prompt deleted");
|
} catch {
|
||||||
} catch {
|
// toast handled by mutation atom
|
||||||
toast.error("Failed to delete prompt");
|
}
|
||||||
}
|
},
|
||||||
}, []);
|
[deletePrompt]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleTogglePublic = useCallback(
|
||||||
|
async (prompt: PromptRead) => {
|
||||||
|
try {
|
||||||
|
await updatePrompt({ id: prompt.id, is_public: !prompt.is_public });
|
||||||
|
} catch {
|
||||||
|
// toast handled by mutation atom
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[updatePrompt]
|
||||||
|
);
|
||||||
|
|
||||||
const handleCancel = useCallback(() => {
|
const handleCancel = useCallback(() => {
|
||||||
setShowForm(false);
|
setShowForm(false);
|
||||||
|
|
@ -91,6 +100,8 @@ export function PromptsContent() {
|
||||||
setEditingId(null);
|
setEditingId(null);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const list = prompts ?? [];
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center py-12">
|
<div className="flex items-center justify-center py-12">
|
||||||
|
|
@ -195,7 +206,7 @@ export function PromptsContent() {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{prompts.length === 0 && !showForm && (
|
{list.length === 0 && !showForm && (
|
||||||
<div className="rounded-lg border border-dashed border-border/60 p-8 text-center">
|
<div className="rounded-lg border border-dashed border-border/60 p-8 text-center">
|
||||||
<Sparkles className="mx-auto size-8 text-muted-foreground/40" />
|
<Sparkles className="mx-auto size-8 text-muted-foreground/40" />
|
||||||
<p className="mt-2 text-sm text-muted-foreground">No prompts yet</p>
|
<p className="mt-2 text-sm text-muted-foreground">No prompts yet</p>
|
||||||
|
|
@ -205,9 +216,9 @@ export function PromptsContent() {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{prompts.length > 0 && (
|
{list.length > 0 && (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{prompts.map((prompt) => (
|
{list.map((prompt) => (
|
||||||
<div
|
<div
|
||||||
key={prompt.id}
|
key={prompt.id}
|
||||||
className="group flex items-start gap-3 rounded-lg border border-border/60 bg-card p-4"
|
className="group flex items-start gap-3 rounded-lg border border-border/60 bg-card p-4"
|
||||||
|
|
@ -247,17 +258,7 @@ export function PromptsContent() {
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
title={prompt.is_public ? "Make private" : "Share with community"}
|
title={prompt.is_public ? "Make private" : "Share with community"}
|
||||||
onClick={async () => {
|
onClick={() => handleTogglePublic(prompt)}
|
||||||
try {
|
|
||||||
const updated = await promptsApiService.update(prompt.id, {
|
|
||||||
is_public: !prompt.is_public,
|
|
||||||
});
|
|
||||||
setPrompts((prev) => prev.map((p) => (p.id === prompt.id ? updated : p)));
|
|
||||||
toast.success(updated.is_public ? "Shared with community" : "Made private");
|
|
||||||
} catch {
|
|
||||||
toast.error("Failed to update");
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
className="flex items-center justify-center size-7 rounded-md text-muted-foreground hover:text-foreground hover:bg-accent transition-colors"
|
className="flex items-center justify-center size-7 rounded-md text-muted-foreground hover:text-foreground hover:bg-accent transition-colors"
|
||||||
>
|
>
|
||||||
{prompt.is_public ? (
|
{prompt.is_public ? (
|
||||||
|
|
|
||||||
71
surfsense_web/atoms/prompts/prompts-mutation.atoms.ts
Normal file
71
surfsense_web/atoms/prompts/prompts-mutation.atoms.ts
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
import { atomWithMutation } from "jotai-tanstack-query";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import type {
|
||||||
|
PromptCreateRequest,
|
||||||
|
PromptRead,
|
||||||
|
PromptUpdateRequest,
|
||||||
|
} from "@/contracts/types/prompts.types";
|
||||||
|
import { promptsApiService } from "@/lib/apis/prompts-api.service";
|
||||||
|
import { cacheKeys } from "@/lib/query-client/cache-keys";
|
||||||
|
import { queryClient } from "@/lib/query-client/client";
|
||||||
|
|
||||||
|
export const createPromptMutationAtom = atomWithMutation(() => ({
|
||||||
|
mutationKey: ["prompts", "create"],
|
||||||
|
mutationFn: async (request: PromptCreateRequest) => {
|
||||||
|
return promptsApiService.create(request);
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success("Prompt created");
|
||||||
|
queryClient.invalidateQueries({ queryKey: cacheKeys.prompts.all() });
|
||||||
|
},
|
||||||
|
onError: (error: Error) => {
|
||||||
|
toast.error(error.message || "Failed to create prompt");
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const updatePromptMutationAtom = atomWithMutation(() => ({
|
||||||
|
mutationKey: ["prompts", "update"],
|
||||||
|
mutationFn: async ({ id, ...data }: PromptUpdateRequest & { id: number }) => {
|
||||||
|
return promptsApiService.update(id, data);
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success("Prompt updated");
|
||||||
|
queryClient.invalidateQueries({ queryKey: cacheKeys.prompts.all() });
|
||||||
|
queryClient.invalidateQueries({ queryKey: cacheKeys.prompts.public() });
|
||||||
|
},
|
||||||
|
onError: (error: Error) => {
|
||||||
|
toast.error(error.message || "Failed to update prompt");
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const deletePromptMutationAtom = atomWithMutation(() => ({
|
||||||
|
mutationKey: ["prompts", "delete"],
|
||||||
|
mutationFn: async (id: number) => {
|
||||||
|
return promptsApiService.delete(id);
|
||||||
|
},
|
||||||
|
onSuccess: (_: unknown, id: number) => {
|
||||||
|
toast.success("Prompt deleted");
|
||||||
|
queryClient.setQueryData(cacheKeys.prompts.all(), (old: PromptRead[] | undefined) => {
|
||||||
|
if (!old) return old;
|
||||||
|
return old.filter((p) => p.id !== id);
|
||||||
|
});
|
||||||
|
queryClient.invalidateQueries({ queryKey: cacheKeys.prompts.public() });
|
||||||
|
},
|
||||||
|
onError: (error: Error) => {
|
||||||
|
toast.error(error.message || "Failed to delete prompt");
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const copyPromptMutationAtom = atomWithMutation(() => ({
|
||||||
|
mutationKey: ["prompts", "copy"],
|
||||||
|
mutationFn: async (promptId: number) => {
|
||||||
|
return promptsApiService.copy(promptId);
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success("Prompt added to your collection");
|
||||||
|
queryClient.invalidateQueries({ queryKey: cacheKeys.prompts.all() });
|
||||||
|
},
|
||||||
|
onError: (error: Error) => {
|
||||||
|
toast.error(error.message || "Failed to copy prompt");
|
||||||
|
},
|
||||||
|
}));
|
||||||
23
surfsense_web/atoms/prompts/prompts-query.atoms.ts
Normal file
23
surfsense_web/atoms/prompts/prompts-query.atoms.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { atomWithQuery } from "jotai-tanstack-query";
|
||||||
|
import { promptsApiService } from "@/lib/apis/prompts-api.service";
|
||||||
|
import { cacheKeys } from "@/lib/query-client/cache-keys";
|
||||||
|
|
||||||
|
export const promptsAtom = atomWithQuery(() => {
|
||||||
|
return {
|
||||||
|
queryKey: cacheKeys.prompts.all(),
|
||||||
|
staleTime: 5 * 60 * 1000,
|
||||||
|
queryFn: async () => {
|
||||||
|
return promptsApiService.list();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export const publicPromptsAtom = atomWithQuery(() => {
|
||||||
|
return {
|
||||||
|
queryKey: cacheKeys.prompts.public(),
|
||||||
|
staleTime: 2 * 60 * 1000,
|
||||||
|
queryFn: async () => {
|
||||||
|
return promptsApiService.listPublic();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useSetAtom } from "jotai";
|
import { useAtomValue, useSetAtom } from "jotai";
|
||||||
import { Plus, Zap } from "lucide-react";
|
import { Plus, Zap } from "lucide-react";
|
||||||
import {
|
import {
|
||||||
forwardRef,
|
forwardRef,
|
||||||
|
|
@ -12,9 +12,8 @@ import {
|
||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
|
|
||||||
|
import { promptsAtom } from "@/atoms/prompts/prompts-query.atoms";
|
||||||
import { userSettingsDialogAtom } from "@/atoms/settings/settings-dialog.atoms";
|
import { userSettingsDialogAtom } from "@/atoms/settings/settings-dialog.atoms";
|
||||||
import type { PromptRead } from "@/contracts/types/prompts.types";
|
|
||||||
import { promptsApiService } from "@/lib/apis/prompts-api.service";
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
export interface PromptPickerRef {
|
export interface PromptPickerRef {
|
||||||
|
|
@ -35,22 +34,16 @@ export const PromptPicker = forwardRef<PromptPickerRef, PromptPickerProps>(funct
|
||||||
ref
|
ref
|
||||||
) {
|
) {
|
||||||
const setUserSettingsDialog = useSetAtom(userSettingsDialogAtom);
|
const setUserSettingsDialog = useSetAtom(userSettingsDialogAtom);
|
||||||
|
const { data: prompts } = useAtomValue(promptsAtom);
|
||||||
const [highlightedIndex, setHighlightedIndex] = useState(0);
|
const [highlightedIndex, setHighlightedIndex] = useState(0);
|
||||||
const [prompts, setPrompts] = useState<PromptRead[]>([]);
|
|
||||||
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
||||||
const shouldScrollRef = useRef(false);
|
const shouldScrollRef = useRef(false);
|
||||||
const itemRefs = useRef<Map<number, HTMLButtonElement>>(new Map());
|
const itemRefs = useRef<Map<number, HTMLButtonElement>>(new Map());
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
promptsApiService
|
|
||||||
.list()
|
|
||||||
.then(setPrompts)
|
|
||||||
.catch(() => {});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const filtered = useMemo(() => {
|
const filtered = useMemo(() => {
|
||||||
if (!externalSearch) return prompts;
|
const list = prompts ?? [];
|
||||||
return prompts.filter((a) => a.name.toLowerCase().includes(externalSearch.toLowerCase()));
|
if (!externalSearch) return list;
|
||||||
|
return list.filter((a) => a.name.toLowerCase().includes(externalSearch.toLowerCase()));
|
||||||
}, [prompts, externalSearch]);
|
}, [prompts, externalSearch]);
|
||||||
|
|
||||||
const prevSearchRef = useRef(externalSearch);
|
const prevSearchRef = useRef(externalSearch);
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,10 @@ export const cacheKeys = {
|
||||||
bySearchSpace: (searchSpaceId: number) =>
|
bySearchSpace: (searchSpaceId: number) =>
|
||||||
["public-chat-snapshots", "search-space", searchSpaceId] as const,
|
["public-chat-snapshots", "search-space", searchSpaceId] as const,
|
||||||
},
|
},
|
||||||
|
prompts: {
|
||||||
|
all: () => ["prompts"] as const,
|
||||||
|
public: () => ["prompts", "public"] as const,
|
||||||
|
},
|
||||||
notifications: {
|
notifications: {
|
||||||
search: (searchSpaceId: number | null, search: string, tab: string) =>
|
search: (searchSpaceId: number | null, search: string, tab: string) =>
|
||||||
["notifications", "search", searchSpaceId, search, tab] as const,
|
["notifications", "search", searchSpaceId, search, tab] as const,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue