mirror of
https://github.com/ItzCrazyKns/Perplexica.git
synced 2025-12-16 00:28:15 +00:00
Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
476303f52b | ||
|
|
21b315d14b | ||
|
|
7c676479d4 | ||
|
|
8e18c32e23 | ||
|
|
5f6e61d7a0 | ||
|
|
32cc430b1b | ||
|
|
cf0abbb9d2 | ||
|
|
dcbcab3122 | ||
|
|
90f9edea95 | ||
|
|
9e7e1d76a2 | ||
|
|
9a36e48de5 | ||
|
|
cfab91ddbf | ||
|
|
2d9ca3835e | ||
|
|
f061345c74 | ||
|
|
5fe08b5ec8 | ||
|
|
6a2f4b8ebf | ||
|
|
4eadc0c797 | ||
|
|
743b67d0e9 | ||
|
|
c8a16a622e | ||
|
|
cae05bcf5e | ||
|
|
710b72d053 | ||
|
|
af9862c019 | ||
|
|
984b80b5ec | ||
|
|
cb65f67140 | ||
|
|
62c7f535db | ||
|
|
943458440c | ||
|
|
d28cfa3319 | ||
|
|
b37a6e1560 | ||
|
|
0a2934935e | ||
|
|
a5978d544c | ||
|
|
d46a844df8 | ||
|
|
c97a434723 | ||
|
|
382fa295e5 | ||
|
|
90f68ab214 | ||
|
|
89c30530bc | ||
|
|
776d389c1e | ||
|
|
996cc1b674 | ||
|
|
f9664d48e7 | ||
|
|
79cfd0a722 |
20
Makefile
20
Makefile
@@ -1,20 +0,0 @@
|
|||||||
.PHONY: run
|
|
||||||
run:
|
|
||||||
docker compose -f docker-compose.yaml up
|
|
||||||
|
|
||||||
|
|
||||||
.PHONY: rebuild-run
|
|
||||||
rebuild-run:
|
|
||||||
docker compose -f docker-compose.yaml build --no-cache \
|
|
||||||
&& docker compose -f docker-compose.yaml up
|
|
||||||
|
|
||||||
|
|
||||||
.PHONY: run-app-only
|
|
||||||
run-app-only:
|
|
||||||
docker compose -f app-docker-compose.yaml up
|
|
||||||
|
|
||||||
|
|
||||||
.PHONY: rebuild-run-app-only
|
|
||||||
rebuild-run-app-only:
|
|
||||||
docker compose -f app-docker-compose.yaml build --no-cache \
|
|
||||||
&& docker compose -f app-docker-compose.yaml up
|
|
||||||
21
README.md
21
README.md
@@ -118,27 +118,6 @@ If you wish to use Perplexica as an alternative to traditional search engines li
|
|||||||
|
|
||||||
[](https://repocloud.io/details/?app_id=267)
|
[](https://repocloud.io/details/?app_id=267)
|
||||||
|
|
||||||
## Deploy Perplexica backend to Google GKE
|
|
||||||
|
|
||||||
0: Install `docker` and `terraform` (Process specific to your system)
|
|
||||||
1a: Copy the `sample.env` file to `.env`
|
|
||||||
1b: Copy the `deploy/gcp/sample.env` file to `deploy/gcp/.env`
|
|
||||||
2a: Fillout desired LLM provider access keys etc. in `.env`
|
|
||||||
|
|
||||||
- Note: you will have to comeback and edit this file again once you have the address of the K8s backend deploy
|
|
||||||
2b: Fillout the GCP info in `deploy/gcp/.env`
|
|
||||||
3: Edit `GCP_REPO` to the correct docker image repo path if you are using something other than Container registry
|
|
||||||
4: Edit the `PREFIX` if you would like images and GKE entities to be prefixed with something else
|
|
||||||
5: In `deploy/gcp` run `make init` to initialize terraform
|
|
||||||
6: Follow the normal Preplexica configuration steps outlined in the project readme
|
|
||||||
7: Auth docker with the appropriate credential for repo Ex. for `gcr.io` -> `gcloud auth configure-docker`
|
|
||||||
8: In `deploy/gcp` run `make build-deplpy` to build and push the project images to the repo, create a GKE cluster and deploy the app
|
|
||||||
9: Once deployed successfully edit the `.env` file in the root project folder and update the `REMOTE_BACKEND_ADDRESS` with the remote k8s deployment address and port
|
|
||||||
10: In root project folder run `make rebuild-run-app-only`
|
|
||||||
|
|
||||||
If you configured everything correctly frontend app will run locally and provide you with a local url to open it.
|
|
||||||
Now you can run queries against the remotely deployed backend from your local machine. :celebrate:
|
|
||||||
|
|
||||||
## Upcoming Features
|
## Upcoming Features
|
||||||
|
|
||||||
- [ ] Finalizing Copilot Mode
|
- [ ] Finalizing Copilot Mode
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
services:
|
|
||||||
perplexica-frontend:
|
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
dockerfile: app.dockerfile
|
|
||||||
args:
|
|
||||||
- NEXT_PUBLIC_SUPER_SECRET_KEY=${SUPER_SECRET_KEY}
|
|
||||||
- NEXT_PUBLIC_API_URL=https://${REMOTE_BACKEND_ADDRESS}/api
|
|
||||||
- NEXT_PUBLIC_WS_URL=wss://${REMOTE_BACKEND_ADDRESS}
|
|
||||||
expose:
|
|
||||||
- 3000
|
|
||||||
ports:
|
|
||||||
- 3000:3000
|
|
||||||
@@ -2,11 +2,8 @@ FROM node:alpine
|
|||||||
|
|
||||||
ARG NEXT_PUBLIC_WS_URL
|
ARG NEXT_PUBLIC_WS_URL
|
||||||
ARG NEXT_PUBLIC_API_URL
|
ARG NEXT_PUBLIC_API_URL
|
||||||
ARG NEXT_PUBLIC_SUPER_SECRET_KEY
|
|
||||||
|
|
||||||
ENV NEXT_PUBLIC_WS_URL=${NEXT_PUBLIC_WS_URL}
|
ENV NEXT_PUBLIC_WS_URL=${NEXT_PUBLIC_WS_URL}
|
||||||
ENV NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}
|
ENV NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}
|
||||||
ENV NEXT_PUBLIC_SUPER_SECRET_KEY=${NEXT_PUBLIC_SUPER_SECRET_KEY}
|
|
||||||
|
|
||||||
WORKDIR /home/perplexica
|
WORKDIR /home/perplexica
|
||||||
|
|
||||||
|
|||||||
6
deploy/gcp/.gitignore
vendored
6
deploy/gcp/.gitignore
vendored
@@ -1,6 +0,0 @@
|
|||||||
.env
|
|
||||||
.auto.tfvars
|
|
||||||
.terraform
|
|
||||||
terraform.tfstate
|
|
||||||
terraform.tfstate.*
|
|
||||||
.terraform.lock.hcl
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
# Adds all the deployment relevant sensitive information about project
|
|
||||||
include .env
|
|
||||||
|
|
||||||
# Adds secrets/ keys we have define for the project locally and deployment
|
|
||||||
include ../../.env
|
|
||||||
|
|
||||||
# Use `location-id-docker.pkg` for artifact registry Ex. west-1-docker.pkg
|
|
||||||
GCP_REPO=gcr.io
|
|
||||||
PREFIX=perplexica
|
|
||||||
SEARCH_PORT=8080
|
|
||||||
BACKEND_PORT=3001
|
|
||||||
SEARCH_IMAGE_TAG=$(GCP_REPO)/$(GCP_PROJECT_ID)/$(PREFIX)-searxng:latest
|
|
||||||
BACKEND_IMAGE_TAG=$(GCP_REPO)/$(GCP_PROJECT_ID)/$(PREFIX)-backend:latest
|
|
||||||
APP_IMAGE_TAG=$(GCP_REPO)/$(GCP_PROJECT_ID)/$(PREFIX)-app:latest
|
|
||||||
CLUSTER_NAME=$(PREFIX)-cluster
|
|
||||||
|
|
||||||
|
|
||||||
.PHONY: build-deploy
|
|
||||||
build-deploy: docker-build-all deploy
|
|
||||||
|
|
||||||
|
|
||||||
.PHONY: docker-build-all
|
|
||||||
docker-build-all: docker-build-push-searxng docker-build-push-backend docker-build-push-app
|
|
||||||
|
|
||||||
|
|
||||||
.PHONY: show_config
|
|
||||||
show_config:
|
|
||||||
@echo $(GCP_PROJECT_ID) \
|
|
||||||
&& echo $(CLUSTER_NAME) \
|
|
||||||
&& echo $(GCP_REGION) \
|
|
||||||
&& echo $(GCP_SERVICE_ACCOUNT_KEY_FILE) \
|
|
||||||
&& echo $(SEARCH_IMAGE_TAG) \
|
|
||||||
&& echo $(BACKEND_IMAGE_TAG) \
|
|
||||||
&& echo $(APP_IMAGE_TAG) \
|
|
||||||
&& echo $(SEARCH_PORT) \
|
|
||||||
&& echo $(BACKEND_PORT) \
|
|
||||||
&& echo $(OPENAI) \
|
|
||||||
&& echo $(SUPER_SECRET_KEY)
|
|
||||||
|
|
||||||
.PHONY: docker-build-push-searxng
|
|
||||||
docker-build-push-searxng:
|
|
||||||
cd ../../ && docker build -f ./deploy/gcp/searxng.dockerfile -t $(SEARCH_IMAGE_TAG) . --platform="linux/amd64"
|
|
||||||
docker push $(SEARCH_IMAGE_TAG)
|
|
||||||
|
|
||||||
|
|
||||||
.PHONY: docker-build-push-backend
|
|
||||||
docker-build-push-backend:
|
|
||||||
cd ../../ && docker build -f ./backend.dockerfile -t $(BACKEND_IMAGE_TAG) . --platform="linux/amd64"
|
|
||||||
docker push $(BACKEND_IMAGE_TAG)
|
|
||||||
|
|
||||||
|
|
||||||
.PHONY: docker-build-push-app
|
|
||||||
docker-build-push-app:
|
|
||||||
#
|
|
||||||
# cd ../../ && docker build -f ./app.dockerfile -t $(APP_IMAGE_TAG) . --platform="linux/amd64"
|
|
||||||
# docker push $(APP_IMAGE_TAG)
|
|
||||||
|
|
||||||
|
|
||||||
.PHONY: init
|
|
||||||
init:
|
|
||||||
terraform init
|
|
||||||
|
|
||||||
|
|
||||||
.PHONY: deploy
|
|
||||||
deploy:
|
|
||||||
export TF_VAR_project_id=$(GCP_PROJECT_ID) \
|
|
||||||
&& export TF_VAR_cluster_name=$(CLUSTER_NAME) \
|
|
||||||
&& export TF_VAR_region=$(GCP_REGION) \
|
|
||||||
&& export TF_VAR_key_file=$(GCP_SERVICE_ACCOUNT_KEY_FILE) \
|
|
||||||
&& export TF_VAR_search_image=$(SEARCH_IMAGE_TAG) \
|
|
||||||
&& export TF_VAR_backend_image=$(BACKEND_IMAGE_TAG) \
|
|
||||||
&& export TF_VAR_app_image=$(APP_IMAGE_TAG) \
|
|
||||||
&& export TF_VAR_search_port=$(SEARCH_PORT) \
|
|
||||||
&& export TF_VAR_backend_port=$(BACKEND_PORT) \
|
|
||||||
&& export TF_VAR_open_ai=$(OPENAI) \
|
|
||||||
&& export TF_VAR_secret_key=$(SUPER_SECRET_KEY) \
|
|
||||||
&& terraform apply
|
|
||||||
|
|
||||||
|
|
||||||
.PHONY: teardown
|
|
||||||
teardown:
|
|
||||||
export TF_VAR_project_id=$(GCP_PROJECT_ID) \
|
|
||||||
&& export TF_VAR_cluster_name=$(CLUSTER_NAME) \
|
|
||||||
&& export TF_VAR_region=$(GCP_REGION) \
|
|
||||||
&& export TF_VAR_key_file=$(GCP_SERVICE_ACCOUNT_KEY_FILE) \
|
|
||||||
&& export TF_VAR_search_image=$(SEARCH_IMAGE_TAG) \
|
|
||||||
&& export TF_VAR_backend_image=$(BACKEND_IMAGE_TAG) \
|
|
||||||
&& export TF_VAR_app_image=$(APP_IMAGE_TAG) \
|
|
||||||
&& export TF_VAR_search_port=$(SEARCH_PORT) \
|
|
||||||
&& export TF_VAR_backend_port=$(BACKEND_PORT) \
|
|
||||||
&& export TF_VAR_open_ai=$(OPENAI) \
|
|
||||||
&& export TF_VAR_secret_key=$(SUPER_SECRET_KEY) \
|
|
||||||
&& terraform destroy
|
|
||||||
|
|
||||||
|
|
||||||
.PHONY: auth-kubectl
|
|
||||||
auth-kubectl:
|
|
||||||
gcloud container clusters get-credentials $(CLUSTER_NAME) --region=$(GCP_REGION)
|
|
||||||
|
|
||||||
|
|
||||||
.PHONY: rollout-new-version-backend
|
|
||||||
rollout-new-version-backend: auth-kubectl
|
|
||||||
kubectl rollout restart deploy backend
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
terraform {
|
|
||||||
required_providers {
|
|
||||||
google = {
|
|
||||||
source = "hashicorp/google"
|
|
||||||
version = "5.28.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "project_id" {
|
|
||||||
description = "The ID of the project in which resources will be deployed."
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "name" {
|
|
||||||
description = "The GKE Cluster name"
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "region" {
|
|
||||||
description = "The GCP region to deploy to."
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "key_file" {
|
|
||||||
description = "The path to the GCP service account key file."
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
provider "google" {
|
|
||||||
credentials = file(var.key_file)
|
|
||||||
project = var.project_id
|
|
||||||
region = var.region
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "google_container_cluster" "cluster" {
|
|
||||||
name = var.name
|
|
||||||
location = var.region
|
|
||||||
initial_node_count = 1
|
|
||||||
remove_default_node_pool = true
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "google_container_node_pool" "primary_preemptible_nodes" {
|
|
||||||
name = "${google_container_cluster.cluster.name}-node-pool"
|
|
||||||
location = var.region
|
|
||||||
cluster = google_container_cluster.cluster.name
|
|
||||||
node_count = 1
|
|
||||||
|
|
||||||
node_config {
|
|
||||||
machine_type = "n1-standard-4"
|
|
||||||
disk_size_gb = 25
|
|
||||||
spot = true
|
|
||||||
oauth_scopes = [
|
|
||||||
"https://www.googleapis.com/auth/cloud-platform",
|
|
||||||
"https://www.googleapis.com/auth/devstorage.read_only",
|
|
||||||
"https://www.googleapis.com/auth/logging.write",
|
|
||||||
"https://www.googleapis.com/auth/monitoring",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,238 +0,0 @@
|
|||||||
terraform {
|
|
||||||
required_providers {
|
|
||||||
google = {
|
|
||||||
source = "hashicorp/google"
|
|
||||||
version = "5.28.0"
|
|
||||||
}
|
|
||||||
kubernetes = {
|
|
||||||
source = "hashicorp/kubernetes"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
provider "google" {
|
|
||||||
credentials = file(var.key_file)
|
|
||||||
project = var.project_id
|
|
||||||
region = var.region
|
|
||||||
}
|
|
||||||
|
|
||||||
data "google_client_config" "default" {
|
|
||||||
depends_on = [module.gke-cluster]
|
|
||||||
}
|
|
||||||
|
|
||||||
# Defer reading the cluster data until the GKE cluster exists.
|
|
||||||
data "google_container_cluster" "default" {
|
|
||||||
name = var.cluster_name
|
|
||||||
depends_on = [module.gke-cluster]
|
|
||||||
location = var.region
|
|
||||||
}
|
|
||||||
|
|
||||||
provider "kubernetes" {
|
|
||||||
host = "https://${data.google_container_cluster.default.endpoint}"
|
|
||||||
token = data.google_client_config.default.access_token
|
|
||||||
cluster_ca_certificate = base64decode(
|
|
||||||
data.google_container_cluster.default.master_auth[0].cluster_ca_certificate,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#####################################################################################################
|
|
||||||
# SearXNG - Search engine deployment and service
|
|
||||||
#####################################################################################################
|
|
||||||
resource "kubernetes_deployment" "searxng" {
|
|
||||||
metadata {
|
|
||||||
name = "searxng"
|
|
||||||
labels = {
|
|
||||||
app = "searxng"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
spec {
|
|
||||||
replicas = 1
|
|
||||||
selector {
|
|
||||||
match_labels = {
|
|
||||||
component = "searxng"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
template {
|
|
||||||
metadata {
|
|
||||||
labels = {
|
|
||||||
component = "searxng"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
spec {
|
|
||||||
container {
|
|
||||||
image = var.search_image
|
|
||||||
name = "searxng-container"
|
|
||||||
port {
|
|
||||||
container_port = var.search_port
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "kubernetes_service" "searxng_service" {
|
|
||||||
metadata {
|
|
||||||
name = "searxng-service"
|
|
||||||
namespace = "default"
|
|
||||||
annotations = {
|
|
||||||
"networking.gke.io/load-balancer-type" = "Internal" # Remove to create an external loadbalancer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
spec {
|
|
||||||
selector = {
|
|
||||||
component = "searxng"
|
|
||||||
}
|
|
||||||
|
|
||||||
port {
|
|
||||||
port = var.search_port
|
|
||||||
target_port = var.search_port
|
|
||||||
}
|
|
||||||
|
|
||||||
type = "LoadBalancer"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#####################################################################################################
|
|
||||||
# Perplexica - backend deployment and service
|
|
||||||
#####################################################################################################
|
|
||||||
resource "kubernetes_deployment" "backend" {
|
|
||||||
metadata {
|
|
||||||
name = "backend"
|
|
||||||
labels = {
|
|
||||||
app = "backend"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
spec {
|
|
||||||
replicas = 1
|
|
||||||
selector {
|
|
||||||
match_labels = {
|
|
||||||
component = "backend"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
template {
|
|
||||||
metadata {
|
|
||||||
labels = {
|
|
||||||
component = "backend"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
spec {
|
|
||||||
container {
|
|
||||||
image = var.backend_image
|
|
||||||
name = "backend-container"
|
|
||||||
port {
|
|
||||||
container_port = var.backend_port
|
|
||||||
}
|
|
||||||
env {
|
|
||||||
# searxng service ip
|
|
||||||
name = "SEARXNG_API_URL"
|
|
||||||
value = "http://${kubernetes_service.searxng_service.status[0].load_balancer[0].ingress[0].ip}:${var.search_port}"
|
|
||||||
}
|
|
||||||
env {
|
|
||||||
# openai key
|
|
||||||
name = "OPENAI"
|
|
||||||
value = var.open_ai
|
|
||||||
}
|
|
||||||
env {
|
|
||||||
# port
|
|
||||||
name = "PORT"
|
|
||||||
value = var.backend_port
|
|
||||||
}
|
|
||||||
env {
|
|
||||||
# Access key for backend
|
|
||||||
name = "SUPER_SECRET_KEY"
|
|
||||||
value = var.secret_key
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "kubernetes_service" "backend_service" {
|
|
||||||
metadata {
|
|
||||||
name = "backend-service"
|
|
||||||
namespace = "default"
|
|
||||||
}
|
|
||||||
|
|
||||||
spec {
|
|
||||||
selector = {
|
|
||||||
component = "backend"
|
|
||||||
}
|
|
||||||
|
|
||||||
port {
|
|
||||||
port = var.backend_port
|
|
||||||
target_port = var.backend_port
|
|
||||||
}
|
|
||||||
|
|
||||||
type = "LoadBalancer"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#####################################################################################################
|
|
||||||
# Variable and module definitions
|
|
||||||
#####################################################################################################
|
|
||||||
variable "project_id" {
|
|
||||||
description = "The ID of the project in which the resources will be deployed."
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "key_file" {
|
|
||||||
description = "The path to the GCP service account key file."
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "region" {
|
|
||||||
description = "The GCP region to deploy to."
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "cluster_name" {
|
|
||||||
description = "The GCP region to deploy to."
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "search_image" {
|
|
||||||
description = "Tag for the searxng image"
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "backend_image" {
|
|
||||||
description = "Tag for the Perplexica backend image"
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "app_image" {
|
|
||||||
description = "Tag for the app image"
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "open_ai" {
|
|
||||||
description = "OPENAI access key"
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "secret_key" {
|
|
||||||
description = "Access key to secure backend endpoints"
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "search_port" {
|
|
||||||
description = "Port for searxng service"
|
|
||||||
type = number
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "backend_port" {
|
|
||||||
description = "Port for backend service"
|
|
||||||
type = number
|
|
||||||
}
|
|
||||||
|
|
||||||
module "gke-cluster" {
|
|
||||||
source = "./gke-cluster"
|
|
||||||
|
|
||||||
project_id = var.project_id
|
|
||||||
name = var.cluster_name
|
|
||||||
region = var.region
|
|
||||||
key_file = var.key_file
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
# Rename this file to .env
|
|
||||||
# 0: Update to your GCP project id
|
|
||||||
# 1: Update to the path where the GCP service account credential file is kept
|
|
||||||
# 2: Update the region to your desired GCP region
|
|
||||||
GCP_PROJECT_ID=name-of-your-gcp-project
|
|
||||||
GCP_SERVICE_ACCOUNT_KEY_FILE=/Path/to/your/gcp-service-account-key-file.json
|
|
||||||
GCP_REGION=us-east1
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
FROM searxng/searxng
|
|
||||||
|
|
||||||
COPY searxng/ /etc/searxng/
|
|
||||||
@@ -14,22 +14,13 @@ services:
|
|||||||
context: .
|
context: .
|
||||||
dockerfile: backend.dockerfile
|
dockerfile: backend.dockerfile
|
||||||
args:
|
args:
|
||||||
- SEARXNG_API_URL=null
|
- SEARXNG_API_URL=http://searxng:8080
|
||||||
volumes:
|
|
||||||
- "/Volumes/keys/headllamp/keys/:/var/keys/"
|
|
||||||
- "${GOOGLE_APPLICATION_CREDENTIALS}:/var/keys/gcp_service_account.json"
|
|
||||||
environment:
|
|
||||||
SEARXNG_API_URL: 'http://searxng:8080'
|
|
||||||
SUPER_SECRET_KEY: ${SUPER_SECRET_KEY}
|
|
||||||
OPENAI: ${OPENAI}
|
|
||||||
GROQ: ${GROQ}
|
|
||||||
OLLAMA_API_URL: ${OLLAMA_API_URL}
|
|
||||||
GOOGLE_APPLICATION_CREDENTIALS: /var/keys/gcp_service_account.json
|
|
||||||
USE_JWT: ${USE_JWT}
|
|
||||||
depends_on:
|
depends_on:
|
||||||
- searxng
|
- searxng
|
||||||
ports:
|
ports:
|
||||||
- 3001:3001
|
- 3001:3001
|
||||||
|
extra_hosts:
|
||||||
|
- "host.docker.internal:host-gateway"
|
||||||
networks:
|
networks:
|
||||||
- perplexica-network
|
- perplexica-network
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
@@ -39,7 +30,6 @@ services:
|
|||||||
context: .
|
context: .
|
||||||
dockerfile: app.dockerfile
|
dockerfile: app.dockerfile
|
||||||
args:
|
args:
|
||||||
- NEXT_PUBLIC_SUPER_SECRET_KEY=${SUPER_SECRET_KEY}
|
|
||||||
- NEXT_PUBLIC_API_URL=http://127.0.0.1:3001/api
|
- NEXT_PUBLIC_API_URL=http://127.0.0.1:3001/api
|
||||||
- NEXT_PUBLIC_WS_URL=ws://127.0.0.1:3001
|
- NEXT_PUBLIC_WS_URL=ws://127.0.0.1:3001
|
||||||
depends_on:
|
depends_on:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "perplexica-backend",
|
"name": "perplexica-backend",
|
||||||
"version": "1.5.0",
|
"version": "1.6.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"author": "ItzCrazyKns",
|
"author": "ItzCrazyKns",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -21,7 +21,6 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iarna/toml": "^2.2.5",
|
"@iarna/toml": "^2.2.5",
|
||||||
"@langchain/google-vertexai": "^0.0.16",
|
|
||||||
"@langchain/openai": "^0.0.25",
|
"@langchain/openai": "^0.0.25",
|
||||||
"@xenova/transformers": "^2.17.1",
|
"@xenova/transformers": "^2.17.1",
|
||||||
"axios": "^1.6.8",
|
"axios": "^1.6.8",
|
||||||
|
|||||||
24
sample.env
24
sample.env
@@ -1,24 +0,0 @@
|
|||||||
# Copy this file over to .env and fill in the desired config.
|
|
||||||
# .env will become available to docker compose and these values will be
|
|
||||||
# used when running docker compose up
|
|
||||||
|
|
||||||
# Edit to set OpenAI access key
|
|
||||||
OPENAI=ADD OPENAI KEY HERE
|
|
||||||
|
|
||||||
# Uncomment and edit to set GROQ access key
|
|
||||||
# GROQ: ${GROQ}
|
|
||||||
|
|
||||||
# Uncomment and edit to set OLLAMA Url
|
|
||||||
# OLLAMA_API_URL: ${OLLAMA_API_URL}
|
|
||||||
|
|
||||||
# Address and port of the remotely deployed Perplexica backend
|
|
||||||
REMOTE_BACKEND_ADDRESS=111.111.111.111:0000
|
|
||||||
|
|
||||||
# Uncomment and edit to configure backend to reject requests without token
|
|
||||||
# leave commented to have open access to all endpoints
|
|
||||||
# Secret key to "secure" backend
|
|
||||||
# SUPER_SECRET_KEY=THISISASUPERSECRETKEYSERIOUSLY
|
|
||||||
|
|
||||||
# Uncomment and edit to configure a specific service account key file to use to
|
|
||||||
# auth with VertexAI when running (backend) full Perplexica stack locally
|
|
||||||
# GOOGLE_APPLICATION_CREDENTIALS=/absolute/path/to/gcp-service-account-key-file.json
|
|
||||||
@@ -209,6 +209,7 @@ const createBasicAcademicSearchAnsweringChain = (
|
|||||||
ChatPromptTemplate.fromMessages([
|
ChatPromptTemplate.fromMessages([
|
||||||
['system', basicAcademicSearchResponsePrompt],
|
['system', basicAcademicSearchResponsePrompt],
|
||||||
new MessagesPlaceholder('chat_history'),
|
new MessagesPlaceholder('chat_history'),
|
||||||
|
['user', '{query}'],
|
||||||
]),
|
]),
|
||||||
llm,
|
llm,
|
||||||
strParser,
|
strParser,
|
||||||
|
|||||||
@@ -205,6 +205,7 @@ const createBasicRedditSearchAnsweringChain = (
|
|||||||
ChatPromptTemplate.fromMessages([
|
ChatPromptTemplate.fromMessages([
|
||||||
['system', basicRedditSearchResponsePrompt],
|
['system', basicRedditSearchResponsePrompt],
|
||||||
new MessagesPlaceholder('chat_history'),
|
new MessagesPlaceholder('chat_history'),
|
||||||
|
['user', '{query}'],
|
||||||
]),
|
]),
|
||||||
llm,
|
llm,
|
||||||
strParser,
|
strParser,
|
||||||
|
|||||||
@@ -203,6 +203,7 @@ const createBasicWebSearchAnsweringChain = (
|
|||||||
ChatPromptTemplate.fromMessages([
|
ChatPromptTemplate.fromMessages([
|
||||||
['system', basicWebSearchResponsePrompt],
|
['system', basicWebSearchResponsePrompt],
|
||||||
new MessagesPlaceholder('chat_history'),
|
new MessagesPlaceholder('chat_history'),
|
||||||
|
['user', '{query}'],
|
||||||
]),
|
]),
|
||||||
llm,
|
llm,
|
||||||
strParser,
|
strParser,
|
||||||
|
|||||||
@@ -165,6 +165,7 @@ const createBasicWolframAlphaSearchAnsweringChain = (llm: BaseChatModel) => {
|
|||||||
ChatPromptTemplate.fromMessages([
|
ChatPromptTemplate.fromMessages([
|
||||||
['system', basicWolframAlphaSearchResponsePrompt],
|
['system', basicWolframAlphaSearchResponsePrompt],
|
||||||
new MessagesPlaceholder('chat_history'),
|
new MessagesPlaceholder('chat_history'),
|
||||||
|
['user', '{query}'],
|
||||||
]),
|
]),
|
||||||
llm,
|
llm,
|
||||||
strParser,
|
strParser,
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ const createWritingAssistantChain = (llm: BaseChatModel) => {
|
|||||||
ChatPromptTemplate.fromMessages([
|
ChatPromptTemplate.fromMessages([
|
||||||
['system', writingAssistantPrompt],
|
['system', writingAssistantPrompt],
|
||||||
new MessagesPlaceholder('chat_history'),
|
new MessagesPlaceholder('chat_history'),
|
||||||
|
['user', '{query}'],
|
||||||
]),
|
]),
|
||||||
llm,
|
llm,
|
||||||
strParser,
|
strParser,
|
||||||
|
|||||||
@@ -205,6 +205,7 @@ const createBasicYoutubeSearchAnsweringChain = (
|
|||||||
ChatPromptTemplate.fromMessages([
|
ChatPromptTemplate.fromMessages([
|
||||||
['system', basicYoutubeSearchResponsePrompt],
|
['system', basicYoutubeSearchResponsePrompt],
|
||||||
new MessagesPlaceholder('chat_history'),
|
new MessagesPlaceholder('chat_history'),
|
||||||
|
['user', '{query}'],
|
||||||
]),
|
]),
|
||||||
llm,
|
llm,
|
||||||
strParser,
|
strParser,
|
||||||
|
|||||||
13
src/app.ts
13
src/app.ts
@@ -3,8 +3,7 @@ import express from 'express';
|
|||||||
import cors from 'cors';
|
import cors from 'cors';
|
||||||
import http from 'http';
|
import http from 'http';
|
||||||
import routes from './routes';
|
import routes from './routes';
|
||||||
import { requireAccessKey } from './auth';
|
import { getPort } from './config';
|
||||||
import { getAccessKey, getPort } from './config';
|
|
||||||
import logger from './utils/logger';
|
import logger from './utils/logger';
|
||||||
|
|
||||||
const port = getPort();
|
const port = getPort();
|
||||||
@@ -14,21 +13,11 @@ const server = http.createServer(app);
|
|||||||
|
|
||||||
const corsOptions = {
|
const corsOptions = {
|
||||||
origin: '*',
|
origin: '*',
|
||||||
allowedHeaders: ['Authorization', 'Content-Type'],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
app.use(cors(corsOptions));
|
app.use(cors(corsOptions));
|
||||||
|
|
||||||
if (getAccessKey()) {
|
|
||||||
app.all('/api/*', requireAccessKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
|
|
||||||
app.get('/', (_, res) => {
|
|
||||||
res.status(200).json({ status: 'ok' });
|
|
||||||
});
|
|
||||||
|
|
||||||
app.use('/api', routes);
|
app.use('/api', routes);
|
||||||
app.get('/api', (_, res) => {
|
app.get('/api', (_, res) => {
|
||||||
res.status(200).json({ status: 'ok' });
|
res.status(200).json({ status: 'ok' });
|
||||||
|
|||||||
29
src/auth.ts
29
src/auth.ts
@@ -1,29 +0,0 @@
|
|||||||
import { auth } from 'google-auth-library';
|
|
||||||
import { getAccessKey } from './config';
|
|
||||||
|
|
||||||
export const requireAccessKey = (req, res, next) => {
|
|
||||||
const authHeader = req.headers.authorization;
|
|
||||||
|
|
||||||
if (authHeader) {
|
|
||||||
if (!checkAccessKey(authHeader)) {
|
|
||||||
return res.sendStatus(403);
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
} else {
|
|
||||||
res.sendStatus(401);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const checkAccessKey = (authHeader) => {
|
|
||||||
const token = authHeader.split(' ')[1];
|
|
||||||
return Boolean(authHeader && token === getAccessKey());
|
|
||||||
};
|
|
||||||
|
|
||||||
export const hasGCPCredentials = async () => {
|
|
||||||
try {
|
|
||||||
const credentials = await auth.getCredentials();
|
|
||||||
return Object.keys(credentials).length > 0;
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -8,7 +8,6 @@ interface Config {
|
|||||||
GENERAL: {
|
GENERAL: {
|
||||||
PORT: number;
|
PORT: number;
|
||||||
SIMILARITY_MEASURE: string;
|
SIMILARITY_MEASURE: string;
|
||||||
SUPER_SECRET_KEY: string;
|
|
||||||
};
|
};
|
||||||
API_KEYS: {
|
API_KEYS: {
|
||||||
OPENAI: string;
|
OPENAI: string;
|
||||||
@@ -29,43 +28,18 @@ const loadConfig = () =>
|
|||||||
fs.readFileSync(path.join(__dirname, `../${configFileName}`), 'utf-8'),
|
fs.readFileSync(path.join(__dirname, `../${configFileName}`), 'utf-8'),
|
||||||
) as any as Config;
|
) as any as Config;
|
||||||
|
|
||||||
const loadEnv = () => {
|
|
||||||
return {
|
|
||||||
GENERAL: {
|
|
||||||
PORT: Number(process.env.PORT),
|
|
||||||
SIMILARITY_MEASURE: process.env.SIMILARITY_MEASURE,
|
|
||||||
SUPER_SECRET_KEY: process.env.SUPER_SECRET_KEY,
|
|
||||||
},
|
|
||||||
API_KEYS: {
|
|
||||||
OPENAI: process.env.OPENAI,
|
|
||||||
GROQ: process.env.GROQ,
|
|
||||||
},
|
|
||||||
API_ENDPOINTS: {
|
|
||||||
SEARXNG: process.env.SEARXNG_API_URL,
|
|
||||||
OLLAMA: process.env.OLLAMA_API_URL,
|
|
||||||
},
|
|
||||||
} as Config;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getPort = () => loadConfig().GENERAL.PORT;
|
export const getPort = () => loadConfig().GENERAL.PORT;
|
||||||
|
|
||||||
export const getAccessKey = () =>
|
|
||||||
loadEnv().GENERAL.SUPER_SECRET_KEY || loadConfig().GENERAL.SUPER_SECRET_KEY;
|
|
||||||
|
|
||||||
export const getSimilarityMeasure = () =>
|
export const getSimilarityMeasure = () =>
|
||||||
loadConfig().GENERAL.SIMILARITY_MEASURE;
|
loadConfig().GENERAL.SIMILARITY_MEASURE;
|
||||||
|
|
||||||
export const getOpenaiApiKey = () =>
|
export const getOpenaiApiKey = () => loadConfig().API_KEYS.OPENAI;
|
||||||
loadEnv().API_KEYS.OPENAI || loadConfig().API_KEYS.OPENAI;
|
|
||||||
|
|
||||||
export const getGroqApiKey = () =>
|
export const getGroqApiKey = () => loadConfig().API_KEYS.GROQ;
|
||||||
loadEnv().API_KEYS.GROQ || loadConfig().API_KEYS.GROQ;
|
|
||||||
|
|
||||||
export const getSearxngApiEndpoint = () =>
|
export const getSearxngApiEndpoint = () => loadConfig().API_ENDPOINTS.SEARXNG;
|
||||||
loadEnv().API_ENDPOINTS.SEARXNG || loadConfig().API_ENDPOINTS.SEARXNG;
|
|
||||||
|
|
||||||
export const getOllamaApiEndpoint = () =>
|
export const getOllamaApiEndpoint = () => loadConfig().API_ENDPOINTS.OLLAMA;
|
||||||
loadEnv().API_ENDPOINTS.OLLAMA || loadConfig().API_ENDPOINTS.OLLAMA;
|
|
||||||
|
|
||||||
export const updateConfig = (config: RecursivePartial<Config>) => {
|
export const updateConfig = (config: RecursivePartial<Config>) => {
|
||||||
const currentConfig = loadConfig();
|
const currentConfig = loadConfig();
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
import { ChatOpenAI, OpenAIEmbeddings } from '@langchain/openai';
|
import { ChatOpenAI, OpenAIEmbeddings } from '@langchain/openai';
|
||||||
import { ChatOllama } from '@langchain/community/chat_models/ollama';
|
import { ChatOllama } from '@langchain/community/chat_models/ollama';
|
||||||
import { VertexAI } from "@langchain/google-vertexai";
|
|
||||||
import { GoogleVertexAIEmbeddings } from "@langchain/community/embeddings/googlevertexai";
|
|
||||||
import { OllamaEmbeddings } from '@langchain/community/embeddings/ollama';
|
import { OllamaEmbeddings } from '@langchain/community/embeddings/ollama';
|
||||||
import { HuggingFaceTransformersEmbeddings } from './huggingfaceTransformer';
|
import { HuggingFaceTransformersEmbeddings } from './huggingfaceTransformer';
|
||||||
import { hasGCPCredentials } from '../auth';
|
|
||||||
import {
|
import {
|
||||||
getGroqApiKey,
|
getGroqApiKey,
|
||||||
getOllamaApiEndpoint,
|
getOllamaApiEndpoint,
|
||||||
@@ -120,23 +117,6 @@ export const getAvailableChatModelProviders = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await hasGCPCredentials()) {
|
|
||||||
try {
|
|
||||||
models['vertexai'] = {
|
|
||||||
'gemini-1.5-pro (preview-0409)': new VertexAI({
|
|
||||||
temperature: 0.7,
|
|
||||||
modelName: 'gemini-1.5-pro-preview-0409',
|
|
||||||
}),
|
|
||||||
'gemini-1.0-pro (Latest)': new VertexAI({
|
|
||||||
temperature: 0.7,
|
|
||||||
modelName: 'gemini-1.0-pro',
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
} catch (err) {
|
|
||||||
logger.error(`Error loading VertexAI models: ${err}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
models['custom_openai'] = {};
|
models['custom_openai'] = {};
|
||||||
|
|
||||||
return models;
|
return models;
|
||||||
@@ -187,16 +167,6 @@ export const getAvailableEmbeddingModelProviders = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await hasGCPCredentials()) {
|
|
||||||
try {
|
|
||||||
models['vertexai'] = {
|
|
||||||
'Text Gecko default': new GoogleVertexAIEmbeddings(),
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
logger.error(`Error loading VertexAI embeddings: ${err}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
models['local'] = {
|
models['local'] = {
|
||||||
'BGE Small': new HuggingFaceTransformersEmbeddings({
|
'BGE Small': new HuggingFaceTransformersEmbeddings({
|
||||||
|
|||||||
@@ -9,8 +9,6 @@ import type { Embeddings } from '@langchain/core/embeddings';
|
|||||||
import type { IncomingMessage } from 'http';
|
import type { IncomingMessage } from 'http';
|
||||||
import logger from '../utils/logger';
|
import logger from '../utils/logger';
|
||||||
import { ChatOpenAI } from '@langchain/openai';
|
import { ChatOpenAI } from '@langchain/openai';
|
||||||
import { getAccessKey } from '../config';
|
|
||||||
import { checkAccessKey } from '../auth';
|
|
||||||
|
|
||||||
export const handleConnection = async (
|
export const handleConnection = async (
|
||||||
ws: WebSocket,
|
ws: WebSocket,
|
||||||
@@ -20,20 +18,6 @@ export const handleConnection = async (
|
|||||||
const searchParams = new URL(request.url, `http://${request.headers.host}`)
|
const searchParams = new URL(request.url, `http://${request.headers.host}`)
|
||||||
.searchParams;
|
.searchParams;
|
||||||
|
|
||||||
if (getAccessKey()) {
|
|
||||||
const securtyProtocolHeader = request.headers['sec-websocket-protocol'];
|
|
||||||
if (!checkAccessKey(securtyProtocolHeader)) {
|
|
||||||
ws.send(
|
|
||||||
JSON.stringify({
|
|
||||||
type: 'error',
|
|
||||||
data: 'Incorrect or missing authentication token.',
|
|
||||||
key: 'FAILED_AUTHORIZATION',
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
ws.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const [chatModelProviders, embeddingModelProviders] = await Promise.all([
|
const [chatModelProviders, embeddingModelProviders] = await Promise.all([
|
||||||
getAvailableChatModelProviders(),
|
getAvailableChatModelProviders(),
|
||||||
getAvailableEmbeddingModelProviders(),
|
getAvailableEmbeddingModelProviders(),
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import './globals.css';
|
|||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import Sidebar from '@/components/Sidebar';
|
import Sidebar from '@/components/Sidebar';
|
||||||
import { Toaster } from 'sonner';
|
import { Toaster } from 'sonner';
|
||||||
|
import ThemeProvider from '@/components/theme/Provider';
|
||||||
|
|
||||||
const montserrat = Montserrat({
|
const montserrat = Montserrat({
|
||||||
weight: ['300', '400', '500', '700'],
|
weight: ['300', '400', '500', '700'],
|
||||||
@@ -24,18 +25,20 @@ export default function RootLayout({
|
|||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}>) {
|
}>) {
|
||||||
return (
|
return (
|
||||||
<html className="h-full" lang="en">
|
<html className="h-full" lang="en" suppressHydrationWarning>
|
||||||
<body className={cn('h-full', montserrat.className)}>
|
<body className={cn('h-full', montserrat.className)}>
|
||||||
|
<ThemeProvider>
|
||||||
<Sidebar>{children}</Sidebar>
|
<Sidebar>{children}</Sidebar>
|
||||||
<Toaster
|
<Toaster
|
||||||
toastOptions={{
|
toastOptions={{
|
||||||
unstyled: true,
|
unstyled: true,
|
||||||
classNames: {
|
classNames: {
|
||||||
toast:
|
toast:
|
||||||
'bg-[#111111] text-white rounded-lg p-4 flex flex-row items-center space-x-2',
|
'bg-light-primary dark:bg-dark-primary text-white rounded-lg p-4 flex flex-row items-center space-x-2',
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</ThemeProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ const Chat = ({
|
|||||||
sendMessage={sendMessage}
|
sendMessage={sendMessage}
|
||||||
/>
|
/>
|
||||||
{!isLast && msg.role === 'assistant' && (
|
{!isLast && msg.role === 'assistant' && (
|
||||||
<div className="h-px w-full bg-[#1C1C1C]" />
|
<div className="h-px w-full bg-light-secondary dark:bg-dark-secondary" />
|
||||||
)}
|
)}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ import EmptyChat from './EmptyChat';
|
|||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { useSearchParams } from 'next/navigation';
|
import { useSearchParams } from 'next/navigation';
|
||||||
import { getSuggestions } from '@/lib/actions';
|
import { getSuggestions } from '@/lib/actions';
|
||||||
import { clientFetch } from '@/lib/utils';
|
|
||||||
import { getAccessKey } from '@/lib/config';
|
|
||||||
|
|
||||||
export type Message = {
|
export type Message = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -39,11 +37,14 @@ const useSocket = (url: string, setIsReady: (ready: boolean) => void) => {
|
|||||||
!embeddingModel ||
|
!embeddingModel ||
|
||||||
!embeddingModelProvider
|
!embeddingModelProvider
|
||||||
) {
|
) {
|
||||||
const providers = await clientFetch('/models', {
|
const providers = await fetch(
|
||||||
|
`${process.env.NEXT_PUBLIC_API_URL}/models`,
|
||||||
|
{
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
}).then(async (res) => await res.json());
|
},
|
||||||
|
).then(async (res) => await res.json());
|
||||||
|
|
||||||
const chatModelProviders = providers.chatModelProviders;
|
const chatModelProviders = providers.chatModelProviders;
|
||||||
const embeddingModelProviders = providers.embeddingModelProviders;
|
const embeddingModelProviders = providers.embeddingModelProviders;
|
||||||
@@ -99,14 +100,7 @@ const useSocket = (url: string, setIsReady: (ready: boolean) => void) => {
|
|||||||
|
|
||||||
wsURL.search = searchParams.toString();
|
wsURL.search = searchParams.toString();
|
||||||
|
|
||||||
let protocols: any[] = [];
|
const ws = new WebSocket(wsURL.toString());
|
||||||
const secretToken = getAccessKey();
|
|
||||||
|
|
||||||
if (secretToken) {
|
|
||||||
protocols = ['Authorization', `${secretToken}`];
|
|
||||||
}
|
|
||||||
|
|
||||||
const ws = new WebSocket(wsURL.toString(), protocols);
|
|
||||||
|
|
||||||
ws.onopen = () => {
|
ws.onopen = () => {
|
||||||
console.log('[DEBUG] open');
|
console.log('[DEBUG] open');
|
||||||
@@ -329,7 +323,7 @@ const ChatWindow = () => {
|
|||||||
<div className="flex flex-row items-center justify-center min-h-screen">
|
<div className="flex flex-row items-center justify-center min-h-screen">
|
||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
className="w-8 h-8 text-[#202020] animate-spin fill-[#ffffff3b]"
|
className="w-8 h-8 text-light-200 fill-light-secondary dark:text-[#202020] animate-spin dark:fill-[#ffffff3b]"
|
||||||
viewBox="0 0 100 101"
|
viewBox="0 0 100 101"
|
||||||
fill="none"
|
fill="none"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import EmptyChatMessageInput from './EmptyChatMessageInput';
|
import EmptyChatMessageInput from './EmptyChatMessageInput';
|
||||||
|
import ThemeSwitcher from './theme/Switcher';
|
||||||
|
|
||||||
const EmptyChat = ({
|
const EmptyChat = ({
|
||||||
sendMessage,
|
sendMessage,
|
||||||
@@ -10,8 +11,11 @@ const EmptyChat = ({
|
|||||||
setFocusMode: (mode: string) => void;
|
setFocusMode: (mode: string) => void;
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
|
<div className="relative">
|
||||||
|
<ThemeSwitcher size={17} className="absolute top-2 right-0 lg:hidden" />
|
||||||
|
|
||||||
<div className="flex flex-col items-center justify-center min-h-screen max-w-screen-sm mx-auto p-2 space-y-8">
|
<div className="flex flex-col items-center justify-center min-h-screen max-w-screen-sm mx-auto p-2 space-y-8">
|
||||||
<h2 className="text-white/70 text-3xl font-medium -mt-8">
|
<h2 className="text-black/70 dark:text-white/70 text-3xl font-medium -mt-8">
|
||||||
Research begins here.
|
Research begins here.
|
||||||
</h2>
|
</h2>
|
||||||
<EmptyChatMessageInput
|
<EmptyChatMessageInput
|
||||||
@@ -20,6 +24,7 @@ const EmptyChat = ({
|
|||||||
setFocusMode={setFocusMode}
|
setFocusMode={setFocusMode}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { ArrowRight } from 'lucide-react';
|
import { ArrowRight } from 'lucide-react';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import TextareaAutosize from 'react-textarea-autosize';
|
import TextareaAutosize from 'react-textarea-autosize';
|
||||||
import { CopilotToggle, Focus } from './MessageInputActions';
|
import CopilotToggle from './MessageInputActions/Copilot';
|
||||||
|
import Focus from './MessageInputActions/Focus';
|
||||||
|
|
||||||
const EmptyChatMessageInput = ({
|
const EmptyChatMessageInput = ({
|
||||||
sendMessage,
|
sendMessage,
|
||||||
@@ -31,12 +32,12 @@ const EmptyChatMessageInput = ({
|
|||||||
}}
|
}}
|
||||||
className="w-full"
|
className="w-full"
|
||||||
>
|
>
|
||||||
<div className="flex flex-col bg-[#111111] px-5 pt-5 pb-2 rounded-lg w-full border border-[#1C1C1C]">
|
<div className="flex flex-col bg-light-secondary dark:bg-dark-secondary px-5 pt-5 pb-2 rounded-lg w-full border border-light-200 dark:border-dark-200">
|
||||||
<TextareaAutosize
|
<TextareaAutosize
|
||||||
value={message}
|
value={message}
|
||||||
onChange={(e) => setMessage(e.target.value)}
|
onChange={(e) => setMessage(e.target.value)}
|
||||||
minRows={2}
|
minRows={2}
|
||||||
className="bg-transparent placeholder:text-white/50 text-sm text-white resize-none focus:outline-none w-full max-h-24 lg:max-h-36 xl:max-h-48"
|
className="bg-transparent placeholder:text-black/50 dark:placeholder:text-white/50 text-sm text-black dark:text-white resize-none focus:outline-none w-full max-h-24 lg:max-h-36 xl:max-h-48"
|
||||||
placeholder="Ask anything..."
|
placeholder="Ask anything..."
|
||||||
/>
|
/>
|
||||||
<div className="flex flex-row items-center justify-between mt-4">
|
<div className="flex flex-row items-center justify-between mt-4">
|
||||||
@@ -51,7 +52,7 @@ const EmptyChatMessageInput = ({
|
|||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
disabled={message.trim().length === 0}
|
disabled={message.trim().length === 0}
|
||||||
className="bg-[#24A0ED] text-white disabled:text-white/50 hover:bg-opacity-85 transition duration-100 disabled:bg-[#ececec21] rounded-full p-2"
|
className="bg-[#24A0ED] text-white disabled:text-black/50 dark:disabled:text-white/50 disabled:bg-[#e0e0dc] dark:disabled:bg-[#ececec21] hover:bg-opacity-85 transition duration-100 rounded-full p-2"
|
||||||
>
|
>
|
||||||
<ArrowRight className="bg-background" size={17} />
|
<ArrowRight className="bg-background" size={17} />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
const Layout = ({ children }: { children: React.ReactNode }) => {
|
const Layout = ({ children }: { children: React.ReactNode }) => {
|
||||||
return (
|
return (
|
||||||
<main className="lg:pl-20 bg-[#0A0A0A] min-h-screen">
|
<main className="lg:pl-20 bg-light-primary dark:bg-dark-primary min-h-screen">
|
||||||
<div className="max-w-screen-lg lg:mx-auto mx-4">{children}</div>
|
<div className="max-w-screen-lg lg:mx-auto mx-4">{children}</div>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ const Copy = ({
|
|||||||
setCopied(true);
|
setCopied(true);
|
||||||
setTimeout(() => setCopied(false), 1000);
|
setTimeout(() => setCopied(false), 1000);
|
||||||
}}
|
}}
|
||||||
className="p-2 text-white/70 rounded-xl hover:bg-[#1c1c1c] transition duration-200 hover:text-white"
|
className="p-2 text-black/70 dark:text-white/70 rounded-xl hover:bg-light-secondary dark:hover:bg-dark-secondary transition duration-200 hover:text-black dark:hover:text-white"
|
||||||
>
|
>
|
||||||
{copied ? <Check size={18} /> : <ClipboardList size={18} />}
|
{copied ? <Check size={18} /> : <ClipboardList size={18} />}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ const Rewrite = ({
|
|||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
onClick={() => rewrite(messageId)}
|
onClick={() => rewrite(messageId)}
|
||||||
className="py-2 px-3 text-white/70 rounded-xl hover:bg-[#1c1c1c] transition duration-200 hover:text-white flex flex-row items-center space-x-1"
|
className="py-2 px-3 text-black/70 dark:text-white/70 rounded-xl hover:bg-light-secondary dark:hover:bg-dark-secondary transition duration-200 hover:text-black dark:hover:text-white flex flex-row items-center space-x-1"
|
||||||
>
|
>
|
||||||
<ArrowLeftRight size={18} />
|
<ArrowLeftRight size={18} />
|
||||||
<p className="text-xs font-medium">Rewrite</p>
|
<p className="text-xs font-medium">Rewrite</p>
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ const MessageBox = ({
|
|||||||
message.content.replace(
|
message.content.replace(
|
||||||
regex,
|
regex,
|
||||||
(_, number) =>
|
(_, number) =>
|
||||||
`<a href="${message.sources?.[number - 1]?.metadata?.url}" target="_blank" className="bg-[#1C1C1C] px-1 rounded ml-1 no-underline text-xs text-white/70 relative">${number}</a>`,
|
`<a href="${message.sources?.[number - 1]?.metadata?.url}" target="_blank" className="bg-light-secondary dark:bg-dark-secondary px-1 rounded ml-1 no-underline text-xs text-black/70 dark:text-white/70 relative">${number}</a>`,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -70,7 +70,7 @@ const MessageBox = ({
|
|||||||
<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')}>
|
||||||
<h2 className="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>
|
||||||
</div>
|
</div>
|
||||||
@@ -85,8 +85,10 @@ const MessageBox = ({
|
|||||||
{message.sources && message.sources.length > 0 && (
|
{message.sources && message.sources.length > 0 && (
|
||||||
<div className="flex flex-col space-y-2">
|
<div className="flex flex-col space-y-2">
|
||||||
<div className="flex flex-row items-center space-x-2">
|
<div className="flex flex-row items-center space-x-2">
|
||||||
<BookCopy className="text-white" size={20} />
|
<BookCopy className="text-black dark:text-white" size={20} />
|
||||||
<h3 className="text-white font-medium text-xl">Sources</h3>
|
<h3 className="text-black dark:text-white font-medium text-xl">
|
||||||
|
Sources
|
||||||
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<MessageSources sources={message.sources} />
|
<MessageSources sources={message.sources} />
|
||||||
</div>
|
</div>
|
||||||
@@ -95,20 +97,27 @@ const MessageBox = ({
|
|||||||
<div className="flex flex-row items-center space-x-2">
|
<div className="flex flex-row items-center space-x-2">
|
||||||
<Disc3
|
<Disc3
|
||||||
className={cn(
|
className={cn(
|
||||||
'text-white',
|
'text-black dark:text-white',
|
||||||
isLast && loading ? 'animate-spin' : 'animate-none',
|
isLast && loading ? 'animate-spin' : 'animate-none',
|
||||||
)}
|
)}
|
||||||
size={20}
|
size={20}
|
||||||
/>
|
/>
|
||||||
<h3 className="text-white font-medium text-xl">Answer</h3>
|
<h3 className="text-black dark:text-white font-medium text-xl">
|
||||||
|
Answer
|
||||||
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<Markdown className="prose max-w-none break-words prose-invert prose-p:leading-relaxed prose-pre:p-0 text-white text-sm md:text-base font-medium">
|
<Markdown
|
||||||
|
className={cn(
|
||||||
|
'prose dark:prose-invert prose-p:leading-relaxed prose-pre:p-0',
|
||||||
|
'max-w-none break-words text-black dark:text-white text-sm md:text-base font-medium',
|
||||||
|
)}
|
||||||
|
>
|
||||||
{parsedMessage}
|
{parsedMessage}
|
||||||
</Markdown>
|
</Markdown>
|
||||||
{loading && isLast ? null : (
|
{loading && isLast ? null : (
|
||||||
<div className="flex flex-row items-center justify-between w-full text-white py-4 -mx-2">
|
<div className="flex flex-row items-center justify-between w-full text-black dark:text-white py-4 -mx-2">
|
||||||
<div className="flex flex-row items-center space-x-1">
|
<div className="flex flex-row items-center space-x-1">
|
||||||
{/* <button className="p-2 text-white/70 rounded-xl hover:bg-[#1c1c1c] transition duration-200 hover:text-white">
|
{/* <button className="p-2 text-black/70 dark:text-white/70 rounded-xl hover:bg-light-secondary dark:hover:bg-dark-secondary transition duration-200 hover:text-black text-black dark:hover:text-white">
|
||||||
<Share size={18} />
|
<Share size={18} />
|
||||||
</button> */}
|
</button> */}
|
||||||
<Rewrite rewrite={rewrite} messageId={message.id} />
|
<Rewrite rewrite={rewrite} messageId={message.id} />
|
||||||
@@ -123,7 +132,7 @@ const MessageBox = ({
|
|||||||
start();
|
start();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className="p-2 text-white/70 rounded-xl hover:bg-[#1c1c1c] transition duration-200 hover:text-white"
|
className="p-2 text-black/70 dark:text-white/70 rounded-xl hover:bg-light-secondary dark:hover:bg-dark-secondary transition duration-200 hover:text-black dark:hover:text-white"
|
||||||
>
|
>
|
||||||
{speechStatus === 'started' ? (
|
{speechStatus === 'started' ? (
|
||||||
<StopCircle size={18} />
|
<StopCircle size={18} />
|
||||||
@@ -140,8 +149,8 @@ const MessageBox = ({
|
|||||||
message.role === 'assistant' &&
|
message.role === 'assistant' &&
|
||||||
!loading && (
|
!loading && (
|
||||||
<>
|
<>
|
||||||
<div className="h-px w-full bg-[#1C1C1C]" />
|
<div className="h-px w-full bg-light-secondary dark:bg-dark-secondary" />
|
||||||
<div className="flex flex-col space-y-3 text-white">
|
<div className="flex flex-col space-y-3 text-black dark:text-white">
|
||||||
<div className="flex flex-row items-center space-x-2 mt-4">
|
<div className="flex flex-row items-center space-x-2 mt-4">
|
||||||
<Layers3 />
|
<Layers3 />
|
||||||
<h3 className="text-xl font-medium">Related</h3>
|
<h3 className="text-xl font-medium">Related</h3>
|
||||||
@@ -152,7 +161,7 @@ const MessageBox = ({
|
|||||||
className="flex flex-col space-y-3 text-sm"
|
className="flex flex-col space-y-3 text-sm"
|
||||||
key={i}
|
key={i}
|
||||||
>
|
>
|
||||||
<div className="h-px w-full bg-[#1C1C1C]" />
|
<div className="h-px w-full bg-light-secondary dark:bg-dark-secondary" />
|
||||||
<div
|
<div
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
sendMessage(suggestion);
|
sendMessage(suggestion);
|
||||||
@@ -162,7 +171,10 @@ const MessageBox = ({
|
|||||||
<p className="transition duration-200 hover:text-[#24A0ED]">
|
<p className="transition duration-200 hover:text-[#24A0ED]">
|
||||||
{suggestion}
|
{suggestion}
|
||||||
</p>
|
</p>
|
||||||
<Plus size={20} className="text-[#24A0ED]" />
|
<Plus
|
||||||
|
size={20}
|
||||||
|
className="text-[#24A0ED] flex-shrink-0"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
const MessageBoxLoading = () => {
|
const MessageBoxLoading = () => {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col space-y-2 w-full lg:w-9/12 bg-[#111111] animate-pulse rounded-lg p-3">
|
<div className="flex flex-col space-y-2 w-full lg:w-9/12 bg-light-primary dark:bg-dark-primary animate-pulse rounded-lg p-3">
|
||||||
<div className="h-2 rounded-full w-full bg-[#1c1c1c]" />
|
<div className="h-2 rounded-full w-full bg-light-secondary dark:bg-dark-secondary" />
|
||||||
<div className="h-2 rounded-full w-9/12 bg-[#1c1c1c]" />
|
<div className="h-2 rounded-full w-9/12 bg-light-secondary dark:bg-dark-secondary" />
|
||||||
<div className="h-2 rounded-full w-10/12 bg-[#1c1c1c]" />
|
<div className="h-2 rounded-full w-10/12 bg-light-secondary dark:bg-dark-secondary" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ import { cn } from '@/lib/utils';
|
|||||||
import { ArrowUp } from 'lucide-react';
|
import { ArrowUp } from 'lucide-react';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import TextareaAutosize from 'react-textarea-autosize';
|
import TextareaAutosize from 'react-textarea-autosize';
|
||||||
import { Attach, CopilotToggle } from './MessageInputActions';
|
import Attach from './MessageInputActions/Attach';
|
||||||
|
import CopilotToggle from './MessageInputActions/Copilot';
|
||||||
|
|
||||||
const MessageInput = ({
|
const MessageInput = ({
|
||||||
sendMessage,
|
sendMessage,
|
||||||
@@ -40,7 +41,7 @@ const MessageInput = ({
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className={cn(
|
className={cn(
|
||||||
'bg-[#111111] p-4 flex items-center overflow-hidden border border-[#1C1C1C]',
|
'bg-light-secondary dark:bg-dark-secondary p-4 flex items-center overflow-hidden border border-light-200 dark:border-dark-200',
|
||||||
mode === 'multi' ? 'flex-col rounded-lg' : 'flex-row rounded-full',
|
mode === 'multi' ? 'flex-col rounded-lg' : 'flex-row rounded-full',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -51,7 +52,7 @@ const MessageInput = ({
|
|||||||
onHeightChange={(height, props) => {
|
onHeightChange={(height, props) => {
|
||||||
setTextareaRows(Math.ceil(height / props.rowHeight));
|
setTextareaRows(Math.ceil(height / props.rowHeight));
|
||||||
}}
|
}}
|
||||||
className="transition bg-transparent placeholder:text-white/50 placeholder:text-sm text-sm text-white resize-none focus:outline-none w-full px-2 max-h-24 lg:max-h-36 xl:max-h-48 flex-grow flex-shrink"
|
className="transition bg-transparent dark:placeholder:text-white/50 placeholder:text-sm text-sm dark:text-white resize-none focus:outline-none w-full px-2 max-h-24 lg:max-h-36 xl:max-h-48 flex-grow flex-shrink"
|
||||||
placeholder="Ask a follow-up"
|
placeholder="Ask a follow-up"
|
||||||
/>
|
/>
|
||||||
{mode === 'single' && (
|
{mode === 'single' && (
|
||||||
@@ -62,7 +63,7 @@ const MessageInput = ({
|
|||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
disabled={message.trim().length === 0 || loading}
|
disabled={message.trim().length === 0 || loading}
|
||||||
className="bg-[#24A0ED] text-white disabled:text-white/50 hover:bg-opacity-85 transition duration-100 disabled:bg-[#ececec21] rounded-full p-2"
|
className="bg-[#24A0ED] text-white disabled:text-black/50 dark:disabled:text-white/50 hover:bg-opacity-85 transition duration-100 disabled:bg-[#e0e0dc79] dark:disabled:bg-[#ececec21] rounded-full p-2"
|
||||||
>
|
>
|
||||||
<ArrowUp className="bg-background" size={17} />
|
<ArrowUp className="bg-background" size={17} />
|
||||||
</button>
|
</button>
|
||||||
@@ -78,7 +79,7 @@ const MessageInput = ({
|
|||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
disabled={message.trim().length === 0 || loading}
|
disabled={message.trim().length === 0 || loading}
|
||||||
className="bg-[#24A0ED] text-white disabled:text-white/50 hover:bg-opacity-85 transition duration-100 disabled:bg-[#ececec21] rounded-full p-2"
|
className="bg-[#24A0ED] text-white text-black/50 dark:disabled:text-white/50 hover:bg-opacity-85 transition duration-100 disabled:bg-[#e0e0dc79] dark:disabled:bg-[#ececec21] rounded-full p-2"
|
||||||
>
|
>
|
||||||
<ArrowUp className="bg-background" size={17} />
|
<ArrowUp className="bg-background" size={17} />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
14
ui/components/MessageInputActions/Attach.tsx
Normal file
14
ui/components/MessageInputActions/Attach.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { CopyPlus } from 'lucide-react';
|
||||||
|
|
||||||
|
const Attach = () => {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="p-2 text-black/50 dark:text-white/50 rounded-xl hover:bg-light-secondary dark:hover:bg-dark-secondary transition duration-200 hover:text-black dark:hover:text-white"
|
||||||
|
>
|
||||||
|
<CopyPlus />
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Attach;
|
||||||
43
ui/components/MessageInputActions/Copilot.tsx
Normal file
43
ui/components/MessageInputActions/Copilot.tsx
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
import { Switch } from '@headlessui/react';
|
||||||
|
|
||||||
|
const CopilotToggle = ({
|
||||||
|
copilotEnabled,
|
||||||
|
setCopilotEnabled,
|
||||||
|
}: {
|
||||||
|
copilotEnabled: boolean;
|
||||||
|
setCopilotEnabled: (enabled: boolean) => void;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className="group flex flex-row items-center space-x-1 active:scale-95 duration-200 transition cursor-pointer">
|
||||||
|
<Switch
|
||||||
|
checked={copilotEnabled}
|
||||||
|
onChange={setCopilotEnabled}
|
||||||
|
className="bg-light-secondary dark:bg-dark-secondary border border-light-200/70 dark:border-dark-200 relative inline-flex h-5 w-10 sm:h-6 sm:w-11 items-center rounded-full"
|
||||||
|
>
|
||||||
|
<span className="sr-only">Copilot</span>
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
copilotEnabled
|
||||||
|
? 'translate-x-6 bg-[#24A0ED]'
|
||||||
|
: 'translate-x-1 bg-black/50 dark:bg-white/50',
|
||||||
|
'inline-block h-3 w-3 sm:h-4 sm:w-4 transform rounded-full transition-all duration-200',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Switch>
|
||||||
|
<p
|
||||||
|
onClick={() => setCopilotEnabled(!copilotEnabled)}
|
||||||
|
className={cn(
|
||||||
|
'text-xs font-medium transition-colors duration-150 ease-in-out',
|
||||||
|
copilotEnabled
|
||||||
|
? 'text-[#24A0ED]'
|
||||||
|
: 'text-black/50 dark:text-white/50 group-hover:text-black dark:group-hover:text-white',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
Copilot
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CopilotToggle;
|
||||||
@@ -1,28 +1,16 @@
|
|||||||
import {
|
import {
|
||||||
BadgePercent,
|
BadgePercent,
|
||||||
ChevronDown,
|
ChevronDown,
|
||||||
CopyPlus,
|
|
||||||
Globe,
|
Globe,
|
||||||
Pencil,
|
Pencil,
|
||||||
ScanEye,
|
ScanEye,
|
||||||
SwatchBook,
|
SwatchBook,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { Popover, Switch, Transition } from '@headlessui/react';
|
import { Popover, Transition } from '@headlessui/react';
|
||||||
import { SiReddit, SiYoutube } from '@icons-pack/react-simple-icons';
|
import { SiReddit, SiYoutube } from '@icons-pack/react-simple-icons';
|
||||||
import { Fragment } from 'react';
|
import { Fragment } from 'react';
|
||||||
|
|
||||||
export const Attach = () => {
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="p-2 text-white/50 rounded-xl hover:bg-[#1c1c1c] transition duration-200 hover:text-white"
|
|
||||||
>
|
|
||||||
<CopyPlus />
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const focusModes = [
|
const focusModes = [
|
||||||
{
|
{
|
||||||
key: 'webSearch',
|
key: 'webSearch',
|
||||||
@@ -74,7 +62,7 @@ const focusModes = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const Focus = ({
|
const Focus = ({
|
||||||
focusMode,
|
focusMode,
|
||||||
setFocusMode,
|
setFocusMode,
|
||||||
}: {
|
}: {
|
||||||
@@ -85,7 +73,7 @@ export const Focus = ({
|
|||||||
<Popover className="fixed w-full max-w-[15rem] md:max-w-md lg:max-w-lg">
|
<Popover className="fixed w-full max-w-[15rem] md:max-w-md lg:max-w-lg">
|
||||||
<Popover.Button
|
<Popover.Button
|
||||||
type="button"
|
type="button"
|
||||||
className="p-2 text-white/50 rounded-xl hover:bg-[#1c1c1c] active:scale-95 transition duration-200 hover:text-white"
|
className="p-2 text-black/50 dark:text-white/50 rounded-xl hover:bg-light-secondary dark:hover:bg-dark-secondary active:scale-95 transition duration-200 hover:text-black dark:hover:text-white"
|
||||||
>
|
>
|
||||||
{focusMode !== 'webSearch' ? (
|
{focusMode !== 'webSearch' ? (
|
||||||
<div className="flex flex-row items-center space-x-1">
|
<div className="flex flex-row items-center space-x-1">
|
||||||
@@ -109,7 +97,7 @@ export const Focus = ({
|
|||||||
leaveTo="opacity-0 translate-y-1"
|
leaveTo="opacity-0 translate-y-1"
|
||||||
>
|
>
|
||||||
<Popover.Panel className="absolute z-10 w-full">
|
<Popover.Panel className="absolute z-10 w-full">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-1 bg-[#0A0A0A] border rounded-lg border-[#1c1c1c] w-full p-2 max-h-[200px] md:max-h-none overflow-y-auto">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-1 bg-light-primary dark:bg-dark-primary border rounded-lg border-light-200 dark:border-dark-200 w-full p-2 max-h-[200px] md:max-h-none overflow-y-auto">
|
||||||
{focusModes.map((mode, i) => (
|
{focusModes.map((mode, i) => (
|
||||||
<Popover.Button
|
<Popover.Button
|
||||||
onClick={() => setFocusMode(mode.key)}
|
onClick={() => setFocusMode(mode.key)}
|
||||||
@@ -117,20 +105,24 @@ export const Focus = ({
|
|||||||
className={cn(
|
className={cn(
|
||||||
'p-2 rounded-lg flex flex-col items-start justify-start text-start space-y-2 duration-200 cursor-pointer transition',
|
'p-2 rounded-lg flex flex-col items-start justify-start text-start space-y-2 duration-200 cursor-pointer transition',
|
||||||
focusMode === mode.key
|
focusMode === mode.key
|
||||||
? 'bg-[#111111]'
|
? 'bg-light-secondary dark:bg-dark-secondary'
|
||||||
: 'hover:bg-[#111111]',
|
: 'hover:bg-light-secondary dark:hover:bg-dark-secondary',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex flex-row items-center space-x-1',
|
'flex flex-row items-center space-x-1',
|
||||||
focusMode === mode.key ? 'text-[#24A0ED]' : 'text-white',
|
focusMode === mode.key
|
||||||
|
? 'text-[#24A0ED]'
|
||||||
|
: 'text-black dark:text-white',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{mode.icon}
|
{mode.icon}
|
||||||
<p className="text-sm font-medium">{mode.title}</p>
|
<p className="text-sm font-medium">{mode.title}</p>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-white/70 text-xs">{mode.description}</p>
|
<p className="text-black/70 dark:text-white/70 text-xs">
|
||||||
|
{mode.description}
|
||||||
|
</p>
|
||||||
</Popover.Button>
|
</Popover.Button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -140,41 +132,4 @@ export const Focus = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CopilotToggle = ({
|
export default Focus;
|
||||||
copilotEnabled,
|
|
||||||
setCopilotEnabled,
|
|
||||||
}: {
|
|
||||||
copilotEnabled: boolean;
|
|
||||||
setCopilotEnabled: (enabled: boolean) => void;
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<div className="group flex flex-row items-center space-x-1 active:scale-95 duration-200 transition cursor-pointer">
|
|
||||||
<Switch
|
|
||||||
checked={copilotEnabled}
|
|
||||||
onChange={setCopilotEnabled}
|
|
||||||
className="bg-[#111111] border border-[#1C1C1C] relative inline-flex h-5 w-10 sm:h-6 sm:w-11 items-center rounded-full"
|
|
||||||
>
|
|
||||||
<span className="sr-only">Copilot</span>
|
|
||||||
<span
|
|
||||||
className={cn(
|
|
||||||
copilotEnabled
|
|
||||||
? 'translate-x-6 bg-[#24A0ED]'
|
|
||||||
: 'translate-x-1 bg-white/50',
|
|
||||||
'inline-block h-3 w-3 sm:h-4 sm:w-4 transform rounded-full transition-all duration-200',
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</Switch>
|
|
||||||
<p
|
|
||||||
onClick={() => setCopilotEnabled(!copilotEnabled)}
|
|
||||||
className={cn(
|
|
||||||
'text-xs font-medium transition-colors duration-150 ease-in-out',
|
|
||||||
copilotEnabled
|
|
||||||
? 'text-[#24A0ED]'
|
|
||||||
: 'text-white/50 group-hover:text-white',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
Copilot
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -20,12 +20,12 @@ const MessageSources = ({ sources }: { sources: Document[] }) => {
|
|||||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-2">
|
<div className="grid grid-cols-2 lg:grid-cols-4 gap-2">
|
||||||
{sources.slice(0, 3).map((source, i) => (
|
{sources.slice(0, 3).map((source, i) => (
|
||||||
<a
|
<a
|
||||||
className="bg-[#111111] hover:bg-[#1c1c1c] transition duration-200 rounded-lg p-3 flex flex-col space-y-2 font-medium"
|
className="bg-light-100 hover:bg-light-200 dark:bg-dark-100 dark:hover:bg-dark-200 transition duration-200 rounded-lg p-3 flex flex-col space-y-2 font-medium"
|
||||||
key={i}
|
key={i}
|
||||||
href={source.metadata.url}
|
href={source.metadata.url}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
<p className="text-white text-xs overflow-hidden whitespace-nowrap text-ellipsis">
|
<p className="dark:text-white text-xs overflow-hidden whitespace-nowrap text-ellipsis">
|
||||||
{source.metadata.title}
|
{source.metadata.title}
|
||||||
</p>
|
</p>
|
||||||
<div className="flex flex-row items-center justify-between">
|
<div className="flex flex-row items-center justify-between">
|
||||||
@@ -37,12 +37,12 @@ const MessageSources = ({ sources }: { sources: Document[] }) => {
|
|||||||
alt="favicon"
|
alt="favicon"
|
||||||
className="rounded-lg h-4 w-4"
|
className="rounded-lg h-4 w-4"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-white/50 overflow-hidden whitespace-nowrap text-ellipsis">
|
<p className="text-xs text-black/50 dark:text-white/50 overflow-hidden whitespace-nowrap text-ellipsis">
|
||||||
{source.metadata.url.replace(/.+\/\/|www.|\..+/g, '')}
|
{source.metadata.url.replace(/.+\/\/|www.|\..+/g, '')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row items-center space-x-1 text-white/50 text-xs">
|
<div className="flex flex-row items-center space-x-1 text-black/50 dark:text-white/50 text-xs">
|
||||||
<div className="bg-white/50 h-[4px] w-[4px] rounded-full" />
|
<div className="bg-black/50 dark:bg-white/50 h-[4px] w-[4px] rounded-full" />
|
||||||
<span>{i + 1}</span>
|
<span>{i + 1}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -51,7 +51,7 @@ const MessageSources = ({ sources }: { sources: Document[] }) => {
|
|||||||
{sources.length > 3 && (
|
{sources.length > 3 && (
|
||||||
<button
|
<button
|
||||||
onClick={openModal}
|
onClick={openModal}
|
||||||
className="bg-[#111111] hover:bg-[#1c1c1c] transition duration-200 rounded-lg px-4 py-2 flex flex-col justify-between space-y-2"
|
className="bg-light-100 hover:bg-light-200 dark:bg-dark-100 dark:hover:bg-dark-200 transition duration-200 rounded-lg p-3 flex flex-col space-y-2 font-medium"
|
||||||
>
|
>
|
||||||
<div className="flex flex-row items-center space-x-1">
|
<div className="flex flex-row items-center space-x-1">
|
||||||
{sources.slice(3, 6).map((source, i) => (
|
{sources.slice(3, 6).map((source, i) => (
|
||||||
@@ -65,7 +65,7 @@ const MessageSources = ({ sources }: { sources: Document[] }) => {
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-white/50">
|
<p className="text-xs text-black/50 dark:text-white/50">
|
||||||
View {sources.length - 3} more
|
View {sources.length - 3} more
|
||||||
</p>
|
</p>
|
||||||
</button>
|
</button>
|
||||||
@@ -83,19 +83,19 @@ const MessageSources = ({ sources }: { sources: Document[] }) => {
|
|||||||
leaveFrom="opacity-100 scale-200"
|
leaveFrom="opacity-100 scale-200"
|
||||||
leaveTo="opacity-0 scale-95"
|
leaveTo="opacity-0 scale-95"
|
||||||
>
|
>
|
||||||
<Dialog.Panel className="w-full max-w-md transform rounded-2xl bg-[#111111] border border-[#1c1c1c] p-6 text-left align-middle shadow-xl transition-all">
|
<Dialog.Panel className="w-full max-w-md transform rounded-2xl bg-light-secondary dark:bg-dark-secondary border border-light-200 dark:border-dark-200 p-6 text-left align-middle shadow-xl transition-all">
|
||||||
<Dialog.Title className="text-lg font-medium leading-6 text-white">
|
<Dialog.Title className="text-lg font-medium leading-6 dark:text-white">
|
||||||
Sources
|
Sources
|
||||||
</Dialog.Title>
|
</Dialog.Title>
|
||||||
<div className="grid grid-cols-2 gap-2 overflow-auto max-h-[300px] mt-2 pr-2">
|
<div className="grid grid-cols-2 gap-2 overflow-auto max-h-[300px] mt-2 pr-2">
|
||||||
{sources.map((source, i) => (
|
{sources.map((source, i) => (
|
||||||
<a
|
<a
|
||||||
className="bg-[#111111] hover:bg-[#1c1c1c] border border-[#1c1c1c] transition duration-200 rounded-lg p-3 flex flex-col space-y-2 font-medium"
|
className="bg-light-secondary hover:bg-light-200 dark:bg-dark-secondary dark:hover:bg-dark-200 border border-light-200 dark:border-dark-200 transition duration-200 rounded-lg p-3 flex flex-col space-y-2 font-medium"
|
||||||
key={i}
|
key={i}
|
||||||
href={source.metadata.url}
|
href={source.metadata.url}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
<p className="text-white text-xs overflow-hidden whitespace-nowrap text-ellipsis">
|
<p className="dark:text-white text-xs overflow-hidden whitespace-nowrap text-ellipsis">
|
||||||
{source.metadata.title}
|
{source.metadata.title}
|
||||||
</p>
|
</p>
|
||||||
<div className="flex flex-row items-center justify-between">
|
<div className="flex flex-row items-center justify-between">
|
||||||
@@ -107,15 +107,15 @@ const MessageSources = ({ sources }: { sources: Document[] }) => {
|
|||||||
alt="favicon"
|
alt="favicon"
|
||||||
className="rounded-lg h-4 w-4"
|
className="rounded-lg h-4 w-4"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-white/50 overflow-hidden whitespace-nowrap text-ellipsis">
|
<p className="text-xs text-black/50 dark:text-white/50 overflow-hidden whitespace-nowrap text-ellipsis">
|
||||||
{source.metadata.url.replace(
|
{source.metadata.url.replace(
|
||||||
/.+\/\/|www.|\..+/g,
|
/.+\/\/|www.|\..+/g,
|
||||||
'',
|
'',
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row items-center space-x-1 text-white/50 text-xs">
|
<div className="flex flex-row items-center space-x-1 text-black/50 dark:text-white/50 text-xs">
|
||||||
<div className="bg-white/50 h-[4px] w-[4px] rounded-full" />
|
<div className="bg-black/50 dark:bg-white/50 h-[4px] w-[4px] rounded-full" />
|
||||||
<span>{i + 1}</span>
|
<span>{i + 1}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Clock, Edit, Share, Trash } from 'lucide-react';
|
|||||||
import { Message } from './ChatWindow';
|
import { Message } from './ChatWindow';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { formatTimeDifference } from '@/lib/utils';
|
import { formatTimeDifference } from '@/lib/utils';
|
||||||
|
import ThemeSwitcher from './theme/Switcher';
|
||||||
|
|
||||||
const Navbar = ({ messages }: { messages: Message[] }) => {
|
const Navbar = ({ messages }: { messages: Message[] }) => {
|
||||||
const [title, setTitle] = useState<string>('');
|
const [title, setTitle] = useState<string>('');
|
||||||
@@ -38,7 +39,7 @@ const Navbar = ({ messages }: { messages: Message[] }) => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed z-40 top-0 left-0 right-0 px-4 lg:pl-[104px] lg:pr-6 lg:px-8 flex flex-row items-center justify-between w-full py-4 text-sm text-white/70 border-b bg-[#0A0A0A] border-[#1C1C1C]">
|
<div className="fixed z-40 top-0 left-0 right-0 px-4 lg:pl-[104px] lg:pr-6 lg:px-8 flex flex-row items-center justify-between w-full py-4 text-sm text-black dark:text-white/70 border-b bg-light-primary dark:bg-dark-primary border-light-100 dark:border-dark-200">
|
||||||
<Edit
|
<Edit
|
||||||
size={17}
|
size={17}
|
||||||
className="active:scale-95 transition duration-100 cursor-pointer lg:hidden"
|
className="active:scale-95 transition duration-100 cursor-pointer lg:hidden"
|
||||||
@@ -48,6 +49,9 @@ const Navbar = ({ messages }: { messages: Message[] }) => {
|
|||||||
<p className="text-xs">{timeAgo} ago</p>
|
<p className="text-xs">{timeAgo} ago</p>
|
||||||
</div>
|
</div>
|
||||||
<p className="hidden lg:flex">{title}</p>
|
<p className="hidden lg:flex">{title}</p>
|
||||||
|
|
||||||
|
<ThemeSwitcher size={17} className="lg:hidden ml-auto mr-4" />
|
||||||
|
|
||||||
<div className="flex flex-row items-center space-x-4">
|
<div className="flex flex-row items-center space-x-4">
|
||||||
<Share
|
<Share
|
||||||
size={17}
|
size={17}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { useState } from 'react';
|
|||||||
import Lightbox from 'yet-another-react-lightbox';
|
import Lightbox from 'yet-another-react-lightbox';
|
||||||
import 'yet-another-react-lightbox/styles.css';
|
import 'yet-another-react-lightbox/styles.css';
|
||||||
import { Message } from './ChatWindow';
|
import { Message } from './ChatWindow';
|
||||||
import { clientFetch } from '@/lib/utils';
|
|
||||||
|
|
||||||
type Image = {
|
type Image = {
|
||||||
url: string;
|
url: string;
|
||||||
@@ -34,7 +33,9 @@ const SearchImages = ({
|
|||||||
const chatModelProvider = localStorage.getItem('chatModelProvider');
|
const chatModelProvider = localStorage.getItem('chatModelProvider');
|
||||||
const chatModel = localStorage.getItem('chatModel');
|
const chatModel = localStorage.getItem('chatModel');
|
||||||
|
|
||||||
const res = await clientFetch('/images', {
|
const res = await fetch(
|
||||||
|
`${process.env.NEXT_PUBLIC_API_URL}/images`,
|
||||||
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
@@ -45,7 +46,8 @@ const SearchImages = ({
|
|||||||
chat_model_provider: chatModelProvider,
|
chat_model_provider: chatModelProvider,
|
||||||
chat_model: chatModel,
|
chat_model: chatModel,
|
||||||
}),
|
}),
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
@@ -60,7 +62,7 @@ const SearchImages = ({
|
|||||||
);
|
);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}}
|
}}
|
||||||
className="border border-dashed border-[#1C1C1C] hover:bg-[#1c1c1c] active:scale-95 duration-200 transition px-4 py-2 flex flex-row items-center justify-between rounded-lg text-white text-sm w-full"
|
className="border border-dashed border-light-200 dark:border-dark-200 hover:bg-light-200 dark:hover:bg-dark-200 active:scale-95 duration-200 transition px-4 py-2 flex flex-row items-center justify-between rounded-lg dark:text-white text-sm w-full"
|
||||||
>
|
>
|
||||||
<div className="flex flex-row items-center space-x-2">
|
<div className="flex flex-row items-center space-x-2">
|
||||||
<ImagesIcon size={17} />
|
<ImagesIcon size={17} />
|
||||||
@@ -74,7 +76,7 @@ const SearchImages = ({
|
|||||||
{[...Array(4)].map((_, i) => (
|
{[...Array(4)].map((_, i) => (
|
||||||
<div
|
<div
|
||||||
key={i}
|
key={i}
|
||||||
className="bg-[#1C1C1C] h-32 w-full rounded-lg animate-pulse aspect-video object-cover"
|
className="bg-light-secondary dark:bg-dark-secondary h-32 w-full rounded-lg animate-pulse aspect-video object-cover"
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -118,7 +120,7 @@ const SearchImages = ({
|
|||||||
{images.length > 4 && (
|
{images.length > 4 && (
|
||||||
<button
|
<button
|
||||||
onClick={() => setOpen(true)}
|
onClick={() => setOpen(true)}
|
||||||
className="bg-[#111111] hover:bg-[#1c1c1c] transition duration-200 active:scale-95 hover:scale-[1.02] h-auto w-full rounded-lg flex flex-col justify-between text-white p-2"
|
className="bg-light-100 hover:bg-light-200 dark:bg-dark-100 dark:hover:bg-dark-200 transition duration-200 active:scale-95 hover:scale-[1.02] h-auto w-full rounded-lg flex flex-col justify-between text-white p-2"
|
||||||
>
|
>
|
||||||
<div className="flex flex-row items-center space-x-1">
|
<div className="flex flex-row items-center space-x-1">
|
||||||
{images.slice(3, 6).map((image, i) => (
|
{images.slice(3, 6).map((image, i) => (
|
||||||
@@ -130,7 +132,7 @@ const SearchImages = ({
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-white/70 text-xs">
|
<p className="text-black/70 dark:text-white/70 text-xs">
|
||||||
View {images.length - 3} more
|
View {images.length - 3} more
|
||||||
</p>
|
</p>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { useState } from 'react';
|
|||||||
import Lightbox, { GenericSlide, VideoSlide } from 'yet-another-react-lightbox';
|
import Lightbox, { GenericSlide, VideoSlide } from 'yet-another-react-lightbox';
|
||||||
import 'yet-another-react-lightbox/styles.css';
|
import 'yet-another-react-lightbox/styles.css';
|
||||||
import { Message } from './ChatWindow';
|
import { Message } from './ChatWindow';
|
||||||
import { clientFetch } from '@/lib/utils';
|
|
||||||
|
|
||||||
type Video = {
|
type Video = {
|
||||||
url: string;
|
url: string;
|
||||||
@@ -47,7 +46,9 @@ const Searchvideos = ({
|
|||||||
const chatModelProvider = localStorage.getItem('chatModelProvider');
|
const chatModelProvider = localStorage.getItem('chatModelProvider');
|
||||||
const chatModel = localStorage.getItem('chatModel');
|
const chatModel = localStorage.getItem('chatModel');
|
||||||
|
|
||||||
const res = await clientFetch('/videos', {
|
const res = await fetch(
|
||||||
|
`${process.env.NEXT_PUBLIC_API_URL}/videos`,
|
||||||
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
@@ -58,7 +59,8 @@ const Searchvideos = ({
|
|||||||
chat_model_provider: chatModelProvider,
|
chat_model_provider: chatModelProvider,
|
||||||
chat_model: chatModel,
|
chat_model: chatModel,
|
||||||
}),
|
}),
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
@@ -75,7 +77,7 @@ const Searchvideos = ({
|
|||||||
);
|
);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}}
|
}}
|
||||||
className="border border-dashed border-[#1C1C1C] hover:bg-[#1c1c1c] active:scale-95 duration-200 transition px-4 py-2 flex flex-row items-center justify-between rounded-lg text-white text-sm w-full"
|
className="border border-dashed border-light-200 dark:border-dark-200 hover:bg-light-200 dark:hover:bg-dark-200 active:scale-95 duration-200 transition px-4 py-2 flex flex-row items-center justify-between rounded-lg dark:text-white text-sm w-full"
|
||||||
>
|
>
|
||||||
<div className="flex flex-row items-center space-x-2">
|
<div className="flex flex-row items-center space-x-2">
|
||||||
<VideoIcon size={17} />
|
<VideoIcon size={17} />
|
||||||
@@ -89,7 +91,7 @@ const Searchvideos = ({
|
|||||||
{[...Array(4)].map((_, i) => (
|
{[...Array(4)].map((_, i) => (
|
||||||
<div
|
<div
|
||||||
key={i}
|
key={i}
|
||||||
className="bg-[#1C1C1C] h-32 w-full rounded-lg animate-pulse aspect-video object-cover"
|
className="bg-light-secondary dark:bg-dark-secondary h-32 w-full rounded-lg animate-pulse aspect-video object-cover"
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -116,7 +118,7 @@ const Searchvideos = ({
|
|||||||
alt={video.title}
|
alt={video.title}
|
||||||
className="relative h-full w-full aspect-video object-cover rounded-lg"
|
className="relative h-full w-full aspect-video object-cover rounded-lg"
|
||||||
/>
|
/>
|
||||||
<div className="absolute bg-black/70 text-white/70 px-2 py-1 flex flex-row items-center space-x-1 bottom-1 right-1 rounded-md">
|
<div className="absolute bg-white/70 dark:bg-black/70 text-black/70 dark:text-white/70 px-2 py-1 flex flex-row items-center space-x-1 bottom-1 right-1 rounded-md">
|
||||||
<PlayCircle size={15} />
|
<PlayCircle size={15} />
|
||||||
<p className="text-xs">Video</p>
|
<p className="text-xs">Video</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -140,7 +142,7 @@ const Searchvideos = ({
|
|||||||
alt={video.title}
|
alt={video.title}
|
||||||
className="relative h-full w-full aspect-video object-cover rounded-lg"
|
className="relative h-full w-full aspect-video object-cover rounded-lg"
|
||||||
/>
|
/>
|
||||||
<div className="absolute bg-black/70 text-white/70 px-2 py-1 flex flex-row items-center space-x-1 bottom-1 right-1 rounded-md">
|
<div className="absolute bg-white/70 dark:bg-black/70 text-black/70 dark:text-white/70 px-2 py-1 flex flex-row items-center space-x-1 bottom-1 right-1 rounded-md">
|
||||||
<PlayCircle size={15} />
|
<PlayCircle size={15} />
|
||||||
<p className="text-xs">Video</p>
|
<p className="text-xs">Video</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -149,7 +151,7 @@ const Searchvideos = ({
|
|||||||
{videos.length > 4 && (
|
{videos.length > 4 && (
|
||||||
<button
|
<button
|
||||||
onClick={() => setOpen(true)}
|
onClick={() => setOpen(true)}
|
||||||
className="bg-[#111111] hover:bg-[#1c1c1c] transition duration-200 active:scale-95 hover:scale-[1.02] h-auto w-full rounded-lg flex flex-col justify-between text-white p-2"
|
className="bg-light-100 hover:bg-light-200 dark:bg-dark-100 dark:hover:bg-dark-200 transition duration-200 active:scale-95 hover:scale-[1.02] h-auto w-full rounded-lg flex flex-col justify-between text-white p-2"
|
||||||
>
|
>
|
||||||
<div className="flex flex-row items-center space-x-1">
|
<div className="flex flex-row items-center space-x-1">
|
||||||
{videos.slice(3, 6).map((video, i) => (
|
{videos.slice(3, 6).map((video, i) => (
|
||||||
@@ -161,7 +163,7 @@ const Searchvideos = ({
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-white/70 text-xs">
|
<p className="text-black/70 dark:text-white/70 text-xs">
|
||||||
View {videos.length - 3} more
|
View {videos.length - 3} more
|
||||||
</p>
|
</p>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -1,7 +1,52 @@
|
|||||||
|
import { cn } from '@/lib/utils';
|
||||||
import { Dialog, Transition } from '@headlessui/react';
|
import { Dialog, Transition } from '@headlessui/react';
|
||||||
import { CloudUpload, RefreshCcw, RefreshCw } from 'lucide-react';
|
import { CloudUpload, RefreshCcw, RefreshCw } from 'lucide-react';
|
||||||
import React, { Fragment, useEffect, useState } from 'react';
|
import React, {
|
||||||
import { clientFetch } from '@/lib/utils';
|
Fragment,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useState,
|
||||||
|
type SelectHTMLAttributes,
|
||||||
|
} from 'react';
|
||||||
|
import ThemeSwitcher from './theme/Switcher';
|
||||||
|
|
||||||
|
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {}
|
||||||
|
|
||||||
|
const Input = ({ className, ...restProps }: InputProps) => {
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
{...restProps}
|
||||||
|
className={cn(
|
||||||
|
'bg-light-secondary dark:bg-dark-secondary px-3 py-2 flex items-center overflow-hidden border border-light-200 dark:border-dark-200 dark:text-white rounded-lg text-sm',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface SelectProps extends SelectHTMLAttributes<HTMLSelectElement> {
|
||||||
|
options: { value: string; label: string; disabled?: boolean }[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Select = ({ className, options, ...restProps }: SelectProps) => {
|
||||||
|
return (
|
||||||
|
<select
|
||||||
|
{...restProps}
|
||||||
|
className={cn(
|
||||||
|
'bg-light-secondary dark:bg-dark-secondary px-3 py-2 flex items-center overflow-hidden border border-light-200 dark:border-dark-200 dark:text-white rounded-lg text-sm',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{options.map(({ label, value, disabled }) => {
|
||||||
|
return (
|
||||||
|
<option key={value} value={value} disabled={disabled}>
|
||||||
|
{label}
|
||||||
|
</option>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</select>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
interface SettingsType {
|
interface SettingsType {
|
||||||
chatModelProviders: {
|
chatModelProviders: {
|
||||||
@@ -43,7 +88,7 @@ const SettingsDialog = ({
|
|||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
const fetchConfig = async () => {
|
const fetchConfig = async () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
const res = await clientFetch('/config', {
|
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/config`, {
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
@@ -103,7 +148,7 @@ const SettingsDialog = ({
|
|||||||
setIsUpdating(true);
|
setIsUpdating(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await clientFetch('/config', {
|
await fetch(`${process.env.NEXT_PUBLIC_API_URL}/config`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
@@ -146,7 +191,7 @@ const SettingsDialog = ({
|
|||||||
leaveFrom="opacity-100"
|
leaveFrom="opacity-100"
|
||||||
leaveTo="opacity-0"
|
leaveTo="opacity-0"
|
||||||
>
|
>
|
||||||
<div className="fixed inset-0 bg-black/50" />
|
<div className="fixed inset-0 bg-white/50 dark:bg-black/50" />
|
||||||
</Transition.Child>
|
</Transition.Child>
|
||||||
<div className="fixed inset-0 overflow-y-auto">
|
<div className="fixed inset-0 overflow-y-auto">
|
||||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||||
@@ -159,18 +204,24 @@ const SettingsDialog = ({
|
|||||||
leaveFrom="opacity-100 scale-200"
|
leaveFrom="opacity-100 scale-200"
|
||||||
leaveTo="opacity-0 scale-95"
|
leaveTo="opacity-0 scale-95"
|
||||||
>
|
>
|
||||||
<Dialog.Panel className="w-full max-w-md transform rounded-2xl bg-[#111111] border border-[#1c1c1c] p-6 text-left align-middle shadow-xl transition-all">
|
<Dialog.Panel className="w-full max-w-md transform rounded-2xl bg-light-secondary dark:bg-dark-secondary border border-light-200 dark:border-dark-200 p-6 text-left align-middle shadow-xl transition-all">
|
||||||
<Dialog.Title className="text-xl font-medium leading-6 text-white">
|
<Dialog.Title className="text-xl font-medium leading-6 dark:text-white">
|
||||||
Settings
|
Settings
|
||||||
</Dialog.Title>
|
</Dialog.Title>
|
||||||
{config && !isLoading && (
|
{config && !isLoading && (
|
||||||
<div className="flex flex-col space-y-4 mt-6">
|
<div className="flex flex-col space-y-4 mt-6">
|
||||||
|
<div className="flex flex-col space-y-1">
|
||||||
|
<p className="text-black/70 dark:text-white/70 text-sm">
|
||||||
|
Theme
|
||||||
|
</p>
|
||||||
|
<ThemeSwitcher />
|
||||||
|
</div>
|
||||||
{config.chatModelProviders && (
|
{config.chatModelProviders && (
|
||||||
<div className="flex flex-col space-y-1">
|
<div className="flex flex-col space-y-1">
|
||||||
<p className="text-white/70 text-sm">
|
<p className="text-black/70 dark:text-white/70 text-sm">
|
||||||
Chat model Provider
|
Chat model Provider
|
||||||
</p>
|
</p>
|
||||||
<select
|
<Select
|
||||||
value={selectedChatModelProvider ?? undefined}
|
value={selectedChatModelProvider ?? undefined}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setSelectedChatModelProvider(e.target.value);
|
setSelectedChatModelProvider(e.target.value);
|
||||||
@@ -178,97 +229,99 @@ const SettingsDialog = ({
|
|||||||
config.chatModelProviders[e.target.value][0],
|
config.chatModelProviders[e.target.value][0],
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
className="bg-[#111111] px-3 py-2 flex items-center overflow-hidden border border-[#1C1C1C] text-white rounded-lg text-sm"
|
options={Object.keys(config.chatModelProviders).map(
|
||||||
>
|
(provider) => ({
|
||||||
{Object.keys(config.chatModelProviders).map(
|
value: provider,
|
||||||
(provider) => (
|
label:
|
||||||
<option key={provider} value={provider}>
|
provider.charAt(0).toUpperCase() +
|
||||||
{provider.charAt(0).toUpperCase() +
|
provider.slice(1),
|
||||||
provider.slice(1)}
|
}),
|
||||||
</option>
|
|
||||||
),
|
|
||||||
)}
|
)}
|
||||||
</select>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{selectedChatModelProvider &&
|
{selectedChatModelProvider &&
|
||||||
selectedChatModelProvider != 'custom_openai' && (
|
selectedChatModelProvider != 'custom_openai' && (
|
||||||
<div className="flex flex-col space-y-1">
|
<div className="flex flex-col space-y-1">
|
||||||
<p className="text-white/70 text-sm">Chat Model</p>
|
<p className="text-black/70 dark:text-white/70 text-sm">
|
||||||
<select
|
Chat Model
|
||||||
|
</p>
|
||||||
|
<Select
|
||||||
value={selectedChatModel ?? undefined}
|
value={selectedChatModel ?? undefined}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setSelectedChatModel(e.target.value)
|
setSelectedChatModel(e.target.value)
|
||||||
}
|
}
|
||||||
className="bg-[#111111] px-3 py-2 flex items-center overflow-hidden border border-[#1C1C1C] text-white rounded-lg text-sm"
|
options={(() => {
|
||||||
>
|
const chatModelProvider =
|
||||||
{config.chatModelProviders[
|
|
||||||
selectedChatModelProvider
|
|
||||||
] ? (
|
|
||||||
config.chatModelProviders[
|
config.chatModelProviders[
|
||||||
selectedChatModelProvider
|
selectedChatModelProvider
|
||||||
].length > 0 ? (
|
];
|
||||||
config.chatModelProviders[
|
|
||||||
selectedChatModelProvider
|
return chatModelProvider
|
||||||
].map((model) => (
|
? chatModelProvider.length > 0
|
||||||
<option key={model} value={model}>
|
? chatModelProvider.map((model) => ({
|
||||||
{model}
|
value: model,
|
||||||
</option>
|
label: model,
|
||||||
))
|
}))
|
||||||
) : (
|
: [
|
||||||
<option value="" disabled>
|
{
|
||||||
No models available
|
value: '',
|
||||||
</option>
|
label: 'No models available',
|
||||||
)
|
disabled: true,
|
||||||
) : (
|
},
|
||||||
<option value="" disabled>
|
]
|
||||||
Invalid provider, please check backend logs
|
: [
|
||||||
</option>
|
{
|
||||||
)}
|
value: '',
|
||||||
</select>
|
label:
|
||||||
|
'Invalid provider, please check backend logs',
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
})()}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{selectedChatModelProvider &&
|
{selectedChatModelProvider &&
|
||||||
selectedChatModelProvider === 'custom_openai' && (
|
selectedChatModelProvider === 'custom_openai' && (
|
||||||
<>
|
<>
|
||||||
<div className="flex flex-col space-y-1">
|
<div className="flex flex-col space-y-1">
|
||||||
<p className="text-white/70 text-sm">Model name</p>
|
<p className="text-black/70 dark:text-white/70 text-sm">
|
||||||
<input
|
Model name
|
||||||
|
</p>
|
||||||
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Model name"
|
placeholder="Model name"
|
||||||
defaultValue={selectedChatModel!}
|
defaultValue={selectedChatModel!}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setSelectedChatModel(e.target.value)
|
setSelectedChatModel(e.target.value)
|
||||||
}
|
}
|
||||||
className="bg-[#111111] px-3 py-2 flex items-center overflow-hidden border border-[#1C1C1C] text-white rounded-lg text-sm"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col space-y-1">
|
<div className="flex flex-col space-y-1">
|
||||||
<p className="text-white/70 text-sm">
|
<p className="text-black/70 dark:text-white/70 text-sm">
|
||||||
Custom OpenAI API Key
|
Custom OpenAI API Key
|
||||||
</p>
|
</p>
|
||||||
<input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Custom OpenAI API Key"
|
placeholder="Custom OpenAI API Key"
|
||||||
defaultValue={customOpenAIApiKey!}
|
defaultValue={customOpenAIApiKey!}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setCustomOpenAIApiKey(e.target.value)
|
setCustomOpenAIApiKey(e.target.value)
|
||||||
}
|
}
|
||||||
className="bg-[#111111] px-3 py-2 flex items-center overflow-hidden border border-[#1C1C1C] text-white rounded-lg text-sm"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col space-y-1">
|
<div className="flex flex-col space-y-1">
|
||||||
<p className="text-white/70 text-sm">
|
<p className="text-black/70 dark:text-white/70 text-sm">
|
||||||
Custom OpenAI Base URL
|
Custom OpenAI Base URL
|
||||||
</p>
|
</p>
|
||||||
<input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Custom OpenAI Base URL"
|
placeholder="Custom OpenAI Base URL"
|
||||||
defaultValue={customOpenAIBaseURL!}
|
defaultValue={customOpenAIBaseURL!}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setCustomOpenAIBaseURL(e.target.value)
|
setCustomOpenAIBaseURL(e.target.value)
|
||||||
}
|
}
|
||||||
className="bg-[#111111] px-3 py-2 flex items-center overflow-hidden border border-[#1C1C1C] text-white rounded-lg text-sm"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
@@ -276,10 +329,10 @@ const SettingsDialog = ({
|
|||||||
{/* Embedding models */}
|
{/* Embedding models */}
|
||||||
{config.embeddingModelProviders && (
|
{config.embeddingModelProviders && (
|
||||||
<div className="flex flex-col space-y-1">
|
<div className="flex flex-col space-y-1">
|
||||||
<p className="text-white/70 text-sm">
|
<p className="text-black/70 dark:text-white/70 text-sm">
|
||||||
Embedding model Provider
|
Embedding model Provider
|
||||||
</p>
|
</p>
|
||||||
<select
|
<Select
|
||||||
value={selectedEmbeddingModelProvider ?? undefined}
|
value={selectedEmbeddingModelProvider ?? undefined}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setSelectedEmbeddingModelProvider(e.target.value);
|
setSelectedEmbeddingModelProvider(e.target.value);
|
||||||
@@ -287,58 +340,63 @@ const SettingsDialog = ({
|
|||||||
config.embeddingModelProviders[e.target.value][0],
|
config.embeddingModelProviders[e.target.value][0],
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
className="bg-[#111111] px-3 py-2 flex items-center overflow-hidden border border-[#1C1C1C] text-white rounded-lg text-sm"
|
options={Object.keys(
|
||||||
>
|
config.embeddingModelProviders,
|
||||||
{Object.keys(config.embeddingModelProviders).map(
|
).map((provider) => ({
|
||||||
(provider) => (
|
label:
|
||||||
<option key={provider} value={provider}>
|
provider.charAt(0).toUpperCase() +
|
||||||
{provider.charAt(0).toUpperCase() +
|
provider.slice(1),
|
||||||
provider.slice(1)}
|
value: provider,
|
||||||
</option>
|
}))}
|
||||||
),
|
/>
|
||||||
)}
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{selectedEmbeddingModelProvider && (
|
{selectedEmbeddingModelProvider && (
|
||||||
<div className="flex flex-col space-y-1">
|
<div className="flex flex-col space-y-1">
|
||||||
<p className="text-white/70 text-sm">Embedding Model</p>
|
<p className="text-black/70 dark:text-white/70 text-sm">
|
||||||
<select
|
Embedding Model
|
||||||
|
</p>
|
||||||
|
<Select
|
||||||
value={selectedEmbeddingModel ?? undefined}
|
value={selectedEmbeddingModel ?? undefined}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setSelectedEmbeddingModel(e.target.value)
|
setSelectedEmbeddingModel(e.target.value)
|
||||||
}
|
}
|
||||||
className="bg-[#111111] px-3 py-2 flex items-center overflow-hidden border border-[#1C1C1C] text-white rounded-lg text-sm"
|
options={(() => {
|
||||||
>
|
const embeddingModelProvider =
|
||||||
{config.embeddingModelProviders[
|
|
||||||
selectedEmbeddingModelProvider
|
|
||||||
] ? (
|
|
||||||
config.embeddingModelProviders[
|
config.embeddingModelProviders[
|
||||||
selectedEmbeddingModelProvider
|
selectedEmbeddingModelProvider
|
||||||
].length > 0 ? (
|
];
|
||||||
config.embeddingModelProviders[
|
|
||||||
selectedEmbeddingModelProvider
|
return embeddingModelProvider
|
||||||
].map((model) => (
|
? embeddingModelProvider.length > 0
|
||||||
<option key={model} value={model}>
|
? embeddingModelProvider.map((model) => ({
|
||||||
{model}
|
label: model,
|
||||||
</option>
|
value: model,
|
||||||
))
|
}))
|
||||||
) : (
|
: [
|
||||||
<option value="" disabled selected>
|
{
|
||||||
No embedding models available
|
label: 'No embedding models available',
|
||||||
</option>
|
value: '',
|
||||||
)
|
disabled: true,
|
||||||
) : (
|
},
|
||||||
<option value="" disabled selected>
|
]
|
||||||
Invalid provider, please check backend logs
|
: [
|
||||||
</option>
|
{
|
||||||
)}
|
label:
|
||||||
</select>
|
'Invalid provider, please check backend logs',
|
||||||
|
value: '',
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
})()}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-col space-y-1">
|
<div className="flex flex-col space-y-1">
|
||||||
<p className="text-white/70 text-sm">OpenAI API Key</p>
|
<p className="text-black/70 dark:text-white/70 text-sm">
|
||||||
<input
|
OpenAI API Key
|
||||||
|
</p>
|
||||||
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="OpenAI API Key"
|
placeholder="OpenAI API Key"
|
||||||
defaultValue={config.openaiApiKey}
|
defaultValue={config.openaiApiKey}
|
||||||
@@ -348,12 +406,13 @@ const SettingsDialog = ({
|
|||||||
openaiApiKey: e.target.value,
|
openaiApiKey: e.target.value,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
className="bg-[#111111] px-3 py-2 flex items-center overflow-hidden border border-[#1C1C1C] text-white rounded-lg text-sm"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col space-y-1">
|
<div className="flex flex-col space-y-1">
|
||||||
<p className="text-white/70 text-sm">Ollama API URL</p>
|
<p className="text-black/70 dark:text-white/70 text-sm">
|
||||||
<input
|
Ollama API URL
|
||||||
|
</p>
|
||||||
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Ollama API URL"
|
placeholder="Ollama API URL"
|
||||||
defaultValue={config.ollamaApiUrl}
|
defaultValue={config.ollamaApiUrl}
|
||||||
@@ -363,12 +422,13 @@ const SettingsDialog = ({
|
|||||||
ollamaApiUrl: e.target.value,
|
ollamaApiUrl: e.target.value,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
className="bg-[#111111] px-3 py-2 flex items-center overflow-hidden border border-[#1C1C1C] text-white rounded-lg text-sm"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col space-y-1">
|
<div className="flex flex-col space-y-1">
|
||||||
<p className="text-white/70 text-sm">GROQ API Key</p>
|
<p className="text-black/70 dark:text-white/70 text-sm">
|
||||||
<input
|
GROQ API Key
|
||||||
|
</p>
|
||||||
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="GROQ API Key"
|
placeholder="GROQ API Key"
|
||||||
defaultValue={config.groqApiKey}
|
defaultValue={config.groqApiKey}
|
||||||
@@ -378,18 +438,17 @@ const SettingsDialog = ({
|
|||||||
groqApiKey: e.target.value,
|
groqApiKey: e.target.value,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
className="bg-[#111111] px-3 py-2 flex items-center overflow-hidden border border-[#1C1C1C] text-white rounded-lg text-sm"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{isLoading && (
|
{isLoading && (
|
||||||
<div className="w-full flex items-center justify-center mt-6 text-white/70 py-6">
|
<div className="w-full flex items-center justify-center mt-6 text-black/70 dark:text-white/70 py-6">
|
||||||
<RefreshCcw className="animate-spin" />
|
<RefreshCcw className="animate-spin" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="w-full mt-6 space-y-2">
|
<div className="w-full mt-6 space-y-2">
|
||||||
<p className="text-xs text-white/50">
|
<p className="text-xs text-black/50 dark:text-white/50">
|
||||||
We'll refresh the page after updating the settings.
|
We'll refresh the page after updating the settings.
|
||||||
</p>
|
</p>
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -4,10 +4,16 @@ import { cn } from '@/lib/utils';
|
|||||||
import { BookOpenText, Home, Search, SquarePen, Settings } from 'lucide-react';
|
import { BookOpenText, Home, Search, SquarePen, Settings } from 'lucide-react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { useSelectedLayoutSegments } from 'next/navigation';
|
import { useSelectedLayoutSegments } from 'next/navigation';
|
||||||
import React, { Fragment, useState } from 'react';
|
import React, { useState, type ReactNode } from 'react';
|
||||||
import Layout from './Layout';
|
import Layout from './Layout';
|
||||||
import { Dialog, Transition } from '@headlessui/react';
|
|
||||||
import SettingsDialog from './SettingsDialog';
|
import SettingsDialog from './SettingsDialog';
|
||||||
|
import ThemeSwitcher from './theme/Switcher';
|
||||||
|
|
||||||
|
const VerticalIconContainer = ({ children }: { children: ReactNode }) => {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center gap-y-3 w-full">{children}</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const Sidebar = ({ children }: { children: React.ReactNode }) => {
|
const Sidebar = ({ children }: { children: React.ReactNode }) => {
|
||||||
const segments = useSelectedLayoutSegments();
|
const segments = useSelectedLayoutSegments();
|
||||||
@@ -38,31 +44,35 @@ const Sidebar = ({ children }: { children: React.ReactNode }) => {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="hidden lg:fixed lg:inset-y-0 lg:z-50 lg:flex lg:w-20 lg:flex-col">
|
<div className="hidden lg:fixed lg:inset-y-0 lg:z-50 lg:flex lg:w-20 lg:flex-col">
|
||||||
<div className="flex grow flex-col items-center justify-between gap-y-5 overflow-y-auto bg-[#111111] px-2 py-8">
|
<div className="flex grow flex-col items-center justify-between gap-y-5 overflow-y-auto bg-light-secondary dark:bg-dark-secondary px-2 py-8">
|
||||||
<a href="/">
|
<a href="/">
|
||||||
<SquarePen className="text-white cursor-pointer" />
|
<SquarePen className="cursor-pointer" />
|
||||||
</a>
|
</a>
|
||||||
<div className="flex flex-col items-center gap-y-3 w-full">
|
<VerticalIconContainer>
|
||||||
{navLinks.map((link, i) => (
|
{navLinks.map((link, i) => (
|
||||||
<Link
|
<Link
|
||||||
key={i}
|
key={i}
|
||||||
href={link.href}
|
href={link.href}
|
||||||
className={cn(
|
className={cn(
|
||||||
'relative flex flex-row items-center justify-center cursor-pointer hover:bg-white/10 hover:text-white duration-150 transition w-full py-2 rounded-lg',
|
'relative flex flex-row items-center justify-center cursor-pointer hover:bg-black/10 dark:hover:bg-white/10 duration-150 transition w-full py-2 rounded-lg',
|
||||||
link.active ? 'text-white' : 'text-white/70',
|
link.active
|
||||||
|
? 'text-black dark:text-white'
|
||||||
|
: 'text-black/70 dark:text-white/70',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<link.icon />
|
<link.icon />
|
||||||
{link.active && (
|
{link.active && (
|
||||||
<div className="absolute right-0 -mr-2 h-full w-1 rounded-l-lg bg-white" />
|
<div className="absolute right-0 -mr-2 h-full w-1 rounded-l-lg bg-black dark:bg-white" />
|
||||||
)}
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
</div>
|
</VerticalIconContainer>
|
||||||
|
|
||||||
<Settings
|
<Settings
|
||||||
onClick={() => setIsSettingsOpen(!isSettingsOpen)}
|
onClick={() => setIsSettingsOpen(!isSettingsOpen)}
|
||||||
className="text-white cursor-pointer"
|
className="cursor-pointer"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SettingsDialog
|
<SettingsDialog
|
||||||
isOpen={isSettingsOpen}
|
isOpen={isSettingsOpen}
|
||||||
setIsOpen={setIsSettingsOpen}
|
setIsOpen={setIsSettingsOpen}
|
||||||
@@ -70,18 +80,20 @@ const Sidebar = ({ children }: { children: React.ReactNode }) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="fixed bottom-0 w-full z-50 flex flex-row items-center gap-x-6 bg-[#111111] px-4 py-4 shadow-sm lg:hidden">
|
<div className="fixed bottom-0 w-full z-50 flex flex-row items-center gap-x-6 bg-light-primary dark:bg-dark-primary px-4 py-4 shadow-sm lg:hidden">
|
||||||
{navLinks.map((link, i) => (
|
{navLinks.map((link, i) => (
|
||||||
<Link
|
<Link
|
||||||
href={link.href}
|
href={link.href}
|
||||||
key={i}
|
key={i}
|
||||||
className={cn(
|
className={cn(
|
||||||
'relative flex flex-col items-center space-y-1 text-center w-full',
|
'relative flex flex-col items-center space-y-1 text-center w-full',
|
||||||
link.active ? 'text-white' : 'text-white/70',
|
link.active
|
||||||
|
? 'text-black dark:text-white'
|
||||||
|
: 'text-black dark:text-white/70',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{link.active && (
|
{link.active && (
|
||||||
<div className="absolute top-0 -mt-4 h-1 w-full rounded-b-lg bg-white" />
|
<div className="absolute top-0 -mt-4 h-1 w-full rounded-b-lg bg-black dark:bg-white" />
|
||||||
)}
|
)}
|
||||||
<link.icon />
|
<link.icon />
|
||||||
<p className="text-xs">{link.label}</p>
|
<p className="text-xs">{link.label}</p>
|
||||||
|
|||||||
16
ui/components/theme/Provider.tsx
Normal file
16
ui/components/theme/Provider.tsx
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
'use client';
|
||||||
|
import { ThemeProvider } from 'next-themes';
|
||||||
|
|
||||||
|
const ThemeProviderComponent = ({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<ThemeProvider attribute="class" enableSystem={false} defaultTheme="dark">
|
||||||
|
{children}
|
||||||
|
</ThemeProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ThemeProviderComponent;
|
||||||
62
ui/components/theme/Switcher.tsx
Normal file
62
ui/components/theme/Switcher.tsx
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
'use client';
|
||||||
|
import { useTheme } from 'next-themes';
|
||||||
|
import { SunIcon, MoonIcon, MonitorIcon } from 'lucide-react';
|
||||||
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
import { Select } from '../SettingsDialog';
|
||||||
|
|
||||||
|
type Theme = 'dark' | 'light' | 'system';
|
||||||
|
|
||||||
|
const ThemeSwitcher = ({ className }: { className?: string }) => {
|
||||||
|
const [mounted, setMounted] = useState(false);
|
||||||
|
|
||||||
|
const { theme, setTheme } = useTheme();
|
||||||
|
|
||||||
|
const isTheme = useCallback((t: Theme) => t === theme, [theme]);
|
||||||
|
|
||||||
|
const handleThemeSwitch = (theme: Theme) => {
|
||||||
|
setTheme(theme);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isTheme('system')) {
|
||||||
|
const preferDarkScheme = window.matchMedia(
|
||||||
|
'(prefers-color-scheme: dark)',
|
||||||
|
);
|
||||||
|
|
||||||
|
const detectThemeChange = (event: MediaQueryListEvent) => {
|
||||||
|
const theme: Theme = event.matches ? 'dark' : 'light';
|
||||||
|
setTheme(theme);
|
||||||
|
};
|
||||||
|
|
||||||
|
preferDarkScheme.addEventListener('change', detectThemeChange);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
preferDarkScheme.removeEventListener('change', detectThemeChange);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [isTheme, setTheme, theme]);
|
||||||
|
|
||||||
|
// Avoid Hydration Mismatch
|
||||||
|
if (!mounted) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
className={className}
|
||||||
|
value={theme}
|
||||||
|
onChange={(e) => handleThemeSwitch(e.target.value as Theme)}
|
||||||
|
options={[
|
||||||
|
{ value: 'light', label: 'Light' },
|
||||||
|
{ value: 'dark', label: 'Dark' }
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ThemeSwitcher;
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
import { Message } from '@/components/ChatWindow';
|
import { Message } from '@/components/ChatWindow';
|
||||||
import { clientFetch } from '@/lib/utils';
|
|
||||||
|
|
||||||
export const getSuggestions = async (chatHisory: Message[]) => {
|
export const getSuggestions = async (chatHisory: Message[]) => {
|
||||||
const chatModel = localStorage.getItem('chatModel');
|
const chatModel = localStorage.getItem('chatModel');
|
||||||
const chatModelProvider = localStorage.getItem('chatModelProvider');
|
const chatModelProvider = localStorage.getItem('chatModelProvider');
|
||||||
|
|
||||||
const res = await clientFetch('/suggestions', {
|
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/suggestions`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
interface Config {
|
|
||||||
GENERAL: {
|
|
||||||
NEXT_PUBLIC_SUPER_SECRET_KEY: string;
|
|
||||||
NEXT_PUBLIC_API_URL: string;
|
|
||||||
NEXT_PUBLIC_WS_URL: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const loadEnv = () => {
|
|
||||||
return {
|
|
||||||
GENERAL: {
|
|
||||||
NEXT_PUBLIC_SUPER_SECRET_KEY: process.env.NEXT_PUBLIC_SUPER_SECRET_KEY!,
|
|
||||||
NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL!,
|
|
||||||
NEXT_PUBLIC_WS_URL: process.env.NEXT_PUBLIC_WS_URL!,
|
|
||||||
},
|
|
||||||
} as Config;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getAccessKey = () =>
|
|
||||||
loadEnv().GENERAL.NEXT_PUBLIC_SUPER_SECRET_KEY;
|
|
||||||
|
|
||||||
export const getBackendURL = () => loadEnv().GENERAL.NEXT_PUBLIC_API_URL;
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import clsx, { ClassValue } from 'clsx';
|
import clsx, { ClassValue } from 'clsx';
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
import { getAccessKey, getBackendURL } from './config';
|
|
||||||
|
|
||||||
export const cn = (...classes: ClassValue[]) => twMerge(clsx(...classes));
|
export const cn = (...classes: ClassValue[]) => twMerge(clsx(...classes));
|
||||||
|
|
||||||
@@ -20,20 +19,3 @@ export const formatTimeDifference = (date1: Date, date2: Date): string => {
|
|||||||
else
|
else
|
||||||
return `${Math.floor(diffInSeconds / 31536000)} year${Math.floor(diffInSeconds / 31536000) !== 1 ? 's' : ''}`;
|
return `${Math.floor(diffInSeconds / 31536000)} year${Math.floor(diffInSeconds / 31536000) !== 1 ? 's' : ''}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const clientFetch = async (path: string, payload: any): Promise<any> => {
|
|
||||||
let headers = payload.headers;
|
|
||||||
const url = `${getBackendURL()}${path}`;
|
|
||||||
const secretToken = getAccessKey();
|
|
||||||
|
|
||||||
if (secretToken) {
|
|
||||||
if (headers == null) {
|
|
||||||
headers = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
headers['Authorization'] = `Bearer ${secretToken}`;
|
|
||||||
payload.headers = headers;
|
|
||||||
}
|
|
||||||
|
|
||||||
return await fetch(url, payload);
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "perplexica-frontend",
|
"name": "perplexica-frontend",
|
||||||
"version": "1.5.0",
|
"version": "1.6.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"author": "ItzCrazyKns",
|
"author": "ItzCrazyKns",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -20,6 +20,7 @@
|
|||||||
"lucide-react": "^0.363.0",
|
"lucide-react": "^0.363.0",
|
||||||
"markdown-to-jsx": "^7.4.5",
|
"markdown-to-jsx": "^7.4.5",
|
||||||
"next": "14.1.4",
|
"next": "14.1.4",
|
||||||
|
"next-themes": "^0.3.0",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
"react-text-to-speech": "^0.14.5",
|
"react-text-to-speech": "^0.14.5",
|
||||||
|
|||||||
@@ -1,4 +1,17 @@
|
|||||||
import type { Config } from 'tailwindcss';
|
import type { Config } from 'tailwindcss';
|
||||||
|
import type { DefaultColors } from 'tailwindcss/types/generated/colors';
|
||||||
|
|
||||||
|
const themeDark = (colors: DefaultColors) => ({
|
||||||
|
50: '#0a0a0a',
|
||||||
|
100: '#111111',
|
||||||
|
200: '#1c1c1c',
|
||||||
|
});
|
||||||
|
|
||||||
|
const themeLight = (colors: DefaultColors) => ({
|
||||||
|
50: '#fcfcf9',
|
||||||
|
100: '#f3f3ee',
|
||||||
|
200: '#e8e8e3',
|
||||||
|
});
|
||||||
|
|
||||||
const config: Config = {
|
const config: Config = {
|
||||||
content: [
|
content: [
|
||||||
@@ -6,8 +19,33 @@ const config: Config = {
|
|||||||
'./components/**/*.{js,ts,jsx,tsx,mdx}',
|
'./components/**/*.{js,ts,jsx,tsx,mdx}',
|
||||||
'./app/**/*.{js,ts,jsx,tsx,mdx}',
|
'./app/**/*.{js,ts,jsx,tsx,mdx}',
|
||||||
],
|
],
|
||||||
|
darkMode: 'class',
|
||||||
theme: {
|
theme: {
|
||||||
extend: {},
|
extend: {
|
||||||
|
borderColor: ({ colors }) => {
|
||||||
|
return {
|
||||||
|
light: themeLight(colors),
|
||||||
|
dark: themeDark(colors),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
colors: ({ colors }) => {
|
||||||
|
const colorsDark = themeDark(colors);
|
||||||
|
const colorsLight = themeLight(colors);
|
||||||
|
|
||||||
|
return {
|
||||||
|
dark: {
|
||||||
|
primary: colorsDark[50],
|
||||||
|
secondary: colorsDark[100],
|
||||||
|
...colorsDark,
|
||||||
|
},
|
||||||
|
light: {
|
||||||
|
primary: colorsLight[50],
|
||||||
|
secondary: colorsLight[100],
|
||||||
|
...colorsLight,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
plugins: [require('@tailwindcss/typography')],
|
plugins: [require('@tailwindcss/typography')],
|
||||||
};
|
};
|
||||||
|
|||||||
26
ui/yarn.lock
26
ui/yarn.lock
@@ -2244,6 +2244,11 @@ natural-compare@^1.4.0:
|
|||||||
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
|
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
|
||||||
integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
|
integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
|
||||||
|
|
||||||
|
next-themes@^0.3.0:
|
||||||
|
version "0.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/next-themes/-/next-themes-0.3.0.tgz#b4d2a866137a67d42564b07f3a3e720e2ff3871a"
|
||||||
|
integrity sha512-/QHIrsYpd6Kfk7xakK4svpDI5mmXP0gfvCoJdGpZQ2TOrQZmsW0QxjaiLn8wbIKjtm4BTSqLoix4lxYYOnLJ/w==
|
||||||
|
|
||||||
next@14.1.4:
|
next@14.1.4:
|
||||||
version "14.1.4"
|
version "14.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/next/-/next-14.1.4.tgz#203310f7310578563fd5c961f0db4729ce7a502d"
|
resolved "https://registry.yarnpkg.com/next/-/next-14.1.4.tgz#203310f7310578563fd5c961f0db4729ce7a502d"
|
||||||
@@ -2854,8 +2859,16 @@ streamsearch@^1.1.0:
|
|||||||
resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764"
|
resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764"
|
||||||
integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==
|
integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==
|
||||||
|
|
||||||
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0:
|
"string-width-cjs@npm:string-width@^4.2.0":
|
||||||
name string-width-cjs
|
version "4.2.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||||
|
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||||
|
dependencies:
|
||||||
|
emoji-regex "^8.0.0"
|
||||||
|
is-fullwidth-code-point "^3.0.0"
|
||||||
|
strip-ansi "^6.0.1"
|
||||||
|
|
||||||
|
string-width@^4.1.0:
|
||||||
version "4.2.3"
|
version "4.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||||
@@ -2919,7 +2932,14 @@ string.prototype.trimstart@^1.0.8:
|
|||||||
define-properties "^1.2.1"
|
define-properties "^1.2.1"
|
||||||
es-object-atoms "^1.0.0"
|
es-object-atoms "^1.0.0"
|
||||||
|
|
||||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
|
||||||
|
version "6.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||||
|
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||||
|
dependencies:
|
||||||
|
ansi-regex "^5.0.1"
|
||||||
|
|
||||||
|
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||||
version "6.0.1"
|
version "6.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||||
|
|||||||
192
yarn.lock
192
yarn.lock
@@ -79,24 +79,6 @@
|
|||||||
uuid "^9.0.0"
|
uuid "^9.0.0"
|
||||||
zod "^3.22.3"
|
zod "^3.22.3"
|
||||||
|
|
||||||
"@langchain/core@>0.1.56 <0.3.0", "@langchain/core@>0.1.56 <0.3.0":
|
|
||||||
version "0.2.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/@langchain/core/-/core-0.2.0.tgz#19c6374a5ad80daf8e14cb58582bc988109a1403"
|
|
||||||
integrity sha512-UbCJUp9eh2JXd9AW/vhPbTgtZoMgTqJgSan5Wf/EP27X8JM65lWdCOpJW+gHyBXvabbyrZz3/EGaptTUL5gutw==
|
|
||||||
dependencies:
|
|
||||||
ansi-styles "^5.0.0"
|
|
||||||
camelcase "6"
|
|
||||||
decamelize "1.2.0"
|
|
||||||
js-tiktoken "^1.0.12"
|
|
||||||
langsmith "~0.1.7"
|
|
||||||
ml-distance "^4.0.0"
|
|
||||||
mustache "^4.2.0"
|
|
||||||
p-queue "^6.6.2"
|
|
||||||
p-retry "4"
|
|
||||||
uuid "^9.0.0"
|
|
||||||
zod "^3.22.4"
|
|
||||||
zod-to-json-schema "^3.22.3"
|
|
||||||
|
|
||||||
"@langchain/core@~0.1.44", "@langchain/core@~0.1.45":
|
"@langchain/core@~0.1.44", "@langchain/core@~0.1.45":
|
||||||
version "0.1.52"
|
version "0.1.52"
|
||||||
resolved "https://registry.yarnpkg.com/@langchain/core/-/core-0.1.52.tgz#7619310b83ffa841628efe2e1eda873ca714d068"
|
resolved "https://registry.yarnpkg.com/@langchain/core/-/core-0.1.52.tgz#7619310b83ffa841628efe2e1eda873ca714d068"
|
||||||
@@ -114,32 +96,6 @@
|
|||||||
zod "^3.22.4"
|
zod "^3.22.4"
|
||||||
zod-to-json-schema "^3.22.3"
|
zod-to-json-schema "^3.22.3"
|
||||||
|
|
||||||
"@langchain/google-common@~0.0.15":
|
|
||||||
version "0.0.16"
|
|
||||||
resolved "https://registry.yarnpkg.com/@langchain/google-common/-/google-common-0.0.16.tgz#e2ff43eaebcf7bea84a067f8bdaf7f01e23bc1c0"
|
|
||||||
integrity sha512-eQMdqEYfzcavkE5Cpk7LCUlFx2Gb+skNZci/DlS2zot4XCSVg8QDIYOkL+PrXtTZBsp36SyOnNfzHUzdbU8cPA==
|
|
||||||
dependencies:
|
|
||||||
"@langchain/core" ">0.1.56 <0.3.0"
|
|
||||||
uuid "^9.0.0"
|
|
||||||
zod-to-json-schema "^3.22.4"
|
|
||||||
|
|
||||||
"@langchain/google-gauth@~0.0.16":
|
|
||||||
version "0.0.16"
|
|
||||||
resolved "https://registry.yarnpkg.com/@langchain/google-gauth/-/google-gauth-0.0.16.tgz#164c865c0d6363385f3375e54e2ed66c6ed06cfd"
|
|
||||||
integrity sha512-mp68iw/XA/lbBwh8+6vV7FFsibP595mt+OZdEFU9QewpUv99YVHH1FT+mNoQAI9p6uZpSHYQ3Iip70nIU976sw==
|
|
||||||
dependencies:
|
|
||||||
"@langchain/core" ">0.1.56 <0.3.0"
|
|
||||||
"@langchain/google-common" "~0.0.15"
|
|
||||||
google-auth-library "^8.9.0"
|
|
||||||
|
|
||||||
"@langchain/google-vertexai@^0.0.16":
|
|
||||||
version "0.0.16"
|
|
||||||
resolved "https://registry.yarnpkg.com/@langchain/google-vertexai/-/google-vertexai-0.0.16.tgz#388ddf21dc9537d4632acc5c046583fe9ac8022a"
|
|
||||||
integrity sha512-tJTyPxg3vYSqhNyqx6/UViPNdn3NPeZL29JqNen26x/w4JYYMpde0Dm20KCd5TCsbdUfrkk7tMyJZjr2e30jMg==
|
|
||||||
dependencies:
|
|
||||||
"@langchain/core" ">0.1.56 <0.3.0"
|
|
||||||
"@langchain/google-gauth" "~0.0.16"
|
|
||||||
|
|
||||||
"@langchain/openai@^0.0.25", "@langchain/openai@~0.0.19":
|
"@langchain/openai@^0.0.25", "@langchain/openai@~0.0.19":
|
||||||
version "0.0.25"
|
version "0.0.25"
|
||||||
resolved "https://registry.yarnpkg.com/@langchain/openai/-/openai-0.0.25.tgz#8332abea1e3acb9b1169f90636e518c0ee90622e"
|
resolved "https://registry.yarnpkg.com/@langchain/openai/-/openai-0.0.25.tgz#8332abea1e3acb9b1169f90636e518c0ee90622e"
|
||||||
@@ -401,13 +357,6 @@ acorn@^8.4.1:
|
|||||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a"
|
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a"
|
||||||
integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==
|
integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==
|
||||||
|
|
||||||
agent-base@6:
|
|
||||||
version "6.0.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
|
|
||||||
integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==
|
|
||||||
dependencies:
|
|
||||||
debug "4"
|
|
||||||
|
|
||||||
agentkeepalive@^4.2.1:
|
agentkeepalive@^4.2.1:
|
||||||
version "4.5.0"
|
version "4.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.5.0.tgz#2673ad1389b3c418c5a20c5d7364f93ca04be923"
|
resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.5.0.tgz#2673ad1389b3c418c5a20c5d7364f93ca04be923"
|
||||||
@@ -443,11 +392,6 @@ array-flatten@1.1.1:
|
|||||||
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
|
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
|
||||||
integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==
|
integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==
|
||||||
|
|
||||||
arrify@^2.0.0:
|
|
||||||
version "2.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa"
|
|
||||||
integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==
|
|
||||||
|
|
||||||
async@^3.2.3:
|
async@^3.2.3:
|
||||||
version "3.2.5"
|
version "3.2.5"
|
||||||
resolved "https://registry.yarnpkg.com/async/-/async-3.2.5.tgz#ebd52a8fdaf7a2289a24df399f8d8485c8a46b66"
|
resolved "https://registry.yarnpkg.com/async/-/async-3.2.5.tgz#ebd52a8fdaf7a2289a24df399f8d8485c8a46b66"
|
||||||
@@ -515,16 +459,11 @@ base-64@^0.1.0:
|
|||||||
resolved "https://registry.yarnpkg.com/base-64/-/base-64-0.1.0.tgz#780a99c84e7d600260361511c4877613bf24f6bb"
|
resolved "https://registry.yarnpkg.com/base-64/-/base-64-0.1.0.tgz#780a99c84e7d600260361511c4877613bf24f6bb"
|
||||||
integrity sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA==
|
integrity sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA==
|
||||||
|
|
||||||
base64-js@^1.3.0, base64-js@^1.3.1, base64-js@^1.5.1:
|
base64-js@^1.3.1, base64-js@^1.5.1:
|
||||||
version "1.5.1"
|
version "1.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
|
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
|
||||||
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
|
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
|
||||||
|
|
||||||
bignumber.js@^9.0.0:
|
|
||||||
version "9.1.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.2.tgz#b7c4242259c008903b13707983b5f4bbd31eda0c"
|
|
||||||
integrity sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==
|
|
||||||
|
|
||||||
binary-extensions@^2.0.0, binary-extensions@^2.2.0:
|
binary-extensions@^2.0.0, binary-extensions@^2.2.0:
|
||||||
version "2.3.0"
|
version "2.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522"
|
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522"
|
||||||
@@ -577,11 +516,6 @@ braces@~3.0.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
fill-range "^7.0.1"
|
fill-range "^7.0.1"
|
||||||
|
|
||||||
buffer-equal-constant-time@1.0.1:
|
|
||||||
version "1.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
|
|
||||||
integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==
|
|
||||||
|
|
||||||
buffer@^5.5.0:
|
buffer@^5.5.0:
|
||||||
version "5.7.1"
|
version "5.7.1"
|
||||||
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
|
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
|
||||||
@@ -782,7 +716,7 @@ debug@2.6.9:
|
|||||||
dependencies:
|
dependencies:
|
||||||
ms "2.0.0"
|
ms "2.0.0"
|
||||||
|
|
||||||
debug@4, debug@^4:
|
debug@^4:
|
||||||
version "4.3.4"
|
version "4.3.4"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
|
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
|
||||||
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
|
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
|
||||||
@@ -853,13 +787,6 @@ dotenv@^16.4.5:
|
|||||||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f"
|
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f"
|
||||||
integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==
|
integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==
|
||||||
|
|
||||||
ecdsa-sig-formatter@1.0.11, ecdsa-sig-formatter@^1.0.11:
|
|
||||||
version "1.0.11"
|
|
||||||
resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf"
|
|
||||||
integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==
|
|
||||||
dependencies:
|
|
||||||
safe-buffer "^5.0.1"
|
|
||||||
|
|
||||||
ee-first@1.1.1:
|
ee-first@1.1.1:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
|
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
|
||||||
@@ -961,21 +888,11 @@ express@^4.19.2:
|
|||||||
utils-merge "1.0.1"
|
utils-merge "1.0.1"
|
||||||
vary "~1.1.2"
|
vary "~1.1.2"
|
||||||
|
|
||||||
extend@^3.0.2:
|
|
||||||
version "3.0.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
|
|
||||||
integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
|
|
||||||
|
|
||||||
fast-fifo@^1.1.0, fast-fifo@^1.2.0:
|
fast-fifo@^1.1.0, fast-fifo@^1.2.0:
|
||||||
version "1.3.2"
|
version "1.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/fast-fifo/-/fast-fifo-1.3.2.tgz#286e31de96eb96d38a97899815740ba2a4f3640c"
|
resolved "https://registry.yarnpkg.com/fast-fifo/-/fast-fifo-1.3.2.tgz#286e31de96eb96d38a97899815740ba2a4f3640c"
|
||||||
integrity sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==
|
integrity sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==
|
||||||
|
|
||||||
fast-text-encoding@^1.0.0:
|
|
||||||
version "1.0.6"
|
|
||||||
resolved "https://registry.yarnpkg.com/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz#0aa25f7f638222e3396d72bf936afcf1d42d6867"
|
|
||||||
integrity sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w==
|
|
||||||
|
|
||||||
fecha@^4.2.0:
|
fecha@^4.2.0:
|
||||||
version "4.2.3"
|
version "4.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/fecha/-/fecha-4.2.3.tgz#4d9ccdbc61e8629b259fdca67e65891448d569fd"
|
resolved "https://registry.yarnpkg.com/fecha/-/fecha-4.2.3.tgz#4d9ccdbc61e8629b259fdca67e65891448d569fd"
|
||||||
@@ -1068,24 +985,6 @@ function-bind@^1.1.2:
|
|||||||
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
|
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
|
||||||
integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
|
integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
|
||||||
|
|
||||||
gaxios@^5.0.0, gaxios@^5.0.1:
|
|
||||||
version "5.1.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/gaxios/-/gaxios-5.1.3.tgz#f7fa92da0fe197c846441e5ead2573d4979e9013"
|
|
||||||
integrity sha512-95hVgBRgEIRQQQHIbnxBXeHbW4TqFk4ZDJW7wmVtvYar72FdhRIo1UGOLS2eRAKCPEdPBWu+M7+A33D9CdX9rA==
|
|
||||||
dependencies:
|
|
||||||
extend "^3.0.2"
|
|
||||||
https-proxy-agent "^5.0.0"
|
|
||||||
is-stream "^2.0.0"
|
|
||||||
node-fetch "^2.6.9"
|
|
||||||
|
|
||||||
gcp-metadata@^5.3.0:
|
|
||||||
version "5.3.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/gcp-metadata/-/gcp-metadata-5.3.0.tgz#6f45eb473d0cb47d15001476b48b663744d25408"
|
|
||||||
integrity sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==
|
|
||||||
dependencies:
|
|
||||||
gaxios "^5.0.0"
|
|
||||||
json-bigint "^1.0.0"
|
|
||||||
|
|
||||||
get-intrinsic@^1.1.3, get-intrinsic@^1.2.4:
|
get-intrinsic@^1.1.3, get-intrinsic@^1.2.4:
|
||||||
version "1.2.4"
|
version "1.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd"
|
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd"
|
||||||
@@ -1109,28 +1008,6 @@ glob-parent@~5.1.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
is-glob "^4.0.1"
|
is-glob "^4.0.1"
|
||||||
|
|
||||||
google-auth-library@^8.9.0:
|
|
||||||
version "8.9.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/google-auth-library/-/google-auth-library-8.9.0.tgz#15a271eb2ec35d43b81deb72211bd61b1ef14dd0"
|
|
||||||
integrity sha512-f7aQCJODJFmYWN6PeNKzgvy9LI2tYmXnzpNDHEjG5sDNPgGb2FXQyTBnXeSH+PAtpKESFD+LmHw3Ox3mN7e1Fg==
|
|
||||||
dependencies:
|
|
||||||
arrify "^2.0.0"
|
|
||||||
base64-js "^1.3.0"
|
|
||||||
ecdsa-sig-formatter "^1.0.11"
|
|
||||||
fast-text-encoding "^1.0.0"
|
|
||||||
gaxios "^5.0.0"
|
|
||||||
gcp-metadata "^5.3.0"
|
|
||||||
gtoken "^6.1.0"
|
|
||||||
jws "^4.0.0"
|
|
||||||
lru-cache "^6.0.0"
|
|
||||||
|
|
||||||
google-p12-pem@^4.0.0:
|
|
||||||
version "4.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/google-p12-pem/-/google-p12-pem-4.0.1.tgz#82841798253c65b7dc2a4e5fe9df141db670172a"
|
|
||||||
integrity sha512-WPkN4yGtz05WZ5EhtlxNDWPhC4JIic6G8ePitwUWy4l+XPVYec+a0j0Ts47PDtW59y3RwAhUd9/h9ZZ63px6RQ==
|
|
||||||
dependencies:
|
|
||||||
node-forge "^1.3.1"
|
|
||||||
|
|
||||||
gopd@^1.0.1:
|
gopd@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c"
|
resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c"
|
||||||
@@ -1138,15 +1015,6 @@ gopd@^1.0.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
get-intrinsic "^1.1.3"
|
get-intrinsic "^1.1.3"
|
||||||
|
|
||||||
gtoken@^6.1.0:
|
|
||||||
version "6.1.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-6.1.2.tgz#aeb7bdb019ff4c3ba3ac100bbe7b6e74dce0e8bc"
|
|
||||||
integrity sha512-4ccGpzz7YAr7lxrT2neugmXQ3hP9ho2gcaityLVkiUecAiwiy60Ii8gRbZeOsXV19fYaRjgBSshs8kXw+NKCPQ==
|
|
||||||
dependencies:
|
|
||||||
gaxios "^5.0.1"
|
|
||||||
google-p12-pem "^4.0.0"
|
|
||||||
jws "^4.0.0"
|
|
||||||
|
|
||||||
guid-typescript@^1.0.9:
|
guid-typescript@^1.0.9:
|
||||||
version "1.0.9"
|
version "1.0.9"
|
||||||
resolved "https://registry.yarnpkg.com/guid-typescript/-/guid-typescript-1.0.9.tgz#e35f77003535b0297ea08548f5ace6adb1480ddc"
|
resolved "https://registry.yarnpkg.com/guid-typescript/-/guid-typescript-1.0.9.tgz#e35f77003535b0297ea08548f5ace6adb1480ddc"
|
||||||
@@ -1192,14 +1060,6 @@ http-errors@2.0.0:
|
|||||||
statuses "2.0.1"
|
statuses "2.0.1"
|
||||||
toidentifier "1.0.1"
|
toidentifier "1.0.1"
|
||||||
|
|
||||||
https-proxy-agent@^5.0.0:
|
|
||||||
version "5.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6"
|
|
||||||
integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==
|
|
||||||
dependencies:
|
|
||||||
agent-base "6"
|
|
||||||
debug "4"
|
|
||||||
|
|
||||||
humanize-ms@^1.2.1:
|
humanize-ms@^1.2.1:
|
||||||
version "1.2.1"
|
version "1.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed"
|
resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed"
|
||||||
@@ -1283,13 +1143,6 @@ is-stream@^2.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077"
|
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077"
|
||||||
integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==
|
integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==
|
||||||
|
|
||||||
js-tiktoken@^1.0.12:
|
|
||||||
version "1.0.12"
|
|
||||||
resolved "https://registry.yarnpkg.com/js-tiktoken/-/js-tiktoken-1.0.12.tgz#af0f5cf58e5e7318240d050c8413234019424211"
|
|
||||||
integrity sha512-L7wURW1fH9Qaext0VzaUDpFGVQgjkdE3Dgsy9/+yXyGEpBKnylTd0mU0bfbNkKDlXRb6TEsZkwuflu1B8uQbJQ==
|
|
||||||
dependencies:
|
|
||||||
base64-js "^1.5.1"
|
|
||||||
|
|
||||||
js-tiktoken@^1.0.7, js-tiktoken@^1.0.8:
|
js-tiktoken@^1.0.7, js-tiktoken@^1.0.8:
|
||||||
version "1.0.10"
|
version "1.0.10"
|
||||||
resolved "https://registry.yarnpkg.com/js-tiktoken/-/js-tiktoken-1.0.10.tgz#2b343ec169399dcee8f9ef9807dbd4fafd3b30dc"
|
resolved "https://registry.yarnpkg.com/js-tiktoken/-/js-tiktoken-1.0.10.tgz#2b343ec169399dcee8f9ef9807dbd4fafd3b30dc"
|
||||||
@@ -1304,35 +1157,11 @@ js-yaml@^4.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
argparse "^2.0.1"
|
argparse "^2.0.1"
|
||||||
|
|
||||||
json-bigint@^1.0.0:
|
|
||||||
version "1.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/json-bigint/-/json-bigint-1.0.0.tgz#ae547823ac0cad8398667f8cd9ef4730f5b01ff1"
|
|
||||||
integrity sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==
|
|
||||||
dependencies:
|
|
||||||
bignumber.js "^9.0.0"
|
|
||||||
|
|
||||||
jsonpointer@^5.0.1:
|
jsonpointer@^5.0.1:
|
||||||
version "5.0.1"
|
version "5.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-5.0.1.tgz#2110e0af0900fd37467b5907ecd13a7884a1b559"
|
resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-5.0.1.tgz#2110e0af0900fd37467b5907ecd13a7884a1b559"
|
||||||
integrity sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==
|
integrity sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==
|
||||||
|
|
||||||
jwa@^2.0.0:
|
|
||||||
version "2.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/jwa/-/jwa-2.0.0.tgz#a7e9c3f29dae94027ebcaf49975c9345593410fc"
|
|
||||||
integrity sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==
|
|
||||||
dependencies:
|
|
||||||
buffer-equal-constant-time "1.0.1"
|
|
||||||
ecdsa-sig-formatter "1.0.11"
|
|
||||||
safe-buffer "^5.0.1"
|
|
||||||
|
|
||||||
jws@^4.0.0:
|
|
||||||
version "4.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/jws/-/jws-4.0.0.tgz#2d4e8cf6a318ffaa12615e9dec7e86e6c97310f4"
|
|
||||||
integrity sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==
|
|
||||||
dependencies:
|
|
||||||
jwa "^2.0.0"
|
|
||||||
safe-buffer "^5.0.1"
|
|
||||||
|
|
||||||
kuler@^2.0.0:
|
kuler@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/kuler/-/kuler-2.0.0.tgz#e2c570a3800388fb44407e851531c1d670b061b3"
|
resolved "https://registry.yarnpkg.com/kuler/-/kuler-2.0.0.tgz#e2c570a3800388fb44407e851531c1d670b061b3"
|
||||||
@@ -1520,11 +1349,6 @@ ms@2.1.3, ms@^2.0.0, ms@^2.1.1:
|
|||||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
|
||||||
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
|
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
|
||||||
|
|
||||||
mustache@^4.2.0:
|
|
||||||
version "4.2.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/mustache/-/mustache-4.2.0.tgz#e5892324d60a12ec9c2a73359edca52972bf6f64"
|
|
||||||
integrity sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==
|
|
||||||
|
|
||||||
napi-build-utils@^1.0.1:
|
napi-build-utils@^1.0.1:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806"
|
resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806"
|
||||||
@@ -1552,18 +1376,13 @@ node-domexception@1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5"
|
resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5"
|
||||||
integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==
|
integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==
|
||||||
|
|
||||||
node-fetch@^2.6.7, node-fetch@^2.6.9:
|
node-fetch@^2.6.7:
|
||||||
version "2.7.0"
|
version "2.7.0"
|
||||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d"
|
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d"
|
||||||
integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==
|
integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==
|
||||||
dependencies:
|
dependencies:
|
||||||
whatwg-url "^5.0.0"
|
whatwg-url "^5.0.0"
|
||||||
|
|
||||||
node-forge@^1.3.1:
|
|
||||||
version "1.3.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3"
|
|
||||||
integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==
|
|
||||||
|
|
||||||
nodemon@^3.1.0:
|
nodemon@^3.1.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-3.1.0.tgz#ff7394f2450eb6a5e96fe4180acd5176b29799c9"
|
resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-3.1.0.tgz#ff7394f2450eb6a5e96fe4180acd5176b29799c9"
|
||||||
@@ -2260,11 +2079,6 @@ zod-to-json-schema@^3.22.3:
|
|||||||
resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.22.5.tgz#3646e81cfc318dbad2a22519e5ce661615418673"
|
resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.22.5.tgz#3646e81cfc318dbad2a22519e5ce661615418673"
|
||||||
integrity sha512-+akaPo6a0zpVCCseDed504KBJUQpEW5QZw7RMneNmKw+fGaML1Z9tUNLnHHAC8x6dzVRO1eB2oEMyZRnuBZg7Q==
|
integrity sha512-+akaPo6a0zpVCCseDed504KBJUQpEW5QZw7RMneNmKw+fGaML1Z9tUNLnHHAC8x6dzVRO1eB2oEMyZRnuBZg7Q==
|
||||||
|
|
||||||
zod-to-json-schema@^3.22.4:
|
|
||||||
version "3.23.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.23.0.tgz#4fc60e88d3c709eedbfaae3f92f8a7bf786469f2"
|
|
||||||
integrity sha512-az0uJ243PxsRIa2x1WmNE/pnuA05gUq/JB8Lwe1EDCCL/Fz9MgjYQ0fPlyc2Tcv6aF2ZA7WM5TWaRZVEFaAIag==
|
|
||||||
|
|
||||||
zod@^3.22.3, zod@^3.22.4:
|
zod@^3.22.3, zod@^3.22.4:
|
||||||
version "3.22.4"
|
version "3.22.4"
|
||||||
resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.4.tgz#f31c3a9386f61b1f228af56faa9255e845cf3fff"
|
resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.4.tgz#f31c3a9386f61b1f228af56faa9255e845cf3fff"
|
||||||
|
|||||||
Reference in New Issue
Block a user