| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348 |
- import { createStore, reconcile } from "solid-js/store"
- import { createEffect, createMemo } from "solid-js"
- import { createSimpleContext } from "@kirincode-ai/ui/context"
- import { persisted } from "@/utils/persist"
- export interface NotificationSettings {
- agent: boolean
- permissions: boolean
- errors: boolean
- }
- export interface SoundSettings {
- agentEnabled: boolean
- agent: string
- permissionsEnabled: boolean
- permissions: string
- errorsEnabled: boolean
- errors: string
- }
- export interface Settings {
- general: {
- autoSave: boolean
- releaseNotes: boolean
- followup: "queue" | "steer"
- showFileTree: boolean
- showNavigation: boolean
- showSearch: boolean
- showStatus: boolean
- showTerminal: boolean
- showReasoningSummaries: boolean
- shellToolPartsExpanded: boolean
- editToolPartsExpanded: boolean
- showCustomAgents: boolean
- mobileTitlebarPosition: "top" | "bottom"
- newLayoutDesigns?: boolean
- }
- appearance: {
- fontSize: number
- mono: string
- sans: string
- terminal: string
- }
- keybinds: Record<string, string>
- permissions: {
- autoApprove: boolean
- }
- notifications: NotificationSettings
- sounds: SoundSettings
- }
- export const monoDefault = "System Mono"
- export const sansDefault = "System Sans"
- export const terminalDefault = "JetBrainsMono Nerd Font Mono"
- export const newLayoutDesignsDefault = import.meta.env.VITE_KIRINCODE_CHANNEL !== "prod"
- const monoFallback =
- 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace'
- const sansFallback = 'ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'
- const terminalFallback =
- '"JetBrainsMono Nerd Font Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace'
- const monoBase = monoFallback
- const sansBase = sansFallback
- const terminalBase = terminalFallback
- function input(font: string | undefined) {
- return font ?? ""
- }
- function family(font: string) {
- if (/^[\w-]+$/.test(font)) return font
- return `"${font.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`
- }
- function stack(font: string | undefined, base: string) {
- const value = font?.trim() ?? ""
- if (!value) return base
- return `${family(value)}, ${base}`
- }
- export function monoInput(font: string | undefined) {
- return input(font)
- }
- export function sansInput(font: string | undefined) {
- return input(font)
- }
- export function monoFontFamily(font: string | undefined) {
- return stack(font, monoBase)
- }
- export function sansFontFamily(font: string | undefined) {
- return stack(font, sansBase)
- }
- export function terminalInput(font: string | undefined) {
- return input(font)
- }
- export function terminalFontFamily(font: string | undefined) {
- return stack(font, terminalBase)
- }
- const defaultSettings: Settings = {
- general: {
- autoSave: true,
- releaseNotes: true,
- followup: "steer",
- showFileTree: false,
- showNavigation: false,
- showSearch: false,
- showStatus: false,
- showTerminal: false,
- showReasoningSummaries: false,
- shellToolPartsExpanded: false,
- editToolPartsExpanded: false,
- showCustomAgents: false,
- mobileTitlebarPosition: "top",
- },
- appearance: {
- fontSize: 14,
- mono: "",
- sans: "",
- terminal: "",
- },
- keybinds: {},
- permissions: {
- autoApprove: false,
- },
- notifications: {
- agent: true,
- permissions: true,
- errors: false,
- },
- sounds: {
- agentEnabled: true,
- agent: "staplebops-01",
- permissionsEnabled: true,
- permissions: "staplebops-02",
- errorsEnabled: true,
- errors: "nope-03",
- },
- }
- function withFallback<T>(read: () => T | undefined, fallback: T) {
- return createMemo(() => read() ?? fallback)
- }
- export const { use: useSettings, provider: SettingsProvider } = createSimpleContext({
- name: "Settings",
- gate: false,
- init: () => {
- const [store, setStore, _, ready] = persisted("settings.v3", createStore<Settings>(defaultSettings))
- const showFileTree = withFallback(() => store.general?.showFileTree, defaultSettings.general.showFileTree)
- const showSearch = withFallback(() => store.general?.showSearch, defaultSettings.general.showSearch)
- const showStatus = withFallback(() => store.general?.showStatus, defaultSettings.general.showStatus)
- const showCustomAgents = withFallback(
- () => store.general?.showCustomAgents,
- defaultSettings.general.showCustomAgents,
- )
- const newLayoutDesigns = withFallback(() => store.general?.newLayoutDesigns, newLayoutDesignsDefault)
- const visible = (preference: () => boolean) => createMemo(() => !newLayoutDesigns() || preference())
- createEffect(() => {
- if (typeof document === "undefined") return
- const root = document.documentElement
- root.style.setProperty("--font-family-mono", monoFontFamily(store.appearance?.mono))
- root.style.setProperty("--font-family-sans", sansFontFamily(store.appearance?.sans))
- })
- createEffect(() => {
- if (store.general?.followup !== "queue") return
- setStore("general", "followup", "steer")
- })
- return {
- ready,
- get current() {
- return store
- },
- general: {
- autoSave: withFallback(() => store.general?.autoSave, defaultSettings.general.autoSave),
- setAutoSave(value: boolean) {
- setStore("general", "autoSave", value)
- },
- releaseNotes: withFallback(() => store.general?.releaseNotes, defaultSettings.general.releaseNotes),
- setReleaseNotes(value: boolean) {
- setStore("general", "releaseNotes", value)
- },
- followup: withFallback(
- () => (store.general?.followup === "queue" ? "steer" : store.general?.followup),
- defaultSettings.general.followup,
- ),
- setFollowup(value: "queue" | "steer") {
- setStore("general", "followup", value === "queue" ? "steer" : value)
- },
- showFileTree,
- setShowFileTree(value: boolean) {
- setStore("general", "showFileTree", value)
- },
- showNavigation: withFallback(() => store.general?.showNavigation, defaultSettings.general.showNavigation),
- setShowNavigation(value: boolean) {
- setStore("general", "showNavigation", value)
- },
- showSearch,
- setShowSearch(value: boolean) {
- setStore("general", "showSearch", value)
- },
- showStatus,
- setShowStatus(value: boolean) {
- setStore("general", "showStatus", value)
- },
- showTerminal: withFallback(() => store.general?.showTerminal, defaultSettings.general.showTerminal),
- setShowTerminal(value: boolean) {
- setStore("general", "showTerminal", value)
- },
- showReasoningSummaries: withFallback(
- () => store.general?.showReasoningSummaries,
- defaultSettings.general.showReasoningSummaries,
- ),
- setShowReasoningSummaries(value: boolean) {
- setStore("general", "showReasoningSummaries", value)
- },
- shellToolPartsExpanded: withFallback(
- () => store.general?.shellToolPartsExpanded,
- defaultSettings.general.shellToolPartsExpanded,
- ),
- setShellToolPartsExpanded(value: boolean) {
- setStore("general", "shellToolPartsExpanded", value)
- },
- editToolPartsExpanded: withFallback(
- () => store.general?.editToolPartsExpanded,
- defaultSettings.general.editToolPartsExpanded,
- ),
- setEditToolPartsExpanded(value: boolean) {
- setStore("general", "editToolPartsExpanded", value)
- },
- showCustomAgents,
- setShowCustomAgents(value: boolean) {
- setStore("general", "showCustomAgents", value)
- },
- mobileTitlebarPosition: withFallback(
- () => store.general?.mobileTitlebarPosition,
- defaultSettings.general.mobileTitlebarPosition,
- ),
- setMobileTitlebarPosition(value: "top" | "bottom") {
- setStore("general", "mobileTitlebarPosition", value)
- },
- newLayoutDesigns,
- setNewLayoutDesigns(value: boolean) {
- setStore("general", "newLayoutDesigns", value)
- },
- },
- visibility: {
- fileTree: visible(showFileTree),
- search: visible(showSearch),
- status: visible(showStatus),
- customAgents: visible(showCustomAgents),
- },
- appearance: {
- fontSize: withFallback(() => store.appearance?.fontSize, defaultSettings.appearance.fontSize),
- setFontSize(value: number) {
- setStore("appearance", "fontSize", value)
- },
- font: withFallback(() => store.appearance?.mono, defaultSettings.appearance.mono),
- setFont(value: string) {
- setStore("appearance", "mono", value.trim() ? value : "")
- },
- uiFont: withFallback(() => store.appearance?.sans, defaultSettings.appearance.sans),
- setUIFont(value: string) {
- setStore("appearance", "sans", value.trim() ? value : "")
- },
- terminalFont: withFallback(() => store.appearance?.terminal, defaultSettings.appearance.terminal),
- setTerminalFont(value: string) {
- setStore("appearance", "terminal", value.trim() ? value : "")
- },
- },
- keybinds: {
- get: (action: string) => store.keybinds?.[action],
- set(action: string, keybind: string) {
- setStore("keybinds", action, keybind)
- },
- reset(action: string) {
- setStore("keybinds", (current) => {
- if (!Object.prototype.hasOwnProperty.call(current, action)) return current
- const next = { ...current }
- delete next[action]
- return next
- })
- },
- resetAll() {
- setStore("keybinds", reconcile({}))
- },
- },
- permissions: {
- autoApprove: withFallback(() => store.permissions?.autoApprove, defaultSettings.permissions.autoApprove),
- setAutoApprove(value: boolean) {
- setStore("permissions", "autoApprove", value)
- },
- },
- notifications: {
- agent: withFallback(() => store.notifications?.agent, defaultSettings.notifications.agent),
- setAgent(value: boolean) {
- setStore("notifications", "agent", value)
- },
- permissions: withFallback(() => store.notifications?.permissions, defaultSettings.notifications.permissions),
- setPermissions(value: boolean) {
- setStore("notifications", "permissions", value)
- },
- errors: withFallback(() => store.notifications?.errors, defaultSettings.notifications.errors),
- setErrors(value: boolean) {
- setStore("notifications", "errors", value)
- },
- },
- sounds: {
- agentEnabled: withFallback(() => store.sounds?.agentEnabled, defaultSettings.sounds.agentEnabled),
- setAgentEnabled(value: boolean) {
- setStore("sounds", "agentEnabled", value)
- },
- agent: withFallback(() => store.sounds?.agent, defaultSettings.sounds.agent),
- setAgent(value: string) {
- setStore("sounds", "agent", value)
- },
- permissionsEnabled: withFallback(
- () => store.sounds?.permissionsEnabled,
- defaultSettings.sounds.permissionsEnabled,
- ),
- setPermissionsEnabled(value: boolean) {
- setStore("sounds", "permissionsEnabled", value)
- },
- permissions: withFallback(() => store.sounds?.permissions, defaultSettings.sounds.permissions),
- setPermissions(value: string) {
- setStore("sounds", "permissions", value)
- },
- errorsEnabled: withFallback(() => store.sounds?.errorsEnabled, defaultSettings.sounds.errorsEnabled),
- setErrorsEnabled(value: boolean) {
- setStore("sounds", "errorsEnabled", value)
- },
- errors: withFallback(() => store.sounds?.errors, defaultSettings.sounds.errors),
- setErrors(value: string) {
- setStore("sounds", "errors", value)
- },
- },
- }
- },
- })
|