агенты: добавить поиск инструментов по каталогу

This commit is contained in:
2026-07-21 13:12:46 +03:00
parent 63f8ee333f
commit 99bd05c145
35 changed files with 2088 additions and 155 deletions
+101 -3
View File
@@ -359,24 +359,122 @@
<!-- 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>
<button @click="clearSelectedOperations()" 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">
<div class="agents-rec-callout" x-show="form.selectedOps.length > 15 && form.accessMode === 'direct'">
<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 class="drawer-section">
<div class="drawer-section-title" data-i18n="agents.drawer.access.title">Tool access</div>
<div class="drawer-section-sub" data-i18n="agents.drawer.access.subtitle">Choose how the model receives this agent's tool catalog.</div>
<div class="tool-access-options">
<button class="tool-access-option" :class="{ active: form.accessMode === 'direct' }" @click="setAccessMode('direct')">
<span class="tool-access-option-title" data-i18n="agents.drawer.access.direct">Show tools immediately</span>
<span class="tool-access-option-body" data-i18n="agents.drawer.access.direct_hint">Best for a small curated catalog. MCP clients receive every tool in tools/list.</span>
</button>
<button class="tool-access-option" :class="{ active: form.accessMode === 'search' }" @click="setAccessMode('search')">
<span class="tool-access-option-title" data-i18n="agents.drawer.access.search">Select tools on demand</span>
<span class="tool-access-option-body" data-i18n="agents.drawer.access.search_hint">The model sees search_tools and call_tool, then discovers only relevant schemas.</span>
</button>
</div>
<div class="tool-search-config" x-show="form.accessMode === 'search'" style="display:none">
<div class="tool-search-config-header">
<div>
<div class="tool-search-config-title" data-i18n="agents.drawer.groups.title">Catalog sections</div>
<div class="drawer-section-sub" data-i18n="agents.drawer.groups.subtitle">Sections help the model narrow a search without blocking catalog-wide discovery.</div>
</div>
<button class="btn-ghost-sm" @click="addToolGroup()" data-i18n="agents.drawer.groups.add">Add section</button>
</div>
<div class="tool-group-empty" x-show="form.groups.length === 0" data-i18n="agents.drawer.groups.empty">No sections yet. Search will use the entire selected catalog.</div>
<template x-for="(group, groupIndex) in form.groups" :key="groupIndex">
<div class="tool-group-card">
<div class="tool-group-card-header">
<strong x-text="group.name || tKey('agents.drawer.groups.untitled')"></strong>
<button class="agent-action-btn danger" @click="removeToolGroup(groupIndex)" :title="tKey('agents.drawer.groups.remove')">
<img src="/icons/general/trash.svg" alt="">
</button>
</div>
<div class="tool-group-fields">
<label>
<span data-i18n="agents.drawer.groups.name">Name</span>
<input class="form-input" type="text" :value="group.name" @input="onToolGroupName(groupIndex, $event.target.value)" data-i18n-ph="agents.drawer.groups.name_placeholder" placeholder="Finance">
</label>
<label>
<span data-i18n="agents.drawer.groups.id">Identifier</span>
<input class="form-input input-mono" type="text" :value="group.id" @input="onToolGroupId(groupIndex, $event.target.value)" placeholder="finance">
</label>
</div>
<label class="tool-group-description">
<span data-i18n="agents.drawer.groups.description">Description for the model</span>
<textarea class="form-textarea" rows="2" x-model="group.description" @input="resetSearchPreview()" data-i18n-ph="agents.drawer.groups.description_placeholder" placeholder="Invoices, payments and refunds"></textarea>
</label>
</div>
</template>
<div class="tool-group-assignments" x-show="form.groups.length > 0 && selectedOperations.length > 0">
<div class="tool-search-config-title" data-i18n="agents.drawer.groups.assign">Assign tools to sections</div>
<template x-for="operation in selectedOperations" :key="operation.id">
<div class="tool-group-assignment-row">
<div class="tool-group-assignment-tool">
<strong x-text="operation.display_name || operation.name"></strong>
<code x-text="operation.name"></code>
</div>
<div class="tool-group-chips">
<template x-for="(group, groupIndex) in form.groups" :key="groupIndex">
<button class="tool-group-chip" :class="{ active: toolInGroup(groupIndex, operation.name) }" @click="toggleToolGroup(groupIndex, operation.name)" x-text="group.name || group.id || '—'"></button>
</template>
</div>
</div>
</template>
</div>
<label class="tool-search-limit">
<span data-i18n="agents.drawer.search.limit">Maximum results per search</span>
<input class="form-input" type="number" min="1" max="20" x-model.number="form.searchMaxResults" @input="resetSearchPreview()">
</label>
<div class="tool-search-preview">
<div class="tool-search-config-title" data-i18n="agents.drawer.search.preview_title">Test tool selection</div>
<div class="drawer-section-sub" data-i18n="agents.drawer.search.preview_subtitle">Enter a task and verify which tools the model will receive.</div>
<div class="tool-search-preview-controls">
<input class="form-input" type="text" x-model="searchPreviewQuery" @keydown.enter.prevent="previewToolSearch()" data-i18n-ph="agents.drawer.search.query_placeholder" placeholder="Create an invoice for a customer">
<select class="form-select" x-model="searchPreviewGroup">
<option value="" data-i18n="agents.drawer.search.all_groups">All sections</option>
<template x-for="group in form.groups" :key="group.id">
<option :value="group.id" x-text="group.name || group.id"></option>
</template>
</select>
<button class="btn-primary-sm" :disabled="searchPreviewLoading || !searchPreviewQuery.trim() || !catalogConfigValid" @click="previewToolSearch()" x-text="searchPreviewLoading ? tKey('agents.drawer.search.testing') : tKey('agents.drawer.search.test')">Test</button>
</div>
<div class="tool-search-results" x-show="searchPreviewItems.length > 0">
<template x-for="item in searchPreviewItems" :key="item.tool.name">
<div class="tool-search-result">
<div><strong x-text="item.tool.title"></strong><code x-text="item.tool.name"></code></div>
<span x-text="item.tool.group_ids.join(', ')"></span>
</div>
</template>
</div>
<div class="tool-group-empty" x-show="searchPreviewRan && !searchPreviewLoading && searchPreviewItems.length === 0" data-i18n="agents.drawer.search.no_results">No tools matched this task.</div>
</div>
</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()"
:disabled="!form.display_name.trim() || !form.slug.trim() || !catalogConfigValid"
@click="saveAgent()"
x-text="drawerMode === 'create' ? tKey('agents.drawer.create') : tKey('agents.drawer.save')">
Create agent