# ModelRoutingEditor

> Admin editor for AI model routing with an automatic-routing switch, a task class to model and fallback route table, an active backend switch and provider or node registration with modality chips.

Source: https://docs.nasaqui.com/components/model-routing-editor

## Install

```bash
npx shadcn@latest add https://docs.nasaqui.com/r/model-routing-editor.json
```

Decides which model does which job. **Automatic routing** lets the system choose; the **route table** sets the preferred model and a fallback per task class (chat, summaries, image understanding). Models are filtered by the task's modality, so an image task only lists models that see images. Below it, **providers and nodes** are listed with what each can do (text, vision, audio, image, embedding), one is chosen as the active backend, and a dialog registers new ones.

## When to use

- An admin screen for an AI product that routes work across several models or hosts.
- Registering a self-hosted node next to cloud providers.

## When not to use

- Picking a model for one run or one chat: use [`AiModelPicker`](https://docs.nasaqui.com/components/ai-model-picker).
- Reporting what the models cost: use [`AiUsageCost`](https://docs.nasaqui.com/components/ai-usage-cost).

## Import

```tsx
import { ModelRoutingEditor, type RoutingValue } from "@fadymondy/nasaq/web";
```

## Quick start

```tsx
import { ModelRoutingEditor } from "@fadymondy/nasaq/web";

export function Routing() {
  return (
    <ModelRoutingEditor
      taskClasses={[{ id: "chat", label: "Chat" }, { id: "vision", label: "Image understanding", modality: "vision" }]}
      models={[{ id: "sonnet", label: "Sonnet 5.5", modalities: ["text", "vision"] }, { id: "haiku", label: "Haiku 4.5" }]}
      providers={[{ id: "cloud", name: "Cloud API", kind: "cloud", modalities: ["text", "vision"], status: "online" }]}
      defaultValue={{ auto: false, routes: { chat: { model: "sonnet", fallback: "haiku" } }, backend: "cloud" }}
      onSave={async (value) => {
        await api.saveRouting(value);
      }}
      onRegisterProvider={async (input) => {
        await api.addProvider(input);
      }}
    />
  );
}
```

## Anatomy

```
ModelRoutingEditor   data-slot="model-routing-editor"  (a form)
  Card               automatic routing Switch
  Card               data-slot="routing-routes"  one data-slot="routing-route" per task: Model + Fallback selects
  Card               data-slot="routing-providers"  backend ToggleGroup, one data-slot="routing-provider" per provider
  routing-footer     data-slot="routing-footer"  Discard and Save (when onSave is set)
  Dialog             data-slot="routing-register"  name, type, endpoint, modalities
```

## API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `taskClasses` | `RoutingTaskClass[]` | required | `{ id, label, description?, modality? }`. |
| `models` | `RoutingModel[]` | required | `{ id, label, modalities? }`. A model with no modalities counts as text. |
| `providers` | `RoutingProvider[]` | required | `{ id, name, kind?, endpoint?, modalities, status? }`. |
| `value` / `defaultValue` | `RoutingValue` | auto on, no routes | `{ auto, routes: { [taskId]: { model?, fallback? } }, backend? }`. |
| `onValueChange` | `(value) => void` | none | Called on every edit. |
| `onSave` | `(value) => Promise<void \| { error? }>` | none | Adds the footer. Return `{ error }` or throw to show a failure. |
| `onRegisterProvider` | `(input) => Promise<void \| { error? }>` | none | Shows the Register button. Add the provider to `providers` when it resolves. |
| `onRemoveProvider` | `(id) => Promise<void \| { error? }>` | none | Shows a remove button per provider. |
| `disabled` | `boolean` | `false` | Read-only editor. |
| `labels` | `ModelRoutingEditorLabels` | en / ar | Override any string. |

With automatic routing off every task class needs a model; a fallback must differ from its model. Save is blocked and the rows are marked until fixed. The pure helpers `modelsFor`, `routingIssues`, `routingEquals`, `toggleModality`, `validateProvider` and `isValidEndpoint` are exported.

## Examples

Read-only view:

```tsx
import { ModelRoutingEditor } from "@fadymondy/nasaq/web";

export const View = (props) => <ModelRoutingEditor {...props} disabled />;
```

Arabic: use `NasaqProvider locale="ar"`. Model names, provider names and endpoints stay left to right.

## Accessibility

Every select is named with its task class and role ("Chat: Model"). Errors sit under the field and are announced by Base UI Field; the form-level warning explains why Save is blocked. The backend choice and the modalities are labelled segmented groups; the modality chips of each provider are a labelled list with text, never colour alone. The register dialog is modal and returns focus to its button.

## RTL & i18n

- Logical properties; the route grid, the dialog and the toggles follow the reading direction.
- Model and provider names and endpoints are `dir="ltr"`; the endpoint input is forced left to right.
- Every string has an English and Arabic default.

## Styling & tokens

- Status badges use `--nq-success` and `--nq-danger`; cards use `--card`.
- Target `[data-slot="routing-route"][data-invalid]` for rows with a problem, `[data-slot="routing-provider"][data-active]` for the active backend.

## Do / Don't

- Do set a fallback on important task classes.
- Do keep model names as plain text without vendor logos.
- Don't register a provider without saying what it can do; a modality is required.

## Related

- [`AiModelPicker`](https://docs.nasaqui.com/components/ai-model-picker)
- [`AiUsageCost`](https://docs.nasaqui.com/components/ai-usage-cost)
- [`Select`](https://docs.nasaqui.com/components/select)

## Lab

https://docs.nasaqui.com/?path=/docs/components-ai-agents-model-routing-editor--docs

## Code

### React

```tsx
import { ModelRoutingEditor } from "@fadymondy/nasaq/web";

export function Routing() {
  return (
    <ModelRoutingEditor
      taskClasses={[{ id: "chat", label: "Chat" }, { id: "vision", label: "Image understanding", modality: "vision" }]}
      models={[{ id: "sonnet", label: "Sonnet 5.5", modalities: ["text", "vision"] }, { id: "haiku", label: "Haiku 4.5" }]}
      providers={[{ id: "cloud", name: "Cloud API", kind: "cloud", modalities: ["text", "vision"], status: "online" }]}
      defaultValue={{ auto: false, routes: { chat: { model: "sonnet", fallback: "haiku" } }, backend: "cloud" }}
      onSave={async (value) => {
        await api.saveRouting(value);
      }}
      onRegisterProvider={async (input) => {
        await api.addProvider(input);
      }}
    />
  );
}
```

### shadcn

```tsx
import { ModelRoutingEditor } from "@/components/ui/model-routing-editor";

export function Routing() {
  return (
    <ModelRoutingEditor
      taskClasses={[{ id: "chat", label: "Chat" }, { id: "vision", label: "Image understanding", modality: "vision" }]}
      models={[{ id: "sonnet", label: "Sonnet 5.5", modalities: ["text", "vision"] }, { id: "haiku", label: "Haiku 4.5" }]}
      providers={[{ id: "cloud", name: "Cloud API", kind: "cloud", modalities: ["text", "vision"], status: "online" }]}
      defaultValue={{ auto: false, routes: { chat: { model: "sonnet", fallback: "haiku" } }, backend: "cloud" }}
      onSave={async (value) => {
        await api.saveRouting(value);
      }}
      onRegisterProvider={async (input) => {
        await api.addProvider(input);
      }}
    />
  );
}
```

### Vue

```vue
<script setup lang="ts">
import { NqModelRoutingEditor, type RoutingValue } from "@fadymondy/nasaq/vue";

const taskClasses = [
  { id: "chat", label: "Chat" },
  { id: "vision", label: "Image understanding", modality: "vision" as const },
];
const models = [
  { id: "sonnet", label: "Sonnet 5.5", modalities: ["text", "vision"] as const },
  { id: "haiku", label: "Haiku 4.5" },
];
const providers = [{ id: "cloud", name: "Cloud API", kind: "cloud" as const, modalities: ["text", "vision"] as const, status: "online" as const }];
const defaultValue: RoutingValue = { auto: false, routes: { chat: { model: "sonnet", fallback: "haiku" } }, backend: "cloud" };

async function save(value: RoutingValue) {
  await Promise.resolve(value);
}
async function register(input: { name: string }) {
  await Promise.resolve(input);
}
</script>

<template>
  <NqModelRoutingEditor
    :task-classes="taskClasses"
    :models="models"
    :providers="providers"
    :default-value="defaultValue"
    :on-save="save"
    :on-register-provider="register"
  />
</template>
```

### Blade

```blade
@php
    $tasks = [
        ['id' => 'chat', 'label' => 'Chat', 'description' => 'Conversations with the assistant'],
        ['id' => 'vision', 'label' => 'Image understanding', 'modality' => 'vision'],
    ];
    $models = [
        ['id' => 'sonnet', 'label' => 'Sonnet', 'modalities' => ['text', 'vision']],
        ['id' => 'haiku', 'label' => 'Haiku'],
    ];
    $providers = [
        ['id' => 'cloud', 'name' => 'Cloud API', 'kind' => 'cloud', 'endpoint' => 'https://api.example.com', 'modalities' => ['text', 'vision'], 'status' => 'online'],
        ['id' => 'edge', 'name' => 'Edge node', 'kind' => 'node', 'endpoint' => 'https://edge.example.com', 'modalities' => ['text'], 'status' => 'offline'],
    ];
    $value = ['auto' => false, 'routes' => ['chat' => ['model' => 'sonnet', 'fallback' => 'haiku'], 'vision' => ['model' => 'sonnet']], 'backend' => 'cloud'];
@endphp
<x-nq::model-routing-editor :task-classes="$tasks" :models="$models" :providers="$providers" :value="$value"
    x-on:nq-routing-save="$event.detail.waitUntil(Promise.resolve())"
    x-on:nq-routing-register="$event.detail.waitUntil(Promise.resolve())"
    x-on:nq-routing-remove-provider="$event.detail.waitUntil(Promise.resolve())" />
```

### HTML + Alpine

```html
<form data-slot="model-routing-editor" novalidate x-data="nqModelRoutingEditor(JSON.parse('{\u0022tasks\u0022:[{\u0022id\u0022:\u0022chat\u0022,\u0022modality\u0022:\u0022text\u0022},{\u0022id\u0022:\u0022vision\u0022,\u0022modality\u0022:\u0022vision\u0022}],\u0022models\u0022:[{\u0022id\u0022:\u0022sonnet\u0022,\u0022modalities\u0022:[\u0022text\u0022,\u0022vision\u0022]},{\u0022id\u0022:\u0022haiku\u0022,\u0022modalities\u0022:[]}],\u0022providers\u0022:[{\u0022id\u0022:\u0022cloud\u0022,\u0022name\u0022:\u0022Cloud API\u0022},{\u0022id\u0022:\u0022edge\u0022,\u0022name\u0022:\u0022Edge node\u0022}],\u0022value\u0022:{\u0022auto\u0022:false,\u0022routes\u0022:{\u0022chat\u0022:{\u0022model\u0022:\u0022sonnet\u0022,\u0022fallback\u0022:\u0022haiku\u0022},\u0022vision\u0022:{\u0022model\u0022:\u0022sonnet\u0022}},\u0022backend\u0022:\u0022cloud\u0022},\u0022labels\u0022:{\u0022missing\u0022:\u0022Choose a model for this task.\u0022,\u0022same\u0022:\u0022The fallback must differ from the model.\u0022,\u0022unknown\u0022:\u0022This model is no longer available.\u0022,\u0022dirty\u0022:\u0022Unsaved routing changes\u0022,\u0022saved\u0022:\u0022Saved\u0022,\u0022failed\u0022:\u0022Could not save. Try again.\u0022,\u0022registerFailed\u0022:\u0022Could not register the provider. Try again.\u0022,\u0022nameRequired\u0022:\u0022Enter a name.\u0022,\u0022nameDuplicate\u0022:\u0022A provider with this name already exists.\u0022,\u0022endpointRequired\u0022:\u0022Enter the endpoint URL.\u0022,\u0022endpointInvalid\u0022:\u0022Enter a full address starting with http:\\\/\\\/ or https:\\\/\\\/.\u0022,\u0022modalitiesRequired\u0022:\u0022Choose at least one modality.\u0022}}'))" x-on:submit.prevent="submit()" x-on:nq-routing-save="$event.detail.waitUntil(Promise.resolve())" x-on:nq-routing-register="$event.detail.waitUntil(Promise.resolve())" x-on:nq-routing-remove-provider="$event.detail.waitUntil(Promise.resolve())" class="flex min-w-0 flex-col gap-4">
    <div data-slot="card" class="flex flex-col gap-4 rounded-card border border-border bg-card py-4 text-card-foreground"><div data-slot="card-header" class="grid auto-rows-min items-start gap-1 px-4 has-data-[slot=card-action]:grid-cols-[1fr_auto]"><h3 data-slot="card-title" class="text-foreground text-h3">Automatic routing</h3>
            <div data-slot="card-description" id="nq-routing-000001-auto-hint" class="text-body-sm text-muted-foreground">Pick the best model for each task class. Your routes below are the preferred choices.</div>
            <div data-slot="card-action" class="col-start-2 row-span-2 row-start-1 self-start justify-self-end"><button type="button" role="switch" data-slot="switch" x-data="nqSwitch(false)" x-modelable="checked" x-bind="root"
    aria-checked="false"
     data-unchecked                 aria-label="Automatic routing" aria-describedby="nq-routing-000001-auto-hint" x-model="auto" x-bind:disabled="null" x-bind:data-disabled="null" class="relative inline-flex h-5 w-9 shrink-0 items-center rounded-full border border-transparent bg-nq-line-strong p-0.5 outline-none transition-colors duration-150 ease-nq data-checked:bg-primary focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus data-disabled:cursor-not-allowed data-disabled:opacity-50">
    <span data-slot="switch-thumb"  data-unchecked         x-bind:data-checked="checked ? '' : undefined" x-bind:data-unchecked="checked ? undefined : ''"
        class="block size-4 rounded-full bg-background shadow-xs data-checked:bg-primary-foreground transition-[translate] duration-150 ease-nq data-checked:translate-x-3.5 rtl:data-checked:-translate-x-3.5"></span>
    </button></div></div></div>
    <div data-slot="card" class="flex flex-col gap-4 rounded-card border border-border bg-card py-4 text-card-foreground"><div data-slot="card-header" class="grid auto-rows-min items-start gap-1 px-4 has-data-[slot=card-action]:grid-cols-[1fr_auto]"><h3 data-slot="card-title" class="text-foreground text-h3">Routes</h3>
            <div data-slot="card-description" class="text-body-sm text-muted-foreground">Which model handles each kind of task, and what to try if it fails.</div></div>
        <div data-slot="card-content" class="px-4"><ul data-slot="routing-routes" class="m-0 flex list-none flex-col divide-y divide-border p-0">
                                                        <li data-slot="routing-route" data-task="chat" x-bind:data-invalid="flags[0].any ? '' : null"
                        class="grid gap-3 py-3 first:pt-0 last:pb-0 md:grid-cols-[minmax(0,1fr)_minmax(0,1fr)_minmax(0,1fr)] md:items-start">
                        <div class="flex min-w-0 flex-col gap-0.5">
                            <span class="text-label text-foreground">Chat</span>
                            <span class="text-caption text-muted-foreground">Conversations with the assistant</span>                                                    </div>
                        <div data-slot="field" x-data="nqField(false)" x-modelable="invalid" x-id="['nq-field']"
         data-valid     x-model="flags[0].model" class="flex flex-col gap-1.5 min-w-0">
    <label data-slot="field-label"  class="text-label text-foreground data-disabled:opacity-50">Model</label>
                            <div data-slot="select" x-data="nqSelect('sonnet', false)" x-modelable="value" x-id="['nq-select']" x-model="rows[0].model" class="contents">
    <button data-slot="select-trigger" x-ref="trigger" x-bind="trigger"
        aria-label="Chat: Model" class="flex h-control w-full min-w-0 items-center justify-between gap-2 rounded-control border border-input bg-card px-3 text-body text-foreground min-h-[var(--nq-touch-min,0px)] cursor-default select-none outline-none transition-colors duration-150 ease-nq focus-visible:border-nq-focus focus-visible:outline-1 focus-visible:outline-nq-focus data-popup-open:border-nq-focus data-invalid:border-nq-danger disabled:cursor-not-allowed disabled:opacity-50 pointer-coarse:text-[16px]">
    <span data-slot="select-value" x-text="label() ?? 'Choose a model'" :data-placeholder="empty() ? '' : undefined"
    class="min-w-0 flex-1 truncate text-start data-placeholder:text-muted-foreground"></span>
    <span class="flex shrink-0 text-muted-foreground [&_svg]:size-4"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="m7 15 5 5 5-5"/>
  <path d="m7 9 5-5 5 5"/>
</svg></span>
</button>
                                <template x-teleport="body">
    <div data-slot="select-content" x-ref="popup" x-bind="popup" x-nq-presence="open" x-anchor.bottom-start.offset.4="$refs.trigger"
        class="z-50 min-w-[var(--anchor-width)] max-h-[var(--available-height)] overflow-y-auto rounded-floating border border-border bg-popover p-1.5 text-popover-foreground shadow-floating outline-none transition-opacity duration-150 ease-nq data-starting-style:opacity-0 data-ending-style:opacity-0">
        <div data-slot="select-item" x-bind="item('sonnet', false)"
    class="relative flex h-nav-row min-h-[var(--nq-touch-min,0px)] cursor-default select-none items-center gap-2.5 rounded-control ps-8 pe-2.5 text-body-sm text-foreground outline-none data-highlighted:bg-nq-selected data-disabled:pointer-events-none data-disabled:opacity-50">
    <span aria-hidden="true" class="absolute start-2.5 inline-flex size-4 items-center justify-center">
        <span x-show="isSelected('sonnet')" x-cloak class="contents"><svg class="size-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M20 6 9 17l-5-5"/>
</svg></span>
    </span>
    <span data-slot="select-item-text" class="min-w-0 flex-1 truncate"><bdi dir="ltr">Sonnet</bdi></span>
</div>
                                                                            <div data-slot="select-item" x-bind="item('haiku', false)"
    class="relative flex h-nav-row min-h-[var(--nq-touch-min,0px)] cursor-default select-none items-center gap-2.5 rounded-control ps-8 pe-2.5 text-body-sm text-foreground outline-none data-highlighted:bg-nq-selected data-disabled:pointer-events-none data-disabled:opacity-50">
    <span aria-hidden="true" class="absolute start-2.5 inline-flex size-4 items-center justify-center">
        <span x-show="isSelected('haiku')" x-cloak class="contents"><svg class="size-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M20 6 9 17l-5-5"/>
</svg></span>
    </span>
    <span data-slot="select-item-text" class="min-w-0 flex-1 truncate"><bdi dir="ltr">Haiku</bdi></span>
</div>
    </div>
</template>
    </div>
                            <div data-slot="field-error" role="alert" x-show="invalid"  style="display: none"  class="text-caption text-nq-danger-text"><span x-text="issueText(0)"></span></div>
</div>
                        <div data-slot="field" x-data="nqField(false)" x-modelable="invalid" x-id="['nq-field']"
         data-valid     x-model="flags[0].same" class="flex flex-col gap-1.5 min-w-0">
    <label data-slot="field-label"  class="text-label text-foreground data-disabled:opacity-50">Fallback</label>
                            <div data-slot="select" x-data="nqSelect('haiku', false)" x-modelable="value" x-id="['nq-select']" x-model="rows[0].fallback" class="contents">
    <button data-slot="select-trigger" x-ref="trigger" x-bind="trigger"
        aria-label="Chat: Fallback" class="flex h-control w-full min-w-0 items-center justify-between gap-2 rounded-control border border-input bg-card px-3 text-body text-foreground min-h-[var(--nq-touch-min,0px)] cursor-default select-none outline-none transition-colors duration-150 ease-nq focus-visible:border-nq-focus focus-visible:outline-1 focus-visible:outline-nq-focus data-popup-open:border-nq-focus data-invalid:border-nq-danger disabled:cursor-not-allowed disabled:opacity-50 pointer-coarse:text-[16px]">
    <span data-slot="select-value" x-text="label() ?? ''" :data-placeholder="empty() ? '' : undefined"
    class="min-w-0 flex-1 truncate text-start data-placeholder:text-muted-foreground"></span>
    <span class="flex shrink-0 text-muted-foreground [&_svg]:size-4"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="m7 15 5 5 5-5"/>
  <path d="m7 9 5-5 5 5"/>
</svg></span>
</button>
                                <template x-teleport="body">
    <div data-slot="select-content" x-ref="popup" x-bind="popup" x-nq-presence="open" x-anchor.bottom-start.offset.4="$refs.trigger"
        class="z-50 min-w-[var(--anchor-width)] max-h-[var(--available-height)] overflow-y-auto rounded-floating border border-border bg-popover p-1.5 text-popover-foreground shadow-floating outline-none transition-opacity duration-150 ease-nq data-starting-style:opacity-0 data-ending-style:opacity-0">
        <div data-slot="select-item" x-bind="item('__none__', false)"
    class="relative flex h-nav-row min-h-[var(--nq-touch-min,0px)] cursor-default select-none items-center gap-2.5 rounded-control ps-8 pe-2.5 text-body-sm text-foreground outline-none data-highlighted:bg-nq-selected data-disabled:pointer-events-none data-disabled:opacity-50">
    <span aria-hidden="true" class="absolute start-2.5 inline-flex size-4 items-center justify-center">
        <span x-show="isSelected('__none__')" x-cloak class="contents"><svg class="size-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M20 6 9 17l-5-5"/>
</svg></span>
    </span>
    <span data-slot="select-item-text" class="min-w-0 flex-1 truncate">None</span>
</div>
                                                                            <div data-slot="select-item" x-bind="item('sonnet', false)"
    class="relative flex h-nav-row min-h-[var(--nq-touch-min,0px)] cursor-default select-none items-center gap-2.5 rounded-control ps-8 pe-2.5 text-body-sm text-foreground outline-none data-highlighted:bg-nq-selected data-disabled:pointer-events-none data-disabled:opacity-50">
    <span aria-hidden="true" class="absolute start-2.5 inline-flex size-4 items-center justify-center">
        <span x-show="isSelected('sonnet')" x-cloak class="contents"><svg class="size-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M20 6 9 17l-5-5"/>
</svg></span>
    </span>
    <span data-slot="select-item-text" class="min-w-0 flex-1 truncate"><bdi dir="ltr">Sonnet</bdi></span>
</div>
                                                                            <div data-slot="select-item" x-bind="item('haiku', false)"
    class="relative flex h-nav-row min-h-[var(--nq-touch-min,0px)] cursor-default select-none items-center gap-2.5 rounded-control ps-8 pe-2.5 text-body-sm text-foreground outline-none data-highlighted:bg-nq-selected data-disabled:pointer-events-none data-disabled:opacity-50">
    <span aria-hidden="true" class="absolute start-2.5 inline-flex size-4 items-center justify-center">
        <span x-show="isSelected('haiku')" x-cloak class="contents"><svg class="size-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M20 6 9 17l-5-5"/>
</svg></span>
    </span>
    <span data-slot="select-item-text" class="min-w-0 flex-1 truncate"><bdi dir="ltr">Haiku</bdi></span>
</div>
    </div>
</template>
    </div>
                            <div data-slot="field-error" role="alert" x-show="invalid"  style="display: none"  class="text-caption text-nq-danger-text"><span x-text="issueText(0)"></span></div>
</div>
                    </li>
                                                        <li data-slot="routing-route" data-task="vision" x-bind:data-invalid="flags[1].any ? '' : null"
                        class="grid gap-3 py-3 first:pt-0 last:pb-0 md:grid-cols-[minmax(0,1fr)_minmax(0,1fr)_minmax(0,1fr)] md:items-start">
                        <div class="flex min-w-0 flex-col gap-0.5">
                            <span class="text-label text-foreground">Image understanding</span>
                                                                                        <span data-slot="badge"     class="inline-flex h-5 shrink-0 items-center gap-1 whitespace-nowrap rounded-[4px] border px-1.5 text-caption font-medium [&_svg]:size-3 border-border bg-secondary text-foreground mt-1 w-fit">Vision</span>
                                                    </div>
                        <div data-slot="field" x-data="nqField(false)" x-modelable="invalid" x-id="['nq-field']"
         data-valid     x-model="flags[1].model" class="flex flex-col gap-1.5 min-w-0">
    <label data-slot="field-label"  class="text-label text-foreground data-disabled:opacity-50">Model</label>
                            <div data-slot="select" x-data="nqSelect('sonnet', false)" x-modelable="value" x-id="['nq-select']" x-model="rows[1].model" class="contents">
    <button data-slot="select-trigger" x-ref="trigger" x-bind="trigger"
        aria-label="Image understanding: Model" class="flex h-control w-full min-w-0 items-center justify-between gap-2 rounded-control border border-input bg-card px-3 text-body text-foreground min-h-[var(--nq-touch-min,0px)] cursor-default select-none outline-none transition-colors duration-150 ease-nq focus-visible:border-nq-focus focus-visible:outline-1 focus-visible:outline-nq-focus data-popup-open:border-nq-focus data-invalid:border-nq-danger disabled:cursor-not-allowed disabled:opacity-50 pointer-coarse:text-[16px]">
    <span data-slot="select-value" x-text="label() ?? 'Choose a model'" :data-placeholder="empty() ? '' : undefined"
    class="min-w-0 flex-1 truncate text-start data-placeholder:text-muted-foreground"></span>
    <span class="flex shrink-0 text-muted-foreground [&_svg]:size-4"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="m7 15 5 5 5-5"/>
  <path d="m7 9 5-5 5 5"/>
</svg></span>
</button>
                                <template x-teleport="body">
    <div data-slot="select-content" x-ref="popup" x-bind="popup" x-nq-presence="open" x-anchor.bottom-start.offset.4="$refs.trigger"
        class="z-50 min-w-[var(--anchor-width)] max-h-[var(--available-height)] overflow-y-auto rounded-floating border border-border bg-popover p-1.5 text-popover-foreground shadow-floating outline-none transition-opacity duration-150 ease-nq data-starting-style:opacity-0 data-ending-style:opacity-0">
        <div data-slot="select-item" x-bind="item('sonnet', false)"
    class="relative flex h-nav-row min-h-[var(--nq-touch-min,0px)] cursor-default select-none items-center gap-2.5 rounded-control ps-8 pe-2.5 text-body-sm text-foreground outline-none data-highlighted:bg-nq-selected data-disabled:pointer-events-none data-disabled:opacity-50">
    <span aria-hidden="true" class="absolute start-2.5 inline-flex size-4 items-center justify-center">
        <span x-show="isSelected('sonnet')" x-cloak class="contents"><svg class="size-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M20 6 9 17l-5-5"/>
</svg></span>
    </span>
    <span data-slot="select-item-text" class="min-w-0 flex-1 truncate"><bdi dir="ltr">Sonnet</bdi></span>
</div>
    </div>
</template>
    </div>
                            <div data-slot="field-error" role="alert" x-show="invalid"  style="display: none"  class="text-caption text-nq-danger-text"><span x-text="issueText(1)"></span></div>
</div>
                        <div data-slot="field" x-data="nqField(false)" x-modelable="invalid" x-id="['nq-field']"
         data-valid     x-model="flags[1].same" class="flex flex-col gap-1.5 min-w-0">
    <label data-slot="field-label"  class="text-label text-foreground data-disabled:opacity-50">Fallback</label>
                            <div data-slot="select" x-data="nqSelect('__none__', false)" x-modelable="value" x-id="['nq-select']" x-model="rows[1].fallback" class="contents">
    <button data-slot="select-trigger" x-ref="trigger" x-bind="trigger"
        aria-label="Image understanding: Fallback" class="flex h-control w-full min-w-0 items-center justify-between gap-2 rounded-control border border-input bg-card px-3 text-body text-foreground min-h-[var(--nq-touch-min,0px)] cursor-default select-none outline-none transition-colors duration-150 ease-nq focus-visible:border-nq-focus focus-visible:outline-1 focus-visible:outline-nq-focus data-popup-open:border-nq-focus data-invalid:border-nq-danger disabled:cursor-not-allowed disabled:opacity-50 pointer-coarse:text-[16px]">
    <span data-slot="select-value" x-text="label() ?? ''" :data-placeholder="empty() ? '' : undefined"
    class="min-w-0 flex-1 truncate text-start data-placeholder:text-muted-foreground"></span>
    <span class="flex shrink-0 text-muted-foreground [&_svg]:size-4"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="m7 15 5 5 5-5"/>
  <path d="m7 9 5-5 5 5"/>
</svg></span>
</button>
                                <template x-teleport="body">
    <div data-slot="select-content" x-ref="popup" x-bind="popup" x-nq-presence="open" x-anchor.bottom-start.offset.4="$refs.trigger"
        class="z-50 min-w-[var(--anchor-width)] max-h-[var(--available-height)] overflow-y-auto rounded-floating border border-border bg-popover p-1.5 text-popover-foreground shadow-floating outline-none transition-opacity duration-150 ease-nq data-starting-style:opacity-0 data-ending-style:opacity-0">
        <div data-slot="select-item" x-bind="item('__none__', false)"
    class="relative flex h-nav-row min-h-[var(--nq-touch-min,0px)] cursor-default select-none items-center gap-2.5 rounded-control ps-8 pe-2.5 text-body-sm text-foreground outline-none data-highlighted:bg-nq-selected data-disabled:pointer-events-none data-disabled:opacity-50">
    <span aria-hidden="true" class="absolute start-2.5 inline-flex size-4 items-center justify-center">
        <span x-show="isSelected('__none__')" x-cloak class="contents"><svg class="size-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M20 6 9 17l-5-5"/>
</svg></span>
    </span>
    <span data-slot="select-item-text" class="min-w-0 flex-1 truncate">None</span>
</div>
                                                                            <div data-slot="select-item" x-bind="item('sonnet', false)"
    class="relative flex h-nav-row min-h-[var(--nq-touch-min,0px)] cursor-default select-none items-center gap-2.5 rounded-control ps-8 pe-2.5 text-body-sm text-foreground outline-none data-highlighted:bg-nq-selected data-disabled:pointer-events-none data-disabled:opacity-50">
    <span aria-hidden="true" class="absolute start-2.5 inline-flex size-4 items-center justify-center">
        <span x-show="isSelected('sonnet')" x-cloak class="contents"><svg class="size-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M20 6 9 17l-5-5"/>
</svg></span>
    </span>
    <span data-slot="select-item-text" class="min-w-0 flex-1 truncate"><bdi dir="ltr">Sonnet</bdi></span>
</div>
    </div>
</template>
    </div>
                            <div data-slot="field-error" role="alert" x-show="invalid"  style="display: none"  class="text-caption text-nq-danger-text"><span x-text="issueText(1)"></span></div>
</div>
                    </li>
                            </ul></div></div>
    <div data-slot="card" class="flex flex-col gap-4 rounded-card border border-border bg-card py-4 text-card-foreground"><div data-slot="card-header" class="grid auto-rows-min items-start gap-1 px-4 has-data-[slot=card-action]:grid-cols-[1fr_auto]"><h3 data-slot="card-title" class="text-foreground text-h3">Providers and nodes</h3>
            <div data-slot="card-description" class="text-body-sm text-muted-foreground">Where models run. Register a cloud provider or a self-hosted node and say what it can do.</div>
                            <div data-slot="card-action" class="col-start-2 row-span-2 row-start-1 self-start justify-self-end"><button data-slot="button"
     type="button"                         x-bind:disabled="null" x-on:click="registerOpen = true" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 border-border bg-card text-foreground hover:bg-nq-hover h-control-sm px-2.5">
        <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M5 12h14"/>
  <path d="M12 5v14"/>
</svg>                        Register provider</button></div></div>
        <div data-slot="card-content" class="px-4 flex flex-col gap-4"><div class="flex flex-col gap-1.5">
                    <span id="nq-routing-000001-backend" class="text-label text-foreground">Active backend</span>
                    <div role="group" data-slot="toggle-group" data-variant="segmented" data-orientation="horizontal"
    x-data="nqToggleGroup(JSON.parse('[\u0022cloud\u0022]'), false)" x-modelable="value" x-bind="root"
            aria-labelledby="nq-routing-000001-backend" x-model="backendSel" class="flex gap-0.5 rounded-control bg-secondary p-0.5 w-fit max-w-full flex-wrap">
    <button type="button" data-slot="toggle" aria-pressed="true"
     x-bind="toggle('cloud')"      data-pressed         class="inline-flex h-7 shrink-0 items-center justify-center gap-1.5 whitespace-nowrap px-3 text-label text-muted-foreground outline-none transition-colors duration-150 ease-nq hover:text-foreground [&_svg]:size-4 [&_svg]:shrink-0 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus data-disabled:pointer-events-none data-disabled:opacity-50 rounded-[calc(var(--radius-control)-2px)] data-pressed:bg-card data-pressed:text-foreground data-pressed:shadow-xs"><bdi dir="ltr">Cloud API</bdi></button>
                                                    <button type="button" data-slot="toggle" aria-pressed="false"
     x-bind="toggle('edge')"             class="inline-flex h-7 shrink-0 items-center justify-center gap-1.5 whitespace-nowrap px-3 text-label text-muted-foreground outline-none transition-colors duration-150 ease-nq hover:text-foreground [&_svg]:size-4 [&_svg]:shrink-0 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus data-disabled:pointer-events-none data-disabled:opacity-50 rounded-[calc(var(--radius-control)-2px)] data-pressed:bg-card data-pressed:text-foreground data-pressed:shadow-xs"><bdi dir="ltr">Edge node</bdi></button>
</div>
                </div>
                <ul data-slot="routing-providers" class="m-0 flex list-none flex-col divide-y divide-border p-0">
                                                                    <li data-slot="routing-provider" x-bind:data-active="isActive(0) ? '' : null" class="flex flex-wrap items-center gap-x-3 gap-y-2 py-3 first:pt-0 last:pb-0">
                            <svg aria-hidden="true" class="size-4 shrink-0 text-muted-foreground" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9Z"/>
</svg>                            <div class="flex min-w-0 flex-1 basis-48 flex-col">
                                <span class="truncate text-label text-foreground"><bdi dir="ltr">Cloud API</bdi></span>
                                <span class="truncate text-caption text-muted-foreground">Cloud · <bdi dir="ltr">https://api.example.com</bdi></span>
                            </div>
                            <ul aria-label="Modalities of Cloud API" class="m-0 flex list-none flex-wrap gap-1 p-0">
                                                                    <li><span data-slot="badge"     class="inline-flex h-5 shrink-0 items-center gap-1 whitespace-nowrap rounded-[4px] border px-1.5 text-caption font-medium [&_svg]:size-3 border-border bg-secondary text-foreground">Text</span>
</li>
                                                                    <li><span data-slot="badge"     class="inline-flex h-5 shrink-0 items-center gap-1 whitespace-nowrap rounded-[4px] border px-1.5 text-caption font-medium [&_svg]:size-3 border-border bg-secondary text-foreground">Vision</span>
</li>
                                                            </ul>
                                                            <span data-slot="badge"     class="inline-flex h-5 shrink-0 items-center gap-1 whitespace-nowrap rounded-[4px] border px-1.5 text-caption font-medium [&_svg]:size-3 border-nq-success/40 bg-nq-success-soft text-nq-success-text">Online</span>
                                                                                        <button data-slot="button"
     type="button"                         aria-label="Remove Cloud API" x-bind:disabled="null" x-on:click="removeProvider(0)" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 text-foreground hover:bg-nq-hover size-control-sm p-0">
        <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M10 11v6"/>
  <path d="M14 11v6"/>
  <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/>
  <path d="M3 6h18"/>
  <path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
</svg></button>
                                                    </li>
                                                                    <li data-slot="routing-provider" x-bind:data-active="isActive(1) ? '' : null" class="flex flex-wrap items-center gap-x-3 gap-y-2 py-3 first:pt-0 last:pb-0">
                            <svg aria-hidden="true" class="size-4 shrink-0 text-muted-foreground" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <rect width="20" height="8" x="2" y="2" rx="2" ry="2"/>
  <rect width="20" height="8" x="2" y="14" rx="2" ry="2"/>
  <line x1="6" x2="6.01" y1="6" y2="6"/>
  <line x1="6" x2="6.01" y1="18" y2="18"/>
</svg>                            <div class="flex min-w-0 flex-1 basis-48 flex-col">
                                <span class="truncate text-label text-foreground"><bdi dir="ltr">Edge node</bdi></span>
                                <span class="truncate text-caption text-muted-foreground">Node · <bdi dir="ltr">https://edge.example.com</bdi></span>
                            </div>
                            <ul aria-label="Modalities of Edge node" class="m-0 flex list-none flex-wrap gap-1 p-0">
                                                                    <li><span data-slot="badge"     class="inline-flex h-5 shrink-0 items-center gap-1 whitespace-nowrap rounded-[4px] border px-1.5 text-caption font-medium [&_svg]:size-3 border-border bg-secondary text-foreground">Text</span>
</li>
                                                            </ul>
                                                            <span data-slot="badge"     class="inline-flex h-5 shrink-0 items-center gap-1 whitespace-nowrap rounded-[4px] border px-1.5 text-caption font-medium [&_svg]:size-3 border-nq-danger/40 bg-nq-danger-soft text-nq-danger-text">Offline</span>
                                                                                        <button data-slot="button"
     type="button"                         aria-label="Remove Edge node" x-bind:disabled="null" x-on:click="removeProvider(1)" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 text-foreground hover:bg-nq-hover size-control-sm p-0">
        <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M10 11v6"/>
  <path d="M14 11v6"/>
  <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/>
  <path d="M3 6h18"/>
  <path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
</svg></button>
                                                    </li>
                                    </ul></div></div>
    <template x-if="failure">
        <div data-slot="alert" data-tone="danger" role="alert"
        class="relative grid grid-cols-[auto_1fr_auto] items-start gap-x-3 rounded-card border p-3 text-start border-nq-danger/30 bg-nq-danger-soft">
    <svg aria-hidden="true" data-slot="alert-icon" class="mt-0.5 size-4 text-nq-danger-text" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <circle cx="12" cy="12" r="10"/>
  <path d="m15 9-6 6"/>
  <path d="m9 9 6 6"/>
</svg>    <div data-slot="alert-body" class="flex min-w-0 flex-col gap-0.5">
                            <div data-slot="alert-description" class="text-body-sm text-foreground"><span x-text="failure"></span></div>
            </div>
    </div>
    </template>
    <template x-if="!failure && submitted && issues.length > 0">
        <div data-slot="alert" data-tone="warning" role="alert"
        class="relative grid grid-cols-[auto_1fr_auto] items-start gap-x-3 rounded-card border p-3 text-start border-nq-warning/30 bg-nq-warning-soft">
    <svg aria-hidden="true" data-slot="alert-icon" class="mt-0.5 size-4 text-nq-warning-text" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <circle cx="12" cy="12" r="10"/>
  <line x1="12" x2="12" y1="8" y2="12"/>
  <line x1="12" x2="12.01" y1="16" y2="16"/>
</svg>    <div data-slot="alert-body" class="flex min-w-0 flex-col gap-0.5">
                            <div data-slot="alert-description" class="text-body-sm text-foreground">Fix the routes marked below to save.</div>
            </div>
    </div>
    </template>
            <div data-slot="routing-footer" class="flex flex-wrap items-center justify-end gap-2">
            <span role="status" class="me-auto text-body-sm text-muted-foreground" x-text="status()"></span>
            <button data-slot="button"
     type="button"                         x-bind:disabled="!dirty || busy || false ? &#039;&#039; : null" x-on:click="discard()" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 text-foreground hover:bg-nq-hover h-control px-[var(--nq-control-pad)]">
        <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M9 14 4 9l5-5"/>
  <path d="M4 9h10.5a5.5 5.5 0 0 1 5.5 5.5a5.5 5.5 0 0 1-5.5 5.5H11"/>
</svg>                Discard changes</button>
            <button data-slot="button"
     type="submit"                         x-bind:aria-busy="busy ? &#039;true&#039; : null" x-bind:disabled="!dirty || false ? &#039;&#039; : null" x-bind:data-disabled="busy ? &#039;&#039; : null" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 bg-primary text-primary-foreground hover:bg-[color-mix(in_oklab,var(--nq-action)_88%,var(--nq-fg))] h-control px-[var(--nq-control-pad)]">
        <svg data-slot="spinner" aria-hidden="true" style="display: none;" x-show="busy" class="size-4 animate-spin motion-reduce:animate-none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M21 12a9 9 0 1 1-6.219-8.56"/>
</svg>                <svg aria-hidden="true" x-show="!busy" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M15.2 3a2 2 0 0 1 1.4.6l3.8 3.8a2 2 0 0 1 .6 1.4V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z"/>
  <path d="M17 21v-7a1 1 0 0 0-1-1H8a1 1 0 0 0-1 1v7"/>
  <path d="M7 3v4a1 1 0 0 0 1 1h7"/>
</svg>                Save routing</button>
        </div>
            <div data-slot="dialog" x-data="nqDialog(false)" x-modelable="open" x-id="['nq-dialog']" x-model="registerOpen" class="contents">
    <template x-teleport="body">
    <div data-slot="dialog-portal">
        <div data-slot="dialog-backdrop" x-nq-presence="open" x-on:click="close()" class="fixed inset-0 z-50 bg-nq-fg/15 dark:bg-nq-bg/60 transition-opacity duration-150 ease-nq data-starting-style:opacity-0 data-ending-style:opacity-0"></div>
        <div data-slot="dialog-content" x-bind="popup" x-nq-presence="open" x-trap.noscroll="open"
            class="fixed inset-0 z-50 m-auto grid h-fit w-[calc(100%-2rem)] gap-4 rounded-floating border border-border bg-popover p-6 text-popover-foreground outline-none transition-opacity duration-150 ease-nq data-starting-style:opacity-0 data-ending-style:opacity-0 max-h-[90dvh] max-w-lg overflow-y-auto">
            <form novalidate data-slot="routing-register" class="flex flex-col gap-4" x-on:submit.prevent.stop="register()">
                    <div data-slot="dialog-header" class="flex flex-col gap-1.5 pe-8 text-start"><h2 data-slot="dialog-title" :id="$id('nq-dialog', 'title')" class="text-h3 text-foreground">Register a provider</h2>
                        <p data-slot="dialog-description" :id="$id('nq-dialog', 'description')" class="text-body-sm text-muted-foreground">Add a cloud API or a self-hosted node and choose which kinds of work it can take.</p></div>
                    <div data-slot="field" x-data="nqField(false)" x-modelable="invalid" x-id="['nq-field']"
         data-valid     x-model="regFlags.name" class="flex flex-col gap-1.5">
    <label data-slot="field-label"  class="text-label text-foreground data-disabled:opacity-50">Name</label>
                        <input data-slot="input" type="text"
                    x-model="regName" autocomplete="off" class="w-full min-w-0 rounded-control border border-input bg-card px-3 text-body text-foreground min-h-[var(--nq-touch-min,0px)] transition-colors duration-150 ease-nq outline-none placeholder:text-muted-foreground focus-visible:border-nq-focus focus-visible:outline-1 focus-visible:outline-nq-focus data-invalid:border-nq-danger aria-invalid:border-nq-danger disabled:cursor-not-allowed disabled:opacity-50 pointer-coarse:text-[16px] h-control">
                        <div data-slot="field-error" role="alert" x-show="invalid"  style="display: none"  class="text-caption text-nq-danger-text"><span x-text="regMessages.name"></span></div>
</div>
                    <div class="flex flex-col gap-1.5">
                        <span id="nq-routing-000001-kind" class="text-label text-foreground">Type</span>
                        <div role="group" data-slot="toggle-group" data-variant="segmented" data-orientation="horizontal"
    x-data="nqToggleGroup(JSON.parse('[\u0022cloud\u0022]'), false)" x-modelable="value" x-bind="root"
            aria-labelledby="nq-routing-000001-kind" x-model="regKind" class="flex max-w-full gap-0.5 rounded-control bg-secondary p-0.5 w-fit">
    <button type="button" data-slot="toggle" aria-pressed="true"
     x-bind="toggle('cloud')"      data-pressed         class="inline-flex h-7 shrink-0 items-center justify-center gap-1.5 whitespace-nowrap px-3 text-label text-muted-foreground outline-none transition-colors duration-150 ease-nq hover:text-foreground [&_svg]:size-4 [&_svg]:shrink-0 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus data-disabled:pointer-events-none data-disabled:opacity-50 rounded-[calc(var(--radius-control)-2px)] data-pressed:bg-card data-pressed:text-foreground data-pressed:shadow-xs">Cloud</button>
                            <button type="button" data-slot="toggle" aria-pressed="false"
     x-bind="toggle('node')"             class="inline-flex h-7 shrink-0 items-center justify-center gap-1.5 whitespace-nowrap px-3 text-label text-muted-foreground outline-none transition-colors duration-150 ease-nq hover:text-foreground [&_svg]:size-4 [&_svg]:shrink-0 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus data-disabled:pointer-events-none data-disabled:opacity-50 rounded-[calc(var(--radius-control)-2px)] data-pressed:bg-card data-pressed:text-foreground data-pressed:shadow-xs">Node</button>
</div>
                    </div>
                    <div data-slot="field" x-data="nqField(false)" x-modelable="invalid" x-id="['nq-field']"
         data-valid     x-model="regFlags.endpoint" class="flex flex-col gap-1.5">
    <label data-slot="field-label"  class="text-label text-foreground data-disabled:opacity-50">Endpoint URL</label>
                        <input data-slot="input" type="url"
     dir="ltr"                 x-model="regEndpoint" inputmode="url" placeholder="https://" autocomplete="off" class="w-full min-w-0 rounded-control border border-input bg-card px-3 text-body text-foreground min-h-[var(--nq-touch-min,0px)] transition-colors duration-150 ease-nq outline-none placeholder:text-muted-foreground focus-visible:border-nq-focus focus-visible:outline-1 focus-visible:outline-nq-focus data-invalid:border-nq-danger aria-invalid:border-nq-danger disabled:cursor-not-allowed disabled:opacity-50 pointer-coarse:text-[16px] h-control text-start">
                        <div data-slot="field-error" role="alert" x-show="invalid"  style="display: none"  class="text-caption text-nq-danger-text"><span x-text="regMessages.endpoint"></span></div>
                        <p class="text-caption text-muted-foreground" x-show="!regFlags.endpoint">The address the provider is reached at.</p>
</div>
                    <div class="flex flex-col gap-1.5">
                        <span id="nq-routing-000001-mod" class="text-label text-foreground">Modalities</span>
                        <div role="group" data-slot="toggle-group" data-variant="segmented" data-orientation="horizontal"
    x-data="nqToggleGroup(JSON.parse('[\u0022text\u0022]'), true)" x-modelable="value" x-bind="root"
     data-multiple         aria-labelledby="nq-routing-000001-mod" x-model="regMods" class="flex gap-0.5 rounded-control bg-secondary p-0.5 w-fit max-w-full flex-wrap">
    <button type="button" data-slot="toggle" aria-pressed="true"
     x-bind="toggle('text')"      data-pressed         class="inline-flex h-7 shrink-0 items-center justify-center gap-1.5 whitespace-nowrap px-3 text-label text-muted-foreground outline-none transition-colors duration-150 ease-nq hover:text-foreground [&_svg]:size-4 [&_svg]:shrink-0 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus data-disabled:pointer-events-none data-disabled:opacity-50 rounded-[calc(var(--radius-control)-2px)] data-pressed:bg-card data-pressed:text-foreground data-pressed:shadow-xs">Text</button>
                                                            <button type="button" data-slot="toggle" aria-pressed="false"
     x-bind="toggle('vision')"             class="inline-flex h-7 shrink-0 items-center justify-center gap-1.5 whitespace-nowrap px-3 text-label text-muted-foreground outline-none transition-colors duration-150 ease-nq hover:text-foreground [&_svg]:size-4 [&_svg]:shrink-0 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus data-disabled:pointer-events-none data-disabled:opacity-50 rounded-[calc(var(--radius-control)-2px)] data-pressed:bg-card data-pressed:text-foreground data-pressed:shadow-xs">Vision</button>
                                                            <button type="button" data-slot="toggle" aria-pressed="false"
     x-bind="toggle('audio')"             class="inline-flex h-7 shrink-0 items-center justify-center gap-1.5 whitespace-nowrap px-3 text-label text-muted-foreground outline-none transition-colors duration-150 ease-nq hover:text-foreground [&_svg]:size-4 [&_svg]:shrink-0 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus data-disabled:pointer-events-none data-disabled:opacity-50 rounded-[calc(var(--radius-control)-2px)] data-pressed:bg-card data-pressed:text-foreground data-pressed:shadow-xs">Audio</button>
                                                            <button type="button" data-slot="toggle" aria-pressed="false"
     x-bind="toggle('image')"             class="inline-flex h-7 shrink-0 items-center justify-center gap-1.5 whitespace-nowrap px-3 text-label text-muted-foreground outline-none transition-colors duration-150 ease-nq hover:text-foreground [&_svg]:size-4 [&_svg]:shrink-0 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus data-disabled:pointer-events-none data-disabled:opacity-50 rounded-[calc(var(--radius-control)-2px)] data-pressed:bg-card data-pressed:text-foreground data-pressed:shadow-xs">Image</button>
                                                            <button type="button" data-slot="toggle" aria-pressed="false"
     x-bind="toggle('embedding')"             class="inline-flex h-7 shrink-0 items-center justify-center gap-1.5 whitespace-nowrap px-3 text-label text-muted-foreground outline-none transition-colors duration-150 ease-nq hover:text-foreground [&_svg]:size-4 [&_svg]:shrink-0 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus data-disabled:pointer-events-none data-disabled:opacity-50 rounded-[calc(var(--radius-control)-2px)] data-pressed:bg-card data-pressed:text-foreground data-pressed:shadow-xs">Embedding</button>
</div>
                        <p role="alert" class="flex items-center gap-1.5 text-caption text-nq-danger-text" x-show="regFlags.modalities" style="display: none">
                            <svg aria-hidden="true" class="size-3.5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <circle cx="12" cy="12" r="10"/>
  <line x1="12" x2="12" y1="8" y2="12"/>
  <line x1="12" x2="12.01" y1="16" y2="16"/>
</svg><span x-text="regMessages.modalities"></span>
                        </p>
                        <p class="text-caption text-muted-foreground" x-show="!regFlags.modalities">What it can handle.</p>
                    </div>
                    <template x-if="regFailure"><div data-slot="alert" data-tone="danger" role="alert"
        class="relative grid grid-cols-[auto_1fr_auto] items-start gap-x-3 rounded-card border p-3 text-start border-nq-danger/30 bg-nq-danger-soft">
    <svg aria-hidden="true" data-slot="alert-icon" class="mt-0.5 size-4 text-nq-danger-text" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <circle cx="12" cy="12" r="10"/>
  <path d="m15 9-6 6"/>
  <path d="m9 9 6 6"/>
</svg>    <div data-slot="alert-body" class="flex min-w-0 flex-col gap-0.5">
                            <div data-slot="alert-description" class="text-body-sm text-foreground"><span x-text="regFailure"></span></div>
            </div>
    </div>
</template>
                    <div data-slot="dialog-footer" class="flex flex-col-reverse gap-2 sm:flex-row sm:justify-end"><button data-slot="button"
     type="button"                         x-bind:disabled="regBusy ? &#039;&#039; : null" x-on:click="registerOpen = false" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 text-foreground hover:bg-nq-hover h-control px-[var(--nq-control-pad)]">
        Cancel</button>
                        <button data-slot="button"
     type="submit"                         x-bind:aria-busy="regBusy ? &#039;true&#039; : null" x-bind:data-disabled="regBusy ? &#039;&#039; : null" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 bg-primary text-primary-foreground hover:bg-[color-mix(in_oklab,var(--nq-action)_88%,var(--nq-fg))] h-control px-[var(--nq-control-pad)]">
        <svg data-slot="spinner" aria-hidden="true" style="display: none;" x-show="regBusy" class="size-4 animate-spin motion-reduce:animate-none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M21 12a9 9 0 1 1-6.219-8.56"/>
</svg>                            Register</button></div>
                </form>
                            <button type="button" data-slot="dialog-close" x-on:click="close()" aria-label="Close"
                    class="absolute end-3 top-3 inline-flex size-8 items-center justify-center rounded-control text-muted-foreground transition-colors duration-150 hover:bg-nq-hover hover:text-foreground focus-visible:outline-2 focus-visible:outline-nq-focus [&_svg]:size-4"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M18 6 6 18"/>
  <path d="m6 6 12 12"/>
</svg></button>
                    </div>
    </div>
</template>
</div>
    </form>
```
