Compare commits
15
Commits
main
...
v0.7.8-hanzo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c4e841c2b9 | ||
|
|
46657ba812 | ||
|
|
e6737188c1 | ||
|
|
2c832aa8b9 | ||
|
|
fd3d031cc7 | ||
|
|
7e6c10299f | ||
|
|
0f8eea1e3a | ||
|
|
751b8122ab | ||
|
|
32d3485663 | ||
|
|
7b05ada0ca | ||
|
|
88cd45da4b | ||
|
|
d8b20d61e9 | ||
|
|
91e9711e6b | ||
|
|
d0b64340fe | ||
|
|
fb7787c87a |
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(git add:*)",
|
||||
"Bash(git rm:*)",
|
||||
"Bash(git rebase:*)",
|
||||
"Bash(git -C /Users/z/work/librechat/LibreChat log --oneline -10)",
|
||||
"Bash(git -C /Users/z/work/librechat/LibreChat log --oneline 97085073..HEAD)",
|
||||
"Bash(ls:*)",
|
||||
"Bash(grep:*)",
|
||||
"Bash(git commit:*)",
|
||||
"Bash(git checkout:*)",
|
||||
"Bash(npm install)",
|
||||
"Bash(rm:*)",
|
||||
"Bash(curl:*)",
|
||||
"Bash(kill:*)",
|
||||
"Bash(npm run build:data-provider:*)",
|
||||
"Bash(npm run build:data-schemas:*)",
|
||||
"Bash(npm run build:api:*)",
|
||||
"Bash(pgrep:*)",
|
||||
"Bash(brew services:*)",
|
||||
"Bash(find:*)",
|
||||
"Bash(git fetch:*)",
|
||||
"Bash(git merge:*)",
|
||||
"Bash(git push:*)",
|
||||
"Bash(pnpm install:*)"
|
||||
],
|
||||
"deny": []
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ services:
|
||||
# - /var/run/docker.sock:/var/run/docker.sock
|
||||
environment:
|
||||
- HOST=0.0.0.0
|
||||
- MONGO_URI=mongodb://mongodb:27017/LibreChat
|
||||
- MONGO_URI=mongodb://mongodb:27017/HanzoChat
|
||||
# - CHATGPT_REVERSE_PROXY=http://host.docker.internal:8080/api/conversation # if you are hosting your own chatgpt reverse proxy with docker
|
||||
# - OPENAI_REVERSE_PROXY=http://host.docker.internal:8070/v1/chat/completions # if you are hosting your own chatgpt reverse proxy with docker
|
||||
- MEILI_HOST=http://meilisearch:7700
|
||||
|
||||
+20
-20
@@ -1,10 +1,10 @@
|
||||
#=====================================================================#
|
||||
# LibreChat Configuration #
|
||||
# Hanzo Chat Configuration #
|
||||
#=====================================================================#
|
||||
# Please refer to the reference documentation for assistance #
|
||||
# with configuring your LibreChat environment. #
|
||||
# with configuring your Hanzo Chat environment. #
|
||||
# #
|
||||
# https://www.librechat.ai/docs/configuration/dotenv #
|
||||
# https://docs.hanzo.ai/chat/configuration/dotenv #
|
||||
#=====================================================================#
|
||||
|
||||
#==================================================#
|
||||
@@ -14,7 +14,7 @@
|
||||
HOST=localhost
|
||||
PORT=3080
|
||||
|
||||
MONGO_URI=mongodb://127.0.0.1:27017/LibreChat
|
||||
MONGO_URI=mongodb://127.0.0.1:27017/HanzoChat
|
||||
|
||||
DOMAIN_CLIENT=http://localhost:3080
|
||||
DOMAIN_SERVER=http://localhost:3080
|
||||
@@ -52,7 +52,7 @@ DEBUG_CONSOLE=false
|
||||
#===============#
|
||||
# Use an absolute path, a relative path, or a URL
|
||||
|
||||
# CONFIG_PATH="/alternative/path/to/librechat.yaml"
|
||||
# CONFIG_PATH="/alternative/path/to/chat.yaml"
|
||||
|
||||
#===================================================#
|
||||
# Endpoints #
|
||||
@@ -63,9 +63,9 @@ DEBUG_CONSOLE=false
|
||||
PROXY=
|
||||
|
||||
#===================================#
|
||||
# Known Endpoints - librechat.yaml #
|
||||
# Known Endpoints - chat.yaml #
|
||||
#===================================#
|
||||
# https://www.librechat.ai/docs/configuration/librechat_yaml/ai_endpoints
|
||||
# https://docs.hanzo.ai/chat/configuration/ai_endpoints
|
||||
|
||||
# ANYSCALE_API_KEY=
|
||||
# APIPIE_API_KEY=
|
||||
@@ -96,8 +96,8 @@ ANTHROPIC_API_KEY=user_provided
|
||||
#============#
|
||||
|
||||
# Note: these variables are DEPRECATED
|
||||
# Use the `librechat.yaml` configuration for `azureOpenAI` instead
|
||||
# You may also continue to use them if you opt out of using the `librechat.yaml` configuration
|
||||
# Use the `chat.yaml` configuration for `azureOpenAI` instead
|
||||
# You may also continue to use them if you opt out of using the `chat.yaml` configuration
|
||||
|
||||
# AZURE_OPENAI_DEFAULT_MODEL=gpt-3.5-turbo # Deprecated
|
||||
# AZURE_OPENAI_MODELS=gpt-3.5-turbo,gpt-4 # Deprecated
|
||||
@@ -207,7 +207,7 @@ ASSISTANTS_API_KEY=user_provided
|
||||
# The models for Azure Assistants are also determined by your Azure OpenAI configuration.
|
||||
|
||||
# More info, including how to enable use of Assistants with Azure here:
|
||||
# https://www.librechat.ai/docs/configuration/librechat_yaml/ai_endpoints/azure#using-assistants-with-azure
|
||||
# https://docs.hanzo.ai/chat/configuration/ai_endpoints/azure#using-assistants-with-azure
|
||||
|
||||
#============#
|
||||
# Plugins #
|
||||
@@ -320,7 +320,7 @@ TTS_API_KEY=
|
||||
#==================================================#
|
||||
# RAG #
|
||||
#==================================================#
|
||||
# More info: https://www.librechat.ai/docs/configuration/rag_api
|
||||
# More info: https://docs.hanzo.ai/chat/configuration/rag_api
|
||||
|
||||
# RAG_OPENAI_BASEURL=
|
||||
# RAG_OPENAI_API_KEY=
|
||||
@@ -513,7 +513,7 @@ EMAIL_ALLOW_SELFSIGNED=
|
||||
EMAIL_USERNAME=
|
||||
EMAIL_PASSWORD=
|
||||
EMAIL_FROM_NAME=
|
||||
EMAIL_FROM=noreply@librechat.ai
|
||||
EMAIL_FROM=noreply@hanzo.ai
|
||||
|
||||
#========================#
|
||||
# Mailgun API #
|
||||
@@ -522,7 +522,7 @@ EMAIL_FROM=noreply@librechat.ai
|
||||
# MAILGUN_API_KEY=your-mailgun-api-key
|
||||
# MAILGUN_DOMAIN=mg.yourdomain.com
|
||||
# EMAIL_FROM=noreply@yourdomain.com
|
||||
# EMAIL_FROM_NAME="LibreChat"
|
||||
# EMAIL_FROM_NAME="Hanzo Chat"
|
||||
|
||||
# # Optional: For EU region
|
||||
# MAILGUN_HOST=https://api.eu.mailgun.net
|
||||
@@ -572,16 +572,16 @@ ALLOW_SHARED_LINKS_PUBLIC=true
|
||||
# STATIC_CACHE_MAX_AGE=172800
|
||||
# STATIC_CACHE_S_MAX_AGE=86400
|
||||
|
||||
# If you have another service in front of your LibreChat doing compression, disable express based compression here
|
||||
# If you have another service in front of your Hanzo Chat doing compression, disable express based compression here
|
||||
# DISABLE_COMPRESSION=true
|
||||
|
||||
#===================================================#
|
||||
# UI #
|
||||
#===================================================#
|
||||
|
||||
APP_TITLE=LibreChat
|
||||
APP_TITLE=Hanzo Chat
|
||||
# CUSTOM_FOOTER="My custom footer"
|
||||
HELP_AND_FAQ_URL=https://librechat.ai
|
||||
HELP_AND_FAQ_URL=https://hanzo.ai
|
||||
|
||||
# SHOW_BIRTHDAY_ICON=true
|
||||
|
||||
@@ -630,11 +630,11 @@ HELP_AND_FAQ_URL=https://librechat.ai
|
||||
OPENWEATHER_API_KEY=
|
||||
|
||||
#====================================#
|
||||
# LibreChat Code Interpreter API #
|
||||
# Hanzo Chat Code Interpreter API #
|
||||
#====================================#
|
||||
|
||||
# https://code.librechat.ai
|
||||
# LIBRECHAT_CODE_API_KEY=your-key
|
||||
# https://code.hanzo.ai
|
||||
# HANZO_CODE_API_KEY=your-key
|
||||
|
||||
#======================#
|
||||
# Web Search #
|
||||
@@ -644,7 +644,7 @@ OPENWEATHER_API_KEY=
|
||||
# Omit values to allow user to provide them.
|
||||
|
||||
# For more information on configuration values, see:
|
||||
# https://librechat.ai/docs/features/web_search
|
||||
# https://docs.hanzo.ai/chat/features/web_search
|
||||
|
||||
# Search Provider (Required)
|
||||
# SERPER_API_KEY=your_serper_api_key
|
||||
|
||||
@@ -42,7 +42,7 @@ Project maintainers have the right and responsibility to remove, edit, or reject
|
||||
- Install [MongoDB Community Edition](https://www.mongodb.com/docs/manual/administration/install-community/), ensure that `mongosh` connects to your local instance.
|
||||
- Run: `npx install playwright`, then `npx playwright install`.
|
||||
- Copy `config.local`: `cp e2e/config.local.example.ts e2e/config.local.ts`.
|
||||
- Copy `librechat.yaml`: `cp librechat.example.yaml librechat.yaml`.
|
||||
- Copy `chat.yaml`: `cp chat.example.yaml chat.yaml`.
|
||||
- Run: `npm run e2e`.
|
||||
|
||||
## 2. Development Notes
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
name: Build and Push Docker Images
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- 'v*'
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=sha
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
build-args: |
|
||||
COMMIT_SHA=${{ github.sha }}
|
||||
BUILD_DATE=${{ steps.meta.outputs.created }}
|
||||
|
||||
build-and-push-rag:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata for RAG API
|
||||
id: meta-rag
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/hanzoai/chat-rag-api
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=sha
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build and push RAG API Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: ./rag.Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: ${{ steps.meta-rag.outputs.tags }}
|
||||
labels: ${{ steps.meta-rag.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
@@ -123,5 +123,11 @@ helm/**/.values.yaml
|
||||
|
||||
!/client/src/@types/i18next.d.ts
|
||||
|
||||
<<<<<<< HEAD
|
||||
# SAML Idp cert
|
||||
*.cert
|
||||
=======
|
||||
|
||||
|
||||
*.orig
|
||||
>>>>>>> 1c21f3543 (Update brand)
|
||||
|
||||
@@ -11,6 +11,7 @@ All notable changes to this project will be documented in this file.
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
<<<<<<< HEAD
|
||||
- ✨ feat: implement search parameter updates by **@mawburn** in [#7151](https://github.com/danny-avila/LibreChat/pull/7151)
|
||||
- 🎏 feat: Add MCP support for Streamable HTTP Transport by **@benverhees** in [#7353](https://github.com/danny-avila/LibreChat/pull/7353)
|
||||
- 🔒 feat: Add Content Security Policy using Helmet middleware by **@rubentalstra** in [#7377](https://github.com/danny-avila/LibreChat/pull/7377)
|
||||
@@ -232,5 +233,13 @@ Changes from v0.7.7 to v0.7.8-rc1.
|
||||
[See full release details][release-v0.7.8-rc1]
|
||||
|
||||
[release-v0.7.8-rc1]: https://github.com/danny-avila/LibreChat/releases/tag/v0.7.8-rc1
|
||||
=======
|
||||
- 🪄 feat: Agent Artifacts by **@danny-avila** in [#5804](https://github.com/danny-avila/Hanzo/pull/5804)
|
||||
|
||||
### ⚙️ Other Changes
|
||||
|
||||
- 🔄 chore: Enforce 18next Language Keys by **@rubentalstra** in [#5803](https://github.com/danny-avila/Hanzo/pull/5803)
|
||||
- 🔃 refactor: Parent Message ID Handling on Error, Update Translations, Bump Agents by **@danny-avila** in [#5833](https://github.com/danny-avila/Hanzo/pull/5833)
|
||||
>>>>>>> 1c21f3543 (Update brand)
|
||||
|
||||
---
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 LibreChat
|
||||
Copyright (c) 2025 Hanzo
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
# Hanzo Chat Project - Development Guide
|
||||
|
||||
## Project Overview
|
||||
Hanzo Chat is a chat application based on LibreChat, which is a platform for interacting with various AI models. The project follows a microservices architecture with several components running in Docker containers.
|
||||
|
||||
## Architecture Components
|
||||
|
||||
### Backend Services
|
||||
- **API Service** (Hanzo container): Main application server
|
||||
- **MongoDB**: Database for storing conversations, users, and other data
|
||||
- **Meilisearch**: Search engine for indexing and searching content
|
||||
- **VectorDB**: PostgreSQL with pgvector extension for vector embeddings
|
||||
- **RAG API**: Service for Retrieval Augmented Generation capabilities
|
||||
|
||||
### Project Structure
|
||||
- `/api`: Backend server code
|
||||
- `/app`: Application logic, clients, and integrations
|
||||
- `/cache`: Caching mechanisms
|
||||
- `/config`: Configuration settings
|
||||
- `/models`: Database schemas and models
|
||||
- `/server`: Routes, controllers, and middleware
|
||||
- `/strategies`: Authentication strategies
|
||||
- `/client`: Frontend application
|
||||
- `/src`: React components, hooks, and UI elements
|
||||
- `/public`: Static assets
|
||||
- `/config`: Configuration scripts and utilities
|
||||
- `/packages`: Shared packages and modules
|
||||
- `/data-provider`: Data fetching and state management
|
||||
- `/data-schemas`: Schema definitions
|
||||
- `/mcp`: Message control/processing
|
||||
|
||||
## Environment Setup
|
||||
|
||||
### Prerequisites
|
||||
- Docker and Docker Compose
|
||||
- Node.js (version specified in package.json)
|
||||
|
||||
### Development Environment
|
||||
- The application runs in Docker containers
|
||||
- Storage configuration:
|
||||
- Named volumes for databases and service data: `mongodb_data`, `meili_data`, `pgdata2`
|
||||
- Local directories mounted for application data: `/images`, `/uploads`, `/logs`
|
||||
|
||||
### Important Environment Variables
|
||||
- `PORT`: The port on which the API service runs (default: 3080)
|
||||
- `RAG_PORT`: Port for the RAG API service (default: 8000)
|
||||
- `MEILI_MASTER_KEY`: Key for Meilisearch
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### Starting the Application
|
||||
1. Ensure proper directory structure (images, uploads, logs directories)
|
||||
2. Use docker-compose.override.yml to set specific container configurations:
|
||||
```yaml
|
||||
services:
|
||||
api:
|
||||
user: "root"
|
||||
mongodb:
|
||||
user: "root"
|
||||
meilisearch:
|
||||
user: "root"
|
||||
vectordb:
|
||||
user: "root"
|
||||
```
|
||||
3. Run `docker compose up -d` to start all services
|
||||
|
||||
### Troubleshooting
|
||||
- **Permissions Issues**: Use `user: "root"` in docker-compose.override.yml for containers
|
||||
- **MongoDB Errors**: Check permissions on data directories or use named volumes
|
||||
- **Container Communication**: Ensure proper networking between containers
|
||||
|
||||
## Configuration
|
||||
- `.env`: Environment variables for application settings
|
||||
- `docker-compose.yml`: Main service configuration
|
||||
- `docker-compose.override.yml`: Custom overrides for local development
|
||||
- `chat.yaml`: Optional configuration file for customizing endpoints
|
||||
|
||||
## Features
|
||||
- Multi-model AI chat interface
|
||||
- Integration with various AI providers
|
||||
- Authentication with multiple strategies
|
||||
- File uploads and processing
|
||||
- Conversation management
|
||||
- Search capabilities
|
||||
- Internationalization support
|
||||
@@ -0,0 +1,231 @@
|
||||
# Hanzo Chat Makefile for Local Development
|
||||
# ==========================================
|
||||
|
||||
# Default target - show help
|
||||
.DEFAULT_GOAL := help
|
||||
|
||||
# Variables
|
||||
DOCKER_COMPOSE := docker-compose
|
||||
NPM := npm
|
||||
PNPM := pnpm
|
||||
|
||||
# Colors for output
|
||||
GREEN := \033[0;32m
|
||||
YELLOW := \033[0;33m
|
||||
RED := \033[0;31m
|
||||
NC := \033[0m # No Color
|
||||
BLUE := \033[0;34m
|
||||
|
||||
# Help command - displays all available commands
|
||||
help: ## Show this help message
|
||||
@echo "$(GREEN)"
|
||||
@echo "╔══════════════════════════════════════════════════════════════╗"
|
||||
@echo "║ HANZO CHAT COMMANDS ║"
|
||||
@echo "╚══════════════════════════════════════════════════════════════╝"
|
||||
@echo "$(NC)"
|
||||
@echo "$(YELLOW)Quick Start:$(NC)"
|
||||
@echo " $(GREEN)make start$(NC) - Start all services with Docker Compose"
|
||||
@echo " $(GREEN)make dev$(NC) - Start in development mode (local Node.js)"
|
||||
@echo " $(GREEN)make stop$(NC) - Stop all services"
|
||||
@echo ""
|
||||
@echo "$(YELLOW)Docker Commands:$(NC)"
|
||||
@echo " $(GREEN)make up$(NC) - Start Docker services in background"
|
||||
@echo " $(GREEN)make down$(NC) - Stop and remove Docker containers"
|
||||
@echo " $(GREEN)make restart$(NC) - Restart all Docker services"
|
||||
@echo " $(GREEN)make logs$(NC) - Show Docker logs (all services)"
|
||||
@echo " $(GREEN)make logs-api$(NC) - Show API logs only"
|
||||
@echo " $(GREEN)make ps$(NC) - Show running containers"
|
||||
@echo " $(GREEN)make build$(NC) - Build Docker images"
|
||||
@echo ""
|
||||
@echo "$(YELLOW)Development Commands:$(NC)"
|
||||
@echo " $(GREEN)make install$(NC) - Install all dependencies"
|
||||
@echo " $(GREEN)make install-force$(NC) - Clean install (remove node_modules)"
|
||||
@echo " $(GREEN)make build-packages$(NC) - Build all packages"
|
||||
@echo " $(GREEN)make dev-backend$(NC) - Start backend in dev mode"
|
||||
@echo " $(GREEN)make dev-frontend$(NC) - Start frontend in dev mode"
|
||||
@echo " $(GREEN)make lint$(NC) - Run linter"
|
||||
@echo " $(GREEN)make format$(NC) - Format code with Prettier"
|
||||
@echo ""
|
||||
@echo "$(YELLOW)Database Commands:$(NC)"
|
||||
@echo " $(GREEN)make db-shell$(NC) - Access MongoDB shell"
|
||||
@echo " $(GREEN)make db-backup$(NC) - Backup MongoDB database"
|
||||
@echo " $(GREEN)make db-restore$(NC) - Restore MongoDB database"
|
||||
@echo ""
|
||||
@echo "$(YELLOW)Utility Commands:$(NC)"
|
||||
@echo " $(GREEN)make clean$(NC) - Clean build artifacts and logs"
|
||||
@echo " $(GREEN)make clean-all$(NC) - Clean everything (including node_modules)"
|
||||
@echo " $(GREEN)make status$(NC) - Show service status"
|
||||
@echo " $(GREEN)make test$(NC) - Run tests"
|
||||
@echo " $(GREEN)make shell$(NC) - Access API container shell"
|
||||
@echo " $(GREEN)make update-branding$(NC) - Update Hanzo logo in running container"
|
||||
@echo ""
|
||||
@echo "$(BLUE)Access the application at: http://localhost:3080$(NC)"
|
||||
@echo ""
|
||||
|
||||
# Quick start with banner
|
||||
start: ## Start all services with Docker Compose (with banner)
|
||||
@echo "$(GREEN)"
|
||||
@echo "╔══════════════════════════════════════════════════════════════╗"
|
||||
@echo "║ Starting Hanzo Chat... ║"
|
||||
@echo "╚══════════════════════════════════════════════════════════════╝"
|
||||
@echo "$(NC)"
|
||||
@$(MAKE) check-docker
|
||||
@$(MAKE) up
|
||||
@echo ""
|
||||
@echo "$(GREEN)✓ All services started successfully!$(NC)"
|
||||
@echo ""
|
||||
@echo "$(YELLOW)Access points:$(NC)"
|
||||
@echo " • Application: $(BLUE)http://localhost:3080$(NC)"
|
||||
@echo " • API Health: $(BLUE)http://localhost:3080/api/health$(NC)"
|
||||
@echo ""
|
||||
@echo "$(YELLOW)Useful commands:$(NC)"
|
||||
@echo " • View logs: $(GREEN)make logs$(NC)"
|
||||
@echo " • Stop: $(GREEN)make stop$(NC)"
|
||||
@echo " • Status: $(GREEN)make status$(NC)"
|
||||
@echo ""
|
||||
|
||||
# Docker commands
|
||||
up: ## Start Docker services in background
|
||||
@$(DOCKER_COMPOSE) up -d
|
||||
|
||||
down: ## Stop and remove Docker containers
|
||||
@$(DOCKER_COMPOSE) down
|
||||
|
||||
stop: ## Stop all services
|
||||
@$(DOCKER_COMPOSE) stop
|
||||
|
||||
restart: ## Restart all Docker services
|
||||
@$(DOCKER_COMPOSE) restart
|
||||
|
||||
logs: ## Show Docker logs (all services)
|
||||
@$(DOCKER_COMPOSE) logs -f
|
||||
|
||||
logs-api: ## Show API logs only
|
||||
@$(DOCKER_COMPOSE) logs -f api
|
||||
|
||||
ps: ## Show running containers
|
||||
@$(DOCKER_COMPOSE) ps
|
||||
|
||||
build: ## Build Docker images
|
||||
@$(DOCKER_COMPOSE) build
|
||||
|
||||
# Development commands
|
||||
dev: ## Start in development mode (local Node.js)
|
||||
@echo "$(YELLOW)Starting development servers...$(NC)"
|
||||
@$(MAKE) install
|
||||
@$(MAKE) build-packages
|
||||
@echo "$(GREEN)Starting backend and frontend...$(NC)"
|
||||
@(trap 'kill 0' SIGINT; \
|
||||
$(NPM) run backend:dev & \
|
||||
$(NPM) run frontend:dev & \
|
||||
wait)
|
||||
|
||||
install: ## Install all dependencies
|
||||
@echo "$(YELLOW)Installing dependencies...$(NC)"
|
||||
@$(NPM) install
|
||||
@cd api && $(NPM) install
|
||||
@cd client && $(NPM) install
|
||||
@cd packages/data-provider && $(NPM) install
|
||||
@cd packages/data-schemas && $(NPM) install
|
||||
@cd packages/api && $(NPM) install
|
||||
@echo "$(GREEN)✓ Dependencies installed$(NC)"
|
||||
|
||||
install-force: ## Clean install (remove node_modules)
|
||||
@echo "$(YELLOW)Performing clean install...$(NC)"
|
||||
@$(MAKE) clean-all
|
||||
@$(MAKE) install
|
||||
|
||||
build-packages: ## Build all packages
|
||||
@echo "$(YELLOW)Building packages...$(NC)"
|
||||
@$(NPM) run build:data-provider
|
||||
@$(NPM) run build:data-schemas
|
||||
@$(NPM) run build:api
|
||||
@echo "$(GREEN)✓ Packages built$(NC)"
|
||||
|
||||
dev-backend: ## Start backend in dev mode
|
||||
@$(NPM) run backend:dev
|
||||
|
||||
dev-frontend: ## Start frontend in dev mode
|
||||
@$(NPM) run frontend:dev
|
||||
|
||||
lint: ## Run linter
|
||||
@$(NPM) run lint
|
||||
|
||||
format: ## Format code with Prettier
|
||||
@$(NPM) run format
|
||||
|
||||
# Database commands
|
||||
db-shell: ## Access MongoDB shell
|
||||
@$(DOCKER_COMPOSE) exec mongodb mongosh Hanzo
|
||||
|
||||
db-backup: ## Backup MongoDB database
|
||||
@mkdir -p ./backups
|
||||
@$(DOCKER_COMPOSE) exec mongodb mongodump --db Hanzo --out /dump
|
||||
@$(DOCKER_COMPOSE) cp mongodb:/dump ./backups/hanzo-backup-$$(date +%Y%m%d-%H%M%S)
|
||||
@echo "$(GREEN)✓ Database backed up$(NC)"
|
||||
|
||||
db-restore: ## Restore MongoDB database from latest backup
|
||||
@echo "$(YELLOW)Restoring from latest backup...$(NC)"
|
||||
@LATEST_BACKUP=$$(ls -t ./backups | head -1); \
|
||||
$(DOCKER_COMPOSE) cp ./backups/$$LATEST_BACKUP mongodb:/restore && \
|
||||
$(DOCKER_COMPOSE) exec mongodb mongorestore --db Hanzo /restore/Hanzo
|
||||
@echo "$(GREEN)✓ Database restored$(NC)"
|
||||
|
||||
# Utility commands
|
||||
update-branding: ## Update Hanzo branding in running container
|
||||
@echo "$(YELLOW)Updating Hanzo branding...$(NC)"
|
||||
@docker exec Hanzo cp /app/client/public/assets/logo.svg /app/client/dist/assets/logo.svg 2>/dev/null || true
|
||||
@echo "$(GREEN)✓ Branding updated. Clear browser cache to see changes.$(NC)"
|
||||
|
||||
clean: ## Clean build artifacts and logs
|
||||
@echo "$(YELLOW)Cleaning build artifacts...$(NC)"
|
||||
@rm -rf api/dist client/dist packages/*/dist
|
||||
@rm -rf logs/*.log
|
||||
@rm -rf backend.log frontend.log mongod.log
|
||||
@echo "$(GREEN)✓ Clean complete$(NC)"
|
||||
|
||||
clean-all: clean ## Clean everything (including node_modules)
|
||||
@echo "$(YELLOW)Removing node_modules...$(NC)"
|
||||
@rm -rf node_modules api/node_modules client/node_modules packages/*/node_modules
|
||||
@echo "$(GREEN)✓ Deep clean complete$(NC)"
|
||||
|
||||
status: ## Show service status
|
||||
@echo "$(YELLOW)Service Status:$(NC)"
|
||||
@$(DOCKER_COMPOSE) ps
|
||||
@echo ""
|
||||
@echo "$(YELLOW)Port Status:$(NC)"
|
||||
@lsof -i :3080 >/dev/null 2>&1 && echo " $(GREEN)✓ Port 3080 (API) is active$(NC)" || echo " $(RED)✗ Port 3080 (API) is not active$(NC)"
|
||||
|
||||
test: ## Run tests
|
||||
@$(NPM) run test:api
|
||||
@$(NPM) run test:client
|
||||
|
||||
shell: ## Access API container shell
|
||||
@$(DOCKER_COMPOSE) exec api /bin/sh
|
||||
|
||||
# Check if Docker/Colima is running
|
||||
check-docker:
|
||||
@echo "$(YELLOW)Checking Docker status...$(NC)"
|
||||
@docker info >/dev/null 2>&1 || (echo "$(RED)Docker is not running. Please start Docker/Colima first.$(NC)" && exit 1)
|
||||
@echo "$(GREEN)✓ Docker is running$(NC)"
|
||||
|
||||
# Environment setup
|
||||
setup-env: ## Create .env file from example
|
||||
@if [ ! -f .env ]; then \
|
||||
echo "$(YELLOW)Creating .env file...$(NC)"; \
|
||||
cp .env.example .env; \
|
||||
echo "$(GREEN)✓ .env file created. Please update with your API keys.$(NC)"; \
|
||||
else \
|
||||
echo "$(YELLOW).env file already exists$(NC)"; \
|
||||
fi
|
||||
|
||||
# Shortcuts
|
||||
s: start
|
||||
d: down
|
||||
l: logs
|
||||
r: restart
|
||||
|
||||
.PHONY: help start up down stop restart logs logs-api ps build \
|
||||
dev install install-force build-packages dev-backend dev-frontend \
|
||||
lint format db-shell db-backup db-restore clean clean-all \
|
||||
status test shell check-docker setup-env update-branding s d l r
|
||||
@@ -1,50 +1,31 @@
|
||||
<p align="center">
|
||||
<a href="https://librechat.ai">
|
||||
<a href="https://hanzo.ai">
|
||||
<img src="client/public/assets/logo.svg" height="256">
|
||||
</a>
|
||||
<h1 align="center">
|
||||
<a href="https://librechat.ai">LibreChat</a>
|
||||
<a href="https://hanzo.ai">Hanzo Chat</a>
|
||||
</h1>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://discord.librechat.ai">
|
||||
<a href="https://discord.gg/hanzo">
|
||||
<img
|
||||
src="https://img.shields.io/discord/1086345563026489514?label=&logo=discord&style=for-the-badge&logoWidth=20&logoColor=white&labelColor=000000&color=blueviolet">
|
||||
src="https://img.shields.io/badge/DISCORD-black.svg?style=for-the-badge&logo=discord&logoColor=white&labelColor=000000">
|
||||
</a>
|
||||
<a href="https://www.youtube.com/@LibreChat">
|
||||
<a href="https://hanzo.ai">
|
||||
<img
|
||||
src="https://img.shields.io/badge/YOUTUBE-red.svg?style=for-the-badge&logo=youtube&logoColor=white&labelColor=000000&logoWidth=20">
|
||||
src="https://img.shields.io/badge/WEBSITE-black.svg?style=for-the-badge&logo=globe&logoColor=white&labelColor=000000">
|
||||
</a>
|
||||
<a href="https://docs.librechat.ai">
|
||||
<a href="https://docs.hanzo.ai">
|
||||
<img
|
||||
src="https://img.shields.io/badge/DOCS-blue.svg?style=for-the-badge&logo=read-the-docs&logoColor=white&labelColor=000000&logoWidth=20">
|
||||
src="https://img.shields.io/badge/DOCS-black.svg?style=for-the-badge&logo=read-the-docs&logoColor=white&labelColor=000000">
|
||||
</a>
|
||||
<a aria-label="Sponsors" href="https://github.com/sponsors/danny-avila">
|
||||
<a aria-label="GitHub" href="https://github.com/hanzoai/chat">
|
||||
<img
|
||||
src="https://img.shields.io/badge/SPONSORS-brightgreen.svg?style=for-the-badge&logo=github-sponsors&logoColor=white&labelColor=000000&logoWidth=20">
|
||||
src="https://img.shields.io/badge/GITHUB-black.svg?style=for-the-badge&logo=github&logoColor=white&labelColor=000000">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://railway.app/template/b5k2mn?referralCode=HI9hWz">
|
||||
<img src="https://railway.app/button.svg" alt="Deploy on Railway" height="30">
|
||||
</a>
|
||||
<a href="https://zeabur.com/templates/0X2ZY8">
|
||||
<img src="https://zeabur.com/button.svg" alt="Deploy on Zeabur" height="30"/>
|
||||
</a>
|
||||
<a href="https://template.cloud.sealos.io/deploy?templateName=librechat">
|
||||
<img src="https://raw.githubusercontent.com/labring-actions/templates/main/Deploy-on-Sealos.svg" alt="Deploy on Sealos" height="30">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://www.librechat.ai/docs/translation">
|
||||
<img
|
||||
src="https://img.shields.io/badge/dynamic/json.svg?style=for-the-badge&color=2096F3&label=locize&query=%24.translatedPercentage&url=https://api.locize.app/badgedata/4cb2598b-ed4d-469c-9b04-2ed531a8cb45&suffix=%+translated"
|
||||
alt="Translation Progress">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
|
||||
# ✨ Features
|
||||
@@ -53,7 +34,7 @@
|
||||
|
||||
- 🤖 **AI Model Selection**:
|
||||
- Anthropic (Claude), AWS Bedrock, OpenAI, Azure OpenAI, Google, Vertex AI, OpenAI Assistants API (incl. Azure)
|
||||
- [Custom Endpoints](https://www.librechat.ai/docs/quick_start/custom_endpoints): Use any OpenAI-compatible API with LibreChat, no proxy required
|
||||
- [Custom Endpoints](https://www.librechat.ai/docs/quick_start/custom_endpoints): Use any OpenAI-compatible API with Hanzo, no proxy required
|
||||
- Compatible with [Local & Remote AI Providers](https://www.librechat.ai/docs/configuration/librechat_yaml/ai_endpoints):
|
||||
- Ollama, groq, Cohere, Mistral AI, Apple MLX, koboldcpp, together.ai,
|
||||
- OpenRouter, Perplexity, ShuttleAI, Deepseek, Qwen, and more
|
||||
@@ -64,12 +45,12 @@
|
||||
- No Privacy Concerns: Fully isolated and secure execution
|
||||
|
||||
- 🔦 **Agents & Tools Integration**:
|
||||
- **[LibreChat Agents](https://www.librechat.ai/docs/features/agents)**:
|
||||
- **[Hanzo Agents](https://www.librechat.ai/docs/features/agents)**:
|
||||
- No-Code Custom Assistants: Build specialized, AI-driven helpers without coding
|
||||
- Flexible & Extensible: Attach tools like DALL-E-3, file search, code execution, and more
|
||||
- Compatible with Custom Endpoints, OpenAI, Azure, Anthropic, AWS Bedrock, and more
|
||||
- [Model Context Protocol (MCP) Support](https://modelcontextprotocol.io/clients#librechat) for Tools
|
||||
- Use LibreChat Agents and OpenAI Assistants with Files, Code Interpreter, Tools, and API Actions
|
||||
- Use Hanzo Agents and OpenAI Assistants with Files, Code Interpreter, Tools, and API Actions
|
||||
|
||||
- 🔍 **Web Search**:
|
||||
- Search the internet and retrieve relevant information to enhance your AI context
|
||||
@@ -110,7 +91,7 @@
|
||||
- Supports OpenAI, Azure OpenAI, and Elevenlabs
|
||||
|
||||
- 📥 **Import & Export Conversations**:
|
||||
- Import Conversations from LibreChat, ChatGPT, Chatbot UI
|
||||
- Import Conversations from Hanzo, ChatGPT, Chatbot UI
|
||||
- Export conversations as screenshots, markdown, text, json
|
||||
|
||||
- 🔍 **Search & Discovery**:
|
||||
@@ -130,13 +111,13 @@
|
||||
|
||||
[For a thorough review of our features, see our docs here](https://docs.librechat.ai/) 📚
|
||||
|
||||
## 🪶 All-In-One AI Conversations with LibreChat
|
||||
## 🪶 All-In-One AI Conversations with Hanzo
|
||||
|
||||
LibreChat brings together the future of assistant AIs with the revolutionary technology of OpenAI's ChatGPT. Celebrating the original styling, LibreChat gives you the ability to integrate multiple AI models. It also integrates and enhances original client features such as conversation and message search, prompt templates and plugins.
|
||||
Hanzo brings together the future of assistant AIs with the revolutionary technology of OpenAI's ChatGPT. Celebrating the original styling, Hanzo gives you the ability to integrate multiple AI models. It also integrates and enhances original client features such as conversation and message search, prompt templates and plugins.
|
||||
|
||||
With LibreChat, you no longer need to opt for ChatGPT Plus and can instead use free or pay-per-call APIs. We welcome contributions, cloning, and forking to enhance the capabilities of this advanced chatbot platform.
|
||||
With Hanzo, you no longer need to opt for ChatGPT Plus and can instead use free or pay-per-call APIs. We welcome contributions, cloning, and forking to enhance the capabilities of this advanced chatbot platform.
|
||||
|
||||
[](https://www.youtube.com/watch?v=ilfwGQtJNlI)
|
||||
[](https://www.youtube.com/watch?v=ilfwGQtJNlI)
|
||||
|
||||
Click on the thumbnail to open the video☝️
|
||||
|
||||
@@ -146,7 +127,7 @@ Click on the thumbnail to open the video☝️
|
||||
|
||||
**GitHub Repo:**
|
||||
- **RAG API:** [github.com/danny-avila/rag_api](https://github.com/danny-avila/rag_api)
|
||||
- **Website:** [github.com/LibreChat-AI/librechat.ai](https://github.com/LibreChat-AI/librechat.ai)
|
||||
- **Website:** [github.com/Hanzo-AI/librechat.ai](https://github.com/Hanzo-AI/librechat.ai)
|
||||
|
||||
**Other:**
|
||||
- **Website:** [librechat.ai](https://librechat.ai)
|
||||
@@ -158,7 +139,7 @@ Click on the thumbnail to open the video☝️
|
||||
## 📝 Changelog
|
||||
|
||||
Keep up with the latest updates by visiting the releases page and notes:
|
||||
- [Releases](https://github.com/danny-avila/LibreChat/releases)
|
||||
- [Releases](https://github.com/danny-avila/Hanzo/releases)
|
||||
- [Changelog](https://www.librechat.ai/changelog)
|
||||
|
||||
**⚠️ Please consult the [changelog](https://www.librechat.ai/changelog) for breaking changes before updating.**
|
||||
@@ -168,13 +149,13 @@ Keep up with the latest updates by visiting the releases page and notes:
|
||||
## ⭐ Star History
|
||||
|
||||
<p align="center">
|
||||
<a href="https://star-history.com/#danny-avila/LibreChat&Date">
|
||||
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=danny-avila/LibreChat&type=Date&theme=dark" onerror="this.src='https://api.star-history.com/svg?repos=danny-avila/LibreChat&type=Date'" />
|
||||
<a href="https://star-history.com/#danny-avila/Hanzo&Date">
|
||||
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=danny-avila/Hanzo&type=Date&theme=dark" onerror="this.src='https://api.star-history.com/svg?repos=danny-avila/Hanzo&type=Date'" />
|
||||
</a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://trendshift.io/repositories/4685" target="_blank" style="padding: 10px;">
|
||||
<img src="https://trendshift.io/api/badge/repositories/4685" alt="danny-avila%2FLibreChat | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/>
|
||||
<img src="https://trendshift.io/api/badge/repositories/4685" alt="danny-avila%2FHanzo | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/>
|
||||
</a>
|
||||
<a href="https://runacap.com/ross-index/q1-24/" target="_blank" rel="noopener" style="margin-left: 20px;">
|
||||
<img style="width: 260px; height: 56px" src="https://runacap.com/wp-content/uploads/2024/04/ROSS_badge_white_Q1_2024.svg" alt="ROSS Index - Fastest Growing Open-Source Startups in Q1 2024 | Runa Capital" width="260" height="56"/>
|
||||
@@ -189,21 +170,21 @@ Contributions, suggestions, bug reports and fixes are welcome!
|
||||
|
||||
For new features, components, or extensions, please open an issue and discuss before sending a PR.
|
||||
|
||||
If you'd like to help translate LibreChat into your language, we'd love your contribution! Improving our translations not only makes LibreChat more accessible to users around the world but also enhances the overall user experience. Please check out our [Translation Guide](https://www.librechat.ai/docs/translation).
|
||||
If you'd like to help translate Hanzo into your language, we'd love your contribution! Improving our translations not only makes Hanzo more accessible to users around the world but also enhances the overall user experience. Please check out our [Translation Guide](https://www.librechat.ai/docs/translation).
|
||||
|
||||
---
|
||||
|
||||
## 💖 This project exists in its current state thanks to all the people who contribute
|
||||
|
||||
<a href="https://github.com/danny-avila/LibreChat/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=danny-avila/LibreChat" />
|
||||
<a href="https://github.com/danny-avila/Hanzo/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=danny-avila/Hanzo" />
|
||||
</a>
|
||||
|
||||
---
|
||||
|
||||
## 🎉 Special Thanks
|
||||
|
||||
We thank [Locize](https://locize.com) for their translation management tools that support multiple languages in LibreChat.
|
||||
We thank [Locize](https://locize.com) for their translation management tools that support multiple languages in Hanzo.
|
||||
|
||||
<p align="center">
|
||||
<a href="https://locize.com" target="_blank" rel="noopener noreferrer">
|
||||
|
||||
@@ -252,7 +252,7 @@ class AnthropicClient extends BaseClient {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Token Count for LibreChat Message
|
||||
* Get Token Count for Hanzo Message
|
||||
* @param {TMessage} responseMessage
|
||||
* @returns {number}
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,804 @@
|
||||
const { Keyv } = require('keyv');
|
||||
const crypto = require('crypto');
|
||||
const { CohereClient } = require('cohere-ai');
|
||||
const { fetchEventSource } = require('@waylaidwanderer/fetch-event-source');
|
||||
const { constructAzureURL, genAzureChatCompletion } = require('@librechat/api');
|
||||
const { encoding_for_model: encodingForModel, get_encoding: getEncoding } = require('tiktoken');
|
||||
const {
|
||||
ImageDetail,
|
||||
EModelEndpoint,
|
||||
resolveHeaders,
|
||||
CohereConstants,
|
||||
mapModelToAzureConfig,
|
||||
} = require('librechat-data-provider');
|
||||
const { createContextHandlers } = require('./prompts');
|
||||
const { createCoherePayload } = require('./llm');
|
||||
const { extractBaseURL } = require('~/utils');
|
||||
const BaseClient = require('./BaseClient');
|
||||
const { logger } = require('~/config');
|
||||
|
||||
const CHATGPT_MODEL = 'gpt-3.5-turbo';
|
||||
const tokenizersCache = {};
|
||||
|
||||
class ChatGPTClient extends BaseClient {
|
||||
constructor(apiKey, options = {}, cacheOptions = {}) {
|
||||
super(apiKey, options, cacheOptions);
|
||||
|
||||
cacheOptions.namespace = cacheOptions.namespace || 'chatgpt';
|
||||
this.conversationsCache = new Keyv(cacheOptions);
|
||||
this.setOptions(options);
|
||||
}
|
||||
|
||||
setOptions(options) {
|
||||
if (this.options && !this.options.replaceOptions) {
|
||||
// nested options aren't spread properly, so we need to do this manually
|
||||
this.options.modelOptions = {
|
||||
...this.options.modelOptions,
|
||||
...options.modelOptions,
|
||||
};
|
||||
delete options.modelOptions;
|
||||
// now we can merge options
|
||||
this.options = {
|
||||
...this.options,
|
||||
...options,
|
||||
};
|
||||
} else {
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
if (this.options.openaiApiKey) {
|
||||
this.apiKey = this.options.openaiApiKey;
|
||||
}
|
||||
|
||||
const modelOptions = this.options.modelOptions || {};
|
||||
this.modelOptions = {
|
||||
...modelOptions,
|
||||
// set some good defaults (check for undefined in some cases because they may be 0)
|
||||
model: modelOptions.model || CHATGPT_MODEL,
|
||||
temperature: typeof modelOptions.temperature === 'undefined' ? 0.8 : modelOptions.temperature,
|
||||
top_p: typeof modelOptions.top_p === 'undefined' ? 1 : modelOptions.top_p,
|
||||
presence_penalty:
|
||||
typeof modelOptions.presence_penalty === 'undefined' ? 1 : modelOptions.presence_penalty,
|
||||
stop: modelOptions.stop,
|
||||
};
|
||||
|
||||
this.isChatGptModel = this.modelOptions.model.includes('gpt-');
|
||||
const { isChatGptModel } = this;
|
||||
this.isUnofficialChatGptModel =
|
||||
this.modelOptions.model.startsWith('text-chat') ||
|
||||
this.modelOptions.model.startsWith('text-davinci-002-render');
|
||||
const { isUnofficialChatGptModel } = this;
|
||||
|
||||
// Davinci models have a max context length of 4097 tokens.
|
||||
this.maxContextTokens = this.options.maxContextTokens || (isChatGptModel ? 4095 : 4097);
|
||||
// I decided to reserve 1024 tokens for the response.
|
||||
// The max prompt tokens is determined by the max context tokens minus the max response tokens.
|
||||
// Earlier messages will be dropped until the prompt is within the limit.
|
||||
this.maxResponseTokens = this.modelOptions.max_tokens || 1024;
|
||||
this.maxPromptTokens =
|
||||
this.options.maxPromptTokens || this.maxContextTokens - this.maxResponseTokens;
|
||||
|
||||
if (this.maxPromptTokens + this.maxResponseTokens > this.maxContextTokens) {
|
||||
throw new Error(
|
||||
`maxPromptTokens + max_tokens (${this.maxPromptTokens} + ${this.maxResponseTokens} = ${
|
||||
this.maxPromptTokens + this.maxResponseTokens
|
||||
}) must be less than or equal to maxContextTokens (${this.maxContextTokens})`,
|
||||
);
|
||||
}
|
||||
|
||||
this.userLabel = this.options.userLabel || 'User';
|
||||
this.chatGptLabel = this.options.chatGptLabel || 'ChatGPT';
|
||||
|
||||
if (isChatGptModel) {
|
||||
// Use these faux tokens to help the AI understand the context since we are building the chat log ourselves.
|
||||
// Trying to use "<|im_start|>" causes the AI to still generate "<" or "<|" at the end sometimes for some reason,
|
||||
// without tripping the stop sequences, so I'm using "||>" instead.
|
||||
this.startToken = '||>';
|
||||
this.endToken = '';
|
||||
this.gptEncoder = this.constructor.getTokenizer('cl100k_base');
|
||||
} else if (isUnofficialChatGptModel) {
|
||||
this.startToken = '<|im_start|>';
|
||||
this.endToken = '<|im_end|>';
|
||||
this.gptEncoder = this.constructor.getTokenizer('text-davinci-003', true, {
|
||||
'<|im_start|>': 100264,
|
||||
'<|im_end|>': 100265,
|
||||
});
|
||||
} else {
|
||||
// Previously I was trying to use "<|endoftext|>" but there seems to be some bug with OpenAI's token counting
|
||||
// system that causes only the first "<|endoftext|>" to be counted as 1 token, and the rest are not treated
|
||||
// as a single token. So we're using this instead.
|
||||
this.startToken = '||>';
|
||||
this.endToken = '';
|
||||
try {
|
||||
this.gptEncoder = this.constructor.getTokenizer(this.modelOptions.model, true);
|
||||
} catch {
|
||||
this.gptEncoder = this.constructor.getTokenizer('text-davinci-003', true);
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.modelOptions.stop) {
|
||||
const stopTokens = [this.startToken];
|
||||
if (this.endToken && this.endToken !== this.startToken) {
|
||||
stopTokens.push(this.endToken);
|
||||
}
|
||||
stopTokens.push(`\n${this.userLabel}:`);
|
||||
stopTokens.push('<|diff_marker|>');
|
||||
// I chose not to do one for `chatGptLabel` because I've never seen it happen
|
||||
this.modelOptions.stop = stopTokens;
|
||||
}
|
||||
|
||||
if (this.options.reverseProxyUrl) {
|
||||
this.completionsUrl = this.options.reverseProxyUrl;
|
||||
} else if (isChatGptModel) {
|
||||
this.completionsUrl = 'https://api.openai.com/v1/chat/completions';
|
||||
} else {
|
||||
this.completionsUrl = 'https://api.openai.com/v1/completions';
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
static getTokenizer(encoding, isModelName = false, extendSpecialTokens = {}) {
|
||||
if (tokenizersCache[encoding]) {
|
||||
return tokenizersCache[encoding];
|
||||
}
|
||||
let tokenizer;
|
||||
if (isModelName) {
|
||||
tokenizer = encodingForModel(encoding, extendSpecialTokens);
|
||||
} else {
|
||||
tokenizer = getEncoding(encoding, extendSpecialTokens);
|
||||
}
|
||||
tokenizersCache[encoding] = tokenizer;
|
||||
return tokenizer;
|
||||
}
|
||||
|
||||
/** @type {getCompletion} */
|
||||
async getCompletion(input, onProgress, onTokenProgress, abortController = null) {
|
||||
if (!abortController) {
|
||||
abortController = new AbortController();
|
||||
}
|
||||
|
||||
let modelOptions = { ...this.modelOptions };
|
||||
if (typeof onProgress === 'function') {
|
||||
modelOptions.stream = true;
|
||||
}
|
||||
if (this.isChatGptModel) {
|
||||
modelOptions.messages = input;
|
||||
} else {
|
||||
modelOptions.prompt = input;
|
||||
}
|
||||
|
||||
if (this.useOpenRouter && modelOptions.prompt) {
|
||||
delete modelOptions.stop;
|
||||
}
|
||||
|
||||
const { debug } = this.options;
|
||||
let baseURL = this.completionsUrl;
|
||||
if (debug) {
|
||||
console.debug();
|
||||
console.debug(baseURL);
|
||||
console.debug(modelOptions);
|
||||
console.debug();
|
||||
}
|
||||
|
||||
const opts = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
};
|
||||
|
||||
if (this.isVisionModel) {
|
||||
modelOptions.max_tokens = 4000;
|
||||
}
|
||||
|
||||
/** @type {TAzureConfig | undefined} */
|
||||
const azureConfig = this.options?.req?.app?.locals?.[EModelEndpoint.azureOpenAI];
|
||||
|
||||
const isAzure = this.azure || this.options.azure;
|
||||
if (
|
||||
(isAzure && this.isVisionModel && azureConfig) ||
|
||||
(azureConfig && this.isVisionModel && this.options.endpoint === EModelEndpoint.azureOpenAI)
|
||||
) {
|
||||
const { modelGroupMap, groupMap } = azureConfig;
|
||||
const {
|
||||
azureOptions,
|
||||
baseURL,
|
||||
headers = {},
|
||||
serverless,
|
||||
} = mapModelToAzureConfig({
|
||||
modelName: modelOptions.model,
|
||||
modelGroupMap,
|
||||
groupMap,
|
||||
});
|
||||
opts.headers = resolveHeaders(headers);
|
||||
this.langchainProxy = extractBaseURL(baseURL);
|
||||
this.apiKey = azureOptions.azureOpenAIApiKey;
|
||||
|
||||
const groupName = modelGroupMap[modelOptions.model].group;
|
||||
this.options.addParams = azureConfig.groupMap[groupName].addParams;
|
||||
this.options.dropParams = azureConfig.groupMap[groupName].dropParams;
|
||||
// Note: `forcePrompt` not re-assigned as only chat models are vision models
|
||||
|
||||
this.azure = !serverless && azureOptions;
|
||||
this.azureEndpoint =
|
||||
!serverless && genAzureChatCompletion(this.azure, modelOptions.model, this);
|
||||
if (serverless === true) {
|
||||
this.options.defaultQuery = azureOptions.azureOpenAIApiVersion
|
||||
? { 'api-version': azureOptions.azureOpenAIApiVersion }
|
||||
: undefined;
|
||||
this.options.headers['api-key'] = this.apiKey;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.options.defaultQuery) {
|
||||
opts.defaultQuery = this.options.defaultQuery;
|
||||
}
|
||||
|
||||
if (this.options.headers) {
|
||||
opts.headers = { ...opts.headers, ...this.options.headers };
|
||||
}
|
||||
|
||||
if (isAzure) {
|
||||
// Azure does not accept `model` in the body, so we need to remove it.
|
||||
delete modelOptions.model;
|
||||
|
||||
baseURL = this.langchainProxy
|
||||
? constructAzureURL({
|
||||
baseURL: this.langchainProxy,
|
||||
azureOptions: this.azure,
|
||||
})
|
||||
: this.azureEndpoint.split(/(?<!\/)\/(chat|completion)\//)[0];
|
||||
|
||||
if (this.options.forcePrompt) {
|
||||
baseURL += '/completions';
|
||||
} else {
|
||||
baseURL += '/chat/completions';
|
||||
}
|
||||
|
||||
opts.defaultQuery = { 'api-version': this.azure.azureOpenAIApiVersion };
|
||||
opts.headers = { ...opts.headers, 'api-key': this.apiKey };
|
||||
} else if (this.apiKey) {
|
||||
opts.headers.Authorization = `Bearer ${this.apiKey}`;
|
||||
}
|
||||
|
||||
if (process.env.OPENAI_ORGANIZATION) {
|
||||
opts.headers['OpenAI-Organization'] = process.env.OPENAI_ORGANIZATION;
|
||||
}
|
||||
|
||||
if (this.useOpenRouter) {
|
||||
opts.headers['HTTP-Referer'] = 'https://librechat.ai';
|
||||
opts.headers['X-Title'] = 'Hanzo';
|
||||
}
|
||||
|
||||
/* hacky fixes for Mistral AI API:
|
||||
- Re-orders system message to the top of the messages payload, as not allowed anywhere else
|
||||
- If there is only one message and it's a system message, change the role to user
|
||||
*/
|
||||
if (baseURL.includes('https://api.mistral.ai/v1') && modelOptions.messages) {
|
||||
const { messages } = modelOptions;
|
||||
|
||||
const systemMessageIndex = messages.findIndex((msg) => msg.role === 'system');
|
||||
|
||||
if (systemMessageIndex > 0) {
|
||||
const [systemMessage] = messages.splice(systemMessageIndex, 1);
|
||||
messages.unshift(systemMessage);
|
||||
}
|
||||
|
||||
modelOptions.messages = messages;
|
||||
|
||||
if (messages.length === 1 && messages[0].role === 'system') {
|
||||
modelOptions.messages[0].role = 'user';
|
||||
}
|
||||
}
|
||||
|
||||
if (this.options.addParams && typeof this.options.addParams === 'object') {
|
||||
modelOptions = {
|
||||
...modelOptions,
|
||||
...this.options.addParams,
|
||||
};
|
||||
logger.debug('[ChatGPTClient] chatCompletion: added params', {
|
||||
addParams: this.options.addParams,
|
||||
modelOptions,
|
||||
});
|
||||
}
|
||||
|
||||
if (this.options.dropParams && Array.isArray(this.options.dropParams)) {
|
||||
this.options.dropParams.forEach((param) => {
|
||||
delete modelOptions[param];
|
||||
});
|
||||
logger.debug('[ChatGPTClient] chatCompletion: dropped params', {
|
||||
dropParams: this.options.dropParams,
|
||||
modelOptions,
|
||||
});
|
||||
}
|
||||
|
||||
if (baseURL.startsWith(CohereConstants.API_URL)) {
|
||||
const payload = createCoherePayload({ modelOptions });
|
||||
return await this.cohereChatCompletion({ payload, onTokenProgress });
|
||||
}
|
||||
|
||||
if (baseURL.includes('v1') && !baseURL.includes('/completions') && !this.isChatCompletion) {
|
||||
baseURL = baseURL.split('v1')[0] + 'v1/completions';
|
||||
} else if (
|
||||
baseURL.includes('v1') &&
|
||||
!baseURL.includes('/chat/completions') &&
|
||||
this.isChatCompletion
|
||||
) {
|
||||
baseURL = baseURL.split('v1')[0] + 'v1/chat/completions';
|
||||
}
|
||||
|
||||
const BASE_URL = new URL(baseURL);
|
||||
if (opts.defaultQuery) {
|
||||
Object.entries(opts.defaultQuery).forEach(([key, value]) => {
|
||||
BASE_URL.searchParams.append(key, value);
|
||||
});
|
||||
delete opts.defaultQuery;
|
||||
}
|
||||
|
||||
const completionsURL = BASE_URL.toString();
|
||||
opts.body = JSON.stringify(modelOptions);
|
||||
|
||||
if (modelOptions.stream) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
let done = false;
|
||||
await fetchEventSource(completionsURL, {
|
||||
...opts,
|
||||
signal: abortController.signal,
|
||||
async onopen(response) {
|
||||
if (response.status === 200) {
|
||||
return;
|
||||
}
|
||||
if (debug) {
|
||||
console.debug(response);
|
||||
}
|
||||
let error;
|
||||
try {
|
||||
const body = await response.text();
|
||||
error = new Error(`Failed to send message. HTTP ${response.status} - ${body}`);
|
||||
error.status = response.status;
|
||||
error.json = JSON.parse(body);
|
||||
} catch {
|
||||
error = error || new Error(`Failed to send message. HTTP ${response.status}`);
|
||||
}
|
||||
throw error;
|
||||
},
|
||||
onclose() {
|
||||
if (debug) {
|
||||
console.debug('Server closed the connection unexpectedly, returning...');
|
||||
}
|
||||
// workaround for private API not sending [DONE] event
|
||||
if (!done) {
|
||||
onProgress('[DONE]');
|
||||
resolve();
|
||||
}
|
||||
},
|
||||
onerror(err) {
|
||||
if (debug) {
|
||||
console.debug(err);
|
||||
}
|
||||
// rethrow to stop the operation
|
||||
throw err;
|
||||
},
|
||||
onmessage(message) {
|
||||
if (debug) {
|
||||
console.debug(message);
|
||||
}
|
||||
if (!message.data || message.event === 'ping') {
|
||||
return;
|
||||
}
|
||||
if (message.data === '[DONE]') {
|
||||
onProgress('[DONE]');
|
||||
resolve();
|
||||
done = true;
|
||||
return;
|
||||
}
|
||||
onProgress(JSON.parse(message.data));
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
const response = await fetch(completionsURL, {
|
||||
...opts,
|
||||
signal: abortController.signal,
|
||||
});
|
||||
if (response.status !== 200) {
|
||||
const body = await response.text();
|
||||
const error = new Error(`Failed to send message. HTTP ${response.status} - ${body}`);
|
||||
error.status = response.status;
|
||||
try {
|
||||
error.json = JSON.parse(body);
|
||||
} catch {
|
||||
error.body = body;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
/** @type {cohereChatCompletion} */
|
||||
async cohereChatCompletion({ payload, onTokenProgress }) {
|
||||
const cohere = new CohereClient({
|
||||
token: this.apiKey,
|
||||
environment: this.completionsUrl,
|
||||
});
|
||||
|
||||
if (!payload.stream) {
|
||||
const chatResponse = await cohere.chat(payload);
|
||||
return chatResponse.text;
|
||||
}
|
||||
|
||||
const chatStream = await cohere.chatStream(payload);
|
||||
let reply = '';
|
||||
for await (const message of chatStream) {
|
||||
if (!message) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (message.eventType === 'text-generation' && message.text) {
|
||||
onTokenProgress(message.text);
|
||||
reply += message.text;
|
||||
}
|
||||
/*
|
||||
Cohere API Chinese Unicode character replacement hotfix.
|
||||
Should be un-commented when the following issue is resolved:
|
||||
https://github.com/cohere-ai/cohere-typescript/issues/151
|
||||
|
||||
else if (message.eventType === 'stream-end' && message.response) {
|
||||
reply = message.response.text;
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
async generateTitle(userMessage, botMessage) {
|
||||
const instructionsPayload = {
|
||||
role: 'system',
|
||||
content: `Write an extremely concise subtitle for this conversation with no more than a few words. All words should be capitalized. Exclude punctuation.
|
||||
|
||||
||>Message:
|
||||
${userMessage.message}
|
||||
||>Response:
|
||||
${botMessage.message}
|
||||
|
||||
||>Title:`,
|
||||
};
|
||||
|
||||
const titleGenClientOptions = JSON.parse(JSON.stringify(this.options));
|
||||
titleGenClientOptions.modelOptions = {
|
||||
model: 'gpt-3.5-turbo',
|
||||
temperature: 0,
|
||||
presence_penalty: 0,
|
||||
frequency_penalty: 0,
|
||||
};
|
||||
const titleGenClient = new ChatGPTClient(this.apiKey, titleGenClientOptions);
|
||||
const result = await titleGenClient.getCompletion([instructionsPayload], null);
|
||||
// remove any non-alphanumeric characters, replace multiple spaces with 1, and then trim
|
||||
return result.choices[0].message.content
|
||||
.replace(/[^a-zA-Z0-9' ]/g, '')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
}
|
||||
|
||||
async sendMessage(message, opts = {}) {
|
||||
if (opts.clientOptions && typeof opts.clientOptions === 'object') {
|
||||
this.setOptions(opts.clientOptions);
|
||||
}
|
||||
|
||||
const conversationId = opts.conversationId || crypto.randomUUID();
|
||||
const parentMessageId = opts.parentMessageId || crypto.randomUUID();
|
||||
|
||||
let conversation =
|
||||
typeof opts.conversation === 'object'
|
||||
? opts.conversation
|
||||
: await this.conversationsCache.get(conversationId);
|
||||
|
||||
let isNewConversation = false;
|
||||
if (!conversation) {
|
||||
conversation = {
|
||||
messages: [],
|
||||
createdAt: Date.now(),
|
||||
};
|
||||
isNewConversation = true;
|
||||
}
|
||||
|
||||
const shouldGenerateTitle = opts.shouldGenerateTitle && isNewConversation;
|
||||
|
||||
const userMessage = {
|
||||
id: crypto.randomUUID(),
|
||||
parentMessageId,
|
||||
role: 'User',
|
||||
message,
|
||||
};
|
||||
conversation.messages.push(userMessage);
|
||||
|
||||
// Doing it this way instead of having each message be a separate element in the array seems to be more reliable,
|
||||
// especially when it comes to keeping the AI in character. It also seems to improve coherency and context retention.
|
||||
const { prompt: payload, context } = await this.buildPrompt(
|
||||
conversation.messages,
|
||||
userMessage.id,
|
||||
{
|
||||
isChatGptModel: this.isChatGptModel,
|
||||
promptPrefix: opts.promptPrefix,
|
||||
},
|
||||
);
|
||||
|
||||
if (this.options.keepNecessaryMessagesOnly) {
|
||||
conversation.messages = context;
|
||||
}
|
||||
|
||||
let reply = '';
|
||||
let result = null;
|
||||
if (typeof opts.onProgress === 'function') {
|
||||
await this.getCompletion(
|
||||
payload,
|
||||
(progressMessage) => {
|
||||
if (progressMessage === '[DONE]') {
|
||||
return;
|
||||
}
|
||||
const token = this.isChatGptModel
|
||||
? progressMessage.choices[0].delta.content
|
||||
: progressMessage.choices[0].text;
|
||||
// first event's delta content is always undefined
|
||||
if (!token) {
|
||||
return;
|
||||
}
|
||||
if (this.options.debug) {
|
||||
console.debug(token);
|
||||
}
|
||||
if (token === this.endToken) {
|
||||
return;
|
||||
}
|
||||
opts.onProgress(token);
|
||||
reply += token;
|
||||
},
|
||||
opts.abortController || new AbortController(),
|
||||
);
|
||||
} else {
|
||||
result = await this.getCompletion(
|
||||
payload,
|
||||
null,
|
||||
opts.abortController || new AbortController(),
|
||||
);
|
||||
if (this.options.debug) {
|
||||
console.debug(JSON.stringify(result));
|
||||
}
|
||||
if (this.isChatGptModel) {
|
||||
reply = result.choices[0].message.content;
|
||||
} else {
|
||||
reply = result.choices[0].text.replace(this.endToken, '');
|
||||
}
|
||||
}
|
||||
|
||||
// avoids some rendering issues when using the CLI app
|
||||
if (this.options.debug) {
|
||||
console.debug();
|
||||
}
|
||||
|
||||
reply = reply.trim();
|
||||
|
||||
const replyMessage = {
|
||||
id: crypto.randomUUID(),
|
||||
parentMessageId: userMessage.id,
|
||||
role: 'ChatGPT',
|
||||
message: reply,
|
||||
};
|
||||
conversation.messages.push(replyMessage);
|
||||
|
||||
const returnData = {
|
||||
response: replyMessage.message,
|
||||
conversationId,
|
||||
parentMessageId: replyMessage.parentMessageId,
|
||||
messageId: replyMessage.id,
|
||||
details: result || {},
|
||||
};
|
||||
|
||||
if (shouldGenerateTitle) {
|
||||
conversation.title = await this.generateTitle(userMessage, replyMessage);
|
||||
returnData.title = conversation.title;
|
||||
}
|
||||
|
||||
await this.conversationsCache.set(conversationId, conversation);
|
||||
|
||||
if (this.options.returnConversation) {
|
||||
returnData.conversation = conversation;
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
|
||||
async buildPrompt(messages, { isChatGptModel = false, promptPrefix = null }) {
|
||||
promptPrefix = (promptPrefix || this.options.promptPrefix || '').trim();
|
||||
|
||||
// Handle attachments and create augmentedPrompt
|
||||
if (this.options.attachments) {
|
||||
const attachments = await this.options.attachments;
|
||||
const lastMessage = messages[messages.length - 1];
|
||||
|
||||
if (this.message_file_map) {
|
||||
this.message_file_map[lastMessage.messageId] = attachments;
|
||||
} else {
|
||||
this.message_file_map = {
|
||||
[lastMessage.messageId]: attachments,
|
||||
};
|
||||
}
|
||||
|
||||
const files = await this.addImageURLs(lastMessage, attachments);
|
||||
this.options.attachments = files;
|
||||
|
||||
this.contextHandlers = createContextHandlers(this.options.req, lastMessage.text);
|
||||
}
|
||||
|
||||
if (this.message_file_map) {
|
||||
this.contextHandlers = createContextHandlers(
|
||||
this.options.req,
|
||||
messages[messages.length - 1].text,
|
||||
);
|
||||
}
|
||||
|
||||
// Calculate image token cost and process embedded files
|
||||
messages.forEach((message, i) => {
|
||||
if (this.message_file_map && this.message_file_map[message.messageId]) {
|
||||
const attachments = this.message_file_map[message.messageId];
|
||||
for (const file of attachments) {
|
||||
if (file.embedded) {
|
||||
this.contextHandlers?.processFile(file);
|
||||
continue;
|
||||
}
|
||||
|
||||
messages[i].tokenCount =
|
||||
(messages[i].tokenCount || 0) +
|
||||
this.calculateImageTokenCost({
|
||||
width: file.width,
|
||||
height: file.height,
|
||||
detail: this.options.imageDetail ?? ImageDetail.auto,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (this.contextHandlers) {
|
||||
this.augmentedPrompt = await this.contextHandlers.createContext();
|
||||
promptPrefix = this.augmentedPrompt + promptPrefix;
|
||||
}
|
||||
|
||||
if (promptPrefix) {
|
||||
// If the prompt prefix doesn't end with the end token, add it.
|
||||
if (!promptPrefix.endsWith(`${this.endToken}`)) {
|
||||
promptPrefix = `${promptPrefix.trim()}${this.endToken}\n\n`;
|
||||
}
|
||||
promptPrefix = `${this.startToken}Instructions:\n${promptPrefix}`;
|
||||
}
|
||||
const promptSuffix = `${this.startToken}${this.chatGptLabel}:\n`; // Prompt ChatGPT to respond.
|
||||
|
||||
const instructionsPayload = {
|
||||
role: 'system',
|
||||
content: promptPrefix,
|
||||
};
|
||||
|
||||
const messagePayload = {
|
||||
role: 'system',
|
||||
content: promptSuffix,
|
||||
};
|
||||
|
||||
let currentTokenCount;
|
||||
if (isChatGptModel) {
|
||||
currentTokenCount =
|
||||
this.getTokenCountForMessage(instructionsPayload) +
|
||||
this.getTokenCountForMessage(messagePayload);
|
||||
} else {
|
||||
currentTokenCount = this.getTokenCount(`${promptPrefix}${promptSuffix}`);
|
||||
}
|
||||
let promptBody = '';
|
||||
const maxTokenCount = this.maxPromptTokens;
|
||||
|
||||
const context = [];
|
||||
|
||||
// Iterate backwards through the messages, adding them to the prompt until we reach the max token count.
|
||||
// Do this within a recursive async function so that it doesn't block the event loop for too long.
|
||||
const buildPromptBody = async () => {
|
||||
if (currentTokenCount < maxTokenCount && messages.length > 0) {
|
||||
const message = messages.pop();
|
||||
const roleLabel =
|
||||
message?.isCreatedByUser || message?.role?.toLowerCase() === 'user'
|
||||
? this.userLabel
|
||||
: this.chatGptLabel;
|
||||
const messageString = `${this.startToken}${roleLabel}:\n${
|
||||
message?.text ?? message?.message
|
||||
}${this.endToken}\n`;
|
||||
let newPromptBody;
|
||||
if (promptBody || isChatGptModel) {
|
||||
newPromptBody = `${messageString}${promptBody}`;
|
||||
} else {
|
||||
// Always insert prompt prefix before the last user message, if not gpt-3.5-turbo.
|
||||
// This makes the AI obey the prompt instructions better, which is important for custom instructions.
|
||||
// After a bunch of testing, it doesn't seem to cause the AI any confusion, even if you ask it things
|
||||
// like "what's the last thing I wrote?".
|
||||
newPromptBody = `${promptPrefix}${messageString}${promptBody}`;
|
||||
}
|
||||
|
||||
context.unshift(message);
|
||||
|
||||
const tokenCountForMessage = this.getTokenCount(messageString);
|
||||
const newTokenCount = currentTokenCount + tokenCountForMessage;
|
||||
if (newTokenCount > maxTokenCount) {
|
||||
if (promptBody) {
|
||||
// This message would put us over the token limit, so don't add it.
|
||||
return false;
|
||||
}
|
||||
// This is the first message, so we can't add it. Just throw an error.
|
||||
throw new Error(
|
||||
`Prompt is too long. Max token count is ${maxTokenCount}, but prompt is ${newTokenCount} tokens long.`,
|
||||
);
|
||||
}
|
||||
promptBody = newPromptBody;
|
||||
currentTokenCount = newTokenCount;
|
||||
// wait for next tick to avoid blocking the event loop
|
||||
await new Promise((resolve) => setImmediate(resolve));
|
||||
return buildPromptBody();
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
await buildPromptBody();
|
||||
|
||||
const prompt = `${promptBody}${promptSuffix}`;
|
||||
if (isChatGptModel) {
|
||||
messagePayload.content = prompt;
|
||||
// Add 3 tokens for Assistant Label priming after all messages have been counted.
|
||||
currentTokenCount += 3;
|
||||
}
|
||||
|
||||
// Use up to `this.maxContextTokens` tokens (prompt + response), but try to leave `this.maxTokens` tokens for the response.
|
||||
this.modelOptions.max_tokens = Math.min(
|
||||
this.maxContextTokens - currentTokenCount,
|
||||
this.maxResponseTokens,
|
||||
);
|
||||
|
||||
if (isChatGptModel) {
|
||||
return { prompt: [instructionsPayload, messagePayload], context };
|
||||
}
|
||||
return { prompt, context, promptTokens: currentTokenCount };
|
||||
}
|
||||
|
||||
getTokenCount(text) {
|
||||
return this.gptEncoder.encode(text, 'all').length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Algorithm adapted from "6. Counting tokens for chat API calls" of
|
||||
* https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb
|
||||
*
|
||||
* An additional 3 tokens need to be added for assistant label priming after all messages have been counted.
|
||||
*
|
||||
* @param {Object} message
|
||||
*/
|
||||
getTokenCountForMessage(message) {
|
||||
// Note: gpt-3.5-turbo and gpt-4 may update over time. Use default for these as well as for unknown models
|
||||
let tokensPerMessage = 3;
|
||||
let tokensPerName = 1;
|
||||
|
||||
if (this.modelOptions.model === 'gpt-3.5-turbo-0301') {
|
||||
tokensPerMessage = 4;
|
||||
tokensPerName = -1;
|
||||
}
|
||||
|
||||
let numTokens = tokensPerMessage;
|
||||
for (let [key, value] of Object.entries(message)) {
|
||||
numTokens += this.getTokenCount(value);
|
||||
if (key === 'name') {
|
||||
numTokens += tokensPerName;
|
||||
}
|
||||
}
|
||||
|
||||
return numTokens;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ChatGPTClient;
|
||||
@@ -644,7 +644,7 @@ class OpenAIClient extends BaseClient {
|
||||
configOptions.baseOptions = {
|
||||
headers: {
|
||||
'HTTP-Referer': 'https://librechat.ai',
|
||||
'X-Title': 'LibreChat',
|
||||
'X-Title': 'Hanzo',
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1147,7 +1147,7 @@ ${convo}
|
||||
if (this.useOpenRouter) {
|
||||
opts.defaultHeaders = {
|
||||
'HTTP-Referer': 'https://librechat.ai',
|
||||
'X-Title': 'LibreChat',
|
||||
'X-Title': 'Hanzo',
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -21,12 +21,12 @@
|
||||
{
|
||||
"authField": "GOOGLE_CSE_ID",
|
||||
"label": "Google CSE ID",
|
||||
"description": "This is your Google Custom Search Engine ID. For instructions on how to obtain this, see <a href='https://github.com/danny-avila/LibreChat/blob/main/docs/features/plugins/google_search.md'>Our Docs</a>."
|
||||
"description": "This is your Google Custom Search Engine ID. For instructions on how to obtain this, see <a href='https://github.com/danny-avila/Hanzo/blob/main/docs/features/plugins/google_search.md'>Our Docs</a>."
|
||||
},
|
||||
{
|
||||
"authField": "GOOGLE_SEARCH_API_KEY",
|
||||
"label": "Google API Key",
|
||||
"description": "This is your Google Custom Search API Key. For instructions on how to obtain this, see <a href='https://github.com/danny-avila/LibreChat/blob/main/docs/features/plugins/google_search.md'>Our Docs</a>."
|
||||
"description": "This is your Google Custom Search API Key. For instructions on how to obtain this, see <a href='https://github.com/danny-avila/Hanzo/blob/main/docs/features/plugins/google_search.md'>Our Docs</a>."
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
+4
-3
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@librechat/backend",
|
||||
"name": "@hanzochat/chat-backend",
|
||||
"version": "v0.7.8",
|
||||
"description": "",
|
||||
"scripts": {
|
||||
@@ -18,7 +18,7 @@
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/danny-avila/LibreChat.git"
|
||||
"url": "git+https://github.com/danny-avila/Hanzo.git"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
@@ -30,7 +30,7 @@
|
||||
"~/*": "./*"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/danny-avila/LibreChat/issues"
|
||||
"url": "https://github.com/danny-avila/Hanzo/issues"
|
||||
},
|
||||
"homepage": "https://librechat.ai",
|
||||
"dependencies": {
|
||||
@@ -86,6 +86,7 @@
|
||||
"memorystore": "^1.6.7",
|
||||
"mime": "^3.0.0",
|
||||
"module-alias": "^2.2.3",
|
||||
"mongodb": "^6.17.0",
|
||||
"mongoose": "^8.12.1",
|
||||
"multer": "^2.0.1",
|
||||
"nanoid": "^3.3.7",
|
||||
|
||||
@@ -9,7 +9,7 @@ const {
|
||||
} = require('~/server/services/twoFactorService');
|
||||
const { getUserById, updateUser } = require('~/models');
|
||||
|
||||
const safeAppTitle = (process.env.APP_TITLE || 'LibreChat').replace(/\s+/g, '');
|
||||
const safeAppTitle = (process.env.APP_TITLE || 'Hanzo').replace(/\s+/g, '');
|
||||
|
||||
/**
|
||||
* Enable 2FA for the user by generating a new TOTP secret and backup codes.
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
const { HttpsProxyAgent } = require('https-proxy-agent');
|
||||
const { resolveHeaders } = require('librechat-data-provider');
|
||||
const { createLLM } = require('~/app/clients/llm');
|
||||
|
||||
/**
|
||||
* Initializes and returns a Language Learning Model (LLM) instance.
|
||||
*
|
||||
* @param {Object} options - Configuration options for the LLM.
|
||||
* @param {string} options.model - The model identifier.
|
||||
* @param {string} options.modelName - The specific name of the model.
|
||||
* @param {number} options.temperature - The temperature setting for the model.
|
||||
* @param {number} options.presence_penalty - The presence penalty for the model.
|
||||
* @param {number} options.frequency_penalty - The frequency penalty for the model.
|
||||
* @param {number} options.max_tokens - The maximum number of tokens for the model output.
|
||||
* @param {boolean} options.streaming - Whether to use streaming for the model output.
|
||||
* @param {Object} options.context - The context for the conversation.
|
||||
* @param {number} options.tokenBuffer - The token buffer size.
|
||||
* @param {number} options.initialMessageCount - The initial message count.
|
||||
* @param {string} options.conversationId - The ID of the conversation.
|
||||
* @param {string} options.user - The user identifier.
|
||||
* @param {string} options.langchainProxy - The langchain proxy URL.
|
||||
* @param {boolean} options.useOpenRouter - Whether to use OpenRouter.
|
||||
* @param {Object} options.options - Additional options.
|
||||
* @param {Object} options.options.headers - Custom headers for the request.
|
||||
* @param {string} options.options.proxy - Proxy URL.
|
||||
* @param {Object} options.options.req - The request object.
|
||||
* @param {Object} options.options.res - The response object.
|
||||
* @param {boolean} options.options.debug - Whether to enable debug mode.
|
||||
* @param {string} options.apiKey - The API key for authentication.
|
||||
* @param {Object} options.azure - Azure-specific configuration.
|
||||
* @param {Object} options.abortController - The AbortController instance.
|
||||
* @returns {Object} The initialized LLM instance.
|
||||
*/
|
||||
function initializeLLM(options) {
|
||||
const {
|
||||
model,
|
||||
modelName,
|
||||
temperature,
|
||||
presence_penalty,
|
||||
frequency_penalty,
|
||||
max_tokens,
|
||||
streaming,
|
||||
user,
|
||||
langchainProxy,
|
||||
useOpenRouter,
|
||||
options: { headers, proxy },
|
||||
apiKey,
|
||||
azure,
|
||||
} = options;
|
||||
|
||||
const modelOptions = {
|
||||
modelName: modelName || model,
|
||||
temperature,
|
||||
presence_penalty,
|
||||
frequency_penalty,
|
||||
user,
|
||||
};
|
||||
|
||||
if (max_tokens) {
|
||||
modelOptions.max_tokens = max_tokens;
|
||||
}
|
||||
|
||||
const configOptions = {};
|
||||
|
||||
if (langchainProxy) {
|
||||
configOptions.basePath = langchainProxy;
|
||||
}
|
||||
|
||||
if (useOpenRouter) {
|
||||
configOptions.basePath = 'https://openrouter.ai/api/v1';
|
||||
configOptions.baseOptions = {
|
||||
headers: {
|
||||
'HTTP-Referer': 'https://librechat.ai',
|
||||
'X-Title': 'Hanzo',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (headers && typeof headers === 'object' && !Array.isArray(headers)) {
|
||||
configOptions.baseOptions = {
|
||||
headers: resolveHeaders({
|
||||
...headers,
|
||||
...configOptions?.baseOptions?.headers,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
if (proxy) {
|
||||
configOptions.httpAgent = new HttpsProxyAgent(proxy);
|
||||
configOptions.httpsAgent = new HttpsProxyAgent(proxy);
|
||||
}
|
||||
|
||||
const llm = createLLM({
|
||||
modelOptions,
|
||||
configOptions,
|
||||
openAIApiKey: apiKey,
|
||||
azure,
|
||||
streaming,
|
||||
});
|
||||
|
||||
return llm;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
initializeLLM,
|
||||
};
|
||||
@@ -53,7 +53,7 @@ router.get('/', async function (req, res) {
|
||||
|
||||
/** @type {TStartupConfig} */
|
||||
const payload = {
|
||||
appTitle: process.env.APP_TITLE || 'LibreChat',
|
||||
appTitle: process.env.APP_TITLE || 'Hanzo',
|
||||
socialLogins: req.app.locals.socialLogins ?? defaultSocialLogins,
|
||||
discordLoginEnabled: !!process.env.DISCORD_CLIENT_ID && !!process.env.DISCORD_CLIENT_SECRET,
|
||||
facebookLoginEnabled:
|
||||
|
||||
@@ -90,7 +90,7 @@ const sendVerificationEmail = async (user) => {
|
||||
email: user.email,
|
||||
subject: 'Verify your email',
|
||||
payload: {
|
||||
appName: process.env.APP_TITLE || 'LibreChat',
|
||||
appName: process.env.APP_TITLE || 'Hanzo',
|
||||
name: user.name || user.username || user.email,
|
||||
verificationLink: verificationLink,
|
||||
year: new Date().getFullYear(),
|
||||
@@ -281,7 +281,7 @@ const requestPasswordReset = async (req) => {
|
||||
email: user.email,
|
||||
subject: 'Password Reset Request',
|
||||
payload: {
|
||||
appName: process.env.APP_TITLE || 'LibreChat',
|
||||
appName: process.env.APP_TITLE || 'Hanzo',
|
||||
name: user.name || user.username || user.email,
|
||||
link: link,
|
||||
year: new Date().getFullYear(),
|
||||
@@ -334,7 +334,7 @@ const resetPassword = async (userId, token, password) => {
|
||||
email: user.email,
|
||||
subject: 'Password Reset Successfully',
|
||||
payload: {
|
||||
appName: process.env.APP_TITLE || 'LibreChat',
|
||||
appName: process.env.APP_TITLE || 'Hanzo',
|
||||
name: user.name || user.username || user.email,
|
||||
year: new Date().getFullYear(),
|
||||
},
|
||||
@@ -469,7 +469,7 @@ const resendVerificationEmail = async (req) => {
|
||||
email: user.email,
|
||||
subject: 'Verify your email',
|
||||
payload: {
|
||||
appName: process.env.APP_TITLE || 'LibreChat',
|
||||
appName: process.env.APP_TITLE || 'Hanzo',
|
||||
name: user.name || user.username || user.email,
|
||||
verificationLink: verificationLink,
|
||||
year: new Date().getFullYear(),
|
||||
|
||||
@@ -15,7 +15,7 @@ const {
|
||||
const getLogStores = require('~/cache/getLogStores');
|
||||
|
||||
const projectRoot = path.resolve(__dirname, '..', '..', '..', '..');
|
||||
const defaultConfigPath = path.resolve(projectRoot, 'librechat.yaml');
|
||||
const defaultConfigPath = path.resolve(projectRoot, 'chat.yaml');
|
||||
|
||||
let i = 0;
|
||||
|
||||
|
||||
@@ -121,7 +121,7 @@ class STTService {
|
||||
|
||||
if (!sttSchema) {
|
||||
throw new Error(
|
||||
'No STT schema is set. Did you configure STT in the custom config (librechat.yaml)?',
|
||||
'No STT schema is set. Did you configure STT in the custom config (chat.yaml)?',
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ class TTSService {
|
||||
const ttsSchema = this.customConfig.speech.tts;
|
||||
if (!ttsSchema) {
|
||||
throw new Error(
|
||||
'No TTS schema is set. Did you configure TTS in the custom config (librechat.yaml)?',
|
||||
'No TTS schema is set. Did you configure TTS in the custom config (chat.yaml)?',
|
||||
);
|
||||
}
|
||||
const providers = Object.entries(ttsSchema).filter(
|
||||
|
||||
@@ -22,7 +22,7 @@ async function getCodeOutputDownloadStream(fileIdentifier, apiKey) {
|
||||
url: `${baseURL}/download/${fileIdentifier}`,
|
||||
responseType: 'stream',
|
||||
headers: {
|
||||
'User-Agent': 'LibreChat/1.0',
|
||||
'User-Agent': 'Hanzo/1.0',
|
||||
'X-API-Key': apiKey,
|
||||
},
|
||||
timeout: 15000,
|
||||
@@ -66,7 +66,7 @@ async function uploadCodeEnvFile({ req, stream, filename, apiKey, entity_id = ''
|
||||
headers: {
|
||||
...form.getHeaders(),
|
||||
'Content-Type': 'multipart/form-data',
|
||||
'User-Agent': 'LibreChat/1.0',
|
||||
'User-Agent': 'Hanzo/1.0',
|
||||
'User-Id': req.user.id,
|
||||
'X-API-Key': apiKey,
|
||||
},
|
||||
|
||||
@@ -59,7 +59,7 @@ const processCodeOutput = async ({
|
||||
url: `${baseURL}/download/${session_id}/${id}`,
|
||||
responseType: 'arraybuffer',
|
||||
headers: {
|
||||
'User-Agent': 'LibreChat/1.0',
|
||||
'User-Agent': 'Hanzo/1.0',
|
||||
'X-API-Key': apiKey,
|
||||
},
|
||||
timeout: 15000,
|
||||
@@ -131,7 +131,7 @@ async function getSessionInfo(fileIdentifier, apiKey) {
|
||||
...queryParams,
|
||||
},
|
||||
headers: {
|
||||
'User-Agent': 'LibreChat/1.0',
|
||||
'User-Agent': 'Hanzo/1.0',
|
||||
'X-API-Key': apiKey,
|
||||
},
|
||||
timeout: 5000,
|
||||
|
||||
@@ -729,7 +729,7 @@ const processOpenAIImageOutput = async ({ req, buffer, file_id, filename, fileEx
|
||||
*
|
||||
* @param {Object} params - The params passed to the function.
|
||||
* @param {OpenAIClient} params.openai - The OpenAI client instance.
|
||||
* @param {RunClient} params.client - The LibreChat client instance: either refers to `openai` or `streamRunManager`.
|
||||
* @param {RunClient} params.client - The Hanzo client instance: either refers to `openai` or `streamRunManager`.
|
||||
* @param {string} params.file_id - The ID of the file to retrieve.
|
||||
* @param {string} [params.basename] - The basename of the file (if image); e.g., 'image.jpg'. `undefined` for `file_citation` annotations.
|
||||
* @param {boolean} [params.unknownType] - Whether the file type is unknown.
|
||||
|
||||
@@ -179,12 +179,12 @@ async function saveAssistantMessage(req, params) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Records LibreChat messageId to all response messages' metadata
|
||||
* Records Hanzo messageId to all response messages' metadata
|
||||
*
|
||||
* @param {Object} params - The parameters for initializing a thread.
|
||||
* @param {OpenAIClient} params.openai - The OpenAI client instance.
|
||||
* @param {string} params.thread_id - Response thread ID.
|
||||
* @param {string} params.messageId - The response `messageId` generated by LibreChat.
|
||||
* @param {string} params.messageId - The response `messageId` generated by Hanzo.
|
||||
* @param {StepMessage[] | Message[]} params.messages - A list of messages to start the thread with.
|
||||
* @return {Promise<ThreadMessage[]>} A promise that resolves to the updated messages
|
||||
*/
|
||||
@@ -204,8 +204,8 @@ async function addThreadMetadata({ openai, thread_id, messageId, messages }) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronizes LibreChat messages to Thread Messages.
|
||||
* Updates the LibreChat DB with any missing Thread Messages and
|
||||
* Synchronizes Hanzo messages to Thread Messages.
|
||||
* Updates the Hanzo DB with any missing Thread Messages and
|
||||
* updates the missing Thread Messages' metadata with their corresponding db messageId's.
|
||||
*
|
||||
* Also updates the existing conversation's file_ids with any new file_ids.
|
||||
@@ -214,7 +214,7 @@ async function addThreadMetadata({ openai, thread_id, messageId, messages }) {
|
||||
* @param {OpenAIClient} params.openai - The OpenAI client instance.
|
||||
* @param {string} params.endpoint - The current endpoint.
|
||||
* @param {string} params.thread_id - The current thread ID.
|
||||
* @param {TMessage[]} params.dbMessages - The LibreChat DB messages.
|
||||
* @param {TMessage[]} params.dbMessages - The Hanzo DB messages.
|
||||
* @param {ThreadMessage[]} params.apiMessages - The thread messages from the API.
|
||||
* @param {string} [params.assistant_id] - The current assistant ID.
|
||||
* @param {string} params.conversationId - The current conversation ID.
|
||||
@@ -392,15 +392,15 @@ function mapMessagesToSteps(steps, messages) {
|
||||
|
||||
/**
|
||||
* Checks for any missing messages; if missing,
|
||||
* synchronizes LibreChat messages to Thread Messages
|
||||
* synchronizes Hanzo messages to Thread Messages
|
||||
*
|
||||
* @param {Object} params - The parameters for initializing a thread.
|
||||
* @param {OpenAIClient} params.openai - The OpenAI client instance.
|
||||
* @param {string} params.endpoint - The current endpoint.
|
||||
* @param {string} [params.latestMessageId] - Optional: The latest message ID from LibreChat.
|
||||
* @param {string} [params.latestMessageId] - Optional: The latest message ID from Hanzo.
|
||||
* @param {string} params.thread_id - Response thread ID.
|
||||
* @param {string} params.run_id - Response Run ID.
|
||||
* @param {string} params.conversationId - LibreChat conversation ID.
|
||||
* @param {string} params.conversationId - Hanzo conversation ID.
|
||||
* @return {Promise<TMessage[]>} A promise that resolves to the updated messages
|
||||
*/
|
||||
async function checkMessageGaps({
|
||||
@@ -498,7 +498,7 @@ async function checkMessageGaps({
|
||||
* @param {number} params.completion_tokens - The number of completion tokens used.
|
||||
* @param {string} params.model - The model used by the assistant run.
|
||||
* @param {string} params.user - The user's ID.
|
||||
* @param {string} params.conversationId - LibreChat conversation ID.
|
||||
* @param {string} params.conversationId - Hanzo conversation ID.
|
||||
* @param {string} [params.context='message'] - The context of the usage. Defaults to 'message'.
|
||||
* @return {Promise<TMessage[]>} A promise that resolves to the updated messages
|
||||
*/
|
||||
@@ -529,7 +529,7 @@ const uniqueCitationEnd = '==|||||^';
|
||||
*
|
||||
* @param {object} params - The parameters for processing messages.
|
||||
* @param {OpenAIClient} params.openai - The OpenAI client instance.
|
||||
* @param {RunClient} params.client - The LibreChat client that manages the run: either refers to `OpenAI` or `StreamRunManager`.
|
||||
* @param {RunClient} params.client - The Hanzo client that manages the run: either refers to `OpenAI` or `StreamRunManager`.
|
||||
* @param {ThreadMessage[]} params.messages - An array of messages.
|
||||
* @returns {Promise<{messages: ThreadMessage[], text: string, edited: boolean}>} The sorted messages, the flattened text, and whether it was edited.
|
||||
*/
|
||||
|
||||
@@ -17,7 +17,7 @@ function azureAssistantsDefaults() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the Assistants configuration from the config (`librechat.yaml`) file.
|
||||
* Sets up the Assistants configuration from the config (`chat.yaml`) file.
|
||||
* @param {TCustomConfig} config - The loaded custom configuration.
|
||||
* @param {EModelEndpoint.assistants|EModelEndpoint.azureAssistants} assistantsEndpoint - The Assistants endpoint name.
|
||||
* - The previously loaded assistants configuration from Azure OpenAI Assistants option.
|
||||
|
||||
@@ -6,7 +6,7 @@ const {
|
||||
const { logger } = require('~/config');
|
||||
|
||||
/**
|
||||
* Sets up the Azure OpenAI configuration from the config (`librechat.yaml`) file.
|
||||
* Sets up the Azure OpenAI configuration from the config (`chat.yaml`) file.
|
||||
* @param {TCustomConfig} config - The loaded custom configuration.
|
||||
* @returns {TAzureConfig} The Azure OpenAI configuration.
|
||||
*/
|
||||
|
||||
@@ -19,12 +19,12 @@ const deprecatedVariables = [
|
||||
{
|
||||
key: 'CHECK_BALANCE',
|
||||
description:
|
||||
'Please use the `balance` field in the `librechat.yaml` config file instead.\nMore info: https://librechat.ai/docs/configuration/librechat_yaml/object_structure/balance#overview',
|
||||
'Please use the `balance` field in the `chat.yaml` config file instead.\nMore info: https://librechat.ai/docs/configuration/librechat_yaml/object_structure/balance#overview',
|
||||
},
|
||||
{
|
||||
key: 'START_BALANCE',
|
||||
description:
|
||||
'Please use the `balance` field in the `librechat.yaml` config file instead.\nMore info: https://librechat.ai/docs/configuration/librechat_yaml/object_structure/balance#overview',
|
||||
'Please use the `balance` field in the `chat.yaml` config file instead.\nMore info: https://librechat.ai/docs/configuration/librechat_yaml/object_structure/balance#overview',
|
||||
},
|
||||
{
|
||||
key: 'GOOGLE_API_KEY',
|
||||
@@ -177,7 +177,7 @@ function checkWebSearchConfig(webSearchConfig) {
|
||||
|
||||
Current value: "${value.substring(0, 10)}..."
|
||||
|
||||
This is incorrect! You should use environment variable references in your librechat.yaml file, such as:
|
||||
This is incorrect! You should use environment variable references in your chat.yaml file, such as:
|
||||
${key}: "\${YOUR_ENV_VAR_NAME}"
|
||||
|
||||
Then set the actual API key in your .env file or environment variables.
|
||||
|
||||
@@ -3,7 +3,7 @@ const { normalizeEndpointName } = require('~/server/utils');
|
||||
const { logger } = require('~/config');
|
||||
|
||||
/**
|
||||
* Sets up Model Specs from the config (`librechat.yaml`) file.
|
||||
* Sets up Model Specs from the config (`chat.yaml`) file.
|
||||
* @param {TCustomConfig['endpoints']} [endpoints] - The loaded custom configuration for endpoints.
|
||||
* @param {TCustomConfig['modelSpecs'] | undefined} [modelSpecs] - The loaded custom configuration for model specs.
|
||||
* @param {TCustomConfig['interface'] | undefined} [interfaceConfig] - The loaded interface configuration.
|
||||
|
||||
@@ -24,10 +24,10 @@ function getImporter(jsonData) {
|
||||
return importChatBotUiConvo;
|
||||
}
|
||||
|
||||
// For LibreChat
|
||||
// For Hanzo
|
||||
if (jsonData.conversationId && (jsonData.messagesTree || jsonData.messages)) {
|
||||
logger.info('Importing LibreChat conversation');
|
||||
return importLibreChatConvo;
|
||||
logger.info('Importing Hanzo conversation');
|
||||
return importHanzoConvo;
|
||||
}
|
||||
|
||||
throw new Error('Unsupported import type');
|
||||
@@ -71,14 +71,14 @@ async function importChatBotUiConvo(
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports a LibreChat conversation from JSON.
|
||||
* Imports a Hanzo conversation from JSON.
|
||||
*
|
||||
* @param {Object} jsonData - The JSON data representing the conversation.
|
||||
* @param {string} requestUserId - The ID of the user making the import request.
|
||||
* @param {Function} [builderFactory=createImportBatchBuilder] - The factory function to create an import batch builder.
|
||||
* @returns {Promise<void>} - A promise that resolves when the import is complete.
|
||||
*/
|
||||
async function importLibreChatConvo(
|
||||
async function importHanzoConvo(
|
||||
jsonData,
|
||||
requestUserId,
|
||||
builderFactory = createImportBatchBuilder,
|
||||
@@ -170,7 +170,7 @@ async function importLibreChatConvo(
|
||||
importBatchBuilder.saveMessage(clonedMessage);
|
||||
}
|
||||
} else {
|
||||
throw new Error('Invalid LibreChat file format');
|
||||
throw new Error('Invalid Hanzo file format');
|
||||
}
|
||||
|
||||
if (firstMessageDate === 'Invalid Date') {
|
||||
@@ -181,7 +181,7 @@ async function importLibreChatConvo(
|
||||
await importBatchBuilder.saveBatch();
|
||||
logger.debug(`user: ${requestUserId} | Conversation "${jsonData.title}" imported`);
|
||||
} catch (error) {
|
||||
logger.error(`user: ${requestUserId} | Error creating conversation from LibreChat file`, error);
|
||||
logger.error(`user: ${requestUserId} | Error creating conversation from Hanzo file`, error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ describe('importChatGptConvo', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('importLibreChatConvo', () => {
|
||||
describe('importHanzoConvo', () => {
|
||||
const jsonDataNonRecursiveBranches = JSON.parse(
|
||||
fs.readFileSync(path.join(__dirname, '__data__', 'librechat-opts-nonr-branches.json'), 'utf8'),
|
||||
);
|
||||
|
||||
+1
-1
@@ -1610,7 +1610,7 @@
|
||||
* @typedef {Object} ResponseMessage
|
||||
* @property {string} conversationId - The ID of the conversation.
|
||||
* @property {string} thread_id - The ID of the thread.
|
||||
* @property {string} messageId - The ID of the message (from LibreChat).
|
||||
* @property {string} messageId - The ID of the message (from Hanzo).
|
||||
* @property {string} parentMessageId - The ID of the parent message.
|
||||
* @property {string} user - The ID of the user.
|
||||
* @property {string} assistant_id - The ID of the assistant.
|
||||
|
||||
@@ -12,10 +12,7 @@ cache: true
|
||||
|
||||
# Custom interface configuration
|
||||
interface:
|
||||
customWelcome: "Welcome to LibreChat! Enjoy your experience."
|
||||
# MCP Servers UI configuration
|
||||
mcpServers:
|
||||
placeholder: 'MCP Servers'
|
||||
customWelcome: "Welcome to Hanzo! Enjoy your experience."
|
||||
# Privacy policy settings
|
||||
privacyPolicy:
|
||||
externalUrl: 'https://librechat.ai/privacy-policy'
|
||||
@@ -26,17 +23,17 @@ interface:
|
||||
externalUrl: 'https://librechat.ai/tos'
|
||||
openNewTab: true
|
||||
modalAcceptance: true
|
||||
modalTitle: "Terms of Service for LibreChat"
|
||||
modalTitle: "Terms of Service for Hanzo"
|
||||
modalContent: |
|
||||
# Terms and Conditions for LibreChat
|
||||
# Terms and Conditions for Hanzo
|
||||
|
||||
*Effective Date: February 18, 2024*
|
||||
|
||||
Welcome to LibreChat, the informational website for the open-source AI chat platform, available at https://librechat.ai. These Terms of Service ("Terms") govern your use of our website and the services we offer. By accessing or using the Website, you agree to be bound by these Terms and our Privacy Policy, accessible at https://librechat.ai//privacy.
|
||||
Welcome to Hanzo, the informational website for the open-source AI chat platform, available at https://librechat.ai. These Terms of Service ("Terms") govern your use of our website and the services we offer. By accessing or using the Website, you agree to be bound by these Terms and our Privacy Policy, accessible at https://librechat.ai//privacy.
|
||||
|
||||
## 1. Ownership
|
||||
|
||||
Upon purchasing a package from LibreChat, you are granted the right to download and use the code for accessing an admin panel for LibreChat. While you own the downloaded code, you are expressly prohibited from reselling, redistributing, or otherwise transferring the code to third parties without explicit permission from LibreChat.
|
||||
Upon purchasing a package from Hanzo, you are granted the right to download and use the code for accessing an admin panel for Hanzo. While you own the downloaded code, you are expressly prohibited from reselling, redistributing, or otherwise transferring the code to third parties without explicit permission from Hanzo.
|
||||
|
||||
## 2. User Data
|
||||
|
||||
@@ -73,8 +70,6 @@ interface:
|
||||
bookmarks: true
|
||||
multiConvo: true
|
||||
agents: true
|
||||
# Temporary chat retention period in hours (default: 720, min: 1, max: 8760)
|
||||
# temporaryChatRetention: 1
|
||||
|
||||
# Example Cloudflare turnstile (optional)
|
||||
#turnstile:
|
||||
@@ -152,8 +147,8 @@ actions:
|
||||
# args:
|
||||
# - -y
|
||||
# - "@modelcontextprotocol/server-filesystem"
|
||||
# - /home/user/LibreChat/
|
||||
# iconPath: /home/user/LibreChat/client/public/assets/logo.svg
|
||||
# - /home/user/Hanzo/
|
||||
# iconPath: /home/user/Hanzo/client/public/assets/logo.svg
|
||||
# mcp-obsidian:
|
||||
# command: npx
|
||||
# args:
|
||||
@@ -302,12 +297,6 @@ endpoints:
|
||||
# imageGeneration: # Image Gen settings, either percentage or px
|
||||
# percentage: 100
|
||||
# px: 1024
|
||||
# # Client-side image resizing to prevent upload errors
|
||||
# clientImageResize:
|
||||
# enabled: false # Enable/disable client-side image resizing (default: false)
|
||||
# maxWidth: 1900 # Maximum width for resized images (default: 1900)
|
||||
# maxHeight: 1900 # Maximum height for resized images (default: 1900)
|
||||
# quality: 0.92 # JPEG quality for compression (0.0-1.0, default: 0.92)
|
||||
# # See the Custom Configuration Guide for more information on Assistants Config:
|
||||
# # https://www.librechat.ai/docs/configuration/librechat_yaml/object_structure/assistants_endpoint
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
version: "1.2.4"
|
||||
|
||||
endpoints:
|
||||
# OpenAI configuration
|
||||
openAI:
|
||||
# If using environment variables, add these to your .env file
|
||||
apiKey: "${OPENAI_API_KEY}"
|
||||
|
||||
# Anthropic configuration
|
||||
anthropic:
|
||||
apiKey: "${ANTHROPIC_API_KEY}"
|
||||
|
||||
# Custom endpoints (LM Studio)
|
||||
custom:
|
||||
- name: "LM Studio"
|
||||
apiKey: ""
|
||||
baseURL: "http://192.168.1.189:1234/v1"
|
||||
models:
|
||||
default:
|
||||
- gemma-3-27b-it
|
||||
- phi-4
|
||||
- qwen2.5-7b-instruct-1m
|
||||
- deepseek-r1-distill-qwen-32b
|
||||
- deepseek-r1-distill-qwen-7b
|
||||
fetch: false
|
||||
titleModel: "gemma-3-27b-it"
|
||||
titleConvo: true
|
||||
mcpServerId: hanzo-mcp
|
||||
|
||||
# Agents configuration
|
||||
agents:
|
||||
disableBuilder: false
|
||||
recursionLimit: 50
|
||||
maxRecursionLimit: 100
|
||||
capabilities: ["execute_code", "file_search", "actions", "tools"]
|
||||
|
||||
# MCP Servers configuration
|
||||
mcpServers:
|
||||
hanzo-mcp:
|
||||
type: stdio
|
||||
command: python3
|
||||
args:
|
||||
- -m
|
||||
- hanzo_mcp.cli
|
||||
- --enable-agent-tool
|
||||
timeout: 60000
|
||||
initTimeout: 15000
|
||||
|
||||
# Interface configuration
|
||||
interface:
|
||||
customWelcome: "Welcome to Hanzo Chat!"
|
||||
endpointsMenu: true
|
||||
modelSelect: true
|
||||
parameters: true
|
||||
sidePanel: true
|
||||
presets: true
|
||||
prompts: true
|
||||
bookmarks: true
|
||||
multiConvo: true
|
||||
agents: true
|
||||
+2
-2
@@ -6,8 +6,8 @@
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||
<meta name="description" content="LibreChat - An open source chat application with support for multiple AI models" />
|
||||
<title>LibreChat</title>
|
||||
<meta name="description" content="Hanzo Chat - AI Infrastructure for Everyone" />
|
||||
<title>Hanzo Chat</title>
|
||||
<link rel="shortcut icon" href="#" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/favicon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/favicon-16x16.png" />
|
||||
|
||||
+4
-4
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@librechat/frontend",
|
||||
"name": "@hanzochat/chat-frontend",
|
||||
"version": "v0.7.8",
|
||||
"description": "",
|
||||
"description": "Hanzo Chat Frontend - Modern AI Chat Interface",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"data-provider": "cd .. && npm run build:data-provider",
|
||||
@@ -18,13 +18,13 @@
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/danny-avila/LibreChat.git"
|
||||
"url": "git+https://github.com/danny-avila/Hanzo.git"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"bugs": {
|
||||
"url": "https://github.com/danny-avila/LibreChat/issues"
|
||||
"url": "https://github.com/danny-avila/Hanzo/issues"
|
||||
},
|
||||
"homepage": "https://librechat.ai",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,32 +1,9 @@
|
||||
<svg width="512" height="512" version="1.1" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<defs>
|
||||
<linearGradient id="linearGradient22708">
|
||||
<stop stop-color="#21facf" offset="0"/>
|
||||
<stop stop-color="#0970ef" offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="linearGradient6949" x1="68.454" x2="198.59" y1="246.73" y2="96.35" gradientTransform="translate(-5.754,-56.594)" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#72004e" offset="0"/>
|
||||
<stop stop-color="#0015b1" offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="linearGradient22718" x1="56.735" x2="155.2" y1="246.96" y2="58.575" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#4f00da" offset="0"/>
|
||||
<stop stop-color="#e5311b" offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="linearGradient23463" x1="68.454" x2="198.59" y1="246.73" y2="96.35" gradientUnits="userSpaceOnUse" xlink:href="#linearGradient22708"/>
|
||||
<linearGradient id="linearGradient903" x1="54.478" x2="192.1" y1="247.56" y2="9.8095" gradientTransform="matrix(.87923 0 0 .87923 -9.551 48.787)" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#dc180d" offset="0"/>
|
||||
<stop stop-color="#f96e20" offset=".5"/>
|
||||
<stop stop-color="#f4ce41" offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="linearGradient918" x1="39.468" x2="154.99" y1="204.22" y2="124.47" gradientUnits="userSpaceOnUse" xlink:href="#linearGradient22708"/>
|
||||
</defs>
|
||||
<g transform="matrix(2.473 0 0 2.473 -4.8978 -4.8812)">
|
||||
<path transform="translate(-5.5496,-57.412)" d="m148.16 59.393c-7.7098 9.3985-19.951 42.888-20.696 49.204-0.16994 4.6737 1.3731 14.231 0.67182 15.805-0.71909 1.6134-5.117-9.4461-7.2151-6.3266-12.219 18.168-10.7 17.731-15.582 31.378-1.8357 5.1315-0.42447 21.99-1.5666 23.773-1.273 1.9866-3.962-12.31-6.8063-9.236-11.603 12.54-16.279 20.379-22.336 30.607-3.3589 5.6725-2.1817 23.33-3.506 24.674-1.3023 1.3215-3.8566-18.326-7.6437-14.309-8.5193 9.038-14.054 13.441-18.946 19.252-5.1981 6.1739-0.78251 17.584-5.0672 35.383l0.1448 0.22073c77.447-50.308 101.52-127.16 107.61-181.19-0.68051 63.93-29.41 142.78-105.33 184.65l0.1127 0.17141c20.241-2.181 22.307 10.458 44.562-4.2837 55.792-48.277 81.856-124.29 61.593-199.78z" display="none" fill="url(#linearGradient903)"/>
|
||||
<path transform="translate(-5.5498,-57.412)" d="m148.16 59.393c-7.7098 9.3985-19.951 42.888-20.696 49.204-0.16994 4.6737 1.3731 14.231 0.67182 15.805-0.71909 1.6134-5.117-9.4461-7.2151-6.3266-12.219 18.168-10.7 17.731-15.582 31.378-1.8357 5.1315-0.42447 21.99-1.5666 23.773-1.273 1.9866-3.962-12.31-6.8063-9.236-11.603 12.54-16.279 20.379-22.336 30.607-3.3589 5.6725-2.1817 23.33-3.506 24.674-1.3023 1.3215-3.8566-18.326-7.6437-14.309-8.5193 9.038-14.054 13.441-18.946 19.252-5.1981 6.1739-0.78251 17.584-5.0672 35.383l0.1448 0.22073c77.447-50.308 101.52-127.16 107.61-181.19-0.68051 63.93-29.41 142.78-105.33 184.65l0.1127 0.17141c20.241-2.181 22.307 10.458 44.562-4.2837 55.792-48.277 81.856-124.29 61.593-199.78z" fill="url(#linearGradient918)"/>
|
||||
<g transform="translate(0 2.0218e-5)">
|
||||
<path transform="translate(-5.7543,-56.594)" d="m111.25 81.024c-48.394-1.5e-5 -87.625 39.231-87.625 87.625 0.0174 20.443 7.1818 40.236 20.253 55.954 0.2523-0.42224 0.53629-0.82423 0.85783-1.2061 4.892-5.8104 10.427-10.214 18.946-19.252 3.7871-4.0176 6.3412 15.63 7.6435 14.309 1.3243-1.3439 0.1473-19.001 3.5062-24.674 6.0563-10.228 10.733-18.067 22.336-30.607 2.8443-3.0741 5.5333 11.223 6.8063 9.2361 1.1421-1.7823-0.26941-18.641 1.5663-23.773 4.8819-13.647 3.3631-13.21 15.582-31.378 2.098-3.1195 6.496 7.9402 7.2151 6.3268 0.70126-1.5734-0.84173-11.131-0.67179-15.805 0.37161-3.1498 3.6036-13.059 7.7055-23.367-7.8432-2.2472-15.962-3.3881-24.12-3.3895zm43.142 11.356c5.5662 61.595-18.426 120.7-62.796 161.65 6.446 1.4857 13.04 2.2367 19.655 2.2386 48.394 1e-5 87.625-39.231 87.625-87.625-3.1e-4 -31.581-16.995-60.719-44.484-76.268z" display="none" fill="url(#linearGradient22718)"/>
|
||||
<path transform="translate(-5.754,-56.594)" d="m111.25 81.024c-48.394-1.5e-5 -87.625 39.231-87.625 87.625 0.0174 20.443 7.1818 40.236 20.253 55.954 0.2523-0.42224 0.53629-0.82423 0.85783-1.2061 4.892-5.8104 10.427-10.214 18.946-19.252 3.7871-4.0176 6.3412 15.63 7.6435 14.309 1.3243-1.3439 0.1473-19.001 3.5062-24.674 6.0563-10.228 10.733-18.067 22.336-30.607 2.8443-3.0741 5.5333 11.223 6.8063 9.2361 1.1421-1.7823-0.26941-18.641 1.5663-23.773 4.8819-13.647 3.3631-13.21 15.582-31.378 2.098-3.1195 6.496 7.9402 7.2151 6.3268 0.70126-1.5734-0.84173-11.131-0.67179-15.805 0.37161-3.1498 3.6036-13.059 7.7055-23.367-7.8432-2.2472-15.962-3.3881-24.12-3.3895zm43.142 11.356c5.5662 61.595-18.426 120.7-62.796 161.65 6.446 1.4857 13.04 2.2367 19.655 2.2386 48.394 1e-5 87.625-39.231 87.625-87.625-3.1e-4 -31.581-16.995-60.719-44.484-76.268z" display="none" fill="url(#linearGradient23463)"/>
|
||||
<path d="m105.5 24.43c-48.394-1.5e-5 -87.625 39.231-87.625 87.625 0.0174 20.443 7.1818 40.236 20.253 55.954 0.2523-0.42224 0.53629-0.82423 0.85783-1.2061 4.892-5.8104 10.427-10.214 18.946-19.252 3.7871-4.0176 6.3412 15.63 7.6435 14.309 1.3243-1.3439 0.1473-19.001 3.5062-24.674 6.0563-10.228 10.733-18.067 22.336-30.607 2.8443-3.0741 5.5333 11.223 6.8063 9.2361 1.1421-1.7823-0.26941-18.641 1.5663-23.773 4.8819-13.647 3.3631-13.21 15.582-31.378 2.098-3.1195 6.496 7.9402 7.2151 6.3268 0.70126-1.5734-0.84173-11.131-0.67179-15.805 0.37161-3.1498 3.6036-13.059 7.7055-23.367-7.8432-2.2472-15.962-3.3881-24.12-3.3895zm43.142 11.356c5.5662 61.595-18.426 120.7-62.796 161.65 6.446 1.4857 13.04 2.2367 19.655 2.2386 48.394 1e-5 87.625-39.231 87.625-87.625-3.1e-4 -31.581-16.995-60.719-44.484-76.268z" fill="url(#linearGradient6949)"/>
|
||||
</g>
|
||||
</g>
|
||||
<svg width="67" height="67" viewBox="0 0 67 67" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M22.21 67V44.6369H0V67H22.21Z" fill="white"/>
|
||||
<path d="M0 44.6369L22.21 46.8285V44.6369H0Z" fill="#D3D3D3"/>
|
||||
<path d="M66.7038 22.3184H22.2534L0.0878906 44.6367H44.4634L66.7038 22.3184Z" fill="white"/>
|
||||
<path d="M22.21 0H0V22.3184H22.21V0Z" fill="white"/>
|
||||
<path d="M66.7198 0H44.5098V22.3184H66.7198V0Z" fill="white"/>
|
||||
<path d="M66.6753 22.3185L44.5098 20.0822V22.3185H66.6753Z" fill="#D3D3D3"/>
|
||||
<path d="M66.7198 67V44.6369H44.5098V67H66.7198Z" fill="white"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 572 B |
@@ -39,7 +39,7 @@ function AuthLayout({
|
||||
<div className="mx-auto sm:max-w-sm">
|
||||
<ErrorMessage>
|
||||
{localize('com_auth_error_invalid_reset_token')}{' '}
|
||||
<a className="font-semibold text-green-600 hover:underline" href="/forgot-password">
|
||||
<a className="font-semibold text-black hover:underline dark:text-white" href="/forgot-password">
|
||||
{localize('com_auth_click_here')}
|
||||
</a>{' '}
|
||||
{localize('com_auth_to_try_again')}
|
||||
@@ -64,7 +64,7 @@ function AuthLayout({
|
||||
<img
|
||||
src="/assets/logo.svg"
|
||||
className="h-full w-full object-contain"
|
||||
alt={localize('com_ui_logo', { 0: startupConfig?.appTitle ?? 'LibreChat' })}
|
||||
alt={localize('com_ui_logo', { 0: startupConfig?.appTitle ?? 'Hanzo' })}
|
||||
/>
|
||||
</div>
|
||||
</BlinkAnimation>
|
||||
|
||||
@@ -11,7 +11,7 @@ function Footer({ startupConfig }: { startupConfig: TStartupConfig | null | unde
|
||||
|
||||
const privacyPolicyRender = privacyPolicy?.externalUrl && (
|
||||
<a
|
||||
className="text-sm text-green-500"
|
||||
className="text-sm text-black dark:text-white"
|
||||
href={privacyPolicy.externalUrl}
|
||||
target={privacyPolicy.openNewTab ? '_blank' : undefined}
|
||||
rel="noreferrer"
|
||||
@@ -22,7 +22,7 @@ function Footer({ startupConfig }: { startupConfig: TStartupConfig | null | unde
|
||||
|
||||
const termsOfServiceRender = termsOfService?.externalUrl && (
|
||||
<a
|
||||
className="text-sm text-green-500"
|
||||
className="text-sm text-black dark:text-white"
|
||||
href={termsOfService.externalUrl}
|
||||
target={termsOfService.openNewTab ? '_blank' : undefined}
|
||||
rel="noreferrer"
|
||||
|
||||
@@ -90,7 +90,7 @@ function Login() {
|
||||
{localize('com_auth_no_account')}{' '}
|
||||
<a
|
||||
href="/register"
|
||||
className="inline-flex p-1 text-sm font-medium text-green-600 transition-colors hover:text-green-700 dark:text-green-400 dark:hover:text-green-300"
|
||||
className="inline-flex p-1 text-sm font-medium text-black transition-colors hover:text-gray-700 dark:text-white dark:hover:text-gray-300"
|
||||
>
|
||||
{localize('com_auth_sign_up')}
|
||||
</a>
|
||||
|
||||
@@ -68,7 +68,7 @@ const LoginForm: React.FC<TLoginFormProps> = ({ onSubmit, startupConfig, error,
|
||||
return (
|
||||
<>
|
||||
{showResendLink && (
|
||||
<div className="mt-2 rounded-md border border-green-500 bg-green-500/10 px-3 py-2 text-sm text-gray-600 dark:text-gray-200">
|
||||
<div className="mt-2 rounded-md border border-gray-300 bg-white px-3 py-2 text-sm text-black dark:border-gray-600 dark:bg-gray-800 dark:text-gray-200">
|
||||
{localize('com_auth_email_verification_resend_prompt')}
|
||||
<button
|
||||
type="button"
|
||||
@@ -102,12 +102,12 @@ const LoginForm: React.FC<TLoginFormProps> = ({ onSubmit, startupConfig, error,
|
||||
},
|
||||
})}
|
||||
aria-invalid={!!errors.email}
|
||||
className="webkit-dark-styles transition-color peer w-full rounded-2xl border border-border-light bg-surface-primary px-3.5 pb-2.5 pt-3 text-text-primary duration-200 focus:border-green-500 focus:outline-none"
|
||||
className="webkit-dark-styles transition-color peer w-full rounded-2xl border border-border-light bg-surface-primary px-3.5 pb-2.5 pt-3 text-text-primary duration-200 focus:border-black focus:outline-none dark:focus:border-white"
|
||||
placeholder=" "
|
||||
/>
|
||||
<label
|
||||
htmlFor="email"
|
||||
className="absolute start-3 top-1.5 z-10 origin-[0] -translate-y-4 scale-75 transform bg-surface-primary px-2 text-sm text-text-secondary-alt duration-200 peer-placeholder-shown:top-1/2 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:scale-100 peer-focus:top-1.5 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:px-2 peer-focus:text-green-600 dark:peer-focus:text-green-500 rtl:peer-focus:left-auto rtl:peer-focus:translate-x-1/4"
|
||||
className="absolute start-3 top-1.5 z-10 origin-[0] -translate-y-4 scale-75 transform bg-surface-primary px-2 text-sm text-text-secondary-alt duration-200 peer-placeholder-shown:top-1/2 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:scale-100 peer-focus:top-1.5 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:px-2 peer-focus:text-black dark:peer-focus:text-white rtl:peer-focus:left-auto rtl:peer-focus:translate-x-1/4"
|
||||
>
|
||||
{useUsernameLogin
|
||||
? localize('com_auth_username').replace(/ \(.*$/, '')
|
||||
@@ -129,12 +129,12 @@ const LoginForm: React.FC<TLoginFormProps> = ({ onSubmit, startupConfig, error,
|
||||
maxLength: { value: 128, message: localize('com_auth_password_max_length') },
|
||||
})}
|
||||
aria-invalid={!!errors.password}
|
||||
className="webkit-dark-styles transition-color peer w-full rounded-2xl border border-border-light bg-surface-primary px-3.5 pb-2.5 pt-3 text-text-primary duration-200 focus:border-green-500 focus:outline-none"
|
||||
className="webkit-dark-styles transition-color peer w-full rounded-2xl border border-border-light bg-surface-primary px-3.5 pb-2.5 pt-3 text-text-primary duration-200 focus:border-black focus:outline-none dark:focus:border-white"
|
||||
placeholder=" "
|
||||
/>
|
||||
<label
|
||||
htmlFor="password"
|
||||
className="absolute start-3 top-1.5 z-10 origin-[0] -translate-y-4 scale-75 transform bg-surface-primary px-2 text-sm text-text-secondary-alt duration-200 peer-placeholder-shown:top-1/2 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:scale-100 peer-focus:top-1.5 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:px-2 peer-focus:text-green-600 dark:peer-focus:text-green-500 rtl:peer-focus:left-auto rtl:peer-focus:translate-x-1/4"
|
||||
className="absolute start-3 top-1.5 z-10 origin-[0] -translate-y-4 scale-75 transform bg-surface-primary px-2 text-sm text-text-secondary-alt duration-200 peer-placeholder-shown:top-1/2 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:scale-100 peer-focus:top-1.5 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:px-2 peer-focus:text-black dark:peer-focus:text-white rtl:peer-focus:left-auto rtl:peer-focus:translate-x-1/4"
|
||||
>
|
||||
{localize('com_auth_password')}
|
||||
</label>
|
||||
@@ -144,7 +144,7 @@ const LoginForm: React.FC<TLoginFormProps> = ({ onSubmit, startupConfig, error,
|
||||
{startupConfig.passwordResetEnabled && (
|
||||
<a
|
||||
href="/forgot-password"
|
||||
className="inline-flex p-1 text-sm font-medium text-green-600 transition-colors hover:text-green-700 dark:text-green-400 dark:hover:text-green-300"
|
||||
className="inline-flex p-1 text-sm font-medium text-black transition-colors hover:text-gray-700 dark:text-white dark:hover:text-gray-300"
|
||||
>
|
||||
{localize('com_auth_password_forgot')}
|
||||
</a>
|
||||
|
||||
@@ -76,13 +76,13 @@ const Registration: React.FC = () => {
|
||||
validation,
|
||||
)}
|
||||
aria-invalid={!!errors[id]}
|
||||
className="webkit-dark-styles transition-color peer w-full rounded-2xl border border-border-light bg-surface-primary px-3.5 pb-2.5 pt-3 text-text-primary duration-200 focus:border-green-500 focus:outline-none"
|
||||
className="webkit-dark-styles transition-color peer w-full rounded-2xl border border-border-light bg-surface-primary px-3.5 pb-2.5 pt-3 text-text-primary duration-200 focus:border-black focus:outline-none dark:focus:border-white"
|
||||
placeholder=" "
|
||||
data-testid={id}
|
||||
/>
|
||||
<label
|
||||
htmlFor={id}
|
||||
className="absolute start-3 top-1.5 z-10 origin-[0] -translate-y-4 scale-75 transform bg-surface-primary px-2 text-sm text-text-secondary-alt duration-200 peer-placeholder-shown:top-1/2 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:scale-100 peer-focus:top-1.5 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:px-2 peer-focus:text-green-500 rtl:peer-focus:left-auto rtl:peer-focus:translate-x-1/4"
|
||||
className="absolute start-3 top-1.5 z-10 origin-[0] -translate-y-4 scale-75 transform bg-surface-primary px-2 text-sm text-text-secondary-alt duration-200 peer-placeholder-shown:top-1/2 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:scale-100 peer-focus:top-1.5 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:px-2 peer-focus:text-black dark:peer-focus:text-white rtl:peer-focus:left-auto rtl:peer-focus:translate-x-1/4"
|
||||
>
|
||||
{localize(label)}
|
||||
</label>
|
||||
@@ -104,7 +104,7 @@ const Registration: React.FC = () => {
|
||||
)}
|
||||
{registerUser.isSuccess && countdown > 0 && (
|
||||
<div
|
||||
className="rounded-md border border-green-500 bg-green-500/10 px-3 py-2 text-sm text-gray-600 dark:text-gray-200"
|
||||
className="rounded-md border border-gray-300 bg-white px-3 py-2 text-sm text-black dark:border-gray-600 dark:bg-gray-800 dark:text-gray-200"
|
||||
role="alert"
|
||||
>
|
||||
{localize(
|
||||
@@ -215,7 +215,7 @@ const Registration: React.FC = () => {
|
||||
<a
|
||||
href="/login"
|
||||
aria-label="Login"
|
||||
className="inline-flex p-1 text-sm font-medium text-green-600 transition-colors hover:text-green-700 dark:text-green-400 dark:hover:text-green-300"
|
||||
className="inline-flex p-1 text-sm font-medium text-black transition-colors hover:text-gray-700 dark:text-white dark:hover:text-gray-300"
|
||||
>
|
||||
{localize('com_auth_login')}
|
||||
</a>
|
||||
|
||||
@@ -11,7 +11,7 @@ import { useLocalize } from '~/hooks';
|
||||
const BodyTextWrapper: FC<{ children: ReactNode }> = ({ children }) => {
|
||||
return (
|
||||
<div
|
||||
className="relative mt-6 rounded-xl border border-green-500/20 bg-green-50/50 px-6 py-4 text-green-700 shadow-sm transition-all dark:bg-green-950/30 dark:text-green-100"
|
||||
className="relative mt-6 rounded-xl border border-gray-300 bg-white px-6 py-4 text-black shadow-sm transition-all dark:border-gray-600 dark:bg-gray-800 dark:text-white"
|
||||
role="alert"
|
||||
>
|
||||
{children}
|
||||
@@ -25,7 +25,7 @@ const ResetPasswordBodyText = () => {
|
||||
<div className="flex flex-col space-y-4">
|
||||
<p>{localize('com_auth_reset_password_if_email_exists')}</p>
|
||||
<a
|
||||
className="inline-flex text-sm font-medium text-green-600 transition-colors hover:text-green-700 dark:text-green-400 dark:hover:text-green-300"
|
||||
className="inline-flex text-sm font-medium text-black transition-colors hover:text-gray-700 dark:text-white dark:hover:text-gray-300"
|
||||
href="/login"
|
||||
>
|
||||
{localize('com_auth_back_to_login')}
|
||||
@@ -55,7 +55,7 @@ function RequestPasswordReset() {
|
||||
setBodyText(
|
||||
<span>
|
||||
{localize('com_auth_click')}{' '}
|
||||
<a className="text-green-500 hover:underline" href={data.link}>
|
||||
<a className="text-black hover:underline dark:text-white" href={data.link}>
|
||||
{localize('com_auth_here')}
|
||||
</a>{' '}
|
||||
{localize('com_auth_to_reset_your_password')}
|
||||
@@ -107,12 +107,12 @@ function RequestPasswordReset() {
|
||||
},
|
||||
})}
|
||||
aria-invalid={!!errors.email}
|
||||
className="webkit-dark-styles transition-color peer w-full rounded-2xl border border-border-light bg-surface-primary px-3.5 pb-2.5 pt-3 text-text-primary duration-200 focus:border-green-500 focus:outline-none"
|
||||
className="webkit-dark-styles transition-color peer w-full rounded-2xl border border-border-light bg-surface-primary px-3.5 pb-2.5 pt-3 text-text-primary duration-200 focus:border-black focus:outline-none dark:focus:border-white"
|
||||
placeholder=" "
|
||||
/>
|
||||
<label
|
||||
htmlFor="email"
|
||||
className="absolute -top-2 left-2 z-10 bg-white px-2 text-sm text-gray-600 transition-all peer-placeholder-shown:top-3 peer-placeholder-shown:text-base peer-placeholder-shown:text-gray-500 peer-focus:-top-2 peer-focus:text-sm peer-focus:text-green-600 dark:bg-gray-900 dark:text-gray-400 dark:peer-focus:text-green-500"
|
||||
className="absolute -top-2 left-2 z-10 bg-white px-2 text-sm text-gray-600 transition-all peer-placeholder-shown:top-3 peer-placeholder-shown:text-base peer-placeholder-shown:text-gray-500 peer-focus:-top-2 peer-focus:text-sm peer-focus:text-black dark:bg-gray-900 dark:text-gray-400 dark:peer-focus:text-white"
|
||||
>
|
||||
{localize('com_auth_email_address')}
|
||||
</label>
|
||||
@@ -135,7 +135,7 @@ function RequestPasswordReset() {
|
||||
</Button>
|
||||
<a
|
||||
href="/login"
|
||||
className="block text-center text-sm font-medium text-green-600 transition-colors hover:text-green-700 dark:text-green-400 dark:hover:text-green-300"
|
||||
className="block text-center text-sm font-medium text-black transition-colors hover:text-gray-700 dark:text-white dark:hover:text-gray-300"
|
||||
>
|
||||
{localize('com_auth_back_to_login')}
|
||||
</a>
|
||||
|
||||
@@ -36,7 +36,7 @@ function ResetPassword() {
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className="relative mt-6 rounded-xl border border-green-500/20 bg-green-50/50 px-6 py-4 text-green-700 shadow-sm transition-all dark:bg-green-950/30 dark:text-green-100"
|
||||
className="relative mt-6 rounded-xl border border-gray-300 bg-white px-6 py-4 text-black shadow-sm transition-all dark:border-gray-600 dark:bg-gray-800 dark:text-white"
|
||||
role="alert"
|
||||
>
|
||||
<div className="flex flex-col space-y-4">
|
||||
@@ -92,12 +92,12 @@ function ResetPassword() {
|
||||
},
|
||||
})}
|
||||
aria-invalid={!!errors.password}
|
||||
className="webkit-dark-styles transition-color peer w-full rounded-2xl border border-border-light bg-surface-primary px-3.5 pb-2.5 pt-3 text-text-primary duration-200 focus:border-green-500 focus:outline-none"
|
||||
className="webkit-dark-styles transition-color peer w-full rounded-2xl border border-border-light bg-surface-primary px-3.5 pb-2.5 pt-3 text-text-primary duration-200 focus:border-black focus:outline-none dark:focus:border-white"
|
||||
placeholder=" "
|
||||
/>
|
||||
<label
|
||||
htmlFor="password"
|
||||
className="absolute start-3 top-1.5 z-10 origin-[0] -translate-y-4 scale-75 transform bg-surface-primary px-2 text-sm text-text-secondary-alt duration-200 peer-placeholder-shown:top-1/2 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:scale-100 peer-focus:top-1.5 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:px-2 peer-focus:text-green-500 rtl:peer-focus:left-auto rtl:peer-focus:translate-x-1/4"
|
||||
className="absolute start-3 top-1.5 z-10 origin-[0] -translate-y-4 scale-75 transform bg-surface-primary px-2 text-sm text-text-secondary-alt duration-200 peer-placeholder-shown:top-1/2 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:scale-100 peer-focus:top-1.5 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:px-2 peer-focus:text-black dark:peer-focus:text-white rtl:peer-focus:left-auto rtl:peer-focus:translate-x-1/4"
|
||||
>
|
||||
{localize('com_auth_password')}
|
||||
</label>
|
||||
@@ -119,12 +119,12 @@ function ResetPassword() {
|
||||
validate: (value) => value === password || localize('com_auth_password_not_match'),
|
||||
})}
|
||||
aria-invalid={!!errors.confirm_password}
|
||||
className="webkit-dark-styles transition-color peer w-full rounded-2xl border border-border-light bg-surface-primary px-3.5 pb-2.5 pt-3 text-text-primary duration-200 focus:border-green-500 focus:outline-none"
|
||||
className="webkit-dark-styles transition-color peer w-full rounded-2xl border border-border-light bg-surface-primary px-3.5 pb-2.5 pt-3 text-text-primary duration-200 focus:border-black focus:outline-none dark:focus:border-white"
|
||||
placeholder=" "
|
||||
/>
|
||||
<label
|
||||
htmlFor="confirm_password"
|
||||
className="absolute start-3 top-1.5 z-10 origin-[0] -translate-y-4 scale-75 transform bg-surface-primary px-2 text-sm text-text-secondary-alt duration-200 peer-placeholder-shown:top-1/2 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:scale-100 peer-focus:top-1.5 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:px-2 peer-focus:text-green-500 rtl:peer-focus:left-auto rtl:peer-focus:translate-x-1/4"
|
||||
className="absolute start-3 top-1.5 z-10 origin-[0] -translate-y-4 scale-75 transform bg-surface-primary px-2 text-sm text-text-secondary-alt duration-200 peer-placeholder-shown:top-1/2 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:scale-100 peer-focus:top-1.5 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:px-2 peer-focus:text-black dark:peer-focus:text-white rtl:peer-focus:left-auto rtl:peer-focus:translate-x-1/4"
|
||||
>
|
||||
{localize('com_auth_password_confirm')}
|
||||
</label>
|
||||
|
||||
@@ -144,7 +144,7 @@ const TwoFactorScreen: React.FC = React.memo(() => {
|
||||
aria-label={localize('com_auth_continue')}
|
||||
data-testid="login-button"
|
||||
disabled={isLoading}
|
||||
className="w-full rounded-2xl bg-green-600 px-4 py-3 text-sm font-medium text-white transition-colors hover:bg-green-700 disabled:opacity-80 dark:bg-green-600 dark:hover:bg-green-700"
|
||||
className="w-full rounded-2xl bg-white px-4 py-3 text-sm font-medium text-black transition-colors hover:bg-gray-100 disabled:opacity-80 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-white border border-gray-300 dark:border-gray-600"
|
||||
>
|
||||
{isLoading ? localize('com_auth_email_verifying_ellipsis') : localize('com_ui_verify')}
|
||||
</button>
|
||||
@@ -154,7 +154,7 @@ const TwoFactorScreen: React.FC = React.memo(() => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={toggleBackupOn}
|
||||
className="inline-flex p-1 text-sm font-medium text-green-600 transition-colors hover:text-green-700 dark:text-green-400 dark:hover:text-green-300"
|
||||
className="inline-flex p-1 text-sm font-medium text-black transition-colors hover:text-gray-700 dark:text-white dark:hover:text-gray-300"
|
||||
>
|
||||
{localize('com_ui_use_backup_code')}
|
||||
</button>
|
||||
@@ -162,7 +162,7 @@ const TwoFactorScreen: React.FC = React.memo(() => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={toggleBackupOff}
|
||||
className="inline-flex p-1 text-sm font-medium text-green-600 transition-colors hover:text-green-700 dark:text-green-400 dark:hover:text-green-300"
|
||||
className="inline-flex p-1 text-sm font-medium text-black transition-colors hover:text-gray-700 dark:text-white dark:hover:text-gray-300"
|
||||
>
|
||||
{localize('com_ui_use_2fa_code')}
|
||||
</button>
|
||||
|
||||
@@ -108,7 +108,7 @@ function RequestPasswordReset() {
|
||||
{localize('com_auth_email_verification_in_progress')}
|
||||
</h1>
|
||||
<div className="mt-4 flex justify-center">
|
||||
<Spinner className="h-8 w-8 text-green-500" />
|
||||
<Spinner className="h-8 w-8 text-black dark:text-white" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -37,9 +37,9 @@ export default function Footer({ className }: { className?: string }) {
|
||||
const mainContentParts = (
|
||||
typeof config?.customFooter === 'string'
|
||||
? config.customFooter
|
||||
: '[LibreChat ' +
|
||||
: '[Hanzo Chat ' +
|
||||
Constants.VERSION +
|
||||
'](https://librechat.ai) - ' +
|
||||
'](https://hanzo.ai) - ' +
|
||||
localize('com_ui_latest_footer')
|
||||
).split('|');
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ function FileSearch() {
|
||||
checked={fileSearchEnabled}
|
||||
setValue={debouncedChange}
|
||||
label={localize('com_assistants_file_search')}
|
||||
isCheckedClassName="border-green-600/40 bg-green-500/10 hover:bg-green-700/10"
|
||||
isCheckedClassName="border-gray-600/40 bg-gray-500/10 hover:bg-gray-700/10"
|
||||
icon={<VectorIcon className="icon-md" />}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -45,7 +45,7 @@ const FileUpload: React.FC<FileUploadProps> = ({
|
||||
|
||||
if (validator) {
|
||||
setStatus('success');
|
||||
setStatusColor('text-green-500 dark:text-green-500');
|
||||
setStatusColor('text-black dark:text-white');
|
||||
}
|
||||
|
||||
onFileSelected(jsonData);
|
||||
@@ -66,7 +66,7 @@ const FileUpload: React.FC<FileUploadProps> = ({
|
||||
<label
|
||||
htmlFor={`file-upload-${id}`}
|
||||
className={cn(
|
||||
'mr-1 flex h-auto cursor-pointer items-center rounded bg-transparent px-2 py-1 text-xs font-normal transition-colors hover:bg-gray-100 hover:text-green-600 dark:bg-transparent dark:text-gray-300 dark:hover:bg-gray-700 dark:hover:text-green-500',
|
||||
'mr-1 flex h-auto cursor-pointer items-center rounded bg-transparent px-2 py-1 text-xs font-normal transition-colors hover:bg-gray-100 hover:text-black dark:bg-transparent dark:text-gray-300 dark:hover:bg-gray-700 dark:hover:text-white',
|
||||
statusColor,
|
||||
containerClassName,
|
||||
)}
|
||||
|
||||
@@ -141,7 +141,7 @@ export const columns: ColumnDef<TFile>[] = [
|
||||
if (source === FileSources.openai) {
|
||||
return (
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<OpenAIMinimalIcon className="icon-sm text-green-600/50" />
|
||||
<OpenAIMinimalIcon className="icon-sm text-black/50 dark:text-white/50" />
|
||||
{'OpenAI'}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,27 +1,74 @@
|
||||
import React, { memo, useCallback, useState } from 'react';
|
||||
import { SettingsIcon } from 'lucide-react';
|
||||
import { Constants } from 'librechat-data-provider';
|
||||
import React, { memo, useRef, useMemo, useEffect, useCallback, useState } from 'react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { Settings2 } from 'lucide-react';
|
||||
import { useUpdateUserPluginsMutation } from 'librechat-data-provider/react-query';
|
||||
import type { TUpdateUserPlugins, TPlugin } from 'librechat-data-provider';
|
||||
import { Constants, EModelEndpoint, LocalStorageKeys } from 'librechat-data-provider';
|
||||
import type { TPlugin, TPluginAuthConfig, TUpdateUserPlugins } from 'librechat-data-provider';
|
||||
import MCPConfigDialog, { type ConfigFieldDetail } from '~/components/ui/MCPConfigDialog';
|
||||
import { useToastContext, useBadgeRowContext } from '~/Providers';
|
||||
import { useAvailableToolsQuery } from '~/data-provider';
|
||||
import useLocalStorage from '~/hooks/useLocalStorageAlt';
|
||||
import MultiSelect from '~/components/ui/MultiSelect';
|
||||
import { MCPIcon } from '~/components/svg';
|
||||
import { ephemeralAgentByConvoId } from '~/store';
|
||||
import { useToastContext } from '~/Providers';
|
||||
import MCPIcon from '~/components/ui/MCPIcon';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
interface McpServerInfo {
|
||||
name: string;
|
||||
pluginKey: string;
|
||||
authConfig?: TPluginAuthConfig[];
|
||||
authenticated?: boolean;
|
||||
}
|
||||
|
||||
// Helper function to extract mcp_serverName from a full pluginKey like action_mcp_serverName
|
||||
const getBaseMCPPluginKey = (fullPluginKey: string): string => {
|
||||
const parts = fullPluginKey.split(Constants.mcp_delimiter);
|
||||
return Constants.mcp_prefix + parts[parts.length - 1];
|
||||
};
|
||||
|
||||
function MCPSelect() {
|
||||
const storageCondition = (value: unknown, rawCurrentValue?: string | null) => {
|
||||
if (rawCurrentValue) {
|
||||
try {
|
||||
const currentValue = rawCurrentValue?.trim() ?? '';
|
||||
if (currentValue.length > 2) {
|
||||
return true;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
return Array.isArray(value) && value.length > 0;
|
||||
};
|
||||
|
||||
function MCPSelect({ conversationId }: { conversationId?: string | null }) {
|
||||
const localize = useLocalize();
|
||||
const { showToast } = useToastContext();
|
||||
const { mcpSelect, startupConfig } = useBadgeRowContext();
|
||||
const { mcpValues, setMCPValues, mcpServerNames, mcpToolDetails, isPinned } = mcpSelect;
|
||||
|
||||
const key = conversationId ?? Constants.NEW_CONVO;
|
||||
const hasSetFetched = useRef<string | null>(null);
|
||||
const [isConfigModalOpen, setIsConfigModalOpen] = useState(false);
|
||||
const [selectedToolForConfig, setSelectedToolForConfig] = useState<TPlugin | null>(null);
|
||||
const [selectedToolForConfig, setSelectedToolForConfig] = useState<McpServerInfo | null>(null);
|
||||
|
||||
const { data: mcpToolDetails, isFetched } = useAvailableToolsQuery(EModelEndpoint.agents, {
|
||||
select: (data: TPlugin[]) => {
|
||||
const mcpToolsMap = new Map<string, McpServerInfo>();
|
||||
data.forEach((tool) => {
|
||||
const isMCP = tool.pluginKey.includes(Constants.mcp_delimiter);
|
||||
if (isMCP && tool.chatMenu !== false) {
|
||||
const parts = tool.pluginKey.split(Constants.mcp_delimiter);
|
||||
const serverName = parts[parts.length - 1];
|
||||
if (!mcpToolsMap.has(serverName)) {
|
||||
mcpToolsMap.set(serverName, {
|
||||
name: serverName,
|
||||
pluginKey: tool.pluginKey,
|
||||
authConfig: tool.authConfig,
|
||||
authenticated: tool.authenticated,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
return Array.from(mcpToolsMap.values());
|
||||
},
|
||||
});
|
||||
|
||||
const updateUserPluginsMutation = useUpdateUserPluginsMutation({
|
||||
onSuccess: () => {
|
||||
@@ -37,6 +84,48 @@ function MCPSelect() {
|
||||
},
|
||||
});
|
||||
|
||||
const [ephemeralAgent, setEphemeralAgent] = useRecoilState(ephemeralAgentByConvoId(key));
|
||||
const mcpState = useMemo(() => {
|
||||
return ephemeralAgent?.mcp ?? [];
|
||||
}, [ephemeralAgent?.mcp]);
|
||||
|
||||
const setSelectedValues = useCallback(
|
||||
(values: string[] | null | undefined) => {
|
||||
if (!values) {
|
||||
return;
|
||||
}
|
||||
if (!Array.isArray(values)) {
|
||||
return;
|
||||
}
|
||||
setEphemeralAgent((prev) => ({
|
||||
...prev,
|
||||
mcp: values,
|
||||
}));
|
||||
},
|
||||
[setEphemeralAgent],
|
||||
);
|
||||
const [mcpValues, setMCPValues] = useLocalStorage<string[]>(
|
||||
`${LocalStorageKeys.LAST_MCP_}${key}`,
|
||||
mcpState,
|
||||
setSelectedValues,
|
||||
storageCondition,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (hasSetFetched.current === key) {
|
||||
return;
|
||||
}
|
||||
if (!isFetched) {
|
||||
return;
|
||||
}
|
||||
hasSetFetched.current = key;
|
||||
if ((mcpToolDetails?.length ?? 0) > 0) {
|
||||
setMCPValues(mcpValues.filter((mcp) => mcpToolDetails?.some((tool) => tool.name === mcp)));
|
||||
return;
|
||||
}
|
||||
setMCPValues([]);
|
||||
}, [isFetched, setMCPValues, mcpToolDetails, key, mcpValues]);
|
||||
|
||||
const renderSelectedValues = useCallback(
|
||||
(values: string[], placeholder?: string) => {
|
||||
if (values.length === 0) {
|
||||
@@ -50,6 +139,10 @@ function MCPSelect() {
|
||||
[localize],
|
||||
);
|
||||
|
||||
const mcpServerNames = useMemo(() => {
|
||||
return (mcpToolDetails ?? []).map((tool) => tool.name);
|
||||
}, [mcpToolDetails]);
|
||||
|
||||
const handleConfigSave = useCallback(
|
||||
(targetName: string, authData: Record<string, string>) => {
|
||||
if (selectedToolForConfig && selectedToolForConfig.name === targetName) {
|
||||
@@ -105,10 +198,10 @@ function MCPSelect() {
|
||||
setSelectedToolForConfig(tool);
|
||||
setIsConfigModalOpen(true);
|
||||
}}
|
||||
className="ml-2 flex h-6 w-6 items-center justify-center rounded p-1 hover:bg-surface-secondary"
|
||||
className="ml-2 flex h-6 w-6 items-center justify-center rounded p-1 hover:bg-black/10 dark:hover:bg-white/10"
|
||||
aria-label={`Configure ${serverName}`}
|
||||
>
|
||||
<SettingsIcon className={`h-4 w-4 ${tool.authenticated ? 'text-green-500' : ''}`} />
|
||||
<Settings2 className={`h-4 w-4 ${tool.authenticated ? 'text-black dark:text-white' : ''}`} />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
@@ -119,17 +212,10 @@ function MCPSelect() {
|
||||
[mcpToolDetails, setSelectedToolForConfig, setIsConfigModalOpen],
|
||||
);
|
||||
|
||||
// Don't render if no servers are selected and not pinned
|
||||
if ((!mcpValues || mcpValues.length === 0) && !isPinned) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!mcpToolDetails || mcpToolDetails.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const placeholderText =
|
||||
startupConfig?.interface?.mcpServers?.placeholder || localize('com_ui_mcp_servers');
|
||||
return (
|
||||
<>
|
||||
<MultiSelect
|
||||
@@ -139,7 +225,7 @@ function MCPSelect() {
|
||||
defaultSelectedValues={mcpValues ?? []}
|
||||
renderSelectedValues={renderSelectedValues}
|
||||
renderItemContent={renderItemContent}
|
||||
placeholder={placeholderText}
|
||||
placeholder={localize('com_ui_mcp_servers')}
|
||||
popoverClassName="min-w-fit"
|
||||
className="badge-icon min-w-fit"
|
||||
selectIcon={<MCPIcon className="icon-md text-text-primary" />}
|
||||
|
||||
@@ -122,7 +122,7 @@ export default function PopoverButtons({
|
||||
type="button"
|
||||
className={cn(
|
||||
button.buttonClass,
|
||||
'border border-gray-300/50 focus:ring-1 focus:ring-green-500/90 dark:border-gray-500/50 dark:focus:ring-green-500',
|
||||
'border border-gray-300/50 focus:ring-1 focus:ring-black dark:border-gray-500/50 dark:focus:ring-white',
|
||||
'ml-1 h-full bg-transparent px-2 py-1 text-xs font-normal text-black hover:bg-gray-100 hover:text-black dark:bg-transparent dark:text-white dark:hover:bg-gray-600 dark:hover:text-white',
|
||||
buttonClass ?? '',
|
||||
)}
|
||||
@@ -141,7 +141,7 @@ export default function PopoverButtons({
|
||||
type="button"
|
||||
className={cn(
|
||||
button.buttonClass,
|
||||
'flex justify-center border border-gray-300/50 focus:ring-1 focus:ring-green-500/90 dark:border-gray-500/50 dark:focus:ring-green-500',
|
||||
'flex justify-center border border-gray-300/50 focus:ring-1 focus:ring-black dark:border-gray-500/50 dark:focus:ring-white',
|
||||
'h-full w-full bg-transparent px-2 py-1 text-xs font-normal text-black hover:bg-gray-100 hover:text-black dark:bg-transparent dark:text-white dark:hover:bg-gray-600 dark:hover:text-white',
|
||||
buttonClass ?? '',
|
||||
)}
|
||||
|
||||
@@ -11,7 +11,7 @@ function ModelSelectorContent() {
|
||||
const localize = useLocalize();
|
||||
|
||||
const {
|
||||
// LibreChat
|
||||
// Hanzo
|
||||
modelSpecs,
|
||||
mappedEndpoints,
|
||||
endpointsConfig,
|
||||
|
||||
@@ -15,7 +15,7 @@ type ModelSelectorContextType = {
|
||||
selectedValues: SelectedValues;
|
||||
endpointSearchValues: Record<string, string>;
|
||||
searchResults: (t.TModelSpec | Endpoint)[] | null;
|
||||
// LibreChat
|
||||
// Hanzo
|
||||
modelSpecs: t.TModelSpec[];
|
||||
mappedEndpoints: Endpoint[];
|
||||
agentsMap: t.TAgentsMap | undefined;
|
||||
@@ -165,7 +165,7 @@ export function ModelSelectorProvider({ children, startupConfig }: ModelSelector
|
||||
searchResults,
|
||||
selectedValues,
|
||||
endpointSearchValues,
|
||||
// LibreChat
|
||||
// Hanzo
|
||||
agentsMap,
|
||||
modelSpecs,
|
||||
assistantsMap,
|
||||
|
||||
@@ -51,7 +51,7 @@ export function EndpointModelItem({ modelId, endpoint, isSelected }: EndpointMod
|
||||
) : null}
|
||||
<span>{modelName}</span>
|
||||
</div>
|
||||
{isGlobal && <EarthIcon className="ml-auto size-4 text-green-400" />}
|
||||
{isGlobal && <EarthIcon className="ml-auto size-4 text-black dark:text-white" />}
|
||||
{isSelected && (
|
||||
<svg
|
||||
width="16"
|
||||
|
||||
@@ -172,7 +172,7 @@ export function SearchResults({ results, localize, searchValue }: SearchResultsP
|
||||
)}
|
||||
<span>{modelName}</span>
|
||||
</div>
|
||||
{isGlobal && <EarthIcon className="ml-auto size-4 text-green-400" />}
|
||||
{isGlobal && <EarthIcon className="ml-auto size-4 text-black dark:text-white" />}
|
||||
{selectedEndpoint === endpoint.value && selectedModel === modelId && (
|
||||
<svg
|
||||
width="16"
|
||||
|
||||
@@ -216,7 +216,7 @@ const EditPresetDialog = ({
|
||||
</button>
|
||||
<button
|
||||
onClick={submitPreset}
|
||||
className="rounded-md bg-green-500 px-3 py-2 text-sm font-medium text-white hover:bg-green-600 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 md:px-4"
|
||||
className="rounded-md bg-white px-3 py-2 text-sm font-medium text-black hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-black focus:ring-offset-2 md:px-4 dark:bg-gray-800 dark:text-white dark:hover:bg-gray-700 dark:focus:ring-white border border-gray-300 dark:border-gray-600"
|
||||
>
|
||||
{localize('com_ui_save')}
|
||||
</button>
|
||||
|
||||
@@ -1,33 +1,14 @@
|
||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import { X, ArrowDownToLine, PanelLeftOpen, PanelLeftClose, RotateCcw } from 'lucide-react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { X, ArrowDownToLine, PanelLeftOpen, PanelLeftClose } from 'lucide-react';
|
||||
import { Button, OGDialog, OGDialogContent, TooltipAnchor } from '~/components';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
const getQualityStyles = (quality: string): string => {
|
||||
if (quality === 'high') {
|
||||
return 'bg-green-100 text-green-800';
|
||||
}
|
||||
if (quality === 'low') {
|
||||
return 'bg-orange-100 text-orange-800';
|
||||
}
|
||||
return 'bg-gray-100 text-gray-800';
|
||||
};
|
||||
|
||||
export default function DialogImage({ isOpen, onOpenChange, src = '', downloadImage, args }) {
|
||||
const localize = useLocalize();
|
||||
const [isPromptOpen, setIsPromptOpen] = useState(false);
|
||||
const [imageSize, setImageSize] = useState<string | null>(null);
|
||||
|
||||
// Zoom and pan state
|
||||
const [zoom, setZoom] = useState(1);
|
||||
const [panX, setPanX] = useState(0);
|
||||
const [panY, setPanY] = useState(0);
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const [dragStart, setDragStart] = useState({ x: 0, y: 0 });
|
||||
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const getImageSize = useCallback(async (url: string) => {
|
||||
const getImageSize = async (url: string) => {
|
||||
try {
|
||||
const response = await fetch(url, { method: 'HEAD' });
|
||||
const contentLength = response.headers.get('Content-Length');
|
||||
@@ -44,7 +25,7 @@ export default function DialogImage({ isOpen, onOpenChange, src = '', downloadIm
|
||||
console.error('Error getting image size:', error);
|
||||
return null;
|
||||
}
|
||||
}, []);
|
||||
};
|
||||
|
||||
const formatFileSize = (bytes: number): string => {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
@@ -56,129 +37,11 @@ export default function DialogImage({ isOpen, onOpenChange, src = '', downloadIm
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
};
|
||||
|
||||
const getImageMaxWidth = () => {
|
||||
// On mobile (when panel overlays), use full width minus padding
|
||||
// On desktop, account for the side panel width
|
||||
if (isPromptOpen) {
|
||||
return window.innerWidth >= 640 ? 'calc(100vw - 22rem)' : 'calc(100vw - 2rem)';
|
||||
}
|
||||
return 'calc(100vw - 2rem)';
|
||||
};
|
||||
|
||||
const resetZoom = useCallback(() => {
|
||||
setZoom(1);
|
||||
setPanX(0);
|
||||
setPanY(0);
|
||||
}, []);
|
||||
|
||||
const getCursor = () => {
|
||||
if (zoom <= 1) return 'default';
|
||||
return isDragging ? 'grabbing' : 'grab';
|
||||
};
|
||||
|
||||
const handleDoubleClick = useCallback(() => {
|
||||
if (zoom > 1) {
|
||||
resetZoom();
|
||||
} else {
|
||||
// Zoom in to 2x on double click when at normal zoom
|
||||
setZoom(2);
|
||||
}
|
||||
}, [zoom, resetZoom]);
|
||||
|
||||
const handleWheel = useCallback(
|
||||
(e: React.WheelEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
if (!containerRef.current) return;
|
||||
|
||||
const rect = containerRef.current.getBoundingClientRect();
|
||||
const mouseX = e.clientX - rect.left;
|
||||
const mouseY = e.clientY - rect.top;
|
||||
|
||||
// Calculate zoom factor
|
||||
const zoomFactor = e.deltaY > 0 ? 0.9 : 1.1;
|
||||
const newZoom = Math.min(Math.max(zoom * zoomFactor, 1), 5);
|
||||
|
||||
if (newZoom === zoom) return;
|
||||
|
||||
// If zooming back to 1, reset pan to center the image
|
||||
if (newZoom === 1) {
|
||||
setZoom(1);
|
||||
setPanX(0);
|
||||
setPanY(0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate the zoom center relative to the current viewport
|
||||
const containerCenterX = rect.width / 2;
|
||||
const containerCenterY = rect.height / 2;
|
||||
|
||||
// Calculate new pan position to zoom towards mouse cursor
|
||||
const zoomRatio = newZoom / zoom;
|
||||
const deltaX = (mouseX - containerCenterX - panX) * (zoomRatio - 1);
|
||||
const deltaY = (mouseY - containerCenterY - panY) * (zoomRatio - 1);
|
||||
|
||||
setZoom(newZoom);
|
||||
setPanX(panX - deltaX);
|
||||
setPanY(panY - deltaY);
|
||||
},
|
||||
[zoom, panX, panY],
|
||||
);
|
||||
|
||||
const handleMouseDown = useCallback(
|
||||
(e: React.MouseEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
if (zoom <= 1) return;
|
||||
setIsDragging(true);
|
||||
setDragStart({
|
||||
x: e.clientX - panX,
|
||||
y: e.clientY - panY,
|
||||
});
|
||||
},
|
||||
[zoom, panX, panY],
|
||||
);
|
||||
|
||||
const handleMouseMove = useCallback(
|
||||
(e: React.MouseEvent<HTMLDivElement>) => {
|
||||
if (!isDragging || zoom <= 1) return;
|
||||
const newPanX = e.clientX - dragStart.x;
|
||||
const newPanY = e.clientY - dragStart.y;
|
||||
setPanX(newPanX);
|
||||
setPanY(newPanY);
|
||||
},
|
||||
[isDragging, dragStart, zoom],
|
||||
);
|
||||
const handleMouseUp = useCallback(() => {
|
||||
setIsDragging(false);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const onKey = (e: KeyboardEvent) => e.key === 'Escape' && resetZoom();
|
||||
document.addEventListener('keydown', onKey);
|
||||
return () => document.removeEventListener('keydown', onKey);
|
||||
}, [resetZoom]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen && src) {
|
||||
getImageSize(src).then(setImageSize);
|
||||
resetZoom();
|
||||
}
|
||||
}, [isOpen, src, getImageSize, resetZoom]);
|
||||
|
||||
// Ensure image is centered when zoom changes to 1
|
||||
useEffect(() => {
|
||||
if (zoom === 1) {
|
||||
setPanX(0);
|
||||
setPanY(0);
|
||||
}
|
||||
}, [zoom]);
|
||||
|
||||
// Reset pan when panel opens/closes to maintain centering
|
||||
useEffect(() => {
|
||||
if (zoom === 1) {
|
||||
setPanX(0);
|
||||
setPanY(0);
|
||||
}
|
||||
}, [isPromptOpen, zoom]);
|
||||
}, [isOpen, src]);
|
||||
|
||||
return (
|
||||
<OGDialog open={isOpen} onOpenChange={onOpenChange}>
|
||||
@@ -189,7 +52,7 @@ export default function DialogImage({ isOpen, onOpenChange, src = '', downloadIm
|
||||
overlayClassName="bg-surface-primary opacity-95 z-50"
|
||||
>
|
||||
<div
|
||||
className={`ease-[cubic-bezier(0.175,0.885,0.32,1.275)] absolute left-0 top-0 z-10 flex items-center justify-between p-3 transition-all duration-500 sm:p-4 ${isPromptOpen ? 'right-0 sm:right-80' : 'right-0'}`}
|
||||
className={`absolute left-0 top-0 z-10 flex items-center justify-between p-4 transition-all duration-500 ease-in-out ${isPromptOpen ? 'right-80' : 'right-0'}`}
|
||||
>
|
||||
<TooltipAnchor
|
||||
description={localize('com_ui_close')}
|
||||
@@ -199,21 +62,11 @@ export default function DialogImage({ isOpen, onOpenChange, src = '', downloadIm
|
||||
variant="ghost"
|
||||
className="h-10 w-10 p-0 hover:bg-surface-hover"
|
||||
>
|
||||
<X className="size-7 sm:size-6" />
|
||||
<X className="size-6" />
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<div className="flex items-center gap-1 sm:gap-2">
|
||||
{zoom > 1 && (
|
||||
<TooltipAnchor
|
||||
description={localize('com_ui_reset_zoom')}
|
||||
render={
|
||||
<Button onClick={resetZoom} variant="ghost" className="h-10 w-10 p-0">
|
||||
<RotateCcw className="size-6" />
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<div className="flex items-center gap-2">
|
||||
<TooltipAnchor
|
||||
description={localize('com_ui_download')}
|
||||
render={
|
||||
@@ -235,9 +88,9 @@ export default function DialogImage({ isOpen, onOpenChange, src = '', downloadIm
|
||||
className="h-10 w-10 p-0"
|
||||
>
|
||||
{isPromptOpen ? (
|
||||
<PanelLeftOpen className="size-7 sm:size-6" />
|
||||
<PanelLeftOpen className="size-6" />
|
||||
) : (
|
||||
<PanelLeftClose className="size-7 sm:size-6" />
|
||||
<PanelLeftClose className="size-6" />
|
||||
)}
|
||||
</Button>
|
||||
}
|
||||
@@ -247,81 +100,36 @@ export default function DialogImage({ isOpen, onOpenChange, src = '', downloadIm
|
||||
|
||||
{/* Main content area with image */}
|
||||
<div
|
||||
className={`ease-[cubic-bezier(0.175,0.885,0.32,1.275)] flex h-full transition-all duration-500 ${isPromptOpen ? 'mr-0 sm:mr-80' : 'mr-0'}`}
|
||||
className={`flex h-full transition-all duration-500 ease-in-out ${isPromptOpen ? 'mr-80' : 'mr-0'}`}
|
||||
>
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="flex flex-1 items-center justify-center px-2 pb-4 pt-16 sm:px-4 sm:pt-20"
|
||||
onWheel={handleWheel}
|
||||
onMouseDown={handleMouseDown}
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseUp={handleMouseUp}
|
||||
onMouseLeave={handleMouseUp}
|
||||
onDoubleClick={handleDoubleClick}
|
||||
style={{
|
||||
cursor: getCursor(),
|
||||
overflow: zoom > 1 ? 'hidden' : 'visible',
|
||||
minHeight: 0, // Allow flexbox to shrink
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="flex items-center justify-center transition-transform duration-100 ease-out"
|
||||
<div className="flex flex-1 items-center justify-center px-4 pb-4 pt-20">
|
||||
<img
|
||||
src={src}
|
||||
alt="Image"
|
||||
className="max-h-full max-w-full object-contain"
|
||||
style={{
|
||||
transform: `translate(${panX}px, ${panY}px) scale(${zoom})`,
|
||||
transformOrigin: 'center center',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
maxHeight: 'calc(100vh - 6rem)',
|
||||
maxWidth: '100%',
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={src}
|
||||
alt="Image"
|
||||
className="block object-contain"
|
||||
style={{
|
||||
maxHeight: 'calc(100vh - 8rem)',
|
||||
maxWidth: getImageMaxWidth(),
|
||||
width: 'auto',
|
||||
height: 'auto',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Side Panel */}
|
||||
<div
|
||||
className={`sm:shadow-l-lg ease-[cubic-bezier(0.175,0.885,0.32,1.275)] fixed right-0 top-0 z-20 h-full w-full transform border-l border-border-light bg-surface-primary shadow-2xl backdrop-blur-sm transition-transform duration-500 sm:w-80 sm:rounded-l-2xl ${
|
||||
className={`shadow-l-lg fixed right-0 top-0 z-20 h-full w-80 transform rounded-l-2xl border-l border-border-light bg-surface-primary transition-transform duration-500 ease-in-out ${
|
||||
isPromptOpen ? 'translate-x-0' : 'translate-x-full'
|
||||
}`}
|
||||
>
|
||||
{/* Mobile pull handle - removed for cleaner look */}
|
||||
|
||||
<div className="h-full overflow-y-auto p-4 sm:p-6">
|
||||
{/* Mobile close button */}
|
||||
<div className="mb-4 flex items-center justify-between sm:hidden">
|
||||
<h3 className="text-lg font-semibold text-text-primary">
|
||||
{localize('com_ui_image_details')}
|
||||
</h3>
|
||||
<Button
|
||||
onClick={() => setIsPromptOpen(false)}
|
||||
variant="ghost"
|
||||
className="h-12 w-12 p-0"
|
||||
>
|
||||
<X className="size-6" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="mb-4 hidden sm:block">
|
||||
<div className="h-full overflow-y-auto p-6">
|
||||
<div className="mb-4">
|
||||
<h3 className="mb-2 text-lg font-semibold text-text-primary">
|
||||
{localize('com_ui_image_details')}
|
||||
</h3>
|
||||
<div className="mb-4 h-px bg-border-medium"></div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4 sm:space-y-6">
|
||||
<div className="space-y-6">
|
||||
{/* Prompt Section */}
|
||||
<div>
|
||||
<h4 className="mb-2 text-sm font-medium text-text-primary">
|
||||
@@ -349,7 +157,13 @@ export default function DialogImage({ isOpen, onOpenChange, src = '', downloadIm
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm text-text-primary">{localize('com_ui_quality')}:</span>
|
||||
<span
|
||||
className={`rounded px-2 py-1 text-xs font-medium capitalize ${getQualityStyles(args?.quality || '')}`}
|
||||
className={`rounded px-2 py-1 text-xs font-medium capitalize ${
|
||||
args?.quality === 'high'
|
||||
? 'bg-gray-100 text-gray-800'
|
||||
: args?.quality === 'low'
|
||||
? 'bg-orange-100 text-orange-800'
|
||||
: 'bg-gray-100 text-gray-800'
|
||||
}`}
|
||||
>
|
||||
{args?.quality || 'Standard'}
|
||||
</span>
|
||||
|
||||
@@ -85,7 +85,7 @@ const SaveAsPresetDialog = ({ open, onOpenChange, preset }: TEditPresetProps) =>
|
||||
}
|
||||
selection={{
|
||||
selectHandler: submitPreset,
|
||||
selectClasses: 'bg-green-500 hover:bg-green-600 dark:hover:bg-green-600 text-white',
|
||||
selectClasses: 'bg-white hover:bg-gray-100 text-black dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-white border border-gray-300 dark:border-gray-600',
|
||||
selectText: localize('com_ui_save'),
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -88,7 +88,7 @@ const Plugin: React.FC<PluginProps> = ({ plugin }) => {
|
||||
<>
|
||||
<div
|
||||
className={cn(
|
||||
plugin.loading ? 'bg-green-100' : 'bg-gray-20',
|
||||
plugin.loading ? 'bg-gray-100' : 'bg-gray-20',
|
||||
'my-1 flex items-center rounded p-3 text-xs text-gray-800',
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -184,7 +184,7 @@ function Avatar() {
|
||||
</div>
|
||||
<Button
|
||||
className={cn(
|
||||
'btn btn-primary mt-4 flex w-full hover:bg-green-600',
|
||||
'btn btn-primary mt-4 flex w-full hover:bg-gray-100 dark:hover:bg-gray-700',
|
||||
isUploading ? 'cursor-not-allowed opacity-90' : '',
|
||||
)}
|
||||
onClick={handleUpload}
|
||||
|
||||
@@ -120,7 +120,7 @@ const BackupCodesItem: React.FC = () => {
|
||||
className={`flex flex-col rounded-xl border p-4 backdrop-blur-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary ${
|
||||
isUsed
|
||||
? 'border-red-200 bg-red-50/80 dark:border-red-800 dark:bg-red-900/20'
|
||||
: 'border-green-200 bg-green-50/80 dark:border-green-800 dark:bg-green-900/20'
|
||||
: 'border-gray-300 bg-white dark:border-gray-600 dark:bg-gray-800'
|
||||
} `}
|
||||
>
|
||||
<div className="flex items-center justify-between" aria-hidden="true">
|
||||
@@ -139,7 +139,7 @@ const BackupCodesItem: React.FC = () => {
|
||||
className={`rounded-full px-3 py-1 text-sm font-medium ${
|
||||
isUsed
|
||||
? 'bg-red-100 text-red-700 dark:bg-red-900/40 dark:text-red-300'
|
||||
: 'bg-green-100 text-green-700 dark:bg-green-900/40 dark:text-green-300'
|
||||
: 'bg-gray-100 text-black dark:bg-gray-700 dark:text-white'
|
||||
}`}
|
||||
>
|
||||
{isUsed ? localize('com_ui_used') : localize('com_ui_not_used')}
|
||||
|
||||
@@ -62,7 +62,7 @@ function ChatGroupItem({
|
||||
>
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
{groupIsGlobal === true && (
|
||||
<EarthIcon className="icon-md text-green-400" aria-label="Global prompt group" />
|
||||
<EarthIcon className="icon-md text-black dark:text-white" aria-label="Global prompt group" />
|
||||
)}
|
||||
<DropdownMenu modal={false}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
|
||||
@@ -95,7 +95,7 @@ function DashGroupItemComponent({ group, instanceProjectId }: DashGroupItemProps
|
||||
<div className="flex h-full items-center gap-2">
|
||||
{isGlobalGroup && (
|
||||
<EarthIcon
|
||||
className="icon-md text-green-500"
|
||||
className="icon-md text-black dark:text-white"
|
||||
aria-label={localize('com_ui_global_group')}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -51,7 +51,7 @@ const VersionTags = ({ tags }: { tags: string[] }) => {
|
||||
className={cn(
|
||||
'w-24 justify-center border border-transparent',
|
||||
tag === 'production'
|
||||
? 'bg-green-100 text-green-500 dark:border-green-500 dark:bg-transparent dark:text-green-500'
|
||||
? 'bg-gray-100 text-black dark:border-gray-500 dark:bg-gray-800 dark:text-white'
|
||||
: 'bg-blue-100 text-blue-500 dark:border-blue-500 dark:bg-transparent dark:text-blue-500',
|
||||
)}
|
||||
labelClassName="flex items-center m-0 justify-center gap-1"
|
||||
@@ -59,7 +59,7 @@ const VersionTags = ({ tags }: { tags: string[] }) => {
|
||||
if (tag === 'production') {
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
<span className="slow-pulse size-2 rounded-full bg-green-400" />
|
||||
<span className="slow-pulse size-2 rounded-full bg-gray-500" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -152,7 +152,7 @@ const PromptVersions = ({
|
||||
<section className="my-6" aria-label="Prompt Versions">
|
||||
<header className="mb-6">
|
||||
<h2 className="flex items-center gap-2 text-base font-semibold text-text-primary">
|
||||
<Layers3 className="h-5 w-5 text-green-500" />
|
||||
<Layers3 className="h-5 w-5 text-black dark:text-white" />
|
||||
{localize('com_ui_versions')}
|
||||
</h2>
|
||||
</header>
|
||||
|
||||
@@ -277,7 +277,7 @@ export default function ActionsInput({
|
||||
<button
|
||||
disabled={!functions || !functions.length}
|
||||
onClick={saveAction}
|
||||
className="focus:shadow-outline mt-1 flex min-w-[100px] items-center justify-center rounded bg-green-500 px-4 py-2 font-semibold text-white hover:bg-green-400 focus:border-green-500 focus:outline-none focus:ring-0 disabled:bg-green-400"
|
||||
className="focus:shadow-outline mt-1 flex min-w-[100px] items-center justify-center rounded bg-white px-4 py-2 font-semibold text-black hover:bg-gray-100 focus:border-black focus:outline-none focus:ring-0 disabled:bg-gray-100 dark:bg-gray-800 dark:text-white dark:hover:bg-gray-700 dark:focus:border-white dark:disabled:bg-gray-700 border border-gray-300 dark:border-gray-600"
|
||||
type="button"
|
||||
>
|
||||
{getButtonContent()}
|
||||
|
||||
@@ -206,7 +206,7 @@ const AdminSettings = () => {
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isSubmitting || isLoading}
|
||||
className="btn rounded bg-green-500 font-bold text-white transition-all hover:bg-green-600"
|
||||
className="btn rounded bg-white font-bold text-black transition-all hover:bg-gray-100 dark:bg-gray-800 dark:text-white dark:hover:bg-gray-700 border border-gray-300 dark:border-gray-600"
|
||||
>
|
||||
{localize('com_ui_save')}
|
||||
</button>
|
||||
|
||||
@@ -76,7 +76,7 @@ export default function AgentFooter({
|
||||
{agent && agent.author === user?.id && <DuplicateAgent agent_id={agent_id} />}
|
||||
{/* Submit Button */}
|
||||
<button
|
||||
className="btn btn-primary focus:shadow-outline flex h-9 w-full items-center justify-center px-4 py-2 font-semibold text-white hover:bg-green-600 focus:border-green-500"
|
||||
className="btn btn-primary focus:shadow-outline flex h-9 w-full items-center justify-center px-4 py-2 font-semibold text-black bg-white hover:bg-gray-100 focus:border-black dark:bg-gray-800 dark:text-white dark:hover:bg-gray-700 dark:focus:border-white border border-gray-300 dark:border-gray-600"
|
||||
type="submit"
|
||||
disabled={createMutation.isLoading || updateMutation.isLoading}
|
||||
aria-busy={createMutation.isLoading || updateMutation.isLoading}
|
||||
|
||||
@@ -51,7 +51,7 @@ export default function AgentSelect({
|
||||
provider: createProviderOption(fullAgent.provider),
|
||||
label: fullAgent.name ?? '',
|
||||
value: fullAgent.id || '',
|
||||
icon: isGlobal ? <EarthIcon className={'icon-lg text-green-400'} /> : null,
|
||||
icon: isGlobal ? <EarthIcon className={'icon-lg text-black dark:text-white'} /> : null,
|
||||
};
|
||||
|
||||
const capabilities: TAgentCapabilities = {
|
||||
|
||||
@@ -55,10 +55,10 @@ export default function ApiKeyDialog({
|
||||
main={
|
||||
<>
|
||||
<div className="mb-4 text-center font-medium">
|
||||
{localize('com_ui_librechat_code_api_title')}
|
||||
{localize('com_ui_hanzo_code_api_title')}
|
||||
</div>
|
||||
<div className="mb-4 text-center text-sm">
|
||||
{localize('com_ui_librechat_code_api_subtitle')}
|
||||
{localize('com_ui_hanzo_code_api_subtitle')}
|
||||
</div>
|
||||
{/* Language Icons Stack */}
|
||||
<div className="mb-6">
|
||||
@@ -79,7 +79,7 @@ export default function ApiKeyDialog({
|
||||
rel="noopener noreferrer"
|
||||
className="block text-center text-[15px] font-medium text-blue-500 underline decoration-1 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300"
|
||||
>
|
||||
{localize('com_ui_librechat_code_api_key')}
|
||||
{localize('com_ui_hanzo_code_api_key')}
|
||||
</a>
|
||||
</div>
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
@@ -96,7 +96,7 @@ export default function ApiKeyDialog({
|
||||
}
|
||||
selection={{
|
||||
selectHandler: handleSubmit(onSubmit),
|
||||
selectClasses: 'bg-green-500 hover:bg-green-600 text-white',
|
||||
selectClasses: 'bg-white hover:bg-gray-100 text-black dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-white border border-gray-300 dark:border-gray-600',
|
||||
selectText: localize('com_ui_save'),
|
||||
}}
|
||||
buttons={
|
||||
|
||||
@@ -231,7 +231,7 @@ export default function MCPInput({ mcp, agent_id, setMCP }: MCPInputProps) {
|
||||
<button
|
||||
onClick={saveMCP}
|
||||
disabled={isLoading}
|
||||
className="focus:shadow-outline mt-1 flex min-w-[100px] items-center justify-center rounded bg-green-500 px-4 py-2 font-semibold text-white hover:bg-green-400 focus:border-green-500 focus:outline-none focus:ring-0 disabled:bg-green-400"
|
||||
className="focus:shadow-outline mt-1 flex min-w-[100px] items-center justify-center rounded bg-white px-4 py-2 font-semibold text-black hover:bg-gray-100 focus:border-black focus:outline-none focus:ring-0 disabled:bg-gray-100 dark:bg-gray-800 dark:text-white dark:hover:bg-gray-700 dark:focus:border-white dark:disabled:bg-gray-700 border border-gray-300 dark:border-gray-600"
|
||||
type="button"
|
||||
>
|
||||
{(() => {
|
||||
|
||||
@@ -348,7 +348,7 @@ export default function ApiKeyDialog({
|
||||
}
|
||||
selection={{
|
||||
selectHandler: handleSubmit(onSubmit),
|
||||
selectClasses: 'bg-green-500 hover:bg-green-600 text-white',
|
||||
selectClasses: 'bg-white hover:bg-gray-100 text-black dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-white border border-gray-300 dark:border-gray-600',
|
||||
selectText: localize('com_ui_save'),
|
||||
}}
|
||||
buttons={
|
||||
|
||||
@@ -43,7 +43,7 @@ export default function VersionItem({
|
||||
{localize('com_ui_agent_version_title', { versionNumber: versionsLength - index })}
|
||||
</span>
|
||||
{isActive && (
|
||||
<span className="rounded-full border border-green-600 bg-green-600/20 px-2 py-0.5 text-xs font-medium text-green-700 dark:border-green-500 dark:bg-green-500/30 dark:text-green-300">
|
||||
<span className="rounded-full border border-gray-400 bg-white px-2 py-0.5 text-xs font-medium text-black dark:border-gray-500 dark:bg-gray-800 dark:text-white">
|
||||
{localize('com_ui_agent_version_active')}
|
||||
</span>
|
||||
)}
|
||||
|
||||
@@ -288,7 +288,7 @@ export default function ActionsInput({
|
||||
<button
|
||||
disabled={!functions || !functions.length}
|
||||
onClick={saveAction}
|
||||
className="focus:shadow-outline mt-1 flex min-w-[100px] items-center justify-center rounded bg-green-500 px-4 py-2 font-semibold text-white hover:bg-green-400 focus:border-green-500 focus:outline-none focus:ring-0 disabled:bg-green-400"
|
||||
className="focus:shadow-outline mt-1 flex min-w-[100px] items-center justify-center rounded bg-white px-4 py-2 font-semibold text-black hover:bg-gray-100 focus:border-black focus:outline-none focus:ring-0 disabled:bg-gray-100 dark:bg-gray-800 dark:text-white dark:hover:bg-gray-700 dark:focus:border-white dark:disabled:bg-gray-700 border border-gray-300 dark:border-gray-600"
|
||||
type="button"
|
||||
>
|
||||
{submitContext()}
|
||||
|
||||
@@ -238,7 +238,7 @@ export default function AssistantPanel({
|
||||
{/* Select Button */}
|
||||
{assistant_id && (
|
||||
<button
|
||||
className="btn btn-primary focus:shadow-outline mx-2 mt-1 h-[40px] rounded bg-green-500 px-4 py-2 font-semibold text-white hover:bg-green-400 focus:border-green-500 focus:outline-none focus:ring-0"
|
||||
className="btn btn-primary focus:shadow-outline mx-2 mt-1 h-[40px] rounded bg-white px-4 py-2 font-semibold text-black hover:bg-gray-100 focus:border-black focus:outline-none focus:ring-0 dark:bg-gray-800 dark:text-white dark:hover:bg-gray-700 dark:focus:border-white border border-gray-300 dark:border-gray-600"
|
||||
type="button"
|
||||
disabled={!assistant_id}
|
||||
onClick={(e) => {
|
||||
@@ -461,7 +461,7 @@ export default function AssistantPanel({
|
||||
/>
|
||||
{/* Submit Button */}
|
||||
<button
|
||||
className="btn btn-primary focus:shadow-outline flex w-full items-center justify-center px-4 py-2 font-semibold text-white hover:bg-green-600 focus:border-green-500"
|
||||
className="btn btn-primary focus:shadow-outline flex w-full items-center justify-center px-4 py-2 font-semibold text-black bg-white hover:bg-gray-100 focus:border-black dark:bg-gray-800 dark:text-white dark:hover:bg-gray-700 dark:focus:border-white border border-gray-300 dark:border-gray-600"
|
||||
type="submit"
|
||||
>
|
||||
{submitContext}
|
||||
|
||||
@@ -291,7 +291,7 @@ export default function AssistantSelect({
|
||||
)}
|
||||
className={cn(
|
||||
'mt-1 rounded-md dark:border-gray-700 dark:bg-gray-850',
|
||||
'z-50 flex h-[40px] w-full flex-none items-center justify-center px-4 hover:cursor-pointer hover:border-green-500 focus:border-gray-400',
|
||||
'z-50 flex h-[40px] w-full flex-none items-center justify-center px-4 hover:cursor-pointer hover:border-black dark:hover:border-white focus:border-gray-400',
|
||||
)}
|
||||
renderOption={() => (
|
||||
<span className="flex items-center gap-1.5 truncate">
|
||||
|
||||
@@ -242,7 +242,7 @@ function MCPVariableEditor({ server, onSave, onRevoke, isSubmitting }: MCPVariab
|
||||
)}
|
||||
<Button
|
||||
type="submit"
|
||||
className="bg-green-500 text-white hover:bg-green-600"
|
||||
className="bg-white text-black hover:bg-gray-100 dark:bg-gray-800 dark:text-white dark:hover:bg-gray-700 border border-gray-300 dark:border-gray-600"
|
||||
disabled={isSubmitting || !isDirty}
|
||||
>
|
||||
{isSubmitting ? localize('com_ui_saving') : localize('com_ui_save')}
|
||||
|
||||
@@ -197,7 +197,7 @@ const AdminSettings = () => {
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isSubmitting || isLoading}
|
||||
className="btn rounded bg-green-500 font-bold text-white transition-all hover:bg-green-600"
|
||||
className="btn rounded bg-white font-bold text-black transition-all hover:bg-gray-100 dark:bg-gray-800 dark:text-white dark:hover:bg-gray-700 border border-gray-300 dark:border-gray-600"
|
||||
>
|
||||
{localize('com_ui_save')}
|
||||
</button>
|
||||
|
||||
@@ -116,7 +116,7 @@ export default function MemoryViewer() {
|
||||
if (percentage > 75) {
|
||||
return 'stroke-yellow-500';
|
||||
}
|
||||
return 'stroke-green-500';
|
||||
return 'stroke-gray-500';
|
||||
};
|
||||
|
||||
const EditMemoryButton = ({ memory }: { memory: TUserMemory }) => {
|
||||
|
||||
@@ -180,7 +180,7 @@ export default function Parameters() {
|
||||
<div className="mt-2 flex justify-center">
|
||||
<button
|
||||
onClick={openDialog}
|
||||
className="btn btn-primary focus:shadow-outline flex w-full items-center justify-center px-4 py-2 font-semibold text-white hover:bg-green-600 focus:border-green-500"
|
||||
className="btn btn-primary focus:shadow-outline flex w-full items-center justify-center px-4 py-2 font-semibold text-black bg-white hover:bg-gray-100 focus:border-black dark:bg-gray-800 dark:text-white dark:hover:bg-gray-700 dark:focus:border-white border border-gray-300 dark:border-gray-600"
|
||||
type="button"
|
||||
>
|
||||
{localize('com_endpoint_save_as_preset')}
|
||||
|
||||
@@ -16,8 +16,8 @@ const buttonVariants = cva(
|
||||
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
||||
ghost: 'hover:bg-surface-hover hover:text-accent-foreground',
|
||||
link: 'text-primary underline-offset-4 hover:underline',
|
||||
// hardcoded text color because of WCAG contrast issues (text-white)
|
||||
submit: 'bg-surface-submit text-white hover:bg-surface-submit-hover',
|
||||
// Updated to white button with black text
|
||||
submit: 'bg-surface-submit text-black hover:bg-surface-submit-hover border border-border-light',
|
||||
},
|
||||
size: {
|
||||
default: 'h-10 px-4 py-2',
|
||||
|
||||
@@ -100,7 +100,7 @@ export default function MCPConfigDialog({
|
||||
}
|
||||
selection={{
|
||||
selectHandler: handleSubmit(onFormSubmit),
|
||||
selectClasses: 'bg-green-500 hover:bg-green-600 text-white',
|
||||
selectClasses: 'bg-white hover:bg-gray-100 text-black dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-white border border-gray-300 dark:border-gray-600',
|
||||
selectText: isSubmitting ? localize('com_ui_saving') : localize('com_ui_save'),
|
||||
}}
|
||||
buttons={
|
||||
|
||||
@@ -19,7 +19,7 @@ const TagPrimitiveRoot = React.forwardRef<HTMLDivElement, TagProps>(
|
||||
ref={ref}
|
||||
{...props}
|
||||
className={cn(
|
||||
'flex max-h-8 items-center overflow-y-hidden rounded-3xl border-2 border-green-600 bg-green-600/20 text-xs text-green-600 dark:text-white',
|
||||
'flex max-h-8 items-center overflow-y-hidden rounded-3xl border-2 border-gray-400 bg-white text-xs text-black dark:border-gray-600 dark:bg-gray-800 dark:text-white',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
@@ -35,7 +35,7 @@ const TagPrimitiveRoot = React.forwardRef<HTMLDivElement, TagProps>(
|
||||
e.stopPropagation();
|
||||
onRemove(e);
|
||||
}}
|
||||
className="rounded-full bg-green-600/50"
|
||||
className="rounded-full bg-gray-300 dark:bg-gray-600"
|
||||
aria-label={`Remove ${label}`}
|
||||
>
|
||||
<X className="m-[1.5px] p-1" />
|
||||
|
||||
@@ -97,7 +97,7 @@ const TermsAndConditionsModal = ({
|
||||
</button>
|
||||
<button
|
||||
onClick={handleAccept}
|
||||
className="inline-flex h-10 items-center justify-center rounded-lg border border-border-heavy bg-surface-secondary px-4 py-2 text-sm text-text-primary hover:bg-green-500 hover:text-white focus:bg-green-500 focus:text-white dark:hover:bg-green-600 dark:focus:bg-green-600"
|
||||
className="inline-flex h-10 items-center justify-center rounded-lg border border-border-heavy bg-surface-secondary px-4 py-2 text-sm text-text-primary hover:bg-gray-100 hover:text-black focus:bg-gray-100 focus:text-black dark:hover:bg-gray-700 dark:focus:bg-gray-700 dark:hover:text-white dark:focus:text-white"
|
||||
>
|
||||
{localize('com_ui_accept')}
|
||||
</button>
|
||||
|
||||
@@ -6,7 +6,7 @@ export default function Toast() {
|
||||
const { toast, onOpenChange } = useToast();
|
||||
const severityClassName = {
|
||||
[NotificationSeverity.INFO]: 'border-gray-500 bg-gray-500',
|
||||
[NotificationSeverity.SUCCESS]: 'border-green-500 bg-green-500',
|
||||
[NotificationSeverity.SUCCESS]: 'border-gray-400 bg-white text-black dark:border-gray-600 dark:bg-gray-800 dark:text-white',
|
||||
[NotificationSeverity.WARNING]: 'border-orange-500 bg-orange-500',
|
||||
[NotificationSeverity.ERROR]: 'border-red-500 bg-red-500',
|
||||
};
|
||||
@@ -23,7 +23,9 @@ export default function Toast() {
|
||||
>
|
||||
<div className="w-full p-1 text-center md:w-auto md:text-justify">
|
||||
<div
|
||||
className={`alert-root pointer-events-auto inline-flex flex-row gap-2 rounded-md border px-3 py-2 text-white ${
|
||||
className={`alert-root pointer-events-auto inline-flex flex-row gap-2 rounded-md border px-3 py-2 ${
|
||||
toast.severity === NotificationSeverity.SUCCESS ? '' : 'text-white'
|
||||
} ${
|
||||
severityClassName[toast.severity]
|
||||
}`}
|
||||
>
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
"com_a11y_end": "انتهى الذكاء الاصطناعي من الرد",
|
||||
"com_a11y_start": "بدأ الذكاء الاصطناعي بالرد",
|
||||
"com_agents_allow_editing": "السماح للمستخدمين الآخرين بتعديل الوكيل الخاص بك",
|
||||
"com_agents_by_librechat": "بواسطة LibreChat",
|
||||
"com_agents_code_interpreter": "عند التمكين، يسمح للوكيل الخاص بك باستخدام واجهة برمجة التطبيقات لمفسر الشفرة LibreChat لتشغيل الشفرة المُنشأة، بما في ذلك معالجة الملفات، بشكل آمن. يتطلب مفتاح API صالح.",
|
||||
"com_agents_by_librechat": "بواسطة Hanzo",
|
||||
"com_agents_code_interpreter": "عند التمكين، يسمح للوكيل الخاص بك باستخدام واجهة برمجة التطبيقات لمفسر الشفرة Hanzo لتشغيل الشفرة المُنشأة، بما في ذلك معالجة الملفات، بشكل آمن. يتطلب مفتاح API صالح.",
|
||||
"com_agents_code_interpreter_title": "واجهة برمجة مُفسِّر الشفرة",
|
||||
"com_agents_create_error": "حدث خطأ أثناء إنشاء الوكيل الخاص بك",
|
||||
"com_agents_description_placeholder": "اختياري: اشرح عميلك هنا",
|
||||
@@ -413,7 +413,7 @@
|
||||
"com_ui_add_model_preset": "إضافة نموذج أو إعداد مسبق للرد الإضافي",
|
||||
"com_ui_add_multi_conversation": "إضافة محادثات متعددة",
|
||||
"com_ui_admin": "مشرف",
|
||||
"com_ui_admin_access_warning": "قد يؤدي تعطيل وصول المسؤول إلى هذه الميزة إلى مشاكل غير متوقعة في واجهة المستخدم تتطلب تحديث الصفحة. في حالة الحفظ، الطريقة الوحيدة للتراجع هي عبر إعداد الواجهة في ملف librechat.yaml والذي يؤثر على جميع الأدوار.",
|
||||
"com_ui_admin_access_warning": "قد يؤدي تعطيل وصول المسؤول إلى هذه الميزة إلى مشاكل غير متوقعة في واجهة المستخدم تتطلب تحديث الصفحة. في حالة الحفظ، الطريقة الوحيدة للتراجع هي عبر إعداد الواجهة في ملف chat.yaml والذي يؤثر على جميع الأدوار.",
|
||||
"com_ui_admin_settings": "إعدادات المسؤول",
|
||||
"com_ui_advanced": "متقدم",
|
||||
"com_ui_agent": "وكيل",
|
||||
@@ -578,9 +578,9 @@
|
||||
"com_ui_input": "إدخال",
|
||||
"com_ui_instructions": "تعليمات",
|
||||
"com_ui_latest_footer": "الذكاء الاصطناعي للجميع.",
|
||||
"com_ui_librechat_code_api_key": "احصل على مفتاح واجهة برمجة التطبيقات لمترجم الكود LibreChat",
|
||||
"com_ui_librechat_code_api_subtitle": "آمن. متعدد اللغات. ملفات الإدخال/الإخراج.",
|
||||
"com_ui_librechat_code_api_title": "تشغيل كود الذكاء الاصطناعي",
|
||||
"com_ui_hanzo_code_api_key": "احصل على مفتاح واجهة برمجة التطبيقات لمترجم الكود Hanzo",
|
||||
"com_ui_hanzo_code_api_subtitle": "آمن. متعدد اللغات. ملفات الإدخال/الإخراج.",
|
||||
"com_ui_hanzo_code_api_title": "تشغيل كود الذكاء الاصطناعي",
|
||||
"com_ui_locked": "مقفل",
|
||||
"com_ui_logo": "شعار {{0}}",
|
||||
"com_ui_manage": "إدارة",
|
||||
|
||||
@@ -464,7 +464,7 @@
|
||||
"com_ui_add_model_preset": "Afegeix un model o predefinit per una resposta addicional",
|
||||
"com_ui_add_multi_conversation": "Afegeix multi-conversa",
|
||||
"com_ui_admin": "Administrador",
|
||||
"com_ui_admin_access_warning": "Desactivar l'accés d'administrador a aquesta funció pot causar problemes inesperats en la interfície que requeriran una actualització. Si es desa, només es pot revertir des de la configuració de la interfície a librechat.yaml, que afecta tots els rols.",
|
||||
"com_ui_admin_access_warning": "Desactivar l'accés d'administrador a aquesta funció pot causar problemes inesperats en la interfície que requeriran una actualització. Si es desa, només es pot revertir des de la configuració de la interfície a chat.yaml, que afecta tots els rols.",
|
||||
"com_ui_admin_settings": "Configuració d'administrador",
|
||||
"com_ui_advanced": "Avançat",
|
||||
"com_ui_advanced_settings": "Configuració avançada",
|
||||
@@ -695,9 +695,9 @@
|
||||
"com_ui_latest_footer": "Cada IA per a tothom.",
|
||||
"com_ui_latest_production_version": "Darrera versió de producció",
|
||||
"com_ui_latest_version": "Darrera versió",
|
||||
"com_ui_librechat_code_api_key": "Aconsegueix la teva clau API d'Intèrpret de Codi de LibreChat",
|
||||
"com_ui_librechat_code_api_subtitle": "Segur. Multiidioma. Fitxers d'entrada/sortida.",
|
||||
"com_ui_librechat_code_api_title": "Executa codi IA",
|
||||
"com_ui_hanzo_code_api_key": "Aconsegueix la teva clau API d'Intèrpret de Codi de LibreChat",
|
||||
"com_ui_hanzo_code_api_subtitle": "Segur. Multiidioma. Fitxers d'entrada/sortida.",
|
||||
"com_ui_hanzo_code_api_title": "Executa codi IA",
|
||||
"com_ui_loading": "Carregant...",
|
||||
"com_ui_locked": "Blocat",
|
||||
"com_ui_logo": "Logotip {{0}}",
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
"com_a11y_end": "AI dokončila svou odpověď.",
|
||||
"com_a11y_start": "AI začala tvořit odpověď.",
|
||||
"com_agents_allow_editing": "Povolit ostatním uživatelům upravovat vašeho agenta",
|
||||
"com_agents_by_librechat": "od LibreChat",
|
||||
"com_agents_code_interpreter": "Při povolení může váš agent využívat LibreChat Code Interpreter API ke spuštění generovaného kódu, včetně zpracování souborů, bezpečně. Vyžaduje platný API klíč.",
|
||||
"com_agents_by_librechat": "od Hanzo",
|
||||
"com_agents_code_interpreter": "Při povolení může váš agent využívat Hanzo Code Interpreter API ke spuštění generovaného kódu, včetně zpracování souborů, bezpečně. Vyžaduje platný API klíč.",
|
||||
"com_agents_code_interpreter_title": "API pro interpretaci kódu",
|
||||
"com_agents_create_error": "Při vytváření agenta došlo k chybě.",
|
||||
"com_agents_description_placeholder": "Volitelné: Popište zde svého agenta",
|
||||
@@ -565,9 +565,9 @@
|
||||
"com_ui_latest_footer": "AICon se může plést. Vždy kontrolujte důležité informace.",
|
||||
"com_ui_latest_production_version": "Nejnovější produkční verze",
|
||||
"com_ui_latest_version": "Nejnovější verze",
|
||||
"com_ui_librechat_code_api_key": "Získejte svůj API klíč pro LibreChat Code Interpreter",
|
||||
"com_ui_librechat_code_api_subtitle": "Bezpečné. Vícejazyčné. Vstupní/Výstupní soubory.",
|
||||
"com_ui_librechat_code_api_title": "Spustit AI kód",
|
||||
"com_ui_hanzo_code_api_key": "Získejte svůj API klíč pro Hanzo Code Interpreter",
|
||||
"com_ui_hanzo_code_api_subtitle": "Bezpečné. Vícejazyčné. Vstupní/Výstupní soubory.",
|
||||
"com_ui_hanzo_code_api_title": "Spustit AI kód",
|
||||
"com_ui_loading": "Načítání...",
|
||||
"com_ui_locked": "Zamčeno",
|
||||
"com_ui_logo": "Logo {{0}}",
|
||||
|
||||
@@ -428,7 +428,7 @@
|
||||
"com_ui_add_multi_conversation": "Tilføj multikonversation",
|
||||
"com_ui_adding_details": "Tilføjer detaljer",
|
||||
"com_ui_admin": "Admin",
|
||||
"com_ui_admin_access_warning": "Deaktivering af administratoradgang til denne funktion kan forårsage uventede UI-problemer, der kræver opdatering. Hvis den gemmes, er den eneste måde at vende tilbage på via grænsefladeindstillingen i librechat.yaml config, som påvirker alle roller.",
|
||||
"com_ui_admin_access_warning": "Deaktivering af administratoradgang til denne funktion kan forårsage uventede UI-problemer, der kræver opdatering. Hvis den gemmes, er den eneste måde at vende tilbage på via grænsefladeindstillingen i chat.yaml config, som påvirker alle roller.",
|
||||
"com_ui_admin_settings": "Administratorindstillinger",
|
||||
"com_ui_advanced": "Avanceret",
|
||||
"com_ui_advanced_settings": "Avancerede indstillinger",
|
||||
@@ -650,9 +650,9 @@
|
||||
"com_ui_latest_footer": "Hver eneste AI for alle.",
|
||||
"com_ui_latest_production_version": "Seneste produktionsversion",
|
||||
"com_ui_latest_version": "Seneste version",
|
||||
"com_ui_librechat_code_api_key": "Få din LibreChat Code Interpreter API-nøgle",
|
||||
"com_ui_librechat_code_api_subtitle": "Sikkert. Flersproget. Input/output-filer.",
|
||||
"com_ui_librechat_code_api_title": "Kør AI-kode",
|
||||
"com_ui_hanzo_code_api_key": "Få din LibreChat Code Interpreter API-nøgle",
|
||||
"com_ui_hanzo_code_api_subtitle": "Sikkert. Flersproget. Input/output-filer.",
|
||||
"com_ui_hanzo_code_api_title": "Kør AI-kode",
|
||||
"com_ui_loading": "Indlæser...",
|
||||
"com_ui_locked": "Låst",
|
||||
"com_ui_logo": "{{0}} Logo",
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
"com_a11y_end": "Die KI hat die Antwort fertiggestellt.",
|
||||
"com_a11y_start": "Die KI hat mit ihrer Antwort begonnen. ",
|
||||
"com_agents_allow_editing": "Anderen Nutzenden die Bearbeitung deines Agenten erlauben",
|
||||
"com_agents_by_librechat": "von LibreChat",
|
||||
"com_agents_code_interpreter": "Wenn aktiviert, ermöglicht es deinem Agenten, die LibreChat Code Interpreter API zu nutzen, um generierten Code sicher auszuführen, einschließlich der Verarbeitung von Dateien. Erfordert einen gültigen API-Schlüssel.",
|
||||
"com_agents_by_librechat": "von Hanzo",
|
||||
"com_agents_code_interpreter": "Wenn aktiviert, ermöglicht es deinem Agenten, die Hanzo Code Interpreter API zu nutzen, um generierten Code sicher auszuführen, einschließlich der Verarbeitung von Dateien. Erfordert einen gültigen API-Schlüssel.",
|
||||
"com_agents_code_interpreter_title": "Code-Interpreter-API",
|
||||
"com_agents_create_error": "Bei der Erstellung deines Agenten ist ein Fehler aufgetreten.",
|
||||
"com_agents_description_placeholder": "Optional: Beschreibe hier deinen Agenten",
|
||||
@@ -475,7 +475,7 @@
|
||||
"com_ui_add_multi_conversation": "Mehrere Chats hinzufügen",
|
||||
"com_ui_adding_details": "Hinzufügen von Details",
|
||||
"com_ui_admin": "Admin",
|
||||
"com_ui_admin_access_warning": "Das Deaktivieren des Admin-Zugriffs auf diese Funktion kann zu unerwarteten Problemen in der Benutzeroberfläche führen, die ein Neuladen erfordern. Nach dem Speichern kann dies nur über die Schnittstelleneinstellung in der librechat.yaml-Konfiguration rückgängig gemacht werden, was sich auf alle Rollen auswirkt.",
|
||||
"com_ui_admin_access_warning": "Das Deaktivieren des Admin-Zugriffs auf diese Funktion kann zu unerwarteten Problemen in der Benutzeroberfläche führen, die ein Neuladen erfordern. Nach dem Speichern kann dies nur über die Schnittstelleneinstellung in der chat.yaml-Konfiguration rückgängig gemacht werden, was sich auf alle Rollen auswirkt.",
|
||||
"com_ui_admin_settings": "Admin-Einstellungen",
|
||||
"com_ui_advanced": "Erweitert",
|
||||
"com_ui_advanced_settings": "Erweiterte Einstellungen",
|
||||
@@ -724,9 +724,9 @@
|
||||
"com_ui_latest_footer": "Alle KIs für alle.",
|
||||
"com_ui_latest_production_version": "Neueste Produktiv-Version",
|
||||
"com_ui_latest_version": "Neueste Version",
|
||||
"com_ui_librechat_code_api_key": "Hole dir deinen LibreChat Code Interpreter API-Schlüssel",
|
||||
"com_ui_librechat_code_api_subtitle": "Sicher. Mehrsprachig. Ein-/Ausgabedateien.",
|
||||
"com_ui_librechat_code_api_title": "KI-Code ausführen",
|
||||
"com_ui_hanzo_code_api_key": "Hole dir deinen Hanzo Code Interpreter API-Schlüssel",
|
||||
"com_ui_hanzo_code_api_subtitle": "Sicher. Mehrsprachig. Ein-/Ausgabedateien.",
|
||||
"com_ui_hanzo_code_api_title": "KI-Code ausführen",
|
||||
"com_ui_loading": "Lade …",
|
||||
"com_ui_locked": "Gesperrt",
|
||||
"com_ui_logo": "{{0}} Logo",
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
"com_a11y_end": "The AI has finished their reply.",
|
||||
"com_a11y_start": "The AI has started their reply.",
|
||||
"com_agents_allow_editing": "Allow other users to edit your agent",
|
||||
"com_agents_by_librechat": "by LibreChat",
|
||||
"com_agents_code_interpreter": "When enabled, allows your agent to leverage the LibreChat Code Interpreter API to run generated code, including file processing, securely. Requires a valid API key.",
|
||||
"com_agents_by_librechat": "by Hanzo",
|
||||
"com_agents_code_interpreter": "When enabled, allows your agent to leverage the Hanzo Code Interpreter API to run generated code, including file processing, securely. Requires a valid API key.",
|
||||
"com_agents_code_interpreter_title": "Code Interpreter API",
|
||||
"com_agents_create_error": "There was an error creating your agent.",
|
||||
"com_agents_description_placeholder": "Optional: Describe your Agent here",
|
||||
@@ -21,7 +21,7 @@
|
||||
"com_agents_mcp_icon_size": "Minimum size 128 x 128 px",
|
||||
"com_agents_mcp_info": "Add MCP servers to your agent to enable it to perform tasks and interact with external services",
|
||||
"com_agents_mcp_name_placeholder": "Custom Tool",
|
||||
"com_agents_mcp_trust_subtext": "Custom connectors are not verified by LibreChat",
|
||||
"com_agents_mcp_trust_subtext": "Custom connectors are not verified by Hanzo",
|
||||
"com_agents_mcps_disabled": "You need to create an agent before adding MCPs.",
|
||||
"com_agents_missing_provider_model": "Please select a provider and model before creating an agent.",
|
||||
"com_agents_name_placeholder": "Optional: The name of the agent",
|
||||
@@ -520,7 +520,7 @@
|
||||
"com_ui_add_multi_conversation": "Add multi-conversation",
|
||||
"com_ui_adding_details": "Adding details",
|
||||
"com_ui_admin": "Admin",
|
||||
"com_ui_admin_access_warning": "Disabling Admin access to this feature may cause unexpected UI issues requiring refresh. If saved, the only way to revert is via the interface setting in librechat.yaml config which affects all roles.",
|
||||
"com_ui_admin_access_warning": "Disabling Admin access to this feature may cause unexpected UI issues requiring refresh. If saved, the only way to revert is via the interface setting in chat.yaml config which affects all roles.",
|
||||
"com_ui_admin_settings": "Admin Settings",
|
||||
"com_ui_advanced": "Advanced",
|
||||
"com_ui_advanced_settings": "Advanced Settings",
|
||||
@@ -811,12 +811,12 @@
|
||||
"com_ui_instructions": "Instructions",
|
||||
"com_ui_key": "Key",
|
||||
"com_ui_late_night": "Happy late night",
|
||||
"com_ui_latest_footer": "Every AI for Everyone.",
|
||||
"com_ui_latest_footer": "AI Infrastructure for Everyone.",
|
||||
"com_ui_latest_production_version": "Latest production version",
|
||||
"com_ui_latest_version": "Latest version",
|
||||
"com_ui_librechat_code_api_key": "Get your LibreChat Code Interpreter API key",
|
||||
"com_ui_librechat_code_api_subtitle": "Secure. Multi-language. Input/Output Files.",
|
||||
"com_ui_librechat_code_api_title": "Run AI Code",
|
||||
"com_ui_hanzo_code_api_key": "Get your Hanzo Code Interpreter API key",
|
||||
"com_ui_hanzo_code_api_subtitle": "Secure. Multi-language. Input/Output Files.",
|
||||
"com_ui_hanzo_code_api_title": "Run AI Code",
|
||||
"com_ui_loading": "Loading...",
|
||||
"com_ui_locked": "Locked",
|
||||
"com_ui_logo": "{{0}} Logo",
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
"com_a11y_end": "La IA ha finalizado su respuesta",
|
||||
"com_a11y_start": "La IA ha comenzado su respuesta",
|
||||
"com_agents_allow_editing": "Permitir que otros usuarios editen su agente",
|
||||
"com_agents_by_librechat": "por LibreChat",
|
||||
"com_agents_code_interpreter": "Cuando está habilitado, permite que su agente utilice la API del Intérprete de Código de LibreChat para ejecutar código generado de manera segura, incluyendo el procesamiento de archivos. Requiere una clave de API válida.",
|
||||
"com_agents_by_librechat": "por Hanzo",
|
||||
"com_agents_code_interpreter": "Cuando está habilitado, permite que su agente utilice la API del Intérprete de Código de Hanzo para ejecutar código generado de manera segura, incluyendo el procesamiento de archivos. Requiere una clave de API válida.",
|
||||
"com_agents_code_interpreter_title": "API del Intérprete de Código",
|
||||
"com_agents_create_error": "Hubo un error al crear su agente.",
|
||||
"com_agents_description_placeholder": "Opcional: Describa su Agente aquí",
|
||||
@@ -437,7 +437,7 @@
|
||||
"com_ui_add_model_preset": "Agregar un modelo o configuración preestablecida para una respuesta adicional",
|
||||
"com_ui_add_multi_conversation": "Agregar múltiples conversaciones",
|
||||
"com_ui_admin": "Administrador",
|
||||
"com_ui_admin_access_warning": "Deshabilitar el acceso de Administrador a esta función puede causar problemas inesperados en la interfaz que requieran actualizar la página. Si se guarda este cambio, la única forma de revertirlo es mediante la configuración de interfaz en el archivo librechat.yaml, lo cual afectará a todos los roles.",
|
||||
"com_ui_admin_access_warning": "Deshabilitar el acceso de Administrador a esta función puede causar problemas inesperados en la interfaz que requieran actualizar la página. Si se guarda este cambio, la única forma de revertirlo es mediante la configuración de interfaz en el archivo chat.yaml, lo cual afectará a todos los roles.",
|
||||
"com_ui_admin_settings": "Configuración de Administrador",
|
||||
"com_ui_advanced": "Avanzado",
|
||||
"com_ui_advanced_settings": "Configuración avanzada",
|
||||
@@ -618,10 +618,14 @@
|
||||
"com_ui_input": "Entrada",
|
||||
"com_ui_instructions": "Instrucciones",
|
||||
"com_ui_latest_footer": "IA para todos.",
|
||||
<<<<<<< HEAD
|
||||
"com_ui_latest_version": "Última versión",
|
||||
"com_ui_librechat_code_api_key": "Obtenga su clave API del Intérprete de Código de LibreChat",
|
||||
"com_ui_librechat_code_api_subtitle": "Seguro. Multilenguaje. Archivos de entrada/salida.",
|
||||
"com_ui_librechat_code_api_title": "Ejecutar Código IA",
|
||||
"com_ui_hanzo_code_api_key": "Obtenga su clave API del Intérprete de Código de LibreChat",
|
||||
=======
|
||||
"com_ui_hanzo_code_api_key": "Obtenga su clave API del Intérprete de Código de Hanzo",
|
||||
>>>>>>> 1c21f3543 (Update brand)
|
||||
"com_ui_hanzo_code_api_subtitle": "Seguro. Multilenguaje. Archivos de entrada/salida.",
|
||||
"com_ui_hanzo_code_api_title": "Ejecutar Código IA",
|
||||
"com_ui_loading": "Cargando...",
|
||||
"com_ui_locked": "Bloqueado",
|
||||
"com_ui_logo": "Logotipo de {{0}}",
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
"com_a11y_end": "AI on oma vastuse lõpetanud.",
|
||||
"com_a11y_start": "AI on oma vastuse andmise alustanud.",
|
||||
"com_agents_allow_editing": "Luba teistel kasutajatel sinu agenti muuta",
|
||||
"com_agents_by_librechat": "LibreChatilt",
|
||||
"com_agents_code_interpreter": "Kui see on lubatud, saab sinu agent kasutada LibreChati koodiinterpreteerimise API-t genereeritud koodi turvaliseks käivitamiseks, sealhulgas failide töötlemiseks. Vajalik on kehtiv API võti.",
|
||||
"com_agents_by_librechat": "Hanzoilt",
|
||||
"com_agents_code_interpreter": "Kui see on lubatud, saab sinu agent kasutada Hanzoi koodiinterpreteerimise API-t genereeritud koodi turvaliseks käivitamiseks, sealhulgas failide töötlemiseks. Vajalik on kehtiv API võti.",
|
||||
"com_agents_code_interpreter_title": "Koodiinterpreteerimise API",
|
||||
"com_agents_create_error": "Agendi loomisel tekkis viga.",
|
||||
"com_agents_description_placeholder": "Valikuline: Kirjelda oma agenti siin",
|
||||
@@ -461,7 +461,7 @@
|
||||
"com_ui_add_model_preset": "Lisa mudel või eelseadistus täiendava vastuse jaoks",
|
||||
"com_ui_add_multi_conversation": "Lisa mitmevestlus",
|
||||
"com_ui_admin": "Administraator",
|
||||
"com_ui_admin_access_warning": "Administraatori juurdepääsu keelamine sellele funktsioonile võib põhjustada ootamatuid kasutajaliidese probleeme, mis nõuavad värskendamist. Kui see on salvestatud, on ainus viis taastada liideseseade kaudu librechat.yaml konfiguratsioonis, mis mõjutab kõiki rolle.",
|
||||
"com_ui_admin_access_warning": "Administraatori juurdepääsu keelamine sellele funktsioonile võib põhjustada ootamatuid kasutajaliidese probleeme, mis nõuavad värskendamist. Kui see on salvestatud, on ainus viis taastada liideseseade kaudu chat.yaml konfiguratsioonis, mis mõjutab kõiki rolle.",
|
||||
"com_ui_admin_settings": "Administraatori seaded",
|
||||
"com_ui_advanced": "Täpsemad",
|
||||
"com_ui_advanced_settings": "Täpsemad seaded",
|
||||
@@ -692,9 +692,9 @@
|
||||
"com_ui_latest_footer": "Igaühele oma AI.",
|
||||
"com_ui_latest_production_version": "Viimane tootmisversioon",
|
||||
"com_ui_latest_version": "Viimane versioon",
|
||||
"com_ui_librechat_code_api_key": "Hangi oma LibreChati koodiinterpreteerimise API võti",
|
||||
"com_ui_librechat_code_api_subtitle": "Turvaline. Mitmekeelne. Sisend-/väljundfailid.",
|
||||
"com_ui_librechat_code_api_title": "Käivita AI koodi",
|
||||
"com_ui_hanzo_code_api_key": "Hangi oma Hanzoi koodiinterpreteerimise API võti",
|
||||
"com_ui_hanzo_code_api_subtitle": "Turvaline. Mitmekeelne. Sisend-/väljundfailid.",
|
||||
"com_ui_hanzo_code_api_title": "Käivita AI koodi",
|
||||
"com_ui_loading": "Laeb...",
|
||||
"com_ui_locked": "Lukus",
|
||||
"com_ui_logo": "{{0}} logo",
|
||||
|
||||
@@ -461,7 +461,7 @@
|
||||
"com_ui_add_model_preset": "یک مدل یا از پیش تعیین شده برای پاسخ اضافی اضافه کنید",
|
||||
"com_ui_add_multi_conversation": "افزودن چند مکالمه",
|
||||
"com_ui_admin": "مدیر",
|
||||
"com_ui_admin_access_warning": "غیرفعال کردن دسترسی ادمین به این ویژگی ممکن است باعث ایجاد مشکلات غیرمنتظره رابط کاربری شود که نیاز به بازخوانی دارد. اگر ذخیره شود، تنها راه برای برگرداندن از طریق تنظیمات رابط در پیکربندی librechat.yaml است که بر همه نقشها تأثیر میگذارد.",
|
||||
"com_ui_admin_access_warning": "غیرفعال کردن دسترسی ادمین به این ویژگی ممکن است باعث ایجاد مشکلات غیرمنتظره رابط کاربری شود که نیاز به بازخوانی دارد. اگر ذخیره شود، تنها راه برای برگرداندن از طریق تنظیمات رابط در پیکربندی chat.yaml است که بر همه نقشها تأثیر میگذارد.",
|
||||
"com_ui_admin_settings": "تنظیمات مدیریت",
|
||||
"com_ui_advanced": "پیشرفته",
|
||||
"com_ui_advanced_settings": "تنظیمات پیشرفته",
|
||||
@@ -686,9 +686,9 @@
|
||||
"com_ui_latest_footer": "هر هوش مصنوعی برای همه",
|
||||
"com_ui_latest_production_version": "آخرین نسخه تولیدی",
|
||||
"com_ui_latest_version": "آخرین نسخه",
|
||||
"com_ui_librechat_code_api_key": "کلید LibreChat Code Interpreter API خود را دریافت کنید",
|
||||
"com_ui_librechat_code_api_subtitle": "امن. چند زبانه. فایل های ورودی/خروجی",
|
||||
"com_ui_librechat_code_api_title": "کد هوش مصنوعی را اجرا کنید",
|
||||
"com_ui_hanzo_code_api_key": "کلید LibreChat Code Interpreter API خود را دریافت کنید",
|
||||
"com_ui_hanzo_code_api_subtitle": "امن. چند زبانه. فایل های ورودی/خروجی",
|
||||
"com_ui_hanzo_code_api_title": "کد هوش مصنوعی را اجرا کنید",
|
||||
"com_ui_loading": "در حال بارگیری...",
|
||||
"com_ui_locked": "قفل شده است",
|
||||
"com_ui_logo": "{{0}} لوگو",
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
"com_a11y_end": "L'IA a terminé sa réponse",
|
||||
"com_a11y_start": "L'IA a commencé sa réponse",
|
||||
"com_agents_allow_editing": "Autoriser les autres utilisateurs à modifier votre agent",
|
||||
"com_agents_by_librechat": "par LibreChat",
|
||||
"com_agents_code_interpreter": "Lorsqu'activé, permet à votre agent d'utiliser l'API d'interpréteur de code LibreChat pour exécuter du code généré de manière sécurisée, y compris le traitement de fichiers. Nécessite une clé API valide.",
|
||||
"com_agents_by_librechat": "par Hanzo",
|
||||
"com_agents_code_interpreter": "Lorsqu'activé, permet à votre agent d'utiliser l'API d'interpréteur de code Hanzo pour exécuter du code généré de manière sécurisée, y compris le traitement de fichiers. Nécessite une clé API valide.",
|
||||
"com_agents_code_interpreter_title": "API d'interpréteur de code",
|
||||
"com_agents_create_error": "Une erreur s'est produite lors de la création de votre agent.",
|
||||
"com_agents_description_placeholder": "Décrivez votre Agent ici (facultatif)",
|
||||
@@ -454,7 +454,7 @@
|
||||
"com_ui_add_model_preset": "Ajouter un modèle ou un préréglage pour une réponse supplémentaire",
|
||||
"com_ui_add_multi_conversation": "Ajouter plusieurs conversations",
|
||||
"com_ui_admin": "Administrateur",
|
||||
"com_ui_admin_access_warning": "La désactivation de l'accès administrateur à cette fonctionnalité peut entraîner des problèmes d'interface imprévus nécessitant une actualisation. Une fois sauvegardé, le seul moyen de rétablir l'accès est via le paramètre d'interface dans la configuration librechat.yaml, ce qui affecte tous les rôles.",
|
||||
"com_ui_admin_access_warning": "La désactivation de l'accès administrateur à cette fonctionnalité peut entraîner des problèmes d'interface imprévus nécessitant une actualisation. Une fois sauvegardé, le seul moyen de rétablir l'accès est via le paramètre d'interface dans la configuration chat.yaml, ce qui affecte tous les rôles.",
|
||||
"com_ui_admin_settings": "Paramètres administratifs",
|
||||
"com_ui_advanced": "Avancé",
|
||||
"com_ui_advanced_settings": "Paramètres avancés",
|
||||
@@ -635,9 +635,9 @@
|
||||
"com_ui_latest_footer": "Chaque IA pour tout le monde.",
|
||||
"com_ui_latest_production_version": "Dernière version de production",
|
||||
"com_ui_latest_version": "Dernière version",
|
||||
"com_ui_librechat_code_api_key": "Obtenir votre clé API pour l'interpréteur de code LibreChat",
|
||||
"com_ui_librechat_code_api_subtitle": "Sécurisé. Multilingue. Fichiers d'entrée/sortie.",
|
||||
"com_ui_librechat_code_api_title": "Exécuter le code IA",
|
||||
"com_ui_hanzo_code_api_key": "Obtenir votre clé API pour l'interpréteur de code Hanzo",
|
||||
"com_ui_hanzo_code_api_subtitle": "Sécurisé. Multilingue. Fichiers d'entrée/sortie.",
|
||||
"com_ui_hanzo_code_api_title": "Exécuter le code IA",
|
||||
"com_ui_locked": "Verrouillé",
|
||||
"com_ui_logo": "Logo {{0}}",
|
||||
"com_ui_manage": "Gérer",
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"com_a11y_end": "הבינה המלאכותית (AI) סיימה להשיב.",
|
||||
"com_a11y_start": "הבינה המלאכותית (AI) מתחילה להשיב.",
|
||||
"com_agents_allow_editing": "אפשר למשתמשים אחרים לערוך את הסוכן שלך",
|
||||
"com_agents_by_librechat": "על ידי LibreChat",
|
||||
"com_agents_by_librechat": "על ידי Hanzo",
|
||||
"com_agents_code_interpreter": "כאשר מופעל, מאפשר לסוכן שלך למנף את ה-API של מפענח הקוד כדי להריץ את הקוד שנוצר, כולל עיבוד קבצים, בצורה מאובטחת. דורש מפתח API חוקי.",
|
||||
"com_agents_code_interpreter_title": "מפענח קוד API",
|
||||
"com_agents_create_error": "אירעה שגיאה ביצירת הסוכן שלך.",
|
||||
@@ -461,7 +461,7 @@
|
||||
"com_ui_add_model_preset": "הוספת מודל או הגדרה קבועה לתגובה נוספת",
|
||||
"com_ui_add_multi_conversation": "הוספת תמיכה בשיחות מרובות",
|
||||
"com_ui_admin": "אדמין",
|
||||
"com_ui_admin_access_warning": "השבתת גישת המנהל לתכונה זו עלולה לגרום לבעיות בלתי צפויות בממשק המשתמש שידרשו רענון. אם השינוי נשמר, הדרך היחידה להחזיר את ההגדרה היא דרך הגדרת הממשק בקובץ librechat.yaml, שמשפיעה על כל התפקידים.",
|
||||
"com_ui_admin_access_warning": "השבתת גישת המנהל לתכונה זו עלולה לגרום לבעיות בלתי צפויות בממשק המשתמש שידרשו רענון. אם השינוי נשמר, הדרך היחידה להחזיר את ההגדרה היא דרך הגדרת הממשק בקובץ chat.yaml, שמשפיעה על כל התפקידים.",
|
||||
"com_ui_admin_settings": "הגדרות אדמין",
|
||||
"com_ui_advanced": "מתקדם",
|
||||
"com_ui_advanced_settings": "הגדרות מתקדמות",
|
||||
@@ -691,9 +691,9 @@
|
||||
"com_ui_latest_footer": "גישה לכל הבינות המלאכותיות (AI) לכולם",
|
||||
"com_ui_latest_production_version": "גרסת הפיתוח העדכנית ביותר",
|
||||
"com_ui_latest_version": "גרסה אחרונה",
|
||||
"com_ui_librechat_code_api_key": "קבל את מפתח ה-API של מפענח הקוד LibreChat",
|
||||
"com_ui_librechat_code_api_subtitle": "אבטחה ללא פשרות. תמיכה במגוון שפות תכנות. יכולת עבודה מלאה עם קבצים.",
|
||||
"com_ui_librechat_code_api_title": "הרץ קוד AI",
|
||||
"com_ui_hanzo_code_api_key": "קבל את מפתח ה-API של מפענח הקוד Hanzo",
|
||||
"com_ui_hanzo_code_api_subtitle": "אבטחה ללא פשרות. תמיכה במגוון שפות תכנות. יכולת עבודה מלאה עם קבצים.",
|
||||
"com_ui_hanzo_code_api_title": "הרץ קוד AI",
|
||||
"com_ui_loading": "טוען...",
|
||||
"com_ui_locked": "נעול",
|
||||
"com_ui_logo": "\"לוגו {{0}}\"",
|
||||
|
||||
@@ -461,7 +461,7 @@
|
||||
"com_ui_add_model_preset": "Modell vagy előre beállított hozzáadása további válaszhoz",
|
||||
"com_ui_add_multi_conversation": "Többes beszélgetés hozzáadása",
|
||||
"com_ui_admin": "Admin",
|
||||
"com_ui_admin_access_warning": "Az adminisztrátori hozzáférés letiltása ehhez a funkcióhoz váratlan kezelőfelületi problémákat okozhat, amelyek frissítést igényelnek. Ha mentésre kerül, a visszaállítás egyetlen módja a librechat.yaml konfigurációs fájlban található interfész beállítás, amely minden szerepkörre hatással van.",
|
||||
"com_ui_admin_access_warning": "Az adminisztrátori hozzáférés letiltása ehhez a funkcióhoz váratlan kezelőfelületi problémákat okozhat, amelyek frissítést igényelnek. Ha mentésre kerül, a visszaállítás egyetlen módja a chat.yaml konfigurációs fájlban található interfész beállítás, amely minden szerepkörre hatással van.",
|
||||
"com_ui_admin_settings": "Adminisztrátori beállítások",
|
||||
"com_ui_advanced": "Haladó",
|
||||
"com_ui_advanced_settings": "Haladó beállítások",
|
||||
@@ -686,9 +686,9 @@
|
||||
"com_ui_latest_footer": "Minden AI mindenkinek.",
|
||||
"com_ui_latest_production_version": "Legfrissebb éles verzió",
|
||||
"com_ui_latest_version": "Legfrissebb verzió",
|
||||
"com_ui_librechat_code_api_key": "Szerezze meg a LibreChat Kódértelmező API kulcsát",
|
||||
"com_ui_librechat_code_api_subtitle": "Biztonságos. Többnyelvű. Be-/Kimeneti fájlok.",
|
||||
"com_ui_librechat_code_api_title": "AI kód futtatása",
|
||||
"com_ui_hanzo_code_api_key": "Szerezze meg a LibreChat Kódértelmező API kulcsát",
|
||||
"com_ui_hanzo_code_api_subtitle": "Biztonságos. Többnyelvű. Be-/Kimeneti fájlok.",
|
||||
"com_ui_hanzo_code_api_title": "AI kód futtatása",
|
||||
"com_ui_loading": "Betöltés...",
|
||||
"com_ui_locked": "Zárolva",
|
||||
"com_ui_logo": "{{0}} Logó",
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
"com_a11y_end": "L'IA ha terminato la sua risposta",
|
||||
"com_a11y_start": "L'IA ha iniziato la sua risposta",
|
||||
"com_agents_allow_editing": "Permetti ad altri utenti di modificare il tuo agente",
|
||||
"com_agents_by_librechat": "da LibreChat",
|
||||
"com_agents_code_interpreter": "Quando abilitato, permette al tuo agente di utilizzare l'API LibreChat Code Interpreter per eseguire codice generato in modo sicuro, inclusa l'elaborazione dei file. Richiede una chiave API valida.",
|
||||
"com_agents_by_librechat": "da Hanzo",
|
||||
"com_agents_code_interpreter": "Quando abilitato, permette al tuo agente di utilizzare l'API Hanzo Code Interpreter per eseguire codice generato in modo sicuro, inclusa l'elaborazione dei file. Richiede una chiave API valida.",
|
||||
"com_agents_code_interpreter_title": "API Interprete Codice",
|
||||
"com_agents_create_error": "Si è verificato un errore durante la creazione del tuo agente.",
|
||||
"com_agents_description_placeholder": "Opzionale: Descrivi qui il tuo Agente",
|
||||
@@ -450,7 +450,7 @@
|
||||
"com_ui_add_model_preset": "Aggiungi un modello o una preimpostazione per una risposta aggiuntiva",
|
||||
"com_ui_add_multi_conversation": "Aggiungi conversazioni multiple",
|
||||
"com_ui_admin": "Amministratore",
|
||||
"com_ui_admin_access_warning": "La disattivazione dell'accesso amministratore a questa funzionalità potrebbe causare problemi imprevisti all'interfaccia utente che richiedono un aggiornamento. Una volta salvata, l'unico modo per ripristinare è attraverso l'impostazione dell'interfaccia nel file di configurazione librechat.yaml, che influisce su tutti i ruoli.",
|
||||
"com_ui_admin_access_warning": "La disattivazione dell'accesso amministratore a questa funzionalità potrebbe causare problemi imprevisti all'interfaccia utente che richiedono un aggiornamento. Una volta salvata, l'unico modo per ripristinare è attraverso l'impostazione dell'interfaccia nel file di configurazione chat.yaml, che influisce su tutti i ruoli.",
|
||||
"com_ui_admin_settings": "Impostazioni Amministratore",
|
||||
"com_ui_advanced": "Avanzate",
|
||||
"com_ui_advanced_settings": "Impostazioni avanzate",
|
||||
@@ -671,9 +671,9 @@
|
||||
"com_ui_latest_footer": "L'intelligenza artificiale per tutti.",
|
||||
"com_ui_latest_production_version": "Ultima versione in produzione",
|
||||
"com_ui_latest_version": "Ultima versione",
|
||||
"com_ui_librechat_code_api_key": "Ottieni la tua chiave API per l'Interprete di Codice LibreChat",
|
||||
"com_ui_librechat_code_api_subtitle": "Sicuro. Multilingue. Gestione File.",
|
||||
"com_ui_librechat_code_api_title": "Esegui Codice AI",
|
||||
"com_ui_hanzo_code_api_key": "Ottieni la tua chiave API per l'Interprete di Codice Hanzo",
|
||||
"com_ui_hanzo_code_api_subtitle": "Sicuro. Multilingue. Gestione File.",
|
||||
"com_ui_hanzo_code_api_title": "Esegui Codice AI",
|
||||
"com_ui_loading": "Caricamento...",
|
||||
"com_ui_locked": "Bloccato",
|
||||
"com_ui_logo": "{{0}} Logo",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user