bluera-knowledge 0.37.0 → 0.37.1
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.
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +18 -0
- package/dist/{chunk-AO45YFHO.js → chunk-VB5V4RC7.js} +2 -1
- package/dist/index.js +2151 -15
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.js +1 -1
- package/package.json +1 -1
- package/scripts/launch-ui.sh +20 -0
- package/scripts/preview-ui.ts +207 -0
- package/skills/ui/SKILL.md +27 -0
- /package/dist/{chunk-AO45YFHO.js.map → chunk-VB5V4RC7.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
BASE_STYLES,
|
|
3
4
|
ZilAdapter,
|
|
4
5
|
runMCPServer,
|
|
5
6
|
spawnBackgroundWorker
|
|
6
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-VB5V4RC7.js";
|
|
7
8
|
import {
|
|
8
9
|
IntelligentCrawler,
|
|
9
10
|
getCrawlStrategy
|
|
@@ -1371,6 +1372,8 @@ Search: "${query}"`);
|
|
|
1371
1372
|
}
|
|
1372
1373
|
|
|
1373
1374
|
// src/cli/commands/serve.ts
|
|
1375
|
+
import { exec } from "child_process";
|
|
1376
|
+
import { platform } from "os";
|
|
1374
1377
|
import { serve } from "@hono/node-server";
|
|
1375
1378
|
import { Command as Command6 } from "commander";
|
|
1376
1379
|
|
|
@@ -1380,6 +1383,2070 @@ import { join as join2 } from "path";
|
|
|
1380
1383
|
import { Hono } from "hono";
|
|
1381
1384
|
import { cors } from "hono/cors";
|
|
1382
1385
|
import { z } from "zod";
|
|
1386
|
+
|
|
1387
|
+
// src/server/ui/admin.ts
|
|
1388
|
+
var ADMIN_STYLES = `
|
|
1389
|
+
/* \u2500\u2500\u2500 Layout \u2500\u2500\u2500 */
|
|
1390
|
+
body { margin: 0; }
|
|
1391
|
+
#app { display: flex; flex-direction: column; height: 100vh; }
|
|
1392
|
+
|
|
1393
|
+
.app-shell { display: flex; flex: 1; min-height: 0; overflow: hidden; }
|
|
1394
|
+
|
|
1395
|
+
/* \u2500\u2500\u2500 Sidebar \u2500\u2500\u2500 */
|
|
1396
|
+
.sidebar {
|
|
1397
|
+
width: 220px;
|
|
1398
|
+
flex-shrink: 0;
|
|
1399
|
+
background: var(--bg-secondary);
|
|
1400
|
+
border-right: 1px solid var(--border-primary);
|
|
1401
|
+
display: flex;
|
|
1402
|
+
flex-direction: column;
|
|
1403
|
+
overflow-y: auto;
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
.sidebar-brand {
|
|
1407
|
+
padding: var(--space-lg);
|
|
1408
|
+
border-bottom: 1px solid var(--border-secondary);
|
|
1409
|
+
display: flex;
|
|
1410
|
+
align-items: center;
|
|
1411
|
+
gap: var(--space-sm);
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
.sidebar-brand-icon {
|
|
1415
|
+
width: 28px;
|
|
1416
|
+
height: 28px;
|
|
1417
|
+
border-radius: var(--radius-md);
|
|
1418
|
+
background: linear-gradient(135deg, var(--accent-blue), var(--accent-purple));
|
|
1419
|
+
display: flex;
|
|
1420
|
+
align-items: center;
|
|
1421
|
+
justify-content: center;
|
|
1422
|
+
flex-shrink: 0;
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
.sidebar-brand-text {
|
|
1426
|
+
font-size: var(--font-size-md);
|
|
1427
|
+
font-weight: 700;
|
|
1428
|
+
color: var(--text-primary);
|
|
1429
|
+
white-space: nowrap;
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
.sidebar-nav { padding: var(--space-sm); flex: 1; }
|
|
1433
|
+
|
|
1434
|
+
.nav-item {
|
|
1435
|
+
display: flex;
|
|
1436
|
+
align-items: center;
|
|
1437
|
+
gap: var(--space-sm);
|
|
1438
|
+
padding: var(--space-sm) var(--space-md);
|
|
1439
|
+
border-radius: var(--radius-md);
|
|
1440
|
+
color: var(--text-secondary);
|
|
1441
|
+
text-decoration: none;
|
|
1442
|
+
font-size: var(--font-size-md);
|
|
1443
|
+
cursor: pointer;
|
|
1444
|
+
transition: background var(--transition-fast), color var(--transition-fast);
|
|
1445
|
+
border: none;
|
|
1446
|
+
background: none;
|
|
1447
|
+
width: 100%;
|
|
1448
|
+
text-align: left;
|
|
1449
|
+
font-family: var(--font-sans);
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
.nav-item:hover { background: var(--bg-tertiary); color: var(--text-primary); }
|
|
1453
|
+
.nav-item.active { background: rgba(88, 166, 255, 0.1); color: var(--accent-blue); }
|
|
1454
|
+
.nav-item svg { flex-shrink: 0; }
|
|
1455
|
+
|
|
1456
|
+
.sidebar-footer {
|
|
1457
|
+
padding: var(--space-md) var(--space-lg);
|
|
1458
|
+
border-top: 1px solid var(--border-secondary);
|
|
1459
|
+
font-size: var(--font-size-xs);
|
|
1460
|
+
color: var(--text-tertiary);
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
/* \u2500\u2500\u2500 Main Content \u2500\u2500\u2500 */
|
|
1464
|
+
.main-content {
|
|
1465
|
+
flex: 1;
|
|
1466
|
+
overflow-y: auto;
|
|
1467
|
+
padding: var(--space-xl) var(--space-2xl);
|
|
1468
|
+
min-width: 0;
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
.page-header {
|
|
1472
|
+
display: flex;
|
|
1473
|
+
align-items: center;
|
|
1474
|
+
justify-content: space-between;
|
|
1475
|
+
margin-bottom: var(--space-xl);
|
|
1476
|
+
flex-wrap: wrap;
|
|
1477
|
+
gap: var(--space-md);
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
.page-title {
|
|
1481
|
+
font-size: var(--font-size-2xl);
|
|
1482
|
+
font-weight: 700;
|
|
1483
|
+
color: var(--text-primary);
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
.page-subtitle {
|
|
1487
|
+
font-size: var(--font-size-sm);
|
|
1488
|
+
color: var(--text-secondary);
|
|
1489
|
+
margin-top: var(--space-xs);
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
/* \u2500\u2500\u2500 Breadcrumb \u2500\u2500\u2500 */
|
|
1493
|
+
.breadcrumb {
|
|
1494
|
+
display: flex;
|
|
1495
|
+
align-items: center;
|
|
1496
|
+
gap: var(--space-xs);
|
|
1497
|
+
font-size: var(--font-size-sm);
|
|
1498
|
+
color: var(--text-tertiary);
|
|
1499
|
+
margin-bottom: var(--space-lg);
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
.breadcrumb a {
|
|
1503
|
+
color: var(--text-link);
|
|
1504
|
+
text-decoration: none;
|
|
1505
|
+
cursor: pointer;
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
.breadcrumb a:hover { text-decoration: underline; }
|
|
1509
|
+
|
|
1510
|
+
.breadcrumb-sep {
|
|
1511
|
+
color: var(--text-tertiary);
|
|
1512
|
+
margin: 0 var(--space-xs);
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
.breadcrumb-current { color: var(--text-secondary); }
|
|
1516
|
+
|
|
1517
|
+
/* \u2500\u2500\u2500 Stats bar \u2500\u2500\u2500 */
|
|
1518
|
+
.stats-bar {
|
|
1519
|
+
display: flex;
|
|
1520
|
+
gap: var(--space-lg);
|
|
1521
|
+
margin-bottom: var(--space-xl);
|
|
1522
|
+
flex-wrap: wrap;
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
.stat-card {
|
|
1526
|
+
background: var(--bg-secondary);
|
|
1527
|
+
border: 1px solid var(--border-primary);
|
|
1528
|
+
border-radius: var(--radius-lg);
|
|
1529
|
+
padding: var(--space-md) var(--space-lg);
|
|
1530
|
+
display: flex;
|
|
1531
|
+
flex-direction: column;
|
|
1532
|
+
min-width: 100px;
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
.stat-value {
|
|
1536
|
+
font-size: var(--font-size-2xl);
|
|
1537
|
+
font-weight: 700;
|
|
1538
|
+
color: var(--text-primary);
|
|
1539
|
+
line-height: 1.2;
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
.stat-label {
|
|
1543
|
+
font-size: var(--font-size-xs);
|
|
1544
|
+
color: var(--text-tertiary);
|
|
1545
|
+
text-transform: uppercase;
|
|
1546
|
+
letter-spacing: 0.05em;
|
|
1547
|
+
margin-top: 2px;
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
/* \u2500\u2500\u2500 Store card grid \u2500\u2500\u2500 */
|
|
1551
|
+
.store-grid {
|
|
1552
|
+
display: grid;
|
|
1553
|
+
grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
|
|
1554
|
+
gap: var(--space-lg);
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
@media (max-width: 768px) {
|
|
1558
|
+
.store-grid { grid-template-columns: 1fr; }
|
|
1559
|
+
.sidebar { display: none; }
|
|
1560
|
+
.main-content { padding: var(--space-lg); }
|
|
1561
|
+
.stats-bar { flex-direction: column; }
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
.store-card {
|
|
1565
|
+
background: var(--bg-card);
|
|
1566
|
+
border: 1px solid var(--border-primary);
|
|
1567
|
+
border-radius: var(--radius-lg);
|
|
1568
|
+
overflow: hidden;
|
|
1569
|
+
transition: border-color var(--transition-normal), box-shadow var(--transition-normal);
|
|
1570
|
+
display: flex;
|
|
1571
|
+
flex-direction: column;
|
|
1572
|
+
cursor: pointer;
|
|
1573
|
+
text-decoration: none;
|
|
1574
|
+
color: inherit;
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
.store-card:hover {
|
|
1578
|
+
border-color: rgba(88, 166, 255, 0.3);
|
|
1579
|
+
box-shadow: var(--shadow-md);
|
|
1580
|
+
background: var(--bg-card-hover);
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
.card-header {
|
|
1584
|
+
padding: var(--space-lg) var(--space-lg) var(--space-sm);
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
.card-title-row {
|
|
1588
|
+
display: flex;
|
|
1589
|
+
align-items: center;
|
|
1590
|
+
gap: var(--space-sm);
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
.card-status { display: flex; align-items: center; flex-shrink: 0; }
|
|
1594
|
+
|
|
1595
|
+
.card-title {
|
|
1596
|
+
font-size: var(--font-size-md);
|
|
1597
|
+
font-weight: 600;
|
|
1598
|
+
color: var(--text-primary);
|
|
1599
|
+
flex: 1;
|
|
1600
|
+
min-width: 0;
|
|
1601
|
+
overflow: hidden;
|
|
1602
|
+
text-overflow: ellipsis;
|
|
1603
|
+
white-space: nowrap;
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
.card-description {
|
|
1607
|
+
font-size: var(--font-size-sm);
|
|
1608
|
+
color: var(--text-secondary);
|
|
1609
|
+
margin-top: var(--space-sm);
|
|
1610
|
+
line-height: 1.5;
|
|
1611
|
+
display: -webkit-box;
|
|
1612
|
+
-webkit-line-clamp: 2;
|
|
1613
|
+
-webkit-box-orient: vertical;
|
|
1614
|
+
overflow: hidden;
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
.card-body {
|
|
1618
|
+
padding: var(--space-sm) var(--space-lg);
|
|
1619
|
+
flex: 1;
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
.card-location {
|
|
1623
|
+
color: var(--text-tertiary);
|
|
1624
|
+
font-size: var(--font-size-sm);
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
.card-tags {
|
|
1628
|
+
display: flex;
|
|
1629
|
+
flex-wrap: wrap;
|
|
1630
|
+
gap: var(--space-xs);
|
|
1631
|
+
margin-top: var(--space-sm);
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
.card-footer {
|
|
1635
|
+
padding: var(--space-sm) var(--space-lg) var(--space-lg);
|
|
1636
|
+
border-top: 1px solid var(--border-secondary);
|
|
1637
|
+
margin-top: auto;
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
/* \u2500\u2500\u2500 Buttons \u2500\u2500\u2500 */
|
|
1641
|
+
.btn {
|
|
1642
|
+
display: inline-flex;
|
|
1643
|
+
align-items: center;
|
|
1644
|
+
gap: var(--space-sm);
|
|
1645
|
+
padding: var(--space-sm) var(--space-lg);
|
|
1646
|
+
border-radius: var(--radius-md);
|
|
1647
|
+
font-size: var(--font-size-md);
|
|
1648
|
+
font-weight: 500;
|
|
1649
|
+
font-family: var(--font-sans);
|
|
1650
|
+
cursor: pointer;
|
|
1651
|
+
border: 1px solid transparent;
|
|
1652
|
+
transition: background var(--transition-fast), border-color var(--transition-fast), opacity var(--transition-fast);
|
|
1653
|
+
white-space: nowrap;
|
|
1654
|
+
text-decoration: none;
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
1658
|
+
|
|
1659
|
+
.btn-primary {
|
|
1660
|
+
background: var(--accent-blue);
|
|
1661
|
+
color: var(--text-inverse);
|
|
1662
|
+
border-color: var(--accent-blue);
|
|
1663
|
+
}
|
|
1664
|
+
.btn-primary:hover:not(:disabled) { opacity: 0.9; }
|
|
1665
|
+
|
|
1666
|
+
.btn-secondary {
|
|
1667
|
+
background: var(--bg-tertiary);
|
|
1668
|
+
color: var(--text-primary);
|
|
1669
|
+
border-color: var(--border-primary);
|
|
1670
|
+
}
|
|
1671
|
+
.btn-secondary:hover:not(:disabled) { background: var(--bg-card-hover); }
|
|
1672
|
+
|
|
1673
|
+
.btn-danger {
|
|
1674
|
+
background: rgba(248, 81, 73, 0.15);
|
|
1675
|
+
color: var(--accent-red);
|
|
1676
|
+
border-color: rgba(248, 81, 73, 0.3);
|
|
1677
|
+
}
|
|
1678
|
+
.btn-danger:hover:not(:disabled) { background: rgba(248, 81, 73, 0.25); }
|
|
1679
|
+
|
|
1680
|
+
.btn-sm {
|
|
1681
|
+
padding: var(--space-xs) var(--space-md);
|
|
1682
|
+
font-size: var(--font-size-sm);
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
.btn-group {
|
|
1686
|
+
display: flex;
|
|
1687
|
+
gap: var(--space-sm);
|
|
1688
|
+
flex-wrap: wrap;
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
/* \u2500\u2500\u2500 Forms \u2500\u2500\u2500 */
|
|
1692
|
+
.form-group {
|
|
1693
|
+
margin-bottom: var(--space-lg);
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
.form-label {
|
|
1697
|
+
display: block;
|
|
1698
|
+
font-size: var(--font-size-sm);
|
|
1699
|
+
font-weight: 500;
|
|
1700
|
+
color: var(--text-primary);
|
|
1701
|
+
margin-bottom: var(--space-xs);
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
.form-hint {
|
|
1705
|
+
font-size: var(--font-size-xs);
|
|
1706
|
+
color: var(--text-tertiary);
|
|
1707
|
+
margin-top: var(--space-xs);
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
.form-input, .form-select, .form-textarea {
|
|
1711
|
+
width: 100%;
|
|
1712
|
+
padding: var(--space-sm) var(--space-md);
|
|
1713
|
+
background: var(--bg-inset);
|
|
1714
|
+
border: 1px solid var(--border-primary);
|
|
1715
|
+
border-radius: var(--radius-md);
|
|
1716
|
+
color: var(--text-primary);
|
|
1717
|
+
font-size: var(--font-size-md);
|
|
1718
|
+
font-family: var(--font-sans);
|
|
1719
|
+
transition: border-color var(--transition-fast);
|
|
1720
|
+
outline: none;
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
.form-input:focus, .form-select:focus, .form-textarea:focus {
|
|
1724
|
+
border-color: var(--accent-blue);
|
|
1725
|
+
box-shadow: 0 0 0 2px rgba(88, 166, 255, 0.2);
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
.form-select {
|
|
1729
|
+
appearance: none;
|
|
1730
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12' fill='%238b949e'%3E%3Cpath d='M2 4l4 4 4-4'/%3E%3C/svg%3E");
|
|
1731
|
+
background-repeat: no-repeat;
|
|
1732
|
+
background-position: right 10px center;
|
|
1733
|
+
padding-right: 32px;
|
|
1734
|
+
cursor: pointer;
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
.form-select option { background: var(--bg-secondary); color: var(--text-primary); }
|
|
1738
|
+
|
|
1739
|
+
.form-textarea { resize: vertical; min-height: 80px; }
|
|
1740
|
+
|
|
1741
|
+
.form-row {
|
|
1742
|
+
display: grid;
|
|
1743
|
+
grid-template-columns: 1fr 1fr;
|
|
1744
|
+
gap: var(--space-lg);
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
@media (max-width: 640px) {
|
|
1748
|
+
.form-row { grid-template-columns: 1fr; }
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
.form-card {
|
|
1752
|
+
background: var(--bg-secondary);
|
|
1753
|
+
border: 1px solid var(--border-primary);
|
|
1754
|
+
border-radius: var(--radius-lg);
|
|
1755
|
+
padding: var(--space-xl);
|
|
1756
|
+
max-width: 640px;
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
.form-error {
|
|
1760
|
+
color: var(--accent-red);
|
|
1761
|
+
font-size: var(--font-size-sm);
|
|
1762
|
+
margin-top: var(--space-sm);
|
|
1763
|
+
display: flex;
|
|
1764
|
+
align-items: center;
|
|
1765
|
+
gap: var(--space-xs);
|
|
1766
|
+
}
|
|
1767
|
+
|
|
1768
|
+
/* \u2500\u2500\u2500 Store Detail \u2500\u2500\u2500 */
|
|
1769
|
+
.detail-grid {
|
|
1770
|
+
display: grid;
|
|
1771
|
+
grid-template-columns: 1fr 1fr;
|
|
1772
|
+
gap: var(--space-lg);
|
|
1773
|
+
margin-bottom: var(--space-xl);
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
@media (max-width: 768px) {
|
|
1777
|
+
.detail-grid { grid-template-columns: 1fr; }
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
.detail-card {
|
|
1781
|
+
background: var(--bg-secondary);
|
|
1782
|
+
border: 1px solid var(--border-primary);
|
|
1783
|
+
border-radius: var(--radius-lg);
|
|
1784
|
+
padding: var(--space-xl);
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1787
|
+
.detail-card-full {
|
|
1788
|
+
grid-column: 1 / -1;
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
.detail-row {
|
|
1792
|
+
display: flex;
|
|
1793
|
+
justify-content: space-between;
|
|
1794
|
+
align-items: flex-start;
|
|
1795
|
+
padding: var(--space-sm) 0;
|
|
1796
|
+
border-bottom: 1px solid var(--border-secondary);
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
.detail-row:last-child { border-bottom: none; }
|
|
1800
|
+
|
|
1801
|
+
.detail-key {
|
|
1802
|
+
font-size: var(--font-size-sm);
|
|
1803
|
+
color: var(--text-tertiary);
|
|
1804
|
+
text-transform: uppercase;
|
|
1805
|
+
letter-spacing: 0.04em;
|
|
1806
|
+
flex-shrink: 0;
|
|
1807
|
+
min-width: 110px;
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1810
|
+
.detail-value {
|
|
1811
|
+
font-size: var(--font-size-sm);
|
|
1812
|
+
color: var(--text-primary);
|
|
1813
|
+
text-align: right;
|
|
1814
|
+
word-break: break-all;
|
|
1815
|
+
max-width: 70%;
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
/* \u2500\u2500\u2500 Search \u2500\u2500\u2500 */
|
|
1819
|
+
.search-bar {
|
|
1820
|
+
display: flex;
|
|
1821
|
+
gap: var(--space-sm);
|
|
1822
|
+
margin-bottom: var(--space-lg);
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1825
|
+
.search-bar .form-input {
|
|
1826
|
+
flex: 1;
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1829
|
+
.search-options {
|
|
1830
|
+
display: flex;
|
|
1831
|
+
gap: var(--space-lg);
|
|
1832
|
+
margin-bottom: var(--space-lg);
|
|
1833
|
+
flex-wrap: wrap;
|
|
1834
|
+
align-items: flex-end;
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
.search-option { min-width: 160px; }
|
|
1838
|
+
|
|
1839
|
+
.search-meta-bar {
|
|
1840
|
+
display: flex;
|
|
1841
|
+
align-items: center;
|
|
1842
|
+
gap: var(--space-md);
|
|
1843
|
+
flex-wrap: wrap;
|
|
1844
|
+
font-size: var(--font-size-sm);
|
|
1845
|
+
color: var(--text-secondary);
|
|
1846
|
+
margin-bottom: var(--space-lg);
|
|
1847
|
+
padding: var(--space-md) var(--space-lg);
|
|
1848
|
+
background: var(--bg-secondary);
|
|
1849
|
+
border: 1px solid var(--border-primary);
|
|
1850
|
+
border-radius: var(--radius-lg);
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
.confidence-badge {
|
|
1854
|
+
display: inline-flex;
|
|
1855
|
+
align-items: center;
|
|
1856
|
+
padding: 2px 8px;
|
|
1857
|
+
border-radius: 9999px;
|
|
1858
|
+
font-size: var(--font-size-xs);
|
|
1859
|
+
font-weight: 500;
|
|
1860
|
+
text-transform: capitalize;
|
|
1861
|
+
}
|
|
1862
|
+
|
|
1863
|
+
.confidence-high { background: var(--confidence-high-bg); color: var(--confidence-high-text); }
|
|
1864
|
+
.confidence-medium { background: var(--confidence-medium-bg); color: var(--confidence-medium-text); }
|
|
1865
|
+
.confidence-low { background: var(--confidence-low-bg); color: var(--confidence-low-text); }
|
|
1866
|
+
|
|
1867
|
+
/* \u2500\u2500\u2500 Results list \u2500\u2500\u2500 */
|
|
1868
|
+
.results-list {
|
|
1869
|
+
display: flex;
|
|
1870
|
+
flex-direction: column;
|
|
1871
|
+
gap: var(--space-md);
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
.result-card {
|
|
1875
|
+
background: var(--bg-card);
|
|
1876
|
+
border: 1px solid var(--border-primary);
|
|
1877
|
+
border-radius: var(--radius-lg);
|
|
1878
|
+
overflow: hidden;
|
|
1879
|
+
transition: border-color var(--transition-normal);
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
.result-card:hover { border-color: rgba(88, 166, 255, 0.3); }
|
|
1883
|
+
|
|
1884
|
+
.result-header {
|
|
1885
|
+
display: flex;
|
|
1886
|
+
gap: var(--space-md);
|
|
1887
|
+
padding: var(--space-lg);
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
.result-rank {
|
|
1891
|
+
flex-shrink: 0;
|
|
1892
|
+
width: 28px;
|
|
1893
|
+
height: 28px;
|
|
1894
|
+
display: flex;
|
|
1895
|
+
align-items: center;
|
|
1896
|
+
justify-content: center;
|
|
1897
|
+
background: var(--bg-tertiary);
|
|
1898
|
+
border: 1px solid var(--border-secondary);
|
|
1899
|
+
border-radius: var(--radius-sm);
|
|
1900
|
+
font-size: var(--font-size-sm);
|
|
1901
|
+
font-weight: 600;
|
|
1902
|
+
color: var(--text-tertiary);
|
|
1903
|
+
font-family: var(--font-mono);
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
.result-main { flex: 1; min-width: 0; }
|
|
1907
|
+
|
|
1908
|
+
.result-title-row {
|
|
1909
|
+
display: flex;
|
|
1910
|
+
align-items: center;
|
|
1911
|
+
gap: var(--space-sm);
|
|
1912
|
+
margin-bottom: var(--space-xs);
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
.type-icon {
|
|
1916
|
+
display: inline-flex;
|
|
1917
|
+
align-items: center;
|
|
1918
|
+
justify-content: center;
|
|
1919
|
+
width: 22px;
|
|
1920
|
+
height: 22px;
|
|
1921
|
+
border-radius: var(--radius-sm);
|
|
1922
|
+
font-size: 11px;
|
|
1923
|
+
font-weight: 700;
|
|
1924
|
+
font-family: var(--font-mono);
|
|
1925
|
+
flex-shrink: 0;
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1928
|
+
.type-fn { background: rgba(88, 166, 255, 0.15); color: var(--accent-blue); }
|
|
1929
|
+
.type-class { background: rgba(210, 153, 34, 0.15); color: var(--accent-orange); }
|
|
1930
|
+
.type-iface { background: rgba(57, 210, 192, 0.15); color: var(--accent-cyan); }
|
|
1931
|
+
.type-type { background: rgba(188, 140, 255, 0.15); color: var(--accent-purple); }
|
|
1932
|
+
.type-const { background: rgba(63, 185, 80, 0.15); color: var(--accent-green); }
|
|
1933
|
+
.type-doc { background: rgba(139, 148, 158, 0.15); color: var(--text-secondary); }
|
|
1934
|
+
.type-example { background: rgba(247, 120, 186, 0.15); color: var(--accent-pink); }
|
|
1935
|
+
.type-default { background: rgba(139, 148, 158, 0.1); color: var(--text-tertiary); }
|
|
1936
|
+
|
|
1937
|
+
.result-name {
|
|
1938
|
+
font-size: var(--font-size-md);
|
|
1939
|
+
font-weight: 600;
|
|
1940
|
+
color: var(--text-primary);
|
|
1941
|
+
font-family: var(--font-mono);
|
|
1942
|
+
flex: 1;
|
|
1943
|
+
min-width: 0;
|
|
1944
|
+
overflow: hidden;
|
|
1945
|
+
text-overflow: ellipsis;
|
|
1946
|
+
white-space: nowrap;
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
.result-signature {
|
|
1950
|
+
color: var(--text-secondary);
|
|
1951
|
+
margin-bottom: var(--space-sm);
|
|
1952
|
+
font-size: var(--font-size-sm);
|
|
1953
|
+
font-family: var(--font-mono);
|
|
1954
|
+
line-height: 1.4;
|
|
1955
|
+
overflow-x: auto;
|
|
1956
|
+
white-space: pre-wrap;
|
|
1957
|
+
word-break: break-all;
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
.result-purpose {
|
|
1961
|
+
color: var(--text-secondary);
|
|
1962
|
+
font-size: var(--font-size-sm);
|
|
1963
|
+
margin-bottom: var(--space-sm);
|
|
1964
|
+
line-height: 1.5;
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1967
|
+
.result-meta { margin-bottom: var(--space-xs); }
|
|
1968
|
+
|
|
1969
|
+
.result-location {
|
|
1970
|
+
color: var(--text-tertiary);
|
|
1971
|
+
font-size: var(--font-size-sm);
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1974
|
+
.line-number { color: var(--accent-blue); }
|
|
1975
|
+
|
|
1976
|
+
.result-store {
|
|
1977
|
+
font-size: var(--font-size-sm);
|
|
1978
|
+
color: var(--text-tertiary);
|
|
1979
|
+
}
|
|
1980
|
+
|
|
1981
|
+
.result-relevance {
|
|
1982
|
+
font-size: var(--font-size-sm);
|
|
1983
|
+
color: var(--text-secondary);
|
|
1984
|
+
font-style: italic;
|
|
1985
|
+
margin-top: var(--space-xs);
|
|
1986
|
+
padding-left: var(--space-md);
|
|
1987
|
+
border-left: 2px solid var(--border-secondary);
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1990
|
+
.score-high { color: var(--score-high); }
|
|
1991
|
+
.score-medium { color: var(--score-medium); }
|
|
1992
|
+
.score-low { color: var(--score-low); }
|
|
1993
|
+
|
|
1994
|
+
.result-card details { margin: 0; border-top: 1px solid var(--border-secondary); }
|
|
1995
|
+
.result-card details summary { padding: var(--space-sm) var(--space-lg); font-weight: 500; }
|
|
1996
|
+
.result-card .details-content { padding: 0 var(--space-lg) var(--space-lg); }
|
|
1997
|
+
|
|
1998
|
+
.context-group, .full-group { margin-bottom: var(--space-md); }
|
|
1999
|
+
.context-group:last-child, .full-group:last-child { margin-bottom: 0; }
|
|
2000
|
+
|
|
2001
|
+
.usage-stats { display: flex; gap: var(--space-lg); }
|
|
2002
|
+
.usage-stat { display: flex; align-items: baseline; gap: var(--space-xs); }
|
|
2003
|
+
.usage-value { font-size: var(--font-size-lg); font-weight: 700; font-family: var(--font-mono); color: var(--text-primary); }
|
|
2004
|
+
.usage-label { font-size: var(--font-size-xs); color: var(--text-tertiary); }
|
|
2005
|
+
|
|
2006
|
+
.documentation-text {
|
|
2007
|
+
font-size: var(--font-size-sm);
|
|
2008
|
+
color: var(--text-secondary);
|
|
2009
|
+
line-height: 1.6;
|
|
2010
|
+
white-space: pre-wrap;
|
|
2011
|
+
}
|
|
2012
|
+
|
|
2013
|
+
.result-card .code-block { max-height: 400px; overflow-y: auto; font-size: var(--font-size-sm); }
|
|
2014
|
+
|
|
2015
|
+
.code-block::-webkit-scrollbar { width: 6px; height: 6px; }
|
|
2016
|
+
.code-block::-webkit-scrollbar-track { background: transparent; }
|
|
2017
|
+
.code-block::-webkit-scrollbar-thumb { background: var(--border-primary); border-radius: 3px; }
|
|
2018
|
+
.code-block::-webkit-scrollbar-thumb:hover { background: var(--text-tertiary); }
|
|
2019
|
+
|
|
2020
|
+
/* \u2500\u2500\u2500 Modal \u2500\u2500\u2500 */
|
|
2021
|
+
.modal-overlay {
|
|
2022
|
+
position: fixed;
|
|
2023
|
+
inset: 0;
|
|
2024
|
+
background: rgba(0, 0, 0, 0.6);
|
|
2025
|
+
display: flex;
|
|
2026
|
+
align-items: center;
|
|
2027
|
+
justify-content: center;
|
|
2028
|
+
z-index: 1000;
|
|
2029
|
+
backdrop-filter: blur(4px);
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
.modal {
|
|
2033
|
+
background: var(--bg-secondary);
|
|
2034
|
+
border: 1px solid var(--border-primary);
|
|
2035
|
+
border-radius: var(--radius-xl);
|
|
2036
|
+
padding: var(--space-xl);
|
|
2037
|
+
max-width: 420px;
|
|
2038
|
+
width: 90%;
|
|
2039
|
+
box-shadow: var(--shadow-lg);
|
|
2040
|
+
}
|
|
2041
|
+
|
|
2042
|
+
.modal-title {
|
|
2043
|
+
font-size: var(--font-size-lg);
|
|
2044
|
+
font-weight: 600;
|
|
2045
|
+
color: var(--text-primary);
|
|
2046
|
+
margin-bottom: var(--space-md);
|
|
2047
|
+
}
|
|
2048
|
+
|
|
2049
|
+
.modal-body {
|
|
2050
|
+
font-size: var(--font-size-sm);
|
|
2051
|
+
color: var(--text-secondary);
|
|
2052
|
+
margin-bottom: var(--space-xl);
|
|
2053
|
+
line-height: 1.5;
|
|
2054
|
+
}
|
|
2055
|
+
|
|
2056
|
+
.modal-actions {
|
|
2057
|
+
display: flex;
|
|
2058
|
+
justify-content: flex-end;
|
|
2059
|
+
gap: var(--space-sm);
|
|
2060
|
+
}
|
|
2061
|
+
|
|
2062
|
+
/* \u2500\u2500\u2500 Toast \u2500\u2500\u2500 */
|
|
2063
|
+
.toast-container {
|
|
2064
|
+
position: fixed;
|
|
2065
|
+
top: var(--space-lg);
|
|
2066
|
+
right: var(--space-lg);
|
|
2067
|
+
z-index: 2000;
|
|
2068
|
+
display: flex;
|
|
2069
|
+
flex-direction: column;
|
|
2070
|
+
gap: var(--space-sm);
|
|
2071
|
+
pointer-events: none;
|
|
2072
|
+
}
|
|
2073
|
+
|
|
2074
|
+
.toast {
|
|
2075
|
+
padding: var(--space-md) var(--space-lg);
|
|
2076
|
+
border-radius: var(--radius-lg);
|
|
2077
|
+
font-size: var(--font-size-sm);
|
|
2078
|
+
box-shadow: var(--shadow-lg);
|
|
2079
|
+
pointer-events: auto;
|
|
2080
|
+
animation: toast-in 300ms ease;
|
|
2081
|
+
max-width: 360px;
|
|
2082
|
+
display: flex;
|
|
2083
|
+
align-items: center;
|
|
2084
|
+
gap: var(--space-sm);
|
|
2085
|
+
}
|
|
2086
|
+
|
|
2087
|
+
.toast-error {
|
|
2088
|
+
background: rgba(248, 81, 73, 0.15);
|
|
2089
|
+
border: 1px solid rgba(248, 81, 73, 0.3);
|
|
2090
|
+
color: var(--accent-red);
|
|
2091
|
+
}
|
|
2092
|
+
|
|
2093
|
+
.toast-success {
|
|
2094
|
+
background: rgba(63, 185, 80, 0.15);
|
|
2095
|
+
border: 1px solid rgba(63, 185, 80, 0.3);
|
|
2096
|
+
color: var(--accent-green);
|
|
2097
|
+
}
|
|
2098
|
+
|
|
2099
|
+
.toast-info {
|
|
2100
|
+
background: rgba(88, 166, 255, 0.15);
|
|
2101
|
+
border: 1px solid rgba(88, 166, 255, 0.3);
|
|
2102
|
+
color: var(--accent-blue);
|
|
2103
|
+
}
|
|
2104
|
+
|
|
2105
|
+
@keyframes toast-in {
|
|
2106
|
+
from { opacity: 0; transform: translateY(-12px); }
|
|
2107
|
+
to { opacity: 1; transform: translateY(0); }
|
|
2108
|
+
}
|
|
2109
|
+
|
|
2110
|
+
/* \u2500\u2500\u2500 Spinner \u2500\u2500\u2500 */
|
|
2111
|
+
.spinner {
|
|
2112
|
+
width: 20px;
|
|
2113
|
+
height: 20px;
|
|
2114
|
+
border: 2px solid var(--border-primary);
|
|
2115
|
+
border-top-color: var(--accent-blue);
|
|
2116
|
+
border-radius: 50%;
|
|
2117
|
+
animation: spin 0.6s linear infinite;
|
|
2118
|
+
display: inline-block;
|
|
2119
|
+
flex-shrink: 0;
|
|
2120
|
+
}
|
|
2121
|
+
|
|
2122
|
+
.spinner-lg { width: 32px; height: 32px; border-width: 3px; }
|
|
2123
|
+
|
|
2124
|
+
@keyframes spin {
|
|
2125
|
+
to { transform: rotate(360deg); }
|
|
2126
|
+
}
|
|
2127
|
+
|
|
2128
|
+
.loading-center {
|
|
2129
|
+
display: flex;
|
|
2130
|
+
align-items: center;
|
|
2131
|
+
justify-content: center;
|
|
2132
|
+
padding: var(--space-2xl);
|
|
2133
|
+
gap: var(--space-md);
|
|
2134
|
+
color: var(--text-secondary);
|
|
2135
|
+
font-size: var(--font-size-sm);
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
/* \u2500\u2500\u2500 Skeleton \u2500\u2500\u2500 */
|
|
2139
|
+
.skeleton {
|
|
2140
|
+
background: linear-gradient(90deg, var(--bg-tertiary) 25%, var(--bg-card-hover) 50%, var(--bg-tertiary) 75%);
|
|
2141
|
+
background-size: 200% 100%;
|
|
2142
|
+
animation: skeleton-pulse 1.5s ease-in-out infinite;
|
|
2143
|
+
border-radius: var(--radius-md);
|
|
2144
|
+
}
|
|
2145
|
+
|
|
2146
|
+
@keyframes skeleton-pulse {
|
|
2147
|
+
0% { background-position: 200% 0; }
|
|
2148
|
+
100% { background-position: -200% 0; }
|
|
2149
|
+
}
|
|
2150
|
+
|
|
2151
|
+
.skeleton-card {
|
|
2152
|
+
height: 180px;
|
|
2153
|
+
border-radius: var(--radius-lg);
|
|
2154
|
+
}
|
|
2155
|
+
|
|
2156
|
+
.skeleton-line {
|
|
2157
|
+
height: 16px;
|
|
2158
|
+
margin-bottom: var(--space-sm);
|
|
2159
|
+
}
|
|
2160
|
+
|
|
2161
|
+
.skeleton-line-short { width: 60%; }
|
|
2162
|
+
|
|
2163
|
+
/* \u2500\u2500\u2500 Multi-select (store filter) \u2500\u2500\u2500 */
|
|
2164
|
+
.multi-select {
|
|
2165
|
+
position: relative;
|
|
2166
|
+
}
|
|
2167
|
+
|
|
2168
|
+
.multi-select-trigger {
|
|
2169
|
+
display: flex;
|
|
2170
|
+
align-items: center;
|
|
2171
|
+
justify-content: space-between;
|
|
2172
|
+
padding: var(--space-sm) var(--space-md);
|
|
2173
|
+
background: var(--bg-inset);
|
|
2174
|
+
border: 1px solid var(--border-primary);
|
|
2175
|
+
border-radius: var(--radius-md);
|
|
2176
|
+
color: var(--text-primary);
|
|
2177
|
+
font-size: var(--font-size-md);
|
|
2178
|
+
cursor: pointer;
|
|
2179
|
+
min-height: 38px;
|
|
2180
|
+
font-family: var(--font-sans);
|
|
2181
|
+
}
|
|
2182
|
+
|
|
2183
|
+
.multi-select-trigger:hover { border-color: var(--accent-blue); }
|
|
2184
|
+
|
|
2185
|
+
.multi-select-dropdown {
|
|
2186
|
+
position: absolute;
|
|
2187
|
+
top: 100%;
|
|
2188
|
+
left: 0;
|
|
2189
|
+
right: 0;
|
|
2190
|
+
background: var(--bg-secondary);
|
|
2191
|
+
border: 1px solid var(--border-primary);
|
|
2192
|
+
border-radius: var(--radius-md);
|
|
2193
|
+
box-shadow: var(--shadow-lg);
|
|
2194
|
+
z-index: 100;
|
|
2195
|
+
margin-top: var(--space-xs);
|
|
2196
|
+
max-height: 200px;
|
|
2197
|
+
overflow-y: auto;
|
|
2198
|
+
display: none;
|
|
2199
|
+
}
|
|
2200
|
+
|
|
2201
|
+
.multi-select-dropdown.open { display: block; }
|
|
2202
|
+
|
|
2203
|
+
.multi-select-option {
|
|
2204
|
+
display: flex;
|
|
2205
|
+
align-items: center;
|
|
2206
|
+
gap: var(--space-sm);
|
|
2207
|
+
padding: var(--space-sm) var(--space-md);
|
|
2208
|
+
cursor: pointer;
|
|
2209
|
+
font-size: var(--font-size-sm);
|
|
2210
|
+
color: var(--text-primary);
|
|
2211
|
+
transition: background var(--transition-fast);
|
|
2212
|
+
}
|
|
2213
|
+
|
|
2214
|
+
.multi-select-option:hover { background: var(--bg-tertiary); }
|
|
2215
|
+
|
|
2216
|
+
.multi-select-option input[type="checkbox"] {
|
|
2217
|
+
accent-color: var(--accent-blue);
|
|
2218
|
+
}
|
|
2219
|
+
|
|
2220
|
+
/* \u2500\u2500\u2500 Mobile nav toggle \u2500\u2500\u2500 */
|
|
2221
|
+
.mobile-nav-toggle {
|
|
2222
|
+
display: none;
|
|
2223
|
+
position: fixed;
|
|
2224
|
+
bottom: var(--space-lg);
|
|
2225
|
+
right: var(--space-lg);
|
|
2226
|
+
width: 48px;
|
|
2227
|
+
height: 48px;
|
|
2228
|
+
border-radius: 50%;
|
|
2229
|
+
background: var(--accent-blue);
|
|
2230
|
+
color: var(--text-inverse);
|
|
2231
|
+
border: none;
|
|
2232
|
+
cursor: pointer;
|
|
2233
|
+
z-index: 500;
|
|
2234
|
+
box-shadow: var(--shadow-lg);
|
|
2235
|
+
align-items: center;
|
|
2236
|
+
justify-content: center;
|
|
2237
|
+
}
|
|
2238
|
+
|
|
2239
|
+
@media (max-width: 768px) {
|
|
2240
|
+
.mobile-nav-toggle { display: flex; }
|
|
2241
|
+
.sidebar.mobile-open { display: flex; position: fixed; top: 0; left: 0; bottom: 0; z-index: 400; }
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
/* \u2500\u2500\u2500 Misc \u2500\u2500\u2500 */
|
|
2245
|
+
.inline-search-section {
|
|
2246
|
+
margin-top: var(--space-xl);
|
|
2247
|
+
padding-top: var(--space-xl);
|
|
2248
|
+
border-top: 1px solid var(--border-primary);
|
|
2249
|
+
}
|
|
2250
|
+
|
|
2251
|
+
.section-title {
|
|
2252
|
+
font-size: var(--font-size-lg);
|
|
2253
|
+
font-weight: 600;
|
|
2254
|
+
color: var(--text-primary);
|
|
2255
|
+
margin-bottom: var(--space-lg);
|
|
2256
|
+
}
|
|
2257
|
+
|
|
2258
|
+
.store-actions {
|
|
2259
|
+
margin-bottom: var(--space-xl);
|
|
2260
|
+
}
|
|
2261
|
+
|
|
2262
|
+
/* \u2500\u2500\u2500 Documents section \u2500\u2500\u2500 */
|
|
2263
|
+
.documents-section {
|
|
2264
|
+
margin-top: var(--space-xl);
|
|
2265
|
+
padding-top: var(--space-xl);
|
|
2266
|
+
border-top: 1px solid var(--border-primary);
|
|
2267
|
+
}
|
|
2268
|
+
|
|
2269
|
+
.documents-header {
|
|
2270
|
+
display: flex;
|
|
2271
|
+
align-items: center;
|
|
2272
|
+
justify-content: space-between;
|
|
2273
|
+
margin-bottom: var(--space-lg);
|
|
2274
|
+
}
|
|
2275
|
+
|
|
2276
|
+
.documents-list {
|
|
2277
|
+
display: flex;
|
|
2278
|
+
flex-direction: column;
|
|
2279
|
+
gap: var(--space-md);
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
.document-item {
|
|
2283
|
+
background: var(--bg-card);
|
|
2284
|
+
border: 1px solid var(--border-primary);
|
|
2285
|
+
border-radius: var(--radius-lg);
|
|
2286
|
+
overflow: hidden;
|
|
2287
|
+
transition: border-color var(--transition-normal);
|
|
2288
|
+
}
|
|
2289
|
+
|
|
2290
|
+
.document-item:hover {
|
|
2291
|
+
border-color: rgba(88, 166, 255, 0.3);
|
|
2292
|
+
}
|
|
2293
|
+
|
|
2294
|
+
.document-meta {
|
|
2295
|
+
display: flex;
|
|
2296
|
+
align-items: center;
|
|
2297
|
+
gap: var(--space-sm);
|
|
2298
|
+
padding: var(--space-md) var(--space-lg);
|
|
2299
|
+
background: var(--bg-tertiary);
|
|
2300
|
+
border-bottom: 1px solid var(--border-secondary);
|
|
2301
|
+
font-size: var(--font-size-sm);
|
|
2302
|
+
min-height: 40px;
|
|
2303
|
+
}
|
|
2304
|
+
|
|
2305
|
+
.document-index {
|
|
2306
|
+
display: inline-flex;
|
|
2307
|
+
align-items: center;
|
|
2308
|
+
justify-content: center;
|
|
2309
|
+
min-width: 24px;
|
|
2310
|
+
height: 24px;
|
|
2311
|
+
padding: 0 var(--space-xs);
|
|
2312
|
+
background: var(--bg-inset);
|
|
2313
|
+
border: 1px solid var(--border-secondary);
|
|
2314
|
+
border-radius: var(--radius-sm);
|
|
2315
|
+
font-size: var(--font-size-xs);
|
|
2316
|
+
font-family: var(--font-mono);
|
|
2317
|
+
color: var(--text-tertiary);
|
|
2318
|
+
flex-shrink: 0;
|
|
2319
|
+
}
|
|
2320
|
+
|
|
2321
|
+
.document-path {
|
|
2322
|
+
color: var(--text-link);
|
|
2323
|
+
overflow: hidden;
|
|
2324
|
+
text-overflow: ellipsis;
|
|
2325
|
+
white-space: nowrap;
|
|
2326
|
+
font-weight: 500;
|
|
2327
|
+
}
|
|
2328
|
+
|
|
2329
|
+
.document-chunk-label {
|
|
2330
|
+
color: var(--text-tertiary);
|
|
2331
|
+
font-size: var(--font-size-xs);
|
|
2332
|
+
white-space: nowrap;
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2335
|
+
.document-content {
|
|
2336
|
+
margin: 0;
|
|
2337
|
+
padding: var(--space-lg) !important;
|
|
2338
|
+
min-height: 80px;
|
|
2339
|
+
max-height: 300px;
|
|
2340
|
+
overflow-y: auto;
|
|
2341
|
+
font-size: var(--font-size-sm) !important;
|
|
2342
|
+
line-height: 1.7 !important;
|
|
2343
|
+
white-space: pre-wrap !important;
|
|
2344
|
+
word-break: break-word;
|
|
2345
|
+
border: none !important;
|
|
2346
|
+
border-radius: 0 !important;
|
|
2347
|
+
background: var(--bg-inset) !important;
|
|
2348
|
+
}
|
|
2349
|
+
|
|
2350
|
+
.document-content::-webkit-scrollbar { width: 6px; }
|
|
2351
|
+
.document-content::-webkit-scrollbar-track { background: transparent; }
|
|
2352
|
+
.document-content::-webkit-scrollbar-thumb { background: var(--border-primary); border-radius: 3px; }
|
|
2353
|
+
.document-content::-webkit-scrollbar-thumb:hover { background: var(--text-tertiary); }
|
|
2354
|
+
|
|
2355
|
+
.documents-pagination {
|
|
2356
|
+
display: flex;
|
|
2357
|
+
align-items: center;
|
|
2358
|
+
justify-content: center;
|
|
2359
|
+
gap: var(--space-md);
|
|
2360
|
+
margin-top: var(--space-xl);
|
|
2361
|
+
padding: var(--space-lg) 0;
|
|
2362
|
+
border-top: 1px solid var(--border-secondary);
|
|
2363
|
+
}
|
|
2364
|
+
|
|
2365
|
+
.btn-sm {
|
|
2366
|
+
padding: var(--space-xs) var(--space-md);
|
|
2367
|
+
font-size: var(--font-size-sm);
|
|
2368
|
+
}
|
|
2369
|
+
|
|
2370
|
+
.text-secondary { color: var(--text-secondary); }
|
|
2371
|
+
.text-tertiary { color: var(--text-tertiary); }
|
|
2372
|
+
`;
|
|
2373
|
+
function renderAdminUI() {
|
|
2374
|
+
return `<!DOCTYPE html>
|
|
2375
|
+
<html lang="en">
|
|
2376
|
+
<head>
|
|
2377
|
+
<meta charset="UTF-8">
|
|
2378
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
2379
|
+
<title>Bluera Knowledge - Admin</title>
|
|
2380
|
+
<style>${BASE_STYLES}${ADMIN_STYLES}</style>
|
|
2381
|
+
</head>
|
|
2382
|
+
<body>
|
|
2383
|
+
<div id="app"></div>
|
|
2384
|
+
<div class="toast-container" id="toast-container"></div>
|
|
2385
|
+
<div id="modal-root"></div>
|
|
2386
|
+
<script>
|
|
2387
|
+
(function() {
|
|
2388
|
+
'use strict';
|
|
2389
|
+
|
|
2390
|
+
// \u2500\u2500\u2500 Utilities \u2500\u2500\u2500
|
|
2391
|
+
|
|
2392
|
+
function esc(str) {
|
|
2393
|
+
if (str == null) return '';
|
|
2394
|
+
return String(str)
|
|
2395
|
+
.replace(/&/g, '&')
|
|
2396
|
+
.replace(/</g, '<')
|
|
2397
|
+
.replace(/>/g, '>')
|
|
2398
|
+
.replace(/"/g, '"')
|
|
2399
|
+
.replace(/'/g, ''');
|
|
2400
|
+
}
|
|
2401
|
+
|
|
2402
|
+
function formatDate(iso) {
|
|
2403
|
+
if (!iso) return '';
|
|
2404
|
+
var d = new Date(iso);
|
|
2405
|
+
var now = new Date();
|
|
2406
|
+
var diffMs = now.getTime() - d.getTime();
|
|
2407
|
+
var diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
|
2408
|
+
if (diffDays === 0) return 'Today';
|
|
2409
|
+
if (diffDays === 1) return 'Yesterday';
|
|
2410
|
+
if (diffDays < 7) return diffDays + 'd ago';
|
|
2411
|
+
if (diffDays < 30) return Math.floor(diffDays / 7) + 'w ago';
|
|
2412
|
+
if (diffDays < 365) return Math.floor(diffDays / 30) + 'mo ago';
|
|
2413
|
+
return Math.floor(diffDays / 365) + 'y ago';
|
|
2414
|
+
}
|
|
2415
|
+
|
|
2416
|
+
function formatFullDate(iso) {
|
|
2417
|
+
if (!iso) return '';
|
|
2418
|
+
return new Date(iso).toLocaleString();
|
|
2419
|
+
}
|
|
2420
|
+
|
|
2421
|
+
// \u2500\u2500\u2500 SVG Icons \u2500\u2500\u2500
|
|
2422
|
+
|
|
2423
|
+
var ICONS = {
|
|
2424
|
+
stores: '<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"><path d="M2 2.5A2.5 2.5 0 014.5 0h8.75a.75.75 0 01.75.75v12.5a.75.75 0 01-.75.75h-2.5a.75.75 0 110-1.5h1.75v-2h-8a1 1 0 00-.714 1.7.75.75 0 01-1.072 1.05A2.495 2.495 0 012 11.5v-9zm10.5-1h-8a1 1 0 00-1 1v6.708A2.486 2.486 0 014.5 9h8V1.5zM5 12.25v3.25a.25.25 0 00.4.2l1.45-1.087a.25.25 0 01.3 0L8.6 15.7a.25.25 0 00.4-.2v-3.25a.25.25 0 00-.25-.25h-3.5a.25.25 0 00-.25.25z"/></svg>',
|
|
2425
|
+
search: '<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"><path d="M10.68 11.74a6 6 0 01-7.922-8.982 6 6 0 018.982 7.922l3.04 3.04a.749.749 0 01-.326 1.275.749.749 0 01-.734-.215l-3.04-3.04zM11.5 7a4.499 4.499 0 10-8.997 0A4.499 4.499 0 0011.5 7z"/></svg>',
|
|
2426
|
+
plus: '<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"><path d="M7.75 2a.75.75 0 01.75.75V7h4.25a.75.75 0 010 1.5H8.5v4.25a.75.75 0 01-1.5 0V8.5H2.75a.75.75 0 010-1.5H7V2.75A.75.75 0 017.75 2z"/></svg>',
|
|
2427
|
+
trash: '<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"><path d="M6.5 1.75a.25.25 0 01.25-.25h2.5a.25.25 0 01.25.25V3h-3V1.75zm4.5 0V3h2.25a.75.75 0 010 1.5H2.75a.75.75 0 010-1.5H5V1.75C5 .784 5.784 0 6.75 0h2.5C10.216 0 11 .784 11 1.75zM4.496 6.675a.75.75 0 10-1.492.15l.66 6.6A1.75 1.75 0 005.405 15h5.19a1.75 1.75 0 001.741-1.575l.66-6.6a.75.75 0 00-1.492-.15l-.66 6.6a.25.25 0 01-.249.225h-5.19a.25.25 0 01-.249-.225l-.66-6.6z"/></svg>',
|
|
2428
|
+
refresh: '<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"><path d="M8 2.5a5.487 5.487 0 00-4.131 1.869l1.204 1.204A.25.25 0 014.896 6H1.25A.25.25 0 011 5.75V2.104a.25.25 0 01.427-.177l1.38 1.38A7.001 7.001 0 0114.95 7.16a.75.75 0 11-1.49.178A5.501 5.501 0 008 2.5zM1.705 8.005a.75.75 0 01.834.656 5.501 5.501 0 009.592 2.97l-1.204-1.204a.25.25 0 01.177-.427h3.646a.25.25 0 01.25.25v3.646a.25.25 0 01-.427.177l-1.38-1.38A7.001 7.001 0 011.05 8.84a.75.75 0 01.656-.834z"/></svg>',
|
|
2429
|
+
repo: '<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor"><path d="M2 2.5A2.5 2.5 0 014.5 0h8.75a.75.75 0 01.75.75v12.5a.75.75 0 01-.75.75h-2.5a.75.75 0 110-1.5h1.75v-2h-8a1 1 0 00-.714 1.7.75.75 0 01-1.072 1.05A2.495 2.495 0 012 11.5v-9zm10.5-1h-8a1 1 0 00-1 1v6.708A2.486 2.486 0 014.5 9h8V1.5zM5 12.25v3.25a.25.25 0 00.4.2l1.45-1.087a.25.25 0 01.3 0L8.6 15.7a.25.25 0 00.4-.2v-3.25a.25.25 0 00-.25-.25h-3.5a.25.25 0 00-.25.25z"/></svg>',
|
|
2430
|
+
file: '<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor"><path d="M3.75 1.5a.25.25 0 00-.25.25v11.5c0 .138.112.25.25.25h8.5a.25.25 0 00.25-.25V6H9.75A1.75 1.75 0 018 4.25V1.5H3.75zm5.75.56v2.19c0 .138.112.25.25.25h2.19L9.5 2.06zM2 1.75C2 .784 2.784 0 3.75 0h5.086c.464 0 .909.184 1.237.513l3.414 3.414c.329.328.513.773.513 1.237v8.086A1.75 1.75 0 0112.25 15h-8.5A1.75 1.75 0 012 13.25V1.75z"/></svg>',
|
|
2431
|
+
web: '<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor"><path d="M1.5 8a6.5 6.5 0 1113 0 6.5 6.5 0 01-13 0zM8 0a8 8 0 100 16A8 8 0 008 0zM6.379 5.227A31.17 31.17 0 006 7.5h4c-.07-.77-.2-1.53-.379-2.273a4.7 4.7 0 01-.862.22 4.5 4.5 0 01-1.038 0 4.7 4.7 0 01-.862-.22zM5.43 7.5c.05-.9.155-1.79.42-2.667a8.2 8.2 0 01-.987-.652A6.54 6.54 0 003.538 7.5H5.43zm-1.893 1.5h1.893c.05.898.155 1.787.42 2.667-.326.217-.668.41-.987.652A6.54 6.54 0 013.538 9zm6.963 0c-.05.898-.155 1.787-.42 2.667.326.217.668.41.987.652A6.54 6.54 0 0012.462 9h-1.893zm1.893-1.5h-1.893c-.05-.9-.155-1.79-.42-2.667.326-.217.668-.41.987-.652A6.54 6.54 0 0112.462 7.5zM6 9h4c-.07.77-.2 1.53-.379 2.273a4.7 4.7 0 01-.862-.22 4.5 4.5 0 01-1.038 0 4.7 4.7 0 01-.862.22A31.17 31.17 0 016 9z"/></svg>',
|
|
2432
|
+
brand: '<svg width="16" height="16" viewBox="0 0 16 16" fill="white"><path d="M8 1.5a6.5 6.5 0 100 13 6.5 6.5 0 000-13zM0 8a8 8 0 1116 0A8 8 0 010 8zm6.5-2a1.5 1.5 0 113 0v4a1.5 1.5 0 01-3 0V6z"/></svg>',
|
|
2433
|
+
hamburger: '<svg width="20" height="20" viewBox="0 0 16 16" fill="currentColor"><path d="M1 2.75A.75.75 0 011.75 2h12.5a.75.75 0 010 1.5H1.75A.75.75 0 011 2.75zm0 5A.75.75 0 011.75 7h12.5a.75.75 0 010 1.5H1.75A.75.75 0 011 7.75zM1.75 12h12.5a.75.75 0 010 1.5H1.75a.75.75 0 010-1.5z"/></svg>',
|
|
2434
|
+
chevron: '<svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor"><path d="M4.7 2.3a.75.75 0 000 1.06L7.94 6.64 4.7 9.88a.75.75 0 101.06 1.06l3.87-3.87a.75.75 0 000-1.06L5.76 2.14a.75.75 0 00-1.06.16z"/></svg>'
|
|
2435
|
+
};
|
|
2436
|
+
|
|
2437
|
+
// \u2500\u2500\u2500 API client \u2500\u2500\u2500
|
|
2438
|
+
|
|
2439
|
+
function api(method, path, body) {
|
|
2440
|
+
var opts = { method: method, headers: { 'Content-Type': 'application/json' } };
|
|
2441
|
+
if (body !== undefined) opts.body = JSON.stringify(body);
|
|
2442
|
+
return fetch(path, opts).then(function(res) {
|
|
2443
|
+
return res.json().then(function(data) {
|
|
2444
|
+
if (!res.ok) throw new Error(data.error || 'Request failed (' + res.status + ')');
|
|
2445
|
+
return data;
|
|
2446
|
+
});
|
|
2447
|
+
});
|
|
2448
|
+
}
|
|
2449
|
+
|
|
2450
|
+
// \u2500\u2500\u2500 Toast notifications \u2500\u2500\u2500
|
|
2451
|
+
|
|
2452
|
+
function showToast(message, type) {
|
|
2453
|
+
var container = document.getElementById('toast-container');
|
|
2454
|
+
var toast = document.createElement('div');
|
|
2455
|
+
toast.className = 'toast toast-' + (type || 'info');
|
|
2456
|
+
toast.textContent = message;
|
|
2457
|
+
container.appendChild(toast);
|
|
2458
|
+
setTimeout(function() {
|
|
2459
|
+
toast.style.transition = 'opacity 300ms';
|
|
2460
|
+
toast.style.opacity = '0';
|
|
2461
|
+
setTimeout(function() { toast.remove(); }, 300);
|
|
2462
|
+
}, 4000);
|
|
2463
|
+
}
|
|
2464
|
+
|
|
2465
|
+
// \u2500\u2500\u2500 Modal \u2500\u2500\u2500
|
|
2466
|
+
|
|
2467
|
+
function showModal(title, body, onConfirm, confirmLabel, confirmClass) {
|
|
2468
|
+
var root = document.getElementById('modal-root');
|
|
2469
|
+
root.innerHTML =
|
|
2470
|
+
'<div class="modal-overlay" id="modal-overlay">' +
|
|
2471
|
+
'<div class="modal">' +
|
|
2472
|
+
'<div class="modal-title">' + esc(title) + '</div>' +
|
|
2473
|
+
'<div class="modal-body">' + esc(body) + '</div>' +
|
|
2474
|
+
'<div class="modal-actions">' +
|
|
2475
|
+
'<button class="btn btn-secondary" id="modal-cancel">Cancel</button>' +
|
|
2476
|
+
'<button class="btn ' + (confirmClass || 'btn-danger') + '" id="modal-confirm">' + esc(confirmLabel || 'Confirm') + '</button>' +
|
|
2477
|
+
'</div>' +
|
|
2478
|
+
'</div>' +
|
|
2479
|
+
'</div>';
|
|
2480
|
+
document.getElementById('modal-cancel').onclick = function() { root.innerHTML = ''; };
|
|
2481
|
+
document.getElementById('modal-overlay').onclick = function(e) {
|
|
2482
|
+
if (e.target === e.currentTarget) root.innerHTML = '';
|
|
2483
|
+
};
|
|
2484
|
+
document.getElementById('modal-confirm').onclick = function() {
|
|
2485
|
+
root.innerHTML = '';
|
|
2486
|
+
onConfirm();
|
|
2487
|
+
};
|
|
2488
|
+
}
|
|
2489
|
+
|
|
2490
|
+
function hideModal() {
|
|
2491
|
+
document.getElementById('modal-root').innerHTML = '';
|
|
2492
|
+
}
|
|
2493
|
+
|
|
2494
|
+
// \u2500\u2500\u2500 State \u2500\u2500\u2500
|
|
2495
|
+
|
|
2496
|
+
var state = {
|
|
2497
|
+
stores: [],
|
|
2498
|
+
storesLoaded: false,
|
|
2499
|
+
currentStore: null,
|
|
2500
|
+
searchResults: null,
|
|
2501
|
+
refreshTimer: null,
|
|
2502
|
+
mobileNavOpen: false
|
|
2503
|
+
};
|
|
2504
|
+
|
|
2505
|
+
// \u2500\u2500\u2500 Type helpers \u2500\u2500\u2500
|
|
2506
|
+
|
|
2507
|
+
function typeIcon(type) {
|
|
2508
|
+
switch (type) {
|
|
2509
|
+
case 'repo': return ICONS.repo;
|
|
2510
|
+
case 'file': return ICONS.file;
|
|
2511
|
+
case 'web': return ICONS.web;
|
|
2512
|
+
default: return '';
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
|
|
2516
|
+
function storeStatus(store) {
|
|
2517
|
+
if (store.status === 'indexing') return 'indexing';
|
|
2518
|
+
if (store.status === 'error') return 'error';
|
|
2519
|
+
if (store.modelId) return 'ready';
|
|
2520
|
+
return 'unknown';
|
|
2521
|
+
}
|
|
2522
|
+
|
|
2523
|
+
function storeStatusLabel(store) {
|
|
2524
|
+
var s = storeStatus(store);
|
|
2525
|
+
switch (s) {
|
|
2526
|
+
case 'ready': return 'Ready';
|
|
2527
|
+
case 'indexing': return 'Indexing';
|
|
2528
|
+
case 'error': return 'Error';
|
|
2529
|
+
default: return 'Not indexed';
|
|
2530
|
+
}
|
|
2531
|
+
}
|
|
2532
|
+
|
|
2533
|
+
function scoreColor(score) {
|
|
2534
|
+
if (score >= 0.7) return 'var(--score-high)';
|
|
2535
|
+
if (score >= 0.4) return 'var(--score-medium)';
|
|
2536
|
+
return 'var(--score-low)';
|
|
2537
|
+
}
|
|
2538
|
+
|
|
2539
|
+
function scoreClass(score) {
|
|
2540
|
+
if (score >= 0.7) return 'high';
|
|
2541
|
+
if (score >= 0.4) return 'medium';
|
|
2542
|
+
return 'low';
|
|
2543
|
+
}
|
|
2544
|
+
|
|
2545
|
+
function resultTypeIcon(type) {
|
|
2546
|
+
var map = {
|
|
2547
|
+
'function': '<span class="type-icon type-fn">fn</span>',
|
|
2548
|
+
'class': '<span class="type-icon type-class">C</span>',
|
|
2549
|
+
'interface': '<span class="type-icon type-iface">I</span>',
|
|
2550
|
+
'type': '<span class="type-icon type-type">T</span>',
|
|
2551
|
+
'const': '<span class="type-icon type-const">K</span>',
|
|
2552
|
+
'documentation': '<span class="type-icon type-doc">D</span>',
|
|
2553
|
+
'example': '<span class="type-icon type-example">E</span>'
|
|
2554
|
+
};
|
|
2555
|
+
return map[type] || '<span class="type-icon type-default">?</span>';
|
|
2556
|
+
}
|
|
2557
|
+
|
|
2558
|
+
// \u2500\u2500\u2500 Routing \u2500\u2500\u2500
|
|
2559
|
+
|
|
2560
|
+
function navigate(hash) {
|
|
2561
|
+
window.location.hash = hash;
|
|
2562
|
+
}
|
|
2563
|
+
|
|
2564
|
+
function getRoute() {
|
|
2565
|
+
var hash = window.location.hash || '#/';
|
|
2566
|
+
if (hash === '#/' || hash === '#/stores') return { view: 'dashboard' };
|
|
2567
|
+
var storeMatch = hash.match(/^#\\/stores\\/(.+)$/);
|
|
2568
|
+
if (storeMatch) return { view: 'detail', id: decodeURIComponent(storeMatch[1]) };
|
|
2569
|
+
if (hash === '#/create') return { view: 'create' };
|
|
2570
|
+
if (hash.startsWith('#/search')) {
|
|
2571
|
+
var params = new URLSearchParams(hash.replace('#/search', '').replace('?', ''));
|
|
2572
|
+
return { view: 'search', query: params.get('q') || '' };
|
|
2573
|
+
}
|
|
2574
|
+
return { view: 'dashboard' };
|
|
2575
|
+
}
|
|
2576
|
+
|
|
2577
|
+
// \u2500\u2500\u2500 Rendering \u2500\u2500\u2500
|
|
2578
|
+
|
|
2579
|
+
function renderApp() {
|
|
2580
|
+
clearInterval(state.refreshTimer);
|
|
2581
|
+
state.refreshTimer = null;
|
|
2582
|
+
|
|
2583
|
+
var route = getRoute();
|
|
2584
|
+
var navActive = route.view;
|
|
2585
|
+
|
|
2586
|
+
var html =
|
|
2587
|
+
'<div class="app-shell">' +
|
|
2588
|
+
renderSidebar(navActive) +
|
|
2589
|
+
'<div class="main-content" id="main-content">' +
|
|
2590
|
+
renderLoading() +
|
|
2591
|
+
'</div>' +
|
|
2592
|
+
'</div>' +
|
|
2593
|
+
'<button class="mobile-nav-toggle" id="mobile-nav-toggle">' + ICONS.hamburger + '</button>';
|
|
2594
|
+
|
|
2595
|
+
document.getElementById('app').innerHTML = html;
|
|
2596
|
+
|
|
2597
|
+
document.getElementById('mobile-nav-toggle').onclick = function() {
|
|
2598
|
+
var sidebar = document.querySelector('.sidebar');
|
|
2599
|
+
state.mobileNavOpen = !state.mobileNavOpen;
|
|
2600
|
+
if (state.mobileNavOpen) {
|
|
2601
|
+
sidebar.classList.add('mobile-open');
|
|
2602
|
+
} else {
|
|
2603
|
+
sidebar.classList.remove('mobile-open');
|
|
2604
|
+
}
|
|
2605
|
+
};
|
|
2606
|
+
|
|
2607
|
+
switch (route.view) {
|
|
2608
|
+
case 'dashboard': loadDashboard(); break;
|
|
2609
|
+
case 'detail': loadStoreDetail(route.id); break;
|
|
2610
|
+
case 'create': renderCreateStore(); break;
|
|
2611
|
+
case 'search': renderSearchView(route.query); break;
|
|
2612
|
+
default: loadDashboard();
|
|
2613
|
+
}
|
|
2614
|
+
}
|
|
2615
|
+
|
|
2616
|
+
function renderSidebar(active) {
|
|
2617
|
+
return (
|
|
2618
|
+
'<div class="sidebar">' +
|
|
2619
|
+
'<div class="sidebar-brand">' +
|
|
2620
|
+
'<div class="sidebar-brand-icon">' + ICONS.brand + '</div>' +
|
|
2621
|
+
'<span class="sidebar-brand-text">Knowledge</span>' +
|
|
2622
|
+
'</div>' +
|
|
2623
|
+
'<nav class="sidebar-nav">' +
|
|
2624
|
+
'<a class="nav-item' + (active === 'dashboard' ? ' active' : '') + '" href="#/stores">' +
|
|
2625
|
+
ICONS.stores + ' Stores' +
|
|
2626
|
+
'</a>' +
|
|
2627
|
+
'<a class="nav-item' + (active === 'search' ? ' active' : '') + '" href="#/search">' +
|
|
2628
|
+
ICONS.search + ' Search' +
|
|
2629
|
+
'</a>' +
|
|
2630
|
+
'<a class="nav-item' + (active === 'create' ? ' active' : '') + '" href="#/create">' +
|
|
2631
|
+
ICONS.plus + ' Create Store' +
|
|
2632
|
+
'</a>' +
|
|
2633
|
+
'</nav>' +
|
|
2634
|
+
'<div class="sidebar-footer">Bluera Knowledge Admin</div>' +
|
|
2635
|
+
'</div>'
|
|
2636
|
+
);
|
|
2637
|
+
}
|
|
2638
|
+
|
|
2639
|
+
function renderLoading() {
|
|
2640
|
+
return '<div class="loading-center"><div class="spinner spinner-lg"></div> Loading...</div>';
|
|
2641
|
+
}
|
|
2642
|
+
|
|
2643
|
+
function renderSkeletonCards(n) {
|
|
2644
|
+
var html = '<div class="store-grid">';
|
|
2645
|
+
for (var i = 0; i < n; i++) {
|
|
2646
|
+
html += '<div class="skeleton skeleton-card"></div>';
|
|
2647
|
+
}
|
|
2648
|
+
html += '</div>';
|
|
2649
|
+
return html;
|
|
2650
|
+
}
|
|
2651
|
+
|
|
2652
|
+
// \u2500\u2500\u2500 Dashboard \u2500\u2500\u2500
|
|
2653
|
+
|
|
2654
|
+
function loadDashboard() {
|
|
2655
|
+
var main = document.getElementById('main-content');
|
|
2656
|
+
main.innerHTML = renderSkeletonCards(6);
|
|
2657
|
+
|
|
2658
|
+
api('GET', '/api/stores').then(function(stores) {
|
|
2659
|
+
state.stores = stores;
|
|
2660
|
+
state.storesLoaded = true;
|
|
2661
|
+
renderDashboardContent();
|
|
2662
|
+
|
|
2663
|
+
state.refreshTimer = setInterval(function() {
|
|
2664
|
+
api('GET', '/api/stores').then(function(s) {
|
|
2665
|
+
state.stores = s;
|
|
2666
|
+
if (getRoute().view === 'dashboard') renderDashboardContent();
|
|
2667
|
+
}).catch(function() {});
|
|
2668
|
+
}, 10000);
|
|
2669
|
+
}).catch(function(err) {
|
|
2670
|
+
main.innerHTML = '<div class="empty-state"><div class="empty-state-title">Failed to load stores</div><p>' + esc(err.message) + '</p></div>';
|
|
2671
|
+
});
|
|
2672
|
+
}
|
|
2673
|
+
|
|
2674
|
+
function renderDashboardContent() {
|
|
2675
|
+
var main = document.getElementById('main-content');
|
|
2676
|
+
var stores = state.stores;
|
|
2677
|
+
|
|
2678
|
+
var repoCount = 0, fileCount = 0, webCount = 0, indexedCount = 0;
|
|
2679
|
+
for (var i = 0; i < stores.length; i++) {
|
|
2680
|
+
if (stores[i].type === 'repo') repoCount++;
|
|
2681
|
+
if (stores[i].type === 'file') fileCount++;
|
|
2682
|
+
if (stores[i].type === 'web') webCount++;
|
|
2683
|
+
if (stores[i].modelId) indexedCount++;
|
|
2684
|
+
}
|
|
2685
|
+
|
|
2686
|
+
var html =
|
|
2687
|
+
'<div class="page-header">' +
|
|
2688
|
+
'<div>' +
|
|
2689
|
+
'<h1 class="page-title">Knowledge Stores</h1>' +
|
|
2690
|
+
'<p class="page-subtitle">' + stores.length + ' store' + (stores.length !== 1 ? 's' : '') + ' configured</p>' +
|
|
2691
|
+
'</div>' +
|
|
2692
|
+
'<a class="btn btn-primary" href="#/create">' + ICONS.plus + ' Create Store</a>' +
|
|
2693
|
+
'</div>' +
|
|
2694
|
+
'<div class="stats-bar">' +
|
|
2695
|
+
renderStatCard(stores.length, 'Total') +
|
|
2696
|
+
renderStatCard(indexedCount, 'Indexed') +
|
|
2697
|
+
renderStatCard(stores.length - indexedCount, 'Pending') +
|
|
2698
|
+
(repoCount > 0 ? renderStatCard(repoCount, 'Repo') : '') +
|
|
2699
|
+
(fileCount > 0 ? renderStatCard(fileCount, 'File') : '') +
|
|
2700
|
+
(webCount > 0 ? renderStatCard(webCount, 'Web') : '') +
|
|
2701
|
+
'</div>';
|
|
2702
|
+
|
|
2703
|
+
if (stores.length === 0) {
|
|
2704
|
+
html += '<div class="empty-state"><div class="empty-state-title">No knowledge stores</div><p>Create a store to start indexing code and documentation.</p></div>';
|
|
2705
|
+
} else {
|
|
2706
|
+
html += '<div class="store-grid">';
|
|
2707
|
+
for (var j = 0; j < stores.length; j++) {
|
|
2708
|
+
html += renderStoreCard(stores[j]);
|
|
2709
|
+
}
|
|
2710
|
+
html += '</div>';
|
|
2711
|
+
}
|
|
2712
|
+
|
|
2713
|
+
main.innerHTML = html;
|
|
2714
|
+
bindCardClicks();
|
|
2715
|
+
}
|
|
2716
|
+
|
|
2717
|
+
function renderStatCard(value, label) {
|
|
2718
|
+
return (
|
|
2719
|
+
'<div class="stat-card">' +
|
|
2720
|
+
'<span class="stat-value">' + value + '</span>' +
|
|
2721
|
+
'<span class="stat-label">' + esc(label) + '</span>' +
|
|
2722
|
+
'</div>'
|
|
2723
|
+
);
|
|
2724
|
+
}
|
|
2725
|
+
|
|
2726
|
+
function renderStoreCard(store) {
|
|
2727
|
+
var st = storeStatus(store);
|
|
2728
|
+
var loc = store.path || store.url || '';
|
|
2729
|
+
var truncLoc = loc.length > 60 ? '...' + loc.slice(loc.length - 57) : loc;
|
|
2730
|
+
|
|
2731
|
+
var tagsHtml = '';
|
|
2732
|
+
if (store.tags && store.tags.length > 0) {
|
|
2733
|
+
tagsHtml = '<div class="card-tags">';
|
|
2734
|
+
for (var i = 0; i < store.tags.length; i++) {
|
|
2735
|
+
tagsHtml += '<span class="tag">' + esc(store.tags[i]) + '</span>';
|
|
2736
|
+
}
|
|
2737
|
+
tagsHtml += '</div>';
|
|
2738
|
+
}
|
|
2739
|
+
|
|
2740
|
+
var descHtml = store.description
|
|
2741
|
+
? '<p class="card-description">' + esc(store.description) + '</p>'
|
|
2742
|
+
: '';
|
|
2743
|
+
|
|
2744
|
+
var branchHtml = store.branch
|
|
2745
|
+
? '<span class="meta-separator"></span><span class="mono">' + esc(store.branch) + '</span>'
|
|
2746
|
+
: '';
|
|
2747
|
+
|
|
2748
|
+
return (
|
|
2749
|
+
'<div class="store-card" data-store-id="' + esc(store.id) + '">' +
|
|
2750
|
+
'<div class="card-header">' +
|
|
2751
|
+
'<div class="card-title-row">' +
|
|
2752
|
+
'<div class="card-status"><span class="status-dot status-' + st + '" title="' + esc(storeStatusLabel(store)) + '"></span></div>' +
|
|
2753
|
+
'<h3 class="card-title">' + esc(store.name) + '</h3>' +
|
|
2754
|
+
'<span class="badge badge-' + store.type + '">' + typeIcon(store.type) + '<span style="margin-left:4px">' + store.type + '</span></span>' +
|
|
2755
|
+
'</div>' +
|
|
2756
|
+
descHtml +
|
|
2757
|
+
'</div>' +
|
|
2758
|
+
'<div class="card-body">' +
|
|
2759
|
+
(loc ? '<div class="card-location mono truncate" title="' + esc(loc) + '">' + esc(truncLoc) + '</div>' : '') +
|
|
2760
|
+
tagsHtml +
|
|
2761
|
+
'</div>' +
|
|
2762
|
+
'<div class="card-footer"><div class="meta-row">' +
|
|
2763
|
+
'<span title="' + esc(store.createdAt) + '">' + formatDate(store.createdAt) + '</span>' +
|
|
2764
|
+
branchHtml +
|
|
2765
|
+
'</div></div>' +
|
|
2766
|
+
'</div>'
|
|
2767
|
+
);
|
|
2768
|
+
}
|
|
2769
|
+
|
|
2770
|
+
function bindCardClicks() {
|
|
2771
|
+
var cards = document.querySelectorAll('.store-card[data-store-id]');
|
|
2772
|
+
for (var i = 0; i < cards.length; i++) {
|
|
2773
|
+
(function(card) {
|
|
2774
|
+
card.onclick = function() {
|
|
2775
|
+
navigate('#/stores/' + encodeURIComponent(card.getAttribute('data-store-id')));
|
|
2776
|
+
};
|
|
2777
|
+
})(cards[i]);
|
|
2778
|
+
}
|
|
2779
|
+
}
|
|
2780
|
+
|
|
2781
|
+
// \u2500\u2500\u2500 Store Detail \u2500\u2500\u2500
|
|
2782
|
+
|
|
2783
|
+
function loadStoreDetail(id) {
|
|
2784
|
+
var main = document.getElementById('main-content');
|
|
2785
|
+
main.innerHTML = renderLoading();
|
|
2786
|
+
|
|
2787
|
+
api('GET', '/api/stores/' + encodeURIComponent(id)).then(function(store) {
|
|
2788
|
+
state.currentStore = store;
|
|
2789
|
+
renderStoreDetailContent(store);
|
|
2790
|
+
}).catch(function(err) {
|
|
2791
|
+
main.innerHTML =
|
|
2792
|
+
'<div class="breadcrumb"><a href="#/stores">Stores</a><span class="breadcrumb-sep">' + ICONS.chevron + '</span><span class="breadcrumb-current">Not found</span></div>' +
|
|
2793
|
+
'<div class="empty-state"><div class="empty-state-title">Store not found</div><p>' + esc(err.message) + '</p></div>';
|
|
2794
|
+
});
|
|
2795
|
+
}
|
|
2796
|
+
|
|
2797
|
+
function renderStoreDetailContent(store) {
|
|
2798
|
+
var main = document.getElementById('main-content');
|
|
2799
|
+
var st = storeStatus(store);
|
|
2800
|
+
|
|
2801
|
+
var rows = [];
|
|
2802
|
+
|
|
2803
|
+
// URL first for repo/web stores \u2014 externally linked
|
|
2804
|
+
if (store.url) rows.push({ key: 'URL', value: store.url, link: true });
|
|
2805
|
+
|
|
2806
|
+
rows.push({ key: 'ID', value: store.id });
|
|
2807
|
+
rows.push({ key: 'Name', value: store.name });
|
|
2808
|
+
rows.push({ key: 'Type', value: store.type });
|
|
2809
|
+
rows.push({ key: 'Status', value: storeStatusLabel(store) });
|
|
2810
|
+
|
|
2811
|
+
if (store.path) rows.push({ key: 'Path', value: store.path });
|
|
2812
|
+
if (store.branch) rows.push({ key: 'Branch', value: store.branch });
|
|
2813
|
+
if (store.depth != null) rows.push({ key: 'Depth', value: String(store.depth) });
|
|
2814
|
+
if (store.maxPages != null) rows.push({ key: 'Max Pages', value: String(store.maxPages) });
|
|
2815
|
+
if (store.modelId) rows.push({ key: 'Model', value: store.modelId });
|
|
2816
|
+
if (store.schemaVersion != null) rows.push({ key: 'Schema', value: 'v' + store.schemaVersion });
|
|
2817
|
+
rows.push({ key: 'Created', value: formatFullDate(store.createdAt) });
|
|
2818
|
+
rows.push({ key: 'Updated', value: formatFullDate(store.updatedAt) });
|
|
2819
|
+
|
|
2820
|
+
var detailRows = '';
|
|
2821
|
+
for (var i = 0; i < rows.length; i++) {
|
|
2822
|
+
var val = rows[i].link
|
|
2823
|
+
? '<a href="' + esc(rows[i].value) + '" target="_blank" rel="noopener noreferrer" style="color:var(--text-link);text-decoration:none">' + esc(rows[i].value) + ' <span style="font-size:11px;opacity:0.6">\u2197</span></a>'
|
|
2824
|
+
: esc(rows[i].value);
|
|
2825
|
+
detailRows += '<div class="detail-row"><span class="detail-key">' + esc(rows[i].key) + '</span><span class="detail-value mono">' + val + '</span></div>';
|
|
2826
|
+
}
|
|
2827
|
+
|
|
2828
|
+
var tagsHtml = '';
|
|
2829
|
+
if (store.tags && store.tags.length > 0) {
|
|
2830
|
+
tagsHtml = '<div class="detail-row"><span class="detail-key">Tags</span><span class="detail-value"><div class="card-tags">';
|
|
2831
|
+
for (var t = 0; t < store.tags.length; t++) {
|
|
2832
|
+
tagsHtml += '<span class="tag">' + esc(store.tags[t]) + '</span>';
|
|
2833
|
+
}
|
|
2834
|
+
tagsHtml += '</div></span></div>';
|
|
2835
|
+
}
|
|
2836
|
+
|
|
2837
|
+
var descHtml = '';
|
|
2838
|
+
if (store.description) {
|
|
2839
|
+
descHtml = '<div class="detail-row"><span class="detail-key">Description</span><span class="detail-value" style="text-align:left;max-width:none;word-break:normal">' + esc(store.description) + '</span></div>';
|
|
2840
|
+
}
|
|
2841
|
+
|
|
2842
|
+
var html =
|
|
2843
|
+
'<div class="breadcrumb">' +
|
|
2844
|
+
'<a href="#/stores">Stores</a>' +
|
|
2845
|
+
'<span class="breadcrumb-sep">' + ICONS.chevron + '</span>' +
|
|
2846
|
+
'<span class="breadcrumb-current">' + esc(store.name) + '</span>' +
|
|
2847
|
+
'</div>' +
|
|
2848
|
+
'<div class="page-header">' +
|
|
2849
|
+
'<div style="display:flex;align-items:center;gap:var(--space-md)">' +
|
|
2850
|
+
'<span class="status-dot status-' + st + '" style="width:12px;height:12px" title="' + esc(storeStatusLabel(store)) + '"></span>' +
|
|
2851
|
+
'<h1 class="page-title">' + esc(store.name) + '</h1>' +
|
|
2852
|
+
'<span class="badge badge-' + store.type + '">' + typeIcon(store.type) + '<span style="margin-left:4px">' + store.type + '</span></span>' +
|
|
2853
|
+
'</div>' +
|
|
2854
|
+
'</div>' +
|
|
2855
|
+
'<div class="store-actions"><div class="btn-group">' +
|
|
2856
|
+
'<button class="btn btn-secondary" id="btn-reindex">' + ICONS.refresh + ' Re-index</button>' +
|
|
2857
|
+
'<button class="btn btn-danger" id="btn-delete">' + ICONS.trash + ' Delete</button>' +
|
|
2858
|
+
'</div></div>' +
|
|
2859
|
+
'<div class="detail-grid">' +
|
|
2860
|
+
'<div class="detail-card">' + detailRows + '</div>' +
|
|
2861
|
+
'<div class="detail-card">' + descHtml + tagsHtml +
|
|
2862
|
+
((!store.description && (!store.tags || store.tags.length === 0))
|
|
2863
|
+
? '<div class="empty-state" style="padding:var(--space-lg) 0"><p>No description or tags</p></div>'
|
|
2864
|
+
: '') +
|
|
2865
|
+
'</div>' +
|
|
2866
|
+
'</div>' +
|
|
2867
|
+
'<div class="inline-search-section">' +
|
|
2868
|
+
'<h2 class="section-title">Search in ' + esc(store.name) + '</h2>' +
|
|
2869
|
+
'<div class="search-bar">' +
|
|
2870
|
+
'<input class="form-input" id="detail-search-input" type="text" placeholder="Search within this store..." />' +
|
|
2871
|
+
'<button class="btn btn-primary" id="detail-search-btn">' + ICONS.search + ' Search</button>' +
|
|
2872
|
+
'</div>' +
|
|
2873
|
+
'<div id="detail-search-results"></div>' +
|
|
2874
|
+
'</div>' +
|
|
2875
|
+
'<div class="documents-section">' +
|
|
2876
|
+
'<h2 class="section-title">Indexed Chunks</h2>' +
|
|
2877
|
+
'<div id="documents-container"><div class="loading-center"><div class="spinner"></div> Loading chunks...</div></div>' +
|
|
2878
|
+
'</div>';
|
|
2879
|
+
|
|
2880
|
+
main.innerHTML = html;
|
|
2881
|
+
|
|
2882
|
+
// Load documents
|
|
2883
|
+
loadDocuments(store.id, 0);
|
|
2884
|
+
|
|
2885
|
+
// Bind actions
|
|
2886
|
+
document.getElementById('btn-reindex').onclick = function() {
|
|
2887
|
+
var btn = this;
|
|
2888
|
+
btn.disabled = true;
|
|
2889
|
+
btn.innerHTML = '<div class="spinner"></div> Indexing...';
|
|
2890
|
+
api('POST', '/api/stores/' + encodeURIComponent(store.id) + '/index').then(function() {
|
|
2891
|
+
showToast('Re-indexing complete', 'success');
|
|
2892
|
+
loadStoreDetail(store.id);
|
|
2893
|
+
}).catch(function(err) {
|
|
2894
|
+
showToast('Re-index failed: ' + err.message, 'error');
|
|
2895
|
+
btn.disabled = false;
|
|
2896
|
+
btn.innerHTML = ICONS.refresh + ' Re-index';
|
|
2897
|
+
});
|
|
2898
|
+
};
|
|
2899
|
+
|
|
2900
|
+
document.getElementById('btn-delete').onclick = function() {
|
|
2901
|
+
showModal(
|
|
2902
|
+
'Delete Store',
|
|
2903
|
+
'Are you sure you want to delete "' + store.name + '"? This action cannot be undone.',
|
|
2904
|
+
function() {
|
|
2905
|
+
api('DELETE', '/api/stores/' + encodeURIComponent(store.id)).then(function() {
|
|
2906
|
+
showToast('Store deleted', 'success');
|
|
2907
|
+
navigate('#/stores');
|
|
2908
|
+
}).catch(function(err) {
|
|
2909
|
+
showToast('Delete failed: ' + err.message, 'error');
|
|
2910
|
+
});
|
|
2911
|
+
},
|
|
2912
|
+
'Delete',
|
|
2913
|
+
'btn-danger'
|
|
2914
|
+
);
|
|
2915
|
+
};
|
|
2916
|
+
|
|
2917
|
+
var searchInput = document.getElementById('detail-search-input');
|
|
2918
|
+
var searchBtn = document.getElementById('detail-search-btn');
|
|
2919
|
+
|
|
2920
|
+
function doDetailSearch() {
|
|
2921
|
+
var q = searchInput.value.trim();
|
|
2922
|
+
if (!q) return;
|
|
2923
|
+
var resultsDiv = document.getElementById('detail-search-results');
|
|
2924
|
+
resultsDiv.innerHTML = '<div class="loading-center"><div class="spinner"></div> Searching...</div>';
|
|
2925
|
+
api('POST', '/api/search', { query: q, stores: [store.id], detail: 'contextual' }).then(function(data) {
|
|
2926
|
+
resultsDiv.innerHTML = renderSearchResults(data);
|
|
2927
|
+
}).catch(function(err) {
|
|
2928
|
+
resultsDiv.innerHTML = '<div class="form-error">' + esc(err.message) + '</div>';
|
|
2929
|
+
});
|
|
2930
|
+
}
|
|
2931
|
+
|
|
2932
|
+
searchBtn.onclick = doDetailSearch;
|
|
2933
|
+
searchInput.onkeydown = function(e) {
|
|
2934
|
+
if (e.key === 'Enter') doDetailSearch();
|
|
2935
|
+
};
|
|
2936
|
+
}
|
|
2937
|
+
|
|
2938
|
+
function loadDocuments(storeId, offset) {
|
|
2939
|
+
var limit = 15;
|
|
2940
|
+
var container = document.getElementById('documents-container');
|
|
2941
|
+
if (!container) return;
|
|
2942
|
+
|
|
2943
|
+
api('GET', '/api/stores/' + encodeURIComponent(storeId) + '/documents?limit=' + limit + '&offset=' + offset).then(function(data) {
|
|
2944
|
+
if (data.total === 0) {
|
|
2945
|
+
container.innerHTML = '<div class="empty-state" style="padding:var(--space-xl) 0"><p>No indexed chunks yet. Try re-indexing this store.</p></div>';
|
|
2946
|
+
return;
|
|
2947
|
+
}
|
|
2948
|
+
|
|
2949
|
+
var html = '<div class="documents-header">' +
|
|
2950
|
+
'<span class="text-secondary" style="font-size:var(--font-size-sm)">' + data.total + ' chunks indexed</span>' +
|
|
2951
|
+
'<span class="text-tertiary" style="font-size:var(--font-size-xs)">Page ' + (Math.floor(offset / limit) + 1) + ' of ' + Math.ceil(data.total / limit) + '</span>' +
|
|
2952
|
+
'</div>';
|
|
2953
|
+
html += '<div class="documents-list">';
|
|
2954
|
+
for (var i = 0; i < data.documents.length; i++) {
|
|
2955
|
+
var doc = data.documents[i];
|
|
2956
|
+
var path = (doc.metadata && doc.metadata.path) ? doc.metadata.path : 'unknown';
|
|
2957
|
+
var chunkLabel = '';
|
|
2958
|
+
if (doc.metadata && doc.metadata.chunkIndex != null) {
|
|
2959
|
+
chunkLabel = '<span class="document-chunk-label">chunk ' + doc.metadata.chunkIndex + ' of ' + (doc.metadata.totalChunks || '?') + '</span>';
|
|
2960
|
+
}
|
|
2961
|
+
var typeBadge = doc.metadata && doc.metadata.type
|
|
2962
|
+
? '<span class="badge badge-' + (doc.metadata.type === 'web' ? 'web' : doc.metadata.type === 'chunk' ? 'file' : 'file') + '" style="font-size:10px;padding:1px 6px;margin-left:auto">' + esc(doc.metadata.type) + '</span>'
|
|
2963
|
+
: '';
|
|
2964
|
+
var preview = doc.content.length > 800 ? doc.content.substring(0, 800) + ' ... (truncated)' : doc.content;
|
|
2965
|
+
|
|
2966
|
+
html += '<div class="document-item">' +
|
|
2967
|
+
'<div class="document-meta">' +
|
|
2968
|
+
'<span class="document-index">' + (offset + i + 1) + '</span>' +
|
|
2969
|
+
'<span class="document-path mono">' + esc(path) + '</span>' +
|
|
2970
|
+
chunkLabel +
|
|
2971
|
+
typeBadge +
|
|
2972
|
+
'</div>' +
|
|
2973
|
+
'<pre class="document-content code-block">' + esc(preview) + '</pre>' +
|
|
2974
|
+
'</div>';
|
|
2975
|
+
}
|
|
2976
|
+
html += '</div>';
|
|
2977
|
+
|
|
2978
|
+
// Pagination
|
|
2979
|
+
html += '<div class="documents-pagination">';
|
|
2980
|
+
if (offset > 0) {
|
|
2981
|
+
html += '<button class="btn btn-secondary btn-sm" id="docs-prev">← Previous</button>';
|
|
2982
|
+
}
|
|
2983
|
+
html += '<span class="text-secondary" style="font-size:var(--font-size-sm)">' +
|
|
2984
|
+
(offset + 1) + '\u2013' + Math.min(offset + limit, data.total) + ' of ' + data.total +
|
|
2985
|
+
'</span>';
|
|
2986
|
+
if (offset + limit < data.total) {
|
|
2987
|
+
html += '<button class="btn btn-secondary btn-sm" id="docs-next">Next →</button>';
|
|
2988
|
+
}
|
|
2989
|
+
html += '</div>';
|
|
2990
|
+
|
|
2991
|
+
container.innerHTML = html;
|
|
2992
|
+
|
|
2993
|
+
// Bind pagination
|
|
2994
|
+
var prevBtn = document.getElementById('docs-prev');
|
|
2995
|
+
if (prevBtn) prevBtn.onclick = function() { loadDocuments(storeId, Math.max(0, offset - limit)); };
|
|
2996
|
+
var nextBtn = document.getElementById('docs-next');
|
|
2997
|
+
if (nextBtn) nextBtn.onclick = function() { loadDocuments(storeId, offset + limit); };
|
|
2998
|
+
}).catch(function(err) {
|
|
2999
|
+
container.innerHTML = '<div class="form-error">' + esc(err.message) + '</div>';
|
|
3000
|
+
});
|
|
3001
|
+
}
|
|
3002
|
+
|
|
3003
|
+
// \u2500\u2500\u2500 Create Store \u2500\u2500\u2500
|
|
3004
|
+
|
|
3005
|
+
function renderCreateStore() {
|
|
3006
|
+
var main = document.getElementById('main-content');
|
|
3007
|
+
|
|
3008
|
+
var html =
|
|
3009
|
+
'<div class="breadcrumb">' +
|
|
3010
|
+
'<a href="#/stores">Stores</a>' +
|
|
3011
|
+
'<span class="breadcrumb-sep">' + ICONS.chevron + '</span>' +
|
|
3012
|
+
'<span class="breadcrumb-current">Create Store</span>' +
|
|
3013
|
+
'</div>' +
|
|
3014
|
+
'<h1 class="page-title" style="margin-bottom:var(--space-xl)">Create Store</h1>' +
|
|
3015
|
+
'<div class="form-card">' +
|
|
3016
|
+
'<div class="form-group">' +
|
|
3017
|
+
'<label class="form-label">Type</label>' +
|
|
3018
|
+
'<select class="form-select" id="create-type">' +
|
|
3019
|
+
'<option value="repo">Repository</option>' +
|
|
3020
|
+
'<option value="file">File / Directory</option>' +
|
|
3021
|
+
'<option value="web">Web</option>' +
|
|
3022
|
+
'</select>' +
|
|
3023
|
+
'</div>' +
|
|
3024
|
+
'<div class="form-group">' +
|
|
3025
|
+
'<label class="form-label">Name</label>' +
|
|
3026
|
+
'<input class="form-input" id="create-name" type="text" placeholder="my-project" />' +
|
|
3027
|
+
'</div>' +
|
|
3028
|
+
'<div class="form-group">' +
|
|
3029
|
+
'<label class="form-label">Description <span style="color:var(--text-tertiary)">(optional)</span></label>' +
|
|
3030
|
+
'<textarea class="form-textarea" id="create-description" placeholder="What this store contains..."></textarea>' +
|
|
3031
|
+
'</div>' +
|
|
3032
|
+
'<div id="create-dynamic-fields"></div>' +
|
|
3033
|
+
'<div class="form-group">' +
|
|
3034
|
+
'<label class="form-label">Tags <span style="color:var(--text-tertiary)">(optional, comma-separated)</span></label>' +
|
|
3035
|
+
'<input class="form-input" id="create-tags" type="text" placeholder="typescript, api, docs" />' +
|
|
3036
|
+
'</div>' +
|
|
3037
|
+
'<div id="create-error"></div>' +
|
|
3038
|
+
'<div class="btn-group" style="margin-top:var(--space-xl)">' +
|
|
3039
|
+
'<button class="btn btn-primary" id="create-submit">' + ICONS.plus + ' Create Store</button>' +
|
|
3040
|
+
'<a class="btn btn-secondary" href="#/stores">Cancel</a>' +
|
|
3041
|
+
'</div>' +
|
|
3042
|
+
'</div>';
|
|
3043
|
+
|
|
3044
|
+
main.innerHTML = html;
|
|
3045
|
+
|
|
3046
|
+
var typeSelect = document.getElementById('create-type');
|
|
3047
|
+
renderDynamicFields(typeSelect.value);
|
|
3048
|
+
typeSelect.onchange = function() { renderDynamicFields(this.value); };
|
|
3049
|
+
|
|
3050
|
+
document.getElementById('create-submit').onclick = submitCreateStore;
|
|
3051
|
+
}
|
|
3052
|
+
|
|
3053
|
+
function renderDynamicFields(type) {
|
|
3054
|
+
var container = document.getElementById('create-dynamic-fields');
|
|
3055
|
+
var html = '';
|
|
3056
|
+
|
|
3057
|
+
switch (type) {
|
|
3058
|
+
case 'repo':
|
|
3059
|
+
html =
|
|
3060
|
+
'<div class="form-row">' +
|
|
3061
|
+
'<div class="form-group">' +
|
|
3062
|
+
'<label class="form-label">URL <span style="color:var(--text-tertiary)">(or path)</span></label>' +
|
|
3063
|
+
'<input class="form-input" id="create-url" type="text" placeholder="https://github.com/org/repo" />' +
|
|
3064
|
+
'</div>' +
|
|
3065
|
+
'<div class="form-group">' +
|
|
3066
|
+
'<label class="form-label">Path <span style="color:var(--text-tertiary)">(or URL)</span></label>' +
|
|
3067
|
+
'<input class="form-input" id="create-path" type="text" placeholder="/path/to/repo" />' +
|
|
3068
|
+
'</div>' +
|
|
3069
|
+
'</div>' +
|
|
3070
|
+
'<div class="form-group">' +
|
|
3071
|
+
'<label class="form-label">Branch <span style="color:var(--text-tertiary)">(optional)</span></label>' +
|
|
3072
|
+
'<input class="form-input" id="create-branch" type="text" placeholder="main" />' +
|
|
3073
|
+
'</div>';
|
|
3074
|
+
break;
|
|
3075
|
+
case 'file':
|
|
3076
|
+
html =
|
|
3077
|
+
'<div class="form-group">' +
|
|
3078
|
+
'<label class="form-label">Path</label>' +
|
|
3079
|
+
'<input class="form-input" id="create-path" type="text" placeholder="/path/to/directory" />' +
|
|
3080
|
+
'<p class="form-hint">Absolute path to the file or directory to index</p>' +
|
|
3081
|
+
'</div>';
|
|
3082
|
+
break;
|
|
3083
|
+
case 'web':
|
|
3084
|
+
html =
|
|
3085
|
+
'<div class="form-group">' +
|
|
3086
|
+
'<label class="form-label">URL</label>' +
|
|
3087
|
+
'<input class="form-input" id="create-url" type="text" placeholder="https://docs.example.com" />' +
|
|
3088
|
+
'</div>' +
|
|
3089
|
+
'<div class="form-row">' +
|
|
3090
|
+
'<div class="form-group">' +
|
|
3091
|
+
'<label class="form-label">Depth <span style="color:var(--text-tertiary)">(optional)</span></label>' +
|
|
3092
|
+
'<input class="form-input" id="create-depth" type="number" min="1" placeholder="3" />' +
|
|
3093
|
+
'<p class="form-hint">How many levels deep to crawl</p>' +
|
|
3094
|
+
'</div>' +
|
|
3095
|
+
'<div class="form-group">' +
|
|
3096
|
+
'<label class="form-label">Max Pages <span style="color:var(--text-tertiary)">(optional)</span></label>' +
|
|
3097
|
+
'<input class="form-input" id="create-maxpages" type="number" min="1" placeholder="50" />' +
|
|
3098
|
+
'</div>' +
|
|
3099
|
+
'</div>';
|
|
3100
|
+
break;
|
|
3101
|
+
}
|
|
3102
|
+
|
|
3103
|
+
container.innerHTML = html;
|
|
3104
|
+
}
|
|
3105
|
+
|
|
3106
|
+
function submitCreateStore() {
|
|
3107
|
+
var btn = document.getElementById('create-submit');
|
|
3108
|
+
var errorDiv = document.getElementById('create-error');
|
|
3109
|
+
errorDiv.innerHTML = '';
|
|
3110
|
+
|
|
3111
|
+
var type = document.getElementById('create-type').value;
|
|
3112
|
+
var name = document.getElementById('create-name').value.trim();
|
|
3113
|
+
|
|
3114
|
+
if (!name) {
|
|
3115
|
+
errorDiv.innerHTML = '<div class="form-error">Name is required</div>';
|
|
3116
|
+
return;
|
|
3117
|
+
}
|
|
3118
|
+
|
|
3119
|
+
var body = { name: name, type: type };
|
|
3120
|
+
|
|
3121
|
+
var descEl = document.getElementById('create-description');
|
|
3122
|
+
if (descEl && descEl.value.trim()) body.description = descEl.value.trim();
|
|
3123
|
+
|
|
3124
|
+
var tagsEl = document.getElementById('create-tags');
|
|
3125
|
+
if (tagsEl && tagsEl.value.trim()) {
|
|
3126
|
+
body.tags = tagsEl.value.split(',').map(function(t) { return t.trim(); }).filter(function(t) { return t.length > 0; });
|
|
3127
|
+
}
|
|
3128
|
+
|
|
3129
|
+
var pathEl = document.getElementById('create-path');
|
|
3130
|
+
if (pathEl && pathEl.value.trim()) body.path = pathEl.value.trim();
|
|
3131
|
+
|
|
3132
|
+
var urlEl = document.getElementById('create-url');
|
|
3133
|
+
if (urlEl && urlEl.value.trim()) body.url = urlEl.value.trim();
|
|
3134
|
+
|
|
3135
|
+
var branchEl = document.getElementById('create-branch');
|
|
3136
|
+
if (branchEl && branchEl.value.trim()) body.branch = branchEl.value.trim();
|
|
3137
|
+
|
|
3138
|
+
var depthEl = document.getElementById('create-depth');
|
|
3139
|
+
if (depthEl && depthEl.value) body.depth = parseInt(depthEl.value, 10);
|
|
3140
|
+
|
|
3141
|
+
btn.disabled = true;
|
|
3142
|
+
btn.innerHTML = '<div class="spinner"></div> Creating...';
|
|
3143
|
+
|
|
3144
|
+
api('POST', '/api/stores', body).then(function(store) {
|
|
3145
|
+
showToast('Store "' + store.name + '" created', 'success');
|
|
3146
|
+
navigate('#/stores/' + encodeURIComponent(store.id));
|
|
3147
|
+
}).catch(function(err) {
|
|
3148
|
+
errorDiv.innerHTML = '<div class="form-error">' + esc(err.message) + '</div>';
|
|
3149
|
+
btn.disabled = false;
|
|
3150
|
+
btn.innerHTML = ICONS.plus + ' Create Store';
|
|
3151
|
+
});
|
|
3152
|
+
}
|
|
3153
|
+
|
|
3154
|
+
// \u2500\u2500\u2500 Search View \u2500\u2500\u2500
|
|
3155
|
+
|
|
3156
|
+
function renderSearchView(initialQuery) {
|
|
3157
|
+
var main = document.getElementById('main-content');
|
|
3158
|
+
|
|
3159
|
+
// First load stores for the filter
|
|
3160
|
+
api('GET', '/api/stores').then(function(stores) {
|
|
3161
|
+
state.stores = stores;
|
|
3162
|
+
renderSearchViewContent(stores, initialQuery);
|
|
3163
|
+
}).catch(function() {
|
|
3164
|
+
renderSearchViewContent([], initialQuery);
|
|
3165
|
+
});
|
|
3166
|
+
}
|
|
3167
|
+
|
|
3168
|
+
function renderSearchViewContent(stores, initialQuery) {
|
|
3169
|
+
var main = document.getElementById('main-content');
|
|
3170
|
+
var selectedStores = [];
|
|
3171
|
+
|
|
3172
|
+
var storeOptions = '';
|
|
3173
|
+
for (var i = 0; i < stores.length; i++) {
|
|
3174
|
+
storeOptions +=
|
|
3175
|
+
'<label class="multi-select-option">' +
|
|
3176
|
+
'<input type="checkbox" value="' + esc(stores[i].id) + '" />' +
|
|
3177
|
+
'<span class="badge badge-' + stores[i].type + '" style="font-size:10px;padding:1px 6px">' + stores[i].type + '</span>' +
|
|
3178
|
+
esc(stores[i].name) +
|
|
3179
|
+
'</label>';
|
|
3180
|
+
}
|
|
3181
|
+
|
|
3182
|
+
var html =
|
|
3183
|
+
'<h1 class="page-title" style="margin-bottom:var(--space-xl)">Search</h1>' +
|
|
3184
|
+
'<div class="search-bar">' +
|
|
3185
|
+
'<input class="form-input" id="search-input" type="text" placeholder="Search across all knowledge stores..." value="' + esc(initialQuery || '') + '" />' +
|
|
3186
|
+
'<button class="btn btn-primary" id="search-btn">' + ICONS.search + ' Search</button>' +
|
|
3187
|
+
'</div>' +
|
|
3188
|
+
'<div class="search-options">' +
|
|
3189
|
+
'<div class="search-option">' +
|
|
3190
|
+
'<label class="form-label">Detail Level</label>' +
|
|
3191
|
+
'<select class="form-select" id="search-detail">' +
|
|
3192
|
+
'<option value="minimal">Minimal</option>' +
|
|
3193
|
+
'<option value="contextual" selected>Contextual</option>' +
|
|
3194
|
+
'<option value="full">Full</option>' +
|
|
3195
|
+
'</select>' +
|
|
3196
|
+
'</div>' +
|
|
3197
|
+
'<div class="search-option">' +
|
|
3198
|
+
'<label class="form-label">Stores</label>' +
|
|
3199
|
+
'<div class="multi-select" id="store-filter">' +
|
|
3200
|
+
'<div class="multi-select-trigger" id="store-filter-trigger">All stores</div>' +
|
|
3201
|
+
'<div class="multi-select-dropdown" id="store-filter-dropdown">' +
|
|
3202
|
+
storeOptions +
|
|
3203
|
+
'</div>' +
|
|
3204
|
+
'</div>' +
|
|
3205
|
+
'</div>' +
|
|
3206
|
+
'</div>' +
|
|
3207
|
+
'<div id="search-results"></div>';
|
|
3208
|
+
|
|
3209
|
+
main.innerHTML = html;
|
|
3210
|
+
|
|
3211
|
+
// Multi-select behavior
|
|
3212
|
+
var trigger = document.getElementById('store-filter-trigger');
|
|
3213
|
+
var dropdown = document.getElementById('store-filter-dropdown');
|
|
3214
|
+
|
|
3215
|
+
trigger.onclick = function(e) {
|
|
3216
|
+
e.stopPropagation();
|
|
3217
|
+
dropdown.classList.toggle('open');
|
|
3218
|
+
};
|
|
3219
|
+
|
|
3220
|
+
document.addEventListener('click', function closeDropdown(e) {
|
|
3221
|
+
if (!dropdown.contains(e.target) && e.target !== trigger) {
|
|
3222
|
+
dropdown.classList.remove('open');
|
|
3223
|
+
}
|
|
3224
|
+
});
|
|
3225
|
+
|
|
3226
|
+
var checkboxes = dropdown.querySelectorAll('input[type="checkbox"]');
|
|
3227
|
+
for (var c = 0; c < checkboxes.length; c++) {
|
|
3228
|
+
checkboxes[c].onchange = function() {
|
|
3229
|
+
selectedStores = [];
|
|
3230
|
+
for (var k = 0; k < checkboxes.length; k++) {
|
|
3231
|
+
if (checkboxes[k].checked) selectedStores.push(checkboxes[k].value);
|
|
3232
|
+
}
|
|
3233
|
+
trigger.textContent = selectedStores.length === 0 ? 'All stores' : selectedStores.length + ' store' + (selectedStores.length > 1 ? 's' : '') + ' selected';
|
|
3234
|
+
};
|
|
3235
|
+
}
|
|
3236
|
+
|
|
3237
|
+
function doSearch() {
|
|
3238
|
+
var q = document.getElementById('search-input').value.trim();
|
|
3239
|
+
if (!q) return;
|
|
3240
|
+
|
|
3241
|
+
var detail = document.getElementById('search-detail').value;
|
|
3242
|
+
var resultsDiv = document.getElementById('search-results');
|
|
3243
|
+
resultsDiv.innerHTML = '<div class="loading-center"><div class="spinner"></div> Searching...</div>';
|
|
3244
|
+
|
|
3245
|
+
// Update URL
|
|
3246
|
+
var newHash = '#/search?q=' + encodeURIComponent(q);
|
|
3247
|
+
if (window.location.hash !== newHash) {
|
|
3248
|
+
history.replaceState(null, '', newHash);
|
|
3249
|
+
}
|
|
3250
|
+
|
|
3251
|
+
var searchBody = { query: q, detail: detail };
|
|
3252
|
+
if (selectedStores.length > 0) searchBody.stores = selectedStores;
|
|
3253
|
+
|
|
3254
|
+
api('POST', '/api/search', searchBody).then(function(data) {
|
|
3255
|
+
resultsDiv.innerHTML = renderSearchResults(data);
|
|
3256
|
+
}).catch(function(err) {
|
|
3257
|
+
resultsDiv.innerHTML = '<div class="form-error">' + esc(err.message) + '</div>';
|
|
3258
|
+
});
|
|
3259
|
+
}
|
|
3260
|
+
|
|
3261
|
+
document.getElementById('search-btn').onclick = doSearch;
|
|
3262
|
+
document.getElementById('search-input').onkeydown = function(e) {
|
|
3263
|
+
if (e.key === 'Enter') doSearch();
|
|
3264
|
+
};
|
|
3265
|
+
|
|
3266
|
+
// Auto-search if we have a query
|
|
3267
|
+
if (initialQuery) {
|
|
3268
|
+
doSearch();
|
|
3269
|
+
}
|
|
3270
|
+
|
|
3271
|
+
// Focus input
|
|
3272
|
+
document.getElementById('search-input').focus();
|
|
3273
|
+
}
|
|
3274
|
+
|
|
3275
|
+
// \u2500\u2500\u2500 Search Results Renderer \u2500\u2500\u2500
|
|
3276
|
+
|
|
3277
|
+
function renderSearchResults(data) {
|
|
3278
|
+
var results = data.results || [];
|
|
3279
|
+
var html = '';
|
|
3280
|
+
|
|
3281
|
+
// Meta bar
|
|
3282
|
+
var metaParts = [];
|
|
3283
|
+
metaParts.push('<strong>' + results.length + '</strong> result' + (results.length !== 1 ? 's' : ''));
|
|
3284
|
+
if (data.timeMs != null) metaParts.push(data.timeMs + 'ms');
|
|
3285
|
+
if (data.mode) metaParts.push(esc(data.mode) + ' mode');
|
|
3286
|
+
|
|
3287
|
+
var confidenceHtml = '';
|
|
3288
|
+
if (data.confidence) {
|
|
3289
|
+
var confClass = data.confidence === 'high' ? 'confidence-high' : data.confidence === 'medium' ? 'confidence-medium' : 'confidence-low';
|
|
3290
|
+
confidenceHtml = '<span class="meta-separator"></span><span class="confidence-badge ' + confClass + '">' + esc(data.confidence) + ' confidence</span>';
|
|
3291
|
+
}
|
|
3292
|
+
|
|
3293
|
+
html += '<div class="search-meta-bar">';
|
|
3294
|
+
html += metaParts.join('<span class="meta-separator"></span>');
|
|
3295
|
+
html += confidenceHtml;
|
|
3296
|
+
html += '</div>';
|
|
3297
|
+
|
|
3298
|
+
if (results.length === 0) {
|
|
3299
|
+
html += '<div class="empty-state"><div class="empty-state-title">No results found</div><p>Try broadening your search query or checking different stores.</p></div>';
|
|
3300
|
+
return html;
|
|
3301
|
+
}
|
|
3302
|
+
|
|
3303
|
+
html += '<div class="results-list">';
|
|
3304
|
+
for (var i = 0; i < results.length; i++) {
|
|
3305
|
+
html += renderResultCard(results[i], i);
|
|
3306
|
+
}
|
|
3307
|
+
html += '</div>';
|
|
3308
|
+
|
|
3309
|
+
return html;
|
|
3310
|
+
}
|
|
3311
|
+
|
|
3312
|
+
function renderResultCard(result, index) {
|
|
3313
|
+
var summary = result.summary || {};
|
|
3314
|
+
var context = result.context;
|
|
3315
|
+
var full = result.full;
|
|
3316
|
+
var pct = Math.round((result.score || 0) * 100);
|
|
3317
|
+
var sc = scoreColor(result.score || 0);
|
|
3318
|
+
var scCls = scoreClass(result.score || 0);
|
|
3319
|
+
|
|
3320
|
+
var hasContext = context && (
|
|
3321
|
+
(context.interfaces && context.interfaces.length > 0) ||
|
|
3322
|
+
(context.keyImports && context.keyImports.length > 0) ||
|
|
3323
|
+
(context.relatedConcepts && context.relatedConcepts.length > 0) ||
|
|
3324
|
+
context.usage
|
|
3325
|
+
);
|
|
3326
|
+
|
|
3327
|
+
var hasFull = full && (
|
|
3328
|
+
(full.completeCode && full.completeCode.length > 0) ||
|
|
3329
|
+
(full.documentation && full.documentation.length > 0)
|
|
3330
|
+
);
|
|
3331
|
+
|
|
3332
|
+
var locParts = (summary.location || '').split(':');
|
|
3333
|
+
var fileName = locParts[0] || '';
|
|
3334
|
+
var lineNum = locParts[1] || '';
|
|
3335
|
+
|
|
3336
|
+
var html =
|
|
3337
|
+
'<div class="result-card">' +
|
|
3338
|
+
'<div class="result-header">' +
|
|
3339
|
+
'<div class="result-rank">' + (index + 1) + '</div>' +
|
|
3340
|
+
'<div class="result-main">' +
|
|
3341
|
+
'<div class="result-title-row">' +
|
|
3342
|
+
(summary.type ? resultTypeIcon(summary.type) : '') +
|
|
3343
|
+
'<span class="result-name">' + esc(summary.name || 'Unknown') + '</span>' +
|
|
3344
|
+
'<div class="score-bar-container">' +
|
|
3345
|
+
'<div class="score-bar-track"><div class="score-bar-fill" style="width:' + pct + '%;background:' + sc + '"></div></div>' +
|
|
3346
|
+
'<span class="score-label score-' + scCls + '">' + pct + '%</span>' +
|
|
3347
|
+
'</div>' +
|
|
3348
|
+
'</div>';
|
|
3349
|
+
|
|
3350
|
+
if (summary.signature) {
|
|
3351
|
+
html += '<div class="result-signature">' + esc(summary.signature) + '</div>';
|
|
3352
|
+
}
|
|
3353
|
+
if (summary.purpose) {
|
|
3354
|
+
html += '<p class="result-purpose">' + esc(summary.purpose) + '</p>';
|
|
3355
|
+
}
|
|
3356
|
+
|
|
3357
|
+
html += '<div class="result-meta meta-row">';
|
|
3358
|
+
if (fileName) {
|
|
3359
|
+
html += '<span class="result-location mono" title="' + esc(summary.location || '') + '">' + esc(fileName) + (lineNum ? '<span class="line-number">:' + esc(lineNum) + '</span>' : '') + '</span>';
|
|
3360
|
+
}
|
|
3361
|
+
if (summary.storeName) {
|
|
3362
|
+
html += '<span class="meta-separator"></span><span class="result-store">' + esc(summary.storeName) + '</span>';
|
|
3363
|
+
}
|
|
3364
|
+
html += '</div>';
|
|
3365
|
+
|
|
3366
|
+
if (summary.relevanceReason) {
|
|
3367
|
+
html += '<div class="result-relevance">' + esc(summary.relevanceReason) + '</div>';
|
|
3368
|
+
}
|
|
3369
|
+
|
|
3370
|
+
html += '</div></div>'; // close result-main, result-header
|
|
3371
|
+
|
|
3372
|
+
// Expandable details
|
|
3373
|
+
if (hasContext || hasFull) {
|
|
3374
|
+
var detailLabel = (hasContext && hasFull) ? 'Context & Source Code' : hasContext ? 'Context' : 'Source Code';
|
|
3375
|
+
html += '<details><summary>' + detailLabel + '</summary><div class="details-content">';
|
|
3376
|
+
|
|
3377
|
+
if (hasContext) {
|
|
3378
|
+
html += renderContextSection(context);
|
|
3379
|
+
}
|
|
3380
|
+
if (hasFull) {
|
|
3381
|
+
html += renderFullSection(full);
|
|
3382
|
+
}
|
|
3383
|
+
|
|
3384
|
+
html += '</div></details>';
|
|
3385
|
+
}
|
|
3386
|
+
|
|
3387
|
+
html += '</div>'; // close result-card
|
|
3388
|
+
return html;
|
|
3389
|
+
}
|
|
3390
|
+
|
|
3391
|
+
function renderContextSection(ctx) {
|
|
3392
|
+
var html = '';
|
|
3393
|
+
|
|
3394
|
+
if (ctx.interfaces && ctx.interfaces.length > 0) {
|
|
3395
|
+
html += '<div class="context-group"><div class="section-label">Interfaces</div><ul class="pill-list">';
|
|
3396
|
+
for (var i = 0; i < ctx.interfaces.length; i++) html += '<li class="pill">' + esc(ctx.interfaces[i]) + '</li>';
|
|
3397
|
+
html += '</ul></div>';
|
|
3398
|
+
}
|
|
3399
|
+
|
|
3400
|
+
if (ctx.keyImports && ctx.keyImports.length > 0) {
|
|
3401
|
+
html += '<div class="context-group"><div class="section-label">Key Imports</div><ul class="pill-list">';
|
|
3402
|
+
for (var j = 0; j < ctx.keyImports.length; j++) html += '<li class="pill">' + esc(ctx.keyImports[j]) + '</li>';
|
|
3403
|
+
html += '</ul></div>';
|
|
3404
|
+
}
|
|
3405
|
+
|
|
3406
|
+
if (ctx.relatedConcepts && ctx.relatedConcepts.length > 0) {
|
|
3407
|
+
html += '<div class="context-group"><div class="section-label">Related Concepts</div><ul class="pill-list">';
|
|
3408
|
+
for (var k = 0; k < ctx.relatedConcepts.length; k++) html += '<li class="pill">' + esc(ctx.relatedConcepts[k]) + '</li>';
|
|
3409
|
+
html += '</ul></div>';
|
|
3410
|
+
}
|
|
3411
|
+
|
|
3412
|
+
if (ctx.usage) {
|
|
3413
|
+
html +=
|
|
3414
|
+
'<div class="context-group"><div class="section-label">Usage</div>' +
|
|
3415
|
+
'<div class="usage-stats">' +
|
|
3416
|
+
'<span class="usage-stat"><span class="usage-value">' + ctx.usage.calledBy + '</span><span class="usage-label">called by</span></span>' +
|
|
3417
|
+
'<span class="usage-stat"><span class="usage-value">' + ctx.usage.calls + '</span><span class="usage-label">calls</span></span>' +
|
|
3418
|
+
'</div></div>';
|
|
3419
|
+
}
|
|
3420
|
+
|
|
3421
|
+
return html;
|
|
3422
|
+
}
|
|
3423
|
+
|
|
3424
|
+
function renderFullSection(full) {
|
|
3425
|
+
var html = '';
|
|
3426
|
+
|
|
3427
|
+
if (full.documentation && full.documentation.length > 0) {
|
|
3428
|
+
html += '<div class="full-group"><div class="section-label">Documentation</div><div class="documentation-text">' + esc(full.documentation) + '</div></div>';
|
|
3429
|
+
}
|
|
3430
|
+
|
|
3431
|
+
if (full.completeCode && full.completeCode.length > 0) {
|
|
3432
|
+
html += '<div class="full-group"><div class="section-label">Source Code</div><pre class="code-block"><code>' + esc(full.completeCode) + '</code></pre></div>';
|
|
3433
|
+
}
|
|
3434
|
+
|
|
3435
|
+
return html;
|
|
3436
|
+
}
|
|
3437
|
+
|
|
3438
|
+
// \u2500\u2500\u2500 Bootstrap \u2500\u2500\u2500
|
|
3439
|
+
|
|
3440
|
+
window.addEventListener('hashchange', renderApp);
|
|
3441
|
+
renderApp();
|
|
3442
|
+
|
|
3443
|
+
})();
|
|
3444
|
+
</script>
|
|
3445
|
+
</body>
|
|
3446
|
+
</html>`;
|
|
3447
|
+
}
|
|
3448
|
+
|
|
3449
|
+
// src/server/app.ts
|
|
1383
3450
|
var CreateStoreBodySchema = z.object({
|
|
1384
3451
|
name: z.string().min(1, "Store name must be a non-empty string"),
|
|
1385
3452
|
type: z.enum(["file", "repo", "web"]),
|
|
@@ -1413,6 +3480,7 @@ var SearchBodySchema = z.object({
|
|
|
1413
3480
|
function createApp(services) {
|
|
1414
3481
|
const app = new Hono();
|
|
1415
3482
|
app.use("*", cors());
|
|
3483
|
+
app.get("/", (c) => c.html(renderAdminUI()));
|
|
1416
3484
|
app.get("/health", (c) => c.json({ status: "ok" }));
|
|
1417
3485
|
app.get("/api/stores", async (c) => {
|
|
1418
3486
|
const stores = await services.store.list();
|
|
@@ -1425,10 +3493,30 @@ function createApp(services) {
|
|
|
1425
3493
|
return c.json({ error: parseResult.error.issues[0]?.message ?? "Invalid request body" }, 400);
|
|
1426
3494
|
}
|
|
1427
3495
|
const result = await services.store.create(parseResult.data);
|
|
1428
|
-
if (result.success) {
|
|
1429
|
-
return c.json(result.
|
|
3496
|
+
if (!result.success) {
|
|
3497
|
+
return c.json({ error: result.error.message }, 400);
|
|
1430
3498
|
}
|
|
1431
|
-
|
|
3499
|
+
const dataDir = services.config.resolveDataDir();
|
|
3500
|
+
const isUrl = parseResult.data.url !== void 0;
|
|
3501
|
+
const jobType = parseResult.data.type === "web" ? "crawl" : parseResult.data.type === "repo" && isUrl ? "clone" : "index";
|
|
3502
|
+
const storePath = result.data.type !== "web" ? result.data.path : void 0;
|
|
3503
|
+
const jobDetails = {
|
|
3504
|
+
storeName: result.data.name,
|
|
3505
|
+
storeId: result.data.id,
|
|
3506
|
+
...isUrl ? { url: parseResult.data.url } : {},
|
|
3507
|
+
...storePath !== void 0 ? { path: storePath } : {},
|
|
3508
|
+
phase: jobType === "crawl" ? "crawling" : jobType === "clone" ? "cloning" : "indexing",
|
|
3509
|
+
phaseStep: 1,
|
|
3510
|
+
phaseTotalSteps: jobType === "index" ? 1 : 2
|
|
3511
|
+
};
|
|
3512
|
+
const jobService = new JobService(dataDir);
|
|
3513
|
+
const job = jobService.createJob({
|
|
3514
|
+
type: jobType,
|
|
3515
|
+
details: jobDetails,
|
|
3516
|
+
message: `${jobType === "crawl" ? "Crawling" : "Indexing"} ${result.data.name}...`
|
|
3517
|
+
});
|
|
3518
|
+
spawnBackgroundWorker(job.id, dataDir);
|
|
3519
|
+
return c.json({ ...result.data, job: { id: job.id, status: job.status } }, 201);
|
|
1432
3520
|
});
|
|
1433
3521
|
app.get("/api/stores/:id", async (c) => {
|
|
1434
3522
|
const store = await services.store.getByIdOrName(c.req.param("id"));
|
|
@@ -1481,6 +3569,21 @@ function createApp(services) {
|
|
|
1481
3569
|
const results = await services.search.search(query);
|
|
1482
3570
|
return c.json(results);
|
|
1483
3571
|
});
|
|
3572
|
+
app.get("/api/stores/:id/documents", async (c) => {
|
|
3573
|
+
const store = await services.store.getByIdOrName(c.req.param("id"));
|
|
3574
|
+
if (!store) return c.json({ error: "Not found" }, 404);
|
|
3575
|
+
const limit = parseInt(c.req.query("limit") ?? "50", 10);
|
|
3576
|
+
const offset = parseInt(c.req.query("offset") ?? "0", 10);
|
|
3577
|
+
try {
|
|
3578
|
+
services.lance.setDimensions(await services.embeddings.ensureDimensions());
|
|
3579
|
+
await services.lance.initialize(store.id);
|
|
3580
|
+
const count = await services.lance.countDocuments(store.id);
|
|
3581
|
+
const documents = await services.lance.getAllDocuments(store.id, { limit, offset });
|
|
3582
|
+
return c.json({ documents, total: count, limit, offset });
|
|
3583
|
+
} catch {
|
|
3584
|
+
return c.json({ documents: [], total: 0, limit, offset });
|
|
3585
|
+
}
|
|
3586
|
+
});
|
|
1484
3587
|
app.post("/api/stores/:id/index", async (c) => {
|
|
1485
3588
|
const store = await services.store.getByIdOrName(c.req.param("id"));
|
|
1486
3589
|
if (!store) return c.json({ error: "Not found" }, 404);
|
|
@@ -1490,15 +3593,44 @@ function createApp(services) {
|
|
|
1490
3593
|
if (result.success) return c.json(result.data);
|
|
1491
3594
|
return c.json({ error: result.error.message }, 400);
|
|
1492
3595
|
});
|
|
3596
|
+
app.get("/api/jobs", (c) => {
|
|
3597
|
+
const dataDir = services.config.resolveDataDir();
|
|
3598
|
+
const jobService = new JobService(dataDir);
|
|
3599
|
+
return c.json(jobService.listJobs());
|
|
3600
|
+
});
|
|
3601
|
+
app.get("/api/jobs/:id", (c) => {
|
|
3602
|
+
const dataDir = services.config.resolveDataDir();
|
|
3603
|
+
const jobService = new JobService(dataDir);
|
|
3604
|
+
const job = jobService.getJob(c.req.param("id"));
|
|
3605
|
+
if (!job) return c.json({ error: "Not found" }, 404);
|
|
3606
|
+
return c.json(job);
|
|
3607
|
+
});
|
|
1493
3608
|
return app;
|
|
1494
3609
|
}
|
|
1495
3610
|
|
|
1496
3611
|
// src/cli/commands/serve.ts
|
|
3612
|
+
function startServer(fetch2, preferredPort, host) {
|
|
3613
|
+
return new Promise((resolve, reject) => {
|
|
3614
|
+
const server = serve({ fetch: fetch2, port: preferredPort, hostname: host }, (info) => {
|
|
3615
|
+
resolve({ server, port: info.port });
|
|
3616
|
+
});
|
|
3617
|
+
server.on("error", (err2) => {
|
|
3618
|
+
if (err2.code === "EADDRINUSE") {
|
|
3619
|
+
const retryServer = serve({ fetch: fetch2, port: 0, hostname: host }, (info) => {
|
|
3620
|
+
resolve({ server: retryServer, port: info.port });
|
|
3621
|
+
});
|
|
3622
|
+
retryServer.on("error", reject);
|
|
3623
|
+
} else {
|
|
3624
|
+
reject(err2);
|
|
3625
|
+
}
|
|
3626
|
+
});
|
|
3627
|
+
});
|
|
3628
|
+
}
|
|
1497
3629
|
function createServeCommand(getOptions) {
|
|
1498
3630
|
return new Command6("serve").description("Start HTTP API server for programmatic search access").option("-p, --port <port>", "Port to listen on (reads from config if not specified)").option(
|
|
1499
3631
|
"--host <host>",
|
|
1500
3632
|
"Bind address (reads from config if not specified, use 0.0.0.0 for all interfaces)"
|
|
1501
|
-
).action(async (options) => {
|
|
3633
|
+
).option("--open", "Open the admin UI in the default browser after starting").action(async (options) => {
|
|
1502
3634
|
const globalOpts = getOptions();
|
|
1503
3635
|
const services = await createServices(
|
|
1504
3636
|
globalOpts.config,
|
|
@@ -1507,22 +3639,26 @@ function createServeCommand(getOptions) {
|
|
|
1507
3639
|
);
|
|
1508
3640
|
const appConfig = await services.config.load();
|
|
1509
3641
|
const app = createApp(services);
|
|
1510
|
-
let
|
|
3642
|
+
let preferredPort;
|
|
1511
3643
|
if (options.port !== void 0) {
|
|
1512
|
-
|
|
1513
|
-
if (Number.isNaN(
|
|
3644
|
+
preferredPort = parseInt(options.port, 10);
|
|
3645
|
+
if (Number.isNaN(preferredPort)) {
|
|
1514
3646
|
throw new Error(`Invalid value for --port: "${options.port}" is not a valid integer`);
|
|
1515
3647
|
}
|
|
1516
3648
|
} else {
|
|
1517
|
-
|
|
3649
|
+
preferredPort = appConfig.server.port;
|
|
1518
3650
|
}
|
|
1519
3651
|
const host = options.host ?? appConfig.server.host;
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
});
|
|
3652
|
+
const { server, port } = await startServer(app.fetch, preferredPort, host);
|
|
3653
|
+
if (port !== preferredPort) {
|
|
3654
|
+
console.log(`Port ${String(preferredPort)} in use, using ${String(port)}`);
|
|
3655
|
+
}
|
|
3656
|
+
const url = `http://${host}:${String(port)}`;
|
|
3657
|
+
console.log(`Starting server on ${url}`);
|
|
3658
|
+
if (options.open === true) {
|
|
3659
|
+
const openCmd = platform() === "darwin" ? "open" : platform() === "win32" ? "start" : "xdg-open";
|
|
3660
|
+
exec(`${openCmd} ${url}`);
|
|
3661
|
+
}
|
|
1526
3662
|
let shuttingDown = false;
|
|
1527
3663
|
const shutdown = () => {
|
|
1528
3664
|
if (shuttingDown) return;
|