claude-code-templates 1.24.16 → 1.24.17

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,554 @@
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
+ /* Search toggle button in header */
708
+ .search-toggle-btn {
709
+ background: var(--bg-tertiary);
710
+ border: 1px solid var(--border-primary);
711
+ color: var(--terminal-orange);
712
+ padding: 6px 12px;
713
+ border-radius: 6px;
714
+ cursor: pointer;
715
+ font-size: 0.875rem;
716
+ transition: all 0.2s ease;
717
+ display: flex;
718
+ align-items: center;
719
+ gap: 6px;
720
+ }
721
+
722
+ .search-toggle-btn:hover {
723
+ background: var(--terminal-orange);
724
+ color: white;
725
+ border-color: var(--terminal-orange);
726
+ }
727
+
728
+ .search-toggle-btn.active {
729
+ background: var(--terminal-orange);
730
+ color: white;
731
+ border-color: var(--terminal-orange);
732
+ }
733
+
186
734
  /* Conversations list */
187
735
  .conversations-list {
188
736
  flex: 1;
@@ -1195,12 +1743,30 @@
1195
1743
 
1196
1744
  <!-- Search -->
1197
1745
  <div class="chat-search">
1198
- <input
1199
- type="text"
1200
- class="search-input"
1201
- placeholder="Search conversations..."
1202
- id="searchInput"
1203
- />
1746
+ <div style="display: flex; align-items: center;">
1747
+ <input
1748
+ type="text"
1749
+ class="search-input"
1750
+ placeholder="Search conversations..."
1751
+ id="searchInput"
1752
+ style="flex: 1;"
1753
+ />
1754
+ <button class="advanced-search-toggle" id="advancedSearchToggle">
1755
+ <span>🔍</span>
1756
+ <span>Advanced</span>
1757
+ </button>
1758
+ </div>
1759
+
1760
+ <!-- Search Results Info -->
1761
+ <div class="search-results-info" id="searchResultsInfo">
1762
+ <div style="display: flex; flex-direction: column; gap: 8px;">
1763
+ <div style="display: flex; justify-content: space-between; align-items: center;">
1764
+ <span>Found <span class="search-results-count" id="searchResultsCount">0</span> conversations</span>
1765
+ <button class="clear-filters-btn" id="clearFiltersBtn">Clear filters</button>
1766
+ </div>
1767
+ <div id="appliedFilters" style="display: flex; flex-wrap: wrap; gap: 6px; margin-top: 4px;"></div>
1768
+ </div>
1769
+ </div>
1204
1770
  </div>
1205
1771
 
1206
1772
  <!-- Conversations List -->
@@ -1209,6 +1775,127 @@
1209
1775
  </div>
1210
1776
  </div>
1211
1777
 
1778
+ <!-- Advanced Search Panel Overlay -->
1779
+ <div class="search-panel-overlay" id="searchPanelOverlay">
1780
+ <div class="search-panel">
1781
+ <!-- Panel Header -->
1782
+ <div class="search-panel-header">
1783
+ <h3 class="search-panel-title">
1784
+ <span class="search-filter-icon">🔍</span>
1785
+ Advanced Search
1786
+ </h3>
1787
+ <button class="search-panel-close" id="searchPanelClose">✕</button>
1788
+ </div>
1789
+
1790
+ <!-- Panel Body -->
1791
+ <div class="search-panel-body">
1792
+ <!-- Quick Search -->
1793
+ <div class="search-filter-group">
1794
+ <label class="search-filter-label">
1795
+ <span class="search-filter-icon">💬</span>
1796
+ Quick Search
1797
+ </label>
1798
+ <input
1799
+ type="text"
1800
+ class="search-filter-input"
1801
+ placeholder="Search by conversation ID or filename..."
1802
+ id="filterQuery"
1803
+ />
1804
+ </div>
1805
+
1806
+ <!-- Working Directory Filter -->
1807
+ <div class="search-filter-group">
1808
+ <label class="search-filter-label">
1809
+ <span class="search-filter-icon">📁</span>
1810
+ Working Directory
1811
+ </label>
1812
+ <div class="folder-browser-wrapper">
1813
+ <input
1814
+ type="text"
1815
+ class="search-filter-input"
1816
+ placeholder="e.g., /Users/name/Projects/MyProject"
1817
+ id="filterWorkingDirectory"
1818
+ style="padding-right: 100px;"
1819
+ />
1820
+ <button class="folder-browser-toggle" id="folderBrowserToggle">
1821
+ 📂 Browse
1822
+ </button>
1823
+
1824
+ <!-- Folder Browser Dropdown -->
1825
+ <div class="folder-browser-dropdown" id="folderBrowserDropdown">
1826
+ <div class="folder-browser-header">
1827
+ <input
1828
+ type="text"
1829
+ class="folder-browser-search"
1830
+ placeholder="Filter directories..."
1831
+ id="folderBrowserSearch"
1832
+ />
1833
+ </div>
1834
+ <div class="folder-browser-list" id="folderBrowserList">
1835
+ <div class="folder-browser-empty">
1836
+ Loading directories...
1837
+ </div>
1838
+ </div>
1839
+ </div>
1840
+ </div>
1841
+ </div>
1842
+
1843
+ <!-- Date Range Filter -->
1844
+ <div class="search-filter-group">
1845
+ <label class="search-filter-label">
1846
+ <span class="search-filter-icon">📅</span>
1847
+ Date Range
1848
+ </label>
1849
+ <div class="search-filter-date-range">
1850
+ <div class="search-filter-date-wrapper">
1851
+ <span class="search-filter-date-label">From</span>
1852
+ <input
1853
+ type="date"
1854
+ class="search-filter-input"
1855
+ id="filterDateFrom"
1856
+ />
1857
+ </div>
1858
+ <div class="search-filter-date-wrapper">
1859
+ <span class="search-filter-date-label">To</span>
1860
+ <input
1861
+ type="date"
1862
+ class="search-filter-input"
1863
+ id="filterDateTo"
1864
+ />
1865
+ </div>
1866
+ </div>
1867
+ </div>
1868
+
1869
+ <!-- Content Search -->
1870
+ <div class="search-filter-group">
1871
+ <label class="search-filter-label">
1872
+ <span class="search-filter-icon">🔎</span>
1873
+ Search in Messages
1874
+ </label>
1875
+ <input
1876
+ type="text"
1877
+ class="search-filter-input"
1878
+ placeholder="Search within conversation content..."
1879
+ id="filterContentSearch"
1880
+ />
1881
+ <small style="color: var(--text-secondary); font-size: 0.75rem; margin-top: 4px; display: block;">
1882
+ ⚠️ This may take longer for large conversations
1883
+ </small>
1884
+ </div>
1885
+ </div>
1886
+
1887
+ <!-- Panel Actions -->
1888
+ <div class="search-panel-actions">
1889
+ <button class="search-btn search-btn-secondary" id="searchPanelReset">
1890
+ Reset
1891
+ </button>
1892
+ <button class="search-btn search-btn-primary" id="searchPanelApply">
1893
+ Search
1894
+ </button>
1895
+ </div>
1896
+ </div>
1897
+ </div>
1898
+
1212
1899
  <!-- Chat View -->
1213
1900
  <div class="chat-view" id="chatView">
1214
1901
  <div class="chat-view-header">
@@ -1227,6 +1914,10 @@
1227
1914
  </button>
1228
1915
  </div>
1229
1916
  <div class="header-right">
1917
+ <button class="search-toggle-btn" id="chatSearchToggle">
1918
+ <span>🔍</span>
1919
+ <span>Search</span>
1920
+ </button>
1230
1921
  <div class="tools-toggle" id="toolsToggle">
1231
1922
  <span class="tools-toggle-label" onclick="document.getElementById('showToolsSwitch').click()">Show Tools</span>
1232
1923
  <label class="toggle-switch">
@@ -1236,6 +1927,31 @@
1236
1927
  </div>
1237
1928
  </div>
1238
1929
  </div>
1930
+
1931
+ <!-- In-Conversation Search Bar -->
1932
+ <div class="chat-search-bar" id="chatSearchBar">
1933
+ <div class="chat-search-input-wrapper">
1934
+ <input
1935
+ type="text"
1936
+ class="chat-search-input"
1937
+ placeholder="Search in this conversation..."
1938
+ id="chatSearchInput"
1939
+ />
1940
+ </div>
1941
+ <div class="chat-search-controls">
1942
+ <span class="chat-search-counter" id="chatSearchCounter">0/0</span>
1943
+ <button class="chat-search-nav-btn" id="chatSearchPrev" title="Previous match (↑)">
1944
+
1945
+ </button>
1946
+ <button class="chat-search-nav-btn" id="chatSearchNext" title="Next match (↓)">
1947
+
1948
+ </button>
1949
+ <button class="chat-search-close-btn" id="chatSearchClose">
1950
+
1951
+ </button>
1952
+ </div>
1953
+ </div>
1954
+
1239
1955
  <div class="chat-messages" id="chatMessages">
1240
1956
  <div class="no-conversations">
1241
1957
  <div class="no-conversations-icon">💬</div>
@@ -1340,6 +2056,109 @@
1340
2056
  this.filterConversations(e.target.value);
1341
2057
  });
1342
2058
 
2059
+ // Advanced search toggle
2060
+ document.getElementById('advancedSearchToggle').addEventListener('click', () => {
2061
+ this.openAdvancedSearch();
2062
+ });
2063
+
2064
+ // Advanced search panel close
2065
+ document.getElementById('searchPanelClose').addEventListener('click', () => {
2066
+ this.closeAdvancedSearch();
2067
+ });
2068
+
2069
+ // Close panel on overlay click
2070
+ document.getElementById('searchPanelOverlay').addEventListener('click', (e) => {
2071
+ if (e.target.id === 'searchPanelOverlay') {
2072
+ this.closeAdvancedSearch();
2073
+ }
2074
+ });
2075
+
2076
+ // Advanced search apply
2077
+ document.getElementById('searchPanelApply').addEventListener('click', () => {
2078
+ this.applyAdvancedSearch();
2079
+ });
2080
+
2081
+ // Advanced search reset
2082
+ document.getElementById('searchPanelReset').addEventListener('click', () => {
2083
+ this.resetAdvancedSearch();
2084
+ });
2085
+
2086
+ // Clear filters button
2087
+ document.getElementById('clearFiltersBtn').addEventListener('click', () => {
2088
+ this.clearAllFilters();
2089
+ });
2090
+
2091
+ // Folder browser toggle
2092
+ document.getElementById('folderBrowserToggle').addEventListener('click', (e) => {
2093
+ e.preventDefault();
2094
+ e.stopPropagation();
2095
+ this.toggleFolderBrowser();
2096
+ });
2097
+
2098
+ // Folder browser search
2099
+ document.getElementById('folderBrowserSearch').addEventListener('input', (e) => {
2100
+ this.filterFolderBrowserList(e.target.value);
2101
+ });
2102
+
2103
+ // Close folder browser when clicking outside
2104
+ document.addEventListener('click', (e) => {
2105
+ const dropdown = document.getElementById('folderBrowserDropdown');
2106
+ const toggle = document.getElementById('folderBrowserToggle');
2107
+ const wrapper = document.querySelector('.folder-browser-wrapper');
2108
+
2109
+ if (dropdown.classList.contains('active') &&
2110
+ !wrapper.contains(e.target)) {
2111
+ dropdown.classList.remove('active');
2112
+ }
2113
+ });
2114
+
2115
+ // In-conversation search toggle
2116
+ document.getElementById('chatSearchToggle').addEventListener('click', () => {
2117
+ this.toggleChatSearch();
2118
+ });
2119
+
2120
+ // In-conversation search input (only search with 3+ characters)
2121
+ document.getElementById('chatSearchInput').addEventListener('input', (e) => {
2122
+ const query = e.target.value.trim();
2123
+ if (query.length >= 3) {
2124
+ this.performChatSearch(query);
2125
+ } else if (query.length === 0) {
2126
+ // Clear search when input is empty
2127
+ this.clearChatSearch();
2128
+ } else {
2129
+ // Show message that minimum 3 characters are required
2130
+ this.updateSearchCounter(0, 0, true);
2131
+ }
2132
+ });
2133
+
2134
+ // In-conversation search navigation
2135
+ document.getElementById('chatSearchPrev').addEventListener('click', () => {
2136
+ this.navigateSearchResults('prev');
2137
+ });
2138
+
2139
+ document.getElementById('chatSearchNext').addEventListener('click', () => {
2140
+ this.navigateSearchResults('next');
2141
+ });
2142
+
2143
+ // In-conversation search close
2144
+ document.getElementById('chatSearchClose').addEventListener('click', () => {
2145
+ this.closeChatSearch();
2146
+ });
2147
+
2148
+ // Keyboard shortcuts for search
2149
+ document.getElementById('chatSearchInput').addEventListener('keydown', (e) => {
2150
+ if (e.key === 'Enter') {
2151
+ e.preventDefault();
2152
+ if (e.shiftKey) {
2153
+ this.navigateSearchResults('prev');
2154
+ } else {
2155
+ this.navigateSearchResults('next');
2156
+ }
2157
+ } else if (e.key === 'Escape') {
2158
+ this.closeChatSearch();
2159
+ }
2160
+ });
2161
+
1343
2162
  // Show Tools toggle functionality
1344
2163
  const showToolsSwitch = document.getElementById('showToolsSwitch');
1345
2164
  showToolsSwitch.addEventListener('change', (e) => {
@@ -1717,22 +2536,26 @@
1717
2536
  const scrollHeight = chatMessages.scrollHeight;
1718
2537
 
1719
2538
  // Prepend new messages
1720
- const newMessagesHTML = messages.map(msg => this.renderMessage(msg)).join('');
2539
+ // Calculate message indices (considering we're prepending older messages)
2540
+ const startIndex = this.messagesPagination.currentPage * this.messagesPagination.limit;
2541
+ const newMessagesHTML = messages.map((msg, idx) =>
2542
+ this.renderMessage(msg, startIndex + idx)
2543
+ ).join('');
1721
2544
  existingMessagesDiv.innerHTML = newMessagesHTML + existingMessagesDiv.innerHTML;
1722
-
2545
+
1723
2546
  // Restore scroll position (account for new content)
1724
2547
  const newScrollHeight = chatMessages.scrollHeight;
1725
2548
  const scrollDiff = newScrollHeight - scrollHeight;
1726
2549
  chatMessages.scrollTop = scrollTop + scrollDiff;
1727
-
2550
+
1728
2551
  } else {
1729
2552
  // Normal render (initial load or replace all)
1730
2553
  const messageHTML = `
1731
2554
  <div class="messages-list">
1732
- ${messages.map(msg => this.renderMessage(msg)).join('')}
2555
+ ${messages.map((msg, idx) => this.renderMessage(msg, idx)).join('')}
1733
2556
  </div>
1734
2557
  `;
1735
-
2558
+
1736
2559
  chatMessages.innerHTML = messageHTML;
1737
2560
  }
1738
2561
  }
@@ -1800,17 +2623,17 @@
1800
2623
  chatMessages.addEventListener('scroll', this.messagesScrollListener);
1801
2624
  }
1802
2625
 
1803
- renderMessage(message) {
2626
+ renderMessage(message, messageIndex) {
1804
2627
  const timestamp = this.formatRelativeTime(new Date(message.timestamp));
1805
2628
  const fullTimestamp = new Date(message.timestamp).toLocaleString();
1806
2629
  const isUser = message.role === 'user' && !message.isCompactSummary;
1807
-
2630
+
1808
2631
  // Detect if message contains tools (either in content or as correlated toolResults)
1809
- const hasToolsInContent = Array.isArray(message.content) &&
2632
+ const hasToolsInContent = Array.isArray(message.content) &&
1810
2633
  message.content.some(block => block.type === 'tool_use');
1811
2634
  const hasCorrelatedTools = message.toolResults && message.toolResults.length > 0;
1812
2635
  const hasTools = hasToolsInContent || hasCorrelatedTools;
1813
-
2636
+
1814
2637
  // Debug logging for tool detection
1815
2638
  if (hasTools) {
1816
2639
  console.log('🔧 Rendering message with tools', {
@@ -1823,16 +2646,19 @@
1823
2646
  willHaveHasToolsClass: !isUser && hasTools
1824
2647
  });
1825
2648
  }
1826
-
1827
- const toolCount = hasToolsInContent ?
2649
+
2650
+ const toolCount = hasToolsInContent ?
1828
2651
  message.content.filter(block => block.type === 'tool_use').length :
1829
2652
  (hasCorrelatedTools ? message.toolResults.length : 0);
1830
-
2653
+
1831
2654
  // Add has-tools class to assistant messages that contain tools
1832
2655
  const hasToolsClass = (!isUser && hasTools) ? ' has-tools' : '';
1833
-
2656
+
2657
+ // Add message index for search functionality
2658
+ const messageIndexAttr = messageIndex !== undefined ? ` data-message-index="${messageIndex}"` : '';
2659
+
1834
2660
  return `
1835
- <div class="message message-${isUser ? 'user' : 'assistant'}${hasToolsClass}" data-message-id="${message.id || ''}">
2661
+ <div class="message message-${isUser ? 'user' : 'assistant'}${hasToolsClass}" data-message-id="${message.id || ''}"${messageIndexAttr}>
1836
2662
  <div class="message-bubble">
1837
2663
  <div class="message-content">
1838
2664
  ${this.formatMessageContent(message.content, message)}
@@ -2200,7 +3026,7 @@
2200
3026
  filterConversations(searchTerm) {
2201
3027
  const items = document.querySelectorAll('.conversation-item');
2202
3028
  const term = searchTerm.toLowerCase();
2203
-
3029
+
2204
3030
  items.forEach(item => {
2205
3031
  const name = item.querySelector('.conversation-name').textContent.toLowerCase();
2206
3032
  const preview = item.querySelector('.conversation-preview').textContent.toLowerCase();
@@ -2209,6 +3035,625 @@
2209
3035
  });
2210
3036
  }
2211
3037
 
3038
+ // Advanced Search Methods
3039
+ openAdvancedSearch() {
3040
+ const overlay = document.getElementById('searchPanelOverlay');
3041
+ overlay.classList.add('active');
3042
+ document.body.style.overflow = 'hidden'; // Prevent background scrolling
3043
+ }
3044
+
3045
+ closeAdvancedSearch() {
3046
+ const overlay = document.getElementById('searchPanelOverlay');
3047
+ overlay.classList.remove('active');
3048
+ document.body.style.overflow = ''; // Restore scrolling
3049
+ }
3050
+
3051
+ async applyAdvancedSearch() {
3052
+ const conversationsList = document.getElementById('conversationsList');
3053
+ const searchResultsInfo = document.getElementById('searchResultsInfo');
3054
+ const searchResultsCount = document.getElementById('searchResultsCount');
3055
+ const appliedFiltersContainer = document.getElementById('appliedFilters');
3056
+
3057
+ try {
3058
+ // Show loading state
3059
+ conversationsList.innerHTML = '<div class="loading-spinner" style="margin: 40px auto;"></div>';
3060
+
3061
+ // Get filter values
3062
+ const filters = {
3063
+ query: document.getElementById('filterQuery').value.trim(),
3064
+ workingDirectory: document.getElementById('filterWorkingDirectory').value.trim(),
3065
+ dateFrom: document.getElementById('filterDateFrom').value,
3066
+ dateTo: document.getElementById('filterDateTo').value,
3067
+ contentSearch: document.getElementById('filterContentSearch').value.trim()
3068
+ };
3069
+
3070
+ // Call the search API
3071
+ const response = await fetch('/api/search', {
3072
+ method: 'POST',
3073
+ headers: {
3074
+ 'Content-Type': 'application/json'
3075
+ },
3076
+ body: JSON.stringify(filters)
3077
+ });
3078
+
3079
+ if (!response.ok) {
3080
+ throw new Error(`Search failed: ${response.status}`);
3081
+ }
3082
+
3083
+ const data = await response.json();
3084
+
3085
+ // Update conversations with search results
3086
+ this.conversations = data.results || [];
3087
+
3088
+ // Get conversation states for the results
3089
+ const statesResponse = await fetch('/api/conversation-state');
3090
+ let states = {};
3091
+ if (statesResponse.ok) {
3092
+ const statesData = await statesResponse.json();
3093
+ states = statesData.activeStates || {};
3094
+ }
3095
+
3096
+ // Render search results
3097
+ this.renderConversations(this.conversations, states);
3098
+
3099
+ // Show search results info with applied filters
3100
+ searchResultsCount.textContent = data.count;
3101
+ this.renderAppliedFilters(filters, appliedFiltersContainer);
3102
+ searchResultsInfo.classList.add('active');
3103
+
3104
+ // Close the search panel
3105
+ this.closeAdvancedSearch();
3106
+
3107
+ console.log('🔍 Search completed:', data.count, 'results found');
3108
+ } catch (error) {
3109
+ console.error('Error performing advanced search:', error);
3110
+ conversationsList.innerHTML = `
3111
+ <div class="no-conversations">
3112
+ <div class="no-conversations-icon">⚠️</div>
3113
+ <h3>Search Error</h3>
3114
+ <p>${error.message}</p>
3115
+ <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>
3116
+ </div>
3117
+ `;
3118
+ }
3119
+ }
3120
+
3121
+ renderAppliedFilters(filters, container) {
3122
+ container.innerHTML = '';
3123
+
3124
+ const filterTags = [];
3125
+
3126
+ // Quick search filter
3127
+ if (filters.query) {
3128
+ filterTags.push({
3129
+ icon: '💬',
3130
+ label: 'Search',
3131
+ value: filters.query
3132
+ });
3133
+ }
3134
+
3135
+ // Working directory filter
3136
+ if (filters.workingDirectory) {
3137
+ // Shorten the path for display
3138
+ const shortPath = filters.workingDirectory.length > 30
3139
+ ? '...' + filters.workingDirectory.slice(-30)
3140
+ : filters.workingDirectory;
3141
+ filterTags.push({
3142
+ icon: '📁',
3143
+ label: 'Directory',
3144
+ value: shortPath
3145
+ });
3146
+ }
3147
+
3148
+ // Date range filter
3149
+ if (filters.dateFrom || filters.dateTo) {
3150
+ let dateValue = '';
3151
+ if (filters.dateFrom && filters.dateTo) {
3152
+ dateValue = `${this.formatDate(filters.dateFrom)} → ${this.formatDate(filters.dateTo)}`;
3153
+ } else if (filters.dateFrom) {
3154
+ dateValue = `From ${this.formatDate(filters.dateFrom)}`;
3155
+ } else if (filters.dateTo) {
3156
+ dateValue = `Until ${this.formatDate(filters.dateTo)}`;
3157
+ }
3158
+ filterTags.push({
3159
+ icon: '📅',
3160
+ label: 'Date',
3161
+ value: dateValue
3162
+ });
3163
+ }
3164
+
3165
+ // Content search filter
3166
+ if (filters.contentSearch) {
3167
+ filterTags.push({
3168
+ icon: '🔎',
3169
+ label: 'Content',
3170
+ value: filters.contentSearch
3171
+ });
3172
+ }
3173
+
3174
+ // Render filter tags
3175
+ filterTags.forEach(tag => {
3176
+ const tagElement = document.createElement('div');
3177
+ tagElement.className = 'filter-tag';
3178
+ tagElement.innerHTML = `
3179
+ <span class="filter-tag-icon">${tag.icon}</span>
3180
+ <span class="filter-tag-label">${tag.label}:</span>
3181
+ <span class="filter-tag-value">${tag.value}</span>
3182
+ `;
3183
+ container.appendChild(tagElement);
3184
+ });
3185
+ }
3186
+
3187
+ formatDate(dateString) {
3188
+ const date = new Date(dateString);
3189
+ const options = { month: 'short', day: 'numeric', year: 'numeric' };
3190
+ return date.toLocaleDateString('en-US', options);
3191
+ }
3192
+
3193
+ // Folder Browser Methods
3194
+ async toggleFolderBrowser() {
3195
+ const dropdown = document.getElementById('folderBrowserDropdown');
3196
+ const isActive = dropdown.classList.contains('active');
3197
+
3198
+ if (isActive) {
3199
+ dropdown.classList.remove('active');
3200
+ } else {
3201
+ // Load directories if not loaded yet
3202
+ if (!this.directories) {
3203
+ await this.loadDirectories();
3204
+ }
3205
+ dropdown.classList.add('active');
3206
+ }
3207
+ }
3208
+
3209
+ async loadDirectories() {
3210
+ const listContainer = document.getElementById('folderBrowserList');
3211
+
3212
+ try {
3213
+ listContainer.innerHTML = '<div class="folder-browser-empty">Loading directories...</div>';
3214
+
3215
+ const response = await fetch('/api/directories');
3216
+ if (!response.ok) {
3217
+ throw new Error('Failed to load directories');
3218
+ }
3219
+
3220
+ const data = await response.json();
3221
+ this.directories = data.directories || [];
3222
+
3223
+ // Count conversations per directory
3224
+ this.directoryCounts = {};
3225
+ this.conversations.forEach(conv => {
3226
+ if (conv.project) {
3227
+ this.directoryCounts[conv.project] = (this.directoryCounts[conv.project] || 0) + 1;
3228
+ }
3229
+ });
3230
+
3231
+ this.renderFolderBrowserList(this.directories);
3232
+ } catch (error) {
3233
+ console.error('Error loading directories:', error);
3234
+ listContainer.innerHTML = '<div class="folder-browser-empty">Error loading directories</div>';
3235
+ }
3236
+ }
3237
+
3238
+ renderFolderBrowserList(directories) {
3239
+ const listContainer = document.getElementById('folderBrowserList');
3240
+ const currentValue = document.getElementById('filterWorkingDirectory').value;
3241
+
3242
+ if (directories.length === 0) {
3243
+ listContainer.innerHTML = '<div class="folder-browser-empty">No directories found</div>';
3244
+ return;
3245
+ }
3246
+
3247
+ listContainer.innerHTML = directories.map(dir => {
3248
+ const count = this.directoryCounts[dir] || 0;
3249
+ const isSelected = dir === currentValue;
3250
+
3251
+ return `
3252
+ <div class="folder-item ${isSelected ? 'selected' : ''}" data-path="${this.escapeHtml(dir)}">
3253
+ <span class="folder-icon">📁</span>
3254
+ <span class="folder-path" title="${this.escapeHtml(dir)}">${this.escapeHtml(dir)}</span>
3255
+ <span class="folder-count">${count}</span>
3256
+ </div>
3257
+ `;
3258
+ }).join('');
3259
+
3260
+ // Add click handlers to folder items
3261
+ listContainer.querySelectorAll('.folder-item').forEach(item => {
3262
+ item.addEventListener('click', () => {
3263
+ const path = item.getAttribute('data-path');
3264
+ this.selectDirectory(path);
3265
+ });
3266
+ });
3267
+ }
3268
+
3269
+ selectDirectory(path) {
3270
+ const input = document.getElementById('filterWorkingDirectory');
3271
+ const dropdown = document.getElementById('folderBrowserDropdown');
3272
+
3273
+ input.value = path;
3274
+ dropdown.classList.remove('active');
3275
+
3276
+ // Update UI to show selected folder
3277
+ this.renderFolderBrowserList(this.directories);
3278
+ }
3279
+
3280
+ filterFolderBrowserList(searchTerm) {
3281
+ if (!this.directories) return;
3282
+
3283
+ const term = searchTerm.toLowerCase();
3284
+ const filtered = this.directories.filter(dir =>
3285
+ dir.toLowerCase().includes(term)
3286
+ );
3287
+
3288
+ this.renderFolderBrowserList(filtered);
3289
+ }
3290
+
3291
+ escapeHtml(text) {
3292
+ const div = document.createElement('div');
3293
+ div.textContent = text;
3294
+ return div.innerHTML;
3295
+ }
3296
+
3297
+ // In-Conversation Search Methods
3298
+ toggleChatSearch() {
3299
+ const searchBar = document.getElementById('chatSearchBar');
3300
+ const toggle = document.getElementById('chatSearchToggle');
3301
+ const isActive = searchBar.classList.contains('active');
3302
+
3303
+ if (isActive) {
3304
+ this.closeChatSearch();
3305
+ } else {
3306
+ searchBar.classList.add('active');
3307
+ toggle.classList.add('active');
3308
+ document.getElementById('chatSearchInput').focus();
3309
+ }
3310
+ }
3311
+
3312
+ closeChatSearch() {
3313
+ const searchBar = document.getElementById('chatSearchBar');
3314
+ const toggle = document.getElementById('chatSearchToggle');
3315
+ const input = document.getElementById('chatSearchInput');
3316
+
3317
+ searchBar.classList.remove('active');
3318
+ toggle.classList.remove('active');
3319
+ input.value = '';
3320
+
3321
+ // Clear search state
3322
+ this.chatSearchMatches = [];
3323
+ this.chatSearchCurrentIndex = -1;
3324
+ this.updateSearchCounter();
3325
+ this.clearSearchHighlights();
3326
+ }
3327
+
3328
+ clearChatSearch() {
3329
+ // Clear search results but keep search bar open
3330
+ this.chatSearchMatches = [];
3331
+ this.chatSearchCurrentIndex = -1;
3332
+ this.updateSearchCounter();
3333
+ this.clearSearchHighlights();
3334
+ }
3335
+
3336
+ async performChatSearch(query) {
3337
+ if (!this.selectedConversationId || !query || query.trim().length === 0) {
3338
+ this.chatSearchMatches = [];
3339
+ this.chatSearchCurrentIndex = -1;
3340
+ this.updateSearchCounter();
3341
+ this.clearSearchHighlights();
3342
+ return;
3343
+ }
3344
+
3345
+ try {
3346
+ const response = await fetch(`/api/conversations/${this.selectedConversationId}/search`, {
3347
+ method: 'POST',
3348
+ headers: {
3349
+ 'Content-Type': 'application/json'
3350
+ },
3351
+ body: JSON.stringify({ query: query.trim() })
3352
+ });
3353
+
3354
+ if (!response.ok) {
3355
+ throw new Error('Search failed');
3356
+ }
3357
+
3358
+ const data = await response.json();
3359
+ this.chatSearchMatches = data.matches || [];
3360
+ this.chatSearchCurrentIndex = this.chatSearchMatches.length > 0 ? 0 : -1;
3361
+
3362
+ console.log('🔍 Search results:', {
3363
+ query: query,
3364
+ totalMatches: this.chatSearchMatches.length,
3365
+ matches: this.chatSearchMatches
3366
+ });
3367
+
3368
+ this.updateSearchCounter();
3369
+ this.highlightSearchResults(query);
3370
+
3371
+ // Navigate to first match
3372
+ if (this.chatSearchMatches.length > 0) {
3373
+ await this.scrollToSearchMatch(0);
3374
+ }
3375
+ } catch (error) {
3376
+ console.error('Error searching in conversation:', error);
3377
+ }
3378
+ }
3379
+
3380
+ async navigateSearchResults(direction) {
3381
+ if (this.chatSearchMatches.length === 0) return;
3382
+
3383
+ if (direction === 'next') {
3384
+ this.chatSearchCurrentIndex = (this.chatSearchCurrentIndex + 1) % this.chatSearchMatches.length;
3385
+ } else {
3386
+ this.chatSearchCurrentIndex = (this.chatSearchCurrentIndex - 1 + this.chatSearchMatches.length) % this.chatSearchMatches.length;
3387
+ }
3388
+
3389
+ this.updateSearchCounter();
3390
+ await this.scrollToSearchMatch(this.chatSearchCurrentIndex);
3391
+ }
3392
+
3393
+ async scrollToSearchMatch(index) {
3394
+ if (index < 0 || index >= this.chatSearchMatches.length) return;
3395
+
3396
+ const match = this.chatSearchMatches[index];
3397
+ const messageId = match.messageId;
3398
+ const messageIndex = match.messageIndex;
3399
+
3400
+ // Try to find the message by ID first (more reliable)
3401
+ let messageElement = document.querySelector(`[data-message-id="${messageId}"]`);
3402
+
3403
+ // Fallback to index if ID doesn't work
3404
+ if (!messageElement) {
3405
+ messageElement = document.querySelector(`[data-message-index="${messageIndex}"]`);
3406
+ }
3407
+
3408
+ if (messageElement) {
3409
+ // Message is already in DOM, just scroll to it
3410
+ this.scrollToMessage(messageElement);
3411
+ this.highlightCurrentMatchById(messageId);
3412
+ } else {
3413
+ // Message not loaded yet - need to load more messages
3414
+ // This handles the infinite scroll case
3415
+ await this.loadMessageById(messageId, messageIndex);
3416
+ }
3417
+ }
3418
+
3419
+ async loadMessageById(messageId, messageIndex) {
3420
+ // Keep loading more messages until we find the target message by ID
3421
+ let attempts = 0;
3422
+ const maxAttempts = 10; // Prevent infinite loops
3423
+
3424
+ while (attempts < maxAttempts) {
3425
+ // Try to find by ID first
3426
+ let messageElement = document.querySelector(`[data-message-id="${messageId}"]`);
3427
+
3428
+ if (messageElement) {
3429
+ // Found the message!
3430
+ this.scrollToMessage(messageElement);
3431
+ this.highlightCurrentMatchById(messageId);
3432
+ return;
3433
+ }
3434
+
3435
+ // Try to load more messages
3436
+ if (this.messagesPagination.hasMore && !this.messagesPagination.isLoading) {
3437
+ console.log(`🔄 Loading more messages to find message ${messageId}...`);
3438
+ await this.loadMoreMessages(this.selectedConversationId, false);
3439
+ attempts++;
3440
+
3441
+ // Wait a bit for messages to render
3442
+ await new Promise(resolve => setTimeout(resolve, 500));
3443
+ } else {
3444
+ // No more messages to load or already loading
3445
+ console.warn('Could not find message with ID', messageId, 'after', attempts, 'attempts');
3446
+ break;
3447
+ }
3448
+ }
3449
+ }
3450
+
3451
+ scrollToMessage(messageElement) {
3452
+ if (!messageElement) {
3453
+ console.warn('No message element to scroll to');
3454
+ return;
3455
+ }
3456
+
3457
+ const messagesContainer = document.getElementById('chatMessages');
3458
+ if (!messagesContainer) return;
3459
+
3460
+ // Get the absolute position of the message within the scrollable container
3461
+ const containerTop = messagesContainer.getBoundingClientRect().top;
3462
+ const messageTop = messageElement.getBoundingClientRect().top;
3463
+ const containerHeight = messagesContainer.clientHeight;
3464
+ const messageHeight = messageElement.offsetHeight;
3465
+
3466
+ // Calculate the current scroll position
3467
+ const currentScroll = messagesContainer.scrollTop;
3468
+
3469
+ // Calculate the offset from the top of the container
3470
+ const messageOffsetFromTop = messageTop - containerTop;
3471
+
3472
+ // Calculate target scroll position to center the message
3473
+ // We want the message to be in the middle of the visible area
3474
+ const targetScroll = currentScroll + messageOffsetFromTop - (containerHeight / 2) + (messageHeight / 2);
3475
+
3476
+ // Scroll to the target position
3477
+ messagesContainer.scrollTo({
3478
+ top: Math.max(0, targetScroll), // Don't scroll to negative values
3479
+ behavior: 'smooth'
3480
+ });
3481
+
3482
+ // Add a brief highlight animation to make it more visible
3483
+ messageElement.style.transition = 'background-color 0.3s ease';
3484
+ const originalBg = messageElement.style.backgroundColor;
3485
+ messageElement.style.backgroundColor = 'rgba(255, 107, 53, 0.1)';
3486
+
3487
+ setTimeout(() => {
3488
+ messageElement.style.backgroundColor = originalBg;
3489
+ setTimeout(() => {
3490
+ messageElement.style.transition = '';
3491
+ }, 300);
3492
+ }, 600);
3493
+ }
3494
+
3495
+ highlightSearchResults(query) {
3496
+ this.clearSearchHighlights();
3497
+ const searchTerm = query.toLowerCase();
3498
+ const messageElements = document.querySelectorAll('.message-content');
3499
+
3500
+ messageElements.forEach(element => {
3501
+ const text = element.textContent;
3502
+ if (text.toLowerCase().includes(searchTerm)) {
3503
+ const highlightedHTML = this.highlightText(element.innerHTML, query);
3504
+ element.innerHTML = highlightedHTML;
3505
+ }
3506
+ });
3507
+ }
3508
+
3509
+ highlightText(html, searchTerm) {
3510
+ const text = document.createElement('div');
3511
+ text.innerHTML = html;
3512
+ const textContent = text.textContent;
3513
+
3514
+ if (!textContent.toLowerCase().includes(searchTerm.toLowerCase())) {
3515
+ return html;
3516
+ }
3517
+
3518
+ // Use regex to find all occurrences (case-insensitive)
3519
+ const regex = new RegExp(`(${this.escapeRegex(searchTerm)})`, 'gi');
3520
+ const newHTML = textContent.replace(regex, '<span class="message-highlight">$1</span>');
3521
+
3522
+ return newHTML;
3523
+ }
3524
+
3525
+ highlightCurrentMatch(messageIndex) {
3526
+ // Remove previous current highlight
3527
+ document.querySelectorAll('.message-current-highlight').forEach(el => {
3528
+ el.classList.remove('message-current-highlight');
3529
+ el.classList.add('message-highlight');
3530
+ });
3531
+
3532
+ // Add current highlight to the current match
3533
+ const messageElement = document.querySelector(`[data-message-index="${messageIndex}"]`);
3534
+ if (messageElement) {
3535
+ const firstHighlight = messageElement.querySelector('.message-highlight');
3536
+ if (firstHighlight) {
3537
+ firstHighlight.classList.remove('message-highlight');
3538
+ firstHighlight.classList.add('message-current-highlight');
3539
+
3540
+ // Scroll the highlight into view within the message
3541
+ // This ensures the actual highlighted text is visible
3542
+ setTimeout(() => {
3543
+ firstHighlight.scrollIntoView({
3544
+ behavior: 'smooth',
3545
+ block: 'center',
3546
+ inline: 'nearest'
3547
+ });
3548
+ }, 100);
3549
+ }
3550
+ }
3551
+ }
3552
+
3553
+ highlightCurrentMatchById(messageId) {
3554
+ // Remove previous current highlight
3555
+ document.querySelectorAll('.message-current-highlight').forEach(el => {
3556
+ el.classList.remove('message-current-highlight');
3557
+ el.classList.add('message-highlight');
3558
+ });
3559
+
3560
+ // Add current highlight to the current match (find by message ID)
3561
+ const messageElement = document.querySelector(`[data-message-id="${messageId}"]`);
3562
+ if (messageElement) {
3563
+ const firstHighlight = messageElement.querySelector('.message-highlight');
3564
+ if (firstHighlight) {
3565
+ firstHighlight.classList.remove('message-highlight');
3566
+ firstHighlight.classList.add('message-current-highlight');
3567
+
3568
+ // Scroll the highlight into view within the message
3569
+ // This ensures the actual highlighted text is visible
3570
+ setTimeout(() => {
3571
+ firstHighlight.scrollIntoView({
3572
+ behavior: 'smooth',
3573
+ block: 'center',
3574
+ inline: 'nearest'
3575
+ });
3576
+ }, 100);
3577
+ }
3578
+ }
3579
+ }
3580
+
3581
+ clearSearchHighlights() {
3582
+ document.querySelectorAll('.message-highlight, .message-current-highlight').forEach(el => {
3583
+ const parent = el.parentNode;
3584
+ parent.replaceChild(document.createTextNode(el.textContent), el);
3585
+ parent.normalize(); // Merge adjacent text nodes
3586
+ });
3587
+ }
3588
+
3589
+ updateSearchCounter(currentIndex = null, totalMatches = null, showMinCharsMessage = false) {
3590
+ const counter = document.getElementById('chatSearchCounter');
3591
+ const prevBtn = document.getElementById('chatSearchPrev');
3592
+ const nextBtn = document.getElementById('chatSearchNext');
3593
+
3594
+ // Use provided values or fall back to instance values
3595
+ const current = currentIndex !== null ? currentIndex : this.chatSearchCurrentIndex;
3596
+ const total = totalMatches !== null ? totalMatches : this.chatSearchMatches.length;
3597
+
3598
+ console.log('📊 Updating counter:', {
3599
+ currentIndex: current,
3600
+ totalMatches: total,
3601
+ showMinCharsMessage: showMinCharsMessage
3602
+ });
3603
+
3604
+ if (showMinCharsMessage) {
3605
+ counter.textContent = 'Min 3 chars';
3606
+ counter.classList.remove('has-results');
3607
+ prevBtn.disabled = true;
3608
+ nextBtn.disabled = true;
3609
+ } else if (total === 0) {
3610
+ counter.textContent = '0/0';
3611
+ counter.classList.remove('has-results');
3612
+ prevBtn.disabled = true;
3613
+ nextBtn.disabled = true;
3614
+ } else {
3615
+ counter.textContent = `${current + 1}/${total}`;
3616
+ counter.classList.add('has-results');
3617
+ prevBtn.disabled = false;
3618
+ nextBtn.disabled = false;
3619
+ }
3620
+ }
3621
+
3622
+ async getTotalMessageCount(conversationId) {
3623
+ const conversation = this.conversations.find(c => c.id === conversationId);
3624
+ return conversation ? conversation.messageCount : 0;
3625
+ }
3626
+
3627
+ escapeRegex(string) {
3628
+ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
3629
+ }
3630
+
3631
+ resetAdvancedSearch() {
3632
+ // Clear all filter inputs
3633
+ document.getElementById('filterQuery').value = '';
3634
+ document.getElementById('filterWorkingDirectory').value = '';
3635
+ document.getElementById('filterDateFrom').value = '';
3636
+ document.getElementById('filterDateTo').value = '';
3637
+ document.getElementById('filterContentSearch').value = '';
3638
+ }
3639
+
3640
+ async clearAllFilters() {
3641
+ const searchResultsInfo = document.getElementById('searchResultsInfo');
3642
+ const searchInput = document.getElementById('searchInput');
3643
+
3644
+ // Clear simple search
3645
+ searchInput.value = '';
3646
+
3647
+ // Clear advanced search filters
3648
+ this.resetAdvancedSearch();
3649
+
3650
+ // Hide search results info
3651
+ searchResultsInfo.classList.remove('active');
3652
+
3653
+ // Reload all conversations
3654
+ await this.loadConversations();
3655
+ }
3656
+
2212
3657
  getStateLabel(state) {
2213
3658
  // Handle all possible states from StateCalculator with icons
2214
3659
  const stateLabels = {