Compare commits

...

17 Commits

Author SHA1 Message Date
Kushagra Srivastava
5901a965f7 Merge pull request #934 from PSYEONE/no-ads
Added ability to remove the widgets in the front empty page of the search. Specifically the Weather and News widget.
2025-11-28 20:03:00 +05:30
ItzCrazyKns
6150784c27 feat(app): lint & beautify 2025-11-28 18:41:11 +05:30
Kushagra Srivastava
cb30e2438a Update src/components/EmptyChat.tsx
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
2025-11-28 18:17:07 +05:30
Kushagra Srivastava
ead2a5b215 Update src/components/EmptyChat.tsx
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
2025-11-28 18:17:01 +05:30
PSYEONE
1df4d886ff Revert "Added updated README for this fork"
This reverts commit 2574287fa8.
2025-11-28 12:24:15 +00:00
PSYEONE
2574287fa8 Added updated README for this fork 2025-11-28 12:08:19 +00:00
PSYEONE
3005b379cf Added functionality for hiding weather and news widgets 2025-11-28 11:59:53 +00:00
ItzCrazyKns
9934c1dbe0 Update README.md 2025-11-14 14:15:06 +05:30
ItzCrazyKns
f767717d7f Update README.md 2025-11-14 14:13:40 +05:30
ItzCrazyKns
e88e1c627c Update README.md 2025-11-14 14:12:43 +05:30
ItzCrazyKns
2dc8078848 Update Exa sponsor image and README styling 2025-11-14 13:23:50 +05:30
ItzCrazyKns
8df81c20cf Update README.md 2025-11-14 13:19:49 +05:30
ItzCrazyKns
34bd02236d Update README.md 2025-11-14 13:17:52 +05:30
ItzCrazyKns
2430376a0c feat(readme): update sponsers 2025-11-14 13:15:59 +05:30
ItzCrazyKns
70c1f7230c feat(assets): remove old preview 2025-11-08 21:31:56 +05:30
ItzCrazyKns
c0771095a6 feat(app): lint & beautify 2025-10-30 17:21:48 +05:30
ItzCrazyKns
0856896aff feat(settings): fix text size, enhance UI 2025-10-30 17:21:40 +05:30
15 changed files with 137 additions and 39 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 MiB

BIN
.assets/sponsers/exa.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

@@ -49,10 +49,29 @@ Perplexica's development is powered by the generous support of our sponsors. The
<img alt="Warp Terminal" src=".assets/sponsers/warp.png" width="100%"> <img alt="Warp Terminal" src=".assets/sponsers/warp.png" width="100%">
</a> </a>
**[Warp](https://www.warp.dev/perplexica)** - The AI-powered terminal revolutionizing development workflows ### **✨ [Try Warp - The AI-Powered Terminal →](https://www.warp.dev/perplexica)**
Warp is revolutionizing development workflows with AI-powered features, modern UX, and blazing-fast performance. Used by developers at top companies worldwide.
</div> </div>
---
We'd also like to thank the following partners for their generous support:
<table>
<tr>
<td width="100" align="center">
<a href="https://dashboard.exa.ai" target="_blank">
<img src=".assets/sponsers/exa.png" alt="Exa" width="80" height="80" style="border-radius: .75rem;" />
</a>
</td>
<td>
<a href="https://dashboard.exa.ai">Exa</a> • The Perfect Web Search API for LLMs - web search, crawling, deep research, and answer APIs
</td>
</tr>
</table>
## Installation ## Installation
There are mainly 2 ways of installing Perplexica - With Docker, Without Docker. Using Docker is highly recommended. There are mainly 2 ways of installing Perplexica - With Docker, Without Docker. Using Docker is highly recommended.

View File

@@ -1,3 +1,6 @@
'use client';
import { useEffect, useState } from 'react';
import { Settings } from 'lucide-react'; import { Settings } from 'lucide-react';
import EmptyChatMessageInput from './EmptyChatMessageInput'; import EmptyChatMessageInput from './EmptyChatMessageInput';
import { File } from './ChatWindow'; import { File } from './ChatWindow';
@@ -5,8 +8,39 @@ import Link from 'next/link';
import WeatherWidget from './WeatherWidget'; import WeatherWidget from './WeatherWidget';
import NewsArticleWidget from './NewsArticleWidget'; import NewsArticleWidget from './NewsArticleWidget';
import SettingsButtonMobile from '@/components/Settings/SettingsButtonMobile'; import SettingsButtonMobile from '@/components/Settings/SettingsButtonMobile';
import {
getShowNewsWidget,
getShowWeatherWidget,
} from '@/lib/config/clientRegistry';
const EmptyChat = () => { const EmptyChat = () => {
const [showWeather, setShowWeather] = useState(() =>
typeof window !== 'undefined' ? getShowWeatherWidget() : true,
);
const [showNews, setShowNews] = useState(() =>
typeof window !== 'undefined' ? getShowNewsWidget() : true,
);
useEffect(() => {
const updateWidgetVisibility = () => {
setShowWeather(getShowWeatherWidget());
setShowNews(getShowNewsWidget());
};
updateWidgetVisibility();
window.addEventListener('client-config-changed', updateWidgetVisibility);
window.addEventListener('storage', updateWidgetVisibility);
return () => {
window.removeEventListener(
'client-config-changed',
updateWidgetVisibility,
);
window.removeEventListener('storage', updateWidgetVisibility);
};
}, []);
return ( return (
<div className="relative"> <div className="relative">
<div className="absolute w-full flex flex-row items-center justify-end mr-5 mt-5"> <div className="absolute w-full flex flex-row items-center justify-end mr-5 mt-5">
@@ -19,14 +53,20 @@ const EmptyChat = () => {
</h2> </h2>
<EmptyChatMessageInput /> <EmptyChatMessageInput />
</div> </div>
{(showWeather || showNews) && (
<div className="flex flex-col w-full gap-4 mt-2 sm:flex-row sm:justify-center"> <div className="flex flex-col w-full gap-4 mt-2 sm:flex-row sm:justify-center">
{showWeather && (
<div className="flex-1 w-full"> <div className="flex-1 w-full">
<WeatherWidget /> <WeatherWidget />
</div> </div>
)}
{showNews && (
<div className="flex-1 w-full"> <div className="flex-1 w-full">
<NewsArticleWidget /> <NewsArticleWidget />
</div> </div>
)}
</div> </div>
)}
</div> </div>
</div> </div>
); );

View File

@@ -97,7 +97,7 @@ const AddModel = ({
> >
<DialogPanel className="w-full mx-4 lg:w-[600px] max-h-[85vh] flex flex-col border bg-light-primary dark:bg-dark-primary border-light-secondary dark:border-dark-secondary rounded-lg"> <DialogPanel className="w-full mx-4 lg:w-[600px] max-h-[85vh] flex flex-col border bg-light-primary dark:bg-dark-primary border-light-secondary dark:border-dark-secondary rounded-lg">
<div className="px-6 pt-6 pb-4"> <div className="px-6 pt-6 pb-4">
<h3 className="text-black/90 dark:text-white/90 font-medium"> <h3 className="text-black/90 dark:text-white/90 font-medium text-sm">
Add new {type === 'chat' ? 'chat' : 'embedding'} model Add new {type === 'chat' ? 'chat' : 'embedding'} model
</h3> </h3>
</div> </div>
@@ -115,7 +115,7 @@ const AddModel = ({
<input <input
value={modelName} value={modelName}
onChange={(e) => setModelName(e.target.value)} onChange={(e) => setModelName(e.target.value)}
className="w-full rounded-lg border border-light-200 dark:border-dark-200 bg-light-primary dark:bg-dark-primary px-4 py-3 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-4 py-3 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="e.g., GPT-4" placeholder="e.g., GPT-4"
type="text" type="text"
required required
@@ -128,7 +128,7 @@ const AddModel = ({
<input <input
value={modelKey} value={modelKey}
onChange={(e) => setModelKey(e.target.value)} onChange={(e) => setModelKey(e.target.value)}
className="w-full rounded-lg border border-light-200 dark:border-dark-200 bg-light-primary dark:bg-dark-primary px-4 py-3 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-4 py-3 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="e.g., gpt-4" placeholder="e.g., gpt-4"
type="text" type="text"
required required
@@ -140,7 +140,7 @@ const AddModel = ({
<button <button
type="submit" type="submit"
disabled={loading} disabled={loading}
className="px-4 py-2 rounded-lg text-sm bg-sky-500 text-white font-medium disabled:opacity-85 hover:opacity-85 active:scale-95 transition duration-200" className="px-4 py-2 rounded-lg text-[13px] bg-sky-500 text-white font-medium disabled:opacity-85 hover:opacity-85 active:scale-95 transition duration-200"
> >
{loading ? ( {loading ? (
<Loader2 className="animate-spin" size={16} /> <Loader2 className="animate-spin" size={16} />

View File

@@ -96,7 +96,7 @@ const AddProvider = ({
<> <>
<button <button
onClick={() => setOpen(true)} onClick={() => setOpen(true)}
className="px-3 md:px-4 py-1.5 md:py-2 rounded-lg text-xs sm:text-sm border border-light-200 dark:border-dark-200 text-black dark:text-white bg-light-secondary/50 dark:bg-dark-secondary/50 hover:bg-light-secondary hover:dark:bg-dark-secondary hover:border-light-300 hover:dark:border-dark-300 flex flex-row items-center space-x-1 active:scale-95 transition duration-200" className="px-3 md:px-4 py-1.5 md:py-2 rounded-lg text-xs sm:text-xs border border-light-200 dark:border-dark-200 text-black dark:text-white bg-light-secondary/50 dark:bg-dark-secondary/50 hover:bg-light-secondary hover:dark:bg-dark-secondary hover:border-light-300 hover:dark:border-dark-300 flex flex-row items-center space-x-1 active:scale-95 transition duration-200"
> >
<Plus className="w-3.5 h-3.5 md:w-4 md:h-4" /> <Plus className="w-3.5 h-3.5 md:w-4 md:h-4" />
<span>Add Connection</span> <span>Add Connection</span>
@@ -119,7 +119,7 @@ const AddProvider = ({
<DialogPanel className="w-full mx-4 lg:w-[600px] max-h-[85vh] flex flex-col border bg-light-primary dark:bg-dark-primary border-light-secondary dark:border-dark-secondary rounded-lg"> <DialogPanel className="w-full mx-4 lg:w-[600px] max-h-[85vh] flex flex-col border bg-light-primary dark:bg-dark-primary border-light-secondary dark:border-dark-secondary rounded-lg">
<form onSubmit={handleSubmit} className="flex flex-col flex-1"> <form onSubmit={handleSubmit} className="flex flex-col flex-1">
<div className="px-6 pt-6 pb-4"> <div className="px-6 pt-6 pb-4">
<h3 className="text-black/90 dark:text-white/90 font-medium"> <h3 className="text-black/90 dark:text-white/90 font-medium text-sm">
Add new connection Add new connection
</h3> </h3>
</div> </div>
@@ -178,7 +178,7 @@ const AddProvider = ({
[field.key]: event.target.value, [field.key]: event.target.value,
})) }))
} }
className="w-full rounded-lg border border-light-200 dark:border-dark-200 bg-light-primary dark:bg-dark-primary px-4 py-3 pr-10 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-4 py-3 pr-10 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={ placeholder={
(field as StringUIConfigField).placeholder (field as StringUIConfigField).placeholder
} }
@@ -194,7 +194,7 @@ const AddProvider = ({
<button <button
type="submit" type="submit"
disabled={loading} disabled={loading}
className="px-4 py-2 rounded-lg text-sm bg-sky-500 text-white font-medium disabled:opacity-85 hover:opacity-85 active:scale-95 transition duration-200" className="px-4 py-2 rounded-lg text-[13px] bg-sky-500 text-white font-medium disabled:opacity-85 hover:opacity-85 active:scale-95 transition duration-200"
> >
{loading ? ( {loading ? (
<Loader2 className="animate-spin" size={16} /> <Loader2 className="animate-spin" size={16} />

View File

@@ -84,11 +84,11 @@ const ModelProvider = ({
<Plug2 size={14} className="text-sky-500" /> <Plug2 size={14} className="text-sky-500" />
</div> </div>
<div className="flex flex-col"> <div className="flex flex-col">
<p className="text-sm lg:text-base text-black dark:text-white font-medium"> <p className="text-sm lg:text-sm text-black dark:text-white font-medium">
{modelProvider.name} {modelProvider.name}
</p> </p>
{modelCount > 0 && ( {modelCount > 0 && (
<p className="text-[10px] lg:text-xs text-black/50 dark:text-white/50"> <p className="text-[10px] lg:text-[11px] text-black/50 dark:text-white/50">
{modelCount} model{modelCount !== 1 ? 's' : ''} configured {modelCount} model{modelCount !== 1 ? 's' : ''} configured
</p> </p>
)} )}
@@ -109,7 +109,7 @@ const ModelProvider = ({
<div className="flex flex-col gap-y-4 px-5 py-4"> <div className="flex flex-col gap-y-4 px-5 py-4">
<div className="flex flex-col gap-y-2"> <div className="flex flex-col gap-y-2">
<div className="flex flex-row w-full justify-between items-center"> <div className="flex flex-row w-full justify-between items-center">
<p className="text-[11px] lg:text-xs font-medium text-black/70 dark:text-white/70 uppercase tracking-wide"> <p className="text-[11px] lg:text-[11px] font-medium text-black/70 dark:text-white/70 uppercase tracking-wide">
Chat Models Chat Models
</p> </p>
{!modelProvider.chatModels.some((m) => m.key === 'error') && ( {!modelProvider.chatModels.some((m) => m.key === 'error') && (
@@ -122,7 +122,7 @@ const ModelProvider = ({
</div> </div>
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
{modelProvider.chatModels.some((m) => m.key === 'error') ? ( {modelProvider.chatModels.some((m) => m.key === 'error') ? (
<div className="flex flex-row items-center gap-2 text-xs lg:text-sm text-red-500 dark:text-red-400 rounded-lg bg-red-50 dark:bg-red-950/20 px-3 py-2 border border-red-200 dark:border-red-900/30"> <div className="flex flex-row items-center gap-2 text-xs lg:text-xs text-red-500 dark:text-red-400 rounded-lg bg-red-50 dark:bg-red-950/20 px-3 py-2 border border-red-200 dark:border-red-900/30">
<AlertCircle size={16} className="shrink-0" /> <AlertCircle size={16} className="shrink-0" />
<span className="break-words"> <span className="break-words">
{ {
@@ -144,7 +144,7 @@ const ModelProvider = ({
{modelProvider.chatModels.map((model, index) => ( {modelProvider.chatModels.map((model, index) => (
<div <div
key={`${modelProvider.id}-chat-${model.key}-${index}`} key={`${modelProvider.id}-chat-${model.key}-${index}`}
className="flex flex-row items-center space-x-1.5 text-xs lg:text-sm text-black/70 dark:text-white/70 rounded-lg bg-light-secondary dark:bg-dark-secondary px-3 py-1.5 border border-light-200 dark:border-dark-200" className="flex flex-row items-center space-x-1.5 text-xs lg:text-xs text-black/70 dark:text-white/70 rounded-lg bg-light-secondary dark:bg-dark-secondary px-3 py-1.5 border border-light-200 dark:border-dark-200"
> >
<span>{model.name}</span> <span>{model.name}</span>
<button <button
@@ -164,7 +164,7 @@ const ModelProvider = ({
<div className="flex flex-col gap-y-2"> <div className="flex flex-col gap-y-2">
<div className="flex flex-row w-full justify-between items-center"> <div className="flex flex-row w-full justify-between items-center">
<p className="text-[11px] lg:text-xs font-medium text-black/70 dark:text-white/70 uppercase tracking-wide"> <p className="text-[11px] lg:text-[11px] font-medium text-black/70 dark:text-white/70 uppercase tracking-wide">
Embedding Models Embedding Models
</p> </p>
{!modelProvider.embeddingModels.some((m) => m.key === 'error') && ( {!modelProvider.embeddingModels.some((m) => m.key === 'error') && (
@@ -177,7 +177,7 @@ const ModelProvider = ({
</div> </div>
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
{modelProvider.embeddingModels.some((m) => m.key === 'error') ? ( {modelProvider.embeddingModels.some((m) => m.key === 'error') ? (
<div className="flex flex-row items-center gap-2 text-xs lg:text-sm text-red-500 dark:text-red-400 rounded-lg bg-red-50 dark:bg-red-950/20 px-3 py-2 border border-red-200 dark:border-red-900/30"> <div className="flex flex-row items-center gap-2 text-xs lg:text-xs text-red-500 dark:text-red-400 rounded-lg bg-red-50 dark:bg-red-950/20 px-3 py-2 border border-red-200 dark:border-red-900/30">
<AlertCircle size={16} className="shrink-0" /> <AlertCircle size={16} className="shrink-0" />
<span className="break-words"> <span className="break-words">
{ {
@@ -199,7 +199,7 @@ const ModelProvider = ({
{modelProvider.embeddingModels.map((model, index) => ( {modelProvider.embeddingModels.map((model, index) => (
<div <div
key={`${modelProvider.id}-embedding-${model.key}-${index}`} key={`${modelProvider.id}-embedding-${model.key}-${index}`}
className="flex flex-row items-center space-x-1.5 text-xs lg:text-sm text-black/70 dark:text-white/70 rounded-lg bg-light-secondary dark:bg-dark-secondary px-3 py-1.5 border border-light-200 dark:border-dark-200" className="flex flex-row items-center space-x-1.5 text-xs lg:text-xs text-black/70 dark:text-white/70 rounded-lg bg-light-secondary dark:bg-dark-secondary px-3 py-1.5 border border-light-200 dark:border-dark-200"
> >
<span>{model.name}</span> <span>{model.name}</span>
<button <button

View File

@@ -59,7 +59,7 @@ const ModelSelect = ({
<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"> <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="space-y-3 lg:space-y-5"> <div className="space-y-3 lg:space-y-5">
<div> <div>
<h4 className="text-sm lg:text-base text-black dark:text-white"> <h4 className="text-sm lg:text-sm text-black dark:text-white">
Select {type === 'chat' ? 'Chat Model' : 'Embedding Model'} Select {type === 'chat' ? 'Chat Model' : 'Embedding Model'}
</h4> </h4>
<p className="text-[11px] lg:text-xs text-black/50 dark:text-white/50"> <p className="text-[11px] lg:text-xs text-black/50 dark:text-white/50">
@@ -86,7 +86,7 @@ const ModelSelect = ({
})), })),
) )
} }
className="!text-xs lg:!text-sm" className="!text-xs lg:!text-[13px]"
loading={loading} loading={loading}
disabled={loading} disabled={loading}
/> />

View File

@@ -20,7 +20,7 @@ const Models = ({
return ( return (
<div className="flex-1 space-y-6 overflow-y-auto py-6"> <div className="flex-1 space-y-6 overflow-y-auto py-6">
<div className="flex flex-col px-6 gap-y-4"> <div className="flex flex-col px-6 gap-y-4">
<h3 className="text-xs lg:text-sm text-black/70 dark:text-white/70"> <h3 className="text-xs lg:text-xs text-black/70 dark:text-white/70">
Select models Select models
</h3> </h3>
<ModelSelect <ModelSelect
@@ -38,7 +38,7 @@ const Models = ({
</div> </div>
<div className="border-t border-light-200 dark:border-dark-200" /> <div className="border-t border-light-200 dark:border-dark-200" />
<div className="flex flex-row justify-between items-center px-6 "> <div className="flex flex-row justify-between items-center px-6 ">
<p className="text-xs lg:text-sm text-black/70 dark:text-white/70"> <p className="text-xs lg:text-xs text-black/70 dark:text-white/70">
Manage connections Manage connections
</p> </p>
<AddProvider modelProviders={fields} setProviders={setProviders} /> <AddProvider modelProviders={fields} setProviders={setProviders} />

View File

@@ -109,7 +109,7 @@ const UpdateProvider = ({
<DialogPanel className="w-full mx-4 lg:w-[600px] max-h-[85vh] flex flex-col border bg-light-primary dark:bg-dark-primary border-light-secondary dark:border-dark-secondary rounded-lg"> <DialogPanel className="w-full mx-4 lg:w-[600px] max-h-[85vh] flex flex-col border bg-light-primary dark:bg-dark-primary border-light-secondary dark:border-dark-secondary rounded-lg">
<form onSubmit={handleSubmit} className="flex flex-col flex-1"> <form onSubmit={handleSubmit} className="flex flex-col flex-1">
<div className="px-6 pt-6 pb-4"> <div className="px-6 pt-6 pb-4">
<h3 className="text-black/90 dark:text-white/90 font-medium"> <h3 className="text-black/90 dark:text-white/90 font-medium text-sm">
Update connection Update connection
</h3> </h3>
</div> </div>
@@ -150,7 +150,7 @@ const UpdateProvider = ({
[field.key]: event.target.value, [field.key]: event.target.value,
})) }))
} }
className="w-full rounded-lg border border-light-200 dark:border-dark-200 bg-light-primary dark:bg-dark-primary px-4 py-3 pr-10 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-4 py-3 pr-10 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={ placeholder={
(field as StringUIConfigField).placeholder (field as StringUIConfigField).placeholder
} }
@@ -166,7 +166,7 @@ const UpdateProvider = ({
<button <button
type="submit" type="submit"
disabled={loading} disabled={loading}
className="px-4 py-2 rounded-lg text-sm bg-sky-500 text-white font-medium disabled:opacity-85 hover:opacity-85 active:scale-95 transition duration-200" className="px-4 py-2 rounded-lg text-[13px] bg-sky-500 text-white font-medium disabled:opacity-85 hover:opacity-85 active:scale-95 transition duration-200"
> >
{loading ? ( {loading ? (
<Loader2 className="animate-spin" size={16} /> <Loader2 className="animate-spin" size={16} />

View File

@@ -176,7 +176,7 @@ const SettingsDialogue = ({
<div className="flex flex-1 flex-col overflow-hidden"> <div className="flex flex-1 flex-col overflow-hidden">
<div className="border-b border-light-200/60 px-6 pb-6 lg:pt-6 dark:border-dark-200/60 flex-shrink-0"> <div className="border-b border-light-200/60 px-6 pb-6 lg:pt-6 dark:border-dark-200/60 flex-shrink-0">
<div className="flex flex-col"> <div className="flex flex-col">
<h4 className="font-medium text-black dark:text-white text-sm lg:text-base"> <h4 className="font-medium text-black dark:text-white text-sm lg:text-sm">
{selectedSection.name} {selectedSection.name}
</h4> </h4>
<p className="text-[11px] lg:text-xs text-black/50 dark:text-white/50"> <p className="text-[11px] lg:text-xs text-black/50 dark:text-white/50">

View File

@@ -12,6 +12,12 @@ import { useTheme } from 'next-themes';
import { Loader2 } from 'lucide-react'; import { Loader2 } from 'lucide-react';
import { Switch } from '@headlessui/react'; import { Switch } from '@headlessui/react';
const emitClientConfigChanged = () => {
if (typeof window !== 'undefined') {
window.dispatchEvent(new Event('client-config-changed'));
}
};
const SettingsSelect = ({ const SettingsSelect = ({
field, field,
value, value,
@@ -35,6 +41,7 @@ const SettingsSelect = ({
if (field.key === 'theme') { if (field.key === 'theme') {
setTheme(newValue); setTheme(newValue);
} }
emitClientConfigChanged();
} else { } else {
const res = await fetch('/api/config', { const res = await fetch('/api/config', {
method: 'POST', method: 'POST',
@@ -64,7 +71,7 @@ const SettingsSelect = ({
<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"> <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="space-y-3 lg:space-y-5"> <div className="space-y-3 lg:space-y-5">
<div> <div>
<h4 className="text-sm lg:text-base text-black dark:text-white"> <h4 className="text-sm lg:text-sm text-black dark:text-white">
{field.name} {field.name}
</h4> </h4>
<p className="text-[11px] lg:text-xs text-black/50 dark:text-white/50"> <p className="text-[11px] lg:text-xs text-black/50 dark:text-white/50">
@@ -106,6 +113,7 @@ const SettingsInput = ({
try { try {
if (field.scope === 'client') { if (field.scope === 'client') {
localStorage.setItem(field.key, newValue); localStorage.setItem(field.key, newValue);
emitClientConfigChanged();
} else { } else {
const res = await fetch('/api/config', { const res = await fetch('/api/config', {
method: 'POST', method: 'POST',
@@ -135,7 +143,7 @@ const SettingsInput = ({
<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"> <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="space-y-3 lg:space-y-5"> <div className="space-y-3 lg:space-y-5">
<div> <div>
<h4 className="text-sm lg:text-base text-black dark:text-white"> <h4 className="text-sm lg:text-sm text-black dark:text-white">
{field.name} {field.name}
</h4> </h4>
<p className="text-[11px] lg:text-xs text-black/50 dark:text-white/50"> <p className="text-[11px] lg:text-xs text-black/50 dark:text-white/50">
@@ -147,7 +155,7 @@ const SettingsInput = ({
value={value ?? field.default ?? ''} value={value ?? field.default ?? ''}
onChange={(event) => setValue(event.target.value)} onChange={(event) => setValue(event.target.value)}
onBlur={(event) => handleSave(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} placeholder={field.placeholder}
type="text" type="text"
disabled={loading} disabled={loading}
@@ -182,6 +190,7 @@ const SettingsTextarea = ({
try { try {
if (field.scope === 'client') { if (field.scope === 'client') {
localStorage.setItem(field.key, newValue); localStorage.setItem(field.key, newValue);
emitClientConfigChanged();
} else { } else {
const res = await fetch('/api/config', { const res = await fetch('/api/config', {
method: 'POST', method: 'POST',
@@ -211,7 +220,7 @@ const SettingsTextarea = ({
<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"> <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="space-y-3 lg:space-y-5"> <div className="space-y-3 lg:space-y-5">
<div> <div>
<h4 className="text-sm lg:text-base text-black dark:text-white"> <h4 className="text-sm lg:text-sm text-black dark:text-white">
{field.name} {field.name}
</h4> </h4>
<p className="text-[11px] lg:text-xs text-black/50 dark:text-white/50"> <p className="text-[11px] lg:text-xs text-black/50 dark:text-white/50">
@@ -223,7 +232,7 @@ const SettingsTextarea = ({
value={value ?? field.default ?? ''} value={value ?? field.default ?? ''}
onChange={(event) => setValue(event.target.value)} onChange={(event) => setValue(event.target.value)}
onBlur={(event) => handleSave(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} placeholder={field.placeholder}
rows={4} rows={4}
disabled={loading} disabled={loading}
@@ -258,6 +267,7 @@ const SettingsSwitch = ({
try { try {
if (field.scope === 'client') { if (field.scope === 'client') {
localStorage.setItem(field.key, String(newValue)); localStorage.setItem(field.key, String(newValue));
emitClientConfigChanged();
} else { } else {
const res = await fetch('/api/config', { const res = await fetch('/api/config', {
method: 'POST', method: 'POST',
@@ -289,7 +299,7 @@ const SettingsSwitch = ({
<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"> <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 className="flex flex-row items-center space-x-3 lg:space-x-5 w-full justify-between">
<div> <div>
<h4 className="text-sm lg:text-base text-black dark:text-white"> <h4 className="text-sm lg:text-sm text-black dark:text-white">
{field.name} {field.name}
</h4> </h4>
<p className="text-[11px] lg:text-xs text-black/50 dark:text-white/50"> <p className="text-[11px] lg:text-xs text-black/50 dark:text-white/50">

View File

@@ -11,3 +11,9 @@ export const getAutoMediaSearch = () =>
export const getSystemInstructions = () => export const getSystemInstructions = () =>
getClientConfig('systemInstructions', ''); getClientConfig('systemInstructions', '');
export const getShowWeatherWidget = () =>
getClientConfig('showWeatherWidget', 'true') === 'true';
export const getShowNewsWidget = () =>
getClientConfig('showNewsWidget', 'true') === 'true';

View File

@@ -69,6 +69,24 @@ class ConfigManager {
default: true, default: true,
scope: 'client', scope: 'client',
}, },
{
name: 'Show weather widget',
key: 'showWeatherWidget',
type: 'switch',
required: false,
description: 'Display the weather card on the home screen.',
default: true,
scope: 'client',
},
{
name: 'Show news widget',
key: 'showNewsWidget',
type: 'switch',
required: false,
description: 'Display the recent news card on the home screen.',
default: true,
scope: 'client',
},
], ],
personalization: [ personalization: [
{ {

View File

@@ -48,7 +48,12 @@ class GeminiProvider extends BaseModelProvider<GeminiConfig> {
let defaultChatModels: Model[] = []; let defaultChatModels: Model[] = [];
data.models.forEach((m: any) => { data.models.forEach((m: any) => {
if (m.supportedGenerationMethods.some((genMethod: string) => genMethod === 'embedText' || genMethod === 'embedContent')) { if (
m.supportedGenerationMethods.some(
(genMethod: string) =>
genMethod === 'embedText' || genMethod === 'embedContent',
)
) {
defaultEmbeddingModels.push({ defaultEmbeddingModels.push({
key: m.name, key: m.name,
name: m.displayName, name: m.displayName,