harshith99 Claude Sonnet 4.6 commited on
Commit
23dabb1
Β·
1 Parent(s): a9cca6c

UX: add FAQ tab with pipeline diagram, About, and accordion FAQ

Browse files
Files changed (3) hide show
  1. README.md +2 -1
  2. web/static/app.css +173 -0
  3. web/templates/index.html +110 -0
README.md CHANGED
@@ -302,11 +302,12 @@ The UI lets you:
302
  - **Drag-and-drop** your Instagram export zip (or `saved_posts.json` directly) β€” zip is extracted in-browser, no unzipping needed
303
  - **Pick a provider** β€” three options available (see below)
304
  - **Watch real-time progress** as posts are extracted, geocoded, and mapped
305
- - **Two-tab layout visible from first load** β€” the app shows two top-level tabs immediately on the landing page:
306
  - **πŸ“₯ Extract** β€” upload your Instagram export, pick a model, run the pipeline, watch real-time progress
307
  - **πŸ—Ί Explore** β€” for returning users; drop your `places_map.kml` or `places_full.csv` here to skip re-extraction entirely. Once data loads, two sub-tabs appear:
308
  - **🎲 Roulette** β€” auto-spins the moment you land on it; filter by category or location (accepts city name or country name); badge shows how many unvisited spots remain. Default after extraction
309
  - **πŸ“‹ Browse** β€” stats bar, interactive Leaflet map, full-text search, filters, card grid, and KML / GeoJSON / CSV download buttons. Default after import
 
310
  - After extraction completes, the app automatically jumps to Explore β†’ Roulette and spins immediately
311
  - **Preview pins on a map** β€” an in-app Leaflet/OpenStreetMap map renders every geocoded place so you can sanity-check for misplaced pins before exporting. Follows your active filters; pins link back to the original reel
312
  - **Filter and search** β€” text search by name/city, country, category, and cuisine dropdowns, multi-select price range chips, "missing pins" toggle, and a **🍴 Food only** toggle (on by default) that limits Browse and Roulette to food/drink places (Restaurant, Bar, Cafe, Bakery, Market) β€” all client-side with instant results
 
302
  - **Drag-and-drop** your Instagram export zip (or `saved_posts.json` directly) β€” zip is extracted in-browser, no unzipping needed
303
  - **Pick a provider** β€” three options available (see below)
304
  - **Watch real-time progress** as posts are extracted, geocoded, and mapped
305
+ - **Three-tab layout visible from first load** β€” the app shows three top-level tabs:
306
  - **πŸ“₯ Extract** β€” upload your Instagram export, pick a model, run the pipeline, watch real-time progress
307
  - **πŸ—Ί Explore** β€” for returning users; drop your `places_map.kml` or `places_full.csv` here to skip re-extraction entirely. Once data loads, two sub-tabs appear:
308
  - **🎲 Roulette** β€” auto-spins the moment you land on it; filter by category or location (accepts city name or country name); badge shows how many unvisited spots remain. Default after extraction
309
  - **πŸ“‹ Browse** β€” stats bar, interactive Leaflet map, full-text search, filters, card grid, and KML / GeoJSON / CSV download buttons. Default after import
310
+ - **❓ FAQ** β€” system pipeline diagram, About The Belagurian, and answers to common questions
311
  - After extraction completes, the app automatically jumps to Explore β†’ Roulette and spins immediately
312
  - **Preview pins on a map** β€” an in-app Leaflet/OpenStreetMap map renders every geocoded place so you can sanity-check for misplaced pins before exporting. Follows your active filters; pins link back to the original reel
313
  - **Filter and search** β€” text search by name/city, country, category, and cuisine dropdowns, multi-select price range chips, "missing pins" toggle, and a **🍴 Food only** toggle (on by default) that limits Browse and Roulette to food/drink places (Restaurant, Bar, Cafe, Bakery, Market) β€” all client-side with instant results
web/static/app.css CHANGED
@@ -837,3 +837,176 @@
837
  }
838
  .action-btn:hover { opacity: 0.88; }
839
  .action-btn:disabled { opacity: 0.4; cursor: not-allowed; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
837
  }
838
  .action-btn:hover { opacity: 0.88; }
839
  .action-btn:disabled { opacity: 0.4; cursor: not-allowed; }
840
+
841
+ /* ── FAQ / About panel ────────────────────────────────────── */
842
+
843
+ /* Pipeline diagram */
844
+ .pipeline-diagram {
845
+ display: flex;
846
+ align-items: flex-start;
847
+ gap: 0;
848
+ overflow-x: auto;
849
+ padding-bottom: 6px;
850
+ scrollbar-width: thin;
851
+ }
852
+ .pipe-node {
853
+ display: flex;
854
+ flex-direction: column;
855
+ align-items: center;
856
+ text-align: center;
857
+ min-width: 96px;
858
+ flex-shrink: 0;
859
+ padding: 12px 8px;
860
+ background: var(--surface2);
861
+ border: 1px solid var(--border);
862
+ border-radius: 10px;
863
+ }
864
+ .pipe-node-hl {
865
+ border-color: rgba(225,48,108,0.4);
866
+ background: rgba(225,48,108,0.07);
867
+ }
868
+ .pipe-icon-wrap { font-size: 1.4rem; margin-bottom: 6px; }
869
+ .pipe-node-label {
870
+ font-size: 0.75rem;
871
+ font-weight: 700;
872
+ color: var(--text);
873
+ margin-bottom: 4px;
874
+ line-height: 1.3;
875
+ }
876
+ .pipe-node-sub {
877
+ font-size: 0.65rem;
878
+ color: var(--muted);
879
+ line-height: 1.4;
880
+ }
881
+ .pipe-providers {
882
+ display: flex;
883
+ flex-wrap: wrap;
884
+ justify-content: center;
885
+ gap: 3px;
886
+ margin-top: 8px;
887
+ }
888
+ .pipe-prov {
889
+ font-size: 0.6rem;
890
+ background: rgba(225,48,108,0.18);
891
+ color: var(--ig1);
892
+ border-radius: 4px;
893
+ padding: 2px 5px;
894
+ white-space: nowrap;
895
+ }
896
+ .pipe-connector {
897
+ display: flex;
898
+ align-items: flex-start;
899
+ padding: 20px 5px 0;
900
+ color: var(--muted);
901
+ font-size: 1rem;
902
+ flex-shrink: 0;
903
+ }
904
+ .pipeline-legend {
905
+ display: flex;
906
+ flex-wrap: wrap;
907
+ align-items: center;
908
+ gap: 8px;
909
+ margin-top: 16px;
910
+ font-size: 0.72rem;
911
+ color: var(--muted);
912
+ }
913
+ .pipe-out {
914
+ background: var(--surface2);
915
+ border: 1px solid var(--border);
916
+ border-radius: 6px;
917
+ padding: 3px 8px;
918
+ color: var(--text);
919
+ font-size: 0.7rem;
920
+ }
921
+
922
+ /* About section */
923
+ .about-row {
924
+ display: flex;
925
+ gap: 20px;
926
+ align-items: flex-start;
927
+ }
928
+ .about-avatar {
929
+ font-size: 2.4rem;
930
+ width: 64px; height: 64px;
931
+ display: flex; align-items: center; justify-content: center;
932
+ background: var(--surface2);
933
+ border: 1px solid var(--border);
934
+ border-radius: 50%;
935
+ flex-shrink: 0;
936
+ }
937
+ .about-body { flex: 1; min-width: 0; }
938
+ .about-name {
939
+ font-size: 1rem;
940
+ font-weight: 800;
941
+ background: var(--grad);
942
+ -webkit-background-clip: text;
943
+ -webkit-text-fill-color: transparent;
944
+ background-clip: text;
945
+ }
946
+ .about-handle {
947
+ font-size: 0.78rem;
948
+ color: var(--muted);
949
+ margin-bottom: 10px;
950
+ }
951
+ .about-bio {
952
+ font-size: 0.85rem;
953
+ color: var(--muted);
954
+ line-height: 1.65;
955
+ margin-bottom: 14px;
956
+ }
957
+ .about-bio strong { color: var(--text); }
958
+ .about-links { display: flex; gap: 10px; flex-wrap: wrap; }
959
+ .about-link {
960
+ font-size: 0.8rem;
961
+ color: var(--ig1);
962
+ text-decoration: none;
963
+ border: 1px solid rgba(225,48,108,0.3);
964
+ border-radius: 6px;
965
+ padding: 4px 10px;
966
+ transition: background 0.15s;
967
+ }
968
+ .about-link:hover { background: rgba(225,48,108,0.1); }
969
+
970
+ /* FAQ accordion */
971
+ .faq-list { display: flex; flex-direction: column; }
972
+ .faq-item {
973
+ border-bottom: 1px solid var(--border);
974
+ }
975
+ .faq-item:last-child { border-bottom: none; }
976
+ .faq-item summary {
977
+ padding: 14px 0;
978
+ font-size: 0.88rem;
979
+ font-weight: 600;
980
+ cursor: pointer;
981
+ list-style: none;
982
+ display: flex;
983
+ align-items: center;
984
+ gap: 10px;
985
+ color: var(--text);
986
+ user-select: none;
987
+ }
988
+ .faq-item summary::-webkit-details-marker { display: none; }
989
+ .faq-item summary::before {
990
+ content: '+';
991
+ font-size: 1.1rem;
992
+ color: var(--ig1);
993
+ font-weight: 400;
994
+ width: 16px;
995
+ flex-shrink: 0;
996
+ text-align: center;
997
+ }
998
+ .faq-item[open] > summary::before { content: 'βˆ’'; }
999
+ .faq-item p {
1000
+ font-size: 0.84rem;
1001
+ color: var(--muted);
1002
+ line-height: 1.75;
1003
+ padding: 0 0 16px 26px;
1004
+ }
1005
+ .faq-item p strong { color: var(--text); }
1006
+ .faq-item p code {
1007
+ font-family: monospace;
1008
+ font-size: 0.8rem;
1009
+ background: var(--surface2);
1010
+ border-radius: 4px;
1011
+ padding: 1px 5px;
1012
+ }
web/templates/index.html CHANGED
@@ -27,6 +27,7 @@
27
  <div class="top-nav-inner">
28
  <button class="top-tab-btn active" data-top-tab="extract">πŸ“₯ Extract</button>
29
  <button class="top-tab-btn" data-top-tab="explore">πŸ—Ί Explore</button>
 
30
  </div>
31
  </div>
32
 
@@ -374,6 +375,115 @@
374
 
375
  </div><!-- /top-panel-explore -->
376
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
377
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
378
  <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
379
  integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
 
27
  <div class="top-nav-inner">
28
  <button class="top-tab-btn active" data-top-tab="extract">πŸ“₯ Extract</button>
29
  <button class="top-tab-btn" data-top-tab="explore">πŸ—Ί Explore</button>
30
+ <button class="top-tab-btn" data-top-tab="faq">❓ FAQ</button>
31
  </div>
32
  </div>
33
 
 
375
 
376
  </div><!-- /top-panel-explore -->
377
 
378
+ <!-- ══ FAQ / About panel ═══════════════════════════════════════ -->
379
+ <div class="top-panel" id="top-panel-faq">
380
+
381
+ <!-- System diagram -->
382
+ <div class="card" style="max-width:640px;">
383
+ <p class="card-title">How it works</p>
384
+ <div class="pipeline-diagram">
385
+ <div class="pipe-node">
386
+ <div class="pipe-icon-wrap">πŸ“±</div>
387
+ <div class="pipe-node-label">Instagram export</div>
388
+ <div class="pipe-node-sub">saved_posts.json or .zip</div>
389
+ </div>
390
+ <div class="pipe-connector">β†’</div>
391
+ <div class="pipe-node">
392
+ <div class="pipe-icon-wrap">πŸ”</div>
393
+ <div class="pipe-node-label">Pre-filter</div>
394
+ <div class="pipe-node-sub">keyword &amp; regex gate β€” skips non-places</div>
395
+ </div>
396
+ <div class="pipe-connector">β†’</div>
397
+ <div class="pipe-node pipe-node-hl">
398
+ <div class="pipe-icon-wrap">πŸ€–</div>
399
+ <div class="pipe-node-label">AI extraction</div>
400
+ <div class="pipe-node-sub">reads captions &amp; hashtags</div>
401
+ <div class="pipe-providers">
402
+ <span class="pipe-prov">Claude API</span>
403
+ <span class="pipe-prov">Ollama</span>
404
+ <span class="pipe-prov">Free chatbot</span>
405
+ </div>
406
+ </div>
407
+ <div class="pipe-connector">β†’</div>
408
+ <div class="pipe-node">
409
+ <div class="pipe-icon-wrap">πŸ“</div>
410
+ <div class="pipe-node-label">Geocoding</div>
411
+ <div class="pipe-node-sub">Nominatim OSM β€” 3-gate validation</div>
412
+ </div>
413
+ <div class="pipe-connector">β†’</div>
414
+ <div class="pipe-node">
415
+ <div class="pipe-icon-wrap">πŸ—ΊοΈ</div>
416
+ <div class="pipe-node-label">Export</div>
417
+ <div class="pipe-node-sub">KML Β· GeoJSON Β· CSV</div>
418
+ </div>
419
+ </div>
420
+ <div class="pipeline-legend">
421
+ <span>Outputs:</span>
422
+ <span class="pipe-out">πŸ—Ί Google My Maps (KML)</span>
423
+ <span class="pipe-out">🌐 GeoJSON</span>
424
+ <span class="pipe-out">πŸ“„ CSV</span>
425
+ </div>
426
+ </div>
427
+
428
+ <!-- About -->
429
+ <div class="card" style="max-width:640px;">
430
+ <p class="card-title">About</p>
431
+ <div class="about-row">
432
+ <div class="about-avatar">🍜</div>
433
+ <div class="about-body">
434
+ <div class="about-name">The Belagurian</div>
435
+ <div class="about-handle">by Harshith Belagur</div>
436
+ <p class="about-bio">
437
+ Food &amp; travel journal documenting meals and places worth remembering. IG Food Mapper was built to solve a real problem: saving hundreds of restaurant reels and never finding them again. Everything runs local-first β€” your data stays yours.
438
+ </p>
439
+ <div class="about-links">
440
+ <a href="https://instagram.com/the_belagurian" target="_blank" rel="noopener" class="about-link">Instagram β†—</a>
441
+ <a href="https://github.com/harshithbelagur/instagram-restaurant-mapper" target="_blank" rel="noopener" class="about-link">GitHub β†—</a>
442
+ </div>
443
+ </div>
444
+ </div>
445
+ </div>
446
+
447
+ <!-- FAQ -->
448
+ <div class="card" style="max-width:640px;">
449
+ <p class="card-title">FAQ</p>
450
+ <div class="faq-list">
451
+
452
+ <details class="faq-item">
453
+ <summary>Is my data private?</summary>
454
+ <p>Yes. Your Instagram export never leaves your device. The only external calls are to the AI model you choose (Anthropic API or a local Ollama instance) and OpenStreetMap's geocoder. Nothing is stored on a server between sessions β€” no analytics, no tracking.</p>
455
+ </details>
456
+
457
+ <details class="faq-item">
458
+ <summary>Which AI models are supported?</summary>
459
+ <p>Three paths: <strong>Claude API</strong> (bring your own Anthropic key β€” Haiku is fast and cheap), <strong>Ollama</strong> (fully local, $0, needs the Ollama app running), or the <strong>free chatbot mode</strong> where you paste posts into ChatGPT or Claude.ai manually and bring the results back here. No API key required for the latter two.</p>
460
+ </details>
461
+
462
+ <details class="faq-item">
463
+ <summary>Why are some places missing map pins?</summary>
464
+ <p>Geocoding only succeeds when all three validation gates pass: country match, city match, and proximity check. If a place name is ambiguous or the caption doesn't include enough location context, the coordinates are left blank rather than guessing. You can fix missing pins manually via the Browse panel β€” click a card to edit it.</p>
465
+ </details>
466
+
467
+ <details class="faq-item">
468
+ <summary>What's the difference between KML, GeoJSON, and CSV?</summary>
469
+ <p><strong>KML</strong> is the primary export β€” import it into Google My Maps or Organic Maps for a navigable map organised by country β†’ city. <strong>GeoJSON</strong> is for developers and tools like Mapbox or QGIS. <strong>CSV</strong> includes every row (including ungeocoded ones) and is useful for spreadsheet review or re-import after manual edits.</p>
470
+ </details>
471
+
472
+ <details class="faq-item">
473
+ <summary>How do I get my Instagram export?</summary>
474
+ <p>Instagram β†’ <strong>☰ Settings and privacy β†’ Your activity β†’ Download your information β†’ Download or transfer information</strong>. Select your account β†’ <em>Some of your information</em> β†’ tick <strong>Saved posts</strong> β†’ Next β†’ Download to device β†’ Format: <strong>JSON</strong> β†’ Date range: <strong>All time</strong> β†’ Create files. Instagram emails you when it's ready (usually under an hour). Drop the zip here directly β€” no unzipping needed.</p>
475
+ </details>
476
+
477
+ <details class="faq-item">
478
+ <summary>Can I re-import a previous export?</summary>
479
+ <p>Yes. Switch to the <strong>Explore</strong> tab and drop a <code>places_map.kml</code> or <code>places_full.csv</code> from a previous run to jump straight to Roulette &amp; Browse without re-running the pipeline.</p>
480
+ </details>
481
+
482
+ </div>
483
+ </div>
484
+
485
+ </div><!-- /top-panel-faq -->
486
+
487
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
488
  <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
489
  integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>