<!-- #region AgentChat.Component [C:4] [TYPE Component] [SEMANTICS agent,chat,conversation,ui,gradio] -->
<!-- @ingroup AgentChat -->
<!-- @BRIEF Self-contained agent chat UI — message bubbles, streaming indicator, input bar, sidebar toggle, all semantic tokens. -->
<!-- @RELATION BINDS_TO -> AgentChat.Model -->
<!-- @RELATION DEPENDS_ON -> $lib/ui/Icon -->
<!-- @RELATION DEPENDS_ON -> AgentChat.ConversationList -->
<!-- @UX_STATE idle -> Welcome screen with suggestion chips, input enabled. -->
<!-- @UX_STATE streaming -> Streaming bubble with cursor animation, input locked. -->
<!-- @UX_STATE awaiting_confirmation -> Confirmation card with confirm/deny buttons. -->
<!-- @UX_STATE error -> Error banner with retry button. -->
<!-- @UX_STATE disconnected -> Reconnect button, input locked. -->
<!-- @INVARIANT Messages from current conversation only. -->
<!-- @INVARIANT User messages right-aligned (bg-primary text-white), assistant left-aligned (bg-surface-card). -->
<script lang="ts">
  import { t } from "$lib/i18n/index.svelte.js";
  import Icon from "$lib/ui/Icon.svelte";
  import MarkdownRenderer from "$lib/components/assistant/MarkdownRenderer.svelte";
  import ConnectionIndicator from "$lib/components/assistant/ConnectionIndicator.svelte";
  import ToolCallCard from "$lib/components/assistant/ToolCallCard.svelte";
  import type { AgentChatModel } from "$lib/models/AgentChatModel.svelte.ts";

  // ── Props ──────────────────────────────────────────────────────
  let {
    model = $bindable(),
  }: {
    model: AgentChatModel;
  } = $props();

  // ── Local state ─────────────────────────────────────────────────
  let inputText = $state("");
  let messagesContainer: HTMLDivElement | undefined = $state();
  let fileInputRef: HTMLInputElement | undefined = $state();
  let pendingFile: File | null = $state(null);
  let sidebarOpen = $state(false);

  const ALLOWED_FILE_TYPES = [".pdf", ".xlsx", ".json", ".csv", ".txt", ".png", ".jpeg", ".jpg"];
  const MAX_FILE_SIZE_BYTES = 10 * 1024 * 1024;

  // ── Derived ─────────────────────────────────────────────────────
  let isInputLocked = $derived(
    model.isInputLocked || model.streamingState === "awaiting_confirmation"
  );

  let showWelcome = $derived(
    model.messages.length === 0 &&
    model.streamingState === "idle" &&
    !model.isLoadingHistory
  );

  // ── Track streaming completion → commit messages ──────────────
  let _prevStreamState = $state("");

  $effect(() => {
    const currentState = model.streamingState;

    // When streaming → idle/error: commit partial text as full assistant message
    if (_prevStreamState === "streaming" && (currentState === "idle" || currentState === "error")) {
      const partialText = model.partialText;
      const toolCalls = [...model.activeToolCalls];
      if (partialText || toolCalls.length > 0) {
        model.messages = [
          ...model.messages,
          {
            id: `msg-${Date.now()}`,
            conversation_id: model.currentConversationId || "",
            role: "assistant",
            text: partialText || (toolCalls.length > 0 ? "[Выполнены вызовы инструментов]" : ""),
            toolCalls: toolCalls,
            created_at: new Date().toISOString(),
          },
        ];
        model._persistMessages?.();
      }
      model.partialText = "";
      model.partialTokens = [];
      model.activeToolCalls = [];
    }

    _prevStreamState = currentState;
  });

  // ── Auto-scroll on new messages or streaming ────────────────────
  let _prevMsgLen = 0;
  $effect(() => {
    const len = model.messages.length;
    const isStreaming = model.streamingState === "streaming";
    if ((len > _prevMsgLen || isStreaming) && messagesContainer) {
      requestAnimationFrame(() => {
        messagesContainer!.scrollTo({ top: messagesContainer!.scrollHeight, behavior: "smooth" });
      });
    }
    _prevMsgLen = len;
  });

  // ── Actions ─────────────────────────────────────────────────────
  function handleSend() {
    const text = inputText.trim();
    const hasFile = pendingFile !== null;
    if ((!text && !hasFile) || isInputLocked) return;

    // Optimistically add user message (skip if no text and only file)
    if (text) {
      model.messages = [
        ...model.messages,
        {
          id: `user-${Date.now()}`,
          conversation_id: model.currentConversationId || "",
          role: "user",
          text: text,
          toolCalls: [],
          created_at: new Date().toISOString(),
        },
      ];
    }

    inputText = "";
    const filesToSend: File[] | undefined = pendingFile ? [pendingFile] : undefined;
    pendingFile = null;
    model.sendMessage(text, filesToSend);
  }

  function handleKeydown(event: KeyboardEvent) {
    if (event.key === "Enter" && !event.shiftKey) {
      event.preventDefault();
      handleSend();
    }
  }

  function handleFileSelect(event: Event) {
    const target = event.target as HTMLInputElement;
    const files = target.files;
    if (!files || files.length === 0) return;
    const file = files[0];
    const ext = "." + file.name.split(".").pop()?.toLowerCase();

    if (!ALLOWED_FILE_TYPES.includes(ext)) {
      target.value = "";
      return;
    }
    if (file.size > MAX_FILE_SIZE_BYTES) {
      target.value = "";
      return;
    }
    pendingFile = file;
    target.value = "";
  }

  function removeFile() {
    pendingFile = null;
    if (fileInputRef) fileInputRef.value = "";
  }

  function formatFileSize(bytes: number): string {
    if (bytes < 1024) return bytes + " B";
    if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + " KB";
    return (bytes / 1024 / 1024).toFixed(1) + " MB";
  }

  function handleSuggest(text: string) {
    inputText = text;
    handleSend();
  }

  // ── Suggestion chips ───────────────────────────────────────────
  const suggestions = [
    { label: "Покажи дашборды", text: "Покажи доступные дашборды" },
    { label: "Запустить миграцию", text: "Запусти миграцию" },
    { label: "Статус задач", text: "Проверь статус текущих задач" },
  ];

  // Toggle conversation sidebar
  function toggleSidebar() {
    sidebarOpen = !sidebarOpen;
  }
</script>

<!-- ── Template ─────────────────────────────────────────────────── -->
<div class="h-full flex flex-col min-h-0 bg-surface-page">
    <!-- ── Header ──────────────────────────────────────────────── -->
    <div class="flex items-center justify-between gap-2 border-b border-border bg-surface-card px-4 py-3 shrink-0">
      <div class="flex items-center gap-3">
        <button
          class="rounded-lg p-1.5 text-text-muted transition hover:bg-surface-muted hover:text-text md:hidden"
          onclick={toggleSidebar}
          aria-label="Toggle sidebar"
        >
          <Icon name="menu" size={20} />
        </button>
        <div class="flex items-center gap-2">
          <ConnectionIndicator
            color={model.connectionDotColor as "success" | "warning" | "destructive"}
            title={model.connectionState === "connected" ? "Agent connected" : "Agent disconnected"}
          />
          <h1 class="text-sm font-semibold text-text">
            {model.currentConversationId
              ? (model.conversations.find(c => c.id === model.currentConversationId)?.title || "Агент-чат")
              : "Агент-чат"}
          </h1>
        </div>
        {#if model.connectionState === "disconnected"}
          <button
            class="rounded-md border border-destructive-ring bg-destructive-light px-2 py-1 text-xs font-medium text-destructive transition hover:bg-destructive hover:text-white"
            onclick={() => model.retryConnection()}
          >
            Переподключиться
          </button>
        {/if}
      </div>

      <div class="flex items-center gap-2">
        {#if model.streamingState === "streaming"}
          <button
            class="rounded-lg border border-destructive-ring bg-destructive-light px-3 py-1.5 text-xs font-semibold text-destructive transition hover:bg-destructive hover:text-white"
            onclick={() => model.cancelGeneration()}
          >
            <div class="flex items-center gap-1.5">
              <svg class="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 24 24">
                <rect x="6" y="6" width="12" height="12" rx="1"/>
              </svg>
              {$t.assistant?.stop || "Стоп"}
            </div>
          </button>
        {/if}
        <button
          class="rounded-lg border border-border-strong bg-surface-card px-3 py-1.5 text-xs font-medium text-text transition hover:bg-surface-muted"
          onclick={() => model.createConversation()}
        >
          <div class="flex items-center gap-1.5">
            <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 5v14M5 12h14"/>
            </svg>
            {$t.assistant?.new || "Новый диалог"}
          </div>
        </button>
      </div>
    </div>

    <!-- ── Messages Area ───────────────────────────────────────── -->
    <div
      class="flex-1 overflow-y-auto px-4 py-6 space-y-4 scroll-smooth"
      bind:this={messagesContainer}
    >
      <!-- Welcome screen -->
      {#if showWelcome}
        <div class="flex flex-col items-center justify-center pt-12 pb-8">
          <!-- Avatar/Logo -->
          <div class="mb-6 flex h-16 w-16 items-center justify-center rounded-2xl bg-primary-light text-primary shadow-sm">
            <svg class="h-8 w-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z"/>
            </svg>
          </div>
          <h2 class="text-xl font-semibold text-text mb-2">{$t.assistant?.welcome_title || "Чем могу помочь?"}</h2>
          <p class="text-sm text-text-muted text-center max-w-md mb-8">
            {model.conversations.length === 0
              ? "Задайте вопрос или выберите одно из предложений ниже."
              : "Продолжите диалог или начните новый."}
          </p>
          <div class="flex flex-wrap gap-2 justify-center">
            {#each suggestions as s}
              <button
                class="rounded-xl border border-primary-ring bg-primary-light px-4 py-2.5 text-sm font-medium text-primary transition hover:bg-primary hover:text-white"
                onclick={() => handleSuggest(s.text)}
              >
                {s.label}
              </button>
            {/each}
          </div>
        </div>
      {/if}

      <!-- Message bubbles -->
      {#each model.messages as msg}
        <div class="flex {msg.role === 'user' ? 'justify-end' : 'justify-start'}">
          {#if msg.role === "user"}
            <!-- User bubble -->
            <div class="max-w-[75%] rounded-2xl bg-primary px-4 py-3 text-white shadow-sm">
              <p class="text-sm whitespace-pre-wrap leading-relaxed">{msg.text}</p>
            </div>
          {:else if msg.role === "tool"}
            <!-- Tool message: inline code block -->
            <div class="max-w-[80%] rounded-xl border border-border bg-surface-muted px-4 py-3">
              <div class="flex items-center gap-2 mb-1">
                <svg class="w-4 h-4 text-text-muted" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.066 2.573c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.573 1.066c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.066-2.573c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/>
                  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
                </svg>
                <span class="text-xs font-medium text-text-muted">Система</span>
              </div>
              <p class="text-sm text-text whitespace-pre-wrap leading-relaxed">{msg.text}</p>
            </div>
          {:else}
            <!-- Assistant bubble -->
            <div class="max-w-[80%] rounded-2xl border border-border bg-surface-card px-4 py-3 shadow-sm">
              <div class="flex items-center gap-2 mb-2">
                <div class="flex h-6 w-6 items-center justify-center rounded-full bg-primary-light text-primary">
                  <svg class="h-3.5 w-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/>
                  </svg>
                </div>
                <span class="text-xs font-semibold text-text-muted">
                  {$t.assistant?.assistant || "Ассистент"}
                </span>
                {#if msg.state}
                  <span class="rounded-md border border-warning bg-warning-light px-1.5 py-0.5 text-[10px] font-medium text-warning">
                    {$t.assistant?.states?.[msg.state] || msg.state}
                  </span>
                {/if}
              </div>
              <div class="text-sm text-text leading-relaxed">
                <MarkdownRenderer source={msg.text} />
              </div>

              <!-- Confirmation actions -->
              {#if msg.state === "needs_confirmation"}
                <div class="mt-3 flex flex-wrap gap-2">
                  {#each msg.actions || [] as action}
                    <button
                      class="rounded-lg border px-3 py-1.5 text-xs font-semibold transition
                        {action.type === 'confirm'
                          ? 'border-success bg-success-light text-success hover:bg-success'
                          : action.type === 'cancel'
                            ? 'border-destructive-ring bg-destructive-light text-destructive hover:bg-destructive hover:text-white'
                            : 'border-border-strong bg-surface-card text-text hover:bg-surface-muted'}"
                    >
                      {action.label}
                    </button>
                  {/each}
                </div>
              {/if}

              <!-- Task ID -->
              {#if msg.task_id}
                <div class="mt-2 flex items-center gap-1.5 text-xs text-text-muted">
                  <Icon name="activity" size={14} />
                  <span>Task: {msg.task_id}</span>
                </div>
              {/if}
            </div>
          {/if}
        </div>
      {/each}

      <!-- Loading history indicator -->
      {#if model.isLoadingHistory}
        <div class="flex justify-center">
          <div class="flex items-center gap-2 rounded-xl border border-border bg-surface-muted px-4 py-3 text-sm text-text-muted">
            <svg class="w-4 h-4 animate-spin" fill="none" viewBox="0 0 24 24">
              <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/>
              <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"/>
            </svg>
            <span>{$t.assistant?.loading_history || "Загрузка истории..."}</span>
          </div>
        </div>
      {/if}

      <!-- Streaming bubble -->
      {#if model.streamingState === "streaming"}
        <div class="flex justify-start">
          <div class="max-w-[80%] rounded-2xl border border-border bg-surface-card px-4 py-3 shadow-sm">
            <div class="flex items-center gap-2 mb-2">
              <div class="flex h-6 w-6 items-center justify-center rounded-full bg-primary-light text-primary">
                <svg class="h-3.5 w-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/>
                </svg>
              </div>
              <span class="text-xs font-semibold text-text-muted">
                {$t.assistant?.assistant || "Ассистент"}
              </span>
              <span class="inline-flex items-center gap-1 text-[10px] text-text-subtle">
                <span class="typing-dot"></span>
                <span class="typing-dot"></span>
                <span class="typing-dot"></span>
              </span>
            </div>

            {#if model.activeToolCalls.length > 0}
              <div class="space-y-2 mb-3">
                {#each model.activeToolCalls as tc}
                  <ToolCallCard tool={tc.tool} input={tc.input} output={tc.output} error={tc.error} status={tc.status} />
                {/each}
              </div>
            {/if}

            {#if model.partialText}
              <div class="text-sm text-text leading-relaxed">
                <MarkdownRenderer source={model.partialText} />
                <span class="inline-block w-1.5 h-4 bg-primary animate-pulse ml-0.5 align-text-bottom rounded-sm"></span>
              </div>
            {:else}
              <div class="flex items-center gap-1 text-sm text-text-muted">
                <span>{$t.assistant?.thinking || "Думаю"}</span>
                <span class="inline-flex items-center gap-0.5">
                  <span class="thinking-dot-sm"></span>
                  <span class="thinking-dot-sm"></span>
                  <span class="thinking-dot-sm"></span>
                </span>
              </div>
            {/if}
          </div>
        </div>
      {/if}

      <!-- Awaiting confirmation -->
      {#if model.streamingState === "awaiting_confirmation"}
        <div class="flex justify-start">
          <div class="max-w-[80%] rounded-2xl border border-warning bg-warning-light px-4 py-4 shadow-sm" role="alertdialog">
            <div class="flex items-center gap-2 mb-2">
              <svg class="w-5 h-5 text-warning shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z"/>
              </svg>
              <span class="text-sm font-semibold text-text">
                {$t.assistant?.confirmation_card_title || "Требуется подтверждение"}
              </span>
            </div>
            <p class="text-sm text-text-muted mb-3">{model.error || "Подтвердите выполнение действия"}</p>
            <div class="flex gap-2">
              <button
                class="rounded-lg border border-success bg-white px-4 py-2 text-xs font-semibold text-success transition hover:bg-success-light"
                onclick={() => model.resumeConfirm("confirm")}
              >
                {$t.assistant?.confirm || "Подтвердить"}
              </button>
              <button
                class="rounded-lg border border-destructive-ring bg-white px-4 py-2 text-xs font-semibold text-destructive transition hover:bg-destructive-light"
                onclick={() => model.resumeConfirm("deny")}
              >
                {$t.assistant?.cancel || "Отклонить"}
              </button>
            </div>
          </div>
        </div>
      {/if}

      <!-- Error -->
      {#if model.streamingState === "error" && model.error}
        <div class="flex justify-start">
          <div class="max-w-[80%] rounded-2xl border border-destructive-ring bg-destructive-light px-4 py-3" role="alert">
            <div class="flex items-center gap-2 mb-2">
              <svg class="w-5 h-5 text-destructive shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"/>
              </svg>
              <span class="text-sm font-semibold text-destructive">Ошибка</span>
            </div>
            <p class="text-sm text-destructive">{model.error}</p>
            <button
              class="mt-3 rounded-lg border border-destructive-ring bg-white px-3 py-1.5 text-xs font-semibold text-destructive transition hover:bg-destructive-light"
              onclick={() => model.sendMessage(inputText)}
            >
              {$t.assistant?.retry || "Повторить"}
            </button>
          </div>
        </div>
      {/if}

      <!-- Bottom spacer for scroll -->
      <div class="h-2"></div>
    </div>

    <!-- ── Input Bar ────────────────────────────────────────────── -->
    <div class="border-t border-border bg-surface-card px-4 py-3 shrink-0">
      {#if pendingFile}
        <div class="flex items-center gap-2 mb-2">
          <div class="flex items-center gap-1.5 rounded-lg border border-border bg-surface-muted px-3 py-1.5 text-xs text-text-muted">
            <svg class="w-3.5 h-3.5 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13"/>
            </svg>
            <span class="truncate max-w-[200px]">{pendingFile.name}</span>
            <span class="text-text-subtle">({formatFileSize(pendingFile.size)})</span>
            <button class="ml-1 rounded p-0.5 text-text-subtle hover:text-destructive" onclick={removeFile}>
              <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
              </svg>
            </button>
          </div>
        </div>
      {/if}

      <div class="flex items-end gap-2">
        <div class="relative flex-1">
          <textarea
            bind:value={inputText}
            rows="1"
            placeholder={$t.assistant?.input_placeholder || "Введите команду..."}
            class="min-h-[44px] max-h-[120px] w-full resize-none rounded-xl border border-border-strong bg-surface-page px-4 py-3 pr-12 text-sm text-text outline-none transition placeholder:text-text-subtle focus:border-primary-ring focus:ring-2 focus:ring-primary-light disabled:cursor-not-allowed disabled:opacity-50"
            onkeydown={handleKeydown}
            disabled={isInputLocked}
            oninput={() => {
              // Auto-resize textarea
              const el = document.activeElement as HTMLTextAreaElement;
              if (el) {
                el.style.height = "auto";
                el.style.height = Math.min(el.scrollHeight, 120) + "px";
              }
            }}
          ></textarea>
          <button
            class="absolute right-2 bottom-2 rounded-lg p-1.5 text-text-subtle transition hover:text-text-muted disabled:opacity-40"
            onclick={() => fileInputRef?.click()}
            disabled={isInputLocked}
            aria-label="Прикрепить файл"
            title="Прикрепить файл"
          >
            <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13"/>
            </svg>
          </button>
          <input
            type="file"
            accept=".pdf,.xlsx,.json,.csv,.txt,.png,.jpeg,.jpg"
            class="hidden"
            bind:this={fileInputRef}
            onchange={handleFileSelect}
          />
        </div>

        {#if model.streamingState === "streaming"}
          <button
            class="flex h-[44px] w-[44px] items-center justify-center rounded-xl bg-destructive text-white transition hover:bg-destructive-hover"
            onclick={() => model.cancelGeneration()}
            aria-label="Остановить"
          >
            <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
              <rect x="6" y="6" width="12" height="12" rx="1.5"/>
            </svg>
          </button>
        {:else}
          <button
            class="flex h-[44px] w-[44px] items-center justify-center rounded-xl bg-primary text-white transition hover:bg-primary-hover disabled:cursor-not-allowed disabled:opacity-40"
            onclick={handleSend}
            disabled={!inputText.trim() && !pendingFile || isInputLocked}
            aria-label="Отправить"
          >
            <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 12h14M12 5l7 7-7 7"/>
            </svg>
          </button>
        {/if}
      </div>
    </div>
</div>

<style>
  .typing-dot {
    display: inline-block;
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: currentColor;
    opacity: 0.4;
    animation: typing-bounce 1.2s infinite ease-in-out;
  }
  .typing-dot:nth-child(2) {
    animation-delay: 0.16s;
  }
  .typing-dot:nth-child(3) {
    animation-delay: 0.32s;
  }
  @keyframes typing-bounce {
    0%, 80%, 100% {
      transform: scale(0.6);
      opacity: 0.4;
    }
    40% {
      transform: scale(1);
      opacity: 1;
    }
  }
  .thinking-dot-sm {
    display: inline-block;
    width: 4px;
    height: 4px;
    border-radius: 50%;
    background: currentColor;
    opacity: 0.5;
  }
</style>
<!-- #endregion AgentChat.Component -->
