From 9fda96a735bcb99fd9af9b4665141b703a0827de Mon Sep 17 00:00:00 2001 From: dittops Date: Tue, 7 Oct 2025 16:59:11 +0000 Subject: [PATCH 01/10] Feat: intial commit for polylingua Signed-off-by: dittops --- PolyLingua/Dockerfile | 25 ++ PolyLingua/README.md | 307 ++++++++++++++++++ PolyLingua/deploy/build.sh | 44 +++ PolyLingua/deploy/nginx.conf | 70 ++++ PolyLingua/deploy/start.sh | 63 ++++ PolyLingua/deploy/stop.sh | 25 ++ PolyLingua/deploy/test.sh | 54 +++ PolyLingua/docker-compose.yaml | 101 ++++++ PolyLingua/requirements.txt | 14 + PolyLingua/set_env.sh | 110 +++++++ PolyLingua/translation.py | 152 +++++++++ PolyLingua/ui/.gitignore | 36 ++ PolyLingua/ui/Dockerfile | 59 ++++ PolyLingua/ui/README.md | 152 +++++++++ PolyLingua/ui/app/globals.css | 59 ++++ PolyLingua/ui/app/layout.tsx | 22 ++ PolyLingua/ui/app/page.tsx | 9 + PolyLingua/ui/components.json | 17 + PolyLingua/ui/components/translation-form.tsx | 243 ++++++++++++++ PolyLingua/ui/components/ui/button.tsx | 56 ++++ PolyLingua/ui/components/ui/card.tsx | 79 +++++ PolyLingua/ui/components/ui/label.tsx | 24 ++ PolyLingua/ui/components/ui/select.tsx | 158 +++++++++ PolyLingua/ui/components/ui/textarea.tsx | 24 ++ PolyLingua/ui/lib/utils.ts | 6 + PolyLingua/ui/next.config.js | 9 + PolyLingua/ui/package.json | 35 ++ PolyLingua/ui/postcss.config.js | 6 + PolyLingua/ui/public/.gitkeep | 0 PolyLingua/ui/tailwind.config.ts | 80 +++++ PolyLingua/ui/tsconfig.json | 27 ++ 31 files changed, 2066 insertions(+) create mode 100644 PolyLingua/Dockerfile create mode 100644 PolyLingua/README.md create mode 100755 PolyLingua/deploy/build.sh create mode 100644 PolyLingua/deploy/nginx.conf create mode 100755 PolyLingua/deploy/start.sh create mode 100755 PolyLingua/deploy/stop.sh create mode 100755 PolyLingua/deploy/test.sh create mode 100644 PolyLingua/docker-compose.yaml create mode 100644 PolyLingua/requirements.txt create mode 100755 PolyLingua/set_env.sh create mode 100644 PolyLingua/translation.py create mode 100644 PolyLingua/ui/.gitignore create mode 100644 PolyLingua/ui/Dockerfile create mode 100644 PolyLingua/ui/README.md create mode 100644 PolyLingua/ui/app/globals.css create mode 100644 PolyLingua/ui/app/layout.tsx create mode 100644 PolyLingua/ui/app/page.tsx create mode 100644 PolyLingua/ui/components.json create mode 100644 PolyLingua/ui/components/translation-form.tsx create mode 100644 PolyLingua/ui/components/ui/button.tsx create mode 100644 PolyLingua/ui/components/ui/card.tsx create mode 100644 PolyLingua/ui/components/ui/label.tsx create mode 100644 PolyLingua/ui/components/ui/select.tsx create mode 100644 PolyLingua/ui/components/ui/textarea.tsx create mode 100644 PolyLingua/ui/lib/utils.ts create mode 100644 PolyLingua/ui/next.config.js create mode 100644 PolyLingua/ui/package.json create mode 100644 PolyLingua/ui/postcss.config.js create mode 100644 PolyLingua/ui/public/.gitkeep create mode 100644 PolyLingua/ui/tailwind.config.ts create mode 100644 PolyLingua/ui/tsconfig.json diff --git a/PolyLingua/Dockerfile b/PolyLingua/Dockerfile new file mode 100644 index 0000000000..a4bbbd6a16 --- /dev/null +++ b/PolyLingua/Dockerfile @@ -0,0 +1,25 @@ +# Copyright (C) 2024 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +FROM python:3.11-slim + +WORKDIR /home/user + +# Install system dependencies +RUN apt-get update && apt-get install -y --no-install-recommends \ + curl \ + && rm -rf /var/lib/apt/lists/* + +# Copy requirements and install Python dependencies +COPY requirements.txt . +RUN pip install --no-cache-dir --upgrade pip && \ + pip install --no-cache-dir -r requirements.txt + +# Copy translation service +COPY translation.py . + +# Expose service port +EXPOSE 8888 + +# Run the translation service +ENTRYPOINT ["python", "translation.py"] diff --git a/PolyLingua/README.md b/PolyLingua/README.md new file mode 100644 index 0000000000..f7355328ad --- /dev/null +++ b/PolyLingua/README.md @@ -0,0 +1,307 @@ +# PloyLingua + +A production-ready translation service built with **OPEA (Open Platform for Enterprise AI)** components, featuring a modern Next.js UI and microservices architecture. + +## 🏗️ Architecture + +This service implements a **5-layer microservices architecture**: + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Nginx Reverse Proxy │ +│ (Port 80) │ +└────────────────┬────────────────────────────────────────────┘ + │ + ┌────────┴─────────┐ + │ │ +┌───────▼────────┐ ┌──────▼──────────────────┐ +│ Next.js UI │ │ Translation Megaservice │ +│ (Port 5173) │ │ (Port 8888) │ +└────────────────┘ └──────┬──────────────────┘ + │ + ┌────────▼────────────┐ + │ LLM Microservice │ + │ (Port 9000) │ + └────────┬────────────┘ + │ + ┌────────▼────────────┐ + │ TGI Model Server │ + │ (Port 8008) │ + └─────────────────────┘ +``` + +### Components + +1. **TGI Service** - HuggingFace Text Generation Inference for model serving +2. **LLM Microservice** - OPEA wrapper providing standardized API +3. **Translation Megaservice** - Orchestrator that formats prompts and routes requests +4. **UI Service** - Next.js 14 frontend with React and TypeScript +5. **Nginx** - Reverse proxy for unified access + +## 🚀 Quick Start + +### Prerequisites + +- Docker and Docker Compose +- Git +- HuggingFace Account (for model access) +- 8GB+ RAM recommended +- ~10GB disk space for models + +### 1. Clone and Setup + +```bash +cd PolyLingua + +# Configure environment variables +./set_env.sh +``` + +You'll be prompted for: +- **HuggingFace API Token** - Get from https://huggingface.co/settings/tokens +- **Model ID** - Default: `haoranxu/ALMA-13B` (translation-optimized model) +- **Host IP** - Your server's IP address +- **Ports and proxy settings** + +### 2. Build Images + +```bash +./deploy/build.sh +``` + +This builds: +- Translation backend service +- Next.js UI service + +### 3. Start Services + +```bash +./deploy/start.sh +``` + +Wait for services to initialize (~2-5 minutes for first run as models download). + +### 4. Access the Application + +- **Web UI**: http://localhost:80 +- **API Endpoint**: http://localhost:8888/v1/translation + +### 5. Test the Service + +```bash +./deploy/test.sh +``` + +Or test manually: + +```bash +curl -X POST http://localhost:8888/v1/translation \ + -H "Content-Type: application/json" \ + -d '{ + "language_from": "English", + "language_to": "Spanish", + "source_language": "Hello, how are you today?" + }' +``` + +## 📋 Configuration + +### Environment Variables + +Key variables in `.env`: + +| Variable | Description | Default | +|----------|-------------|---------| +| `HF_TOKEN` | HuggingFace API token | Required | +| `LLM_MODEL_ID` | Model to use for translation | `haoranxu/ALMA-13B` | +| `MODEL_CACHE` | Directory for model storage | `./data` | +| `host_ip` | Server IP address | `localhost` | +| `NGINX_PORT` | External port for web access | `80` | + +See `.env.example` for full configuration options. + +### Supported Models + +The service works with any HuggingFace text generation model. Recommended models: + +- **swiss-ai/Apertus-8B-Instruct-2509** - Multilingual translation (default) +- **haoranxu/ALMA-7B** - Specialized translation model + + +## 🛠️ Development + +### Project Structure + +``` +opea-translation/ +├── translation.py # Backend translation service +├── requirements.txt # Python dependencies +├── Dockerfile # Backend container definition +├── docker-compose.yaml # Multi-service orchestration +├── set_env.sh # Environment setup script +├── .env.example # Environment template +├── ui/ # Next.js frontend +│ ├── app/ # Next.js app directory +│ ├── components/ # React components +│ ├── Dockerfile # UI container definition +│ └── package.json # Node dependencies +└── deploy/ # Deployment scripts + ├── nginx.conf # Nginx configuration + ├── build.sh # Image build script + ├── start.sh # Service startup script + ├── stop.sh # Service shutdown script + └── test.sh # API testing script +``` + +### Running Locally (Development) + +**Backend:** +```bash +# Install dependencies +pip install -r requirements.txt + +# Set environment variables +export LLM_SERVICE_HOST_IP=localhost +export LLM_SERVICE_PORT=9000 +export MEGA_SERVICE_PORT=8888 + +# Run service +python translation.py +``` + +**Frontend:** +```bash +cd ui +npm install +npm run dev +``` + +### API Reference + +#### POST /v1/translation + +Translate text between languages. + +**Request:** +```json +{ + "language_from": "English", + "language_to": "Spanish", + "source_language": "Your text to translate" +} +``` + +**Response:** +```json +{ + "model": "translation", + "choices": [{ + "index": 0, + "message": { + "role": "assistant", + "content": "Translated text here" + }, + "finish_reason": "stop" + }], + "usage": {} +} +``` + +## 🔧 Operations + +### View Logs + +```bash +# All services +docker compose logs -f + +# Specific service +docker compose logs -f translation-backend-server +docker compose logs -f translation-ui-server +``` + +### Stop Services + +```bash +./deploy/stop.sh +``` + +### Update Services + +```bash +# Rebuild images +./deploy/build.sh + +# Restart services +docker compose down +./deploy/start.sh +``` + +### Clean Up + +```bash +# Stop and remove containers +docker compose down + +# Remove volumes (including model cache) +docker compose down -v +``` + +## 🐛 Troubleshooting + +### Service won't start + +1. Check if ports are available: + ```bash + sudo lsof -i :80,8888,9000,8008,5173 + ``` + +2. Verify environment variables: + ```bash + cat .env + ``` + +3. Check service health: + ```bash + docker compose ps + docker compose logs + ``` + +### Model download fails + +- Ensure `HF_TOKEN` is set correctly +- Check internet connection +- Verify model ID exists on HuggingFace +- Check disk space in `MODEL_CACHE` directory + +### Translation errors + +- Wait for TGI service to fully initialize (check logs) +- Verify LLM service is healthy: `curl http://localhost:9000/v1/health` +- Check TGI service: `curl http://localhost:8008/health` + +### UI can't connect to backend + +- Verify `BACKEND_SERVICE_ENDPOINT` in `.env` +- Check if backend is running: `docker compose ps` +- Test API directly: `curl http://localhost:8888/v1/translation` + + + +## 🔗 Resources + +- [OPEA Project](https://github.com/opea-project) +- [GenAIComps](https://github.com/opea-project/GenAIComps) +- [GenAIExamples](https://github.com/opea-project/GenAIExamples) +- [HuggingFace Text Generation Inference](https://github.com/huggingface/text-generation-inference) + +## 📧 Support + +For issues and questions: +- Open an issue on GitHub +- Check existing issues for solutions +- Review OPEA documentation + +--- + +**Built with OPEA - Open Platform for Enterprise AI** 🚀 diff --git a/PolyLingua/deploy/build.sh b/PolyLingua/deploy/build.sh new file mode 100755 index 0000000000..361e2ee1cf --- /dev/null +++ b/PolyLingua/deploy/build.sh @@ -0,0 +1,44 @@ +#!/bin/bash +# Copyright (C) 2024 +# SPDX-License-Identifier: Apache-2.0 + +set -e + +echo "======================================" +echo "Building OPEA Translation Service Images" +echo "======================================" + +# Source environment variables +if [ -f .env ]; then + echo "Loading environment from .env file..." + export $(cat .env | grep -v '^#' | xargs) +else + echo "Warning: .env file not found. Using default values." + echo "Run './set_env.sh' to configure environment variables." +fi + +# Build translation backend +echo "" +echo "Building translation backend service..." +docker build --no-cache -t ${REGISTRY:-opea}/translation:${TAG:-latest} -f Dockerfile . + +# Build translation UI +echo "" +echo "Building translation UI service..." +docker build --no-cache \ + --build-arg BACKEND_SERVICE_ENDPOINT=${BACKEND_SERVICE_ENDPOINT} \ + -t ${REGISTRY:-opea}/translation-ui:${TAG:-latest} \ + -f ui/Dockerfile ./ui + +echo "" +echo "======================================" +echo "Build completed successfully!" +echo "======================================" +echo "" +echo "Images built:" +echo " - ${REGISTRY:-opea}/translation:${TAG:-latest}" +echo " - ${REGISTRY:-opea}/translation-ui:${TAG:-latest}" +echo "" +echo "To start the services, run:" +echo " ./deploy/start.sh" +echo "" diff --git a/PolyLingua/deploy/nginx.conf b/PolyLingua/deploy/nginx.conf new file mode 100644 index 0000000000..0d473dc442 --- /dev/null +++ b/PolyLingua/deploy/nginx.conf @@ -0,0 +1,70 @@ +# Copyright (C) 2024 +# SPDX-License-Identifier: Apache-2.0 + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + error_log /var/log/nginx/error.log warn; + + sendfile on; + keepalive_timeout 65; + + # Frontend server + upstream frontend { + server translation-ui-server:5173; + } + + # Backend server + upstream backend { + server translation-backend-server:8888; + } + + server { + listen 80; + server_name localhost; + + # Frontend routes + location / { + proxy_pass http://frontend; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # Backend API routes + location /v1/ { + proxy_pass http://backend; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_connect_timeout 600s; + proxy_send_timeout 600s; + proxy_read_timeout 600s; + send_timeout 600s; + } + + # Health check endpoint + location /health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; + } + } +} diff --git a/PolyLingua/deploy/start.sh b/PolyLingua/deploy/start.sh new file mode 100755 index 0000000000..132157e1e1 --- /dev/null +++ b/PolyLingua/deploy/start.sh @@ -0,0 +1,63 @@ +#!/bin/bash +# Copyright (C) 2024 +# SPDX-License-Identifier: Apache-2.0 + +set -e + +echo "======================================" +echo "Starting OPEA Translation Service" +echo "======================================" + +# Source environment variables +if [ -f .env ]; then + echo "Loading environment from .env file..." + export $(cat .env | grep -v '^#' | xargs) +else + echo "ERROR: .env file not found!" + echo "Please run './set_env.sh' first to configure environment variables." + exit 1 +fi + +# Check for HuggingFace token +if [ -z "$HF_TOKEN" ]; then + echo "WARNING: HF_TOKEN is not set!" + echo "You may need a HuggingFace token to download models." + read -p "Continue anyway? (y/N): " confirm + if [[ ! $confirm =~ ^[Yy]$ ]]; then + exit 1 + fi +fi + +# Create model cache directory if it doesn't exist +mkdir -p ${MODEL_CACHE:-./data} + +echo "" +echo "Starting services with docker compose..." +docker compose up -d + +echo "" +echo "Waiting for services to start..." +sleep 5 + +echo "" +echo "======================================" +echo "Service Status" +echo "======================================" +docker compose ps + +echo "" +echo "======================================" +echo "Services started successfully!" +echo "======================================" +echo "" +echo "Access points:" +echo " - Frontend UI: http://${host_ip:-localhost}:${NGINX_PORT:-80}" +echo " - Backend API: http://${host_ip:-localhost}:8888" +# echo " - LLM Service: http://${host_ip:-localhost}:9000" +echo "" +# echo "To view logs:" +# echo " docker compose logs -f" +# echo "" +echo "To stop services:" +echo " ./deploy/stop.sh" +echo "" diff --git a/PolyLingua/deploy/stop.sh b/PolyLingua/deploy/stop.sh new file mode 100755 index 0000000000..6d1bf5ab36 --- /dev/null +++ b/PolyLingua/deploy/stop.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# Copyright (C) 2024 +# SPDX-License-Identifier: Apache-2.0 + +set -e + +echo "======================================" +echo "Stopping OPEA Translation Service" +echo "======================================" + +echo "" +echo "Stopping services..." +docker compose down + +echo "" +echo "======================================" +echo "Services stopped successfully!" +echo "======================================" +echo "" +echo "To start services again:" +echo " ./deploy/start.sh" +echo "" +echo "To remove all data (including model cache):" +echo " docker compose down -v" +echo "" diff --git a/PolyLingua/deploy/test.sh b/PolyLingua/deploy/test.sh new file mode 100755 index 0000000000..4137cd6277 --- /dev/null +++ b/PolyLingua/deploy/test.sh @@ -0,0 +1,54 @@ +#!/bin/bash +# Copyright (C) 2024 +# SPDX-License-Identifier: Apache-2.0 + +set -e + +echo "======================================" +echo "Testing OPEA Translation Service" +echo "======================================" + +# Source environment variables +if [ -f .env ]; then + export $(cat .env | grep -v '^#' | xargs) +fi + +HOST=${host_ip:-localhost} +PORT=${BACKEND_SERVICE_PORT:-8888} + +echo "" +echo "Testing translation endpoint..." +echo "Target: http://${HOST}:${PORT}/v1/translation" +echo "" + +response=$(curl -s -w "\n%{http_code}" -X POST "http://${HOST}:${PORT}/v1/translation" \ + -H "Content-Type: application/json" \ + -d '{ + "language_from": "English", + "language_to": "Spanish", + "source_language": "Hello, how are you today?" + }') + +http_code=$(echo "$response" | tail -n1) +body=$(echo "$response" | head -n-1) + +echo "HTTP Status: $http_code" +echo "" + +if [ "$http_code" -eq 200 ]; then + echo "✓ Translation service is working!" + echo "" + echo "Response:" + echo "$body" | jq '.' 2>/dev/null || echo "$body" +else + echo "✗ Translation service returned an error!" + echo "" + echo "Response:" + echo "$body" + exit 1 +fi + +echo "" +echo "======================================" +echo "Test completed successfully!" +echo "======================================" diff --git a/PolyLingua/docker-compose.yaml b/PolyLingua/docker-compose.yaml new file mode 100644 index 0000000000..eac6650d27 --- /dev/null +++ b/PolyLingua/docker-compose.yaml @@ -0,0 +1,101 @@ +# Copyright (C) 2024 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +services: + tgi-service: + image: ghcr.io/huggingface/text-generation-inference:2.4.0-intel-cpu + container_name: tgi-service + ports: + - "8008:80" + environment: + no_proxy: ${no_proxy} + http_proxy: ${http_proxy} + https_proxy: ${https_proxy} + HF_TOKEN: ${HF_TOKEN} + HF_HUB_DISABLE_PROGRESS_BARS: 1 + HF_HUB_ENABLE_HF_TRANSFER: 0 + host_ip: ${host_ip} + healthcheck: + test: ["CMD-SHELL", "curl -f http://${host_ip}:8008/health || exit 1"] + interval: 10s + timeout: 10s + retries: 100 + volumes: + - "${MODEL_CACHE:-./data}:/data" + shm_size: 1g + command: --model-id ${LLM_MODEL_ID} --cuda-graphs 0 + + llm: + image: ${REGISTRY:-opea}/llm-textgen:${TAG:-latest} + container_name: llm-textgen-server + depends_on: + tgi-service: + condition: service_healthy + ports: + - "9000:9000" + ipc: host + environment: + no_proxy: ${no_proxy} + http_proxy: ${http_proxy} + https_proxy: ${https_proxy} + LLM_ENDPOINT: ${TGI_LLM_ENDPOINT} + LLM_MODEL_ID: ${LLM_MODEL_ID} + HF_TOKEN: ${HF_TOKEN} + HF_HUB_DISABLE_PROGRESS_BARS: 1 + HF_HUB_ENABLE_HF_TRANSFER: 0 + restart: unless-stopped + + translation-backend-server: + image: ${REGISTRY:-opea}/translation:${TAG:-latest} + container_name: translation-backend-server + depends_on: + - tgi-service + - llm + ports: + - "8888:8888" + environment: + - no_proxy=${no_proxy} + - https_proxy=${https_proxy} + - http_proxy=${http_proxy} + - MEGA_SERVICE_HOST_IP=${MEGA_SERVICE_HOST_IP} + - MEGA_SERVICE_PORT=8888 + - LLM_SERVICE_HOST_IP=${LLM_SERVICE_HOST_IP} + - LLM_SERVICE_PORT=${LLM_SERVICE_PORT} + ipc: host + restart: always + + translation-ui-server: + image: ${REGISTRY:-opea}/translation-ui:${TAG:-latest} + container_name: translation-ui-server + depends_on: + - translation-backend-server + ports: + - "5173:5173" + environment: + - no_proxy=${no_proxy} + - https_proxy=${https_proxy} + - http_proxy=${http_proxy} + - BACKEND_SERVICE_ENDPOINT=${BACKEND_SERVICE_ENDPOINT} + ipc: host + restart: always + + translation-nginx-server: + image: nginx:alpine + container_name: translation-nginx-server + depends_on: + - translation-backend-server + - translation-ui-server + ports: + - "${NGINX_PORT:-80}:80" + volumes: + - ./deploy/nginx.conf:/etc/nginx/nginx.conf:ro + environment: + - no_proxy=${no_proxy} + - https_proxy=${https_proxy} + - http_proxy=${http_proxy} + ipc: host + restart: always + +networks: + default: + driver: bridge diff --git a/PolyLingua/requirements.txt b/PolyLingua/requirements.txt new file mode 100644 index 0000000000..aae4c653c1 --- /dev/null +++ b/PolyLingua/requirements.txt @@ -0,0 +1,14 @@ +# OPEA GenAIComps Framework +opea-comps>=1.3.0 + +# Core Dependencies +fastapi>=0.109.0 +uvicorn[standard]>=0.27.0 +python-multipart>=0.0.9 + +# Async Support +aiohttp>=3.9.0 +asyncio>=3.4.3 + +# Language Detection +langdetect>=1.0.9 diff --git a/PolyLingua/set_env.sh b/PolyLingua/set_env.sh new file mode 100755 index 0000000000..f3adb267f6 --- /dev/null +++ b/PolyLingua/set_env.sh @@ -0,0 +1,110 @@ +#!/bin/bash +# Copyright (C) 2024 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +# Environment Setup Script for OPEA Translation Service + +echo "======================================" +echo "OPEA Translation Service Setup" +echo "======================================" +echo "" + +# Function to prompt for input with default value +prompt_with_default() { + local prompt="$1" + local default="$2" + local var_name="$3" + + read -p "$prompt [$default]: " input + input="${input:-$default}" + export $var_name="$input" + echo "export $var_name=\"$input\"" >> .env +} + +# Remove existing .env file +rm -f .env + +# Get host IP +host_ip=$(hostname -I | awk '{print $1}') +if [ -z "$host_ip" ]; then + host_ip="localhost" +fi + +echo "Detected host IP: $host_ip" +echo "" + +# HuggingFace Configuration +echo "--- HuggingFace Configuration ---" +prompt_with_default "Enter your HuggingFace API Token (get from https://huggingface.co/settings/tokens)" "" "HF_TOKEN" + +# Model Configuration +echo "" +echo "--- Model Configuration ---" +prompt_with_default "Enter LLM Model ID" "haoranxu/ALMA-13B" "LLM_MODEL_ID" +prompt_with_default "Enter Model Cache Directory" "./data" "MODEL_CACHE" + +# Host Configuration +echo "" +echo "--- Host Configuration ---" +prompt_with_default "Enter Host IP" "$host_ip" "host_ip" + +# Service Endpoints +echo "" +echo "--- Service Endpoints ---" +export TGI_LLM_ENDPOINT="http://${host_ip}:8008" +echo "export TGI_LLM_ENDPOINT=\"http://${host_ip}:8008\"" >> .env + +export LLM_SERVICE_HOST_IP="${host_ip}" +echo "export LLM_SERVICE_HOST_IP=\"${host_ip}\"" >> .env + +export LLM_SERVICE_PORT="9000" +echo "export LLM_SERVICE_PORT=\"9000\"" >> .env + +export MEGA_SERVICE_HOST_IP="${host_ip}" +echo "export MEGA_SERVICE_HOST_IP=\"${host_ip}\"" >> .env + +export BACKEND_SERVICE_ENDPOINT="http://${host_ip}:8888" +echo "export BACKEND_SERVICE_ENDPOINT=\"http://${host_ip}:8888\"" >> .env + +export FRONTEND_SERVICE_IP="${host_ip}" +echo "export FRONTEND_SERVICE_IP=\"${host_ip}\"" >> .env + +export FRONTEND_SERVICE_PORT="5173" +echo "export FRONTEND_SERVICE_PORT=\"5173\"" >> .env + +export BACKEND_SERVICE_NAME="translation" +echo "export BACKEND_SERVICE_NAME=\"translation\"" >> .env + +export BACKEND_SERVICE_IP="${host_ip}" +echo "export BACKEND_SERVICE_IP=\"${host_ip}\"" >> .env + +export BACKEND_SERVICE_PORT="8888" +echo "export BACKEND_SERVICE_PORT=\"8888\"" >> .env + +# Docker Configuration +echo "" +echo "--- Docker Configuration ---" +prompt_with_default "Enter Docker Registry" "opea" "REGISTRY" +prompt_with_default "Enter Docker Tag" "latest" "TAG" + +# Nginx Configuration +prompt_with_default "Enter Nginx Port" "80" "NGINX_PORT" + +# Proxy Settings (optional) +echo "" +echo "--- Proxy Settings (optional, press Enter to skip) ---" +prompt_with_default "Enter HTTP Proxy" "" "http_proxy" +prompt_with_default "Enter HTTPS Proxy" "" "https_proxy" +prompt_with_default "Enter No Proxy" "" "no_proxy" + +echo "" +echo "======================================" +echo "Configuration saved to .env" +echo "======================================" +echo "" +echo "To load these environment variables, run:" +echo " source .env" +echo "" +echo "To start the services, run:" +echo " docker compose up -d" +echo "" diff --git a/PolyLingua/translation.py b/PolyLingua/translation.py new file mode 100644 index 0000000000..867782144f --- /dev/null +++ b/PolyLingua/translation.py @@ -0,0 +1,152 @@ +# Copyright (c) 2024 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import asyncio +import os +from langdetect import detect, LangDetectException + +from comps import MegaServiceEndpoint, MicroService, ServiceOrchestrator, ServiceRoleType, ServiceType +from comps.cores.proto.api_protocol import ( + ChatCompletionRequest, + ChatCompletionResponse, + ChatCompletionResponseChoice, + ChatMessage, + UsageInfo, +) +from fastapi import Request +from fastapi.responses import StreamingResponse + +MEGA_SERVICE_PORT = int(os.getenv("MEGA_SERVICE_PORT", 8888)) +LLM_SERVICE_HOST_IP = os.getenv("LLM_SERVICE_HOST_IP", "0.0.0.0") +LLM_SERVICE_PORT = int(os.getenv("LLM_SERVICE_PORT", 9000)) +LLM_MODEL_ID = os.getenv("LLM_MODEL_ID", "swiss-ai/Apertus-8B-Instruct-2509") + +# Language code to name mapping +LANGUAGE_MAP = { + "en": "English", + "es": "Spanish", + "fr": "French", + "de": "German", + "it": "Italian", + "pt": "Portuguese", + "ru": "Russian", + "ja": "Japanese", + "ko": "Korean", + "zh-cn": "Chinese (Simplified)", + "zh-tw": "Chinese (Traditional)", + "ar": "Arabic", + "hi": "Hindi", + "nl": "Dutch", + "pl": "Polish", + "tr": "Turkish", + "sv": "Swedish", +} + + +class TranslationService: + def __init__(self, host="0.0.0.0", port=8000): + self.host = host + self.port = port + self.megaservice = ServiceOrchestrator() + self.endpoint = str(MegaServiceEndpoint.TRANSLATION) + + def add_remote_service(self): + llm = MicroService( + name="llm", + host=LLM_SERVICE_HOST_IP, + port=LLM_SERVICE_PORT, + endpoint="/v1/chat/completions", + use_remote_service=True, + service_type=ServiceType.LLM, + ) + self.megaservice.add(llm) + + async def handle_request(self, request: Request): + data = await request.json() + language_from = data["language_from"] + language_to = data["language_to"] + source_language = data["source_language"] + + # Auto-detect source language if set to "auto" + if language_from.lower() == "auto": + try: + detected_code = detect(source_language) + language_from = LANGUAGE_MAP.get(detected_code, "English") + except LangDetectException: + # Fallback to English if detection fails + language_from = "English" + prompt_template = """ + You are a translation assistant who is specialized in translating {language_from} to {language_to}. + + 1. Answer should only contain the translation of the source language to the target language. + 2. Do not include any other text or information. + 3. Do not include any other language than the target language. + 4. Do not include any other information than the translation. + + Translate this from {language_from} to {language_to}: + + {source_language} + + """ + prompt = prompt_template.format( + language_from=language_from, language_to=language_to, source_language=source_language + ) + + # Create chat completion request as dict for the LLM service + chat_request_dict = { + "model": LLM_MODEL_ID, + "messages": [{"role": "user", "content": prompt}], + "stream": True + } + + result_dict, runtime_graph = await self.megaservice.schedule(initial_inputs=chat_request_dict) + for node, response in result_dict.items(): + # Here it suppose the last microservice in the megaservice is LLM. + if ( + isinstance(response, StreamingResponse) + and node == list(self.megaservice.services.keys())[-1] + and self.megaservice.services[node].service_type == ServiceType.LLM + ): + return response + last_node = runtime_graph.all_leaves()[-1] + response = result_dict[last_node]["text"] + choices = [] + usage = UsageInfo() + choices.append( + ChatCompletionResponseChoice( + index=0, + message=ChatMessage(role="assistant", content=response), + finish_reason="stop", + ) + ) + return ChatCompletionResponse(model="translation", choices=choices, usage=usage) + + def start(self): + self.service = MicroService( + self.__class__.__name__, + service_role=ServiceRoleType.MEGASERVICE, + host=self.host, + port=self.port, + endpoint=self.endpoint, + input_datatype=ChatCompletionRequest, + output_datatype=ChatCompletionResponse, + ) + self.service.add_route(self.endpoint, self.handle_request, methods=["POST"]) + self.service.start() + + +if __name__ == "__main__": + translation = TranslationService(port=MEGA_SERVICE_PORT) + translation.add_remote_service() + translation.start() diff --git a/PolyLingua/ui/.gitignore b/PolyLingua/ui/.gitignore new file mode 100644 index 0000000000..45c1abce86 --- /dev/null +++ b/PolyLingua/ui/.gitignore @@ -0,0 +1,36 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local +.env + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/PolyLingua/ui/Dockerfile b/PolyLingua/ui/Dockerfile new file mode 100644 index 0000000000..a8e4fc730b --- /dev/null +++ b/PolyLingua/ui/Dockerfile @@ -0,0 +1,59 @@ +# Copyright (C) 2024 +# SPDX-License-Identifier: Apache-2.0 + +FROM node:18-alpine AS base + +# Install dependencies +FROM base AS deps +RUN apk add --no-cache libc6-compat +WORKDIR /app + +COPY package.json package-lock.json* ./ +RUN npm ci + +# Build the application +FROM base AS builder +WORKDIR /app + +# Accept build argument +ARG BACKEND_SERVICE_ENDPOINT +ENV BACKEND_SERVICE_ENDPOINT=$BACKEND_SERVICE_ENDPOINT + +COPY --from=deps /app/node_modules ./node_modules +COPY . . + +# Ensure public directory exists +RUN mkdir -p public + +# Set environment for build +ENV NEXT_TELEMETRY_DISABLED 1 + +RUN npm run build + +# Production image +FROM base AS runner +WORKDIR /app + +ENV NODE_ENV production +ENV NEXT_TELEMETRY_DISABLED 1 + +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs + +# Copy build output +COPY --from=builder /app/next.config.js ./ +COPY --from=builder /app/package.json ./ +COPY --from=builder /app/.next ./.next +COPY --from=builder /app/node_modules ./node_modules +COPY --from=builder /app/public ./public + +RUN chown -R nextjs:nodejs /app + +USER nextjs + +EXPOSE 5173 + +ENV PORT 5173 +ENV HOSTNAME "0.0.0.0" + +CMD ["npm", "start"] diff --git a/PolyLingua/ui/README.md b/PolyLingua/ui/README.md new file mode 100644 index 0000000000..d39fc5127e --- /dev/null +++ b/PolyLingua/ui/README.md @@ -0,0 +1,152 @@ +# Translation UI + +A modern, single-page translation interface built with Next.js 14, React, and shadcn/ui components. + +## Features + +- 🌐 Clean and intuitive translation interface +- 🎨 Beautiful UI using shadcn/ui components and Tailwind CSS +- 📱 Fully responsive design +- 🌍 Support for 15 languages (Spanish, French, German, Italian, Portuguese, Russian, Japanese, Korean, Chinese, Arabic, Hindi, Dutch, Polish, Turkish, Swedish) +- ⚡ Real-time character count +- 🔄 Loading states and smooth animations + +## Tech Stack + +- **Framework**: Next.js 14 (App Router) +- **UI Components**: shadcn/ui (Radix UI + Tailwind CSS) +- **Styling**: Tailwind CSS +- **Icons**: Lucide React +- **Language**: TypeScript + +## Getting Started + +### Prerequisites + +- Node.js 18.x or higher +- npm, yarn, or pnpm + +### Installation + +1. Navigate to the ui directory: +```bash +cd ui +``` + +2. Install dependencies: +```bash +npm install +# or +yarn install +# or +pnpm install +``` + +3. Run the development server: +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +``` + +4. Open [http://localhost:3000](http://localhost:3000) in your browser to see the application. + +## Project Structure + +``` +ui/ +├── app/ +│ ├── globals.css # Global styles and Tailwind configuration +│ ├── layout.tsx # Root layout component +│ └── page.tsx # Main page (home) +├── components/ +│ ├── ui/ # shadcn/ui components +│ │ ├── button.tsx +│ │ ├── card.tsx +│ │ ├── label.tsx +│ │ ├── select.tsx +│ │ └── textarea.tsx +│ └── translation-form.tsx # Main translation form component +├── lib/ +│ └── utils.ts # Utility functions +├── package.json +├── tailwind.config.ts +├── tsconfig.json +└── next.config.js +``` + +## Usage + +1. **Enter Text**: Type or paste the text you want to translate in the source text area +2. **Select Language**: Choose your target language from the dropdown menu +3. **Translate**: Click the "Translate" button to see the translation + +## Backend Integration + +Currently, the app uses a mock translation function. To connect to a real translation backend: + +1. Update the `handleTranslate` function in `components/translation-form.tsx`: + +```typescript +const handleTranslate = async () => { + if (!sourceText.trim()) return; + + setIsLoading(true); + + try { + const response = await fetch('/api/translate', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + text: sourceText, + targetLanguage: targetLanguage, + }), + }); + + const data = await response.json(); + setTranslatedText(data.translatedText); + } catch (error) { + console.error("Translation error:", error); + setTranslatedText("Error: Translation failed. Please try again."); + } finally { + setIsLoading(false); + } +}; +``` + +2. Create an API route at `app/api/translate/route.ts` to handle the backend connection. + +## Build for Production + +```bash +npm run build +npm start +``` + +## Customization + +### Adding More Languages + +Edit the `languages` array in `components/translation-form.tsx`: + +```typescript +const languages = [ + { code: "es", name: "Spanish" }, + { code: "fr", name: "French" }, + // Add more languages here +]; +``` + +### Styling + +- Global styles: `app/globals.css` +- Tailwind configuration: `tailwind.config.ts` +- Component-specific styles: Use Tailwind utility classes + +## License + +MIT diff --git a/PolyLingua/ui/app/globals.css b/PolyLingua/ui/app/globals.css new file mode 100644 index 0000000000..01b77aafd6 --- /dev/null +++ b/PolyLingua/ui/app/globals.css @@ -0,0 +1,59 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 222.2 84% 4.9%; + --card: 0 0% 100%; + --card-foreground: 222.2 84% 4.9%; + --popover: 0 0% 100%; + --popover-foreground: 222.2 84% 4.9%; + --primary: 270 70% 55%; + --primary-foreground: 210 40% 98%; + --secondary: 210 40% 96.1%; + --secondary-foreground: 222.2 47.4% 11.2%; + --muted: 210 40% 96.1%; + --muted-foreground: 215.4 16.3% 46.9%; + --accent: 210 40% 96.1%; + --accent-foreground: 222.2 47.4% 11.2%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 210 40% 98%; + --border: 214.3 31.8% 91.4%; + --input: 214.3 31.8% 91.4%; + --ring: 270 70% 55%; + --radius: 0.5rem; + } + + .dark { + --background: 222.2 84% 4.9%; + --foreground: 210 40% 98%; + --card: 222.2 84% 4.9%; + --card-foreground: 210 40% 98%; + --popover: 222.2 84% 4.9%; + --popover-foreground: 210 40% 98%; + --primary: 270 75% 60%; + --primary-foreground: 222.2 47.4% 11.2%; + --secondary: 217.2 32.6% 17.5%; + --secondary-foreground: 210 40% 98%; + --muted: 217.2 32.6% 17.5%; + --muted-foreground: 215 20.2% 65.1%; + --accent: 217.2 32.6% 17.5%; + --accent-foreground: 210 40% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 210 40% 98%; + --border: 217.2 32.6% 17.5%; + --input: 217.2 32.6% 17.5%; + --ring: 270 75% 60%; + } +} + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } +} diff --git a/PolyLingua/ui/app/layout.tsx b/PolyLingua/ui/app/layout.tsx new file mode 100644 index 0000000000..1a838bdc89 --- /dev/null +++ b/PolyLingua/ui/app/layout.tsx @@ -0,0 +1,22 @@ +import type { Metadata } from "next" +import { Inter } from "next/font/google" +import "./globals.css" + +const inter = Inter({ subsets: ["latin"] }) + +export const metadata: Metadata = { + title: "Translation App", + description: "Translate text to multiple languages", +} + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode +}>) { + return ( + + {children} + + ) +} diff --git a/PolyLingua/ui/app/page.tsx b/PolyLingua/ui/app/page.tsx new file mode 100644 index 0000000000..5cb89305a7 --- /dev/null +++ b/PolyLingua/ui/app/page.tsx @@ -0,0 +1,9 @@ +import { TranslationForm } from "@/components/translation-form" + +export default function Home() { + return ( +
+ +
+ ) +} diff --git a/PolyLingua/ui/components.json b/PolyLingua/ui/components.json new file mode 100644 index 0000000000..fa674c93d1 --- /dev/null +++ b/PolyLingua/ui/components.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "default", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "tailwind.config.ts", + "css": "app/globals.css", + "baseColor": "slate", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils" + } +} diff --git a/PolyLingua/ui/components/translation-form.tsx b/PolyLingua/ui/components/translation-form.tsx new file mode 100644 index 0000000000..349af50286 --- /dev/null +++ b/PolyLingua/ui/components/translation-form.tsx @@ -0,0 +1,243 @@ +"use client" + +import * as React from "react" +import { Button } from "@/components/ui/button" +import { Textarea } from "@/components/ui/textarea" +import { Label } from "@/components/ui/label" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" +import { Languages, Loader2 } from "lucide-react" + +const languages = [ + { code: "en", name: "English" }, + { code: "es", name: "Spanish" }, + { code: "fr", name: "French" }, + { code: "de", name: "German" }, + { code: "it", name: "Italian" }, + { code: "pt", name: "Portuguese" }, + { code: "ru", name: "Russian" }, + { code: "ja", name: "Japanese" }, + { code: "ko", name: "Korean" }, + { code: "zh", name: "Chinese (Simplified)" }, + { code: "ar", name: "Arabic" }, + { code: "hi", name: "Hindi" }, + { code: "nl", name: "Dutch" }, + { code: "pl", name: "Polish" }, + { code: "tr", name: "Turkish" }, + { code: "sv", name: "Swedish" }, +] + +export function TranslationForm() { + const [sourceText, setSourceText] = React.useState("") + const [translatedText, setTranslatedText] = React.useState("") + const [targetLanguage, setTargetLanguage] = React.useState("es") + const [isLoading, setIsLoading] = React.useState(false) + + const handleTranslate = async () => { + if (!sourceText.trim()) { + return + } + + setIsLoading(true) + setTranslatedText("") // Clear previous translation + + try { + const selectedLang = languages.find(lang => lang.code === targetLanguage) + const backendUrl = process.env.NEXT_PUBLIC_BACKEND_URL || "http://localhost:8888" + + const response = await fetch(`${backendUrl}/v1/translation`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + language_from: "auto", + language_to: selectedLang?.name || "Spanish", + source_language: sourceText, + }), + }) + + if (!response.ok) { + throw new Error(`Translation failed: ${response.statusText}`) + } + + // Check if response is streaming (SSE) + const contentType = response.headers.get("content-type") + if (contentType?.includes("text/event-stream")) { + // Handle Server-Sent Events streaming + const reader = response.body?.getReader() + const decoder = new TextDecoder() + let accumulatedText = "" + + if (!reader) { + throw new Error("Response body is not readable") + } + + while (true) { + const { done, value } = await reader.read() + if (done) break + + const chunk = decoder.decode(value, { stream: true }) + const lines = chunk.split("\n") + + for (const line of lines) { + if (line.startsWith("data: ")) { + const data = line.slice(6) // Remove "data: " prefix + + if (data === "[DONE]") { + continue + } + + try { + const parsed = JSON.parse(data) + // Extract content from chat completion streaming format + const text = parsed.choices?.[0]?.delta?.content || "" + if (text) { + accumulatedText += text + setTranslatedText(accumulatedText) + } + } catch (e) { + // Skip malformed JSON chunks + console.warn("Failed to parse chunk:", e) + } + } + } + } + } else { + // Handle regular JSON response (fallback) + const data = await response.json() + const translatedContent = data.choices?.[0]?.message?.content || data.text || "Translation not available" + setTranslatedText(translatedContent) + } + } catch (error) { + console.error("Translation error:", error) + setTranslatedText(`Error: Translation failed. Please try again.\n\nDetails: ${error instanceof Error ? error.message : "Unknown error"}`) + } finally { + setIsLoading(false) + } + } + + const characterCount = sourceText.length + + return ( +
+
+
+ +

Translation Service

+
+

+ Translate your text to multiple languages +

+
+ +
+ Bud Ecosystem Logo + OPEA Logo + NetApp Logo +
+ +
+ + +
+
+ Input Text + + Enter the text to translate + +
+
+
+ +
+ +
+
+
+ +
+