Source code for calute.ui.themes

# Copyright 2025 The EasyDeL/Calute Author @erfanzar (Erfan Zare Chavoshi).
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


"""Theme configuration for Calute Chainlit UI.

This module provides theming and styling configuration for the
Chainlit-based chat interface. It includes:
- Dark and light theme color definitions
- Chainlit theme.json configuration
- Custom CSS for thinking panels, tool steps, and code blocks
- Chainlit config.toml generation
- Theme file setup utilities

The theme is inspired by OpenWebUI and provides a modern, dark
interface with proper syntax highlighting and step visualization.

Module Attributes:
    APP_TITLE: Application title displayed in the UI.
    APP_SUBTITLE: Application subtitle/description.
    COLORS: Dictionary of color values for programmatic use.
    CHAINLIT_THEME: Theme configuration for theme.json.
    CUSTOM_CSS: Custom CSS styles for enhanced UI elements.
    CONFIG_TOML: Chainlit config.toml content.

Example:
    >>> from calute.ui.themes import setup_chainlit_theme, get_theme_colors
    >>> setup_chainlit_theme()  # Creates theme files in current directory
    >>> colors = get_theme_colors()
    >>> print(colors["primary"])
"""

import json
from pathlib import Path

APP_TITLE = "Calute"
APP_SUBTITLE = "Advanced conversational AI with reasoning and tool capabilities"

COLORS = {
    "background": "#171717",
    "surface": "#262626",
    "surface_light": "#333333",
    "border": "#4e4e4e",
    "text": "#e3e3e3",
    "text_secondary": "#b4b4b4",
    "text_muted": "#9b9b9b",
    "success": "#10b981",
    "error": "#ef4444",
    "warning": "#f59e0b",
    "primary": "#676767",
    "code_bg": "#0d0d0d",
}

CHAINLIT_THEME = {
    "variables": {
        "dark": {
            "--font-sans": "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
            "--font-mono": "'JetBrains Mono', 'Fira Code', monospace",
            "--background": "0 0% 9%",
            "--foreground": "0 0% 89%",
            "--card": "0 0% 15%",
            "--card-foreground": "0 0% 89%",
            "--popover": "0 0% 15%",
            "--popover-foreground": "0 0% 89%",
            "--primary": "0 0% 40%",
            "--primary-foreground": "0 0% 89%",
            "--secondary": "0 0% 20%",
            "--secondary-foreground": "0 0% 89%",
            "--muted": "0 0% 15%",
            "--muted-foreground": "0 0% 61%",
            "--accent": "0 0% 20%",
            "--accent-foreground": "0 0% 89%",
            "--destructive": "0 62% 30%",
            "--destructive-foreground": "0 0% 98%",
            "--border": "0 0% 31%",
            "--input": "0 0% 31%",
            "--ring": "0 0% 40%",
            "--radius": "0.75rem",
        },
        "light": {
            "--font-sans": "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
            "--font-mono": "'JetBrains Mono', 'Fira Code', monospace",
            "--background": "0 0% 100%",
            "--foreground": "0 0% 9%",
            "--card": "0 0% 100%",
            "--card-foreground": "0 0% 9%",
            "--popover": "0 0% 100%",
            "--popover-foreground": "0 0% 9%",
            "--primary": "0 0% 40%",
            "--primary-foreground": "0 0% 100%",
            "--secondary": "0 0% 96%",
            "--secondary-foreground": "0 0% 9%",
            "--muted": "0 0% 96%",
            "--muted-foreground": "0 0% 45%",
            "--accent": "0 0% 96%",
            "--accent-foreground": "0 0% 9%",
            "--destructive": "0 84% 60%",
            "--destructive-foreground": "0 0% 98%",
            "--border": "0 0% 90%",
            "--input": "0 0% 90%",
            "--ring": "0 0% 40%",
            "--radius": "0.5rem",
        },
    }
}

CUSTOM_CSS = """
/* Google Fonts */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&display=swap');

/* Thinking step styling */
[data-testid="step"][data-type="llm"] {
    background: linear-gradient(135deg, hsl(0, 0%, 15%) 0%, hsl(0, 0%, 10%) 100%) !important;
    border-left: 3px solid hsl(0, 0%, 40%) !important;
    font-style: italic;
}

[data-testid="step"][data-type="llm"] .step-content {
    color: hsl(0, 0%, 61%) !important;
}

/* Tool step styling */
[data-testid="step"][data-type="tool"] {
    border-left: 3px solid hsl(142, 76%, 36%) !important;
}

/* Running tool animation */
[data-testid="step"][data-type="tool"].running {
    border-left-color: hsl(38, 92%, 50%) !important;
    animation: pulse-border 1.5s ease-in-out infinite;
}

@keyframes pulse-border {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.5; }
}

/* Code block styling */
pre, code {
    font-family: 'JetBrains Mono', 'Fira Code', monospace !important;
}

pre {
    background: hsl(0, 0%, 5%) !important;
    border: 1px solid hsl(0, 0%, 20%) !important;
    border-radius: 6px !important;
}

/* Message styling */
.message-content {
    font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif !important;
}

/* Hide watermark */
.watermark {
    display: none !important;
}

/* Scrollbar styling */
::-webkit-scrollbar {
    width: 8px;
    height: 8px;
}

::-webkit-scrollbar-track {
    background: hsl(0, 0%, 9%);
    border-radius: 8px;
}

::-webkit-scrollbar-thumb {
    background: hsl(0, 0%, 31%);
    border-radius: 8px;
}

::-webkit-scrollbar-thumb:hover {
    background: hsl(0, 0%, 40%);
}
"""

CONFIG_TOML = f"""[project]
user_env = []
session_timeout = 3600
cache = false
allow_origins = ["*"]

[features]
unsafe_allow_html = true
latex = false

[features.spontaneous_file_upload]
enabled = true
accept = ["*/*"]
max_files = 20
max_size_mb = 500

# MCP (Model Context Protocol) configuration
[features.mcp]
enabled = true

[features.mcp.sse]
enabled = true

[features.mcp.streamable-http]
enabled = true

[features.mcp.stdio]
enabled = true
allowed_executables = ["npx", "uvx", "python", "node"]

[UI]
name = "{APP_TITLE}"
default_theme = "dark"
cot = "full"

[meta]
generated_by = "2.9.2"
"""


[docs]def setup_chainlit_theme(base_path: Path | str | None = None) -> None: """Generate Chainlit configuration files. Creates the necessary theme.json, custom.css, and config.toml files for Chainlit theming. Removes any existing .chainlit directory first to avoid version conflicts. The function creates: - .chainlit/config.toml - Main Chainlit configuration - public/theme.json - Theme color definitions - public/custom.css - Custom CSS styling Args: base_path: Base directory for config files. Defaults to current working directory if not specified. Note: Any existing .chainlit directory is removed before setup to prevent version mismatch issues with Chainlit upgrades. """ import shutil base = Path(base_path) if base_path else Path.cwd() chainlit_dir = base / ".chainlit" if chainlit_dir.exists(): shutil.rmtree(chainlit_dir) chainlit_dir.mkdir(exist_ok=True) public_dir = base / "public" public_dir.mkdir(exist_ok=True) theme_path = public_dir / "theme.json" with open(theme_path, "w") as f: json.dump(CHAINLIT_THEME, f, indent=2) css_path = public_dir / "custom.css" with open(css_path, "w") as f: f.write(CUSTOM_CSS) config_path = chainlit_dir / "config.toml" with open(config_path, "w") as f: f.write(CONFIG_TOML)
[docs]def get_theme_colors() -> dict[str, str]: """Get theme colors for programmatic use. Provides access to the theme color palette for use in custom components or styling. Returns a copy to prevent modification of the original color definitions. Returns: Dictionary mapping color names to hex values. Keys include: background, surface, surface_light, border, text, text_secondary, text_muted, success, error, warning, primary, code_bg. Example: >>> colors = get_theme_colors() >>> print(f"Primary color: {colors['primary']}") Primary color: #676767 """ return COLORS.copy()