<hlm-tabs tab="preview" class="mx-auto flex h-[calc(100vh-7.5rem)] max-w-6xl flex-col">
<hlm-tabs-list variant="line" class="shrink-0">
<button hlmTabsTrigger="preview">Preview</button>
<button hlmTabsTrigger="code">Code</button>
</hlm-tabs-list>
<div hlmTabsContent="preview" class="mt-3 min-h-0 flex-1">
<div
class="border-border bg-background flex h-full flex-col overflow-hidden rounded-lg border md:flex-row"
>
<!-- Conversation sidebar -->
<aside
class="border-border hidden h-full min-h-0 w-64 shrink-0 flex-col border-r md:flex"
>
<div class="border-border flex items-center justify-between border-b px-3 py-2">
<span class="text-foreground text-sm font-medium">Conversations</span>
<button
hlmBtn
variant="ghost"
size="icon-sm"
type="button"
aria-label="New conversation"
(click)="newConversation()"
>
<ng-icon hlm size="xs" name="lucidePlus" />
</button>
</div>
<div class="border-border border-b px-2 py-2">
<div
class="bg-muted/40 focus-within:ring-ring flex items-center gap-2 rounded-md px-2 focus-within:ring-2"
>
<ng-icon
hlm
size="xs"
name="lucideSearch"
class="text-muted-foreground shrink-0"
/>
<input
type="search"
[(ngModel)]="searchQueryInput"
(ngModelChange)="searchQuery.set($event)"
placeholder="Search chats..."
aria-label="Search conversations"
class="text-foreground placeholder:text-muted-foreground h-8 w-full bg-transparent text-sm outline-none"
/>
@if (searchQuery().length > 0) {
<button
type="button"
aria-label="Clear search"
class="text-muted-foreground hover:text-foreground shrink-0"
(click)="clearSearch()"
>
<ng-icon hlm size="xs" name="lucideX" />
</button>
}
</div>
</div>
@if (filteredConversations().length === 0) {
<p class="text-muted-foreground flex-1 px-3 py-6 text-center text-xs">
No conversations match "{{ searchQuery() }}"
</p>
} @else {
<pk-conversation-list
[conversations]="filteredConversations()"
[activeId]="selectedConvoId()"
(selected)="selectedConvoId.set($event)"
(renamed)="onRename($event)"
(deleted)="onDelete($event)"
class="min-h-0 flex-1"
/>
}
<div class="border-border border-t p-2">
<button
type="button"
[hlmDropdownMenuTrigger]="userMenu"
side="top"
align="start"
class="hover:bg-muted focus-visible:ring-ring flex w-full items-center gap-3 rounded-md p-2 text-left transition-colors focus-visible:outline-none focus-visible:ring-2"
aria-label="Account menu"
>
<img
[src]="userAvatar"
alt=""
aria-hidden="true"
class="h-8 w-8 shrink-0 rounded-full object-cover"
/>
<div class="flex min-w-0 flex-1 flex-col">
<span class="text-foreground truncate text-sm font-medium">PianoNic</span>
<span class="text-muted-foreground truncate text-xs">Pro plan</span>
</div>
<ng-icon
hlm
size="sm"
name="lucideChevronsUpDown"
class="text-muted-foreground shrink-0"
/>
</button>
<ng-template #userMenu>
<hlm-dropdown-menu class="min-w-[220px]">
<button hlmDropdownMenuItem type="button" (triggered)="onMenu('account')">
<ng-icon hlm size="xs" name="lucideUserRound" />
Account
</button>
<button hlmDropdownMenuItem type="button" (triggered)="onMenu('settings')">
<ng-icon hlm size="xs" name="lucideSettings" />
Settings
</button>
<hlm-dropdown-menu-separator />
<button
hlmDropdownMenuItem
variant="destructive"
type="button"
(triggered)="onMenu('signout')"
>
<ng-icon hlm size="xs" name="lucideLogOut" />
Sign out
</button>
</hlm-dropdown-menu>
</ng-template>
</div>
</aside>
<!-- Main chat column -->
<section class="flex h-full min-h-0 min-w-0 flex-1 flex-col">
<!-- Header -->
<header class="border-border flex items-center justify-between gap-3 border-b px-4 py-2">
<pk-model-picker
[compact]="true"
[models]="models"
[(selectedId)]="selectedModelId"
/>
<span class="text-muted-foreground truncate text-xs">
{{ currentConvoTitle() }}
</span>
</header>
<!-- Message thread -->
<pk-chat-container-root class="relative flex-1 px-4 py-4">
<pk-chat-container-content class="gap-6">
@for (m of currentMessages(); track m.id) {
@if (m.role === 'user') {
<div class="group flex max-w-[80%] flex-col items-end gap-1 self-end">
<pk-message class="justify-end">
<pk-message-edit
#userEditor
editTrigger="hidden"
[content]="m.content"
(saved)="onMessageSaved(m.id, $event)"
>
<pk-message-content
class="bg-primary text-primary-foreground"
[content]="m.content"
/>
</pk-message-edit>
</pk-message>
@if (m.attachments?.length) {
<pk-attachment-preview
[attachments]="m.attachments ?? []"
[removable]="false"
/>
}
<pk-message-actions-bar
[actions]="userActions"
(actionPicked)="onUserAction($event, m, userEditor)"
/>
</div>
} @else {
<div class="group flex max-w-[85%] flex-col gap-1 self-start">
<pk-message>
<pk-message-avatar src="" alt="Assistant" fallback="AI" />
@if (m.streaming) {
<pk-message-content>
<pk-response-stream
mode="typewriter"
[speed]="60"
[textStream]="m.content"
(completed)="onStreamCompleted(m.id)"
/>
</pk-message-content>
} @else {
<pk-message-content
[markdown]="m.markdown === true"
[content]="m.content"
/>
}
</pk-message>
@if (!m.streaming) {
<pk-message-actions-bar
class="ml-11"
[actions]="assistantActionsFor(m.id)"
(actionPicked)="onAssistantAction($event, m)"
/>
}
</div>
}
}
@if (isStreaming()) {
<div class="flex items-center gap-3 pl-11">
<pk-loader variant="text-shimmer" text="Thinking..." />
</div>
}
</pk-chat-container-content>
<pk-chat-container-scroll-anchor />
<div class="sticky bottom-2 ml-auto w-fit pr-1">
<pk-scroll-button />
</div>
</pk-chat-container-root>
<!-- Composer -->
<div class="border-border border-t px-4 py-3">
@if (stagedAttachments().length > 0) {
<pk-attachment-preview
class="mb-2"
[attachments]="stagedAttachments()"
(removed)="onStagedRemoved($event)"
/>
}
<pk-prompt-input [(value)]="inputValue" (submitted)="send()">
<pk-prompt-input-textarea placeholder="Ask anything..." />
<pk-prompt-input-actions class="mt-2 justify-between">
<div class="flex items-center gap-1">
<pk-prompt-input-action tooltip="Attach files">
<button
hlmBtn
variant="ghost"
size="icon-sm"
type="button"
aria-label="Attach files"
(click)="addSampleAttachment()"
>
<ng-icon hlm size="sm" name="lucidePaperclip" />
</button>
</pk-prompt-input-action>
<pk-prompt-input-action tooltip="Voice input">
<button
hlmBtn
variant="ghost"
size="icon-sm"
type="button"
aria-label="Voice input"
>
<ng-icon hlm size="sm" name="lucideMic" />
</button>
</pk-prompt-input-action>
<pk-token-counter
class="ml-2"
display="compact"
[text]="inputValue()"
[limit]="2000"
/>
</div>
<pk-prompt-input-action tooltip="Send message">
<button
hlmBtn
size="icon-sm"
type="button"
class="rounded-full"
aria-label="Send message"
[disabled]="!canSend()"
(click)="send()"
>
<ng-icon hlm size="xs" name="lucideArrowUp" />
</button>
</pk-prompt-input-action>
</pk-prompt-input-actions>
</pk-prompt-input>
</div>
</section>
</div>
</div>
<div hlmTabsContent="code" class="mt-3 min-h-0 min-w-0 flex-1 overflow-hidden">
<div class="border-border bg-muted/30 h-full w-full overflow-auto rounded-lg border">
<pk-code-block class="block w-max min-w-full">
<pk-code-block-code [code]="fullChatHtmlSource" language="html" />
</pk-code-block>
</div>
</div>
</hlm-tabs>