voyageai-cli 1.20.6 → 1.22.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.
- package/CHANGELOG.md +142 -26
- package/README.md +130 -2
- package/package.json +3 -2
- package/src/cli.js +10 -0
- package/src/commands/bug.js +249 -0
- package/src/commands/eval.js +420 -10
- package/src/commands/generate.js +220 -0
- package/src/commands/playground.js +93 -0
- package/src/commands/purge.js +271 -0
- package/src/commands/refresh.js +322 -0
- package/src/commands/scaffold.js +217 -0
- package/src/lib/codegen.js +339 -0
- package/src/lib/explanations.js +155 -0
- package/src/lib/scaffold-structure.js +114 -0
- package/src/lib/templates/nextjs/README.md.tpl +106 -0
- package/src/lib/templates/nextjs/env.example.tpl +8 -0
- package/src/lib/templates/nextjs/layout.jsx.tpl +29 -0
- package/src/lib/templates/nextjs/lib-mongo.js.tpl +111 -0
- package/src/lib/templates/nextjs/lib-voyage.js.tpl +103 -0
- package/src/lib/templates/nextjs/package.json.tpl +33 -0
- package/src/lib/templates/nextjs/page-search.jsx.tpl +147 -0
- package/src/lib/templates/nextjs/route-ingest.js.tpl +114 -0
- package/src/lib/templates/nextjs/route-search.js.tpl +97 -0
- package/src/lib/templates/nextjs/theme.js.tpl +84 -0
- package/src/lib/templates/python/README.md.tpl +145 -0
- package/src/lib/templates/python/app.py.tpl +221 -0
- package/src/lib/templates/python/chunker.py.tpl +127 -0
- package/src/lib/templates/python/env.example.tpl +12 -0
- package/src/lib/templates/python/mongo_client.py.tpl +125 -0
- package/src/lib/templates/python/requirements.txt.tpl +10 -0
- package/src/lib/templates/python/voyage_client.py.tpl +124 -0
- package/src/lib/templates/vanilla/README.md.tpl +156 -0
- package/src/lib/templates/vanilla/client.js.tpl +103 -0
- package/src/lib/templates/vanilla/connection.js.tpl +126 -0
- package/src/lib/templates/vanilla/env.example.tpl +11 -0
- package/src/lib/templates/vanilla/ingest.js.tpl +231 -0
- package/src/lib/templates/vanilla/package.json.tpl +31 -0
- package/src/lib/templates/vanilla/retrieval.js.tpl +100 -0
- package/src/lib/templates/vanilla/search-api.js.tpl +175 -0
- package/src/lib/templates/vanilla/server.js.tpl +81 -0
- package/src/lib/zip.js +130 -0
- package/src/playground/index.html +708 -3
|
@@ -660,6 +660,48 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
660
660
|
font-size: 12px;
|
|
661
661
|
}
|
|
662
662
|
|
|
663
|
+
/* Subtabs - segmented control style */
|
|
664
|
+
.subtabs {
|
|
665
|
+
display: flex;
|
|
666
|
+
background: var(--card);
|
|
667
|
+
border-radius: var(--radius);
|
|
668
|
+
padding: 4px;
|
|
669
|
+
gap: 4px;
|
|
670
|
+
margin-bottom: 20px;
|
|
671
|
+
border: 1px solid var(--accent-dim);
|
|
672
|
+
}
|
|
673
|
+
.subtab {
|
|
674
|
+
flex: 1;
|
|
675
|
+
background: transparent;
|
|
676
|
+
color: var(--text-secondary);
|
|
677
|
+
border: none;
|
|
678
|
+
padding: 10px 20px;
|
|
679
|
+
border-radius: calc(var(--radius) - 2px);
|
|
680
|
+
font-size: 14px;
|
|
681
|
+
font-weight: 500;
|
|
682
|
+
font-family: var(--font);
|
|
683
|
+
cursor: pointer;
|
|
684
|
+
transition: all 0.2s;
|
|
685
|
+
display: flex;
|
|
686
|
+
align-items: center;
|
|
687
|
+
justify-content: center;
|
|
688
|
+
gap: 8px;
|
|
689
|
+
}
|
|
690
|
+
.subtab:hover {
|
|
691
|
+
color: var(--text);
|
|
692
|
+
background: rgba(0, 237, 100, 0.05);
|
|
693
|
+
}
|
|
694
|
+
.subtab.active {
|
|
695
|
+
background: var(--accent);
|
|
696
|
+
color: var(--green-dark);
|
|
697
|
+
font-weight: 600;
|
|
698
|
+
}
|
|
699
|
+
.subtab svg {
|
|
700
|
+
width: 16px;
|
|
701
|
+
height: 16px;
|
|
702
|
+
}
|
|
703
|
+
[data-theme="light"] .subtab.active { color: #FFFFFF; }
|
|
704
|
+
|
|
663
705
|
.options-row {
|
|
664
706
|
display: flex;
|
|
665
707
|
gap: 12px;
|
|
@@ -2297,6 +2339,9 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
2297
2339
|
<symbol id="lg-config" viewBox="0 0 16 16">
|
|
2298
2340
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.5 1.5A.5.5 0 0 1 7 1h2a.5.5 0 0 1 .5.5v1.05a5 5 0 0 1 1.37.57l.74-.74a.5.5 0 0 1 .7 0l1.42 1.42a.5.5 0 0 1 0 .7l-.74.74c.25.43.44.89.57 1.37H14.5a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.5.5h-1.05a5 5 0 0 1-.57 1.37l.74.74a.5.5 0 0 1 0 .7l-1.42 1.42a.5.5 0 0 1-.7 0l-.74-.74a5 5 0 0 1-1.37.57V14.5a.5.5 0 0 1-.5.5H7a.5.5 0 0 1-.5-.5v-1.05a5 5 0 0 1-1.37-.57l-.74.74a.5.5 0 0 1-.7 0L2.27 12.2a.5.5 0 0 1 0-.7l.74-.74a5 5 0 0 1-.57-1.37H1.5A.5.5 0 0 1 1 9V7a.5.5 0 0 1 .5-.5h1.05c.13-.48.32-.94.57-1.37l-.74-.74a.5.5 0 0 1 0-.7L3.8 2.27a.5.5 0 0 1 .7 0l.74.74A5 5 0 0 1 6.6 2.44V1.5zM8 5.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5z" fill="currentColor"/>
|
|
2299
2341
|
</symbol>
|
|
2342
|
+
<symbol id="lg-code" viewBox="0 0 16 16">
|
|
2343
|
+
<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
|
+
</symbol>
|
|
2300
2345
|
</svg>
|
|
2301
2346
|
|
|
2302
2347
|
<div class="app-shell">
|
|
@@ -2315,10 +2360,11 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
2315
2360
|
<button class="tab-btn" data-tab="compare" role="tab" aria-selected="false" aria-controls="tab-compare" id="tab-btn-compare"><span class="tab-btn-icon" aria-hidden="true"><svg><use href="#lg-arrows"/></svg></span><span>Compare</span></button>
|
|
2316
2361
|
<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>
|
|
2317
2362
|
<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
|
+
<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>
|
|
2318
2364
|
</div>
|
|
2319
2365
|
<div class="sidebar-nav-divider"></div>
|
|
2320
|
-
<div class="sidebar-nav-group" role="tablist" aria-label="
|
|
2321
|
-
<div class="sidebar-nav-label" id="nav-
|
|
2366
|
+
<div class="sidebar-nav-group" role="tablist" aria-label="Learn">
|
|
2367
|
+
<div class="sidebar-nav-label" id="nav-learn-label">Learn</div>
|
|
2322
2368
|
<button class="tab-btn" data-tab="benchmark" role="tab" aria-selected="false" aria-controls="tab-benchmark" id="tab-btn-benchmark"><span class="tab-btn-icon" aria-hidden="true"><svg><use href="#lg-gauge"/></svg></span><span>Benchmark</span></button>
|
|
2323
2369
|
<button class="tab-btn" data-tab="explore" role="tab" aria-selected="false" aria-controls="tab-explore" id="tab-btn-explore"><span class="tab-btn-icon" aria-hidden="true"><svg><use href="#lg-bulb"/></svg></span><span>Explore</span></button>
|
|
2324
2370
|
<button class="tab-btn" data-tab="about" role="tab" aria-selected="false" aria-controls="tab-about" id="tab-btn-about"><span class="tab-btn-icon" aria-hidden="true"><svg><use href="#lg-info"/></svg></span><span>About</span></button>
|
|
@@ -3214,6 +3260,179 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
3214
3260
|
</div>
|
|
3215
3261
|
</div>
|
|
3216
3262
|
|
|
3263
|
+
<!-- ========== GENERATE TAB ========== -->
|
|
3264
|
+
<div class="tab-panel" id="tab-generate" role="tabpanel" aria-labelledby="tab-btn-generate" tabindex="0">
|
|
3265
|
+
<div class="page-header">
|
|
3266
|
+
<h2 class="page-header-title">Generate</h2>
|
|
3267
|
+
<p class="page-header-subtitle">Generate code and scaffold projects</p>
|
|
3268
|
+
</div>
|
|
3269
|
+
|
|
3270
|
+
<!-- Mode Toggle -->
|
|
3271
|
+
<div class="subtabs" role="tablist">
|
|
3272
|
+
<button class="subtab active" id="genModeCode" onclick="setGenerateMode('code')" role="tab" aria-selected="true">
|
|
3273
|
+
<svg viewBox="0 0 16 16"><path fill="currentColor" d="M5.854 4.854a.5.5 0 1 0-.708-.708l-3.5 3.5a.5.5 0 0 0 0 .708l3.5 3.5a.5.5 0 0 0 .708-.708L2.707 8l3.147-3.146zm4.292 0a.5.5 0 0 1 .708-.708l3.5 3.5a.5.5 0 0 1 0 .708l-3.5 3.5a.5.5 0 0 1-.708-.708L13.293 8l-3.147-3.146z"/></svg>
|
|
3274
|
+
Generate Code
|
|
3275
|
+
</button>
|
|
3276
|
+
<button class="subtab" id="genModeScaffold" onclick="setGenerateMode('scaffold')" role="tab" aria-selected="false">
|
|
3277
|
+
<svg viewBox="0 0 16 16"><path fill="currentColor" d="M14.5 3a.5.5 0 0 1 .5.5v9a.5.5 0 0 1-.5.5h-13a.5.5 0 0 1-.5-.5v-9a.5.5 0 0 1 .5-.5h13zm-13-1A1.5 1.5 0 0 0 0 3.5v9A1.5 1.5 0 0 0 1.5 14h13a1.5 1.5 0 0 0 1.5-1.5v-9A1.5 1.5 0 0 0 14.5 2h-13z"/><path fill="currentColor" d="M5 8a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7A.5.5 0 0 1 5 8zm0-2.5a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5zm0 5a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5zm-1-5a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0zM4 8a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0zm0 2.5a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0z"/></svg>
|
|
3278
|
+
Scaffold Project
|
|
3279
|
+
</button>
|
|
3280
|
+
</div>
|
|
3281
|
+
|
|
3282
|
+
<!-- Generate Code Panel -->
|
|
3283
|
+
<div id="generateCodePanel" style="display:block;">
|
|
3284
|
+
<div class="card">
|
|
3285
|
+
<div class="card-header"><h3>Generate Code Snippet</h3></div>
|
|
3286
|
+
<div class="card-body">
|
|
3287
|
+
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:16px;">
|
|
3288
|
+
<label>
|
|
3289
|
+
<span style="display:block;margin-bottom:4px;font-weight:500;">Target Framework</span>
|
|
3290
|
+
<select id="genTarget" onchange="updateGenComponents()" style="width:100%;">
|
|
3291
|
+
<option value="vanilla">Node.js + Express</option>
|
|
3292
|
+
<option value="nextjs">Next.js + MUI</option>
|
|
3293
|
+
<option value="python">Python + Flask</option>
|
|
3294
|
+
</select>
|
|
3295
|
+
</label>
|
|
3296
|
+
<label>
|
|
3297
|
+
<span style="display:block;margin-bottom:4px;font-weight:500;">Component</span>
|
|
3298
|
+
<select id="genComponent" style="width:100%;">
|
|
3299
|
+
<option value="client">API Client</option>
|
|
3300
|
+
<option value="connection">MongoDB Connection</option>
|
|
3301
|
+
<option value="retrieval">RAG Retrieval</option>
|
|
3302
|
+
<option value="ingest">Document Ingest</option>
|
|
3303
|
+
<option value="search-api">Search API</option>
|
|
3304
|
+
</select>
|
|
3305
|
+
</label>
|
|
3306
|
+
</div>
|
|
3307
|
+
|
|
3308
|
+
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:12px;margin-bottom:16px;">
|
|
3309
|
+
<label>
|
|
3310
|
+
<span style="display:block;margin-bottom:4px;font-weight:500;">Model</span>
|
|
3311
|
+
<select id="genModel" style="width:100%;">
|
|
3312
|
+
<option value="voyage-4-large">voyage-4-large</option>
|
|
3313
|
+
<option value="voyage-4">voyage-4</option>
|
|
3314
|
+
<option value="voyage-4-lite">voyage-4-lite</option>
|
|
3315
|
+
<option value="voyage-3-large">voyage-3-large</option>
|
|
3316
|
+
</select>
|
|
3317
|
+
</label>
|
|
3318
|
+
<label>
|
|
3319
|
+
<span style="display:block;margin-bottom:4px;font-weight:500;">Database</span>
|
|
3320
|
+
<input type="text" id="genDb" value="myapp" style="width:100%;">
|
|
3321
|
+
</label>
|
|
3322
|
+
<label>
|
|
3323
|
+
<span style="display:block;margin-bottom:4px;font-weight:500;">Collection</span>
|
|
3324
|
+
<input type="text" id="genCollection" value="documents" style="width:100%;">
|
|
3325
|
+
</label>
|
|
3326
|
+
</div>
|
|
3327
|
+
|
|
3328
|
+
<label style="display:flex;align-items:center;gap:8px;margin-bottom:16px;">
|
|
3329
|
+
<input type="checkbox" id="genRerank" checked>
|
|
3330
|
+
<span>Include reranking</span>
|
|
3331
|
+
</label>
|
|
3332
|
+
|
|
3333
|
+
<button class="btn btn-primary" onclick="generateCode()" id="genCodeBtn">
|
|
3334
|
+
Generate Code
|
|
3335
|
+
</button>
|
|
3336
|
+
</div>
|
|
3337
|
+
</div>
|
|
3338
|
+
|
|
3339
|
+
<!-- Generated Code Output -->
|
|
3340
|
+
<div class="card" id="genOutputCard" style="margin-top:16px;display:none;">
|
|
3341
|
+
<div class="card-header" style="display:flex;justify-content:space-between;align-items:center;">
|
|
3342
|
+
<h3 id="genOutputFilename">Generated Code</h3>
|
|
3343
|
+
<button class="btn btn-outline" onclick="copyGeneratedCode()" style="padding:4px 12px;">Copy</button>
|
|
3344
|
+
</div>
|
|
3345
|
+
<div class="card-body" style="padding:0;">
|
|
3346
|
+
<pre id="genOutputCode" style="margin:0;padding:16px;overflow:auto;max-height:500px;background:var(--surface-darker);border-radius:0 0 8px 8px;font-size:13px;"></pre>
|
|
3347
|
+
</div>
|
|
3348
|
+
</div>
|
|
3349
|
+
</div>
|
|
3350
|
+
|
|
3351
|
+
<!-- Scaffold Project Panel -->
|
|
3352
|
+
<div id="scaffoldPanel" style="display:none;">
|
|
3353
|
+
<div class="card">
|
|
3354
|
+
<div class="card-header"><h3>Scaffold New Project</h3></div>
|
|
3355
|
+
<div class="card-body">
|
|
3356
|
+
<p style="margin-bottom:16px;color:var(--text-dim);">Create a complete, ready-to-run project with all files pre-configured.</p>
|
|
3357
|
+
|
|
3358
|
+
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:16px;">
|
|
3359
|
+
<label>
|
|
3360
|
+
<span style="display:block;margin-bottom:4px;font-weight:500;">Project Name</span>
|
|
3361
|
+
<input type="text" id="scaffoldName" value="my-rag-app" placeholder="my-rag-app" style="width:100%;">
|
|
3362
|
+
</label>
|
|
3363
|
+
<label>
|
|
3364
|
+
<span style="display:block;margin-bottom:4px;font-weight:500;">Target Framework</span>
|
|
3365
|
+
<select id="scaffoldTarget" onchange="updateScaffoldPreview()" style="width:100%;">
|
|
3366
|
+
<option value="vanilla">Node.js + Express (9 files)</option>
|
|
3367
|
+
<option value="nextjs">Next.js + MUI (13 files)</option>
|
|
3368
|
+
<option value="python">Python + Flask (8 files)</option>
|
|
3369
|
+
</select>
|
|
3370
|
+
</label>
|
|
3371
|
+
</div>
|
|
3372
|
+
|
|
3373
|
+
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:12px;margin-bottom:16px;">
|
|
3374
|
+
<label>
|
|
3375
|
+
<span style="display:block;margin-bottom:4px;font-weight:500;">Model</span>
|
|
3376
|
+
<select id="scaffoldModel" style="width:100%;">
|
|
3377
|
+
<option value="voyage-4-large">voyage-4-large</option>
|
|
3378
|
+
<option value="voyage-4">voyage-4</option>
|
|
3379
|
+
<option value="voyage-4-lite">voyage-4-lite</option>
|
|
3380
|
+
</select>
|
|
3381
|
+
</label>
|
|
3382
|
+
<label>
|
|
3383
|
+
<span style="display:block;margin-bottom:4px;font-weight:500;">Database</span>
|
|
3384
|
+
<input type="text" id="scaffoldDb" value="myapp" style="width:100%;">
|
|
3385
|
+
</label>
|
|
3386
|
+
<label>
|
|
3387
|
+
<span style="display:block;margin-bottom:4px;font-weight:500;">Collection</span>
|
|
3388
|
+
<input type="text" id="scaffoldCollection" value="documents" style="width:100%;">
|
|
3389
|
+
</label>
|
|
3390
|
+
</div>
|
|
3391
|
+
|
|
3392
|
+
<!-- Location Picker (Electron only) -->
|
|
3393
|
+
<div id="scaffoldLocationSection" style="margin-bottom:16px;display:none;">
|
|
3394
|
+
<span style="display:block;margin-bottom:4px;font-weight:500;">Location</span>
|
|
3395
|
+
<div style="display:flex;gap:8px;">
|
|
3396
|
+
<input type="text" id="scaffoldPath" readonly placeholder="Choose a folder..." style="flex:1;background:var(--surface-darker);">
|
|
3397
|
+
<button class="btn btn-outline" onclick="pickScaffoldDirectory()">Browse...</button>
|
|
3398
|
+
</div>
|
|
3399
|
+
</div>
|
|
3400
|
+
|
|
3401
|
+
<!-- Web Mode: Download ZIP -->
|
|
3402
|
+
<div id="scaffoldWebSection" style="margin-bottom:16px;display:none;">
|
|
3403
|
+
<div style="padding:12px;background:var(--surface-darker);border-radius:8px;margin-bottom:12px;">
|
|
3404
|
+
<p style="margin:0;font-size:14px;">📦 <strong>Web Mode</strong> — Download as ZIP or use CLI</p>
|
|
3405
|
+
<p style="margin:8px 0 0;font-size:13px;color:var(--text-dim);">
|
|
3406
|
+
CLI: <code style="padding:4px 8px;background:var(--surface);border-radius:4px;">vai scaffold <span id="scaffoldCliName">my-rag-app</span> --target <span id="scaffoldCliTarget">vanilla</span></code>
|
|
3407
|
+
</p>
|
|
3408
|
+
</div>
|
|
3409
|
+
</div>
|
|
3410
|
+
|
|
3411
|
+
<!-- Buttons: different for Electron vs Web -->
|
|
3412
|
+
<div id="scaffoldElectronButtons" style="display:none;">
|
|
3413
|
+
<button class="btn btn-primary" onclick="createScaffold()" id="scaffoldBtn" disabled>
|
|
3414
|
+
Create Project
|
|
3415
|
+
</button>
|
|
3416
|
+
</div>
|
|
3417
|
+
<div id="scaffoldWebButtons" style="display:none;">
|
|
3418
|
+
<button class="btn btn-primary" onclick="downloadScaffoldZip()" id="scaffoldDownloadBtn">
|
|
3419
|
+
<svg width="16" height="16" viewBox="0 0 16 16" style="margin-right:6px;"><path fill="currentColor" d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z"/><path fill="currentColor" d="M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708l3 3z"/></svg>
|
|
3420
|
+
Download ZIP
|
|
3421
|
+
</button>
|
|
3422
|
+
</div>
|
|
3423
|
+
</div>
|
|
3424
|
+
</div>
|
|
3425
|
+
|
|
3426
|
+
<!-- Scaffold Progress/Result -->
|
|
3427
|
+
<div class="card" id="scaffoldResultCard" style="margin-top:16px;display:none;">
|
|
3428
|
+
<div class="card-header"><h3>Project Created</h3></div>
|
|
3429
|
+
<div class="card-body">
|
|
3430
|
+
<div id="scaffoldResult"></div>
|
|
3431
|
+
</div>
|
|
3432
|
+
</div>
|
|
3433
|
+
</div>
|
|
3434
|
+
</div>
|
|
3435
|
+
|
|
3217
3436
|
<!-- ========== EXPLORE TAB ========== -->
|
|
3218
3437
|
<div class="tab-panel" id="tab-explore" role="tabpanel" aria-labelledby="tab-btn-explore" tabindex="0">
|
|
3219
3438
|
<div class="page-header">
|
|
@@ -4114,6 +4333,261 @@ function createResultItem(rank, result, maxScore, movement) {
|
|
|
4114
4333
|
}
|
|
4115
4334
|
|
|
4116
4335
|
// ── Explore ──
|
|
4336
|
+
// ── Generate & Scaffold ──
|
|
4337
|
+
|
|
4338
|
+
let generateMode = 'code';
|
|
4339
|
+
|
|
4340
|
+
function setGenerateMode(mode) {
|
|
4341
|
+
generateMode = mode;
|
|
4342
|
+
const codeBtn = document.getElementById('genModeCode');
|
|
4343
|
+
const scaffoldBtn = document.getElementById('genModeScaffold');
|
|
4344
|
+
codeBtn.classList.toggle('active', mode === 'code');
|
|
4345
|
+
codeBtn.setAttribute('aria-selected', mode === 'code');
|
|
4346
|
+
scaffoldBtn.classList.toggle('active', mode === 'scaffold');
|
|
4347
|
+
scaffoldBtn.setAttribute('aria-selected', mode === 'scaffold');
|
|
4348
|
+
document.getElementById('generateCodePanel').style.display = mode === 'code' ? 'block' : 'none';
|
|
4349
|
+
document.getElementById('scaffoldPanel').style.display = mode === 'scaffold' ? 'block' : 'none';
|
|
4350
|
+
}
|
|
4351
|
+
|
|
4352
|
+
function updateGenComponents() {
|
|
4353
|
+
const target = document.getElementById('genTarget').value;
|
|
4354
|
+
const select = document.getElementById('genComponent');
|
|
4355
|
+
const components = {
|
|
4356
|
+
vanilla: ['client', 'connection', 'retrieval', 'ingest', 'search-api'],
|
|
4357
|
+
nextjs: ['client', 'connection', 'retrieval', 'ingest', 'search-page'],
|
|
4358
|
+
python: ['client', 'connection', 'retrieval', 'ingest'],
|
|
4359
|
+
};
|
|
4360
|
+
const labels = {
|
|
4361
|
+
client: 'API Client',
|
|
4362
|
+
connection: 'MongoDB Connection',
|
|
4363
|
+
retrieval: 'RAG Retrieval',
|
|
4364
|
+
ingest: 'Document Ingest',
|
|
4365
|
+
'search-api': 'Search API',
|
|
4366
|
+
'search-page': 'Search Page (MUI)',
|
|
4367
|
+
};
|
|
4368
|
+
select.innerHTML = (components[target] || []).map(c =>
|
|
4369
|
+
`<option value="${c}">${labels[c] || c}</option>`
|
|
4370
|
+
).join('');
|
|
4371
|
+
}
|
|
4372
|
+
|
|
4373
|
+
async function generateCode() {
|
|
4374
|
+
const btn = document.getElementById('genCodeBtn');
|
|
4375
|
+
const outputCard = document.getElementById('genOutputCard');
|
|
4376
|
+
|
|
4377
|
+
btn.disabled = true;
|
|
4378
|
+
btn.textContent = 'Generating...';
|
|
4379
|
+
|
|
4380
|
+
try {
|
|
4381
|
+
const config = {
|
|
4382
|
+
model: document.getElementById('genModel').value,
|
|
4383
|
+
db: document.getElementById('genDb').value,
|
|
4384
|
+
collection: document.getElementById('genCollection').value,
|
|
4385
|
+
rerank: document.getElementById('genRerank').checked,
|
|
4386
|
+
};
|
|
4387
|
+
|
|
4388
|
+
const target = document.getElementById('genTarget').value;
|
|
4389
|
+
const component = document.getElementById('genComponent').value;
|
|
4390
|
+
|
|
4391
|
+
// Check if running in Electron with vai.generate
|
|
4392
|
+
if (window.vai && window.vai.generate) {
|
|
4393
|
+
const result = await window.vai.generate.code({ target, component, config });
|
|
4394
|
+
if (result.error) throw new Error(result.error);
|
|
4395
|
+
|
|
4396
|
+
document.getElementById('genOutputFilename').textContent = result.filename;
|
|
4397
|
+
document.getElementById('genOutputCode').textContent = result.code;
|
|
4398
|
+
outputCard.style.display = 'block';
|
|
4399
|
+
} else {
|
|
4400
|
+
// Web fallback - call the API
|
|
4401
|
+
const res = await fetch('/api/generate', {
|
|
4402
|
+
method: 'POST',
|
|
4403
|
+
headers: { 'Content-Type': 'application/json' },
|
|
4404
|
+
body: JSON.stringify({ target, component, config }),
|
|
4405
|
+
});
|
|
4406
|
+
const result = await res.json();
|
|
4407
|
+
if (result.error) throw new Error(result.error);
|
|
4408
|
+
|
|
4409
|
+
document.getElementById('genOutputFilename').textContent = result.filename || `${component}.js`;
|
|
4410
|
+
document.getElementById('genOutputCode').textContent = result.code;
|
|
4411
|
+
outputCard.style.display = 'block';
|
|
4412
|
+
}
|
|
4413
|
+
} catch (err) {
|
|
4414
|
+
alert('Error generating code: ' + err.message);
|
|
4415
|
+
} finally {
|
|
4416
|
+
btn.disabled = false;
|
|
4417
|
+
btn.textContent = 'Generate Code';
|
|
4418
|
+
}
|
|
4419
|
+
}
|
|
4420
|
+
|
|
4421
|
+
function copyGeneratedCode() {
|
|
4422
|
+
const code = document.getElementById('genOutputCode').textContent;
|
|
4423
|
+
navigator.clipboard.writeText(code);
|
|
4424
|
+
// Brief feedback
|
|
4425
|
+
const btn = event.target;
|
|
4426
|
+
btn.textContent = 'Copied!';
|
|
4427
|
+
setTimeout(() => { btn.textContent = 'Copy'; }, 1500);
|
|
4428
|
+
}
|
|
4429
|
+
|
|
4430
|
+
// Scaffold functions
|
|
4431
|
+
function initScaffold() {
|
|
4432
|
+
const isElectron = window.vai && window.vai.scaffold;
|
|
4433
|
+
|
|
4434
|
+
// Show/hide mode-specific sections
|
|
4435
|
+
document.getElementById('scaffoldLocationSection').style.display = isElectron ? 'block' : 'none';
|
|
4436
|
+
document.getElementById('scaffoldWebSection').style.display = isElectron ? 'none' : 'block';
|
|
4437
|
+
document.getElementById('scaffoldElectronButtons').style.display = isElectron ? 'block' : 'none';
|
|
4438
|
+
document.getElementById('scaffoldWebButtons').style.display = isElectron ? 'none' : 'block';
|
|
4439
|
+
|
|
4440
|
+
// Update CLI command preview on input
|
|
4441
|
+
document.getElementById('scaffoldName').addEventListener('input', updateScaffoldPreview);
|
|
4442
|
+
document.getElementById('scaffoldTarget').addEventListener('change', updateScaffoldPreview);
|
|
4443
|
+
}
|
|
4444
|
+
|
|
4445
|
+
function updateScaffoldPreview() {
|
|
4446
|
+
const name = document.getElementById('scaffoldName').value || 'my-rag-app';
|
|
4447
|
+
const target = document.getElementById('scaffoldTarget').value;
|
|
4448
|
+
document.getElementById('scaffoldCliName').textContent = name;
|
|
4449
|
+
document.getElementById('scaffoldCliTarget').textContent = target;
|
|
4450
|
+
}
|
|
4451
|
+
|
|
4452
|
+
async function downloadScaffoldZip() {
|
|
4453
|
+
const btn = document.getElementById('scaffoldDownloadBtn');
|
|
4454
|
+
const projectName = document.getElementById('scaffoldName').value || 'my-rag-app';
|
|
4455
|
+
const target = document.getElementById('scaffoldTarget').value;
|
|
4456
|
+
const config = {
|
|
4457
|
+
model: document.getElementById('scaffoldModel').value,
|
|
4458
|
+
db: document.getElementById('scaffoldDb').value,
|
|
4459
|
+
collection: document.getElementById('scaffoldCollection').value,
|
|
4460
|
+
};
|
|
4461
|
+
|
|
4462
|
+
btn.disabled = true;
|
|
4463
|
+
btn.innerHTML = '<span style="margin-right:6px;">⏳</span> Generating...';
|
|
4464
|
+
|
|
4465
|
+
try {
|
|
4466
|
+
const res = await fetch('/api/scaffold', {
|
|
4467
|
+
method: 'POST',
|
|
4468
|
+
headers: { 'Content-Type': 'application/json' },
|
|
4469
|
+
body: JSON.stringify({ projectName, target, config }),
|
|
4470
|
+
});
|
|
4471
|
+
|
|
4472
|
+
if (!res.ok) {
|
|
4473
|
+
const err = await res.json();
|
|
4474
|
+
throw new Error(err.error || 'Failed to generate scaffold');
|
|
4475
|
+
}
|
|
4476
|
+
|
|
4477
|
+
// Download the ZIP
|
|
4478
|
+
const blob = await res.blob();
|
|
4479
|
+
const url = URL.createObjectURL(blob);
|
|
4480
|
+
const a = document.createElement('a');
|
|
4481
|
+
a.href = url;
|
|
4482
|
+
a.download = `${projectName}.zip`;
|
|
4483
|
+
document.body.appendChild(a);
|
|
4484
|
+
a.click();
|
|
4485
|
+
document.body.removeChild(a);
|
|
4486
|
+
URL.revokeObjectURL(url);
|
|
4487
|
+
|
|
4488
|
+
// Show success
|
|
4489
|
+
btn.innerHTML = '<span style="margin-right:6px;">✓</span> Downloaded!';
|
|
4490
|
+
setTimeout(() => {
|
|
4491
|
+
btn.innerHTML = '<svg width="16" height="16" viewBox="0 0 16 16" style="margin-right:6px;"><path fill="currentColor" d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z"/><path fill="currentColor" d="M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708l3 3z"/></svg> Download ZIP';
|
|
4492
|
+
btn.disabled = false;
|
|
4493
|
+
}, 2000);
|
|
4494
|
+
} catch (err) {
|
|
4495
|
+
alert('Error: ' + err.message);
|
|
4496
|
+
btn.innerHTML = '<svg width="16" height="16" viewBox="0 0 16 16" style="margin-right:6px;"><path fill="currentColor" d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z"/><path fill="currentColor" d="M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708l3 3z"/></svg> Download ZIP';
|
|
4497
|
+
btn.disabled = false;
|
|
4498
|
+
}
|
|
4499
|
+
}
|
|
4500
|
+
|
|
4501
|
+
async function pickScaffoldDirectory() {
|
|
4502
|
+
if (!window.vai || !window.vai.scaffold) return;
|
|
4503
|
+
|
|
4504
|
+
const result = await window.vai.scaffold.pickDirectory();
|
|
4505
|
+
if (!result.canceled) {
|
|
4506
|
+
document.getElementById('scaffoldPath').value = result.path;
|
|
4507
|
+
document.getElementById('scaffoldBtn').disabled = false;
|
|
4508
|
+
}
|
|
4509
|
+
}
|
|
4510
|
+
|
|
4511
|
+
async function createScaffold() {
|
|
4512
|
+
if (!window.vai || !window.vai.scaffold) return;
|
|
4513
|
+
|
|
4514
|
+
const btn = document.getElementById('scaffoldBtn');
|
|
4515
|
+
const resultCard = document.getElementById('scaffoldResultCard');
|
|
4516
|
+
const resultDiv = document.getElementById('scaffoldResult');
|
|
4517
|
+
|
|
4518
|
+
const dirPath = document.getElementById('scaffoldPath').value;
|
|
4519
|
+
const projectName = document.getElementById('scaffoldName').value || 'my-rag-app';
|
|
4520
|
+
const target = document.getElementById('scaffoldTarget').value;
|
|
4521
|
+
|
|
4522
|
+
if (!dirPath) {
|
|
4523
|
+
alert('Please choose a location first.');
|
|
4524
|
+
return;
|
|
4525
|
+
}
|
|
4526
|
+
|
|
4527
|
+
btn.disabled = true;
|
|
4528
|
+
btn.textContent = 'Creating...';
|
|
4529
|
+
resultCard.style.display = 'none';
|
|
4530
|
+
|
|
4531
|
+
try {
|
|
4532
|
+
// Check if directory exists
|
|
4533
|
+
const check = await window.vai.scaffold.checkDirectory({ dirPath, projectName });
|
|
4534
|
+
if (check.exists && check.fileCount > 0) {
|
|
4535
|
+
const confirm = await window.vai.scaffold.confirmOverwrite({ projectName });
|
|
4536
|
+
if (!confirm.confirmed) {
|
|
4537
|
+
btn.disabled = false;
|
|
4538
|
+
btn.textContent = 'Create Project';
|
|
4539
|
+
return;
|
|
4540
|
+
}
|
|
4541
|
+
}
|
|
4542
|
+
|
|
4543
|
+
// Listen for progress
|
|
4544
|
+
const cleanup = window.vai.scaffold.onProgress((data) => {
|
|
4545
|
+
resultDiv.innerHTML = `<p>Creating: ${data.file} (${data.current}/${data.total})</p>`;
|
|
4546
|
+
resultCard.style.display = 'block';
|
|
4547
|
+
});
|
|
4548
|
+
|
|
4549
|
+
// Create the project
|
|
4550
|
+
const config = {
|
|
4551
|
+
model: document.getElementById('scaffoldModel').value,
|
|
4552
|
+
db: document.getElementById('scaffoldDb').value,
|
|
4553
|
+
collection: document.getElementById('scaffoldCollection').value,
|
|
4554
|
+
};
|
|
4555
|
+
|
|
4556
|
+
const result = await window.vai.scaffold.create({ dirPath, projectName, target, config });
|
|
4557
|
+
cleanup();
|
|
4558
|
+
|
|
4559
|
+
if (result.error) throw new Error(result.error);
|
|
4560
|
+
|
|
4561
|
+
// Show success
|
|
4562
|
+
resultDiv.innerHTML = `
|
|
4563
|
+
<p style="color:var(--primary);">✓ Created ${result.files.length} files in <code>${projectName}/</code></p>
|
|
4564
|
+
<div style="margin:12px 0;padding:12px;background:var(--surface-darker);border-radius:8px;">
|
|
4565
|
+
<p style="margin:0 0 8px;font-weight:500;">Next steps:</p>
|
|
4566
|
+
<code style="display:block;margin-bottom:4px;">cd ${projectName}</code>
|
|
4567
|
+
<code style="display:block;margin-bottom:4px;">cp .env.example .env</code>
|
|
4568
|
+
<code style="display:block;margin-bottom:4px;">${result.postInstall}</code>
|
|
4569
|
+
<code style="display:block;">${result.startCommand}</code>
|
|
4570
|
+
</div>
|
|
4571
|
+
<button class="btn btn-outline" onclick="window.vai.scaffold.openDirectory({ dirPath: '${result.projectDir.replace(/'/g, "\\'")}' })">
|
|
4572
|
+
Open in Finder
|
|
4573
|
+
</button>
|
|
4574
|
+
`;
|
|
4575
|
+
resultCard.style.display = 'block';
|
|
4576
|
+
|
|
4577
|
+
} catch (err) {
|
|
4578
|
+
resultDiv.innerHTML = `<p style="color:var(--error);">Error: ${err.message}</p>`;
|
|
4579
|
+
resultCard.style.display = 'block';
|
|
4580
|
+
} finally {
|
|
4581
|
+
btn.disabled = false;
|
|
4582
|
+
btn.textContent = 'Create Project';
|
|
4583
|
+
}
|
|
4584
|
+
}
|
|
4585
|
+
|
|
4586
|
+
// Initialize scaffold on load
|
|
4587
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
4588
|
+
setTimeout(initScaffold, 100);
|
|
4589
|
+
});
|
|
4590
|
+
|
|
4117
4591
|
// ── Explore: icons and tab mappings per concept ──
|
|
4118
4592
|
const CONCEPT_META = {
|
|
4119
4593
|
embeddings: { icon: '🧮', tab: 'embed' },
|
|
@@ -4139,6 +4613,10 @@ const CONCEPT_META = {
|
|
|
4139
4613
|
'modality-gap': { icon: '🕳️', tab: 'multimodal' },
|
|
4140
4614
|
'multimodal-rag': { icon: '📄', tab: 'multimodal' },
|
|
4141
4615
|
'provider-comparison': { icon: '⚖️', tab: 'explore' },
|
|
4616
|
+
// Code generation & scaffolding
|
|
4617
|
+
'code-generation': { icon: '💻', tab: 'explore' },
|
|
4618
|
+
scaffolding: { icon: '🏗️', tab: 'explore' },
|
|
4619
|
+
'eval-comparison': { icon: '📊', tab: 'benchmark' },
|
|
4142
4620
|
};
|
|
4143
4621
|
|
|
4144
4622
|
let exploreConcepts = {};
|
|
@@ -5598,7 +6076,7 @@ function initOnboarding() {
|
|
|
5598
6076
|
target: null, // welcome card, no spotlight
|
|
5599
6077
|
icon: '🚀',
|
|
5600
6078
|
title: 'Welcome to Vai',
|
|
5601
|
-
body: 'Explore embeddings,
|
|
6079
|
+
body: 'Explore embeddings, build RAG pipelines, generate production code — all from one playground.',
|
|
5602
6080
|
},
|
|
5603
6081
|
{
|
|
5604
6082
|
target: '[data-tab="settings"]',
|
|
@@ -5629,6 +6107,35 @@ function initOnboarding() {
|
|
|
5629
6107
|
body: 'Run <strong>vector search</strong> against a set of documents and optionally <strong>rerank</strong> results for higher precision. Great for building RAG pipelines.',
|
|
5630
6108
|
arrow: 'left',
|
|
5631
6109
|
},
|
|
6110
|
+
{
|
|
6111
|
+
target: '[data-tab="multimodal"]',
|
|
6112
|
+
icon: '🖼️',
|
|
6113
|
+
title: 'Multimodal',
|
|
6114
|
+
body: 'Embed <strong>images and text</strong> in the same vector space. Search images with text queries, or find similar images across your collection.',
|
|
6115
|
+
arrow: 'left',
|
|
6116
|
+
},
|
|
6117
|
+
{
|
|
6118
|
+
target: '[data-tab="generate"]',
|
|
6119
|
+
icon: '💻',
|
|
6120
|
+
title: 'Generate & Scaffold',
|
|
6121
|
+
body: '<strong>Generate code snippets</strong> for retrieval, ingestion, and API routes — or <strong>scaffold entire projects</strong> with Next.js, Express, or Flask.' +
|
|
6122
|
+
(isElectron ? ' Create projects directly on disk.' : ' Download as ZIP.'),
|
|
6123
|
+
arrow: 'left',
|
|
6124
|
+
},
|
|
6125
|
+
{
|
|
6126
|
+
target: '[data-tab="benchmark"]',
|
|
6127
|
+
icon: '⏱️',
|
|
6128
|
+
title: 'Benchmark',
|
|
6129
|
+
body: 'Compare model <strong>latency, throughput, and cost</strong>. Test embedding speeds, reranking quality, and quantization trade-offs.',
|
|
6130
|
+
arrow: 'left',
|
|
6131
|
+
},
|
|
6132
|
+
{
|
|
6133
|
+
target: '[data-tab="explore"]',
|
|
6134
|
+
icon: '💡',
|
|
6135
|
+
title: 'Explore & Learn',
|
|
6136
|
+
body: 'Dive into <strong>25 interactive explainers</strong> covering embeddings, vector search, RAG, reranking, and more. Each topic links to live demos.',
|
|
6137
|
+
arrow: 'left',
|
|
6138
|
+
},
|
|
5632
6139
|
];
|
|
5633
6140
|
|
|
5634
6141
|
let currentStep = 0;
|
|
@@ -6519,6 +7026,15 @@ function startVSI(canvas, onExit) {
|
|
|
6519
7026
|
loop();
|
|
6520
7027
|
}
|
|
6521
7028
|
|
|
7029
|
+
// ── Expose functions to global scope for onclick handlers ──
|
|
7030
|
+
window.setGenerateMode = setGenerateMode;
|
|
7031
|
+
window.copyGeneratedCode = copyGeneratedCode;
|
|
7032
|
+
window.generateCode = generateCode;
|
|
7033
|
+
window.updateGenComponents = updateGenComponents;
|
|
7034
|
+
window.createScaffold = createScaffold;
|
|
7035
|
+
window.pickScaffoldDirectory = pickScaffoldDirectory;
|
|
7036
|
+
window.downloadScaffoldZip = downloadScaffoldZip;
|
|
7037
|
+
|
|
6522
7038
|
// ── Start ──
|
|
6523
7039
|
init();
|
|
6524
7040
|
})();
|
|
@@ -6607,5 +7123,194 @@ init();
|
|
|
6607
7123
|
<canvas id="vsiCanvas" width="600" height="500" style="border:1px solid #3D4F58;border-radius:8px;image-rendering:pixelated;"></canvas>
|
|
6608
7124
|
<div style="color:#889397;font-size:11px;font-family:monospace;text-align:center;">← → move | SPACE shoot | ESC exit</div>
|
|
6609
7125
|
</div>
|
|
7126
|
+
|
|
7127
|
+
<!-- 🐛 Bug Reporter -->
|
|
7128
|
+
<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)}
|
|
7131
|
+
.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
|
+
.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
|
+
.bug-reporter-header{display:flex;align-items:center;gap:12px;padding:16px 20px;border-bottom:1px solid var(--border)}
|
|
7134
|
+
.bug-reporter-header h2{flex:1;margin:0;font-size:18px;font-weight:600;color:var(--accent-text)}
|
|
7135
|
+
.bug-reporter-header .close-btn{background:none;border:none;color:var(--text-muted);font-size:24px;cursor:pointer;padding:4px 8px;border-radius:4px}
|
|
7136
|
+
.bug-reporter-header .close-btn:hover{background:rgba(255,255,255,0.1);color:var(--text)}
|
|
7137
|
+
.bug-reporter-form{padding:20px}
|
|
7138
|
+
.bug-reporter-form .form-group{margin-bottom:16px}
|
|
7139
|
+
.bug-reporter-form label{display:block;font-size:13px;font-weight:500;color:var(--text-muted);margin-bottom:6px}
|
|
7140
|
+
.bug-reporter-form input,.bug-reporter-form textarea{width:100%;padding:10px 12px;border:1px solid var(--border);border-radius:8px;background:var(--bg-input);color:var(--text);font-size:14px;font-family:inherit}
|
|
7141
|
+
.bug-reporter-form input:focus,.bug-reporter-form textarea:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-glow)}
|
|
7142
|
+
.bug-reporter-form textarea{resize:vertical;min-height:80px}
|
|
7143
|
+
.bug-env-info{padding:12px;background:var(--accent-glow);border-radius:8px;font-size:12px;margin-bottom:16px;color:var(--accent)}
|
|
7144
|
+
.bug-env-info code{color:var(--text-muted);font-size:11px}
|
|
7145
|
+
.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}
|
|
7146
|
+
.bug-actions{display:flex;gap:12px}
|
|
7147
|
+
.bug-actions button{flex:1;padding:12px 16px;border-radius:8px;font-size:14px;font-weight:500;cursor:pointer;transition:all .2s;border:none}
|
|
7148
|
+
.bug-actions .primary{background:linear-gradient(135deg,var(--accent),#00c853);color:#000}
|
|
7149
|
+
.bug-actions .primary:hover{box-shadow:0 4px 12px rgba(0,237,100,0.4)}
|
|
7150
|
+
.bug-actions .primary:disabled{opacity:0.6;cursor:not-allowed}
|
|
7151
|
+
.bug-actions .secondary{background:rgba(255,255,255,0.1);color:var(--text);border:1px solid var(--border)}
|
|
7152
|
+
.bug-actions .secondary:hover{background:rgba(255,255,255,0.15)}
|
|
7153
|
+
.bug-success{padding:40px 20px;text-align:center}
|
|
7154
|
+
.bug-success .icon{width:60px;height:60px;border-radius:50%;background:linear-gradient(135deg,var(--accent),#00c853);color:#000;font-size:32px;display:flex;align-items:center;justify-content:center;margin:0 auto 20px}
|
|
7155
|
+
.bug-success h3{margin:0 0 12px;color:var(--accent-text);font-size:20px}
|
|
7156
|
+
.bug-success p{margin:8px 0;color:var(--text-muted)}
|
|
7157
|
+
.bug-success code{background:rgba(255,255,255,0.1);padding:4px 8px;border-radius:4px;font-size:12px;color:var(--accent)}
|
|
7158
|
+
</style>
|
|
7159
|
+
|
|
7160
|
+
<button class="bug-floating-button" id="bugButton" title="Report a Bug">🐛</button>
|
|
7161
|
+
|
|
7162
|
+
<div class="bug-reporter-overlay" id="bugOverlay" style="display:none">
|
|
7163
|
+
<div class="bug-reporter-modal">
|
|
7164
|
+
<div class="bug-reporter-header">
|
|
7165
|
+
<span style="font-size:28px">🐛</span>
|
|
7166
|
+
<h2>Report a Bug</h2>
|
|
7167
|
+
<button class="close-btn" id="bugClose">×</button>
|
|
7168
|
+
</div>
|
|
7169
|
+
<div class="bug-reporter-form" id="bugForm">
|
|
7170
|
+
<div class="form-group">
|
|
7171
|
+
<label>Title *</label>
|
|
7172
|
+
<input type="text" id="bugTitle" placeholder="Brief description of the bug" maxlength="200">
|
|
7173
|
+
</div>
|
|
7174
|
+
<div class="form-group">
|
|
7175
|
+
<label>Description *</label>
|
|
7176
|
+
<textarea id="bugDescription" placeholder="What happened? What did you expect?" rows="4" maxlength="5000"></textarea>
|
|
7177
|
+
</div>
|
|
7178
|
+
<div class="form-group">
|
|
7179
|
+
<label>Steps to Reproduce</label>
|
|
7180
|
+
<textarea id="bugSteps" placeholder="1. Go to... 2. Click on... 3. See error" rows="3" maxlength="2000"></textarea>
|
|
7181
|
+
</div>
|
|
7182
|
+
<div class="form-group">
|
|
7183
|
+
<label>Email (optional, for follow-up)</label>
|
|
7184
|
+
<input type="email" id="bugEmail" placeholder="your@email.com">
|
|
7185
|
+
</div>
|
|
7186
|
+
<div class="bug-env-info">
|
|
7187
|
+
<span>📋 Environment will be included:</span><br>
|
|
7188
|
+
<code id="bugEnvInfo">Loading...</code>
|
|
7189
|
+
</div>
|
|
7190
|
+
<div class="bug-error" id="bugError" style="display:none"></div>
|
|
7191
|
+
<div class="bug-actions">
|
|
7192
|
+
<button class="primary" id="bugSubmit">Submit Bug Report</button>
|
|
7193
|
+
<button class="secondary" id="bugGithub">Open GitHub Issue</button>
|
|
7194
|
+
</div>
|
|
7195
|
+
</div>
|
|
7196
|
+
<div class="bug-success" id="bugSuccess" style="display:none">
|
|
7197
|
+
<div class="icon">✓</div>
|
|
7198
|
+
<h3>Bug Reported!</h3>
|
|
7199
|
+
<p>Bug ID: <code id="bugResultId"></code></p>
|
|
7200
|
+
<p>Thank you for helping improve Vai!</p>
|
|
7201
|
+
<div class="bug-actions" style="justify-content:center;margin-top:24px">
|
|
7202
|
+
<button class="primary" id="bugSuccessClose">Close</button>
|
|
7203
|
+
</div>
|
|
7204
|
+
</div>
|
|
7205
|
+
</div>
|
|
7206
|
+
</div>
|
|
7207
|
+
|
|
7208
|
+
<script>
|
|
7209
|
+
(function() {
|
|
7210
|
+
const BUG_API = 'https://vai.mlynn.org/api/bugs';
|
|
7211
|
+
const GITHUB_URL = 'https://github.com/mrlynn/voyageai-cli/issues/new';
|
|
7212
|
+
|
|
7213
|
+
function getEnv() {
|
|
7214
|
+
const env = { platform: navigator.platform, source: 'desktop-app' };
|
|
7215
|
+
if (window.electronAPI) {
|
|
7216
|
+
env.appVersion = window.electronAPI.appVersion || 'unknown';
|
|
7217
|
+
env.cliVersion = window.electronAPI.cliVersion || 'unknown';
|
|
7218
|
+
env.electronVersion = window.electronAPI.electronVersion || 'unknown';
|
|
7219
|
+
}
|
|
7220
|
+
env.currentScreen = document.querySelector('.tab-btn.active')?.textContent || 'unknown';
|
|
7221
|
+
return env;
|
|
7222
|
+
}
|
|
7223
|
+
|
|
7224
|
+
function showBugReporter() {
|
|
7225
|
+
const env = getEnv();
|
|
7226
|
+
document.getElementById('bugEnvInfo').textContent =
|
|
7227
|
+
`${env.platform} • App v${env.appVersion || '?'} • ${env.currentScreen}`;
|
|
7228
|
+
document.getElementById('bugOverlay').style.display = 'flex';
|
|
7229
|
+
document.getElementById('bugForm').style.display = 'block';
|
|
7230
|
+
document.getElementById('bugSuccess').style.display = 'none';
|
|
7231
|
+
document.getElementById('bugError').style.display = 'none';
|
|
7232
|
+
document.getElementById('bugTitle').value = '';
|
|
7233
|
+
document.getElementById('bugDescription').value = '';
|
|
7234
|
+
document.getElementById('bugSteps').value = '';
|
|
7235
|
+
document.getElementById('bugEmail').value = '';
|
|
7236
|
+
}
|
|
7237
|
+
|
|
7238
|
+
function hideBugReporter() {
|
|
7239
|
+
document.getElementById('bugOverlay').style.display = 'none';
|
|
7240
|
+
}
|
|
7241
|
+
|
|
7242
|
+
async function submitBug() {
|
|
7243
|
+
const title = document.getElementById('bugTitle').value.trim();
|
|
7244
|
+
const description = document.getElementById('bugDescription').value.trim();
|
|
7245
|
+
const steps = document.getElementById('bugSteps').value.trim();
|
|
7246
|
+
const email = document.getElementById('bugEmail').value.trim();
|
|
7247
|
+
|
|
7248
|
+
if (!title || title.length < 5) {
|
|
7249
|
+
document.getElementById('bugError').textContent = 'Title is required (min 5 characters)';
|
|
7250
|
+
document.getElementById('bugError').style.display = 'block';
|
|
7251
|
+
return;
|
|
7252
|
+
}
|
|
7253
|
+
if (!description || description.length < 10) {
|
|
7254
|
+
document.getElementById('bugError').textContent = 'Description is required (min 10 characters)';
|
|
7255
|
+
document.getElementById('bugError').style.display = 'block';
|
|
7256
|
+
return;
|
|
7257
|
+
}
|
|
7258
|
+
|
|
7259
|
+
document.getElementById('bugSubmit').disabled = true;
|
|
7260
|
+
document.getElementById('bugSubmit').textContent = 'Submitting...';
|
|
7261
|
+
document.getElementById('bugError').style.display = 'none';
|
|
7262
|
+
|
|
7263
|
+
try {
|
|
7264
|
+
const env = getEnv();
|
|
7265
|
+
const res = await fetch(BUG_API, {
|
|
7266
|
+
method: 'POST',
|
|
7267
|
+
headers: { 'Content-Type': 'application/json' },
|
|
7268
|
+
body: JSON.stringify({
|
|
7269
|
+
title, description, stepsToReproduce: steps || null, email: email || null,
|
|
7270
|
+
...env
|
|
7271
|
+
})
|
|
7272
|
+
});
|
|
7273
|
+
|
|
7274
|
+
if (!res.ok) {
|
|
7275
|
+
const err = await res.json().catch(() => ({}));
|
|
7276
|
+
throw new Error(err.error || `HTTP ${res.status}`);
|
|
7277
|
+
}
|
|
7278
|
+
|
|
7279
|
+
const data = await res.json();
|
|
7280
|
+
document.getElementById('bugResultId').textContent = data.bugId;
|
|
7281
|
+
document.getElementById('bugForm').style.display = 'none';
|
|
7282
|
+
document.getElementById('bugSuccess').style.display = 'block';
|
|
7283
|
+
} catch (err) {
|
|
7284
|
+
document.getElementById('bugError').textContent = err.message;
|
|
7285
|
+
document.getElementById('bugError').style.display = 'block';
|
|
7286
|
+
} finally {
|
|
7287
|
+
document.getElementById('bugSubmit').disabled = false;
|
|
7288
|
+
document.getElementById('bugSubmit').textContent = 'Submit Bug Report';
|
|
7289
|
+
}
|
|
7290
|
+
}
|
|
7291
|
+
|
|
7292
|
+
function openGithub() {
|
|
7293
|
+
const title = document.getElementById('bugTitle').value.trim() || 'Bug Report';
|
|
7294
|
+
const description = document.getElementById('bugDescription').value.trim();
|
|
7295
|
+
const steps = document.getElementById('bugSteps').value.trim();
|
|
7296
|
+
const env = getEnv();
|
|
7297
|
+
|
|
7298
|
+
const body = `## Description\n${description}\n\n## Steps to Reproduce\n${steps || '1. \\n2. \\n3. '}\n\n## Environment\n- **App Version:** ${env.appVersion || 'N/A'}\n- **Platform:** ${env.platform}\n- **Screen:** ${env.currentScreen}`;
|
|
7299
|
+
const url = `${GITHUB_URL}?title=${encodeURIComponent('[Bug] ' + title)}&body=${encodeURIComponent(body)}&labels=bug`;
|
|
7300
|
+
window.open(url, '_blank');
|
|
7301
|
+
}
|
|
7302
|
+
|
|
7303
|
+
// Event listeners
|
|
7304
|
+
document.getElementById('bugButton').addEventListener('click', showBugReporter);
|
|
7305
|
+
document.getElementById('bugClose').addEventListener('click', hideBugReporter);
|
|
7306
|
+
document.getElementById('bugOverlay').addEventListener('click', (e) => {
|
|
7307
|
+
if (e.target.id === 'bugOverlay') hideBugReporter();
|
|
7308
|
+
});
|
|
7309
|
+
document.getElementById('bugSubmit').addEventListener('click', submitBug);
|
|
7310
|
+
document.getElementById('bugGithub').addEventListener('click', openGithub);
|
|
7311
|
+
document.getElementById('bugSuccessClose').addEventListener('click', hideBugReporter);
|
|
7312
|
+
})();
|
|
7313
|
+
</script>
|
|
7314
|
+
|
|
6610
7315
|
</body>
|
|
6611
7316
|
</html>
|