voyageai-cli 1.24.0 → 1.26.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/package.json +1 -1
- package/src/cli.js +2 -0
- package/src/commands/about.js +1 -1
- package/src/commands/bug.js +1 -1
- package/src/commands/chat.js +281 -78
- package/src/commands/playground.js +73 -19
- package/src/commands/scaffold.js +23 -1
- package/src/commands/workflow.js +336 -0
- package/src/lib/chat.js +170 -4
- package/src/lib/explanations.js +53 -0
- package/src/lib/llm.js +304 -2
- package/src/lib/mongo.js +6 -6
- package/src/lib/prompt.js +60 -1
- package/src/lib/scaffold-structure.js +8 -9
- package/src/lib/telemetry.js +1 -1
- package/src/lib/template-engine.js +240 -0
- package/src/lib/templates/nextjs/README.md.tpl +78 -55
- package/src/lib/templates/nextjs/favicon.svg.tpl +11 -0
- package/src/lib/templates/nextjs/footer.jsx.tpl +49 -0
- package/src/lib/templates/nextjs/layout.jsx.tpl +16 -10
- package/src/lib/templates/nextjs/lib-mongo.js.tpl +5 -5
- package/src/lib/templates/nextjs/lib-voyage.js.tpl +13 -8
- package/src/lib/templates/nextjs/navbar.jsx.tpl +98 -0
- package/src/lib/templates/nextjs/page-home.jsx.tpl +201 -0
- package/src/lib/templates/nextjs/page-search.jsx.tpl +184 -82
- package/src/lib/templates/nextjs/theme-registry.jsx.tpl +51 -0
- package/src/lib/templates/nextjs/theme.js.tpl +138 -65
- package/src/lib/templates/nextjs/vai-logo-256.png +0 -0
- package/src/lib/tool-registry.js +194 -0
- package/src/lib/workflow-utils.js +65 -0
- package/src/lib/workflow.js +1259 -0
- package/src/mcp/tools/embedding.js +55 -43
- package/src/mcp/tools/ingest.js +74 -67
- package/src/mcp/tools/management.js +54 -101
- package/src/mcp/tools/retrieval.js +181 -163
- package/src/mcp/tools/utility.js +171 -153
- package/src/playground/icons/dark/128.png +0 -0
- package/src/playground/icons/dark/16.png +0 -0
- package/src/playground/icons/dark/256.png +0 -0
- package/src/playground/icons/dark/32.png +0 -0
- package/src/playground/icons/dark/64.png +0 -0
- package/src/playground/icons/light/128.png +0 -0
- package/src/playground/icons/light/16.png +0 -0
- package/src/playground/icons/light/256.png +0 -0
- package/src/playground/icons/light/32.png +0 -0
- package/src/playground/icons/light/64.png +0 -0
- package/src/playground/icons/watermark.png +0 -0
- package/src/playground/index.html +633 -83
- package/src/workflows/consistency-check.json +64 -0
- package/src/workflows/cost-analysis.json +69 -0
- package/src/workflows/multi-collection-search.json +80 -0
- package/src/workflows/research-and-summarize.json +46 -0
- package/src/workflows/smart-ingest.json +63 -0
|
@@ -10,56 +10,56 @@
|
|
|
10
10
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
11
11
|
|
|
12
12
|
:root {
|
|
13
|
-
/*
|
|
13
|
+
/* Vai Dark Mode Palette (default) — Teal/Cyan brand */
|
|
14
14
|
--bg: #001E2B; /* MDB Black */
|
|
15
15
|
--bg-surface: #112733; /* Gray Dark 4 */
|
|
16
16
|
--bg-card: #1C2D38; /* Gray Dark 3 */
|
|
17
17
|
--bg-input: #112733; /* Gray Dark 4 */
|
|
18
|
-
--accent: #
|
|
18
|
+
--accent: #00D4AA; /* Teal — interactive elements */
|
|
19
19
|
--accent-text: #FFFFFF; /* Bright white — headings/labels in dark mode */
|
|
20
|
-
--accent-dim: #
|
|
21
|
-
--accent-glow: rgba(0,
|
|
20
|
+
--accent-dim: #009E80; /* Teal Dark */
|
|
21
|
+
--accent-glow: rgba(0, 212, 170, 0.10);
|
|
22
22
|
--text: #E8EDEB; /* Gray Light 2 */
|
|
23
23
|
--text-dim: #C1C7C6; /* Gray Light 1 */
|
|
24
24
|
--text-muted: #889397; /* Gray Base */
|
|
25
25
|
--border: #3D4F58; /* Gray Dark 2 */
|
|
26
26
|
--error: #FF6960; /* Red Light 1 */
|
|
27
27
|
--warning: #FFC010; /* Yellow Base */
|
|
28
|
-
--success: #
|
|
28
|
+
--success: #00D4AA; /* Teal */
|
|
29
29
|
--red: #FF6960; /* Red Light 1 */
|
|
30
30
|
--yellow: #FFC010; /* Yellow Base */
|
|
31
|
-
--green: #
|
|
32
|
-
--blue: #
|
|
31
|
+
--green: #00D4AA; /* Teal */
|
|
32
|
+
--blue: #40E0FF; /* Cyan (links) */
|
|
33
33
|
--purple: #B45AF2; /* Purple Base */
|
|
34
|
-
--green-dark: #
|
|
34
|
+
--green-dark: #00241E; /* Teal Dark 3 */
|
|
35
35
|
--radius: 8px;
|
|
36
36
|
--font: 'Euclid Circular A', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
|
37
37
|
--mono: 'Source Code Pro', 'SF Mono', 'Fira Code', 'Cascadia Code', 'Consolas', monospace;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
/*
|
|
40
|
+
/* Vai Light Mode Palette — Teal/Cyan brand */
|
|
41
41
|
[data-theme="light"] {
|
|
42
42
|
--bg: #FFFFFF; /* White */
|
|
43
43
|
--bg-surface: #F9FBFA; /* Gray Light 3 */
|
|
44
44
|
--bg-card: #FFFFFF; /* White */
|
|
45
45
|
--bg-input: #F9FBFA; /* Gray Light 3 */
|
|
46
|
-
--accent: #
|
|
46
|
+
--accent: #009E80; /* Teal Dark — interactive elements */
|
|
47
47
|
--accent-text: #001E2B; /* MDB Black — headings/labels in light mode */
|
|
48
|
-
--accent-dim: #
|
|
49
|
-
--accent-glow: rgba(0,
|
|
48
|
+
--accent-dim: #007A63; /* Teal Darker */
|
|
49
|
+
--accent-glow: rgba(0, 158, 128, 0.08);
|
|
50
50
|
--text: #001E2B; /* MDB Black */
|
|
51
51
|
--text-dim: #5C6C75; /* Gray Dark 1 */
|
|
52
52
|
--text-muted: #889397; /* Gray Base */
|
|
53
53
|
--border: #E8EDEB; /* Gray Light 2 */
|
|
54
54
|
--error: #DB3030; /* Red Base */
|
|
55
55
|
--warning: #944F01; /* Yellow Dark 2 */
|
|
56
|
-
--success: #
|
|
56
|
+
--success: #009E80; /* Teal Dark */
|
|
57
57
|
--red: #DB3030; /* Red Base */
|
|
58
58
|
--yellow: #944F01; /* Yellow Dark 2 */
|
|
59
|
-
--green: #
|
|
60
|
-
--blue: #
|
|
59
|
+
--green: #009E80; /* Teal Dark */
|
|
60
|
+
--blue: #0088CC; /* Cyan Dark (links) */
|
|
61
61
|
--purple: #5E0C9E; /* Purple Dark 2 */
|
|
62
|
-
--green-dark: #
|
|
62
|
+
--green-dark: #00241E; /* Teal Dark 3 */
|
|
63
63
|
}
|
|
64
64
|
/* Light mode shadow + card adjustments */
|
|
65
65
|
[data-theme="light"] .explore-card,
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
box-shadow: 0 1px 4px rgba(0, 30, 43, 0.08);
|
|
70
70
|
}
|
|
71
71
|
[data-theme="light"] .explore-card:hover {
|
|
72
|
-
box-shadow: 0 4px 16px rgba(0,
|
|
72
|
+
box-shadow: 0 4px 16px rgba(0, 158, 128, 0.12);
|
|
73
73
|
}
|
|
74
74
|
[data-theme="light"] .cost-modal,
|
|
75
75
|
[data-theme="light"] .explore-modal {
|
|
@@ -83,14 +83,14 @@
|
|
|
83
83
|
box-shadow: 1px 0 3px rgba(0, 30, 43, 0.06);
|
|
84
84
|
}
|
|
85
85
|
/* Light mode gradient overrides */
|
|
86
|
-
[data-theme="light"] .quant-bar-fill.storage { background: linear-gradient(90deg, #
|
|
87
|
-
[data-theme="light"] .quant-bar-fill.latency { background: linear-gradient(90deg, #
|
|
88
|
-
[data-theme="light"] .quant-meter-fill.perfect { background: linear-gradient(90deg, #
|
|
86
|
+
[data-theme="light"] .quant-bar-fill.storage { background: linear-gradient(90deg, #009E80, #00D4AA); }
|
|
87
|
+
[data-theme="light"] .quant-bar-fill.latency { background: linear-gradient(90deg, #0088CC, #40E0FF); }
|
|
88
|
+
[data-theme="light"] .quant-meter-fill.perfect { background: linear-gradient(90deg, #009E80, #00D4AA); }
|
|
89
89
|
[data-theme="light"] .quant-meter-fill.good { background: linear-gradient(90deg, #944F01, #FFC010); }
|
|
90
90
|
[data-theme="light"] .quant-meter-fill.degraded { background: linear-gradient(90deg, #DB3030, #FF6960); }
|
|
91
91
|
/* Light mode button text */
|
|
92
92
|
[data-theme="light"] .btn { color: #FFFFFF; }
|
|
93
|
-
[data-theme="light"] .btn:hover { background: #
|
|
93
|
+
[data-theme="light"] .btn:hover { background: #007A63; }
|
|
94
94
|
|
|
95
95
|
html, body { height: 100%; }
|
|
96
96
|
|
|
@@ -108,8 +108,8 @@ body {
|
|
|
108
108
|
align-items: center;
|
|
109
109
|
gap: 10px;
|
|
110
110
|
padding: 8px 16px;
|
|
111
|
-
background: linear-gradient(135deg, rgba(0,
|
|
112
|
-
border-bottom: 1px solid rgba(0,
|
|
111
|
+
background: linear-gradient(135deg, rgba(0,158,128,0.12), rgba(4,152,236,0.12));
|
|
112
|
+
border-bottom: 1px solid rgba(0,212,170,0.2);
|
|
113
113
|
font-size: 13px;
|
|
114
114
|
color: var(--text);
|
|
115
115
|
flex-shrink: 0;
|
|
@@ -178,7 +178,7 @@ body {
|
|
|
178
178
|
padding: 24px 28px 20px;
|
|
179
179
|
width: 380px;
|
|
180
180
|
max-width: 90vw;
|
|
181
|
-
box-shadow: 0 12px 40px rgba(0,0,0,0.4), 0 0 0 1px rgba(0,
|
|
181
|
+
box-shadow: 0 12px 40px rgba(0,0,0,0.4), 0 0 0 1px rgba(0,212,170,0.15);
|
|
182
182
|
transition: all 0.4s cubic-bezier(0.4,0,0.2,1);
|
|
183
183
|
opacity: 0;
|
|
184
184
|
transform: translateY(10px);
|
|
@@ -296,7 +296,7 @@ body {
|
|
|
296
296
|
width: 460px;
|
|
297
297
|
max-width: 90vw;
|
|
298
298
|
text-align: center;
|
|
299
|
-
box-shadow: 0 20px 60px rgba(0,0,0,0.5), 0 0 80px rgba(0,
|
|
299
|
+
box-shadow: 0 20px 60px rgba(0,0,0,0.5), 0 0 80px rgba(0,212,170,0.08);
|
|
300
300
|
pointer-events: auto;
|
|
301
301
|
opacity: 0;
|
|
302
302
|
transform: scale(0.95);
|
|
@@ -324,10 +324,10 @@ body {
|
|
|
324
324
|
margin-bottom: 28px;
|
|
325
325
|
}
|
|
326
326
|
[data-theme="light"] .onboarding-tooltip {
|
|
327
|
-
box-shadow: 0 12px 40px rgba(0,30,43,0.15), 0 0 0 1px rgba(0,
|
|
327
|
+
box-shadow: 0 12px 40px rgba(0,30,43,0.15), 0 0 0 1px rgba(0,158,128,0.2);
|
|
328
328
|
}
|
|
329
329
|
[data-theme="light"] .onboarding-welcome-card {
|
|
330
|
-
box-shadow: 0 20px 60px rgba(0,30,43,0.18), 0 0 80px rgba(0,
|
|
330
|
+
box-shadow: 0 20px 60px rgba(0,30,43,0.18), 0 0 80px rgba(0,158,128,0.06);
|
|
331
331
|
}
|
|
332
332
|
[data-theme="light"] .onboarding-backdrop {
|
|
333
333
|
background: rgba(0,30,43,0.45);
|
|
@@ -480,7 +480,7 @@ body:not(.is-electron) .sidebar-drag-region {
|
|
|
480
480
|
height: 16px;
|
|
481
481
|
}
|
|
482
482
|
[data-theme="light"] .tab-btn:hover { background: rgba(0,30,43,0.04); }
|
|
483
|
-
[data-theme="light"] .tab-btn.active { background: rgba(0,
|
|
483
|
+
[data-theme="light"] .tab-btn.active { background: rgba(0,158,128,0.08); }
|
|
484
484
|
|
|
485
485
|
.sidebar-footer {
|
|
486
486
|
padding: 12px 16px;
|
|
@@ -558,6 +558,27 @@ body:not(.is-electron) .sidebar-drag-region {
|
|
|
558
558
|
overflow-y: auto;
|
|
559
559
|
display: flex;
|
|
560
560
|
flex-direction: column;
|
|
561
|
+
position: relative;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
.content-area::after {
|
|
565
|
+
content: '';
|
|
566
|
+
position: fixed;
|
|
567
|
+
bottom: 2rem;
|
|
568
|
+
right: 2rem;
|
|
569
|
+
width: 200px;
|
|
570
|
+
height: 200px;
|
|
571
|
+
background-image: url('/icons/watermark.png');
|
|
572
|
+
background-size: contain;
|
|
573
|
+
background-repeat: no-repeat;
|
|
574
|
+
opacity: 0.05;
|
|
575
|
+
pointer-events: none;
|
|
576
|
+
z-index: 0;
|
|
577
|
+
filter: invert(1);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
[data-theme="light"] .content-area::after {
|
|
581
|
+
filter: none;
|
|
561
582
|
}
|
|
562
583
|
|
|
563
584
|
.content-drag-region {
|
|
@@ -644,7 +665,7 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
644
665
|
align-items: center;
|
|
645
666
|
gap: 8px;
|
|
646
667
|
}
|
|
647
|
-
.btn:hover { background: #
|
|
668
|
+
.btn:hover { background: #5CE8CC; transform: translateY(-1px); }
|
|
648
669
|
.btn:active { transform: translateY(0); }
|
|
649
670
|
.btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }
|
|
650
671
|
|
|
@@ -689,7 +710,7 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
689
710
|
}
|
|
690
711
|
.subtab:hover {
|
|
691
712
|
color: var(--text);
|
|
692
|
-
background: rgba(0,
|
|
713
|
+
background: rgba(0, 212, 170, 0.05);
|
|
693
714
|
}
|
|
694
715
|
.subtab.active {
|
|
695
716
|
background: var(--accent);
|
|
@@ -964,7 +985,7 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
964
985
|
.explore-card:hover {
|
|
965
986
|
border-color: var(--accent);
|
|
966
987
|
transform: translateY(-2px);
|
|
967
|
-
box-shadow: 0 4px 20px rgba(0,
|
|
988
|
+
box-shadow: 0 4px 20px rgba(0, 212, 170, 0.1);
|
|
968
989
|
}
|
|
969
990
|
.explore-card.expanded {
|
|
970
991
|
border-color: var(--accent);
|
|
@@ -1252,8 +1273,8 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
1252
1273
|
font-family: var(--mono); font-size: 12px; font-weight: 600;
|
|
1253
1274
|
color: var(--green-dark); white-space: nowrap; min-width: fit-content;
|
|
1254
1275
|
}
|
|
1255
|
-
.quant-bar-fill.storage { background: linear-gradient(90deg, #
|
|
1256
|
-
.quant-bar-fill.latency { background: linear-gradient(90deg, #
|
|
1276
|
+
.quant-bar-fill.storage { background: linear-gradient(90deg, #00D4AA, #5CE8CC); }
|
|
1277
|
+
.quant-bar-fill.latency { background: linear-gradient(90deg, #40E0FF, #0088CC); }
|
|
1257
1278
|
.quant-bar-badge {
|
|
1258
1279
|
position: absolute; right: 10px; top: 50%; transform: translateY(-50%);
|
|
1259
1280
|
font-size: 12px; color: var(--text-dim); font-family: var(--mono);
|
|
@@ -1278,7 +1299,7 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
1278
1299
|
height: 100%; border-radius: 5px;
|
|
1279
1300
|
transition: width 0.8s cubic-bezier(0.22, 1, 0.36, 1);
|
|
1280
1301
|
}
|
|
1281
|
-
.quant-meter-fill.perfect { background: linear-gradient(90deg, #
|
|
1302
|
+
.quant-meter-fill.perfect { background: linear-gradient(90deg, #00D4AA, #5CE8CC); }
|
|
1282
1303
|
.quant-meter-fill.good { background: linear-gradient(90deg, #FFC010, #FFEC9E); }
|
|
1283
1304
|
.quant-meter-fill.degraded { background: linear-gradient(90deg, #FF6960, #FFCDC7); }
|
|
1284
1305
|
.quant-meter-detail { font-size: 11px; color: var(--text-muted); margin-top: 4px; font-family: var(--mono); }
|
|
@@ -1374,7 +1395,7 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
1374
1395
|
font-weight: 600;
|
|
1375
1396
|
}
|
|
1376
1397
|
.cost-mode-btn:hover:not(.active) {
|
|
1377
|
-
background: rgba(0,
|
|
1398
|
+
background: rgba(0, 212, 170, 0.1);
|
|
1378
1399
|
color: var(--text);
|
|
1379
1400
|
}
|
|
1380
1401
|
.cost-select {
|
|
@@ -1514,7 +1535,7 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
1514
1535
|
border-bottom: 1px solid rgba(42, 53, 80, 0.3);
|
|
1515
1536
|
font-family: var(--mono);
|
|
1516
1537
|
}
|
|
1517
|
-
.cost-table tr:hover { background: rgba(0,
|
|
1538
|
+
.cost-table tr:hover { background: rgba(0, 212, 170, 0.03); }
|
|
1518
1539
|
.cost-highlight {
|
|
1519
1540
|
color: var(--accent-text);
|
|
1520
1541
|
font-weight: 600;
|
|
@@ -1541,7 +1562,7 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
1541
1562
|
.cost-tip {
|
|
1542
1563
|
font-size: 12px;
|
|
1543
1564
|
color: var(--text-muted);
|
|
1544
|
-
background: rgba(0,
|
|
1565
|
+
background: rgba(0, 212, 170, 0.05);
|
|
1545
1566
|
border-left: 3px solid var(--accent);
|
|
1546
1567
|
padding: 10px 14px;
|
|
1547
1568
|
border-radius: 0 6px 6px 0;
|
|
@@ -1661,7 +1682,7 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
1661
1682
|
}
|
|
1662
1683
|
.cost-modal .formula .accent { color: var(--accent); font-weight: 600; }
|
|
1663
1684
|
.cost-modal .example {
|
|
1664
|
-
background: rgba(0,
|
|
1685
|
+
background: rgba(0, 212, 170, 0.05);
|
|
1665
1686
|
border-left: 3px solid var(--accent);
|
|
1666
1687
|
border-radius: 0 8px 8px 0;
|
|
1667
1688
|
padding: 12px 16px;
|
|
@@ -1843,8 +1864,8 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
1843
1864
|
line-height: 1.5;
|
|
1844
1865
|
}
|
|
1845
1866
|
.settings-api-banner.success {
|
|
1846
|
-
background: rgba(0,
|
|
1847
|
-
border-color: rgba(0,
|
|
1867
|
+
background: rgba(0,212,170,0.06);
|
|
1868
|
+
border-color: rgba(0,212,170,0.2);
|
|
1848
1869
|
color: var(--success);
|
|
1849
1870
|
}
|
|
1850
1871
|
[data-theme="light"] .settings-api-banner {
|
|
@@ -1932,8 +1953,8 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
1932
1953
|
font-size: 13px;
|
|
1933
1954
|
font-weight: 600;
|
|
1934
1955
|
color: var(--accent);
|
|
1935
|
-
background: rgba(0,
|
|
1936
|
-
border: 1px solid rgba(0,
|
|
1956
|
+
background: rgba(0,212,170,0.08);
|
|
1957
|
+
border: 1px solid rgba(0,212,170,0.2);
|
|
1937
1958
|
border-radius: 6px;
|
|
1938
1959
|
padding: 4px 12px;
|
|
1939
1960
|
letter-spacing: 0.3px;
|
|
@@ -1941,7 +1962,7 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
1941
1962
|
[data-theme="light"] .version-badge {
|
|
1942
1963
|
background: rgba(0,104,74,0.06);
|
|
1943
1964
|
border-color: rgba(0,104,74,0.2);
|
|
1944
|
-
color: #
|
|
1965
|
+
color: #007A63;
|
|
1945
1966
|
}
|
|
1946
1967
|
.settings-api-field .save-btn {
|
|
1947
1968
|
color: var(--accent);
|
|
@@ -2174,7 +2195,7 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
2174
2195
|
}
|
|
2175
2196
|
[data-theme="light"] .settings-origin.origin-env {
|
|
2176
2197
|
background: rgba(4,120,190,0.10);
|
|
2177
|
-
color: #
|
|
2198
|
+
color: #0088CC;
|
|
2178
2199
|
}
|
|
2179
2200
|
.settings-origin.origin-config {
|
|
2180
2201
|
background: rgba(180,90,242,0.12);
|
|
@@ -2185,12 +2206,12 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
2185
2206
|
color: #7B3DB5;
|
|
2186
2207
|
}
|
|
2187
2208
|
.settings-origin.origin-project {
|
|
2188
|
-
background: rgba(0,
|
|
2209
|
+
background: rgba(0,212,170,0.12);
|
|
2189
2210
|
color: var(--accent);
|
|
2190
2211
|
}
|
|
2191
2212
|
[data-theme="light"] .settings-origin.origin-project {
|
|
2192
|
-
background: rgba(0,
|
|
2193
|
-
color: #
|
|
2213
|
+
background: rgba(0,158,128,0.10);
|
|
2214
|
+
color: #009E80;
|
|
2194
2215
|
}
|
|
2195
2216
|
.settings-origin.origin-default {
|
|
2196
2217
|
background: rgba(136,147,151,0.12);
|
|
@@ -2313,6 +2334,108 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
2313
2334
|
.chat-sources summary { cursor: pointer; }
|
|
2314
2335
|
.chat-sources ul { margin: 4px 0 0 16px; padding: 0; }
|
|
2315
2336
|
.chat-sources li { margin: 2px 0; }
|
|
2337
|
+
/* Markdown rendering in assistant messages */
|
|
2338
|
+
.chat-message.assistant .chat-message-content.rendered {
|
|
2339
|
+
white-space: normal;
|
|
2340
|
+
}
|
|
2341
|
+
.chat-message.assistant .chat-message-content.rendered p {
|
|
2342
|
+
margin: 0 0 8px 0;
|
|
2343
|
+
}
|
|
2344
|
+
.chat-message.assistant .chat-message-content.rendered p:last-child {
|
|
2345
|
+
margin-bottom: 0;
|
|
2346
|
+
}
|
|
2347
|
+
.chat-message.assistant .chat-message-content.rendered h1,
|
|
2348
|
+
.chat-message.assistant .chat-message-content.rendered h2,
|
|
2349
|
+
.chat-message.assistant .chat-message-content.rendered h3,
|
|
2350
|
+
.chat-message.assistant .chat-message-content.rendered h4 {
|
|
2351
|
+
margin: 12px 0 6px 0;
|
|
2352
|
+
font-weight: 600;
|
|
2353
|
+
color: var(--accent-text, #fff);
|
|
2354
|
+
}
|
|
2355
|
+
.chat-message.assistant .chat-message-content.rendered h1 { font-size: 1.3em; }
|
|
2356
|
+
.chat-message.assistant .chat-message-content.rendered h2 { font-size: 1.15em; }
|
|
2357
|
+
.chat-message.assistant .chat-message-content.rendered h3 { font-size: 1.05em; }
|
|
2358
|
+
.chat-message.assistant .chat-message-content.rendered code {
|
|
2359
|
+
font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', monospace;
|
|
2360
|
+
font-size: 0.9em;
|
|
2361
|
+
background: var(--bg-input, #112733);
|
|
2362
|
+
padding: 2px 5px;
|
|
2363
|
+
border-radius: 4px;
|
|
2364
|
+
}
|
|
2365
|
+
.chat-message.assistant .chat-message-content.rendered pre {
|
|
2366
|
+
background: var(--bg-input, #112733);
|
|
2367
|
+
border: 1px solid var(--border);
|
|
2368
|
+
border-radius: 8px;
|
|
2369
|
+
padding: 12px;
|
|
2370
|
+
margin: 8px 0;
|
|
2371
|
+
overflow-x: auto;
|
|
2372
|
+
white-space: pre;
|
|
2373
|
+
}
|
|
2374
|
+
.chat-message.assistant .chat-message-content.rendered pre code {
|
|
2375
|
+
background: none;
|
|
2376
|
+
padding: 0;
|
|
2377
|
+
border-radius: 0;
|
|
2378
|
+
font-size: 13px;
|
|
2379
|
+
line-height: 1.5;
|
|
2380
|
+
}
|
|
2381
|
+
.chat-message.assistant .chat-message-content.rendered ul,
|
|
2382
|
+
.chat-message.assistant .chat-message-content.rendered ol {
|
|
2383
|
+
margin: 6px 0;
|
|
2384
|
+
padding-left: 20px;
|
|
2385
|
+
}
|
|
2386
|
+
.chat-message.assistant .chat-message-content.rendered li {
|
|
2387
|
+
margin: 3px 0;
|
|
2388
|
+
}
|
|
2389
|
+
.chat-message.assistant .chat-message-content.rendered blockquote {
|
|
2390
|
+
border-left: 3px solid var(--accent, #00D4AA);
|
|
2391
|
+
margin: 8px 0;
|
|
2392
|
+
padding: 4px 12px;
|
|
2393
|
+
color: var(--text-muted);
|
|
2394
|
+
}
|
|
2395
|
+
.chat-message.assistant .chat-message-content.rendered a {
|
|
2396
|
+
color: var(--accent, #00D4AA);
|
|
2397
|
+
text-decoration: none;
|
|
2398
|
+
}
|
|
2399
|
+
.chat-message.assistant .chat-message-content.rendered a:hover {
|
|
2400
|
+
text-decoration: underline;
|
|
2401
|
+
}
|
|
2402
|
+
.chat-message.assistant .chat-message-content.rendered table {
|
|
2403
|
+
border-collapse: collapse;
|
|
2404
|
+
margin: 8px 0;
|
|
2405
|
+
font-size: 13px;
|
|
2406
|
+
width: 100%;
|
|
2407
|
+
}
|
|
2408
|
+
.chat-message.assistant .chat-message-content.rendered th,
|
|
2409
|
+
.chat-message.assistant .chat-message-content.rendered td {
|
|
2410
|
+
border: 1px solid var(--border);
|
|
2411
|
+
padding: 6px 10px;
|
|
2412
|
+
text-align: left;
|
|
2413
|
+
}
|
|
2414
|
+
.chat-message.assistant .chat-message-content.rendered th {
|
|
2415
|
+
background: var(--bg-input, #112733);
|
|
2416
|
+
font-weight: 600;
|
|
2417
|
+
}
|
|
2418
|
+
.chat-message.assistant .chat-message-content.rendered hr {
|
|
2419
|
+
border: none;
|
|
2420
|
+
border-top: 1px solid var(--border);
|
|
2421
|
+
margin: 12px 0;
|
|
2422
|
+
}
|
|
2423
|
+
|
|
2424
|
+
.chat-tool-calls {
|
|
2425
|
+
display: flex; flex-direction: column; gap: 4px;
|
|
2426
|
+
margin-bottom: 4px; align-self: flex-start; max-width: 85%;
|
|
2427
|
+
}
|
|
2428
|
+
.chat-tool-call {
|
|
2429
|
+
font-size: 12px; color: var(--text-muted);
|
|
2430
|
+
padding: 6px 12px; border-radius: 8px;
|
|
2431
|
+
background: var(--bg-card); border: 1px dashed var(--border);
|
|
2432
|
+
display: flex; align-items: center; gap: 6px;
|
|
2433
|
+
}
|
|
2434
|
+
.chat-tool-call .tool-icon { opacity: 0.5; font-size: 11px; }
|
|
2435
|
+
.chat-tool-call .tool-name { font-weight: 600; }
|
|
2436
|
+
.chat-tool-call .tool-time { opacity: 0.5; }
|
|
2437
|
+
.chat-tool-call.error { border-color: #e74c3c; }
|
|
2438
|
+
.chat-tool-call .tool-error { color: #e74c3c; font-size: 11px; }
|
|
2316
2439
|
.chat-input-area {
|
|
2317
2440
|
padding: 12px 16px;
|
|
2318
2441
|
border-top: 1px solid var(--border);
|
|
@@ -4025,6 +4148,39 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
4025
4148
|
</select>
|
|
4026
4149
|
</div>
|
|
4027
4150
|
</div>
|
|
4151
|
+
<div class="settings-row" id="chatApiKeyRow" style="display:none">
|
|
4152
|
+
<div class="settings-label">
|
|
4153
|
+
<span class="settings-label-text">API Key</span>
|
|
4154
|
+
<span class="settings-label-hint">Provider API key (stored securely in OS keychain)</span>
|
|
4155
|
+
</div>
|
|
4156
|
+
<div class="settings-control" style="display:flex;gap:8px;align-items:center">
|
|
4157
|
+
<input
|
|
4158
|
+
type="password"
|
|
4159
|
+
class="settings-input"
|
|
4160
|
+
id="chatApiKey"
|
|
4161
|
+
placeholder="Enter your API key"
|
|
4162
|
+
style="flex:1;font-family:monospace;font-size:13px"
|
|
4163
|
+
>
|
|
4164
|
+
<button
|
|
4165
|
+
class="btn btn-secondary"
|
|
4166
|
+
id="chatApiKeyToggle"
|
|
4167
|
+
onclick="toggleChatApiKeyVisibility()"
|
|
4168
|
+
title="Show/hide API key"
|
|
4169
|
+
style="padding:8px 12px;min-width:auto"
|
|
4170
|
+
>
|
|
4171
|
+
👁️
|
|
4172
|
+
</button>
|
|
4173
|
+
<button
|
|
4174
|
+
class="btn"
|
|
4175
|
+
id="chatApiKeySave"
|
|
4176
|
+
onclick="saveChatApiKey()"
|
|
4177
|
+
title="Save API key"
|
|
4178
|
+
style="padding:8px 12px;min-width:auto"
|
|
4179
|
+
>
|
|
4180
|
+
💾
|
|
4181
|
+
</button>
|
|
4182
|
+
</div>
|
|
4183
|
+
</div>
|
|
4028
4184
|
</div>
|
|
4029
4185
|
<div class="settings-section">
|
|
4030
4186
|
<div class="settings-section-title">Knowledge Base</div>
|
|
@@ -4064,6 +4220,18 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
4064
4220
|
<button class="settings-toggle active" id="chatRerank" type="button"></button>
|
|
4065
4221
|
</div>
|
|
4066
4222
|
</div>
|
|
4223
|
+
<div class="settings-row">
|
|
4224
|
+
<div class="settings-label">
|
|
4225
|
+
<span class="settings-label-text">Chat Mode</span>
|
|
4226
|
+
<span class="settings-label-hint">Pipeline: fixed RAG retrieval. Agent: LLM chooses which tools to call.</span>
|
|
4227
|
+
</div>
|
|
4228
|
+
<div class="settings-control">
|
|
4229
|
+
<select class="settings-select" id="chatMode">
|
|
4230
|
+
<option value="pipeline">Pipeline (fixed RAG)</option>
|
|
4231
|
+
<option value="agent">Agent (tool-calling)</option>
|
|
4232
|
+
</select>
|
|
4233
|
+
</div>
|
|
4234
|
+
</div>
|
|
4067
4235
|
</div>
|
|
4068
4236
|
<div class="settings-section">
|
|
4069
4237
|
<div class="settings-section-title">Custom Instructions</div>
|
|
@@ -4280,7 +4448,7 @@ let lastEmbedding = null;
|
|
|
4280
4448
|
|
|
4281
4449
|
// ── Init ──
|
|
4282
4450
|
// ── Anonymous Telemetry ──
|
|
4283
|
-
const TELEMETRY_URL = 'https://
|
|
4451
|
+
const TELEMETRY_URL = 'https://vaicli.com/api/telemetry';
|
|
4284
4452
|
const TELEMETRY_KEY = 'vai-telemetry-enabled';
|
|
4285
4453
|
|
|
4286
4454
|
function isTelemetryEnabled() {
|
|
@@ -5318,8 +5486,8 @@ const BENCH_SAMPLE_TEXTS = [
|
|
|
5318
5486
|
];
|
|
5319
5487
|
|
|
5320
5488
|
const MODEL_COLORS = [
|
|
5321
|
-
'#
|
|
5322
|
-
'#FF6960', '#B45AF2', '#FFC010', '#
|
|
5489
|
+
'#00D4AA', '#5CE8CC', '#40E0FF', '#B45AF2', '#FFC010',
|
|
5490
|
+
'#FF6960', '#B45AF2', '#FFC010', '#0088CC', '#A8F0E0',
|
|
5323
5491
|
];
|
|
5324
5492
|
|
|
5325
5493
|
window.doBenchLatency = async function() {
|
|
@@ -5660,7 +5828,7 @@ window.doBenchQuantization = async function() {
|
|
|
5660
5828
|
const baseline = completed.find(r => r.dtype === 'float') || completed[0];
|
|
5661
5829
|
const maxBytes = Math.max(...completed.map(r => r.bytesPerVec));
|
|
5662
5830
|
const maxLatency = Math.max(...completed.map(r => r.latency));
|
|
5663
|
-
const DTYPE_COLORS = { float: '#
|
|
5831
|
+
const DTYPE_COLORS = { float: '#00D4AA', int8: '#5CE8CC', uint8: '#40E0FF', ubinary: '#FFC010', binary: '#FF6960' };
|
|
5664
5832
|
|
|
5665
5833
|
// ── Storage Bar Chart ──
|
|
5666
5834
|
let storageHTML = '';
|
|
@@ -5671,7 +5839,7 @@ window.doBenchQuantization = async function() {
|
|
|
5671
5839
|
const savings = r.bytesPerVec < baseline.bytesPerVec
|
|
5672
5840
|
? `${(baseline.bytesPerVec / r.bytesPerVec).toFixed(0)}× smaller`
|
|
5673
5841
|
: 'baseline';
|
|
5674
|
-
const color = DTYPE_COLORS[r.dtype] || '#
|
|
5842
|
+
const color = DTYPE_COLORS[r.dtype] || '#40E0FF';
|
|
5675
5843
|
storageHTML += `<div class="quant-bar-group">
|
|
5676
5844
|
<div class="quant-bar-label">
|
|
5677
5845
|
<span class="dtype-name">${r.dtype}</span>
|
|
@@ -5689,7 +5857,7 @@ window.doBenchQuantization = async function() {
|
|
|
5689
5857
|
const minLatency = Math.min(...completed.map(r => r.latency));
|
|
5690
5858
|
for (const r of completed) {
|
|
5691
5859
|
const pct = Math.max(8, (r.latency / maxLatency) * 100);
|
|
5692
|
-
const color = DTYPE_COLORS[r.dtype] || '#
|
|
5860
|
+
const color = DTYPE_COLORS[r.dtype] || '#40E0FF';
|
|
5693
5861
|
const badge = r.latency === minLatency ? ' ⚡' : '';
|
|
5694
5862
|
latencyHTML += `<div class="quant-bar-group">
|
|
5695
5863
|
<div class="quant-bar-label">
|
|
@@ -6476,6 +6644,8 @@ function initSettings() {
|
|
|
6476
6644
|
}
|
|
6477
6645
|
const chatModel = document.getElementById('chatModel');
|
|
6478
6646
|
if (chatModel) chatModel.addEventListener('change', saveChatSettings);
|
|
6647
|
+
const chatMode = document.getElementById('chatMode');
|
|
6648
|
+
if (chatMode) chatMode.addEventListener('change', saveChatSettings);
|
|
6479
6649
|
const chatMaxDocs = document.getElementById('chatMaxDocs');
|
|
6480
6650
|
if (chatMaxDocs) chatMaxDocs.addEventListener('change', saveChatSettings);
|
|
6481
6651
|
|
|
@@ -6487,6 +6657,23 @@ function initSettings() {
|
|
|
6487
6657
|
const chatSystemPrompt = document.getElementById('chatSystemPrompt');
|
|
6488
6658
|
if (chatSystemPrompt) chatSystemPrompt.addEventListener('input', saveChatSettingsDebounced);
|
|
6489
6659
|
|
|
6660
|
+
// API key input listeners
|
|
6661
|
+
const chatApiKey = document.getElementById('chatApiKey');
|
|
6662
|
+
if (chatApiKey) {
|
|
6663
|
+
// Mark as modified when user types
|
|
6664
|
+
chatApiKey.addEventListener('input', () => {
|
|
6665
|
+
delete chatApiKey.dataset.isMasked;
|
|
6666
|
+
delete chatApiKey.dataset.actualKey;
|
|
6667
|
+
});
|
|
6668
|
+
// Save on Enter key
|
|
6669
|
+
chatApiKey.addEventListener('keydown', (e) => {
|
|
6670
|
+
if (e.key === 'Enter') {
|
|
6671
|
+
e.preventDefault();
|
|
6672
|
+
saveChatApiKey();
|
|
6673
|
+
}
|
|
6674
|
+
});
|
|
6675
|
+
}
|
|
6676
|
+
|
|
6490
6677
|
// Set up settings sub-navigation
|
|
6491
6678
|
setupSettingsNav();
|
|
6492
6679
|
}
|
|
@@ -6860,10 +7047,29 @@ function initMultimodal() {
|
|
|
6860
7047
|
const dropZone = document.getElementById('mmDropZone');
|
|
6861
7048
|
const fileInput = document.getElementById('mmFileInput');
|
|
6862
7049
|
|
|
6863
|
-
// Click to browse
|
|
6864
|
-
|
|
7050
|
+
// Click to browse — in Electron, use native dialog (more reliable);
|
|
7051
|
+
// in browser, use <input type="file"> with a re-trigger guard.
|
|
7052
|
+
let fileDialogOpen = false;
|
|
7053
|
+
dropZone.addEventListener('click', async () => {
|
|
7054
|
+
if (fileDialogOpen) return;
|
|
7055
|
+
fileDialogOpen = true;
|
|
7056
|
+
try {
|
|
7057
|
+
if (window.vai && window.vai.isElectron && window.vai.openImageDialog) {
|
|
7058
|
+
const result = await window.vai.openImageDialog();
|
|
7059
|
+
if (!result.canceled && result.dataUrl) {
|
|
7060
|
+
handleMultimodalImageFromData(result.dataUrl, result.name, result.size);
|
|
7061
|
+
}
|
|
7062
|
+
} else {
|
|
7063
|
+
fileInput.click();
|
|
7064
|
+
}
|
|
7065
|
+
} finally {
|
|
7066
|
+
setTimeout(() => { fileDialogOpen = false; }, 300);
|
|
7067
|
+
}
|
|
7068
|
+
});
|
|
6865
7069
|
fileInput.addEventListener('change', (e) => {
|
|
7070
|
+
fileDialogOpen = false;
|
|
6866
7071
|
if (e.target.files && e.target.files[0]) handleMultimodalImage(e.target.files[0]);
|
|
7072
|
+
fileInput.value = '';
|
|
6867
7073
|
});
|
|
6868
7074
|
|
|
6869
7075
|
// Drag and drop
|
|
@@ -6915,6 +7121,23 @@ function initMultimodal() {
|
|
|
6915
7121
|
});
|
|
6916
7122
|
}
|
|
6917
7123
|
|
|
7124
|
+
// Handle image from Electron native dialog (already has dataUrl)
|
|
7125
|
+
function handleMultimodalImageFromData(dataUrl, name, size) {
|
|
7126
|
+
mmImageData = dataUrl;
|
|
7127
|
+
const img = document.getElementById('mmPreviewImg');
|
|
7128
|
+
img.src = mmImageData;
|
|
7129
|
+
img.onload = () => {
|
|
7130
|
+
const info = document.getElementById('mmFileInfo');
|
|
7131
|
+
const sizeStr = size > 1024 * 1024
|
|
7132
|
+
? (size / (1024 * 1024)).toFixed(1) + ' MB'
|
|
7133
|
+
: (size / 1024).toFixed(0) + ' KB';
|
|
7134
|
+
info.textContent = `${name} · ${img.naturalWidth}×${img.naturalHeight} · ${sizeStr}`;
|
|
7135
|
+
};
|
|
7136
|
+
document.getElementById('mmDropZone').style.display = 'none';
|
|
7137
|
+
document.getElementById('mmPreview').classList.add('visible');
|
|
7138
|
+
hideError('mmError');
|
|
7139
|
+
}
|
|
7140
|
+
|
|
6918
7141
|
function handleMultimodalImage(file) {
|
|
6919
7142
|
const VALID_TYPES = ['image/png', 'image/jpeg', 'image/webp', 'image/gif'];
|
|
6920
7143
|
if (!VALID_TYPES.includes(file.type)) {
|
|
@@ -7052,13 +7275,33 @@ function renderGalleryGrid() {
|
|
|
7052
7275
|
const addSlot = document.createElement('div');
|
|
7053
7276
|
addSlot.className = 'mm-gallery-slot';
|
|
7054
7277
|
addSlot.innerHTML = '<span class="mm-slot-add">+</span>';
|
|
7055
|
-
addSlot.addEventListener('click', () => {
|
|
7056
|
-
|
|
7278
|
+
addSlot.addEventListener('click', async () => {
|
|
7279
|
+
if (window._galleryDialogOpen) return;
|
|
7280
|
+
window._galleryDialogOpen = true;
|
|
7281
|
+
try {
|
|
7282
|
+
if (window.vai && window.vai.isElectron && window.vai.openImageDialog) {
|
|
7283
|
+
const result = await window.vai.openImageDialog();
|
|
7284
|
+
if (!result.canceled && result.dataUrl) {
|
|
7285
|
+
addGalleryImageFromData(result.dataUrl, result.name, result.size);
|
|
7286
|
+
}
|
|
7287
|
+
} else {
|
|
7288
|
+
document.getElementById('mmGalleryFileInput').click();
|
|
7289
|
+
}
|
|
7290
|
+
} finally {
|
|
7291
|
+
setTimeout(() => { window._galleryDialogOpen = false; }, 300);
|
|
7292
|
+
}
|
|
7057
7293
|
});
|
|
7058
7294
|
grid.appendChild(addSlot);
|
|
7059
7295
|
}
|
|
7060
7296
|
}
|
|
7061
7297
|
|
|
7298
|
+
// Add gallery image from Electron native dialog (already has dataUrl)
|
|
7299
|
+
function addGalleryImageFromData(dataUrl, name, size) {
|
|
7300
|
+
if (mmGalleryImages.length >= 6) return;
|
|
7301
|
+
mmGalleryImages.push({ dataUrl, name, size });
|
|
7302
|
+
renderGalleryGrid();
|
|
7303
|
+
}
|
|
7304
|
+
|
|
7062
7305
|
function addGalleryImage(file) {
|
|
7063
7306
|
const VALID_TYPES = ['image/png', 'image/jpeg', 'image/webp', 'image/gif'];
|
|
7064
7307
|
if (!VALID_TYPES.includes(file.type)) return;
|
|
@@ -7229,7 +7472,22 @@ init = async function() {
|
|
|
7229
7472
|
// ── Start ──
|
|
7230
7473
|
init();
|
|
7231
7474
|
|
|
7232
|
-
// ── Vector Space Invaders (Easter Egg) ──
|
|
7475
|
+
// ── Vector Space Invaders (Easter Egg) ──
|
|
7476
|
+
|
|
7477
|
+
// VSI Telemetry helper
|
|
7478
|
+
function sendVSITelemetry(event, extra) {
|
|
7479
|
+
try {
|
|
7480
|
+
fetch('https://vaicli.com/api/telemetry', {
|
|
7481
|
+
method: 'POST',
|
|
7482
|
+
headers: { 'Content-Type': 'application/json' },
|
|
7483
|
+
body: JSON.stringify({ event, ...extra, sentAt: new Date().toISOString() }),
|
|
7484
|
+
}).catch(() => {});
|
|
7485
|
+
} catch (_) {}
|
|
7486
|
+
}
|
|
7487
|
+
|
|
7488
|
+
let _vsiGameStartTime = 0;
|
|
7489
|
+
let _vsiGameTrigger = 'unknown';
|
|
7490
|
+
|
|
7233
7491
|
(function initEasterEgg() {
|
|
7234
7492
|
const KONAMI = ['ArrowUp','ArrowUp','ArrowDown','ArrowDown','ArrowLeft','ArrowRight','ArrowLeft','ArrowRight','b','a'];
|
|
7235
7493
|
let konamiIdx = 0;
|
|
@@ -7237,7 +7495,7 @@ init();
|
|
|
7237
7495
|
|
|
7238
7496
|
document.addEventListener('keydown', (e) => {
|
|
7239
7497
|
if (document.getElementById('vsiOverlay')?.style.display === 'flex') return;
|
|
7240
|
-
if (e.key === KONAMI[konamiIdx]) { konamiIdx++; if (konamiIdx === KONAMI.length) { konamiIdx = 0; launchVSI(); } }
|
|
7498
|
+
if (e.key === KONAMI[konamiIdx]) { konamiIdx++; if (konamiIdx === KONAMI.length) { konamiIdx = 0; _vsiGameTrigger = 'konami'; launchVSI(); } }
|
|
7241
7499
|
else { konamiIdx = 0; }
|
|
7242
7500
|
});
|
|
7243
7501
|
|
|
@@ -7247,7 +7505,7 @@ init();
|
|
|
7247
7505
|
const now = Date.now();
|
|
7248
7506
|
logoClicks.push(now);
|
|
7249
7507
|
logoClicks = logoClicks.filter(t => now - t < 2000);
|
|
7250
|
-
if (logoClicks.length >= 7) { logoClicks = []; launchVSI(); }
|
|
7508
|
+
if (logoClicks.length >= 7) { logoClicks = []; _vsiGameTrigger = 'logo_click'; launchVSI(); }
|
|
7251
7509
|
});
|
|
7252
7510
|
}
|
|
7253
7511
|
|
|
@@ -7256,6 +7514,22 @@ init();
|
|
|
7256
7514
|
const canvas = document.getElementById('vsiCanvas');
|
|
7257
7515
|
if (!overlay || !canvas) return;
|
|
7258
7516
|
overlay.style.display = 'flex';
|
|
7517
|
+
_vsiGameStartTime = Date.now();
|
|
7518
|
+
|
|
7519
|
+
// Detect version from page
|
|
7520
|
+
const versionEl = document.querySelector('.version-label, [data-version]');
|
|
7521
|
+
const version = versionEl ? (versionEl.dataset?.version || versionEl.textContent?.trim()) : 'unknown';
|
|
7522
|
+
const ua = navigator.userAgent;
|
|
7523
|
+
const platform = /Mac/.test(ua) ? 'macOS' : /Win/.test(ua) ? 'Windows' : /Linux/.test(ua) ? 'Linux' : /Android/.test(ua) ? 'Android' : /iPhone|iPad/.test(ua) ? 'iOS' : 'unknown';
|
|
7524
|
+
|
|
7525
|
+
sendVSITelemetry('vsi_game_start', {
|
|
7526
|
+
version,
|
|
7527
|
+
context: 'playground',
|
|
7528
|
+
platform,
|
|
7529
|
+
locale: navigator.language || 'unknown',
|
|
7530
|
+
trigger: _vsiGameTrigger,
|
|
7531
|
+
});
|
|
7532
|
+
|
|
7259
7533
|
startVSI(canvas, () => { overlay.style.display = 'none'; });
|
|
7260
7534
|
}
|
|
7261
7535
|
})();
|
|
@@ -7277,7 +7551,7 @@ function startVSI(canvas, onExit) {
|
|
|
7277
7551
|
ctx.quadraticCurveTo(x - size * 0.6, y - size * 0.3, x, y - size);
|
|
7278
7552
|
ctx.fill();
|
|
7279
7553
|
// Central vein
|
|
7280
|
-
ctx.strokeStyle = color === '#
|
|
7554
|
+
ctx.strokeStyle = color === '#00D4AA' ? '#009E80' : color;
|
|
7281
7555
|
ctx.lineWidth = 1;
|
|
7282
7556
|
ctx.beginPath();
|
|
7283
7557
|
ctx.moveTo(x, y - size);
|
|
@@ -7312,7 +7586,7 @@ function startVSI(canvas, onExit) {
|
|
|
7312
7586
|
let waveQueued = false;
|
|
7313
7587
|
|
|
7314
7588
|
const keys = {};
|
|
7315
|
-
const keyDown = (e) => { keys[e.key] = true; if (['ArrowLeft','ArrowRight',' ','Escape'].includes(e.key)) e.preventDefault(); if (e.key === 'Escape') { cleanup(); onExit(); } };
|
|
7589
|
+
const keyDown = (e) => { keys[e.key] = true; if (['ArrowLeft','ArrowRight',' ','Escape'].includes(e.key)) e.preventDefault(); if (e.key === 'Escape') { sendVSITelemetry('vsi_game_over', { score, wave, durationMs: Date.now() - _vsiGameStartTime, livesRemaining: lives, exitReason: 'esc' }); cleanup(); onExit(); } };
|
|
7316
7590
|
const keyUp = (e) => { keys[e.key] = false; };
|
|
7317
7591
|
document.addEventListener('keydown', keyDown);
|
|
7318
7592
|
document.addEventListener('keyup', keyUp);
|
|
@@ -7353,7 +7627,7 @@ function startVSI(canvas, onExit) {
|
|
|
7353
7627
|
}
|
|
7354
7628
|
|
|
7355
7629
|
function addExplosion(x, y, sim) {
|
|
7356
|
-
const color = sim > 0.7 ? '#
|
|
7630
|
+
const color = sim > 0.7 ? '#00D4AA' : sim > 0.4 ? '#FFC010' : '#FF6960';
|
|
7357
7631
|
for (let i = 0; i < 8; i++) {
|
|
7358
7632
|
const angle = (Math.PI * 2 / 8) * i + Math.random() * 0.5;
|
|
7359
7633
|
const speed = 1.5 + Math.random() * 2;
|
|
@@ -7445,7 +7719,7 @@ function startVSI(canvas, onExit) {
|
|
|
7445
7719
|
if (enemies[i].y > H - 50) {
|
|
7446
7720
|
enemies.splice(i, 1);
|
|
7447
7721
|
lives--;
|
|
7448
|
-
if (lives <= 0) gameOver = true;
|
|
7722
|
+
if (lives <= 0) { gameOver = true; sendVSITelemetry('vsi_game_over', { score, wave, durationMs: Date.now() - _vsiGameStartTime, livesRemaining: 0, exitReason: 'death' }); }
|
|
7449
7723
|
}
|
|
7450
7724
|
}
|
|
7451
7725
|
|
|
@@ -7482,16 +7756,16 @@ function startVSI(canvas, onExit) {
|
|
|
7482
7756
|
}
|
|
7483
7757
|
|
|
7484
7758
|
// Ship (Voyage AI leaf)
|
|
7485
|
-
drawLeaf(ctx, ship.x, H - 32, 12, '#
|
|
7759
|
+
drawLeaf(ctx, ship.x, H - 32, 12, '#00D4AA');
|
|
7486
7760
|
// Ship label
|
|
7487
|
-
ctx.fillStyle = '#
|
|
7761
|
+
ctx.fillStyle = '#00D4AA';
|
|
7488
7762
|
ctx.font = '9px monospace';
|
|
7489
7763
|
ctx.textAlign = 'center';
|
|
7490
7764
|
ctx.fillText('voyage', ship.x, H - 14);
|
|
7491
7765
|
|
|
7492
7766
|
// Bullets (small leaf shapes)
|
|
7493
7767
|
for (const b of bullets) {
|
|
7494
|
-
drawLeaf(ctx, b.x, b.y, 3, '#
|
|
7768
|
+
drawLeaf(ctx, b.x, b.y, 3, '#00D4AA');
|
|
7495
7769
|
}
|
|
7496
7770
|
|
|
7497
7771
|
// Boss
|
|
@@ -7516,7 +7790,7 @@ function startVSI(canvas, onExit) {
|
|
|
7516
7790
|
for (const e of enemies) {
|
|
7517
7791
|
ctx.fillStyle = 'rgba(61,79,88,0.6)';
|
|
7518
7792
|
ctx.fillRect(e.x - e.w / 2, e.y - e.h / 2, e.w, e.h);
|
|
7519
|
-
ctx.strokeStyle = '#
|
|
7793
|
+
ctx.strokeStyle = '#40E0FF';
|
|
7520
7794
|
ctx.lineWidth = 1;
|
|
7521
7795
|
ctx.strokeRect(e.x - e.w / 2, e.y - e.h / 2, e.w, e.h);
|
|
7522
7796
|
ctx.fillStyle = '#E8EDEB';
|
|
@@ -7556,19 +7830,19 @@ function startVSI(canvas, onExit) {
|
|
|
7556
7830
|
|
|
7557
7831
|
// Lives (leaf icons)
|
|
7558
7832
|
for (let i = 0; i < lives; i++) {
|
|
7559
|
-
drawLeaf(ctx, W - 20 - (i * 18), 15, 6, '#
|
|
7833
|
+
drawLeaf(ctx, W - 20 - (i * 18), 15, 6, '#00D4AA');
|
|
7560
7834
|
}
|
|
7561
7835
|
|
|
7562
7836
|
// Title with Voyage AI branding
|
|
7563
|
-
ctx.fillStyle = '#
|
|
7837
|
+
ctx.fillStyle = '#00D4AA';
|
|
7564
7838
|
ctx.font = 'bold 10px monospace';
|
|
7565
7839
|
ctx.textAlign = 'center';
|
|
7566
7840
|
ctx.fillText('VECTOR SPACE INVADERS', W / 2, H - 4);
|
|
7567
7841
|
|
|
7568
7842
|
// Voyage AI watermark (top right corner)
|
|
7569
7843
|
ctx.globalAlpha = 0.3;
|
|
7570
|
-
drawLeaf(ctx, W - 25, 25, 8, '#
|
|
7571
|
-
ctx.fillStyle = '#
|
|
7844
|
+
drawLeaf(ctx, W - 25, 25, 8, '#00D4AA');
|
|
7845
|
+
ctx.fillStyle = '#00D4AA';
|
|
7572
7846
|
ctx.font = 'bold 9px monospace';
|
|
7573
7847
|
ctx.textAlign = 'right';
|
|
7574
7848
|
ctx.fillText('VOYAGE AI', W - 38, 28);
|
|
@@ -7578,7 +7852,7 @@ function startVSI(canvas, onExit) {
|
|
|
7578
7852
|
if (gameOver) {
|
|
7579
7853
|
ctx.fillStyle = 'rgba(0,30,43,0.8)';
|
|
7580
7854
|
ctx.fillRect(0, 0, W, H);
|
|
7581
|
-
ctx.fillStyle = '#
|
|
7855
|
+
ctx.fillStyle = '#00D4AA';
|
|
7582
7856
|
ctx.font = 'bold 28px monospace';
|
|
7583
7857
|
ctx.textAlign = 'center';
|
|
7584
7858
|
ctx.fillText('GAME OVER', W / 2, H / 2 - 30);
|
|
@@ -7679,6 +7953,7 @@ function saveChatSettings() {
|
|
|
7679
7953
|
maxDocs: parseInt(document.getElementById('chatMaxDocs').value) || 5,
|
|
7680
7954
|
rerank: document.getElementById('chatRerank').classList.contains('active'),
|
|
7681
7955
|
systemPrompt: document.getElementById('chatSystemPrompt').value.trim(),
|
|
7956
|
+
mode: document.getElementById('chatMode').value,
|
|
7682
7957
|
};
|
|
7683
7958
|
fetch('/api/chat/config', {
|
|
7684
7959
|
method: 'POST',
|
|
@@ -7727,6 +8002,7 @@ async function loadChatConfig() {
|
|
|
7727
8002
|
if (data.chat?.maxContextDocs) document.getElementById('chatMaxDocs').value = data.chat.maxContextDocs;
|
|
7728
8003
|
if (data.chat?.rerank === false) document.getElementById('chatRerank').classList.remove('active');
|
|
7729
8004
|
if (data.chat?.systemPrompt) document.getElementById('chatSystemPrompt').value = data.chat.systemPrompt;
|
|
8005
|
+
if (data.mode || data.chat?.mode) document.getElementById('chatMode').value = data.mode || data.chat?.mode || 'pipeline';
|
|
7730
8006
|
updateChatStatus();
|
|
7731
8007
|
// Show not-configured banner if incomplete
|
|
7732
8008
|
const notConfigured = document.getElementById('chatNotConfigured');
|
|
@@ -7740,23 +8016,137 @@ async function loadChatConfig() {
|
|
|
7740
8016
|
}
|
|
7741
8017
|
}
|
|
7742
8018
|
|
|
8019
|
+
// ── Chat API Key Management ──
|
|
8020
|
+
|
|
8021
|
+
async function loadChatApiKey() {
|
|
8022
|
+
if (!window.vai?.llmKey) return;
|
|
8023
|
+
const apiKeyInput = document.getElementById('chatApiKey');
|
|
8024
|
+
if (!apiKeyInput) return;
|
|
8025
|
+
|
|
8026
|
+
try {
|
|
8027
|
+
const key = await window.vai.llmKey.get();
|
|
8028
|
+
if (key) {
|
|
8029
|
+
// Mask the key by default (show first 4 + last 4)
|
|
8030
|
+
const masked = key.slice(0, 4) + '•'.repeat(Math.max(0, key.length - 8)) + key.slice(-4);
|
|
8031
|
+
apiKeyInput.value = masked;
|
|
8032
|
+
apiKeyInput.dataset.actualKey = key;
|
|
8033
|
+
apiKeyInput.dataset.isMasked = 'true';
|
|
8034
|
+
apiKeyInput.type = 'password';
|
|
8035
|
+
} else {
|
|
8036
|
+
apiKeyInput.value = '';
|
|
8037
|
+
delete apiKeyInput.dataset.actualKey;
|
|
8038
|
+
delete apiKeyInput.dataset.isMasked;
|
|
8039
|
+
}
|
|
8040
|
+
} catch (err) {
|
|
8041
|
+
console.error('Failed to load LLM API key:', err);
|
|
8042
|
+
}
|
|
8043
|
+
}
|
|
8044
|
+
|
|
8045
|
+
function toggleChatApiKeyVisibility() {
|
|
8046
|
+
const apiKeyInput = document.getElementById('chatApiKey');
|
|
8047
|
+
const toggleBtn = document.getElementById('chatApiKeyToggle');
|
|
8048
|
+
if (!apiKeyInput || !toggleBtn) return;
|
|
8049
|
+
|
|
8050
|
+
if (apiKeyInput.dataset.isMasked === 'true' && apiKeyInput.dataset.actualKey) {
|
|
8051
|
+
// Show actual key
|
|
8052
|
+
apiKeyInput.value = apiKeyInput.dataset.actualKey;
|
|
8053
|
+
apiKeyInput.type = 'text';
|
|
8054
|
+
apiKeyInput.dataset.isMasked = 'false';
|
|
8055
|
+
toggleBtn.textContent = '🙈';
|
|
8056
|
+
toggleBtn.title = 'Hide API key';
|
|
8057
|
+
} else if (apiKeyInput.dataset.actualKey) {
|
|
8058
|
+
// Re-mask the key
|
|
8059
|
+
const key = apiKeyInput.dataset.actualKey;
|
|
8060
|
+
const masked = key.slice(0, 4) + '•'.repeat(Math.max(0, key.length - 8)) + key.slice(-4);
|
|
8061
|
+
apiKeyInput.value = masked;
|
|
8062
|
+
apiKeyInput.type = 'password';
|
|
8063
|
+
apiKeyInput.dataset.isMasked = 'true';
|
|
8064
|
+
toggleBtn.textContent = '👁️';
|
|
8065
|
+
toggleBtn.title = 'Show API key';
|
|
8066
|
+
} else {
|
|
8067
|
+
// No stored key, just toggle between text/password for new input
|
|
8068
|
+
apiKeyInput.type = apiKeyInput.type === 'password' ? 'text' : 'password';
|
|
8069
|
+
toggleBtn.textContent = apiKeyInput.type === 'password' ? '👁️' : '🙈';
|
|
8070
|
+
toggleBtn.title = apiKeyInput.type === 'password' ? 'Show API key' : 'Hide API key';
|
|
8071
|
+
}
|
|
8072
|
+
}
|
|
8073
|
+
|
|
8074
|
+
async function saveChatApiKey() {
|
|
8075
|
+
if (!window.vai?.llmKey) return;
|
|
8076
|
+
const apiKeyInput = document.getElementById('chatApiKey');
|
|
8077
|
+
const toggleBtn = document.getElementById('chatApiKeyToggle');
|
|
8078
|
+
if (!apiKeyInput) return;
|
|
8079
|
+
|
|
8080
|
+
let key = apiKeyInput.value.trim();
|
|
8081
|
+
|
|
8082
|
+
// If showing masked value and it hasn't been modified, no need to save
|
|
8083
|
+
if (apiKeyInput.dataset.isMasked === 'true' && apiKeyInput.dataset.actualKey) {
|
|
8084
|
+
const currentMasked = apiKeyInput.dataset.actualKey.slice(0, 4) + '•'.repeat(Math.max(0, apiKeyInput.dataset.actualKey.length - 8)) + apiKeyInput.dataset.actualKey.slice(-4);
|
|
8085
|
+
if (key === currentMasked) {
|
|
8086
|
+
flashSaved();
|
|
8087
|
+
return;
|
|
8088
|
+
}
|
|
8089
|
+
}
|
|
8090
|
+
|
|
8091
|
+
if (!key) {
|
|
8092
|
+
// Delete the key if empty
|
|
8093
|
+
try {
|
|
8094
|
+
await window.vai.llmKey.delete();
|
|
8095
|
+
delete apiKeyInput.dataset.actualKey;
|
|
8096
|
+
delete apiKeyInput.dataset.isMasked;
|
|
8097
|
+
flashSaved();
|
|
8098
|
+
} catch (err) {
|
|
8099
|
+
console.error('Failed to delete LLM API key:', err);
|
|
8100
|
+
alert('Failed to delete API key: ' + err.message);
|
|
8101
|
+
}
|
|
8102
|
+
return;
|
|
8103
|
+
}
|
|
8104
|
+
|
|
8105
|
+
try {
|
|
8106
|
+
await window.vai.llmKey.set(key);
|
|
8107
|
+
// Mask the newly saved key
|
|
8108
|
+
const masked = key.slice(0, 4) + '•'.repeat(Math.max(0, key.length - 8)) + key.slice(-4);
|
|
8109
|
+
apiKeyInput.value = masked;
|
|
8110
|
+
apiKeyInput.dataset.actualKey = key;
|
|
8111
|
+
apiKeyInput.dataset.isMasked = 'true';
|
|
8112
|
+
apiKeyInput.type = 'password';
|
|
8113
|
+
toggleBtn.textContent = '👁️';
|
|
8114
|
+
toggleBtn.title = 'Show API key';
|
|
8115
|
+
flashSaved();
|
|
8116
|
+
} catch (err) {
|
|
8117
|
+
console.error('Failed to save LLM API key:', err);
|
|
8118
|
+
alert('Failed to save API key: ' + err.message);
|
|
8119
|
+
}
|
|
8120
|
+
}
|
|
8121
|
+
|
|
7743
8122
|
function updateChatStatus() {
|
|
7744
8123
|
const provider = document.getElementById('chatProvider');
|
|
7745
8124
|
const db = document.getElementById('chatDb').value;
|
|
7746
8125
|
const collection = document.getElementById('chatCollection').value;
|
|
8126
|
+
const mode = document.getElementById('chatMode')?.value || 'pipeline';
|
|
7747
8127
|
const providerLabel = provider.value
|
|
7748
8128
|
? provider.options[provider.selectedIndex].text
|
|
7749
8129
|
: 'No provider';
|
|
7750
|
-
|
|
8130
|
+
const modeLabel = mode === 'agent' ? 'Agent' : 'Pipeline';
|
|
8131
|
+
document.getElementById('chatStatusProvider').textContent = `${providerLabel} (${modeLabel})`;
|
|
7751
8132
|
document.getElementById('chatStatusDb').textContent =
|
|
7752
|
-
(db && collection) ? `${db}.${collection}` : 'No database';
|
|
8133
|
+
(db && collection) ? `${db}.${collection}` : (mode === 'agent' ? 'Agent discovers' : 'No database');
|
|
7753
8134
|
}
|
|
7754
8135
|
|
|
7755
8136
|
async function chatProviderChanged() {
|
|
7756
8137
|
updateChatStatus();
|
|
7757
8138
|
const provider = document.getElementById('chatProvider').value;
|
|
7758
8139
|
const modelSelect = document.getElementById('chatModel');
|
|
7759
|
-
|
|
8140
|
+
const apiKeyRow = document.getElementById('chatApiKeyRow');
|
|
8141
|
+
|
|
8142
|
+
// Show/hide API key row based on provider (not needed for Ollama)
|
|
8143
|
+
if (apiKeyRow) {
|
|
8144
|
+
apiKeyRow.style.display = (provider === 'anthropic' || provider === 'openai') ? 'flex' : 'none';
|
|
8145
|
+
if (provider === 'anthropic' || provider === 'openai') {
|
|
8146
|
+
await loadChatApiKey();
|
|
8147
|
+
}
|
|
8148
|
+
}
|
|
8149
|
+
|
|
7760
8150
|
if (!provider) {
|
|
7761
8151
|
modelSelect.innerHTML = '<option value="">Select provider first</option>';
|
|
7762
8152
|
return;
|
|
@@ -7834,6 +8224,123 @@ function chatInputKeydown(e) {
|
|
|
7834
8224
|
}
|
|
7835
8225
|
}
|
|
7836
8226
|
|
|
8227
|
+
/**
|
|
8228
|
+
* Lightweight markdown-to-HTML renderer for assistant chat messages.
|
|
8229
|
+
* Handles: fenced code blocks, inline code, headings, bold, italic,
|
|
8230
|
+
* links, images, unordered/ordered lists, blockquotes, tables, and <hr>.
|
|
8231
|
+
* No external dependencies.
|
|
8232
|
+
*/
|
|
8233
|
+
function renderMarkdown(md) {
|
|
8234
|
+
// Escape HTML to prevent XSS, then selectively render markdown
|
|
8235
|
+
function esc(s) {
|
|
8236
|
+
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
8237
|
+
}
|
|
8238
|
+
|
|
8239
|
+
// Extract fenced code blocks first (preserve content as-is)
|
|
8240
|
+
const codeBlocks = [];
|
|
8241
|
+
let text = md.replace(/```(\w*)\n([\s\S]*?)```/g, (_, lang, code) => {
|
|
8242
|
+
const idx = codeBlocks.length;
|
|
8243
|
+
codeBlocks.push(`<pre><code${lang ? ' data-lang="' + esc(lang) + '"' : ''}>${esc(code.replace(/\n$/, ''))}</code></pre>`);
|
|
8244
|
+
return '\x00CB' + idx + '\x00';
|
|
8245
|
+
});
|
|
8246
|
+
|
|
8247
|
+
// Escape remaining HTML
|
|
8248
|
+
text = esc(text);
|
|
8249
|
+
|
|
8250
|
+
// Restore code block placeholders
|
|
8251
|
+
text = text.replace(/\x00CB(\d+)\x00/g, (_, i) => codeBlocks[i]);
|
|
8252
|
+
|
|
8253
|
+
// Inline code (must come after escape but before other inline formatting)
|
|
8254
|
+
text = text.replace(/`([^`\n]+)`/g, '<code>$1</code>');
|
|
8255
|
+
|
|
8256
|
+
// Headings (must be at line start)
|
|
8257
|
+
text = text.replace(/^#### (.+)$/gm, '<h4>$1</h4>');
|
|
8258
|
+
text = text.replace(/^### (.+)$/gm, '<h3>$1</h3>');
|
|
8259
|
+
text = text.replace(/^## (.+)$/gm, '<h2>$1</h2>');
|
|
8260
|
+
text = text.replace(/^# (.+)$/gm, '<h1>$1</h1>');
|
|
8261
|
+
|
|
8262
|
+
// Horizontal rule
|
|
8263
|
+
text = text.replace(/^---+$/gm, '<hr>');
|
|
8264
|
+
|
|
8265
|
+
// Bold and italic
|
|
8266
|
+
text = text.replace(/\*\*\*(.+?)\*\*\*/g, '<strong><em>$1</em></strong>');
|
|
8267
|
+
text = text.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
|
|
8268
|
+
text = text.replace(/\*(.+?)\*/g, '<em>$1</em>');
|
|
8269
|
+
|
|
8270
|
+
// Links and images
|
|
8271
|
+
text = text.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '<img alt="$1" src="$2" style="max-width:100%;border-radius:4px;">');
|
|
8272
|
+
text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener">$1</a>');
|
|
8273
|
+
|
|
8274
|
+
// Tables (pipe-delimited)
|
|
8275
|
+
text = text.replace(/((?:^\|.+\|$\n?)+)/gm, (block) => {
|
|
8276
|
+
const rows = block.trim().split('\n').filter(r => r.trim());
|
|
8277
|
+
if (rows.length < 2) return block;
|
|
8278
|
+
// Check if second row is separator
|
|
8279
|
+
const sep = rows[1];
|
|
8280
|
+
if (!/^\|[\s:-]+\|$/.test(sep.trim().replace(/\|/g, '|'))) return block;
|
|
8281
|
+
const headerCells = rows[0].split('|').filter(c => c.trim() !== '').map(c => c.trim());
|
|
8282
|
+
let html = '<table><thead><tr>' + headerCells.map(c => '<th>' + c + '</th>').join('') + '</tr></thead><tbody>';
|
|
8283
|
+
for (let i = 2; i < rows.length; i++) {
|
|
8284
|
+
const cells = rows[i].split('|').filter(c => c.trim() !== '').map(c => c.trim());
|
|
8285
|
+
html += '<tr>' + cells.map(c => '<td>' + c + '</td>').join('') + '</tr>';
|
|
8286
|
+
}
|
|
8287
|
+
html += '</tbody></table>';
|
|
8288
|
+
return html;
|
|
8289
|
+
});
|
|
8290
|
+
|
|
8291
|
+
// Blockquotes
|
|
8292
|
+
text = text.replace(/^> (.+)$/gm, '<blockquote>$1</blockquote>');
|
|
8293
|
+
// Merge adjacent blockquotes
|
|
8294
|
+
text = text.replace(/<\/blockquote>\n<blockquote>/g, '\n');
|
|
8295
|
+
|
|
8296
|
+
// Unordered lists (lines starting with - or *)
|
|
8297
|
+
text = text.replace(/((?:^[\s]*[-*] .+$\n?)+)/gm, (block) => {
|
|
8298
|
+
const items = block.trim().split('\n').map(line => {
|
|
8299
|
+
const content = line.replace(/^[\s]*[-*] /, '');
|
|
8300
|
+
return '<li>' + content + '</li>';
|
|
8301
|
+
});
|
|
8302
|
+
return '<ul>' + items.join('') + '</ul>';
|
|
8303
|
+
});
|
|
8304
|
+
|
|
8305
|
+
// Ordered lists (lines starting with number.)
|
|
8306
|
+
text = text.replace(/((?:^[\s]*\d+\. .+$\n?)+)/gm, (block) => {
|
|
8307
|
+
const items = block.trim().split('\n').map(line => {
|
|
8308
|
+
const content = line.replace(/^[\s]*\d+\. /, '');
|
|
8309
|
+
return '<li>' + content + '</li>';
|
|
8310
|
+
});
|
|
8311
|
+
return '<ol>' + items.join('') + '</ol>';
|
|
8312
|
+
});
|
|
8313
|
+
|
|
8314
|
+
// Paragraphs: wrap remaining text lines that aren't already wrapped in block elements
|
|
8315
|
+
const blockTags = ['<h1', '<h2', '<h3', '<h4', '<hr', '<pre', '<ul', '<ol', '<table', '<blockquote'];
|
|
8316
|
+
const lines = text.split('\n');
|
|
8317
|
+
const result = [];
|
|
8318
|
+
let paraLines = [];
|
|
8319
|
+
|
|
8320
|
+
function flushPara() {
|
|
8321
|
+
if (paraLines.length > 0) {
|
|
8322
|
+
const content = paraLines.join('\n').trim();
|
|
8323
|
+
if (content) result.push('<p>' + content + '</p>');
|
|
8324
|
+
paraLines = [];
|
|
8325
|
+
}
|
|
8326
|
+
}
|
|
8327
|
+
|
|
8328
|
+
for (const line of lines) {
|
|
8329
|
+
const trimmed = line.trim();
|
|
8330
|
+
if (trimmed === '') {
|
|
8331
|
+
flushPara();
|
|
8332
|
+
} else if (blockTags.some(tag => trimmed.startsWith(tag))) {
|
|
8333
|
+
flushPara();
|
|
8334
|
+
result.push(line);
|
|
8335
|
+
} else {
|
|
8336
|
+
paraLines.push(line);
|
|
8337
|
+
}
|
|
8338
|
+
}
|
|
8339
|
+
flushPara();
|
|
8340
|
+
|
|
8341
|
+
return result.join('\n');
|
|
8342
|
+
}
|
|
8343
|
+
|
|
7837
8344
|
function addChatMessage(role, content, sources) {
|
|
7838
8345
|
const container = document.getElementById('chatMessages');
|
|
7839
8346
|
const div = document.createElement('div');
|
|
@@ -7877,12 +8384,14 @@ async function sendChatMessage() {
|
|
|
7877
8384
|
const maxDocs = parseInt(document.getElementById('chatMaxDocs').value) || 5;
|
|
7878
8385
|
const rerank = document.getElementById('chatRerank').classList.contains('active');
|
|
7879
8386
|
const systemPrompt = document.getElementById('chatSystemPrompt').value.trim() || undefined;
|
|
8387
|
+
const mode = document.getElementById('chatMode')?.value || 'pipeline';
|
|
8388
|
+
const isAgent = mode === 'agent';
|
|
7880
8389
|
|
|
7881
8390
|
if (!provider) {
|
|
7882
8391
|
addChatMessage('system-msg', 'Please select an LLM provider in <a href="#" onclick="openChatSettings();return false;">Chat Settings</a>.');
|
|
7883
8392
|
return;
|
|
7884
8393
|
}
|
|
7885
|
-
if (!db || !collection) {
|
|
8394
|
+
if (!isAgent && (!db || !collection)) {
|
|
7886
8395
|
addChatMessage('system-msg', 'Please configure a database and collection in <a href="#" onclick="openChatSettings();return false;">Chat Settings</a>.');
|
|
7887
8396
|
return;
|
|
7888
8397
|
}
|
|
@@ -7895,7 +8404,7 @@ async function sendChatMessage() {
|
|
|
7895
8404
|
// Show typing indicator
|
|
7896
8405
|
const typing = document.createElement('div');
|
|
7897
8406
|
typing.className = 'chat-typing';
|
|
7898
|
-
typing.textContent = 'Thinking';
|
|
8407
|
+
typing.textContent = isAgent ? 'Agent working' : 'Thinking';
|
|
7899
8408
|
document.getElementById('chatMessages').appendChild(typing);
|
|
7900
8409
|
|
|
7901
8410
|
// Disable input
|
|
@@ -7907,7 +8416,7 @@ async function sendChatMessage() {
|
|
|
7907
8416
|
const res = await fetch('/api/chat/message', {
|
|
7908
8417
|
method: 'POST',
|
|
7909
8418
|
headers: { 'Content-Type': 'application/json' },
|
|
7910
|
-
body: JSON.stringify({ query, db, collection, provider, model, maxDocs, rerank, systemPrompt }),
|
|
8419
|
+
body: JSON.stringify({ query, db, collection, provider, model, maxDocs, rerank, systemPrompt, mode }),
|
|
7911
8420
|
});
|
|
7912
8421
|
|
|
7913
8422
|
if (!res.ok) {
|
|
@@ -7924,6 +8433,7 @@ async function sendChatMessage() {
|
|
|
7924
8433
|
let assistantDiv = null;
|
|
7925
8434
|
let fullText = '';
|
|
7926
8435
|
let sources = [];
|
|
8436
|
+
let toolCallsDiv = null;
|
|
7927
8437
|
|
|
7928
8438
|
while (true) {
|
|
7929
8439
|
const { done, value } = await reader.read();
|
|
@@ -7946,6 +8456,37 @@ async function sendChatMessage() {
|
|
|
7946
8456
|
typing.textContent = `Retrieved ${data.docs?.length || 0} docs (${data.timeMs}ms)`;
|
|
7947
8457
|
}
|
|
7948
8458
|
|
|
8459
|
+
if (currentEvent === 'tool_call') {
|
|
8460
|
+
if (!toolCallsDiv) {
|
|
8461
|
+
toolCallsDiv = document.createElement('div');
|
|
8462
|
+
toolCallsDiv.className = 'chat-tool-calls';
|
|
8463
|
+
document.getElementById('chatMessages').appendChild(toolCallsDiv);
|
|
8464
|
+
}
|
|
8465
|
+
const tc = document.createElement('div');
|
|
8466
|
+
tc.className = 'chat-tool-call' + (data.error ? ' error' : '');
|
|
8467
|
+
const icon = document.createElement('span');
|
|
8468
|
+
icon.className = 'tool-icon';
|
|
8469
|
+
icon.textContent = '\u2699';
|
|
8470
|
+
tc.appendChild(icon);
|
|
8471
|
+
const nameSpan = document.createElement('span');
|
|
8472
|
+
nameSpan.className = 'tool-name';
|
|
8473
|
+
nameSpan.textContent = data.name;
|
|
8474
|
+
tc.appendChild(nameSpan);
|
|
8475
|
+
const timeSpan = document.createElement('span');
|
|
8476
|
+
timeSpan.className = 'tool-time';
|
|
8477
|
+
timeSpan.textContent = (data.timeMs || 0) + 'ms';
|
|
8478
|
+
tc.appendChild(timeSpan);
|
|
8479
|
+
if (data.error) {
|
|
8480
|
+
const errSpan = document.createElement('span');
|
|
8481
|
+
errSpan.className = 'tool-error';
|
|
8482
|
+
errSpan.textContent = data.error;
|
|
8483
|
+
tc.appendChild(errSpan);
|
|
8484
|
+
}
|
|
8485
|
+
toolCallsDiv.appendChild(tc);
|
|
8486
|
+
typing.textContent = 'Calling ' + data.name + '...';
|
|
8487
|
+
document.getElementById('chatMessages').scrollTop = document.getElementById('chatMessages').scrollHeight;
|
|
8488
|
+
}
|
|
8489
|
+
|
|
7949
8490
|
if (currentEvent === 'chunk') {
|
|
7950
8491
|
if (!assistantDiv) {
|
|
7951
8492
|
typing.remove();
|
|
@@ -7957,6 +8498,12 @@ async function sendChatMessage() {
|
|
|
7957
8498
|
}
|
|
7958
8499
|
|
|
7959
8500
|
if (currentEvent === 'done') {
|
|
8501
|
+
// Render accumulated text as markdown for assistant messages
|
|
8502
|
+
if (assistantDiv && fullText) {
|
|
8503
|
+
const contentEl = assistantDiv.querySelector('.chat-message-content');
|
|
8504
|
+
contentEl.innerHTML = renderMarkdown(fullText);
|
|
8505
|
+
contentEl.classList.add('rendered');
|
|
8506
|
+
}
|
|
7960
8507
|
sources = data.sources || [];
|
|
7961
8508
|
if (sources.length > 0 && assistantDiv) {
|
|
7962
8509
|
const details = document.createElement('details');
|
|
@@ -8022,6 +8569,9 @@ window.switchSettingsSection = switchSettingsSection;
|
|
|
8022
8569
|
window.updateChatStatus = updateChatStatus;
|
|
8023
8570
|
window.chatInputKeydown = chatInputKeydown;
|
|
8024
8571
|
window.chatProviderChanged = chatProviderChanged;
|
|
8572
|
+
window.loadChatApiKey = loadChatApiKey;
|
|
8573
|
+
window.toggleChatApiKeyVisibility = toggleChatApiKeyVisibility;
|
|
8574
|
+
window.saveChatApiKey = saveChatApiKey;
|
|
8025
8575
|
|
|
8026
8576
|
// ── Start ──
|
|
8027
8577
|
init();
|
|
@@ -8141,13 +8691,13 @@ init();
|
|
|
8141
8691
|
.bug-error{padding:10px 12px;background:rgba(255,107,107,0.15);border:1px solid rgba(255,107,107,0.3);border-radius:8px;color:var(--error);font-size:13px;margin-bottom:16px}
|
|
8142
8692
|
.bug-actions{display:flex;gap:12px}
|
|
8143
8693
|
.bug-actions button{flex:1;padding:12px 16px;border-radius:8px;font-size:14px;font-weight:500;cursor:pointer;transition:all .2s;border:none}
|
|
8144
|
-
.bug-actions .primary{background:linear-gradient(135deg,var(--accent),#
|
|
8145
|
-
.bug-actions .primary:hover{box-shadow:0 4px 12px rgba(0,
|
|
8694
|
+
.bug-actions .primary{background:linear-gradient(135deg,var(--accent),#5CE8CC);color:#000}
|
|
8695
|
+
.bug-actions .primary:hover{box-shadow:0 4px 12px rgba(0,212,170,0.4)}
|
|
8146
8696
|
.bug-actions .primary:disabled{opacity:0.6;cursor:not-allowed}
|
|
8147
8697
|
.bug-actions .secondary{background:rgba(255,255,255,0.1);color:var(--text);border:1px solid var(--border)}
|
|
8148
8698
|
.bug-actions .secondary:hover{background:rgba(255,255,255,0.15)}
|
|
8149
8699
|
.bug-success{padding:40px 20px;text-align:center}
|
|
8150
|
-
.bug-success .icon{width:60px;height:60px;border-radius:50%;background:linear-gradient(135deg,var(--accent),#
|
|
8700
|
+
.bug-success .icon{width:60px;height:60px;border-radius:50%;background:linear-gradient(135deg,var(--accent),#5CE8CC);color:#000;font-size:32px;display:flex;align-items:center;justify-content:center;margin:0 auto 20px}
|
|
8151
8701
|
.bug-success h3{margin:0 0 12px;color:var(--accent-text);font-size:20px}
|
|
8152
8702
|
.bug-success p{margin:8px 0;color:var(--text-muted)}
|
|
8153
8703
|
.bug-success code{background:rgba(255,255,255,0.1);padding:4px 8px;border-radius:4px;font-size:12px;color:var(--accent)}
|
|
@@ -8203,7 +8753,7 @@ init();
|
|
|
8203
8753
|
|
|
8204
8754
|
<script>
|
|
8205
8755
|
(function() {
|
|
8206
|
-
const BUG_API = 'https://
|
|
8756
|
+
const BUG_API = 'https://vaicli.com/api/bugs';
|
|
8207
8757
|
const GITHUB_URL = 'https://github.com/mrlynn/voyageai-cli/issues/new';
|
|
8208
8758
|
|
|
8209
8759
|
function getEnv() {
|