feat: wire brain service to platform gateway

- Gateway proxies /api/brain/* to brain-api:8200/api/* via pangolin network
- User identity injected via X-Gateway-User-Id header
- Brain app registered in gateway database (sort_order 9)
- Added to GATEWAY_KEY_SERVICES for dashboard integration
- Tested: health, config, list, create all working through gateway

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Yusuf Suleman
2026-04-01 16:32:53 -05:00
parent c9e776df59
commit 2072c359aa
34 changed files with 16745 additions and 1379 deletions

View File

@@ -1,5 +1,6 @@
<script lang="ts">
import ImmichPicker from '$lib/components/shared/ImmichPicker.svelte';
import PdfInlinePreview from './PdfInlinePreview.svelte';
let {
entityType,
@@ -148,6 +149,12 @@
showImmich = false;
onUpload();
}
function isPdfDocument(doc: any) {
const fileName = String(doc.file_name || doc.original_name || '').toLowerCase();
const mimeType = String(doc.mime_type || '').toLowerCase();
return mimeType.includes('pdf') || fileName.endsWith('.pdf');
}
</script>
<div class="upload-section">
@@ -169,10 +176,21 @@
{#if documents && documents.length > 0}
<div class="doc-list">
{#each documents as doc}
<div class="doc-row">
<svg class="doc-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/></svg>
<a href={doc.url || `/api/trips/documents/${doc.file_path}`} target="_blank" class="doc-name">{doc.file_name || doc.original_name || 'Document'}</a>
<button class="doc-delete" onclick={() => deleteDoc(doc.id)} disabled={deletingDocId === doc.id}>×</button>
<div class="doc-card">
{#if isPdfDocument(doc)}
<PdfInlinePreview
url={doc.url || `/api/trips/documents/${doc.file_path}`}
name={doc.file_name || doc.original_name || 'PDF document'}
onDelete={() => deleteDoc(doc.id)}
deleting={deletingDocId === doc.id}
/>
{:else}
<div class="doc-row">
<svg class="doc-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/></svg>
<a href={doc.url || `/api/trips/documents/${doc.file_path}`} target="_blank" class="doc-name">{doc.file_name || doc.original_name || 'Document'}</a>
<button class="doc-delete" onclick={() => deleteDoc(doc.id)} disabled={deletingDocId === doc.id}>×</button>
</div>
{/if}
</div>
{/each}
</div>
@@ -241,13 +259,36 @@
border-radius: 50%; background: rgba(0,0,0,0.5); color: white; border: none;
font-size: var(--text-sm); cursor: pointer; display: flex; align-items: center; justify-content: center;
}
.doc-list { display: flex; flex-direction: column; gap: 4px; }
.doc-row { display: flex; align-items: center; gap: 6px; padding: 6px 8px; border-radius: 6px; background: var(--surface-secondary); }
.doc-icon { width: 14px; height: 14px; color: var(--text-4); flex-shrink: 0; }
.doc-name { flex: 1; font-size: var(--text-sm); color: var(--text-2); text-decoration: none; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.doc-name:hover { color: var(--accent); }
.doc-delete { background: none; border: none; color: var(--text-4); cursor: pointer; font-size: var(--text-base); padding: 2px 4px; }
.doc-delete:hover { color: var(--error); }
.doc-list { display: flex; flex-direction: column; gap: 12px; }
.doc-card {
display: flex;
flex-direction: column;
gap: 10px;
padding: 0;
}
.doc-row { display: flex; align-items: center; gap: 8px; min-width: 0; }
.doc-icon { width: 16px; height: 16px; color: rgba(111, 88, 64, 0.7); flex-shrink: 0; }
.doc-name {
flex: 1;
min-width: 0;
font-size: 0.92rem;
font-weight: 600;
color: #3a291b;
text-decoration: none;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.doc-name:hover { color: #1f1510; }
.doc-delete {
background: none;
border: none;
color: rgba(112, 86, 62, 0.64);
cursor: pointer;
font-size: 1.1rem;
padding: 2px 4px;
}
.doc-delete:hover { color: #8f3928; }
.upload-actions { display: flex; flex-wrap: wrap; gap: 6px; }
.upload-btn {