diff --git a/apps/mana/apps/web/src/lib/modules/research-lab/ListView.svelte b/apps/mana/apps/web/src/lib/modules/research-lab/ListView.svelte index 0fbe6a9f2..e978fa4af 100644 --- a/apps/mana/apps/web/src/lib/modules/research-lab/ListView.svelte +++ b/apps/mana/apps/web/src/lib/modules/research-lab/ListView.svelte @@ -6,6 +6,7 @@ is a thin orchestrator over the mana-research service. --> + + + Research Run · Mana + + +
+
+ + {#if run} +
+ {run.category} + {run.mode} + {#if run.totalCostCredits > 0} + {run.totalCostCredits}¢ + {/if} + {formatDate(run.createdAt)} +
+ {/if} +
+ + {#if loading} +

Lade Run …

+ {:else if error} +
{error}
+ {:else if run} +

{run.query}

+

+ {run.providersRequested.length} Anbieter · Run {run.id.slice(0, 8)} +

+
+ {#each entries as entry (entry.resultId)} + + {/each} +
+ {/if} +
+ + diff --git a/packages/shared-branding/src/app-icons.ts b/packages/shared-branding/src/app-icons.ts index 0ae3c6141..b65bb7fb3 100644 --- a/packages/shared-branding/src/app-icons.ts +++ b/packages/shared-branding/src/app-icons.ts @@ -116,6 +116,9 @@ export const APP_ICONS = { 'news-research': svgToDataUrl( `` ), + 'research-lab': svgToDataUrl( + `` + ), guides: svgToDataUrl( `` ), diff --git a/packages/shared-branding/src/mana-apps.ts b/packages/shared-branding/src/mana-apps.ts index 907e04d9e..ed26b23a3 100644 --- a/packages/shared-branding/src/mana-apps.ts +++ b/packages/shared-branding/src/mana-apps.ts @@ -496,6 +496,23 @@ export const MANA_APPS: ManaApp[] = [ status: 'development', requiredTier: 'guest', }, + { + id: 'research-lab', + name: 'Research Lab', + description: { + de: 'Web-Research Anbieter Seite-an-Seite vergleichen', + en: 'Compare web-research providers side-by-side', + }, + longDescription: { + de: 'Schick dieselbe Anfrage parallel an bis zu fünf Anbieter (Brave, Tavily, Exa, Perplexity, Claude, Gemini, OpenAI …) und vergleich Antworten, Latenzen und Kosten in einer Ansicht. Alle Runs werden serverseitig persistiert für spätere Auswertung.', + en: 'Send the same query to up to five providers in parallel (Brave, Tavily, Exa, Perplexity, Claude, Gemini, OpenAI …) and compare answers, latency, and cost side-by-side. All runs are persisted server-side for later review.', + }, + icon: APP_ICONS['research-lab'], + color: '#8b5cf6', + comingSoon: false, + status: 'beta', + requiredTier: 'beta', + }, { id: 'calc', name: 'Calc', diff --git a/packages/shared-research/src/types.ts b/packages/shared-research/src/types.ts index 4ead32756..1c451d278 100644 --- a/packages/shared-research/src/types.ts +++ b/packages/shared-research/src/types.ts @@ -72,6 +72,7 @@ export interface CompareResponse { success: boolean; data?: T; meta: ProviderMeta; + resultId?: string; }>; } diff --git a/services/mana-research/src/routes/extract.ts b/services/mana-research/src/routes/extract.ts index 40fcb9a21..5574d0153 100644 --- a/services/mana-research/src/routes/extract.ts +++ b/services/mana-research/src/routes/extract.ts @@ -126,10 +126,11 @@ export function createExtractRoutes( ); let totalCost = 0; + const resultIds: string[] = []; for (let i = 0; i < providers.length; i++) { const out = settled[i]; totalCost += out.meta.costCredits; - await storage.addResult({ + const row = await storage.addResult({ runId: run.id, providerId: providers[i].id, success: out.success, @@ -140,6 +141,7 @@ export function createExtractRoutes( normalizedResult: out.data ?? null, errorCode: out.meta.errorCode ?? null, }); + resultIds.push(row.id); } if (totalCost > 0) await storage.finalizeRunCost(run.id, totalCost); @@ -151,6 +153,7 @@ export function createExtractRoutes( success: settled[i].success, data: settled[i].data as { content: ExtractedContent } | undefined, meta: settled[i].meta, + resultId: resultIds[i], })), }); }); diff --git a/services/mana-research/src/routes/research.ts b/services/mana-research/src/routes/research.ts index 69c47fb44..d8aa411cf 100644 --- a/services/mana-research/src/routes/research.ts +++ b/services/mana-research/src/routes/research.ts @@ -130,10 +130,11 @@ export function createResearchRoutes( ); let totalCost = 0; + const resultIds: string[] = []; for (let i = 0; i < providers.length; i++) { const out = settled[i]; totalCost += out.meta.costCredits; - await storage.addResult({ + const row = await storage.addResult({ runId: run.id, providerId: providers[i].id, success: out.success, @@ -144,6 +145,7 @@ export function createResearchRoutes( normalizedResult: out.data ?? null, errorCode: out.meta.errorCode ?? null, }); + resultIds.push(row.id); } if (totalCost > 0) await storage.finalizeRunCost(run.id, totalCost); @@ -155,6 +157,7 @@ export function createResearchRoutes( success: settled[i].success, data: settled[i].data as { answer: AgentAnswer } | undefined, meta: settled[i].meta, + resultId: resultIds[i], })), }); }); diff --git a/services/mana-research/src/routes/search.ts b/services/mana-research/src/routes/search.ts index e8d4f682a..8db5e199c 100644 --- a/services/mana-research/src/routes/search.ts +++ b/services/mana-research/src/routes/search.ts @@ -86,7 +86,7 @@ export function createSearchRoutes( deps ); - await storage.addResult({ + const resultRow = await storage.addResult({ runId: run.id, providerId, success: out.success, @@ -110,6 +110,7 @@ export function createSearchRoutes( success: out.success, data: out.data, meta: out.meta, + resultId: resultRow.id, }); }) .post('/compare', async (c) => { @@ -147,10 +148,11 @@ export function createSearchRoutes( ); let totalCost = 0; + const resultIds: string[] = []; for (let i = 0; i < providers.length; i++) { const out = settled[i]; totalCost += out.meta.costCredits; - await storage.addResult({ + const row = await storage.addResult({ runId: run.id, providerId: providers[i].id, success: out.success, @@ -161,6 +163,7 @@ export function createSearchRoutes( normalizedResult: out.data ?? null, errorCode: out.meta.errorCode ?? null, }); + resultIds.push(row.id); } if (totalCost > 0) await storage.finalizeRunCost(run.id, totalCost); @@ -172,6 +175,7 @@ export function createSearchRoutes( success: settled[i].success, data: settled[i].data as { results: SearchHit[] } | undefined, meta: settled[i].meta, + resultId: resultIds[i], })), }); });