+
{children}
);
@@ -845,6 +880,122 @@ const DelimeterComponent: React.FC = () => (
);
+const PresetManager: FC<{
+ config: Configuration;
+ onLoadConfig: (config: Configuration) => void;
+}> = ({ config, onLoadConfig }) => {
+ const { presets, savePreset, removePreset } = useAppContext();
+ const { showConfirm, showPrompt } = useModals();
+
+ const handleSavePreset = async () => {
+ const newPresetName = (
+ (await showPrompt('Enter the new preset name')) || ''
+ ).trim();
+ if (newPresetName === '') return;
+
+ const existingPreset = presets.find((p) => p.name === newPresetName);
+ if (
+ !existingPreset ||
+ (await showConfirm(
+ `Preset "${newPresetName}" already exists, overwrite it?`
+ ))
+ ) {
+ await savePreset(newPresetName, config);
+ }
+ };
+
+ const handleLoadPreset = async (preset: ConfigurationPreset) => {
+ if (
+ await showConfirm(
+ `Load preset "${preset.name}"? Current settings will be replaced.`
+ )
+ ) {
+ onLoadConfig(
+ Object.assign(Object.assign({}, CONFIG_DEFAULT), preset.config)
+ );
+ }
+ };
+
+ const handleDeletePreset = async (preset: ConfigurationPreset) => {
+ if (await showConfirm(`Delete preset "${preset.name}"?`)) {
+ await removePreset(preset.name);
+ }
+ };
+
+ return (
+ <>
+ {/* Save new preset */}
+
+ {lang.settings.presetManager.newPreset.title}
+
+
+
+
+ {/* List of saved presets */}
+
+ {lang.settings.presetManager.savedPresets.title}
+
+
+ {presets.length === 0 && (
+
+ )}
+
+ {presets.length > 0 && (
+
+ {presets
+ .sort((a, b) => b.createdAt - a.createdAt)
+ .map((preset) => (
+
+
+
+
{preset.name}
+
+ Created: {dateFormatter.format(preset.createdAt)}
+
+
+
+
+
+
+
+
+
+ ))}
+
+ )}
+ >
+ );
+};
+
const ImportExportComponent: React.FC<{ onClose: () => void }> = ({
onClose,
}) => {
diff --git a/src/context/app.context.tsx b/src/context/app.context.tsx
index 4310480..72ea009 100644
--- a/src/context/app.context.tsx
+++ b/src/context/app.context.tsx
@@ -1,11 +1,15 @@
import React, { createContext, useContext, useEffect, useState } from 'react';
+import toast from 'react-hot-toast';
import { CONFIG_DEFAULT, isDev } from '../config';
import StorageUtils from '../utils/storage';
-import { Configuration } from '../utils/types';
+import { Configuration, ConfigurationPreset } from '../utils/types';
interface AppContextValue {
config: Configuration;
saveConfig: (config: Configuration) => void;
+ presets: ConfigurationPreset[];
+ savePreset: (name: string, config: Configuration) => Promise
;
+ removePreset: (name: string) => Promise;
showSettings: boolean;
setShowSettings: (show: boolean) => void;
}
@@ -13,6 +17,9 @@ interface AppContextValue {
const AppContext = createContext({
config: {} as Configuration,
saveConfig: () => {},
+ presets: [],
+ savePreset: () => new Promise(() => {}),
+ removePreset: () => new Promise(() => {}),
showSettings: false,
setShowSettings: () => {},
});
@@ -23,11 +30,18 @@ export const AppContextProvider = ({
children: React.ReactElement;
}) => {
const [config, setConfig] = useState(CONFIG_DEFAULT);
+ const [presets, setPresets] = useState([]);
const [showSettings, setShowSettings] = useState(false);
useEffect(() => {
- if (isDev) console.debug('Load config');
- setConfig(StorageUtils.getConfig());
+ const init = async () => {
+ if (isDev) console.debug('Load config');
+ setConfig(StorageUtils.getConfig());
+
+ if (isDev) console.debug('Load presets');
+ setPresets(await StorageUtils.getPresets());
+ };
+ init();
}, []);
const saveConfig = (config: Configuration) => {
@@ -36,11 +50,28 @@ export const AppContextProvider = ({
setConfig(config);
};
+ const savePreset = async (name: string, config: Configuration) => {
+ if (isDev) console.debug('Save preset', { name, config });
+ StorageUtils.savePreset(name, config);
+ setPresets(await StorageUtils.getPresets());
+ toast.success('Preset is saved successfully');
+ };
+
+ const removePreset = async (name: string) => {
+ if (isDev) console.debug('Remove preset', name);
+ StorageUtils.removePreset(name);
+ setPresets(await StorageUtils.getPresets());
+ toast.success('Preset is removed successfully');
+ };
+
return (
{
const db = new Dexie('LlamacppWebui') as Dexie & {
conversations: Table;
messages: Table;
+ userConfigurations: Table;
};
// Define database schema
@@ -59,6 +61,8 @@ db.version(1).stores({
conversations: '&id, lastModified',
// Index messages by 'id' (unique), 'convId', composite key '[convId+id]', and 'timestamp'
messages: '&id, convId, [convId+id], timestamp',
+ // Index userConfigurations by 'id' (unique) and 'name'
+ userConfigurations: '&id, name',
});
// --- Main Storage Utility Functions ---
@@ -435,6 +439,51 @@ const StorageUtils = {
localStorage.setItem('theme', theme);
}
},
+
+ /**
+ * Retrieves the user's configuration presets.
+ * @returns The array of configuration preset.
+ */
+ async getPresets() {
+ const presets = await db.transaction(
+ 'r',
+ db.userConfigurations,
+ async () => {
+ return await db.userConfigurations.toArray();
+ }
+ );
+ return presets;
+ },
+
+ /**
+ * Saves the user's configuration preset to localStorage, replacing the existing one.
+ * @param name The preset name to save.
+ * @param config The Configuration object to save.
+ */
+ async savePreset(name: string, config: Configuration, id?: string) {
+ const now = Date.now();
+ const newPreset: ConfigurationPreset = {
+ id: id || `config-${now}`,
+ name,
+ createdAt: now,
+ config,
+ };
+ return await db.transaction('rw', db.userConfigurations, async () => {
+ await db.userConfigurations.where('name').equals(name).delete();
+ await db.userConfigurations.add(newPreset);
+ return newPreset;
+ });
+ },
+
+ /**
+ * Removes the user's configuration preset.
+ * @param name The preset name to remove.
+ */
+ async removePreset(name: string) {
+ return await db.transaction('rw', db.userConfigurations, async () => {
+ return await db.userConfigurations.where('name').equals(name).delete();
+ });
+ },
};
export default StorageUtils;
diff --git a/src/utils/types.ts b/src/utils/types.ts
index 3d77d40..91ceff4 100644
--- a/src/utils/types.ts
+++ b/src/utils/types.ts
@@ -65,6 +65,13 @@ export interface Configuration {
}
export type ConfigurationKey = keyof Configuration;
+export interface ConfigurationPreset {
+ id: string;
+ name: string;
+ createdAt: number;
+ config: Partial;
+}
+
export interface InferenceProviders {
[key: string]: {
baseUrl: string;