The weeb for the next gen discord boat - Wamellow wamellow.com
bot discord
3
fork

Configure Feed

Select the types of activity you want to include in your feed.

add nsfw threshold slider

Luna 277572e3 3a4db7c0

+175 -1
+10 -1
app/dashboard/[guildId]/nsfw-image-scanning/page.tsx
··· 10 10 import { guildStore } from "@/common/guilds"; 11 11 import MultiSelectMenu from "@/components/inputs/multi-select-menu"; 12 12 import SelectMenu from "@/components/inputs/select-menu"; 13 + import Slider from "@/components/inputs/slider-input"; 13 14 import Switch from "@/components/inputs/switch"; 14 15 import Notice, { NoticeType } from "@/components/notice"; 15 16 import { ScreenMessage } from "@/components/screen-message"; ··· 66 67 67 68 <Notice 68 69 type={NoticeType.Info} 69 - message="Images could be false positives of false negatives, this will not replace human moderation." 70 + message="Images can be false positives or false negatives. This does not replace human moderation." 70 71 /> 71 72 72 73 <Switch ··· 146 147 /> 147 148 </div> 148 149 </div> 150 + 151 + <Slider 152 + name="Threshold" 153 + description="The threshold at which an image should be considered NSFW; low values are sensitive, high values are lax." 154 + url={url} 155 + dataName="threshold" 156 + defaultState={0.1} 157 + /> 149 158 150 159 <span className="mb-2" > 151 160 Members with the <Code color="secondary">Manage Messages</Code> permission bypass the NSFW image scanning automatically. <br />
+41
components/inputs/request.ts
··· 1 + import { RouteErrorResponse } from "@/typings"; 2 + 3 + interface Props { 4 + key: string; 5 + body: unknown; 6 + onSuccess: () => void; 7 + onError: (err: string) => void; 8 + } 9 + 10 + export async function request(url: string, { 11 + key, 12 + body, 13 + 14 + onSuccess, 15 + onError 16 + }: Props) { 17 + const res = await fetch([process.env.NEXT_PUBLIC_API, url].join(""), { 18 + method: "PATCH", 19 + credentials: "include", 20 + headers: { 21 + "Content-Type": "application/json" 22 + }, 23 + body: JSON.stringify(key.includes(".") ? 24 + { [key.split(".")[0]]: { [key.split(".")[1]]: body } } 25 + : 26 + { [key]: body } 27 + ) 28 + }) 29 + .catch(() => null); 30 + 31 + if (res?.ok) { 32 + onSuccess(); 33 + return; 34 + } 35 + 36 + 37 + const response = await res?.json() 38 + .catch(() => null); 39 + 40 + onError((response as unknown as RouteErrorResponse | null)?.message || "unknown server error") 41 + }
+124
components/inputs/slider-input.tsx
··· 1 + import { Chip, Slider as UiSlider } from "@nextui-org/react"; 2 + import { useState } from "react"; 3 + import { TailSpin } from "react-loading-icons"; 4 + 5 + import { RouteErrorResponse } from "@/typings"; 6 + import cn from "@/utils/cn"; 7 + import { request } from "./request"; 8 + 9 + enum State { 10 + Idle = 0, 11 + Loading = 1, 12 + Success = 2 13 + } 14 + 15 + interface Props { 16 + className?: string; 17 + 18 + name: string; 19 + badge?: string; 20 + disabled?: boolean; 21 + description?: string; 22 + 23 + minValue?: number 24 + maxValue?: number; 25 + steps?: number; 26 + 27 + url: string 28 + dataName: string; 29 + defaultState: number; 30 + } 31 + 32 + export default function Slider({ 33 + className, 34 + 35 + name, 36 + badge, 37 + description, 38 + disabled, 39 + 40 + minValue = 0.1, 41 + maxValue = 1, 42 + steps = 0.1, 43 + 44 + url, 45 + dataName, 46 + defaultState, 47 + }: Props) { 48 + const [state, setState] = useState<State>(State.Idle); 49 + const [error, setError] = useState<string | null>(null); 50 + 51 + function update(now: number | number[]) { 52 + setState(State.Loading); 53 + setError(null); 54 + 55 + request(url, { 56 + key: dataName, 57 + body: now, 58 + 59 + onSuccess: () => { 60 + setState(State.Success); 61 + setTimeout(() => setState(State.Idle), 1_000 * 8); 62 + }, 63 + onError: (err) => { 64 + setState(State.Idle); 65 + setError(err); 66 + } 67 + }); 68 + } 69 + 70 + return ( 71 + <div className={cn("relative mb-4", className)}> 72 + 73 + <div> 74 + <div className="flex items-center gap-2"> 75 + <span className="sm:text-lg font-medium dark:text-neutral-400 text-neutral-600"> 76 + {name} 77 + </span> 78 + 79 + {badge && 80 + <Chip 81 + variant="flat" 82 + color="secondary" 83 + size="sm" 84 + > 85 + {badge} 86 + </Chip> 87 + } 88 + 89 + {state === State.Loading && 90 + <TailSpin stroke="#d4d4d4" strokeWidth={8} className="relative h-3 w-3 overflow-visible" /> 91 + } 92 + </div> 93 + 94 + <UiSlider 95 + size="md" 96 + step={steps} 97 + color="secondary" 98 + showSteps={true} 99 + maxValue={maxValue} 100 + minValue={minValue} 101 + defaultValue={defaultState} 102 + onChangeEnd={update} 103 + isDisabled={disabled} 104 + /> 105 + </div> 106 + 107 + 108 + <div className="flex gap-2"> 109 + {description && 110 + <div className="text-neutral-500 text-sm"> 111 + {description} 112 + </div> 113 + } 114 + 115 + {error && 116 + <div className="ml-auto text-red-500 text-sm shrink-0"> 117 + {error} 118 + </div> 119 + } 120 + </div> 121 + 122 + </div> 123 + ); 124 + }