voyageai-cli 1.29.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 +1 -1
- 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 +52 -6
- 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 +286 -0
- 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 +6 -0
- package/src/lib/llm.js +125 -18
- 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-scaffold.js +61 -0
- package/src/lib/workflow-test-runner.js +208 -0
- package/src/lib/workflow.js +128 -2
- package/src/playground/announcements.md +9 -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/index.html +1482 -184
|
@@ -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;
|
|
@@ -2568,7 +2642,10 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
2568
2642
|
.chat-thinking .thinking-icon {
|
|
2569
2643
|
font-size: 14px;
|
|
2570
2644
|
line-height: 1;
|
|
2645
|
+
display: flex;
|
|
2646
|
+
align-items: center;
|
|
2571
2647
|
}
|
|
2648
|
+
.chat-thinking .thinking-icon svg { color: var(--accent); }
|
|
2572
2649
|
.chat-thinking .thinking-label {
|
|
2573
2650
|
font-weight: 500;
|
|
2574
2651
|
}
|
|
@@ -2626,6 +2703,7 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
2626
2703
|
background: var(--bg-input, #112733);
|
|
2627
2704
|
border: 1px solid var(--border);
|
|
2628
2705
|
}
|
|
2706
|
+
.thinking-step-icon svg { color: var(--text-dim); }
|
|
2629
2707
|
.thinking-step.active .thinking-step-icon {
|
|
2630
2708
|
border-color: var(--accent);
|
|
2631
2709
|
animation: thinkingPulse 1.2s ease-in-out infinite;
|
|
@@ -3020,10 +3098,15 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
3020
3098
|
min-height: 0;
|
|
3021
3099
|
}
|
|
3022
3100
|
.wf-library {
|
|
3023
|
-
width: 220px; min-width:
|
|
3101
|
+
width: 220px; min-width: 0; flex-shrink: 0;
|
|
3024
3102
|
border-right: 1px solid var(--border);
|
|
3025
3103
|
display: flex; flex-direction: column;
|
|
3026
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;
|
|
3027
3110
|
}
|
|
3028
3111
|
.wf-library-header {
|
|
3029
3112
|
padding: 14px 16px 10px; font-weight: 700; font-size: 13px;
|
|
@@ -3086,6 +3169,88 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
3086
3169
|
font-size: 10px; font-weight: 600; color: var(--text-muted);
|
|
3087
3170
|
padding: 8px 12px 2px; text-transform: uppercase; letter-spacing: 0.5px;
|
|
3088
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
|
+
}
|
|
3089
3254
|
.wf-canvas-area {
|
|
3090
3255
|
flex: 1; position: relative; overflow: hidden;
|
|
3091
3256
|
background: var(--bg);
|
|
@@ -3093,8 +3258,8 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
3093
3258
|
background-size: 20px 20px;
|
|
3094
3259
|
}
|
|
3095
3260
|
.wf-canvas-toolbar {
|
|
3096
|
-
position: absolute; top: 12px; right: 12px; z-index: 10;
|
|
3097
|
-
display: flex; gap: 4px;
|
|
3261
|
+
position: absolute; top: 12px; left: 12px; right: 12px; z-index: 10;
|
|
3262
|
+
display: flex; gap: 4px; align-items: center;
|
|
3098
3263
|
}
|
|
3099
3264
|
.wf-canvas-toolbar button {
|
|
3100
3265
|
width: 32px; height: 32px; border-radius: 8px;
|
|
@@ -3165,6 +3330,119 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
3165
3330
|
.wf-palette-icon { width: 20px; text-align: center; display: flex; align-items: center; justify-content: center; }
|
|
3166
3331
|
.wf-palette-label { font-weight: 500; }
|
|
3167
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
|
+
|
|
3168
3446
|
/* ── Workflow Store ── */
|
|
3169
3447
|
.wf-store-overlay {
|
|
3170
3448
|
position: absolute; inset: 0; background: var(--bg); z-index: 50;
|
|
@@ -3263,6 +3541,30 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
3263
3541
|
.wf-store-card-badges { display: flex; gap: 4px; }
|
|
3264
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; }
|
|
3265
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; }
|
|
3266
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; }
|
|
3267
3569
|
.wf-store-card-author { font-size: 11px; color: var(--text-muted); margin-bottom: 10px; }
|
|
3268
3570
|
.wf-store-card-meta { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
|
|
@@ -3283,11 +3585,14 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
3283
3585
|
@keyframes wfStoreDetailSlideIn { from { transform: translateY(20px); opacity: 0; } }
|
|
3284
3586
|
.wf-store-detail-panel {
|
|
3285
3587
|
background: var(--bg-surface); border: 1px solid var(--border); border-radius: 18px;
|
|
3286
|
-
width: 100%; max-width: 580px; max-height: 82vh; overflow
|
|
3287
|
-
animation: wfStoreDetailSlideIn 0.18s ease-out;
|
|
3588
|
+
width: 100%; max-width: 580px; max-height: 82vh; overflow: hidden;
|
|
3589
|
+
animation: wfStoreDetailSlideIn 0.18s ease-out; display: flex; flex-direction: column;
|
|
3288
3590
|
}
|
|
3289
|
-
.wf-store-detail-
|
|
3290
|
-
|
|
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; }
|
|
3291
3596
|
.wf-store-detail-hero { padding: 28px 28px 20px; border-radius: 18px 18px 0 0; position: relative; }
|
|
3292
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; }
|
|
3293
3598
|
.wf-store-detail-hero-inner { position: relative; z-index: 1; display: flex; align-items: center; gap: 14px; }
|
|
@@ -3633,38 +3938,76 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
3633
3938
|
.wf-edge--else { stroke-dasharray: 6 4; opacity: 0.4; }
|
|
3634
3939
|
.wf-edge--skipped { opacity: 0.12; stroke-dasharray: 4 4; }
|
|
3635
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) ── */
|
|
3636
3990
|
.wf-inspector {
|
|
3637
3991
|
flex-shrink: 0; position: relative;
|
|
3638
3992
|
display: flex; flex-direction: row;
|
|
3639
3993
|
background: var(--bg);
|
|
3640
|
-
transition: width 0.
|
|
3994
|
+
transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
3641
3995
|
width: 300px;
|
|
3642
3996
|
overflow: hidden;
|
|
3643
3997
|
align-self: stretch;
|
|
3998
|
+
border-left: 1px solid var(--border);
|
|
3644
3999
|
}
|
|
3645
4000
|
.wf-inspector.collapsed {
|
|
3646
|
-
width:
|
|
4001
|
+
width: 0; border-left: none;
|
|
3647
4002
|
}
|
|
3648
4003
|
.wf-inspector-toggle {
|
|
3649
|
-
|
|
3650
|
-
border: none; border-left: 1px solid var(--border);
|
|
3651
|
-
background: var(--bg); color: var(--text-muted);
|
|
3652
|
-
cursor: pointer; font-size: 14px; padding: 0;
|
|
3653
|
-
display: flex; align-items: center; justify-content: center;
|
|
3654
|
-
transition: color 0.15s, background 0.15s;
|
|
3655
|
-
}
|
|
3656
|
-
.wf-inspector-toggle:hover {
|
|
3657
|
-
color: var(--text); background: var(--bg-card);
|
|
3658
|
-
}
|
|
3659
|
-
.wf-inspector.collapsed .wf-inspector-toggle {
|
|
3660
|
-
border-left: 1px solid var(--border);
|
|
4004
|
+
display: none; /* replaced by edge handle */
|
|
3661
4005
|
}
|
|
3662
4006
|
.wf-inspector.collapsed .wf-inspector-content {
|
|
3663
4007
|
display: none;
|
|
3664
4008
|
}
|
|
3665
4009
|
.wf-inspector-content {
|
|
3666
4010
|
flex: 1; display: flex; flex-direction: column;
|
|
3667
|
-
border-left: 1px solid var(--border);
|
|
3668
4011
|
overflow-y: auto; min-width: 0; height: 100%;
|
|
3669
4012
|
}
|
|
3670
4013
|
.wf-inspector-header {
|
|
@@ -3731,11 +4074,11 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
3731
4074
|
.wf-inspector-result.error { border-color: #e74c3c; }
|
|
3732
4075
|
/* Responsive */
|
|
3733
4076
|
@media (max-width: 900px) {
|
|
3734
|
-
.wf-library {
|
|
4077
|
+
.wf-library { width: 0; border-right: none; }
|
|
3735
4078
|
.wf-inspector:not(.collapsed) { width: 240px; }
|
|
3736
4079
|
}
|
|
3737
4080
|
@media (max-width: 600px) {
|
|
3738
|
-
.wf-inspector {
|
|
4081
|
+
.wf-inspector { width: 0; border-left: none; }
|
|
3739
4082
|
}
|
|
3740
4083
|
/* Workflow visualizer light mode */
|
|
3741
4084
|
[data-theme="light"] .wf-node-label { fill: #001E2B; }
|
|
@@ -3913,18 +4256,25 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
3913
4256
|
border-radius: 16px;
|
|
3914
4257
|
background: linear-gradient(135deg, var(--bg-card), var(--bg-surface));
|
|
3915
4258
|
border: 1px solid var(--border);
|
|
4259
|
+
min-height: 180px;
|
|
3916
4260
|
}
|
|
3917
4261
|
|
|
3918
4262
|
.home-announcement-card {
|
|
3919
|
-
|
|
4263
|
+
position: absolute;
|
|
4264
|
+
top: 0;
|
|
4265
|
+
left: 0;
|
|
4266
|
+
right: 0;
|
|
3920
4267
|
padding: 32px;
|
|
3921
4268
|
text-align: center;
|
|
3922
|
-
|
|
4269
|
+
opacity: 0;
|
|
4270
|
+
pointer-events: none;
|
|
4271
|
+
transition: opacity 0.5s ease-in-out;
|
|
3923
4272
|
}
|
|
3924
4273
|
|
|
3925
4274
|
.home-announcement-card.active {
|
|
3926
|
-
|
|
3927
|
-
|
|
4275
|
+
position: relative;
|
|
4276
|
+
opacity: 1;
|
|
4277
|
+
pointer-events: auto;
|
|
3928
4278
|
}
|
|
3929
4279
|
|
|
3930
4280
|
.home-announcement-card .badge {
|
|
@@ -4010,6 +4360,72 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
4010
4360
|
background: linear-gradient(135deg, #00ED64, #00C2FF);
|
|
4011
4361
|
}
|
|
4012
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
|
+
|
|
4013
4429
|
/* Section Headers */
|
|
4014
4430
|
.home-section {
|
|
4015
4431
|
margin-bottom: 48px;
|
|
@@ -4520,6 +4936,42 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
4520
4936
|
<symbol id="lg-pulse" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
4521
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"/>
|
|
4522
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>
|
|
4523
4975
|
</svg>
|
|
4524
4976
|
|
|
4525
4977
|
<div class="app-shell">
|
|
@@ -4529,26 +4981,27 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
4529
4981
|
<div class="sidebar-drag-region">
|
|
4530
4982
|
<img class="sidebar-logo" id="sidebarLogo" src="/icons/dark/64.png" alt="Vai">
|
|
4531
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>
|
|
4532
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>
|
|
4533
4986
|
</div>
|
|
4534
4987
|
<nav class="sidebar-nav">
|
|
4535
4988
|
<div class="sidebar-nav-group" role="tablist" aria-label="Tools">
|
|
4536
4989
|
<div class="sidebar-nav-label" id="nav-tools-label">Tools</div>
|
|
4537
|
-
<button class="tab-btn active" data-tab="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>
|
|
4538
|
-
<button class="tab-btn" data-tab="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>
|
|
4539
|
-
<button class="tab-btn" data-tab="compare" role="tab" aria-selected="false" aria-controls="tab-compare" id="tab-btn-compare"><span class="tab-btn-icon" aria-hidden="true"><svg><use href="#lg-arrows"/></svg></span><span>Compare</span></button>
|
|
4540
|
-
<button class="tab-btn" data-tab="search" role="tab" aria-selected="false" aria-controls="tab-search" id="tab-btn-search"><span class="tab-btn-icon" aria-hidden="true"><svg><use href="#lg-search"/></svg></span><span>Search</span></button>
|
|
4541
|
-
<button class="tab-btn" data-tab="multimodal" role="tab" aria-selected="false" aria-controls="tab-multimodal" id="tab-btn-multimodal"><span class="tab-btn-icon" aria-hidden="true"><svg><use href="#lg-image"/></svg></span><span>Multimodal</span></button>
|
|
4542
|
-
<button class="tab-btn" data-tab="generate" role="tab" aria-selected="false" aria-controls="tab-generate" id="tab-btn-generate"><span class="tab-btn-icon" aria-hidden="true"><svg><use href="#lg-code"/></svg></span><span>Generate</span></button>
|
|
4543
|
-
<button class="tab-btn" data-tab="chat" role="tab" aria-selected="false" aria-controls="tab-chat" id="tab-btn-chat"><span class="tab-btn-icon" aria-hidden="true"><svg><use href="#lg-chat"/></svg></span><span>Chat</span></button>
|
|
4544
|
-
<button class="tab-btn" data-tab="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>
|
|
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>
|
|
4545
4998
|
</div>
|
|
4546
4999
|
<div class="sidebar-nav-divider"></div>
|
|
4547
5000
|
<div class="sidebar-nav-group" role="tablist" aria-label="Learn">
|
|
4548
5001
|
<div class="sidebar-nav-label" id="nav-learn-label">Learn</div>
|
|
4549
|
-
<button class="tab-btn" data-tab="benchmark" role="tab" aria-selected="false" aria-controls="tab-benchmark" id="tab-btn-benchmark"><span class="tab-btn-icon" aria-hidden="true"><svg><use href="#lg-gauge"/></svg></span><span>Benchmark</span></button>
|
|
4550
|
-
<button class="tab-btn" data-tab="explore" role="tab" aria-selected="false" aria-controls="tab-explore" id="tab-btn-explore"><span class="tab-btn-icon" aria-hidden="true"><svg><use href="#lg-bulb"/></svg></span><span>Explore</span></button>
|
|
4551
|
-
<button class="tab-btn" data-tab="about" role="tab" aria-selected="false" aria-controls="tab-about" id="tab-btn-about"><span class="tab-btn-icon" aria-hidden="true"><svg><use href="#lg-info"/></svg></span><span>About</span></button>
|
|
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>
|
|
4552
5005
|
</div>
|
|
4553
5006
|
</nav>
|
|
4554
5007
|
<div class="sidebar-footer">
|
|
@@ -4632,6 +5085,9 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
4632
5085
|
<div class="home-announcements-dots" id="announcementsDots">
|
|
4633
5086
|
<!-- Dots will be inserted here -->
|
|
4634
5087
|
</div>
|
|
5088
|
+
<div class="home-announcements-restore" id="announcementsRestore" style="display: none;">
|
|
5089
|
+
<button onclick="restoreAnnouncements()">Show dismissed announcements</button>
|
|
5090
|
+
</div>
|
|
4635
5091
|
</div>
|
|
4636
5092
|
|
|
4637
5093
|
<!-- What's New (Release Notes) -->
|
|
@@ -5560,7 +6016,7 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
5560
6016
|
<!-- ========== WORKFLOWS TAB ========== -->
|
|
5561
6017
|
<div class="tab-panel" id="tab-workflows" role="tabpanel" aria-labelledby="tab-btn-workflows" tabindex="0">
|
|
5562
6018
|
<div class="wf-container">
|
|
5563
|
-
<div class="wf-library">
|
|
6019
|
+
<div class="wf-library" id="wfLibrary">
|
|
5564
6020
|
<div class="wf-library-header">
|
|
5565
6021
|
<div class="wf-library-tabs" style="flex:1;">
|
|
5566
6022
|
<button class="wf-lib-tab active" data-lib-tab="library" onclick="wfSwitchLibTab('library')">Library</button>
|
|
@@ -5569,6 +6025,11 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
5569
6025
|
<button class="wf-store-btn" id="wfStoreBtn" onclick="wfStoreOpen()" title="Browse Workflow Store">
|
|
5570
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>
|
|
5571
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>
|
|
5572
6033
|
</div>
|
|
5573
6034
|
<div class="wf-library-list" id="wfLibraryList">
|
|
5574
6035
|
<div style="padding: 16px; color: var(--text-muted); font-size: 12px;">Loading...</div>
|
|
@@ -5599,7 +6060,15 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
5599
6060
|
<button onclick="wfExportJson()" id="wfExportBtn" disabled title="Export workflow JSON">
|
|
5600
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>
|
|
5601
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>
|
|
5602
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>
|
|
5603
6072
|
<!-- Execution status bar -->
|
|
5604
6073
|
<div class="wf-exec-status" id="wfExecStatus" style="display:none;">
|
|
5605
6074
|
<span class="wf-exec-status-dot"></span>
|
|
@@ -5638,7 +6107,6 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
5638
6107
|
</div>
|
|
5639
6108
|
</div>
|
|
5640
6109
|
<div class="wf-inspector collapsed" id="wfInspector">
|
|
5641
|
-
<button class="wf-inspector-toggle" id="wfInspectorToggle" onclick="wfToggleInspector()" title="Toggle inspector">‹</button>
|
|
5642
6110
|
<div class="wf-inspector-content">
|
|
5643
6111
|
<div class="wf-inspector-header" id="wfInspectorHeader">Inspector</div>
|
|
5644
6112
|
<div class="wf-inspector-body" id="wfInspectorBody">
|
|
@@ -5666,6 +6134,14 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
5666
6134
|
</div>
|
|
5667
6135
|
</div>
|
|
5668
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
|
+
|
|
5669
6145
|
<!-- ── Workflow Input Modal (pre-execution) ── -->
|
|
5670
6146
|
<div class="wf-input-modal-backdrop" id="wfInputModalBackdrop" style="display:none;" onclick="wfCloseInputModal()">
|
|
5671
6147
|
<div class="wf-input-modal" onclick="event.stopPropagation()">
|
|
@@ -6581,6 +7057,17 @@ function sendTelemetry(event, extra = {}) {
|
|
|
6581
7057
|
} catch { /* telemetry should never break the app */ }
|
|
6582
7058
|
}
|
|
6583
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
|
+
|
|
6584
7071
|
function initTelemetryToggle() {
|
|
6585
7072
|
const toggle = document.getElementById('settingsTelemetry');
|
|
6586
7073
|
if (!toggle) return;
|
|
@@ -6613,6 +7100,7 @@ async function init() {
|
|
|
6613
7100
|
// Telemetry
|
|
6614
7101
|
initTelemetryToggle();
|
|
6615
7102
|
sendTelemetry('app_launch');
|
|
7103
|
+
sendTelemetry('playground_open');
|
|
6616
7104
|
}
|
|
6617
7105
|
|
|
6618
7106
|
// ── Tabs ──
|
|
@@ -6679,6 +7167,7 @@ function switchTab(tab) {
|
|
|
6679
7167
|
if (settingsBtn) settingsBtn.classList.toggle('active', tab === 'settings');
|
|
6680
7168
|
// Track tab views
|
|
6681
7169
|
sendTelemetry('tab_view', { tab });
|
|
7170
|
+
sendTelemetry('playground_tab', { tab });
|
|
6682
7171
|
|
|
6683
7172
|
// Initialize Home page if switching to it
|
|
6684
7173
|
if (tab === 'home') {
|
|
@@ -6789,6 +7278,9 @@ async function loadAnnouncements() {
|
|
|
6789
7278
|
const res = await fetch('/api/home/announcements');
|
|
6790
7279
|
const data = await res.json();
|
|
6791
7280
|
|
|
7281
|
+
// Store total count before filtering
|
|
7282
|
+
homeData.totalAnnouncements = data.announcements.length;
|
|
7283
|
+
|
|
6792
7284
|
// Filter out dismissed announcements
|
|
6793
7285
|
const dismissed = JSON.parse(localStorage.getItem('vai-dismissed-announcements') || '[]');
|
|
6794
7286
|
homeData.announcements = data.announcements.filter(a => !dismissed.includes(a.id));
|
|
@@ -6796,6 +7288,7 @@ async function loadAnnouncements() {
|
|
|
6796
7288
|
} catch (err) {
|
|
6797
7289
|
console.error('Failed to load announcements:', err);
|
|
6798
7290
|
homeData.announcements = [];
|
|
7291
|
+
homeData.totalAnnouncements = 0;
|
|
6799
7292
|
}
|
|
6800
7293
|
}
|
|
6801
7294
|
|
|
@@ -6855,22 +7348,41 @@ function renderAnnouncements() {
|
|
|
6855
7348
|
const container = document.getElementById('homeAnnouncements');
|
|
6856
7349
|
const carousel = document.getElementById('announcementsCarousel');
|
|
6857
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';
|
|
6858
7356
|
|
|
6859
7357
|
if (!homeData.announcements.length) {
|
|
6860
|
-
container.style.display = 'none';
|
|
7358
|
+
container.style.display = hasDismissed ? 'block' : 'none';
|
|
7359
|
+
carousel.innerHTML = '';
|
|
7360
|
+
dots.innerHTML = '';
|
|
6861
7361
|
return;
|
|
6862
7362
|
}
|
|
6863
7363
|
|
|
6864
7364
|
// Render cards
|
|
6865
|
-
carousel.innerHTML = homeData.announcements.map((ann, i) =>
|
|
6866
|
-
|
|
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}">
|
|
6867
7378
|
<button class="home-announcement-dismiss" onclick="dismissAnnouncement('${ann.id}')">×</button>
|
|
7379
|
+
${ann.icon ? `<div class="home-announcement-icon">${ann.icon}</div>` : ''}
|
|
6868
7380
|
${ann.badge ? `<div class="badge">${ann.badge}</div>` : ''}
|
|
6869
7381
|
<h3>${ann.title}</h3>
|
|
6870
7382
|
<p>${ann.description}</p>
|
|
6871
7383
|
${ann.cta ? `<button class="cta" onclick="${ann.cta.action === 'navigate' ? `switchTab('${ann.cta.target.slice(1)}')` : 'void(0)'}">${ann.cta.label}</button>` : ''}
|
|
6872
|
-
</div
|
|
6873
|
-
|
|
7384
|
+
</div>`;
|
|
7385
|
+
}).join('');
|
|
6874
7386
|
|
|
6875
7387
|
// Render dots
|
|
6876
7388
|
if (homeData.announcements.length > 1) {
|
|
@@ -6890,22 +7402,63 @@ function showAnnouncement(index) {
|
|
|
6890
7402
|
|
|
6891
7403
|
// Update cards
|
|
6892
7404
|
document.querySelectorAll('.home-announcement-card').forEach((card, i) => {
|
|
6893
|
-
|
|
7405
|
+
if (i === index) {
|
|
7406
|
+
card.classList.add('active');
|
|
7407
|
+
} else {
|
|
7408
|
+
card.classList.remove('active');
|
|
7409
|
+
}
|
|
6894
7410
|
});
|
|
6895
7411
|
|
|
6896
7412
|
// Update dots
|
|
6897
7413
|
document.querySelectorAll('.home-announcement-dot').forEach((dot, i) => {
|
|
6898
|
-
|
|
7414
|
+
if (i === index) {
|
|
7415
|
+
dot.classList.add('active');
|
|
7416
|
+
} else {
|
|
7417
|
+
dot.classList.remove('active');
|
|
7418
|
+
}
|
|
6899
7419
|
});
|
|
7420
|
+
|
|
7421
|
+
// Reset the rotation timer so we get a full interval after manual clicks
|
|
7422
|
+
resetAnnouncementRotation();
|
|
6900
7423
|
}
|
|
6901
7424
|
|
|
7425
|
+
let announcementRotationTimer = null;
|
|
7426
|
+
|
|
6902
7427
|
function startAnnouncementRotation() {
|
|
6903
|
-
|
|
6904
|
-
|
|
6905
|
-
|
|
6906
|
-
|
|
6907
|
-
|
|
6908
|
-
|
|
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();
|
|
6909
7462
|
}
|
|
6910
7463
|
|
|
6911
7464
|
function dismissAnnouncement(id) {
|
|
@@ -6918,6 +7471,19 @@ function dismissAnnouncement(id) {
|
|
|
6918
7471
|
renderAnnouncements();
|
|
6919
7472
|
}
|
|
6920
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
|
+
|
|
6921
7487
|
function renderReleases() {
|
|
6922
7488
|
const container = document.getElementById('releasesTimeline');
|
|
6923
7489
|
|
|
@@ -7174,11 +7740,13 @@ window.doEmbed = async function() {
|
|
|
7174
7740
|
hideError('embedError');
|
|
7175
7741
|
const text = document.getElementById('embedInput').value.trim();
|
|
7176
7742
|
if (!text) { showError('embedError', 'Enter some text to embed'); return; }
|
|
7177
|
-
|
|
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 });
|
|
7178
7746
|
|
|
7179
7747
|
setLoading('embedBtn', true);
|
|
7180
7748
|
try {
|
|
7181
|
-
const model =
|
|
7749
|
+
const model = _embedModel;
|
|
7182
7750
|
const inputType = document.getElementById('embedInputType').value || undefined;
|
|
7183
7751
|
const dims = document.getElementById('embedDimensions').value;
|
|
7184
7752
|
const dimensions = dims ? parseInt(dims, 10) : undefined;
|
|
@@ -7216,6 +7784,8 @@ window.doEmbed = async function() {
|
|
|
7216
7784
|
buildHeatmap(emb, document.getElementById('embedHeatmap'));
|
|
7217
7785
|
|
|
7218
7786
|
document.getElementById('embedResult').classList.add('visible');
|
|
7787
|
+
CostTracker.addOperation('embed', model, data.usage?.total_tokens || 0);
|
|
7788
|
+
_embedDone();
|
|
7219
7789
|
} catch (err) {
|
|
7220
7790
|
showError('embedError', err.message);
|
|
7221
7791
|
} finally {
|
|
@@ -7258,6 +7828,7 @@ function buildHeatmap(vec, container) {
|
|
|
7258
7828
|
window.doCompare = async function() {
|
|
7259
7829
|
hideError('compareError');
|
|
7260
7830
|
sendTelemetry('api_call', { endpoint: 'compare', model: document.getElementById('compareModel').value });
|
|
7831
|
+
const _compareDone = telemetryTimer('playground_similarity', { model: document.getElementById('compareModel').value });
|
|
7261
7832
|
const a = document.getElementById('compareA').value.trim();
|
|
7262
7833
|
const b = document.getElementById('compareB').value.trim();
|
|
7263
7834
|
if (!a || !b) { showError('compareError', 'Enter text in both fields'); return; }
|
|
@@ -7340,6 +7911,8 @@ window.doCompare = async function() {
|
|
|
7340
7911
|
`;
|
|
7341
7912
|
|
|
7342
7913
|
document.getElementById('compareResult').classList.add('visible');
|
|
7914
|
+
CostTracker.addOperation('compare', data.model || model, data.usage?.total_tokens || 0);
|
|
7915
|
+
_compareDone();
|
|
7343
7916
|
} catch (err) {
|
|
7344
7917
|
showError('compareError', err.message);
|
|
7345
7918
|
} finally {
|
|
@@ -7351,6 +7924,7 @@ window.doCompare = async function() {
|
|
|
7351
7924
|
window.doSearch = async function(withRerank) {
|
|
7352
7925
|
hideError('searchError');
|
|
7353
7926
|
sendTelemetry('api_call', { endpoint: withRerank ? 'rerank' : 'search' });
|
|
7927
|
+
const _searchDone = telemetryTimer(withRerank ? 'playground_rerank' : 'playground_search', { model: document.getElementById('searchEmbedModel').value });
|
|
7354
7928
|
const query = document.getElementById('searchQuery').value.trim();
|
|
7355
7929
|
const docsText = document.getElementById('searchDocs').value.trim();
|
|
7356
7930
|
if (!query || !docsText) { showError('searchError', 'Enter a query and documents'); return; }
|
|
@@ -7383,9 +7957,10 @@ window.doSearch = async function(withRerank) {
|
|
|
7383
7957
|
const embeddingResults = scores.slice(0, topK);
|
|
7384
7958
|
|
|
7385
7959
|
let rerankResults = null;
|
|
7960
|
+
let rerankData = null;
|
|
7386
7961
|
if (withRerank) {
|
|
7387
7962
|
const rerankModel = document.getElementById('searchRerankModel').value;
|
|
7388
|
-
|
|
7963
|
+
rerankData = await apiPost('/api/rerank', { query, documents, model: rerankModel, topK });
|
|
7389
7964
|
rerankResults = rerankData.data.map(r => ({
|
|
7390
7965
|
index: r.index,
|
|
7391
7966
|
text: documents[r.index],
|
|
@@ -7395,6 +7970,12 @@ window.doSearch = async function(withRerank) {
|
|
|
7395
7970
|
|
|
7396
7971
|
renderSearchResults(embeddingResults, rerankResults);
|
|
7397
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();
|
|
7398
7979
|
} catch (err) {
|
|
7399
7980
|
showError('searchError', err.message);
|
|
7400
7981
|
} finally {
|
|
@@ -8048,6 +8629,7 @@ window.doBenchLatency = async function() {
|
|
|
8048
8629
|
latencies.push(data.elapsed);
|
|
8049
8630
|
tokens = data.tokens;
|
|
8050
8631
|
dims = data.dimensions;
|
|
8632
|
+
CostTracker.addOperation('bench-latency', model, data.tokens || 0);
|
|
8051
8633
|
} catch (err) {
|
|
8052
8634
|
document.getElementById(`bench-stats-${mi}`).textContent = 'Error';
|
|
8053
8635
|
document.getElementById(`bench-bar-${mi}`).classList.remove('running');
|
|
@@ -8141,6 +8723,8 @@ window.doBenchRanking = async function() {
|
|
|
8141
8723
|
|
|
8142
8724
|
rankedA = rankBySimilarity(dataA.embeddings, documents, topK);
|
|
8143
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);
|
|
8144
8728
|
} else {
|
|
8145
8729
|
// Rerank mode
|
|
8146
8730
|
const [dataA, dataB] = await Promise.all([
|
|
@@ -8158,6 +8742,8 @@ window.doBenchRanking = async function() {
|
|
|
8158
8742
|
text: documents[r.index],
|
|
8159
8743
|
score: r.relevance_score,
|
|
8160
8744
|
}));
|
|
8745
|
+
CostTracker.addOperation('bench-ranking', modelA, dataA.usage?.total_tokens || 0);
|
|
8746
|
+
CostTracker.addOperation('bench-ranking', modelB, dataB.usage?.total_tokens || 0);
|
|
8161
8747
|
}
|
|
8162
8748
|
|
|
8163
8749
|
// Render comparison
|
|
@@ -8298,6 +8884,7 @@ window.doBenchQuantization = async function() {
|
|
|
8298
8884
|
const start = performance.now();
|
|
8299
8885
|
const data = await apiPost('/api/embed', body);
|
|
8300
8886
|
const elapsed = performance.now() - start;
|
|
8887
|
+
CostTracker.addOperation('bench-quantization', model, data.usage?.total_tokens || 0);
|
|
8301
8888
|
|
|
8302
8889
|
const embeddings = data.data.map(d => d.embedding);
|
|
8303
8890
|
const queryEmbed = embeddings[0];
|
|
@@ -9859,6 +10446,7 @@ window.doMultimodalCompare = async function() {
|
|
|
9859
10446
|
}
|
|
9860
10447
|
|
|
9861
10448
|
document.getElementById('mmResult').classList.add('visible');
|
|
10449
|
+
CostTracker.addOperation('multimodal-compare', data.model || model, usage.total_tokens || 0);
|
|
9862
10450
|
} catch (err) {
|
|
9863
10451
|
showError('mmError', err.message);
|
|
9864
10452
|
} finally {
|
|
@@ -10062,6 +10650,7 @@ window.doMultimodalSearch = async function() {
|
|
|
10062
10650
|
});
|
|
10063
10651
|
|
|
10064
10652
|
document.getElementById('mmSearchResult').classList.add('visible');
|
|
10653
|
+
CostTracker.addOperation('multimodal-search', model, data.usage?.total_tokens || 0);
|
|
10065
10654
|
} catch (err) {
|
|
10066
10655
|
showError('mmSearchError', err.message);
|
|
10067
10656
|
} finally {
|
|
@@ -10960,20 +11549,24 @@ function renderMarkdown(md) {
|
|
|
10960
11549
|
/**
|
|
10961
11550
|
* Tool metadata: icon, label, and a function to summarize the call for the thinking panel.
|
|
10962
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
|
+
|
|
10963
11556
|
const TOOL_META = {
|
|
10964
|
-
vai_query: {
|
|
10965
|
-
vai_search: {
|
|
10966
|
-
vai_rerank: {
|
|
10967
|
-
vai_embed: {
|
|
10968
|
-
vai_similarity: {
|
|
10969
|
-
vai_collections: {
|
|
10970
|
-
vai_models: {
|
|
10971
|
-
vai_topics: {
|
|
10972
|
-
vai_explain: {
|
|
10973
|
-
vai_estimate: {
|
|
10974
|
-
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' },
|
|
10975
11568
|
};
|
|
10976
|
-
const DEFAULT_TOOL_META = {
|
|
11569
|
+
const DEFAULT_TOOL_META = { iconId: 'lg-config', label: '', verb: 'Running', descFn: () => '' };
|
|
10977
11570
|
|
|
10978
11571
|
/**
|
|
10979
11572
|
* Create the thinking panel <details> element.
|
|
@@ -10986,7 +11579,7 @@ function createThinkingPanel() {
|
|
|
10986
11579
|
|
|
10987
11580
|
const summary = document.createElement('summary');
|
|
10988
11581
|
summary.innerHTML =
|
|
10989
|
-
'<span class="thinking-icon"
|
|
11582
|
+
'<span class="thinking-icon">' + lucideIcon('lg-brain', 14) + '</span>' +
|
|
10990
11583
|
'<span class="thinking-label">Thinking</span>' +
|
|
10991
11584
|
'<span class="thinking-count">0</span>' +
|
|
10992
11585
|
'<span class="thinking-elapsed"></span>' +
|
|
@@ -11029,7 +11622,7 @@ function createThinkingPanel() {
|
|
|
11029
11622
|
|
|
11030
11623
|
const iconDiv = document.createElement('div');
|
|
11031
11624
|
iconDiv.className = 'thinking-step-icon';
|
|
11032
|
-
iconDiv.
|
|
11625
|
+
iconDiv.innerHTML = lucideIcon(meta.iconId, 14);
|
|
11033
11626
|
step.appendChild(iconDiv);
|
|
11034
11627
|
|
|
11035
11628
|
const body = document.createElement('div');
|
|
@@ -11084,7 +11677,7 @@ function createThinkingPanel() {
|
|
|
11084
11677
|
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
11085
11678
|
summary.querySelector('.thinking-elapsed').textContent = elapsed + 's';
|
|
11086
11679
|
summary.querySelector('.thinking-label').textContent = 'Thought for ' + elapsed + 's';
|
|
11087
|
-
summary.querySelector('.thinking-icon').
|
|
11680
|
+
summary.querySelector('.thinking-icon').innerHTML = lucideIcon('lg-sparkles', 14);
|
|
11088
11681
|
// Mark last step done and collapse
|
|
11089
11682
|
if (activeStep) {
|
|
11090
11683
|
activeStep.classList.remove('active');
|
|
@@ -11209,6 +11802,7 @@ async function sendChatMessage() {
|
|
|
11209
11802
|
let fullText = '';
|
|
11210
11803
|
let sources = [];
|
|
11211
11804
|
let thinkingPanel = null;
|
|
11805
|
+
let retrievalCostTracked = false;
|
|
11212
11806
|
|
|
11213
11807
|
while (true) {
|
|
11214
11808
|
const { done, value } = await reader.read();
|
|
@@ -11229,6 +11823,14 @@ async function sendChatMessage() {
|
|
|
11229
11823
|
|
|
11230
11824
|
if (currentEvent === 'retrieval') {
|
|
11231
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
|
+
}
|
|
11232
11834
|
}
|
|
11233
11835
|
|
|
11234
11836
|
if (currentEvent === 'tool_call') {
|
|
@@ -11253,6 +11855,24 @@ async function sendChatMessage() {
|
|
|
11253
11855
|
}
|
|
11254
11856
|
|
|
11255
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
|
+
}
|
|
11256
11876
|
// Finalize the thinking panel (collapse, show elapsed)
|
|
11257
11877
|
if (thinkingPanel) thinkingPanel.finalize();
|
|
11258
11878
|
// Render accumulated text as markdown for assistant messages
|
|
@@ -11459,6 +12079,57 @@ init();
|
|
|
11459
12079
|
.bug-success h3{margin:0 0 12px;color:var(--accent-text);font-size:20px}
|
|
11460
12080
|
.bug-success p{margin:8px 0;color:var(--text-muted)}
|
|
11461
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; }
|
|
11462
12133
|
</style>
|
|
11463
12134
|
|
|
11464
12135
|
<!-- Bug button moved to sidebar footer -->
|
|
@@ -11602,92 +12273,119 @@ async function wfLoadLibrary() {
|
|
|
11602
12273
|
}
|
|
11603
12274
|
}
|
|
11604
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
|
+
|
|
11605
12323
|
function wfRenderLibrary() {
|
|
11606
12324
|
const list = document.getElementById('wfLibraryList');
|
|
11607
12325
|
if (!list) return;
|
|
11608
|
-
|
|
11609
|
-
|
|
11610
|
-
|
|
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);
|
|
11611
12336
|
}
|
|
11612
12337
|
|
|
11613
|
-
|
|
11614
|
-
|
|
11615
|
-
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>` : '';
|
|
11616
12342
|
return `<div class="wf-library-item" data-wf-name="${w.name}" onclick="wfSelectWorkflow('${w.name}')">
|
|
11617
12343
|
<div class="wf-library-item-name">${displayName}</div>
|
|
11618
12344
|
<div class="wf-library-item-desc">${w.description || ''}</div>
|
|
12345
|
+
${authorLine}${tagLine}
|
|
11619
12346
|
</div>`;
|
|
11620
|
-
}
|
|
12347
|
+
}
|
|
11621
12348
|
|
|
11622
|
-
|
|
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 = '';
|
|
12368
|
+
|
|
12369
|
+
// Built-in
|
|
12370
|
+
html += renderSection('builtin', 'Built-in', wfState.workflows, '');
|
|
12371
|
+
|
|
12372
|
+
// Examples
|
|
11623
12373
|
const examples = wfState.examples || [];
|
|
11624
12374
|
if (examples.length > 0) {
|
|
11625
|
-
html +=
|
|
11626
|
-
<button class="wf-library-section-toggle" onclick="wfToggleExamples(this)">
|
|
11627
|
-
<span class="arrow">▶</span> Examples (${examples.length})
|
|
11628
|
-
</button>
|
|
11629
|
-
<div class="wf-examples-content" style="display:none;">`;
|
|
11630
|
-
|
|
11631
|
-
const categories = ['Retrieval', 'RAG', 'Ingestion', 'Analysis', 'Other'];
|
|
11632
|
-
for (const cat of categories) {
|
|
11633
|
-
const items = examples.filter(e => e.category === cat);
|
|
11634
|
-
if (items.length === 0) continue;
|
|
11635
|
-
html += `<div class="wf-library-category">${cat}</div>`;
|
|
11636
|
-
html += items.map(w => {
|
|
11637
|
-
const displayName = w.name.replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
|
|
11638
|
-
return `<div class="wf-library-item" data-wf-name="${w.name}" onclick="wfSelectWorkflow('${w.name}')">
|
|
11639
|
-
<div class="wf-library-item-name">${displayName}</div>
|
|
11640
|
-
<div class="wf-library-item-desc">${w.description || ''}</div>
|
|
11641
|
-
</div>`;
|
|
11642
|
-
}).join('');
|
|
11643
|
-
}
|
|
11644
|
-
html += '</div></div>';
|
|
12375
|
+
html += renderSection('examples', 'Examples', examples, '');
|
|
11645
12376
|
}
|
|
11646
12377
|
|
|
11647
|
-
// Official catalog
|
|
12378
|
+
// Official catalog
|
|
11648
12379
|
const official = wfState.official || [];
|
|
11649
|
-
html +=
|
|
11650
|
-
<div class="wf-library-category" style="display:flex;align-items:center;justify-content:space-between;">
|
|
11651
|
-
<span>Official Catalog (@vaicli) (${official.length})</span>
|
|
11652
|
-
</div>`;
|
|
11653
|
-
if (official.length === 0) {
|
|
11654
|
-
html += '<div style="padding:8px 12px;color:var(--text-muted);font-size:11px;">No official workflows installed.</div>';
|
|
11655
|
-
} else {
|
|
11656
|
-
html += official.map(w => {
|
|
11657
|
-
const displayName = w.name.replace(/^@vaicli\/vai-workflow-/, '').replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
|
|
11658
|
-
const authorLine = w.author ? `<span style="color:var(--text-muted);font-size:10px;">by ${w.author}${w.version ? ' · v' + w.version : ''}</span>` : '';
|
|
11659
|
-
const tagLine = (w.tags || []).length ? `<div style="margin-top:2px;font-size:10px;color:var(--text-muted);">${w.tags.join(' · ')}</div>` : '';
|
|
11660
|
-
return `<div class="wf-library-item" data-wf-name="${w.name}" onclick="wfSelectWorkflow('${w.name}')">
|
|
11661
|
-
<div class="wf-library-item-name">✓ ${displayName}</div>
|
|
11662
|
-
<div class="wf-library-item-desc">${w.description || ''}</div>
|
|
11663
|
-
${authorLine}${tagLine}
|
|
11664
|
-
</div>`;
|
|
11665
|
-
}).join('');
|
|
11666
|
-
}
|
|
11667
|
-
html += '</div>';
|
|
12380
|
+
html += renderSection('catalog', 'Official Catalog (@vaicli)', official, '✓ ');
|
|
11668
12381
|
|
|
11669
|
-
// Community
|
|
12382
|
+
// Community
|
|
11670
12383
|
const community = wfState.community || [];
|
|
11671
|
-
html +=
|
|
11672
|
-
|
|
11673
|
-
|
|
11674
|
-
|
|
11675
|
-
</div>`;
|
|
11676
|
-
if (community.length === 0) {
|
|
11677
|
-
html += '<div style="padding:8px 12px;color:var(--text-muted);font-size:11px;">No community workflows installed.<br><code style="font-size:10px;">vai workflow install <name></code></div>';
|
|
11678
|
-
} else {
|
|
11679
|
-
html += community.map(w => {
|
|
11680
|
-
const displayName = w.name.replace(/^vai-workflow-/, '').replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
|
|
11681
|
-
const authorLine = w.author ? `<span style="color:var(--text-muted);font-size:10px;">by ${w.author}${w.version ? ' · v' + w.version : ''}</span>` : '';
|
|
11682
|
-
const tagLine = (w.tags || []).length ? `<div style="margin-top:2px;font-size:10px;color:var(--text-muted);">${w.tags.join(' · ')}</div>` : '';
|
|
11683
|
-
return `<div class="wf-library-item" data-wf-name="${w.name}" onclick="wfSelectWorkflow('${w.name}')">
|
|
11684
|
-
<div class="wf-library-item-name">🌐 ${displayName}</div>
|
|
11685
|
-
<div class="wf-library-item-desc">${w.description || ''}</div>
|
|
11686
|
-
${authorLine}${tagLine}
|
|
11687
|
-
</div>`;
|
|
11688
|
-
}).join('');
|
|
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>';
|
|
11689
12388
|
}
|
|
11690
|
-
html += '</div>';
|
|
11691
12389
|
|
|
11692
12390
|
// Install from npm button
|
|
11693
12391
|
html += `<div style="padding:8px 12px;">
|
|
@@ -12244,22 +12942,200 @@ function wfDrawEdge(fromId, toId, positions) {
|
|
|
12244
12942
|
return g;
|
|
12245
12943
|
}
|
|
12246
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
|
+
|
|
12247
13027
|
// ── Inspector Toggle ──
|
|
12248
13028
|
function wfToggleInspector() {
|
|
12249
13029
|
const panel = document.getElementById('wfInspector');
|
|
12250
|
-
const btn = document.getElementById('wfInspectorToggle');
|
|
12251
13030
|
if (!panel) return;
|
|
12252
13031
|
panel.classList.toggle('collapsed');
|
|
12253
|
-
|
|
13032
|
+
wfSyncPanelUI();
|
|
13033
|
+
wfSaveLayoutState();
|
|
12254
13034
|
}
|
|
12255
13035
|
|
|
12256
13036
|
function wfOpenInspector() {
|
|
12257
13037
|
const panel = document.getElementById('wfInspector');
|
|
12258
|
-
const btn = document.getElementById('wfInspectorToggle');
|
|
12259
13038
|
if (!panel || !panel.classList.contains('collapsed')) return;
|
|
12260
13039
|
panel.classList.remove('collapsed');
|
|
12261
|
-
|
|
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
|
+
}
|
|
12262
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
|
+
});
|
|
12263
13139
|
|
|
12264
13140
|
// ── Node Selection ──
|
|
12265
13141
|
function wfSelectNode(stepId) {
|
|
@@ -12321,56 +13197,83 @@ function wfUpdateInspector() {
|
|
|
12321
13197
|
<div style="font-size:10px;color:var(--text-muted);">Add steps from the Palette tab, then drag between ports to connect them.</div>
|
|
12322
13198
|
</div>`;
|
|
12323
13199
|
} else {
|
|
12324
|
-
// Read-only: Description
|
|
13200
|
+
// Read-only: Description (above accordion)
|
|
12325
13201
|
if (def.description) {
|
|
12326
13202
|
html += `<div class="wf-inspector-section">
|
|
12327
|
-
<div class="wf-inspector-section-title">Description</div>
|
|
12328
13203
|
<div style="font-size:12px;color:var(--text);line-height:1.4;">${escapeHtml(def.description)}</div>
|
|
12329
13204
|
</div>`;
|
|
12330
13205
|
}
|
|
12331
13206
|
|
|
12332
|
-
//
|
|
12333
|
-
|
|
12334
|
-
|
|
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) {
|
|
12335
13221
|
for (const [key, spec] of Object.entries(def.inputs)) {
|
|
12336
13222
|
const req = spec.required ? ' <span style="color:#e74c3c">*</span>' : '';
|
|
12337
|
-
|
|
12338
|
-
html += `<div style="margin-bottom:8px;">
|
|
13223
|
+
html += `<div style="margin-bottom:12px;">
|
|
12339
13224
|
<div style="font-size:12px;font-weight:600;color:var(--text);">${escapeHtml(key)}${req}</div>
|
|
12340
|
-
<div style="font-size:
|
|
12341
|
-
<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)) : ''}">
|
|
12342
13227
|
</div>`;
|
|
12343
13228
|
}
|
|
12344
|
-
|
|
13229
|
+
} else {
|
|
13230
|
+
html += '<div style="font-size:12px;color:var(--text-muted);">No inputs defined</div>';
|
|
12345
13231
|
}
|
|
12346
|
-
|
|
12347
|
-
|
|
12348
|
-
|
|
12349
|
-
|
|
12350
|
-
|
|
12351
|
-
|
|
12352
|
-
|
|
12353
|
-
|
|
12354
|
-
|
|
12355
|
-
|
|
12356
|
-
|
|
12357
|
-
|
|
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>
|
|
12358
13253
|
</div>`;
|
|
12359
13254
|
}
|
|
12360
|
-
|
|
13255
|
+
html += '</div></div>';
|
|
12361
13256
|
|
|
12362
|
-
|
|
12363
|
-
|
|
12364
|
-
|
|
12365
|
-
|
|
12366
|
-
|
|
12367
|
-
|
|
12368
|
-
|
|
12369
|
-
|
|
12370
|
-
|
|
12371
|
-
|
|
12372
|
-
|
|
12373
|
-
|
|
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>';
|
|
12374
13277
|
}
|
|
12375
13278
|
|
|
12376
13279
|
body.innerHTML = html;
|
|
@@ -12390,10 +13293,13 @@ function wfUpdateInspector() {
|
|
|
12390
13293
|
|
|
12391
13294
|
let html = '';
|
|
12392
13295
|
|
|
12393
|
-
// Tool badge (always shown)
|
|
13296
|
+
// Tool badge (always shown) with help button
|
|
12394
13297
|
html += `<div class="wf-inspector-section">
|
|
12395
13298
|
<div class="wf-inspector-section-title">Tool</div>
|
|
12396
|
-
<
|
|
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>
|
|
12397
13303
|
</div>`;
|
|
12398
13304
|
|
|
12399
13305
|
if (wfState.builderMode) {
|
|
@@ -13051,9 +13957,105 @@ function wfCopyOutput() {
|
|
|
13051
13957
|
});
|
|
13052
13958
|
}
|
|
13053
13959
|
|
|
13054
|
-
//
|
|
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
|
|
13055
14051
|
document.addEventListener('keydown', (e) => {
|
|
13056
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
|
+
}
|
|
13057
14059
|
const backdrop = document.getElementById('wfOutputModalBackdrop');
|
|
13058
14060
|
if (backdrop && backdrop.style.display !== 'none') {
|
|
13059
14061
|
wfCloseOutputModal();
|
|
@@ -13222,6 +14224,7 @@ function wfRenderPalette() {
|
|
|
13222
14224
|
html += `<div class="wf-palette-item" draggable="true" ondragstart="event.dataTransfer.setData('text/plain','${item.tool}')" onclick="wfAddNodeFromPalette('${item.tool}')">
|
|
13223
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>
|
|
13224
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>
|
|
13225
14228
|
</div>`;
|
|
13226
14229
|
}
|
|
13227
14230
|
html += '</div>';
|
|
@@ -13728,7 +14731,25 @@ const _WF_FALLBACK_BRANDING = {
|
|
|
13728
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' },
|
|
13729
14732
|
'index-health-check': { icon:'bar-chart-3', color:'#1D4ED8', iconPath:'M12 20V10M18 20V4M6 20v-4' },
|
|
13730
14733
|
};
|
|
13731
|
-
WF_STORE_FALLBACK.forEach(wf => {
|
|
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 = {};
|
|
13732
14753
|
|
|
13733
14754
|
let _wfStoreCatalog = null;
|
|
13734
14755
|
let _wfStoreCat = 'all';
|
|
@@ -13833,8 +14854,13 @@ function wfStoreRender() {
|
|
|
13833
14854
|
const dots = (wf.tools||[]).slice(0,5).map(t =>
|
|
13834
14855
|
`<div class="wf-store-card-dot" style="background:${WF_STORE_TOOL_COLORS[t]||'#666'}"></div>`
|
|
13835
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>` : '');
|
|
13836
|
-
const badges = (wf.
|
|
14857
|
+
const badges = (wf.verified ? '<span class="wf-store-badge-verified">✓ VERIFIED</span>' : '') +
|
|
14858
|
+
(wf.installed ? '<span class="wf-store-badge-installed">installed</span>' : '') +
|
|
13837
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
|
+
: '';
|
|
13838
14864
|
const cardIconColor = (wf.branding && wf.branding.color) || '#64748B';
|
|
13839
14865
|
const cardIconSvg = wfBrandingIcon(wf, 16);
|
|
13840
14866
|
const cardIconHtml = cardIconSvg
|
|
@@ -13848,6 +14874,7 @@ function wfStoreRender() {
|
|
|
13848
14874
|
</div>
|
|
13849
14875
|
<div class="wf-store-card-desc">${wf.description||''}</div>
|
|
13850
14876
|
${wf.author && wf.author.name && wf.author.name !== 'unknown' ? `<div class="wf-store-card-author">by ${wf.author.name}</div>` : ''}
|
|
14877
|
+
${capBadgesHtml}
|
|
13851
14878
|
<div class="wf-store-card-meta">
|
|
13852
14879
|
<span class="wf-store-card-cat">${wf.category||'utility'}</span>
|
|
13853
14880
|
<div class="wf-store-card-dots">${dots}</div>
|
|
@@ -13893,6 +14920,7 @@ function wfStoreShowDetail(name) {
|
|
|
13893
14920
|
modal.className = 'wf-store-detail-bg';
|
|
13894
14921
|
modal.onclick = (e) => { if (e.target === modal) modal.remove(); };
|
|
13895
14922
|
modal.innerHTML = `<div class="wf-store-detail-panel" onclick="event.stopPropagation()">
|
|
14923
|
+
<div class="wf-store-detail-scroll">
|
|
13896
14924
|
<div class="wf-store-detail-hero" style="background:${wf.gradient}">
|
|
13897
14925
|
<button class="wf-store-detail-close" onclick="document.getElementById('wfStoreDetail').remove()">×</button>
|
|
13898
14926
|
<div class="wf-store-detail-hero-inner">
|
|
@@ -13955,12 +14983,45 @@ function wfStoreShowDetail(name) {
|
|
|
13955
14983
|
<span>$ ${rc}</span><span class="wf-store-detail-cmd-hint">copy</span>
|
|
13956
14984
|
</div>
|
|
13957
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>
|
|
13958
15018
|
<div class="wf-store-detail-actions">
|
|
13959
15019
|
${installBtn}
|
|
13960
15020
|
<button class="wf-store-detail-btn wf-store-detail-btn-secondary" onclick="wfStoreRun('${wf.name}')">Run</button>
|
|
13961
15021
|
<button class="wf-store-detail-btn wf-store-detail-btn-secondary" onclick="wfStoreCanvas('${wf.name}')">Canvas</button>
|
|
13962
15022
|
</div>
|
|
13963
15023
|
</div>
|
|
15024
|
+
</div>
|
|
13964
15025
|
</div>`;
|
|
13965
15026
|
document.body.appendChild(modal);
|
|
13966
15027
|
}
|
|
@@ -14046,6 +15107,7 @@ document.addEventListener('keydown', (e) => {
|
|
|
14046
15107
|
});
|
|
14047
15108
|
|
|
14048
15109
|
function wfInit() {
|
|
15110
|
+
wfRestoreLayoutState();
|
|
14049
15111
|
wfLoadLibrary();
|
|
14050
15112
|
wfInitPan();
|
|
14051
15113
|
}
|
|
@@ -14177,5 +15239,241 @@ let wfInitialized = false;
|
|
|
14177
15239
|
})();
|
|
14178
15240
|
</script>
|
|
14179
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
|
+
|
|
14180
15478
|
</body>
|
|
14181
15479
|
</html>
|