Improve camera model search with per-model ranking and two-stage loading
- Split camera results into individual models (Brand: Model format) - Add model-specific relevance scoring for better search results - Implement two-stage autocomplete: 10 results immediately, 50 after 1 second - Filter out "Other" models from search results - Sort models by relevance score (exact matches first) - Add auto.json brand for automatic detection fallback 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
export class CameraSearchAPI {
|
||||
constructor(baseURL = null) {
|
||||
// Auto-detect API URL based on current host
|
||||
if (!baseURL) {
|
||||
const currentHost = window.location.hostname;
|
||||
this.baseURL = `http://${currentHost}:8080`;
|
||||
} else {
|
||||
this.baseURL = baseURL;
|
||||
}
|
||||
}
|
||||
|
||||
async search(query, limit = 10) {
|
||||
const response = await fetch(`${this.baseURL}/api/v1/cameras/search`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ query, limit }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
export class StreamDiscoveryAPI {
|
||||
constructor(baseURL = null) {
|
||||
// Auto-detect API URL based on current host
|
||||
if (!baseURL) {
|
||||
const currentHost = window.location.hostname;
|
||||
this.baseURL = `http://${currentHost}:8080`;
|
||||
} else {
|
||||
this.baseURL = baseURL;
|
||||
}
|
||||
this.eventSource = null;
|
||||
}
|
||||
|
||||
discover(request, callbacks) {
|
||||
this.close();
|
||||
|
||||
const url = new URL(`${this.baseURL}/api/v1/streams/discover`);
|
||||
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'text/event-stream',
|
||||
},
|
||||
body: JSON.stringify(request),
|
||||
}).then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const reader = response.body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
const processStream = ({ done, value }) => {
|
||||
if (done) {
|
||||
return;
|
||||
}
|
||||
|
||||
const chunk = decoder.decode(value, { stream: true });
|
||||
const lines = chunk.split('\n');
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('event:')) {
|
||||
const eventType = line.substring(6).trim();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.startsWith('data:')) {
|
||||
const data = line.substring(5).trim();
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(data);
|
||||
this.handleEvent(parsed, callbacks);
|
||||
} catch (e) {
|
||||
console.error('Failed to parse SSE data:', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return reader.read().then(processStream);
|
||||
};
|
||||
|
||||
return reader.read().then(processStream);
|
||||
}).catch(error => {
|
||||
if (callbacks.onError) {
|
||||
callbacks.onError(error.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
handleEvent(data, callbacks) {
|
||||
// Determine event type from data
|
||||
if (data.tested !== undefined && data.found !== undefined) {
|
||||
// Progress event
|
||||
if (callbacks.onProgress) {
|
||||
callbacks.onProgress(data);
|
||||
}
|
||||
} else if (data.stream) {
|
||||
// Stream found event
|
||||
if (callbacks.onStreamFound) {
|
||||
callbacks.onStreamFound(data);
|
||||
}
|
||||
} else if (data.total_tested !== undefined) {
|
||||
// Complete event
|
||||
if (callbacks.onComplete) {
|
||||
callbacks.onComplete(data);
|
||||
}
|
||||
} else if (data.error) {
|
||||
// Error event
|
||||
if (callbacks.onError) {
|
||||
callbacks.onError(data.error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
close() {
|
||||
if (this.eventSource) {
|
||||
this.eventSource.close();
|
||||
this.eventSource = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user