festbench-web/resources/views/livewire/runs/show.blade.php

288 lines
18 KiB
PHP
Raw Normal View History

<?php
use App\Models\BenchmarkRun;
use Flux\Flux;
use Illuminate\Auth\Access\AuthorizationException;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Layout;
use Livewire\Component;
new #[Layout('components.layouts.app')] class extends Component {
public ?string $notes = null;
public ?string $runId = null;
/** Depth-sweep drill-in filter: only this thinking level's rows. */
public ?string $level = null;
public function mount(string $run): void
{
$this->runId = $run;
$this->level = request()->query('level');
}
#[Computed]
public function run(): BenchmarkRun
{
$run = BenchmarkRun::where('run_id', $this->runId)
->visibleTo(auth()->user())
->firstOrFail();
if ($run->isOwnedBy(auth()->user()) && $this->notes === null) {
$this->notes = $run->notes;
}
return $run;
}
public function saveNotes(): void
{
$this->authorizeOwner();
$this->validate([
'notes' => ['nullable', 'string', 'max:65535'],
]);
$this->run->fill(['notes' => $this->notes])->save();
Flux::toast(variant: 'success', text: __('Notes saved.'));
}
public function toggleVisibility(): void
{
$this->authorizeOwner();
$this->run->fill([
'visibility' => $this->run->visibility === BenchmarkRun::VISIBILITY_PUBLIC
? BenchmarkRun::VISIBILITY_PRIVATE
: BenchmarkRun::VISIBILITY_PUBLIC,
])->save();
Flux::toast(variant: 'success', text: __('Visibility updated.'));
}
private function authorizeOwner(): void
{
if (! $this->run->isOwnedBy(auth()->user())) {
throw new AuthorizationException;
}
}
};
?>
<div class="flex flex-col gap-6">
<div class="flex flex-wrap items-center justify-between gap-3">
<div>
<flux:heading size="xl">{{ $this->run->suite }} <span class="text-zinc-400">·</span> <span class="font-mono text-lg">{{ $this->run->model_id }}</span></flux:heading>
<flux:subheading>
@if ($this->run->label)
<flux:badge color="amber" size="xs">{{ $this->run->label }}</flux:badge>
@else
@if ($this->run->model_quant)<flux:badge color="zinc" size="xs">{{ $this->run->model_quant }}</flux:badge> @endif
@if ($this->run->engine)<flux:badge color="sky" size="xs">{{ $this->run->engine }}</flux:badge> @endif
@endif
@if ($this->run->visibility === 'private')<flux:badge color="rose" size="xs">{{ __('Private') }}</flux:badge> @endif
{{ optional($this->run->ran_at)->format('Y-m-d H:i') }}
</flux:subheading>
</div>
<div class="flex items-center gap-2">
<flux:button variant="ghost" icon="arrow-left" href="{{ route('home') }}" wire:navigate>{{ __('Sessions') }}</flux:button>
@if ($this->run->isOwnedBy(auth()->user()))
<flux:button
size="sm"
:variant="$this->run->visibility === 'public' ? 'subtle' : 'primary'"
wire:click="toggleVisibility"
icon="{{ $this->run->visibility === 'public' ? 'lock-closed' : 'lock-open' }}"
>
{{ $this->run->visibility === 'public' ? __('Make private') : __('Make public') }}
</flux:button>
@endif
</div>
</div>
<flux:card>
<flux:heading size="md">{{ __('Run metadata') }}</flux:heading>
<div class="mt-4 grid grid-cols-2 gap-x-8 gap-y-2 text-sm md:grid-cols-4">
<div><span class="text-zinc-500">{{ __('Engine build') }}</span><div class="font-mono">{{ $this->run->engine_build ?? '—' }}</div></div>
<div><span class="text-zinc-500">{{ __('Context') }}</span><div class="font-mono">{{ number_format($this->run->n_ctx ?? 0) }}</div></div>
<div><span class="text-zinc-500">{{ __('Slots') }}</span><div class="font-mono">{{ $this->run->slots ?? '—' }}</div></div>
<div><span class="text-zinc-500">{{ __('GPU') }}</span><div>{{ ($this->run->gpu_count ? $this->run->gpu_count.'x ' : '').($this->run->gpu ?? 'unknown') }}</div></div>
<div><span class="text-zinc-500">{{ __('Inference host') }}</span><div><flux:badge size="xs" color="sky">{{ $this->run->engine ?? 'unknown' }}</flux:badge></div></div>
<div class="col-span-2"><span class="text-zinc-500">{{ __('Image digest') }}</span><div class="break-all font-mono text-xs">{{ $this->run->image_digest ?? '—' }}</div></div>
@if ($this->run->serverConfig)
<div class="col-span-2"><span class="text-zinc-500">{{ __('Server config') }}</span><div><flux:badge size="xs" color="amber">{{ $this->run->serverConfig->name }}</flux:badge></div></div>
@endif
@if ($this->run->suite_versions)
<div class="col-span-2"><span class="text-zinc-500">{{ __('Suite versions') }}</span><div class="break-all font-mono text-xs">{{ json_encode($this->run->suite_versions) }}</div></div>
@endif
@if ($this->run->validators)
<div class="col-span-2"><span class="text-zinc-500">{{ __('Validators') }}</span><div class="break-all font-mono text-xs">{{ json_encode($this->run->validators) }}</div></div>
@endif
</div>
</flux:card>
@if ($this->run->isOwnedBy(auth()->user()))
<flux:card>
<flux:heading size="md">{{ __('Your notes') }}</flux:heading>
<flux:subheading>{{ __("Festbench data can't be modified — add your own context here.") }}</flux:subheading>
<flux:textarea wire:model="notes" rows="4" class="mt-4" placeholder="{{ __('Hardware details, environment, caveats…') }}" />
<div class="mt-3">
<flux:button size="sm" variant="primary" wire:click="saveNotes">{{ __('Save notes') }}</flux:button>
</div>
</flux:card>
@endif
<div>
<flux:heading size="md">{{ $this->run->suite === 'scenariobench' ? __('Scenario results') : ($this->run->suite === 'depthsweep' ? __('Depth sweep results') : __('Task results')) }}</flux:heading>
<div class="mt-3">
@if ($this->run->suite === 'scenariobench')
<flux:table>
<flux:table.columns>
<flux:table.column>{{ __('Scenario') }}</flux:table.column>
<flux:table.column>{{ __('Level') }}</flux:table.column>
<flux:table.column>{{ __('Pass') }}</flux:table.column>
<flux:table.column>{{ __('Outcome') }}</flux:table.column>
<flux:table.column class="text-right">{{ __('Wall (s)') }}</flux:table.column>
<flux:table.column class="text-right">{{ __('Turns') }}</flux:table.column>
<flux:table.column class="text-right">{{ __('Commands') }}</flux:table.column>
<flux:table.column class="text-right">{{ __('Submissions') }}</flux:table.column>
<flux:table.column class="text-right">{{ __('Rejected') }}</flux:table.column>
<flux:table.column class="text-right">{{ __('t first sub') }}</flux:table.column>
<flux:table.column class="text-right">{{ __('t pass') }}</flux:table.column>
<flux:table.column class="text-right">{{ __('Gen tokens') }}</flux:table.column>
</flux:table.columns>
<flux:table.rows>
@foreach ($this->run->suiteResults() as $result)
<flux:table.row :key="$result->id">
<flux:table.cell>{{ $result->scenario }}</flux:table.cell>
<flux:table.cell>{{ $result->level ?? '—' }}</flux:table.cell>
<flux:table.cell>
@if ($result->ok)
<flux:icon name="check-circle" variant="solid" class="size-4 text-emerald-500" />
@elseif ($result->ok === null)
<span class="text-zinc-400">—</span>
@else
<flux:icon name="x-circle" variant="solid" class="size-4 text-red-500" />
@endif
</flux:table.cell>
<flux:table.cell><flux:badge size="xs" color="{{ $result->outcome === 'passed' ? 'emerald' : 'rose' }}">{{ $result->outcome ?? '—' }}</flux:badge></flux:table.cell>
<flux:table.cell class="text-right font-mono text-sm">{{ number_format($result->wall, 1) }}</flux:table.cell>
<flux:table.cell class="text-right font-mono text-sm">{{ number_format($result->turns) }}</flux:table.cell>
<flux:table.cell class="text-right font-mono text-sm">{{ number_format($result->commands) }}</flux:table.cell>
<flux:table.cell class="text-right font-mono text-sm">{{ number_format($result->submissions) }}</flux:table.cell>
<flux:table.cell class="text-right font-mono text-sm">{{ number_format($result->rejected) }}</flux:table.cell>
<flux:table.cell class="text-right font-mono text-sm">{{ number_format($result->t_first_sub, 1) }}</flux:table.cell>
<flux:table.cell class="text-right font-mono text-sm">{{ $result->t_pass !== null ? number_format($result->t_pass, 1) : '—' }}</flux:table.cell>
<flux:table.cell class="text-right font-mono text-sm">{{ number_format($result->gen_tokens) }}</flux:table.cell>
</flux:table.row>
@endforeach
</flux:table.rows>
</flux:table>
@elseif ($this->run->suite === 'depthsweep')
@if ($this->level)
<div class="mb-3 flex items-center gap-2">
<flux:text size="sm">{{ __('Thinking level') }}:</flux:text>
<flux:badge size="xs" color="sky">{{ $this->level }}</flux:badge>
<flux:button size="xs" variant="ghost" icon="x-mark" href="{{ route('runs.show', $this->run->run_id) }}" wire:navigate>{{ __('show all levels') }}</flux:button>
</div>
@endif
<flux:table>
<flux:table.columns>
<flux:table.column>{{ __('Depth') }}</flux:table.column>
<flux:table.column>{{ __('Task') }}</flux:table.column>
<flux:table.column>{{ __('Level') }}</flux:table.column>
<flux:table.column>{{ __('Found') }}</flux:table.column>
<flux:table.column>{{ __('Needle') }}</flux:table.column>
<flux:table.column class="text-right">{{ __('TTFT') }}</flux:table.column>
<flux:table.column class="text-right">{{ __('Wall') }}</flux:table.column>
<flux:table.column class="text-right">{{ __('Visible') }}</flux:table.column>
</flux:table.columns>
<flux:table.rows>
@foreach ($this->run->suiteResults($this->level) as $result)
<flux:table.row :key="$result->id">
<flux:table.cell><flux:badge size="xs" color="sky">{{ $result->section }}</flux:badge></flux:table.cell>
<flux:table.cell>{{ $result->task }}</flux:table.cell>
<flux:table.cell>{{ $result->level ?? '—' }}</flux:table.cell>
<flux:table.cell>
@if ($result->ok)
<flux:icon name="check-circle" variant="solid" class="size-4 text-emerald-500" />
@else
<flux:icon name="x-circle" variant="solid" class="size-4 text-red-500" />
@endif
</flux:table.cell>
<flux:table.cell class="font-mono text-xs">{{ $result->detail ?? '—' }}</flux:table.cell>
<flux:table.cell class="text-right font-mono text-sm">{{ number_format($result->ttft, 2) }}</flux:table.cell>
<flux:table.cell class="text-right font-mono text-sm">{{ number_format($result->wall, 2) }}</flux:table.cell>
<flux:table.cell class="text-right font-mono text-sm">{{ number_format($result->visible) }}</flux:table.cell>
</flux:table.row>
@endforeach
</flux:table.rows>
</flux:table>
@else
<flux:table>
<flux:table.columns>
@if (in_array($this->run->suite, ['softbench', 'prosebench', 'psychbench']))
<flux:table.column>{{ __('Section') }}</flux:table.column>
@endif
<flux:table.column>{{ __('Task') }}</flux:table.column>
<flux:table.column>{{ __('Level') }}</flux:table.column>
<flux:table.column>{{ __('Pass') }}</flux:table.column>
@if (in_array($this->run->suite, ['prosebench', 'psychbench']))
<flux:table.column class="text-right">{{ __('Checks') }}</flux:table.column>
@endif
@if ($this->run->suite === 'infrabench')
<flux:table.column>{{ __('Kind') }}</flux:table.column>
@endif
<flux:table.column class="text-right">{{ __('User tok/s') }}</flux:table.column>
<flux:table.column class="text-right">{{ __('Decode') }}</flux:table.column>
<flux:table.column class="text-right">{{ __('TTFT') }}</flux:table.column>
<flux:table.column class="text-right">{{ __('t_answer') }}</flux:table.column>
<flux:table.column class="text-right">{{ __('Wall') }}</flux:table.column>
<flux:table.column class="text-right">{{ __('Reasoning') }}</flux:table.column>
<flux:table.column class="text-right">{{ __('Visible') }}</flux:table.column>
<flux:table.column>{{ __('Finish') }}</flux:table.column>
</flux:table.columns>
<flux:table.rows>
@foreach ($this->run->suiteResults() as $result)
<flux:table.row :key="$result->id">
@if (in_array($this->run->suite, ['softbench', 'prosebench', 'psychbench']))
<flux:table.cell>{{ $result->section ?? '—' }}</flux:table.cell>
@endif
<flux:table.cell>{{ $result->task }}</flux:table.cell>
<flux:table.cell>{{ $result->level ?? '—' }}</flux:table.cell>
<flux:table.cell>
@if ($result->ok)
<flux:icon name="check-circle" variant="solid" class="size-4 text-emerald-500" />
@elseif ($result->ok === null)
<span class="text-zinc-400">—</span>
@else
<flux:icon name="x-circle" variant="solid" class="size-4 text-red-500" />
@endif
</flux:table.cell>
@if (in_array($this->run->suite, ['prosebench', 'psychbench']))
<flux:table.cell class="text-right font-mono text-sm">{{ $result->checks_passed !== null ? $result->checks_passed.'/'.$result->checks_total : '—' }}</flux:table.cell>
@endif
@if ($this->run->suite === 'infrabench')
<flux:table.cell>{{ $result->kind ?? '—' }}</flux:table.cell>
@endif
<flux:table.cell class="text-right font-mono text-sm">{{ number_format($result->user_tps, 1) }}</flux:table.cell>
<flux:table.cell class="text-right font-mono text-sm">{{ number_format($result->decode, 1) }}</flux:table.cell>
<flux:table.cell class="text-right font-mono text-sm">{{ number_format($result->ttft, 2) }}</flux:table.cell>
<flux:table.cell class="text-right font-mono text-sm">{{ number_format($result->t_answer, 2) }}</flux:table.cell>
<flux:table.cell class="text-right font-mono text-sm">{{ number_format($result->wall, 2) }}</flux:table.cell>
<flux:table.cell class="text-right font-mono text-sm">{{ number_format($result->reasoning) }}</flux:table.cell>
<flux:table.cell class="text-right font-mono text-sm">{{ number_format($result->visible) }}</flux:table.cell>
<flux:table.cell><flux:badge size="xs" color="{{ $result->finish === 'stop' ? 'emerald' : 'amber' }}">{{ $result->finish ?? '—' }}</flux:badge></flux:table.cell>
</flux:table.row>
@endforeach
</flux:table.rows>
</flux:table>
@endif
</div>
</div>
</div>