feat: natural language queries respect UI filters (v1.2.0)
- AskRequest now accepts optional filter fields: services, actor, operation, result, start, end, include_tags, exclude_tags - ask_question merges NL-extracted constraints with explicit UI filters - Frontend sends active filter state with every ask request - Show filter hint below ask input when filters are active - Add tests for service+result filtering and actor filtering in /api/ask Bump version to 1.2.0
This commit is contained in:
@@ -50,6 +50,9 @@
|
||||
/>
|
||||
<button type="submit" :disabled="askLoading" x-text="askLoading ? 'Thinking…' : 'Ask'">Ask</button>
|
||||
</div>
|
||||
<div x-show="hasActiveFilters()" class="ask-filter-hint">
|
||||
<small>Respecting active filters: <span x-text="activeFilterSummary()"></span></small>
|
||||
</div>
|
||||
</form>
|
||||
<template x-if="askAnswer">
|
||||
<div class="ask-result">
|
||||
@@ -491,11 +494,29 @@
|
||||
this.askAnswer = '';
|
||||
this.askAnswerHtml = '';
|
||||
this.askEvents = [];
|
||||
this.askLlmError = '';
|
||||
|
||||
const payload = { question: q };
|
||||
if (this.filters.selectedServices && this.filters.selectedServices.length) {
|
||||
payload.services = this.filters.selectedServices;
|
||||
}
|
||||
if (this.filters.actor) payload.actor = this.filters.actor;
|
||||
if (this.filters.operation) payload.operation = this.filters.operation;
|
||||
if (this.filters.result) payload.result = this.filters.result;
|
||||
if (this.filters.start) payload.start = new Date(this.filters.start).toISOString();
|
||||
if (this.filters.end) payload.end = new Date(this.filters.end).toISOString();
|
||||
if (this.filters.includeTags) {
|
||||
payload.include_tags = this.filters.includeTags.split(/[,;]+/).map(t => t.trim()).filter(Boolean);
|
||||
}
|
||||
if (this.filters.excludeTags) {
|
||||
payload.exclude_tags = this.filters.excludeTags.split(/[,;]+/).map(t => t.trim()).filter(Boolean);
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/ask', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', ...this.authHeader() },
|
||||
body: JSON.stringify({ question: q }),
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
if (!res.ok) throw new Error(await res.text());
|
||||
const body = await res.json();
|
||||
@@ -532,6 +553,27 @@
|
||||
.replace(/\n/g, '<br>');
|
||||
},
|
||||
|
||||
hasActiveFilters() {
|
||||
return this.filters.actor || this.filters.operation || this.filters.result ||
|
||||
this.filters.start || this.filters.end || this.filters.includeTags ||
|
||||
this.filters.excludeTags ||
|
||||
(this.filters.selectedServices && this.filters.selectedServices.length &&
|
||||
this.filters.selectedServices.length < this.options.services.length);
|
||||
},
|
||||
|
||||
activeFilterSummary() {
|
||||
const parts = [];
|
||||
if (this.filters.actor) parts.push('actor');
|
||||
if (this.filters.operation) parts.push('action');
|
||||
if (this.filters.result) parts.push('result');
|
||||
if (this.filters.start || this.filters.end) parts.push('time');
|
||||
if (this.filters.includeTags || this.filters.excludeTags) parts.push('tags');
|
||||
const svcCount = this.filters.selectedServices?.length || 0;
|
||||
const allCount = this.options.services?.length || 0;
|
||||
if (svcCount && svcCount < allCount) parts.push(`${svcCount} service${svcCount === 1 ? '' : 's'}`);
|
||||
return parts.join(', ') || 'none';
|
||||
},
|
||||
|
||||
async bulkTagMatching() {
|
||||
const tag = prompt('Enter tag to apply to all matching events:');
|
||||
if (!tag || !tag.trim()) return;
|
||||
|
||||
@@ -428,6 +428,11 @@ input {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.ask-filter-hint {
|
||||
margin-top: 6px;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.ask-events {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user