winebox 0.1.2__py3-none-any.whl → 0.1.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- winebox/__init__.py +1 -1
- winebox/config.py +4 -0
- winebox/routers/wines.py +146 -10
- winebox/services/ocr.py +37 -0
- winebox/services/vision.py +251 -0
- winebox/static/css/style.css +344 -0
- winebox/static/favicon.svg +22 -0
- winebox/static/index.html +75 -1
- winebox/static/js/app.js +299 -5
- {winebox-0.1.2.dist-info → winebox-0.1.3.dist-info}/METADATA +34 -1
- {winebox-0.1.2.dist-info → winebox-0.1.3.dist-info}/RECORD +14 -12
- {winebox-0.1.2.dist-info → winebox-0.1.3.dist-info}/WHEEL +0 -0
- {winebox-0.1.2.dist-info → winebox-0.1.3.dist-info}/entry_points.txt +0 -0
- {winebox-0.1.2.dist-info → winebox-0.1.3.dist-info}/licenses/LICENSE +0 -0
winebox/static/js/app.js
CHANGED
|
@@ -17,8 +17,23 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
17
17
|
initModals();
|
|
18
18
|
initAuth();
|
|
19
19
|
checkAuth();
|
|
20
|
+
loadAppInfo();
|
|
20
21
|
});
|
|
21
22
|
|
|
23
|
+
// Load app info for footer
|
|
24
|
+
async function loadAppInfo() {
|
|
25
|
+
try {
|
|
26
|
+
const response = await fetch('/health');
|
|
27
|
+
const data = await response.json();
|
|
28
|
+
const appInfo = document.getElementById('app-info');
|
|
29
|
+
if (appInfo && data.app_name && data.version) {
|
|
30
|
+
appInfo.innerHTML = `${data.app_name} <span class="version">v${data.version}</span>`;
|
|
31
|
+
}
|
|
32
|
+
} catch (error) {
|
|
33
|
+
console.log('Could not load app info');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
22
37
|
// Authentication
|
|
23
38
|
function initAuth() {
|
|
24
39
|
// Login form
|
|
@@ -26,6 +41,28 @@ function initAuth() {
|
|
|
26
41
|
|
|
27
42
|
// Logout button
|
|
28
43
|
document.getElementById('logout-btn').addEventListener('click', handleLogout);
|
|
44
|
+
|
|
45
|
+
// Password toggle
|
|
46
|
+
const passwordToggle = document.querySelector('.password-toggle');
|
|
47
|
+
if (passwordToggle) {
|
|
48
|
+
passwordToggle.addEventListener('click', function() {
|
|
49
|
+
const passwordInput = document.getElementById('login-password');
|
|
50
|
+
const eyeIcon = this.querySelector('.eye-icon');
|
|
51
|
+
const eyeOffIcon = this.querySelector('.eye-off-icon');
|
|
52
|
+
|
|
53
|
+
if (passwordInput.type === 'password') {
|
|
54
|
+
passwordInput.type = 'text';
|
|
55
|
+
eyeIcon.style.display = 'none';
|
|
56
|
+
eyeOffIcon.style.display = 'block';
|
|
57
|
+
this.setAttribute('aria-label', 'Hide password');
|
|
58
|
+
} else {
|
|
59
|
+
passwordInput.type = 'password';
|
|
60
|
+
eyeIcon.style.display = 'block';
|
|
61
|
+
eyeOffIcon.style.display = 'none';
|
|
62
|
+
this.setAttribute('aria-label', 'Show password');
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
29
66
|
}
|
|
30
67
|
|
|
31
68
|
async function checkAuth() {
|
|
@@ -177,6 +214,7 @@ function initForms() {
|
|
|
177
214
|
checkinForm.addEventListener('reset', () => {
|
|
178
215
|
document.getElementById('front-preview').innerHTML = 'Tap to take photo or select image';
|
|
179
216
|
document.getElementById('back-preview').innerHTML = 'Tap to take photo or select image';
|
|
217
|
+
clearRawLabelText();
|
|
180
218
|
});
|
|
181
219
|
|
|
182
220
|
// Image previews - make clickable to trigger file input
|
|
@@ -187,9 +225,11 @@ function initForms() {
|
|
|
187
225
|
|
|
188
226
|
frontLabel.addEventListener('change', (e) => {
|
|
189
227
|
previewImage(e.target, 'front-preview');
|
|
228
|
+
scanLabels();
|
|
190
229
|
});
|
|
191
230
|
backLabel.addEventListener('change', (e) => {
|
|
192
231
|
previewImage(e.target, 'back-preview');
|
|
232
|
+
scanLabels();
|
|
193
233
|
});
|
|
194
234
|
|
|
195
235
|
// Click on preview to trigger file input
|
|
@@ -200,6 +240,25 @@ function initForms() {
|
|
|
200
240
|
backLabel.click();
|
|
201
241
|
});
|
|
202
242
|
|
|
243
|
+
// Label text collapsible toggle
|
|
244
|
+
const labelTextToggle = document.getElementById('label-text-toggle');
|
|
245
|
+
if (labelTextToggle) {
|
|
246
|
+
labelTextToggle.addEventListener('click', () => {
|
|
247
|
+
const section = document.getElementById('label-text-section');
|
|
248
|
+
const content = document.getElementById('label-text-content');
|
|
249
|
+
const icon = section.querySelector('.collapse-icon');
|
|
250
|
+
|
|
251
|
+
section.classList.toggle('open');
|
|
252
|
+
if (section.classList.contains('open')) {
|
|
253
|
+
content.style.display = 'block';
|
|
254
|
+
icon.textContent = '-';
|
|
255
|
+
} else {
|
|
256
|
+
content.style.display = 'none';
|
|
257
|
+
icon.textContent = '+';
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
203
262
|
// Search form
|
|
204
263
|
document.getElementById('search-form').addEventListener('submit', handleSearch);
|
|
205
264
|
|
|
@@ -227,6 +286,141 @@ function previewImage(input, previewId) {
|
|
|
227
286
|
}
|
|
228
287
|
}
|
|
229
288
|
|
|
289
|
+
async function scanLabels() {
|
|
290
|
+
const frontLabel = document.getElementById('front-label');
|
|
291
|
+
|
|
292
|
+
// Only scan if front label is present
|
|
293
|
+
if (!frontLabel.files || !frontLabel.files[0]) {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const backLabel = document.getElementById('back-label');
|
|
298
|
+
const formData = new FormData();
|
|
299
|
+
formData.append('front_label', frontLabel.files[0]);
|
|
300
|
+
|
|
301
|
+
if (backLabel.files && backLabel.files[0]) {
|
|
302
|
+
formData.append('back_label', backLabel.files[0]);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Show scanning indicator
|
|
306
|
+
showScanningIndicator(true);
|
|
307
|
+
|
|
308
|
+
try {
|
|
309
|
+
const response = await fetchWithAuth(`${API_BASE}/wines/scan`, {
|
|
310
|
+
method: 'POST',
|
|
311
|
+
body: formData
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
if (!response.ok) {
|
|
315
|
+
const error = await response.json();
|
|
316
|
+
throw new Error(error.detail || 'Scan failed');
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const result = await response.json();
|
|
320
|
+
populateFormFromScan(result);
|
|
321
|
+
const methodName = result.method === 'claude_vision' ? 'Claude Vision' : 'Tesseract OCR';
|
|
322
|
+
showToast(`Label scanned with ${methodName}`, 'success');
|
|
323
|
+
} catch (error) {
|
|
324
|
+
showToast(`Scan failed: ${error.message}`, 'error');
|
|
325
|
+
} finally {
|
|
326
|
+
showScanningIndicator(false);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function populateFormFromScan(result) {
|
|
331
|
+
const parsed = result.parsed;
|
|
332
|
+
|
|
333
|
+
// Update fields with scanned values (overwrites previous scan results)
|
|
334
|
+
const fields = {
|
|
335
|
+
'wine-name': parsed.name,
|
|
336
|
+
'winery': parsed.winery,
|
|
337
|
+
'vintage': parsed.vintage,
|
|
338
|
+
'grape-variety': parsed.grape_variety,
|
|
339
|
+
'region': parsed.region,
|
|
340
|
+
'country': parsed.country,
|
|
341
|
+
'alcohol': parsed.alcohol_percentage
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
for (const [fieldId, value] of Object.entries(fields)) {
|
|
345
|
+
const input = document.getElementById(fieldId);
|
|
346
|
+
if (input && value !== null && value !== undefined) {
|
|
347
|
+
input.value = value;
|
|
348
|
+
// Add visual indicator that field was auto-filled
|
|
349
|
+
input.classList.add('auto-filled');
|
|
350
|
+
setTimeout(() => input.classList.remove('auto-filled'), 2000);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Populate raw label text section
|
|
355
|
+
populateRawLabelText(result.ocr, result.method);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function populateRawLabelText(ocr, method) {
|
|
359
|
+
const section = document.getElementById('label-text-section');
|
|
360
|
+
const frontText = document.getElementById('raw-front-label-text');
|
|
361
|
+
const backSection = document.getElementById('raw-back-label-section');
|
|
362
|
+
const backText = document.getElementById('raw-back-label-text');
|
|
363
|
+
const header = section.querySelector('h3');
|
|
364
|
+
|
|
365
|
+
// Update header to show scan method
|
|
366
|
+
const methodName = method === 'claude_vision' ? 'Claude Vision' : 'Tesseract OCR';
|
|
367
|
+
header.innerHTML = `Raw Label Text <span class="scan-method-badge">${methodName}</span>`;
|
|
368
|
+
|
|
369
|
+
if (ocr.front_label_text) {
|
|
370
|
+
frontText.textContent = ocr.front_label_text;
|
|
371
|
+
section.style.display = 'block';
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if (ocr.back_label_text) {
|
|
375
|
+
backText.textContent = ocr.back_label_text;
|
|
376
|
+
backSection.style.display = 'block';
|
|
377
|
+
} else {
|
|
378
|
+
backSection.style.display = 'none';
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function clearRawLabelText() {
|
|
383
|
+
const section = document.getElementById('label-text-section');
|
|
384
|
+
const frontText = document.getElementById('raw-front-label-text');
|
|
385
|
+
const backSection = document.getElementById('raw-back-label-section');
|
|
386
|
+
const backText = document.getElementById('raw-back-label-text');
|
|
387
|
+
|
|
388
|
+
section.style.display = 'none';
|
|
389
|
+
section.classList.remove('open');
|
|
390
|
+
document.getElementById('label-text-content').style.display = 'none';
|
|
391
|
+
document.querySelector('#label-text-section .collapse-icon').textContent = '+';
|
|
392
|
+
frontText.textContent = '';
|
|
393
|
+
backText.textContent = '';
|
|
394
|
+
backSection.style.display = 'none';
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function showScanningIndicator(show) {
|
|
398
|
+
const submitBtn = document.querySelector('#checkin-form button[type="submit"]');
|
|
399
|
+
const formNote = document.querySelector('#checkin-form .form-note');
|
|
400
|
+
|
|
401
|
+
if (show) {
|
|
402
|
+
if (submitBtn) {
|
|
403
|
+
submitBtn.disabled = true;
|
|
404
|
+
submitBtn.dataset.originalText = submitBtn.textContent;
|
|
405
|
+
submitBtn.textContent = 'Scanning...';
|
|
406
|
+
}
|
|
407
|
+
if (formNote) {
|
|
408
|
+
formNote.dataset.originalText = formNote.textContent;
|
|
409
|
+
formNote.textContent = 'Analyzing label with Claude Vision...';
|
|
410
|
+
formNote.classList.add('scanning');
|
|
411
|
+
}
|
|
412
|
+
} else {
|
|
413
|
+
if (submitBtn) {
|
|
414
|
+
submitBtn.disabled = false;
|
|
415
|
+
submitBtn.textContent = submitBtn.dataset.originalText || 'Check In Wine';
|
|
416
|
+
}
|
|
417
|
+
if (formNote) {
|
|
418
|
+
formNote.textContent = formNote.dataset.originalText || 'Leave fields blank to use OCR-detected values';
|
|
419
|
+
formNote.classList.remove('scanning');
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
230
424
|
async function handleCheckin(e) {
|
|
231
425
|
e.preventDefault();
|
|
232
426
|
const form = e.target;
|
|
@@ -244,14 +438,74 @@ async function handleCheckin(e) {
|
|
|
244
438
|
}
|
|
245
439
|
|
|
246
440
|
const wine = await response.json();
|
|
247
|
-
|
|
441
|
+
showCheckinConfirmation(wine);
|
|
248
442
|
form.reset();
|
|
249
|
-
navigateTo('cellar');
|
|
250
443
|
} catch (error) {
|
|
251
444
|
showToast(error.message, 'error');
|
|
252
445
|
}
|
|
253
446
|
}
|
|
254
447
|
|
|
448
|
+
function showCheckinConfirmation(wine) {
|
|
449
|
+
const modal = document.getElementById('checkin-confirm-modal');
|
|
450
|
+
|
|
451
|
+
// Set wine name
|
|
452
|
+
document.getElementById('checkin-confirm-name').textContent = wine.name;
|
|
453
|
+
|
|
454
|
+
// Set image
|
|
455
|
+
const imageContainer = document.getElementById('checkin-confirm-image');
|
|
456
|
+
if (wine.front_label_image_path) {
|
|
457
|
+
imageContainer.innerHTML = `<img src="${API_BASE}/images/${wine.front_label_image_path}" alt="Wine label">`;
|
|
458
|
+
} else {
|
|
459
|
+
imageContainer.innerHTML = '<div style="display:flex;align-items:center;justify-content:center;height:100%;color:var(--text-muted);">No image</div>';
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// Set parsed fields
|
|
463
|
+
const fieldsContainer = document.getElementById('checkin-confirm-fields');
|
|
464
|
+
const fields = [
|
|
465
|
+
{ label: 'Winery', value: wine.winery },
|
|
466
|
+
{ label: 'Vintage', value: wine.vintage },
|
|
467
|
+
{ label: 'Grape Variety', value: wine.grape_variety },
|
|
468
|
+
{ label: 'Region', value: wine.region },
|
|
469
|
+
{ label: 'Country', value: wine.country },
|
|
470
|
+
{ label: 'Alcohol %', value: wine.alcohol_percentage ? `${wine.alcohol_percentage}%` : null },
|
|
471
|
+
{ label: 'Quantity', value: wine.inventory?.quantity || 1 }
|
|
472
|
+
];
|
|
473
|
+
|
|
474
|
+
fieldsContainer.innerHTML = fields.map(field => `
|
|
475
|
+
<div class="checkin-confirm-field">
|
|
476
|
+
<div class="label">${field.label}</div>
|
|
477
|
+
<div class="value ${field.value ? '' : 'empty'}">${field.value || 'Not detected'}</div>
|
|
478
|
+
</div>
|
|
479
|
+
`).join('');
|
|
480
|
+
|
|
481
|
+
// Set OCR text
|
|
482
|
+
document.getElementById('checkin-confirm-front-ocr').textContent = wine.front_label_text || 'No text extracted';
|
|
483
|
+
|
|
484
|
+
const backOcrSection = document.getElementById('checkin-confirm-back-ocr-section');
|
|
485
|
+
if (wine.back_label_text) {
|
|
486
|
+
backOcrSection.style.display = 'block';
|
|
487
|
+
document.getElementById('checkin-confirm-back-ocr').textContent = wine.back_label_text;
|
|
488
|
+
} else {
|
|
489
|
+
backOcrSection.style.display = 'none';
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Show modal
|
|
493
|
+
modal.classList.add('active');
|
|
494
|
+
|
|
495
|
+
// Set up button handlers
|
|
496
|
+
document.getElementById('checkin-confirm-done').onclick = () => {
|
|
497
|
+
modal.classList.remove('active');
|
|
498
|
+
navigateTo('cellar');
|
|
499
|
+
};
|
|
500
|
+
|
|
501
|
+
document.getElementById('checkin-confirm-another').onclick = () => {
|
|
502
|
+
modal.classList.remove('active');
|
|
503
|
+
// Reset form previews
|
|
504
|
+
document.getElementById('front-preview').innerHTML = 'Tap to take photo or select image';
|
|
505
|
+
document.getElementById('back-preview').innerHTML = 'Tap to take photo or select image';
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
|
|
255
509
|
async function handleSearch(e) {
|
|
256
510
|
e.preventDefault();
|
|
257
511
|
const form = e.target;
|
|
@@ -558,9 +812,25 @@ async function showWineDetail(wineId) {
|
|
|
558
812
|
` : ''}
|
|
559
813
|
|
|
560
814
|
${wine.front_label_text ? `
|
|
561
|
-
<div class="wine-detail-
|
|
562
|
-
<div class="
|
|
563
|
-
|
|
815
|
+
<div class="wine-detail-label-text collapsible">
|
|
816
|
+
<div class="collapsible-header" onclick="toggleWineDetailLabelText(this)">
|
|
817
|
+
<span class="label">Show Raw Label Text</span>
|
|
818
|
+
<span class="collapse-icon">+</span>
|
|
819
|
+
</div>
|
|
820
|
+
<div class="collapsible-content" style="display: none;">
|
|
821
|
+
<div class="ocr-raw-text">
|
|
822
|
+
<div class="ocr-raw-section">
|
|
823
|
+
<label>Front Label:</label>
|
|
824
|
+
<pre>${escapeHtml(wine.front_label_text)}</pre>
|
|
825
|
+
</div>
|
|
826
|
+
${wine.back_label_text ? `
|
|
827
|
+
<div class="ocr-raw-section">
|
|
828
|
+
<label>Back Label:</label>
|
|
829
|
+
<pre>${escapeHtml(wine.back_label_text)}</pre>
|
|
830
|
+
</div>
|
|
831
|
+
` : ''}
|
|
832
|
+
</div>
|
|
833
|
+
</div>
|
|
564
834
|
</div>
|
|
565
835
|
` : ''}
|
|
566
836
|
|
|
@@ -678,6 +948,30 @@ function formatDate(dateString) {
|
|
|
678
948
|
});
|
|
679
949
|
}
|
|
680
950
|
|
|
951
|
+
function escapeHtml(text) {
|
|
952
|
+
const div = document.createElement('div');
|
|
953
|
+
div.textContent = text;
|
|
954
|
+
return div.innerHTML;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
function toggleWineDetailLabelText(header) {
|
|
958
|
+
const section = header.parentElement;
|
|
959
|
+
const content = section.querySelector('.collapsible-content');
|
|
960
|
+
const icon = header.querySelector('.collapse-icon');
|
|
961
|
+
const label = header.querySelector('.label');
|
|
962
|
+
|
|
963
|
+
section.classList.toggle('open');
|
|
964
|
+
if (section.classList.contains('open')) {
|
|
965
|
+
content.style.display = 'block';
|
|
966
|
+
icon.textContent = '-';
|
|
967
|
+
label.textContent = 'Hide Raw Label Text';
|
|
968
|
+
} else {
|
|
969
|
+
content.style.display = 'none';
|
|
970
|
+
icon.textContent = '+';
|
|
971
|
+
label.textContent = 'Show Raw Label Text';
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
|
|
681
975
|
function showToast(message, type = 'info') {
|
|
682
976
|
const container = document.getElementById('toast-container');
|
|
683
977
|
const toast = document.createElement('div');
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: winebox
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: Wine Cellar Management Application with OCR label scanning
|
|
5
5
|
Project-URL: Homepage, https://github.com/jdrumgoole/winebox
|
|
6
6
|
Project-URL: Repository, https://github.com/jdrumgoole/winebox
|
|
@@ -22,6 +22,7 @@ Classifier: Topic :: Home Automation
|
|
|
22
22
|
Requires-Python: >=3.11
|
|
23
23
|
Requires-Dist: aiofiles>=23.0.0
|
|
24
24
|
Requires-Dist: aiosqlite>=0.19.0
|
|
25
|
+
Requires-Dist: anthropic>=0.40.0
|
|
25
26
|
Requires-Dist: bcrypt<4.1.0,>=4.0.0
|
|
26
27
|
Requires-Dist: fastapi>=0.109.0
|
|
27
28
|
Requires-Dist: jinja2>=3.1.0
|
|
@@ -173,6 +174,38 @@ Images are served via the API at `/api/images/{filename}`.
|
|
|
173
174
|
|
|
174
175
|
**Note:** The `data/` directory is excluded from git (see `.gitignore`). Make sure to back up this directory to preserve your wine collection data.
|
|
175
176
|
|
|
177
|
+
## Label Scanning
|
|
178
|
+
|
|
179
|
+
WineBox uses AI-powered label scanning to extract wine information from photos.
|
|
180
|
+
|
|
181
|
+
### Claude Vision (Recommended)
|
|
182
|
+
|
|
183
|
+
For best results, configure Claude Vision by setting your Anthropic API key:
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
export ANTHROPIC_API_KEY=your-api-key
|
|
187
|
+
# or
|
|
188
|
+
export WINEBOX_ANTHROPIC_API_KEY=your-api-key
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Claude Vision provides intelligent label analysis that:
|
|
192
|
+
- Handles decorative and artistic fonts
|
|
193
|
+
- Understands wine-specific terminology
|
|
194
|
+
- Extracts structured data (winery, vintage, grape variety, region, etc.)
|
|
195
|
+
- Works with curved or angled text
|
|
196
|
+
|
|
197
|
+
### Tesseract OCR (Fallback)
|
|
198
|
+
|
|
199
|
+
If no Anthropic API key is configured, WineBox falls back to Tesseract OCR. This requires Tesseract to be installed on your system:
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
# macOS
|
|
203
|
+
brew install tesseract
|
|
204
|
+
|
|
205
|
+
# Ubuntu/Debian
|
|
206
|
+
sudo apt-get install tesseract-ocr
|
|
207
|
+
```
|
|
208
|
+
|
|
176
209
|
## Authentication
|
|
177
210
|
|
|
178
211
|
WineBox requires authentication for all API endpoints (except `/health`).
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
winebox/__init__.py,sha256=
|
|
2
|
-
winebox/config.py,sha256=
|
|
1
|
+
winebox/__init__.py,sha256=1MbD6vmUlpuZKB-RvWkszuH5NNsw0MFclRCQgwErdNQ,75
|
|
2
|
+
winebox/config.py,sha256=mxdWkchZdYkPrSpfgn1RV26nZK09_3rql0iGbcAGvM0,1233
|
|
3
3
|
winebox/database.py,sha256=jTdf9mb48D8644THo6Mx6lHCKDFlAZ18P8Dvuk2eROI,1128
|
|
4
4
|
winebox/main.py,sha256=wlXrBwMvJQhG3g8mmOGWp-QbEEbR805SaElBryR61t0,2398
|
|
5
5
|
winebox/cli/__init__.py,sha256=FOUoclklBXVfsfON3ohA8v_coJ9p5LSbVYvTucyXieg,29
|
|
@@ -15,20 +15,22 @@ winebox/routers/auth.py,sha256=7rmld-TOIHmCMJfQKuQfkZFoNAAZd0zuj-mKqmAbHeg,2446
|
|
|
15
15
|
winebox/routers/cellar.py,sha256=QSyAnPQ2hiNaoojLk-5hJy95OnG8RMHOD0h5LV6bAWM,3404
|
|
16
16
|
winebox/routers/search.py,sha256=aIqwsjnB9e-D84VVcuye-01V_9WBUjFKzXNsX0gkRak,4971
|
|
17
17
|
winebox/routers/transactions.py,sha256=K0UDIJbVSTjV7NdI_CS0pvoTflH3Yp89DmneyFyl9ok,2055
|
|
18
|
-
winebox/routers/wines.py,sha256=
|
|
18
|
+
winebox/routers/wines.py,sha256=muTwU0T21vkihNOyVtnXjQY7Fl6d2BRKBpJoIyyieA4,14238
|
|
19
19
|
winebox/schemas/__init__.py,sha256=R4j1wdV1F-6kA45rHA8NUP2cqxBmLb2CMklo8tFjzGQ,357
|
|
20
20
|
winebox/schemas/transaction.py,sha256=GV_AZGeuFp_nOkg902UUhrHGwB1DxcQmGoG82m1UccI,899
|
|
21
21
|
winebox/schemas/wine.py,sha256=xHWDiV5bvEJuLPN2qWgOhqVMOTdlDSmfan5mtEy3Nvw,2301
|
|
22
22
|
winebox/services/__init__.py,sha256=rlAd5FBcqZJ7rsrFqR8MwTqGJVerlQHVUleEmoIaz1Y,277
|
|
23
23
|
winebox/services/auth.py,sha256=RDXP26hL3cGGxKcniXWMZ0sPUP6ew4LLMnfo4dv4P2E,3854
|
|
24
24
|
winebox/services/image_storage.py,sha256=ftiX54dVuDT0DVLpyBOL9N7zhZkapI0l1TFknfPedno,2393
|
|
25
|
-
winebox/services/ocr.py,sha256=
|
|
25
|
+
winebox/services/ocr.py,sha256=zFmQGj3a_jv21MBx8RfNkFIenAOnJ0O28JupSrsNC44,4925
|
|
26
|
+
winebox/services/vision.py,sha256=9WKHrDdFK0AmmmnST7vnV50Vgr6UsKR-WUtflKklJLc,9100
|
|
26
27
|
winebox/services/wine_parser.py,sha256=n5ldv2x2XsTfpK3acYDyMNyZHdJ_8WQU0keOql14MJ0,10456
|
|
27
|
-
winebox/static/
|
|
28
|
-
winebox/static/
|
|
29
|
-
winebox/static/
|
|
30
|
-
winebox
|
|
31
|
-
winebox-0.1.
|
|
32
|
-
winebox-0.1.
|
|
33
|
-
winebox-0.1.
|
|
34
|
-
winebox-0.1.
|
|
28
|
+
winebox/static/favicon.svg,sha256=jYFlQ-dEfhUoHQJepoSEMYQx0jxk8HyuX0wrd4OApzE,1061
|
|
29
|
+
winebox/static/index.html,sha256=7T_bgTKKGOQ0eKoo4scyVAD51GyCfXfNrJMTwlNvbYQ,17065
|
|
30
|
+
winebox/static/css/style.css,sha256=TBVTdw5eEkm2du7GeS-sPTsWiE-_ttzAWozWLXOuBzU,25940
|
|
31
|
+
winebox/static/js/app.js,sha256=RNDdXMaz3uC3emRnsaUPxDCi9Q5SPcUQeuIrTRAmX2U,34741
|
|
32
|
+
winebox-0.1.3.dist-info/METADATA,sha256=UcVbvLKE-F1v3AiS8GISrF379XqEN2tF_B63ra0Je-k,8001
|
|
33
|
+
winebox-0.1.3.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
34
|
+
winebox-0.1.3.dist-info/entry_points.txt,sha256=XY-GMf023m8Iof3rmokkITONECm8gCOVkTX0rCkze30,103
|
|
35
|
+
winebox-0.1.3.dist-info/licenses/LICENSE,sha256=3popbhCShFhWVwTLKeQE-JNDYuPOYfmCiz-LoQpizTA,1070
|
|
36
|
+
winebox-0.1.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|