diff --git a/src/app/analytics/ai-analytics/ai-analytics.component.ts b/src/app/analytics/ai-analytics/ai-analytics.component.ts index afe55d1..eb981f7 100644 --- a/src/app/analytics/ai-analytics/ai-analytics.component.ts +++ b/src/app/analytics/ai-analytics/ai-analytics.component.ts @@ -1,5 +1,6 @@ import { Component, OnInit } from '@angular/core'; import { environment } from '../../../environments/environment'; +import { authHeaders } from '../../shared/services/auth-fetch'; @Component({ selector: 'app-ai-analytics', @@ -18,7 +19,7 @@ export class AiAnalyticsComponent implements OnInit { fetchUsage() { this.loading = true; - fetch(`${environment.SERVER_URL}/rag-analytics/adhoc-usage`) + fetch(`${environment.SERVER_URL}/rag-analytics/adhoc-usage`, { headers: authHeaders() }) .then(res => res.json()) .then(data => { this.usage = data; diff --git a/src/app/analytics/ai-pipeline/ai-pipeline.component.ts b/src/app/analytics/ai-pipeline/ai-pipeline.component.ts index 2f3492a..7d8d8bc 100644 --- a/src/app/analytics/ai-pipeline/ai-pipeline.component.ts +++ b/src/app/analytics/ai-pipeline/ai-pipeline.component.ts @@ -1,6 +1,7 @@ import { Component, OnInit } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; +import { authHeaders } from '../../shared/services/auth-fetch'; interface AgentPrompt { role: string; @@ -184,9 +185,7 @@ Return ONLY valid JSON: fetch('/api/rag-analytics/playground/analyze', { method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, + headers: authHeaders({ 'Content-Type': 'application/json' }), body: JSON.stringify({ text: this.playgroundText }) }) .then(response => { diff --git a/src/app/analytics/ai-playground/ai-playground.component.ts b/src/app/analytics/ai-playground/ai-playground.component.ts index 9dc3818..53ccebd 100644 --- a/src/app/analytics/ai-playground/ai-playground.component.ts +++ b/src/app/analytics/ai-playground/ai-playground.component.ts @@ -3,6 +3,7 @@ import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { CdkDragDrop, DragDropModule, moveItemInArray } from '@angular/cdk/drag-drop'; import { environment } from '../../../environments/environment'; +import { authHeaders } from '../../shared/services/auth-fetch'; interface PipelineStage { id: string; @@ -146,7 +147,7 @@ export class AiPlaygroundComponent { fetch(`${environment.SERVER_URL}/rag-analytics/playground/generate-prompt`, { method: 'POST', - headers: { 'Content-Type': 'application/json' }, + headers: authHeaders({ 'Content-Type': 'application/json' }), body: JSON.stringify({ description: stage.userDescription }) }) .then(res => res.json()) @@ -166,7 +167,7 @@ export class AiPlaygroundComponent { // Stage Library fetchSavedStages() { - fetch(`${environment.SERVER_URL}/rag-analytics/stages`) + fetch(`${environment.SERVER_URL}/rag-analytics/stages`, { headers: authHeaders() }) .then(res => res.json()) .then(data => { this.savedStages = Array.isArray(data) ? data : []; }) .catch(() => { this.savedStages = []; }); @@ -175,7 +176,7 @@ export class AiPlaygroundComponent { saveStageToLibrary(stage: PipelineStage) { fetch(`${environment.SERVER_URL}/rag-analytics/stages`, { method: 'POST', - headers: { 'Content-Type': 'application/json' }, + headers: authHeaders({ 'Content-Type': 'application/json' }), body: JSON.stringify({ stage_id: stage.id, name: stage.name, @@ -208,7 +209,7 @@ export class AiPlaygroundComponent { } deleteSavedStage(stageId: string) { - fetch(`${environment.SERVER_URL}/rag-analytics/stages/${stageId}`, { method: 'DELETE' }) + fetch(`${environment.SERVER_URL}/rag-analytics/stages/${stageId}`, { method: 'DELETE', headers: authHeaders() }) .then(() => this.fetchSavedStages()) .catch(err => console.error('Failed to delete stage:', err)); } @@ -220,7 +221,7 @@ export class AiPlaygroundComponent { // Generate prompt from description fetch(`${environment.SERVER_URL}/rag-analytics/playground/generate-prompt`, { method: 'POST', - headers: { 'Content-Type': 'application/json' }, + headers: authHeaders({ 'Content-Type': 'application/json' }), body: JSON.stringify({ description: this.newStageDescription }) }) .then(res => res.json()) @@ -229,7 +230,7 @@ export class AiPlaygroundComponent { // Generate examples return fetch(`${environment.SERVER_URL}/rag-analytics/stages/generate-examples`, { method: 'POST', - headers: { 'Content-Type': 'application/json' }, + headers: authHeaders({ 'Content-Type': 'application/json' }), body: JSON.stringify({ system_prompt: systemPrompt, user_description: this.newStageDescription }) }) .then(res => res.json()) @@ -239,7 +240,7 @@ export class AiPlaygroundComponent { // Save to DB return fetch(`${environment.SERVER_URL}/rag-analytics/stages`, { method: 'POST', - headers: { 'Content-Type': 'application/json' }, + headers: authHeaders({ 'Content-Type': 'application/json' }), body: JSON.stringify({ stage_id: stageId, name, @@ -268,7 +269,7 @@ export class AiPlaygroundComponent { generateExamples(stage: PipelineStage) { fetch(`${environment.SERVER_URL}/rag-analytics/stages/generate-examples`, { method: 'POST', - headers: { 'Content-Type': 'application/json' }, + headers: authHeaders({ 'Content-Type': 'application/json' }), body: JSON.stringify({ system_prompt: stage.systemPrompt, user_description: stage.userDescription }) }) .then(res => res.json()) @@ -284,7 +285,7 @@ export class AiPlaygroundComponent { // Pipeline Templates fetchSavedPipelines() { - fetch(`${environment.SERVER_URL}/rag-analytics/pipelines`) + fetch(`${environment.SERVER_URL}/rag-analytics/pipelines`, { headers: authHeaders() }) .then(res => res.json()) .then(data => { this.savedPipelines = Array.isArray(data) ? data : []; }) .catch(() => { this.savedPipelines = []; }); @@ -300,7 +301,7 @@ export class AiPlaygroundComponent { fetch(`${environment.SERVER_URL}/rag-analytics/pipelines`, { method: 'POST', - headers: { 'Content-Type': 'application/json' }, + headers: authHeaders({ 'Content-Type': 'application/json' }), body: JSON.stringify({ template_id: templateId, name: this.savePipelineName, @@ -330,7 +331,7 @@ export class AiPlaygroundComponent { } deleteSavedPipeline(templateId: string) { - fetch(`${environment.SERVER_URL}/rag-analytics/pipelines/${templateId}`, { method: 'DELETE' }) + fetch(`${environment.SERVER_URL}/rag-analytics/pipelines/${templateId}`, { method: 'DELETE', headers: authHeaders() }) .then(() => this.fetchSavedPipelines()) .catch(err => console.error('Failed to delete pipeline:', err)); } @@ -358,7 +359,7 @@ export class AiPlaygroundComponent { fetch(`${environment.SERVER_URL}/rag-analytics/playground/execute-stage`, { method: 'POST', - headers: { 'Content-Type': 'application/json' }, + headers: authHeaders({ 'Content-Type': 'application/json' }), body: JSON.stringify({ type: stage.type, systemPrompt: stage.systemPrompt, @@ -449,7 +450,7 @@ export class AiPlaygroundComponent { const apiUrl = `${environment.SERVER_URL}/rag-analytics/playground/execute-pipeline`; - fetch(apiUrl, { method: 'POST', body: formData }) + fetch(apiUrl, { method: 'POST', headers: authHeaders(), body: formData }) .then(response => { const reader = response.body!.getReader(); const decoder = new TextDecoder(); diff --git a/src/app/home/home/home.component.ts b/src/app/home/home/home.component.ts index cc45c2f..45287dd 100644 --- a/src/app/home/home/home.component.ts +++ b/src/app/home/home/home.component.ts @@ -15,6 +15,7 @@ import { Title } from '@angular/platform-browser'; import { GoogleAnalyticsService } from 'ngx-google-analytics'; import { environment } from '../../../environments/environment'; import { Section508Clause, resolveClause, clauseToPlainText } from '../../shared/section508-clause'; +import { authHeaders } from '../../shared/services/auth-fetch'; @Component({ selector: 'app-home', @@ -376,7 +377,7 @@ export class HomeComponent try { const response = await fetch(`${environment.SERVER_URL}/rag-analytics/playground/package-synthesis`, { method: 'POST', - headers: { 'Content-Type': 'application/json' }, + headers: authHeaders({ 'Content-Type': 'application/json' }), body: JSON.stringify({ summaries, file_count: fileNames.length, diff --git a/src/app/shared/services/auth-fetch.ts b/src/app/shared/services/auth-fetch.ts new file mode 100644 index 0000000..1575c2c --- /dev/null +++ b/src/app/shared/services/auth-fetch.ts @@ -0,0 +1,19 @@ +/** + * Authorization headers for raw fetch() calls. + * + * Angular's HttpClient gets its bearer token from TokenInterceptor, but raw + * fetch() bypasses the interceptor entirely. Any fetch() to an authenticated + * SRT endpoint has to attach the header itself, and this is the one place that + * knows how. + * + * Prefer HttpClient where practical. fetch() is used in a few places for + * streaming responses and multipart uploads, which is where this helper applies. + * + * @param extra additional headers to merge, for example Content-Type. Omit + * Content-Type entirely when sending FormData so the browser can + * set the multipart boundary itself. + */ +export function authHeaders (extra: Record = {}): Record { + const token = localStorage.getItem('token') + return token ? { ...extra, Authorization: `Bearer ${token}` } : { ...extra } +}