Add research planning detail tab

This commit is contained in:
faligam
2026-09-07 12:17:44 +02:00
parent 54d7222eb5
commit c5bc74a127
3 changed files with 129 additions and 3 deletions

View File

@@ -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<ResearchDetail> {
const [status, sources, claims, evidence, report] = await Promise.all([
const [status, sources, claims, evidence, report, plan] = await Promise.all([
apiGet<ApiResearchStatus>(`/v1/research/${researchId}`, { apiKey }),
apiGet<ApiSourcesResponse>(`/v1/research/${researchId}/sources`, { apiKey }),
apiGet<ApiClaimsResponse>(`/v1/research/${researchId}/claims`, { apiKey }),
apiGet<ApiEvidenceResponse>(`/v1/research/${researchId}/evidence`, { apiKey }),
apiGet<ApiReportResponse>(`/v1/research/${researchId}/report`, { apiKey }),
apiGet<ApiPlanResponse>(`/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<string, number>;
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;

View File

@@ -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<typeof setInterval> | null = null;
@@ -279,6 +280,10 @@
completed_at={detail?.completed_at ?? ''}
/>
<!-- Planning Tab -->
{:else if activeTab === 'Planung'}
<PlanTab plan={detail?.plan} loading={false} {error} />
<!-- Sources Tab -->
{:else if activeTab === 'Quellen'}
<SourcesTab

View File

@@ -0,0 +1,100 @@
<script lang="ts">
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<string, string> = {
primary_source: 'Primärquelle',
news: 'Nachrichten',
scientific: 'Wissenschaft',
counter_evidence: 'Gegenprüfung',
general: 'Allgemein'
};
const sourceLabels: Record<string, string> = {
primary_source: 'Primärquelle',
secondary_source: 'Sekundärquelle',
academic: 'Wissenschaftliche Quelle'
};
function label(values: Record<string, string>, value: string): string {
return values[value] ?? value;
}
</script>
{#if loading}
<p class="text-sm text-gray-500 dark:text-gray-400">Planungsinformationen werden geladen …</p>
{:else if error}
<p class="text-sm text-red-600 dark:text-red-400">{error}</p>
{:else if !plan}
<div class="rounded-lg border border-gray-200 dark:border-gray-700 p-5 text-sm text-gray-500 dark:text-gray-400">
Der validierte Rechercheplan ist noch nicht verfügbar. Er erscheint nach Abschluss der Planungsphase.
</div>
{:else}
<div class="space-y-6 text-sm text-gray-700 dark:text-gray-200">
<section>
<h2 class="text-base font-semibold text-gray-900 dark:text-white">{plan.topic}</h2>
<p class="mt-1 text-gray-500 dark:text-gray-400">
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}
</p>
</section>
<section>
<h3 class="font-semibold text-gray-900 dark:text-white">Suchanfragen</h3>
<div class="mt-2 space-y-2">
{#each plan.queries ?? [] as query}
<article class="rounded-lg border border-gray-200 dark:border-gray-700 p-3">
<p class="font-medium text-gray-900 dark:text-white">{query.query}</p>
<p class="mt-1 text-gray-500 dark:text-gray-400">{query.purpose}</p>
<span class="mt-2 inline-block rounded bg-indigo-50 px-2 py-0.5 text-xs text-indigo-700 dark:bg-indigo-900/40 dark:text-indigo-200">
{label(categoryLabels, query.category)} · {query.language}
</span>
</article>
{/each}
</div>
</section>
<section class="grid gap-5 md:grid-cols-2">
<div>
<h3 class="font-semibold text-gray-900 dark:text-white">Perspektiven und Entitäten</h3>
<ul class="mt-2 list-disc space-y-1 pl-5">
{#each plan.search_dimensions ?? [] as dimension}<li>{dimension}</li>{/each}
{#each plan.entities ?? [] as entity}<li>{entity}</li>{/each}
</ul>
</div>
<div>
<h3 class="font-semibold text-gray-900 dark:text-white">Vorgesehene Quellen</h3>
<ul class="mt-2 space-y-2">
{#each plan.potential_sources ?? [] as source}
<li><span class="font-medium">{label(sourceLabels, source.type)}:</span> {source.description}</li>
{/each}
</ul>
</div>
</section>
{#if plan.counter_hypotheses?.length || plan.search_bias_mitigation?.length}
<section class="grid gap-5 md:grid-cols-2">
{#if plan.counter_hypotheses?.length}
<div>
<h3 class="font-semibold text-gray-900 dark:text-white">Gegenhypothesen</h3>
<ul class="mt-2 list-disc space-y-1 pl-5">
{#each plan.counter_hypotheses as hypothesis}<li>{hypothesis}</li>{/each}
</ul>
</div>
{/if}
{#if plan.search_bias_mitigation?.length}
<div>
<h3 class="font-semibold text-gray-900 dark:text-white">Bias-Minderung</h3>
<ul class="mt-2 list-disc space-y-1 pl-5">
{#each plan.search_bias_mitigation as measure}<li>{measure}</li>{/each}
</ul>
</div>
{/if}
</section>
{/if}
</div>
{/if}