TailAI
A chat dashboard for your AI application
<!--
TailAI - A chat dashboard for your AI application
Created by John Champ
Founder of Pixelcave -> https://pixelcave.com
Building Tailkit -> https://tailkit.com
Let's connect on X -> https://x.com/pixelcave_john
on Bluesky -> https://bsky.app/profile/pixelcave-john.bsky.social
-->
<div x-data="chatApp">
<!-- Page Container -->
<div id="page-container" class="mx-auto flex min-h-screen w-full min-w-[320px] flex-col bg-zinc-100 lg:ps-64">
<!-- Page Sidebar -->
<nav id="page-sidebar"
class="fixed start-0 top-0 bottom-0 z-50 flex h-full w-80 flex-col bg-zinc-100 transition-transform duration-500 ease-out lg:w-64 lg:ltr:translate-x-0 lg:rtl:translate-x-0"
x-bind:class="{
'ltr:-translate-x-full rtl:translate-x-full': !mobileSidebarOpen,
'translate-x-0 border-e border-zinc-200 shadow-lg lg:shadow-none lg:border-none': mobileSidebarOpen,
}" aria-label="Main Sidebar Navigation" x-cloak>
<!-- Sidebar Header -->
<div class="flex h-14 w-full flex-none items-center justify-between ps-3 pe-3">
<!-- Brand -->
<a href="javascript:void(0)"
class="flex h-9 flex-none items-center justify-center rounded-lg px-2 text-zinc-700 hover:bg-zinc-200/75 hover:text-zinc-950">
<span>Tail</span><span class="inline-block font-bold text-zinc-800">AI</span>
</a>
<!-- END Brand -->
<!-- Close Sidebar on Mobile -->
<div class="lg:hidden">
<button type="button"
class="flex size-9 flex-none items-center justify-center rounded-lg text-zinc-400 hover:bg-zinc-200/75 hover:text-zinc-600"
x-on:click="mobileSidebarOpen = false" aria-label="Close Sidebar">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="lucide lucide-x inline-block size-5">
<path d="M18 6 6 18"></path>
<path d="m6 6 12 12"></path>
</svg>
</button>
</div>
<!-- END Close Sidebar on Mobile -->
</div>
<!-- END Sidebar Header -->
<!-- Main Navigation -->
<div class="flex grow flex-col gap-1 overflow-y-auto px-3 pb-3">
<!-- New Chat Button -->
<a href="javascript:void(0)"
class="group flex items-center gap-2 rounded-lg px-2.5 py-1.5 text-sm font-medium text-zinc-600 hover:bg-zinc-200/75 hover:text-zinc-900 active:bg-zinc-200/50 active:text-zinc-950">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-square-pen inline-block size-4">
<path d="M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
<path
d="M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z">
</path>
</svg>
<span>New chat</span>
</a>
<!-- END New Chat Button -->
<!-- Search -->
<a href="javascript:void(0)"
class="group flex items-center gap-2 rounded-lg px-2.5 py-1.5 text-sm font-medium text-zinc-600 hover:bg-zinc-200/75 hover:text-zinc-900 active:bg-zinc-200/50 active:text-zinc-950">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-search inline-block size-4">
<circle cx="11" cy="11" r="8"></circle>
<path d="m21 21-4.3-4.3"></path>
</svg>
<span>Search chats</span>
</a>
<!-- END Search -->
<!-- Images -->
<a href="javascript:void(0)"
class="group flex items-center gap-2 rounded-lg px-2.5 py-1.5 text-sm font-medium text-zinc-600 hover:bg-zinc-200/75 hover:text-zinc-900 active:bg-zinc-200/50 active:text-zinc-950">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-images inline-block size-4">
<path d="M18 22H4a2 2 0 0 1-2-2V6"></path>
<path d="m22 13-1.296-1.296a2.41 2.41 0 0 0-3.408 0L11 18"></path>
<circle cx="12" cy="8" r="2"></circle>
<rect width="16" height="16" x="6" y="2" rx="2"></rect>
</svg>
<span>Images</span>
</a>
<!-- END Images -->
<!-- Apps -->
<a href="javascript:void(0)"
class="group flex items-center gap-2 rounded-lg px-2.5 py-1.5 text-sm font-medium text-zinc-600 hover:bg-zinc-200/75 hover:text-zinc-900 active:bg-zinc-200/50 active:text-zinc-950">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-grid-2x2 inline-block size-4">
<path d="M12 3v18"></path>
<path d="M3 12h18"></path>
<rect x="3" y="3" width="18" height="18" rx="2"></rect>
</svg>
<span>Apps</span>
</a>
<!-- END Apps -->
<!-- Divider -->
<hr class="mx-2.5 my-2 border-zinc-200" />
<!-- GPTs Section -->
<h3 class="mb-1 px-3 py-1.5 text-xs font-medium tracking-wide text-zinc-500 uppercase">
GPTs
</h3>
<!-- Write For Me -->
<a href="javascript:void(0)"
class="group flex items-center gap-2 rounded-lg px-2.5 py-1.5 text-sm font-medium text-zinc-600 hover:bg-zinc-200/75 hover:text-zinc-900 active:bg-zinc-200/50 active:text-zinc-950">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round"
class="lucide lucide-pencil inline-block size-4 text-rose-500">
<path
d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z">
</path>
<path d="m15 5 4 4"></path>
</svg>
<span>Write For Me</span>
</a>
<!-- END Write For Me -->
<!-- Explore GPTs -->
<a href="javascript:void(0)"
class="group flex items-center gap-2 rounded-lg px-2.5 py-1.5 text-sm font-medium text-zinc-600 hover:bg-zinc-200/75 hover:text-zinc-900 active:bg-zinc-200/50 active:text-zinc-950">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-box inline-block size-4">
<path
d="M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z">
</path>
<path d="m3.3 7 8.7 5 8.7-5"></path>
<path d="M12 22V12"></path>
</svg>
<span class="text-sm">Explore GPTs</span>
</a>
<!-- END Explore GPTs -->
<!-- END GPTs Section -->
<!-- Divider -->
<hr class="mx-2.5 my-2 border-zinc-200" />
<!-- Projects Section -->
<h3 class="mb-1 px-3 py-1.5 text-xs font-medium tracking-wide text-zinc-500 uppercase">
Projects
</h3>
<!-- New project -->
<a href="javascript:void(0)"
class="group flex items-center gap-2 rounded-lg px-2.5 py-1.5 text-sm font-medium text-zinc-600 hover:bg-zinc-200/75 hover:text-zinc-900 active:bg-zinc-200/50 active:text-zinc-950">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-folder-plus inline-block size-4">
<path d="M12 10v6"></path>
<path d="M9 13h6"></path>
<path
d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z">
</path>
</svg>
<span class="text-sm">New project</span>
</a>
<!-- END New project -->
<!-- Tools -->
<a href="javascript:void(0)"
class="group flex items-center gap-2 rounded-lg px-2.5 py-1.5 text-sm font-medium text-zinc-600 hover:bg-zinc-200/75 hover:text-zinc-900 active:bg-zinc-200/50 active:text-zinc-950">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round"
class="lucide lucide-wrench inline-block size-4 text-amber-500">
<path
d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z">
</path>
</svg>
<span class="text-sm">Tools</span>
</a>
<!-- END Tools -->
<!-- Work -->
<a href="javascript:void(0)"
class="group flex items-center gap-2 rounded-lg px-2.5 py-1.5 text-sm font-medium text-zinc-600 hover:bg-zinc-200/75 hover:text-zinc-900 active:bg-zinc-200/50 active:text-zinc-950">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round"
class="lucide lucide-folder-heart inline-block size-4 text-rose-500">
<path
d="M11 20H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H20a2 2 0 0 1 2 2v1.5">
</path>
<path
d="M13.9 17.45c-1.2-1.2-1.14-2.8-.2-3.73a2.43 2.43 0 0 1 3.44 0l.36.34.34-.34a2.43 2.43 0 0 1 3.45-.01c.95.95 1 2.53-.2 3.74L17.5 21Z">
</path>
</svg>
<span class="text-sm">Work</span>
</a>
<!-- END Work -->
<!-- Freelancing -->
<a href="javascript:void(0)"
class="group flex items-center gap-2 rounded-lg px-2.5 py-1.5 text-sm font-medium text-zinc-600 hover:bg-zinc-200/75 hover:text-zinc-900 active:bg-zinc-200/50 active:text-zinc-950">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round"
class="lucide lucide-briefcase-business inline-block size-4 text-blue-500">
<path d="M12 12h.01"></path>
<path d="M16 6V4a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v2"></path>
<path d="M22 13a18.15 18.15 0 0 1-20 0"></path>
<rect width="20" height="14" x="2" y="6" rx="2"></rect>
</svg>
<span class="text-sm">Freelancing</span>
</a>
<!-- END Freelancing -->
<!-- See more -->
<a href="javascript:void(0)"
class="group flex items-center gap-2 rounded-lg px-2.5 py-1.5 text-sm font-medium text-zinc-500 hover:bg-zinc-200/75 hover:text-zinc-900 active:bg-zinc-200/50 active:text-zinc-950">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-ellipsis inline-block size-4">
<circle cx="12" cy="12" r="1"></circle>
<circle cx="19" cy="12" r="1"></circle>
<circle cx="5" cy="12" r="1"></circle>
</svg>
<span class="text-sm">See more</span>
</a>
<!-- END See more -->
<!-- END Projects Section -->
<!-- Divider -->
<hr class="mx-2.5 my-2 border-zinc-200" />
<!-- Your Chats Section -->
<h3 class="mb-1 px-3 py-1.5 text-xs font-medium tracking-wide text-zinc-500 uppercase">
Your chats
</h3>
<a href="javascript:void(0)"
class="group flex items-center gap-2 rounded-lg px-2.5 py-1.5 text-sm font-medium text-zinc-500 hover:bg-zinc-200/75 hover:text-zinc-900 active:bg-zinc-200/50 active:text-zinc-950">
<span class="truncate">Ideas exploration</span>
</a>
<a href="javascript:void(0)"
class="group flex items-center gap-2 rounded-lg px-2.5 py-1.5 text-sm font-medium text-zinc-500 hover:bg-zinc-200/75 hover:text-zinc-900 active:bg-zinc-200/50 active:text-zinc-950">
<span class="truncate">Traveling to SEA</span>
</a>
<a href="javascript:void(0)"
class="group flex items-center gap-2 rounded-lg px-2.5 py-1.5 text-sm font-medium text-zinc-500 hover:bg-zinc-200/75 hover:text-zinc-900 active:bg-zinc-200/50 active:text-zinc-950">
<span class="truncate">Starting a online business</span>
</a>
<!-- END Your Chats Section -->
</div>
<!-- END Main Navigation -->
<!-- User Section -->
<div class="flex h-20 w-full flex-none items-center gap-2 border-t border-zinc-200 p-3">
<a href="javascript:void(0)"
class="group flex grow items-center gap-3 rounded-xl px-3 py-2 font-medium text-zinc-700 hover:bg-white hover:shadow-xs hover:shadow-zinc-300/50 active:opacity-75">
<div
class="flex size-8 flex-none items-center justify-center rounded-full bg-indigo-100 text-sm font-bold text-indigo-700">
JD
</div>
<div class="flex max-w-15 flex-col">
<span class="truncate text-sm font-semibold">John</span>
<span class="truncate text-xs text-zinc-500">Free Plan</span>
</div>
</a>
<a href="javascript:void(0)"
class="flex flex-none items-center gap-1.5 rounded-lg border border-indigo-200 bg-indigo-50 px-1.5 py-1 text-xs font-semibold text-indigo-700 hover:border-indigo-300 hover:bg-indigo-100 active:opacity-75">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-zap inline-block size-4 opacity-75">
<path
d="M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z">
</path>
</svg>
<span>Upgrade</span>
</a>
</div>
<!-- END User Section -->
</nav>
<!-- END Page Sidebar -->
<!-- Page Header -->
<header id="page-header"
class="fixed start-0 end-0 top-0 z-30 flex h-14 flex-none items-center bg-white/95 shadow-xs backdrop-blur-xs lg:hidden">
<div class="container mx-auto flex justify-between px-4 lg:px-8 xl:max-w-7xl">
<!-- Left Section -->
<div class="flex items-center gap-2">
<!-- Toggle Sidebar on Mobile -->
<button type="button"
class="flex size-9 flex-none items-center justify-center rounded-lg text-zinc-400 hover:bg-zinc-100 hover:text-zinc-600"
x-on:click="mobileSidebarOpen = true" aria-label="Toggle Sidebar">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="lucide lucide-panel-left inline-block size-5">
<rect width="18" height="18" x="3" y="3" rx="2"></rect>
<path d="M9 3v18"></path>
</svg>
</button>
<!-- END Toggle Sidebar on Mobile -->
</div>
<!-- END Left Section -->
<!-- Middle Section -->
<div class="flex items-center gap-2">
<!-- Brand -->
<a href="javascript:void(0)"
class="flex h-9 flex-none items-center justify-center rounded-lg px-2 text-zinc-700 hover:bg-zinc-100 hover:text-zinc-950">
<span>Tail</span><span class="inline-block font-bold text-zinc-800">AI</span>
</a>
<!-- END Brand -->
</div>
<!-- END Middle Section -->
<!-- Right Section -->
<div class="flex items-center gap-2">
<!-- Settings -->
<a href="javascript:void(0)"
class="flex size-9 flex-none items-center justify-center rounded-lg text-zinc-400 hover:bg-zinc-100 hover:text-zinc-600"
aria-label="Settings">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="lucide lucide-circle-user inline-block size-5">
<circle cx="12" cy="12" r="10"></circle>
<circle cx="12" cy="10" r="3"></circle>
<path d="M7 20.662V19a2 2 0 0 1 2-2h6a2 2 0 0 1 2 2v1.662"></path>
</svg>
</a>
<!-- END Settings -->
</div>
<!-- END Right Section -->
</div>
</header>
<!-- END Page Header -->
<!-- Page Content -->
<main id="page-content"
class="flex max-w-full flex-auto flex-col bg-white pt-14 lg:border-s lg:border-zinc-200 lg:pt-0">
<!-- Chat Container -->
<div class="flex min-h-[calc(100dvh-3.5rem)] flex-col lg:min-h-dvh"
x-bind:class="{ 'justify-center': messages.length === 0 }">
<!-- Empty State / Welcome (shown when no messages) -->
<div x-show="messages.length === 0" class="flex grow flex-col items-center justify-center px-4 py-8">
<div class="w-full max-w-2xl text-center">
<h1 class="mb-8 text-2xl font-bold text-zinc-800 md:text-3xl">
What are you working on?
</h1>
<!-- Chat Input (Welcome State) -->
<div
class="relative rounded-full border border-zinc-200 bg-white shadow-lg shadow-zinc-200/50 transition-shadow focus-within:border-zinc-300 focus-within:shadow-xl">
<div class="flex items-center gap-2 px-4 py-3">
<!-- Attachment Button -->
<button type="button"
class="flex size-9 flex-none items-center justify-center rounded-lg text-zinc-400 hover:bg-zinc-100 hover:text-zinc-600"
aria-label="Attach file">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="lucide lucide-plus inline-block size-6">
<path d="M5 12h14"></path>
<path d="M12 5v14"></path>
</svg>
</button>
<!-- END Attachment Button -->
<!-- Text Input -->
<input type="text" x-model="messageInput" x-on:keydown.enter="sendMessage()"
class="min-w-0 grow border-0 bg-transparent text-zinc-800 placeholder-zinc-400 focus:ring-0 focus:outline-none"
placeholder="Ask anything" />
<!-- END Text Input -->
<!-- Voice Button -->
<button type="button"
class="flex size-9 flex-none items-center justify-center rounded-lg text-zinc-400 hover:bg-zinc-100 hover:text-zinc-600"
aria-label="Voice input">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="lucide lucide-mic inline-block size-6">
<path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z"></path>
<path d="M19 10v2a7 7 0 0 1-14 0v-2"></path>
<line x1="12" x2="12" y1="19" y2="22"></line>
</svg>
</button>
<!-- END Voice Button -->
<!-- Send Button -->
<button type="button" x-on:click="sendMessage()"
class="flex size-9 flex-none items-center justify-center rounded-full bg-zinc-800 text-white transition hover:bg-zinc-700"
x-bind:class="{
'opacity-50 cursor-not-allowed': !messageInput.trim(),
'hover:bg-zinc-700': messageInput.trim()
}" x-bind:disabled="!messageInput.trim()" aria-label="Send message">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="lucide lucide-arrow-up inline-block size-6">
<path d="m5 12 7-7 7 7"></path>
<path d="M12 19V5"></path>
</svg>
</button>
<!-- END Send Button -->
</div>
</div>
<!-- END Chat Input -->
<!-- Suggestion Chips -->
<div class="mt-6 flex flex-wrap items-center justify-center gap-2">
<button type="button" x-on:click="messageInput = 'Help me write some code'; sendMessage()"
class="inline-flex items-center gap-2 rounded-full border border-zinc-200 bg-white px-4 py-2 text-sm font-medium text-zinc-600 shadow-xs hover:border-zinc-300 hover:bg-zinc-50">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="lucide lucide-code inline-block size-5 text-indigo-500">
<polyline points="16 18 22 12 16 6"></polyline>
<polyline points="8 6 2 12 8 18"></polyline>
</svg>
<span>Write code</span>
</button>
<button type="button" x-on:click="messageInput = 'Help me brainstorm some ideas'; sendMessage()"
class="inline-flex items-center gap-2 rounded-full border border-zinc-200 bg-white px-4 py-2 text-sm font-medium text-zinc-600 shadow-xs hover:border-zinc-300 hover:bg-zinc-50">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="lucide lucide-lightbulb inline-block size-5 text-amber-500">
<path
d="M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5">
</path>
<path d="M9 18h6"></path>
<path d="M10 22h4"></path>
</svg>
<span>Brainstorm ideas</span>
</button>
<button type="button" x-on:click="messageInput = 'Can you help me?'; sendMessage()"
class="inline-flex items-center gap-2 rounded-full border border-zinc-200 bg-white px-4 py-2 text-sm font-medium text-zinc-600 shadow-xs hover:border-zinc-300 hover:bg-zinc-50">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="lucide lucide-file-text inline-block size-5 text-blue-500">
<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"></path>
<path d="M14 2v4a2 2 0 0 0 2 2h4"></path>
<path d="M10 9H8"></path>
<path d="M16 13H8"></path>
<path d="M16 17H8"></path>
</svg>
<span>Get help</span>
</button>
<button type="button" x-on:click="messageInput = 'Hello!'; sendMessage()"
class="inline-flex items-center gap-2 rounded-full border border-zinc-200 bg-white px-4 py-2 text-sm font-medium text-zinc-600 shadow-xs hover:border-zinc-300 hover:bg-zinc-50">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="lucide lucide-hand inline-block size-5 text-emerald-500">
<path d="M18 11V6a2 2 0 0 0-2-2a2 2 0 0 0-2 2"></path>
<path d="M14 10V4a2 2 0 0 0-2-2a2 2 0 0 0-2 2v2"></path>
<path d="M10 10.5V6a2 2 0 0 0-2-2a2 2 0 0 0-2 2v8"></path>
<path
d="M18 8a2 2 0 1 1 4 0v6a8 8 0 0 1-8 8h-2c-2.8 0-4.5-.86-5.99-2.34l-3.6-3.6a2 2 0 0 1 2.83-2.82L7 15">
</path>
</svg>
<span>Say hello</span>
</button>
</div>
<!-- END Suggestion Chips -->
</div>
</div>
<!-- END Empty State -->
<!-- Chat Messages (shown when there are messages) -->
<div x-show="messages.length > 0" x-cloak class="flex grow flex-col">
<!-- Messages Container -->
<div id="chat-messages" class="grow px-4 pt-6 pb-28">
<div class="mx-auto flex max-w-3xl flex-col gap-6">
<!-- Message Loop -->
<template x-for="(message, index) in messages" :key="index">
<div x-data="{ shown: false }" x-init="$nextTick(() => shown = true)" x-show="shown"
x-transition:enter="transition ease-out duration-200"
x-transition:enter-start="opacity-0 translate-y-2" x-transition:enter-end="opacity-100 translate-y-0">
<!-- User Message -->
<div x-show="message.role === 'user'" class="flex justify-end">
<div class="max-w-[85%] rounded-2xl rounded-br-md bg-zinc-800 px-4 py-3 text-white md:max-w-[70%]">
<p x-text="message.content" class="text-sm leading-relaxed whitespace-pre-wrap">
</p>
</div>
</div>
<!-- END User Message -->
<!-- Assistant Message -->
<div x-show="message.role === 'assistant'" class="flex gap-3">
<!-- AI Avatar -->
<div
class="flex size-8 flex-none items-center justify-center rounded-full border border-indigo-500 bg-indigo-400 text-white">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="lucide lucide-wand-sparkles inline-block size-4">
<path
d="m21.64 3.64-1.28-1.28a1.21 1.21 0 0 0-1.72 0L2.36 18.64a1.21 1.21 0 0 0 0 1.72l1.28 1.28a1.2 1.2 0 0 0 1.72 0L21.64 5.36a1.2 1.2 0 0 0 0-1.72">
</path>
<path d="m14 7 3 3"></path>
<path d="M5 6v4"></path>
<path d="M19 14v4"></path>
<path d="M10 2v2"></path>
<path d="M7 8H3"></path>
<path d="M21 16h-4"></path>
<path d="M11 3H9"></path>
</svg>
</div>
<!-- END AI Avatar -->
<!-- Message Content -->
<div class="max-w-[85%] md:max-w-[70%]">
<p x-html="formatMessage(message.content)" class="text-sm leading-relaxed text-zinc-700">
</p>
<!-- Message Actions -->
<div class="mt-2 flex items-center gap-1">
<button type="button"
class="flex size-7 items-center justify-center rounded-lg text-zinc-400 hover:bg-zinc-100 hover:text-zinc-600"
aria-label="Copy">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="lucide lucide-copy inline-block size-4">
<rect width="14" height="14" x="8" y="8" rx="2" ry="2"></rect>
<path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"></path>
</svg>
</button>
<button type="button"
class="flex size-7 items-center justify-center rounded-lg text-zinc-400 hover:bg-zinc-100 hover:text-zinc-600"
aria-label="Like">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="lucide lucide-thumbs-up inline-block size-4">
<path d="M7 10v12"></path>
<path
d="M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2a3.13 3.13 0 0 1 3 3.88Z">
</path>
</svg>
</button>
<button type="button"
class="flex size-7 items-center justify-center rounded-lg text-zinc-400 hover:bg-zinc-100 hover:text-zinc-600"
aria-label="Dislike">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="lucide lucide-thumbs-down inline-block size-4">
<path d="M17 14V2"></path>
<path
d="M9 18.12 10 14H4.17a2 2 0 0 1-1.92-2.56l2.33-8A2 2 0 0 1 6.5 2H20a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-2.76a2 2 0 0 0-1.79 1.11L12 22a3.13 3.13 0 0 1-3-3.88Z">
</path>
</svg>
</button>
</div>
<!-- END Message Actions -->
</div>
<!-- END Message Content -->
</div>
<!-- END Assistant Message -->
</div>
</template>
<!-- END Message Loop -->
<!-- Typing Indicator -->
<div x-show="isTyping" class="flex gap-3">
<!-- AI Avatar -->
<div
class="flex size-8 flex-none items-center justify-center rounded-full border border-indigo-500 bg-indigo-400 text-white">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="lucide lucide-wand-sparkles inline-block size-4">
<path
d="m21.64 3.64-1.28-1.28a1.21 1.21 0 0 0-1.72 0L2.36 18.64a1.21 1.21 0 0 0 0 1.72l1.28 1.28a1.2 1.2 0 0 0 1.72 0L21.64 5.36a1.2 1.2 0 0 0 0-1.72">
</path>
<path d="m14 7 3 3"></path>
<path d="M5 6v4"></path>
<path d="M19 14v4"></path>
<path d="M10 2v2"></path>
<path d="M7 8H3"></path>
<path d="M21 16h-4"></path>
<path d="M11 3H9"></path>
</svg>
</div>
<!-- END AI Avatar -->
<!-- Typing Animation -->
<div class="flex items-center gap-1 py-3">
<span class="size-2 animate-bounce rounded-full bg-zinc-400"></span>
<span class="animate-delay-150 size-2 animate-bounce rounded-full bg-zinc-400"
style="animation-delay: 150ms"></span>
<span class="size-2 animate-bounce rounded-full bg-zinc-400" style="animation-delay: 300ms"></span>
</div>
<!-- END Typing Animation -->
</div>
<!-- END Typing Indicator -->
</div>
</div>
<!-- END Messages Container -->
<!-- Chat Input (Active Chat State) -->
<div
class="fixed right-0 bottom-0 left-0 flex h-28 items-end bg-linear-to-b from-transparent to-white px-4 pb-4 lg:start-64 lg:border-s lg:border-zinc-200">
<div class="mx-auto w-full max-w-3xl">
<div
class="relative rounded-full border border-zinc-200 bg-zinc-50 shadow-sm transition-shadow focus-within:border-zinc-300 focus-within:bg-white focus-within:shadow-md">
<div class="flex items-center gap-2 px-4 py-2">
<!-- Attachment Button -->
<button type="button"
class="flex size-9 flex-none items-center justify-center rounded-lg text-zinc-400 hover:bg-zinc-200 hover:text-zinc-600"
aria-label="Attach file">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="lucide lucide-plus inline-block size-5">
<path d="M5 12h14"></path>
<path d="M12 5v14"></path>
</svg>
</button>
<!-- END Attachment Button -->
<!-- Text Input -->
<input type="text" x-ref="chatInput" x-model="messageInput" x-on:keydown.enter="sendMessage()"
class="min-w-0 grow border-0 bg-transparent text-zinc-800 placeholder-zinc-400 focus:ring-0 focus:outline-none"
placeholder="Message..." />
<!-- END Text Input -->
<!-- Voice Button -->
<button type="button"
class="flex size-9 flex-none items-center justify-center rounded-lg text-zinc-400 hover:bg-zinc-200 hover:text-zinc-600"
aria-label="Voice input">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="lucide lucide-mic inline-block size-5">
<path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z"></path>
<path d="M19 10v2a7 7 0 0 1-14 0v-2"></path>
<line x1="12" x2="12" y1="19" y2="22"></line>
</svg>
</button>
<!-- END Voice Button -->
<!-- Send Button -->
<button type="button" x-on:click="sendMessage()"
class="flex size-9 flex-none items-center justify-center rounded-full bg-zinc-800 text-white transition hover:bg-zinc-700"
x-bind:class="{
'opacity-50 cursor-not-allowed': !messageInput.trim(),
'hover:bg-zinc-700': messageInput.trim()
}" x-bind:disabled="!messageInput.trim()" aria-label="Send message">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="lucide lucide-arrow-up inline-block size-5">
<path d="m5 12 7-7 7 7"></path>
<path d="M12 19V5"></path>
</svg>
</button>
<!-- END Send Button -->
</div>
</div>
</div>
</div>
<!-- END Chat Input -->
</div>
<!-- END Chat Messages -->
</div>
<!-- END Chat Container -->
</main>
<!-- END Page Content -->
</div>
<!-- END Page Container -->
</div>
<!-- Custom functionality with Alpine.js -->
<script>
document.addEventListener("alpine:init", () => {
window.Alpine.data("chatApp", () => ({
mobileSidebarOpen: false,
messageInput: "",
messages: [],
isTyping: false,
/* Send message to the assistant */
sendMessage() {
if (this.messageInput.trim()) {
this.messages.push({
role: "user",
content: this.messageInput.trim(),
});
const userMessage = this.messageInput.trim();
this.messageInput = "";
this.isTyping = true;
// Auto-focus the input after sending
this.$nextTick(() => {
if (this.$refs.chatInput) this.$refs.chatInput.focus();
});
// Scroll to bottom after user message
this.scrollToBottom();
setTimeout(() => {
this.isTyping = false;
this.messages.push({
role: "assistant",
content: this.generateResponse(userMessage),
});
// Scroll to bottom after assistant message
this.scrollToBottom();
}, 1500);
}
},
/* Scroll to bottom of the page */
scrollToBottom() {
this.$nextTick(() => {
setTimeout(() => {
window.scrollTo({
top: document.body.scrollHeight,
behavior: "smooth",
});
}, 50);
});
},
/* Generate response based on the user message */
generateResponse(userMessage) {
const msg = userMessage.toLowerCase();
if (msg.includes("hello") || msg.includes("hi")) {
return "Hello! I'm TailAI, your AI assistant. How can I help you today? I can help with coding, writing, brainstorming ideas, answering questions, and much more.";
} else if (msg.includes("code") || msg.includes("programming")) {
return "I'd be happy to help with coding! I can assist with:\n\n• Writing new code in various languages\n• Debugging existing code\n• Explaining code concepts\n• Code review and optimization\n• Architecture recommendations\n\nWhat programming task would you like help with?";
} else if (msg.includes("help")) {
return "Of course! I'm here to help. I can assist you with:\n\n1. **Writing & Editing** - Articles, emails, creative writing\n2. **Coding** - Debug, write, or explain code\n3. **Analysis** - Data interpretation, research summaries\n4. **Brainstorming** - Generate ideas for projects\n5. **Learning** - Explain complex topics simply\n\nWhat would you like to explore?";
} else {
return "That's a great question! Let me help you with that.\n\nBased on what you've shared, here are my thoughts:\n\n1. **First**, I'd recommend breaking this down into smaller, manageable parts.\n2. **Second**, consider the key objectives you want to achieve.\n3. **Third**, let's work through any specific challenges together.\n\nWould you like me to elaborate on any of these points, or is there a specific aspect you'd like to dive deeper into?";
}
},
/* Format message (simplified formatting for demonstration) */
formatMessage(content) {
// Security: Escape ALL HTML entities first to prevent XSS
let safe = content
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
// Only apply formatting AFTER escaping (safe transforms)
safe = safe
.replace(/\n/g, "<br>")
.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>")
.replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, "<em>$1</em>")
.replace(
/`([^`]+)`/g,
"<code class='bg-zinc-100 px-1.5 py-0.5 rounded text-sm font-mono'>$1</code>",
);
return safe;
},
}));
});
</script>
Unlock an exclusive dashboard and get early access to all the new ones!
Join our pixelcave newsletter today, and we’ll send you a custom UI Design Kit which includes an exclusive dashboard template and many other free Tailwind CSS templates, just for you! Also, you will be the first to get any upcoming dashboards.