feat: brain file upload button — PDFs, images, text files
- Upload button (arrow icon) in capture bar next to text input - Accepts: PDF, PNG, JPG, GIF, WEBP, TXT, MD, CSV - Multiple file upload supported - Hidden file input triggered by button click - Upload status indicator while processing - Files sent to /api/brain/items/upload endpoint Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -37,6 +37,8 @@
|
||||
// Capture
|
||||
let captureInput = $state('');
|
||||
let capturing = $state(false);
|
||||
let uploading = $state(false);
|
||||
let fileInput: HTMLInputElement;
|
||||
|
||||
// Detail
|
||||
let selectedItem = $state<BrainItem | null>(null);
|
||||
@@ -119,6 +121,26 @@
|
||||
searching = false;
|
||||
}
|
||||
|
||||
async function uploadFile(e: Event) {
|
||||
const input = e.target as HTMLInputElement;
|
||||
if (!input.files?.length) return;
|
||||
uploading = true;
|
||||
try {
|
||||
for (const file of input.files) {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
await fetch('/api/brain/items/upload', {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
body: formData,
|
||||
});
|
||||
}
|
||||
input.value = '';
|
||||
await loadItems();
|
||||
} catch { /* silent */ }
|
||||
uploading = false;
|
||||
}
|
||||
|
||||
function startEditNote() {
|
||||
if (!selectedItem) return;
|
||||
editNoteContent = selectedItem.raw_content || '';
|
||||
@@ -254,12 +276,19 @@
|
||||
onkeydown={handleCaptureKey}
|
||||
disabled={capturing}
|
||||
/>
|
||||
<button class="upload-btn" onclick={() => fileInput?.click()} disabled={uploading} title="Upload file">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
|
||||
</button>
|
||||
{#if captureInput.trim()}
|
||||
<button class="capture-btn" onclick={capture} disabled={capturing}>
|
||||
{capturing ? 'Saving...' : 'Save'}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
<input bind:this={fileInput} type="file" class="hidden-input" accept=".pdf,.png,.jpg,.jpeg,.gif,.webp,.txt,.md,.csv" onchange={uploadFile} multiple />
|
||||
{#if uploading}
|
||||
<div class="upload-status">Uploading...</div>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
<!-- ═══ Folder signal strip ═══ -->
|
||||
@@ -535,6 +564,26 @@
|
||||
color: #1e1812; font-size: 1rem; font-family: var(--font); outline: none;
|
||||
}
|
||||
.capture-input::placeholder { color: #8b7b6a; }
|
||||
.upload-btn {
|
||||
flex-shrink: 0;
|
||||
width: 36px; height: 36px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(35,26,17,0.1);
|
||||
background: rgba(255,255,255,0.6);
|
||||
color: #7f7365;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
transition: all 160ms;
|
||||
}
|
||||
.upload-btn:hover { background: rgba(255,255,255,0.9); color: #1e1812; border-color: rgba(35,26,17,0.2); }
|
||||
.upload-btn:active { transform: scale(0.95); }
|
||||
|
||||
.hidden-input { display: none; }
|
||||
|
||||
.upload-status {
|
||||
font-size: 0.82rem; color: #8c7b69;
|
||||
margin-top: 8px; padding-left: 28px;
|
||||
}
|
||||
|
||||
.capture-btn {
|
||||
padding: 8px 18px; border-radius: 999px;
|
||||
background: #1e1812; color: white; border: none;
|
||||
@@ -640,6 +689,7 @@
|
||||
height: auto;
|
||||
display: block;
|
||||
object-fit: cover;
|
||||
object-position: top;
|
||||
max-height: 240px;
|
||||
}
|
||||
.card-type-badge {
|
||||
|
||||
Reference in New Issue
Block a user