claude-code-templates 1.24.16 → 1.25.0

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.
@@ -183,6 +183,605 @@
183
183
  color: var(--text-secondary);
184
184
  }
185
185
 
186
+ /* Advanced Search Panel - Telegram Style */
187
+ .search-panel-overlay {
188
+ position: fixed;
189
+ top: 0;
190
+ left: 0;
191
+ right: 0;
192
+ bottom: 0;
193
+ background: rgba(0, 0, 0, 0.5);
194
+ z-index: 1000;
195
+ display: none;
196
+ animation: fadeIn 0.2s ease;
197
+ }
198
+
199
+ .search-panel-overlay.active {
200
+ display: flex;
201
+ align-items: flex-start;
202
+ justify-content: center;
203
+ padding-top: 10vh;
204
+ }
205
+
206
+ @keyframes fadeIn {
207
+ from { opacity: 0; }
208
+ to { opacity: 1; }
209
+ }
210
+
211
+ .search-panel {
212
+ background: var(--bg-secondary);
213
+ border-radius: 12px;
214
+ width: 90%;
215
+ max-width: 600px;
216
+ max-height: 80vh;
217
+ overflow-y: auto;
218
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
219
+ animation: slideDown 0.3s ease;
220
+ }
221
+
222
+ @keyframes slideDown {
223
+ from {
224
+ opacity: 0;
225
+ transform: translateY(-20px);
226
+ }
227
+ to {
228
+ opacity: 1;
229
+ transform: translateY(0);
230
+ }
231
+ }
232
+
233
+ .search-panel-header {
234
+ padding: 20px;
235
+ border-bottom: 1px solid var(--border-primary);
236
+ display: flex;
237
+ align-items: center;
238
+ justify-content: space-between;
239
+ position: sticky;
240
+ top: 0;
241
+ background: var(--bg-secondary);
242
+ z-index: 10;
243
+ }
244
+
245
+ .search-panel-title {
246
+ font-size: 1.25rem;
247
+ font-weight: 600;
248
+ color: var(--text-primary);
249
+ display: flex;
250
+ align-items: center;
251
+ gap: 10px;
252
+ }
253
+
254
+ .search-panel-close {
255
+ background: none;
256
+ border: none;
257
+ color: var(--text-secondary);
258
+ font-size: 1.5rem;
259
+ cursor: pointer;
260
+ padding: 4px 8px;
261
+ border-radius: 4px;
262
+ transition: all 0.2s ease;
263
+ }
264
+
265
+ .search-panel-close:hover {
266
+ background: var(--bg-tertiary);
267
+ color: var(--text-primary);
268
+ }
269
+
270
+ .search-panel-body {
271
+ padding: 20px;
272
+ }
273
+
274
+ .search-filter-group {
275
+ margin-bottom: 24px;
276
+ }
277
+
278
+ .search-filter-label {
279
+ display: block;
280
+ color: var(--text-secondary);
281
+ font-size: 0.875rem;
282
+ font-weight: 500;
283
+ margin-bottom: 8px;
284
+ text-transform: uppercase;
285
+ letter-spacing: 0.5px;
286
+ }
287
+
288
+ .search-filter-input {
289
+ width: 100%;
290
+ padding: 12px 16px;
291
+ background: var(--bg-tertiary);
292
+ border: 1px solid var(--border-primary);
293
+ border-radius: 8px;
294
+ color: var(--text-primary);
295
+ font-size: 1rem;
296
+ outline: none;
297
+ transition: all 0.2s ease;
298
+ }
299
+
300
+ .search-filter-input:focus {
301
+ border-color: var(--terminal-orange);
302
+ background: var(--bg-primary);
303
+ }
304
+
305
+ .search-filter-input::placeholder {
306
+ color: var(--text-secondary);
307
+ }
308
+
309
+ .search-filter-date-range {
310
+ display: grid;
311
+ grid-template-columns: 1fr 1fr;
312
+ gap: 12px;
313
+ }
314
+
315
+ .search-filter-date-wrapper {
316
+ display: flex;
317
+ flex-direction: column;
318
+ }
319
+
320
+ .search-filter-date-label {
321
+ font-size: 0.75rem;
322
+ color: var(--text-secondary);
323
+ margin-bottom: 4px;
324
+ }
325
+
326
+ .search-panel-actions {
327
+ padding: 16px 20px;
328
+ border-top: 1px solid var(--border-primary);
329
+ display: flex;
330
+ gap: 12px;
331
+ position: sticky;
332
+ bottom: 0;
333
+ background: var(--bg-secondary);
334
+ }
335
+
336
+ .search-btn {
337
+ flex: 1;
338
+ padding: 14px 24px;
339
+ border: none;
340
+ border-radius: 8px;
341
+ font-size: 1rem;
342
+ font-weight: 600;
343
+ cursor: pointer;
344
+ transition: all 0.2s ease;
345
+ text-transform: uppercase;
346
+ letter-spacing: 0.5px;
347
+ }
348
+
349
+ .search-btn-primary {
350
+ background: var(--terminal-orange);
351
+ color: white;
352
+ }
353
+
354
+ .search-btn-primary:hover {
355
+ background: var(--terminal-orange-hover);
356
+ transform: translateY(-1px);
357
+ box-shadow: 0 4px 12px rgba(255, 107, 53, 0.3);
358
+ }
359
+
360
+ .search-btn-secondary {
361
+ background: var(--bg-tertiary);
362
+ color: var(--text-primary);
363
+ }
364
+
365
+ .search-btn-secondary:hover {
366
+ background: var(--border-secondary);
367
+ }
368
+
369
+ .search-btn:active {
370
+ transform: translateY(0);
371
+ }
372
+
373
+ .search-filter-icon {
374
+ display: inline-block;
375
+ margin-right: 6px;
376
+ }
377
+
378
+ .advanced-search-toggle {
379
+ background: var(--bg-tertiary);
380
+ border: 1px solid var(--border-primary);
381
+ color: var(--terminal-orange);
382
+ padding: 8px 12px;
383
+ border-radius: 20px;
384
+ cursor: pointer;
385
+ font-size: 0.875rem;
386
+ margin-left: 8px;
387
+ transition: all 0.2s ease;
388
+ display: inline-flex;
389
+ align-items: center;
390
+ gap: 6px;
391
+ }
392
+
393
+ .advanced-search-toggle:hover {
394
+ background: var(--terminal-orange);
395
+ color: white;
396
+ border-color: var(--terminal-orange);
397
+ }
398
+
399
+ .search-results-info {
400
+ background: var(--bg-tertiary);
401
+ border-radius: 8px;
402
+ padding: 12px 16px;
403
+ margin-bottom: 16px;
404
+ color: var(--text-secondary);
405
+ font-size: 0.875rem;
406
+ display: none;
407
+ }
408
+
409
+ .search-results-info.active {
410
+ display: block;
411
+ }
412
+
413
+ .search-results-count {
414
+ color: var(--terminal-orange);
415
+ font-weight: 600;
416
+ }
417
+
418
+ .clear-filters-btn {
419
+ background: none;
420
+ border: none;
421
+ color: var(--terminal-orange);
422
+ cursor: pointer;
423
+ font-size: 0.875rem;
424
+ text-decoration: underline;
425
+ padding: 0;
426
+ margin-left: 8px;
427
+ }
428
+
429
+ .clear-filters-btn:hover {
430
+ color: var(--terminal-orange-hover);
431
+ }
432
+
433
+ .filter-tag {
434
+ display: inline-flex;
435
+ align-items: center;
436
+ gap: 6px;
437
+ background: var(--bg-primary);
438
+ border: 1px solid var(--border-secondary);
439
+ border-radius: 12px;
440
+ padding: 4px 10px;
441
+ font-size: 0.75rem;
442
+ color: var(--text-primary);
443
+ }
444
+
445
+ .filter-tag-icon {
446
+ font-size: 0.7rem;
447
+ }
448
+
449
+ .filter-tag-label {
450
+ color: var(--text-secondary);
451
+ margin-right: 2px;
452
+ }
453
+
454
+ .filter-tag-value {
455
+ color: var(--terminal-orange);
456
+ font-weight: 500;
457
+ }
458
+
459
+ /* Folder Browser */
460
+ .folder-browser-wrapper {
461
+ position: relative;
462
+ }
463
+
464
+ .folder-browser-toggle {
465
+ position: absolute;
466
+ right: 12px;
467
+ top: 50%;
468
+ transform: translateY(-50%);
469
+ background: var(--terminal-orange);
470
+ border: none;
471
+ color: white;
472
+ padding: 6px 12px;
473
+ border-radius: 6px;
474
+ cursor: pointer;
475
+ font-size: 0.875rem;
476
+ transition: all 0.2s ease;
477
+ z-index: 1;
478
+ }
479
+
480
+ .folder-browser-toggle:hover {
481
+ background: var(--terminal-orange-hover);
482
+ transform: translateY(-50%) scale(1.05);
483
+ }
484
+
485
+ .folder-browser-dropdown {
486
+ position: absolute;
487
+ top: calc(100% + 8px);
488
+ left: 0;
489
+ right: 0;
490
+ background: var(--bg-primary);
491
+ border: 1px solid var(--border-secondary);
492
+ border-radius: 8px;
493
+ max-height: 300px;
494
+ overflow-y: auto;
495
+ z-index: 100;
496
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
497
+ display: none;
498
+ }
499
+
500
+ .folder-browser-dropdown.active {
501
+ display: block;
502
+ animation: slideDown 0.2s ease;
503
+ }
504
+
505
+ .folder-browser-header {
506
+ padding: 12px 16px;
507
+ background: var(--bg-secondary);
508
+ border-bottom: 1px solid var(--border-primary);
509
+ position: sticky;
510
+ top: 0;
511
+ z-index: 10;
512
+ }
513
+
514
+ .folder-browser-search {
515
+ width: 100%;
516
+ padding: 8px 12px;
517
+ background: var(--bg-tertiary);
518
+ border: 1px solid var(--border-primary);
519
+ border-radius: 6px;
520
+ color: var(--text-primary);
521
+ font-size: 0.875rem;
522
+ outline: none;
523
+ }
524
+
525
+ .folder-browser-search:focus {
526
+ border-color: var(--terminal-orange);
527
+ }
528
+
529
+ .folder-browser-list {
530
+ padding: 4px 0;
531
+ }
532
+
533
+ .folder-item {
534
+ padding: 12px 16px;
535
+ cursor: pointer;
536
+ transition: background 0.2s ease;
537
+ display: flex;
538
+ align-items: center;
539
+ gap: 10px;
540
+ border-bottom: 1px solid var(--border-primary);
541
+ }
542
+
543
+ .folder-item:last-child {
544
+ border-bottom: none;
545
+ }
546
+
547
+ .folder-item:hover {
548
+ background: var(--bg-tertiary);
549
+ }
550
+
551
+ .folder-item.selected {
552
+ background: var(--terminal-orange);
553
+ color: white;
554
+ }
555
+
556
+ .folder-icon {
557
+ font-size: 1.2rem;
558
+ flex-shrink: 0;
559
+ }
560
+
561
+ .folder-path {
562
+ flex: 1;
563
+ font-size: 0.875rem;
564
+ overflow: hidden;
565
+ text-overflow: ellipsis;
566
+ white-space: nowrap;
567
+ }
568
+
569
+ .folder-item.selected .folder-path {
570
+ color: white;
571
+ }
572
+
573
+ .folder-count {
574
+ font-size: 0.75rem;
575
+ color: var(--text-secondary);
576
+ background: var(--bg-tertiary);
577
+ padding: 2px 8px;
578
+ border-radius: 10px;
579
+ flex-shrink: 0;
580
+ }
581
+
582
+ .folder-item.selected .folder-count {
583
+ background: rgba(255, 255, 255, 0.2);
584
+ color: white;
585
+ }
586
+
587
+ .folder-browser-empty {
588
+ padding: 24px 16px;
589
+ text-align: center;
590
+ color: var(--text-secondary);
591
+ font-size: 0.875rem;
592
+ }
593
+
594
+ /* In-Conversation Search */
595
+ .chat-search-bar {
596
+ display: none;
597
+ background: var(--bg-secondary);
598
+ border-bottom: 1px solid var(--border-primary);
599
+ padding: 12px 16px;
600
+ align-items: center;
601
+ gap: 12px;
602
+ animation: slideDown 0.2s ease;
603
+ }
604
+
605
+ .chat-search-bar.active {
606
+ display: flex;
607
+ }
608
+
609
+ .chat-search-input-wrapper {
610
+ flex: 1;
611
+ position: relative;
612
+ }
613
+
614
+ .chat-search-input {
615
+ width: 100%;
616
+ padding: 8px 12px;
617
+ background: var(--bg-tertiary);
618
+ border: 1px solid var(--border-primary);
619
+ border-radius: 6px;
620
+ color: var(--text-primary);
621
+ font-size: 0.875rem;
622
+ outline: none;
623
+ }
624
+
625
+ .chat-search-input:focus {
626
+ border-color: var(--terminal-orange);
627
+ }
628
+
629
+ .chat-search-controls {
630
+ display: flex;
631
+ align-items: center;
632
+ gap: 8px;
633
+ }
634
+
635
+ .chat-search-counter {
636
+ font-size: 0.875rem;
637
+ color: var(--text-secondary);
638
+ white-space: nowrap;
639
+ min-width: 60px;
640
+ text-align: center;
641
+ }
642
+
643
+ .chat-search-counter.has-results {
644
+ color: var(--terminal-orange);
645
+ font-weight: 500;
646
+ }
647
+
648
+ .chat-search-nav-btn {
649
+ background: var(--bg-tertiary);
650
+ border: 1px solid var(--border-primary);
651
+ color: var(--text-primary);
652
+ padding: 6px 10px;
653
+ border-radius: 4px;
654
+ cursor: pointer;
655
+ font-size: 1rem;
656
+ transition: all 0.2s ease;
657
+ display: flex;
658
+ align-items: center;
659
+ justify-content: center;
660
+ }
661
+
662
+ .chat-search-nav-btn:hover:not(:disabled) {
663
+ background: var(--terminal-orange);
664
+ border-color: var(--terminal-orange);
665
+ color: white;
666
+ }
667
+
668
+ .chat-search-nav-btn:disabled {
669
+ opacity: 0.3;
670
+ cursor: not-allowed;
671
+ }
672
+
673
+ .chat-search-close-btn {
674
+ background: none;
675
+ border: none;
676
+ color: var(--text-secondary);
677
+ padding: 4px 8px;
678
+ border-radius: 4px;
679
+ cursor: pointer;
680
+ font-size: 1.2rem;
681
+ transition: all 0.2s ease;
682
+ }
683
+
684
+ .chat-search-close-btn:hover {
685
+ background: var(--bg-tertiary);
686
+ color: var(--text-primary);
687
+ }
688
+
689
+ /* Message highlight */
690
+ .message-highlight {
691
+ background: var(--terminal-orange);
692
+ color: white;
693
+ padding: 2px 4px;
694
+ border-radius: 3px;
695
+ font-weight: 500;
696
+ }
697
+
698
+ .message-current-highlight {
699
+ background: #ffaa00;
700
+ color: white;
701
+ padding: 2px 4px;
702
+ border-radius: 3px;
703
+ font-weight: 600;
704
+ box-shadow: 0 0 8px rgba(255, 170, 0, 0.6);
705
+ }
706
+
707
+ /* Action buttons group - unified design with orange accent */
708
+ .action-buttons-group {
709
+ display: inline-flex;
710
+ border-radius: 8px;
711
+ overflow: hidden;
712
+ border: 2px solid rgba(255, 170, 0, 0.4);
713
+ background: rgba(255, 170, 0, 0.08);
714
+ box-shadow: 0 2px 8px rgba(255, 170, 0, 0.15);
715
+ }
716
+
717
+ .action-btn {
718
+ background: transparent;
719
+ border: none;
720
+ border-right: 1px solid rgba(255, 170, 0, 0.2);
721
+ color: var(--text-primary);
722
+ padding: 10px 18px;
723
+ cursor: pointer;
724
+ font-size: 0.875rem;
725
+ font-weight: 600;
726
+ transition: all 0.2s ease;
727
+ display: flex;
728
+ align-items: center;
729
+ gap: 8px;
730
+ white-space: nowrap;
731
+ }
732
+
733
+ .action-btn:last-child {
734
+ border-right: none;
735
+ }
736
+
737
+ .action-btn svg {
738
+ width: 15px;
739
+ height: 15px;
740
+ transition: all 0.2s ease;
741
+ stroke: var(--text-primary);
742
+ }
743
+
744
+ .action-btn:hover {
745
+ background: rgba(255, 170, 0, 0.12);
746
+ transform: translateY(-1px);
747
+ }
748
+
749
+ .action-btn.resume-btn:hover {
750
+ background: rgba(63, 185, 80, 0.15);
751
+ color: rgba(63, 185, 80, 1);
752
+ }
753
+
754
+ .action-btn.resume-btn:hover svg {
755
+ stroke: rgba(63, 185, 80, 1);
756
+ }
757
+
758
+ .action-btn.share-btn:hover {
759
+ background: rgba(59, 130, 246, 0.15);
760
+ color: rgba(59, 130, 246, 1);
761
+ }
762
+
763
+ .action-btn.share-btn:hover svg {
764
+ stroke: rgba(59, 130, 246, 1);
765
+ }
766
+
767
+ .action-btn.search-btn:hover {
768
+ background: rgba(251, 146, 60, 0.15);
769
+ color: rgba(251, 146, 60, 1);
770
+ }
771
+
772
+ .action-btn.search-btn:hover svg {
773
+ stroke: rgba(251, 146, 60, 1);
774
+ }
775
+
776
+ .action-btn.search-btn.active {
777
+ background: rgba(251, 146, 60, 0.2);
778
+ color: rgba(251, 146, 60, 1);
779
+ }
780
+
781
+ .action-btn.search-btn.active svg {
782
+ stroke: rgba(251, 146, 60, 1);
783
+ }
784
+
186
785
  /* Conversations list */
187
786
  .conversations-list {
188
787
  flex: 1;
@@ -268,6 +867,88 @@
268
867
  margin-top: 4px;
269
868
  }
270
869
 
870
+ /* Project grouping styles */
871
+ .project-group {
872
+ border-bottom: 1px solid var(--border-secondary);
873
+ }
874
+
875
+ .project-header {
876
+ display: flex;
877
+ align-items: center;
878
+ padding: 16px 20px;
879
+ background: var(--bg-secondary);
880
+ border-bottom: 1px solid var(--border-primary);
881
+ cursor: pointer;
882
+ transition: background-color 0.2s ease;
883
+ position: sticky;
884
+ top: 0;
885
+ z-index: 10;
886
+ }
887
+
888
+ .project-header:hover {
889
+ background: var(--bg-tertiary);
890
+ }
891
+
892
+ .project-avatar {
893
+ width: 44px;
894
+ height: 44px;
895
+ border-radius: 8px;
896
+ background: linear-gradient(135deg, var(--terminal-orange) 0%, #e67e22 100%);
897
+ display: flex;
898
+ align-items: center;
899
+ justify-content: center;
900
+ font-size: 1.2rem;
901
+ margin-right: 12px;
902
+ flex-shrink: 0;
903
+ color: var(--bg-primary);
904
+ font-weight: bold;
905
+ }
906
+
907
+ .project-info {
908
+ flex: 1;
909
+ min-width: 0;
910
+ }
911
+
912
+ .project-name {
913
+ font-weight: 600;
914
+ color: var(--text-primary);
915
+ font-size: 1rem;
916
+ margin-bottom: 2px;
917
+ }
918
+
919
+ .project-count {
920
+ color: var(--text-secondary);
921
+ font-size: 0.85rem;
922
+ }
923
+
924
+ .project-toggle {
925
+ margin-left: 8px;
926
+ color: var(--text-secondary);
927
+ transition: transform 0.2s ease;
928
+ }
929
+
930
+ .toggle-icon {
931
+ transition: transform 0.2s ease;
932
+ }
933
+
934
+ .toggle-icon.expanded {
935
+ transform: rotate(180deg);
936
+ }
937
+
938
+ /* Smaller avatar for conversations within groups */
939
+ .conversation-avatar-small {
940
+ width: 36px;
941
+ height: 36px;
942
+ font-size: 0.85rem;
943
+ background: var(--bg-tertiary);
944
+ color: var(--text-primary);
945
+ }
946
+
947
+ /* Indent conversations within project groups */
948
+ .project-group .conversation-item {
949
+ padding-left: 36px;
950
+ }
951
+
271
952
  .conversation-state {
272
953
  font-size: 0.7rem;
273
954
  padding: 3px 8px;
@@ -805,13 +1486,16 @@
805
1486
  align-items: center;
806
1487
  justify-content: center;
807
1488
  flex: 0 0 auto;
1489
+ gap: 8px;
808
1490
  }
809
1491
 
1492
+
810
1493
  .header-right {
811
1494
  display: flex;
812
1495
  align-items: center;
813
1496
  justify-content: flex-end;
814
1497
  flex: 1;
1498
+ gap: 12px;
815
1499
  }
816
1500
 
817
1501
  .chat-view-back {
@@ -1195,12 +1879,30 @@
1195
1879
 
1196
1880
  <!-- Search -->
1197
1881
  <div class="chat-search">
1198
- <input
1199
- type="text"
1200
- class="search-input"
1201
- placeholder="Search conversations..."
1202
- id="searchInput"
1203
- />
1882
+ <div style="display: flex; align-items: center;">
1883
+ <input
1884
+ type="text"
1885
+ class="search-input"
1886
+ placeholder="Search conversations..."
1887
+ id="searchInput"
1888
+ style="flex: 1;"
1889
+ />
1890
+ <button class="advanced-search-toggle" id="advancedSearchToggle">
1891
+ <span>🔍</span>
1892
+ <span>Advanced</span>
1893
+ </button>
1894
+ </div>
1895
+
1896
+ <!-- Search Results Info -->
1897
+ <div class="search-results-info" id="searchResultsInfo">
1898
+ <div style="display: flex; flex-direction: column; gap: 8px;">
1899
+ <div style="display: flex; justify-content: space-between; align-items: center;">
1900
+ <span>Found <span class="search-results-count" id="searchResultsCount">0</span> conversations</span>
1901
+ <button class="clear-filters-btn" id="clearFiltersBtn">Clear filters</button>
1902
+ </div>
1903
+ <div id="appliedFilters" style="display: flex; flex-wrap: wrap; gap: 6px; margin-top: 4px;"></div>
1904
+ </div>
1905
+ </div>
1204
1906
  </div>
1205
1907
 
1206
1908
  <!-- Conversations List -->
@@ -1209,6 +1911,127 @@
1209
1911
  </div>
1210
1912
  </div>
1211
1913
 
1914
+ <!-- Advanced Search Panel Overlay -->
1915
+ <div class="search-panel-overlay" id="searchPanelOverlay">
1916
+ <div class="search-panel">
1917
+ <!-- Panel Header -->
1918
+ <div class="search-panel-header">
1919
+ <h3 class="search-panel-title">
1920
+ <span class="search-filter-icon">🔍</span>
1921
+ Advanced Search
1922
+ </h3>
1923
+ <button class="search-panel-close" id="searchPanelClose">✕</button>
1924
+ </div>
1925
+
1926
+ <!-- Panel Body -->
1927
+ <div class="search-panel-body">
1928
+ <!-- Quick Search -->
1929
+ <div class="search-filter-group">
1930
+ <label class="search-filter-label">
1931
+ <span class="search-filter-icon">💬</span>
1932
+ Quick Search
1933
+ </label>
1934
+ <input
1935
+ type="text"
1936
+ class="search-filter-input"
1937
+ placeholder="Search by conversation ID or filename..."
1938
+ id="filterQuery"
1939
+ />
1940
+ </div>
1941
+
1942
+ <!-- Working Directory Filter -->
1943
+ <div class="search-filter-group">
1944
+ <label class="search-filter-label">
1945
+ <span class="search-filter-icon">📁</span>
1946
+ Working Directory
1947
+ </label>
1948
+ <div class="folder-browser-wrapper">
1949
+ <input
1950
+ type="text"
1951
+ class="search-filter-input"
1952
+ placeholder="e.g., /Users/name/Projects/MyProject"
1953
+ id="filterWorkingDirectory"
1954
+ style="padding-right: 100px;"
1955
+ />
1956
+ <button class="folder-browser-toggle" id="folderBrowserToggle">
1957
+ 📂 Browse
1958
+ </button>
1959
+
1960
+ <!-- Folder Browser Dropdown -->
1961
+ <div class="folder-browser-dropdown" id="folderBrowserDropdown">
1962
+ <div class="folder-browser-header">
1963
+ <input
1964
+ type="text"
1965
+ class="folder-browser-search"
1966
+ placeholder="Filter directories..."
1967
+ id="folderBrowserSearch"
1968
+ />
1969
+ </div>
1970
+ <div class="folder-browser-list" id="folderBrowserList">
1971
+ <div class="folder-browser-empty">
1972
+ Loading directories...
1973
+ </div>
1974
+ </div>
1975
+ </div>
1976
+ </div>
1977
+ </div>
1978
+
1979
+ <!-- Date Range Filter -->
1980
+ <div class="search-filter-group">
1981
+ <label class="search-filter-label">
1982
+ <span class="search-filter-icon">📅</span>
1983
+ Date Range
1984
+ </label>
1985
+ <div class="search-filter-date-range">
1986
+ <div class="search-filter-date-wrapper">
1987
+ <span class="search-filter-date-label">From</span>
1988
+ <input
1989
+ type="date"
1990
+ class="search-filter-input"
1991
+ id="filterDateFrom"
1992
+ />
1993
+ </div>
1994
+ <div class="search-filter-date-wrapper">
1995
+ <span class="search-filter-date-label">To</span>
1996
+ <input
1997
+ type="date"
1998
+ class="search-filter-input"
1999
+ id="filterDateTo"
2000
+ />
2001
+ </div>
2002
+ </div>
2003
+ </div>
2004
+
2005
+ <!-- Content Search -->
2006
+ <div class="search-filter-group">
2007
+ <label class="search-filter-label">
2008
+ <span class="search-filter-icon">🔎</span>
2009
+ Search in Messages
2010
+ </label>
2011
+ <input
2012
+ type="text"
2013
+ class="search-filter-input"
2014
+ placeholder="Search within conversation content..."
2015
+ id="filterContentSearch"
2016
+ />
2017
+ <small style="color: var(--text-secondary); font-size: 0.75rem; margin-top: 4px; display: block;">
2018
+ ⚠️ This may take longer for large conversations
2019
+ </small>
2020
+ </div>
2021
+ </div>
2022
+
2023
+ <!-- Panel Actions -->
2024
+ <div class="search-panel-actions">
2025
+ <button class="search-btn search-btn-secondary" id="searchPanelReset">
2026
+ Reset
2027
+ </button>
2028
+ <button class="search-btn search-btn-primary" id="searchPanelApply">
2029
+ Search
2030
+ </button>
2031
+ </div>
2032
+ </div>
2033
+ </div>
2034
+
1212
2035
  <!-- Chat View -->
1213
2036
  <div class="chat-view" id="chatView">
1214
2037
  <div class="chat-view-header">
@@ -1217,14 +2040,34 @@
1217
2040
 
1218
2041
  </button>
1219
2042
  <div class="chat-view-info">
1220
- <h2 class="chat-view-title" id="chatViewTitle">Select a conversation</h2>
2043
+ <h2 class="chat-view-title" id="chatViewTitle">Select a session</h2>
1221
2044
  <p class="chat-view-subtitle" id="chatViewSubtitle"></p>
1222
2045
  </div>
1223
2046
  </div>
1224
2047
  <div class="header-center">
1225
- <button class="header-btn resume-btn" id="resumeConversation" style="display: none;" onclick="resumeConversationWithClaude()">
1226
- ▶️ Resume
1227
- </button>
2048
+ <div class="action-buttons-group" style="display: none;" id="actionButtonsGroup">
2049
+ <button class="action-btn resume-btn" id="resumeConversation" onclick="resumeConversationWithClaude()">
2050
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2051
+ <polygon points="5 3 19 12 5 21 5 3"></polygon>
2052
+ </svg>
2053
+ <span>Resume</span>
2054
+ </button>
2055
+ <button class="action-btn share-btn" id="shareConversation" onclick="shareConversation()">
2056
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2057
+ <path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"></path>
2058
+ <polyline points="16 6 12 2 8 6"></polyline>
2059
+ <line x1="12" y1="2" x2="12" y2="15"></line>
2060
+ </svg>
2061
+ <span>Share</span>
2062
+ </button>
2063
+ <button class="action-btn search-btn" id="chatSearchToggle">
2064
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2065
+ <circle cx="11" cy="11" r="8"></circle>
2066
+ <path d="m21 21-4.35-4.35"></path>
2067
+ </svg>
2068
+ <span>Search</span>
2069
+ </button>
2070
+ </div>
1228
2071
  </div>
1229
2072
  <div class="header-right">
1230
2073
  <div class="tools-toggle" id="toolsToggle">
@@ -1236,6 +2079,31 @@
1236
2079
  </div>
1237
2080
  </div>
1238
2081
  </div>
2082
+
2083
+ <!-- In-Conversation Search Bar -->
2084
+ <div class="chat-search-bar" id="chatSearchBar">
2085
+ <div class="chat-search-input-wrapper">
2086
+ <input
2087
+ type="text"
2088
+ class="chat-search-input"
2089
+ placeholder="Search in this conversation..."
2090
+ id="chatSearchInput"
2091
+ />
2092
+ </div>
2093
+ <div class="chat-search-controls">
2094
+ <span class="chat-search-counter" id="chatSearchCounter">0/0</span>
2095
+ <button class="chat-search-nav-btn" id="chatSearchPrev" title="Previous match (↑)">
2096
+
2097
+ </button>
2098
+ <button class="chat-search-nav-btn" id="chatSearchNext" title="Next match (↓)">
2099
+
2100
+ </button>
2101
+ <button class="chat-search-close-btn" id="chatSearchClose">
2102
+
2103
+ </button>
2104
+ </div>
2105
+ </div>
2106
+
1239
2107
  <div class="chat-messages" id="chatMessages">
1240
2108
  <div class="no-conversations">
1241
2109
  <div class="no-conversations-icon">💬</div>
@@ -1275,6 +2143,106 @@
1275
2143
  </div>
1276
2144
  </div>
1277
2145
 
2146
+ <!-- Confirm Share Modal (Security Warning) -->
2147
+ <div class="modal-overlay" id="confirmShareModal">
2148
+ <div class="modal">
2149
+ <div class="modal-header" style="background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);">
2150
+ <span class="modal-icon">📤</span>
2151
+ <h3 class="modal-title">Share Your Session</h3>
2152
+ </div>
2153
+ <div style="padding: 20px;">
2154
+ <p class="modal-description" style="margin-bottom: 16px; line-height: 1.5;">
2155
+ Your conversation will be uploaded to <strong>x0.at</strong>, a temporary file hosting service.
2156
+ The generated link can be shared with others to clone your session.
2157
+ </p>
2158
+
2159
+ <div style="background: rgba(59, 130, 246, 0.08); border-left: 3px solid #3b82f6; padding: 12px; border-radius: 6px; margin-bottom: 16px;">
2160
+ <h4 style="color: #3b82f6; margin: 0 0 8px 0; font-size: 14px;">📋 What you're sharing</h4>
2161
+ <p style="margin: 0; font-size: 13px; color: var(--text-secondary); line-height: 1.4;">
2162
+ Last <strong>100 messages</strong> from this conversation (or all messages if less than 100)
2163
+ </p>
2164
+ </div>
2165
+
2166
+ <div style="background: rgba(234, 179, 8, 0.08); border-left: 3px solid #eab308; padding: 12px; border-radius: 6px; margin-bottom: 16px;">
2167
+ <h4 style="color: #eab308; margin: 0 0 8px 0; font-size: 14px;">ℹ️ About x0.at</h4>
2168
+ <ul style="margin: 0; padding-left: 20px; font-size: 13px; color: var(--text-secondary); line-height: 1.5;">
2169
+ <li style="margin-bottom: 4px;">Simple temporary file hosting (open source)</li>
2170
+ <li style="margin-bottom: 4px;">Files available for 3-100 days depending on size</li>
2171
+ <li style="margin-bottom: 4px;">Anyone with the link can download your session</li>
2172
+ <li style="margin-bottom: 4px;">No encryption - avoid sharing sensitive data</li>
2173
+ </ul>
2174
+ </div>
2175
+
2176
+ <div style="background: rgba(100, 116, 139, 0.08); border-left: 3px solid #64748b; padding: 12px; border-radius: 6px;">
2177
+ <p style="margin: 0; font-size: 12px; color: var(--text-secondary); line-height: 1.4;">
2178
+ 💡 <strong>Tip:</strong> Only share links with people you trust. The session includes your conversation history and may contain project-specific information.
2179
+ </p>
2180
+ </div>
2181
+ </div>
2182
+
2183
+ <div class="modal-actions">
2184
+ <button class="modal-btn secondary" onclick="closeConfirmShareModal()">Cancel</button>
2185
+ <button class="modal-btn primary" onclick="proceedWithShare()" style="background: #3b82f6;">
2186
+ Continue & Upload
2187
+ </button>
2188
+ </div>
2189
+ </div>
2190
+ </div>
2191
+
2192
+ <!-- Share Modal -->
2193
+ <div class="modal-overlay" id="shareModal">
2194
+ <div class="modal">
2195
+ <div class="modal-header">
2196
+ <span class="modal-icon">📤</span>
2197
+ <h3 class="modal-title">Share Conversation</h3>
2198
+ </div>
2199
+ <div id="shareModalLoading" style="text-align: center; padding: 40px; display: none;">
2200
+ <div class="loading-spinner"></div>
2201
+ <p style="margin-top: 16px; color: var(--text-secondary);">Uploading session...</p>
2202
+ </div>
2203
+ <div id="shareModalContent" style="display: none;">
2204
+ <p class="modal-description">
2205
+ Your conversation has been uploaded to x0.at. Share the command or QR code below with others.
2206
+ </p>
2207
+ <div id="shareMessageInfo" style="display: none; padding: 12px; background: var(--bg-tertiary); border-radius: 6px; margin-bottom: 16px;">
2208
+ <p style="font-size: 13px; color: var(--text-secondary); margin: 0;">
2209
+ <span id="shareMessageCount"></span>
2210
+ </p>
2211
+ </div>
2212
+ <div style="text-align: center; margin: 24px 0;">
2213
+ <div style="margin-bottom: 16px; display: flex; flex-direction: column; align-items: center;">
2214
+ <h4 style="margin-bottom: 12px; color: var(--text-primary);">📱 Scan QR Code</h4>
2215
+ <img id="shareQRCode" src="" alt="QR Code" style="max-width: 300px; border-radius: 8px; border: 2px solid var(--border-color); display: block; margin: 0 auto;">
2216
+ <p style="font-size: 12px; color: var(--text-secondary); margin-top: 12px; margin-bottom: 0;">
2217
+ Scan this QR code to get the share command
2218
+ </p>
2219
+ </div>
2220
+ </div>
2221
+ <div>
2222
+ <h4 style="margin-bottom: 8px; color: var(--text-primary);">📋 Share Command</h4>
2223
+ <div class="modal-command" id="shareModalCommand">
2224
+ <!-- Command will be inserted here -->
2225
+ </div>
2226
+ </div>
2227
+ <div style="margin-top: 16px; padding: 12px; background: var(--bg-tertiary); border-radius: 6px;">
2228
+ <p style="font-size: 12px; color: var(--text-secondary); margin: 0;">
2229
+ 🔗 Direct URL: <a id="shareDirectUrl" href="#" target="_blank" style="color: var(--text-accent); word-break: break-all;"></a>
2230
+ </p>
2231
+ <p style="font-size: 12px; color: var(--text-warning); margin: 8px 0 0 0;">
2232
+ ⚠️ Files kept for 3-100 days (based on size)
2233
+ </p>
2234
+ <p style="font-size: 12px; color: var(--text-secondary); margin: 4px 0 0 0;">
2235
+ 🔓 Files are not encrypted by default
2236
+ </p>
2237
+ </div>
2238
+ </div>
2239
+ <div class="modal-actions">
2240
+ <button class="modal-btn secondary" onclick="closeShareModal()">Close</button>
2241
+ <button class="modal-btn primary" id="copyShareCommandBtn" onclick="copyShareCommand()" style="display: none;">Copy Command</button>
2242
+ </div>
2243
+ </div>
2244
+ </div>
2245
+
1278
2246
  <!-- Import WebSocket and Data Services -->
1279
2247
  <script src="services/WebSocketService.js"></script>
1280
2248
  <script src="services/DataService.js"></script>
@@ -1340,6 +2308,109 @@
1340
2308
  this.filterConversations(e.target.value);
1341
2309
  });
1342
2310
 
2311
+ // Advanced search toggle
2312
+ document.getElementById('advancedSearchToggle').addEventListener('click', () => {
2313
+ this.openAdvancedSearch();
2314
+ });
2315
+
2316
+ // Advanced search panel close
2317
+ document.getElementById('searchPanelClose').addEventListener('click', () => {
2318
+ this.closeAdvancedSearch();
2319
+ });
2320
+
2321
+ // Close panel on overlay click
2322
+ document.getElementById('searchPanelOverlay').addEventListener('click', (e) => {
2323
+ if (e.target.id === 'searchPanelOverlay') {
2324
+ this.closeAdvancedSearch();
2325
+ }
2326
+ });
2327
+
2328
+ // Advanced search apply
2329
+ document.getElementById('searchPanelApply').addEventListener('click', () => {
2330
+ this.applyAdvancedSearch();
2331
+ });
2332
+
2333
+ // Advanced search reset
2334
+ document.getElementById('searchPanelReset').addEventListener('click', () => {
2335
+ this.resetAdvancedSearch();
2336
+ });
2337
+
2338
+ // Clear filters button
2339
+ document.getElementById('clearFiltersBtn').addEventListener('click', () => {
2340
+ this.clearAllFilters();
2341
+ });
2342
+
2343
+ // Folder browser toggle
2344
+ document.getElementById('folderBrowserToggle').addEventListener('click', (e) => {
2345
+ e.preventDefault();
2346
+ e.stopPropagation();
2347
+ this.toggleFolderBrowser();
2348
+ });
2349
+
2350
+ // Folder browser search
2351
+ document.getElementById('folderBrowserSearch').addEventListener('input', (e) => {
2352
+ this.filterFolderBrowserList(e.target.value);
2353
+ });
2354
+
2355
+ // Close folder browser when clicking outside
2356
+ document.addEventListener('click', (e) => {
2357
+ const dropdown = document.getElementById('folderBrowserDropdown');
2358
+ const toggle = document.getElementById('folderBrowserToggle');
2359
+ const wrapper = document.querySelector('.folder-browser-wrapper');
2360
+
2361
+ if (dropdown.classList.contains('active') &&
2362
+ !wrapper.contains(e.target)) {
2363
+ dropdown.classList.remove('active');
2364
+ }
2365
+ });
2366
+
2367
+ // In-conversation search toggle
2368
+ document.getElementById('chatSearchToggle').addEventListener('click', () => {
2369
+ this.toggleChatSearch();
2370
+ });
2371
+
2372
+ // In-conversation search input (only search with 3+ characters)
2373
+ document.getElementById('chatSearchInput').addEventListener('input', (e) => {
2374
+ const query = e.target.value.trim();
2375
+ if (query.length >= 3) {
2376
+ this.performChatSearch(query);
2377
+ } else if (query.length === 0) {
2378
+ // Clear search when input is empty
2379
+ this.clearChatSearch();
2380
+ } else {
2381
+ // Show message that minimum 3 characters are required
2382
+ this.updateSearchCounter(0, 0, true);
2383
+ }
2384
+ });
2385
+
2386
+ // In-conversation search navigation
2387
+ document.getElementById('chatSearchPrev').addEventListener('click', () => {
2388
+ this.navigateSearchResults('prev');
2389
+ });
2390
+
2391
+ document.getElementById('chatSearchNext').addEventListener('click', () => {
2392
+ this.navigateSearchResults('next');
2393
+ });
2394
+
2395
+ // In-conversation search close
2396
+ document.getElementById('chatSearchClose').addEventListener('click', () => {
2397
+ this.closeChatSearch();
2398
+ });
2399
+
2400
+ // Keyboard shortcuts for search
2401
+ document.getElementById('chatSearchInput').addEventListener('keydown', (e) => {
2402
+ if (e.key === 'Enter') {
2403
+ e.preventDefault();
2404
+ if (e.shiftKey) {
2405
+ this.navigateSearchResults('prev');
2406
+ } else {
2407
+ this.navigateSearchResults('next');
2408
+ }
2409
+ } else if (e.key === 'Escape') {
2410
+ this.closeChatSearch();
2411
+ }
2412
+ });
2413
+
1343
2414
  // Show Tools toggle functionality
1344
2415
  const showToolsSwitch = document.getElementById('showToolsSwitch');
1345
2416
  showToolsSwitch.addEventListener('change', (e) => {
@@ -1420,56 +2491,101 @@
1420
2491
 
1421
2492
  renderConversations(conversations, states = {}) {
1422
2493
  const conversationsList = document.getElementById('conversationsList');
1423
-
2494
+
1424
2495
  if (conversations.length === 0) {
1425
2496
  conversationsList.innerHTML = `
1426
2497
  <div class="no-conversations">
1427
2498
  <div class="no-conversations-icon">💬</div>
1428
- <h3>No conversations found</h3>
1429
- <p>Start a conversation with Claude Code to see it here</p>
2499
+ <h3>No sessions found</h3>
2500
+ <p>Start a session with Claude Code to see it here</p>
1430
2501
  </div>
1431
2502
  `;
1432
2503
  return;
1433
2504
  }
1434
2505
 
1435
- conversationsList.innerHTML = conversations.map(conv => {
1436
- const state = states[conv.id] || 'inactive';
1437
- const stateClass = this.getStateClass(state);
1438
- const stateLabel = this.getStateLabel(state);
1439
-
1440
- // Debug logging for first few conversations
1441
- console.log(`🔍 Conversation ${conv.id.slice(-8)}: State="${state}" -> Label="${stateLabel}" Class="${stateClass}"`);
1442
-
1443
- const lastActivity = this.formatRelativeTime(new Date(conv.lastModified));
1444
- const messageCount = conv.messageCount || 0;
2506
+ // Group conversations by project
2507
+ const groupedByProject = conversations.reduce((groups, conv) => {
1445
2508
  const projectName = conv.project || 'Unknown Project';
1446
- const conversationId = conv.id.slice(-8);
1447
-
1448
- // Get first letter of project name for avatar
2509
+ if (!groups[projectName]) {
2510
+ groups[projectName] = [];
2511
+ }
2512
+ groups[projectName].push(conv);
2513
+ return groups;
2514
+ }, {});
2515
+
2516
+ // Sort projects alphabetically
2517
+ const sortedProjects = Object.keys(groupedByProject).sort();
2518
+
2519
+ // Initialize expanded state if not exists
2520
+ if (!this.expandedProjects) {
2521
+ this.expandedProjects = new Set(); // All collapsed by default
2522
+ }
2523
+
2524
+ // Render grouped conversations
2525
+ conversationsList.innerHTML = sortedProjects.map(projectName => {
2526
+ const projectConversations = groupedByProject[projectName];
2527
+ const isExpanded = this.expandedProjects.has(projectName);
1449
2528
  const firstLetter = projectName.charAt(0).toUpperCase();
1450
-
1451
- return `
1452
- <div class="conversation-item" data-conversation-id="${conv.id}">
1453
- <div class="conversation-avatar">
1454
- ${firstLetter}
2529
+ const conversationCount = projectConversations.length;
2530
+
2531
+ // Render conversations for this project
2532
+ const conversationsHTML = projectConversations.map(conv => {
2533
+ const state = states[conv.id] || 'inactive';
2534
+ const stateClass = this.getStateClass(state);
2535
+ const stateLabel = this.getStateLabel(state);
2536
+
2537
+ const lastActivity = this.formatRelativeTime(new Date(conv.lastModified));
2538
+ const messageCount = conv.messageCount || 0;
2539
+ const conversationId = conv.id.slice(-8);
2540
+
2541
+ return `
2542
+ <div class="conversation-item" data-conversation-id="${conv.id}" style="display: ${isExpanded ? 'flex' : 'none'}">
2543
+ <div class="conversation-avatar conversation-avatar-small">
2544
+ ${conversationId.substring(0, 2).toUpperCase()}
2545
+ </div>
2546
+ <div class="conversation-content">
2547
+ <div class="conversation-header">
2548
+ <div class="conversation-name">Session ${conversationId}</div>
2549
+ <div class="conversation-time">${lastActivity}</div>
2550
+ </div>
2551
+ <div class="conversation-meta">
2552
+ <span class="conversation-state ${stateClass}">${stateLabel}</span>
2553
+ ${messageCount > 0 ? `<span class="message-count">${messageCount}</span>` : ''}
2554
+ </div>
2555
+ </div>
1455
2556
  </div>
1456
- <div class="conversation-content">
1457
- <div class="conversation-header">
1458
- <div class="conversation-name">${projectName}</div>
1459
- <div class="conversation-time">${lastActivity}</div>
2557
+ `;
2558
+ }).join('');
2559
+
2560
+ return `
2561
+ <div class="project-group" data-project="${projectName}">
2562
+ <div class="project-header" data-project="${projectName}">
2563
+ <div class="project-avatar">
2564
+ ${firstLetter}
1460
2565
  </div>
1461
- <div class="conversation-preview">
1462
- Conversation ${conversationId}
2566
+ <div class="project-info">
2567
+ <div class="project-name">${projectName}</div>
2568
+ <div class="project-count">${conversationCount} session${conversationCount !== 1 ? 's' : ''}</div>
1463
2569
  </div>
1464
- <div class="conversation-meta">
1465
- <span class="conversation-state ${stateClass}">${stateLabel}</span>
1466
- ${messageCount > 0 ? `<span class="message-count">${messageCount}</span>` : ''}
2570
+ <div class="project-toggle">
2571
+ <svg class="toggle-icon ${isExpanded ? 'expanded' : ''}" width="20" height="20" viewBox="0 0 20 20" fill="currentColor">
2572
+ <path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd"/>
2573
+ </svg>
1467
2574
  </div>
1468
2575
  </div>
2576
+ ${conversationsHTML}
1469
2577
  </div>
1470
2578
  `;
1471
2579
  }).join('');
1472
2580
 
2581
+ // Bind project toggle events
2582
+ conversationsList.querySelectorAll('.project-header').forEach(header => {
2583
+ header.addEventListener('click', (e) => {
2584
+ const projectName = header.dataset.project;
2585
+ this.toggleProject(projectName);
2586
+ });
2587
+ });
2588
+
1473
2589
  // Bind conversation click events
1474
2590
  conversationsList.querySelectorAll('.conversation-item').forEach(item => {
1475
2591
  item.addEventListener('click', () => {
@@ -1479,6 +2595,17 @@
1479
2595
  });
1480
2596
  }
1481
2597
 
2598
+ toggleProject(projectName) {
2599
+ if (this.expandedProjects.has(projectName)) {
2600
+ this.expandedProjects.delete(projectName);
2601
+ } else {
2602
+ this.expandedProjects.add(projectName);
2603
+ }
2604
+
2605
+ // Re-render conversations with current states
2606
+ this.renderConversations(this.conversations, this.conversationStates);
2607
+ }
2608
+
1482
2609
  selectConversation(conversationId) {
1483
2610
  this.selectedConversationId = conversationId;
1484
2611
 
@@ -1503,7 +2630,7 @@
1503
2630
  const projectName = conversation.project || 'Unknown Project';
1504
2631
  const convId = conversation.id.slice(-8);
1505
2632
  chatViewTitle.textContent = projectName;
1506
- chatViewSubtitle.textContent = `Conversation ${convId}`;
2633
+ chatViewSubtitle.textContent = `Session ${convId}`;
1507
2634
 
1508
2635
  // Show chat view with animation
1509
2636
  chatView.classList.add('active');
@@ -1515,10 +2642,14 @@
1515
2642
  chatView.classList.remove('show-tools');
1516
2643
  }
1517
2644
 
1518
- // Show resume button
2645
+ // Show action buttons group
2646
+ const actionButtonsGroup = document.getElementById('actionButtonsGroup');
2647
+ actionButtonsGroup.style.display = 'inline-flex';
2648
+
1519
2649
  const resumeBtn = document.getElementById('resumeConversation');
1520
- resumeBtn.style.display = 'block';
2650
+ const shareBtn = document.getElementById('shareConversation');
1521
2651
  resumeBtn.setAttribute('data-conversation-id', conversationId);
2652
+ shareBtn.setAttribute('data-conversation-id', conversationId);
1522
2653
 
1523
2654
  // Load messages (placeholder for now)
1524
2655
  this.loadChatMessages(conversationId);
@@ -1533,9 +2664,9 @@
1533
2664
  // Clean up scroll tracking when leaving conversation
1534
2665
  this.removeScrollTracking();
1535
2666
 
1536
- // Hide resume button
1537
- const resumeBtn = document.getElementById('resumeConversation');
1538
- resumeBtn.style.display = 'none';
2667
+ // Hide action buttons group
2668
+ const actionButtonsGroup = document.getElementById('actionButtonsGroup');
2669
+ actionButtonsGroup.style.display = 'none';
1539
2670
 
1540
2671
  // Remove active state from conversations
1541
2672
  document.querySelectorAll('.conversation-item').forEach(item => {
@@ -1717,22 +2848,26 @@
1717
2848
  const scrollHeight = chatMessages.scrollHeight;
1718
2849
 
1719
2850
  // Prepend new messages
1720
- const newMessagesHTML = messages.map(msg => this.renderMessage(msg)).join('');
2851
+ // Calculate message indices (considering we're prepending older messages)
2852
+ const startIndex = this.messagesPagination.currentPage * this.messagesPagination.limit;
2853
+ const newMessagesHTML = messages.map((msg, idx) =>
2854
+ this.renderMessage(msg, startIndex + idx)
2855
+ ).join('');
1721
2856
  existingMessagesDiv.innerHTML = newMessagesHTML + existingMessagesDiv.innerHTML;
1722
-
2857
+
1723
2858
  // Restore scroll position (account for new content)
1724
2859
  const newScrollHeight = chatMessages.scrollHeight;
1725
2860
  const scrollDiff = newScrollHeight - scrollHeight;
1726
2861
  chatMessages.scrollTop = scrollTop + scrollDiff;
1727
-
2862
+
1728
2863
  } else {
1729
2864
  // Normal render (initial load or replace all)
1730
2865
  const messageHTML = `
1731
2866
  <div class="messages-list">
1732
- ${messages.map(msg => this.renderMessage(msg)).join('')}
2867
+ ${messages.map((msg, idx) => this.renderMessage(msg, idx)).join('')}
1733
2868
  </div>
1734
2869
  `;
1735
-
2870
+
1736
2871
  chatMessages.innerHTML = messageHTML;
1737
2872
  }
1738
2873
  }
@@ -1800,17 +2935,17 @@
1800
2935
  chatMessages.addEventListener('scroll', this.messagesScrollListener);
1801
2936
  }
1802
2937
 
1803
- renderMessage(message) {
2938
+ renderMessage(message, messageIndex) {
1804
2939
  const timestamp = this.formatRelativeTime(new Date(message.timestamp));
1805
2940
  const fullTimestamp = new Date(message.timestamp).toLocaleString();
1806
2941
  const isUser = message.role === 'user' && !message.isCompactSummary;
1807
-
2942
+
1808
2943
  // Detect if message contains tools (either in content or as correlated toolResults)
1809
- const hasToolsInContent = Array.isArray(message.content) &&
2944
+ const hasToolsInContent = Array.isArray(message.content) &&
1810
2945
  message.content.some(block => block.type === 'tool_use');
1811
2946
  const hasCorrelatedTools = message.toolResults && message.toolResults.length > 0;
1812
2947
  const hasTools = hasToolsInContent || hasCorrelatedTools;
1813
-
2948
+
1814
2949
  // Debug logging for tool detection
1815
2950
  if (hasTools) {
1816
2951
  console.log('🔧 Rendering message with tools', {
@@ -1823,16 +2958,19 @@
1823
2958
  willHaveHasToolsClass: !isUser && hasTools
1824
2959
  });
1825
2960
  }
1826
-
1827
- const toolCount = hasToolsInContent ?
2961
+
2962
+ const toolCount = hasToolsInContent ?
1828
2963
  message.content.filter(block => block.type === 'tool_use').length :
1829
2964
  (hasCorrelatedTools ? message.toolResults.length : 0);
1830
-
2965
+
1831
2966
  // Add has-tools class to assistant messages that contain tools
1832
2967
  const hasToolsClass = (!isUser && hasTools) ? ' has-tools' : '';
1833
-
2968
+
2969
+ // Add message index for search functionality
2970
+ const messageIndexAttr = messageIndex !== undefined ? ` data-message-index="${messageIndex}"` : '';
2971
+
1834
2972
  return `
1835
- <div class="message message-${isUser ? 'user' : 'assistant'}${hasToolsClass}" data-message-id="${message.id || ''}">
2973
+ <div class="message message-${isUser ? 'user' : 'assistant'}${hasToolsClass}" data-message-id="${message.id || ''}"${messageIndexAttr}>
1836
2974
  <div class="message-bubble">
1837
2975
  <div class="message-content">
1838
2976
  ${this.formatMessageContent(message.content, message)}
@@ -2106,107 +3244,785 @@
2106
3244
  return scrollHeight - scrollTop - clientHeight <= this.scrollThreshold;
2107
3245
  }
2108
3246
 
2109
- /**
2110
- * Smart scroll to bottom with chat logic
2111
- * Only scrolls if user is near bottom or auto-scroll is enabled
2112
- */
2113
- scrollToBottom() {
2114
- const chatMessages = document.getElementById('chatMessages');
2115
- if (!chatMessages) return;
2116
-
2117
- // Always scroll on initial load or if user is near bottom
2118
- if (this.autoScrollEnabled || this.isNearBottom()) {
2119
- console.log('📱 Auto-scrolling to bottom', {
2120
- autoScrollEnabled: this.autoScrollEnabled,
2121
- isNearBottom: this.isNearBottom(),
2122
- userScrolling: this.isUserScrolling
2123
- });
2124
-
2125
- chatMessages.scrollTop = chatMessages.scrollHeight;
3247
+ /**
3248
+ * Smart scroll to bottom with chat logic
3249
+ * Only scrolls if user is near bottom or auto-scroll is enabled
3250
+ */
3251
+ scrollToBottom() {
3252
+ const chatMessages = document.getElementById('chatMessages');
3253
+ if (!chatMessages) return;
3254
+
3255
+ // Always scroll on initial load or if user is near bottom
3256
+ if (this.autoScrollEnabled || this.isNearBottom()) {
3257
+ console.log('📱 Auto-scrolling to bottom', {
3258
+ autoScrollEnabled: this.autoScrollEnabled,
3259
+ isNearBottom: this.isNearBottom(),
3260
+ userScrolling: this.isUserScrolling
3261
+ });
3262
+
3263
+ chatMessages.scrollTop = chatMessages.scrollHeight;
3264
+ } else {
3265
+ console.log('📱 Skipping auto-scroll (user viewing older messages)', {
3266
+ scrollTop: chatMessages.scrollTop,
3267
+ scrollHeight: chatMessages.scrollHeight,
3268
+ isNearBottom: this.isNearBottom()
3269
+ });
3270
+ }
3271
+ }
3272
+
3273
+ /**
3274
+ * Setup scroll tracking for intelligent auto-scroll
3275
+ */
3276
+ setupScrollTracking() {
3277
+ const chatMessages = document.getElementById('chatMessages');
3278
+ if (!chatMessages || this.scrollListener) return;
3279
+
3280
+ this.scrollListener = () => {
3281
+ // Clear previous timeout
3282
+ if (this.userScrollTimeout) {
3283
+ clearTimeout(this.userScrollTimeout);
3284
+ }
3285
+
3286
+ // Mark as user scrolling
3287
+ this.isUserScrolling = true;
3288
+
3289
+ // Check if user scrolled back to bottom
3290
+ if (this.isNearBottom()) {
3291
+ this.autoScrollEnabled = true;
3292
+ console.log('📱 User scrolled to bottom, re-enabling auto-scroll');
3293
+ } else {
3294
+ this.autoScrollEnabled = false;
3295
+ }
3296
+
3297
+ // Reset user scrolling flag after a delay
3298
+ this.userScrollTimeout = setTimeout(() => {
3299
+ this.isUserScrolling = false;
3300
+ }, 1000);
3301
+ };
3302
+
3303
+ chatMessages.addEventListener('scroll', this.scrollListener, { passive: true });
3304
+ console.log('📱 Scroll tracking enabled for intelligent auto-scroll');
3305
+ }
3306
+
3307
+ /**
3308
+ * Remove scroll tracking
3309
+ */
3310
+ removeScrollTracking() {
3311
+ const chatMessages = document.getElementById('chatMessages');
3312
+ if (chatMessages && this.scrollListener) {
3313
+ chatMessages.removeEventListener('scroll', this.scrollListener);
3314
+ this.scrollListener = null;
3315
+ }
3316
+
3317
+ if (this.userScrollTimeout) {
3318
+ clearTimeout(this.userScrollTimeout);
3319
+ this.userScrollTimeout = null;
3320
+ }
3321
+ }
3322
+
3323
+ /**
3324
+ * Escape HTML characters to prevent double-encoding issues
3325
+ * @param {string} text - Text to escape
3326
+ * @returns {string} Escaped text
3327
+ */
3328
+ escapeHtml(text) {
3329
+ if (typeof text !== 'string') return text;
3330
+
3331
+ const div = document.createElement('div');
3332
+ div.textContent = text;
3333
+ return div.innerHTML;
3334
+ }
3335
+
3336
+
3337
+
3338
+ filterConversations(searchTerm) {
3339
+ const term = searchTerm.toLowerCase().trim();
3340
+ const projectGroups = document.querySelectorAll('.project-group');
3341
+
3342
+ if (!term) {
3343
+ // If search is empty, collapse all projects and reset
3344
+ this.expandedProjects.clear(); // Close all projects
3345
+
3346
+ projectGroups.forEach(group => {
3347
+ group.style.display = 'block';
3348
+
3349
+ // Hide all conversations
3350
+ const conversations = group.querySelectorAll('.conversation-item');
3351
+ conversations.forEach(conv => {
3352
+ conv.style.display = 'none';
3353
+ });
3354
+
3355
+ // Update toggle icon to collapsed state
3356
+ const toggleIcon = group.querySelector('.toggle-icon');
3357
+ if (toggleIcon) {
3358
+ toggleIcon.classList.remove('expanded');
3359
+ }
3360
+ });
3361
+ return;
3362
+ }
3363
+
3364
+ // Filter projects and conversations
3365
+ projectGroups.forEach(group => {
3366
+ const projectName = group.dataset.project;
3367
+ const projectNameLower = projectName.toLowerCase();
3368
+ const conversations = group.querySelectorAll('.conversation-item');
3369
+
3370
+ let hasMatchingConversation = false;
3371
+
3372
+ // Check each conversation in this project
3373
+ conversations.forEach(item => {
3374
+ const nameElement = item.querySelector('.conversation-name');
3375
+ const name = nameElement ? nameElement.textContent.toLowerCase() : '';
3376
+ const matches = name.includes(term) || projectNameLower.includes(term);
3377
+
3378
+ if (matches) {
3379
+ item.style.display = 'flex';
3380
+ hasMatchingConversation = true;
3381
+ } else {
3382
+ item.style.display = 'none';
3383
+ }
3384
+ });
3385
+
3386
+ // Show project group if it has matching conversations or if project name matches
3387
+ if (hasMatchingConversation || projectNameLower.includes(term)) {
3388
+ group.style.display = 'block';
3389
+ // Auto-expand project when searching
3390
+ if (!this.expandedProjects.has(projectName)) {
3391
+ this.expandedProjects.add(projectName);
3392
+ // Update toggle icon
3393
+ const toggleIcon = group.querySelector('.toggle-icon');
3394
+ if (toggleIcon) {
3395
+ toggleIcon.classList.add('expanded');
3396
+ }
3397
+ }
3398
+ } else {
3399
+ group.style.display = 'none';
3400
+ }
3401
+ });
3402
+ }
3403
+
3404
+ // Advanced Search Methods
3405
+ openAdvancedSearch() {
3406
+ const overlay = document.getElementById('searchPanelOverlay');
3407
+ overlay.classList.add('active');
3408
+ document.body.style.overflow = 'hidden'; // Prevent background scrolling
3409
+ }
3410
+
3411
+ closeAdvancedSearch() {
3412
+ const overlay = document.getElementById('searchPanelOverlay');
3413
+ overlay.classList.remove('active');
3414
+ document.body.style.overflow = ''; // Restore scrolling
3415
+ }
3416
+
3417
+ async applyAdvancedSearch() {
3418
+ const conversationsList = document.getElementById('conversationsList');
3419
+ const searchResultsInfo = document.getElementById('searchResultsInfo');
3420
+ const searchResultsCount = document.getElementById('searchResultsCount');
3421
+ const appliedFiltersContainer = document.getElementById('appliedFilters');
3422
+
3423
+ try {
3424
+ // Show loading state
3425
+ conversationsList.innerHTML = '<div class="loading-spinner" style="margin: 40px auto;"></div>';
3426
+
3427
+ // Get filter values
3428
+ const filters = {
3429
+ query: document.getElementById('filterQuery').value.trim(),
3430
+ workingDirectory: document.getElementById('filterWorkingDirectory').value.trim(),
3431
+ dateFrom: document.getElementById('filterDateFrom').value,
3432
+ dateTo: document.getElementById('filterDateTo').value,
3433
+ contentSearch: document.getElementById('filterContentSearch').value.trim()
3434
+ };
3435
+
3436
+ // Call the search API
3437
+ const response = await fetch('/api/search', {
3438
+ method: 'POST',
3439
+ headers: {
3440
+ 'Content-Type': 'application/json'
3441
+ },
3442
+ body: JSON.stringify(filters)
3443
+ });
3444
+
3445
+ if (!response.ok) {
3446
+ throw new Error(`Search failed: ${response.status}`);
3447
+ }
3448
+
3449
+ const data = await response.json();
3450
+
3451
+ // Update conversations with search results
3452
+ this.conversations = data.results || [];
3453
+
3454
+ // Get conversation states for the results
3455
+ const statesResponse = await fetch('/api/conversation-state');
3456
+ let states = {};
3457
+ if (statesResponse.ok) {
3458
+ const statesData = await statesResponse.json();
3459
+ states = statesData.activeStates || {};
3460
+ }
3461
+
3462
+ // Render search results
3463
+ this.renderConversations(this.conversations, states);
3464
+
3465
+ // Show search results info with applied filters
3466
+ searchResultsCount.textContent = data.count;
3467
+ this.renderAppliedFilters(filters, appliedFiltersContainer);
3468
+ searchResultsInfo.classList.add('active');
3469
+
3470
+ // Close the search panel
3471
+ this.closeAdvancedSearch();
3472
+
3473
+ console.log('🔍 Search completed:', data.count, 'results found');
3474
+ } catch (error) {
3475
+ console.error('Error performing advanced search:', error);
3476
+ conversationsList.innerHTML = `
3477
+ <div class="no-conversations">
3478
+ <div class="no-conversations-icon">⚠️</div>
3479
+ <h3>Search Error</h3>
3480
+ <p>${error.message}</p>
3481
+ <button onclick="location.reload()" style="margin-top: 12px; padding: 8px 16px; background: var(--text-accent); color: white; border: none; border-radius: 4px; cursor: pointer;">Retry</button>
3482
+ </div>
3483
+ `;
3484
+ }
3485
+ }
3486
+
3487
+ renderAppliedFilters(filters, container) {
3488
+ container.innerHTML = '';
3489
+
3490
+ const filterTags = [];
3491
+
3492
+ // Quick search filter
3493
+ if (filters.query) {
3494
+ filterTags.push({
3495
+ icon: '💬',
3496
+ label: 'Search',
3497
+ value: filters.query
3498
+ });
3499
+ }
3500
+
3501
+ // Working directory filter
3502
+ if (filters.workingDirectory) {
3503
+ // Shorten the path for display
3504
+ const shortPath = filters.workingDirectory.length > 30
3505
+ ? '...' + filters.workingDirectory.slice(-30)
3506
+ : filters.workingDirectory;
3507
+ filterTags.push({
3508
+ icon: '📁',
3509
+ label: 'Directory',
3510
+ value: shortPath
3511
+ });
3512
+ }
3513
+
3514
+ // Date range filter
3515
+ if (filters.dateFrom || filters.dateTo) {
3516
+ let dateValue = '';
3517
+ if (filters.dateFrom && filters.dateTo) {
3518
+ dateValue = `${this.formatDate(filters.dateFrom)} → ${this.formatDate(filters.dateTo)}`;
3519
+ } else if (filters.dateFrom) {
3520
+ dateValue = `From ${this.formatDate(filters.dateFrom)}`;
3521
+ } else if (filters.dateTo) {
3522
+ dateValue = `Until ${this.formatDate(filters.dateTo)}`;
3523
+ }
3524
+ filterTags.push({
3525
+ icon: '📅',
3526
+ label: 'Date',
3527
+ value: dateValue
3528
+ });
3529
+ }
3530
+
3531
+ // Content search filter
3532
+ if (filters.contentSearch) {
3533
+ filterTags.push({
3534
+ icon: '🔎',
3535
+ label: 'Content',
3536
+ value: filters.contentSearch
3537
+ });
3538
+ }
3539
+
3540
+ // Render filter tags
3541
+ filterTags.forEach(tag => {
3542
+ const tagElement = document.createElement('div');
3543
+ tagElement.className = 'filter-tag';
3544
+ tagElement.innerHTML = `
3545
+ <span class="filter-tag-icon">${tag.icon}</span>
3546
+ <span class="filter-tag-label">${tag.label}:</span>
3547
+ <span class="filter-tag-value">${tag.value}</span>
3548
+ `;
3549
+ container.appendChild(tagElement);
3550
+ });
3551
+ }
3552
+
3553
+ formatDate(dateString) {
3554
+ const date = new Date(dateString);
3555
+ const options = { month: 'short', day: 'numeric', year: 'numeric' };
3556
+ return date.toLocaleDateString('en-US', options);
3557
+ }
3558
+
3559
+ // Folder Browser Methods
3560
+ async toggleFolderBrowser() {
3561
+ const dropdown = document.getElementById('folderBrowserDropdown');
3562
+ const isActive = dropdown.classList.contains('active');
3563
+
3564
+ if (isActive) {
3565
+ dropdown.classList.remove('active');
3566
+ } else {
3567
+ // Load directories if not loaded yet
3568
+ if (!this.directories) {
3569
+ await this.loadDirectories();
3570
+ }
3571
+ dropdown.classList.add('active');
3572
+ }
3573
+ }
3574
+
3575
+ async loadDirectories() {
3576
+ const listContainer = document.getElementById('folderBrowserList');
3577
+
3578
+ try {
3579
+ listContainer.innerHTML = '<div class="folder-browser-empty">Loading directories...</div>';
3580
+
3581
+ const response = await fetch('/api/directories');
3582
+ if (!response.ok) {
3583
+ throw new Error('Failed to load directories');
3584
+ }
3585
+
3586
+ const data = await response.json();
3587
+ this.directories = data.directories || [];
3588
+
3589
+ // Count conversations per directory
3590
+ this.directoryCounts = {};
3591
+ this.conversations.forEach(conv => {
3592
+ if (conv.project) {
3593
+ this.directoryCounts[conv.project] = (this.directoryCounts[conv.project] || 0) + 1;
3594
+ }
3595
+ });
3596
+
3597
+ this.renderFolderBrowserList(this.directories);
3598
+ } catch (error) {
3599
+ console.error('Error loading directories:', error);
3600
+ listContainer.innerHTML = '<div class="folder-browser-empty">Error loading directories</div>';
3601
+ }
3602
+ }
3603
+
3604
+ renderFolderBrowserList(directories) {
3605
+ const listContainer = document.getElementById('folderBrowserList');
3606
+ const currentValue = document.getElementById('filterWorkingDirectory').value;
3607
+
3608
+ if (directories.length === 0) {
3609
+ listContainer.innerHTML = '<div class="folder-browser-empty">No directories found</div>';
3610
+ return;
3611
+ }
3612
+
3613
+ listContainer.innerHTML = directories.map(dir => {
3614
+ const count = this.directoryCounts[dir] || 0;
3615
+ const isSelected = dir === currentValue;
3616
+
3617
+ return `
3618
+ <div class="folder-item ${isSelected ? 'selected' : ''}" data-path="${this.escapeHtml(dir)}">
3619
+ <span class="folder-icon">📁</span>
3620
+ <span class="folder-path" title="${this.escapeHtml(dir)}">${this.escapeHtml(dir)}</span>
3621
+ <span class="folder-count">${count}</span>
3622
+ </div>
3623
+ `;
3624
+ }).join('');
3625
+
3626
+ // Add click handlers to folder items
3627
+ listContainer.querySelectorAll('.folder-item').forEach(item => {
3628
+ item.addEventListener('click', () => {
3629
+ const path = item.getAttribute('data-path');
3630
+ this.selectDirectory(path);
3631
+ });
3632
+ });
3633
+ }
3634
+
3635
+ selectDirectory(path) {
3636
+ const input = document.getElementById('filterWorkingDirectory');
3637
+ const dropdown = document.getElementById('folderBrowserDropdown');
3638
+
3639
+ input.value = path;
3640
+ dropdown.classList.remove('active');
3641
+
3642
+ // Update UI to show selected folder
3643
+ this.renderFolderBrowserList(this.directories);
3644
+ }
3645
+
3646
+ filterFolderBrowserList(searchTerm) {
3647
+ if (!this.directories) return;
3648
+
3649
+ const term = searchTerm.toLowerCase();
3650
+ const filtered = this.directories.filter(dir =>
3651
+ dir.toLowerCase().includes(term)
3652
+ );
3653
+
3654
+ this.renderFolderBrowserList(filtered);
3655
+ }
3656
+
3657
+ escapeHtml(text) {
3658
+ const div = document.createElement('div');
3659
+ div.textContent = text;
3660
+ return div.innerHTML;
3661
+ }
3662
+
3663
+ // In-Conversation Search Methods
3664
+ toggleChatSearch() {
3665
+ const searchBar = document.getElementById('chatSearchBar');
3666
+ const toggle = document.getElementById('chatSearchToggle');
3667
+ const isActive = searchBar.classList.contains('active');
3668
+
3669
+ if (isActive) {
3670
+ this.closeChatSearch();
3671
+ } else {
3672
+ searchBar.classList.add('active');
3673
+ toggle.classList.add('active');
3674
+ document.getElementById('chatSearchInput').focus();
3675
+ }
3676
+ }
3677
+
3678
+ closeChatSearch() {
3679
+ const searchBar = document.getElementById('chatSearchBar');
3680
+ const toggle = document.getElementById('chatSearchToggle');
3681
+ const input = document.getElementById('chatSearchInput');
3682
+
3683
+ searchBar.classList.remove('active');
3684
+ toggle.classList.remove('active');
3685
+ input.value = '';
3686
+
3687
+ // Clear search state
3688
+ this.chatSearchMatches = [];
3689
+ this.chatSearchCurrentIndex = -1;
3690
+ this.updateSearchCounter();
3691
+ this.clearSearchHighlights();
3692
+ }
3693
+
3694
+ clearChatSearch() {
3695
+ // Clear search results but keep search bar open
3696
+ this.chatSearchMatches = [];
3697
+ this.chatSearchCurrentIndex = -1;
3698
+ this.updateSearchCounter();
3699
+ this.clearSearchHighlights();
3700
+ }
3701
+
3702
+ async performChatSearch(query) {
3703
+ if (!this.selectedConversationId || !query || query.trim().length === 0) {
3704
+ this.chatSearchMatches = [];
3705
+ this.chatSearchCurrentIndex = -1;
3706
+ this.updateSearchCounter();
3707
+ this.clearSearchHighlights();
3708
+ return;
3709
+ }
3710
+
3711
+ try {
3712
+ const response = await fetch(`/api/conversations/${this.selectedConversationId}/search`, {
3713
+ method: 'POST',
3714
+ headers: {
3715
+ 'Content-Type': 'application/json'
3716
+ },
3717
+ body: JSON.stringify({ query: query.trim() })
3718
+ });
3719
+
3720
+ if (!response.ok) {
3721
+ throw new Error('Search failed');
3722
+ }
3723
+
3724
+ const data = await response.json();
3725
+ this.chatSearchMatches = data.matches || [];
3726
+ this.chatSearchCurrentIndex = this.chatSearchMatches.length > 0 ? 0 : -1;
3727
+
3728
+ console.log('🔍 Search results:', {
3729
+ query: query,
3730
+ totalMatches: this.chatSearchMatches.length,
3731
+ matches: this.chatSearchMatches
3732
+ });
3733
+
3734
+ this.updateSearchCounter();
3735
+ this.highlightSearchResults(query);
3736
+
3737
+ // Navigate to first match
3738
+ if (this.chatSearchMatches.length > 0) {
3739
+ await this.scrollToSearchMatch(0);
3740
+ }
3741
+ } catch (error) {
3742
+ console.error('Error searching in conversation:', error);
3743
+ }
3744
+ }
3745
+
3746
+ async navigateSearchResults(direction) {
3747
+ if (this.chatSearchMatches.length === 0) return;
3748
+
3749
+ if (direction === 'next') {
3750
+ this.chatSearchCurrentIndex = (this.chatSearchCurrentIndex + 1) % this.chatSearchMatches.length;
2126
3751
  } else {
2127
- console.log('📱 Skipping auto-scroll (user viewing older messages)', {
2128
- scrollTop: chatMessages.scrollTop,
2129
- scrollHeight: chatMessages.scrollHeight,
2130
- isNearBottom: this.isNearBottom()
2131
- });
3752
+ this.chatSearchCurrentIndex = (this.chatSearchCurrentIndex - 1 + this.chatSearchMatches.length) % this.chatSearchMatches.length;
2132
3753
  }
3754
+
3755
+ this.updateSearchCounter();
3756
+ await this.scrollToSearchMatch(this.chatSearchCurrentIndex);
2133
3757
  }
2134
3758
 
2135
- /**
2136
- * Setup scroll tracking for intelligent auto-scroll
2137
- */
2138
- setupScrollTracking() {
2139
- const chatMessages = document.getElementById('chatMessages');
2140
- if (!chatMessages || this.scrollListener) return;
3759
+ async scrollToSearchMatch(index) {
3760
+ if (index < 0 || index >= this.chatSearchMatches.length) return;
2141
3761
 
2142
- this.scrollListener = () => {
2143
- // Clear previous timeout
2144
- if (this.userScrollTimeout) {
2145
- clearTimeout(this.userScrollTimeout);
3762
+ const match = this.chatSearchMatches[index];
3763
+ const messageId = match.messageId;
3764
+ const messageIndex = match.messageIndex;
3765
+
3766
+ // Try to find the message by ID first (more reliable)
3767
+ let messageElement = document.querySelector(`[data-message-id="${messageId}"]`);
3768
+
3769
+ // Fallback to index if ID doesn't work
3770
+ if (!messageElement) {
3771
+ messageElement = document.querySelector(`[data-message-index="${messageIndex}"]`);
3772
+ }
3773
+
3774
+ if (messageElement) {
3775
+ // Message is already in DOM, just scroll to it
3776
+ this.scrollToMessage(messageElement);
3777
+ this.highlightCurrentMatchById(messageId);
3778
+ } else {
3779
+ // Message not loaded yet - need to load more messages
3780
+ // This handles the infinite scroll case
3781
+ await this.loadMessageById(messageId, messageIndex);
3782
+ }
3783
+ }
3784
+
3785
+ async loadMessageById(messageId, messageIndex) {
3786
+ // Keep loading more messages until we find the target message by ID
3787
+ let attempts = 0;
3788
+ const maxAttempts = 10; // Prevent infinite loops
3789
+
3790
+ while (attempts < maxAttempts) {
3791
+ // Try to find by ID first
3792
+ let messageElement = document.querySelector(`[data-message-id="${messageId}"]`);
3793
+
3794
+ if (messageElement) {
3795
+ // Found the message!
3796
+ this.scrollToMessage(messageElement);
3797
+ this.highlightCurrentMatchById(messageId);
3798
+ return;
2146
3799
  }
2147
3800
 
2148
- // Mark as user scrolling
2149
- this.isUserScrolling = true;
3801
+ // Try to load more messages
3802
+ if (this.messagesPagination.hasMore && !this.messagesPagination.isLoading) {
3803
+ console.log(`🔄 Loading more messages to find message ${messageId}...`);
3804
+ await this.loadMoreMessages(this.selectedConversationId, false);
3805
+ attempts++;
2150
3806
 
2151
- // Check if user scrolled back to bottom
2152
- if (this.isNearBottom()) {
2153
- this.autoScrollEnabled = true;
2154
- console.log('📱 User scrolled to bottom, re-enabling auto-scroll');
3807
+ // Wait a bit for messages to render
3808
+ await new Promise(resolve => setTimeout(resolve, 500));
2155
3809
  } else {
2156
- this.autoScrollEnabled = false;
3810
+ // No more messages to load or already loading
3811
+ console.warn('Could not find message with ID', messageId, 'after', attempts, 'attempts');
3812
+ break;
2157
3813
  }
3814
+ }
3815
+ }
2158
3816
 
2159
- // Reset user scrolling flag after a delay
2160
- this.userScrollTimeout = setTimeout(() => {
2161
- this.isUserScrolling = false;
2162
- }, 1000);
2163
- };
3817
+ scrollToMessage(messageElement) {
3818
+ if (!messageElement) {
3819
+ console.warn('No message element to scroll to');
3820
+ return;
3821
+ }
2164
3822
 
2165
- chatMessages.addEventListener('scroll', this.scrollListener, { passive: true });
2166
- console.log('📱 Scroll tracking enabled for intelligent auto-scroll');
3823
+ const messagesContainer = document.getElementById('chatMessages');
3824
+ if (!messagesContainer) return;
3825
+
3826
+ // Get the absolute position of the message within the scrollable container
3827
+ const containerTop = messagesContainer.getBoundingClientRect().top;
3828
+ const messageTop = messageElement.getBoundingClientRect().top;
3829
+ const containerHeight = messagesContainer.clientHeight;
3830
+ const messageHeight = messageElement.offsetHeight;
3831
+
3832
+ // Calculate the current scroll position
3833
+ const currentScroll = messagesContainer.scrollTop;
3834
+
3835
+ // Calculate the offset from the top of the container
3836
+ const messageOffsetFromTop = messageTop - containerTop;
3837
+
3838
+ // Calculate target scroll position to center the message
3839
+ // We want the message to be in the middle of the visible area
3840
+ const targetScroll = currentScroll + messageOffsetFromTop - (containerHeight / 2) + (messageHeight / 2);
3841
+
3842
+ // Scroll to the target position
3843
+ messagesContainer.scrollTo({
3844
+ top: Math.max(0, targetScroll), // Don't scroll to negative values
3845
+ behavior: 'smooth'
3846
+ });
3847
+
3848
+ // Add a brief highlight animation to make it more visible
3849
+ messageElement.style.transition = 'background-color 0.3s ease';
3850
+ const originalBg = messageElement.style.backgroundColor;
3851
+ messageElement.style.backgroundColor = 'rgba(255, 107, 53, 0.1)';
3852
+
3853
+ setTimeout(() => {
3854
+ messageElement.style.backgroundColor = originalBg;
3855
+ setTimeout(() => {
3856
+ messageElement.style.transition = '';
3857
+ }, 300);
3858
+ }, 600);
2167
3859
  }
2168
3860
 
2169
- /**
2170
- * Remove scroll tracking
2171
- */
2172
- removeScrollTracking() {
2173
- const chatMessages = document.getElementById('chatMessages');
2174
- if (chatMessages && this.scrollListener) {
2175
- chatMessages.removeEventListener('scroll', this.scrollListener);
2176
- this.scrollListener = null;
3861
+ highlightSearchResults(query) {
3862
+ this.clearSearchHighlights();
3863
+ const searchTerm = query.toLowerCase();
3864
+ const messageElements = document.querySelectorAll('.message-content');
3865
+
3866
+ messageElements.forEach(element => {
3867
+ const text = element.textContent;
3868
+ if (text.toLowerCase().includes(searchTerm)) {
3869
+ const highlightedHTML = this.highlightText(element.innerHTML, query);
3870
+ element.innerHTML = highlightedHTML;
3871
+ }
3872
+ });
3873
+ }
3874
+
3875
+ highlightText(html, searchTerm) {
3876
+ const text = document.createElement('div');
3877
+ text.innerHTML = html;
3878
+ const textContent = text.textContent;
3879
+
3880
+ if (!textContent.toLowerCase().includes(searchTerm.toLowerCase())) {
3881
+ return html;
2177
3882
  }
2178
-
2179
- if (this.userScrollTimeout) {
2180
- clearTimeout(this.userScrollTimeout);
2181
- this.userScrollTimeout = null;
3883
+
3884
+ // Use regex to find all occurrences (case-insensitive)
3885
+ const regex = new RegExp(`(${this.escapeRegex(searchTerm)})`, 'gi');
3886
+ const newHTML = textContent.replace(regex, '<span class="message-highlight">$1</span>');
3887
+
3888
+ return newHTML;
3889
+ }
3890
+
3891
+ highlightCurrentMatch(messageIndex) {
3892
+ // Remove previous current highlight
3893
+ document.querySelectorAll('.message-current-highlight').forEach(el => {
3894
+ el.classList.remove('message-current-highlight');
3895
+ el.classList.add('message-highlight');
3896
+ });
3897
+
3898
+ // Add current highlight to the current match
3899
+ const messageElement = document.querySelector(`[data-message-index="${messageIndex}"]`);
3900
+ if (messageElement) {
3901
+ const firstHighlight = messageElement.querySelector('.message-highlight');
3902
+ if (firstHighlight) {
3903
+ firstHighlight.classList.remove('message-highlight');
3904
+ firstHighlight.classList.add('message-current-highlight');
3905
+
3906
+ // Scroll the highlight into view within the message
3907
+ // This ensures the actual highlighted text is visible
3908
+ setTimeout(() => {
3909
+ firstHighlight.scrollIntoView({
3910
+ behavior: 'smooth',
3911
+ block: 'center',
3912
+ inline: 'nearest'
3913
+ });
3914
+ }, 100);
3915
+ }
2182
3916
  }
2183
3917
  }
2184
3918
 
2185
- /**
2186
- * Escape HTML characters to prevent double-encoding issues
2187
- * @param {string} text - Text to escape
2188
- * @returns {string} Escaped text
2189
- */
2190
- escapeHtml(text) {
2191
- if (typeof text !== 'string') return text;
2192
-
2193
- const div = document.createElement('div');
2194
- div.textContent = text;
2195
- return div.innerHTML;
3919
+ highlightCurrentMatchById(messageId) {
3920
+ // Remove previous current highlight
3921
+ document.querySelectorAll('.message-current-highlight').forEach(el => {
3922
+ el.classList.remove('message-current-highlight');
3923
+ el.classList.add('message-highlight');
3924
+ });
3925
+
3926
+ // Add current highlight to the current match (find by message ID)
3927
+ const messageElement = document.querySelector(`[data-message-id="${messageId}"]`);
3928
+ if (messageElement) {
3929
+ const firstHighlight = messageElement.querySelector('.message-highlight');
3930
+ if (firstHighlight) {
3931
+ firstHighlight.classList.remove('message-highlight');
3932
+ firstHighlight.classList.add('message-current-highlight');
3933
+
3934
+ // Scroll the highlight into view within the message
3935
+ // This ensures the actual highlighted text is visible
3936
+ setTimeout(() => {
3937
+ firstHighlight.scrollIntoView({
3938
+ behavior: 'smooth',
3939
+ block: 'center',
3940
+ inline: 'nearest'
3941
+ });
3942
+ }, 100);
3943
+ }
3944
+ }
3945
+ }
3946
+
3947
+ clearSearchHighlights() {
3948
+ document.querySelectorAll('.message-highlight, .message-current-highlight').forEach(el => {
3949
+ const parent = el.parentNode;
3950
+ parent.replaceChild(document.createTextNode(el.textContent), el);
3951
+ parent.normalize(); // Merge adjacent text nodes
3952
+ });
2196
3953
  }
2197
3954
 
3955
+ updateSearchCounter(currentIndex = null, totalMatches = null, showMinCharsMessage = false) {
3956
+ const counter = document.getElementById('chatSearchCounter');
3957
+ const prevBtn = document.getElementById('chatSearchPrev');
3958
+ const nextBtn = document.getElementById('chatSearchNext');
2198
3959
 
3960
+ // Use provided values or fall back to instance values
3961
+ const current = currentIndex !== null ? currentIndex : this.chatSearchCurrentIndex;
3962
+ const total = totalMatches !== null ? totalMatches : this.chatSearchMatches.length;
2199
3963
 
2200
- filterConversations(searchTerm) {
2201
- const items = document.querySelectorAll('.conversation-item');
2202
- const term = searchTerm.toLowerCase();
2203
-
2204
- items.forEach(item => {
2205
- const name = item.querySelector('.conversation-name').textContent.toLowerCase();
2206
- const preview = item.querySelector('.conversation-preview').textContent.toLowerCase();
2207
- const matches = name.includes(term) || preview.includes(term);
2208
- item.style.display = matches ? 'flex' : 'none';
3964
+ console.log('📊 Updating counter:', {
3965
+ currentIndex: current,
3966
+ totalMatches: total,
3967
+ showMinCharsMessage: showMinCharsMessage
2209
3968
  });
3969
+
3970
+ if (showMinCharsMessage) {
3971
+ counter.textContent = 'Min 3 chars';
3972
+ counter.classList.remove('has-results');
3973
+ prevBtn.disabled = true;
3974
+ nextBtn.disabled = true;
3975
+ } else if (total === 0) {
3976
+ counter.textContent = '0/0';
3977
+ counter.classList.remove('has-results');
3978
+ prevBtn.disabled = true;
3979
+ nextBtn.disabled = true;
3980
+ } else {
3981
+ counter.textContent = `${current + 1}/${total}`;
3982
+ counter.classList.add('has-results');
3983
+ prevBtn.disabled = false;
3984
+ nextBtn.disabled = false;
3985
+ }
3986
+ }
3987
+
3988
+ async getTotalMessageCount(conversationId) {
3989
+ const conversation = this.conversations.find(c => c.id === conversationId);
3990
+ return conversation ? conversation.messageCount : 0;
3991
+ }
3992
+
3993
+ escapeRegex(string) {
3994
+ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
3995
+ }
3996
+
3997
+ resetAdvancedSearch() {
3998
+ // Clear all filter inputs
3999
+ document.getElementById('filterQuery').value = '';
4000
+ document.getElementById('filterWorkingDirectory').value = '';
4001
+ document.getElementById('filterDateFrom').value = '';
4002
+ document.getElementById('filterDateTo').value = '';
4003
+ document.getElementById('filterContentSearch').value = '';
4004
+ }
4005
+
4006
+ async clearAllFilters() {
4007
+ const searchResultsInfo = document.getElementById('searchResultsInfo');
4008
+ const searchInput = document.getElementById('searchInput');
4009
+
4010
+ // Clear simple search
4011
+ searchInput.value = '';
4012
+
4013
+ // Clear advanced search filters
4014
+ this.resetAdvancedSearch();
4015
+
4016
+ // Reset expanded projects state (close all projects)
4017
+ if (this.expandedProjects) {
4018
+ this.expandedProjects.clear();
4019
+ }
4020
+
4021
+ // Hide search results info
4022
+ searchResultsInfo.classList.remove('active');
4023
+
4024
+ // Reload all conversations
4025
+ await this.loadConversations();
2210
4026
  }
2211
4027
 
2212
4028
  getStateLabel(state) {
@@ -2906,6 +4722,199 @@
2906
4722
  }
2907
4723
  }
2908
4724
 
4725
+ // Share conversation functions
4726
+ function shareConversation() {
4727
+ const shareBtn = document.getElementById('shareConversation');
4728
+ const conversationId = shareBtn.getAttribute('data-conversation-id');
4729
+
4730
+ if (!conversationId) {
4731
+ console.error('No conversation ID found');
4732
+ return;
4733
+ }
4734
+
4735
+ console.log('📤 Opening share confirmation for conversation:', conversationId);
4736
+
4737
+ // Show confirmation modal first
4738
+ const confirmModal = document.getElementById('confirmShareModal');
4739
+ confirmModal.classList.add('show');
4740
+
4741
+ // Close modal when clicking outside
4742
+ confirmModal.addEventListener('click', (e) => {
4743
+ if (e.target === confirmModal) {
4744
+ closeConfirmShareModal();
4745
+ }
4746
+ });
4747
+ }
4748
+
4749
+ function closeConfirmShareModal() {
4750
+ const confirmModal = document.getElementById('confirmShareModal');
4751
+ confirmModal.classList.remove('show');
4752
+ }
4753
+
4754
+ async function proceedWithShare() {
4755
+ // Close confirmation modal
4756
+ closeConfirmShareModal();
4757
+
4758
+ const shareBtn = document.getElementById('shareConversation');
4759
+ const conversationId = shareBtn.getAttribute('data-conversation-id');
4760
+
4761
+ if (!conversationId) {
4762
+ console.error('No conversation ID found');
4763
+ return;
4764
+ }
4765
+
4766
+ console.log('📤 User confirmed - Proceeding with share for conversation:', conversationId);
4767
+
4768
+ // Show share modal with loading state
4769
+ const modal = document.getElementById('shareModal');
4770
+ const loadingDiv = document.getElementById('shareModalLoading');
4771
+ const contentDiv = document.getElementById('shareModalContent');
4772
+ const copyBtn = document.getElementById('copyShareCommandBtn');
4773
+
4774
+ modal.classList.add('show');
4775
+ loadingDiv.style.display = 'block';
4776
+ contentDiv.style.display = 'none';
4777
+ copyBtn.style.display = 'none';
4778
+
4779
+ // Close modal when clicking outside
4780
+ modal.addEventListener('click', (e) => {
4781
+ if (e.target === modal) {
4782
+ closeShareModal();
4783
+ }
4784
+ });
4785
+
4786
+ try {
4787
+ // Call API to share the conversation
4788
+ const response = await fetch(`/api/conversations/${conversationId}/share`, {
4789
+ method: 'POST',
4790
+ headers: {
4791
+ 'Content-Type': 'application/json'
4792
+ }
4793
+ });
4794
+
4795
+ if (!response.ok) {
4796
+ throw new Error(`Failed to share session: ${response.statusText}`);
4797
+ }
4798
+
4799
+ const data = await response.json();
4800
+
4801
+ console.log('✅ Session shared successfully:', data);
4802
+
4803
+ // Update modal with share data
4804
+ const qrCodeImg = document.getElementById('shareQRCode');
4805
+ const commandDiv = document.getElementById('shareModalCommand');
4806
+ const directUrlLink = document.getElementById('shareDirectUrl');
4807
+
4808
+ // Set QR code
4809
+ if (data.qrCode && data.qrCode.dataUrl) {
4810
+ qrCodeImg.src = data.qrCode.dataUrl;
4811
+ qrCodeImg.style.display = 'block';
4812
+ } else {
4813
+ qrCodeImg.style.display = 'none';
4814
+ }
4815
+
4816
+ // Set command
4817
+ commandDiv.textContent = data.shareCommand;
4818
+ commandDiv.setAttribute('data-command', data.shareCommand);
4819
+
4820
+ // Set direct URL
4821
+ directUrlLink.textContent = data.uploadUrl;
4822
+ directUrlLink.href = data.uploadUrl;
4823
+
4824
+ // Show message count information
4825
+ const messageInfoDiv = document.getElementById('shareMessageInfo');
4826
+ const messageCountSpan = document.getElementById('shareMessageCount');
4827
+
4828
+ if (data.wasLimited) {
4829
+ messageCountSpan.innerHTML = `⚠️ This session has <strong>${data.totalMessageCount}</strong> messages. Sharing last <strong>${data.messageCount}</strong> messages to keep file size manageable.`;
4830
+ messageInfoDiv.style.display = 'block';
4831
+ } else {
4832
+ messageCountSpan.innerHTML = `✅ Sharing <strong>${data.messageCount}</strong> messages from this conversation.`;
4833
+ messageInfoDiv.style.display = 'block';
4834
+ }
4835
+
4836
+ // Show content and hide loading
4837
+ loadingDiv.style.display = 'none';
4838
+ contentDiv.style.display = 'block';
4839
+ copyBtn.style.display = 'block';
4840
+
4841
+ } catch (error) {
4842
+ console.error('❌ Failed to share session:', error);
4843
+ alert(`Failed to share session: ${error.message}`);
4844
+ closeShareModal();
4845
+ }
4846
+ }
4847
+
4848
+ function closeShareModal() {
4849
+ const modal = document.getElementById('shareModal');
4850
+ modal.classList.remove('show');
4851
+ }
4852
+
4853
+ function copyShareCommand() {
4854
+ const commandDiv = document.getElementById('shareModalCommand');
4855
+ const copyBtn = document.getElementById('copyShareCommandBtn');
4856
+ const command = commandDiv.getAttribute('data-command');
4857
+
4858
+ if (!command) {
4859
+ console.error('No command found to copy');
4860
+ return;
4861
+ }
4862
+
4863
+ if (navigator.clipboard && navigator.clipboard.writeText) {
4864
+ navigator.clipboard.writeText(command).then(() => {
4865
+ // Show success feedback
4866
+ const originalText = copyBtn.textContent;
4867
+ copyBtn.textContent = '✅ Copied!';
4868
+ copyBtn.style.backgroundColor = 'rgba(63, 185, 80, 0.8)';
4869
+ copyBtn.style.borderColor = 'rgba(63, 185, 80, 1)';
4870
+
4871
+ setTimeout(() => {
4872
+ copyBtn.textContent = originalText;
4873
+ copyBtn.style.backgroundColor = '';
4874
+ copyBtn.style.borderColor = '';
4875
+ }, 1500);
4876
+
4877
+ console.log('📋 Share command copied to clipboard:', command);
4878
+ }).catch(err => {
4879
+ console.error('Failed to copy to clipboard:', err);
4880
+ fallbackCopyShare(command);
4881
+ });
4882
+ } else {
4883
+ // Fallback for browsers without clipboard API
4884
+ fallbackCopyShare(command);
4885
+ }
4886
+ }
4887
+
4888
+ function fallbackCopyShare(command) {
4889
+ // Create a temporary text area to select and copy
4890
+ const tempTextArea = document.createElement('textarea');
4891
+ tempTextArea.value = command;
4892
+ tempTextArea.style.position = 'fixed';
4893
+ tempTextArea.style.left = '-9999px';
4894
+ document.body.appendChild(tempTextArea);
4895
+ tempTextArea.select();
4896
+
4897
+ try {
4898
+ document.execCommand('copy');
4899
+ const copyBtn = document.getElementById('copyShareCommandBtn');
4900
+ const originalText = copyBtn.textContent;
4901
+ copyBtn.textContent = '✅ Copied!';
4902
+ copyBtn.style.backgroundColor = 'rgba(63, 185, 80, 0.8)';
4903
+
4904
+ setTimeout(() => {
4905
+ copyBtn.textContent = originalText;
4906
+ copyBtn.style.backgroundColor = '';
4907
+ }, 1500);
4908
+
4909
+ console.log('📋 Share command copied using fallback method:', command);
4910
+ } catch (err) {
4911
+ console.error('Fallback copy failed:', err);
4912
+ alert(`Please copy this command manually:\n\n${command}`);
4913
+ } finally {
4914
+ document.body.removeChild(tempTextArea);
4915
+ }
4916
+ }
4917
+
2909
4918
  // Initialize the app
2910
4919
  document.addEventListener('DOMContentLoaded', () => {
2911
4920
  new ChatsMobileApp();