import { Component, createSignal, For, JSXElement, Signal } from "solid-js"; import { CheckIcon, ChevronsUpDownIcon, CogIcon, PipetteIcon, PlusIcon, Trash2Icon, XIcon, } from "lucide-solid"; import { Button } from "~/components/ui/button"; import { Field } from "~/components/ui/field"; import { Stack, Box, HStack } from "styled-system/jsx"; import { IconButton } from "~/components/ui/icon-button"; import { FormLabel } from "~/components/ui/form-label"; import { Checkbox } from "~/components/ui/checkbox"; import { Drawer } from "~/components/ui/drawer"; import { Text } from "~/components/ui/text"; import { Handle, isHandle } from "@atcute/lexicons/syntax"; import { css } from "styled-system/css"; import { flow, sessions } from "~/lib/oauth"; import { Account, loggingIn, accounts, setAccounts, setSelectedAccount, } from "~/lib/accounts"; import { showProfilePicture as showProfilePictureSetting, showVisualizer as showVisualizerSetting, backgroundColor as backgroundColorSetting, frameRate as frameRateSetting, useDominantColorAsBg as useDominantColorAsBgSetting, autoTranscribe as autoTranscribeSetting, whisperModel as whisperModelSetting, Setting, defaultWhisperModel, } from "~/lib/settings"; import { handleResolver } from "~/lib/at"; import { toaster } from "~/components/Toaster"; import { createListCollection, Select } from "~/components/ui/select"; import { type Color, type ListCollection, parseColor } from "@ark-ui/solid"; import { ColorPicker } from "~/components/ui/color-picker"; import { Input } from "~/components/ui/input"; import { preloadModel } from "~/lib/transcribe"; const SettingCheckbox = (props: { setting: Setting; signal: Signal; label: string; disabled?: boolean; }) => ( { const val = e.checked === "indeterminate" ? false : e.checked; props.signal[1](val); props.setting.set(val); }} disabled={props.disabled} colorPalette={props.disabled ? "gray" : undefined} cursor={props.disabled ? { _hover: "not-allowed" } : undefined} > {props.label} ); const SettingSelect = (props: { label: string; signal: Signal; collection: ListCollection<{ label: string; value: string }>; }) => ( props.signal[1](details.value[0])} collection={props.collection} > {props.label} {(item) => ( {item.label} )} ); const SettingColorPicker = (props: { label: string; signal: Signal; }) => { return ( props.signal[1](e.value)} onValueChangeEnd={(e) => props.signal[1](e.value)} > {(api) => ( <> {props.label} } /> ( )} /> ( )} /> ( )} /> ( )} /> )} ); }; const Category = ({ title, children, }: { title: string; children: JSXElement; }) => ( {title} {children} ); const Settings = () => { const [handle, setHandle] = createSignal(""); const isHandleValid = () => isHandle(handle()); const deleteAccount = (account: Account) => { const newAccounts = accounts().filter((a) => a.did !== account.did); setAccounts(newAccounts); sessions.remove(account.did); setSelectedAccount(newAccounts[0]?.did ?? undefined); }; const startAccountFlow = async () => { try { toaster.create({ title: "logging in", description: `logging in to ${handle()}...`, type: "info", }); const did = await handleResolver.resolve(handle() as Handle); loggingIn.set(did); await flow.start(did); } catch (err) { console.error(err); toaster.create({ title: "login error", description: `${err}`, type: "error", }); loggingIn.set(undefined); } }; const Accounts = () => { const item = (account: Account, isLatest: boolean) => ( {account.handle ? `@${account.handle}` : account.did}
deleteAccount(account)} variant="ghost" size="sm" > ); const items = (accounts: Account[]) => ( no accounts added } > {(acc, idx) => item(acc, idx() === accounts.length - 1)} ); return ( setHandle(e.currentTarget.value)} /> {items(accounts())} ); }; const [showProfilePicture, setShowProfilePicture] = createSignal( showProfilePictureSetting.get() ?? true, ); const [showVisualizer, setShowVisualizer] = createSignal( showVisualizerSetting.get() ?? true, ); const [useDominantColorAsBg, setUseDominantColorAsBg] = createSignal( useDominantColorAsBgSetting.get() ?? true, ); const frameRateCollection = createListCollection({ items: [24, 30, 60].map((rate) => ({ label: `${rate} FPS`, value: rate.toString(), })), }); const [frameRate, _setFrameRate] = createSignal( (frameRateSetting.get() ?? 24).toString(), ); const setFrameRate = (value: string | ((prev: string) => string)) => { const newFrameRate = _setFrameRate(value); frameRateSetting.set(parseInt(newFrameRate)); }; const [backgroundColor, _setBackgroundColor] = createSignal( parseColor(backgroundColorSetting.get() ?? "#000000"), ); const setBackgroundColor = (value: Color | ((prev: Color) => Color)) => { const newColor = _setBackgroundColor(value); backgroundColorSetting.set(newColor.toString("rgb")); }; const whisperModelCollection = createListCollection({ items: [ { tag: "tiny", size: "40MB" }, { tag: "base", size: "80MB" }, { tag: "small", size: "250MB" }, ].map((model) => ({ label: `${model.tag} (${model.size})`, value: `onnx-community/whisper-${model.tag}`, })), }); const [whisperModel, _setWhisperModel] = createSignal( (whisperModelSetting.get() ?? defaultWhisperModel).toString(), ); const setWhisperModel = (value: string | ((prev: string) => string)) => { const newModel = _setWhisperModel(value); whisperModelSetting.set(newModel); if (autoTranscribe()) setTimeout(() => preloadModel(), 200); }; const [autoTranscribe, setAutoTranscribe] = createSignal( autoTranscribeSetting.get() ?? false, ); return ( ( )} /> settings
( )} />
{ const newVal = setAutoTranscribe(val); if (newVal) preloadModel(); return val; }, ]} /> note: the model will only be downloaded once. ( )} />
); }; export default Settings;