{% extends "base.html" %}
{% from "_macros.html" import icon, mark, model_avatar %}

{% block title %}{{ chat.title if chat else "New chat" }} - {{ brand.name }}{% endblock %}

{% block head %}
<link rel="stylesheet" href="{{ url_for('static', path='css/chat.css') }}">
{% if terminal_enabled %}
<link rel="stylesheet" href="{{ url_for('static', path='vendor/xterm.css') }}">
{% endif %}
{% endblock %}

{% block body_attrs %} data-authenticated="true"{% endblock %}

{% block body %}
<div class="shell">
  {% include "partials/sidebar.html" %}

  <main class="main">
    <header class="topbar">
      <button class="btn btn--icon" type="button" aria-label="Toggle sidebar"
              aria-expanded="true" data-toggle="#sidebar">
        {{ icon("sidebar") }}
      </button>

      <h1 class="topbar__title">
        <span id="chat-title">{{ chat.title if chat else "New chat" }}</span>
        {#
          Rename, where the name is. A themed dialog through `data-prompt`
          rather than an inline field: the heading is in a flex row beside the
          badges and the connection chip, and swapping it for a text box moves
          all of them. The response is the same out-of-band pair the `done`
          frame sends, so the sidebar row follows without a second request.
        #}
        {% if chat %}
        <button class="btn btn--icon btn--sm topbar__rename" type="button"
                hx-patch="/api/chats/{{ chat.id }}" hx-swap="none"
                data-prompt="What should this chat be called?"
                data-prompt-title="Rename chat" data-prompt-field="title"
                data-prompt-value="{{ chat.title }}"
                aria-label="Rename chat" title="Rename chat">
          {{ icon("pencil", "icon--sm") }}
        </button>
        {% endif %}
        {# Beside the title because it describes the chat rather than acting on
           it. The way out of it is Keep, in the overflow menu. #}
        {% if chat and chat.temporary %}
        <span class="badge badge--warning"
              title="Not listed in the sidebar, and removed 24 hours after the last message.">
          Temporary
        </span>
        {% endif %}
      </h1>

      {# Where this chat runs. Beside the title because it describes the chat
         and cannot be changed -- update_chat refuses the connection and the
         directory with a 409 -- so it is of a kind with the Temporary badge
         rather than with the controls on the right. The mode is the one thing
         here that moves, and it stays down by the message box. #}
      {% if chat and chat.kind == "agent" %}
      <span class="topbar__where" title="{{ chat.project_dir }}">
        {{ icon("bolt", "icon--sm") }}
        <span class="topbar__where-name">
          {{ agent_profile.name if agent_profile else "connection missing" }}
        </span>
        <span class="topbar__where-dir">{{ chat.project_dir }}</span>
      </span>
      {% endif %}

      <div class="topbar__actions">
        {#
          A link, not a script: the flag lives in the URL, so it survives a
          reload and can be bookmarked.
        #}
        {% if not chat and can.get("chat.create") %}
        <a class="btn btn--icon {{ 'is-active' if starting_temporary }}"
           href="{{ '/chat' if starting_temporary else '/chat?temporary=1' }}"
           aria-label="Temporary chat"
           title="{% if starting_temporary %}Starting a temporary chat. Click to go back to a normal one.{% else %}Start a temporary chat: not listed in the sidebar, and removed after a day.{% endif %}">
          {{ icon("clock") }}
        </a>
        {% endif %}

        {% if models %}
          {% if can.get("chat.model_select") or not chat %}
            {% include "chat/_model_picker.html" %}
          {% elif current_model %}
          <span class="badge">{{ current_model.label }}</span>
          {% endif %}
        {% endif %}

        {# Before a chat exists, both of these depend on a decision being made in
           the browser -- the kind toggle and the connection select -- which the
           server cannot see. So they render carrying `data-agent-only` and start
           `hidden`; `ui.js` shows them when `lembas:agent-target` names a
           connection and hides them again when it stops. On a chat that exists
           the server's answer is complete and neither attribute appears. #}
        {% if canvas_enabled %}
        {# Nearest the conversation of the three, being the widest and the one
           most likely to be open beside it. All three share one slot: at
           1280px the sidebar plus two panels leaves about seventy pixels of
           chat. #}
        <button class="btn btn--icon" type="button" aria-label="Canvas"
                title="Open a file beside the conversation"
                {% if not chat %}data-agent-only hidden{% endif %}
                aria-expanded="false" data-toggle="#canvas" data-toggle-group="side">
          {{ icon("file-text") }}
        </button>
        {% endif %}

        {% if terminal_enabled %}
        {# To the left of the inspector, and never open beside it: see the
           toggle group in app.js. #}
        <button class="btn btn--icon" type="button" aria-label="Terminal"
                title="Open a shell on {{ agent_profile.name if agent_profile else 'this connection' }}"
                {% if not chat %}data-agent-only hidden{% endif %}
                aria-expanded="false" data-toggle="#terminal" data-toggle-group="side">
          {{ icon("terminal") }}
        </button>
        {% endif %}

        {% if chat and user.is_admin %}
        <button class="btn btn--icon" type="button" aria-label="Inspect this chat"
                title="Inspect this chat" aria-expanded="false" data-toggle="#inspector"
                data-toggle-group="side">
          {{ icon("search") }}
        </button>
        {% endif %}

        {#
          Everything else about this chat, behind one button.

          The bar carried eight controls, which is a toolbar nobody can read at
          a glance. What is left in it is what you reach for while writing --
          the model, and the two panels. These three are occasional, and every
          one of them is also a slash command now.
        #}
        {% if chat %}
        <div class="picker" data-picker>
          <button class="btn btn--icon" type="button" data-picker-toggle
                  aria-haspopup="menu" aria-expanded="false"
                  aria-label="More" title="More">
            {{ icon("dots") }}
          </button>

          <div class="picker__menu picker__menu--compact" data-picker-menu role="menu"
               hidden aria-label="More">
            {% if chat.temporary %}
            <button class="picker__option" type="button" role="menuitem"
                    hx-post="/api/chats/{{ chat.id }}/keep" hx-swap="none">
              {{ icon("pin", "icon--sm") }}
              <span class="picker__option-body">
                <span class="picker__option-name">Keep this chat</span>
                <span class="picker__option-note">
                  Temporary — removed 24 hours after the last message
                </span>
              </span>
            </button>
            {% endif %}

            {% if can.get("chat.system_prompt") or can.get("chat.params") %}
            <button class="picker__option" type="button" role="menuitem"
                    data-toggle="#chat-settings">
              {{ icon("sliders", "icon--sm") }}
              <span class="picker__option-body">
                <span class="picker__option-name">Chat settings</span>
                <span class="picker__option-note">Prompt, knowledge, sampling</span>
              </span>
            </button>
            {% endif %}

            {% if messages %}
            {# Runs the same code as /compact rather than posting itself. Two
               implementations meant a spinner on neither and the endpoint's
               error messages reaching nobody. #}
            <button class="picker__option" type="button" role="menuitem"
                    onclick="window.lembasCommands &amp;&amp; window.lembasCommands.run('compact')">
              {{ icon("archive", "icon--sm") }}
              <span class="picker__option-body">
                <span class="picker__option-name">Compact</span>
                <span class="picker__option-note">
                  Summarise the earlier turns so they stop costing context
                </span>
              </span>
            </button>
            {% endif %}
          </div>
        </div>
        {% endif %}
      </div>
    </header>

    {% if chat and (can.get("chat.system_prompt") or can.get("chat.params")) %}
    {# Collapsed by default: per-chat overrides, not everyday controls. Each
       field saves on change, so there is no half-applied state. #}
    <section class="panel" id="chat-settings" hidden>
      <div class="panel__inner">
        {% if current_model and current_model.description %}
        <p class="panel__note">{{ current_model.description }}</p>
        {% endif %}

        {% if can.get("chat.system_prompt") %}
        <div class="field">
          <label class="field__label" for="system-prompt">System prompt</label>
          <textarea class="textarea" id="system-prompt" name="system_prompt" rows="3"
                    placeholder="{{ inherited_prompt or 'Instructions that apply to every message in this chat.' }}"
                    hx-patch="/api/chats/{{ chat.id }}" hx-swap="none"
                    hx-trigger="change">{{ chat.system_prompt }}</textarea>
          <p class="field__hint">
            {% if inherited_prompt %}
              Leave empty to use the {{ inherited_from }} prompt shown above.
            {% else %}
              Overrides the model and instance prompts for this chat only.
            {% endif %}
          </p>
        </div>
        {% endif %}

        {% if knowledge_bases %}
        {# Which bases this chat draws on. None ticked means everything you can
           see, which is what a chat with nothing chosen should do. #}
        <div class="field">
          <label class="field__label">Knowledge</label>
          <form hx-patch="/api/chats/{{ chat.id }}" hx-swap="none" hx-trigger="change">
            {# Always submitted, so unticking the last box still says something.
               An absent checkbox carries no signal of its own. #}
            <input type="hidden" name="knowledge_base_ids" value="">
            <div class="checkbox-row">
              {% for base in knowledge_bases %}
              <label class="checkbox">
                <input type="checkbox" name="knowledge_base_ids" value="{{ base.id }}"
                       {{ 'checked' if base.id in attached_base_ids }}>
                <span>{{ base.name }}</span>
              </label>
              {% endfor %}
            </div>
          </form>
          <p class="field__hint">
            {% if attached_base_ids %}
              Searches in this chat are limited to what is ticked.
            {% else %}
              Nothing ticked, so this chat can search everything in your
              <a href="/library/knowledge">library</a>.
            {% endif %}
          </p>
        </div>
        {% endif %}

        {% if can.get("chat.params") %}
        <div class="grid grid--3">
          <div class="field">
            <label class="field__label" for="temperature">Temperature</label>
            <input class="input" id="temperature" name="temperature" type="number"
                   min="0" max="2" step="0.05" placeholder="default"
                   value="{{ chat.params_json.get('temperature') if chat.params_json.get('temperature') is not none else '' }}"
                   hx-patch="/api/chats/{{ chat.id }}" hx-swap="none" hx-trigger="change">
          </div>
          <div class="field">
            <label class="field__label" for="top-p">Top-p</label>
            <input class="input" id="top-p" name="top_p" type="number"
                   min="0" max="1" step="0.05" placeholder="default"
                   value="{{ chat.params_json.get('top_p') if chat.params_json.get('top_p') is not none else '' }}"
                   hx-patch="/api/chats/{{ chat.id }}" hx-swap="none" hx-trigger="change">
          </div>
          <div class="field">
            <label class="field__label" for="max-tokens">Max tokens</label>
            <input class="input" id="max-tokens" name="max_tokens" type="number"
                   min="1" step="1" placeholder="default"
                   value="{{ chat.params_json.get('max_tokens') if chat.params_json.get('max_tokens') is not none else '' }}"
                   hx-patch="/api/chats/{{ chat.id }}" hx-swap="none" hx-trigger="change">
          </div>
        </div>
        <p class="field__hint">
          Leave a field empty to let the provider decide. Values outside the
          allowed range are ignored rather than clamped.
        </p>
        {% endif %}
      </div>
    </section>
    {% endif %}

    {% if not models %}
      {# Every fresh install lands here, so it points at the fix. #}
      <div class="empty">
        {{ icon("server", "empty__mark") }}
        <h2 class="empty__title">No models available</h2>
        <p class="empty__text">
          {% if user.is_admin %}
            Add an OpenAI-compatible connection and {{ brand.name }} will load its models.
          {% else %}
            No model connections have been set up yet. Ask an administrator.
          {% endif %}
        </p>
        {% if user.is_admin %}
        <a class="btn btn--primary" href="/admin/connections">
          {{ icon("server", "icon--sm") }} Set up a connection
        </a>
        {% endif %}
      </div>

    {% else %}
      <div class="thread-scroll" id="thread-scroll">
        <div class="thread" id="thread">
          {% if not messages %}
          <div class="thread__intro">
            {{ mark(cls="empty__mark", uid="intro") }}
            <h2 class="empty__title">What would you ask?</h2>
            <p class="empty__text">{{ brand.text.chat_empty }}</p>

            {# Only on a chat that does not exist yet. An empty chat someone
               opened on purpose already has a model and a prompt chosen. #}
            {% if not chat and suggestions %}
            <div class="suggestions">
              {% for suggestion in suggestions %}
              <button class="suggestion" type="button"
                      data-suggestion="{{ suggestion.prompt }}">
                <span class="suggestion__name">{{ suggestion.name }}</span>
                {% if suggestion.description %}
                <span class="suggestion__note">{{ suggestion.description }}</span>
                {% endif %}
              </button>
              {% endfor %}
            </div>
            {% endif %}
          </div>
          {% endif %}

          {# The same include the rewind response returns, so the conversation
             is described in one place. Markdown was rendered server-side in
             pages.py, keyed by message id. #}
          {% include "chat/_thread.html" %}
        </div>
      </div>

      {% if chat %}
      {#
        Where a reply that started outside a request arrives. A background job
        finishing wakes the chat server-side and there is no channel to say so:
        the only stream is per-message, opened by a bubble this page has not got.
        See `GET /api/chats/{id}/tail`, and `app.js` for the cursor -- which is
        read from the DOM on `htmx:configRequest`, not rendered here, because the
        DOM is the honest answer to what this page already holds.

        OUTSIDE `#thread`, not in it: a rewind and a compaction both swap
        `chat/_thread.html` into that container with `innerHTML`, and a poller
        living inside would be swapped away by the first one and never fire
        again. OUTSIDE the composer's form as well, and it names its own target
        regardless -- htmx inherits `hx-target`, the form carries `#thread`, and
        the jobs chip has already demonstrated once what an unstated target does
        to a transcript.

        `hx-sync="this:drop"` because a poll fires whether or not the last one
        has come back, and two answers computed from the same cursor are two
        copies of one bubble.
      #}
      <div hidden id="thread-tail"
           hx-get="/api/chats/{{ chat.id }}/tail"
           hx-trigger="every 5s"
           hx-target="#thread" hx-swap="beforeend"
           hx-sync="this:drop"></div>
      {% endif %}

      {#
        A task chat has no composer, and that is the section rather than a
        restriction on it: a schedule's chat is written into by the schedule and
        read by a person, so there is nothing for them to send. The controls
        that *do* apply — run it now, pause it, edit it — take the row's place.

        Suppressed by not including it, not by hiding it. `chat/_composer.html`
        is the only thing that posts a message, so its absence is the guarantee;
        a hidden one would still be a form anybody could post to.
      #}
      {% if chat and chat.kind == "task" %}
        {% include "schedules/_strip.html" %}
      {% else %}
        {% include "chat/_composer.html" %}
      {% endif %}
    {% endif %}
  </main>

  {# The panels, mirroring the sidebar opposite, in the order they sit on
     screen: the canvas nearest the conversation, then the terminal, then the
     inspector. Only ever one of them is open -- see the toggle group. #}
  {% if canvas_enabled %}
    {% include "chat/_canvas.html" %}
  {% endif %}
  {% if terminal_enabled %}
    {% include "chat/_terminal.html" %}
  {% endif %}
  {% if chat and user.is_admin %}
    {% include "chat/_inspector.html" %}
  {% endif %}
</div>
{% endblock %}

{% block scripts %}
{# Unconditional: every chat has a transcript, and this is what keeps a block
   somebody opened open across the swaps that arrive twelve times a second. #}
<script src="{{ url_for('static', path='js/steps.js') }}" defer></script>
{% if not chat and (canvas_enabled or terminal_enabled) %}
{# Only where there is no chat yet. It points both panels at a draft id for
   whatever the composer has selected, and does nothing at all once a chat
   exists -- which is every other page this block renders on. #}
<script src="{{ url_for('static', path='js/draft.js') }}" defer></script>
{% endif %}
{% if canvas_enabled %}
<script src="{{ url_for('static', path='js/canvas.js') }}" defer></script>
{% endif %}
{% if terminal_enabled %}
{# Only where it can be used. xterm is nearly three times everything else
   vendored, so a plain chat must never load it. #}
<script src="{{ url_for('static', path='vendor/xterm.js') }}" defer></script>
<script src="{{ url_for('static', path='vendor/xterm-addon-fit.js') }}" defer></script>
<script src="{{ url_for('static', path='js/terminal.js') }}" defer></script>
{% endif %}
{% endblock %}
