From ba65fba7f336e64a7013873380ddd2d2aa18559e Mon Sep 17 00:00:00 2001 From: Blaise Alako Date: Tue, 1 Apr 2025 23:58:02 +0100 Subject: [PATCH] App Quick guide notebook --- .../ai_code_converter/.dockerignore | 49 - .../ai_code_converter/.env.example | 9 - .../.github/workflows/README.md | 51 - .../.github/workflows/deploy-production.yml | 48 - .../.github/workflows/deploy-staging.yml | 44 - .../.github/workflows/docker-build.yml | 65 - .../.github/workflows/python-test.yml | 40 - .../ai_code_converter/.gitignore | 66 - .../ai_code_converter/Dockerfile | 287 ----- .../ai_code_converter/LICENSE | 21 - .../ai_code_converter/Makefile | 39 - .../ai_code_converter/README.md | 191 --- .../ai_code_converter/codeXchange.ipynb | 107 ++ .../ai_code_converter/docker-compose.yml | 23 - .../ai_code_converter/docs/README.md | 12 - .../docs/architecture_diagram.md | 297 ----- .../docs/before_any_update.md | 9 - .../ai_code_converter/docs/ci_cd_pipeline.md | 86 -- .../ai_code_converter/docs/configuration.md | 82 -- .../ai_code_converter/docs/contributing.md | 89 -- .../ai_code_converter/docs/development.md | 180 --- .../ai_code_converter/docs/languages.md | 68 -- .../docs/project_structure.md | 108 -- .../ai_code_converter/gradio_theme.py | 332 ----- .../ai_code_converter/pytest.ini | 6 - .../ai_code_converter/requirements.txt | 81 -- .../ai_code_converter/run-docker.sh | 318 ----- .../ai_code_converter/run.py | 44 - .../screenshots/codeXchange_1.png | Bin 0 -> 66426 bytes .../screenshots/codeXchange_2.png | Bin 0 -> 26818 bytes .../screenshots/codeXchange_3.png | Bin 0 -> 186001 bytes .../screenshots/codeXchange_3_1.png | Bin 0 -> 20447 bytes .../screenshots/codeXchange_4.png | Bin 0 -> 43026 bytes .../ai_code_converter/setup.py | 19 - .../ai_code_converter/src/__init__.py | 1 - .../src/ai_code_converter/__init__.py | 3 - .../src/ai_code_converter/app.py | 1083 ----------------- .../src/ai_code_converter/config.py | 443 ------- .../src/ai_code_converter/core/__init__.py | 1 - .../ai_code_converter/core/code_execution.py | 631 ---------- .../src/ai_code_converter/core/file_utils.py | 250 ---- .../core/language_detection.py | 793 ------------ .../src/ai_code_converter/main.py | 31 - .../src/ai_code_converter/models/__init__.py | 1 - .../ai_code_converter/models/ai_streaming.py | 301 ----- .../ai_code_converter/project_structure.txt | 48 - .../src/ai_code_converter/template.j2 | 287 ----- .../src/ai_code_converter/template.j2.old | 78 -- .../src/ai_code_converter/utils/__init__.py | 1 - .../src/ai_code_converter/utils/logger.py | 132 -- .../ai_code_converter/tests/test_basic.py | 29 - .../tests/test_csharp_detection.py | 101 -- .../tests/test_kotlin_detection.py | 189 --- .../tests/test_lua_detection.py | 164 --- .../tests/test_perl_detection.py | 174 --- .../tests/test_php_detection.py | 184 --- .../tests/test_r_detection.py | 118 -- .../tests/test_ruby_detection.py | 57 - .../tests/test_rust_detection.py | 89 -- .../tests/test_sql_detection.py | 209 ---- .../tests/test_swift_detection.py | 96 -- .../tests/test_typescript_detection.py | 148 --- .../ai_code_converter/themes/gradio_theme.py | 332 ----- .../themes/gradio_theme_light.py | 35 - 64 files changed, 107 insertions(+), 8673 deletions(-) delete mode 100644 week4/community-contributions/ai_code_converter/.dockerignore delete mode 100644 week4/community-contributions/ai_code_converter/.env.example delete mode 100644 week4/community-contributions/ai_code_converter/.github/workflows/README.md delete mode 100644 week4/community-contributions/ai_code_converter/.github/workflows/deploy-production.yml delete mode 100644 week4/community-contributions/ai_code_converter/.github/workflows/deploy-staging.yml delete mode 100644 week4/community-contributions/ai_code_converter/.github/workflows/docker-build.yml delete mode 100644 week4/community-contributions/ai_code_converter/.github/workflows/python-test.yml delete mode 100644 week4/community-contributions/ai_code_converter/.gitignore delete mode 100644 week4/community-contributions/ai_code_converter/Dockerfile delete mode 100644 week4/community-contributions/ai_code_converter/LICENSE delete mode 100644 week4/community-contributions/ai_code_converter/Makefile delete mode 100644 week4/community-contributions/ai_code_converter/README.md create mode 100644 week4/community-contributions/ai_code_converter/codeXchange.ipynb delete mode 100644 week4/community-contributions/ai_code_converter/docker-compose.yml delete mode 100644 week4/community-contributions/ai_code_converter/docs/README.md delete mode 100644 week4/community-contributions/ai_code_converter/docs/architecture_diagram.md delete mode 100644 week4/community-contributions/ai_code_converter/docs/before_any_update.md delete mode 100644 week4/community-contributions/ai_code_converter/docs/ci_cd_pipeline.md delete mode 100644 week4/community-contributions/ai_code_converter/docs/configuration.md delete mode 100644 week4/community-contributions/ai_code_converter/docs/contributing.md delete mode 100644 week4/community-contributions/ai_code_converter/docs/development.md delete mode 100644 week4/community-contributions/ai_code_converter/docs/languages.md delete mode 100644 week4/community-contributions/ai_code_converter/docs/project_structure.md delete mode 100644 week4/community-contributions/ai_code_converter/gradio_theme.py delete mode 100644 week4/community-contributions/ai_code_converter/pytest.ini delete mode 100644 week4/community-contributions/ai_code_converter/requirements.txt delete mode 100755 week4/community-contributions/ai_code_converter/run-docker.sh delete mode 100644 week4/community-contributions/ai_code_converter/run.py create mode 100644 week4/community-contributions/ai_code_converter/screenshots/codeXchange_1.png create mode 100644 week4/community-contributions/ai_code_converter/screenshots/codeXchange_2.png create mode 100644 week4/community-contributions/ai_code_converter/screenshots/codeXchange_3.png create mode 100644 week4/community-contributions/ai_code_converter/screenshots/codeXchange_3_1.png create mode 100644 week4/community-contributions/ai_code_converter/screenshots/codeXchange_4.png delete mode 100644 week4/community-contributions/ai_code_converter/setup.py delete mode 100644 week4/community-contributions/ai_code_converter/src/__init__.py delete mode 100644 week4/community-contributions/ai_code_converter/src/ai_code_converter/__init__.py delete mode 100644 week4/community-contributions/ai_code_converter/src/ai_code_converter/app.py delete mode 100644 week4/community-contributions/ai_code_converter/src/ai_code_converter/config.py delete mode 100644 week4/community-contributions/ai_code_converter/src/ai_code_converter/core/__init__.py delete mode 100644 week4/community-contributions/ai_code_converter/src/ai_code_converter/core/code_execution.py delete mode 100644 week4/community-contributions/ai_code_converter/src/ai_code_converter/core/file_utils.py delete mode 100644 week4/community-contributions/ai_code_converter/src/ai_code_converter/core/language_detection.py delete mode 100644 week4/community-contributions/ai_code_converter/src/ai_code_converter/main.py delete mode 100644 week4/community-contributions/ai_code_converter/src/ai_code_converter/models/__init__.py delete mode 100644 week4/community-contributions/ai_code_converter/src/ai_code_converter/models/ai_streaming.py delete mode 100644 week4/community-contributions/ai_code_converter/src/ai_code_converter/project_structure.txt delete mode 100644 week4/community-contributions/ai_code_converter/src/ai_code_converter/template.j2 delete mode 100644 week4/community-contributions/ai_code_converter/src/ai_code_converter/template.j2.old delete mode 100644 week4/community-contributions/ai_code_converter/src/ai_code_converter/utils/__init__.py delete mode 100644 week4/community-contributions/ai_code_converter/src/ai_code_converter/utils/logger.py delete mode 100644 week4/community-contributions/ai_code_converter/tests/test_basic.py delete mode 100644 week4/community-contributions/ai_code_converter/tests/test_csharp_detection.py delete mode 100644 week4/community-contributions/ai_code_converter/tests/test_kotlin_detection.py delete mode 100644 week4/community-contributions/ai_code_converter/tests/test_lua_detection.py delete mode 100644 week4/community-contributions/ai_code_converter/tests/test_perl_detection.py delete mode 100644 week4/community-contributions/ai_code_converter/tests/test_php_detection.py delete mode 100644 week4/community-contributions/ai_code_converter/tests/test_r_detection.py delete mode 100644 week4/community-contributions/ai_code_converter/tests/test_ruby_detection.py delete mode 100644 week4/community-contributions/ai_code_converter/tests/test_rust_detection.py delete mode 100644 week4/community-contributions/ai_code_converter/tests/test_sql_detection.py delete mode 100644 week4/community-contributions/ai_code_converter/tests/test_swift_detection.py delete mode 100644 week4/community-contributions/ai_code_converter/tests/test_typescript_detection.py delete mode 100644 week4/community-contributions/ai_code_converter/themes/gradio_theme.py delete mode 100644 week4/community-contributions/ai_code_converter/themes/gradio_theme_light.py diff --git a/week4/community-contributions/ai_code_converter/.dockerignore b/week4/community-contributions/ai_code_converter/.dockerignore deleted file mode 100644 index a0fc3e2..0000000 --- a/week4/community-contributions/ai_code_converter/.dockerignore +++ /dev/null @@ -1,49 +0,0 @@ -# Version control -.git -.gitignore - -# Python -__pycache__/ -*.py[cod] -*$py.class -*.so -.Python -.env -.venv -env/ -venv/ -ENV/ -*.egg-info/ -dist/ -build/ - -# Logs and databases -*.log -logs/ -*.sqlite - -# Downloads and temporary files -downloads/ -tmp/ -temp/ - -# IDE specific files -.idea/ -.vscode/ -*.swp -*.swo - -# Docker -Dockerfile -.dockerignore - -# Documentation -docs/ -*.md -LICENSE - -# Test files -tests/ -.pytest_cache/ -.coverage -htmlcov/ \ No newline at end of file diff --git a/week4/community-contributions/ai_code_converter/.env.example b/week4/community-contributions/ai_code_converter/.env.example deleted file mode 100644 index b183e19..0000000 --- a/week4/community-contributions/ai_code_converter/.env.example +++ /dev/null @@ -1,9 +0,0 @@ -# API Keys Configuration -OPENAI_API_KEY=your-openai-key -ANTHROPIC_API_KEY=your-anthropic-key -GOOGLE_API_KEY=your-google-key -DEEPSEEK_API_KEY=your-deepseek-key -GROQ_API_KEY=your-groq-key - -# Server Configuration -PORT=7860 # Change this if port 7860 is already in use \ No newline at end of file diff --git a/week4/community-contributions/ai_code_converter/.github/workflows/README.md b/week4/community-contributions/ai_code_converter/.github/workflows/README.md deleted file mode 100644 index ca65c7d..0000000 --- a/week4/community-contributions/ai_code_converter/.github/workflows/README.md +++ /dev/null @@ -1,51 +0,0 @@ -# CodeXchange AI CI/CD Workflows - -This directory contains GitHub Actions workflows for continuous integration and deployment of the CodeXchange AI application. - -## Workflow Overview - -1. **python-test.yml**: Runs Python tests, linting, and code coverage -2. **docker-build.yml**: Builds and validates Docker images with security scanning -3. **deploy-staging.yml**: Deploys to staging environment when changes are pushed to develop branch -4. **deploy-production.yml**: Deploys to production environment when a new release is published - -## Required GitHub Secrets - -To use these workflows, you need to set up the following secrets in your GitHub repository: - -1. Go to your GitHub repository -2. Navigate to Settings → Secrets and Variables → Actions -3. Add the following secrets: - -| Secret Name | Description | -|-------------|-------------| -| `DOCKERHUB_USERNAME` | Your Docker Hub username | -| `DOCKERHUB_TOKEN` | A personal access token from Docker Hub (not your password) | - -## Getting a Docker Hub Access Token - -1. Log in to [Docker Hub](https://hub.docker.com) -2. Go to Account Settings → Security -3. Create a new access token with appropriate permissions -4. Copy the token immediately (it's only shown once) -5. Add it as a GitHub secret - -## Customizing Deployment - -The deployment workflows currently include placeholder comments where you would add your specific deployment commands. Update these sections based on your hosting environment: - -- For VPS hosting: Add SSH-based deployment steps -- For cloud providers: Add their specific deployment APIs -- For Kubernetes: Add kubectl commands or Helm chart updates - -## Testing GitHub Actions Locally - -You can test GitHub Actions locally using [act](https://github.com/nektos/act): - -```bash -# Install act -brew install act - -# Run a specific workflow -act -j test -W .github/workflows/python-test.yml -``` diff --git a/week4/community-contributions/ai_code_converter/.github/workflows/deploy-production.yml b/week4/community-contributions/ai_code_converter/.github/workflows/deploy-production.yml deleted file mode 100644 index a25d64a..0000000 --- a/week4/community-contributions/ai_code_converter/.github/workflows/deploy-production.yml +++ /dev/null @@ -1,48 +0,0 @@ -name: Deploy to Production - -on: - release: - types: [published] - -jobs: - build-and-deploy: - runs-on: ubuntu-latest - environment: production - steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - - name: Login to DockerHub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Extract metadata - id: meta - uses: docker/metadata-action@v4 - with: - images: ${{ secrets.DOCKERHUB_USERNAME }}/codexchangeai - tags: | - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - latest - - - name: Build and push Docker image - uses: docker/build-push-action@v4 - with: - context: . - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max - - # Production deployment steps would be added here - # This could involve: - # 1. Updating a Kubernetes deployment - # 2. SSH into production server and updating containers - # 3. Using cloud provider deployment APIs diff --git a/week4/community-contributions/ai_code_converter/.github/workflows/deploy-staging.yml b/week4/community-contributions/ai_code_converter/.github/workflows/deploy-staging.yml deleted file mode 100644 index 6e5d795..0000000 --- a/week4/community-contributions/ai_code_converter/.github/workflows/deploy-staging.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: Deploy to Staging - -on: - push: - branches: [ develop ] - -jobs: - build-and-deploy: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - - name: Login to DockerHub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Extract metadata - id: meta - uses: docker/metadata-action@v4 - with: - images: ${{ secrets.DOCKERHUB_USERNAME }}/codexchangeai - tags: | - type=ref,event=branch - type=sha,format=short - - - name: Build and push Docker image - uses: docker/build-push-action@v4 - with: - context: . - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max - - # Here you would typically include steps to deploy to your staging environment - # For example, using SSH to connect to your server and pull the new image - # Or using a cloud provider's deployment API diff --git a/week4/community-contributions/ai_code_converter/.github/workflows/docker-build.yml b/week4/community-contributions/ai_code_converter/.github/workflows/docker-build.yml deleted file mode 100644 index 2811ff7..0000000 --- a/week4/community-contributions/ai_code_converter/.github/workflows/docker-build.yml +++ /dev/null @@ -1,65 +0,0 @@ -name: Docker Build - -on: - push: - branches: [ main, develop ] - pull_request: - branches: [ main, develop ] - -permissions: - contents: read - security-events: write # Required for uploading SARIF results - -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - - name: Extract metadata - id: meta - uses: docker/metadata-action@v4 - with: - images: ${{ secrets.DOCKERHUB_USERNAME }}/codexchangeai - tags: | - type=ref,event=branch - type=sha,format=short - type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} - - - name: Login to Docker Hub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Build and push Docker image - uses: docker/build-push-action@v4 - with: - context: . - push: true # Always push the image - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max - - - name: Run Trivy vulnerability scanner - id: trivy - uses: aquasecurity/trivy-action@master - with: - image-ref: "${{ secrets.DOCKERHUB_USERNAME }}/codexchangeai:${{ github.ref_name }}" - format: 'table' - exit-code: '0' # Temporarily allow workflow to pass while addressing vulnerabilities - ignore-unfixed: true - vuln-type: 'os,library' - severity: 'CRITICAL,HIGH' - - # Output clear warning about vulnerabilities - - name: Check for vulnerabilities - run: | - echo "::warning ::⚠️ Security vulnerabilities found! Check the Security tab for details" - echo "::warning ::This pipeline is temporarily set to succeed despite vulnerabilities" - echo "::warning ::Please address these issues as soon as possible" \ No newline at end of file diff --git a/week4/community-contributions/ai_code_converter/.github/workflows/python-test.yml b/week4/community-contributions/ai_code_converter/.github/workflows/python-test.yml deleted file mode 100644 index dca92ca..0000000 --- a/week4/community-contributions/ai_code_converter/.github/workflows/python-test.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: Python Tests - -on: - push: - branches: [ main, develop ] - pull_request: - branches: [ main, develop ] - -jobs: - test: - runs-on: ubuntu-latest - strategy: - matrix: - python-version: ['3.10', '3.11'] - - steps: - - uses: actions/checkout@v4 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - cache: 'pip' - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install flake8 pytest pytest-cov - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - - - name: Lint with flake8 - run: | - # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - # exit-zero treats all errors as warnings - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=120 --statistics - - - name: Test with pytest - run: | - pytest --cov=src/ai_code_converter tests/ diff --git a/week4/community-contributions/ai_code_converter/.gitignore b/week4/community-contributions/ai_code_converter/.gitignore deleted file mode 100644 index 757f937..0000000 --- a/week4/community-contributions/ai_code_converter/.gitignore +++ /dev/null @@ -1,66 +0,0 @@ -# Environment variables -.env - -# Windsurf -.windsurfrules - -action_plan_save.md -action_plan.md - -ai_converter_v2.code-workspace - -# Python -__pycache__/ -*.py[cod] -*$py.class -*.so -.Python -env/ -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -*.egg-info/ -.installed.cfg -*.egg - -# Logs -logs/ -*.log - -# IDE specific files -.idea/ -.vscode/ -*.swp -*.swo - -# OS specific files -.DS_Store -Thumbs.db - -# Virtual Environment -venv/ -.venv/ -ENV/ - -# Prometheus -/tmp/prometheus-multiproc/ - -# Local development -.env.local -.env.development.local -.env.test.local -.env.production.local - -# Temporary files -tmp/ -temp/ -*.tmp diff --git a/week4/community-contributions/ai_code_converter/Dockerfile b/week4/community-contributions/ai_code_converter/Dockerfile deleted file mode 100644 index 1cd8b6a..0000000 --- a/week4/community-contributions/ai_code_converter/Dockerfile +++ /dev/null @@ -1,287 +0,0 @@ -# Stage 1: Base development image -FROM ubuntu:22.04 AS builder - -# Add metadata about build environment -LABEL org.opencontainers.image.title="CodeXchange AI Builder" -LABEL org.opencontainers.image.description="Multi-platform build environment for CodeXchange AI" - -# Prevent interactive prompts during package installation -ENV DEBIAN_FRONTEND=noninteractive - -# Install essential build tools and compilers -RUN apt-get update && apt-get install -y --no-install-recommends \ - python3.10 \ - python3-pip \ - python3.10-venv \ - build-essential \ - gcc \ - g++ \ - openjdk-17-jdk \ - curl \ - ca-certificates \ - git \ - nodejs \ - npm \ - perl \ - lua5.3 \ - php \ - r-base \ - ruby \ - rustc \ - cargo \ - mono-complete \ - mono-devel \ - mono-mcs \ - sqlite3 \ - unzip \ - && rm -rf /var/lib/apt/lists/* - -# Install TypeScript -RUN npm install -g typescript - -# Install Swift -RUN apt-get update && apt-get install -y --no-install-recommends \ - binutils \ - libc6-dev \ - libcurl4 \ - libedit2 \ - libgcc-9-dev \ - libpython3.10 \ - libsqlite3-0 \ - libstdc++-9-dev \ - libxml2 \ - libz3-dev \ - pkg-config \ - tzdata \ - zlib1g-dev \ - && rm -rf /var/lib/apt/lists/* - -RUN arch=$(uname -m) && \ - if [ "$arch" = "x86_64" ]; then \ - SWIFT_URL="https://download.swift.org/swift-5.9.2-release/ubuntu2204/swift-5.9.2-RELEASE/swift-5.9.2-RELEASE-ubuntu22.04.tar.gz"; \ - elif [ "$arch" = "aarch64" ] || [ "$arch" = "arm64" ]; then \ - SWIFT_URL="https://download.swift.org/swift-5.9.2-release/ubuntu2204-aarch64/swift-5.9.2-RELEASE/swift-5.9.2-RELEASE-ubuntu22.04-aarch64.tar.gz"; \ - else \ - echo "Unsupported architecture for Swift: $arch"; \ - exit 1; \ - fi && \ - curl -fL $SWIFT_URL | tar xz -C /opt && \ - ln -s /opt/swift-5.9.2-RELEASE-ubuntu22.04*/usr/bin/swift /usr/local/bin/swift - -# Install Kotlin -RUN KOTLIN_VERSION=1.9.22 && \ - cd /tmp && \ - curl -LO "https://github.com/JetBrains/kotlin/releases/download/v${KOTLIN_VERSION}/kotlin-compiler-${KOTLIN_VERSION}.zip" && \ - unzip "kotlin-compiler-${KOTLIN_VERSION}.zip" -d /opt && \ - rm "kotlin-compiler-${KOTLIN_VERSION}.zip" && \ - ln -s "/opt/kotlinc/bin/kotlin" /usr/local/bin/kotlin && \ - ln -s "/opt/kotlinc/bin/kotlinc" /usr/local/bin/kotlinc - -# Install Julia based on architecture (with Windows WSL2 compatibility) -RUN arch=$(uname -m) && \ - echo "Detected architecture: $arch" && \ - if [ "$arch" = "aarch64" ] || [ "$arch" = "arm64" ]; then \ - echo "Installing ARM64 version of Julia" && \ - curl -fL https://julialang-s3.julialang.org/bin/linux/aarch64/1.9/julia-1.9.3-linux-aarch64.tar.gz | tar xz -C /opt && \ - ln -s /opt/julia-1.9.3/bin/julia /usr/local/bin/julia; \ - elif [ "$arch" = "x86_64" ] || [ "$arch" = "amd64" ]; then \ - echo "Installing x86_64 version of Julia" && \ - curl -fL https://julialang-s3.julialang.org/bin/linux/x64/1.9/julia-1.9.3-linux-x86_64.tar.gz | tar xz -C /opt && \ - ln -s /opt/julia-1.9.3/bin/julia /usr/local/bin/julia; \ - else \ - echo "WARNING: Unknown architecture $arch, defaulting to x86_64" && \ - curl -fL https://julialang-s3.julialang.org/bin/linux/x64/1.9/julia-1.9.3-linux-x86_64.tar.gz | tar xz -C /opt && \ - ln -s /opt/julia-1.9.3/bin/julia /usr/local/bin/julia; \ - fi - -# Install Go based on architecture (with Windows WSL2 compatibility) -RUN arch=$(uname -m) && \ - echo "Detected architecture for Go: $arch" && \ - if [ "$arch" = "aarch64" ] || [ "$arch" = "arm64" ]; then \ - echo "Installing ARM64 version of Go" && \ - curl -L https://go.dev/dl/go1.21.6.linux-arm64.tar.gz | tar -C /usr/local -xzf -; \ - elif [ "$arch" = "x86_64" ] || [ "$arch" = "amd64" ]; then \ - echo "Installing x86_64 version of Go" && \ - curl -L https://go.dev/dl/go1.21.6.linux-amd64.tar.gz | tar -C /usr/local -xzf -; \ - else \ - echo "WARNING: Unknown architecture $arch for Go, defaulting to x86_64" && \ - curl -L https://go.dev/dl/go1.21.6.linux-amd64.tar.gz | tar -C /usr/local -xzf -; \ - fi -ENV PATH="/usr/local/go/bin:${PATH}" -ENV GOPATH="/go" -ENV PATH="${GOPATH}/bin:${PATH}" - -# Create app user -RUN useradd -m -s /bin/bash app -WORKDIR /app - -# Copy project files -COPY --chown=app:app . . - -# Create and activate virtual environment -RUN python3 -m venv /app/.venv -ENV PATH="/app/.venv/bin:$PATH" - -# Install Python dependencies -RUN pip install --no-cache-dir -r requirements.txt - -# Stage 2: Runtime image -FROM ubuntu:22.04 - -# Add metadata about runtime environment -LABEL org.opencontainers.image.title="AI CodeXchange" -LABEL org.opencontainers.image.description="Multi-platform AI CodeXchange application" -LABEL org.opencontainers.image.version="1.0" - -# Create platform-specific label at build time -RUN echo "Building on $(uname -s) $(uname -m) architecture" > /platform-info.txt -LABEL org.opencontainers.image.platform="$(cat /platform-info.txt)" - -# Install runtime dependencies -ENV DEBIAN_FRONTEND=noninteractive -RUN apt-get update && apt-get install -y --no-install-recommends \ - python3.10 \ - python3-pip \ - gcc \ - g++ \ - openjdk-17-jdk \ - curl \ - nodejs \ - npm \ - perl \ - lua5.3 \ - php \ - r-base \ - ruby \ - rustc \ - cargo \ - mono-complete \ - mono-devel \ - mono-mcs \ - sqlite3 \ - unzip \ - && rm -rf /var/lib/apt/lists/* - -# Install TypeScript -RUN npm install -g typescript - -# Install Swift -RUN apt-get update && apt-get install -y --no-install-recommends \ - binutils \ - libc6-dev \ - libcurl4 \ - libedit2 \ - libgcc-9-dev \ - libpython3.10 \ - libsqlite3-0 \ - libstdc++-9-dev \ - libxml2 \ - libz3-dev \ - pkg-config \ - tzdata \ - zlib1g-dev \ - && rm -rf /var/lib/apt/lists/* - -RUN arch=$(uname -m) && \ - if [ "$arch" = "x86_64" ]; then \ - SWIFT_URL="https://download.swift.org/swift-5.9.2-release/ubuntu2204/swift-5.9.2-RELEASE/swift-5.9.2-RELEASE-ubuntu22.04.tar.gz"; \ - elif [ "$arch" = "aarch64" ] || [ "$arch" = "arm64" ]; then \ - SWIFT_URL="https://download.swift.org/swift-5.9.2-release/ubuntu2204-aarch64/swift-5.9.2-RELEASE/swift-5.9.2-RELEASE-ubuntu22.04-aarch64.tar.gz"; \ - else \ - echo "Unsupported architecture for Swift: $arch"; \ - exit 1; \ - fi && \ - curl -fL $SWIFT_URL | tar xz -C /opt && \ - ln -s /opt/swift-5.9.2-RELEASE-ubuntu22.04*/usr/bin/swift /usr/local/bin/swift - -# Install Kotlin -RUN KOTLIN_VERSION=1.9.22 && \ - cd /tmp && \ - curl -LO "https://github.com/JetBrains/kotlin/releases/download/v${KOTLIN_VERSION}/kotlin-compiler-${KOTLIN_VERSION}.zip" && \ - unzip "kotlin-compiler-${KOTLIN_VERSION}.zip" -d /opt && \ - rm "kotlin-compiler-${KOTLIN_VERSION}.zip" && \ - ln -s "/opt/kotlinc/bin/kotlin" /usr/local/bin/kotlin && \ - ln -s "/opt/kotlinc/bin/kotlinc" /usr/local/bin/kotlinc - -# Install Julia based on architecture (with Windows WSL2 compatibility) -RUN arch=$(uname -m) && \ - echo "Detected architecture: $arch" && \ - if [ "$arch" = "aarch64" ] || [ "$arch" = "arm64" ]; then \ - echo "Installing ARM64 version of Julia" && \ - curl -fL https://julialang-s3.julialang.org/bin/linux/aarch64/1.9/julia-1.9.3-linux-aarch64.tar.gz | tar xz -C /opt && \ - ln -s /opt/julia-1.9.3/bin/julia /usr/local/bin/julia; \ - elif [ "$arch" = "x86_64" ] || [ "$arch" = "amd64" ]; then \ - echo "Installing x86_64 version of Julia" && \ - curl -fL https://julialang-s3.julialang.org/bin/linux/x64/1.9/julia-1.9.3-linux-x86_64.tar.gz | tar xz -C /opt && \ - ln -s /opt/julia-1.9.3/bin/julia /usr/local/bin/julia; \ - else \ - echo "WARNING: Unknown architecture $arch, defaulting to x86_64" && \ - curl -fL https://julialang-s3.julialang.org/bin/linux/x64/1.9/julia-1.9.3-linux-x86_64.tar.gz | tar xz -C /opt && \ - ln -s /opt/julia-1.9.3/bin/julia /usr/local/bin/julia; \ - fi - -# Install Go runtime -COPY --from=builder /usr/local/go /usr/local/go -ENV PATH="/usr/local/go/bin:${PATH}" -ENV GOPATH="/go" -ENV PATH="${GOPATH}/bin:${PATH}" - -# Create app user -RUN useradd -m -s /bin/bash app -WORKDIR /app - -# Copy virtual environment and application files from builder -COPY --from=builder --chown=app:app /app/.venv /app/.venv -COPY --from=builder --chown=app:app /app /app - -# Set environment variables -ENV PATH="/app/.venv/bin:$PATH" \ - PYTHONPATH="/app/src" \ - PYTHONUNBUFFERED=1 \ - GRADIO_SERVER_NAME=0.0.0.0 \ - GRADIO_SERVER_PORT=7860 - -# Create necessary directories with correct permissions -RUN mkdir -p /app/logs /app/downloads \ - && chown -R app:app /app/logs /app/downloads - -# Verify installations with comprehensive platform information -RUN echo "======= PLATFORM & LANGUAGE VERIFICATION =======" && \ - echo "OS: $(uname -s)" && \ - echo "Architecture: $(uname -m)" && \ - echo "Kernel: $(uname -r)" && \ - echo "Host: $(uname -n)" && \ - echo "\n=== Language Installations ===" && \ - echo "Node.js: $(node --version 2>/dev/null || echo 'NOT VERIFIED')" && \ - echo "TypeScript: $(tsc --version 2>/dev/null || echo 'NOT VERIFIED')" && \ - echo "Java: $(java -version 2>&1 | head -n 1 || echo 'NOT VERIFIED')" && \ - echo "Julia: $(julia --version 2>/dev/null || echo 'NOT VERIFIED')" && \ - echo "Go: $(go version 2>/dev/null || echo 'NOT VERIFIED')" && \ - echo "Python: $(python3 --version 2>/dev/null || echo 'NOT VERIFIED')" && \ - echo "Perl: $(perl -v 2>/dev/null | head -n 2 || echo 'NOT VERIFIED')" && \ - echo "Lua: $(lua5.3 -v 2>/dev/null || echo 'NOT VERIFIED')" && \ - echo "PHP: $(php --version 2>/dev/null | head -n 1 || echo 'NOT VERIFIED')" && \ - echo "R: $(R --version 2>/dev/null | head -n 1 || echo 'NOT VERIFIED')" && \ - echo "Ruby: $(ruby --version 2>/dev/null || echo 'NOT VERIFIED')" && \ - echo "Rust: $(rustc --version 2>/dev/null || echo 'NOT VERIFIED')" && \ - echo "C#/Mono: $(mono-csc --version 2>/dev/null || echo 'NOT VERIFIED')" && \ - echo "SQLite: $(sqlite3 --version 2>/dev/null || echo 'NOT VERIFIED')" && \ - echo "Kotlin: $(kotlinc -version 2>&1 || echo 'NOT VERIFIED')" && \ - echo "C++: $(g++ --version 2>/dev/null | head -n 1 || echo 'NOT VERIFIED')" && \ - echo "\n=== Environment Variables ===" && \ - echo "PATH: $PATH" && \ - echo "======= VERIFICATION COMPLETE =======" - -# Switch to non-root user -USER app - -# Expose port -EXPOSE 7860 - -# Health check -HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \ - CMD curl -f http://localhost:7860/healthz || exit 1 - -# Set entrypoint and default command -ENTRYPOINT ["python3"] -CMD ["run.py"] \ No newline at end of file diff --git a/week4/community-contributions/ai_code_converter/LICENSE b/week4/community-contributions/ai_code_converter/LICENSE deleted file mode 100644 index 1cbe5ae..0000000 --- a/week4/community-contributions/ai_code_converter/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2025 Blaise Alako - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/week4/community-contributions/ai_code_converter/Makefile b/week4/community-contributions/ai_code_converter/Makefile deleted file mode 100644 index 6842a84..0000000 --- a/week4/community-contributions/ai_code_converter/Makefile +++ /dev/null @@ -1,39 +0,0 @@ -.PHONY: install run dev docker-build docker-run test clean - -# Install project dependencies -install: - pip install -r requirements.txt - -# Run the application -run: - python run.py - -# Run with development settings (enables hot-reloading) -dev: - PYTHON_ENV=development python run.py - -# Build Docker container -docker-build: - docker build -t ai-code-converter . - -# Run Docker container -docker-run: - docker run -p 7860:7860 --env-file .env ai-code-converter - -# Run tests -test: - python -m pytest tests/ - -# Clean Python cache and build artifacts -clean: - find . -type d -name "__pycache__" -exec rm -rf {} + - find . -type f -name "*.pyc" -delete - find . -type f -name "*.pyo" -delete - find . -type f -name "*.pyd" -delete - find . -type d -name ".pytest_cache" -exec rm -rf {} + - find . -type d -name "build" -exec rm -rf {} + - find . -type d -name "dist" -exec rm -rf {} + - -# Generate documentation diagrams -docs-diagrams: - cd docs && python -c "import mermaid_generator; mermaid_generator.generate_diagrams()" \ No newline at end of file diff --git a/week4/community-contributions/ai_code_converter/README.md b/week4/community-contributions/ai_code_converter/README.md deleted file mode 100644 index 4a8a26d..0000000 --- a/week4/community-contributions/ai_code_converter/README.md +++ /dev/null @@ -1,191 +0,0 @@ -# CodeXchange AI - -[![Python Version](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://python.org) -[![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE) -[![CI/CD](https://github.com/alakob/ai_code_converter/actions/workflows/python-test.yml/badge.svg)](https://github.com/alakob/ai_code_converter/actions/workflows/python-test.yml) - -A powerful tool for converting and executing code between different programming languages using AI models. - -## Overview - -CodeXchangeAI is a Python application that leverages various AI models to translate code between programming languages while maintaining functionality and idiomatic patterns. - -### Key Features - -- Multi-language code conversion -- Real-time code execution -- Multiple AI model support (GPT, Claude, DeepSeek, GROQ, Gemini) -- File upload/download functionality -- Syntax highlighting -- Detailed logging system -- Docker support - -## Quick Start - -```bash -# Clone the repository -git clone git@github.com:alakob/ai_code_converter.git -cd ai_code_converter - -# Configure environment -cp .env.example .env -# Edit .env with your API keys - -``` - -### Using the Docker Wrapper Script (Recommended) - -For a more convenient way to run the application with Docker, you can use the provided wrapper script: - -```bash -# Make the script executable -chmod +x run-docker.sh - -# Run the application -./run-docker.sh # Build and run normally -./run-docker.sh -d # Run in detached mode -./run-docker.sh -p 8080 # Run on port 8080 -./run-docker.sh -s # Stop the container -./run-docker.sh -h # Show help message -``` - -## CI/CD Pipeline - -CodeXchange AI uses GitHub Actions for continuous integration and deployment. The pipeline includes: - -### Automated Testing -- Runs Python tests on multiple Python versions (3.9, 3.10, 3.11) -- Performs code linting and style checks -- Generates test coverage reports - -### Docker Image Validation -- Builds the Docker image to verify Dockerfile integrity -- Performs vulnerability scanning with Trivy -- Validates container startup and dependencies - -### Deployment Automation -- Automatically deploys to staging environment when changes are pushed to develop branch -- Creates production releases with semantic versioning -- Publishes Docker images to Docker Hub - -### Setting Up for Development - -To use the CI/CD pipeline in your fork, you'll need to add these secrets to your GitHub repository: - -1. `DOCKERHUB_USERNAME`: Your Docker Hub username -2. `DOCKERHUB_TOKEN`: A Docker Hub access token (not your password) - -See the [CI/CD documentation](docs/ci_cd_pipeline.md) for detailed setup instructions. - -The wrapper script provides several options for customizing the Docker deployment: - -```bash -Usage: ./run-docker.sh [OPTIONS] - -Options: - -b, --build Build the Docker image without running the container - -d, --detach Run container in detached mode (background) - -e, --env FILE Specify an environment file (default: .env) - -p, --port PORT Specify the port to expose (default: 7860) - -l, --logs Follow the container logs after starting - -s, --stop Stop the running container - -r, --restart Restart the container - -D, --down Stop and remove the container - -k, --keys Check for API keys and show setup instructions if missing - -h, --help Display this help message - -v, --version Display script version -``` - -Examples: -- Run on a different port: `./run-docker.sh -p 8080` -- Run in background: `./run-docker.sh -d` -- Stop the application: `./run-docker.sh -s` -- View logs: `./run-docker.sh -l` - -The application will be available at `http://localhost:7860` - -### Manual Installation - -```bash -# Create and activate virtual environment -python -m venv .venv -source .venv/bin/activate # On Windows: .venv\Scripts\activate - -# Install dependencies -pip install -r requirements.txt - -# Configure environment -cp .env.example .env -# Edit .env with your API keys - -# Run the application -python run.py -``` - -### Using Make Commands - -The project includes a Makefile with useful commands to streamline development: - -```bash -# Install dependencies -make install - -# Run the application -make run - -# Run with development settings (hot-reloading) -make dev - -# Build Docker container -make docker-build - -# Run Docker container -make docker-run - -# Or use the Docker wrapper script for more options -./run-docker.sh - -# Run tests -make test - -# Clean Python cache and build artifacts -make clean -``` - -## Basic Usage - -1. Select source and target programming languages -2. Enter or upload source code -3. Choose AI model and temperature -4. Click "Convert" to translate the code -5. Use "Run" buttons to execute original or converted code -6. Download the results including compiled binaries (for compiled languages) - -## Supported Languages - -Currently supports 17 programming languages including Python, JavaScript, Java, C++, Julia, Go, Ruby, Swift, Rust, C#, TypeScript, R, Perl, Lua, PHP, Kotlin, and SQL. - -See [Supported Languages](./docs/languages.md) for detailed information on each language. - -## Documentation - -For detailed documentation, please refer to the [docs](./docs) directory: - -- [Supported Languages](./docs/languages.md) - Details on all supported programming languages -- [Configuration Guide](./docs/configuration.md) - How to configure the application -- [Development Guide](./docs/development.md) - Guide for developers extending the application -- [Contributing Guidelines](./docs/contributing.md) - How to contribute to the project -- [Project Structure](./docs/project_structure.md) - Overview of the codebase architecture -- [Architecture Diagram](./docs/architecture_diagram.md) - Visual representation of the application architecture - -## License - -This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. - -## Acknowledgments - -- OpenAI for GPT models -- Anthropic for Claude -- Google for Gemini -- DeepSeek and GROQ for their AI models -- The Gradio team for the web interface framework \ No newline at end of file diff --git a/week4/community-contributions/ai_code_converter/codeXchange.ipynb b/week4/community-contributions/ai_code_converter/codeXchange.ipynb new file mode 100644 index 0000000..0640c6e --- /dev/null +++ b/week4/community-contributions/ai_code_converter/codeXchange.ipynb @@ -0,0 +1,107 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## codeXchange AI: Transform Code with a Click!\n", + "\n", + "**Created by Blaise Alako**\n", + "\n", + "Get ready to revolutionize your coding experience with **codeXchange AI**—a web-based Gradio app that converts code between programming languages in a flash! Powered by cutting-edge frontier and open-source LLMs, this tool is a game-changer for beginners diving into new languages, intermediates streamlining projects, and advanced users pushing the limits of innovation. Just paste or upload your code, choose your target language, and watch the magic unfold!\n", + "\n", + "**Why codeXchange AI?**\n", + "- **Effortless**: No downloads—just pure web-based magic.\n", + "- **Brilliant**: AI-driven conversions that nail accuracy.\n", + "- **Adaptable**: Add new languages or models with ease.\n", + "\n", + "Explore the source code [codeXchange AI](https://github.com/alakob/ai_code_converter) and experience the thrill!\n", + "\n", + "---\n", + "\n", + "### Table of Contents\n", + "1. [Explore the Interface](#explore-the-interface)\n", + "2. [Upload and Convert](#upload-and-convert)\n", + "3. [See the Results](#see-the-results)\n", + "4. [Unleash Advanced Features](#unleash-advanced-features)\n", + "5. [Performance That Wows](#performance-that-wows)\n", + "6. [Get Started Now](#get-started-now)\n", + "\n", + "---\n", + "\n", + "### Explore the Interface\n", + "\n", + "#### A Sleek Starting Point\n", + "Step into the world of codeXchange AI with its stunningly simple interface, designed to make your coding journey a breeze!\n", + "\n", + "![Initial Interface](screenshots/codeXchange_1.png) \n", + "*Screenshot: The app’s clean starting screen, ready for your code.*\n", + "\n", + "With options to upload files or pick example snippets, you’re just a click away from transforming your code.\n", + "\n", + "---\n", + "\n", + "### Upload and Convert\n", + "\n", + "#### Load Your Code with Ease\n", + "Whether you’re a beginner or a pro, uploading your code is a snap. Drag and drop a file, or select a preloaded snippet to kick things off.\n", + "\n", + "![Loading Code](screenshots/codeXchange_2.png) \n", + "*Screenshot: The upload section with a dropdown for example snippets.*\n", + "\n", + "Choose your input language, pick your target, and hit “Convert”—it’s that easy to bridge the language gap!\n", + "\n", + "---\n", + "\n", + "### See the Results\n", + "\n", + "#### Witness the Transformation\n", + "Watch codeXchange AI work its magic! It converts your code with precision, adding helpful documentation to make the output crystal clear.\n", + "\n", + "![Conversion Output](screenshots/codeXchange_3.png) \n", + "*Screenshot: A converted result with documentation, ready to run.*\n", + "\n", + "From Python to C++ or beyond, the app ensures your code is ready to shine in its new language.\n", + "\n", + "---\n", + "\n", + "### Unleash Advanced Features\n", + "\n", + "#### Power Up Your Workflow\n", + "For those who love to tinker, codeXchange AI offers exciting customization! Select different models, adjust the “Temperature” for creative flair, and even add new languages to the mix.\n", + "\n", + "![Advanced Options](screenshots/codeXchange_3_1.png) \n", + "*Screenshot: Interface showcasing model selection, temperature slider, and more.*\n", + "\n", + "Download your converted code with a single click and take your projects to the next level!\n", + "\n", + "---\n", + "\n", + "### Performance That Wows\n", + "\n", + "#### Speed That Impresses\n", + "codeXchange AI doesn’t just convert—it optimizes! Check out the performance boost when running your code in a new language, with execution times that’ll leave you amazed.\n", + "\n", + "![Performance Results](screenshots/codeXchange_4.png) \n", + "*Screenshot: Execution results highlighting speed improvements.*\n", + "\n", + "From 31.49 seconds in Python to just 2.32 seconds in C++—see the difference for yourself!\n", + "\n", + "---\n", + "\n", + "### Get Started Now\n", + "\n", + "Ready to transform your coding game? Jump into [codeXchange AI source code](https://github.com/alakob/ai_code_converter) Convert, run, and download your code in seconds. Whether you’re just starting out, managing complex projects, or innovating at an advanced level, this app is your ultimate coding companion.\n", + "\n", + "---\n" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/week4/community-contributions/ai_code_converter/docker-compose.yml b/week4/community-contributions/ai_code_converter/docker-compose.yml deleted file mode 100644 index ee2fc36..0000000 --- a/week4/community-contributions/ai_code_converter/docker-compose.yml +++ /dev/null @@ -1,23 +0,0 @@ - -services: - ai_code_converter: - build: . - ports: - - "${PORT:-7860}:7860" - volumes: - - ./logs:/app/logs - - ./downloads:/app/downloads - environment: - - OPENAI_API_KEY=${OPENAI_API_KEY} - - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY} - - GOOGLE_API_KEY=${GOOGLE_API_KEY} - - DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY} - - GROQ_API_KEY=${GROQ_API_KEY} - - PORT=${PORT:-7860} - healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:7860/healthz"] - interval: 30s - timeout: 30s - retries: 3 - start_period: 5s - restart: unless-stopped \ No newline at end of file diff --git a/week4/community-contributions/ai_code_converter/docs/README.md b/week4/community-contributions/ai_code_converter/docs/README.md deleted file mode 100644 index 470ef61..0000000 --- a/week4/community-contributions/ai_code_converter/docs/README.md +++ /dev/null @@ -1,12 +0,0 @@ -# CodeXchange AI Documentation - -This directory contains comprehensive documentation for the CodeXchange AI project. - -## Contents - -- [Supported Languages](./languages.md) - Details on all supported programming languages -- [Configuration Guide](./configuration.md) - How to configure the application -- [Development Guide](./development.md) - Guide for developers extending the application -- [Contributing Guidelines](./contributing.md) - How to contribute to the project -- [Project Structure](./project_structure.md) - Overview of the codebase architecture -- [Architecture Diagram](./architecture_diagram.md) - Visual representation of the application architecture and component relationships diff --git a/week4/community-contributions/ai_code_converter/docs/architecture_diagram.md b/week4/community-contributions/ai_code_converter/docs/architecture_diagram.md deleted file mode 100644 index 1e737a4..0000000 --- a/week4/community-contributions/ai_code_converter/docs/architecture_diagram.md +++ /dev/null @@ -1,297 +0,0 @@ -# Architecture Diagram - -This diagram illustrates the architecture and component relationships of the CodeXchange AI application. - -> **Note:** For detailed information about the CI/CD pipeline, see [CI/CD Pipeline Architecture](ci_cd_pipeline.md). - -## Application Flow Diagram - -```mermaid -graph TD - %% Main Entry Points - A[run.py] --> B[main.py] - B --> C[CodeConverterApp] - - %% Core Components - C --> D[Gradio UI] - C --> E[AIModelStreamer] - C --> F[CodeExecutor] - C --> G[LanguageDetector] - C --> H[FileHandler] - - %% AI Models - E --> I[OpenAI GPT] - E --> J[Anthropic Claude] - E --> K[Google Gemini] - E --> L[DeepSeek] - E --> M[GROQ] - - %% Language Processing - G --> N[Language Validation] - F --> O[Code Execution] - - %% File Operations - H --> P[File Upload/Download] - H --> Q[ZIP Creation] - - %% Configuration - R[config.py] --> C - R --> E - R --> F - - %% Template - S[template.j2] --> C - - %% User Interactions - D --> T[Code Input] - D --> U[Language Selection] - D --> V[Model Selection] - D --> W[Code Conversion] - D --> X[Code Execution] - D --> Y[File Download] - - %% Logging - Z[logger.py] --> C - Z --> E - Z --> F - Z --> G - Z --> H - - %% Styling - style A fill:#f9d77e,stroke:#333,stroke-width:2px - style B fill:#f9d77e,stroke:#333,stroke-width:2px - style C fill:#f9d77e,stroke:#333,stroke-width:2px - style D fill:#a8d5ba,stroke:#333,stroke-width:2px - style E fill:#a8d5ba,stroke:#333,stroke-width:2px - style F fill:#a8d5ba,stroke:#333,stroke-width:2px - style G fill:#a8d5ba,stroke:#333,stroke-width:2px - style H fill:#a8d5ba,stroke:#333,stroke-width:2px - style I fill:#ffb6c1,stroke:#333,stroke-width:2px - style J fill:#ffb6c1,stroke:#333,stroke-width:2px - style K fill:#ffb6c1,stroke:#333,stroke-width:2px - style L fill:#ffb6c1,stroke:#333,stroke-width:2px - style M fill:#ffb6c1,stroke:#333,stroke-width:2px - style R fill:#d0e0e3,stroke:#333,stroke-width:2px - style S fill:#d0e0e3,stroke:#333,stroke-width:2px - style Z fill:#d0e0e3,stroke:#333,stroke-width:2px -``` - -## Component Interaction Sequence - -```mermaid -sequenceDiagram - participant User - participant UI as Gradio UI - participant App as CodeConverterApp - participant AI as AIModelStreamer - participant Executor as CodeExecutor - participant Detector as LanguageDetector - participant Files as FileHandler - - User->>UI: Enter Source Code - User->>UI: Select Source Language - User->>UI: Select Target Language - User->>UI: Select AI Model - User->>UI: Click Convert - - UI->>App: Request Code Conversion - App->>Detector: Validate Source Language - Detector-->>App: Validation Result - - App->>App: Create Prompt from Template - App->>AI: Send Prompt to Selected Model - AI-->>App: Stream Converted Code - App-->>UI: Display Converted Code - - User->>UI: Click Run Original - UI->>App: Request Code Execution - App->>Executor: Execute Original Code - Executor-->>App: Execution Result - App-->>UI: Display Execution Result - - User->>UI: Click Run Converted - UI->>App: Request Code Execution - App->>Executor: Execute Converted Code - Executor-->>App: Execution Result - App-->>UI: Display Execution Result - - User->>UI: Click Download - UI->>App: Request Download - App->>Files: Create ZIP with Files - Files-->>App: ZIP File - App-->>UI: Provide Download Link - UI-->>User: Download Files -``` - -## Class Diagram - -```mermaid -classDiagram - class CodeConverterApp { - -AIModelStreamer ai_streamer - -CodeExecutor executor - -LanguageDetector detector - -FileHandler file_handler - -GradioInterface demo - +__init__() - +_setup_environment() - +_initialize_components() - +_create_gradio_interface() - +convert_code() - +execute_code() - +download_files() - +run() - } - - class AIModelStreamer { - -OpenAI openai - -Anthropic claude - -OpenAI deepseek - -OpenAI groq - -GenerativeModel gemini - +__init__() - +stream_gpt() - +stream_claude() - +stream_gemini() - +stream_deepseek() - +stream_groq() - +stream_completion() - } - - class CodeExecutor { - -dict executors - +__init__() - +execute() - +execute_python() - +execute_javascript() - +execute_java() - +execute_cpp() - +execute_julia() - +execute_go() - +execute_ruby() - +execute_swift() - +execute_rust() - +execute_csharp() - +execute_typescript() - +execute_r() - +execute_perl() - +execute_lua() - +execute_php() - +execute_kotlin() - +execute_sql() - } - - class LanguageDetector { - +detect_python() - +detect_javascript() - +detect_java() - +detect_cpp() - +detect_julia() - +detect_go() - +detect_ruby() - +detect_swift() - +detect_rust() - +detect_csharp() - +detect_typescript() - +detect_r() - +detect_perl() - +detect_lua() - +detect_php() - +detect_kotlin() - +detect_sql() - +validate_language() - } - - class FileHandler { - +create_readme() - +create_zip() - +handle_upload() - +handle_download() - } - - CodeConverterApp --> AIModelStreamer - CodeConverterApp --> CodeExecutor - CodeConverterApp --> LanguageDetector - CodeConverterApp --> FileHandler -``` - -## Supported Languages and Models - -```mermaid -graph LR - subgraph "AI Models" - M1[GPT] - M2[Claude] - M3[Gemini] - M4[DeepSeek] - M5[GROQ] - end - - subgraph "Supported Languages" - L1[Python] - L2[JavaScript] - L3[Java] - L4[C++] - L5[Julia] - L6[Go] - L7[Ruby] - L8[Swift] - L9[Rust] - L10[C#] - L11[TypeScript] - L12[R] - L13[Perl] - L14[Lua] - L15[PHP] - L16[Kotlin] - L17[SQL] - end - - subgraph "Fully Implemented" - L1 - L2 - L3 - L4 - L5 - L6 - end - - subgraph "Template Ready" - L7 - L8 - L9 - L10 - L11 - L12 - L13 - L14 - L15 - L16 - L17 - end - - style M1 fill:#ffb6c1,stroke:#333,stroke-width:2px - style M2 fill:#ffb6c1,stroke:#333,stroke-width:2px - style M3 fill:#ffb6c1,stroke:#333,stroke-width:2px - style M4 fill:#ffb6c1,stroke:#333,stroke-width:2px - style M5 fill:#ffb6c1,stroke:#333,stroke-width:2px - - style L1 fill:#a8d5ba,stroke:#333,stroke-width:2px - style L2 fill:#a8d5ba,stroke:#333,stroke-width:2px - style L3 fill:#a8d5ba,stroke:#333,stroke-width:2px - style L4 fill:#a8d5ba,stroke:#333,stroke-width:2px - style L5 fill:#a8d5ba,stroke:#333,stroke-width:2px - style L6 fill:#a8d5ba,stroke:#333,stroke-width:2px - - style L7 fill:#d0e0e3,stroke:#333,stroke-width:2px - style L8 fill:#d0e0e3,stroke:#333,stroke-width:2px - style L9 fill:#d0e0e3,stroke:#333,stroke-width:2px - style L10 fill:#d0e0e3,stroke:#333,stroke-width:2px - style L11 fill:#d0e0e3,stroke:#333,stroke-width:2px - style L12 fill:#d0e0e3,stroke:#333,stroke-width:2px - style L13 fill:#d0e0e3,stroke:#333,stroke-width:2px - style L14 fill:#d0e0e3,stroke:#333,stroke-width:2px - style L15 fill:#d0e0e3,stroke:#333,stroke-width:2px - style L16 fill:#d0e0e3,stroke:#333,stroke-width:2px - style L17 fill:#d0e0e3,stroke:#333,stroke-width:2px -``` diff --git a/week4/community-contributions/ai_code_converter/docs/before_any_update.md b/week4/community-contributions/ai_code_converter/docs/before_any_update.md deleted file mode 100644 index 204aae2..0000000 --- a/week4/community-contributions/ai_code_converter/docs/before_any_update.md +++ /dev/null @@ -1,9 +0,0 @@ - -**Task Overview:** -Thoroughly analyze the entire codebase and gain a deep understanding of the application flow diagram and the interaction sequence between all components. - -**Objective:** -Develop a comprehensive understanding of how different modules interact, their dependencies, and how data flows across the system. This foundational knowledge will be essential for implementing future updates accurately and efficiently without breaking the existing codebase. - -**Important Note:** -⚠️ **No code should be written or modified at this stage.** The primary focus is on understanding the architecture, relationships, and dependencies to ensure a seamless update process in subsequent phases. diff --git a/week4/community-contributions/ai_code_converter/docs/ci_cd_pipeline.md b/week4/community-contributions/ai_code_converter/docs/ci_cd_pipeline.md deleted file mode 100644 index 9c6eb07..0000000 --- a/week4/community-contributions/ai_code_converter/docs/ci_cd_pipeline.md +++ /dev/null @@ -1,86 +0,0 @@ -# CI/CD Pipeline Architecture - -This document outlines the continuous integration and continuous deployment (CI/CD) pipeline implemented for CodeXchange AI. - -## Pipeline Overview - -The CI/CD pipeline automates testing, building, and deployment processes using GitHub Actions. It follows a trunk-based development model with protection for the main branch. - -```mermaid -graph TD - Code[Developer Code] --> PR[Pull Request] - PR --> Tests[Automated Tests] - Tests --> DockerBuild[Docker Build & Validation] - DockerBuild --> |On merge to develop| StagingDeploy[Staging Deployment] - StagingDeploy --> |On release creation| ProdDeploy[Production Deployment] - - %% Pipeline Components - subgraph "CI Pipeline" - Tests - DockerBuild - end - - subgraph "CD Pipeline" - StagingDeploy - ProdDeploy - end -``` - -## Workflow Components - -### 1. Python Testing Workflow - -The testing workflow (`python-test.yml`) performs: -- Syntax validation and linting with flake8 -- Unit and integration tests with pytest -- Code coverage analysis -- Matrix testing across Python 3.9, 3.10, and 3.11 - -### 2. Docker Build Workflow - -The Docker workflow (`docker-build.yml`) performs: -- Dockerfile validation -- Container image building -- Security scanning with Trivy -- Vulnerability assessment - -### 3. Staging Deployment - -The staging workflow (`deploy-staging.yml`): -- Triggered on pushes to the develop branch -- Builds and tags Docker images with branch name and commit hash -- Pushes images to Docker Hub -- Deploys to the staging environment - -### 4. Production Deployment - -The production workflow (`deploy-production.yml`): -- Triggered on release publication -- Builds and tags Docker images with semantic version -- Pushes images to Docker Hub with version tags -- Deploys to the production environment with approval gate - -## Security Considerations - -- Sensitive credentials stored as GitHub Secrets -- Docker Hub authentication using access tokens, not passwords -- Security scanning for both code and container images -- Protected branches requiring CI checks to pass before merging - -## Development Workflow - -1. Create feature branch from develop -2. Implement changes with tests -3. Open pull request to develop -4. Pass all CI checks -5. Merge to develop (triggers staging deployment) -6. Create release for production deployment - -## Required Repository Secrets - -The following secrets must be configured in the GitHub repository: - -| Secret Name | Purpose | -|-------------|---------| -| `DOCKERHUB_USERNAME` | Docker Hub username for image publishing | -| `DOCKERHUB_TOKEN` | Docker Hub authentication token | diff --git a/week4/community-contributions/ai_code_converter/docs/configuration.md b/week4/community-contributions/ai_code_converter/docs/configuration.md deleted file mode 100644 index 5a86ab1..0000000 --- a/week4/community-contributions/ai_code_converter/docs/configuration.md +++ /dev/null @@ -1,82 +0,0 @@ -# Configuration Guide - -## Environment Variables - -Create a `.env` file in the root directory: - -```bash -OPENAI_API_KEY=your_openai_key_here -ANTHROPIC_API_KEY=your_anthropic_key_here -GOOGLE_API_KEY=your_google_key_here -DEEPSEEK_API_KEY=your_deepseek_key_here -GROQ_API_KEY=your_groq_key_here -PORT=7860 # Optional, default port for the web interface -``` - -## Model Configuration - -Model names are configured in `src/ai_code_converter/config.py`: - -```python -# Model configurations -OPENAI_MODEL = "gpt-4o-mini" # OpenAI model name -CLAUDE_MODEL = "claude-3-sonnet-20240307" # Anthropic Claude model -DEEPSEEK_MODEL = "deepseek-chat" # DeepSeek model -GEMINI_MODEL = "gemini-1.5-flash" # Google Gemini model -GROQ_MODEL = "llama3-70b-8192" # GROQ model -``` - -You can modify these values to use different model versions based on your requirements and API access. - -## Prerequisites - -The following dependencies are required for full functionality: - -- Python 3.10+ -- Node.js and npm (with TypeScript) -- Java JDK 17+ -- Julia 1.9+ -- Go 1.21+ -- GCC/G++ -- Perl -- Lua 5.3+ -- PHP -- R -- Ruby -- Rust (rustc and cargo) -- Mono (for C#) -- Swift 5.9+ -- Kotlin -- SQLite3 - -Using Docker is recommended as it includes all necessary dependencies. - -## Docker Configuration - -The application includes Docker support for easy deployment. The `docker-compose.yml` file defines the service configuration: - -```yaml -version: '3' -services: - ai_code_converter: - build: . - ports: - - "${PORT:-7860}:7860" - volumes: - - ./logs:/app/logs - env_file: - - .env - restart: unless-stopped -``` - -You can customize the port mapping and volume mounts as needed. - -## Application Settings - -Additional application settings can be configured in `src/ai_code_converter/config.py`: - -- UI theme and styling -- Default language selections -- Model temperature settings -- Execution timeouts -- Logging configuration diff --git a/week4/community-contributions/ai_code_converter/docs/contributing.md b/week4/community-contributions/ai_code_converter/docs/contributing.md deleted file mode 100644 index 7d6a972..0000000 --- a/week4/community-contributions/ai_code_converter/docs/contributing.md +++ /dev/null @@ -1,89 +0,0 @@ -# Contributing Guidelines - -Thank you for your interest in contributing to the CodeXchange AI project! This document provides guidelines and instructions for contributing to the project. - -## How to Contribute - -1. Fork the repository -2. Create a feature branch (`git checkout -b feature/amazing-feature`) -3. Commit your changes (`git commit -m 'Add amazing feature'`) -4. Push to the branch (`git push origin feature/amazing-feature`) -5. Open a Pull Request - -## Development Workflow - -1. Check the issues page for open tasks or create a new issue for the feature/bug you want to work on -2. Assign yourself to the issue -3. Implement your changes following the best practices outlined below -4. Write tests for your changes -5. Update documentation as needed -6. Submit a pull request referencing the issue - -## Best Practices - -### Code Style - -- Follow existing patterns for consistency -- Follow PEP 8 style guidelines for Python code -- Use descriptive variable and function names -- Add type hints for all new functions -- Keep functions small and focused on a single responsibility -- Use docstrings for all public functions and classes - -### Error Handling - -- Add comprehensive error handling -- Use specific exception types -- Provide helpful error messages -- Log errors with appropriate context - -### Logging - -- Include detailed logging -- Use the existing logging framework -- Log at appropriate levels (DEBUG, INFO, WARNING, ERROR) -- Include relevant context in log messages - -### Documentation - -- Update documentation for any changes -- Document new features, configuration options, and APIs -- Keep the README and docs directory in sync -- Use clear, concise language - -### Testing - -- Write unit tests for new functionality -- Ensure all tests pass before submitting a PR -- Test edge cases and error conditions -- Aim for good test coverage - -## Pull Request Process - -1. Ensure your code follows the style guidelines -2. Update documentation as needed -3. Include tests for new functionality -4. Link the PR to any related issues -5. Wait for code review and address any feedback - -## Code Review - -All submissions require review. We use GitHub pull requests for this purpose: - -1. Reviewers will check code quality, test coverage, and documentation -2. Address any comments or requested changes -3. Once approved, maintainers will merge your PR - -## Acknowledgments - -We would like to thank the following organizations and projects that make CodeXchange AI possible: - -- OpenAI for GPT models -- Anthropic for Claude -- Google for Gemini -- DeepSeek and GROQ for their AI models -- The Gradio team for the web interface framework - -## License - -By contributing to this project, you agree that your contributions will be licensed under the project's MIT License. diff --git a/week4/community-contributions/ai_code_converter/docs/development.md b/week4/community-contributions/ai_code_converter/docs/development.md deleted file mode 100644 index 5043231..0000000 --- a/week4/community-contributions/ai_code_converter/docs/development.md +++ /dev/null @@ -1,180 +0,0 @@ -# Development Guide - -This guide provides instructions for extending the CodeXchange AI application with new languages and AI models. - -Before diving into development, it's recommended to review the [Architecture Diagram](./architecture_diagram.md) to understand the component relationships and application flow. - -## Adding New Programming Languages - -1. Update Language Configuration (`config.py`): -```python -SUPPORTED_LANGUAGES = [..., "NewLanguage"] -LANGUAGE_MAPPING = {..., "NewLanguage": "language_highlight_name"} -``` - -2. Add Language Detection (`core/language_detection.py`): -```python -class LanguageDetector: - @staticmethod - def detect_new_language(code: str) -> bool: - patterns = [r'pattern1', r'pattern2', r'pattern3'] - return any(re.search(pattern, code) for pattern in patterns) -``` - -3. Add Execution Support (`core/code_execution.py`): -```python -def execute_new_language(self, code: str) -> tuple[str, Optional[bytes]]: - with tempfile.NamedTemporaryFile(suffix='.ext', mode='w', delete=False) as f: - f.write(code) - file_path = f.name - try: - result = subprocess.run( - ["compiler/interpreter", file_path], - capture_output=True, - text=True, - check=True - ) - return result.stdout, None - except Exception as e: - return f"Error: {str(e)}", None - finally: - os.unlink(file_path) -``` - -4. Update the Dockerfile: -```dockerfile -# Add necessary dependencies for the new language -RUN apt-get update && apt-get install -y --no-install-recommends \ - new-language-package \ - && rm -rf /var/lib/apt/lists/* - -# Verify the installation -RUN echo "New Language: $(new-language --version 2>/dev/null || echo 'NOT VERIFIED')" -``` - -5. Update the UI Components in `app.py`: -```python -def _initialize_components(self): - # Add the new language to dropdown options - self.source_language = gr.Dropdown( - choices=["Python", "JavaScript", ..., "NewLanguage"], - ... - ) - self.target_language = gr.Dropdown( - choices=["Python", "JavaScript", ..., "NewLanguage"], - ... - ) -``` - -6. Add Language-Specific Instructions in `template.j2`: -```jinja -{% if target_language == "NewLanguage" %} -# NewLanguage-specific conversion instructions -- Follow NewLanguage best practices -- Use idiomatic NewLanguage patterns -- Handle NewLanguage-specific edge cases -{% endif %} -``` - -## Adding New AI Models - -1. Update Model Configuration (`config.py`): -```python -NEW_MODEL = "model-name-version" -MODELS = [..., "NewModel"] -``` - -2. Add Model Integration (`models/ai_streaming.py`): -```python -def stream_new_model(self, prompt: str) -> Generator[str, None, None]: - try: - response = self.new_model_client.generate( - prompt=prompt, - stream=True - ) - reply = "" - for chunk in response: - fragment = chunk.text - reply += fragment - yield reply - except Exception as e: - logger.error(f"New Model API error: {str(e)}", exc_info=True) - yield f"Error with New Model API: {str(e)}" -``` - -3. Add API Client Initialization: -```python -def __init__(self, api_keys: dict): - # Initialize existing clients - ... - - # Initialize new model client - if "NEW_MODEL_API_KEY" in api_keys: - self.new_model_client = NewModelClient( - api_key=api_keys["NEW_MODEL_API_KEY"] - ) -``` - -4. Update the Model Selection Logic: -```python -def stream_completion(self, model: str, prompt: str) -> Generator[str, None, None]: - if model == "NewModel": - yield from self.stream_new_model(prompt) - else: - # Existing model handling - ... -``` - -## Testing - -1. Add test cases for new components: - - Unit tests for language detection - - Integration tests for code execution - - End-to-end tests for UI components - -2. Test language detection with sample code: - - Positive examples (valid code in the target language) - - Negative examples (code from other languages) - - Edge cases (minimal valid code snippets) - -3. Test code execution with various examples: - - Simple "Hello World" programs - - Programs with external dependencies - - Programs with different runtime characteristics - - Error handling cases - -4. Test model streaming with different prompts: - - Short prompts - - Long prompts - - Edge cases (empty prompts, very complex code) - - Error handling - -5. Verify error handling and edge cases: - - API rate limiting - - Network failures - - Invalid inputs - - Resource constraints - -## Logging - -The application uses a structured logging system: - -- JSON formatted logs with timestamps -- Stored in `logs` directory -- Separate console and file logging -- Detailed execution metrics - -To add logging for new components: - -```python -import logging -logger = logging.getLogger(__name__) - -def new_function(): - try: - logger.info("Starting operation", extra={"component": "new_component"}) - # Function logic - logger.info("Operation completed", extra={"component": "new_component", "metrics": {...}}) - except Exception as e: - logger.error(f"Error: {str(e)}", exc_info=True, extra={"component": "new_component"}) -``` diff --git a/week4/community-contributions/ai_code_converter/docs/languages.md b/week4/community-contributions/ai_code_converter/docs/languages.md deleted file mode 100644 index 5b424be..0000000 --- a/week4/community-contributions/ai_code_converter/docs/languages.md +++ /dev/null @@ -1,68 +0,0 @@ -# Supported Languages - -CodeXchange AI currently supports the following programming languages: - -## Language Support Table - -| Language | Execution Method | File Extension | -|------------|----------------------------------------|----------------| -| Python | Direct execution in restricted env | .py | -| JavaScript | Node.js | .js | -| Java | javac + java | .java | -| C++ | g++ + executable | .cpp | -| Julia | julia | .jl | -| Go | go run | .go | -| Ruby | ruby | .rb | -| Swift | swift | .swift | -| Rust | rustc + executable | .rs | -| C# | csc (Mono) | .cs | -| TypeScript | tsc + node | .ts | -| R | Rscript | .R | -| Perl | perl | .pl | -| Lua | lua5.3 | .lua | -| PHP | php | .php | -| Kotlin | kotlinc + kotlin | .kt | -| SQL | sqlite3 | .sql | - -## Currently Implemented Languages - -While the application has templates and instructions for all the languages listed above, the following languages are currently fully implemented with language detection and execution support: - -- Python -- JavaScript -- Java -- C++ -- Julia -- Go - -## Language-Specific Notes - -### Python -- Executed directly in a restricted environment -- Supports most standard libraries -- Execution timeout: 30 seconds - -### JavaScript -- Executed using Node.js -- Supports ES6+ features -- No external npm packages are installed during execution - -### Java -- Requires a class with a main method -- Class name must match filename -- Compiled with javac before execution - -### C++ -- Compiled with g++ -- Standard C++17 support -- Execution timeout: 30 seconds - -### Julia -- Executed with the julia interpreter -- Supports Julia 1.9+ -- Limited package support during execution - -### Go -- Executed with go run -- Supports Go 1.21+ -- Standard library support only diff --git a/week4/community-contributions/ai_code_converter/docs/project_structure.md b/week4/community-contributions/ai_code_converter/docs/project_structure.md deleted file mode 100644 index 59c2716..0000000 --- a/week4/community-contributions/ai_code_converter/docs/project_structure.md +++ /dev/null @@ -1,108 +0,0 @@ -# Project Structure - -This document provides an overview of the CodeXchange AI project structure and architecture. - -For a visual representation of the application architecture and component relationships, please refer to the [Architecture Diagram](./architecture_diagram.md). - -## Directory Structure - -``` -ai_code_converter/ -├── README.md # Project documentation -├── requirements.txt # Python dependencies -├── run.py # Application entry point -├── Dockerfile # Docker configuration -├── docker-compose.yml # Docker Compose configuration -├── .env.example # Environment variables template -├── docs/ # Detailed documentation -└── src/ # Source code directory - └── ai_code_converter/ - ├── main.py # Main application logic - ├── app.py # Gradio interface setup - ├── config.py # Model and app configuration - ├── template.j2 # Prompt template - ├── core/ # Core functionality - │ ├── __init__.py - │ ├── language_detection.py # Language validation - │ └── code_execution.py # Code execution - ├── models/ # AI model integration - │ ├── __init__.py - │ └── ai_streaming.py # API clients and streaming - └── utils/ # Utility functions - ├── __init__.py - └── logging.py # Logging configuration -``` - -## Component Descriptions - -### Entry Points - -- **run.py**: The main entry point for the application. It imports and initializes the necessary modules and starts the application. - -- **main.py**: Contains the main application logic, initializes the application components, and starts the Gradio interface. - -### Core Components - -- **app.py**: Sets up the Gradio interface and defines UI components. Contains the `CodeConverterApp` class that handles the UI and code conversion logic. - -- **config.py**: Contains configuration for models, languages, and application settings. Defines supported languages, model names, and UI styling. - -- **template.j2**: A Jinja2 template used to create prompts for the LLMs with language-specific instructions for code conversion. - -### Core Directory - -The `core` directory contains modules for core functionality: - -- **language_detection.py**: Contains the `LanguageDetector` class with static methods to validate if code matches the expected language patterns. - -- **code_execution.py**: Handles the execution of code in different programming languages. Contains language-specific execution methods. - -### Models Directory - -The `models` directory contains modules for AI model integration: - -- **ai_streaming.py**: Handles API calls to various LLMs (GPT, Claude, Gemini, DeepSeek, GROQ). Contains methods for streaming responses from different AI providers. - -### Utils Directory - -The `utils` directory contains utility modules: - -- **logging.py**: Configures the logging system for the application. Sets up console and file handlers with appropriate formatting. - -## Application Flow - -1. **Initialization**: - - `run.py` imports the main module - - `main.py` initializes the application components - - `app.py` sets up the Gradio interface - -2. **User Interaction**: - - User selects source and target languages - - User enters or uploads source code - - User selects AI model and temperature - - User clicks "Convert" - -3. **Code Conversion**: - - Source language is validated using `language_detection.py` - - Prompt is created using `template.j2` - - AI model is called using `ai_streaming.py` - - Response is streamed back to the UI - -4. **Code Execution** (optional): - - User clicks "Run" on original or converted code - - Code is executed using appropriate method in `code_execution.py` - - Output is displayed in the UI - -## Design Patterns - -- **Singleton Pattern**: Used for API clients to ensure only one instance exists -- **Factory Pattern**: Used for creating language-specific execution methods -- **Strategy Pattern**: Used for selecting the appropriate AI model -- **Observer Pattern**: Used for streaming responses from AI models - -## Dependencies - -- **Gradio**: Web interface framework -- **Jinja2**: Template engine for creating prompts -- **OpenAI, Anthropic, Google, DeepSeek, GROQ APIs**: AI model providers -- **Various language interpreters and compilers**: For code execution diff --git a/week4/community-contributions/ai_code_converter/gradio_theme.py b/week4/community-contributions/ai_code_converter/gradio_theme.py deleted file mode 100644 index 748f51d..0000000 --- a/week4/community-contributions/ai_code_converter/gradio_theme.py +++ /dev/null @@ -1,332 +0,0 @@ -import gradio as gr - -# Define custom CSS with dark theme fixes, updated to use html.dark -custom_css = """ -/* Root variables for light theme */ -:root { - --background: #ffffff; - --secondary-background: #f7f7f7; - --tertiary-background: #f0f0f0; - --text: #000000; - --secondary-text: #5d5d5d; - --border: #e0e0e0; - --accent: #ff7b2c; - --accent-hover: #ff6a14; - --button-text: #ffffff; - --code-bg: #f7f7f7; - --code-text: #000000; - --dropdown-bg: #ffffff; - --slider-track: #e0e0e0; - --slider-thumb: #ff7b2c; - --collapsible-header: #f0f0f0; - --primary-button-bg: #4a6ee0; /* Blue */ - --primary-button-hover: #3a5ec8; - --secondary-button-bg: #444444; - --secondary-button-hover: #555555; - --danger-button-bg: #e74c3c; /* Red */ - --danger-button-hover: #c0392b; - --success-button-bg: #e67e22; /* Orange */ - --success-button-hover: #d35400; -} - -/* Dark theme variables using html.dark */ -html.dark { - --background: #1a1a1a; - --secondary-background: #252525; - --tertiary-background: #2a2a2a; - --text: #ffffff; - --secondary-text: #cccccc; - --border: #444444; - --accent: #ff7b2c; - --accent-hover: #ff6a14; - --button-text: #ffffff; - --code-bg: #252525; - --code-text: #ffffff; - --dropdown-bg: #2a2a2a; - --slider-track: #444444; - --slider-thumb: #ff7b2c; - --collapsible-header: #333333; - --primary-button-bg: #4a6ee0; - --primary-button-hover: #3a5ec8; - --secondary-button-bg: #444444; - --secondary-button-hover: #555555; - --danger-button-bg: #e74c3c; - --danger-button-hover: #c0392b; - --success-button-bg: #e67e22; - --success-button-hover: #d35400; -} - -/* Base styles */ -body { - background-color: var(--background) !important; - color: var(--text) !important; - transition: all 0.3s ease; -} - -.gradio-container { - background-color: var(--background) !important; - max-width: 100% !important; -} - -/* Headers */ -h1, h2, h3, h4, h5, h6, .gr-header { - color: var(--text) !important; -} - -/* Panels and blocks */ -.gr-panel, .gr-form, .gr-box, .gr-block { - background-color: var(--secondary-background) !important; - color: var(--text) !important; - border-color: var(--border) !important; - border-radius: 6px !important; -} - -/* Validation messages section */ -.gr-accordion .gr-panel { - background-color: var(--secondary-background) !important; - color: var(--text) !important; - border-color: #e74c3c !important; /* Red border */ -} - -.gr-accordion-header { - background-color: var(--collapsible-header) !important; - color: var(--text) !important; -} - -/* Code editors */ -.codebox, .code-editor, .cm-editor { - background-color: var(--code-bg) !important; - color: var(--code-text) !important; - border: 1px solid var(--border) !important; - border-radius: 4px !important; -} - -/* Syntax highlighting */ -.cm-editor .cm-content, .cm-editor .cm-line { - color: var(--code-text) !important; -} -.cm-editor .cm-keyword { color: #ff79c6 !important; } /* Pink */ -.cm-editor .cm-number { color: #bd93f9 !important; } /* Purple */ -.cm-editor .cm-string { color: #f1fa8c !important; } /* Yellow */ -.cm-editor .cm-comment { color: #6272a4 !important; } /* Gray */ - -/* Buttons */ -.gr-button { - background-color: var(--tertiary-background) !important; - color: var(--text) !important; - border-color: var(--border) !important; - border-radius: 4px !important; -} -.gr-button.success { - background-color: var(--success-button-bg) !important; - color: var(--button-text) !important; -} -.gr-button.success:hover { - background-color: var(--success-button-hover) !important; -} -.gr-button.danger { - background-color: var(--danger-button-bg) !important; - color: var(--button-text) !important; -} -.gr-button.danger:hover { - background-color: var(--danger-button-hover) !important; -} -.gr-button.secondary { - background-color: var(--secondary-button-bg) !important; - color: var(--button-text) !important; -} -.gr-button.secondary:hover { - background-color: var(--secondary-button-hover) !important; -} - -/* File upload */ -.gr-file, .gr-file-upload { - background-color: var(--secondary-background) !important; - color: var(--text) !important; - border-color: var(--border) !important; -} - -/* Dropdowns */ -.gr-dropdown, .gr-dropdown-container, .gr-dropdown select, .gr-dropdown option { - background-color: var(--dropdown-bg) !important; - color: var(--text) !important; - border-color: var(--border) !important; -} - -/* Slider */ -.gr-slider { - background-color: var(--background) !important; -} -.gr-slider-track { - background-color: var(--slider-track) !important; -} -.gr-slider-handle { - background-color: var(--slider-thumb) !important; -} -.gr-slider-value { - color: var(--text) !important; -} - -/* Code output */ -.gr-code { - background-color: var(--code-bg) !important; - color: var(--code-text) !important; - border-color: var(--border) !important; -} - -/* Footer */ -.footer { - color: var(--secondary-text) !important; -} -.footer a { - color: var(--accent) !important; -} -""" - -# JavaScript for theme toggling and initialization -js_code = """ - -""" - -# Language and model options -INPUT_LANGUAGES = ["Python", "JavaScript", "Java", "TypeScript", "Swift", "C#", "Ruby", "Go", "Rust", "PHP"] -OUTPUT_LANGUAGES = ["Python", "JavaScript", "Java", "TypeScript", "Swift", "C#", "Ruby", "Go", "Rust", "PHP", "Julia"] -MODELS = ["GPT", "Claude", "CodeLlama", "Llama", "Gemini"] -DOC_STYLES = ["standard", "detailed", "minimal"] - -# Build the interface -with gr.Blocks(css=custom_css, theme=gr.themes.Base()) as demo: - # Inject the JavaScript code - gr.HTML(js_code) - - # States for dynamic button text - input_lang_state = gr.State("Python") - output_lang_state = gr.State("JavaScript") - - # Header - with gr.Row(elem_classes="header-container"): - gr.HTML("

AI CodeXchange

") - # Theme selection radio and apply button - theme_radio = gr.Radio(["light", "dark"], label="Theme", elem_id="theme_radio_container", value="dark") - theme_button = gr.Button("Apply Theme") - - # Validation Messages - with gr.Accordion("Validation Messages", open=True): - with gr.Row(): - with gr.Column(): - input_code = gr.Code(language="python", label="Code In", lines=15, elem_classes="code-container") - with gr.Column(): - output_code = gr.Code(language="javascript", label="Converted Code", lines=15, elem_classes="code-container") - - # Configuration Options - with gr.Row(): - input_lang = gr.Dropdown(INPUT_LANGUAGES, label="Code In", value="Python") - output_lang = gr.Dropdown(OUTPUT_LANGUAGES, label="Code Out", value="JavaScript") - model = gr.Dropdown(MODELS, label="Model", value="GPT") - temperature = gr.Slider(minimum=0, maximum=1, step=0.1, value=0.7, label="Temperature") - - # Document Options - with gr.Row(): - document_check = gr.Checkbox(label="Document", value=True) - doc_style = gr.Dropdown(DOC_STYLES, label="Document Style", value="standard") - - # File Upload - with gr.Accordion("Upload", open=True): - file_upload = gr.File(label="Drop File Here - or - Click to Upload") - - # Action Buttons - with gr.Row(elem_classes="button-row"): - convert_btn = gr.Button("Convert", elem_classes="success") - clear_btn = gr.Button("Clear All", elem_classes="danger") - - # Run Buttons - with gr.Row(elem_classes="button-row"): - run_source = gr.Button("Run Source Code", elem_classes="secondary") - run_target = gr.Button("Run Target Code", elem_classes="secondary") - - # Results - with gr.Row(): - source_result = gr.Code(label="Source Code Result", language="python", lines=10, elem_classes="code-container") - target_result = gr.Code(label="Converted Code Result", language="javascript", lines=10, elem_classes="code-container") - - # Download - with gr.Accordion("Download", open=True): - with gr.Row(): - dl_source = gr.Button("Download Source Code") - dl_target = gr.Button("Download Converted Code") - - # Footer - with gr.Row(elem_classes="footer"): - gr.HTML("
Use via API
Built with Gradio
Settings
") - - # Theme toggle event - theme_button.click( - fn=None, - inputs=None, - outputs=[], - js=""" - var theme = document.querySelector('#theme_radio_container input:checked').value; - toggleTheme(theme); - """ - ) - - # Existing event handlers - def convert_code(input_code, in_lang, out_lang, model, temp, doc, doc_style): - return f"// Converted from {in_lang} to {out_lang} using {model}\n// Temperature: {temp}\n// Documentation: {doc} ({doc_style if doc else 'N/A'})" - - convert_btn.click(fn=convert_code, inputs=[input_code, input_lang_state, output_lang_state, model, temperature, document_check, doc_style], outputs=[output_code]) - - def update_convert_btn_text(in_lang, out_lang): - return f"Convert {in_lang} to {out_lang}", in_lang, out_lang - - input_lang.change(fn=update_convert_btn_text, inputs=[input_lang, output_lang], outputs=[convert_btn, input_lang_state, output_lang_state]) - output_lang.change(fn=update_convert_btn_text, inputs=[input_lang, output_lang], outputs=[convert_btn, input_lang_state, output_lang_state]) - - def update_run_btn_text(in_lang, out_lang): - return f"Run {in_lang}", f"Run {out_lang}" - - input_lang.change(fn=update_run_btn_text, inputs=[input_lang, output_lang], outputs=[run_source, run_target]) - output_lang.change(fn=update_run_btn_text, inputs=[input_lang, output_lang], outputs=[run_source, run_target]) - - def clear_all(): - return "", "", "", "" - - clear_btn.click(fn=clear_all, inputs=[], outputs=[input_code, output_code, source_result, target_result]) - - def run_code(code, lang): - return f"// Running {lang} code...\n// Results would appear here" - - run_source.click(fn=run_code, inputs=[input_code, input_lang_state], outputs=[source_result]) - run_target.click(fn=run_code, inputs=[output_code, output_lang_state], outputs=[target_result]) - - def handle_file_upload(file): - if file is None: - return "" - with open(file.name, "r") as f: - return f.read() - - file_upload.change(fn=handle_file_upload, inputs=[file_upload], outputs=[input_code]) - -if __name__ == "__main__": - demo.launch() \ No newline at end of file diff --git a/week4/community-contributions/ai_code_converter/pytest.ini b/week4/community-contributions/ai_code_converter/pytest.ini deleted file mode 100644 index fcf3c75..0000000 --- a/week4/community-contributions/ai_code_converter/pytest.ini +++ /dev/null @@ -1,6 +0,0 @@ -[pytest] -testpaths = tests -python_files = test_*.py -python_classes = Test* -python_functions = test_* -addopts = --cov=src/ai_code_converter --cov-report=term --cov-report=xml -v diff --git a/week4/community-contributions/ai_code_converter/requirements.txt b/week4/community-contributions/ai_code_converter/requirements.txt deleted file mode 100644 index 32ae06a..0000000 --- a/week4/community-contributions/ai_code_converter/requirements.txt +++ /dev/null @@ -1,81 +0,0 @@ -openai>=1.0.0 -anthropic>=0.3.0 -google-generativeai>=0.1.0 -python-dotenv>=1.0.0 -jinja2>=3.0.0 -aiofiles==23.2.1 -annotated-types==0.7.0 -anthropic==0.49.0 -anyio==4.8.0 -cachetools==5.5.1 -certifi==2025.1.31 -charset-normalizer==3.4.1 -click==8.1.8 -distro==1.9.0 -exceptiongroup==1.2.2 -fastapi==0.115.8 -ffmpy==0.5.0 -filelock==3.17.0 -fsspec==2025.2.0 -google-ai-generativelanguage==0.6.15 -google-api-core==2.24.1 -google-api-python-client==2.160.0 -google-auth==2.38.0 -google-auth-httplib2==0.2.0 -google-generativeai==0.8.4 -googleapis-common-protos==1.66.0 -gradio>=4.0.0 -gradio_client==1.7.0 -grpcio==1.70.0 -grpcio-status==1.70.0 -h11==0.14.0 -httpcore==1.0.7 -httplib2==0.22.0 -httpx==0.28.1 -huggingface-hub==0.28.1 -idna==3.10 -Jinja2==3.1.5 -jiter==0.8.2 -markdown-it-py==3.0.0 -MarkupSafe==2.1.5 -mdurl==0.1.2 -numpy==2.2.2 -openai==1.61.1 -orjson==3.10.15 -packaging==24.2 -pandas==2.2.3 -pillow==11.1.0 -proto-plus==1.26.0 -protobuf==5.29.3 -pyasn1==0.6.1 -pyasn1_modules==0.4.1 -pydantic==2.10.6 -pydantic-settings==2.7.1 -pydantic_core==2.27.2 -pydub==0.25.1 -Pygments==2.19.1 -pyparsing==3.2.1 -python-dateutil==2.9.0.post0 -python-dotenv==1.0.1 -python-multipart==0.0.20 -pytz==2025.1 -PyYAML==6.0.2 -requests==2.32.3 -rich==13.9.4 -rsa==4.9 -ruff==0.9.6 -safehttpx==0.1.6 -semantic-version==2.10.0 -shellingham==1.5.4 -six==1.17.0 -sniffio==1.3.1 -starlette==0.45.3 -tomlkit==0.13.2 -tqdm==4.67.1 -typer==0.15.1 -typing_extensions==4.12.2 -tzdata==2025.1 -uritemplate==4.1.1 -urllib3==2.3.0 -uvicorn==0.34.0 -websockets==14.2 diff --git a/week4/community-contributions/ai_code_converter/run-docker.sh b/week4/community-contributions/ai_code_converter/run-docker.sh deleted file mode 100755 index 1a058eb..0000000 --- a/week4/community-contributions/ai_code_converter/run-docker.sh +++ /dev/null @@ -1,318 +0,0 @@ -#!/bin/bash -# -# CodeXchange AI Docker Wrapper Script -# This script simplifies running the CodeXchange AI application using Docker. -# - -set -e - -# Color codes for terminal output -GREEN='\033[0;32m' -YELLOW='\033[0;33m' -RED='\033[0;31m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -# Script version -VERSION="1.0.0" - -# Default values -ENV_FILE=".env" -PORT=7860 -BUILD_ONLY=false -DETACHED=false -FOLLOW_LOGS=false -STOP=false -RESTART=false -DOWN=false -API_KEY_REMIND=false - -# Display script banner -display_banner() { - echo -e "${BLUE}" - echo "╔═══════════════════════════════════════════════════╗" - echo "║ CodeXchange AI - Docker Launcher ║" - echo "╚═══════════════════════════════════════════════════╝" - echo -e "${NC}" -} - -# Display help message -display_help() { - echo -e "Usage: $0 [OPTIONS]" - echo - echo "Options:" - echo " -b, --build Build the Docker image without running the container" - echo " -d, --detach Run container in detached mode (background)" - echo " -e, --env FILE Specify an environment file (default: .env)" - echo " -p, --port PORT Specify the port to expose (default: 7860)" - echo " -l, --logs Follow the container logs after starting" - echo " -s, --stop Stop the running container" - echo " -r, --restart Restart the container" - echo " -D, --down Stop and remove the container" - echo " -k, --keys Check for API keys and show setup instructions if missing" - echo " -h, --help Display this help message" - echo " -v, --version Display script version" - echo - echo "Examples:" - echo " $0 Build and run the container" - echo " $0 -d -p 8080 Run in detached mode on port 8080" - echo " $0 -b Build the image only" - echo " $0 -s Stop the running container" - echo -} - -# Check prerequisites -check_prerequisites() { - echo -e "${BLUE}Checking prerequisites...${NC}" - - # Check for Docker - if ! command -v docker &> /dev/null; then - echo -e "${RED}Error: Docker is not installed or not in PATH.${NC}" - echo "Please install Docker to continue: https://docs.docker.com/get-docker/" - exit 1 - fi - - # Check for docker-compose - if ! command -v docker-compose &> /dev/null; then - echo -e "${YELLOW}Warning: docker-compose not found. Checking for Docker Compose V2...${NC}" - if ! docker compose version &> /dev/null; then - echo -e "${RED}Error: Docker Compose not found.${NC}" - echo "Please install Docker Compose to continue: https://docs.docker.com/compose/install/" - exit 1 - else - echo -e "${GREEN}Docker Compose V2 found.${NC}" - COMPOSE_CMD="docker compose" - fi - else - COMPOSE_CMD="docker-compose" - fi - - echo -e "${GREEN}Prerequisites satisfied.${NC}" -} - -# Check for environment variables -check_env_file() { - # Check if the specified .env file exists - if [ ! -f "$ENV_FILE" ]; then - echo -e "${YELLOW}Warning: $ENV_FILE file not found.${NC}" - - # If it's the default .env and .env.example exists, offer to copy it - if [ "$ENV_FILE" = ".env" ] && [ -f ".env.example" ]; then - echo -e "Would you like to create a .env file from .env.example? (y/n)" - read -r response - if [[ "$response" =~ ^([yY][eE][sS]|[yY])$ ]]; then - cp .env.example .env - echo -e "${GREEN}.env file created from .env.example. Please edit it to add your API keys.${NC}" - API_KEY_REMIND=true - fi - else - echo -e "${YELLOW}Please create an environment file with your API keys.${NC}" - API_KEY_REMIND=true - fi - fi -} - -# Check for required API keys -check_api_keys() { - if [ "$API_KEY_REMIND" = true ] || [ "$1" = true ]; then - echo -e "${YELLOW}API Key Requirements:${NC}" - echo "The CodeXchange AI requires API keys for the following services:" - echo " - OPENAI_API_KEY (Required for OpenAI GPT models)" - echo " - ANTHROPIC_API_KEY (Required for Claude models)" - echo " - GOOGLE_API_KEY (Required for Google Gemini models)" - echo " - DEEPSEEK_API_KEY (Required for DeepSeek models)" - echo " - GROQ_API_KEY (Required for GROQ models)" - echo - echo -e "Please ensure these keys are set in your ${BLUE}$ENV_FILE${NC} file." - echo "You only need keys for the models you plan to use." - fi -} - -# Parse command line arguments -parse_arguments() { - while [[ $# -gt 0 ]]; do - case $1 in - -b|--build) - BUILD_ONLY=true - shift - ;; - -d|--detach) - DETACHED=true - shift - ;; - -e|--env) - ENV_FILE="$2" - shift 2 - ;; - -p|--port) - PORT="$2" - shift 2 - ;; - -l|--logs) - FOLLOW_LOGS=true - shift - ;; - -s|--stop) - STOP=true - shift - ;; - -r|--restart) - RESTART=true - shift - ;; - -D|--down) - DOWN=true - shift - ;; - -k|--keys) - check_api_keys true - exit 0 - ;; - -h|--help) - display_banner - display_help - exit 0 - ;; - -v|--version) - echo "CodeXchange AI Docker Wrapper v$VERSION" - exit 0 - ;; - *) - echo -e "${RED}Error: Unknown option $1${NC}" - display_help - exit 1 - ;; - esac - done -} - -# Build the Docker image -build_image() { - echo -e "${BLUE}Building the Docker image...${NC}" - - # Export PORT from the environment for docker-compose - export PORT=$PORT - - if [ -f "$ENV_FILE" ]; then - echo -e "${GREEN}Using environment file: $ENV_FILE${NC}" - OPTS="--env-file $ENV_FILE" - else - OPTS="" - fi - - # Build the image - $COMPOSE_CMD $OPTS build - - echo -e "${GREEN}Docker image built successfully.${NC}" -} - -# Run the container -run_container() { - echo -e "${BLUE}Starting the CodeXchange AI...${NC}" - - # Export PORT from the environment for docker-compose - export PORT=$PORT - - if [ -f "$ENV_FILE" ]; then - OPTS="--env-file $ENV_FILE" - else - OPTS="" - fi - - # Add detach flag if specified - if [ "$DETACHED" = true ]; then - OPTS="$OPTS -d" - fi - - # Run the container - $COMPOSE_CMD $OPTS up - - # Follow logs if detached and requested - if [ "$DETACHED" = true ] && [ "$FOLLOW_LOGS" = true ]; then - $COMPOSE_CMD logs -f - fi -} - -# Stop the container -stop_container() { - echo -e "${BLUE}Stopping the CodeXchange AI...${NC}" - $COMPOSE_CMD stop - echo -e "${GREEN}Container stopped.${NC}" -} - -# Restart the container -restart_container() { - echo -e "${BLUE}Restarting the CodeXchange AI...${NC}" - $COMPOSE_CMD restart - echo -e "${GREEN}Container restarted.${NC}" - - if [ "$FOLLOW_LOGS" = true ]; then - $COMPOSE_CMD logs -f - fi -} - -# Bring down the container -down_container() { - echo -e "${BLUE}Stopping and removing the CodeXchange AI container...${NC}" - $COMPOSE_CMD down - echo -e "${GREEN}Container stopped and removed.${NC}" -} - -# Display access information -display_access_info() { - echo -e "${GREEN}The CodeXchange AI is running!${NC}" - echo -e "Access the application at: ${BLUE}http://localhost:$PORT${NC}" - - # Check if port is custom and update info - if [ "$PORT" != "7860" ]; then - echo -e "Running on custom port: ${BLUE}$PORT${NC}" - fi - - # If running detached, provide instructions for logs - if [ "$DETACHED" = true ]; then - echo -e "Running in detached mode. Use ${YELLOW}$0 --logs${NC} to follow logs." - fi - - echo -e "To stop the application, press ${YELLOW}Ctrl+C${NC} or run ${YELLOW}$0 --stop${NC}" -} - -# Main function -main() { - display_banner - check_prerequisites - parse_arguments "$@" - - if [ "$DOWN" = true ]; then - down_container - exit 0 - fi - - if [ "$STOP" = true ]; then - stop_container - exit 0 - fi - - if [ "$RESTART" = true ]; then - restart_container - exit 0 - fi - - check_env_file - check_api_keys - - build_image - - if [ "$BUILD_ONLY" = false ]; then - run_container - - # Only display access info if not in detached mode or if following logs - if [ "$DETACHED" = false ] || [ "$FOLLOW_LOGS" = true ]; then - display_access_info - fi - else - echo -e "${GREEN}Build completed successfully. Use $0 to run the application.${NC}" - fi -} - -# Run main function with all arguments -main "$@" \ No newline at end of file diff --git a/week4/community-contributions/ai_code_converter/run.py b/week4/community-contributions/ai_code_converter/run.py deleted file mode 100644 index a7179fc..0000000 --- a/week4/community-contributions/ai_code_converter/run.py +++ /dev/null @@ -1,44 +0,0 @@ -"""Runner script for the CodeXchange AI.""" -import sys -from pathlib import Path -from setuptools import setup, find_packages - -# Add src to Python path -src_path = Path(__file__).parent -sys.path.append(str(src_path)) - -from src.ai_code_converter.main import main -from src.ai_code_converter.models.ai_streaming import AIModelStreamer -from src.ai_code_converter.core.code_execution import CodeExecutor -from src.ai_code_converter.core.language_detection import LanguageDetector -from src.ai_code_converter.core.file_utils import FileHandler -from src.ai_code_converter.config import ( - CUSTOM_CSS, - LANGUAGE_MAPPING, - MODELS, - SUPPORTED_LANGUAGES, - OPENAI_MODEL, - CLAUDE_MODEL, - DEEPSEEK_MODEL, - GEMINI_MODEL, - GROQ_MODEL -) - -if __name__ == "__main__": - main() - -setup( - name="ai_code_converter", - version="0.1.0", - packages=find_packages(where="src"), - package_dir={"": "src"}, - install_requires=[ - "gradio>=4.0.0", - "openai>=1.0.0", - "anthropic>=0.3.0", - "google-generativeai>=0.1.0", - "python-dotenv>=1.0.0", - "jinja2>=3.0.0" - ], - python_requires=">=3.8", -) \ No newline at end of file diff --git a/week4/community-contributions/ai_code_converter/screenshots/codeXchange_1.png b/week4/community-contributions/ai_code_converter/screenshots/codeXchange_1.png new file mode 100644 index 0000000000000000000000000000000000000000..2b801ab3fce95996c0451a362a48d03916c6ebc6 GIT binary patch literal 66426 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 literal 0 HcmV?d00001 diff --git a/week4/community-contributions/ai_code_converter/screenshots/codeXchange_2.png b/week4/community-contributions/ai_code_converter/screenshots/codeXchange_2.png new file mode 100644 index 0000000000000000000000000000000000000000..a95c2e30ccd99f549f5d0fdff1804e7cfc7d4ce4 GIT binary patch literal 26818 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

gr?1}s?o|6l|EnIRr5i1Z z7cV-|H;`oR89xo^_Lo+)u8WIGPsH|sPG~lM+-wN?Tn&OndGGbD^U!gUa(TC&!jVtM zOIz86forv8rJDY65NLEM^ICJ0FEHKK?)4YQu3R{=ysqp*{uAam8iuv`>y+d+xrGq) zWrgzskqdSE774rJa%xi`u!3Q(UVOGIxMh(s06^!o?$=lk1Ru$0fsi@IA4 z2)hQO$yMT{{;M0a&cn`4efUtw&#tuhn)FSA|6uUNGriSV>@61pEz)*J6dcG&%1jW+ zs%+O|VJ2N3c(y;C7(YVq$#tK`KSyygDIb01|9%C_vtMj*jA|u=>*R9kYW$Iqu~f7X z%!(jD7f+l<|88?w@Hi0J{!gNg!wq|VPa?#)G0jD>G+^^_AT~g?TyvJhT{r98#c85Y z$#a~4E9!iQI$Y%+z13h6imDOA7knZkU~k2E*v2)~B?n5Cn3^+W$aBm{ACz1%Q>Wec z-PPI(`*W;Pk|h`+o**?0#6HoEQi32#+|~W`@GEEU3hl#MZMZVRY+Y2wkNag>7o`@= zwtQ6quMj2|Tk?E1sRQ&aHt_nB{OPaW2}zcVzUaU1POt2&Lb%u_u($VK@X~@?(>)!{ zPaCFEqW~n629MG7+)HxtxDP4YaMerTMngCWm{Sl-R!h~+nHpK{dm2D`tY;`pl~mz- z?OjcG?>Mb6)cimFK0Nerf~vmj3W$OVn}+#V=p5!^Z=GtXw0)v(@xx!Ghy#%4XYt|Y z9;;m{jDncOAWq6Kb0|<{qOV?ZseKjVde)R>L1!dq?BIO@9Vc9mJKAEIb1BqnK;qNH+iL5n zoj!McF^kgH8^Ke}Rg>Y*Yex3Sv!~;`i}uEIQq^8C3v#%fPQ+DG_2J`~D6hTqifu5D ztTlpFPItq*>E=-z2y~ut+c>D%l`6KE>XS)X>xWG`#J;Wb$qtKL#oJ9T13Mo#uIHTm zG%fSM^uK$7;lBhyD*1B4mV6KQ;paNahq! zG3j+R6oSL;5LrbT^95JXi;)}=ISnDW&-{|z`W>^h6DSz4(42*7tLXcG{Kffy{lx%~T{uG9sYjh3*3WI4PXX zCS8YA+YM9kqZ&I}P-XpY1PK?64h2OCD%To#vbi6V0lA4X^Hh9vlk$kr(G~qc2-Jf} z^o<4ITYezxF;+x_JL8G*vE)FiqsG;<^@U1ITr$LR^6A{d^h(K=67-DqMq}PB*n)lDRikK$xy+R_)J*n-;Ekm z>`=2s^%+()Rbf(DC2i5S%$MoQqER_?Y;N_s^H0fqHXX$+@~W!ZMXetlrB?>3pR1#Z zuUz&uzxodr#!2(TS6Dl!VIECs1HpN4RLQnVj*aDI%?G*T5kTwQlok<+>!LdRHf;27 zU5K9~oF6bl>JLO)znh)&Z00h&zwd7%DK3#_=YHJQ<+3|(V_~f^(6D$mD=eI=SQFGT zP0`@+k<&{2`A5nzuMyLp4rt`et%eZZ{Qm5_KJmyOy>q4x{ZW8O&tcxA9wiay}QV#hiN=|YtR0!%FYeKFT^i32s@c2@3HL_PUv4s3pPE}1;d<6~MgP~qJs4>=HeEJ?11!$Se2 zUuII+FaHl)?;Ks(wteANoT^l8+qP}n728S0wr$%L+qP}nw%hYxM!;;o%~`gfRW#=r$*K8(6sIn4n>Sz)~+7t6Ifvxhy5FqfX#-!GXyuE!^m zVEf~%yTI0L%TrMz4M#q|&qYg&E%9xlmZqcXz#NEjtI^PvI3Q}?qgV;!)S;d&T;OZ^ zK4K2Bep-~I8E@g`OJKwGT6%4r(vJk{>=jCVWR8zZVQ{&WC`frpfSzIjvj+Yo$UAo2 zOdJ~_M}Nezp_hK?vRW4*FsC^GE)wD^EmkqLMWYhr8cT+A&(V>p7Dd|s*T84?;s=PP6Ziale;SZiiR8EjF1NS zm!%Cs6t+{FJ>o);w6Y5j!7}-i)EvlV2soZ8UOzrsmK$m_e(ifg!)Q|tdsYd%LDNG1 zQV4P+2x(NsqJaCl((7t>?VG>v`vfM@;yC@_$Kl_DBzSxygaOFr_&q=CdDDsdqT#;Y z!2blWRDq@2?rxHD-hL+;#>NmVD?xm678Nuo&BMht%of3!!`AuTe@_sI2WB9JJa0@H^xpMWJNwwXm^E=&%{VhT^-bh*P9R^vJ&=9jA**;s?(^u#_@~= zt9p-DU?VDA$D<|!kbY<0Obbf{{J_-_cm-PO5Rj>E@8@A0GG8SQE3fB zPyupE5E5DVjTPBUbMdc5cuE5diI5K(+(SCqph5jc)McG=A1^yuP*4zFZ#soN6XX=#*bmA-uAYtImk3nVxB>>lNGN@qz}vi?sVS$tV5UI*N-%k05}# zuO|2is1xPa3D;nT_+9e%wLl|MKN3&0CzDGw_8`s3)^I}2B~)*aHJKEV@8_hcsb~h3 z&CQL6-59G*mYOwhVxmg$EyD%0l&V7*?C$Unh(H>;S)pG^1?-5;OM`z-P7*b4WbkTD z8yicwaF_|gKi{iCJR3N5>KjfRfNbDOzVFkrNQ=(7+tZ;xG8;iDza9zUgVmyw_h)MH zGq<;FA6XCeSh69U5MpqQkqmsjNNYww>Sx>qm7T<9H#p!m9=G%w)_Y5Gx{F<18VATFQVAMPkbG^H+euE8R_|5IpxiKkv!Tyxbj` zzT93tM;r1SWmC3RCB$bp3U3z&W0{^ht9)XtHMU7Kq1-Nqx99)Kb-RQ%>uYzHs9#FO ze(6|s$OV>xu*>>j+I6y_k}qOFwa#9Tdk4`P+1T*y{Cp?MalB8r98P&uqQ6r~VR&r~L>DgliWFwDX^S~^W z{W!AkPKhS1gWVP*Ap11F?Zwp(ojbVsgCkD}d;i|CbiQ?-6S+qo9Er@(B{G zRq>5tUO@OwiY5MspqnMqLv~Zh+AtzXq5PRQa*s(&@#086JbNp1LG0BjbJuG5&mI4g zdXvjKpRMqSXEcnL$KQ6G#z(wJEna~wdAk~J>+BL-SR#ThGIRzLBVxrR&|T_O(U_&F zX1iSr0wk=smsYgnr zM>v<>Zi`J;{`61(>5ro&f(cB><**Z&`WvNKW42KL9*>4LpGBS%zL#>D$^C6X=TBN4 zP!D=9*-x7cjg+L%!NZ&osW1mb9?RYzb&6sb@lwwv1PJ6F&|NL?FGx>tg$_$C0*|Qm z`F*r_zyC;>QfzIi?Sn*MtpEl!a`%R!bHk8A`7#B&4_(18R@#B1?iGu9_yGEHy>1UC zFcb06?Auk>Sm+UVfJn7ZU$v@;b>c5c*S0+@nCqcz>q#wlEiWpR9*sD1}kvB>j)lb81V@6j78Mf!sY;H;>$(mhquu| zFW3pmTlncR$W&$#hwcB8*1O1kXeFjV*YXjk7|flhObMDQDgA-#=g>1=R}LIm%b1QK z*rujZ^YErus$=>`Z?JryM`;uq5W$N=?WP0jH#%0voqjg@d|rUm^VpTlv&D!crx?{I$QoHIAU7LHHKqY%@--LSi zJ+UIEVv3#29?RLuPgnfnk9^Uh0&u$Oy=7-EJ6gcc78K_U7&&L-o(TIddiv6S(}-XP zTFD~Wr?{v}p3W|WJHI#FzfH*_O?dtXJxc`ai0q5Sax+G*aE?~=9T+lF#+4}hMJ^0F zv9NL6#LNumnp}uzs#Xl7YH;4-@GFt5^(Y^eoO9rHD+?hpf`%_Yqt|NobDULdg6&3Y>o$UL4s7>lI zV?|)sJ*w#87H~OiUxOS>fR87|^$k|)K{)la*cDLwHF!7JUR4}wqb3~cGcuI>iU=*; zr;G^}K}Vdcv_pynb-F}xfUHXFlC}MQ?lym5!_IutqCg@hYs+@XtRy3nvCr0iA6)VF zCTz%$hrILjTEl3?KowspRV{avWna28yS_HQ|M-%Ls|yElJXs7;c}Ej|I4vu+h!(lO zNPazokI8-T4J*>ZHpwy|5B3W^+9?g=zE1LhK_@ zVyw)Tn5Flf|GwriKj1CdrY&)m^eTTzc#69E;bFq_=6cMWILd9G4Ow6)G9a3ueg z34oS=s>%g4BJ&YG84TfOzbWwk^al4ZJStWX770Yos6{jBh&TCBa%SD#T#7=TQ!K+s z-Egn`VZv%`Fqq8lLpoK{sW?>iK2_O(Es+?s;Y7t9(L zyK9F$)Gf$dd-)TGgWE{98Ryq^JBNlMWymTEf8 zb*;i09fP@2b@WKHl=p6$)#y1KVIZ*LHTw|h?! zW7unc!#C=`$sgJE(v?(QgsFw?9vb=yS|cPJ52ddDQhq~dep$7I=f=FNw#Z6E^N3+o zSA{ox*kxf*swYS)Ax&Pa7%-di&wcz~r{*uPf)XMy%hFGRrtR@m*%a*0!wF2YwZ@A5 z-CZd_Zn^Q;a5yo1%0x?Yk)6JBfY9fjJVdO4$!503>S$4({qtQsIm|stTQPp^njbW4 z3LYy6#86n;cTr= zrz*ki62o@Y{fh{L|7%WCMEXo9N-DVt9hl~j1T4`E*_PFty7qArUIeIu1bnGP@RnJ- z5R5m8PmYdS?{)P)DtR+>Q*)lhkIa;!k<2tH${g+VnX zPb}^Dll|4I-|Pyh#B%Q>tRU%di6sTV0+GV=9n2+A-i-`vahzonPU=xL z>AI8Y+RmF-8NbZr{igHrws&5V2e!8`AG%dG*8a$^O3x?L#izUYiNxr`<`3jv2b%2DWgF0Fmi#}NQQ=4w*386cukfy=)(AmTKes#GxWcRjQ^rjDEzzETk2dH$#g))8Lfs^Rs#L}+h$`4;R$lm zPCizhc<_d!w9#@=(vr7eQD=|z+$NwM?FL5i0~O2iN9mm zks1|i<@Wn?!W~4sPIyK;d0uCb!AzvLc5W`YZ_TPgW5#}h48_$|;kq`Slie-}#3%oL z1Z`-!0J^naQr;iAaJcebHo{M5K-51OmX%bmYgVc+-IG>srgCXGp_;6@LBILXZ*_S0 zVY68^z!M#}maJ)G3A4B7b>n%#Sa`Ze;D37MZ4yIjsdk4N2vUzK^?C1r>{hADW!2(p z4^DHqAk1^r;X${q4F93yUQm_BW6%_5k5%f?W2M~ntNcnWy9m}hM8MB&IbYU4}|BmkrM?~a5l{7q;&j>&Y|r$`F$ z_?31~S*+3#{d%thRKXbD*a0>xNRf2tQesOp3Ow50ad(SFr)qO~xhY84+z%E!6Bq@# zHtbUu0aJG+nY4&?eC}@N$y}}Z;oKeG!D-+$#{hYZR&1q& za9#ZTk4!b;nQY*6N0n+Me$7{dr4i>$ZdS0XPG;*^$$@wcvDj;Lk8ZEhG<~tzHqE4b z=}XnpblC5=R<~#DA+1uLX^ovXpjyjyu{78#)W6q6Y&50O?c@t4^c)eO#B}=>Gh5zu zN&I?{Iv#B7uur0p2ugPG%qk~<$-_Vj)#K;Nx=0%oJx^cu%N#rgO!AH&?v*T**=%ey zCAjMS+u7l=O(c1E+SA`5HbfSOx6jv%<&VB`n<-5zQ{TMonpZTlG%Fu4 zc@kr=wB+mtGgl6&rLCJF2=`;MNl5p2QA{?K=h>uy3JB!iW5TiF%q&LL-Y%~mfy4JRLqI^Av2_CF?EtuK>k@#R zE*DB}D|XfY>;ic3B`TmQmB07_;u+B^7V9Mo2EBd)UI1jRGNzFlCMW;%CwHNa8mxLR zr8$adLzsrkpX1M~y8xE!LS~KC#|Zyx{JxJ1`B$z6ldAo>5Bw+sJPnvT6PXW*{IwPN zzZ6b(0;iwx62mi~1m3dPT1ST!W+1{l4DWJ&k@(NarN7u+V;B$(mb+-Pq&bHm1yVCT zLTgVD@SChuX2D|D7VEY7wJV#ib~S}KaIENcBn@r-(8uX9QztbCDZTEMc1scDR zsVpB89h{5*cDO4Lz!B8AC%-+JpF6f5>5E?U_;srf-NJys&yqH8LeTMghl%7)+_+q_ znfZu<&4bHd^?BP0d8~3?l!*A3BOO*#`Cf zt56Koni+Keaj4?4|G?w#xx1WIIKC_`VL-qJEfXxDggVlHl)|HMl9H|a5YQWnQAZTV8(TkBuZbP`IH`-n^Em*i`ZIa8)6nCQ8l3k znc)Tya$861naxFfONRY*0kzuk40d}toe7X4g_<7AQ_nfyuxICO`1CCED5S*4ICB`1 zR5#F?C$ajN&Gve7=Z^h@w`d=|PjlX+BoaT6v?!+*eR0+KV`nSQBiG4V-tKW)V?2Dd zvu<@x=NeUOUMZcw__aX%%qFesMjCkPS{Z)KT5&)7C&3IN-*PJ7a|Jgw6jv3QAA>Er zS+gsK@+aG2)ussLu;9tvL)Xlh?qJ%zT_BbMDD(kLh*zC&Nv7y6;DE22`W?Soey47_ z>@Dao?T7yB!I)zLLt=o~vqJLNX%BxI0#eJ0hld#rr;Pole`!9h$dgD?e$)o3nwh%5ysRW_U8h#4l{Q6Z#M91Dy+{R6Pm>`OWXwMco z8?)$CZ`hV)g+fL(p#Va%#JD@9nE$xZuWXWSOyW%GGP*ylmh%ARdTzvVbF`G;$R7La z1GxXfL&mJ2^h)aEuw5`!{08asqPG^2Kb|Mzt#08a4aCB9t;T8~O;L(IT#IT?oFo3z zi~54>QWxmK1b23y>cf~1y$qGoiFofv$NQYce1(y!oBq84Q_EH6{pTOp6<)f~MeO(B zp^i-BpWZ1+Jg@M@(HfH8=Pz|?o_mNX-j7Fi9gQ^EY;%Yf$;uA9d7tQ;OMO{~(5iEU zycQ>XmN!RXEI}~X5{$-D#elitECd8KW@=#q+i*Nxdj>W@qyCQ{=G33dr3=Cn!GRY! z9q!JUx}IkhPsiK*9q|o2d0hVv%JWeR{a?urzQE@5o!w!}6E-F|7{VgMAt5%Fzw7;0 zkvcR*6cc1go$9j;nfgM&_WB>yPJ;xSZp3oYq1U}Rc~4iWup37zBe~>-t+ZtS6@UwLhLIB2UGathNgcDS$OB9NW3xt+q)$G z|6d7?xZ85!4>R}O_^(Y**QC2zqr_&NMW`l;%EH=R{IS2YJiZ;d?H$L!!y|85mWyTa zTQsv;9uwCGn3h9$meOFyq2^_ziVEpRA|3ncdVvpw4)$vvB@6ZlG~A0T32HMtRa zKqp!xz96+&F{V5xIDSJu{^@IurL|5@%H1*#`Upv~w|}ahmz+^XUbsjtUka|}lTDW8 z2z-QB9+7IQEcIQ(B!#G0r>Wglca;{H^J1kgdDFw*_k`M&2EK&;za?-eQt9RRMnyNX z1^hz7A)vUBuBo9G{jC@TiP9L&q>tI2lDy?C4a`@S>uk<^b}w=iP*AA#&o=|CywYft)u7 zt<(xDrmm8zTCi_M2LBqRtlIoBmtda&nZV~0$@oVV90p73<>ke)7g4(ddu)^F|DDzP zpR;{vUk~n~x7b*(xi;EG+sBIzkcgTOA@NPVw)*<7+O|eP~#XF+|fA z_b;z5k}t>pNH>kijKS5_b!K-U{1{NF>;Dqt`RYfMl?Q;H23!Hrj2)k^W6b*Fzv2G( zehlS0LH_-zgb0|?m`eaKwxP$s=EL_58J)opt-)+Asq^bolfzaQm)Uw&zfuh+a8(t3k!-(NBDBjm_RK)O&WYT|Mt~vFu ziidF~+4Dc>K_GzA%?GGfGXV7bxTYppFi=p0+w;NxjAB?`kF8(wuK$}KUx?z1rIu`P zrsn+LhqfW|jXY_alnr2RxIHYOlG9ZaySlk8UWkva{JjwQD)@6&NANtzGcvWzq4|F2 z{QIAjkO8l(tRSJHQvN9}E>_gj!*8(OD9W1{ar^gIs>y*_SP{Wy+CCmb=gU-a1=2#D zw>-z_w%aaS(-SiM(b3Vd>@GYd#Kq~?tNwtfk`iE-k}-;^7SS+03IR^AO2Y$tb7LyS zQqZaf_@rL7&{&}gwSQ{nFxHWO|9ZUKi1+&Xh^7Es@lL4H%+r~N`7z~1QAbwGVmmzP z4)BEZSe4Ssi}Qu~?zZnn+7V?+oXYYq#*xq;94+pll#w-l(pny{i7gBJvX%}4 zP>=wNkN;gJn@1QBI+E-5a4J7n#^G#RFspm;0FaT zEGxg)0m58@rd4%3`Rh!k3`O67F?hWM8u&iW<)Ayb!qBzR`jpl1Xx7QOBGA0L1 zt*$Jsg}&H}zwa?Xd!Q5~QcVK`GHu87RaX@f{6dk8Cm-85CG&54`vE;;j~C>~jOu*WS5MrTW|b43f>u@Z!soownVVTSd#yabP`clW zePpW5#4fyRU|l?z%Y_O=n(d9!Mz0`=ex>+bC0hS#2Qg~Q<1*EhB)&5b=Z>+u8U@^; zRR3ygc{X79z+~qIRF=(DQoQr+V}@&aX#3Wx%ikx*xwEdQu?2K>j@>M!gTJA>!wa> zW_v4BR)37r<1=os`dvAoJsGBwMC=Ya0cjWy@4XWNdEAfnwZ5#&i%Qbt@W%bQjrF8_ z4dLkb4AC+idp!3>pH*ZnF*7b}QyluKjTsI%6F=^U`=)$*RfqeXMPQCNIkIs})Elm1 zFV%o8Sy=Yzb>4ah^10d*D+?P=H+R3ZoZJoob4B{)NjpNx+e}~@-O$BW=V)jl>MZ90{ zT5!YBJR15X1}8b8T=QfrF&BugrM{&YwkpJ(^T7jx$W%ahBgBzGQxK8vVL5#w{*Zot%F) zj@Egk{kwO?5fP~EG%ZOaKNrxUq@bikbg^8MsVjhiFk2?k>O5O=Jm!uKYUkpanZ&_F zlXjSa|2R)O9d-GpBKftZ%(qp|UJ@Q3CPRF3e9)lr`eDmMY;qF4rB>Cz(YY9T4(VCJ zu0kd@Qf6^yCZfZg+FDn}joq1(AM7=pPht&Fze2K{hv>}O#to0XpUg@O43H-dXO!&! zVF6%R%8os$Z;`{8S&PEn*B2LiO%y|TPyf*2i;)&{Ch@s^xF3qlP@Z3?GOhHwq|oT( z&F0bddtA45KSj);W$kFvfNL@FTAD4Oabijt!8c??6x5v__diagHxFz7CB}V1rK4K?N{MA+4@WPx=Fi+3w-YVMEf)uir znIMFos#Q>0e*Fqt>b`STi!Xom?9AD^x1cTeb2$pt3ojDRV(Qxy1Pm75yTaXVm>BtT zf#lv{g5wJTwsvQVN83H|)}-(i0ZI~}L2v*;G@n{B?)M?2@30YFuAM=x9vsxizL8PL zEuIehd3bQ^v-gDp*j=<8Z)WOs=07B0`!K3!_) zBBj2|UpC-h{@;ua4?J2uwll74!zIyWAlD~?#r5wKI@*#mMtmk@2nbRBd8w*&&Z!A8 zWc%>%AHnM%;SdwokfxL&M?F=WMzg0PR)d?AX>1*@?i{VVjCSR^@eU4EEr*hT`4g=V z;N#`duUMTcJ-+~#+jV{y>Zplc`?^JS<6c*-=>Kw>rbFEI~5r!=KpC z=g_}zuZtE9t{N8;cC@YBVK-h}i$t;ssN=1slB~#YuB~rz9nw>xzjAKgy*_2&x4ikJ z1;L?agD}N^4R#1q4({V@%B70Au%Hq;q0`Yh$2hX{Cn#U$Y7Y)T?JAv!wvhg>Ov8h( zj`Epc_D^bsg1MxC+$PPYf;Y|sART*yZTrAKE-THrVPR#>w>r5mR*^}p@zrTFuKN05 z(@?1GcybEE_(VE4%1G7M2%t)E;#r1G0pwRRLA87ca$REODin%UkD{0;5;#OOY@-_C zvl-B_RZ97r!Dxf;VCCxb-=TN1?g9nYf;7XM4i)>r0aEq(u|S%PDNdqW-Pdrj)s*RR zaxnHWGKr3z$5tAj|L`bg8j__b{7v9Jiv_w}1(RX&qwBpIZ;bZYs_eY@fe&kD%JasexTxr^I*3NtAd({$ zH^{ohYW;Vu4s&fNd#^VPBt7YVpxYBP0aRDLAHM2K+dYB+-r%mzV))V^ z+~{l?LQdE7zbC=@i7W)R z-((iezuT%)m79>5VTf0?G#6KD*90V`kr&`ZYPnOVlrv#qwc~b5_aZN-%9g_M@Tq^a zC^HRy2qF2+-tUnSLH~IDkhio?%k6@Ca!0dd4p{{U^OA{_J5YfjI_yzlD$@`+OXfk% zKXKx&iG{^}YGNJBmYVm8Lo1S7a^m%8{&9u3Vt!)}QsXhZJlAbDvfnw2Vf^+Pxwurq zs-}6-@`J_`&h4?{WEPPqtL%+-F+KggUIne;m&unXpt0dQhGNXMNI7}HV08?$M>B*p zRlTPkE|%cBG(hnDmzb900D2!$a{v2)0!W81 z#YN9V^9Cv+IvVw8BE1O?1C?ky@GF40#f>?0FfyGFkXM0lU^3;Ev_`RJn~OiztUX^V zqgqdAI}68Nr-_;uxce3659Yu#@Gj6$0(K@E!eLJFg&MPFHTZav~Dj~<@Ws_rVG4~x-O3A7s<<;)pd(?;vCoXcsQ$v0uk5jo3 z6+yCb8cK4OTCAuy4tWj^S689p{QX))9*~Cn`(YLkhL9JniHY%n3TROn1!srIl0=Qf z)}K)?r*_ul@45spbol42Pu%9rht{=SJ6*@er})Fsf*>Yg-?hf<9F^NsDu1ff$wjw*bfNyf&Si!m;3K;?Jq6y)21gDtCvT% zEYSh9xbIJoHB&h??yY%8cewkE9F(2Yez(#!G8CgkzF;fM zSEp^^E4lD=o-5h5Q3o%EGfJ+kR2E;kwP^RF#^$g&;e$z)OZyjd3No zAsWtOOi*>p4O)14F|)3lq` zlpNS^ru_;s?EMIp%{vIIj^sz8MO)fRaSlf3c}k0hmL!=6KcLVUG1n zhoxJx91N?(kl~rke-NLqnWOo$LWHn4yh5;yXo-}X$f8vy0|q%a1tsrhY+I2n+0NhY z9XV=lB^Sq0mgb$k?}`1;>BiIDxo-Um0o7deMoSci^G+B2`xlpuEnmbNxr$oWzpI{) zKo=e;Jw5dAo`H*NK!6#71`p^%wPCBtFM6I&;K0BOLk9L3ZI2t*Jx!Hpe%nv|i`hkW znBH>u0wQ`XSv+k6;QKQN=nDg@B#PxIq^W_XjQBu%Ri9Uj4z$+YRS4$gb`g7+to|I-Hhv5Nwj+BA|f>ukM)Laoa-&pdOZi(?l z_u5*zNHmTxG$u=d)mo!ECBO83(+wb80bVz^? z*zC6PKo!L~YSZ0#ZYXYOg-H(0>6eg5!X;udS08aONX8)sr%*5vlA|t)duY7{Ue4Rx znOWkPO9WHDU6Qi$dc&`qY!z3HCbDT8_IC5Yx` zSAAC~JU}9eKRDj#Hn&4lU{TaZTNGe0m}#@3TehjxhTLs^P!(|nK_^BGb|F11@w?}K z`Mrf^UYH)@F+=NU2sz0whUO<=K!udvpm{FWFRpvoelWy)?lE`TaUMpk)tgCgdE7Be zXY=?^W`r`S7W6a2!oo^OwzPCaj9zdkL9L@m{2b>;C8+=Y2qXEAHEKni1%|bTTdY& zAd6w@9jdWiR+*d-a|Pn@-(96qX|;s_;n>KY?O&hzv9_DdeX_hCDKeR}Ht%RUv}bl^ z-fyF;lb|ej!Msf^N)l(;vo)D7*D}sIbDHg zE9HeRNYxq$zPjVz#p3Bl6h`^hOeN`qkAA?!%%x2@=0-B3m`cId52@*jaM&ozv)c>7 z9Z+krT94hMf!YG7z2z5}TE+miG!$>po9Pg(g8Mr9M5#QB@WGdzAVlIY1h10tI+UGX z6Y$;WCKW$oavi z>i&J3U)A?KsgzsYW%LheFW$t~7XY!|pVl}ueei=0z1%vLeh+@N%^5bn2dEcV|eRA~TY>bAjM!sxOH8d~OI&hYn{QVnNQQ;LJ zUFu#4f*pCr-uuA5QpZ#BU?RP=irD*~z;0#2Zf|e^=l2gd&~sc0LeHNQ@kWaOhzHg* zz<8dEpghB!osj^xkaq=g*>%TYr4v&pVevJ6{C{4W#QE@44P%(N(ZMTJk-&54#ST7p zRn5ywO>hVi>O^lc`0PSo)Y)MMf>ki}_Hcs9S{3IXReS*-kJ(>2xWZEXUqb@eHqqFlK_VZ7Gnra-agqw=U zd7kK*#O#@sh^^D%_VD}^)B&kYSVIw2bnA|vJr}&cpAhk?*6y&7v9rR)#-(O#c!EAgvD)xHZSt+Nu80jg$ibQ#;$u zc6;PxB@K9f=8p1r*4EYpFYgez?V=+iBloKoHoqN)Y5;LNxXMfor#6i`T|5r^!}Onh zTdBQ$gk+p;F3nzZ@Bp(yZmF_o|9sttMyqu^_PAVB%o2b(YA~J2I9;k5;)jfyJ()9+ zN~gMGN3jN!izJaE)F>-mA1aSW(;e=tVd3G3FK?skdd$d{i{sI+Pgjy>OJ9;FCj;@C zs&jCo3lr6Pf@Bn@<D7#bH~xpP$rheB!>{ zV9)D60X>}1%x1e|pSDex1m1u#l6NX#(09NX34Ff`z?0tfc)o^5!uG5j9jR?uuSN60 z$F&k$xt+J75GTI$_g3%!>K<70(No;rl7zi++ ziV8wQoR?O6Pb0`T+^aRKHp%sXDqB z(04SIjk+|M6`2Q^hFU;pfYd+Q+mi-_oXnTRs+I~(kCoy3LT2#d5{hm30DX}0@bJI0 z`WXMV-5-`GNf$lIa=*+2q{+v(9dHiRsdczfJD$vn?+;K|Se;)snlHr3WN|4kQY>iD z(BBXs_Cz-LK>|yvs-l)nwQKa0`DW??g^~q$zu%}rV{J@eBWz}~SZT~`c~@2Zj+0-N z;s-@UjF+HPDZZlBxX&7R=>X(cpj@roiZC4Mc3gzFBeWPb38Vbx>E@ag)#UO%J2e0c z_@amcF-VJUd?L8hFa`xbd#rqMH?`OZ1>WIw2^`X=0wljNc{~rNXVDC}Nd4ZgKHK*{mSWh3_tbwMr0#?;~h4v4}6ol~fX$7qc&Ux1I)c%CU$gqYzO z&!N?7B{~FVq?i1_V`paK07U1&V?w4t41&-<{j}vKD&S1EBHHvd7>Wu%a=2J!j^(ZK z{_-LtU_rxRDxqCnZ?J%Uk{1K)Sa9~BG=QWExP2ft!LkC=0zlXw?O?0X7>mDay6N;j z#HMrmkqZwxdTPDT`c0SFEF=glPLRpUvsElh2&Gl(^e}8~!<1UPnC``Oa6`4)>-LQ+p=Htt>5|UEKC9M!C%ONE<5(nPER6KT;6* z3p3L*SryTP|7ke)B>??H?K3_Q9?I$gi3*s0qCE^OZM#;b#E`h)@*5#RakRFs^i$&B z3lco-N3q^ybx3u3u-5CdCxFC8&)!!mP9p}t3xq*Fi3? z-Tl=55L#7GExZ|EY9MI|y3N1A8sifb6Lfr+c z5zNLLOL==ZW7q$5*~Bw4MV;DPZJ+`c2Xn$RuB@!&x+>B~aY6O*B>(}xM?65{B|T`p zA@$?!`H7nT%?;YJ6%A^1`(t!sAw5uU4tPqmY=MF1FuTF7t!QXjxBISNjSd#84^%UM zhK?SKIBN~?6b94AvxkF=77XqkaE4#*mU`|F&5MMc9oGQMAq2AP;lp2H4A7xL_93%IlsQYXJ!rUFK)! z@B96KyW=_LaMdlw)2nn_UT@OcaLkCDYkfhH)etH&G+s5hB{%MI=8h54eV zrE;nG4r!;hj6;GlJ(&rA;EOIco)jz4y(Z z2L$+ifMC_dAJ|A5Ob8kHg6K3b_zcl>4{cQ}Mls~3&E!Nq+z-V|R}k!qheeryF#qQk zh^bBP2Y(k_%&+#PnDg_q-hK2btYy=NLfQyw&#i0i9c+ljXCB`WjPNDsib^xyq)ja@ zT8}Gy7w?<5-e+|5rbq+RrR(-jKyb*-MxQ-!QA|5m#ZDWfWkgqao=+-ncV7DyLa|`aP!7vz8A%IVPQp%Cokg(5`#&!eRJT#%*T( z;ZoVV7l$pM4Z4rBK*gfXG9O4;ds$cmC{qxfD!1dG-OhRayoURshF{oPj5ZzWk|spK z)PEa_3%-1brr-}m;<)P4GmW|oYJ7u3I@@5{+?yj-BSitPVD^qu^%Sq&GR|UR&NVTE zy`XJ#DK=wCzA9~fyU3gmMSg+rna(nXh?FV7ZlPb(F=$Y)OpE)z9(Ar1^v;*)g<>#FAtO=C)vgWTA-6;fdQzrg>IYt1zVo7)9utCu0< z`P5VHwc1@`8`QAPdIRhPpnL}yQ62~F0hTq&K3sq%zMsUJDTt87OpNYL#0}~d>@#s# zV1odLybk{8;wjlj%A3%3R7nXLNexeL018PtTs^?-02_lsy;t)y+(}D9y;#fwS`BbQ zI&3Qf-u+%4k&QmO>9|A$@Ru_2zVOaQ>wtP7I$f4@cQG+ARDc!dc&>SH*lfkxA@K3J zP2ZpC^B_@<@D>O6Iq2zUK;L$!R_m?i>|^k^_1f`)x$^T3ehSk&!J!CW!}yryYMUvn zK0$;26gun=jr*)Q{Ohr@`{vu|gLv5B-IcYihG7tnGST0TB+c_4%gDg+bKs_OJ5xTZ za3?6y|F#o%xm?c6n{DfP)3?2+9<5*ICCk@m2MZYLyXg!5vlb)*WN#G1=#$J>rUUXr zWOhY~;eFrz6JojeH0+_Z)gC{3oxI|$+`q>%1RyBL<&2+z>Lmftn3C|p@_OC7tcnGT zMknULYclDf(R$D5cHI-~O7C7fYEPq{Qa3?kAP<7b@~p?N_-$el5S4dwy+f$-=2HZT z!9tZ(Jq!#48tUgtDwPSqx(<5T6c6MLxzrS&C-`5J`GHZs*W;PO9>%Nf3)sr4OZojo ziw9k2)jr?B3V`=4*J{(Idd>x-Bgt(t$f=c6L)Z zo`hNxI-SnN?uj;iy6niK-J^#^o0c_&%XK_nZIBE-tlnnV#(-LQl&Bj2nHgAbBUm zcR51hx{*m1sf=WvPkL3_MBIDB10H>ge8a;HBf&c0^BR)?mw4;+<=WX$>J%K@qg#q@ za7MjCVOoFo3aVLD!&exz!odB1$a?FjD!c9rRNzR*p^*+jI+SjZ?oJ5_LAty108-K^ zA>BxKOLup7cXJ5(@XGTN45E*~{TOxFc=E#rv3s6=@}XJQxh2K-t{n=W zxxK+$rty$h5Z{9vc|B8(VA61=U&{A?glQPQ-{EH$Ww7dB18+OaNZw;>D?v}6A%KGk zRS5+j^y{_xm?-_L&t4FpcWZ0G1FYx0z0~+zm4TzP`ik%`bcuDJ$s$Eph@54>NKA{J z>jXRpOPcoC^nQSOL-}^IO$F5m-8kdP*N2*B{tcKS@n#3_W6De>4NEZ^3nEh%%{GV!<*wsvR zm`T310^tAtpp4A`s*pqUh)zZ{?wOfUA5cu_(97AD(#wR&;7d;naY;ja3xHG=S(8#Y z4yu>q@7pz)_4LDSvk9vPTzcZup|+9jLITx{l;ukq1k9y9 zg^CbM%HNYzA*`@Qg#gpn5MuPeT$gAcWLKGO^u043J0DjggV|RnAa|M=(kmRi!HU^p zSinAN^Xbw&_&wfOWbsx%)n(YlL>iN~aV%RLp7*S&V$81r3hH^i`89hTs>LZi8d2qA zV;=bl+%h)rN8=5IFO#9yXd0_T!Yg%=H>wD;0i=w09O)?OFYyf8vQ?eO(BjPnhp&s6 zSDi6Eu*q&)sEt(SMg?DHXWLHRHxU4s9e2gh0z4@qgt~lqSggbWZMHr;@Se)F`HUaW znmsNjou1;~n}q`xDlR1@ShqyQ*A@g9d6C);r1rU51JYw^tw$D z$p(HqdUnAdu3t*wmq8_Frl!;n+NG?Qs?q+**R^N_ZWQ9ExO<^8-b^Y+UX>C5`WX_% z|H%xfHUsFYdb!Q&@9_Fi*`~Z~YyQLi+C{K>a?ipqKTt z+hUtyGYjdJP(kIKK`(SNRN=V&Tc*M|C6xcDb|v-r&3)jo2v`Pl?d`8{NJCFdE1;_% zMXcFNv2$;Glzj`>vwD)hx2u)-iGV3@S^jI{`=&(t48im+KGO7||LO1LkiB|gmjp?F zEI7Z(LyM*a{vv@mYV43khaM zMG<$%WAXnWZ9UBkW^yXiB_o$&gLZF!SFO`n6VO}rmm30dO}5EN`&Ng#^{yHJ6L8`A z0wc;1_2!QjYVB6bsPS1%A*Ze-NXo}EWo7XDDn^w>>puJutX~Et)*euyA^DS9mg&rK zM=Pymrv)Ofs71=yD-F!@6I_?aOJM+^ zXW}-uz2k%rr(`2~zP~p?f`g81xg2fr7k{W=Ks{Q1bkKq)Z(k?0+v+!#I~rF?QQhQXyV*?}OENi!DAaiTnH%I3 zu;_Yd;zHTT<^L-Uwf?vizD4lU4SrTq`u=r)5^ndn@dVpwG`-psP==F;PcMj`rn&6p zsLz$@6Tahl$<}&jF);j|sYh|w?(Tg*(<*YYswKBvF*6<#C5LHCKHo{SR8z5dBAGyc`+9 zFV56*n+jhD{h@ze&JW1$A}1jsp{g$$8dgV;33Mg@UP#{Wm`XrGce9oE?|HgN_$SoJ z`4FCMaC0=%pHQec@-!MAus01Fc|BOZ4kzXlixv95JTC)_f>~`dYBx)?Ib*`Pr8klV zgz=1TVFrsxnl{a^aQRP0T=k|`hvHsav3;DQXEpO5*x;F)NIAIpI)&amZLMBKv}Nje zsbGylBu)Rb_GL=EJLO)7I1QL(9|M38-CGwzPMh*s>iKU!q+}beaKlnJC&U3$xpjUN z#Q_&*qf)}dm+#V;-u*HSg5uuK0d^lQ`N_Tbf|XXwLmuVw|IKFsTTb7c=Qv@KWWk0f zE1d6jcHg)61(Pst2TplYzM=R1*7QfnGHC>osWxSEEuQY#-rw)%`p_8=KcU6tD*rF| zH-m##UH_0T=EUxcv#-fi*bnixXq<0Nl zkd~U?k`P%(bbdP!o8lVhF#*zQ}Kl|uDdnR^vQd7=v-T!A!0gO_~LdKKM zOO70_?hobON$IchRos^EdMV_~?eB2b=-}TyG|{z#kkb{Mtg+u5`rp+>?qi(}c)pW*^E1+F-TZ_xKZ5YD zB+2U?kDG(}`1ojOX{Bfw`KvY6lQ!m~^+wub0bg3)^L@lltPm|K^XhmV7bP)}PtVv) zb8s$dHg6bJcRXWGZA|$tTKiW#U^V~RH<@?_=xNB`-2`FPoN9i4Pk>vady9;75G4=J z8D3gS8=shvweGY3{~LQUzQL;?ydh*WLl#dV6g+9ZiD+-(zeW0E`c;@#U@H=QX2qw*Siy(*=3xT;cC7G`lSo&YW}}NqkggRCmp8 zXQezketyM7Z_@8+gYr2UWdBz@`uE*p11VHSHmhwytX_|x``eA~07xc0p>o@^p~-M6 z4nORlVK0&@yMGA1rZNoHYe3FAZv?N-2UXH zNM`oXVe(yD#Pc_5aw9-ioKa!;8|UElbL17T=Wf@DUg&}6PSe1}vDDYGcdzGU0dU4-w4OPs!ZNUA@Oh&H_aX`b5a5Pv#Z_+f zIFAxi)`%)Iw&R>&-uemQbJ)zw39_H?r(?e4O^^nJDXHS z!(4@h1rpLPrm-U%yse($`6OqZ;*(Ent}4A3)yn3(n}sJ`SAc$}uh!lgzhYI}7WKoJ zvNsd%Zf62QZ*4)1%`F_|BM4KB4Y%;|>ca!ny z8RE57mO%1?`zUpRY=$y^;rT;z_T7p@SynqWMJn;YP? zr2?0uta5ZL;bPzLC?8?t&i}L-d3tpz(nrjKl?h)jnCFagFg95 zunHLV7*3XFF4I5H=y~Usm#1yvM}A$KKdl{c7|coIFy9s@;&IY<#$Xc_OUFjoS2Q`C z9Xl-YQhQ`7L78}{UExo!{8eTj(aDfoOP?Nz&z36rGo~)m=PNFwhVBL_zEc9DPX5;k zD!=mM74M9GzXOMpWm(W>qaEek`mCjGcEf!|w}vqRkXC~3+yCtOAvg&Tgl}`Rv;-~j zX@i9}*v-!?XxgPCdBm?#Q6~=yX1K<`G%zO&%1L72*X*bNjx+C;%oz6$jgwWOY+V6b z$(;44+&Q76@c42vsQS=ma)apNvHpAcG(sYqbL^J$qiN2|lpz^h%v)^0ak-jH&TQC2 zad=-I+_(CQ9cK(qrqib<;x$+#?|nnkDbd(hl}$8wg{eYaZAW;Q7S zz6Cm~$_LXWU3I90oNs$5z5f)C)iAzUM+5UL%iJtdrbtXbkZ~n<%?Z$pG0_C&{tnTc z#e*`f7({s70@N5}R=#+`yu7?!COQvm>+6H~EyOZTi!Kqzes~T?`1BDBLsK&|!N4Uv zH(AN@8utFzL%X98qu}P_V={c})i0B5%?GDcAjXE7#SYxHJDO>ui1rrP-13={O~4%b zUh^jF6)&L<1vPY$%%HxnZf>wK7vX-Ujwj2ii?C}#X~IrRujaZ~sywFwN#(S|^&yuK zNRg3=Nj{41oAGRU`}*?nk~^r+e7ULKl$JW5(wtl8_1M5*lNb?q74A%?C{TPdnTti1 z%Rh=mvm+Rxh~OCleSYliJkFjMVDt&I9M?MDb2y;ZMCSzP{ZeBDgOrq_n~OG=Q(m8U zRf&c@-;CDX6r(&@mIq~4TDnbrt$Mv3EXr=Oi6_|<6?&Q=|H#!~du6T1OZuGRkw({ud=?V2S>b1}peq4YJ$3KM;vU9Q`tqK&6zSbTxOUSBzn4)cCD3q^7kLW4I{|#QP2$5d=4h2Gw$Dt ze%@bDr0cWW^_+?6RH49bjC?%Amu@rndN9j^B2aP{(Axy$BFyJ@21mjx_ok*2hM@>v zfYuGHt>3YRkeY>rgvgkwbm*OS1cDTC0}w>u@EIXmB{**nx)3hB9CUScyFE}cjYY67 z?yn9)UO4$(EAtuq&8zQ>$^A&fNgE~XjcA#n!b7>=F@7XKI3kcy|Ez>KL;)cMQB{l@ z31=jMDjXcA%+-EJl2DXx1pC2AR2Q&*9oLAG4TN9kXDu03S{DEr`{X^(CKwoQbXDx3 zNk3^MM=FsdcLSU!UEV)eQ-0tdXe!dB;C$2Y6;?TIq%K`N`b5O>T06bvkkhp(`75%<{zLsnRP+)BOXEIVm&S26UP^q|R zS|%`i!mh!OP&eu~#NF`-j#b|u4h{Go2=CpV#Z0z%?zMgtDi$2fD1bqLu7^a*V|jVD z!BPEeD+2^j!r8CCBS7`}!lFI9<@(u@($WmlV7FiR=#jviv8KH5@CNUM-rfoMtJO%P zA&#A&n^6~b!j)OCEc?~1&C%r+j~aDYU|xLmd1E+~o(_5kZ)s#>V{-k#BhTY}jFYUe zX`u%L*1`yNWDij`zi8J?@R_)pMP z#x=RZCBbhodzanJH;0@=IHOB?T(B_d7>TIlZBETm-byvwHfzP4Li zr>Mp3{6#zH;&7cfsK ztUfO{7eSz9d>hsd`d5!?ks8uE6gdnEK6HaV3d`5=BR^!}Mkhe*fbL;DnUC%_>ys~& zWS2KuOok^T=+g?33O%^>p3#XBvB5T75|jgUAC8^!@|1oJ`WEmGFqS`sm&b_x%BjD^ zQybFaksPBD&aPw}EmRs4kQ~fzUO~q^qx(gX^$q}Zwy2}&p@AV2#LyH;$R=~_<0$@R z))_Q*M*5+h2p%d(+lRI{Bk-WRves#c*CyJZx8g*mf5Gby1BaTWoF{jN zs_|Yn?fw;@6_o;@Cr&=S=6MKw^AeB{36MG;muaTNUl&muIkL=rXTizFEy+JKDwn+R zt&T@-@j9Waa$C_tjO4dKZsdeT513Z?-i|Tai-h5eANeQt_BC9BKyWFWLXGjz9cQ1Q8UGhDtxD46a{aYP8b%v3`6MRnDIjy_BCu zBamT>JDBDl!+}&-ZWmzGV+ak)zMkh@)#v^)Vb`;RofK*7=}Hd}3bUyH;4lu16mx(| zp`ITDCfkzoMBSjbdYpy*R@$|+hbcmt`IG{yu=DUt5rp4(L1Jf#^Hxdh@a6DD;z%Wu zd*y+3%6wP$>yF%LpXv(wxRl^MUt?6ME~oVJQNvgcCfwa}EqQHvuCAvUn;D|3^SR8E6^~mfw~UR)+nH9B z3X`4=?Oy2ROrk|w(u8--9$kS@9*)=;r$GFgW0_?lx6uOC6^I)(E@t6Kl2Hi>_Jf-P zkgQm5leM#JX-*Bq-bec^`wodWE^@?tdj8RT;S-rP|9mScLFNlmB151Q+-(jZACFyr z4TY`BN!JFlWQ~ z!`Q^+c=3EGZSe7!>t^v(LWo^`og1DqZ&X>=N6y~dlSHjFQWuJW zp^c}n&iN6tprzG%K+2*4ubEwnsZNT{KQkGj}`{po3 zVz4Ii(rUiZR&q^tnE3TrkbIBQnU6x@w4+af`=Qbru9`2#NO~5-N|Utl!HSUa0b7jW zlsm6rI~)FJjr9tyt?8+cMA5P#_$cXuL5O$i@QL}gN~x$WrF67+y;PO|3@!c4qt!gm z4gq4ZN}7lN0Dhj9CtAMB$8(FvbxWFTw;A8>BOJ_=RnKbzCS)Z0y@kXi{3pGkudA$= zBUSt#z$(?HvEZj$a|b@(8MEZa_ec%)lb3zoea|0wIN&)Z)r&?Ha6&5_9HxF$9vkrC zM8e??|5U})`xz#Vrl7Bq=B;J;RAT6l%6gQnIWoa}9p z6AMDG-I(R>;&O?@m)~w&&7T~2LQTh|`1Kkc(uYr0hL0(~I;@RaHQ;!Mcu2u1j@N5r zZiI6)tQe{@X53S88(lsc`9ia z+4!Jk%)#x|tXwSoI$XC3gGHPtt!xINykO~R<>ad+_Uqho?1`cEM1m1Y8+y%2Lqs{M zyqvc>WEzNJu5JS+11+6fv3XZvMMa9@k7sy>%F7AzckN# zI>ZBL&eIl>o?4eRi=w!tpC;?Q_8_}(f4)6`UftgZiAoYo4-*1K?JYOI^E_>DJiN9I zqWI!k-r7oY`GJao;v5TO`Ron_QuI7mi{!Uuk%Du#^Obl9Ow=(bQ2(atxj_-H&X zE`?P<%&FFodaB?0jW)R$wQ4XwQIHpSgk8s8k65>BwSyzDk9P$hc3!B_Rf;Me%uGvq zoVr)zJGH5=KUp-aD@~k#@sey1zViRyB-q^e zCEUGVroT9&G2AEN^It+cdK?gh3K$~_g%66WKMNTXRQ*J4BH2?q6*{%@l+yRIZ61KI2}U-?w#s^9f@eoF>TgG1iCfm@XGxX zoWt3vZW6`p+7P3|#yy+e23tgp*UegqTd@UYZrOD_&Lj&GAc}8NREsa;d$@#NZbG?{MeT zlnw)W2FkhFxGNrQw`QqZO@>yN0>K8Sxid({>h_j$2d#fG!MY17J%jC~1w9eJeLzHN z_W`vkjdoH$TtZ&qnxaas!y88qN5Z^q73jbqL936}I}Tq%lyao{=3S=a;ZE0DS7y$f z+0dFkBR(`Uy%PWx$EoMZS!&XIy*&FjQ7yNmVksL9zhPe0z2AszJq%?#4TZo<&+Gsb zJM2i)rT4BiAAGK`D|FYsz4fi&C-#}Q!hqk(B@+O z1q~UQ`-sqV|KLyyD$%h)cGuVWYIA3+&^`w%py3vfT~-!ZeC5*kV)X%PoXAy z9HrlWAfsB!YQ~d;i_pm_kW@nx({J_!0x!bv>KnyK5MDJ?Jw<2pJ%qFx)s+OuK;P+f6>4Dwg}9$8ua*ntoY~KY(xsi z!I1-6MWOWBpph_uq?l+tk;7V3Z*sA9yW3L4!oZ+C=zTivbOz>#6e3e%HAj|nlTp4k zYW@j}qZ1inMUOTC9vDg%9q_xF7I7S-h^rUJQxWWF2)iB!DBRt{xnJZ>YGt|u1CpPz zZ*Pm?JQTNUEO`JWaoS#}vx#+Tp=!}6P)v^>aqw&K{N{{5^E_K`rJ<%)nBTS_gVel+ zg+0fQPkGP&Vdh7z<-#z1?S$veRW^{3y4k?XAc(-}?-MFyqQKg#6q^>9^_$vI(hY0`t>fEh?1f`i$zTTBqtj`T1 zC(AdZKp*%v(;>~3u{qnZPK#e7I)adEKMF4Z{Hbc!npT-AHajXfu@N5+PY{^yhd$F7 z6{QbISp>9^ZwAf)&AdWq5K6v_t+f}tz*pVk7Ns1CLd{=6o4)xW5O`dqsgA&~*_7UU zF4<&Oa(HHMZvmXl-~ziX33TaTG?HjLizoG3E6YL^8YKe?Qn8Nt>I36fuUdVnWbu{y zzEc|aePc79?h|}vKnVm@snbh>n+e!0VmGKsJj>L}B9N(51-yM3KlRS1Y@ctr5F5eA zJK{wWH?ukO(-yu@P!D?OA{IVGZLr<=!tHsFT$tH-Y&I|`1GTGG%#7mFF@zq^TgRIf z4hYeO)NpndW|N)z_qjMx2lOU#W%C{OlnlZ@H9CE4xI&>ICx`d6Wu03Oxn{GRPxSNu zIJ?Cf4J4W};JCyRywQv{n<=Hl+46iqJxz{t&*4TE^D$9S0C#s0#(Qdp9LS~3Ppo%K z8#9R60M^(*p%Aff{9bE>0~M>~!LcP*8Y~K-%k_sUtY$Vdm7PwGp;qv4{PdM{i;8YuJ)($WK-Br zTkNs0Rb`2Xi`0Mn8;nVNc~Q=F%!mZ23JQL0u-giSXSZ080-I`B5FbZ?KWoqvp-sEI z`t8r(HQescV$f|in$ZHGgpUVm{qq(gpdn4mUxx8H0v|(U4v^7{!cXNZ%1$!|m}fNG z+dOA}%kib<1z;9d=ewh#P!eYCs{!SsUM#pj9s=)M-jwX23c+zq#3I z52+?sAdUY9@`aPe31AAqR-$#^DAumk4OpnR-!UfmF_Io0voSr~4h7+W$Uq1u)muCQ zHaVV=(3t3imu@XimT*Q4crAaL&kR5E_54~m6#iMYrj^390Mxc?Dv`=x=U6Lp^m5;3|lS|JRb*2TK3EH(zpDQrbdVq(pE9Vpg_G z-M6r?Y$1P6pV?);7hW(djv}m;K|!ZKC-M5)v1V~%V;$4{zD*o$oNL6qZO!(Skx;W3 zaSfS7RDV1JogZ3hRQb42)v6FlX{s#C*pH|`#XMZ)M(oyZ+Cp#1m>4+0WV7OyXd6}s&h4|*ad zR6&7;L6~N%(+32tQQulf1dbC2K5J+s$@mpH?2QlF#a%*etu-=Z1Tc!w6>EI`nhUWh zx9IePGsiEkpY1_{JS;8Z zKrH)R3juqv{A$1fidBZh?J=p+*fWE64Fb&Il%vQeLj`F27n>nrg)&XUbBqo_C@=AR z7OtY9<+7z~!^ z3>Fx|3FtmdcIsVh?d_GK2SPFP6`97gt)JA1t%l&jM#Y4Mp^=t7$=yhYPnoUuT1-od z%9Go!Fx7Z9FwWZ0pnUJl%XNu(zF>%!bFY&TOk>wY7+4V$E4kbTpGr$LU~8b1X8w*K zN@x;Gs~Ps4LiXzc2fB;~U%|#xxqlsqFQG^L-bIAz`^msCG9-n1c&YWp2ZERy`J62P z#s!Cg3345j7nv(i#vD#pS1K34wVL&;sVwiB<15H*cLcP*}ZG3eYp~L;aWxj&5|^$I{XMRUqfiV$XvO~$2x7QnBC?pci-J$ zmu@5)Ufyq_*XVOKR}WGC_dhUfFnljNIbA2=zxl;666sg7K4x~>{txnpB0_0k>Uv(W zp|(^=Z=>Nq`Mt^qG8CU~H=N1kY+n3xa;T{^Qc1zj&+bXQGBbt7$&DsuWr?jpa2~v~ z&L_*F+sMQMUP{}+YrGof`r*FPlp++w&*n3weU9|F91@U^o%;rpFqsA##@{?^hFLzn z`CRfLMA#c$IevYdy3-|?G=Gq>1dO~(2a3+QLc!sO|LidWPAKmX>ea3PPW?cpa_Y|I zahtFBQ={=TQ!Y&l^@X1lLi)(;JSqVeE)RW!V6dP>Z{TfXW5c}`CenF?b@!nL=D~u}owxmHvS6Zvfoi)P;_cDnb?JF9ePoB+ zfd>Ou?zC>Kp+lz{8f!)rQXYCj$k+@{Xmn2T+toiNX{qfFbDb++a?e>V7GVOt^~#e- zLb?Bf9rD(Qr{$cQhDsQnxW{ztt% zE+oST{C<0F&JY$;y1bO2 zn62EaJt}9OIHE3ctfjl{q&b&gZRcwYy$_W(Kl=SB3i20n zKP&!aI%d`f+df{Rjf<{E)5MF}MUGJ%2pHe8T0Boa3$0NxW`Zjvz#KqVD8`QTt^QhN z0&_4*X%1M#AQHmEeAQRV_}ROQmsrolmF+df zZ-Bsz?#Iqx(z})568q6Fkf(n7P!V$&HZ3(^dn(m*Ye6QSUYy3>jK)mWOZjc z9ezl)fD3M>Pt&GZj2ZWmsFmz-899uo<@fGx4~8HtV_O7ji{PMemqBS>w1WIl@)Y2^ zbPII^x5^H_>pDTV#~qW0SG>FcQj{=0!TJ-FYZ#Kiey~9;UY4lwA`$bvF9oFvk&eu~ zu>dZ(NSvd!=mL9QaRcT^2>Z`yiXVm(Zq%R0n~bZfHGg@%A1UgU+zDIPlIB{=*!hQ1 zh;3tluGa~sX*@t?x0p=~LUF%TgK7wS9U)J+YCKw?i9;bzYE6dAl2x)6X!OR? z^6osRQvT(Ca3&N4xB^7v`HVeLJ`B>iA62V&p)iq=k;UZOc0X?$r*K%#lOG2qV#3oQ ze&2dzgGVtpfKlr`%uP)-EJM}a(!}%?jqcJHLd? z5&j2i9c2qqZRrF_fxj>C50y|$38kU>zBJ8Q+iz43;L`x6$md^Hq&L8IFrged(t>VY z3+ht2=s(`!|B?_$(5{TI=z_WKH|ea#{cwa)d+19a*+Kmnui z^hLYILhj-ED6gwar0BQfzlSLn;UDbwTg&`E>?T3VcuZgNsa}}g7cgwUYXSf&IbRx^ zIaN5(0~5CseGU9IuV+HJjfaB+reCAQU#N|!{feNsCi(+8d8$6!V!eHBh4f0?KeSR? z2uKB)8@)yC-)$xuCa9vKiim)a3Djs(zm=4&u}V%*YZYjfckxXQ4TS^WI;t_@@WrvQ@Ht_(RNUBB)5CG+fB)WlUyll8H#aw>y>Y0d zhM*J}tN29TQb~l(*S_B=rME>{?zCXNZYZTBUOoR^RzB*svEN9;bJ|^P$J7f!BKCa zUEQ~8Ge@|m$BQyeB&$sr^CyoIn0-{lr9n zR_o;xUf*Xz6nxf1SY$kE1F&Kzm0W5YBZat;-Q7W1s}La2#dgNuG3@n3V&2!xmB}AW zfJZOtAqnm9UK zIRJDsT^>!s0{A!T12j-VQvp6vSHQZDMFSd;UQNd(CKk#Tdm8w3`f%di1*0Kw-c)bA z?J4>dMi`8cbpn)ql{#g*L_ovqeHy=~82^FyzZ(kR|LgHSwLDIvSWV=HSJq%G3IpC` zQTVI_2y;-hqW6nZV6X_PHgqi>TTEg|j0D}aAUz`!W&O-0b=;Qxlq0W^KZBn=HC zC5ONRpG;%()dWVYxEx1- zfV{6>gA%D$MuSxS40R2+GCa<g+t2gzshl)BtL|;CzP`TSowxd@ z#(x03Mc*onMi)@{Uwm$^t{r75o&^j_vFr)7TR)C@e69d=`-H&&J_KSg#Pto!(t-0w z$-{#eU{(SF84A@3!M(rHQDee6jcmlG0kbm|xt0C7Rv2j{HywytElKv{Yoh6NW3OX7Fr z4=~b~nbI_kfOj^aR_yohsuiouJKeR(5lfh@vhY%>E7~_ikt~+ zE1+R?aRl&vFLAR$?IIE(ZBa)4hG+TfN@w9=8Q4mFR^7{0Hf!Xrbsg5wj;8c<=tX&6??+==J_g?dZ-DGmJiVqAI5_uxS-Fii&4DZ#7T0|Q zNHOdhh)YlmAfV4BChq`+LY_fGVX)^}6))xg?~b@dIGU{q&i04-=J#9c(nIfzW(A1> z)L&lyzT*vGyo%SPXrFg|yp?Rc^2yoa0^ik99eU1Cugu>BJwe8UUzdDVJ(@20H5vZ& z|3P?yP6)th{HLA(L|iesGIu@<8YZ*-M#=H;=1AKOWS?C_dVBi~lKQRuk(9mQQR>hQ zhKc9}0UgxkZxSS9OG@{nfZ5JRzMp^2v~V!=>tnC-^ESW0t0dA}pwJ?FkRk7RJ4?!p ze1J3TyYTM(WVS*&p4q1;FPfaZLlp1Lk25P!Y5OOROQrYPG3S3qh3s0Rq}n)8b% zzqnvVL(76ez~QK}e|d(nV~nrL|HP%V&s?0;v&ap>9W?hJpxtHz&i~~vd`flUFUR$H zdGOR8E1vh4-@Tsi4^GIF#{>>LAL}+Zu_}~~7i#tBof9!bKte{4Wgz&6i^QUQ#At~1 zb>v*Ldn8D;K=W4vgsm>ykERYlt3f4`&X6MNe*f~S1ZMruUy}lCmRdZ6Q=xArfXen! z8#QJ`OtWTRsP*&fARm!NvIf98M0Zk-qxDdFmQsGzEOfi%>CAJNmSF*J>VDjNeP8hV z|FlK_lqcE+N>Cj3p_xMdrZ|pN&fMbtK5@*{)R3i3#y*%5ik;a3*$FByI) zGKVXJ)JwP3HebRReJ=MmOUchlcs6wxA8gLX+s9)kV-Fno_^qFbGupO?QbO{;&?j3a zb~l&g@;@-foj@#v%ozf}SeV-Py=iG~=n_Ex2=$zo&9F!2dBG(_z}cv*C=}ynt#)PG zD6ejT*7{Fftq;`Iqgs?b`~e*T?rJk{pwW2gjXd<3IW;kPdRVki=CI-Bus?`3&z1b< z9GQ0qx@i67-Bj;7rnOQk=S>cH2KITik7}w{)LhH+x3`@2i-?!XfY-PI>ged$>&DeX zcXs@gtzme&lHh$QYP0r z2mLKhRt|*X{Fe%*8k69lETbzNhi}uRJqNC9!Q4XH#W7#n z<-T-i%13k^4O0kpT%yDW+Q)8V-)*GgPfx48Bx z((yFnj`w_G-7qc<17~>iEE~$f0eMqyR#O@o+LmJ865cJWQC8=%-*0aRSUvC2CbPwv z(BHhC2lK?m!K37fb?Q>dpRC8)F@(f7-&Lh8Mv$1z95dqcT(3C1Joys}&;)b&XPGG< zxs&ANYy=HlX3?q^G9QraExSJ$7j=yYynLf*Ci(uARqXN!5xf5aD}x%J*QLBmO{2j< zv!;J?kn3I4jyCpfu=#N(X#!%|J{z)piDN{@QE= z(Splc&x`{{<{w&3Cx@~P5B^5?GmDq;Dg*oJFL$sgx9@*RR1L=1E=+SV(AFy|NXAgU z-{6|&hf^()=7&m3AV(}&N;JFnN|Za%5C^m^Sq)D=GL*0jzZ1CUSGjDXNXfQ{@p#{V z^D8{Hlc2Ap2eK$Jzdk>{ZsHvk9$0d!Rd2pQ4C{ZH8!~hSTb@Pco7RVQ;4ccH%W91Q zBf&FcQq&ZhAA&IHBTO%dMk4ae6GkS|^>U-VT|^7LTMfy(wfOdE9mj)RFSak5?kNE+ zsCU{V^uZKWYG{tJ^&`pA>WY{yrOa!54lCs5(~e#T-&YrfkH!6O`0m->fZPF7g{HBr zGvS18%cC7WT2}L5yx>Gt9#cyq?T-q^Uu58kw4WpZ>ENpcdr^X`RsJtO3iS>h62>#2 z=szNL1i23T(=(e@wWMja3b(SJuZm7)Gbcln=eioUQNo>2U8ogeaVw^TyI5b_{A#Ye z_kdDR`E;evbnhaHSQ1e$IflKsSHRwFx_d)&X>(z)xpO4utk&*UB4W+dByBA)eJq(xGhm&#pio*xAiH}cxdj7ZCzj1l@M;#~sN>Y$X_{R2*AlN+ej~u) z)|ddNvo`m}ePr<`u6H$q@KBuQ9K>QNZTNUz0uCo}o%pQ|r?k2pk#Ujs2Vpo44}RTY zt(!CNa;unnkbrXW_3zA_(AD>%6s(i1<(@IyY#&a}U?N%;P$A$zcP%!2q*uQW})W=rq zZYA2mY=D-#*|S80X5-o-+M`?g_1F}WZI$`83U|fWCC?QKVvdxhd*XtHWv4FR^f|$U zK3ib*)Pg`%sSdZed!0P4q0=wNxRQ9jUFZ*rM&B6j6%et$r z&f!v{e7v$8H0?|lqH|TA<%%b%s4$+o*@S9wEqLwsw>bYJJGwveB%*li`X9qOIH#mRG^(q zHKJvmiaCcmchom8Y~IB6EW7FsRy;q+N9(z~rA*})TkuXaa8j>UjkLDWB!0vbrWEGn z=6-Q{pW7GnZX#c?EVR+I$FPPUXpSb1xCS2A?<_lL0=aaZ6*y97&dvuXUmB3_$@Jj! zjhXez%`*RjVjCU|NN8dIz(N27JUIITRLb@WrLz_zT*Dd>9|SlUPvo+B+(+qJb4Kld zDeVR}^5u(CI!hU|u$zd}%I=+r8b3)c3Yb`Lxb8Kz$|dqFO|+#k_;8pm533||LuNXM z*7pn@WoW~{gsmM{N0=Qg>|Rl&b9{14Kuf6`hk=GN(fA7&ZZF>TkaXPY2m$V9-=rWBpiwwsSd_;>RN&!rvl7dB~ zhlBQj`AFl_ofZvEJGBaTt#p3Y8!BMBb1APuZ>wT8Z1UE+bsS484}s{`{{P|bt^cxW z*6(qpkq+rbx?8$aX{5Um>5?v$F6mAQk?!v9mhSGB{*2Fao^y`x^Zg6Hzwm;&_ultr zX7=p4*0t8Uunv>&_@@poGXk>_0-WD?!4;g}x%6Iz?-JwZp>^t|K1L|+D-xIfoRh~k zyeic)Nz#BLGmr8=L(_vuTo z7X^yx(;mNUf;Gx&ap^rV($yC6m+jjj!l(1X!nsyZcm|s@609#d^|_hxTKK zJ8My%m8;QZ2X6TN5BQNLi{EI)6#GWu`T|ZN;mmS;WTs3M^zR~+%?G%Sm5@wdB1S#Q zzn{oOl8lyT8YNMotKJhy&TSUm7bJQ^)Of@SMXPkNUNUz1Y2b`dD3Z(bY`J_{mwzlG z!qoEGY3^)F>?Ujg9_vZCmNZh=d0dj%bI=!wPU--0;2hM=>JmnnF{SBd7#TZU!05fj zPQT-+g!&uoQ-Bapdln~)Cvb0}S#Aux4+&&umVp_qg1I^F?LkGv_@PS`iYGo+_D=*0 z6{>|)ymDr~K3^TTX%2pw`%3P2^iJOVn?uuW?6+3DbZy?_=?xQNKZQd{6$S}o4+Xta zwx`0Y(`%*k*22)=9t%?hLU=CjFjO-Vt<`y>c%;5%t4WBvFLRPR=t@>neZjS@pJKt= z-RA1QYsuwle}0jRKf?y=O}BL@TJUsWbJ_1cX?M?cf5P9d4MnDDrtLoKG1@ONely`n zbA|e?Y~G^V@eo}rZBk0)AaKC#+a+kbp@OQ8FIq< zCTd<8K#?+kM}~f9Qb?`E8RF`$jGML(_eNEUaN>S`We>^xIK@WH(cm$g<{L5lvwi!` zw-5taxSU+Te~mEw=VBY_o3*fgwcWEZf0gF0MZ*Uq0|Ph58xI=yC%;|Cgf7M(HflG# zpI#4G(9Rq%%51R8#FmJFg?i#0maRdUW?~2eA|iW(1|F|6I#Y%4Tq;@Ce7{gfnqEs5 zota$V8)h-n_C9hlx6drrHqcM z1zr!g1e>1RPhr}f(}PHfemHWeJh9B?<<5uiZ#OVz9<>r*hO|2_JnF3zRAWX>$ixpX zQ41p%j!*m(3%pK!Mz-H{yJrb}AsG_|&1W-yQ#}dVRoU&|M<1kkKhrz`P`n*@L$p*6PlQAi_K{9GTk_wP! zT$czJP9jwsA5v=d+~QxbZv(i4AeO%GJ*TJ$YSKau{sH@M+Y0%zeta$+RXO# zB$9ZnvWVM#NEbzMv%zhA?^@Rcdl+Hs!L_c;ZV!s5`OyYTlA~a(gS-HGw*@I#uaFMh4dE39{i4GpB@Zi6yd9l-FB5%^wV@ZW4a;Wk|Ir^9a?F-699liJTU z9D7OGG@fELoevx9#t&`4=qFxIsXcXtjC;SY8`!KtBWn=x!8c$OHIN}9=&u!#x@n)x z(XN3WFhs=XjA?YO9Mx?P)882la58XqkGx0#8>u=E4S$9?2P zy%YW(AjdAB3uFL#=f{CW!r_%&-_MwBx}vkFC$W! zR7dm@mt@w9D21OBh`3fn@cI^kjXJ}TINj-xJ>Vc=M6AfOxc4>6w$C$6yJH#JC|jf~VfN;5~GH?(CE@AYbI z^!UjdH{i%NWK2^<4+BBbSk{*X)%M~P8i81;BS0eXoB8Z7vo1el*R&=p%gdkrC`3~{ z*VyrsLwd~}jG-94E!sK8u#ohrW!Kk0Qv8y!Oqwqq?I_>cgMrW>1V3CSh*zBEY4E85 zry_p)!0GEb8q!pZwPzbDm3se&hg+m4f`Qv1%-HkGA8e1&kg6KQ;jMUG4%qVQgIb+@ z-TV&C-@qbCk7hzh!~FQmp!!qNg5aCDy30Z5$5v$f?dF5zr)*~G?kK4eoo0$@1D!&x z>$rL9J(13lubG=4k2bd}ul&ah#}JC9BCTzqlusIZt%p`z?ayVZ>#?W?DJtKgz`*E3 z_wXsc5=3T>QAC|C(h49Cq2z8Pz>AYD)Tp2=NllI1O#v{pqOoy~ge>+P^tE0ZY^3l; zrR%wuS<5d6PTVlb)1 zBZL6R%)R5=(zxX99qfoY@TL|0XBjz@U~9Bbny2%px^=L0VXT@jt%~5Y{d`&JFl1P{ zO2Nd5Qwz8>5bc=Tc|jvEZe#e~xufHdaKFCFJmK$17cH+RljSj|!PGeCa50e!vxhA5 zFr7Y>dwt@S#UHdhDlhhLUAPbCD%x&km4C|xo$6X&1nXKJXXs&nP!U^jfje^9vCv_G z^6`}aFc)g-;|cx*Keq4t52^l~g9bHI@*imsa`^LX#xqLFo7KoO-aH78^m5P4k_ATI z+CE1tnhGpUdA_w^5KgqU+-B_R@u2kn^2~VF`*MxjtA&FLO%Y>#Z5WM=$dux{wc^it83 z5d-m6=9^N1Dnd|GNnIP$2&G)qdnoPz4rgL*mTULOPEUy?oYmFsjz`Am4JLgeONdt# zcX+|mkHZG*{+7)Y(GV|Fw7Y*%yq(@&JwVD=6T2Ey>lx*}uI*=7y^yC&w(*BumbW$X z008$cNT+e03t_wC6&6+yA2Af@ctuXtm#YG%RIa$7>q&&G~a;uN5-CNuR9_RBG#|tw|Rk^sBKn} z2U8U`OV=tkt+u*KYb?&kO_6=Fz|1_VQs1{vT=j*+iXc7l>9iIG>*oD#ULt}1u3|M& zb!N+x?$_Pgqh4Z&oBAD(BF!q3$Id{s5s)LIP{qsc~MWIakXq#qc zqKD7;+MdcO&=_;oKFYQ&P}XJ{9%WR0-gD5Z&fX@^qDX$mR@7%^Uw6LYLzcL>^sLaFS8l}Z}af(KQODwe~t&k`RCA!x+$;WV+5%$b2 zEJo#*gg?Ja-mY(^7Ce%Q5@8P8?sql{OJZyxGOCyQtnfSxFttdRre7V-J18W=X)xK1 z=STx++nYyo!77&NH=n!|@TSZ)q`P}9Pz3oxZAP_rCJUnDX0@Gp>&YY~*Yn>$_YkE$ z?8sWTW0F~GS-+Nj6DMU+;<9Nw{cdqrWK9;2jVZP|RVrK4WRaqhgXiJ!3%*Kx_79E~ z>0hGF(wPX-buGep%mXouqU3J;RKz1)>Df<@tbMP=LL|ma*>)&JWcx=JRYTa{? zMl@Xv80oUQHPC*M6s}7pbT$4(j(i!0x@L1pFCk05xQi-3nBLeqDIdeJON3HC*xh@@ zv0!mskbIF2&9(EY;TMSk)ODL_7j@#(*4-IFB-2My{Z|VM%d3&A9R28oE8J9YxYn)} z$IC5(0pvssu3=Bi_6XP*^tmC5wK^3ouLc>I$2a(h6?Blx!dX90`nrM zo1m5t|LGTp9~70`4dBn8hDQ6>pBzGcf#sC7q{DX7mWrX^f{4|(gK6@IzEUyR+%=UF z-PGHw!*~YLKu!Px!dxEAMw5||sbeOI5o4v4C}!gUx@aim;JsDhZoMEe+(xnqYSaYq z4Co6ve0)PW*dg<)mptMe>JFPC<-lR2NvPNwm%eYG@*Ohe`yRg%u7GNYRY}N#aHc!deENs-^Dg#v?)v}P0 zug`TG``ID=>D$SivY4LWj2$Qr9FJ}`Ue9`f`i9~Bv+jqmSy)R?cbl0T&2|o(RmpEoyI1BFxgIMqd zzwbhL3a2@$4t1m42VX|SIAJZTifZEhyTuiF_h0xW(tl%=_J1ojIF^%UKOCAPUu%S< zOgFkBXtZn=)jYld1!{i8@VTDBa{Bv_h;eOU;}yJ3ET7B=Z#<*Sj{$`UB6`Giw$Z4B zNb{(EQip{N6I66c787Ocw+Y*FuPUTIbWgLXBS$g$GME-3e;do^rn#USG4cr<9EkBC zPee`)H@F^EFYJ1(Ga)79$A8mO{1Ep3UA_rN#O847hgsfX(oT%pk&cgOtBJ`Io&7ai zws;T3un5#6Z4PoLr-4cdC$@B+4Fc~HJnH?#?Ow^hSlt*=RS88t7EG3)#x96Kk*rwL zGpbT>nd>ktbzpGe@n8(2waa*J#7{fOAuG)?Bp4M<|0r6!{x9_$W}xjP?$@f)o>_SE z)Xdip`F}rI$ml{oxG(*X+s8lrhH-o$C?mJ6vm`buQ3O8D5O6cZKD2n>TEM`@CDJVd zE{B*ute_YMWoQj9)W+H78<0Sjcm1_oYX{l(^|kxeAVhcc`ChPl{prwg953Q0@-21s z+$MUrlvry+O4d9vT^s5qkBVioA2Y^@ZYLgnPmXjFbU$U_uE#?AenOt^Z<*d!ZvK+x z>1I6Lmnh$-xoa(1_jz7Q(mo!H`5w*wzObqQo$uM*s@73k^uQrS?VIxMX~4%#4eCabz>c|?f7yz zU5?f7=`NS2_$1Nw>N{Rsicj+3meCh>+8Ehk#q7CvT|CNMUE)}({IyZ0vHd4A#NAk( z2Fp|-9Ah{vU-TuI{TO<j-=x9sc;|Ln1`+dB zmXg3HBf z1;1eh{>d;ef<21N60})FgEFq3r5s5wskqUZi2%3pIJIA|dxpLYQ2XVHsX0cU%f-Zg1nd>!5D`wC^r?a znEn#R-~?Ajx3&{uEVczC%VP1i9zyDN1?V3=S_`XM)$BF2_6~xjua6C*u34P~9%26<09-*GvqBl8L3I*f%n9H0$B{6qxIInze3l#1>bS z{x>;I2z~?B*8_nhS}ja`G!p0_MuBxK>&x92(K|rGGXI269V(V2VEfa3UNUU#rAu~?7yHoDzRpOhMD=wBFI6%`*E4wff^L!SxWE<4 zdh;<6Wc&wiP>yTyEOP4bM?2B>I~RweW8)+x)M0AcCke1y$nDCu3@+S-fjW^E@0`M=0l)Xxi{?A^U9 zRL#@0V2a*-P4=|KuA+o;P&O4#OUBMDAKzH!rey=c`x+nsCKa1dnW02Iw9v_a^OUFje=Ga?)rZ@yQhJWH8E@U3h zm;bFJtf6{-uhX7#^)KmFLs~>O@9_uOfq15GEswX+UiYWGH(E|3{MX}6*H?K}mTDnj z1T8E|{_gq=W;E&x!&2rpdsVG)`<%Cp!7`eaOkr*wNrO?Mu5E_l?e&M9NcC}RzdZVZ z!0Qa1Vs{3wvYQfEjAwdG4^Mgig&0f1cXp*xG)4}m3o@1T#})?SbUMP4gZHpv*87Ij zwdJ^@!)`mEmG~Q8Ru~0bL@ai@B6&CxT8r-9oM4IHtNh9yR94n^B`GO5&$~mltzyTW z(V)@|^eyD!6mHQ4$st&$f6+T*d@$f`e`5Sw0T3f(4X^LHu-cKHOYsIv`RyR7gB_p- z#nt%*W-OK&nVoz6L;Isx`2cZ41_Mm*3LJNznO)27|NEXupqM3ZrquMZ7aLqmG%>gs z8Rh)C^&fP^2)Z;I{>_EJgn@89qT~VO`1j+lG;iZ#VqP(_06j_ch;pt+Qt^mQ^9`|v zAoHT!gF*(>0>A4nLOQx5f0~*vQ)l1AOoVmrQU?A^3D%k_mi7c6|6*UyjQ<+ba&T+# z9NLiW$qMWar~@^EshtPhn?9NUpi?VeJ6?za?|<8^Fu zmYMD>D70vJa`1VEsncH;R1mL6fFH>yNEevTRTij(-Z`9`Ce`{+7tZ(qA0lz!r!-k^ zLRYW_nBH{-awG5lHHeXBQ1%osBdKh%2{j*N-wp~VaUHv)33-RZAmP1*gh6~Iib%FR zZ_^^(ZFnBon%h?g<@cYgC2tL>#-#4ulB9a6ZU%pgSE~5%{JQPb-nW9~hj$hS)DuD> zWBZA0c@v;=EgO8B^Wogs&oZN($2(Z*>630N>T)ZWGsaa(D$bG|9gvU+roK3KsfN$n z*kIRe*>cRShJl5Bv(at0zQj%(645d^5P2`e?{+~ZNz|U*{(UsEfhdP9et6u>`!V9H z&8rD=!Y|`lRw28+v)NnrR#u@z{K2rDcw;Ht4pt^K?04D3_~O=$$OZDdB)3Kzo^qCI zUDG-RZZ&tiC5ocd95=2}jiZ4L~k117o@ zKq^ozp{@O-0NHaZ%AAYGd5}ZWXDD?7i9(?lj_>hTwqXzs*HM@wLx*PSdUwwau-& z8(q(7czBX1Wp9~~@UQY+TZ+O0)Zx5Z4kY&`OUbM9mr~^Pcx1!Avu80q-i}YxY(0QA zC!0}+M|pc##{?Ji+*hQ&?EY~3C+&_z>eF4evK9_V?eWyOSf;@VJ3?LOE&w*ATwfCd z3ybiPnnrBZKfJ;UpchTLzrXLDZ+M-`i*3dcaisFEX{I0X?32E}J~RHL-0VPHwox6r zzq$}M7Z zp8N#M_-c8jT&3a~l@<+s7)4*pIQ>+wi?`v#RRDAM?XEhe5U!!;fyPpxb*Ut^?9_9Z4Z*8?nO4w!c5x?aEdt{Nd?$(709 zl0Z}(b=+HCr}Ev9<4E3!R)f+{&RSF00O>db13zx8?Q=zrdb*E7{rXK!Us%`B(sFa| zIjl>cS7nLZ6IE|Rj$rC+Q=;=oT0i5r0+r+D)rIN)7!L~H@Ztpdms&2_Zvw8zAE76{ z9ehtc&NjUFao*fOn>(Gzb37b}9@sLQvtF))Se&H{{rXkOVmE8ubYN14a3DG&0(L6@ zWvi7#_OdBq7UUhH9h|F++wJGuN|zDwU~APc)mGuz&<+vfmzD(^!#8f%g(8>uFv^C8 zFNSVcpLG!g=k@kKP8NG|pD}HUdp+t_I-c5T)?MUOQoQaAINH_wEHdzGJWVHYey+va z8;^tb{3|}cn`0gVHe>l`FQZ%mjrr%kYG$n%m@40}bhzDvogXi7DN?!kwI!=}g`a!qe~jJY_^5*GcgELMl^xMz&+`81_#=Wf;hqt#?DFE?0<@#r5f zfaB{-Nz1X~!)+Q0iZC9h!rq7LOw4LT)goWlKCazVyW9XYrF9N?i!&)D!DmRI8a2j=wiYj}_@$j5 zy=<#xZ>b==Y8wb7B9F9^?B(6@F@Gfq0QM;yd>7n33+nJ-;FI=!_kJRljmmtvk$A`c z;CY`i>eKAq-73CH93B!O%}}iyX*j2Bs=oEtq88#UL|o331|)-!>)|@V4b4Ek#*ex( zOv;p@Obm@|CoE&5&@aSuu%!eUwW2*(m{XS^Z#GJs+GKk0~QKOQC*$; z?Ch-HO3|(Pe|k&^UL93RPA*qDFMOgaWSaqOkWGs_Gf1IIw1dD~(b9E-j6wYWV|YIK z#H-n{G_Q*4BD8DQ1+|mqizs-1`=7D$y(fK^jn9r0lf3j9ty^?Z7}RPU95c;{PE;{&itBvJAt~*k%T+!vFa`g zuK)W_WI$;L0n-Du+v>`q|N5H0r6az4WGhjUo8~P4_mhP!C=U;h0x@{Ovy+p?A?cq- zk74*UF$oDmY9*Aj!zx0O+DeG3`3k30xcoO5Oc_8bZ*0P(eKhD4kkVN#K*qIESEI0gT0Z zy#3F&U3&)xlJ6fNbI8mP+6}ux?i$l3Y`%Y(g{!xx5YWn7H>dz)OLpU4)bC2)UL4L= z1SlyfmA~r-jIp=C1NEx|A@^ybSy~kS9#8}k_7%Iv_za-uE9W8q&iPFiC{xutA8J;M z6BftjrMU%zutG8b_oJ(;A~@F^7(lAj*_-gVp1pd~sa-p{Nm7KzbcO)s-oEYZn1e)O za$SM2u<(WV)3rAyF|oYaNE*kni*ZMk`%j<{D*zhH3qU*LQOem;gkY3jx!F$7<2`PS zKB`?0cPs&Fg1Tz$a^)J=>^Jeq8)g>{j7(6c9C7r!=~k)hJ2UA?NiwC#w>Pnd>#AAV zdD6*p=>og9UXrX>J1N4Uj*k};@)tYV5lRJ0-||6N06g8=i@V_7CVw z0E5@m2k;{m!1a&uV2J+Ys*PiEiop*Wnt}VqEz|ZJg94W#U;a{~YqZ<-(U3s78a&F^ z(pNY%fZPtopkJUe=r``%+hB)iSPbkJfU-%#DE_7iAPZ*!8`sO;dBE;1@mgy>U!x-T zUMl1zodn1nWN|xe8M}s8**m7AQ@@1*Ro|qpm)w~E8>n(gIj?ID#;?*O8R( zE|x}F;^-Zyi9yx&J@C^uKc9nP2725BDA7Jz@3cR-x$Y*e0W56ufa-3*Ya!eAn9unj zL46B2Zqppi)h?~-e7Pg(7ju1ld}KWda>u<|*R2LzSl3)JUW3`EyW^HFWti>=&=-B4 zZop@?(qsUu0Ctn-?BKn1r8Tv$2Z=(4Au}17U#?s#tq7`T8{}4-KkPRxptZ^f!l3Gh zB>yy5YS7VnhdhUrL4v5L zS)k_aB-jVpUCuVRl$dY=Jr|?+Gc2Ln&+Rg1po-@;elpNuV%{uLy?WK_#OiXST_!opy zUwr`X1?pv4 zfo<5jABACzTb>@>+l6*2Mg-olu&_8@>?r#2{!nBTqnL|LHJi!0*TMeQ48}Vqi8E|# zc`%xBis9PtJ%e=`svQnmSsX9~cvtt6+0BppvlO{xc#MN>rpShH?JAChNu&aiw#&&g zKK<5bWfHH~uv~yKhiiUUKGGJQA-iNWx~&O>ZDARQ4Lp`Gq7MiLHLVRN^5o;k6yStw zUDm#Qu|$}e1;OwT4#}D=z^fPTNg(Wo)N@-~h0X)Nml4msRx-11=QB2LgT)#YuzvitIeKwaFFp5w7u>Lig`dBoFzk2csCM?%O(Z|xcREN zcq;;a+oFi3ngSCBk394)wJvRt&`V}gwZv<9-TJSFMHQ&jp`(C<@9!Rp-WD62?Q+<9 zxL%g8Jt9@g&dSTIYj6y$1dieGJ6M}*N8l1pF-l}+4Y3?%C50dxPh>NrciFE-x3z$%{c>^=7|?Dyl$2OPo~;b+ zio&)x=|Z8P0&nc}G+SvA?MO((TzcOI5dg(h26R8O-QKlac)~AcO7v{JmO3h^p-7Ib z66Sm+K@A~SNmqbb#aVUeKeHr;QHokYi;gz_c(idb%R%K-Kh9lxtNKH|?R~Tz{rbRr z392JNk8d%@c`JN(J_1cpvnnX}LH6o9BQscyKdiN(QGOiM=ufacZG*CT71~PHvg-B4 z2M1U519=uTI-_R7YMAi>m%zMxKN7rc>Bry zDF0}>yVwxn8j1Ii#JUOT7bwBX#|l6K+H`~gZ+{s6*0FT60fI@Yv|kCZc)1mpC(O5N zAqAhm1#!-=TAxb^5D1OJ1q#>Z7!aW|@`P5mmV>I%Xzv&6tKQ~Yjb(jShi1vdMoIr= z(7FdZ0JR_$#_eBfu0NRz_6hciMmcaOCEZY4HAC8!T~5-)ZfsK_!+`8wacdFZ1@ORJV}41d9d1447m;;c%Ad z;KZU>HZS=RXOLiQ*=c;|qkPD(i6v`W4N>Swwss8m{0xLUFJFw{d<?p1Sf0JHmb6EiIH6q?)M;4}wxy|XUaUpO08vvf&2!0P z!Rh{wAYO$9vx}lfxtgL0u(QIeNPKUTR2RiTZf3B7T@8t!p?rFAWpVxs}GAj<$rI7f%wbAiW& z2C=-dLUQx$Im)q+Wr6h~&jrBmHF!5FJ=47(p|v2l&oeqO4iiFf}s!sc|#A zzUC>iLsr!Wo=wQr(iMNg8NLJ`GFFk^`9<~e3sH=U5B?MM z{u2E(2-D3qNP$K z_z&k@Vn|$m5nwnZkf&i6Z@vn3#yhrksoGTfut5`gyho{t)AG{lA4|%A1@yIHaQf0P z1IA2CJ|+T-!-N2Ksawx8`(M~a`zr{Q_QBWIHmES%>JJ@@S2XwP5Z&dMzmItIq;UvDB`w9_}szzKUlJ=0z0CJjls@m7cI z-4HY?68;lxz;uNGakVrtB&29>N5{nGU5NP7Ie8mdLITc+rGC=KO-aJ=xB|mbMWv+w zfhiiGNNivD_m<5|@taeEnP7@ppZPz~L9HhoX1>X2Pfxr|%n2R3>H7RORTlpc!}Dl5 zA(7^Yue+DA^ENUgF5(c-cAmRco53xJRu0zin4MLz^PGD(pf@(sgnWa9Cx#}|*VdVo z?9VP2YP8=dKro85CcFyW+KQk5q^rkxW;ANL#qE3F+g|?_MqjNm7W(`@3AFa z%UsdsZ!-pd2(Ya~QS zB@rws$`X}!&w|oU{(avw-8n`7WXJyF3xml30F{vG>w>1g+%5l@)vv`eS|NI0PvQ;z zliBzOxD$#ZQ7AW-em|ef@vcNMRYXLj7ubPVFEvWr+1XXP91CdHJK{1mr>VcX-TR&j zaKZ1(Db-s4XTX&pT$ZQ8i9BqXKL-RTE7+pm+}CB?*uZI7hpJw6@P6{TXwA4Q(7b%F%8OvIqtzDQuSPBZ{P}ZC0!%SB zucQLMQQD&W#R3PI^Z1gue=(Q-ncvp<5T$T)7=F0ILd*gutt9n#M-82y+1KqJQd$6T zxy7W%p!46i-U{=4kQ4z9uF_7$r2+5xAlfI(e1?#WqDPV^Q4gZwo|N{iq=iL<^a~}f zv8-Fm39tXU;S5v^IK!D%#q38};HnoB87Tyq*qOkbjC*pe`DO7~B&}M3VUS9|nPu)h z)id~b)s~m860v=XCci_`x|Wg5{XQC%AL=Z2xLx|f25*5VJ`Y4>K_E!qRycn@{iADo zB*1uS<9tN$aFiQpp-=iIJdRwazQ5}m7@$kH99#P*L2oQeQgSRE6&0nDB^LC9Zkj+Q z0~K{=v4IIxsfbT=KcWGC2`?Jpoo(~qq52u^=_r!!pcX+4LUG`z1_a-H-pE%%kw-Z@ zp{I;%Yqut(2X2dDI4pi3R^L+4iy#)1INyFcQdsgE-e5Ik2KH}{mnAbBm*a1kl#Z90 zdOOJ7zsLepktBf!*zawtRve&*BGaB}v%#DiA4dqLiGWwvkJ|S+Bbn&{5(e+b(2o&> zyk96~lcb8YYQ7qtt*5aA6SKXkLNmGKf^8aK2ntkOjUeEfDh)t1fS@we;PWOgB_(CYQv2zcNvFPw zN(`J(Xx5qpkJ|+`;DH&$k!K`xTCGG@8+`V!t`>pERDuZHxa=W5-`&ki&jO*Na=L)$ z&UU(4uB8>QpeWLuA}4F7VOH37&+-389ODD+i$QtL8*4>LX!9-eMN?j|DlZM4HSD6t3Hvk9e=1HZH`Anp zY61?@0XZ*!Z}Jn_yw?Jz-Kn{`6G6ej@Y#_@@er(XovQaS8;46xyobx)KAbj7^yy7a zAtBHkx0l91qZn7G_KSS7$(`#35>o7$T*_h0(I)2996t4~$l%x$x^UZKsN50~t2JDH>h;}y$4DUN})Y!2`|_>BRjTeo-t zSk;G`hez{>3%s=jf%<0zT6!pnWBRfQn-dUWht<07zd*({~}E*m*P zIl+Ix7(VH8VNOrT<0#c%2#RAk?Tn^P|IkQ==ib}Nt6p)I;S`WK!o6!VlX?BdVg{p+A9lKJa#BS`R(8G44|@3^Wje6V zzc@`>4iT^Qx8rBP468~1sL%6<%`J-sb1L~XDS)-`1O2^Z`LfIMvpE3VXdnWn$en$3 zR~WY88Aw>8ReRqZVwbV&f-LtJWX}X$ubY<|P!*y9;lMIVJ_sMudKj=pKQ#j}%k4=A zjfBI64ty>9b9Fj_+Z_xvza2PjXVzeW_@fL?UHI2QLI6PA0rS|DG|&_{|57Pi>PP9- zo}kow+>|fOgEHv-*QM^_7hwe4B3sHxxU4F}_`Fw6Y3{7)OKz4BTIWv@Ul4Icz@lqE zQW2_h4Penx40%4eTD^AYdr)30`*eM|U!?~0*D#sSji1ZXY!WD8L=ks>b54lR&2ktS zF^KD^+EBGL2$iwi$@9^k>KkR}rW9gsPR>cI+Kc#&E!KS`vcS+ns(3pL4xwf5C!zKf zVRh)!*5I*eU`3%&g?l8au z>3sy;W@)nfN}FZHN9MM#f!x6o?MTi)OC>k?LU*byXQp5095afooc0i}k6j|E>b-X6 zrL5ImzXoir?zf8p&|Z)Cb!Y8P947q%_)~`Dk?J#NmS-UPIHX zHj(^-6I4|Hr6utgx3&$9_{F$39EZa>r+8GqQaHIZzS&jtEC6sq?8mE#XTMFmf6ie( z%G~hrjG<*f1BTeNaAB%&#=I-|B>|!-PghURIpm(?#SoXCqL>&I62WU9VD#DdSXx@H z+E%1dA#r81SRX4ogB+V|n#$v3Y)BYAki zS1O-tFvD&$fb~a~JX<9d8<7aSmEbupbfKW0f1^3cazy>6K}|*mF`g3w-S&lyT2%oP z4f81w`X#=&0vf$KwQ47$ti`w%$tzOV?Zn!iO?!(4o&HTO;?1M#!C{YUdxw$X!KM7D za;tgHbKhERKu0mE*-XEuX}jMGnrZoEt-}&Z?ltt1!;* zcBTSZX71e+h>#)+5k0iWAkYKjL!xR>U>#M6|$|-uKUb} zMy*Jb;oH#lj@Lu+h>B3Y@5+FwTl`U;u)b~*8xEow_XxDVMJAKxMI3H7ckkoErz>4^ zV3`x|`9rV8cfU?&Ws~^fYhj0#9BgTY--sc zpFVpd;btHXc4IIv0Z3cfF@5ocHR;0iB`6Rn8=E=oJy&jG`F)eVw*}(JTlr|@Ji9kb z+~y4k!-gh5LvK-OS;vALN`D86pz&6zx6O8Sp|rYs?amISmCc33+c`G%+_$*%2qpVY zbAEENqpqKMCMy&&Me2W6Te>xzCEE~LZo!1JPvFeWM={pR<*{`phH%$E1fpdhq;vrN zsg(&gIj;o?6Wl{gT5UFo+v$#I=>qMKk6ikyLfiHZ}Ymsf4%~iC}Hg?I1la>!pU{)K`GefI%L`hrrO+XC6~;9 zo@?g2*?8+t&el{n7()|16gGIp(Zd2`?@L@t;up<5QaH+yJTH65zTu?Zpo#_u_ ztcdx1Mdbj?*f{E!OxbP8` z*&)4grL>pISLhMx7n@BJ=^)6jSuuTX!wd?xUBEXr?V{ zs@UimyI$y$M9M=|GjFh^r&xSXT!fg*-cEFU6@me`$Mf*eTKTm`NTFtmZ~U%7*0QBC zyu%&}Rj5@D!p!aVqGv;S+s)K-&xyW|$d~VW0)0vS0@j7g`o8aeX~k(twF2=uIQZGW ze%QYyxv7EV_~q~<*c060s{w)fq$I>?j8>2ye}v>>A$bx_aiuUySCAG%c z5CVkn{oR@k9aAGDwNVBX#7)i1eAZ|t2sjXj%=-ahKWlKT?USks+R2y}fKI z@rLx3x%Y&YiSux15XyLJ{x#o!c>Js%o7z|C$qM@W!bZq52n~$hYT{Hu*RxLy8fAuE z6nuPSmfwsmq0UNuuIPc0-pg$p64ZBgXIsZ~sP!9($21|Um?xUOlv_CM^j3g5e zMnk>5`w>a}$5-k@v7=5VyfI?(rb_p6d>qY$tmb=3tHSx2{_waw>d#{%OQ0(y6(EJF zuFj|=q#PZCUU{2J>Haa7o7j z235k|pUq2PrZyFn{_$9#!r6mfBN1#c+9w?CSF zxSsn7+tAXB{xPZjv_Zf3$}>;>+1v<^is+_nfcr;>p>BWy754HgAo`9i9z(=4jJKD6G(We2SY5(0HPHw{|CS^2xgQ-DH!*hcC)OYTl@JM}Z*A>sV9FIh zz~e~b&rs1km#04p4+WxYT06Tk4M86F|5^!tcWMp|DlV=G@3JGLH|!??B#bE{VTd;N z5q!EozdaqQazfUKF|6r6)#C+>lQ}iN-=hG-0n8DaKECq9F4 zl?ENgl*-T_{KrYZK=Y`4q_O`Wts4|z*cu(Hsps{%DPi|Gh?y&u0Dj z<12mALGuHYH0K|!Lb~WAID>D@Ldwbhm{eY%`2yqB?4SMfKR2xe$T&25&3x#8T~y%3 zCvowYjZYuWsr8z_5(s*+zi9~g@lk-65)^iKbx8G1CN>TT zMz%UvAG`*ObxI;fN13^8^`A7hJ*VC+ZssOS%$?Vm3~W^lo9&FQIxhnrda4sqovWmG zv&&KazV2_&*1_e4vIT9E{x3qK@D~TO%76|+3owFMVC>ls^5t}3U;d)rEmtkmVj@=> zfDFdF;5(?E35o4#e7Av;mTD*KzDcC zBuBl>P-BUy!MQhIch76TXkDj^MkPNIlNxo`Ep>{Dl$6vSB*GKVx!!^O^XoSa?s4DV zCbBA3nvc#80-}CC`IH`R>l+c10>k!8Ap!-Ab|q^)ai*^A*W) zx$Sd6CR2(BI8YiUH=eBxaV^K-Ti23ytSz}Ld!7xljnr1p*V<*@gM?A>Of%ou$ivO~ z_8Wl69BeZXJ+PdpDPk%0_QF@Yg(R`toPJWPz-H8c!&FOts``D}%oogIR|%~=jMk;)?& z$YI)!iWn9PzsR`lH%d>R=GnKpKHL<(k>{>ELk#%9^{3+*?r<=o39yAmI8)Wb_6B7>2jt?XlQ7tuxCWhz2mUF#+P zmm7`ed%ZxiSkzFnUcWu!0EK#zE>xo1v=Jfr7|CMP-D_FWWGY~$Ij4UDDsq>lbGhYU zF=!@?)L2&!0i4Gs*xxe35YUpW?y5cSoGr$4f^PU|*Qcke%F`omxm9Ycb?#jBzRA37 zh+yP(+==AZ^@l;)s9E-U6IDZ0BN~95>v_BAaYgyl39J|8R(-$;?*k;GS!$f9zxqSy zRES@_Hk0G~5;G9KS`l8Jp98i{RnIhnQ_?orN`JsFh?pxjA7A2g8nb$Oyt9e-SAGP} z!_pB%{0CP6$Nlk`dZiQJI0oK zdo8Y_8ZSLtH<&8ECiiR3^r6gZLi@v346t1x350?eV@m*A9WU48$%2rkIa@)7)Aj0q=pg;j=|{Q*#e7DWdMV@^NPs@4D7}mSy8)PHFjUg z0aSX@%VK)P9@x;@KU_Xop?@bxGeKav!lLtyGTIVbEM9J@vpyMaIj2vu8A?PY1*E&XyF)~z8zhD>fT0GE z7#hB7a6kLmJZpVFzi+)i-ajtatl^&fzOOpZ<2;Vz?3JJ7YC+gGv_-323HV4Ok?=Y_ z9iIbi882(bzt(A#%$C1wKjI$jDwF`EGL=!8E7$N@+uGW)3|daNh9sybAv#z1Pn6R{ zCwIr^3=0*e48Q7lI1l6P7UqsG#WJLDH- z21VUjTMk!bh;Pvo_6+a!c<#xSA*={LQY2@#zHMme_+RahegeKw)?yl$=uBQ29;zZB(t7vaf z>KJY-fR2^!b#e#I17ORgFU0G4Pe zgg+Smu8dHjM_^lSE=nN2Y4vIUGag$k-j}hyyoZ?XHVqR-rMU!oTpt2=h z(-wf838gE34s~^_f z6NQ>(;V63`@(o>kyrWC`wA3Kk6K#2M*6QvuX<*QmZ%7yd?q0!W!q@njxK8OInS`p;P1| z4A8-Xpka*%lSAS1HX@&d-sHOvz3djV!cznIp8>Fl@?p0i`WZ39vX(t4B><_jUxR*v>EBmr0 znddYaOs(d$#^Ssp#|8z&D~nhr3fp8yb$!~tK%o3$Qsj45jD9%+xFUb^X!f$0>f(7G zV>!IFavJX>l!~^sV$TzFh2NLrT)J)LDH|bc5Yo zb2W7TyDwqy)w&+#9Sg66g_vr6a(1g%4Q74hu(h&|Mkgf&JDP&NMj}--2^@ z7J8~DZ+9%{3pw+OxG<`V_B_$b#N@l*=e+GW+YCQLMXHQDgk72^S!#l7d}Joe7oJme zF%{PiN4uZrcE4a5Fg&Qp0;2zg(I1_ zL{ySvE>f>VfWM2=n_$7adKwe=18eq!Cp;o$}%OTmX9qg4vfu^BA0^`wHVJJnSbH8|buynTf zUVrr=@5)X56Hpyovv=O{qtXygsmhU!s7_mlhTurVgPy5IH6!}zCF}YY+vq<$--zmc-wffcK zRfe;pIXtvX@yM&?O_?Nv96^4*M9!uJlj8&AMs=$@;K|#R<{+$$my?~66TBwB%_f5M zrnEJ_cpSxbX*6?tukv^7lT}NE;tIP2!^z<)8~6TDTH@4%bxACN5SubfhWpDBypGY1fW_Wq(E?|Vjvts*B7gOn^clXu6_(>gFaYa!niEaB=sPKei z)`rBuUK9a~ivokf*Lqv}d6d9%3}+SEW8*2zLU6}U6jZW~))DBY@aVMusK(!4?{f>| z-%Q%et@*`wu@U;HX5~U$iH(_TlSDT1v`P%f;Tf;viReV`aFzP(L@hu4PGlzb8m5DlHSvkcHBF zNuz|Pl>+4K z)`E$dZLXN(>Jk{6UW za5+wp|A5sj`GdbcRJU39RwJaG@L|yQq zHZmBLt#9xjgPqgV%(e}RepGsD@)a2BK%2~ZV=bu&5zxe~N!gKoiJGCbg1Fs5o9Wf8 z%cCqc)3)S`Drmeni#d+NTo2XeVB5*AT3Kq;P0tc1Gfl@1;GqIrwfMurM{6qlq^w1k zo)(q6QQ7q;n)k&-B%nMbSdMmyZbrl-4ad2ts?paT>(i5F{)3vF_m&-0 z_S{sG{cNJM4~zHS9X)n>Scp?bem!4lYNH_1zyx%WF}HnSU(rOo-n2Knyej~6->9WN z0x=<{DW$Qmh9G)0Z?6ZKqA8PEHGy^lMgs}W61!)?v()PyKL7%|uOY%lwcN;?X)%io$s}Z&p-Z^|{3hwPr>&XNK9`he1)&~0l&0&fneM!!Ot9HmJ z9L^b2NS~CE(*gZf7?)I$+Zv}|*!ys~{UHi}G_y@-m(vtT>Rm!=(fi8-W+06aq{LZe zfpUr|0(lG+Nw^{upVBYfYcqU5P22pW?gqXUBA&m>sb2Nes({FvlH829aSi5PZ+m;qLxs~O$-wAQ)R&o|A< z#nrnLmf0AX*W>J#ibk76R{k~kUaN!|E)yTB58D-ZkH%SvSCck;Lla>8V_g;+zU!$=Jvzg#FAm5;Iy7hCe>PbI~tb z%ucaCOmN$qb__)=jGF?%M$Or(c>>N8z7{!Tt0l&SyXkRY@yMkYeiAEJ2Y`ZS+RMd) zJu@W@QIr9@NwX|C&|{J|@Ug68})>d5uJWI;s# z$;fwEcl;;IAMC(VFhUM5H}W+Z=g=p4=klZteY~fQgWt%dIrduJ9Uo8>Y_1f8O8R8K zr&D(pv}Cqa{EDa?pnSqCPXv0-Rtieu@!p_jgsNLm;!2Oqww-W4cD-?Q2* zYAS@=4@#}%kd7v%Dy(LajwK#ldy4oC_S@^&L6=$$OB1D5{sW~i-&m5wPa4S;vuZh5 zq&O0oq0|h(e6LOf@}|`$iSzC}((ga8GQbZjkxK3?yL)$2;46&63(|j(#B~7Xu9FK$ z{DFggbnmC~1PwI=ho2*+SPWqvOvRrT6#6fqjsW9U1n?(mGD+b1;rb8sh^-Ai9{fBX zdJ+D72H;O!H|2*!7l6)N7T{wMya?e7pN8Rq`ikQZsWiz8KPi$5KEA>tdwTIxRuTYz zzGHaz;39bLbLYDR@}FC@SXg(>0atR@m%uVA@5DzdqL*pVgarkpKbgA-ielpT!q8EB~_LF1NyQezX) zUUKVqM*;2Q$G={4s$4aq!4y+`JeX1i-(bHvK<}v7V|E+}7TjJ55&g0~5b3sISlvf9 zO4Ztn^CqO;w&ch(64Z1VNe!lTfgX{GQ0S?e(1}ykC@6Pk%s4C@+O^C;K#QBsDANo@ zaPJ~s#Di&RJJ%O$D&##ug~sO-o&_SDBAePohdnkfEp2v!vte#~par6#4~x=u(PF>_ z?5O3Dlvk!2)iz{g+%~y8DEA~xLRqDo63MKkj6-k_Li6G#>5RMz=nR($+KQm-7J+{P z8*v?J#evoLJlIV#4$b0*;?UJ8CiOf550w zT!^pzVyi+>PTJO)JSvjnQ9*uq{{unv&pX`{4NguM|{~s>^EYSyHW85!l z&VKlToy)U&_okWZN@>qmga~^Eilf5o_fI;P3P4osj$|b4#DMHTz`@Z3MWME&BG#)y zN3sPi$Pc`;oL8%d%YH3nUn_MrE04K~rymWPxO4-`NpDzxphFw}6`NUdT96-&E&j%o z{XwK2g+{SOxuEzYcUO=T`l28aeht_RRj}jJqYX1p9SYDI^RcoV)%Uw zKd-Ha*B&tK$qlT)0Cmk~Ge_*{lT8Q0YNv z%jR0OuhOg|K)y3}Qw%i^FH#^P9)L&M273HYUDQMzA_2#V;P$rc$rVj)!D*_K*Fk5o8p^z3Aa#tq~~araY#7V-;k z0kba$(U_6rD$+J};u)$$I~wTMJ6XD48w4iH^=#DxyGXj7Y}R!_*D0o4;rlnxJQeqAykxSU+@SK`UBtFdNWJf-ax<#;)GYPr;M>XZ$6>q`0xE&nm0O37#D^^<%Y$iRFHd`v4}oK2d8Hr) z&H3>drgIfwZFE4wTSIE%XidgQipfe1u9mahO+3&ss%tp4XCz8?urP&hb`U>%zDM(S zq(OT1k$Rz_m$XGGOTB*Nbm+>F+17UjaoP$&&-x^Ah&M!=rq|$K>_z7l^5ggW7H-L=}Y}j3{ z8>ejiN2#uR&1>Z$+01r9r`RO8m^w8CiCzBWZf~8Hg9+vhU&eOO7xCrB{|34#L-8=( zUg8AZqiF$FW)uswj*-^I%PgRL5qWxY)N@-ymkt#FfZC!(4YXP{rLv0v`uiSW&xAT1 z$q{D7yVJ-_ZW2Nzs0{QvFTI5|Hk6y-y9`@^YZS*1ZDF{2gQWEG9JFTU0jBh07w>{h zB3@Wb=$NYW7#bSle!7O(J0y|h^J~&?f;-GOd-iTDHnRfxesxoPn;*j-fmLa)YilvU z1>=C|J`(nN#=ASR3%x#--1;5N-RFC1zN;l0Oh6!|Nd>pi4m94vI)C6J>~d(V1zIiO zKXcRlDv*{jy~AS|@Jr!XxQrP2mM+djyi-;4E}3gtA9lE`#a%mWXxAI7R81=9KGS@# zsDlW~rC8Bz_V>RI+Z`L9pR#uE0VO1B_}XD7V4ez7_|L$QdK^Zd-5UDZNo&|!3J|Sm zKJ?1i$h+-srjq#CFCkk$gU%ux2a1-p*9C@BULFE4!{%1La80Jz^Cz#yQlP_5ZTKgq z^PNMB$OQc|F)11v8fa=B&xfHDn1jL;1#CpF%?5%1eY@khmc5pz>mrWm&!gXRJrUs; zr-_D|GiSY0hBvK-!fm7HGz*9ZE)Z-^-kcPG_Ndl7F>2Lq86UYGg`Yov_S$WtBS3=z#N09^hl&3| z(d^MQPuTz|3%my>fM=i^c~mhZ2@%!g4d(%J^o`yY&Hi^B^Z!Ot_ger@P74?Y<%Rs z()O>(m90N3EzRG*v=T~Y1l3wcrND9E-dAw*-cw0^H>2W8+ZkesjdeZXDA*#mQ{}6h$&$d{|k`X3<+H zZdm^aM7nWC843;M-}GyN74kGr0C4)x)+_eOAP|OvnmCydJYW?`4XW$LzJ1T$-RMH5 zlz`G(h_sn^k9yBBD64e>ID~~SN3kcfDH?u-j3IS2S%Sfm3#gm) ze*f-W0>$@ZTX%A2i=}KiVMIa?z`Aci!5RO>)m^6ElFB`T!a03_<%S46Y!=D+b7d^wi$RWz!Bl zMaorHx0gq9LU-ZISyx>ob7tE)`)~dl1tyOz6TEK?Jbu^V(vO2sE@hJ6Wo5bz)Z06O zA4;#?z2XQ=4MHwn*JQ72i5i6hoy5npfH-H-jElMlFjc2MK|j9@aM8@4eYyykxv`LK zm4v8%%W2R!4;6R3Gcs=qoDEz$^Jf)bi~!ncuY#&7x0tao7mh*a{_mQ}g<}jcW*+A< z{`DmN&(n&@YY)XRG^(AB_n9hzFFwR~CkyfqPrgyZgtXv`dOo1uWG=(60nT%ZiRI&# z;P6>FRbNJYa?ps@jAtC8Xor>IdW%Il(;xSgs?_mnQp(%G+0O1LrnpOv?)g6w$S4)Y ztwZNPZ_PD1=yXkqa#r9yeivB+Y9Sj^YlU>2Z1PWhQlWZfFU2XQ`FfoCGx{9`9OnHi z{6Upa1Tu$fQuSd7<-LFZfWhzho%ld%3vJgm%wBlOr-EbxK)FeXn4xvJc|*;dzDbQU zmZ&-PN!*PlnY>dGk2;5iwc}^zNoa`eWch9(mS~^xK(b&SmZ)B_nEaDlYXJCL7WZf; zkn&tBn@^Zog3G~ePzz29hesg)TYqrACpo+T`Ly{o@1s8{veq4oiPAK1u16d&2COQt z@p~O(BWN)6)kP$gZ5Y^qI*tp!Br#Uw&Ii>y4^*`2Ip19ZnamZ~!>#kPc{Afu+w&8D zDfRRB+e#NUc3+@X&E4vpu5?S%r?C3*O=A4{Nr>H_CG~eW2%*LWI|X%;P5wo;(^G&Q zgkXpM8LYn}XAvO`BT0fGA^##HDFkreXj(Cg_#d+olWw5 zWk6oP_7i#btnvAaJDZx_GJo#MgLtr791)u+kL}CriStwlb4`)r+x`K(^zDIB5J+#8 zx&IqX_ykiBxgrYHsr=BjOp-iu_51#xpP5(p0UX?kbdVhDer}_2uzq|VjJ+12A{wLu zOw5d*m5+I~oZhr1jv^<{|D;Y|gkp7i&=3iKcmuOwAz?`DyY%r$d9thL@AaG)iTTF^ z(A{=s!hja`-^q;t1K=VBLh35sKP|ItDw yAJ`|>0bl>`u`O6A-3*XVg+V$28s&S8ts;2gm)fh{`t!JI{!3)MQ&}`n^hLm5&^DCfKPX8XOHoJ zR@wL9*I#vgVxy5N>~7g^0vDOKw>o9O8-p#|D5+nZQVQT>uQ#-R$2kAlkl6q+cQLC% zTu?83xR?d>U!N_7l^8$Mig`_-AK21|KgmNS6N_hXGv4aIg&Y`E(+0 zUwFjL2(W*rK)P@)dX&*}N231#92Cr?08Hru!B)9ps|*>=Tb*i~s1&a$TA))rWe#L2Ar#iV4=8i?O^dky-XJsY zSj;siHJ+U+!#0{+O+LTnSGEi5llfe6;I>S)4mz@jWoFV+=;}FiwQm99YY!-cEIU)9 z7C@8uf=Ct}b&OOTboQ!5Sibd~G>w_OWI2>xgCTI*qe|Ky0}?zdfFh%@t~Tz4TN=WD ze!(3XaV_vT*^N;yP-g*jC~7^}Q-#Go_txkt&m$QW8a@rsH)r((hp3MQCC%Ev&W{!J zL9Yt!m&Rx3cWE4*Z~3M*4=>cIDQ5JRfUOqQ=s7!pqy?nFECm|gFR{jOUCQE5wt7mG zi*>c~bZU~1e|Kzfv+LE^HrY&+w%Ts*%^^&I{x3W>c4KIJps2tZ1DwJkSmEd zAN~X)^l(gy0Agfm8=FF4p_e)Jg7iFZ97}{=5%nzc`35*vX$o0V5%e7HXl5NhVD&`y z#41zQQO<6%%DQG*>K@&dYXp%XE0F?$qOudA^~TiZ51y{srlYmZk-b?RW{ zSVdUVC8#=Ze=&l>9ipl?_kw$#QXC>Ik9aY%)Og z4i0f=$E+%hM7XNu&s55&U8$B~Ym(Kjwt?0(0Iubl3&sTskWl0FOA6+JdOC)wc*!be zRfzn$%gSi`O*IrfwPz)VT5~;wi%a&|vu8jnu$66I;zy~Sw@uCpmgoQTfQYapn0RpJ>u29F)v8^@^uxRVbe z=x6Mx__Mmbc9tGj%Q`Oh-l0s+)9aHT7Qi8-vU<}HLeGAp2QvKCLh0QJ(AAitmql~4 zoXd9lX*=9*m0tF)-3e=R?YpiTU)1v2SV0c%mum_4tA3l+uJn9UbGNQ?tpWVY>U&^V zr9blgTc6TNh{e~m3S0*It9(+EeQoe@s`i*UI3BdujbD1{srTm)PCcOKe6_d>Ax)~W0kJ8Qg?7YG2=Mnuy4XFA? z4FdthdG1%4)bdUCPEa*-Hu_0?dbYlm6n6KP_@dVfo8a+I4|8k&gJ>XRa>Qt>_KJb> z)FmVZCmy3RZLl23z>$x~s7!qoQ7pP;FY8b*zP%pbz6;9rzDW|g)yv}fOzb{a=kSP=M5jB zoSp4YmLx}cXT<7!9V^hIlD%`wx^gmTd+^l@LxS^6;mRI<%Ct@s60kc*alfVYer zA`Lf-`dNi`vSfEU+2xt|k@%})&UUNrGtS%X#ipex^()f&n$EWA{d3#0@qYaYj^Q}@ zIr8msM(JJK#dxT2?n z$N&8Xs$S0?0iggI?f$#-W1DzR(L!}(k)SUy=;vucY4puP#F24=r~Ag-{yHtEvXG33 zM^!ws?)f{5wB!?(($dmgg63WA02TT``E@c&#+R#fMoQh)& zuqS6&iRSOLlcZ-!)H%YJhZCQ`>H<*6f( zb9#?3@Kb->i2fJAbwvCq02pIg6W|@hf@;{LQP%2sNd=GfP{9?RIe~BJLd&`*kZ+l7 zd{w=CTuvj|DRQrB`mqf?--bC!fYt5gUlI&o}BHYO4r{Xc=T%=4-%of zUs(?$hz>`Hetn&>*#^$EgeZK|7r}Qe-M{DqjAusoN>f)v&IEF*Ew}YMLrP(}Xl#Ft zW4*sb@Lf}H;a3aYgeZ;~1$V}zOlO@IFW+gvm|V0?|D^{TNIps2S)V~n_URT@=7T4&*JCyWF z+7v)3%~@ks;9qefq~!*)PId3}phFz$E`@+wSO83Y{;4Uu%kQ*pq%(iHhwWr zmf42&i!gZ1k1!n-o2Q`b=+J=+eETodJBP(8UW(;JAxin>V^trt^78Z()NLmP5RZqQ z91&(-=SbQG0BU8EHeU%#5l{}pJ@!(v1{-XxUahc<YWF~Ge_h|Lj#VYr_zRx{eoROcJKywjDQIc6j*)yOof;9934f6Ck zCGC>NDK*bwi2o$7Fm;H;`+!9)mK*;gOMNW@c^^PO*UfS5QQQ5CxdOI>JOG!FYrGu) zjzA${#vtL~eKFv3;oClmAPw&2bq@)~pdbJF+3WMMAXx|*ay0z+i^Eh#0EMGJndTxG z!3$K1vBo?1?*4aKg8y~$0v1SO1+w#Bmj!T62rkfe0;Uwx$B$PX3OE1fkFL91{!qt- z-g4byygOZC9z()HE+?|IQSr>{!c|`XH7KI^2LSo(*|iCzoi2L-P?YWpH}&WW#E>BV zeJcXkl@hPUiz$D8(Wff#gp(0b>OJVnxi>-f?=JyV5R5yQEZ4wT^#6b@*ENBhZ}$3y zI}j1V5LEmak{(@r7uddy*DfY$9uL9WAynW~y9g*<$AJ<4_kCmjcYvYZP$xU(UmS{C zH@B!RKF?Mf7#tyiWz~xh0J%Fa_|E{IUwU2)41tH+aNfn4Aj011e&Ge(>huG1<6tvW z#q{EMPg7QqYhvI-c^mQ5!K+LQ>H|HDxdS;P+c8st&Urn+cH81=zVjH5<{zM7AR zS+)N8bID(u?2Pw*ws>&bTX6r`T|{1nhtD6q9Vr(O(m&-(x_j&6ImFaFKV9IEE?eL| z6~jD1`e!^^u1_DR#5Pv7?TNCX9(*=^u6ns8GC=mvTisMMbXj9OpcQHzw^2rIs08I< zST%XFTIWw5CWbXdZ;6f|7|Pyhvh@C0>1?f&XM$5y{v zBfj*#bR#@3>dwFL5vf!Fs^q%A0yr)bm#&*IgK1Fqm(Im87X<*~U8fwbGxdi*kH0Bz zmKzaiSE?3^_&;s=Kw{w%^_;s__1O*50jIbJ{m=(==BJ6vs4G=Qw%a#V1-s544g&Rg z_QXoE>b_W}WRbQ@hBh(1WCk)t!QHPyQ>D~Ojd3#lo%O)d*AH1^;|AB(+={RK2FK?Y zHetR{8Z2JMTgthyk;C5^1(bP)A=eUX}UTnl!0g__>Ke%Hp?!gUxy_Nk>rf zW;Jd5JtAEEipwiqgvk-V@aQuqzQTSaOGy48qHihhf7Bun9p4~FaFd@w1(t;*BT}3A zwy@LOqB4J5{Rd#)x;AuLZm-Urzi*$^TNdbk{&XL$wPHPGAWm37@~?+s0bQ7Cmb6!q z!LNhutj;=Q_saG_%v<9Q+6R2vHzFC0L;v#sbx^fw@?M0BG}W9vG({wkjmQ)EQcA!@ z5lzUiQku#0)%n=NDUF}xVHE7dAbV{=Jk{=FrU5|XGvzDLof z_bgK+!j`dmjC2`x#{!;3(y&{ldPLH4MuW?wAvor6=g;z%^NIiURjTYg{z65zoau+< zs-tvg>&Xf?`}}Uz=2JNDSy=~d{3PL8iL<{GJgLSG;f!8; zM8sdzxO}E&l#iPUQCrVuI%EQ_2}PIQc#@@9(g0O>DQP0g$hXg^@kw(h+Z;70j~^ST zdzSi^goQCHdFo+WR)M7IYBQ1GZ^{50kGKFfG3aL2kzaJt;v-*VL|ErTTwL78KIXY3 zlv|N+pOcg(Oh780zf*<1C3$EPN}*pD5*dqrHv=qS!b73AC~D&&O=qo*xg zfR6bAtgZ|4Wv7>K*I^(OdA`%b6jGi12+F;8tpX4&b9YYXh0i2k>gomtT71$tN_MAB ze~xR3k*RcSPXGRu)s4?1IlMeFY%=sW$9j*t;4+YB)E;CT1F- zsaj00!|kyG}IBTapjY-;|uAv_;=Z^JC7MBV3CP# zdygCB&welFUAEZS=Mk5^RUm4Rhdd2enPZM9RaTQ_vlhc;)a=DhV`{HFHS=G;YUZ|P zq~Y}^NbPjTQo7C5o#wnc%;HwZsy9C%LG{tN<@mun>u-_yzBpafD%qWjWSp79RpZjj zXjInAfni=!4-9!O&26q%sq;c01)UlsqCB?4|4_S|> zO%WzeqGq6f$UK-|LQ+dRi*3z7_j^+xmy;(~N}!U7H|6H(2R*?#>`W%w(-J#fov&WU z>0}&y&p#J@i>Wn;ppYEzK1-&hqoWrdk>Dg`%T%l+d!VgCG_$xuM)5Qcd6< z-ft&eSPB)E{@E2lq;%YiKN=Eiy3W`r`4^*&t)fb)^zA&pSE+i={iQ}^IBDy!&3m9k zMr;_HYv;9=gon-bifP)se3do*-O;oi@*7?3k8~v#ID*&bU41ZDlX^h59~%Fi10NVOXq zpE^Hs8bi5gjb{1I@0?OHqUCy2c%=Dm8zarLCq!eYc?5lMa|B4Y$uSWKB=pAGp`CvY z=SGQcjji&Jl>)u0p|s>2g~Tx5#o`nL*LCJqImKFN|GhkGIF9q&t{{A3x0td~AP@fQ z1Ci_Z*B&G}?EWN^=m(4@ zd{z<}-!RiZPCPR={k>hPETxh5FSEN=%cQQRR{gr+%Tc2cB_!cjsIF;+)JMPmp6zgX zX3uT{KbGBTGsc5`9+$h;$0Y`_^|2pjx+APKMyz8u2L_Z^mQzq4UWN4Ed#D6G(kj?= zB<~(t9?zPfV^Nq{8aiQ|Mm3Gp~~}`<>|iI6m{GDKDXrOjRWp4bLfyj$nZlpUf=*%RzZq(-I#j|DBxVpCTu z$BUlbxKp*g$Z_O&hmPt{SN^O)Ekx+arziun?ZJk3^l|!6Q42lN!+g17${r4c$+I^Z zlvX2ONcV7l=+g=oDJIIT`g|lF)l0I~mu|l96dQ+{#PKmCeW+Pio;Yo9MiS8cEuncy z?j_gq2KLwW>18;jQh8>RlPX?|-y%ZiC9 zrn%cI>A!n@UkTDXZC|-R9||3WzjD$$YVh~!O)k78@?98{h1@t_!>td$Zy2&qlV)aQ z8<$Q@8hGq346X%LzBpGK!(H0Y7H*7<4IilYnaxj@#^Ix?2We8?Q&ex zb1gyV5tffdibvq$qTQb5UZeMm+&^=mm>9&<+P1b43V(;s;3DasTa57tx2Mn7j~SV3 zc$KIy>4lHSqf}^kwOn&%i#$X3+C)Xm?{{n+W(I#!k49XJhD=lhD7W#1+20sRbql{) z82u31P0QgoDEb3T0I-;Ju$@9cEe@E#7+ewE!%CsJ^GNT(gG_t3yN=gqjQ%({g|T4q zNhtcgw+c0cV2KvJH2}hiq3@9M*Z1Flyofg~V{_-EMPK~)v-AH=J_6x-(e%BGHa(q` zU>Vo_5>UMH=Vt%T^dLIm@TiDN@VyxG*NK6I&NcCx^nYX>e}2QH0Hkl#4vy>>ui=U@ zSqj+}VZ4|=+S~?trn)V`#hUg!0m$s8?WrDK{dd+52`j>cZQXsX7q2mWA3O|y|COwZ zd1Sd7xIdjb-n$pC!H-GuJ7B3K7hZ`-^^IFM&6s$uT`X@p_=(gC2mVELhcWFhaXKym zY1+5@!_?V-vcxbcpUme{vi?zD_Km}x_*f(OO&iIeI$vMdoWGG>&_uXktl$hk}ej*z*Vc@OFd7AfK;<9&@U9$LA9)&_l zJkfP{_|~7DI|sW`ZWv7V=ZM81s-ttB3ih*4)To3ShXazED}##vN#&%#yzUx5ag4ZX zdY+$Nr@~}z%~yT-B*OmA4_S#yx5qsnhL#*1y|QrmQ(=Y3{hi=VGt>7uwx7=epG2a~ z-4|xf>nSR`i?!#s-O6(7HM*FS0sV(Q&(e<6h4B%zN0 zjbgElrRDe{QJ!AGdlE)nN%T5`O-jn#n^<4N3#lHn>3^N~)uv+hwhp@AgYoWdy1BDj z#({k$MQn#>kjLvwTGY|isKVum$Nt#5(5RFtA-`<14_3ns$lDIb6ZVKx-X7TVOFq50 z`@_vJ{x=3*=7Tz4nZ+QQ*f@l1G{!G(hPRweGA{H)eFT(r%kex=kSjka%wL+XvEHLf zn9Hkmvm9Etnrt$+%Rgqdaq{U+6cX32-mo24D^%16QiCWf{y+H$VZMF@Ihy@=7*7)n zg~?n>UX(f2XX>n~`B{F_xUPJ1a8$zkod_93O?VG~by-!hc2;1ZdJJKY*H)~^S@}QF7OtmB;)$7U1z$c$eUwD z7ss*eGW0@8W$3{>)2YY+4wrcdE*}3jgNN8$GKcFon&E{lbg_pmwQGchenqked%MFp z&fhR81pl&kwdWMo6Dv&xaxTP6+xU(lbq zOr>7-%rx7z;`mYh7sqZh{~DT2;3!r=U{{_|FF&)_ecis!v{$j=I7COHQ>UcwW4WnJ z*D*3DC_3Wr`L2$AJcM>$8`M@WK7EEp2^$g?35I~OHc%h|GoZKc-}?)_c|sr>H%zI%>KL`xk~tD} zVlusdw2)|}rVK5>U){47E`_Zmf-Wb?j0M66%C=7ged5UzQc(eMjwt&O>Y$IOz8zh{ z{4SHu15KOdJL^2p?y%`F#&ETCvaVXa>DTEkgYS@8p#{dqaWb2S z>JC+9RK#LhnQL3yt_3!qazYD)+{EZ-g{D|X4i4LJ z4D3?&R%YqOnwi0$&4K;glZV#N15#>?q{ zolZ5%vF5t!=Vy#^qQ2K5YbsUPcgl9N1Q%K*%%)7Ky~|%DCslkB^4><7=Ali&sk{#y z=$0Dp^8yn?;|}Bb-(-&S#kj_qhOG}B#p3j`$4knKJpEj{+W0wT{xW{kM5y6K?-!a_ z_bW|4ac+A#y1&UzE$0Lp>TmNsxD?R{ui? z3?l=(vS&#GUS5$lSHxl;Yo%XPIl^>!dxf*fvm=CBK`l{i@#|xW>8UPNic&B(9(Mo( zD=zHjR>Y^^oD`m#wvdlGDX|am^xb8;6MiJ~dF-6UbGsbD{As0R1sYP4V>vg}7?lLH z%>)Do%2{>Fy8DwRPLH&Q-m5&{mX{S5vf06|Ti14!7%rNV^2J-}eto~Dw)*#iIlFG$ zpBD+4yRn~v8fkdn?``_A@#rgFx%Fxdhx?)!qrgB*saW9n;mbAXUKi&;pF&mQRWP-M z&oyc0dqrcB_tPy_>J(3z$a(LF9bMhAO>URlbou`2ehZ1qPgKVIq0mg0SA>k-!Ez?- zL@!(TY-&Y2`ddVI)T2%U_|=i0Mlc~Ff8l7jt+H8x0wP+AvAU-OtGU`!?U9C4pYK4) z`%wy?irbr6p4%?6V1vfk?R92;S{@1T!owEUZ&vItaZkQkzg$fsF`oRX7yC^;v5(;m zK9IPAqXk<8oMr=c_q8ttzK90cjW~!6nBSD~V)+oo9;}^#`n&goR6G*)gZ~mYGjDAI z#{Wm@mtNQ`;_n;(#03zEtKmd+r~CaCtk1Ba2rWd}Ijd9T(p3+exyGp897)#4_Yb0X zXF1INHV)*y5Ae_V)l*(2^AL{tNJUGO|`4xJ#h40C^tz)MOR)7!)_tsN#N*^+x z=L^YyU4MQwufiVd#3wJ`LDkFonuO#$va7xN|9*gbT#5U#e^*L6IA+j4ors(Yw5GG` z{$qD|J@Iv%SelsmPGDY%PpXRK6t~2i^9=yBNx3d^w5UazVhi~dM+U4eE&$(K?=f`7 z?H9KhFS=E}ew_nYQ90KyejhZ`!$A4+9BLQsDh0r9At?So(xv$;80`I3c;15x(bacG zup}8jTVIU*s4=mhZ-ngai}$AhB7TLx^eDI@7&Gy zx~)%dwH@zJbzYWt%3^e-dy~G`#ttNT09UFald2$2>r)OT2|+)D2xr;K?m`IJ(g|OMHTq9-s)_? zg5TyYx}TxVgnxI0f-m0HTE6RiuDnr$)Ko%Y5AK4pZ6PqApPg>94fQxZKmz7fEX_s` z=xJJaywzh0s@dg0_9`1=1bh}9fd5!`wp_ySOb+OF3G}_Uby|R&uoL5pwvLX|tFsz= zR0oV4O+ZJBxS4k-aFY7y(xm!)FRGbcsS>D{_X}SfGu*U6+%CC$Sr@n|iffe``sjj3 z$P00(c20%Lig#gwyOzMJ%Yydowx)7#0SeAX;4>ft4Erz=S%-xv4YMO1(}_|e3@eF3 zF$(C8oA-a>)%^_q!?qWzC6o+qQu3*ib6FVHjHfD>IAEIL0{;ws3b$o$ZSKACdfAA@oTF9rOuchO zhdXEshvMtA-rxMeVL42j2l@)?Ev{wsI3*p;1XLn7fL=wKvs0%DUIpyG!hnwG75;}U zbs);CpEz8~$gW(m)i|wbcKiJkji&_~swA}wV3w!XU$5Xu0fupF$P$alPLHUg6ulM{ zQ9f0?B;J`U=spz$!?boCEz}zMrtAB-$t^eU)sN2{@EwbzX&s$kvjpgkIRgR26+4t{ z&>a>V{$E8B>;=w)ZqePCVUf3i8gFg5YSa`<^lAWjhA zzf}!XwR)`N<h@5q2`5F&k7d7h$0YxPd%D1g=_*Ze<5a`CBf+ClVB(Qz>D$ zd5Wi5OnZBK7tlMbZ397;3=qegQY>PeB5)F?uD774tbvNo*>b8fPcYlI@bVIHV>4RK zXbJ`m3KIL;5W=8wNd!o3D6YFRAutwjzS1bhF*beelFF6!`I3bpK<1;~PnIK(&L6sI%1K5YqfTkdoq|bLNj=`f>jg8?&P9<9m zr%D*)jD4%E@tq>DaF_xwwijpt?{k{Ow(%m2_B`F;O zg0za#p}?jaR2rolBt$@v770Z~>5%SjDd`63Zs{|5p7;H}G0x93&KL}a!DjEZ*1hgE z=QXd`1G(CJY*N;C;5a_W4;On5Yf->@m=0xWR@p-s&@G&oq>Vl)_Xx54pA(USZ1A~Ki_XLP?4#tNe6@}{C&WJ z^_X1k{rjd7`;_7hZb8o~S5Hq@PkX7}sb9Zdo%h&iYim2dV^)ZLJb`gM3SouQPs%{2 z^jzaN&E_79^mjAM3b1~uePDHygaMf(lnfb58B57p;&ok^@fRF?)6dfF!oG_*e{jie zvU|b_AUn7yAdrpMCH=6Iegb}dnx�)QSX+n}Y`M(TAR3K+Lrah#j-(1ix|dO!6rrs!#1TZoqV2UivS3Ib)(Owd1t zBq^~gLM{CJ5Ad(lj}t@pWW`_WS2+DBvTO;_!`=&I*zEfhDBT}W^ehOxa;qjCa$f%B z0+Ut_e>F7Xf;n9MdnTIVFPFkoUtT2$TB#V^3l-o~E6Vw}

#VFB6gpWA!pCl~Zk` zU1a|21t6{i^vW+dECcld?b0aB(kT27k;3RNbC5p0Q8t>qA#=R0Rju|nJltkqJI^ns zy#D-V2ObO7?(LY1 z^%iY{((PA3t*YE#X^&=Fgao2e=lc`jHS@`akI3)diwCxCv&hJs7O++r{H00iEsate z42YYBx@uYlQ+a2&{WngYYv=KvJY-Q}Fi);?YWY!%C@pZ^Hlza2s%u5rq91Qjs{!5J z4ys8hrDj=`cwVE+RF1zB!N%=!>@A6}pFw13EJQr7D~-AJ<}P5vs-(vr?MPIMvF%@D z#rYNA*Su?AcM(j8k4T$Utk;!5pb@7(y^cL`CEo-kUpOVd><9>G*uq5?k$xM+0zkSWx(y4 z+9b6TEEr(i1_cz(o}X-p3?Ujzyp5^ub%P>LmMLy;zvd%c?FZclq z2);MTOBk+LIc*Mb!=kem9^}BZV~f}96T{@BMWeI4SA&k&N*^^D?!$4A3DUs)WI>@u zw>IZP%k!b&rmUkgA<<(u<=3A2`j7O#?1pcr4sVSX+qI)=Nm>iid?~cPH{ak9>cPZ7 zUtVY`H+Gwh3(@Q#vGgiwS>6wA{xCw=b|X$TVO{2b4(b7eWV=XJX&aSAU*4WL zg*V!FJUM#3B_$NcBxx_A_y#SEg}r^EnKhL$%9*XZ6Eta9&6p>au2LD!J?RGi;*YLx zJ!Ya~Y-aiQceX?VxUbjo^%o(^u5zt9)qxw*-#E0#erS*Dll*pbM0l1){?wk82Nnvl z7&89zqoLMl^Vwc+kMPas#{=t6-y>m1Az`~QMb%e(EdSIw`#7HC+_lAhD?j?5|A>;l zd5cJDktVL;?m=i#)<17fgJpblkmk#?W@PjIofU0C13v2Y?|`qriVTvSA{>F>u^z5^ z{YiIIrBRAWi_zyI$7;sJAhfGMtb&8QIL>7C`Zr0VI7UgI$Jjr`SeP}VPSI3}p7)WD z=c>@Je0A(h)2~Cf&k8&GI@%<sP+x^CG#6AH`y=P0GH)Jji~Ia42ewFzev`Ow4c?oZL*@wNZ%X<4?@9Zg ze~*$5CrKBe{Qvoq=-JD9RKmxX=O>4};Hn+n&gdWydl?%P>$v*C{fSQ?xSYj#?RP&y z_L+GKvpfkr(BozW^Mc`#)&8<~=uP&JRgFA@A&GRFSqFFv6t%JkmNQRm)m`~?0BKHLBimV`QF1b)lSFdUmeNMS6!`i-DU+9xt;voz=Sb@{J-xc={fi>_J}XKT?J`=&q_YHZ>I##*#_eo=pm>7R}#UlihA6oCo&NJS_? z7zHoSb(JpdCIyMb0rZv(7~9YV#-~aI<{L*)3F=PpdmMNcK`&Th9>=bJ>lU8lmRPJl z`xU)!we&ZoB-8P8gmkPM8&Q7sfR^1%Efd&=53>`I5e@i)@F<){#1I^USwMo(NuwuT5?f3&Qu4bqAzGFnrqtBdQGyLn^l5@QT)HfgQ$oH7HR(-HM zSc&$y^u6L_0=*EQ>;couItm3~+#XO7YBSVD`O)|rw+oY%nRk%HS$Q6B#ekbtx%osx zU|rQv>m^Z9o5t@Avq_06>k*zf+wB^P;kL<|d9PSDRl1I7rjV0}nr|1X(6%gsIg5+R z>_S}Pd)tPseeY)Coo5BrrUIU4NA%{S1#{zd&=(5NE+VL|*MO(dWTvXrLPgB)_VRC9 z`?n6UITyliboFctW?TkuRc%XZORDVAnrX6of!&Yb!YcOOpBUA|-9{v35vM;6w>8%s zsrXG}&(HViBnZ(-7qFE4z$N+#pJg@(-w)Ofzh!gLMM>1g2k7~p>sR?gr!5r!fyEMIO|eGY>X&{-^CUUqh4H6CPE2A!i_R~C zy0a248wj)h6NSO1fTth^gavj(aL4F-Agw;7l+Q=;12xHtSF7(i%qeHDX09%{t}UZD zs6z!E=b1r8n7X>N1>3;~(P@$HB2cpN3iw{OJ!XYZf2~(hxVrpRY!2}gAZ*vz7}{WvnDlM`XnQeX9ZYr( z9Usc*#IV-z(tGWEN^MGCZ=qOUa|FjUP0CriSwhL(AfF{_(dR`+ICZ}W7?t&*^@@3Hb2rNR-mai>(w3A>Has>k` zTlU^Hw%rZW0;>)&{U&Mq=1`M>j&eB7ZAwmfL;myWGN{f1sCg%p2Tk`DcjlVpo_jVS zg!hu6^gl}$iGf9%Z z8)7io!Ej30EPR857i#+-MU6&dUkOuh~%MXpOr8V}4>vhZ^ z)A&v`dp!Iq^a`TpDU*IbuA8`MYQDqT!`~6=XVtyu;s4oTW7JWQ|L9YyUWHQy_>Mgq zFY_I0FWv*UXY#QElMEHP&#JUycX9|zINQXQ-`TuXGQzVUjd_QaYKD@zjHskcTEm~4WLZxA|ZX|0OR@n1sGi_UiUSCT_+SjIes7PJ3z(+64QmJ3H?JcE<2X2Y= z$a_JnUzELZ@p8AXc=VsLokY&mFLosubklqB)kHrrQFvbYim+2;#wH#Bm!wKNzJ<>e znjKyT{qIrUE~U$u7@8AG{!K5Xi9y0+MnaJ(#FNF|=+Agdtfprgeww2rxHw*tEyr-w zM?j5(+t~N1$v`R!85INPQY%mY$B40k-+R<9JxCK%vO?(fK02Zo>jjk~RoPEjh{P}; zS6MVh`Y3gEf4opSih^BDTJprakX-8J5?3zgYddgc)z@s~7jwn>ve2fQC~P!RqF0dm zr#orzM!wpo0J)CN{F#)2@>>%(LG#p& zGlsxlrKT-7S&yZ$Lq30CRi@^?F#~pdp8^wxTQ_%-?Q8i_FP-HIPoGY_Q^{3K_~iuN zPzjAo&HEtgotxD8)NIV~hZ~&n<+dBnPv~Fhx>EF@pzkGDW)A+*DKgh;Au)3B1t2ds z#U2_|i@|U9ULRy$GXzT)#ghF&H?h4P@Yk?(<0^c>nJnbJxVw`X@%ckc0-_D8-pj9H z#fGo`E;kQOcn3D(DGK{w7D&tXF{am2u7IoHqid5NvQ48N?;OfJb3C#QuG#YKMDs!n z`Z*GJuT{vOfLh_#LeBT>Yrk8(5c8!|PY>EoL4AeZv^wL|lIjmqsXg$9xcVK$k`T=3 zePry|l8%eYgQoPfzNRF|@wRRM=L!NFM3tYqHNuQ--1RKBke*nub%r(w}Pbh zG(Qe9s~&?Q!vw2R;uqzH2b8CgfL_v|b7mx&ude=nkoGH0!cz-)LwCW3f>`cK7>OhAq(^DRTd5MUn`9Px@l@nu z<#F0@@EPaCtw9P;8q&KG^bT)>+Q_%Y05E@!IClO7(r{c2 zcpPaLL??}Uqwel{zhG2NRD^{o{H`cx$7|&JRxJw3yyRw5_lkdllTr~E2PBw z@4fv>06`BTF+-l3|Zv$Q;@H&pV}2$oJN#rAa(w^xUz{vI7hD*c zVE5`e#6&9=MO&s_&|8zldgM)W=b8DW0nYlyS`aS9t5z!j4WsQk7rU-Hp?hbA!@{z{ zcjUkX8RbM>-nvMiPWt8Dkn7;Wi-Vt8>9jQ6;Y5}%Oya=HFqi6&2YsI$t1^5_XJt*; z?ZfXGXkU02T$nZEQs+jQLn7;cq52SJpW=3Oec_S@ai;2D2c&@$uUfS8T=M8Eyd}}UiJMRnG2Q)=iS|pR+FLNG?y8s9Ds(cnN-B(qov!ciWoBvYR+Mu zd^l0!sflS2&oV4ndq~T3GqIuRqTyxG(F3ws4wS{-+mOT5#4h9>zGq;R;fFncKRUPs zPMn^c8k@61-twpO@!jxe`>2zGi1BnhQw_`jG{Fe=I*7J8^#%_&4!228q?RF-6=OMp ztm5~OMly1iCS)z_FWu~i-i!KrWoM458_CVzq)vG~RHoM-wa$8IHpOpd7MvRnj-hW??!k}&~WZP-dBnhj&B~;5|~hRldCxx z=&Gc`$&67@7(A|z>h_$9tG=BM{ zl_GC<7OZJY?ppu&6~~#-v;R`(hFrM)oVwqx3_QSoe|S5S#YbSy4dG<#UduMlz>mMzM3h;T?Y9$&AlLwiIiX+huA~_Xb32gx{Mf{&45>^-vJ}rI)i|!|r>obVSY{BjMnq^FMRVAxtnwks8X>kf zJl~78!`5q--yvj?An&%z;e$-vhtoV8<0TW$<20)5zYE%B86SuvHa0B(=qFvcB54zL zO`(!6#&aiEO*)aXzOwXKRvG2WZl4nNT-v;(4a8>Wc0smAKEcJ z8{daf6<$D!!(nOwJ=p@d{!zMq3(FF}PuYkAZb@7vd}aZekZ5)~(YC=MtO|%=Nacsd zfyruj#awExku6!ttV0VAl28Jaf&#N)U<1_5B^{IZrPX`WTBA+X&{ltavqDX#q3XVt z`*9&U)>h@>1;g2V`@xCbXXP8%h?LJ969nfX{uYJDtuI@{sVZeAz!moAOk=ab17y3V zVFBzM-#~%qw*#l$Onjr0hMZ9pm0VOF$qVc1@r|XB6E_4eh zZpZc3c)p{47`6awzLi#+e308{y0#DZ%Ph~oqyXWpRr)y^!V!Qi68%5npAteU&-^KS+|3^<+b2+~AA z!tmi*+zaAHY`pKE5KU`T>Y_xESd?B??NNqSw@ifYxBdREPI^Nre*P2j#W@9W@9shy zO`%R=>y~{WnJ9demjS2KY1Z4uM3kK_v`UYpQ7ey%tlH1CZ6TR(UVc}dCUb- z4AnG=)!ENTf~!T4PYWw2QEF|D;?IF9U%6K^lMA|Sh>*8?n0S=tTLxw~q!YPa2py~s zZ}!cK4L8a;KN2k8@k?l#9nzl2%BCihK|9aeok+XuSY7F(xBTg#(SQbdOYLuDxx@38 zaLH5)gy5V;vE7t5^}DOb45cJ~hqQs3XgU3X*(as8GnHAMT}ofvF>J(;%kJhlPD4`g z^5SFfwg<#q99s=~qW@Wdv1$@xZnCGwB8eU>Sbs)oeZaRNT& zE0vzj$KU_Y&{h`Z0bu&x*LZlkjY zAU|Tr6B%vEmfi0q^s@`&Nr&}%cbVdEhf@n_#FaQ;pP!RNd$pP4=r{HYTmL`}^V88A@I)4qZV^5~;QIAQ7O2rmUVcX%zELkW_~VHc=h>Cyw(ggDnnqLb zAjzW4*Z|PRy}50q>bVWi+U-{x4x07ZEMUR#J@C2Bv`LSgJ11sxmd$jvJa_I+MO!%I zB(9^81D!j)xSQI=_#A7PHixuhv!K~jT9#(ofDpl`8)or5U%Q1~tZgH>^qcZEy%`V8nzgOl*DIJEkNtS^D1z`SzAxh0JM@d| zFoC@S3>51)haT^J+H*eL>cPw#ToPf^y{BPxiGwx!Yo zk*maZZ(<|ROm#~SHz!8g14YLP)6<%l1PcBlOaP&(JY%S(hvCFjcj3`F=*#+JtNF^YI2 zZPEz8(g;v%>F@<{1?cZ+mo~%^<|V8o(|NF`b<$1VFTX9e`%}0q@Z3+o`F&_8iuv71 z_c9yo{iue!DPS2HQ>kU-freB%lzP5?XSUGOBR8aX5eNPqUqL)A3!%0z|n|RtR=EQ%^s`+Ii7H zZ47M@Hs*$zpNEhQrR5*#N{G8#lT#mkjM+JJ@lc23iN=p9O2(+o+aS%E0OhYT_w z?mSQN#n<8r>4m;AsbQHvBzsO3VzK)1R<_qtJ&(?9Tx>s3O1`I`5wQBT^>R$BlxUnU z*K9U~{xvW1V$rx13l*bFQ#i}E(?;JUoxy$YK#RcgR$(4zbri8dlWBss z`PxbZ8^#$-WwcB-t*6@w1!ss7w~%|ua)N5vw|Id6z(PphH|{~^j4+Ra?VUi{&TfuKs8q_#Zwi; z$sF)Vc&N2mg3INR_Dq(4ZARyL>BrbUV|N)2;-v9;31}t84$XrZ;_J_iR>>Qwl=e={ zxN&e#wxC?BLw>zdDs9^Bb8ky?>X}$m=-yF0{i$Zoqy5-z;>G8#5&}is*IQZ|;k_}G zY>pi3p)3j8E{wbp|5Jm9aa>$ZtEz=C|3MEM)!Jjvv(hZ%XhR?=@7uTK!!7vzB1PRL{(Ks8x>KErA{t8!cJ- zY0y`xPUG|mxiRu*pFgG1PhPdYqtB#i9m>Bd@~Cfa)dH+KaGgawpg>(Uops1;p~f`D zl-K`zr$|-2I@QmN#+QupNcTGD<7n#6_ulpvFEe5XMBMTxVk6Qtk++d3h#d=W)aIIKGDwSrtV+&OY^I?#!T0xhfl`D~u12)=78U{uxfdeBzTVQWs+9 zlpf=s*)pELTVe7u^Ljq~|9djkpS)bhg#Vq!{O@-M25E~vWTjUrt#&HIz}3wT z58Z3Y<}WCwK*j#klN>^HJ*#10xW2PzCEkv9L^J-xnoTLCyvGmYg$IQG>Ce$dhbeg3 z+FYupr#DIBEaz!W0OOR@CW3EMH?og+b;#Wg$)J1!rsSzkUJnMij1Cd9wZm2kS9EvX@@+d*t(gxzpLB+*v;Ja>I z?mBl)tFfta*%aV4B$Og3P^O%X z8U5u8$mSButX0IhJ9pzGg!piRr}oR1CQlqN)?vBWVkX?p3WEBOX6z`+j!e*D)_5N$ zE=2=y#8b)p+LlYRfR^hLX5QIyG&|eEZg-ZXKoNKp*bXHnPPk zzZKdDGxk{xY0rZdyUr5@inyzbV^M7bFuu+VAVFasMdtmk8pB$GM=#mPu8rTLHEIUU zVNpQt2MXTH=IY7AtXg9_UX4Q`FalA;A!ASPEQ_;*WY5z^hw9;U&FpwN>34S;i%Rd< z;(X2fG)CeP>;A(Dg(W6&| ziVei+c(>eQLIyKcpaln7bYF0VRck1blmbz0}+CfMn@nZe%f!?smgu9me%u5K^RG%EN{d5|rT2Yf$-HQ$*XeED#7qmE#1s zqreSfhlT3ef~a(BK ztPW+Dt(V}rnd}aHeY*&V^6Mw{LW0S@!(DAAaaHd7@pv@kM+BANc9k^tTX0G8=biY* zZ5xg8v_>VH6tQ0FBSFoMVpA6755K!_sl;v9o*Am*(@QEz?doDDf)wBR4>bQAW7LkKqi)fnWM_Log(J8dMGwnbVuqhg$SC_HOYW zhaOM(nyw&iwp?4;(5IsI=kr0``fcdx2C7`?8}_eRFUG12iADHyLa9agC9+q+wO-eA zm;CyGze~wA7C>G!JhN8{me^Oq_ou+f;Q>D?Egk53UL^!&-478_GV;EE_O~NuMkL1l z`ELWlB7Bp2KmYMwu@3S~Nt_s)%XZ;*yzI@PY$@B*BAUGI?`_}_81Y^gFGNoL13P%{ zo%psbJf&NBxbq+QpDIaGPV(YFa3Sb@!_9Z`Kmbbe*-Cu1Jw3(#o=u4M5(1GF7hT~~ z7%YDz7%%aB2n-(c)5#h9Gdh+2#uVRK|DQ|Ve!j1*F|Th30KA_l&h9R-g-?##vJO#T z=X|3~F#cF<+M+<)mi~@jLd>RmE*9fnlo)#Z5lp`xPglDiwk*OV(aF8vjfQko;t4zI zLaxW5;Zxi#KugMFwZc>Q93esBw#Dwz1nO@=Of!Br1S3A>T-A;04mC~!DHxESGdUbn zEE$Q_nZj7}>c_3n2e7gB{RGeB;a=%0Mc@yux*0lOm?)Ex)dGgy2{`O47w45e*NEA( zr@aZB+A4j~5NdLdLmK9fJVRNUgD)BunV?8~ArH7!U3N@wl3=DWYab3eF8R}jqUVEh zHzJ{CB`e>NR;%6rB^}{%{@3v$GLnc~>6p)YSg9lo0($S?Q^Ybui;Xx8BKlpxUJ3@| zK{cMbzL9hBZ^RmI2)gf?=Y4UAxG7>+T^mR!yEI<14%;9WTDcvMPSJ06Y3`pr#?R1S z^%cxlxc(j%s#3)|053}%w0b{edzyw4*^Gv`GH{dR(WbBZ>9P!ceMV#NOV+WpF7;Q|(^u@r`GhS@tu6Bj+Nm9UjjgC=nvqskD-fu%R!w(0p$xq$Xhp8Xgg zegsSNS?B>AsPBX?KwuY)1q(;0Q_rA5z#m5NYp?0ZWb7|QSbf%py=5$>KZ@?7#e}S& zzGqc7)n9yBsjCb8H>Ui(N}Ac?`zf6Tq^FhDpq-lG*mO{VJX{ zWcwUemifZ8fH9m_j zSc{kl_CuDApdMnX8SeEUj9&hr>@`zh5{Tp8o%&5NVHhi*yY*D(p_SZ|FOPXjUJMBo zXFhs`(fgtvimFU*}HKg z-hJ=Sa2GnxAMI?4;g_byL3L1-t%~xse-KJ8B@830y|2?eixPtknB*OWJ)wnLr&X!o z09w&nsF)EXdS2x)_gMwwcfOGkw@YHfqfl6^V^6zJ?WU`Bu68EjSmZG=+6SEyf)%Y{ zOK^@-|J=+Z_H4As;%(CH8B+{d-gO2d+h%%JS!3cBG$f4JYaUrGaj4x`ux@#mFrsaT zrnMRW4-G5trvrFsKH%KQy=tW{hOym%&;+8)_BMeE_ro;&lb|@Bd2VK| zDMYEJAkyb&=ON6mJ0X!Gdm%0~EKp$Ri@ezaKFN~70v$aaFL;`+`6#@r`MMj9M@Y?X zj87wchxBh%&**~lB8m>~*fSAU2aj*5zL%l~ktXi9hoHxIuxkqDdzW~!Pw~CvB}~LD zspPbC6(XorAue%3UuzIjRg(9y4?e`;9onGQ#Q<4Kh2eWA1IDVVgyvMan96{E$@b8J z1grjbI$|scU=m=15Yj02GLBf_d6`T#;(^q7;dmy*_QiYPUKEpWW^v}7ArF5k5X98wFpFF=S-zETW}AUeS#a?kmk7c=TM*Tp-M^^h{T)0k*5SI?E3K&v;Bc7}-k zbhgs0jX-e1&@hf^(zE=r>>MOrV)k%ZC@P~S1OU!Ea~~M+K6T8kW_EGb@-3sv9V*9Vj)2wf?mKLA`XUc zUmV1<>Nj82GF*%mTREf9l0#1Yj6j#!ixsCE*NBB+(3>X+<6MW;frly2N^13vF&uNO z2(=nP9WfO~vNb3l9Q?MK7Z$V+@lFqBV?Ng=({k5w6ct*Tpmo0F9l<22iAQ0r#STB* zmy)c)yPA-k-^paYL0Uu#tDkMS-#uPl=Bzvgc+mm%@a9mS!IO7$zx*N|6Q$p&A0kPi zzvOE8Owl8SsPV!dHMr48Q|yI>$Ox8^z}VrZU4vei{Y+gnrlpS^4x#7y+{v*98V%$g6cqGzabn1P z{({n4$#%U-$dIm~W;39~s^iJ&Qd*GAUE`=M6k5~ZQ953(sMU9d7ldPNFr0hVX-y50 zO7xwtX}Zr(_9G^gI2YnizF9~6nx{5Fct(~dWmseU8s#~dTIMQuxT*s2q}S#P=~2#& z+a#s^$fL$n9OF{P^$dbc(})o)6XQlG;AT^<$3NvXrmLuvWB`Y)jDH%?yNZY?zMi9b zGv=5;m|j12229Gc&Wrvq(;lU7=Nn@$6KSccsExY<(@aJt0zSm8>l6!C5?Z7C$`KTf4U%9sCxsA>t??0;qCnoK{|3tKTNq4 zXohewa8nkY0uHsFWU6S5h_Z)=zC}u)L_|bkprCh#RGqPr&mV72w3U0=42c(-S&oj^ zbx>2d&ryr%m47zIRJ0jDX+RD7-lG zm?3xLvmI+TJ&*t#;k#fk(d*HgL7eqr?@u8x z*n3**vWUo!mTrbAVU{Phz6bH_{9ETY4xVW11wy24iz%)cl&)FNEm0V3Vs1=U{Yshh zxz&myj2MYEyQ!VWc0&``_GZar8M z-IyrLcO*^8*!1^n^;-feiHGl0m#c3$AF0Hv6(VJ`T#1H#B;32H{eaI_Z3&HE13I%x z*S*2T`zo8j#V;~}a#`&VH*RQf3Nu^DVqxom-C#t%0`e(Z)g_G?v8IT78z1+pGO{EHGN=T+GW9ZVN_~25B1^%Qdv=hptC6M7 zWFtT|(pLxB>C=lBsMZX_4p2f^>s~_2-Oxl!viJkZS-b>ePU9kSyT(x>%&IUCU3fwj z?(Lj6SN6gFQEtxO`oLl0!On3KK%Par!aq#OI_&RA3{UBq-9F|gXvNY3(6&;h-9RaP z0kWh&s|fs7=z>-r`3jr)aSZF`RDWICs$wj=g^Lwl@n?5M9Ey9@UELDW{QAX9hIKB- zcc&u-ty+IY5hrYN6CPU^WErdBQAqb9Wn-7_x@_kVVHOwc7=>sM5BU|%iM@%i6z_WT zVp?7KU6NUK5o)|fFH!sggJ6}nKSW#F%l|U{3s(j|RDb!nj zu7Xk+hxDiESIPvFL5_#DWw z&wMR4=wl$#Jh6Pl_{z{$I&p}8af-$!E_U~_zVJ0H8Z~z2mpzccVbZ-C! z9Y>)p(!d!({g>>M=6? zm&MLZl#GR@OjYf)ShO*^o1v5@`4F^ctUy7HP`-(v`0x89!~4v%C;oF@9y5@``v@dT z`6A25ag+$(GpY??hNr93=|Nf{7HYQaKj&N;<&rqvX8#?`M*iW?H%p?(=m>^xmZiCU z4qqQ4f!PGtj87H!QNMA-zvugL{Uf+ck^!c7{&Uh48Q42#G3mSG06t~E*7y}-rN%>N z-KNWsL;2r>k%fbyPzb;O?}bjrhJ}mf2bESQz>lVaxWf_uxtiy|s+wka{)RqBxcSftFj@}E4E`u-FD9a=7DT+s^n#WFc$9q1x_703)LrCtGT`6~ z^?qo4QMJV>3~#TSTHB23OApT^A?x5TN=XAp9{QqVb-q&n(BaQ_A64lV-diy4};+kg}$-2v&Rew@d#&y%z=g*cAj zd-VMY5I5WRYStv^SIWH(0O!nmKJ*e#*XGf_P-d_)%pyX?9M;*X>onG@y;gs z&hr3(BPS%>zeKJNd8$jE*_1#^r!LiA)i;d_YR^SDIA5(?)x= zOQ-ee!H@SGnlC^_7hqg;ZD_9XO-ZM%+RYy(Ku1Ja9|RET(lYKVpU#V%*U4{dah=8@ zv+F>p1MVYAJ5GSDjlQX-UB>`?tq&Wl^}hFfvit`Cu1|9LfTaAp;RF&&l-Yze2Qwc> zH{$($B!4Zf25>*N*88jr!YI+n+kwN~nQVVF;P&KOH7{sUHDvm-G;upXq_Omtstg2+ z;27JRH352Ovpbi-{`lg)&(}i9*4Ge8Eu>In)q5R=9S2+A`aPi#WQzt&6V-Vr2gSa$;qoGf-63zy8u2X2*@lX~kFAO@-NSszvw z1=@e7mhp3{@=%5=XIWD0=YrFo}P&pD(Is$R#2V^6Y!I zFIVpTellbZS5m;<5MfxZP4s;_R)#$;kr5s``R8y@MNW$x?pGHbA2LZ*^$(6uR{G|g zpU6qx*8Ld@aIa=-B<*?@g%2*}&B&57f!srAwPTPX%oNEGsjV?&rwJy1uVTk4_tR%f_Sauxg#Z;u2S zX5}){X`-ghd?h3@!h}=;PuT`x(9To`f{4e6)KjXsR3sEc-00F5v4#SlEQB>^%=AxDv4JPLJ^eq*tKm^cuA` zSp3r%M31X4!ryP(A>3}j9;f2Xj{r(1pUEhoC%IF=6@}F8kjfX89)}8olA;VOit$nO zK7ZAxJ+j|PtB3}{%|w%*z}PxpEt>&abU|6`X~47bn!@tD_$3QjW#QJ#Bt;G__B^)p z|4OR(c!y3U6G;@mmqxtoT0#=dy#Q&!5!AHP{FmWfQCJINh5>Ps z32F-_Eg4thmdS8Vk`rjxm=GS%*f31O{fY`}9oBw7U$U=M$J4XUTumL`{|m~2Y!c*$ z_P;*5%Q?6V5D)}lZtB!sa%Oz5;lAdyKY!_~oGNHVKm~BOx?C}?*dEBEy8xZ+FQH$7 z&BR|7L}Ty-EE9i=cTB5*Wcf6KOXmgIGP()G+DYJ9y&vU>5ND(Bq$MPd-=b80s5d=!L44icM@5FyqTVUf1=$bcf6Rk z!JJ_ACH_Nt+`>QPCskWm_9ZPo5fTIj(ZA2&N$ z@e&*nwE6te&@o9p)XIx!y# z3V4I9UC!Mk;W6;#Sjd-slv0X8{A{B*i_hZ;8ZqGsBq|XzB~T~I51n|ZI!X#G2q-Cm zvQM_gn$OZ>F|Jf>_uV-|6GIzp;~Y@e@talfo#3LEyYE|$2e-UUSTPrxXSzGYy}@x6 zBmL~3BDgr*sy9`LwueA)7C#>gotjOoM8M*5xzhzocFUvfC}IY|h=LqrODzf-+-{Y@ zU9azsh90XAbCVZme6}XtHD5#cN9OR?>6#ghhH)?to&VE0x`dR%dC9i+wJ31gU7qEl_8)Y5C1RPrw*ajCE^9C_hTpnc_YjNpI-NnlxSN zyX%BrCsPW`9+QV~hw&ag$p*k)A6cXmWZZ9|_~k@6Ht- zhnYht#uD_25n$Z#V|eI?T`B&YJV|H*Q?p3{)pTBk(?>Xw;W%00CA20Tmu^#U*y zidV`8wa^l2G+vONRJC)Cu(Wy+3fc4W65wdGxZTUyf6>u6ih@JI_5RuvLl@)I&sp(3 zKYi)5j3=csx7%hQQp#4`UITb%qSeyOu>4he_R z{1(?wwIaSTTXZ+W29MRP0ULw;^JYy2QDS!OXf23Qom)34Sv<(!g?8g9>t*wPEaO>5o z%UD&I&40|lMoc;CFzJ=7Ci4wPW*z|HzsI~uQnP5*2X&o#YX~NY-8uVQ`8xN1G&AF} ziW=WonK*{^Z^y;x8Wq4H&A%rOXHDXeY0uW z?vgKUAPJSZnDI?}S+Vo*cmDVxsx(!*&_xt~6pfZ2p6u0pi~eq8Eoc4ki!l$CX?~R}JG?&ib}>kcau~89#dx85H4fBIk#qjGElWI^74z>4%lZ zgXa#k(+{ubhw-2S`*65rLP}I12aJ?1H^vA@9a^E!O?ASy;|frd|JK78wiV~(tya@s z-`Xzc0)?s=vHcC_)qxg=g&XTs_2xB);Y|vmhgU8QKkQr_0o|V$^G`2-1C^!Hjk>v} z8KGjSnQZh_lj2>SU9XGgZpukf ze!lu}7MJ4ywb4@B?8?Mz-EeVZcv6+dMy!-r4>XL3!MFvzlM!V+Xc?KO*nvMFZcNW@v*f69>8tnINVwjw zmz_1&P&2nfC8ocZB=|A0I7~Itqoi_MJ1jmGvEQ5%A^{D*X`oFL2@;n#rJ&`<-Iexd zlXO+Le*nU!hdO*z|?waPavtp&1rmp6L;80m^Wp-U36H#)}SDH~fYRk}iXSVad;a=fu9i zWp1r0j;mZ>YL7OEd1$A96eA8vR$^NX<#=8E#e{%H8<`;o77%5V+Wvl2DupPpP=$~+>wGyJi!+Tv`A-!+RZ+hqwTB6cr*N*!?*9Q z_?SXk&Oz@wT*Od5D~7f{2)TeShWEIKos$#a04|ZFoHu8sLTvJ3#feU(4@obA9QM`k55Uu#XKNPZm**%kzSy zUbTG>ta4S|b>jMVl^tCR<5H5&dw1_M0x$DxQq9JO#?u_PTCc_+X!a#5qfuVZ{hk2@`~JP3sNhdJZ*#DD)b3)ZCZAJ5ej!~?|0NCS)9-}BTlHcNzNlnq2lm(5cSqSP zW(8V=4f4U22_`zhlf4Z?|EImPj*7DF{yicfr64IH(j@|lbhmU!cZo=sFf>vkqI5S1 z(nBM|5YkG6bV+x2bFO)O9^Y5r-#Y)Db8FyqaUa0X5};ix6b82`=0G>SjJCM#);9u(s!GeINAKy2*BE9$ z-cmyO;Y#1xgw@WSa;=I<#|VV$tBqEkN9UlKCxIlT-iiZ{gbwbbpoS(81SGqDBoltEh2fnRcpo* z{*AQED~A{*%KgiGk|7jh3UcMwd_8Y&8pMxQ_K&F-sBLoy+l!JJ>K$;n$X*}1jM}n{ zuWN3qZJ2e7!ZBL06n4igh&M6#enN`F0=pCC7Blz3j3NamKV^iHsBAT5VXj(bvY`B& z#%GxV>7>|k=}^jyOs15X?E-0@676iLG(gJloRzZX#x(NF;it!mTm|-FCY$fLcegd( zh+cC6Wo`pH*WYC&W96w#KGfQHHMv*w*&R2sw5u#7{O9{NbBr@%W zx-hc_Xm%F1o4coC@mljPLO7+MLKuCWPj0lx4?qo5f-g`E)vUg^o^7U=)Dsp^{;iYx z({HykAp|4p8O2>v#1EUrfH*lfBY#x!%}%4`?zO*sz(58?r$yGh>iQ|18&0fa00rll z1p4y<1yFX`?p$s1==>ps3L}?e?~?pWO8qJ7`y5NXj%he{86JJ{x19XoU9s;qP8-5Wyj_QEA5nm*I=P$1<80p9gNJIh*4olq^gbOtw z$@M;rZB)A<--vka^r*eBJsrq<%dpNfnNo5I6DO(-ixCJR*iCADC<#IotRC z{#-vV3I6rx2JU)FFTYX#Kc7=f4czr1m_2{Pihj-x#A@&sAI!d4uDGs0$Nhg_Bu}-veN(-J>U&`;-0z>NJF>0bIdhbpQiUsEW{yPrZ3$ z+9?Pc0EnO?OO{w}q5IT;SLN4u{`G(xPYO)sRo%C^F9ROQ*klx=i{7sBx=1itaQz>4 z6bF&j5(oJ{-D;fzraxZ}tvVO=U*)~E)M z5|9RlXJlssJUTfBU?wK;VEfNsr3vGXi?$iaNuTeCRRrcmN?p3 z1Y;LK^2u`Qh<_6Jxxn?Ggz(>^gW^7ZmCH^Bs6T59{{pUauLyNdREgPd^CsN$tgaE(mrGK<0q_(iQ@@M5~y>Pq)b+0M#t$}nqD#GxA+Dze~HHcwT4Lb zbP)}~ft^!kFi#P5Yj>9yjGhSoJ}1-awyb(<=uzEvWAHP$Gnwn*=n^{u9{O^!*hsD| zBzb=fWZkOi4gQC>_ysh5e}BL1$LZ=R_<+@G+@ElaRY~N4`c-Xgj8CF@W;0Pm39R$# zbO>aSmuULgZpT4{fV;CAcFE%QUe9nsm(GJ6iQDF0mnUW)uv+@r+BSdG8x*>yysnMD zPFv=@p)Cp1r393ocd44c0iIz*^R!#x#25shWkYJ6B0O&f6Nh_>*=|8u2 zJ8GnlgXvX=SLuB?X-Z@A!@zs1eO$~a2U7A206&W$mze~e=|&fV!~609_$QAI zO_uW90x?~4L2{-Q7(=rs9-oyNN5+BCC@HlA@D>qBsEN~sNkGH@2CDTJf?4a>i5kaP z7Hk33!fgW7IY}pRm)Nl28>PShCyM@ON!!8zY&$s;klK@2>^h@R-$Bn~Lq~-IHuCPE z#@Z|DIFR<1_CxzX@D*_gpPJ@XXezZo&BN#rp9YfSZ&&c)mZ6Z2$c*51(k}FrWAYbq#1D`NduMph(^$*;_xa}Y8n+4r z9-t^uL4fXhgb@K7Mgkl9u#z^)N4xB}sNeT#3L11_%J(;AmwZ2AziHMpq5d`e{@E|Y z!Pbu*PWtV|uP^rR2d{-dgmEesZ2upjhd4TlHJ7r1DSx6zF`0a;|}`mhilGyAjJcd9R`tY~isF)bgZ2Zip5*y6Wim_@)3Vn*~^r_xY0D zeGwf0fn1eIMOX+ZKVtlFmm>mj2CKUz4k_4NO}i4*KsJkblqw$h&i`$gOHst&zg3HA z05Gf|I4v$FB{G5N(cV-Xs7>NSUbsOCJYA(A*XIgo<+Zdy0~NqWCIz-H>gXYvC_zs* zFpYx`1`oh=lLMZB99P0NMgW6~#JwB=U{=H>vcL ze~5sQ3tvlOGksUsAHW;)KurdL;0NF{KlK|68u{w-UZ*ScS^)B#|Bj&yq*Ei<&7TeP ztfhe**DBpv=aU{p#{lNy-edvyq3VYqMTLMo!s_M_eF7rDf9V0f9>HeK=NgCkuaxo$ z?BM`1YMBDE{@=?%Sz|Qt41WU!8choUl|DTHx%PY*sy74O!TNp13r|qv=1+(V>;RM~ z+~T&?Sh;zXlYh!iQl0HYm2xy6gF1RJF)Jt+T$Qf#IGOju5ex&)7}<@niY(v`a;pXS zul|ouaRfG}YT*y8ssz2xU2;K@PjzZHh+~T2SERhJa)6?!f`~PMgCG`sPv)MWuY7hN zFrHBXj^g0lZE8s=kZamM4x&%7>yZpiU$!1Cl^_&J; zJr=~Lm6@-$F^4Vl=KM>U_=0gw^YE?5d}Q@GBO~+Xt`%d z+mRvwC&CKAkikPPuwm9N4-@f2gF)8tZFrg8`NH>#ncXhPtwfsh%)&6x%dDK-ULhlznu=_1`D8Pn9%< zLRF9?sx{!B&I5l~3<#at0hRgy=R}006L5sUq!I^?xa>8&^zA4HWzlGff(Aaf{Tw3C zy+!#8L^}@cx&03Bt77S!1r7-A`V+ui@2gRo0ugJ)y{NNkKb*XUTlO<4s|0{-ic^@s z?~UcMJJ3(p=)e~=q?*F-+8^BpimL{--*Na(&yw7CnZ5?hrCG8f z{C3l?%?P~yFyg*7K@lBdivtBdD?jS%ZG3&@Rf|J#ERg`&+#UuZk-@uFz|f)>dV`5% zf(lGE$BNe#pk&eOY=bM+hPEI`1FEasL5XKrMrw7Ayfz2yfw@CmvL}9U`k`x$Y&Wc_lnsN zR9dRdFoSADCO(iEC$SrDA)kPd{I;@z;})lnrWuJpo9PDCU9IvFBvfyu{$k0Vv2B#Z zR=E^@Er1mL7*hiLQ0BLbS4B>I8qt|32p(05QBkm5@?S6bpecJ}_`9Iz=}R6Z1|wyE zDs%}fbv_2IVr*?AFe|~yL?ahC5N)QA^OM}aK+{JG&`1Y0O>qG3k1z|&PpHY7;q(`u zA)_kA?3!YIJezbo4EZn$pE^-wb>(ePmYw7QDB-ivSH=P~y4t`t|KPLjj~K8>DNO;J zu$}3Dt_)OR6!Hn>M;5$-mFeO^&ss5^w@x|X^B^>ht#v;d(ZhsvUj*!J5@+O5XuBG9 zk?GzyN)NX9qL5mQEGi=UC7*745|aT-T?%y6UE}BU@L4kzcM`rF5vId!U?~Eg&757# zwNLJ^eX(LSdtUF2=nX!pGpq4M4L&~Cb@{Xa`E6Xk#X(6UKgpBzxiPyn0TW1jX`F2O zNJ%CYpSDBo`wF0X2oQ2WBydmC9-h0RF@)Y`^p%>6uQwCC^!VJ-v03Z?^}E9DednoE zw?{00EP(42xTTh8CO7nl{v#?grU&s;$zENt`fm#dSVV9O5vXw{{TB+quPMUFB_OhD z7;!bwWcuy>gN^sCI&z84_4t?8%uhnqkr|&&*iJ3PP1Njdi^aLxzYYB+KnSDbf{=N+$DLjDHFM&>uWf%lE{Q=J#%>+cX?pkDvk;2j zK0-6_w2FW7`^^AyhBiYwH%uuB6p^=+N}Qk*#J|T2uTeaJlmGwpQz)cR z@MtifiS(&pM~mEmf@57YXga{FyI}tNcjyDE3=PhH&?vvdwkHxxQb7Z@Eh>;23j=4b zr%d}CmV%M7`gz2KrjH!AP=I}!22>-;r+8K#3fu<4!vn;g^7Otd%|r!QNfY;&QHCd* zKp8Y6ST-FlOgmkUo}PWBZs8~b{|lqHC)3DNeXI*yIGJH+H}zuxb~wf#qv^^YaQbKg z$rO_HVHxS5?TPgf|I*iRv3G<_cUiFJLrX!KDaj^(9gRYgW{UeJPp-<67Eri@^n1YG zli&5y8w7NED__zif){st#c*KVCk~GQsSKohqgjFI0{B` zp?y$9b|e9eNj761XDlYz@*7*UogSQ3o<(|33QO9>z(G3dVd4Eb_k;|28i^1G{SHw& zdX0*xag84MZm{JI5$_cb)oWzjcEZ3kLknV&U<>sfI)SKMgi(zkc>A5ot81@)QD|Qb zLnn_2c7RU|YjXw=_qJe*UdWj35wxi}zJBMD@9)?|CFB8dkdaYkQ97Ov5(3D_vIxX8 z@Q)|FzsDo<<`x+hLV5yWOFLi$$8cBV;tsawzdTHjswTz?b|?s)CFkf;F?2i zcbNU$8|m*I3;~6vAj*P*PRhd?30$^+X#~K_uC(pj3u2v|-8XIslCk88oBo_){B9kB zc!5VFMFEObUo99x!a(?@Rbm_uI5Y@M@B;*u8=RrPH3kH;pSFH5kOf-IJQygFu!5YW zwB--?cyw6|>K=mKryfK)q7T#y3Wn2A5JV#cQQXUWuPfzQrIoAs@TQs?XP=-yFphLw zU23QU0gVE5F##|DXq>~@R{-M3H0$BwP!PYWwEe+C^5n&PhPy>SDPx6k4`N18-Cha-P?Q9Rtc*9(u*&&!C zz0Uzgy3Hgpjbh5gBEv(12dYYdJ7Dgq))DXSp)0Hpwy7APK;^T&-cm|1^zkLcz81sa z0ixP)uQs7afrA*Bia-}qTXE1U=aEVktgSg+Wx7kOTZn{e*5DgjSTrM;4&a)xtP?zN zR;4h(E000~#%FR@Y5dNs1Iiz7-FYMoSOhd60OgXI;RhAE6%RfD?{gG57=1GdjgA2A zz!J~&#d@y1-OZ4fR1U7L{7Pt>!rJk~eh?MTc09^OkLJv}Nl0ZqrqrL8c_Hgwe_9#V zFDe~idu9{lQ6?G1N)km2FTuWCI!L*?=rj}Dd>^k=y~iKgW+s>lCic?Q9{&)VpdNQ@ zjqXWWsvsAN18I9P-X_H2o%tH&e?oIQ<=8EPhTloO!$aXJ{(XvyP0 zo^G-kD$pvi_WasDHY>414aWH?9WE_VsF2`fd(GikzRgS{wzuF@E%#wv$HD~=i+y9( zET$N;#So#z5UwaPG4MR#a8Ayx-;wrJ8)!~G57g*H!+>6IcSG{dcxGo$w^iNL;ZN9}Z>9!V^B_%cWpAB|Z2zi;D zjGRqY7JS&z^3Ly(c3&F>(1BPM9-h%c&638zQm4wqGr&;)=C8Ch#pOQyQoJr(@YOPt zb7bAiK4W-%?d2)y1TZa6hEaO;*2-pt@xnw4by7Fk*oa_>HmjzM=3uS8q^tLYZr86t z;Y0pk0qb_L`W37bI2H-4a!C6c7-?J>b$zdjK&5+3{P;0K)ClnPM z9zN3oAhz>H~ZETyKp8|sY={# z1dlS+?I47M#Q|f1?G~u{nsm3VPs7-{cgq2sPiu+iDlV#?3*C%}OJHB&%eE9@O^&^< z4K&L>`IK`5cRS^nR#Nxo5Zc%+u;PbWcMS3$O>ja1X`zJwgKHSzBfnrt^FJCX znbQR^_{GsY8>z|x1oR10FE?%>*rItcc0C*{c_S5>WYG_@T)N_UPgZdo&fUl-PU|IYFpzv`vZ>)TPJW+mVwJ*nG^@ey#MtiLT-nXNwFnr|x+NV=YaVE<+LtS!b>wbRt=44UH4GcU6 zMn=XVsCcO;HK?MrFe=FVSd9r+eU>P4W~^FbjLLfHDC8ugz+#)8r`~XdaHV0C7@mdJ ztTl*(vAWL7`trhNvb1L{86xR++chrW)8QH>X)kX{^* zw!DSRPJVbi84^M_w}z&yN-qxuU|l7934l?%;MWcbH>Ro=Dpafq zZ@z4La})ot6yq8?VpM=t$vOFpPqxt7Gx+7Owcb^}4u{kBt((x(%OCpFYL2SB}g(->VPI=|<)m809Jf`bAyRapsjiCNcl)+k05<137&|J!u=qN2dp&Pf9+y zxqb6|<}Ts}Z1aAhu66lWO@Z}<cY z3`6qT>l#i@stc!nn&6@`8bRwQXWi8ooa&cTtcWp7N^z{+i4xFw0dc$ znDWv2%psugWN-N3QeE*@dZXb1^pOE=mygvzPRYSVZg$FHBNPUMV168&(BbKF8%eVi z#2rz8yx9W;K1-d+YQ5XBiF?YvZa_4Qp=_%I^(>c^Vi!rs9=<=fA@JS9eZJ&v8Q9gI z+4Vg;BQx@4DfuQN?{;?LTQ*V3y1B0~DM>Cf#_{UfouDtYA6Xze>X-9}mTqRoPiz{( z@Xy!8kH(*JRXrzn%iI25^K8_@yp(Bgtaf;5X+n8RB0I0Q?(@{$MX`Rb^2B5PT7vG; z>Mv$Fq|bw-d047{XA!A&};%<%Tsk9uj2aoP$B3SyAV?6@)<(-6cH}Q*|hd-tic|*IidK zi`jww3>S~YDzJ9#9zn*P)PXnp4!iT8WNJ_uj|(L9=Z)#Q1F_m$>Sa{{zzx2rU195O zZx!34BcR5DkYG~$0)X{^( z7Pb=|hY*KAZBT0LC^M0bW%Tax$FS$LJ2WCc=P(75&!n{BT*OwA{&-~pB)Z=%&XTyi zi08HTwO2E1g_AE>cR3#(PkftUax}ai3v)46>YN(xyt~PHzMEyRzW}L!)P?@pqCME= zd%t-J+c;;Ty<6d#c@UGvl4?Eg$~T*XeGTI?!Z+H3@F?kT9D3j8??PX=d*dt;Cur^J zFV#NYji(thw-Sl`lo_&7 z0(1#5?i30$-cE-p6YISK#Wky%+RV?O-$A2}4WLpjc7z_7YAoT!2Q8_N%+U`iPTXgk zC)|29xEK8Do-vb|#T9(3&en6lm`nTO*%HjSWw$x6vJh3A0F5EMkmj*E%)zvtYC-UF z&rA@gijV-JkviYYlanP^I_`3pcSP{Mnpz4}WX_Qg@8Kl&0QJp>jJl5VUm;dr)3mYA zS!eMc9zU@jdnJUk|BRHn3lf!|bz*0{GoYfVotomYn>)hKRhap}{w}xrT?ov$%)CWP{1tiGL?sx{YQEo@UpRoG zYLvfLyt+J!mo2nZyy%wZfoAQNf9btJ{(dLJKG|TTmDP2{?lsGgmFrN)5+Id%k8 zJI3+`^kzE7XvX9Wd5C^^Ix804`|i2uuaSd{%b-%>GrrXfx2{X`*M#vFo82bTS83f@M?6{ns+!otWa1k0{T&I19}sh!SwRj( z_QzHB-!gUOUwWm9D#=FW8lJRtsIqbGZdGM0>#tz9AB271YiAPcJkYLjX~W2XNzxdJ z&7}4So+U9iQkjXd3;|H^3aU6gIUVm<@cZ=^Gn%y1eb}Id&|c?N{2kE1G-9%I^#w0F z;l@ZwWW9*jH~F|n-Mgwq@`;Z4AqizCPeW2w_?A@cJOWJGMIs*E@;VgGSm=97UKU_Y zqaO{*tk=Qm+9wI^@+uoNT={XdM??LjzK^u_DsRrqY9F;++RU@6>w zC5ud;L)lAkHeX%Tmls#!^dxY4i3l%$sg1dA-89xIq|3zNQ9E<#-Rid*iUkNbPXf*1u>IKW-4J91q?%Wadi1Rjl+yaFhC~ki0e@gE8 za_g>H1?y2U&`dbnR9&@^q_FCi)1TJ59ax2vb?ptb!`$9d#40bPW$sDI%~FUk8s%<% z5LJ=>=L92c2)X@LyR5fp+0~!N)50&2tvTC7-e55e4_jWK{5aSv#WpmEJITGQ7IXZRzDQS$KNcGGTpO$ZY8$ zK2cnzSK;`k+CZp^CAO?eY^Ij`qh02FOVS(7?uye_AeZl)^ zBz;EUOrO0x?YDL;p?(PydmPY)$|7SFS}$gT+Syxr>2OFxv?mK3Ku$nM3691(&F8n5 zm2+?}cQ7+f0H{%(@OF*3PhQRu7p62@DDeDjygaAcI z{6S9+4dovdHFbQb=D}>gZa#$&a*IsVVg1cGtNIJ#sPB2HE$`^#neNQfM)Q5K?$Vu^ zY)o1)Zm6qW(7UbgBluvMC-*#x5vJGfJJgjw0cIc?eWF25yp8bzK5me7w>SkFtzDyy z!X6$Ao{G_ep9#udcR8px^Xr}KN#Nk4L&I`EpXLtm487uceZET9{e4`^TT!UCf|(^^ zB?NCG7>dhp+Dlxfb%N^-K8q_T>GResXmwAVfLsV-;5lE zVtW|>CFt6W^SExJhh*b?I2GFr9H;+|VK3=W?9i_0uD6(jWz7}8_ui1k#$VA>Kzo zc3KhNPfJVZ+%_h9s;oBsKc2~RZG~#(V?FPMxe#-S*M)X$Qj+n-WDU>NkEXNybUTkf zU&;5if7+|Q(*w&x@x1_xJ#h8-c+&Oeh)AaD%*==YUTI9Rf0pZ?@BS_FH6C9)Umw9! zrnCL*AQ7i$WK*QP7aGV*Bfo~q&u1K>A$yM{HPoh;C0+mhb6i7zjxUVuK>PPS{QFbm zGWeq4`bpUJuCV>_>&+Cyzk#_cW~{t#FrV?B8IO4{!P_7l;N#ZNSS!#{Lxs|NTj7Q@^kT z1Z`eS&S6vjYh3+xPbl6a89#cD>H5cwrBXn8d}rs{C-*;o-iS_t)|3F;X6xM0!U)q&~pJ~$l zy`B09$WMQ{o?i_-Ue%!c;bxzhTAu2t>5CVq!*2xKl8ReF;&byB_cYQG@at8Bd}kmb zwDhk7@JwKZ>oTMj4cg~r8VnUHF{dw7N=#qpxSPsE79Q`WfP4IaSSH>^$xWe z9GfUv1$(!RkXOeuP9{qp-8|Nl;ZN_YrM;MgMKkLR2^6D6b8{;uf$+Qm2ww{b z1)r3CSq(B`u;|#aaEiNOR4tVk)l6GSoUdQx&jaoa~16hMST-9I!w;qY^#d zq0M>a5qxP`%Xwc7kR!&@N!~@oB2;1^Fo{pkq{d~aMtP>9QhUcL7V9;NEZmuRseN3J zwEnWX8DxM5O--#CRU@l|Vz%@3;0gsP;`u1X8)G-zclrjv)y9Er6%2i~($6wN%%uAk z2hYoCmDBDim&MAh9R0RIo*EHKizS0{`?*&TU}>)&7~q9!l|@cYLXIF@KPKp}WV~#> zKr$QNKIuHJ$7fAl@TJ~&dAo?>$uewo{B##eM8 zWcuVH;_`8AAXm1|3A4-*wEMPceQ#c}_fT#!&C2cI;pgSWXy}r3yn>)f&v&-TjHL_b zaRDaG$jHb^U_aQ9Fl5rGJFkN}E^qp?z|@8Iz7P zd3Gtn;Q++t5;AHHF|Us=y0mS6cpfK&F{`N$d zTIr&w5Re*$fpVm~Z7wzq*J#{uc;=NHd*<&eByzSC>cNMc$?cn!ZGfkhg_^qQo(e|a z)g`1fz@uG@cR9^(B8u_H+0Jomtj?U?=ceR)56M)<=h{LmoCz{q*Gmp`K`PDqz3&cA zw+Qk@X$K1gXrA_<3q%NLZZcU)tFt|vV;@GoWqrvzHauA9?jH>d8-|emzNc`IzphU8 zN+4x74H*S;%vy_sH7EnGZ`}n6ikC|y`|^CNLjwJ$f=;VPHlXo&Nof#yfBp+t%1t)C z$T2M&56K8p?z#d}E}?H(SJWLG(8L8dy{Hv8y`&Yi_Y7-jx0oX}J!3zA@9!75ih#Nw zDWrURq47!1+*^RYZH(DKaFop3Lj}mn_u#DycR6h)nh&|GMn;(RlV)fbqRRnPL6pmK z2nW<;(3Fck?^L!*jHZpJCZ?;4!l2T%RV$SXWNu~i@F zZ3bM9FRf9HSp}bSjbtN(N&*Ml`ecDibswR>CgZU7oS@ih{5x@*VvS6z;P5d-6i2{L}fK!5Ecsak;G=DRyl$4nYt zNB#k5OGJk$NxBWv#Vc?0bHTC$eHxmm8_Pmgdq-yj)O~D0>rS;zoF?pV8c)oQTlCMn z;CHlJ{@u5Wf)kK-%gW-bhhR3StcN$zNm=9$&%E^AZ* z@GP@N#f(g~e3i+@fO;2=YQM9?@tw^e`ul1ej@-0(z>4s4_6hENJSDEFkq&r{KtW1U z#au|fEe_@UozOdvCvK7+zNxrkNnuCcfPo|I`KBY$G>IRML1;$?;-u@nYR|;yFJ%L^ai^YuV?6zgI`h6J0Q! z_`2X8=R4VIgHWu1-POB&M=QX*R2Fy(_+j&cHksU*VJpyO7Q;lPhI@?m2k z)?fuenm?KeG&z@dy$-oJ5O4@;05-@__24LHv8g|V*J29*7i@menpOtvlCUooD0$s9;@e_KDZGU`)(nOtP6%TH6 zXzT9C%T+P-=mfX$?di{v%yR6w{;2k-f!MB-_j;C6-04E8=bvu@YJSS#+mUlg`}2>| zR8pdfR1|hnew8$f)y}nzoP?5z@vy!m+MtGyA-t`g=R1KS%I@CBGiQ^ab@__E`v##9 z^s0ovaGcGg6?^OXlqJ<%yxHYOi<$gU2Wx%vs)ZHclNpaYxY0h00&?G~;3)%~j7Mi! z-#r?`lP87u&Arx01q&j+HtG=Wet1CJNCl@^cj)E4P-n=5{byTqI58uS3g{Uo@PWKel&>6JC1E5I%- z^I+u-wriKTBN!NPpuhGXT@lgKYjwMwSBkq+L^&yWdaV^2r5xQ@Oo1%h<(75@6)L4? z5W6MU7#eIX>DA4Gp3l^CnU7&0Sv3K^+3BK|S=d-v`(;mtL~xCgce}S@8DP$Q#`mlO z*6z>5yZ9!?xEhb%1mNJl7QRiXRP%N}(gtj1x9N&OW!GBrJ%0*N@;8Q(R!N8qV0;!_ zF55~vcf`+VS_#^n0fjz(tv9PG4M9o)7`B+9ekqmS&&+Tgog#wkCe-%AV{{*ODm3sk~?oh+a2{_ zu`9u9CVfnQDY-G37RIqN6LJ!5P+^g^CHr(34pHQ?7;uN`d}hL8*59)f8p-ah&&ZwoVO(&rMP3!f}qV4SLO?`@ZzUr8W;H2s(roxFrU0P`w5vsXDQ)jMUajq)4Z zx1}bmv(6c06JIEJtn{0nb3f$r$;F-F+gdmCgb8|~bkAOmbLVDPd)Oed#o4sh==f-9QufsNh z0})LCCYrQDG)g~XcG7B-YzG{aw6)qS`ms5<*woh+L4x!-G}an!ToJCntCc;@oitR) zXjG$R2N|vs&3IsFc<*^hO+Jr?mTP{%3{})VjJYSuOs9ft_9*W9o$|;Z?&?G8^iVg( z2}R1tl@<9(;CnJ21T6k!g9GHP@8{K^=A;^6AGu-nA0=xo(=)ZdNIGt_J8Q?6Kays# zuc6MW7CL{_v{(fK{%@jQTkxV(KF|=U)&x??QMkL~bb7d9i}ew>7l-{%p&*smARYS zLDZJuOhxN9-P@JcT2rpcDIHl|Uo~I;h=1)9o2M?cWoHdY z+8FB+INwGZhV9YP4dC;AuiW4%IRrbvA~7`H2a)$TW5Ws*H9xmk%29>v3jyG`~P4nqV%YFEPdHHu$SIa5(ht4*inK+a48hvg>m zVVu!LEOmCOkkZ%mMSsR-`<>YqI=b?lPS*N|kLaJWgy_PL+1>Wf6ofF+ABkdwH`5F& zI!$>({CQ*@r12u})nA>PmAfB~W^Zs9ei3s{v2cXYVtk?aM!Wu<;k)M?@m=KyZ*j3# zi4JQ)g^rh?;f=)LY^8?jqy6%@w~H!ZCv#1gU;U(?D;{LkN7W{8^v=khf4Nm}y+49kSbJC&?OH6t#P^eZyXBpg&V<=xOZIK89v`ZRkYALR^DPg(+w41PufBi4HkoKWaKu*zSk>xl{4rlG@>|1#_>>>7L7m_PM$&V z?>61pE^Ue|Gs;iNR)}k)dk9(XMh!VhA~a_uRrG0WqUi8Xskm>sed;`IkAl^o_YkXS z5N>k6?8O>dQ{BR|xC~FZWZIWwN$Dqp?3eIjk`D@A=r-3L9aeZYq=Gr@5}RE$d2Er@ z+`JFcy+RBk6J$R3&z2g{8_rF`U$(^y|$QuP^Cpjdfl1ICD>v-t#NWvrP zcA$MJQpU6*7ehLuvzj(Tq_)@xHXZDINsAKt4$}1Xuce-XO0Y;dZrYTH6nK^c}kh4~r#!;mdc=4Bh zMC$K%Xw-bd#`e%4B>nKl68_Mwzl4tr$uvYt6Lv){Ss1u~1Ek=ep|FR_rCU`~eo%C9 zLkJ#;mYIp?$CA_9yADojc>zpsjgA%_Q3AYdcjt_7F_$OB^+HZWNj)T` zzCg5ylvz{-u+(*NZZwL&fy(OfUGq!ZNUlBNwvM!XW)e&)^f?pe&&N~`KV+iO2-yPO zeCLG?jP$~-6xM(Ef;+zov3lQ>Bi3uAktrQV>hx=%yh0I@J zbI9!z=4fFC;JutWe?AOzB<2D(QXM%-8>>jVmC88zS+Ue!G*Py>WRXHTnV<6ug90sh z_3L-rzfx=rC_Z-A-flCYH0m+rg_&HOzx4_ct>55&{?$-Oh~q3<*g0J*58;Gh*hM70 zF7c3l%CPhDhEH$5Me0l>a{ns@G`%HZ*i*o=ePsM)6!GgoWx+$AC-Oo6g2He0*AIkg zK-!1;@%vZEf1B!rZLZ}U;`+c9qKW>xgy9zL_7xIUy#u$<01Y}4_#-W$APyBZ^!;D- Cy@TTb literal 0 HcmV?d00001 diff --git a/week4/community-contributions/ai_code_converter/screenshots/codeXchange_3_1.png b/week4/community-contributions/ai_code_converter/screenshots/codeXchange_3_1.png new file mode 100644 index 0000000000000000000000000000000000000000..ed1a708a6b9bf61dfdec6697747a51627e77b9ba GIT binary patch literal 20447 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 literal 0 HcmV?d00001 diff --git a/week4/community-contributions/ai_code_converter/screenshots/codeXchange_4.png b/week4/community-contributions/ai_code_converter/screenshots/codeXchange_4.png new file mode 100644 index 0000000000000000000000000000000000000000..7fb79e54f7fa689d3f569f98f8e3dd6ff4f6831b GIT binary patch literal 43026 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𝔛< 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 aIWgQ&#=LOvIKfc{e9qW9our)bP5uXPY^K2g literal 0 HcmV?d00001 diff --git a/week4/community-contributions/ai_code_converter/setup.py b/week4/community-contributions/ai_code_converter/setup.py deleted file mode 100644 index cb4e041..0000000 --- a/week4/community-contributions/ai_code_converter/setup.py +++ /dev/null @@ -1,19 +0,0 @@ -from setuptools import setup, find_packages - -setup( - name="ai_code_converter", - version="1.0.0", - packages=find_packages(), - package_data={ - 'src.ai_code_converter': ['template.j2'], - }, - install_requires=[ - 'gradio', - 'openai', - 'anthropic', - 'google-generativeai', - 'python-dotenv', - 'jinja2', - ], - python_requires='>=3.10', -) \ No newline at end of file diff --git a/week4/community-contributions/ai_code_converter/src/__init__.py b/week4/community-contributions/ai_code_converter/src/__init__.py deleted file mode 100644 index f665b01..0000000 --- a/week4/community-contributions/ai_code_converter/src/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""CodeXchange AI - Application""" diff --git a/week4/community-contributions/ai_code_converter/src/ai_code_converter/__init__.py b/week4/community-contributions/ai_code_converter/src/ai_code_converter/__init__.py deleted file mode 100644 index c069de4..0000000 --- a/week4/community-contributions/ai_code_converter/src/ai_code_converter/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -"""CodeXchange AI - package.""" - -__version__ = "1.0.0" \ No newline at end of file diff --git a/week4/community-contributions/ai_code_converter/src/ai_code_converter/app.py b/week4/community-contributions/ai_code_converter/src/ai_code_converter/app.py deleted file mode 100644 index 02834df..0000000 --- a/week4/community-contributions/ai_code_converter/src/ai_code_converter/app.py +++ /dev/null @@ -1,1083 +0,0 @@ -"""Main application module for CodeXchange AI.""" - -import logging -import os -from typing import Any, Dict, Generator, Optional, Tuple -import re -from datetime import datetime -import time -import threading - -from anthropic import Anthropic, AnthropicError -import google.generativeai as genai -import gradio as gr -from dotenv import load_dotenv -from jinja2 import Template -from openai import OpenAI - -# Import configuration -from src.ai_code_converter.config import ( - SUPPORTED_LANGUAGES, - MODELS, - LANGUAGE_MAPPING, - PREDEFINED_SNIPPETS, - SNIPPET_LANGUAGE_MAP, - LANGUAGE_FILE_EXTENSIONS -) -from src.ai_code_converter.models.ai_streaming import AIModelStreamer -from src.ai_code_converter.core.code_execution import CodeExecutor -from src.ai_code_converter.core.language_detection import LanguageDetector -from src.ai_code_converter.core.file_utils import FileHandler -from src.ai_code_converter.utils.logger import setup_logger, log_execution_time -from src.ai_code_converter.config import ( - CUSTOM_CSS, - LANGUAGE_MAPPING, - MODELS, - SUPPORTED_LANGUAGES, - OPENAI_MODEL, - CLAUDE_MODEL, - DEEPSEEK_MODEL, - GEMINI_MODEL, - GROQ_MODEL -) - -# Initialize logger for this module -logger = setup_logger(__name__) - -class CodeConverterApp: - """Main application class for the CodeXchange AI.""" - - def __init__(self): - """Initialize the application components.""" - logger.info("Initializing CodeConverterApp") - try: - self._setup_environment() - self._initialize_components() - self.demo = self._create_gradio_interface() - logger.info("CodeConverterApp initialized successfully") - except Exception as e: - logger.error("Failed to initialize CodeConverterApp", exc_info=True) - raise - - def _load_snippet(self, snippet_name: str) -> Tuple[str, str]: - """Load a predefined code snippet and get its language - - Args: - snippet_name: Name of the snippet to load - - Returns: - A tuple containing (code_content, language) - """ - logger.info(f"Loading predefined snippet: {snippet_name}") - # Use the imported configuration from config.py - from src.ai_code_converter.config import PREDEFINED_SNIPPETS, SNIPPET_LANGUAGE_MAP - code = PREDEFINED_SNIPPETS.get(snippet_name, "# No snippet selected") - language = SNIPPET_LANGUAGE_MAP.get(snippet_name, "Python") - - logger.info(f"Snippet loaded with language: {language}") - return code, language - - def _setup_environment(self) -> None: - """Set up environment variables and logging.""" - load_dotenv() - - # Set up API keys - os.environ['OPENAI_API_KEY'] = os.getenv('OPENAI_API_KEY', 'your-key-if-not-using-env') - os.environ['ANTHROPIC_API_KEY'] = os.getenv('ANTHROPIC_API_KEY', 'your-key-if-not-using-env') - - # Initialize AI clients - self.openai = OpenAI() - self.claude = Anthropic() - self.deepseek = OpenAI( - base_url="https://api.deepseek.com/v1", - api_key=os.getenv('DEEPSEEK_API_KEY', 'your-key-if-not-using-env') - ) - self.groq = OpenAI( - base_url="https://api.groq.com/openai/v1", - api_key=os.getenv('GROQ_API_KEY', 'your-key-if-not-using-env') - ) - - # Initialize Gemini - genai.configure(api_key=os.getenv('GOOGLE_API_KEY', 'your-key-if-not-using-env')) - self.gemini = genai.GenerativeModel('gemini-1.5-flash') - - def _initialize_components(self) -> None: - """Initialize application components.""" - # Load conversion template using package path - template_path = os.path.join(os.path.dirname(__file__), 'template.j2') - with open(template_path, 'r') as f: - self.template = Template(f.read()) - - # Initialize components - self.language_detector = LanguageDetector() - self.code_executor = CodeExecutor() - self.file_handler = FileHandler() - self.model_streamer = AIModelStreamer( - self.openai, - self.claude, - self.deepseek, - self.groq, - self.gemini - ) - - # Extend Gradio's allowed languages - for lang in LANGUAGE_MAPPING.values(): - if lang not in gr.Code.languages: - gr.Code.languages.append(lang) - - # Add additional languages to Gradio's supported languages if needed - additional_languages = { - "Perl": "perl", - "Lua": "lua", - "PHP": "php", - "Kotlin": "kotlin", - "SQL": "sql" - } - - for lang, lang_code in additional_languages.items(): - if lang_code not in gr.Code.languages: - gr.Code.languages.append(lang_code) - - def _create_gradio_interface(self) -> gr.Blocks: - """Create and configure the Gradio interface.""" - with gr.Blocks(css=CUSTOM_CSS) as demo: - # Add validation state - validation_state = gr.State(True) - error_state = gr.State("") - - # Header - gr.HTML('

') - - # Error Message Accordion - with gr.Accordion("Validation Messages", open=False, elem_classes=["error-accordion"]) as error_accordion: - error_message = gr.Markdown( - value="", - elem_classes=["error-message"] - ) - - # Code Input/Output - with gr.Row(equal_height=True): - with gr.Column(elem_classes="code-container"): - source_code = gr.Code( - value="", - language=LANGUAGE_MAPPING["Python"], - label="Source Code", - interactive=True, - lines=22, - elem_classes=["scroll-hide"], - container=False - ) - with gr.Column(elem_classes="converted-code-container"): - converted_code = gr.Code( - label="Converted Code", - language="python", - interactive=False, - elem_classes=["code-container"] - ) - - # Language & Model Selection - with gr.Row(): - source_lang = gr.Dropdown( - choices=SUPPORTED_LANGUAGES, - value="Python", - label="Code In", - interactive=True - ) - target_lang = gr.Dropdown( - choices=[lang for lang in SUPPORTED_LANGUAGES if lang != "Python"], - value="R", - label="Code Out", - interactive=True - ) - model = gr.Dropdown( - choices=MODELS, - value="GPT", - label="Model" - ) - temperature = gr.Slider( - minimum=0, - maximum=1, - value=0.7, - step=0.1, - label="Temperature" - ) - - # Document Options Row - with gr.Row(visible=True) as document_row: - document_checkbox = gr.Checkbox( - label="Document", - value=True, - info="Generate documentation for the converted code" - ) - - # Import document styles from config - from src.ai_code_converter.config import DOCUMENT_STYLES - - # Default to R document styles initially - default_doc_styles = DOCUMENT_STYLES.get("R", []) - - # Find the standard style in the choices or use the first one - default_value = next((item["value"] for item in default_doc_styles if item["value"] == "standard"), - default_doc_styles[0]["value"] if default_doc_styles else "") - - # Convert the styles to the format expected by Gradio - choices_list = [] - for style in default_doc_styles: - choices_list.append(style["label"]) - - # Map style labels to values for internal use - self.doc_style_value_map = {style["label"]: style["value"] for style in default_doc_styles} - - default_label = next((style["label"] for style in default_doc_styles if style["value"] == default_value), - default_doc_styles[0]["label"] if default_doc_styles else "Standard") - - document_type_dropdown = gr.Dropdown( - choices=choices_list, - value=default_label, - label="Documentation Style", - visible=True, - interactive=True - ) - - # Get default value for document style state - default_doc_styles = DOCUMENT_STYLES.get("Python", []) - default_value = next((item["value"] for item in default_doc_styles if item["value"] == "standard"), - default_doc_styles[0]["value"] if default_doc_styles else "") - - # Add states to track document options - document_checkbox_state = gr.State(value=True) - document_style_state = gr.State(value=default_value) - - # File Operations and Code Snippets - with gr.Accordion("Upload & Code Snippets", open=False, elem_classes="accordion"): - with gr.Row(): - file_upload = gr.File(label="Upload File") - - with gr.Row(): - snippet_dropdown = gr.Dropdown( - choices=list(PREDEFINED_SNIPPETS.keys()), - value=None, - label="Example Code Snippets", - info="Select a code snippet to load into the editor" - ) - load_snippet_btn = gr.Button( - "Load Snippet", - variant="secondary", - size="sm" - ) - - # Action Buttons - with gr.Row(): - convert_btn = gr.Button( - "Convert Python to R", - variant="primary", - size="sm" - ) - clear_btn = gr.Button( - "Clear All", - variant="stop", - size="sm" - ) - - # Execution Buttons - with gr.Row(): - run_source_btn = gr.Button( - "Run Python", - variant="huggingface", - size="sm" - ) - run_converted_btn = gr.Button( - "Run R", - variant="huggingface", - size="sm" - ) - - # Execution Results - with gr.Row(): - source_result = gr.Code( - label="Source Code Result", - language=None, - interactive=False, - lines=5 - ) - converted_result = gr.Code( - label="Converted Code Result", - language=None, - interactive=False, - lines=5 - ) - - # Move Download Buttons into an Accordion - with gr.Accordion("Download", open=False, elem_classes="accordion"): - with gr.Row(elem_classes="download-area"): - source_download = gr.File( - label="Download Source", - visible=False - ) - converted_download = gr.File( - label="Download Converted", - visible=False - ) - - # Set up event handlers - self._setup_event_handlers( - source_code, converted_code, - source_lang, target_lang, - model, temperature, - validation_state, error_state, - error_message, error_accordion, - file_upload, snippet_dropdown, load_snippet_btn, - convert_btn, clear_btn, - run_source_btn, run_converted_btn, - source_result, converted_result, - source_download, converted_download, - document_checkbox, document_type_dropdown, document_checkbox_state, document_style_state - ) - - return demo - - def run(self, share: bool = True) -> None: - """ - Launch the Gradio interface. - - Args: - share: Whether to create a public link - """ - logger.info("="*50) - logger.info("Initializing Gradio interface") - logger.info(f"Supported languages: {SUPPORTED_LANGUAGES}") - logger.info(f"Available models: {MODELS}") - - try: - self.demo.queue().launch(share=share) - except Exception as e: - logger.error(f"Error launching Gradio interface: {str(e)}", exc_info=True) - raise - finally: - logger.info("Gradio interface shutdown") - logger.info("="*50) - - def _setup_event_handlers( - self, - source_code: gr.Code, - converted_code: gr.Code, - source_lang: gr.Dropdown, - target_lang: gr.Dropdown, - model: gr.Dropdown, - temperature: gr.Slider, - validation_state: gr.State, - error_state: gr.State, - error_message: gr.Markdown, - error_accordion: gr.Accordion, - file_upload: gr.File, - snippet_dropdown: gr.Dropdown, - load_snippet_btn: gr.Button, - convert_btn: gr.Button, - clear_btn: gr.Button, - run_source_btn: gr.Button, - run_converted_btn: gr.Button, - source_result: gr.Code, - converted_result: gr.Code, - source_download: gr.File, - converted_download: gr.File, - document_checkbox: gr.Checkbox, - document_type_dropdown: gr.Dropdown, - document_checkbox_state: gr.State, - document_style_state: gr.State - ) -> None: - """Set up all event handlers for the Gradio interface.""" - - # Document checkbox handler - def _update_document_dropdown_visibility(is_checked: bool) -> tuple[gr.update, bool]: - """Update document type dropdown visibility based on checkbox state""" - logger.info(f"Document checkbox changed to: {is_checked}") - return gr.update(visible=is_checked), is_checked - - document_checkbox.change( - fn=_update_document_dropdown_visibility, - inputs=[document_checkbox], - outputs=[document_type_dropdown, document_checkbox_state], - queue=False - ) - - # Language change handlers - source_lang.change( - fn=self._handle_source_language_change, - inputs=[source_code, source_lang, error_state], - outputs=[source_code, error_message, error_state, error_accordion, validation_state], - queue=False - ).then( - fn=self._update_code_language, - inputs=[source_lang], - outputs=[source_code], - queue=False - ).then( - fn=self._update_target_options, - inputs=[source_lang], - outputs=[target_lang] - ).then( - fn=self._update_button_labels, - inputs=[source_lang, target_lang], - outputs=[run_source_btn, run_converted_btn, convert_btn], - queue=False - ) - - # Code input handlers - source_code.change( - fn=self._handle_code_input, - inputs=[source_code, source_lang, error_state], - outputs=[source_code, error_message, error_state, error_accordion], - queue=False - ) - - # File upload handler - file_upload.change( - fn=self._handle_file_upload, - inputs=[file_upload, source_lang, error_state], - outputs=[source_code, error_message, error_state, error_accordion], - queue=False - ) - - # Convert button handler - convert_btn.click( - fn=self._stream_converted_code, - inputs=[ - source_code, source_lang, target_lang, model, temperature, validation_state, - document_checkbox_state, document_type_dropdown - ], - outputs=[converted_code, converted_download], - queue=True, - show_progress=True - ) - - # Clear button handler - clear_btn.click( - fn=self._clear_all, - outputs=[ - source_code, - error_message, - error_state, - error_accordion, - converted_code, - source_result, - converted_result, - file_upload - ], - queue=False - ) - - # Run code handlers - run_source_btn.click( - fn=self._handle_code_execution, - inputs=[source_code, source_lang], - outputs=[ - source_result, # Code component for output - source_download # File component for download - ] - ) - - run_converted_btn.click( - fn=self._run_converted_code_with_validation, - inputs=[converted_code, target_lang], - outputs=converted_result # Only output string - ) - - # Snippet selection handler - load_snippet_btn.click( - fn=self._handle_snippet_selection, - inputs=[snippet_dropdown, error_state], - outputs=[ - source_code, - source_lang, - error_message, - error_state, - error_accordion, - validation_state - ] - ) - - # Download handlers - source_code.change( - fn=self._handle_source_code_change, - inputs=[source_code, source_lang], - outputs=[source_download, error_message, error_accordion], - queue=False - ) - - converted_code.change( - fn=lambda code, lang: self._handle_source_code_change(code, lang), - inputs=[converted_code, target_lang], - outputs=[converted_download, error_message, error_accordion], - queue=False - ) - - # Target language change handler - target_lang.change( - fn=self._update_button_labels, - inputs=[source_lang, target_lang], - outputs=[run_source_btn, run_converted_btn, convert_btn], - queue=False - ).then( - fn=lambda lang: gr.update(language=LANGUAGE_MAPPING.get(lang, "python")), - inputs=target_lang, - outputs=converted_code, - queue=False - ).then( - fn=self._update_document_styles, - inputs=[target_lang], - outputs=[document_type_dropdown, document_style_state], - queue=False - ) - - def _handle_source_language_change( - self, code: str, new_lang: str, current_error: str - ) -> tuple[str, str, str, gr.update, bool]: - """Validate code against newly selected source language.""" - logger.info(f"Verifying code for language change to: {new_lang}") - - if not code: - return "", "", "", gr.update(open=False), True - - is_valid, error_msg = self.language_detector.validate_language(code, new_lang) - if not is_valid: - logger.warning(f"Language mismatch detected: {error_msg}") - return ( - code, # Keep the code but mark as invalid - f"## ⚠️ Language Mismatch\n{error_msg}", - error_msg, - gr.update(open=True), - False # Invalid state - ) - - logger.info(f"Code verified successfully for {new_lang}") - return code, "", "", gr.update(open=False), True - - def _handle_snippet_selection( - self, snippet_name: str, current_error: str - ) -> Tuple[str, str, str, str, gr.update, bool]: - """Load and validate selected code snippet. - - Args: - snippet_name: Selected snippet name - current_error: Existing error message - - Returns: - Code, language, error info, and validation status - """ - logger.info(f"Handling snippet selection: {snippet_name}") - - # Default values - source_code = "# No snippet selected" - source_lang = "Python" - error_message = "" - error_state = "" - error_accordion_visible = gr.update(open=False) - is_valid = True - - try: - # Import the configurations from config.py - from src.ai_code_converter.config import PREDEFINED_SNIPPETS - - # Early return if no snippet selected - if not snippet_name or snippet_name not in PREDEFINED_SNIPPETS: - logger.warning(f"Invalid snippet name or no snippet selected: {snippet_name}") - return source_code, source_lang, error_message, error_state, error_accordion_visible, is_valid - - # Get code and language from the snippet - source_code, source_lang = self._load_snippet(snippet_name) - logger.info(f"Loaded snippet: {snippet_name} with language: {source_lang}") - - # Validate that the code matches the language - if source_code and source_code.strip(): - is_valid, error_details = self.language_detector.validate_language(source_code, source_lang) - if not is_valid: - error_message = f"⚠️ Warning: The code doesn't appear to match {source_lang} syntax perfectly. {error_details}" - error_state = error_message - error_accordion_visible = gr.update(open=True) - logger.warning(f"Snippet validation failed: {error_details}") - except Exception as e: - logger.error(f"Error handling snippet selection: {e}", exc_info=True) - source_code = "# Error loading snippet" - error_message = f"Error loading snippet: {str(e)}" - error_state = error_message - error_accordion_visible = gr.update(open=True) - is_valid = False - - # Return all the updated values in the expected order - logger.info(f"Returning snippet: Valid={is_valid}, Lang={source_lang}, Error={bool(error_message)}") - return source_code, source_lang, error_message, error_state, error_accordion_visible, is_valid - - def _update_code_language(self, lang: str) -> gr.update: - """Set syntax highlighting based on selected language.""" - if not lang: - return gr.update(language="python") - - gradio_lang = LANGUAGE_MAPPING.get(lang, "python").lower() - logger.info(f"Updating syntax highlighting to: {gradio_lang}") - - if gradio_lang not in gr.Code.languages: - logger.warning(f"Language {gradio_lang} not supported by Gradio, defaulting to python") - gradio_lang = "python" - - return gr.update(language=gradio_lang) - - def _update_target_options(self, source_lang: str) -> gr.update: - """Filter target language dropdown to exclude source language.""" - if not source_lang: - return gr.update(choices=SUPPORTED_LANGUAGES) - available_languages = [lang for lang in SUPPORTED_LANGUAGES if lang != source_lang] - return gr.update( - choices=available_languages, - value=available_languages[0] if available_languages else None - ) - - def _update_button_labels( - self, source_lang: str, target_lang: str - ) -> tuple[gr.update, gr.update, gr.update]: - """Customize button labels with selected language names.""" - source_lang = source_lang or "Python" - target_lang = target_lang or "Julia" - - convert_label = f"Convert {source_lang} to {target_lang}" - run_source_label = f"Run {source_lang}" - run_target_label = f"Run {target_lang}" - - logger.info(f"Updating button labels: {convert_label}, {run_source_label}, {run_target_label}") - - return [ - gr.update(value=run_source_label), - gr.update(value=run_target_label), - gr.update(value=convert_label) - ] - - def _update_document_styles(self, target_lang: str) -> tuple[gr.update, str]: - """Update document style dropdown based on target language. - - Args: - target_lang: Target programming language - - Returns: - Tuple of (dropdown_update, default_style_value) - """ - from src.ai_code_converter.config import DOCUMENT_STYLES - - logger.info(f"Updating document styles for language: {target_lang}") - - # Get document styles for the selected language, fallback to empty list - styles = DOCUMENT_STYLES.get(target_lang, []) - - # Default to 'standard' style if available, otherwise use first style in list - default_style_value = "standard" - if styles and not any(style.get("value") == "standard" for style in styles): - default_style_value = styles[0].get("value") if styles else "standard" - - # Convert the styles to the format expected by Gradio - choices_list = [style["label"] for style in styles] - - # Update the style label-to-value mapping - self.doc_style_value_map = {style["label"]: style["value"] for style in styles} - - # Get the label corresponding to the default value - default_label = next((style["label"] for style in styles if style["value"] == default_style_value), - styles[0]["label"] if styles else "Standard") - - logger.info(f"Document styles updated: {len(styles)} styles available") - return gr.update(choices=choices_list, value=default_label), default_style_value - - def _handle_code_input( - self, code: str, lang_in: str, current_error: str - ) -> tuple[str, str, str, gr.update]: - """Handle code input with validation.""" - logger.info(f"Processing code input for {lang_in}") - - if not code: - return "", "", "", gr.update(open=False) - - is_valid, error_msg = self.language_detector.validate_language(code, lang_in) - if not is_valid: - return code, error_msg, error_msg, gr.update(open=True) - - return code, "", "", gr.update(open=False) - - def _handle_file_upload( - self, file_obj: gr.File, lang_in: str, current_error: str - ) -> tuple[str, str, str, gr.update]: - """Handle file upload with validation.""" - logger.info("Processing file upload") - - if file_obj is None: - return "", "", "", gr.update(open=False) - - try: - code = self.file_handler.load_file(file_obj.name) - is_valid, error_msg = self.language_detector.validate_language(code, lang_in) - - if not is_valid: - return "", f"## ⚠️ Invalid File\n{error_msg}", error_msg, gr.update(open=True) - - return code, "", "", gr.update(open=False) - - except Exception as e: - error_msg = f"## ⚠️ Error\nError loading file: {str(e)}" - logger.error("File upload failed", exc_info=True) - return "", error_msg, error_msg, gr.update(open=True) - - def _stream_converted_code( - self, - code: str, - lang_in: str, - lang_out: str, - model: str, - temperature: float, - is_valid: bool, - document_enabled: bool = True, - document_style: str = "Standard" - ) -> tuple[gr.update, gr.update]: - """Stream the converted code with syntax highlighting.""" - response = self._convert_code( - code, lang_in, lang_out, model, temperature, - document_enabled=document_enabled, document_style=document_style - ) - - if not response: - return gr.update(value="", visible=True), gr.update(visible=False) - - target_lang = LANGUAGE_MAPPING.get(lang_out, "python").lower() - - # Prepare download if conversion was successful - temp_file, filename = self.file_handler.prepare_download(response, lang_out) - if temp_file and filename: - return ( - gr.update(value=response, language=target_lang, visible=True), - gr.update(value=temp_file, visible=True, label=f"Download {lang_out} Code") - ) - - return gr.update(value=response, language=target_lang, visible=True), gr.update(visible=False) - - def _clear_all(self) -> tuple: - """Clear all fields while retaining language settings.""" - return ( - gr.update(value=""), # source_code - "", # error_message - "", # error_state - gr.update(open=False), # error_accordion - gr.update(value=""), # converted_code - gr.update(value=""), # source_result - gr.update(value=""), # converted_result - None # file_upload - ) - - def _handle_code_execution(self, code: str, language: str) -> tuple[str, gr.update]: - """Handle code execution and prepare download.""" - if not code: - return "", gr.update(visible=False) - - try: - # Execute code and get output and binary - output, binary = self.code_executor.execute(code, language) - - # Prepare download with compiled binary if available - if binary and language in ["C++", "Java", "Go"]: - # Ensure binary is bytes - binary_data = binary if isinstance(binary, bytes) else None - if binary_data: - logger.info(f"Got compiled binary for {language}, size: {len(binary_data)} bytes") - else: - logger.warning(f"No valid binary data for {language}") - - temp_file, filename = self.file_handler.prepare_download( - code=code, - language=language, - compiled_code=binary_data - ) - - if temp_file and filename: - return ( - output, # Return just the output string for Code component - gr.update(value=temp_file, visible=True, label=f"Download {language} Code") - ) - - return output, gr.update(visible=False) - - except Exception as e: - logger.error(f"Error executing code: {str(e)}", exc_info=True) - return f"Error: {str(e)}", gr.update(visible=False) - - def _run_converted_code_with_validation(self, code: str, target_lang: str) -> str: - """Validate and execute converted code.""" - logger.info(f"Validating converted code for execution") - - if not code: - return "No code to execute." - - is_valid, error_msg = self.language_detector.validate_language(code, target_lang) - if not is_valid: - logger.error(f"Language validation failed: {error_msg}") - return f"⚠️ Error: Cannot execute code.\n{error_msg}" - - logger.info("Language validation passed, proceeding with execution") - # Only return the output string, ignore binary - output, _ = self.code_executor.execute(code, target_lang) - return output - - def _handle_source_code_change( - self, code: str, lang: str - ) -> tuple[gr.update, str, gr.update]: - """Handle source code changes with validation.""" - if not code: - return gr.update(visible=False), "", gr.update(open=False) - - try: - temp_file, filename = self.file_handler.prepare_download(code, lang) - if temp_file and filename: - return ( - gr.update(value=temp_file, visible=True, label=f"Download {lang} Code"), - "", - gr.update(open=False) - ) - else: - return ( - gr.update(visible=False), - "## ⚠️ Error preparing download", - gr.update(open=True) - ) - except Exception as e: - logger.error(f"Error preparing download: {str(e)}", exc_info=True) - return ( - gr.update(visible=False), - f"## ⚠️ Error preparing download: {str(e)}", - gr.update(open=True) - ) - - @log_execution_time(logger) - def _convert_code(self, code: str, lang_in: str, lang_out: str, model: str, temp: float, document_enabled: bool = True, document_style: str = "Standard") -> str: - """Convert code between programming languages. - - Args: - code: Source code to convert - lang_in: Source language - lang_out: Target language - model: LLM model to use - temp: Temperature parameter - document_enabled: Whether to include documentation - document_style: Documentation style to apply - - Returns: - Converted code string - """ - logger.info("STARTING CODE CONVERSION") - logger.info(f"Params: {lang_in} → {lang_out} | Model: {model} | Temp: {temp}") - logger.info(f"Documentation: {document_enabled}, Style: {document_style}") - - # Log input parameters (excluding sensitive data) - logger.debug({ - "source_language": lang_in, - "target_language": lang_out, - "model": model, - "temperature": temp, - "code_length": len(code), - "process_id": os.getpid(), - "thread_id": threading.get_ident() - }) - - # Parameter validation - if not all([code, lang_in, lang_out, model]): - missing = [param for param, value in { - 'code': code, - 'source_language': lang_in, - 'target_language': lang_out, - 'model': model - }.items() if not value] - logger.warning("Missing required parameters", extra={ - "missing_params": missing - }) - return "" - - try: - # Code validation - validation_start = time.time() - logger.info("Starting code validation") - is_valid, message = self.language_detector.validate_language(code, lang_in) - logger.debug( - "Validation completed in %.4fs", - time.time() - validation_start - ) - - if not is_valid: - logger.error("Code validation failed", extra={ - "error_message": message, - "source_language": lang_in - }) - return message - - logger.info("Code validation successful") - - # Create and execute prompt - prompt_start = time.time() - - # Map the document style label to its value - style_value = "standard" # Default fallback - if document_style in self.doc_style_value_map: - style_value = self.doc_style_value_map[document_style] - else: - logger.warning(f"Document style mapping not found for '{document_style}', using 'standard' as fallback") - - prompt = self.template.render( - source_language=lang_in, - target_language=lang_out, - input_code=code, - doc_enabled=document_enabled, - doc_style=style_value - ) - logger.debug( - "Prompt creation completed in %.4fs", - time.time() - prompt_start - ) - - logger.info(f"Template: doc_enabled={document_enabled}, style={style_value} (from {document_style})") - - # Stream model response - stream_start = time.time() - logger.info(f"Starting {model} stream") - progress = gr.Progress(track_tqdm=True) - - response = self._stream_model_response(model, prompt, progress) - logger.debug( - "Streaming completed in %.4fs", - time.time() - stream_start - ) - - # Clean response - clean_start = time.time() - cleaned_response = self._clean_response(response) - logger.debug( - "Response cleaning completed in %.4fs", - time.time() - clean_start - ) - - # If converting to Python, ensure output is visible - if lang_out == "Python": - cleaned_response = self._prepare_python_code(cleaned_response) - - logger.info("Code conversion completed successfully", extra={ - "output_length": len(cleaned_response) - }) - logger.info("="*50) - - return cleaned_response - - except Exception as e: - logger.error( - "Error during code conversion", - exc_info=True, - extra={ - "error_type": type(e).__name__, - "source_language": lang_in, - "target_language": lang_out, - "model": model - } - ) - logger.info("="*50) - return f"Error during conversion: {str(e)}" - - def _stream_model_response(self, model: str, prompt: str, progress: gr.Progress) -> str: - """Stream response from selected model with logging.""" - logger = logging.getLogger(__name__) - - logger.info(f"Streaming response from {model}") - response = "" - - try: - if model == "GPT": - stream = self.model_streamer.stream_gpt(prompt) - elif model == "Claude": - stream = self.model_streamer.stream_claude(prompt) - elif model == "DeepSeek": - stream = self.model_streamer.stream_deepseek(prompt) - elif model == "GROQ": - stream = self.model_streamer.stream_groq(prompt) - elif model == "Gemini": - stream = self.model_streamer.stream_gemini(prompt) - else: - logger.error(f"Unsupported model selected: {model}") - return "Unsupported model selected" - - for i, chunk in enumerate(stream): - response = chunk - progress_value = min(0.99, (1 - (1 / (1 + 0.1 * i)))) - progress(progress_value, desc=f"Converting - {int(progress_value * 100)}%") - - logger.info(f"Streaming completed for {model}") - return response - - except Exception as e: - logger.error(f"Error streaming from {model}", exc_info=True) - raise - - def _clean_response(self, response: str) -> str: - """Clean up the model response.""" - try: - cleaned = re.sub( - r'(```\w*\n?)|(^Here is the converted( is the converted)?.*?\n)', - '', - response, - flags=re.MULTILINE - ) - return cleaned.strip() - except Exception as e: - logger.error(f"Error cleaning response: {str(e)}", exc_info=True) - return response - - def _prepare_python_code(self, code: str) -> str: - """Prepare Python code for execution by ensuring output is visible.""" - # Split code into lines - lines = code.strip().split('\n') - - # Track function definitions and their indentation - functions = {} - modified_lines = [] - current_function = None - in_function = False - - for line in lines: - stripped = line.strip() - - # Handle function definitions - if stripped.startswith('def '): - current_function = stripped[4:].split('(')[0].strip() - in_function = True - functions[current_function] = True - modified_lines.append(line) - continue - - # Check if we're still in a function definition - if in_function: - if line and line[0].isspace(): - modified_lines.append(line) - continue - else: - in_function = False - current_function = None - - # Skip imports, comments, and empty lines - if not stripped or stripped.startswith(('import ', 'from ', '#')): - modified_lines.append(line) - continue - - # Handle function calls and other statements - if not in_function and stripped: - # Don't wrap lines that already have print - if stripped.startswith('print('): - modified_lines.append(line) - # Don't wrap function definitions or control flow statements - elif any(stripped.startswith(keyword) for keyword in ['def ', 'class ', 'if ', 'for ', 'while ', 'try:', 'except:', 'finally:', 'else:', 'elif ']): - modified_lines.append(line) - # Don't wrap assignments - elif '=' in stripped and not stripped.startswith('return'): - modified_lines.append(line) - # Wrap function calls and expressions - else: - modified_lines.append(f"print({stripped})") - - # Add a final newline for cleaner output - return '\n'.join(modified_lines) + '\n' \ No newline at end of file diff --git a/week4/community-contributions/ai_code_converter/src/ai_code_converter/config.py b/week4/community-contributions/ai_code_converter/src/ai_code_converter/config.py deleted file mode 100644 index b86a904..0000000 --- a/week4/community-contributions/ai_code_converter/src/ai_code_converter/config.py +++ /dev/null @@ -1,443 +0,0 @@ -"""Configuration settings for the CodeXchange AI application.""" - -# Model configurations -OPENAI_MODEL = "gpt-4o-mini" -CLAUDE_MODEL = "claude-3-5-sonnet-20241022" -DEEPSEEK_MODEL = "deepseek-chat" -GEMINI_MODEL = "gemini-1.5-flash" -GROQ_MODEL = "llama3-70b-8192" - -# Supported languages and models -SUPPORTED_LANGUAGES = ["Python", "Julia", "JavaScript", "Go", "Java", "C++", "Ruby", "Swift", "Rust", "C#", "TypeScript", "R", "Perl", "Lua", "PHP", "Kotlin", "SQL"] -MODELS = ["GPT", "Claude", "Gemini", "DeepSeek", "GROQ"] - -# Language mapping for syntax highlighting -LANGUAGE_MAPPING = { - "Python": "python", - "JavaScript": "javascript", - "Java": "python", - "C++": "cpp", - "Julia": "python", - "Go": "c", - "Ruby": "python", - "Swift": "python", - "Rust": "python", - "C#": "python", - "TypeScript": "typescript", - "R": "r", - "Perl": "python", - "Lua": "python", - "PHP": "python", - "Kotlin": "python", - "SQL": "sql" -} - -# File extensions for each language -LANGUAGE_FILE_EXTENSIONS = { - "Python": ".py", - "JavaScript": ".js", - "Java": ".java", - "C++": ".cpp", - "Julia": ".jl", - "Go": ".go", - "Ruby": ".rb", - "Swift": ".swift", - "Rust": ".rs", - "C#": ".cs", - "TypeScript": ".ts", - "R": ".r", - "Perl": ".pl", - "Lua": ".lua", - "PHP": ".php", - "Kotlin": ".kt", - "SQL": ".sql" -} - -# Documentation styles available for each language -DOCUMENT_STYLES = { - "Python": [ - {"value": "standard", "label": "Standard (PEP 257)"}, - {"value": "google", "label": "Google Style"}, - {"value": "numpy", "label": "NumPy Style"} - ], - "Julia": [ - {"value": "standard", "label": "Standard"}, - {"value": "docsystem", "label": "Documenter.jl Style"} - ], - "JavaScript": [ - {"value": "standard", "label": "Standard"}, - {"value": "jsdoc", "label": "JSDoc Style"}, - {"value": "tsdoc", "label": "TSDoc Style"} - ], - "Go": [ - {"value": "standard", "label": "Standard"}, - {"value": "godoc", "label": "GoDoc Style"} - ], - "Java": [ - {"value": "standard", "label": "Standard"}, - {"value": "javadoc", "label": "JavaDoc Style"} - ], - "C++": [ - {"value": "standard", "label": "Standard"}, - {"value": "doxygen", "label": "Doxygen Style"} - ], - "Ruby": [ - {"value": "standard", "label": "Standard"}, - {"value": "yard", "label": "YARD Style"}, - {"value": "rdoc", "label": "RDoc Style"} - ], - "Swift": [ - {"value": "standard", "label": "Standard"}, - {"value": "markup", "label": "Swift Markup"} - ], - "Rust": [ - {"value": "standard", "label": "Standard"}, - {"value": "rustdoc", "label": "Rustdoc Style"} - ], - "C#": [ - {"value": "standard", "label": "Standard"}, - {"value": "xmldoc", "label": "XML Documentation"} - ], - "TypeScript": [ - {"value": "standard", "label": "Standard"}, - {"value": "tsdoc", "label": "TSDoc Style"} - ], - "R": [ - {"value": "standard", "label": "Standard"}, - {"value": "roxygen2", "label": "Roxygen2 Style"} - ], - "Perl": [ - {"value": "standard", "label": "Standard"}, - {"value": "pod", "label": "POD Style"} - ], - "Lua": [ - {"value": "standard", "label": "Standard"}, - {"value": "ldoc", "label": "LDoc Style"} - ], - "PHP": [ - {"value": "standard", "label": "Standard"}, - {"value": "phpdoc", "label": "PHPDoc Style"} - ], - "Kotlin": [ - {"value": "standard", "label": "Standard"}, - {"value": "kdoc", "label": "KDoc Style"} - ], - "SQL": [ - {"value": "standard", "label": "Standard"}, - {"value": "block", "label": "Block Comment Style"} - ] -} - -# Predefined code snippets for the UI -PREDEFINED_SNIPPETS = { - "Python Code Simple" : """ - import time - -def calculate(iterations, param1, param2): - result = 1.0 - for i in range(1, iterations+1): - j = i * param1 - param2 - result -= (1/j) - j = i * param1 + param2 - result += (1/j) - return result - -start_time = time.time() -result = calculate(100_000_000, 4, 1) * 4 -end_time = time.time() - -print(f"Result: {result:.12f}") -print(f"Execution Time: {(end_time - start_time):.6f} seconds") - """, - "Python Code": """import time -import random -from typing import List, Tuple - -class LCG: - #Linear Congruential Generator - def __init__(self, seed: int): - self.value = seed - self.a = 1664525 - self.c = 1013904223 - self.m = 2**32 - - def next(self) -> int: - # Generate next random number - self.value = (self.a * self.value + self.c) % self.m - return self.value - -def max_subarray_sum(n: int, seed: int, min_val: int, max_val: int) -> int: - # Calculate maximum subarray sum for array of random numbers - lcg = LCG(seed) - random_numbers = [] - - # Generate random numbers - for _ in range(n): - random_numbers.append((lcg.next() % (max_val - min_val + 1)) + min_val) - - max_sum = float('-inf') - - # Calculate max subarray sum - for i in range(n): - current_sum = 0 - for j in range(i, n): - current_sum += random_numbers[j] - if current_sum > max_sum: - max_sum = current_sum - - return max_sum - -def total_max_subarray_sum(n: int, initial_seed: int, min_val: int, max_val: int) -> int: - # Calculate total of maximum subarray sums over 20 iterations - total_sum = 0 - lcg = LCG(initial_seed) - - for _ in range(20): - seed = lcg.next() - total_sum += max_subarray_sum(n, seed, min_val, max_val) - - return total_sum - -# Main program -def main(): - n = 10000 - initial_seed = 42 - min_val = -10 - max_val = 10 - - start_time = time.time() - - result = total_max_subarray_sum(n, initial_seed, min_val, max_val) - - end_time = time.time() - duration = end_time - start_time - - print(f"Total Maximum Subarray Sum (20 runs): {result}") - print(f"Execution Time: {duration:.6f} seconds") - -if __name__ == "__main__": - main()""", - - "C++ Code": """#include -#include -#include -#include - -class LCG { -private: - uint32_t value; - const uint32_t a = 1664525; - const uint32_t c = 1013904223; - const uint32_t m = 1 << 32; - -public: - LCG(uint32_t seed) : value(seed) {} - - uint32_t next() { - value = (a * value + c) % m; - return value; - } -}; - -/* - * Calculates maximum subarray sum for array of random numbers - */ -int64_t max_subarray_sum(int n, uint32_t seed, int min_val, int max_val) { - LCG lcg(seed); - std::vector random_numbers(n); - - // Generate random numbers - for(int i = 0; i < n; i++) { - random_numbers[i] = (lcg.next() % (max_val - min_val + 1)) + min_val; - } - - int64_t max_sum = std::numeric_limits::min(); - - // Calculate max subarray sum - for(int i = 0; i < n; i++) { - int64_t current_sum = 0; - for(int j = i; j < n; j++) { - current_sum += random_numbers[j]; - if(current_sum > max_sum) { - max_sum = current_sum; - } - } - } - return max_sum; -} - -/* - * Calculates total of maximum subarray sums over 20 iterations - */ -int64_t total_max_subarray_sum(int n, uint32_t initial_seed, int min_val, int max_val) { - int64_t total_sum = 0; - LCG lcg(initial_seed); - - for(int i = 0; i < 20; i++) { - uint32_t seed = lcg.next(); - total_sum += max_subarray_sum(n, seed, min_val, max_val); - } - return total_sum; -} - -int main() { - const int n = 10000; - const uint32_t initial_seed = 42; - const int min_val = -10; - const int max_val = 10; - - auto start_time = std::chrono::high_resolution_clock::now(); - - int64_t result = total_max_subarray_sum(n, initial_seed, min_val, max_val); - - auto end_time = std::chrono::high_resolution_clock::now(); - auto duration = std::chrono::duration_cast(end_time - start_time); - - std::cout << "Total Maximum Subarray Sum (20 runs): " << result << std::endl; - std::cout << "Execution Time: " << duration.count() / 1000000.0 << " seconds" << std::endl; - - return 0; -}""", - "Java Code": """import java.util.ArrayList; - -public class MaxSubarraySum { - /* Linear Congruential Generator implementation */ - static class LCG { - private long value; - private final long a = 1664525; - private final long c = 1013904223; - private final long m = 1L << 32; - - public LCG(long seed) { - this.value = seed; - } - - public long next() { - value = (a * value + c) % m; - return value; - } - } - - /* Calculates maximum subarray sum for given array of random numbers */ - private static long maxSubarraySum(int n, long seed, int minVal, int maxVal) { - LCG lcg = new LCG(seed); - ArrayList randomNumbers = new ArrayList<>(); - - // Generate random numbers - for (int i = 0; i < n; i++) { - long num = lcg.next() % (maxVal - minVal + 1) + minVal; - randomNumbers.add(num); - } - - long maxSum = Long.MIN_VALUE; - for (int i = 0; i < n; i++) { - long currentSum = 0; - for (int j = i; j < n; j++) { - currentSum += randomNumbers.get(j); - maxSum = Math.max(maxSum, currentSum); - } - } - return maxSum; - } - - /* Calculates total of maximum subarray sums for 20 different seeds */ - private static long totalMaxSubarraySum(int n, long initialSeed, int minVal, int maxVal) { - long totalSum = 0; - LCG lcg = new LCG(initialSeed); - - for (int i = 0; i < 20; i++) { - long seed = lcg.next(); - totalSum += maxSubarraySum(n, seed, minVal, maxVal); - } - return totalSum; - } - - public static void main(String[] args) { - // Parameters - int n = 10000; // Number of random numbers - long initialSeed = 42; // Initial seed for the LCG - int minVal = -10; // Minimum value of random numbers - int maxVal = 10; // Maximum value of random numbers - - // Timing the function - long startTime = System.nanoTime(); - long result = totalMaxSubarraySum(n, initialSeed, minVal, maxVal); - long endTime = System.nanoTime(); - - System.out.println("Total Maximum Subarray Sum (20 runs): " + result); - System.out.printf("Execution Time: %.6f seconds%n", (endTime - startTime) / 1e9); - } -}""" -} - -# Map snippets to their corresponding languages -SNIPPET_LANGUAGE_MAP = { - "Python Code": "Python", - "Python Code Simple": "Python", - "C++ Code": "C++", - "Java Code": "Java" -} - -# CSS styling -CUSTOM_CSS = """ -.code-container { - height: 30vh; - overflow: auto; - border-radius: 4px; - scrollbar-width: none; - -ms-overflow-style: none; -} - -.code-container::-webkit-scrollbar { - display: none !important; - width: 0 !important; - height: 0 !important; - background: transparent !important; -} - -.code-container .scroll-hide::-webkit-scrollbar, -.code-container > div::-webkit-scrollbar, -.code-container textarea::-webkit-scrollbar, -.code-container pre::-webkit-scrollbar, -.code-container code::-webkit-scrollbar { - display: none !important; - width: 0 !important; - height: 0 !important; - background: transparent !important; -} - -.code-container .language-select { - overflow: auto !important; - max-height: 100% !important; -} - -.accordion { - margin-top: 1rem !important; -} - -.error-accordion { - margin: 10px 0; - border: 2px solid #ff4444 !important; - border-radius: 4px !important; - background-color: #ffffff !important; -} - -.error-message { - color: #ff4444; - font-weight: bold; - font-size: 16px; - padding: 10px; -} - -.gradio-container { - padding-top: 1rem; -} - -.header-text { - text-align: center; - font-size: 2rem; - font-color: #283042; - font-weight: bold; - margin-bottom: 1rem; -} -""" \ No newline at end of file diff --git a/week4/community-contributions/ai_code_converter/src/ai_code_converter/core/__init__.py b/week4/community-contributions/ai_code_converter/src/ai_code_converter/core/__init__.py deleted file mode 100644 index 06a5ccc..0000000 --- a/week4/community-contributions/ai_code_converter/src/ai_code_converter/core/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Core functionality modules.""" diff --git a/week4/community-contributions/ai_code_converter/src/ai_code_converter/core/code_execution.py b/week4/community-contributions/ai_code_converter/src/ai_code_converter/core/code_execution.py deleted file mode 100644 index c034cb8..0000000 --- a/week4/community-contributions/ai_code_converter/src/ai_code_converter/core/code_execution.py +++ /dev/null @@ -1,631 +0,0 @@ -"""Module for executing code in different programming languages.""" - -import contextlib -import io -import logging -import os -import re -import subprocess -import tempfile -from typing import Optional -from datetime import datetime -from src.ai_code_converter.utils.logger import setup_logger -from pathlib import Path - -# Initialize logger for this module -logger = setup_logger(__name__) - -class CodeExecutor: - """Class for executing code in various programming languages.""" - - def __init__(self): - """Initialize the code executor.""" - logger.info("Initializing CodeExecutor") - self.executors = { - "Python": self.execute_python, - "JavaScript": self.execute_javascript, - "Java": self.execute_java, - "C++": self.execute_cpp, - "Julia": self.execute_julia, - "Go": self.execute_go, - "Perl": self.execute_perl, - "Lua": self.execute_lua, - "PHP": self.execute_php, - "Kotlin": self.execute_kotlin, - "SQL": self.execute_sql, - "R": self.execute_r, - "Ruby": self.execute_ruby, - "Swift": self.execute_swift, - "Rust": self.execute_rust, - "C#": self.execute_csharp, - "TypeScript": self.execute_typescript - } - - def execute(self, code: str, language: str) -> tuple[str, Optional[bytes]]: - """Execute code with detailed logging.""" - logger.info("="*50) - logger.info(f"STARTING CODE EXECUTION: {language}") - logger.info("="*50) - logger.info(f"Code length: {len(code)} characters") - - if not code: - logger.warning("No code provided for execution") - return "No code to execute", None - - executor = self.executors.get(language) - if not executor: - logger.error(f"No executor found for language: {language}") - return f"Execution not implemented for {language}", None - - try: - logger.info(f"Executing {language} code") - start_time = datetime.now() - - output, binary = executor(code) - - execution_time = (datetime.now() - start_time).total_seconds() - logger.info(f"Execution completed in {execution_time:.2f} seconds") - logger.info(f"Output length: {len(output)} characters") - if binary: - logger.info(f"Binary size: {len(binary)} bytes") - logger.info("="*50) - - return f"{output}\nExecution completed in {execution_time:.2f} seconds", binary - - except Exception as e: - logger.error(f"Error executing {language} code", exc_info=True) - logger.info("="*50) - return f"Error: {str(e)}", None - - def execute_python(self, code: str) -> tuple[str, Optional[bytes]]: - """Execute Python code in a safe environment.""" - output = io.StringIO() - with contextlib.redirect_stdout(output): - try: - # Create a shared namespace for globals and locals - namespace = {} - - # Execute the code with shared namespace - exec(code, namespace, namespace) - - # Get any stored output - execution_output = output.getvalue() - - # If there's a result variable, append it to output - if '_result' in namespace: - execution_output += str(namespace['_result']) - - return execution_output, None - except Exception as e: - logger.error(f"Python execution error: {str(e)}", exc_info=True) - return f"Error: {str(e)}", None - finally: - output.close() - - def execute_javascript(self, code: str) -> tuple[str, Optional[bytes]]: - """Execute JavaScript code using Node.js.""" - with tempfile.NamedTemporaryFile(suffix='.js', mode='w', delete=False) as f: - f.write(code) - js_file = f.name - - try: - result = subprocess.run( - ["node", js_file], - capture_output=True, - text=True, - check=True - ) - return result.stdout, None - except subprocess.CalledProcessError as e: - return f"Error: {e.stderr}", None - except Exception as e: - return f"Error: {str(e)}", None - finally: - os.unlink(js_file) - - def execute_julia(self, code: str) -> tuple[str, Optional[bytes]]: - """Execute Julia code.""" - with tempfile.NamedTemporaryFile(suffix='.jl', mode='w', delete=False) as f: - f.write(code) - jl_file = f.name - - try: - result = subprocess.run( - ["julia", jl_file], - capture_output=True, - text=True, - check=True - ) - return result.stdout, None - except subprocess.CalledProcessError as e: - return f"Error: {e.stderr}", None - except Exception as e: - return f"Error: {str(e)}", None - finally: - os.unlink(jl_file) - - def execute_cpp(self, code: str) -> tuple[str, Optional[bytes]]: - """Compile and execute C++ code.""" - with tempfile.NamedTemporaryFile(suffix='.cpp', mode='w', delete=False) as f: - f.write(code) - cpp_file = f.name - - try: - # Compile - exe_file = cpp_file[:-4] # Remove .cpp - if os.name == 'nt': # Windows - exe_file += '.exe' - - compile_result = subprocess.run( - ["g++", cpp_file, "-o", exe_file], - capture_output=True, - text=True, - check=True - ) - - # Execute - run_result = subprocess.run( - [exe_file], - capture_output=True, - text=True, - check=True - ) - - # Read the compiled binary - with open(exe_file, 'rb') as f: - compiled_binary = f.read() - - return run_result.stdout, compiled_binary - - except subprocess.CalledProcessError as e: - return f"Error: {e.stderr}", None - except Exception as e: - return f"Error: {str(e)}", None - finally: - os.unlink(cpp_file) - if os.path.exists(exe_file): - os.unlink(exe_file) - - def execute_java(self, code: str) -> tuple[str, Optional[bytes]]: - """Compile and execute Java code.""" - logger.info("Starting Java code execution") - - # Create a temporary directory with proper permissions - with tempfile.TemporaryDirectory() as temp_dir: - try: - # Extract class name - class_match = re.search(r'public\s+class\s+(\w+)', code) - if not class_match: - logger.error("Could not find public class name in Java code") - return "Error: Could not find public class name", None - - class_name = class_match.group(1) - temp_path = Path(temp_dir) - java_file = temp_path / f"{class_name}.java" - class_file = temp_path / f"{class_name}.class" - - # Write code to file - java_file.write_text(code) - logger.info(f"Wrote Java source to {java_file}") - - # Compile - logger.info("Compiling Java code") - compile_result = subprocess.run( - ["javac", str(java_file)], - capture_output=True, - text=True, - check=True, - cwd=temp_dir # Set working directory to temp_dir - ) - logger.info("Java compilation successful") - - # Verify class file exists - if not class_file.exists(): - logger.error(f"Class file {class_file} not found after compilation") - return "Error: Compilation failed to produce class file", None - - # Read compiled bytecode - compiled_binary = class_file.read_bytes() - logger.info(f"Read compiled binary, size: {len(compiled_binary)} bytes") - - # Execute - logger.info("Executing Java code") - run_result = subprocess.run( - ["java", class_name], - capture_output=True, - text=True, - check=True, - cwd=temp_dir # Set working directory to temp_dir - ) - logger.info("Java execution successful") - - # Return both output and compiled binary - return run_result.stdout, compiled_binary - - except subprocess.CalledProcessError as e: - logger.error(f"Java compilation/execution error: {e.stderr}") - return f"Error: {e.stderr}", None - except Exception as e: - logger.error(f"Unexpected error in Java execution: {str(e)}", exc_info=True) - return f"Error: {str(e)}", None - - def execute_go(self, code: str) -> tuple[str, Optional[bytes]]: - """Execute Go code.""" - with tempfile.NamedTemporaryFile(suffix='.go', mode='w', delete=False) as f: - f.write(code) - go_file = f.name - - try: - # Compile first - exe_file = go_file[:-3] # Remove .go - if os.name == 'nt': - exe_file += '.exe' - - compile_result = subprocess.run( - ["go", "build", "-o", exe_file, go_file], - capture_output=True, - text=True, - check=True - ) - - # Read compiled binary - with open(exe_file, 'rb') as f: - compiled_binary = f.read() - - # Execute - run_result = subprocess.run( - [exe_file], - capture_output=True, - text=True, - check=True - ) - return run_result.stdout, compiled_binary - - except subprocess.CalledProcessError as e: - return f"Error: {e.stderr}", None - except Exception as e: - return f"Error: {str(e)}", None - finally: - os.unlink(go_file) - if os.path.exists(exe_file): - os.unlink(exe_file) - - def execute_perl(self, code: str) -> tuple[str, Optional[bytes]]: - """Execute Perl code.""" - with tempfile.NamedTemporaryFile(suffix='.pl', mode='w', delete=False) as f: - f.write(code) - pl_file = f.name - - try: - result = subprocess.run( - ["perl", pl_file], - capture_output=True, - text=True, - check=True - ) - return result.stdout, None - except subprocess.CalledProcessError as e: - return f"Error: {e.stderr}", None - except Exception as e: - return f"Error: {str(e)}", None - finally: - os.unlink(pl_file) - - def execute_lua(self, code: str) -> tuple[str, Optional[bytes]]: - """Execute Lua code.""" - with tempfile.NamedTemporaryFile(suffix='.lua', mode='w', delete=False) as f: - f.write(code) - lua_file = f.name - - try: - result = subprocess.run( - ["lua", lua_file], - capture_output=True, - text=True, - check=True - ) - return result.stdout, None - except subprocess.CalledProcessError as e: - return f"Error: {e.stderr}", None - except Exception as e: - return f"Error: {str(e)}", None - finally: - os.unlink(lua_file) - - def execute_php(self, code: str) -> tuple[str, Optional[bytes]]: - """Execute PHP code.""" - with tempfile.NamedTemporaryFile(suffix='.php', mode='w', delete=False) as f: - f.write(code) - php_file = f.name - - try: - result = subprocess.run( - ["php", php_file], - capture_output=True, - text=True, - check=True - ) - return result.stdout, None - except subprocess.CalledProcessError as e: - return f"Error: {e.stderr}", None - except Exception as e: - return f"Error: {str(e)}", None - finally: - os.unlink(php_file) - - def execute_kotlin(self, code: str) -> tuple[str, Optional[bytes]]: - """Compile and execute Kotlin code.""" - with tempfile.NamedTemporaryFile(suffix='.kt', mode='w', delete=False) as f: - f.write(code) - kt_file = f.name - - try: - # Extract main class name (best effort) - class_match = re.search(r'class\s+(\w+)', code) - class_name = class_match.group(1) if class_match else "MainKt" - - # Compile - jar_file = kt_file[:-3] + ".jar" - compile_result = subprocess.run( - ["kotlinc", kt_file, "-include-runtime", "-d", jar_file], - capture_output=True, - text=True, - check=True - ) - - # Read compiled bytecode - with open(jar_file, 'rb') as f: - compiled_binary = f.read() - - # Execute - run_result = subprocess.run( - ["java", "-jar", jar_file], - capture_output=True, - text=True, - check=True - ) - - return run_result.stdout, compiled_binary - except subprocess.CalledProcessError as e: - return f"Error: {e.stderr}", None - except Exception as e: - return f"Error: {str(e)}", None - finally: - os.unlink(kt_file) - jar_file = kt_file[:-3] + ".jar" - if os.path.exists(jar_file): - os.unlink(jar_file) - - def execute_sql(self, code: str) -> tuple[str, Optional[bytes]]: - """Execute SQL code using SQLite.""" - # Create a temporary database file - db_file = tempfile.NamedTemporaryFile(suffix='.db', delete=False).name - - try: - # Write SQL directly to the sqlite3 process via stdin - process = subprocess.Popen( - ["sqlite3", db_file], - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True - ) - - # Send the SQL code to sqlite3 - stdout, stderr = process.communicate(input=code) - - if process.returncode != 0: - return f"Error: {stderr}", None - - # For SQL, we'll also run a simple query to show tables - tables_result = subprocess.run( - ["sqlite3", db_file, ".tables"], - capture_output=True, - text=True, - check=False - ) - - # Combine the results - output = stdout - if tables_result.returncode == 0 and tables_result.stdout.strip(): - output += "\n\nTables in database:\n" + tables_result.stdout - - # For each table, show schema - for table in tables_result.stdout.split(): - schema_result = subprocess.run( - ["sqlite3", db_file, f".schema {table}"], - capture_output=True, - text=True, - check=False - ) - if schema_result.returncode == 0: - output += "\n" + schema_result.stdout - - return output, None - except Exception as e: - return f"Error: {str(e)}", None - finally: - if os.path.exists(db_file): - os.unlink(db_file) - - def execute_r(self, code: str) -> tuple[str, Optional[bytes]]: - """Execute R code.""" - with tempfile.NamedTemporaryFile(suffix='.R', mode='w', delete=False) as f: - f.write(code) - r_file = f.name - - try: - result = subprocess.run( - ["Rscript", r_file], - capture_output=True, - text=True, - check=True - ) - return result.stdout, None - except subprocess.CalledProcessError as e: - return f"Error: {e.stderr}", None - except Exception as e: - return f"Error: {str(e)}", None - finally: - os.unlink(r_file) - - def execute_ruby(self, code: str) -> tuple[str, Optional[bytes]]: - """Execute Ruby code.""" - with tempfile.NamedTemporaryFile(suffix='.rb', mode='w', delete=False) as f: - f.write(code) - rb_file = f.name - - try: - result = subprocess.run( - ["ruby", rb_file], - capture_output=True, - text=True, - check=True - ) - return result.stdout, None - except subprocess.CalledProcessError as e: - return f"Error: {e.stderr}", None - except Exception as e: - return f"Error: {str(e)}", None - finally: - os.unlink(rb_file) - - def execute_swift(self, code: str) -> tuple[str, Optional[bytes]]: - """Execute Swift code.""" - with tempfile.NamedTemporaryFile(suffix='.swift', mode='w', delete=False) as f: - f.write(code) - swift_file = f.name - - try: - result = subprocess.run( - ["swift", swift_file], - capture_output=True, - text=True, - check=True - ) - return result.stdout, None - except subprocess.CalledProcessError as e: - return f"Error: {e.stderr}", None - except Exception as e: - return f"Error: {str(e)}", None - finally: - os.unlink(swift_file) - - def execute_rust(self, code: str) -> tuple[str, Optional[bytes]]: - """Compile and execute Rust code.""" - # Create a temporary directory for Rust project - with tempfile.TemporaryDirectory() as temp_dir: - # Create main.rs file - main_rs = os.path.join(temp_dir, "main.rs") - with open(main_rs, 'w') as f: - f.write(code) - - try: - # Compile - exe_file = os.path.join(temp_dir, "rustapp") - compile_result = subprocess.run( - ["rustc", main_rs, "-o", exe_file], - capture_output=True, - text=True, - check=True - ) - - # Read compiled binary - with open(exe_file, 'rb') as f: - compiled_binary = f.read() - - # Execute - run_result = subprocess.run( - [exe_file], - capture_output=True, - text=True, - check=True - ) - - return run_result.stdout, compiled_binary - except subprocess.CalledProcessError as e: - return f"Error: {e.stderr}", None - except Exception as e: - return f"Error: {str(e)}", None - - def execute_csharp(self, code: str) -> tuple[str, Optional[bytes]]: - """Compile and execute C# code.""" - with tempfile.NamedTemporaryFile(suffix='.cs', mode='w', delete=False) as f: - f.write(code) - cs_file = f.name - - try: - # Compile to executable - exe_file = cs_file[:-3] + ".exe" - compile_result = subprocess.run( - ["mono-csc", cs_file, "-out:" + exe_file], - capture_output=True, - text=True, - check=True - ) - - # Read compiled binary - with open(exe_file, 'rb') as f: - compiled_binary = f.read() - - # Execute - if os.name == 'nt': # Windows - run_result = subprocess.run( - [exe_file], - capture_output=True, - text=True, - check=True - ) - else: # Unix-like - run_result = subprocess.run( - ["mono", exe_file], - capture_output=True, - text=True, - check=True - ) - - return run_result.stdout, compiled_binary - except subprocess.CalledProcessError as e: - return f"Error: {e.stderr}", None - except Exception as e: - return f"Error: {str(e)}", None - finally: - os.unlink(cs_file) - exe_file = cs_file[:-3] + ".exe" - if os.path.exists(exe_file): - os.unlink(exe_file) - - def execute_typescript(self, code: str) -> tuple[str, Optional[bytes]]: - """Compile and execute TypeScript code.""" - with tempfile.NamedTemporaryFile(suffix='.ts', mode='w', delete=False) as f: - f.write(code) - ts_file = f.name - - try: - # Compile TypeScript to JavaScript - js_file = ts_file[:-3] + ".js" - compile_result = subprocess.run( - ["tsc", ts_file, "--outFile", js_file], - capture_output=True, - text=True, - check=True - ) - - # Execute the compiled JavaScript - run_result = subprocess.run( - ["node", js_file], - capture_output=True, - text=True, - check=True - ) - - return run_result.stdout, None - except subprocess.CalledProcessError as e: - return f"Error: {e.stderr}", None - except Exception as e: - return f"Error: {str(e)}", None - finally: - os.unlink(ts_file) - js_file = ts_file[:-3] + ".js" - if os.path.exists(js_file): - os.unlink(js_file) \ No newline at end of file diff --git a/week4/community-contributions/ai_code_converter/src/ai_code_converter/core/file_utils.py b/week4/community-contributions/ai_code_converter/src/ai_code_converter/core/file_utils.py deleted file mode 100644 index 9c02d1e..0000000 --- a/week4/community-contributions/ai_code_converter/src/ai_code_converter/core/file_utils.py +++ /dev/null @@ -1,250 +0,0 @@ -"""Module for handling file operations.""" - -import logging -import tempfile -import zipfile -import platform -from pathlib import Path -from typing import Optional, Tuple -import os -import shutil -import gradio as gr -from datetime import datetime - -from src.ai_code_converter.utils.logger import setup_logger - -logger = setup_logger(__name__) - -class FileHandler: - """Class for handling file operations.""" - - def create_readme(self, language: str, files: list[str]) -> str: - """Create a README file with compilation instructions.""" - system_info = platform.uname() - timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - - readme = f"""# {language} Code Compilation Instructions - -Generated on: {timestamp} - -## Compilation Environment -- OS: {system_info.system} -- Architecture: {system_info.machine} -- Platform: {system_info.version} - -## Files Included -{chr(10).join(f"- {file}" for file in files)} - -## Compilation Instructions - -""" - if language == "C++": - readme += """### Windows -1. Install MinGW or Visual Studio with C++ support -2. Open command prompt in this directory -3. Run: `g++ main.cpp -o program.exe` -4. Execute: `program.exe` - -### Linux -1. Install GCC: `sudo apt-get install build-essential` (Ubuntu/Debian) -2. Open terminal in this directory -3. Run: `g++ main.cpp -o program` -4. Execute: `./program` - -### macOS -1. Install Xcode Command Line Tools: `xcode-select --install` -2. Open terminal in this directory -3. Run: `g++ main.cpp -o program` -4. Execute: `./program` -""" - elif language == "Java": - readme += """### All Platforms (Windows/Linux/macOS) -1. Install JDK 11 or later -2. Open terminal/command prompt in this directory -3. Compile: `javac Main.java` -4. Run: `java Main` - -Note: The class name in the Java file must be 'Main' for these instructions. -""" - elif language == "Go": - readme += """### Windows -1. Install Go from https://golang.org/ -2. Open command prompt in this directory -3. Run: `go build main.go` -4. Execute: `main.exe` - -### Linux/macOS -1. Install Go: - - Linux: `sudo apt-get install golang` (Ubuntu/Debian) - - macOS: `brew install go` (using Homebrew) -2. Open terminal in this directory -3. Run: `go build main.go` -4. Execute: `./main` -""" - return readme - - def create_compilation_zip(self, code: str, language: str, compiled_code: Optional[bytes] = None) -> Tuple[Optional[str], Optional[str]]: - """Create a zip file containing source, compiled files, and README.""" - try: - timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") - project_name = f"ai_code_converter_{language.lower()}_{timestamp}" - - # Create temporary directory - with tempfile.TemporaryDirectory() as temp_dir: - temp_dir_path = Path(temp_dir) - files_included = [] - - # Write source code with standardized names - if language == "C++": - source_file = temp_dir_path / "main.cpp" - compiled_file = temp_dir_path / ("main.exe" if os.name == 'nt' else "main") - files_included.extend(["main.cpp", "main.exe" if os.name == 'nt' else "main"]) - elif language == "Java": - source_file = temp_dir_path / "Main.java" - compiled_file = temp_dir_path / "Main.class" - files_included.extend(["Main.java", "Main.class"]) - elif language == "Go": - source_file = temp_dir_path / "main.go" - compiled_file = temp_dir_path / ("main.exe" if os.name == 'nt' else "main") - files_included.extend(["main.go", "main.exe" if os.name == 'nt' else "main"]) - else: - return None, None - - # Write source code - source_file.write_text(code) - - # Write compiled code if available - if compiled_code: - logger.info(f"Writing compiled binary: {compiled_file}") - try: - # Ensure the file is writable - compiled_file.parent.mkdir(parents=True, exist_ok=True) - compiled_file.write_bytes(compiled_code) - logger.info(f"Successfully wrote compiled binary: {compiled_file}") - except Exception as e: - logger.error(f"Error writing compiled binary: {e}", exc_info=True) - else: - logger.warning("No compiled code available") - - # Create README - readme_path = temp_dir_path / "README.md" - readme_content = self.create_readme(language, files_included) - readme_path.write_text(readme_content) - files_included.append("README.md") - - # Create zip file with descriptive name - zip_filename = f"{project_name}.zip" - zip_path = temp_dir_path / zip_filename - - # Create zip file - with zipfile.ZipFile(zip_path, 'w') as zf: - for file in files_included: - file_path = temp_dir_path / file - if file_path.exists(): - logger.info(f"Adding to zip: {file}") - zf.write(file_path, file_path.name) - else: - logger.warning(f"File not found for zip: {file}") - - # Create a permanent location for the zip file - downloads_dir = Path("downloads") - downloads_dir.mkdir(exist_ok=True) - - final_zip_path = downloads_dir / zip_filename - shutil.copy2(zip_path, final_zip_path) - - # Verify zip contents - with zipfile.ZipFile(final_zip_path, 'r') as zf: - logger.info(f"Zip file contents: {zf.namelist()}") - - return str(final_zip_path), zip_filename - - except Exception as e: - logger.error(f"Error creating compilation zip: {str(e)}", exc_info=True) - return None, None - - def prepare_download(self, code: str, language: str, compiled_code: Optional[bytes] = None) -> Tuple[Optional[str], Optional[str]]: - """Prepare code for download with consistent naming.""" - try: - timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") - downloads_dir = Path("downloads") - downloads_dir.mkdir(exist_ok=True) - - # For compiled languages, create a zip with source and instructions - if language in ["C++", "Java", "Go"]: - logger.info(f"Creating compilation zip for {language}") - # Pass compiled_code directly as bytes - return self.create_compilation_zip( - code=code, - language=language, - compiled_code=compiled_code if isinstance(compiled_code, bytes) else None - ) - - # For interpreted languages, create a single file with timestamp - extension = self._get_file_extension(language) - filename = f"ai_code_converter_{language.lower()}_{timestamp}{extension}" - file_path = downloads_dir / filename - - # Write the file - file_path.write_text(code) - return str(file_path), filename - - except Exception as e: - logger.error(f"Error preparing download: {str(e)}", exc_info=True) - return None, None - - def load_file(self, file_path: str) -> str: - """Load code from a file.""" - try: - with open(file_path, 'r') as f: - return f.read() - except Exception as e: - logger.error(f"Error loading file: {str(e)}", exc_info=True) - raise - - def _get_file_extension(self, language: str) -> str: - """Get file extension for a language.""" - extensions = { - "Python": ".py", - "JavaScript": ".js", - "Java": ".java", - "C++": ".cpp", - "Julia": ".jl", - "Go": ".go" - } - return extensions.get(language, ".txt") - - def handle_file_upload(self, file: gr.File) -> tuple[str, str]: - """Handle file upload with detailed logging.""" - logger = logging.getLogger(__name__) - - if not file: - logger.warning("File upload attempted but no file was provided") - return "", "No file uploaded" - - try: - logger.info(f"Processing uploaded file: {file.name}") - logger.info(f"File size: {os.path.getsize(file.name)} bytes") - - # Create downloads directory if it doesn't exist - downloads_dir = Path("downloads") - downloads_dir.mkdir(exist_ok=True) - - # Copy uploaded file to downloads with timestamp - timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") - _, ext = os.path.splitext(file.name) - new_filename = f"ai_code_converter_upload_{timestamp}{ext}" - new_path = downloads_dir / new_filename - - with open(file.name, 'r') as src, open(new_path, 'w') as dst: - content = src.read() - dst.write(content) - - logger.info(f"File saved as: {new_path}") - logger.info(f"Content length: {len(content)} characters") - return content, None - - except Exception as e: - error_msg = f"Error loading file: {str(e)}" - logger.error(error_msg, exc_info=True) - return "", error_msg \ No newline at end of file diff --git a/week4/community-contributions/ai_code_converter/src/ai_code_converter/core/language_detection.py b/week4/community-contributions/ai_code_converter/src/ai_code_converter/core/language_detection.py deleted file mode 100644 index 1ddccd4..0000000 --- a/week4/community-contributions/ai_code_converter/src/ai_code_converter/core/language_detection.py +++ /dev/null @@ -1,793 +0,0 @@ -"""Module for detecting programming languages in code snippets.""" - -import re -import logging -from typing import Tuple - -logger = logging.getLogger(__name__) - -class LanguageDetector: - """Class for detecting programming languages in code snippets.""" - - @staticmethod - def detect_python(code: str) -> bool: - """Detect if code is Python.""" - patterns = [ - r'def\s+\w+\s*\([^)]*\)\s*:', # Function definition - r'import\s+[\w\s,]+', # Import statements - r'from\s+[\w.]+\s+import', # From import statements - r'print\s*\([^)]*\)', # Print statements - r':\s*$' # Line ending with colon - ] - return any(re.search(pattern, code) for pattern in patterns) - - @staticmethod - def detect_javascript(code: str) -> bool: - """Detect if code is JavaScript.""" - patterns = [ - r'function\s+\w+\s*\([^)]*\)', # Function declaration - r'const\s+\w+\s*=', # Const declaration - r'let\s+\w+\s*=', # Let declaration - r'var\s+\w+\s*=', # Var declaration - r'console\.(log|error|warn)' # Console methods - ] - return any(re.search(pattern, code) for pattern in patterns) - - @staticmethod - def detect_java(code: str) -> bool: - """Detect if code is Java.""" - patterns = [ - r'public\s+class\s+\w+', # Class declaration - r'public\s+static\s+void\s+main', # Main method - r'System\.(out|err)\.', # System output - r'private|protected|public', # Access modifiers - r'import\s+java\.' # Java imports - ] - return any(re.search(pattern, code) for pattern in patterns) - - @staticmethod - def detect_cpp(code: str) -> bool: - """Detect if code is C++.""" - patterns = [ - r'#include\s*<[^>]+>', # Include statements - r'std::\w+', # STD namespace usage - r'cout\s*<<', # Console output - r'cin\s*>>', # Console input - r'int\s+main\s*\(' # Main function - ] - return any(re.search(pattern, code) for pattern in patterns) - - @staticmethod - def detect_julia(code: str) -> bool: - """Detect if code is Julia.""" - patterns = [ - r'function\s+\w+\s*\([^)]*\)\s*end', # Function with end - r'println\(', # Print function - r'using\s+\w+', # Using statement - r'module\s+\w+', # Module declaration - r'struct\s+\w+' # Struct declaration - ] - return any(re.search(pattern, code) for pattern in patterns) - - @staticmethod - def detect_go(code: str) -> bool: - """Detect if code is Go.""" - patterns = [ - r'package\s+\w+', # Package declaration - r'func\s+\w+\s*\(', # Function declaration - r'import\s*\(', # Import block - r'fmt\.', # fmt package usage - r'type\s+\w+\s+struct' # Struct declaration - ] - return any(re.search(pattern, code) for pattern in patterns) - - @staticmethod - def detect_ruby(code: str) -> bool: - """Detect if code is Ruby.""" - patterns = [ - r'def\s+\w+\s*(?:\([^)]*\))?\s*$', # Method definition without end - r'class\s+\w+(?:\s*<\s*\w+)?\s*$', # Class definition without end - r'require\s+["\']\w+["\']', # Require statement - r'\b(?:puts|print)\s', # Output methods - r'\bdo\s*\|[^|]*\|', # Block with parameters - r'\bend\b', # End keyword - r':[a-zA-Z_]\w*\s*=>', # Symbol hash syntax - r'[a-zA-Z_]\w*:\s*[^,\s]' # New hash syntax - ] - return any(re.search(pattern, code) for pattern in patterns) - - @staticmethod - def detect_swift(code: str) -> bool: - """Detect if code is Swift.""" - patterns = [ - r'import\s+(?:Foundation|UIKit|SwiftUI)', # Common Swift imports - r'(?:var|let)\s+\w+\s*:\s*\w+', # Variable declaration with type - r'func\s+\w+\s*\([^)]*\)\s*(?:->\s*\w+)?\s*\{', # Function declaration - r'class\s+\w+(?:\s*:\s*\w+)?\s*\{', # Class declaration - r'struct\s+\w+\s*\{', # Struct declaration - r'@IBOutlet|@IBAction', # iOS annotations - r'guard\s+let', # Guard statement - r'if\s+let|if\s+var', # Optional binding - r'override\s+func', # Method override - r'\bimport\s+Swift\b', # Swift import - r'\?\?', # Nil coalescing operator - r'extension\s+\w+', # Extensions - r'protocol\s+\w+', # Protocols - r'enum\s+\w+\s*(?::\s*\w+)?\s*\{', # Enums - r'case\s+\w+(?:\(.*?\))?', # Enum cases - r'typealias\s+\w+', # Type aliases - r'init\s*\(', # Initializer - r'deinit\s*\{', # Deinitializer - r'\$\d+', # String interpolation - r'\bOptional<', # Optional type - r'\bas\?', # Type casting - r'\bas!', # Forced type casting - r'convenience\s+init', # Convenience initializer - r'required\s+init', # Required initializer - r'\bUInt\d*\b', # Swift integer types - r'\bInt\d*\b', # Swift integer types - r'\barray<', # Swift arrays - r'\bdictionary<', # Swift dictionaries - r'@escaping', # Escaping closures - r'\bweak\s+var', # Weak references - r'\bunowned\b' # Unowned references - ] - return any(re.search(pattern, code, re.IGNORECASE) for pattern in patterns) - - @staticmethod - def detect_rust(code: str) -> bool: - """Detect if code is Rust.""" - patterns = [ - r'fn\s+\w+\s*\([^)]*\)\s*(?:->\s*[^{]+)?\s*\{', # Function declaration - r'let\s+mut\s+\w+', # Mutable variable declaration - r'struct\s+\w+\s*\{[^}]*\}', # Struct definition - r'impl\s+\w+(?:\s+for\s+\w+)?', # Implementation block - r'use\s+[\w:]+', # Import/use statement - r'pub\s+(?:fn|struct|enum|mod)', # Public items - r'Vec<[^>]+>', # Vec generic type - r'match\s+\w+\s*\{', # Match expression - r'#\[\w+(?:\([^)]*\))?\]', # Attribute macros - r'\bResult<', # Result type - r'\bOption<', # Option type - r'\bmod\s+\w+', # Module declaration - r'&mut\s+\w+', # Mutable references - r'&\w+', # Immutable references - r'\|[^|]*\|\s*\{', # Closure syntax - r'::\s*[A-Z]\w+', # Path to types - r'::(?:<[^>]+>)?\s*\w+\(', # Method call with turbofish - r'enum\s+\w+\s*\{', # Enum definition - r'-\s*>\s*[\w<>:]+', # Return type arrow - r'async\s+fn', # Async functions - r'await', # Await syntax - r'move\s*\|', # Move closures - r'trait\s+\w+', # Trait definition - r'\bSome\(', # Some variant - r'\bNone\b', # None variant - r'\bOk\(', # Ok variant - r'\bErr\(', # Err variant - r'\bcrate::\w+', # Crate references - r'pub\(crate\)', # Crate visibility - r'\bdyn\s+\w+', # Dynamic dispatch - r'\bif\s+let\s+Some', # If let for Option - r'\bif\s+let\s+Ok' # If let for Result - ] - return any(re.search(pattern, code, re.IGNORECASE) for pattern in patterns) - - @staticmethod - def detect_csharp(code: str) -> bool: - """Detect if code is C#.""" - patterns = [ - r'using\s+[\w.]+;', # Using statement - r'namespace\s+[\w.]+', # Namespace declaration - r'(public|private|protected|internal)\s+(class|struct|interface|enum)', # Type declarations - r'(public|private|protected|internal)\s+[\w<>\[\]]+\s+\w+\s*\(', # Method declaration - r'Console\.(Write|WriteLine)', # Console output - r'\bvar\s+\w+\s*=', # Var keyword - r'new\s+\w+\s*\(', # Object instantiation - r'\bawait\s+', # Async/await - r'\btask<', # Task object - r'\bdynamic\b', # Dynamic type - r'\bstring\b', # String type - r'\$".*?\{.*?\}.*?"', # String interpolation with $ - r'\bIEnumerable<', # C# generics - r'\bList<', # C# collections - r'\bdictionary<', # C# collections - r'\bforeach\s*\(', # foreach loops - r'\bassert\.\w+\(', # Unit testing - r'\[\w+\]', # Attributes - r'\bdelegate\b', # Delegates - r'\bevent\b', # Events - r'\bpartial\b', # Partial classes - r'\bvirtual\b', # Virtual methods - r'\boverride\b', # Override methods - r'\?\s*\w+\s*\??', # Nullable types - r'<\w+>\s*where\s+\w+\s*:', # Generic constraints - r'set\s*\{', # Property setters - r'get\s*\{', # Property getters - r'using\s*\(', # Using statements with blocks - r'\bget;\s*set;', # Auto-properties - r'\bselect\s+new\b' # LINQ expressions - ] - return any(re.search(pattern, code, re.IGNORECASE) for pattern in patterns) - - @staticmethod - def detect_typescript(code: str) -> bool: - """Detect if code is TypeScript.""" - # TypeScript-specific patterns (not commonly found in other languages) - ts_patterns = [ - r':\s*[A-Za-z]+(?:<[^>]+>)?\s*(?:=|;|\)|\})', # Type annotations - r'interface\s+\w+\s*\{', # Interface declaration - r'class\s+\w+(?:\s+implements|\s+extends)?', # Class with implements or extends - r'(private|public|protected)\s+\w+', # Access modifiers - r'\w+\s*<[^>]+>', # Generic types - r'import\s+\{[^}]+\}\s+from', # ES6 import - r'export\s+(interface|class|type|const|let)', # Exports - r'type\s+\w+\s*=', # Type aliases - r'enum\s+\w+', # Enums - r'@\w+(?:\([^)]*\))?', # Decorators - r'\w+\s*:\s*(?:string|number|boolean|any|void|never|unknown)', # Basic TypeScript types - r'(?:string|number|boolean|any|void)\[\]', # Array type notation - r'readonly\s+\w+', # Readonly modifier - r'namespace\s+\w+', # TypeScript namespaces - r'declare\s+(?:var|let|const|function|class|interface)', # Declarations - r'<[^>]+>\(', # Generic function calls - r'extends\s+\w+<', # Generic type inheritance - r'implements\s+\w+', # Interface implementation - r'as\s+(?:string|number|boolean|any)', # Type assertions - r'\?\s*:', # Optional properties - r'\w+\s*\?\s*:', # Optional parameters - r'keyof\s+\w+', # keyof operator - r'typeof\s+\w+', # typeof operator - r'\bReadonly<', # Utility types - r'\bPartial<', # Utility types - r'\bRequired<', # Utility types - r'\bRecord<', # Utility types - r'\|\s*null', # Union with null - r'\|\s*undefined', # Union with undefined - r'\w+\s*&\s*\w+', # Intersection types - r'import\s+type', # Import types - r'export\s+type' # Export types - ] - - # Check for TypeScript-specific patterns - ts_unique = any(re.search(pattern, code, re.IGNORECASE) for pattern in ts_patterns) - - # JavaScript patterns for basic JS syntax (TypeScript is a superset of JS) - js_patterns = [ - r'function\s+\w+\s*\([^)]*\)', # Function declaration - r'const\s+\w+\s*=', # Const declaration - r'let\s+\w+\s*=', # Let declaration - r'var\s+\w+\s*=', # Var declaration - r'console\.(log|error|warn)' # Console methods - ] - - js_general = any(re.search(pattern, code, re.IGNORECASE) for pattern in js_patterns) - - # For TypeScript detection, we need to differentiate between object property definitions (which exist in JS) - # and type annotations (which are unique to TS) - - # First, check for specific patterns from other languages to avoid false positives - - # Lua-specific patterns - lua_patterns = [ - r'local\s+\w+\s*=', # Local variable declarations - r'function\s*\([^)]*\)\s*', # Anonymous functions - r'require\(["\']\w+["\'](\))', # Module imports - r'end\b', # End keyword - r'\bnil\b', # nil value - r'\bipairs\(', # ipairs function - r'\bpairs\(', # pairs function - r'\s*\.\.\.?\s*' # String concatenation or varargs - ] - - # PHP-specific patterns - php_patterns = [ - r'<\?php', # PHP opening tag - r'\$\w+', # PHP variables - r'\-\>\w+', # PHP object access - r'public\s+function', # PHP method declaration - r'namespace\s+\w+\\', # PHP namespace with backslash - r'use\s+\w+\\', # PHP use statements with backslash - r'\$this\->' # PHP $this reference - ] - - # If the code contains clear Lua or PHP indicators, it's not TypeScript - is_likely_lua = any(re.search(pattern, code) for pattern in lua_patterns) - is_likely_php = any(re.search(pattern, code) for pattern in php_patterns) - - if is_likely_lua or is_likely_php: - return False - - # Check for type annotations in context (not inside object literals) - type_annotation_patterns = [ - r'function\s+\w+\([^)]*\)\s*:\s*\w+', # Function return type - r'const\s+\w+\s*:\s*\w+', # Variable with type annotation - r'let\s+\w+\s*:\s*\w+', # Variable with type annotation - r':\s*(?:string|number|boolean|any|void|null|undefined)\b', # Basic type annotations - r':\s*[A-Z][\w]+(?![\w\(])', # Custom type annotations (type names typically start with capital letter) - r':\s*[\w\[\]<>|&]+(?=\s*(?:[,\);=]|$))' # Complex type annotations followed by certain delimiters - ] - - has_type_annotation = any(re.search(pattern, code) for pattern in type_annotation_patterns) - - # Look for JavaScript syntax indicators - js_object_literal = re.search(r'\{\s*\w+\s*:\s*[^:\{]+\s*(?:,|\})', code) is not None - contains_js_imports = re.search(r'import\s+[{\w\s,}]+\s+from\s+["\']', code) is not None - - # Only return true for TypeScript-specific patterns or if we have type annotations - # that are likely to be TypeScript and not other languages - return ts_unique or (has_type_annotation and (contains_js_imports or js_general) and not (js_object_literal and not ts_unique)) - - @staticmethod - def detect_r(code: str) -> bool: - """Detect if code is R.""" - patterns = [ - r'<-\s*(?:function|\w+)', # Assignment with <- - r'library\([\w\.]+\)', # Library import - r'(?:data|read)\.(?:frame|csv|table)', # Data frames - r'\b(?:if|for|while)\s*\(', # Control structures - r'\$\w+', # Variable access with $ - r'\bNA\b|\bNULL\b|\bTRUE\b|\bFALSE\b', # R constants - r'c\(.*?\)', # Vector creation with c() - r'(?:plot|ggplot)\(', # Plotting functions - r'\s*#.*$', # R comments - r'%>%', # Pipe operator - r'\bfactor\(', # Factor function - r'\bstr\(', # Structure function - r'\bas\.\w+\(', # Type conversion functions - r'\w+\s*<-\s*\w+\[.+?\]' # Subsetting with brackets - ] - return any(re.search(pattern, code) for pattern in patterns) - - @staticmethod - def detect_perl(code: str) -> bool: - """Detect if code is Perl.""" - patterns = [ - r'\$\w+', # Scalar variables - r'@\w+', # Array variables - r'%\w+', # Hash variables - r'use\s+[\w:]+\s*;', # Module imports - r'\bsub\s+\w+\s*\{', # Subroutine definition - r'\bmy\s+(?:\$|@|%)\w+', # Variable declarations - r'=~\s*(?:m|s|tr)', # Regular expression operators - r'print\s+(?:\$|@|%|")', # Print statements - r'(?:if|unless|while|for|foreach)\s*\(', # Control structures - r'\{.*?\}.*?\{.*?\}', # Block structure typical in Perl - r'->\w+', # Method calls - r'\b(?:shift|pop|push|splice)', # Array operations - r';\s*$', # Statements ending with semicolon - r'#.*$', # Comments - r'\bdie\s+', # Die statements - r'\bqw\s*\(', # qw() operator - r'\$_', # Special $_ variable - r'\bdefined\s+(?:\$|@|%)' # Defined operator - ] - return any(re.search(pattern, code) for pattern in patterns) - - @staticmethod - def detect_lua(code: str) -> bool: - """Detect if code is Lua.""" - patterns = [ - r'\blocal\s+\w+', # Local variable declarations - r'\bfunction\s+\w+(?:\w*\.\w+)*\s*\(', # Function definitions - r'(?:end|then|do|else)\b', # Lua keywords - r'\brequire\s*\(["\w\.\']+\)', # Module imports - r'\breturn\s+.+?$', # Return statements - r'\bnil\b', # Nil value - r'\bfor\s+\w+\s*=\s*\d+\s*,\s*\d+', # Numeric for loops - r'\bfor\s+\w+(?:\s*,\s*\w+)*\s+in\b', # Generic for loops - r'\bif\s+.+?\s+then\b', # If statements - r'\belseif\s+.+?\s+then\b', # Elseif statements - r'\btable\.(\w+)\b', # Table library functions - r'\bstring\.(\w+)\b', # String library functions - r'\bmath\.(\w+)\b', # Math library functions - r'\bpairs\(\w+\)', # Pairs function - r'\bipairs\(\w+\)', # Ipairs function - r'\btostring\(', # Tostring function - r'\btonumber\(', # Tonumber function - r'\bprint\(', # Print function - r'--.*$', # Comments - r'\[\[.*?\]\]', # Multiline strings - r'\{\s*[\w"\']+\s*=', # Table initialization - r'\w+\[\w+\]', # Table index access - r'\w+\.\.\w+' # String concatenation - ] - return any(re.search(pattern, code) for pattern in patterns) - - - @staticmethod - def detect_php(code: str) -> bool: - """Detect if code is PHP.""" - patterns = [ - r'<\?php', # PHP opening tag - r'\$\w+', # Variable with $ prefix - r'function\s+\w+\s*\(', # Function definition - r'echo\s+[\$\w\'\"]+', # Echo statement - r'class\s+\w+(?:\s+extends|\s+implements)?', # Class definition - r'(?:public|private|protected)\s+function', # Class methods - r'(?:public|private|protected)\s+\$\w+', # Class properties - r'namespace\s+[\w\\\\]+', # Namespace declaration - r'use\s+[\w\\\\]+', # Use statement - r'=>', # Array key => value syntax - r'array\s*\(', # Array creation - r'\[\s*[\'\"]*\w+[\'\"]*\s*\]', # Array access with [] - r'require(?:_once)?\s*\(', # Require statements - r'include(?:_once)?\s*\(', # Include statements - r'new\s+\w+', # Object instantiation - r'->', # Object property/method access - r'::', # Static property/method access - r'|$)', # Short echo syntax - r'if\s*\(.+?\)\s*\{', # If statements - r'foreach\s*\(\s*\$\w+', # Foreach loops - r';$' # Statement ending with semicolon - ] - return any(re.search(pattern, code) for pattern in patterns) - - - @staticmethod - def detect_kotlin(code: str) -> bool: - """Detect if code is Kotlin.""" - patterns = [ - r'fun\s+\w+\s*\(', # Function declaration - r'val\s+\w+(?:\s*:\s*\w+)?', # Val declaration - r'var\s+\w+(?:\s*:\s*\w+)?', # Var declaration - r'class\s+\w+(?:\s*\((?:[^)]*)\))?', # Class declaration - r'package\s+[\w\.]+', # Package declaration - r'import\s+[\w\.]+', # Import statement - r'object\s+\w+', # Object declaration - r'interface\s+\w+', # Interface declaration - r'data\s+class', # Data class - r'(?:override|open|abstract|final)\s+fun', # Modified functions - r'(?:companion|sealed)\s+object', # Special objects - r'when\s*\(', # When expression - r'(?:if|else|for|while)\s*\(', # Control structures - r'->', # Lambda syntax - r'[\w\.\(\)]+\.\w+\{', # Extension functions - r'(?:List|Set|Map)<', # Generic collections - r'(?:private|public|internal|protected)', # Visibility modifiers - r'lateinit\s+var', # Lateinit vars - r'(?:suspend|inline)\s+fun', # Special function modifiers - r'@\w+(?:\([^)]*\))?' # Annotations - ] - return any(re.search(pattern, code) for pattern in patterns) - - - @staticmethod - def detect_sql(code: str) -> bool: - """Detect if code is SQL.""" - patterns = [ - r'SELECT\s+[\w\*,\s]+\s+FROM', # SELECT statement - r'INSERT\s+INTO\s+\w+', # INSERT statement - r'UPDATE\s+\w+\s+SET', # UPDATE statement - r'DELETE\s+FROM\s+\w+', # DELETE statement - r'CREATE\s+TABLE\s+\w+', # CREATE TABLE statement - r'ALTER\s+TABLE\s+\w+', # ALTER TABLE statement - r'DROP\s+TABLE\s+\w+', # DROP TABLE statement - r'TRUNCATE\s+TABLE\s+\w+', # TRUNCATE TABLE statement - r'JOIN\s+\w+\s+ON', # JOIN clause - r'WHERE\s+\w+\s*(?:=|<|>|<=|>=|<>|!=|LIKE|IN)', # WHERE clause - r'GROUP\s+BY\s+\w+', # GROUP BY clause - r'ORDER\s+BY\s+\w+\s+(?:ASC|DESC)?', # ORDER BY clause - r'HAVING\s+\w+', # HAVING clause - r'CONSTRAINT\s+\w+', # CONSTRAINT declaration - r'PRIMARY\s+KEY', # PRIMARY KEY constraint - r'FOREIGN\s+KEY', # FOREIGN KEY constraint - r'(?:VARCHAR|INT|INTEGER|FLOAT|DATE|DATETIME|BOOLEAN|TEXT|BLOB)', # Common data types - r'(?:COUNT|SUM|AVG|MIN|MAX)\s*\(', # Aggregate functions - r'CASE\s+WHEN\s+.+?\s+THEN', # CASE statement - r'BEGIN\s+TRANSACTION', # Transaction control - r'COMMIT', # COMMIT statement - r'ROLLBACK' # ROLLBACK statement - ] - return any(re.search(pattern, code, re.IGNORECASE) for pattern in patterns) - - - def __init__(self): - """Initialize language detector with detection functions.""" - self.detectors = { - "Python": self.detect_python, - "JavaScript": self.detect_javascript, - "Java": self.detect_java, - "C++": self.detect_cpp, - "Julia": self.detect_julia, - "Go": self.detect_go, - "Ruby": self.detect_ruby, - "Swift": self.detect_swift, - "Rust": self.detect_rust, - "C#": self.detect_csharp, - "TypeScript": self.detect_typescript, - "R": self.detect_r, - "Perl": self.detect_perl, - "Lua": self.detect_lua, - "PHP": self.detect_php, - "Kotlin": self.detect_kotlin, - "SQL": self.detect_sql - } - - - def detect_language(self, code: str) -> str: - """ - Detect the programming language of the given code. - - Args: - code: Code snippet to analyze - - Returns: - Detected language name or None if unknown - """ - # Initial matching - which language detectors return positive - matches = {} - - # First pass: check which languages match - for lang, detector in self.detectors.items(): - if detector(code): - matches[lang] = 0 - - if not matches: - return None - - # If only one language matches, return it - if len(matches) == 1: - return list(matches.keys())[0] - - # Improved scoring system with distinctive language features - # These patterns are selected to be highly distinctive to each language - # Each pattern is assigned a weight based on how uniquely it identifies a language - unique_patterns = { - # C# distinctive features (to differentiate from Java and other languages) - "C#": [ - (r'using\s+[\w.]+;', 3), # Using statement - (r'namespace\s+[\w.]+', 3), # Namespace - (r'Console\.(Write|WriteLine)', 2), # Console output - (r'\bvar\s+\w+\s*=', 3), # Var keyword - (r'\bawait\s+', 4), # Async/await - (r'\btask<', 4), # Task object - (r'\bdynamic\b', 4), # Dynamic type - (r'\$".*?\{.*?\}.*?"', 5), # String interpolation with $ - (r'\bIEnumerable<', 4), # C# collections - (r'\bList<', 3), # C# collections - (r'\bforeach\s*\(', 2), # foreach loops - (r'\bget;\s*set;', 5), # Auto-properties - (r'\bselect\s+new\b', 4), # LINQ - (r'\bwhere\s+\w+\s*=', 4), # LINQ - (r'<\w+>\s*where\s+\w+\s*:', 5) # Generic constraints - ], - - # Rust distinctive features (to differentiate from C++) - "Rust": [ - (r'fn\s+\w+\s*\(', 3), # Function declaration - (r'let\s+mut\s+\w+', 5), # Mutable variable - (r'impl\s+\w+(?:\s+for\s+\w+)?', 6), # Implementation - (r'use\s+[\w:]+', 2), # Use statement - (r'pub\s+(?:fn|struct|enum|mod)', 4), # Public items - (r'\bResult<', 5), # Result type - (r'\bOption<', 5), # Option type - (r'\bmod\s+\w+', 4), # Module declaration - (r'&mut\s+\w+', 5), # Mutable references - (r'trait\s+\w+', 6), # Trait definition - (r'\bSome\(', 5), # Some variant - (r'\bNone\b', 5), # None variant - (r'\bOk\(', 5), # Ok variant - (r'\bErr\(', 5), # Err variant - (r'\bcrate::\w+', 5), # Crate references - (r'\bif\s+let\s+Some', 6) # If let for Option - ], - - # Swift distinctive features (to differentiate from Kotlin) - "Swift": [ - (r'import\s+(?:Foundation|UIKit|SwiftUI)', 6), # Swift imports - (r'@IBOutlet|@IBAction', 8), # iOS annotations - (r'guard\s+let', 6), # Guard statement - (r'\bOptional<', 6), # Optional type - (r'\bas\?', 5), # Type casting - (r'\bas!', 5), # Forced type casting - (r'\?\?', 4), # Nil coalescing - (r'extension\s+\w+', 4), # Extensions - (r'protocol\s+\w+', 4), # Protocols - (r'convenience\s+init', 6), # Convenience init - (r'required\s+init', 6), # Required init - (r'\bUInt\d*\b', 5), # Swift integer types - (r'\bInt\d*\b', 4), # Swift integer types - (r'\barray<', 3), # Swift arrays - (r'\bdictionary<', 3), # Swift dictionaries - (r'@escaping', 8) # Escaping closures - ], - - # TypeScript distinctive features (to differentiate from JavaScript and Python) - "TypeScript": [ - (r':\s*[A-Za-z]+(?:<[^>]+>)?\s*(?:=|;|\)|\})', 5), # Type annotations - (r'interface\s+\w+\s*\{', 6), # Interface - (r'type\s+\w+\s*=', 6), # Type aliases - (r'enum\s+\w+', 5), # Enums - (r'namespace\s+\w+', 6), # TypeScript namespaces - (r'declare\s+(?:var|let|const|function|class|interface)', 7), # Declarations - (r'as\s+(?:string|number|boolean|any)', 6), # Type assertions - (r'\?\s*:', 5), # Optional properties - (r'\w+\s*\?\s*:', 5), # Optional parameters - (r'keyof\s+\w+', 7), # keyof operator - (r'typeof\s+\w+', 6), # typeof operator - (r'\bReadonly<', 7), # Utility types - (r'\bPartial<', 7), # Utility types - (r'\bRequired<', 7), # Utility types - (r'\bRecord<', 7), # Utility types - (r'\w+\s*&\s*\w+', 6), # Intersection types - (r'import\s+type', 8), # Import types - (r'export\s+type', 8) # Export types - ], - - # Keep other language patterns but add more weight to distinctive features - "PHP": [(r'<\?php', 10), (r'\$\w+\s*=', 2), (r'function\s+\w+\s*\(.*?\)\s*\{', 2)], - "SQL": [(r'(?i)SELECT\s+[\w\*,\s]+\s+FROM', 10), (r'(?i)INSERT\s+INTO', 8), (r'(?i)CREATE\s+TABLE', 8)], - "Perl": [(r'\buse\s+[\w:]+\s*;', 6), (r'\bmy\s+(?:\$|@|%)', 8), (r'\bperl\b', 10)], - "Lua": [(r'\blocal\s+\w+', 6), (r'\bfunction\s+\w+(?:\w*\.\w+)*\s*\(', 6), (r'\bend\s*$', 4)], - "Kotlin": [(r'\bfun\s+\w+\s*\(', 6), (r'\bval\s+\w+(?:\s*:\s*\w+)?', 6), (r'data\s+class', 10)], - "Ruby": [(r'\bdef\s+\w+\s*(?:\([^)]*\))?\s*$', 6), (r'\bend\b', 4), (r'\bdo\s*\|[^|]*\|', 6)], - "R": [(r'<-\s*(?:function|\w+)', 8), (r'library\([\w\.]+\)', 8), (r'%>%', 10)], - "Python": [(r'def\s+\w+\s*\([^)]*\)\s*:', 6), (r'import\s+[\w\s,]+', 4), (r'from\s+[\w.]+\s+import', 6)], - "JavaScript": [(r'function\s+\w+\s*\([^)]*\)', 4), (r'const\s+\w+\s*=', 3), (r'let\s+\w+\s*=', 3)], - "Java": [(r'public\s+class\s+\w+', 6), (r'public\s+static\s+void\s+main', 8), (r'System\.(out|err)\.', 6)], - "C++": [(r'#include\s*<[^>]+>', 6), (r'std::\w+', 8), (r'int\s+main\s*\(', 4)], - "Julia": [(r'function\s+\w+\s*\([^)]*\)\s*end', 8), (r'module\s+\w+', 6), (r'using\s+\w+', 4)], - "Go": [(r'package\s+\w+', 6), (r'func\s+\w+\s*\(', 4), (r'import\s*\(', 4)] - } - - # Apply the detailed scoring system - for lang, patterns in unique_patterns.items(): - if lang in matches: - for pattern, weight in patterns: - if re.search(pattern, code, re.IGNORECASE): - matches[lang] += weight - - # Additional weighting for core language features - # C# vs Java disambiguation - if "C#" in matches and "Java" in matches: - # C# specific features that are unlikely in Java - csharp_specific = [ - (r'\$"', 8), # String interpolation - (r'\bvar\b', 6), # var keyword - (r'\basync\b', 6), # async keyword - (r'\bawait\b', 6), # await keyword - (r'\bIEnumerable<', 6), # C# specific interfaces - (r'\bget;\s*set;', 8), # Auto-properties - (r'\bLINQ\b', 8), # LINQ - (r'\busing\s+static', 8) # using static - ] - for pattern, weight in csharp_specific: - if re.search(pattern, code, re.IGNORECASE): - matches["C#"] += weight - - # Java specific features that are unlikely in C# - java_specific = [ - (r'\bimport\s+java\.', 8), # Java imports - (r'\bSystem\.out\.print', 6), # Java System.out - (r'\bpublic\s+static\s+void\s+main', 8), # Java main method - (r'\@Override\b', 6), # Java annotations - (r'\bextends\s+\w+\s*\{', 6), # Java inheritance - (r'\bimplements\s+\w+\s*\{', 6) # Java interface implementation - ] - for pattern, weight in java_specific: - if re.search(pattern, code, re.IGNORECASE): - matches["Java"] += weight - - # Rust vs C++ disambiguation - if "Rust" in matches and "C++" in matches: - # Rust specific features that are unlikely in C++ - rust_specific = [ - (r'\bfn\b', 8), # fn keyword - (r'\blet\b', 8), # let keyword - (r'\bmut\b', 8), # mut keyword - (r'\bimpl\b', 8), # impl keyword - (r'\buse\b', 6), # use keyword - (r'\bSome\(', 8), # Some variant - (r'\bNone\b', 8), # None variant - (r'\bResult<', 8), # Result type - (r'\bOption<', 8) # Option type - ] - for pattern, weight in rust_specific: - if re.search(pattern, code, re.IGNORECASE): - matches["Rust"] += weight - - # C++ specific features that are unlikely in Rust - cpp_specific = [ - (r'#include', 8), # C++ include - (r'std::', 6), # C++ std namespace - (r'\bclass\b', 6), # class keyword - (r'\bpublic:\b', 8), # public: access specifier - (r'\bprivate:\b', 8), # private: access specifier - (r'\bprotected:\b', 8), # protected: access specifier - (r'\btypedef\b', 8), # typedef keyword - (r'\bnew\b', 4), # new keyword - (r'\bdelete\b', 8) # delete keyword - ] - for pattern, weight in cpp_specific: - if re.search(pattern, code, re.IGNORECASE): - matches["C++"] += weight - - # Swift vs Kotlin disambiguation - if "Swift" in matches and "Kotlin" in matches: - # Swift specific features that are unlikely in Kotlin - swift_specific = [ - (r'\bimport\s+(?:Foundation|UIKit|SwiftUI)', 10), # Swift imports - (r'\bguard\b', 8), # guard keyword - (r'\?\?', 8), # Nil coalescing operator - (r'\bas\?', 8), # Optional downcasting - (r'\bas!', 8), # Forced downcasting - (r'@IBOutlet', 10), # Interface Builder - (r'@IBAction', 10), # Interface Builder - (r'@objc', 10), # Objective-C interop - (r'\bUIViewController\b', 10) # UIKit - ] - for pattern, weight in swift_specific: - if re.search(pattern, code, re.IGNORECASE): - matches["Swift"] += weight - - # Kotlin specific features that are unlikely in Swift - kotlin_specific = [ - (r'\bdata\s+class', 10), # Data class - (r'\bfun\b', 8), # fun keyword - (r'\bval\b', 6), # val keyword - (r'\bvar\b', 4), # var keyword - (r'\bcompanion\s+object', 10), # Companion object - (r'\bwhen\b', 8), # when expression - (r'\bcoroutine', 10), # Coroutines - (r'\bsuspend\b', 10), # Suspend functions - (r'\blateinit\b', 10), # Late initialization - (r'\bimport\s+(?:kotlin|androidx|android)', 10) # Kotlin imports - ] - for pattern, weight in kotlin_specific: - if re.search(pattern, code, re.IGNORECASE): - matches["Kotlin"] += weight - - # TypeScript vs JavaScript disambiguation (and Python confusion) - if "TypeScript" in matches and ("JavaScript" in matches or "Python" in matches): - # TypeScript specific features that are unlikely in JS or Python - ts_specific = [ - (r':\s*[A-Za-z]+', 8), # Type annotations - (r'\binterface\b', 8), # interface keyword - (r'\btype\b\s+\w+\s*=', 10), # type aliases - (r'\bnamespace\b', 10), # namespace keyword - (r'\benum\b', 8), # enum keyword - (r'\bas\s+(?:string|number|boolean|any)', 10), # Type assertions - (r'\?:\s*', 10), # Optional types - (r'\bReadonly<', 10), # Utility types - (r'\w+\s*&\s*\w+', 10), # Intersection types - (r'\bimport\s+type', 10), # Import types - (r'\[\w+\s*:\s*\w+\]', 10) # Typed arrays - ] - for pattern, weight in ts_specific: - if re.search(pattern, code, re.IGNORECASE): - matches["TypeScript"] += weight - # Remove Python if it's a false positive - if "Python" in matches and matches["Python"] < matches["TypeScript"]: - matches.pop("Python", None) - - # Return the language with the highest score - if matches: - return max(matches.items(), key=lambda x: x[1])[0] - - return None - - def validate_language(self, code: str, expected_lang: str) -> tuple[bool, str]: - """Validate if code matches the expected programming language.""" - logger = logging.getLogger(__name__) - - logger.info(f"Starting code validation for {expected_lang}") - logger.info(f"Code length: {len(code)} characters") - - if not code or not expected_lang: - logger.warning("Empty code or language not specified") - return True, "" - - detector = self.detectors.get(expected_lang) - if not detector: - logger.warning(f"No detector found for language: {expected_lang}") - return True, "" - - if detector(code): - logger.info(f"Code successfully validated as {expected_lang}") - return True, "" - - detected_lang = self.detect_language(code) - error_msg = f"Code appears to be {detected_lang or 'unknown'} but {expected_lang} was selected" - logger.error(f"Language validation failed: {error_msg}") - return False, error_msg \ No newline at end of file diff --git a/week4/community-contributions/ai_code_converter/src/ai_code_converter/main.py b/week4/community-contributions/ai_code_converter/src/ai_code_converter/main.py deleted file mode 100644 index dd848f1..0000000 --- a/week4/community-contributions/ai_code_converter/src/ai_code_converter/main.py +++ /dev/null @@ -1,31 +0,0 @@ -"""Main entry point for the CodeXchange AI application.""" - -from src.ai_code_converter.app import CodeConverterApp -from src.ai_code_converter.utils.logger import setup_logger - -def main(): - """Initialize and run the application.""" - # Initialize logger - logger = setup_logger("ai_code_converter.main") - - try: - logger.info("="*50) - logger.info("Starting CodeXchange AI") - logger.info("="*50) - - logger.info("Initializing application components") - app = CodeConverterApp() - - logger.info("Starting Gradio interface") - app.run(share=True) - - except Exception as e: - logger.error("Application failed to start", exc_info=True) - raise - finally: - logger.info("="*50) - logger.info("Application shutdown") - logger.info("="*50) - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/week4/community-contributions/ai_code_converter/src/ai_code_converter/models/__init__.py b/week4/community-contributions/ai_code_converter/src/ai_code_converter/models/__init__.py deleted file mode 100644 index bc3029c..0000000 --- a/week4/community-contributions/ai_code_converter/src/ai_code_converter/models/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Model-related modules.""" diff --git a/week4/community-contributions/ai_code_converter/src/ai_code_converter/models/ai_streaming.py b/week4/community-contributions/ai_code_converter/src/ai_code_converter/models/ai_streaming.py deleted file mode 100644 index 8df5006..0000000 --- a/week4/community-contributions/ai_code_converter/src/ai_code_converter/models/ai_streaming.py +++ /dev/null @@ -1,301 +0,0 @@ -"""Module for handling AI model streaming responses.""" - -import logging -import time -from typing import Generator, Any, Callable, TypeVar, cast -from anthropic import Anthropic, AnthropicError, APIError, RateLimitError, AuthenticationError -import google.generativeai as genai -from openai import OpenAI - -from src.ai_code_converter.config import ( - OPENAI_MODEL, - CLAUDE_MODEL, - DEEPSEEK_MODEL, - GEMINI_MODEL, - GROQ_MODEL -) - -logger = logging.getLogger(__name__) - -# Type variable for generic retry function -T = TypeVar('T') - -def retry_with_exponential_backoff( - max_retries: int = 5, - initial_delay: float = 1.0, - exponential_base: float = 2.0, - jitter: bool = True, - retryable_exceptions: tuple = (Exception,), - retryable_return: Callable[[Any], bool] = None, -) -> Callable: - """Retry decorator with exponential backoff - - Args: - max_retries: Maximum number of retries - initial_delay: Initial delay in seconds - exponential_base: Base for exponential calculation - jitter: Add random jitter to delay - retryable_exceptions: Exceptions that trigger retry - retryable_return: Function that takes the return value and returns - True if it should be retried - - Returns: - Decorator function - """ - def decorator(func: Callable) -> Callable: - def wrapper(*args: Any, **kwargs: Any) -> Any: - # Initialize variables - num_retries = 0 - delay = initial_delay - - # Loop until max retries reached - while True: - try: - response = func(*args, **kwargs) - - # Check if we should retry based on response value - if retryable_return and num_retries < max_retries: - try: - if retryable_return(response): - num_retries += 1 - logger.warning( - f"Retrying {func.__name__} due to response condition " - f"(attempt {num_retries} of {max_retries})" - ) - time.sleep(delay) - delay = calculate_next_delay(delay, exponential_base, jitter) - continue - except Exception as e: - # If retryable_return itself fails, log and continue - logger.warning(f"Error in retry condition: {e}") - - return response - - except retryable_exceptions as e: - # Check if we've exceeded max retries - if num_retries >= max_retries: - logger.error( - f"Maximum retries ({max_retries}) exceeded for {func.__name__}: {str(e)}" - ) - raise - - # Handle specific error types with custom messages - if hasattr(e, 'error') and isinstance(e.error, dict) and e.error.get('type') == 'overloaded_error': - logger.warning( - f"Service overloaded, retrying {func.__name__} " - f"(attempt {num_retries + 1} of {max_retries})" - ) - else: - logger.warning( - f"Exception in {func.__name__}, retrying " - f"(attempt {num_retries + 1} of {max_retries}): {str(e)}" - ) - - # Increment retry count and delay - num_retries += 1 - time.sleep(delay) - delay = calculate_next_delay(delay, exponential_base, jitter) - - return cast(Callable, wrapper) - return decorator - - -def calculate_next_delay(current_delay: float, exponential_base: float, jitter: bool) -> float: - """Calculate the next delay time using exponential backoff and optional jitter""" - import random - delay = current_delay * exponential_base - # Add jitter - random value between -0.2 and +0.2 of the delay - if jitter: - delay = delay * (1 + random.uniform(-0.2, 0.2)) - return delay - - -class AIModelStreamer: - """Class for handling streaming responses from various AI models.""" - - def __init__(self, openai_client: OpenAI, claude_client: Anthropic, - deepseek_client: OpenAI, groq_client: OpenAI, gemini_model: genai.GenerativeModel): - """Initialize with AI model clients.""" - self.openai = openai_client - self.claude = claude_client - self.deepseek = deepseek_client - self.groq = groq_client - self.gemini = gemini_model - self.max_retries = 5 - - @retry_with_exponential_backoff( - max_retries=5, - initial_delay=1.0, - retryable_exceptions=(Exception,) - ) - def _call_gpt_api(self, prompt: str): - """Call the GPT API with retry logic""" - messages = [{"role": "user", "content": prompt}] - return self.openai.chat.completions.create( - model=OPENAI_MODEL, - messages=messages, - stream=True - ) - - def stream_gpt(self, prompt: str) -> Generator[str, None, None]: - """Stream responses from GPT model.""" - try: - stream = self._call_gpt_api(prompt) - - response = "" - for chunk in stream: - fragment = chunk.choices[0].delta.content or "" - response += fragment - yield response - - except Exception as e: - logger.error(f"GPT API error: {str(e)}", exc_info=True) - yield f"Error with GPT API: {str(e)}" - - @retry_with_exponential_backoff( - max_retries=5, - initial_delay=1.0, - exponential_base=2.0, - retryable_exceptions=(Exception,) - ) - def _call_claude_api(self, prompt: str): - """Call the Claude API with retry logic""" - return self.claude.messages.stream( - model=CLAUDE_MODEL, - max_tokens=4000, - messages=[{"role": "user", "content": prompt}] - ) - - def stream_claude(self, prompt: str) -> Generator[str, None, None]: - """Stream responses from Claude model with retry for overloaded errors.""" - retry_count = 0 - max_retries = self.max_retries - delay = 1.0 - - while True: - try: - result = self._call_claude_api(prompt) - - response = "" - with result as stream: - for text in stream.text_stream: - response += text - yield response - - # If we get here, we succeeded, so break out of retry loop - break - - except (AnthropicError, APIError) as e: - # Special handling for overloaded_error - if hasattr(e, 'error') and getattr(e, 'error', {}).get('type') == 'overloaded_error': - if retry_count < max_retries: - retry_count += 1 - wait_time = delay * (2 ** (retry_count - 1)) # Exponential backoff - logger.warning(f"Claude API overloaded, retrying in {wait_time:.2f}s (attempt {retry_count}/{max_retries})") - time.sleep(wait_time) - continue - - # If reached maximum retries or not an overloaded error - logger.error(f"Claude API error: {str(e)}", exc_info=True) - yield f"Error with Claude API: {str(e)}" - break - - except Exception as e: - logger.error(f"Claude API error: {str(e)}", exc_info=True) - yield f"Error with Claude API: {str(e)}" - break - - @retry_with_exponential_backoff( - max_retries=5, - initial_delay=1.0, - retryable_exceptions=(Exception,) - ) - def _call_deepseek_api(self, prompt: str): - """Call the DeepSeek API with retry logic""" - messages = [{"role": "user", "content": prompt}] - return self.deepseek.chat.completions.create( - model=DEEPSEEK_MODEL, - messages=messages, - stream=True, - temperature=0.7, - max_tokens=4000 - ) - - def stream_deepseek(self, prompt: str) -> Generator[str, None, None]: - """Stream responses from DeepSeek model.""" - try: - stream = self._call_deepseek_api(prompt) - - reply = "" - for chunk in stream: - fragment = chunk.choices[0].delta.content or "" - reply += fragment - yield reply - - except Exception as e: - logger.error(f"DeepSeek API error: {str(e)}", exc_info=True) - yield f"Error with DeepSeek API: {str(e)}" - - @retry_with_exponential_backoff( - max_retries=5, - initial_delay=1.0, - retryable_exceptions=(Exception,) - ) - def _call_groq_api(self, prompt: str): - """Call the GROQ API with retry logic""" - messages = [{"role": "user", "content": prompt}] - return self.groq.chat.completions.create( - model=GROQ_MODEL, - messages=messages, - stream=True, - temperature=0.7, - max_tokens=4000 - ) - - def stream_groq(self, prompt: str) -> Generator[str, None, None]: - """Stream responses from GROQ model.""" - try: - stream = self._call_groq_api(prompt) - - reply = "" - for chunk in stream: - fragment = chunk.choices[0].delta.content or "" - reply += fragment - yield reply - - except Exception as e: - logger.error(f"GROQ API error: {str(e)}", exc_info=True) - yield f"Error with GROQ API: {str(e)}" - - @retry_with_exponential_backoff( - max_retries=5, - initial_delay=1.0, - retryable_exceptions=(Exception,) - ) - def _call_gemini_api(self, prompt: str): - """Call the Gemini API with retry logic""" - return self.gemini.generate_content( - prompt, - generation_config={ - "temperature": 0.7, - "top_p": 1, - "top_k": 1, - "max_output_tokens": 4000, - }, - stream=True - ) - - def stream_gemini(self, prompt: str) -> Generator[str, None, None]: - """Stream responses from Gemini model.""" - try: - response = self._call_gemini_api(prompt) - - reply = "" - for chunk in response: - if chunk.text: - reply += chunk.text - yield reply - - except Exception as e: - logger.error(f"Gemini API error: {str(e)}", exc_info=True) - yield f"Error with Gemini API: {str(e)}" \ No newline at end of file diff --git a/week4/community-contributions/ai_code_converter/src/ai_code_converter/project_structure.txt b/week4/community-contributions/ai_code_converter/src/ai_code_converter/project_structure.txt deleted file mode 100644 index b4bbafa..0000000 --- a/week4/community-contributions/ai_code_converter/src/ai_code_converter/project_structure.txt +++ /dev/null @@ -1,48 +0,0 @@ -ai_converter_v2/ -├── README.md # Project documentation -├── requirements.txt # Python dependencies -├── setup.py # Package installation configuration -├── run.py # Application entry point -├── Makefile # Build and development commands -├── .env.example # Example environment variables -├── .gitignore # Git ignore patterns -└── src/ - └── ai_code_converter/ - ├── __init__.py - ├── main.py # Main application initialization - ├── app.py # Gradio interface and core app logic - ├── config.py # Configuration settings - ├── template.j2 # Code conversion prompt template - ├── core/ # Core functionality modules - │ ├── __init__.py - │ ├── code_execution.py # Code execution logic - │ ├── language_detection.py # Language detection - │ └── file_utils.py # File operations - ├── models/ # AI model interfaces - │ ├── __init__.py - │ └── ai_streaming.py # Streaming implementations - └── utils/ # Utility functions - ├── __init__.py - └── logger.py # Logging configuration - -Key Components: -- core/: Core functionality modules - - code_execution.py: Handles code execution in different languages - - language_detection.py: Detects and validates programming languages - - file_utils.py: Handles file operations and downloads - -- models/: AI model implementations - - ai_streaming.py: Streaming implementations for different AI models - -- utils/: Utility modules - - logger.py: Logging configuration and setup - -- Configuration: - - config.py: Central configuration settings - - template.j2: Prompt template for code conversion - - .env: Environment variables (API keys) - -- Entry Points: - - run.py: Main entry point - - main.py: Application initialization - - app.py: Gradio interface implementation \ No newline at end of file diff --git a/week4/community-contributions/ai_code_converter/src/ai_code_converter/template.j2 b/week4/community-contributions/ai_code_converter/src/ai_code_converter/template.j2 deleted file mode 100644 index 783fc8a..0000000 --- a/week4/community-contributions/ai_code_converter/src/ai_code_converter/template.j2 +++ /dev/null @@ -1,287 +0,0 @@ ---- -description: A template for converting code between different programming languages -author: AI Conversion Assistant ---- - -You're an AI assistant specialized in code conversion with expertise in: -- Language-specific idioms, patterns and best practices -- Performance optimization techniques for each target language -- Memory management paradigms across different languages -- Cross-platform compatibility considerations -- Equivalent library and framework implementations -- Data structure transformations between languages -- Type system differences and appropriate conversions -- Error handling strategies for each language ecosystem -- Concurrency and parallelism models -- Standard toolchains and build systems - -# CONTEXT -You will be provided with code written in **{{ source_language }}**. -Your task is to convert it to **{{ target_language }}**, ensuring that the output produces the same functionality and is optimized for performance. - -# INSTRUCTIONS -- Respond only with **{{ target_language }}** code. -{% if doc_enabled %}{% else %}- Provide minimal comments, focusing only on critical parts of the code.{% endif %} -- Use base libraries and packages where possible. -- Ensure that data types and syntax are correctly adapted between languages. -- Avoid explanations outside of comments in the code. -- Maintain identical behavior for functions like random number generation to ensure consistent output. - -{% if doc_enabled %} -# DOCUMENTATION INSTRUCTIONS -- Include comprehensive documentation in your response following the style specified below. -- Document the purpose and functionality of classes, functions, and important code blocks. -- Explain important parameters, return values, and exceptions. -- Make documentation clear and helpful for new developers to understand the code. -{% endif %} - -{% if target_language == 'Python' %} -# ADDITIONAL INSTRUCTIONS FOR PYTHON -- Use Black's default settings -- Normalize code structure -- Produce clean, consistent code formatting -- Use Pythonic idioms and list comprehensions where appropriate. -- Ensure proper exception handling and type safety. -- Follow PEP 8 styling conventions. -- Correct Python indentation (4 spaces) -- Proper syntax for Python -- No syntax errors -- Complete, runnable code -{% if doc_style == 'google' %} -- Use Google-style docstrings (summary line, blank line, Args:, Returns:, Raises:) -{% elif doc_style == 'numpy' %} -- Use NumPy-style docstrings (summary line, Parameters, Returns, Raises sections) -{% else %} -- Follow PEP 257 for docstrings (summary line, blank line, detailed description) -{% endif %} -- Ensure all output is done through print() statements -- For functions that return values, print the return value -- Use proper Python indentation -- Include necessary imports at the top -- Handle exceptions appropriately - -{% elif target_language == 'Julia' %} -# ADDITIONAL INSTRUCTIONS FOR JULIA -- Use Julia's multiple dispatch where beneficial. -- Ensure correct handling of data types and performance optimizations. -- Use built-in functions and packages like `Base` and `LinearAlgebra` if applicable. -{% if doc_style == 'standard' %} -- Use standard Julia docstrings with triple quotes """Summary\n\nDetailed description""" -{% elif doc_style == 'docsystem' %} -- Use Documenter.jl style docstrings with """Function description\n\n# Arguments\n- `arg1`: description\n# Returns\n- description""" -{% else %} -- Include simple docstrings with description of functionality -{% endif %} - -{% elif target_language == 'JavaScript' %} -# ADDITIONAL INSTRUCTIONS FOR JAVASCRIPT -- Use ES6+ features where applicable. -- Ensure asynchronous functions use `async/await` correctly. -- Follow best practices for variable scoping (`const`, `let`, `var`). -{% if doc_style == 'jsdoc' %} -- Use JSDoc comments with @param, @returns, and other appropriate tags -{% elif doc_style == 'tsdoc' %} -- Use TSDoc style comments compatible with TypeScript -{% else %} -- Use simple block comments with function descriptions -{% endif %} - -{% elif target_language == 'Go' %} -# ADDITIONAL INSTRUCTIONS FOR GO -- Use Go idioms such as goroutines for concurrency when needed. -- Ensure proper handling of errors using Go's `error` type. -- Optimize for performance using Go's built-in profiling tools. -{% if doc_style == 'godoc' %} -- Follow standard GoDoc comment style (starting with function name) -{% else %} -- Use clear comments for package-level and exported declarations -{% endif %} - -{% elif target_language == 'Java' %} -# ADDITIONAL INSTRUCTIONS FOR JAVA -- Use appropriate class and method structures. -- Ensure proper handling of exceptions using `try-catch-finally`. -- Optimize performance using multithreading where applicable. -{% if doc_style == 'javadoc' %} -- Use JavaDoc with @param, @return, @throws and other appropriate tags -{% else %} -- Use simple block comments with method descriptions -{% endif %} - -{% elif target_language == 'C++' %} -# ADDITIONAL INSTRUCTIONS FOR C++ -- Use `#include` directives for necessary libraries. -- Pay attention to integer overflow issues. -- Optimize for execution speed where possible using memory management techniques. -{% if doc_style == 'doxygen' %} -- Use Doxygen-style comments with @brief, @param, @return tags -{% else %} -- Use block comments with function descriptions -{% endif %} - -{% elif target_language == 'Ruby' %} -# ADDITIONAL INSTRUCTIONS FOR RUBY -- Use Ruby idioms like blocks, procs, and lambdas where appropriate. -- Follow Ruby style guidelines (2 space indentation, snake_case for methods). -- Use Ruby's built-in enumerable methods for collection operations. -{% if doc_style == 'yard' %} -- Use YARD documentation style with @param, @return, and other appropriate tags -{% elif doc_style == 'rdoc' %} -- Use RDoc documentation style with simple comment blocks -{% else %} -- Use simple comment blocks to document methods and classes -{% endif %} - -{% elif target_language == 'Swift' %} -# ADDITIONAL INSTRUCTIONS FOR SWIFT -- Use Swift's strong typing system and optional handling. -- Implement proper error handling with do-catch blocks. -- Follow Swift naming conventions (camelCase for variables, methods). -{% if doc_style == 'markdown' %} -- Use Swift's markdown documentation style with parameters and returns sections -{% elif doc_style == 'headerDoc' %} -- Use HeaderDoc style documentation -{% else %} -- Use triple-slash /// comments for documentation -{% endif %} - -{% elif target_language == 'Rust' %} -# ADDITIONAL INSTRUCTIONS FOR RUST -- Ensure memory safety with proper ownership, borrowing, and lifetimes. -- Use pattern matching where appropriate. -- Handle errors with Result and Option types. -{% if doc_style == 'rustdoc' %} -- Use standard Rust documentation with triple-slash /// comments -{% else %} -- Include documentation comments that explain functionality -{% endif %} - -{% elif target_language == 'C#' %} -# ADDITIONAL INSTRUCTIONS FOR C# -- Use appropriate .NET libraries and LINQ where beneficial. -- Implement proper exception handling. -- Use C# properties instead of getter/setter methods where appropriate. -{% if doc_style == 'xml' %} -- Use XML documentation comments with , , tags -{% else %} -- Use simple comments to document classes and methods -{% endif %} - -{% elif target_language == 'TypeScript' %} -# ADDITIONAL INSTRUCTIONS FOR TYPESCRIPT -- Utilize TypeScript's static typing system. -- Define appropriate interfaces and types. -- Use ES6+ features and TypeScript-specific patterns. -{% if doc_style == 'tsdoc' %} -- Use TSDoc with @param, @returns, and other appropriate tags -{% else %} -- Include simple comments explaining functionality -{% endif %} - -{% elif target_language == 'R' %} -# ADDITIONAL INSTRUCTIONS FOR R -- Use R's vectorized operations where possible for performance. -- Leverage tidyverse packages when appropriate for data manipulation. -- Follow R style guidelines (snake_case for variables, descriptive names). -{% if doc_style == 'roxygen2' %} -- Use roxygen2 style comments with @param, @return, @examples tags -{% elif doc_style == 'r-native' %} -- Use R's native documentation system with simple comments -{% else %} -- Include comments explaining functionality at the beginning of functions -{% endif %} -- Ensure compatibility with R's functional programming paradigm -- Implement proper error handling with tryCatch() when necessary -- Make appropriate use of R's specialized data structures like data.frames and lists - -{% elif target_language == 'Perl' %} -# ADDITIONAL INSTRUCTIONS FOR PERL -- Use Perl's powerful regular expression capabilities when appropriate. -- Follow Perl's style guidelines including use of sigils and variable naming. -- Leverage Perl's CPAN modules when beneficial. -{% if doc_style == 'pod' %} -- Use Plain Old Documentation (POD) style with =head1, =head2, =item tags -{% else %} -- Use block comments to document subroutines and functionality -{% endif %} -- Ensure proper handling of scalar vs. list context -- Use Perl's error handling mechanisms with eval and die/warn -- Implement appropriate memory management techniques - -{% elif target_language == 'Lua' %} -# ADDITIONAL INSTRUCTIONS FOR LUA -- Use Lua's lightweight table structure effectively. -- Follow Lua coding style (no semicolons, use of 'local' variables). -- Respect Lua's 1-based indexing for arrays and string manipulation. -{% if doc_style == 'ldoc' %} -- Use LDoc style comments with @param, @return, and other appropriate tags -{% else %} -- Use block comments to document functions and modules -{% endif %} -- Leverage Lua's coroutines for concurrent programming when appropriate -- Implement proper error handling with pcall and xpcall -- Optimize for Lua's garbage collection - -{% elif target_language == 'PHP' %} -# ADDITIONAL INSTRUCTIONS FOR PHP -- Follow PHP-FIG standards (PSR-1, PSR-12) for code style. -- Use modern PHP features (namespaces, type declarations). -- Implement appropriate error handling with try/catch blocks. -{% if doc_style == 'phpdoc' %} -- Use PHPDoc style comments with @param, @return, @throws tags -{% else %} -- Use block comments to document classes and methods -{% endif %} -- Include necessary composer dependencies -- Consider performance implications of string operations and array handling -- Apply appropriate security measures (input validation, output escaping) - -{% elif target_language == 'Kotlin' %} -# ADDITIONAL INSTRUCTIONS FOR KOTLIN -- Use Kotlin's null safety features and smart casts. -- Apply functional programming concepts with lambda expressions. -- Leverage Kotlin's extension functions and properties where appropriate. -{% if doc_style == 'kdoc' %} -- Use KDoc style comments with @param, @return, @throws tags -{% else %} -- Use block comments to document classes and functions -{% endif %} -- Take advantage of Kotlin's coroutines for asynchronous programming -- Follow Kotlin conventions for naming and structure -- Implement proper exception handling with try-catch blocks - -{% elif target_language == 'SQL' %} -# ADDITIONAL INSTRUCTIONS FOR SQL -- Optimize queries for performance with proper indexing hints. -- Use appropriate SQL dialect features based on the specified database system. -- Follow SQL style guidelines (uppercase keywords, proper indentation). -{% if doc_style == 'standard' %} -- Use standard SQL comment style with detailed descriptions -{% elif doc_style == 'database-specific' %} -- Use database-specific comment annotations (e.g., Oracle hints, MySQL comments) -{% else %} -- Include simple comments explaining query functionality -{% endif %} -- Ensure security by avoiding SQL injection vulnerabilities -- Consider execution plan optimization in complex queries -- Apply appropriate transaction handling when necessary - -{% endif %} - -# INPUT CODE: -{{ input_code }} - -# EXPECTED OUTPUT: -A fully functional **{{ target_language }}** implementation of the provided code. - -Convert this {{ source_language }} code to {{ target_language }}. -Follow these rules: -1. Maintain the same functionality -2. Keep variable names similar when possible -3. Use idiomatic {{ target_language }} patterns -4. Include necessary imports -5. Provide only the converted code without explanations - -Here's the code to convert: - -{{ input_code }} \ No newline at end of file diff --git a/week4/community-contributions/ai_code_converter/src/ai_code_converter/template.j2.old b/week4/community-contributions/ai_code_converter/src/ai_code_converter/template.j2.old deleted file mode 100644 index 30782d9..0000000 --- a/week4/community-contributions/ai_code_converter/src/ai_code_converter/template.j2.old +++ /dev/null @@ -1,78 +0,0 @@ ---- -description: A template for converting code between different programming languages -author: AI Conversion Assistant ---- - -You're an AI assistant specialized in code conversion. - -# CONTEXT -You will be provided with code written in **{{ source_language }}**. -Your task is to convert it to **{{ target_language }}**, ensuring that the output produces the same functionality and is optimized for performance. - -# INSTRUCTIONS -- Respond only with **{{ target_language }}** code. -- Provide minimal but useful comments where necessary. -- Ensure that data types and syntax are correctly adapted between languages. -- Avoid explanations outside of comments in the code. -- Maintain identical behavior for functions like random number generation to ensure consistent output. - -{% if target_language == 'Python' %} -# ADDITIONAL INSTRUCTIONS FOR PYTHON -- Use Pythonic idioms and list comprehensions where appropriate. -- Ensure proper exception handling and type safety. -- Follow PEP 8 styling conventions. -- Ensure all output is done through print() statements -- For functions that return values, print the return value -- Use proper Python indentation -- Include necessary imports at the top -- Handle exceptions appropriately - -{% elif target_language == 'Julia' %} -# ADDITIONAL INSTRUCTIONS FOR JULIA -- Use Julia's multiple dispatch where beneficial. -- Ensure correct handling of data types and performance optimizations. -- Use built-in functions and packages like `Base` and `LinearAlgebra` if applicable. - -{% elif target_language == 'JavaScript' %} -# ADDITIONAL INSTRUCTIONS FOR JAVASCRIPT -- Use ES6+ features where applicable. -- Ensure asynchronous functions use `async/await` correctly. -- Follow best practices for variable scoping (`const`, `let`, `var`). - -{% elif target_language == 'Go' %} -# ADDITIONAL INSTRUCTIONS FOR GO -- Use Go idioms such as goroutines for concurrency when needed. -- Ensure proper handling of errors using Go's `error` type. -- Optimize for performance using Go's built-in profiling tools. - -{% elif target_language == 'Java' %} -# ADDITIONAL INSTRUCTIONS FOR JAVA -- Use appropriate class and method structures. -- Ensure proper handling of exceptions using `try-catch-finally`. -- Optimize performance using multithreading where applicable. - -{% elif target_language == 'C++' %} -# ADDITIONAL INSTRUCTIONS FOR C++ -- Use `#include` directives for necessary libraries. -- Pay attention to integer overflow issues. -- Optimize for execution speed where possible using memory management techniques. - -{% endif %} - -# INPUT CODE: -{{ input_code }} - -# EXPECTED OUTPUT: -A fully functional **{{ target_language }}** implementation of the provided code. - -Convert this {{ source_language }} code to {{ target_language }}. -Follow these rules: -1. Maintain the same functionality -2. Keep variable names similar when possible -3. Use idiomatic {{ target_language }} patterns -4. Include necessary imports -5. Provide only the converted code without explanations - -Here's the code to convert: - -{{ input_code }} \ No newline at end of file diff --git a/week4/community-contributions/ai_code_converter/src/ai_code_converter/utils/__init__.py b/week4/community-contributions/ai_code_converter/src/ai_code_converter/utils/__init__.py deleted file mode 100644 index 183c974..0000000 --- a/week4/community-contributions/ai_code_converter/src/ai_code_converter/utils/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Utility modules.""" diff --git a/week4/community-contributions/ai_code_converter/src/ai_code_converter/utils/logger.py b/week4/community-contributions/ai_code_converter/src/ai_code_converter/utils/logger.py deleted file mode 100644 index 951234e..0000000 --- a/week4/community-contributions/ai_code_converter/src/ai_code_converter/utils/logger.py +++ /dev/null @@ -1,132 +0,0 @@ -"""Logging configuration for the CodeXchange AI.""" - -import json -import logging -import logging.handlers -import os -import sys -import threading -import time -from datetime import datetime -from functools import wraps -from pathlib import Path -from typing import Any, Callable - -class JSONFormatter(logging.Formatter): - """Custom JSON formatter for structured logging.""" - - def format(self, record: logging.LogRecord) -> str: - """Format log record as JSON.""" - log_data = { - "timestamp": self.formatTime(record), - "level": record.levelname, - "process_id": record.process, - "thread_id": record.thread, - "thread_name": record.threadName, - "module": record.module, - "function": record.funcName, - "line": record.lineno, - "message": record.getMessage() - } - - if hasattr(record, 'duration'): - log_data['duration'] = f"{record.duration:.4f}s" - - if record.exc_info: - log_data['exception'] = self.formatException(record.exc_info) - - return json.dumps(log_data) - -def log_execution_time(logger: logging.Logger) -> Callable: - """Decorator to log function execution time.""" - def decorator(func: Callable) -> Callable: - @wraps(func) - def wrapper(*args, **kwargs): - start_time = time.time() - - # Log function entry - logger.debug( - "Function Entry | %s | Args: %s | Kwargs: %s", - func.__name__, - [str(arg) for arg in args], - {k: str(v) for k, v in kwargs.items() if not any(sensitive in k.lower() for sensitive in ('password', 'key', 'token', 'secret'))} - ) - - try: - result = func(*args, **kwargs) - duration = time.time() - start_time - - # Log function exit - logger.debug( - "Function Exit | %s | Duration: %.4fs", - func.__name__, - duration - ) - return result - - except Exception as e: - duration = time.time() - start_time - logger.error( - "Function Error | %s | Duration: %.4fs | Error: %s", - func.__name__, - duration, - str(e), - exc_info=True - ) - raise - - return wrapper - return decorator - -def setup_logger(name: str) -> logging.Logger: - """Set up a logger with both file and console output.""" - # Create logger - logger = logging.getLogger(name) - logger.setLevel(logging.DEBUG) - - # Clear any existing handlers - logger.handlers = [] - - # Create logs directory - log_dir = Path("logs") - log_dir.mkdir(exist_ok=True) - - # Create formatters - console_formatter = logging.Formatter( - '[%(asctime)s] %(levelname)-8s [%(name)s:%(funcName)s:%(lineno)d] [PID:%(process)d-%(threadName)s] - %(message)s', - datefmt='%Y-%m-%d %H:%M:%S' - ) - - json_formatter = JSONFormatter() - - # Console handler with color coding - console_handler = logging.StreamHandler(sys.stdout) - console_handler.setLevel(logging.INFO) - console_handler.setFormatter(console_formatter) - - # File handler with daily rotation and JSON formatting - log_file = log_dir / f"ai_converter_{datetime.now().strftime('%Y-%m-%d')}.json" - file_handler = logging.handlers.RotatingFileHandler( - log_file, - maxBytes=10*1024*1024, # 10MB - backupCount=30, # Keep 30 backup files - encoding='utf-8' - ) - file_handler.setLevel(logging.DEBUG) - file_handler.setFormatter(json_formatter) - - # Add handlers - logger.addHandler(console_handler) - logger.addHandler(file_handler) - - # Log initialization - logger.info("="*50) - logger.info("Logger Initialized") - logger.info(f"Logger Name: {name}") - logger.info(f"Log File: {log_file}") - logger.info(f"Process ID: {os.getpid()}") - logger.info(f"Thread ID: {threading.get_ident()}") - logger.info(f"Thread Name: {threading.current_thread().name}") - logger.info("="*50) - - return logger \ No newline at end of file diff --git a/week4/community-contributions/ai_code_converter/tests/test_basic.py b/week4/community-contributions/ai_code_converter/tests/test_basic.py deleted file mode 100644 index ec21a70..0000000 --- a/week4/community-contributions/ai_code_converter/tests/test_basic.py +++ /dev/null @@ -1,29 +0,0 @@ -"""Basic tests for CodeXchange AI application.""" - -import pytest -import os -import sys - -# Add project root to path -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) - -from src.ai_code_converter.config import SUPPORTED_LANGUAGES, DOCUMENT_STYLES - - -def test_supported_languages(): - """Test that supported languages configuration is valid.""" - assert isinstance(SUPPORTED_LANGUAGES, list) - assert len(SUPPORTED_LANGUAGES) > 0 - assert "Python" in SUPPORTED_LANGUAGES - - -def test_document_styles(): - """Test that document styles configuration is valid.""" - assert isinstance(DOCUMENT_STYLES, dict) - assert len(DOCUMENT_STYLES) > 0 - - # Check that each language has at least one document style - for language in SUPPORTED_LANGUAGES: - assert language in DOCUMENT_STYLES, f"{language} missing from document styles" - assert isinstance(DOCUMENT_STYLES[language], list) - assert len(DOCUMENT_STYLES[language]) > 0 diff --git a/week4/community-contributions/ai_code_converter/tests/test_csharp_detection.py b/week4/community-contributions/ai_code_converter/tests/test_csharp_detection.py deleted file mode 100644 index ddcb295..0000000 --- a/week4/community-contributions/ai_code_converter/tests/test_csharp_detection.py +++ /dev/null @@ -1,101 +0,0 @@ -"""Test module for C# language detection.""" - -import sys -import os - -# Add the src directory to the Python path -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../src'))) - -from ai_code_converter.core.language_detection import LanguageDetector - - -def test_csharp_detection(): - """Test the C# language detection functionality.""" - detector = LanguageDetector() - - # Sample C# code - csharp_code = """ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace MyApp -{ - // A simple C# class - public class Person - { - // Properties - public string Name { get; set; } - public int Age { get; private set; } - - // Constructor - public Person(string name, int age) - { - Name = name; - Age = age; - } - - // Method - public string Greet() - { - return $"Hello, my name is {Name} and I am {Age} years old."; - } - - // Method with out parameter - public bool TryParse(string input, out int result) - { - return int.TryParse(input, out result); - } - } - - // Interface - public interface IRepository where T : class - { - Task GetByIdAsync(int id); - Task> GetAllAsync(); - Task AddAsync(T entity); - } - - // Async method - public class DataProcessor - { - public async Task ProcessDataAsync() - { - await Task.Delay(1000); - Console.WriteLine("Data processed!"); - } - } - - class Program - { - static void Main(string[] args) - { - // Variable declaration with var - var person = new Person("John", 30); - Console.WriteLine(person.Greet()); - - // LINQ query - var numbers = new List { 1, 2, 3, 4, 5 }; - var evenNumbers = numbers.Where(n => n % 2 == 0).ToList(); - - // String interpolation - string message = $"Found {evenNumbers.Count} even numbers."; - Console.WriteLine(message); - } - } -} -""" - - # Test the detection - assert detector.detect_csharp(csharp_code) == True - assert detector.detect_language(csharp_code) == "C#" - - # Check validation - valid, _ = detector.validate_language(csharp_code, "C#") - assert valid == True - - -if __name__ == "__main__": - test_csharp_detection() - print("All C# detection tests passed!") diff --git a/week4/community-contributions/ai_code_converter/tests/test_kotlin_detection.py b/week4/community-contributions/ai_code_converter/tests/test_kotlin_detection.py deleted file mode 100644 index 1f8cf58..0000000 --- a/week4/community-contributions/ai_code_converter/tests/test_kotlin_detection.py +++ /dev/null @@ -1,189 +0,0 @@ -"""Test module for Kotlin language detection.""" - -import sys -import os - -# Add the src directory to the Python path -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../src'))) - -from ai_code_converter.core.language_detection import LanguageDetector - - -def test_kotlin_detection(): - """Test the Kotlin language detection functionality.""" - detector = LanguageDetector() - - # Sample Kotlin code - kotlin_code = """ -package com.example.myapp - -import android.os.Bundle -import android.widget.Button -import android.widget.TextView -import androidx.appcompat.app.AppCompatActivity -import kotlinx.coroutines.* -import kotlinx.coroutines.flow.* -import java.util.* - -/** - * Main Activity for the application - */ -class MainActivity : AppCompatActivity() { - - // Properties - private lateinit var textView: TextView - private lateinit var button: Button - private val viewModel: MainViewModel by viewModels() - private val job = Job() - private val coroutineScope = CoroutineScope(Dispatchers.Main + job) - - // Immutable property - val API_KEY: String = "abc123" - - // Computed property - val isActive: Boolean - get() = viewModel.isActive && !isFinishing - - // Data class - data class User( - val id: Int, - val name: String, - val email: String, - var isActive: Boolean = true - ) - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) - - // View binding - textView = findViewById(R.id.textView) - button = findViewById(R.id.button) - - // Click listener - button.setOnClickListener { - fetchData() - } - - // Observe live data - viewModel.userData.observe(this) { user -> - updateUI(user) - } - - // Extension function call - "Hello, Kotlin!".printDebug() - - // Using when expression - val result = when(viewModel.status) { - Status.LOADING -> "Loading..." - Status.SUCCESS -> "Success!" - Status.ERROR -> "Error!" - else -> "Unknown" - } - - textView.text = result - } - - // Suspend function - private suspend fun fetchUserData(): User { - return withContext(Dispatchers.IO) { - // Simulate network delay - delay(1000) - User(1, "John Doe", "john@example.com") - } - } - - // Coroutine usage - private fun fetchData() { - coroutineScope.launch { - try { - textView.text = "Loading..." - val user = fetchUserData() - updateUI(user) - } catch (e: Exception) { - textView.text = "Error: ${e.message}" - } - } - } - - // Normal function - private fun updateUI(user: User) { - textView.text = "Welcome, ${user.name}!" - - // Smart cast - val info: Any = user.email - if (info is String) { - // No need to cast, Kotlin knows it's a String - textView.text = info.toUpperCase() - } - - // Collection operations - val numbers = listOf(1, 2, 3, 4, 5) - val sum = numbers.filter { it % 2 == 0 }.sum() - - // String template - val message = "Sum of even numbers: $sum" - println(message) - } - - // Higher-order function - private inline fun performOperation( - value: Int, - operation: (Int) -> Int - ): Int { - return operation(value) - } - - // Companion object - companion object { - const val TAG = "MainActivity" - - fun newInstance(): MainActivity { - return MainActivity() - } - } - - // Enum class - enum class Status { - LOADING, - SUCCESS, - ERROR - } - - // Interface definition - interface OnDataLoadListener { - fun onDataLoaded(data: Any) - fun onError(message: String) - } - - // Object declaration (singleton) - object Logger { - fun log(message: String) { - println("[$TAG] $message") - } - } - - // Extension function - fun String.printDebug() { - println("Debug: $this") - } - - override fun onDestroy() { - super.onDestroy() - job.cancel() // Cancel coroutines when activity is destroyed - } -} -""" - - # Test the detection - assert detector.detect_kotlin(kotlin_code) == True - assert detector.detect_language(kotlin_code) == "Kotlin" - - # Check validation - valid, _ = detector.validate_language(kotlin_code, "Kotlin") - assert valid == True - - -if __name__ == "__main__": - test_kotlin_detection() - print("All Kotlin detection tests passed!") diff --git a/week4/community-contributions/ai_code_converter/tests/test_lua_detection.py b/week4/community-contributions/ai_code_converter/tests/test_lua_detection.py deleted file mode 100644 index 89c6cbf..0000000 --- a/week4/community-contributions/ai_code_converter/tests/test_lua_detection.py +++ /dev/null @@ -1,164 +0,0 @@ -"""Test module for Lua language detection.""" - -import sys -import os - -# Add the src directory to the Python path -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../src'))) - -from ai_code_converter.core.language_detection import LanguageDetector - - -def test_lua_detection(): - """Test the Lua language detection functionality.""" - detector = LanguageDetector() - - # Sample Lua code - lua_code = """ --- Simple Lua script -local function factorial(n) - if n == 0 then - return 1 - else - return n * factorial(n - 1) - end -end - --- Variables -local name = "John" -local age = 30 -local is_active = true -local value = nil - --- Table creation -local person = { - name = "John", - age = 30, - email = "john@example.com", - greet = function(self) - return "Hello, " .. self.name - end -} - --- Accessing table properties -print(person.name) -print(person["age"]) -print(person:greet()) - --- Metatables -local mt = { - __add = function(a, b) - return { value = a.value + b.value } - end -} - -local obj1 = { value = 10 } -local obj2 = { value = 20 } -setmetatable(obj1, mt) -local result = obj1 + obj2 -print(result.value) -- 30 - --- Control structures -for i = 1, 10 do - print(i) -end - -local fruits = {"apple", "banana", "orange"} -for i, fruit in ipairs(fruits) do - print(i, fruit) -end - -for key, value in pairs(person) do - if type(value) ~= "function" then - print(key, value) - end -end - -local count = 1 -while count <= 5 do - print(count) - count = count + 1 -end - --- Using modules -local math = require("math") -print(math.floor(3.14)) -print(math.random()) - --- String operations -local message = "Hello, " .. name .. "!" -print(message) -print(string.upper(message)) -print(string.sub(message, 1, 5)) -print(string.find(message, "Hello")) - --- Multiple return values -local function get_person() - return "John", 30, true -end - -local name, age, active = get_person() -print(name, age, active) - --- Closures -local function counter() - local count = 0 - return function() - count = count + 1 - return count - end -end - -local c1 = counter() -print(c1()) -- 1 -print(c1()) -- 2 - --- Error handling -local status, err = pcall(function() - error("Something went wrong") -end) - -if not status then - print("Error:", err) -end - --- Coroutines -local co = coroutine.create(function() - for i = 1, 3 do - print("Coroutine", i) - coroutine.yield() - end -end) - -coroutine.resume(co) -- Coroutine 1 -coroutine.resume(co) -- Coroutine 2 -coroutine.resume(co) -- Coroutine 3 - --- Multi-line strings -local multi_line = [[ -This is a multi-line -string in Lua. -It can contain "quotes" without escaping. -]] -print(multi_line) - --- Bit operations (Lua 5.3+) -local a = 10 -- 1010 in binary -local b = 6 -- 0110 in binary -print(a & b) -- AND: 0010 = 2 -print(a | b) -- OR: 1110 = 14 -print(a ~ b) -- XOR: 1100 = 12 -""" - - # Test the detection - assert detector.detect_lua(lua_code) == True - assert detector.detect_language(lua_code) == "Lua" - - # Check validation - valid, _ = detector.validate_language(lua_code, "Lua") - assert valid == True - - -if __name__ == "__main__": - test_lua_detection() - print("All Lua detection tests passed!") diff --git a/week4/community-contributions/ai_code_converter/tests/test_perl_detection.py b/week4/community-contributions/ai_code_converter/tests/test_perl_detection.py deleted file mode 100644 index b1340f5..0000000 --- a/week4/community-contributions/ai_code_converter/tests/test_perl_detection.py +++ /dev/null @@ -1,174 +0,0 @@ -"""Test module for Perl language detection.""" - -import sys -import os - -# Add the src directory to the Python path -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../src'))) - -from ai_code_converter.core.language_detection import LanguageDetector - - -def test_perl_detection(): - """Test the Perl language detection functionality.""" - detector = LanguageDetector() - - # Sample Perl code - perl_code = """ -#!/usr/bin/perl -use strict; -use warnings; -use Data::Dumper; -use File::Basename; -use Getopt::Long; - -# Scalar variable -my $name = "John Doe"; -my $age = 30; -my $pi = 3.14159; - -# Array variables -my @fruits = ("apple", "banana", "orange"); -my @numbers = (1..10); - -# Hash variables -my %user = ( - "name" => "John", - "age" => 30, - "email" => "john@example.com" -); - -# Print statements -print "Hello, $name!\\n"; -print "Your age is $age\\n"; - -# Accessing array elements -print "First fruit: $fruits[0]\\n"; -print "Last fruit: $fruits[-1]\\n"; - -# Accessing hash elements -print "User name: $user{name}\\n"; -print "User email: $user{email}\\n"; - -# Subroutine definition -sub greet { - my ($person) = @_; - print "Hello, $person!\\n"; - return "Greeting sent"; -} - -# Call the subroutine -my $result = greet($name); -print "Result: $result\\n"; - -# Control structures -if ($age >= 18) { - print "You are an adult.\\n"; -} else { - print "You are a minor.\\n"; -} - -# Unless example -unless ($age < 18) { - print "Not a minor.\\n"; -} - -# For loop -for my $i (0..$#fruits) { - print "Fruit $i: $fruits[$i]\\n"; -} - -# Foreach loop -foreach my $fruit (@fruits) { - print "I like $fruit\\n"; -} - -# While loop -my $counter = 0; -while ($counter < 5) { - print "Counter: $counter\\n"; - $counter++; -} - -# Regular expressions -my $text = "The quick brown fox"; -if ($text =~ m/quick/) { - print "Match found!\\n"; -} - -# Substitution -my $modified = $text; -$modified =~ s/quick/slow/; -print "Modified text: $modified\\n"; - -# Special variables -print "Script name: $0\\n"; -print "Command line arguments: @ARGV\\n"; -print "Current line: $. \\n"; - -# File operations -open(my $fh, '>', 'output.txt') or die "Cannot open file: $!"; -print $fh "Hello, Perl!\\n"; -close $fh; - -# Reading from standard input -print "Enter your name: "; -my $input = ; -chomp($input); # Remove newline -print "Hello, $input!\\n"; - -# References -my $array_ref = \\@fruits; -print "First element via ref: ${$array_ref}[0]\\n"; - -my $hash_ref = \\%user; -print "Name via ref: ${$hash_ref}{name}\\n"; - -# Using references for functions -sub process_data { - my ($data_ref) = @_; - foreach my $key (keys %{$data_ref}) { - print "Key: $key, Value: ${$data_ref}{$key}\\n"; - } -} - -process_data(\\%user); - -# Object-oriented style -package Person; - -sub new { - my ($class, $name, $age) = @_; - return bless { name => $name, age => $age }, $class; -} - -sub get_name { - my ($self) = @_; - return $self->{name}; -} - -sub get_age { - my ($self) = @_; - return $self->{age}; -} - -package main; -my $person = Person->new("Bob", 25); -print "Person name: " . $person->get_name() . "\\n"; -print "Person age: " . $person->get_age() . "\\n"; - -exit 0; -""" - - # Test the detection - assert detector.detect_perl(perl_code) == True - assert detector.detect_language(perl_code) == "Perl" - - # Check validation - valid, _ = detector.validate_language(perl_code, "Perl") - assert valid == True - - -if __name__ == "__main__": - test_perl_detection() - print("All Perl detection tests passed!") diff --git a/week4/community-contributions/ai_code_converter/tests/test_php_detection.py b/week4/community-contributions/ai_code_converter/tests/test_php_detection.py deleted file mode 100644 index 52182ad..0000000 --- a/week4/community-contributions/ai_code_converter/tests/test_php_detection.py +++ /dev/null @@ -1,184 +0,0 @@ -"""Test module for PHP language detection.""" - -import sys -import os - -# Add the src directory to the Python path -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../src'))) - -from ai_code_converter.core.language_detection import LanguageDetector - - -def test_php_detection(): - """Test the PHP language detection functionality.""" - detector = LanguageDetector() - - # Sample PHP code - php_code = r""" -authService = $authService; - } - - /** - * Login user - */ - public function login($request) - { - // Get request data - $email = $request->input('email'); - $password = $request->input('password'); - $remember = $request->input('remember', false); - - // Validate input - if (empty($email) || empty($password)) { - return [ - 'status' => 'error', - 'message' => 'Email and password are required' - ]; - } - - try { - // Attempt to login - $user = $this->authService->authenticate($email, $password); - - // Create session - $_SESSION['user_id'] = $user->id; - - // Set remember me cookie if requested - if ($remember) { - $token = $this->authService->createRememberToken($user->id); - setcookie('remember_token', $token, time() + 86400 * 30, '/'); - } - - return [ - 'status' => 'success', - 'user' => $user - ]; - } catch (AuthException $e) { - return [ - 'status' => 'error', - 'message' => $e->getMessage() - ]; - } - } - - /** - * Register new user - */ - public function register($request) - { - // Get request data - $name = $request->input('name'); - $email = $request->input('email'); - $password = $request->input('password'); - - // Validate input - if (empty($name) || empty($email) || empty($password)) { - return [ - 'status' => 'error', - 'message' => 'All fields are required' - ]; - } - - // Check if email already exists - $existingUser = User::findByEmail($email); - if ($existingUser) { - return [ - 'status' => 'error', - 'message' => 'Email already registered' - ]; - } - - // Create user - $user = new User(); - $user->name = $name; - $user->email = $email; - $user->password = password_hash($password, PASSWORD_DEFAULT); - $user->save(); - - // Login user - $_SESSION['user_id'] = $user->id; - - return [ - 'status' => 'success', - 'user' => $user - ]; - } - - /** - * Logout user - */ - public function logout() - { - // Clear session - unset($_SESSION['user_id']); - session_destroy(); - - // Clear remember token cookie if it exists - if (isset($_COOKIE['remember_token'])) { - setcookie('remember_token', '', time() - 3600, '/'); - } - - return [ - 'status' => 'success', - 'message' => 'Logged out successfully' - ]; - } - - /** - * Get current user profile - */ - public function profile() - { - if (!isset($_SESSION['user_id'])) { - return [ - 'status' => 'error', - 'message' => 'Not authenticated' - ]; - } - - $user = User::find($_SESSION['user_id']); - - return [ - 'status' => 'success', - 'user' => $user - ]; - } -} -?> -""" - - # Test the detection - assert detector.detect_php(php_code) == True - assert detector.detect_language(php_code) == "PHP" - - # Check validation - valid, _ = detector.validate_language(php_code, "PHP") - assert valid == True - - -if __name__ == "__main__": - test_php_detection() - print("All PHP detection tests passed!") diff --git a/week4/community-contributions/ai_code_converter/tests/test_r_detection.py b/week4/community-contributions/ai_code_converter/tests/test_r_detection.py deleted file mode 100644 index 342b266..0000000 --- a/week4/community-contributions/ai_code_converter/tests/test_r_detection.py +++ /dev/null @@ -1,118 +0,0 @@ -"""Test module for R language detection.""" - -import sys -import os - -# Add the src directory to the Python path -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../src'))) - -from ai_code_converter.core.language_detection import LanguageDetector - - -def test_r_detection(): - """Test the R language detection functionality.""" - detector = LanguageDetector() - - # Sample R code - r_code = """ -# Load necessary libraries -library(tidyverse) -library(ggplot2) -library(dplyr) - -# Create a data frame -data <- data.frame( - name = c("Alice", "Bob", "Charlie", "David"), - age = c(25, 30, 35, 40), - score = c(85, 92, 78, 95) -) - -# Basic data operations -summary(data) -str(data) -head(data) - -# Create a function -calculate_average <- function(x) { - return(mean(x, na.rm = TRUE)) -} - -# Apply the function -avg_score <- calculate_average(data$score) -print(paste("Average score:", avg_score)) - -# Data manipulation with dplyr -filtered_data <- data %>% - filter(age > 30) %>% - select(name, score) %>% - arrange(desc(score)) - -# Control structures -if (nrow(filtered_data) > 0) { - print("Found records with age > 30") -} else { - print("No records with age > 30") -} - -# For loop example -for (i in 1:nrow(data)) { - if (data$age[i] < 30) { - data$category[i] <- "Young" - } else if (data$age[i] < 40) { - data$category[i] <- "Middle" - } else { - data$category[i] <- "Senior" - } -} - -# Create vectors -ages <- c(25, 30, 35, 40) -names <- c("Alice", "Bob", "Charlie", "David") - -# Create a list -person <- list( - name = "Alice", - age = 25, - scores = c(85, 90, 92) -) - -# Access list elements -person$name -person$scores[2] - -# Create factors -gender <- factor(c("Male", "Female", "Female", "Male")) -levels(gender) - -# Basic plotting -plot(data$age, data$score, - main = "Age vs. Score", - xlab = "Age", - ylab = "Score", - col = "blue", - pch = 19) - -# ggplot visualization -ggplot(data, aes(x = age, y = score)) + - geom_point(color = "blue", size = 3) + - geom_smooth(method = "lm", se = FALSE) + - labs(title = "Age vs. Score", x = "Age", y = "Score") + - theme_minimal() - -# Statistical analysis -model <- lm(score ~ age, data = data) -summary(model) -""" - - # Test the detection - assert detector.detect_r(r_code) == True - assert detector.detect_language(r_code) == "R" - - # Check validation - valid, _ = detector.validate_language(r_code, "R") - assert valid == True - - -if __name__ == "__main__": - test_r_detection() - print("All R detection tests passed!") diff --git a/week4/community-contributions/ai_code_converter/tests/test_ruby_detection.py b/week4/community-contributions/ai_code_converter/tests/test_ruby_detection.py deleted file mode 100644 index a7ba1bc..0000000 --- a/week4/community-contributions/ai_code_converter/tests/test_ruby_detection.py +++ /dev/null @@ -1,57 +0,0 @@ -"""Test module for Ruby language detection.""" - -import sys -import os - -# Add the src directory to the Python path -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../src'))) - -from ai_code_converter.core.language_detection import LanguageDetector - - -def test_ruby_detection(): - """Test the Ruby language detection functionality.""" - detector = LanguageDetector() - - # Sample Ruby code - ruby_code = """ -# A simple Ruby class -class Person - attr_accessor :name, :age - - def initialize(name, age) - @name = name - @age = age - end - - def greet - puts "Hello, my name is #{@name} and I am #{@age} years old." - end -end - -# Create a new Person -person = Person.new("John", 30) -person.greet - -# Hash examples -old_syntax = { :name => "Ruby", :created_by => "Yukihiro Matsumoto" } -new_syntax = { name: "Ruby", created_by: "Yukihiro Matsumoto" } - -# Block with parameters -[1, 2, 3].each do |num| - puts num * 2 -end -""" - - # Test the detection - assert detector.detect_ruby(ruby_code) == True - assert detector.detect_language(ruby_code) == "Ruby" - - # Check validation - valid, _ = detector.validate_language(ruby_code, "Ruby") - assert valid == True - - -if __name__ == "__main__": - test_ruby_detection() - print("All Ruby detection tests passed!") diff --git a/week4/community-contributions/ai_code_converter/tests/test_rust_detection.py b/week4/community-contributions/ai_code_converter/tests/test_rust_detection.py deleted file mode 100644 index 5fe59f9..0000000 --- a/week4/community-contributions/ai_code_converter/tests/test_rust_detection.py +++ /dev/null @@ -1,89 +0,0 @@ -"""Test module for Rust language detection.""" - -import sys -import os - -# Add the src directory to the Python path -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../src'))) - -from ai_code_converter.core.language_detection import LanguageDetector - - -def test_rust_detection(): - """Test the Rust language detection functionality.""" - detector = LanguageDetector() - - # Sample Rust code - rust_code = """ -use std::collections::HashMap; -use std::io::{self, Write}; - -// A struct in Rust -#[derive(Debug, Clone)] -pub struct Person { - name: String, - age: u32, -} - -// Implementation block for Person -impl Person { - // Constructor (associated function) - pub fn new(name: String, age: u32) -> Self { - Person { name, age } - } - - // Method - pub fn greet(&self) -> String { - format!("Hello, my name is {} and I am {} years old.", self.name, self.age) - } - - // Mutable method - pub fn celebrate_birthday(&mut self) { - self.age += 1; - println!("Happy birthday! Now I am {} years old.", self.age); - } -} - -// A simple function with pattern matching -fn process_option(opt: Option) -> i32 { - match opt { - Some(value) => value, - None => -1, - } -} - -// Main function with vector usage -fn main() { - // Create a vector - let mut numbers: Vec = vec![1, 2, 3, 4, 5]; - numbers.push(6); - - // Create a Person instance - let mut person = Person::new(String::from("Alice"), 30); - println!("{}", person.greet()); - person.celebrate_birthday(); - - // Use a HashMap - let mut scores = HashMap::new(); - scores.insert(String::from("Blue"), 10); - scores.insert(String::from("Red"), 50); - - // Pattern matching with if let - if let Some(score) = scores.get("Blue") { - println!("Blue team score: {}", score); - } -} -""" - - # Test the detection - assert detector.detect_rust(rust_code) == True - assert detector.detect_language(rust_code) == "Rust" - - # Check validation - valid, _ = detector.validate_language(rust_code, "Rust") - assert valid == True - - -if __name__ == "__main__": - test_rust_detection() - print("All Rust detection tests passed!") diff --git a/week4/community-contributions/ai_code_converter/tests/test_sql_detection.py b/week4/community-contributions/ai_code_converter/tests/test_sql_detection.py deleted file mode 100644 index 91058f3..0000000 --- a/week4/community-contributions/ai_code_converter/tests/test_sql_detection.py +++ /dev/null @@ -1,209 +0,0 @@ -"""Test module for SQL language detection.""" - -import sys -import os - -# Add the src directory to the Python path -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../src'))) - -from ai_code_converter.core.language_detection import LanguageDetector - - -def test_sql_detection(): - """Test the SQL language detection functionality.""" - detector = LanguageDetector() - - # Sample SQL code - sql_code = """ --- Create database -CREATE DATABASE ecommerce; - --- Use the database -USE ecommerce; - --- Create tables -CREATE TABLE customers ( - customer_id INT PRIMARY KEY AUTO_INCREMENT, - first_name VARCHAR(50) NOT NULL, - last_name VARCHAR(50) NOT NULL, - email VARCHAR(100) UNIQUE NOT NULL, - password VARCHAR(255) NOT NULL, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - is_active BOOLEAN DEFAULT TRUE -); - -CREATE TABLE categories ( - category_id INT PRIMARY KEY AUTO_INCREMENT, - name VARCHAR(100) NOT NULL, - description TEXT, - parent_id INT, - FOREIGN KEY (parent_id) REFERENCES categories(category_id) -); - -CREATE TABLE products ( - product_id INT PRIMARY KEY AUTO_INCREMENT, - name VARCHAR(100) NOT NULL, - description TEXT, - price DECIMAL(10, 2) NOT NULL, - stock_quantity INT DEFAULT 0, - category_id INT, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - FOREIGN KEY (category_id) REFERENCES categories(category_id) -); - -CREATE TABLE orders ( - order_id INT PRIMARY KEY AUTO_INCREMENT, - customer_id INT NOT NULL, - order_date DATETIME DEFAULT CURRENT_TIMESTAMP, - status ENUM('pending', 'processing', 'shipped', 'delivered', 'cancelled') DEFAULT 'pending', - total_amount DECIMAL(10, 2) NOT NULL, - FOREIGN KEY (customer_id) REFERENCES customers(customer_id) -); - -CREATE TABLE order_items ( - order_item_id INT PRIMARY KEY AUTO_INCREMENT, - order_id INT NOT NULL, - product_id INT NOT NULL, - quantity INT NOT NULL, - price DECIMAL(10, 2) NOT NULL, - FOREIGN KEY (order_id) REFERENCES orders(order_id), - FOREIGN KEY (product_id) REFERENCES products(product_id) -); - --- Insert sample data -INSERT INTO categories (name, description) -VALUES ('Electronics', 'Electronic devices and gadgets'), - ('Clothing', 'Apparel and accessories'), - ('Books', 'Books and publications'); - -INSERT INTO products (name, description, price, stock_quantity, category_id) -VALUES ('Smartphone', 'Latest smartphone with advanced features', 699.99, 100, 1), - ('Laptop', 'High-performance laptop for professionals', 1299.99, 50, 1), - ('T-shirt', 'Cotton t-shirt in various colors', 19.99, 200, 2), - ('Jeans', 'Classic denim jeans', 49.99, 150, 2), - ('Programming Book', 'Learn programming from experts', 39.99, 75, 3); - --- Simple queries -SELECT * FROM products WHERE price > 50.00; - -SELECT p.name, p.price, c.name AS category -FROM products p -JOIN categories c ON p.category_id = c.category_id -WHERE p.stock_quantity > 0 -ORDER BY p.price DESC; - --- Aggregate functions -SELECT - c.name AS category, - COUNT(p.product_id) AS product_count, - AVG(p.price) AS average_price, - MIN(p.price) AS min_price, - MAX(p.price) AS max_price -FROM products p -JOIN categories c ON p.category_id = c.category_id -GROUP BY c.name -HAVING COUNT(p.product_id) > 1; - --- Transactions -BEGIN TRANSACTION; - -UPDATE products -SET stock_quantity = stock_quantity - 1 -WHERE product_id = 1; - -INSERT INTO orders (customer_id, total_amount) -VALUES (1, 699.99); - -INSERT INTO order_items (order_id, product_id, quantity, price) -VALUES (LAST_INSERT_ID(), 1, 1, 699.99); - -COMMIT; - --- Views -CREATE VIEW product_details AS -SELECT - p.product_id, - p.name, - p.description, - p.price, - p.stock_quantity, - c.name AS category -FROM products p -JOIN categories c ON p.category_id = c.category_id; - --- Stored procedure -DELIMITER // -CREATE PROCEDURE get_product_inventory() -BEGIN - SELECT - p.name, - p.stock_quantity, - CASE - WHEN p.stock_quantity = 0 THEN 'Out of Stock' - WHEN p.stock_quantity < 10 THEN 'Low Stock' - WHEN p.stock_quantity < 50 THEN 'Medium Stock' - ELSE 'Well Stocked' - END AS stock_status - FROM products p - ORDER BY p.stock_quantity; -END // -DELIMITER ; - --- Triggers -DELIMITER // -CREATE TRIGGER after_order_insert -AFTER INSERT ON orders -FOR EACH ROW -BEGIN - INSERT INTO order_history (order_id, customer_id, status, action) - VALUES (NEW.order_id, NEW.customer_id, NEW.status, 'created'); -END // -DELIMITER ; - --- Indexes -CREATE INDEX idx_product_price ON products(price); -CREATE INDEX idx_order_customer ON orders(customer_id); - --- Subqueries -SELECT c.name, c.email -FROM customers c -WHERE c.customer_id IN ( - SELECT DISTINCT o.customer_id - FROM orders o - JOIN order_items oi ON o.order_id = oi.order_id - JOIN products p ON oi.product_id = p.product_id - WHERE p.category_id = 1 -); - --- Common Table Expressions (CTE) -WITH high_value_orders AS ( - SELECT - o.order_id, - o.customer_id, - o.total_amount - FROM orders o - WHERE o.total_amount > 500 -) -SELECT - c.first_name, - c.last_name, - COUNT(hvo.order_id) AS high_value_order_count -FROM customers c -JOIN high_value_orders hvo ON c.customer_id = hvo.customer_id -GROUP BY c.customer_id; -""" - - # Test the detection - assert detector.detect_sql(sql_code) == True - assert detector.detect_language(sql_code) == "SQL" - - # Check validation - valid, _ = detector.validate_language(sql_code, "SQL") - assert valid == True - - -if __name__ == "__main__": - test_sql_detection() - print("All SQL detection tests passed!") diff --git a/week4/community-contributions/ai_code_converter/tests/test_swift_detection.py b/week4/community-contributions/ai_code_converter/tests/test_swift_detection.py deleted file mode 100644 index d49c2ce..0000000 --- a/week4/community-contributions/ai_code_converter/tests/test_swift_detection.py +++ /dev/null @@ -1,96 +0,0 @@ -"""Test module for Swift language detection.""" - -import sys -import os - -# Add the src directory to the Python path -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../src'))) - -from ai_code_converter.core.language_detection import LanguageDetector - - -def test_swift_detection(): - """Test the Swift language detection functionality.""" - detector = LanguageDetector() - - # Sample Swift code - swift_code = """ -import Foundation -import UIKit - -// A simple Swift class -class Person { - var name: String - var age: Int - - init(name: String, age: Int) { - self.name = name - self.age = age - } - - func greet() -> String { - return "Hello, my name is \(name) and I am \(age) years old." - } - - func celebrateBirthday() { - age += 1 - print("Happy birthday! Now I am \(age) years old.") - } -} - -// Swift structs -struct Point { - let x: Double - let y: Double - - func distanceTo(point: Point) -> Double { - return sqrt(pow(point.x - x, 2) + pow(point.y - y, 2)) - } -} - -// Swift optional binding -func processName(name: String?) { - if let unwrappedName = name { - print("Hello, \(unwrappedName)!") - } else { - print("Hello, anonymous!") - } -} - -// Guard statement -func process(value: Int?) { - guard let value = value else { - print("Value is nil") - return - } - - print("Value is \(value)") -} - -// UIKit elements -class MyViewController: UIViewController { - @IBOutlet weak var nameLabel: UILabel! - - @IBAction func buttonPressed(_ sender: UIButton) { - nameLabel.text = "Button was pressed!" - } - - override func viewDidLoad() { - super.viewDidLoad() - // Do additional setup - } -} -""" - - # Test the detection - assert detector.detect_swift(swift_code) == True - assert detector.detect_language(swift_code) == "Swift" - - # Check validation - valid, _ = detector.validate_language(swift_code, "Swift") - assert valid == True - - -if __name__ == "__main__": - test_swift_detection() - print("All Swift detection tests passed!") diff --git a/week4/community-contributions/ai_code_converter/tests/test_typescript_detection.py b/week4/community-contributions/ai_code_converter/tests/test_typescript_detection.py deleted file mode 100644 index 2fa0e51..0000000 --- a/week4/community-contributions/ai_code_converter/tests/test_typescript_detection.py +++ /dev/null @@ -1,148 +0,0 @@ -"""Test module for TypeScript language detection.""" - -import sys -import os - -# Add the src directory to the Python path -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../src'))) - -from ai_code_converter.core.language_detection import LanguageDetector - - -def test_typescript_detection(): - """Test the TypeScript language detection functionality.""" - detector = LanguageDetector() - - # Sample TypeScript code - typescript_code = """ -import { Component, OnInit } from '@angular/core'; -import axios from 'axios'; - -// Interface definition -interface User { - id: number; - name: string; - email: string; - isActive: boolean; -} - -// Type alias -type UserResponse = User[] | null; - -// Enum -enum UserRole { - Admin = 'ADMIN', - User = 'USER', - Guest = 'GUEST' -} - -// Class with type annotations -export class UserService { - private apiUrl: string; - private users: User[] = []; - - constructor(baseUrl: string) { - this.apiUrl = `${baseUrl}/users`; - } - - // Async method with return type - public async getUsers(): Promise { - try { - const response = await axios.get(this.apiUrl); - if (response.data) { - this.users = response.data; - return this.users; - } - return []; - } catch (error) { - console.error('Error fetching users:', error); - return []; - } - } - - // Method with typed parameters - public getUserById(id: number): User | undefined { - return this.users.find(user => user.id === id); - } - - // Method with union type parameters - public filterUsers(status: boolean | null): User[] { - if (status === null) return this.users; - return this.users.filter(user => user.isActive === status); - } -} - -// Generic class -class DataStore { - private items: T[] = []; - - public add(item: T): void { - this.items.push(item); - } - - public getAll(): T[] { - return this.items; - } -} - -// Decorator example -@Component({ - selector: 'app-user-list', - template: '
User List Component
' -}) -class UserListComponent implements OnInit { - users: User[] = []; - userService: UserService; - - constructor() { - this.userService = new UserService('https://api.example.com'); - } - - async ngOnInit(): Promise { - this.users = await this.userService.getUsers(); - console.log('Users loaded:', this.users.length); - } -} - -// Function with default parameters -function createUser(name: string, email: string, isActive: boolean = true): User { - const id = Math.floor(Math.random() * 1000); - return { id, name, email, isActive }; -} - -// Main code -const userStore = new DataStore(); -const newUser = createUser('John Doe', 'john@example.com'); -userStore.add(newUser); - -console.log('Users in store:', userStore.getAll().length); -""" - - # Test the detection - assert detector.detect_typescript(typescript_code) == True - assert detector.detect_language(typescript_code) == "TypeScript" - - # Check that it's distinguishable from JavaScript - js_code = """ -function greet(name) { - console.log(`Hello, ${name}!`); -} - -const user = { - name: 'John', - age: 30 -}; - -greet(user.name); -""" - assert detector.detect_typescript(js_code) == False - assert detector.detect_language(js_code) == "JavaScript" - - # Check validation - valid, _ = detector.validate_language(typescript_code, "TypeScript") - assert valid == True - - -if __name__ == "__main__": - test_typescript_detection() - print("All TypeScript detection tests passed!") diff --git a/week4/community-contributions/ai_code_converter/themes/gradio_theme.py b/week4/community-contributions/ai_code_converter/themes/gradio_theme.py deleted file mode 100644 index 748f51d..0000000 --- a/week4/community-contributions/ai_code_converter/themes/gradio_theme.py +++ /dev/null @@ -1,332 +0,0 @@ -import gradio as gr - -# Define custom CSS with dark theme fixes, updated to use html.dark -custom_css = """ -/* Root variables for light theme */ -:root { - --background: #ffffff; - --secondary-background: #f7f7f7; - --tertiary-background: #f0f0f0; - --text: #000000; - --secondary-text: #5d5d5d; - --border: #e0e0e0; - --accent: #ff7b2c; - --accent-hover: #ff6a14; - --button-text: #ffffff; - --code-bg: #f7f7f7; - --code-text: #000000; - --dropdown-bg: #ffffff; - --slider-track: #e0e0e0; - --slider-thumb: #ff7b2c; - --collapsible-header: #f0f0f0; - --primary-button-bg: #4a6ee0; /* Blue */ - --primary-button-hover: #3a5ec8; - --secondary-button-bg: #444444; - --secondary-button-hover: #555555; - --danger-button-bg: #e74c3c; /* Red */ - --danger-button-hover: #c0392b; - --success-button-bg: #e67e22; /* Orange */ - --success-button-hover: #d35400; -} - -/* Dark theme variables using html.dark */ -html.dark { - --background: #1a1a1a; - --secondary-background: #252525; - --tertiary-background: #2a2a2a; - --text: #ffffff; - --secondary-text: #cccccc; - --border: #444444; - --accent: #ff7b2c; - --accent-hover: #ff6a14; - --button-text: #ffffff; - --code-bg: #252525; - --code-text: #ffffff; - --dropdown-bg: #2a2a2a; - --slider-track: #444444; - --slider-thumb: #ff7b2c; - --collapsible-header: #333333; - --primary-button-bg: #4a6ee0; - --primary-button-hover: #3a5ec8; - --secondary-button-bg: #444444; - --secondary-button-hover: #555555; - --danger-button-bg: #e74c3c; - --danger-button-hover: #c0392b; - --success-button-bg: #e67e22; - --success-button-hover: #d35400; -} - -/* Base styles */ -body { - background-color: var(--background) !important; - color: var(--text) !important; - transition: all 0.3s ease; -} - -.gradio-container { - background-color: var(--background) !important; - max-width: 100% !important; -} - -/* Headers */ -h1, h2, h3, h4, h5, h6, .gr-header { - color: var(--text) !important; -} - -/* Panels and blocks */ -.gr-panel, .gr-form, .gr-box, .gr-block { - background-color: var(--secondary-background) !important; - color: var(--text) !important; - border-color: var(--border) !important; - border-radius: 6px !important; -} - -/* Validation messages section */ -.gr-accordion .gr-panel { - background-color: var(--secondary-background) !important; - color: var(--text) !important; - border-color: #e74c3c !important; /* Red border */ -} - -.gr-accordion-header { - background-color: var(--collapsible-header) !important; - color: var(--text) !important; -} - -/* Code editors */ -.codebox, .code-editor, .cm-editor { - background-color: var(--code-bg) !important; - color: var(--code-text) !important; - border: 1px solid var(--border) !important; - border-radius: 4px !important; -} - -/* Syntax highlighting */ -.cm-editor .cm-content, .cm-editor .cm-line { - color: var(--code-text) !important; -} -.cm-editor .cm-keyword { color: #ff79c6 !important; } /* Pink */ -.cm-editor .cm-number { color: #bd93f9 !important; } /* Purple */ -.cm-editor .cm-string { color: #f1fa8c !important; } /* Yellow */ -.cm-editor .cm-comment { color: #6272a4 !important; } /* Gray */ - -/* Buttons */ -.gr-button { - background-color: var(--tertiary-background) !important; - color: var(--text) !important; - border-color: var(--border) !important; - border-radius: 4px !important; -} -.gr-button.success { - background-color: var(--success-button-bg) !important; - color: var(--button-text) !important; -} -.gr-button.success:hover { - background-color: var(--success-button-hover) !important; -} -.gr-button.danger { - background-color: var(--danger-button-bg) !important; - color: var(--button-text) !important; -} -.gr-button.danger:hover { - background-color: var(--danger-button-hover) !important; -} -.gr-button.secondary { - background-color: var(--secondary-button-bg) !important; - color: var(--button-text) !important; -} -.gr-button.secondary:hover { - background-color: var(--secondary-button-hover) !important; -} - -/* File upload */ -.gr-file, .gr-file-upload { - background-color: var(--secondary-background) !important; - color: var(--text) !important; - border-color: var(--border) !important; -} - -/* Dropdowns */ -.gr-dropdown, .gr-dropdown-container, .gr-dropdown select, .gr-dropdown option { - background-color: var(--dropdown-bg) !important; - color: var(--text) !important; - border-color: var(--border) !important; -} - -/* Slider */ -.gr-slider { - background-color: var(--background) !important; -} -.gr-slider-track { - background-color: var(--slider-track) !important; -} -.gr-slider-handle { - background-color: var(--slider-thumb) !important; -} -.gr-slider-value { - color: var(--text) !important; -} - -/* Code output */ -.gr-code { - background-color: var(--code-bg) !important; - color: var(--code-text) !important; - border-color: var(--border) !important; -} - -/* Footer */ -.footer { - color: var(--secondary-text) !important; -} -.footer a { - color: var(--accent) !important; -} -""" - -# JavaScript for theme toggling and initialization -js_code = """ - -""" - -# Language and model options -INPUT_LANGUAGES = ["Python", "JavaScript", "Java", "TypeScript", "Swift", "C#", "Ruby", "Go", "Rust", "PHP"] -OUTPUT_LANGUAGES = ["Python", "JavaScript", "Java", "TypeScript", "Swift", "C#", "Ruby", "Go", "Rust", "PHP", "Julia"] -MODELS = ["GPT", "Claude", "CodeLlama", "Llama", "Gemini"] -DOC_STYLES = ["standard", "detailed", "minimal"] - -# Build the interface -with gr.Blocks(css=custom_css, theme=gr.themes.Base()) as demo: - # Inject the JavaScript code - gr.HTML(js_code) - - # States for dynamic button text - input_lang_state = gr.State("Python") - output_lang_state = gr.State("JavaScript") - - # Header - with gr.Row(elem_classes="header-container"): - gr.HTML("

AI CodeXchange

") - # Theme selection radio and apply button - theme_radio = gr.Radio(["light", "dark"], label="Theme", elem_id="theme_radio_container", value="dark") - theme_button = gr.Button("Apply Theme") - - # Validation Messages - with gr.Accordion("Validation Messages", open=True): - with gr.Row(): - with gr.Column(): - input_code = gr.Code(language="python", label="Code In", lines=15, elem_classes="code-container") - with gr.Column(): - output_code = gr.Code(language="javascript", label="Converted Code", lines=15, elem_classes="code-container") - - # Configuration Options - with gr.Row(): - input_lang = gr.Dropdown(INPUT_LANGUAGES, label="Code In", value="Python") - output_lang = gr.Dropdown(OUTPUT_LANGUAGES, label="Code Out", value="JavaScript") - model = gr.Dropdown(MODELS, label="Model", value="GPT") - temperature = gr.Slider(minimum=0, maximum=1, step=0.1, value=0.7, label="Temperature") - - # Document Options - with gr.Row(): - document_check = gr.Checkbox(label="Document", value=True) - doc_style = gr.Dropdown(DOC_STYLES, label="Document Style", value="standard") - - # File Upload - with gr.Accordion("Upload", open=True): - file_upload = gr.File(label="Drop File Here - or - Click to Upload") - - # Action Buttons - with gr.Row(elem_classes="button-row"): - convert_btn = gr.Button("Convert", elem_classes="success") - clear_btn = gr.Button("Clear All", elem_classes="danger") - - # Run Buttons - with gr.Row(elem_classes="button-row"): - run_source = gr.Button("Run Source Code", elem_classes="secondary") - run_target = gr.Button("Run Target Code", elem_classes="secondary") - - # Results - with gr.Row(): - source_result = gr.Code(label="Source Code Result", language="python", lines=10, elem_classes="code-container") - target_result = gr.Code(label="Converted Code Result", language="javascript", lines=10, elem_classes="code-container") - - # Download - with gr.Accordion("Download", open=True): - with gr.Row(): - dl_source = gr.Button("Download Source Code") - dl_target = gr.Button("Download Converted Code") - - # Footer - with gr.Row(elem_classes="footer"): - gr.HTML("
Use via API
Built with Gradio
Settings
") - - # Theme toggle event - theme_button.click( - fn=None, - inputs=None, - outputs=[], - js=""" - var theme = document.querySelector('#theme_radio_container input:checked').value; - toggleTheme(theme); - """ - ) - - # Existing event handlers - def convert_code(input_code, in_lang, out_lang, model, temp, doc, doc_style): - return f"// Converted from {in_lang} to {out_lang} using {model}\n// Temperature: {temp}\n// Documentation: {doc} ({doc_style if doc else 'N/A'})" - - convert_btn.click(fn=convert_code, inputs=[input_code, input_lang_state, output_lang_state, model, temperature, document_check, doc_style], outputs=[output_code]) - - def update_convert_btn_text(in_lang, out_lang): - return f"Convert {in_lang} to {out_lang}", in_lang, out_lang - - input_lang.change(fn=update_convert_btn_text, inputs=[input_lang, output_lang], outputs=[convert_btn, input_lang_state, output_lang_state]) - output_lang.change(fn=update_convert_btn_text, inputs=[input_lang, output_lang], outputs=[convert_btn, input_lang_state, output_lang_state]) - - def update_run_btn_text(in_lang, out_lang): - return f"Run {in_lang}", f"Run {out_lang}" - - input_lang.change(fn=update_run_btn_text, inputs=[input_lang, output_lang], outputs=[run_source, run_target]) - output_lang.change(fn=update_run_btn_text, inputs=[input_lang, output_lang], outputs=[run_source, run_target]) - - def clear_all(): - return "", "", "", "" - - clear_btn.click(fn=clear_all, inputs=[], outputs=[input_code, output_code, source_result, target_result]) - - def run_code(code, lang): - return f"// Running {lang} code...\n// Results would appear here" - - run_source.click(fn=run_code, inputs=[input_code, input_lang_state], outputs=[source_result]) - run_target.click(fn=run_code, inputs=[output_code, output_lang_state], outputs=[target_result]) - - def handle_file_upload(file): - if file is None: - return "" - with open(file.name, "r") as f: - return f.read() - - file_upload.change(fn=handle_file_upload, inputs=[file_upload], outputs=[input_code]) - -if __name__ == "__main__": - demo.launch() \ No newline at end of file diff --git a/week4/community-contributions/ai_code_converter/themes/gradio_theme_light.py b/week4/community-contributions/ai_code_converter/themes/gradio_theme_light.py deleted file mode 100644 index d4fcfe4..0000000 --- a/week4/community-contributions/ai_code_converter/themes/gradio_theme_light.py +++ /dev/null @@ -1,35 +0,0 @@ -import gradio as gr - -# JavaScript function to toggle theme -js_code = """ -function toggleTheme(theme) { - var url = new URL(window.location.href); - if (theme === "dark") { - url.searchParams.set('__theme', 'dark'); - } else { - url.searchParams.delete('__theme'); - } - window.location.href = url.href; -} -""" - -def create_app(): - with gr.Blocks(js=js_code) as demo: - theme_radio = gr.Radio(["light", "dark"], label="Theme") - - # Button to trigger the theme change - theme_button = gr.Button("Toggle Theme") - - # Use JavaScript to toggle the theme when the button is clicked - theme_button.click( - None, - inputs=[theme_radio], - js=f"(theme) => {{ toggleTheme(theme); return []; }}", - queue=False, - ) - - return demo - -if __name__ == "__main__": - app = create_app() - app.launch() \ No newline at end of file