voyageai-cli 1.22.0 → 1.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2058,6 +2058,330 @@ select:focus { outline: none; border-color: var(--accent); }
2058
2058
  .settings-api-key-missing { color: var(--warning); }
2059
2059
  .settings-row .settings-select, .settings-row .settings-input { text-align: right; }
2060
2060
 
2061
+ /* ── Settings two-column layout (Claude Desktop style) ── */
2062
+ .settings-layout {
2063
+ display: flex;
2064
+ height: 100%;
2065
+ max-width: 960px;
2066
+ margin: 0 auto;
2067
+ gap: 0;
2068
+ }
2069
+ .settings-nav {
2070
+ width: 200px;
2071
+ min-width: 200px;
2072
+ flex-shrink: 0;
2073
+ padding: 24px 0;
2074
+ border-right: 1px solid var(--border);
2075
+ display: flex;
2076
+ flex-direction: column;
2077
+ gap: 2px;
2078
+ position: sticky;
2079
+ top: 0;
2080
+ align-self: flex-start;
2081
+ }
2082
+ .settings-nav-header {
2083
+ font-size: 18px;
2084
+ font-weight: 700;
2085
+ color: var(--text);
2086
+ padding: 0 16px 20px;
2087
+ letter-spacing: -0.3px;
2088
+ }
2089
+ .settings-nav-item {
2090
+ display: flex;
2091
+ align-items: center;
2092
+ gap: 10px;
2093
+ width: 100%;
2094
+ padding: 10px 16px;
2095
+ background: none;
2096
+ border: none;
2097
+ border-left: 3px solid transparent;
2098
+ border-radius: 0 6px 6px 0;
2099
+ color: var(--text-dim);
2100
+ font-size: 13px;
2101
+ font-weight: 500;
2102
+ font-family: var(--font);
2103
+ cursor: pointer;
2104
+ transition: all 0.15s;
2105
+ text-align: left;
2106
+ }
2107
+ .settings-nav-item:hover {
2108
+ background: rgba(255,255,255,0.04);
2109
+ color: var(--text);
2110
+ }
2111
+ [data-theme="light"] .settings-nav-item:hover {
2112
+ background: rgba(0,30,43,0.04);
2113
+ }
2114
+ .settings-nav-item.active {
2115
+ color: var(--accent);
2116
+ border-left-color: var(--accent);
2117
+ background: var(--accent-glow);
2118
+ }
2119
+ .settings-nav-item svg {
2120
+ width: 16px;
2121
+ height: 16px;
2122
+ flex-shrink: 0;
2123
+ color: inherit;
2124
+ }
2125
+ .settings-content {
2126
+ flex: 1;
2127
+ padding: 24px 32px;
2128
+ overflow-y: auto;
2129
+ max-width: 640px;
2130
+ }
2131
+ .settings-panel {
2132
+ display: none;
2133
+ }
2134
+ .settings-panel.active {
2135
+ display: block;
2136
+ }
2137
+ .settings-panel-header {
2138
+ margin-bottom: 24px;
2139
+ padding-bottom: 16px;
2140
+ border-bottom: 1px solid var(--border);
2141
+ }
2142
+ .settings-panel-title {
2143
+ font-size: 22px;
2144
+ font-weight: 700;
2145
+ color: var(--text);
2146
+ margin: 0 0 4px;
2147
+ letter-spacing: -0.3px;
2148
+ }
2149
+ .settings-panel-subtitle {
2150
+ font-size: 14px;
2151
+ color: var(--text-dim);
2152
+ margin: 0;
2153
+ font-weight: 500;
2154
+ }
2155
+ /* Settings origin/provenance badge */
2156
+ .settings-origin {
2157
+ display: inline-flex;
2158
+ align-items: center;
2159
+ font-size: 10px;
2160
+ font-weight: 600;
2161
+ text-transform: uppercase;
2162
+ letter-spacing: 0.5px;
2163
+ padding: 2px 7px;
2164
+ border-radius: 4px;
2165
+ margin-left: 6px;
2166
+ white-space: nowrap;
2167
+ vertical-align: middle;
2168
+ line-height: 1;
2169
+ }
2170
+ .settings-origin:empty { display: none; }
2171
+ .settings-origin.origin-env {
2172
+ background: rgba(4,152,236,0.12);
2173
+ color: #0497EC;
2174
+ }
2175
+ [data-theme="light"] .settings-origin.origin-env {
2176
+ background: rgba(4,120,190,0.10);
2177
+ color: #016BF8;
2178
+ }
2179
+ .settings-origin.origin-config {
2180
+ background: rgba(180,90,242,0.12);
2181
+ color: #B45AF2;
2182
+ }
2183
+ [data-theme="light"] .settings-origin.origin-config {
2184
+ background: rgba(140,60,200,0.10);
2185
+ color: #7B3DB5;
2186
+ }
2187
+ .settings-origin.origin-project {
2188
+ background: rgba(0,237,100,0.12);
2189
+ color: var(--accent);
2190
+ }
2191
+ [data-theme="light"] .settings-origin.origin-project {
2192
+ background: rgba(0,163,92,0.10);
2193
+ color: #00A35C;
2194
+ }
2195
+ .settings-origin.origin-default {
2196
+ background: rgba(136,147,151,0.12);
2197
+ color: var(--text-muted);
2198
+ }
2199
+ /* Chat not-configured state */
2200
+ .chat-not-configured {
2201
+ display: flex;
2202
+ flex-direction: column;
2203
+ align-items: center;
2204
+ justify-content: center;
2205
+ gap: 12px;
2206
+ padding: 48px 24px;
2207
+ text-align: center;
2208
+ color: var(--text-dim);
2209
+ font-size: 14px;
2210
+ }
2211
+ .chat-not-configured-icon {
2212
+ font-size: 40px;
2213
+ opacity: 0.5;
2214
+ }
2215
+ .chat-not-configured .btn {
2216
+ margin-top: 8px;
2217
+ background: var(--accent);
2218
+ color: var(--bg);
2219
+ border: none;
2220
+ padding: 8px 20px;
2221
+ border-radius: var(--radius);
2222
+ font-weight: 600;
2223
+ font-size: 13px;
2224
+ cursor: pointer;
2225
+ font-family: var(--font);
2226
+ }
2227
+ .chat-not-configured .btn:hover {
2228
+ filter: brightness(1.1);
2229
+ }
2230
+ /* Responsive: collapse settings nav on small screens */
2231
+ @media (max-width: 768px) {
2232
+ .settings-layout {
2233
+ flex-direction: column;
2234
+ }
2235
+ .settings-nav {
2236
+ width: 100%;
2237
+ min-width: 100%;
2238
+ flex-direction: row;
2239
+ overflow-x: auto;
2240
+ border-right: none;
2241
+ border-bottom: 1px solid var(--border);
2242
+ padding: 12px 8px;
2243
+ gap: 4px;
2244
+ position: static;
2245
+ }
2246
+ .settings-nav-header { display: none; }
2247
+ .settings-nav-item {
2248
+ border-left: none;
2249
+ border-bottom: 2px solid transparent;
2250
+ border-radius: 6px;
2251
+ padding: 8px 12px;
2252
+ white-space: nowrap;
2253
+ font-size: 12px;
2254
+ }
2255
+ .settings-nav-item.active {
2256
+ border-left-color: transparent;
2257
+ border-bottom-color: var(--accent);
2258
+ }
2259
+ .settings-content { padding: 16px; }
2260
+ }
2261
+
2262
+ /* ── Chat Tab ── */
2263
+ .chat-container {
2264
+ display: flex;
2265
+ flex-direction: column;
2266
+ height: calc(100vh - 140px);
2267
+ max-width: 900px;
2268
+ margin: 0 auto;
2269
+ }
2270
+ .chat-messages {
2271
+ flex: 1;
2272
+ overflow-y: auto;
2273
+ padding: 16px;
2274
+ display: flex;
2275
+ flex-direction: column;
2276
+ gap: 16px;
2277
+ }
2278
+ .chat-message {
2279
+ max-width: 85%;
2280
+ padding: 12px 16px;
2281
+ border-radius: 12px;
2282
+ line-height: 1.6;
2283
+ font-size: 14px;
2284
+ white-space: pre-wrap;
2285
+ word-break: break-word;
2286
+ }
2287
+ .chat-message.user {
2288
+ align-self: flex-end;
2289
+ background: var(--accent);
2290
+ color: #fff;
2291
+ border-bottom-right-radius: 4px;
2292
+ }
2293
+ .chat-message.assistant {
2294
+ align-self: flex-start;
2295
+ background: var(--bg-card);
2296
+ border: 1px solid var(--border);
2297
+ border-bottom-left-radius: 4px;
2298
+ }
2299
+ .chat-message.system-msg {
2300
+ align-self: center;
2301
+ background: none;
2302
+ color: var(--text-muted);
2303
+ font-size: 12px;
2304
+ padding: 4px 8px;
2305
+ }
2306
+ .chat-sources {
2307
+ margin-top: 8px;
2308
+ padding-top: 8px;
2309
+ border-top: 1px solid var(--border);
2310
+ font-size: 12px;
2311
+ color: var(--text-muted);
2312
+ }
2313
+ .chat-sources summary { cursor: pointer; }
2314
+ .chat-sources ul { margin: 4px 0 0 16px; padding: 0; }
2315
+ .chat-sources li { margin: 2px 0; }
2316
+ .chat-input-area {
2317
+ padding: 12px 16px;
2318
+ border-top: 1px solid var(--border);
2319
+ display: flex;
2320
+ gap: 8px;
2321
+ align-items: flex-end;
2322
+ }
2323
+ .chat-input {
2324
+ flex: 1;
2325
+ padding: 10px 14px;
2326
+ border: 1px solid var(--border);
2327
+ border-radius: 8px;
2328
+ background: var(--bg-input);
2329
+ color: var(--text);
2330
+ font-size: 14px;
2331
+ font-family: inherit;
2332
+ resize: none;
2333
+ min-height: 40px;
2334
+ max-height: 120px;
2335
+ outline: none;
2336
+ }
2337
+ .chat-input:focus { border-color: var(--accent); }
2338
+ .chat-send-btn {
2339
+ padding: 10px 20px;
2340
+ background: var(--accent);
2341
+ color: #fff;
2342
+ border: none;
2343
+ border-radius: 8px;
2344
+ cursor: pointer;
2345
+ font-size: 14px;
2346
+ font-weight: 600;
2347
+ }
2348
+ .chat-send-btn:disabled { opacity: 0.5; cursor: not-allowed; }
2349
+ .chat-header {
2350
+ display: flex;
2351
+ align-items: center;
2352
+ justify-content: space-between;
2353
+ padding: 8px 16px;
2354
+ border-bottom: 1px solid var(--border);
2355
+ font-size: 13px;
2356
+ color: var(--text-muted);
2357
+ }
2358
+ .chat-header-status { display: flex; gap: 8px; align-items: center; }
2359
+ .chat-header-status span { opacity: 0.7; }
2360
+ .chat-config-toggle {
2361
+ background: none; border: none; color: var(--text-muted);
2362
+ cursor: pointer; padding: 4px 8px; border-radius: 4px; font-size: 14px;
2363
+ display: flex; align-items: center; gap: 4px;
2364
+ }
2365
+ .chat-config-toggle:hover { background: rgba(255,255,255,0.06); color: var(--text); }
2366
+ [data-theme="light"] .chat-config-toggle:hover { background: rgba(0,0,0,0.04); }
2367
+ .chat-config-toggle.active { color: var(--accent); }
2368
+ /* chat-config-panel/bar removed — chat settings moved to Settings page */
2369
+ .chat-typing {
2370
+ align-self: flex-start;
2371
+ color: var(--text-muted);
2372
+ font-size: 13px;
2373
+ padding: 8px 16px;
2374
+ }
2375
+ .chat-typing::after {
2376
+ content: '';
2377
+ animation: chatDots 1.4s infinite;
2378
+ }
2379
+ @keyframes chatDots {
2380
+ 0%, 20% { content: '.'; }
2381
+ 40% { content: '..'; }
2382
+ 60%, 100% { content: '...'; }
2383
+ }
2384
+
2061
2385
  /* ── Multimodal Tab ── */
2062
2386
  .mm-grid {
2063
2387
  display: grid;
@@ -2296,7 +2620,7 @@ select:focus { outline: none; border-color: var(--accent); }
2296
2620
  .mm-gallery-grid { grid-template-columns: repeat(3, 1fr); }
2297
2621
  .compare-grid, .search-results { grid-template-columns: 1fr; }
2298
2622
  .sidebar { width: 56px; min-width: 56px; }
2299
- .sidebar-title, .status-label { display: none; }
2623
+ .sidebar-title, .status-label, .sidebar-bug-label { display: none; }
2300
2624
  .tab-btn { justify-content: center; padding: 10px; }
2301
2625
  .tab-btn span:not(.tab-btn-icon) { display: none; }
2302
2626
  .main { padding: 16px; }
@@ -2342,6 +2666,18 @@ select:focus { outline: none; border-color: var(--accent); }
2342
2666
  <symbol id="lg-code" viewBox="0 0 16 16">
2343
2667
  <path fill="currentColor" d="M5.854 4.146a.5.5 0 0 1 0 .708L2.707 8l3.147 3.146a.5.5 0 0 1-.708.708l-3.5-3.5a.5.5 0 0 1 0-.708l3.5-3.5a.5.5 0 0 1 .708 0zm4.292 0a.5.5 0 0 0 0 .708L13.293 8l-3.147 3.146a.5.5 0 0 0 .708.708l3.5-3.5a.5.5 0 0 0 0-.708l-3.5-3.5a.5.5 0 0 0-.708 0z"/>
2344
2668
  </symbol>
2669
+ <symbol id="lg-palette" viewBox="0 0 16 16">
2670
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M8 1C4.13401 1 1 4.13401 1 8C1 11.866 4.13401 15 8 15C8.55228 15 9 14.5523 9 14V12.5C9 11.6716 9.67157 11 10.5 11H12C13.6569 11 15 9.65685 15 8C15 4.13401 11.866 1 8 1ZM4.5 9C5.32843 9 6 8.32843 6 7.5C6 6.67157 5.32843 6 4.5 6C3.67157 6 3 6.67157 3 7.5C3 8.32843 3.67157 9 4.5 9ZM7 5.5C7 6.32843 6.32843 7 5.5 7C4.67157 7 4 6.32843 4 5.5C4 4.67157 4.67157 4 5.5 4C6.32843 4 7 4.67157 7 5.5ZM9.5 6C10.3284 6 11 5.32843 11 4.5C11 3.67157 10.3284 3 9.5 3C8.67157 3 8 3.67157 8 4.5C8 5.32843 8.67157 6 9.5 6ZM13 7.5C13 8.32843 12.3284 9 11.5 9C10.6716 9 10 8.32843 10 7.5C10 6.67157 10.6716 6 11.5 6C12.3284 6 13 6.67157 13 7.5Z" fill="currentColor"/>
2671
+ </symbol>
2672
+ <symbol id="lg-cube" viewBox="0 0 16 16">
2673
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M8.35 1.18a.75.75 0 0 0-.7 0l-6 3.25A.75.75 0 0 0 1.25 5v6a.75.75 0 0 0 .4.66l6 3.25a.75.75 0 0 0 .7 0l6-3.25a.75.75 0 0 0 .4-.66V5a.75.75 0 0 0-.4-.66l-6-3.16zM8 3.2 3.47 5.65 8 7.82l4.53-2.17L8 3.2zM2.75 6.8v3.82L7.25 12.8V8.97L2.75 6.8zm5.5 6 4.5-2.18V6.8L8.25 8.97V12.8z" fill="currentColor"/>
2674
+ </symbol>
2675
+ <symbol id="lg-chat" viewBox="0 0 16 16">
2676
+ <path d="M2 2h12v9H5l-3 3V2z" fill="none" stroke="currentColor" stroke-width="1.5"/>
2677
+ </symbol>
2678
+ <symbol id="lg-shield" viewBox="0 0 16 16">
2679
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M8.35 1.18a.75.75 0 0 0-.7 0l-5 2.7A.75.75 0 0 0 2.25 4.5V8c0 2.9 2.1 5.5 5.5 6.95a.75.75 0 0 0 .5 0C11.65 13.5 13.75 10.9 13.75 8V4.5a.75.75 0 0 0-.4-.62l-5-2.7zM8 3.2 3.75 5.5V8c0 2.2 1.6 4.2 4.25 5.45C10.65 12.2 12.25 10.2 12.25 8V5.5L8 3.2z" fill="currentColor"/>
2680
+ </symbol>
2345
2681
  </svg>
2346
2682
 
2347
2683
  <div class="app-shell">
@@ -2361,6 +2697,7 @@ select:focus { outline: none; border-color: var(--accent); }
2361
2697
  <button class="tab-btn" data-tab="search" role="tab" aria-selected="false" aria-controls="tab-search" id="tab-btn-search"><span class="tab-btn-icon" aria-hidden="true"><svg><use href="#lg-search"/></svg></span><span>Search</span></button>
2362
2698
  <button class="tab-btn" data-tab="multimodal" role="tab" aria-selected="false" aria-controls="tab-multimodal" id="tab-btn-multimodal"><span class="tab-btn-icon" aria-hidden="true"><svg><use href="#lg-image"/></svg></span><span>Multimodal</span></button>
2363
2699
  <button class="tab-btn" data-tab="generate" role="tab" aria-selected="false" aria-controls="tab-generate" id="tab-btn-generate"><span class="tab-btn-icon" aria-hidden="true"><svg><use href="#lg-code"/></svg></span><span>Generate</span></button>
2700
+ <button class="tab-btn" data-tab="chat" role="tab" aria-selected="false" aria-controls="tab-chat" id="tab-btn-chat"><span class="tab-btn-icon" aria-hidden="true"><svg width="16" height="16" viewBox="0 0 16 16"><path d="M2 2h12v9H5l-3 3V2z" fill="none" stroke="currentColor" stroke-width="1.5"/></svg></span><span>Chat</span></button>
2364
2701
  </div>
2365
2702
  <div class="sidebar-nav-divider"></div>
2366
2703
  <div class="sidebar-nav-group" role="tablist" aria-label="Learn">
@@ -2378,7 +2715,13 @@ select:focus { outline: none; border-color: var(--accent); }
2378
2715
  </div>
2379
2716
  <button class="theme-toggle" id="themeToggle" title="Toggle light/dark mode">🌙</button>
2380
2717
  </div>
2381
- <div id="appVersionLabel" style="font-size:10px;color:var(--text-muted);text-align:center;"></div>
2718
+ <div style="display:flex;align-items:center;justify-content:space-between;">
2719
+ <div id="appVersionLabel" style="font-size:10px;color:var(--text-muted);"></div>
2720
+ <button class="sidebar-bug-link" id="bugButton" title="Report a Bug">
2721
+ <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="8" cy="9" r="4.5"/><path d="M5.5 5.5L3 3M10.5 5.5L13 3M3 9H1M15 9h-2M5.5 12.5L4 15M10.5 12.5L12 15"/></svg>
2722
+ <span class="sidebar-bug-label">Bug</span>
2723
+ </button>
2724
+ </div>
2382
2725
  </div>
2383
2726
  </aside>
2384
2727
 
@@ -3169,6 +3512,35 @@ Reranking models rescore initial search results to improve relevance ordering.</
3169
3512
 
3170
3513
  </div>
3171
3514
 
3515
+ <!-- ========== CHAT TAB ========== -->
3516
+ <div class="tab-panel" id="tab-chat" role="tabpanel" aria-labelledby="tab-btn-chat" tabindex="0">
3517
+ <div class="chat-container">
3518
+ <div class="chat-header">
3519
+ <div class="chat-header-status" id="chatHeaderStatus">
3520
+ <span id="chatStatusProvider">No provider</span>
3521
+ <span>·</span>
3522
+ <span id="chatStatusDb">No database</span>
3523
+ </div>
3524
+ <button class="chat-config-toggle" id="chatOpenSettings" onclick="openChatSettings()" title="Configure in Settings">
3525
+ <svg width="14" height="14" viewBox="0 0 16 16"><use href="#lg-config"/></svg>
3526
+ Configure
3527
+ </button>
3528
+ </div>
3529
+ <div class="chat-not-configured" id="chatNotConfigured" style="display:none;">
3530
+ <div class="chat-not-configured-icon">💬</div>
3531
+ <p>Chat requires an LLM provider and database to be configured.</p>
3532
+ <button class="btn" onclick="openChatSettings()">Open Chat Settings</button>
3533
+ </div>
3534
+ <div class="chat-messages" id="chatMessages">
3535
+ <div class="chat-message system-msg">Chat with your knowledge base. Click <strong>Configure</strong> above to adjust your LLM provider and database.</div>
3536
+ </div>
3537
+ <div class="chat-input-area">
3538
+ <textarea class="chat-input" id="chatInput" placeholder="Ask a question about your documents..." rows="1" onkeydown="chatInputKeydown(event)"></textarea>
3539
+ <button class="chat-send-btn" id="chatSendBtn" onclick="sendChatMessage()">Send</button>
3540
+ </div>
3541
+ </div>
3542
+ </div>
3543
+
3172
3544
  <!-- ========== ABOUT TAB ========== -->
3173
3545
  <div class="tab-panel" id="tab-about" role="tabpanel" aria-labelledby="tab-btn-about" tabindex="0">
3174
3546
  <div class="about-container">
@@ -3467,213 +3839,354 @@ Reranking models rescore initial search results to improve relevance ordering.</
3467
3839
 
3468
3840
  <!-- ========== SETTINGS TAB ========== -->
3469
3841
  <div class="tab-panel" id="tab-settings">
3470
- <div class="settings-container">
3471
- <div class="page-header">
3472
- <h2 class="page-header-title">Settings</h2>
3473
- <p class="page-header-subtitle">Configure appearance, models, and API access</p>
3474
- </div>
3842
+ <div class="settings-layout">
3843
+ <!-- Settings left nav -->
3844
+ <nav class="settings-nav">
3845
+ <div class="settings-nav-header">Settings</div>
3846
+ <button class="settings-nav-item active" data-settings-section="general">
3847
+ <svg width="16" height="16" viewBox="0 0 16 16"><use href="#lg-config"/></svg>
3848
+ <span>General</span>
3849
+ </button>
3850
+ <button class="settings-nav-item" data-settings-section="appearance">
3851
+ <svg width="16" height="16" viewBox="0 0 16 16"><use href="#lg-palette"/></svg>
3852
+ <span>Appearance</span>
3853
+ </button>
3854
+ <button class="settings-nav-item" data-settings-section="models">
3855
+ <svg width="16" height="16" viewBox="0 0 16 16"><use href="#lg-cube"/></svg>
3856
+ <span>Models</span>
3857
+ </button>
3858
+ <button class="settings-nav-item" data-settings-section="chat">
3859
+ <svg width="16" height="16" viewBox="0 0 16 16"><use href="#lg-chat"/></svg>
3860
+ <span>Chat</span>
3861
+ </button>
3862
+ <button class="settings-nav-item" data-settings-section="benchmark">
3863
+ <svg width="16" height="16" viewBox="0 0 16 16"><use href="#lg-gauge"/></svg>
3864
+ <span>Benchmark</span>
3865
+ </button>
3866
+ <button class="settings-nav-item" data-settings-section="privacy">
3867
+ <svg width="16" height="16" viewBox="0 0 16 16"><use href="#lg-shield"/></svg>
3868
+ <span>Data &amp; Privacy</span>
3869
+ </button>
3870
+ </nav>
3475
3871
 
3476
- <div class="settings-section">
3477
- <div class="settings-section-title">Appearance</div>
3478
- <div class="settings-row">
3479
- <div class="settings-label">
3480
- <span class="settings-label-text">Theme</span>
3481
- <span class="settings-label-hint">Controls the color scheme of the interface</span>
3482
- </div>
3483
- <div class="settings-control">
3484
- <select class="settings-select" id="settingsTheme">
3485
- <option value="dark">Dark</option>
3486
- <option value="light">Light</option>
3487
- </select>
3488
- </div>
3489
- </div>
3490
- <div class="settings-row">
3491
- <div class="settings-label">
3492
- <span class="settings-label-text">Vector Heatmap Colors</span>
3493
- <span class="settings-label-hint">Color palette for embedding visualizations</span>
3494
- </div>
3495
- <div class="settings-control" style="display:flex;align-items:center;gap:8px;">
3496
- <select class="settings-select" id="settingsHeatmap">
3497
- <option value="viridis">Viridis</option>
3498
- <option value="plasma">Plasma</option>
3499
- <option value="inferno">Inferno</option>
3500
- <option value="magma">Magma</option>
3501
- <option value="cividis">Cividis</option>
3502
- <option value="turbo">Turbo</option>
3503
- </select>
3504
- <span class="heatmap-preview" id="heatmapPreview"></span>
3505
- </div>
3506
- </div>
3507
- </div>
3872
+ <!-- Settings content panels -->
3873
+ <div class="settings-content">
3508
3874
 
3509
- <div class="settings-section">
3510
- <div class="settings-section-title">Models</div>
3511
- <div class="settings-row">
3512
- <div class="settings-label">
3513
- <span class="settings-label-text">Default Embedding Model</span>
3514
- <span class="settings-label-hint">Pre-selected model for Embed and Compare tabs</span>
3515
- </div>
3516
- <div class="settings-control">
3517
- <select class="settings-select" id="settingsDefaultModel"></select>
3518
- </div>
3519
- </div>
3520
- <div class="settings-row">
3521
- <div class="settings-label">
3522
- <span class="settings-label-text">Default Input Type</span>
3523
- <span class="settings-label-hint">Pre-selected input type for embedding requests</span>
3875
+ <!-- ── General ── -->
3876
+ <div class="settings-panel active" id="settings-general">
3877
+ <div class="settings-panel-header">
3878
+ <h3 class="settings-panel-title">General</h3>
3879
+ <p class="settings-panel-subtitle">API access and connection settings</p>
3524
3880
  </div>
3525
- <div class="settings-control">
3526
- <select class="settings-select" id="settingsInputType">
3527
- <option value="document">document</option>
3528
- <option value="query">query</option>
3529
- </select>
3881
+ <div class="settings-section">
3882
+ <div class="settings-api-banner" id="settingsApiBanner">
3883
+ <span class="settings-api-banner-icon" id="settingsApiBannerIcon">⚠️</span>
3884
+ <span id="settingsApiKeyMsg">No API key configured — add your key below to get started.</span>
3885
+ </div>
3886
+ <div class="settings-row">
3887
+ <div class="settings-label">
3888
+ <span class="settings-label-text">API Key <span class="settings-origin" data-origin-key="apiKey"></span></span>
3889
+ <span class="settings-label-hint">Encrypted via OS keychain · <a href="https://dash.voyageai.com" target="_blank" class="settings-key-link">🔑 Get a key</a></span>
3890
+ </div>
3891
+ <div class="settings-control" style="min-width:260px;">
3892
+ <div class="settings-api-field">
3893
+ <input type="password" id="settingsApiKey" placeholder="pa-..." autocomplete="off" spellcheck="false">
3894
+ <button type="button" id="settingsApiKeyToggle" title="Show/hide key">👁</button>
3895
+ <button type="button" id="settingsApiKeySave" class="save-btn" title="Save key">Save</button>
3896
+ </div>
3897
+ </div>
3898
+ </div>
3899
+ <div class="settings-row">
3900
+ <div class="settings-label">
3901
+ <span class="settings-label-text">API Base URL <span class="settings-origin" data-origin-key="apiBase"></span></span>
3902
+ <span class="settings-label-hint">Override the default endpoint (leave empty for default)</span>
3903
+ </div>
3904
+ <div class="settings-control">
3905
+ <input type="text" class="settings-input" id="settingsApiBase" placeholder="https://api.voyageai.com/v1">
3906
+ </div>
3907
+ </div>
3908
+ <div class="settings-row">
3909
+ <div class="settings-label">
3910
+ <span class="settings-label-text">Request Timeout</span>
3911
+ <span class="settings-label-hint">Max seconds to wait for API responses</span>
3912
+ </div>
3913
+ <div class="settings-control">
3914
+ <select class="settings-select" id="settingsTimeout">
3915
+ <option value="15">15 seconds</option>
3916
+ <option value="30" selected>30 seconds</option>
3917
+ <option value="60">60 seconds</option>
3918
+ <option value="120">120 seconds</option>
3919
+ </select>
3920
+ </div>
3921
+ </div>
3530
3922
  </div>
3531
3923
  </div>
3532
- </div>
3533
3924
 
3534
- <div class="settings-section">
3535
- <div class="settings-section-title">API</div>
3536
- <div class="settings-api-banner" id="settingsApiBanner">
3537
- <span class="settings-api-banner-icon" id="settingsApiBannerIcon">⚠️</span>
3538
- <span id="settingsApiKeyMsg">No API key configured — add your key below to get started.</span>
3539
- </div>
3540
- <div class="settings-row">
3541
- <div class="settings-label">
3542
- <span class="settings-label-text">API Key</span>
3543
- <span class="settings-label-hint">Encrypted via OS keychain · <a href="https://dash.voyageai.com" target="_blank" class="settings-key-link">🔑 Get a key</a></span>
3925
+ <!-- ── Appearance ── -->
3926
+ <div class="settings-panel" id="settings-appearance">
3927
+ <div class="settings-panel-header">
3928
+ <h3 class="settings-panel-title">Appearance</h3>
3929
+ <p class="settings-panel-subtitle">Theme and visualization options</p>
3544
3930
  </div>
3545
- <div class="settings-control" style="min-width:260px;">
3546
- <div class="settings-api-field">
3547
- <input type="password" id="settingsApiKey" placeholder="pa-..." autocomplete="off" spellcheck="false">
3548
- <button type="button" id="settingsApiKeyToggle" title="Show/hide key">👁</button>
3549
- <button type="button" id="settingsApiKeySave" class="save-btn" title="Save key">Save</button>
3931
+ <div class="settings-section">
3932
+ <div class="settings-row">
3933
+ <div class="settings-label">
3934
+ <span class="settings-label-text">Theme</span>
3935
+ <span class="settings-label-hint">Controls the color scheme of the interface</span>
3936
+ </div>
3937
+ <div class="settings-control">
3938
+ <select class="settings-select" id="settingsTheme">
3939
+ <option value="dark">Dark</option>
3940
+ <option value="light">Light</option>
3941
+ </select>
3942
+ </div>
3943
+ </div>
3944
+ <div class="settings-row">
3945
+ <div class="settings-label">
3946
+ <span class="settings-label-text">Vector Heatmap Colors</span>
3947
+ <span class="settings-label-hint">Color palette for embedding visualizations</span>
3948
+ </div>
3949
+ <div class="settings-control" style="display:flex;align-items:center;gap:8px;">
3950
+ <select class="settings-select" id="settingsHeatmap">
3951
+ <option value="viridis">Viridis</option>
3952
+ <option value="plasma">Plasma</option>
3953
+ <option value="inferno">Inferno</option>
3954
+ <option value="magma">Magma</option>
3955
+ <option value="cividis">Cividis</option>
3956
+ <option value="turbo">Turbo</option>
3957
+ </select>
3958
+ <span class="heatmap-preview" id="heatmapPreview"></span>
3959
+ </div>
3550
3960
  </div>
3551
3961
  </div>
3552
3962
  </div>
3553
- <div class="settings-row">
3554
- <div class="settings-label">
3555
- <span class="settings-label-text">API Base URL</span>
3556
- <span class="settings-label-hint">Override the default endpoint (leave empty for default)</span>
3557
- </div>
3558
- <div class="settings-control">
3559
- <input type="text" class="settings-input" id="settingsApiBase" placeholder="https://api.voyageai.com/v1">
3560
- </div>
3561
- </div>
3562
- <div class="settings-row">
3563
- <div class="settings-label">
3564
- <span class="settings-label-text">Request Timeout</span>
3565
- <span class="settings-label-hint">Max seconds to wait for API responses</span>
3963
+
3964
+ <!-- ── Models ── -->
3965
+ <div class="settings-panel" id="settings-models">
3966
+ <div class="settings-panel-header">
3967
+ <h3 class="settings-panel-title">Models</h3>
3968
+ <p class="settings-panel-subtitle">Default embedding model preferences</p>
3566
3969
  </div>
3567
- <div class="settings-control">
3568
- <select class="settings-select" id="settingsTimeout">
3569
- <option value="15">15 seconds</option>
3570
- <option value="30" selected>30 seconds</option>
3571
- <option value="60">60 seconds</option>
3572
- <option value="120">120 seconds</option>
3573
- </select>
3970
+ <div class="settings-section">
3971
+ <div class="settings-row">
3972
+ <div class="settings-label">
3973
+ <span class="settings-label-text">Default Embedding Model</span>
3974
+ <span class="settings-label-hint">Pre-selected model for Embed and Compare tabs</span>
3975
+ </div>
3976
+ <div class="settings-control">
3977
+ <select class="settings-select" id="settingsDefaultModel"></select>
3978
+ </div>
3979
+ </div>
3980
+ <div class="settings-row">
3981
+ <div class="settings-label">
3982
+ <span class="settings-label-text">Default Input Type</span>
3983
+ <span class="settings-label-hint">Pre-selected input type for embedding requests</span>
3984
+ </div>
3985
+ <div class="settings-control">
3986
+ <select class="settings-select" id="settingsInputType">
3987
+ <option value="document">document</option>
3988
+ <option value="query">query</option>
3989
+ </select>
3990
+ </div>
3991
+ </div>
3574
3992
  </div>
3575
3993
  </div>
3576
- </div>
3577
3994
 
3578
- <div class="settings-section">
3579
- <div class="settings-section-title">Benchmark Defaults</div>
3580
- <div class="settings-row">
3581
- <div class="settings-label">
3582
- <span class="settings-label-text">Iterations per Model</span>
3583
- <span class="settings-label-hint">Number of runs when benchmarking latency</span>
3995
+ <!-- ── Chat ── -->
3996
+ <div class="settings-panel" id="settings-chat">
3997
+ <div class="settings-panel-header">
3998
+ <h3 class="settings-panel-title">Chat</h3>
3999
+ <p class="settings-panel-subtitle">LLM provider and knowledge base configuration</p>
3584
4000
  </div>
3585
- <div class="settings-control">
3586
- <select class="settings-select" id="settingsBenchIter">
3587
- <option value="3">3 runs</option>
3588
- <option value="5" selected>5 runs</option>
3589
- <option value="10">10 runs</option>
3590
- <option value="20">20 runs</option>
3591
- </select>
4001
+ <div class="settings-section">
4002
+ <div class="settings-section-title">LLM Provider</div>
4003
+ <div class="settings-row">
4004
+ <div class="settings-label">
4005
+ <span class="settings-label-text">Provider <span class="settings-origin" data-origin-key="provider"></span></span>
4006
+ <span class="settings-label-hint">Which LLM to use for chat responses</span>
4007
+ </div>
4008
+ <div class="settings-control">
4009
+ <select class="settings-select" id="chatProvider" onchange="chatProviderChanged()">
4010
+ <option value="">Not configured</option>
4011
+ <option value="anthropic">Anthropic</option>
4012
+ <option value="openai">OpenAI</option>
4013
+ <option value="ollama">Ollama</option>
4014
+ </select>
4015
+ </div>
4016
+ </div>
4017
+ <div class="settings-row">
4018
+ <div class="settings-label">
4019
+ <span class="settings-label-text">Model <span class="settings-origin" data-origin-key="model"></span></span>
4020
+ <span class="settings-label-hint">LLM model for generating responses</span>
4021
+ </div>
4022
+ <div class="settings-control">
4023
+ <select class="settings-select" id="chatModel" style="min-width:220px">
4024
+ <option value="">Select provider first</option>
4025
+ </select>
4026
+ </div>
4027
+ </div>
3592
4028
  </div>
3593
- </div>
3594
- <div class="settings-row">
3595
- <div class="settings-label">
3596
- <span class="settings-label-text">Show Detailed Timings</span>
3597
- <span class="settings-label-hint">Display p50/p95/p99 in benchmark results</span>
4029
+ <div class="settings-section">
4030
+ <div class="settings-section-title">Knowledge Base</div>
4031
+ <div class="settings-row">
4032
+ <div class="settings-label">
4033
+ <span class="settings-label-text">Database <span class="settings-origin" data-origin-key="db"></span></span>
4034
+ <span class="settings-label-hint">MongoDB database containing your documents</span>
4035
+ </div>
4036
+ <div class="settings-control">
4037
+ <input type="text" class="settings-input" id="chatDb" placeholder="database">
4038
+ </div>
4039
+ </div>
4040
+ <div class="settings-row">
4041
+ <div class="settings-label">
4042
+ <span class="settings-label-text">Collection <span class="settings-origin" data-origin-key="collection"></span></span>
4043
+ <span class="settings-label-hint">Collection with vector-indexed documents</span>
4044
+ </div>
4045
+ <div class="settings-control">
4046
+ <input type="text" class="settings-input" id="chatCollection" placeholder="collection">
4047
+ </div>
4048
+ </div>
4049
+ <div class="settings-row">
4050
+ <div class="settings-label">
4051
+ <span class="settings-label-text">Max Context Documents</span>
4052
+ <span class="settings-label-hint">How many documents to retrieve per query</span>
4053
+ </div>
4054
+ <div class="settings-control">
4055
+ <input type="number" class="settings-input" id="chatMaxDocs" value="5" min="1" max="20" style="min-width:80px;width:80px;">
4056
+ </div>
4057
+ </div>
4058
+ <div class="settings-row">
4059
+ <div class="settings-label">
4060
+ <span class="settings-label-text">Rerank Results</span>
4061
+ <span class="settings-label-hint">Use Voyage reranker to improve retrieval quality</span>
4062
+ </div>
4063
+ <div class="settings-control">
4064
+ <button class="settings-toggle active" id="chatRerank" type="button"></button>
4065
+ </div>
4066
+ </div>
3598
4067
  </div>
3599
- <div class="settings-control">
3600
- <button class="settings-toggle" id="settingsDetailedTimings" type="button"></button>
4068
+ <div class="settings-section">
4069
+ <div class="settings-section-title">Custom Instructions</div>
4070
+ <div class="settings-row" style="flex-direction:column;align-items:stretch;gap:8px;">
4071
+ <div class="settings-label">
4072
+ <span class="settings-label-text">System Prompt</span>
4073
+ <span class="settings-label-hint">Additional instructions appended to the base system prompt</span>
4074
+ </div>
4075
+ <textarea class="settings-input" id="chatSystemPrompt" rows="3" placeholder="e.g. You are a movie expert. Be enthusiastic about recommendations." style="min-width:100%;resize:vertical;font-family:var(--font);text-align:left;"></textarea>
4076
+ </div>
3601
4077
  </div>
3602
4078
  </div>
3603
- </div>
3604
4079
 
3605
- <div class="settings-section">
3606
- <div class="settings-section-title">Help</div>
3607
- <div class="settings-row">
3608
- <div class="settings-label">
3609
- <span class="settings-label-text">Welcome Tour</span>
3610
- <span class="settings-label-hint">Replay the onboarding walkthrough</span>
4080
+ <!-- ── Benchmark ── -->
4081
+ <div class="settings-panel" id="settings-benchmark">
4082
+ <div class="settings-panel-header">
4083
+ <h3 class="settings-panel-title">Benchmark</h3>
4084
+ <p class="settings-panel-subtitle">Benchmark iteration and display defaults</p>
3611
4085
  </div>
3612
- <div class="settings-control">
3613
- <button class="settings-reset-btn" id="settingsShowTour" style="background:var(--accent);color:var(--bg);">Show Welcome Tour</button>
4086
+ <div class="settings-section">
4087
+ <div class="settings-row">
4088
+ <div class="settings-label">
4089
+ <span class="settings-label-text">Iterations per Model</span>
4090
+ <span class="settings-label-hint">Number of runs when benchmarking latency</span>
4091
+ </div>
4092
+ <div class="settings-control">
4093
+ <select class="settings-select" id="settingsBenchIter">
4094
+ <option value="3">3 runs</option>
4095
+ <option value="5" selected>5 runs</option>
4096
+ <option value="10">10 runs</option>
4097
+ <option value="20">20 runs</option>
4098
+ </select>
4099
+ </div>
4100
+ </div>
4101
+ <div class="settings-row">
4102
+ <div class="settings-label">
4103
+ <span class="settings-label-text">Show Detailed Timings</span>
4104
+ <span class="settings-label-hint">Display p50/p95/p99 in benchmark results</span>
4105
+ </div>
4106
+ <div class="settings-control">
4107
+ <button class="settings-toggle" id="settingsDetailedTimings" type="button"></button>
4108
+ </div>
4109
+ </div>
3614
4110
  </div>
3615
4111
  </div>
3616
- </div>
3617
4112
 
3618
- <div class="settings-section">
3619
- <div class="settings-section-title">Data &amp; Privacy</div>
3620
- <div class="settings-row">
3621
- <div class="settings-label">
3622
- <span class="settings-label-text">Persist Embeddings Locally</span>
3623
- <span class="settings-label-hint">Cache embedding results in browser storage to avoid re-fetching</span>
4113
+ <!-- ── Data & Privacy ── -->
4114
+ <div class="settings-panel" id="settings-privacy">
4115
+ <div class="settings-panel-header">
4116
+ <h3 class="settings-panel-title">Data &amp; Privacy</h3>
4117
+ <p class="settings-panel-subtitle">Caching, analytics, and data management</p>
3624
4118
  </div>
3625
- <div class="settings-control">
3626
- <button class="settings-toggle active" id="settingsCacheEmbeddings" type="button"></button>
3627
- </div>
3628
- </div>
3629
- <div class="settings-row">
3630
- <div class="settings-label">
3631
- <span class="settings-label-text">Clear Cached Data</span>
3632
- <span class="settings-label-hint">Remove all locally stored embeddings and preferences</span>
3633
- </div>
3634
- <div class="settings-control">
3635
- <button class="settings-reset-btn" id="settingsClearCache">Clear All Data</button>
4119
+ <div class="settings-section">
4120
+ <div class="settings-row">
4121
+ <div class="settings-label">
4122
+ <span class="settings-label-text">Persist Embeddings Locally</span>
4123
+ <span class="settings-label-hint">Cache embedding results in browser storage to avoid re-fetching</span>
4124
+ </div>
4125
+ <div class="settings-control">
4126
+ <button class="settings-toggle active" id="settingsCacheEmbeddings" type="button"></button>
4127
+ </div>
4128
+ </div>
4129
+ <div class="settings-row">
4130
+ <div class="settings-label">
4131
+ <span class="settings-label-text">Clear Cached Data</span>
4132
+ <span class="settings-label-hint">Remove all locally stored embeddings and preferences</span>
4133
+ </div>
4134
+ <div class="settings-control">
4135
+ <button class="settings-reset-btn" id="settingsClearCache">Clear All Data</button>
4136
+ </div>
4137
+ </div>
4138
+ <div class="settings-row">
4139
+ <div class="settings-label">
4140
+ <span class="settings-label-text">Anonymous Usage Analytics</span>
4141
+ <span class="settings-label-hint">Help improve Vai by sharing anonymous usage data (version, platform, features used). No API keys or personal data.</span>
4142
+ </div>
4143
+ <div class="settings-control">
4144
+ <button class="settings-toggle active" id="settingsTelemetry" type="button"></button>
4145
+ </div>
4146
+ </div>
3636
4147
  </div>
3637
- </div>
3638
- <div class="settings-row">
3639
- <div class="settings-label">
3640
- <span class="settings-label-text">Anonymous Usage Analytics</span>
3641
- <span class="settings-label-hint">Help improve Vai by sharing anonymous usage data (version, platform, features used). No API keys or personal data.</span>
4148
+ <div class="settings-section">
4149
+ <div class="settings-section-title">Help</div>
4150
+ <div class="settings-row">
4151
+ <div class="settings-label">
4152
+ <span class="settings-label-text">Welcome Tour</span>
4153
+ <span class="settings-label-hint">Replay the onboarding walkthrough</span>
4154
+ </div>
4155
+ <div class="settings-control">
4156
+ <button class="settings-reset-btn" id="settingsShowTour" style="background:var(--accent);color:var(--bg);">Show Welcome Tour</button>
4157
+ </div>
4158
+ </div>
3642
4159
  </div>
3643
- <div class="settings-control">
3644
- <button class="settings-toggle active" id="settingsTelemetry" type="button"></button>
4160
+ <!-- Version Info (visible in Electron only) -->
4161
+ <div class="settings-section" id="settingsVersionSection" style="display:none;">
4162
+ <div class="settings-section-title">Version</div>
4163
+ <div class="settings-row">
4164
+ <div class="settings-label">
4165
+ <span class="settings-label-text">Vai Desktop</span>
4166
+ <span class="settings-label-hint">Electron desktop application</span>
4167
+ </div>
4168
+ <div class="settings-control">
4169
+ <span class="version-badge" id="settingsAppVersion">—</span>
4170
+ </div>
4171
+ </div>
4172
+ <div class="settings-row">
4173
+ <div class="settings-label">
4174
+ <span class="settings-label-text">Vai CLI Engine</span>
4175
+ <span class="settings-label-hint">Underlying CLI powering embeddings, search &amp; reranking</span>
4176
+ </div>
4177
+ <div class="settings-control">
4178
+ <span class="version-badge" id="settingsCliVersion">—</span>
4179
+ </div>
4180
+ </div>
3645
4181
  </div>
3646
4182
  </div>
3647
- </div>
3648
4183
 
3649
- <!-- Version Info (visible in Electron only) -->
3650
- <div class="settings-section" id="settingsVersionSection" style="display:none;">
3651
- <div class="settings-section-title">Version</div>
3652
- <div class="settings-row">
3653
- <div class="settings-label">
3654
- <span class="settings-label-text">Vai Desktop</span>
3655
- <span class="settings-label-hint">Electron desktop application</span>
3656
- </div>
3657
- <div class="settings-control">
3658
- <span class="version-badge" id="settingsAppVersion">—</span>
3659
- </div>
4184
+ <div style="text-align:center;padding:8px 0;">
4185
+ <span class="settings-saved" id="settingsSavedMsg">✓ Saved</span>
3660
4186
  </div>
3661
- <div class="settings-row">
3662
- <div class="settings-label">
3663
- <span class="settings-label-text">Vai CLI Engine</span>
3664
- <span class="settings-label-hint">Underlying CLI powering embeddings, search &amp; reranking</span>
3665
- </div>
3666
- <div class="settings-control">
3667
- <span class="version-badge" id="settingsCliVersion">—</span>
3668
- </div>
3669
- </div>
3670
- </div>
3671
4187
 
3672
- <div style="text-align:center;padding:8px 0;">
3673
- <span class="settings-saved" id="settingsSavedMsg">✓ Saved</span>
3674
- </div>
3675
-
3676
- </div>
4188
+ </div><!-- .settings-content -->
4189
+ </div><!-- .settings-layout -->
3677
4190
  </div>
3678
4191
 
3679
4192
  </div><!-- .main -->
@@ -4617,6 +5130,9 @@ const CONCEPT_META = {
4617
5130
  'code-generation': { icon: '💻', tab: 'explore' },
4618
5131
  scaffolding: { icon: '🏗️', tab: 'explore' },
4619
5132
  'eval-comparison': { icon: '📊', tab: 'benchmark' },
5133
+ // MongoDB Auto-Embedding
5134
+ 'auto-embedding': { icon: '⚡', tab: 'explore' },
5135
+ 'vai-vs-auto-embedding': { icon: '🔄', tab: 'explore' },
4620
5136
  };
4621
5137
 
4622
5138
  let exploreConcepts = {};
@@ -5938,6 +6454,41 @@ function initSettings() {
5938
6454
  const embedType = document.getElementById('embedInputType');
5939
6455
  if (embedType) embedType.value = s.inputType;
5940
6456
  }
6457
+
6458
+ // ── Chat settings event listeners (fields now live in settings page) ──
6459
+
6460
+ // Rerank toggle (was checkbox, now settings-toggle button)
6461
+ const rerankToggle = document.getElementById('chatRerank');
6462
+ if (rerankToggle) {
6463
+ rerankToggle.addEventListener('click', () => {
6464
+ rerankToggle.classList.toggle('active');
6465
+ saveChatSettings();
6466
+ });
6467
+ }
6468
+
6469
+ // Immediate save on select/toggle changes
6470
+ const chatProvider = document.getElementById('chatProvider');
6471
+ if (chatProvider) {
6472
+ chatProvider.addEventListener('change', () => {
6473
+ // chatProviderChanged() is called via onchange attr — save after a tick
6474
+ setTimeout(saveChatSettings, 100);
6475
+ });
6476
+ }
6477
+ const chatModel = document.getElementById('chatModel');
6478
+ if (chatModel) chatModel.addEventListener('change', saveChatSettings);
6479
+ const chatMaxDocs = document.getElementById('chatMaxDocs');
6480
+ if (chatMaxDocs) chatMaxDocs.addEventListener('change', saveChatSettings);
6481
+
6482
+ // Debounced save on text input changes
6483
+ const chatDb = document.getElementById('chatDb');
6484
+ if (chatDb) chatDb.addEventListener('input', () => { updateChatStatus(); saveChatSettingsDebounced(); });
6485
+ const chatCollection = document.getElementById('chatCollection');
6486
+ if (chatCollection) chatCollection.addEventListener('input', () => { updateChatStatus(); saveChatSettingsDebounced(); });
6487
+ const chatSystemPrompt = document.getElementById('chatSystemPrompt');
6488
+ if (chatSystemPrompt) chatSystemPrompt.addEventListener('input', saveChatSettingsDebounced);
6489
+
6490
+ // Set up settings sub-navigation
6491
+ setupSettingsNav();
5941
6492
  }
5942
6493
 
5943
6494
  // ── Update Checker ──
@@ -6122,6 +6673,13 @@ function initOnboarding() {
6122
6673
  (isElectron ? ' Create projects directly on disk.' : ' Download as ZIP.'),
6123
6674
  arrow: 'left',
6124
6675
  },
6676
+ {
6677
+ target: '[data-tab="chat"]',
6678
+ icon: '💬',
6679
+ title: 'RAG Chat',
6680
+ body: '<strong>Chat with your knowledge base</strong> using retrieval-augmented generation. Voyage AI finds relevant documents, then your chosen LLM generates grounded answers with source citations.',
6681
+ arrow: 'left',
6682
+ },
6125
6683
  {
6126
6684
  target: '[data-tab="benchmark"]',
6127
6685
  icon: '⏱️',
@@ -6707,6 +7265,26 @@ function startVSI(canvas, onExit) {
6707
7265
  const W = canvas.width = 600;
6708
7266
  const H = canvas.height = 500;
6709
7267
 
7268
+ // Helper function to draw a leaf shape
7269
+ function drawLeaf(ctx, x, y, size, color) {
7270
+ ctx.fillStyle = color;
7271
+ ctx.beginPath();
7272
+ // Leaf shape using bezier curves
7273
+ ctx.moveTo(x, y - size);
7274
+ ctx.quadraticCurveTo(x + size * 0.6, y - size * 0.3, x + size * 0.3, y + size * 0.5);
7275
+ ctx.quadraticCurveTo(x, y + size * 0.3, x, y + size);
7276
+ ctx.quadraticCurveTo(x, y + size * 0.3, x - size * 0.3, y + size * 0.5);
7277
+ ctx.quadraticCurveTo(x - size * 0.6, y - size * 0.3, x, y - size);
7278
+ ctx.fill();
7279
+ // Central vein
7280
+ ctx.strokeStyle = color === '#00ED64' ? '#00A35C' : color;
7281
+ ctx.lineWidth = 1;
7282
+ ctx.beginPath();
7283
+ ctx.moveTo(x, y - size);
7284
+ ctx.lineTo(x, y + size);
7285
+ ctx.stroke();
7286
+ }
7287
+
6710
7288
  // Ship
6711
7289
  const ship = { x: W / 2, w: 40, h: 16, speed: 5 };
6712
7290
  const bullets = [];
@@ -6779,7 +7357,8 @@ function startVSI(canvas, onExit) {
6779
7357
  for (let i = 0; i < 8; i++) {
6780
7358
  const angle = (Math.PI * 2 / 8) * i + Math.random() * 0.5;
6781
7359
  const speed = 1.5 + Math.random() * 2;
6782
- particles.push({ x, y, dx: Math.cos(angle) * speed, dy: Math.sin(angle) * speed, life: 30, color });
7360
+ const isLeaf = i % 3 === 0; // Every 3rd particle is a leaf
7361
+ particles.push({ x, y, dx: Math.cos(angle) * speed, dy: Math.sin(angle) * speed, life: 30, color, isLeaf });
6783
7362
  }
6784
7363
  explosionTexts.push({ x, y, text: sim.toFixed(2), life: 40, color });
6785
7364
  }
@@ -6902,24 +7481,17 @@ function startVSI(canvas, onExit) {
6902
7481
  ctx.fillRect(sx, sy, 1, 1);
6903
7482
  }
6904
7483
 
6905
- // Ship (triangle)
6906
- ctx.fillStyle = '#00ED64';
6907
- ctx.beginPath();
6908
- ctx.moveTo(ship.x, H - 40);
6909
- ctx.lineTo(ship.x - ship.w / 2, H - 24);
6910
- ctx.lineTo(ship.x + ship.w / 2, H - 24);
6911
- ctx.closePath();
6912
- ctx.fill();
7484
+ // Ship (Voyage AI leaf)
7485
+ drawLeaf(ctx, ship.x, H - 32, 12, '#00ED64');
6913
7486
  // Ship label
6914
7487
  ctx.fillStyle = '#00ED64';
6915
7488
  ctx.font = '9px monospace';
6916
7489
  ctx.textAlign = 'center';
6917
- ctx.fillText('query', ship.x, H - 14);
7490
+ ctx.fillText('voyage', ship.x, H - 14);
6918
7491
 
6919
- // Bullets
6920
- ctx.fillStyle = '#00ED64';
7492
+ // Bullets (small leaf shapes)
6921
7493
  for (const b of bullets) {
6922
- ctx.fillRect(b.x - 1, b.y, 2, 8);
7494
+ drawLeaf(ctx, b.x, b.y, 3, '#00ED64');
6923
7495
  }
6924
7496
 
6925
7497
  // Boss
@@ -6953,11 +7525,15 @@ function startVSI(canvas, onExit) {
6953
7525
  ctx.fillText(e.text, e.x, e.y + 3);
6954
7526
  }
6955
7527
 
6956
- // Particles
7528
+ // Particles (with occasional leaf shapes)
6957
7529
  for (const p of particles) {
6958
7530
  ctx.globalAlpha = p.life / 30;
6959
- ctx.fillStyle = p.color;
6960
- ctx.fillRect(p.x - 2, p.y - 2, 4, 4);
7531
+ if (p.isLeaf) {
7532
+ drawLeaf(ctx, p.x, p.y, 2, p.color);
7533
+ } else {
7534
+ ctx.fillStyle = p.color;
7535
+ ctx.fillRect(p.x - 2, p.y - 2, 4, 4);
7536
+ }
6961
7537
  }
6962
7538
  ctx.globalAlpha = 1;
6963
7539
 
@@ -6977,15 +7553,27 @@ function startVSI(canvas, onExit) {
6977
7553
  ctx.textAlign = 'left';
6978
7554
  ctx.fillText(`SCORE: ${score}`, 12, 20);
6979
7555
  ctx.fillText(`WAVE: ${wave}`, 12, 38);
6980
- ctx.textAlign = 'right';
6981
- ctx.fillText('❤️'.repeat(lives), W - 12, 20);
6982
7556
 
6983
- // Title
7557
+ // Lives (leaf icons)
7558
+ for (let i = 0; i < lives; i++) {
7559
+ drawLeaf(ctx, W - 20 - (i * 18), 15, 6, '#00ED64');
7560
+ }
7561
+
7562
+ // Title with Voyage AI branding
6984
7563
  ctx.fillStyle = '#00ED64';
6985
7564
  ctx.font = 'bold 10px monospace';
6986
7565
  ctx.textAlign = 'center';
6987
7566
  ctx.fillText('VECTOR SPACE INVADERS', W / 2, H - 4);
6988
7567
 
7568
+ // Voyage AI watermark (top right corner)
7569
+ ctx.globalAlpha = 0.3;
7570
+ drawLeaf(ctx, W - 25, 25, 8, '#00ED64');
7571
+ ctx.fillStyle = '#00ED64';
7572
+ ctx.font = 'bold 9px monospace';
7573
+ ctx.textAlign = 'right';
7574
+ ctx.fillText('VOYAGE AI', W - 38, 28);
7575
+ ctx.globalAlpha = 1;
7576
+
6989
7577
  // Game over
6990
7578
  if (gameOver) {
6991
7579
  ctx.fillStyle = 'rgba(0,30,43,0.8)';
@@ -7026,6 +7614,400 @@ function startVSI(canvas, onExit) {
7026
7614
  loop();
7027
7615
  }
7028
7616
 
7617
+ // ── Settings Sub-Navigation ──
7618
+
7619
+ function switchSettingsSection(section) {
7620
+ document.querySelectorAll('.settings-nav-item').forEach(btn => {
7621
+ btn.classList.toggle('active', btn.dataset.settingsSection === section);
7622
+ });
7623
+ document.querySelectorAll('.settings-panel').forEach(p => {
7624
+ p.classList.toggle('active', p.id === 'settings-' + section);
7625
+ });
7626
+ // Scroll content area to top
7627
+ const content = document.querySelector('.settings-content');
7628
+ if (content) content.scrollTop = 0;
7629
+ }
7630
+
7631
+ function openChatSettings() {
7632
+ switchTab('settings');
7633
+ switchSettingsSection('chat');
7634
+ }
7635
+
7636
+ function setupSettingsNav() {
7637
+ document.querySelectorAll('.settings-nav-item').forEach(btn => {
7638
+ btn.addEventListener('click', () => {
7639
+ switchSettingsSection(btn.dataset.settingsSection);
7640
+ });
7641
+ });
7642
+ }
7643
+
7644
+ // ── Settings Origins (provenance badges) ──
7645
+
7646
+ async function loadSettingsOrigins() {
7647
+ try {
7648
+ const res = await fetch('/api/settings/origins');
7649
+ if (!res.ok) return;
7650
+ const origins = await res.json();
7651
+ const labels = { env: 'ENV', config: '~/.vai', project: '.vai.json', default: 'Default' };
7652
+ const tooltips = {
7653
+ env: 'Set via environment variable',
7654
+ config: 'Set in ~/.vai/config.json',
7655
+ project: 'Set in project .vai.json',
7656
+ default: 'Using built-in default',
7657
+ };
7658
+ Object.entries(origins).forEach(([key, origin]) => {
7659
+ const badge = document.querySelector(`[data-origin-key="${key}"]`);
7660
+ if (badge) {
7661
+ badge.className = `settings-origin origin-${origin}`;
7662
+ badge.textContent = labels[origin] || origin;
7663
+ badge.title = tooltips[origin] || '';
7664
+ }
7665
+ });
7666
+ } catch { /* silently ignore */ }
7667
+ }
7668
+
7669
+ // ── Save Chat Settings ──
7670
+
7671
+ let _chatSaveTimer = null;
7672
+
7673
+ function saveChatSettings() {
7674
+ const settings = {
7675
+ provider: document.getElementById('chatProvider').value,
7676
+ model: document.getElementById('chatModel').value,
7677
+ db: document.getElementById('chatDb').value,
7678
+ collection: document.getElementById('chatCollection').value,
7679
+ maxDocs: parseInt(document.getElementById('chatMaxDocs').value) || 5,
7680
+ rerank: document.getElementById('chatRerank').classList.contains('active'),
7681
+ systemPrompt: document.getElementById('chatSystemPrompt').value.trim(),
7682
+ };
7683
+ fetch('/api/chat/config', {
7684
+ method: 'POST',
7685
+ headers: { 'Content-Type': 'application/json' },
7686
+ body: JSON.stringify(settings),
7687
+ }).catch(() => {});
7688
+ updateChatStatus();
7689
+ // Update not-configured banner
7690
+ const notConfigured = document.getElementById('chatNotConfigured');
7691
+ if (notConfigured) {
7692
+ notConfigured.style.display = (!settings.provider || !settings.db) ? 'flex' : 'none';
7693
+ }
7694
+ // Refresh origin badges after save
7695
+ loadSettingsOrigins();
7696
+ flashSaved();
7697
+ }
7698
+
7699
+ function saveChatSettingsDebounced() {
7700
+ clearTimeout(_chatSaveTimer);
7701
+ _chatSaveTimer = setTimeout(saveChatSettings, 500);
7702
+ }
7703
+
7704
+ // flashSaved() is defined earlier (near initSettings) — reused here
7705
+
7706
+ // ── Chat Tab ──
7707
+ let chatSessionId = null;
7708
+
7709
+ async function loadChatConfig() {
7710
+ try {
7711
+ const res = await fetch('/api/chat/config');
7712
+ const data = await res.json();
7713
+ if (data.provider) {
7714
+ document.getElementById('chatProvider').value = data.provider;
7715
+ await chatProviderChanged(); // Populate model dropdown
7716
+ // After models load, select the configured model
7717
+ if (data.model) {
7718
+ const modelSelect = document.getElementById('chatModel');
7719
+ const exists = Array.from(modelSelect.options).some(o => o.value === data.model);
7720
+ if (exists) {
7721
+ modelSelect.value = data.model;
7722
+ }
7723
+ }
7724
+ }
7725
+ if (data.db) document.getElementById('chatDb').value = data.db;
7726
+ if (data.collection) document.getElementById('chatCollection').value = data.collection;
7727
+ if (data.chat?.maxContextDocs) document.getElementById('chatMaxDocs').value = data.chat.maxContextDocs;
7728
+ if (data.chat?.rerank === false) document.getElementById('chatRerank').classList.remove('active');
7729
+ if (data.chat?.systemPrompt) document.getElementById('chatSystemPrompt').value = data.chat.systemPrompt;
7730
+ updateChatStatus();
7731
+ // Show not-configured banner if incomplete
7732
+ const notConfigured = document.getElementById('chatNotConfigured');
7733
+ if (notConfigured) {
7734
+ notConfigured.style.display = (!data.provider || !data.db) ? 'flex' : 'none';
7735
+ }
7736
+ } catch {
7737
+ // No config — show not-configured banner
7738
+ const notConfigured = document.getElementById('chatNotConfigured');
7739
+ if (notConfigured) notConfigured.style.display = 'flex';
7740
+ }
7741
+ }
7742
+
7743
+ function updateChatStatus() {
7744
+ const provider = document.getElementById('chatProvider');
7745
+ const db = document.getElementById('chatDb').value;
7746
+ const collection = document.getElementById('chatCollection').value;
7747
+ const providerLabel = provider.value
7748
+ ? provider.options[provider.selectedIndex].text
7749
+ : 'No provider';
7750
+ document.getElementById('chatStatusProvider').textContent = providerLabel;
7751
+ document.getElementById('chatStatusDb').textContent =
7752
+ (db && collection) ? `${db}.${collection}` : 'No database';
7753
+ }
7754
+
7755
+ async function chatProviderChanged() {
7756
+ updateChatStatus();
7757
+ const provider = document.getElementById('chatProvider').value;
7758
+ const modelSelect = document.getElementById('chatModel');
7759
+
7760
+ if (!provider) {
7761
+ modelSelect.innerHTML = '<option value="">Select provider first</option>';
7762
+ return;
7763
+ }
7764
+
7765
+ modelSelect.innerHTML = '<option value="">Loading models...</option>';
7766
+ modelSelect.disabled = true;
7767
+
7768
+ try {
7769
+ const res = await fetch(`/api/chat/models?provider=${provider}`);
7770
+ const data = await res.json();
7771
+ const models = data.models || [];
7772
+ const defaults = { anthropic: 'claude-sonnet-4-5-20250929', openai: 'gpt-4o', ollama: 'llama3.1' };
7773
+ const defaultModel = defaults[provider];
7774
+
7775
+ modelSelect.innerHTML = '';
7776
+
7777
+ if (models.length === 0) {
7778
+ if (provider === 'ollama') {
7779
+ modelSelect.innerHTML = '<option value="">No models found — is Ollama running?</option>';
7780
+ } else {
7781
+ // Fallback to default
7782
+ modelSelect.innerHTML = `<option value="${defaultModel}">${defaultModel}</option>`;
7783
+ }
7784
+ } else {
7785
+ models.forEach(m => {
7786
+ const opt = document.createElement('option');
7787
+ opt.value = m.id;
7788
+ let label = m.name || m.id;
7789
+ if (m.context) label += ` (${m.context})`;
7790
+ if (m.size) label += ` — ${m.size}`;
7791
+ if (m.parameterSize) label += ` [${m.parameterSize}]`;
7792
+ if (m.quantization) label += ` ${m.quantization}`;
7793
+ opt.textContent = label;
7794
+ if (m.id === defaultModel) opt.selected = true;
7795
+ modelSelect.appendChild(opt);
7796
+ });
7797
+ }
7798
+
7799
+ // Always add a custom option
7800
+ const customOpt = document.createElement('option');
7801
+ customOpt.value = '__custom__';
7802
+ customOpt.textContent = '✏️ Custom model...';
7803
+ modelSelect.appendChild(customOpt);
7804
+
7805
+ } catch {
7806
+ const defaults = { anthropic: 'claude-sonnet-4-5-20250929', openai: 'gpt-4o', ollama: 'llama3.1' };
7807
+ modelSelect.innerHTML = `<option value="${defaults[provider] || ''}">${defaults[provider] || 'default'}</option>`;
7808
+ }
7809
+
7810
+ modelSelect.disabled = false;
7811
+ }
7812
+
7813
+ // Handle custom model selection
7814
+ document.getElementById('chatModel')?.addEventListener('change', function() {
7815
+ if (this.value === '__custom__') {
7816
+ const custom = prompt('Enter model name:');
7817
+ if (custom) {
7818
+ const opt = document.createElement('option');
7819
+ opt.value = custom;
7820
+ opt.textContent = custom;
7821
+ // Insert before the custom option
7822
+ this.insertBefore(opt, this.lastChild);
7823
+ this.value = custom;
7824
+ } else {
7825
+ this.selectedIndex = 0;
7826
+ }
7827
+ }
7828
+ });
7829
+
7830
+ function chatInputKeydown(e) {
7831
+ if (e.key === 'Enter' && !e.shiftKey) {
7832
+ e.preventDefault();
7833
+ sendChatMessage();
7834
+ }
7835
+ }
7836
+
7837
+ function addChatMessage(role, content, sources) {
7838
+ const container = document.getElementById('chatMessages');
7839
+ const div = document.createElement('div');
7840
+ div.className = `chat-message ${role}`;
7841
+ const contentSpan = document.createElement('span');
7842
+ contentSpan.className = 'chat-message-content';
7843
+ contentSpan.textContent = content;
7844
+ div.appendChild(contentSpan);
7845
+
7846
+ if (sources && sources.length > 0) {
7847
+ const details = document.createElement('details');
7848
+ details.className = 'chat-sources';
7849
+ const summary = document.createElement('summary');
7850
+ summary.textContent = `${sources.length} source${sources.length > 1 ? 's' : ''}`;
7851
+ details.appendChild(summary);
7852
+ const ul = document.createElement('ul');
7853
+ sources.forEach(s => {
7854
+ const li = document.createElement('li');
7855
+ li.textContent = `${s.source} (${s.score?.toFixed(2) || 'N/A'})`;
7856
+ ul.appendChild(li);
7857
+ });
7858
+ details.appendChild(ul);
7859
+ div.appendChild(details);
7860
+ }
7861
+
7862
+ container.appendChild(div);
7863
+ container.scrollTop = container.scrollHeight;
7864
+ return div;
7865
+ }
7866
+
7867
+ async function sendChatMessage() {
7868
+ const input = document.getElementById('chatInput');
7869
+ const query = input.value.trim();
7870
+ if (!query) return;
7871
+
7872
+ const provider = document.getElementById('chatProvider').value;
7873
+ const modelVal = document.getElementById('chatModel').value;
7874
+ const model = (modelVal && modelVal !== '__custom__') ? modelVal : undefined;
7875
+ const db = document.getElementById('chatDb').value;
7876
+ const collection = document.getElementById('chatCollection').value;
7877
+ const maxDocs = parseInt(document.getElementById('chatMaxDocs').value) || 5;
7878
+ const rerank = document.getElementById('chatRerank').classList.contains('active');
7879
+ const systemPrompt = document.getElementById('chatSystemPrompt').value.trim() || undefined;
7880
+
7881
+ if (!provider) {
7882
+ addChatMessage('system-msg', 'Please select an LLM provider in <a href="#" onclick="openChatSettings();return false;">Chat Settings</a>.');
7883
+ return;
7884
+ }
7885
+ if (!db || !collection) {
7886
+ addChatMessage('system-msg', 'Please configure a database and collection in <a href="#" onclick="openChatSettings();return false;">Chat Settings</a>.');
7887
+ return;
7888
+ }
7889
+
7890
+ // Show user message
7891
+ addChatMessage('user', query);
7892
+ input.value = '';
7893
+ input.style.height = 'auto';
7894
+
7895
+ // Show typing indicator
7896
+ const typing = document.createElement('div');
7897
+ typing.className = 'chat-typing';
7898
+ typing.textContent = 'Thinking';
7899
+ document.getElementById('chatMessages').appendChild(typing);
7900
+
7901
+ // Disable input
7902
+ const sendBtn = document.getElementById('chatSendBtn');
7903
+ sendBtn.disabled = true;
7904
+ input.disabled = true;
7905
+
7906
+ try {
7907
+ const res = await fetch('/api/chat/message', {
7908
+ method: 'POST',
7909
+ headers: { 'Content-Type': 'application/json' },
7910
+ body: JSON.stringify({ query, db, collection, provider, model, maxDocs, rerank, systemPrompt }),
7911
+ });
7912
+
7913
+ if (!res.ok) {
7914
+ const err = await res.json();
7915
+ typing.remove();
7916
+ addChatMessage('system-msg', `Error: ${err.error || 'Request failed'}`);
7917
+ return;
7918
+ }
7919
+
7920
+ // Parse SSE stream
7921
+ const reader = res.body.getReader();
7922
+ const decoder = new TextDecoder();
7923
+ let buffer = '';
7924
+ let assistantDiv = null;
7925
+ let fullText = '';
7926
+ let sources = [];
7927
+
7928
+ while (true) {
7929
+ const { done, value } = await reader.read();
7930
+ if (done) break;
7931
+
7932
+ buffer += decoder.decode(value, { stream: true });
7933
+ const lines = buffer.split('\n');
7934
+ buffer = lines.pop() || '';
7935
+
7936
+ let currentEvent = null;
7937
+ for (const line of lines) {
7938
+ if (line.startsWith('event: ')) {
7939
+ currentEvent = line.slice(7).trim();
7940
+ continue;
7941
+ }
7942
+ if (line.startsWith('data: ')) {
7943
+ const data = JSON.parse(line.slice(6));
7944
+
7945
+ if (currentEvent === 'retrieval') {
7946
+ typing.textContent = `Retrieved ${data.docs?.length || 0} docs (${data.timeMs}ms)`;
7947
+ }
7948
+
7949
+ if (currentEvent === 'chunk') {
7950
+ if (!assistantDiv) {
7951
+ typing.remove();
7952
+ assistantDiv = addChatMessage('assistant', '');
7953
+ }
7954
+ fullText += data.text;
7955
+ assistantDiv.querySelector('.chat-message-content').textContent = fullText;
7956
+ document.getElementById('chatMessages').scrollTop = document.getElementById('chatMessages').scrollHeight;
7957
+ }
7958
+
7959
+ if (currentEvent === 'done') {
7960
+ sources = data.sources || [];
7961
+ if (sources.length > 0 && assistantDiv) {
7962
+ const details = document.createElement('details');
7963
+ details.className = 'chat-sources';
7964
+ const summary = document.createElement('summary');
7965
+ summary.textContent = `${sources.length} source${sources.length > 1 ? 's' : ''}`;
7966
+ details.appendChild(summary);
7967
+ const ul = document.createElement('ul');
7968
+ sources.forEach(s => {
7969
+ const li = document.createElement('li');
7970
+ li.textContent = `${s.source} (${s.score?.toFixed(2) || 'N/A'})`;
7971
+ ul.appendChild(li);
7972
+ });
7973
+ details.appendChild(ul);
7974
+ assistantDiv.appendChild(details);
7975
+ }
7976
+ }
7977
+
7978
+ if (currentEvent === 'error') {
7979
+ typing.remove();
7980
+ addChatMessage('system-msg', `Error: ${data.error}`);
7981
+ }
7982
+
7983
+ currentEvent = null;
7984
+ }
7985
+ }
7986
+ }
7987
+
7988
+ // Remove typing if still present
7989
+ if (typing.parentNode) typing.remove();
7990
+
7991
+ } catch (err) {
7992
+ if (typing.parentNode) typing.remove();
7993
+ addChatMessage('system-msg', `Error: ${err.message}`);
7994
+ } finally {
7995
+ sendBtn.disabled = false;
7996
+ input.disabled = false;
7997
+ input.focus();
7998
+ }
7999
+ }
8000
+
8001
+ // Auto-resize chat input
8002
+ document.getElementById('chatInput')?.addEventListener('input', function() {
8003
+ this.style.height = 'auto';
8004
+ this.style.height = Math.min(this.scrollHeight, 120) + 'px';
8005
+ });
8006
+
8007
+ // Load chat config and settings origins on init
8008
+ loadChatConfig();
8009
+ loadSettingsOrigins();
8010
+
7029
8011
  // ── Expose functions to global scope for onclick handlers ──
7030
8012
  window.setGenerateMode = setGenerateMode;
7031
8013
  window.copyGeneratedCode = copyGeneratedCode;
@@ -7034,6 +8016,12 @@ window.updateGenComponents = updateGenComponents;
7034
8016
  window.createScaffold = createScaffold;
7035
8017
  window.pickScaffoldDirectory = pickScaffoldDirectory;
7036
8018
  window.downloadScaffoldZip = downloadScaffoldZip;
8019
+ window.sendChatMessage = sendChatMessage;
8020
+ window.openChatSettings = openChatSettings;
8021
+ window.switchSettingsSection = switchSettingsSection;
8022
+ window.updateChatStatus = updateChatStatus;
8023
+ window.chatInputKeydown = chatInputKeydown;
8024
+ window.chatProviderChanged = chatProviderChanged;
7037
8025
 
7038
8026
  // ── Start ──
7039
8027
  init();
@@ -7126,8 +8114,16 @@ init();
7126
8114
 
7127
8115
  <!-- 🐛 Bug Reporter -->
7128
8116
  <style>
7129
- .bug-floating-button{position:fixed;bottom:20px;right:20px;width:48px;height:48px;border-radius:50%;background:linear-gradient(135deg,#ff6b6b,#ee5a5a);border:none;cursor:pointer;font-size:24px;display:flex;align-items:center;justify-content:center;box-shadow:0 4px 12px rgba(238,90,90,0.4);transition:all .2s;z-index:9998}
7130
- .bug-floating-button:hover{transform:scale(1.1);box-shadow:0 6px 16px rgba(238,90,90,0.5)}
8117
+ .sidebar-bug-link{
8118
+ display:flex;align-items:center;gap:6px;
8119
+ background:none;border:none;cursor:pointer;
8120
+ font-size:11px;color:var(--text-muted);
8121
+ padding:4px 0;font-family:inherit;
8122
+ transition:color .15s;
8123
+ }
8124
+ .sidebar-bug-link:hover{color:var(--text);}
8125
+ .sidebar-bug-link svg{width:12px;height:12px;flex-shrink:0;opacity:0.6;}
8126
+ .sidebar-bug-link:hover svg{opacity:1;}
7131
8127
  .bug-reporter-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.6);backdrop-filter:blur(4px);display:flex;align-items:center;justify-content:center;z-index:9999}
7132
8128
  .bug-reporter-modal{background:var(--bg-surface);border-radius:12px;width:90%;max-width:500px;max-height:90vh;overflow-y:auto;box-shadow:0 20px 60px rgba(0,0,0,0.5)}
7133
8129
  .bug-reporter-header{display:flex;align-items:center;gap:12px;padding:16px 20px;border-bottom:1px solid var(--border)}
@@ -7157,7 +8153,7 @@ init();
7157
8153
  .bug-success code{background:rgba(255,255,255,0.1);padding:4px 8px;border-radius:4px;font-size:12px;color:var(--accent)}
7158
8154
  </style>
7159
8155
 
7160
- <button class="bug-floating-button" id="bugButton" title="Report a Bug">🐛</button>
8156
+ <!-- Bug button moved to sidebar footer -->
7161
8157
 
7162
8158
  <div class="bug-reporter-overlay" id="bugOverlay" style="display:none">
7163
8159
  <div class="bug-reporter-modal">