Compare commits

...
15 Commits
Author SHA1 Message Date
Hanzo DevandClaude c4e841c2b9 feat: Add RAG API Docker image build and publish
- Create rag.Dockerfile to rebrand LibreChat RAG API
- Update GitHub Actions to build both main and RAG API images
- Both images now published to ghcr.io/hanzoai/

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-06-25 23:36:24 -04:00
Hanzo Dev 46657ba812 fix: Update Docker build workflow with manual trigger 2025-06-25 23:21:48 -04:00
Hanzo DevandClaude e6737188c1 feat: Complete Hanzo Chat rebranding
- Replace all LibreChat references with Hanzo Chat
- Update package namespace from @librechat to @hanzochat
- Update Docker images to use ghcr.io/hanzoai/chat
- Update configuration files and environment variables
- Update translation keys and UI references
- Rename helm charts to hanzo-chat
- Update MongoDB database name to HanzoChat
- Update all documentation links to hanzo.ai

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-06-25 23:11:58 -04:00
Hanzo DevandClaude 2c832aa8b9 refactor: Rename configuration file from librechat.yaml to chat.yaml
- Renamed librechat.yaml to chat.yaml for brand consistency
- Renamed librechat.example.yaml to chat.example.yaml
- Updated all references across the codebase:
  - Source files (JS/TS)
  - Translation files (all locales)
  - Docker compose files
  - Helm charts
  - Documentation

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-06-25 22:52:55 -04:00
Hanzo DevandClaude fd3d031cc7 feat: Add GitHub Actions workflow for Docker image publishing
- Add workflow to build and push Docker images to ghcr.io on push to main
- Update docker-compose.yml to use ghcr.io registry instead of Docker Hub
- Support multi-platform builds (amd64 and arm64)
- Automatic tagging based on branch/version/SHA

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-06-25 22:46:29 -04:00
Hanzo DevandClaude 7e6c10299f feat: Complete Hanzo Chat rebranding
- Update footer to show "Hanzo Chat v0.7.8 - AI Infrastructure for Everyone"
- Replace all LibreChat references with Hanzo Chat
- Update README with Hanzo Chat branding and clean badges
- Remove all remaining green color styles (replaced with grayscale)
- Update package.json descriptions and metadata
- Update HTML meta tags and window title
- Add ALLOW_REGISTRATION=true to .env for user signup

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-06-25 18:29:16 -04:00
Hanzo DevandClaude 0f8eea1e3a chore: Update Makefile with branding command and sync with upstream
- Add 'make update-branding' command to update logo in running container
- Merge latest upstream LibreChat changes (486c1314a)
- Includes: configurable retention for temp chats, Bedrock MCP support,
  thinking budget for Gemini 2.5, client-side image resizing, and more
- Ensure all Hanzo customizations are preserved

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-06-25 17:58:53 -04:00
Hanzo Dev 751b8122ab Merge latest upstream LibreChat changes with Hanzo customizations 2025-06-25 17:50:20 -04:00
Hanzo DevandClaude 32d3485663 feat: Customize Hanzo Chat with white/black theme and Hanzo logo
- Replace LibreChat logo with Hanzo geometric logo
- Update theme to use white buttons with black text throughout
- Remove green color scheme in favor of white/black styling
- Add comprehensive Makefile for local development
- Update docker-compose.override.yml for asset mounting
- Configure all auth components, panels, and UI elements

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-06-25 17:27:03 -04:00
Hanzo Dev 7b05ada0ca Merge upstream LibreChat updates: Index sync, MeiliSearch optimization, and agent packages bump
# Conflicts:
#	package-lock.json
2025-06-23 17:47:44 -04:00
Hanzo Dev 88cd45da4b Fix package.json exports for CommonJS compatibility 2025-06-23 17:46:49 -04:00
Hanzo Dev d8b20d61e9 Fix all merge conflicts and update dependencies 2025-06-20 14:07:53 -04:00
Hanzo Dev 91e9711e6b Fix merge conflict in package.json - update to v0.7.8 2025-06-20 13:52:49 -04:00
Hanzo Dev d0b64340fe Fix MCP and add example 2025-06-20 13:43:44 -04:00
Hanzo Dev fb7787c87a Update brand 2025-06-20 13:43:27 -04:00
170 changed files with 18183 additions and 27432 deletions
+30
View File
@@ -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": []
}
}
+1 -1
View File
@@ -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
View File
@@ -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
+1 -1
View File
@@ -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
+109
View File
@@ -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
+6
View File
@@ -123,5 +123,11 @@ helm/**/.values.yaml
!/client/src/@types/i18next.d.ts
<<<<<<< HEAD
# SAML Idp cert
*.cert
=======
*.orig
>>>>>>> 1c21f3543 (Update brand)
+9
View File
@@ -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 -1
View File
@@ -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
+85
View File
@@ -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
+231
View File
@@ -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
+27 -46
View File
@@ -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.
[![Watch the video](https://raw.githubusercontent.com/LibreChat-AI/librechat.ai/main/public/images/changelog/v0.7.6.gif)](https://www.youtube.com/watch?v=ilfwGQtJNlI)
[![Watch the video](https://raw.githubusercontent.com/Hanzo-AI/librechat.ai/main/public/images/changelog/v0.7.6.gif)](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">
+1 -1
View File
@@ -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}
*/
+804
View File
@@ -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;
+2 -2
View File
@@ -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',
};
}
+2 -2
View File
@@ -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
View File
@@ -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.
+106
View File
@@ -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,
};
+1 -1
View File
@@ -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:
+4 -4
View File
@@ -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(
+2 -2
View File
@@ -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,
},
+2 -2
View File
@@ -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,
+1 -1
View File
@@ -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.
+10 -10
View File
@@ -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.
*/
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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.
*/
+3 -3
View File
@@ -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.
+1 -1
View File
@@ -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.
+7 -7
View File
@@ -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);
}
}
+1 -1
View File
@@ -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
View File
@@ -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.
+7 -18
View File
@@ -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
+60
View File
@@ -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
View File
@@ -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
View File
@@ -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": {
+8 -31
View File
@@ -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

+2 -2
View File
@@ -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>
+2 -2
View File
@@ -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"
+1 -1
View File
@@ -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>
+6 -6
View File
@@ -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>
+4 -4
View File
@@ -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>
+5 -5
View File
@@ -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>
+1 -1
View File
@@ -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>
);
+2 -2
View File
@@ -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>
);
+107 -21
View File
@@ -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')}
+2 -2
View File
@@ -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',
+1 -1
View File
@@ -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={
+2 -2
View File
@@ -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>
+4 -2
View File
@@ -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]
}`}
>
+6 -6
View File
@@ -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": "إدارة",
+4 -4
View File
@@ -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}}",
+5 -5
View File
@@ -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}}",
+4 -4
View File
@@ -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",
+6 -6
View File
@@ -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",
+8 -8
View File
@@ -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",
+10 -6
View File
@@ -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}}",
+6 -6
View File
@@ -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",
+4 -4
View File
@@ -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}} لوگو",
+6 -6
View File
@@ -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 -5
View File
@@ -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}}\"",
+4 -4
View File
@@ -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ó",
+6 -6
View File
@@ -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