All blocks
Block
Attachments above input
Multimodal compose row. Drag a file anywhere on the preview or click the paperclip — chips appear in the attachment-preview row above the textarea.
Drop-anywhere multimodal compose
<pk-file-upload
#fu
accept="image/*,application/pdf"
(filesAdded)="onFilesAdded($event)"
>
@if (attachments().length) {
<pk-attachment-preview
[attachments]="attachments()"
(removed)="onRemove($event)"
/>
}
<pk-prompt-input [(value)]="value" (submitted)="onSubmit()">
<pk-prompt-input-textarea placeholder="Describe what you want or drop files..." />
<pk-prompt-input-actions class="mt-2 justify-between">
<pk-prompt-input-action tooltip="Attach files">
<button hlmBtn size="icon-sm" variant="ghost"
(click)="fu.openPicker(); $event.stopPropagation()">
<ng-icon hlm size="sm" name="lucidePaperclip" />
</button>
</pk-prompt-input-action>
<pk-prompt-input-action tooltip="Send">
<button hlmBtn size="icon-sm" class="rounded-full" (click)="onSubmit()">
<ng-icon hlm size="xs" name="lucideArrowUp" />
</button>
</pk-prompt-input-action>
</pk-prompt-input-actions>
</pk-prompt-input>
<pk-file-upload-content>
<!-- drag-state overlay -->
</pk-file-upload-content>
</pk-file-upload>
// Component
protected readonly value = signal('');
protected readonly attachments = signal<Attachment[]>([]);
protected onFilesAdded(files: File[]): void {
const next: Attachment[] = files.map((f, i) => ({
id: `${Date.now()}-${i}`,
name: f.name,
type: f.type.startsWith('image/') ? 'image' : 'file',
size: f.size,
mimeType: f.type,
}));
this.attachments.update(list => [...list, ...next]);
}
protected onRemove(id: string): void {
this.attachments.update(list => list.filter(a => a.id !== id));
}