mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-21 18:55:16 +02:00
refactor: enhance PromptsContent with dropdown menu for actions, update loading states, and improve styling consistency
This commit is contained in:
parent
3b168e987d
commit
78ad19dd6a
5 changed files with 68 additions and 92 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useAtomValue } from "jotai";
|
import { useAtomValue } from "jotai";
|
||||||
import { AlertTriangle, Globe, Lock, Pencil, Sparkles, Trash2 } from "lucide-react";
|
import { AlertTriangle, Globe, Lock, MoreHorizontal, Pencil, Sparkles, Trash2 } from "lucide-react";
|
||||||
import { useCallback, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import {
|
import {
|
||||||
|
|
@ -10,6 +10,7 @@ import {
|
||||||
updatePromptMutationAtom,
|
updatePromptMutationAtom,
|
||||||
} from "@/atoms/prompts/prompts-mutation.atoms";
|
} from "@/atoms/prompts/prompts-mutation.atoms";
|
||||||
import { promptsAtom } from "@/atoms/prompts/prompts-query.atoms";
|
import { promptsAtom } from "@/atoms/prompts/prompts-query.atoms";
|
||||||
|
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||||
import {
|
import {
|
||||||
AlertDialog,
|
AlertDialog,
|
||||||
AlertDialogAction,
|
AlertDialogAction,
|
||||||
|
|
@ -20,7 +21,6 @@ import {
|
||||||
AlertDialogHeader,
|
AlertDialogHeader,
|
||||||
AlertDialogTitle,
|
AlertDialogTitle,
|
||||||
} from "@/components/ui/alert-dialog";
|
} from "@/components/ui/alert-dialog";
|
||||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Card, CardContent } from "@/components/ui/card";
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
import {
|
import {
|
||||||
|
|
@ -31,10 +31,14 @@ import {
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/components/ui/dropdown-menu";
|
||||||
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 { ShortcutKbd } from "@/components/ui/shortcut-kbd";
|
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
|
|
@ -42,6 +46,8 @@ import {
|
||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
|
import { ShortcutKbd } from "@/components/ui/shortcut-kbd";
|
||||||
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
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";
|
||||||
|
|
@ -221,8 +227,12 @@ export function PromptsContent() {
|
||||||
<SelectValue />
|
<SelectValue />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="transform">Transform — rewrites or modifies your text</SelectItem>
|
<SelectItem value="transform">
|
||||||
<SelectItem value="explore">Explore — answers a question about your text</SelectItem>
|
Transform — rewrites or modifies your text
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="explore">
|
||||||
|
Explore — answers a question about your text
|
||||||
|
</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -333,41 +343,45 @@ export function PromptsContent() {
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1 shrink-0 opacity-0 pointer-events-none transition-opacity duration-150 group-hover:opacity-100 group-hover:pointer-events-auto">
|
<DropdownMenu>
|
||||||
<Button
|
<DropdownMenuTrigger asChild>
|
||||||
type="button"
|
<Button
|
||||||
variant="ghost"
|
type="button"
|
||||||
size="icon"
|
variant="ghost"
|
||||||
title={prompt.is_public ? "Make private" : "Share with community"}
|
size="icon"
|
||||||
onClick={() => handleTogglePublic(prompt)}
|
className="h-7 w-7 shrink-0 self-center rounded-lg text-muted-foreground opacity-100 pointer-events-auto transition-opacity duration-150 hover:text-accent-foreground sm:opacity-0 sm:pointer-events-none sm:group-hover:opacity-100 sm:group-hover:pointer-events-auto"
|
||||||
disabled={togglingPublicIds.has(prompt.id)}
|
>
|
||||||
className="h-7 w-7 rounded-lg text-muted-foreground hover:text-accent-foreground"
|
<MoreHorizontal className="size-3.5" />
|
||||||
>
|
<span className="sr-only">Prompt actions</span>
|
||||||
{togglingPublicIds.has(prompt.id) ? (
|
</Button>
|
||||||
<Spinner className="size-3.5" />
|
</DropdownMenuTrigger>
|
||||||
) : prompt.is_public ? (
|
<DropdownMenuContent align="end">
|
||||||
<Lock className="size-3.5" />
|
<DropdownMenuItem
|
||||||
) : (
|
onClick={() => handleTogglePublic(prompt)}
|
||||||
<Globe className="size-3.5" />
|
disabled={togglingPublicIds.has(prompt.id)}
|
||||||
)}
|
>
|
||||||
</Button>
|
{togglingPublicIds.has(prompt.id) ? (
|
||||||
<Button
|
<Spinner className="size-4" />
|
||||||
variant="ghost"
|
) : prompt.is_public ? (
|
||||||
size="icon"
|
<Lock className="size-4" />
|
||||||
className="h-7 w-7 rounded-lg text-muted-foreground hover:text-accent-foreground"
|
) : (
|
||||||
onClick={() => handleEdit(prompt)}
|
<Globe className="size-4" />
|
||||||
>
|
)}
|
||||||
<Pencil className="size-3.5" />
|
{prompt.is_public ? "Make private" : "Share with community"}
|
||||||
</Button>
|
</DropdownMenuItem>
|
||||||
<Button
|
<DropdownMenuItem onClick={() => handleEdit(prompt)}>
|
||||||
variant="ghost"
|
<Pencil className="size-4" />
|
||||||
size="icon"
|
Edit
|
||||||
className="h-7 w-7 rounded-lg text-muted-foreground hover:text-destructive"
|
</DropdownMenuItem>
|
||||||
onClick={() => setDeleteTarget(prompt.id)}
|
<DropdownMenuItem
|
||||||
>
|
onClick={() => setDeleteTarget(prompt.id)}
|
||||||
<Trash2 className="size-3.5" />
|
className="text-destructive focus:text-destructive"
|
||||||
</Button>
|
>
|
||||||
</div>
|
<Trash2 className="size-4" />
|
||||||
|
Delete
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
||||||
import { materialDark, materialLight } from "react-syntax-highlighter/dist/esm/styles/prism";
|
import { materialDark, materialLight } from "react-syntax-highlighter/dist/esm/styles/prism";
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
|
||||||
import { cn, copyToClipboard } from "@/lib/utils";
|
import { cn, copyToClipboard } from "@/lib/utils";
|
||||||
|
|
||||||
type MarkdownCodeBlockProps = {
|
type MarkdownCodeBlockProps = {
|
||||||
|
|
@ -49,7 +48,7 @@ function MarkdownCodeBlockComponent({
|
||||||
}, [hasCopied]);
|
}, [hasCopied]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mt-4 overflow-hidden rounded-2xl" style={{ background: "var(--syntax-bg)" }}>
|
<div className="mt-4 overflow-hidden rounded-md bg-accent">
|
||||||
<div className="flex items-center justify-between gap-4 px-4 py-2 font-semibold text-muted-foreground text-sm">
|
<div className="flex items-center justify-between gap-4 px-4 py-2 font-semibold text-muted-foreground text-sm">
|
||||||
<span className="lowercase text-xs">{language}</span>
|
<span className="lowercase text-xs">{language}</span>
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -82,23 +81,3 @@ function MarkdownCodeBlockComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MarkdownCodeBlock = memo(MarkdownCodeBlockComponent);
|
export const MarkdownCodeBlock = memo(MarkdownCodeBlockComponent);
|
||||||
|
|
||||||
export function MarkdownCodeBlockSkeleton() {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="mt-4 overflow-hidden rounded-2xl border"
|
|
||||||
style={{ background: "var(--syntax-bg)" }}
|
|
||||||
>
|
|
||||||
<div className="flex items-center justify-between gap-4 border-b px-4 py-2">
|
|
||||||
<Skeleton className="h-3 w-16" />
|
|
||||||
<Skeleton className="h-8 w-8 rounded-md" />
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2 p-4">
|
|
||||||
<Skeleton className="h-4 w-11/12" />
|
|
||||||
<Skeleton className="h-4 w-10/12" />
|
|
||||||
<Skeleton className="h-4 w-8/12" />
|
|
||||||
<Skeleton className="h-4 w-9/12" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ import { MentionChip } from "@/components/assistant-ui/mention-chip";
|
||||||
import "katex/dist/katex.min.css";
|
import "katex/dist/katex.min.css";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { processChildrenWithCitations } from "@/components/citations/citation-renderer";
|
import { processChildrenWithCitations } from "@/components/citations/citation-renderer";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
|
||||||
import { tryGetHostname } from "@/lib/url";
|
import { tryGetHostname } from "@/lib/url";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
|
@ -39,30 +38,14 @@ import { getVirtualPathDisplay } from "@/lib/chat/virtual-path-display";
|
||||||
import { type CitationUrlMap, preprocessCitationMarkdown } from "@/lib/citations/citation-parser";
|
import { type CitationUrlMap, preprocessCitationMarkdown } from "@/lib/citations/citation-parser";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function MarkdownCodeBlockSkeleton() {
|
function MarkdownCodeBlockLoading() {
|
||||||
return (
|
return <div className="mt-4 h-32 overflow-hidden rounded-md bg-accent" />;
|
||||||
<div
|
|
||||||
className="mt-4 overflow-hidden rounded-2xl border"
|
|
||||||
style={{ background: "var(--syntax-bg)" }}
|
|
||||||
>
|
|
||||||
<div className="flex items-center justify-between gap-4 border-b px-4 py-2">
|
|
||||||
<Skeleton className="h-3 w-16" />
|
|
||||||
<Skeleton className="h-8 w-8 rounded-md" />
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2 p-4">
|
|
||||||
<Skeleton className="h-4 w-11/12" />
|
|
||||||
<Skeleton className="h-4 w-10/12" />
|
|
||||||
<Skeleton className="h-4 w-8/12" />
|
|
||||||
<Skeleton className="h-4 w-9/12" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const LazyMarkdownCodeBlock = dynamic(
|
const LazyMarkdownCodeBlock = dynamic(
|
||||||
() => import("./markdown-code-block").then((mod) => mod.MarkdownCodeBlock),
|
() => import("./markdown-code-block").then((mod) => mod.MarkdownCodeBlock),
|
||||||
{
|
{
|
||||||
loading: () => <MarkdownCodeBlockSkeleton />,
|
loading: () => <MarkdownCodeBlockLoading />,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -443,7 +426,7 @@ const defaultComponents = memoizeMarkdownComponents({
|
||||||
<hr className={cn("aui-md-hr my-5 border-b", className)} {...props} />
|
<hr className={cn("aui-md-hr my-5 border-b", className)} {...props} />
|
||||||
),
|
),
|
||||||
table: ({ className, ...props }) => (
|
table: ({ className, ...props }) => (
|
||||||
<div className="aui-md-table-wrapper my-5 overflow-hidden rounded-2xl border">
|
<div className="aui-md-table-wrapper my-5 overflow-hidden rounded-md border">
|
||||||
<Table className={cn("aui-md-table", className)} {...props} />
|
<Table className={cn("aui-md-table", className)} {...props} />
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
|
|
@ -520,7 +503,7 @@ const defaultComponents = memoizeMarkdownComponents({
|
||||||
return (
|
return (
|
||||||
<code
|
<code
|
||||||
className={cn(
|
className={cn(
|
||||||
"aui-md-inline-code rounded-md border bg-muted px-1.5 py-0.5 font-mono text-[0.9em] font-normal",
|
"aui-md-inline-code rounded-md bg-primary/10 px-1.5 py-0.5 font-mono text-[0.9em] font-normal text-primary/80",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -533,7 +516,7 @@ const defaultComponents = memoizeMarkdownComponents({
|
||||||
return (
|
return (
|
||||||
<code
|
<code
|
||||||
className={cn(
|
className={cn(
|
||||||
"aui-md-inline-code rounded-md border bg-muted px-1.5 py-0.5 font-mono text-[0.9em] font-normal",
|
"aui-md-inline-code rounded-md bg-primary/10 px-1.5 py-0.5 font-mono text-[0.9em] font-normal text-primary/80",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
|
||||||
|
|
@ -916,8 +916,8 @@ const ComposerAction: FC<ComposerActionProps> = ({ isBlockedByOtherUser = false
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
className="size-9 rounded-full p-1 font-semibold text-xs text-muted-foreground dark:border-muted-foreground/15 hover:bg-accent hover:text-accent-foreground"
|
className="h-9 w-9 rounded-full p-0 font-semibold text-xs text-muted-foreground transition-colors dark:border-muted-foreground/15 hover:bg-foreground/10 hover:text-foreground"
|
||||||
aria-label="More actions"
|
aria-label="Upload files, connect tools and more"
|
||||||
data-joyride="connector-icon"
|
data-joyride="connector-icon"
|
||||||
>
|
>
|
||||||
<Plus className="size-5" />
|
<Plus className="size-5" />
|
||||||
|
|
@ -1066,13 +1066,13 @@ const ComposerAction: FC<ComposerActionProps> = ({ isBlockedByOtherUser = false
|
||||||
<DropdownMenu onOpenChange={(open) => !open && setToolsPopoverOpen(false)}>
|
<DropdownMenu onOpenChange={(open) => !open && setToolsPopoverOpen(false)}>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<TooltipIconButton
|
<TooltipIconButton
|
||||||
tooltip="More actions"
|
tooltip="Upload files, connect tools and more"
|
||||||
side="bottom"
|
side="bottom"
|
||||||
disableTooltip={toolsPopoverOpen}
|
disableTooltip={toolsPopoverOpen}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
className="size-9 rounded-full p-1 font-semibold text-xs text-muted-foreground dark:border-muted-foreground/15 hover:bg-accent hover:text-accent-foreground"
|
className="h-9 w-9 rounded-full p-0 font-semibold text-xs text-muted-foreground transition-colors dark:border-muted-foreground/15 hover:bg-foreground/10 hover:text-foreground"
|
||||||
aria-label="More actions"
|
aria-label="Upload files, connect tools and more"
|
||||||
data-joyride="connector-icon"
|
data-joyride="connector-icon"
|
||||||
>
|
>
|
||||||
<Plus className="size-5" />
|
<Plus className="size-5" />
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ function TooltipContent({
|
||||||
data-slot="tooltip-content"
|
data-slot="tooltip-content"
|
||||||
sideOffset={sideOffset}
|
sideOffset={sideOffset}
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-neutral-800 text-white font-medium shadow-md shadow-neutral-950/40 px-3 py-1.5 dark:bg-neutral-800 dark:text-white border-none animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit rounded-md text-xs text-pretty pointer-events-none select-none",
|
"bg-neutral-950 text-white font-medium px-3 py-1.5 dark:bg-neutral-950 dark:text-white border-none animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit rounded-md text-xs text-pretty pointer-events-none select-none",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue