mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 20:21:09 +02:00
i18n(ai-workbench): translate ListView via $_() — tabs, filters, audit table, timeline buckets
- Tabs (Timeline / Datenzugriff)
- Filter labels (Modul/Mission/Agent) with shared "alle" option
- Time-range buttons routed via dynamic key labelKey
- Audit: loading, error_prefix interpolation, empty paragraph, 4 column headers
- Timeline empty state
- Bucket revert button (title + Läuft… / Rückgängig label) + event-count tooltip + event-link "Zum Modul"
- Confirm + alert summary parts ({n} zurückgenommen / nicht unterstützt / fehlgeschlagen) + "Revert fehlgeschlagen — siehe Console." fallback
- Date/time formatters switched to get(locale) ?? 'de'
Baselines: hardcoded 1090 → 1082 (8 cleared); missing-keys baseline +1 (ai-workbench.list_view.range_* dynamic key).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
08ad86ec59
commit
391017bcfa
3 changed files with 64 additions and 36 deletions
|
|
@ -11,6 +11,8 @@
|
||||||
import { fetchDecryptAudit, type AuditRow } from '$lib/data/ai/audit/queries';
|
import { fetchDecryptAudit, type AuditRow } from '$lib/data/ai/audit/queries';
|
||||||
import { isMissionGrantsEnabled } from '$lib/api/config';
|
import { isMissionGrantsEnabled } from '$lib/api/config';
|
||||||
import type { DomainEvent } from '$lib/data/events/types';
|
import type { DomainEvent } from '$lib/data/events/types';
|
||||||
|
import { _, locale } from 'svelte-i18n';
|
||||||
|
import { get } from 'svelte/store';
|
||||||
|
|
||||||
let moduleFilter = $state<string | null>(null);
|
let moduleFilter = $state<string | null>(null);
|
||||||
let missionFilter = $state<string | null>(null);
|
let missionFilter = $state<string | null>(null);
|
||||||
|
|
@ -54,10 +56,16 @@
|
||||||
return title ? `${e.type} · ${title}` : e.type;
|
return title ? `${e.type} · ${title}` : e.type;
|
||||||
}
|
}
|
||||||
function formatTime(iso: string) {
|
function formatTime(iso: string) {
|
||||||
return new Date(iso).toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' });
|
return new Date(iso).toLocaleTimeString(get(locale) ?? 'de', {
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
function formatDate(iso: string) {
|
function formatDate(iso: string) {
|
||||||
return new Date(iso).toLocaleDateString('de-DE', { day: 'numeric', month: 'short' });
|
return new Date(iso).toLocaleDateString(get(locale) ?? 'de', {
|
||||||
|
day: 'numeric',
|
||||||
|
month: 'short',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Tab switcher: timeline ↔ decrypt audit ─────────────
|
// ── Tab switcher: timeline ↔ decrypt audit ─────────────
|
||||||
|
|
@ -92,7 +100,7 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
function formatAuditTs(iso: string): string {
|
function formatAuditTs(iso: string): string {
|
||||||
return new Date(iso).toLocaleString('de-DE', {
|
return new Date(iso).toLocaleString(get(locale) ?? 'de', {
|
||||||
day: '2-digit',
|
day: '2-digit',
|
||||||
month: '2-digit',
|
month: '2-digit',
|
||||||
hour: '2-digit',
|
hour: '2-digit',
|
||||||
|
|
@ -102,17 +110,27 @@
|
||||||
|
|
||||||
let revertingKey = $state<string | null>(null);
|
let revertingKey = $state<string | null>(null);
|
||||||
async function handleRevert(key: string, missionId: string, iterationId: string) {
|
async function handleRevert(key: string, missionId: string, iterationId: string) {
|
||||||
if (!confirm('Alle AI-Writes dieser Iteration zurücknehmen?')) return;
|
if (!confirm($_('ai-workbench.list_view.confirm_revert'))) return;
|
||||||
revertingKey = key;
|
revertingKey = key;
|
||||||
try {
|
try {
|
||||||
const stats = await revertIteration(missionId, iterationId);
|
const stats = await revertIteration(missionId, iterationId);
|
||||||
const parts = [`${stats.reverted} zurückgenommen`];
|
const parts = [
|
||||||
if (stats.skippedUnsupported > 0) parts.push(`${stats.skippedUnsupported} nicht unterstützt`);
|
$_('ai-workbench.list_view.revert_summary_done', { values: { n: stats.reverted } }),
|
||||||
if (stats.failed > 0) parts.push(`${stats.failed} fehlgeschlagen`);
|
];
|
||||||
|
if (stats.skippedUnsupported > 0)
|
||||||
|
parts.push(
|
||||||
|
$_('ai-workbench.list_view.revert_summary_unsupported', {
|
||||||
|
values: { n: stats.skippedUnsupported },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
if (stats.failed > 0)
|
||||||
|
parts.push(
|
||||||
|
$_('ai-workbench.list_view.revert_summary_failed', { values: { n: stats.failed } })
|
||||||
|
);
|
||||||
alert(parts.join(' · '));
|
alert(parts.join(' · '));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
alert('Revert fehlgeschlagen — siehe Console.');
|
alert($_('ai-workbench.list_view.revert_alert_failed'));
|
||||||
} finally {
|
} finally {
|
||||||
revertingKey = null;
|
revertingKey = null;
|
||||||
}
|
}
|
||||||
|
|
@ -129,7 +147,7 @@
|
||||||
aria-selected={tab === 'timeline'}
|
aria-selected={tab === 'timeline'}
|
||||||
onclick={() => (tab = 'timeline')}
|
onclick={() => (tab = 'timeline')}
|
||||||
>
|
>
|
||||||
Timeline
|
{$_('ai-workbench.list_view.tab_timeline')}
|
||||||
</button>
|
</button>
|
||||||
{#if grantsEnabled}
|
{#if grantsEnabled}
|
||||||
<button
|
<button
|
||||||
|
|
@ -140,7 +158,7 @@
|
||||||
aria-selected={tab === 'audit'}
|
aria-selected={tab === 'audit'}
|
||||||
onclick={() => (tab = 'audit')}
|
onclick={() => (tab = 'audit')}
|
||||||
>
|
>
|
||||||
Datenzugriff
|
{$_('ai-workbench.list_view.tab_audit')}
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -148,9 +166,9 @@
|
||||||
<div class="filters">
|
<div class="filters">
|
||||||
{#if tab === 'timeline'}
|
{#if tab === 'timeline'}
|
||||||
<label>
|
<label>
|
||||||
<span class="lbl">Modul</span>
|
<span class="lbl">{$_('ai-workbench.list_view.label_module')}</span>
|
||||||
<select bind:value={moduleFilter}>
|
<select bind:value={moduleFilter}>
|
||||||
<option value={null}>alle</option>
|
<option value={null}>{$_('ai-workbench.list_view.option_all')}</option>
|
||||||
{#each allModules as m}
|
{#each allModules as m}
|
||||||
<option value={m}>{m}</option>
|
<option value={m}>{m}</option>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
@ -158,26 +176,26 @@
|
||||||
</label>
|
</label>
|
||||||
{/if}
|
{/if}
|
||||||
<label>
|
<label>
|
||||||
<span class="lbl">Mission</span>
|
<span class="lbl">{$_('ai-workbench.list_view.label_mission')}</span>
|
||||||
<select bind:value={missionFilter}>
|
<select bind:value={missionFilter}>
|
||||||
<option value={null}>alle</option>
|
<option value={null}>{$_('ai-workbench.list_view.option_all')}</option>
|
||||||
{#each missions.value as m (m.id)}
|
{#each missions.value as m (m.id)}
|
||||||
<option value={m.id}>{m.title}</option>
|
<option value={m.id}>{m.title}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
<span class="lbl">Agent</span>
|
<span class="lbl">{$_('ai-workbench.list_view.label_agent')}</span>
|
||||||
<select bind:value={agentFilter}>
|
<select bind:value={agentFilter}>
|
||||||
<option value={null}>alle</option>
|
<option value={null}>{$_('ai-workbench.list_view.option_all')}</option>
|
||||||
{#each agents.value as a (a.id)}
|
{#each agents.value as a (a.id)}
|
||||||
<option value={a.id}>{a.avatar ?? '🤖'} {a.name}</option>
|
<option value={a.id}>{a.avatar ?? '🤖'} {a.name}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
{#if tab === 'timeline'}
|
{#if tab === 'timeline'}
|
||||||
<div class="range-group" role="tablist" aria-label="Zeitraum">
|
<div class="range-group" role="tablist" aria-label={$_('ai-workbench.list_view.range_aria')}>
|
||||||
{#each [{ id: '24h', label: '24h' }, { id: '7d', label: '7T' }, { id: 'all', label: 'alle' }] as const as opt}
|
{#each [{ id: '24h', labelKey: 'range_24h' }, { id: '7d', labelKey: 'range_7d' }, { id: 'all', labelKey: 'range_all' }] as const as opt}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="range-btn"
|
class="range-btn"
|
||||||
|
|
@ -185,7 +203,7 @@
|
||||||
aria-pressed={timeRangeFilter === opt.id}
|
aria-pressed={timeRangeFilter === opt.id}
|
||||||
onclick={() => (timeRangeFilter = opt.id)}
|
onclick={() => (timeRangeFilter = opt.id)}
|
||||||
>
|
>
|
||||||
{opt.label}
|
{$_('ai-workbench.list_view.' + opt.labelKey)}
|
||||||
</button>
|
</button>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -194,23 +212,23 @@
|
||||||
|
|
||||||
{#if tab === 'audit'}
|
{#if tab === 'audit'}
|
||||||
{#if auditLoading}
|
{#if auditLoading}
|
||||||
<p class="empty">Lade Audit…</p>
|
<p class="empty">{$_('ai-workbench.list_view.audit_loading')}</p>
|
||||||
{:else if auditError}
|
{:else if auditError}
|
||||||
<p class="empty error">Fehler: {auditError}</p>
|
<p class="empty error">
|
||||||
|
{$_('ai-workbench.list_view.audit_error_prefix', { values: { error: auditError } })}
|
||||||
|
</p>
|
||||||
{:else if auditRows.length === 0}
|
{:else if auditRows.length === 0}
|
||||||
<p class="empty">
|
<p class="empty">
|
||||||
Keine serverseitigen Entschlüsselungen. Der mana-ai Runner hat für diese Mission noch keine
|
{$_('ai-workbench.list_view.audit_empty')}
|
||||||
Records gelesen — entweder ist kein Key-Grant erteilt, oder die Mission nutzt nur plaintext
|
|
||||||
Inputs (goals).
|
|
||||||
</p>
|
</p>
|
||||||
{:else}
|
{:else}
|
||||||
<table class="audit-table">
|
<table class="audit-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Zeit</th>
|
<th>{$_('ai-workbench.list_view.audit_col_time')}</th>
|
||||||
<th>Mission</th>
|
<th>{$_('ai-workbench.list_view.audit_col_mission')}</th>
|
||||||
<th>Record</th>
|
<th>{$_('ai-workbench.list_view.audit_col_record')}</th>
|
||||||
<th>Status</th>
|
<th>{$_('ai-workbench.list_view.audit_col_status')}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|
@ -230,8 +248,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
{:else if buckets.length === 0}
|
{:else if buckets.length === 0}
|
||||||
<p class="empty">
|
<p class="empty">
|
||||||
Noch keine AI-Aktivität. Sobald eine Mission läuft und Proposals approved werden, erscheinen
|
{$_('ai-workbench.list_view.timeline_empty')}
|
||||||
hier die Änderungen.
|
|
||||||
</p>
|
</p>
|
||||||
{:else}
|
{:else}
|
||||||
<ol class="timeline">
|
<ol class="timeline">
|
||||||
|
|
@ -251,8 +268,11 @@
|
||||||
<span class="agent-name">{bucketAgent?.name ?? b.agentDisplayName}</span>
|
<span class="agent-name">{bucketAgent?.name ?? b.agentDisplayName}</span>
|
||||||
<span class="mission-sep">·</span>
|
<span class="mission-sep">·</span>
|
||||||
{missionTitleById.get(b.missionId) ?? b.missionId}
|
{missionTitleById.get(b.missionId) ?? b.missionId}
|
||||||
<span class="event-count" title="{b.events.length} Änderungen in dieser Iteration"
|
<span
|
||||||
>{b.events.length}</span
|
class="event-count"
|
||||||
|
title={$_('ai-workbench.list_view.event_count_title', {
|
||||||
|
values: { n: b.events.length },
|
||||||
|
})}>{b.events.length}</span
|
||||||
>
|
>
|
||||||
</span>
|
</span>
|
||||||
{#if b.rationale}
|
{#if b.rationale}
|
||||||
|
|
@ -264,10 +284,14 @@
|
||||||
class="revert"
|
class="revert"
|
||||||
disabled={revertingKey !== null}
|
disabled={revertingKey !== null}
|
||||||
onclick={() => handleRevert(b.key, b.missionId, b.iterationId)}
|
onclick={() => handleRevert(b.key, b.missionId, b.iterationId)}
|
||||||
title="Alle Änderungen dieser Iteration zurücknehmen"
|
title={$_('ai-workbench.list_view.revert_title')}
|
||||||
>
|
>
|
||||||
<ArrowCounterClockwise size={13} weight="bold" />
|
<ArrowCounterClockwise size={13} weight="bold" />
|
||||||
<span>{revertingKey === b.key ? 'Läuft…' : 'Rückgängig'}</span>
|
<span
|
||||||
|
>{revertingKey === b.key
|
||||||
|
? $_('ai-workbench.list_view.revert_running')
|
||||||
|
: $_('ai-workbench.list_view.revert_label')}</span
|
||||||
|
>
|
||||||
</button>
|
</button>
|
||||||
</header>
|
</header>
|
||||||
<ul class="events">
|
<ul class="events">
|
||||||
|
|
@ -275,7 +299,11 @@
|
||||||
<li class="event">
|
<li class="event">
|
||||||
<span class="mod">{e.meta.appId}</span>
|
<span class="mod">{e.meta.appId}</span>
|
||||||
<span class="desc">{describeEvent(e)}</span>
|
<span class="desc">{describeEvent(e)}</span>
|
||||||
<a class="link" href={`/${e.meta.appId}`} title="Zum Modul">
|
<a
|
||||||
|
class="link"
|
||||||
|
href={`/${e.meta.appId}`}
|
||||||
|
title={$_('ai-workbench.list_view.event_link_title')}
|
||||||
|
>
|
||||||
<ArrowSquareOut size={11} />
|
<ArrowSquareOut size={11} />
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,6 @@
|
||||||
"apps/mana/apps/web/src/lib/modules/admin/tabs/UsersTab.svelte": 3,
|
"apps/mana/apps/web/src/lib/modules/admin/tabs/UsersTab.svelte": 3,
|
||||||
"apps/mana/apps/web/src/lib/modules/ai-health/ListView.svelte": 2,
|
"apps/mana/apps/web/src/lib/modules/ai-health/ListView.svelte": 2,
|
||||||
"apps/mana/apps/web/src/lib/modules/ai-insights/ListView.svelte": 4,
|
"apps/mana/apps/web/src/lib/modules/ai-insights/ListView.svelte": 4,
|
||||||
"apps/mana/apps/web/src/lib/modules/ai-workbench/ListView.svelte": 8,
|
|
||||||
"apps/mana/apps/web/src/lib/modules/articles/components/AddUrlForm.svelte": 4,
|
"apps/mana/apps/web/src/lib/modules/articles/components/AddUrlForm.svelte": 4,
|
||||||
"apps/mana/apps/web/src/lib/modules/articles/components/HighlightMenu.svelte": 5,
|
"apps/mana/apps/web/src/lib/modules/articles/components/HighlightMenu.svelte": 5,
|
||||||
"apps/mana/apps/web/src/lib/modules/articles/components/HomeSectionSources.svelte": 1,
|
"apps/mana/apps/web/src/lib/modules/articles/components/HomeSectionSources.svelte": 1,
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
"apps/mana/apps/web/src/lib/components/PwaUpdatePrompt.svelte": 3,
|
"apps/mana/apps/web/src/lib/components/PwaUpdatePrompt.svelte": 3,
|
||||||
"apps/mana/apps/web/src/lib/modules/ai-agents/ListView.svelte": 2,
|
"apps/mana/apps/web/src/lib/modules/ai-agents/ListView.svelte": 2,
|
||||||
"apps/mana/apps/web/src/lib/modules/ai-missions/ListView.svelte": 2,
|
"apps/mana/apps/web/src/lib/modules/ai-missions/ListView.svelte": 2,
|
||||||
|
"apps/mana/apps/web/src/lib/modules/ai-workbench/ListView.svelte": 1,
|
||||||
"apps/mana/apps/web/src/lib/modules/broadcast/views/DetailView.svelte": 1,
|
"apps/mana/apps/web/src/lib/modules/broadcast/views/DetailView.svelte": 1,
|
||||||
"apps/mana/apps/web/src/lib/modules/credits/ListView.svelte": 1,
|
"apps/mana/apps/web/src/lib/modules/credits/ListView.svelte": 1,
|
||||||
"apps/mana/apps/web/src/lib/modules/dreams/ListView.svelte": 1,
|
"apps/mana/apps/web/src/lib/modules/dreams/ListView.svelte": 1,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue