Compare commits

...
10 Commits
Author SHA1 Message Date
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
114 changed files with 17836 additions and 27267 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": []
}
}
+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
- `librechat.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": "@hanzo/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(),
+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.
*/
+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.
+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": "@hanzo/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 = {
@@ -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]
}`}
>
+3 -3
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": "اختياري: اشرح عميلك هنا",
@@ -578,7 +578,7 @@
"com_ui_input": "إدخال",
"com_ui_instructions": "تعليمات",
"com_ui_latest_footer": "الذكاء الاصطناعي للجميع.",
"com_ui_librechat_code_api_key": "احصل على مفتاح واجهة برمجة التطبيقات لمترجم الكود LibreChat",
"com_ui_librechat_code_api_key": "احصل على مفتاح واجهة برمجة التطبيقات لمترجم الكود Hanzo",
"com_ui_librechat_code_api_subtitle": "آمن. متعدد اللغات. ملفات الإدخال/الإخراج.",
"com_ui_librechat_code_api_title": "تشغيل كود الذكاء الاصطناعي",
"com_ui_locked": "مقفل",
+3 -3
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,7 +565,7 @@
"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_key": "Získejte svůj API klíč pro Hanzo 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_loading": "Načítání...",
+3 -3
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",
@@ -724,7 +724,7 @@
"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_key": "Hole dir deinen Hanzo 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_loading": "Lade …",
+4 -4
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",
@@ -811,10 +811,10 @@
"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_key": "Get your Hanzo 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_loading": "Loading...",
+6 -2
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í",
@@ -618,8 +618,12 @@
"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_key": "Obtenga su clave API del Intérprete de Código de Hanzo",
>>>>>>> 1c21f3543 (Update brand)
"com_ui_librechat_code_api_subtitle": "Seguro. Multilenguaje. Archivos de entrada/salida.",
"com_ui_librechat_code_api_title": "Ejecutar Código IA",
"com_ui_loading": "Cargando...",
+3 -3
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",
@@ -692,7 +692,7 @@
"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_key": "Hangi oma Hanzoi 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_loading": "Laeb...",
+3 -3
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)",
@@ -635,7 +635,7 @@
"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_key": "Obtenir votre clé API pour l'interpréteur de code Hanzo",
"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_locked": "Verrouillé",
+2 -2
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": "אירעה שגיאה ביצירת הסוכן שלך.",
@@ -691,7 +691,7 @@
"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_key": "קבל את מפתח ה-API של מפענח הקוד Hanzo",
"com_ui_librechat_code_api_subtitle": "אבטחה ללא פשרות. תמיכה במגוון שפות תכנות. יכולת עבודה מלאה עם קבצים.",
"com_ui_librechat_code_api_title": "הרץ קוד AI",
"com_ui_loading": "טוען...",
+3 -3
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",
@@ -671,7 +671,7 @@
"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_key": "Ottieni la tua chiave API per l'Interprete di Codice Hanzo",
"com_ui_librechat_code_api_subtitle": "Sicuro. Multilingue. Gestione File.",
"com_ui_librechat_code_api_title": "Esegui Codice AI",
"com_ui_loading": "Caricamento...",
+6 -2
View File
@@ -5,8 +5,8 @@
"com_a11y_end": "AIは返信を完了しました。",
"com_a11y_start": "AIが返信を開始しました。",
"com_agents_allow_editing": "他のユーザーにエージェントの編集を許可する",
"com_agents_by_librechat": "LibreChatより",
"com_agents_code_interpreter": "有効にすると、エージェントがLibreChat Code Interpreter APIを使用して、ファイル処理を含む生成されたコードを安全に実行できるようになります。有効な APIキーが必要です。",
"com_agents_by_librechat": "Hanzoより",
"com_agents_code_interpreter": "有効にすると、エージェントがHanzo Code Interpreter APIを使用して、ファイル処理を含む生成されたコードを安全に実行できるようになります。有効な APIキーが必要です。",
"com_agents_code_interpreter_title": "コードインタープリター API",
"com_agents_create_error": "エージェントの作成中にエラーが発生しました。",
"com_agents_description_placeholder": "オプション: エージェントの説明を入力してください",
@@ -693,9 +693,13 @@
"com_ui_instructions": "指示文",
"com_ui_late_night": "遅い夜を楽しんで",
"com_ui_latest_footer": "Every AI for Everyone.",
<<<<<<< HEAD
"com_ui_latest_production_version": "最新の製品バージョン",
"com_ui_latest_version": "最新バージョン",
"com_ui_librechat_code_api_key": "LibreChat コードインタープリター APIキーを取得",
=======
"com_ui_librechat_code_api_key": "Hanzo コードインタープリター APIキーを取得",
>>>>>>> 1c21f3543 (Update brand)
"com_ui_librechat_code_api_subtitle": "セキュア。多言語対応。ファイル入出力。",
"com_ui_librechat_code_api_title": "AIコードを実行",
"com_ui_loading": "読み込み中...",
+6 -2
View File
@@ -5,8 +5,8 @@
"com_a11y_end": "AI의 답변이 끝났습니다.",
"com_a11y_start": "AI가 응답 중입니다",
"com_agents_allow_editing": "다른 사용자가 내 에이전트를 수정할 수 있도록 허용",
"com_agents_by_librechat": "LibreChat 제공",
"com_agents_code_interpreter": "활성화하면 에이전트가 LibreChat 코드 인터프리터 API를 사용하여 파일 처리를 포함한 생성된 코드를 안전하게 실행할 수 있습니다. 유효한 API 키가 필요합니다.",
"com_agents_by_librechat": "Hanzo 제공",
"com_agents_code_interpreter": "활성화하면 에이전트가 Hanzo 코드 인터프리터 API를 사용하여 파일 처리를 포함한 생성된 코드를 안전하게 실행할 수 있습니다. 유효한 API 키가 필요합니다.",
"com_agents_code_interpreter_title": "코드 인터프리터 API",
"com_agents_create_error": "에이전트 생성 중 오류가 발생했습니다",
"com_agents_description_placeholder": "선택 사항: 여기에 에이전트를 설명하세요",
@@ -726,9 +726,13 @@
"com_ui_instructions": "설명",
"com_ui_late_night": "늦은 밤에도 행복하세요",
"com_ui_latest_footer": "모두를 위한 AI, 한곳에서",
<<<<<<< HEAD
"com_ui_latest_production_version": "최신 프로덕션 버전",
"com_ui_latest_version": "최신 버전",
"com_ui_librechat_code_api_key": "LibreChat 코드 인터프리터 API 키 받기",
=======
"com_ui_librechat_code_api_key": "Hanzo 코드 인터프리터 API 키 받기",
>>>>>>> 1c21f3543 (Update brand)
"com_ui_librechat_code_api_subtitle": "안전한 보안. 다국어 지원. 파일 입출력.",
"com_ui_librechat_code_api_title": "AI 코드 실행",
"com_ui_loading": "로딩 중...",
+5
View File
@@ -5,8 +5,13 @@
"com_a11y_end": "De AI is klaar met het antwoord.",
"com_a11y_start": "De AI is begonnen met antwoorden.",
"com_agents_allow_editing": "Sta sndere gebruikers toe om je agent te bewerken",
<<<<<<< HEAD
"com_agents_by_librechat": "door LibreChat",
"com_agents_code_interpreter": "Indien ingeschakeld, kan je agent de LibreChat Code Interpreter API gebruiken om gegenereerde code, inclusief het verwerken van bestanden, veilig uit te voeren. Vereist een geldige API-sleutel.",
=======
"com_agents_by_librechat": "door LibreCha",
"com_agents_code_interpreter": "Indien ingeschakeld, kan je agent de Hanzo Code Interpreter API gebruiken om gegenereerde code, inclusief het verwerken van bestanden, veilig uit te voeren. Vereist een geldige API-sleutel.",
>>>>>>> 1c21f3543 (Update brand)
"com_agents_code_interpreter_title": "Code Interpreter API",
"com_agents_create_error": "Er is een fout opgetreden bij het aanmaken van je agent.",
"com_agents_description_placeholder": "Optioneel: Beschrijf hier je agent",
+2 -2
View File
@@ -3,7 +3,7 @@
"com_a11y_end": "AI zakończył swoją odpowiedź.",
"com_a11y_start": "AI rozpoczął swoją odpowiedź.",
"com_agents_allow_editing": "Zezwól by inni użytkownicy mogli edytować twojego agenta",
"com_agents_by_librechat": "od LibreChat",
"com_agents_by_librechat": "od Hanzo",
"com_agents_code_interpreter_title": "API interpretera kodu",
"com_agents_create_error": "Wystąpił błąd podczas tworzenia agenta.",
"com_agents_description_placeholder": "Opcjonalnie: Opisz swojego agenta tutaj",
@@ -588,7 +588,7 @@
"com_ui_latest_footer": "Każde AI dla wszystkich.",
"com_ui_latest_production_version": "Najnowsza wersja produkcyjna",
"com_ui_latest_version": "Najnowsza wersja",
"com_ui_librechat_code_api_key": "Uzyskaj klucz API interpretera kodu LibreChat",
"com_ui_librechat_code_api_key": "Uzyskaj klucz API interpretera kodu Hanzo",
"com_ui_librechat_code_api_subtitle": "Bezpieczny. Wielojęzyczny. Pliki wejściowe/wyjściowe.",
"com_ui_librechat_code_api_title": "Uruchom kod AI",
"com_ui_locked": "Zablokowane",
+7 -1
View File
@@ -4,9 +4,15 @@
"com_a11y_ai_composing": "A IA ainda está compondo.",
"com_a11y_end": "A IA terminou de responder.",
"com_a11y_start": "A IA começou a responder.",
<<<<<<< HEAD
"com_agents_allow_editing": "Permitir que outros utilizadores editem o seu agente",
"com_agents_by_librechat": "por LibreChat",
"com_agents_code_interpreter": "Quando ativado, permite que seu agente aproveite a API do interpretador de código LibreChat para executar o código gerado, incluindo o processamento de arquivos, com segurança. Requer uma chave de API válida.",
=======
"com_agents_allow_editing": "Permitir que outros usuários editem seu agente",
"com_agents_by_librechat": "por Hanzo",
"com_agents_code_interpreter": "Quando ativado, permite que seu agente aproveite a API do interpretador de código Hanzo para executar o código gerado, incluindo o processamento de arquivos, com segurança. Requer uma chave de API válida.",
>>>>>>> 1c21f3543 (Update brand)
"com_agents_code_interpreter_title": "API do Interpretador de Código",
"com_agents_create_error": "Houve um erro ao criar seu agente.",
"com_agents_description_placeholder": "Opcional: Descreva seu Agente aqui",
@@ -660,7 +666,7 @@
"com_ui_latest_footer": "Toda IA para Todos.",
"com_ui_latest_production_version": "Última versão de produção",
"com_ui_latest_version": "Ultima versão",
"com_ui_librechat_code_api_key": "Obtenha sua chave de API do LibreChat Code Interpreter",
"com_ui_librechat_code_api_key": "Obtenha sua chave de API do Hanzo Code Interpreter",
"com_ui_librechat_code_api_subtitle": "Seguro. Multi-idioma. Arquivos de entrada/saída.",
"com_ui_librechat_code_api_title": "Execute o código AI",
"com_ui_loading": "Carregando",
+2 -2
View File
@@ -3,8 +3,8 @@
"com_a11y_end": "A IA terminou de responder.",
"com_a11y_start": "A IA começou a responder.",
"com_agents_allow_editing": "Permitir que outros utilizadores editem o seu agente",
"com_agents_by_librechat": "por LibreChat",
"com_agents_code_interpreter": "Quando ativo, permite que os seus agentes usem a API de Interpretação de código do LibreChat para correr código gerado, inclusivé processamento de ficheiros em segurança. Requer uma chave API válida.",
"com_agents_by_librechat": "por Hanzo",
"com_agents_code_interpreter": "Quando ativo, permite que os seus agentes usem a API de Interpretação de código do Hanzo para correr código gerado, inclusivé processamento de ficheiros em segurança. Requer uma chave API válida.",
"com_agents_code_interpreter_title": "API de Interpretação de Código",
"com_agents_create_error": "Houve um erro ao criar seu agente.",
"com_agents_description_placeholder": "Opcional: Descreva seu Agente aqui",
+3 -3
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": "При включении позволяет агенту использовать API интерпретатора кода LibreChat для безопасного выполнения сгенерированного кода, включая обработку файлов. Требуется действующий ключ API.",
"com_agents_by_librechat": "от Hanzo",
"com_agents_code_interpreter": "При включении позволяет агенту использовать API интерпретатора кода Hanzo для безопасного выполнения сгенерированного кода, включая обработку файлов. Требуется действующий ключ API.",
"com_agents_code_interpreter_title": "API Интерпретатора кода",
"com_agents_create_error": "Произошла ошибка при создании вашего агента",
"com_agents_description_placeholder": "Необязательно: описание вашего агента",
@@ -696,7 +696,7 @@
"com_ui_latest_footer": "Искусственный интеллект для каждого",
"com_ui_latest_production_version": "Последняя рабочая версия",
"com_ui_latest_version": "Последняя версия",
"com_ui_librechat_code_api_key": "Получить ключ API интерпретатора кода LibreChat",
"com_ui_librechat_code_api_key": "Получить ключ API интерпретатора кода Hanzo",
"com_ui_librechat_code_api_subtitle": "Безопасно. Многоязычно. Работа с файлами.",
"com_ui_librechat_code_api_title": "Запустить AI-код",
"com_ui_loading": "Загрузка...",
+3 -3
View File
@@ -3,8 +3,8 @@
"com_a11y_end": "AI ตอบคำถามเสร็จสิ้นแล้ว",
"com_a11y_start": "AI เริ่มต้นตอบคำถามแล้ว",
"com_agents_allow_editing": "อนุญาตให้ผู้ใช้คนอื่นแก้ไขเอเจนต์ของคุณ",
"com_agents_by_librechat": "โดย LibreChat",
"com_agents_code_interpreter": "เมื่อเปิดใช้งาน อนุญาตให้เอเจนต์ของคุณใช้ประโยชน์จาก LibreChat Code Interpreter API เพื่อรันโค้ดที่สร้างขึ้น รวมถึงการประมวลผลไฟล์ได้อย่างปลอดภัย ต้องใช้คีย์ API ที่ถูกต้อง",
"com_agents_by_librechat": "โดย Hanzo",
"com_agents_code_interpreter": "เมื่อเปิดใช้งาน อนุญาตให้เอเจนต์ของคุณใช้ประโยชน์จาก Hanzo Code Interpreter API เพื่อรันโค้ดที่สร้างขึ้น รวมถึงการประมวลผลไฟล์ได้อย่างปลอดภัย ต้องใช้คีย์ API ที่ถูกต้อง",
"com_agents_code_interpreter_title": "Code Interpreter API",
"com_agents_create_error": "เกิดข้อผิดพลาดในการสร้างเอเจนต์ของคุณ",
"com_agents_description_placeholder": "ตัวเลือกเพิ่มเติม: อธิบายเอเจนต์ของคุณที่นี่",
@@ -650,7 +650,7 @@
"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_key": "รับคีย์ API ตัวแปลโค้ด Hanzo ของคุณ",
"com_ui_librechat_code_api_subtitle": "ปลอดภัย หลายภาษา ไฟล์อินพุต/เอาต์พุต",
"com_ui_librechat_code_api_title": "รันโค้ด AI",
"com_ui_loading": "กำลังโหลด...",
+3 -3
View File
@@ -3,8 +3,8 @@
"com_a11y_end": "Yapay zeka yanıtını tamamladı.",
"com_a11y_start": "Yapay zeka yanıtlamaya başladı.",
"com_agents_allow_editing": "Diğer kullanıcıların ajanınızı düzenlemesine izin verin",
"com_agents_by_librechat": "LibreChat tarafından",
"com_agents_code_interpreter": "Etkinleştirildiğinde, ajanınızın oluşturulan kodu çalıştırması ve dosya işleme dahil olmak üzere LibreChat Kod Yorumlayıcı API'sini güvenli bir şekilde kullanmasına olanak tanır. Geçerli bir API anahtarı gerektirir.",
"com_agents_by_librechat": "Hanzo tarafından",
"com_agents_code_interpreter": "Etkinleştirildiğinde, ajanınızın oluşturulan kodu çalıştırması ve dosya işleme dahil olmak üzere Hanzo Kod Yorumlayıcı API'sini güvenli bir şekilde kullanmasına olanak tanır. Geçerli bir API anahtarı gerektirir.",
"com_agents_code_interpreter_title": "Kod Yorumlayıcı API",
"com_agents_create_error": "Ajanınız oluşturulurken bir hata oluştu.",
"com_agents_description_placeholder": "İsteğe bağlı: Ajanınızı burada tanımlayın",
@@ -598,7 +598,7 @@
"com_ui_latest_footer": "Herkes için Her Yapay Zeka.",
"com_ui_latest_production_version": "En son üretim sürümü",
"com_ui_latest_version": "En son sürüm",
"com_ui_librechat_code_api_key": "LibreChat Kod Yorumlayıcı API anahtarınızı alın",
"com_ui_librechat_code_api_key": "Hanzo Kod Yorumlayıcı API anahtarınızı alın",
"com_ui_librechat_code_api_subtitle": "Güvenli. Çoklu dil. Giriş/Çıkış Dosyaları.",
"com_ui_librechat_code_api_title": "Yapay Zeka Kodu Çalıştır",
"com_ui_locked": "Kilitli",
+7 -1
View File
@@ -4,9 +4,15 @@
"com_a11y_ai_composing": "AI 仍在撰写中。",
"com_a11y_end": "AI 已完成回复。",
"com_a11y_start": "AI 已开始回复。",
<<<<<<< HEAD
"com_agents_allow_editing": "允许其他用户编辑您的代理",
"com_agents_by_librechat": "由 LibreChat 提供",
"com_agents_code_interpreter": "启用后,您的代理可以安全地使用 LibreChat 代码解释器 API 来运行生成的代码,包括文件处理功能。需要有效的 API 密钥。",
=======
"com_agents_allow_editing": "允许其他用户编辑您的助手",
"com_agents_by_librechat": "由 Hanzo 提供",
"com_agents_code_interpreter": "启用后,您的代理可以安全地使用Hanzo代码解释器API来运行生成的代码,包括文件处理功能。需要有效的API密钥。",
>>>>>>> 1c21f3543 (Update brand)
"com_agents_code_interpreter_title": "代码解释器 API",
"com_agents_create_error": "更新代理时出现错误。",
"com_agents_description_placeholder": "可选:在此描述您的代理",
@@ -686,7 +692,7 @@
"com_ui_latest_footer": "Every AI for Everyone.",
"com_ui_latest_production_version": "最新在用版本",
"com_ui_latest_version": "最新版本",
"com_ui_librechat_code_api_key": "获取您的 LibreChat 代码解释器 API 密钥",
"com_ui_librechat_code_api_key": "获取您的 Hanzo 代码解释器 API 密钥",
"com_ui_librechat_code_api_subtitle": "安全可靠。多语言支持。文件输入/输出。",
"com_ui_librechat_code_api_title": "运行 AI 代码",
"com_ui_loading": "加载中...",
+3 -3
View File
@@ -3,8 +3,8 @@
"com_a11y_end": "AI 已完成回覆",
"com_a11y_start": "AI 已開始回覆。",
"com_agents_allow_editing": "允許其他使用者編輯您的助理",
"com_agents_by_librechat": "由 LibreChat 提供",
"com_agents_code_interpreter": "啟用後,您的代理可以安全地使用 LibreChat 程式碼解譯器 API 來執行產生的程式碼,包括檔案處理功能。需要有效的 API 金鑰。",
"com_agents_by_librechat": "由 Hanzo 提供",
"com_agents_code_interpreter": "啟用後,您的代理可以安全地使用 Hanzo 程式碼解譯器 API 來執行產生的程式碼,包括檔案處理功能。需要有效的 API 金鑰。",
"com_agents_code_interpreter_title": "程式碼解譯器 API",
"com_agents_create_error": "建立您的代理時發生錯誤。",
"com_agents_description_placeholder": "選填:在此描述您的代理程式",
@@ -578,7 +578,7 @@
"com_ui_input": "輸入",
"com_ui_instructions": "說明",
"com_ui_latest_footer": "讓每個人都能使用 AI",
"com_ui_librechat_code_api_key": "取得你的 LibreChat 程式碼解譯器 API 金鑰",
"com_ui_librechat_code_api_key": "取得你的 Hanzo 程式碼解譯器 API 金鑰",
"com_ui_librechat_code_api_subtitle": "安全性高。多語言支援。檔案輸入/輸出。",
"com_ui_librechat_code_api_title": "執行 AI 程式碼",
"com_ui_locked": "已鎖定",
+1 -1
View File
@@ -38,7 +38,7 @@ export default function StartupLayout({ isAuthenticated }: { isAuthenticated?: b
}, [isAuthenticated, navigate, data]);
useEffect(() => {
document.title = startupConfig?.appTitle || 'LibreChat';
document.title = startupConfig?.appTitle || 'Hanzo';
}, [startupConfig?.appTitle]);
useEffect(() => {
+15 -15
View File
@@ -18,17 +18,17 @@
--gray-800: #212121;
--gray-850: #171717;
--gray-900: #0d0d0d;
--green-50: #ecfdf5;
--green-100: #d1fae5;
--green-200: #a7f3d0;
--green-300: #6ee7b7;
--green-400: #34d399;
--green-500: #10b981;
--green-600: #059669;
--green-700: #047857;
--green-800: #065f46;
--green-900: #064e3b;
--green-950: #022c22;
--green-50: #f9fafb;
--green-100: #f3f4f6;
--green-200: #e5e7eb;
--green-300: #d1d5db;
--green-400: #9ca3af;
--green-500: #6b7280;
--green-600: #4b5563;
--green-700: #374151;
--green-800: #1f2937;
--green-900: #111827;
--green-950: #030712;
--red-50: #fef2f2;
--red-100: #fee2e2;
--red-200: #fecaca;
@@ -83,8 +83,8 @@ html {
--surface-tertiary: var(--gray-100);
--surface-tertiary-alt: var(--white);
--surface-dialog: var(--white);
--surface-submit: var(--green-700);
--surface-submit-hover: var(--green-800);
--surface-submit: var(--white);
--surface-submit-hover: var(--gray-100);
--surface-destructive: var(--red-700);
--surface-destructive-hover: var(--red-800);
--surface-chat: var(--white);
@@ -142,8 +142,8 @@ html {
--surface-tertiary: var(--gray-700);
--surface-tertiary-alt: var(--gray-700);
--surface-dialog: var(--gray-850);
--surface-submit: var(--green-700);
--surface-submit-hover: var(--green-800);
--surface-submit: var(--white);
--surface-submit-hover: var(--gray-100);
--surface-destructive: var(--red-800);
--surface-destructive-hover: var(--red-900);
--surface-chat: var(--gray-700);
+1 -1
View File
@@ -23,7 +23,7 @@ const textDocument = {
const spreadsheet = {
paths: SheetPaths,
fill: '#10A37F',
fill: '#6b7280',
title: 'Spreadsheet',
};
+1 -1
View File
@@ -69,7 +69,7 @@ export const processAgentOption = ({
...(_agent ?? ({} as Agent)),
label: _agent?.name ?? '',
value: _agent?.id ?? '',
icon: isGlobal ? <EarthIcon className="icon-md text-green-400" /> : null,
icon: isGlobal ? <EarthIcon className="icon-md text-black dark:text-white" /> : null,
context_files: _agent?.tool_resources?.ocr?.file_ids
? ([] as Array<[string, ExtendedFile]>)
: undefined,
+2 -2
View File
@@ -52,8 +52,8 @@ export default defineConfig(({ command }) => ({
},
includeAssets: [],
manifest: {
name: 'LibreChat',
short_name: 'LibreChat',
name: 'Hanzo',
short_name: 'Hanzo',
start_url: '/',
display: 'standalone',
background_color: '#000000',
+4 -4
View File
@@ -40,7 +40,7 @@ const shouldRebase = process.argv.includes('--rebase');
console.orange(downCommand);
execSync(downCommand, { stdio: 'inherit' });
console.purple('Removing all tags for LibreChat `deployed` images...');
console.purple('Removing all tags for Hanzo `deployed` images...');
const repositories = ['ghcr.io/danny-avila/librechat-dev-api', 'librechat-client'];
repositories.forEach((repo) => {
const tags = execSync(`sudo docker images ${repo} -q`, { encoding: 'utf8' })
@@ -53,16 +53,16 @@ const shouldRebase = process.argv.includes('--rebase');
});
});
console.purple('Pulling latest LibreChat images...');
console.purple('Pulling latest Hanzo images...');
const pullCommand = 'sudo docker-compose -f ./deploy-compose.yml pull api';
console.orange(pullCommand);
execSync(pullCommand, { stdio: 'inherit' });
let startCommand = 'sudo docker-compose -f ./deploy-compose.yml up -d';
console.green('Your LibreChat app is now up to date! Start the app with the following command:');
console.green('Your Hanzo app is now up to date! Start the app with the following command:');
console.purple(startCommand);
console.orange(
'Note: it\'s also recommended to clear your browser cookies and localStorage for LibreChat to assure a fully clean installation.',
'Note: it\'s also recommended to clear your browser cookies and localStorage for Hanzo to assure a fully clean installation.',
);
console.orange('Also: Don\'t worry, your data is safe :)');
})();

Some files were not shown because too many files have changed in this diff Show More