Compare commits

..

22 Commits

Author SHA1 Message Date
realies
1f3c33de1f Merge b68aec1b7f into 89b5229ce9 2025-03-05 12:24:06 +05:30
ItzCrazyKns
89b5229ce9 Merge pull request #663 from ericdachen/master
Update Readme
2025-03-05 11:11:07 +05:30
ItzCrazyKns
7756340dd9 Update README.md 2025-03-05 11:09:19 +05:30
ItzCrazyKns
bbd2e9c359 feat(readme): update warp banner 2025-03-05 11:05:25 +05:30
ItzCrazyKns
a32eb1dda3 feat(readme): lint & beautify, update anchor URL 2025-03-05 10:55:02 +05:30
Eric Chen
aa834f7f04 Update README.md 2025-03-04 14:45:10 -05:00
Eric Chen
064c0fbe42 Update README.md 2025-03-04 12:16:10 -05:00
Eric Chen
bf4cf8eaeb Update README.md 2025-03-04 12:14:17 -05:00
realies
b68aec1b7f fix(docker-usage): init 2025-03-03 04:56:44 +00:00
ItzCrazyKns
a24992a3db Merge pull request #655 from ShortCipher5/patch-1
chore: Add Sealos 1-click deployment
2025-03-01 21:56:01 +05:30
ShortCipher5
d584067bb1 Update README.md 2025-02-27 23:26:45 -08:00
ItzCrazyKns
df4350f966 Merge branch 'master' of https://github.com/ItzCrazyKns/Perplexica 2025-02-26 10:40:34 +05:30
ItzCrazyKns
652ca2fdf4 Merge pull request #649 from QuietlyChan/fix/light-theme-ui-bug
fix(ui): improve dark mode text color for attachment buttons
2025-02-26 10:36:41 +05:30
QuietlyChan
216576128d fix(ui): update attachment text color for light and dark modes 2025-02-25 19:26:58 +08:00
QuietlyChan
bb3f180583 fix(ui): improve dark mode text color for attachment buttons 2025-02-25 17:26:33 +08:00
ItzCrazyKns
4d24d73161 Merge pull request #631 from user1007017/patch-1
Update README.md grammatical error
2025-02-20 10:37:33 +05:30
wellCh4n
2e166c217b fix(MessageBox): break too long message title 2025-02-19 10:34:51 +08:00
ItzCrazyKns
4c73caadf6 feat(custom-openai): save live changes 2025-02-17 16:24:41 +05:30
user1007017
5f0b87f4a9 Update README.md 2025-02-15 19:06:46 +01:00
ItzCrazyKns
115e6b2a71 Merge branch 'master' of https://github.com/ItzCrazyKns/Perplexica 2025-02-15 12:52:30 +05:30
ItzCrazyKns
a5c79c92ed feat(settings): add embedding provider settings 2025-02-15 12:52:27 +05:30
ItzCrazyKns
db3cea446e Update UPDATING.md 2025-02-15 12:33:43 +05:30
17 changed files with 379 additions and 133 deletions

View File

@@ -1,7 +1,22 @@
# 🚀 Perplexica - An AI-powered search engine 🔎 <!-- omit in toc --> # 🚀 Perplexica - An AI-powered search engine 🔎 <!-- omit in toc -->
[![Discord](https://dcbadge.vercel.app/api/server/26aArMy8tT?style=flat&compact=true)](https://discord.gg/26aArMy8tT) <div align="center" markdown="1">
<sup>Special thanks to:</sup>
<br>
<br>
<a href="https://www.warp.dev/perplexica">
<img alt="Warp sponsorship" width="400" src="https://github.com/user-attachments/assets/775dd593-9b5f-40f1-bf48-479faff4c27b">
</a>
### [Warp, the AI Devtool that lives in your terminal](https://www.warp.dev/perplexica)
[Available for MacOS, Linux, & Windows](https://www.warp.dev/perplexica)
</div>
<hr/>
[![Discord](https://dcbadge.vercel.app/api/server/26aArMy8tT?style=flat&compact=true)](https://discord.gg/26aArMy8tT)
![preview](.assets/perplexica-screenshot.png?) ![preview](.assets/perplexica-screenshot.png?)
@@ -13,10 +28,11 @@
- [Installation](#installation) - [Installation](#installation)
- [Getting Started with Docker (Recommended)](#getting-started-with-docker-recommended) - [Getting Started with Docker (Recommended)](#getting-started-with-docker-recommended)
- [Non-Docker Installation](#non-docker-installation) - [Non-Docker Installation](#non-docker-installation)
- [Nginx Reverse Proxy](#nginx-reverse-proxy)
- [Ollama Connection Errors](#ollama-connection-errors) - [Ollama Connection Errors](#ollama-connection-errors)
- [Using as a Search Engine](#using-as-a-search-engine) - [Using as a Search Engine](#using-as-a-search-engine)
- [Using Perplexica's API](#using-perplexicas-api) - [Using Perplexica's API](#using-perplexicas-api)
- [Expose Perplexica to a network](#expose-perplexica-to-network) - [Expose Perplexica to a Network](#expose-perplexica-to-a-network)
- [One-Click Deployment](#one-click-deployment) - [One-Click Deployment](#one-click-deployment)
- [Upcoming Features](#upcoming-features) - [Upcoming Features](#upcoming-features)
- [Support Us](#support-us) - [Support Us](#support-us)
@@ -44,7 +60,7 @@ Want to know more about its architecture and how it works? You can read it [here
- **Normal Mode:** Processes your query and performs a web search. - **Normal Mode:** Processes your query and performs a web search.
- **Focus Modes:** Special modes to better answer specific types of questions. Perplexica currently has 6 focus modes: - **Focus Modes:** Special modes to better answer specific types of questions. Perplexica currently has 6 focus modes:
- **All Mode:** Searches the entire web to find the best results. - **All Mode:** Searches the entire web to find the best results.
- **Writing Assistant Mode:** Helpful for writing tasks that does not require searching the web. - **Writing Assistant Mode:** Helpful for writing tasks that do not require searching the web.
- **Academic Search Mode:** Finds articles and papers, ideal for academic research. - **Academic Search Mode:** Finds articles and papers, ideal for academic research.
- **YouTube Search Mode:** Finds YouTube videos based on the search query. - **YouTube Search Mode:** Finds YouTube videos based on the search query.
- **Wolfram Alpha Search Mode:** Answers queries that need calculations or data analysis using Wolfram Alpha. - **Wolfram Alpha Search Mode:** Answers queries that need calculations or data analysis using Wolfram Alpha.
@@ -103,6 +119,17 @@ There are mainly 2 ways of installing Perplexica - With Docker, Without Docker.
See the [installation documentation](https://github.com/ItzCrazyKns/Perplexica/tree/master/docs/installation) for more information like exposing it your network, etc. See the [installation documentation](https://github.com/ItzCrazyKns/Perplexica/tree/master/docs/installation) for more information like exposing it your network, etc.
### Nginx Reverse Proxy
Perplexica includes an Nginx reverse proxy that provides several key benefits:
- **Single Port Access**: Access both frontend and backend through a single port (8080)
- **Dynamic Configuration**: Works with any domain or IP without rebuilding
- **WebSocket Support**: Automatic WebSocket URL configuration based on the current domain
- **Security Headers**: Enhanced security with proper HTTP headers
When using Docker, the reverse proxy is automatically configured. Access Perplexica at `http://localhost:8080` or `http://your-ip:8080` after starting the containers.
### Ollama Connection Errors ### Ollama Connection Errors
If you're encountering an Ollama connection error, it is likely due to the backend being unable to connect to Ollama's API. To fix this issue you can: If you're encountering an Ollama connection error, it is likely due to the backend being unable to connect to Ollama's API. To fix this issue you can:
@@ -128,7 +155,7 @@ If you wish to use Perplexica as an alternative to traditional search engines li
1. Open your browser's settings. 1. Open your browser's settings.
2. Navigate to the 'Search Engines' section. 2. Navigate to the 'Search Engines' section.
3. Add a new site search with the following URL: `http://localhost:3000/?q=%s`. Replace `localhost` with your IP address or domain name, and `3000` with the port number if Perplexica is not hosted locally. 3. Add a new site search with the following URL: `http://localhost:8080/?q=%s`. Replace `localhost` with your IP address or domain name if needed.
4. Click the add button. Now, you can use Perplexica directly from your browser's search bar. 4. Click the add button. Now, you can use Perplexica directly from your browser's search bar.
## Using Perplexica's API ## Using Perplexica's API
@@ -137,12 +164,19 @@ Perplexica also provides an API for developers looking to integrate its powerful
For more details, check out the full documentation [here](https://github.com/ItzCrazyKns/Perplexica/tree/master/docs/API/SEARCH.md). For more details, check out the full documentation [here](https://github.com/ItzCrazyKns/Perplexica/tree/master/docs/API/SEARCH.md).
## Expose Perplexica to network ## Expose Perplexica to a Network
You can access Perplexica over your home network by following our networking guide [here](https://github.com/ItzCrazyKns/Perplexica/blob/master/docs/installation/NETWORKING.md). Perplexica can be easily accessed over your home network or exposed to the internet through the Nginx reverse proxy. With this setup:
1. **Local Network Access**: Access Perplexica from any device on your network using `http://server-ip:8080`
2. **Domain Configuration**: If you have a domain name, point it to your server and access Perplexica with `http://your-domain.com:8080`
3. **SSL Support**: Configure SSL certificates in Nginx for secure `https://` access
For more network configuration details, see our [networking guide](https://github.com/ItzCrazyKns/Perplexica/blob/master/docs/installation/NETWORKING.md).
## One-Click Deployment ## One-Click Deployment
[![Deploy to Sealos](https://raw.githubusercontent.com/labring-actions/templates/main/Deploy-on-Sealos.svg)](https://usw.sealos.io/?openapp=system-template%3FtemplateName%3Dperplexica)
[![Deploy to RepoCloud](https://d16t0pc4846x52.cloudfront.net/deploylobe.svg)](https://repocloud.io/details/?app_id=267) [![Deploy to RepoCloud](https://d16t0pc4846x52.cloudfront.net/deploylobe.svg)](https://repocloud.io/details/?app_id=267)
## Upcoming Features ## Upcoming Features

View File

@@ -35,8 +35,8 @@ services:
context: . context: .
dockerfile: app.dockerfile dockerfile: app.dockerfile
args: args:
- NEXT_PUBLIC_API_URL=http://127.0.0.1:3001/api - NEXT_PUBLIC_API_URL=/api
- NEXT_PUBLIC_WS_URL=ws://127.0.0.1:3001 - NEXT_PUBLIC_WS_URL=auto
image: itzcrazykns1337/perplexica-frontend:main image: itzcrazykns1337/perplexica-frontend:main
depends_on: depends_on:
- perplexica-backend - perplexica-backend
@@ -46,6 +46,19 @@ services:
- perplexica-network - perplexica-network
restart: unless-stopped restart: unless-stopped
nginx:
image: nginx:alpine
ports:
- "8080:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- perplexica-frontend
- perplexica-backend
networks:
- perplexica-network
restart: unless-stopped
networks: networks:
perplexica-network: perplexica-network:

View File

@@ -1,109 +1,106 @@
# Expose Perplexica to a network # Exposing Perplexica to a Network
This guide will show you how to make Perplexica available over a network. Follow these steps to allow computers on the same network to interact with Perplexica. Choose the instructions that match the operating system you are using. This guide explains how to make Perplexica available over a network using the built-in Nginx reverse proxy.
## Windows ## Accessing Perplexica Over a Network
1. Open PowerShell as Administrator ### Basic Access
2. Navigate to the directory containing the `docker-compose.yaml` file With the Nginx reverse proxy, Perplexica is automatically accessible from any device on your network:
3. Stop and remove the existing Perplexica containers and images:
1. Start Perplexica using Docker Compose:
```bash ```bash
docker compose down --rmi all docker compose up -d
``` ```
4. Open the `docker-compose.yaml` file in a text editor like Notepad++ 2. Find your server's IP address:
- **Windows**: `ipconfig` in Command Prompt
- **macOS**: `ifconfig | grep "inet "` in Terminal
- **Linux**: `ip addr show | grep "inet "` in Terminal
5. Replace `127.0.0.1` with the IP address of the server Perplexica is running on in these two lines: 3. Access Perplexica from any device on your network:
```
```bash http://YOUR_SERVER_IP:8080
args:
- NEXT_PUBLIC_API_URL=http://127.0.0.1:3001/api
- NEXT_PUBLIC_WS_URL=ws://127.0.0.1:3001
``` ```
6. Save and close the `docker-compose.yaml` file ### Domain Configuration
7. Rebuild and restart the Perplexica container: If you have a domain name, you can point it to your server:
```bash 1. Configure your domain's DNS settings to point to your server IP
docker compose up -d --build
2. Access Perplexica via:
```
http://your-domain.com:8080
``` ```
## macOS ## Advanced Configuration
1. Open the Terminal application ### Custom Port
2. Navigate to the directory with the `docker-compose.yaml` file: If you need to use a different port instead of the default 8080:
```bash 1. Modify the `docker-compose.yaml` file:
cd /path/to/docker-compose.yaml ```yaml
nginx:
ports:
- "YOUR_CUSTOM_PORT:80"
``` ```
3. Stop and remove existing containers and images: 2. Restart the containers:
```bash ```bash
docker compose down --rmi all docker compose down && docker compose up -d
``` ```
4. Open `docker-compose.yaml` in a text editor like Sublime Text: ### SSL/HTTPS Configuration
```bash For secure HTTPS access:
nano docker-compose.yaml
1. Modify the Nginx configuration to include SSL:
```nginx
# In nginx.conf
server {
listen 80;
listen 443 ssl;
ssl_certificate /path/to/certificate.crt;
ssl_certificate_key /path/to/private.key;
# Rest of configuration...
}
``` ```
5. Replace `127.0.0.1` with the server IP in these lines: 2. Update the Docker volume to include your certificates:
```yaml
```bash nginx:
args: volumes:
- NEXT_PUBLIC_API_URL=http://127.0.0.1:3001/api - ./nginx.conf:/etc/nginx/nginx.conf:ro
- NEXT_PUBLIC_WS_URL=ws://127.0.0.1:3001 - ./ssl:/path/to/ssl:ro
``` ```
6. Save and exit the editor 3. Restart the containers:
7. Rebuild and restart Perplexica:
```bash ```bash
docker compose up -d --build docker compose down && docker compose up -d
``` ```
## Linux 4. Or just use another reverse proxy on top of this one...
1. Open the terminal ## Troubleshooting
2. Navigate to the `docker-compose.yaml` directory: If you encounter issues accessing Perplexica over your network:
1. **Firewall Settings**: Ensure port 8080 (or your custom port) is allowed in your firewall
2. **Docker Network**: Check if Docker's network settings allow external connections:
```bash ```bash
cd /path/to/docker-compose.yaml docker network inspect perplexica_perplexica-network
``` ```
3. Stop and remove containers and images: 3. **Nginx Logs**: Check for any connection issues:
```bash ```bash
docker compose down --rmi all docker logs perplexica-nginx-1
``` ```
4. Edit `docker-compose.yaml`: 4. **Direct Access**: Verify if you can access the services directly:
- Frontend: http://YOUR_SERVER_IP:3000
```bash - Backend: http://YOUR_SERVER_IP:3001
nano docker-compose.yaml
```
5. Replace `127.0.0.1` with the server IP:
```bash
args:
- NEXT_PUBLIC_API_URL=http://127.0.0.1:3001/api
- NEXT_PUBLIC_WS_URL=ws://127.0.0.1:3001
```
6. Save and exit the editor
7. Rebuild and restart Perplexica:
```bash
docker compose up -d --build
```

View File

@@ -7,34 +7,43 @@ To update Perplexica to the latest version, follow these steps:
1. Clone the latest version of Perplexica from GitHub: 1. Clone the latest version of Perplexica from GitHub:
```bash ```bash
git clone https://github.com/ItzCrazyKns/Perplexica.git git clone https://github.com/ItzCrazyKns/Perplexica.git
``` ```
2. Navigate to the Project Directory. 2. Navigate to the project directory.
3. Pull latest images from registry. 3. Check for changes in the configuration files. If the `sample.config.toml` file contains new fields, delete your existing `config.toml` file, rename `sample.config.toml` to `config.toml`, and update the configuration accordingly.
4. Pull the latest images from the registry.
```bash ```bash
docker compose pull docker compose pull
``` ```
4. Update and Recreate containers. 5. Update and recreate the containers.
```bash ```bash
docker compose up -d docker compose up -d
``` ```
5. Once the command completes running go to http://localhost:3000 and verify the latest changes. 6. Once the command completes, go to http://localhost:3000 and verify the latest changes.
## For non Docker users ## For non-Docker users
1. Clone the latest version of Perplexica from GitHub: 1. Clone the latest version of Perplexica from GitHub:
```bash ```bash
git clone https://github.com/ItzCrazyKns/Perplexica.git git clone https://github.com/ItzCrazyKns/Perplexica.git
``` ```
2. Navigate to the Project Directory 2. Navigate to the project directory.
3. Execute `npm i` in both the `ui` folder and the root directory.
4. Once packages are updated, execute `npm run build` in both the `ui` folder and the root directory. 3. Check for changes in the configuration files. If the `sample.config.toml` file contains new fields, delete your existing `config.toml` file, rename `sample.config.toml` to `config.toml`, and update the configuration accordingly.
5. Finally, start both the frontend and the backend by running `npm run start` in both the `ui` folder and the root directory.
4. Execute `npm i` in both the `ui` folder and the root directory.
5. Once the packages are updated, execute `npm run build` in both the `ui` folder and the root directory.
6. Finally, start both the frontend and the backend by running `npm run start` in both the `ui` folder and the root directory.
---

50
nginx.conf Normal file
View File

@@ -0,0 +1,50 @@
events {
worker_connections 1024;
}
http {
port_in_redirect on;
absolute_redirect off;
server {
listen 80;
server_name localhost:8080;
# API requests
location /api {
proxy_pass http://perplexica-backend:3001;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# WebSocket requests
location /ws {
proxy_pass http://perplexica-backend:3001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Frontend requests
location / {
proxy_pass http://perplexica-frontend:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
server_tokens off;
}
}

View File

@@ -85,10 +85,12 @@ router.post('/', async (req, res) => {
if (body.chatModel?.provider === 'custom_openai') { if (body.chatModel?.provider === 'custom_openai') {
llm = new ChatOpenAI({ llm = new ChatOpenAI({
modelName: body.chatModel?.model || getCustomOpenaiModelName(), modelName: body.chatModel?.model || getCustomOpenaiModelName(),
openAIApiKey: body.chatModel?.customOpenAIKey || getCustomOpenaiApiKey(), openAIApiKey:
body.chatModel?.customOpenAIKey || getCustomOpenaiApiKey(),
temperature: 0.7, temperature: 0.7,
configuration: { configuration: {
baseURL: body.chatModel?.customOpenAIBaseURL || getCustomOpenaiApiUrl(), baseURL:
body.chatModel?.customOpenAIBaseURL || getCustomOpenaiApiUrl(),
}, },
}) as unknown as BaseChatModel; }) as unknown as BaseChatModel;
} else if ( } else if (

View File

@@ -223,11 +223,11 @@ const Page = () => {
setChatModels(data.chatModelProviders || {}); setChatModels(data.chatModelProviders || {});
setEmbeddingModels(data.embeddingModelProviders || {}); setEmbeddingModels(data.embeddingModelProviders || {});
const currentProvider = selectedChatModelProvider; const currentChatProvider = selectedChatModelProvider;
const newProviders = Object.keys(data.chatModelProviders || {}); const newChatProviders = Object.keys(data.chatModelProviders || {});
if (!currentProvider && newProviders.length > 0) { if (!currentChatProvider && newChatProviders.length > 0) {
const firstProvider = newProviders[0]; const firstProvider = newChatProviders[0];
const firstModel = data.chatModelProviders[firstProvider]?.[0]?.name; const firstModel = data.chatModelProviders[firstProvider]?.[0]?.name;
if (firstModel) { if (firstModel) {
@@ -237,11 +237,11 @@ const Page = () => {
localStorage.setItem('chatModel', firstModel); localStorage.setItem('chatModel', firstModel);
} }
} else if ( } else if (
currentProvider && currentChatProvider &&
(!data.chatModelProviders || (!data.chatModelProviders ||
!data.chatModelProviders[currentProvider] || !data.chatModelProviders[currentChatProvider] ||
!Array.isArray(data.chatModelProviders[currentProvider]) || !Array.isArray(data.chatModelProviders[currentChatProvider]) ||
data.chatModelProviders[currentProvider].length === 0) data.chatModelProviders[currentChatProvider].length === 0)
) { ) {
const firstValidProvider = Object.entries( const firstValidProvider = Object.entries(
data.chatModelProviders || {}, data.chatModelProviders || {},
@@ -267,6 +267,55 @@ const Page = () => {
} }
} }
const currentEmbeddingProvider = selectedEmbeddingModelProvider;
const newEmbeddingProviders = Object.keys(
data.embeddingModelProviders || {},
);
if (!currentEmbeddingProvider && newEmbeddingProviders.length > 0) {
const firstProvider = newEmbeddingProviders[0];
const firstModel =
data.embeddingModelProviders[firstProvider]?.[0]?.name;
if (firstModel) {
setSelectedEmbeddingModelProvider(firstProvider);
setSelectedEmbeddingModel(firstModel);
localStorage.setItem('embeddingModelProvider', firstProvider);
localStorage.setItem('embeddingModel', firstModel);
}
} else if (
currentEmbeddingProvider &&
(!data.embeddingModelProviders ||
!data.embeddingModelProviders[currentEmbeddingProvider] ||
!Array.isArray(
data.embeddingModelProviders[currentEmbeddingProvider],
) ||
data.embeddingModelProviders[currentEmbeddingProvider].length === 0)
) {
const firstValidProvider = Object.entries(
data.embeddingModelProviders || {},
).find(
([_, models]) => Array.isArray(models) && models.length > 0,
)?.[0];
if (firstValidProvider) {
setSelectedEmbeddingModelProvider(firstValidProvider);
setSelectedEmbeddingModel(
data.embeddingModelProviders[firstValidProvider][0].name,
);
localStorage.setItem('embeddingModelProvider', firstValidProvider);
localStorage.setItem(
'embeddingModel',
data.embeddingModelProviders[firstValidProvider][0].name,
);
} else {
setSelectedEmbeddingModelProvider(null);
setSelectedEmbeddingModel(null);
localStorage.removeItem('embeddingModelProvider');
localStorage.removeItem('embeddingModel');
}
}
setConfig(data); setConfig(data);
} }
@@ -278,6 +327,10 @@ const Page = () => {
localStorage.setItem('chatModelProvider', value); localStorage.setItem('chatModelProvider', value);
} else if (key === 'chatModel') { } else if (key === 'chatModel') {
localStorage.setItem('chatModel', value); localStorage.setItem('chatModel', value);
} else if (key === 'embeddingModelProvider') {
localStorage.setItem('embeddingModelProvider', value);
} else if (key === 'embeddingModel') {
localStorage.setItem('embeddingModel', value);
} }
} catch (err) { } catch (err) {
console.error('Failed to save:', err); console.error('Failed to save:', err);
@@ -436,7 +489,6 @@ const Page = () => {
const value = e.target.value; const value = e.target.value;
setSelectedChatModelProvider(value); setSelectedChatModelProvider(value);
saveConfig('chatModelProvider', value); saveConfig('chatModelProvider', value);
// Auto-select first model of new provider
const firstModel = const firstModel =
config.chatModelProviders[value]?.[0]?.name; config.chatModelProviders[value]?.[0]?.name;
if (firstModel) { if (firstModel) {
@@ -511,12 +563,16 @@ const Page = () => {
<Input <Input
type="text" type="text"
placeholder="Model name" placeholder="Model name"
defaultValue={config.customOpenaiModelName} value={config.customOpenaiModelName}
onChange={(e) => isSaving={savingStates['customOpenaiModelName']}
setConfig({ onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
...config, setConfig((prev) => ({
...prev!,
customOpenaiModelName: e.target.value, customOpenaiModelName: e.target.value,
}) }));
}}
onSave={(value) =>
saveConfig('customOpenaiModelName', value)
} }
/> />
</div> </div>
@@ -527,12 +583,16 @@ const Page = () => {
<Input <Input
type="text" type="text"
placeholder="Custom OpenAI API Key" placeholder="Custom OpenAI API Key"
defaultValue={config.customOpenaiApiKey} value={config.customOpenaiApiKey}
onChange={(e) => isSaving={savingStates['customOpenaiApiKey']}
setConfig({ onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
...config, setConfig((prev) => ({
...prev!,
customOpenaiApiKey: e.target.value, customOpenaiApiKey: e.target.value,
}) }));
}}
onSave={(value) =>
saveConfig('customOpenaiApiKey', value)
} }
/> />
</div> </div>
@@ -543,17 +603,96 @@ const Page = () => {
<Input <Input
type="text" type="text"
placeholder="Custom OpenAI Base URL" placeholder="Custom OpenAI Base URL"
defaultValue={config.customOpenaiApiUrl} value={config.customOpenaiApiUrl}
onChange={(e) => isSaving={savingStates['customOpenaiApiUrl']}
setConfig({ onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
...config, setConfig((prev) => ({
...prev!,
customOpenaiApiUrl: e.target.value, customOpenaiApiUrl: e.target.value,
}) }));
}}
onSave={(value) =>
saveConfig('customOpenaiApiUrl', value)
} }
/> />
</div> </div>
</div> </div>
)} )}
{config.embeddingModelProviders && (
<div className="flex flex-col space-y-4 mt-4 pt-4 border-t border-light-200 dark:border-dark-200">
<div className="flex flex-col space-y-1">
<p className="text-black/70 dark:text-white/70 text-sm">
Embedding Model Provider
</p>
<Select
value={selectedEmbeddingModelProvider ?? undefined}
onChange={(e) => {
const value = e.target.value;
setSelectedEmbeddingModelProvider(value);
saveConfig('embeddingModelProvider', value);
const firstModel =
config.embeddingModelProviders[value]?.[0]?.name;
if (firstModel) {
setSelectedEmbeddingModel(firstModel);
saveConfig('embeddingModel', firstModel);
}
}}
options={Object.keys(config.embeddingModelProviders).map(
(provider) => ({
value: provider,
label:
provider.charAt(0).toUpperCase() +
provider.slice(1),
}),
)}
/>
</div>
{selectedEmbeddingModelProvider && (
<div className="flex flex-col space-y-1">
<p className="text-black/70 dark:text-white/70 text-sm">
Embedding Model
</p>
<Select
value={selectedEmbeddingModel ?? undefined}
onChange={(e) => {
const value = e.target.value;
setSelectedEmbeddingModel(value);
saveConfig('embeddingModel', value);
}}
options={(() => {
const embeddingModelProvider =
config.embeddingModelProviders[
selectedEmbeddingModelProvider
];
return embeddingModelProvider
? embeddingModelProvider.length > 0
? embeddingModelProvider.map((model) => ({
value: model.name,
label: model.displayName,
}))
: [
{
value: '',
label: 'No models available',
disabled: true,
},
]
: [
{
value: '',
label:
'Invalid provider, please check backend logs',
disabled: true,
},
];
})()}
/>
</div>
)}
</div>
)}
</SettingsSection> </SettingsSection>
<SettingsSection title="API Keys"> <SettingsSection title="API Keys">

View File

@@ -368,7 +368,7 @@ const loadMessages = async (
const ChatWindow = ({ id }: { id?: string }) => { const ChatWindow = ({ id }: { id?: string }) => {
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const initialMessage = searchParams.get('q'); const initialMessage = searchParams?.get('q');
const [chatId, setChatId] = useState<string | undefined>(id); const [chatId, setChatId] = useState<string | undefined>(id);
const [newChatCreated, setNewChatCreated] = useState(false); const [newChatCreated, setNewChatCreated] = useState(false);
@@ -378,7 +378,9 @@ const ChatWindow = ({ id }: { id?: string }) => {
const [isWSReady, setIsWSReady] = useState(false); const [isWSReady, setIsWSReady] = useState(false);
const ws = useSocket( const ws = useSocket(
process.env.NEXT_PUBLIC_WS_URL!, process.env.NEXT_PUBLIC_WS_URL === 'auto'
? `${window.location.protocol === 'https:' ? 'wss:' : 'ws:'}//${window.location.host}/ws`
: process.env.NEXT_PUBLIC_WS_URL!,
setIsWSReady, setIsWSReady,
setHasError, setHasError,
); );

View File

@@ -68,7 +68,7 @@ const MessageBox = ({
return ( return (
<div> <div>
{message.role === 'user' && ( {message.role === 'user' && (
<div className={cn('w-full', messageIndex === 0 ? 'pt-16' : 'pt-8')}> <div className={cn('w-full', messageIndex === 0 ? 'pt-16' : 'pt-8', 'break-words')}>
<h2 className="text-black dark:text-white font-medium text-3xl lg:w-9/12"> <h2 className="text-black dark:text-white font-medium text-3xl lg:w-9/12">
{message.content} {message.content}
</h2> </h2>

View File

@@ -110,7 +110,7 @@ const Attach = ({
<button <button
type="button" type="button"
onClick={() => fileInputRef.current.click()} onClick={() => fileInputRef.current.click()}
className="flex flex-row items-center space-x-1 text-white/70 hover:text-white transition duration-200" className="flex flex-row items-center space-x-1 text-black/70 dark:text-white/70 hover:text-black hover:dark:text-white transition duration-200"
> >
<input <input
type="file" type="file"
@@ -128,7 +128,7 @@ const Attach = ({
setFiles([]); setFiles([]);
setFileIds([]); setFileIds([]);
}} }}
className="flex flex-row items-center space-x-1 text-white/70 hover:text-white transition duration-200" className="flex flex-row items-center space-x-1 text-black/70 dark:text-white/70 hover:text-black hover:dark:text-white transition duration-200"
> >
<Trash size={14} /> <Trash size={14} />
<p className="text-xs">Clear</p> <p className="text-xs">Clear</p>
@@ -145,7 +145,7 @@ const Attach = ({
<div className="bg-dark-100 flex items-center justify-center w-10 h-10 rounded-md"> <div className="bg-dark-100 flex items-center justify-center w-10 h-10 rounded-md">
<File size={16} className="text-white/70" /> <File size={16} className="text-white/70" />
</div> </div>
<p className="text-white/70 text-sm"> <p className="text-black/70 dark:text-white/70 text-sm">
{file.fileName.length > 25 {file.fileName.length > 25
? file.fileName.replace(/\.\w+$/, '').substring(0, 25) + ? file.fileName.replace(/\.\w+$/, '').substring(0, 25) +
'...' + '...' +

View File

@@ -82,7 +82,7 @@ const AttachSmall = ({
<button <button
type="button" type="button"
onClick={() => fileInputRef.current.click()} onClick={() => fileInputRef.current.click()}
className="flex flex-row items-center space-x-1 text-white/70 hover:text-white transition duration-200" className="flex flex-row items-center space-x-1 text-black/70 dark:text-white/70 hover:text-black hover:dark:text-white transition duration-200"
> >
<input <input
type="file" type="file"
@@ -100,7 +100,7 @@ const AttachSmall = ({
setFiles([]); setFiles([]);
setFileIds([]); setFileIds([]);
}} }}
className="flex flex-row items-center space-x-1 text-white/70 hover:text-white transition duration-200" className="flex flex-row items-center space-x-1 text-black/70 dark:text-white/70 hover:text-black hover:dark:text-white transition duration-200"
> >
<Trash size={14} /> <Trash size={14} />
<p className="text-xs">Clear</p> <p className="text-xs">Clear</p>
@@ -117,7 +117,7 @@ const AttachSmall = ({
<div className="bg-dark-100 flex items-center justify-center w-10 h-10 rounded-md"> <div className="bg-dark-100 flex items-center justify-center w-10 h-10 rounded-md">
<File size={16} className="text-white/70" /> <File size={16} className="text-white/70" />
</div> </div>
<p className="text-white/70 text-sm"> <p className="text-black/70 dark:text-white/70 text-sm">
{file.fileName.length > 25 {file.fileName.length > 25
? file.fileName.replace(/\.\w+$/, '').substring(0, 25) + ? file.fileName.replace(/\.\w+$/, '').substring(0, 25) +
'...' + '...' +