mirror of
https://github.com/ItzCrazyKns/Perplexica.git
synced 2025-10-24 16:08:15 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f50462f1d | ||
|
|
231bc22a36 | ||
|
|
cb1d85e458 | ||
|
|
ce78b4ff62 | ||
|
|
88ae67065b | ||
|
|
f35d12f94c | ||
|
|
3d17975d83 | ||
|
|
950717e0cf | ||
|
|
4f39b5746a |
Binary file not shown.
|
Before Width: | Height: | Size: 2.1 MiB After Width: | Height: | Size: 2.1 MiB |
@@ -79,19 +79,19 @@ There are mainly 2 ways of installing Perplexica - With Docker, Without Docker.
|
|||||||
Perplexica can be easily run using Docker. Simply run the following command:
|
Perplexica can be easily run using Docker. Simply run the following command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run -p 3000:3000 --name perplexica itzcrazykns1337/perplexica:latest
|
docker run -d -p 3000:3000 -v perplexica-data:/home/perplexica/data -v perplexica-uploads:/home/perplexica/uploads --name perplexica itzcrazykns1337/perplexica:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
This will pull and start the Perplexica container with the bundled SearxNG search engine. Once running, open your browser and navigate to http://localhost:3000. You can then configure your settings (API keys, models, etc.) directly in the setup screen.
|
This will pull and start the Perplexica container with the bundled SearxNG search engine. Once running, open your browser and navigate to http://localhost:3000. You can then configure your settings (API keys, models, etc.) directly in the setup screen.
|
||||||
|
|
||||||
**Note**: The image includes both Perplexica and SearxNG, so no additional setup is required.
|
**Note**: The image includes both Perplexica and SearxNG, so no additional setup is required. The `-v` flags create persistent volumes for your data and uploaded files.
|
||||||
|
|
||||||
#### Using Perplexica with Your Own SearxNG Instance
|
#### Using Perplexica with Your Own SearxNG Instance
|
||||||
|
|
||||||
If you already have SearxNG running, you can use the slim version of Perplexica:
|
If you already have SearxNG running, you can use the slim version of Perplexica:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run -p 3000:3000 -e SEARXNG_API_URL=http://your-searxng-url:8080 --name perplexica itzcrazykns1337/perplexica:slim-latest
|
docker run -d -p 3000:3000 -e SEARXNG_API_URL=http://your-searxng-url:8080 -v perplexica-data:/home/perplexica/data -v perplexica-uploads:/home/perplexica/uploads --name perplexica itzcrazykns1337/perplexica:slim-latest
|
||||||
```
|
```
|
||||||
|
|
||||||
**Important**: Make sure your SearxNG instance has:
|
**Important**: Make sure your SearxNG instance has:
|
||||||
@@ -118,7 +118,7 @@ If you prefer to build from source or need more control:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker build -t perplexica .
|
docker build -t perplexica .
|
||||||
docker run -p 3000:3000 --name perplexica perplexica
|
docker run -d -p 3000:3000 -v perplexica-data:/home/perplexica/data -v perplexica-uploads:/home/perplexica/uploads --name perplexica perplexica
|
||||||
```
|
```
|
||||||
|
|
||||||
5. Access Perplexica at http://localhost:3000 and configure your settings in the setup screen.
|
5. Access Perplexica at http://localhost:3000 and configure your settings in the setup screen.
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
FROM node:24.5.0-slim AS builder
|
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y python3 python3-pip sqlite3 && rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
WORKDIR /home/perplexica
|
|
||||||
|
|
||||||
COPY package.json yarn.lock ./
|
|
||||||
RUN yarn install --frozen-lockfile --network-timeout 600000
|
|
||||||
|
|
||||||
COPY tsconfig.json next.config.mjs next-env.d.ts postcss.config.js drizzle.config.ts tailwind.config.ts ./
|
|
||||||
COPY src ./src
|
|
||||||
COPY public ./public
|
|
||||||
COPY drizzle ./drizzle
|
|
||||||
|
|
||||||
RUN mkdir -p /home/perplexica/data
|
|
||||||
RUN yarn build
|
|
||||||
|
|
||||||
FROM node:24.5.0-slim
|
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y python3 python3-pip sqlite3 && rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
WORKDIR /home/perplexica
|
|
||||||
|
|
||||||
COPY --from=builder /home/perplexica/public ./public
|
|
||||||
COPY --from=builder /home/perplexica/.next/static ./public/_next/static
|
|
||||||
|
|
||||||
COPY --from=builder /home/perplexica/.next/standalone ./
|
|
||||||
COPY --from=builder /home/perplexica/data ./data
|
|
||||||
COPY drizzle ./drizzle
|
|
||||||
|
|
||||||
RUN mkdir /home/perplexica/uploads
|
|
||||||
|
|
||||||
COPY entrypoint.sh ./entrypoint.sh
|
|
||||||
RUN chmod +x ./entrypoint.sh
|
|
||||||
RUN sed -i 's/\r$//' ./entrypoint.sh || true
|
|
||||||
|
|
||||||
CMD ["/home/perplexica/entrypoint.sh"]
|
|
||||||
15
docker-compose.yaml
Normal file
15
docker-compose.yaml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
services:
|
||||||
|
perplexica:
|
||||||
|
image: itzcrazykns1337/perplexica:latest
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
volumes:
|
||||||
|
- data:/home/perplexica/data
|
||||||
|
- uploads:/home/perplexica/uploads
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
data:
|
||||||
|
name: 'perplexica-data'
|
||||||
|
uploads:
|
||||||
|
name: 'perplexica-uploads'
|
||||||
@@ -10,7 +10,7 @@ Simply pull the latest image and restart your container:
|
|||||||
docker pull itzcrazykns1337/perplexica:latest
|
docker pull itzcrazykns1337/perplexica:latest
|
||||||
docker stop perplexica
|
docker stop perplexica
|
||||||
docker rm perplexica
|
docker rm perplexica
|
||||||
docker run -p 3000:3000 --name perplexica itzcrazykns1337/perplexica:latest
|
docker run -d -p 3000:3000 -v perplexica-data:/home/perplexica/data -v perplexica-uploads:/home/perplexica/uploads --name perplexica itzcrazykns1337/perplexica:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
For slim version:
|
For slim version:
|
||||||
@@ -19,7 +19,7 @@ For slim version:
|
|||||||
docker pull itzcrazykns1337/perplexica:slim-latest
|
docker pull itzcrazykns1337/perplexica:slim-latest
|
||||||
docker stop perplexica
|
docker stop perplexica
|
||||||
docker rm perplexica
|
docker rm perplexica
|
||||||
docker run -p 3000:3000 -e SEARXNG_API_URL=http://your-searxng-url:8080 --name perplexica itzcrazykns1337/perplexica:slim-latest
|
docker run -d -p 3000:3000 -e SEARXNG_API_URL=http://your-searxng-url:8080 -v perplexica-data:/home/perplexica/data -v perplexica-uploads:/home/perplexica/uploads --name perplexica itzcrazykns1337/perplexica:slim-latest
|
||||||
```
|
```
|
||||||
|
|
||||||
Once updated, go to http://localhost:3000 and verify the latest changes. Your settings are preserved automatically.
|
Once updated, go to http://localhost:3000 and verify the latest changes. Your settings are preserved automatically.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "perplexica-frontend",
|
"name": "perplexica-frontend",
|
||||||
"version": "1.11.0",
|
"version": "1.11.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"author": "ItzCrazyKns",
|
"author": "ItzCrazyKns",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -112,100 +112,96 @@ const ModelProvider = ({
|
|||||||
>
|
>
|
||||||
<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-col gap-y-4 px-5 py-4">
|
<div className="flex flex-col gap-y-4 px-5 py-4">
|
||||||
{modelProvider.chatModels.length > 0 && (
|
<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 text-black/70 dark:text-white/70">
|
||||||
<p className="text-[11px] lg:text-xs text-black/70 dark:text-white/70">
|
Chat models
|
||||||
Chat models
|
</p>
|
||||||
</p>
|
<AddModel
|
||||||
<AddModel
|
providerId={modelProvider.id}
|
||||||
providerId={modelProvider.id}
|
setProviders={setProviders}
|
||||||
setProviders={setProviders}
|
type="chat"
|
||||||
type="chat"
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
{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">
|
|
||||||
<AlertCircle size={16} className="shrink-0" />
|
|
||||||
<span className="break-words">
|
|
||||||
{
|
|
||||||
modelProvider.chatModels.find(
|
|
||||||
(m) => m.key === 'error',
|
|
||||||
)?.name
|
|
||||||
}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="flex flex-row flex-wrap gap-2">
|
|
||||||
{modelProvider.chatModels.map((model, index) => (
|
|
||||||
<div
|
|
||||||
key={`${modelProvider.id}-chat-${model.key}-${index}`}
|
|
||||||
className="flex flex-row items-center space-x-1 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"
|
|
||||||
>
|
|
||||||
<span>{model.name}</span>
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
handleModelDelete('chat', model.key);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<X size={12} />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
<div className="flex flex-col gap-2">
|
||||||
{modelProvider.embeddingModels.length > 0 && (
|
{modelProvider.chatModels.some((m) => m.key === 'error') ? (
|
||||||
<div className="flex flex-col gap-y-2">
|
<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 w-full justify-between items-center">
|
<AlertCircle size={16} className="shrink-0" />
|
||||||
<p className="text-[11px] lg:text-xs text-black/70 dark:text-white/70">
|
<span className="break-words">
|
||||||
Embedding models
|
{
|
||||||
</p>
|
modelProvider.chatModels.find(
|
||||||
<AddModel
|
(m) => m.key === 'error',
|
||||||
providerId={modelProvider.id}
|
)?.name
|
||||||
setProviders={setProviders}
|
}
|
||||||
type="embedding"
|
</span>
|
||||||
/>
|
</div>
|
||||||
</div>
|
) : (
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-row flex-wrap gap-2">
|
||||||
{modelProvider.embeddingModels.some(
|
{modelProvider.chatModels.map((model, index) => (
|
||||||
(m) => m.key === 'error',
|
<div
|
||||||
) ? (
|
key={`${modelProvider.id}-chat-${model.key}-${index}`}
|
||||||
<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">
|
className="flex flex-row items-center space-x-1 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"
|
||||||
<AlertCircle size={16} className="shrink-0" />
|
>
|
||||||
<span className="break-words">
|
<span>{model.name}</span>
|
||||||
{
|
<button
|
||||||
modelProvider.embeddingModels.find(
|
onClick={() => {
|
||||||
(m) => m.key === 'error',
|
handleModelDelete('chat', model.key);
|
||||||
)?.name
|
}}
|
||||||
}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="flex flex-row flex-wrap gap-2">
|
|
||||||
{modelProvider.embeddingModels.map((model, index) => (
|
|
||||||
<div
|
|
||||||
key={`${modelProvider.id}-embedding-${model.key}-${index}`}
|
|
||||||
className="flex flex-row items-center space-x-1 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"
|
|
||||||
>
|
>
|
||||||
<span>{model.name}</span>
|
<X size={12} />
|
||||||
<button
|
</button>
|
||||||
onClick={() => {
|
</div>
|
||||||
handleModelDelete('embedding', model.key);
|
))}
|
||||||
}}
|
</div>
|
||||||
>
|
)}
|
||||||
<X size={12} />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
|
<div className="flex flex-col gap-y-2">
|
||||||
|
<div className="flex flex-row w-full justify-between items-center">
|
||||||
|
<p className="text-[11px] lg:text-xs text-black/70 dark:text-white/70">
|
||||||
|
Embedding models
|
||||||
|
</p>
|
||||||
|
<AddModel
|
||||||
|
providerId={modelProvider.id}
|
||||||
|
setProviders={setProviders}
|
||||||
|
type="embedding"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
{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">
|
||||||
|
<AlertCircle size={16} className="shrink-0" />
|
||||||
|
<span className="break-words">
|
||||||
|
{
|
||||||
|
modelProvider.embeddingModels.find(
|
||||||
|
(m) => m.key === 'error',
|
||||||
|
)?.name
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex flex-row flex-wrap gap-2">
|
||||||
|
{modelProvider.embeddingModels.map((model, index) => (
|
||||||
|
<div
|
||||||
|
key={`${modelProvider.id}-embedding-${model.key}-${index}`}
|
||||||
|
className="flex flex-row items-center space-x-1 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"
|
||||||
|
>
|
||||||
|
<span>{model.name}</span>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
handleModelDelete('embedding', model.key);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<X size={12} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -11,14 +11,16 @@ const ModelSelect = ({
|
|||||||
type: 'chat' | 'embedding';
|
type: 'chat' | 'embedding';
|
||||||
}) => {
|
}) => {
|
||||||
const [selectedModel, setSelectedModel] = useState<string>(
|
const [selectedModel, setSelectedModel] = useState<string>(
|
||||||
`${providers[0]?.id}/${providers[0].embeddingModels[0]?.key}`,
|
type === 'chat'
|
||||||
|
? `${localStorage.getItem('chatModelProviderId')}/${localStorage.getItem('chatModelKey')}`
|
||||||
|
: `${localStorage.getItem('embeddingModelProviderId')}/${localStorage.getItem('embeddingModelKey')}`,
|
||||||
);
|
);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
const handleSave = async (newValue: string) => {
|
const handleSave = async (newValue: string) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setSelectedModel(newValue);
|
setSelectedModel(newValue);
|
||||||
console.log(newValue);
|
|
||||||
try {
|
try {
|
||||||
if (type === 'chat') {
|
if (type === 'chat') {
|
||||||
localStorage.setItem('chatModelProviderId', newValue.split('/')[0]);
|
localStorage.setItem('chatModelProviderId', newValue.split('/')[0]);
|
||||||
|
|||||||
@@ -63,8 +63,7 @@ const SetupConfig = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const hasProviders =
|
const hasProviders = providers.length > 0;
|
||||||
providers.filter((p) => p.chatModels.length > 0).length > 0;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-[95vw] md:w-[80vw] lg:w-[65vw] mx-auto px-2 sm:px-4 md:px-6 flex flex-col space-y-6">
|
<div className="w-[95vw] md:w-[80vw] lg:w-[65vw] mx-auto px-2 sm:px-4 md:px-6 flex flex-col space-y-6">
|
||||||
|
|||||||
@@ -50,6 +50,25 @@ class ConfigManager {
|
|||||||
'e.g., "Respond in a friendly and concise tone" or "Use British English and format answers as bullet points."',
|
'e.g., "Respond in a friendly and concise tone" or "Use British English and format answers as bullet points."',
|
||||||
scope: 'client',
|
scope: 'client',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'Measurement Unit',
|
||||||
|
key: 'measureUnit',
|
||||||
|
type: 'select',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Imperial',
|
||||||
|
value: 'Imperial',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Metric',
|
||||||
|
value: 'Metric',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
required: false,
|
||||||
|
description: 'Choose between Metric and Imperial measurement unit.',
|
||||||
|
default: 'Metric',
|
||||||
|
scope: 'client',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
modelProviders: [],
|
modelProviders: [],
|
||||||
search: [
|
search: [
|
||||||
|
|||||||
Reference in New Issue
Block a user