aboutsummaryrefslogtreecommitdiffstats
path: root/apps/web/app
diff options
context:
space:
mode:
Diffstat (limited to 'apps/web/app')
-rw-r--r--apps/web/app/dashboard/settings/ai/page.tsx325
-rw-r--r--apps/web/app/dashboard/settings/page.tsx70
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&apos;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>
);
}