-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathsearch.html
More file actions
160 lines (147 loc) · 6.13 KB
/
Copy pathsearch.html
File metadata and controls
160 lines (147 loc) · 6.13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
{% extends "base.html" %}
{% block title %}Search — Cursor Chat Browser{% endblock %}
{% block content %}
<div class="search-page">
<div class="page-header">
<h1>Search</h1>
<div class="btn-group" style="align-self:center">
<button class="btn" id="filter-all" onclick="setFilter('all')">All</button>
<button class="btn btn-outline" id="filter-chat" onclick="setFilter('chat')">Ask Logs</button>
<button class="btn btn-outline" id="filter-composer" onclick="setFilter('composer')">Agent Logs</button>
</div>
</div>
<div class="form-group" style="margin-bottom:1.5rem">
<div class="search-bar">
<input type="text" id="search-input" class="input" placeholder="Search across all logs..." autofocus>
<button
type="button"
class="search-info-tooltip"
id="search-window-help"
aria-label="Search window help"
aria-describedby="search-window-help-text"
>
<span class="search-info-icon" aria-hidden="true">i</span>
<span class="search-info-tooltip-text" id="search-window-help-text" role="tooltip">
By default, search covers the most recent 30-day indexed window.
Chats without a parseable date are always included.
Check “Include chats older than 30 days” to search full history.
</span>
</button>
<button class="btn btn-primary" onclick="doSearch()">Search</button>
</div>
<label class="text-sm" style="display:flex;align-items:center;gap:0.5rem;margin-top:0.75rem;cursor:pointer">
<input type="checkbox" id="all-history" onchange="doSearch()">
Include chats older than 30 days <span class="text-muted">(searches all history — slower)</span>
</label>
</div>
<div id="loading" class="loading" style="display:none">
<div class="spinner"></div>
<p>Searching...</p>
</div>
<div id="parse-warnings-host"></div>
<p id="result-count" class="text-muted" style="display:none"></p>
<div id="results-container"></div>
</div>
<script>
let currentFilter = 'all';
document.addEventListener('DOMContentLoaded', () => {
const params = new URLSearchParams(window.location.search);
const q = params.get('q');
const t = params.get('type') || 'all';
const allHistory = params.get('all_history') === '1' || params.get('all_history') === 'true';
document.getElementById('all-history').checked = allHistory;
if (q) {
document.getElementById('search-input').value = q;
currentFilter = t;
updateFilterButtons();
performSearch(q, t, allHistory);
}
document.getElementById('search-input').addEventListener('keydown', e => {
if (e.key === 'Enter') doSearch();
});
});
function setFilter(type) {
currentFilter = type;
updateFilterButtons();
doSearch();
}
function updateFilterButtons() {
['all', 'chat', 'composer'].forEach(t => {
const btn = document.getElementById('filter-' + t);
btn.className = t === currentFilter ? 'btn' : 'btn btn-outline';
});
}
function doSearch() {
const q = document.getElementById('search-input').value.trim();
if (!q) return;
const allHistory = document.getElementById('all-history').checked;
const url = new URL(window.location);
url.searchParams.set('q', q);
url.searchParams.set('type', currentFilter);
if (allHistory) {
url.searchParams.set('all_history', '1');
} else {
url.searchParams.delete('all_history');
}
window.history.pushState({}, '', url);
performSearch(q, currentFilter, allHistory);
}
async function performSearch(query, type, allHistory) {
const loading = document.getElementById('loading');
const countEl = document.getElementById('result-count');
const container = document.getElementById('results-container');
loading.style.display = 'flex';
countEl.style.display = 'none';
container.innerHTML = '';
try {
const params = new URLSearchParams({ q: query, type: type });
if (allHistory) params.set('all_history', '1');
const res = await fetch(`/api/search?${params.toString()}`);
const data = await res.json().catch(() => ({}));
if (!res.ok) {
showIncompleteResultsBanner('parse-warnings-host', []);
loading.style.display = 'none';
const message = typeof data.error === 'string' ? data.error : 'Search failed.';
const codeAttr = typeof data.code === 'string'
? ` data-error-code="${escapeHtml(data.code)}"`
: '';
container.innerHTML = `<p class="text-danger"${codeAttr}>${escapeHtml(message)}</p>`;
return;
}
const results = data.results || [];
showIncompleteResultsBanner('parse-warnings-host', data.warnings);
loading.style.display = 'none';
countEl.style.display = 'block';
const windowNote = data.allHistory
? ' (all history)'
: ` (last ${data.searchWindowDays || 30} days; undated chats included)`;
countEl.textContent = `Found ${results.length} results for "${query}"${windowNote}`;
let html = '';
for (const r of results) {
const badge = r.type === 'chat' ? '<span class="badge">Ask Log</span>' : '<span class="badge badge-secondary">Agent Log</span>';
const ts = formatDate(r.timestamp);
const snippet = escapeHtml((r.matchingText || '').slice(0, 300));
const folder = r.workspaceId === 'global'
? '<span class="text-sm" style="color:var(--blue)">Global Storage</span>'
: (r.workspaceFolder ? `<span class="text-sm text-muted">${escapeHtml(r.workspaceFolder)}</span>` : '');
html += `<div class="card" style="margin-bottom:1rem;padding:1rem">
<div class="flex-between">
<div>
<a href="/workspace/${r.workspaceId}?tab=${r.chatId}&type=${r.type}" class="link text-lg">${escapeHtml(r.chatTitle)}</a>
<div class="text-sm text-muted">${ts}</div>
</div>
${badge}
</div>
<div class="text-sm" style="margin-top:0.5rem">${snippet}</div>
${folder ? '<div style="margin-top:0.5rem">' + folder + '</div>' : ''}
</div>`;
}
container.innerHTML = html;
} catch (e) {
showIncompleteResultsBanner('parse-warnings-host', []);
loading.style.display = 'none';
container.innerHTML = '<p class="text-danger">Search failed.</p>';
}
}
</script>
{% endblock %}