docs(similarity): add API documentation and fix template accessibility
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
+44
@@ -666,6 +666,50 @@ curl -OJ "http://<your-instance>/api/files/123/download?version=original"
|
||||
- `404`: File not found in database or on disk
|
||||
- `400`: Invalid `version` parameter (must be `processed` or `original`)
|
||||
|
||||
### Similar Documents
|
||||
|
||||
**GET** `/api/files/{file_id}/similar`
|
||||
|
||||
Find documents similar to the specified file using text embeddings and cosine similarity. Similarity scores range from 0 (completely different) to 1 (identical content). Embeddings are generated from OCR-extracted text and cached for subsequent requests.
|
||||
|
||||
**Parameters**:
|
||||
- `limit` (optional, default: `5`, max: `20`): Maximum number of similar documents to return
|
||||
- `threshold` (optional, default: `0.3`, range: `0.0–1.0`): Minimum similarity score to include
|
||||
|
||||
**Response**:
|
||||
```json
|
||||
{
|
||||
"file_id": 42,
|
||||
"similar_documents": [
|
||||
{
|
||||
"file_id": 15,
|
||||
"original_filename": "Invoice_2026-01.pdf",
|
||||
"document_title": "January Invoice",
|
||||
"similarity_score": 0.8934,
|
||||
"mime_type": "application/pdf",
|
||||
"created_at": "2026-01-15T10:30:00+00:00"
|
||||
}
|
||||
],
|
||||
"count": 1
|
||||
}
|
||||
```
|
||||
|
||||
**Example**:
|
||||
```bash
|
||||
# Find top 5 similar documents
|
||||
curl "http://<your-instance>/api/files/42/similar"
|
||||
|
||||
# Find top 10 documents with at least 50% similarity
|
||||
curl "http://<your-instance>/api/files/42/similar?limit=10&threshold=0.5"
|
||||
```
|
||||
|
||||
**Error Responses**:
|
||||
- `404`: File not found
|
||||
- `422`: Invalid query parameters (limit or threshold out of range)
|
||||
- `500`: Embedding generation failed
|
||||
|
||||
> **Note:** Documents without OCR text are excluded from similarity comparisons. The response includes a `message` field when the target file has no OCR text available.
|
||||
|
||||
### Batch Processing
|
||||
|
||||
**POST** `/api/processall`
|
||||
|
||||
@@ -981,32 +981,35 @@
|
||||
}
|
||||
|
||||
// Build the results HTML
|
||||
let html = '<div style="display: grid; gap: 0.75rem;">';
|
||||
let html = '<div style="display: grid; gap: 0.75rem;" role="list" aria-label="Similar documents">';
|
||||
for (const doc of data.similar_documents) {
|
||||
const scorePercent = Math.round(doc.similarity_score * 100);
|
||||
const title = doc.document_title || doc.original_filename || 'Untitled';
|
||||
const filename = doc.original_filename || 'Unknown';
|
||||
const createdAt = doc.created_at ? new Date(doc.created_at).toLocaleDateString() : '';
|
||||
const scoreLabel = scorePercent >= 80 ? 'High' : scorePercent >= 50 ? 'Medium' : 'Low';
|
||||
|
||||
html += `
|
||||
<a href="/files/${doc.file_id}/detail" style="text-decoration: none; color: inherit;">
|
||||
<div style="display: flex; align-items: center; gap: 1rem; padding: 0.75rem 1rem; background-color: #f7fafc; border-radius: 0.5rem; border: 1px solid #e2e8f0; transition: border-color 0.2s;">
|
||||
<div style="flex-shrink: 0; width: 48px; height: 48px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 0.875rem; color: white; background-color: ${scorePercent >= 80 ? '#48bb78' : scorePercent >= 50 ? '#ecc94b' : '#a0aec0'};">
|
||||
<div role="listitem">
|
||||
<a href="/files/${doc.file_id}/detail" aria-label="${title} — ${scorePercent}% similarity (${scoreLabel})" style="text-decoration: none; color: inherit; display: block;">
|
||||
<div style="display: flex; align-items: center; gap: 1rem; padding: 0.75rem 1rem; background-color: #f7fafc; border-radius: 0.5rem; border: 1px solid #e2e8f0; transition: border-color 0.2s; cursor: pointer;" onmouseover="this.style.borderColor='#4299e1'" onmouseout="this.style.borderColor='#e2e8f0'">
|
||||
<div style="flex-shrink: 0; width: 48px; height: 48px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 0.875rem; color: white; background-color: ${scorePercent >= 80 ? '#48bb78' : scorePercent >= 50 ? '#ecc94b' : '#718096'};" aria-hidden="true">
|
||||
${scorePercent}%
|
||||
</div>
|
||||
<div style="flex: 1; min-width: 0;">
|
||||
<div style="font-weight: 600; color: #2d3748; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;" title="${title}">
|
||||
${title}
|
||||
</div>
|
||||
<div style="font-size: 0.75rem; color: #718096; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
|
||||
${filename}${createdAt ? ' · ' + createdAt : ''}
|
||||
<div style="font-size: 0.75rem; color: #4a5568; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
|
||||
${scorePercent}% match (${scoreLabel}) · ${filename}${createdAt ? ' · ' + createdAt : ''}
|
||||
</div>
|
||||
</div>
|
||||
<div style="flex-shrink: 0; color: #a0aec0;">
|
||||
<div style="flex-shrink: 0; color: #4a5568;">
|
||||
<i class="fas fa-chevron-right" aria-hidden="true"></i>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
html += '</div>';
|
||||
|
||||
Reference in New Issue
Block a user