diff options
Diffstat (limited to 'apps/web/app')
| -rw-r--r-- | apps/web/app/dashboard/settings/ai/page.tsx | 325 | ||||
| -rw-r--r-- | apps/web/app/dashboard/settings/page.tsx | 70 |
2 files changed, 49 insertions, 346 deletions
diff --git a/apps/web/app/dashboard/settings/ai/page.tsx b/apps/web/app/dashboard/settings/ai/page.tsx deleted file mode 100644 index 009919a0..00000000 --- a/apps/web/app/dashboard/settings/ai/page.tsx +++ /dev/null @@ -1,325 +0,0 @@ -"use client"; - -import { ActionButton } from "@/components/ui/action-button"; -import { - Form, - FormControl, - FormField, - FormItem, - FormMessage, -} from "@/components/ui/form"; -import { FullPageSpinner } from "@/components/ui/full-page-spinner"; -import { Input } from "@/components/ui/input"; -import { - Select, - SelectContent, - SelectGroup, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { toast } from "@/components/ui/use-toast"; -import { useClientConfig } from "@/lib/clientConfig"; -import { api } from "@/lib/trpc"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { Plus, Save, Trash2 } from "lucide-react"; -import { useForm } from "react-hook-form"; -import { z } from "zod"; - -import { buildImagePrompt, buildTextPrompt } from "@hoarder/shared/prompts"; -import { - zNewPromptSchema, - ZPrompt, - zUpdatePromptSchema, -} from "@hoarder/shared/types/prompts"; - -export function PromptEditor() { - const apiUtils = api.useUtils(); - - const form = useForm<z.infer<typeof zNewPromptSchema>>({ - resolver: zodResolver(zNewPromptSchema), - defaultValues: { - text: "", - appliesTo: "all", - }, - }); - - const { mutateAsync: createPrompt, isPending: isCreating } = - api.prompts.create.useMutation({ - onSuccess: () => { - toast({ - description: "Prompt has been created!", - }); - apiUtils.prompts.list.invalidate(); - }, - }); - - return ( - <Form {...form}> - <form - className="flex gap-2" - onSubmit={form.handleSubmit(async (value) => { - await createPrompt(value); - form.resetField("text"); - })} - > - <FormField - control={form.control} - name="text" - render={({ field }) => { - return ( - <FormItem className="flex-1"> - <FormControl> - <Input - placeholder="Add a custom prompt" - type="text" - {...field} - /> - </FormControl> - <FormMessage /> - </FormItem> - ); - }} - /> - - <FormField - control={form.control} - name="appliesTo" - render={({ field }) => { - return ( - <FormItem className="flex-0"> - <FormControl> - <Select - onValueChange={field.onChange} - defaultValue={field.value} - > - <SelectTrigger> - <SelectValue placeholder="Applies To" /> - </SelectTrigger> - <SelectContent> - <SelectGroup> - <SelectItem value="all">All</SelectItem> - <SelectItem value="text">Text</SelectItem> - <SelectItem value="images">Images</SelectItem> - </SelectGroup> - </SelectContent> - </Select> - </FormControl> - <FormMessage /> - </FormItem> - ); - }} - /> - <ActionButton - type="submit" - loading={isCreating} - variant="default" - className="items-center" - > - <Plus className="mr-2 size-4" /> - Add - </ActionButton> - </form> - </Form> - ); -} - -export function PromptRow({ prompt }: { prompt: ZPrompt }) { - const apiUtils = api.useUtils(); - const { mutateAsync: updatePrompt, isPending: isUpdating } = - api.prompts.update.useMutation({ - onSuccess: () => { - toast({ - description: "Prompt has been updated!", - }); - apiUtils.prompts.list.invalidate(); - }, - }); - const { mutate: deletePrompt, isPending: isDeleting } = - api.prompts.delete.useMutation({ - onSuccess: () => { - toast({ - description: "Prompt has been deleted!", - }); - apiUtils.prompts.list.invalidate(); - }, - }); - - const form = useForm<z.infer<typeof zUpdatePromptSchema>>({ - resolver: zodResolver(zUpdatePromptSchema), - defaultValues: { - promptId: prompt.id, - text: prompt.text, - appliesTo: prompt.appliesTo, - }, - }); - - return ( - <Form {...form}> - <form - className="flex gap-2" - onSubmit={form.handleSubmit(async (value) => { - await updatePrompt(value); - })} - > - <FormField - control={form.control} - name="promptId" - render={({ field }) => { - return ( - <FormItem className="hidden"> - <FormControl> - <Input - placeholder="Add a custom prompt" - type="hidden" - {...field} - /> - </FormControl> - <FormMessage /> - </FormItem> - ); - }} - /> - <FormField - control={form.control} - name="text" - render={({ field }) => { - return ( - <FormItem className="flex-1"> - <FormControl> - <Input - placeholder="Add a custom prompt" - type="text" - {...field} - /> - </FormControl> - <FormMessage /> - </FormItem> - ); - }} - /> - - <FormField - control={form.control} - name="appliesTo" - render={({ field }) => { - return ( - <FormItem className="flex-0"> - <FormControl> - <Select - onValueChange={field.onChange} - defaultValue={field.value} - > - <SelectTrigger> - <SelectValue placeholder="Applies To" /> - </SelectTrigger> - <SelectContent> - <SelectGroup> - <SelectItem value="all">All</SelectItem> - <SelectItem value="text">Text</SelectItem> - <SelectItem value="images">Images</SelectItem> - </SelectGroup> - </SelectContent> - </Select> - </FormControl> - <FormMessage /> - </FormItem> - ); - }} - /> - <ActionButton - loading={isUpdating} - variant="secondary" - type="submit" - className="items-center" - > - <Save className="mr-2 size-4" /> - Save - </ActionButton> - <ActionButton - loading={isDeleting} - variant="destructive" - onClick={() => deletePrompt({ promptId: prompt.id })} - className="items-center" - type="button" - > - <Trash2 className="mr-2 size-4" /> - Delete - </ActionButton> - </form> - </Form> - ); -} - -export function TaggingRules() { - const { data: prompts, isLoading } = api.prompts.list.useQuery(); - - return ( - <div className="mt-2 flex flex-col gap-2"> - <div className="w-full text-xl font-medium sm:w-1/3">Tagging Rules</div> - <p className="mb-1 text-xs italic text-muted-foreground"> - Prompts that you add here will be included as rules to the model during - tag generation. You can view the final prompts in the prompt preview - section. - </p> - {isLoading && <FullPageSpinner />} - {prompts && prompts.length == 0 && ( - <p className="rounded-md bg-muted p-2 text-sm text-muted-foreground"> - You don't have any custom prompts yet. - </p> - )} - {prompts && - prompts.map((prompt) => <PromptRow key={prompt.id} prompt={prompt} />)} - <PromptEditor /> - </div> - ); -} - -export function PromptDemo() { - const { data: prompts } = api.prompts.list.useQuery(); - const clientConfig = useClientConfig(); - return ( - <div className="flex flex-col gap-2"> - <div className="mb-4 w-full text-xl font-medium sm:w-1/3"> - Prompt Preview - </div> - <p>Text Prompt</p> - <code className="whitespace-pre-wrap rounded-md bg-muted p-3 text-sm text-muted-foreground"> - {buildTextPrompt( - clientConfig.inference.inferredTagLang, - (prompts ?? []) - .filter((p) => p.appliesTo == "text" || p.appliesTo == "all") - .map((p) => p.text), - "\n<CONTENT_HERE>\n", - ).trim()} - </code> - <p>Image Prompt</p> - <code className="whitespace-pre-wrap rounded-md bg-muted p-3 text-sm text-muted-foreground"> - {buildImagePrompt( - clientConfig.inference.inferredTagLang, - (prompts ?? []) - .filter((p) => p.appliesTo == "images" || p.appliesTo == "all") - .map((p) => p.text), - ).trim()} - </code> - </div> - ); -} - -export default function PromptsPage() { - return ( - <> - <div className="rounded-md border bg-background p-4"> - <div className="mb-2 flex flex-col gap-3"> - <div className="w-full text-2xl font-medium sm:w-1/3"> - AI Settings - </div> - <TaggingRules /> - </div> - </div> - <div className="mt-4 rounded-md border bg-background p-4"> - <PromptDemo /> - </div> - </> - ); -} diff --git a/apps/web/app/dashboard/settings/page.tsx b/apps/web/app/dashboard/settings/page.tsx index 97048657..c116fb86 100644 --- a/apps/web/app/dashboard/settings/page.tsx +++ b/apps/web/app/dashboard/settings/page.tsx @@ -1,32 +1,60 @@ -import Link from "next/link"; +import AISettings from "@/components/dashboard/settings/AISettings"; import ApiKeySettings from "@/components/dashboard/settings/ApiKeySettings"; import { ChangePassword } from "@/components/dashboard/settings/ChangePassword"; import ImportExport from "@/components/dashboard/settings/ImportExport"; import UserDetails from "@/components/dashboard/settings/UserDetails"; -import { ExternalLink } from "lucide-react"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { Download, KeyRound, Sparkle, User } from "lucide-react"; export default async function Settings() { return ( - <> - <div className="rounded-md border bg-background p-4"> - <UserDetails /> - <ChangePassword /> - </div> - <div className="mt-4 rounded-md border bg-background p-4"> - <Link - className="flex items-center gap-2 text-lg font-medium" - href="/dashboard/settings/ai" - > + <Tabs + defaultValue="info" + orientation="horizontal" + className="flex flex-col gap-1" + > + <TabsList className="flex justify-start"> + <TabsTrigger className="flex items-center gap-2 p-3" value="info"> + <User className="size-4" /> + User Info + </TabsTrigger> + <TabsTrigger className="flex items-center gap-2 p-3" value="ai"> + <Sparkle className="size-4" /> AI Settings - <ExternalLink /> - </Link> - </div> - <div className="mt-4 rounded-md border bg-background p-4"> - <ImportExport /> - </div> - <div className="mt-4 rounded-md border bg-background p-4"> - <ApiKeySettings /> + </TabsTrigger> + <TabsTrigger + className="flex items-center gap-2 p-3" + value="importexport" + > + <Download className="size-4" /> + Import / Export + </TabsTrigger> + <TabsTrigger className="flex items-center gap-2 p-3" value="apikeys"> + <KeyRound className="size-4" /> + API Keys + </TabsTrigger> + </TabsList> + <div className="w-full"> + <TabsContent value="info"> + <div className="rounded-md border bg-background p-4"> + <UserDetails /> + <ChangePassword /> + </div> + </TabsContent> + <TabsContent value="ai"> + <AISettings /> + </TabsContent> + <TabsContent value="importexport"> + <div className="rounded-md border bg-background p-4"> + <ImportExport /> + </div> + </TabsContent> + <TabsContent value="apikeys"> + <div className="rounded-md border bg-background p-4"> + <ApiKeySettings /> + </div> + </TabsContent> </div> - </> + </Tabs> ); } |
