voyageai-cli 1.28.0 → 1.30.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/README.md +82 -8
- package/package.json +2 -1
- package/src/commands/app.js +15 -0
- package/src/commands/benchmark.js +22 -8
- package/src/commands/chat.js +18 -0
- package/src/commands/chunk.js +10 -0
- package/src/commands/demo.js +4 -0
- package/src/commands/embed.js +13 -0
- package/src/commands/estimate.js +3 -0
- package/src/commands/eval.js +6 -0
- package/src/commands/explain.js +2 -0
- package/src/commands/generate.js +2 -0
- package/src/commands/ingest.js +4 -0
- package/src/commands/init.js +2 -0
- package/src/commands/mcp-server.js +2 -0
- package/src/commands/models.js +2 -0
- package/src/commands/ping.js +7 -0
- package/src/commands/pipeline.js +15 -0
- package/src/commands/playground.js +685 -8
- package/src/commands/query.js +16 -0
- package/src/commands/rerank.js +12 -0
- package/src/commands/scaffold.js +2 -0
- package/src/commands/search.js +11 -0
- package/src/commands/similarity.js +9 -0
- package/src/commands/store.js +4 -0
- package/src/commands/workflow.js +702 -13
- package/src/lib/capability-report.js +134 -0
- package/src/lib/chat.js +32 -1
- package/src/lib/config.js +2 -0
- package/src/lib/cost-display.js +107 -0
- package/src/lib/explanations.js +94 -0
- package/src/lib/llm.js +125 -18
- package/src/lib/npm-utils.js +265 -0
- package/src/lib/quality-audit.js +71 -0
- package/src/lib/security/blocked-domains.json +17 -0
- package/src/lib/security-audit.js +198 -0
- package/src/lib/telemetry.js +23 -1
- package/src/lib/workflow-registry.js +416 -0
- package/src/lib/workflow-scaffold.js +380 -0
- package/src/lib/workflow-test-runner.js +208 -0
- package/src/lib/workflow.js +559 -7
- package/src/playground/announcements.md +80 -0
- package/src/playground/assets/announcements/appstore.jpg +0 -0
- package/src/playground/assets/announcements/circuits.jpg +0 -0
- package/src/playground/assets/announcements/csvingest.jpg +0 -0
- package/src/playground/assets/announcements/green-wave.jpg +0 -0
- package/src/playground/help/workflow-nodes.js +472 -0
- package/src/playground/icons/V.png +0 -0
- package/src/playground/index.html +3634 -226
- package/src/workflows/consistency-check.json +4 -0
- package/src/workflows/cost-analysis.json +4 -0
- package/src/workflows/enrich-and-ingest.json +56 -0
- package/src/workflows/intelligent-ingest.json +66 -0
- package/src/workflows/kb-health-report.json +45 -0
- package/src/workflows/multi-collection-search.json +4 -0
- package/src/workflows/research-and-summarize.json +4 -0
- package/src/workflows/search-with-fallback.json +66 -0
- package/src/workflows/smart-ingest.json +4 -0
|
@@ -350,6 +350,79 @@ body {
|
|
|
350
350
|
flex-direction: column;
|
|
351
351
|
overflow: hidden;
|
|
352
352
|
z-index: 100;
|
|
353
|
+
transition: width 0.25s cubic-bezier(0.4, 0, 0.2, 1), min-width 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
|
354
|
+
}
|
|
355
|
+
.sidebar.collapsed {
|
|
356
|
+
width: 56px;
|
|
357
|
+
min-width: 56px;
|
|
358
|
+
}
|
|
359
|
+
.sidebar.collapsed .sidebar-title,
|
|
360
|
+
.sidebar.collapsed .sidebar-nav-label,
|
|
361
|
+
.sidebar.collapsed .sidebar-nav-divider,
|
|
362
|
+
.sidebar.collapsed .status-label,
|
|
363
|
+
.sidebar.collapsed .sidebar-bug-label,
|
|
364
|
+
.sidebar.collapsed .sidebar-docs-link span,
|
|
365
|
+
.sidebar.collapsed .sidebar-model-group,
|
|
366
|
+
.sidebar.collapsed .sidebar-footer > div:last-child { display: none; }
|
|
367
|
+
.sidebar.collapsed .sidebar-footer { overflow: hidden; padding: 8px 0; align-items: center; border-top: none; }
|
|
368
|
+
.sidebar.collapsed .sidebar-footer > div:first-child { justify-content: center; }
|
|
369
|
+
.sidebar.collapsed .sidebar-footer .theme-toggle { display: none; }
|
|
370
|
+
.sidebar.collapsed #appVersionLabel { display: none; }
|
|
371
|
+
.sidebar.collapsed .sidebar-docs-link { display: none; }
|
|
372
|
+
.sidebar.collapsed .tab-btn {
|
|
373
|
+
justify-content: center;
|
|
374
|
+
padding: 10px;
|
|
375
|
+
border-left-width: 2px;
|
|
376
|
+
}
|
|
377
|
+
.sidebar.collapsed .tab-btn span:not(.tab-btn-icon) { display: none; }
|
|
378
|
+
.sidebar.collapsed .sidebar-settings-btn { display: none; }
|
|
379
|
+
.sidebar.collapsed .sidebar-drag-region { justify-content: center; padding: 0; padding-top: 52px; }
|
|
380
|
+
body:not(.is-electron) .sidebar.collapsed .sidebar-drag-region { padding-top: 16px; padding-bottom: 12px; }
|
|
381
|
+
|
|
382
|
+
/* Sidebar collapse toggle */
|
|
383
|
+
.sidebar-collapse-btn {
|
|
384
|
+
-webkit-app-region: no-drag;
|
|
385
|
+
background: none;
|
|
386
|
+
border: none;
|
|
387
|
+
cursor: pointer;
|
|
388
|
+
color: var(--text-muted);
|
|
389
|
+
padding: 4px;
|
|
390
|
+
border-radius: 4px;
|
|
391
|
+
display: flex;
|
|
392
|
+
align-items: center;
|
|
393
|
+
justify-content: center;
|
|
394
|
+
transition: all 0.15s;
|
|
395
|
+
flex-shrink: 0;
|
|
396
|
+
font-size: 14px;
|
|
397
|
+
line-height: 1;
|
|
398
|
+
}
|
|
399
|
+
.sidebar-collapse-btn:hover { color: var(--text); background: rgba(255,255,255,0.06); }
|
|
400
|
+
[data-theme="light"] .sidebar-collapse-btn:hover { background: rgba(0,0,0,0.04); }
|
|
401
|
+
.sidebar.collapsed .sidebar-collapse-btn { transform: rotate(180deg); }
|
|
402
|
+
|
|
403
|
+
/* Sidebar tooltip for collapsed mode */
|
|
404
|
+
.sidebar.collapsed .tab-btn {
|
|
405
|
+
position: relative;
|
|
406
|
+
}
|
|
407
|
+
.sidebar.collapsed .tab-btn::after {
|
|
408
|
+
content: attr(data-tooltip);
|
|
409
|
+
position: absolute;
|
|
410
|
+
left: calc(100% + 8px);
|
|
411
|
+
top: 50%;
|
|
412
|
+
transform: translateY(-50%);
|
|
413
|
+
background: #2d333b;
|
|
414
|
+
color: #e6edf3;
|
|
415
|
+
font-size: 12px;
|
|
416
|
+
padding: 4px 8px;
|
|
417
|
+
border-radius: 4px;
|
|
418
|
+
white-space: nowrap;
|
|
419
|
+
pointer-events: none;
|
|
420
|
+
opacity: 0;
|
|
421
|
+
transition: opacity 0.15s 0.15s;
|
|
422
|
+
z-index: 1000;
|
|
423
|
+
}
|
|
424
|
+
.sidebar.collapsed .tab-btn:hover::after {
|
|
425
|
+
opacity: 1;
|
|
353
426
|
}
|
|
354
427
|
|
|
355
428
|
.sidebar-drag-region {
|
|
@@ -484,6 +557,7 @@ body:not(.is-electron) .sidebar-drag-region {
|
|
|
484
557
|
|
|
485
558
|
.sidebar-footer {
|
|
486
559
|
padding: 12px 16px;
|
|
560
|
+
padding-bottom: 56px; /* Clear the fixed cost status bar */
|
|
487
561
|
border-top: 1px solid var(--border);
|
|
488
562
|
display: flex;
|
|
489
563
|
flex-direction: column;
|
|
@@ -1779,6 +1853,43 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
1779
1853
|
margin-bottom: 8px;
|
|
1780
1854
|
}
|
|
1781
1855
|
.about-text { font-size: 14px; line-height: 1.8; color: var(--text); }
|
|
1856
|
+
.about-changelog { font-size: 13px; line-height: 1.6; }
|
|
1857
|
+
.about-changelog details {
|
|
1858
|
+
border-bottom: 1px solid rgba(255,255,255,0.06);
|
|
1859
|
+
padding: 0;
|
|
1860
|
+
}
|
|
1861
|
+
.about-changelog details:last-child { border-bottom: none; }
|
|
1862
|
+
.about-changelog summary {
|
|
1863
|
+
padding: 10px 0;
|
|
1864
|
+
cursor: pointer;
|
|
1865
|
+
color: var(--text);
|
|
1866
|
+
list-style: none;
|
|
1867
|
+
display: flex;
|
|
1868
|
+
align-items: center;
|
|
1869
|
+
gap: 8px;
|
|
1870
|
+
}
|
|
1871
|
+
.about-changelog summary::before {
|
|
1872
|
+
content: '';
|
|
1873
|
+
display: inline-block;
|
|
1874
|
+
width: 5px; height: 5px;
|
|
1875
|
+
border-right: 1.5px solid var(--accent, #00D4AA);
|
|
1876
|
+
border-bottom: 1.5px solid var(--accent, #00D4AA);
|
|
1877
|
+
transform: rotate(-45deg);
|
|
1878
|
+
transition: transform 0.15s ease;
|
|
1879
|
+
flex-shrink: 0;
|
|
1880
|
+
}
|
|
1881
|
+
.about-changelog details[open] > summary::before {
|
|
1882
|
+
transform: rotate(45deg);
|
|
1883
|
+
}
|
|
1884
|
+
.about-changelog summary::-webkit-details-marker { display: none; }
|
|
1885
|
+
.about-changelog summary:hover { color: var(--accent-text); }
|
|
1886
|
+
.about-changelog p {
|
|
1887
|
+
margin: 0 0 10px 13px;
|
|
1888
|
+
color: var(--text-muted);
|
|
1889
|
+
font-size: 12.5px;
|
|
1890
|
+
line-height: 1.6;
|
|
1891
|
+
}
|
|
1892
|
+
[data-theme="light"] .about-changelog details { border-color: rgba(0,0,0,0.08); }
|
|
1782
1893
|
.about-text a { color: var(--blue); text-decoration: none; }
|
|
1783
1894
|
.about-text a:hover { text-decoration: underline; }
|
|
1784
1895
|
.about-disclaimer {
|
|
@@ -2531,7 +2642,10 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
2531
2642
|
.chat-thinking .thinking-icon {
|
|
2532
2643
|
font-size: 14px;
|
|
2533
2644
|
line-height: 1;
|
|
2645
|
+
display: flex;
|
|
2646
|
+
align-items: center;
|
|
2534
2647
|
}
|
|
2648
|
+
.chat-thinking .thinking-icon svg { color: var(--accent); }
|
|
2535
2649
|
.chat-thinking .thinking-label {
|
|
2536
2650
|
font-weight: 500;
|
|
2537
2651
|
}
|
|
@@ -2589,6 +2703,7 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
2589
2703
|
background: var(--bg-input, #112733);
|
|
2590
2704
|
border: 1px solid var(--border);
|
|
2591
2705
|
}
|
|
2706
|
+
.thinking-step-icon svg { color: var(--text-dim); }
|
|
2592
2707
|
.thinking-step.active .thinking-step-icon {
|
|
2593
2708
|
border-color: var(--accent);
|
|
2594
2709
|
animation: thinkingPulse 1.2s ease-in-out infinite;
|
|
@@ -2983,10 +3098,15 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
2983
3098
|
min-height: 0;
|
|
2984
3099
|
}
|
|
2985
3100
|
.wf-library {
|
|
2986
|
-
width: 220px; min-width:
|
|
3101
|
+
width: 220px; min-width: 0; flex-shrink: 0;
|
|
2987
3102
|
border-right: 1px solid var(--border);
|
|
2988
3103
|
display: flex; flex-direction: column;
|
|
2989
3104
|
background: var(--bg);
|
|
3105
|
+
transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
3106
|
+
overflow: hidden;
|
|
3107
|
+
}
|
|
3108
|
+
.wf-library.collapsed {
|
|
3109
|
+
width: 0; border-right: none;
|
|
2990
3110
|
}
|
|
2991
3111
|
.wf-library-header {
|
|
2992
3112
|
padding: 14px 16px 10px; font-weight: 700; font-size: 13px;
|
|
@@ -3049,6 +3169,88 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
3049
3169
|
font-size: 10px; font-weight: 600; color: var(--text-muted);
|
|
3050
3170
|
padding: 8px 12px 2px; text-transform: uppercase; letter-spacing: 0.5px;
|
|
3051
3171
|
}
|
|
3172
|
+
/* ── Library Search ── */
|
|
3173
|
+
.wf-library-search-wrap {
|
|
3174
|
+
padding: 8px 12px 4px; position: relative;
|
|
3175
|
+
}
|
|
3176
|
+
.wf-library-search-wrap input {
|
|
3177
|
+
width: 100%; padding: 6px 28px 6px 10px; border-radius: 6px;
|
|
3178
|
+
border: 1px solid var(--border); background: var(--bg-card);
|
|
3179
|
+
color: var(--text); font-size: 12px; outline: none;
|
|
3180
|
+
box-sizing: border-box;
|
|
3181
|
+
transition: border-color 0.15s;
|
|
3182
|
+
}
|
|
3183
|
+
.wf-library-search-wrap input:focus {
|
|
3184
|
+
border-color: var(--accent);
|
|
3185
|
+
}
|
|
3186
|
+
.wf-library-search-wrap input::placeholder {
|
|
3187
|
+
color: var(--text-muted);
|
|
3188
|
+
}
|
|
3189
|
+
.wf-library-search-clear {
|
|
3190
|
+
position: absolute; right: 18px; top: 50%; transform: translateY(-50%);
|
|
3191
|
+
background: none; border: none; color: var(--text-muted); cursor: pointer;
|
|
3192
|
+
font-size: 14px; padding: 2px 4px; line-height: 1; display: none;
|
|
3193
|
+
}
|
|
3194
|
+
.wf-library-search-clear:hover { color: var(--text); }
|
|
3195
|
+
/* ── Collapsible Library Sections ── */
|
|
3196
|
+
.wf-lib-section-header {
|
|
3197
|
+
display: flex; align-items: center; gap: 6px;
|
|
3198
|
+
width: 100%; padding: 8px 12px; border: none; background: none;
|
|
3199
|
+
color: var(--text-muted); font-size: 10px; font-weight: 600;
|
|
3200
|
+
text-transform: uppercase; letter-spacing: 1.2px; cursor: pointer;
|
|
3201
|
+
transition: color 0.15s;
|
|
3202
|
+
}
|
|
3203
|
+
.wf-lib-section-header:hover { color: var(--text); }
|
|
3204
|
+
.wf-lib-section-header .wf-chevron {
|
|
3205
|
+
font-size: 10px; transition: transform 0.2s; display: inline-block;
|
|
3206
|
+
}
|
|
3207
|
+
.wf-lib-section-header.expanded .wf-chevron { transform: rotate(90deg); }
|
|
3208
|
+
.wf-lib-section-header .wf-section-count {
|
|
3209
|
+
color: var(--text-muted); font-size: 10px; font-weight: 400;
|
|
3210
|
+
}
|
|
3211
|
+
.wf-lib-section-body {
|
|
3212
|
+
overflow: hidden; transition: max-height 0.2s ease-out;
|
|
3213
|
+
}
|
|
3214
|
+
/* ── Properties Accordion ── */
|
|
3215
|
+
.wf-accordion-header {
|
|
3216
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
3217
|
+
padding: 8px 12px; cursor: pointer; border-bottom: 1px solid var(--border);
|
|
3218
|
+
transition: background-color 0.15s ease; user-select: none;
|
|
3219
|
+
}
|
|
3220
|
+
.wf-accordion-header.expanded {
|
|
3221
|
+
background: rgba(0,212,170,0.05);
|
|
3222
|
+
}
|
|
3223
|
+
.wf-accordion-header .wf-acc-left {
|
|
3224
|
+
display: flex; align-items: center; gap: 6px;
|
|
3225
|
+
font-size: 12px; font-weight: 600; letter-spacing: 0.5px;
|
|
3226
|
+
color: var(--text);
|
|
3227
|
+
}
|
|
3228
|
+
.wf-accordion-header .wf-acc-left .wf-chevron {
|
|
3229
|
+
font-size: 10px; transition: transform 0.2s; display: inline-block;
|
|
3230
|
+
}
|
|
3231
|
+
.wf-accordion-header.expanded .wf-acc-left .wf-chevron { transform: rotate(90deg); }
|
|
3232
|
+
.wf-accordion-header .wf-acc-summary {
|
|
3233
|
+
font-size: 10px; color: var(--text-muted); font-weight: 400;
|
|
3234
|
+
}
|
|
3235
|
+
.wf-accordion-body {
|
|
3236
|
+
overflow: hidden; transition: max-height 0.2s ease-out;
|
|
3237
|
+
}
|
|
3238
|
+
.wf-accordion-body-inner {
|
|
3239
|
+
padding: 12px;
|
|
3240
|
+
}
|
|
3241
|
+
.wf-step-row {
|
|
3242
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
3243
|
+
padding: 4px 0; font-size: 12px;
|
|
3244
|
+
}
|
|
3245
|
+
.wf-step-dot {
|
|
3246
|
+
width: 8px; height: 8px; border-radius: 50%; display: inline-block;
|
|
3247
|
+
margin-right: 8px; flex-shrink: 0;
|
|
3248
|
+
}
|
|
3249
|
+
.wf-step-name { flex: 1; color: var(--text); }
|
|
3250
|
+
.wf-step-time {
|
|
3251
|
+
font-family: 'SF Mono', 'Fira Code', monospace; font-size: 10px;
|
|
3252
|
+
color: var(--text-muted); margin-left: 8px;
|
|
3253
|
+
}
|
|
3052
3254
|
.wf-canvas-area {
|
|
3053
3255
|
flex: 1; position: relative; overflow: hidden;
|
|
3054
3256
|
background: var(--bg);
|
|
@@ -3056,8 +3258,8 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
3056
3258
|
background-size: 20px 20px;
|
|
3057
3259
|
}
|
|
3058
3260
|
.wf-canvas-toolbar {
|
|
3059
|
-
position: absolute; top: 12px; right: 12px; z-index: 10;
|
|
3060
|
-
display: flex; gap: 4px;
|
|
3261
|
+
position: absolute; top: 12px; left: 12px; right: 12px; z-index: 10;
|
|
3262
|
+
display: flex; gap: 4px; align-items: center;
|
|
3061
3263
|
}
|
|
3062
3264
|
.wf-canvas-toolbar button {
|
|
3063
3265
|
width: 32px; height: 32px; border-radius: 8px;
|
|
@@ -3127,6 +3329,339 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
3127
3329
|
.wf-palette-item:active { cursor: grabbing; }
|
|
3128
3330
|
.wf-palette-icon { width: 20px; text-align: center; display: flex; align-items: center; justify-content: center; }
|
|
3129
3331
|
.wf-palette-label { font-weight: 500; }
|
|
3332
|
+
|
|
3333
|
+
/* ── Workflow Node Help ── */
|
|
3334
|
+
.wf-palette-item { position: relative; }
|
|
3335
|
+
.wf-help-trigger {
|
|
3336
|
+
position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
|
|
3337
|
+
width: 20px; height: 20px; border-radius: 50%;
|
|
3338
|
+
border: 1px solid var(--border); background: var(--bg);
|
|
3339
|
+
color: var(--text-muted); cursor: pointer;
|
|
3340
|
+
display: flex; align-items: center; justify-content: center;
|
|
3341
|
+
opacity: 0; transition: opacity 0.15s ease, background 0.15s ease, color 0.15s ease;
|
|
3342
|
+
padding: 0; z-index: 2;
|
|
3343
|
+
}
|
|
3344
|
+
.wf-palette-item:hover .wf-help-trigger { opacity: 1; }
|
|
3345
|
+
.wf-help-trigger:hover { background: var(--accent); color: #fff; border-color: var(--accent); }
|
|
3346
|
+
.wf-help-trigger svg { width: 12px; height: 12px; }
|
|
3347
|
+
|
|
3348
|
+
.wf-inspector-help-btn {
|
|
3349
|
+
display: inline-flex; align-items: center; gap: 4px;
|
|
3350
|
+
padding: 3px 8px; border-radius: 4px;
|
|
3351
|
+
border: 1px solid var(--border); background: var(--bg);
|
|
3352
|
+
color: var(--text-muted); font-size: 11px; cursor: pointer;
|
|
3353
|
+
transition: background 0.15s ease, color 0.15s ease;
|
|
3354
|
+
margin-left: 8px; vertical-align: middle;
|
|
3355
|
+
}
|
|
3356
|
+
.wf-inspector-help-btn:hover { background: var(--accent); color: #fff; border-color: var(--accent); }
|
|
3357
|
+
.wf-inspector-help-btn svg { width: 12px; height: 12px; }
|
|
3358
|
+
|
|
3359
|
+
.wf-help-modal-backdrop {
|
|
3360
|
+
position: fixed; inset: 0; z-index: 10000;
|
|
3361
|
+
background: rgba(0,0,0,0.5); backdrop-filter: blur(4px);
|
|
3362
|
+
display: flex; align-items: center; justify-content: center;
|
|
3363
|
+
animation: wfStoreDetailFadeIn 0.12s ease-out;
|
|
3364
|
+
}
|
|
3365
|
+
.wf-help-modal {
|
|
3366
|
+
background: var(--bg); border: 1px solid var(--border);
|
|
3367
|
+
border-radius: 12px; width: 540px; max-width: 92vw;
|
|
3368
|
+
max-height: 82vh; display: flex; flex-direction: column;
|
|
3369
|
+
box-shadow: 0 16px 48px rgba(0,0,0,0.3);
|
|
3370
|
+
animation: wfStoreDetailFadeIn 0.12s ease-out;
|
|
3371
|
+
}
|
|
3372
|
+
.wf-help-modal-header {
|
|
3373
|
+
display: flex; align-items: center; gap: 10px;
|
|
3374
|
+
padding: 16px 20px; border-bottom: 1px solid var(--border);
|
|
3375
|
+
flex-shrink: 0;
|
|
3376
|
+
}
|
|
3377
|
+
.wf-help-modal-header .wf-help-icon {
|
|
3378
|
+
width: 32px; height: 32px; border-radius: 8px;
|
|
3379
|
+
display: flex; align-items: center; justify-content: center;
|
|
3380
|
+
flex-shrink: 0;
|
|
3381
|
+
}
|
|
3382
|
+
.wf-help-modal-header .wf-help-title { font-size: 16px; font-weight: 600; flex: 1; }
|
|
3383
|
+
.wf-help-modal-header .wf-help-category-badge {
|
|
3384
|
+
font-size: 10px; font-weight: 600; text-transform: uppercase;
|
|
3385
|
+
letter-spacing: 0.5px; padding: 2px 8px; border-radius: 4px;
|
|
3386
|
+
background: var(--bg-card); color: var(--text-muted); flex-shrink: 0;
|
|
3387
|
+
}
|
|
3388
|
+
.wf-help-modal-header .wf-help-close-btn {
|
|
3389
|
+
width: 28px; height: 28px; border-radius: 6px;
|
|
3390
|
+
border: 1px solid var(--border); background: var(--bg);
|
|
3391
|
+
color: var(--text-muted); cursor: pointer;
|
|
3392
|
+
display: flex; align-items: center; justify-content: center;
|
|
3393
|
+
flex-shrink: 0; padding: 0; transition: background 0.15s ease, color 0.15s ease;
|
|
3394
|
+
}
|
|
3395
|
+
.wf-help-modal-header .wf-help-close-btn:hover { background: var(--bg-card); color: var(--text); }
|
|
3396
|
+
|
|
3397
|
+
.wf-help-modal-body {
|
|
3398
|
+
overflow-y: auto; padding: 20px; flex: 1;
|
|
3399
|
+
}
|
|
3400
|
+
.wf-help-section { margin-bottom: 20px; }
|
|
3401
|
+
.wf-help-section:last-child { margin-bottom: 0; }
|
|
3402
|
+
.wf-help-section-title {
|
|
3403
|
+
font-size: 11px; font-weight: 600; text-transform: uppercase;
|
|
3404
|
+
letter-spacing: 0.5px; color: var(--text-muted); margin-bottom: 8px;
|
|
3405
|
+
}
|
|
3406
|
+
.wf-help-section p {
|
|
3407
|
+
font-size: 13px; line-height: 1.6; color: var(--text); margin: 0;
|
|
3408
|
+
}
|
|
3409
|
+
|
|
3410
|
+
.wf-help-io-table {
|
|
3411
|
+
width: 100%; border-collapse: collapse; font-size: 12px;
|
|
3412
|
+
}
|
|
3413
|
+
.wf-help-io-table th {
|
|
3414
|
+
text-align: left; padding: 6px 8px; font-weight: 600;
|
|
3415
|
+
color: var(--text-muted); border-bottom: 1px solid var(--border);
|
|
3416
|
+
font-size: 11px; text-transform: uppercase; letter-spacing: 0.3px;
|
|
3417
|
+
}
|
|
3418
|
+
.wf-help-io-table td {
|
|
3419
|
+
padding: 6px 8px; border-bottom: 1px solid var(--border);
|
|
3420
|
+
color: var(--text); vertical-align: top;
|
|
3421
|
+
}
|
|
3422
|
+
.wf-help-io-table tr:last-child td { border-bottom: none; }
|
|
3423
|
+
.wf-help-io-table .wf-help-key {
|
|
3424
|
+
font-family: 'SF Mono', 'Fira Code', monospace; font-size: 11px;
|
|
3425
|
+
color: var(--accent); white-space: nowrap;
|
|
3426
|
+
}
|
|
3427
|
+
.wf-help-io-table .wf-help-type {
|
|
3428
|
+
font-size: 10px; color: var(--text-muted); white-space: nowrap;
|
|
3429
|
+
}
|
|
3430
|
+
.wf-help-io-table .wf-help-required {
|
|
3431
|
+
display: inline-block; font-size: 9px; font-weight: 600;
|
|
3432
|
+
color: #ff6b6b; background: rgba(255,107,107,0.1);
|
|
3433
|
+
padding: 1px 4px; border-radius: 3px; margin-left: 4px;
|
|
3434
|
+
}
|
|
3435
|
+
|
|
3436
|
+
.wf-help-tip {
|
|
3437
|
+
display: flex; gap: 8px; margin-bottom: 8px;
|
|
3438
|
+
font-size: 13px; line-height: 1.5; color: var(--text);
|
|
3439
|
+
}
|
|
3440
|
+
.wf-help-tip:last-child { margin-bottom: 0; }
|
|
3441
|
+
.wf-help-tip-dot {
|
|
3442
|
+
width: 6px; height: 6px; border-radius: 50%;
|
|
3443
|
+
background: var(--accent); flex-shrink: 0; margin-top: 7px;
|
|
3444
|
+
}
|
|
3445
|
+
|
|
3446
|
+
/* ── Workflow Store ── */
|
|
3447
|
+
.wf-store-overlay {
|
|
3448
|
+
position: absolute; inset: 0; background: var(--bg); z-index: 50;
|
|
3449
|
+
display: flex; flex-direction: column;
|
|
3450
|
+
opacity: 0; transform: scale(0.98); pointer-events: none;
|
|
3451
|
+
transition: opacity 0.2s ease, transform 0.2s ease;
|
|
3452
|
+
}
|
|
3453
|
+
.wf-store-overlay.open {
|
|
3454
|
+
opacity: 1; transform: scale(1); pointer-events: all;
|
|
3455
|
+
}
|
|
3456
|
+
.wf-store-overlay::before {
|
|
3457
|
+
content: ''; position: absolute; top: -150px; left: 50%; transform: translateX(-50%);
|
|
3458
|
+
width: 700px; height: 500px;
|
|
3459
|
+
background: radial-gradient(ellipse, rgba(0,212,170,.05) 0%, rgba(64,224,255,.02) 40%, transparent 70%);
|
|
3460
|
+
pointer-events: none; z-index: 0;
|
|
3461
|
+
}
|
|
3462
|
+
.wf-store-header {
|
|
3463
|
+
padding: 14px 20px; border-bottom: 1px solid var(--border);
|
|
3464
|
+
display: flex; align-items: center; gap: 14px; position: relative; z-index: 1; flex-shrink: 0;
|
|
3465
|
+
}
|
|
3466
|
+
.wf-store-back {
|
|
3467
|
+
display: flex; align-items: center; gap: 6px; background: none; border: none;
|
|
3468
|
+
color: var(--text-muted); font-family: var(--font); font-size: 13px; cursor: pointer;
|
|
3469
|
+
padding: 6px 10px; border-radius: 6px; transition: all 0.15s;
|
|
3470
|
+
}
|
|
3471
|
+
.wf-store-back:hover { background: var(--bg-surface); color: var(--accent); }
|
|
3472
|
+
.wf-store-title-area { display: flex; align-items: center; gap: 8px; flex: 1; }
|
|
3473
|
+
.wf-store-title { font-family: var(--mono); font-size: 13px; font-weight: 700; color: var(--text); letter-spacing: 0.02em; }
|
|
3474
|
+
.wf-store-badge { font-family: var(--mono); font-size: 9px; padding: 3px 8px; background: rgba(0,212,170,.15); border: 1px solid rgba(0,212,170,.2); border-radius: 100px; color: var(--accent); letter-spacing: 0.04em; text-transform: uppercase; }
|
|
3475
|
+
.wf-store-search-wrap { position: relative; width: 220px; }
|
|
3476
|
+
.wf-store-search {
|
|
3477
|
+
width: 100%; background: var(--bg-surface); border: 1px solid var(--border); border-radius: 8px;
|
|
3478
|
+
padding: 8px 12px 8px 30px; font-family: var(--font); font-size: 13px; color: var(--text); outline: none;
|
|
3479
|
+
transition: border-color 0.15s;
|
|
3480
|
+
}
|
|
3481
|
+
.wf-store-search:focus { border-color: var(--accent); }
|
|
3482
|
+
.wf-store-search::placeholder { color: var(--text-muted); }
|
|
3483
|
+
.wf-store-search-icon { position: absolute; left: 10px; top: 50%; transform: translateY(-50%); color: var(--text-muted); font-size: 13px; pointer-events: none; }
|
|
3484
|
+
.wf-store-sort {
|
|
3485
|
+
background: var(--bg-surface); border: 1px solid var(--border); border-radius: 8px;
|
|
3486
|
+
padding: 8px 10px; font-family: var(--font); font-size: 12px; color: var(--text-muted); outline: none; cursor: pointer;
|
|
3487
|
+
}
|
|
3488
|
+
.wf-store-body { flex: 1; overflow-y: auto; position: relative; z-index: 1; }
|
|
3489
|
+
.wf-store-body::-webkit-scrollbar { width: 6px; }
|
|
3490
|
+
.wf-store-body::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }
|
|
3491
|
+
.wf-store-inner { max-width: 1100px; margin: 0 auto; padding: 20px 24px 40px; }
|
|
3492
|
+
.wf-store-chips { display: flex; gap: 5px; flex-wrap: wrap; margin-bottom: 20px; }
|
|
3493
|
+
.wf-store-chip {
|
|
3494
|
+
padding: 6px 14px; background: none; border: 1px solid var(--border); border-radius: 100px;
|
|
3495
|
+
font-family: var(--font); font-size: 12px; color: var(--text-muted); cursor: pointer;
|
|
3496
|
+
transition: all 0.15s; white-space: nowrap;
|
|
3497
|
+
}
|
|
3498
|
+
.wf-store-chip:hover { border-color: var(--text-dim); color: var(--text-dim); }
|
|
3499
|
+
.wf-store-chip.active { background: rgba(0,212,170,.15); border-color: rgba(0,212,170,.3); color: var(--accent); }
|
|
3500
|
+
.wf-store-section-label {
|
|
3501
|
+
font-family: var(--mono); font-size: 10px; letter-spacing: 0.1em;
|
|
3502
|
+
text-transform: uppercase; color: var(--text-muted); margin-bottom: 12px;
|
|
3503
|
+
display: flex; align-items: center; gap: 8px;
|
|
3504
|
+
}
|
|
3505
|
+
.wf-store-count { background: var(--bg-surface); padding: 2px 7px; border-radius: 100px; font-size: 10px; }
|
|
3506
|
+
/* Featured cards */
|
|
3507
|
+
.wf-store-featured-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; margin-bottom: 24px; }
|
|
3508
|
+
.wf-store-featured-card {
|
|
3509
|
+
border-radius: 14px; padding: 22px 20px 18px; position: relative; overflow: hidden;
|
|
3510
|
+
cursor: pointer; transition: transform 0.2s, box-shadow 0.2s; min-height: 150px;
|
|
3511
|
+
display: flex; flex-direction: column; justify-content: flex-end;
|
|
3512
|
+
}
|
|
3513
|
+
.wf-store-featured-card:hover { transform: translateY(-3px); box-shadow: 0 16px 48px -8px rgba(0,0,0,.5); }
|
|
3514
|
+
.wf-store-featured-card::before {
|
|
3515
|
+
content: ''; position: absolute; inset: 0;
|
|
3516
|
+
background: linear-gradient(180deg, transparent 15%, rgba(0,0,0,.65) 100%); z-index: 1;
|
|
3517
|
+
}
|
|
3518
|
+
.wf-store-featured-content { position: relative; z-index: 2; }
|
|
3519
|
+
.wf-store-featured-content h3 { font-family: var(--mono); font-size: 14px; font-weight: 700; margin-bottom: 4px; color: #fff; }
|
|
3520
|
+
.wf-store-featured-content p { font-size: 12px; color: rgba(255,255,255,.65); line-height: 1.45; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
|
|
3521
|
+
.wf-store-featured-icon { position: absolute; top: 14px; left: 16px; z-index: 2; width: 36px; height: 36px; border-radius: 10px; background: rgba(255,255,255,.18); backdrop-filter: blur(4px); display: flex; align-items: center; justify-content: center; }
|
|
3522
|
+
.wf-store-featured-icon svg { width: 20px; height: 20px; color: #fff; }
|
|
3523
|
+
.wf-store-featured-dl { position: absolute; top: 12px; right: 14px; z-index: 2; font-family: var(--mono); font-size: 10px; color: rgba(255,255,255,.5); }
|
|
3524
|
+
.wf-store-featured-author { position: relative; z-index: 2; display: flex; align-items: center; gap: 6px; margin-top: 6px; font-size: 11px; color: rgba(255,255,255,.55); }
|
|
3525
|
+
.wf-store-featured-avatar { width: 18px; height: 18px; border-radius: 50%; background: rgba(255,255,255,.2); display: flex; align-items: center; justify-content: center; font-size: 9px; color: #fff; font-weight: 700; overflow: hidden; }
|
|
3526
|
+
.wf-store-featured-avatar img { width: 100%; height: 100%; object-fit: cover; }
|
|
3527
|
+
/* Workflow cards grid */
|
|
3528
|
+
.wf-store-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 10px; }
|
|
3529
|
+
.wf-store-card {
|
|
3530
|
+
background: var(--bg-surface); border: 1px solid var(--border); border-radius: 14px;
|
|
3531
|
+
padding: 16px; cursor: pointer; transition: all 0.15s; position: relative; overflow: hidden;
|
|
3532
|
+
}
|
|
3533
|
+
.wf-store-card:hover { border-color: var(--text-dim); background: var(--bg-card); transform: translateY(-1px); }
|
|
3534
|
+
.wf-store-card-bar { position: absolute; top: 0; left: 0; right: 0; height: 2px; opacity: 0; transition: opacity 0.15s; }
|
|
3535
|
+
.wf-store-card:hover .wf-store-card-bar { opacity: 1; }
|
|
3536
|
+
.wf-store-card-top { display: flex; align-items: flex-start; justify-content: space-between; margin-bottom: 6px; gap: 8px; }
|
|
3537
|
+
.wf-store-card-icon { width: 28px; height: 28px; border-radius: 8px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
|
|
3538
|
+
.wf-store-card-icon svg { width: 16px; height: 16px; }
|
|
3539
|
+
.wf-store-card-name-wrap { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
|
3540
|
+
.wf-store-card-name { font-family: var(--mono); font-size: 13px; font-weight: 700; color: var(--text); }
|
|
3541
|
+
.wf-store-card-badges { display: flex; gap: 4px; }
|
|
3542
|
+
.wf-store-badge-official { font-family: var(--mono); font-size: 9px; padding: 2px 7px; border-radius: 100px; background: rgba(0,212,170,.1); color: var(--accent); border: 1px solid rgba(0,212,170,.15); white-space: nowrap; }
|
|
3543
|
+
.wf-store-badge-installed { font-family: var(--mono); font-size: 9px; padding: 2px 7px; border-radius: 100px; background: rgba(16,185,129,.1); color: #10B981; border: 1px solid rgba(16,185,129,.15); white-space: nowrap; }
|
|
3544
|
+
.wf-store-badge-verified { font-family: var(--mono); font-size: 9px; padding: 2px 7px; border-radius: 100px; background: rgba(16,185,129,.1); color: #10B981; border: 1px solid rgba(16,185,129,.15); white-space: nowrap; }
|
|
3545
|
+
.wf-store-badge-capability { font-family: var(--mono); font-size: 9px; padding: 2px 6px; border-radius: 100px; white-space: nowrap; display: inline-flex; align-items: center; gap: 3px; }
|
|
3546
|
+
.wf-store-badge-cap-network { background: rgba(59,130,246,.1); color: #3B82F6; border: 1px solid rgba(59,130,246,.15); }
|
|
3547
|
+
.wf-store-badge-cap-writedb { background: rgba(245,158,11,.1); color: #F59E0B; border: 1px solid rgba(245,158,11,.15); }
|
|
3548
|
+
.wf-store-badge-cap-llm { background: rgba(139,92,246,.1); color: #8B5CF6; border: 1px solid rgba(139,92,246,.15); }
|
|
3549
|
+
.wf-store-badge-cap-loop { background: rgba(6,182,212,.1); color: #06B6D4; border: 1px solid rgba(6,182,212,.15); }
|
|
3550
|
+
.wf-store-badge-cap-readdb { background: rgba(34,197,94,.1); color: #22C55E; border: 1px solid rgba(34,197,94,.15); }
|
|
3551
|
+
.wf-store-detail-security-item { display: flex; align-items: center; gap: 8px; padding: 8px 10px; background: var(--bg-card); border-radius: 6px; margin-bottom: 4px; font-size: 12px; color: var(--text-dim); }
|
|
3552
|
+
.wf-store-detail-security-sev { font-family: var(--mono); font-size: 9px; padding: 2px 7px; border-radius: 100px; font-weight: 700; text-transform: uppercase; }
|
|
3553
|
+
.wf-store-detail-security-sev.critical { background: rgba(239,68,68,.15); color: #EF4444; }
|
|
3554
|
+
.wf-store-detail-security-sev.high { background: rgba(249,115,22,.15); color: #F97316; }
|
|
3555
|
+
.wf-store-detail-security-sev.medium { background: rgba(234,179,8,.15); color: #EAB308; }
|
|
3556
|
+
.wf-store-detail-security-sev.low { background: rgba(148,163,184,.15); color: #94A3B8; }
|
|
3557
|
+
.wf-store-detail-security-ok { color: #10B981; font-size: 12px; padding: 8px 10px; background: rgba(16,185,129,.06); border-radius: 6px; }
|
|
3558
|
+
.wf-store-detail-stars { display: inline-flex; gap: 2px; cursor: pointer; }
|
|
3559
|
+
.wf-store-detail-stars span { font-size: 18px; color: var(--text-muted); transition: color 0.1s; }
|
|
3560
|
+
.wf-store-detail-stars span.filled { color: #F59E0B; }
|
|
3561
|
+
.wf-store-detail-stars span:hover, .wf-store-detail-stars span:hover ~ span { color: var(--text-muted); }
|
|
3562
|
+
.wf-store-report-form { margin-top: 8px; padding: 12px; background: var(--bg-card); border-radius: 8px; border: 1px solid var(--border); }
|
|
3563
|
+
.wf-store-report-form select, .wf-store-report-form textarea { width: 100%; background: var(--bg); border: 1px solid var(--border); border-radius: 6px; padding: 8px; color: var(--text); font-family: var(--font); font-size: 12px; margin-bottom: 8px; }
|
|
3564
|
+
.wf-store-report-form textarea { resize: vertical; min-height: 60px; }
|
|
3565
|
+
.wf-store-report-submit { padding: 6px 14px; background: var(--error); color: #fff; border: none; border-radius: 6px; font-size: 11px; font-weight: 600; cursor: pointer; }
|
|
3566
|
+
.wf-store-report-submit:hover { filter: brightness(1.1); }
|
|
3567
|
+
.wf-store-cap-badges { display: flex; gap: 3px; flex-wrap: wrap; margin-top: 4px; }
|
|
3568
|
+
.wf-store-card-desc { font-size: 12px; color: var(--text-dim); line-height: 1.5; margin-bottom: 4px; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
|
|
3569
|
+
.wf-store-card-author { font-size: 11px; color: var(--text-muted); margin-bottom: 10px; }
|
|
3570
|
+
.wf-store-card-meta { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
|
|
3571
|
+
.wf-store-card-cat { font-family: var(--mono); font-size: 10px; padding: 2px 8px; background: var(--bg-card); border-radius: 100px; color: var(--text-muted); }
|
|
3572
|
+
.wf-store-card-dots { display: flex; gap: 2px; }
|
|
3573
|
+
.wf-store-card-dot { width: 7px; height: 7px; border-radius: 50%; opacity: 0.65; }
|
|
3574
|
+
.wf-store-card-complexity { font-family: var(--mono); font-size: 10px; color: var(--text-muted); }
|
|
3575
|
+
.wf-store-card-downloads { margin-left: auto; font-family: var(--mono); font-size: 10px; color: var(--text-muted); }
|
|
3576
|
+
.wf-store-empty { text-align: center; padding: 40px 0; color: var(--text-muted); }
|
|
3577
|
+
.wf-store-empty p { font-family: var(--mono); font-size: 13px; }
|
|
3578
|
+
/* Detail modal */
|
|
3579
|
+
.wf-store-detail-bg {
|
|
3580
|
+
position: fixed; inset: 0; background: rgba(0,0,0,.55); backdrop-filter: blur(6px);
|
|
3581
|
+
z-index: 200; display: flex; align-items: center; justify-content: center;
|
|
3582
|
+
animation: wfStoreDetailFadeIn 0.12s ease-out; padding: 20px;
|
|
3583
|
+
}
|
|
3584
|
+
@keyframes wfStoreDetailFadeIn { from { opacity: 0; } }
|
|
3585
|
+
@keyframes wfStoreDetailSlideIn { from { transform: translateY(20px); opacity: 0; } }
|
|
3586
|
+
.wf-store-detail-panel {
|
|
3587
|
+
background: var(--bg-surface); border: 1px solid var(--border); border-radius: 18px;
|
|
3588
|
+
width: 100%; max-width: 580px; max-height: 82vh; overflow: hidden;
|
|
3589
|
+
animation: wfStoreDetailSlideIn 0.18s ease-out; display: flex; flex-direction: column;
|
|
3590
|
+
}
|
|
3591
|
+
.wf-store-detail-scroll {
|
|
3592
|
+
overflow-y: auto; flex: 1; min-height: 0;
|
|
3593
|
+
}
|
|
3594
|
+
.wf-store-detail-scroll::-webkit-scrollbar { width: 4px; }
|
|
3595
|
+
.wf-store-detail-scroll::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }
|
|
3596
|
+
.wf-store-detail-hero { padding: 28px 28px 20px; border-radius: 18px 18px 0 0; position: relative; }
|
|
3597
|
+
.wf-store-detail-hero::before { content: ''; position: absolute; inset: 0; background: linear-gradient(180deg, transparent 25%, var(--bg-surface) 100%); border-radius: 18px 18px 0 0; }
|
|
3598
|
+
.wf-store-detail-hero-inner { position: relative; z-index: 1; display: flex; align-items: center; gap: 14px; }
|
|
3599
|
+
.wf-store-detail-hero-icon { width: 48px; height: 48px; border-radius: 12px; background: rgba(255,255,255,.18); backdrop-filter: blur(4px); display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
|
|
3600
|
+
.wf-store-detail-hero-icon svg { width: 26px; height: 26px; color: #fff; }
|
|
3601
|
+
.wf-store-detail-close {
|
|
3602
|
+
position: absolute; top: 14px; right: 14px; z-index: 2; width: 28px; height: 28px;
|
|
3603
|
+
border-radius: 50%; background: rgba(0,0,0,.35); border: none; color: #fff; font-size: 16px;
|
|
3604
|
+
cursor: pointer; display: flex; align-items: center; justify-content: center;
|
|
3605
|
+
}
|
|
3606
|
+
.wf-store-detail-close:hover { background: rgba(0,0,0,.55); }
|
|
3607
|
+
.wf-store-detail-name { font-family: var(--mono); font-size: 20px; font-weight: 700; color: #fff; margin-bottom: 4px; }
|
|
3608
|
+
.wf-store-detail-pkg { font-family: var(--mono); font-size: 11px; color: rgba(255,255,255,.45); }
|
|
3609
|
+
.wf-store-detail-body { padding: 0 28px 28px; }
|
|
3610
|
+
.wf-store-detail-desc { font-size: 14px; color: var(--text-dim); line-height: 1.65; margin-bottom: 20px; }
|
|
3611
|
+
.wf-store-detail-section { margin-bottom: 16px; }
|
|
3612
|
+
.wf-store-detail-label { font-family: var(--mono); font-size: 9px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--text-muted); margin-bottom: 8px; }
|
|
3613
|
+
.wf-store-detail-stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
|
|
3614
|
+
.wf-store-detail-stat { background: var(--bg-card); border-radius: 8px; padding: 12px; text-align: center; }
|
|
3615
|
+
.wf-store-detail-stat-val { font-family: var(--mono); font-size: 20px; font-weight: 700; color: var(--text); }
|
|
3616
|
+
.wf-store-detail-stat-lbl { font-size: 10px; color: var(--text-muted); margin-top: 2px; }
|
|
3617
|
+
.wf-store-detail-tools { display: flex; gap: 5px; flex-wrap: wrap; }
|
|
3618
|
+
.wf-store-detail-tool { padding: 4px 10px; border-radius: 100px; font-size: 11px; font-family: var(--mono); border: 1px solid; }
|
|
3619
|
+
.wf-store-detail-tags { display: flex; gap: 5px; flex-wrap: wrap; }
|
|
3620
|
+
.wf-store-detail-tag { padding: 4px 10px; background: var(--bg-card); border-radius: 100px; font-size: 11px; color: var(--text-dim); font-family: var(--mono); }
|
|
3621
|
+
.wf-store-detail-install { margin-top: 20px; padding: 16px; background: var(--bg-card); border-radius: 8px; border: 1px solid var(--border); }
|
|
3622
|
+
.wf-store-detail-cmd {
|
|
3623
|
+
font-family: var(--mono); font-size: 12px; color: var(--accent); background: var(--bg);
|
|
3624
|
+
padding: 10px 14px; border-radius: 6px; margin-top: 6px; display: flex; align-items: center;
|
|
3625
|
+
justify-content: space-between; cursor: pointer; border: 1px solid var(--border);
|
|
3626
|
+
transition: border-color 0.15s; overflow-x: auto; white-space: nowrap; gap: 10px;
|
|
3627
|
+
}
|
|
3628
|
+
.wf-store-detail-cmd:hover { border-color: var(--accent); }
|
|
3629
|
+
.wf-store-detail-cmd-hint { font-size: 10px; color: var(--text-muted); flex-shrink: 0; }
|
|
3630
|
+
.wf-store-detail-author { display: flex; align-items: center; gap: 8px; margin-bottom: 16px; }
|
|
3631
|
+
.wf-store-detail-avatar { width: 24px; height: 24px; border-radius: 50%; background: var(--bg-card); display: flex; align-items: center; justify-content: center; font-size: 11px; color: var(--text-muted); font-weight: 700; overflow: hidden; flex-shrink: 0; }
|
|
3632
|
+
.wf-store-detail-avatar img { width: 100%; height: 100%; object-fit: cover; }
|
|
3633
|
+
.wf-store-detail-author-name { font-size: 13px; color: var(--text-dim); }
|
|
3634
|
+
.wf-store-detail-author-name a { color: var(--accent); text-decoration: none; }
|
|
3635
|
+
.wf-store-detail-author-name a:hover { text-decoration: underline; }
|
|
3636
|
+
.wf-store-detail-inputs-table { width: 100%; border-collapse: collapse; font-size: 12px; font-family: var(--mono); }
|
|
3637
|
+
.wf-store-detail-inputs-table th { text-align: left; padding: 6px 10px; color: var(--text-muted); font-weight: 600; font-size: 10px; text-transform: uppercase; letter-spacing: 0.05em; border-bottom: 1px solid var(--border); }
|
|
3638
|
+
.wf-store-detail-inputs-table td { padding: 6px 10px; color: var(--text-dim); border-bottom: 1px solid var(--border); }
|
|
3639
|
+
.wf-store-detail-screenshots { display: flex; gap: 10px; overflow-x: auto; padding-bottom: 6px; }
|
|
3640
|
+
.wf-store-detail-screenshots img { height: 180px; border-radius: 8px; border: 1px solid var(--border); flex-shrink: 0; }
|
|
3641
|
+
.wf-store-detail-actions { display: flex; gap: 8px; margin-top: 16px; }
|
|
3642
|
+
.wf-store-detail-btn {
|
|
3643
|
+
flex: 1; padding: 10px; border-radius: 8px; font-family: var(--mono); font-size: 12px;
|
|
3644
|
+
font-weight: 700; cursor: pointer; border: none; transition: all 0.15s; text-align: center;
|
|
3645
|
+
}
|
|
3646
|
+
.wf-store-detail-btn-primary { background: var(--accent); color: var(--bg); }
|
|
3647
|
+
.wf-store-detail-btn-primary:hover { filter: brightness(1.1); }
|
|
3648
|
+
.wf-store-detail-btn-secondary { background: var(--bg-card); color: var(--text-dim); border: 1px solid var(--border); }
|
|
3649
|
+
.wf-store-detail-btn-secondary:hover { background: var(--border); color: var(--text); }
|
|
3650
|
+
.wf-store-detail-btn-installed { background: rgba(16,185,129,.1); color: #10B981; border: 1px solid rgba(16,185,129,.2); cursor: default; }
|
|
3651
|
+
/* Store button in library header */
|
|
3652
|
+
.wf-store-btn {
|
|
3653
|
+
width: 28px; height: 28px; border-radius: 6px; border: 1px solid transparent;
|
|
3654
|
+
background: none; color: var(--text-muted); font-size: 14px; cursor: pointer;
|
|
3655
|
+
display: flex; align-items: center; justify-content: center; transition: all 0.15s;
|
|
3656
|
+
flex-shrink: 0;
|
|
3657
|
+
}
|
|
3658
|
+
.wf-store-btn:hover { background: var(--bg-card); color: var(--accent); border-color: rgba(0,212,170,.15); }
|
|
3659
|
+
.wf-store-btn.active { color: var(--accent); }
|
|
3660
|
+
@media (max-width: 700px) {
|
|
3661
|
+
.wf-store-featured-grid { grid-template-columns: 1fr; }
|
|
3662
|
+
.wf-store-grid { grid-template-columns: 1fr; }
|
|
3663
|
+
}
|
|
3664
|
+
|
|
3130
3665
|
/* Builder port styles */
|
|
3131
3666
|
.wf-port-builder { cursor: crosshair; transition: r 0.15s; pointer-events: all !important; }
|
|
3132
3667
|
.wf-port-builder:hover { r: 9; filter: brightness(1.4); }
|
|
@@ -3400,39 +3935,79 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
3400
3935
|
}
|
|
3401
3936
|
@keyframes wf-flow { to { stroke-dashoffset: -12; } }
|
|
3402
3937
|
.wf-edge--complete { stroke: var(--accent, #6c63ff); opacity: 0.6; stroke-dasharray: none; }
|
|
3938
|
+
.wf-edge--else { stroke-dasharray: 6 4; opacity: 0.4; }
|
|
3939
|
+
.wf-edge--skipped { opacity: 0.12; stroke-dasharray: 4 4; }
|
|
3403
3940
|
/* Inspector */
|
|
3941
|
+
/* ── Library collapse chevron ── */
|
|
3942
|
+
.wf-library-collapse-btn {
|
|
3943
|
+
width: 28px; height: 28px; border: none; background: none;
|
|
3944
|
+
color: var(--text-muted); cursor: pointer; font-size: 14px;
|
|
3945
|
+
display: flex; align-items: center; justify-content: center;
|
|
3946
|
+
border-radius: 6px; transition: color 0.15s, background 0.15s;
|
|
3947
|
+
flex-shrink: 0; padding: 0;
|
|
3948
|
+
}
|
|
3949
|
+
.wf-library-collapse-btn:hover { color: var(--text); background: var(--bg-card); }
|
|
3950
|
+
|
|
3951
|
+
/* ── Canvas edge handles ── */
|
|
3952
|
+
.wf-edge-handle {
|
|
3953
|
+
position: absolute; top: 50%; transform: translateY(-50%);
|
|
3954
|
+
width: 28px; height: 56px; z-index: 20;
|
|
3955
|
+
background: var(--bg-card); border: 1px solid var(--border);
|
|
3956
|
+
color: var(--text-muted); cursor: pointer;
|
|
3957
|
+
display: none; align-items: center; justify-content: center;
|
|
3958
|
+
font-size: 14px; transition: background 0.15s, color 0.15s;
|
|
3959
|
+
}
|
|
3960
|
+
.wf-edge-handle:hover { background: var(--bg); color: var(--text); }
|
|
3961
|
+
.wf-edge-handle--left {
|
|
3962
|
+
left: 0; border-radius: 0 8px 8px 0; border-left: none;
|
|
3963
|
+
}
|
|
3964
|
+
.wf-edge-handle--right {
|
|
3965
|
+
right: 0; border-radius: 8px 0 0 8px; border-right: none;
|
|
3966
|
+
}
|
|
3967
|
+
.wf-edge-handle.visible { display: flex; }
|
|
3968
|
+
|
|
3969
|
+
/* ── Toolbar panel toggle group ── */
|
|
3970
|
+
.wf-toolbar-spacer { flex: 1; }
|
|
3971
|
+
.wf-toolbar-toggle-group {
|
|
3972
|
+
display: flex; background: var(--bg); border-radius: 6px; padding: 2px;
|
|
3973
|
+
border: 1px solid var(--border);
|
|
3974
|
+
}
|
|
3975
|
+
.wf-toolbar-toggle-group button {
|
|
3976
|
+
width: auto !important; height: 28px !important; padding: 0 10px !important;
|
|
3977
|
+
border: none !important; border-radius: 4px !important;
|
|
3978
|
+
background: transparent !important; color: var(--text-muted) !important;
|
|
3979
|
+
font-size: 11px !important; font-weight: 500 !important; cursor: pointer;
|
|
3980
|
+
transition: background 0.15s, color 0.15s;
|
|
3981
|
+
}
|
|
3982
|
+
.wf-toolbar-toggle-group button.active {
|
|
3983
|
+
background: rgba(0,212,170,0.15) !important; color: var(--accent) !important;
|
|
3984
|
+
}
|
|
3985
|
+
.wf-toolbar-toggle-group button:hover:not(.active) {
|
|
3986
|
+
color: var(--text) !important;
|
|
3987
|
+
}
|
|
3988
|
+
|
|
3989
|
+
/* ── Inspector (upgraded) ── */
|
|
3404
3990
|
.wf-inspector {
|
|
3405
3991
|
flex-shrink: 0; position: relative;
|
|
3406
3992
|
display: flex; flex-direction: row;
|
|
3407
3993
|
background: var(--bg);
|
|
3408
|
-
transition: width 0.
|
|
3994
|
+
transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
3409
3995
|
width: 300px;
|
|
3410
3996
|
overflow: hidden;
|
|
3411
3997
|
align-self: stretch;
|
|
3998
|
+
border-left: 1px solid var(--border);
|
|
3412
3999
|
}
|
|
3413
4000
|
.wf-inspector.collapsed {
|
|
3414
|
-
width:
|
|
4001
|
+
width: 0; border-left: none;
|
|
3415
4002
|
}
|
|
3416
4003
|
.wf-inspector-toggle {
|
|
3417
|
-
|
|
3418
|
-
border: none; border-left: 1px solid var(--border);
|
|
3419
|
-
background: var(--bg); color: var(--text-muted);
|
|
3420
|
-
cursor: pointer; font-size: 14px; padding: 0;
|
|
3421
|
-
display: flex; align-items: center; justify-content: center;
|
|
3422
|
-
transition: color 0.15s, background 0.15s;
|
|
3423
|
-
}
|
|
3424
|
-
.wf-inspector-toggle:hover {
|
|
3425
|
-
color: var(--text); background: var(--bg-card);
|
|
3426
|
-
}
|
|
3427
|
-
.wf-inspector.collapsed .wf-inspector-toggle {
|
|
3428
|
-
border-left: 1px solid var(--border);
|
|
4004
|
+
display: none; /* replaced by edge handle */
|
|
3429
4005
|
}
|
|
3430
4006
|
.wf-inspector.collapsed .wf-inspector-content {
|
|
3431
4007
|
display: none;
|
|
3432
4008
|
}
|
|
3433
4009
|
.wf-inspector-content {
|
|
3434
4010
|
flex: 1; display: flex; flex-direction: column;
|
|
3435
|
-
border-left: 1px solid var(--border);
|
|
3436
4011
|
overflow-y: auto; min-width: 0; height: 100%;
|
|
3437
4012
|
}
|
|
3438
4013
|
.wf-inspector-header {
|
|
@@ -3499,11 +4074,11 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
3499
4074
|
.wf-inspector-result.error { border-color: #e74c3c; }
|
|
3500
4075
|
/* Responsive */
|
|
3501
4076
|
@media (max-width: 900px) {
|
|
3502
|
-
.wf-library {
|
|
4077
|
+
.wf-library { width: 0; border-right: none; }
|
|
3503
4078
|
.wf-inspector:not(.collapsed) { width: 240px; }
|
|
3504
4079
|
}
|
|
3505
4080
|
@media (max-width: 600px) {
|
|
3506
|
-
.wf-inspector {
|
|
4081
|
+
.wf-inspector { width: 0; border-left: none; }
|
|
3507
4082
|
}
|
|
3508
4083
|
/* Workflow visualizer light mode */
|
|
3509
4084
|
[data-theme="light"] .wf-node-label { fill: #001E2B; }
|
|
@@ -3552,105 +4127,890 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
3552
4127
|
}
|
|
3553
4128
|
.wf-canvas-watermark img { width: 300px; height: 300px; filter: invert(1); }
|
|
3554
4129
|
[data-theme="light"] .wf-canvas-watermark img { filter: none; }
|
|
3555
|
-
</style>
|
|
3556
|
-
</head>
|
|
3557
|
-
<body>
|
|
3558
4130
|
|
|
3559
|
-
|
|
3560
|
-
<svg xmlns="http://www.w3.org/2000/svg" style="display:none;">
|
|
3561
|
-
<!-- Zap (Embed) -->
|
|
3562
|
-
<symbol id="lg-lightning" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
3563
|
-
<path d="M13 2 3 14h9l-1 8 10-12h-9l1-8z"/>
|
|
3564
|
-
</symbol>
|
|
3565
|
-
<!-- Arrow Left Right (Compare) -->
|
|
3566
|
-
<symbol id="lg-arrows" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
3567
|
-
<path d="M8 3 4 7l4 4"/><path d="M4 7h16"/><path d="m16 21 4-4-4-4"/><path d="M20 17H4"/>
|
|
3568
|
-
</symbol>
|
|
3569
|
-
<!-- Search -->
|
|
3570
|
-
<symbol id="lg-search" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
3571
|
-
<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>
|
|
3572
|
-
</symbol>
|
|
3573
|
-
<!-- Gauge (Benchmark) -->
|
|
3574
|
-
<symbol id="lg-gauge" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
3575
|
-
<path d="m12 14 4-4"/><path d="M3.34 19a10 10 0 1 1 17.32 0"/>
|
|
3576
|
-
</symbol>
|
|
3577
|
-
<!-- Lightbulb (Explore) -->
|
|
3578
|
-
<symbol id="lg-bulb" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
3579
|
-
<path d="M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5"/><path d="M9 18h6"/><path d="M10 22h4"/>
|
|
3580
|
-
</symbol>
|
|
3581
|
-
<!-- Info (About) -->
|
|
3582
|
-
<symbol id="lg-info" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
3583
|
-
<circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/>
|
|
3584
|
-
</symbol>
|
|
3585
|
-
<!-- Image (Multimodal) -->
|
|
3586
|
-
<symbol id="lg-image" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
3587
|
-
<rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/>
|
|
3588
|
-
</symbol>
|
|
3589
|
-
<!-- Settings (Config) -->
|
|
3590
|
-
<symbol id="lg-config" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
3591
|
-
<path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/>
|
|
3592
|
-
</symbol>
|
|
3593
|
-
<!-- Code (Generate) -->
|
|
3594
|
-
<symbol id="lg-code" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
3595
|
-
<polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/>
|
|
3596
|
-
</symbol>
|
|
3597
|
-
<!-- Palette (Theme) -->
|
|
3598
|
-
<symbol id="lg-palette" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
3599
|
-
<circle cx="13.5" cy="6.5" r=".5" fill="currentColor"/><circle cx="17.5" cy="10.5" r=".5" fill="currentColor"/><circle cx="8.5" cy="7.5" r=".5" fill="currentColor"/><circle cx="6.5" cy="12.5" r=".5" fill="currentColor"/><path d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10c.926 0 1.648-.746 1.648-1.688 0-.437-.18-.835-.437-1.125-.29-.289-.438-.652-.438-1.125a1.64 1.64 0 0 1 1.668-1.668h1.996c3.051 0 5.555-2.503 5.555-5.554C21.965 6.012 17.461 2 12 2z"/>
|
|
3600
|
-
</symbol>
|
|
3601
|
-
<!-- Box (Cube) -->
|
|
3602
|
-
<symbol id="lg-cube" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
3603
|
-
<path d="M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z"/><path d="m3.3 7 8.7 5 8.7-5"/><path d="M12 22V12"/>
|
|
3604
|
-
</symbol>
|
|
3605
|
-
<!-- Message Square (Chat) -->
|
|
3606
|
-
<symbol id="lg-chat" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
3607
|
-
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
|
|
3608
|
-
</symbol>
|
|
3609
|
-
<!-- Shield -->
|
|
3610
|
-
<symbol id="lg-shield" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
3611
|
-
<path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 .5-.87l7-4a1 1 0 0 1 1 0l7 4A1 1 0 0 1 20 6z"/>
|
|
3612
|
-
</symbol>
|
|
3613
|
-
<!-- Activity (Pulse) -->
|
|
3614
|
-
<symbol id="lg-pulse" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
3615
|
-
<path d="M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36-3.18-19.64A2 2 0 0 0 10.12 1h-.24a2 2 0 0 0-1.94 1.55L5.18 12H2"/>
|
|
3616
|
-
</symbol>
|
|
3617
|
-
</svg>
|
|
4131
|
+
/* ── Home Page Styles ── */
|
|
3618
4132
|
|
|
3619
|
-
|
|
4133
|
+
/* Home Header */
|
|
4134
|
+
.home-header {
|
|
4135
|
+
display: flex;
|
|
4136
|
+
align-items: center;
|
|
4137
|
+
justify-content: space-between;
|
|
4138
|
+
padding: 24px 32px 16px;
|
|
4139
|
+
border-bottom: 1px solid var(--border);
|
|
4140
|
+
background: var(--bg);
|
|
4141
|
+
}
|
|
3620
4142
|
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
4143
|
+
.home-header-left {
|
|
4144
|
+
display: flex;
|
|
4145
|
+
align-items: center;
|
|
4146
|
+
gap: 12px;
|
|
4147
|
+
}
|
|
4148
|
+
|
|
4149
|
+
.home-logo {
|
|
4150
|
+
width: 32px;
|
|
4151
|
+
height: 32px;
|
|
4152
|
+
}
|
|
4153
|
+
|
|
4154
|
+
.home-header-title {
|
|
4155
|
+
font-size: 16px;
|
|
4156
|
+
font-weight: 600;
|
|
4157
|
+
color: var(--text);
|
|
4158
|
+
letter-spacing: -0.01em;
|
|
4159
|
+
}
|
|
4160
|
+
|
|
4161
|
+
.home-version-badge {
|
|
4162
|
+
background: var(--bg-card);
|
|
4163
|
+
border: 1px solid var(--border);
|
|
4164
|
+
color: var(--text-dim);
|
|
4165
|
+
padding: 4px 8px;
|
|
4166
|
+
border-radius: 12px;
|
|
4167
|
+
font-size: 11px;
|
|
4168
|
+
font-weight: 500;
|
|
4169
|
+
}
|
|
4170
|
+
|
|
4171
|
+
.home-header-right {
|
|
4172
|
+
display: flex;
|
|
4173
|
+
align-items: center;
|
|
4174
|
+
gap: 12px;
|
|
4175
|
+
}
|
|
4176
|
+
|
|
4177
|
+
.home-settings-btn {
|
|
4178
|
+
background: none;
|
|
4179
|
+
border: 1px solid var(--border);
|
|
4180
|
+
color: var(--text-dim);
|
|
4181
|
+
padding: 8px;
|
|
4182
|
+
border-radius: 8px;
|
|
4183
|
+
cursor: pointer;
|
|
4184
|
+
transition: all 0.2s;
|
|
4185
|
+
}
|
|
4186
|
+
|
|
4187
|
+
.home-settings-btn:hover {
|
|
4188
|
+
background: var(--bg-card);
|
|
4189
|
+
color: var(--text);
|
|
4190
|
+
border-color: var(--accent);
|
|
4191
|
+
}
|
|
4192
|
+
|
|
4193
|
+
.home-settings-btn svg {
|
|
4194
|
+
width: 16px;
|
|
4195
|
+
height: 16px;
|
|
4196
|
+
}
|
|
4197
|
+
|
|
4198
|
+
/* API Key Warning Banner */
|
|
4199
|
+
.home-api-warning {
|
|
4200
|
+
margin: 16px 32px;
|
|
4201
|
+
background: linear-gradient(135deg, rgba(255, 201, 16, 0.1), rgba(255, 105, 96, 0.1));
|
|
4202
|
+
border: 1px solid rgba(255, 201, 16, 0.3);
|
|
4203
|
+
border-radius: 12px;
|
|
4204
|
+
padding: 16px;
|
|
4205
|
+
animation: slideDown 0.3s ease-out;
|
|
4206
|
+
}
|
|
4207
|
+
|
|
4208
|
+
.home-api-warning-content {
|
|
4209
|
+
display: flex;
|
|
4210
|
+
align-items: center;
|
|
4211
|
+
gap: 12px;
|
|
4212
|
+
color: var(--warning);
|
|
4213
|
+
}
|
|
4214
|
+
|
|
4215
|
+
.home-api-warning svg {
|
|
4216
|
+
width: 20px;
|
|
4217
|
+
height: 20px;
|
|
4218
|
+
flex-shrink: 0;
|
|
4219
|
+
}
|
|
4220
|
+
|
|
4221
|
+
.home-api-warning button {
|
|
4222
|
+
margin-left: auto;
|
|
4223
|
+
background: none;
|
|
4224
|
+
border: 1px solid var(--warning);
|
|
4225
|
+
color: var(--warning);
|
|
4226
|
+
padding: 6px 12px;
|
|
4227
|
+
border-radius: 6px;
|
|
4228
|
+
cursor: pointer;
|
|
4229
|
+
font-size: 12px;
|
|
4230
|
+
transition: all 0.2s;
|
|
4231
|
+
}
|
|
4232
|
+
|
|
4233
|
+
.home-api-warning button:hover {
|
|
4234
|
+
background: var(--warning);
|
|
4235
|
+
color: var(--bg);
|
|
4236
|
+
}
|
|
4237
|
+
|
|
4238
|
+
/* Home Content */
|
|
4239
|
+
.home-content {
|
|
4240
|
+
padding: 32px;
|
|
4241
|
+
max-width: 1200px;
|
|
4242
|
+
margin: 0 auto;
|
|
4243
|
+
overflow-y: auto;
|
|
4244
|
+
height: calc(100vh - 120px);
|
|
4245
|
+
}
|
|
4246
|
+
|
|
4247
|
+
/* Announcements Banner */
|
|
4248
|
+
.home-announcements {
|
|
4249
|
+
margin-bottom: 48px;
|
|
4250
|
+
animation: fadeInUp 0.6s ease-out;
|
|
4251
|
+
}
|
|
4252
|
+
|
|
4253
|
+
.home-announcements-carousel {
|
|
4254
|
+
position: relative;
|
|
4255
|
+
overflow: hidden;
|
|
4256
|
+
border-radius: 16px;
|
|
4257
|
+
background: linear-gradient(135deg, var(--bg-card), var(--bg-surface));
|
|
4258
|
+
border: 1px solid var(--border);
|
|
4259
|
+
min-height: 180px;
|
|
4260
|
+
}
|
|
4261
|
+
|
|
4262
|
+
.home-announcement-card {
|
|
4263
|
+
position: absolute;
|
|
4264
|
+
top: 0;
|
|
4265
|
+
left: 0;
|
|
4266
|
+
right: 0;
|
|
4267
|
+
padding: 32px;
|
|
4268
|
+
text-align: center;
|
|
4269
|
+
opacity: 0;
|
|
4270
|
+
pointer-events: none;
|
|
4271
|
+
transition: opacity 0.5s ease-in-out;
|
|
4272
|
+
}
|
|
4273
|
+
|
|
4274
|
+
.home-announcement-card.active {
|
|
4275
|
+
position: relative;
|
|
4276
|
+
opacity: 1;
|
|
4277
|
+
pointer-events: auto;
|
|
4278
|
+
}
|
|
4279
|
+
|
|
4280
|
+
.home-announcement-card .badge {
|
|
4281
|
+
display: inline-block;
|
|
4282
|
+
background: linear-gradient(135deg, #00ED64, #00C2FF);
|
|
4283
|
+
color: white;
|
|
4284
|
+
padding: 4px 12px;
|
|
4285
|
+
border-radius: 12px;
|
|
4286
|
+
font-size: 11px;
|
|
4287
|
+
font-weight: 600;
|
|
4288
|
+
margin-bottom: 16px;
|
|
4289
|
+
text-transform: uppercase;
|
|
4290
|
+
letter-spacing: 0.5px;
|
|
4291
|
+
}
|
|
4292
|
+
|
|
4293
|
+
.home-announcement-card h3 {
|
|
4294
|
+
font-size: 24px;
|
|
4295
|
+
font-weight: 700;
|
|
4296
|
+
color: var(--accent-text);
|
|
4297
|
+
margin-bottom: 12px;
|
|
4298
|
+
}
|
|
4299
|
+
|
|
4300
|
+
.home-announcement-card p {
|
|
4301
|
+
color: var(--text-dim);
|
|
4302
|
+
line-height: 1.6;
|
|
4303
|
+
margin-bottom: 24px;
|
|
4304
|
+
max-width: 600px;
|
|
4305
|
+
margin-left: auto;
|
|
4306
|
+
margin-right: auto;
|
|
4307
|
+
}
|
|
4308
|
+
|
|
4309
|
+
.home-announcement-card .cta {
|
|
4310
|
+
background: linear-gradient(135deg, #00ED64, #00C2FF);
|
|
4311
|
+
color: white;
|
|
4312
|
+
border: none;
|
|
4313
|
+
padding: 12px 24px;
|
|
4314
|
+
border-radius: 8px;
|
|
4315
|
+
font-weight: 600;
|
|
4316
|
+
cursor: pointer;
|
|
4317
|
+
transition: all 0.2s;
|
|
4318
|
+
}
|
|
4319
|
+
|
|
4320
|
+
.home-announcement-card .cta:hover {
|
|
4321
|
+
transform: translateY(-2px);
|
|
4322
|
+
box-shadow: 0 8px 32px rgba(0, 237, 100, 0.3);
|
|
4323
|
+
}
|
|
4324
|
+
|
|
4325
|
+
.home-announcement-dismiss {
|
|
4326
|
+
position: absolute;
|
|
4327
|
+
top: 16px;
|
|
4328
|
+
right: 16px;
|
|
4329
|
+
background: none;
|
|
4330
|
+
border: none;
|
|
4331
|
+
color: var(--text-muted);
|
|
4332
|
+
cursor: pointer;
|
|
4333
|
+
padding: 4px;
|
|
4334
|
+
border-radius: 4px;
|
|
4335
|
+
transition: all 0.2s;
|
|
4336
|
+
}
|
|
4337
|
+
|
|
4338
|
+
.home-announcement-dismiss:hover {
|
|
4339
|
+
background: var(--bg-card);
|
|
4340
|
+
color: var(--text);
|
|
4341
|
+
}
|
|
4342
|
+
|
|
4343
|
+
.home-announcements-dots {
|
|
4344
|
+
display: flex;
|
|
4345
|
+
justify-content: center;
|
|
4346
|
+
gap: 8px;
|
|
4347
|
+
margin-top: 16px;
|
|
4348
|
+
}
|
|
4349
|
+
|
|
4350
|
+
.home-announcement-dot {
|
|
4351
|
+
width: 8px;
|
|
4352
|
+
height: 8px;
|
|
4353
|
+
border-radius: 50%;
|
|
4354
|
+
background: var(--border);
|
|
4355
|
+
cursor: pointer;
|
|
4356
|
+
transition: all 0.2s;
|
|
4357
|
+
}
|
|
4358
|
+
|
|
4359
|
+
.home-announcement-dot.active {
|
|
4360
|
+
background: linear-gradient(135deg, #00ED64, #00C2FF);
|
|
4361
|
+
}
|
|
4362
|
+
|
|
4363
|
+
.home-announcements-restore {
|
|
4364
|
+
text-align: center;
|
|
4365
|
+
margin-top: 12px;
|
|
4366
|
+
}
|
|
4367
|
+
|
|
4368
|
+
.home-announcements-restore button {
|
|
4369
|
+
background: var(--bg-card);
|
|
4370
|
+
border: 1px dashed var(--border);
|
|
4371
|
+
color: var(--text-dim);
|
|
4372
|
+
font-size: 13px;
|
|
4373
|
+
cursor: pointer;
|
|
4374
|
+
padding: 8px 16px;
|
|
4375
|
+
border-radius: 8px;
|
|
4376
|
+
transition: all 0.2s;
|
|
4377
|
+
}
|
|
4378
|
+
|
|
4379
|
+
.home-announcements-restore button:hover {
|
|
4380
|
+
color: var(--accent-text);
|
|
4381
|
+
border-color: var(--accent-text);
|
|
4382
|
+
background: var(--bg-surface);
|
|
4383
|
+
}
|
|
4384
|
+
|
|
4385
|
+
.home-announcement-card.has-bg-image {
|
|
4386
|
+
background-size: cover;
|
|
4387
|
+
background-position: center;
|
|
4388
|
+
background-repeat: no-repeat;
|
|
4389
|
+
min-height: 220px;
|
|
4390
|
+
display: flex;
|
|
4391
|
+
flex-direction: column;
|
|
4392
|
+
justify-content: center;
|
|
4393
|
+
align-items: center;
|
|
4394
|
+
}
|
|
4395
|
+
|
|
4396
|
+
.home-announcement-card.has-bg-image::before {
|
|
4397
|
+
content: '';
|
|
4398
|
+
position: absolute;
|
|
4399
|
+
inset: 0;
|
|
4400
|
+
background: rgba(0, 0, 0, 0.5);
|
|
4401
|
+
border-radius: inherit;
|
|
4402
|
+
z-index: 0;
|
|
4403
|
+
}
|
|
4404
|
+
|
|
4405
|
+
.home-announcement-card.has-bg-image > * {
|
|
4406
|
+
position: relative;
|
|
4407
|
+
z-index: 1;
|
|
4408
|
+
}
|
|
4409
|
+
|
|
4410
|
+
.home-announcement-card.has-bg-image h3,
|
|
4411
|
+
.home-announcement-card.has-bg-image p {
|
|
4412
|
+
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.6);
|
|
4413
|
+
}
|
|
4414
|
+
|
|
4415
|
+
.home-announcement-card.has-bg-image h3 {
|
|
4416
|
+
color: #fff;
|
|
4417
|
+
}
|
|
4418
|
+
|
|
4419
|
+
.home-announcement-card.has-bg-image p {
|
|
4420
|
+
color: rgba(255, 255, 255, 0.9);
|
|
4421
|
+
}
|
|
4422
|
+
|
|
4423
|
+
.home-announcement-icon {
|
|
4424
|
+
font-size: 48px;
|
|
4425
|
+
margin-bottom: 12px;
|
|
4426
|
+
line-height: 1;
|
|
4427
|
+
}
|
|
4428
|
+
|
|
4429
|
+
/* Section Headers */
|
|
4430
|
+
.home-section {
|
|
4431
|
+
margin-bottom: 48px;
|
|
4432
|
+
}
|
|
4433
|
+
|
|
4434
|
+
.home-section-header {
|
|
4435
|
+
display: flex;
|
|
4436
|
+
align-items: center;
|
|
4437
|
+
justify-content: space-between;
|
|
4438
|
+
margin-bottom: 24px;
|
|
4439
|
+
}
|
|
4440
|
+
|
|
4441
|
+
.home-section-header h3 {
|
|
4442
|
+
font-size: 20px;
|
|
4443
|
+
font-weight: 700;
|
|
4444
|
+
color: var(--accent-text);
|
|
4445
|
+
}
|
|
4446
|
+
|
|
4447
|
+
.home-section-header a,
|
|
4448
|
+
.home-marketplace-cta {
|
|
4449
|
+
color: var(--blue);
|
|
4450
|
+
text-decoration: none;
|
|
4451
|
+
font-weight: 500;
|
|
4452
|
+
transition: all 0.2s;
|
|
4453
|
+
}
|
|
4454
|
+
|
|
4455
|
+
.home-marketplace-cta {
|
|
4456
|
+
background: linear-gradient(135deg, #00ED64, #00C2FF);
|
|
4457
|
+
color: white;
|
|
4458
|
+
border: none;
|
|
4459
|
+
padding: 10px 20px;
|
|
4460
|
+
border-radius: 8px;
|
|
4461
|
+
cursor: pointer;
|
|
4462
|
+
font-weight: 600;
|
|
4463
|
+
transition: all 0.2s;
|
|
4464
|
+
}
|
|
4465
|
+
|
|
4466
|
+
.home-marketplace-cta:hover {
|
|
4467
|
+
transform: translateY(-1px);
|
|
4468
|
+
box-shadow: 0 4px 16px rgba(0, 237, 100, 0.3);
|
|
4469
|
+
}
|
|
4470
|
+
|
|
4471
|
+
.home-section-header a:hover {
|
|
4472
|
+
color: var(--accent);
|
|
4473
|
+
}
|
|
4474
|
+
|
|
4475
|
+
/* Release Notes Timeline */
|
|
4476
|
+
.home-releases-timeline {
|
|
4477
|
+
display: flex;
|
|
4478
|
+
flex-direction: column;
|
|
4479
|
+
gap: 24px;
|
|
4480
|
+
}
|
|
4481
|
+
|
|
4482
|
+
.home-release-item {
|
|
4483
|
+
display: flex;
|
|
4484
|
+
gap: 16px;
|
|
4485
|
+
padding: 24px;
|
|
4486
|
+
background: var(--bg-card);
|
|
4487
|
+
border: 1px solid var(--border);
|
|
4488
|
+
border-radius: 12px;
|
|
4489
|
+
border-left: 4px solid transparent;
|
|
4490
|
+
border-left-color: #00ED64;
|
|
4491
|
+
background: linear-gradient(90deg, transparent, var(--bg-card) 4px);
|
|
4492
|
+
transition: all 0.2s;
|
|
4493
|
+
animation: fadeInUp 0.6s ease-out;
|
|
4494
|
+
}
|
|
4495
|
+
|
|
4496
|
+
.home-release-item:hover {
|
|
4497
|
+
background: var(--bg-surface);
|
|
4498
|
+
border-color: var(--accent);
|
|
4499
|
+
}
|
|
4500
|
+
|
|
4501
|
+
.home-release-version {
|
|
4502
|
+
font-size: 16px;
|
|
4503
|
+
font-weight: 700;
|
|
4504
|
+
color: var(--accent-text);
|
|
4505
|
+
margin-bottom: 4px;
|
|
4506
|
+
}
|
|
4507
|
+
|
|
4508
|
+
.home-release-date {
|
|
4509
|
+
font-size: 12px;
|
|
4510
|
+
color: var(--text-muted);
|
|
4511
|
+
margin-bottom: 12px;
|
|
4512
|
+
}
|
|
4513
|
+
|
|
4514
|
+
.home-release-highlights {
|
|
4515
|
+
list-style: none;
|
|
4516
|
+
display: flex;
|
|
4517
|
+
flex-direction: column;
|
|
4518
|
+
gap: 8px;
|
|
4519
|
+
}
|
|
4520
|
+
|
|
4521
|
+
.home-release-highlights li {
|
|
4522
|
+
display: flex;
|
|
4523
|
+
align-items: flex-start;
|
|
4524
|
+
gap: 8px;
|
|
4525
|
+
color: var(--text-dim);
|
|
4526
|
+
line-height: 1.5;
|
|
4527
|
+
}
|
|
4528
|
+
|
|
4529
|
+
.home-release-highlights li:before {
|
|
4530
|
+
content: "•";
|
|
4531
|
+
color: var(--accent);
|
|
4532
|
+
font-weight: bold;
|
|
4533
|
+
flex-shrink: 0;
|
|
4534
|
+
margin-top: 2px;
|
|
4535
|
+
}
|
|
4536
|
+
|
|
4537
|
+
/* Subsections */
|
|
4538
|
+
.home-subsection {
|
|
4539
|
+
margin-bottom: 32px;
|
|
4540
|
+
}
|
|
4541
|
+
|
|
4542
|
+
.home-subsection h4 {
|
|
4543
|
+
font-size: 16px;
|
|
4544
|
+
font-weight: 600;
|
|
4545
|
+
color: var(--text);
|
|
4546
|
+
margin-bottom: 16px;
|
|
4547
|
+
}
|
|
4548
|
+
|
|
4549
|
+
.home-subsection-header {
|
|
4550
|
+
display: flex;
|
|
4551
|
+
align-items: center;
|
|
4552
|
+
justify-content: space-between;
|
|
4553
|
+
margin-bottom: 16px;
|
|
4554
|
+
}
|
|
4555
|
+
|
|
4556
|
+
/* Workflow Carousels */
|
|
4557
|
+
.home-workflows-carousel {
|
|
4558
|
+
display: flex;
|
|
4559
|
+
gap: 16px;
|
|
4560
|
+
overflow-x: auto;
|
|
4561
|
+
padding-bottom: 8px;
|
|
4562
|
+
scroll-snap-type: x mandatory;
|
|
4563
|
+
}
|
|
4564
|
+
|
|
4565
|
+
.home-workflows-carousel::-webkit-scrollbar {
|
|
4566
|
+
height: 6px;
|
|
4567
|
+
}
|
|
4568
|
+
|
|
4569
|
+
.home-workflows-carousel::-webkit-scrollbar-track {
|
|
4570
|
+
background: var(--bg-surface);
|
|
4571
|
+
border-radius: 3px;
|
|
4572
|
+
}
|
|
4573
|
+
|
|
4574
|
+
.home-workflows-carousel::-webkit-scrollbar-thumb {
|
|
4575
|
+
background: var(--border);
|
|
4576
|
+
border-radius: 3px;
|
|
4577
|
+
}
|
|
4578
|
+
|
|
4579
|
+
.home-workflow-card {
|
|
4580
|
+
flex: 0 0 280px;
|
|
4581
|
+
background: var(--bg-card);
|
|
4582
|
+
border: 1px solid var(--border);
|
|
4583
|
+
border-radius: 12px;
|
|
4584
|
+
padding: 20px;
|
|
4585
|
+
scroll-snap-align: start;
|
|
4586
|
+
transition: all 0.2s;
|
|
4587
|
+
animation: fadeInUp 0.6s ease-out;
|
|
4588
|
+
}
|
|
4589
|
+
|
|
4590
|
+
.home-workflow-card:hover {
|
|
4591
|
+
background: var(--bg-surface);
|
|
4592
|
+
border-color: var(--accent);
|
|
4593
|
+
transform: translateY(-2px);
|
|
4594
|
+
box-shadow: 0 8px 32px rgba(0, 212, 170, 0.1);
|
|
4595
|
+
}
|
|
4596
|
+
|
|
4597
|
+
.home-workflow-card h5 {
|
|
4598
|
+
font-size: 14px;
|
|
4599
|
+
font-weight: 600;
|
|
4600
|
+
color: var(--accent-text);
|
|
4601
|
+
margin-bottom: 8px;
|
|
4602
|
+
}
|
|
4603
|
+
|
|
4604
|
+
.home-workflow-card p {
|
|
4605
|
+
font-size: 12px;
|
|
4606
|
+
color: var(--text-dim);
|
|
4607
|
+
line-height: 1.5;
|
|
4608
|
+
margin-bottom: 12px;
|
|
4609
|
+
display: -webkit-box;
|
|
4610
|
+
-webkit-line-clamp: 2;
|
|
4611
|
+
-webkit-box-orient: vertical;
|
|
4612
|
+
overflow: hidden;
|
|
4613
|
+
}
|
|
4614
|
+
|
|
4615
|
+
.home-workflow-meta {
|
|
4616
|
+
display: flex;
|
|
4617
|
+
align-items: center;
|
|
4618
|
+
gap: 8px;
|
|
4619
|
+
margin-bottom: 16px;
|
|
4620
|
+
}
|
|
4621
|
+
|
|
4622
|
+
.home-workflow-domain {
|
|
4623
|
+
background: rgba(0, 237, 100, 0.1);
|
|
4624
|
+
color: var(--accent);
|
|
4625
|
+
padding: 4px 8px;
|
|
4626
|
+
border-radius: 6px;
|
|
4627
|
+
font-size: 10px;
|
|
4628
|
+
font-weight: 500;
|
|
4629
|
+
}
|
|
4630
|
+
|
|
4631
|
+
.home-workflow-author {
|
|
4632
|
+
font-size: 11px;
|
|
4633
|
+
color: var(--text-muted);
|
|
4634
|
+
}
|
|
4635
|
+
|
|
4636
|
+
.home-workflow-actions {
|
|
4637
|
+
display: flex;
|
|
4638
|
+
gap: 8px;
|
|
4639
|
+
}
|
|
4640
|
+
|
|
4641
|
+
.home-workflow-btn {
|
|
4642
|
+
flex: 1;
|
|
4643
|
+
background: none;
|
|
4644
|
+
border: 1px solid var(--border);
|
|
4645
|
+
color: var(--text-dim);
|
|
4646
|
+
padding: 8px 12px;
|
|
4647
|
+
border-radius: 6px;
|
|
4648
|
+
font-size: 11px;
|
|
4649
|
+
cursor: pointer;
|
|
4650
|
+
transition: all 0.2s;
|
|
4651
|
+
}
|
|
4652
|
+
|
|
4653
|
+
.home-workflow-btn:hover {
|
|
4654
|
+
background: var(--accent);
|
|
4655
|
+
color: white;
|
|
4656
|
+
border-color: var(--accent);
|
|
4657
|
+
}
|
|
4658
|
+
|
|
4659
|
+
/* Domain Pills */
|
|
4660
|
+
.home-domain-pills {
|
|
4661
|
+
display: flex;
|
|
4662
|
+
flex-wrap: wrap;
|
|
4663
|
+
gap: 8px;
|
|
4664
|
+
}
|
|
4665
|
+
|
|
4666
|
+
.home-domain-pill {
|
|
4667
|
+
background: var(--bg-card);
|
|
4668
|
+
border: 1px solid var(--border);
|
|
4669
|
+
color: var(--text-dim);
|
|
4670
|
+
padding: 8px 16px;
|
|
4671
|
+
border-radius: 20px;
|
|
4672
|
+
font-size: 12px;
|
|
4673
|
+
font-weight: 500;
|
|
4674
|
+
cursor: pointer;
|
|
4675
|
+
transition: all 0.2s;
|
|
4676
|
+
}
|
|
4677
|
+
|
|
4678
|
+
.home-domain-pill:hover {
|
|
4679
|
+
background: linear-gradient(135deg, #00ED64, #00C2FF);
|
|
4680
|
+
color: white;
|
|
4681
|
+
border-color: transparent;
|
|
4682
|
+
transform: translateY(-1px);
|
|
4683
|
+
}
|
|
4684
|
+
|
|
4685
|
+
/* Sort Tabs */
|
|
4686
|
+
.home-sort-tabs {
|
|
4687
|
+
display: flex;
|
|
4688
|
+
gap: 2px;
|
|
4689
|
+
background: var(--bg-surface);
|
|
4690
|
+
border: 1px solid var(--border);
|
|
4691
|
+
border-radius: 8px;
|
|
4692
|
+
padding: 4px;
|
|
4693
|
+
}
|
|
4694
|
+
|
|
4695
|
+
.home-sort-tab {
|
|
4696
|
+
background: none;
|
|
4697
|
+
border: none;
|
|
4698
|
+
color: var(--text-muted);
|
|
4699
|
+
padding: 6px 12px;
|
|
4700
|
+
border-radius: 6px;
|
|
4701
|
+
font-size: 12px;
|
|
4702
|
+
cursor: pointer;
|
|
4703
|
+
transition: all 0.2s;
|
|
4704
|
+
}
|
|
4705
|
+
|
|
4706
|
+
.home-sort-tab.active {
|
|
4707
|
+
background: var(--accent);
|
|
4708
|
+
color: white;
|
|
4709
|
+
}
|
|
4710
|
+
|
|
4711
|
+
.home-sort-tab:hover:not(.active) {
|
|
4712
|
+
background: var(--bg-card);
|
|
4713
|
+
color: var(--text);
|
|
4714
|
+
}
|
|
4715
|
+
|
|
4716
|
+
/* Community Grid */
|
|
4717
|
+
.home-community-grid {
|
|
4718
|
+
display: grid;
|
|
4719
|
+
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
|
4720
|
+
gap: 16px;
|
|
4721
|
+
}
|
|
4722
|
+
|
|
4723
|
+
.home-community-empty {
|
|
4724
|
+
grid-column: 1 / -1;
|
|
4725
|
+
text-align: center;
|
|
4726
|
+
padding: 48px;
|
|
4727
|
+
color: var(--text-muted);
|
|
4728
|
+
}
|
|
4729
|
+
|
|
4730
|
+
.home-community-empty h4 {
|
|
4731
|
+
font-size: 16px;
|
|
4732
|
+
margin-bottom: 8px;
|
|
4733
|
+
color: var(--text);
|
|
4734
|
+
}
|
|
4735
|
+
|
|
4736
|
+
/* Quick Actions */
|
|
4737
|
+
.home-quick-actions {
|
|
4738
|
+
display: grid;
|
|
4739
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
4740
|
+
gap: 16px;
|
|
4741
|
+
}
|
|
4742
|
+
|
|
4743
|
+
.home-quick-action {
|
|
4744
|
+
display: flex;
|
|
4745
|
+
flex-direction: column;
|
|
4746
|
+
align-items: center;
|
|
4747
|
+
gap: 12px;
|
|
4748
|
+
background: var(--bg-card);
|
|
4749
|
+
border: 1px solid var(--border);
|
|
4750
|
+
border-radius: 12px;
|
|
4751
|
+
padding: 24px;
|
|
4752
|
+
cursor: pointer;
|
|
4753
|
+
transition: all 0.2s;
|
|
4754
|
+
text-decoration: none;
|
|
4755
|
+
color: var(--text);
|
|
4756
|
+
animation: fadeInUp 0.6s ease-out;
|
|
4757
|
+
}
|
|
4758
|
+
|
|
4759
|
+
.home-quick-action:hover {
|
|
4760
|
+
background: var(--bg-surface);
|
|
4761
|
+
border-color: var(--accent);
|
|
4762
|
+
transform: translateY(-4px);
|
|
4763
|
+
box-shadow: 0 12px 40px rgba(0, 212, 170, 0.1);
|
|
4764
|
+
color: var(--text);
|
|
4765
|
+
}
|
|
4766
|
+
|
|
4767
|
+
.home-quick-action svg {
|
|
4768
|
+
width: 24px;
|
|
4769
|
+
height: 24px;
|
|
4770
|
+
color: var(--accent);
|
|
4771
|
+
}
|
|
4772
|
+
|
|
4773
|
+
.home-quick-action span {
|
|
4774
|
+
font-size: 14px;
|
|
4775
|
+
font-weight: 500;
|
|
4776
|
+
text-align: center;
|
|
4777
|
+
}
|
|
4778
|
+
|
|
4779
|
+
/* Footer */
|
|
4780
|
+
.home-footer {
|
|
4781
|
+
margin-top: 64px;
|
|
4782
|
+
padding-top: 32px;
|
|
4783
|
+
border-top: 1px solid var(--border);
|
|
4784
|
+
text-align: center;
|
|
4785
|
+
}
|
|
4786
|
+
|
|
4787
|
+
.home-footer-links {
|
|
4788
|
+
display: flex;
|
|
4789
|
+
justify-content: center;
|
|
4790
|
+
gap: 24px;
|
|
4791
|
+
margin-bottom: 16px;
|
|
4792
|
+
flex-wrap: wrap;
|
|
4793
|
+
}
|
|
4794
|
+
|
|
4795
|
+
.home-footer-links a {
|
|
4796
|
+
color: var(--text-dim);
|
|
4797
|
+
text-decoration: none;
|
|
4798
|
+
font-size: 12px;
|
|
4799
|
+
transition: color 0.2s;
|
|
4800
|
+
}
|
|
4801
|
+
|
|
4802
|
+
.home-footer-links a:hover {
|
|
4803
|
+
color: var(--accent);
|
|
4804
|
+
}
|
|
4805
|
+
|
|
4806
|
+
.home-footer-branding {
|
|
4807
|
+
display: flex;
|
|
4808
|
+
flex-direction: column;
|
|
4809
|
+
gap: 4px;
|
|
4810
|
+
color: var(--text-muted);
|
|
4811
|
+
font-size: 11px;
|
|
4812
|
+
}
|
|
4813
|
+
|
|
4814
|
+
/* Animations */
|
|
4815
|
+
@keyframes fadeIn {
|
|
4816
|
+
from { opacity: 0; }
|
|
4817
|
+
to { opacity: 1; }
|
|
4818
|
+
}
|
|
4819
|
+
|
|
4820
|
+
@keyframes fadeInUp {
|
|
4821
|
+
from {
|
|
4822
|
+
opacity: 0;
|
|
4823
|
+
transform: translateY(20px);
|
|
4824
|
+
}
|
|
4825
|
+
to {
|
|
4826
|
+
opacity: 1;
|
|
4827
|
+
transform: translateY(0);
|
|
4828
|
+
}
|
|
4829
|
+
}
|
|
4830
|
+
|
|
4831
|
+
@keyframes slideDown {
|
|
4832
|
+
from {
|
|
4833
|
+
opacity: 0;
|
|
4834
|
+
transform: translateY(-10px);
|
|
4835
|
+
}
|
|
4836
|
+
to {
|
|
4837
|
+
opacity: 1;
|
|
4838
|
+
transform: translateY(0);
|
|
4839
|
+
}
|
|
4840
|
+
}
|
|
4841
|
+
|
|
4842
|
+
/* Responsive */
|
|
4843
|
+
@media (max-width: 1024px) {
|
|
4844
|
+
.home-content {
|
|
4845
|
+
padding: 24px;
|
|
4846
|
+
}
|
|
4847
|
+
|
|
4848
|
+
.home-quick-actions {
|
|
4849
|
+
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
|
|
4850
|
+
}
|
|
4851
|
+
|
|
4852
|
+
.home-community-grid {
|
|
4853
|
+
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
4854
|
+
}
|
|
4855
|
+
}
|
|
4856
|
+
|
|
4857
|
+
@media (max-width: 768px) {
|
|
4858
|
+
.home-header {
|
|
4859
|
+
padding: 16px 24px 12px;
|
|
4860
|
+
}
|
|
4861
|
+
|
|
4862
|
+
.home-content {
|
|
4863
|
+
padding: 16px;
|
|
4864
|
+
}
|
|
4865
|
+
|
|
4866
|
+
.home-section-header {
|
|
4867
|
+
flex-direction: column;
|
|
4868
|
+
align-items: flex-start;
|
|
4869
|
+
gap: 12px;
|
|
4870
|
+
}
|
|
4871
|
+
|
|
4872
|
+
.home-footer-links {
|
|
4873
|
+
flex-direction: column;
|
|
4874
|
+
gap: 12px;
|
|
4875
|
+
}
|
|
4876
|
+
}
|
|
4877
|
+
</style>
|
|
4878
|
+
</head>
|
|
4879
|
+
<body>
|
|
4880
|
+
|
|
4881
|
+
<!-- Lucide Icons (lucide.dev) — stroke-based, 16×16 -->
|
|
4882
|
+
<svg xmlns="http://www.w3.org/2000/svg" style="display:none;">
|
|
4883
|
+
<!-- Zap (Embed) -->
|
|
4884
|
+
<symbol id="lg-lightning" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
4885
|
+
<path d="M13 2 3 14h9l-1 8 10-12h-9l1-8z"/>
|
|
4886
|
+
</symbol>
|
|
4887
|
+
<!-- Arrow Left Right (Compare) -->
|
|
4888
|
+
<symbol id="lg-arrows" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
4889
|
+
<path d="M8 3 4 7l4 4"/><path d="M4 7h16"/><path d="m16 21 4-4-4-4"/><path d="M20 17H4"/>
|
|
4890
|
+
</symbol>
|
|
4891
|
+
<!-- Search -->
|
|
4892
|
+
<symbol id="lg-search" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
4893
|
+
<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>
|
|
4894
|
+
</symbol>
|
|
4895
|
+
<!-- Gauge (Benchmark) -->
|
|
4896
|
+
<symbol id="lg-gauge" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
4897
|
+
<path d="m12 14 4-4"/><path d="M3.34 19a10 10 0 1 1 17.32 0"/>
|
|
4898
|
+
</symbol>
|
|
4899
|
+
<!-- Lightbulb (Explore) -->
|
|
4900
|
+
<symbol id="lg-bulb" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
4901
|
+
<path d="M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5"/><path d="M9 18h6"/><path d="M10 22h4"/>
|
|
4902
|
+
</symbol>
|
|
4903
|
+
<!-- Info (About) -->
|
|
4904
|
+
<symbol id="lg-info" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
4905
|
+
<circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/>
|
|
4906
|
+
</symbol>
|
|
4907
|
+
<!-- Image (Multimodal) -->
|
|
4908
|
+
<symbol id="lg-image" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
4909
|
+
<rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/>
|
|
4910
|
+
</symbol>
|
|
4911
|
+
<!-- Settings (Config) -->
|
|
4912
|
+
<symbol id="lg-config" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
4913
|
+
<path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/>
|
|
4914
|
+
</symbol>
|
|
4915
|
+
<!-- Code (Generate) -->
|
|
4916
|
+
<symbol id="lg-code" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
4917
|
+
<polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/>
|
|
4918
|
+
</symbol>
|
|
4919
|
+
<!-- Palette (Theme) -->
|
|
4920
|
+
<symbol id="lg-palette" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
4921
|
+
<circle cx="13.5" cy="6.5" r=".5" fill="currentColor"/><circle cx="17.5" cy="10.5" r=".5" fill="currentColor"/><circle cx="8.5" cy="7.5" r=".5" fill="currentColor"/><circle cx="6.5" cy="12.5" r=".5" fill="currentColor"/><path d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10c.926 0 1.648-.746 1.648-1.688 0-.437-.18-.835-.437-1.125-.29-.289-.438-.652-.438-1.125a1.64 1.64 0 0 1 1.668-1.668h1.996c3.051 0 5.555-2.503 5.555-5.554C21.965 6.012 17.461 2 12 2z"/>
|
|
4922
|
+
</symbol>
|
|
4923
|
+
<!-- Box (Cube) -->
|
|
4924
|
+
<symbol id="lg-cube" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
4925
|
+
<path d="M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z"/><path d="m3.3 7 8.7 5 8.7-5"/><path d="M12 22V12"/>
|
|
4926
|
+
</symbol>
|
|
4927
|
+
<!-- Message Square (Chat) -->
|
|
4928
|
+
<symbol id="lg-chat" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
4929
|
+
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
|
|
4930
|
+
</symbol>
|
|
4931
|
+
<!-- Shield -->
|
|
4932
|
+
<symbol id="lg-shield" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
4933
|
+
<path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 .5-.87l7-4a1 1 0 0 1 1 0l7 4A1 1 0 0 1 20 6z"/>
|
|
4934
|
+
</symbol>
|
|
4935
|
+
<!-- Activity (Pulse) -->
|
|
4936
|
+
<symbol id="lg-pulse" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
4937
|
+
<path d="M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36-3.18-19.64A2 2 0 0 0 10.12 1h-.24a2 2 0 0 0-1.94 1.55L5.18 12H2"/>
|
|
4938
|
+
</symbol>
|
|
4939
|
+
<!-- Brain (Thinking) -->
|
|
4940
|
+
<symbol id="lg-brain" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
4941
|
+
<path d="M12 5a3 3 0 1 0-5.997.125 4 4 0 0 0-2.526 5.77 4 4 0 0 0 .556 6.588A4 4 0 1 0 12 18Z"/><path d="M12 5a3 3 0 1 1 5.997.125 4 4 0 0 1 2.526 5.77 4 4 0 0 1-.556 6.588A4 4 0 1 1 12 18Z"/><path d="M15 13a4.5 4.5 0 0 1-3-4 4.5 4.5 0 0 1-3 4"/><path d="M17.599 6.5a3 3 0 0 0 .399-1.375"/><path d="M6.003 5.125A3 3 0 0 0 6.401 6.5"/><path d="M3.477 10.896a4 4 0 0 1 .585-.396"/><path d="M19.938 10.5a4 4 0 0 1 .585.396"/><path d="M6 18a4 4 0 0 1-1.967-.516"/><path d="M19.967 17.484A4 4 0 0 1 18 18"/>
|
|
4942
|
+
</symbol>
|
|
4943
|
+
<!-- Sparkles -->
|
|
4944
|
+
<symbol id="lg-sparkles" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
4945
|
+
<path d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"/><path d="M20 3v4"/><path d="M22 5h-4"/><path d="M4 17v2"/><path d="M5 18H3"/>
|
|
4946
|
+
</symbol>
|
|
4947
|
+
<!-- Target (Similarity) -->
|
|
4948
|
+
<symbol id="lg-target" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
4949
|
+
<circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="6"/><circle cx="12" cy="12" r="2"/>
|
|
4950
|
+
</symbol>
|
|
4951
|
+
<!-- Database (Collections) -->
|
|
4952
|
+
<symbol id="lg-database" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
4953
|
+
<ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M3 5V19A9 3 0 0 0 21 19V5"/><path d="M3 12A9 3 0 0 0 21 12"/>
|
|
4954
|
+
</symbol>
|
|
4955
|
+
<!-- Bot (Models) -->
|
|
4956
|
+
<symbol id="lg-bot" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
4957
|
+
<path d="M12 8V4H8"/><rect width="16" height="12" x="4" y="8" rx="2"/><path d="M2 14h2"/><path d="M20 14h2"/><path d="M15 13v2"/><path d="M9 13v2"/>
|
|
4958
|
+
</symbol>
|
|
4959
|
+
<!-- ArrowUpDown (Rerank) -->
|
|
4960
|
+
<symbol id="lg-arrow-up-down" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
4961
|
+
<path d="m21 16-4 4-4-4"/><path d="M17 20V4"/><path d="m3 8 4-4 4 4"/><path d="M7 4v16"/>
|
|
4962
|
+
</symbol>
|
|
4963
|
+
<!-- Coins (Cost) -->
|
|
4964
|
+
<symbol id="lg-coins" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
4965
|
+
<circle cx="8" cy="8" r="6"/><path d="M18.09 10.37A6 6 0 1 1 10.34 18"/><path d="M7 6h1v4"/><path d="m16.71 13.88.7.71-2.82 2.82"/>
|
|
4966
|
+
</symbol>
|
|
4967
|
+
<!-- Inbox (Ingest) -->
|
|
4968
|
+
<symbol id="lg-inbox" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
4969
|
+
<polyline points="22 12 16 12 14 15 10 15 8 12 2 12"/><path d="M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"/>
|
|
4970
|
+
</symbol>
|
|
4971
|
+
<!-- SearchCode (Vector Search) -->
|
|
4972
|
+
<symbol id="lg-search-code" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
4973
|
+
<path d="m13 13.5 2-2.5-2-2.5"/><path d="m9 8.5-2 2.5 2 2.5"/><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>
|
|
4974
|
+
</symbol>
|
|
4975
|
+
</svg>
|
|
4976
|
+
|
|
4977
|
+
<div class="app-shell">
|
|
4978
|
+
|
|
4979
|
+
<!-- Sidebar -->
|
|
4980
|
+
<aside class="sidebar">
|
|
4981
|
+
<div class="sidebar-drag-region">
|
|
4982
|
+
<img class="sidebar-logo" id="sidebarLogo" src="/icons/dark/64.png" alt="Vai">
|
|
4983
|
+
<span class="sidebar-title">Vai</span>
|
|
4984
|
+
<button class="sidebar-collapse-btn" id="sidebarCollapseBtn" onclick="toggleSidebarCollapse()" title="Toggle sidebar"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"/></svg></button>
|
|
4985
|
+
<button class="sidebar-settings-btn" data-tab="settings" title="Settings"><svg width="16" height="16" viewBox="0 0 24 24"><use href="#lg-config"/></svg></button>
|
|
4986
|
+
</div>
|
|
4987
|
+
<nav class="sidebar-nav">
|
|
4988
|
+
<div class="sidebar-nav-group" role="tablist" aria-label="Tools">
|
|
4989
|
+
<div class="sidebar-nav-label" id="nav-tools-label">Tools</div>
|
|
4990
|
+
<button class="tab-btn active" data-tab="home" data-tooltip="Home" role="tab" aria-selected="true" aria-controls="tab-home" id="tab-btn-home"><span class="tab-btn-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8"/><path d="M3 10a2 2 0 0 1 .709-1.528l7-5.999a2 2 0 0 1 2.582 0l7 5.999A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg></span><span>Home</span></button>
|
|
4991
|
+
<button class="tab-btn" data-tab="embed" data-tooltip="Embed" role="tab" aria-selected="false" aria-controls="tab-embed" id="tab-btn-embed"><span class="tab-btn-icon" aria-hidden="true"><svg><use href="#lg-lightning"/></svg></span><span>Embed</span></button>
|
|
4992
|
+
<button class="tab-btn" data-tab="compare" data-tooltip="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>
|
|
4993
|
+
<button class="tab-btn" data-tab="search" data-tooltip="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>
|
|
4994
|
+
<button class="tab-btn" data-tab="multimodal" data-tooltip="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>
|
|
4995
|
+
<button class="tab-btn" data-tab="generate" data-tooltip="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>
|
|
4996
|
+
<button class="tab-btn" data-tab="chat" data-tooltip="Chat" role="tab" aria-selected="false" aria-controls="tab-chat" id="tab-btn-chat"><span class="tab-btn-icon" aria-hidden="true"><svg><use href="#lg-chat"/></svg></span><span>Chat</span></button>
|
|
4997
|
+
<button class="tab-btn" data-tab="workflows" data-tooltip="Workflows" role="tab" aria-selected="false" aria-controls="tab-workflows" id="tab-btn-workflows"><span class="tab-btn-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><circle cx="5" cy="12" r="3"/><circle cx="19" cy="6" r="3"/><circle cx="19" cy="18" r="3"/><line x1="7.7" y1="10.7" x2="16.3" y2="7.3"/><line x1="7.7" y1="13.3" x2="16.3" y2="16.7"/></svg></span><span>Workflows</span></button>
|
|
4998
|
+
</div>
|
|
4999
|
+
<div class="sidebar-nav-divider"></div>
|
|
5000
|
+
<div class="sidebar-nav-group" role="tablist" aria-label="Learn">
|
|
5001
|
+
<div class="sidebar-nav-label" id="nav-learn-label">Learn</div>
|
|
5002
|
+
<button class="tab-btn" data-tab="benchmark" data-tooltip="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>
|
|
5003
|
+
<button class="tab-btn" data-tab="explore" data-tooltip="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>
|
|
5004
|
+
<button class="tab-btn" data-tab="about" data-tooltip="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>
|
|
5005
|
+
</div>
|
|
5006
|
+
</nav>
|
|
5007
|
+
<div class="sidebar-footer">
|
|
5008
|
+
<div style="display:flex;align-items:center;gap:8px;justify-content:space-between;">
|
|
5009
|
+
<div style="display:flex;align-items:center;gap:5px;">
|
|
5010
|
+
<div class="status-dot" id="statusDot"></div>
|
|
5011
|
+
<span class="status-label" id="statusLabel">Checking...</span>
|
|
5012
|
+
</div>
|
|
5013
|
+
<button class="theme-toggle" id="themeToggle" title="Toggle light/dark mode">🌙</button>
|
|
3654
5014
|
</div>
|
|
3655
5015
|
<div style="display:flex;align-items:center;justify-content:space-between;">
|
|
3656
5016
|
<div id="appVersionLabel" style="font-size:10px;color:var(--text-muted);"></div>
|
|
@@ -3687,8 +5047,172 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
3687
5047
|
</div>
|
|
3688
5048
|
<div class="main">
|
|
3689
5049
|
|
|
5050
|
+
<!-- ========== HOME TAB ========== -->
|
|
5051
|
+
<div class="tab-panel active" id="tab-home" role="tabpanel" aria-labelledby="tab-btn-home" tabindex="0">
|
|
5052
|
+
<!-- Header Bar -->
|
|
5053
|
+
<div class="home-header">
|
|
5054
|
+
<div class="home-header-left">
|
|
5055
|
+
<img class="home-logo" id="homeLogo" src="/icons/V.png" alt="Vai" onerror="this.src='/icons/dark/64.png'">
|
|
5056
|
+
<span class="home-header-title">Voyage AI Playground</span>
|
|
5057
|
+
<span class="home-version-badge" id="homeVersionBadge">v1.0.0</span>
|
|
5058
|
+
</div>
|
|
5059
|
+
<div class="home-header-right">
|
|
5060
|
+
<button class="home-settings-btn" onclick="switchTab('settings')" title="Settings">
|
|
5061
|
+
<svg width="20" height="20" viewBox="0 0 24 24"><use href="#lg-config"/></svg>
|
|
5062
|
+
</button>
|
|
5063
|
+
</div>
|
|
5064
|
+
</div>
|
|
5065
|
+
|
|
5066
|
+
<!-- API Key Warning Banner (hidden by default) -->
|
|
5067
|
+
<div class="home-api-warning" id="homeApiWarning" style="display: none;">
|
|
5068
|
+
<div class="home-api-warning-content">
|
|
5069
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75">
|
|
5070
|
+
<triangle cx="12" cy="12" r="10"/>
|
|
5071
|
+
<line x1="12" y1="8" x2="12" y2="12"/>
|
|
5072
|
+
<line x1="12" y1="16" x2="12.01" y2="16"/>
|
|
5073
|
+
</svg>
|
|
5074
|
+
<span>Set up your Voyage AI API key to get started</span>
|
|
5075
|
+
<button onclick="switchTab('settings')">Settings →</button>
|
|
5076
|
+
</div>
|
|
5077
|
+
</div>
|
|
5078
|
+
|
|
5079
|
+
<div class="home-content">
|
|
5080
|
+
<!-- Announcements Banner -->
|
|
5081
|
+
<div class="home-announcements" id="homeAnnouncements" style="display: none;">
|
|
5082
|
+
<div class="home-announcements-carousel" id="announcementsCarousel">
|
|
5083
|
+
<!-- Cards will be inserted here -->
|
|
5084
|
+
</div>
|
|
5085
|
+
<div class="home-announcements-dots" id="announcementsDots">
|
|
5086
|
+
<!-- Dots will be inserted here -->
|
|
5087
|
+
</div>
|
|
5088
|
+
<div class="home-announcements-restore" id="announcementsRestore" style="display: none;">
|
|
5089
|
+
<button onclick="restoreAnnouncements()">Show dismissed announcements</button>
|
|
5090
|
+
</div>
|
|
5091
|
+
</div>
|
|
5092
|
+
|
|
5093
|
+
<!-- What's New (Release Notes) -->
|
|
5094
|
+
<div class="home-section" id="homeReleases">
|
|
5095
|
+
<div class="home-section-header">
|
|
5096
|
+
<h3>What's New</h3>
|
|
5097
|
+
<a href="https://github.com/mrlynn/voyageai-cli/releases" target="_blank" rel="noopener">View All →</a>
|
|
5098
|
+
</div>
|
|
5099
|
+
<div class="home-releases-timeline" id="releasesTimeline">
|
|
5100
|
+
<!-- Release items will be inserted here -->
|
|
5101
|
+
</div>
|
|
5102
|
+
</div>
|
|
5103
|
+
|
|
5104
|
+
<!-- Marketplace Spotlight -->
|
|
5105
|
+
<div class="home-section">
|
|
5106
|
+
<div class="home-section-header">
|
|
5107
|
+
<h3>Marketplace Spotlight</h3>
|
|
5108
|
+
<button class="home-marketplace-cta" onclick="switchTab('workflows')">
|
|
5109
|
+
Explore the Marketplace →
|
|
5110
|
+
</button>
|
|
5111
|
+
</div>
|
|
5112
|
+
|
|
5113
|
+
<!-- Featured Workflows -->
|
|
5114
|
+
<div class="home-subsection">
|
|
5115
|
+
<h4>Featured Workflows</h4>
|
|
5116
|
+
<div class="home-workflows-carousel" id="featuredWorkflows">
|
|
5117
|
+
<!-- Workflow cards will be inserted here -->
|
|
5118
|
+
</div>
|
|
5119
|
+
</div>
|
|
5120
|
+
|
|
5121
|
+
<!-- Browse by Domain -->
|
|
5122
|
+
<div class="home-subsection">
|
|
5123
|
+
<h4>Browse by Domain</h4>
|
|
5124
|
+
<div class="home-domain-pills">
|
|
5125
|
+
<button class="home-domain-pill" onclick="filterAndSwitchWorkflows('Healthcare')">Healthcare</button>
|
|
5126
|
+
<button class="home-domain-pill" onclick="filterAndSwitchWorkflows('Finance')">Finance</button>
|
|
5127
|
+
<button class="home-domain-pill" onclick="filterAndSwitchWorkflows('Legal')">Legal</button>
|
|
5128
|
+
<button class="home-domain-pill" onclick="filterAndSwitchWorkflows('DevOps')">DevOps</button>
|
|
5129
|
+
<button class="home-domain-pill" onclick="filterAndSwitchWorkflows('Education')">Education</button>
|
|
5130
|
+
<button class="home-domain-pill" onclick="filterAndSwitchWorkflows('E-Commerce')">E-Commerce</button>
|
|
5131
|
+
<button class="home-domain-pill" onclick="filterAndSwitchWorkflows('Marketing')">Marketing</button>
|
|
5132
|
+
</div>
|
|
5133
|
+
</div>
|
|
5134
|
+
|
|
5135
|
+
<!-- Community Workflows -->
|
|
5136
|
+
<div class="home-subsection">
|
|
5137
|
+
<div class="home-subsection-header">
|
|
5138
|
+
<h4>Community Workflows</h4>
|
|
5139
|
+
<div class="home-sort-tabs">
|
|
5140
|
+
<button class="home-sort-tab active" data-sort="trending" onclick="sortCommunityWorkflows('trending')">Trending</button>
|
|
5141
|
+
<button class="home-sort-tab" data-sort="newest" onclick="sortCommunityWorkflows('newest')">Newest</button>
|
|
5142
|
+
<button class="home-sort-tab" data-sort="installs" onclick="sortCommunityWorkflows('installs')">Most Installed</button>
|
|
5143
|
+
</div>
|
|
5144
|
+
</div>
|
|
5145
|
+
<div class="home-community-grid" id="communityWorkflows">
|
|
5146
|
+
<!-- Community workflow cards will be inserted here -->
|
|
5147
|
+
</div>
|
|
5148
|
+
</div>
|
|
5149
|
+
</div>
|
|
5150
|
+
|
|
5151
|
+
<!-- Quick Actions -->
|
|
5152
|
+
<div class="home-section">
|
|
5153
|
+
<div class="home-section-header">
|
|
5154
|
+
<h3>Quick Actions</h3>
|
|
5155
|
+
</div>
|
|
5156
|
+
<div class="home-quick-actions">
|
|
5157
|
+
<button class="home-quick-action" onclick="switchTab('embed')" title="Generate embeddings for text">
|
|
5158
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75">
|
|
5159
|
+
<path d="M13 2 3 14h9l-1 8 10-12h-9l1-8z"/>
|
|
5160
|
+
</svg>
|
|
5161
|
+
<span>New Embedding</span>
|
|
5162
|
+
</button>
|
|
5163
|
+
<button class="home-quick-action" onclick="switchTab('compare')" title="Compare model similarities">
|
|
5164
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75">
|
|
5165
|
+
<path d="M21 12c0 1.2-4.03 6-9 6s-9-4.8-9-6c0-1.2 4.03-6 9-6s9 4.8 9 6"/>
|
|
5166
|
+
<circle cx="12" cy="12" r="3"/>
|
|
5167
|
+
</svg>
|
|
5168
|
+
<span>Compare Models</span>
|
|
5169
|
+
</button>
|
|
5170
|
+
<button class="home-quick-action" onclick="switchTab('search')" title="Search through vectors">
|
|
5171
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75">
|
|
5172
|
+
<circle cx="11" cy="11" r="8"/>
|
|
5173
|
+
<path d="m21 21-4.35-4.35"/>
|
|
5174
|
+
</svg>
|
|
5175
|
+
<span>Search Vectors</span>
|
|
5176
|
+
</button>
|
|
5177
|
+
<button class="home-quick-action" onclick="switchTab('workflows')" title="View installed workflows">
|
|
5178
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75">
|
|
5179
|
+
<circle cx="5" cy="12" r="3"/>
|
|
5180
|
+
<circle cx="19" cy="6" r="3"/>
|
|
5181
|
+
<circle cx="19" cy="18" r="3"/>
|
|
5182
|
+
<line x1="7.7" y1="10.7" x2="16.3" y2="7.3"/>
|
|
5183
|
+
<line x1="7.7" y1="13.3" x2="16.3" y2="16.7"/>
|
|
5184
|
+
</svg>
|
|
5185
|
+
<span>My Workflows</span>
|
|
5186
|
+
</button>
|
|
5187
|
+
<button class="home-quick-action" onclick="publishWorkflow()" title="Publish a new workflow">
|
|
5188
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75">
|
|
5189
|
+
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
|
|
5190
|
+
<polyline points="17,8 12,3 7,8"/>
|
|
5191
|
+
<line x1="12" y1="3" x2="12" y2="15"/>
|
|
5192
|
+
</svg>
|
|
5193
|
+
<span>Publish Workflow</span>
|
|
5194
|
+
</button>
|
|
5195
|
+
</div>
|
|
5196
|
+
</div>
|
|
5197
|
+
|
|
5198
|
+
<!-- Footer -->
|
|
5199
|
+
<div class="home-footer">
|
|
5200
|
+
<div class="home-footer-links">
|
|
5201
|
+
<a href="https://docs.vaicli.com" target="_blank" rel="noopener">Documentation</a>
|
|
5202
|
+
<a href="https://github.com/mrlynn/voyageai-cli" target="_blank" rel="noopener">GitHub</a>
|
|
5203
|
+
<a href="#" onclick="reportBug()">Report Bug</a>
|
|
5204
|
+
<!-- <a href="#" target="_blank" rel="noopener">Community / Discord</a> -->
|
|
5205
|
+
</div>
|
|
5206
|
+
<div class="home-footer-branding">
|
|
5207
|
+
<span>Built with Voyage AI + MongoDB Atlas</span>
|
|
5208
|
+
<span id="homeFooterVersion">Version 1.0.0 • © 2026 VAI</span>
|
|
5209
|
+
</div>
|
|
5210
|
+
</div>
|
|
5211
|
+
</div>
|
|
5212
|
+
</div>
|
|
5213
|
+
|
|
3690
5214
|
<!-- ========== EMBED TAB ========== -->
|
|
3691
|
-
<div class="tab-panel
|
|
5215
|
+
<div class="tab-panel" id="tab-embed" role="tabpanel" aria-labelledby="tab-btn-embed" tabindex="0">
|
|
3692
5216
|
<div class="page-header">
|
|
3693
5217
|
<h2 class="page-header-title">Embed</h2>
|
|
3694
5218
|
<p class="page-header-subtitle">Generate vector embeddings for text</p>
|
|
@@ -4492,12 +6016,20 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
4492
6016
|
<!-- ========== WORKFLOWS TAB ========== -->
|
|
4493
6017
|
<div class="tab-panel" id="tab-workflows" role="tabpanel" aria-labelledby="tab-btn-workflows" tabindex="0">
|
|
4494
6018
|
<div class="wf-container">
|
|
4495
|
-
<div class="wf-library">
|
|
6019
|
+
<div class="wf-library" id="wfLibrary">
|
|
4496
6020
|
<div class="wf-library-header">
|
|
4497
|
-
<div class="wf-library-tabs">
|
|
6021
|
+
<div class="wf-library-tabs" style="flex:1;">
|
|
4498
6022
|
<button class="wf-lib-tab active" data-lib-tab="library" onclick="wfSwitchLibTab('library')">Library</button>
|
|
4499
6023
|
<button class="wf-lib-tab" data-lib-tab="palette" onclick="wfSwitchLibTab('palette')">Palette</button>
|
|
4500
6024
|
</div>
|
|
6025
|
+
<button class="wf-store-btn" id="wfStoreBtn" onclick="wfStoreOpen()" title="Browse Workflow Store">
|
|
6026
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/></svg>
|
|
6027
|
+
</button>
|
|
6028
|
+
<button class="wf-library-collapse-btn" onclick="wfToggleLibrary()" title="Collapse library">‹</button>
|
|
6029
|
+
</div>
|
|
6030
|
+
<div class="wf-library-search-wrap">
|
|
6031
|
+
<input type="text" id="wfLibrarySearch" placeholder="Search workflows..." oninput="wfOnLibrarySearch()">
|
|
6032
|
+
<button class="wf-library-search-clear" id="wfLibrarySearchClear" onclick="wfClearLibrarySearch()">✕</button>
|
|
4501
6033
|
</div>
|
|
4502
6034
|
<div class="wf-library-list" id="wfLibraryList">
|
|
4503
6035
|
<div style="padding: 16px; color: var(--text-muted); font-size: 12px;">Loading...</div>
|
|
@@ -4528,7 +6060,15 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
4528
6060
|
<button onclick="wfExportJson()" id="wfExportBtn" disabled title="Export workflow JSON">
|
|
4529
6061
|
<svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M3 2h10M8 14V5M5 8l3-3 3 3"/></svg>
|
|
4530
6062
|
</button>
|
|
6063
|
+
<span class="wf-toolbar-spacer"></span>
|
|
6064
|
+
<div class="wf-toolbar-toggle-group">
|
|
6065
|
+
<button id="wfToggleLibBtn" class="active" onclick="wfToggleLibrary()">Library</button>
|
|
6066
|
+
<button id="wfTogglePropsBtn" class="active" onclick="wfToggleInspector()">Props</button>
|
|
6067
|
+
</div>
|
|
4531
6068
|
</div>
|
|
6069
|
+
<!-- Edge handles for collapsed panels -->
|
|
6070
|
+
<div class="wf-edge-handle wf-edge-handle--left" id="wfEdgeHandleLeft" onclick="wfToggleLibrary()" title="Expand library">›</div>
|
|
6071
|
+
<div class="wf-edge-handle wf-edge-handle--right" id="wfEdgeHandleRight" onclick="wfToggleInspector()" title="Expand properties">‹</div>
|
|
4532
6072
|
<!-- Execution status bar -->
|
|
4533
6073
|
<div class="wf-exec-status" id="wfExecStatus" style="display:none;">
|
|
4534
6074
|
<span class="wf-exec-status-dot"></span>
|
|
@@ -4541,9 +6081,32 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
4541
6081
|
</div>
|
|
4542
6082
|
<div class="wf-canvas-watermark" id="wfCanvasWatermark"><img src="/icons/watermark.png" alt=""></div>
|
|
4543
6083
|
<svg id="wf-canvas" xmlns="http://www.w3.org/2000/svg" ondragover="event.preventDefault()" ondrop="wfCanvasDrop(event)"></svg>
|
|
6084
|
+
<!-- Workflow Store Overlay -->
|
|
6085
|
+
<div class="wf-store-overlay" id="wfStoreOverlay">
|
|
6086
|
+
<div class="wf-store-header">
|
|
6087
|
+
<button class="wf-store-back" onclick="wfStoreClose()">← Library</button>
|
|
6088
|
+
<div class="wf-store-title-area">
|
|
6089
|
+
<span class="wf-store-title">Workflow Store</span>
|
|
6090
|
+
<span class="wf-store-badge">@vaicli</span>
|
|
6091
|
+
</div>
|
|
6092
|
+
<div class="wf-store-search-wrap">
|
|
6093
|
+
<span class="wf-store-search-icon">⌕</span>
|
|
6094
|
+
<input class="wf-store-search" id="wfStoreSearch" placeholder="Search..." oninput="wfStoreRender()">
|
|
6095
|
+
</div>
|
|
6096
|
+
<select class="wf-store-sort" id="wfStoreSort" onchange="wfStoreRender()">
|
|
6097
|
+
<option value="downloads">Popular</option>
|
|
6098
|
+
<option value="name">Name A–Z</option>
|
|
6099
|
+
<option value="complexity">Complex</option>
|
|
6100
|
+
</select>
|
|
6101
|
+
</div>
|
|
6102
|
+
<div class="wf-store-body">
|
|
6103
|
+
<div class="wf-store-inner" id="wfStoreContent">
|
|
6104
|
+
<div style="padding:40px;text-align:center;color:var(--text-muted);">Loading catalog...</div>
|
|
6105
|
+
</div>
|
|
6106
|
+
</div>
|
|
6107
|
+
</div>
|
|
4544
6108
|
</div>
|
|
4545
6109
|
<div class="wf-inspector collapsed" id="wfInspector">
|
|
4546
|
-
<button class="wf-inspector-toggle" id="wfInspectorToggle" onclick="wfToggleInspector()" title="Toggle inspector">‹</button>
|
|
4547
6110
|
<div class="wf-inspector-content">
|
|
4548
6111
|
<div class="wf-inspector-header" id="wfInspectorHeader">Inspector</div>
|
|
4549
6112
|
<div class="wf-inspector-body" id="wfInspectorBody">
|
|
@@ -4571,6 +6134,14 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
4571
6134
|
</div>
|
|
4572
6135
|
</div>
|
|
4573
6136
|
|
|
6137
|
+
<!-- ── Workflow Node Help Modal ── -->
|
|
6138
|
+
<div class="wf-help-modal-backdrop" id="wfHelpModalBackdrop" style="display:none;" onclick="wfCloseHelpModal()">
|
|
6139
|
+
<div class="wf-help-modal" onclick="event.stopPropagation()">
|
|
6140
|
+
<div class="wf-help-modal-header" id="wfHelpModalHeader"></div>
|
|
6141
|
+
<div class="wf-help-modal-body" id="wfHelpModalBody"></div>
|
|
6142
|
+
</div>
|
|
6143
|
+
</div>
|
|
6144
|
+
|
|
4574
6145
|
<!-- ── Workflow Input Modal (pre-execution) ── -->
|
|
4575
6146
|
<div class="wf-input-modal-backdrop" id="wfInputModalBackdrop" style="display:none;" onclick="wfCloseInputModal()">
|
|
4576
6147
|
<div class="wf-input-modal" onclick="event.stopPropagation()">
|
|
@@ -4665,13 +6236,39 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
4665
6236
|
<div class="card" style="margin-top:16px;">
|
|
4666
6237
|
<div class="about-section" style="padding-bottom:0;">
|
|
4667
6238
|
<div class="about-section-title">What's New</div>
|
|
4668
|
-
<div class="about-
|
|
4669
|
-
<
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
<
|
|
4674
|
-
|
|
6239
|
+
<div class="about-changelog">
|
|
6240
|
+
<details open>
|
|
6241
|
+
<summary><strong>v1.28</strong> — Lucide icon overhaul</summary>
|
|
6242
|
+
<p>Replaced all emoji and filled LeafyGreen icons with clean, stroke-based Lucide SVGs across the entire app — tabs, workflow nodes, action buttons, benchmark page, explore cards, settings, and more. Added reusable icon helper and path library.</p>
|
|
6243
|
+
</details>
|
|
6244
|
+
<details>
|
|
6245
|
+
<summary><strong>v1.27</strong> — Workflow builder improvements</summary>
|
|
6246
|
+
<p>Workflow palette with drag-and-drop node creation, edge drawing, dry-run preview, and builder mode toggle. New workflow node types: query, rerank, search, ingest.</p>
|
|
6247
|
+
</details>
|
|
6248
|
+
<details>
|
|
6249
|
+
<summary><strong>v1.26</strong> — Agent workflows</summary>
|
|
6250
|
+
<p>Agent workflows with thinking panel & markdown rendering, multi-step tool orchestration, interactive DAG visualization with execution animation.</p>
|
|
6251
|
+
</details>
|
|
6252
|
+
<details>
|
|
6253
|
+
<summary><strong>v1.25</strong> — Code generation & scaffolding</summary>
|
|
6254
|
+
<p>Generate embedding/search code snippets and scaffold full project directories with best-practice structure, ready-to-run RAG pipelines.</p>
|
|
6255
|
+
</details>
|
|
6256
|
+
<details>
|
|
6257
|
+
<summary><strong>v1.24</strong> — MCP server management</summary>
|
|
6258
|
+
<p>MCP server install/uninstall/status commands. Electron app v1.5 with signed macOS builds.</p>
|
|
6259
|
+
</details>
|
|
6260
|
+
<details>
|
|
6261
|
+
<summary><strong>v1.23</strong> — MCP server</summary>
|
|
6262
|
+
<p>Expose vai tools to AI agents via MCP protocol. HTTP transport, bearer auth, 71+ MCP tests.</p>
|
|
6263
|
+
</details>
|
|
6264
|
+
<details>
|
|
6265
|
+
<summary><strong>v1.22</strong> — RAG chat</summary>
|
|
6266
|
+
<p>RAG chat with smart source labels, configurable system prompts, streaming responses.</p>
|
|
6267
|
+
</details>
|
|
6268
|
+
<details>
|
|
6269
|
+
<summary><strong>v1.2</strong> — Multimodal & more</summary>
|
|
6270
|
+
<p>Multimodal tab, 4 new Explore concepts, auto-update, hidden easter egg <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;"><rect width="14" height="20" x="5" y="2" rx="2" ry="2"/><path d="M12 18h.01"/></svg></p>
|
|
6271
|
+
</details>
|
|
4675
6272
|
</div>
|
|
4676
6273
|
</div>
|
|
4677
6274
|
</div>
|
|
@@ -4976,6 +6573,26 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
4976
6573
|
</select>
|
|
4977
6574
|
</div>
|
|
4978
6575
|
</div>
|
|
6576
|
+
<div class="settings-row">
|
|
6577
|
+
<div class="settings-label">
|
|
6578
|
+
<span class="settings-label-text">Default Tab</span>
|
|
6579
|
+
<span class="settings-label-hint">Which tab to open when the app starts</span>
|
|
6580
|
+
</div>
|
|
6581
|
+
<div class="settings-control">
|
|
6582
|
+
<select class="settings-select" id="settingsDefaultTab">
|
|
6583
|
+
<option value="home" selected>Home</option>
|
|
6584
|
+
<option value="embed">Embed</option>
|
|
6585
|
+
<option value="compare">Compare</option>
|
|
6586
|
+
<option value="search">Search</option>
|
|
6587
|
+
<option value="multimodal">Multimodal</option>
|
|
6588
|
+
<option value="generate">Generate</option>
|
|
6589
|
+
<option value="chat">Chat</option>
|
|
6590
|
+
<option value="workflows">Workflows</option>
|
|
6591
|
+
<option value="benchmark">Benchmark</option>
|
|
6592
|
+
<option value="explore">Explore</option>
|
|
6593
|
+
</select>
|
|
6594
|
+
</div>
|
|
6595
|
+
</div>
|
|
4979
6596
|
</div>
|
|
4980
6597
|
</div>
|
|
4981
6598
|
|
|
@@ -5440,6 +7057,17 @@ function sendTelemetry(event, extra = {}) {
|
|
|
5440
7057
|
} catch { /* telemetry should never break the app */ }
|
|
5441
7058
|
}
|
|
5442
7059
|
|
|
7060
|
+
function telemetryTimer(event, baseFields = {}) {
|
|
7061
|
+
const start = performance.now();
|
|
7062
|
+
return (extra = {}) => {
|
|
7063
|
+
sendTelemetry(event, {
|
|
7064
|
+
...baseFields,
|
|
7065
|
+
...extra,
|
|
7066
|
+
durationMs: Math.round(performance.now() - start),
|
|
7067
|
+
});
|
|
7068
|
+
};
|
|
7069
|
+
}
|
|
7070
|
+
|
|
5443
7071
|
function initTelemetryToggle() {
|
|
5444
7072
|
const toggle = document.getElementById('settingsTelemetry');
|
|
5445
7073
|
if (!toggle) return;
|
|
@@ -5464,9 +7092,15 @@ async function init() {
|
|
|
5464
7092
|
populateModelSelects();
|
|
5465
7093
|
buildExploreCards();
|
|
5466
7094
|
|
|
7095
|
+
// Apply default tab setting
|
|
7096
|
+
const settings = JSON.parse(localStorage.getItem('vai-settings') || '{}');
|
|
7097
|
+
const defaultTab = settings.defaultTab || 'home';
|
|
7098
|
+
switchTab(defaultTab);
|
|
7099
|
+
|
|
5467
7100
|
// Telemetry
|
|
5468
7101
|
initTelemetryToggle();
|
|
5469
7102
|
sendTelemetry('app_launch');
|
|
7103
|
+
sendTelemetry('playground_open');
|
|
5470
7104
|
}
|
|
5471
7105
|
|
|
5472
7106
|
// ── Tabs ──
|
|
@@ -5533,10 +7167,457 @@ function switchTab(tab) {
|
|
|
5533
7167
|
if (settingsBtn) settingsBtn.classList.toggle('active', tab === 'settings');
|
|
5534
7168
|
// Track tab views
|
|
5535
7169
|
sendTelemetry('tab_view', { tab });
|
|
7170
|
+
sendTelemetry('playground_tab', { tab });
|
|
7171
|
+
|
|
7172
|
+
// Initialize Home page if switching to it
|
|
7173
|
+
if (tab === 'home') {
|
|
7174
|
+
homeInit();
|
|
7175
|
+
}
|
|
5536
7176
|
}
|
|
5537
7177
|
// Expose globally so Electron main process can call it
|
|
5538
7178
|
window.switchTab = switchTab;
|
|
5539
7179
|
|
|
7180
|
+
// ── Home Page ──
|
|
7181
|
+
|
|
7182
|
+
let homeData = {
|
|
7183
|
+
announcements: null,
|
|
7184
|
+
releases: null,
|
|
7185
|
+
featuredWorkflows: null,
|
|
7186
|
+
communityWorkflows: null,
|
|
7187
|
+
currentAnnouncementIndex: 0,
|
|
7188
|
+
sortMode: 'trending'
|
|
7189
|
+
};
|
|
7190
|
+
|
|
7191
|
+
async function homeInit() {
|
|
7192
|
+
// Only initialize once per session
|
|
7193
|
+
if (homeData.announcements !== null) return;
|
|
7194
|
+
|
|
7195
|
+
try {
|
|
7196
|
+
// Load version info
|
|
7197
|
+
await updateHomeVersionInfo();
|
|
7198
|
+
|
|
7199
|
+
// Check API key status and show warning if needed
|
|
7200
|
+
checkApiKeyStatus();
|
|
7201
|
+
|
|
7202
|
+
// Load announcements and releases first (fast, local data)
|
|
7203
|
+
await Promise.all([
|
|
7204
|
+
loadAnnouncements(),
|
|
7205
|
+
loadReleases(),
|
|
7206
|
+
]);
|
|
7207
|
+
|
|
7208
|
+
// Render available sections immediately
|
|
7209
|
+
renderAnnouncements();
|
|
7210
|
+
renderReleases();
|
|
7211
|
+
|
|
7212
|
+
// Load marketplace data in background (slower, network-dependent)
|
|
7213
|
+
loadMarketplaceData().then(() => {
|
|
7214
|
+
renderFeaturedWorkflows();
|
|
7215
|
+
renderCommunityWorkflows();
|
|
7216
|
+
}).catch(() => {});
|
|
7217
|
+
|
|
7218
|
+
} catch (err) {
|
|
7219
|
+
console.error('Failed to initialize Home page:', err);
|
|
7220
|
+
}
|
|
7221
|
+
}
|
|
7222
|
+
|
|
7223
|
+
async function updateHomeVersionInfo() {
|
|
7224
|
+
let version = window.appVersion;
|
|
7225
|
+
|
|
7226
|
+
// If version not set yet, try to fetch from Electron
|
|
7227
|
+
if (!version && window.vai && window.vai.getVersion) {
|
|
7228
|
+
try {
|
|
7229
|
+
const v = await window.vai.getVersion();
|
|
7230
|
+
if (v) {
|
|
7231
|
+
version = typeof v === 'object' ? v.app : v;
|
|
7232
|
+
window.appVersion = version;
|
|
7233
|
+
}
|
|
7234
|
+
} catch (e) {
|
|
7235
|
+
console.warn('Failed to get version:', e);
|
|
7236
|
+
}
|
|
7237
|
+
}
|
|
7238
|
+
|
|
7239
|
+
// Fallback for web mode: try to get from CLI package.json via API
|
|
7240
|
+
if (!version) {
|
|
7241
|
+
try {
|
|
7242
|
+
const res = await fetch('/api/version');
|
|
7243
|
+
if (res.ok) {
|
|
7244
|
+
const data = await res.json();
|
|
7245
|
+
version = data.version || data.app;
|
|
7246
|
+
if (version) window.appVersion = version;
|
|
7247
|
+
}
|
|
7248
|
+
} catch (e) {
|
|
7249
|
+
// Ignore fetch errors in web mode
|
|
7250
|
+
}
|
|
7251
|
+
}
|
|
7252
|
+
|
|
7253
|
+
version = version || 'dev';
|
|
7254
|
+
document.getElementById('homeVersionBadge').textContent = `v${version}`;
|
|
7255
|
+
const footerEl = document.getElementById('homeFooterVersion');
|
|
7256
|
+
if (footerEl) footerEl.textContent = `Version ${version} • © 2026 VAI`;
|
|
7257
|
+
}
|
|
7258
|
+
|
|
7259
|
+
async function checkApiKeyStatus() {
|
|
7260
|
+
try {
|
|
7261
|
+
const res = await fetch('/api/config');
|
|
7262
|
+
const data = await res.json();
|
|
7263
|
+
const warningEl = document.getElementById('homeApiWarning');
|
|
7264
|
+
|
|
7265
|
+
if (!data.hasKey) {
|
|
7266
|
+
warningEl.style.display = 'block';
|
|
7267
|
+
} else {
|
|
7268
|
+
warningEl.style.display = 'none';
|
|
7269
|
+
}
|
|
7270
|
+
} catch {
|
|
7271
|
+
// If config check fails, assume no API key
|
|
7272
|
+
document.getElementById('homeApiWarning').style.display = 'block';
|
|
7273
|
+
}
|
|
7274
|
+
}
|
|
7275
|
+
|
|
7276
|
+
async function loadAnnouncements() {
|
|
7277
|
+
try {
|
|
7278
|
+
const res = await fetch('/api/home/announcements');
|
|
7279
|
+
const data = await res.json();
|
|
7280
|
+
|
|
7281
|
+
// Store total count before filtering
|
|
7282
|
+
homeData.totalAnnouncements = data.announcements.length;
|
|
7283
|
+
|
|
7284
|
+
// Filter out dismissed announcements
|
|
7285
|
+
const dismissed = JSON.parse(localStorage.getItem('vai-dismissed-announcements') || '[]');
|
|
7286
|
+
homeData.announcements = data.announcements.filter(a => !dismissed.includes(a.id));
|
|
7287
|
+
|
|
7288
|
+
} catch (err) {
|
|
7289
|
+
console.error('Failed to load announcements:', err);
|
|
7290
|
+
homeData.announcements = [];
|
|
7291
|
+
homeData.totalAnnouncements = 0;
|
|
7292
|
+
}
|
|
7293
|
+
}
|
|
7294
|
+
|
|
7295
|
+
async function loadReleases() {
|
|
7296
|
+
try {
|
|
7297
|
+
// Try cache first
|
|
7298
|
+
const cached = localStorage.getItem('vai-releases-cache');
|
|
7299
|
+
if (cached) {
|
|
7300
|
+
const { data, timestamp } = JSON.parse(cached);
|
|
7301
|
+
// Use cache if less than 30 minutes old AND it has real data (not fallback)
|
|
7302
|
+
const hasRealData = data && data.length > 0 && !data[0].version?.includes('1.0.0');
|
|
7303
|
+
if (hasRealData && Date.now() - timestamp < 30 * 60 * 1000) {
|
|
7304
|
+
homeData.releases = data;
|
|
7305
|
+
return;
|
|
7306
|
+
}
|
|
7307
|
+
}
|
|
7308
|
+
|
|
7309
|
+
const res = await fetch('/api/home/releases');
|
|
7310
|
+
const data = await res.json();
|
|
7311
|
+
homeData.releases = data.releases;
|
|
7312
|
+
|
|
7313
|
+
// Only cache if we got real data (not fallback)
|
|
7314
|
+
const hasRealData = data.releases && data.releases.length > 0 && !data.releases[0].version?.includes('1.0.0');
|
|
7315
|
+
if (hasRealData) {
|
|
7316
|
+
localStorage.setItem('vai-releases-cache', JSON.stringify({
|
|
7317
|
+
data: data.releases,
|
|
7318
|
+
timestamp: Date.now()
|
|
7319
|
+
}));
|
|
7320
|
+
} else {
|
|
7321
|
+
// Clear stale cache if we got fallback data
|
|
7322
|
+
localStorage.removeItem('vai-releases-cache');
|
|
7323
|
+
}
|
|
7324
|
+
|
|
7325
|
+
} catch (err) {
|
|
7326
|
+
console.error('Failed to load releases:', err);
|
|
7327
|
+
homeData.releases = [];
|
|
7328
|
+
}
|
|
7329
|
+
}
|
|
7330
|
+
|
|
7331
|
+
async function loadMarketplaceData() {
|
|
7332
|
+
try {
|
|
7333
|
+
const res = await fetch('/api/workflows/catalog');
|
|
7334
|
+
const data = await res.json();
|
|
7335
|
+
|
|
7336
|
+
// Extract featured and community workflows
|
|
7337
|
+
homeData.featuredWorkflows = data.workflows.filter(w => w.featured).slice(0, 4);
|
|
7338
|
+
homeData.communityWorkflows = data.workflows.filter(w => !w.featured);
|
|
7339
|
+
|
|
7340
|
+
} catch (err) {
|
|
7341
|
+
console.error('Failed to load marketplace data:', err);
|
|
7342
|
+
homeData.featuredWorkflows = [];
|
|
7343
|
+
homeData.communityWorkflows = [];
|
|
7344
|
+
}
|
|
7345
|
+
}
|
|
7346
|
+
|
|
7347
|
+
function renderAnnouncements() {
|
|
7348
|
+
const container = document.getElementById('homeAnnouncements');
|
|
7349
|
+
const carousel = document.getElementById('announcementsCarousel');
|
|
7350
|
+
const dots = document.getElementById('announcementsDots');
|
|
7351
|
+
const restoreBtn = document.getElementById('announcementsRestore');
|
|
7352
|
+
|
|
7353
|
+
// Show restore button if any announcements have been dismissed
|
|
7354
|
+
const hasDismissed = (homeData.totalAnnouncements || 0) > homeData.announcements.length;
|
|
7355
|
+
if (restoreBtn) restoreBtn.style.display = hasDismissed ? 'block' : 'none';
|
|
7356
|
+
|
|
7357
|
+
if (!homeData.announcements.length) {
|
|
7358
|
+
container.style.display = hasDismissed ? 'block' : 'none';
|
|
7359
|
+
carousel.innerHTML = '';
|
|
7360
|
+
dots.innerHTML = '';
|
|
7361
|
+
return;
|
|
7362
|
+
}
|
|
7363
|
+
|
|
7364
|
+
// Render cards
|
|
7365
|
+
carousel.innerHTML = homeData.announcements.map((ann, i) => {
|
|
7366
|
+
const bgClasses = [
|
|
7367
|
+
'home-announcement-card',
|
|
7368
|
+
i === 0 ? 'active' : '',
|
|
7369
|
+
ann.bg_image ? 'has-bg-image' : ''
|
|
7370
|
+
].filter(Boolean).join(' ');
|
|
7371
|
+
const bgStyle = ann.bg_image
|
|
7372
|
+
? `background-image: url('${ann.bg_image}');`
|
|
7373
|
+
: ann.bg_color
|
|
7374
|
+
? `background: ${ann.bg_color};`
|
|
7375
|
+
: '';
|
|
7376
|
+
return `
|
|
7377
|
+
<div class="${bgClasses}" data-id="${ann.id}" style="${bgStyle}">
|
|
7378
|
+
<button class="home-announcement-dismiss" onclick="dismissAnnouncement('${ann.id}')">×</button>
|
|
7379
|
+
${ann.icon ? `<div class="home-announcement-icon">${ann.icon}</div>` : ''}
|
|
7380
|
+
${ann.badge ? `<div class="badge">${ann.badge}</div>` : ''}
|
|
7381
|
+
<h3>${ann.title}</h3>
|
|
7382
|
+
<p>${ann.description}</p>
|
|
7383
|
+
${ann.cta ? `<button class="cta" onclick="${ann.cta.action === 'navigate' ? `switchTab('${ann.cta.target.slice(1)}')` : 'void(0)'}">${ann.cta.label}</button>` : ''}
|
|
7384
|
+
</div>`;
|
|
7385
|
+
}).join('');
|
|
7386
|
+
|
|
7387
|
+
// Render dots
|
|
7388
|
+
if (homeData.announcements.length > 1) {
|
|
7389
|
+
dots.innerHTML = homeData.announcements.map((_, i) =>
|
|
7390
|
+
`<div class="home-announcement-dot${i === 0 ? ' active' : ''}" onclick="showAnnouncement(${i})"></div>`
|
|
7391
|
+
).join('');
|
|
7392
|
+
|
|
7393
|
+
// Start rotation
|
|
7394
|
+
startAnnouncementRotation();
|
|
7395
|
+
}
|
|
7396
|
+
|
|
7397
|
+
container.style.display = 'block';
|
|
7398
|
+
}
|
|
7399
|
+
|
|
7400
|
+
function showAnnouncement(index) {
|
|
7401
|
+
homeData.currentAnnouncementIndex = index;
|
|
7402
|
+
|
|
7403
|
+
// Update cards
|
|
7404
|
+
document.querySelectorAll('.home-announcement-card').forEach((card, i) => {
|
|
7405
|
+
if (i === index) {
|
|
7406
|
+
card.classList.add('active');
|
|
7407
|
+
} else {
|
|
7408
|
+
card.classList.remove('active');
|
|
7409
|
+
}
|
|
7410
|
+
});
|
|
7411
|
+
|
|
7412
|
+
// Update dots
|
|
7413
|
+
document.querySelectorAll('.home-announcement-dot').forEach((dot, i) => {
|
|
7414
|
+
if (i === index) {
|
|
7415
|
+
dot.classList.add('active');
|
|
7416
|
+
} else {
|
|
7417
|
+
dot.classList.remove('active');
|
|
7418
|
+
}
|
|
7419
|
+
});
|
|
7420
|
+
|
|
7421
|
+
// Reset the rotation timer so we get a full interval after manual clicks
|
|
7422
|
+
resetAnnouncementRotation();
|
|
7423
|
+
}
|
|
7424
|
+
|
|
7425
|
+
let announcementRotationTimer = null;
|
|
7426
|
+
|
|
7427
|
+
function startAnnouncementRotation() {
|
|
7428
|
+
stopAnnouncementRotation();
|
|
7429
|
+
if (homeData.announcements.length <= 1) return;
|
|
7430
|
+
announcementRotationTimer = setInterval(() => {
|
|
7431
|
+
const next = (homeData.currentAnnouncementIndex + 1) % homeData.announcements.length;
|
|
7432
|
+
homeData.currentAnnouncementIndex = next;
|
|
7433
|
+
|
|
7434
|
+
// Update cards directly (don't call showAnnouncement to avoid resetting timer)
|
|
7435
|
+
document.querySelectorAll('.home-announcement-card').forEach((card, i) => {
|
|
7436
|
+
if (i === next) {
|
|
7437
|
+
card.classList.add('active');
|
|
7438
|
+
} else {
|
|
7439
|
+
card.classList.remove('active');
|
|
7440
|
+
}
|
|
7441
|
+
});
|
|
7442
|
+
document.querySelectorAll('.home-announcement-dot').forEach((dot, i) => {
|
|
7443
|
+
if (i === next) {
|
|
7444
|
+
dot.classList.add('active');
|
|
7445
|
+
} else {
|
|
7446
|
+
dot.classList.remove('active');
|
|
7447
|
+
}
|
|
7448
|
+
});
|
|
7449
|
+
}, 6000);
|
|
7450
|
+
}
|
|
7451
|
+
|
|
7452
|
+
function stopAnnouncementRotation() {
|
|
7453
|
+
if (announcementRotationTimer) {
|
|
7454
|
+
clearInterval(announcementRotationTimer);
|
|
7455
|
+
announcementRotationTimer = null;
|
|
7456
|
+
}
|
|
7457
|
+
}
|
|
7458
|
+
|
|
7459
|
+
function resetAnnouncementRotation() {
|
|
7460
|
+
stopAnnouncementRotation();
|
|
7461
|
+
startAnnouncementRotation();
|
|
7462
|
+
}
|
|
7463
|
+
|
|
7464
|
+
function dismissAnnouncement(id) {
|
|
7465
|
+
const dismissed = JSON.parse(localStorage.getItem('vai-dismissed-announcements') || '[]');
|
|
7466
|
+
dismissed.push(id);
|
|
7467
|
+
localStorage.setItem('vai-dismissed-announcements', JSON.stringify(dismissed));
|
|
7468
|
+
|
|
7469
|
+
// Remove from current data and re-render
|
|
7470
|
+
homeData.announcements = homeData.announcements.filter(a => a.id !== id);
|
|
7471
|
+
renderAnnouncements();
|
|
7472
|
+
}
|
|
7473
|
+
|
|
7474
|
+
async function restoreAnnouncements() {
|
|
7475
|
+
localStorage.removeItem('vai-dismissed-announcements');
|
|
7476
|
+
// Re-fetch all announcements
|
|
7477
|
+
try {
|
|
7478
|
+
const res = await fetch('/api/home/announcements');
|
|
7479
|
+
const data = await res.json();
|
|
7480
|
+
homeData.announcements = data.announcements;
|
|
7481
|
+
renderAnnouncements();
|
|
7482
|
+
} catch (err) {
|
|
7483
|
+
console.error('Failed to restore announcements:', err);
|
|
7484
|
+
}
|
|
7485
|
+
}
|
|
7486
|
+
|
|
7487
|
+
function renderReleases() {
|
|
7488
|
+
const container = document.getElementById('releasesTimeline');
|
|
7489
|
+
|
|
7490
|
+
const releases = homeData.releases.slice(0, 1); // Show only the latest release
|
|
7491
|
+
container.innerHTML = releases.map(release => `
|
|
7492
|
+
<div class="home-release-item">
|
|
7493
|
+
<div>
|
|
7494
|
+
<div class="home-release-version">${release.version}</div>
|
|
7495
|
+
<div class="home-release-date">${new Date(release.date).toLocaleDateString()}</div>
|
|
7496
|
+
<ul class="home-release-highlights">
|
|
7497
|
+
${release.highlights.map(highlight => `<li>${highlight}</li>`).join('')}
|
|
7498
|
+
</ul>
|
|
7499
|
+
</div>
|
|
7500
|
+
</div>
|
|
7501
|
+
`).join('');
|
|
7502
|
+
}
|
|
7503
|
+
|
|
7504
|
+
function renderFeaturedWorkflows() {
|
|
7505
|
+
const container = document.getElementById('featuredWorkflows');
|
|
7506
|
+
|
|
7507
|
+
if (!homeData.featuredWorkflows.length) {
|
|
7508
|
+
container.innerHTML = '<div class="home-community-empty"><h4>Coming Soon</h4><p>Featured workflows launching soon</p></div>';
|
|
7509
|
+
return;
|
|
7510
|
+
}
|
|
7511
|
+
|
|
7512
|
+
container.innerHTML = homeData.featuredWorkflows.map(workflow => `
|
|
7513
|
+
<div class="home-workflow-card">
|
|
7514
|
+
<h5>${workflow.name || 'Untitled'}</h5>
|
|
7515
|
+
<p>${workflow.description || 'No description available'}</p>
|
|
7516
|
+
<div class="home-workflow-meta">
|
|
7517
|
+
<div class="home-workflow-domain">${workflow.category || 'utility'}</div>
|
|
7518
|
+
<div class="home-workflow-author">by ${workflow.author?.name || 'unknown'}</div>
|
|
7519
|
+
</div>
|
|
7520
|
+
<div class="home-workflow-actions">
|
|
7521
|
+
<button class="home-workflow-btn" onclick="installWorkflow('${workflow.packageName}')">Install</button>
|
|
7522
|
+
<button class="home-workflow-btn" onclick="viewWorkflowDetails('${workflow.packageName}')">Details</button>
|
|
7523
|
+
</div>
|
|
7524
|
+
</div>
|
|
7525
|
+
`).join('');
|
|
7526
|
+
}
|
|
7527
|
+
|
|
7528
|
+
function renderCommunityWorkflows() {
|
|
7529
|
+
const container = document.getElementById('communityWorkflows');
|
|
7530
|
+
|
|
7531
|
+
if (!homeData.communityWorkflows.length) {
|
|
7532
|
+
container.innerHTML = '<div class="home-community-empty"><h4>Be the First!</h4><p>Publish a workflow to get started</p></div>';
|
|
7533
|
+
return;
|
|
7534
|
+
}
|
|
7535
|
+
|
|
7536
|
+
let workflows = [...homeData.communityWorkflows];
|
|
7537
|
+
|
|
7538
|
+
// Sort based on current mode
|
|
7539
|
+
switch (homeData.sortMode) {
|
|
7540
|
+
case 'trending':
|
|
7541
|
+
// Use downloads as proxy for trending
|
|
7542
|
+
workflows.sort((a, b) => (b.downloads || 0) - (a.downloads || 0));
|
|
7543
|
+
break;
|
|
7544
|
+
case 'newest':
|
|
7545
|
+
// Sort by name alphabetically as fallback (no publish date available)
|
|
7546
|
+
workflows.sort((a, b) => (a.name || '').localeCompare(b.name || ''));
|
|
7547
|
+
break;
|
|
7548
|
+
case 'installs':
|
|
7549
|
+
workflows.sort((a, b) => (b.downloads || 0) - (a.downloads || 0));
|
|
7550
|
+
break;
|
|
7551
|
+
}
|
|
7552
|
+
|
|
7553
|
+
container.innerHTML = workflows.slice(0, 6).map(workflow => `
|
|
7554
|
+
<div class="home-workflow-card">
|
|
7555
|
+
<h5>${workflow.name || 'Untitled'}</h5>
|
|
7556
|
+
<p>${workflow.description || 'No description available'}</p>
|
|
7557
|
+
<div class="home-workflow-meta">
|
|
7558
|
+
<div class="home-workflow-domain">${workflow.category || 'utility'}</div>
|
|
7559
|
+
<div class="home-workflow-author">by ${workflow.author?.name || 'unknown'}</div>
|
|
7560
|
+
</div>
|
|
7561
|
+
<div class="home-workflow-actions">
|
|
7562
|
+
<button class="home-workflow-btn" onclick="installWorkflow('${workflow.packageName}')">Install</button>
|
|
7563
|
+
<button class="home-workflow-btn" onclick="viewWorkflowDetails('${workflow.packageName}')">Details</button>
|
|
7564
|
+
</div>
|
|
7565
|
+
</div>
|
|
7566
|
+
`).join('');
|
|
7567
|
+
}
|
|
7568
|
+
|
|
7569
|
+
function sortCommunityWorkflows(mode) {
|
|
7570
|
+
homeData.sortMode = mode;
|
|
7571
|
+
|
|
7572
|
+
// Update tab appearance
|
|
7573
|
+
document.querySelectorAll('.home-sort-tab').forEach(tab => {
|
|
7574
|
+
tab.classList.toggle('active', tab.dataset.sort === mode);
|
|
7575
|
+
});
|
|
7576
|
+
|
|
7577
|
+
renderCommunityWorkflows();
|
|
7578
|
+
}
|
|
7579
|
+
|
|
7580
|
+
function filterAndSwitchWorkflows(domain) {
|
|
7581
|
+
// Switch to workflows tab with domain filter
|
|
7582
|
+
switchTab('workflows');
|
|
7583
|
+
// TODO: Apply domain filter when workflows tab supports it
|
|
7584
|
+
console.log('Filtering workflows by domain:', domain);
|
|
7585
|
+
}
|
|
7586
|
+
|
|
7587
|
+
function installWorkflow(packageName) {
|
|
7588
|
+
// Use the workflow store install function
|
|
7589
|
+
wfStoreInstall(packageName, null);
|
|
7590
|
+
}
|
|
7591
|
+
|
|
7592
|
+
function viewWorkflowDetails(packageName) {
|
|
7593
|
+
// Find the workflow by packageName and show its details
|
|
7594
|
+
const allWorkflows = [...(homeData.featuredWorkflows || []), ...(homeData.communityWorkflows || [])];
|
|
7595
|
+
const wf = allWorkflows.find(w => w.packageName === packageName);
|
|
7596
|
+
if (wf) {
|
|
7597
|
+
// Use the workflow store detail modal
|
|
7598
|
+
wfStoreShowDetail(wf.name);
|
|
7599
|
+
} else {
|
|
7600
|
+
// Fallback: switch to workflows tab
|
|
7601
|
+
switchTab('workflows');
|
|
7602
|
+
}
|
|
7603
|
+
}
|
|
7604
|
+
|
|
7605
|
+
function publishWorkflow() {
|
|
7606
|
+
// TODO: Open workflow publishing flow
|
|
7607
|
+
console.log('Opening workflow publisher');
|
|
7608
|
+
switchTab('workflows');
|
|
7609
|
+
}
|
|
7610
|
+
|
|
7611
|
+
function reportBug() {
|
|
7612
|
+
// Open bug report (reuse existing functionality if available)
|
|
7613
|
+
const bugButton = document.getElementById('bugButton');
|
|
7614
|
+
if (bugButton) {
|
|
7615
|
+
bugButton.click();
|
|
7616
|
+
} else {
|
|
7617
|
+
window.open('https://github.com/mrlynn/voyageai-cli/issues/new', '_blank');
|
|
7618
|
+
}
|
|
7619
|
+
}
|
|
7620
|
+
|
|
5540
7621
|
// ── Config ──
|
|
5541
7622
|
async function loadConfig() {
|
|
5542
7623
|
try {
|
|
@@ -5659,11 +7740,13 @@ window.doEmbed = async function() {
|
|
|
5659
7740
|
hideError('embedError');
|
|
5660
7741
|
const text = document.getElementById('embedInput').value.trim();
|
|
5661
7742
|
if (!text) { showError('embedError', 'Enter some text to embed'); return; }
|
|
5662
|
-
|
|
7743
|
+
const _embedModel = document.getElementById('embedModel').value;
|
|
7744
|
+
sendTelemetry('api_call', { endpoint: 'embed', model: _embedModel });
|
|
7745
|
+
const _embedDone = telemetryTimer('playground_embed', { model: _embedModel, inputType: document.getElementById('embedInputType').value || undefined });
|
|
5663
7746
|
|
|
5664
7747
|
setLoading('embedBtn', true);
|
|
5665
7748
|
try {
|
|
5666
|
-
const model =
|
|
7749
|
+
const model = _embedModel;
|
|
5667
7750
|
const inputType = document.getElementById('embedInputType').value || undefined;
|
|
5668
7751
|
const dims = document.getElementById('embedDimensions').value;
|
|
5669
7752
|
const dimensions = dims ? parseInt(dims, 10) : undefined;
|
|
@@ -5701,6 +7784,8 @@ window.doEmbed = async function() {
|
|
|
5701
7784
|
buildHeatmap(emb, document.getElementById('embedHeatmap'));
|
|
5702
7785
|
|
|
5703
7786
|
document.getElementById('embedResult').classList.add('visible');
|
|
7787
|
+
CostTracker.addOperation('embed', model, data.usage?.total_tokens || 0);
|
|
7788
|
+
_embedDone();
|
|
5704
7789
|
} catch (err) {
|
|
5705
7790
|
showError('embedError', err.message);
|
|
5706
7791
|
} finally {
|
|
@@ -5743,6 +7828,7 @@ function buildHeatmap(vec, container) {
|
|
|
5743
7828
|
window.doCompare = async function() {
|
|
5744
7829
|
hideError('compareError');
|
|
5745
7830
|
sendTelemetry('api_call', { endpoint: 'compare', model: document.getElementById('compareModel').value });
|
|
7831
|
+
const _compareDone = telemetryTimer('playground_similarity', { model: document.getElementById('compareModel').value });
|
|
5746
7832
|
const a = document.getElementById('compareA').value.trim();
|
|
5747
7833
|
const b = document.getElementById('compareB').value.trim();
|
|
5748
7834
|
if (!a || !b) { showError('compareError', 'Enter text in both fields'); return; }
|
|
@@ -5825,6 +7911,8 @@ window.doCompare = async function() {
|
|
|
5825
7911
|
`;
|
|
5826
7912
|
|
|
5827
7913
|
document.getElementById('compareResult').classList.add('visible');
|
|
7914
|
+
CostTracker.addOperation('compare', data.model || model, data.usage?.total_tokens || 0);
|
|
7915
|
+
_compareDone();
|
|
5828
7916
|
} catch (err) {
|
|
5829
7917
|
showError('compareError', err.message);
|
|
5830
7918
|
} finally {
|
|
@@ -5836,6 +7924,7 @@ window.doCompare = async function() {
|
|
|
5836
7924
|
window.doSearch = async function(withRerank) {
|
|
5837
7925
|
hideError('searchError');
|
|
5838
7926
|
sendTelemetry('api_call', { endpoint: withRerank ? 'rerank' : 'search' });
|
|
7927
|
+
const _searchDone = telemetryTimer(withRerank ? 'playground_rerank' : 'playground_search', { model: document.getElementById('searchEmbedModel').value });
|
|
5839
7928
|
const query = document.getElementById('searchQuery').value.trim();
|
|
5840
7929
|
const docsText = document.getElementById('searchDocs').value.trim();
|
|
5841
7930
|
if (!query || !docsText) { showError('searchError', 'Enter a query and documents'); return; }
|
|
@@ -5868,9 +7957,10 @@ window.doSearch = async function(withRerank) {
|
|
|
5868
7957
|
const embeddingResults = scores.slice(0, topK);
|
|
5869
7958
|
|
|
5870
7959
|
let rerankResults = null;
|
|
7960
|
+
let rerankData = null;
|
|
5871
7961
|
if (withRerank) {
|
|
5872
7962
|
const rerankModel = document.getElementById('searchRerankModel').value;
|
|
5873
|
-
|
|
7963
|
+
rerankData = await apiPost('/api/rerank', { query, documents, model: rerankModel, topK });
|
|
5874
7964
|
rerankResults = rerankData.data.map(r => ({
|
|
5875
7965
|
index: r.index,
|
|
5876
7966
|
text: documents[r.index],
|
|
@@ -5880,6 +7970,12 @@ window.doSearch = async function(withRerank) {
|
|
|
5880
7970
|
|
|
5881
7971
|
renderSearchResults(embeddingResults, rerankResults);
|
|
5882
7972
|
document.getElementById('searchResult').classList.add('visible');
|
|
7973
|
+
CostTracker.addOperation('search', embedModel, embedData.usage?.total_tokens || 0);
|
|
7974
|
+
if (withRerank && rerankResults) {
|
|
7975
|
+
const rerankModel = document.getElementById('searchRerankModel').value;
|
|
7976
|
+
CostTracker.addOperation('rerank', rerankModel, rerankData?.usage?.total_tokens || 0);
|
|
7977
|
+
}
|
|
7978
|
+
_searchDone();
|
|
5883
7979
|
} catch (err) {
|
|
5884
7980
|
showError('searchError', err.message);
|
|
5885
7981
|
} finally {
|
|
@@ -6533,6 +8629,7 @@ window.doBenchLatency = async function() {
|
|
|
6533
8629
|
latencies.push(data.elapsed);
|
|
6534
8630
|
tokens = data.tokens;
|
|
6535
8631
|
dims = data.dimensions;
|
|
8632
|
+
CostTracker.addOperation('bench-latency', model, data.tokens || 0);
|
|
6536
8633
|
} catch (err) {
|
|
6537
8634
|
document.getElementById(`bench-stats-${mi}`).textContent = 'Error';
|
|
6538
8635
|
document.getElementById(`bench-bar-${mi}`).classList.remove('running');
|
|
@@ -6626,6 +8723,8 @@ window.doBenchRanking = async function() {
|
|
|
6626
8723
|
|
|
6627
8724
|
rankedA = rankBySimilarity(dataA.embeddings, documents, topK);
|
|
6628
8725
|
rankedB = rankBySimilarity(dataB.embeddings, documents, topK);
|
|
8726
|
+
CostTracker.addOperation('bench-ranking', modelA, dataA.tokens || 0);
|
|
8727
|
+
CostTracker.addOperation('bench-ranking', modelB, dataB.tokens || 0);
|
|
6629
8728
|
} else {
|
|
6630
8729
|
// Rerank mode
|
|
6631
8730
|
const [dataA, dataB] = await Promise.all([
|
|
@@ -6643,6 +8742,8 @@ window.doBenchRanking = async function() {
|
|
|
6643
8742
|
text: documents[r.index],
|
|
6644
8743
|
score: r.relevance_score,
|
|
6645
8744
|
}));
|
|
8745
|
+
CostTracker.addOperation('bench-ranking', modelA, dataA.usage?.total_tokens || 0);
|
|
8746
|
+
CostTracker.addOperation('bench-ranking', modelB, dataB.usage?.total_tokens || 0);
|
|
6646
8747
|
}
|
|
6647
8748
|
|
|
6648
8749
|
// Render comparison
|
|
@@ -6783,6 +8884,7 @@ window.doBenchQuantization = async function() {
|
|
|
6783
8884
|
const start = performance.now();
|
|
6784
8885
|
const data = await apiPost('/api/embed', body);
|
|
6785
8886
|
const elapsed = performance.now() - start;
|
|
8887
|
+
CostTracker.addOperation('bench-quantization', model, data.usage?.total_tokens || 0);
|
|
6786
8888
|
|
|
6787
8889
|
const embeddings = data.data.map(d => d.embedding);
|
|
6788
8890
|
const queryEmbed = embeddings[0];
|
|
@@ -7461,6 +9563,15 @@ function initSettings() {
|
|
|
7461
9563
|
timeoutSel.addEventListener('change', () => saveSetting('timeout', timeoutSel.value));
|
|
7462
9564
|
}
|
|
7463
9565
|
|
|
9566
|
+
// Default Tab
|
|
9567
|
+
const defaultTabSel = document.getElementById('settingsDefaultTab');
|
|
9568
|
+
if (defaultTabSel) {
|
|
9569
|
+
defaultTabSel.value = s.defaultTab || 'home';
|
|
9570
|
+
defaultTabSel.addEventListener('change', () => {
|
|
9571
|
+
saveSetting('defaultTab', defaultTabSel.value);
|
|
9572
|
+
});
|
|
9573
|
+
}
|
|
9574
|
+
|
|
7464
9575
|
// Benchmark iterations
|
|
7465
9576
|
const benchIterSel = document.getElementById('settingsBenchIter');
|
|
7466
9577
|
if (benchIterSel) {
|
|
@@ -8335,6 +10446,7 @@ window.doMultimodalCompare = async function() {
|
|
|
8335
10446
|
}
|
|
8336
10447
|
|
|
8337
10448
|
document.getElementById('mmResult').classList.add('visible');
|
|
10449
|
+
CostTracker.addOperation('multimodal-compare', data.model || model, usage.total_tokens || 0);
|
|
8338
10450
|
} catch (err) {
|
|
8339
10451
|
showError('mmError', err.message);
|
|
8340
10452
|
} finally {
|
|
@@ -8538,6 +10650,7 @@ window.doMultimodalSearch = async function() {
|
|
|
8538
10650
|
});
|
|
8539
10651
|
|
|
8540
10652
|
document.getElementById('mmSearchResult').classList.add('visible');
|
|
10653
|
+
CostTracker.addOperation('multimodal-search', model, data.usage?.total_tokens || 0);
|
|
8541
10654
|
} catch (err) {
|
|
8542
10655
|
showError('mmSearchError', err.message);
|
|
8543
10656
|
} finally {
|
|
@@ -9436,20 +11549,24 @@ function renderMarkdown(md) {
|
|
|
9436
11549
|
/**
|
|
9437
11550
|
* Tool metadata: icon, label, and a function to summarize the call for the thinking panel.
|
|
9438
11551
|
*/
|
|
11552
|
+
function lucideIcon(id, size = 16) {
|
|
11553
|
+
return `<svg width="${size}" height="${size}" viewBox="0 0 24 24" style="color:var(--accent)"><use href="#${id}"/></svg>`;
|
|
11554
|
+
}
|
|
11555
|
+
|
|
9439
11556
|
const TOOL_META = {
|
|
9440
|
-
vai_query: {
|
|
9441
|
-
vai_search: {
|
|
9442
|
-
vai_rerank: {
|
|
9443
|
-
vai_embed: {
|
|
9444
|
-
vai_similarity: {
|
|
9445
|
-
vai_collections: {
|
|
9446
|
-
vai_models: {
|
|
9447
|
-
vai_topics: {
|
|
9448
|
-
vai_explain: {
|
|
9449
|
-
vai_estimate: {
|
|
9450
|
-
vai_ingest: {
|
|
11557
|
+
vai_query: { iconId: 'lg-search', label: 'RAG Query', verb: 'Searching', descFn: a => a.query ? `"${a.query}"` : '' },
|
|
11558
|
+
vai_search: { iconId: 'lg-search-code', label: 'Vector Search', verb: 'Searching vectors', descFn: a => a.query ? `"${a.query}"` : '' },
|
|
11559
|
+
vai_rerank: { iconId: 'lg-arrow-up-down', label: 'Rerank', verb: 'Reranking', descFn: a => a.query ? `${a.documents?.length || '?'} docs for "${a.query}"` : '' },
|
|
11560
|
+
vai_embed: { iconId: 'lg-lightning', label: 'Embed', verb: 'Embedding', descFn: a => a.text ? `"${a.text.slice(0, 60)}${a.text.length > 60 ? '...' : ''}"` : '' },
|
|
11561
|
+
vai_similarity: { iconId: 'lg-target', label: 'Similarity', verb: 'Comparing', descFn: a => a.text1 ? `two texts` : '' },
|
|
11562
|
+
vai_collections: { iconId: 'lg-database', label: 'Collections', verb: 'Discovering', descFn: a => a.db ? `in ${a.db}` : 'available databases' },
|
|
11563
|
+
vai_models: { iconId: 'lg-bot', label: 'Models', verb: 'Listing', descFn: () => 'available models' },
|
|
11564
|
+
vai_topics: { iconId: 'lg-cube', label: 'Topics', verb: 'Browsing', descFn: () => 'educational topics' },
|
|
11565
|
+
vai_explain: { iconId: 'lg-bulb', label: 'Explain', verb: 'Explaining', descFn: a => a.topic || '' },
|
|
11566
|
+
vai_estimate: { iconId: 'lg-coins', label: 'Cost Estimate', verb: 'Estimating', descFn: a => a.docs ? `${a.docs} docs` : '' },
|
|
11567
|
+
vai_ingest: { iconId: 'lg-inbox', label: 'Ingest', verb: 'Ingesting', descFn: a => a.source || 'document' },
|
|
9451
11568
|
};
|
|
9452
|
-
const DEFAULT_TOOL_META = {
|
|
11569
|
+
const DEFAULT_TOOL_META = { iconId: 'lg-config', label: '', verb: 'Running', descFn: () => '' };
|
|
9453
11570
|
|
|
9454
11571
|
/**
|
|
9455
11572
|
* Create the thinking panel <details> element.
|
|
@@ -9462,7 +11579,7 @@ function createThinkingPanel() {
|
|
|
9462
11579
|
|
|
9463
11580
|
const summary = document.createElement('summary');
|
|
9464
11581
|
summary.innerHTML =
|
|
9465
|
-
'<span class="thinking-icon"
|
|
11582
|
+
'<span class="thinking-icon">' + lucideIcon('lg-brain', 14) + '</span>' +
|
|
9466
11583
|
'<span class="thinking-label">Thinking</span>' +
|
|
9467
11584
|
'<span class="thinking-count">0</span>' +
|
|
9468
11585
|
'<span class="thinking-elapsed"></span>' +
|
|
@@ -9505,7 +11622,7 @@ function createThinkingPanel() {
|
|
|
9505
11622
|
|
|
9506
11623
|
const iconDiv = document.createElement('div');
|
|
9507
11624
|
iconDiv.className = 'thinking-step-icon';
|
|
9508
|
-
iconDiv.
|
|
11625
|
+
iconDiv.innerHTML = lucideIcon(meta.iconId, 14);
|
|
9509
11626
|
step.appendChild(iconDiv);
|
|
9510
11627
|
|
|
9511
11628
|
const body = document.createElement('div');
|
|
@@ -9560,7 +11677,7 @@ function createThinkingPanel() {
|
|
|
9560
11677
|
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
9561
11678
|
summary.querySelector('.thinking-elapsed').textContent = elapsed + 's';
|
|
9562
11679
|
summary.querySelector('.thinking-label').textContent = 'Thought for ' + elapsed + 's';
|
|
9563
|
-
summary.querySelector('.thinking-icon').
|
|
11680
|
+
summary.querySelector('.thinking-icon').innerHTML = lucideIcon('lg-sparkles', 14);
|
|
9564
11681
|
// Mark last step done and collapse
|
|
9565
11682
|
if (activeStep) {
|
|
9566
11683
|
activeStep.classList.remove('active');
|
|
@@ -9685,6 +11802,7 @@ async function sendChatMessage() {
|
|
|
9685
11802
|
let fullText = '';
|
|
9686
11803
|
let sources = [];
|
|
9687
11804
|
let thinkingPanel = null;
|
|
11805
|
+
let retrievalCostTracked = false;
|
|
9688
11806
|
|
|
9689
11807
|
while (true) {
|
|
9690
11808
|
const { done, value } = await reader.read();
|
|
@@ -9705,6 +11823,14 @@ async function sendChatMessage() {
|
|
|
9705
11823
|
|
|
9706
11824
|
if (currentEvent === 'retrieval') {
|
|
9707
11825
|
typing.textContent = `Retrieved ${data.docs?.length || 0} docs (${data.timeMs}ms)`;
|
|
11826
|
+
// Track embedding cost from retrieval (flag to avoid double-counting with done)
|
|
11827
|
+
if (data.tokens?.embed) {
|
|
11828
|
+
CostTracker.addOperation('chat-embed', 'voyage-4-lite', data.tokens.embed);
|
|
11829
|
+
retrievalCostTracked = true;
|
|
11830
|
+
}
|
|
11831
|
+
if (data.tokens?.rerank) {
|
|
11832
|
+
CostTracker.addOperation('chat-rerank', 'rerank-2.5', data.tokens.rerank);
|
|
11833
|
+
}
|
|
9708
11834
|
}
|
|
9709
11835
|
|
|
9710
11836
|
if (currentEvent === 'tool_call') {
|
|
@@ -9729,6 +11855,24 @@ async function sendChatMessage() {
|
|
|
9729
11855
|
}
|
|
9730
11856
|
|
|
9731
11857
|
if (currentEvent === 'done') {
|
|
11858
|
+
// Track costs from done metadata (only if retrieval event didn't already)
|
|
11859
|
+
const meta = data.metadata || {};
|
|
11860
|
+
if (!retrievalCostTracked && meta.tokens?.embed) {
|
|
11861
|
+
CostTracker.addOperation('chat-embed', 'voyage-4-lite', meta.tokens.embed);
|
|
11862
|
+
}
|
|
11863
|
+
if (!retrievalCostTracked && meta.tokens?.rerank) {
|
|
11864
|
+
CostTracker.addOperation('chat-rerank', 'rerank-2.5', meta.tokens.rerank);
|
|
11865
|
+
}
|
|
11866
|
+
// Track LLM generation cost
|
|
11867
|
+
if (meta.tokens?.llmInput || meta.tokens?.llmOutput) {
|
|
11868
|
+
CostTracker.addLLMOperation('chat-llm', meta.llmModel, meta.tokens.llmInput, meta.tokens.llmOutput);
|
|
11869
|
+
}
|
|
11870
|
+
// Agent mode: track tool call tokens from steps/layers
|
|
11871
|
+
if (data.steps) {
|
|
11872
|
+
for (const step of data.steps) {
|
|
11873
|
+
if (step.tokens) CostTracker.addOperation('chat-tool', step.model || 'voyage-4-lite', step.tokens);
|
|
11874
|
+
}
|
|
11875
|
+
}
|
|
9732
11876
|
// Finalize the thinking panel (collapse, show elapsed)
|
|
9733
11877
|
if (thinkingPanel) thinkingPanel.finalize();
|
|
9734
11878
|
// Render accumulated text as markdown for assistant messages
|
|
@@ -9935,6 +12079,57 @@ init();
|
|
|
9935
12079
|
.bug-success h3{margin:0 0 12px;color:var(--accent-text);font-size:20px}
|
|
9936
12080
|
.bug-success p{margin:8px 0;color:var(--text-muted)}
|
|
9937
12081
|
.bug-success code{background:rgba(255,255,255,0.1);padding:4px 8px;border-radius:4px;font-size:12px;color:var(--accent)}
|
|
12082
|
+
|
|
12083
|
+
/* ── Cost Dashboard ── */
|
|
12084
|
+
#costStatusBar {
|
|
12085
|
+
position: fixed; bottom: 0; left: 0; right: 0; z-index: 9999;
|
|
12086
|
+
background: var(--bg-surface); border-top: 1px solid var(--border);
|
|
12087
|
+
padding: 8px 16px; font-size: 13px; color: var(--text-dim);
|
|
12088
|
+
display: flex; align-items: center; gap: 12px; backdrop-filter: blur(8px);
|
|
12089
|
+
opacity: 0.95; font-family: 'SF Mono', 'Fira Code', monospace;
|
|
12090
|
+
}
|
|
12091
|
+
#costStatusBar .cost-sep { color: var(--border); }
|
|
12092
|
+
#costStatusBar .cost-savings { color: var(--green, #00D4AA); }
|
|
12093
|
+
#costStatusBar .cost-more { color: var(--red, #FF6960); }
|
|
12094
|
+
#costStatusBar .cost-val { color: var(--accent); font-weight: 600; }
|
|
12095
|
+
#costDetailsToggle {
|
|
12096
|
+
margin-left: auto; cursor: pointer; color: var(--accent);
|
|
12097
|
+
background: none; border: 1px solid var(--border); border-radius: 6px;
|
|
12098
|
+
padding: 3px 10px; font-size: 12px; font-family: inherit;
|
|
12099
|
+
}
|
|
12100
|
+
#costDetailsToggle:hover { background: var(--bg-card); }
|
|
12101
|
+
#costDetailPanel {
|
|
12102
|
+
position: fixed; bottom: 40px; left: 0; right: 0; z-index: 9998;
|
|
12103
|
+
background: var(--bg-surface); border-top: 1px solid var(--border);
|
|
12104
|
+
max-height: 0; overflow: hidden; transition: max-height 0.3s ease;
|
|
12105
|
+
}
|
|
12106
|
+
#costDetailPanel.open { max-height: 50vh; overflow-y: auto; }
|
|
12107
|
+
#costDetailPanel .cost-panel-inner { padding: 16px 20px; }
|
|
12108
|
+
#costDetailPanel table {
|
|
12109
|
+
width: 100%; border-collapse: collapse; font-size: 12px; font-family: 'SF Mono', 'Fira Code', monospace;
|
|
12110
|
+
}
|
|
12111
|
+
#costDetailPanel th { text-align: left; color: var(--text-muted); font-weight: 500; padding: 4px 8px; border-bottom: 1px solid var(--border); }
|
|
12112
|
+
#costDetailPanel td { padding: 4px 8px; color: var(--text-dim); border-bottom: 1px solid var(--border); }
|
|
12113
|
+
#costDetailPanel .cost-panel-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; }
|
|
12114
|
+
#costDetailPanel .cost-panel-header h3 { margin: 0; font-size: 14px; color: var(--text); }
|
|
12115
|
+
#costResetBtn {
|
|
12116
|
+
background: none; border: 1px solid var(--border); border-radius: 6px;
|
|
12117
|
+
padding: 3px 10px; font-size: 12px; color: var(--text-dim); cursor: pointer; font-family: inherit;
|
|
12118
|
+
}
|
|
12119
|
+
#costResetBtn:hover { background: var(--bg-card); color: var(--text); }
|
|
12120
|
+
.cost-projection { margin-top: 12px; font-size: 12px; color: var(--text-muted); }
|
|
12121
|
+
.cost-projection span { color: var(--accent); font-weight: 600; }
|
|
12122
|
+
.cost-toast {
|
|
12123
|
+
position: fixed; bottom: 52px; left: 50%; transform: translateX(-50%);
|
|
12124
|
+
background: var(--bg-card); border: 1px solid var(--border); border-radius: 8px;
|
|
12125
|
+
padding: 8px 16px; font-size: 12px; color: var(--text-dim); z-index: 10000;
|
|
12126
|
+
font-family: 'SF Mono', 'Fira Code', monospace; white-space: nowrap;
|
|
12127
|
+
animation: costToastIn 0.2s ease, costToastOut 0.3s ease 2.7s forwards;
|
|
12128
|
+
pointer-events: none;
|
|
12129
|
+
}
|
|
12130
|
+
@keyframes costToastIn { from { opacity: 0; transform: translateX(-50%) translateY(10px); } to { opacity: 1; transform: translateX(-50%) translateY(0); } }
|
|
12131
|
+
@keyframes costToastOut { from { opacity: 1; } to { opacity: 0; transform: translateX(-50%) translateY(-10px); } }
|
|
12132
|
+
body { padding-bottom: 44px; }
|
|
9938
12133
|
</style>
|
|
9939
12134
|
|
|
9940
12135
|
<!-- Bug button moved to sidebar footer -->
|
|
@@ -10016,6 +12211,13 @@ const WF_NODE_META = {
|
|
|
10016
12211
|
rerank: { icon: 'M3 6h18M7 12h10M10 18h4', label: 'Rerank', color: '#CE93D8', category: 'retrieval' },
|
|
10017
12212
|
search: { icon: 'M21 21l-4.3-4.3M11 19a8 8 0 1 0 0-16 8 8 0 0 0 0 16z', label: 'Search', color: '#64B5F6', category: 'retrieval' },
|
|
10018
12213
|
ingest: { icon: 'M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3', label: 'Ingest', color: '#4DB6AC', category: 'management' },
|
|
12214
|
+
// Phase 1-3: New workflow nodes
|
|
12215
|
+
conditional: { icon: 'M12 3l9 9-9 9-9-9z', label: 'Conditional', color: '#90A4AE', category: 'control', shape: 'diamond' },
|
|
12216
|
+
loop: { icon: 'M17 1l4 4-4 4M3 11V9a4 4 0 0 1 4-4h14M7 23l-4-4 4-4M21 13v2a4 4 0 0 1-4 4H3', label: 'Loop', color: '#90A4AE', category: 'control' },
|
|
12217
|
+
template: { icon: 'M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8zM14 2v6h6M16 13H8M16 17H8M10 9H8', label: 'Template', color: '#90A4AE', category: 'control' },
|
|
12218
|
+
chunk: { icon: 'M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8zM14 2v6h6M9 15h6M9 11h6M9 19h4', label: 'Chunk', color: '#00D4AA', category: 'processing' },
|
|
12219
|
+
aggregate: { icon: 'M22 3H2l8 9.46V19l4 2v-8.54L22 3z', label: 'Aggregate', color: '#00D4AA', category: 'processing' },
|
|
12220
|
+
http: { icon: 'M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20zM2 12h20M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z', label: 'HTTP Request', color: '#F5A623', category: 'integration' },
|
|
10019
12221
|
};
|
|
10020
12222
|
|
|
10021
12223
|
// Fallback icon (gear) for unknown workflow node types
|
|
@@ -10061,6 +12263,8 @@ async function wfLoadLibrary() {
|
|
|
10061
12263
|
const wfData = await wfRes.json();
|
|
10062
12264
|
const exData = await exRes.json();
|
|
10063
12265
|
wfState.workflows = wfData.workflows || [];
|
|
12266
|
+
wfState.official = wfData.official || [];
|
|
12267
|
+
wfState.community = wfData.community || [];
|
|
10064
12268
|
wfState.examples = exData.examples || [];
|
|
10065
12269
|
wfRenderLibrary();
|
|
10066
12270
|
} catch (err) {
|
|
@@ -10069,51 +12273,238 @@ async function wfLoadLibrary() {
|
|
|
10069
12273
|
}
|
|
10070
12274
|
}
|
|
10071
12275
|
|
|
12276
|
+
// ── Library Search ──
|
|
12277
|
+
let _wfLibSearchTimer = null;
|
|
12278
|
+
function wfOnLibrarySearch() {
|
|
12279
|
+
clearTimeout(_wfLibSearchTimer);
|
|
12280
|
+
_wfLibSearchTimer = setTimeout(() => wfRenderLibrary(), 150);
|
|
12281
|
+
const input = document.getElementById('wfLibrarySearch');
|
|
12282
|
+
const clear = document.getElementById('wfLibrarySearchClear');
|
|
12283
|
+
if (input && clear) clear.style.display = input.value ? 'block' : 'none';
|
|
12284
|
+
}
|
|
12285
|
+
function wfClearLibrarySearch() {
|
|
12286
|
+
const input = document.getElementById('wfLibrarySearch');
|
|
12287
|
+
if (input) { input.value = ''; input.focus(); }
|
|
12288
|
+
const clear = document.getElementById('wfLibrarySearchClear');
|
|
12289
|
+
if (clear) clear.style.display = 'none';
|
|
12290
|
+
wfRenderLibrary();
|
|
12291
|
+
}
|
|
12292
|
+
|
|
12293
|
+
function wfGetLibSectionStates() {
|
|
12294
|
+
const state = wfGetLayoutState();
|
|
12295
|
+
return (state.library && state.library.sections) || { builtin: true, examples: true, catalog: false, community: false };
|
|
12296
|
+
}
|
|
12297
|
+
function wfSaveLibSectionState(key, expanded) {
|
|
12298
|
+
const state = wfGetLayoutState();
|
|
12299
|
+
if (!state.library) state.library = { open: true };
|
|
12300
|
+
if (!state.library.sections) state.library.sections = { builtin: true, examples: true, catalog: false, community: false };
|
|
12301
|
+
state.library.sections[key] = expanded;
|
|
12302
|
+
try { localStorage.setItem('vai-workflow-layout', JSON.stringify(state)); } catch(e) {}
|
|
12303
|
+
}
|
|
12304
|
+
function wfToggleLibSection(key) {
|
|
12305
|
+
const states = wfGetLibSectionStates();
|
|
12306
|
+
const newVal = !states[key];
|
|
12307
|
+
wfSaveLibSectionState(key, newVal);
|
|
12308
|
+
// Toggle UI directly
|
|
12309
|
+
const header = document.querySelector(`.wf-lib-section-header[data-section="${key}"]`);
|
|
12310
|
+
const body = document.querySelector(`.wf-lib-section-body[data-section="${key}"]`);
|
|
12311
|
+
if (header && body) {
|
|
12312
|
+
header.classList.toggle('expanded', newVal);
|
|
12313
|
+
if (newVal) {
|
|
12314
|
+
body.style.maxHeight = body.scrollHeight + 'px';
|
|
12315
|
+
setTimeout(() => { body.style.maxHeight = 'none'; }, 200);
|
|
12316
|
+
} else {
|
|
12317
|
+
body.style.maxHeight = body.scrollHeight + 'px';
|
|
12318
|
+
requestAnimationFrame(() => { body.style.maxHeight = '0px'; });
|
|
12319
|
+
}
|
|
12320
|
+
}
|
|
12321
|
+
}
|
|
12322
|
+
|
|
10072
12323
|
function wfRenderLibrary() {
|
|
10073
12324
|
const list = document.getElementById('wfLibraryList');
|
|
10074
12325
|
if (!list) return;
|
|
10075
|
-
|
|
10076
|
-
|
|
10077
|
-
|
|
12326
|
+
|
|
12327
|
+
const searchInput = document.getElementById('wfLibrarySearch');
|
|
12328
|
+
const query = (searchInput ? searchInput.value : '').toLowerCase().trim();
|
|
12329
|
+
const sectionStates = wfGetLibSectionStates();
|
|
12330
|
+
|
|
12331
|
+
function matchesSearch(w) {
|
|
12332
|
+
if (!query) return true;
|
|
12333
|
+
const name = (w.name || '').toLowerCase();
|
|
12334
|
+
const desc = (w.description || '').toLowerCase();
|
|
12335
|
+
return name.includes(query) || desc.includes(query);
|
|
10078
12336
|
}
|
|
10079
12337
|
|
|
10080
|
-
|
|
10081
|
-
|
|
10082
|
-
const
|
|
12338
|
+
function renderItem(w, prefix) {
|
|
12339
|
+
const displayName = prefix + w.name.replace(/^@vaicli\/vai-workflow-/, '').replace(/^vai-workflow-/, '').replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
|
|
12340
|
+
const authorLine = w.author ? `<span style="color:var(--text-muted);font-size:10px;">by ${w.author}${w.version ? ' · v' + w.version : ''}</span>` : '';
|
|
12341
|
+
const tagLine = (w.tags || []).length ? `<div style="margin-top:2px;font-size:10px;color:var(--text-muted);">${w.tags.join(' · ')}</div>` : '';
|
|
10083
12342
|
return `<div class="wf-library-item" data-wf-name="${w.name}" onclick="wfSelectWorkflow('${w.name}')">
|
|
10084
12343
|
<div class="wf-library-item-name">${displayName}</div>
|
|
10085
12344
|
<div class="wf-library-item-desc">${w.description || ''}</div>
|
|
12345
|
+
${authorLine}${tagLine}
|
|
10086
12346
|
</div>`;
|
|
10087
|
-
}
|
|
12347
|
+
}
|
|
12348
|
+
|
|
12349
|
+
function renderSection(key, label, items, prefix) {
|
|
12350
|
+
const filtered = items.filter(matchesSearch);
|
|
12351
|
+
if (filtered.length === 0) return '';
|
|
12352
|
+
const expanded = sectionStates[key] !== false;
|
|
12353
|
+
const chevron = '▶';
|
|
12354
|
+
const itemsHtml = filtered.map(w => renderItem(w, prefix)).join('');
|
|
12355
|
+
return `<div class="wf-library-section" data-section="${key}">
|
|
12356
|
+
<div class="wf-lib-section-header ${expanded ? 'expanded' : ''}" data-section="${key}" onclick="wfToggleLibSection('${key}')">
|
|
12357
|
+
<span class="wf-chevron">${chevron}</span>
|
|
12358
|
+
<span>${label}</span>
|
|
12359
|
+
<span class="wf-section-count">(${filtered.length})</span>
|
|
12360
|
+
</div>
|
|
12361
|
+
<div class="wf-lib-section-body" data-section="${key}" style="max-height:${expanded ? 'none' : '0px'};">
|
|
12362
|
+
${itemsHtml}
|
|
12363
|
+
</div>
|
|
12364
|
+
</div>`;
|
|
12365
|
+
}
|
|
12366
|
+
|
|
12367
|
+
let html = '';
|
|
10088
12368
|
|
|
10089
|
-
//
|
|
12369
|
+
// Built-in
|
|
12370
|
+
html += renderSection('builtin', 'Built-in', wfState.workflows, '');
|
|
12371
|
+
|
|
12372
|
+
// Examples
|
|
10090
12373
|
const examples = wfState.examples || [];
|
|
10091
12374
|
if (examples.length > 0) {
|
|
10092
|
-
html +=
|
|
10093
|
-
|
|
10094
|
-
|
|
10095
|
-
|
|
10096
|
-
|
|
10097
|
-
|
|
10098
|
-
|
|
10099
|
-
|
|
10100
|
-
|
|
10101
|
-
|
|
10102
|
-
|
|
10103
|
-
|
|
10104
|
-
|
|
10105
|
-
return `<div class="wf-library-item" data-wf-name="${w.name}" onclick="wfSelectWorkflow('${w.name}')">
|
|
10106
|
-
<div class="wf-library-item-name">${displayName}</div>
|
|
10107
|
-
<div class="wf-library-item-desc">${w.description || ''}</div>
|
|
10108
|
-
</div>`;
|
|
10109
|
-
}).join('');
|
|
10110
|
-
}
|
|
10111
|
-
html += '</div></div>';
|
|
12375
|
+
html += renderSection('examples', 'Examples', examples, '');
|
|
12376
|
+
}
|
|
12377
|
+
|
|
12378
|
+
// Official catalog
|
|
12379
|
+
const official = wfState.official || [];
|
|
12380
|
+
html += renderSection('catalog', 'Official Catalog (@vaicli)', official, '✓ ');
|
|
12381
|
+
|
|
12382
|
+
// Community
|
|
12383
|
+
const community = wfState.community || [];
|
|
12384
|
+
html += renderSection('community', 'Community', community, '🌐 ');
|
|
12385
|
+
|
|
12386
|
+
if (!html) {
|
|
12387
|
+
html = '<div style="padding:16px;color:var(--text-muted);font-size:12px;">No workflows found</div>';
|
|
10112
12388
|
}
|
|
10113
12389
|
|
|
12390
|
+
// Install from npm button
|
|
12391
|
+
html += `<div style="padding:8px 12px;">
|
|
12392
|
+
<button onclick="wfShowInstallDialog()" style="background:none;border:1px dashed var(--border);color:var(--accent);cursor:pointer;padding:6px 12px;border-radius:6px;font-size:11px;width:100%;text-align:center;">+ Install from npm</button>
|
|
12393
|
+
</div>`;
|
|
12394
|
+
|
|
10114
12395
|
list.innerHTML = html;
|
|
10115
12396
|
}
|
|
10116
12397
|
|
|
12398
|
+
// ── Install Dialog ──
|
|
12399
|
+
function wfShowInstallDialog() {
|
|
12400
|
+
let modal = document.getElementById('wfInstallModal');
|
|
12401
|
+
if (!modal) {
|
|
12402
|
+
modal = document.createElement('div');
|
|
12403
|
+
modal.id = 'wfInstallModal';
|
|
12404
|
+
modal.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.6);display:flex;align-items:center;justify-content:center;z-index:9999;';
|
|
12405
|
+
modal.innerHTML = `
|
|
12406
|
+
<div style="background:var(--bg-panel);border:1px solid var(--border);border-radius:12px;padding:24px;width:480px;max-height:70vh;display:flex;flex-direction:column;">
|
|
12407
|
+
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;">
|
|
12408
|
+
<h3 style="margin:0;font-size:16px;">Install Workflow Package</h3>
|
|
12409
|
+
<button onclick="wfCloseInstallDialog()" style="background:none;border:none;color:var(--text-muted);cursor:pointer;font-size:18px;">✕</button>
|
|
12410
|
+
</div>
|
|
12411
|
+
<div style="display:flex;gap:8px;margin-bottom:12px;">
|
|
12412
|
+
<input id="wfInstallSearch" type="text" placeholder="Search vai workflows on npm..." style="flex:1;padding:8px 12px;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:13px;" onkeydown="if(event.key==='Enter')wfSearchNpm()">
|
|
12413
|
+
<button onclick="wfSearchNpm()" style="padding:8px 16px;background:var(--accent);color:#fff;border:none;border-radius:6px;cursor:pointer;font-size:13px;">Search</button>
|
|
12414
|
+
</div>
|
|
12415
|
+
<div id="wfInstallResults" style="overflow-y:auto;flex:1;min-height:100px;"></div>
|
|
12416
|
+
</div>`;
|
|
12417
|
+
document.body.appendChild(modal);
|
|
12418
|
+
modal.addEventListener('click', (e) => { if (e.target === modal) wfCloseInstallDialog(); });
|
|
12419
|
+
}
|
|
12420
|
+
modal.style.display = 'flex';
|
|
12421
|
+
document.getElementById('wfInstallSearch').value = '';
|
|
12422
|
+
document.getElementById('wfInstallResults').innerHTML = '<div style="padding:16px;color:var(--text-muted);font-size:12px;text-align:center;">Search for vai-workflow packages on npm</div>';
|
|
12423
|
+
document.getElementById('wfInstallSearch').focus();
|
|
12424
|
+
}
|
|
12425
|
+
|
|
12426
|
+
function wfCloseInstallDialog() {
|
|
12427
|
+
const modal = document.getElementById('wfInstallModal');
|
|
12428
|
+
if (modal) modal.style.display = 'none';
|
|
12429
|
+
}
|
|
12430
|
+
|
|
12431
|
+
async function wfSearchNpm() {
|
|
12432
|
+
const query = document.getElementById('wfInstallSearch').value.trim();
|
|
12433
|
+
const results = document.getElementById('wfInstallResults');
|
|
12434
|
+
if (!query) return;
|
|
12435
|
+
results.innerHTML = '<div style="padding:16px;color:var(--text-muted);font-size:12px;text-align:center;">Searching...</div>';
|
|
12436
|
+
try {
|
|
12437
|
+
const res = await fetch('/api/workflows/community/search?q=' + encodeURIComponent(query) + '&limit=10');
|
|
12438
|
+
const data = await res.json();
|
|
12439
|
+
if (!data.results || data.results.length === 0) {
|
|
12440
|
+
results.innerHTML = '<div style="padding:16px;color:var(--text-muted);font-size:12px;text-align:center;">No packages found</div>';
|
|
12441
|
+
return;
|
|
12442
|
+
}
|
|
12443
|
+
results.innerHTML = data.results.map(r => {
|
|
12444
|
+
const isOfficial = r.name.startsWith('@vaicli/');
|
|
12445
|
+
const badge = isOfficial ? '<span style="background:var(--accent);color:#fff;font-size:9px;padding:1px 5px;border-radius:3px;margin-left:6px;">OFFICIAL</span>' : '';
|
|
12446
|
+
const installed = [...(wfState.official||[]), ...(wfState.community||[])].some(w => w.name === r.name);
|
|
12447
|
+
const btn = installed
|
|
12448
|
+
? '<button disabled style="padding:4px 10px;background:var(--bg);border:1px solid var(--border);border-radius:4px;color:var(--text-muted);font-size:11px;cursor:default;">Installed</button>'
|
|
12449
|
+
: `<button onclick="wfInstallPkg('${r.name.replace(/'/g,"\\'")}')" style="padding:4px 10px;background:var(--accent);color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:11px;">Install</button>`;
|
|
12450
|
+
return `<div style="padding:10px 12px;border-bottom:1px solid var(--border);display:flex;align-items:flex-start;justify-content:space-between;">
|
|
12451
|
+
<div style="flex:1;min-width:0;">
|
|
12452
|
+
<div style="font-size:13px;font-weight:500;">${r.name}${badge}</div>
|
|
12453
|
+
<div style="font-size:11px;color:var(--text-muted);margin-top:2px;">${r.description || ''}</div>
|
|
12454
|
+
<div style="font-size:10px;color:var(--text-muted);margin-top:2px;">v${r.version || '?'}</div>
|
|
12455
|
+
</div>
|
|
12456
|
+
<div style="margin-left:12px;flex-shrink:0;">${btn}</div>
|
|
12457
|
+
</div>`;
|
|
12458
|
+
}).join('');
|
|
12459
|
+
} catch (err) {
|
|
12460
|
+
results.innerHTML = `<div style="padding:16px;color:#f44;font-size:12px;text-align:center;">Search failed: ${err.message}</div>`;
|
|
12461
|
+
}
|
|
12462
|
+
}
|
|
12463
|
+
|
|
12464
|
+
async function wfInstallPkg(name) {
|
|
12465
|
+
const results = document.getElementById('wfInstallResults');
|
|
12466
|
+
// Find the clicked button and show installing state
|
|
12467
|
+
const btns = results.querySelectorAll('button');
|
|
12468
|
+
let clickedBtn = null;
|
|
12469
|
+
btns.forEach(b => {
|
|
12470
|
+
if (b.textContent === 'Install' && !clickedBtn) {
|
|
12471
|
+
// Find the button in the same row as the package name
|
|
12472
|
+
const row = b.closest('div[style*="border-bottom"]');
|
|
12473
|
+
if (row && row.textContent.includes(name)) {
|
|
12474
|
+
clickedBtn = b;
|
|
12475
|
+
b.textContent = 'Installing...';
|
|
12476
|
+
b.disabled = true;
|
|
12477
|
+
b.style.opacity = '0.7';
|
|
12478
|
+
}
|
|
12479
|
+
}
|
|
12480
|
+
});
|
|
12481
|
+
|
|
12482
|
+
try {
|
|
12483
|
+
const res = await fetch('/api/workflows/community/install', {
|
|
12484
|
+
method: 'POST',
|
|
12485
|
+
headers: { 'Content-Type': 'application/json' },
|
|
12486
|
+
body: JSON.stringify({ name })
|
|
12487
|
+
});
|
|
12488
|
+
const text = await res.text();
|
|
12489
|
+
let data;
|
|
12490
|
+
try { data = JSON.parse(text); } catch { data = { error: text || 'Empty response' }; }
|
|
12491
|
+
if (data.success) {
|
|
12492
|
+
if (clickedBtn) { clickedBtn.textContent = '✓ Installed'; clickedBtn.style.opacity = '1'; }
|
|
12493
|
+
await wfLoadLibrary();
|
|
12494
|
+
await wfSearchNpm();
|
|
12495
|
+
} else {
|
|
12496
|
+
const errMsg = data.error || 'Unknown error';
|
|
12497
|
+
if (clickedBtn) { clickedBtn.textContent = 'Install'; clickedBtn.disabled = false; clickedBtn.style.opacity = '1'; }
|
|
12498
|
+
console.error('Install failed:', errMsg);
|
|
12499
|
+
alert('Install failed: ' + errMsg);
|
|
12500
|
+
}
|
|
12501
|
+
} catch (err) {
|
|
12502
|
+
if (clickedBtn) { clickedBtn.textContent = 'Install'; clickedBtn.disabled = false; clickedBtn.style.opacity = '1'; }
|
|
12503
|
+
console.error('Install error:', err);
|
|
12504
|
+
alert('Install failed: ' + err.message);
|
|
12505
|
+
}
|
|
12506
|
+
}
|
|
12507
|
+
|
|
10117
12508
|
function wfToggleExamples(btn) {
|
|
10118
12509
|
const content = btn.nextElementSibling;
|
|
10119
12510
|
const isOpen = content.style.display !== 'none';
|
|
@@ -10254,7 +12645,20 @@ async function wfRenderWorkflow(definition) {
|
|
|
10254
12645
|
nodeHasDeps[stepId] = true;
|
|
10255
12646
|
nodeHasDependents[depId] = true;
|
|
10256
12647
|
}
|
|
10257
|
-
});
|
|
12648
|
+
});
|
|
12649
|
+
}
|
|
12650
|
+
|
|
12651
|
+
// Build conditional branch maps for edge styling
|
|
12652
|
+
const elseBranchEdges = new Set(); // "fromId->toId" for else branches
|
|
12653
|
+
if (definition.steps) {
|
|
12654
|
+
for (const step of definition.steps) {
|
|
12655
|
+
if (step.tool === 'conditional' && step.inputs) {
|
|
12656
|
+
const elseSteps = step.inputs.else || [];
|
|
12657
|
+
for (const eId of elseSteps) {
|
|
12658
|
+
elseBranchEdges.add(`${step.id}->${eId}`);
|
|
12659
|
+
}
|
|
12660
|
+
}
|
|
12661
|
+
}
|
|
10258
12662
|
}
|
|
10259
12663
|
|
|
10260
12664
|
// Draw edges first (behind nodes)
|
|
@@ -10267,6 +12671,15 @@ async function wfRenderWorkflow(definition) {
|
|
|
10267
12671
|
const depId = rawDepId.replace(/^!/, '');
|
|
10268
12672
|
if (positions[depId] && positions[stepId]) {
|
|
10269
12673
|
const edge = wfDrawEdge(depId, stepId, positions);
|
|
12674
|
+
// Mark else-branch edges with dashed style
|
|
12675
|
+
const edgeKey = `${depId}->${stepId}`;
|
|
12676
|
+
if (elseBranchEdges.has(edgeKey)) {
|
|
12677
|
+
edge.querySelector('.wf-edge')?.classList.add('wf-edge--else');
|
|
12678
|
+
}
|
|
12679
|
+
// Mark edges to skipped nodes
|
|
12680
|
+
if (wfState.executionState[stepId] === 'skipped') {
|
|
12681
|
+
edge.querySelector('.wf-edge')?.classList.add('wf-edge--skipped');
|
|
12682
|
+
}
|
|
10270
12683
|
edgeGroup.appendChild(edge);
|
|
10271
12684
|
}
|
|
10272
12685
|
});
|
|
@@ -10334,14 +12747,27 @@ function wfDrawNode(step, x, y, state, hasDeps, hasDependents) {
|
|
|
10334
12747
|
});
|
|
10335
12748
|
}
|
|
10336
12749
|
|
|
10337
|
-
// Background rect
|
|
10338
|
-
|
|
10339
|
-
|
|
10340
|
-
|
|
10341
|
-
|
|
10342
|
-
|
|
10343
|
-
|
|
10344
|
-
|
|
12750
|
+
// Background shape: diamond for conditional, rounded rect for others
|
|
12751
|
+
if (meta.shape === 'diamond') {
|
|
12752
|
+
const diamond = document.createElementNS('http://www.w3.org/2000/svg', 'polygon');
|
|
12753
|
+
const cx = WF_NODE_W / 2, cy = WF_NODE_H / 2;
|
|
12754
|
+
const rx = WF_NODE_W / 2 + 8, ry = WF_NODE_H / 2 + 4;
|
|
12755
|
+
diamond.setAttribute('points', `${cx},${cy - ry} ${cx + rx},${cy} ${cx},${cy + ry} ${cx - rx},${cy}`);
|
|
12756
|
+
diamond.setAttribute('fill', meta.color);
|
|
12757
|
+
diamond.setAttribute('stroke', meta.color);
|
|
12758
|
+
diamond.setAttribute('opacity', '0.85');
|
|
12759
|
+
g.appendChild(diamond);
|
|
12760
|
+
} else {
|
|
12761
|
+
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
|
12762
|
+
rect.setAttribute('width', WF_NODE_W);
|
|
12763
|
+
rect.setAttribute('height', WF_NODE_H);
|
|
12764
|
+
rect.setAttribute('rx', '6');
|
|
12765
|
+
rect.setAttribute('ry', '6');
|
|
12766
|
+
rect.setAttribute('fill', meta.color);
|
|
12767
|
+
rect.setAttribute('stroke', meta.color);
|
|
12768
|
+
rect.setAttribute('opacity', '0.85');
|
|
12769
|
+
g.appendChild(rect);
|
|
12770
|
+
}
|
|
10345
12771
|
|
|
10346
12772
|
// Input port (left side): show in builder mode always, otherwise only if has deps
|
|
10347
12773
|
const showInPort = wfState.builderMode || hasDeps;
|
|
@@ -10516,22 +12942,200 @@ function wfDrawEdge(fromId, toId, positions) {
|
|
|
10516
12942
|
return g;
|
|
10517
12943
|
}
|
|
10518
12944
|
|
|
12945
|
+
// ── Panel Layout State ──
|
|
12946
|
+
let _wfLayoutSaveTimer = null;
|
|
12947
|
+
function wfGetLayoutState() {
|
|
12948
|
+
try {
|
|
12949
|
+
const s = localStorage.getItem('vai-workflow-layout');
|
|
12950
|
+
if (s) return JSON.parse(s);
|
|
12951
|
+
} catch(e) {}
|
|
12952
|
+
return { library: { open: true }, properties: { open: true } };
|
|
12953
|
+
}
|
|
12954
|
+
function wfSaveLayoutState() {
|
|
12955
|
+
clearTimeout(_wfLayoutSaveTimer);
|
|
12956
|
+
_wfLayoutSaveTimer = setTimeout(() => {
|
|
12957
|
+
const lib = document.getElementById('wfLibrary');
|
|
12958
|
+
const insp = document.getElementById('wfInspector');
|
|
12959
|
+
const existing = wfGetLayoutState();
|
|
12960
|
+
const state = {
|
|
12961
|
+
library: { open: lib ? !lib.classList.contains('collapsed') : true, sections: (existing.library && existing.library.sections) || { builtin: true, examples: true, catalog: false, community: false } },
|
|
12962
|
+
properties: { open: insp ? !insp.classList.contains('collapsed') : true, sections: (existing.properties && existing.properties.sections) || { inputs: true, steps: false, output: false } }
|
|
12963
|
+
};
|
|
12964
|
+
try { localStorage.setItem('vai-workflow-layout', JSON.stringify(state)); } catch(e) {}
|
|
12965
|
+
}, 300);
|
|
12966
|
+
}
|
|
12967
|
+
// ── Properties Accordion State ──
|
|
12968
|
+
function wfGetAccordionStates() {
|
|
12969
|
+
const state = wfGetLayoutState();
|
|
12970
|
+
return (state.properties && state.properties.sections) || { inputs: true, steps: false, output: false };
|
|
12971
|
+
}
|
|
12972
|
+
function wfSaveAccordionState(key, expanded) {
|
|
12973
|
+
const state = wfGetLayoutState();
|
|
12974
|
+
if (!state.properties) state.properties = { open: true };
|
|
12975
|
+
if (!state.properties.sections) state.properties.sections = { inputs: true, steps: false, output: false };
|
|
12976
|
+
state.properties.sections[key] = expanded;
|
|
12977
|
+
try { localStorage.setItem('vai-workflow-layout', JSON.stringify(state)); } catch(e) {}
|
|
12978
|
+
}
|
|
12979
|
+
function wfToggleAccordion(key, headerEl) {
|
|
12980
|
+
const body = document.querySelector(`.wf-accordion-body[data-acc="${key}"]`);
|
|
12981
|
+
if (!body) return;
|
|
12982
|
+
const expanded = headerEl.classList.contains('expanded');
|
|
12983
|
+
const newVal = !expanded;
|
|
12984
|
+
headerEl.classList.toggle('expanded', newVal);
|
|
12985
|
+
if (newVal) {
|
|
12986
|
+
body.style.maxHeight = body.scrollHeight + 'px';
|
|
12987
|
+
setTimeout(() => { body.style.maxHeight = 'none'; }, 200);
|
|
12988
|
+
} else {
|
|
12989
|
+
body.style.maxHeight = body.scrollHeight + 'px';
|
|
12990
|
+
requestAnimationFrame(() => { body.style.maxHeight = '0px'; });
|
|
12991
|
+
}
|
|
12992
|
+
wfSaveAccordionState(key, newVal);
|
|
12993
|
+
}
|
|
12994
|
+
|
|
12995
|
+
function wfSyncPanelUI() {
|
|
12996
|
+
const lib = document.getElementById('wfLibrary');
|
|
12997
|
+
const insp = document.getElementById('wfInspector');
|
|
12998
|
+
const libBtn = document.getElementById('wfToggleLibBtn');
|
|
12999
|
+
const propsBtn = document.getElementById('wfTogglePropsBtn');
|
|
13000
|
+
const leftHandle = document.getElementById('wfEdgeHandleLeft');
|
|
13001
|
+
const rightHandle = document.getElementById('wfEdgeHandleRight');
|
|
13002
|
+
const libCollapsed = lib && lib.classList.contains('collapsed');
|
|
13003
|
+
const inspCollapsed = insp && insp.classList.contains('collapsed');
|
|
13004
|
+
if (libBtn) libBtn.classList.toggle('active', !libCollapsed);
|
|
13005
|
+
if (propsBtn) propsBtn.classList.toggle('active', !inspCollapsed);
|
|
13006
|
+
if (leftHandle) leftHandle.classList.toggle('visible', !!libCollapsed);
|
|
13007
|
+
if (rightHandle) rightHandle.classList.toggle('visible', !!inspCollapsed);
|
|
13008
|
+
}
|
|
13009
|
+
function wfRestoreLayoutState() {
|
|
13010
|
+
const state = wfGetLayoutState();
|
|
13011
|
+
const lib = document.getElementById('wfLibrary');
|
|
13012
|
+
const insp = document.getElementById('wfInspector');
|
|
13013
|
+
if (lib) lib.classList.toggle('collapsed', !state.library.open);
|
|
13014
|
+
if (insp) insp.classList.toggle('collapsed', !state.properties.open);
|
|
13015
|
+
wfSyncPanelUI();
|
|
13016
|
+
}
|
|
13017
|
+
|
|
13018
|
+
// ── Library Toggle ──
|
|
13019
|
+
function wfToggleLibrary() {
|
|
13020
|
+
const panel = document.getElementById('wfLibrary');
|
|
13021
|
+
if (!panel) return;
|
|
13022
|
+
panel.classList.toggle('collapsed');
|
|
13023
|
+
wfSyncPanelUI();
|
|
13024
|
+
wfSaveLayoutState();
|
|
13025
|
+
}
|
|
13026
|
+
|
|
10519
13027
|
// ── Inspector Toggle ──
|
|
10520
13028
|
function wfToggleInspector() {
|
|
10521
13029
|
const panel = document.getElementById('wfInspector');
|
|
10522
|
-
const btn = document.getElementById('wfInspectorToggle');
|
|
10523
13030
|
if (!panel) return;
|
|
10524
13031
|
panel.classList.toggle('collapsed');
|
|
10525
|
-
|
|
13032
|
+
wfSyncPanelUI();
|
|
13033
|
+
wfSaveLayoutState();
|
|
10526
13034
|
}
|
|
10527
13035
|
|
|
10528
13036
|
function wfOpenInspector() {
|
|
10529
13037
|
const panel = document.getElementById('wfInspector');
|
|
10530
|
-
const btn = document.getElementById('wfInspectorToggle');
|
|
10531
13038
|
if (!panel || !panel.classList.contains('collapsed')) return;
|
|
10532
13039
|
panel.classList.remove('collapsed');
|
|
10533
|
-
|
|
13040
|
+
wfSyncPanelUI();
|
|
13041
|
+
wfSaveLayoutState();
|
|
13042
|
+
}
|
|
13043
|
+
|
|
13044
|
+
// ── Sidebar Collapse ──
|
|
13045
|
+
function toggleSidebarCollapse() {
|
|
13046
|
+
const sidebar = document.querySelector('.sidebar');
|
|
13047
|
+
if (!sidebar) return;
|
|
13048
|
+
sidebar.classList.toggle('collapsed');
|
|
13049
|
+
// Persist
|
|
13050
|
+
const state = wfGetLayoutState();
|
|
13051
|
+
if (!state.sidebar) state.sidebar = {};
|
|
13052
|
+
state.sidebar.collapsed = sidebar.classList.contains('collapsed');
|
|
13053
|
+
try { localStorage.setItem('vai-workflow-layout', JSON.stringify(state)); } catch(e) {}
|
|
13054
|
+
}
|
|
13055
|
+
function restoreSidebarState() {
|
|
13056
|
+
const state = wfGetLayoutState();
|
|
13057
|
+
if (state.sidebar && state.sidebar.collapsed) {
|
|
13058
|
+
const sidebar = document.querySelector('.sidebar');
|
|
13059
|
+
if (sidebar) sidebar.classList.add('collapsed');
|
|
13060
|
+
}
|
|
10534
13061
|
}
|
|
13062
|
+
restoreSidebarState();
|
|
13063
|
+
|
|
13064
|
+
// ── Keyboard Shortcuts (Workflows tab only) ──
|
|
13065
|
+
let _wfLastOpenedPanel = null; // 'library' or 'properties'
|
|
13066
|
+
(function() {
|
|
13067
|
+
const origToggleLib = window.wfToggleLibrary;
|
|
13068
|
+
window.wfToggleLibrary = function() {
|
|
13069
|
+
origToggleLib();
|
|
13070
|
+
const lib = document.getElementById('wfLibrary');
|
|
13071
|
+
if (lib && !lib.classList.contains('collapsed')) _wfLastOpenedPanel = 'library';
|
|
13072
|
+
};
|
|
13073
|
+
const origToggleInsp = window.wfToggleInspector;
|
|
13074
|
+
window.wfToggleInspector = function() {
|
|
13075
|
+
origToggleInsp();
|
|
13076
|
+
const insp = document.getElementById('wfInspector');
|
|
13077
|
+
if (insp && !insp.classList.contains('collapsed')) _wfLastOpenedPanel = 'properties';
|
|
13078
|
+
};
|
|
13079
|
+
})();
|
|
13080
|
+
|
|
13081
|
+
document.addEventListener('keydown', function(e) {
|
|
13082
|
+
// Only active when Workflows tab is visible
|
|
13083
|
+
const wfTab = document.getElementById('tab-workflows');
|
|
13084
|
+
if (!wfTab || wfTab.style.display === 'none' || !wfTab.classList.contains('active')) {
|
|
13085
|
+
// Check if tab-btn-workflows is active instead
|
|
13086
|
+
const wfBtn = document.getElementById('tab-btn-workflows');
|
|
13087
|
+
if (!wfBtn || !wfBtn.classList.contains('active')) return;
|
|
13088
|
+
}
|
|
13089
|
+
|
|
13090
|
+
const mod = e.metaKey || e.ctrlKey;
|
|
13091
|
+
if (!mod && e.key !== 'Escape') return;
|
|
13092
|
+
|
|
13093
|
+
// Cmd/Ctrl + Shift + B → Toggle sidebar
|
|
13094
|
+
if (mod && e.shiftKey && (e.key === 'B' || e.key === 'b')) {
|
|
13095
|
+
e.preventDefault();
|
|
13096
|
+
toggleSidebarCollapse();
|
|
13097
|
+
return;
|
|
13098
|
+
}
|
|
13099
|
+
// Cmd/Ctrl + B → Toggle Library
|
|
13100
|
+
if (mod && !e.shiftKey && (e.key === 'B' || e.key === 'b')) {
|
|
13101
|
+
e.preventDefault();
|
|
13102
|
+
wfToggleLibrary();
|
|
13103
|
+
return;
|
|
13104
|
+
}
|
|
13105
|
+
// Cmd/Ctrl + I → Toggle Properties
|
|
13106
|
+
if (mod && (e.key === 'I' || e.key === 'i')) {
|
|
13107
|
+
e.preventDefault();
|
|
13108
|
+
wfToggleInspector();
|
|
13109
|
+
return;
|
|
13110
|
+
}
|
|
13111
|
+
// Cmd/Ctrl + \ → Collapse both panels
|
|
13112
|
+
if (mod && e.key === '\\') {
|
|
13113
|
+
e.preventDefault();
|
|
13114
|
+
const lib = document.getElementById('wfLibrary');
|
|
13115
|
+
const insp = document.getElementById('wfInspector');
|
|
13116
|
+
if (lib && !lib.classList.contains('collapsed')) { lib.classList.add('collapsed'); }
|
|
13117
|
+
if (insp && !insp.classList.contains('collapsed')) { insp.classList.add('collapsed'); }
|
|
13118
|
+
wfSyncPanelUI();
|
|
13119
|
+
wfSaveLayoutState();
|
|
13120
|
+
return;
|
|
13121
|
+
}
|
|
13122
|
+
// Escape → Close most recently opened panel
|
|
13123
|
+
if (e.key === 'Escape' && !mod) {
|
|
13124
|
+
if (_wfLastOpenedPanel === 'library') {
|
|
13125
|
+
const lib = document.getElementById('wfLibrary');
|
|
13126
|
+
if (lib && !lib.classList.contains('collapsed')) { wfToggleLibrary(); _wfLastOpenedPanel = null; return; }
|
|
13127
|
+
}
|
|
13128
|
+
if (_wfLastOpenedPanel === 'properties') {
|
|
13129
|
+
const insp = document.getElementById('wfInspector');
|
|
13130
|
+
if (insp && !insp.classList.contains('collapsed')) { wfToggleInspector(); _wfLastOpenedPanel = null; return; }
|
|
13131
|
+
}
|
|
13132
|
+
// Fallback: close properties then library
|
|
13133
|
+
const insp = document.getElementById('wfInspector');
|
|
13134
|
+
if (insp && !insp.classList.contains('collapsed')) { wfToggleInspector(); return; }
|
|
13135
|
+
const lib = document.getElementById('wfLibrary');
|
|
13136
|
+
if (lib && !lib.classList.contains('collapsed')) { wfToggleLibrary(); return; }
|
|
13137
|
+
}
|
|
13138
|
+
});
|
|
10535
13139
|
|
|
10536
13140
|
// ── Node Selection ──
|
|
10537
13141
|
function wfSelectNode(stepId) {
|
|
@@ -10593,56 +13197,83 @@ function wfUpdateInspector() {
|
|
|
10593
13197
|
<div style="font-size:10px;color:var(--text-muted);">Add steps from the Palette tab, then drag between ports to connect them.</div>
|
|
10594
13198
|
</div>`;
|
|
10595
13199
|
} else {
|
|
10596
|
-
// Read-only: Description
|
|
13200
|
+
// Read-only: Description (above accordion)
|
|
10597
13201
|
if (def.description) {
|
|
10598
13202
|
html += `<div class="wf-inspector-section">
|
|
10599
|
-
<div class="wf-inspector-section-title">Description</div>
|
|
10600
13203
|
<div style="font-size:12px;color:var(--text);line-height:1.4;">${escapeHtml(def.description)}</div>
|
|
10601
13204
|
</div>`;
|
|
10602
13205
|
}
|
|
10603
13206
|
|
|
10604
|
-
//
|
|
10605
|
-
|
|
10606
|
-
|
|
13207
|
+
// Get accordion states from localStorage
|
|
13208
|
+
const accStates = wfGetAccordionStates();
|
|
13209
|
+
const hasDone = !!wfState.executionResults._done;
|
|
13210
|
+
|
|
13211
|
+
// ── INPUTS Accordion ──
|
|
13212
|
+
const inputKeys = def.inputs ? Object.keys(def.inputs) : [];
|
|
13213
|
+
const inputsExpanded = accStates.inputs !== false;
|
|
13214
|
+
html += `<div class="wf-accordion-header ${inputsExpanded ? 'expanded' : ''}" onclick="wfToggleAccordion('inputs', this)">
|
|
13215
|
+
<span class="wf-acc-left"><span class="wf-chevron">▶</span> INPUTS</span>
|
|
13216
|
+
<span class="wf-acc-summary">${inputKeys.length} field${inputKeys.length !== 1 ? 's' : ''}</span>
|
|
13217
|
+
</div>
|
|
13218
|
+
<div class="wf-accordion-body" data-acc="inputs" style="max-height:${inputsExpanded ? 'none' : '0px'};">
|
|
13219
|
+
<div class="wf-accordion-body-inner">`;
|
|
13220
|
+
if (inputKeys.length > 0) {
|
|
10607
13221
|
for (const [key, spec] of Object.entries(def.inputs)) {
|
|
10608
13222
|
const req = spec.required ? ' <span style="color:#e74c3c">*</span>' : '';
|
|
10609
|
-
|
|
10610
|
-
html += `<div style="margin-bottom:8px;">
|
|
13223
|
+
html += `<div style="margin-bottom:12px;">
|
|
10611
13224
|
<div style="font-size:12px;font-weight:600;color:var(--text);">${escapeHtml(key)}${req}</div>
|
|
10612
|
-
<div style="font-size:
|
|
10613
|
-
<input class="wf-inspector-input" id="wf-input-${key}" placeholder="${escapeHtml(key)}" value="${spec.default !== undefined ? spec.default : ''}">
|
|
13225
|
+
<div style="font-size:10px;color:var(--text-muted);margin-bottom:4px;">${escapeHtml(spec.description || spec.type || '')}</div>
|
|
13226
|
+
<input class="wf-inspector-input" id="wf-input-${key}" placeholder="${escapeHtml(key)}" value="${spec.default !== undefined ? escapeHtml(String(spec.default)) : ''}">
|
|
10614
13227
|
</div>`;
|
|
10615
13228
|
}
|
|
10616
|
-
|
|
13229
|
+
} else {
|
|
13230
|
+
html += '<div style="font-size:12px;color:var(--text-muted);">No inputs defined</div>';
|
|
10617
13231
|
}
|
|
10618
|
-
|
|
10619
|
-
|
|
10620
|
-
|
|
10621
|
-
|
|
10622
|
-
|
|
10623
|
-
|
|
10624
|
-
|
|
10625
|
-
|
|
10626
|
-
|
|
10627
|
-
|
|
10628
|
-
|
|
10629
|
-
|
|
13232
|
+
html += '</div></div>';
|
|
13233
|
+
|
|
13234
|
+
// ── STEPS Accordion ──
|
|
13235
|
+
const stepCount = def.steps.length;
|
|
13236
|
+
const layerCount = wfState.layers ? wfState.layers.length : 0;
|
|
13237
|
+
const stepsSummary = `${stepCount} step${stepCount !== 1 ? 's' : ''}${layerCount ? ' · ' + layerCount + ' layer' + (layerCount !== 1 ? 's' : '') : ''}`;
|
|
13238
|
+
const stepsExpanded = accStates.steps === true;
|
|
13239
|
+
html += `<div class="wf-accordion-header ${stepsExpanded ? 'expanded' : ''}" onclick="wfToggleAccordion('steps', this)">
|
|
13240
|
+
<span class="wf-acc-left"><span class="wf-chevron">▶</span> STEPS</span>
|
|
13241
|
+
<span class="wf-acc-summary">${stepsSummary}</span>
|
|
13242
|
+
</div>
|
|
13243
|
+
<div class="wf-accordion-body" data-acc="steps" style="max-height:${stepsExpanded ? 'none' : '0px'};">
|
|
13244
|
+
<div class="wf-accordion-body-inner">`;
|
|
13245
|
+
for (const step of def.steps) {
|
|
13246
|
+
const meta = WF_NODE_META[step.tool] || { color: '#666' };
|
|
13247
|
+
const result = wfState.executionResults[step.id];
|
|
13248
|
+
const timeStr = result && result.timeMs != null ? result.timeMs + 'ms' : '—';
|
|
13249
|
+
html += `<div class="wf-step-row">
|
|
13250
|
+
<span class="wf-step-dot" style="background:${meta.color}"></span>
|
|
13251
|
+
<span class="wf-step-name">${escapeHtml(step.name || step.id)}</span>
|
|
13252
|
+
<span class="wf-step-time">${timeStr}</span>
|
|
10630
13253
|
</div>`;
|
|
10631
13254
|
}
|
|
10632
|
-
|
|
13255
|
+
html += '</div></div>';
|
|
10633
13256
|
|
|
10634
|
-
|
|
10635
|
-
|
|
10636
|
-
|
|
10637
|
-
|
|
10638
|
-
|
|
10639
|
-
|
|
10640
|
-
|
|
10641
|
-
|
|
10642
|
-
|
|
10643
|
-
|
|
10644
|
-
|
|
10645
|
-
|
|
13257
|
+
// ── OUTPUT Accordion ──
|
|
13258
|
+
const outputExpanded = hasDone || accStates.output === true;
|
|
13259
|
+
html += `<div class="wf-accordion-header ${outputExpanded ? 'expanded' : ''}" onclick="wfToggleAccordion('output', this)">
|
|
13260
|
+
<span class="wf-acc-left"><span class="wf-chevron">▶</span> OUTPUT</span>
|
|
13261
|
+
<span class="wf-acc-summary"></span>
|
|
13262
|
+
</div>
|
|
13263
|
+
<div class="wf-accordion-body" data-acc="output" style="max-height:${outputExpanded ? 'none' : '0px'};">
|
|
13264
|
+
<div class="wf-accordion-body-inner">`;
|
|
13265
|
+
if (hasDone) {
|
|
13266
|
+
const r = wfState.executionResults._done;
|
|
13267
|
+
const doneJson = JSON.stringify(r.output, null, 2);
|
|
13268
|
+
html += `<div style="font-size:11px;color:var(--text-muted);margin-bottom:6px;">Completed in ${r.totalTimeMs}ms</div>
|
|
13269
|
+
<div class="wf-inspector-code" style="max-height:200px;overflow:auto;color:#40E0FF;">${escapeHtml(doneJson)}</div>
|
|
13270
|
+
<button class="wf-output-expand-btn" data-expand-step="_done">⤢ Expand</button>`;
|
|
13271
|
+
} else if (def.output) {
|
|
13272
|
+
html += `<div class="wf-inspector-code" style="color:#40E0FF;">${escapeHtml(JSON.stringify(def.output, null, 2))}</div>`;
|
|
13273
|
+
} else {
|
|
13274
|
+
html += '<div style="font-size:12px;color:var(--text-muted);">Run the workflow to see output</div>';
|
|
13275
|
+
}
|
|
13276
|
+
html += '</div></div>';
|
|
10646
13277
|
}
|
|
10647
13278
|
|
|
10648
13279
|
body.innerHTML = html;
|
|
@@ -10662,10 +13293,13 @@ function wfUpdateInspector() {
|
|
|
10662
13293
|
|
|
10663
13294
|
let html = '';
|
|
10664
13295
|
|
|
10665
|
-
// Tool badge (always shown)
|
|
13296
|
+
// Tool badge (always shown) with help button
|
|
10666
13297
|
html += `<div class="wf-inspector-section">
|
|
10667
13298
|
<div class="wf-inspector-section-title">Tool</div>
|
|
10668
|
-
<
|
|
13299
|
+
<div style="display:flex;align-items:center;">
|
|
13300
|
+
<span class="wf-tool-badge" style="background:${meta.color}"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:4px;"><path d="${meta.icon || WF_FALLBACK_ICON}"/></svg>${meta.label}</span>
|
|
13301
|
+
<button class="wf-inspector-help-btn" onclick="wfOpenHelpModal('${step.tool}')"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg> Help</button>
|
|
13302
|
+
</div>
|
|
10669
13303
|
</div>`;
|
|
10670
13304
|
|
|
10671
13305
|
if (wfState.builderMode) {
|
|
@@ -11323,9 +13957,105 @@ function wfCopyOutput() {
|
|
|
11323
13957
|
});
|
|
11324
13958
|
}
|
|
11325
13959
|
|
|
11326
|
-
//
|
|
13960
|
+
// ── Node Help Modal ──
|
|
13961
|
+
let _nodeHelpCache = null;
|
|
13962
|
+
async function getNodeHelp() {
|
|
13963
|
+
if (_nodeHelpCache) return _nodeHelpCache;
|
|
13964
|
+
try {
|
|
13965
|
+
const res = await fetch('/api/workflows/node-help');
|
|
13966
|
+
const data = await res.json();
|
|
13967
|
+
_nodeHelpCache = data.nodeHelp || {};
|
|
13968
|
+
} catch { _nodeHelpCache = {}; }
|
|
13969
|
+
return _nodeHelpCache;
|
|
13970
|
+
}
|
|
13971
|
+
|
|
13972
|
+
async function wfOpenHelpModal(tool) {
|
|
13973
|
+
const helpData = await getNodeHelp();
|
|
13974
|
+
const help = helpData[tool];
|
|
13975
|
+
const meta = WF_NODE_META[tool] || { icon: WF_FALLBACK_ICON, label: tool, color: '#666', category: 'unknown' };
|
|
13976
|
+
const categoryLabel = WF_CATEGORY_LABELS[meta.category] || meta.category || 'Unknown';
|
|
13977
|
+
|
|
13978
|
+
const headerEl = document.getElementById('wfHelpModalHeader');
|
|
13979
|
+
const bodyEl = document.getElementById('wfHelpModalBody');
|
|
13980
|
+
const backdrop = document.getElementById('wfHelpModalBackdrop');
|
|
13981
|
+
if (!headerEl || !bodyEl || !backdrop) return;
|
|
13982
|
+
|
|
13983
|
+
// Build header
|
|
13984
|
+
const isEmoji = !meta.icon.includes(' ');
|
|
13985
|
+
const iconHtml = isEmoji
|
|
13986
|
+
? `<div class="wf-help-icon" style="background:${meta.color}22;font-size:18px;">${meta.icon}</div>`
|
|
13987
|
+
: `<div class="wf-help-icon" style="background:${meta.color}22;color:${meta.color};"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="${meta.icon}"/></svg></div>`;
|
|
13988
|
+
|
|
13989
|
+
headerEl.innerHTML = iconHtml
|
|
13990
|
+
+ `<span class="wf-help-title">${escapeHtml(meta.label)}</span>`
|
|
13991
|
+
+ `<span class="wf-help-category-badge">${escapeHtml(categoryLabel)}</span>`
|
|
13992
|
+
+ `<button class="wf-help-close-btn" onclick="wfCloseHelpModal()" title="Close"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg></button>`;
|
|
13993
|
+
|
|
13994
|
+
// Build body
|
|
13995
|
+
if (!help) {
|
|
13996
|
+
bodyEl.innerHTML = `<div class="wf-help-section"><p>No help content available for this node yet.</p></div>`;
|
|
13997
|
+
backdrop.style.display = 'flex';
|
|
13998
|
+
return;
|
|
13999
|
+
}
|
|
14000
|
+
|
|
14001
|
+
let html = '';
|
|
14002
|
+
|
|
14003
|
+
// Overview
|
|
14004
|
+
if (help.description) {
|
|
14005
|
+
html += `<div class="wf-help-section"><div class="wf-help-section-title">Overview</div><p>${escapeHtml(help.description)}</p></div>`;
|
|
14006
|
+
}
|
|
14007
|
+
|
|
14008
|
+
// How it Works
|
|
14009
|
+
if (help.howItWorks) {
|
|
14010
|
+
html += `<div class="wf-help-section"><div class="wf-help-section-title">How it Works</div><p>${escapeHtml(help.howItWorks)}</p></div>`;
|
|
14011
|
+
}
|
|
14012
|
+
|
|
14013
|
+
// Inputs
|
|
14014
|
+
if (help.inputs && help.inputs.length > 0) {
|
|
14015
|
+
html += `<div class="wf-help-section"><div class="wf-help-section-title">Inputs</div><table class="wf-help-io-table"><thead><tr><th>Parameter</th><th>Type</th><th>Description</th></tr></thead><tbody>`;
|
|
14016
|
+
for (const inp of help.inputs) {
|
|
14017
|
+
const reqBadge = inp.required ? '<span class="wf-help-required">required</span>' : '';
|
|
14018
|
+
html += `<tr><td><span class="wf-help-key">${escapeHtml(inp.key)}</span>${reqBadge}</td><td><span class="wf-help-type">${escapeHtml(inp.type)}</span></td><td>${escapeHtml(inp.desc)}</td></tr>`;
|
|
14019
|
+
}
|
|
14020
|
+
html += '</tbody></table></div>';
|
|
14021
|
+
}
|
|
14022
|
+
|
|
14023
|
+
// Outputs
|
|
14024
|
+
if (help.outputs && help.outputs.length > 0) {
|
|
14025
|
+
html += `<div class="wf-help-section"><div class="wf-help-section-title">Output</div><table class="wf-help-io-table"><thead><tr><th>Field</th><th>Type</th><th>Description</th></tr></thead><tbody>`;
|
|
14026
|
+
for (const out of help.outputs) {
|
|
14027
|
+
html += `<tr><td><span class="wf-help-key">${escapeHtml(out.key)}</span></td><td><span class="wf-help-type">${escapeHtml(out.type)}</span></td><td>${escapeHtml(out.desc)}</td></tr>`;
|
|
14028
|
+
}
|
|
14029
|
+
html += '</tbody></table></div>';
|
|
14030
|
+
}
|
|
14031
|
+
|
|
14032
|
+
// Tips
|
|
14033
|
+
if (help.tips && help.tips.length > 0) {
|
|
14034
|
+
html += `<div class="wf-help-section"><div class="wf-help-section-title">Tips</div>`;
|
|
14035
|
+
for (const tip of help.tips) {
|
|
14036
|
+
html += `<div class="wf-help-tip"><span class="wf-help-tip-dot"></span><span>${escapeHtml(tip)}</span></div>`;
|
|
14037
|
+
}
|
|
14038
|
+
html += '</div>';
|
|
14039
|
+
}
|
|
14040
|
+
|
|
14041
|
+
bodyEl.innerHTML = html;
|
|
14042
|
+
backdrop.style.display = 'flex';
|
|
14043
|
+
}
|
|
14044
|
+
|
|
14045
|
+
function wfCloseHelpModal() {
|
|
14046
|
+
const backdrop = document.getElementById('wfHelpModalBackdrop');
|
|
14047
|
+
if (backdrop) backdrop.style.display = 'none';
|
|
14048
|
+
}
|
|
14049
|
+
|
|
14050
|
+
// Close modals on Escape
|
|
11327
14051
|
document.addEventListener('keydown', (e) => {
|
|
11328
14052
|
if (e.key === 'Escape') {
|
|
14053
|
+
const helpBackdrop = document.getElementById('wfHelpModalBackdrop');
|
|
14054
|
+
if (helpBackdrop && helpBackdrop.style.display !== 'none') {
|
|
14055
|
+
wfCloseHelpModal();
|
|
14056
|
+
e.preventDefault();
|
|
14057
|
+
return;
|
|
14058
|
+
}
|
|
11329
14059
|
const backdrop = document.getElementById('wfOutputModalBackdrop');
|
|
11330
14060
|
if (backdrop && backdrop.style.display !== 'none') {
|
|
11331
14061
|
wfCloseOutputModal();
|
|
@@ -11454,10 +14184,16 @@ const WF_INPUT_DEFS = {
|
|
|
11454
14184
|
filter: [{ key: 'input', type: 'text', required: true, placeholder: '{{ step.output }}' }, { key: 'condition', type: 'text', required: true, placeholder: 'item.score > 0.5' }],
|
|
11455
14185
|
transform: [{ key: 'input', type: 'text', required: true, placeholder: '{{ step.output }}' }, { key: 'expression', type: 'text', required: true, placeholder: 'item.text' }],
|
|
11456
14186
|
generate: [{ key: 'prompt', type: 'textarea', required: true, placeholder: 'Generate a summary of...' }, { key: 'context', type: 'text', required: false, placeholder: '{{ step.output }}' }],
|
|
14187
|
+
conditional: [{ key: 'condition', type: 'text', required: true, placeholder: '{{ step.output.results.length > 0 }}' }, { key: 'then', type: 'json', required: true, placeholder: '["step_a"]' }, { key: 'else', type: 'json', required: false, placeholder: '["step_b"]' }],
|
|
14188
|
+
loop: [{ key: 'items', type: 'text', required: true, placeholder: '{{ step.output.results }}' }, { key: 'as', type: 'text', required: true, placeholder: 'item' }, { key: 'step', type: 'json', required: true, placeholder: '{"tool":"template","inputs":{"text":"{{ item }}"}}' }, { key: 'maxIterations', type: 'number', required: false, placeholder: '100' }],
|
|
14189
|
+
template: [{ key: 'text', type: 'textarea', required: true, placeholder: 'Compose text with {{ step.output }} references' }],
|
|
14190
|
+
chunk: [{ key: 'text', type: 'textarea', required: true, placeholder: '{{ step.output.text }}' }, { key: 'strategy', type: 'select', required: false, options: ['fixed','sentence','paragraph','recursive','markdown'] }, { key: 'size', type: 'number', required: false, placeholder: '512' }, { key: 'overlap', type: 'number', required: false, placeholder: '50' }, { key: 'source', type: 'text', required: false, placeholder: 'document.md' }],
|
|
14191
|
+
aggregate: [{ key: 'pipeline', type: 'json', required: true, placeholder: '[{"$group":{"_id":"$field","count":{"$sum":1}}}]' }, { key: 'collection', type: 'text', required: false }, { key: 'db', type: 'text', required: false }],
|
|
14192
|
+
http: [{ key: 'url', type: 'text', required: true, placeholder: 'https://api.example.com/data' }, { key: 'method', type: 'select', required: false, options: ['GET','POST','PUT','PATCH','DELETE'] }, { key: 'headers', type: 'json', required: false, placeholder: '{"Authorization":"Bearer ..."}' }, { key: 'body', type: 'json', required: false, placeholder: '{}' }, { key: 'timeout', type: 'number', required: false, placeholder: '30000' }],
|
|
11457
14193
|
};
|
|
11458
14194
|
|
|
11459
|
-
const WF_CATEGORY_ORDER = ['retrieval', 'embedding', '
|
|
11460
|
-
const WF_CATEGORY_LABELS = { retrieval: 'Retrieval', embedding: 'Embedding', management: 'Management', utility: 'Utility', control: 'Control Flow', generation: 'Generation' };
|
|
14195
|
+
const WF_CATEGORY_ORDER = ['retrieval', 'embedding', 'processing', 'control', 'generation', 'integration', 'management', 'utility'];
|
|
14196
|
+
const WF_CATEGORY_LABELS = { retrieval: 'Retrieval', embedding: 'Embedding', management: 'Management', utility: 'Utility', control: 'Control Flow', generation: 'Generation', processing: 'Processing', integration: 'Integration' };
|
|
11461
14197
|
|
|
11462
14198
|
// ── Builder: Library/Palette tab toggle ──
|
|
11463
14199
|
function wfSwitchLibTab(tab) {
|
|
@@ -11488,6 +14224,7 @@ function wfRenderPalette() {
|
|
|
11488
14224
|
html += `<div class="wf-palette-item" draggable="true" ondragstart="event.dataTransfer.setData('text/plain','${item.tool}')" onclick="wfAddNodeFromPalette('${item.tool}')">
|
|
11489
14225
|
<span class="wf-palette-icon" style="color:${item.color}"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="${item.icon || WF_FALLBACK_ICON}"/></svg></span>
|
|
11490
14226
|
<span class="wf-palette-label">${item.label}</span>
|
|
14227
|
+
<button class="wf-help-trigger" onclick="event.stopPropagation();wfOpenHelpModal('${item.tool}')" title="Help: ${item.label}"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg></button>
|
|
11491
14228
|
</div>`;
|
|
11492
14229
|
}
|
|
11493
14230
|
html += '</div>';
|
|
@@ -11935,7 +14672,442 @@ document.addEventListener('keydown', (e) => {
|
|
|
11935
14672
|
});
|
|
11936
14673
|
|
|
11937
14674
|
// ── Init ──
|
|
14675
|
+
// ── Workflow Store ──
|
|
14676
|
+
const WF_STORE_TOOL_COLORS = {
|
|
14677
|
+
query:'#00D4AA',search:'#40E0FF',rerank:'#0EA5E9',embed:'#8B5CF6',similarity:'#A78BFA',
|
|
14678
|
+
ingest:'#10B981',collections:'#F59E0B',models:'#EF4444',explain:'#EC4899',estimate:'#F97316',
|
|
14679
|
+
merge:'#6366F1',filter:'#14B8A6',transform:'#D946EF',generate:'#F472B6'
|
|
14680
|
+
};
|
|
14681
|
+
const WF_STORE_CATEGORIES = [
|
|
14682
|
+
{ id:'all', label:'All', icon:'◈' }, { id:'retrieval', label:'Retrieval', icon:'⊕' },
|
|
14683
|
+
{ id:'analysis', label:'Analysis', icon:'◉' }, { id:'domain-specific', label:'Domain', icon:'◆' },
|
|
14684
|
+
{ id:'ingestion', label:'Ingestion', icon:'⊞' }, { id:'utility', label:'Utility', icon:'⊡' },
|
|
14685
|
+
{ id:'integration', label:'Integration', icon:'⊗' },
|
|
14686
|
+
];
|
|
14687
|
+
// Hardcoded fallback data matching the spec's 20 workflows
|
|
14688
|
+
const WF_STORE_FALLBACK = [
|
|
14689
|
+
{ name:'model-shootout', packageName:'@vaicli/vai-workflow-model-shootout', description:'Compare voyage-4-large, voyage-4, and voyage-4-lite side-by-side on your data.', category:'utility', tags:['benchmarking','model-comparison','cost','shared-space'], tools:['query','estimate','similarity','generate'], steps:7, tier:'official', downloads:3241, featured:true, installed:false, gradient:'linear-gradient(135deg, #0D9488, #06B6D4)' },
|
|
14690
|
+
{ name:'asymmetric-search', packageName:'@vaicli/vai-workflow-asymmetric-search', description:'The canonical shared embedding space demo. Embed with voyage-4-large, query with voyage-4-lite. ~83% cost reduction.', category:'retrieval', tags:['asymmetric','shared-space','cost-savings'], tools:['embed','search','rerank'], steps:3, tier:'official', downloads:4812, featured:true, installed:false, gradient:'linear-gradient(135deg, #00D4AA, #40E0FF)' },
|
|
14691
|
+
{ name:'cost-optimizer', packageName:'@vaicli/vai-workflow-cost-optimizer', description:'Quantify the exact cost savings of asymmetric retrieval on your collection.', category:'utility', tags:['cost','optimization','asymmetric'], tools:['query','estimate','similarity','generate'], steps:6, tier:'official', downloads:2918, featured:true, installed:false, gradient:'linear-gradient(135deg, #F59E0B, #EF4444)' },
|
|
14692
|
+
{ name:'question-decomposition', packageName:'@vaicli/vai-workflow-question-decomposition', description:'Break complex questions into focused sub-queries, search each in parallel, merge and rerank.', category:'retrieval', tags:['decomposition','fan-out','synthesis'], tools:['generate','query','merge','rerank'], steps:6, tier:'official', downloads:1876, installed:false, gradient:'linear-gradient(135deg, #8B5CF6, #EC4899)' },
|
|
14693
|
+
{ name:'contract-clause-finder', packageName:'@vaicli/vai-workflow-contract-clause-finder', description:'Search legal documents for specific clause types using voyage-law-2.', category:'domain-specific', tags:['legal','contracts','voyage-law-2'], tools:['query','rerank','generate'], steps:3, tier:'official', downloads:1543, installed:false, gradient:'linear-gradient(135deg, #1E40AF, #7C3AED)' },
|
|
14694
|
+
{ name:'knowledge-base-bootstrap', packageName:'@vaicli/vai-workflow-knowledge-base-bootstrap', description:'End-to-end onboarding: ingest documents, verify, test a query, and generate a status report.', category:'integration', tags:['onboarding','bootstrap','end-to-end'], tools:['ingest','collections','query','generate'], steps:4, tier:'official', downloads:3654, installed:false, gradient:'linear-gradient(135deg, #059669, #10B981)' },
|
|
14695
|
+
{ name:'embedding-drift-detector', packageName:'@vaicli/vai-workflow-embedding-drift-detector', description:'Monitor embedding quality over time. Re-embed sample documents and compare against stored vectors.', category:'analysis', tags:['monitoring','drift','operations'], tools:['search','embed','similarity','generate'], steps:5, tier:'official', downloads:987, installed:false, gradient:'linear-gradient(135deg, #DC2626, #F97316)' },
|
|
14696
|
+
{ name:'multilingual-search', packageName:'@vaicli/vai-workflow-multilingual-search', description:'Translate your query into multiple languages, search each in parallel, merge and rerank.', category:'retrieval', tags:['multilingual','translation','cross-lingual'], tools:['generate','query','merge','rerank'], steps:5, tier:'official', downloads:1234, installed:false, gradient:'linear-gradient(135deg, #0EA5E9, #6366F1)' },
|
|
14697
|
+
{ name:'financial-risk-scanner', packageName:'@vaicli/vai-workflow-financial-risk-scanner', description:'Scan financial documents for risk signals using voyage-finance-2.', category:'domain-specific', tags:['finance','risk','voyage-finance-2'], tools:['query','rerank','filter','generate'], steps:4, tier:'official', downloads:1102, installed:false, gradient:'linear-gradient(135deg, #B45309, #D97706)' },
|
|
14698
|
+
{ name:'doc-freshness', packageName:'@vaicli/vai-workflow-doc-freshness', description:'Audit your knowledge base for stale content. Gathers metadata from 5 tools in parallel.', category:'utility', tags:['freshness','maintenance','monitoring'], tools:['collections','models','search','explain','estimate','generate'], steps:6, tier:'official', downloads:1456, installed:false, gradient:'linear-gradient(135deg, #4338CA, #7C3AED)' },
|
|
14699
|
+
{ name:'incremental-sync', packageName:'@vaicli/vai-workflow-incremental-sync', description:'Smart ingestion with deduplication. Checks similarity before storing.', category:'ingestion', tags:['dedup','sync','conditional'], tools:['search','similarity','ingest','filter','generate'], steps:5, tier:'official', downloads:2103, installed:false, gradient:'linear-gradient(135deg, #15803D, #4ADE80)' },
|
|
14700
|
+
{ name:'rag-ab-test', packageName:'@vaicli/vai-workflow-rag-ab-test', description:'Run the same query through two RAG configurations side-by-side.', category:'integration', tags:['ab-test','comparison','evaluation'], tools:['query','generate'], steps:5, tier:'official', downloads:1678, installed:false, gradient:'linear-gradient(135deg, #BE185D, #F472B6)' },
|
|
14701
|
+
{ name:'hybrid-precision-search', packageName:'@vaicli/vai-workflow-hybrid-precision-search', description:'Three retrieval strategies in parallel — lite, large, and filtered — merged and reranked.', category:'retrieval', tags:['hybrid','parallel','precision'], tools:['query','search','merge','rerank'], steps:5, tier:'official', downloads:1890, installed:false, gradient:'linear-gradient(135deg, #0891B2, #22D3EE)' },
|
|
14702
|
+
{ name:'code-migration-helper', packageName:'@vaicli/vai-workflow-code-migration-helper', description:'Find similar code patterns across your codebase using voyage-code-3.', category:'domain-specific', tags:['code','migration','voyage-code-3'], tools:['query','rerank','generate'], steps:3, tier:'official', downloads:932, installed:false, gradient:'linear-gradient(135deg, #475569, #94A3B8)' },
|
|
14703
|
+
{ name:'meeting-action-items', packageName:'@vaicli/vai-workflow-meeting-action-items', description:'Ingest meeting notes and extract structured action items with owners, deadlines, and context.', category:'domain-specific', tags:['meetings','action-items','productivity'], tools:['ingest','query','generate'], steps:3, tier:'official', downloads:2567, installed:false, gradient:'linear-gradient(135deg, #7C2D12, #EA580C)' },
|
|
14704
|
+
{ name:'collection-overlap-audit', packageName:'@vaicli/vai-workflow-collection-overlap-audit', description:'Detect duplicate content across two collections.', category:'analysis', tags:['dedup','overlap','audit'], tools:['search','similarity','merge','filter','generate'], steps:5, tier:'official', downloads:678, installed:false, gradient:'linear-gradient(135deg, #6D28D9, #A78BFA)' },
|
|
14705
|
+
{ name:'query-quality-scorer', packageName:'@vaicli/vai-workflow-query-quality-scorer', description:'Evaluate retrieval quality without ground truth labels.', category:'analysis', tags:['evaluation','quality','scoring'], tools:['query','similarity','transform','generate'], steps:4, tier:'official', downloads:823, installed:false, gradient:'linear-gradient(135deg, #9333EA, #C084FC)' },
|
|
14706
|
+
{ name:'clinical-protocol-match', packageName:'@vaicli/vai-workflow-clinical-protocol-match', description:'Match clinical presentations to treatment protocols.', category:'domain-specific', tags:['clinical','healthcare','protocols'], tools:['query','rerank','generate'], steps:3, tier:'official', downloads:712, installed:false, gradient:'linear-gradient(135deg, #0F766E, #2DD4BF)' },
|
|
14707
|
+
{ name:'batch-quality-gate', packageName:'@vaicli/vai-workflow-batch-quality-gate', description:'Quality-filtered ingestion. Only ingest content that meets your similarity threshold.', category:'ingestion', tags:['quality','gate','filtering'], tools:['search','embed','similarity','ingest','filter','generate'], steps:5, tier:'official', downloads:543, installed:false, gradient:'linear-gradient(135deg, #166534, #86EFAC)' },
|
|
14708
|
+
{ name:'index-health-check', packageName:'@vaicli/vai-workflow-index-health-check', description:'Diagnostic pipeline for your vector index.', category:'utility', tags:['diagnostics','health','index'], tools:['collections','search','query','estimate','generate'], steps:5, tier:'official', downloads:1345, installed:false, gradient:'linear-gradient(135deg, #1D4ED8, #60A5FA)' },
|
|
14709
|
+
];
|
|
14710
|
+
|
|
14711
|
+
// Inject branding into fallback data (matches DEFAULT_BRANDING in catalog API)
|
|
14712
|
+
const _WF_FALLBACK_BRANDING = {
|
|
14713
|
+
'model-shootout': { icon:'trophy', color:'#0D9488', iconPath:'M6 9H4.5a2.5 2.5 0 0 1 0-5H6M18 9h1.5a2.5 2.5 0 0 0 0-5H18M4 22h16M10 14.66V17c0 .55-.47.98-.97 1.21C7.85 18.75 7 20.24 7 22M14 14.66V17c0 .55.47.98.97 1.21C16.15 18.75 17 20.24 17 22M18 2H6v7a6 6 0 0 0 12 0V2z' },
|
|
14714
|
+
'asymmetric-search': { icon:'split', color:'#00D4AA', iconPath:'M16 3h5v5M8 3H3v5M12 22v-8.3a4 4 0 0 0-1.172-2.872L3 3M21 3l-7.828 7.828A4 4 0 0 0 12 13.7V22' },
|
|
14715
|
+
'cost-optimizer': { icon:'dollar-sign', color:'#F59E0B', iconPath:'M12 2v20M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6' },
|
|
14716
|
+
'question-decomposition': { icon:'sparkle', color:'#8B5CF6', iconPath:'M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z' },
|
|
14717
|
+
'contract-clause-finder': { icon:'file-search', color:'#1E40AF', iconPath:'M14 2v4a2 2 0 0 0 2 2h4M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7zM9.5 12.5a2.5 2.5 0 1 0 5 0 2.5 2.5 0 1 0-5 0M13.3 14.3 15 16' },
|
|
14718
|
+
'knowledge-base-bootstrap': { icon:'database', color:'#059669', iconPath:'M21 5c0 1.1-3.134 3-9 3S3 6.1 3 5M21 5c0-1.1-3.134-3-9-3S3 3.9 3 5M21 5v14c0 1.1-3.134 3-9 3s-9-1.9-9-3V5M21 12c0 1.1-3.134 3-9 3s-9-1.9-9-3' },
|
|
14719
|
+
'embedding-drift-detector': { icon:'activity', color:'#DC2626', iconPath:'M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36-3.18-19.64A2 2 0 0 0 10.12 1h-.24a2 2 0 0 0-1.94 1.55L5.18 12H2' },
|
|
14720
|
+
'multilingual-search': { icon:'globe', color:'#0EA5E9', iconPath:'M12 12m-10 0a10 10 0 1 0 20 0 10 10 0 1 0-20 0M2 12h20M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z' },
|
|
14721
|
+
'financial-risk-scanner': { icon:'shield-alert', color:'#B45309', iconPath:'M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 .5-.87l7-4a1 1 0 0 1 1 0l7 4A1 1 0 0 1 20 6zM12 8v4M12 16h.01' },
|
|
14722
|
+
'doc-freshness': { icon:'timer', color:'#4338CA', iconPath:'M10 2h4M12 14l3-3M12 22a8 8 0 1 0 0-16 8 8 0 0 0 0 16z' },
|
|
14723
|
+
'incremental-sync': { icon:'refresh-cw', color:'#15803D', iconPath:'M21 2v6h-6M3 12a9 9 0 0 1 15-6.7L21 8M3 22v-6h6M21 12a9 9 0 0 1-15 6.7L3 16' },
|
|
14724
|
+
'rag-ab-test': { icon:'flask-conical', color:'#BE185D', iconPath:'M10 2v7.527a2 2 0 0 1-.211.896L4.72 20.55a1 1 0 0 0 .9 1.45h12.76a1 1 0 0 0 .9-1.45l-5.069-10.127A2 2 0 0 1 14 9.527V2M8.5 2h7M7 16h10' },
|
|
14725
|
+
'hybrid-precision-search': { icon:'target', color:'#0891B2', iconPath:'M12 12m-10 0a10 10 0 1 0 20 0 10 10 0 1 0-20 0M12 12m-6 0a6 6 0 1 0 12 0 6 6 0 1 0-12 0M12 12m-2 0a2 2 0 1 0 4 0 2 2 0 1 0-4 0' },
|
|
14726
|
+
'code-migration-helper': { icon:'code', color:'#475569', iconPath:'M16 18l6-6-6-6M8 6l-6 6 6 6' },
|
|
14727
|
+
'meeting-action-items': { icon:'clipboard-list',color:'#7C2D12', iconPath:'M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2M9 2h6a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1H9a1 1 0 0 1-1-1V3a1 1 0 0 1 1-1zM12 11h4M12 16h4M8 11h.01M8 16h.01' },
|
|
14728
|
+
'collection-overlap-audit': { icon:'layers', color:'#6D28D9', iconPath:'M12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.84zM2 12l8.58 3.91a2 2 0 0 0 1.66 0L22 12M2 17l8.58 3.91a2 2 0 0 0 1.66 0L22 17' },
|
|
14729
|
+
'query-quality-scorer': { icon:'microscope', color:'#9333EA', iconPath:'M6 18h8M3 22h18M14 22a7 7 0 1 0 0-14h-1M9 14h2M9 12a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2z' },
|
|
14730
|
+
'clinical-protocol-match': { icon:'heart-pulse', color:'#0F766E', iconPath:'M19.5 12.572l-7.5 7.428-7.5-7.428A5 5 0 0 1 7.5 5c1.8 0 3.3.9 4.5 2.7C13.2 5.9 14.7 5 16.5 5a5 5 0 0 1 3 9.572zM12 6l-1 4h4l-1 4' },
|
|
14731
|
+
'batch-quality-gate': { icon:'check-circle', color:'#166534', iconPath:'M22 11.08V12a10 10 0 1 1-5.93-9.14M22 4 12 14.01l-3-3' },
|
|
14732
|
+
'index-health-check': { icon:'bar-chart-3', color:'#1D4ED8', iconPath:'M12 20V10M18 20V4M6 20v-4' },
|
|
14733
|
+
};
|
|
14734
|
+
WF_STORE_FALLBACK.forEach(wf => {
|
|
14735
|
+
wf.branding = _WF_FALLBACK_BRANDING[wf.name] || { icon:'zap', color:'#64748B', iconPath:'M13 2 3 14h9l-1 8 10-12h-9l1-8z' };
|
|
14736
|
+
wf.verified = true;
|
|
14737
|
+
wf.security = [];
|
|
14738
|
+
wf.rating = null;
|
|
14739
|
+
});
|
|
14740
|
+
|
|
14741
|
+
function getCapabilityBadges(tools) {
|
|
14742
|
+
if (!Array.isArray(tools)) return [];
|
|
14743
|
+
const badges = [];
|
|
14744
|
+
if (tools.includes('http')) badges.push({ emoji: '🌐', label: 'NETWORK', cls: 'wf-store-badge-cap-network' });
|
|
14745
|
+
if (tools.includes('ingest') || tools.includes('aggregate')) badges.push({ emoji: '💾', label: 'WRITE_DB', cls: 'wf-store-badge-cap-writedb' });
|
|
14746
|
+
if (tools.includes('generate')) badges.push({ emoji: '🤖', label: 'LLM', cls: 'wf-store-badge-cap-llm' });
|
|
14747
|
+
if (tools.includes('loop') || tools.includes('forEach')) badges.push({ emoji: '🔄', label: 'LOOP', cls: 'wf-store-badge-cap-loop' });
|
|
14748
|
+
if (tools.some(t => ['query','search','collections','aggregate'].includes(t))) badges.push({ emoji: '📊', label: 'READ_DB', cls: 'wf-store-badge-cap-readdb' });
|
|
14749
|
+
return badges;
|
|
14750
|
+
}
|
|
14751
|
+
|
|
14752
|
+
let _wfStoreUserRatings = {};
|
|
14753
|
+
|
|
14754
|
+
let _wfStoreCatalog = null;
|
|
14755
|
+
let _wfStoreCat = 'all';
|
|
14756
|
+
|
|
14757
|
+
function wfStoreOpen() {
|
|
14758
|
+
const overlay = document.getElementById('wfStoreOverlay');
|
|
14759
|
+
if (!overlay) return;
|
|
14760
|
+
overlay.classList.add('open');
|
|
14761
|
+
document.getElementById('wfStoreBtn').classList.add('active');
|
|
14762
|
+
if (!_wfStoreCatalog) wfStoreFetchCatalog();
|
|
14763
|
+
}
|
|
14764
|
+
|
|
14765
|
+
function wfStoreClose() {
|
|
14766
|
+
const overlay = document.getElementById('wfStoreOverlay');
|
|
14767
|
+
if (overlay) overlay.classList.remove('open');
|
|
14768
|
+
document.getElementById('wfStoreBtn').classList.remove('active');
|
|
14769
|
+
// Close detail modal if open
|
|
14770
|
+
const detail = document.getElementById('wfStoreDetail');
|
|
14771
|
+
if (detail) detail.remove();
|
|
14772
|
+
}
|
|
14773
|
+
|
|
14774
|
+
// Store icons map (populated from API response or used from inline fallback)
|
|
14775
|
+
let _wfStoreIcons = {};
|
|
14776
|
+
|
|
14777
|
+
// Render a branding icon as inline SVG
|
|
14778
|
+
function wfBrandingIcon(wf, size = 16) {
|
|
14779
|
+
const b = wf.branding || {};
|
|
14780
|
+
const iconPath = b.iconPath || _wfStoreIcons[b.icon] || '';
|
|
14781
|
+
if (!iconPath) return '';
|
|
14782
|
+
return `<svg width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="${iconPath}"/></svg>`;
|
|
14783
|
+
}
|
|
14784
|
+
|
|
14785
|
+
async function wfStoreFetchCatalog() {
|
|
14786
|
+
try {
|
|
14787
|
+
const res = await fetch('/api/workflows/catalog');
|
|
14788
|
+
const data = await res.json();
|
|
14789
|
+
if (data.icons) _wfStoreIcons = data.icons;
|
|
14790
|
+
if (data.workflows && data.workflows.length > 0) {
|
|
14791
|
+
_wfStoreCatalog = data.workflows;
|
|
14792
|
+
} else {
|
|
14793
|
+
_wfStoreCatalog = WF_STORE_FALLBACK;
|
|
14794
|
+
}
|
|
14795
|
+
} catch {
|
|
14796
|
+
_wfStoreCatalog = WF_STORE_FALLBACK;
|
|
14797
|
+
}
|
|
14798
|
+
wfStoreRender();
|
|
14799
|
+
}
|
|
14800
|
+
|
|
14801
|
+
function wfStoreRender() {
|
|
14802
|
+
const container = document.getElementById('wfStoreContent');
|
|
14803
|
+
if (!container || !_wfStoreCatalog) return;
|
|
14804
|
+
const search = (document.getElementById('wfStoreSearch')?.value || '').toLowerCase();
|
|
14805
|
+
const sort = document.getElementById('wfStoreSort')?.value || 'downloads';
|
|
14806
|
+
|
|
14807
|
+
const filtered = _wfStoreCatalog.filter(wf => {
|
|
14808
|
+
const mc = _wfStoreCat === 'all' || wf.category === _wfStoreCat;
|
|
14809
|
+
const ms = !search || wf.name.includes(search) || (wf.description||'').toLowerCase().includes(search)
|
|
14810
|
+
|| (wf.tags||[]).some(t => t.includes(search)) || (wf.tools||[]).some(t => t.includes(search));
|
|
14811
|
+
return mc && ms;
|
|
14812
|
+
}).sort((a, b) => sort === 'downloads' ? (b.downloads||0) - (a.downloads||0) : sort === 'name' ? a.name.localeCompare(b.name) : (b.steps||0) - (a.steps||0));
|
|
14813
|
+
|
|
14814
|
+
const featured = _wfStoreCatalog.filter(w => w.featured);
|
|
14815
|
+
const showFeat = _wfStoreCat === 'all' && !search;
|
|
14816
|
+
|
|
14817
|
+
let html = '';
|
|
14818
|
+
// Category chips
|
|
14819
|
+
html += '<div class="wf-store-chips">';
|
|
14820
|
+
WF_STORE_CATEGORIES.forEach(c => {
|
|
14821
|
+
html += `<button class="wf-store-chip ${_wfStoreCat===c.id?'active':''}" onclick="_wfStoreCat='${c.id}';wfStoreRender()">${c.icon} ${c.label}</button>`;
|
|
14822
|
+
});
|
|
14823
|
+
html += '</div>';
|
|
14824
|
+
|
|
14825
|
+
// Featured section
|
|
14826
|
+
if (showFeat && featured.length > 0) {
|
|
14827
|
+
html += '<div class="wf-store-section-label">Featured</div>';
|
|
14828
|
+
html += '<div class="wf-store-featured-grid">';
|
|
14829
|
+
featured.forEach(wf => {
|
|
14830
|
+
const featAuthor = wf.author && wf.author.name && wf.author.name !== 'unknown' ? wf.author : null;
|
|
14831
|
+
const featAvatarHtml = featAuthor ? (featAuthor.avatar
|
|
14832
|
+
? `<div class="wf-store-featured-avatar"><img src="${featAuthor.avatar}" onerror="this.parentNode.textContent='${featAuthor.name.charAt(0).toUpperCase()}'"></div>`
|
|
14833
|
+
: `<div class="wf-store-featured-avatar">${featAuthor.name.charAt(0).toUpperCase()}</div>`) : '';
|
|
14834
|
+
const featAuthorHtml = featAuthor ? `<div class="wf-store-featured-author">${featAvatarHtml} by ${featAuthor.name}</div>` : '';
|
|
14835
|
+
const featIconHtml = wfBrandingIcon(wf, 20) ? `<div class="wf-store-featured-icon">${wfBrandingIcon(wf, 20)}</div>` : '';
|
|
14836
|
+
html += `<div class="wf-store-featured-card" style="background:${wf.gradient}" onclick="wfStoreShowDetail('${wf.name}')">
|
|
14837
|
+
${featIconHtml}
|
|
14838
|
+
<div class="wf-store-featured-dl">↓ ${(wf.downloads||0).toLocaleString()}</div>
|
|
14839
|
+
<div class="wf-store-featured-content"><h3>${wf.name}</h3><p>${wf.description||''}</p>${featAuthorHtml}</div>
|
|
14840
|
+
</div>`;
|
|
14841
|
+
});
|
|
14842
|
+
html += '</div>';
|
|
14843
|
+
}
|
|
14844
|
+
|
|
14845
|
+
// Grid label
|
|
14846
|
+
const label = _wfStoreCat === 'all' ? 'All Workflows' : (WF_STORE_CATEGORIES.find(c=>c.id===_wfStoreCat)?.label || _wfStoreCat);
|
|
14847
|
+
html += `<div class="wf-store-section-label">${label} <span class="wf-store-count">${filtered.length}</span></div>`;
|
|
14848
|
+
|
|
14849
|
+
if (filtered.length === 0) {
|
|
14850
|
+
html += '<div class="wf-store-empty"><p>No workflows match your search</p></div>';
|
|
14851
|
+
} else {
|
|
14852
|
+
html += '<div class="wf-store-grid">';
|
|
14853
|
+
filtered.forEach(wf => {
|
|
14854
|
+
const dots = (wf.tools||[]).slice(0,5).map(t =>
|
|
14855
|
+
`<div class="wf-store-card-dot" style="background:${WF_STORE_TOOL_COLORS[t]||'#666'}"></div>`
|
|
14856
|
+
).join('') + ((wf.tools||[]).length > 5 ? `<span style="font-family:var(--mono);font-size:9px;color:var(--text-muted);margin-left:2px">+${wf.tools.length-5}</span>` : '');
|
|
14857
|
+
const badges = (wf.verified ? '<span class="wf-store-badge-verified">✓ VERIFIED</span>' : '') +
|
|
14858
|
+
(wf.installed ? '<span class="wf-store-badge-installed">installed</span>' : '') +
|
|
14859
|
+
`<span class="wf-store-badge-official">${wf.tier==='official' ? '✓ OFFICIAL' : 'COMMUNITY'}</span>`;
|
|
14860
|
+
const capBadges = getCapabilityBadges(wf.tools);
|
|
14861
|
+
const capBadgesHtml = capBadges.length > 0
|
|
14862
|
+
? `<div class="wf-store-cap-badges">${capBadges.map(b => `<span class="wf-store-badge-capability ${b.cls}">${b.emoji} ${b.label}</span>`).join('')}</div>`
|
|
14863
|
+
: '';
|
|
14864
|
+
const cardIconColor = (wf.branding && wf.branding.color) || '#64748B';
|
|
14865
|
+
const cardIconSvg = wfBrandingIcon(wf, 16);
|
|
14866
|
+
const cardIconHtml = cardIconSvg
|
|
14867
|
+
? `<div class="wf-store-card-icon" style="background:${cardIconColor}18;color:${cardIconColor}">${cardIconSvg}</div>`
|
|
14868
|
+
: '';
|
|
14869
|
+
html += `<div class="wf-store-card" onclick="wfStoreShowDetail('${wf.name}')">
|
|
14870
|
+
<div class="wf-store-card-bar" style="background:${wf.gradient}"></div>
|
|
14871
|
+
<div class="wf-store-card-top">
|
|
14872
|
+
<div class="wf-store-card-name-wrap">${cardIconHtml}<span class="wf-store-card-name">${wf.name}</span></div>
|
|
14873
|
+
<div class="wf-store-card-badges">${badges}</div>
|
|
14874
|
+
</div>
|
|
14875
|
+
<div class="wf-store-card-desc">${wf.description||''}</div>
|
|
14876
|
+
${wf.author && wf.author.name && wf.author.name !== 'unknown' ? `<div class="wf-store-card-author">by ${wf.author.name}</div>` : ''}
|
|
14877
|
+
${capBadgesHtml}
|
|
14878
|
+
<div class="wf-store-card-meta">
|
|
14879
|
+
<span class="wf-store-card-cat">${wf.category||'utility'}</span>
|
|
14880
|
+
<div class="wf-store-card-dots">${dots}</div>
|
|
14881
|
+
<span class="wf-store-card-complexity">${wf.steps||0}s·${wf.toolCount||(Array.isArray(wf.tools)?wf.tools.length:0)}t</span>
|
|
14882
|
+
<span class="wf-store-card-downloads">↓ ${(wf.downloads||0).toLocaleString()}</span>
|
|
14883
|
+
</div>
|
|
14884
|
+
</div>`;
|
|
14885
|
+
});
|
|
14886
|
+
html += '</div>';
|
|
14887
|
+
}
|
|
14888
|
+
|
|
14889
|
+
container.innerHTML = html;
|
|
14890
|
+
}
|
|
14891
|
+
|
|
14892
|
+
function wfStoreShowDetail(name) {
|
|
14893
|
+
const wf = (_wfStoreCatalog || []).find(w => w.name === name);
|
|
14894
|
+
if (!wf) return;
|
|
14895
|
+
// Remove existing detail
|
|
14896
|
+
const existing = document.getElementById('wfStoreDetail');
|
|
14897
|
+
if (existing) existing.remove();
|
|
14898
|
+
|
|
14899
|
+
const ic = `vai workflow install ${wf.packageName}`;
|
|
14900
|
+
const rc = `vai workflow run ${wf.packageName}`;
|
|
14901
|
+
|
|
14902
|
+
// Escape for use in HTML attributes (single-quoted onclick handlers)
|
|
14903
|
+
const icEsc = ic.replace(/'/g, "\\'");
|
|
14904
|
+
const rcEsc = rc.replace(/'/g, "\\'");
|
|
14905
|
+
|
|
14906
|
+
const toolsHtml = (wf.tools||[]).map(t => {
|
|
14907
|
+
const c = WF_STORE_TOOL_COLORS[t] || '#666';
|
|
14908
|
+
return `<span class="wf-store-detail-tool" style="color:${c};border-color:${c}44;background:${c}11">${t}</span>`;
|
|
14909
|
+
}).join('');
|
|
14910
|
+
|
|
14911
|
+
const tagsHtml = `<span class="wf-store-detail-tag">${wf.category||'utility'}</span>` +
|
|
14912
|
+
(wf.tags||[]).map(t => `<span class="wf-store-detail-tag">${t}</span>`).join('');
|
|
14913
|
+
|
|
14914
|
+
const installBtn = wf.installed
|
|
14915
|
+
? '<button class="wf-store-detail-btn wf-store-detail-btn-installed">✓ Installed</button>'
|
|
14916
|
+
: `<button class="wf-store-detail-btn wf-store-detail-btn-primary" onclick="wfStoreInstall('${wf.packageName}',this)">Install</button>`;
|
|
14917
|
+
|
|
14918
|
+
const modal = document.createElement('div');
|
|
14919
|
+
modal.id = 'wfStoreDetail';
|
|
14920
|
+
modal.className = 'wf-store-detail-bg';
|
|
14921
|
+
modal.onclick = (e) => { if (e.target === modal) modal.remove(); };
|
|
14922
|
+
modal.innerHTML = `<div class="wf-store-detail-panel" onclick="event.stopPropagation()">
|
|
14923
|
+
<div class="wf-store-detail-scroll">
|
|
14924
|
+
<div class="wf-store-detail-hero" style="background:${wf.gradient}">
|
|
14925
|
+
<button class="wf-store-detail-close" onclick="document.getElementById('wfStoreDetail').remove()">×</button>
|
|
14926
|
+
<div class="wf-store-detail-hero-inner">
|
|
14927
|
+
${wfBrandingIcon(wf, 26) ? `<div class="wf-store-detail-hero-icon">${wfBrandingIcon(wf, 26)}</div>` : ''}
|
|
14928
|
+
<div>
|
|
14929
|
+
<div class="wf-store-detail-name">${wf.name}</div>
|
|
14930
|
+
<div class="wf-store-detail-pkg">${wf.packageName}</div>
|
|
14931
|
+
</div>
|
|
14932
|
+
</div>
|
|
14933
|
+
</div>
|
|
14934
|
+
<div class="wf-store-detail-body">
|
|
14935
|
+
${(() => {
|
|
14936
|
+
const a = wf.author && wf.author.name && wf.author.name !== 'unknown' ? wf.author : null;
|
|
14937
|
+
if (!a) return '';
|
|
14938
|
+
const avatarInner = a.avatar
|
|
14939
|
+
? `<img src="${a.avatar}" onerror="this.parentNode.textContent='${a.name.charAt(0).toUpperCase()}'">`
|
|
14940
|
+
: a.name.charAt(0).toUpperCase();
|
|
14941
|
+
const nameHtml = a.url
|
|
14942
|
+
? `<a href="${a.url}" target="_blank" rel="noopener">${a.name}</a>`
|
|
14943
|
+
: a.name;
|
|
14944
|
+
return `<div class="wf-store-detail-author"><div class="wf-store-detail-avatar">${avatarInner}</div><div class="wf-store-detail-author-name">${nameHtml}</div></div>`;
|
|
14945
|
+
})()}
|
|
14946
|
+
<p class="wf-store-detail-desc">${wf.description||''}</p>
|
|
14947
|
+
${wf.assets && wf.assets.screenshots && wf.assets.screenshots.length > 0 ? `
|
|
14948
|
+
<div class="wf-store-detail-section">
|
|
14949
|
+
<div class="wf-store-detail-label">Screenshots</div>
|
|
14950
|
+
<div class="wf-store-detail-screenshots">${wf.assets.screenshots.map(s => `<img src="${s}" alt="Screenshot">`).join('')}</div>
|
|
14951
|
+
</div>` : ''}
|
|
14952
|
+
<div class="wf-store-detail-section">
|
|
14953
|
+
<div class="wf-store-detail-label">Stats</div>
|
|
14954
|
+
<div class="wf-store-detail-stats">
|
|
14955
|
+
<div class="wf-store-detail-stat"><div class="wf-store-detail-stat-val">${wf.steps||0}</div><div class="wf-store-detail-stat-lbl">Steps</div></div>
|
|
14956
|
+
<div class="wf-store-detail-stat"><div class="wf-store-detail-stat-val">${wf.toolCount||(Array.isArray(wf.tools)?wf.tools.length:0)}</div><div class="wf-store-detail-stat-lbl">Tools</div></div>
|
|
14957
|
+
<div class="wf-store-detail-stat"><div class="wf-store-detail-stat-val">${(wf.downloads||0).toLocaleString()}</div><div class="wf-store-detail-stat-lbl">Downloads</div></div>
|
|
14958
|
+
</div>
|
|
14959
|
+
</div>
|
|
14960
|
+
<div class="wf-store-detail-section">
|
|
14961
|
+
<div class="wf-store-detail-label">Tools</div>
|
|
14962
|
+
<div class="wf-store-detail-tools">${toolsHtml}</div>
|
|
14963
|
+
</div>
|
|
14964
|
+
<div class="wf-store-detail-section">
|
|
14965
|
+
<div class="wf-store-detail-label">Tags</div>
|
|
14966
|
+
<div class="wf-store-detail-tags">${tagsHtml}</div>
|
|
14967
|
+
</div>
|
|
14968
|
+
${wf.inputs && wf.inputs.length > 0 ? `
|
|
14969
|
+
<div class="wf-store-detail-section">
|
|
14970
|
+
<div class="wf-store-detail-label">Input Parameters</div>
|
|
14971
|
+
<table class="wf-store-detail-inputs-table">
|
|
14972
|
+
<thead><tr><th>Name</th><th>Type</th><th>Required / Default</th></tr></thead>
|
|
14973
|
+
<tbody>${wf.inputs.map(inp => `<tr><td>${inp.name}</td><td>${inp.type}</td><td>${inp.required ? 'required' : inp.default !== undefined ? 'default: ' + inp.default : 'optional'}</td></tr>`).join('')}</tbody>
|
|
14974
|
+
</table>
|
|
14975
|
+
</div>` : ''}
|
|
14976
|
+
<div class="wf-store-detail-install">
|
|
14977
|
+
<div class="wf-store-detail-label">Install</div>
|
|
14978
|
+
<div class="wf-store-detail-cmd" onclick="navigator.clipboard.writeText('${icEsc}');this.querySelector('.wf-store-detail-cmd-hint').textContent='✓ copied';setTimeout(()=>this.querySelector('.wf-store-detail-cmd-hint').textContent='copy',2000)">
|
|
14979
|
+
<span>$ ${ic}</span><span class="wf-store-detail-cmd-hint">copy</span>
|
|
14980
|
+
</div>
|
|
14981
|
+
<div class="wf-store-detail-label" style="margin-top:12px">Run</div>
|
|
14982
|
+
<div class="wf-store-detail-cmd" style="color:var(--text-dim)" onclick="navigator.clipboard.writeText('${rcEsc}');this.querySelector('.wf-store-detail-cmd-hint').textContent='✓ copied';setTimeout(()=>this.querySelector('.wf-store-detail-cmd-hint').textContent='copy',2000)">
|
|
14983
|
+
<span>$ ${rc}</span><span class="wf-store-detail-cmd-hint">copy</span>
|
|
14984
|
+
</div>
|
|
14985
|
+
</div>
|
|
14986
|
+
${(() => {
|
|
14987
|
+
const capBadges = getCapabilityBadges(wf.tools);
|
|
14988
|
+
if (capBadges.length === 0) return '';
|
|
14989
|
+
return `<div class="wf-store-detail-section">
|
|
14990
|
+
<div class="wf-store-detail-label">Capabilities</div>
|
|
14991
|
+
<div style="display:flex;gap:5px;flex-wrap:wrap">${capBadges.map(b => `<span class="wf-store-badge-capability ${b.cls}">${b.emoji} ${b.label}</span>`).join('')}</div>
|
|
14992
|
+
</div>`;
|
|
14993
|
+
})()}
|
|
14994
|
+
${(() => {
|
|
14995
|
+
const sec = wf.security || [];
|
|
14996
|
+
if (sec.length === 0) return `<div class="wf-store-detail-section"><div class="wf-store-detail-label">Security</div><div class="wf-store-detail-security-ok">✓ No security issues found</div></div>`;
|
|
14997
|
+
return `<div class="wf-store-detail-section"><div class="wf-store-detail-label">Security</div>${sec.map(f => `<div class="wf-store-detail-security-item"><span class="wf-store-detail-security-sev ${f.severity||'low'}">${f.severity||'low'}</span><span>${f.message||f.rule||'Issue found'}</span></div>`).join('')}</div>`;
|
|
14998
|
+
})()}
|
|
14999
|
+
${wf.verified ? `<div class="wf-store-detail-section"><div class="wf-store-detail-label">Verified</div><div class="wf-store-detail-security-ok">✓ This workflow has passed all validation checks (L1–L4): schema, security audit, quality audit, and capability analysis.</div></div>` : ''}
|
|
15000
|
+
<div class="wf-store-detail-section">
|
|
15001
|
+
<div class="wf-store-detail-label">Rating</div>
|
|
15002
|
+
<div style="display:flex;align-items:center;gap:12px">
|
|
15003
|
+
<span style="font-size:12px;color:var(--text-dim)">${wf.rating != null ? wf.rating.toFixed(1) + ' / 5' : 'Not yet rated'}</span>
|
|
15004
|
+
</div>
|
|
15005
|
+
<div style="margin-top:8px;font-size:11px;color:var(--text-muted)">Rate this workflow:</div>
|
|
15006
|
+
<div class="wf-store-detail-stars" id="wfStoreRateStars">
|
|
15007
|
+
${[1,2,3,4,5].map(i => `<span onclick="_wfStoreUserRatings['${wf.name}']=${i};document.querySelectorAll('#wfStoreRateStars span').forEach((s,j)=>s.className=j<${i}?'filled':'')" class="${(_wfStoreUserRatings[wf.name]||0)>=i?'filled':''}">★</span>`).join('')}
|
|
15008
|
+
</div>
|
|
15009
|
+
</div>
|
|
15010
|
+
<div class="wf-store-detail-section">
|
|
15011
|
+
<button class="wf-store-detail-btn wf-store-detail-btn-secondary" style="flex:none;width:auto;padding:6px 14px;font-size:11px" onclick="document.getElementById('wfStoreReportForm').style.display=document.getElementById('wfStoreReportForm').style.display==='none'?'block':'none'">⚑ Report</button>
|
|
15012
|
+
<div id="wfStoreReportForm" class="wf-store-report-form" style="display:none">
|
|
15013
|
+
<select id="wfStoreReportReason"><option value="">Select reason...</option><option value="malicious">Malicious</option><option value="broken">Broken</option><option value="low quality">Low quality</option><option value="spam">Spam</option></select>
|
|
15014
|
+
<textarea id="wfStoreReportComment" placeholder="Optional comment..."></textarea>
|
|
15015
|
+
<button class="wf-store-report-submit" onclick="document.getElementById('wfStoreReportForm').style.display='none';const t=document.createElement('div');t.style.cssText='position:fixed;bottom:20px;right:20px;background:var(--bg-surface);border:1px solid var(--border);border-radius:8px;padding:12px 20px;color:var(--text);font-size:13px;z-index:9999;animation:wfStoreDetailFadeIn .2s';t.textContent='✓ Report submitted';document.body.appendChild(t);setTimeout(()=>t.remove(),3000)">Submit Report</button>
|
|
15016
|
+
</div>
|
|
15017
|
+
</div>
|
|
15018
|
+
<div class="wf-store-detail-actions">
|
|
15019
|
+
${installBtn}
|
|
15020
|
+
<button class="wf-store-detail-btn wf-store-detail-btn-secondary" onclick="wfStoreRun('${wf.name}')">Run</button>
|
|
15021
|
+
<button class="wf-store-detail-btn wf-store-detail-btn-secondary" onclick="wfStoreCanvas('${wf.name}')">Canvas</button>
|
|
15022
|
+
</div>
|
|
15023
|
+
</div>
|
|
15024
|
+
</div>
|
|
15025
|
+
</div>`;
|
|
15026
|
+
document.body.appendChild(modal);
|
|
15027
|
+
}
|
|
15028
|
+
|
|
15029
|
+
async function wfStoreInstall(packageName, btn) {
|
|
15030
|
+
if (btn) { btn.textContent = 'Installing...'; btn.disabled = true; }
|
|
15031
|
+
try {
|
|
15032
|
+
const res = await fetch('/api/workflows/community/install', {
|
|
15033
|
+
method: 'POST',
|
|
15034
|
+
headers: { 'Content-Type': 'application/json' },
|
|
15035
|
+
body: JSON.stringify({ name: packageName })
|
|
15036
|
+
});
|
|
15037
|
+
const text = await res.text();
|
|
15038
|
+
let data;
|
|
15039
|
+
try { data = JSON.parse(text); } catch { data = { error: text || 'Empty response (status ' + res.status + ')' }; }
|
|
15040
|
+
console.log('[Store Install]', packageName, data);
|
|
15041
|
+
if (data.success) {
|
|
15042
|
+
if (btn) { btn.textContent = '✓ Installed'; btn.className = 'wf-store-detail-btn wf-store-detail-btn-installed'; btn.disabled = true; }
|
|
15043
|
+
// Update catalog state
|
|
15044
|
+
const wf = (_wfStoreCatalog||[]).find(w => w.packageName === packageName);
|
|
15045
|
+
if (wf) wf.installed = true;
|
|
15046
|
+
wfStoreRender();
|
|
15047
|
+
await wfLoadLibrary(); // Refresh library panel
|
|
15048
|
+
} else {
|
|
15049
|
+
const errMsg = data.error || 'Unknown error';
|
|
15050
|
+
console.error('[Store Install Error]', errMsg);
|
|
15051
|
+
if (btn) { btn.textContent = 'Failed'; btn.title = errMsg; setTimeout(() => { btn.textContent = 'Install'; btn.disabled = false; btn.title = ''; }, 4000); }
|
|
15052
|
+
}
|
|
15053
|
+
} catch (err) {
|
|
15054
|
+
console.error('[Store Install Exception]', err);
|
|
15055
|
+
if (btn) { btn.textContent = 'Failed'; btn.title = err.message; setTimeout(() => { btn.textContent = 'Install'; btn.disabled = false; btn.title = ''; }, 4000); }
|
|
15056
|
+
}
|
|
15057
|
+
}
|
|
15058
|
+
|
|
15059
|
+
async function wfStoreRun(name) {
|
|
15060
|
+
const detail = document.getElementById('wfStoreDetail');
|
|
15061
|
+
if (detail) detail.remove();
|
|
15062
|
+
wfStoreClose();
|
|
15063
|
+
|
|
15064
|
+
// Find the workflow in catalog
|
|
15065
|
+
const wf = (_wfStoreCatalog||[]).find(w => w.name === name);
|
|
15066
|
+
if (!wf) {
|
|
15067
|
+
alert('Workflow not found: ' + name);
|
|
15068
|
+
return;
|
|
15069
|
+
}
|
|
15070
|
+
|
|
15071
|
+
// Check if installed
|
|
15072
|
+
if (!wf.installed) {
|
|
15073
|
+
const doInstall = confirm(`"${wf.name}" is not installed. Install it now?`);
|
|
15074
|
+
if (doInstall) {
|
|
15075
|
+
await wfStoreInstall(wf.packageName, null);
|
|
15076
|
+
// Refresh catalog to update installed status
|
|
15077
|
+
await wfStoreFetchCatalog();
|
|
15078
|
+
// Check again
|
|
15079
|
+
const updated = (_wfStoreCatalog||[]).find(w => w.name === name);
|
|
15080
|
+
if (!updated || !updated.installed) {
|
|
15081
|
+
alert('Installation may have failed. Please try again.');
|
|
15082
|
+
return;
|
|
15083
|
+
}
|
|
15084
|
+
} else {
|
|
15085
|
+
return;
|
|
15086
|
+
}
|
|
15087
|
+
}
|
|
15088
|
+
|
|
15089
|
+
// Switch to workflows tab and load the workflow
|
|
15090
|
+
switchTab('workflows');
|
|
15091
|
+
// Use the full package name for resolution
|
|
15092
|
+
wfSelectWorkflow(wf.packageName);
|
|
15093
|
+
}
|
|
15094
|
+
|
|
15095
|
+
function wfStoreCanvas(name) {
|
|
15096
|
+
wfStoreRun(name); // Same behavior — load the workflow into canvas
|
|
15097
|
+
}
|
|
15098
|
+
|
|
15099
|
+
// Escape key handler for store
|
|
15100
|
+
document.addEventListener('keydown', (e) => {
|
|
15101
|
+
if (e.key === 'Escape') {
|
|
15102
|
+
const detail = document.getElementById('wfStoreDetail');
|
|
15103
|
+
if (detail) { detail.remove(); return; }
|
|
15104
|
+
const overlay = document.getElementById('wfStoreOverlay');
|
|
15105
|
+
if (overlay && overlay.classList.contains('open')) { wfStoreClose(); }
|
|
15106
|
+
}
|
|
15107
|
+
});
|
|
15108
|
+
|
|
11938
15109
|
function wfInit() {
|
|
15110
|
+
wfRestoreLayoutState();
|
|
11939
15111
|
wfLoadLibrary();
|
|
11940
15112
|
wfInitPan();
|
|
11941
15113
|
}
|
|
@@ -12067,5 +15239,241 @@ let wfInitialized = false;
|
|
|
12067
15239
|
})();
|
|
12068
15240
|
</script>
|
|
12069
15241
|
|
|
15242
|
+
<!-- Cost Dashboard -->
|
|
15243
|
+
<div id="costDetailPanel">
|
|
15244
|
+
<div class="cost-panel-inner">
|
|
15245
|
+
<div class="cost-panel-header">
|
|
15246
|
+
<h3>💰 Cost Dashboard</h3>
|
|
15247
|
+
<button id="costResetBtn" onclick="CostTracker.reset()">Reset Session</button>
|
|
15248
|
+
</div>
|
|
15249
|
+
<table id="costOpsTable">
|
|
15250
|
+
<thead><tr><th>Time</th><th>Operation</th><th>Model</th><th>Tokens</th><th>Cost</th><th>If v4-large</th><th>If OpenAI</th></tr></thead>
|
|
15251
|
+
<tbody></tbody>
|
|
15252
|
+
</table>
|
|
15253
|
+
<div class="cost-projection" id="costProjection"></div>
|
|
15254
|
+
</div>
|
|
15255
|
+
</div>
|
|
15256
|
+
<div id="costStatusBar">
|
|
15257
|
+
<span>💰 Session: <span class="cost-val" id="costTotal">$0.000000</span></span>
|
|
15258
|
+
<span class="cost-sep">│</span>
|
|
15259
|
+
<span><span id="costTokens">0</span> tokens</span>
|
|
15260
|
+
<span class="cost-sep">│</span>
|
|
15261
|
+
<span><span id="costOps">0</span> ops</span>
|
|
15262
|
+
<span class="cost-sep">│</span>
|
|
15263
|
+
<span>LLM: <span class="cost-val" id="costLLM">$0.000000</span> (<span id="costLLMIn">0</span>in/<span id="costLLMOut">0</span>out)</span>
|
|
15264
|
+
<span class="cost-sep">│</span>
|
|
15265
|
+
<span>If symmetric: <span id="costSymmetric">$0.000000</span> <span id="costSymDelta"></span></span>
|
|
15266
|
+
<span class="cost-sep">│</span>
|
|
15267
|
+
<span>If OpenAI: <span id="costCompetitor">$0.000000</span> <span id="costCompDelta"></span></span>
|
|
15268
|
+
<button id="costDetailsToggle" onclick="CostTracker.togglePanel()">Details ▾</button>
|
|
15269
|
+
</div>
|
|
15270
|
+
|
|
15271
|
+
<script>
|
|
15272
|
+
(function() {
|
|
15273
|
+
const PRICING = {
|
|
15274
|
+
'voyage-4-large': 0.12, 'voyage-4': 0.06, 'voyage-4-lite': 0.02,
|
|
15275
|
+
'voyage-code-3': 0.18, 'voyage-finance-2': 0.12, 'voyage-law-2': 0.12,
|
|
15276
|
+
'rerank-2.5': 0.05, 'rerank-2.5-lite': 0.02,
|
|
15277
|
+
'voyage-multimodal-3': 0.12,
|
|
15278
|
+
};
|
|
15279
|
+
const LARGE_PRICE = 0.12;
|
|
15280
|
+
const OPENAI_PRICE = 0.13;
|
|
15281
|
+
|
|
15282
|
+
// LLM pricing: per-million tokens { input, output }
|
|
15283
|
+
const LLM_PRICING = {
|
|
15284
|
+
'claude-sonnet-4-5-20250929': { input: 3.00, output: 15.00 },
|
|
15285
|
+
'claude-opus-4-20250514': { input: 15.00, output: 75.00 },
|
|
15286
|
+
'claude-3-5-haiku-20241022': { input: 0.80, output: 4.00 },
|
|
15287
|
+
'claude-sonnet-4': { input: 3.00, output: 15.00 },
|
|
15288
|
+
'claude-opus-4': { input: 15.00, output: 75.00 },
|
|
15289
|
+
'gpt-4o': { input: 2.50, output: 10.00 },
|
|
15290
|
+
'gpt-4o-mini': { input: 0.15, output: 0.60 },
|
|
15291
|
+
'gpt-4-turbo': { input: 10.00, output: 30.00 },
|
|
15292
|
+
'o1': { input: 15.00, output: 60.00 },
|
|
15293
|
+
'o1-mini': { input: 3.00, output: 12.00 },
|
|
15294
|
+
'o3-mini': { input: 1.10, output: 4.40 },
|
|
15295
|
+
};
|
|
15296
|
+
|
|
15297
|
+
function getLLMPrice(model) {
|
|
15298
|
+
if (!model) return { input: 0, output: 0 };
|
|
15299
|
+
if (LLM_PRICING[model]) return LLM_PRICING[model];
|
|
15300
|
+
// Fuzzy match
|
|
15301
|
+
for (const k of Object.keys(LLM_PRICING)) {
|
|
15302
|
+
if (model.includes(k) || k.includes(model)) return LLM_PRICING[k];
|
|
15303
|
+
}
|
|
15304
|
+
// Match by prefix (e.g. "claude-sonnet-4-5" matches the full versioned key)
|
|
15305
|
+
for (const k of Object.keys(LLM_PRICING)) {
|
|
15306
|
+
const base = k.replace(/-\d{8}$/, '');
|
|
15307
|
+
if (model.includes(base) || base.includes(model)) return LLM_PRICING[k];
|
|
15308
|
+
}
|
|
15309
|
+
return { input: 0, output: 0 }; // Unknown model: free (don't inflate costs)
|
|
15310
|
+
}
|
|
15311
|
+
|
|
15312
|
+
function llmCostForTokens(model, inputTokens, outputTokens) {
|
|
15313
|
+
const prices = getLLMPrice(model);
|
|
15314
|
+
return (prices.input / 1_000_000) * inputTokens + (prices.output / 1_000_000) * outputTokens;
|
|
15315
|
+
}
|
|
15316
|
+
|
|
15317
|
+
function getPrice(model) {
|
|
15318
|
+
if (PRICING[model] != null) return PRICING[model];
|
|
15319
|
+
// fuzzy match
|
|
15320
|
+
for (const k of Object.keys(PRICING)) {
|
|
15321
|
+
if (model.includes(k) || k.includes(model)) return PRICING[k];
|
|
15322
|
+
}
|
|
15323
|
+
return 0.06; // default fallback
|
|
15324
|
+
}
|
|
15325
|
+
|
|
15326
|
+
function costForTokens(pricePerMillion, tokens) {
|
|
15327
|
+
return (pricePerMillion / 1_000_000) * tokens;
|
|
15328
|
+
}
|
|
15329
|
+
|
|
15330
|
+
const session = {
|
|
15331
|
+
operations: [], totalCost: 0, totalTokens: 0, totalOperations: 0,
|
|
15332
|
+
symmetricCost: 0, competitorCost: 0,
|
|
15333
|
+
llmCost: 0, llmInputTokens: 0, llmOutputTokens: 0,
|
|
15334
|
+
};
|
|
15335
|
+
|
|
15336
|
+
window.CostTracker = {
|
|
15337
|
+
addOperation(op, model, tokens) {
|
|
15338
|
+
if (!tokens || tokens <= 0) return;
|
|
15339
|
+
const price = getPrice(model);
|
|
15340
|
+
const cost = costForTokens(price, tokens);
|
|
15341
|
+
const largeCost = costForTokens(LARGE_PRICE, tokens);
|
|
15342
|
+
const competitorCost = costForTokens(OPENAI_PRICE, tokens);
|
|
15343
|
+
|
|
15344
|
+
session.operations.push({
|
|
15345
|
+
timestamp: new Date(), operation: op, model, tokens, cost,
|
|
15346
|
+
hypotheticalLargeCost: largeCost, hypotheticalCompetitorCost: competitorCost,
|
|
15347
|
+
});
|
|
15348
|
+
session.totalCost += cost;
|
|
15349
|
+
session.totalTokens += tokens;
|
|
15350
|
+
session.totalOperations++;
|
|
15351
|
+
session.symmetricCost += largeCost;
|
|
15352
|
+
session.competitorCost += competitorCost;
|
|
15353
|
+
|
|
15354
|
+
this._updateUI();
|
|
15355
|
+
this._showToast(op, model, tokens, cost, largeCost);
|
|
15356
|
+
},
|
|
15357
|
+
|
|
15358
|
+
addLLMOperation(op, model, inputTokens, outputTokens) {
|
|
15359
|
+
if ((!inputTokens || inputTokens <= 0) && (!outputTokens || outputTokens <= 0)) return;
|
|
15360
|
+
const cost = llmCostForTokens(model, inputTokens || 0, outputTokens || 0);
|
|
15361
|
+
const totalTokens = (inputTokens || 0) + (outputTokens || 0);
|
|
15362
|
+
|
|
15363
|
+
session.operations.push({
|
|
15364
|
+
timestamp: new Date(), operation: op, model: model || 'unknown',
|
|
15365
|
+
tokens: totalTokens, cost, isLLM: true,
|
|
15366
|
+
inputTokens: inputTokens || 0, outputTokens: outputTokens || 0,
|
|
15367
|
+
hypotheticalLargeCost: 0, hypotheticalCompetitorCost: 0,
|
|
15368
|
+
});
|
|
15369
|
+
session.totalCost += cost;
|
|
15370
|
+
session.totalTokens += totalTokens;
|
|
15371
|
+
session.totalOperations++;
|
|
15372
|
+
session.llmCost += cost;
|
|
15373
|
+
session.llmInputTokens += (inputTokens || 0);
|
|
15374
|
+
session.llmOutputTokens += (outputTokens || 0);
|
|
15375
|
+
|
|
15376
|
+
this._updateUI();
|
|
15377
|
+
this._showLLMToast(op, model, inputTokens || 0, outputTokens || 0, cost);
|
|
15378
|
+
},
|
|
15379
|
+
|
|
15380
|
+
reset() {
|
|
15381
|
+
session.operations.length = 0;
|
|
15382
|
+
session.totalCost = 0; session.totalTokens = 0; session.totalOperations = 0;
|
|
15383
|
+
session.symmetricCost = 0; session.competitorCost = 0;
|
|
15384
|
+
session.llmCost = 0; session.llmInputTokens = 0; session.llmOutputTokens = 0;
|
|
15385
|
+
this._updateUI();
|
|
15386
|
+
},
|
|
15387
|
+
|
|
15388
|
+
getProjectedMonthlyCost(queriesPerMonth) {
|
|
15389
|
+
if (session.totalOperations === 0) return { actual: 0, symmetric: 0, competitor: 0 };
|
|
15390
|
+
const avgCost = session.totalCost / session.totalOperations;
|
|
15391
|
+
const avgSym = session.symmetricCost / session.totalOperations;
|
|
15392
|
+
const avgComp = session.competitorCost / session.totalOperations;
|
|
15393
|
+
return { actual: avgCost * queriesPerMonth, symmetric: avgSym * queriesPerMonth, competitor: avgComp * queriesPerMonth };
|
|
15394
|
+
},
|
|
15395
|
+
|
|
15396
|
+
togglePanel() {
|
|
15397
|
+
const panel = document.getElementById('costDetailPanel');
|
|
15398
|
+
const btn = document.getElementById('costDetailsToggle');
|
|
15399
|
+
const isOpen = panel.classList.toggle('open');
|
|
15400
|
+
btn.textContent = isOpen ? 'Details ▴' : 'Details ▾';
|
|
15401
|
+
if (isOpen && typeof sendTelemetry === 'function') sendTelemetry('cost_panel_viewed');
|
|
15402
|
+
},
|
|
15403
|
+
|
|
15404
|
+
_updateUI() {
|
|
15405
|
+
document.getElementById('costTotal').textContent = '$' + session.totalCost.toFixed(6);
|
|
15406
|
+
document.getElementById('costTokens').textContent = session.totalTokens.toLocaleString();
|
|
15407
|
+
document.getElementById('costOps').textContent = session.totalOperations;
|
|
15408
|
+
document.getElementById('costLLM').textContent = '$' + session.llmCost.toFixed(6);
|
|
15409
|
+
document.getElementById('costLLMIn').textContent = session.llmInputTokens.toLocaleString();
|
|
15410
|
+
document.getElementById('costLLMOut').textContent = session.llmOutputTokens.toLocaleString();
|
|
15411
|
+
document.getElementById('costSymmetric').textContent = '$' + session.symmetricCost.toFixed(6);
|
|
15412
|
+
document.getElementById('costCompetitor').textContent = '$' + session.competitorCost.toFixed(6);
|
|
15413
|
+
|
|
15414
|
+
// Deltas
|
|
15415
|
+
const symDelta = document.getElementById('costSymDelta');
|
|
15416
|
+
const compDelta = document.getElementById('costCompDelta');
|
|
15417
|
+
if (session.totalCost > 0) {
|
|
15418
|
+
const symPct = ((session.symmetricCost - session.totalCost) / session.totalCost * 100).toFixed(0);
|
|
15419
|
+
const compPct = ((session.competitorCost - session.totalCost) / session.totalCost * 100).toFixed(0);
|
|
15420
|
+
symDelta.textContent = symPct > 0 ? `(+${symPct}%)` : `(${symPct}%)`;
|
|
15421
|
+
symDelta.className = symPct > 0 ? 'cost-more' : 'cost-savings';
|
|
15422
|
+
compDelta.textContent = compPct > 0 ? `(+${compPct}%)` : `(${compPct}%)`;
|
|
15423
|
+
compDelta.className = compPct > 0 ? 'cost-more' : 'cost-savings';
|
|
15424
|
+
} else {
|
|
15425
|
+
symDelta.textContent = ''; compDelta.textContent = '';
|
|
15426
|
+
}
|
|
15427
|
+
|
|
15428
|
+
// Table
|
|
15429
|
+
const tbody = document.querySelector('#costOpsTable tbody');
|
|
15430
|
+
tbody.innerHTML = session.operations.map(o => {
|
|
15431
|
+
const tokensDisplay = o.isLLM
|
|
15432
|
+
? `${o.inputTokens.toLocaleString()}in / ${o.outputTokens.toLocaleString()}out`
|
|
15433
|
+
: o.tokens.toLocaleString();
|
|
15434
|
+
const largeCostDisplay = o.isLLM ? 'n/a' : `$${o.hypotheticalLargeCost.toFixed(6)}`;
|
|
15435
|
+
const compCostDisplay = o.isLLM ? 'n/a' : `$${o.hypotheticalCompetitorCost.toFixed(6)}`;
|
|
15436
|
+
return `<tr${o.isLLM ? ' style="background:rgba(180,90,242,0.08)"' : ''}>
|
|
15437
|
+
<td>${o.timestamp.toLocaleTimeString()}</td>
|
|
15438
|
+
<td>${o.operation}</td>
|
|
15439
|
+
<td>${o.model}</td>
|
|
15440
|
+
<td>${tokensDisplay}</td>
|
|
15441
|
+
<td>$${o.cost.toFixed(6)}</td>
|
|
15442
|
+
<td>${largeCostDisplay}</td>
|
|
15443
|
+
<td>${compCostDisplay}</td>
|
|
15444
|
+
</tr>`;
|
|
15445
|
+
}).join('');
|
|
15446
|
+
|
|
15447
|
+
// Projection
|
|
15448
|
+
const proj = this.getProjectedMonthlyCost(1_000_000);
|
|
15449
|
+
const projEl = document.getElementById('costProjection');
|
|
15450
|
+
if (session.totalOperations > 0) {
|
|
15451
|
+
projEl.innerHTML = `📊 Projected at 1M queries/month: <span>$${proj.actual.toFixed(2)}</span> actual │ $${proj.symmetric.toFixed(2)} symmetric │ $${proj.competitor.toFixed(2)} OpenAI`;
|
|
15452
|
+
} else {
|
|
15453
|
+
projEl.innerHTML = '';
|
|
15454
|
+
}
|
|
15455
|
+
},
|
|
15456
|
+
|
|
15457
|
+
_showToast(op, model, tokens, cost, largeCost) {
|
|
15458
|
+
const saved = largeCost > 0 ? Math.round((1 - cost / largeCost) * 100) : 0;
|
|
15459
|
+
const savingsText = saved > 0 ? ` — saved ${saved}% vs v4-large` : '';
|
|
15460
|
+
const toast = document.createElement('div');
|
|
15461
|
+
toast.className = 'cost-toast';
|
|
15462
|
+
toast.textContent = `✅ ${op} — $${cost.toFixed(6)} (${model}, ${tokens.toLocaleString()} tokens)${savingsText}`;
|
|
15463
|
+
document.body.appendChild(toast);
|
|
15464
|
+
setTimeout(() => toast.remove(), 3100);
|
|
15465
|
+
},
|
|
15466
|
+
|
|
15467
|
+
_showLLMToast(op, model, inputTokens, outputTokens, cost) {
|
|
15468
|
+
const toast = document.createElement('div');
|
|
15469
|
+
toast.className = 'cost-toast';
|
|
15470
|
+
toast.textContent = `🤖 ${op} — $${cost.toFixed(6)} (${model || 'LLM'}, ${inputTokens.toLocaleString()}in/${outputTokens.toLocaleString()}out)`;
|
|
15471
|
+
document.body.appendChild(toast);
|
|
15472
|
+
setTimeout(() => toast.remove(), 3100);
|
|
15473
|
+
}
|
|
15474
|
+
};
|
|
15475
|
+
})();
|
|
15476
|
+
</script>
|
|
15477
|
+
|
|
12070
15478
|
</body>
|
|
12071
15479
|
</html>
|