-
+
{selectedSection.name}
diff --git a/src/components/Settings/SettingsField.tsx b/src/components/Settings/SettingsField.tsx
index 8b2fc41..55aa640 100644
--- a/src/components/Settings/SettingsField.tsx
+++ b/src/components/Settings/SettingsField.tsx
@@ -1,6 +1,7 @@
import {
SelectUIConfigField,
StringUIConfigField,
+ SwitchUIConfigField,
TextareaUIConfigField,
UIConfigField,
} from '@/lib/config/types';
@@ -9,6 +10,7 @@ import Select from '../ui/Select';
import { toast } from 'sonner';
import { useTheme } from 'next-themes';
import { Loader2 } from 'lucide-react';
+import { Switch } from '@headlessui/react';
const SettingsSelect = ({
field,
@@ -62,7 +64,7 @@ const SettingsSelect = ({
-
+
{field.name}
@@ -133,7 +135,7 @@ const SettingsInput = ({
-
+
{field.name}
@@ -145,7 +147,7 @@ const SettingsInput = ({
value={value ?? field.default ?? ''}
onChange={(event) => setValue(event.target.value)}
onBlur={(event) => handleSave(event.target.value)}
- className="w-full rounded-lg border border-light-200 dark:border-dark-200 bg-light-primary dark:bg-dark-primary px-3 py-2 lg:px-4 lg:py-3 pr-10 !text-xs lg:!text-sm text-black/80 dark:text-white/80 placeholder:text-black/40 dark:placeholder:text-white/40 focus-visible:outline-none focus-visible:border-light-300 dark:focus-visible:border-dark-300 transition-colors disabled:cursor-not-allowed disabled:opacity-60"
+ className="w-full rounded-lg border border-light-200 dark:border-dark-200 bg-light-primary dark:bg-dark-primary px-3 py-2 lg:px-4 lg:py-3 pr-10 !text-xs lg:!text-[13px] text-black/80 dark:text-white/80 placeholder:text-black/40 dark:placeholder:text-white/40 focus-visible:outline-none focus-visible:border-light-300 dark:focus-visible:border-dark-300 transition-colors disabled:cursor-not-allowed disabled:opacity-60"
placeholder={field.placeholder}
type="text"
disabled={loading}
@@ -209,7 +211,7 @@ const SettingsTextarea = ({
-
+
{field.name}
@@ -221,7 +223,7 @@ const SettingsTextarea = ({
value={value ?? field.default ?? ''}
onChange={(event) => setValue(event.target.value)}
onBlur={(event) => handleSave(event.target.value)}
- className="w-full rounded-lg border border-light-200 dark:border-dark-200 bg-light-primary dark:bg-dark-primary px-3 py-2 lg:px-4 lg:py-3 pr-10 !text-xs lg:!text-sm text-black/80 dark:text-white/80 placeholder:text-black/40 dark:placeholder:text-white/40 focus-visible:outline-none focus-visible:border-light-300 dark:focus-visible:border-dark-300 transition-colors disabled:cursor-not-allowed disabled:opacity-60"
+ className="w-full rounded-lg border border-light-200 dark:border-dark-200 bg-light-primary dark:bg-dark-primary px-3 py-2 lg:px-4 lg:py-3 pr-10 !text-xs lg:!text-[13px] text-black/80 dark:text-white/80 placeholder:text-black/40 dark:placeholder:text-white/40 focus-visible:outline-none focus-visible:border-light-300 dark:focus-visible:border-dark-300 transition-colors disabled:cursor-not-allowed disabled:opacity-60"
placeholder={field.placeholder}
rows={4}
disabled={loading}
@@ -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 (
+
+
+
+
+ {field.name}
+
+
+ {field.description}
+
+
+
+
+
+
+
+ );
+};
+
const SettingsField = ({
field,
value,
@@ -276,6 +351,15 @@ const SettingsField = ({
dataAdd={dataAdd}
/>
);
+ case 'switch':
+ return (
+
+ );
default:
return
Unsupported field type: {field.type}
;
}
diff --git a/src/components/Setup/SetupConfig.tsx b/src/components/Setup/SetupConfig.tsx
index 334974f..4e17a92 100644
--- a/src/components/Setup/SetupConfig.tsx
+++ b/src/components/Setup/SetupConfig.tsx
@@ -63,7 +63,11 @@ const SetupConfig = ({
}
};
- const hasProviders = providers.length > 0;
+ const visibleProviders = providers.filter(
+ (p) => p.name.toLowerCase() !== 'transformers',
+ );
+ const hasProviders =
+ visibleProviders.filter((p) => p.chatModels.length > 0).length > 0;
return (
@@ -81,10 +85,10 @@ const SetupConfig = ({
- Manage Providers
+ Manage Connections
- Add and configure your model providers
+ Add connections to access AI models
- ) : providers.length === 0 ? (
+ ) : visibleProviders.length === 0 ? (
- No providers configured
+ No connections configured
+
+
+ Click "Add Connection" above to get started
) : (
- providers.map((provider) => (
+ visibleProviders.map((provider) => (
{
export const getTheme = () => getClientConfig('theme', 'dark');
-export const getAutoImageSearch = () =>
- Boolean(getClientConfig('autoImageSearch', 'true'));
-
-export const getAutoVideoSearch = () =>
- Boolean(getClientConfig('autoVideoSearch', 'true'));
+export const getAutoMediaSearch = () =>
+ getClientConfig('autoMediaSearch', 'true') === 'true';
export const getSystemInstructions = () =>
getClientConfig('systemInstructions', '');
diff --git a/src/lib/config/index.ts b/src/lib/config/index.ts
index 0487a11..9b69c8a 100644
--- a/src/lib/config/index.ts
+++ b/src/lib/config/index.ts
@@ -13,14 +13,15 @@ class ConfigManager {
currentConfig: Config = {
version: this.configVersion,
setupComplete: false,
- general: {},
+ preferences: {},
+ personalization: {},
modelProviders: [],
search: {
searxngURL: '',
},
};
uiConfigSections: UIConfigSections = {
- general: [
+ preferences: [
{
name: 'Theme',
key: 'theme',
@@ -40,16 +41,6 @@ class ConfigManager {
default: 'dark',
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',
key: 'measureUnit',
@@ -69,6 +60,27 @@ class ConfigManager {
default: 'Metric',
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: [],
search: [
diff --git a/src/lib/config/types.ts b/src/lib/config/types.ts
index 8497cb5..6eaa70c 100644
--- a/src/lib/config/types.ts
+++ b/src/lib/config/types.ts
@@ -38,11 +38,17 @@ type TextareaUIConfigField = BaseUIConfigField & {
default?: string;
};
+type SwitchUIConfigField = BaseUIConfigField & {
+ type: 'switch';
+ default?: boolean;
+};
+
type UIConfigField =
| StringUIConfigField
| SelectUIConfigField
| PasswordUIConfigField
- | TextareaUIConfigField;
+ | TextareaUIConfigField
+ | SwitchUIConfigField;
type ConfigModelProvider = {
id: string;
@@ -57,7 +63,10 @@ type ConfigModelProvider = {
type Config = {
version: number;
setupComplete: boolean;
- general: {
+ preferences: {
+ [key: string]: any;
+ };
+ personalization: {
[key: string]: any;
};
modelProviders: ConfigModelProvider[];
@@ -80,7 +89,8 @@ type ModelProviderUISection = {
};
type UIConfigSections = {
- general: UIConfigField[];
+ preferences: UIConfigField[];
+ personalization: UIConfigField[];
modelProviders: ModelProviderUISection[];
search: UIConfigField[];
};
@@ -95,4 +105,5 @@ export type {
ModelProviderUISection,
ConfigModelProvider,
TextareaUIConfigField,
+ SwitchUIConfigField,
};
diff --git a/src/lib/hooks/useChat.tsx b/src/lib/hooks/useChat.tsx
index 8ef57ef..ee7e9c7 100644
--- a/src/lib/hooks/useChat.tsx
+++ b/src/lib/hooks/useChat.tsx
@@ -21,6 +21,7 @@ import { useParams, useSearchParams } from 'next/navigation';
import { toast } from 'sonner';
import { getSuggestions } from '../actions';
import { MinimalProvider } from '../models/types';
+import { getAutoMediaSearch } from '../config/clientRegistry';
export type Section = {
userMessage: UserMessage;
@@ -94,17 +95,6 @@ const checkConfig = async (
'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`, {
headers: {
'Content-Type': 'application/json',
@@ -624,16 +614,13 @@ export const ChatProvider = ({ children }: { children: React.ReactNode }) => {
const lastMsg = messagesRef.current[messagesRef.current.length - 1];
- const autoImageSearch = localStorage.getItem('autoImageSearch');
- const autoVideoSearch = localStorage.getItem('autoVideoSearch');
+ const autoMediaSearch = getAutoMediaSearch();
- if (autoImageSearch === 'true') {
+ if (autoMediaSearch) {
document
.getElementById(`search-images-${lastMsg.messageId}`)
?.click();
- }
- if (autoVideoSearch === 'true') {
document
.getElementById(`search-videos-${lastMsg.messageId}`)
?.click();
diff --git a/src/lib/models/providers/gemini.ts b/src/lib/models/providers/gemini.ts
index 6cf3584..6cfd913 100644
--- a/src/lib/models/providers/gemini.ts
+++ b/src/lib/models/providers/gemini.ts
@@ -48,7 +48,12 @@ class GeminiProvider extends BaseModelProvider {
let defaultChatModels: Model[] = [];
data.models.forEach((m: any) => {
- if (m.supportedGenerationMethods.includes('embedText')) {
+ if (
+ m.supportedGenerationMethods.some(
+ (genMethod: string) =>
+ genMethod === 'embedText' || genMethod === 'embedContent',
+ )
+ ) {
defaultEmbeddingModels.push({
key: m.name,
name: m.displayName,