# Customizing django-anyllm

This guide shows how host projects can override django-anyllm's appearance and behavior without forking the package.

## Table of contents

- [Template overrides](#template-overrides)
- [CSS / theming](#css--theming)
- [Template blocks reference](#template-blocks-reference)
- [Settings](#settings)
- [Internal details (debug mode)](#internal-details-debug-mode)

---

## Template overrides

Django's template loader automatically prefers templates in your project's `templates/` directory over app-provided ones. Create a file with the **same relative path** and Django will use yours.

```
your_project/
  templates/
    django_anyllm/
      base.html          # Override the page shell
      chat.html          # Override the chat detail page layout
      _message.html      # Override how a single message renders
      _message_form.html # Override the input form
      _message_pair.html # Override the streaming partial
      _chat_list.html    # Override the sidebar chat list
```

> **Tip:** You do not need to override every template. You can override just `base.html` to inject your own layout (e.g. extend your project's base template instead) and leave the inner partials untouched.

### Extending your project's base template

If your project already has a `base.html` with navigation, branding, and asset loading, override `django_anyllm/base.html` to extend it:

```django
{# your_project/templates/django_anyllm/base.html #}
{% extends "your_app/base.html" %}{% load i18n %}

{% block page_title %}{% trans "Chat" %}{% endblock %}

{% block extra_css %}
  {{ block.super }}
  <link rel="stylesheet" href="{% static 'django_anyllm/css/anyllm.css' %}">
  {# Your override CSS #}
  <link rel="stylesheet" href="{% static 'css/chat-overrides.css' %}">
{% endblock %}

{% block content %}
  {# Re-use the default content or fully replace it #}
  {{ block.super }}
{% endblock %}

{% block extra_js %}
  {{ block.super }}
  <script src="https://unpkg.com/htmx.org@1.9.12"></script>
  <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
{% endblock %}
```

If you extend your own base template **and** want to keep the default anyllm markup, you can use `{% include %}` to pull in the original partials. If you only need to tweak colors, see the [CSS / theming](#css--theming) section below — you may not need to override templates at all.

---

## CSS / theming

All visual styles are controlled by **CSS custom properties** defined in `django_anyllm/css/anyllm.css`. Host projects can override any variable in their own stylesheet.

### Quick override

Create a CSS file in your project's static files and load it via `extra_head`:

```css
/* your_project/static/css/chat-theme.css */
:root {
  /* Brand colors */
  --accent: #6366f1;
  --accent-hover: #4f46e5;
  --accent-light: #e0e7ff;

  /* Backgrounds */
  --bg: #ffffff;
  --bg-sidebar: #f8fafc;
  --bg-message-user: #f1f5f9;
  --bg-message-assistant: #ffffff;

  /* Text */
  --text: #0f172a;
  --text-muted: #64748b;
  --text-light: #94a3b8;

  /* Borders */
  --border: #e2e8f0;
  --border-light: #f1f5f9;

  /* Radii */
  --radius-sm: 0.375rem;
  --radius-md: 0.75rem;
  --radius-lg: 1rem;

  /* Shadows */
  --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
  --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
}
```

Then load it in `extra_head` (either by overriding `base.html` or via your project's layout):

```django
{% block extra_head %}
  <link rel="stylesheet" href="{% static 'css/chat-theme.css' %}">
{% endblock %}
```

### Full style replacement

If you need to replace **all** styles (e.g. to match a design system with utility classes), you can:

1. Override `base.html` and **omit** the default CSS link.
2. Provide your own stylesheet that targets the anyllm class names.

The markup uses stable BEM-style class names:

| Class | Element |
|-------|---------|
| `.anyllm-app` | Root container |
| `.anyllm-sidebar` | Sidebar panel |
| `.anyllm-chat-list` | Chat list in sidebar |
| `.anyllm-main` | Main content area |
| `.anyllm-messages` | Message scroll container |
| `.anyllm-message-wrapper` | Message row (has `.user` / `.assistant` / `.tool`) |
| `.anyllm-avatar` | Message avatar |
| `.anyllm-message-body` | Message content + meta |
| `.anyllm-message-meta` | Role label + timestamp |
| `.anyllm-message` | Rendered message bubble |
| `.anyllm-message .content` | Markdown content container |
| `.anyllm-reasoning` | Reasoning block (debug) |
| `.anyllm-tool-call` | Tool call indicator |
| `.anyllm-tool-result` | Tool result output |
| `.anyllm-streaming` | Streaming animation dots |
| `.anyllm-input-area` | Input form container |
| `.anyllm-input-wrapper` | Textarea + actions wrapper |
| `.anyllm-send-btn` | Send button |
| `.anyllm-file-btn` | File attachment button |
| `.anyllm-empty` | Empty state placeholder |
| `.anyllm-error` | Error message banner |

---

## Template blocks reference

`base.html` exposes the following blocks:

| Block | Purpose |
|-------|---------|
| `title` | Page `<title>` |
| `extra_head` | Additional `<head>` content (CSS, fonts, meta) |
| `content` | Main page body |
| `extra_js` | Additional scripts at end of `<body>` |

### Overriding inner templates

The chat page is composed of several partials. Override any of them:

- **`_message.html`** — Renders a single message. Context: `message`.
- **`_message_pair.html`** — HTMX partial returned on new message. Sets up SSE listeners. Context: `chat`, `user_message`, `assistant_message`.
- **`_message_form.html`** — Input form. Context: `chat`.
- **`_chat_list.html`** — Sidebar list of chats. Context: `chats`, `chat` (may be None).

---

## Settings

Configure django-anyllm in your `settings.py`:

```python
DJANGO_ANYLLM = {
    "DEFAULT_PROVIDER": "openai",
    "DEFAULT_MODEL": "gpt-4",
    "MAX_QUERY_LIMIT": 100,
    "SHOW_INTERNAL": False,  # Show reasoning + tool calls
}
```

| Setting | Default | Description |
|---------|---------|-------------|
| `DEFAULT_PROVIDER` | `"openai"` | LLM provider slug |
| `DEFAULT_MODEL` | `"gpt-4"` | Default model name for new chats |
| `MAX_QUERY_LIMIT` | `100` | Max messages per chat |
| `SHOW_INTERNAL` | `False` | Show reasoning blocks and tool calls in the UI |

---

## Internal details (debug mode)

Set `SHOW_INTERNAL = True` to display:

- **Reasoning** blocks (when the model emits thinking content)
- **Tool calls** (function names being called)
- **Tool results** (raw function outputs)

These are hidden by default so end users see a clean chat interface.
