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
|
// Capture
|
||||||
let captureInput = $state('');
|
let captureInput = $state('');
|
||||||
let capturing = $state(false);
|
let capturing = $state(false);
|
||||||
|
let uploading = $state(false);
|
||||||
|
let fileInput: HTMLInputElement;
|
||||||
|
|
||||||
// Detail
|
// Detail
|
||||||
let selectedItem = $state<BrainItem | null>(null);
|
let selectedItem = $state<BrainItem | null>(null);
|
||||||
@@ -119,6 +121,26 @@
|
|||||||
searching = false;
|
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() {
|
function startEditNote() {
|
||||||
if (!selectedItem) return;
|
if (!selectedItem) return;
|
||||||
editNoteContent = selectedItem.raw_content || '';
|
editNoteContent = selectedItem.raw_content || '';
|
||||||
@@ -254,12 +276,19 @@
|
|||||||
onkeydown={handleCaptureKey}
|
onkeydown={handleCaptureKey}
|
||||||
disabled={capturing}
|
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()}
|
{#if captureInput.trim()}
|
||||||
<button class="capture-btn" onclick={capture} disabled={capturing}>
|
<button class="capture-btn" onclick={capture} disabled={capturing}>
|
||||||
{capturing ? 'Saving...' : 'Save'}
|
{capturing ? 'Saving...' : 'Save'}
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</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>
|
</section>
|
||||||
|
|
||||||
<!-- ═══ Folder signal strip ═══ -->
|
<!-- ═══ Folder signal strip ═══ -->
|
||||||
@@ -535,6 +564,26 @@
|
|||||||
color: #1e1812; font-size: 1rem; font-family: var(--font); outline: none;
|
color: #1e1812; font-size: 1rem; font-family: var(--font); outline: none;
|
||||||
}
|
}
|
||||||
.capture-input::placeholder { color: #8b7b6a; }
|
.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 {
|
.capture-btn {
|
||||||
padding: 8px 18px; border-radius: 999px;
|
padding: 8px 18px; border-radius: 999px;
|
||||||
background: #1e1812; color: white; border: none;
|
background: #1e1812; color: white; border: none;
|
||||||
@@ -640,6 +689,7 @@
|
|||||||
height: auto;
|
height: auto;
|
||||||
display: block;
|
display: block;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
|
object-position: top;
|
||||||
max-height: 240px;
|
max-height: 240px;
|
||||||
}
|
}
|
||||||
.card-type-badge {
|
.card-type-badge {
|
||||||
|
|||||||
Reference in New Issue
Block a user