The stored blob is the inference server invocation, and it is no longer only a local server's: a hosted provider is addressed rather than configured. Display text only -- the table, routes and relations keep their names, so there is no migration and no broken links.
140 lines
No EOL
5.8 KiB
PHP
140 lines
No EOL
5.8 KiB
PHP
<?php
|
|
|
|
use App\Models\ServerConfig;
|
|
use Flux\Flux;
|
|
use Illuminate\Validation\Rule;
|
|
use Livewire\Attributes\Layout;
|
|
use Livewire\Component;
|
|
|
|
new #[Layout('components.layouts.app')] class extends Component {
|
|
public ?int $editingId = null;
|
|
|
|
public string $name = '';
|
|
|
|
public string $engine = 'llama.cpp';
|
|
|
|
public string $config_yaml = '';
|
|
|
|
public function create(): void
|
|
{
|
|
$this->reset(['editingId', 'name', 'config_yaml']);
|
|
$this->engine = 'llama.cpp';
|
|
$this->modal('config-form')->show();
|
|
}
|
|
|
|
public function edit(int $id): void
|
|
{
|
|
$config = ServerConfig::where('user_id', auth()->id())->findOrFail($id);
|
|
|
|
$this->editingId = $config->id;
|
|
$this->name = $config->name;
|
|
$this->engine = $config->engine;
|
|
$this->config_yaml = $config->config_yaml;
|
|
|
|
$this->modal('config-form')->show();
|
|
}
|
|
|
|
public function save(): void
|
|
{
|
|
$validated = $this->validate([
|
|
'name' => ['required', 'string', 'max:128',
|
|
Rule::unique('server_configs', 'name')->where('user_id', auth()->id())->ignore($this->editingId)],
|
|
'engine' => ['required', 'string', 'max:64'],
|
|
'config_yaml' => ['nullable', 'string', 'max:65535'],
|
|
]);
|
|
|
|
ServerConfig::updateOrCreate(
|
|
['id' => $this->editingId, 'user_id' => auth()->id()],
|
|
$validated
|
|
);
|
|
|
|
$this->modal('config-form')->close();
|
|
Flux::toast(variant: 'success', text: __('Inference args saved.'));
|
|
|
|
// Config identity is part of the session grouping key — re-summarize
|
|
// so linked runs regroup immediately.
|
|
Illuminate\Support\Facades\Artisan::call('festbench:summary');
|
|
}
|
|
|
|
public function delete(int $id): void
|
|
{
|
|
ServerConfig::where('user_id', auth()->id())->whereKey($id)->firstOrFail()->delete();
|
|
|
|
Flux::toast(variant: 'success', text: __('Inference args deleted.'));
|
|
Illuminate\Support\Facades\Artisan::call('festbench:summary');
|
|
}
|
|
|
|
public function with(): array
|
|
{
|
|
return [
|
|
'configs' => ServerConfig::where('user_id', auth()->id())->orderBy('name')->get(),
|
|
];
|
|
}
|
|
};
|
|
?>
|
|
|
|
<div class="flex flex-col gap-6">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<flux:heading size="xl">{{ __('Inference Args') }}</flux:heading>
|
|
<flux:subheading>{{ __('Named inference server invocations. Runs link to them; the identity keeps differently-tuned sessions in their own groups.') }}</flux:subheading>
|
|
</div>
|
|
<flux:button variant="primary" icon="plus" wire:click="create">{{ __('New inference args') }}</flux:button>
|
|
</div>
|
|
|
|
@if ($configs->isNotEmpty())
|
|
<flux:table>
|
|
<flux:table.columns>
|
|
<flux:table.column>{{ __('Name') }}</flux:table.column>
|
|
<flux:table.column>{{ __('Engine') }}</flux:table.column>
|
|
<flux:table.column class="text-right">{{ __('Linked runs') }}</flux:table.column>
|
|
<flux:table.column></flux:table.column>
|
|
</flux:table.columns>
|
|
|
|
<flux:table.rows>
|
|
@foreach ($configs as $config)
|
|
<flux:table.row :key="$config->id">
|
|
<flux:table.cell>{{ $config->name }}</flux:table.cell>
|
|
<flux:table.cell><flux:badge size="xs" color="sky">{{ $config->engine }}</flux:badge></flux:table.cell>
|
|
<flux:table.cell class="text-right font-mono text-sm">{{ $config->runs()->count() }}</flux:table.cell>
|
|
<flux:table.cell>
|
|
<div class="flex justify-end gap-1">
|
|
<flux:button size="xs" variant="ghost" icon="pencil-square" wire:click="edit({{ $config->id }})">{{ __('Edit') }}</flux:button>
|
|
<flux:button size="xs" variant="ghost" color="danger" icon="trash-2" wire:click="delete({{ $config->id }})">{{ __('Delete') }}</flux:button>
|
|
</div>
|
|
</flux:table.cell>
|
|
</flux:table.row>
|
|
@endforeach
|
|
</flux:table.rows>
|
|
</flux:table>
|
|
@else
|
|
<flux:callout variant="brand">
|
|
<flux:callout.heading>{{ __('No inference args yet') }}</flux:callout.heading>
|
|
<flux:callout.text>{{ __('Save your llama.cpp / vllm / hosted-provider invocation as a named entry so runs group by their tuning.') }}</flux:callout.text>
|
|
</flux:callout>
|
|
@endif
|
|
|
|
<flux:modal name="config-form" class="w-2xl">
|
|
<div class="flex flex-col gap-4">
|
|
<div>
|
|
<flux:heading size="lg">{{ $editingId ? __('Edit config') : __('New inference args') }}</flux:heading>
|
|
<flux:subheading>{{ __('Paste the full inference server invocation (YAML or argv list), or how a hosted model was addressed.') }}</flux:subheading>
|
|
</div>
|
|
|
|
<flux:input wire:model="name" :label="__('Name')" placeholder="{{ __('llama.cpp DFlash spec-decode, 131k ctx') }}" />
|
|
|
|
<flux:select wire:model="engine" :label="__('Engine')">
|
|
@foreach (['llama.cpp', 'vllm', 'unknown', 'other'] as $engine)
|
|
<flux:select.option value="{{ $engine }}">{{ $engine }}</flux:select.option>
|
|
@endforeach
|
|
</flux:select>
|
|
|
|
<flux:textarea wire:model="config_yaml" :label="__('Inference args')" rows="12" class="font-mono text-xs" placeholder="--host 0.0.0.0 --port 8000 -hf model:Q5_K_M -ngl 99 ..." />
|
|
|
|
<div class="flex justify-end gap-2">
|
|
<flux:button variant="ghost" wire:click="$toggle('config-form')">{{ __('Cancel') }}</flux:button>
|
|
<flux:button variant="primary" wire:click="save">{{ __('Save config') }}</flux:button>
|
|
</div>
|
|
</div>
|
|
</flux:modal>
|
|
</div>
|