Files
crank/apps/ui/html/agents.html
T
github-ops ba29ac7b94
Deploy / deploy (push) Successful in 2m44s
CI / Rust Checks (push) Successful in 5m31s
CI / UI Checks (push) Successful in 5s
CI / Deployment Manifests (push) Successful in 2s
CI / Frontend E2E (push) Successful in 4m24s
chore: publish clean community baseline
2026-06-19 16:45:51 +00:00

395 lines
24 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<base href="../">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Crank — Agents</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<link rel="stylesheet" href="css/variables.css">
<link rel="stylesheet" href="css/layout.css">
<link rel="stylesheet" href="css/pages.css">
<link rel="stylesheet" href="css/catalog.css">
<link rel="stylesheet" href="css/wizard.css">
<script src="%CRANK_BUNDLE_PROTECTED_CORE%"></script>
</head>
<body x-data="agents">
<!-- ═══════════════════ NAVBAR ═══════════════════ -->
<nav class="navbar">
<a href="/" class="nav-logo">
<div class="nav-logo-mark"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 28 28" fill="white"><path fill-rule="evenodd" d="M 10.000,1.800 L 12.287,3.962 L 15.408,3.557 L 15.987,6.650 L 18.750,8.157 L 17.400,11.000 L 18.750,13.843 L 15.987,15.350 L 15.408,18.443 L 12.287,18.038 L 10.000,20.200 L 7.713,18.038 L 4.592,18.443 L 4.013,15.350 L 1.250,13.843 L 2.600,11.000 L 1.250,8.157 L 4.013,6.650 L 4.592,3.557 L 7.713,3.962 Z M 12.800,11.000 A 2.8 2.8 0 1 0 7.200,11.000 A 2.8 2.8 0 1 0 12.800,11.000 Z"/><path fill-rule="evenodd" d="M 11.791,15.604 Q 16.083,17.179 21.375,21.154 A 2.00 2.00 0 1 1 23.625,17.846 Q 19.782,15.610 14.940,10.973 Z M 24.300,19.500 A 1.8 1.8 0 1 0 20.700,19.500 A 1.8 1.8 0 1 0 24.300,19.500 Z"/><path d="M 10.0,9.7 L 11.3,11.0 L 10.0,12.3 L 8.7,11.0 Z" fill="rgba(0,0,0,0.3)"/></svg></div>
<span class="nav-logo-text">Crank</span>
</a>
<!-- Workspace switcher -->
<div class="ws-switcher" id="ws-switcher">
<button class="ws-switcher-trigger" onclick="toggleWsSwitcher(event)">
<div class="ws-dot" id="ws-dot">A</div>
<span class="ws-current-name" id="ws-current-name">acme-workspace</span>
<svg width="11" height="11" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6l4 4 4-4"/></svg>
</button>
<div class="ws-dropdown" id="ws-dropdown" hidden>
<div class="ws-dropdown-label" data-i18n="nav.workspaces">Your workspaces</div>
<div id="ws-dropdown-list"></div>
<div class="ws-dropdown-footer">
<button class="ws-dropdown-create" onclick="window.location.href='/workspace-setup?mode=create'">
<svg width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"><path d="M8 3v10M3 8h10"/></svg>
<span data-i18n="nav.create_workspace">Create workspace</span>
</button>
</div>
</div>
</div>
<div class="nav-links">
<a href="/" class="nav-link" data-i18n="nav.operations">Operations</a>
<a href="/agents" class="nav-link active" data-i18n="nav.agents">Agents</a>
<a href="/api-keys" class="nav-link" data-i18n="nav.apikeys">API Keys</a>
<a href="/secrets" class="nav-link" data-i18n="nav.secrets">Secrets</a>
<a href="/logs" class="nav-link" data-i18n="nav.logs">Logs</a>
<a href="/usage" class="nav-link" data-i18n="nav.usage">Usage</a>
</div>
<div class="nav-right">
<button class="nav-hamburger" data-i18n-title="nav.menu" data-i18n-aria-label="nav.menu" aria-label="Menu"><span></span><span></span><span></span></button>
<button class="nav-icon-btn" data-i18n-title="nav.notifications" title="Notifications">
<svg width="15" height="15"><use href="icons/general/bell.svg#icon"/></svg>
</button>
<div class="nav-divider"></div>
<div class="user-menu" @click.stop>
<div class="nav-avatar" @click="openDropdown = openDropdown === 'user' ? null : 'user'" data-i18n-title="nav.account" title="Account">AT</div>
<div class="user-dropdown" x-show="openDropdown === 'user'" x-transition style="display:none">
<div class="user-dropdown-header">
<div class="user-dropdown-name">Crank</div>
<div class="user-dropdown-role" id="user-ws-role"></div>
</div>
<button class="user-dropdown-item" onclick="window.location.href='/settings'">
<svg width="13" height="13"><use href="icons/general/settings.svg#icon"/></svg>
<span data-i18n="nav.settings">Settings</span>
</button>
<div class="dropdown-divider"></div>
<button class="user-dropdown-item danger" onclick="window.CrankAuth.logout()">
<svg width="13" height="13"><use href="icons/general/logout.svg#icon"/></svg>
<span data-i18n="nav.logout">Log out</span>
</button>
</div>
</div>
</div>
</nav>
<div class="mobile-nav" hidden>
<a href="/" class="mobile-nav-link" data-i18n="nav.operations">Operations</a>
<a href="/agents" class="mobile-nav-link active" data-i18n="nav.agents">Agents</a>
<a href="/api-keys" class="mobile-nav-link" data-i18n="nav.apikeys">API Keys</a>
<a href="/secrets" class="mobile-nav-link" data-i18n="nav.secrets">Secrets</a>
<a href="/logs" class="mobile-nav-link" data-i18n="nav.logs">Logs</a>
<a href="/usage" class="mobile-nav-link" data-i18n="nav.usage">Usage</a>
</div>
<!-- ═══════════════════ PAGE ═══════════════════ -->
<div class="page">
<!-- Page header -->
<div class="page-header">
<div>
<h1 class="page-heading" data-i18n="agents.title">Agents</h1>
<p class="page-subheading" x-text="subtitleText()">Named MCP endpoints that expose a curated subset of operations to an LLM</p>
</div>
<button class="btn-new" @click="openCreate()">
<svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"><path d="M8 3v10M3 8h10"/></svg>
<span data-i18n="agents.new">New agent</span>
</button>
</div>
<!-- Stats -->
<div class="stats-row">
<div class="stat-card">
<div class="stat-label" data-i18n="agents.stats.total">Total agents</div>
<div class="stat-value" x-text="agents.length"></div>
<div class="stat-delta" x-text="tKey('agents.stats.total_sub')">across this workspace</div>
</div>
<div class="stat-card">
<div class="stat-label" data-i18n="agents.stats.published">Published</div>
<div class="stat-value" x-text="activeCount"></div>
<div class="stat-delta" x-text="agents.length ? tfKey('agents.stats.published_sub', { value: Math.round(activeCount/agents.length*100) }) : ''"></div>
</div>
<div class="stat-card">
<div class="stat-label" data-i18n="agents.stats.tools">Operations exposed</div>
<div class="stat-value" x-text="totalOpsExposed"></div>
<div class="stat-delta" x-text="tfKey('agents.stats.tools_sub', { count: agents.length })"></div>
</div>
<div class="stat-card">
<div class="stat-label" data-i18n="agents.stats.calls">Calls today</div>
<div class="stat-value" x-text="formatCalls(totalCalls)"></div>
<div class="stat-delta up" x-text="tKey('agents.stats.calls_sub')">all agents combined</div>
</div>
</div>
<!-- Search + filter bar -->
<div class="filter-bar">
<div class="search-box">
<svg width="14" height="14"><use href="icons/general/search.svg#icon"/></svg>
<input
type="text"
data-i18n-ph="agents.search"
placeholder="Search agents..."
:value="agentSearch"
@input="agentSearch = $event.target.value"
>
</div>
<div class="filter-right">
<span class="results-count" x-show="agentSearch">
<span x-text="tfKey('agents.results', { shown: filteredAgents.length, total: agents.length })"></span>
</span>
</div>
</div>
<!-- Info callout -->
<div class="agents-info-callout">
<svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="var(--accent)" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><circle cx="8" cy="8" r="7"/><path d="M8 11V8M8 5v-.5"/></svg>
<span x-text="agentCalloutText()">Each agent gets its own MCP endpoint.</span>
</div>
<!-- Loading -->
<div class="loading-spinner" x-show="loading">
<div class="spinner"></div>
<span data-i18n="agents.loading">Loading agents…</span>
</div>
<div class="empty-state" x-show="!loading && loadError">
<div class="empty-state-title" data-i18n="agents.error.title">Failed to load agents</div>
<div class="empty-state-sub" x-text="loadError"></div>
</div>
<!-- Empty state: no agents at all -->
<div class="empty-state" x-show="!loading && !loadError && agents.length === 0">
<div class="empty-state-icon">
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" style="opacity:0.35"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><path d="M14 17.5h7M17.5 14v7"/></svg>
</div>
<div class="empty-state-title" data-i18n="agents.empty.initial.title">No agents yet</div>
<div class="empty-state-sub" x-text="emptyStateText()">Create your first agent to get a dedicated MCP endpoint with a curated set of tools.</div>
<div class="empty-state-sub" style="margin-top:8px;" x-text="tKey('agents.empty.initial.next_step')">After creation, issue separate machine-access keys for this endpoint on the API Keys page.</div>
<button class="btn-new" @click="openCreate()" style="margin-top: 16px;">
<svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"><path d="M8 3v10M3 8h10"/></svg>
<span data-i18n="agents.new">New agent</span>
</button>
</div>
<!-- Empty state: search returned nothing -->
<div class="empty-state" x-show="!loading && !loadError && agents.length > 0 && filteredAgents.length === 0">
<div class="empty-state-title" x-text="tfKey('agents.empty.search.title', { query: agentSearch })">No agents match</div>
<div class="empty-state-sub" data-i18n="agents.empty.search.sub">Try a different search term.</div>
</div>
<!-- Agents grid -->
<div class="agents-grid" x-show="!loading && !loadError && filteredAgents.length > 0">
<template x-for="agent in filteredAgents" :key="agent.id">
<div class="agent-card">
<!-- Card header -->
<div class="agent-card-header">
<div class="agent-card-icon">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><path d="M14 17.5h7M17.5 14v7"/></svg>
</div>
<div class="agent-card-title-wrap">
<div class="agent-card-name" x-text="agent.display_name"></div>
<div class="agent-card-slug" x-text="agent.slug"></div>
</div>
<span :class="statusClass(agent.status)" x-text="lifecycleLabel(agent)"></span>
</div>
<!-- Description -->
<div class="agent-card-desc" x-text="agent.description"></div>
<!-- Stats row -->
<div class="agent-card-stats">
<div class="agent-stat">
<svg width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M2 5h12M2 8h8M2 11h5"/></svg>
<span class="agent-stat-value" x-text="agent.operation_count"></span>
<span data-i18n="agents.card.tools">tools</span>
</div>
<div class="agent-stat">
<svg width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M7 2H4a1 1 0 00-1 1v3M7 2l7 7M7 2v5h5"/><path d="M3 9v4a1 1 0 001 1h8a1 1 0 001-1V9"/></svg>
<span class="agent-stat-value" x-text="agent.key_count"></span>
<span data-i18n="agents.card.keys">API keys</span>
</div>
<div class="agent-stat" x-show="agent.calls_today > 0">
<svg width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M1 8s3-5 7-5 7 5 7 5-3 5-7 5-7-5-7-5z"/><circle cx="8" cy="8" r="2"/></svg>
<span class="agent-stat-value" x-text="formatCalls(agent.calls_today)"></span>
<span data-i18n="agents.card.calls_today">calls today</span>
</div>
</div>
<!-- MCP endpoint -->
<div class="agent-endpoint-row">
<svg width="11" height="11" viewBox="0 0 16 16" fill="none" stroke="var(--text-muted)" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M2 8h12M10 4l4 4-4 4"/></svg>
<code class="agent-endpoint-url" x-text="mcpEndpoint(agent)"></code>
<button class="agent-endpoint-copy" @click.stop="copyEndpoint(agent)" :title="tKey('agents.card.copy_endpoint')">
<svg width="11" height="11" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="5" y="5" width="9" height="9" rx="1"/><path d="M5 5V3a1 1 0 011-1h7a1 1 0 011 1v7a1 1 0 01-1 1h-2"/></svg>
</button>
</div>
<div class="agent-card-date" style="margin-top:8px;" x-text="endpointHelpText(agent)"></div>
<!-- Footer -->
<div class="agent-card-footer">
<span class="agent-card-date" x-text="tfKey('agents.card.created', { date: formatDate(agent.created_at) })"></span>
<div class="agent-card-actions">
<button class="agent-action-btn" @click.stop="openEdit(agent)">
<svg width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M11 2l3 3-8 8H3v-3l8-8z"/></svg>
<span data-i18n="agents.action.edit">Edit</span>
</button>
<button class="agent-action-btn" @click.stop="applyLifecycle(agent, lifecycleAction(agent).key)">
<svg width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4 3.5v9l8-4.5-8-4.5z"/></svg>
<span x-text="lifecycleAction(agent).label"></span>
</button>
<button class="agent-action-btn" x-show="agent.raw_status !== 'archived'" @click.stop="applyLifecycle(agent, 'archive')">
<svg width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M2.5 4.5h11v2l-1 6.5h-8l-1-6.5v-2z"/><path d="M5 4.5v-1A1.5 1.5 0 016.5 2h3A1.5 1.5 0 0111 3.5v1"/></svg>
<span data-i18n="agents.action.archive">Archive</span>
</button>
<button class="agent-action-btn danger" @click.stop="deleteAgent(agent.id)">
<svg width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M2 4h12M5 4V3a1 1 0 011-1h4a1 1 0 011 1v1M10 8v4M6 8v4"/><path d="M3 4l1 9a1 1 0 001 1h6a1 1 0 001-1l1-9"/></svg>
<span data-i18n="agents.action.delete">Delete</span>
</button>
</div>
</div>
</div>
</template>
</div>
</div><!-- /page -->
<!-- ═══════════════════ DRAWER ═══════════════════ -->
<!-- Overlay -->
<div class="drawer-overlay" x-show="drawerOpen" x-transition:enter="transition-opacity" @click="closeDrawer()" style="display:none"></div>
<!-- Drawer panel -->
<div class="drawer" :class="{ open: drawerOpen }" style="display:none" x-show="drawerOpen">
<!-- Drawer header -->
<div class="drawer-header">
<div>
<div class="drawer-title" x-text="drawerMode === 'create' ? tKey('agents.drawer.new_title') : tKey('agents.drawer.edit_title')"></div>
<div class="drawer-subtitle" x-text="drawerSubText()"></div>
</div>
<button class="drawer-close" @click="closeDrawer()">
<svg width="14" height="14" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><line x1="1" y1="1" x2="11" y2="11"/><line x1="11" y1="1" x2="1" y2="11"/></svg>
</button>
</div>
<!-- Drawer body -->
<div class="drawer-body">
<!-- Identity section -->
<div class="drawer-section">
<div class="drawer-section-title" data-i18n="agents.drawer.identity">Identity</div>
<div class="form-group" style="margin-bottom: 14px;">
<label class="form-label"><span data-i18n="agents.drawer.display_name">Display name</span> <span class="form-label-required" data-i18n="agents.drawer.required">required</span></label>
<input class="form-input" type="text" data-i18n-ph="agents.drawer.placeholder.name" placeholder="Customer Support" autocomplete="off"
:value="form.display_name" @input="onNameInput($event.target.value)">
</div>
<div class="form-group" style="margin-bottom: 14px;">
<label class="form-label"><span data-i18n="agents.drawer.slug">Slug</span> <span class="form-label-required" data-i18n="agents.drawer.required">required</span></label>
<input class="form-input input-mono" type="text" data-i18n-ph="agents.drawer.placeholder.slug" placeholder="customer-support" autocomplete="off"
:value="form.slug" @input="onSlugInput($event.target.value)">
<div class="form-hint" x-show="form.slug">
<span data-i18n="agents.drawer.endpoint">MCP endpoint</span>: <code style="font-family:monospace;font-size:11px;color:var(--accent)" x-text="'/mcp/v1/' + (localStorage.getItem('crank_workspace_slug') || 'default') + '/' + form.slug"></code>
</div>
<div class="form-hint" data-i18n="agents.drawer.slug_hint">Keep the slug stable after clients start using this endpoint. Changing it changes the MCP path.</div>
</div>
<div class="form-group" style="margin-bottom: 14px;">
<label class="form-label"><span data-i18n="agents.drawer.description">Description</span> <span class="form-label-optional" data-i18n="agents.drawer.optional">(optional)</span></label>
<textarea class="form-textarea" rows="3" data-i18n-ph="agents.drawer.placeholder.description" placeholder="What does this agent do? What LLM or use-case is it for?"
x-model="form.description"></textarea>
</div>
<div class="form-group">
<label class="form-label" data-i18n="agents.drawer.status">Status</label>
<div class="agent-status-toggle">
<button class="agent-status-opt" :class="{ active: form.status === 'published' }" @click="form.status = 'published'">
<span class="status-dot" style="background: var(--success, #3fb950)"></span> <span data-i18n="agents.lifecycle.published">Published</span>
</button>
<button class="agent-status-opt" :class="{ active: form.status === 'draft' }" @click="form.status = 'draft'">
<span class="status-dot" style="background: var(--text-muted)"></span> <span data-i18n="agents.lifecycle.draft">Draft</span>
</button>
<button class="agent-status-opt" :class="{ active: form.status === 'archived' }" @click="form.status = 'archived'">
<span class="status-dot" style="background: var(--orange, #d29922)"></span> <span data-i18n="agents.lifecycle.archived">Archived</span>
</button>
</div>
</div>
</div>
<!-- Operations picker section -->
<div class="drawer-section">
<div class="drawer-section-header">
<div class="drawer-section-title" data-i18n="agents.drawer.operations">Operations</div>
<span class="drawer-section-count" x-text="tfKey('agents.drawer.selected', { count: form.selectedOps.length })"></span>
</div>
<div class="drawer-section-sub" x-text="operationsSubText()">Select which operations this agent exposes. LLMs connecting to this agent will only see these tools.</div>
<div class="ops-picker">
<!-- Search -->
<div class="ops-picker-search">
<svg width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="var(--text-muted)" stroke-width="1.8" stroke-linecap="round"><circle cx="7" cy="7" r="5"/><path d="M12 12l2.5 2.5"/></svg>
<input class="ops-picker-search-input" type="text" data-i18n-ph="agents.drawer.filter_ops" placeholder="Filter operations…" x-model="opSearch" autocomplete="off" spellcheck="false">
<button class="ops-picker-clear" x-show="opSearch" @click="opSearch = ''" style="display:none">
<svg width="10" height="10" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><line x1="1" y1="1" x2="11" y2="11"/><line x1="11" y1="1" x2="1" y2="11"/></svg>
</button>
</div>
<!-- List -->
<div class="ops-picker-list">
<template x-for="op in filteredOps" :key="op.id || op.name">
<div class="ops-picker-item" :class="{ selected: isOpSelected(op.id || op.name) }" @click="toggleOp(op.id || op.name)">
<div class="ops-picker-check">
<svg x-show="isOpSelected(op.id || op.name)" width="10" height="10" viewBox="0 0 12 12" fill="none" stroke="var(--accent)" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="1.5,6 4.5,9 10.5,3"/></svg>
</div>
<div class="ops-picker-info">
<div class="ops-picker-name" x-text="op.display_name || op.name"></div>
<div class="ops-picker-slug" x-text="op.name"></div>
</div>
<span :class="protocolBadge(op)" x-text="protocolLabel(op)" style="font-size:10px; padding: 2px 6px;"></span>
<div class="ops-picker-status-dot" :class="'ops-status-' + op.status"></div>
</div>
</template>
<div class="ops-picker-empty" x-show="filteredOps.length === 0">
<span x-text="tfKey('agents.drawer.ops_no_match', { query: opSearch })"></span>
</div>
</div>
<!-- Footer count -->
<div class="ops-picker-footer" x-show="form.selectedOps.length > 0">
<span x-text="tfKey('agents.drawer.ops_selected', { count: form.selectedOps.length })"></span>
<button @click="form.selectedOps = []" style="background:none;border:none;color:var(--text-muted);font-size:12px;cursor:pointer;margin-left:8px;" data-i18n="agents.drawer.clear_all">Clear all</button>
</div>
</div>
<!-- Recommendation callout -->
<div class="agents-rec-callout" x-show="form.selectedOps.length > 15">
<svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><polygon points="8,1 15,14 1,14" fill="none"/><path d="M8 6v4M8 11.5v.5"/></svg>
<span x-text="tfKey('agents.drawer.recommendation', { count: form.selectedOps.length })">You've selected tools.</span>
</div>
</div>
</div><!-- /drawer-body -->
<!-- Drawer footer -->
<div class="drawer-footer">
<button class="btn-ghost-sm" style="padding: 8px 16px; font-size: 13px;" @click="closeDrawer()" data-i18n="agents.drawer.cancel">Cancel</button>
<button class="btn-primary-sm" style="padding: 8px 20px; font-size: 13px;"
:disabled="!form.display_name.trim() || !form.slug.trim()"
@click="saveAgent()"
x-text="drawerMode === 'create' ? tKey('agents.drawer.create') : tKey('agents.drawer.save')">
Create agent
</button>
</div>
</div><!-- /drawer -->
<script src="%CRANK_BUNDLE_AGENTS%"></script>
</body>
</html>