mirror of
https://github.com/ItzCrazyKns/Perplexica.git
synced 2025-11-22 13:08:14 +00:00
Compare commits
5 Commits
22695f4ef6
...
3da53aed03
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3da53aed03 | ||
|
|
244675759c | ||
|
|
ce6a37aaff | ||
|
|
c3abba8462 | ||
|
|
f709aa8224 |
29
src/components/Settings/Sections/Personalization.tsx
Normal file
29
src/components/Settings/Sections/Personalization.tsx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { UIConfigField } from '@/lib/config/types';
|
||||||
|
import SettingsField from '../SettingsField';
|
||||||
|
|
||||||
|
const Personalization = ({
|
||||||
|
fields,
|
||||||
|
values,
|
||||||
|
}: {
|
||||||
|
fields: UIConfigField[];
|
||||||
|
values: Record<string, any>;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className="flex-1 space-y-6 overflow-y-auto px-6 py-6">
|
||||||
|
{fields.map((field) => (
|
||||||
|
<SettingsField
|
||||||
|
key={field.key}
|
||||||
|
field={field}
|
||||||
|
value={
|
||||||
|
(field.scope === 'client'
|
||||||
|
? localStorage.getItem(field.key)
|
||||||
|
: values[field.key]) ?? field.default
|
||||||
|
}
|
||||||
|
dataAdd="personalization"
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Personalization;
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { UIConfigField } from '@/lib/config/types';
|
import { UIConfigField } from '@/lib/config/types';
|
||||||
import SettingsField from '../SettingsField';
|
import SettingsField from '../SettingsField';
|
||||||
|
|
||||||
const General = ({
|
const Preferences = ({
|
||||||
fields,
|
fields,
|
||||||
values,
|
values,
|
||||||
}: {
|
}: {
|
||||||
@@ -19,11 +19,11 @@ const General = ({
|
|||||||
? localStorage.getItem(field.key)
|
? localStorage.getItem(field.key)
|
||||||
: values[field.key]) ?? field.default
|
: values[field.key]) ?? field.default
|
||||||
}
|
}
|
||||||
dataAdd="general"
|
dataAdd="preferences"
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default General;
|
export default Preferences;
|
||||||
@@ -4,9 +4,10 @@ import {
|
|||||||
BrainCog,
|
BrainCog,
|
||||||
ChevronLeft,
|
ChevronLeft,
|
||||||
Search,
|
Search,
|
||||||
Settings,
|
Sliders,
|
||||||
|
ToggleRight,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import General from './Sections/General';
|
import Preferences from './Sections/Preferences';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
@@ -15,15 +16,24 @@ import { cn } from '@/lib/utils';
|
|||||||
import Models from './Sections/Models/Section';
|
import Models from './Sections/Models/Section';
|
||||||
import SearchSection from './Sections/Search';
|
import SearchSection from './Sections/Search';
|
||||||
import Select from '@/components/ui/Select';
|
import Select from '@/components/ui/Select';
|
||||||
|
import Personalization from './Sections/Personalization';
|
||||||
|
|
||||||
const sections = [
|
const sections = [
|
||||||
{
|
{
|
||||||
key: 'general',
|
key: 'preferences',
|
||||||
name: 'General',
|
name: 'Preferences',
|
||||||
description: 'Adjust common settings.',
|
description: 'Customize your application preferences.',
|
||||||
icon: Settings,
|
icon: Sliders,
|
||||||
component: General,
|
component: Preferences,
|
||||||
dataAdd: 'general',
|
dataAdd: 'preferences',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'personalization',
|
||||||
|
name: 'Personalization',
|
||||||
|
description: 'Customize the behavior and tone of the model.',
|
||||||
|
icon: ToggleRight,
|
||||||
|
component: Personalization,
|
||||||
|
dataAdd: 'personalization',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'models',
|
key: 'models',
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
SelectUIConfigField,
|
SelectUIConfigField,
|
||||||
StringUIConfigField,
|
StringUIConfigField,
|
||||||
|
SwitchUIConfigField,
|
||||||
TextareaUIConfigField,
|
TextareaUIConfigField,
|
||||||
UIConfigField,
|
UIConfigField,
|
||||||
} from '@/lib/config/types';
|
} from '@/lib/config/types';
|
||||||
@@ -9,6 +10,7 @@ import Select from '../ui/Select';
|
|||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { useTheme } from 'next-themes';
|
import { useTheme } from 'next-themes';
|
||||||
import { Loader2 } from 'lucide-react';
|
import { Loader2 } from 'lucide-react';
|
||||||
|
import { Switch } from '@headlessui/react';
|
||||||
|
|
||||||
const SettingsSelect = ({
|
const SettingsSelect = ({
|
||||||
field,
|
field,
|
||||||
@@ -237,6 +239,79 @@ const SettingsTextarea = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const SettingsSwitch = ({
|
||||||
|
field,
|
||||||
|
value,
|
||||||
|
setValue,
|
||||||
|
dataAdd,
|
||||||
|
}: {
|
||||||
|
field: SwitchUIConfigField;
|
||||||
|
value?: any;
|
||||||
|
setValue: (value: any) => void;
|
||||||
|
dataAdd: string;
|
||||||
|
}) => {
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const handleSave = async (newValue: boolean) => {
|
||||||
|
setLoading(true);
|
||||||
|
setValue(newValue);
|
||||||
|
try {
|
||||||
|
if (field.scope === 'client') {
|
||||||
|
localStorage.setItem(field.key, String(newValue));
|
||||||
|
} else {
|
||||||
|
const res = await fetch('/api/config', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
key: `${dataAdd}.${field.key}`,
|
||||||
|
value: newValue,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
console.error('Failed to save config:', await res.text());
|
||||||
|
throw new Error('Failed to save configuration');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error saving config:', error);
|
||||||
|
toast.error('Failed to save configuration.');
|
||||||
|
} finally {
|
||||||
|
setTimeout(() => setLoading(false), 150);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const isChecked = value === true || value === 'true';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="rounded-xl border border-light-200 bg-light-primary/80 p-4 lg:p-6 transition-colors dark:border-dark-200 dark:bg-dark-primary/80">
|
||||||
|
<div className="flex flex-row items-center space-x-3 lg:space-x-5 w-full justify-between">
|
||||||
|
<div>
|
||||||
|
<h4 className="text-sm lg:text-base text-black dark:text-white">
|
||||||
|
{field.name}
|
||||||
|
</h4>
|
||||||
|
<p className="text-[11px] lg:text-xs text-black/50 dark:text-white/50">
|
||||||
|
{field.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
checked={isChecked}
|
||||||
|
onChange={handleSave}
|
||||||
|
disabled={loading}
|
||||||
|
className="group relative flex h-6 w-12 shrink-0 cursor-pointer rounded-full bg-white/10 p-1 duration-200 ease-in-out focus:outline-none transition-colors disabled:opacity-60 disabled:cursor-not-allowed data-[checked]:bg-sky-500"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
className="pointer-events-none inline-block size-4 translate-x-0 rounded-full bg-white shadow-lg ring-0 transition duration-200 ease-in-out group-data-[checked]:translate-x-6"
|
||||||
|
/>
|
||||||
|
</Switch>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const SettingsField = ({
|
const SettingsField = ({
|
||||||
field,
|
field,
|
||||||
value,
|
value,
|
||||||
@@ -276,6 +351,15 @@ const SettingsField = ({
|
|||||||
dataAdd={dataAdd}
|
dataAdd={dataAdd}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
case 'switch':
|
||||||
|
return (
|
||||||
|
<SettingsSwitch
|
||||||
|
field={field}
|
||||||
|
value={val}
|
||||||
|
setValue={setVal}
|
||||||
|
dataAdd={dataAdd}
|
||||||
|
/>
|
||||||
|
);
|
||||||
default:
|
default:
|
||||||
return <div>Unsupported field type: {field.type}</div>;
|
return <div>Unsupported field type: {field.type}</div>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,11 +6,8 @@ const getClientConfig = (key: string, defaultVal?: any) => {
|
|||||||
|
|
||||||
export const getTheme = () => getClientConfig('theme', 'dark');
|
export const getTheme = () => getClientConfig('theme', 'dark');
|
||||||
|
|
||||||
export const getAutoImageSearch = () =>
|
export const getAutoMediaSearch = () =>
|
||||||
Boolean(getClientConfig('autoImageSearch', 'true'));
|
getClientConfig('autoMediaSearch', 'true') === 'true';
|
||||||
|
|
||||||
export const getAutoVideoSearch = () =>
|
|
||||||
Boolean(getClientConfig('autoVideoSearch', 'true'));
|
|
||||||
|
|
||||||
export const getSystemInstructions = () =>
|
export const getSystemInstructions = () =>
|
||||||
getClientConfig('systemInstructions', '');
|
getClientConfig('systemInstructions', '');
|
||||||
|
|||||||
@@ -13,14 +13,15 @@ class ConfigManager {
|
|||||||
currentConfig: Config = {
|
currentConfig: Config = {
|
||||||
version: this.configVersion,
|
version: this.configVersion,
|
||||||
setupComplete: false,
|
setupComplete: false,
|
||||||
general: {},
|
preferences: {},
|
||||||
|
personalization: {},
|
||||||
modelProviders: [],
|
modelProviders: [],
|
||||||
search: {
|
search: {
|
||||||
searxngURL: '',
|
searxngURL: '',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
uiConfigSections: UIConfigSections = {
|
uiConfigSections: UIConfigSections = {
|
||||||
general: [
|
preferences: [
|
||||||
{
|
{
|
||||||
name: 'Theme',
|
name: 'Theme',
|
||||||
key: 'theme',
|
key: 'theme',
|
||||||
@@ -40,16 +41,6 @@ class ConfigManager {
|
|||||||
default: 'dark',
|
default: 'dark',
|
||||||
scope: 'client',
|
scope: 'client',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'System Instructions',
|
|
||||||
key: 'systemInstructions',
|
|
||||||
type: 'textarea',
|
|
||||||
required: false,
|
|
||||||
description: 'Add custom behavior or tone for the model.',
|
|
||||||
placeholder:
|
|
||||||
'e.g., "Respond in a friendly and concise tone" or "Use British English and format answers as bullet points."',
|
|
||||||
scope: 'client',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'Measurement Unit',
|
name: 'Measurement Unit',
|
||||||
key: 'measureUnit',
|
key: 'measureUnit',
|
||||||
@@ -69,6 +60,27 @@ class ConfigManager {
|
|||||||
default: 'Metric',
|
default: 'Metric',
|
||||||
scope: 'client',
|
scope: 'client',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'Auto video & image search',
|
||||||
|
key: 'autoMediaSearch',
|
||||||
|
type: 'switch',
|
||||||
|
required: false,
|
||||||
|
description: 'Automatically search for relevant images and videos.',
|
||||||
|
default: true,
|
||||||
|
scope: 'client',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
personalization: [
|
||||||
|
{
|
||||||
|
name: 'System Instructions',
|
||||||
|
key: 'systemInstructions',
|
||||||
|
type: 'textarea',
|
||||||
|
required: false,
|
||||||
|
description: 'Add custom behavior or tone for the model.',
|
||||||
|
placeholder:
|
||||||
|
'e.g., "Respond in a friendly and concise tone" or "Use British English and format answers as bullet points."',
|
||||||
|
scope: 'client',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
modelProviders: [],
|
modelProviders: [],
|
||||||
search: [
|
search: [
|
||||||
|
|||||||
@@ -38,11 +38,17 @@ type TextareaUIConfigField = BaseUIConfigField & {
|
|||||||
default?: string;
|
default?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type SwitchUIConfigField = BaseUIConfigField & {
|
||||||
|
type: 'switch';
|
||||||
|
default?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
type UIConfigField =
|
type UIConfigField =
|
||||||
| StringUIConfigField
|
| StringUIConfigField
|
||||||
| SelectUIConfigField
|
| SelectUIConfigField
|
||||||
| PasswordUIConfigField
|
| PasswordUIConfigField
|
||||||
| TextareaUIConfigField;
|
| TextareaUIConfigField
|
||||||
|
| SwitchUIConfigField;
|
||||||
|
|
||||||
type ConfigModelProvider = {
|
type ConfigModelProvider = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -57,7 +63,10 @@ type ConfigModelProvider = {
|
|||||||
type Config = {
|
type Config = {
|
||||||
version: number;
|
version: number;
|
||||||
setupComplete: boolean;
|
setupComplete: boolean;
|
||||||
general: {
|
preferences: {
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
personalization: {
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
};
|
};
|
||||||
modelProviders: ConfigModelProvider[];
|
modelProviders: ConfigModelProvider[];
|
||||||
@@ -80,7 +89,8 @@ type ModelProviderUISection = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type UIConfigSections = {
|
type UIConfigSections = {
|
||||||
general: UIConfigField[];
|
preferences: UIConfigField[];
|
||||||
|
personalization: UIConfigField[];
|
||||||
modelProviders: ModelProviderUISection[];
|
modelProviders: ModelProviderUISection[];
|
||||||
search: UIConfigField[];
|
search: UIConfigField[];
|
||||||
};
|
};
|
||||||
@@ -95,4 +105,5 @@ export type {
|
|||||||
ModelProviderUISection,
|
ModelProviderUISection,
|
||||||
ConfigModelProvider,
|
ConfigModelProvider,
|
||||||
TextareaUIConfigField,
|
TextareaUIConfigField,
|
||||||
|
SwitchUIConfigField,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import { useParams, useSearchParams } from 'next/navigation';
|
|||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { getSuggestions } from '../actions';
|
import { getSuggestions } from '../actions';
|
||||||
import { MinimalProvider } from '../models/types';
|
import { MinimalProvider } from '../models/types';
|
||||||
|
import { getAutoMediaSearch } from '../config/clientRegistry';
|
||||||
|
|
||||||
export type Section = {
|
export type Section = {
|
||||||
userMessage: UserMessage;
|
userMessage: UserMessage;
|
||||||
@@ -94,17 +95,6 @@ const checkConfig = async (
|
|||||||
'embeddingModelProviderId',
|
'embeddingModelProviderId',
|
||||||
);
|
);
|
||||||
|
|
||||||
const autoImageSearch = localStorage.getItem('autoImageSearch');
|
|
||||||
const autoVideoSearch = localStorage.getItem('autoVideoSearch');
|
|
||||||
|
|
||||||
if (!autoImageSearch) {
|
|
||||||
localStorage.setItem('autoImageSearch', 'true');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!autoVideoSearch) {
|
|
||||||
localStorage.setItem('autoVideoSearch', 'false');
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await fetch(`/api/providers`, {
|
const res = await fetch(`/api/providers`, {
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
@@ -624,16 +614,13 @@ export const ChatProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
|
|
||||||
const lastMsg = messagesRef.current[messagesRef.current.length - 1];
|
const lastMsg = messagesRef.current[messagesRef.current.length - 1];
|
||||||
|
|
||||||
const autoImageSearch = localStorage.getItem('autoImageSearch');
|
const autoMediaSearch = getAutoMediaSearch();
|
||||||
const autoVideoSearch = localStorage.getItem('autoVideoSearch');
|
|
||||||
|
|
||||||
if (autoImageSearch === 'true') {
|
if (autoMediaSearch) {
|
||||||
document
|
document
|
||||||
.getElementById(`search-images-${lastMsg.messageId}`)
|
.getElementById(`search-images-${lastMsg.messageId}`)
|
||||||
?.click();
|
?.click();
|
||||||
}
|
|
||||||
|
|
||||||
if (autoVideoSearch === 'true') {
|
|
||||||
document
|
document
|
||||||
.getElementById(`search-videos-${lastMsg.messageId}`)
|
.getElementById(`search-videos-${lastMsg.messageId}`)
|
||||||
?.click();
|
?.click();
|
||||||
|
|||||||
Reference in New Issue
Block a user