mirror of
https://github.com/ItzCrazyKns/Perplexica.git
synced 2025-09-16 14:21:32 +00:00
Compare commits
23 Commits
v1.10.0-rc
...
2a8cc6e1b6
Author | SHA1 | Date | |
---|---|---|---|
|
2a8cc6e1b6 | ||
|
a661450633 | ||
|
b3b8a05bd2 | ||
|
89b5229ce9 | ||
|
7756340dd9 | ||
|
bbd2e9c359 | ||
|
a32eb1dda3 | ||
|
aa834f7f04 | ||
|
064c0fbe42 | ||
|
bf4cf8eaeb | ||
|
a24992a3db | ||
|
d584067bb1 | ||
|
df4350f966 | ||
|
652ca2fdf4 | ||
|
216576128d | ||
|
bb3f180583 | ||
|
4d24d73161 | ||
|
2e166c217b | ||
|
4c73caadf6 | ||
|
5f0b87f4a9 | ||
|
115e6b2a71 | ||
|
a5c79c92ed | ||
|
db3cea446e |
37
.github/workflows/docker-build.yaml
vendored
37
.github/workflows/docker-build.yaml
vendored
@@ -10,9 +10,6 @@ on:
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
service: [backend, app]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
@@ -36,38 +33,24 @@ jobs:
|
||||
id: version
|
||||
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
|
||||
|
||||
- name: Build and push Docker image for ${{ matrix.service }}
|
||||
- name: Build and push Docker image (latest)
|
||||
if: github.ref == 'refs/heads/master' && github.event_name == 'push'
|
||||
run: |
|
||||
docker buildx create --use
|
||||
if [[ "${{ matrix.service }}" == "backend" ]]; then \
|
||||
DOCKERFILE=backend.dockerfile; \
|
||||
IMAGE_NAME=perplexica-backend; \
|
||||
else \
|
||||
DOCKERFILE=app.dockerfile; \
|
||||
IMAGE_NAME=perplexica-frontend; \
|
||||
fi
|
||||
docker buildx build --platform linux/amd64,linux/arm64 \
|
||||
--cache-from=type=registry,ref=itzcrazykns1337/${IMAGE_NAME}:main \
|
||||
docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 \
|
||||
--cache-from=type=registry,ref=itzcrazykns1337/perplexica:latest \
|
||||
--cache-to=type=inline \
|
||||
-f $DOCKERFILE \
|
||||
-t itzcrazykns1337/${IMAGE_NAME}:main \
|
||||
-f docker/Dockerfile \
|
||||
-t itzcrazykns1337/perplexica:latest \
|
||||
--push .
|
||||
|
||||
- name: Build and push release Docker image for ${{ matrix.service }}
|
||||
- name: Build and push Docker image (release)
|
||||
if: github.event_name == 'release'
|
||||
run: |
|
||||
docker buildx create --use
|
||||
if [[ "${{ matrix.service }}" == "backend" ]]; then \
|
||||
DOCKERFILE=backend.dockerfile; \
|
||||
IMAGE_NAME=perplexica-backend; \
|
||||
else \
|
||||
DOCKERFILE=app.dockerfile; \
|
||||
IMAGE_NAME=perplexica-frontend; \
|
||||
fi
|
||||
docker buildx build --platform linux/amd64,linux/arm64 \
|
||||
--cache-from=type=registry,ref=itzcrazykns1337/${IMAGE_NAME}:${{ env.RELEASE_VERSION }} \
|
||||
docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 \
|
||||
--cache-from=type=registry,ref=itzcrazykns1337/perplexica:${{ env.RELEASE_VERSION }} \
|
||||
--cache-to=type=inline \
|
||||
-f $DOCKERFILE \
|
||||
-t itzcrazykns1337/${IMAGE_NAME}:${{ env.RELEASE_VERSION }} \
|
||||
-f docker/Dockerfile \
|
||||
-t itzcrazykns1337/perplexica:${{ env.RELEASE_VERSION }} \
|
||||
--push .
|
||||
|
59
README.md
59
README.md
@@ -1,7 +1,22 @@
|
||||
# 🚀 Perplexica - An AI-powered search engine 🔎 <!-- omit in toc -->
|
||||
|
||||
[](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/>
|
||||
|
||||
[](https://discord.gg/26aArMy8tT)
|
||||
|
||||

|
||||
|
||||
@@ -11,12 +26,13 @@
|
||||
- [Preview](#preview)
|
||||
- [Features](#features)
|
||||
- [Installation](#installation)
|
||||
- [Getting Started with Docker (Recommended)](#getting-started-with-docker-recommended)
|
||||
- [Docker Installation (Recommended)](#docker-installation-recommended)
|
||||
- [Non-Docker Installation](#non-docker-installation)
|
||||
- [Nginx Reverse Proxy](#nginx-reverse-proxy)
|
||||
- [Ollama Connection Errors](#ollama-connection-errors)
|
||||
- [Using as a Search Engine](#using-as-a-search-engine)
|
||||
- [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)
|
||||
- [Upcoming Features](#upcoming-features)
|
||||
- [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.
|
||||
- **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.
|
||||
- **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.
|
||||
- **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.
|
||||
@@ -56,9 +72,9 @@ It has many more features like image and video search. Some of the planned featu
|
||||
|
||||
## Installation
|
||||
|
||||
There are mainly 2 ways of installing Perplexica - With Docker, Without Docker. Using Docker is highly recommended.
|
||||
Perplexica can be installed using Docker (recommended) or directly on your system.
|
||||
|
||||
### Getting Started with Docker (Recommended)
|
||||
### Docker Installation (Recommended)
|
||||
|
||||
1. Ensure Docker is installed and running on your system.
|
||||
2. Clone the Perplexica repository:
|
||||
@@ -86,10 +102,15 @@ There are mainly 2 ways of installing Perplexica - With Docker, Without Docker.
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
6. Wait a few minutes for the setup to complete. You can access Perplexica at http://localhost:3000 in your web browser.
|
||||
6. Wait a few minutes for the setup to complete. You can access Perplexica at http://localhost:8080 in your web browser.
|
||||
|
||||
**Note**: After the containers are built, you can start Perplexica directly from Docker without having to open a terminal.
|
||||
|
||||
The Docker configuration is located in the `docker/` directory, containing:
|
||||
- Dockerfile with multi-stage build for efficient images
|
||||
- Service configurations for the integrated process manager
|
||||
- Nginx reverse proxy configuration
|
||||
|
||||
### Non-Docker Installation
|
||||
|
||||
1. Install SearXNG and allow `JSON` format in the SearXNG settings.
|
||||
@@ -103,6 +124,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.
|
||||
|
||||
### 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
|
||||
|
||||
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 +160,7 @@ If you wish to use Perplexica as an alternative to traditional search engines li
|
||||
|
||||
1. Open your browser's settings.
|
||||
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.
|
||||
|
||||
## Using Perplexica's API
|
||||
@@ -137,12 +169,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).
|
||||
|
||||
## 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
|
||||
|
||||
[](https://usw.sealos.io/?openapp=system-template%3FtemplateName%3Dperplexica)
|
||||
[](https://repocloud.io/details/?app_id=267)
|
||||
|
||||
## Upcoming Features
|
||||
|
@@ -1,15 +0,0 @@
|
||||
FROM node:20.18.0-alpine
|
||||
|
||||
ARG NEXT_PUBLIC_WS_URL=ws://127.0.0.1:3001
|
||||
ARG NEXT_PUBLIC_API_URL=http://127.0.0.1:3001/api
|
||||
ENV NEXT_PUBLIC_WS_URL=${NEXT_PUBLIC_WS_URL}
|
||||
ENV NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}
|
||||
|
||||
WORKDIR /home/perplexica
|
||||
|
||||
COPY ui /home/perplexica/
|
||||
|
||||
RUN yarn install --frozen-lockfile
|
||||
RUN yarn build
|
||||
|
||||
CMD ["yarn", "start"]
|
@@ -1,17 +0,0 @@
|
||||
FROM node:18-slim
|
||||
|
||||
WORKDIR /home/perplexica
|
||||
|
||||
COPY src /home/perplexica/src
|
||||
COPY tsconfig.json /home/perplexica/
|
||||
COPY drizzle.config.ts /home/perplexica/
|
||||
COPY package.json /home/perplexica/
|
||||
COPY yarn.lock /home/perplexica/
|
||||
|
||||
RUN mkdir /home/perplexica/data
|
||||
RUN mkdir /home/perplexica/uploads
|
||||
|
||||
RUN yarn install --frozen-lockfile --network-timeout 600000
|
||||
RUN yarn build
|
||||
|
||||
CMD ["yarn", "start"]
|
@@ -2,49 +2,43 @@ services:
|
||||
searxng:
|
||||
image: docker.io/searxng/searxng:latest
|
||||
volumes:
|
||||
- ./searxng:/etc/searxng:rw
|
||||
ports:
|
||||
- 4000:8080
|
||||
- ./searxng:/etc/searxng
|
||||
networks:
|
||||
- perplexica-network
|
||||
restart: unless-stopped
|
||||
|
||||
perplexica-backend:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: backend.dockerfile
|
||||
image: itzcrazykns1337/perplexica-backend:main
|
||||
environment:
|
||||
- SEARXNG_API_URL=http://searxng:8080
|
||||
depends_on:
|
||||
- searxng
|
||||
perplexica:
|
||||
image: itzcrazykns1337/perplexica:latest
|
||||
ports:
|
||||
- 3001:3001
|
||||
- "8080:8080"
|
||||
environment:
|
||||
- SEARXNG_API_URL=http://searxng:4000
|
||||
- SIMILARITY_MEASURE=cosine
|
||||
- KEEP_ALIVE=5m
|
||||
- OPENAI_API_KEY=${OPENAI_API_KEY:-}
|
||||
- GROQ_API_KEY=${GROQ_API_KEY:-}
|
||||
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-}
|
||||
- GEMINI_API_KEY=${GEMINI_API_KEY:-}
|
||||
- OLLAMA_API_URL=${OLLAMA_API_URL:-}
|
||||
- CUSTOM_OPENAI_API_KEY=${CUSTOM_OPENAI_API_KEY:-}
|
||||
- CUSTOM_OPENAI_API_URL=${CUSTOM_OPENAI_API_URL:-}
|
||||
- CUSTOM_OPENAI_MODEL_NAME=${CUSTOM_OPENAI_MODEL_NAME:-}
|
||||
volumes:
|
||||
- backend-dbstore:/home/perplexica/data
|
||||
- uploads:/home/perplexica/uploads
|
||||
- ./config.toml:/home/perplexica/config.toml
|
||||
- backend-dbstore:/app/backend/data
|
||||
- uploads:/app/backend/uploads
|
||||
extra_hosts:
|
||||
- 'host.docker.internal:host-gateway'
|
||||
networks:
|
||||
- perplexica-network
|
||||
restart: unless-stopped
|
||||
|
||||
perplexica-frontend:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: app.dockerfile
|
||||
args:
|
||||
- NEXT_PUBLIC_API_URL=http://127.0.0.1:3001/api
|
||||
- NEXT_PUBLIC_WS_URL=ws://127.0.0.1:3001
|
||||
image: itzcrazykns1337/perplexica-frontend:main
|
||||
depends_on:
|
||||
- perplexica-backend
|
||||
ports:
|
||||
- 3000:3000
|
||||
- searxng
|
||||
networks:
|
||||
- perplexica-network
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8080/"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 5s
|
||||
|
||||
networks:
|
||||
perplexica-network:
|
||||
|
93
docker/Dockerfile
Normal file
93
docker/Dockerfile
Normal file
@@ -0,0 +1,93 @@
|
||||
# Multi-stage build for Perplexica
|
||||
# Stage 1: Build the backend
|
||||
FROM node:lts-alpine as backend-builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY src ./src
|
||||
COPY tsconfig.json drizzle.config.ts package.json yarn.lock ./
|
||||
|
||||
RUN yarn install --frozen-lockfile --network-timeout 600000 && \
|
||||
yarn build
|
||||
|
||||
# Stage 2: Build the frontend
|
||||
FROM node:lts-alpine as frontend-builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY ui ./
|
||||
ARG NEXT_PUBLIC_API_URL=/api
|
||||
ARG NEXT_PUBLIC_WS_URL=auto
|
||||
ENV NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}
|
||||
ENV NEXT_PUBLIC_WS_URL=${NEXT_PUBLIC_WS_URL}
|
||||
|
||||
RUN yarn install --frozen-lockfile && \
|
||||
yarn build
|
||||
|
||||
# Stage 3: Final image
|
||||
FROM node:lts-alpine
|
||||
|
||||
# Install curl and jq for GitHub API access
|
||||
RUN apk add --no-cache curl jq
|
||||
|
||||
# Determine latest S6 overlay version at build time
|
||||
RUN S6_OVERLAY_VERSION=$(curl -s https://api.github.com/repos/just-containers/s6-overlay/releases/latest | jq -r .tag_name | sed 's/^v//') && \
|
||||
echo "Using S6 overlay version: $S6_OVERLAY_VERSION" && \
|
||||
echo "$S6_OVERLAY_VERSION" > /tmp/s6-version
|
||||
|
||||
# Use Docker's TARGETARCH for automatic architecture detection
|
||||
ARG TARGETARCH
|
||||
|
||||
# Install additional required packages and create directory structure in one layer
|
||||
RUN apk add --no-cache \
|
||||
nginx \
|
||||
tzdata \
|
||||
bash && \
|
||||
mkdir -p /app/backend /app/frontend /app/data /app/uploads
|
||||
|
||||
# Map Docker's architecture names to s6-overlay architecture names and download/install
|
||||
RUN S6_OVERLAY_VERSION=$(cat /tmp/s6-version) && \
|
||||
case "${TARGETARCH}" in \
|
||||
"amd64") S6_OVERLAY_ARCH="x86_64" ;; \
|
||||
"arm64") S6_OVERLAY_ARCH="aarch64" ;; \
|
||||
"arm") S6_OVERLAY_ARCH="arm" ;; \
|
||||
*) echo "Unsupported architecture: ${TARGETARCH}. Only amd64, arm64, and arm are supported." && exit 1 ;; \
|
||||
esac && \
|
||||
echo "Target architecture: ${TARGETARCH} -> S6 architecture: ${S6_OVERLAY_ARCH}" && \
|
||||
echo "Downloading s6-overlay v${S6_OVERLAY_VERSION} for architecture: ${S6_OVERLAY_ARCH}" && \
|
||||
curl -L -s -o /tmp/s6-overlay-noarch.tar.xz "https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-noarch.tar.xz" && \
|
||||
tar -C / -Jxpf /tmp/s6-overlay-noarch.tar.xz && \
|
||||
curl -L -s -o /tmp/s6-overlay-arch.tar.xz "https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-${S6_OVERLAY_ARCH}.tar.xz" && \
|
||||
tar -C / -Jxpf /tmp/s6-overlay-arch.tar.xz && \
|
||||
curl -L -s -o /tmp/s6-overlay-symlinks-noarch.tar.xz "https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-symlinks-noarch.tar.xz" && \
|
||||
tar -C / -Jxpf /tmp/s6-overlay-symlinks-noarch.tar.xz && \
|
||||
rm -f /tmp/s6-overlay-*.tar.xz /tmp/s6-version
|
||||
|
||||
# Copy configuration files
|
||||
COPY docker/etc/s6-overlay/services /etc/services.d/
|
||||
COPY docker/etc/nginx/nginx.conf /etc/nginx/nginx.conf
|
||||
|
||||
# Make service scripts executable
|
||||
RUN chmod +x /etc/services.d/*/run /etc/services.d/*/finish
|
||||
|
||||
# Copy application files from builders
|
||||
COPY --from=backend-builder /app/dist /app/backend/dist
|
||||
COPY --from=backend-builder /app/node_modules /app/backend/node_modules
|
||||
COPY --from=backend-builder /app/package.json /app/backend/package.json
|
||||
COPY --from=backend-builder /app/drizzle.config.ts /app/backend/drizzle.config.ts
|
||||
# Copy only the schema file for Drizzle migrations
|
||||
COPY --from=backend-builder /app/src/db/schema.ts /app/backend/src/db/schema.ts
|
||||
COPY --from=frontend-builder /app/.next /app/frontend/.next
|
||||
COPY --from=frontend-builder /app/node_modules /app/frontend/node_modules
|
||||
COPY --from=frontend-builder /app/package.json /app/frontend/package.json
|
||||
COPY --from=frontend-builder /app/public /app/frontend/public
|
||||
|
||||
# Configure volumes and ports
|
||||
VOLUME ["/app/backend/data", "/app/backend/uploads"]
|
||||
EXPOSE 8080
|
||||
|
||||
# Set up healthcheck
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
||||
CMD curl -f http://localhost:8080/ || exit 1
|
||||
|
||||
ENTRYPOINT ["/init"]
|
55
docker/etc/nginx/nginx.conf
Normal file
55
docker/etc/nginx/nginx.conf
Normal file
@@ -0,0 +1,55 @@
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
port_in_redirect on;
|
||||
absolute_redirect off;
|
||||
|
||||
server {
|
||||
listen 8080;
|
||||
server_name localhost;
|
||||
|
||||
# Global timeout settings for all locations
|
||||
proxy_read_timeout 86400s; # 24 hours
|
||||
proxy_send_timeout 86400s; # 24 hours
|
||||
proxy_connect_timeout 60s; # Connection establishment timeout
|
||||
|
||||
# API requests
|
||||
location /api {
|
||||
proxy_pass http://localhost: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://localhost: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://localhost: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;
|
||||
}
|
||||
}
|
3
docker/etc/s6-overlay/services/backend/finish
Normal file
3
docker/etc/s6-overlay/services/backend/finish
Normal file
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/with-contenv bash
|
||||
s6-svc -d /var/run/s6/services/frontend
|
||||
s6-svc -d /var/run/s6/services/nginx
|
8
docker/etc/s6-overlay/services/backend/run
Normal file
8
docker/etc/s6-overlay/services/backend/run
Normal file
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/with-contenv bash
|
||||
cd /app/backend
|
||||
|
||||
# Run database migrations before starting the app
|
||||
yarn db:push
|
||||
|
||||
# Start the application
|
||||
exec node dist/app.js
|
2
docker/etc/s6-overlay/services/frontend/finish
Normal file
2
docker/etc/s6-overlay/services/frontend/finish
Normal file
@@ -0,0 +1,2 @@
|
||||
#!/usr/bin/with-contenv bash
|
||||
s6-svc -d /var/run/s6/services/nginx
|
3
docker/etc/s6-overlay/services/frontend/run
Normal file
3
docker/etc/s6-overlay/services/frontend/run
Normal file
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/with-contenv bash
|
||||
cd /app/frontend
|
||||
exec node_modules/.bin/next start
|
2
docker/etc/s6-overlay/services/nginx/run
Normal file
2
docker/etc/s6-overlay/services/nginx/run
Normal file
@@ -0,0 +1,2 @@
|
||||
#!/usr/bin/with-contenv bash
|
||||
exec nginx -g "daemon off;"
|
@@ -1,109 +1,46 @@
|
||||
# Expose Perplexica to a network
|
||||
# Accessing Perplexica over 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 access Perplexica over your network using the nginx reverse proxy included in the Docker setup.
|
||||
|
||||
## Windows
|
||||
## Basic Network Access
|
||||
|
||||
1. Open PowerShell as Administrator
|
||||
|
||||
2. Navigate to the directory containing the `docker-compose.yaml` file
|
||||
|
||||
3. Stop and remove the existing Perplexica containers and images:
|
||||
Perplexica is automatically accessible from any device on your network:
|
||||
|
||||
1. Start Perplexica using Docker Compose:
|
||||
```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:
|
||||
|
||||
```bash
|
||||
args:
|
||||
- NEXT_PUBLIC_API_URL=http://127.0.0.1:3001/api
|
||||
- NEXT_PUBLIC_WS_URL=ws://127.0.0.1:3001
|
||||
3. Access Perplexica from any device on your network:
|
||||
```
|
||||
http://YOUR_SERVER_IP:8080
|
||||
```
|
||||
|
||||
6. Save and close the `docker-compose.yaml` file
|
||||
## Custom Port Configuration
|
||||
|
||||
7. Rebuild and restart the Perplexica container:
|
||||
If you need to use a different port instead of the default 8080:
|
||||
|
||||
```bash
|
||||
docker compose up -d --build
|
||||
1. Modify the `docker-compose.yaml` file:
|
||||
```yaml
|
||||
perplexica:
|
||||
ports:
|
||||
- "YOUR_CUSTOM_PORT:8080"
|
||||
```
|
||||
|
||||
## macOS
|
||||
|
||||
1. Open the Terminal application
|
||||
|
||||
2. Navigate to the directory with the `docker-compose.yaml` file:
|
||||
|
||||
2. Restart the containers:
|
||||
```bash
|
||||
cd /path/to/docker-compose.yaml
|
||||
docker compose down && docker compose up -d
|
||||
```
|
||||
|
||||
3. Stop and remove existing containers and images:
|
||||
## Troubleshooting
|
||||
|
||||
```bash
|
||||
docker compose down --rmi all
|
||||
```
|
||||
If you encounter issues accessing Perplexica over your network:
|
||||
|
||||
4. Open `docker-compose.yaml` in a text editor like Sublime Text:
|
||||
|
||||
```bash
|
||||
nano docker-compose.yaml
|
||||
```
|
||||
|
||||
5. Replace `127.0.0.1` with the server IP in these lines:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
## Linux
|
||||
|
||||
1. Open the terminal
|
||||
|
||||
2. Navigate to the `docker-compose.yaml` directory:
|
||||
|
||||
```bash
|
||||
cd /path/to/docker-compose.yaml
|
||||
```
|
||||
|
||||
3. Stop and remove containers and images:
|
||||
|
||||
```bash
|
||||
docker compose down --rmi all
|
||||
```
|
||||
|
||||
4. Edit `docker-compose.yaml`:
|
||||
|
||||
```bash
|
||||
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
|
||||
```
|
||||
1. **Firewall Settings**: Ensure port 8080 (or your custom port) is allowed in your firewall
|
||||
2. **Docker Logs**: Check for any connection issues with `docker logs perplexica`
|
||||
3. **Network Access**: Make sure your devices are on the same network and can reach the server
|
||||
|
@@ -10,23 +10,25 @@ To update Perplexica to the latest version, follow these steps:
|
||||
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
|
||||
docker compose pull
|
||||
```
|
||||
|
||||
4. Update and Recreate containers.
|
||||
5. Update and recreate the containers.
|
||||
|
||||
```bash
|
||||
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:
|
||||
|
||||
@@ -34,7 +36,14 @@ To update Perplexica to the latest version, follow these steps:
|
||||
git clone https://github.com/ItzCrazyKns/Perplexica.git
|
||||
```
|
||||
|
||||
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.
|
||||
5. Finally, start both the frontend and the backend by running `npm run start` in both the `ui` folder and the root directory.
|
||||
2. Navigate to the project 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.
|
||||
|
||||
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.
|
||||
|
||||
---
|
||||
|
@@ -41,39 +41,83 @@ type RecursivePartial<T> = {
|
||||
[P in keyof T]?: RecursivePartial<T[P]>;
|
||||
};
|
||||
|
||||
const loadConfig = () =>
|
||||
toml.parse(
|
||||
const loadConfig = () => {
|
||||
try {
|
||||
return toml.parse(
|
||||
fs.readFileSync(path.join(__dirname, `../${configFileName}`), 'utf-8'),
|
||||
) as any as Config;
|
||||
} catch (error) {
|
||||
// Return default config if file doesn't exist
|
||||
return {
|
||||
GENERAL: {
|
||||
PORT: 3001,
|
||||
SIMILARITY_MEASURE: 'cosine',
|
||||
KEEP_ALIVE: '5m',
|
||||
},
|
||||
MODELS: {
|
||||
OPENAI: {
|
||||
API_KEY: '',
|
||||
},
|
||||
GROQ: {
|
||||
API_KEY: '',
|
||||
},
|
||||
ANTHROPIC: {
|
||||
API_KEY: '',
|
||||
},
|
||||
GEMINI: {
|
||||
API_KEY: '',
|
||||
},
|
||||
OLLAMA: {
|
||||
API_URL: '',
|
||||
},
|
||||
CUSTOM_OPENAI: {
|
||||
API_URL: '',
|
||||
API_KEY: '',
|
||||
MODEL_NAME: '',
|
||||
},
|
||||
},
|
||||
API_ENDPOINTS: {
|
||||
SEARXNG: '',
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const getPort = () => loadConfig().GENERAL.PORT;
|
||||
export const getPort = () =>
|
||||
process.env.PORT ? parseInt(process.env.PORT, 10) : loadConfig().GENERAL.PORT;
|
||||
|
||||
export const getSimilarityMeasure = () =>
|
||||
loadConfig().GENERAL.SIMILARITY_MEASURE;
|
||||
process.env.SIMILARITY_MEASURE || loadConfig().GENERAL.SIMILARITY_MEASURE;
|
||||
|
||||
export const getKeepAlive = () => loadConfig().GENERAL.KEEP_ALIVE;
|
||||
export const getKeepAlive = () =>
|
||||
process.env.KEEP_ALIVE || loadConfig().GENERAL.KEEP_ALIVE;
|
||||
|
||||
export const getOpenaiApiKey = () => loadConfig().MODELS.OPENAI.API_KEY;
|
||||
export const getOpenaiApiKey = () =>
|
||||
process.env.OPENAI_API_KEY || loadConfig().MODELS.OPENAI.API_KEY;
|
||||
|
||||
export const getGroqApiKey = () => loadConfig().MODELS.GROQ.API_KEY;
|
||||
export const getGroqApiKey = () =>
|
||||
process.env.GROQ_API_KEY || loadConfig().MODELS.GROQ.API_KEY;
|
||||
|
||||
export const getAnthropicApiKey = () => loadConfig().MODELS.ANTHROPIC.API_KEY;
|
||||
export const getAnthropicApiKey = () =>
|
||||
process.env.ANTHROPIC_API_KEY || loadConfig().MODELS.ANTHROPIC.API_KEY;
|
||||
|
||||
export const getGeminiApiKey = () => loadConfig().MODELS.GEMINI.API_KEY;
|
||||
export const getGeminiApiKey = () =>
|
||||
process.env.GEMINI_API_KEY || loadConfig().MODELS.GEMINI.API_KEY;
|
||||
|
||||
export const getSearxngApiEndpoint = () =>
|
||||
process.env.SEARXNG_API_URL || loadConfig().API_ENDPOINTS.SEARXNG;
|
||||
|
||||
export const getOllamaApiEndpoint = () => loadConfig().MODELS.OLLAMA.API_URL;
|
||||
export const getOllamaApiEndpoint = () =>
|
||||
process.env.OLLAMA_API_URL || loadConfig().MODELS.OLLAMA.API_URL;
|
||||
|
||||
export const getCustomOpenaiApiKey = () =>
|
||||
loadConfig().MODELS.CUSTOM_OPENAI.API_KEY;
|
||||
process.env.CUSTOM_OPENAI_API_KEY || loadConfig().MODELS.CUSTOM_OPENAI.API_KEY;
|
||||
|
||||
export const getCustomOpenaiApiUrl = () =>
|
||||
loadConfig().MODELS.CUSTOM_OPENAI.API_URL;
|
||||
process.env.CUSTOM_OPENAI_API_URL || loadConfig().MODELS.CUSTOM_OPENAI.API_URL;
|
||||
|
||||
export const getCustomOpenaiModelName = () =>
|
||||
loadConfig().MODELS.CUSTOM_OPENAI.MODEL_NAME;
|
||||
process.env.CUSTOM_OPENAI_MODEL_NAME || loadConfig().MODELS.CUSTOM_OPENAI.MODEL_NAME;
|
||||
|
||||
const mergeConfigs = (current: any, update: any): any => {
|
||||
if (update === null || update === undefined) {
|
||||
|
@@ -85,10 +85,12 @@ router.post('/', async (req, res) => {
|
||||
if (body.chatModel?.provider === 'custom_openai') {
|
||||
llm = new ChatOpenAI({
|
||||
modelName: body.chatModel?.model || getCustomOpenaiModelName(),
|
||||
openAIApiKey: body.chatModel?.customOpenAIKey || getCustomOpenaiApiKey(),
|
||||
openAIApiKey:
|
||||
body.chatModel?.customOpenAIKey || getCustomOpenaiApiKey(),
|
||||
temperature: 0.7,
|
||||
configuration: {
|
||||
baseURL: body.chatModel?.customOpenAIBaseURL || getCustomOpenaiApiUrl(),
|
||||
baseURL:
|
||||
body.chatModel?.customOpenAIBaseURL || getCustomOpenaiApiUrl(),
|
||||
},
|
||||
}) as unknown as BaseChatModel;
|
||||
} else if (
|
||||
|
@@ -223,11 +223,11 @@ const Page = () => {
|
||||
setChatModels(data.chatModelProviders || {});
|
||||
setEmbeddingModels(data.embeddingModelProviders || {});
|
||||
|
||||
const currentProvider = selectedChatModelProvider;
|
||||
const newProviders = Object.keys(data.chatModelProviders || {});
|
||||
const currentChatProvider = selectedChatModelProvider;
|
||||
const newChatProviders = Object.keys(data.chatModelProviders || {});
|
||||
|
||||
if (!currentProvider && newProviders.length > 0) {
|
||||
const firstProvider = newProviders[0];
|
||||
if (!currentChatProvider && newChatProviders.length > 0) {
|
||||
const firstProvider = newChatProviders[0];
|
||||
const firstModel = data.chatModelProviders[firstProvider]?.[0]?.name;
|
||||
|
||||
if (firstModel) {
|
||||
@@ -237,11 +237,11 @@ const Page = () => {
|
||||
localStorage.setItem('chatModel', firstModel);
|
||||
}
|
||||
} else if (
|
||||
currentProvider &&
|
||||
currentChatProvider &&
|
||||
(!data.chatModelProviders ||
|
||||
!data.chatModelProviders[currentProvider] ||
|
||||
!Array.isArray(data.chatModelProviders[currentProvider]) ||
|
||||
data.chatModelProviders[currentProvider].length === 0)
|
||||
!data.chatModelProviders[currentChatProvider] ||
|
||||
!Array.isArray(data.chatModelProviders[currentChatProvider]) ||
|
||||
data.chatModelProviders[currentChatProvider].length === 0)
|
||||
) {
|
||||
const firstValidProvider = Object.entries(
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -278,6 +327,10 @@ const Page = () => {
|
||||
localStorage.setItem('chatModelProvider', value);
|
||||
} else if (key === 'chatModel') {
|
||||
localStorage.setItem('chatModel', value);
|
||||
} else if (key === 'embeddingModelProvider') {
|
||||
localStorage.setItem('embeddingModelProvider', value);
|
||||
} else if (key === 'embeddingModel') {
|
||||
localStorage.setItem('embeddingModel', value);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to save:', err);
|
||||
@@ -436,7 +489,6 @@ const Page = () => {
|
||||
const value = e.target.value;
|
||||
setSelectedChatModelProvider(value);
|
||||
saveConfig('chatModelProvider', value);
|
||||
// Auto-select first model of new provider
|
||||
const firstModel =
|
||||
config.chatModelProviders[value]?.[0]?.name;
|
||||
if (firstModel) {
|
||||
@@ -511,12 +563,16 @@ const Page = () => {
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Model name"
|
||||
defaultValue={config.customOpenaiModelName}
|
||||
onChange={(e) =>
|
||||
setConfig({
|
||||
...config,
|
||||
value={config.customOpenaiModelName}
|
||||
isSaving={savingStates['customOpenaiModelName']}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setConfig((prev) => ({
|
||||
...prev!,
|
||||
customOpenaiModelName: e.target.value,
|
||||
})
|
||||
}));
|
||||
}}
|
||||
onSave={(value) =>
|
||||
saveConfig('customOpenaiModelName', value)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
@@ -527,12 +583,16 @@ const Page = () => {
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Custom OpenAI API Key"
|
||||
defaultValue={config.customOpenaiApiKey}
|
||||
onChange={(e) =>
|
||||
setConfig({
|
||||
...config,
|
||||
value={config.customOpenaiApiKey}
|
||||
isSaving={savingStates['customOpenaiApiKey']}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setConfig((prev) => ({
|
||||
...prev!,
|
||||
customOpenaiApiKey: e.target.value,
|
||||
})
|
||||
}));
|
||||
}}
|
||||
onSave={(value) =>
|
||||
saveConfig('customOpenaiApiKey', value)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
@@ -543,17 +603,96 @@ const Page = () => {
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Custom OpenAI Base URL"
|
||||
defaultValue={config.customOpenaiApiUrl}
|
||||
onChange={(e) =>
|
||||
setConfig({
|
||||
...config,
|
||||
value={config.customOpenaiApiUrl}
|
||||
isSaving={savingStates['customOpenaiApiUrl']}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setConfig((prev) => ({
|
||||
...prev!,
|
||||
customOpenaiApiUrl: e.target.value,
|
||||
})
|
||||
}));
|
||||
}}
|
||||
onSave={(value) =>
|
||||
saveConfig('customOpenaiApiUrl', value)
|
||||
}
|
||||
/>
|
||||
</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 title="API Keys">
|
||||
|
@@ -368,7 +368,7 @@ const loadMessages = async (
|
||||
|
||||
const ChatWindow = ({ id }: { id?: string }) => {
|
||||
const searchParams = useSearchParams();
|
||||
const initialMessage = searchParams.get('q');
|
||||
const initialMessage = searchParams?.get('q');
|
||||
|
||||
const [chatId, setChatId] = useState<string | undefined>(id);
|
||||
const [newChatCreated, setNewChatCreated] = useState(false);
|
||||
@@ -378,7 +378,9 @@ const ChatWindow = ({ id }: { id?: string }) => {
|
||||
|
||||
const [isWSReady, setIsWSReady] = useState(false);
|
||||
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,
|
||||
setHasError,
|
||||
);
|
||||
|
@@ -68,7 +68,7 @@ const MessageBox = ({
|
||||
return (
|
||||
<div>
|
||||
{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">
|
||||
{message.content}
|
||||
</h2>
|
||||
|
@@ -110,7 +110,7 @@ const Attach = ({
|
||||
<button
|
||||
type="button"
|
||||
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
|
||||
type="file"
|
||||
@@ -128,7 +128,7 @@ const Attach = ({
|
||||
setFiles([]);
|
||||
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} />
|
||||
<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">
|
||||
<File size={16} className="text-white/70" />
|
||||
</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.replace(/\.\w+$/, '').substring(0, 25) +
|
||||
'...' +
|
||||
|
@@ -82,7 +82,7 @@ const AttachSmall = ({
|
||||
<button
|
||||
type="button"
|
||||
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
|
||||
type="file"
|
||||
@@ -100,7 +100,7 @@ const AttachSmall = ({
|
||||
setFiles([]);
|
||||
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} />
|
||||
<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">
|
||||
<File size={16} className="text-white/70" />
|
||||
</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.replace(/\.\w+$/, '').substring(0, 25) +
|
||||
'...' +
|
||||
|
Reference in New Issue
Block a user