diff --git a/src/lib/research-api.ts b/src/lib/research-api.ts index 97b8f1a..a411bd9 100644 --- a/src/lib/research-api.ts +++ b/src/lib/research-api.ts @@ -50,6 +50,11 @@ interface ApiReportResponse { methodology: string; } +interface ApiPlanResponse { + plan: ResearchPlan | null; + available: boolean; +} + function normalizeStatus(status: ApiResearchStatus): ResearchStatus { return { id: status.research_id, @@ -103,12 +108,13 @@ export async function getResearchDetail( apiKey: string, researchId: string ): Promise { - const [status, sources, claims, evidence, report] = await Promise.all([ + const [status, sources, claims, evidence, report, plan] = await Promise.all([ apiGet(`/v1/research/${researchId}`, { apiKey }), apiGet(`/v1/research/${researchId}/sources`, { apiKey }), apiGet(`/v1/research/${researchId}/claims`, { apiKey }), apiGet(`/v1/research/${researchId}/evidence`, { apiKey }), apiGet(`/v1/research/${researchId}/report`, { apiKey }), + apiGet(`/v1/research/${researchId}/plan`, { apiKey }), ]); return { ...normalizeDetail(status), @@ -136,6 +142,7 @@ export async function getResearchDetail( .filter(Boolean) .join('\n\n'), methodology: report.methodology, + plan: plan.available ? plan.plan ?? undefined : undefined, }; } @@ -246,9 +253,23 @@ export interface ResearchDetail { evidence_scores?: Record; report?: string; methodology?: string; + plan?: ResearchPlan; username?: string; } +export interface ResearchPlan { + topic: string; + time_range?: { start?: string | null; end?: string | null; description?: string }; + entities?: string[]; + search_dimensions?: string[]; + queries?: Array<{ query: string; purpose: string; category: string; language: string }>; + potential_sources?: Array<{ type: string; description: string }>; + counter_hypotheses?: string[]; + search_bias_mitigation?: string[]; + estimated_depth?: string; + confidence?: number; +} + export interface ShareData { id: string; query: string; diff --git a/src/routes/research/[id]/+page.svelte b/src/routes/research/[id]/+page.svelte index 506a0d6..9963a58 100644 --- a/src/routes/research/[id]/+page.svelte +++ b/src/routes/research/[id]/+page.svelte @@ -12,6 +12,7 @@ import EvidenceTab from './components/EvidenceTab.svelte'; import ReportTab from './components/ReportTab.svelte'; import MethodologyTab from './components/MethodologyTab.svelte'; + import PlanTab from './components/PlanTab.svelte'; import ShareModal from '$lib/components/ShareModal.svelte'; import ShareButton from './components/ShareButton.svelte'; import { errorBanners } from '$lib/components/ErrorBannerStore'; @@ -29,8 +30,8 @@ let hasFinished = false; // --- Tab definitions with i18n labels --- - const TABS = ['Status', 'Quellen', 'Claims', 'Evidence', 'Bericht', 'Methodik']; - const TABS_COMPLETED = ['Quellen', 'Claims', 'Evidence', 'Bericht', 'Methodik']; + const TABS = ['Status', 'Planung', 'Quellen', 'Claims', 'Evidence', 'Bericht', 'Methodik']; + const TABS_COMPLETED = ['Planung', 'Quellen', 'Claims', 'Evidence', 'Bericht', 'Methodik']; // --- Polling --- let pollTimer: ReturnType | null = null; @@ -279,6 +280,10 @@ completed_at={detail?.completed_at ?? ''} /> + + {:else if activeTab === 'Planung'} + + {:else if activeTab === 'Quellen'} + import type { ResearchPlan } from '$lib/research-api'; + + export let plan: ResearchPlan | undefined; + export let loading = false; + export let error: string | null = null; + + const categoryLabels: Record = { + primary_source: 'Primärquelle', + news: 'Nachrichten', + scientific: 'Wissenschaft', + counter_evidence: 'Gegenprüfung', + general: 'Allgemein' + }; + + const sourceLabels: Record = { + primary_source: 'Primärquelle', + secondary_source: 'Sekundärquelle', + academic: 'Wissenschaftliche Quelle' + }; + + function label(values: Record, value: string): string { + return values[value] ?? value; + } + + +{#if loading} +

Planungsinformationen werden geladen …

+{:else if error} +

{error}

+{:else if !plan} +
+ Der validierte Rechercheplan ist noch nicht verfügbar. Er erscheint nach Abschluss der Planungsphase. +
+{:else} +
+
+

{plan.topic}

+

+ Zeitraum: {plan.time_range?.description ?? 'Nicht festgelegt'} + {#if plan.estimated_depth} · Tiefe: {plan.estimated_depth}{/if} + {#if plan.confidence !== undefined} · Planungszuversicht: {Math.round(plan.confidence * 100)}%{/if} +

+
+ +
+

Suchanfragen

+
+ {#each plan.queries ?? [] as query} +
+

{query.query}

+

{query.purpose}

+ + {label(categoryLabels, query.category)} · {query.language} + +
+ {/each} +
+
+ +
+
+

Perspektiven und Entitäten

+
    + {#each plan.search_dimensions ?? [] as dimension}
  • {dimension}
  • {/each} + {#each plan.entities ?? [] as entity}
  • {entity}
  • {/each} +
+
+
+

Vorgesehene Quellen

+
    + {#each plan.potential_sources ?? [] as source} +
  • {label(sourceLabels, source.type)}: {source.description}
  • + {/each} +
+
+
+ + {#if plan.counter_hypotheses?.length || plan.search_bias_mitigation?.length} +
+ {#if plan.counter_hypotheses?.length} +
+

Gegenhypothesen

+
    + {#each plan.counter_hypotheses as hypothesis}
  • {hypothesis}
  • {/each} +
+
+ {/if} + {#if plan.search_bias_mitigation?.length} +
+

Bias-Minderung

+
    + {#each plan.search_bias_mitigation as measure}
  • {measure}
  • {/each} +
+
+ {/if} +
+ {/if} +
+{/if}